]> Pileus Git - ~andy/linux/blobdiff - sound/soc/fsl/fsl_ssi.c
ASoC: fsl-ssi: ac97-slave support
[~andy/linux] / sound / soc / fsl / fsl_ssi.c
index 3168998dcf1db6e4a29a82c42993a6d8ea80c754..9e410e1e49a973aebf33c0e25b63f581656f8871 100644 (file)
@@ -141,6 +141,7 @@ struct fsl_ssi_private {
 
        bool new_binding;
        bool ssi_on_imx;
+       bool imx_ac97;
        bool use_dma;
        struct clk *clk;
        struct snd_dmaengine_dai_dma_data dma_params_tx;
@@ -320,6 +321,124 @@ static irqreturn_t fsl_ssi_isr(int irq, void *dev_id)
        return ret;
 }
 
+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;
+       else
+               i2s_mode = CCSR_SSI_SCR_I2S_MODE_SLAVE;
+
+       /*
+        * Section 16.5 of the MPC8610 reference manual says that the SSI needs
+        * to be disabled before updating the registers we set here.
+        */
+       write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_SSIEN, 0);
+
+       /*
+        * Program the SSI into I2S Slave Non-Network Synchronous mode. Also
+        * enable the transmit and receive FIFO.
+        *
+        * FIXME: Little-endian samples require a different shift dir
+        */
+       write_ssi_mask(&ssi->scr,
+               CCSR_SSI_SCR_I2S_MODE_MASK | CCSR_SSI_SCR_SYN,
+               CCSR_SSI_SCR_TFR_CLK_DIS |
+               i2s_mode |
+               (synchronous ? CCSR_SSI_SCR_SYN : 0));
+
+       write_ssi(CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 |
+                CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TEFS |
+                CCSR_SSI_STCR_TSCKP, &ssi->stcr);
+
+       write_ssi(CCSR_SSI_SRCR_RXBIT0 | CCSR_SSI_SRCR_RFEN0 |
+                CCSR_SSI_SRCR_RFSI | CCSR_SSI_SRCR_REFS |
+                CCSR_SSI_SRCR_RSCKP, &ssi->srcr);
+       /*
+        * The DC and PM bits are only used if the SSI is the clock master.
+        */
+
+       /*
+        * Set the watermark for transmit FIFI 0 and receive FIFO 0. We don't
+        * use FIFO 1. We program the transmit water to signal a DMA transfer
+        * if there are only two (or fewer) elements left in the FIFO. Two
+        * elements equals one frame (left channel, right channel). This value,
+        * however, depends on the depth of the transmit buffer.
+        *
+        * We set the watermark on the same level as the DMA burstsize.  For
+        * fiq it is probably better to use the biggest possible watermark
+        * size.
+        */
+       if (ssi_private->use_dma)
+               wm = ssi_private->fifo_depth - 2;
+       else
+               wm = ssi_private->fifo_depth;
+
+       write_ssi(CCSR_SSI_SFCSR_TFWM0(wm) | CCSR_SSI_SFCSR_RFWM0(wm) |
+               CCSR_SSI_SFCSR_TFWM1(wm) | CCSR_SSI_SFCSR_RFWM1(wm),
+               &ssi->sfcsr);
+
+       /*
+        * For non-ac97 setups, we keep the SSI disabled because if we enable
+        * it, then the DMA controller will start. It's not supposed to start
+        * until the SCR.TE (or SCR.RE) bit is set, but it does anyway. The DMA
+        * controller will transfer one "BWC" of data (i.e. the amount of data
+        * that the MR.BWC bits are set to). The reason this is bad is because
+        * at this point, the PCM driver has not finished initializing the DMA
+        * controller.
+        */
+
+
+       /*
+        * For ac97 interrupts are enabled with the startup of the substream
+        * because it is also running without an active substream. Normally SSI
+        * is only enabled when there is a substream.
+        */
+       if (!ssi_private->imx_ac97) {
+               /* Enable the interrupts and DMA requests */
+               if (ssi_private->use_dma)
+                       write_ssi(SIER_FLAGS, &ssi->sier);
+               else
+                       write_ssi(CCSR_SSI_SIER_TIE | CCSR_SSI_SIER_TFE0_EN |
+                                       CCSR_SSI_SIER_RIE |
+                                       CCSR_SSI_SIER_RFF0_EN, &ssi->sier);
+       } else {
+               /*
+                * 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
+                */
+               write_ssi_mask(&ssi->scr, 0, CCSR_SSI_SCR_SSIEN);
+               write_ssi(CCSR_SSI_SOR_WAIT(3), &ssi->sor);
+
+               /*
+                * Enable Transmit and Receive
+                */
+               write_ssi_mask(&ssi->scr, 0, CCSR_SSI_SCR_TE | CCSR_SSI_SCR_RE);
+       }
+
+       return 0;
+}
+
+
 /**
  * fsl_ssi_startup: create a new substream
  *
@@ -341,75 +460,14 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream,
         * and initialize the SSI registers.
         */
        if (!ssi_private->first_stream) {
-               struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
-
                ssi_private->first_stream = substream;
 
                /*
-                * Section 16.5 of the MPC8610 reference manual says that the
-                * SSI needs to be disabled before updating the registers we set
-                * here.
-                */
-               write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_SSIEN, 0);
-
-               /*
-                * Program the SSI into I2S Slave Non-Network Synchronous mode.
-                * Also enable the transmit and receive FIFO.
-                *
-                * FIXME: Little-endian samples require a different shift dir
-                */
-               write_ssi_mask(&ssi->scr,
-                       CCSR_SSI_SCR_I2S_MODE_MASK | CCSR_SSI_SCR_SYN,
-                       CCSR_SSI_SCR_TFR_CLK_DIS | CCSR_SSI_SCR_I2S_MODE_SLAVE
-                       | (synchronous ? CCSR_SSI_SCR_SYN : 0));
-
-               write_ssi(CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 |
-                        CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TEFS |
-                        CCSR_SSI_STCR_TSCKP, &ssi->stcr);
-
-               write_ssi(CCSR_SSI_SRCR_RXBIT0 | CCSR_SSI_SRCR_RFEN0 |
-                        CCSR_SSI_SRCR_RFSI | CCSR_SSI_SRCR_REFS |
-                        CCSR_SSI_SRCR_RSCKP, &ssi->srcr);
-
-               /*
-                * The DC and PM bits are only used if the SSI is the clock
-                * master.
-                */
-
-               /* Enable the interrupts and DMA requests */
-               if (ssi_private->use_dma)
-                       write_ssi(SIER_FLAGS, &ssi->sier);
-               else
-                       write_ssi(CCSR_SSI_SIER_TIE | CCSR_SSI_SIER_TFE0_EN |
-                                       CCSR_SSI_SIER_RIE |
-                                       CCSR_SSI_SIER_RFF0_EN, &ssi->sier);
-
-               /*
-                * Set the watermark for transmit FIFI 0 and receive FIFO 0. We
-                * don't use FIFO 1.  We program the transmit water to signal a
-                * DMA transfer if there are only two (or fewer) elements left
-                * in the FIFO.  Two elements equals one frame (left channel,
-                * right channel).  This value, however, depends on the depth of
-                * the transmit buffer.
-                *
-                * We program the receive FIFO to notify us if at least two
-                * elements (one frame) have been written to the FIFO.  We could
-                * make this value larger (and maybe we should), but this way
-                * data will be written to memory as soon as it's available.
-                */
-               write_ssi(CCSR_SSI_SFCSR_TFWM0(ssi_private->fifo_depth - 2) |
-                       CCSR_SSI_SFCSR_RFWM0(ssi_private->fifo_depth - 2),
-                       &ssi->sfcsr);
-
-               /*
-                * We keep the SSI disabled because if we enable it, then the
-                * DMA controller will start.  It's not supposed to start until
-                * the SCR.TE (or SCR.RE) bit is set, but it does anyway.  The
-                * DMA controller will transfer one "BWC" of data (i.e. the
-                * amount of data that the MR.BWC bits are set to).  The reason
-                * this is bad is because at this point, the PCM driver has not
-                * finished initializing the DMA controller.
+                * 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 =
@@ -538,7 +596,8 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
                else
                        write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_RE, 0);
 
-               if ((read_ssi(&ssi->scr) & (CCSR_SSI_SCR_TE | CCSR_SSI_SCR_RE)) == 0)
+               if (!ssi_private->imx_ac97 && (read_ssi(&ssi->scr) &
+                                       (CCSR_SSI_SCR_TE | CCSR_SSI_SCR_RE)) == 0)
                        write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_SSIEN, 0);
                break;
 
@@ -608,6 +667,133 @@ static const struct snd_soc_component_driver fsl_ssi_component = {
        .name           = "fsl-ssi",
 };
 
+/**
+ * fsl_ssi_ac97_trigger: start and stop the AC97 receive/transmit.
+ *
+ * This function is called by ALSA to start, stop, pause, and resume the
+ * transfer of data.
+ */
+static int fsl_ssi_ac97_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 = snd_soc_dai_get_drvdata(
+                       rtd->cpu_dai);
+       struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+                       write_ssi_mask(&ssi->sier, 0, CCSR_SSI_SIER_TIE |
+                                       CCSR_SSI_SIER_TFE0_EN);
+               else
+                       write_ssi_mask(&ssi->sier, 0, CCSR_SSI_SIER_RIE |
+                                       CCSR_SSI_SIER_RFF0_EN);
+               break;
+
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+                       write_ssi_mask(&ssi->sier, CCSR_SSI_SIER_TIE |
+                                       CCSR_SSI_SIER_TFE0_EN, 0);
+               else
+                       write_ssi_mask(&ssi->sier, CCSR_SSI_SIER_RIE |
+                                       CCSR_SSI_SIER_RFF0_EN, 0);
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               write_ssi(CCSR_SSI_SOR_TX_CLR, &ssi->sor);
+       else
+               write_ssi(CCSR_SSI_SOR_RX_CLR, &ssi->sor);
+
+       return 0;
+}
+
+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,
+};
+
+static struct snd_soc_dai_driver fsl_ssi_ac97_dai = {
+       .ac97_control = 1,
+       .playback = {
+               .stream_name = "AC97 Playback",
+               .channels_min = 2,
+               .channels_max = 2,
+               .rates = SNDRV_PCM_RATE_8000_48000,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE,
+       },
+       .capture = {
+               .stream_name = "AC97 Capture",
+               .channels_min = 2,
+               .channels_max = 2,
+               .rates = SNDRV_PCM_RATE_48000,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE,
+       },
+       .ops = &fsl_ssi_ac97_dai_ops,
+};
+
+
+static struct fsl_ssi_private *fsl_ac97_data;
+
+static void fsl_ssi_ac97_init(void)
+{
+       fsl_ssi_setup(fsl_ac97_data);
+}
+
+void fsl_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+               unsigned short val)
+{
+       struct ccsr_ssi *ssi = fsl_ac97_data->ssi;
+       unsigned int lreg;
+       unsigned int lval;
+
+       if (reg > 0x7f)
+               return;
+
+
+       lreg = reg <<  12;
+       write_ssi(lreg, &ssi->sacadd);
+
+       lval = val << 4;
+       write_ssi(lval , &ssi->sacdat);
+
+       write_ssi_mask(&ssi->sacnt, CCSR_SSI_SACNT_RDWR_MASK,
+                       CCSR_SSI_SACNT_WR);
+       udelay(100);
+}
+
+unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97,
+               unsigned short reg)
+{
+       struct ccsr_ssi *ssi = fsl_ac97_data->ssi;
+
+       unsigned short val = -1;
+       unsigned int lreg;
+
+       lreg = (reg & 0x7f) <<  12;
+       write_ssi(lreg, &ssi->sacadd);
+       write_ssi_mask(&ssi->sacnt, CCSR_SSI_SACNT_RDWR_MASK,
+                       CCSR_SSI_SACNT_RD);
+
+       udelay(100);
+
+       val = (read_ssi(&ssi->sacdat) >> 4) & 0xffff;
+
+       return val;
+}
+
+static struct snd_ac97_bus_ops fsl_ssi_ac97_ops = {
+       .read           = fsl_ssi_ac97_read,
+       .write          = fsl_ssi_ac97_write,
+};
+
 /* 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.
@@ -684,6 +870,7 @@ static int fsl_ssi_probe(struct platform_device *pdev)
        struct resource res;
        char name[64];
        bool shared;
+       bool ac97 = false;
 
        /* SSIs that are not connected on the board should have a
         *      status = "disabled"
@@ -694,7 +881,13 @@ static int fsl_ssi_probe(struct platform_device *pdev)
 
        /* We only support the SSI in "I2S Slave" mode */
        sprop = of_get_property(np, "fsl,mode", NULL);
-       if (!sprop || strcmp(sprop, "i2s-slave")) {
+       if (!sprop) {
+               dev_err(&pdev->dev, "fsl,mode property is necessary\n");
+               return -EINVAL;
+       }
+       if (!strcmp(sprop, "ac97-slave")) {
+               ac97 = true;
+       } else if (strcmp(sprop, "i2s-slave")) {
                dev_notice(&pdev->dev, "mode %s is unsupported\n", sprop);
                return -ENODEV;
        }
@@ -713,9 +906,19 @@ static int fsl_ssi_probe(struct platform_device *pdev)
        ssi_private->use_dma = !of_property_read_bool(np,
                        "fsl,fiq-stream-filter");
 
-       /* Initialize this copy of the CPU DAI driver structure */
-       memcpy(&ssi_private->cpu_dai_drv, &fsl_ssi_dai_template,
-              sizeof(fsl_ssi_dai_template));
+       if (ac97) {
+               memcpy(&ssi_private->cpu_dai_drv, &fsl_ssi_ac97_dai,
+                               sizeof(fsl_ssi_ac97_dai));
+
+               fsl_ac97_data = ssi_private;
+               ssi_private->imx_ac97 = true;
+
+               snd_soc_set_ac97_ops_of_reset(&fsl_ssi_ac97_ops, pdev);
+       } else {
+               /* Initialize this copy of the CPU DAI driver structure */
+               memcpy(&ssi_private->cpu_dai_drv, &fsl_ssi_dai_template,
+                      sizeof(fsl_ssi_dai_template));
+       }
        ssi_private->cpu_dai_drv.name = ssi_private->name;
 
        /* Get the addresses and IRQ */
@@ -901,6 +1104,9 @@ static int fsl_ssi_probe(struct platform_device *pdev)
        }
 
 done:
+       if (ssi_private->imx_ac97)
+               fsl_ssi_ac97_init();
+
        return 0;
 
 error_dai: