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 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
,
216 SOC_SINGLE("TDM1 Attenuation Initial Value", FSL_AUDMIX_ATIVAL0
,
218 SOC_SINGLE("TDM1 Attenuation Step Up Factor", FSL_AUDMIX_ATSTPUP0
,
220 SOC_SINGLE("TDM1 Attenuation Step Down Factor", FSL_AUDMIX_ATSTPDN0
,
222 SOC_SINGLE("TDM1 Attenuation Step Target", FSL_AUDMIX_ATSTPTGT0
,
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
,
229 SOC_SINGLE("TDM2 Attenuation Initial Value", FSL_AUDMIX_ATIVAL1
,
231 SOC_SINGLE("TDM2 Attenuation Step Up Factor", FSL_AUDMIX_ATSTPUP1
,
233 SOC_SINGLE("TDM2 Attenuation Step Down Factor", FSL_AUDMIX_ATSTPDN1
,
235 SOC_SINGLE("TDM2 Attenuation Step Target", FSL_AUDMIX_ATSTPTGT1
,
239 static int fsl_audmix_dai_set_fmt(struct snd_soc_dai
*dai
, unsigned int fmt
)
241 struct snd_soc_component
*comp
= dai
->component
;
242 u32 mask
= 0, ctr
= 0;
244 /* AUDMIX is working in DSP_A format only */
245 switch (fmt
& SND_SOC_DAIFMT_FORMAT_MASK
) {
246 case SND_SOC_DAIFMT_DSP_A
:
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
:
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);
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);
274 mask
|= FSL_AUDMIX_CTR_OUTCKPOL_MASK
;
276 return snd_soc_component_update_bits(comp
, FSL_AUDMIX_CTR
, mask
, ctr
);
279 static int fsl_audmix_dai_trigger(struct snd_pcm_substream
*substream
, int cmd
,
280 struct snd_soc_dai
*dai
)
282 struct fsl_audmix
*priv
= snd_soc_dai_get_drvdata(dai
);
283 unsigned long lock_flags
;
285 /* Capture stream shall not be handled */
286 if (substream
->stream
== SNDRV_PCM_STREAM_CAPTURE
)
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(&priv
->lock
, lock_flags
);
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(&priv
->lock
, lock_flags
);
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
,
316 static struct snd_soc_dai_driver fsl_audmix_dai
[] = {
321 .stream_name
= "AUDMIX-Playback-0",
326 .rates
= SNDRV_PCM_RATE_8000_96000
,
327 .formats
= FSL_AUDMIX_FORMATS
,
329 .ops
= &fsl_audmix_dai_ops
,
335 .stream_name
= "AUDMIX-Playback-1",
340 .rates
= SNDRV_PCM_RATE_8000_96000
,
341 .formats
= FSL_AUDMIX_FORMATS
,
343 .ops
= &fsl_audmix_dai_ops
,
349 .stream_name
= "AUDMIX-Capture-0",
354 .rates
= SNDRV_PCM_RATE_8000_96000
,
355 .formats
= FSL_AUDMIX_FORMATS
,
357 .ops
= &fsl_audmix_dai_ops
,
361 static const struct snd_soc_component_driver fsl_audmix_component
= {
362 .name
= "fsl-audmix-dai",
363 .controls
= fsl_audmix_snd_controls
,
364 .num_controls
= ARRAY_SIZE(fsl_audmix_snd_controls
),
367 static bool fsl_audmix_readable_reg(struct device
*dev
, unsigned int reg
)
372 case FSL_AUDMIX_ATCR0
:
373 case FSL_AUDMIX_ATIVAL0
:
374 case FSL_AUDMIX_ATSTPUP0
:
375 case FSL_AUDMIX_ATSTPDN0
:
376 case FSL_AUDMIX_ATSTPTGT0
:
377 case FSL_AUDMIX_ATTNVAL0
:
378 case FSL_AUDMIX_ATSTP0
:
379 case FSL_AUDMIX_ATCR1
:
380 case FSL_AUDMIX_ATIVAL1
:
381 case FSL_AUDMIX_ATSTPUP1
:
382 case FSL_AUDMIX_ATSTPDN1
:
383 case FSL_AUDMIX_ATSTPTGT1
:
384 case FSL_AUDMIX_ATTNVAL1
:
385 case FSL_AUDMIX_ATSTP1
:
392 static bool fsl_audmix_writeable_reg(struct device
*dev
, unsigned int reg
)
396 case FSL_AUDMIX_ATCR0
:
397 case FSL_AUDMIX_ATIVAL0
:
398 case FSL_AUDMIX_ATSTPUP0
:
399 case FSL_AUDMIX_ATSTPDN0
:
400 case FSL_AUDMIX_ATSTPTGT0
:
401 case FSL_AUDMIX_ATCR1
:
402 case FSL_AUDMIX_ATIVAL1
:
403 case FSL_AUDMIX_ATSTPUP1
:
404 case FSL_AUDMIX_ATSTPDN1
:
405 case FSL_AUDMIX_ATSTPTGT1
:
412 static const struct reg_default fsl_audmix_reg
[] = {
413 { FSL_AUDMIX_CTR
, 0x00060 },
414 { FSL_AUDMIX_STR
, 0x00003 },
415 { FSL_AUDMIX_ATCR0
, 0x00000 },
416 { FSL_AUDMIX_ATIVAL0
, 0x3FFFF },
417 { FSL_AUDMIX_ATSTPUP0
, 0x2AAAA },
418 { FSL_AUDMIX_ATSTPDN0
, 0x30000 },
419 { FSL_AUDMIX_ATSTPTGT0
, 0x00010 },
420 { FSL_AUDMIX_ATTNVAL0
, 0x00000 },
421 { FSL_AUDMIX_ATSTP0
, 0x00000 },
422 { FSL_AUDMIX_ATCR1
, 0x00000 },
423 { FSL_AUDMIX_ATIVAL1
, 0x3FFFF },
424 { FSL_AUDMIX_ATSTPUP1
, 0x2AAAA },
425 { FSL_AUDMIX_ATSTPDN1
, 0x30000 },
426 { FSL_AUDMIX_ATSTPTGT1
, 0x00010 },
427 { FSL_AUDMIX_ATTNVAL1
, 0x00000 },
428 { FSL_AUDMIX_ATSTP1
, 0x00000 },
431 static const struct regmap_config fsl_audmix_regmap_config
= {
435 .max_register
= FSL_AUDMIX_ATSTP1
,
436 .reg_defaults
= fsl_audmix_reg
,
437 .num_reg_defaults
= ARRAY_SIZE(fsl_audmix_reg
),
438 .readable_reg
= fsl_audmix_readable_reg
,
439 .writeable_reg
= fsl_audmix_writeable_reg
,
440 .cache_type
= REGCACHE_FLAT
,
443 static const struct of_device_id fsl_audmix_ids
[] = {
445 .compatible
= "fsl,imx8qm-audmix",
449 MODULE_DEVICE_TABLE(of
, fsl_audmix_ids
);
451 static int fsl_audmix_probe(struct platform_device
*pdev
)
453 struct device
*dev
= &pdev
->dev
;
454 struct fsl_audmix
*priv
;
458 priv
= devm_kzalloc(dev
, sizeof(*priv
), GFP_KERNEL
);
462 /* Get the addresses */
463 regs
= devm_platform_ioremap_resource(pdev
, 0);
465 return PTR_ERR(regs
);
467 priv
->regmap
= devm_regmap_init_mmio(dev
, regs
, &fsl_audmix_regmap_config
);
468 if (IS_ERR(priv
->regmap
)) {
469 dev_err(dev
, "failed to init regmap\n");
470 return PTR_ERR(priv
->regmap
);
473 priv
->ipg_clk
= devm_clk_get(dev
, "ipg");
474 if (IS_ERR(priv
->ipg_clk
)) {
475 dev_err(dev
, "failed to get ipg clock\n");
476 return PTR_ERR(priv
->ipg_clk
);
479 spin_lock_init(&priv
->lock
);
480 platform_set_drvdata(pdev
, priv
);
481 pm_runtime_enable(dev
);
483 ret
= devm_snd_soc_register_component(dev
, &fsl_audmix_component
,
485 ARRAY_SIZE(fsl_audmix_dai
));
487 dev_err(dev
, "failed to register ASoC DAI\n");
491 priv
->pdev
= platform_device_register_data(dev
, "imx-audmix", 0, NULL
, 0);
492 if (IS_ERR(priv
->pdev
)) {
493 ret
= PTR_ERR(priv
->pdev
);
494 dev_err(dev
, "failed to register platform: %d\n", ret
);
501 pm_runtime_disable(dev
);
505 static void fsl_audmix_remove(struct platform_device
*pdev
)
507 struct fsl_audmix
*priv
= dev_get_drvdata(&pdev
->dev
);
509 pm_runtime_disable(&pdev
->dev
);
512 platform_device_unregister(priv
->pdev
);
515 static int fsl_audmix_runtime_resume(struct device
*dev
)
517 struct fsl_audmix
*priv
= dev_get_drvdata(dev
);
520 ret
= clk_prepare_enable(priv
->ipg_clk
);
522 dev_err(dev
, "Failed to enable IPG clock: %d\n", ret
);
526 regcache_cache_only(priv
->regmap
, false);
527 regcache_mark_dirty(priv
->regmap
);
529 return regcache_sync(priv
->regmap
);
532 static int fsl_audmix_runtime_suspend(struct device
*dev
)
534 struct fsl_audmix
*priv
= dev_get_drvdata(dev
);
536 regcache_cache_only(priv
->regmap
, true);
538 clk_disable_unprepare(priv
->ipg_clk
);
543 static const struct dev_pm_ops fsl_audmix_pm
= {
544 RUNTIME_PM_OPS(fsl_audmix_runtime_suspend
, fsl_audmix_runtime_resume
,
546 SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend
, pm_runtime_force_resume
)
549 static struct platform_driver fsl_audmix_driver
= {
550 .probe
= fsl_audmix_probe
,
551 .remove
= fsl_audmix_remove
,
553 .name
= "fsl-audmix",
554 .of_match_table
= fsl_audmix_ids
,
555 .pm
= pm_ptr(&fsl_audmix_pm
),
558 module_platform_driver(fsl_audmix_driver
);
560 MODULE_DESCRIPTION("NXP AUDMIX ASoC DAI driver");
561 MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>");
562 MODULE_ALIAS("platform:fsl-audmix");
563 MODULE_LICENSE("GPL v2");