| /* linux/drivers/dma/pl330.c | |
| * | |
| * Copyright (C) 2010 Samsung Electronics Co. Ltd. | |
| * Jaswinder Singh <jassi.brar@samsung.com> | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation; either version 2 of the License, or | |
| * (at your option) any later version. | |
| */ | |
| #include <linux/io.h> | |
| #include <linux/init.h> | |
| #include <linux/slab.h> | |
| #include <linux/module.h> | |
| #include <linux/dmaengine.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/amba/bus.h> | |
| #include <linux/amba/pl330.h> | |
| #define NR_DEFAULT_DESC 16 | |
| enum desc_status { | |
| /* In the DMAC pool */ | |
| FREE, | |
| /* | |
| * Allocted to some channel during prep_xxx | |
| * Also may be sitting on the work_list. | |
| */ | |
| PREP, | |
| /* | |
| * Sitting on the work_list and already submitted | |
| * to the PL330 core. Not more than two descriptors | |
| * of a channel can be BUSY at any time. | |
| */ | |
| BUSY, | |
| /* | |
| * Sitting on the channel work_list but xfer done | |
| * by PL330 core | |
| */ | |
| DONE, | |
| }; | |
| struct dma_pl330_chan { | |
| /* Schedule desc completion */ | |
| struct tasklet_struct task; | |
| /* DMA-Engine Channel */ | |
| struct dma_chan chan; | |
| /* Last completed cookie */ | |
| dma_cookie_t completed; | |
| /* List of to be xfered descriptors */ | |
| struct list_head work_list; | |
| /* Pointer to the DMAC that manages this channel, | |
| * NULL if the channel is available to be acquired. | |
| * As the parent, this DMAC also provides descriptors | |
| * to the channel. | |
| */ | |
| struct dma_pl330_dmac *dmac; | |
| /* To protect channel manipulation */ | |
| spinlock_t lock; | |
| /* Token of a hardware channel thread of PL330 DMAC | |
| * NULL if the channel is available to be acquired. | |
| */ | |
| void *pl330_chid; | |
| }; | |
| struct dma_pl330_dmac { | |
| struct pl330_info pif; | |
| /* DMA-Engine Device */ | |
| struct dma_device ddma; | |
| /* Pool of descriptors available for the DMAC's channels */ | |
| struct list_head desc_pool; | |
| /* To protect desc_pool manipulation */ | |
| spinlock_t pool_lock; | |
| /* Peripheral channels connected to this DMAC */ | |
| struct dma_pl330_chan peripherals[0]; /* keep at end */ | |
| }; | |
| struct dma_pl330_desc { | |
| /* To attach to a queue as child */ | |
| struct list_head node; | |
| /* Descriptor for the DMA Engine API */ | |
| struct dma_async_tx_descriptor txd; | |
| /* Xfer for PL330 core */ | |
| struct pl330_xfer px; | |
| struct pl330_reqcfg rqcfg; | |
| struct pl330_req req; | |
| enum desc_status status; | |
| /* The channel which currently holds this desc */ | |
| struct dma_pl330_chan *pchan; | |
| }; | |
| static inline struct dma_pl330_chan * | |
| to_pchan(struct dma_chan *ch) | |
| { | |
| if (!ch) | |
| return NULL; | |
| return container_of(ch, struct dma_pl330_chan, chan); | |
| } | |
| static inline struct dma_pl330_desc * | |
| to_desc(struct dma_async_tx_descriptor *tx) | |
| { | |
| return container_of(tx, struct dma_pl330_desc, txd); | |
| } | |
| static inline void free_desc_list(struct list_head *list) | |
| { | |
| struct dma_pl330_dmac *pdmac; | |
| struct dma_pl330_desc *desc; | |
| struct dma_pl330_chan *pch; | |
| unsigned long flags; | |
| if (list_empty(list)) | |
| return; | |
| /* Finish off the work list */ | |
| list_for_each_entry(desc, list, node) { | |
| dma_async_tx_callback callback; | |
| void *param; | |
| /* All desc in a list belong to same channel */ | |
| pch = desc->pchan; | |
| callback = desc->txd.callback; | |
| param = desc->txd.callback_param; | |
| if (callback) | |
| callback(param); | |
| desc->pchan = NULL; | |
| } | |
| pdmac = pch->dmac; | |
| spin_lock_irqsave(&pdmac->pool_lock, flags); | |
| list_splice_tail_init(list, &pdmac->desc_pool); | |
| spin_unlock_irqrestore(&pdmac->pool_lock, flags); | |
| } | |
| static inline void fill_queue(struct dma_pl330_chan *pch) | |
| { | |
| struct dma_pl330_desc *desc; | |
| int ret; | |
| list_for_each_entry(desc, &pch->work_list, node) { | |
| /* If already submitted */ | |
| if (desc->status == BUSY) | |
| break; | |
| ret = pl330_submit_req(pch->pl330_chid, | |
| &desc->req); | |
| if (!ret) { | |
| desc->status = BUSY; | |
| break; | |
| } else if (ret == -EAGAIN) { | |
| /* QFull or DMAC Dying */ | |
| break; | |
| } else { | |
| /* Unacceptable request */ | |
| desc->status = DONE; | |
| dev_err(pch->dmac->pif.dev, "%s:%d Bad Desc(%d)\n", | |
| __func__, __LINE__, desc->txd.cookie); | |
| tasklet_schedule(&pch->task); | |
| } | |
| } | |
| } | |
| static void pl330_tasklet(unsigned long data) | |
| { | |
| struct dma_pl330_chan *pch = (struct dma_pl330_chan *)data; | |
| struct dma_pl330_desc *desc, *_dt; | |
| unsigned long flags; | |
| LIST_HEAD(list); | |
| spin_lock_irqsave(&pch->lock, flags); | |
| /* Pick up ripe tomatoes */ | |
| list_for_each_entry_safe(desc, _dt, &pch->work_list, node) | |
| if (desc->status == DONE) { | |
| pch->completed = desc->txd.cookie; | |
| list_move_tail(&desc->node, &list); | |
| } | |
| /* Try to submit a req imm. next to the last completed cookie */ | |
| fill_queue(pch); | |
| /* Make sure the PL330 Channel thread is active */ | |
| pl330_chan_ctrl(pch->pl330_chid, PL330_OP_START); | |
| spin_unlock_irqrestore(&pch->lock, flags); | |
| free_desc_list(&list); | |
| } | |
| static void dma_pl330_rqcb(void *token, enum pl330_op_err err) | |
| { | |
| struct dma_pl330_desc *desc = token; | |
| struct dma_pl330_chan *pch = desc->pchan; | |
| unsigned long flags; | |
| /* If desc aborted */ | |
| if (!pch) | |
| return; | |
| spin_lock_irqsave(&pch->lock, flags); | |
| desc->status = DONE; | |
| spin_unlock_irqrestore(&pch->lock, flags); | |
| tasklet_schedule(&pch->task); | |
| } | |
| static int pl330_alloc_chan_resources(struct dma_chan *chan) | |
| { | |
| struct dma_pl330_chan *pch = to_pchan(chan); | |
| struct dma_pl330_dmac *pdmac = pch->dmac; | |
| unsigned long flags; | |
| spin_lock_irqsave(&pch->lock, flags); | |
| pch->completed = chan->cookie = 1; | |
| pch->pl330_chid = pl330_request_channel(&pdmac->pif); | |
| if (!pch->pl330_chid) { | |
| spin_unlock_irqrestore(&pch->lock, flags); | |
| return 0; | |
| } | |
| tasklet_init(&pch->task, pl330_tasklet, (unsigned long) pch); | |
| spin_unlock_irqrestore(&pch->lock, flags); | |
| return 1; | |
| } | |
| static int pl330_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, unsigned long arg) | |
| { | |
| struct dma_pl330_chan *pch = to_pchan(chan); | |
| struct dma_pl330_desc *desc; | |
| unsigned long flags; | |
| /* Only supports DMA_TERMINATE_ALL */ | |
| if (cmd != DMA_TERMINATE_ALL) | |
| return -ENXIO; | |
| spin_lock_irqsave(&pch->lock, flags); | |
| /* FLUSH the PL330 Channel thread */ | |
| pl330_chan_ctrl(pch->pl330_chid, PL330_OP_FLUSH); | |
| /* Mark all desc done */ | |
| list_for_each_entry(desc, &pch->work_list, node) | |
| desc->status = DONE; | |
| spin_unlock_irqrestore(&pch->lock, flags); | |
| pl330_tasklet((unsigned long) pch); | |
| return 0; | |
| } | |
| static void pl330_free_chan_resources(struct dma_chan *chan) | |
| { | |
| struct dma_pl330_chan *pch = to_pchan(chan); | |
| unsigned long flags; | |
| tasklet_kill(&pch->task); | |
| spin_lock_irqsave(&pch->lock, flags); | |
| pl330_release_channel(pch->pl330_chid); | |
| pch->pl330_chid = NULL; | |
| spin_unlock_irqrestore(&pch->lock, flags); | |
| } | |
| static enum dma_status | |
| pl330_tx_status(struct dma_chan *chan, dma_cookie_t cookie, | |
| struct dma_tx_state *txstate) | |
| { | |
| struct dma_pl330_chan *pch = to_pchan(chan); | |
| dma_cookie_t last_done, last_used; | |
| int ret; | |
| last_done = pch->completed; | |
| last_used = chan->cookie; | |
| ret = dma_async_is_complete(cookie, last_done, last_used); | |
| dma_set_tx_state(txstate, last_done, last_used, 0); | |
| return ret; | |
| } | |
| static void pl330_issue_pending(struct dma_chan *chan) | |
| { | |
| pl330_tasklet((unsigned long) to_pchan(chan)); | |
| } | |
| /* | |
| * We returned the last one of the circular list of descriptor(s) | |
| * from prep_xxx, so the argument to submit corresponds to the last | |
| * descriptor of the list. | |
| */ | |
| static dma_cookie_t pl330_tx_submit(struct dma_async_tx_descriptor *tx) | |
| { | |
| struct dma_pl330_desc *desc, *last = to_desc(tx); | |
| struct dma_pl330_chan *pch = to_pchan(tx->chan); | |
| dma_cookie_t cookie; | |
| unsigned long flags; | |
| spin_lock_irqsave(&pch->lock, flags); | |
| /* Assign cookies to all nodes */ | |
| cookie = tx->chan->cookie; | |
| while (!list_empty(&last->node)) { | |
| desc = list_entry(last->node.next, struct dma_pl330_desc, node); | |
| if (++cookie < 0) | |
| cookie = 1; | |
| desc->txd.cookie = cookie; | |
| list_move_tail(&desc->node, &pch->work_list); | |
| } | |
| if (++cookie < 0) | |
| cookie = 1; | |
| last->txd.cookie = cookie; | |
| list_add_tail(&last->node, &pch->work_list); | |
| tx->chan->cookie = cookie; | |
| spin_unlock_irqrestore(&pch->lock, flags); | |
| return cookie; | |
| } | |
| static inline void _init_desc(struct dma_pl330_desc *desc) | |
| { | |
| desc->pchan = NULL; | |
| desc->req.x = &desc->px; | |
| desc->req.token = desc; | |
| desc->rqcfg.swap = SWAP_NO; | |
| desc->rqcfg.privileged = 0; | |
| desc->rqcfg.insnaccess = 0; | |
| desc->rqcfg.scctl = SCCTRL0; | |
| desc->rqcfg.dcctl = DCCTRL0; | |
| desc->req.cfg = &desc->rqcfg; | |
| desc->req.xfer_cb = dma_pl330_rqcb; | |
| desc->txd.tx_submit = pl330_tx_submit; | |
| INIT_LIST_HEAD(&desc->node); | |
| } | |
| /* Returns the number of descriptors added to the DMAC pool */ | |
| int add_desc(struct dma_pl330_dmac *pdmac, gfp_t flg, int count) | |
| { | |
| struct dma_pl330_desc *desc; | |
| unsigned long flags; | |
| int i; | |
| if (!pdmac) | |
| return 0; | |
| desc = kmalloc(count * sizeof(*desc), flg); | |
| if (!desc) | |
| return 0; | |
| spin_lock_irqsave(&pdmac->pool_lock, flags); | |
| for (i = 0; i < count; i++) { | |
| _init_desc(&desc[i]); | |
| list_add_tail(&desc[i].node, &pdmac->desc_pool); | |
| } | |
| spin_unlock_irqrestore(&pdmac->pool_lock, flags); | |
| return count; | |
| } | |
| static struct dma_pl330_desc * | |
| pluck_desc(struct dma_pl330_dmac *pdmac) | |
| { | |
| struct dma_pl330_desc *desc = NULL; | |
| unsigned long flags; | |
| if (!pdmac) | |
| return NULL; | |
| spin_lock_irqsave(&pdmac->pool_lock, flags); | |
| if (!list_empty(&pdmac->desc_pool)) { | |
| desc = list_entry(pdmac->desc_pool.next, | |
| struct dma_pl330_desc, node); | |
| list_del_init(&desc->node); | |
| desc->status = PREP; | |
| desc->txd.callback = NULL; | |
| } | |
| spin_unlock_irqrestore(&pdmac->pool_lock, flags); | |
| return desc; | |
| } | |
| static struct dma_pl330_desc *pl330_get_desc(struct dma_pl330_chan *pch) | |
| { | |
| struct dma_pl330_dmac *pdmac = pch->dmac; | |
| struct dma_pl330_peri *peri = pch->chan.private; | |
| struct dma_pl330_desc *desc; | |
| /* Pluck one desc from the pool of DMAC */ | |
| desc = pluck_desc(pdmac); | |
| /* If the DMAC pool is empty, alloc new */ | |
| if (!desc) { | |
| if (!add_desc(pdmac, GFP_ATOMIC, 1)) | |
| return NULL; | |
| /* Try again */ | |
| desc = pluck_desc(pdmac); | |
| if (!desc) { | |
| dev_err(pch->dmac->pif.dev, | |
| "%s:%d ALERT!\n", __func__, __LINE__); | |
| return NULL; | |
| } | |
| } | |
| /* Initialize the descriptor */ | |
| desc->pchan = pch; | |
| desc->txd.cookie = 0; | |
| async_tx_ack(&desc->txd); | |
| desc->req.rqtype = peri->rqtype; | |
| desc->req.peri = peri->peri_id; | |
| dma_async_tx_descriptor_init(&desc->txd, &pch->chan); | |
| return desc; | |
| } | |
| static inline void fill_px(struct pl330_xfer *px, | |
| dma_addr_t dst, dma_addr_t src, size_t len) | |
| { | |
| px->next = NULL; | |
| px->bytes = len; | |
| px->dst_addr = dst; | |
| px->src_addr = src; | |
| } | |
| static struct dma_pl330_desc * | |
| __pl330_prep_dma_memcpy(struct dma_pl330_chan *pch, dma_addr_t dst, | |
| dma_addr_t src, size_t len) | |
| { | |
| struct dma_pl330_desc *desc = pl330_get_desc(pch); | |
| if (!desc) { | |
| dev_err(pch->dmac->pif.dev, "%s:%d Unable to fetch desc\n", | |
| __func__, __LINE__); | |
| return NULL; | |
| } | |
| /* | |
| * Ideally we should lookout for reqs bigger than | |
| * those that can be programmed with 256 bytes of | |
| * MC buffer, but considering a req size is seldom | |
| * going to be word-unaligned and more than 200MB, | |
| * we take it easy. | |
| * Also, should the limit is reached we'd rather | |
| * have the platform increase MC buffer size than | |
| * complicating this API driver. | |
| */ | |
| fill_px(&desc->px, dst, src, len); | |
| return desc; | |
| } | |
| /* Call after fixing burst size */ | |
| static inline int get_burst_len(struct dma_pl330_desc *desc, size_t len) | |
| { | |
| struct dma_pl330_chan *pch = desc->pchan; | |
| struct pl330_info *pi = &pch->dmac->pif; | |
| int burst_len; | |
| burst_len = pi->pcfg.data_bus_width / 8; | |
| burst_len *= pi->pcfg.data_buf_dep; | |
| burst_len >>= desc->rqcfg.brst_size; | |
| /* src/dst_burst_len can't be more than 16 */ | |
| if (burst_len > 16) | |
| burst_len = 16; | |
| while (burst_len > 1) { | |
| if (!(len % (burst_len << desc->rqcfg.brst_size))) | |
| break; | |
| burst_len--; | |
| } | |
| return burst_len; | |
| } | |
| static struct dma_async_tx_descriptor * | |
| pl330_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dst, | |
| dma_addr_t src, size_t len, unsigned long flags) | |
| { | |
| struct dma_pl330_desc *desc; | |
| struct dma_pl330_chan *pch = to_pchan(chan); | |
| struct dma_pl330_peri *peri = chan->private; | |
| struct pl330_info *pi; | |
| int burst; | |
| if (unlikely(!pch || !len || !peri)) | |
| return NULL; | |
| if (peri->rqtype != MEMTOMEM) | |
| return NULL; | |
| pi = &pch->dmac->pif; | |
| desc = __pl330_prep_dma_memcpy(pch, dst, src, len); | |
| if (!desc) | |
| return NULL; | |
| desc->rqcfg.src_inc = 1; | |
| desc->rqcfg.dst_inc = 1; | |
| /* Select max possible burst size */ | |
| burst = pi->pcfg.data_bus_width / 8; | |
| while (burst > 1) { | |
| if (!(len % burst)) | |
| break; | |
| burst /= 2; | |
| } | |
| desc->rqcfg.brst_size = 0; | |
| while (burst != (1 << desc->rqcfg.brst_size)) | |
| desc->rqcfg.brst_size++; | |
| desc->rqcfg.brst_len = get_burst_len(desc, len); | |
| desc->txd.flags = flags; | |
| return &desc->txd; | |
| } | |
| static struct dma_async_tx_descriptor * | |
| pl330_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, | |
| unsigned int sg_len, enum dma_data_direction direction, | |
| unsigned long flg) | |
| { | |
| struct dma_pl330_desc *first, *desc = NULL; | |
| struct dma_pl330_chan *pch = to_pchan(chan); | |
| struct dma_pl330_peri *peri = chan->private; | |
| struct scatterlist *sg; | |
| unsigned long flags; | |
| int i, burst_size; | |
| dma_addr_t addr; | |
| if (unlikely(!pch || !sgl || !sg_len)) | |
| return NULL; | |
| /* Make sure the direction is consistent */ | |
| if ((direction == DMA_TO_DEVICE && | |
| peri->rqtype != MEMTODEV) || | |
| (direction == DMA_FROM_DEVICE && | |
| peri->rqtype != DEVTOMEM)) { | |
| dev_err(pch->dmac->pif.dev, "%s:%d Invalid Direction\n", | |
| __func__, __LINE__); | |
| return NULL; | |
| } | |
| addr = peri->fifo_addr; | |
| burst_size = peri->burst_sz; | |
| first = NULL; | |
| for_each_sg(sgl, sg, sg_len, i) { | |
| desc = pl330_get_desc(pch); | |
| if (!desc) { | |
| struct dma_pl330_dmac *pdmac = pch->dmac; | |
| dev_err(pch->dmac->pif.dev, | |
| "%s:%d Unable to fetch desc\n", | |
| __func__, __LINE__); | |
| if (!first) | |
| return NULL; | |
| spin_lock_irqsave(&pdmac->pool_lock, flags); | |
| while (!list_empty(&first->node)) { | |
| desc = list_entry(first->node.next, | |
| struct dma_pl330_desc, node); | |
| list_move_tail(&desc->node, &pdmac->desc_pool); | |
| } | |
| list_move_tail(&first->node, &pdmac->desc_pool); | |
| spin_unlock_irqrestore(&pdmac->pool_lock, flags); | |
| return NULL; | |
| } | |
| if (!first) | |
| first = desc; | |
| else | |
| list_add_tail(&desc->node, &first->node); | |
| if (direction == DMA_TO_DEVICE) { | |
| desc->rqcfg.src_inc = 1; | |
| desc->rqcfg.dst_inc = 0; | |
| fill_px(&desc->px, | |
| addr, sg_dma_address(sg), sg_dma_len(sg)); | |
| } else { | |
| desc->rqcfg.src_inc = 0; | |
| desc->rqcfg.dst_inc = 1; | |
| fill_px(&desc->px, | |
| sg_dma_address(sg), addr, sg_dma_len(sg)); | |
| } | |
| desc->rqcfg.brst_size = burst_size; | |
| desc->rqcfg.brst_len = 1; | |
| } | |
| /* Return the last desc in the chain */ | |
| desc->txd.flags = flg; | |
| return &desc->txd; | |
| } | |
| static irqreturn_t pl330_irq_handler(int irq, void *data) | |
| { | |
| if (pl330_update(data)) | |
| return IRQ_HANDLED; | |
| else | |
| return IRQ_NONE; | |
| } | |
| static int __devinit | |
| pl330_probe(struct amba_device *adev, const struct amba_id *id) | |
| { | |
| struct dma_pl330_platdata *pdat; | |
| struct dma_pl330_dmac *pdmac; | |
| struct dma_pl330_chan *pch; | |
| struct pl330_info *pi; | |
| struct dma_device *pd; | |
| struct resource *res; | |
| int i, ret, irq; | |
| pdat = adev->dev.platform_data; | |
| if (!pdat || !pdat->nr_valid_peri) { | |
| dev_err(&adev->dev, "platform data missing\n"); | |
| return -ENODEV; | |
| } | |
| /* Allocate a new DMAC and its Channels */ | |
| pdmac = kzalloc(pdat->nr_valid_peri * sizeof(*pch) | |
| + sizeof(*pdmac), GFP_KERNEL); | |
| if (!pdmac) { | |
| dev_err(&adev->dev, "unable to allocate mem\n"); | |
| return -ENOMEM; | |
| } | |
| pi = &pdmac->pif; | |
| pi->dev = &adev->dev; | |
| pi->pl330_data = NULL; | |
| pi->mcbufsz = pdat->mcbuf_sz; | |
| res = &adev->res; | |
| request_mem_region(res->start, resource_size(res), "dma-pl330"); | |
| pi->base = ioremap(res->start, resource_size(res)); | |
| if (!pi->base) { | |
| ret = -ENXIO; | |
| goto probe_err1; | |
| } | |
| irq = adev->irq[0]; | |
| ret = request_irq(irq, pl330_irq_handler, 0, | |
| dev_name(&adev->dev), pi); | |
| if (ret) | |
| goto probe_err2; | |
| ret = pl330_add(pi); | |
| if (ret) | |
| goto probe_err3; | |
| INIT_LIST_HEAD(&pdmac->desc_pool); | |
| spin_lock_init(&pdmac->pool_lock); | |
| /* Create a descriptor pool of default size */ | |
| if (!add_desc(pdmac, GFP_KERNEL, NR_DEFAULT_DESC)) | |
| dev_warn(&adev->dev, "unable to allocate desc\n"); | |
| pd = &pdmac->ddma; | |
| INIT_LIST_HEAD(&pd->channels); | |
| /* Initialize channel parameters */ | |
| for (i = 0; i < pdat->nr_valid_peri; i++) { | |
| struct dma_pl330_peri *peri = &pdat->peri[i]; | |
| pch = &pdmac->peripherals[i]; | |
| switch (peri->rqtype) { | |
| case MEMTOMEM: | |
| dma_cap_set(DMA_MEMCPY, pd->cap_mask); | |
| break; | |
| case MEMTODEV: | |
| case DEVTOMEM: | |
| dma_cap_set(DMA_SLAVE, pd->cap_mask); | |
| break; | |
| default: | |
| dev_err(&adev->dev, "DEVTODEV Not Supported\n"); | |
| continue; | |
| } | |
| INIT_LIST_HEAD(&pch->work_list); | |
| spin_lock_init(&pch->lock); | |
| pch->pl330_chid = NULL; | |
| pch->chan.private = peri; | |
| pch->chan.device = pd; | |
| pch->chan.chan_id = i; | |
| pch->dmac = pdmac; | |
| /* Add the channel to the DMAC list */ | |
| pd->chancnt++; | |
| list_add_tail(&pch->chan.device_node, &pd->channels); | |
| } | |
| pd->dev = &adev->dev; | |
| pd->device_alloc_chan_resources = pl330_alloc_chan_resources; | |
| pd->device_free_chan_resources = pl330_free_chan_resources; | |
| pd->device_prep_dma_memcpy = pl330_prep_dma_memcpy; | |
| pd->device_tx_status = pl330_tx_status; | |
| pd->device_prep_slave_sg = pl330_prep_slave_sg; | |
| pd->device_control = pl330_control; | |
| pd->device_issue_pending = pl330_issue_pending; | |
| ret = dma_async_device_register(pd); | |
| if (ret) { | |
| dev_err(&adev->dev, "unable to register DMAC\n"); | |
| goto probe_err4; | |
| } | |
| amba_set_drvdata(adev, pdmac); | |
| dev_info(&adev->dev, | |
| "Loaded driver for PL330 DMAC-%d\n", adev->periphid); | |
| dev_info(&adev->dev, | |
| "\tDBUFF-%ux%ubytes Num_Chans-%u Num_Peri-%u Num_Events-%u\n", | |
| pi->pcfg.data_buf_dep, | |
| pi->pcfg.data_bus_width / 8, pi->pcfg.num_chan, | |
| pi->pcfg.num_peri, pi->pcfg.num_events); | |
| return 0; | |
| probe_err4: | |
| pl330_del(pi); | |
| probe_err3: | |
| free_irq(irq, pi); | |
| probe_err2: | |
| iounmap(pi->base); | |
| probe_err1: | |
| release_mem_region(res->start, resource_size(res)); | |
| kfree(pdmac); | |
| return ret; | |
| } | |
| static int __devexit pl330_remove(struct amba_device *adev) | |
| { | |
| struct dma_pl330_dmac *pdmac = amba_get_drvdata(adev); | |
| struct dma_pl330_chan *pch, *_p; | |
| struct pl330_info *pi; | |
| struct resource *res; | |
| int irq; | |
| if (!pdmac) | |
| return 0; | |
| amba_set_drvdata(adev, NULL); | |
| /* Idle the DMAC */ | |
| list_for_each_entry_safe(pch, _p, &pdmac->ddma.channels, | |
| chan.device_node) { | |
| /* Remove the channel */ | |
| list_del(&pch->chan.device_node); | |
| /* Flush the channel */ | |
| pl330_control(&pch->chan, DMA_TERMINATE_ALL, 0); | |
| pl330_free_chan_resources(&pch->chan); | |
| } | |
| pi = &pdmac->pif; | |
| pl330_del(pi); | |
| irq = adev->irq[0]; | |
| free_irq(irq, pi); | |
| iounmap(pi->base); | |
| res = &adev->res; | |
| release_mem_region(res->start, resource_size(res)); | |
| kfree(pdmac); | |
| return 0; | |
| } | |
| static struct amba_id pl330_ids[] = { | |
| { | |
| .id = 0x00041330, | |
| .mask = 0x000fffff, | |
| }, | |
| { 0, 0 }, | |
| }; | |
| static struct amba_driver pl330_driver = { | |
| .drv = { | |
| .owner = THIS_MODULE, | |
| .name = "dma-pl330", | |
| }, | |
| .id_table = pl330_ids, | |
| .probe = pl330_probe, | |
| .remove = pl330_remove, | |
| }; | |
| static int __init pl330_init(void) | |
| { | |
| return amba_driver_register(&pl330_driver); | |
| } | |
| module_init(pl330_init); | |
| static void __exit pl330_exit(void) | |
| { | |
| amba_driver_unregister(&pl330_driver); | |
| return; | |
| } | |
| module_exit(pl330_exit); | |
| MODULE_AUTHOR("Jaswinder Singh <jassi.brar@samsung.com>"); | |
| MODULE_DESCRIPTION("API Driver for PL330 DMAC"); | |
| MODULE_LICENSE("GPL"); |