1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // |
3 | // Copyright 2023 NXP |
4 | // |
5 | |
6 | #include <dt-bindings/clock/imx8-clock.h> |
7 | #include <linux/clk-provider.h> |
8 | #include <linux/device.h> |
9 | #include <linux/err.h> |
10 | #include <linux/io.h> |
11 | #include <linux/module.h> |
12 | #include <linux/of.h> |
13 | #include <linux/of_device.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/pm_domain.h> |
16 | #include <linux/pm_runtime.h> |
17 | #include <linux/slab.h> |
18 | |
19 | #include "clk.h" |
20 | |
21 | /** |
22 | * struct clk_imx_acm_pm_domains - structure for multi power domain |
23 | * @pd_dev: power domain device |
24 | * @pd_dev_link: power domain device link |
25 | * @num_domains: power domain nummber |
26 | */ |
27 | struct clk_imx_acm_pm_domains { |
28 | struct device **pd_dev; |
29 | struct device_link **pd_dev_link; |
30 | int num_domains; |
31 | }; |
32 | |
33 | /** |
34 | * struct clk_imx8_acm_sel - for clock mux |
35 | * @name: clock name |
36 | * @clkid: clock id |
37 | * @parents: clock parents |
38 | * @num_parents: clock parents number |
39 | * @reg: register offset |
40 | * @shift: bit shift in register |
41 | * @width: bits width |
42 | */ |
43 | struct clk_imx8_acm_sel { |
44 | const char *name; |
45 | int clkid; |
46 | const struct clk_parent_data *parents; /* For mux */ |
47 | int num_parents; |
48 | u32 reg; |
49 | u8 shift; |
50 | u8 width; |
51 | }; |
52 | |
53 | /** |
54 | * struct imx8_acm_soc_data - soc specific data |
55 | * @sels: pointer to struct clk_imx8_acm_sel |
56 | * @num_sels: numbers of items |
57 | */ |
58 | struct imx8_acm_soc_data { |
59 | struct clk_imx8_acm_sel *sels; |
60 | unsigned int num_sels; |
61 | }; |
62 | |
63 | /** |
64 | * struct imx8_acm_priv - private structure |
65 | * @dev_pm: multi power domain |
66 | * @soc_data: pointer to soc data |
67 | * @reg: base address of registers |
68 | * @regs: save registers for suspend |
69 | */ |
70 | struct imx8_acm_priv { |
71 | struct clk_imx_acm_pm_domains dev_pm; |
72 | const struct imx8_acm_soc_data *soc_data; |
73 | void __iomem *reg; |
74 | u32 regs[IMX_ADMA_ACM_CLK_END]; |
75 | }; |
76 | |
77 | static const struct clk_parent_data imx8qm_aud_clk_sels[] = { |
78 | { .fw_name = "aud_rec_clk0_lpcg_clk" }, |
79 | { .fw_name = "aud_rec_clk1_lpcg_clk" }, |
80 | { .fw_name = "dummy" }, |
81 | { .fw_name = "hdmi_rx_mclk" }, |
82 | { .fw_name = "ext_aud_mclk0" }, |
83 | { .fw_name = "ext_aud_mclk1" }, |
84 | { .fw_name = "esai0_rx_clk" }, |
85 | { .fw_name = "esai0_rx_hf_clk" }, |
86 | { .fw_name = "esai0_tx_clk" }, |
87 | { .fw_name = "esai0_tx_hf_clk" }, |
88 | { .fw_name = "esai1_rx_clk" }, |
89 | { .fw_name = "esai1_rx_hf_clk" }, |
90 | { .fw_name = "esai1_tx_clk" }, |
91 | { .fw_name = "esai1_tx_hf_clk" }, |
92 | { .fw_name = "spdif0_rx" }, |
93 | { .fw_name = "spdif1_rx" }, |
94 | { .fw_name = "sai0_rx_bclk" }, |
95 | { .fw_name = "sai0_tx_bclk" }, |
96 | { .fw_name = "sai1_rx_bclk" }, |
97 | { .fw_name = "sai1_tx_bclk" }, |
98 | { .fw_name = "sai2_rx_bclk" }, |
99 | { .fw_name = "sai3_rx_bclk" }, |
100 | { .fw_name = "sai4_rx_bclk" }, |
101 | }; |
102 | |
103 | static const struct clk_parent_data imx8qm_mclk_out_sels[] = { |
104 | { .fw_name = "aud_rec_clk0_lpcg_clk" }, |
105 | { .fw_name = "aud_rec_clk1_lpcg_clk" }, |
106 | { .fw_name = "dummy" }, |
107 | { .fw_name = "hdmi_rx_mclk" }, |
108 | { .fw_name = "spdif0_rx" }, |
109 | { .fw_name = "spdif1_rx" }, |
110 | { .fw_name = "sai4_rx_bclk" }, |
111 | { .fw_name = "sai6_rx_bclk" }, |
112 | }; |
113 | |
114 | static const struct clk_parent_data imx8qm_mclk_sels[] = { |
115 | { .fw_name = "aud_pll_div_clk0_lpcg_clk" }, |
116 | { .fw_name = "aud_pll_div_clk1_lpcg_clk" }, |
117 | { .fw_name = "acm_aud_clk0_sel" }, |
118 | { .fw_name = "acm_aud_clk1_sel" }, |
119 | }; |
120 | |
121 | static const struct clk_parent_data imx8qm_asrc_mux_clk_sels[] = { |
122 | { .fw_name = "sai4_rx_bclk" }, |
123 | { .fw_name = "sai5_tx_bclk" }, |
124 | { .index = -1 }, |
125 | { .fw_name = "dummy" }, |
126 | |
127 | }; |
128 | |
129 | static struct clk_imx8_acm_sel imx8qm_sels[] = { |
130 | { "acm_aud_clk0_sel" , IMX_ADMA_ACM_AUD_CLK0_SEL, imx8qm_aud_clk_sels, ARRAY_SIZE(imx8qm_aud_clk_sels), 0x000000, 0, 5 }, |
131 | { "acm_aud_clk1_sel" , IMX_ADMA_ACM_AUD_CLK1_SEL, imx8qm_aud_clk_sels, ARRAY_SIZE(imx8qm_aud_clk_sels), 0x010000, 0, 5 }, |
132 | { "acm_mclkout0_sel" , IMX_ADMA_ACM_MCLKOUT0_SEL, imx8qm_mclk_out_sels, ARRAY_SIZE(imx8qm_mclk_out_sels), 0x020000, 0, 3 }, |
133 | { "acm_mclkout1_sel" , IMX_ADMA_ACM_MCLKOUT1_SEL, imx8qm_mclk_out_sels, ARRAY_SIZE(imx8qm_mclk_out_sels), 0x030000, 0, 3 }, |
134 | { "acm_asrc0_mclk_sel" , IMX_ADMA_ACM_ASRC0_MUX_CLK_SEL, imx8qm_asrc_mux_clk_sels, ARRAY_SIZE(imx8qm_asrc_mux_clk_sels), 0x040000, 0, 2 }, |
135 | { "acm_esai0_mclk_sel" , IMX_ADMA_ACM_ESAI0_MCLK_SEL, imx8qm_mclk_sels, ARRAY_SIZE(imx8qm_mclk_sels), 0x060000, 0, 2 }, |
136 | { "acm_esai1_mclk_sel" , IMX_ADMA_ACM_ESAI1_MCLK_SEL, imx8qm_mclk_sels, ARRAY_SIZE(imx8qm_mclk_sels), 0x070000, 0, 2 }, |
137 | { "acm_sai0_mclk_sel" , IMX_ADMA_ACM_SAI0_MCLK_SEL, imx8qm_mclk_sels, ARRAY_SIZE(imx8qm_mclk_sels), 0x0E0000, 0, 2 }, |
138 | { "acm_sai1_mclk_sel" , IMX_ADMA_ACM_SAI1_MCLK_SEL, imx8qm_mclk_sels, ARRAY_SIZE(imx8qm_mclk_sels), 0x0F0000, 0, 2 }, |
139 | { "acm_sai2_mclk_sel" , IMX_ADMA_ACM_SAI2_MCLK_SEL, imx8qm_mclk_sels, ARRAY_SIZE(imx8qm_mclk_sels), 0x100000, 0, 2 }, |
140 | { "acm_sai3_mclk_sel" , IMX_ADMA_ACM_SAI3_MCLK_SEL, imx8qm_mclk_sels, ARRAY_SIZE(imx8qm_mclk_sels), 0x110000, 0, 2 }, |
141 | { "acm_sai4_mclk_sel" , IMX_ADMA_ACM_SAI4_MCLK_SEL, imx8qm_mclk_sels, ARRAY_SIZE(imx8qm_mclk_sels), 0x120000, 0, 2 }, |
142 | { "acm_sai5_mclk_sel" , IMX_ADMA_ACM_SAI5_MCLK_SEL, imx8qm_mclk_sels, ARRAY_SIZE(imx8qm_mclk_sels), 0x130000, 0, 2 }, |
143 | { "acm_sai6_mclk_sel" , IMX_ADMA_ACM_SAI6_MCLK_SEL, imx8qm_mclk_sels, ARRAY_SIZE(imx8qm_mclk_sels), 0x140000, 0, 2 }, |
144 | { "acm_sai7_mclk_sel" , IMX_ADMA_ACM_SAI7_MCLK_SEL, imx8qm_mclk_sels, ARRAY_SIZE(imx8qm_mclk_sels), 0x150000, 0, 2 }, |
145 | { "acm_spdif0_mclk_sel" , IMX_ADMA_ACM_SPDIF0_TX_CLK_SEL, imx8qm_mclk_sels, ARRAY_SIZE(imx8qm_mclk_sels), 0x1A0000, 0, 2 }, |
146 | { "acm_spdif1_mclk_sel" , IMX_ADMA_ACM_SPDIF1_TX_CLK_SEL, imx8qm_mclk_sels, ARRAY_SIZE(imx8qm_mclk_sels), 0x1B0000, 0, 2 }, |
147 | { "acm_mqs_mclk_sel" , IMX_ADMA_ACM_MQS_TX_CLK_SEL, imx8qm_mclk_sels, ARRAY_SIZE(imx8qm_mclk_sels), 0x1C0000, 0, 2 }, |
148 | }; |
149 | |
150 | static const struct clk_parent_data imx8qxp_aud_clk_sels[] = { |
151 | { .fw_name = "aud_rec_clk0_lpcg_clk" }, |
152 | { .fw_name = "aud_rec_clk1_lpcg_clk" }, |
153 | { .fw_name = "ext_aud_mclk0" }, |
154 | { .fw_name = "ext_aud_mclk1" }, |
155 | { .fw_name = "esai0_rx_clk" }, |
156 | { .fw_name = "esai0_rx_hf_clk" }, |
157 | { .fw_name = "esai0_tx_clk" }, |
158 | { .fw_name = "esai0_tx_hf_clk" }, |
159 | { .fw_name = "spdif0_rx" }, |
160 | { .fw_name = "sai0_rx_bclk" }, |
161 | { .fw_name = "sai0_tx_bclk" }, |
162 | { .fw_name = "sai1_rx_bclk" }, |
163 | { .fw_name = "sai1_tx_bclk" }, |
164 | { .fw_name = "sai2_rx_bclk" }, |
165 | { .fw_name = "sai3_rx_bclk" }, |
166 | }; |
167 | |
168 | static const struct clk_parent_data imx8qxp_mclk_out_sels[] = { |
169 | { .fw_name = "aud_rec_clk0_lpcg_clk" }, |
170 | { .fw_name = "aud_rec_clk1_lpcg_clk" }, |
171 | { .index = -1 }, |
172 | { .index = -1 }, |
173 | { .fw_name = "spdif0_rx" }, |
174 | { .index = -1 }, |
175 | { .index = -1 }, |
176 | { .fw_name = "sai4_rx_bclk" }, |
177 | }; |
178 | |
179 | static const struct clk_parent_data imx8qxp_mclk_sels[] = { |
180 | { .fw_name = "aud_pll_div_clk0_lpcg_clk" }, |
181 | { .fw_name = "aud_pll_div_clk1_lpcg_clk" }, |
182 | { .fw_name = "acm_aud_clk0_sel" }, |
183 | { .fw_name = "acm_aud_clk1_sel" }, |
184 | }; |
185 | |
186 | static struct clk_imx8_acm_sel imx8qxp_sels[] = { |
187 | { "acm_aud_clk0_sel" , IMX_ADMA_ACM_AUD_CLK0_SEL, imx8qxp_aud_clk_sels, ARRAY_SIZE(imx8qxp_aud_clk_sels), 0x000000, 0, 5 }, |
188 | { "acm_aud_clk1_sel" , IMX_ADMA_ACM_AUD_CLK1_SEL, imx8qxp_aud_clk_sels, ARRAY_SIZE(imx8qxp_aud_clk_sels), 0x010000, 0, 5 }, |
189 | { "acm_mclkout0_sel" , IMX_ADMA_ACM_MCLKOUT0_SEL, imx8qxp_mclk_out_sels, ARRAY_SIZE(imx8qxp_mclk_out_sels), 0x020000, 0, 3 }, |
190 | { "acm_mclkout1_sel" , IMX_ADMA_ACM_MCLKOUT1_SEL, imx8qxp_mclk_out_sels, ARRAY_SIZE(imx8qxp_mclk_out_sels), 0x030000, 0, 3 }, |
191 | { "acm_esai0_mclk_sel" , IMX_ADMA_ACM_ESAI0_MCLK_SEL, imx8qxp_mclk_sels, ARRAY_SIZE(imx8qxp_mclk_sels), 0x060000, 0, 2 }, |
192 | { "acm_sai0_mclk_sel" , IMX_ADMA_ACM_SAI0_MCLK_SEL, imx8qxp_mclk_sels, ARRAY_SIZE(imx8qxp_mclk_sels), 0x0E0000, 0, 2 }, |
193 | { "acm_sai1_mclk_sel" , IMX_ADMA_ACM_SAI1_MCLK_SEL, imx8qxp_mclk_sels, ARRAY_SIZE(imx8qxp_mclk_sels), 0x0F0000, 0, 2 }, |
194 | { "acm_sai2_mclk_sel" , IMX_ADMA_ACM_SAI2_MCLK_SEL, imx8qxp_mclk_sels, ARRAY_SIZE(imx8qxp_mclk_sels), 0x100000, 0, 2 }, |
195 | { "acm_sai3_mclk_sel" , IMX_ADMA_ACM_SAI3_MCLK_SEL, imx8qxp_mclk_sels, ARRAY_SIZE(imx8qxp_mclk_sels), 0x110000, 0, 2 }, |
196 | { "acm_sai4_mclk_sel" , IMX_ADMA_ACM_SAI4_MCLK_SEL, imx8qxp_mclk_sels, ARRAY_SIZE(imx8qxp_mclk_sels), 0x140000, 0, 2 }, |
197 | { "acm_sai5_mclk_sel" , IMX_ADMA_ACM_SAI5_MCLK_SEL, imx8qxp_mclk_sels, ARRAY_SIZE(imx8qxp_mclk_sels), 0x150000, 0, 2 }, |
198 | { "acm_spdif0_mclk_sel" , IMX_ADMA_ACM_SPDIF0_TX_CLK_SEL, imx8qxp_mclk_sels, ARRAY_SIZE(imx8qxp_mclk_sels), 0x1A0000, 0, 2 }, |
199 | { "acm_mqs_mclk_sel" , IMX_ADMA_ACM_MQS_TX_CLK_SEL, imx8qxp_mclk_sels, ARRAY_SIZE(imx8qxp_mclk_sels), 0x1C0000, 0, 2 }, |
200 | }; |
201 | |
202 | static const struct clk_parent_data imx8dxl_aud_clk_sels[] = { |
203 | { .fw_name = "aud_rec_clk0_lpcg_clk" }, |
204 | { .fw_name = "aud_rec_clk1_lpcg_clk" }, |
205 | { .fw_name = "ext_aud_mclk0" }, |
206 | { .fw_name = "ext_aud_mclk1" }, |
207 | { .index = -1 }, |
208 | { .index = -1 }, |
209 | { .index = -1 }, |
210 | { .index = -1 }, |
211 | { .fw_name = "spdif0_rx" }, |
212 | { .fw_name = "sai0_rx_bclk" }, |
213 | { .fw_name = "sai0_tx_bclk" }, |
214 | { .fw_name = "sai1_rx_bclk" }, |
215 | { .fw_name = "sai1_tx_bclk" }, |
216 | { .fw_name = "sai2_rx_bclk" }, |
217 | { .fw_name = "sai3_rx_bclk" }, |
218 | }; |
219 | |
220 | static const struct clk_parent_data imx8dxl_mclk_out_sels[] = { |
221 | { .fw_name = "aud_rec_clk0_lpcg_clk" }, |
222 | { .fw_name = "aud_rec_clk1_lpcg_clk" }, |
223 | { .index = -1 }, |
224 | { .index = -1 }, |
225 | { .fw_name = "spdif0_rx" }, |
226 | { .index = -1 }, |
227 | { .index = -1 }, |
228 | { .index = -1 }, |
229 | }; |
230 | |
231 | static const struct clk_parent_data imx8dxl_mclk_sels[] = { |
232 | { .fw_name = "aud_pll_div_clk0_lpcg_clk" }, |
233 | { .fw_name = "aud_pll_div_clk1_lpcg_clk" }, |
234 | { .fw_name = "acm_aud_clk0_sel" }, |
235 | { .fw_name = "acm_aud_clk1_sel" }, |
236 | }; |
237 | |
238 | static struct clk_imx8_acm_sel imx8dxl_sels[] = { |
239 | { "acm_aud_clk0_sel" , IMX_ADMA_ACM_AUD_CLK0_SEL, imx8dxl_aud_clk_sels, ARRAY_SIZE(imx8dxl_aud_clk_sels), 0x000000, 0, 5 }, |
240 | { "acm_aud_clk1_sel" , IMX_ADMA_ACM_AUD_CLK1_SEL, imx8dxl_aud_clk_sels, ARRAY_SIZE(imx8dxl_aud_clk_sels), 0x010000, 0, 5 }, |
241 | { "acm_mclkout0_sel" , IMX_ADMA_ACM_MCLKOUT0_SEL, imx8dxl_mclk_out_sels, ARRAY_SIZE(imx8dxl_mclk_out_sels), 0x020000, 0, 3 }, |
242 | { "acm_mclkout1_sel" , IMX_ADMA_ACM_MCLKOUT1_SEL, imx8dxl_mclk_out_sels, ARRAY_SIZE(imx8dxl_mclk_out_sels), 0x030000, 0, 3 }, |
243 | { "acm_sai0_mclk_sel" , IMX_ADMA_ACM_SAI0_MCLK_SEL, imx8dxl_mclk_sels, ARRAY_SIZE(imx8dxl_mclk_sels), 0x0E0000, 0, 2 }, |
244 | { "acm_sai1_mclk_sel" , IMX_ADMA_ACM_SAI1_MCLK_SEL, imx8dxl_mclk_sels, ARRAY_SIZE(imx8dxl_mclk_sels), 0x0F0000, 0, 2 }, |
245 | { "acm_sai2_mclk_sel" , IMX_ADMA_ACM_SAI2_MCLK_SEL, imx8dxl_mclk_sels, ARRAY_SIZE(imx8dxl_mclk_sels), 0x100000, 0, 2 }, |
246 | { "acm_sai3_mclk_sel" , IMX_ADMA_ACM_SAI3_MCLK_SEL, imx8dxl_mclk_sels, ARRAY_SIZE(imx8dxl_mclk_sels), 0x110000, 0, 2 }, |
247 | { "acm_spdif0_mclk_sel" , IMX_ADMA_ACM_SPDIF0_TX_CLK_SEL, imx8dxl_mclk_sels, ARRAY_SIZE(imx8dxl_mclk_sels), 0x1A0000, 0, 2 }, |
248 | { "acm_mqs_mclk_sel" , IMX_ADMA_ACM_MQS_TX_CLK_SEL, imx8dxl_mclk_sels, ARRAY_SIZE(imx8dxl_mclk_sels), 0x1C0000, 0, 2 }, |
249 | }; |
250 | |
251 | /** |
252 | * clk_imx_acm_attach_pm_domains: attach multi power domains |
253 | * @dev: device pointer |
254 | * @dev_pm: power domains for device |
255 | */ |
256 | static int clk_imx_acm_attach_pm_domains(struct device *dev, |
257 | struct clk_imx_acm_pm_domains *dev_pm) |
258 | { |
259 | int ret; |
260 | int i; |
261 | |
262 | dev_pm->num_domains = of_count_phandle_with_args(np: dev->of_node, list_name: "power-domains" , |
263 | cells_name: "#power-domain-cells" ); |
264 | if (dev_pm->num_domains <= 1) |
265 | return 0; |
266 | |
267 | dev_pm->pd_dev = devm_kmalloc_array(dev, n: dev_pm->num_domains, |
268 | size: sizeof(*dev_pm->pd_dev), |
269 | GFP_KERNEL); |
270 | if (!dev_pm->pd_dev) |
271 | return -ENOMEM; |
272 | |
273 | dev_pm->pd_dev_link = devm_kmalloc_array(dev, |
274 | n: dev_pm->num_domains, |
275 | size: sizeof(*dev_pm->pd_dev_link), |
276 | GFP_KERNEL); |
277 | if (!dev_pm->pd_dev_link) |
278 | return -ENOMEM; |
279 | |
280 | for (i = 0; i < dev_pm->num_domains; i++) { |
281 | dev_pm->pd_dev[i] = dev_pm_domain_attach_by_id(dev, index: i); |
282 | if (IS_ERR(ptr: dev_pm->pd_dev[i])) { |
283 | ret = PTR_ERR(ptr: dev_pm->pd_dev[i]); |
284 | goto detach_pm; |
285 | } |
286 | |
287 | dev_pm->pd_dev_link[i] = device_link_add(consumer: dev, |
288 | supplier: dev_pm->pd_dev[i], |
289 | DL_FLAG_STATELESS | |
290 | DL_FLAG_PM_RUNTIME | |
291 | DL_FLAG_RPM_ACTIVE); |
292 | if (IS_ERR(ptr: dev_pm->pd_dev_link[i])) { |
293 | dev_pm_domain_detach(dev: dev_pm->pd_dev[i], power_off: false); |
294 | ret = PTR_ERR(ptr: dev_pm->pd_dev_link[i]); |
295 | goto detach_pm; |
296 | } |
297 | } |
298 | return 0; |
299 | |
300 | detach_pm: |
301 | while (--i >= 0) { |
302 | device_link_del(link: dev_pm->pd_dev_link[i]); |
303 | dev_pm_domain_detach(dev: dev_pm->pd_dev[i], power_off: false); |
304 | } |
305 | return ret; |
306 | } |
307 | |
308 | /** |
309 | * clk_imx_acm_detach_pm_domains: detach multi power domains |
310 | * @dev: deivice pointer |
311 | * @dev_pm: multi power domain for device |
312 | */ |
313 | static void clk_imx_acm_detach_pm_domains(struct device *dev, |
314 | struct clk_imx_acm_pm_domains *dev_pm) |
315 | { |
316 | int i; |
317 | |
318 | if (dev_pm->num_domains <= 1) |
319 | return; |
320 | |
321 | for (i = 0; i < dev_pm->num_domains; i++) { |
322 | device_link_del(link: dev_pm->pd_dev_link[i]); |
323 | dev_pm_domain_detach(dev: dev_pm->pd_dev[i], power_off: false); |
324 | } |
325 | } |
326 | |
327 | static int imx8_acm_clk_probe(struct platform_device *pdev) |
328 | { |
329 | struct clk_hw_onecell_data *clk_hw_data; |
330 | struct device *dev = &pdev->dev; |
331 | struct clk_imx8_acm_sel *sels; |
332 | struct imx8_acm_priv *priv; |
333 | struct clk_hw **hws; |
334 | void __iomem *base; |
335 | int ret; |
336 | int i; |
337 | |
338 | base = devm_of_iomap(dev, node: dev->of_node, index: 0, NULL); |
339 | if (WARN_ON(IS_ERR(base))) |
340 | return PTR_ERR(ptr: base); |
341 | |
342 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
343 | if (!priv) |
344 | return -ENOMEM; |
345 | |
346 | priv->reg = base; |
347 | priv->soc_data = of_device_get_match_data(dev); |
348 | platform_set_drvdata(pdev, data: priv); |
349 | |
350 | clk_hw_data = devm_kzalloc(dev: &pdev->dev, struct_size(clk_hw_data, hws, IMX_ADMA_ACM_CLK_END), |
351 | GFP_KERNEL); |
352 | if (!clk_hw_data) |
353 | return -ENOMEM; |
354 | |
355 | clk_hw_data->num = IMX_ADMA_ACM_CLK_END; |
356 | hws = clk_hw_data->hws; |
357 | |
358 | ret = clk_imx_acm_attach_pm_domains(dev: &pdev->dev, dev_pm: &priv->dev_pm); |
359 | if (ret) |
360 | return ret; |
361 | |
362 | pm_runtime_enable(dev: &pdev->dev); |
363 | pm_runtime_get_sync(dev: &pdev->dev); |
364 | |
365 | sels = priv->soc_data->sels; |
366 | for (i = 0; i < priv->soc_data->num_sels; i++) { |
367 | hws[sels[i].clkid] = devm_clk_hw_register_mux_parent_data_table(dev, |
368 | sels[i].name, sels[i].parents, |
369 | sels[i].num_parents, 0, |
370 | base + sels[i].reg, |
371 | sels[i].shift, sels[i].width, |
372 | 0, NULL, NULL); |
373 | if (IS_ERR(ptr: hws[sels[i].clkid])) { |
374 | ret = PTR_ERR(ptr: hws[sels[i].clkid]); |
375 | imx_check_clk_hws(clks: hws, IMX_ADMA_ACM_CLK_END); |
376 | goto err_clk_register; |
377 | } |
378 | } |
379 | |
380 | ret = devm_of_clk_add_hw_provider(dev, get: of_clk_hw_onecell_get, data: clk_hw_data); |
381 | if (ret < 0) { |
382 | dev_err(dev, "failed to register hws for ACM\n" ); |
383 | goto err_clk_register; |
384 | } |
385 | |
386 | pm_runtime_put_sync(dev: &pdev->dev); |
387 | return 0; |
388 | |
389 | err_clk_register: |
390 | pm_runtime_put_sync(dev: &pdev->dev); |
391 | pm_runtime_disable(dev: &pdev->dev); |
392 | clk_imx_acm_detach_pm_domains(dev: &pdev->dev, dev_pm: &priv->dev_pm); |
393 | |
394 | return ret; |
395 | } |
396 | |
397 | static void imx8_acm_clk_remove(struct platform_device *pdev) |
398 | { |
399 | struct imx8_acm_priv *priv = dev_get_drvdata(dev: &pdev->dev); |
400 | |
401 | pm_runtime_disable(dev: &pdev->dev); |
402 | |
403 | clk_imx_acm_detach_pm_domains(dev: &pdev->dev, dev_pm: &priv->dev_pm); |
404 | } |
405 | |
406 | static const struct imx8_acm_soc_data imx8qm_acm_data = { |
407 | .sels = imx8qm_sels, |
408 | .num_sels = ARRAY_SIZE(imx8qm_sels), |
409 | }; |
410 | |
411 | static const struct imx8_acm_soc_data imx8qxp_acm_data = { |
412 | .sels = imx8qxp_sels, |
413 | .num_sels = ARRAY_SIZE(imx8qxp_sels), |
414 | }; |
415 | |
416 | static const struct imx8_acm_soc_data imx8dxl_acm_data = { |
417 | .sels = imx8dxl_sels, |
418 | .num_sels = ARRAY_SIZE(imx8dxl_sels), |
419 | }; |
420 | |
421 | static const struct of_device_id imx8_acm_match[] = { |
422 | { .compatible = "fsl,imx8qm-acm" , .data = &imx8qm_acm_data }, |
423 | { .compatible = "fsl,imx8qxp-acm" , .data = &imx8qxp_acm_data }, |
424 | { .compatible = "fsl,imx8dxl-acm" , .data = &imx8dxl_acm_data }, |
425 | { /* sentinel */ } |
426 | }; |
427 | MODULE_DEVICE_TABLE(of, imx8_acm_match); |
428 | |
429 | static int __maybe_unused imx8_acm_runtime_suspend(struct device *dev) |
430 | { |
431 | struct imx8_acm_priv *priv = dev_get_drvdata(dev); |
432 | struct clk_imx8_acm_sel *sels; |
433 | int i; |
434 | |
435 | sels = priv->soc_data->sels; |
436 | |
437 | for (i = 0; i < priv->soc_data->num_sels; i++) |
438 | priv->regs[i] = readl_relaxed(priv->reg + sels[i].reg); |
439 | |
440 | return 0; |
441 | } |
442 | |
443 | static int __maybe_unused imx8_acm_runtime_resume(struct device *dev) |
444 | { |
445 | struct imx8_acm_priv *priv = dev_get_drvdata(dev); |
446 | struct clk_imx8_acm_sel *sels; |
447 | int i; |
448 | |
449 | sels = priv->soc_data->sels; |
450 | |
451 | for (i = 0; i < priv->soc_data->num_sels; i++) |
452 | writel_relaxed(priv->regs[i], priv->reg + sels[i].reg); |
453 | |
454 | return 0; |
455 | } |
456 | |
457 | static const struct dev_pm_ops imx8_acm_pm_ops = { |
458 | SET_RUNTIME_PM_OPS(imx8_acm_runtime_suspend, |
459 | imx8_acm_runtime_resume, NULL) |
460 | SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, |
461 | pm_runtime_force_resume) |
462 | }; |
463 | |
464 | static struct platform_driver imx8_acm_clk_driver = { |
465 | .driver = { |
466 | .name = "imx8-acm" , |
467 | .of_match_table = imx8_acm_match, |
468 | .pm = &imx8_acm_pm_ops, |
469 | }, |
470 | .probe = imx8_acm_clk_probe, |
471 | .remove_new = imx8_acm_clk_remove, |
472 | }; |
473 | module_platform_driver(imx8_acm_clk_driver); |
474 | |
475 | MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>" ); |
476 | MODULE_DESCRIPTION("Freescale i.MX8 Audio Clock Mux driver" ); |
477 | MODULE_LICENSE("GPL" ); |
478 | |