diff options
author | Tirupathi Reddy <tirupath@codeaurora.org> | 2017-11-21 04:11:04 -0500 |
---|---|---|
committer | Stephen Boyd <sboyd@codeaurora.org> | 2017-12-07 01:30:30 -0500 |
commit | 4cfaa55f42aa3365603f99e3d695939114706b8b (patch) | |
tree | 1314b022808203ab63185204e2f9d653e41b4d19 | |
parent | e3447a67c96f4456829bafb11d0fa05b790f56b3 (diff) |
clk: qcom: Add spmi_pmic clock divider support
Clkdiv module provides a clock output on the PMIC with CXO as
the source. This clock can be routed through PMIC GPIOs. Add
a device driver to configure this clkdiv module.
Signed-off-by: Tirupathi Reddy <tirupath@codeaurora.org>
[sboyd: Simplified code and moved to devm clk provider APIs]
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
-rw-r--r-- | drivers/clk/qcom/Kconfig | 9 | ||||
-rw-r--r-- | drivers/clk/qcom/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/qcom/clk-spmi-pmic-div.c | 302 |
3 files changed, 312 insertions, 0 deletions
diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 9f6c278deead..20b5d6fd501d 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig | |||
@@ -196,3 +196,12 @@ config MSM_MMCC_8996 | |||
196 | Support for the multimedia clock controller on msm8996 devices. | 196 | Support for the multimedia clock controller on msm8996 devices. |
197 | Say Y if you want to support multimedia devices such as display, | 197 | Say Y if you want to support multimedia devices such as display, |
198 | graphics, video encode/decode, camera, etc. | 198 | graphics, video encode/decode, camera, etc. |
199 | |||
200 | config SPMI_PMIC_CLKDIV | ||
201 | tristate "SPMI PMIC clkdiv Support" | ||
202 | depends on (COMMON_CLK_QCOM && SPMI) || COMPILE_TEST | ||
203 | help | ||
204 | This driver supports the clkdiv functionality on the Qualcomm | ||
205 | Technologies, Inc. SPMI PMIC. It configures the frequency of | ||
206 | clkdiv outputs of the PMIC. These clocks are typically wired | ||
207 | through alternate functions on GPIO pins. | ||
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 26410d31446b..602af3841522 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile | |||
@@ -34,3 +34,4 @@ obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o | |||
34 | obj-$(CONFIG_MSM_MMCC_8996) += mmcc-msm8996.o | 34 | obj-$(CONFIG_MSM_MMCC_8996) += mmcc-msm8996.o |
35 | obj-$(CONFIG_QCOM_CLK_RPM) += clk-rpm.o | 35 | obj-$(CONFIG_QCOM_CLK_RPM) += clk-rpm.o |
36 | obj-$(CONFIG_QCOM_CLK_SMD_RPM) += clk-smd-rpm.o | 36 | obj-$(CONFIG_QCOM_CLK_SMD_RPM) += clk-smd-rpm.o |
37 | obj-$(CONFIG_SPMI_PMIC_CLKDIV) += clk-spmi-pmic-div.o | ||
diff --git a/drivers/clk/qcom/clk-spmi-pmic-div.c b/drivers/clk/qcom/clk-spmi-pmic-div.c new file mode 100644 index 000000000000..8672ab84746f --- /dev/null +++ b/drivers/clk/qcom/clk-spmi-pmic-div.c | |||
@@ -0,0 +1,302 @@ | |||
1 | /* Copyright (c) 2017, The Linux Foundation. All rights reserved. | ||
2 | * | ||
3 | * This program is free software; you can redistribute it and/or modify | ||
4 | * it under the terms of the GNU General Public License version 2 and | ||
5 | * only version 2 as published by the Free Software Foundation. | ||
6 | * | ||
7 | * This program is distributed in the hope that it will be useful, | ||
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
10 | * GNU General Public License for more details. | ||
11 | */ | ||
12 | |||
13 | #include <linux/bitops.h> | ||
14 | #include <linux/clk.h> | ||
15 | #include <linux/clk-provider.h> | ||
16 | #include <linux/delay.h> | ||
17 | #include <linux/err.h> | ||
18 | #include <linux/log2.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/of.h> | ||
21 | #include <linux/platform_device.h> | ||
22 | #include <linux/regmap.h> | ||
23 | #include <linux/slab.h> | ||
24 | #include <linux/types.h> | ||
25 | |||
26 | #define REG_DIV_CTL1 0x43 | ||
27 | #define DIV_CTL1_DIV_FACTOR_MASK GENMASK(2, 0) | ||
28 | |||
29 | #define REG_EN_CTL 0x46 | ||
30 | #define REG_EN_MASK BIT(7) | ||
31 | |||
32 | struct clkdiv { | ||
33 | struct regmap *regmap; | ||
34 | u16 base; | ||
35 | spinlock_t lock; | ||
36 | |||
37 | struct clk_hw hw; | ||
38 | unsigned int cxo_period_ns; | ||
39 | }; | ||
40 | |||
41 | static inline struct clkdiv *to_clkdiv(struct clk_hw *hw) | ||
42 | { | ||
43 | return container_of(hw, struct clkdiv, hw); | ||
44 | } | ||
45 | |||
46 | static inline unsigned int div_factor_to_div(unsigned int div_factor) | ||
47 | { | ||
48 | if (!div_factor) | ||
49 | div_factor = 1; | ||
50 | |||
51 | return 1 << (div_factor - 1); | ||
52 | } | ||
53 | |||
54 | static inline unsigned int div_to_div_factor(unsigned int div) | ||
55 | { | ||
56 | return min(ilog2(div) + 1, 7); | ||
57 | } | ||
58 | |||
59 | static bool is_spmi_pmic_clkdiv_enabled(struct clkdiv *clkdiv) | ||
60 | { | ||
61 | unsigned int val = 0; | ||
62 | |||
63 | regmap_read(clkdiv->regmap, clkdiv->base + REG_EN_CTL, &val); | ||
64 | |||
65 | return val & REG_EN_MASK; | ||
66 | } | ||
67 | |||
68 | static int | ||
69 | __spmi_pmic_clkdiv_set_enable_state(struct clkdiv *clkdiv, bool enable, | ||
70 | unsigned int div_factor) | ||
71 | { | ||
72 | int ret; | ||
73 | unsigned int ns = clkdiv->cxo_period_ns; | ||
74 | unsigned int div = div_factor_to_div(div_factor); | ||
75 | |||
76 | ret = regmap_update_bits(clkdiv->regmap, clkdiv->base + REG_EN_CTL, | ||
77 | REG_EN_MASK, enable ? REG_EN_MASK : 0); | ||
78 | if (ret) | ||
79 | return ret; | ||
80 | |||
81 | if (enable) | ||
82 | ndelay((2 + 3 * div) * ns); | ||
83 | else | ||
84 | ndelay(3 * div * ns); | ||
85 | |||
86 | return 0; | ||
87 | } | ||
88 | |||
89 | static int spmi_pmic_clkdiv_set_enable_state(struct clkdiv *clkdiv, bool enable) | ||
90 | { | ||
91 | unsigned int div_factor; | ||
92 | |||
93 | regmap_read(clkdiv->regmap, clkdiv->base + REG_DIV_CTL1, &div_factor); | ||
94 | div_factor &= DIV_CTL1_DIV_FACTOR_MASK; | ||
95 | |||
96 | return __spmi_pmic_clkdiv_set_enable_state(clkdiv, enable, div_factor); | ||
97 | } | ||
98 | |||
99 | static int clk_spmi_pmic_div_enable(struct clk_hw *hw) | ||
100 | { | ||
101 | struct clkdiv *clkdiv = to_clkdiv(hw); | ||
102 | unsigned long flags; | ||
103 | int ret; | ||
104 | |||
105 | spin_lock_irqsave(&clkdiv->lock, flags); | ||
106 | ret = spmi_pmic_clkdiv_set_enable_state(clkdiv, true); | ||
107 | spin_unlock_irqrestore(&clkdiv->lock, flags); | ||
108 | |||
109 | return ret; | ||
110 | } | ||
111 | |||
112 | static void clk_spmi_pmic_div_disable(struct clk_hw *hw) | ||
113 | { | ||
114 | struct clkdiv *clkdiv = to_clkdiv(hw); | ||
115 | unsigned long flags; | ||
116 | |||
117 | spin_lock_irqsave(&clkdiv->lock, flags); | ||
118 | spmi_pmic_clkdiv_set_enable_state(clkdiv, false); | ||
119 | spin_unlock_irqrestore(&clkdiv->lock, flags); | ||
120 | } | ||
121 | |||
122 | static long clk_spmi_pmic_div_round_rate(struct clk_hw *hw, unsigned long rate, | ||
123 | unsigned long *parent_rate) | ||
124 | { | ||
125 | unsigned int div, div_factor; | ||
126 | |||
127 | div = DIV_ROUND_UP(*parent_rate, rate); | ||
128 | div_factor = div_to_div_factor(div); | ||
129 | div = div_factor_to_div(div_factor); | ||
130 | |||
131 | return *parent_rate / div; | ||
132 | } | ||
133 | |||
134 | static unsigned long | ||
135 | clk_spmi_pmic_div_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) | ||
136 | { | ||
137 | struct clkdiv *clkdiv = to_clkdiv(hw); | ||
138 | unsigned int div_factor; | ||
139 | |||
140 | regmap_read(clkdiv->regmap, clkdiv->base + REG_DIV_CTL1, &div_factor); | ||
141 | div_factor &= DIV_CTL1_DIV_FACTOR_MASK; | ||
142 | |||
143 | return parent_rate / div_factor_to_div(div_factor); | ||
144 | } | ||
145 | |||
146 | static int clk_spmi_pmic_div_set_rate(struct clk_hw *hw, unsigned long rate, | ||
147 | unsigned long parent_rate) | ||
148 | { | ||
149 | struct clkdiv *clkdiv = to_clkdiv(hw); | ||
150 | unsigned int div_factor = div_to_div_factor(parent_rate / rate); | ||
151 | unsigned long flags; | ||
152 | bool enabled; | ||
153 | int ret; | ||
154 | |||
155 | spin_lock_irqsave(&clkdiv->lock, flags); | ||
156 | enabled = is_spmi_pmic_clkdiv_enabled(clkdiv); | ||
157 | if (enabled) { | ||
158 | ret = spmi_pmic_clkdiv_set_enable_state(clkdiv, false); | ||
159 | if (ret) | ||
160 | goto unlock; | ||
161 | } | ||
162 | |||
163 | ret = regmap_update_bits(clkdiv->regmap, clkdiv->base + REG_DIV_CTL1, | ||
164 | DIV_CTL1_DIV_FACTOR_MASK, div_factor); | ||
165 | if (ret) | ||
166 | goto unlock; | ||
167 | |||
168 | if (enabled) | ||
169 | ret = __spmi_pmic_clkdiv_set_enable_state(clkdiv, true, | ||
170 | div_factor); | ||
171 | |||
172 | unlock: | ||
173 | spin_unlock_irqrestore(&clkdiv->lock, flags); | ||
174 | |||
175 | return ret; | ||
176 | } | ||
177 | |||
178 | static const struct clk_ops clk_spmi_pmic_div_ops = { | ||
179 | .enable = clk_spmi_pmic_div_enable, | ||
180 | .disable = clk_spmi_pmic_div_disable, | ||
181 | .set_rate = clk_spmi_pmic_div_set_rate, | ||
182 | .recalc_rate = clk_spmi_pmic_div_recalc_rate, | ||
183 | .round_rate = clk_spmi_pmic_div_round_rate, | ||
184 | }; | ||
185 | |||
186 | struct spmi_pmic_div_clk_cc { | ||
187 | int nclks; | ||
188 | struct clkdiv clks[]; | ||
189 | }; | ||
190 | |||
191 | static struct clk_hw * | ||
192 | spmi_pmic_div_clk_hw_get(struct of_phandle_args *clkspec, void *data) | ||
193 | { | ||
194 | struct spmi_pmic_div_clk_cc *cc = data; | ||
195 | int idx = clkspec->args[0] - 1; /* Start at 1 instead of 0 */ | ||
196 | |||
197 | if (idx < 0 || idx >= cc->nclks) { | ||
198 | pr_err("%s: index value %u is invalid; allowed range [1, %d]\n", | ||
199 | __func__, clkspec->args[0], cc->nclks); | ||
200 | return ERR_PTR(-EINVAL); | ||
201 | } | ||
202 | |||
203 | return &cc->clks[idx].hw; | ||
204 | } | ||
205 | |||
206 | static int spmi_pmic_clkdiv_probe(struct platform_device *pdev) | ||
207 | { | ||
208 | struct spmi_pmic_div_clk_cc *cc; | ||
209 | struct clk_init_data init = {}; | ||
210 | struct clkdiv *clkdiv; | ||
211 | struct clk *cxo; | ||
212 | struct regmap *regmap; | ||
213 | struct device *dev = &pdev->dev; | ||
214 | struct device_node *of_node = dev->of_node; | ||
215 | const char *parent_name; | ||
216 | int nclks, i, ret, cxo_hz; | ||
217 | char name[20]; | ||
218 | u32 start; | ||
219 | |||
220 | ret = of_property_read_u32(of_node, "reg", &start); | ||
221 | if (ret < 0) { | ||
222 | dev_err(dev, "reg property reading failed\n"); | ||
223 | return ret; | ||
224 | } | ||
225 | |||
226 | regmap = dev_get_regmap(dev->parent, NULL); | ||
227 | if (!regmap) { | ||
228 | dev_err(dev, "Couldn't get parent's regmap\n"); | ||
229 | return -EINVAL; | ||
230 | } | ||
231 | |||
232 | ret = of_property_read_u32(of_node, "qcom,num-clkdivs", &nclks); | ||
233 | if (ret < 0) { | ||
234 | dev_err(dev, "qcom,num-clkdivs property reading failed, ret=%d\n", | ||
235 | ret); | ||
236 | return ret; | ||
237 | } | ||
238 | |||
239 | if (!nclks) | ||
240 | return -EINVAL; | ||
241 | |||
242 | cc = devm_kzalloc(dev, sizeof(*cc) + sizeof(*cc->clks) * nclks, | ||
243 | GFP_KERNEL); | ||
244 | if (!cc) | ||
245 | return -ENOMEM; | ||
246 | cc->nclks = nclks; | ||
247 | |||
248 | cxo = clk_get(dev, "xo"); | ||
249 | if (IS_ERR(cxo)) { | ||
250 | ret = PTR_ERR(cxo); | ||
251 | if (ret != -EPROBE_DEFER) | ||
252 | dev_err(dev, "failed to get xo clock\n"); | ||
253 | return ret; | ||
254 | } | ||
255 | cxo_hz = clk_get_rate(cxo); | ||
256 | clk_put(cxo); | ||
257 | |||
258 | parent_name = of_clk_get_parent_name(of_node, 0); | ||
259 | if (!parent_name) { | ||
260 | dev_err(dev, "missing parent clock\n"); | ||
261 | return -ENODEV; | ||
262 | } | ||
263 | |||
264 | init.name = name; | ||
265 | init.parent_names = &parent_name; | ||
266 | init.num_parents = 1; | ||
267 | init.ops = &clk_spmi_pmic_div_ops; | ||
268 | |||
269 | for (i = 0, clkdiv = cc->clks; i < nclks; i++) { | ||
270 | snprintf(name, sizeof(name), "div_clk%d", i + 1); | ||
271 | |||
272 | spin_lock_init(&clkdiv[i].lock); | ||
273 | clkdiv[i].base = start + i * 0x100; | ||
274 | clkdiv[i].regmap = regmap; | ||
275 | clkdiv[i].cxo_period_ns = NSEC_PER_SEC / cxo_hz; | ||
276 | clkdiv[i].hw.init = &init; | ||
277 | |||
278 | ret = devm_clk_hw_register(dev, &clkdiv[i].hw); | ||
279 | if (ret) | ||
280 | return ret; | ||
281 | } | ||
282 | |||
283 | return devm_of_clk_add_hw_provider(dev, spmi_pmic_div_clk_hw_get, cc); | ||
284 | } | ||
285 | |||
286 | static const struct of_device_id spmi_pmic_clkdiv_match_table[] = { | ||
287 | { .compatible = "qcom,spmi-clkdiv" }, | ||
288 | { /* sentinel */ } | ||
289 | }; | ||
290 | MODULE_DEVICE_TABLE(of, spmi_pmic_clkdiv_match_table); | ||
291 | |||
292 | static struct platform_driver spmi_pmic_clkdiv_driver = { | ||
293 | .driver = { | ||
294 | .name = "qcom,spmi-pmic-clkdiv", | ||
295 | .of_match_table = spmi_pmic_clkdiv_match_table, | ||
296 | }, | ||
297 | .probe = spmi_pmic_clkdiv_probe, | ||
298 | }; | ||
299 | module_platform_driver(spmi_pmic_clkdiv_driver); | ||
300 | |||
301 | MODULE_DESCRIPTION("QCOM SPMI PMIC clkdiv driver"); | ||
302 | MODULE_LICENSE("GPL v2"); | ||