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 | |
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>
-rw-r--r-- | Documentation/devicetree/bindings/clock/clk-s5pv210-audss.txt | 53 | ||||
-rw-r--r-- | drivers/clk/samsung/Makefile | 2 | ||||
-rw-r--r-- | drivers/clk/samsung/clk-s5pv210-audss.c | 241 | ||||
-rw-r--r-- | include/dt-bindings/clock/s5pv210-audss.h | 34 |
4 files changed, 329 insertions, 1 deletions
diff --git a/Documentation/devicetree/bindings/clock/clk-s5pv210-audss.txt b/Documentation/devicetree/bindings/clock/clk-s5pv210-audss.txt new file mode 100644 index 000000000000..4fc869b69d4a --- /dev/null +++ b/Documentation/devicetree/bindings/clock/clk-s5pv210-audss.txt | |||
@@ -0,0 +1,53 @@ | |||
1 | * Samsung Audio Subsystem Clock Controller | ||
2 | |||
3 | The Samsung Audio Subsystem clock controller generates and supplies clocks | ||
4 | to Audio Subsystem block available in the S5PV210 and compatible SoCs. | ||
5 | |||
6 | Required Properties: | ||
7 | |||
8 | - compatible: should be "samsung,s5pv210-audss-clock". | ||
9 | - reg: physical base address and length of the controller's register set. | ||
10 | |||
11 | - #clock-cells: should be 1. | ||
12 | |||
13 | - clocks: | ||
14 | - hclk: AHB bus clock of the Audio Subsystem. | ||
15 | - xxti: Optional fixed rate PLL reference clock, parent of mout_audss. If | ||
16 | not specified (i.e. xusbxti is used for PLL reference), it is fixed to | ||
17 | a clock named "xxti". | ||
18 | - fout_epll: Input PLL to the AudioSS block, parent of mout_audss. | ||
19 | - iiscdclk0: Optional external i2s clock, parent of mout_i2s. If not | ||
20 | specified, it is fixed to a clock named "iiscdclk0". | ||
21 | - sclk_audio0: Audio bus clock, parent of mout_i2s. | ||
22 | |||
23 | - clock-names: Aliases for the above clocks. They should be "hclk", | ||
24 | "xxti", "fout_epll", "iiscdclk0", and "sclk_audio0" respectively. | ||
25 | |||
26 | All available clocks are defined as preprocessor macros in | ||
27 | dt-bindings/clock/s5pv210-audss-clk.h header and can be used in device | ||
28 | tree sources. | ||
29 | |||
30 | Example: Clock controller node. | ||
31 | |||
32 | clk_audss: clock-controller@c0900000 { | ||
33 | compatible = "samsung,s5pv210-audss-clock"; | ||
34 | reg = <0xc0900000 0x1000>; | ||
35 | #clock-cells = <1>; | ||
36 | clock-names = "hclk", "xxti", | ||
37 | "fout_epll", "sclk_audio0"; | ||
38 | clocks = <&clocks DOUT_HCLKP>, <&xxti>, | ||
39 | <&clocks FOUT_EPLL>, <&clocks SCLK_AUDIO0>; | ||
40 | }; | ||
41 | |||
42 | Example: I2S controller node that consumes the clock generated by the clock | ||
43 | controller. Refer to the standard clock bindings for information | ||
44 | about 'clocks' and 'clock-names' property. | ||
45 | |||
46 | i2s0: i2s@03830000 { | ||
47 | /* ... */ | ||
48 | clock-names = "iis", "i2s_opclk0", | ||
49 | "i2s_opclk1"; | ||
50 | clocks = <&clk_audss CLK_I2S>, <&clk_audss CLK_I2S>, | ||
51 | <&clk_audss CLK_DOUT_AUD_BUS>; | ||
52 | /* ... */ | ||
53 | }; | ||
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"); | ||
diff --git a/include/dt-bindings/clock/s5pv210-audss.h b/include/dt-bindings/clock/s5pv210-audss.h new file mode 100644 index 000000000000..fe57406e24de --- /dev/null +++ b/include/dt-bindings/clock/s5pv210-audss.h | |||
@@ -0,0 +1,34 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2014 Tomasz Figa <tomasz.figa@gmail.com> | ||
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 | * This header provides constants for Samsung audio subsystem | ||
9 | * clock controller. | ||
10 | * | ||
11 | * The constants defined in this header are being used in dts | ||
12 | * and s5pv210 audss driver. | ||
13 | */ | ||
14 | |||
15 | #ifndef _DT_BINDINGS_CLOCK_S5PV210_AUDSS_H | ||
16 | #define _DT_BINDINGS_CLOCK_S5PV210_AUDSS_H | ||
17 | |||
18 | #define CLK_MOUT_AUDSS 0 | ||
19 | #define CLK_MOUT_I2S_A 1 | ||
20 | |||
21 | #define CLK_DOUT_AUD_BUS 2 | ||
22 | #define CLK_DOUT_I2S_A 3 | ||
23 | |||
24 | #define CLK_I2S 4 | ||
25 | #define CLK_HCLK_I2S 5 | ||
26 | #define CLK_HCLK_UART 6 | ||
27 | #define CLK_HCLK_HWA 7 | ||
28 | #define CLK_HCLK_DMA 8 | ||
29 | #define CLK_HCLK_BUF 9 | ||
30 | #define CLK_HCLK_RP 10 | ||
31 | |||
32 | #define AUDSS_MAX_CLKS 11 | ||
33 | |||
34 | #endif | ||