OSDN Git Service

Merge remote-tracking branch 'asoc/topic/pcm' into for-tiwai
authorMark Brown <broonie@linaro.org>
Thu, 16 Jan 2014 12:42:57 +0000 (12:42 +0000)
committerMark Brown <broonie@linaro.org>
Thu, 16 Jan 2014 12:42:57 +0000 (12:42 +0000)
1  2 
include/sound/pcm.h
include/sound/soc.h
sound/soc/fsl/fsl_ssi.c
sound/soc/soc-pcm.c

diff --combined include/sound/pcm.h
@@@ -381,6 -381,7 +381,6 @@@ struct snd_pcm_substream 
        struct pm_qos_request latency_pm_qos_req; /* pm_qos request */
        size_t buffer_bytes_max;        /* limit ring buffer size */
        struct snd_dma_buffer dma_buffer;
 -      unsigned int dma_buf_id;
        size_t dma_max;
        /* -- hardware operations -- */
        const struct snd_pcm_ops *ops;
@@@ -900,6 -901,8 +900,8 @@@ extern const struct snd_pcm_hw_constrai
  int snd_pcm_limit_hw_rates(struct snd_pcm_runtime *runtime);
  unsigned int snd_pcm_rate_to_rate_bit(unsigned int rate);
  unsigned int snd_pcm_rate_bit_to_rate(unsigned int rate_bit);
+ unsigned int snd_pcm_rate_mask_intersect(unsigned int rates_a,
+                                        unsigned int rates_b);
  
  static inline void snd_pcm_set_runtime_buffer(struct snd_pcm_substream *substream,
                                              struct snd_dma_buffer *bufp)
diff --combined include/sound/soc.h
@@@ -334,7 -334,9 +334,7 @@@ struct snd_soc_jack_pin
  #include <sound/soc-dapm.h>
  #include <sound/soc-dpcm.h>
  
 -#ifdef CONFIG_GPIOLIB
  struct snd_soc_jack_gpio;
 -#endif
  
  typedef int (*hw_write_t)(void *,const char* ,int);
  
@@@ -444,17 -446,6 +444,17 @@@ int snd_soc_jack_add_gpios(struct snd_s
                        struct snd_soc_jack_gpio *gpios);
  void snd_soc_jack_free_gpios(struct snd_soc_jack *jack, int count,
                        struct snd_soc_jack_gpio *gpios);
 +#else
 +static inline int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count,
 +                                       struct snd_soc_jack_gpio *gpios)
 +{
 +      return 0;
 +}
 +
 +static inline void snd_soc_jack_free_gpios(struct snd_soc_jack *jack, int count,
 +                                         struct snd_soc_jack_gpio *gpios)
 +{
 +}
  #endif
  
  /* codec register bit access */
@@@ -589,6 -580,7 +589,6 @@@ struct snd_soc_jack_zone 
   *                   to provide more complex checks (eg, reading an
   *                   ADC).
   */
 -#ifdef CONFIG_GPIOLIB
  struct snd_soc_jack_gpio {
        unsigned int gpio;
        const char *name;
  
        int (*jack_status_check)(void);
  };
 -#endif
  
  struct snd_soc_jack {
        struct mutex mutex;
@@@ -886,8 -879,6 +886,8 @@@ struct snd_soc_dai_link 
  
        /* Symmetry requirements */
        unsigned int symmetric_rates:1;
 +      unsigned int symmetric_channels:1;
 +      unsigned int symmetric_samplebits:1;
  
        /* Do not create a PCM for this DAI link (Backend link) */
        unsigned int no_pcm:1;
        /* This DAI link can route to other DAI links at runtime (Frontend)*/
        unsigned int dynamic:1;
  
+       /* DPCM capture and Playback support */
+       unsigned int dpcm_capture:1;
+       unsigned int dpcm_playback:1;
        /* pmdown_time is ignored at stop */
        unsigned int ignore_pmdown_time:1;
  
diff --combined sound/soc/fsl/fsl_ssi.c
@@@ -38,7 -38,6 +38,7 @@@
  #include <linux/device.h>
  #include <linux/delay.h>
  #include <linux/slab.h>
 +#include <linux/spinlock.h>
  #include <linux/of_address.h>
  #include <linux/of_irq.h>
  #include <linux/of_platform.h>
@@@ -80,8 -79,7 +80,7 @@@ static inline void write_ssi_mask(u32 _
   * ALSA that we support all rates and let the codec driver decide what rates
   * are really supported.
   */
- #define FSLSSI_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
-                         SNDRV_PCM_RATE_CONTINUOUS)
+ #define FSLSSI_I2S_RATES SNDRV_PCM_RATE_CONTINUOUS
  
  /**
   * FSLSSI_I2S_FORMATS: audio formats supported by the SSI
   * @ssi: pointer to the SSI's registers
   * @ssi_phys: physical address of the SSI registers
   * @irq: IRQ of this SSI
 - * @first_stream: pointer to the stream that was opened first
 - * @second_stream: pointer to second stream
   * @playback: the number of playback streams opened
   * @capture: the number of capture streams opened
   * @cpu_dai: the CPU DAI for this device
@@@ -131,6 -131,8 +130,6 @@@ struct fsl_ssi_private 
        struct ccsr_ssi __iomem *ssi;
        dma_addr_t ssi_phys;
        unsigned int irq;
 -      struct snd_pcm_substream *first_stream;
 -      struct snd_pcm_substream *second_stream;
        unsigned int fifo_depth;
        struct snd_soc_dai_driver cpu_dai_drv;
        struct device_attribute dev_attr;
        bool ssi_on_imx;
        bool imx_ac97;
        bool use_dma;
 +      bool baudclk_locked;
 +      u8 i2s_mode;
 +      spinlock_t baudclk_lock;
 +      struct clk *baudclk;
        struct clk *clk;
        struct snd_dmaengine_dai_dma_data dma_params_tx;
        struct snd_dmaengine_dai_dma_data dma_params_rx;
@@@ -322,46 -320,17 +321,46 @@@ static irqreturn_t fsl_ssi_isr(int irq
        return ret;
  }
  
 +static void fsl_ssi_setup_ac97(struct fsl_ssi_private *ssi_private)
 +{
 +      struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
 +
 +      /*
 +       * Setup the clock control register
 +       */
 +      write_ssi(CCSR_SSI_SxCCR_WL(17) | CCSR_SSI_SxCCR_DC(13),
 +                      &ssi->stccr);
 +      write_ssi(CCSR_SSI_SxCCR_WL(17) | CCSR_SSI_SxCCR_DC(13),
 +                      &ssi->srccr);
 +
 +      /*
 +       * Enable AC97 mode and startup the SSI
 +       */
 +      write_ssi(CCSR_SSI_SACNT_AC97EN | CCSR_SSI_SACNT_FV,
 +                      &ssi->sacnt);
 +      write_ssi(0xff, &ssi->saccdis);
 +      write_ssi(0x300, &ssi->saccen);
 +
 +      /*
 +       * Enable SSI, Transmit and Receive. AC97 has to communicate with the
 +       * codec before a stream is started.
 +       */
 +      write_ssi_mask(&ssi->scr, 0, CCSR_SSI_SCR_SSIEN |
 +                      CCSR_SSI_SCR_TE | CCSR_SSI_SCR_RE);
 +
 +      write_ssi(CCSR_SSI_SOR_WAIT(3), &ssi->sor);
 +}
 +
  static int fsl_ssi_setup(struct fsl_ssi_private *ssi_private)
  {
        struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
 -      u8 i2s_mode;
        u8 wm;
        int synchronous = ssi_private->cpu_dai_drv.symmetric_rates;
  
        if (ssi_private->imx_ac97)
 -              i2s_mode = CCSR_SSI_SCR_I2S_MODE_NORMAL | CCSR_SSI_SCR_NET;
 +              ssi_private->i2s_mode = CCSR_SSI_SCR_I2S_MODE_NORMAL | CCSR_SSI_SCR_NET;
        else
 -              i2s_mode = CCSR_SSI_SCR_I2S_MODE_SLAVE;
 +              ssi_private->i2s_mode = CCSR_SSI_SCR_I2S_MODE_SLAVE;
  
        /*
         * Section 16.5 of the MPC8610 reference manual says that the SSI needs
        write_ssi_mask(&ssi->scr,
                CCSR_SSI_SCR_I2S_MODE_MASK | CCSR_SSI_SCR_SYN,
                CCSR_SSI_SCR_TFR_CLK_DIS |
 -              i2s_mode |
 +              ssi_private->i2s_mode |
                (synchronous ? CCSR_SSI_SCR_SYN : 0));
  
        write_ssi(CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 |
         * because it is also running without an active substream. Normally SSI
         * is only enabled when there is a substream.
         */
 -      if (ssi_private->imx_ac97) {
 -              /*
 -               * Setup the clock control register
 -               */
 -              write_ssi(CCSR_SSI_SxCCR_WL(17) | CCSR_SSI_SxCCR_DC(13),
 -                              &ssi->stccr);
 -              write_ssi(CCSR_SSI_SxCCR_WL(17) | CCSR_SSI_SxCCR_DC(13),
 -                              &ssi->srccr);
 -
 -              /*
 -               * Enable AC97 mode and startup the SSI
 -               */
 -              write_ssi(CCSR_SSI_SACNT_AC97EN | CCSR_SSI_SACNT_FV,
 -                              &ssi->sacnt);
 -              write_ssi(0xff, &ssi->saccdis);
 -              write_ssi(0x300, &ssi->saccen);
 -
 -              /*
 -               * Enable SSI, Transmit and Receive
 -               */
 -              write_ssi_mask(&ssi->scr, 0, CCSR_SSI_SCR_SSIEN |
 -                              CCSR_SSI_SCR_TE | CCSR_SSI_SCR_RE);
 -
 -              write_ssi(CCSR_SSI_SOR_WAIT(3), &ssi->sor);
 -      }
 +      if (ssi_private->imx_ac97)
 +              fsl_ssi_setup_ac97(ssi_private);
  
        return 0;
  }
@@@ -438,17 -430,53 +437,17 @@@ static int fsl_ssi_startup(struct snd_p
        struct snd_soc_pcm_runtime *rtd = substream->private_data;
        struct fsl_ssi_private *ssi_private =
                snd_soc_dai_get_drvdata(rtd->cpu_dai);
 -      int synchronous = ssi_private->cpu_dai_drv.symmetric_rates;
 +      unsigned long flags;
  
 -      /*
 -       * If this is the first stream opened, then request the IRQ
 -       * and initialize the SSI registers.
 +      /* First, we only do fsl_ssi_setup() when SSI is going to be active.
 +       * Second, fsl_ssi_setup was already called by ac97_init earlier if
 +       * the driver is in ac97 mode.
         */
 -      if (!ssi_private->first_stream) {
 -              ssi_private->first_stream = substream;
 -
 -              /*
 -               * fsl_ssi_setup was already called by ac97_init earlier if
 -               * the driver is in ac97 mode.
 -               */
 -              if (!ssi_private->imx_ac97)
 -                      fsl_ssi_setup(ssi_private);
 -      } else {
 -              if (synchronous) {
 -                      struct snd_pcm_runtime *first_runtime =
 -                              ssi_private->first_stream->runtime;
 -                      /*
 -                       * This is the second stream open, and we're in
 -                       * synchronous mode, so we need to impose sample
 -                       * sample size constraints. This is because STCCR is
 -                       * used for playback and capture in synchronous mode,
 -                       * so there's no way to specify different word
 -                       * lengths.
 -                       *
 -                       * Note that this can cause a race condition if the
 -                       * second stream is opened before the first stream is
 -                       * fully initialized.  We provide some protection by
 -                       * checking to make sure the first stream is
 -                       * initialized, but it's not perfect.  ALSA sometimes
 -                       * re-initializes the driver with a different sample
 -                       * rate or size.  If the second stream is opened
 -                       * before the first stream has received its final
 -                       * parameters, then the second stream may be
 -                       * constrained to the wrong sample rate or size.
 -                       */
 -                      if (first_runtime->sample_bits) {
 -                              snd_pcm_hw_constraint_minmax(substream->runtime,
 -                                              SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
 -                              first_runtime->sample_bits,
 -                              first_runtime->sample_bits);
 -                      }
 -              }
 -
 -              ssi_private->second_stream = substream;
 +      if (!dai->active && !ssi_private->imx_ac97) {
 +              fsl_ssi_setup(ssi_private);
 +              spin_lock_irqsave(&ssi_private->baudclk_lock, flags);
 +              ssi_private->baudclk_locked = false;
 +              spin_unlock_irqrestore(&ssi_private->baudclk_lock, flags);
        }
  
        return 0;
@@@ -472,7 -500,6 +471,7 @@@ static int fsl_ssi_hw_params(struct snd
  {
        struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai);
        struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
 +      unsigned int channels = params_channels(hw_params);
        unsigned int sample_size =
                snd_pcm_format_width(params_format(hw_params));
        u32 wl = CCSR_SSI_SxCCR_WL(sample_size);
        else
                write_ssi_mask(&ssi->srccr, CCSR_SSI_SxCCR_WL_MASK, wl);
  
 +      if (!ssi_private->imx_ac97)
 +              write_ssi_mask(&ssi->scr,
 +                              CCSR_SSI_SCR_NET | CCSR_SSI_SCR_I2S_MODE_MASK,
 +                              channels == 1 ? 0 : ssi_private->i2s_mode);
 +
 +      return 0;
 +}
 +
 +/**
 + * fsl_ssi_set_dai_fmt - configure Digital Audio Interface Format.
 + */
 +static int fsl_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
 +{
 +      struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai);
 +      struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
 +      u32 strcr = 0, stcr, srcr, scr, mask;
 +
 +      scr = read_ssi(&ssi->scr) & ~(CCSR_SSI_SCR_SYN | CCSR_SSI_SCR_I2S_MODE_MASK);
 +      scr |= CCSR_SSI_SCR_NET;
 +
 +      mask = CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFDIR | CCSR_SSI_STCR_TXDIR |
 +              CCSR_SSI_STCR_TSCKP | CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TFSL |
 +              CCSR_SSI_STCR_TEFS;
 +      stcr = read_ssi(&ssi->stcr) & ~mask;
 +      srcr = read_ssi(&ssi->srcr) & ~mask;
 +
 +      switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
 +      case SND_SOC_DAIFMT_I2S:
 +              switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
 +              case SND_SOC_DAIFMT_CBS_CFS:
 +                      ssi_private->i2s_mode = CCSR_SSI_SCR_I2S_MODE_MASTER;
 +                      break;
 +              case SND_SOC_DAIFMT_CBM_CFM:
 +                      ssi_private->i2s_mode = CCSR_SSI_SCR_I2S_MODE_SLAVE;
 +                      break;
 +              default:
 +                      return -EINVAL;
 +              }
 +              scr |= ssi_private->i2s_mode;
 +
 +              /* Data on rising edge of bclk, frame low, 1clk before data */
 +              strcr |= CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TSCKP |
 +                      CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TEFS;
 +              break;
 +      case SND_SOC_DAIFMT_LEFT_J:
 +              /* Data on rising edge of bclk, frame high */
 +              strcr |= CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TSCKP;
 +              break;
 +      case SND_SOC_DAIFMT_DSP_A:
 +              /* Data on rising edge of bclk, frame high, 1clk before data */
 +              strcr |= CCSR_SSI_STCR_TFSL | CCSR_SSI_STCR_TSCKP |
 +                      CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TEFS;
 +              break;
 +      case SND_SOC_DAIFMT_DSP_B:
 +              /* Data on rising edge of bclk, frame high */
 +              strcr |= CCSR_SSI_STCR_TFSL | CCSR_SSI_STCR_TSCKP |
 +                      CCSR_SSI_STCR_TXBIT0;
 +              break;
 +      default:
 +              return -EINVAL;
 +      }
 +
 +      /* DAI clock inversion */
 +      switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
 +      case SND_SOC_DAIFMT_NB_NF:
 +              /* Nothing to do for both normal cases */
 +              break;
 +      case SND_SOC_DAIFMT_IB_NF:
 +              /* Invert bit clock */
 +              strcr ^= CCSR_SSI_STCR_TSCKP;
 +              break;
 +      case SND_SOC_DAIFMT_NB_IF:
 +              /* Invert frame clock */
 +              strcr ^= CCSR_SSI_STCR_TFSI;
 +              break;
 +      case SND_SOC_DAIFMT_IB_IF:
 +              /* Invert both clocks */
 +              strcr ^= CCSR_SSI_STCR_TSCKP;
 +              strcr ^= CCSR_SSI_STCR_TFSI;
 +              break;
 +      default:
 +              return -EINVAL;
 +      }
 +
 +      /* DAI clock master masks */
 +      switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
 +      case SND_SOC_DAIFMT_CBS_CFS:
 +              strcr |= CCSR_SSI_STCR_TFDIR | CCSR_SSI_STCR_TXDIR;
 +              scr |= CCSR_SSI_SCR_SYS_CLK_EN;
 +              break;
 +      case SND_SOC_DAIFMT_CBM_CFM:
 +              scr &= ~CCSR_SSI_SCR_SYS_CLK_EN;
 +              break;
 +      default:
 +              return -EINVAL;
 +      }
 +
 +      stcr |= strcr;
 +      srcr |= strcr;
 +
 +      if (ssi_private->cpu_dai_drv.symmetric_rates) {
 +              /* Need to clear RXDIR when using SYNC mode */
 +              srcr &= ~CCSR_SSI_SRCR_RXDIR;
 +              scr |= CCSR_SSI_SCR_SYN;
 +      }
 +
 +      write_ssi(stcr, &ssi->stcr);
 +      write_ssi(srcr, &ssi->srcr);
 +      write_ssi(scr, &ssi->scr);
 +
 +      return 0;
 +}
 +
 +/**
 + * fsl_ssi_set_dai_sysclk - configure Digital Audio Interface bit clock
 + *
 + * Note: This function can be only called when using SSI as DAI master
 + *
 + * Quick instruction for parameters:
 + * freq: Output BCLK frequency = samplerate * 32 (fixed) * channels
 + * dir: SND_SOC_CLOCK_OUT -> TxBCLK, SND_SOC_CLOCK_IN -> RxBCLK.
 + */
 +static int fsl_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
 +                                int clk_id, unsigned int freq, int dir)
 +{
 +      struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai);
 +      struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
 +      int synchronous = ssi_private->cpu_dai_drv.symmetric_rates, ret;
 +      u32 pm = 999, div2, psr, stccr, mask, afreq, factor, i;
 +      unsigned long flags, clkrate, baudrate, tmprate;
 +      u64 sub, savesub = 100000;
 +
 +      /* Don't apply it to any non-baudclk circumstance */
 +      if (IS_ERR(ssi_private->baudclk))
 +              return -EINVAL;
 +
 +      /* It should be already enough to divide clock by setting pm alone */
 +      psr = 0;
 +      div2 = 0;
 +
 +      factor = (div2 + 1) * (7 * psr + 1) * 2;
 +
 +      for (i = 0; i < 255; i++) {
 +              /* The bclk rate must be smaller than 1/5 sysclk rate */
 +              if (factor * (i + 1) < 5)
 +                      continue;
 +
 +              tmprate = freq * factor * (i + 2);
 +              clkrate = clk_round_rate(ssi_private->baudclk, tmprate);
 +
 +              do_div(clkrate, factor);
 +              afreq = (u32)clkrate / (i + 1);
 +
 +              if (freq == afreq)
 +                      sub = 0;
 +              else if (freq / afreq == 1)
 +                      sub = freq - afreq;
 +              else if (afreq / freq == 1)
 +                      sub = afreq - freq;
 +              else
 +                      continue;
 +
 +              /* Calculate the fraction */
 +              sub *= 100000;
 +              do_div(sub, freq);
 +
 +              if (sub < savesub) {
 +                      baudrate = tmprate;
 +                      savesub = sub;
 +                      pm = i;
 +              }
 +
 +              /* We are lucky */
 +              if (savesub == 0)
 +                      break;
 +      }
 +
 +      /* No proper pm found if it is still remaining the initial value */
 +      if (pm == 999) {
 +              dev_err(cpu_dai->dev, "failed to handle the required sysclk\n");
 +              return -EINVAL;
 +      }
 +
 +      stccr = CCSR_SSI_SxCCR_PM(pm + 1) | (div2 ? CCSR_SSI_SxCCR_DIV2 : 0) |
 +              (psr ? CCSR_SSI_SxCCR_PSR : 0);
 +      mask = CCSR_SSI_SxCCR_PM_MASK | CCSR_SSI_SxCCR_DIV2 | CCSR_SSI_SxCCR_PSR;
 +
 +      if (dir == SND_SOC_CLOCK_OUT || synchronous)
 +              write_ssi_mask(&ssi->stccr, mask, stccr);
 +      else
 +              write_ssi_mask(&ssi->srccr, mask, stccr);
 +
 +      spin_lock_irqsave(&ssi_private->baudclk_lock, flags);
 +      if (!ssi_private->baudclk_locked) {
 +              ret = clk_set_rate(ssi_private->baudclk, baudrate);
 +              if (ret) {
 +                      spin_unlock_irqrestore(&ssi_private->baudclk_lock, flags);
 +                      dev_err(cpu_dai->dev, "failed to set baudclk rate\n");
 +                      return -EINVAL;
 +              }
 +              ssi_private->baudclk_locked = true;
 +      }
 +      spin_unlock_irqrestore(&ssi_private->baudclk_lock, flags);
 +
 +      return 0;
 +}
 +
 +/**
 + * fsl_ssi_set_dai_tdm_slot - set TDM slot number
 + *
 + * Note: This function can be only called when using SSI as DAI master
 + */
 +static int fsl_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask,
 +                              u32 rx_mask, int slots, int slot_width)
 +{
 +      struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai);
 +      struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
 +      u32 val;
 +
 +      /* The slot number should be >= 2 if using Network mode or I2S mode */
 +      val = read_ssi(&ssi->scr) & (CCSR_SSI_SCR_I2S_MODE_MASK | CCSR_SSI_SCR_NET);
 +      if (val && slots < 2) {
 +              dev_err(cpu_dai->dev, "slot number should be >= 2 in I2S or NET\n");
 +              return -EINVAL;
 +      }
 +
 +      write_ssi_mask(&ssi->stccr, CCSR_SSI_SxCCR_DC_MASK,
 +                      CCSR_SSI_SxCCR_DC(slots));
 +      write_ssi_mask(&ssi->srccr, CCSR_SSI_SxCCR_DC_MASK,
 +                      CCSR_SSI_SxCCR_DC(slots));
 +
 +      /* The register SxMSKs needs SSI to provide essential clock due to
 +       * hardware design. So we here temporarily enable SSI to set them.
 +       */
 +      val = read_ssi(&ssi->scr) & CCSR_SSI_SCR_SSIEN;
 +      write_ssi_mask(&ssi->scr, 0, CCSR_SSI_SCR_SSIEN);
 +
 +      write_ssi(tx_mask, &ssi->stmsk);
 +      write_ssi(rx_mask, &ssi->srmsk);
 +
 +      write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_SSIEN, val);
 +
        return 0;
  }
  
@@@ -763,7 -548,6 +762,7 @@@ static int fsl_ssi_trigger(struct snd_p
        struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai);
        struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
        unsigned int sier_bits;
 +      unsigned long flags;
  
        /*
         *  Enable only the interrupts and DMA requests
                        write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_RE, 0);
  
                if (!ssi_private->imx_ac97 && (read_ssi(&ssi->scr) &
 -                                      (CCSR_SSI_SCR_TE | CCSR_SSI_SCR_RE)) == 0)
 +                                      (CCSR_SSI_SCR_TE | CCSR_SSI_SCR_RE)) == 0) {
                        write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_SSIEN, 0);
 +                      spin_lock_irqsave(&ssi_private->baudclk_lock, flags);
 +                      ssi_private->baudclk_locked = false;
 +                      spin_unlock_irqrestore(&ssi_private->baudclk_lock, flags);
 +              }
                break;
  
        default:
        return 0;
  }
  
 -/**
 - * fsl_ssi_shutdown: shutdown the SSI
 - *
 - * Shutdown the SSI if there are no other substreams open.
 - */
 -static void fsl_ssi_shutdown(struct snd_pcm_substream *substream,
 -                           struct snd_soc_dai *dai)
 -{
 -      struct snd_soc_pcm_runtime *rtd = substream->private_data;
 -      struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai);
 -
 -      if (ssi_private->first_stream == substream)
 -              ssi_private->first_stream = ssi_private->second_stream;
 -
 -      ssi_private->second_stream = NULL;
 -}
 -
  static int fsl_ssi_dai_probe(struct snd_soc_dai *dai)
  {
        struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(dai);
  static const struct snd_soc_dai_ops fsl_ssi_dai_ops = {
        .startup        = fsl_ssi_startup,
        .hw_params      = fsl_ssi_hw_params,
 -      .shutdown       = fsl_ssi_shutdown,
 +      .set_fmt        = fsl_ssi_set_dai_fmt,
 +      .set_sysclk     = fsl_ssi_set_dai_sysclk,
 +      .set_tdm_slot   = fsl_ssi_set_dai_tdm_slot,
        .trigger        = fsl_ssi_trigger,
  };
  
  static struct snd_soc_dai_driver fsl_ssi_dai_template = {
        .probe = fsl_ssi_dai_probe,
        .playback = {
 -              /* The SSI does not support monaural audio. */
 -              .channels_min = 2,
 +              .channels_min = 1,
                .channels_max = 2,
                .rates = FSLSSI_I2S_RATES,
                .formats = FSLSSI_I2S_FORMATS,
        },
        .capture = {
 -              .channels_min = 2,
 +              .channels_min = 1,
                .channels_max = 2,
                .rates = FSLSSI_I2S_RATES,
                .formats = FSLSSI_I2S_FORMATS,
@@@ -913,6 -709,7 +912,6 @@@ static int fsl_ssi_ac97_trigger(struct 
  
  static const struct snd_soc_dai_ops fsl_ssi_ac97_dai_ops = {
        .startup        = fsl_ssi_startup,
 -      .shutdown       = fsl_ssi_shutdown,
        .trigger        = fsl_ssi_ac97_trigger,
  };
  
@@@ -1137,11 -934,8 +1136,11 @@@ static int fsl_ssi_probe(struct platfor
        }
  
        /* Are the RX and the TX clocks locked? */
 -      if (!of_find_property(np, "fsl,ssi-asynchronous", NULL))
 +      if (!of_find_property(np, "fsl,ssi-asynchronous", NULL)) {
                ssi_private->cpu_dai_drv.symmetric_rates = 1;
 +              ssi_private->cpu_dai_drv.symmetric_channels = 1;
 +              ssi_private->cpu_dai_drv.symmetric_samplebits = 1;
 +      }
  
        /* Determine the FIFO depth. */
        iprop = of_get_property(np, "fsl,fifo-depth", NULL);
                  /* Older 8610 DTs didn't have the fifo-depth property */
                ssi_private->fifo_depth = 8;
  
 +      ssi_private->baudclk_locked = false;
 +      spin_lock_init(&ssi_private->baudclk_lock);
 +
        if (of_device_is_compatible(pdev->dev.of_node, "fsl,imx21-ssi")) {
                u32 dma_events[2];
                ssi_private->ssi_on_imx = true;
                        goto error_irqmap;
                }
  
 +              /* For those SLAVE implementations, we ingore non-baudclk cases
 +               * and, instead, abandon MASTER mode that needs baud clock.
 +               */
 +              ssi_private->baudclk = devm_clk_get(&pdev->dev, "baud");
 +              if (IS_ERR(ssi_private->baudclk))
 +                      dev_warn(&pdev->dev, "could not get baud clock: %d\n", ret);
 +              else
 +                      clk_prepare_enable(ssi_private->baudclk);
 +
                /*
                 * We have burstsize be "fifo_depth - 2" to match the SSI
                 * watermark setting in fsl_ssi_startup().
@@@ -1319,17 -1101,16 +1318,17 @@@ done
        return 0;
  
  error_dai:
 -      if (ssi_private->ssi_on_imx)
 -              imx_pcm_dma_exit(pdev);
        snd_soc_unregister_component(&pdev->dev);
  
  error_dev:
        device_remove_file(&pdev->dev, dev_attr);
  
  error_clk:
 -      if (ssi_private->ssi_on_imx)
 +      if (ssi_private->ssi_on_imx) {
 +              if (!IS_ERR(ssi_private->baudclk))
 +                      clk_disable_unprepare(ssi_private->baudclk);
                clk_disable_unprepare(ssi_private->clk);
 +      }
  
  error_irqmap:
        irq_dispose_mapping(ssi_private->irq);
@@@ -1343,13 -1124,12 +1342,13 @@@ static int fsl_ssi_remove(struct platfo
  
        if (!ssi_private->new_binding)
                platform_device_unregister(ssi_private->pdev);
 -      if (ssi_private->ssi_on_imx)
 -              imx_pcm_dma_exit(pdev);
        snd_soc_unregister_component(&pdev->dev);
        device_remove_file(&pdev->dev, &ssi_private->dev_attr);
 -      if (ssi_private->ssi_on_imx)
 +      if (ssi_private->ssi_on_imx) {
 +              if (!IS_ERR(ssi_private->baudclk))
 +                      clk_disable_unprepare(ssi_private->baudclk);
                clk_disable_unprepare(ssi_private->clk);
 +      }
        irq_dispose_mapping(ssi_private->irq);
  
        return 0;
diff --combined sound/soc/soc-pcm.c
@@@ -84,117 -84,35 +84,117 @@@ static int soc_pcm_apply_symmetry(struc
        struct snd_soc_pcm_runtime *rtd = substream->private_data;
        int ret;
  
 -      if (!soc_dai->driver->symmetric_rates &&
 -          !rtd->dai_link->symmetric_rates)
 -              return 0;
 -
 -      /* This can happen if multiple streams are starting simultaneously -
 -       * the second can need to get its constraints before the first has
 -       * picked a rate.  Complain and allow the application to carry on.
 -       */
 -      if (!soc_dai->rate) {
 -              dev_warn(soc_dai->dev,
 -                       "ASoC: Not enforcing symmetric_rates due to race\n");
 -              return 0;
 +      if (soc_dai->rate && (soc_dai->driver->symmetric_rates ||
 +                              rtd->dai_link->symmetric_rates)) {
 +              dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %dHz rate\n",
 +                              soc_dai->rate);
 +
 +              ret = snd_pcm_hw_constraint_minmax(substream->runtime,
 +                                              SNDRV_PCM_HW_PARAM_RATE,
 +                                              soc_dai->rate, soc_dai->rate);
 +              if (ret < 0) {
 +                      dev_err(soc_dai->dev,
 +                              "ASoC: Unable to apply rate constraint: %d\n",
 +                              ret);
 +                      return ret;
 +              }
        }
  
 -      dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %dHz rate\n", soc_dai->rate);
 +      if (soc_dai->channels && (soc_dai->driver->symmetric_channels ||
 +                              rtd->dai_link->symmetric_channels)) {
 +              dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %d channel(s)\n",
 +                              soc_dai->channels);
  
 -      ret = snd_pcm_hw_constraint_minmax(substream->runtime,
 -                                         SNDRV_PCM_HW_PARAM_RATE,
 -                                         soc_dai->rate, soc_dai->rate);
 -      if (ret < 0) {
 -              dev_err(soc_dai->dev,
 -                      "ASoC: Unable to apply rate symmetry constraint: %d\n",
 -                      ret);
 -              return ret;
 +              ret = snd_pcm_hw_constraint_minmax(substream->runtime,
 +                                              SNDRV_PCM_HW_PARAM_CHANNELS,
 +                                              soc_dai->channels,
 +                                              soc_dai->channels);
 +              if (ret < 0) {
 +                      dev_err(soc_dai->dev,
 +                              "ASoC: Unable to apply channel symmetry constraint: %d\n",
 +                              ret);
 +                      return ret;
 +              }
 +      }
 +
 +      if (soc_dai->sample_bits && (soc_dai->driver->symmetric_samplebits ||
 +                              rtd->dai_link->symmetric_samplebits)) {
 +              dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %d sample bits\n",
 +                              soc_dai->sample_bits);
 +
 +              ret = snd_pcm_hw_constraint_minmax(substream->runtime,
 +                                              SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
 +                                              soc_dai->sample_bits,
 +                                              soc_dai->sample_bits);
 +              if (ret < 0) {
 +                      dev_err(soc_dai->dev,
 +                              "ASoC: Unable to apply sample bits symmetry constraint: %d\n",
 +                              ret);
 +                      return ret;
 +              }
 +      }
 +
 +      return 0;
 +}
 +
 +static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream,
 +                              struct snd_pcm_hw_params *params)
 +{
 +      struct snd_soc_pcm_runtime *rtd = substream->private_data;
 +      struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
 +      struct snd_soc_dai *codec_dai = rtd->codec_dai;
 +      unsigned int rate, channels, sample_bits, symmetry;
 +
 +      rate = params_rate(params);
 +      channels = params_channels(params);
 +      sample_bits = snd_pcm_format_physical_width(params_format(params));
 +
 +      /* reject unmatched parameters when applying symmetry */
 +      symmetry = cpu_dai->driver->symmetric_rates ||
 +              codec_dai->driver->symmetric_rates ||
 +              rtd->dai_link->symmetric_rates;
 +      if (symmetry && cpu_dai->rate && cpu_dai->rate != rate) {
 +              dev_err(rtd->dev, "ASoC: unmatched rate symmetry: %d - %d\n",
 +                              cpu_dai->rate, rate);
 +              return -EINVAL;
 +      }
 +
 +      symmetry = cpu_dai->driver->symmetric_channels ||
 +              codec_dai->driver->symmetric_channels ||
 +              rtd->dai_link->symmetric_channels;
 +      if (symmetry && cpu_dai->channels && cpu_dai->channels != channels) {
 +              dev_err(rtd->dev, "ASoC: unmatched channel symmetry: %d - %d\n",
 +                              cpu_dai->channels, channels);
 +              return -EINVAL;
 +      }
 +
 +      symmetry = cpu_dai->driver->symmetric_samplebits ||
 +              codec_dai->driver->symmetric_samplebits ||
 +              rtd->dai_link->symmetric_samplebits;
 +      if (symmetry && cpu_dai->sample_bits && cpu_dai->sample_bits != sample_bits) {
 +              dev_err(rtd->dev, "ASoC: unmatched sample bits symmetry: %d - %d\n",
 +                              cpu_dai->sample_bits, sample_bits);
 +              return -EINVAL;
        }
  
        return 0;
  }
  
 +static bool soc_pcm_has_symmetry(struct snd_pcm_substream *substream)
 +{
 +      struct snd_soc_pcm_runtime *rtd = substream->private_data;
 +      struct snd_soc_dai_driver *cpu_driver = rtd->cpu_dai->driver;
 +      struct snd_soc_dai_driver *codec_driver = rtd->codec_dai->driver;
 +      struct snd_soc_dai_link *link = rtd->dai_link;
 +
 +      return cpu_driver->symmetric_rates || codec_driver->symmetric_rates ||
 +              link->symmetric_rates || cpu_driver->symmetric_channels ||
 +              codec_driver->symmetric_channels || link->symmetric_channels ||
 +              cpu_driver->symmetric_samplebits ||
 +              codec_driver->symmetric_samplebits ||
 +              link->symmetric_samplebits;
 +}
 +
  /*
   * List of sample sizes that might go over the bus for parameter
   * application.  There ought to be a wildcard sample size for things
@@@ -240,14 -158,15 +240,15 @@@ static void soc_pcm_init_runtime_hw(str
                cpu_stream->channels_min);
        hw->channels_max = min(codec_stream->channels_max,
                cpu_stream->channels_max);
-       hw->formats = codec_stream->formats & cpu_stream->formats;
-       hw->rates = codec_stream->rates & cpu_stream->rates;
-       if (codec_stream->rates
-               & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
-               hw->rates |= cpu_stream->rates;
-       if (cpu_stream->rates
-               & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
-               hw->rates |= codec_stream->rates;
+       if (hw->formats)
+               hw->formats &= codec_stream->formats & cpu_stream->formats;
+       else
+               hw->formats = codec_stream->formats & cpu_stream->formats;
+       hw->rates = snd_pcm_rate_mask_intersect(codec_stream->rates,
+               cpu_stream->rates);
+       hw->rate_min = 0;
+       hw->rate_max = UINT_MAX;
  
        snd_pcm_limit_hw_rates(runtime);
  
@@@ -331,9 -250,6 +332,9 @@@ static int soc_pcm_open(struct snd_pcm_
                        &cpu_dai_drv->capture);
        }
  
 +      if (soc_pcm_has_symmetry(substream))
 +              runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX;
 +
        ret = -EINVAL;
        if (!runtime->hw.rates) {
                printk(KERN_ERR "ASoC: %s <-> %s No matching rates\n",
@@@ -481,6 -397,11 +482,6 @@@ static int soc_pcm_close(struct snd_pcm
        if (!codec_dai->active)
                codec_dai->rate = 0;
  
 -      /* Muting the DAC suppresses artifacts caused during digital
 -       * shutdown, for example from stopping clocks.
 -       */
 -      snd_soc_dai_digital_mute(codec_dai, 1, substream->stream);
 -
        if (cpu_dai->driver->ops->shutdown)
                cpu_dai->driver->ops->shutdown(substream, cpu_dai);
  
@@@ -611,10 -532,6 +612,10 @@@ static int soc_pcm_hw_params(struct snd
  
        mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
  
 +      ret = soc_pcm_params_symmetry(substream, params);
 +      if (ret)
 +              goto out;
 +
        if (rtd->dai_link->ops && rtd->dai_link->ops->hw_params) {
                ret = rtd->dai_link->ops->hw_params(substream, params);
                if (ret < 0) {
                }
        }
  
 -      /* store the rate for each DAIs */
 +      /* store the parameters for each DAIs */
        cpu_dai->rate = params_rate(params);
 +      cpu_dai->channels = params_channels(params);
 +      cpu_dai->sample_bits =
 +              snd_pcm_format_physical_width(params_format(params));
 +
        codec_dai->rate = params_rate(params);
 +      codec_dai->channels = params_channels(params);
 +      codec_dai->sample_bits =
 +              snd_pcm_format_physical_width(params_format(params));
  
  out:
        mutex_unlock(&rtd->pcm_mutex);
@@@ -691,26 -601,12 +692,26 @@@ static int soc_pcm_hw_free(struct snd_p
        struct snd_soc_platform *platform = rtd->platform;
        struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
        struct snd_soc_dai *codec_dai = rtd->codec_dai;
 -      struct snd_soc_codec *codec = rtd->codec;
 +      bool playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
  
        mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
  
 +      /* clear the corresponding DAIs parameters when going to be inactive */
 +      if (cpu_dai->active == 1) {
 +              cpu_dai->rate = 0;
 +              cpu_dai->channels = 0;
 +              cpu_dai->sample_bits = 0;
 +      }
 +
 +      if (codec_dai->active == 1) {
 +              codec_dai->rate = 0;
 +              codec_dai->channels = 0;
 +              codec_dai->sample_bits = 0;
 +      }
 +
        /* apply codec digital mute */
 -      if (!codec->active)
 +      if ((playback && codec_dai->playback_active == 1) ||
 +          (!playback && codec_dai->capture_active == 1))
                snd_soc_dai_digital_mute(codec_dai, 1, substream->stream);
  
        /* free any machine hw params */
@@@ -776,7 -672,7 +777,7 @@@ static int soc_pcm_bespoke_trigger(stru
                        return ret;
        }
  
 -      if (platform->driver->ops && platform->driver->bespoke_trigger) {
 +      if (platform->driver->bespoke_trigger) {
                ret = platform->driver->bespoke_trigger(substream, cmd);
                if (ret < 0)
                        return ret;
@@@ -1235,20 -1131,6 +1236,20 @@@ unwind
        return err;
  }
  
 +static void dpcm_init_runtime_hw(struct snd_pcm_runtime *runtime,
 +      struct snd_soc_pcm_stream *stream)
 +{
 +      runtime->hw.rate_min = stream->rate_min;
 +      runtime->hw.rate_max = stream->rate_max;
 +      runtime->hw.channels_min = stream->channels_min;
 +      runtime->hw.channels_max = stream->channels_max;
 +      if (runtime->hw.formats)
 +              runtime->hw.formats &= stream->formats;
 +      else
 +              runtime->hw.formats = stream->formats;
 +      runtime->hw.rates = stream->rates;
 +}
 +
  static void dpcm_set_fe_runtime(struct snd_pcm_substream *substream)
  {
        struct snd_pcm_runtime *runtime = substream->runtime;
        struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
        struct snd_soc_dai_driver *cpu_dai_drv = cpu_dai->driver;
  
 -      if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
 -              runtime->hw.rate_min = cpu_dai_drv->playback.rate_min;
 -              runtime->hw.rate_max = cpu_dai_drv->playback.rate_max;
 -              runtime->hw.channels_min = cpu_dai_drv->playback.channels_min;
 -              runtime->hw.channels_max = cpu_dai_drv->playback.channels_max;
 -              runtime->hw.formats &= cpu_dai_drv->playback.formats;
 -              runtime->hw.rates = cpu_dai_drv->playback.rates;
 -      } else {
 -              runtime->hw.rate_min = cpu_dai_drv->capture.rate_min;
 -              runtime->hw.rate_max = cpu_dai_drv->capture.rate_max;
 -              runtime->hw.channels_min = cpu_dai_drv->capture.channels_min;
 -              runtime->hw.channels_max = cpu_dai_drv->capture.channels_max;
 -              runtime->hw.formats &= cpu_dai_drv->capture.formats;
 -              runtime->hw.rates = cpu_dai_drv->capture.rates;
 -      }
 +      if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 +              dpcm_init_runtime_hw(runtime, &cpu_dai_drv->playback);
 +      else
 +              dpcm_init_runtime_hw(runtime, &cpu_dai_drv->capture);
  }
  
  static int dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream)
@@@ -2140,10 -2033,8 +2141,8 @@@ int soc_new_pcm(struct snd_soc_pcm_runt
        int ret = 0, playback = 0, capture = 0;
  
        if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) {
-               if (cpu_dai->driver->playback.channels_min)
-                       playback = 1;
-               if (cpu_dai->driver->capture.channels_min)
-                       capture = 1;
+               playback = rtd->dai_link->dpcm_playback;
+               capture = rtd->dai_link->dpcm_capture;
        } else {
                if (codec_dai->driver->playback.channels_min &&
                    cpu_dai->driver->playback.channels_min)