1 /*
2  * Copyright (c) 2013 Samsung Electronics Co., Ltd.
3  * Author: Padmavathi Venna <padma.v@samsung.com>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License version 2 as
7  * published by the Free Software Foundation.
8  *
9  * Common Clock Framework support for Audio Subsystem Clock Controller.
10 */
11 
12 #include <linux/slab.h>
13 #include <linux/io.h>
14 #include <linux/clk.h>
15 #include <linux/clk-provider.h>
16 #include <linux/of_address.h>
17 #include <linux/of_device.h>
18 #include <linux/module.h>
19 #include <linux/platform_device.h>
20 #include <linux/pm_runtime.h>
21 
22 #include <dt-bindings/clock/exynos-audss-clk.h>
23 
24 static DEFINE_SPINLOCK(lock);
25 static void __iomem *reg_base;
26 static struct clk_hw_onecell_data *clk_data;
27 /*
28  * On Exynos5420 this will be a clock which has to be enabled before any
29  * access to audss registers. Typically a child of EPLL.
30  *
31  * On other platforms this will be -ENODEV.
32  */
33 static struct clk *epll;
34 
35 #define ASS_CLK_SRC 0x0
36 #define ASS_CLK_DIV 0x4
37 #define ASS_CLK_GATE 0x8
38 
39 static unsigned long reg_save[][2] = {
40 	{ ASS_CLK_SRC,  0 },
41 	{ ASS_CLK_DIV,  0 },
42 	{ ASS_CLK_GATE, 0 },
43 };
44 
45 static int __maybe_unused exynos_audss_clk_suspend(struct device *dev)
46 {
47 	int i;
48 
49 	for (i = 0; i < ARRAY_SIZE(reg_save); i++)
50 		reg_save[i][1] = readl(reg_base + reg_save[i][0]);
51 
52 	return 0;
53 }
54 
55 static int __maybe_unused exynos_audss_clk_resume(struct device *dev)
56 {
57 	int i;
58 
59 	for (i = 0; i < ARRAY_SIZE(reg_save); i++)
60 		writel(reg_save[i][1], reg_base + reg_save[i][0]);
61 
62 	return 0;
63 }
64 
65 struct exynos_audss_clk_drvdata {
66 	unsigned int has_adma_clk:1;
67 	unsigned int has_mst_clk:1;
68 	unsigned int enable_epll:1;
69 	unsigned int num_clks;
70 };
71 
72 static const struct exynos_audss_clk_drvdata exynos4210_drvdata = {
73 	.num_clks	= EXYNOS_AUDSS_MAX_CLKS - 1,
74 	.enable_epll	= 1,
75 };
76 
77 static const struct exynos_audss_clk_drvdata exynos5410_drvdata = {
78 	.num_clks	= EXYNOS_AUDSS_MAX_CLKS - 1,
79 	.has_mst_clk	= 1,
80 };
81 
82 static const struct exynos_audss_clk_drvdata exynos5420_drvdata = {
83 	.num_clks	= EXYNOS_AUDSS_MAX_CLKS,
84 	.has_adma_clk	= 1,
85 	.enable_epll	= 1,
86 };
87 
88 static const struct of_device_id exynos_audss_clk_of_match[] = {
89 	{
90 		.compatible	= "samsung,exynos4210-audss-clock",
91 		.data		= &exynos4210_drvdata,
92 	}, {
93 		.compatible	= "samsung,exynos5250-audss-clock",
94 		.data		= &exynos4210_drvdata,
95 	}, {
96 		.compatible	= "samsung,exynos5410-audss-clock",
97 		.data		= &exynos5410_drvdata,
98 	}, {
99 		.compatible	= "samsung,exynos5420-audss-clock",
100 		.data		= &exynos5420_drvdata,
101 	},
102 	{ },
103 };
104 MODULE_DEVICE_TABLE(of, exynos_audss_clk_of_match);
105 
106 static void exynos_audss_clk_teardown(void)
107 {
108 	int i;
109 
110 	for (i = EXYNOS_MOUT_AUDSS; i < EXYNOS_DOUT_SRP; i++) {
111 		if (!IS_ERR(clk_data->hws[i]))
112 			clk_hw_unregister_mux(clk_data->hws[i]);
113 	}
114 
115 	for (; i < EXYNOS_SRP_CLK; i++) {
116 		if (!IS_ERR(clk_data->hws[i]))
117 			clk_hw_unregister_divider(clk_data->hws[i]);
118 	}
119 
120 	for (; i < clk_data->num; i++) {
121 		if (!IS_ERR(clk_data->hws[i]))
122 			clk_hw_unregister_gate(clk_data->hws[i]);
123 	}
124 }
125 
126 /* register exynos_audss clocks */
127 static int exynos_audss_clk_probe(struct platform_device *pdev)
128 {
129 	const char *mout_audss_p[] = {"fin_pll", "fout_epll"};
130 	const char *mout_i2s_p[] = {"mout_audss", "cdclk0", "sclk_audio0"};
131 	const char *sclk_pcm_p = "sclk_pcm0";
132 	struct clk *pll_ref, *pll_in, *cdclk, *sclk_audio, *sclk_pcm_in;
133 	const struct exynos_audss_clk_drvdata *variant;
134 	struct clk_hw **clk_table;
135 	struct resource *res;
136 	struct device *dev = &pdev->dev;
137 	int i, ret = 0;
138 
139 	variant = of_device_get_match_data(&pdev->dev);
140 	if (!variant)
141 		return -EINVAL;
142 
143 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
144 	reg_base = devm_ioremap_resource(dev, res);
145 	if (IS_ERR(reg_base))
146 		return PTR_ERR(reg_base);
147 
148 	epll = ERR_PTR(-ENODEV);
149 
150 	clk_data = devm_kzalloc(dev,
151 				struct_size(clk_data, hws,
152 					    EXYNOS_AUDSS_MAX_CLKS),
153 				GFP_KERNEL);
154 	if (!clk_data)
155 		return -ENOMEM;
156 
157 	clk_data->num = variant->num_clks;
158 	clk_table = clk_data->hws;
159 
160 	pll_ref = devm_clk_get(dev, "pll_ref");
161 	pll_in = devm_clk_get(dev, "pll_in");
162 	if (!IS_ERR(pll_ref))
163 		mout_audss_p[0] = __clk_get_name(pll_ref);
164 	if (!IS_ERR(pll_in)) {
165 		mout_audss_p[1] = __clk_get_name(pll_in);
166 
167 		if (variant->enable_epll) {
168 			epll = pll_in;
169 
170 			ret = clk_prepare_enable(epll);
171 			if (ret) {
172 				dev_err(dev,
173 					"failed to prepare the epll clock\n");
174 				return ret;
175 			}
176 		}
177 	}
178 
179 	/*
180 	 * Enable runtime PM here to allow the clock core using runtime PM
181 	 * for the registered clocks. Additionally, we increase the runtime
182 	 * PM usage count before registering the clocks, to prevent the
183 	 * clock core from runtime suspending the device.
184 	 */
185 	pm_runtime_get_noresume(dev);
186 	pm_runtime_set_active(dev);
187 	pm_runtime_enable(dev);
188 
189 	clk_table[EXYNOS_MOUT_AUDSS] = clk_hw_register_mux(dev, "mout_audss",
190 				mout_audss_p, ARRAY_SIZE(mout_audss_p),
191 				CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT,
192 				reg_base + ASS_CLK_SRC, 0, 1, 0, &lock);
193 
194 	cdclk = devm_clk_get(dev, "cdclk");
195 	sclk_audio = devm_clk_get(dev, "sclk_audio");
196 	if (!IS_ERR(cdclk))
197 		mout_i2s_p[1] = __clk_get_name(cdclk);
198 	if (!IS_ERR(sclk_audio))
199 		mout_i2s_p[2] = __clk_get_name(sclk_audio);
200 	clk_table[EXYNOS_MOUT_I2S] = clk_hw_register_mux(dev, "mout_i2s",
201 				mout_i2s_p, ARRAY_SIZE(mout_i2s_p),
202 				CLK_SET_RATE_NO_REPARENT,
203 				reg_base + ASS_CLK_SRC, 2, 2, 0, &lock);
204 
205 	clk_table[EXYNOS_DOUT_SRP] = clk_hw_register_divider(dev, "dout_srp",
206 				"mout_audss", CLK_SET_RATE_PARENT,
207 				reg_base + ASS_CLK_DIV, 0, 4, 0, &lock);
208 
209 	clk_table[EXYNOS_DOUT_AUD_BUS] = clk_hw_register_divider(dev,
210 				"dout_aud_bus", "dout_srp", CLK_SET_RATE_PARENT,
211 				reg_base + ASS_CLK_DIV, 4, 4, 0, &lock);
212 
213 	clk_table[EXYNOS_DOUT_I2S] = clk_hw_register_divider(dev, "dout_i2s",
214 				"mout_i2s", 0, reg_base + ASS_CLK_DIV, 8, 4, 0,
215 				&lock);
216 
217 	clk_table[EXYNOS_SRP_CLK] = clk_hw_register_gate(dev, "srp_clk",
218 				"dout_srp", CLK_SET_RATE_PARENT,
219 				reg_base + ASS_CLK_GATE, 0, 0, &lock);
220 
221 	clk_table[EXYNOS_I2S_BUS] = clk_hw_register_gate(dev, "i2s_bus",
222 				"dout_aud_bus", CLK_SET_RATE_PARENT,
223 				reg_base + ASS_CLK_GATE, 2, 0, &lock);
224 
225 	clk_table[EXYNOS_SCLK_I2S] = clk_hw_register_gate(dev, "sclk_i2s",
226 				"dout_i2s", CLK_SET_RATE_PARENT,
227 				reg_base + ASS_CLK_GATE, 3, 0, &lock);
228 
229 	clk_table[EXYNOS_PCM_BUS] = clk_hw_register_gate(dev, "pcm_bus",
230 				 "sclk_pcm", CLK_SET_RATE_PARENT,
231 				reg_base + ASS_CLK_GATE, 4, 0, &lock);
232 
233 	sclk_pcm_in = devm_clk_get(dev, "sclk_pcm_in");
234 	if (!IS_ERR(sclk_pcm_in))
235 		sclk_pcm_p = __clk_get_name(sclk_pcm_in);
236 	clk_table[EXYNOS_SCLK_PCM] = clk_hw_register_gate(dev, "sclk_pcm",
237 				sclk_pcm_p, CLK_SET_RATE_PARENT,
238 				reg_base + ASS_CLK_GATE, 5, 0, &lock);
239 
240 	if (variant->has_adma_clk) {
241 		clk_table[EXYNOS_ADMA] = clk_hw_register_gate(dev, "adma",
242 				"dout_srp", CLK_SET_RATE_PARENT,
243 				reg_base + ASS_CLK_GATE, 9, 0, &lock);
244 	}
245 
246 	for (i = 0; i < clk_data->num; i++) {
247 		if (IS_ERR(clk_table[i])) {
248 			dev_err(dev, "failed to register clock %d\n", i);
249 			ret = PTR_ERR(clk_table[i]);
250 			goto unregister;
251 		}
252 	}
253 
254 	ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get,
255 				     clk_data);
256 	if (ret) {
257 		dev_err(dev, "failed to add clock provider\n");
258 		goto unregister;
259 	}
260 
261 	pm_runtime_put_sync(dev);
262 
263 	return 0;
264 
265 unregister:
266 	exynos_audss_clk_teardown();
267 	pm_runtime_put_sync(dev);
268 	pm_runtime_disable(dev);
269 
270 	if (!IS_ERR(epll))
271 		clk_disable_unprepare(epll);
272 
273 	return ret;
274 }
275 
276 static int exynos_audss_clk_remove(struct platform_device *pdev)
277 {
278 	of_clk_del_provider(pdev->dev.of_node);
279 
280 	exynos_audss_clk_teardown();
281 	pm_runtime_disable(&pdev->dev);
282 
283 	if (!IS_ERR(epll))
284 		clk_disable_unprepare(epll);
285 
286 	return 0;
287 }
288 
289 static const struct dev_pm_ops exynos_audss_clk_pm_ops = {
290 	SET_RUNTIME_PM_OPS(exynos_audss_clk_suspend, exynos_audss_clk_resume,
291 			   NULL)
292 	SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
293 				     pm_runtime_force_resume)
294 };
295 
296 static struct platform_driver exynos_audss_clk_driver = {
297 	.driver	= {
298 		.name = "exynos-audss-clk",
299 		.of_match_table = exynos_audss_clk_of_match,
300 		.pm = &exynos_audss_clk_pm_ops,
301 	},
302 	.probe = exynos_audss_clk_probe,
303 	.remove = exynos_audss_clk_remove,
304 };
305 
306 module_platform_driver(exynos_audss_clk_driver);
307 
308 MODULE_AUTHOR("Padmavathi Venna <padma.v@samsung.com>");
309 MODULE_DESCRIPTION("Exynos Audio Subsystem Clock Controller");
310 MODULE_LICENSE("GPL v2");
311 MODULE_ALIAS("platform:exynos-audss-clk");
312