1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2014 Tomasz Figa <t.figa@samsung.com> |
4 | * |
5 | * Based on Exynos Audio Subsystem Clock Controller driver: |
6 | * |
7 | * Copyright (c) 2013 Samsung Electronics Co., Ltd. |
8 | * Author: Padmavathi Venna <padma.v@samsung.com> |
9 | * |
10 | * Driver for Audio Subsystem Clock Controller of S5PV210-compatible SoCs. |
11 | */ |
12 | |
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/syscore_ops.h> |
18 | #include <linux/init.h> |
19 | #include <linux/platform_device.h> |
20 | |
21 | #include <dt-bindings/clock/s5pv210-audss.h> |
22 | |
23 | static DEFINE_SPINLOCK(lock); |
24 | static void __iomem *reg_base; |
25 | static struct clk_hw_onecell_data *clk_data; |
26 | |
27 | #define ASS_CLK_SRC 0x0 |
28 | #define ASS_CLK_DIV 0x4 |
29 | #define ASS_CLK_GATE 0x8 |
30 | |
31 | #ifdef CONFIG_PM_SLEEP |
32 | static unsigned long reg_save[][2] = { |
33 | {ASS_CLK_SRC, 0}, |
34 | {ASS_CLK_DIV, 0}, |
35 | {ASS_CLK_GATE, 0}, |
36 | }; |
37 | |
38 | static int s5pv210_audss_clk_suspend(void) |
39 | { |
40 | int i; |
41 | |
42 | for (i = 0; i < ARRAY_SIZE(reg_save); i++) |
43 | reg_save[i][1] = readl(addr: reg_base + reg_save[i][0]); |
44 | |
45 | return 0; |
46 | } |
47 | |
48 | static void s5pv210_audss_clk_resume(void) |
49 | { |
50 | int i; |
51 | |
52 | for (i = 0; i < ARRAY_SIZE(reg_save); i++) |
53 | writel(val: reg_save[i][1], addr: reg_base + reg_save[i][0]); |
54 | } |
55 | |
56 | static struct syscore_ops s5pv210_audss_clk_syscore_ops = { |
57 | .suspend = s5pv210_audss_clk_suspend, |
58 | .resume = s5pv210_audss_clk_resume, |
59 | }; |
60 | #endif /* CONFIG_PM_SLEEP */ |
61 | |
62 | /* register s5pv210_audss clocks */ |
63 | static int s5pv210_audss_clk_probe(struct platform_device *pdev) |
64 | { |
65 | int i, ret = 0; |
66 | const char *mout_audss_p[2]; |
67 | const char *mout_i2s_p[3]; |
68 | const char *hclk_p; |
69 | struct clk_hw **clk_table; |
70 | struct clk *hclk, *pll_ref, *pll_in, *cdclk, *sclk_audio; |
71 | |
72 | reg_base = devm_platform_ioremap_resource(pdev, index: 0); |
73 | if (IS_ERR(ptr: reg_base)) |
74 | return PTR_ERR(ptr: reg_base); |
75 | |
76 | clk_data = devm_kzalloc(dev: &pdev->dev, |
77 | struct_size(clk_data, hws, AUDSS_MAX_CLKS), |
78 | GFP_KERNEL); |
79 | |
80 | if (!clk_data) |
81 | return -ENOMEM; |
82 | |
83 | clk_data->num = AUDSS_MAX_CLKS; |
84 | clk_table = clk_data->hws; |
85 | |
86 | hclk = devm_clk_get(dev: &pdev->dev, id: "hclk" ); |
87 | if (IS_ERR(ptr: hclk)) { |
88 | dev_err(&pdev->dev, "failed to get hclk clock\n" ); |
89 | return PTR_ERR(ptr: hclk); |
90 | } |
91 | |
92 | pll_in = devm_clk_get(dev: &pdev->dev, id: "fout_epll" ); |
93 | if (IS_ERR(ptr: pll_in)) { |
94 | dev_err(&pdev->dev, "failed to get fout_epll clock\n" ); |
95 | return PTR_ERR(ptr: pll_in); |
96 | } |
97 | |
98 | sclk_audio = devm_clk_get(dev: &pdev->dev, id: "sclk_audio0" ); |
99 | if (IS_ERR(ptr: sclk_audio)) { |
100 | dev_err(&pdev->dev, "failed to get sclk_audio0 clock\n" ); |
101 | return PTR_ERR(ptr: sclk_audio); |
102 | } |
103 | |
104 | /* iiscdclk0 is an optional external I2S codec clock */ |
105 | cdclk = devm_clk_get(dev: &pdev->dev, id: "iiscdclk0" ); |
106 | pll_ref = devm_clk_get(dev: &pdev->dev, id: "xxti" ); |
107 | |
108 | if (!IS_ERR(ptr: pll_ref)) |
109 | mout_audss_p[0] = __clk_get_name(clk: pll_ref); |
110 | else |
111 | mout_audss_p[0] = "xxti" ; |
112 | mout_audss_p[1] = __clk_get_name(clk: pll_in); |
113 | clk_table[CLK_MOUT_AUDSS] = clk_hw_register_mux(NULL, "mout_audss" , |
114 | mout_audss_p, ARRAY_SIZE(mout_audss_p), |
115 | CLK_SET_RATE_NO_REPARENT, |
116 | reg_base + ASS_CLK_SRC, 0, 1, 0, &lock); |
117 | |
118 | mout_i2s_p[0] = "mout_audss" ; |
119 | if (!IS_ERR(ptr: cdclk)) |
120 | mout_i2s_p[1] = __clk_get_name(clk: cdclk); |
121 | else |
122 | mout_i2s_p[1] = "iiscdclk0" ; |
123 | mout_i2s_p[2] = __clk_get_name(clk: sclk_audio); |
124 | clk_table[CLK_MOUT_I2S_A] = clk_hw_register_mux(NULL, "mout_i2s_audss" , |
125 | mout_i2s_p, ARRAY_SIZE(mout_i2s_p), |
126 | CLK_SET_RATE_NO_REPARENT, |
127 | reg_base + ASS_CLK_SRC, 2, 2, 0, &lock); |
128 | |
129 | clk_table[CLK_DOUT_AUD_BUS] = clk_hw_register_divider(NULL, |
130 | "dout_aud_bus" , "mout_audss" , 0, |
131 | reg_base + ASS_CLK_DIV, 0, 4, 0, &lock); |
132 | clk_table[CLK_DOUT_I2S_A] = clk_hw_register_divider(NULL, |
133 | "dout_i2s_audss" , "mout_i2s_audss" , 0, |
134 | reg_base + ASS_CLK_DIV, 4, 4, 0, &lock); |
135 | |
136 | clk_table[CLK_I2S] = clk_hw_register_gate(NULL, "i2s_audss" , |
137 | "dout_i2s_audss" , CLK_SET_RATE_PARENT, |
138 | reg_base + ASS_CLK_GATE, 6, 0, &lock); |
139 | |
140 | hclk_p = __clk_get_name(clk: hclk); |
141 | |
142 | clk_table[CLK_HCLK_I2S] = clk_hw_register_gate(NULL, "hclk_i2s_audss" , |
143 | hclk_p, CLK_IGNORE_UNUSED, |
144 | reg_base + ASS_CLK_GATE, 5, 0, &lock); |
145 | clk_table[CLK_HCLK_UART] = clk_hw_register_gate(NULL, "hclk_uart_audss" , |
146 | hclk_p, CLK_IGNORE_UNUSED, |
147 | reg_base + ASS_CLK_GATE, 4, 0, &lock); |
148 | clk_table[CLK_HCLK_HWA] = clk_hw_register_gate(NULL, "hclk_hwa_audss" , |
149 | hclk_p, CLK_IGNORE_UNUSED, |
150 | reg_base + ASS_CLK_GATE, 3, 0, &lock); |
151 | clk_table[CLK_HCLK_DMA] = clk_hw_register_gate(NULL, "hclk_dma_audss" , |
152 | hclk_p, CLK_IGNORE_UNUSED, |
153 | reg_base + ASS_CLK_GATE, 2, 0, &lock); |
154 | clk_table[CLK_HCLK_BUF] = clk_hw_register_gate(NULL, "hclk_buf_audss" , |
155 | hclk_p, CLK_IGNORE_UNUSED, |
156 | reg_base + ASS_CLK_GATE, 1, 0, &lock); |
157 | clk_table[CLK_HCLK_RP] = clk_hw_register_gate(NULL, "hclk_rp_audss" , |
158 | hclk_p, CLK_IGNORE_UNUSED, |
159 | reg_base + ASS_CLK_GATE, 0, 0, &lock); |
160 | |
161 | for (i = 0; i < clk_data->num; i++) { |
162 | if (IS_ERR(ptr: clk_table[i])) { |
163 | dev_err(&pdev->dev, "failed to register clock %d\n" , i); |
164 | ret = PTR_ERR(ptr: clk_table[i]); |
165 | goto unregister; |
166 | } |
167 | } |
168 | |
169 | ret = of_clk_add_hw_provider(np: pdev->dev.of_node, get: of_clk_hw_onecell_get, |
170 | data: clk_data); |
171 | if (ret) { |
172 | dev_err(&pdev->dev, "failed to add clock provider\n" ); |
173 | goto unregister; |
174 | } |
175 | |
176 | #ifdef CONFIG_PM_SLEEP |
177 | register_syscore_ops(ops: &s5pv210_audss_clk_syscore_ops); |
178 | #endif |
179 | |
180 | return 0; |
181 | |
182 | unregister: |
183 | for (i = 0; i < clk_data->num; i++) { |
184 | if (!IS_ERR(ptr: clk_table[i])) |
185 | clk_hw_unregister(hw: clk_table[i]); |
186 | } |
187 | |
188 | return ret; |
189 | } |
190 | |
191 | static const struct of_device_id s5pv210_audss_clk_of_match[] = { |
192 | { .compatible = "samsung,s5pv210-audss-clock" , }, |
193 | {}, |
194 | }; |
195 | |
196 | static struct platform_driver s5pv210_audss_clk_driver = { |
197 | .driver = { |
198 | .name = "s5pv210-audss-clk" , |
199 | .suppress_bind_attrs = true, |
200 | .of_match_table = s5pv210_audss_clk_of_match, |
201 | }, |
202 | .probe = s5pv210_audss_clk_probe, |
203 | }; |
204 | |
205 | static int __init s5pv210_audss_clk_init(void) |
206 | { |
207 | return platform_driver_register(&s5pv210_audss_clk_driver); |
208 | } |
209 | core_initcall(s5pv210_audss_clk_init); |
210 | |