diff options
author | Tomasz Figa <t.figa@samsung.com> | 2014-07-02 13:28:27 -0400 |
---|---|---|
committer | Kukjin Kim <kgene.kim@samsung.com> | 2014-07-18 15:25:08 -0400 |
commit | 9978f28f695adb63fa1726744a7f95e12920e8c9 (patch) | |
tree | b235bb8bc00105038d8b15dfca69d076586279cd /drivers | |
parent | 32726d2d5502302ba5753854f5f2f12ba22681c4 (diff) |
clk: samsung: Add S5PV210 Audio Subsystem clock driver
This patch adds a driver for clock controller being a part of Audio
Subsystem present on S5PV210 and compatible SoCs. It is used to provide
clocks for other IP blocks of this subsystem.
Signed-off-by: Tomasz Figa <t.figa@samsung.com>
Signed-off-by: Kukjin Kim <kgene.kim@samsung.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/clk/samsung/Makefile | 2 | ||||
-rw-r--r-- | drivers/clk/samsung/clk-s5pv210-audss.c | 241 |
2 files changed, 242 insertions, 1 deletions
diff --git a/drivers/clk/samsung/Makefile b/drivers/clk/samsung/Makefile index 49d6ce1ea107..9f256a4ba775 100644 --- a/drivers/clk/samsung/Makefile +++ b/drivers/clk/samsung/Makefile | |||
@@ -16,4 +16,4 @@ obj-$(CONFIG_S3C2410_COMMON_DCLK)+= clk-s3c2410-dclk.o | |||
16 | obj-$(CONFIG_S3C2412_COMMON_CLK)+= clk-s3c2412.o | 16 | obj-$(CONFIG_S3C2412_COMMON_CLK)+= clk-s3c2412.o |
17 | obj-$(CONFIG_S3C2443_COMMON_CLK)+= clk-s3c2443.o | 17 | obj-$(CONFIG_S3C2443_COMMON_CLK)+= clk-s3c2443.o |
18 | obj-$(CONFIG_ARCH_S3C64XX) += clk-s3c64xx.o | 18 | obj-$(CONFIG_ARCH_S3C64XX) += clk-s3c64xx.o |
19 | obj-$(CONFIG_ARCH_S5PV210) += clk-s5pv210.o | 19 | obj-$(CONFIG_ARCH_S5PV210) += clk-s5pv210.o clk-s5pv210-audss.o |
diff --git a/drivers/clk/samsung/clk-s5pv210-audss.c b/drivers/clk/samsung/clk-s5pv210-audss.c new file mode 100644 index 000000000000..a8053b4aca56 --- /dev/null +++ b/drivers/clk/samsung/clk-s5pv210-audss.c | |||
@@ -0,0 +1,241 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2014 Tomasz Figa <t.figa@samsung.com> | ||
3 | * | ||
4 | * Based on Exynos Audio Subsystem Clock Controller driver: | ||
5 | * | ||
6 | * Copyright (c) 2013 Samsung Electronics Co., Ltd. | ||
7 | * Author: Padmavathi Venna <padma.v@samsung.com> | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License version 2 as | ||
11 | * published by the Free Software Foundation. | ||
12 | * | ||
13 | * Driver for Audio Subsystem Clock Controller of S5PV210-compatible SoCs. | ||
14 | */ | ||
15 | |||
16 | #include <linux/clkdev.h> | ||
17 | #include <linux/io.h> | ||
18 | #include <linux/clk-provider.h> | ||
19 | #include <linux/of_address.h> | ||
20 | #include <linux/syscore_ops.h> | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/platform_device.h> | ||
23 | |||
24 | #include <dt-bindings/clock/s5pv210-audss.h> | ||
25 | |||
26 | static DEFINE_SPINLOCK(lock); | ||
27 | static struct clk **clk_table; | ||
28 | static void __iomem *reg_base; | ||
29 | static struct clk_onecell_data clk_data; | ||
30 | |||
31 | #define ASS_CLK_SRC 0x0 | ||
32 | #define ASS_CLK_DIV 0x4 | ||
33 | #define ASS_CLK_GATE 0x8 | ||
34 | |||
35 | #ifdef CONFIG_PM_SLEEP | ||
36 | static unsigned long reg_save[][2] = { | ||
37 | {ASS_CLK_SRC, 0}, | ||
38 | {ASS_CLK_DIV, 0}, | ||
39 | {ASS_CLK_GATE, 0}, | ||
40 | }; | ||
41 | |||
42 | static int s5pv210_audss_clk_suspend(void) | ||
43 | { | ||
44 | int i; | ||
45 | |||
46 | for (i = 0; i < ARRAY_SIZE(reg_save); i++) | ||
47 | reg_save[i][1] = readl(reg_base + reg_save[i][0]); | ||
48 | |||
49 | return 0; | ||
50 | } | ||
51 | |||
52 | static void s5pv210_audss_clk_resume(void) | ||
53 | { | ||
54 | int i; | ||
55 | |||
56 | for (i = 0; i < ARRAY_SIZE(reg_save); i++) | ||
57 | writel(reg_save[i][1], reg_base + reg_save[i][0]); | ||
58 | } | ||
59 | |||
60 | static struct syscore_ops s5pv210_audss_clk_syscore_ops = { | ||
61 | .suspend = s5pv210_audss_clk_suspend, | ||
62 | .resume = s5pv210_audss_clk_resume, | ||
63 | }; | ||
64 | #endif /* CONFIG_PM_SLEEP */ | ||
65 | |||
66 | /* register s5pv210_audss clocks */ | ||
67 | static int s5pv210_audss_clk_probe(struct platform_device *pdev) | ||
68 | { | ||
69 | int i, ret = 0; | ||
70 | struct resource *res; | ||
71 | const char *mout_audss_p[2]; | ||
72 | const char *mout_i2s_p[3]; | ||
73 | const char *hclk_p; | ||
74 | struct clk *hclk, *pll_ref, *pll_in, *cdclk, *sclk_audio; | ||
75 | |||
76 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
77 | reg_base = devm_ioremap_resource(&pdev->dev, res); | ||
78 | if (IS_ERR(reg_base)) { | ||
79 | dev_err(&pdev->dev, "failed to map audss registers\n"); | ||
80 | return PTR_ERR(reg_base); | ||
81 | } | ||
82 | |||
83 | clk_table = devm_kzalloc(&pdev->dev, | ||
84 | sizeof(struct clk *) * AUDSS_MAX_CLKS, | ||
85 | GFP_KERNEL); | ||
86 | if (!clk_table) | ||
87 | return -ENOMEM; | ||
88 | |||
89 | clk_data.clks = clk_table; | ||
90 | clk_data.clk_num = AUDSS_MAX_CLKS; | ||
91 | |||
92 | hclk = devm_clk_get(&pdev->dev, "hclk"); | ||
93 | if (IS_ERR(hclk)) { | ||
94 | dev_err(&pdev->dev, "failed to get hclk clock\n"); | ||
95 | return PTR_ERR(hclk); | ||
96 | } | ||
97 | |||
98 | pll_in = devm_clk_get(&pdev->dev, "fout_epll"); | ||
99 | if (IS_ERR(pll_in)) { | ||
100 | dev_err(&pdev->dev, "failed to get fout_epll clock\n"); | ||
101 | return PTR_ERR(pll_in); | ||
102 | } | ||
103 | |||
104 | sclk_audio = devm_clk_get(&pdev->dev, "sclk_audio0"); | ||
105 | if (IS_ERR(sclk_audio)) { | ||
106 | dev_err(&pdev->dev, "failed to get sclk_audio0 clock\n"); | ||
107 | return PTR_ERR(sclk_audio); | ||
108 | } | ||
109 | |||
110 | /* iiscdclk0 is an optional external I2S codec clock */ | ||
111 | cdclk = devm_clk_get(&pdev->dev, "iiscdclk0"); | ||
112 | pll_ref = devm_clk_get(&pdev->dev, "xxti"); | ||
113 | |||
114 | if (!IS_ERR(pll_ref)) | ||
115 | mout_audss_p[0] = __clk_get_name(pll_ref); | ||
116 | else | ||
117 | mout_audss_p[0] = "xxti"; | ||
118 | mout_audss_p[1] = __clk_get_name(pll_in); | ||
119 | clk_table[CLK_MOUT_AUDSS] = clk_register_mux(NULL, "mout_audss", | ||
120 | mout_audss_p, ARRAY_SIZE(mout_audss_p), | ||
121 | CLK_SET_RATE_NO_REPARENT, | ||
122 | reg_base + ASS_CLK_SRC, 0, 1, 0, &lock); | ||
123 | |||
124 | mout_i2s_p[0] = "mout_audss"; | ||
125 | if (!IS_ERR(cdclk)) | ||
126 | mout_i2s_p[1] = __clk_get_name(cdclk); | ||
127 | else | ||
128 | mout_i2s_p[1] = "iiscdclk0"; | ||
129 | mout_i2s_p[2] = __clk_get_name(sclk_audio); | ||
130 | clk_table[CLK_MOUT_I2S_A] = clk_register_mux(NULL, "mout_i2s_audss", | ||
131 | mout_i2s_p, ARRAY_SIZE(mout_i2s_p), | ||
132 | CLK_SET_RATE_NO_REPARENT, | ||
133 | reg_base + ASS_CLK_SRC, 2, 2, 0, &lock); | ||
134 | |||
135 | clk_table[CLK_DOUT_AUD_BUS] = clk_register_divider(NULL, | ||
136 | "dout_aud_bus", "mout_audss", 0, | ||
137 | reg_base + ASS_CLK_DIV, 0, 4, 0, &lock); | ||
138 | clk_table[CLK_DOUT_I2S_A] = clk_register_divider(NULL, "dout_i2s_audss", | ||
139 | "mout_i2s_audss", 0, reg_base + ASS_CLK_DIV, | ||
140 | 4, 4, 0, &lock); | ||
141 | |||
142 | clk_table[CLK_I2S] = clk_register_gate(NULL, "i2s_audss", | ||
143 | "dout_i2s_audss", CLK_SET_RATE_PARENT, | ||
144 | reg_base + ASS_CLK_GATE, 6, 0, &lock); | ||
145 | |||
146 | hclk_p = __clk_get_name(hclk); | ||
147 | |||
148 | clk_table[CLK_HCLK_I2S] = clk_register_gate(NULL, "hclk_i2s_audss", | ||
149 | hclk_p, CLK_IGNORE_UNUSED, | ||
150 | reg_base + ASS_CLK_GATE, 5, 0, &lock); | ||
151 | clk_table[CLK_HCLK_UART] = clk_register_gate(NULL, "hclk_uart_audss", | ||
152 | hclk_p, CLK_IGNORE_UNUSED, | ||
153 | reg_base + ASS_CLK_GATE, 4, 0, &lock); | ||
154 | clk_table[CLK_HCLK_HWA] = clk_register_gate(NULL, "hclk_hwa_audss", | ||
155 | hclk_p, CLK_IGNORE_UNUSED, | ||
156 | reg_base + ASS_CLK_GATE, 3, 0, &lock); | ||
157 | clk_table[CLK_HCLK_DMA] = clk_register_gate(NULL, "hclk_dma_audss", | ||
158 | hclk_p, CLK_IGNORE_UNUSED, | ||
159 | reg_base + ASS_CLK_GATE, 2, 0, &lock); | ||
160 | clk_table[CLK_HCLK_BUF] = clk_register_gate(NULL, "hclk_buf_audss", | ||
161 | hclk_p, CLK_IGNORE_UNUSED, | ||
162 | reg_base + ASS_CLK_GATE, 1, 0, &lock); | ||
163 | clk_table[CLK_HCLK_RP] = clk_register_gate(NULL, "hclk_rp_audss", | ||
164 | hclk_p, CLK_IGNORE_UNUSED, | ||
165 | reg_base + ASS_CLK_GATE, 0, 0, &lock); | ||
166 | |||
167 | for (i = 0; i < clk_data.clk_num; i++) { | ||
168 | if (IS_ERR(clk_table[i])) { | ||
169 | dev_err(&pdev->dev, "failed to register clock %d\n", i); | ||
170 | ret = PTR_ERR(clk_table[i]); | ||
171 | goto unregister; | ||
172 | } | ||
173 | } | ||
174 | |||
175 | ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_onecell_get, | ||
176 | &clk_data); | ||
177 | if (ret) { | ||
178 | dev_err(&pdev->dev, "failed to add clock provider\n"); | ||
179 | goto unregister; | ||
180 | } | ||
181 | |||
182 | #ifdef CONFIG_PM_SLEEP | ||
183 | register_syscore_ops(&s5pv210_audss_clk_syscore_ops); | ||
184 | #endif | ||
185 | |||
186 | return 0; | ||
187 | |||
188 | unregister: | ||
189 | for (i = 0; i < clk_data.clk_num; i++) { | ||
190 | if (!IS_ERR(clk_table[i])) | ||
191 | clk_unregister(clk_table[i]); | ||
192 | } | ||
193 | |||
194 | return ret; | ||
195 | } | ||
196 | |||
197 | static int s5pv210_audss_clk_remove(struct platform_device *pdev) | ||
198 | { | ||
199 | int i; | ||
200 | |||
201 | of_clk_del_provider(pdev->dev.of_node); | ||
202 | |||
203 | for (i = 0; i < clk_data.clk_num; i++) { | ||
204 | if (!IS_ERR(clk_table[i])) | ||
205 | clk_unregister(clk_table[i]); | ||
206 | } | ||
207 | |||
208 | return 0; | ||
209 | } | ||
210 | |||
211 | static const struct of_device_id s5pv210_audss_clk_of_match[] = { | ||
212 | { .compatible = "samsung,s5pv210-audss-clock", }, | ||
213 | {}, | ||
214 | }; | ||
215 | |||
216 | static struct platform_driver s5pv210_audss_clk_driver = { | ||
217 | .driver = { | ||
218 | .name = "s5pv210-audss-clk", | ||
219 | .owner = THIS_MODULE, | ||
220 | .of_match_table = s5pv210_audss_clk_of_match, | ||
221 | }, | ||
222 | .probe = s5pv210_audss_clk_probe, | ||
223 | .remove = s5pv210_audss_clk_remove, | ||
224 | }; | ||
225 | |||
226 | static int __init s5pv210_audss_clk_init(void) | ||
227 | { | ||
228 | return platform_driver_register(&s5pv210_audss_clk_driver); | ||
229 | } | ||
230 | core_initcall(s5pv210_audss_clk_init); | ||
231 | |||
232 | static void __exit s5pv210_audss_clk_exit(void) | ||
233 | { | ||
234 | platform_driver_unregister(&s5pv210_audss_clk_driver); | ||
235 | } | ||
236 | module_exit(s5pv210_audss_clk_exit); | ||
237 | |||
238 | MODULE_AUTHOR("Tomasz Figa <t.figa@samsung.com>"); | ||
239 | MODULE_DESCRIPTION("S5PV210 Audio Subsystem Clock Controller"); | ||
240 | MODULE_LICENSE("GPL v2"); | ||
241 | MODULE_ALIAS("platform:s5pv210-audss-clk"); | ||