1 // SPDX-License-Identifier: GPL-2.0
3 * NXP AUDMIX ALSA SoC Digital Audio Interface (DAI) driver
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>
15 #include "fsl_audmix.h"
17 #define SOC_ENUM_SINGLE_S(xreg, xshift, xtexts) \
18 SOC_ENUM_SINGLE(xreg, xshift, ARRAY_SIZE(xtexts), xtexts)
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", };
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
),
45 struct fsl_audmix_state
{
51 static const struct fsl_audmix_state prms
[4][4] = {{
52 /* DIS->DIS, do nothing */
53 { .tdms
= 0, .clk
= 0, .msg
= "" },
55 { .tdms
= 1, .clk
= 1, .msg
= "DIS->TDM1: TDM1 not started!\n" },
57 { .tdms
= 2, .clk
= 2, .msg
= "DIS->TDM2: TDM2 not started!\n" },
59 { .tdms
= 3, .clk
= 0, .msg
= "DIS->MIX: Please start both TDMs!\n" }
61 { .tdms
= 1, .clk
= 0, .msg
= "TDM1->DIS: TDM1 not started!\n" },
62 /* TDM1->TDM1, do nothing */
63 { .tdms
= 0, .clk
= 0, .msg
= "" },
65 { .tdms
= 3, .clk
= 2, .msg
= "TDM1->TDM2: Please start both TDMs!\n" },
67 { .tdms
= 3, .clk
= 0, .msg
= "TDM1->MIX: Please start both TDMs!\n" }
69 { .tdms
= 2, .clk
= 0, .msg
= "TDM2->DIS: TDM2 not started!\n" },
71 { .tdms
= 3, .clk
= 1, .msg
= "TDM2->TDM1: Please start both TDMs!\n" },
72 /* TDM2->TDM2, do nothing */
73 { .tdms
= 0, .clk
= 0, .msg
= "" },
75 { .tdms
= 3, .clk
= 0, .msg
= "TDM2->MIX: Please start both TDMs!\n" }
77 { .tdms
= 3, .clk
= 0, .msg
= "MIX->DIS: Please start both TDMs!\n" },
79 { .tdms
= 3, .clk
= 1, .msg
= "MIX->TDM1: Please start both TDMs!\n" },
81 { .tdms
= 3, .clk
= 2, .msg
= "MIX->TDM2: Please start both TDMs!\n" },
82 /* MIX->MIX, do nothing */
83 { .tdms
= 0, .clk
= 0, .msg
= "" }
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
)
90 struct fsl_audmix
*priv
= snd_soc_component_get_drvdata(comp
);
91 /* Enforce all required TDMs are started */
92 if ((priv
->tdms
& prm
.tdms
) != prm
.tdms
) {
93 dev_dbg(comp
->dev
, "%s", prm
.msg
);
101 (*mask
) |= FSL_AUDMIX_CTR_MIXCLK_MASK
;
102 (*ctr
) |= FSL_AUDMIX_CTR_MIXCLK(prm
.clk
- 1);
111 static int fsl_audmix_put_mix_clk_src(struct snd_kcontrol
*kcontrol
,
112 struct snd_ctl_elem_value
*ucontrol
)
114 struct snd_soc_component
*comp
= snd_kcontrol_chip(kcontrol
);
115 struct fsl_audmix
*priv
= snd_soc_component_get_drvdata(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
;
120 /* Get current state */
121 reg_val
= snd_soc_component_read(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
[0]);
126 dev_dbg(comp
->dev
, "TDMs=x%08x, val=x%08x\n", priv
->tdms
, val
);
129 * Ensure the current selected mixer clock is available
130 * for configuration propagation
132 if (!(priv
->tdms
& BIT(mix_clk
))) {
134 "Started TDM%d needed for config propagation!\n",
139 if (!(priv
->tdms
& BIT(val
))) {
141 "The selected clock source has no TDM%d enabled!\n",
146 return snd_soc_put_enum_double(kcontrol
, ucontrol
);
149 static int fsl_audmix_put_out_src(struct snd_kcontrol
*kcontrol
,
150 struct snd_ctl_elem_value
*ucontrol
)
152 struct snd_soc_component
*comp
= snd_kcontrol_chip(kcontrol
);
153 struct fsl_audmix
*priv
= snd_soc_component_get_drvdata(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;
160 /* Get current state */
161 reg_val
= snd_soc_component_read(comp
, FSL_AUDMIX_CTR
);
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
);
170 val
= snd_soc_enum_item_to_val(e
, item
[0]);
172 dev_dbg(comp
->dev
, "TDMs=x%08x, val=x%08x\n", priv
->tdms
, val
);
174 /* Check if state is changing ... */
178 * Ensure the current selected mixer clock is available
179 * for configuration propagation
181 if (!(priv
->tdms
& BIT(mix_clk
))) {
183 "Started TDM%d needed for config propagation!\n",
188 /* Check state transition constraints */
189 ret
= fsl_audmix_state_trans(comp
, &mask
, &ctr
, prms
[out_src
][val
]);
193 /* Complete transition to new state */
194 mask
|= FSL_AUDMIX_CTR_OUTSRC_MASK
;
195 ctr
|= FSL_AUDMIX_CTR_OUTSRC(val
);
197 return snd_soc_component_update_bits(comp
, FSL_AUDMIX_CTR
, mask
, ctr
);
200 static const struct snd_kcontrol_new fsl_audmix_snd_controls
[] = {
201 /* FSL_AUDMIX_CTR controls */
202 { .iface
= SNDRV_CTL_ELEM_IFACE_MIXER
,
203 .name
= "Mixing Clock Source",
204 .info
= snd_soc_info_enum_double
,
205 .access
= SNDRV_CTL_ELEM_ACCESS_WRITE
,
206 .put
= fsl_audmix_put_mix_clk_src
,
207 .private_value
= (unsigned long)&fsl_audmix_enum
[0] },
208 { .iface
= SNDRV_CTL_ELEM_IFACE_MIXER
,
209 .name
= "Output Source",
210 .info
= snd_soc_info_enum_double
,
211 .access
= SNDRV_CTL_ELEM_ACCESS_WRITE
,
212 .put
= fsl_audmix_put_out_src
,
213 .private_value
= (unsigned long)&fsl_audmix_enum
[1] },
214 SOC_ENUM("Output Width", fsl_audmix_enum
[2]),
215 SOC_ENUM("Frame Rate Diff Error", fsl_audmix_enum
[3]),
216 SOC_ENUM("Clock Freq Diff Error", fsl_audmix_enum
[4]),
217 SOC_ENUM("Sync Mode Config", fsl_audmix_enum
[5]),
218 SOC_ENUM("Sync Mode Clk Source", fsl_audmix_enum
[6]),
219 /* TDM1 Attenuation controls */
220 SOC_ENUM("TDM1 Attenuation", fsl_audmix_enum
[7]),
221 SOC_ENUM("TDM1 Attenuation Direction", fsl_audmix_enum
[8]),
222 SOC_SINGLE("TDM1 Attenuation Step Divider", FSL_AUDMIX_ATCR0
,
224 SOC_SINGLE("TDM1 Attenuation Initial Value", FSL_AUDMIX_ATIVAL0
,
226 SOC_SINGLE("TDM1 Attenuation Step Up Factor", FSL_AUDMIX_ATSTPUP0
,
228 SOC_SINGLE("TDM1 Attenuation Step Down Factor", FSL_AUDMIX_ATSTPDN0
,
230 SOC_SINGLE("TDM1 Attenuation Step Target", FSL_AUDMIX_ATSTPTGT0
,
232 /* TDM2 Attenuation controls */
233 SOC_ENUM("TDM2 Attenuation", fsl_audmix_enum
[9]),
234 SOC_ENUM("TDM2 Attenuation Direction", fsl_audmix_enum
[10]),
235 SOC_SINGLE("TDM2 Attenuation Step Divider", FSL_AUDMIX_ATCR1
,
237 SOC_SINGLE("TDM2 Attenuation Initial Value", FSL_AUDMIX_ATIVAL1
,
239 SOC_SINGLE("TDM2 Attenuation Step Up Factor", FSL_AUDMIX_ATSTPUP1
,
241 SOC_SINGLE("TDM2 Attenuation Step Down Factor", FSL_AUDMIX_ATSTPDN1
,
243 SOC_SINGLE("TDM2 Attenuation Step Target", FSL_AUDMIX_ATSTPTGT1
,
247 static int fsl_audmix_dai_set_fmt(struct snd_soc_dai
*dai
, unsigned int fmt
)
249 struct snd_soc_component
*comp
= dai
->component
;
250 u32 mask
= 0, ctr
= 0;
252 /* AUDMIX is working in DSP_A format only */
253 switch (fmt
& SND_SOC_DAIFMT_FORMAT_MASK
) {
254 case SND_SOC_DAIFMT_DSP_A
:
260 /* For playback the AUDMIX is slave, and for record is master */
261 switch (fmt
& SND_SOC_DAIFMT_MASTER_MASK
) {
262 case SND_SOC_DAIFMT_CBM_CFM
:
263 case SND_SOC_DAIFMT_CBS_CFS
:
269 switch (fmt
& SND_SOC_DAIFMT_INV_MASK
) {
270 case SND_SOC_DAIFMT_IB_NF
:
271 /* Output data will be written on positive edge of the clock */
272 ctr
|= FSL_AUDMIX_CTR_OUTCKPOL(0);
274 case SND_SOC_DAIFMT_NB_NF
:
275 /* Output data will be written on negative edge of the clock */
276 ctr
|= FSL_AUDMIX_CTR_OUTCKPOL(1);
282 mask
|= FSL_AUDMIX_CTR_OUTCKPOL_MASK
;
284 return snd_soc_component_update_bits(comp
, FSL_AUDMIX_CTR
, mask
, ctr
);
287 static int fsl_audmix_dai_trigger(struct snd_pcm_substream
*substream
, int cmd
,
288 struct snd_soc_dai
*dai
)
290 struct fsl_audmix
*priv
= snd_soc_dai_get_drvdata(dai
);
291 unsigned long lock_flags
;
293 /* Capture stream shall not be handled */
294 if (substream
->stream
== SNDRV_PCM_STREAM_CAPTURE
)
298 case SNDRV_PCM_TRIGGER_START
:
299 case SNDRV_PCM_TRIGGER_RESUME
:
300 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE
:
301 spin_lock_irqsave(&priv
->lock
, lock_flags
);
302 priv
->tdms
|= BIT(dai
->driver
->id
);
303 spin_unlock_irqrestore(&priv
->lock
, lock_flags
);
305 case SNDRV_PCM_TRIGGER_STOP
:
306 case SNDRV_PCM_TRIGGER_SUSPEND
:
307 case SNDRV_PCM_TRIGGER_PAUSE_PUSH
:
308 spin_lock_irqsave(&priv
->lock
, lock_flags
);
309 priv
->tdms
&= ~BIT(dai
->driver
->id
);
310 spin_unlock_irqrestore(&priv
->lock
, lock_flags
);
319 static const struct snd_soc_dai_ops fsl_audmix_dai_ops
= {
320 .set_fmt
= fsl_audmix_dai_set_fmt
,
321 .trigger
= fsl_audmix_dai_trigger
,
324 static struct snd_soc_dai_driver fsl_audmix_dai
[] = {
329 .stream_name
= "AUDMIX-Playback-0",
334 .rates
= SNDRV_PCM_RATE_8000_96000
,
335 .formats
= FSL_AUDMIX_FORMATS
,
338 .stream_name
= "AUDMIX-Capture-0",
343 .rates
= SNDRV_PCM_RATE_8000_96000
,
344 .formats
= FSL_AUDMIX_FORMATS
,
346 .ops
= &fsl_audmix_dai_ops
,
352 .stream_name
= "AUDMIX-Playback-1",
357 .rates
= SNDRV_PCM_RATE_8000_96000
,
358 .formats
= FSL_AUDMIX_FORMATS
,
361 .stream_name
= "AUDMIX-Capture-1",
366 .rates
= SNDRV_PCM_RATE_8000_96000
,
367 .formats
= FSL_AUDMIX_FORMATS
,
369 .ops
= &fsl_audmix_dai_ops
,
373 static const struct snd_soc_component_driver fsl_audmix_component
= {
374 .name
= "fsl-audmix-dai",
375 .controls
= fsl_audmix_snd_controls
,
376 .num_controls
= ARRAY_SIZE(fsl_audmix_snd_controls
),
379 static bool fsl_audmix_readable_reg(struct device
*dev
, unsigned int reg
)
384 case FSL_AUDMIX_ATCR0
:
385 case FSL_AUDMIX_ATIVAL0
:
386 case FSL_AUDMIX_ATSTPUP0
:
387 case FSL_AUDMIX_ATSTPDN0
:
388 case FSL_AUDMIX_ATSTPTGT0
:
389 case FSL_AUDMIX_ATTNVAL0
:
390 case FSL_AUDMIX_ATSTP0
:
391 case FSL_AUDMIX_ATCR1
:
392 case FSL_AUDMIX_ATIVAL1
:
393 case FSL_AUDMIX_ATSTPUP1
:
394 case FSL_AUDMIX_ATSTPDN1
:
395 case FSL_AUDMIX_ATSTPTGT1
:
396 case FSL_AUDMIX_ATTNVAL1
:
397 case FSL_AUDMIX_ATSTP1
:
404 static bool fsl_audmix_writeable_reg(struct device
*dev
, unsigned int reg
)
408 case FSL_AUDMIX_ATCR0
:
409 case FSL_AUDMIX_ATIVAL0
:
410 case FSL_AUDMIX_ATSTPUP0
:
411 case FSL_AUDMIX_ATSTPDN0
:
412 case FSL_AUDMIX_ATSTPTGT0
:
413 case FSL_AUDMIX_ATCR1
:
414 case FSL_AUDMIX_ATIVAL1
:
415 case FSL_AUDMIX_ATSTPUP1
:
416 case FSL_AUDMIX_ATSTPDN1
:
417 case FSL_AUDMIX_ATSTPTGT1
:
424 static const struct reg_default fsl_audmix_reg
[] = {
425 { FSL_AUDMIX_CTR
, 0x00060 },
426 { FSL_AUDMIX_STR
, 0x00003 },
427 { FSL_AUDMIX_ATCR0
, 0x00000 },
428 { FSL_AUDMIX_ATIVAL0
, 0x3FFFF },
429 { FSL_AUDMIX_ATSTPUP0
, 0x2AAAA },
430 { FSL_AUDMIX_ATSTPDN0
, 0x30000 },
431 { FSL_AUDMIX_ATSTPTGT0
, 0x00010 },
432 { FSL_AUDMIX_ATTNVAL0
, 0x00000 },
433 { FSL_AUDMIX_ATSTP0
, 0x00000 },
434 { FSL_AUDMIX_ATCR1
, 0x00000 },
435 { FSL_AUDMIX_ATIVAL1
, 0x3FFFF },
436 { FSL_AUDMIX_ATSTPUP1
, 0x2AAAA },
437 { FSL_AUDMIX_ATSTPDN1
, 0x30000 },
438 { FSL_AUDMIX_ATSTPTGT1
, 0x00010 },
439 { FSL_AUDMIX_ATTNVAL1
, 0x00000 },
440 { FSL_AUDMIX_ATSTP1
, 0x00000 },
443 static const struct regmap_config fsl_audmix_regmap_config
= {
447 .max_register
= FSL_AUDMIX_ATSTP1
,
448 .reg_defaults
= fsl_audmix_reg
,
449 .num_reg_defaults
= ARRAY_SIZE(fsl_audmix_reg
),
450 .readable_reg
= fsl_audmix_readable_reg
,
451 .writeable_reg
= fsl_audmix_writeable_reg
,
452 .cache_type
= REGCACHE_FLAT
,
455 static const struct of_device_id fsl_audmix_ids
[] = {
457 .compatible
= "fsl,imx8qm-audmix",
461 MODULE_DEVICE_TABLE(of
, fsl_audmix_ids
);
463 static int fsl_audmix_probe(struct platform_device
*pdev
)
465 struct device
*dev
= &pdev
->dev
;
466 struct fsl_audmix
*priv
;
470 priv
= devm_kzalloc(dev
, sizeof(*priv
), GFP_KERNEL
);
474 /* Get the addresses */
475 regs
= devm_platform_ioremap_resource(pdev
, 0);
477 return PTR_ERR(regs
);
479 priv
->regmap
= devm_regmap_init_mmio_clk(dev
, "ipg", regs
,
480 &fsl_audmix_regmap_config
);
481 if (IS_ERR(priv
->regmap
)) {
482 dev_err(dev
, "failed to init regmap\n");
483 return PTR_ERR(priv
->regmap
);
486 priv
->ipg_clk
= devm_clk_get(dev
, "ipg");
487 if (IS_ERR(priv
->ipg_clk
)) {
488 dev_err(dev
, "failed to get ipg clock\n");
489 return PTR_ERR(priv
->ipg_clk
);
492 spin_lock_init(&priv
->lock
);
493 platform_set_drvdata(pdev
, priv
);
494 pm_runtime_enable(dev
);
496 ret
= devm_snd_soc_register_component(dev
, &fsl_audmix_component
,
498 ARRAY_SIZE(fsl_audmix_dai
));
500 dev_err(dev
, "failed to register ASoC DAI\n");
504 priv
->pdev
= platform_device_register_data(dev
, "imx-audmix", 0, NULL
, 0);
505 if (IS_ERR(priv
->pdev
)) {
506 ret
= PTR_ERR(priv
->pdev
);
507 dev_err(dev
, "failed to register platform: %d\n", ret
);
514 pm_runtime_disable(dev
);
518 static int fsl_audmix_remove(struct platform_device
*pdev
)
520 struct fsl_audmix
*priv
= dev_get_drvdata(&pdev
->dev
);
522 pm_runtime_disable(&pdev
->dev
);
525 platform_device_unregister(priv
->pdev
);
531 static int fsl_audmix_runtime_resume(struct device
*dev
)
533 struct fsl_audmix
*priv
= dev_get_drvdata(dev
);
536 ret
= clk_prepare_enable(priv
->ipg_clk
);
538 dev_err(dev
, "Failed to enable IPG clock: %d\n", ret
);
542 regcache_cache_only(priv
->regmap
, false);
543 regcache_mark_dirty(priv
->regmap
);
545 return regcache_sync(priv
->regmap
);
548 static int fsl_audmix_runtime_suspend(struct device
*dev
)
550 struct fsl_audmix
*priv
= dev_get_drvdata(dev
);
552 regcache_cache_only(priv
->regmap
, true);
554 clk_disable_unprepare(priv
->ipg_clk
);
558 #endif /* CONFIG_PM */
560 static const struct dev_pm_ops fsl_audmix_pm
= {
561 SET_RUNTIME_PM_OPS(fsl_audmix_runtime_suspend
,
562 fsl_audmix_runtime_resume
,
564 SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend
,
565 pm_runtime_force_resume
)
568 static struct platform_driver fsl_audmix_driver
= {
569 .probe
= fsl_audmix_probe
,
570 .remove
= fsl_audmix_remove
,
572 .name
= "fsl-audmix",
573 .of_match_table
= fsl_audmix_ids
,
574 .pm
= &fsl_audmix_pm
,
577 module_platform_driver(fsl_audmix_driver
);
579 MODULE_DESCRIPTION("NXP AUDMIX ASoC DAI driver");
580 MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>");
581 MODULE_ALIAS("platform:fsl-audmix");
582 MODULE_LICENSE("GPL v2");