struct snd_soc_platform_driver dai;
dma_addr_t ssi_stx_phys;
dma_addr_t ssi_srx_phys;
+ unsigned int ssi_fifo_depth;
struct ccsr_dma_channel __iomem *channel;
unsigned int irq;
bool assigned;
unsigned int irq;
struct snd_pcm_substream *substream;
dma_addr_t ssi_sxx_phys;
+ unsigned int ssi_fifo_depth;
dma_addr_t ld_buf_phys;
unsigned int current_link;
dma_addr_t dma_buf_phys;
else
dma_private->ssi_sxx_phys = dma->ssi_srx_phys;
+ dma_private->ssi_fifo_depth = dma->ssi_fifo_depth;
dma_private->dma_channel = dma->channel;
dma_private->irq = dma->irq;
dma_private->substream = substream;
struct device *dev = rtd->platform->dev;
/* Number of bits per sample */
- unsigned int sample_size =
+ unsigned int sample_bits =
snd_pcm_format_physical_width(params_format(hw_params));
/* Number of bytes per frame */
- unsigned int frame_size = 2 * (sample_size / 8);
+ unsigned int sample_bytes = sample_bits / 8;
/* Bus address of SSI STX register */
dma_addr_t ssi_sxx_phys = dma_private->ssi_sxx_phys;
* that offset here. While we're at it, also tell the DMA controller
* how much data to transfer per sample.
*/
- switch (sample_size) {
+ switch (sample_bits) {
case 8:
mr |= CCSR_DMA_MR_DAHTS_1 | CCSR_DMA_MR_SAHTS_1;
ssi_sxx_phys += 3;
break;
default:
/* We should never get here */
- dev_err(dev, "unsupported sample size %u\n", sample_size);
+ dev_err(dev, "unsupported sample size %u\n", sample_bits);
return -EINVAL;
}
/*
- * BWC should always be a multiple of the frame size. BWC determines
- * how many bytes are sent/received before the DMA controller checks the
- * SSI to see if it needs to stop. For playback, the transmit FIFO can
- * hold three frames, so we want to send two frames at a time. For
- * capture, the receive FIFO is triggered when it contains one frame, so
- * we want to receive one frame at a time.
+ * BWC determines how many bytes are sent/received before the DMA
+ * controller checks the SSI to see if it needs to stop. BWC should
+ * always be a multiple of the frame size, so that we always transmit
+ * whole frames. Each frame occupies two slots in the FIFO. The
+ * parameter for CCSR_DMA_MR_BWC() is rounded down the next power of two
+ * (MR[BWC] can only represent even powers of two).
+ *
+ * To simplify the process, we set BWC to the largest value that is
+ * less than or equal to the FIFO watermark. For playback, this ensures
+ * that we transfer the maximum amount without overrunning the FIFO.
+ * For capture, this ensures that we transfer the maximum amount without
+ * underrunning the FIFO.
+ *
+ * f = SSI FIFO depth
+ * w = SSI watermark value (which equals f - 2)
+ * b = DMA bandwidth count (in bytes)
+ * s = sample size (in bytes, which equals frame_size * 2)
+ *
+ * For playback, we never transmit more than the transmit FIFO
+ * watermark, otherwise we might write more data than the FIFO can hold.
+ * The watermark is equal to the FIFO depth minus two.
+ *
+ * For capture, two equations must hold:
+ * w > f - (b / s)
+ * w >= b / s
+ *
+ * So, b > 2 * s, but b must also be <= s * w. To simplify, we set
+ * b = s * w, which is equal to
+ * (dma_private->ssi_fifo_depth - 2) * sample_bytes.
*/
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- mr |= CCSR_DMA_MR_BWC(2 * frame_size);
- else
- mr |= CCSR_DMA_MR_BWC(frame_size);
+ mr |= CCSR_DMA_MR_BWC((dma_private->ssi_fifo_depth - 2) * sample_bytes);
out_be32(&dma_channel->mr, mr);
struct device_node *np = of_dev->dev.of_node;
struct device_node *ssi_np;
struct resource res;
+ const uint32_t *iprop;
int ret;
/* Find the SSI node that points to us. */
}
ret = of_address_to_resource(ssi_np, 0, &res);
- of_node_put(ssi_np);
if (ret) {
- dev_err(&of_dev->dev, "could not determine device resources\n");
+ dev_err(&of_dev->dev, "could not determine resources for %s\n",
+ ssi_np->full_name);
+ of_node_put(ssi_np);
return ret;
}
dma = kzalloc(sizeof(*dma) + strlen(np->full_name), GFP_KERNEL);
if (!dma) {
dev_err(&of_dev->dev, "could not allocate dma object\n");
+ of_node_put(ssi_np);
return -ENOMEM;
}
dma->ssi_stx_phys = res.start + offsetof(struct ccsr_ssi, stx0);
dma->ssi_srx_phys = res.start + offsetof(struct ccsr_ssi, srx0);
+ iprop = of_get_property(ssi_np, "fsl,fifo-depth", NULL);
+ if (iprop)
+ dma->ssi_fifo_depth = *iprop;
+ else
+ /* Older 8610 DTs didn't have the fifo-depth property */
+ dma->ssi_fifo_depth = 8;
+
+ of_node_put(ssi_np);
+
ret = snd_soc_register_platform(&of_dev->dev, &dma->dai);
if (ret) {
dev_err(&of_dev->dev, "could not register platform\n");
unsigned int playback;
unsigned int capture;
int asynchronous;
+ unsigned int fifo_depth;
struct snd_soc_dai_driver cpu_dai_drv;
struct device_attribute dev_attr;
struct platform_device *pdev;
/*
* Set the watermark for transmit FIFI 0 and receive FIFO 0. We
- * don't use FIFO 1. Since the SSI only supports stereo, the
- * watermark should never be an odd number.
+ * 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.
*/
out_be32(&ssi->sfcsr,
- CCSR_SSI_SFCSR_TFWM0(6) | CCSR_SSI_SFCSR_RFWM0(2));
+ CCSR_SSI_SFCSR_TFWM0(ssi_private->fifo_depth - 2) |
+ CCSR_SSI_SFCSR_RFWM0(ssi_private->fifo_depth - 2));
/*
* We keep the SSI disabled because if we enable it, then the
struct device_attribute *dev_attr = NULL;
struct device_node *np = of_dev->dev.of_node;
const char *p, *sprop;
+ const uint32_t *iprop;
struct resource res;
char name[64];
else
ssi_private->cpu_dai_drv.symmetric_rates = 1;
+ /* Determine the FIFO depth. */
+ iprop = of_get_property(np, "fsl,fifo-depth", NULL);
+ if (iprop)
+ ssi_private->fifo_depth = *iprop;
+ else
+ /* Older 8610 DTs didn't have the fifo-depth property */
+ ssi_private->fifo_depth = 8;
+
/* Initialize the the device_attribute structure */
dev_attr = &ssi_private->dev_attr;
dev_attr->attr.name = "statistics";