@@ -120,6 +120,7 @@ struct fsl_ssi_private {
bool new_binding;
bool ssi_on_imx;
+ bool imx_ac97;
bool dma;
struct clk *clk;
struct platform_device *imx_pcm_pdev;
@@ -127,6 +128,9 @@ struct fsl_ssi_private {
struct imx_pcm_dma_params dma_params_rx;
struct imx_pcm_fiq_params fiq_params;
+ void (*ac97_reset) (struct snd_ac97 *ac97);
+ void (*ac97_warm_reset)(struct snd_ac97 *ac97);
+
struct {
unsigned int rfrc;
unsigned int tfrc;
@@ -323,67 +327,76 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream,
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));
+ /* If we use AC97, the registers are already setup correctly */
+ if (!ssi_private->imx_ac97) {
+ /*
+ * 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);
- write_ssi(CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 |
- CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TEFS |
- CCSR_SSI_STCR_TSCKP, &ssi->stcr);
+ /*
+ * 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.
+ */
- write_ssi(CCSR_SSI_SRCR_RXBIT0 | CCSR_SSI_SRCR_RFEN0 |
- CCSR_SSI_SRCR_RFSI | CCSR_SSI_SRCR_REFS |
- CCSR_SSI_SRCR_RSCKP, &ssi->srcr);
+ /*
+ * 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);
- /*
- * The DC and PM bits are only used if the SSI is the clock
- * master.
- */
+ /*
+ * 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.
+ */
+ }
/* Enable the interrupts and DMA requests */
if (ssi_private->dma)
write_ssi(SIER_FLAGS, &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.
- */
} else {
if (synchronous) {
struct snd_pcm_runtime *first_runtime =
@@ -500,27 +513,29 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
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->scr, 0,
- CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_TE);
- else
- write_ssi_mask(&ssi->scr, 0,
- CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_RE);
- break;
-
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_TE, 0);
- else
- write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_RE, 0);
- break;
-
- default:
- return -EINVAL;
+ if (!ssi_private->imx_ac97) {
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ write_ssi_mask(&ssi->scr, 0,
+ CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_TE);
+ else
+ write_ssi_mask(&ssi->scr, 0,
+ CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_RE);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_TE, 0);
+ else
+ write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_RE, 0);
+ break;
+
+ default:
+ return -EINVAL;
+ }
}
if (!ssi_private->dma) {
@@ -556,8 +571,9 @@ static void fsl_ssi_shutdown(struct snd_pcm_substream *substream,
/*
* If this is the last active substream, disable the SSI.
+ * If AC97 is active, we don't want to disable SSI.
*/
- if (!ssi_private->first_stream) {
+ if (!ssi_private->first_stream && !ssi_private->imx_ac97) {
struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_SSIEN, 0);
@@ -589,6 +605,152 @@ static struct snd_soc_dai_driver fsl_ssi_dai_template = {
.ops = &fsl_ssi_dai_ops,
};
+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_dai_ops,
+};
+
+
+static struct fsl_ssi_private *fsl_ac97_data;
+
+static void fsl_ssi_ac97_setup(struct ccsr_ssi *ssi)
+{
+ write_ssi(0x0, &ssi->scr);
+ write_ssi(0x0, &ssi->stcr);
+ write_ssi(0x0, &ssi->srcr);
+
+ write_ssi(CCSR_SSI_SCR_SYN | CCSR_SSI_SCR_NET, &ssi->scr);
+
+ write_ssi(CCSR_SSI_SFCSR_RFWM0(8) | CCSR_SSI_SFCSR_TFWM0(8) |
+ CCSR_SSI_SFCSR_RFWM1(8) | CCSR_SSI_SFCSR_TFWM1(8),
+ &ssi->sfcsr);
+
+ 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);
+
+ write_ssi(CCSR_SSI_SCR_SYN | CCSR_SSI_SCR_NET |
+ CCSR_SSI_SCR_SSIEN, &ssi->scr);
+ write_ssi(CCSR_SSI_SOR_WAIT(3), &ssi->sor);
+
+ write_ssi(CCSR_SSI_SCR_SYN | CCSR_SSI_SCR_NET |
+ CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_TE |
+ CCSR_SSI_SCR_RE, &ssi->scr);
+
+ write_ssi(CCSR_SSI_SACNT_AC97EN | CCSR_SSI_SACNT_FV,
+ &ssi->sacnt);
+ write_ssi(0xff, &ssi->saccdis);
+ write_ssi(0x300, &ssi->saccen);
+
+ write_ssi(0x0, &ssi->sier);
+}
+
+static 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);
+}
+
+static 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 void fsl_ssi_ac97_reset(struct snd_ac97 *ac97)
+{
+ struct fsl_ssi_private *ssi_private = fsl_ac97_data;
+
+ if (ssi_private->ac97_reset)
+ ssi_private->ac97_reset(ac97);
+ /* First read sometimes fails, do a dummy read */
+ fsl_ssi_ac97_read(ac97, 0);
+}
+
+static void fsl_ssi_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+ struct fsl_ssi_private *ssi_private = fsl_ac97_data;
+
+ if (ssi_private->ac97_warm_reset)
+ ssi_private->ac97_warm_reset(ac97);
+
+ /* First read sometimes fails, do a dummy read */
+ fsl_ssi_ac97_read(ac97, 0);
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = fsl_ssi_ac97_read,
+ .write = fsl_ssi_ac97_write,
+ .reset = fsl_ssi_ac97_reset,
+ .warm_reset = fsl_ssi_ac97_warm_reset
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+/*
+ * Pointer to AC97 reset functions for specific boards
+ */
+#if IS_ENABLED(CONFIG_MACH_PCA100)
+extern void pca100_ac97_cold_reset(struct snd_ac97 *ac97);
+extern void pca100_ac97_warm_reset(struct snd_ac97 *ac97);
+#else
+void pca100_ac97_cold_reset(struct snd_ac97 *ac97) { }
+void pca100_ac97_warm_reset(struct snd_ac97 *ac97) { }
+#endif
+
+#if IS_ENABLED(CONFIG_MACH_PCM043)
+extern void pcm043_ac97_cold_reset(struct snd_ac97 *ac97);
+extern void pcm043_ac97_warm_reset(struct snd_ac97 *ac97);
+#else
+void pcm043_ac97_cold_reset(struct snd_ac97 *ac97) { }
+void pcm043_ac97_warm_reset(struct snd_ac97 *ac97) { }
+#endif
+
+
/* 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.
@@ -664,6 +826,7 @@ static int fsl_ssi_probe(struct platform_device *pdev)
const uint32_t *iprop;
struct resource res;
char name[64];
+ bool ac97 = false;
/* SSIs that are not connected on the board should have a
* status = "disabled"
@@ -674,7 +837,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;
}
@@ -690,13 +859,39 @@ static int fsl_ssi_probe(struct platform_device *pdev)
strcpy(ssi_private->name, p);
- /* 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;
-
ssi_private->dma = !of_property_read_bool(np, "fsl,imx-fiq");
+ if (ac97) {
+ sprop = of_get_property(of_find_node_by_path("/"), "compatible",
+ NULL);
+ p = strrchr(sprop, ',');
+ if (p)
+ sprop = p + 1;
+
+ if (!strcmp(sprop, "imx27-pca100")) {
+ ssi_private->ac97_reset = pca100_ac97_cold_reset;
+ ssi_private->ac97_warm_reset = pca100_ac97_warm_reset;
+ } else if (!strcmp(sprop, "imx27-pcm043")) {
+ ssi_private->ac97_reset = pcm043_ac97_cold_reset;
+ ssi_private->ac97_warm_reset = pcm043_ac97_warm_reset;
+ } else {
+ dev_err(&pdev->dev, "Failed to enable ssi AC97 mode, unknown board.\n");
+ ret = -EINVAL;
+ goto error_kmalloc;
+ }
+
+ 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;
+ } 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 */
ret = of_address_to_resource(np, 0, &res);
if (ret) {
@@ -729,6 +924,9 @@ static int fsl_ssi_probe(struct platform_device *pdev)
}
}
+ if (ssi_private->imx_ac97)
+ fsl_ssi_ac97_setup(ssi_private->ssi);
+
/* Are the RX and the TX clocks locked? */
if (!of_find_property(np, "fsl,ssi-asynchronous", NULL))
ssi_private->cpu_dai_drv.symmetric_rates = 1;
This patch copies some parts from imx-ssi to support AC97 on imx27-pca100 and imx27-pcm043. It is activated with a new fsl,imx-ac97 bool property. It was tested on imx27-pca100. Signed-off-by: Markus Pargmann <mpa@pengutronix.de> --- sound/soc/fsl/fsl_ssi.c | 360 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 279 insertions(+), 81 deletions(-)