diff options
| author | Jassi Brar <jassi.brar@samsung.com> | 2010-05-23 23:28:19 -0400 |
|---|---|---|
| committer | Dan Williams <dan.j.williams@intel.com> | 2010-05-23 23:28:19 -0400 |
| commit | b3040e40675ec2c43542331cd30d4ee3dae797e8 (patch) | |
| tree | a327c4a92118dd0e1c61b8455e744910cd121f57 | |
| parent | 6f68fbaafbaa033205cd131d3e1f3c4b914e9b78 (diff) | |
DMA: PL330: Add dma api driver
Add DMA Engine API driver for the PL330 DMAC.
This driver is supposed to be reusable by various
platforms that have one or more PL330 DMACs.
Atm, DMA_SLAVE and DMA_MEMCPY capabilities have been
implemented.
Signed-off-by: Jassi Brar <jassi.brar@samsung.com>
Reviewed-by: Linus Walleij <linus.walleij@stericsson.com>
[dan.j.williams@intel.com: missing slab.h and ->device_control() fixups]
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
| -rw-r--r-- | drivers/dma/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/dma/Makefile | 1 | ||||
| -rw-r--r-- | drivers/dma/pl330.c | 866 | ||||
| -rw-r--r-- | include/linux/amba/pl330.h | 45 |
4 files changed, 921 insertions, 0 deletions
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 1b8877922fb0..9e01e96fee94 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig | |||
| @@ -166,6 +166,15 @@ config TIMB_DMA | |||
| 166 | config ARCH_HAS_ASYNC_TX_FIND_CHANNEL | 166 | config ARCH_HAS_ASYNC_TX_FIND_CHANNEL |
| 167 | bool | 167 | bool |
| 168 | 168 | ||
| 169 | config PL330_DMA | ||
| 170 | tristate "DMA API Driver for PL330" | ||
| 171 | select DMA_ENGINE | ||
| 172 | depends on PL330 | ||
| 173 | help | ||
| 174 | Select if your platform has one or more PL330 DMACs. | ||
| 175 | You need to provide platform specific settings via | ||
| 176 | platform_data for a dma-pl330 device. | ||
| 177 | |||
| 169 | config DMA_ENGINE | 178 | config DMA_ENGINE |
| 170 | bool | 179 | bool |
| 171 | 180 | ||
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index 20881426c1ac..0fe5ebbfda5d 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile | |||
| @@ -22,3 +22,4 @@ obj-$(CONFIG_COH901318) += coh901318.o coh901318_lli.o | |||
| 22 | obj-$(CONFIG_AMCC_PPC440SPE_ADMA) += ppc4xx/ | 22 | obj-$(CONFIG_AMCC_PPC440SPE_ADMA) += ppc4xx/ |
| 23 | obj-$(CONFIG_TIMB_DMA) += timb_dma.o | 23 | obj-$(CONFIG_TIMB_DMA) += timb_dma.o |
| 24 | obj-$(CONFIG_STE_DMA40) += ste_dma40.o ste_dma40_ll.o | 24 | obj-$(CONFIG_STE_DMA40) += ste_dma40.o ste_dma40_ll.o |
| 25 | obj-$(CONFIG_PL330_DMA) += pl330.o | ||
diff --git a/drivers/dma/pl330.c b/drivers/dma/pl330.c new file mode 100644 index 000000000000..7c50f6dfd3f4 --- /dev/null +++ b/drivers/dma/pl330.c | |||
| @@ -0,0 +1,866 @@ | |||
| 1 | /* linux/drivers/dma/pl330.c | ||
| 2 | * | ||
| 3 | * Copyright (C) 2010 Samsung Electronics Co. Ltd. | ||
| 4 | * Jaswinder Singh <jassi.brar@samsung.com> | ||
| 5 | * | ||
| 6 | * This program is free software; you can redistribute it and/or modify | ||
| 7 | * it under the terms of the GNU General Public License as published by | ||
| 8 | * the Free Software Foundation; either version 2 of the License, or | ||
| 9 | * (at your option) any later version. | ||
| 10 | */ | ||
| 11 | |||
| 12 | #include <linux/io.h> | ||
| 13 | #include <linux/init.h> | ||
| 14 | #include <linux/slab.h> | ||
| 15 | #include <linux/module.h> | ||
| 16 | #include <linux/dmaengine.h> | ||
| 17 | #include <linux/interrupt.h> | ||
| 18 | #include <linux/amba/bus.h> | ||
| 19 | #include <linux/amba/pl330.h> | ||
| 20 | |||
| 21 | #define NR_DEFAULT_DESC 16 | ||
| 22 | |||
| 23 | enum desc_status { | ||
| 24 | /* In the DMAC pool */ | ||
| 25 | FREE, | ||
| 26 | /* | ||
| 27 | * Allocted to some channel during prep_xxx | ||
| 28 | * Also may be sitting on the work_list. | ||
| 29 | */ | ||
| 30 | PREP, | ||
| 31 | /* | ||
| 32 | * Sitting on the work_list and already submitted | ||
| 33 | * to the PL330 core. Not more than two descriptors | ||
| 34 | * of a channel can be BUSY at any time. | ||
| 35 | */ | ||
| 36 | BUSY, | ||
| 37 | /* | ||
| 38 | * Sitting on the channel work_list but xfer done | ||
| 39 | * by PL330 core | ||
| 40 | */ | ||
| 41 | DONE, | ||
| 42 | }; | ||
| 43 | |||
| 44 | struct dma_pl330_chan { | ||
| 45 | /* Schedule desc completion */ | ||
| 46 | struct tasklet_struct task; | ||
| 47 | |||
| 48 | /* DMA-Engine Channel */ | ||
| 49 | struct dma_chan chan; | ||
| 50 | |||
| 51 | /* Last completed cookie */ | ||
| 52 | dma_cookie_t completed; | ||
| 53 | |||
| 54 | /* List of to be xfered descriptors */ | ||
| 55 | struct list_head work_list; | ||
| 56 | |||
| 57 | /* Pointer to the DMAC that manages this channel, | ||
| 58 | * NULL if the channel is available to be acquired. | ||
| 59 | * As the parent, this DMAC also provides descriptors | ||
| 60 | * to the channel. | ||
| 61 | */ | ||
| 62 | struct dma_pl330_dmac *dmac; | ||
| 63 | |||
| 64 | /* To protect channel manipulation */ | ||
| 65 | spinlock_t lock; | ||
| 66 | |||
| 67 | /* Token of a hardware channel thread of PL330 DMAC | ||
| 68 | * NULL if the channel is available to be acquired. | ||
| 69 | */ | ||
| 70 | void *pl330_chid; | ||
| 71 | }; | ||
| 72 | |||
| 73 | struct dma_pl330_dmac { | ||
| 74 | struct pl330_info pif; | ||
| 75 | |||
| 76 | /* DMA-Engine Device */ | ||
| 77 | struct dma_device ddma; | ||
| 78 | |||
| 79 | /* Pool of descriptors available for the DMAC's channels */ | ||
| 80 | struct list_head desc_pool; | ||
| 81 | /* To protect desc_pool manipulation */ | ||
| 82 | spinlock_t pool_lock; | ||
| 83 | |||
| 84 | /* Peripheral channels connected to this DMAC */ | ||
| 85 | struct dma_pl330_chan peripherals[0]; /* keep at end */ | ||
| 86 | }; | ||
| 87 | |||
| 88 | struct dma_pl330_desc { | ||
| 89 | /* To attach to a queue as child */ | ||
| 90 | struct list_head node; | ||
| 91 | |||
| 92 | /* Descriptor for the DMA Engine API */ | ||
| 93 | struct dma_async_tx_descriptor txd; | ||
| 94 | |||
| 95 | /* Xfer for PL330 core */ | ||
| 96 | struct pl330_xfer px; | ||
| 97 | |||
| 98 | struct pl330_reqcfg rqcfg; | ||
| 99 | struct pl330_req req; | ||
| 100 | |||
| 101 | enum desc_status status; | ||
| 102 | |||
| 103 | /* The channel which currently holds this desc */ | ||
| 104 | struct dma_pl330_chan *pchan; | ||
| 105 | }; | ||
| 106 | |||
| 107 | static inline struct dma_pl330_chan * | ||
| 108 | to_pchan(struct dma_chan *ch) | ||
| 109 | { | ||
| 110 | if (!ch) | ||
| 111 | return NULL; | ||
| 112 | |||
| 113 | return container_of(ch, struct dma_pl330_chan, chan); | ||
| 114 | } | ||
| 115 | |||
| 116 | static inline struct dma_pl330_desc * | ||
| 117 | to_desc(struct dma_async_tx_descriptor *tx) | ||
| 118 | { | ||
| 119 | return container_of(tx, struct dma_pl330_desc, txd); | ||
| 120 | } | ||
| 121 | |||
| 122 | static inline void free_desc_list(struct list_head *list) | ||
| 123 | { | ||
| 124 | struct dma_pl330_dmac *pdmac; | ||
| 125 | struct dma_pl330_desc *desc; | ||
| 126 | struct dma_pl330_chan *pch; | ||
| 127 | unsigned long flags; | ||
| 128 | |||
| 129 | if (list_empty(list)) | ||
| 130 | return; | ||
| 131 | |||
| 132 | /* Finish off the work list */ | ||
| 133 | list_for_each_entry(desc, list, node) { | ||
| 134 | dma_async_tx_callback callback; | ||
| 135 | void *param; | ||
| 136 | |||
| 137 | /* All desc in a list belong to same channel */ | ||
| 138 | pch = desc->pchan; | ||
| 139 | callback = desc->txd.callback; | ||
| 140 | param = desc->txd.callback_param; | ||
| 141 | |||
| 142 | if (callback) | ||
| 143 | callback(param); | ||
| 144 | |||
| 145 | desc->pchan = NULL; | ||
| 146 | } | ||
| 147 | |||
| 148 | pdmac = pch->dmac; | ||
| 149 | |||
| 150 | spin_lock_irqsave(&pdmac->pool_lock, flags); | ||
| 151 | list_splice_tail_init(list, &pdmac->desc_pool); | ||
| 152 | spin_unlock_irqrestore(&pdmac->pool_lock, flags); | ||
| 153 | } | ||
| 154 | |||
| 155 | static inline void fill_queue(struct dma_pl330_chan *pch) | ||
| 156 | { | ||
| 157 | struct dma_pl330_desc *desc; | ||
| 158 | int ret; | ||
| 159 | |||
| 160 | list_for_each_entry(desc, &pch->work_list, node) { | ||
| 161 | |||
| 162 | /* If already submitted */ | ||
| 163 | if (desc->status == BUSY) | ||
| 164 | break; | ||
| 165 | |||
| 166 | ret = pl330_submit_req(pch->pl330_chid, | ||
| 167 | &desc->req); | ||
| 168 | if (!ret) { | ||
| 169 | desc->status = BUSY; | ||
| 170 | break; | ||
| 171 | } else if (ret == -EAGAIN) { | ||
| 172 | /* QFull or DMAC Dying */ | ||
| 173 | break; | ||
| 174 | } else { | ||
