Alsa-devel
Threads by month
- ----- 2025 -----
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
January 2020
- 133 participants
- 385 discussions

09 Jan '20
In current driver, locks can be taken as follows:
- Register access: take a lock on regmap config and then on clock.
- Master clock provider: take a lock on clock and then on regmap config.
This can lead to the circular locking summarized below.
Remove peripheral clock management through regmap framework, and manage
peripheral clock in driver instead. On register access, lock on clock
is taken first, which allows to avoid possible locking issue.
[ 6696.561513] ======================================================
[ 6696.567670] WARNING: possible circular locking dependency detected
[ 6696.573842] 4.19.49 #866 Not tainted
[ 6696.577397] ------------------------------------------------------
[ 6696.583566] pulseaudio/6439 is trying to acquire lock:
[ 6696.588697] 87b0a25b (enable_lock){..-.}, at: clk_enable_lock+0x64/0x128
[ 6696.595377]
[ 6696.595377] but task is already holding lock:
[ 6696.601197] d858f825 (stm32_sai_sub:1342:(sai->regmap_config)->lock){....}
...
[ 6696.812513] Possible unsafe locking scenario:
[ 6696.812513]
[ 6696.818418] CPU0 CPU1
[ 6696.822935] ---- ----
[ 6696.827451] lock(stm32_sai_sub:1342:(sai->regmap_config)->lock);
[ 6696.833618] lock(enable_lock);
[ 6696.839350] lock(stm32_sai_sub:1342:
(sai->regmap_config)->lock);
[ 6696.848035] lock(enable_lock);
Fixes: 03e78a242a15 ("ASoC: stm32: sai: add h7 support")
Signed-off-by: Olivier Moysan <olivier.moysan(a)st.com>
---
sound/soc/stm/stm32_sai_sub.c | 194 ++++++++++++++++++++++++----------
1 file changed, 140 insertions(+), 54 deletions(-)
diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c
index 48e629ac2d88..30bcd5d3a32a 100644
--- a/sound/soc/stm/stm32_sai_sub.c
+++ b/sound/soc/stm/stm32_sai_sub.c
@@ -184,6 +184,56 @@ static bool stm32_sai_sub_writeable_reg(struct device *dev, unsigned int reg)
}
}
+static int stm32_sai_sub_reg_up(struct stm32_sai_sub_data *sai,
+ unsigned int reg, unsigned int mask,
+ unsigned int val)
+{
+ int ret;
+
+ ret = clk_enable(sai->pdata->pclk);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(sai->regmap, reg, mask, val);
+
+ clk_disable(sai->pdata->pclk);
+
+ return ret;
+}
+
+static int stm32_sai_sub_reg_wr(struct stm32_sai_sub_data *sai,
+ unsigned int reg, unsigned int mask,
+ unsigned int val)
+{
+ int ret;
+
+ ret = clk_enable(sai->pdata->pclk);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write_bits(sai->regmap, reg, mask, val);
+
+ clk_disable(sai->pdata->pclk);
+
+ return ret;
+}
+
+static int stm32_sai_sub_reg_rd(struct stm32_sai_sub_data *sai,
+ unsigned int reg, unsigned int *val)
+{
+ int ret;
+
+ ret = clk_enable(sai->pdata->pclk);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(sai->regmap, reg, val);
+
+ clk_disable(sai->pdata->pclk);
+
+ return ret;
+}
+
static const struct regmap_config stm32_sai_sub_regmap_config_f4 = {
.reg_bits = 32,
.reg_stride = 4,
@@ -295,7 +345,7 @@ static int stm32_sai_set_clk_div(struct stm32_sai_sub_data *sai,
mask = SAI_XCR1_MCKDIV_MASK(SAI_XCR1_MCKDIV_WIDTH(version));
cr1 = SAI_XCR1_MCKDIV_SET(div);
- ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, mask, cr1);
+ ret = stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX, mask, cr1);
if (ret < 0)
dev_err(&sai->pdev->dev, "Failed to update CR1 register\n");
@@ -372,8 +422,8 @@ static int stm32_sai_mclk_enable(struct clk_hw *hw)
dev_dbg(&sai->pdev->dev, "Enable master clock\n");
- return regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
- SAI_XCR1_MCKEN, SAI_XCR1_MCKEN);
+ return stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX,
+ SAI_XCR1_MCKEN, SAI_XCR1_MCKEN);
}
static void stm32_sai_mclk_disable(struct clk_hw *hw)
@@ -383,7 +433,7 @@ static void stm32_sai_mclk_disable(struct clk_hw *hw)
dev_dbg(&sai->pdev->dev, "Disable master clock\n");
- regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, SAI_XCR1_MCKEN, 0);
+ stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX, SAI_XCR1_MCKEN, 0);
}
static const struct clk_ops mclk_ops = {
@@ -446,15 +496,15 @@ static irqreturn_t stm32_sai_isr(int irq, void *devid)
unsigned int sr, imr, flags;
snd_pcm_state_t status = SNDRV_PCM_STATE_RUNNING;
- regmap_read(sai->regmap, STM_SAI_IMR_REGX, &imr);
- regmap_read(sai->regmap, STM_SAI_SR_REGX, &sr);
+ stm32_sai_sub_reg_rd(sai, STM_SAI_IMR_REGX, &imr);
+ stm32_sai_sub_reg_rd(sai, STM_SAI_SR_REGX, &sr);
flags = sr & imr;
if (!flags)
return IRQ_NONE;
- regmap_write_bits(sai->regmap, STM_SAI_CLRFR_REGX, SAI_XCLRFR_MASK,
- SAI_XCLRFR_MASK);
+ stm32_sai_sub_reg_wr(sai, STM_SAI_CLRFR_REGX, SAI_XCLRFR_MASK,
+ SAI_XCLRFR_MASK);
if (!sai->substream) {
dev_err(&pdev->dev, "Device stopped. Spurious IRQ 0x%x\n", sr);
@@ -503,8 +553,8 @@ static int stm32_sai_set_sysclk(struct snd_soc_dai *cpu_dai,
int ret;
if (dir == SND_SOC_CLOCK_OUT && sai->sai_mclk) {
- ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
- SAI_XCR1_NODIV,
+ ret = stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX,
+ SAI_XCR1_NODIV,
freq ? 0 : SAI_XCR1_NODIV);
if (ret < 0)
return ret;
@@ -583,7 +633,7 @@ static int stm32_sai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask,
slotr_mask |= SAI_XSLOTR_SLOTEN_MASK;
- regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX, slotr_mask, slotr);
+ stm32_sai_sub_reg_up(sai, STM_SAI_SLOTR_REGX, slotr_mask, slotr);
sai->slot_width = slot_width;
sai->slots = slots;
@@ -665,7 +715,7 @@ static int stm32_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
cr1_mask |= SAI_XCR1_CKSTR;
frcr_mask |= SAI_XFRCR_FSPOL;
- regmap_update_bits(sai->regmap, STM_SAI_FRCR_REGX, frcr_mask, frcr);
+ stm32_sai_sub_reg_up(sai, STM_SAI_FRCR_REGX, frcr_mask, frcr);
/* DAI clock master masks */
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
@@ -693,7 +743,7 @@ static int stm32_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
cr1_mask |= SAI_XCR1_SLAVE;
conf_update:
- ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1);
+ ret = stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX, cr1_mask, cr1);
if (ret < 0) {
dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
return ret;
@@ -730,12 +780,12 @@ static int stm32_sai_startup(struct snd_pcm_substream *substream,
}
/* Enable ITs */
- regmap_write_bits(sai->regmap, STM_SAI_CLRFR_REGX,
- SAI_XCLRFR_MASK, SAI_XCLRFR_MASK);
+ stm32_sai_sub_reg_wr(sai, STM_SAI_CLRFR_REGX,
+ SAI_XCLRFR_MASK, SAI_XCLRFR_MASK);
imr = SAI_XIMR_OVRUDRIE;
if (STM_SAI_IS_CAPTURE(sai)) {
- regmap_read(sai->regmap, STM_SAI_CR2_REGX, &cr2);
+ stm32_sai_sub_reg_rd(sai, STM_SAI_CR2_REGX, &cr2);
if (cr2 & SAI_XCR2_MUTECNT_MASK)
imr |= SAI_XIMR_MUTEDETIE;
}
@@ -745,8 +795,8 @@ static int stm32_sai_startup(struct snd_pcm_substream *substream,
else
imr |= SAI_XIMR_AFSDETIE | SAI_XIMR_LFSDETIE;
- regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX,
- SAI_XIMR_MASK, imr);
+ stm32_sai_sub_reg_up(sai, STM_SAI_IMR_REGX,
+ SAI_XIMR_MASK, imr);
return 0;
}
@@ -763,10 +813,10 @@ static int stm32_sai_set_config(struct snd_soc_dai *cpu_dai,
* SAI fifo threshold is set to half fifo, to keep enough space
* for DMA incoming bursts.
*/
- regmap_write_bits(sai->regmap, STM_SAI_CR2_REGX,
- SAI_XCR2_FFLUSH | SAI_XCR2_FTH_MASK,
- SAI_XCR2_FFLUSH |
- SAI_XCR2_FTH_SET(STM_SAI_FIFO_TH_HALF));
+ stm32_sai_sub_reg_wr(sai, STM_SAI_CR2_REGX,
+ SAI_XCR2_FFLUSH | SAI_XCR2_FTH_MASK,
+ SAI_XCR2_FFLUSH |
+ SAI_XCR2_FTH_SET(STM_SAI_FIFO_TH_HALF));
/* DS bits in CR1 not set for SPDIF (size forced to 24 bits).*/
if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) {
@@ -795,7 +845,7 @@ static int stm32_sai_set_config(struct snd_soc_dai *cpu_dai,
if ((sai->slots == 2) && (params_channels(params) == 1))
cr1 |= SAI_XCR1_MONO;
- ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1);
+ ret = stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX, cr1_mask, cr1);
if (ret < 0) {
dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
return ret;
@@ -809,7 +859,7 @@ static int stm32_sai_set_slots(struct snd_soc_dai *cpu_dai)
struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
int slotr, slot_sz;
- regmap_read(sai->regmap, STM_SAI_SLOTR_REGX, &slotr);
+ stm32_sai_sub_reg_rd(sai, STM_SAI_SLOTR_REGX, &slotr);
/*
* If SLOTSZ is set to auto in SLOTR, align slot width on data size
@@ -831,16 +881,16 @@ static int stm32_sai_set_slots(struct snd_soc_dai *cpu_dai)
sai->slots = 2;
/* The number of slots in the audio frame is equal to NBSLOT[3:0] + 1*/
- regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX,
- SAI_XSLOTR_NBSLOT_MASK,
- SAI_XSLOTR_NBSLOT_SET((sai->slots - 1)));
+ stm32_sai_sub_reg_up(sai, STM_SAI_SLOTR_REGX,
+ SAI_XSLOTR_NBSLOT_MASK,
+ SAI_XSLOTR_NBSLOT_SET((sai->slots - 1)));
/* Set default slots mask if not already set from DT */
if (!(slotr & SAI_XSLOTR_SLOTEN_MASK)) {
sai->slot_mask = (1 << sai->slots) - 1;
- regmap_update_bits(sai->regmap,
- STM_SAI_SLOTR_REGX, SAI_XSLOTR_SLOTEN_MASK,
- SAI_XSLOTR_SLOTEN_SET(sai->slot_mask));
+ stm32_sai_sub_reg_up(sai,
+ STM_SAI_SLOTR_REGX, SAI_XSLOTR_SLOTEN_MASK,
+ SAI_XSLOTR_SLOTEN_SET(sai->slot_mask));
}
dev_dbg(cpu_dai->dev, "Slots %d, slot width %d\n",
@@ -870,14 +920,14 @@ static void stm32_sai_set_frame(struct snd_soc_dai *cpu_dai)
dev_dbg(cpu_dai->dev, "Frame length %d, frame active %d\n",
sai->fs_length, fs_active);
- regmap_update_bits(sai->regmap, STM_SAI_FRCR_REGX, frcr_mask, frcr);
+ stm32_sai_sub_reg_up(sai, STM_SAI_FRCR_REGX, frcr_mask, frcr);
if ((sai->fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_LSB) {
offset = sai->slot_width - sai->data_size;
- regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX,
- SAI_XSLOTR_FBOFF_MASK,
- SAI_XSLOTR_FBOFF_SET(offset));
+ stm32_sai_sub_reg_up(sai, STM_SAI_SLOTR_REGX,
+ SAI_XSLOTR_FBOFF_MASK,
+ SAI_XSLOTR_FBOFF_SET(offset));
}
}
@@ -994,9 +1044,9 @@ static int stm32_sai_configure_clock(struct snd_soc_dai *cpu_dai,
return -EINVAL;
}
- regmap_update_bits(sai->regmap,
- STM_SAI_CR1_REGX,
- SAI_XCR1_OSR, cr1);
+ stm32_sai_sub_reg_up(sai,
+ STM_SAI_CR1_REGX,
+ SAI_XCR1_OSR, cr1);
div = stm32_sai_get_clk_div(sai, sai_clk_rate,
sai->mclk_rate);
@@ -1058,12 +1108,12 @@ static int stm32_sai_trigger(struct snd_pcm_substream *substream, int cmd,
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
dev_dbg(cpu_dai->dev, "Enable DMA and SAI\n");
- regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
- SAI_XCR1_DMAEN, SAI_XCR1_DMAEN);
+ stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX,
+ SAI_XCR1_DMAEN, SAI_XCR1_DMAEN);
/* Enable SAI */
- ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
- SAI_XCR1_SAIEN, SAI_XCR1_SAIEN);
+ ret = stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX,
+ SAI_XCR1_SAIEN, SAI_XCR1_SAIEN);
if (ret < 0)
dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
break;
@@ -1072,16 +1122,16 @@ static int stm32_sai_trigger(struct snd_pcm_substream *substream, int cmd,
case SNDRV_PCM_TRIGGER_STOP:
dev_dbg(cpu_dai->dev, "Disable DMA and SAI\n");
- regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX,
- SAI_XIMR_MASK, 0);
+ stm32_sai_sub_reg_up(sai, STM_SAI_IMR_REGX,
+ SAI_XIMR_MASK, 0);
- regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
- SAI_XCR1_SAIEN,
- (unsigned int)~SAI_XCR1_SAIEN);
+ stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX,
+ SAI_XCR1_SAIEN,
+ (unsigned int)~SAI_XCR1_SAIEN);
- ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
- SAI_XCR1_DMAEN,
- (unsigned int)~SAI_XCR1_DMAEN);
+ ret = stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX,
+ SAI_XCR1_DMAEN,
+ (unsigned int)~SAI_XCR1_DMAEN);
if (ret < 0)
dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
@@ -1101,7 +1151,7 @@ static void stm32_sai_shutdown(struct snd_pcm_substream *substream,
struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
unsigned long flags;
- regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX, SAI_XIMR_MASK, 0);
+ stm32_sai_sub_reg_up(sai, STM_SAI_IMR_REGX, SAI_XIMR_MASK, 0);
clk_disable_unprepare(sai->sai_ck);
@@ -1169,7 +1219,7 @@ static int stm32_sai_dai_probe(struct snd_soc_dai *cpu_dai)
cr1_mask |= SAI_XCR1_SYNCEN_MASK;
cr1 |= SAI_XCR1_SYNCEN_SET(sai->sync);
- return regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1);
+ return stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX, cr1_mask, cr1);
}
static const struct snd_soc_dai_ops stm32_sai_pcm_dai_ops = {
@@ -1322,8 +1372,13 @@ static int stm32_sai_sub_parse_of(struct platform_device *pdev,
if (STM_SAI_HAS_PDM(sai) && STM_SAI_IS_SUB_A(sai))
sai->regmap_config = &stm32_sai_sub_regmap_config_h7;
- sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "sai_ck",
- base, sai->regmap_config);
+ /*
+ * Do not manage peripheral clock through regmap framework as this
+ * can lead to circular locking issue with sai master clock provider.
+ * Manage peripheral clock directly in driver instead.
+ */
+ sai->regmap = devm_regmap_init_mmio(&pdev->dev, base,
+ sai->regmap_config);
if (IS_ERR(sai->regmap)) {
dev_err(&pdev->dev, "Failed to initialize MMIO\n");
return PTR_ERR(sai->regmap);
@@ -1420,6 +1475,10 @@ static int stm32_sai_sub_parse_of(struct platform_device *pdev,
return PTR_ERR(sai->sai_ck);
}
+ ret = clk_prepare(sai->pdata->pclk);
+ if (ret < 0)
+ return ret;
+
if (STM_SAI_IS_F4(sai->pdata))
return 0;
@@ -1501,22 +1560,48 @@ static int stm32_sai_sub_probe(struct platform_device *pdev)
return 0;
}
+static int stm32_sai_sub_remove(struct platform_device *pdev)
+{
+ struct stm32_sai_sub_data *sai = dev_get_drvdata(&pdev->dev);
+
+ clk_unprepare(sai->pdata->pclk);
+
+ return 0;
+}
+
#ifdef CONFIG_PM_SLEEP
static int stm32_sai_sub_suspend(struct device *dev)
{
struct stm32_sai_sub_data *sai = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_enable(sai->pdata->pclk);
+ if (ret < 0)
+ return ret;
regcache_cache_only(sai->regmap, true);
regcache_mark_dirty(sai->regmap);
+
+ clk_disable(sai->pdata->pclk);
+
return 0;
}
static int stm32_sai_sub_resume(struct device *dev)
{
struct stm32_sai_sub_data *sai = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_enable(sai->pdata->pclk);
+ if (ret < 0)
+ return ret;
regcache_cache_only(sai->regmap, false);
- return regcache_sync(sai->regmap);
+ ret = regcache_sync(sai->regmap);
+
+ clk_disable(sai->pdata->pclk);
+
+ return ret;
}
#endif /* CONFIG_PM_SLEEP */
@@ -1531,6 +1616,7 @@ static struct platform_driver stm32_sai_sub_driver = {
.pm = &stm32_sai_sub_pm_ops,
},
.probe = stm32_sai_sub_probe,
+ .remove = stm32_sai_sub_remove,
};
module_platform_driver(stm32_sai_sub_driver);
--
2.17.1
2
1

09 Jan '20
From: Shuming Fan <shumingf(a)realtek.com>
This is the initial codec driver for rt711.
Signed-off-by: Shuming Fan <shumingf(a)realtek.com>
---
sound/soc/codecs/Kconfig | 10 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/rt711-sdw.c | 552 +++++++++++++++
sound/soc/codecs/rt711-sdw.h | 281 ++++++++
sound/soc/codecs/rt711.c | 1293 ++++++++++++++++++++++++++++++++++
sound/soc/codecs/rt711.h | 227 ++++++
6 files changed, 2365 insertions(+)
create mode 100644 sound/soc/codecs/rt711-sdw.c
create mode 100644 sound/soc/codecs/rt711-sdw.h
create mode 100644 sound/soc/codecs/rt711.c
create mode 100644 sound/soc/codecs/rt711.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 146682049007..f58c6d35e47a 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -165,6 +165,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_RT5670 if I2C
select SND_SOC_RT5677 if I2C && SPI_MASTER
select SND_SOC_RT5682 if I2C
+ select SND_SOC_RT711_SDW if SOUNDWIRE
select SND_SOC_SGTL5000 if I2C
select SND_SOC_SI476X if MFD_SI476X_CORE
select SND_SOC_SIMPLE_AMPLIFIER
@@ -1058,6 +1059,15 @@ config SND_SOC_RT5677_SPI
config SND_SOC_RT5682
tristate
+config SND_SOC_RT711
+ tristate
+
+config SND_SOC_RT711_SDW
+ tristate "Realtek RT711 Codec - SDW"
+ depends on SOUNDWIRE
+ select SND_SOC_RT711
+ select REGMAP_SOUNDWIRE
+
#Freescale sgtl5000 codec
config SND_SOC_SGTL5000
tristate "Freescale SGTL5000 CODEC"
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 0290fb389835..3fb6d0c04f5d 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -173,6 +173,7 @@ snd-soc-rt5670-objs := rt5670.o
snd-soc-rt5677-objs := rt5677.o
snd-soc-rt5677-spi-objs := rt5677-spi.o
snd-soc-rt5682-objs := rt5682.o
+snd-soc-rt711-objs := rt711.o rt711-sdw.o
snd-soc-sgtl5000-objs := sgtl5000.o
snd-soc-alc5623-objs := alc5623.o
snd-soc-alc5632-objs := alc5632.o
@@ -464,6 +465,7 @@ obj-$(CONFIG_SND_SOC_RT5670) += snd-soc-rt5670.o
obj-$(CONFIG_SND_SOC_RT5677) += snd-soc-rt5677.o
obj-$(CONFIG_SND_SOC_RT5677_SPI) += snd-soc-rt5677-spi.o
obj-$(CONFIG_SND_SOC_RT5682) += snd-soc-rt5682.o
+obj-$(CONFIG_SND_SOC_RT711) += snd-soc-rt711.o
obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o
obj-$(CONFIG_SND_SOC_SIGMADSP) += snd-soc-sigmadsp.o
obj-$(CONFIG_SND_SOC_SIGMADSP_I2C) += snd-soc-sigmadsp-i2c.o
diff --git a/sound/soc/codecs/rt711-sdw.c b/sound/soc/codecs/rt711-sdw.c
new file mode 100644
index 000000000000..e28dc84ede5b
--- /dev/null
+++ b/sound/soc/codecs/rt711-sdw.c
@@ -0,0 +1,552 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// rt711-sdw.c -- rt711 ALSA SoC audio driver
+//
+// Copyright(c) 2019 Realtek Semiconductor Corp.
+//
+//
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+#include "rt711.h"
+#include "rt711-sdw.h"
+
+static bool rt711_readable_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case 0x00e0:
+ case 0x00f0:
+ case 0x2012 ... 0x2016:
+ case 0x201a ... 0x2027:
+ case 0x2029 ... 0x202a:
+ case 0x202d ... 0x2034:
+ case 0x2201 ... 0x2204:
+ case 0x2206 ... 0x2212:
+ case 0x2220 ... 0x2223:
+ case 0x2230 ... 0x2239:
+ case 0x2f01 ... 0x2f0f:
+ case 0x3000 ... 0x3fff:
+ case 0x7000 ... 0x7fff:
+ case 0x8300 ... 0x83ff:
+ case 0x9c00 ... 0x9cff:
+ case 0xb900 ... 0xb9ff:
+ case 0x752009:
+ case 0x752011:
+ case 0x75201a:
+ case 0x752045:
+ case 0x752046:
+ case 0x752048:
+ case 0x75204a:
+ case 0x75206b:
+ case 0x75206f:
+ case 0x752080:
+ case 0x752081:
+ case 0x752091:
+ case 0x755800:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool rt711_volatile_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case 0x2016:
+ case 0x201b:
+ case 0x201c:
+ case 0x201d:
+ case 0x201f:
+ case 0x2021:
+ case 0x2023:
+ case 0x2230:
+ case 0x2012 ... 0x2015: /* HD-A read */
+ case 0x202d ... 0x202f: /* BRA */
+ case 0x2201 ... 0x2212: /* i2c debug */
+ case 0x2220 ... 0x2223: /* decoded HD-A */
+ case 0x9c00 ... 0x9cff:
+ case 0xb900 ... 0xb9ff:
+ case 0xff01:
+ case 0x75201a:
+ case 0x752046:
+ case 0x752080:
+ case 0x752081:
+ case 0x755800:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int rt711_sdw_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct device *dev = context;
+ struct rt711_priv *rt711 = dev_get_drvdata(dev);
+ unsigned int sdw_data_3, sdw_data_2, sdw_data_1, sdw_data_0;
+ unsigned int reg2 = 0, reg3 = 0, reg4 = 0, mask, nid, val2;
+ unsigned int is_hda_reg = 1, is_index_reg = 0;
+ int ret;
+
+ if (reg > 0xffff)
+ is_index_reg = 1;
+
+ mask = reg & 0xf000;
+
+ if (is_index_reg) { /* index registers */
+ val2 = reg & 0xff;
+ reg = reg >> 8;
+ nid = reg & 0xff;
+ ret = regmap_write(rt711->sdw_regmap, reg, 0);
+ if (ret < 0)
+ return ret;
+ reg2 = reg + 0x1000;
+ reg2 |= 0x80;
+ ret = regmap_write(rt711->sdw_regmap, reg2, val2);
+ if (ret < 0)
+ return ret;
+
+ reg3 = RT711_PRIV_DATA_R_H | nid;
+ ret = regmap_write(rt711->sdw_regmap,
+ reg3, ((*val >> 8) & 0xff));
+ if (ret < 0)
+ return ret;
+ reg4 = reg3 + 0x1000;
+ reg4 |= 0x80;
+ ret = regmap_write(rt711->sdw_regmap, reg4, (*val & 0xff));
+ if (ret < 0)
+ return ret;
+ } else if (mask == 0x3000) {
+ reg += 0x8000;
+ ret = regmap_write(rt711->sdw_regmap, reg, *val);
+ if (ret < 0)
+ return ret;
+ } else if (mask == 0x7000) {
+ reg += 0x2000;
+ reg |= 0x800;
+ ret = regmap_write(rt711->sdw_regmap,
+ reg, ((*val >> 8) & 0xff));
+ if (ret < 0)
+ return ret;
+ reg2 = reg + 0x1000;
+ reg2 |= 0x80;
+ ret = regmap_write(rt711->sdw_regmap, reg2, (*val & 0xff));
+ if (ret < 0)
+ return ret;
+ } else if ((reg & 0xff00) == 0x8300) { /* for R channel */
+ reg2 = reg - 0x1000;
+ reg2 &= ~0x80;
+ ret = regmap_write(rt711->sdw_regmap,
+ reg2, ((*val >> 8) & 0xff));
+ if (ret < 0)
+ return ret;
+ ret = regmap_write(rt711->sdw_regmap, reg, (*val & 0xff));
+ if (ret < 0)
+ return ret;
+ } else if (mask == 0x9000) {
+ ret = regmap_write(rt711->sdw_regmap,
+ reg, ((*val >> 8) & 0xff));
+ if (ret < 0)
+ return ret;
+ reg2 = reg + 0x1000;
+ reg2 |= 0x80;
+ ret = regmap_write(rt711->sdw_regmap, reg2, (*val & 0xff));
+ if (ret < 0)
+ return ret;
+ } else if (mask == 0xb000) {
+ ret = regmap_write(rt711->sdw_regmap, reg, *val);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = regmap_read(rt711->sdw_regmap, reg, val);
+ if (ret < 0)
+ return ret;
+ is_hda_reg = 0;
+ }
+
+ if (is_hda_reg || is_index_reg) {
+ sdw_data_3 = 0;
+ sdw_data_2 = 0;
+ sdw_data_1 = 0;
+ sdw_data_0 = 0;
+ ret = regmap_read(rt711->sdw_regmap,
+ RT711_READ_HDA_3, &sdw_data_3);
+ if (ret < 0)
+ return ret;
+ ret = regmap_read(rt711->sdw_regmap,
+ RT711_READ_HDA_2, &sdw_data_2);
+ if (ret < 0)
+ return ret;
+ ret = regmap_read(rt711->sdw_regmap,
+ RT711_READ_HDA_1, &sdw_data_1);
+ if (ret < 0)
+ return ret;
+ ret = regmap_read(rt711->sdw_regmap,
+ RT711_READ_HDA_0, &sdw_data_0);
+ if (ret < 0)
+ return ret;
+ *val = ((sdw_data_3 & 0xff) << 24) |
+ ((sdw_data_2 & 0xff) << 16) |
+ ((sdw_data_1 & 0xff) << 8) | (sdw_data_0 & 0xff);
+ }
+
+ if (is_hda_reg == 0)
+ dev_dbg(dev, "[%s] %04x => %08x\n", __func__, reg, *val);
+ else if (is_index_reg)
+ dev_dbg(dev, "[%s] %04x %04x %04x %04x => %08x\n",
+ __func__, reg, reg2, reg3, reg4, *val);
+ else
+ dev_dbg(dev, "[%s] %04x %04x => %08x\n",
+ __func__, reg, reg2, *val);
+
+ return 0;
+}
+
+static int rt711_sdw_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct device *dev = context;
+ struct rt711_priv *rt711 = dev_get_drvdata(dev);
+ unsigned int reg2 = 0, reg3, reg4, nid, mask, val2;
+ unsigned int is_index_reg = 0;
+ int ret;
+
+ if (reg > 0xffff)
+ is_index_reg = 1;
+
+ mask = reg & 0xf000;
+
+ if (is_index_reg) { /* index registers */
+ val2 = reg & 0xff;
+ reg = reg >> 8;
+ nid = reg & 0xff;
+ ret = regmap_write(rt711->sdw_regmap, reg, 0);
+ if (ret < 0)
+ return ret;
+ reg2 = reg + 0x1000;
+ reg2 |= 0x80;
+ ret = regmap_write(rt711->sdw_regmap, reg2, val2);
+ if (ret < 0)
+ return ret;
+
+ reg3 = RT711_PRIV_DATA_W_H | nid;
+ ret = regmap_write(rt711->sdw_regmap,
+ reg3, ((val >> 8) & 0xff));
+ if (ret < 0)
+ return ret;
+ reg4 = reg3 + 0x1000;
+ reg4 |= 0x80;
+ ret = regmap_write(rt711->sdw_regmap, reg4, (val & 0xff));
+ if (ret < 0)
+ return ret;
+ is_index_reg = 1;
+ } else if (reg < 0x4fff) {
+ ret = regmap_write(rt711->sdw_regmap, reg, val);
+ if (ret < 0)
+ return ret;
+ } else if (reg == RT711_FUNC_RESET) {
+ ret = regmap_write(rt711->sdw_regmap, reg, val);
+ if (ret < 0)
+ return ret;
+ } else if (mask == 0x7000) {
+ ret = regmap_write(rt711->sdw_regmap,
+ reg, ((val >> 8) & 0xff));
+ if (ret < 0)
+ return ret;
+ reg2 = reg + 0x1000;
+ reg2 |= 0x80;
+ ret = regmap_write(rt711->sdw_regmap, reg2, (val & 0xff));
+ if (ret < 0)
+ return ret;
+ } else if ((reg & 0xff00) == 0x8300) { /* for R channel */
+ reg2 = reg - 0x1000;
+ reg2 &= ~0x80;
+ ret = regmap_write(rt711->sdw_regmap,
+ reg2, ((val >> 8) & 0xff));
+ if (ret < 0)
+ return ret;
+ ret = regmap_write(rt711->sdw_regmap, reg, (val & 0xff));
+ if (ret < 0)
+ return ret;
+ }
+
+ if (reg2 == 0)
+ dev_dbg(dev, "[%s] %04x <= %04x\n", __func__, reg, val);
+ else if (is_index_reg)
+ dev_dbg(dev, "[%s] %04x %04x %04x %04x <= %04x %04x\n",
+ __func__, reg, reg2, reg3, reg4, val2, val);
+ else
+ dev_dbg(dev, "[%s] %04x %04x <= %04x\n",
+ __func__, reg, reg2, val);
+
+ return 0;
+}
+
+static const struct regmap_config rt711_regmap = {
+ .reg_bits = 24,
+ .val_bits = 32,
+ .readable_reg = rt711_readable_register,
+ .volatile_reg = rt711_volatile_register,
+ .max_register = 0x755800,
+ .reg_defaults = rt711_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(rt711_reg_defaults),
+ .cache_type = REGCACHE_RBTREE,
+ .use_single_read = true,
+ .use_single_write = true,
+ .reg_read = rt711_sdw_read,
+ .reg_write = rt711_sdw_write,
+};
+
+static const struct regmap_config rt711_sdw_regmap = {
+ .name = "sdw",
+ .reg_bits = 32,
+ .val_bits = 8,
+ .readable_reg = rt711_readable_register,
+ .max_register = 0xff01,
+ .cache_type = REGCACHE_NONE,
+ .use_single_read = true,
+ .use_single_write = true,
+};
+
+static int rt711_update_status(struct sdw_slave *slave,
+ enum sdw_slave_status status)
+{
+ struct rt711_priv *rt711 = dev_get_drvdata(&slave->dev);
+
+ /* Update the status */
+ rt711->status = status;
+
+ if (status == SDW_SLAVE_UNATTACHED)
+ rt711->hw_init = false;
+
+ /*
+ * Perform initialization only if slave status is present and
+ * hw_init flag is false
+ */
+ if (rt711->hw_init || rt711->status != SDW_SLAVE_ATTACHED)
+ return 0;
+
+ /* perform I/O transfers required for Slave initialization */
+ return rt711_io_init(&slave->dev, slave);
+}
+
+static int rt711_read_prop(struct sdw_slave *slave)
+{
+ struct sdw_slave_prop *prop = &slave->prop;
+ int nval, i, num_of_ports = 1;
+ u32 bit;
+ unsigned long addr;
+ struct sdw_dpn_prop *dpn;
+
+ prop->paging_support = false;
+
+ /* first we need to allocate memory for set bits in port lists */
+ prop->source_ports = 0x14; /* BITMAP: 00010100 */
+ prop->sink_ports = 0x8; /* BITMAP: 00001000 */
+
+ nval = hweight32(prop->source_ports);
+ num_of_ports += nval;
+ prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval,
+ sizeof(*prop->src_dpn_prop),
+ GFP_KERNEL);
+ if (!prop->src_dpn_prop)
+ return -ENOMEM;
+
+ i = 0;
+ dpn = prop->src_dpn_prop;
+ addr = prop->source_ports;
+ for_each_set_bit(bit, &addr, 32) {
+ dpn[i].num = bit;
+ dpn[i].type = SDW_DPN_FULL;
+ dpn[i].simple_ch_prep_sm = true;
+ dpn[i].ch_prep_timeout = 10;
+ i++;
+ }
+
+ /* do this again for sink now */
+ nval = hweight32(prop->sink_ports);
+ num_of_ports += nval;
+ prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval,
+ sizeof(*prop->sink_dpn_prop),
+ GFP_KERNEL);
+ if (!prop->sink_dpn_prop)
+ return -ENOMEM;
+
+ i = 0;
+ dpn = prop->sink_dpn_prop;
+ addr = prop->sink_ports;
+ for_each_set_bit(bit, &addr, 32) {
+ dpn[i].num = bit;
+ dpn[i].type = SDW_DPN_FULL;
+ dpn[i].simple_ch_prep_sm = true;
+ dpn[i].ch_prep_timeout = 10;
+ i++;
+ }
+
+ /* Allocate port_ready based on num_of_ports */
+ slave->port_ready = devm_kcalloc(&slave->dev, num_of_ports,
+ sizeof(*slave->port_ready),
+ GFP_KERNEL);
+ if (!slave->port_ready)
+ return -ENOMEM;
+
+ /* Initialize completion */
+ for (i = 0; i < num_of_ports; i++)
+ init_completion(&slave->port_ready[i]);
+
+ /* set the timeout values */
+ prop->clk_stop_timeout = 20;
+
+ /* wake-up event */
+ prop->wake_capable = 1;
+
+ return 0;
+}
+
+static int rt711_bus_config(struct sdw_slave *slave,
+ struct sdw_bus_params *params)
+{
+ struct rt711_priv *rt711 = dev_get_drvdata(&slave->dev);
+ int ret;
+
+ memcpy(&rt711->params, params, sizeof(*params));
+
+ ret = rt711_clock_config(&slave->dev);
+ if (ret < 0)
+ dev_err(&slave->dev, "Invalid clk config");
+
+ return ret;
+}
+
+static int rt711_interrupt_callback(struct sdw_slave *slave,
+ struct sdw_slave_intr_status *status)
+{
+ struct rt711_priv *rt711 = dev_get_drvdata(&slave->dev);
+
+ dev_dbg(&slave->dev,
+ "%s control_port_stat=%x", __func__, status->control_port);
+
+ if (status->control_port & 0x4) {
+ mod_delayed_work(system_power_efficient_wq,
+ &rt711->jack_detect_work, msecs_to_jiffies(250));
+ }
+
+ return 0;
+}
+
+static struct sdw_slave_ops rt711_slave_ops = {
+ .read_prop = rt711_read_prop,
+ .interrupt_callback = rt711_interrupt_callback,
+ .update_status = rt711_update_status,
+ .bus_config = rt711_bus_config,
+};
+
+static int rt711_sdw_probe(struct sdw_slave *slave,
+ const struct sdw_device_id *id)
+{
+ struct regmap *sdw_regmap, *regmap;
+
+ /* Assign ops */
+ slave->ops = &rt711_slave_ops;
+
+ /* Regmap Initialization */
+ sdw_regmap = devm_regmap_init_sdw(slave, &rt711_sdw_regmap);
+ if (!sdw_regmap)
+ return -EINVAL;
+
+ regmap = devm_regmap_init(&slave->dev, NULL,
+ &slave->dev, &rt711_regmap);
+ if (!regmap)
+ return -EINVAL;
+
+ rt711_init(&slave->dev, sdw_regmap, regmap, slave);
+
+ return 0;
+}
+
+static int rt711_sdw_remove(struct sdw_slave *slave)
+{
+ struct rt711_priv *rt711 = dev_get_drvdata(&slave->dev);
+
+ if (rt711 && rt711->hw_init) {
+ cancel_delayed_work(&rt711->jack_detect_work);
+ cancel_delayed_work(&rt711->jack_btn_check_work);
+ cancel_work_sync(&rt711->calibration_work);
+ }
+
+ return 0;
+}
+
+static const struct sdw_device_id rt711_id[] = {
+ SDW_SLAVE_ENTRY(0x025d, 0x711, 0),
+ {},
+};
+MODULE_DEVICE_TABLE(sdw, rt711_id);
+
+static int rt711_dev_suspend(struct device *dev)
+{
+ struct rt711_priv *rt711 = dev_get_drvdata(dev);
+
+ if (!rt711->hw_init)
+ return 0;
+
+ regcache_cache_only(rt711->regmap, true);
+
+ return 0;
+}
+
+#define RT711_PROBE_TIMEOUT 2000
+
+static int rt711_dev_resume(struct device *dev)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+ struct rt711_priv *rt711 = dev_get_drvdata(dev);
+ unsigned long time;
+
+ if (!rt711->hw_init)
+ return 0;
+
+ if (!slave->unattach_request)
+ goto regmap_sync;
+
+ time = wait_for_completion_timeout(&slave->initialization_complete,
+ msecs_to_jiffies(RT711_PROBE_TIMEOUT));
+ if (!time) {
+ dev_err(&slave->dev, "Initialization not complete, timed out\n");
+ return -ETIMEDOUT;
+ }
+
+regmap_sync:
+ slave->unattach_request = 0;
+ regcache_cache_only(rt711->regmap, false);
+ regcache_sync_region(rt711->regmap, 0x3000, 0x8fff);
+ regcache_sync_region(rt711->regmap, 0x752009, 0x752091);
+
+ return 0;
+}
+
+static const struct dev_pm_ops rt711_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(rt711_dev_suspend, rt711_dev_resume)
+ SET_RUNTIME_PM_OPS(rt711_dev_suspend, rt711_dev_resume, NULL)
+};
+
+static struct sdw_driver rt711_sdw_driver = {
+ .driver = {
+ .name = "rt711",
+ .owner = THIS_MODULE,
+ .pm = &rt711_pm,
+ },
+ .probe = rt711_sdw_probe,
+ .remove = rt711_sdw_remove,
+ .ops = &rt711_slave_ops,
+ .id_table = rt711_id,
+};
+module_sdw_driver(rt711_sdw_driver);
+
+MODULE_DESCRIPTION("ASoC RT711 SDW driver");
+MODULE_AUTHOR("Shuming Fan <shumingf(a)realtek.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/rt711-sdw.h b/sound/soc/codecs/rt711-sdw.h
new file mode 100644
index 000000000000..43b2b984b29c
--- /dev/null
+++ b/sound/soc/codecs/rt711-sdw.h
@@ -0,0 +1,281 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * rt711-sdw.h -- RT711 ALSA SoC audio driver header
+ *
+ * Copyright(c) 2019 Realtek Semiconductor Corp.
+ */
+
+#ifndef __RT711_SDW_H__
+#define __RT711_SDW_H__
+
+static const struct reg_default rt711_reg_defaults[] = {
+ { 0x0000, 0x00 },
+ { 0x0001, 0x00 },
+ { 0x0002, 0x00 },
+ { 0x0003, 0x00 },
+ { 0x0004, 0x00 },
+ { 0x0005, 0x01 },
+ { 0x0020, 0x00 },
+ { 0x0022, 0x00 },
+ { 0x0023, 0x00 },
+ { 0x0024, 0x00 },
+ { 0x0025, 0x00 },
+ { 0x0026, 0x00 },
+ { 0x0030, 0x00 },
+ { 0x0032, 0x00 },
+ { 0x0033, 0x00 },
+ { 0x0034, 0x00 },
+ { 0x0035, 0x00 },
+ { 0x0036, 0x00 },
+ { 0x0040, 0x00 },
+ { 0x0041, 0x00 },
+ { 0x0042, 0x00 },
+ { 0x0043, 0x00 },
+ { 0x0044, 0x20 },
+ { 0x0045, 0x01 },
+ { 0x0046, 0x01 },
+ { 0x0050, 0x20 },
+ { 0x0051, 0x02 },
+ { 0x0052, 0x5d },
+ { 0x0053, 0x07 },
+ { 0x0054, 0x11 },
+ { 0x0055, 0x00 },
+ { 0x0060, 0x00 },
+ { 0x0070, 0x00 },
+ { 0x0080, 0xc0 },
+ { 0x0088, 0x00 },
+ { 0x00e0, 0x00 },
+ { 0x00e1, 0x00 },
+ { 0x00e2, 0x00 },
+ { 0x00e3, 0x00 },
+ { 0x00e5, 0x00 },
+ { 0x00ee, 0x00 },
+ { 0x00ef, 0x00 },
+ { 0x00f0, 0x00 },
+ { 0x00f1, 0x00 },
+ { 0x00f2, 0x00 },
+ { 0x00f3, 0x00 },
+ { 0x00f4, 0x00 },
+ { 0x00f5, 0x00 },
+ { 0x00fe, 0x00 },
+ { 0x00ff, 0x00 },
+ { 0x0100, 0x00 },
+ { 0x0101, 0x00 },
+ { 0x0102, 0x00 },
+ { 0x0103, 0x00 },
+ { 0x0104, 0x00 },
+ { 0x0105, 0x00 },
+ { 0x0120, 0x00 },
+ { 0x0122, 0x00 },
+ { 0x0123, 0x00 },
+ { 0x0124, 0x00 },
+ { 0x0125, 0x00 },
+ { 0x0126, 0x00 },
+ { 0x0127, 0x00 },
+ { 0x0130, 0x00 },
+ { 0x0132, 0x00 },
+ { 0x0133, 0x00 },
+ { 0x0134, 0x00 },
+ { 0x0135, 0x00 },
+ { 0x0136, 0x00 },
+ { 0x0137, 0x00 },
+ { 0x0200, 0x00 },
+ { 0x0201, 0x00 },
+ { 0x0202, 0x00 },
+ { 0x0203, 0x00 },
+ { 0x0204, 0x00 },
+ { 0x0205, 0x03 },
+ { 0x0220, 0x00 },
+ { 0x0222, 0x00 },
+ { 0x0223, 0x00 },
+ { 0x0224, 0x00 },
+ { 0x0225, 0x00 },
+ { 0x0226, 0x00 },
+ { 0x0227, 0x00 },
+ { 0x0230, 0x00 },
+ { 0x0232, 0x00 },
+ { 0x0233, 0x00 },
+ { 0x0234, 0x00 },
+ { 0x0235, 0x00 },
+ { 0x0236, 0x00 },
+ { 0x0237, 0x00 },
+ { 0x0300, 0x00 },
+ { 0x0301, 0x00 },
+ { 0x0302, 0x20 },
+ { 0x0303, 0x00 },
+ { 0x0304, 0x00 },
+ { 0x0305, 0x03 },
+ { 0x0320, 0x00 },
+ { 0x0322, 0x00 },
+ { 0x0323, 0x00 },
+ { 0x0324, 0x00 },
+ { 0x0325, 0x00 },
+ { 0x0326, 0x00 },
+ { 0x0327, 0x00 },
+ { 0x0330, 0x00 },
+ { 0x0332, 0x00 },
+ { 0x0333, 0x00 },
+ { 0x0334, 0x00 },
+ { 0x0335, 0x00 },
+ { 0x0336, 0x00 },
+ { 0x0337, 0x00 },
+ { 0x0400, 0x00 },
+ { 0x0401, 0x00 },
+ { 0x0402, 0x00 },
+ { 0x0403, 0x00 },
+ { 0x0404, 0x00 },
+ { 0x0405, 0x03 },
+ { 0x0420, 0x00 },
+ { 0x0422, 0x00 },
+ { 0x0423, 0x00 },
+ { 0x0424, 0x00 },
+ { 0x0425, 0x00 },
+ { 0x0426, 0x00 },
+ { 0x0427, 0x00 },
+ { 0x0430, 0x00 },
+ { 0x0432, 0x00 },
+ { 0x0433, 0x00 },
+ { 0x0434, 0x00 },
+ { 0x0435, 0x00 },
+ { 0x0436, 0x00 },
+ { 0x0437, 0x00 },
+ { 0x0f00, 0x00 },
+ { 0x0f01, 0x00 },
+ { 0x0f02, 0x20 },
+ { 0x0f03, 0x00 },
+ { 0x0f04, 0x00 },
+ { 0x0f05, 0x03 },
+ { 0x0f06, 0x00 },
+ { 0x0f07, 0x00 },
+ { 0x0f08, 0x00 },
+ { 0x0f09, 0x00 },
+ { 0x0f10, 0x00 },
+ { 0x0f11, 0x00 },
+ { 0x0f12, 0x00 },
+ { 0x0f13, 0x00 },
+ { 0x0f14, 0x00 },
+ { 0x0f15, 0x00 },
+ { 0x0f16, 0x00 },
+ { 0x0f17, 0x00 },
+ { 0x0f18, 0x00 },
+ { 0x0f19, 0x00 },
+ { 0x0f1a, 0x00 },
+ { 0x0f1b, 0x00 },
+ { 0x0f1c, 0x00 },
+ { 0x0f1d, 0x00 },
+ { 0x0f1e, 0x00 },
+ { 0x0f1f, 0x00 },
+ { 0x0f20, 0x00 },
+ { 0x0f22, 0x00 },
+ { 0x0f23, 0x00 },
+ { 0x0f24, 0x00 },
+ { 0x0f25, 0x00 },
+ { 0x0f26, 0x00 },
+ { 0x0f27, 0x00 },
+ { 0x0f30, 0x00 },
+ { 0x0f32, 0x00 },
+ { 0x0f33, 0x00 },
+ { 0x0f34, 0x00 },
+ { 0x0f35, 0x00 },
+ { 0x0f36, 0x00 },
+ { 0x0f37, 0x00 },
+ { 0x2012, 0x00 },
+ { 0x2013, 0x00 },
+ { 0x2014, 0x00 },
+ { 0x2015, 0x00 },
+ { 0x2016, 0x00 },
+ { 0x201a, 0x00 },
+ { 0x201b, 0x00 },
+ { 0x201c, 0x0c },
+ { 0x201d, 0x00 },
+ { 0x201e, 0x00 },
+ { 0x201f, 0x00 },
+ { 0x2020, 0x00 },
+ { 0x2021, 0x00 },
+ { 0x2022, 0x00 },
+ { 0x2023, 0x00 },
+ { 0x2024, 0x00 },
+ { 0x2025, 0x01 },
+ { 0x2026, 0x00 },
+ { 0x2027, 0x00 },
+ { 0x2029, 0x00 },
+ { 0x202a, 0x00 },
+ { 0x202d, 0x00 },
+ { 0x202e, 0x00 },
+ { 0x202f, 0x00 },
+ { 0x2030, 0x00 },
+ { 0x2031, 0x00 },
+ { 0x2032, 0x00 },
+ { 0x2033, 0x00 },
+ { 0x2034, 0x00 },
+ { 0x2201, 0xc7 },
+ { 0x2202, 0x0c },
+ { 0x2203, 0x22 },
+ { 0x2204, 0x04 },
+ { 0x2206, 0x00 },
+ { 0x2207, 0x00 },
+ { 0x2208, 0x00 },
+ { 0x2209, 0x00 },
+ { 0x220a, 0x00 },
+ { 0x220b, 0x00 },
+ { 0x220c, 0x00 },
+ { 0x220d, 0x04 },
+ { 0x220e, 0x00 },
+ { 0x220f, 0x00 },
+ { 0x2211, 0x01 },
+ { 0x2212, 0x00 },
+ { 0x2220, 0x00 },
+ { 0x2221, 0x00 },
+ { 0x2222, 0x00 },
+ { 0x2223, 0x00 },
+ { 0x2230, 0x00 },
+ { 0x2231, 0x2f },
+ { 0x2232, 0x80 },
+ { 0x2233, 0x00 },
+ { 0x2234, 0x00 },
+ { 0x2235, 0x00 },
+ { 0x2236, 0x00 },
+ { 0x2237, 0x00 },
+ { 0x2238, 0x00 },
+ { 0x2239, 0x00 },
+ { 0x2f01, 0x00 },
+ { 0x2f02, 0x09 },
+ { 0x2f03, 0x00 },
+ { 0x2f04, 0x00 },
+ { 0x2f05, 0x0b },
+ { 0x2f06, 0x01 },
+ { 0x2f07, 0xcf },
+ { 0x2f08, 0x00 },
+ { 0x2f09, 0x00 },
+ { 0x2f0a, 0x00 },
+ { 0x2f0b, 0x00 },
+ { 0x2f0c, 0x00 },
+ { 0x2f0d, 0x00 },
+ { 0x2f0e, 0x00 },
+ { 0x2f0f, 0x00 },
+ { 0x3122, 0x00 },
+ { 0x3123, 0x00 },
+ { 0x7303, 0x57 },
+ { 0x8383, 0x57 },
+ { 0x7308, 0x97 },
+ { 0x8388, 0x97 },
+ { 0x7309, 0x97 },
+ { 0x8389, 0x97 },
+ { 0x7312, 0x00 },
+ { 0x8392, 0x00 },
+ { 0x7313, 0x00 },
+ { 0x8393, 0x00 },
+ { 0x7319, 0x00 },
+ { 0x8399, 0x00 },
+ { 0x752009, 0x1029 },
+ { 0x752011, 0x007a },
+ { 0x75201a, 0x8003 },
+ { 0x752045, 0x5289 },
+ { 0x752048, 0xd049 },
+ { 0x75204a, 0xa83b },
+ { 0x75206b, 0x5064 },
+ { 0x75206f, 0x058b },
+ { 0x752091, 0x0000 },
+};
+
+#endif /* __RT711_SDW_H__ */
diff --git a/sound/soc/codecs/rt711.c b/sound/soc/codecs/rt711.c
new file mode 100644
index 000000000000..3bebba7a63be
--- /dev/null
+++ b/sound/soc/codecs/rt711.c
@@ -0,0 +1,1293 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// rt711.c -- rt711 ALSA SoC audio driver
+//
+// Copyright(c) 2019 Realtek Semiconductor Corp.
+//
+//
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/hda_verbs.h>
+#include <sound/jack.h>
+
+#include "rt711.h"
+
+static int rt711_index_write(struct regmap *regmap,
+ unsigned int nid, unsigned int reg, unsigned int value)
+{
+ int ret;
+ unsigned int addr = ((RT711_PRIV_INDEX_W_H | nid) << 8) | reg;
+
+ ret = regmap_write(regmap, addr, value);
+ if (ret < 0)
+ pr_err("Failed to set private value: %06x <= %04x ret=%d\n",
+ addr, value, ret);
+
+ return ret;
+}
+
+static int rt711_index_read(struct regmap *regmap,
+ unsigned int nid, unsigned int reg, unsigned int *value)
+{
+ int ret;
+ unsigned int addr = ((RT711_PRIV_INDEX_W_H | nid) << 8) | reg;
+
+ *value = 0;
+ ret = regmap_read(regmap, addr, value);
+ if (ret < 0)
+ pr_err("Failed to get private value: %06x => %04x ret=%d\n",
+ addr, *value, ret);
+
+ return ret;
+}
+
+static int rt711_index_update_bits(struct regmap *regmap, unsigned int nid,
+ unsigned int reg, unsigned int mask, unsigned int val)
+{
+ unsigned int tmp, orig;
+ int ret;
+
+ ret = rt711_index_read(regmap, nid, reg, &orig);
+ if (ret < 0)
+ return ret;
+
+ tmp = orig & ~mask;
+ tmp |= val & mask;
+
+ return rt711_index_write(regmap, nid, reg, tmp);
+}
+
+static void rt711_reset(struct regmap *regmap)
+{
+ regmap_write(regmap, RT711_FUNC_RESET, 0);
+ rt711_index_update_bits(regmap, RT711_VENDOR_REG,
+ RT711_PARA_VERB_CTL, RT711_HIDDEN_REG_SW_RESET,
+ RT711_HIDDEN_REG_SW_RESET);
+}
+
+static int rt711_calibration(struct rt711_priv *rt711)
+{
+ unsigned int val, loop = 0;
+ struct device *dev;
+ struct regmap *regmap = rt711->regmap;
+ int ret = 0;
+
+ mutex_lock(&rt711->calibrate_mutex);
+ regmap_write(rt711->regmap,
+ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0);
+
+ dev = regmap_get_device(regmap);
+
+ /* Calibration manual mode */
+ rt711_index_update_bits(regmap, RT711_VENDOR_REG, RT711_FSM_CTL,
+ 0xf, 0x0);
+
+ /* trigger */
+ rt711_index_update_bits(regmap, RT711_VENDOR_CALI,
+ RT711_DAC_DC_CALI_CTL1, RT711_DAC_DC_CALI_TRIGGER,
+ RT711_DAC_DC_CALI_TRIGGER);
+
+ /* wait for calibration process */
+ rt711_index_read(regmap, RT711_VENDOR_CALI,
+ RT711_DAC_DC_CALI_CTL1, &val);
+
+ while (val & RT711_DAC_DC_CALI_TRIGGER) {
+ if (loop >= 500) {
+ pr_err("%s, calibration time-out!\n",
+ __func__);
+ ret = -ETIMEDOUT;
+ break;
+ }
+ loop++;
+
+ usleep_range(10000, 11000);
+ rt711_index_read(regmap, RT711_VENDOR_CALI,
+ RT711_DAC_DC_CALI_CTL1, &val);
+ }
+
+ /* depop mode */
+ rt711_index_update_bits(regmap, RT711_VENDOR_REG,
+ RT711_FSM_CTL, 0xf, RT711_DEPOP_CTL);
+
+ regmap_write(rt711->regmap,
+ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3);
+ mutex_unlock(&rt711->calibrate_mutex);
+
+ dev_dbg(dev, "%s calibration complete, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+static unsigned int rt711_button_detect(struct rt711_priv *rt711)
+{
+ unsigned int btn_type = 0, val80, val81;
+ int ret;
+
+ ret = rt711_index_read(rt711->regmap, RT711_VENDOR_REG,
+ RT711_IRQ_FLAG_TABLE1, &val80);
+ if (ret < 0)
+ goto read_error;
+ ret = rt711_index_read(rt711->regmap, RT711_VENDOR_REG,
+ RT711_IRQ_FLAG_TABLE2, &val81);
+ if (ret < 0)
+ goto read_error;
+
+ val80 &= 0x0381;
+ val81 &= 0xff00;
+
+ switch (val80) {
+ case 0x0200:
+ case 0x0100:
+ case 0x0080:
+ btn_type |= SND_JACK_BTN_0;
+ break;
+ case 0x0001:
+ btn_type |= SND_JACK_BTN_3;
+ break;
+ }
+ switch (val81) {
+ case 0x8000:
+ case 0x4000:
+ case 0x2000:
+ btn_type |= SND_JACK_BTN_1;
+ break;
+ case 0x1000:
+ case 0x0800:
+ case 0x0400:
+ btn_type |= SND_JACK_BTN_2;
+ break;
+ case 0x0200:
+ case 0x0100:
+ btn_type |= SND_JACK_BTN_3;
+ break;
+ }
+read_error:
+ return btn_type;
+}
+
+static int rt711_headset_detect(struct rt711_priv *rt711)
+{
+ unsigned int buf, loop = 0;
+ int ret;
+ unsigned int jack_status = 0, reg;
+
+ ret = rt711_index_read(rt711->regmap, RT711_VENDOR_REG,
+ RT711_COMBO_JACK_AUTO_CTL2, &buf);
+ if (ret < 0)
+ goto io_error;
+
+ while (loop < 500 &&
+ (buf & RT711_COMBOJACK_AUTO_DET_STATUS) == 0) {
+ loop++;
+
+ usleep_range(9000, 10000);
+ ret = rt711_index_read(rt711->regmap, RT711_VENDOR_REG,
+ RT711_COMBO_JACK_AUTO_CTL2, &buf);
+ if (ret < 0)
+ goto io_error;
+
+ reg = RT711_VERB_GET_PIN_SENSE | RT711_HP_OUT;
+ ret = regmap_read(rt711->regmap, reg, &jack_status);
+ if (ret < 0)
+ goto io_error;
+ if ((jack_status & (1 << 31)) == 0)
+ goto remove_error;
+ }
+
+ if (loop >= 500)
+ goto to_error;
+
+ if (buf & RT711_COMBOJACK_AUTO_DET_TRS)
+ rt711->jack_type = SND_JACK_HEADPHONE;
+ else if ((buf & RT711_COMBOJACK_AUTO_DET_CTIA) ||
+ (buf & RT711_COMBOJACK_AUTO_DET_OMTP))
+ rt711->jack_type = SND_JACK_HEADSET;
+
+ return 0;
+
+to_error:
+ ret = -ETIMEDOUT;
+ pr_err_ratelimited("Time-out error in %s\n", __func__);
+ return ret;
+io_error:
+ pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret);
+ return ret;
+remove_error:
+ pr_err_ratelimited("Jack removal in %s\n", __func__);
+ return -ENODEV;
+}
+
+static void rt711_jack_detect_handler(struct work_struct *work)
+{
+ struct rt711_priv *rt711 =
+ container_of(work, struct rt711_priv, jack_detect_work.work);
+ int btn_type = 0, ret;
+ unsigned int jack_status = 0, reg;
+
+ if (!rt711->hs_jack)
+ return;
+
+ if (!rt711->component->card->instantiated)
+ return;
+
+ reg = RT711_VERB_GET_PIN_SENSE | RT711_HP_OUT;
+ ret = regmap_read(rt711->regmap, reg, &jack_status);
+ if (ret < 0)
+ goto io_error;
+
+ /* pin attached */
+ if (jack_status & (1 << 31)) {
+ /* jack in */
+ if (rt711->jack_type == 0) {
+ ret = rt711_headset_detect(rt711);
+ if (ret < 0)
+ return;
+ if (rt711->jack_type == SND_JACK_HEADSET)
+ btn_type = rt711_button_detect(rt711);
+ } else if (rt711->jack_type == SND_JACK_HEADSET) {
+ /* jack is already in, report button event */
+ btn_type = rt711_button_detect(rt711);
+ }
+ } else {
+ /* jack out */
+ rt711->jack_type = 0;
+ }
+
+ dev_dbg(&rt711->slave->dev,
+ "in %s, jack_type=0x%x\n", __func__, rt711->jack_type);
+ dev_dbg(&rt711->slave->dev,
+ "in %s, btn_type=0x%x\n", __func__, btn_type);
+
+ snd_soc_jack_report(rt711->hs_jack, rt711->jack_type | btn_type,
+ SND_JACK_HEADSET |
+ SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3);
+
+ if (btn_type) {
+ /* button released */
+ snd_soc_jack_report(rt711->hs_jack, rt711->jack_type,
+ SND_JACK_HEADSET |
+ SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3);
+
+ mod_delayed_work(system_power_efficient_wq,
+ &rt711->jack_btn_check_work, msecs_to_jiffies(200));
+ }
+
+ return;
+
+io_error:
+ pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret);
+}
+
+static void rt711_btn_check_handler(struct work_struct *work)
+{
+ struct rt711_priv *rt711 = container_of(work, struct rt711_priv,
+ jack_btn_check_work.work);
+ int btn_type = 0, ret;
+ unsigned int jack_status = 0, reg;
+
+ reg = RT711_VERB_GET_PIN_SENSE | RT711_HP_OUT;
+ ret = regmap_read(rt711->regmap, reg, &jack_status);
+ if (ret < 0)
+ goto io_error;
+
+ /* pin attached */
+ if (jack_status & (1 << 31)) {
+ if (rt711->jack_type == SND_JACK_HEADSET) {
+ /* jack is already in, report button event */
+ btn_type = rt711_button_detect(rt711);
+ }
+ } else {
+ rt711->jack_type = 0;
+ }
+
+ /* cbj comparator */
+ ret = rt711_index_read(rt711->regmap, RT711_VENDOR_REG,
+ RT711_COMBO_JACK_AUTO_CTL2, ®);
+ if (ret < 0)
+ goto io_error;
+
+ if ((reg & 0xf0) == 0xf0)
+ btn_type = 0;
+
+ dev_dbg(&rt711->slave->dev,
+ "%s, btn_type=0x%x\n", __func__, btn_type);
+ snd_soc_jack_report(rt711->hs_jack, rt711->jack_type | btn_type,
+ SND_JACK_HEADSET |
+ SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3);
+
+ if (btn_type) {
+ /* button released */
+ snd_soc_jack_report(rt711->hs_jack, rt711->jack_type,
+ SND_JACK_HEADSET |
+ SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3);
+
+ mod_delayed_work(system_power_efficient_wq,
+ &rt711->jack_btn_check_work, msecs_to_jiffies(200));
+ }
+
+ return;
+
+io_error:
+ pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret);
+}
+
+static void rt711_jack_init(struct rt711_priv *rt711)
+{
+ struct snd_soc_dapm_context *dapm =
+ snd_soc_component_get_dapm(rt711->component);
+
+ mutex_lock(&rt711->calibrate_mutex);
+ /* power on */
+ if (dapm->bias_level <= SND_SOC_BIAS_STANDBY)
+ regmap_write(rt711->regmap,
+ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0);
+
+ if (rt711->hs_jack) {
+ /* unsolicited response & IRQ control */
+ regmap_write(rt711->regmap,
+ RT711_SET_MIC2_UNSOLICITED_ENABLE, 0x82);
+ regmap_write(rt711->regmap,
+ RT711_SET_HP_UNSOLICITED_ENABLE, 0x81);
+ regmap_write(rt711->regmap,
+ RT711_SET_INLINE_UNSOLICITED_ENABLE, 0x83);
+ rt711_index_write(rt711->regmap, RT711_VENDOR_REG,
+ 0x10, 0x2420);
+ rt711_index_write(rt711->regmap, RT711_VENDOR_REG,
+ 0x19, 0x2e11);
+
+ switch (rt711->jd_src) {
+ case RT711_JD1:
+ /* default settings was already for JD1 */
+ break;
+ case RT711_JD2:
+ rt711_index_update_bits(rt711->regmap, RT711_VENDOR_REG,
+ RT711_JD_CTL2, RT711_JD2_2PORT_200K_DECODE_HP |
+ RT711_HP_JD_SEL_JD2,
+ RT711_JD2_2PORT_200K_DECODE_HP |
+ RT711_HP_JD_SEL_JD2);
+ rt711_index_update_bits(rt711->regmap, RT711_VENDOR_REG,
+ RT711_CC_DET1,
+ RT711_HP_JD_FINAL_RESULT_CTL_JD12,
+ RT711_HP_JD_FINAL_RESULT_CTL_JD12);
+ break;
+ default:
+ dev_warn(rt711->component->dev, "Wrong JD source\n");
+ break;
+ }
+
+ dev_dbg(&rt711->slave->dev, "in %s enable\n", __func__);
+
+ mod_delayed_work(system_power_efficient_wq,
+ &rt711->jack_detect_work, msecs_to_jiffies(250));
+ } else {
+ regmap_write(rt711->regmap,
+ RT711_SET_MIC2_UNSOLICITED_ENABLE, 0x00);
+ regmap_write(rt711->regmap,
+ RT711_SET_HP_UNSOLICITED_ENABLE, 0x00);
+ regmap_write(rt711->regmap,
+ RT711_SET_INLINE_UNSOLICITED_ENABLE, 0x00);
+
+ dev_dbg(&rt711->slave->dev, "in %s disable\n", __func__);
+ }
+
+ /* power off */
+ if (dapm->bias_level <= SND_SOC_BIAS_STANDBY)
+ regmap_write(rt711->regmap,
+ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3);
+ mutex_unlock(&rt711->calibrate_mutex);
+}
+
+static int rt711_set_jack_detect(struct snd_soc_component *component,
+ struct snd_soc_jack *hs_jack, void *data)
+{
+ struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component);
+
+ rt711->hs_jack = hs_jack;
+
+ if (!rt711->hw_init) {
+ dev_dbg(&rt711->slave->dev,
+ "%s hw_init not ready yet\n", __func__);
+ return 0;
+ }
+
+ rt711_jack_init(rt711);
+
+ return 0;
+}
+
+static void rt711_get_gain(struct rt711_priv *rt711, unsigned int addr_h,
+ unsigned int addr_l, unsigned int val_h,
+ unsigned int *r_val, unsigned int *l_val)
+{
+ /* R Channel */
+ *r_val = (val_h << 8);
+ regmap_read(rt711->regmap, addr_l, r_val);
+
+ /* L Channel */
+ val_h |= 0x20;
+ *l_val = (val_h << 8);
+ regmap_read(rt711->regmap, addr_h, l_val);
+}
+
+/* For Verb-Set Amplifier Gain (Verb ID = 3h) */
+static int rt711_set_amp_gain_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_context *dapm =
+ snd_soc_component_get_dapm(component);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component);
+ unsigned int addr_h, addr_l, val_h, val_ll, val_lr;
+ unsigned int read_ll, read_rl;
+ int i;
+
+ /* Can't use update bit function, so read the original value first */
+ addr_h = mc->reg;
+ addr_l = mc->rreg;
+ if (mc->shift == RT711_DIR_OUT_SFT) /* output */
+ val_h = 0x80;
+ else /* input */
+ val_h = 0x0;
+
+ rt711_get_gain(rt711, addr_h, addr_l, val_h, &read_rl, &read_ll);
+
+ /* L Channel */
+ if (mc->invert) {
+ /* for mute/unmute */
+ val_ll = (mc->max - ucontrol->value.integer.value[0])
+ << RT711_MUTE_SFT;
+ /* keep gain */
+ read_ll = read_ll & 0x7f;
+ val_ll |= read_ll;
+ } else {
+ /* for gain */
+ val_ll = ((ucontrol->value.integer.value[0]) & 0x7f);
+ if (val_ll > mc->max)
+ val_ll = mc->max;
+ /* keep mute status */
+ read_ll = read_ll & (1 << RT711_MUTE_SFT);
+ val_ll |= read_ll;
+ }
+
+ if (dapm->bias_level <= SND_SOC_BIAS_STANDBY)
+ regmap_write(rt711->regmap,
+ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0);
+
+ /* R Channel */
+ if (mc->invert) {
+ /* for mute/unmute */
+ val_lr = (mc->max - ucontrol->value.integer.value[1])
+ << RT711_MUTE_SFT;
+ /* keep gain */
+ read_rl = read_rl & 0x7f;
+ val_lr |= read_rl;
+ } else {
+ /* for gain */
+ val_lr = ((ucontrol->value.integer.value[1]) & 0x7f);
+ if (val_lr > mc->max)
+ val_lr = mc->max;
+ /* keep mute status */
+ read_rl = read_rl & (1 << RT711_MUTE_SFT);
+ val_lr |= read_rl;
+ }
+
+ for (i = 0; i < 3; i++) { /* retry 3 times at most */
+
+ if (val_ll == val_lr) {
+ /* Set both L/R channels at the same time */
+ val_h = (1 << mc->shift) | (3 << 4);
+ regmap_write(rt711->regmap,
+ addr_h, (val_h << 8 | val_ll));
+ regmap_write(rt711->regmap,
+ addr_l, (val_h << 8 | val_ll));
+ } else {
+ /* Lch*/
+ val_h = (1 << mc->shift) | (1 << 5);
+ regmap_write(rt711->regmap,
+ addr_h, (val_h << 8 | val_ll));
+
+ /* Rch */
+ val_h = (1 << mc->shift) | (1 << 4);
+ regmap_write(rt711->regmap,
+ addr_l, (val_h << 8 | val_lr));
+ }
+ /* check result */
+ if (mc->shift == RT711_DIR_OUT_SFT) /* output */
+ val_h = 0x80;
+ else /* input */
+ val_h = 0x0;
+
+ rt711_get_gain(rt711, addr_h, addr_l, val_h,
+ &read_rl, &read_ll);
+ if (read_rl == val_lr && read_ll == val_ll)
+ break;
+ }
+
+ if (dapm->bias_level <= SND_SOC_BIAS_STANDBY)
+ regmap_write(rt711->regmap,
+ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3);
+ return 0;
+}
+
+static int rt711_set_amp_gain_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ unsigned int addr_h, addr_l, val_h;
+ unsigned int read_ll, read_rl;
+
+ /* switch to get command */
+ addr_h = mc->reg;
+ addr_l = mc->rreg;
+ if (mc->shift == RT711_DIR_OUT_SFT) /* output */
+ val_h = 0x80;
+ else /* input */
+ val_h = 0x0;
+
+ rt711_get_gain(rt711, addr_h, addr_l, val_h, &read_rl, &read_ll);
+
+ if (mc->invert) {
+ /* mute/unmute for switch controls */
+ read_ll = !((read_ll & 0x80) >> RT711_MUTE_SFT);
+ read_rl = !((read_rl & 0x80) >> RT711_MUTE_SFT);
+ } else {
+ /* for gain volume controls */
+ read_ll = read_ll & 0x7f;
+ read_rl = read_rl & 0x7f;
+ }
+ ucontrol->value.integer.value[0] = read_ll;
+ ucontrol->value.integer.value[1] = read_rl;
+
+ return 0;
+}
+
+static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -6525, 75, 0);
+static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -1725, 75, 0);
+static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, 0, 1000, 0);
+
+static const struct snd_kcontrol_new rt711_snd_controls[] = {
+ SOC_DOUBLE_R_EXT_TLV("DAC Surr Playback Volume",
+ RT711_SET_GAIN_DAC2_H, RT711_SET_GAIN_DAC2_L,
+ RT711_DIR_OUT_SFT, 0x57, 0,
+ rt711_set_amp_gain_get, rt711_set_amp_gain_put, out_vol_tlv),
+ SOC_DOUBLE_R_EXT("ADC 08 Capture Switch",
+ RT711_SET_GAIN_ADC2_H, RT711_SET_GAIN_ADC2_L,
+ RT711_DIR_IN_SFT, 1, 1,
+ rt711_set_amp_gain_get, rt711_set_amp_gain_put),
+ SOC_DOUBLE_R_EXT("ADC 09 Capture Switch",
+ RT711_SET_GAIN_ADC1_H, RT711_SET_GAIN_ADC1_L,
+ RT711_DIR_IN_SFT, 1, 1,
+ rt711_set_amp_gain_get, rt711_set_amp_gain_put),
+ SOC_DOUBLE_R_EXT_TLV("ADC 08 Capture Volume",
+ RT711_SET_GAIN_ADC2_H, RT711_SET_GAIN_ADC2_L,
+ RT711_DIR_IN_SFT, 0x3f, 0,
+ rt711_set_amp_gain_get, rt711_set_amp_gain_put, in_vol_tlv),
+ SOC_DOUBLE_R_EXT_TLV("ADC 09 Capture Volume",
+ RT711_SET_GAIN_ADC1_H, RT711_SET_GAIN_ADC1_L,
+ RT711_DIR_IN_SFT, 0x3f, 0,
+ rt711_set_amp_gain_get, rt711_set_amp_gain_put, in_vol_tlv),
+ SOC_DOUBLE_R_EXT_TLV("AMIC Volume",
+ RT711_SET_GAIN_AMIC_H, RT711_SET_GAIN_AMIC_L,
+ RT711_DIR_IN_SFT, 3, 0,
+ rt711_set_amp_gain_get, rt711_set_amp_gain_put, mic_vol_tlv),
+ SOC_DOUBLE_R_EXT_TLV("DMIC1 Volume",
+ RT711_SET_GAIN_DMIC1_H, RT711_SET_GAIN_DMIC1_L,
+ RT711_DIR_IN_SFT, 3, 0,
+ rt711_set_amp_gain_get, rt711_set_amp_gain_put, mic_vol_tlv),
+ SOC_DOUBLE_R_EXT_TLV("DMIC2 Volume",
+ RT711_SET_GAIN_DMIC2_H, RT711_SET_GAIN_DMIC2_L,
+ RT711_DIR_IN_SFT, 3, 0,
+ rt711_set_amp_gain_get, rt711_set_amp_gain_put, mic_vol_tlv),
+};
+
+static int rt711_mux_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_dapm_kcontrol_component(kcontrol);
+ struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component);
+ unsigned int reg, val = 0, nid;
+ int ret;
+
+ if (strstr(ucontrol->id.name, "ADC 22 Mux"))
+ nid = RT711_MIXER_IN1;
+ else if (strstr(ucontrol->id.name, "ADC 23 Mux"))
+ nid = RT711_MIXER_IN2;
+ else
+ return -EINVAL;
+
+ /* vid = 0xf01 */
+ reg = RT711_VERB_SET_CONNECT_SEL | nid;
+ ret = regmap_read(rt711->regmap, reg, &val);
+ if (ret < 0) {
+ dev_err(component->dev, "%s: sdw read failed: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ucontrol->value.enumerated.item[0] = val;
+
+ return 0;
+}
+
+static int rt711_mux_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_dapm_kcontrol_component(kcontrol);
+ struct snd_soc_dapm_context *dapm =
+ snd_soc_dapm_kcontrol_dapm(kcontrol);
+ struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+ unsigned int val, val2 = 0, change, reg, nid;
+ int ret;
+
+ if (item[0] >= e->items)
+ return -EINVAL;
+
+ if (strstr(ucontrol->id.name, "ADC 22 Mux"))
+ nid = RT711_MIXER_IN1;
+ else if (strstr(ucontrol->id.name, "ADC 23 Mux"))
+ nid = RT711_MIXER_IN2;
+ else
+ return -EINVAL;
+
+ /* Verb ID = 0x701h */
+ val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l;
+
+ reg = RT711_VERB_SET_CONNECT_SEL | nid;
+ ret = regmap_read(rt711->regmap, reg, &val2);
+ if (ret < 0) {
+ dev_err(component->dev, "%s: sdw read failed: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ if (val == val2)
+ change = 0;
+ else
+ change = 1;
+
+ if (change) {
+ reg = RT711_VERB_SET_CONNECT_SEL | nid;
+ regmap_write(rt711->regmap, reg, val);
+ }
+
+ snd_soc_dapm_mux_update_power(dapm, kcontrol,
+ item[0], e, NULL);
+
+ return change;
+}
+
+static const char * const adc_mux_text[] = {
+ "MIC2",
+ "LINE1",
+ "LINE2",
+ "DMIC",
+};
+
+static SOC_ENUM_SINGLE_DECL(
+ rt711_adc22_enum, SND_SOC_NOPM, 0, adc_mux_text);
+
+static SOC_ENUM_SINGLE_DECL(
+ rt711_adc23_enum, SND_SOC_NOPM, 0, adc_mux_text);
+
+static const struct snd_kcontrol_new rt711_adc22_mux =
+ SOC_DAPM_ENUM_EXT("ADC 22 Mux", rt711_adc22_enum,
+ rt711_mux_get, rt711_mux_put);
+
+static const struct snd_kcontrol_new rt711_adc23_mux =
+ SOC_DAPM_ENUM_EXT("ADC 23 Mux", rt711_adc23_enum,
+ rt711_mux_get, rt711_mux_put);
+
+static int rt711_dac_surround_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *component =
+ snd_soc_dapm_to_component(w->dapm);
+ struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component);
+ unsigned int val_h = (1 << RT711_DIR_OUT_SFT) | (0x3 << 4);
+ unsigned int val_l;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ regmap_write(rt711->regmap,
+ RT711_SET_STREAMID_DAC2, 0x10);
+
+ val_l = 0x00;
+ regmap_write(rt711->regmap,
+ RT711_SET_GAIN_HP_H, (val_h << 8 | val_l));
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ val_l = (1 << RT711_MUTE_SFT);
+ regmap_write(rt711->regmap,
+ RT711_SET_GAIN_HP_H, (val_h << 8 | val_l));
+ usleep_range(50000, 55000);
+
+ regmap_write(rt711->regmap,
+ RT711_SET_STREAMID_DAC2, 0x00);
+ break;
+ }
+ return 0;
+}
+
+static int rt711_adc_09_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *component =
+ snd_soc_dapm_to_component(w->dapm);
+ struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ regmap_write(rt711->regmap,
+ RT711_SET_STREAMID_ADC1, 0x10);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ regmap_write(rt711->regmap,
+ RT711_SET_STREAMID_ADC1, 0x00);
+ break;
+ }
+ return 0;
+}
+
+static int rt711_adc_08_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *component =
+ snd_soc_dapm_to_component(w->dapm);
+ struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ regmap_write(rt711->regmap,
+ RT711_SET_STREAMID_ADC2, 0x10);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ regmap_write(rt711->regmap,
+ RT711_SET_STREAMID_ADC2, 0x00);
+ break;
+ }
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget rt711_dapm_widgets[] = {
+ SND_SOC_DAPM_OUTPUT("HP"),
+ SND_SOC_DAPM_INPUT("MIC2"),
+ SND_SOC_DAPM_INPUT("DMIC1"),
+ SND_SOC_DAPM_INPUT("DMIC2"),
+ SND_SOC_DAPM_INPUT("LINE1"),
+ SND_SOC_DAPM_INPUT("LINE2"),
+
+ SND_SOC_DAPM_DAC_E("DAC Surround", NULL, SND_SOC_NOPM, 0, 0,
+ rt711_dac_surround_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_ADC_E("ADC 09", NULL, SND_SOC_NOPM, 0, 0,
+ rt711_adc_09_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_ADC_E("ADC 08", NULL, SND_SOC_NOPM, 0, 0,
+ rt711_adc_08_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_MUX("ADC 22 Mux", SND_SOC_NOPM, 0, 0,
+ &rt711_adc22_mux),
+ SND_SOC_DAPM_MUX("ADC 23 Mux", SND_SOC_NOPM, 0, 0,
+ &rt711_adc23_mux),
+
+ SND_SOC_DAPM_AIF_IN("DP3RX", "DP3 Playback", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("DP2TX", "DP2 Capture", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("DP4TX", "DP4 Capture", 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route rt711_audio_map[] = {
+ {"DAC Surround", NULL, "DP3RX"},
+ {"DP2TX", NULL, "ADC 09"},
+ {"DP4TX", NULL, "ADC 08"},
+
+ {"ADC 09", NULL, "ADC 22 Mux"},
+ {"ADC 08", NULL, "ADC 23 Mux"},
+ {"ADC 22 Mux", "DMIC", "DMIC1"},
+ {"ADC 22 Mux", "LINE1", "LINE1"},
+ {"ADC 22 Mux", "LINE2", "LINE2"},
+ {"ADC 22 Mux", "MIC2", "MIC2"},
+ {"ADC 23 Mux", "DMIC", "DMIC2"},
+ {"ADC 23 Mux", "LINE1", "LINE1"},
+ {"ADC 23 Mux", "LINE2", "LINE2"},
+ {"ADC 23 Mux", "MIC2", "MIC2"},
+
+ {"HP", NULL, "DAC Surround"},
+};
+
+static int rt711_set_bias_level(struct snd_soc_component *component,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_dapm_context *dapm =
+ snd_soc_component_get_dapm(component);
+ struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component);
+
+ switch (level) {
+ case SND_SOC_BIAS_PREPARE:
+ if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {
+ regmap_write(rt711->regmap,
+ RT711_SET_AUDIO_POWER_STATE,
+ AC_PWRST_D0);
+ }
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ regmap_write(rt711->regmap,
+ RT711_SET_AUDIO_POWER_STATE,
+ AC_PWRST_D3);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int rt711_parse_dt(struct rt711_priv *rt711, struct device *dev)
+{
+ device_property_read_u32(dev, "realtek,jd-src",
+ &rt711->jd_src);
+
+ return 0;
+}
+
+static int rt711_probe(struct snd_soc_component *component)
+{
+ struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component);
+
+ rt711_parse_dt(rt711, &rt711->slave->dev);
+ rt711->component = component;
+
+ return 0;
+}
+
+static const struct snd_soc_component_driver soc_codec_dev_rt711 = {
+ .probe = rt711_probe,
+ .set_bias_level = rt711_set_bias_level,
+ .controls = rt711_snd_controls,
+ .num_controls = ARRAY_SIZE(rt711_snd_controls),
+ .dapm_widgets = rt711_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(rt711_dapm_widgets),
+ .dapm_routes = rt711_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(rt711_audio_map),
+ .set_jack = rt711_set_jack_detect,
+};
+
+static int rt711_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream,
+ int direction)
+{
+ struct sdw_stream_data *stream;
+
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+ if (!stream)
+ return -ENOMEM;
+
+ stream->sdw_stream = (struct sdw_stream_runtime *)sdw_stream;
+
+ /* Use tx_mask or rx_mask to configure stream tag and set dma_data */
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK)
+ dai->playback_dma_data = stream;
+ else
+ dai->capture_dma_data = stream;
+
+ return 0;
+}
+
+static void rt711_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sdw_stream_data *stream;
+
+ stream = snd_soc_dai_get_dma_data(dai, substream);
+ snd_soc_dai_set_dma_data(dai, substream, NULL);
+ kfree(stream);
+}
+
+static int rt711_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component);
+ struct sdw_stream_config stream_config;
+ struct sdw_port_config port_config;
+ enum sdw_data_direction direction;
+ struct sdw_stream_data *stream;
+ int retval, port, num_channels;
+ unsigned int val = 0;
+
+ dev_dbg(dai->dev, "%s %s", __func__, dai->name);
+ stream = snd_soc_dai_get_dma_data(dai, substream);
+
+ if (!stream)
+ return -EINVAL;
+
+ if (!rt711->slave)
+ return -EINVAL;
+
+ /* SoundWire specific configuration */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ direction = SDW_DATA_DIR_RX;
+ port = 3;
+ } else {
+ direction = SDW_DATA_DIR_TX;
+ if (dai->id == RT711_AIF1)
+ port = 4;
+ else if (dai->id == RT711_AIF2)
+ port = 2;
+ else
+ return -EINVAL;
+ }
+
+ stream_config.frame_rate = params_rate(params);
+ stream_config.ch_count = params_channels(params);
+ stream_config.bps = snd_pcm_format_width(params_format(params));
+ stream_config.direction = direction;
+
+ num_channels = params_channels(params);
+ port_config.ch_mask = (1 << (num_channels)) - 1;
+ port_config.num = port;
+
+ retval = sdw_stream_add_slave(rt711->slave, &stream_config,
+ &port_config, 1, stream->sdw_stream);
+ if (retval) {
+ dev_err(dai->dev, "Unable to configure port\n");
+ return retval;
+ }
+
+ if (params_channels(params) <= 16) {
+ /* bit 3:0 Number of Channel */
+ val |= (params_channels(params) - 1);
+ } else {
+ dev_err(component->dev, "Unsupported channels %d\n",
+ params_channels(params));
+ return -EINVAL;
+ }
+
+ switch (params_width(params)) {
+ /* bit 6:4 Bits per Sample */
+ case 8:
+ break;
+ case 16:
+ val |= (0x1 << 4);
+ break;
+ case 20:
+ val |= (0x2 << 4);
+ break;
+ case 24:
+ val |= (0x3 << 4);
+ break;
+ case 32:
+ val |= (0x4 << 4);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* 48Khz */
+ regmap_write(rt711->regmap, RT711_DAC_FORMAT_H, val);
+ regmap_write(rt711->regmap, RT711_ADC1_FORMAT_H, val);
+ regmap_write(rt711->regmap, RT711_ADC2_FORMAT_H, val);
+
+ return retval;
+}
+
+static int rt711_pcm_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component);
+ struct sdw_stream_data *stream =
+ snd_soc_dai_get_dma_data(dai, substream);
+
+ if (!rt711->slave)
+ return -EINVAL;
+
+ sdw_stream_remove_slave(rt711->slave, stream->sdw_stream);
+ return 0;
+}
+
+#define RT711_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+#define RT711_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8)
+
+static struct snd_soc_dai_ops rt711_ops = {
+ .hw_params = rt711_pcm_hw_params,
+ .hw_free = rt711_pcm_hw_free,
+ .set_sdw_stream = rt711_set_sdw_stream,
+ .shutdown = rt711_shutdown,
+};
+
+static struct snd_soc_dai_driver rt711_dai[] = {
+ {
+ .name = "rt711-aif1",
+ .id = RT711_AIF1,
+ .playback = {
+ .stream_name = "DP3 Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = RT711_STEREO_RATES,
+ .formats = RT711_FORMATS,
+ },
+ .capture = {
+ .stream_name = "DP4 Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = RT711_STEREO_RATES,
+ .formats = RT711_FORMATS,
+ },
+ .ops = &rt711_ops,
+ },
+ {
+ .name = "rt711-aif2",
+ .id = RT711_AIF2,
+ .capture = {
+ .stream_name = "DP2 Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = RT711_STEREO_RATES,
+ .formats = RT711_FORMATS,
+ },
+ .ops = &rt711_ops,
+ }
+};
+
+/* Bus clock frequency */
+#define RT711_CLK_FREQ_9600000HZ 9600000
+#define RT711_CLK_FREQ_12000000HZ 12000000
+#define RT711_CLK_FREQ_6000000HZ 6000000
+#define RT711_CLK_FREQ_4800000HZ 4800000
+#define RT711_CLK_FREQ_2400000HZ 2400000
+#define RT711_CLK_FREQ_12288000HZ 12288000
+
+int rt711_clock_config(struct device *dev)
+{
+ struct rt711_priv *rt711 = dev_get_drvdata(dev);
+ unsigned int clk_freq, value;
+
+ clk_freq = (rt711->params.curr_dr_freq >> 1);
+
+ switch (clk_freq) {
+ case RT711_CLK_FREQ_12000000HZ:
+ value = 0x0;
+ break;
+ case RT711_CLK_FREQ_6000000HZ:
+ value = 0x1;
+ break;
+ case RT711_CLK_FREQ_9600000HZ:
+ value = 0x2;
+ break;
+ case RT711_CLK_FREQ_4800000HZ:
+ value = 0x3;
+ break;
+ case RT711_CLK_FREQ_2400000HZ:
+ value = 0x4;
+ break;
+ case RT711_CLK_FREQ_12288000HZ:
+ value = 0x5;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_write(rt711->regmap, 0xe0, value);
+ regmap_write(rt711->regmap, 0xf0, value);
+
+ dev_dbg(dev, "%s complete, clk_freq=%d\n", __func__, clk_freq);
+
+ return 0;
+}
+
+static void rt711_calibration_work(struct work_struct *work)
+{
+ struct rt711_priv *rt711 =
+ container_of(work, struct rt711_priv, calibration_work);
+
+ rt711_calibration(rt711);
+}
+
+int rt711_init(struct device *dev, struct regmap *sdw_regmap,
+ struct regmap *regmap, struct sdw_slave *slave)
+{
+ struct rt711_priv *rt711;
+ int ret;
+
+ rt711 = devm_kzalloc(dev, sizeof(*rt711), GFP_KERNEL);
+ if (!rt711)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, rt711);
+ rt711->slave = slave;
+ rt711->sdw_regmap = sdw_regmap;
+ rt711->regmap = regmap;
+
+ /*
+ * Mark hw_init to false
+ * HW init will be performed when device reports present
+ */
+ rt711->hw_init = false;
+ rt711->first_hw_init = false;
+
+ /* JD source uses JD2 in default */
+ rt711->jd_src = RT711_JD2;
+
+ ret = devm_snd_soc_register_component(dev,
+ &soc_codec_dev_rt711,
+ rt711_dai,
+ ARRAY_SIZE(rt711_dai));
+
+ dev_dbg(&slave->dev, "%s\n", __func__);
+
+ return ret;
+}
+
+int rt711_io_init(struct device *dev, struct sdw_slave *slave)
+{
+ struct rt711_priv *rt711 = dev_get_drvdata(dev);
+
+ if (rt711->hw_init)
+ return 0;
+
+ if (rt711->first_hw_init) {
+ regcache_cache_only(rt711->regmap, false);
+ regcache_cache_bypass(rt711->regmap, true);
+ }
+
+ /*
+ * PM runtime is only enabled when a Slave reports as Attached
+ */
+ if (!rt711->first_hw_init) {
+ /* set autosuspend parameters */
+ pm_runtime_set_autosuspend_delay(&slave->dev, 3000);
+ pm_runtime_use_autosuspend(&slave->dev);
+
+ /* update count of parent 'active' children */
+ pm_runtime_set_active(&slave->dev);
+
+ /* make sure the device does not suspend immediately */
+ pm_runtime_mark_last_busy(&slave->dev);
+
+ pm_runtime_enable(&slave->dev);
+ }
+
+ pm_runtime_get_noresume(&slave->dev);
+
+ rt711_reset(rt711->regmap);
+
+ /* power on */
+ regmap_write(rt711->regmap, RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0);
+
+ /* Set Pin Widget */
+ regmap_write(rt711->regmap, RT711_SET_PIN_MIC2, 0x25);
+ regmap_write(rt711->regmap, RT711_SET_PIN_HP, 0xc0);
+ regmap_write(rt711->regmap, RT711_SET_PIN_DMIC1, 0x20);
+ regmap_write(rt711->regmap, RT711_SET_PIN_DMIC2, 0x20);
+ regmap_write(rt711->regmap, RT711_SET_PIN_LINE1, 0x20);
+ regmap_write(rt711->regmap, RT711_SET_PIN_LINE2, 0x20);
+
+ /* Mute HP/ADC1/ADC2 */
+ regmap_write(rt711->regmap, RT711_SET_GAIN_HP_H, 0xa080);
+ regmap_write(rt711->regmap, RT711_SET_GAIN_HP_H, 0x9080);
+ regmap_write(rt711->regmap, RT711_SET_GAIN_ADC2_H, 0x6080);
+ regmap_write(rt711->regmap, RT711_SET_GAIN_ADC2_H, 0x5080);
+ regmap_write(rt711->regmap, RT711_SET_GAIN_ADC1_H, 0x6080);
+ regmap_write(rt711->regmap, RT711_SET_GAIN_ADC1_H, 0x5080);
+
+ /* Set Configuration Default */
+ regmap_write(rt711->regmap, 0x4f12, 0x91);
+ regmap_write(rt711->regmap, 0x4e12, 0xd6);
+ regmap_write(rt711->regmap, 0x4d12, 0x11);
+ regmap_write(rt711->regmap, 0x4c12, 0x20);
+ regmap_write(rt711->regmap, 0x4f13, 0x91);
+ regmap_write(rt711->regmap, 0x4e13, 0xd6);
+ regmap_write(rt711->regmap, 0x4d13, 0x11);
+ regmap_write(rt711->regmap, 0x4c13, 0x21);
+ regmap_write(rt711->regmap, 0x4c21, 0xf0);
+ regmap_write(rt711->regmap, 0x4d21, 0x11);
+ regmap_write(rt711->regmap, 0x4e21, 0x11);
+ regmap_write(rt711->regmap, 0x4f21, 0x01);
+
+ /* Data port arrangement */
+ rt711_index_write(rt711->regmap, RT711_VENDOR_REG,
+ RT711_TX_RX_MUX_CTL, 0x0154);
+
+ /* Set index */
+ rt711_index_write(rt711->regmap, RT711_VENDOR_REG,
+ RT711_DIGITAL_MISC_CTRL4, 0x201b);
+ rt711_index_write(rt711->regmap, RT711_VENDOR_REG,
+ RT711_COMBO_JACK_AUTO_CTL1, 0x5089);
+ rt711_index_write(rt711->regmap, RT711_VENDOR_REG,
+ RT711_VREFOUT_CTL, 0x5064);
+ rt711_index_write(rt711->regmap, RT711_VENDOR_REG,
+ RT711_INLINE_CMD_CTL, 0xd249);
+
+ /* Finish Initial Settings, set power to D3 */
+ regmap_write(rt711->regmap, RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3);
+
+ if (rt711->first_hw_init)
+ rt711_calibration(rt711);
+ else {
+ INIT_DELAYED_WORK(&rt711->jack_detect_work,
+ rt711_jack_detect_handler);
+ INIT_DELAYED_WORK(&rt711->jack_btn_check_work,
+ rt711_btn_check_handler);
+ mutex_init(&rt711->calibrate_mutex);
+ INIT_WORK(&rt711->calibration_work, rt711_calibration_work);
+ schedule_work(&rt711->calibration_work);
+ }
+
+ /*
+ * if set_jack callback occurred early than io_init,
+ * we set up the jack detection function now
+ */
+ if (rt711->hs_jack)
+ rt711_jack_init(rt711);
+
+ if (rt711->first_hw_init) {
+ regcache_cache_bypass(rt711->regmap, false);
+ regcache_mark_dirty(rt711->regmap);
+ } else
+ rt711->first_hw_init = true;
+
+ /* Mark Slave initialization complete */
+ rt711->hw_init = true;
+
+ pm_runtime_mark_last_busy(&slave->dev);
+ pm_runtime_put_autosuspend(&slave->dev);
+
+ dev_dbg(&slave->dev, "%s hw_init complete\n", __func__);
+ return 0;
+}
+
+MODULE_DESCRIPTION("ASoC RT711 SDW driver");
+MODULE_AUTHOR("Shuming Fan <shumingf(a)realtek.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/rt711.h b/sound/soc/codecs/rt711.h
new file mode 100644
index 000000000000..ca0f581feec7
--- /dev/null
+++ b/sound/soc/codecs/rt711.h
@@ -0,0 +1,227 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * rt711.h -- RT711 ALSA SoC audio driver header
+ *
+ * Copyright(c) 2019 Realtek Semiconductor Corp.
+ */
+
+#ifndef __RT711_H__
+#define __RT711_H__
+
+extern const struct dev_pm_ops rt711_runtime_pm;
+
+struct rt711_priv {
+ struct regmap *regmap;
+ struct regmap *sdw_regmap;
+ struct snd_soc_component *component;
+ struct sdw_slave *slave;
+ enum sdw_slave_status status;
+ struct sdw_bus_params params;
+ bool hw_init;
+ bool first_hw_init;
+ struct snd_soc_jack *hs_jack;
+ struct delayed_work jack_detect_work;
+ struct delayed_work jack_btn_check_work;
+ struct work_struct calibration_work;
+ struct mutex calibrate_mutex; /* for headset calibration */
+ int jack_type, jd_src;
+};
+
+struct sdw_stream_data {
+ struct sdw_stream_runtime *sdw_stream;
+};
+
+/* NID */
+#define RT711_AUDIO_FUNCTION_GROUP 0x01
+#define RT711_DAC_OUT2 0x03
+#define RT711_ADC_IN1 0x09
+#define RT711_ADC_IN2 0x08
+#define RT711_DMIC1 0x12
+#define RT711_DMIC2 0x13
+#define RT711_MIC2 0x19
+#define RT711_LINE1 0x1a
+#define RT711_LINE2 0x1b
+#define RT711_BEEP 0x1d
+#define RT711_VENDOR_REG 0x20
+#define RT711_HP_OUT 0x21
+#define RT711_MIXER_IN1 0x22
+#define RT711_MIXER_IN2 0x23
+#define RT711_INLINE_CMD 0x55
+#define RT711_VENDOR_CALI 0x58
+#define RT711_VENDOR_IMS_DRE 0x5b
+
+/* Index (NID:20h) */
+#define RT711_DAC_DC_CALI_CTL1 0x00
+#define RT711_JD_CTL2 0x09
+#define RT711_CC_DET1 0x11
+#define RT711_PARA_VERB_CTL 0x1a
+#define RT711_COMBO_JACK_AUTO_CTL1 0x45
+#define RT711_COMBO_JACK_AUTO_CTL2 0x46
+#define RT711_INLINE_CMD_CTL 0x48
+#define RT711_DIGITAL_MISC_CTRL4 0x4a
+#define RT711_VREFOUT_CTL 0x6b
+#define RT711_FSM_CTL 0x6f
+#define RT711_IRQ_FLAG_TABLE1 0x80
+#define RT711_IRQ_FLAG_TABLE2 0x81
+#define RT711_IRQ_FLAG_TABLE3 0x82
+#define RT711_TX_RX_MUX_CTL 0x91
+
+/* Index (NID:5bh) */
+#define RT711_IMS_DIGITAL_CTL1 0x00
+#define RT711_HP_IMS_RESULT_L 0x20
+#define RT711_HP_IMS_RESULT_R 0x21
+
+/* Verb */
+#define RT711_VERB_SET_CONNECT_SEL 0x3100
+#define RT711_VERB_SET_EAPD_BTLENABLE 0x3c00
+#define RT711_VERB_GET_CONNECT_SEL 0xb100
+#define RT711_VERB_SET_POWER_STATE 0x3500
+#define RT711_VERB_SET_CHANNEL_STREAMID 0x3600
+#define RT711_VERB_SET_PIN_WIDGET_CONTROL 0x3700
+#define RT711_VERB_SET_UNSOLICITED_ENABLE 0x3800
+#define RT711_SET_AMP_GAIN_MUTE_H 0x7300
+#define RT711_SET_AMP_GAIN_MUTE_L 0x8380
+#define RT711_VERB_GET_POWER_STATE 0xb500
+#define RT711_VERB_GET_CHANNEL_STREAMID 0xb600
+#define RT711_VERB_GET_PIN_SENSE 0xb900
+#define RT711_FUNC_RESET 0xff01
+
+#define RT711_READ_HDA_3 0x2012
+#define RT711_READ_HDA_2 0x2013
+#define RT711_READ_HDA_1 0x2014
+#define RT711_READ_HDA_0 0x2015
+#define RT711_PRIV_INDEX_W_H 0x7500
+#define RT711_PRIV_INDEX_W_L 0x8580
+#define RT711_PRIV_DATA_W_H 0x7400
+#define RT711_PRIV_DATA_W_L 0x8480
+#define RT711_PRIV_INDEX_R_H 0x9d00
+#define RT711_PRIV_INDEX_R_L 0xad80
+#define RT711_PRIV_DATA_R_H 0x9c00
+#define RT711_PRIV_DATA_R_L 0xac80
+#define RT711_DAC_FORMAT_H 0x7203
+#define RT711_DAC_FORMAT_L 0x8283
+#define RT711_ADC1_FORMAT_H 0x7209
+#define RT711_ADC1_FORMAT_L 0x8289
+#define RT711_ADC2_FORMAT_H 0x7208
+#define RT711_ADC2_FORMAT_L 0x8288
+
+#define RT711_SET_AUDIO_POWER_STATE\
+ (RT711_VERB_SET_POWER_STATE | RT711_AUDIO_FUNCTION_GROUP)
+#define RT711_GET_AUDIO_POWER_STATE\
+ (RT711_VERB_GET_POWER_STATE | RT711_AUDIO_FUNCTION_GROUP)
+#define RT711_SET_PIN_DMIC1\
+ (RT711_VERB_SET_PIN_WIDGET_CONTROL | RT711_DMIC1)
+#define RT711_SET_PIN_DMIC2\
+ (RT711_VERB_SET_PIN_WIDGET_CONTROL | RT711_DMIC2)
+#define RT711_SET_PIN_HP\
+ (RT711_VERB_SET_PIN_WIDGET_CONTROL | RT711_HP_OUT)
+#define RT711_SET_PIN_MIC2\
+ (RT711_VERB_SET_PIN_WIDGET_CONTROL | RT711_MIC2)
+#define RT711_SET_PIN_LINE1\
+ (RT711_VERB_SET_PIN_WIDGET_CONTROL | RT711_LINE1)
+#define RT711_SET_PIN_LINE2\
+ (RT711_VERB_SET_PIN_WIDGET_CONTROL | RT711_LINE2)
+#define RT711_SET_MIC2_UNSOLICITED_ENABLE\
+ (RT711_VERB_SET_UNSOLICITED_ENABLE | RT711_MIC2)
+#define RT711_SET_HP_UNSOLICITED_ENABLE\
+ (RT711_VERB_SET_UNSOLICITED_ENABLE | RT711_HP_OUT)
+#define RT711_SET_INLINE_UNSOLICITED_ENABLE\
+ (RT711_VERB_SET_UNSOLICITED_ENABLE | RT711_INLINE_CMD)
+#define RT711_SET_STREAMID_DAC2\
+ (RT711_VERB_SET_CHANNEL_STREAMID | RT711_DAC_OUT2)
+#define RT711_SET_STREAMID_ADC1\
+ (RT711_VERB_SET_CHANNEL_STREAMID | RT711_ADC_IN1)
+#define RT711_SET_STREAMID_ADC2\
+ (RT711_VERB_SET_CHANNEL_STREAMID | RT711_ADC_IN2)
+#define RT711_GET_STREAMID_DAC2\
+ (RT711_VERB_GET_CHANNEL_STREAMID | RT711_DAC_OUT2)
+#define RT711_GET_STREAMID_ADC1\
+ (RT711_VERB_GET_CHANNEL_STREAMID | RT711_ADC_IN1)
+#define RT711_GET_STREAMID_ADC2\
+ (RT711_VERB_GET_CHANNEL_STREAMID | RT711_ADC_IN2)
+#define RT711_SET_GAIN_DAC2_L\
+ (RT711_SET_AMP_GAIN_MUTE_L | RT711_DAC_OUT2)
+#define RT711_SET_GAIN_DAC2_H\
+ (RT711_SET_AMP_GAIN_MUTE_H | RT711_DAC_OUT2)
+#define RT711_SET_GAIN_ADC1_L\
+ (RT711_SET_AMP_GAIN_MUTE_L | RT711_ADC_IN1)
+#define RT711_SET_GAIN_ADC1_H\
+ (RT711_SET_AMP_GAIN_MUTE_H | RT711_ADC_IN1)
+#define RT711_SET_GAIN_ADC2_L\
+ (RT711_SET_AMP_GAIN_MUTE_L | RT711_ADC_IN2)
+#define RT711_SET_GAIN_ADC2_H\
+ (RT711_SET_AMP_GAIN_MUTE_H | RT711_ADC_IN2)
+#define RT711_SET_GAIN_AMIC_L\
+ (RT711_SET_AMP_GAIN_MUTE_L | RT711_MIC2)
+#define RT711_SET_GAIN_AMIC_H\
+ (RT711_SET_AMP_GAIN_MUTE_H | RT711_MIC2)
+#define RT711_SET_GAIN_DMIC1_L\
+ (RT711_SET_AMP_GAIN_MUTE_L | RT711_DMIC1)
+#define RT711_SET_GAIN_DMIC1_H\
+ (RT711_SET_AMP_GAIN_MUTE_H | RT711_DMIC1)
+#define RT711_SET_GAIN_DMIC2_L\
+ (RT711_SET_AMP_GAIN_MUTE_L | RT711_DMIC2)
+#define RT711_SET_GAIN_DMIC2_H\
+ (RT711_SET_AMP_GAIN_MUTE_H | RT711_DMIC2)
+#define RT711_SET_GAIN_HP_L\
+ (RT711_SET_AMP_GAIN_MUTE_L | RT711_HP_OUT)
+#define RT711_SET_GAIN_HP_H\
+ (RT711_SET_AMP_GAIN_MUTE_H | RT711_HP_OUT)
+
+/* DAC DC offset calibration control-1 (0x00)(NID:20h) */
+#define RT711_DAC_DC_CALI_TRIGGER (0x1 << 15)
+
+/* jack detect control 2 (0x09)(NID:20h) */
+#define RT711_JD2_2PORT_200K_DECODE_HP (0x1 << 13)
+#define RT711_HP_JD_SEL_JD1 (0x0 << 1)
+#define RT711_HP_JD_SEL_JD2 (0x1 << 1)
+
+/* CC DET1 (0x11)(NID:20h) */
+#define RT711_HP_JD_FINAL_RESULT_CTL_JD12 (0x1 << 10)
+#define RT711_HP_JD_FINAL_RESULT_CTL_CCDET (0x0 << 10)
+
+/* Parameter & Verb control (0x1a)(NID:20h) */
+#define RT711_HIDDEN_REG_SW_RESET (0x1 << 14)
+
+/* combo jack auto switch control 2 (0x46)(NID:20h) */
+#define RT711_COMBOJACK_AUTO_DET_STATUS (0x1 << 11)
+#define RT711_COMBOJACK_AUTO_DET_TRS (0x1 << 10)
+#define RT711_COMBOJACK_AUTO_DET_CTIA (0x1 << 9)
+#define RT711_COMBOJACK_AUTO_DET_OMTP (0x1 << 8)
+
+/* FSM control (0x6f)(NID:20h) */
+#define RT711_CALI_CTL (0x0 << 0)
+#define RT711_COMBOJACK_CTL (0x1 << 0)
+#define RT711_IMS_CTL (0x2 << 0)
+#define RT711_DEPOP_CTL (0x3 << 0)
+
+/* Impedance Sense Digital Control 1 (0x00)(NID:5bh) */
+#define RT711_TRIGGER_IMS (0x1 << 15)
+#define RT711_IMS_EN (0x1 << 6)
+
+#define RT711_EAPD_HIGH 0x2
+#define RT711_EAPD_LOW 0x0
+#define RT711_MUTE_SFT 7
+/* set input/output mapping to payload[14][15] separately */
+#define RT711_DIR_IN_SFT 6
+#define RT711_DIR_OUT_SFT 7
+
+enum {
+ RT711_AIF1,
+ RT711_AIF2,
+ RT711_AIFS,
+};
+
+enum rt711_jd_src {
+ RT711_JD_NULL,
+ RT711_JD1,
+ RT711_JD2
+};
+
+int rt711_io_init(struct device *dev, struct sdw_slave *slave);
+int rt711_init(struct device *dev, struct regmap *sdw_regmap,
+ struct regmap *regmap, struct sdw_slave *slave);
+
+int rt711_jack_detect(struct rt711_priv *rt711, bool *hp, bool *mic);
+int rt711_clock_config(struct device *dev);
+#endif /* __RT711_H__ */
--
2.24.1
3
2

[alsa-devel] [PATCH v2] ASoC: max98090: save and restore SHDN when changing sensitive registers
by Tzung-Bi Shih 09 Jan '20
by Tzung-Bi Shih 09 Jan '20
09 Jan '20
According to the datasheet, there are some registers can only be changed
when SHDN is 0. Changing these settings during SHDN = 1 can compromise
device stability and performance specifications.
Saves SHDN before writing to these sensitive registers and restores SHDN
afterward.
Here is the register list codec driver of max98090 wants to change:
M98090_REG_QUICK_SYSTEM_CLOCK 0x04
M98090_REG_QUICK_SAMPLE_RATE 0x05
M98090_REG_DAI_INTERFACE 0x06
M98090_REG_DAC_PATH 0x07
M98090_REG_MIC_DIRECT_TO_ADC 0x08
M98090_REG_LINE_TO_ADC 0x09
M98090_REG_ANALOG_MIC_LOOP 0x0A
M98090_REG_ANALOG_LINE_LOOP 0x0B
M98090_REG_SYSTEM_CLOCK 0x1B
M98090_REG_CLOCK_MODE 0x1C
M98090_REG_CLOCK_RATIO_NI_MSB 0x1D
M98090_REG_CLOCK_RATIO_NI_LSB 0x1E
M98090_REG_CLOCK_RATIO_MI_MSB 0x1F
M98090_REG_CLOCK_RATIO_MI_LSB 0x20
M98090_REG_MASTER_MODE 0x21
M98090_REG_INTERFACE_FORMAT 0x22
M98090_REG_TDM_CONTROL 0x23
M98090_REG_TDM_FORMAT 0x24
M98090_REG_IO_CONFIGURATION 0x25
M98090_REG_FILTER_CONFIG 0x26
M98090_REG_INPUT_ENABLE 0x3E
M98090_REG_OUTPUT_ENABLE 0x3F
M98090_REG_BIAS_CONTROL 0x42
M98090_REG_DAC_CONTROL 0x43
M98090_REG_ADC_CONTROL 0x44
M98090_REG_DRC_TIMING 0x33
M98090_REG_DRC_COMPRESSOR 0x34
M98090_REG_DRC_EXPANDER 0x35
M98090_REG_DSP_FILTER_ENABLE 0x41
M98090_REG_EQUALIZER_BASE 0x46
M98090_REG_RECORD_BIQUAD_BASE 0xAF
M98090_REG_DIGITAL_MIC_ENABLE 0x13
M98090_REG_DIGITAL_MIC_CONFIG 0x14
Signed-off-by: Tzung-Bi Shih <tzungbi(a)google.com>
---
This patch is a follow up fix for the question:
https://mailman.alsa-project.org/pipermail/alsa-devel/2019-October/157364.h…
Changes from v1:
https://mailman.alsa-project.org/pipermail/alsa-devel/2019-November/158855.…
- fix a typo in commit message
- rebase to the latest for-next (a few line numbers changed)
sound/soc/codecs/max98090.c | 433 ++++++++++++++++++++++++++----------
sound/soc/codecs/max98090.h | 3 +-
2 files changed, 312 insertions(+), 124 deletions(-)
diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c
index e46b6ada13b1..da23810f958e 100644
--- a/sound/soc/codecs/max98090.c
+++ b/sound/soc/codecs/max98090.c
@@ -5,24 +5,149 @@
* Copyright 2011-2012 Maxim Integrated Products
*/
+#include <linux/acpi.h>
+#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
-#include <linux/acpi.h>
-#include <linux/clk.h>
#include <sound/jack.h>
+#include <sound/max98090.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/tlv.h>
-#include <sound/max98090.h>
#include "max98090.h"
+static void max98090_shdn_save_locked(struct max98090_priv *max98090)
+{
+ int shdn = 0;
+
+ /* saved_shdn, saved_count, SHDN are protected by card->dapm_mutex */
+ regmap_read(max98090->regmap, M98090_REG_DEVICE_SHUTDOWN, &shdn);
+ max98090->saved_shdn |= shdn;
+ ++max98090->saved_count;
+
+ if (shdn)
+ regmap_write(max98090->regmap, M98090_REG_DEVICE_SHUTDOWN, 0x0);
+}
+
+static void max98090_shdn_restore_locked(struct max98090_priv *max98090)
+{
+ /* saved_shdn, saved_count, SHDN are protected by card->dapm_mutex */
+ if (--max98090->saved_count == 0) {
+ if (max98090->saved_shdn) {
+ regmap_write(max98090->regmap,
+ M98090_REG_DEVICE_SHUTDOWN,
+ M98090_SHDNN_MASK);
+ max98090->saved_shdn = 0;
+ }
+ }
+}
+
+static void max98090_shdn_save(struct max98090_priv *max98090)
+{
+ mutex_lock(&max98090->component->card->dapm_mutex);
+ max98090_shdn_save_locked(max98090);
+}
+
+static void max98090_shdn_restore(struct max98090_priv *max98090)
+{
+ max98090_shdn_restore_locked(max98090);
+ mutex_unlock(&max98090->component->card->dapm_mutex);
+}
+
+static int max98090_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct max98090_priv *max98090 =
+ snd_soc_component_get_drvdata(component);
+ int ret;
+
+ max98090_shdn_save(max98090);
+ ret = snd_soc_put_volsw(kcontrol, ucontrol);
+ max98090_shdn_restore(max98090);
+
+ return ret;
+}
+
+static int max98090_dapm_put_enum_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct max98090_priv *max98090 =
+ snd_soc_component_get_drvdata(component);
+ int ret;
+
+ max98090_shdn_save(max98090);
+ ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol);
+ max98090_shdn_restore(max98090);
+
+ return ret;
+}
+
+static int max98090_put_enum_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct max98090_priv *max98090 =
+ snd_soc_component_get_drvdata(component);
+ int ret;
+
+ max98090_shdn_save(max98090);
+ ret = snd_soc_put_enum_double(kcontrol, ucontrol);
+ max98090_shdn_restore(max98090);
+
+ return ret;
+}
+
+static int max98090_bytes_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct max98090_priv *max98090 =
+ snd_soc_component_get_drvdata(component);
+ int ret;
+
+ max98090_shdn_save(max98090);
+ ret = snd_soc_bytes_put(kcontrol, ucontrol);
+ max98090_shdn_restore(max98090);
+
+ return ret;
+}
+
+static int max98090_dapm_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *component =
+ snd_soc_dapm_to_component(w->dapm);
+ struct max98090_priv *max98090 =
+ snd_soc_component_get_drvdata(component);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ case SND_SOC_DAPM_PRE_PMD:
+ max98090_shdn_save_locked(max98090);
+ break;
+ case SND_SOC_DAPM_POST_PMU:
+ case SND_SOC_DAPM_POST_PMD:
+ max98090_shdn_restore_locked(max98090);
+ break;
+ }
+
+ return 0;
+}
+
/* Allows for sparsely populated register maps */
static const struct reg_default max98090_reg[] = {
{ 0x00, 0x00 }, /* 00 Software Reset */
@@ -506,10 +631,13 @@ static SOC_ENUM_SINGLE_DECL(max98090_adchp_enum,
max98090_pwr_perf_text);
static const struct snd_kcontrol_new max98090_snd_controls[] = {
- SOC_ENUM("MIC Bias VCM Bandgap", max98090_vcmbandgap_enum),
+ SOC_ENUM_EXT("MIC Bias VCM Bandgap", max98090_vcmbandgap_enum,
+ snd_soc_get_enum_double, max98090_put_enum_double),
- SOC_SINGLE("DMIC MIC Comp Filter Config", M98090_REG_DIGITAL_MIC_CONFIG,
- M98090_DMIC_COMP_SHIFT, M98090_DMIC_COMP_NUM - 1, 0),
+ SOC_SINGLE_EXT("DMIC MIC Comp Filter Config",
+ M98090_REG_DIGITAL_MIC_CONFIG,
+ M98090_DMIC_COMP_SHIFT, M98090_DMIC_COMP_NUM - 1, 0,
+ snd_soc_get_volsw, max98090_put_volsw),
SOC_SINGLE_EXT_TLV("MIC1 Boost Volume",
M98090_REG_MIC1_INPUT_LEVEL, M98090_MIC_PA1EN_SHIFT,
@@ -564,24 +692,34 @@ static const struct snd_kcontrol_new max98090_snd_controls[] = {
M98090_AVR_SHIFT, M98090_AVR_NUM - 1, 1,
max98090_av_tlv),
- SOC_ENUM("ADC Oversampling Rate", max98090_osr128_enum),
- SOC_SINGLE("ADC Quantizer Dither", M98090_REG_ADC_CONTROL,
- M98090_ADCDITHER_SHIFT, M98090_ADCDITHER_NUM - 1, 0),
- SOC_ENUM("ADC High Performance Mode", max98090_adchp_enum),
-
- SOC_SINGLE("DAC Mono Mode", M98090_REG_IO_CONFIGURATION,
- M98090_DMONO_SHIFT, M98090_DMONO_NUM - 1, 0),
- SOC_SINGLE("SDIN Mode", M98090_REG_IO_CONFIGURATION,
- M98090_SDIEN_SHIFT, M98090_SDIEN_NUM - 1, 0),
- SOC_SINGLE("SDOUT Mode", M98090_REG_IO_CONFIGURATION,
- M98090_SDOEN_SHIFT, M98090_SDOEN_NUM - 1, 0),
- SOC_SINGLE("SDOUT Hi-Z Mode", M98090_REG_IO_CONFIGURATION,
- M98090_HIZOFF_SHIFT, M98090_HIZOFF_NUM - 1, 1),
- SOC_ENUM("Filter Mode", max98090_mode_enum),
- SOC_SINGLE("Record Path DC Blocking", M98090_REG_FILTER_CONFIG,
- M98090_AHPF_SHIFT, M98090_AHPF_NUM - 1, 0),
- SOC_SINGLE("Playback Path DC Blocking", M98090_REG_FILTER_CONFIG,
- M98090_DHPF_SHIFT, M98090_DHPF_NUM - 1, 0),
+ SOC_ENUM_EXT("ADC Oversampling Rate", max98090_osr128_enum,
+ snd_soc_get_enum_double, max98090_put_enum_double),
+ SOC_SINGLE_EXT("ADC Quantizer Dither", M98090_REG_ADC_CONTROL,
+ M98090_ADCDITHER_SHIFT, M98090_ADCDITHER_NUM - 1, 0,
+ snd_soc_get_volsw, max98090_put_volsw),
+ SOC_ENUM_EXT("ADC High Performance Mode", max98090_adchp_enum,
+ snd_soc_get_enum_double, max98090_put_enum_double),
+
+ SOC_SINGLE_EXT("DAC Mono Mode", M98090_REG_IO_CONFIGURATION,
+ M98090_DMONO_SHIFT, M98090_DMONO_NUM - 1, 0,
+ snd_soc_get_volsw, max98090_put_volsw),
+ SOC_SINGLE_EXT("SDIN Mode", M98090_REG_IO_CONFIGURATION,
+ M98090_SDIEN_SHIFT, M98090_SDIEN_NUM - 1, 0,
+ snd_soc_get_volsw, max98090_put_volsw),
+ SOC_SINGLE_EXT("SDOUT Mode", M98090_REG_IO_CONFIGURATION,
+ M98090_SDOEN_SHIFT, M98090_SDOEN_NUM - 1, 0,
+ snd_soc_get_volsw, max98090_put_volsw),
+ SOC_SINGLE_EXT("SDOUT Hi-Z Mode", M98090_REG_IO_CONFIGURATION,
+ M98090_HIZOFF_SHIFT, M98090_HIZOFF_NUM - 1, 1,
+ snd_soc_get_volsw, max98090_put_volsw),
+ SOC_ENUM_EXT("Filter Mode", max98090_mode_enum,
+ snd_soc_get_enum_double, max98090_put_enum_double),
+ SOC_SINGLE_EXT("Record Path DC Blocking", M98090_REG_FILTER_CONFIG,
+ M98090_AHPF_SHIFT, M98090_AHPF_NUM - 1, 0,
+ snd_soc_get_volsw, max98090_put_volsw),
+ SOC_SINGLE_EXT("Playback Path DC Blocking", M98090_REG_FILTER_CONFIG,
+ M98090_DHPF_SHIFT, M98090_DHPF_NUM - 1, 0,
+ snd_soc_get_volsw, max98090_put_volsw),
SOC_SINGLE_TLV("Digital BQ Volume", M98090_REG_ADC_BIQUAD_LEVEL,
M98090_AVBQ_SHIFT, M98090_AVBQ_NUM - 1, 1, max98090_dv_tlv),
SOC_SINGLE_EXT_TLV("Digital Sidetone Volume",
@@ -594,13 +732,17 @@ static const struct snd_kcontrol_new max98090_snd_controls[] = {
SOC_SINGLE_TLV("Digital Volume", M98090_REG_DAI_PLAYBACK_LEVEL,
M98090_DV_SHIFT, M98090_DV_NUM - 1, 1,
max98090_dv_tlv),
- SND_SOC_BYTES("EQ Coefficients", M98090_REG_EQUALIZER_BASE, 105),
- SOC_SINGLE("Digital EQ 3 Band Switch", M98090_REG_DSP_FILTER_ENABLE,
- M98090_EQ3BANDEN_SHIFT, M98090_EQ3BANDEN_NUM - 1, 0),
- SOC_SINGLE("Digital EQ 5 Band Switch", M98090_REG_DSP_FILTER_ENABLE,
- M98090_EQ5BANDEN_SHIFT, M98090_EQ5BANDEN_NUM - 1, 0),
- SOC_SINGLE("Digital EQ 7 Band Switch", M98090_REG_DSP_FILTER_ENABLE,
- M98090_EQ7BANDEN_SHIFT, M98090_EQ7BANDEN_NUM - 1, 0),
+ SND_SOC_BYTES_E("EQ Coefficients", M98090_REG_EQUALIZER_BASE, 105,
+ snd_soc_bytes_get, max98090_bytes_put),
+ SOC_SINGLE_EXT("Digital EQ 3 Band Switch", M98090_REG_DSP_FILTER_ENABLE,
+ M98090_EQ3BANDEN_SHIFT, M98090_EQ3BANDEN_NUM - 1, 0,
+ snd_soc_get_volsw, max98090_put_volsw),
+ SOC_SINGLE_EXT("Digital EQ 5 Band Switch", M98090_REG_DSP_FILTER_ENABLE,
+ M98090_EQ5BANDEN_SHIFT, M98090_EQ5BANDEN_NUM - 1, 0,
+ snd_soc_get_volsw, max98090_put_volsw),
+ SOC_SINGLE_EXT("Digital EQ 7 Band Switch", M98090_REG_DSP_FILTER_ENABLE,
+ M98090_EQ7BANDEN_SHIFT, M98090_EQ7BANDEN_NUM - 1, 0,
+ snd_soc_get_volsw, max98090_put_volsw),
SOC_SINGLE("Digital EQ Clipping Detection", M98090_REG_DAI_PLAYBACK_LEVEL_EQ,
M98090_EQCLPN_SHIFT, M98090_EQCLPN_NUM - 1,
1),
@@ -608,25 +750,34 @@ static const struct snd_kcontrol_new max98090_snd_controls[] = {
M98090_DVEQ_SHIFT, M98090_DVEQ_NUM - 1, 1,
max98090_dv_tlv),
- SOC_SINGLE("ALC Enable", M98090_REG_DRC_TIMING,
- M98090_DRCEN_SHIFT, M98090_DRCEN_NUM - 1, 0),
- SOC_ENUM("ALC Attack Time", max98090_drcatk_enum),
- SOC_ENUM("ALC Release Time", max98090_drcrls_enum),
+ SOC_SINGLE_EXT("ALC Enable", M98090_REG_DRC_TIMING,
+ M98090_DRCEN_SHIFT, M98090_DRCEN_NUM - 1, 0,
+ snd_soc_get_volsw, max98090_put_volsw),
+ SOC_ENUM_EXT("ALC Attack Time", max98090_drcatk_enum,
+ snd_soc_get_enum_double, max98090_put_enum_double),
+ SOC_ENUM_EXT("ALC Release Time", max98090_drcrls_enum,
+ snd_soc_get_enum_double, max98090_put_enum_double),
SOC_SINGLE_TLV("ALC Make Up Volume", M98090_REG_DRC_GAIN,
M98090_DRCG_SHIFT, M98090_DRCG_NUM - 1, 0,
max98090_alcmakeup_tlv),
- SOC_ENUM("ALC Compression Ratio", max98090_alccmp_enum),
- SOC_ENUM("ALC Expansion Ratio", max98090_drcexp_enum),
- SOC_SINGLE_TLV("ALC Compression Threshold Volume",
+ SOC_ENUM_EXT("ALC Compression Ratio", max98090_alccmp_enum,
+ snd_soc_get_enum_double, max98090_put_enum_double),
+ SOC_ENUM_EXT("ALC Expansion Ratio", max98090_drcexp_enum,
+ snd_soc_get_enum_double, max98090_put_enum_double),
+ SOC_SINGLE_EXT_TLV("ALC Compression Threshold Volume",
M98090_REG_DRC_COMPRESSOR, M98090_DRCTHC_SHIFT,
- M98090_DRCTHC_NUM - 1, 1, max98090_alccomp_tlv),
- SOC_SINGLE_TLV("ALC Expansion Threshold Volume",
+ M98090_DRCTHC_NUM - 1, 1,
+ snd_soc_get_volsw, max98090_put_volsw, max98090_alccomp_tlv),
+ SOC_SINGLE_EXT_TLV("ALC Expansion Threshold Volume",
M98090_REG_DRC_EXPANDER, M98090_DRCTHE_SHIFT,
- M98090_DRCTHE_NUM - 1, 1, max98090_drcexp_tlv),
+ M98090_DRCTHE_NUM - 1, 1,
+ snd_soc_get_volsw, max98090_put_volsw, max98090_drcexp_tlv),
- SOC_ENUM("DAC HP Playback Performance Mode",
- max98090_dac_perfmode_enum),
- SOC_ENUM("DAC High Performance Mode", max98090_dachp_enum),
+ SOC_ENUM_EXT("DAC HP Playback Performance Mode",
+ max98090_dac_perfmode_enum,
+ snd_soc_get_enum_double, max98090_put_enum_double),
+ SOC_ENUM_EXT("DAC High Performance Mode", max98090_dachp_enum,
+ snd_soc_get_enum_double, max98090_put_enum_double),
SOC_SINGLE_TLV("Headphone Left Mixer Volume",
M98090_REG_HP_CONTROL, M98090_MIXHPLG_SHIFT,
@@ -684,9 +835,12 @@ static const struct snd_kcontrol_new max98090_snd_controls[] = {
SOC_SINGLE("Volume Adjustment Smoothing", M98090_REG_LEVEL_CONTROL,
M98090_VSENN_SHIFT, M98090_VSENN_NUM - 1, 1),
- SND_SOC_BYTES("Biquad Coefficients", M98090_REG_RECORD_BIQUAD_BASE, 15),
- SOC_SINGLE("Biquad Switch", M98090_REG_DSP_FILTER_ENABLE,
- M98090_ADCBQEN_SHIFT, M98090_ADCBQEN_NUM - 1, 0),
+ SND_SOC_BYTES_E("Biquad Coefficients",
+ M98090_REG_RECORD_BIQUAD_BASE, 15,
+ snd_soc_bytes_get, max98090_bytes_put),
+ SOC_SINGLE_EXT("Biquad Switch", M98090_REG_DSP_FILTER_ENABLE,
+ M98090_ADCBQEN_SHIFT, M98090_ADCBQEN_NUM - 1, 0,
+ snd_soc_get_volsw, max98090_put_volsw),
};
static const struct snd_kcontrol_new max98091_snd_controls[] = {
@@ -695,10 +849,12 @@ static const struct snd_kcontrol_new max98091_snd_controls[] = {
M98090_DMIC34_ZEROPAD_SHIFT,
M98090_DMIC34_ZEROPAD_NUM - 1, 0),
- SOC_ENUM("Filter DMIC34 Mode", max98090_filter_dmic34mode_enum),
- SOC_SINGLE("DMIC34 DC Blocking", M98090_REG_FILTER_CONFIG,
+ SOC_ENUM_EXT("Filter DMIC34 Mode", max98090_filter_dmic34mode_enum,
+ snd_soc_get_enum_double, max98090_put_enum_double),
+ SOC_SINGLE_EXT("DMIC34 DC Blocking", M98090_REG_FILTER_CONFIG,
M98090_FLT_DMIC34HPF_SHIFT,
- M98090_FLT_DMIC34HPF_NUM - 1, 0),
+ M98090_FLT_DMIC34HPF_NUM - 1, 0,
+ snd_soc_get_volsw, max98090_put_volsw),
SOC_SINGLE_TLV("DMIC3 Boost Volume", M98090_REG_DMIC3_VOLUME,
M98090_DMIC_AV3G_SHIFT, M98090_DMIC_AV3G_NUM - 1, 0,
@@ -716,8 +872,9 @@ static const struct snd_kcontrol_new max98091_snd_controls[] = {
SND_SOC_BYTES("DMIC34 Biquad Coefficients",
M98090_REG_DMIC34_BIQUAD_BASE, 15),
- SOC_SINGLE("DMIC34 Biquad Switch", M98090_REG_DSP_FILTER_ENABLE,
- M98090_DMIC34BQEN_SHIFT, M98090_DMIC34BQEN_NUM - 1, 0),
+ SOC_SINGLE_EXT("DMIC34 Biquad Switch", M98090_REG_DSP_FILTER_ENABLE,
+ M98090_DMIC34BQEN_SHIFT, M98090_DMIC34BQEN_NUM - 1, 0,
+ snd_soc_get_volsw, max98090_put_volsw),
SOC_SINGLE_TLV("DMIC34 BQ PreAttenuation Volume",
M98090_REG_DMIC34_BQ_PREATTEN, M98090_AV34BQ_SHIFT,
@@ -771,19 +928,6 @@ static int max98090_micinput_event(struct snd_soc_dapm_widget *w,
return 0;
}
-static int max98090_shdn_event(struct snd_soc_dapm_widget *w,
- struct snd_kcontrol *kcontrol, int event)
-{
- struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
- struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component);
-
- if (event & SND_SOC_DAPM_POST_PMU)
- max98090->shdn_pending = true;
-
- return 0;
-
-}
-
static const char *mic1_mux_text[] = { "IN12", "IN56" };
static SOC_ENUM_SINGLE_DECL(mic1_mux_enum,
@@ -884,10 +1028,14 @@ static SOC_ENUM_SINGLE_DECL(ltenr_mux_enum,
lten_mux_text);
static const struct snd_kcontrol_new max98090_ltenl_mux =
- SOC_DAPM_ENUM("LTENL Mux", ltenl_mux_enum);
+ SOC_DAPM_ENUM_EXT("LTENL Mux", ltenl_mux_enum,
+ snd_soc_dapm_get_enum_double,
+ max98090_dapm_put_enum_double);
static const struct snd_kcontrol_new max98090_ltenr_mux =
- SOC_DAPM_ENUM("LTENR Mux", ltenr_mux_enum);
+ SOC_DAPM_ENUM_EXT("LTENR Mux", ltenr_mux_enum,
+ snd_soc_dapm_get_enum_double,
+ max98090_dapm_put_enum_double);
static const char *lben_mux_text[] = { "Normal", "Loopback" };
@@ -902,10 +1050,14 @@ static SOC_ENUM_SINGLE_DECL(lbenr_mux_enum,
lben_mux_text);
static const struct snd_kcontrol_new max98090_lbenl_mux =
- SOC_DAPM_ENUM("LBENL Mux", lbenl_mux_enum);
+ SOC_DAPM_ENUM_EXT("LBENL Mux", lbenl_mux_enum,
+ snd_soc_dapm_get_enum_double,
+ max98090_dapm_put_enum_double);
static const struct snd_kcontrol_new max98090_lbenr_mux =
- SOC_DAPM_ENUM("LBENR Mux", lbenr_mux_enum);
+ SOC_DAPM_ENUM_EXT("LBENR Mux", lbenr_mux_enum,
+ snd_soc_dapm_get_enum_double,
+ max98090_dapm_put_enum_double);
static const char *stenl_mux_text[] = { "Normal", "Sidetone Left" };
@@ -1072,21 +1224,25 @@ static const struct snd_soc_dapm_widget max98090_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("IN56"),
SND_SOC_DAPM_SUPPLY("MICBIAS", M98090_REG_INPUT_ENABLE,
- M98090_MBEN_SHIFT, 0, NULL, 0),
+ M98090_MBEN_SHIFT, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
SND_SOC_DAPM_SUPPLY("SHDN", M98090_REG_DEVICE_SHUTDOWN,
M98090_SHDNN_SHIFT, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("SDIEN", M98090_REG_IO_CONFIGURATION,
- M98090_SDIEN_SHIFT, 0, NULL, 0),
+ M98090_SDIEN_SHIFT, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
SND_SOC_DAPM_SUPPLY("SDOEN", M98090_REG_IO_CONFIGURATION,
- M98090_SDOEN_SHIFT, 0, NULL, 0),
+ M98090_SDOEN_SHIFT, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
SND_SOC_DAPM_SUPPLY("DMICL_ENA", M98090_REG_DIGITAL_MIC_ENABLE,
- M98090_DIGMICL_SHIFT, 0, max98090_shdn_event,
- SND_SOC_DAPM_POST_PMU),
+ M98090_DIGMICL_SHIFT, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
SND_SOC_DAPM_SUPPLY("DMICR_ENA", M98090_REG_DIGITAL_MIC_ENABLE,
- M98090_DIGMICR_SHIFT, 0, max98090_shdn_event,
- SND_SOC_DAPM_POST_PMU),
+ M98090_DIGMICR_SHIFT, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
SND_SOC_DAPM_SUPPLY("AHPF", M98090_REG_FILTER_CONFIG,
- M98090_AHPF_SHIFT, 0, NULL, 0),
+ M98090_AHPF_SHIFT, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
/*
* Note: Sysclk and misc power supplies are taken care of by SHDN
@@ -1116,10 +1272,12 @@ static const struct snd_soc_dapm_widget max98090_dapm_widgets[] = {
&max98090_lineb_mixer_controls[0],
ARRAY_SIZE(max98090_lineb_mixer_controls)),
- SND_SOC_DAPM_PGA("LINEA Input", M98090_REG_INPUT_ENABLE,
- M98090_LINEAEN_SHIFT, 0, NULL, 0),
- SND_SOC_DAPM_PGA("LINEB Input", M98090_REG_INPUT_ENABLE,
- M98090_LINEBEN_SHIFT, 0, NULL, 0),
+ SND_SOC_DAPM_PGA_E("LINEA Input", M98090_REG_INPUT_ENABLE,
+ M98090_LINEAEN_SHIFT, 0, NULL, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
+ SND_SOC_DAPM_PGA_E("LINEB Input", M98090_REG_INPUT_ENABLE,
+ M98090_LINEBEN_SHIFT, 0, NULL, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
SND_SOC_DAPM_MIXER("Left ADC Mixer", SND_SOC_NOPM, 0, 0,
&max98090_left_adc_mixer_controls[0],
@@ -1130,11 +1288,11 @@ static const struct snd_soc_dapm_widget max98090_dapm_widgets[] = {
ARRAY_SIZE(max98090_right_adc_mixer_controls)),
SND_SOC_DAPM_ADC_E("ADCL", NULL, M98090_REG_INPUT_ENABLE,
- M98090_ADLEN_SHIFT, 0, max98090_shdn_event,
- SND_SOC_DAPM_POST_PMU),
+ M98090_ADLEN_SHIFT, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
SND_SOC_DAPM_ADC_E("ADCR", NULL, M98090_REG_INPUT_ENABLE,
- M98090_ADREN_SHIFT, 0, max98090_shdn_event,
- SND_SOC_DAPM_POST_PMU),
+ M98090_ADREN_SHIFT, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
SND_SOC_DAPM_AIF_OUT("AIFOUTL", "HiFi Capture", 0,
SND_SOC_NOPM, 0, 0),
@@ -1162,10 +1320,12 @@ static const struct snd_soc_dapm_widget max98090_dapm_widgets[] = {
SND_SOC_DAPM_AIF_IN("AIFINL", "HiFi Playback", 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("AIFINR", "HiFi Playback", 1, SND_SOC_NOPM, 0, 0),
- SND_SOC_DAPM_DAC("DACL", NULL, M98090_REG_OUTPUT_ENABLE,
- M98090_DALEN_SHIFT, 0),
- SND_SOC_DAPM_DAC("DACR", NULL, M98090_REG_OUTPUT_ENABLE,
- M98090_DAREN_SHIFT, 0),
+ SND_SOC_DAPM_DAC_E("DACL", NULL, M98090_REG_OUTPUT_ENABLE,
+ M98090_DALEN_SHIFT, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
+ SND_SOC_DAPM_DAC_E("DACR", NULL, M98090_REG_OUTPUT_ENABLE,
+ M98090_DAREN_SHIFT, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
SND_SOC_DAPM_MIXER("Left Headphone Mixer", SND_SOC_NOPM, 0, 0,
&max98090_left_hp_mixer_controls[0],
@@ -1200,20 +1360,26 @@ static const struct snd_soc_dapm_widget max98090_dapm_widgets[] = {
SND_SOC_DAPM_MUX("MIXHPRSEL Mux", SND_SOC_NOPM, 0, 0,
&max98090_mixhprsel_mux),
- SND_SOC_DAPM_PGA("HP Left Out", M98090_REG_OUTPUT_ENABLE,
- M98090_HPLEN_SHIFT, 0, NULL, 0),
- SND_SOC_DAPM_PGA("HP Right Out", M98090_REG_OUTPUT_ENABLE,
- M98090_HPREN_SHIFT, 0, NULL, 0),
-
- SND_SOC_DAPM_PGA("SPK Left Out", M98090_REG_OUTPUT_ENABLE,
- M98090_SPLEN_SHIFT, 0, NULL, 0),
- SND_SOC_DAPM_PGA("SPK Right Out", M98090_REG_OUTPUT_ENABLE,
- M98090_SPREN_SHIFT, 0, NULL, 0),
-
- SND_SOC_DAPM_PGA("RCV Left Out", M98090_REG_OUTPUT_ENABLE,
- M98090_RCVLEN_SHIFT, 0, NULL, 0),
- SND_SOC_DAPM_PGA("RCV Right Out", M98090_REG_OUTPUT_ENABLE,
- M98090_RCVREN_SHIFT, 0, NULL, 0),
+ SND_SOC_DAPM_PGA_E("HP Left Out", M98090_REG_OUTPUT_ENABLE,
+ M98090_HPLEN_SHIFT, 0, NULL, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
+ SND_SOC_DAPM_PGA_E("HP Right Out", M98090_REG_OUTPUT_ENABLE,
+ M98090_HPREN_SHIFT, 0, NULL, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
+
+ SND_SOC_DAPM_PGA_E("SPK Left Out", M98090_REG_OUTPUT_ENABLE,
+ M98090_SPLEN_SHIFT, 0, NULL, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
+ SND_SOC_DAPM_PGA_E("SPK Right Out", M98090_REG_OUTPUT_ENABLE,
+ M98090_SPREN_SHIFT, 0, NULL, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
+
+ SND_SOC_DAPM_PGA_E("RCV Left Out", M98090_REG_OUTPUT_ENABLE,
+ M98090_RCVLEN_SHIFT, 0, NULL, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
+ SND_SOC_DAPM_PGA_E("RCV Right Out", M98090_REG_OUTPUT_ENABLE,
+ M98090_RCVREN_SHIFT, 0, NULL, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
SND_SOC_DAPM_OUTPUT("HPL"),
SND_SOC_DAPM_OUTPUT("HPR"),
@@ -1228,9 +1394,11 @@ static const struct snd_soc_dapm_widget max98091_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("DMIC4"),
SND_SOC_DAPM_SUPPLY("DMIC3_ENA", M98090_REG_DIGITAL_MIC_ENABLE,
- M98090_DIGMIC3_SHIFT, 0, NULL, 0),
+ M98090_DIGMIC3_SHIFT, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
SND_SOC_DAPM_SUPPLY("DMIC4_ENA", M98090_REG_DIGITAL_MIC_ENABLE,
- M98090_DIGMIC4_SHIFT, 0, NULL, 0),
+ M98090_DIGMIC4_SHIFT, 0, max98090_dapm_event,
+ SND_SOC_DAPM_PRE_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD),
};
static const struct snd_soc_dapm_route max98090_dapm_routes[] = {
@@ -1501,6 +1669,11 @@ static void max98090_configure_bclk(struct snd_soc_component *component)
return;
}
+ /*
+ * Master mode: no need to save and restore SHDN for the following
+ * sensitive registers.
+ */
+
/* Check for supported PCLK to LRCLK ratios */
for (i = 0; i < ARRAY_SIZE(pclk_rates); i++) {
if ((pclk_rates[i] == max98090->sysclk) &&
@@ -1587,12 +1760,14 @@ static int max98090_dai_set_fmt(struct snd_soc_dai *codec_dai,
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS:
/* Set to slave mode PLL - MAS mode off */
+ max98090_shdn_save(max98090);
snd_soc_component_write(component,
M98090_REG_CLOCK_RATIO_NI_MSB, 0x00);
snd_soc_component_write(component,
M98090_REG_CLOCK_RATIO_NI_LSB, 0x00);
snd_soc_component_update_bits(component, M98090_REG_CLOCK_MODE,
M98090_USE_M1_MASK, 0);
+ max98090_shdn_restore(max98090);
max98090->master = false;
break;
case SND_SOC_DAIFMT_CBM_CFM:
@@ -1618,7 +1793,9 @@ static int max98090_dai_set_fmt(struct snd_soc_dai *codec_dai,
dev_err(component->dev, "DAI clock mode unsupported");
return -EINVAL;
}
+ max98090_shdn_save(max98090);
snd_soc_component_write(component, M98090_REG_MASTER_MODE, regval);
+ max98090_shdn_restore(max98090);
regval = 0;
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
@@ -1663,8 +1840,10 @@ static int max98090_dai_set_fmt(struct snd_soc_dai *codec_dai,
if (max98090->tdm_slots > 1)
regval ^= M98090_BCI_MASK;
+ max98090_shdn_save(max98090);
snd_soc_component_write(component,
M98090_REG_INTERFACE_FORMAT, regval);
+ max98090_shdn_restore(max98090);
}
return 0;
@@ -1676,6 +1855,7 @@ static int max98090_set_tdm_slot(struct snd_soc_dai *codec_dai,
struct snd_soc_component *component = codec_dai->component;
struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component);
struct max98090_cdata *cdata;
+
cdata = &max98090->dai[0];
if (slots < 0 || slots > 4)
@@ -1685,6 +1865,7 @@ static int max98090_set_tdm_slot(struct snd_soc_dai *codec_dai,
max98090->tdm_width = slot_width;
if (max98090->tdm_slots > 1) {
+ max98090_shdn_save(max98090);
/* SLOTL SLOTR SLOTDLY */
snd_soc_component_write(component, M98090_REG_TDM_FORMAT,
0 << M98090_TDM_SLOTL_SHIFT |
@@ -1695,6 +1876,7 @@ static int max98090_set_tdm_slot(struct snd_soc_dai *codec_dai,
snd_soc_component_update_bits(component, M98090_REG_TDM_CONTROL,
M98090_TDM_MASK,
M98090_TDM_MASK);
+ max98090_shdn_restore(max98090);
}
/*
@@ -1894,6 +2076,7 @@ static int max98090_configure_dmic(struct max98090_priv *max98090,
dmic_freq = dmic_table[pclk_index].settings[micclk_index].freq;
dmic_comp = dmic_table[pclk_index].settings[micclk_index].comp[i];
+ max98090_shdn_save(max98090);
regmap_update_bits(max98090->regmap, M98090_REG_DIGITAL_MIC_ENABLE,
M98090_MICCLK_MASK,
micclk_index << M98090_MICCLK_SHIFT);
@@ -1902,6 +2085,7 @@ static int max98090_configure_dmic(struct max98090_priv *max98090,
M98090_DMIC_COMP_MASK | M98090_DMIC_FREQ_MASK,
dmic_comp << M98090_DMIC_COMP_SHIFT |
dmic_freq << M98090_DMIC_FREQ_SHIFT);
+ max98090_shdn_restore(max98090);
return 0;
}
@@ -1938,8 +2122,10 @@ static int max98090_dai_hw_params(struct snd_pcm_substream *substream,
switch (params_width(params)) {
case 16:
+ max98090_shdn_save(max98090);
snd_soc_component_update_bits(component, M98090_REG_INTERFACE_FORMAT,
M98090_WS_MASK, 0);
+ max98090_shdn_restore(max98090);
break;
default:
return -EINVAL;
@@ -1950,6 +2136,7 @@ static int max98090_dai_hw_params(struct snd_pcm_substream *substream,
cdata->rate = max98090->lrclk;
+ max98090_shdn_save(max98090);
/* Update filter mode */
if (max98090->lrclk < 24000)
snd_soc_component_update_bits(component, M98090_REG_FILTER_CONFIG,
@@ -1965,6 +2152,7 @@ static int max98090_dai_hw_params(struct snd_pcm_substream *substream,
else
snd_soc_component_update_bits(component, M98090_REG_FILTER_CONFIG,
M98090_DHF_MASK, M98090_DHF_MASK);
+ max98090_shdn_restore(max98090);
max98090_configure_dmic(max98090, max98090->dmic_freq, max98090->pclk,
max98090->lrclk);
@@ -1995,6 +2183,7 @@ static int max98090_dai_set_sysclk(struct snd_soc_dai *dai,
* 0x02 (when master clk is 20MHz to 40MHz)..
* 0x03 (when master clk is 40MHz to 60MHz)..
*/
+ max98090_shdn_save(max98090);
if ((freq >= 10000000) && (freq <= 20000000)) {
snd_soc_component_write(component, M98090_REG_SYSTEM_CLOCK,
M98090_PSCLK_DIV1);
@@ -2009,8 +2198,10 @@ static int max98090_dai_set_sysclk(struct snd_soc_dai *dai,
max98090->pclk = freq >> 2;
} else {
dev_err(component->dev, "Invalid master clock frequency\n");
+ max98090_shdn_restore(max98090);
return -EINVAL;
}
+ max98090_shdn_restore(max98090);
max98090->sysclk = freq;
@@ -2122,10 +2313,12 @@ static void max98090_pll_work(struct max98090_priv *max98090)
*/
/* Toggle shutdown OFF then ON */
+ mutex_lock(&component->card->dapm_mutex);
snd_soc_component_update_bits(component, M98090_REG_DEVICE_SHUTDOWN,
M98090_SHDNN_MASK, 0);
snd_soc_component_update_bits(component, M98090_REG_DEVICE_SHUTDOWN,
M98090_SHDNN_MASK, M98090_SHDNN_MASK);
+ mutex_unlock(&component->card->dapm_mutex);
for (i = 0; i < 10; ++i) {
/* Give PLL time to lock */
@@ -2448,7 +2641,12 @@ static int max98090_probe(struct snd_soc_component *component)
*/
snd_soc_component_read32(component, M98090_REG_DEVICE_STATUS);
- /* High Performance is default */
+ /*
+ * SHDN should be 0 at the point, no need to save/restore for the
+ * following registers.
+ *
+ * High Performance is default
+ */
snd_soc_component_update_bits(component, M98090_REG_DAC_CONTROL,
M98090_DACHP_MASK,
1 << M98090_DACHP_SHIFT);
@@ -2459,7 +2657,12 @@ static int max98090_probe(struct snd_soc_component *component)
M98090_ADCHP_MASK,
1 << M98090_ADCHP_SHIFT);
- /* Turn on VCM bandgap reference */
+ /*
+ * SHDN should be 0 at the point, no need to save/restore for the
+ * following registers.
+ *
+ * Turn on VCM bandgap reference
+ */
snd_soc_component_write(component, M98090_REG_BIAS_CONTROL,
M98090_VCM_MODE_MASK);
@@ -2491,25 +2694,9 @@ static void max98090_remove(struct snd_soc_component *component)
max98090->component = NULL;
}
-static void max98090_seq_notifier(struct snd_soc_component *component,
- enum snd_soc_dapm_type event, int subseq)
-{
- struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component);
-
- if (max98090->shdn_pending) {
- snd_soc_component_update_bits(component, M98090_REG_DEVICE_SHUTDOWN,
- M98090_SHDNN_MASK, 0);
- msleep(40);
- snd_soc_component_update_bits(component, M98090_REG_DEVICE_SHUTDOWN,
- M98090_SHDNN_MASK, M98090_SHDNN_MASK);
- max98090->shdn_pending = false;
- }
-}
-
static const struct snd_soc_component_driver soc_component_dev_max98090 = {
.probe = max98090_probe,
.remove = max98090_remove,
- .seq_notifier = max98090_seq_notifier,
.set_bias_level = max98090_set_bias_level,
.idle_bias_on = 1,
.use_pmdown_time = 1,
diff --git a/sound/soc/codecs/max98090.h b/sound/soc/codecs/max98090.h
index a197114b0dad..0a31708b7df7 100644
--- a/sound/soc/codecs/max98090.h
+++ b/sound/soc/codecs/max98090.h
@@ -1539,7 +1539,8 @@ struct max98090_priv {
unsigned int pa2en;
unsigned int sidetone;
bool master;
- bool shdn_pending;
+ int saved_count;
+ int saved_shdn;
};
int max98090_mic_detect(struct snd_soc_component *component,
--
2.24.0.432.g9d3f5f5b63-goog
3
20
alsa-project/alsa-lib pull request #21 was opened from cujomalainey:
v2 changes
Dropped Jack* as it looks like we may need to do some renaming internally.
Dropped Gain controls as we are adjusting our definitions.
Dropped disable software volume per comments and will look at removing internally
Notes:
Per request regarding CaptureChannelMap, is it work changing the format if the current format transfers directly to alsa plugin inputs?
Apologies for the multi month delay between versions
Request URL : https://github.com/alsa-project/alsa-lib/pull/21
Patch URL : https://github.com/alsa-project/alsa-lib/pull/21.patch
Repository URL: https://github.com/alsa-project/alsa-lib
1
0

09 Jan '20
From: Jeff Chang <jeff_chang(a)richtek.com>
The MT6660 is a boosted BTL class-D amplifier with V/I sensing.
A built-in DC-DC step-up converter is used to provide efficient
power for class-D amplifier with multi-level class-G operation.
The digital audio interface supports I2S, left-justified,
right-justified, TDM and DSP A/B format for audio in with a data
out used for chip information like voltage sense and current
sense, which are able to be monitored via DATAO through proper
Signed-off-by: Jeff Chang <jeff_chang(a)richtek.com>
---
Documentation/devicetree/bindings/sound/mt6660.txt | 53 ++
sound/soc/codecs/Kconfig | 11 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/mt6660.c | 628 +++++++++++++++++++++
sound/soc/codecs/mt6660.h | 75 +++
5 files changed, 769 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/mt6660.txt
create mode 100644 sound/soc/codecs/mt6660.c
create mode 100644 sound/soc/codecs/mt6660.h
changelogs between v4 & v3
- remove unnecessary kcontrols.
- modify copy right header.
- use dev_dbg instead of dev_info.
- add necessary debug message.
- add DT binding documentation.
- add space before } at every table.
changelogs between v3 & v2
- modify MT6660 Kconfig, remove unnecessary selection.
- remove my own debug io interface. use standard regmap for debugging.
- remove regmap volatile ops, we do not use cache.
- remove component io read/write function, use snd_soc_component_init_regmap.
- remove init setting write code. Using parsing dts to set them.
- remove unnecessary pr_info log message.
- remove mt6660_component_put_volsw. Using snd_soc_get_volsw
diff --git a/Documentation/devicetree/bindings/sound/mt6660.txt b/Documentation/devicetree/bindings/sound/mt6660.txt
new file mode 100644
index 0000000..2a1736b
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/mt6660.txt
@@ -0,0 +1,53 @@
+MT6660 MediaTek Speaker Amplifier
+
+This device supports I2C mode only.
+
+Required properties:
+
+ - compatible : "mediatek,mt6660"
+
+ - reg : The I2C slave address
+
+Optional properties:
+
+ - rt,init_setting_num : The initial register setting element number.
+
+ - rt,init_setting_addr : the addreses array for INIT Setting table.
+
+ - rt,init_setting_mask : the mask array for INIT Setting table.
+
+ - rt,init_setting_val : the value array for INIT Setting table.
+
+Example:
+
+ mt6660@34 {
+ status = "ok";
+ compatible = "mediatek,mt6660";
+ reg = <0x34>;
+ rt,init_setting_num = <26>;
+ rt,init_setting_addr =
+ <0x20 0x30 0x50 0xB1
+ 0xD3 0xE0 0x98 0xB9
+ 0xB7 0xB6 0x6B 0x07
+ 0xBB 0x69 0xBD 0x70
+ 0x7C 0x46 0x1A 0x1B
+ 0x51 0xA2 0x33 0x4C
+ 0x15 0x68>;
+ rt,init_setting_mask =
+ <0x80 0x01 0x1c 0x0c
+ 0x03 0x01 0x44 0xff
+ 0x7777 0x07 0xe0 0xff
+ 0xff 0xff 0xffff 0xff
+ 0xff 0xff 0xffffffff 0xffffffff
+ 0xff 0xff 0xffff 0xffff
+ 0x1800 0x1f>;
+ rt,init_setting_val =
+ <0x00 0x00 0x04 0x00
+ 0x03 0x00 0x04 0x82
+ 0x7273 0x03 0x20 0x70
+ 0x20 0x40 0x17f8 0x15
+ 0x00 0x1d 0x7fdb7ffe 0x7fdb7ffe
+ 0x58 0xce 0x7fff 0x0116
+ 0x0800 0x07>;
+ };
+
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 229cc89..f135fbb 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -122,6 +122,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_ML26124 if I2C
select SND_SOC_MT6351 if MTK_PMIC_WRAP
select SND_SOC_MT6358 if MTK_PMIC_WRAP
+ select SND_SOC_MT6660 if I2C
select SND_SOC_NAU8540 if I2C
select SND_SOC_NAU8810 if I2C
select SND_SOC_NAU8822 if I2C
@@ -1465,6 +1466,16 @@ config SND_SOC_MT6358
Enable support for the platform which uses MT6358 as
external codec device.
+config SND_SOC_MT6660
+ tristate "Mediatek MT6660 Speaker Amplifier"
+ depends on I2C
+ help
+ MediaTek MT6660 is a smart power amplifier which contain
+ speaker protection, multi-band DRC, equalizer functions.
+ Select N if you don't have MT6660 on board.
+ Select M to build this as module.
+
+
config SND_SOC_NAU8540
tristate "Nuvoton Technology Corporation NAU85L40 CODEC"
depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index c498373..2b6814c 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -119,6 +119,7 @@ snd-soc-msm8916-analog-objs := msm8916-wcd-analog.o
snd-soc-msm8916-digital-objs := msm8916-wcd-digital.o
snd-soc-mt6351-objs := mt6351.o
snd-soc-mt6358-objs := mt6358.o
+snd-soc-mt6660-objs := mt6660.o
snd-soc-nau8540-objs := nau8540.o
snd-soc-nau8810-objs := nau8810.o
snd-soc-nau8822-objs := nau8822.o
@@ -403,6 +404,7 @@ obj-$(CONFIG_SND_SOC_MSM8916_WCD_ANALOG) +=snd-soc-msm8916-analog.o
obj-$(CONFIG_SND_SOC_MSM8916_WCD_DIGITAL) +=snd-soc-msm8916-digital.o
obj-$(CONFIG_SND_SOC_MT6351) += snd-soc-mt6351.o
obj-$(CONFIG_SND_SOC_MT6358) += snd-soc-mt6358.o
+obj-$(CONFIG_SND_SOC_MT6660) += snd-soc-mt6660.o
obj-$(CONFIG_SND_SOC_NAU8540) += snd-soc-nau8540.o
obj-$(CONFIG_SND_SOC_NAU8810) += snd-soc-nau8810.o
obj-$(CONFIG_SND_SOC_NAU8822) += snd-soc-nau8822.o
diff --git a/sound/soc/codecs/mt6660.c b/sound/soc/codecs/mt6660.c
new file mode 100644
index 0000000..b8fc53b
--- /dev/null
+++ b/sound/soc/codecs/mt6660.c
@@ -0,0 +1,628 @@
+// SPDX-License-Identifier: GPL-2.0 //
+
+// Copyright (c) 2019 MediaTek Inc.
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/pm_runtime.h>
+#include <linux/delay.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/pcm_params.h>
+#include <linux/debugfs.h>
+
+#include "mt6660.h"
+
+struct codec_reg_val {
+ u32 addr;
+ u32 mask;
+ u32 data;
+};
+
+struct reg_size_table {
+ u32 addr;
+ u8 size;
+};
+
+static const struct reg_size_table mt6660_reg_size_table[] = {
+ { MT6660_REG_HPF1_COEF, 4 },
+ { MT6660_REG_HPF2_COEF, 4 },
+ { MT6660_REG_TDM_CFG3, 2 },
+ { MT6660_REG_RESV17, 2 },
+ { MT6660_REG_RESV23, 2 },
+ { MT6660_REG_SIGMAX, 2 },
+ { MT6660_REG_DEVID, 2 },
+ { MT6660_REG_HCLIP_CTRL, 2 },
+ { MT6660_REG_DA_GAIN, 2 },
+};
+
+static int mt6660_get_reg_size(uint32_t addr)
+{
+ int i = 0;
+
+ for (i = 0; i < ARRAY_SIZE(mt6660_reg_size_table); i++) {
+ if (mt6660_reg_size_table[i].addr == addr)
+ return mt6660_reg_size_table[i].size;
+ }
+ return 1;
+}
+
+static int mt6660_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct mt6660_chip *chip = context;
+ int size = mt6660_get_reg_size(reg);
+ u8 reg_data[4] = {0};
+ int i = 0, ret = 0;
+
+ for (i = 0; i < size; i++)
+ reg_data[size - i - 1] = (val >> (8 * i)) & 0xff;
+
+ ret = i2c_smbus_write_i2c_block_data(chip->i2c, reg, size, reg_data);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static int mt6660_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct mt6660_chip *chip = context;
+ int size = mt6660_get_reg_size(reg);
+ int i = 0, ret = 0;
+ u8 data[4] = {0};
+ u32 reg_data = 0;
+
+ ret = i2c_smbus_read_i2c_block_data(chip->i2c, reg, size, data);
+ if (ret < 0)
+ return ret;
+ for (i = 0; i < size; i++) {
+ reg_data <<= 8;
+ reg_data |= data[i];
+ }
+ *val = reg_data;
+ return 0;
+}
+
+static struct regmap_config mt6660_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 32,
+ .reg_write = mt6660_reg_write,
+ .reg_read = mt6660_reg_read,
+};
+
+static int mt6660_codec_dac_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ usleep_range(1000, 1100);
+ break;
+ }
+ return 0;
+}
+
+static int mt6660_codec_classd_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *component =
+ snd_soc_dapm_to_component(w->dapm);
+ int ret = 0;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ dev_dbg(component->dev,
+ "%s: before classd turn on\n", __func__);
+ /* config to adaptive mode */
+ ret = snd_soc_component_update_bits(component,
+ MT6660_REG_BST_CTRL, 0x03, 0x03);
+ if (ret < 0) {
+ dev_err(component->dev, "config mode adaptive fail\n");
+ return ret;
+ }
+ break;
+ case SND_SOC_DAPM_POST_PMU:
+ /* voltage sensing enable */
+ ret = snd_soc_component_update_bits(component,
+ MT6660_REG_RESV7, 0x04, 0x04);
+ if (ret < 0) {
+ dev_err(component->dev,
+ "enable voltage sensing fail\n");
+ return ret;
+ }
+ dev_dbg(component->dev, "Amp on\n");
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ dev_dbg(component->dev, "Amp off\n");
+ /* voltage sensing disable */
+ ret = snd_soc_component_update_bits(component,
+ MT6660_REG_RESV7, 0x04, 0x00);
+ if (ret < 0) {
+ dev_err(component->dev,
+ "disable voltage sensing fail\n");
+ return ret;
+ }
+ /* pop-noise improvement 1 */
+ ret = snd_soc_component_update_bits(component,
+ MT6660_REG_RESV10, 0x10, 0x10);
+ if (ret < 0) {
+ dev_err(component->dev,
+ "pop-noise improvement 1 fail\n");
+ return ret;
+ }
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ dev_dbg(component->dev,
+ "%s: after classd turn off\n", __func__);
+ /* pop-noise improvement 2 */
+ ret = snd_soc_component_update_bits(component,
+ MT6660_REG_RESV10, 0x10, 0x00);
+ if (ret < 0) {
+ dev_err(component->dev,
+ "pop-noise improvement 2 fail\n");
+ return ret;
+ }
+ /* config to off mode */
+ ret = snd_soc_component_update_bits(component,
+ MT6660_REG_BST_CTRL, 0x03, 0x00);
+ if (ret < 0) {
+ dev_err(component->dev, "config mode off fail\n");
+ return ret;
+ }
+ break;
+ }
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget mt6660_component_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC_E("DAC", NULL, MT6660_REG_PLL_CFG1,
+ 0, 1, mt6660_codec_dac_event, SND_SOC_DAPM_POST_PMU),
+ SND_SOC_DAPM_ADC("VI ADC", NULL, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_PGA("PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_OUT_DRV_E("ClassD", MT6660_REG_SYSTEM_CTRL, 2, 0,
+ NULL, 0, mt6660_codec_classd_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_OUTPUT("OUTP"),
+ SND_SOC_DAPM_OUTPUT("OUTN"),
+};
+
+static const struct snd_soc_dapm_route mt6660_component_dapm_routes[] = {
+ { "DAC", NULL, "aif_playback" },
+ { "PGA", NULL, "DAC" },
+ { "ClassD", NULL, "PGA" },
+ { "OUTP", NULL, "ClassD" },
+ { "OUTN", NULL, "ClassD" },
+ { "VI ADC", NULL, "ClassD" },
+ { "aif_capture", NULL, "VI ADC" },
+};
+
+static int mt6660_component_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct mt6660_chip *chip = (struct mt6660_chip *)
+ snd_soc_component_get_drvdata(component);
+ int ret = -EINVAL;
+
+ if (!strcmp(kcontrol->id.name, "Chip Rev")) {
+ ucontrol->value.integer.value[0] = chip->chip_rev & 0x0f;
+ ret = 0;
+ }
+ return ret;
+}
+
+static const DECLARE_TLV_DB_SCALE(vol_ctl_tlv, -1155, 5, 0);
+
+static const struct snd_kcontrol_new mt6660_component_snd_controls[] = {
+ SOC_SINGLE_TLV("Digital Volume", MT6660_REG_VOL_CTRL, 0, 255,
+ 1, vol_ctl_tlv),
+ SOC_SINGLE("Hard Clip Switch", MT6660_REG_HCLIP_CTRL, 8, 1, 0),
+ SOC_SINGLE("Clip Switch", MT6660_REG_SPS_CTRL, 0, 1, 0),
+ SOC_SINGLE("Boost Mode", MT6660_REG_BST_CTRL, 0, 3, 0),
+ SOC_SINGLE("DRE Switch", MT6660_REG_DRE_CTRL, 0, 1, 0),
+ SOC_SINGLE("DC Protect Switch", MT6660_REG_DC_PROTECT_CTRL, 3, 1, 0),
+ SOC_SINGLE("Data Output Left Channel Selection",
+ MT6660_REG_DATAO_SEL, 3, 7, 0),
+ SOC_SINGLE("Data Output Right Channel Selection",
+ MT6660_REG_DATAO_SEL, 0, 7, 0),
+ SOC_SINGLE_EXT("T0 SEL", MT6660_REG_CALI_T0, 0, 7, 0,
+ snd_soc_get_volsw, NULL),
+ SOC_SINGLE_EXT("Chip Rev", MT6660_REG_DEVID, 8, 15, 0,
+ mt6660_component_get_volsw, NULL),
+};
+
+static inline int _mt6660_chip_power_on(struct mt6660_chip *chip, int on_off)
+{
+ u8 reg_data = 0;
+ int ret = 0;
+
+ ret = i2c_smbus_read_byte_data(chip->i2c, MT6660_REG_SYSTEM_CTRL);
+ if (ret < 0)
+ return ret;
+ reg_data = (u8)ret;
+ if (on_off)
+ reg_data &= (~0x01);
+ else
+ reg_data |= 0x01;
+ return regmap_write(chip->regmap, MT6660_REG_SYSTEM_CTRL, reg_data);
+}
+
+static int mt6660_apply_plat_data(struct mt6660_chip *chip,
+ struct snd_soc_component *component)
+{
+ size_t i = 0;
+ int num = chip->plat_data.init_setting_num;
+ int ret = 0;
+
+ ret = _mt6660_chip_power_on(chip, 1);
+ if (ret < 0) {
+ dev_err(chip->dev, "%s power on failed\n", __func__);
+ return ret;
+ }
+
+ for (i = 0; i < num; i++) {
+ ret = snd_soc_component_update_bits(component,
+ chip->plat_data.init_setting_addr[i],
+ chip->plat_data.init_setting_mask[i],
+ chip->plat_data.init_setting_val[i]);
+ if (ret < 0)
+ return ret;
+ }
+ ret = _mt6660_chip_power_on(chip, 0);
+ if (ret < 0) {
+ dev_err(chip->dev, "%s power on failed\n", __func__);
+ return ret;
+ }
+ return 0;
+}
+
+static int mt6660_component_probe(struct snd_soc_component *component)
+{
+ struct mt6660_chip *chip = snd_soc_component_get_drvdata(component);
+ int ret = 0;
+
+ dev_dbg(component->dev, "%s\n", __func__);
+ snd_soc_component_init_regmap(component, chip->regmap);
+
+ ret = mt6660_apply_plat_data(chip, component);
+ if (ret < 0)
+ dev_err(chip->dev, "mt6660 apply plat data failed\n");
+
+ return ret;
+}
+
+static void mt6660_component_remove(struct snd_soc_component *component)
+{
+ dev_dbg(component->dev, "%s\n", __func__);
+ snd_soc_component_exit_regmap(component);
+}
+
+static const struct snd_soc_component_driver mt6660_component_driver = {
+ .probe = mt6660_component_probe,
+ .remove = mt6660_component_remove,
+
+ .controls = mt6660_component_snd_controls,
+ .num_controls = ARRAY_SIZE(mt6660_component_snd_controls),
+ .dapm_widgets = mt6660_component_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(mt6660_component_dapm_widgets),
+ .dapm_routes = mt6660_component_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(mt6660_component_dapm_routes),
+
+ .idle_bias_on = false, /* idle_bias_off = true */
+};
+
+static int mt6660_component_aif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai)
+{
+ int word_len = params_physical_width(hw_params);
+ int aud_bit = params_width(hw_params);
+ u16 reg_data = 0;
+ int ret = 0;
+
+ dev_dbg(dai->dev, "%s: ++\n", __func__);
+ dev_dbg(dai->dev, "format: 0x%08x\n", params_format(hw_params));
+ dev_dbg(dai->dev, "rate: 0x%08x\n", params_rate(hw_params));
+ dev_dbg(dai->dev, "word_len: %d, aud_bit: %d\n", word_len, aud_bit);
+ if (word_len > 32 || word_len < 16) {
+ dev_err(dai->dev, "not supported word length\n");
+ return -ENOTSUPP;
+ }
+ switch (aud_bit) {
+ case 16:
+ reg_data = 3;
+ break;
+ case 18:
+ reg_data = 2;
+ break;
+ case 20:
+ reg_data = 1;
+ break;
+ case 24:
+ case 32:
+ reg_data = 0;
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+ ret = snd_soc_component_update_bits(dai->component,
+ MT6660_REG_SERIAL_CFG1, 0xc0, (reg_data << 6));
+ if (ret < 0) {
+ dev_err(dai->dev, "config aud bit fail\n");
+ return ret;
+ }
+ ret = snd_soc_component_update_bits(dai->component,
+ MT6660_REG_TDM_CFG3, 0x3f0, word_len << 4);
+ if (ret < 0) {
+ dev_err(dai->dev, "config word len fail\n");
+ return ret;
+ }
+ dev_dbg(dai->dev, "%s: --\n", __func__);
+ return 0;
+}
+
+static const struct snd_soc_dai_ops mt6660_component_aif_ops = {
+ .hw_params = mt6660_component_aif_hw_params,
+};
+
+#define STUB_RATES SNDRV_PCM_RATE_8000_192000
+#define STUB_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_U16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_U24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE | \
+ SNDRV_PCM_FMTBIT_U32_LE)
+
+static struct snd_soc_dai_driver mt6660_codec_dai = {
+ .name = "mt6660-aif",
+ .playback = {
+ .stream_name = "aif_playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = STUB_RATES,
+ .formats = STUB_FORMATS,
+ },
+ .capture = {
+ .stream_name = "aif_capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = STUB_RATES,
+ .formats = STUB_FORMATS,
+ },
+ /* dai properties */
+ .symmetric_rates = 1,
+ .symmetric_channels = 1,
+ .symmetric_samplebits = 1,
+ /* dai operations */
+ .ops = &mt6660_component_aif_ops,
+};
+
+static inline int _mt6660_chip_id_check(struct mt6660_chip *chip)
+{
+ u8 id[2] = {0};
+ int ret = 0;
+
+ ret = i2c_smbus_read_i2c_block_data(chip->i2c, MT6660_REG_DEVID, 2, id);
+ if (ret < 0)
+ return ret;
+ ret = (id[0] << 8) + id[1];
+ ret &= 0x0ff0;
+ if (ret != 0x00e0 && ret != 0x01e0) {
+ dev_err(chip->dev, "%s id(%x) not match\n", __func__, ret);
+ return -ENODEV;
+ }
+ return ret;
+}
+
+static inline int _mt6660_chip_sw_reset(struct mt6660_chip *chip)
+{
+ int ret;
+
+ /* turn on main pll first, then trigger reset */
+ ret = regmap_write(chip->regmap, 0x03, 0x00);
+ if (ret < 0)
+ return ret;
+ ret = regmap_write(chip->regmap, MT6660_REG_SYSTEM_CTRL, 0x80);
+ msleep(30);
+ return 0;
+}
+
+static inline int _mt6660_read_chip_revision(struct mt6660_chip *chip)
+{
+ u8 reg_data[2] = {0};
+ int ret = 0;
+
+ ret = i2c_smbus_read_i2c_block_data(
+ chip->i2c, MT6660_REG_DEVID, 2, reg_data);
+ if (ret < 0) {
+ dev_err(chip->dev, "get chip revision fail\n");
+ return ret;
+ }
+ chip->chip_rev = reg_data[1];
+ return 0;
+}
+
+static int mt6660_parse_dt(struct mt6660_chip *chip, struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ u32 val;
+ size_t i = 0;
+
+ if (!np) {
+ dev_err(dev, "no device node\n");
+ return -EINVAL;
+ }
+
+ if (of_property_read_u32(np, "rt,init_setting_num", &val)) {
+ dev_err(dev, "no init setting\n");
+ chip->plat_data.init_setting_num = 0;
+ } else {
+ chip->plat_data.init_setting_num = val;
+ }
+
+ chip->plat_data.init_setting_addr =
+ devm_kzalloc(dev, sizeof(u32) *
+ chip->plat_data.init_setting_num, GFP_KERNEL);
+ chip->plat_data.init_setting_mask =
+ devm_kzalloc(dev, sizeof(u32) *
+ chip->plat_data.init_setting_num, GFP_KERNEL);
+ chip->plat_data.init_setting_val =
+ devm_kzalloc(dev, sizeof(u32) *
+ chip->plat_data.init_setting_num, GFP_KERNEL);
+
+ if (of_property_read_u32_array(np, "rt,init_setting_addr",
+ chip->plat_data.init_setting_addr,
+ chip->plat_data.init_setting_num)) {
+ dev_err(dev, "no init setting addr\n");
+ }
+ if (of_property_read_u32_array(np, "rt,init_setting_mask",
+ chip->plat_data.init_setting_mask,
+ chip->plat_data.init_setting_num)) {
+ dev_err(dev, "no init setting addr\n");
+ }
+ if (of_property_read_u32_array(np, "rt,init_setting_val",
+ chip->plat_data.init_setting_val,
+ chip->plat_data.init_setting_num)) {
+ dev_err(dev, "no init setting addr\n");
+ }
+
+ dev_dbg(dev, "%s, init stting table, num = %d\n", __func__,
+ chip->plat_data.init_setting_num);
+ for (i = 0; i < chip->plat_data.init_setting_num; i++) {
+ dev_dbg(dev, "0x%02x, 0x%08x, 0x%08x\n",
+ chip->plat_data.init_setting_addr[i],
+ chip->plat_data.init_setting_mask[i],
+ chip->plat_data.init_setting_val[i]);
+ }
+ return 0;
+}
+
+static int mt6660_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct mt6660_chip *chip = NULL;
+ int ret = 0;
+
+ dev_dbg(&client->dev, "%s\n", __func__);
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+ chip->i2c = client;
+ chip->dev = &client->dev;
+ mutex_init(&chip->io_lock);
+ i2c_set_clientdata(client, chip);
+
+ ret = mt6660_parse_dt(chip, &client->dev);
+ if (ret < 0) {
+ dev_err(&client->dev, "parsing dts failed\n");
+ return ret;
+ }
+
+ chip->regmap = devm_regmap_init(&client->dev,
+ NULL, chip, &mt6660_regmap_config);
+ if (IS_ERR(chip->regmap)) {
+ ret = PTR_ERR(chip->regmap);
+ dev_err(&client->dev, "failed to initialise regmap: %d\n", ret);
+ return ret;
+ }
+
+ /* chip reset first */
+ ret = _mt6660_chip_sw_reset(chip);
+ if (ret < 0) {
+ dev_err(chip->dev, "chip reset fail\n");
+ goto probe_fail;
+ }
+ /* chip power on */
+ ret = _mt6660_chip_power_on(chip, 1);
+ if (ret < 0) {
+ dev_err(chip->dev, "chip power on 2 fail\n");
+ goto probe_fail;
+ }
+ /* chip devid check */
+ ret = _mt6660_chip_id_check(chip);
+ if (ret < 0) {
+ dev_err(chip->dev, "chip id check fail\n");
+ goto probe_fail;
+ }
+ /* chip revision get */
+ ret = _mt6660_read_chip_revision(chip);
+ if (ret < 0) {
+ dev_err(chip->dev, "read chip revision fail\n");
+ goto probe_fail;
+ }
+ pm_runtime_set_active(chip->dev);
+ pm_runtime_enable(chip->dev);
+
+ ret = devm_snd_soc_register_component(chip->dev,
+ &mt6660_component_driver,
+ &mt6660_codec_dai, 1);
+ return ret;
+probe_fail:
+ _mt6660_chip_power_on(chip, 0);
+ mutex_destroy(&chip->io_lock);
+ return ret;
+}
+
+static int mt6660_i2c_remove(struct i2c_client *client)
+{
+ struct mt6660_chip *chip = i2c_get_clientdata(client);
+
+ pm_runtime_disable(chip->dev);
+ pm_runtime_set_suspended(chip->dev);
+ mutex_destroy(&chip->io_lock);
+ return 0;
+}
+
+static int __maybe_unused mt6660_i2c_runtime_suspend(struct device *dev)
+{
+ struct mt6660_chip *chip = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "enter low power mode\n");
+ return regmap_update_bits(chip->regmap,
+ MT6660_REG_SYSTEM_CTRL, 0x01, 0x01);
+}
+
+static int __maybe_unused mt6660_i2c_runtime_resume(struct device *dev)
+{
+ struct mt6660_chip *chip = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "exit low power mode\n");
+ return regmap_update_bits(chip->regmap,
+ MT6660_REG_SYSTEM_CTRL, 0x01, 0x00);
+}
+
+static const struct dev_pm_ops mt6660_dev_pm_ops = {
+ SET_RUNTIME_PM_OPS(mt6660_i2c_runtime_suspend,
+ mt6660_i2c_runtime_resume, NULL)
+};
+
+static const struct of_device_id __maybe_unused mt6660_of_id[] = {
+ { .compatible = "mediatek,mt6660",},
+ {},
+};
+MODULE_DEVICE_TABLE(of, mt6660_of_id);
+
+static const struct i2c_device_id mt6660_i2c_id[] = {
+ {"mt6660", 0 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, mt6660_i2c_id);
+
+static struct i2c_driver mt6660_i2c_driver = {
+ .driver = {
+ .name = "mt6660",
+ .of_match_table = of_match_ptr(mt6660_of_id),
+ .pm = &mt6660_dev_pm_ops,
+ },
+ .probe = mt6660_i2c_probe,
+ .remove = mt6660_i2c_remove,
+ .id_table = mt6660_i2c_id,
+};
+module_i2c_driver(mt6660_i2c_driver);
+
+MODULE_AUTHOR("Jeff Chang <jeff_chang(a)richtek.com>");
+MODULE_DESCRIPTION("MT6660 SPKAMP Driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0.7_G");
diff --git a/sound/soc/codecs/mt6660.h b/sound/soc/codecs/mt6660.h
new file mode 100644
index 0000000..6c40b40
--- /dev/null
+++ b/sound/soc/codecs/mt6660.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2019 MediaTek Inc.
+ */
+
+#ifndef __SND_SOC_MT6660_H
+#define __SND_SOC_MT6660_H
+
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+
+struct mt6660_platform_data {
+ u8 init_setting_num;
+ u32 *init_setting_addr;
+ u32 *init_setting_mask;
+ u32 *init_setting_val;
+};
+
+struct mt6660_chip {
+ struct i2c_client *i2c;
+ struct device *dev;
+ struct platform_device *param_dev;
+ struct mt6660_platform_data plat_data;
+ struct mutex io_lock;
+ struct regmap *regmap;
+ u16 chip_rev;
+};
+
+#define MT6660_REG_DEVID (0x00)
+#define MT6660_REG_SYSTEM_CTRL (0x03)
+#define MT6660_REG_IRQ_STATUS1 (0x05)
+#define MT6660_REG_ADDA_CLOCK (0x07)
+#define MT6660_REG_SERIAL_CFG1 (0x10)
+#define MT6660_REG_DATAO_SEL (0x12)
+#define MT6660_REG_TDM_CFG3 (0x15)
+#define MT6660_REG_HPF_CTRL (0x18)
+#define MT6660_REG_HPF1_COEF (0x1A)
+#define MT6660_REG_HPF2_COEF (0x1B)
+#define MT6660_REG_PATH_BYPASS (0x1E)
+#define MT6660_REG_WDT_CTRL (0x20)
+#define MT6660_REG_HCLIP_CTRL (0x24)
+#define MT6660_REG_VOL_CTRL (0x29)
+#define MT6660_REG_SPS_CTRL (0x30)
+#define MT6660_REG_SIGMAX (0x33)
+#define MT6660_REG_CALI_T0 (0x3F)
+#define MT6660_REG_BST_CTRL (0x40)
+#define MT6660_REG_PROTECTION_CFG (0x46)
+#define MT6660_REG_DA_GAIN (0x4c)
+#define MT6660_REG_AUDIO_IN2_SEL (0x50)
+#define MT6660_REG_SIG_GAIN (0x51)
+#define MT6660_REG_PLL_CFG1 (0x60)
+#define MT6660_REG_DRE_CTRL (0x68)
+#define MT6660_REG_DRE_THDMODE (0x69)
+#define MT6660_REG_DRE_CORASE (0x6B)
+#define MT6660_REG_PWM_CTRL (0x70)
+#define MT6660_REG_DC_PROTECT_CTRL (0x74)
+#define MT6660_REG_ADC_USB_MODE (0x7c)
+#define MT6660_REG_INTERNAL_CFG (0x88)
+#define MT6660_REG_RESV0 (0x98)
+#define MT6660_REG_RESV1 (0x99)
+#define MT6660_REG_RESV2 (0x9A)
+#define MT6660_REG_RESV3 (0x9B)
+#define MT6660_REG_RESV6 (0xA2)
+#define MT6660_REG_RESV7 (0xA3)
+#define MT6660_REG_RESV10 (0xB0)
+#define MT6660_REG_RESV11 (0xB1)
+#define MT6660_REG_RESV16 (0xB6)
+#define MT6660_REG_RESV17 (0xB7)
+#define MT6660_REG_RESV19 (0xB9)
+#define MT6660_REG_RESV21 (0xBB)
+#define MT6660_REG_RESV23 (0xBD)
+#define MT6660_REG_RESV31 (0xD3)
+#define MT6660_REG_RESV40 (0xE0)
+
+#endif /* __SND_SOC_MT6660_H */
--
2.7.4
3
2

09 Jan '20
Hi,
this is a patch set for updating ALSA PCM API usages in dw-hdmi
driver. I already tried to "fix" the driver some time ago but it was
utterly wrong. So this is a combination of the revised patch and
another cleanup patch.
The first one is to change the buffer allocation mechanism in the
driver to the manual allocation of the h/w buffer and the automatic
allocation of PCM stream buffers via the new standard API. The
significant change is that size of the h/w buffer isn't no longer
controlled via ALSA preallocation proc file but rather via the new
module option (if any).
The second one is a oneliner patch just to remove the superfluous PCM
ops.
Both need the ALSA PCM core changes in 5.5-rc1, so please apply them
on top of 5.5-rc1 or later. Or, just let me know if I should apply
them through sound git tree.
thanks,
Takashi
===
Takashi Iwai (2):
drm/bridge: dw-hdmi: Follow the standard ALSA memalloc way
drm/bridge: dw-hdmi: Drop superfluous ioctl PCM ops
.../gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c | 49 ++++++++++++----------
1 file changed, 26 insertions(+), 23 deletions(-)
--
2.16.4
3
5
Dear all,
I'm working on a board that is equipped with a Bluetooth chip and a gsm
phone, both connected to the CPU (AM3352) on 2 different pcm. I wrote the
codecs and they are working (read as: I can play/record on both devices).
What I want to achieve now is to route those pcm together and still being
able to (at least) play audio on both. I tried alsaloop, but it eats too
much CPU (80% +) even in half duplex loop (BT capture => GSM playback), and
it was the only application running. In [1] I saw that I should be able to
route the codec together in kernel space (to avoid data copy between user
and kernel spaces). I did not tried that solution yet because it's not
really clear to me how to implement it, any suggestions/examples? If I
follow that solution, can I still use those PCM to playback my audio file
(I see that I can disconnect that link through a mixer control, so this
should be possible)?
Again any suggestion is really appreciated.
Best regards,
Gabriele
[1]:
https://www.kernel.org/doc/html/latest/sound/soc/dpcm.html#hostless-pcm-str…
1
0

09 Jan '20
We've got quite a few bug reports showing the SOF driver being loaded
unintentionally recently, and the reason seems to be that users didn't
know the module option change: with the recent kernel, a new option
dsp_driver=1 has to be passed to a new module snd-intel-dspcfg
instead of snd_hda_intel.dmic_detect=0 option.
That is, actually there are two tricky things here:
- We changed the whole detection in another module and another
option semantics.
- The existing option for skipping the DSP probe was also renamed.
For avoiding the confusion and giving user more hint, this patch
reverts the renamed option dsp_driver back to dmic_detect for
snd-hda-intel module, and show the warning about the module option
change when the non-default value is passed.
Fixes: 82d9d54a6c0e ("ALSA: hda: add Intel DSP configuration / probe code")
Signed-off-by: Takashi Iwai <tiwai(a)suse.de>
---
sound/pci/hda/hda_intel.c | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
index 5b92f290cbb0..8ef223aa1e37 100644
--- a/sound/pci/hda/hda_intel.c
+++ b/sound/pci/hda/hda_intel.c
@@ -125,7 +125,7 @@ static char *patch[SNDRV_CARDS];
static bool beep_mode[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] =
CONFIG_SND_HDA_INPUT_BEEP_MODE};
#endif
-static bool dsp_driver = 1;
+static bool dmic_detect = 1;
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for Intel HD audio interface.");
@@ -160,9 +160,10 @@ module_param_array(beep_mode, bool, NULL, 0444);
MODULE_PARM_DESC(beep_mode, "Select HDA Beep registration mode "
"(0=off, 1=on) (default=1).");
#endif
-module_param(dsp_driver, bool, 0444);
-MODULE_PARM_DESC(dsp_driver, "Allow DSP driver selection (bypass this driver) "
- "(0=off, 1=on) (default=1)");
+module_param(dmic_detect, bool, 0444);
+MODULE_PARM_DESC(dmic_detect, "Allow DSP driver selection (bypass this driver) "
+ "(0=off, 1=on) (default=1); "
+ "deprecated, use snd-intel-dspcfg.dsp_driver option instead");
#ifdef CONFIG_PM
static int param_set_xint(const char *val, const struct kernel_param *kp);
@@ -2099,11 +2100,13 @@ static int azx_probe(struct pci_dev *pci,
/*
* stop probe if another Intel's DSP driver should be activated
*/
- if (dsp_driver) {
+ if (dmic_detect) {
err = snd_intel_dsp_driver_probe(pci);
if (err != SND_INTEL_DSP_DRIVER_ANY &&
err != SND_INTEL_DSP_DRIVER_LEGACY)
return -ENODEV;
+ } else {
+ dev_warn(&pci->dev, "dmic_detect option is deprecated, pass snd-intel-dspcfg.dsp_driver=1 option instead\n");
}
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
--
2.16.4
1
0

[alsa-devel] [PATCH][resend] ASoC: simple-card: switch to yaml base Documentation
by Kuninori Morimoto 09 Jan '20
by Kuninori Morimoto 09 Jan '20
09 Jan '20
From: Kuninori Morimoto <kuninori.morimoto.gx(a)renesas.com>
This patch switches from .txt base to .yaml base Document.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx(a)renesas.com>
---
.../devicetree/bindings/sound/simple-card.txt | 351 ---------------
.../devicetree/bindings/sound/simple-card.yaml | 472 +++++++++++++++++++++
2 files changed, 472 insertions(+), 351 deletions(-)
delete mode 100644 Documentation/devicetree/bindings/sound/simple-card.txt
create mode 100644 Documentation/devicetree/bindings/sound/simple-card.yaml
diff --git a/Documentation/devicetree/bindings/sound/simple-card.txt b/Documentation/devicetree/bindings/sound/simple-card.txt
deleted file mode 100644
index 79954cd6..0000000
--- a/Documentation/devicetree/bindings/sound/simple-card.txt
+++ /dev/null
@@ -1,351 +0,0 @@
-Simple-Card:
-
-Simple-Card specifies audio DAI connections of SoC <-> codec.
-
-Required properties:
-
-- compatible : "simple-audio-card"
-
-Optional properties:
-
-- simple-audio-card,name : User specified audio sound card name, one string
- property.
-- simple-audio-card,widgets : Please refer to widgets.txt.
-- simple-audio-card,routing : A list of the connections between audio components.
- Each entry is a pair of strings, the first being the
- connection's sink, the second being the connection's
- source.
-- simple-audio-card,mclk-fs : Multiplication factor between stream rate and codec
- mclk. When defined, mclk-fs property defined in
- dai-link sub nodes are ignored.
-- simple-audio-card,hp-det-gpio : Reference to GPIO that signals when
- headphones are attached.
-- simple-audio-card,mic-det-gpio : Reference to GPIO that signals when
- a microphone is attached.
-- simple-audio-card,aux-devs : List of phandles pointing to auxiliary devices, such
- as amplifiers, to be added to the sound card.
-- simple-audio-card,pin-switches : List of strings containing the widget names for
- which pin switches must be created.
-
-Optional subnodes:
-
-- simple-audio-card,dai-link : Container for dai-link level
- properties and the CPU and CODEC
- sub-nodes. This container may be
- omitted when the card has only one
- DAI link. See the examples and the
- section below.
-
-Dai-link subnode properties and subnodes:
-
-If dai-link subnode is omitted and the subnode properties are directly
-under "sound"-node the subnode property and subnode names have to be
-prefixed with "simple-audio-card,"-prefix.
-
-Required dai-link subnodes:
-
-- cpu : CPU sub-node
-- codec : CODEC sub-node
-
-Optional dai-link subnode properties:
-
-- format : CPU/CODEC common audio format.
- "i2s", "right_j", "left_j" , "dsp_a"
- "dsp_b", "ac97", "pdm", "msb", "lsb"
-- frame-master : Indicates dai-link frame master.
- phandle to a cpu or codec subnode.
-- bitclock-master : Indicates dai-link bit clock master.
- phandle to a cpu or codec subnode.
-- bitclock-inversion : bool property. Add this if the
- dai-link uses bit clock inversion.
-- frame-inversion : bool property. Add this if the
- dai-link uses frame clock inversion.
-- mclk-fs : Multiplication factor between stream
- rate and codec mclk, applied only for
- the dai-link.
-
-For backward compatibility the frame-master and bitclock-master
-properties can be used as booleans in codec subnode to indicate if the
-codec is the dai-link frame or bit clock master. In this case there
-should be no dai-link node, the same properties should not be present
-at sound-node level, and the bitclock-inversion and frame-inversion
-properties should also be placed in the codec node if needed.
-
-Required CPU/CODEC subnodes properties:
-
-- sound-dai : phandle and port of CPU/CODEC
-
-Optional CPU/CODEC subnodes properties:
-
-- dai-tdm-slot-num : Please refer to tdm-slot.txt.
-- dai-tdm-slot-width : Please refer to tdm-slot.txt.
-- clocks / system-clock-frequency : specify subnode's clock if needed.
- it can be specified via "clocks" if system has
- clock node (= common clock), or "system-clock-frequency"
- (if system doens't support common clock)
- If a clock is specified, it is
- enabled with clk_prepare_enable()
- in dai startup() and disabled with
- clk_disable_unprepare() in dai
- shutdown().
- If a clock is specified and a
- multiplication factor is given with
- mclk-fs, the clock will be set to the
- calculated mclk frequency when the
- stream starts.
-- system-clock-direction-out : specifies clock direction as 'out' on
- initialization. It is useful for some aCPUs with
- fixed clocks.
-
--------------------------------------------
-Example 1 - single DAI link:
--------------------------------------------
-
-sound {
- compatible = "simple-audio-card";
- simple-audio-card,name = "VF610-Tower-Sound-Card";
- simple-audio-card,format = "left_j";
- simple-audio-card,bitclock-master = <&dailink0_master>;
- simple-audio-card,frame-master = <&dailink0_master>;
- simple-audio-card,widgets =
- "Microphone", "Microphone Jack",
- "Headphone", "Headphone Jack",
- "Speaker", "External Speaker";
- simple-audio-card,routing =
- "MIC_IN", "Microphone Jack",
- "Headphone Jack", "HP_OUT",
- "External Speaker", "LINE_OUT";
-
- simple-audio-card,cpu {
- sound-dai = <&sh_fsi2 0>;
- };
-
- dailink0_master: simple-audio-card,codec {
- sound-dai = <&ak4648>;
- clocks = <&osc>;
- };
-};
-
-&i2c0 {
- ak4648: ak4648@12 {
- #sound-dai-cells = <0>;
- compatible = "asahi-kasei,ak4648";
- reg = <0x12>;
- };
-};
-
-sh_fsi2: sh_fsi2@ec230000 {
- #sound-dai-cells = <1>;
- compatible = "renesas,sh_fsi2";
- reg = <0xec230000 0x400>;
- interrupt-parent = <&gic>;
- interrupts = <0 146 0x4>;
-};
-
--------------------------------------------
-Example 2 - many DAI links:
--------------------------------------------
-
-sound {
- compatible = "simple-audio-card";
- simple-audio-card,name = "Cubox Audio";
-
- simple-audio-card,dai-link@0 { /* I2S - HDMI */
- reg = <0>;
- format = "i2s";
- cpu {
- sound-dai = <&audio1 0>;
- };
- codec {
- sound-dai = <&tda998x 0>;
- };
- };
-
- simple-audio-card,dai-link@1 { /* S/PDIF - HDMI */
- reg = <1>;
- cpu {
- sound-dai = <&audio1 1>;
- };
- codec {
- sound-dai = <&tda998x 1>;
- };
- };
-
- simple-audio-card,dai-link@2 { /* S/PDIF - S/PDIF */
- reg = <2>;
- cpu {
- sound-dai = <&audio1 1>;
- };
- codec {
- sound-dai = <&spdif_codec>;
- };
- };
-};
-
--------------------------------------------
-Example 3 - route audio from IMX6 SSI2 through TLV320DAC3100 codec
-through TPA6130A2 amplifier to headphones:
--------------------------------------------
-
-&i2c0 {
- codec: tlv320dac3100@18 {
- compatible = "ti,tlv320dac3100";
- ...
- }
-
- amp: tpa6130a2@60 {
- compatible = "ti,tpa6130a2";
- ...
- }
-}
-
-sound {
- compatible = "simple-audio-card";
- ...
- simple-audio-card,widgets =
- "Headphone", "Headphone Jack";
- simple-audio-card,routing =
- "Headphone Jack", "HPLEFT",
- "Headphone Jack", "HPRIGHT",
- "LEFTIN", "HPL",
- "RIGHTIN", "HPR";
- simple-audio-card,aux-devs = <&>;
- simple-audio-card,cpu {
- sound-dai = <&ssi2>;
- };
- simple-audio-card,codec {
- sound-dai = <&codec>;
- clocks = ...
- };
-};
-
--------------------------------------------
-Example 4. Sampling Rate Conversion
--------------------------------------------
-
-sound {
- compatible = "simple-audio-card";
-
- simple-audio-card,name = "rsnd-ak4643";
- simple-audio-card,format = "left_j";
- simple-audio-card,bitclock-master = <&sndcodec>;
- simple-audio-card,frame-master = <&sndcodec>;
-
- simple-audio-card,convert-rate = <48000>;
-
- simple-audio-card,prefix = "ak4642";
- simple-audio-card,routing = "ak4642 Playback", "DAI0 Playback",
- "DAI0 Capture", "ak4642 Capture";
-
- sndcpu: simple-audio-card,cpu {
- sound-dai = <&rcar_sound>;
- };
-
- sndcodec: simple-audio-card,codec {
- sound-dai = <&ak4643>;
- system-clock-frequency = <11289600>;
- };
-};
-
--------------------------------------------
-Example 5. 2 CPU 1 Codec (Mixing)
--------------------------------------------
-sound {
- compatible = "simple-audio-card";
-
- simple-audio-card,name = "rsnd-ak4643";
- simple-audio-card,format = "left_j";
- simple-audio-card,bitclock-master = <&dpcmcpu>;
- simple-audio-card,frame-master = <&dpcmcpu>;
-
- simple-audio-card,routing = "ak4642 Playback", "DAI0 Playback",
- "ak4642 Playback", "DAI1 Playback";
-
- dpcmcpu: cpu@0 {
- sound-dai = <&rcar_sound 0>;
- };
-
- cpu@1 {
- sound-dai = <&rcar_sound 1>;
- };
-
- codec {
- prefix = "ak4642";
- sound-dai = <&ak4643>;
- clocks = <&audio_clock>;
- };
-};
-
--------------------------------------------
-Example 6 - many DAI links with DPCM:
--------------------------------------------
-
-CPU0 ------ ak4613
-CPU1 ------ PCM3168A-p /* DPCM 1ch/2ch */
-CPU2 --/ /* DPCM 3ch/4ch */
-CPU3 --/ /* DPCM 5ch/6ch */
-CPU4 --/ /* DPCM 7ch/8ch */
-CPU5 ------ PCM3168A-c
-
-sound {
- compatible = "simple-audio-card";
-
- simple-audio-card,routing =
- "pcm3168a Playback", "DAI1 Playback",
- "pcm3168a Playback", "DAI2 Playback",
- "pcm3168a Playback", "DAI3 Playback",
- "pcm3168a Playback", "DAI4 Playback";
-
- simple-audio-card,dai-link@0 {
- format = "left_j";
- bitclock-master = <&sndcpu0>;
- frame-master = <&sndcpu0>;
-
- sndcpu0: cpu {
- sound-dai = <&rcar_sound 0>;
- };
- codec {
- sound-dai = <&ak4613>;
- };
- };
- simple-audio-card,dai-link@1 {
- format = "i2s";
- bitclock-master = <&sndcpu1>;
- frame-master = <&sndcpu1>;
-
- convert-channels = <8>; /* TDM Split */
-
- sndcpu1: cpu@0 {
- sound-dai = <&rcar_sound 1>;
- };
- cpu@1 {
- sound-dai = <&rcar_sound 2>;
- };
- cpu@2 {
- sound-dai = <&rcar_sound 3>;
- };
- cpu@3 {
- sound-dai = <&rcar_sound 4>;
- };
- codec {
- mclk-fs = <512>;
- prefix = "pcm3168a";
- dai-tdm-slot-num = <8>;
- sound-dai = <&pcm3168a 0>;
- };
- };
- simple-audio-card,dai-link@2 {
- format = "i2s";
- bitclock-master = <&sndcpu2>;
- frame-master = <&sndcpu2>;
-
- sndcpu2: cpu {
- sound-dai = <&rcar_sound 5>;
- };
- codec {
- mclk-fs = <512>;
- prefix = "pcm3168a";
- sound-dai = <&pcm3168a 1>;
- };
- };
-};
diff --git a/Documentation/devicetree/bindings/sound/simple-card.yaml b/Documentation/devicetree/bindings/sound/simple-card.yaml
new file mode 100644
index 0000000..ac3975f
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/simple-card.yaml
@@ -0,0 +1,472 @@
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/simple-card.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Simple Audio Card Driver Device Tree Bindings
+
+maintainers:
+ - Kuninori Morimoto <kuninori.morimoto.gx(a)renesas.com>
+
+definitions:
+
+ frame-master:
+ description: Indicates dai-link frame master.
+ $ref: /schemas/types.yaml#/definitions/phandle-array
+
+ bitclock-master:
+ description: Indicates dai-link bit clock master
+ $ref: /schemas/types.yaml#/definitions/phandle-array
+
+ frame-inversion:
+ description: dai-link uses frame clock inversion
+ $ref: /schemas/types.yaml#/definitions/flag
+
+ bitclock-inversion:
+ description: dai-link uses bit clock inversion
+ $ref: /schemas/types.yaml#/definitions/flag
+
+ mclk-fs:
+ description: |
+ Multiplication factor between stream rate and codec mclk.
+ When defined, mclk-fs property defined in dai-link sub nodes are ignored.
+ $ref: /schemas/types.yaml#/definitions/uint32
+
+ hp-det-gpio:
+ description: |
+ Reference to GPIO that signals when headphones are attached.
+ $ref: /schemas/types.yaml#/definitions/phandle
+
+ aux-devs:
+ description: |
+ List of phandles pointing to auxiliary devices, such
+ as amplifiers, to be added to the sound card.
+ $ref: /schemas/types.yaml#/definitions/phandle-array
+
+ mic-det-gpio:
+ description: |
+ Reference to GPIO that signals when a microphone is attached.
+ $ref: /schemas/types.yaml#/definitions/phandle
+
+ convert-rate:
+ description: CPU to Codec rate convert.
+ $ref: /schemas/types.yaml#/definitions/uint32
+
+ convert-channels:
+ description: CPU to Codec rate channels.
+ $ref: /schemas/types.yaml#/definitions/uint32
+
+ prefix:
+ description: "device name prefix"
+ $ref: /schemas/types.yaml#/definitions/string
+
+ pin-switches:
+ description: the widget names for which pin switches must be created.
+ $ref: /schemas/types.yaml#/definitions/string-array
+
+ format:
+ description: audio format.
+ oneOf:
+ - items:
+ enum:
+ - i2s
+ - right_j
+ - left_j
+ - dsp_a
+ - dsp_b
+ - ac97
+ - pdm
+ - msb
+ - lsb
+
+ dai:
+ type: object
+ properties:
+ sound-dai:
+ $ref: /schemas/types.yaml#/definitions/phandle-array
+
+ # common properties
+ mclk-fs:
+ $ref: "#/definitions/mclk-fs"
+ prefix:
+ $ref: "#/definitions/prefix"
+ frame-inversion:
+ $ref: "#/definitions/frame-inversion"
+ bitclock-inversion:
+ $ref: "#/definitions/bitclock-inversion"
+ frame-master:
+ $ref: /schemas/types.yaml#/definitions/flag
+ bitclock-master:
+ $ref: /schemas/types.yaml#/definitions/flag
+
+ dai-tdm-slot-num:
+ description: see tdm-slot.txt.
+ $ref: /schemas/types.yaml#/definitions/uint32
+ dai-tdm-slot-width:
+ description: see tdm-slot.txt.
+ $ref: /schemas/types.yaml#/definitions/uint32
+ clocks:
+ description: |
+ If a clock is specified and a multiplication factor is given with
+ mclk-fs, the clock will be set to the calculated mclk frequency
+ when the stream starts.
+ $ref: /schemas/types.yaml#/definitions/phandle-array
+ system-clock-frequency:
+ description: |
+ If a clock is specified and a multiplication factor is given with
+ mclk-fs, the clock will be set to the calculated mclk frequency
+ when the stream starts.
+ $ref: /schemas/types.yaml#/definitions/uint32
+ system-clock-direction-out:
+ description: |
+ specifies clock direction as 'out' on initialization.
+ It is useful for some aCPUs with fixed clocks.
+ $ref: /schemas/types.yaml#/definitions/flag
+ required:
+ - sound-dai
+
+properties:
+ compatible:
+ contains:
+ enum:
+ - simple-audio-card
+ - simple-scu-audio-card
+
+ "#address-cells": true
+ "#size-cells": true
+
+ simple-audio-card,name:
+ description: User specified audio sound card name.
+ $ref: /schemas/types.yaml#/definitions/string
+
+ simple-audio-card,widgets:
+ description: User specified audio sound card name.
+ $ref: /schemas/types.yaml#/definitions/non-unique-string-array
+
+ simple-audio-card,routing:
+ description: |
+ A list of the connections between audio components.
+ Each entry is a pair of strings, the first being the
+ connection's sink, the second being the connection's source.
+ $ref: /schemas/types.yaml#/definitions/non-unique-string-array
+
+patternProperties:
+ "^simple-audio-card,cpu.*":
+ description: CPU node
+ $ref: "#/definitions/dai"
+ "^simple-audio-card,codec.*":
+ description: CPU node
+ $ref: "#/definitions/dai"
+
+ # common properties
+ "^simple-audio-card,frame-master$":
+ $ref: "#/definitions/frame-master"
+ "^simple-audio-card,bitclock-master$":
+ $ref: "#/definitions/bitclock-master"
+ "^simple-audio-card,frame-inversion$":
+ $ref: "#/definitions/frame-inversion"
+ "^simple-audio-card,bitclock-inversion$":
+ $ref: "#/definitions/bitclock-inversion"
+ "^simple-audio-card,format$":
+ $ref: "#/definitions/format"
+ "^simple-audio-card,mclk-fs$":
+ $ref: "#/definitions/mclk-fs"
+ "^simple-audio-card,hp-det-gpio$":
+ $ref: "#/definitions/hp-det-gpio"
+ "^simple-audio-card,mic-det-gpio$":
+ $ref: "#/definitions/mic-det-gpio"
+ "^simple-audio-card,aux-devs$":
+ $ref: "#/definitions/aux-devs"
+ "^simple-audio-card,convert-rate$":
+ $ref: "#/definitions/convert-rate"
+ "^simple-audio-card,convert-channels$":
+ $ref: "#/definitions/convert-channels"
+ "^simple-audio-card,prefix$":
+ $ref: "#/definitions/prefix"
+ "^simple-audio-card,pin-switches$":
+ $ref: "#/definitions/pin-switches"
+
+ "^simple-audio-card,dai-link":
+ description: |
+ Container for dai-link level properties and the CPU and CODEC sub-nodes.
+ This container may be omitted when the card has only one DAI link.
+ type: object
+ properties:
+ reg:
+ $ref: /schemas/types.yaml#/definitions/uint32
+
+ # common properties
+ frame-master:
+ $ref: "#/definitions/frame-master"
+ bitclock-master:
+ $ref: "#/definitions/bitclock-master"
+ frame-inversion:
+ $ref: "#/definitions/frame-inversion"
+ bitclock-inversion:
+ $ref: "#/definitions/bitclock-inversion"
+ format:
+ $ref: "#/definitions/format"
+ mclk-fs:
+ $ref: "#/definitions/mclk-fs"
+ hp-det-gpio:
+ $ref: "#/definitions/hp-det-gpio"
+ mic-det-gpio:
+ $ref: "#/definitions/mic-det-gpio"
+ aux-devs:
+ $ref: "#/definitions/aux-devs"
+ convert-rate:
+ $ref: "#/definitions/convert-rate"
+ convert-channels:
+ $ref: "#/definitions/convert-channels"
+ prefix:
+ $ref: "#/definitions/prefix"
+ pin-switches:
+ $ref: "#/definitions/pin-switches"
+
+ patternProperties:
+ "^cpu.*":
+ $ref: "#/definitions/dai"
+ "^codec.*":
+ $ref: "#/definitions/dai"
+ additionalProperties: false
+
+required:
+ - compatible
+
+additionalProperties: false
+
+examples:
+#--------------------
+# single DAI link
+#--------------------
+ - |
+ sound {
+ compatible = "simple-audio-card";
+ simple-audio-card,name = "VF610-Tower-Sound-Card";
+ simple-audio-card,format = "left_j";
+ simple-audio-card,bitclock-master = <&dailink0_master>;
+ simple-audio-card,frame-master = <&dailink0_master>;
+ simple-audio-card,widgets =
+ "Microphone", "Microphone Jack",
+ "Headphone", "Headphone Jack",
+ "Speaker", "External Speaker";
+ simple-audio-card,routing =
+ "MIC_IN", "Microphone Jack",
+ "Headphone Jack", "HP_OUT",
+ "External Speaker", "LINE_OUT";
+
+ simple-audio-card,cpu {
+ sound-dai = <&sh_fsi2 0>;
+ };
+
+ dailink0_master: simple-audio-card,codec {
+ sound-dai = <&ak4648>;
+ clocks = <&osc>;
+ };
+ };
+
+#--------------------
+# Multi DAI links
+#--------------------
+ - |
+ sound {
+ compatible = "simple-audio-card";
+ simple-audio-card,name = "Cubox Audio";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ simple-audio-card,dai-link@0 { /* I2S - HDMI */
+ reg = <0>;
+ format = "i2s";
+ cpu {
+ sound-dai = <&audio0>;
+ };
+ codec {
+ sound-dai = <&tda998x0>;
+ };
+ };
+
+ simple-audio-card,dai-link@1 { /* S/PDIF - HDMI */
+ reg = <1>;
+ cpu {
+ sound-dai = <&audio1>;
+ };
+ codec {
+ sound-dai = <&tda998x1>;
+ };
+ };
+
+ simple-audio-card,dai-link@2 { /* S/PDIF - S/PDIF */
+ reg = <2>;
+ cpu {
+ sound-dai = <&audio2>;
+ };
+ codec {
+ sound-dai = <&spdif_codec>;
+ };
+ };
+ };
+
+#--------------------
+# route audio from IMX6 SSI2 through TLV320DAC3100 codec
+# through TPA6130A2 amplifier to headphones:
+#--------------------
+ - |
+ sound {
+ compatible = "simple-audio-card";
+
+ simple-audio-card,widgets =
+ "Headphone", "Headphone Jack";
+ simple-audio-card,routing =
+ "Headphone Jack", "HPLEFT",
+ "Headphone Jack", "HPRIGHT",
+ "LEFTIN", "HPL",
+ "RIGHTIN", "HPR";
+ simple-audio-card,aux-devs = <&>;
+ simple-audio-card,cpu {
+ sound-dai = <&ssi2>;
+ };
+ simple-audio-card,codec {
+ sound-dai = <&codec>;
+ clocks = <&clocks>;
+ };
+ };
+
+#--------------------
+# Sampling Rate Conversion
+#--------------------
+ - |
+ sound {
+ compatible = "simple-audio-card";
+
+ simple-audio-card,name = "rsnd-ak4643";
+ simple-audio-card,format = "left_j";
+ simple-audio-card,bitclock-master = <&sndcodec>;
+ simple-audio-card,frame-master = <&sndcodec>;
+
+ simple-audio-card,convert-rate = <48000>;
+
+ simple-audio-card,prefix = "ak4642";
+ simple-audio-card,routing = "ak4642 Playback", "DAI0 Playback",
+ "DAI0 Capture", "ak4642 Capture";
+
+ sndcpu: simple-audio-card,cpu {
+ sound-dai = <&rcar_sound>;
+ };
+
+ sndcodec: simple-audio-card,codec {
+ sound-dai = <&ak4643>;
+ system-clock-frequency = <11289600>;
+ };
+ };
+
+#--------------------
+# 2 CPU 1 Codec (Mixing)
+#--------------------
+ - |
+ sound {
+ compatible = "simple-audio-card";
+
+ simple-audio-card,name = "rsnd-ak4643";
+ simple-audio-card,format = "left_j";
+ simple-audio-card,bitclock-master = <&dpcmcpu>;
+ simple-audio-card,frame-master = <&dpcmcpu>;
+
+ simple-audio-card,convert-rate = <48000>;
+ simple-audio-card,convert-channels = <2>;
+
+ simple-audio-card,routing = "ak4642 Playback", "DAI0 Playback",
+ "ak4642 Playback", "DAI1 Playback";
+
+ dpcmcpu: simple-audio-card,cpu@0 {
+ sound-dai = <&rcar_sound 0>;
+ };
+
+ simple-audio-card,cpu@1 {
+ sound-dai = <&rcar_sound 1>;
+ };
+
+ simple-audio-card,codec {
+ prefix = "ak4642";
+ sound-dai = <&ak4643>;
+ clocks = <&audio_clock>;
+ };
+ };
+
+#--------------------
+# Multi DAI links with DPCM:
+#
+# CPU0 ------ ak4613
+# CPU1 ------ PCM3168A-p /* DPCM 1ch/2ch */
+# CPU2 --/ /* DPCM 3ch/4ch */
+# CPU3 --/ /* DPCM 5ch/6ch */
+# CPU4 --/ /* DPCM 7ch/8ch */
+# CPU5 ------ PCM3168A-c
+#--------------------
+ - |
+ sound {
+ compatible = "simple-audio-card";
+
+ simple-audio-card,routing =
+ "pcm3168a Playback", "DAI1 Playback",
+ "pcm3168a Playback", "DAI2 Playback",
+ "pcm3168a Playback", "DAI3 Playback",
+ "pcm3168a Playback", "DAI4 Playback";
+
+ simple-audio-card,dai-link@0 {
+ format = "left_j";
+ bitclock-master = <&sndcpu0>;
+ frame-master = <&sndcpu0>;
+
+ sndcpu0: cpu {
+ sound-dai = <&rcar_sound 0>;
+ };
+ codec {
+ sound-dai = <&ak4613>;
+ };
+ };
+
+ simple-audio-card,dai-link@1 {
+ format = "i2s";
+ bitclock-master = <&sndcpu1>;
+ frame-master = <&sndcpu1>;
+
+ convert-channels = <8>; /* TDM Split */
+
+ sndcpu1: cpu@0 {
+ sound-dai = <&rcar_sound 1>;
+ };
+ cpu@1 {
+ sound-dai = <&rcar_sound 2>;
+ };
+ cpu@2 {
+ sound-dai = <&rcar_sound 3>;
+ };
+ cpu@3 {
+ sound-dai = <&rcar_sound 4>;
+ };
+ codec {
+ mclk-fs = <512>;
+ prefix = "pcm3168a";
+ dai-tdm-slot-num = <8>;
+ sound-dai = <&pcm3168a 0>;
+ };
+ };
+
+ simple-audio-card,dai-link@2 {
+ format = "i2s";
+ bitclock-master = <&sndcpu2>;
+ frame-master = <&sndcpu2>;
+
+ sndcpu2: cpu {
+ sound-dai = <&rcar_sound 5>;
+ };
+ codec {
+ mclk-fs = <512>;
+ prefix = "pcm3168a";
+ sound-dai = <&pcm3168a 1>;
+ };
+ };
+ };
--
2.7.4
2
2

09 Jan '20
What is the impact assessment so far done for MIDI 2.0 for ALSA & Linux ?
Will a kernel change be needed for (USB) MIDI to support the MIDI 2.0
packet interface ?
How about the ALSA sequencer API ? Any activities around this ?
MIDI application based on Jack MIDI impact ?
https://www.midi.org/articles-old/details-about-midi-2-0-midi-ci-profiles-a…
https://www.midi.org/articles-old/midi-2-0-scope
https://thefeaturestory.com/2019/12/05/artlogic-give-exclusive-preview-of-m…
https://www.namm.org/thenammshow/2020/events/a3e-workshop-art-logic-midi-20…
https://juce.com/discover/stories/introducing-midi-2-0
https://www.musictech.net/features/midi-2-0-what-mean-for-you/
1
0