| Richard Röjfors | de5d445 | 2010-03-25 19:44:21 +0100 | [diff] [blame] | 1 | /* | 
|  | 2 | * timb_dma.c timberdale FPGA DMA driver | 
|  | 3 | * Copyright (c) 2010 Intel Corporation | 
|  | 4 | * | 
|  | 5 | * This program is free software; you can redistribute it and/or modify | 
|  | 6 | * it under the terms of the GNU General Public License version 2 as | 
|  | 7 | * published by the Free Software Foundation. | 
|  | 8 | * | 
|  | 9 | * This program is distributed in the hope that it will be useful, | 
|  | 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | 12 | * GNU General Public License for more details. | 
|  | 13 | * | 
|  | 14 | * You should have received a copy of the GNU General Public License | 
|  | 15 | * along with this program; if not, write to the Free Software | 
|  | 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | 
|  | 17 | */ | 
|  | 18 |  | 
|  | 19 | /* Supports: | 
|  | 20 | * Timberdale FPGA DMA engine | 
|  | 21 | */ | 
|  | 22 |  | 
|  | 23 | #include <linux/dmaengine.h> | 
|  | 24 | #include <linux/dma-mapping.h> | 
|  | 25 | #include <linux/init.h> | 
|  | 26 | #include <linux/interrupt.h> | 
|  | 27 | #include <linux/io.h> | 
|  | 28 | #include <linux/module.h> | 
|  | 29 | #include <linux/platform_device.h> | 
| Stephen Rothwell | 6a3cd3e | 2010-03-29 15:54:40 +1100 | [diff] [blame] | 30 | #include <linux/slab.h> | 
| Richard Röjfors | de5d445 | 2010-03-25 19:44:21 +0100 | [diff] [blame] | 31 |  | 
|  | 32 | #include <linux/timb_dma.h> | 
|  | 33 |  | 
|  | 34 | #define DRIVER_NAME "timb-dma" | 
|  | 35 |  | 
|  | 36 | /* Global DMA registers */ | 
|  | 37 | #define TIMBDMA_ACR		0x34 | 
|  | 38 | #define TIMBDMA_32BIT_ADDR	0x01 | 
|  | 39 |  | 
|  | 40 | #define TIMBDMA_ISR		0x080000 | 
|  | 41 | #define TIMBDMA_IPR		0x080004 | 
|  | 42 | #define TIMBDMA_IER		0x080008 | 
|  | 43 |  | 
|  | 44 | /* Channel specific registers */ | 
|  | 45 | /* RX instances base addresses are 0x00, 0x40, 0x80 ... | 
|  | 46 | * TX instances base addresses are 0x18, 0x58, 0x98 ... | 
|  | 47 | */ | 
|  | 48 | #define TIMBDMA_INSTANCE_OFFSET		0x40 | 
|  | 49 | #define TIMBDMA_INSTANCE_TX_OFFSET	0x18 | 
|  | 50 |  | 
|  | 51 | /* RX registers, relative the instance base */ | 
|  | 52 | #define TIMBDMA_OFFS_RX_DHAR	0x00 | 
|  | 53 | #define TIMBDMA_OFFS_RX_DLAR	0x04 | 
|  | 54 | #define TIMBDMA_OFFS_RX_LR	0x0C | 
|  | 55 | #define TIMBDMA_OFFS_RX_BLR	0x10 | 
|  | 56 | #define TIMBDMA_OFFS_RX_ER	0x14 | 
|  | 57 | #define TIMBDMA_RX_EN		0x01 | 
|  | 58 | /* bytes per Row, video specific register | 
|  | 59 | * which is placed after the TX registers... | 
|  | 60 | */ | 
|  | 61 | #define TIMBDMA_OFFS_RX_BPRR	0x30 | 
|  | 62 |  | 
|  | 63 | /* TX registers, relative the instance base */ | 
|  | 64 | #define TIMBDMA_OFFS_TX_DHAR	0x00 | 
|  | 65 | #define TIMBDMA_OFFS_TX_DLAR	0x04 | 
|  | 66 | #define TIMBDMA_OFFS_TX_BLR	0x0C | 
|  | 67 | #define TIMBDMA_OFFS_TX_LR	0x14 | 
|  | 68 |  | 
|  | 69 |  | 
|  | 70 | #define TIMB_DMA_DESC_SIZE	8 | 
|  | 71 |  | 
|  | 72 | struct timb_dma_desc { | 
|  | 73 | struct list_head		desc_node; | 
|  | 74 | struct dma_async_tx_descriptor	txd; | 
|  | 75 | u8				*desc_list; | 
|  | 76 | unsigned int			desc_list_len; | 
|  | 77 | bool				interrupt; | 
|  | 78 | }; | 
|  | 79 |  | 
|  | 80 | struct timb_dma_chan { | 
|  | 81 | struct dma_chan		chan; | 
|  | 82 | void __iomem		*membase; | 
| Richard Röjfors | 0f65169 | 2010-03-26 08:23:58 +0100 | [diff] [blame] | 83 | spinlock_t		lock; /* Used to protect data structures, | 
|  | 84 | especially the lists and descriptors, | 
|  | 85 | from races between the tasklet and calls | 
|  | 86 | from above */ | 
| Richard Röjfors | de5d445 | 2010-03-25 19:44:21 +0100 | [diff] [blame] | 87 | dma_cookie_t		last_completed_cookie; | 
|  | 88 | bool			ongoing; | 
|  | 89 | struct list_head	active_list; | 
|  | 90 | struct list_head	queue; | 
|  | 91 | struct list_head	free_list; | 
|  | 92 | unsigned int		bytes_per_line; | 
|  | 93 | enum dma_data_direction	direction; | 
|  | 94 | unsigned int		descs; /* Descriptors to allocate */ | 
|  | 95 | unsigned int		desc_elems; /* number of elems per descriptor */ | 
|  | 96 | }; | 
|  | 97 |  | 
|  | 98 | struct timb_dma { | 
|  | 99 | struct dma_device	dma; | 
|  | 100 | void __iomem		*membase; | 
|  | 101 | struct tasklet_struct	tasklet; | 
|  | 102 | struct timb_dma_chan	channels[0]; | 
|  | 103 | }; | 
|  | 104 |  | 
|  | 105 | static struct device *chan2dev(struct dma_chan *chan) | 
|  | 106 | { | 
|  | 107 | return &chan->dev->device; | 
|  | 108 | } | 
|  | 109 | static struct device *chan2dmadev(struct dma_chan *chan) | 
|  | 110 | { | 
|  | 111 | return chan2dev(chan)->parent->parent; | 
|  | 112 | } | 
|  | 113 |  | 
|  | 114 | static struct timb_dma *tdchantotd(struct timb_dma_chan *td_chan) | 
|  | 115 | { | 
|  | 116 | int id = td_chan->chan.chan_id; | 
|  | 117 | return (struct timb_dma *)((u8 *)td_chan - | 
|  | 118 | id * sizeof(struct timb_dma_chan) - sizeof(struct timb_dma)); | 
|  | 119 | } | 
|  | 120 |  | 
|  | 121 | /* Must be called with the spinlock held */ | 
|  | 122 | static void __td_enable_chan_irq(struct timb_dma_chan *td_chan) | 
|  | 123 | { | 
|  | 124 | int id = td_chan->chan.chan_id; | 
|  | 125 | struct timb_dma *td = tdchantotd(td_chan); | 
|  | 126 | u32 ier; | 
|  | 127 |  | 
|  | 128 | /* enable interrupt for this channel */ | 
|  | 129 | ier = ioread32(td->membase + TIMBDMA_IER); | 
|  | 130 | ier |= 1 << id; | 
|  | 131 | dev_dbg(chan2dev(&td_chan->chan), "Enabling irq: %d, IER: 0x%x\n", id, | 
|  | 132 | ier); | 
|  | 133 | iowrite32(ier, td->membase + TIMBDMA_IER); | 
|  | 134 | } | 
|  | 135 |  | 
|  | 136 | /* Should be called with the spinlock held */ | 
|  | 137 | static bool __td_dma_done_ack(struct timb_dma_chan *td_chan) | 
|  | 138 | { | 
|  | 139 | int id = td_chan->chan.chan_id; | 
|  | 140 | struct timb_dma *td = (struct timb_dma *)((u8 *)td_chan - | 
|  | 141 | id * sizeof(struct timb_dma_chan) - sizeof(struct timb_dma)); | 
|  | 142 | u32 isr; | 
|  | 143 | bool done = false; | 
|  | 144 |  | 
|  | 145 | dev_dbg(chan2dev(&td_chan->chan), "Checking irq: %d, td: %p\n", id, td); | 
|  | 146 |  | 
|  | 147 | isr = ioread32(td->membase + TIMBDMA_ISR) & (1 << id); | 
|  | 148 | if (isr) { | 
|  | 149 | iowrite32(isr, td->membase + TIMBDMA_ISR); | 
|  | 150 | done = true; | 
|  | 151 | } | 
|  | 152 |  | 
|  | 153 | return done; | 
|  | 154 | } | 
|  | 155 |  | 
|  | 156 | static void __td_unmap_desc(struct timb_dma_chan *td_chan, const u8 *dma_desc, | 
|  | 157 | bool single) | 
|  | 158 | { | 
|  | 159 | dma_addr_t addr; | 
|  | 160 | int len; | 
|  | 161 |  | 
|  | 162 | addr = (dma_desc[7] << 24) | (dma_desc[6] << 16) | (dma_desc[5] << 8) | | 
|  | 163 | dma_desc[4]; | 
|  | 164 |  | 
|  | 165 | len = (dma_desc[3] << 8) | dma_desc[2]; | 
|  | 166 |  | 
|  | 167 | if (single) | 
|  | 168 | dma_unmap_single(chan2dev(&td_chan->chan), addr, len, | 
|  | 169 | td_chan->direction); | 
|  | 170 | else | 
|  | 171 | dma_unmap_page(chan2dev(&td_chan->chan), addr, len, | 
|  | 172 | td_chan->direction); | 
|  | 173 | } | 
|  | 174 |  | 
|  | 175 | static void __td_unmap_descs(struct timb_dma_desc *td_desc, bool single) | 
|  | 176 | { | 
|  | 177 | struct timb_dma_chan *td_chan = container_of(td_desc->txd.chan, | 
|  | 178 | struct timb_dma_chan, chan); | 
|  | 179 | u8 *descs; | 
|  | 180 |  | 
|  | 181 | for (descs = td_desc->desc_list; ; descs += TIMB_DMA_DESC_SIZE) { | 
|  | 182 | __td_unmap_desc(td_chan, descs, single); | 
|  | 183 | if (descs[0] & 0x02) | 
|  | 184 | break; | 
|  | 185 | } | 
|  | 186 | } | 
|  | 187 |  | 
|  | 188 | static int td_fill_desc(struct timb_dma_chan *td_chan, u8 *dma_desc, | 
|  | 189 | struct scatterlist *sg, bool last) | 
|  | 190 | { | 
| Alexey Dobriyan | 4be929b | 2010-05-24 14:33:03 -0700 | [diff] [blame] | 191 | if (sg_dma_len(sg) > USHRT_MAX) { | 
| Richard Röjfors | de5d445 | 2010-03-25 19:44:21 +0100 | [diff] [blame] | 192 | dev_err(chan2dev(&td_chan->chan), "Too big sg element\n"); | 
|  | 193 | return -EINVAL; | 
|  | 194 | } | 
|  | 195 |  | 
|  | 196 | /* length must be word aligned */ | 
|  | 197 | if (sg_dma_len(sg) % sizeof(u32)) { | 
|  | 198 | dev_err(chan2dev(&td_chan->chan), "Incorrect length: %d\n", | 
|  | 199 | sg_dma_len(sg)); | 
|  | 200 | return -EINVAL; | 
|  | 201 | } | 
|  | 202 |  | 
| Dan Carpenter | efcc289 | 2010-05-25 11:55:06 +0200 | [diff] [blame] | 203 | dev_dbg(chan2dev(&td_chan->chan), "desc: %p, addr: 0x%llx\n", | 
|  | 204 | dma_desc, (unsigned long long)sg_dma_address(sg)); | 
| Richard Röjfors | de5d445 | 2010-03-25 19:44:21 +0100 | [diff] [blame] | 205 |  | 
|  | 206 | dma_desc[7] = (sg_dma_address(sg) >> 24) & 0xff; | 
|  | 207 | dma_desc[6] = (sg_dma_address(sg) >> 16) & 0xff; | 
|  | 208 | dma_desc[5] = (sg_dma_address(sg) >> 8) & 0xff; | 
|  | 209 | dma_desc[4] = (sg_dma_address(sg) >> 0) & 0xff; | 
|  | 210 |  | 
|  | 211 | dma_desc[3] = (sg_dma_len(sg) >> 8) & 0xff; | 
|  | 212 | dma_desc[2] = (sg_dma_len(sg) >> 0) & 0xff; | 
|  | 213 |  | 
|  | 214 | dma_desc[1] = 0x00; | 
|  | 215 | dma_desc[0] = 0x21 | (last ? 0x02 : 0); /* tran, valid */ | 
|  | 216 |  | 
|  | 217 | return 0; | 
|  | 218 | } | 
|  | 219 |  | 
|  | 220 | /* Must be called with the spinlock held */ | 
|  | 221 | static void __td_start_dma(struct timb_dma_chan *td_chan) | 
|  | 222 | { | 
|  | 223 | struct timb_dma_desc *td_desc; | 
|  | 224 |  | 
|  | 225 | if (td_chan->ongoing) { | 
|  | 226 | dev_err(chan2dev(&td_chan->chan), | 
|  | 227 | "Transfer already ongoing\n"); | 
|  | 228 | return; | 
|  | 229 | } | 
|  | 230 |  | 
|  | 231 | td_desc = list_entry(td_chan->active_list.next, struct timb_dma_desc, | 
|  | 232 | desc_node); | 
|  | 233 |  | 
|  | 234 | dev_dbg(chan2dev(&td_chan->chan), | 
|  | 235 | "td_chan: %p, chan: %d, membase: %p\n", | 
|  | 236 | td_chan, td_chan->chan.chan_id, td_chan->membase); | 
|  | 237 |  | 
|  | 238 | if (td_chan->direction == DMA_FROM_DEVICE) { | 
|  | 239 |  | 
|  | 240 | /* descriptor address */ | 
|  | 241 | iowrite32(0, td_chan->membase + TIMBDMA_OFFS_RX_DHAR); | 
|  | 242 | iowrite32(td_desc->txd.phys, td_chan->membase + | 
|  | 243 | TIMBDMA_OFFS_RX_DLAR); | 
|  | 244 | /* Bytes per line */ | 
|  | 245 | iowrite32(td_chan->bytes_per_line, td_chan->membase + | 
|  | 246 | TIMBDMA_OFFS_RX_BPRR); | 
|  | 247 | /* enable RX */ | 
|  | 248 | iowrite32(TIMBDMA_RX_EN, td_chan->membase + TIMBDMA_OFFS_RX_ER); | 
|  | 249 | } else { | 
|  | 250 | /* address high */ | 
|  | 251 | iowrite32(0, td_chan->membase + TIMBDMA_OFFS_TX_DHAR); | 
|  | 252 | iowrite32(td_desc->txd.phys, td_chan->membase + | 
|  | 253 | TIMBDMA_OFFS_TX_DLAR); | 
|  | 254 | } | 
|  | 255 |  | 
|  | 256 | td_chan->ongoing = true; | 
|  | 257 |  | 
|  | 258 | if (td_desc->interrupt) | 
|  | 259 | __td_enable_chan_irq(td_chan); | 
|  | 260 | } | 
|  | 261 |  | 
|  | 262 | static void __td_finish(struct timb_dma_chan *td_chan) | 
|  | 263 | { | 
|  | 264 | dma_async_tx_callback		callback; | 
|  | 265 | void				*param; | 
|  | 266 | struct dma_async_tx_descriptor	*txd; | 
|  | 267 | struct timb_dma_desc		*td_desc; | 
|  | 268 |  | 
|  | 269 | /* can happen if the descriptor is canceled */ | 
|  | 270 | if (list_empty(&td_chan->active_list)) | 
|  | 271 | return; | 
|  | 272 |  | 
|  | 273 | td_desc = list_entry(td_chan->active_list.next, struct timb_dma_desc, | 
|  | 274 | desc_node); | 
|  | 275 | txd = &td_desc->txd; | 
|  | 276 |  | 
|  | 277 | dev_dbg(chan2dev(&td_chan->chan), "descriptor %u complete\n", | 
|  | 278 | txd->cookie); | 
|  | 279 |  | 
|  | 280 | /* make sure to stop the transfer */ | 
|  | 281 | if (td_chan->direction == DMA_FROM_DEVICE) | 
|  | 282 | iowrite32(0, td_chan->membase + TIMBDMA_OFFS_RX_ER); | 
|  | 283 | /* Currently no support for stopping DMA transfers | 
|  | 284 | else | 
|  | 285 | iowrite32(0, td_chan->membase + TIMBDMA_OFFS_TX_DLAR); | 
|  | 286 | */ | 
|  | 287 | td_chan->last_completed_cookie = txd->cookie; | 
|  | 288 | td_chan->ongoing = false; | 
|  | 289 |  | 
|  | 290 | callback = txd->callback; | 
|  | 291 | param = txd->callback_param; | 
|  | 292 |  | 
|  | 293 | list_move(&td_desc->desc_node, &td_chan->free_list); | 
|  | 294 |  | 
|  | 295 | if (!(txd->flags & DMA_COMPL_SKIP_SRC_UNMAP)) | 
|  | 296 | __td_unmap_descs(td_desc, | 
|  | 297 | txd->flags & DMA_COMPL_SRC_UNMAP_SINGLE); | 
|  | 298 |  | 
|  | 299 | /* | 
|  | 300 | * The API requires that no submissions are done from a | 
|  | 301 | * callback, so we don't need to drop the lock here | 
|  | 302 | */ | 
|  | 303 | if (callback) | 
|  | 304 | callback(param); | 
|  | 305 | } | 
|  | 306 |  | 
|  | 307 | static u32 __td_ier_mask(struct timb_dma *td) | 
|  | 308 | { | 
|  | 309 | int i; | 
|  | 310 | u32 ret = 0; | 
|  | 311 |  | 
|  | 312 | for (i = 0; i < td->dma.chancnt; i++) { | 
|  | 313 | struct timb_dma_chan *td_chan = td->channels + i; | 
|  | 314 | if (td_chan->ongoing) { | 
|  | 315 | struct timb_dma_desc *td_desc = | 
|  | 316 | list_entry(td_chan->active_list.next, | 
|  | 317 | struct timb_dma_desc, desc_node); | 
|  | 318 | if (td_desc->interrupt) | 
|  | 319 | ret |= 1 << i; | 
|  | 320 | } | 
|  | 321 | } | 
|  | 322 |  | 
|  | 323 | return ret; | 
|  | 324 | } | 
|  | 325 |  | 
|  | 326 | static void __td_start_next(struct timb_dma_chan *td_chan) | 
|  | 327 | { | 
|  | 328 | struct timb_dma_desc *td_desc; | 
|  | 329 |  | 
|  | 330 | BUG_ON(list_empty(&td_chan->queue)); | 
|  | 331 | BUG_ON(td_chan->ongoing); | 
|  | 332 |  | 
|  | 333 | td_desc = list_entry(td_chan->queue.next, struct timb_dma_desc, | 
|  | 334 | desc_node); | 
|  | 335 |  | 
|  | 336 | dev_dbg(chan2dev(&td_chan->chan), "%s: started %u\n", | 
|  | 337 | __func__, td_desc->txd.cookie); | 
|  | 338 |  | 
|  | 339 | list_move(&td_desc->desc_node, &td_chan->active_list); | 
|  | 340 | __td_start_dma(td_chan); | 
|  | 341 | } | 
|  | 342 |  | 
|  | 343 | static dma_cookie_t td_tx_submit(struct dma_async_tx_descriptor *txd) | 
|  | 344 | { | 
|  | 345 | struct timb_dma_desc *td_desc = container_of(txd, struct timb_dma_desc, | 
|  | 346 | txd); | 
|  | 347 | struct timb_dma_chan *td_chan = container_of(txd->chan, | 
|  | 348 | struct timb_dma_chan, chan); | 
|  | 349 | dma_cookie_t cookie; | 
|  | 350 |  | 
|  | 351 | spin_lock_bh(&td_chan->lock); | 
|  | 352 |  | 
|  | 353 | cookie = txd->chan->cookie; | 
|  | 354 | if (++cookie < 0) | 
|  | 355 | cookie = 1; | 
|  | 356 | txd->chan->cookie = cookie; | 
|  | 357 | txd->cookie = cookie; | 
|  | 358 |  | 
|  | 359 | if (list_empty(&td_chan->active_list)) { | 
|  | 360 | dev_dbg(chan2dev(txd->chan), "%s: started %u\n", __func__, | 
|  | 361 | txd->cookie); | 
|  | 362 | list_add_tail(&td_desc->desc_node, &td_chan->active_list); | 
|  | 363 | __td_start_dma(td_chan); | 
|  | 364 | } else { | 
|  | 365 | dev_dbg(chan2dev(txd->chan), "tx_submit: queued %u\n", | 
|  | 366 | txd->cookie); | 
|  | 367 |  | 
|  | 368 | list_add_tail(&td_desc->desc_node, &td_chan->queue); | 
|  | 369 | } | 
|  | 370 |  | 
|  | 371 | spin_unlock_bh(&td_chan->lock); | 
|  | 372 |  | 
|  | 373 | return cookie; | 
|  | 374 | } | 
|  | 375 |  | 
|  | 376 | static struct timb_dma_desc *td_alloc_init_desc(struct timb_dma_chan *td_chan) | 
|  | 377 | { | 
|  | 378 | struct dma_chan *chan = &td_chan->chan; | 
|  | 379 | struct timb_dma_desc *td_desc; | 
|  | 380 | int err; | 
|  | 381 |  | 
|  | 382 | td_desc = kzalloc(sizeof(struct timb_dma_desc), GFP_KERNEL); | 
|  | 383 | if (!td_desc) { | 
|  | 384 | dev_err(chan2dev(chan), "Failed to alloc descriptor\n"); | 
| Julia Lawall | 4856800 | 2010-05-27 14:33:17 +0200 | [diff] [blame] | 385 | goto out; | 
| Richard Röjfors | de5d445 | 2010-03-25 19:44:21 +0100 | [diff] [blame] | 386 | } | 
|  | 387 |  | 
|  | 388 | td_desc->desc_list_len = td_chan->desc_elems * TIMB_DMA_DESC_SIZE; | 
|  | 389 |  | 
|  | 390 | td_desc->desc_list = kzalloc(td_desc->desc_list_len, GFP_KERNEL); | 
|  | 391 | if (!td_desc->desc_list) { | 
|  | 392 | dev_err(chan2dev(chan), "Failed to alloc descriptor\n"); | 
|  | 393 | goto err; | 
|  | 394 | } | 
|  | 395 |  | 
|  | 396 | dma_async_tx_descriptor_init(&td_desc->txd, chan); | 
|  | 397 | td_desc->txd.tx_submit = td_tx_submit; | 
|  | 398 | td_desc->txd.flags = DMA_CTRL_ACK; | 
|  | 399 |  | 
|  | 400 | td_desc->txd.phys = dma_map_single(chan2dmadev(chan), | 
|  | 401 | td_desc->desc_list, td_desc->desc_list_len, DMA_TO_DEVICE); | 
|  | 402 |  | 
|  | 403 | err = dma_mapping_error(chan2dmadev(chan), td_desc->txd.phys); | 
|  | 404 | if (err) { | 
|  | 405 | dev_err(chan2dev(chan), "DMA mapping error: %d\n", err); | 
|  | 406 | goto err; | 
|  | 407 | } | 
|  | 408 |  | 
|  | 409 | return td_desc; | 
|  | 410 | err: | 
|  | 411 | kfree(td_desc->desc_list); | 
|  | 412 | kfree(td_desc); | 
| Julia Lawall | 4856800 | 2010-05-27 14:33:17 +0200 | [diff] [blame] | 413 | out: | 
| Richard Röjfors | de5d445 | 2010-03-25 19:44:21 +0100 | [diff] [blame] | 414 | return NULL; | 
|  | 415 |  | 
|  | 416 | } | 
|  | 417 |  | 
|  | 418 | static void td_free_desc(struct timb_dma_desc *td_desc) | 
|  | 419 | { | 
|  | 420 | dev_dbg(chan2dev(td_desc->txd.chan), "Freeing desc: %p\n", td_desc); | 
|  | 421 | dma_unmap_single(chan2dmadev(td_desc->txd.chan), td_desc->txd.phys, | 
|  | 422 | td_desc->desc_list_len, DMA_TO_DEVICE); | 
|  | 423 |  | 
|  | 424 | kfree(td_desc->desc_list); | 
|  | 425 | kfree(td_desc); | 
|  | 426 | } | 
|  | 427 |  | 
|  | 428 | static void td_desc_put(struct timb_dma_chan *td_chan, | 
|  | 429 | struct timb_dma_desc *td_desc) | 
|  | 430 | { | 
|  | 431 | dev_dbg(chan2dev(&td_chan->chan), "Putting desc: %p\n", td_desc); | 
|  | 432 |  | 
|  | 433 | spin_lock_bh(&td_chan->lock); | 
|  | 434 | list_add(&td_desc->desc_node, &td_chan->free_list); | 
|  | 435 | spin_unlock_bh(&td_chan->lock); | 
|  | 436 | } | 
|  | 437 |  | 
|  | 438 | static struct timb_dma_desc *td_desc_get(struct timb_dma_chan *td_chan) | 
|  | 439 | { | 
|  | 440 | struct timb_dma_desc *td_desc, *_td_desc; | 
|  | 441 | struct timb_dma_desc *ret = NULL; | 
|  | 442 |  | 
|  | 443 | spin_lock_bh(&td_chan->lock); | 
|  | 444 | list_for_each_entry_safe(td_desc, _td_desc, &td_chan->free_list, | 
|  | 445 | desc_node) { | 
|  | 446 | if (async_tx_test_ack(&td_desc->txd)) { | 
|  | 447 | list_del(&td_desc->desc_node); | 
|  | 448 | ret = td_desc; | 
|  | 449 | break; | 
|  | 450 | } | 
|  | 451 | dev_dbg(chan2dev(&td_chan->chan), "desc %p not ACKed\n", | 
|  | 452 | td_desc); | 
|  | 453 | } | 
|  | 454 | spin_unlock_bh(&td_chan->lock); | 
|  | 455 |  | 
|  | 456 | return ret; | 
|  | 457 | } | 
|  | 458 |  | 
|  | 459 | static int td_alloc_chan_resources(struct dma_chan *chan) | 
|  | 460 | { | 
|  | 461 | struct timb_dma_chan *td_chan = | 
|  | 462 | container_of(chan, struct timb_dma_chan, chan); | 
|  | 463 | int i; | 
|  | 464 |  | 
|  | 465 | dev_dbg(chan2dev(chan), "%s: entry\n", __func__); | 
|  | 466 |  | 
|  | 467 | BUG_ON(!list_empty(&td_chan->free_list)); | 
|  | 468 | for (i = 0; i < td_chan->descs; i++) { | 
|  | 469 | struct timb_dma_desc *td_desc = td_alloc_init_desc(td_chan); | 
|  | 470 | if (!td_desc) { | 
|  | 471 | if (i) | 
|  | 472 | break; | 
|  | 473 | else { | 
|  | 474 | dev_err(chan2dev(chan), | 
|  | 475 | "Couldnt allocate any descriptors\n"); | 
|  | 476 | return -ENOMEM; | 
|  | 477 | } | 
|  | 478 | } | 
|  | 479 |  | 
|  | 480 | td_desc_put(td_chan, td_desc); | 
|  | 481 | } | 
|  | 482 |  | 
|  | 483 | spin_lock_bh(&td_chan->lock); | 
|  | 484 | td_chan->last_completed_cookie = 1; | 
|  | 485 | chan->cookie = 1; | 
|  | 486 | spin_unlock_bh(&td_chan->lock); | 
|  | 487 |  | 
|  | 488 | return 0; | 
|  | 489 | } | 
|  | 490 |  | 
|  | 491 | static void td_free_chan_resources(struct dma_chan *chan) | 
|  | 492 | { | 
|  | 493 | struct timb_dma_chan *td_chan = | 
|  | 494 | container_of(chan, struct timb_dma_chan, chan); | 
|  | 495 | struct timb_dma_desc *td_desc, *_td_desc; | 
|  | 496 | LIST_HEAD(list); | 
|  | 497 |  | 
|  | 498 | dev_dbg(chan2dev(chan), "%s: Entry\n", __func__); | 
|  | 499 |  | 
|  | 500 | /* check that all descriptors are free */ | 
|  | 501 | BUG_ON(!list_empty(&td_chan->active_list)); | 
|  | 502 | BUG_ON(!list_empty(&td_chan->queue)); | 
|  | 503 |  | 
|  | 504 | spin_lock_bh(&td_chan->lock); | 
|  | 505 | list_splice_init(&td_chan->free_list, &list); | 
|  | 506 | spin_unlock_bh(&td_chan->lock); | 
|  | 507 |  | 
|  | 508 | list_for_each_entry_safe(td_desc, _td_desc, &list, desc_node) { | 
|  | 509 | dev_dbg(chan2dev(chan), "%s: Freeing desc: %p\n", __func__, | 
|  | 510 | td_desc); | 
|  | 511 | td_free_desc(td_desc); | 
|  | 512 | } | 
|  | 513 | } | 
|  | 514 |  | 
| Linus Walleij | 0793448 | 2010-03-26 16:50:49 -0700 | [diff] [blame] | 515 | static enum dma_status td_tx_status(struct dma_chan *chan, dma_cookie_t cookie, | 
|  | 516 | struct dma_tx_state *txstate) | 
| Richard Röjfors | de5d445 | 2010-03-25 19:44:21 +0100 | [diff] [blame] | 517 | { | 
|  | 518 | struct timb_dma_chan *td_chan = | 
|  | 519 | container_of(chan, struct timb_dma_chan, chan); | 
|  | 520 | dma_cookie_t		last_used; | 
|  | 521 | dma_cookie_t		last_complete; | 
|  | 522 | int			ret; | 
|  | 523 |  | 
|  | 524 | dev_dbg(chan2dev(chan), "%s: Entry\n", __func__); | 
|  | 525 |  | 
|  | 526 | last_complete = td_chan->last_completed_cookie; | 
|  | 527 | last_used = chan->cookie; | 
|  | 528 |  | 
|  | 529 | ret = dma_async_is_complete(cookie, last_complete, last_used); | 
|  | 530 |  | 
| Dan Williams | bca3469 | 2010-03-26 16:52:10 -0700 | [diff] [blame] | 531 | dma_set_tx_state(txstate, last_complete, last_used, 0); | 
| Richard Röjfors | de5d445 | 2010-03-25 19:44:21 +0100 | [diff] [blame] | 532 |  | 
|  | 533 | dev_dbg(chan2dev(chan), | 
|  | 534 | "%s: exit, ret: %d, last_complete: %d, last_used: %d\n", | 
|  | 535 | __func__, ret, last_complete, last_used); | 
|  | 536 |  | 
|  | 537 | return ret; | 
|  | 538 | } | 
|  | 539 |  | 
|  | 540 | static void td_issue_pending(struct dma_chan *chan) | 
|  | 541 | { | 
|  | 542 | struct timb_dma_chan *td_chan = | 
|  | 543 | container_of(chan, struct timb_dma_chan, chan); | 
|  | 544 |  | 
|  | 545 | dev_dbg(chan2dev(chan), "%s: Entry\n", __func__); | 
|  | 546 | spin_lock_bh(&td_chan->lock); | 
|  | 547 |  | 
|  | 548 | if (!list_empty(&td_chan->active_list)) | 
|  | 549 | /* transfer ongoing */ | 
|  | 550 | if (__td_dma_done_ack(td_chan)) | 
|  | 551 | __td_finish(td_chan); | 
|  | 552 |  | 
|  | 553 | if (list_empty(&td_chan->active_list) && !list_empty(&td_chan->queue)) | 
|  | 554 | __td_start_next(td_chan); | 
|  | 555 |  | 
|  | 556 | spin_unlock_bh(&td_chan->lock); | 
|  | 557 | } | 
|  | 558 |  | 
|  | 559 | static struct dma_async_tx_descriptor *td_prep_slave_sg(struct dma_chan *chan, | 
|  | 560 | struct scatterlist *sgl, unsigned int sg_len, | 
|  | 561 | enum dma_data_direction direction, unsigned long flags) | 
|  | 562 | { | 
|  | 563 | struct timb_dma_chan *td_chan = | 
|  | 564 | container_of(chan, struct timb_dma_chan, chan); | 
|  | 565 | struct timb_dma_desc *td_desc; | 
|  | 566 | struct scatterlist *sg; | 
|  | 567 | unsigned int i; | 
|  | 568 | unsigned int desc_usage = 0; | 
|  | 569 |  | 
|  | 570 | if (!sgl || !sg_len) { | 
|  | 571 | dev_err(chan2dev(chan), "%s: No SG list\n", __func__); | 
|  | 572 | return NULL; | 
|  | 573 | } | 
|  | 574 |  | 
|  | 575 | /* even channels are for RX, odd for TX */ | 
|  | 576 | if (td_chan->direction != direction) { | 
|  | 577 | dev_err(chan2dev(chan), | 
|  | 578 | "Requesting channel in wrong direction\n"); | 
|  | 579 | return NULL; | 
|  | 580 | } | 
|  | 581 |  | 
|  | 582 | td_desc = td_desc_get(td_chan); | 
|  | 583 | if (!td_desc) { | 
|  | 584 | dev_err(chan2dev(chan), "Not enough descriptors available\n"); | 
|  | 585 | return NULL; | 
|  | 586 | } | 
|  | 587 |  | 
|  | 588 | td_desc->interrupt = (flags & DMA_PREP_INTERRUPT) != 0; | 
|  | 589 |  | 
|  | 590 | for_each_sg(sgl, sg, sg_len, i) { | 
|  | 591 | int err; | 
|  | 592 | if (desc_usage > td_desc->desc_list_len) { | 
|  | 593 | dev_err(chan2dev(chan), "No descriptor space\n"); | 
|  | 594 | return NULL; | 
|  | 595 | } | 
|  | 596 |  | 
|  | 597 | err = td_fill_desc(td_chan, td_desc->desc_list + desc_usage, sg, | 
|  | 598 | i == (sg_len - 1)); | 
|  | 599 | if (err) { | 
|  | 600 | dev_err(chan2dev(chan), "Failed to update desc: %d\n", | 
|  | 601 | err); | 
|  | 602 | td_desc_put(td_chan, td_desc); | 
|  | 603 | return NULL; | 
|  | 604 | } | 
|  | 605 | desc_usage += TIMB_DMA_DESC_SIZE; | 
|  | 606 | } | 
|  | 607 |  | 
|  | 608 | dma_sync_single_for_device(chan2dmadev(chan), td_desc->txd.phys, | 
|  | 609 | td_desc->desc_list_len, DMA_TO_DEVICE); | 
|  | 610 |  | 
|  | 611 | return &td_desc->txd; | 
|  | 612 | } | 
|  | 613 |  | 
| Linus Walleij | 0582763 | 2010-05-17 16:30:42 -0700 | [diff] [blame] | 614 | static int td_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, | 
|  | 615 | unsigned long arg) | 
| Richard Röjfors | de5d445 | 2010-03-25 19:44:21 +0100 | [diff] [blame] | 616 | { | 
|  | 617 | struct timb_dma_chan *td_chan = | 
|  | 618 | container_of(chan, struct timb_dma_chan, chan); | 
|  | 619 | struct timb_dma_desc *td_desc, *_td_desc; | 
|  | 620 |  | 
|  | 621 | dev_dbg(chan2dev(chan), "%s: Entry\n", __func__); | 
|  | 622 |  | 
| Linus Walleij | c3635c7 | 2010-03-26 16:44:01 -0700 | [diff] [blame] | 623 | if (cmd != DMA_TERMINATE_ALL) | 
|  | 624 | return -ENXIO; | 
|  | 625 |  | 
| Richard Röjfors | de5d445 | 2010-03-25 19:44:21 +0100 | [diff] [blame] | 626 | /* first the easy part, put the queue into the free list */ | 
|  | 627 | spin_lock_bh(&td_chan->lock); | 
|  | 628 | list_for_each_entry_safe(td_desc, _td_desc, &td_chan->queue, | 
|  | 629 | desc_node) | 
|  | 630 | list_move(&td_desc->desc_node, &td_chan->free_list); | 
|  | 631 |  | 
|  | 632 | /* now tear down the runnning */ | 
|  | 633 | __td_finish(td_chan); | 
|  | 634 | spin_unlock_bh(&td_chan->lock); | 
| Linus Walleij | c3635c7 | 2010-03-26 16:44:01 -0700 | [diff] [blame] | 635 |  | 
|  | 636 | return 0; | 
| Richard Röjfors | de5d445 | 2010-03-25 19:44:21 +0100 | [diff] [blame] | 637 | } | 
|  | 638 |  | 
|  | 639 | static void td_tasklet(unsigned long data) | 
|  | 640 | { | 
|  | 641 | struct timb_dma *td = (struct timb_dma *)data; | 
|  | 642 | u32 isr; | 
|  | 643 | u32 ipr; | 
|  | 644 | u32 ier; | 
|  | 645 | int i; | 
|  | 646 |  | 
|  | 647 | isr = ioread32(td->membase + TIMBDMA_ISR); | 
|  | 648 | ipr = isr & __td_ier_mask(td); | 
|  | 649 |  | 
|  | 650 | /* ack the interrupts */ | 
|  | 651 | iowrite32(ipr, td->membase + TIMBDMA_ISR); | 
|  | 652 |  | 
|  | 653 | for (i = 0; i < td->dma.chancnt; i++) | 
|  | 654 | if (ipr & (1 << i)) { | 
|  | 655 | struct timb_dma_chan *td_chan = td->channels + i; | 
|  | 656 | spin_lock(&td_chan->lock); | 
|  | 657 | __td_finish(td_chan); | 
|  | 658 | if (!list_empty(&td_chan->queue)) | 
|  | 659 | __td_start_next(td_chan); | 
|  | 660 | spin_unlock(&td_chan->lock); | 
|  | 661 | } | 
|  | 662 |  | 
|  | 663 | ier = __td_ier_mask(td); | 
|  | 664 | iowrite32(ier, td->membase + TIMBDMA_IER); | 
|  | 665 | } | 
|  | 666 |  | 
|  | 667 |  | 
|  | 668 | static irqreturn_t td_irq(int irq, void *devid) | 
|  | 669 | { | 
|  | 670 | struct timb_dma *td = devid; | 
|  | 671 | u32 ipr = ioread32(td->membase + TIMBDMA_IPR); | 
|  | 672 |  | 
|  | 673 | if (ipr) { | 
|  | 674 | /* disable interrupts, will be re-enabled in tasklet */ | 
|  | 675 | iowrite32(0, td->membase + TIMBDMA_IER); | 
|  | 676 |  | 
|  | 677 | tasklet_schedule(&td->tasklet); | 
|  | 678 |  | 
|  | 679 | return IRQ_HANDLED; | 
|  | 680 | } else | 
|  | 681 | return IRQ_NONE; | 
|  | 682 | } | 
|  | 683 |  | 
|  | 684 |  | 
|  | 685 | static int __devinit td_probe(struct platform_device *pdev) | 
|  | 686 | { | 
|  | 687 | struct timb_dma_platform_data *pdata = pdev->dev.platform_data; | 
|  | 688 | struct timb_dma *td; | 
|  | 689 | struct resource *iomem; | 
|  | 690 | int irq; | 
|  | 691 | int err; | 
|  | 692 | int i; | 
|  | 693 |  | 
|  | 694 | if (!pdata) { | 
|  | 695 | dev_err(&pdev->dev, "No platform data\n"); | 
|  | 696 | return -EINVAL; | 
|  | 697 | } | 
|  | 698 |  | 
|  | 699 | iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
|  | 700 | if (!iomem) | 
|  | 701 | return -EINVAL; | 
|  | 702 |  | 
|  | 703 | irq = platform_get_irq(pdev, 0); | 
|  | 704 | if (irq < 0) | 
|  | 705 | return irq; | 
|  | 706 |  | 
|  | 707 | if (!request_mem_region(iomem->start, resource_size(iomem), | 
|  | 708 | DRIVER_NAME)) | 
|  | 709 | return -EBUSY; | 
|  | 710 |  | 
|  | 711 | td  = kzalloc(sizeof(struct timb_dma) + | 
|  | 712 | sizeof(struct timb_dma_chan) * pdata->nr_channels, GFP_KERNEL); | 
|  | 713 | if (!td) { | 
|  | 714 | err = -ENOMEM; | 
|  | 715 | goto err_release_region; | 
|  | 716 | } | 
|  | 717 |  | 
|  | 718 | dev_dbg(&pdev->dev, "Allocated TD: %p\n", td); | 
|  | 719 |  | 
|  | 720 | td->membase = ioremap(iomem->start, resource_size(iomem)); | 
|  | 721 | if (!td->membase) { | 
|  | 722 | dev_err(&pdev->dev, "Failed to remap I/O memory\n"); | 
|  | 723 | err = -ENOMEM; | 
|  | 724 | goto err_free_mem; | 
|  | 725 | } | 
|  | 726 |  | 
|  | 727 | /* 32bit addressing */ | 
|  | 728 | iowrite32(TIMBDMA_32BIT_ADDR, td->membase + TIMBDMA_ACR); | 
|  | 729 |  | 
|  | 730 | /* disable and clear any interrupts */ | 
|  | 731 | iowrite32(0x0, td->membase + TIMBDMA_IER); | 
|  | 732 | iowrite32(0xFFFFFFFF, td->membase + TIMBDMA_ISR); | 
|  | 733 |  | 
|  | 734 | tasklet_init(&td->tasklet, td_tasklet, (unsigned long)td); | 
|  | 735 |  | 
|  | 736 | err = request_irq(irq, td_irq, IRQF_SHARED, DRIVER_NAME, td); | 
|  | 737 | if (err) { | 
|  | 738 | dev_err(&pdev->dev, "Failed to request IRQ\n"); | 
|  | 739 | goto err_tasklet_kill; | 
|  | 740 | } | 
|  | 741 |  | 
|  | 742 | td->dma.device_alloc_chan_resources	= td_alloc_chan_resources; | 
|  | 743 | td->dma.device_free_chan_resources	= td_free_chan_resources; | 
| Linus Walleij | 0793448 | 2010-03-26 16:50:49 -0700 | [diff] [blame] | 744 | td->dma.device_tx_status		= td_tx_status; | 
| Richard Röjfors | de5d445 | 2010-03-25 19:44:21 +0100 | [diff] [blame] | 745 | td->dma.device_issue_pending		= td_issue_pending; | 
|  | 746 |  | 
|  | 747 | dma_cap_set(DMA_SLAVE, td->dma.cap_mask); | 
|  | 748 | dma_cap_set(DMA_PRIVATE, td->dma.cap_mask); | 
|  | 749 | td->dma.device_prep_slave_sg = td_prep_slave_sg; | 
| Linus Walleij | c3635c7 | 2010-03-26 16:44:01 -0700 | [diff] [blame] | 750 | td->dma.device_control = td_control; | 
| Richard Röjfors | de5d445 | 2010-03-25 19:44:21 +0100 | [diff] [blame] | 751 |  | 
|  | 752 | td->dma.dev = &pdev->dev; | 
|  | 753 |  | 
|  | 754 | INIT_LIST_HEAD(&td->dma.channels); | 
|  | 755 |  | 
|  | 756 | for (i = 0; i < pdata->nr_channels; i++, td->dma.chancnt++) { | 
|  | 757 | struct timb_dma_chan *td_chan = &td->channels[i]; | 
|  | 758 | struct timb_dma_platform_data_channel *pchan = | 
|  | 759 | pdata->channels + i; | 
|  | 760 |  | 
|  | 761 | /* even channels are RX, odd are TX */ | 
| Nicolas Kaiser | 9cb047d | 2010-10-08 00:48:01 +0200 | [diff] [blame] | 762 | if ((i % 2) == pchan->rx) { | 
| Richard Röjfors | de5d445 | 2010-03-25 19:44:21 +0100 | [diff] [blame] | 763 | dev_err(&pdev->dev, "Wrong channel configuration\n"); | 
|  | 764 | err = -EINVAL; | 
|  | 765 | goto err_tasklet_kill; | 
|  | 766 | } | 
|  | 767 |  | 
|  | 768 | td_chan->chan.device = &td->dma; | 
|  | 769 | td_chan->chan.cookie = 1; | 
|  | 770 | td_chan->chan.chan_id = i; | 
|  | 771 | spin_lock_init(&td_chan->lock); | 
|  | 772 | INIT_LIST_HEAD(&td_chan->active_list); | 
|  | 773 | INIT_LIST_HEAD(&td_chan->queue); | 
|  | 774 | INIT_LIST_HEAD(&td_chan->free_list); | 
|  | 775 |  | 
|  | 776 | td_chan->descs = pchan->descriptors; | 
|  | 777 | td_chan->desc_elems = pchan->descriptor_elements; | 
|  | 778 | td_chan->bytes_per_line = pchan->bytes_per_line; | 
|  | 779 | td_chan->direction = pchan->rx ? DMA_FROM_DEVICE : | 
|  | 780 | DMA_TO_DEVICE; | 
|  | 781 |  | 
|  | 782 | td_chan->membase = td->membase + | 
|  | 783 | (i / 2) * TIMBDMA_INSTANCE_OFFSET + | 
|  | 784 | (pchan->rx ? 0 : TIMBDMA_INSTANCE_TX_OFFSET); | 
|  | 785 |  | 
|  | 786 | dev_dbg(&pdev->dev, "Chan: %d, membase: %p\n", | 
|  | 787 | i, td_chan->membase); | 
|  | 788 |  | 
|  | 789 | list_add_tail(&td_chan->chan.device_node, &td->dma.channels); | 
|  | 790 | } | 
|  | 791 |  | 
|  | 792 | err = dma_async_device_register(&td->dma); | 
|  | 793 | if (err) { | 
|  | 794 | dev_err(&pdev->dev, "Failed to register async device\n"); | 
|  | 795 | goto err_free_irq; | 
|  | 796 | } | 
|  | 797 |  | 
|  | 798 | platform_set_drvdata(pdev, td); | 
|  | 799 |  | 
|  | 800 | dev_dbg(&pdev->dev, "Probe result: %d\n", err); | 
|  | 801 | return err; | 
|  | 802 |  | 
|  | 803 | err_free_irq: | 
|  | 804 | free_irq(irq, td); | 
|  | 805 | err_tasklet_kill: | 
|  | 806 | tasklet_kill(&td->tasklet); | 
|  | 807 | iounmap(td->membase); | 
|  | 808 | err_free_mem: | 
|  | 809 | kfree(td); | 
|  | 810 | err_release_region: | 
|  | 811 | release_mem_region(iomem->start, resource_size(iomem)); | 
|  | 812 |  | 
|  | 813 | return err; | 
|  | 814 |  | 
|  | 815 | } | 
|  | 816 |  | 
|  | 817 | static int __devexit td_remove(struct platform_device *pdev) | 
|  | 818 | { | 
|  | 819 | struct timb_dma *td = platform_get_drvdata(pdev); | 
|  | 820 | struct resource *iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
|  | 821 | int irq = platform_get_irq(pdev, 0); | 
|  | 822 |  | 
|  | 823 | dma_async_device_unregister(&td->dma); | 
|  | 824 | free_irq(irq, td); | 
|  | 825 | tasklet_kill(&td->tasklet); | 
|  | 826 | iounmap(td->membase); | 
|  | 827 | kfree(td); | 
|  | 828 | release_mem_region(iomem->start, resource_size(iomem)); | 
|  | 829 |  | 
|  | 830 | platform_set_drvdata(pdev, NULL); | 
|  | 831 |  | 
|  | 832 | dev_dbg(&pdev->dev, "Removed...\n"); | 
|  | 833 | return 0; | 
|  | 834 | } | 
|  | 835 |  | 
|  | 836 | static struct platform_driver td_driver = { | 
|  | 837 | .driver = { | 
|  | 838 | .name	= DRIVER_NAME, | 
|  | 839 | .owner  = THIS_MODULE, | 
|  | 840 | }, | 
|  | 841 | .probe	= td_probe, | 
|  | 842 | .remove	= __exit_p(td_remove), | 
|  | 843 | }; | 
|  | 844 |  | 
|  | 845 | static int __init td_init(void) | 
|  | 846 | { | 
|  | 847 | return platform_driver_register(&td_driver); | 
|  | 848 | } | 
|  | 849 | module_init(td_init); | 
|  | 850 |  | 
|  | 851 | static void __exit td_exit(void) | 
|  | 852 | { | 
|  | 853 | platform_driver_unregister(&td_driver); | 
|  | 854 | } | 
|  | 855 | module_exit(td_exit); | 
|  | 856 |  | 
|  | 857 | MODULE_LICENSE("GPL v2"); | 
|  | 858 | MODULE_DESCRIPTION("Timberdale DMA controller driver"); | 
|  | 859 | MODULE_AUTHOR("Pelagicore AB <info@pelagicore.com>"); | 
|  | 860 | MODULE_ALIAS("platform:"DRIVER_NAME); |