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