]> git.proxmox.com Git - mirror_ubuntu-focal-kernel.git/blobdiff - drivers/dma/sh/shdma-base.c
dma: sh: provide a migration path for slave drivers to stop using .private
[mirror_ubuntu-focal-kernel.git] / drivers / dma / sh / shdma-base.c
index 73db282a14362dbd7fa8d2782fa70c8cfdfa11cf..27f5c781fd73fa2b1de7d96fa189eada935c0467 100644 (file)
@@ -171,6 +171,65 @@ static struct shdma_desc *shdma_get_desc(struct shdma_chan *schan)
        return NULL;
 }
 
+static int shdma_setup_slave(struct shdma_chan *schan, int slave_id)
+{
+       struct shdma_dev *sdev = to_shdma_dev(schan->dma_chan.device);
+       const struct shdma_ops *ops = sdev->ops;
+       int ret;
+
+       if (slave_id < 0 || slave_id >= slave_num)
+               return -EINVAL;
+
+       if (test_and_set_bit(slave_id, shdma_slave_used))
+               return -EBUSY;
+
+       ret = ops->set_slave(schan, slave_id, false);
+       if (ret < 0) {
+               clear_bit(slave_id, shdma_slave_used);
+               return ret;
+       }
+
+       schan->slave_id = slave_id;
+
+       return 0;
+}
+
+/*
+ * This is the standard shdma filter function to be used as a replacement to the
+ * "old" method, using the .private pointer. If for some reason you allocate a
+ * channel without slave data, use something like ERR_PTR(-EINVAL) as a filter
+ * parameter. If this filter is used, the slave driver, after calling
+ * dma_request_channel(), will also have to call dmaengine_slave_config() with
+ * .slave_id, .direction, and either .src_addr or .dst_addr set.
+ * NOTE: this filter doesn't support multiple DMAC drivers with the DMA_SLAVE
+ * capability! If this becomes a requirement, hardware glue drivers, using this
+ * services would have to provide their own filters, which first would check
+ * the device driver, similar to how other DMAC drivers, e.g., sa11x0-dma.c, do
+ * this, and only then, in case of a match, call this common filter.
+ */
+bool shdma_chan_filter(struct dma_chan *chan, void *arg)
+{
+       struct shdma_chan *schan = to_shdma_chan(chan);
+       struct shdma_dev *sdev = to_shdma_dev(schan->dma_chan.device);
+       const struct shdma_ops *ops = sdev->ops;
+       int slave_id = (int)arg;
+       int ret;
+
+       if (slave_id < 0)
+               /* No slave requested - arbitrary channel */
+               return true;
+
+       if (slave_id >= slave_num)
+               return false;
+
+       ret = ops->set_slave(schan, slave_id, true);
+       if (ret < 0)
+               return false;
+
+       return true;
+}
+EXPORT_SYMBOL(shdma_chan_filter);
+
 static int shdma_alloc_chan_resources(struct dma_chan *chan)
 {
        struct shdma_chan *schan = to_shdma_chan(chan);
@@ -185,21 +244,10 @@ static int shdma_alloc_chan_resources(struct dma_chan *chan)
         * never runs concurrently with itself or free_chan_resources.
         */
        if (slave) {
-               if (slave->slave_id < 0 || slave->slave_id >= slave_num) {
-                       ret = -EINVAL;
-                       goto evalid;
-               }
-
-               if (test_and_set_bit(slave->slave_id, shdma_slave_used)) {
-                       ret = -EBUSY;
-                       goto etestused;
-               }
-
-               ret = ops->set_slave(schan, slave->slave_id);
+               /* Legacy mode: .private is set in filter */
+               ret = shdma_setup_slave(schan, slave->slave_id);
                if (ret < 0)
                        goto esetslave;
-
-               schan->slave_id = slave->slave_id;
        } else {
                schan->slave_id = -EINVAL;
        }
@@ -228,8 +276,6 @@ edescalloc:
        if (slave)
 esetslave:
                clear_bit(slave->slave_id, shdma_slave_used);
-etestused:
-evalid:
        chan->private = NULL;
        return ret;
 }
@@ -587,22 +633,40 @@ static int shdma_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
        struct shdma_chan *schan = to_shdma_chan(chan);
        struct shdma_dev *sdev = to_shdma_dev(chan->device);
        const struct shdma_ops *ops = sdev->ops;
+       struct dma_slave_config *config;
        unsigned long flags;
-
-       /* Only supports DMA_TERMINATE_ALL */
-       if (cmd != DMA_TERMINATE_ALL)
-               return -ENXIO;
+       int ret;
 
        if (!chan)
                return -EINVAL;
 
-       spin_lock_irqsave(&schan->chan_lock, flags);
-
-       ops->halt_channel(schan);
-
-       spin_unlock_irqrestore(&schan->chan_lock, flags);
+       switch (cmd) {
+       case DMA_TERMINATE_ALL:
+               spin_lock_irqsave(&schan->chan_lock, flags);
+               ops->halt_channel(schan);
+               spin_unlock_irqrestore(&schan->chan_lock, flags);
 
-       shdma_chan_ld_cleanup(schan, true);
+               shdma_chan_ld_cleanup(schan, true);
+               break;
+       case DMA_SLAVE_CONFIG:
+               /*
+                * So far only .slave_id is used, but the slave drivers are
+                * encouraged to also set a transfer direction and an address.
+                */
+               if (!arg)
+                       return -EINVAL;
+               /*
+                * We could lock this, but you shouldn't be configuring the
+                * channel, while using it...
+                */
+               config = (struct dma_slave_config *)arg;
+               ret = shdma_setup_slave(schan, config->slave_id);
+               if (ret < 0)
+                       return ret;
+               break;
+       default:
+               return -ENXIO;
+       }
 
        return 0;
 }