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