Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0-only
2 : : /*
3 : : * Intel Atom platform clocks driver for BayTrail and CherryTrail SoCs
4 : : *
5 : : * Copyright (C) 2016, Intel Corporation
6 : : * Author: Irina Tirdea <irina.tirdea@intel.com>
7 : : */
8 : :
9 : : #include <linux/clk-provider.h>
10 : : #include <linux/clkdev.h>
11 : : #include <linux/err.h>
12 : : #include <linux/io.h>
13 : : #include <linux/platform_data/x86/clk-pmc-atom.h>
14 : : #include <linux/platform_device.h>
15 : : #include <linux/slab.h>
16 : :
17 : : #define PLT_CLK_NAME_BASE "pmc_plt_clk"
18 : :
19 : : #define PMC_CLK_CTL_OFFSET 0x60
20 : : #define PMC_CLK_CTL_SIZE 4
21 : : #define PMC_CLK_NUM 6
22 : : #define PMC_CLK_CTL_GATED_ON_D3 0x0
23 : : #define PMC_CLK_CTL_FORCE_ON 0x1
24 : : #define PMC_CLK_CTL_FORCE_OFF 0x2
25 : : #define PMC_CLK_CTL_RESERVED 0x3
26 : : #define PMC_MASK_CLK_CTL GENMASK(1, 0)
27 : : #define PMC_MASK_CLK_FREQ BIT(2)
28 : : #define PMC_CLK_FREQ_XTAL (0 << 2) /* 25 MHz */
29 : : #define PMC_CLK_FREQ_PLL (1 << 2) /* 19.2 MHz */
30 : :
31 : : struct clk_plt_fixed {
32 : : struct clk_hw *clk;
33 : : struct clk_lookup *lookup;
34 : : };
35 : :
36 : : struct clk_plt {
37 : : struct clk_hw hw;
38 : : void __iomem *reg;
39 : : struct clk_lookup *lookup;
40 : : /* protect access to PMC registers */
41 : : spinlock_t lock;
42 : : };
43 : :
44 : : #define to_clk_plt(_hw) container_of(_hw, struct clk_plt, hw)
45 : :
46 : : struct clk_plt_data {
47 : : struct clk_plt_fixed **parents;
48 : : u8 nparents;
49 : : struct clk_plt *clks[PMC_CLK_NUM];
50 : : struct clk_lookup *mclk_lookup;
51 : : struct clk_lookup *ether_clk_lookup;
52 : : };
53 : :
54 : : /* Return an index in parent table */
55 : 0 : static inline int plt_reg_to_parent(int reg)
56 : : {
57 : 0 : switch (reg & PMC_MASK_CLK_FREQ) {
58 : : default:
59 : : case PMC_CLK_FREQ_XTAL:
60 : : return 0;
61 : 0 : case PMC_CLK_FREQ_PLL:
62 : 0 : return 1;
63 : : }
64 : : }
65 : :
66 : : /* Return clk index of parent */
67 : 0 : static inline int plt_parent_to_reg(int index)
68 : : {
69 : 0 : switch (index) {
70 : : default:
71 : : case 0:
72 : : return PMC_CLK_FREQ_XTAL;
73 : 0 : case 1:
74 : 0 : return PMC_CLK_FREQ_PLL;
75 : : }
76 : : }
77 : :
78 : : /* Abstract status in simpler enabled/disabled value */
79 : 0 : static inline int plt_reg_to_enabled(int reg)
80 : : {
81 : 0 : switch (reg & PMC_MASK_CLK_CTL) {
82 : : case PMC_CLK_CTL_GATED_ON_D3:
83 : : case PMC_CLK_CTL_FORCE_ON:
84 : : return 1; /* enabled */
85 : 0 : case PMC_CLK_CTL_FORCE_OFF:
86 : : case PMC_CLK_CTL_RESERVED:
87 : : default:
88 : 0 : return 0; /* disabled */
89 : : }
90 : : }
91 : :
92 : 0 : static void plt_clk_reg_update(struct clk_plt *clk, u32 mask, u32 val)
93 : : {
94 : 0 : u32 tmp;
95 : 0 : unsigned long flags;
96 : :
97 : 0 : spin_lock_irqsave(&clk->lock, flags);
98 : :
99 : 0 : tmp = readl(clk->reg);
100 : 0 : tmp = (tmp & ~mask) | (val & mask);
101 : 0 : writel(tmp, clk->reg);
102 : :
103 : 0 : spin_unlock_irqrestore(&clk->lock, flags);
104 : 0 : }
105 : :
106 : 0 : static int plt_clk_set_parent(struct clk_hw *hw, u8 index)
107 : : {
108 : 0 : struct clk_plt *clk = to_clk_plt(hw);
109 : :
110 [ # # ]: 0 : plt_clk_reg_update(clk, PMC_MASK_CLK_FREQ, plt_parent_to_reg(index));
111 : :
112 : 0 : return 0;
113 : : }
114 : :
115 : 0 : static u8 plt_clk_get_parent(struct clk_hw *hw)
116 : : {
117 : 0 : struct clk_plt *clk = to_clk_plt(hw);
118 : 0 : u32 value;
119 : :
120 : 0 : value = readl(clk->reg);
121 : :
122 [ # # ]: 0 : return plt_reg_to_parent(value);
123 : : }
124 : :
125 : 0 : static int plt_clk_enable(struct clk_hw *hw)
126 : : {
127 : 0 : struct clk_plt *clk = to_clk_plt(hw);
128 : :
129 : 0 : plt_clk_reg_update(clk, PMC_MASK_CLK_CTL, PMC_CLK_CTL_FORCE_ON);
130 : :
131 : 0 : return 0;
132 : : }
133 : :
134 : 0 : static void plt_clk_disable(struct clk_hw *hw)
135 : : {
136 : 0 : struct clk_plt *clk = to_clk_plt(hw);
137 : :
138 : 0 : plt_clk_reg_update(clk, PMC_MASK_CLK_CTL, PMC_CLK_CTL_FORCE_OFF);
139 : 0 : }
140 : :
141 : 0 : static int plt_clk_is_enabled(struct clk_hw *hw)
142 : : {
143 : 0 : struct clk_plt *clk = to_clk_plt(hw);
144 : 0 : u32 value;
145 : :
146 : 0 : value = readl(clk->reg);
147 : :
148 [ # # ]: 0 : return plt_reg_to_enabled(value);
149 : : }
150 : :
151 : : static const struct clk_ops plt_clk_ops = {
152 : : .enable = plt_clk_enable,
153 : : .disable = plt_clk_disable,
154 : : .is_enabled = plt_clk_is_enabled,
155 : : .get_parent = plt_clk_get_parent,
156 : : .set_parent = plt_clk_set_parent,
157 : : .determine_rate = __clk_mux_determine_rate,
158 : : };
159 : :
160 : : static struct clk_plt *plt_clk_register(struct platform_device *pdev, int id,
161 : : const struct pmc_clk_data *pmc_data,
162 : : const char **parent_names,
163 : : int num_parents)
164 : : {
165 : : struct clk_plt *pclk;
166 : : struct clk_init_data init;
167 : : int ret;
168 : :
169 : : pclk = devm_kzalloc(&pdev->dev, sizeof(*pclk), GFP_KERNEL);
170 : : if (!pclk)
171 : : return ERR_PTR(-ENOMEM);
172 : :
173 : : init.name = kasprintf(GFP_KERNEL, "%s_%d", PLT_CLK_NAME_BASE, id);
174 : : init.ops = &plt_clk_ops;
175 : : init.flags = 0;
176 : : init.parent_names = parent_names;
177 : : init.num_parents = num_parents;
178 : :
179 : : pclk->hw.init = &init;
180 : : pclk->reg = pmc_data->base + PMC_CLK_CTL_OFFSET + id * PMC_CLK_CTL_SIZE;
181 : : spin_lock_init(&pclk->lock);
182 : :
183 : : /*
184 : : * On some systems, the pmc_plt_clocks already enabled by the
185 : : * firmware are being marked as critical to avoid them being
186 : : * gated by the clock framework.
187 : : */
188 : : if (pmc_data->critical && plt_clk_is_enabled(&pclk->hw))
189 : : init.flags |= CLK_IS_CRITICAL;
190 : :
191 : : ret = devm_clk_hw_register(&pdev->dev, &pclk->hw);
192 : : if (ret) {
193 : : pclk = ERR_PTR(ret);
194 : : goto err_free_init;
195 : : }
196 : :
197 : : pclk->lookup = clkdev_hw_create(&pclk->hw, init.name, NULL);
198 : : if (!pclk->lookup) {
199 : : pclk = ERR_PTR(-ENOMEM);
200 : : goto err_free_init;
201 : : }
202 : :
203 : : err_free_init:
204 : : kfree(init.name);
205 : : return pclk;
206 : : }
207 : :
208 : 0 : static void plt_clk_unregister(struct clk_plt *pclk)
209 : : {
210 : 0 : clkdev_drop(pclk->lookup);
211 : 0 : }
212 : :
213 : 0 : static struct clk_plt_fixed *plt_clk_register_fixed_rate(struct platform_device *pdev,
214 : : const char *name,
215 : : const char *parent_name,
216 : : unsigned long fixed_rate)
217 : : {
218 : 0 : struct clk_plt_fixed *pclk;
219 : :
220 : 0 : pclk = devm_kzalloc(&pdev->dev, sizeof(*pclk), GFP_KERNEL);
221 [ # # ]: 0 : if (!pclk)
222 : : return ERR_PTR(-ENOMEM);
223 : :
224 : 0 : pclk->clk = clk_hw_register_fixed_rate(&pdev->dev, name, parent_name,
225 : : 0, fixed_rate);
226 [ # # ]: 0 : if (IS_ERR(pclk->clk))
227 : : return ERR_CAST(pclk->clk);
228 : :
229 : 0 : pclk->lookup = clkdev_hw_create(pclk->clk, name, NULL);
230 [ # # ]: 0 : if (!pclk->lookup) {
231 : 0 : clk_hw_unregister_fixed_rate(pclk->clk);
232 : 0 : return ERR_PTR(-ENOMEM);
233 : : }
234 : :
235 : : return pclk;
236 : : }
237 : :
238 : : static void plt_clk_unregister_fixed_rate(struct clk_plt_fixed *pclk)
239 : : {
240 : : clkdev_drop(pclk->lookup);
241 : : clk_hw_unregister_fixed_rate(pclk->clk);
242 : : }
243 : :
244 : : static void plt_clk_unregister_fixed_rate_loop(struct clk_plt_data *data,
245 : : unsigned int i)
246 : : {
247 : : while (i--)
248 : : plt_clk_unregister_fixed_rate(data->parents[i]);
249 : : }
250 : :
251 : 0 : static void plt_clk_free_parent_names_loop(const char **parent_names,
252 : : unsigned int i)
253 : : {
254 [ # # ]: 0 : while (i--)
255 : 0 : kfree_const(parent_names[i]);
256 : 0 : kfree(parent_names);
257 : 0 : }
258 : :
259 : 0 : static void plt_clk_unregister_loop(struct clk_plt_data *data,
260 : : unsigned int i)
261 : : {
262 [ # # # # ]: 0 : while (i--)
263 : 0 : plt_clk_unregister(data->clks[i]);
264 : : }
265 : :
266 : 0 : static const char **plt_clk_register_parents(struct platform_device *pdev,
267 : : struct clk_plt_data *data,
268 : : const struct pmc_clk *clks)
269 : : {
270 : 0 : const char **parent_names;
271 : 0 : unsigned int i;
272 : 0 : int err;
273 : 0 : int nparents = 0;
274 : :
275 : 0 : data->nparents = 0;
276 [ # # ]: 0 : while (clks[nparents].name)
277 : 0 : nparents++;
278 : :
279 [ # # ]: 0 : data->parents = devm_kcalloc(&pdev->dev, nparents,
280 : : sizeof(*data->parents), GFP_KERNEL);
281 [ # # ]: 0 : if (!data->parents)
282 : : return ERR_PTR(-ENOMEM);
283 : :
284 : 0 : parent_names = kcalloc(nparents, sizeof(*parent_names),
285 : : GFP_KERNEL);
286 [ # # ]: 0 : if (!parent_names)
287 : : return ERR_PTR(-ENOMEM);
288 : :
289 [ # # ]: 0 : for (i = 0; i < nparents; i++) {
290 : 0 : data->parents[i] =
291 : 0 : plt_clk_register_fixed_rate(pdev, clks[i].name,
292 : : clks[i].parent_name,
293 : 0 : clks[i].freq);
294 [ # # ]: 0 : if (IS_ERR(data->parents[i])) {
295 : 0 : err = PTR_ERR(data->parents[i]);
296 : 0 : goto err_unreg;
297 : : }
298 : 0 : parent_names[i] = kstrdup_const(clks[i].name, GFP_KERNEL);
299 : : }
300 : :
301 : 0 : data->nparents = nparents;
302 : 0 : return parent_names;
303 : :
304 : : err_unreg:
305 : 0 : plt_clk_unregister_fixed_rate_loop(data, i);
306 : 0 : plt_clk_free_parent_names_loop(parent_names, i);
307 : 0 : return ERR_PTR(err);
308 : : }
309 : :
310 : 0 : static void plt_clk_unregister_parents(struct clk_plt_data *data)
311 : : {
312 : 0 : plt_clk_unregister_fixed_rate_loop(data, data->nparents);
313 : : }
314 : :
315 : 0 : static int plt_clk_probe(struct platform_device *pdev)
316 : : {
317 : 0 : const struct pmc_clk_data *pmc_data;
318 : 0 : const char **parent_names;
319 : 0 : struct clk_plt_data *data;
320 : 0 : unsigned int i;
321 : 0 : int err;
322 : :
323 [ # # ]: 0 : pmc_data = dev_get_platdata(&pdev->dev);
324 [ # # # # ]: 0 : if (!pmc_data || !pmc_data->clks)
325 : : return -EINVAL;
326 : :
327 : 0 : data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
328 [ # # ]: 0 : if (!data)
329 : : return -ENOMEM;
330 : :
331 : 0 : parent_names = plt_clk_register_parents(pdev, data, pmc_data->clks);
332 [ # # ]: 0 : if (IS_ERR(parent_names))
333 : 0 : return PTR_ERR(parent_names);
334 : :
335 [ # # ]: 0 : for (i = 0; i < PMC_CLK_NUM; i++) {
336 : 0 : data->clks[i] = plt_clk_register(pdev, i, pmc_data,
337 : 0 : parent_names, data->nparents);
338 [ # # ]: 0 : if (IS_ERR(data->clks[i])) {
339 : 0 : err = PTR_ERR(data->clks[i]);
340 : 0 : goto err_unreg_clk_plt;
341 : : }
342 : : }
343 : 0 : data->mclk_lookup = clkdev_hw_create(&data->clks[3]->hw, "mclk", NULL);
344 [ # # ]: 0 : if (!data->mclk_lookup) {
345 : 0 : err = -ENOMEM;
346 : 0 : goto err_unreg_clk_plt;
347 : : }
348 : :
349 : 0 : data->ether_clk_lookup = clkdev_hw_create(&data->clks[4]->hw,
350 : : "ether_clk", NULL);
351 [ # # ]: 0 : if (!data->ether_clk_lookup) {
352 : 0 : err = -ENOMEM;
353 : 0 : goto err_drop_mclk;
354 : : }
355 : :
356 : 0 : plt_clk_free_parent_names_loop(parent_names, data->nparents);
357 : :
358 : 0 : platform_set_drvdata(pdev, data);
359 : 0 : return 0;
360 : :
361 : : err_drop_mclk:
362 : 0 : clkdev_drop(data->mclk_lookup);
363 : 0 : err_unreg_clk_plt:
364 : 0 : plt_clk_unregister_loop(data, i);
365 : 0 : plt_clk_unregister_parents(data);
366 : 0 : plt_clk_free_parent_names_loop(parent_names, data->nparents);
367 : 0 : return err;
368 : : }
369 : :
370 : 0 : static int plt_clk_remove(struct platform_device *pdev)
371 : : {
372 : 0 : struct clk_plt_data *data;
373 : :
374 : 0 : data = platform_get_drvdata(pdev);
375 : :
376 : 0 : clkdev_drop(data->ether_clk_lookup);
377 : 0 : clkdev_drop(data->mclk_lookup);
378 : 0 : plt_clk_unregister_loop(data, PMC_CLK_NUM);
379 : 0 : plt_clk_unregister_parents(data);
380 : 0 : return 0;
381 : : }
382 : :
383 : : static struct platform_driver plt_clk_driver = {
384 : : .driver = {
385 : : .name = "clk-pmc-atom",
386 : : },
387 : : .probe = plt_clk_probe,
388 : : .remove = plt_clk_remove,
389 : : };
390 : 21 : builtin_platform_driver(plt_clk_driver);
|