Merge tag 'ecryptfs-3.19-rc1-fixes' of git://git.kernel.org/pub/scm/linux/kernel...
[pandora-kernel.git] / drivers / dma / sun6i-dma.c
index 3aa10b3..159f173 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/interrupt.h>
 #include <linux/module.h>
 #include <linux/of_dma.h>
+#include <linux/of_device.h>
 #include <linux/platform_device.h>
 #include <linux/reset.h>
 #include <linux/slab.h>
 
 #include "virt-dma.h"
 
-/*
- * There's 16 physical channels that can work in parallel.
- *
- * However we have 30 different endpoints for our requests.
- *
- * Since the channels are able to handle only an unidirectional
- * transfer, we need to allocate more virtual channels so that
- * everyone can grab one channel.
- *
- * Some devices can't work in both direction (mostly because it
- * wouldn't make sense), so we have a bit fewer virtual channels than
- * 2 channels per endpoints.
- */
-
-#define NR_MAX_CHANNELS                16
-#define NR_MAX_REQUESTS                30
-#define NR_MAX_VCHANS          53
-
 /*
  * Common registers
  */
 
 #define DMA_STAT               0x30
 
+/*
+ * sun8i specific registers
+ */
+#define SUN8I_DMA_GATE         0x20
+#define SUN8I_DMA_GATE_ENABLE  0x4
+
 /*
  * Channels specific registers
  */
 #define NORMAL_WAIT    8
 #define DRQ_SDRAM      1
 
+/*
+ * Hardware channels / ports representation
+ *
+ * The hardware is used in several SoCs, with differing numbers
+ * of channels and endpoints. This structure ties those numbers
+ * to a certain compatible string.
+ */
+struct sun6i_dma_config {
+       u32 nr_max_channels;
+       u32 nr_max_requests;
+       u32 nr_max_vchans;
+};
+
 /*
  * Hardware representation of the LLI
  *
@@ -159,6 +161,7 @@ struct sun6i_dma_dev {
        struct dma_pool         *pool;
        struct sun6i_pchan      *pchans;
        struct sun6i_vchan      *vchans;
+       const struct sun6i_dma_config *cfg;
 };
 
 static struct device *chan2dev(struct dma_chan *chan)
@@ -230,30 +233,25 @@ static inline void sun6i_dma_dump_chan_regs(struct sun6i_dma_dev *sdev,
                readl(pchan->base + DMA_CHAN_CUR_PARA));
 }
 
-static inline int convert_burst(u32 maxburst, u8 *burst)
+static inline s8 convert_burst(u32 maxburst)
 {
        switch (maxburst) {
        case 1:
-               *burst = 0;
-               break;
+               return 0;
        case 8:
-               *burst = 2;
-               break;
+               return 2;
        default:
                return -EINVAL;
        }
-
-       return 0;
 }
 
-static inline int convert_buswidth(enum dma_slave_buswidth addr_width, u8 *width)
+static inline s8 convert_buswidth(enum dma_slave_buswidth addr_width)
 {
        if ((addr_width < DMA_SLAVE_BUSWIDTH_1_BYTE) ||
            (addr_width > DMA_SLAVE_BUSWIDTH_4_BYTES))
                return -EINVAL;
 
-       *width = addr_width >> 1;
-       return 0;
+       return addr_width >> 1;
 }
 
 static void *sun6i_dma_lli_add(struct sun6i_dma_lli *prev,
@@ -284,26 +282,25 @@ static inline int sun6i_dma_cfg_lli(struct sun6i_dma_lli *lli,
                                    struct dma_slave_config *config)
 {
        u8 src_width, dst_width, src_burst, dst_burst;
-       int ret;
 
        if (!config)
                return -EINVAL;
 
-       ret = convert_burst(config->src_maxburst, &src_burst);
-       if (ret)
-               return ret;
+       src_burst = convert_burst(config->src_maxburst);
+       if (src_burst)
+               return src_burst;
 
-       ret = convert_burst(config->dst_maxburst, &dst_burst);
-       if (ret)
-               return ret;
+       dst_burst = convert_burst(config->dst_maxburst);
+       if (dst_burst)
+               return dst_burst;
 
-       ret = convert_buswidth(config->src_addr_width, &src_width);
-       if (ret)
-               return ret;
+       src_width = convert_buswidth(config->src_addr_width);
+       if (src_width)
+               return src_width;
 
-       ret = convert_buswidth(config->dst_addr_width, &dst_width);
-       if (ret)
-               return ret;
+       dst_width = convert_buswidth(config->dst_addr_width);
+       if (dst_width)
+               return dst_width;
 
        lli->cfg = DMA_CHAN_CFG_SRC_BURST(src_burst) |
                DMA_CHAN_CFG_SRC_WIDTH(src_width) |
@@ -432,6 +429,7 @@ static int sun6i_dma_start_desc(struct sun6i_vchan *vchan)
 static void sun6i_dma_tasklet(unsigned long data)
 {
        struct sun6i_dma_dev *sdev = (struct sun6i_dma_dev *)data;
+       const struct sun6i_dma_config *cfg = sdev->cfg;
        struct sun6i_vchan *vchan;
        struct sun6i_pchan *pchan;
        unsigned int pchan_alloc = 0;
@@ -459,7 +457,7 @@ static void sun6i_dma_tasklet(unsigned long data)
        }
 
        spin_lock_irq(&sdev->lock);
-       for (pchan_idx = 0; pchan_idx < NR_MAX_CHANNELS; pchan_idx++) {
+       for (pchan_idx = 0; pchan_idx < cfg->nr_max_channels; pchan_idx++) {
                pchan = &sdev->pchans[pchan_idx];
 
                if (pchan->vchan || list_empty(&sdev->pending))
@@ -480,7 +478,7 @@ static void sun6i_dma_tasklet(unsigned long data)
        }
        spin_unlock_irq(&sdev->lock);
 
-       for (pchan_idx = 0; pchan_idx < NR_MAX_CHANNELS; pchan_idx++) {
+       for (pchan_idx = 0; pchan_idx < cfg->nr_max_channels; pchan_idx++) {
                if (!(pchan_alloc & BIT(pchan_idx)))
                        continue;
 
@@ -502,7 +500,7 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
        int i, j, ret = IRQ_NONE;
        u32 status;
 
-       for (i = 0; i < 2; i++) {
+       for (i = 0; i < sdev->cfg->nr_max_channels / DMA_IRQ_CHAN_NR; i++) {
                status = readl(sdev->base + DMA_IRQ_STAT(i));
                if (!status)
                        continue;
@@ -512,7 +510,7 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
 
                writel(status, sdev->base + DMA_IRQ_STAT(i));
 
-               for (j = 0; (j < 8) && status; j++) {
+               for (j = 0; (j < DMA_IRQ_CHAN_NR) && status; j++) {
                        if (status & DMA_IRQ_QUEUE) {
                                pchan = sdev->pchans + j;
                                vchan = pchan->vchan;
@@ -525,7 +523,7 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
                                }
                        }
 
-                       status = status >> 4;
+                       status = status >> DMA_IRQ_CHAN_WIDTH;
                }
 
                if (!atomic_read(&sdev->tasklet_shutdown))
@@ -542,11 +540,10 @@ static struct dma_async_tx_descriptor *sun6i_dma_prep_dma_memcpy(
 {
        struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
        struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
-       struct dma_slave_config *sconfig = &vchan->cfg;
        struct sun6i_dma_lli *v_lli;
        struct sun6i_desc *txd;
        dma_addr_t p_lli;
-       int ret;
+       s8 burst, width;
 
        dev_dbg(chan2dev(chan),
                "%s; chan: %d, dest: %pad, src: %pad, len: %zu. flags: 0x%08lx\n",
@@ -565,14 +562,21 @@ static struct dma_async_tx_descriptor *sun6i_dma_prep_dma_memcpy(
                goto err_txd_free;
        }
 
-       ret = sun6i_dma_cfg_lli(v_lli, src, dest, len, sconfig);
-       if (ret)
-               goto err_dma_free;
+       v_lli->src = src;
+       v_lli->dst = dest;
+       v_lli->len = len;
+       v_lli->para = NORMAL_WAIT;
 
+       burst = convert_burst(8);
+       width = convert_buswidth(DMA_SLAVE_BUSWIDTH_4_BYTES);
        v_lli->cfg |= DMA_CHAN_CFG_SRC_DRQ(DRQ_SDRAM) |
                DMA_CHAN_CFG_DST_DRQ(DRQ_SDRAM) |
                DMA_CHAN_CFG_DST_LINEAR_MODE |
-               DMA_CHAN_CFG_SRC_LINEAR_MODE;
+               DMA_CHAN_CFG_SRC_LINEAR_MODE |
+               DMA_CHAN_CFG_SRC_BURST(burst) |
+               DMA_CHAN_CFG_SRC_WIDTH(width) |
+               DMA_CHAN_CFG_DST_BURST(burst) |
+               DMA_CHAN_CFG_DST_WIDTH(width);
 
        sun6i_dma_lli_add(NULL, v_lli, p_lli, txd);
 
@@ -580,8 +584,6 @@ static struct dma_async_tx_descriptor *sun6i_dma_prep_dma_memcpy(
 
        return vchan_tx_prep(&vchan->vc, &txd->vd, flags);
 
-err_dma_free:
-       dma_pool_free(sdev->pool, v_lli, p_lli);
 err_txd_free:
        kfree(txd);
        return NULL;
@@ -817,7 +819,7 @@ static struct dma_chan *sun6i_dma_of_xlate(struct of_phandle_args *dma_spec,
        struct dma_chan *chan;
        u8 port = dma_spec->args[0];
 
-       if (port > NR_MAX_REQUESTS)
+       if (port > sdev->cfg->nr_max_requests)
                return NULL;
 
        chan = dma_get_any_slave_channel(&sdev->slave);
@@ -850,7 +852,7 @@ static inline void sun6i_dma_free(struct sun6i_dma_dev *sdev)
 {
        int i;
 
-       for (i = 0; i < NR_MAX_VCHANS; i++) {
+       for (i = 0; i < sdev->cfg->nr_max_vchans; i++) {
                struct sun6i_vchan *vchan = &sdev->vchans[i];
 
                list_del(&vchan->vc.chan.device_node);
@@ -858,8 +860,48 @@ static inline void sun6i_dma_free(struct sun6i_dma_dev *sdev)
        }
 }
 
+/*
+ * For A31:
+ *
+ * There's 16 physical channels that can work in parallel.
+ *
+ * However we have 30 different endpoints for our requests.
+ *
+ * Since the channels are able to handle only an unidirectional
+ * transfer, we need to allocate more virtual channels so that
+ * everyone can grab one channel.
+ *
+ * Some devices can't work in both direction (mostly because it
+ * wouldn't make sense), so we have a bit fewer virtual channels than
+ * 2 channels per endpoints.
+ */
+
+static struct sun6i_dma_config sun6i_a31_dma_cfg = {
+       .nr_max_channels = 16,
+       .nr_max_requests = 30,
+       .nr_max_vchans   = 53,
+};
+
+/*
+ * The A23 only has 8 physical channels, a maximum DRQ port id of 24,
+ * and a total of 37 usable source and destination endpoints.
+ */
+
+static struct sun6i_dma_config sun8i_a23_dma_cfg = {
+       .nr_max_channels = 8,
+       .nr_max_requests = 24,
+       .nr_max_vchans   = 37,
+};
+
+static struct of_device_id sun6i_dma_match[] = {
+       { .compatible = "allwinner,sun6i-a31-dma", .data = &sun6i_a31_dma_cfg },
+       { .compatible = "allwinner,sun8i-a23-dma", .data = &sun8i_a23_dma_cfg },
+       { /* sentinel */ }
+};
+
 static int sun6i_dma_probe(struct platform_device *pdev)
 {
+       const struct of_device_id *device;
        struct sun6i_dma_dev *sdc;
        struct resource *res;
        int ret, i;
@@ -868,6 +910,11 @@ static int sun6i_dma_probe(struct platform_device *pdev)
        if (!sdc)
                return -ENOMEM;
 
+       device = of_match_device(sun6i_dma_match, &pdev->dev);
+       if (!device)
+               return -ENODEV;
+       sdc->cfg = device->data;
+
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        sdc->base = devm_ioremap_resource(&pdev->dev, res);
        if (IS_ERR(sdc->base))
@@ -914,30 +961,30 @@ static int sun6i_dma_probe(struct platform_device *pdev)
        sdc->slave.device_prep_slave_sg         = sun6i_dma_prep_slave_sg;
        sdc->slave.device_prep_dma_memcpy       = sun6i_dma_prep_dma_memcpy;
        sdc->slave.device_control               = sun6i_dma_control;
-       sdc->slave.chancnt                      = NR_MAX_VCHANS;
+       sdc->slave.copy_align                   = 4;
 
        sdc->slave.dev = &pdev->dev;
 
-       sdc->pchans = devm_kcalloc(&pdev->dev, NR_MAX_CHANNELS,
+       sdc->pchans = devm_kcalloc(&pdev->dev, sdc->cfg->nr_max_channels,
                                   sizeof(struct sun6i_pchan), GFP_KERNEL);
        if (!sdc->pchans)
                return -ENOMEM;
 
-       sdc->vchans = devm_kcalloc(&pdev->dev, NR_MAX_VCHANS,
+       sdc->vchans = devm_kcalloc(&pdev->dev, sdc->cfg->nr_max_vchans,
                                   sizeof(struct sun6i_vchan), GFP_KERNEL);
        if (!sdc->vchans)
                return -ENOMEM;
 
        tasklet_init(&sdc->task, sun6i_dma_tasklet, (unsigned long)sdc);
 
-       for (i = 0; i < NR_MAX_CHANNELS; i++) {
+       for (i = 0; i < sdc->cfg->nr_max_channels; i++) {
                struct sun6i_pchan *pchan = &sdc->pchans[i];
 
                pchan->idx = i;
                pchan->base = sdc->base + 0x100 + i * 0x40;
        }
 
-       for (i = 0; i < NR_MAX_VCHANS; i++) {
+       for (i = 0; i < sdc->cfg->nr_max_vchans; i++) {
                struct sun6i_vchan *vchan = &sdc->vchans[i];
 
                INIT_LIST_HEAD(&vchan->node);
@@ -977,6 +1024,15 @@ static int sun6i_dma_probe(struct platform_device *pdev)
                goto err_dma_unregister;
        }
 
+       /*
+        * sun8i variant requires us to toggle a dma gating register,
+        * as seen in Allwinner's SDK. This register is not documented
+        * in the A23 user manual.
+        */
+       if (of_device_is_compatible(pdev->dev.of_node,
+                                   "allwinner,sun8i-a23-dma"))
+               writel(SUN8I_DMA_GATE_ENABLE, sdc->base + SUN8I_DMA_GATE);
+
        return 0;
 
 err_dma_unregister:
@@ -1009,11 +1065,6 @@ static int sun6i_dma_remove(struct platform_device *pdev)
        return 0;
 }
 
-static struct of_device_id sun6i_dma_match[] = {
-       { .compatible = "allwinner,sun6i-a31-dma" },
-       { /* sentinel */ }
-};
-
 static struct platform_driver sun6i_dma_driver = {
        .probe          = sun6i_dma_probe,
        .remove         = sun6i_dma_remove,