Commit 11d4e474 authored by Mark Brown's avatar Mark Brown

ASoC: mchp-pdmc: fix poc noises when starting

Merge series from Claudiu Beznea <claudiu.beznea@microchip.com>:

To start capture on Microchip PDMC the enable bits for each supported
microphone need to be set. After this bit is set the PDMC starts to
receive data from microphones and it considers this data as valid data.
Thus if microphones are not ready the PDMC captures anyway data from its
lines. This data is interpreted by the human ear as poc noises.

To avoid this the following software workaround need to be applied when
starting capture:
1/ enable PDMC channel
2/ wait 150ms
3/ execute 16 dummy reads from RHR
4/ clear interrupts
5/ enable interrupts
6/ enable DMA channel

For this workaround to work step 6 need to be executed at the end.
For step 6 was added patch 1/3 from this series. With this, component
DAI driver sets its struct snd_soc_component_driver::start_dma_last = 1
and proper action is taken based on this flag when starting DAI trigger
vs DMA.
parents b2019299 c5682e2b
...@@ -67,6 +67,12 @@ properties: ...@@ -67,6 +67,12 @@ properties:
maxItems: 4 maxItems: 4
uniqueItems: true uniqueItems: true
microchip,startup-delay-us:
description: |
Specifies the delay in microseconds that needs to be applied after
enabling the PDMC microphones to avoid unwanted noise due to microphones
not being ready.
required: required:
- compatible - compatible
- reg - reg
......
...@@ -190,6 +190,8 @@ struct snd_soc_component_driver { ...@@ -190,6 +190,8 @@ struct snd_soc_component_driver {
bool use_dai_pcm_id; /* use DAI link PCM ID as PCM device number */ bool use_dai_pcm_id; /* use DAI link PCM ID as PCM device number */
int be_pcm_base; /* base device ID for all BE PCMs */ int be_pcm_base; /* base device ID for all BE PCMs */
unsigned int start_dma_last;
#ifdef CONFIG_DEBUG_FS #ifdef CONFIG_DEBUG_FS
const char *debugfs_prefix; const char *debugfs_prefix;
#endif #endif
......
...@@ -114,6 +114,7 @@ struct mchp_pdmc { ...@@ -114,6 +114,7 @@ struct mchp_pdmc {
struct clk *gclk; struct clk *gclk;
u32 pdmcen; u32 pdmcen;
u32 suspend_irq; u32 suspend_irq;
u32 startup_delay_us;
int mic_no; int mic_no;
int sinc_order; int sinc_order;
bool audio_filter_en; bool audio_filter_en;
...@@ -425,6 +426,7 @@ static const struct snd_soc_component_driver mchp_pdmc_dai_component = { ...@@ -425,6 +426,7 @@ static const struct snd_soc_component_driver mchp_pdmc_dai_component = {
.open = &mchp_pdmc_open, .open = &mchp_pdmc_open,
.close = &mchp_pdmc_close, .close = &mchp_pdmc_close,
.legacy_dai_naming = 1, .legacy_dai_naming = 1,
.start_dma_last = 1,
}; };
static const unsigned int mchp_pdmc_1mic[] = {1}; static const unsigned int mchp_pdmc_1mic[] = {1};
...@@ -632,6 +634,29 @@ static int mchp_pdmc_hw_params(struct snd_pcm_substream *substream, ...@@ -632,6 +634,29 @@ static int mchp_pdmc_hw_params(struct snd_pcm_substream *substream,
return 0; return 0;
} }
static void mchp_pdmc_noise_filter_workaround(struct mchp_pdmc *dd)
{
u32 tmp, steps = 16;
/*
* PDMC doesn't wait for microphones' startup time thus the acquisition
* may start before the microphones are ready leading to poc noises at
* the beginning of capture. To avoid this, we need to wait 50ms (in
* normal startup procedure) or 150 ms (worst case after resume from sleep
* states) after microphones are enabled and then clear the FIFOs (by
* reading the RHR 16 times) and possible interrupts before continuing.
* Also, for this to work the DMA needs to be started after interrupts
* are enabled.
*/
usleep_range(dd->startup_delay_us, dd->startup_delay_us + 5);
while (steps--)
regmap_read(dd->regmap, MCHP_PDMC_RHR, &tmp);
/* Clear interrupts. */
regmap_read(dd->regmap, MCHP_PDMC_ISR, &tmp);
}
static int mchp_pdmc_trigger(struct snd_pcm_substream *substream, static int mchp_pdmc_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai) int cmd, struct snd_soc_dai *dai)
{ {
...@@ -644,15 +669,17 @@ static int mchp_pdmc_trigger(struct snd_pcm_substream *substream, ...@@ -644,15 +669,17 @@ static int mchp_pdmc_trigger(struct snd_pcm_substream *substream,
switch (cmd) { switch (cmd) {
case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_START:
/* Enable overrun and underrun error interrupts */
regmap_write(dd->regmap, MCHP_PDMC_IER, dd->suspend_irq |
MCHP_PDMC_IR_RXOVR | MCHP_PDMC_IR_RXUDR);
dd->suspend_irq = 0;
fallthrough;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
snd_soc_component_update_bits(cpu, MCHP_PDMC_MR, snd_soc_component_update_bits(cpu, MCHP_PDMC_MR,
MCHP_PDMC_MR_PDMCEN_MASK, MCHP_PDMC_MR_PDMCEN_MASK,
dd->pdmcen); dd->pdmcen);
mchp_pdmc_noise_filter_workaround(dd);
/* Enable interrupts. */
regmap_write(dd->regmap, MCHP_PDMC_IER, dd->suspend_irq |
MCHP_PDMC_IR_RXOVR | MCHP_PDMC_IR_RXUDR);
dd->suspend_irq = 0;
break; break;
case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_SUSPEND:
regmap_read(dd->regmap, MCHP_PDMC_IMR, &dd->suspend_irq); regmap_read(dd->regmap, MCHP_PDMC_IMR, &dd->suspend_irq);
...@@ -796,6 +823,7 @@ static bool mchp_pdmc_readable_reg(struct device *dev, unsigned int reg) ...@@ -796,6 +823,7 @@ static bool mchp_pdmc_readable_reg(struct device *dev, unsigned int reg)
case MCHP_PDMC_CFGR: case MCHP_PDMC_CFGR:
case MCHP_PDMC_IMR: case MCHP_PDMC_IMR:
case MCHP_PDMC_ISR: case MCHP_PDMC_ISR:
case MCHP_PDMC_RHR:
case MCHP_PDMC_VER: case MCHP_PDMC_VER:
return true; return true;
default: default:
...@@ -817,6 +845,17 @@ static bool mchp_pdmc_writeable_reg(struct device *dev, unsigned int reg) ...@@ -817,6 +845,17 @@ static bool mchp_pdmc_writeable_reg(struct device *dev, unsigned int reg)
} }
} }
static bool mchp_pdmc_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case MCHP_PDMC_ISR:
case MCHP_PDMC_RHR:
return true;
default:
return false;
}
}
static bool mchp_pdmc_precious_reg(struct device *dev, unsigned int reg) static bool mchp_pdmc_precious_reg(struct device *dev, unsigned int reg)
{ {
switch (reg) { switch (reg) {
...@@ -836,6 +875,7 @@ static const struct regmap_config mchp_pdmc_regmap_config = { ...@@ -836,6 +875,7 @@ static const struct regmap_config mchp_pdmc_regmap_config = {
.readable_reg = mchp_pdmc_readable_reg, .readable_reg = mchp_pdmc_readable_reg,
.writeable_reg = mchp_pdmc_writeable_reg, .writeable_reg = mchp_pdmc_writeable_reg,
.precious_reg = mchp_pdmc_precious_reg, .precious_reg = mchp_pdmc_precious_reg,
.volatile_reg = mchp_pdmc_volatile_reg,
.cache_type = REGCACHE_FLAT, .cache_type = REGCACHE_FLAT,
}; };
...@@ -918,6 +958,9 @@ static int mchp_pdmc_dt_init(struct mchp_pdmc *dd) ...@@ -918,6 +958,9 @@ static int mchp_pdmc_dt_init(struct mchp_pdmc *dd)
dd->channel_mic_map[i].clk_edge = edge; dd->channel_mic_map[i].clk_edge = edge;
} }
dd->startup_delay_us = 150000;
of_property_read_u32(np, "microchip,startup-delay-us", &dd->startup_delay_us);
return 0; return 0;
} }
......
...@@ -1088,22 +1088,39 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, ...@@ -1088,22 +1088,39 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream,
static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{ {
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
int ret = -EINVAL, _ret = 0; struct snd_soc_component *component;
int ret = -EINVAL, _ret = 0, start_dma_last = 0, i;
int rollback = 0; int rollback = 0;
switch (cmd) { switch (cmd) {
case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
/* Do we need to start dma last? */
for_each_rtd_components(rtd, i, component) {
if (component->driver->start_dma_last) {
start_dma_last = 1;
break;
}
}
ret = snd_soc_link_trigger(substream, cmd, 0); ret = snd_soc_link_trigger(substream, cmd, 0);
if (ret < 0) if (ret < 0)
goto start_err; goto start_err;
ret = snd_soc_pcm_component_trigger(substream, cmd, 0); if (start_dma_last) {
if (ret < 0) ret = snd_soc_pcm_dai_trigger(substream, cmd, 0);
goto start_err; if (ret < 0)
goto start_err;
ret = snd_soc_pcm_component_trigger(substream, cmd, 0);
} else {
ret = snd_soc_pcm_component_trigger(substream, cmd, 0);
if (ret < 0)
goto start_err;
ret = snd_soc_pcm_dai_trigger(substream, cmd, 0); ret = snd_soc_pcm_dai_trigger(substream, cmd, 0);
}
start_err: start_err:
if (ret < 0) if (ret < 0)
rollback = 1; rollback = 1;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment