cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

clk-spmi-pmic-div.c (7044B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
      3 */
      4
      5#include <linux/bitops.h>
      6#include <linux/clk.h>
      7#include <linux/clk-provider.h>
      8#include <linux/delay.h>
      9#include <linux/err.h>
     10#include <linux/log2.h>
     11#include <linux/module.h>
     12#include <linux/of.h>
     13#include <linux/platform_device.h>
     14#include <linux/regmap.h>
     15#include <linux/slab.h>
     16#include <linux/types.h>
     17
     18#define REG_DIV_CTL1			0x43
     19#define DIV_CTL1_DIV_FACTOR_MASK	GENMASK(2, 0)
     20
     21#define REG_EN_CTL			0x46
     22#define REG_EN_MASK			BIT(7)
     23
     24struct clkdiv {
     25	struct regmap		*regmap;
     26	u16			base;
     27	spinlock_t		lock;
     28
     29	struct clk_hw		hw;
     30	unsigned int		cxo_period_ns;
     31};
     32
     33static inline struct clkdiv *to_clkdiv(struct clk_hw *hw)
     34{
     35	return container_of(hw, struct clkdiv, hw);
     36}
     37
     38static inline unsigned int div_factor_to_div(unsigned int div_factor)
     39{
     40	if (!div_factor)
     41		div_factor = 1;
     42
     43	return 1 << (div_factor - 1);
     44}
     45
     46static inline unsigned int div_to_div_factor(unsigned int div)
     47{
     48	return min(ilog2(div) + 1, 7);
     49}
     50
     51static bool is_spmi_pmic_clkdiv_enabled(struct clkdiv *clkdiv)
     52{
     53	unsigned int val = 0;
     54
     55	regmap_read(clkdiv->regmap, clkdiv->base + REG_EN_CTL, &val);
     56
     57	return val & REG_EN_MASK;
     58}
     59
     60static int
     61__spmi_pmic_clkdiv_set_enable_state(struct clkdiv *clkdiv, bool enable,
     62				    unsigned int div_factor)
     63{
     64	int ret;
     65	unsigned int ns = clkdiv->cxo_period_ns;
     66	unsigned int div = div_factor_to_div(div_factor);
     67
     68	ret = regmap_update_bits(clkdiv->regmap, clkdiv->base + REG_EN_CTL,
     69				 REG_EN_MASK, enable ? REG_EN_MASK : 0);
     70	if (ret)
     71		return ret;
     72
     73	if (enable)
     74		ndelay((2 + 3 * div) * ns);
     75	else
     76		ndelay(3 * div * ns);
     77
     78	return 0;
     79}
     80
     81static int spmi_pmic_clkdiv_set_enable_state(struct clkdiv *clkdiv, bool enable)
     82{
     83	unsigned int div_factor;
     84
     85	regmap_read(clkdiv->regmap, clkdiv->base + REG_DIV_CTL1, &div_factor);
     86	div_factor &= DIV_CTL1_DIV_FACTOR_MASK;
     87
     88	return __spmi_pmic_clkdiv_set_enable_state(clkdiv, enable, div_factor);
     89}
     90
     91static int clk_spmi_pmic_div_enable(struct clk_hw *hw)
     92{
     93	struct clkdiv *clkdiv = to_clkdiv(hw);
     94	unsigned long flags;
     95	int ret;
     96
     97	spin_lock_irqsave(&clkdiv->lock, flags);
     98	ret = spmi_pmic_clkdiv_set_enable_state(clkdiv, true);
     99	spin_unlock_irqrestore(&clkdiv->lock, flags);
    100
    101	return ret;
    102}
    103
    104static void clk_spmi_pmic_div_disable(struct clk_hw *hw)
    105{
    106	struct clkdiv *clkdiv = to_clkdiv(hw);
    107	unsigned long flags;
    108
    109	spin_lock_irqsave(&clkdiv->lock, flags);
    110	spmi_pmic_clkdiv_set_enable_state(clkdiv, false);
    111	spin_unlock_irqrestore(&clkdiv->lock, flags);
    112}
    113
    114static long clk_spmi_pmic_div_round_rate(struct clk_hw *hw, unsigned long rate,
    115					 unsigned long *parent_rate)
    116{
    117	unsigned int div, div_factor;
    118
    119	div = DIV_ROUND_UP(*parent_rate, rate);
    120	div_factor = div_to_div_factor(div);
    121	div = div_factor_to_div(div_factor);
    122
    123	return *parent_rate / div;
    124}
    125
    126static unsigned long
    127clk_spmi_pmic_div_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
    128{
    129	struct clkdiv *clkdiv = to_clkdiv(hw);
    130	unsigned int div_factor;
    131
    132	regmap_read(clkdiv->regmap, clkdiv->base + REG_DIV_CTL1, &div_factor);
    133	div_factor &= DIV_CTL1_DIV_FACTOR_MASK;
    134
    135	return parent_rate / div_factor_to_div(div_factor);
    136}
    137
    138static int clk_spmi_pmic_div_set_rate(struct clk_hw *hw, unsigned long rate,
    139				      unsigned long parent_rate)
    140{
    141	struct clkdiv *clkdiv = to_clkdiv(hw);
    142	unsigned int div_factor = div_to_div_factor(parent_rate / rate);
    143	unsigned long flags;
    144	bool enabled;
    145	int ret;
    146
    147	spin_lock_irqsave(&clkdiv->lock, flags);
    148	enabled = is_spmi_pmic_clkdiv_enabled(clkdiv);
    149	if (enabled) {
    150		ret = spmi_pmic_clkdiv_set_enable_state(clkdiv, false);
    151		if (ret)
    152			goto unlock;
    153	}
    154
    155	ret = regmap_update_bits(clkdiv->regmap, clkdiv->base + REG_DIV_CTL1,
    156				 DIV_CTL1_DIV_FACTOR_MASK, div_factor);
    157	if (ret)
    158		goto unlock;
    159
    160	if (enabled)
    161		ret = __spmi_pmic_clkdiv_set_enable_state(clkdiv, true,
    162							  div_factor);
    163
    164unlock:
    165	spin_unlock_irqrestore(&clkdiv->lock, flags);
    166
    167	return ret;
    168}
    169
    170static const struct clk_ops clk_spmi_pmic_div_ops = {
    171	.enable = clk_spmi_pmic_div_enable,
    172	.disable = clk_spmi_pmic_div_disable,
    173	.set_rate = clk_spmi_pmic_div_set_rate,
    174	.recalc_rate = clk_spmi_pmic_div_recalc_rate,
    175	.round_rate = clk_spmi_pmic_div_round_rate,
    176};
    177
    178struct spmi_pmic_div_clk_cc {
    179	int		nclks;
    180	struct clkdiv	clks[];
    181};
    182
    183static struct clk_hw *
    184spmi_pmic_div_clk_hw_get(struct of_phandle_args *clkspec, void *data)
    185{
    186	struct spmi_pmic_div_clk_cc *cc = data;
    187	int idx = clkspec->args[0] - 1; /* Start at 1 instead of 0 */
    188
    189	if (idx < 0 || idx >= cc->nclks) {
    190		pr_err("%s: index value %u is invalid; allowed range [1, %d]\n",
    191		       __func__, clkspec->args[0], cc->nclks);
    192		return ERR_PTR(-EINVAL);
    193	}
    194
    195	return &cc->clks[idx].hw;
    196}
    197
    198static int spmi_pmic_clkdiv_probe(struct platform_device *pdev)
    199{
    200	struct spmi_pmic_div_clk_cc *cc;
    201	struct clk_init_data init = {};
    202	struct clkdiv *clkdiv;
    203	struct clk *cxo;
    204	struct regmap *regmap;
    205	struct device *dev = &pdev->dev;
    206	struct device_node *of_node = dev->of_node;
    207	const char *parent_name;
    208	int nclks, i, ret, cxo_hz;
    209	char name[20];
    210	u32 start;
    211
    212	ret = of_property_read_u32(of_node, "reg", &start);
    213	if (ret < 0) {
    214		dev_err(dev, "reg property reading failed\n");
    215		return ret;
    216	}
    217
    218	regmap = dev_get_regmap(dev->parent, NULL);
    219	if (!regmap) {
    220		dev_err(dev, "Couldn't get parent's regmap\n");
    221		return -EINVAL;
    222	}
    223
    224	ret = of_property_read_u32(of_node, "qcom,num-clkdivs", &nclks);
    225	if (ret < 0) {
    226		dev_err(dev, "qcom,num-clkdivs property reading failed, ret=%d\n",
    227			ret);
    228		return ret;
    229	}
    230
    231	if (!nclks)
    232		return -EINVAL;
    233
    234	cc = devm_kzalloc(dev, struct_size(cc, clks, nclks), GFP_KERNEL);
    235	if (!cc)
    236		return -ENOMEM;
    237	cc->nclks = nclks;
    238
    239	cxo = clk_get(dev, "xo");
    240	if (IS_ERR(cxo)) {
    241		ret = PTR_ERR(cxo);
    242		if (ret != -EPROBE_DEFER)
    243			dev_err(dev, "failed to get xo clock\n");
    244		return ret;
    245	}
    246	cxo_hz = clk_get_rate(cxo);
    247	clk_put(cxo);
    248
    249	parent_name = of_clk_get_parent_name(of_node, 0);
    250	if (!parent_name) {
    251		dev_err(dev, "missing parent clock\n");
    252		return -ENODEV;
    253	}
    254
    255	init.name = name;
    256	init.parent_names = &parent_name;
    257	init.num_parents = 1;
    258	init.ops = &clk_spmi_pmic_div_ops;
    259
    260	for (i = 0, clkdiv = cc->clks; i < nclks; i++) {
    261		snprintf(name, sizeof(name), "div_clk%d", i + 1);
    262
    263		spin_lock_init(&clkdiv[i].lock);
    264		clkdiv[i].base = start + i * 0x100;
    265		clkdiv[i].regmap = regmap;
    266		clkdiv[i].cxo_period_ns = NSEC_PER_SEC / cxo_hz;
    267		clkdiv[i].hw.init = &init;
    268
    269		ret = devm_clk_hw_register(dev, &clkdiv[i].hw);
    270		if (ret)
    271			return ret;
    272	}
    273
    274	return devm_of_clk_add_hw_provider(dev, spmi_pmic_div_clk_hw_get, cc);
    275}
    276
    277static const struct of_device_id spmi_pmic_clkdiv_match_table[] = {
    278	{ .compatible = "qcom,spmi-clkdiv" },
    279	{ /* sentinel */ }
    280};
    281MODULE_DEVICE_TABLE(of, spmi_pmic_clkdiv_match_table);
    282
    283static struct platform_driver spmi_pmic_clkdiv_driver = {
    284	.driver		= {
    285		.name	= "qcom,spmi-pmic-clkdiv",
    286		.of_match_table = spmi_pmic_clkdiv_match_table,
    287	},
    288	.probe		= spmi_pmic_clkdiv_probe,
    289};
    290module_platform_driver(spmi_pmic_clkdiv_driver);
    291
    292MODULE_DESCRIPTION("QCOM SPMI PMIC clkdiv driver");
    293MODULE_LICENSE("GPL v2");