aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTirupathi Reddy <tirupath@codeaurora.org>2017-11-21 04:11:04 -0500
committerStephen Boyd <sboyd@codeaurora.org>2017-12-07 01:30:30 -0500
commit4cfaa55f42aa3365603f99e3d695939114706b8b (patch)
tree1314b022808203ab63185204e2f9d653e41b4d19
parente3447a67c96f4456829bafb11d0fa05b790f56b3 (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/Kconfig9
-rw-r--r--drivers/clk/qcom/Makefile1
-rw-r--r--drivers/clk/qcom/clk-spmi-pmic-div.c302
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
200config 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
34obj-$(CONFIG_MSM_MMCC_8996) += mmcc-msm8996.o 34obj-$(CONFIG_MSM_MMCC_8996) += mmcc-msm8996.o
35obj-$(CONFIG_QCOM_CLK_RPM) += clk-rpm.o 35obj-$(CONFIG_QCOM_CLK_RPM) += clk-rpm.o
36obj-$(CONFIG_QCOM_CLK_SMD_RPM) += clk-smd-rpm.o 36obj-$(CONFIG_QCOM_CLK_SMD_RPM) += clk-smd-rpm.o
37obj-$(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
32struct 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
41static inline struct clkdiv *to_clkdiv(struct clk_hw *hw)
42{
43 return container_of(hw, struct clkdiv, hw);
44}
45
46static 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
54static inline unsigned int div_to_div_factor(unsigned int div)
55{
56 return min(ilog2(div) + 1, 7);
57}
58
59static 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
68static 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
89static 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
99static 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
112static 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
122static 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
134static unsigned long
135clk_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
146static 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
172unlock:
173 spin_unlock_irqrestore(&clkdiv->lock, flags);
174
175 return ret;
176}
177
178static 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
186struct spmi_pmic_div_clk_cc {
187 int nclks;
188 struct clkdiv clks[];
189};
190
191static struct clk_hw *
192spmi_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
206static 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
286static const struct of_device_id spmi_pmic_clkdiv_match_table[] = {
287 { .compatible = "qcom,spmi-clkdiv" },
288 { /* sentinel */ }
289};
290MODULE_DEVICE_TABLE(of, spmi_pmic_clkdiv_match_table);
291
292static 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};
299module_platform_driver(spmi_pmic_clkdiv_driver);
300
301MODULE_DESCRIPTION("QCOM SPMI PMIC clkdiv driver");
302MODULE_LICENSE("GPL v2");