]> Pileus Git - ~andy/linux/blobdiff - sound/soc/soc-generic-dmaengine-pcm.c
Merge tag 'for-linus-20140225' of git://git.infradead.org/linux-mtd
[~andy/linux] / sound / soc / soc-generic-dmaengine-pcm.c
index 41949af3baaed2580dae2df585222339ade75208..5bace124ef438f9bea6a1ad16941df48760e8276 100644 (file)
@@ -137,10 +137,15 @@ static int dmaengine_pcm_set_runtime_hwparams(struct snd_pcm_substream *substrea
        hw.buffer_bytes_max = SIZE_MAX;
        hw.fifo_size = dma_data->fifo_size;
 
+       if (pcm->flags & SND_DMAENGINE_PCM_FLAG_NO_RESIDUE)
+               hw.info |= SNDRV_PCM_INFO_BATCH;
+
        ret = dma_get_slave_caps(chan, &dma_caps);
        if (ret == 0) {
                if (dma_caps.cmd_pause)
                        hw.info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME;
+               if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT)
+                       hw.info |= SNDRV_PCM_INFO_BATCH;
        }
 
        return snd_soc_set_runtime_hwparams(substream, &hw);
@@ -171,17 +176,35 @@ static struct dma_chan *dmaengine_pcm_compat_request_channel(
 {
        struct dmaengine_pcm *pcm = soc_platform_to_pcm(rtd->platform);
        struct snd_dmaengine_dai_dma_data *dma_data;
+       dma_filter_fn fn = NULL;
 
        dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
 
        if ((pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) && pcm->chan[0])
                return pcm->chan[0];
 
-       if (pcm->config->compat_request_channel)
+       if (pcm->config && pcm->config->compat_request_channel)
                return pcm->config->compat_request_channel(rtd, substream);
 
-       return snd_dmaengine_pcm_request_channel(pcm->config->compat_filter_fn,
-                                                dma_data->filter_data);
+       if (pcm->config)
+               fn = pcm->config->compat_filter_fn;
+
+       return snd_dmaengine_pcm_request_channel(fn, dma_data->filter_data);
+}
+
+static bool dmaengine_pcm_can_report_residue(struct dma_chan *chan)
+{
+       struct dma_slave_caps dma_caps;
+       int ret;
+
+       ret = dma_get_slave_caps(chan, &dma_caps);
+       if (ret != 0)
+               return true;
+
+       if (dma_caps.residue_granularity == DMA_RESIDUE_GRANULARITY_DESCRIPTOR)
+               return false;
+
+       return true;
 }
 
 static int dmaengine_pcm_new(struct snd_soc_pcm_runtime *rtd)
@@ -236,6 +259,16 @@ static int dmaengine_pcm_new(struct snd_soc_pcm_runtime *rtd)
                                max_buffer_size);
                if (ret)
                        goto err_free;
+
+               /*
+                * This will only return false if we know for sure that at least
+                * one channel does not support residue reporting. If the DMA
+                * driver does not implement the slave_caps API we rely having
+                * the NO_RESIDUE flag set manually in case residue reporting is
+                * not supported.
+                */
+               if (!dmaengine_pcm_can_report_residue(pcm->chan[i]))
+                       pcm->flags |= SND_DMAENGINE_PCM_FLAG_NO_RESIDUE;
        }
 
        return 0;
@@ -245,6 +278,18 @@ err_free:
        return ret;
 }
 
+static snd_pcm_uframes_t dmaengine_pcm_pointer(
+       struct snd_pcm_substream *substream)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct dmaengine_pcm *pcm = soc_platform_to_pcm(rtd->platform);
+
+       if (pcm->flags & SND_DMAENGINE_PCM_FLAG_NO_RESIDUE)
+               return snd_dmaengine_pcm_pointer_no_residue(substream);
+       else
+               return snd_dmaengine_pcm_pointer(substream);
+}
+
 static const struct snd_pcm_ops dmaengine_pcm_ops = {
        .open           = dmaengine_pcm_open,
        .close          = snd_dmaengine_pcm_close,
@@ -252,7 +297,7 @@ static const struct snd_pcm_ops dmaengine_pcm_ops = {
        .hw_params      = dmaengine_pcm_hw_params,
        .hw_free        = snd_pcm_lib_free_pages,
        .trigger        = snd_dmaengine_pcm_trigger,
-       .pointer        = snd_dmaengine_pcm_pointer,
+       .pointer        = dmaengine_pcm_pointer,
 };
 
 static const struct snd_soc_platform_driver dmaengine_pcm_platform = {
@@ -262,47 +307,59 @@ static const struct snd_soc_platform_driver dmaengine_pcm_platform = {
        .probe_order    = SND_SOC_COMP_ORDER_LATE,
 };
 
-static const struct snd_pcm_ops dmaengine_no_residue_pcm_ops = {
-       .open           = dmaengine_pcm_open,
-       .close          = snd_dmaengine_pcm_close,
-       .ioctl          = snd_pcm_lib_ioctl,
-       .hw_params      = dmaengine_pcm_hw_params,
-       .hw_free        = snd_pcm_lib_free_pages,
-       .trigger        = snd_dmaengine_pcm_trigger,
-       .pointer        = snd_dmaengine_pcm_pointer_no_residue,
-};
-
-static const struct snd_soc_platform_driver dmaengine_no_residue_pcm_platform = {
-       .ops            = &dmaengine_no_residue_pcm_ops,
-       .pcm_new        = dmaengine_pcm_new,
-       .pcm_free       = dmaengine_pcm_free,
-       .probe_order    = SND_SOC_COMP_ORDER_LATE,
-};
-
 static const char * const dmaengine_pcm_dma_channel_names[] = {
        [SNDRV_PCM_STREAM_PLAYBACK] = "tx",
        [SNDRV_PCM_STREAM_CAPTURE] = "rx",
 };
 
-static void dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm,
-       struct device *dev)
+static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm,
+       struct device *dev, const struct snd_dmaengine_pcm_config *config)
 {
        unsigned int i;
+       const char *name;
+       struct dma_chan *chan;
 
        if ((pcm->flags & (SND_DMAENGINE_PCM_FLAG_NO_DT |
                           SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME)) ||
            !dev->of_node)
-               return;
+               return 0;
+
+       if (config && config->dma_dev) {
+               /*
+                * If this warning is seen, it probably means that your Linux
+                * device structure does not match your HW device structure.
+                * It would be best to refactor the Linux device structure to
+                * correctly match the HW structure.
+                */
+               dev_warn(dev, "DMA channels sourced from device %s",
+                        dev_name(config->dma_dev));
+               dev = config->dma_dev;
+       }
 
-       if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) {
-               pcm->chan[0] = dma_request_slave_channel(dev, "rx-tx");
-               pcm->chan[1] = pcm->chan[0];
-       } else {
-               for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE; i++) {
-                       pcm->chan[i] = dma_request_slave_channel(dev,
-                                       dmaengine_pcm_dma_channel_names[i]);
+       for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE;
+            i++) {
+               if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
+                       name = "rx-tx";
+               else
+                       name = dmaengine_pcm_dma_channel_names[i];
+               if (config && config->chan_names[i])
+                       name = config->chan_names[i];
+               chan = dma_request_slave_channel_reason(dev, name);
+               if (IS_ERR(chan)) {
+                       if (PTR_ERR(chan) == -EPROBE_DEFER)
+                               return -EPROBE_DEFER;
+                       pcm->chan[i] = NULL;
+               } else {
+                       pcm->chan[i] = chan;
                }
+               if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
+                       break;
        }
+
+       if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
+               pcm->chan[1] = pcm->chan[0];
+
+       return 0;
 }
 
 static void dmaengine_pcm_release_chan(struct dmaengine_pcm *pcm)
@@ -338,14 +395,12 @@ int snd_dmaengine_pcm_register(struct device *dev,
        pcm->config = config;
        pcm->flags = flags;
 
-       dmaengine_pcm_request_chan_of(pcm, dev);
+       ret = dmaengine_pcm_request_chan_of(pcm, dev, config);
+       if (ret)
+               goto err_free_dma;
 
-       if (flags & SND_DMAENGINE_PCM_FLAG_NO_RESIDUE)
-               ret = snd_soc_add_platform(dev, &pcm->platform,
-                               &dmaengine_no_residue_pcm_platform);
-       else
-               ret = snd_soc_add_platform(dev, &pcm->platform,
-                               &dmaengine_pcm_platform);
+       ret = snd_soc_add_platform(dev, &pcm->platform,
+               &dmaengine_pcm_platform);
        if (ret)
                goto err_free_dma;