xref: /linux/drivers/clk/qcom/clk-ref.c (revision 502d45774af09f1c681c754c4b7cdfb5d7f72fd9)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries.
4  */
5 
6 #include <linux/clk-provider.h>
7 #include <linux/clk/qcom.h>
8 #include <linux/delay.h>
9 #include <linux/err.h>
10 #include <linux/export.h>
11 #include <linux/platform_device.h>
12 #include <linux/regmap.h>
13 #include <linux/regulator/consumer.h>
14 #include <linux/slab.h>
15 
16 #define QCOM_CLK_REF_EN_MASK BIT(0)
17 
18 struct qcom_clk_ref_provider {
19 	size_t num_refs;
20 	struct qcom_clk_ref refs[] __counted_by(num_refs);
21 };
22 
to_qcom_clk_ref(struct clk_hw * hw)23 static inline struct qcom_clk_ref *to_qcom_clk_ref(struct clk_hw *hw)
24 {
25 	return container_of(hw, struct qcom_clk_ref, hw);
26 }
27 
28 static const struct clk_parent_data qcom_clk_ref_parent_data = {
29 	.index = 0,
30 };
31 
qcom_clk_ref_prepare(struct clk_hw * hw)32 static int qcom_clk_ref_prepare(struct clk_hw *hw)
33 {
34 	struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw);
35 	int ret;
36 
37 	if (!rclk->desc.num_regulators)
38 		return 0;
39 
40 	ret = regulator_bulk_enable(rclk->desc.num_regulators, rclk->regulators);
41 	if (ret)
42 		pr_err("Failed to enable regulators for %s: %d\n",
43 		       clk_hw_get_name(hw), ret);
44 
45 	return ret;
46 }
47 
qcom_clk_ref_unprepare(struct clk_hw * hw)48 static void qcom_clk_ref_unprepare(struct clk_hw *hw)
49 {
50 	struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw);
51 
52 	if (rclk->desc.num_regulators)
53 		regulator_bulk_disable(rclk->desc.num_regulators, rclk->regulators);
54 }
55 
qcom_clk_ref_enable(struct clk_hw * hw)56 static int qcom_clk_ref_enable(struct clk_hw *hw)
57 {
58 	struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw);
59 	int ret;
60 
61 	ret = regmap_set_bits(rclk->regmap, rclk->desc.offset, QCOM_CLK_REF_EN_MASK);
62 	if (ret)
63 		return ret;
64 
65 	udelay(10);
66 
67 	return 0;
68 }
69 
qcom_clk_ref_disable(struct clk_hw * hw)70 static void qcom_clk_ref_disable(struct clk_hw *hw)
71 {
72 	struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw);
73 
74 	regmap_clear_bits(rclk->regmap, rclk->desc.offset, QCOM_CLK_REF_EN_MASK);
75 	udelay(10);
76 }
77 
qcom_clk_ref_is_enabled(struct clk_hw * hw)78 static int qcom_clk_ref_is_enabled(struct clk_hw *hw)
79 {
80 	struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw);
81 	u32 val;
82 	int ret;
83 
84 	ret = regmap_read(rclk->regmap, rclk->desc.offset, &val);
85 	if (ret)
86 		return 0;
87 
88 	return !!(val & QCOM_CLK_REF_EN_MASK);
89 }
90 
91 static const struct clk_ops qcom_clk_ref_ops = {
92 	.prepare = qcom_clk_ref_prepare,
93 	.unprepare = qcom_clk_ref_unprepare,
94 	.enable = qcom_clk_ref_enable,
95 	.disable = qcom_clk_ref_disable,
96 	.is_enabled = qcom_clk_ref_is_enabled,
97 };
98 
qcom_clk_ref_register(struct device * dev,struct regmap * regmap,struct qcom_clk_ref * clk_refs,const struct qcom_clk_ref_desc * const * descs,size_t num_clk_refs)99 static int qcom_clk_ref_register(struct device *dev, struct regmap *regmap,
100 				 struct qcom_clk_ref *clk_refs,
101 				 const struct qcom_clk_ref_desc * const *descs,
102 				 size_t num_clk_refs)
103 {
104 	const struct qcom_clk_ref_desc *desc;
105 	struct clk_init_data init_data = {};
106 	struct qcom_clk_ref *clk_ref;
107 	size_t clk_idx;
108 	unsigned int i;
109 	int ret;
110 
111 	for (clk_idx = 0; clk_idx < num_clk_refs; clk_idx++) {
112 		clk_ref = &clk_refs[clk_idx];
113 		desc = descs[clk_idx];
114 
115 		/* Skip unpopulated indices; the array is indexed by clock ID. */
116 		if (!desc)
117 			continue;
118 
119 		if (WARN_ON(!desc->name))
120 			continue;
121 
122 		clk_ref->regmap = regmap;
123 		clk_ref->desc = *desc;
124 
125 		if (clk_ref->desc.num_regulators) {
126 			clk_ref->regulators = devm_kcalloc(dev, clk_ref->desc.num_regulators,
127 							   sizeof(*clk_ref->regulators),
128 							   GFP_KERNEL);
129 			if (!clk_ref->regulators)
130 				return -ENOMEM;
131 
132 			for (i = 0; i < clk_ref->desc.num_regulators; i++)
133 				clk_ref->regulators[i].supply =
134 					clk_ref->desc.regulator_names[i];
135 
136 			ret = devm_regulator_bulk_get(dev, clk_ref->desc.num_regulators,
137 						      clk_ref->regulators);
138 			if (ret)
139 				return dev_err_probe(dev, ret,
140 						     "Failed to get regulators for %s\n",
141 						     clk_ref->desc.name);
142 		}
143 
144 		init_data.name = clk_ref->desc.name;
145 		init_data.parent_data = &qcom_clk_ref_parent_data;
146 		init_data.num_parents = 1;
147 		init_data.ops = &qcom_clk_ref_ops;
148 		clk_ref->hw.init = &init_data;
149 
150 		ret = devm_clk_hw_register(dev, &clk_ref->hw);
151 		if (ret)
152 			return ret;
153 	}
154 
155 	return 0;
156 }
157 
qcom_clk_ref_provider_get(struct of_phandle_args * clkspec,void * data)158 static struct clk_hw *qcom_clk_ref_provider_get(struct of_phandle_args *clkspec, void *data)
159 {
160 	struct qcom_clk_ref_provider *provider = data;
161 	unsigned int idx = clkspec->args[0];
162 
163 	if (idx >= provider->num_refs)
164 		return ERR_PTR(-EINVAL);
165 
166 	if (!provider->refs[idx].regmap)
167 		return ERR_PTR(-ENOENT);
168 
169 	return &provider->refs[idx].hw;
170 }
171 
qcom_clk_ref_probe(struct platform_device * pdev,const struct regmap_config * config,const struct qcom_clk_ref_desc * const * descs,size_t num_clk_refs)172 int qcom_clk_ref_probe(struct platform_device *pdev,
173 		       const struct regmap_config *config,
174 		       const struct qcom_clk_ref_desc * const *descs,
175 		       size_t num_clk_refs)
176 {
177 	struct qcom_clk_ref_provider *provider;
178 	struct device *dev = &pdev->dev;
179 	struct regmap *regmap;
180 	void __iomem *base;
181 	int ret;
182 
183 	base = devm_platform_ioremap_resource(pdev, 0);
184 	if (IS_ERR(base))
185 		return PTR_ERR(base);
186 
187 	regmap = devm_regmap_init_mmio(dev, base, config);
188 	if (IS_ERR(regmap))
189 		return PTR_ERR(regmap);
190 
191 	provider = devm_kzalloc(dev, struct_size(provider, refs, num_clk_refs),
192 				GFP_KERNEL);
193 	if (!provider)
194 		return -ENOMEM;
195 
196 	provider->num_refs = num_clk_refs;
197 
198 	ret = qcom_clk_ref_register(dev, regmap, provider->refs, descs,
199 				    provider->num_refs);
200 	if (ret)
201 		return ret;
202 
203 	return devm_of_clk_add_hw_provider(dev, qcom_clk_ref_provider_get, provider);
204 }
205 EXPORT_SYMBOL_GPL(qcom_clk_ref_probe);
206