ASoC: TWL4030: Only update the needed bits in *set_dai_sysclk
[safe/jmp/linux-2.6] / sound / soc / fsl / fsl_ssi.c
index b2a11b0..93f0f38 100644 (file)
         SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE)
 #endif
 
+/* SIER bitflag of interrupts to enable */
+#define SIER_FLAGS (CCSR_SSI_SIER_TFRC_EN | CCSR_SSI_SIER_TDMAE | \
+                   CCSR_SSI_SIER_TIE | CCSR_SSI_SIER_TUE0_EN | \
+                   CCSR_SSI_SIER_TUE1_EN | CCSR_SSI_SIER_RFRC_EN | \
+                   CCSR_SSI_SIER_RDMAE | CCSR_SSI_SIER_RIE | \
+                   CCSR_SSI_SIER_ROE0_EN | CCSR_SSI_SIER_ROE1_EN)
+
 /**
  * fsl_ssi_private: per-SSI private data
  *
  * @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
  * @dev: struct device pointer
  * @playback: the number of playback streams opened
  * @capture: the number of capture streams opened
+ * @asynchronous: 0=synchronous mode, 1=asynchronous mode
  * @cpu_dai: the CPU DAI for this device
  * @dev_attr: the sysfs device attribute structure
  * @stats: SSI statistics
@@ -79,10 +89,13 @@ 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;
        struct device *dev;
        unsigned int playback;
        unsigned int capture;
-       struct snd_soc_cpu_dai cpu_dai;
+       int asynchronous;
+       struct snd_soc_dai cpu_dai;
        struct device_attribute dev_attr;
 
        struct {
@@ -134,7 +147,7 @@ static irqreturn_t fsl_ssi_isr(int irq, void *dev_id)
           were interrupted for.  We mask it with the Interrupt Enable register
           so that we only check for events that we're interested in.
         */
-       sisr = in_be32(&ssi->sisr) & in_be32(&ssi->sier);
+       sisr = in_be32(&ssi->sisr) & SIER_FLAGS;
 
        if (sisr & CCSR_SSI_SISR_RFRC) {
                ssi_private->stats.rfrc++;
@@ -262,7 +275,8 @@ static irqreturn_t fsl_ssi_isr(int irq, void *dev_id)
  * If this is the first stream open, then grab the IRQ and program most of
  * the SSI registers.
  */
-static int fsl_ssi_startup(struct snd_pcm_substream *substream)
+static int fsl_ssi_startup(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 = rtd->dai->cpu_dai->private_data;
@@ -296,9 +310,10 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream)
                 *
                 * FIXME: Little-endian samples require a different shift dir
                 */
-               clrsetbits_be32(&ssi->scr, CCSR_SSI_SCR_I2S_MODE_MASK,
-                       CCSR_SSI_SCR_TFR_CLK_DIS |
-                       CCSR_SSI_SCR_I2S_MODE_SLAVE | CCSR_SSI_SCR_SYN);
+               clrsetbits_be32(&ssi->scr,
+                       CCSR_SSI_SCR_I2S_MODE_MASK | CCSR_SSI_SCR_SYN,
+                       CCSR_SSI_SCR_TFR_CLK_DIS | CCSR_SSI_SCR_I2S_MODE_SLAVE
+                       | (ssi_private->asynchronous ? 0 : CCSR_SSI_SCR_SYN));
 
                out_be32(&ssi->stcr,
                         CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 |
@@ -316,12 +331,7 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream)
                 */
 
                /* 4. Enable the interrupts and DMA requests */
-               out_be32(&ssi->sier,
-                        CCSR_SSI_SIER_TFRC_EN | CCSR_SSI_SIER_TDMAE |
-                        CCSR_SSI_SIER_TIE | CCSR_SSI_SIER_TUE0_EN |
-                        CCSR_SSI_SIER_TUE1_EN | CCSR_SSI_SIER_RFRC_EN |
-                        CCSR_SSI_SIER_RDMAE | CCSR_SSI_SIER_RIE |
-                        CCSR_SSI_SIER_ROE0_EN | CCSR_SSI_SIER_ROE1_EN);
+               out_be32(&ssi->sier, SIER_FLAGS);
 
                /*
                 * Set the watermark for transmit FIFI 0 and receive FIFO 0. We
@@ -342,6 +352,50 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream)
                 */
        }
 
+       if (!ssi_private->first_stream)
+               ssi_private->first_stream = substream;
+       else {
+               /* This is the second stream open, so we need to impose sample
+                * rate and maybe sample size constraints.  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.
+                *
+                * FIXME: This code does not handle opening and closing streams
+                * repeatedly.  If you open two streams and then close the first
+                * one, you may not be able to open another stream until you
+                * close the second one as well.
+                */
+               struct snd_pcm_runtime *first_runtime =
+                       ssi_private->first_stream->runtime;
+
+               if (!first_runtime->sample_bits) {
+                       dev_err(substream->pcm->card->dev,
+                               "set sample size in %s stream first\n",
+                               substream->stream == SNDRV_PCM_STREAM_PLAYBACK
+                               ? "capture" : "playback");
+                       return -EAGAIN;
+               }
+
+               /* If we're in synchronous mode, then we need to constrain
+                * the sample size as well.  We don't support independent sample
+                * rates in asynchronous mode.
+                */
+               if (!ssi_private->asynchronous)
+                       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 (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
                ssi_private->playback++;
 
@@ -352,7 +406,7 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream)
 }
 
 /**
- * fsl_ssi_prepare: prepare the SSI.
+ * fsl_ssi_hw_params - program the sample size
  *
  * Most of the SSI registers have been programmed in the startup function,
  * but the word length must be programmed here.  Unfortunately, programming
@@ -364,25 +418,28 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream)
  * Note: The SxCCR.DC and SxCCR.PM bits are only used if the SSI is the
  * clock master.
  */
-static int fsl_ssi_prepare(struct snd_pcm_substream *substream)
+static int fsl_ssi_hw_params(struct snd_pcm_substream *substream,
+       struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *cpu_dai)
 {
-       struct snd_pcm_runtime *runtime = substream->runtime;
-       struct snd_soc_pcm_runtime *rtd = substream->private_data;
-       struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data;
-
-       struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
-       u32 wl;
-
-       wl = CCSR_SSI_SxCCR_WL(snd_pcm_format_width(runtime->format));
+       struct fsl_ssi_private *ssi_private = cpu_dai->private_data;
 
-       clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
+       if (substream == ssi_private->first_stream) {
+               struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+               unsigned int sample_size =
+                       snd_pcm_format_width(params_format(hw_params));
+               u32 wl = CCSR_SSI_SxCCR_WL(sample_size);
 
-       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
-               clrsetbits_be32(&ssi->stccr, CCSR_SSI_SxCCR_WL_MASK, wl);
-       else
-               clrsetbits_be32(&ssi->srccr, CCSR_SSI_SxCCR_WL_MASK, wl);
+               /* The SSI should always be disabled at this points (SSIEN=0) */
 
-       setbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
+               /* In synchronous mode, the SSI uses STCCR for capture */
+               if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ||
+                   !ssi_private->asynchronous)
+                       clrsetbits_be32(&ssi->stccr,
+                                       CCSR_SSI_SxCCR_WL_MASK, wl);
+               else
+                       clrsetbits_be32(&ssi->srccr,
+                                       CCSR_SSI_SxCCR_WL_MASK, wl);
+       }
 
        return 0;
 }
@@ -396,7 +453,8 @@ static int fsl_ssi_prepare(struct snd_pcm_substream *substream)
  * The DMA channel is in external master start and pause mode, which
  * means the SSI completely controls the flow of data.
  */
-static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd)
+static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
+                          struct snd_soc_dai *dai)
 {
        struct snd_soc_pcm_runtime *rtd = substream->private_data;
        struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data;
@@ -404,24 +462,17 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd)
 
        switch (cmd) {
        case SNDRV_PCM_TRIGGER_START:
-       case SNDRV_PCM_TRIGGER_RESUME:
+               clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
-               if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
-                       setbits32(&ssi->scr, CCSR_SSI_SCR_TE);
-               } else {
-                       setbits32(&ssi->scr, CCSR_SSI_SCR_RE);
-
-                       /*
-                        * I think we need this delay to allow time for the SSI
-                        * to put data into its FIFO.  Without it, ALSA starts
-                        * to complain about overruns.
-                        */
-                       msleep(1);
-               }
+               if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+                       setbits32(&ssi->scr,
+                               CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_TE);
+               else
+                       setbits32(&ssi->scr,
+                               CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_RE);
                break;
 
        case SNDRV_PCM_TRIGGER_STOP:
-       case SNDRV_PCM_TRIGGER_SUSPEND:
        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
                        clrbits32(&ssi->scr, CCSR_SSI_SCR_TE);
@@ -441,7 +492,8 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd)
  *
  * Shutdown the SSI if there are no other substreams open.
  */
-static void fsl_ssi_shutdown(struct snd_pcm_substream *substream)
+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 = rtd->dai->cpu_dai->private_data;
@@ -452,6 +504,11 @@ static void fsl_ssi_shutdown(struct snd_pcm_substream *substream)
        if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
                ssi_private->capture--;
 
+       if (ssi_private->first_stream == substream)
+               ssi_private->first_stream = ssi_private->second_stream;
+
+       ssi_private->second_stream = NULL;
+
        /*
         * If this is the last active substream, disable the SSI and release
         * the IRQ.
@@ -479,7 +536,7 @@ static void fsl_ssi_shutdown(struct snd_pcm_substream *substream)
  * @freq: the frequency of the given clock ID, currently ignored
  * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master)
  */
-static int fsl_ssi_set_sysclk(struct snd_soc_cpu_dai *cpu_dai,
+static int fsl_ssi_set_sysclk(struct snd_soc_dai *cpu_dai,
                              int clk_id, unsigned int freq, int dir)
 {
 
@@ -497,7 +554,7 @@ static int fsl_ssi_set_sysclk(struct snd_soc_cpu_dai *cpu_dai,
  *
  * @format: one of SND_SOC_DAIFMT_xxx
  */
-static int fsl_ssi_set_fmt(struct snd_soc_cpu_dai *cpu_dai, unsigned int format)
+static int fsl_ssi_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format)
 {
        return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL;
 }
@@ -505,7 +562,16 @@ static int fsl_ssi_set_fmt(struct snd_soc_cpu_dai *cpu_dai, unsigned int format)
 /**
  * fsl_ssi_dai_template: template CPU DAI for the SSI
  */
-static struct snd_soc_cpu_dai fsl_ssi_dai_template = {
+static struct snd_soc_dai_ops fsl_ssi_dai_ops = {
+       .startup        = fsl_ssi_startup,
+       .hw_params      = fsl_ssi_hw_params,
+       .shutdown       = fsl_ssi_shutdown,
+       .trigger        = fsl_ssi_trigger,
+       .set_sysclk     = fsl_ssi_set_sysclk,
+       .set_fmt        = fsl_ssi_set_fmt,
+};
+
+static struct snd_soc_dai fsl_ssi_dai_template = {
        .playback = {
                /* The SSI does not support monaural audio. */
                .channels_min = 2,
@@ -519,65 +585,69 @@ static struct snd_soc_cpu_dai fsl_ssi_dai_template = {
                .rates = FSLSSI_I2S_RATES,
                .formats = FSLSSI_I2S_FORMATS,
        },
-       .ops = {
-               .startup = fsl_ssi_startup,
-               .prepare = fsl_ssi_prepare,
-               .shutdown = fsl_ssi_shutdown,
-               .trigger = fsl_ssi_trigger,
-       },
-       .dai_ops = {
-               .set_sysclk = fsl_ssi_set_sysclk,
-               .set_fmt = fsl_ssi_set_fmt,
-       },
+       .ops = &fsl_ssi_dai_ops,
 };
 
+/* Show the statistics of a flag only if its interrupt is enabled.  The
+ * compiler will optimze this code to a no-op if the interrupt is not
+ * enabled.
+ */
+#define SIER_SHOW(flag, name) \
+       do { \
+               if (SIER_FLAGS & CCSR_SSI_SIER_##flag) \
+                       length += sprintf(buf + length, #name "=%u\n", \
+                               ssi_private->stats.name); \
+       } while (0)
+
+
 /**
  * fsl_sysfs_ssi_show: display SSI statistics
  *
- * Display the statistics for the current SSI device.
+ * Display the statistics for the current SSI device.  To avoid confusion,
+ * we only show those counts that are enabled.
  */
 static ssize_t fsl_sysfs_ssi_show(struct device *dev,
        struct device_attribute *attr, char *buf)
 {
        struct fsl_ssi_private *ssi_private =
-       container_of(attr, struct fsl_ssi_private, dev_attr);
-       ssize_t length;
-
-       length = sprintf(buf, "rfrc=%u", ssi_private->stats.rfrc);
-       length += sprintf(buf + length, "\ttfrc=%u", ssi_private->stats.tfrc);
-       length += sprintf(buf + length, "\tcmdau=%u", ssi_private->stats.cmdau);
-       length += sprintf(buf + length, "\tcmddu=%u", ssi_private->stats.cmddu);
-       length += sprintf(buf + length, "\trxt=%u", ssi_private->stats.rxt);
-       length += sprintf(buf + length, "\trdr1=%u", ssi_private->stats.rdr1);
-       length += sprintf(buf + length, "\trdr0=%u", ssi_private->stats.rdr0);
-       length += sprintf(buf + length, "\ttde1=%u", ssi_private->stats.tde1);
-       length += sprintf(buf + length, "\ttde0=%u", ssi_private->stats.tde0);
-       length += sprintf(buf + length, "\troe1=%u", ssi_private->stats.roe1);
-       length += sprintf(buf + length, "\troe0=%u", ssi_private->stats.roe0);
-       length += sprintf(buf + length, "\ttue1=%u", ssi_private->stats.tue1);
-       length += sprintf(buf + length, "\ttue0=%u", ssi_private->stats.tue0);
-       length += sprintf(buf + length, "\ttfs=%u", ssi_private->stats.tfs);
-       length += sprintf(buf + length, "\trfs=%u", ssi_private->stats.rfs);
-       length += sprintf(buf + length, "\ttls=%u", ssi_private->stats.tls);
-       length += sprintf(buf + length, "\trls=%u", ssi_private->stats.rls);
-       length += sprintf(buf + length, "\trff1=%u", ssi_private->stats.rff1);
-       length += sprintf(buf + length, "\trff0=%u", ssi_private->stats.rff0);
-       length += sprintf(buf + length, "\ttfe1=%u", ssi_private->stats.tfe1);
-       length += sprintf(buf + length, "\ttfe0=%u\n", ssi_private->stats.tfe0);
+               container_of(attr, struct fsl_ssi_private, dev_attr);
+       ssize_t length = 0;
+
+       SIER_SHOW(RFRC_EN, rfrc);
+       SIER_SHOW(TFRC_EN, tfrc);
+       SIER_SHOW(CMDAU_EN, cmdau);
+       SIER_SHOW(CMDDU_EN, cmddu);
+       SIER_SHOW(RXT_EN, rxt);
+       SIER_SHOW(RDR1_EN, rdr1);
+       SIER_SHOW(RDR0_EN, rdr0);
+       SIER_SHOW(TDE1_EN, tde1);
+       SIER_SHOW(TDE0_EN, tde0);
+       SIER_SHOW(ROE1_EN, roe1);
+       SIER_SHOW(ROE0_EN, roe0);
+       SIER_SHOW(TUE1_EN, tue1);
+       SIER_SHOW(TUE0_EN, tue0);
+       SIER_SHOW(TFS_EN, tfs);
+       SIER_SHOW(RFS_EN, rfs);
+       SIER_SHOW(TLS_EN, tls);
+       SIER_SHOW(RLS_EN, rls);
+       SIER_SHOW(RFF1_EN, rff1);
+       SIER_SHOW(RFF0_EN, rff0);
+       SIER_SHOW(TFE1_EN, tfe1);
+       SIER_SHOW(TFE0_EN, tfe0);
 
        return length;
 }
 
 /**
- * fsl_ssi_create_dai: create a snd_soc_cpu_dai structure
+ * fsl_ssi_create_dai: create a snd_soc_dai structure
  *
- * This function is called by the machine driver to create a snd_soc_cpu_dai
+ * This function is called by the machine driver to create a snd_soc_dai
  * structure.  The function creates an ssi_private object, which contains
- * the snd_soc_cpu_dai.  It also creates the sysfs statistics device.
+ * the snd_soc_dai.  It also creates the sysfs statistics device.
  */
-struct snd_soc_cpu_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info)
+struct snd_soc_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info)
 {
-       struct snd_soc_cpu_dai *fsl_ssi_dai;
+       struct snd_soc_dai *fsl_ssi_dai;
        struct fsl_ssi_private *ssi_private;
        int ret = 0;
        struct device_attribute *dev_attr;
@@ -588,7 +658,7 @@ struct snd_soc_cpu_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info)
                return NULL;
        }
        memcpy(&ssi_private->cpu_dai, &fsl_ssi_dai_template,
-              sizeof(struct snd_soc_cpu_dai));
+              sizeof(struct snd_soc_dai));
 
        fsl_ssi_dai = &ssi_private->cpu_dai;
        dev_attr = &ssi_private->dev_attr;
@@ -598,8 +668,9 @@ struct snd_soc_cpu_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info)
        ssi_private->ssi_phys = ssi_info->ssi_phys;
        ssi_private->irq = ssi_info->irq;
        ssi_private->dev = ssi_info->dev;
+       ssi_private->asynchronous = ssi_info->asynchronous;
 
-       ssi_private->dev->driver_data = fsl_ssi_dai;
+       dev_set_drvdata(ssi_private->dev, fsl_ssi_dai);
 
        /* Initialize the the device_attribute structure */
        dev_attr->attr.name = "ssi-stats";
@@ -617,27 +688,46 @@ struct snd_soc_cpu_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info)
        fsl_ssi_dai->private_data = ssi_private;
        fsl_ssi_dai->name = ssi_private->name;
        fsl_ssi_dai->id = ssi_info->id;
+       fsl_ssi_dai->dev = ssi_info->dev;
+       fsl_ssi_dai->symmetric_rates = 1;
+
+       ret = snd_soc_register_dai(fsl_ssi_dai);
+       if (ret != 0) {
+               dev_err(ssi_info->dev, "failed to register DAI: %d\n", ret);
+               kfree(fsl_ssi_dai);
+               return NULL;
+       }
 
        return fsl_ssi_dai;
 }
 EXPORT_SYMBOL_GPL(fsl_ssi_create_dai);
 
 /**
- * fsl_ssi_destroy_dai: destroy the snd_soc_cpu_dai object
+ * fsl_ssi_destroy_dai: destroy the snd_soc_dai object
  *
  * This function undoes the operations of fsl_ssi_create_dai()
  */
-void fsl_ssi_destroy_dai(struct snd_soc_cpu_dai *fsl_ssi_dai)
+void fsl_ssi_destroy_dai(struct snd_soc_dai *fsl_ssi_dai)
 {
        struct fsl_ssi_private *ssi_private =
        container_of(fsl_ssi_dai, struct fsl_ssi_private, cpu_dai);
 
        device_remove_file(ssi_private->dev, &ssi_private->dev_attr);
 
+       snd_soc_unregister_dai(&ssi_private->cpu_dai);
+
        kfree(ssi_private);
 }
 EXPORT_SYMBOL_GPL(fsl_ssi_destroy_dai);
 
+static int __init fsl_ssi_init(void)
+{
+       printk(KERN_INFO "Freescale Synchronous Serial Interface (SSI) ASoC Driver\n");
+
+       return 0;
+}
+module_init(fsl_ssi_init);
+
 MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
 MODULE_DESCRIPTION("Freescale Synchronous Serial Interface (SSI) ASoC Driver");
 MODULE_LICENSE("GPL");