1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Driver for i.MX8M Plus Audio BLK_CTRL |
4 | * |
5 | * Copyright (C) 2022 Marek Vasut <marex@denx.de> |
6 | */ |
7 | |
8 | #include <linux/clk-provider.h> |
9 | #include <linux/device.h> |
10 | #include <linux/mod_devicetable.h> |
11 | #include <linux/module.h> |
12 | #include <linux/of.h> |
13 | #include <linux/platform_device.h> |
14 | |
15 | #include <dt-bindings/clock/imx8mp-clock.h> |
16 | |
17 | #include "clk.h" |
18 | |
19 | #define CLKEN0 0x000 |
20 | #define CLKEN1 0x004 |
21 | #define SAI1_MCLK_SEL 0x300 |
22 | #define SAI2_MCLK_SEL 0x304 |
23 | #define SAI3_MCLK_SEL 0x308 |
24 | #define SAI5_MCLK_SEL 0x30C |
25 | #define SAI6_MCLK_SEL 0x310 |
26 | #define SAI7_MCLK_SEL 0x314 |
27 | #define PDM_SEL 0x318 |
28 | #define SAI_PLL_GNRL_CTL 0x400 |
29 | |
30 | #define SAIn_MCLK1_PARENT(n) \ |
31 | static const struct clk_parent_data \ |
32 | clk_imx8mp_audiomix_sai##n##_mclk1_parents[] = { \ |
33 | { \ |
34 | .fw_name = "sai"__stringify(n), \ |
35 | .name = "sai"__stringify(n) \ |
36 | }, { \ |
37 | .fw_name = "sai"__stringify(n)"_mclk", \ |
38 | .name = "sai"__stringify(n)"_mclk" \ |
39 | }, \ |
40 | } |
41 | |
42 | SAIn_MCLK1_PARENT(1); |
43 | SAIn_MCLK1_PARENT(2); |
44 | SAIn_MCLK1_PARENT(3); |
45 | SAIn_MCLK1_PARENT(5); |
46 | SAIn_MCLK1_PARENT(6); |
47 | SAIn_MCLK1_PARENT(7); |
48 | |
49 | static const struct clk_parent_data clk_imx8mp_audiomix_sai_mclk2_parents[] = { |
50 | { .fw_name = "sai1" , .name = "sai1" }, |
51 | { .fw_name = "sai2" , .name = "sai2" }, |
52 | { .fw_name = "sai3" , .name = "sai3" }, |
53 | { .name = "dummy" }, |
54 | { .fw_name = "sai5" , .name = "sai5" }, |
55 | { .fw_name = "sai6" , .name = "sai6" }, |
56 | { .fw_name = "sai7" , .name = "sai7" }, |
57 | { .fw_name = "sai1_mclk" , .name = "sai1_mclk" }, |
58 | { .fw_name = "sai2_mclk" , .name = "sai2_mclk" }, |
59 | { .fw_name = "sai3_mclk" , .name = "sai3_mclk" }, |
60 | { .name = "dummy" }, |
61 | { .fw_name = "sai5_mclk" , .name = "sai5_mclk" }, |
62 | { .fw_name = "sai6_mclk" , .name = "sai6_mclk" }, |
63 | { .fw_name = "sai7_mclk" , .name = "sai7_mclk" }, |
64 | { .fw_name = "spdif_extclk" , .name = "spdif_extclk" }, |
65 | { .name = "dummy" }, |
66 | }; |
67 | |
68 | static const struct clk_parent_data clk_imx8mp_audiomix_pdm_parents[] = { |
69 | { .fw_name = "pdm" , .name = "pdm" }, |
70 | { .name = "sai_pll_out_div2" }, |
71 | { .fw_name = "sai1_mclk" , .name = "sai1_mclk" }, |
72 | { .name = "dummy" }, |
73 | }; |
74 | |
75 | |
76 | static const struct clk_parent_data clk_imx8mp_audiomix_pll_parents[] = { |
77 | { .fw_name = "osc_24m" , .name = "osc_24m" }, |
78 | { .name = "dummy" }, |
79 | { .name = "dummy" }, |
80 | { .name = "dummy" }, |
81 | }; |
82 | |
83 | static const struct clk_parent_data clk_imx8mp_audiomix_pll_bypass_sels[] = { |
84 | { .fw_name = "sai_pll" , .name = "sai_pll" }, |
85 | { .fw_name = "sai_pll_ref_sel" , .name = "sai_pll_ref_sel" }, |
86 | }; |
87 | |
88 | #define CLK_GATE(gname, cname) \ |
89 | { \ |
90 | gname"_cg", \ |
91 | IMX8MP_CLK_AUDIOMIX_##cname, \ |
92 | { .fw_name = "ahb", .name = "ahb" }, NULL, 1, \ |
93 | CLKEN0 + 4 * !!(IMX8MP_CLK_AUDIOMIX_##cname / 32), \ |
94 | 1, IMX8MP_CLK_AUDIOMIX_##cname % 32 \ |
95 | } |
96 | |
97 | #define CLK_SAIn(n) \ |
98 | { \ |
99 | "sai"__stringify(n)"_mclk1_sel", \ |
100 | IMX8MP_CLK_AUDIOMIX_SAI##n##_MCLK1_SEL, {}, \ |
101 | clk_imx8mp_audiomix_sai##n##_mclk1_parents, \ |
102 | ARRAY_SIZE(clk_imx8mp_audiomix_sai##n##_mclk1_parents), \ |
103 | SAI##n##_MCLK_SEL, 1, 0 \ |
104 | }, { \ |
105 | "sai"__stringify(n)"_mclk2_sel", \ |
106 | IMX8MP_CLK_AUDIOMIX_SAI##n##_MCLK2_SEL, {}, \ |
107 | clk_imx8mp_audiomix_sai_mclk2_parents, \ |
108 | ARRAY_SIZE(clk_imx8mp_audiomix_sai_mclk2_parents), \ |
109 | SAI##n##_MCLK_SEL, 4, 1 \ |
110 | }, { \ |
111 | "sai"__stringify(n)"_ipg_cg", \ |
112 | IMX8MP_CLK_AUDIOMIX_SAI##n##_IPG, \ |
113 | { .fw_name = "ahb", .name = "ahb" }, NULL, 1, \ |
114 | CLKEN0, 1, IMX8MP_CLK_AUDIOMIX_SAI##n##_IPG \ |
115 | }, { \ |
116 | "sai"__stringify(n)"_mclk1_cg", \ |
117 | IMX8MP_CLK_AUDIOMIX_SAI##n##_MCLK1, \ |
118 | { \ |
119 | .fw_name = "sai"__stringify(n)"_mclk1_sel", \ |
120 | .name = "sai"__stringify(n)"_mclk1_sel" \ |
121 | }, NULL, 1, \ |
122 | CLKEN0, 1, IMX8MP_CLK_AUDIOMIX_SAI##n##_MCLK1 \ |
123 | }, { \ |
124 | "sai"__stringify(n)"_mclk2_cg", \ |
125 | IMX8MP_CLK_AUDIOMIX_SAI##n##_MCLK2, \ |
126 | { \ |
127 | .fw_name = "sai"__stringify(n)"_mclk2_sel", \ |
128 | .name = "sai"__stringify(n)"_mclk2_sel" \ |
129 | }, NULL, 1, \ |
130 | CLKEN0, 1, IMX8MP_CLK_AUDIOMIX_SAI##n##_MCLK2 \ |
131 | }, { \ |
132 | "sai"__stringify(n)"_mclk3_cg", \ |
133 | IMX8MP_CLK_AUDIOMIX_SAI##n##_MCLK3, \ |
134 | { \ |
135 | .fw_name = "sai_pll_out_div2", \ |
136 | .name = "sai_pll_out_div2" \ |
137 | }, NULL, 1, \ |
138 | CLKEN0, 1, IMX8MP_CLK_AUDIOMIX_SAI##n##_MCLK3 \ |
139 | } |
140 | |
141 | #define CLK_PDM \ |
142 | { \ |
143 | "pdm_sel", IMX8MP_CLK_AUDIOMIX_PDM_SEL, {}, \ |
144 | clk_imx8mp_audiomix_pdm_parents, \ |
145 | ARRAY_SIZE(clk_imx8mp_audiomix_pdm_parents), \ |
146 | PDM_SEL, 2, 0 \ |
147 | } |
148 | |
149 | struct clk_imx8mp_audiomix_sel { |
150 | const char *name; |
151 | int clkid; |
152 | const struct clk_parent_data parent; /* For gate */ |
153 | const struct clk_parent_data *parents; /* For mux */ |
154 | int num_parents; |
155 | u16 reg; |
156 | u8 width; |
157 | u8 shift; |
158 | }; |
159 | |
160 | static struct clk_imx8mp_audiomix_sel sels[] = { |
161 | CLK_GATE("asrc" , ASRC_IPG), |
162 | CLK_GATE("pdm" , PDM_IPG), |
163 | CLK_GATE("earc" , EARC_IPG), |
164 | CLK_GATE("ocrama" , OCRAMA_IPG), |
165 | CLK_GATE("aud2htx" , AUD2HTX_IPG), |
166 | CLK_GATE("earc_phy" , EARC_PHY), |
167 | CLK_GATE("sdma2" , SDMA2_ROOT), |
168 | CLK_GATE("sdma3" , SDMA3_ROOT), |
169 | CLK_GATE("spba2" , SPBA2_ROOT), |
170 | CLK_GATE("dsp" , DSP_ROOT), |
171 | CLK_GATE("dspdbg" , DSPDBG_ROOT), |
172 | CLK_GATE("edma" , EDMA_ROOT), |
173 | CLK_GATE("audpll" , AUDPLL_ROOT), |
174 | CLK_GATE("mu2" , MU2_ROOT), |
175 | CLK_GATE("mu3" , MU3_ROOT), |
176 | CLK_PDM, |
177 | CLK_SAIn(1), |
178 | CLK_SAIn(2), |
179 | CLK_SAIn(3), |
180 | CLK_SAIn(5), |
181 | CLK_SAIn(6), |
182 | CLK_SAIn(7) |
183 | }; |
184 | |
185 | static int clk_imx8mp_audiomix_probe(struct platform_device *pdev) |
186 | { |
187 | struct clk_hw_onecell_data *priv; |
188 | struct device *dev = &pdev->dev; |
189 | void __iomem *base; |
190 | struct clk_hw *hw; |
191 | int i; |
192 | |
193 | priv = devm_kzalloc(dev, |
194 | struct_size(priv, hws, IMX8MP_CLK_AUDIOMIX_END), |
195 | GFP_KERNEL); |
196 | if (!priv) |
197 | return -ENOMEM; |
198 | |
199 | priv->num = IMX8MP_CLK_AUDIOMIX_END; |
200 | |
201 | base = devm_platform_ioremap_resource(pdev, index: 0); |
202 | if (IS_ERR(ptr: base)) |
203 | return PTR_ERR(ptr: base); |
204 | |
205 | for (i = 0; i < ARRAY_SIZE(sels); i++) { |
206 | if (sels[i].num_parents == 1) { |
207 | hw = devm_clk_hw_register_gate_parent_data(dev, |
208 | sels[i].name, &sels[i].parent, 0, |
209 | base + sels[i].reg, sels[i].shift, 0, NULL); |
210 | } else { |
211 | hw = devm_clk_hw_register_mux_parent_data_table(dev, |
212 | sels[i].name, sels[i].parents, |
213 | sels[i].num_parents, 0, |
214 | base + sels[i].reg, |
215 | sels[i].shift, sels[i].width, |
216 | 0, NULL, NULL); |
217 | } |
218 | |
219 | if (IS_ERR(ptr: hw)) |
220 | return PTR_ERR(ptr: hw); |
221 | |
222 | priv->hws[sels[i].clkid] = hw; |
223 | } |
224 | |
225 | /* SAI PLL */ |
226 | hw = devm_clk_hw_register_mux_parent_data_table(dev, |
227 | "sai_pll_ref_sel" , clk_imx8mp_audiomix_pll_parents, |
228 | ARRAY_SIZE(clk_imx8mp_audiomix_pll_parents), |
229 | CLK_SET_RATE_NO_REPARENT, base + SAI_PLL_GNRL_CTL, |
230 | 0, 2, 0, NULL, NULL); |
231 | priv->hws[IMX8MP_CLK_AUDIOMIX_SAI_PLL_REF_SEL] = hw; |
232 | |
233 | hw = imx_dev_clk_hw_pll14xx(dev, name: "sai_pll" , parent_name: "sai_pll_ref_sel" , |
234 | base: base + 0x400, pll_clk: &imx_1443x_pll); |
235 | if (IS_ERR(ptr: hw)) |
236 | return PTR_ERR(ptr: hw); |
237 | priv->hws[IMX8MP_CLK_AUDIOMIX_SAI_PLL] = hw; |
238 | |
239 | hw = devm_clk_hw_register_mux_parent_data_table(dev, |
240 | "sai_pll_bypass" , clk_imx8mp_audiomix_pll_bypass_sels, |
241 | ARRAY_SIZE(clk_imx8mp_audiomix_pll_bypass_sels), |
242 | CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT, |
243 | base + SAI_PLL_GNRL_CTL, 16, 1, 0, NULL, NULL); |
244 | if (IS_ERR(ptr: hw)) |
245 | return PTR_ERR(ptr: hw); |
246 | priv->hws[IMX8MP_CLK_AUDIOMIX_SAI_PLL_BYPASS] = hw; |
247 | |
248 | hw = devm_clk_hw_register_gate(dev, "sai_pll_out" , "sai_pll_bypass" , |
249 | 0, base + SAI_PLL_GNRL_CTL, 13, |
250 | 0, NULL); |
251 | if (IS_ERR(ptr: hw)) |
252 | return PTR_ERR(ptr: hw); |
253 | priv->hws[IMX8MP_CLK_AUDIOMIX_SAI_PLL_OUT] = hw; |
254 | |
255 | hw = devm_clk_hw_register_fixed_factor(dev, name: "sai_pll_out_div2" , |
256 | parent_name: "sai_pll_out" , flags: 0, mult: 1, div: 2); |
257 | if (IS_ERR(ptr: hw)) |
258 | return PTR_ERR(ptr: hw); |
259 | |
260 | return devm_of_clk_add_hw_provider(dev: &pdev->dev, get: of_clk_hw_onecell_get, |
261 | data: priv); |
262 | } |
263 | |
264 | static const struct of_device_id clk_imx8mp_audiomix_of_match[] = { |
265 | { .compatible = "fsl,imx8mp-audio-blk-ctrl" }, |
266 | { /* sentinel */ } |
267 | }; |
268 | MODULE_DEVICE_TABLE(of, clk_imx8mp_audiomix_of_match); |
269 | |
270 | static struct platform_driver clk_imx8mp_audiomix_driver = { |
271 | .probe = clk_imx8mp_audiomix_probe, |
272 | .driver = { |
273 | .name = "imx8mp-audio-blk-ctrl" , |
274 | .of_match_table = clk_imx8mp_audiomix_of_match, |
275 | }, |
276 | }; |
277 | |
278 | module_platform_driver(clk_imx8mp_audiomix_driver); |
279 | |
280 | MODULE_AUTHOR("Marek Vasut <marex@denx.de>" ); |
281 | MODULE_DESCRIPTION("Freescale i.MX8MP Audio Block Controller driver" ); |
282 | MODULE_LICENSE("GPL" ); |
283 | |