xref: /openbmc/linux/drivers/clk/qcom/common.c (revision 060f35a317ef09101b128f399dce7ed13d019461)
1f9419783STaniya Das // SPDX-License-Identifier: GPL-2.0
249fc825fSStephen Boyd /*
349fc825fSStephen Boyd  * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
449fc825fSStephen Boyd  */
549fc825fSStephen Boyd 
649fc825fSStephen Boyd #include <linux/export.h>
7169f05e8SStephen Boyd #include <linux/module.h>
849fc825fSStephen Boyd #include <linux/regmap.h>
949fc825fSStephen Boyd #include <linux/platform_device.h>
1049fc825fSStephen Boyd #include <linux/clk-provider.h>
1149fc825fSStephen Boyd #include <linux/reset-controller.h>
12ee15faffSStephen Boyd #include <linux/of.h>
1349fc825fSStephen Boyd 
1449fc825fSStephen Boyd #include "common.h"
1550c6a503SStephen Boyd #include "clk-rcg.h"
1649fc825fSStephen Boyd #include "clk-regmap.h"
1749fc825fSStephen Boyd #include "reset.h"
185e5cc241SRajendra Nayak #include "gdsc.h"
1949fc825fSStephen Boyd 
2049fc825fSStephen Boyd struct qcom_cc {
2149fc825fSStephen Boyd 	struct qcom_reset_controller reset;
22120c1552SStephen Boyd 	struct clk_regmap **rclks;
23120c1552SStephen Boyd 	size_t num_rclks;
2449fc825fSStephen Boyd };
2549fc825fSStephen Boyd 
2650c6a503SStephen Boyd const
qcom_find_freq(const struct freq_tbl * f,unsigned long rate)2750c6a503SStephen Boyd struct freq_tbl *qcom_find_freq(const struct freq_tbl *f, unsigned long rate)
2850c6a503SStephen Boyd {
2950c6a503SStephen Boyd 	if (!f)
3050c6a503SStephen Boyd 		return NULL;
3150c6a503SStephen Boyd 
32efd164b5SJeffrey Hugo 	if (!f->freq)
33efd164b5SJeffrey Hugo 		return f;
34efd164b5SJeffrey Hugo 
3550c6a503SStephen Boyd 	for (; f->freq; f++)
3650c6a503SStephen Boyd 		if (rate <= f->freq)
3750c6a503SStephen Boyd 			return f;
3850c6a503SStephen Boyd 
3950c6a503SStephen Boyd 	/* Default to our fastest rate */
4050c6a503SStephen Boyd 	return f - 1;
4150c6a503SStephen Boyd }
4250c6a503SStephen Boyd EXPORT_SYMBOL_GPL(qcom_find_freq);
4350c6a503SStephen Boyd 
qcom_find_freq_floor(const struct freq_tbl * f,unsigned long rate)44081ba802SRajendra Nayak const struct freq_tbl *qcom_find_freq_floor(const struct freq_tbl *f,
45081ba802SRajendra Nayak 					    unsigned long rate)
46081ba802SRajendra Nayak {
47081ba802SRajendra Nayak 	const struct freq_tbl *best = NULL;
48081ba802SRajendra Nayak 
49081ba802SRajendra Nayak 	for ( ; f->freq; f++) {
50081ba802SRajendra Nayak 		if (rate >= f->freq)
51081ba802SRajendra Nayak 			best = f;
52081ba802SRajendra Nayak 		else
53081ba802SRajendra Nayak 			break;
54081ba802SRajendra Nayak 	}
55081ba802SRajendra Nayak 
56081ba802SRajendra Nayak 	return best;
57081ba802SRajendra Nayak }
58081ba802SRajendra Nayak EXPORT_SYMBOL_GPL(qcom_find_freq_floor);
59081ba802SRajendra Nayak 
qcom_find_src_index(struct clk_hw * hw,const struct parent_map * map,u8 src)60293d2e97SGeorgi Djakov int qcom_find_src_index(struct clk_hw *hw, const struct parent_map *map, u8 src)
61293d2e97SGeorgi Djakov {
62497295afSStephen Boyd 	int i, num_parents = clk_hw_get_num_parents(hw);
63293d2e97SGeorgi Djakov 
64293d2e97SGeorgi Djakov 	for (i = 0; i < num_parents; i++)
65293d2e97SGeorgi Djakov 		if (src == map[i].src)
66293d2e97SGeorgi Djakov 			return i;
67293d2e97SGeorgi Djakov 
68293d2e97SGeorgi Djakov 	return -ENOENT;
69293d2e97SGeorgi Djakov }
70293d2e97SGeorgi Djakov EXPORT_SYMBOL_GPL(qcom_find_src_index);
71293d2e97SGeorgi Djakov 
qcom_find_cfg_index(struct clk_hw * hw,const struct parent_map * map,u8 cfg)729a61f813SDmitry Baryshkov int qcom_find_cfg_index(struct clk_hw *hw, const struct parent_map *map, u8 cfg)
739a61f813SDmitry Baryshkov {
749a61f813SDmitry Baryshkov 	int i, num_parents = clk_hw_get_num_parents(hw);
759a61f813SDmitry Baryshkov 
769a61f813SDmitry Baryshkov 	for (i = 0; i < num_parents; i++)
779a61f813SDmitry Baryshkov 		if (cfg == map[i].cfg)
789a61f813SDmitry Baryshkov 			return i;
799a61f813SDmitry Baryshkov 
809a61f813SDmitry Baryshkov 	return -ENOENT;
819a61f813SDmitry Baryshkov }
829a61f813SDmitry Baryshkov EXPORT_SYMBOL_GPL(qcom_find_cfg_index);
839a61f813SDmitry Baryshkov 
845b6b7490SStephen Boyd struct regmap *
qcom_cc_map(struct platform_device * pdev,const struct qcom_cc_desc * desc)855b6b7490SStephen Boyd qcom_cc_map(struct platform_device *pdev, const struct qcom_cc_desc *desc)
8649fc825fSStephen Boyd {
8749fc825fSStephen Boyd 	void __iomem *base;
885b6b7490SStephen Boyd 	struct device *dev = &pdev->dev;
895b6b7490SStephen Boyd 
90437cbbb0SCai Huoqing 	base = devm_platform_ioremap_resource(pdev, 0);
915b6b7490SStephen Boyd 	if (IS_ERR(base))
925b6b7490SStephen Boyd 		return ERR_CAST(base);
935b6b7490SStephen Boyd 
945b6b7490SStephen Boyd 	return devm_regmap_init_mmio(dev, base, desc->config);
955b6b7490SStephen Boyd }
965b6b7490SStephen Boyd EXPORT_SYMBOL_GPL(qcom_cc_map);
975b6b7490SStephen Boyd 
98400d9fdaSRajendra Nayak void
qcom_pll_set_fsm_mode(struct regmap * map,u32 reg,u8 bias_count,u8 lock_count)99400d9fdaSRajendra Nayak qcom_pll_set_fsm_mode(struct regmap *map, u32 reg, u8 bias_count, u8 lock_count)
100400d9fdaSRajendra Nayak {
101400d9fdaSRajendra Nayak 	u32 val;
102400d9fdaSRajendra Nayak 	u32 mask;
103400d9fdaSRajendra Nayak 
104400d9fdaSRajendra Nayak 	/* De-assert reset to FSM */
105400d9fdaSRajendra Nayak 	regmap_update_bits(map, reg, PLL_VOTE_FSM_RESET, 0);
106400d9fdaSRajendra Nayak 
107400d9fdaSRajendra Nayak 	/* Program bias count and lock count */
108400d9fdaSRajendra Nayak 	val = bias_count << PLL_BIAS_COUNT_SHIFT |
109400d9fdaSRajendra Nayak 		lock_count << PLL_LOCK_COUNT_SHIFT;
110400d9fdaSRajendra Nayak 	mask = PLL_BIAS_COUNT_MASK << PLL_BIAS_COUNT_SHIFT;
111400d9fdaSRajendra Nayak 	mask |= PLL_LOCK_COUNT_MASK << PLL_LOCK_COUNT_SHIFT;
112400d9fdaSRajendra Nayak 	regmap_update_bits(map, reg, mask, val);
113400d9fdaSRajendra Nayak 
114400d9fdaSRajendra Nayak 	/* Enable PLL FSM voting */
115400d9fdaSRajendra Nayak 	regmap_update_bits(map, reg, PLL_VOTE_FSM_ENA, PLL_VOTE_FSM_ENA);
116400d9fdaSRajendra Nayak }
117400d9fdaSRajendra Nayak EXPORT_SYMBOL_GPL(qcom_pll_set_fsm_mode);
118400d9fdaSRajendra Nayak 
qcom_cc_gdsc_unregister(void * data)11994c51f40SStephen Boyd static void qcom_cc_gdsc_unregister(void *data)
12094c51f40SStephen Boyd {
12194c51f40SStephen Boyd 	gdsc_unregister(data);
12294c51f40SStephen Boyd }
12394c51f40SStephen Boyd 
124ee15faffSStephen Boyd /*
125ee15faffSStephen Boyd  * Backwards compatibility with old DTs. Register a pass-through factor 1/1
126ad61dd30SStephen Boyd  * clock to translate 'path' clk into 'name' clk and register the 'path'
127ee15faffSStephen Boyd  * clk as a fixed rate clock if it isn't present.
128ee15faffSStephen Boyd  */
_qcom_cc_register_board_clk(struct device * dev,const char * path,const char * name,unsigned long rate,bool add_factor)129ee15faffSStephen Boyd static int _qcom_cc_register_board_clk(struct device *dev, const char *path,
130ee15faffSStephen Boyd 				       const char *name, unsigned long rate,
131ee15faffSStephen Boyd 				       bool add_factor)
132ee15faffSStephen Boyd {
133ee15faffSStephen Boyd 	struct device_node *node = NULL;
134ee15faffSStephen Boyd 	struct device_node *clocks_node;
135ee15faffSStephen Boyd 	struct clk_fixed_factor *factor;
136ee15faffSStephen Boyd 	struct clk_fixed_rate *fixed;
137ee15faffSStephen Boyd 	struct clk_init_data init_data = { };
138120c1552SStephen Boyd 	int ret;
139ee15faffSStephen Boyd 
140ee15faffSStephen Boyd 	clocks_node = of_find_node_by_path("/clocks");
14143a51019SJohan Hovold 	if (clocks_node) {
14243a51019SJohan Hovold 		node = of_get_child_by_name(clocks_node, path);
14343a51019SJohan Hovold 		of_node_put(clocks_node);
14443a51019SJohan Hovold 	}
145ee15faffSStephen Boyd 
146ee15faffSStephen Boyd 	if (!node) {
147ee15faffSStephen Boyd 		fixed = devm_kzalloc(dev, sizeof(*fixed), GFP_KERNEL);
148ee15faffSStephen Boyd 		if (!fixed)
149ee15faffSStephen Boyd 			return -EINVAL;
150ee15faffSStephen Boyd 
151ee15faffSStephen Boyd 		fixed->fixed_rate = rate;
152ee15faffSStephen Boyd 		fixed->hw.init = &init_data;
153ee15faffSStephen Boyd 
154ee15faffSStephen Boyd 		init_data.name = path;
155ee15faffSStephen Boyd 		init_data.ops = &clk_fixed_rate_ops;
156ee15faffSStephen Boyd 
157120c1552SStephen Boyd 		ret = devm_clk_hw_register(dev, &fixed->hw);
158120c1552SStephen Boyd 		if (ret)
159120c1552SStephen Boyd 			return ret;
160ee15faffSStephen Boyd 	}
161ee15faffSStephen Boyd 	of_node_put(node);
162ee15faffSStephen Boyd 
163ee15faffSStephen Boyd 	if (add_factor) {
164ee15faffSStephen Boyd 		factor = devm_kzalloc(dev, sizeof(*factor), GFP_KERNEL);
165ee15faffSStephen Boyd 		if (!factor)
166ee15faffSStephen Boyd 			return -EINVAL;
167ee15faffSStephen Boyd 
168ee15faffSStephen Boyd 		factor->mult = factor->div = 1;
169ee15faffSStephen Boyd 		factor->hw.init = &init_data;
170ee15faffSStephen Boyd 
171ee15faffSStephen Boyd 		init_data.name = name;
172ee15faffSStephen Boyd 		init_data.parent_names = &path;
173ee15faffSStephen Boyd 		init_data.num_parents = 1;
174ee15faffSStephen Boyd 		init_data.flags = 0;
175ee15faffSStephen Boyd 		init_data.ops = &clk_fixed_factor_ops;
176ee15faffSStephen Boyd 
177120c1552SStephen Boyd 		ret = devm_clk_hw_register(dev, &factor->hw);
178120c1552SStephen Boyd 		if (ret)
179120c1552SStephen Boyd 			return ret;
180ee15faffSStephen Boyd 	}
181ee15faffSStephen Boyd 
182ee15faffSStephen Boyd 	return 0;
183ee15faffSStephen Boyd }
184ee15faffSStephen Boyd 
qcom_cc_register_board_clk(struct device * dev,const char * path,const char * name,unsigned long rate)185ee15faffSStephen Boyd int qcom_cc_register_board_clk(struct device *dev, const char *path,
186ee15faffSStephen Boyd 			       const char *name, unsigned long rate)
187ee15faffSStephen Boyd {
188ee15faffSStephen Boyd 	bool add_factor = true;
189ee15faffSStephen Boyd 
19054823af9SGeorgi Djakov 	/*
19154823af9SGeorgi Djakov 	 * TODO: The RPM clock driver currently does not support the xo clock.
19254823af9SGeorgi Djakov 	 * When xo is added to the RPM clock driver, we should change this
19354823af9SGeorgi Djakov 	 * function to skip registration of xo factor clocks.
19454823af9SGeorgi Djakov 	 */
195ee15faffSStephen Boyd 
196ee15faffSStephen Boyd 	return _qcom_cc_register_board_clk(dev, path, name, rate, add_factor);
197ee15faffSStephen Boyd }
198ee15faffSStephen Boyd EXPORT_SYMBOL_GPL(qcom_cc_register_board_clk);
199ee15faffSStephen Boyd 
qcom_cc_register_sleep_clk(struct device * dev)200ee15faffSStephen Boyd int qcom_cc_register_sleep_clk(struct device *dev)
201ee15faffSStephen Boyd {
202ee15faffSStephen Boyd 	return _qcom_cc_register_board_clk(dev, "sleep_clk", "sleep_clk_src",
203ee15faffSStephen Boyd 					   32768, true);
204ee15faffSStephen Boyd }
205ee15faffSStephen Boyd EXPORT_SYMBOL_GPL(qcom_cc_register_sleep_clk);
206ee15faffSStephen Boyd 
207b181b3b8SStephen Boyd /* Drop 'protected-clocks' from the list of clocks to register */
qcom_cc_drop_protected(struct device * dev,struct qcom_cc * cc)208b181b3b8SStephen Boyd static void qcom_cc_drop_protected(struct device *dev, struct qcom_cc *cc)
209b181b3b8SStephen Boyd {
210b181b3b8SStephen Boyd 	struct device_node *np = dev->of_node;
211b181b3b8SStephen Boyd 	u32 i;
212b181b3b8SStephen Boyd 
213*914ef7d1SLuca Ceresoli 	of_property_for_each_u32(np, "protected-clocks", i) {
214b181b3b8SStephen Boyd 		if (i >= cc->num_rclks)
215b181b3b8SStephen Boyd 			continue;
216b181b3b8SStephen Boyd 
217b181b3b8SStephen Boyd 		cc->rclks[i] = NULL;
218b181b3b8SStephen Boyd 	}
219b181b3b8SStephen Boyd }
220b181b3b8SStephen Boyd 
qcom_cc_clk_hw_get(struct of_phandle_args * clkspec,void * data)221120c1552SStephen Boyd static struct clk_hw *qcom_cc_clk_hw_get(struct of_phandle_args *clkspec,
222120c1552SStephen Boyd 					 void *data)
223120c1552SStephen Boyd {
224120c1552SStephen Boyd 	struct qcom_cc *cc = data;
225120c1552SStephen Boyd 	unsigned int idx = clkspec->args[0];
226120c1552SStephen Boyd 
227120c1552SStephen Boyd 	if (idx >= cc->num_rclks) {
228120c1552SStephen Boyd 		pr_err("%s: invalid index %u\n", __func__, idx);
229120c1552SStephen Boyd 		return ERR_PTR(-EINVAL);
230120c1552SStephen Boyd 	}
231120c1552SStephen Boyd 
232ffe37edeSTaniya Das 	return cc->rclks[idx] ? &cc->rclks[idx]->hw : NULL;
233120c1552SStephen Boyd }
234120c1552SStephen Boyd 
qcom_cc_really_probe(struct platform_device * pdev,const struct qcom_cc_desc * desc,struct regmap * regmap)2355b6b7490SStephen Boyd int qcom_cc_really_probe(struct platform_device *pdev,
2365b6b7490SStephen Boyd 			 const struct qcom_cc_desc *desc, struct regmap *regmap)
2375b6b7490SStephen Boyd {
23849fc825fSStephen Boyd 	int i, ret;
23949fc825fSStephen Boyd 	struct device *dev = &pdev->dev;
24049fc825fSStephen Boyd 	struct qcom_reset_controller *reset;
24149fc825fSStephen Boyd 	struct qcom_cc *cc;
242c2c7f0a4SRajendra Nayak 	struct gdsc_desc *scd;
24349fc825fSStephen Boyd 	size_t num_clks = desc->num_clks;
24449fc825fSStephen Boyd 	struct clk_regmap **rclks = desc->clks;
245760be658SJeffrey Hugo 	size_t num_clk_hws = desc->num_clk_hws;
246760be658SJeffrey Hugo 	struct clk_hw **clk_hws = desc->clk_hws;
24749fc825fSStephen Boyd 
248120c1552SStephen Boyd 	cc = devm_kzalloc(dev, sizeof(*cc), GFP_KERNEL);
24949fc825fSStephen Boyd 	if (!cc)
25049fc825fSStephen Boyd 		return -ENOMEM;
25149fc825fSStephen Boyd 
25249fc825fSStephen Boyd 	reset = &cc->reset;
25349fc825fSStephen Boyd 	reset->rcdev.of_node = dev->of_node;
25449fc825fSStephen Boyd 	reset->rcdev.ops = &qcom_reset_ops;
25549fc825fSStephen Boyd 	reset->rcdev.owner = dev->driver->owner;
25649fc825fSStephen Boyd 	reset->rcdev.nr_resets = desc->num_resets;
25749fc825fSStephen Boyd 	reset->regmap = regmap;
25849fc825fSStephen Boyd 	reset->reset_map = desc->resets;
25949fc825fSStephen Boyd 
260b87206f8SStephen Boyd 	ret = devm_reset_controller_register(dev, &reset->rcdev);
26166f5ce25SSudip Mukherjee 	if (ret)
26266f5ce25SSudip Mukherjee 		return ret;
26349fc825fSStephen Boyd 
2645e5cc241SRajendra Nayak 	if (desc->gdscs && desc->num_gdscs) {
265c2c7f0a4SRajendra Nayak 		scd = devm_kzalloc(dev, sizeof(*scd), GFP_KERNEL);
266c2c7f0a4SRajendra Nayak 		if (!scd)
267c2c7f0a4SRajendra Nayak 			return -ENOMEM;
268c2c7f0a4SRajendra Nayak 		scd->dev = dev;
269c2c7f0a4SRajendra Nayak 		scd->scs = desc->gdscs;
270c2c7f0a4SRajendra Nayak 		scd->num = desc->num_gdscs;
271c2c7f0a4SRajendra Nayak 		ret = gdsc_register(scd, &reset->rcdev, regmap);
272c2c7f0a4SRajendra Nayak 		if (ret)
273c2c7f0a4SRajendra Nayak 			return ret;
274c2c7f0a4SRajendra Nayak 		ret = devm_add_action_or_reset(dev, qcom_cc_gdsc_unregister,
275c2c7f0a4SRajendra Nayak 					       scd);
2765e5cc241SRajendra Nayak 		if (ret)
27794c51f40SStephen Boyd 			return ret;
2785e5cc241SRajendra Nayak 	}
2795e5cc241SRajendra Nayak 
28031543ebbSRajendra Nayak 	cc->rclks = rclks;
28131543ebbSRajendra Nayak 	cc->num_rclks = num_clks;
28231543ebbSRajendra Nayak 
283b181b3b8SStephen Boyd 	qcom_cc_drop_protected(dev, cc);
284b181b3b8SStephen Boyd 
285760be658SJeffrey Hugo 	for (i = 0; i < num_clk_hws; i++) {
286760be658SJeffrey Hugo 		ret = devm_clk_hw_register(dev, clk_hws[i]);
287760be658SJeffrey Hugo 		if (ret)
288760be658SJeffrey Hugo 			return ret;
289760be658SJeffrey Hugo 	}
290760be658SJeffrey Hugo 
29131543ebbSRajendra Nayak 	for (i = 0; i < num_clks; i++) {
29231543ebbSRajendra Nayak 		if (!rclks[i])
29331543ebbSRajendra Nayak 			continue;
29431543ebbSRajendra Nayak 
29531543ebbSRajendra Nayak 		ret = devm_clk_register_regmap(dev, rclks[i]);
29631543ebbSRajendra Nayak 		if (ret)
29731543ebbSRajendra Nayak 			return ret;
29831543ebbSRajendra Nayak 	}
29931543ebbSRajendra Nayak 
30031543ebbSRajendra Nayak 	ret = devm_of_clk_add_hw_provider(dev, qcom_cc_clk_hw_get, cc);
30131543ebbSRajendra Nayak 	if (ret)
30231543ebbSRajendra Nayak 		return ret;
30331543ebbSRajendra Nayak 
304c2c7f0a4SRajendra Nayak 	return 0;
30549fc825fSStephen Boyd }
3065b6b7490SStephen Boyd EXPORT_SYMBOL_GPL(qcom_cc_really_probe);
3075b6b7490SStephen Boyd 
qcom_cc_probe(struct platform_device * pdev,const struct qcom_cc_desc * desc)3085b6b7490SStephen Boyd int qcom_cc_probe(struct platform_device *pdev, const struct qcom_cc_desc *desc)
3095b6b7490SStephen Boyd {
3105b6b7490SStephen Boyd 	struct regmap *regmap;
3115b6b7490SStephen Boyd 
3125b6b7490SStephen Boyd 	regmap = qcom_cc_map(pdev, desc);
3135b6b7490SStephen Boyd 	if (IS_ERR(regmap))
3145b6b7490SStephen Boyd 		return PTR_ERR(regmap);
3155b6b7490SStephen Boyd 
3165b6b7490SStephen Boyd 	return qcom_cc_really_probe(pdev, desc, regmap);
3175b6b7490SStephen Boyd }
31849fc825fSStephen Boyd EXPORT_SYMBOL_GPL(qcom_cc_probe);
31949fc825fSStephen Boyd 
qcom_cc_probe_by_index(struct platform_device * pdev,int index,const struct qcom_cc_desc * desc)32075e0a1e3SGovind Singh int qcom_cc_probe_by_index(struct platform_device *pdev, int index,
32175e0a1e3SGovind Singh 			   const struct qcom_cc_desc *desc)
32275e0a1e3SGovind Singh {
32375e0a1e3SGovind Singh 	struct regmap *regmap;
32475e0a1e3SGovind Singh 	void __iomem *base;
32575e0a1e3SGovind Singh 
326437cbbb0SCai Huoqing 	base = devm_platform_ioremap_resource(pdev, index);
32775e0a1e3SGovind Singh 	if (IS_ERR(base))
32875e0a1e3SGovind Singh 		return -ENOMEM;
32975e0a1e3SGovind Singh 
33075e0a1e3SGovind Singh 	regmap = devm_regmap_init_mmio(&pdev->dev, base, desc->config);
33175e0a1e3SGovind Singh 	if (IS_ERR(regmap))
33275e0a1e3SGovind Singh 		return PTR_ERR(regmap);
33375e0a1e3SGovind Singh 
33475e0a1e3SGovind Singh 	return qcom_cc_really_probe(pdev, desc, regmap);
33575e0a1e3SGovind Singh }
33675e0a1e3SGovind Singh EXPORT_SYMBOL_GPL(qcom_cc_probe_by_index);
33775e0a1e3SGovind Singh 
338169f05e8SStephen Boyd MODULE_LICENSE("GPL v2");
339