]> git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/blob - drivers/clk/samsung/clk-exynos-audss.c
Merge tag 'clk-for-linus-3.19' of git://git.linaro.org/people/mike.turquette/linux
[mirror_ubuntu-zesty-kernel.git] / drivers / clk / samsung / clk-exynos-audss.c
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/clkdev.h>
13 #include <linux/io.h>
14 #include <linux/clk-provider.h>
15 #include <linux/of_address.h>
16 #include <linux/syscore_ops.h>
17 #include <linux/module.h>
18 #include <linux/platform_device.h>
19
20 #include <dt-bindings/clock/exynos-audss-clk.h>
21
22 enum exynos_audss_clk_type {
23 TYPE_EXYNOS4210,
24 TYPE_EXYNOS5250,
25 TYPE_EXYNOS5420,
26 };
27
28 static DEFINE_SPINLOCK(lock);
29 static struct clk **clk_table;
30 static void __iomem *reg_base;
31 static struct clk_onecell_data clk_data;
32 /*
33 * On Exynos5420 this will be a clock which has to be enabled before any
34 * access to audss registers. Typically a child of EPLL.
35 *
36 * On other platforms this will be -ENODEV.
37 */
38 static struct clk *epll;
39
40 #define ASS_CLK_SRC 0x0
41 #define ASS_CLK_DIV 0x4
42 #define ASS_CLK_GATE 0x8
43
44 #ifdef CONFIG_PM_SLEEP
45 static unsigned long reg_save[][2] = {
46 {ASS_CLK_SRC, 0},
47 {ASS_CLK_DIV, 0},
48 {ASS_CLK_GATE, 0},
49 };
50
51 static int exynos_audss_clk_suspend(void)
52 {
53 int i;
54
55 for (i = 0; i < ARRAY_SIZE(reg_save); i++)
56 reg_save[i][1] = readl(reg_base + reg_save[i][0]);
57
58 return 0;
59 }
60
61 static void exynos_audss_clk_resume(void)
62 {
63 int i;
64
65 for (i = 0; i < ARRAY_SIZE(reg_save); i++)
66 writel(reg_save[i][1], reg_base + reg_save[i][0]);
67 }
68
69 static struct syscore_ops exynos_audss_clk_syscore_ops = {
70 .suspend = exynos_audss_clk_suspend,
71 .resume = exynos_audss_clk_resume,
72 };
73 #endif /* CONFIG_PM_SLEEP */
74
75 static const struct of_device_id exynos_audss_clk_of_match[] = {
76 { .compatible = "samsung,exynos4210-audss-clock",
77 .data = (void *)TYPE_EXYNOS4210, },
78 { .compatible = "samsung,exynos5250-audss-clock",
79 .data = (void *)TYPE_EXYNOS5250, },
80 { .compatible = "samsung,exynos5420-audss-clock",
81 .data = (void *)TYPE_EXYNOS5420, },
82 {},
83 };
84
85 /* register exynos_audss clocks */
86 static int exynos_audss_clk_probe(struct platform_device *pdev)
87 {
88 int i, ret = 0;
89 struct resource *res;
90 const char *mout_audss_p[] = {"fin_pll", "fout_epll"};
91 const char *mout_i2s_p[] = {"mout_audss", "cdclk0", "sclk_audio0"};
92 const char *sclk_pcm_p = "sclk_pcm0";
93 struct clk *pll_ref, *pll_in, *cdclk, *sclk_audio, *sclk_pcm_in;
94 const struct of_device_id *match;
95 enum exynos_audss_clk_type variant;
96
97 match = of_match_node(exynos_audss_clk_of_match, pdev->dev.of_node);
98 if (!match)
99 return -EINVAL;
100 variant = (enum exynos_audss_clk_type)match->data;
101
102 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
103 reg_base = devm_ioremap_resource(&pdev->dev, res);
104 if (IS_ERR(reg_base)) {
105 dev_err(&pdev->dev, "failed to map audss registers\n");
106 return PTR_ERR(reg_base);
107 }
108 /* EPLL don't have to be enabled for boards other than Exynos5420 */
109 epll = ERR_PTR(-ENODEV);
110
111 clk_table = devm_kzalloc(&pdev->dev,
112 sizeof(struct clk *) * EXYNOS_AUDSS_MAX_CLKS,
113 GFP_KERNEL);
114 if (!clk_table)
115 return -ENOMEM;
116
117 clk_data.clks = clk_table;
118 if (variant == TYPE_EXYNOS5420)
119 clk_data.clk_num = EXYNOS_AUDSS_MAX_CLKS;
120 else
121 clk_data.clk_num = EXYNOS_AUDSS_MAX_CLKS - 1;
122
123 pll_ref = devm_clk_get(&pdev->dev, "pll_ref");
124 pll_in = devm_clk_get(&pdev->dev, "pll_in");
125 if (!IS_ERR(pll_ref))
126 mout_audss_p[0] = __clk_get_name(pll_ref);
127 if (!IS_ERR(pll_in)) {
128 mout_audss_p[1] = __clk_get_name(pll_in);
129
130 if (variant == TYPE_EXYNOS5420) {
131 epll = pll_in;
132
133 ret = clk_prepare_enable(epll);
134 if (ret) {
135 dev_err(&pdev->dev,
136 "failed to prepare the epll clock\n");
137 return ret;
138 }
139 }
140 }
141 clk_table[EXYNOS_MOUT_AUDSS] = clk_register_mux(NULL, "mout_audss",
142 mout_audss_p, ARRAY_SIZE(mout_audss_p),
143 CLK_SET_RATE_NO_REPARENT,
144 reg_base + ASS_CLK_SRC, 0, 1, 0, &lock);
145
146 cdclk = devm_clk_get(&pdev->dev, "cdclk");
147 sclk_audio = devm_clk_get(&pdev->dev, "sclk_audio");
148 if (!IS_ERR(cdclk))
149 mout_i2s_p[1] = __clk_get_name(cdclk);
150 if (!IS_ERR(sclk_audio))
151 mout_i2s_p[2] = __clk_get_name(sclk_audio);
152 clk_table[EXYNOS_MOUT_I2S] = clk_register_mux(NULL, "mout_i2s",
153 mout_i2s_p, ARRAY_SIZE(mout_i2s_p),
154 CLK_SET_RATE_NO_REPARENT,
155 reg_base + ASS_CLK_SRC, 2, 2, 0, &lock);
156
157 clk_table[EXYNOS_DOUT_SRP] = clk_register_divider(NULL, "dout_srp",
158 "mout_audss", 0, reg_base + ASS_CLK_DIV, 0, 4,
159 0, &lock);
160
161 clk_table[EXYNOS_DOUT_AUD_BUS] = clk_register_divider(NULL,
162 "dout_aud_bus", "dout_srp", 0,
163 reg_base + ASS_CLK_DIV, 4, 4, 0, &lock);
164
165 clk_table[EXYNOS_DOUT_I2S] = clk_register_divider(NULL, "dout_i2s",
166 "mout_i2s", 0, reg_base + ASS_CLK_DIV, 8, 4, 0,
167 &lock);
168
169 clk_table[EXYNOS_SRP_CLK] = clk_register_gate(NULL, "srp_clk",
170 "dout_srp", CLK_SET_RATE_PARENT,
171 reg_base + ASS_CLK_GATE, 0, 0, &lock);
172
173 clk_table[EXYNOS_I2S_BUS] = clk_register_gate(NULL, "i2s_bus",
174 "dout_aud_bus", CLK_SET_RATE_PARENT,
175 reg_base + ASS_CLK_GATE, 2, 0, &lock);
176
177 clk_table[EXYNOS_SCLK_I2S] = clk_register_gate(NULL, "sclk_i2s",
178 "dout_i2s", CLK_SET_RATE_PARENT,
179 reg_base + ASS_CLK_GATE, 3, 0, &lock);
180
181 clk_table[EXYNOS_PCM_BUS] = clk_register_gate(NULL, "pcm_bus",
182 "sclk_pcm", CLK_SET_RATE_PARENT,
183 reg_base + ASS_CLK_GATE, 4, 0, &lock);
184
185 sclk_pcm_in = devm_clk_get(&pdev->dev, "sclk_pcm_in");
186 if (!IS_ERR(sclk_pcm_in))
187 sclk_pcm_p = __clk_get_name(sclk_pcm_in);
188 clk_table[EXYNOS_SCLK_PCM] = clk_register_gate(NULL, "sclk_pcm",
189 sclk_pcm_p, CLK_SET_RATE_PARENT,
190 reg_base + ASS_CLK_GATE, 5, 0, &lock);
191
192 if (variant == TYPE_EXYNOS5420) {
193 clk_table[EXYNOS_ADMA] = clk_register_gate(NULL, "adma",
194 "dout_srp", CLK_SET_RATE_PARENT,
195 reg_base + ASS_CLK_GATE, 9, 0, &lock);
196 }
197
198 for (i = 0; i < clk_data.clk_num; i++) {
199 if (IS_ERR(clk_table[i])) {
200 dev_err(&pdev->dev, "failed to register clock %d\n", i);
201 ret = PTR_ERR(clk_table[i]);
202 goto unregister;
203 }
204 }
205
206 ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_onecell_get,
207 &clk_data);
208 if (ret) {
209 dev_err(&pdev->dev, "failed to add clock provider\n");
210 goto unregister;
211 }
212
213 #ifdef CONFIG_PM_SLEEP
214 register_syscore_ops(&exynos_audss_clk_syscore_ops);
215 #endif
216
217 dev_info(&pdev->dev, "setup completed\n");
218
219 return 0;
220
221 unregister:
222 for (i = 0; i < clk_data.clk_num; i++) {
223 if (!IS_ERR(clk_table[i]))
224 clk_unregister(clk_table[i]);
225 }
226
227 if (!IS_ERR(epll))
228 clk_disable_unprepare(epll);
229
230 return ret;
231 }
232
233 static int exynos_audss_clk_remove(struct platform_device *pdev)
234 {
235 int i;
236
237 #ifdef CONFIG_PM_SLEEP
238 unregister_syscore_ops(&exynos_audss_clk_syscore_ops);
239 #endif
240
241 of_clk_del_provider(pdev->dev.of_node);
242
243 for (i = 0; i < clk_data.clk_num; i++) {
244 if (!IS_ERR(clk_table[i]))
245 clk_unregister(clk_table[i]);
246 }
247
248 if (!IS_ERR(epll))
249 clk_disable_unprepare(epll);
250
251 return 0;
252 }
253
254 static struct platform_driver exynos_audss_clk_driver = {
255 .driver = {
256 .name = "exynos-audss-clk",
257 .of_match_table = exynos_audss_clk_of_match,
258 },
259 .probe = exynos_audss_clk_probe,
260 .remove = exynos_audss_clk_remove,
261 };
262
263 static int __init exynos_audss_clk_init(void)
264 {
265 return platform_driver_register(&exynos_audss_clk_driver);
266 }
267 core_initcall(exynos_audss_clk_init);
268
269 static void __exit exynos_audss_clk_exit(void)
270 {
271 platform_driver_unregister(&exynos_audss_clk_driver);
272 }
273 module_exit(exynos_audss_clk_exit);
274
275 MODULE_AUTHOR("Padmavathi Venna <padma.v@samsung.com>");
276 MODULE_DESCRIPTION("Exynos Audio Subsystem Clock Controller");
277 MODULE_LICENSE("GPL v2");
278 MODULE_ALIAS("platform:exynos-audss-clk");