1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * NXP AUDMIX ALSA SoC Digital Audio Interface (DAI) driver |
4 | * |
5 | * Copyright 2017 NXP |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/module.h> |
10 | #include <linux/of_platform.h> |
11 | #include <linux/pm_runtime.h> |
12 | #include <sound/soc.h> |
13 | #include <sound/pcm_params.h> |
14 | |
15 | #include "fsl_audmix.h" |
16 | |
17 | #define SOC_ENUM_SINGLE_S(xreg, xshift, xtexts) \ |
18 | SOC_ENUM_SINGLE(xreg, xshift, ARRAY_SIZE(xtexts), xtexts) |
19 | |
20 | static const char |
21 | *tdm_sel[] = { "TDM1" , "TDM2" , }, |
22 | *mode_sel[] = { "Disabled" , "TDM1" , "TDM2" , "Mixed" , }, |
23 | *width_sel[] = { "16b" , "18b" , "20b" , "24b" , "32b" , }, |
24 | *endis_sel[] = { "Disabled" , "Enabled" , }, |
25 | *updn_sel[] = { "Downward" , "Upward" , }, |
26 | *mask_sel[] = { "Unmask" , "Mask" , }; |
27 | |
28 | static const struct soc_enum fsl_audmix_enum[] = { |
29 | /* FSL_AUDMIX_CTR enums */ |
30 | SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_MIXCLK_SHIFT, tdm_sel), |
31 | SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_OUTSRC_SHIFT, mode_sel), |
32 | SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_OUTWIDTH_SHIFT, width_sel), |
33 | SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_MASKRTDF_SHIFT, mask_sel), |
34 | SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_MASKCKDF_SHIFT, mask_sel), |
35 | SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_SYNCMODE_SHIFT, endis_sel), |
36 | SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_SYNCSRC_SHIFT, tdm_sel), |
37 | /* FSL_AUDMIX_ATCR0 enums */ |
38 | SOC_ENUM_SINGLE_S(FSL_AUDMIX_ATCR0, 0, endis_sel), |
39 | SOC_ENUM_SINGLE_S(FSL_AUDMIX_ATCR0, 1, updn_sel), |
40 | /* FSL_AUDMIX_ATCR1 enums */ |
41 | SOC_ENUM_SINGLE_S(FSL_AUDMIX_ATCR1, 0, endis_sel), |
42 | SOC_ENUM_SINGLE_S(FSL_AUDMIX_ATCR1, 1, updn_sel), |
43 | }; |
44 | |
45 | struct fsl_audmix_state { |
46 | u8 tdms; |
47 | u8 clk; |
48 | char msg[64]; |
49 | }; |
50 | |
51 | static const struct fsl_audmix_state prms[4][4] = {{ |
52 | /* DIS->DIS, do nothing */ |
53 | { .tdms = 0, .clk = 0, .msg = "" }, |
54 | /* DIS->TDM1*/ |
55 | { .tdms = 1, .clk = 1, .msg = "DIS->TDM1: TDM1 not started!\n" }, |
56 | /* DIS->TDM2*/ |
57 | { .tdms = 2, .clk = 2, .msg = "DIS->TDM2: TDM2 not started!\n" }, |
58 | /* DIS->MIX */ |
59 | { .tdms = 3, .clk = 0, .msg = "DIS->MIX: Please start both TDMs!\n" } |
60 | }, { /* TDM1->DIS */ |
61 | { .tdms = 1, .clk = 0, .msg = "TDM1->DIS: TDM1 not started!\n" }, |
62 | /* TDM1->TDM1, do nothing */ |
63 | { .tdms = 0, .clk = 0, .msg = "" }, |
64 | /* TDM1->TDM2 */ |
65 | { .tdms = 3, .clk = 2, .msg = "TDM1->TDM2: Please start both TDMs!\n" }, |
66 | /* TDM1->MIX */ |
67 | { .tdms = 3, .clk = 0, .msg = "TDM1->MIX: Please start both TDMs!\n" } |
68 | }, { /* TDM2->DIS */ |
69 | { .tdms = 2, .clk = 0, .msg = "TDM2->DIS: TDM2 not started!\n" }, |
70 | /* TDM2->TDM1 */ |
71 | { .tdms = 3, .clk = 1, .msg = "TDM2->TDM1: Please start both TDMs!\n" }, |
72 | /* TDM2->TDM2, do nothing */ |
73 | { .tdms = 0, .clk = 0, .msg = "" }, |
74 | /* TDM2->MIX */ |
75 | { .tdms = 3, .clk = 0, .msg = "TDM2->MIX: Please start both TDMs!\n" } |
76 | }, { /* MIX->DIS */ |
77 | { .tdms = 3, .clk = 0, .msg = "MIX->DIS: Please start both TDMs!\n" }, |
78 | /* MIX->TDM1 */ |
79 | { .tdms = 3, .clk = 1, .msg = "MIX->TDM1: Please start both TDMs!\n" }, |
80 | /* MIX->TDM2 */ |
81 | { .tdms = 3, .clk = 2, .msg = "MIX->TDM2: Please start both TDMs!\n" }, |
82 | /* MIX->MIX, do nothing */ |
83 | { .tdms = 0, .clk = 0, .msg = "" } |
84 | }, }; |
85 | |
86 | static int fsl_audmix_state_trans(struct snd_soc_component *comp, |
87 | unsigned int *mask, unsigned int *ctr, |
88 | const struct fsl_audmix_state prm) |
89 | { |
90 | struct fsl_audmix *priv = snd_soc_component_get_drvdata(c: comp); |
91 | /* Enforce all required TDMs are started */ |
92 | if ((priv->tdms & prm.tdms) != prm.tdms) { |
93 | dev_dbg(comp->dev, "%s" , prm.msg); |
94 | return -EINVAL; |
95 | } |
96 | |
97 | switch (prm.clk) { |
98 | case 1: |
99 | case 2: |
100 | /* Set mix clock */ |
101 | (*mask) |= FSL_AUDMIX_CTR_MIXCLK_MASK; |
102 | (*ctr) |= FSL_AUDMIX_CTR_MIXCLK(prm.clk - 1); |
103 | break; |
104 | default: |
105 | break; |
106 | } |
107 | |
108 | return 0; |
109 | } |
110 | |
111 | static int fsl_audmix_put_mix_clk_src(struct snd_kcontrol *kcontrol, |
112 | struct snd_ctl_elem_value *ucontrol) |
113 | { |
114 | struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); |
115 | struct fsl_audmix *priv = snd_soc_component_get_drvdata(c: comp); |
116 | struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; |
117 | unsigned int *item = ucontrol->value.enumerated.item; |
118 | unsigned int reg_val, val, mix_clk; |
119 | |
120 | /* Get current state */ |
121 | reg_val = snd_soc_component_read(component: comp, FSL_AUDMIX_CTR); |
122 | mix_clk = ((reg_val & FSL_AUDMIX_CTR_MIXCLK_MASK) |
123 | >> FSL_AUDMIX_CTR_MIXCLK_SHIFT); |
124 | val = snd_soc_enum_item_to_val(e, item: item[0]); |
125 | |
126 | dev_dbg(comp->dev, "TDMs=x%08x, val=x%08x\n" , priv->tdms, val); |
127 | |
128 | /** |
129 | * Ensure the current selected mixer clock is available |
130 | * for configuration propagation |
131 | */ |
132 | if (!(priv->tdms & BIT(mix_clk))) { |
133 | dev_err(comp->dev, |
134 | "Started TDM%d needed for config propagation!\n" , |
135 | mix_clk + 1); |
136 | return -EINVAL; |
137 | } |
138 | |
139 | if (!(priv->tdms & BIT(val))) { |
140 | dev_err(comp->dev, |
141 | "The selected clock source has no TDM%d enabled!\n" , |
142 | val + 1); |
143 | return -EINVAL; |
144 | } |
145 | |
146 | return snd_soc_put_enum_double(kcontrol, ucontrol); |
147 | } |
148 | |
149 | static int fsl_audmix_put_out_src(struct snd_kcontrol *kcontrol, |
150 | struct snd_ctl_elem_value *ucontrol) |
151 | { |
152 | struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); |
153 | struct fsl_audmix *priv = snd_soc_component_get_drvdata(c: comp); |
154 | struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; |
155 | unsigned int *item = ucontrol->value.enumerated.item; |
156 | u32 out_src, mix_clk; |
157 | unsigned int reg_val, val, mask = 0, ctr = 0; |
158 | int ret; |
159 | |
160 | /* Get current state */ |
161 | reg_val = snd_soc_component_read(component: comp, FSL_AUDMIX_CTR); |
162 | |
163 | /* "From" state */ |
164 | out_src = ((reg_val & FSL_AUDMIX_CTR_OUTSRC_MASK) |
165 | >> FSL_AUDMIX_CTR_OUTSRC_SHIFT); |
166 | mix_clk = ((reg_val & FSL_AUDMIX_CTR_MIXCLK_MASK) |
167 | >> FSL_AUDMIX_CTR_MIXCLK_SHIFT); |
168 | |
169 | /* "To" state */ |
170 | val = snd_soc_enum_item_to_val(e, item: item[0]); |
171 | |
172 | dev_dbg(comp->dev, "TDMs=x%08x, val=x%08x\n" , priv->tdms, val); |
173 | |
174 | /* Check if state is changing ... */ |
175 | if (out_src == val) |
176 | return 0; |
177 | /** |
178 | * Ensure the current selected mixer clock is available |
179 | * for configuration propagation |
180 | */ |
181 | if (!(priv->tdms & BIT(mix_clk))) { |
182 | dev_err(comp->dev, |
183 | "Started TDM%d needed for config propagation!\n" , |
184 | mix_clk + 1); |
185 | return -EINVAL; |
186 | } |
187 | |
188 | /* Check state transition constraints */ |
189 | ret = fsl_audmix_state_trans(comp, mask: &mask, ctr: &ctr, prm: prms[out_src][val]); |
190 | if (ret) |
191 | return ret; |
192 | |
193 | /* Complete transition to new state */ |
194 | mask |= FSL_AUDMIX_CTR_OUTSRC_MASK; |
195 | ctr |= FSL_AUDMIX_CTR_OUTSRC(val); |
196 | |
197 | return snd_soc_component_update_bits(component: comp, FSL_AUDMIX_CTR, mask, val: ctr); |
198 | } |
199 | |
200 | static const struct snd_kcontrol_new fsl_audmix_snd_controls[] = { |
201 | /* FSL_AUDMIX_CTR controls */ |
202 | SOC_ENUM_EXT("Mixing Clock Source" , fsl_audmix_enum[0], |
203 | snd_soc_get_enum_double, fsl_audmix_put_mix_clk_src), |
204 | SOC_ENUM_EXT("Output Source" , fsl_audmix_enum[1], |
205 | snd_soc_get_enum_double, fsl_audmix_put_out_src), |
206 | SOC_ENUM("Output Width" , fsl_audmix_enum[2]), |
207 | SOC_ENUM("Frame Rate Diff Error" , fsl_audmix_enum[3]), |
208 | SOC_ENUM("Clock Freq Diff Error" , fsl_audmix_enum[4]), |
209 | SOC_ENUM("Sync Mode Config" , fsl_audmix_enum[5]), |
210 | SOC_ENUM("Sync Mode Clk Source" , fsl_audmix_enum[6]), |
211 | /* TDM1 Attenuation controls */ |
212 | SOC_ENUM("TDM1 Attenuation" , fsl_audmix_enum[7]), |
213 | SOC_ENUM("TDM1 Attenuation Direction" , fsl_audmix_enum[8]), |
214 | SOC_SINGLE("TDM1 Attenuation Step Divider" , FSL_AUDMIX_ATCR0, |
215 | 2, 0x00fff, 0), |
216 | SOC_SINGLE("TDM1 Attenuation Initial Value" , FSL_AUDMIX_ATIVAL0, |
217 | 0, 0x3ffff, 0), |
218 | SOC_SINGLE("TDM1 Attenuation Step Up Factor" , FSL_AUDMIX_ATSTPUP0, |
219 | 0, 0x3ffff, 0), |
220 | SOC_SINGLE("TDM1 Attenuation Step Down Factor" , FSL_AUDMIX_ATSTPDN0, |
221 | 0, 0x3ffff, 0), |
222 | SOC_SINGLE("TDM1 Attenuation Step Target" , FSL_AUDMIX_ATSTPTGT0, |
223 | 0, 0x3ffff, 0), |
224 | /* TDM2 Attenuation controls */ |
225 | SOC_ENUM("TDM2 Attenuation" , fsl_audmix_enum[9]), |
226 | SOC_ENUM("TDM2 Attenuation Direction" , fsl_audmix_enum[10]), |
227 | SOC_SINGLE("TDM2 Attenuation Step Divider" , FSL_AUDMIX_ATCR1, |
228 | 2, 0x00fff, 0), |
229 | SOC_SINGLE("TDM2 Attenuation Initial Value" , FSL_AUDMIX_ATIVAL1, |
230 | 0, 0x3ffff, 0), |
231 | SOC_SINGLE("TDM2 Attenuation Step Up Factor" , FSL_AUDMIX_ATSTPUP1, |
232 | 0, 0x3ffff, 0), |
233 | SOC_SINGLE("TDM2 Attenuation Step Down Factor" , FSL_AUDMIX_ATSTPDN1, |
234 | 0, 0x3ffff, 0), |
235 | SOC_SINGLE("TDM2 Attenuation Step Target" , FSL_AUDMIX_ATSTPTGT1, |
236 | 0, 0x3ffff, 0), |
237 | }; |
238 | |
239 | static int fsl_audmix_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) |
240 | { |
241 | struct snd_soc_component *comp = dai->component; |
242 | u32 mask = 0, ctr = 0; |
243 | |
244 | /* AUDMIX is working in DSP_A format only */ |
245 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { |
246 | case SND_SOC_DAIFMT_DSP_A: |
247 | break; |
248 | default: |
249 | return -EINVAL; |
250 | } |
251 | |
252 | /* For playback the AUDMIX is consumer, and for record is provider */ |
253 | switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { |
254 | case SND_SOC_DAIFMT_BC_FC: |
255 | case SND_SOC_DAIFMT_BP_FP: |
256 | break; |
257 | default: |
258 | return -EINVAL; |
259 | } |
260 | |
261 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { |
262 | case SND_SOC_DAIFMT_IB_NF: |
263 | /* Output data will be written on positive edge of the clock */ |
264 | ctr |= FSL_AUDMIX_CTR_OUTCKPOL(0); |
265 | break; |
266 | case SND_SOC_DAIFMT_NB_NF: |
267 | /* Output data will be written on negative edge of the clock */ |
268 | ctr |= FSL_AUDMIX_CTR_OUTCKPOL(1); |
269 | break; |
270 | default: |
271 | return -EINVAL; |
272 | } |
273 | |
274 | mask |= FSL_AUDMIX_CTR_OUTCKPOL_MASK; |
275 | |
276 | return snd_soc_component_update_bits(component: comp, FSL_AUDMIX_CTR, mask, val: ctr); |
277 | } |
278 | |
279 | static int fsl_audmix_dai_trigger(struct snd_pcm_substream *substream, int cmd, |
280 | struct snd_soc_dai *dai) |
281 | { |
282 | struct fsl_audmix *priv = snd_soc_dai_get_drvdata(dai); |
283 | unsigned long lock_flags; |
284 | |
285 | /* Capture stream shall not be handled */ |
286 | if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
287 | return 0; |
288 | |
289 | switch (cmd) { |
290 | case SNDRV_PCM_TRIGGER_START: |
291 | case SNDRV_PCM_TRIGGER_RESUME: |
292 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
293 | spin_lock_irqsave(&priv->lock, lock_flags); |
294 | priv->tdms |= BIT(dai->driver->id); |
295 | spin_unlock_irqrestore(lock: &priv->lock, flags: lock_flags); |
296 | break; |
297 | case SNDRV_PCM_TRIGGER_STOP: |
298 | case SNDRV_PCM_TRIGGER_SUSPEND: |
299 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
300 | spin_lock_irqsave(&priv->lock, lock_flags); |
301 | priv->tdms &= ~BIT(dai->driver->id); |
302 | spin_unlock_irqrestore(lock: &priv->lock, flags: lock_flags); |
303 | break; |
304 | default: |
305 | return -EINVAL; |
306 | } |
307 | |
308 | return 0; |
309 | } |
310 | |
311 | static const struct snd_soc_dai_ops fsl_audmix_dai_ops = { |
312 | .set_fmt = fsl_audmix_dai_set_fmt, |
313 | .trigger = fsl_audmix_dai_trigger, |
314 | }; |
315 | |
316 | static struct snd_soc_dai_driver fsl_audmix_dai[] = { |
317 | { |
318 | .id = 0, |
319 | .name = "audmix-0" , |
320 | .playback = { |
321 | .stream_name = "AUDMIX-Playback-0" , |
322 | .channels_min = 8, |
323 | .channels_max = 8, |
324 | .rate_min = 8000, |
325 | .rate_max = 96000, |
326 | .rates = SNDRV_PCM_RATE_8000_96000, |
327 | .formats = FSL_AUDMIX_FORMATS, |
328 | }, |
329 | .capture = { |
330 | .stream_name = "AUDMIX-Capture-0" , |
331 | .channels_min = 8, |
332 | .channels_max = 8, |
333 | .rate_min = 8000, |
334 | .rate_max = 96000, |
335 | .rates = SNDRV_PCM_RATE_8000_96000, |
336 | .formats = FSL_AUDMIX_FORMATS, |
337 | }, |
338 | .ops = &fsl_audmix_dai_ops, |
339 | }, |
340 | { |
341 | .id = 1, |
342 | .name = "audmix-1" , |
343 | .playback = { |
344 | .stream_name = "AUDMIX-Playback-1" , |
345 | .channels_min = 8, |
346 | .channels_max = 8, |
347 | .rate_min = 8000, |
348 | .rate_max = 96000, |
349 | .rates = SNDRV_PCM_RATE_8000_96000, |
350 | .formats = FSL_AUDMIX_FORMATS, |
351 | }, |
352 | .capture = { |
353 | .stream_name = "AUDMIX-Capture-1" , |
354 | .channels_min = 8, |
355 | .channels_max = 8, |
356 | .rate_min = 8000, |
357 | .rate_max = 96000, |
358 | .rates = SNDRV_PCM_RATE_8000_96000, |
359 | .formats = FSL_AUDMIX_FORMATS, |
360 | }, |
361 | .ops = &fsl_audmix_dai_ops, |
362 | }, |
363 | }; |
364 | |
365 | static const struct snd_soc_component_driver fsl_audmix_component = { |
366 | .name = "fsl-audmix-dai" , |
367 | .controls = fsl_audmix_snd_controls, |
368 | .num_controls = ARRAY_SIZE(fsl_audmix_snd_controls), |
369 | }; |
370 | |
371 | static bool fsl_audmix_readable_reg(struct device *dev, unsigned int reg) |
372 | { |
373 | switch (reg) { |
374 | case FSL_AUDMIX_CTR: |
375 | case FSL_AUDMIX_STR: |
376 | case FSL_AUDMIX_ATCR0: |
377 | case FSL_AUDMIX_ATIVAL0: |
378 | case FSL_AUDMIX_ATSTPUP0: |
379 | case FSL_AUDMIX_ATSTPDN0: |
380 | case FSL_AUDMIX_ATSTPTGT0: |
381 | case FSL_AUDMIX_ATTNVAL0: |
382 | case FSL_AUDMIX_ATSTP0: |
383 | case FSL_AUDMIX_ATCR1: |
384 | case FSL_AUDMIX_ATIVAL1: |
385 | case FSL_AUDMIX_ATSTPUP1: |
386 | case FSL_AUDMIX_ATSTPDN1: |
387 | case FSL_AUDMIX_ATSTPTGT1: |
388 | case FSL_AUDMIX_ATTNVAL1: |
389 | case FSL_AUDMIX_ATSTP1: |
390 | return true; |
391 | default: |
392 | return false; |
393 | } |
394 | } |
395 | |
396 | static bool fsl_audmix_writeable_reg(struct device *dev, unsigned int reg) |
397 | { |
398 | switch (reg) { |
399 | case FSL_AUDMIX_CTR: |
400 | case FSL_AUDMIX_ATCR0: |
401 | case FSL_AUDMIX_ATIVAL0: |
402 | case FSL_AUDMIX_ATSTPUP0: |
403 | case FSL_AUDMIX_ATSTPDN0: |
404 | case FSL_AUDMIX_ATSTPTGT0: |
405 | case FSL_AUDMIX_ATCR1: |
406 | case FSL_AUDMIX_ATIVAL1: |
407 | case FSL_AUDMIX_ATSTPUP1: |
408 | case FSL_AUDMIX_ATSTPDN1: |
409 | case FSL_AUDMIX_ATSTPTGT1: |
410 | return true; |
411 | default: |
412 | return false; |
413 | } |
414 | } |
415 | |
416 | static const struct reg_default fsl_audmix_reg[] = { |
417 | { FSL_AUDMIX_CTR, 0x00060 }, |
418 | { FSL_AUDMIX_STR, 0x00003 }, |
419 | { FSL_AUDMIX_ATCR0, 0x00000 }, |
420 | { FSL_AUDMIX_ATIVAL0, 0x3FFFF }, |
421 | { FSL_AUDMIX_ATSTPUP0, 0x2AAAA }, |
422 | { FSL_AUDMIX_ATSTPDN0, 0x30000 }, |
423 | { FSL_AUDMIX_ATSTPTGT0, 0x00010 }, |
424 | { FSL_AUDMIX_ATTNVAL0, 0x00000 }, |
425 | { FSL_AUDMIX_ATSTP0, 0x00000 }, |
426 | { FSL_AUDMIX_ATCR1, 0x00000 }, |
427 | { FSL_AUDMIX_ATIVAL1, 0x3FFFF }, |
428 | { FSL_AUDMIX_ATSTPUP1, 0x2AAAA }, |
429 | { FSL_AUDMIX_ATSTPDN1, 0x30000 }, |
430 | { FSL_AUDMIX_ATSTPTGT1, 0x00010 }, |
431 | { FSL_AUDMIX_ATTNVAL1, 0x00000 }, |
432 | { FSL_AUDMIX_ATSTP1, 0x00000 }, |
433 | }; |
434 | |
435 | static const struct regmap_config fsl_audmix_regmap_config = { |
436 | .reg_bits = 32, |
437 | .reg_stride = 4, |
438 | .val_bits = 32, |
439 | .max_register = FSL_AUDMIX_ATSTP1, |
440 | .reg_defaults = fsl_audmix_reg, |
441 | .num_reg_defaults = ARRAY_SIZE(fsl_audmix_reg), |
442 | .readable_reg = fsl_audmix_readable_reg, |
443 | .writeable_reg = fsl_audmix_writeable_reg, |
444 | .cache_type = REGCACHE_FLAT, |
445 | }; |
446 | |
447 | static const struct of_device_id fsl_audmix_ids[] = { |
448 | { |
449 | .compatible = "fsl,imx8qm-audmix" , |
450 | }, |
451 | { /* sentinel */ } |
452 | }; |
453 | MODULE_DEVICE_TABLE(of, fsl_audmix_ids); |
454 | |
455 | static int fsl_audmix_probe(struct platform_device *pdev) |
456 | { |
457 | struct device *dev = &pdev->dev; |
458 | struct fsl_audmix *priv; |
459 | void __iomem *regs; |
460 | int ret; |
461 | |
462 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
463 | if (!priv) |
464 | return -ENOMEM; |
465 | |
466 | /* Get the addresses */ |
467 | regs = devm_platform_ioremap_resource(pdev, index: 0); |
468 | if (IS_ERR(ptr: regs)) |
469 | return PTR_ERR(ptr: regs); |
470 | |
471 | priv->regmap = devm_regmap_init_mmio(dev, regs, &fsl_audmix_regmap_config); |
472 | if (IS_ERR(ptr: priv->regmap)) { |
473 | dev_err(dev, "failed to init regmap\n" ); |
474 | return PTR_ERR(ptr: priv->regmap); |
475 | } |
476 | |
477 | priv->ipg_clk = devm_clk_get(dev, id: "ipg" ); |
478 | if (IS_ERR(ptr: priv->ipg_clk)) { |
479 | dev_err(dev, "failed to get ipg clock\n" ); |
480 | return PTR_ERR(ptr: priv->ipg_clk); |
481 | } |
482 | |
483 | spin_lock_init(&priv->lock); |
484 | platform_set_drvdata(pdev, data: priv); |
485 | pm_runtime_enable(dev); |
486 | |
487 | ret = devm_snd_soc_register_component(dev, component_driver: &fsl_audmix_component, |
488 | dai_drv: fsl_audmix_dai, |
489 | ARRAY_SIZE(fsl_audmix_dai)); |
490 | if (ret) { |
491 | dev_err(dev, "failed to register ASoC DAI\n" ); |
492 | goto err_disable_pm; |
493 | } |
494 | |
495 | priv->pdev = platform_device_register_data(parent: dev, name: "imx-audmix" , id: 0, NULL, size: 0); |
496 | if (IS_ERR(ptr: priv->pdev)) { |
497 | ret = PTR_ERR(ptr: priv->pdev); |
498 | dev_err(dev, "failed to register platform: %d\n" , ret); |
499 | goto err_disable_pm; |
500 | } |
501 | |
502 | return 0; |
503 | |
504 | err_disable_pm: |
505 | pm_runtime_disable(dev); |
506 | return ret; |
507 | } |
508 | |
509 | static void fsl_audmix_remove(struct platform_device *pdev) |
510 | { |
511 | struct fsl_audmix *priv = dev_get_drvdata(dev: &pdev->dev); |
512 | |
513 | pm_runtime_disable(dev: &pdev->dev); |
514 | |
515 | if (priv->pdev) |
516 | platform_device_unregister(priv->pdev); |
517 | } |
518 | |
519 | #ifdef CONFIG_PM |
520 | static int fsl_audmix_runtime_resume(struct device *dev) |
521 | { |
522 | struct fsl_audmix *priv = dev_get_drvdata(dev); |
523 | int ret; |
524 | |
525 | ret = clk_prepare_enable(clk: priv->ipg_clk); |
526 | if (ret) { |
527 | dev_err(dev, "Failed to enable IPG clock: %d\n" , ret); |
528 | return ret; |
529 | } |
530 | |
531 | regcache_cache_only(map: priv->regmap, enable: false); |
532 | regcache_mark_dirty(map: priv->regmap); |
533 | |
534 | return regcache_sync(map: priv->regmap); |
535 | } |
536 | |
537 | static int fsl_audmix_runtime_suspend(struct device *dev) |
538 | { |
539 | struct fsl_audmix *priv = dev_get_drvdata(dev); |
540 | |
541 | regcache_cache_only(map: priv->regmap, enable: true); |
542 | |
543 | clk_disable_unprepare(clk: priv->ipg_clk); |
544 | |
545 | return 0; |
546 | } |
547 | #endif /* CONFIG_PM */ |
548 | |
549 | static const struct dev_pm_ops fsl_audmix_pm = { |
550 | SET_RUNTIME_PM_OPS(fsl_audmix_runtime_suspend, |
551 | fsl_audmix_runtime_resume, |
552 | NULL) |
553 | SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, |
554 | pm_runtime_force_resume) |
555 | }; |
556 | |
557 | static struct platform_driver fsl_audmix_driver = { |
558 | .probe = fsl_audmix_probe, |
559 | .remove_new = fsl_audmix_remove, |
560 | .driver = { |
561 | .name = "fsl-audmix" , |
562 | .of_match_table = fsl_audmix_ids, |
563 | .pm = &fsl_audmix_pm, |
564 | }, |
565 | }; |
566 | module_platform_driver(fsl_audmix_driver); |
567 | |
568 | MODULE_DESCRIPTION("NXP AUDMIX ASoC DAI driver" ); |
569 | MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>" ); |
570 | MODULE_ALIAS("platform:fsl-audmix" ); |
571 | MODULE_LICENSE("GPL v2" ); |
572 | |