ASoC: mchp-i2s-mcc: Fix simultaneous capture and playback in master mode
authorCodrin Ciubotariu <codrin.ciubotariu@microchip.com>
Tue, 20 Aug 2019 16:24:11 +0000 (19:24 +0300)
committerMark Brown <broonie@kernel.org>
Tue, 20 Aug 2019 17:39:46 +0000 (18:39 +0100)
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: Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
Link: https://lore.kernel.org/r/20190820162411.24836-4-codrin.ciubotariu@microchip.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/atmel/mchp-i2s-mcc.c

index 86495883ca3f1c7b1b89672ca2573c86ccf6d3ee..9a406144b18ff1c0c697b5327ef8453f5d016795 100644 (file)
@@ -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 {
+       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;