summaryrefslogtreecommitdiffstats
path: root/sound/soc/fsl/fsl_ssi.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/fsl/fsl_ssi.c')
-rw-r--r--sound/soc/fsl/fsl_ssi.c98
1 files changed, 63 insertions, 35 deletions
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c
index c6d6eb71dc1d..169bca295b78 100644
--- a/sound/soc/fsl/fsl_ssi.c
+++ b/sound/soc/fsl/fsl_ssi.c
@@ -72,6 +72,7 @@
* @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
@@ -86,6 +87,7 @@ struct fsl_ssi_private {
struct device *dev;
unsigned int playback;
unsigned int capture;
+ int asynchronous;
struct snd_soc_dai cpu_dai;
struct device_attribute dev_attr;
@@ -301,9 +303,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 |
@@ -382,10 +385,15 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream,
SNDRV_PCM_HW_PARAM_RATE,
first_runtime->rate, first_runtime->rate);
- snd_pcm_hw_constraint_minmax(substream->runtime,
- SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
- first_runtime->sample_bits,
- first_runtime->sample_bits);
+ /* 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;
}
@@ -400,7 +408,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
@@ -412,23 +420,27 @@ 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,
- struct snd_soc_dai *dai)
+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;
+ struct fsl_ssi_private *ssi_private = cpu_dai->private_data;
if (substream == ssi_private->first_stream) {
- u32 wl;
+ 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);
/* The SSI should always be disabled at this points (SSIEN=0) */
- wl = CCSR_SSI_SxCCR_WL(snd_pcm_format_width(runtime->format));
/* In synchronous mode, the SSI uses STCCR for capture */
- clrsetbits_be32(&ssi->stccr, CCSR_SSI_SxCCR_WL_MASK, wl);
+ 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;
@@ -452,28 +464,33 @@ 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) {
- clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
setbits32(&ssi->scr,
CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_TE);
} else {
- clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
+ long timeout = jiffies + 10;
+
setbits32(&ssi->scr,
CCSR_SSI_SCR_SSIEN | 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.
+ /* Wait until the SSI has filled its FIFO. Without this
+ * delay, ALSA complains about overruns. When the FIFO
+ * is full, the DMA controller initiates its first
+ * transfer. Until then, however, the DMA's DAR
+ * register is zero, which translates to an
+ * out-of-bounds pointer. This makes ALSA think an
+ * overrun has occurred.
*/
- mdelay(1);
+ while (!(in_be32(&ssi->sisr) & CCSR_SSI_SISR_RFF0) &&
+ (jiffies < timeout));
+ if (!(in_be32(&ssi->sisr) & CCSR_SSI_SISR_RFF0))
+ return -EIO;
}
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);
@@ -563,6 +580,15 @@ static int fsl_ssi_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format)
/**
* fsl_ssi_dai_template: template CPU DAI for the SSI
*/
+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. */
@@ -577,14 +603,7 @@ static struct snd_soc_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,
- .set_sysclk = fsl_ssi_set_sysclk,
- .set_fmt = fsl_ssi_set_fmt,
- },
+ .ops = &fsl_ssi_dai_ops,
};
/**
@@ -654,6 +673,7 @@ struct snd_soc_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;
@@ -704,6 +724,14 @@ void fsl_ssi_destroy_dai(struct snd_soc_dai *fsl_ssi_dai)
}
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");