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
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 {
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++;
* 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;
*
* 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 |
*/
/* 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
*/
}
+ 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++;
}
/**
- * 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
* 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;
}
* 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;
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);
*
* 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;
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.
* @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)
{
*
* @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;
}
/**
* 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,
.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;
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;
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";
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");