1 /*
2  * Copyright (c) 2013 Heiko Stuebner <heiko@sntech.de>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation.
7  *
8  * Common Clock Framework support for s3c24xx external clock output.
9  */
10 
11 #include <linux/clkdev.h>
12 #include <linux/slab.h>
13 #include <linux/clk.h>
14 #include <linux/clk-provider.h>
15 #include <linux/platform_device.h>
16 #include <linux/module.h>
17 #include "clk.h"
18 
19 /* legacy access to misccr, until dt conversion is finished */
20 #include <mach/hardware.h>
21 #include <mach/regs-gpio.h>
22 
23 #define MUX_DCLK0	0
24 #define MUX_DCLK1	1
25 #define DIV_DCLK0	2
26 #define DIV_DCLK1	3
27 #define GATE_DCLK0	4
28 #define GATE_DCLK1	5
29 #define MUX_CLKOUT0	6
30 #define MUX_CLKOUT1	7
31 #define DCLK_MAX_CLKS	(MUX_CLKOUT1 + 1)
32 
33 enum supported_socs {
34 	S3C2410,
35 	S3C2412,
36 	S3C2440,
37 	S3C2443,
38 };
39 
40 struct s3c24xx_dclk_drv_data {
41 	const char **clkout0_parent_names;
42 	int clkout0_num_parents;
43 	const char **clkout1_parent_names;
44 	int clkout1_num_parents;
45 	const char **mux_parent_names;
46 	int mux_num_parents;
47 };
48 
49 /*
50  * Clock for output-parent selection in misccr
51  */
52 
53 struct s3c24xx_clkout {
54 	struct clk_hw		hw;
55 	u32			mask;
56 	u8			shift;
57 };
58 
59 #define to_s3c24xx_clkout(_hw) container_of(_hw, struct s3c24xx_clkout, hw)
60 
s3c24xx_clkout_get_parent(struct clk_hw * hw)61 static u8 s3c24xx_clkout_get_parent(struct clk_hw *hw)
62 {
63 	struct s3c24xx_clkout *clkout = to_s3c24xx_clkout(hw);
64 	int num_parents = clk_hw_get_num_parents(hw);
65 	u32 val;
66 
67 	val = readl_relaxed(S3C24XX_MISCCR) >> clkout->shift;
68 	val >>= clkout->shift;
69 	val &= clkout->mask;
70 
71 	if (val >= num_parents)
72 		return -EINVAL;
73 
74 	return val;
75 }
76 
s3c24xx_clkout_set_parent(struct clk_hw * hw,u8 index)77 static int s3c24xx_clkout_set_parent(struct clk_hw *hw, u8 index)
78 {
79 	struct s3c24xx_clkout *clkout = to_s3c24xx_clkout(hw);
80 
81 	s3c2410_modify_misccr((clkout->mask << clkout->shift),
82 			      (index << clkout->shift));
83 
84 	return 0;
85 }
86 
87 static const struct clk_ops s3c24xx_clkout_ops = {
88 	.get_parent = s3c24xx_clkout_get_parent,
89 	.set_parent = s3c24xx_clkout_set_parent,
90 	.determine_rate = __clk_mux_determine_rate,
91 };
92 
s3c24xx_register_clkout(struct device * dev,const char * name,const char ** parent_names,u8 num_parents,u8 shift,u32 mask)93 static struct clk_hw *s3c24xx_register_clkout(struct device *dev,
94 		const char *name, const char **parent_names, u8 num_parents,
95 		u8 shift, u32 mask)
96 {
97 	struct s3c24xx_clkout *clkout;
98 	struct clk_init_data init;
99 	int ret;
100 
101 	/* allocate the clkout */
102 	clkout = kzalloc(sizeof(*clkout), GFP_KERNEL);
103 	if (!clkout)
104 		return ERR_PTR(-ENOMEM);
105 
106 	init.name = name;
107 	init.ops = &s3c24xx_clkout_ops;
108 	init.flags = CLK_IS_BASIC;
109 	init.parent_names = parent_names;
110 	init.num_parents = num_parents;
111 
112 	clkout->shift = shift;
113 	clkout->mask = mask;
114 	clkout->hw.init = &init;
115 
116 	ret = clk_hw_register(dev, &clkout->hw);
117 	if (ret)
118 		return ERR_PTR(ret);
119 
120 	return &clkout->hw;
121 }
122 
123 /*
124  * dclk and clkout init
125  */
126 
127 struct s3c24xx_dclk {
128 	struct device *dev;
129 	void __iomem *base;
130 	struct notifier_block dclk0_div_change_nb;
131 	struct notifier_block dclk1_div_change_nb;
132 	spinlock_t dclk_lock;
133 	unsigned long reg_save;
134 	/* clk_data must be the last entry in the structure */
135 	struct clk_hw_onecell_data clk_data;
136 };
137 
138 #define to_s3c24xx_dclk0(x) \
139 		container_of(x, struct s3c24xx_dclk, dclk0_div_change_nb)
140 
141 #define to_s3c24xx_dclk1(x) \
142 		container_of(x, struct s3c24xx_dclk, dclk1_div_change_nb)
143 
144 static const char *dclk_s3c2410_p[] = { "pclk", "uclk" };
145 static const char *clkout0_s3c2410_p[] = { "mpll", "upll", "fclk", "hclk", "pclk",
146 			     "gate_dclk0" };
147 static const char *clkout1_s3c2410_p[] = { "mpll", "upll", "fclk", "hclk", "pclk",
148 			     "gate_dclk1" };
149 
150 static const char *clkout0_s3c2412_p[] = { "mpll", "upll", "rtc_clkout",
151 			     "hclk", "pclk", "gate_dclk0" };
152 static const char *clkout1_s3c2412_p[] = { "xti", "upll", "fclk", "hclk", "pclk",
153 			     "gate_dclk1" };
154 
155 static const char *clkout0_s3c2440_p[] = { "xti", "upll", "fclk", "hclk", "pclk",
156 			     "gate_dclk0" };
157 static const char *clkout1_s3c2440_p[] = { "mpll", "upll", "rtc_clkout",
158 			     "hclk", "pclk", "gate_dclk1" };
159 
160 static const char *dclk_s3c2443_p[] = { "pclk", "epll" };
161 static const char *clkout0_s3c2443_p[] = { "xti", "epll", "armclk", "hclk", "pclk",
162 			     "gate_dclk0" };
163 static const char *clkout1_s3c2443_p[] = { "dummy", "epll", "rtc_clkout",
164 			     "hclk", "pclk", "gate_dclk1" };
165 
166 #define DCLKCON_DCLK_DIV_MASK		0xf
167 #define DCLKCON_DCLK0_DIV_SHIFT		4
168 #define DCLKCON_DCLK0_CMP_SHIFT		8
169 #define DCLKCON_DCLK1_DIV_SHIFT		20
170 #define DCLKCON_DCLK1_CMP_SHIFT		24
171 
s3c24xx_dclk_update_cmp(struct s3c24xx_dclk * s3c24xx_dclk,int div_shift,int cmp_shift)172 static void s3c24xx_dclk_update_cmp(struct s3c24xx_dclk *s3c24xx_dclk,
173 				    int div_shift, int cmp_shift)
174 {
175 	unsigned long flags = 0;
176 	u32 dclk_con, div, cmp;
177 
178 	spin_lock_irqsave(&s3c24xx_dclk->dclk_lock, flags);
179 
180 	dclk_con = readl_relaxed(s3c24xx_dclk->base);
181 
182 	div = ((dclk_con >> div_shift) & DCLKCON_DCLK_DIV_MASK) + 1;
183 	cmp = ((div + 1) / 2) - 1;
184 
185 	dclk_con &= ~(DCLKCON_DCLK_DIV_MASK << cmp_shift);
186 	dclk_con |= (cmp << cmp_shift);
187 
188 	writel_relaxed(dclk_con, s3c24xx_dclk->base);
189 
190 	spin_unlock_irqrestore(&s3c24xx_dclk->dclk_lock, flags);
191 }
192 
s3c24xx_dclk0_div_notify(struct notifier_block * nb,unsigned long event,void * data)193 static int s3c24xx_dclk0_div_notify(struct notifier_block *nb,
194 			       unsigned long event, void *data)
195 {
196 	struct s3c24xx_dclk *s3c24xx_dclk = to_s3c24xx_dclk0(nb);
197 
198 	if (event == POST_RATE_CHANGE) {
199 		s3c24xx_dclk_update_cmp(s3c24xx_dclk,
200 			DCLKCON_DCLK0_DIV_SHIFT, DCLKCON_DCLK0_CMP_SHIFT);
201 	}
202 
203 	return NOTIFY_DONE;
204 }
205 
s3c24xx_dclk1_div_notify(struct notifier_block * nb,unsigned long event,void * data)206 static int s3c24xx_dclk1_div_notify(struct notifier_block *nb,
207 			       unsigned long event, void *data)
208 {
209 	struct s3c24xx_dclk *s3c24xx_dclk = to_s3c24xx_dclk1(nb);
210 
211 	if (event == POST_RATE_CHANGE) {
212 		s3c24xx_dclk_update_cmp(s3c24xx_dclk,
213 			DCLKCON_DCLK1_DIV_SHIFT, DCLKCON_DCLK1_CMP_SHIFT);
214 	}
215 
216 	return NOTIFY_DONE;
217 }
218 
219 #ifdef CONFIG_PM_SLEEP
s3c24xx_dclk_suspend(struct device * dev)220 static int s3c24xx_dclk_suspend(struct device *dev)
221 {
222 	struct s3c24xx_dclk *s3c24xx_dclk = dev_get_drvdata(dev);
223 
224 	s3c24xx_dclk->reg_save = readl_relaxed(s3c24xx_dclk->base);
225 	return 0;
226 }
227 
s3c24xx_dclk_resume(struct device * dev)228 static int s3c24xx_dclk_resume(struct device *dev)
229 {
230 	struct s3c24xx_dclk *s3c24xx_dclk = dev_get_drvdata(dev);
231 
232 	writel_relaxed(s3c24xx_dclk->reg_save, s3c24xx_dclk->base);
233 	return 0;
234 }
235 #endif
236 
237 static SIMPLE_DEV_PM_OPS(s3c24xx_dclk_pm_ops,
238 			 s3c24xx_dclk_suspend, s3c24xx_dclk_resume);
239 
s3c24xx_dclk_probe(struct platform_device * pdev)240 static int s3c24xx_dclk_probe(struct platform_device *pdev)
241 {
242 	struct s3c24xx_dclk *s3c24xx_dclk;
243 	struct resource *mem;
244 	struct s3c24xx_dclk_drv_data *dclk_variant;
245 	struct clk_hw **clk_table;
246 	int ret, i;
247 
248 	s3c24xx_dclk = devm_kzalloc(&pdev->dev,
249 				    struct_size(s3c24xx_dclk, clk_data.hws,
250 						DCLK_MAX_CLKS),
251 				    GFP_KERNEL);
252 	if (!s3c24xx_dclk)
253 		return -ENOMEM;
254 
255 	clk_table = s3c24xx_dclk->clk_data.hws;
256 
257 	s3c24xx_dclk->dev = &pdev->dev;
258 	s3c24xx_dclk->clk_data.num = DCLK_MAX_CLKS;
259 	platform_set_drvdata(pdev, s3c24xx_dclk);
260 	spin_lock_init(&s3c24xx_dclk->dclk_lock);
261 
262 	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
263 	s3c24xx_dclk->base = devm_ioremap_resource(&pdev->dev, mem);
264 	if (IS_ERR(s3c24xx_dclk->base))
265 		return PTR_ERR(s3c24xx_dclk->base);
266 
267 	dclk_variant = (struct s3c24xx_dclk_drv_data *)
268 				platform_get_device_id(pdev)->driver_data;
269 
270 
271 	clk_table[MUX_DCLK0] = clk_hw_register_mux(&pdev->dev, "mux_dclk0",
272 				dclk_variant->mux_parent_names,
273 				dclk_variant->mux_num_parents, 0,
274 				s3c24xx_dclk->base, 1, 1, 0,
275 				&s3c24xx_dclk->dclk_lock);
276 	clk_table[MUX_DCLK1] = clk_hw_register_mux(&pdev->dev, "mux_dclk1",
277 				dclk_variant->mux_parent_names,
278 				dclk_variant->mux_num_parents, 0,
279 				s3c24xx_dclk->base, 17, 1, 0,
280 				&s3c24xx_dclk->dclk_lock);
281 
282 	clk_table[DIV_DCLK0] = clk_hw_register_divider(&pdev->dev, "div_dclk0",
283 				"mux_dclk0", 0, s3c24xx_dclk->base,
284 				4, 4, 0, &s3c24xx_dclk->dclk_lock);
285 	clk_table[DIV_DCLK1] = clk_hw_register_divider(&pdev->dev, "div_dclk1",
286 				"mux_dclk1", 0, s3c24xx_dclk->base,
287 				20, 4, 0, &s3c24xx_dclk->dclk_lock);
288 
289 	clk_table[GATE_DCLK0] = clk_hw_register_gate(&pdev->dev, "gate_dclk0",
290 				"div_dclk0", CLK_SET_RATE_PARENT,
291 				s3c24xx_dclk->base, 0, 0,
292 				&s3c24xx_dclk->dclk_lock);
293 	clk_table[GATE_DCLK1] = clk_hw_register_gate(&pdev->dev, "gate_dclk1",
294 				"div_dclk1", CLK_SET_RATE_PARENT,
295 				s3c24xx_dclk->base, 16, 0,
296 				&s3c24xx_dclk->dclk_lock);
297 
298 	clk_table[MUX_CLKOUT0] = s3c24xx_register_clkout(&pdev->dev,
299 				"clkout0", dclk_variant->clkout0_parent_names,
300 				dclk_variant->clkout0_num_parents, 4, 7);
301 	clk_table[MUX_CLKOUT1] = s3c24xx_register_clkout(&pdev->dev,
302 				"clkout1", dclk_variant->clkout1_parent_names,
303 				dclk_variant->clkout1_num_parents, 8, 7);
304 
305 	for (i = 0; i < DCLK_MAX_CLKS; i++)
306 		if (IS_ERR(clk_table[i])) {
307 			dev_err(&pdev->dev, "clock %d failed to register\n", i);
308 			ret = PTR_ERR(clk_table[i]);
309 			goto err_clk_register;
310 		}
311 
312 	ret = clk_hw_register_clkdev(clk_table[MUX_DCLK0], "dclk0", NULL);
313 	if (!ret)
314 		ret = clk_hw_register_clkdev(clk_table[MUX_DCLK1], "dclk1",
315 					     NULL);
316 	if (!ret)
317 		ret = clk_hw_register_clkdev(clk_table[MUX_CLKOUT0],
318 					     "clkout0", NULL);
319 	if (!ret)
320 		ret = clk_hw_register_clkdev(clk_table[MUX_CLKOUT1],
321 					     "clkout1", NULL);
322 	if (ret) {
323 		dev_err(&pdev->dev, "failed to register aliases, %d\n", ret);
324 		goto err_clk_register;
325 	}
326 
327 	s3c24xx_dclk->dclk0_div_change_nb.notifier_call =
328 						s3c24xx_dclk0_div_notify;
329 
330 	s3c24xx_dclk->dclk1_div_change_nb.notifier_call =
331 						s3c24xx_dclk1_div_notify;
332 
333 	ret = clk_notifier_register(clk_table[DIV_DCLK0]->clk,
334 				    &s3c24xx_dclk->dclk0_div_change_nb);
335 	if (ret)
336 		goto err_clk_register;
337 
338 	ret = clk_notifier_register(clk_table[DIV_DCLK1]->clk,
339 				    &s3c24xx_dclk->dclk1_div_change_nb);
340 	if (ret)
341 		goto err_dclk_notify;
342 
343 	return 0;
344 
345 err_dclk_notify:
346 	clk_notifier_unregister(clk_table[DIV_DCLK0]->clk,
347 				&s3c24xx_dclk->dclk0_div_change_nb);
348 err_clk_register:
349 	for (i = 0; i < DCLK_MAX_CLKS; i++)
350 		if (clk_table[i] && !IS_ERR(clk_table[i]))
351 			clk_hw_unregister(clk_table[i]);
352 
353 	return ret;
354 }
355 
s3c24xx_dclk_remove(struct platform_device * pdev)356 static int s3c24xx_dclk_remove(struct platform_device *pdev)
357 {
358 	struct s3c24xx_dclk *s3c24xx_dclk = platform_get_drvdata(pdev);
359 	struct clk_hw **clk_table = s3c24xx_dclk->clk_data.hws;
360 	int i;
361 
362 	clk_notifier_unregister(clk_table[DIV_DCLK1]->clk,
363 				&s3c24xx_dclk->dclk1_div_change_nb);
364 	clk_notifier_unregister(clk_table[DIV_DCLK0]->clk,
365 				&s3c24xx_dclk->dclk0_div_change_nb);
366 
367 	for (i = 0; i < DCLK_MAX_CLKS; i++)
368 		clk_hw_unregister(clk_table[i]);
369 
370 	return 0;
371 }
372 
373 static struct s3c24xx_dclk_drv_data dclk_variants[] = {
374 	[S3C2410] = {
375 		.clkout0_parent_names = clkout0_s3c2410_p,
376 		.clkout0_num_parents = ARRAY_SIZE(clkout0_s3c2410_p),
377 		.clkout1_parent_names = clkout1_s3c2410_p,
378 		.clkout1_num_parents = ARRAY_SIZE(clkout1_s3c2410_p),
379 		.mux_parent_names = dclk_s3c2410_p,
380 		.mux_num_parents = ARRAY_SIZE(dclk_s3c2410_p),
381 	},
382 	[S3C2412] = {
383 		.clkout0_parent_names = clkout0_s3c2412_p,
384 		.clkout0_num_parents = ARRAY_SIZE(clkout0_s3c2412_p),
385 		.clkout1_parent_names = clkout1_s3c2412_p,
386 		.clkout1_num_parents = ARRAY_SIZE(clkout1_s3c2412_p),
387 		.mux_parent_names = dclk_s3c2410_p,
388 		.mux_num_parents = ARRAY_SIZE(dclk_s3c2410_p),
389 	},
390 	[S3C2440] = {
391 		.clkout0_parent_names = clkout0_s3c2440_p,
392 		.clkout0_num_parents = ARRAY_SIZE(clkout0_s3c2440_p),
393 		.clkout1_parent_names = clkout1_s3c2440_p,
394 		.clkout1_num_parents = ARRAY_SIZE(clkout1_s3c2440_p),
395 		.mux_parent_names = dclk_s3c2410_p,
396 		.mux_num_parents = ARRAY_SIZE(dclk_s3c2410_p),
397 	},
398 	[S3C2443] = {
399 		.clkout0_parent_names = clkout0_s3c2443_p,
400 		.clkout0_num_parents = ARRAY_SIZE(clkout0_s3c2443_p),
401 		.clkout1_parent_names = clkout1_s3c2443_p,
402 		.clkout1_num_parents = ARRAY_SIZE(clkout1_s3c2443_p),
403 		.mux_parent_names = dclk_s3c2443_p,
404 		.mux_num_parents = ARRAY_SIZE(dclk_s3c2443_p),
405 	},
406 };
407 
408 static const struct platform_device_id s3c24xx_dclk_driver_ids[] = {
409 	{
410 		.name		= "s3c2410-dclk",
411 		.driver_data	= (kernel_ulong_t)&dclk_variants[S3C2410],
412 	}, {
413 		.name		= "s3c2412-dclk",
414 		.driver_data	= (kernel_ulong_t)&dclk_variants[S3C2412],
415 	}, {
416 		.name		= "s3c2440-dclk",
417 		.driver_data	= (kernel_ulong_t)&dclk_variants[S3C2440],
418 	}, {
419 		.name		= "s3c2443-dclk",
420 		.driver_data	= (kernel_ulong_t)&dclk_variants[S3C2443],
421 	},
422 	{ }
423 };
424 
425 MODULE_DEVICE_TABLE(platform, s3c24xx_dclk_driver_ids);
426 
427 static struct platform_driver s3c24xx_dclk_driver = {
428 	.driver = {
429 		.name			= "s3c24xx-dclk",
430 		.pm			= &s3c24xx_dclk_pm_ops,
431 		.suppress_bind_attrs	= true,
432 	},
433 	.probe = s3c24xx_dclk_probe,
434 	.remove = s3c24xx_dclk_remove,
435 	.id_table = s3c24xx_dclk_driver_ids,
436 };
437 module_platform_driver(s3c24xx_dclk_driver);
438 
439 MODULE_LICENSE("GPL v2");
440 MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
441 MODULE_DESCRIPTION("Driver for the S3C24XX external clock outputs");
442