Commit | Line | Data |
---|---|---|
9978f28f TF |
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 | ||
9978f28f | 16 | #include <linux/io.h> |
6f1ed07a | 17 | #include <linux/clk.h> |
9978f28f TF |
18 | #include <linux/clk-provider.h> |
19 | #include <linux/of_address.h> | |
20 | #include <linux/syscore_ops.h> | |
4c7c28ff | 21 | #include <linux/init.h> |
9978f28f TF |
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 | ||
9978f28f TF |
197 | static const struct of_device_id s5pv210_audss_clk_of_match[] = { |
198 | { .compatible = "samsung,s5pv210-audss-clock", }, | |
199 | {}, | |
200 | }; | |
201 | ||
202 | static struct platform_driver s5pv210_audss_clk_driver = { | |
203 | .driver = { | |
204 | .name = "s5pv210-audss-clk", | |
4c7c28ff | 205 | .suppress_bind_attrs = true, |
9978f28f TF |
206 | .of_match_table = s5pv210_audss_clk_of_match, |
207 | }, | |
208 | .probe = s5pv210_audss_clk_probe, | |
9978f28f TF |
209 | }; |
210 | ||
211 | static int __init s5pv210_audss_clk_init(void) | |
212 | { | |
213 | return platform_driver_register(&s5pv210_audss_clk_driver); | |
214 | } | |
215 | core_initcall(s5pv210_audss_clk_init); |