Commit c9cff337 authored by Codrin Ciubotariu's avatar Codrin Ciubotariu Committed by Mark Brown

ASoC: mchp-i2s-mcc: Fix simultaneous capture and playback in master mode

This controller supports capture and playback running at the same time,
with the limitation that both capture and playback must be configured the
same way (sample rate, sample format, number of channels, etc). For this,
we have to assure that the configuration registers look the same when
capture and playback are initiated.
This patch fixes a bug in which the controller is in master mode and the
hw_params() callback fails for the second audio stream. The fail occurs
because the divisors are calculated after comparing the configuration
registers for capture and playback. The fix consists in calculating the
divisors before comparing the configuration registers. BCLK and LRC are
then configured and started only if the controller is not already running.

Fixes: 7e0cdf545a55 ("ASoC: mchp-i2s-mcc: add driver for I2SC Multi-Channel Controller")
Signed-off-by: default avatarCodrin Ciubotariu <codrin.ciubotariu@microchip.com>
Link: https://lore.kernel.org/r/20190820162411.24836-4-codrin.ciubotariu@microchip.comSigned-off-by: default avatarMark Brown <broonie@kernel.org>
parent bbf9a127
......@@ -392,11 +392,11 @@ static int mchp_i2s_mcc_clk_get_rate_diff(struct clk *clk,
}
static int mchp_i2s_mcc_config_divs(struct mchp_i2s_mcc_dev *dev,
unsigned int bclk, unsigned int *mra)
unsigned int bclk, unsigned int *mra,
unsigned long *best_rate)
{
unsigned long clk_rate;
unsigned long lcm_rate;
unsigned long best_rate = 0;
unsigned long best_diff_rate = ~0;
unsigned int sysclk;
struct clk *best_clk = NULL;
......@@ -423,7 +423,7 @@ static int mchp_i2s_mcc_config_divs(struct mchp_i2s_mcc_dev *dev,
(clk_rate == bclk || clk_rate / (bclk * 2) <= GENMASK(5, 0));
clk_rate += lcm_rate) {
ret = mchp_i2s_mcc_clk_get_rate_diff(dev->gclk, clk_rate,
&best_clk, &best_rate,
&best_clk, best_rate,
&best_diff_rate);
if (ret) {
dev_err(dev->dev, "gclk error for rate %lu: %d",
......@@ -437,7 +437,7 @@ static int mchp_i2s_mcc_config_divs(struct mchp_i2s_mcc_dev *dev,
}
ret = mchp_i2s_mcc_clk_get_rate_diff(dev->pclk, clk_rate,
&best_clk, &best_rate,
&best_clk, best_rate,
&best_diff_rate);
if (ret) {
dev_err(dev->dev, "pclk error for rate %lu: %d",
......@@ -459,33 +459,17 @@ static int mchp_i2s_mcc_config_divs(struct mchp_i2s_mcc_dev *dev,
dev_dbg(dev->dev, "source CLK is %s with rate %lu, diff %lu\n",
best_clk == dev->pclk ? "pclk" : "gclk",
best_rate, best_diff_rate);
/* set the rate */
ret = clk_set_rate(best_clk, best_rate);
if (ret) {
dev_err(dev->dev, "unable to set rate %lu to %s: %d\n",
best_rate, best_clk == dev->pclk ? "PCLK" : "GCLK",
ret);
return ret;
}
*best_rate, best_diff_rate);
/* Configure divisors */
if (dev->sysclk)
*mra |= MCHP_I2SMCC_MRA_IMCKDIV(best_rate / (2 * sysclk));
*mra |= MCHP_I2SMCC_MRA_ISCKDIV(best_rate / (2 * bclk));
*mra |= MCHP_I2SMCC_MRA_IMCKDIV(*best_rate / (2 * sysclk));
*mra |= MCHP_I2SMCC_MRA_ISCKDIV(*best_rate / (2 * bclk));
if (best_clk == dev->gclk) {
if (best_clk == dev->gclk)
*mra |= MCHP_I2SMCC_MRA_SRCCLK_GCLK;
ret = clk_prepare(dev->gclk);
if (ret < 0)
dev_err(dev->dev, "unable to prepare GCLK: %d\n", ret);
else
dev->gclk_use = 1;
} else {
*mra |= MCHP_I2SMCC_MRA_SRCCLK_PCLK;
dev->gclk_use = 0;
}
return 0;
}
......@@ -502,6 +486,7 @@ static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
unsigned long rate = 0;
struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai);
u32 mra = 0;
u32 mrb = 0;
......@@ -640,6 +625,17 @@ static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream,
return -EINVAL;
}
if (set_divs) {
bclk_rate = frame_length * params_rate(params);
ret = mchp_i2s_mcc_config_divs(dev, bclk_rate, &mra,
&rate);
if (ret) {
dev_err(dev->dev,
"unable to configure the divisors: %d\n", ret);
return ret;
}
}
/*
* If we are already running, the wanted setup must be
* the same with the one that's currently ongoing
......@@ -656,19 +652,27 @@ static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream,
return 0;
}
/* Save the number of channels to know what interrupts to enable */
dev->channels = channels;
if (set_divs) {
bclk_rate = frame_length * params_rate(params);
ret = mchp_i2s_mcc_config_divs(dev, bclk_rate, &mra);
if (mra & MCHP_I2SMCC_MRA_SRCCLK_GCLK && !dev->gclk_use) {
/* set the rate */
ret = clk_set_rate(dev->gclk, rate);
if (ret) {
dev_err(dev->dev, "unable to configure the divisors: %d\n",
ret);
dev_err(dev->dev,
"unable to set rate %lu to GCLK: %d\n",
rate, ret);
return ret;
}
ret = clk_prepare(dev->gclk);
if (ret < 0) {
dev_err(dev->dev, "unable to prepare GCLK: %d\n", ret);
return ret;
}
dev->gclk_use = 1;
}
/* Save the number of channels to know what interrupts to enable */
dev->channels = channels;
ret = regmap_write(dev->regmap, MCHP_I2SMCC_MRA, mra);
if (ret < 0)
return ret;
......
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