On Fri, 2012-04-20 at 14:38 +0530, Laxman Dewangan wrote: > Adding dmaengine based NVIDIA's Tegra APB dma driver. > This driver support the slave mode of data transfer from > peripheral to memory and vice versa. > The driver supports for the cyclic and non-cyclic mode > of data transfer. > > Signed-off-by: Laxman Dewangan <ldewangan@xxxxxxxxxx> > --- > This is NVIDIA Tegra's APB dma controller driver based on dmaengine. > There is already old driver in mach-tegra/dma.c and we want to get rid > of this old style driver which exposes private apis. > Once this driver get through, there will be series of patches to move all > existing driver to use the dmaengine based driver and old mach-tegra/dma.c > will get deleted. This driver has following feature than old one: > - better queue managment. > - Cyclic transfer supports. > - Platform driver. > - Full support for device tree. > - Uses regmap mmio interface for debugfs/ context restore. > - Multiple bug fixes over old driver. [snip] > + * dma_transfer_mode: Different dma transfer mode. > + * DMA_MODE_ONCE: Dma transfer the configured buffer once and at the end of > + * transfer, dma stops automatically and generates interrupt > + * if enabled. SW need to reprogram dma for next transfer. > + * DMA_MODE_CYCLE: Dma keeps transferring the same buffer again and again > + * until dma stopped explicitly by SW or another buffer configured. > + * After transfer completes, dma again starts transfer from > + * beginning of buffer without sw intervention. If any new > + * address/size is configured during buffer transfer then > + * dma start transfer with new configuration otherwise it > + * will keep transferring with old configuration. It also > + * generates the interrupt after buffer transfer completes. why do you need to define this? use the cyclic api to convey this > + * DMA_MODE_CYCLE_HALF_NOTIFY: In this mode dma keeps transferring the buffer > + * into two folds. This is kind of ping-pong buffer where both > + * buffer size should be same. Dma completes the one buffer, > + * generates interrupt and keep transferring the next buffer > + * whose address start just next to first buffer. At the end of > + * second buffer transfer, dma again generates interrupt and > + * keep transferring of the data from starting of first buffer. > + * If sw wants to change the address/size of the buffer then > + * it needs to change only when dma transferring the second > + * half of buffer. In dma configuration, it only need to > + * configure starting of first buffer and size of first buffer. > + * Dma hw assumes that striating address of second buffer is just > + * next to end of first buffer and size is same as the first > + * buffer. isnt this a specifc example of cylci and frankly why should dmaengine care about this. This one of the configurations you are passing for a cyclic dma operation > + */ > +enum dma_transfer_mode { > + DMA_MODE_NONE, > + DMA_MODE_ONCE, > + DMA_MODE_CYCLE, > + DMA_MODE_CYCLE_HALF_NOTIFY, > +}; > + > +/* List of memory allocated for that channel */ > +struct tegra_dma_chan_mem_alloc { > + struct list_head node; > +}; this seems questionable too... > + > +/* Dma channel registers */ > +struct tegra_dma_channel_regs { > + unsigned long csr; > + unsigned long ahb_ptr; > + unsigned long apb_ptr; > + unsigned long ahb_seq; > + unsigned long apb_seq; > +}; > + > +/* > + * tegra_dma_sg_req: Dma request details to configure hardware. This > + * contains the details for one transfer to configure dma hw. > + * The client's request for data transfer can be broken into multiple > + * sub-transfer as per requestor details and hw support. typo ^^^^^^^^^ > + * This sub transfer get added in the list of transfer and point to Tegra > + * dma descriptor which manages the transfer details. > + */ > +struct tegra_dma_sg_req { > + struct tegra_dma_channel_regs ch_regs; > + int req_len; > + bool configured; > + bool last_sg; > + bool half_done; > + struct list_head node; > + struct tegra_dma_desc *dma_desc; > +}; > + > +/* > + * tegra_dma_desc: Tegra dma descriptors which manages the client requests. > + * This de scripts keep track of transfer status, callbacks, transfer and again ^^^^ > + * request counts etc. > + */ > +struct tegra_dma_desc { > + int bytes_requested; > + int bytes_transferred; > + enum dma_status dma_status; > + struct dma_async_tx_descriptor txd; > + struct list_head node; > + struct list_head tx_list; > + struct list_head cb_node; > + bool ack_reqd; > + bool cb_due; > + dma_cookie_t cookie; > +}; > + > +struct tegra_dma_channel; > + > +typedef void (*dma_isr_handler)(struct tegra_dma_channel *tdc, > + bool to_terminate); > + > +/* tegra_dma_channel: Channel specific information */ > +struct tegra_dma_channel { > + bool config_init; > + int id; > + int irq; > + unsigned long chan_base_offset; > + spinlock_t lock; > + bool busy; > + enum dma_transfer_mode dma_mode; > + int descs_allocated; > + struct dma_chan dma_chan; > + struct tegra_dma *tdma; > + > + /* Different lists for managing the requests */ > + struct list_head free_sg_req; > + struct list_head pending_sg_req; > + struct list_head free_dma_desc; > + struct list_head wait_ack_dma_desc; > + struct list_head cb_desc; > + > + /* isr handler and tasklet for bottom half of isr handling */ > + dma_isr_handler isr_handler; > + struct tasklet_struct tasklet; > + dma_async_tx_callback callback; > + void *callback_param; > + > + /* Channel-slave specific configuration */ > + struct dma_slave_config dma_sconfig; > + struct tegra_dma_slave dma_slave; > + > + /* Allocated memory pointer list for this channel */ > + struct list_head alloc_ptr_list; > +}; > + > +/* tegra_dma: Tegra dma specific information */ > +struct tegra_dma { > + struct dma_device dma_dev; > + struct device *dev; > + struct clk *dma_clk; > + spinlock_t global_lock; > + void __iomem *base_addr; > + struct regmap *regmap_dma; > + struct tegra_dma_chip_data chip_data; > + > + /* Last member of the structure */ > + struct tegra_dma_channel channels[0]; > +}; > + > +static inline void tdma_write(struct tegra_dma *tdma, u32 reg, u32 val) > +{ > + regmap_write(tdma->regmap_dma, reg, val); > +} > + > +static inline u32 tdma_read(struct tegra_dma *tdma, u32 reg) > +{ > + u32 val; > + regmap_read(tdma->regmap_dma, reg, &val); > + return val; > +} > + > +static inline void tdc_write(struct tegra_dma_channel *tdc, > + u32 reg, u32 val) > +{ > + regmap_write(tdc->tdma->regmap_dma, tdc->chan_base_offset + reg, val); > +} > + > +static inline u32 tdc_read(struct tegra_dma_channel *tdc, u32 reg) > +{ > + u32 val; > + regmap_read(tdc->tdma->regmap_dma, tdc->chan_base_offset + reg, &val); > + return val; > +} > + > +static inline struct tegra_dma_channel *to_tegra_dma_chan(struct dma_chan *dc) > +{ > + return container_of(dc, struct tegra_dma_channel, dma_chan); > +} > + > +static inline struct tegra_dma_desc *txd_to_tegra_dma_desc( > + struct dma_async_tx_descriptor *td) > +{ > + return container_of(td, struct tegra_dma_desc, txd); > +} > + > +static inline struct device *tdc2dev(struct tegra_dma_channel *tdc) > +{ > + return &tdc->dma_chan.dev->device; > +} > + > +static dma_cookie_t tegra_dma_tx_submit(struct dma_async_tx_descriptor *tx); > + > +static int allocate_tegra_desc(struct tegra_dma_channel *tdc, > + int ndma_desc, int nsg_req) what does the last arg mean? > +{ > + int i; > + struct tegra_dma_desc *dma_desc; > + struct tegra_dma_sg_req *sg_req; > + struct dma_chan *dc = &tdc->dma_chan; > + struct list_head dma_desc_list; > + struct list_head sg_req_list; > + struct tegra_dma_chan_mem_alloc *chan_mem; > + void *memptr; > + size_t dma_desc_size; > + size_t sg_req_size; > + size_t chan_mem_size; > + size_t total_size; > + unsigned long flags; > + > + INIT_LIST_HEAD(&dma_desc_list); > + INIT_LIST_HEAD(&sg_req_list); > + > + /* Calculate total require size of memory and then allocate */ > + dma_desc_size = sizeof(struct tegra_dma_desc) * ndma_desc; > + sg_req_size = sizeof(struct tegra_dma_sg_req) * nsg_req; > + chan_mem_size = sizeof(struct tegra_dma_chan_mem_alloc); > + total_size = chan_mem_size + dma_desc_size + sg_req_size; why cant you simply allocate three structs you need? > + > + memptr = kzalloc(total_size, GFP_KERNEL); > + if (!memptr) { > + dev_err(tdc2dev(tdc), > + "%s(): Memory allocation fails\n", __func__); > + return -ENOMEM; > + } > + chan_mem = memptr; > + > + /* Initialize dma descriptors */ > + dma_desc = memptr + chan_mem_size; > + for (i = 0; i < ndma_desc; ++i, dma_desc++) { > + dma_async_tx_descriptor_init(&dma_desc->txd, dc); > + dma_desc->txd.tx_submit = tegra_dma_tx_submit; > + dma_desc->txd.flags = DMA_CTRL_ACK; > + list_add_tail(&dma_desc->node, &dma_desc_list); > + } > + > + /* Initialize req descriptors */ > + sg_req = memptr + chan_mem_size + dma_desc_size; > + for (i = 0; i < nsg_req; ++i, sg_req++) > + list_add_tail(&sg_req->node, &sg_req_list); > + > + spin_lock_irqsave(&tdc->lock, flags); > + list_add_tail(&chan_mem->node, &tdc->alloc_ptr_list); > + > + if (ndma_desc) { > + tdc->descs_allocated += ndma_desc; > + list_splice(&dma_desc_list, &tdc->free_dma_desc); > + } > + > + if (nsg_req) > + list_splice(&sg_req_list, &tdc->free_sg_req); > + spin_unlock_irqrestore(&tdc->lock, flags); > + return tdc->descs_allocated; > +} > + > +/* Get dma desc from free list, if not there then allocate it */ > +static struct tegra_dma_desc *tegra_dma_desc_get(struct tegra_dma_channel *tdc) > +{ > + struct tegra_dma_desc *dma_desc = NULL; > + unsigned long flags; > + > + spin_lock_irqsave(&tdc->lock, flags); > + > + /* Check from free list desc */ > + if (!list_empty(&tdc->free_dma_desc)) { > + dma_desc = list_first_entry(&tdc->free_dma_desc, > + typeof(*dma_desc), node); > + list_del(&dma_desc->node); > + goto end; > + } > + > + /* > + * Check list with desc which are waiting for ack, may be it > + * got acked from client. > + */ > + if (!list_empty(&tdc->wait_ack_dma_desc)) { > + list_for_each_entry(dma_desc, &tdc->wait_ack_dma_desc, node) { > + if (async_tx_test_ack(&dma_desc->txd)) { > + list_del(&dma_desc->node); > + goto end; > + } > + } > + } > + > + /* There is no free desc, allocate it */ > + spin_unlock_irqrestore(&tdc->lock, flags); > + dev_dbg(tdc2dev(tdc), > + "Allocating more descriptors for channel %d\n", tdc->id); > + allocate_tegra_desc(tdc, DMA_NR_DESCS_PER_CHANNEL, > + DMA_NR_DESCS_PER_CHANNEL * DMA_NR_REQ_PER_DESC); > + spin_lock_irqsave(&tdc->lock, flags); > + if (list_empty(&tdc->free_dma_desc)) > + goto end; > + > + dma_desc = list_first_entry(&tdc->free_dma_desc, > + typeof(*dma_desc), node); > + list_del(&dma_desc->node); > +end: > + spin_unlock_irqrestore(&tdc->lock, flags); > + return dma_desc; > +} > + > +static void tegra_dma_desc_put(struct tegra_dma_channel *tdc, > + struct tegra_dma_desc *dma_desc) > +{ > + unsigned long flags; > + > + spin_lock_irqsave(&tdc->lock, flags); > + if (!list_empty(&dma_desc->tx_list)) > + list_splice_init(&dma_desc->tx_list, &tdc->free_sg_req); > + list_add_tail(&dma_desc->node, &tdc->free_dma_desc); > + spin_unlock_irqrestore(&tdc->lock, flags); > +} > + > +static void tegra_dma_desc_done_locked(struct tegra_dma_channel *tdc, > + struct tegra_dma_desc *dma_desc) > +{ > + if (dma_desc->ack_reqd) > + list_add_tail(&dma_desc->node, &tdc->wait_ack_dma_desc); > + else > + list_add_tail(&dma_desc->node, &tdc->free_dma_desc); > +} > + > +static struct tegra_dma_sg_req *tegra_dma_sg_req_get( > + struct tegra_dma_channel *tdc) > +{ > + struct tegra_dma_sg_req *sg_req = NULL; > + unsigned long flags; > + > + spin_lock_irqsave(&tdc->lock, flags); > + if (list_empty(&tdc->free_sg_req)) { > + spin_unlock_irqrestore(&tdc->lock, flags); > + dev_dbg(tdc2dev(tdc), > + "Reallocating sg_req for channel %d\n", tdc->id); > + allocate_tegra_desc(tdc, 0, > + DMA_NR_DESCS_PER_CHANNEL * DMA_NR_REQ_PER_DESC); > + spin_lock_irqsave(&tdc->lock, flags); > + if (list_empty(&tdc->free_sg_req)) { > + dev_dbg(tdc2dev(tdc), > + "Not found free sg_req for channel %d\n", tdc->id); > + goto end; > + } > + } > + > + sg_req = list_first_entry(&tdc->free_sg_req, typeof(*sg_req), node); > + list_del(&sg_req->node); > +end: > + spin_unlock_irqrestore(&tdc->lock, flags); > + return sg_req; > +} > + > +static int tegra_dma_slave_config(struct dma_chan *dc, > + struct dma_slave_config *sconfig) > +{ > + struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc); > + > + if (!list_empty(&tdc->pending_sg_req)) { > + dev_err(tdc2dev(tdc), > + "dma requests are pending, cannot take new configuration"); > + return -EBUSY; > + } > + > + /* Slave specific configuration is must for channel configuration */ > + if (!dc->private) { private is deprecated, pls dont use that > + dev_err(tdc2dev(tdc), > + "Slave specific private data not found for chan %d\n", > + tdc->id); > + return -EINVAL; > + } > + > + memcpy(&tdc->dma_sconfig, sconfig, sizeof(*sconfig)); > + memcpy(&tdc->dma_slave, dc->private, sizeof(tdc->dma_slave)); > + tdc->config_init = true; > + return 0; > +} > + > +static void tegra_dma_pause(struct tegra_dma_channel *tdc, > + bool wait_for_burst_complete) > +{ > + struct tegra_dma *tdma = tdc->tdma; > + spin_lock(&tdma->global_lock); > + tdma_write(tdma, APB_DMA_GEN, 0); > + if (wait_for_burst_complete) > + udelay(DMA_BUSRT_COMPLETE_TIME); > +} > + > +static void tegra_dma_resume(struct tegra_dma_channel *tdc) > +{ > + struct tegra_dma *tdma = tdc->tdma; > + tdma_write(tdma, APB_DMA_GEN, GEN_ENABLE); > + spin_unlock(&tdma->global_lock); > +} > + > +static void tegra_dma_stop(struct tegra_dma_channel *tdc) > +{ > + u32 csr; > + u32 status; > + > + /* Disable interrupts */ > + csr = tdc_read(tdc, APB_DMA_CHAN_CSR); > + csr &= ~CSR_IE_EOC; > + tdc_write(tdc, APB_DMA_CHAN_CSR, csr); > + > + /* Disable dma */ > + csr &= ~CSR_ENB; > + tdc_write(tdc, APB_DMA_CHAN_CSR, csr); > + > + /* Clear interrupt status if it is there */ > + status = tdc_read(tdc, APB_DMA_CHAN_STA); > + if (status & STA_ISE_EOC) { > + dev_dbg(tdc2dev(tdc), "%s():clearing interrupt\n", __func__); > + tdc_write(tdc, APB_DMA_CHAN_STA, status); > + } > + tdc->busy = false; > +} > + > +static void tegra_dma_start(struct tegra_dma_channel *tdc, > + struct tegra_dma_sg_req *sg_req) > +{ > + struct tegra_dma_channel_regs *ch_regs = &sg_req->ch_regs; > + unsigned long csr = ch_regs->csr; > + > + tdc_write(tdc, APB_DMA_CHAN_CSR, csr); > + tdc_write(tdc, APB_DMA_CHAN_APB_SEQ, ch_regs->apb_seq); > + tdc_write(tdc, APB_DMA_CHAN_APB_PTR, ch_regs->apb_ptr); > + tdc_write(tdc, APB_DMA_CHAN_AHB_SEQ, ch_regs->ahb_seq); > + tdc_write(tdc, APB_DMA_CHAN_AHB_PTR, ch_regs->ahb_ptr); > + > + /* Dump the configuration register if verbose mode enabled */ > + dev_vdbg(tdc2dev(tdc), > + "%s(): csr: 0x%08lx\n", __func__, ch_regs->csr); > + dev_vdbg(tdc2dev(tdc), > + "%s(): apbseq: 0x%08lx\n", __func__, ch_regs->apb_seq); > + dev_vdbg(tdc2dev(tdc), > + "%s(): apbptr: 0x%08lx\n", __func__, ch_regs->apb_ptr); > + dev_vdbg(tdc2dev(tdc), > + "%s(): ahbseq: 0x%08lx\n", __func__, ch_regs->ahb_seq); > + dev_vdbg(tdc2dev(tdc), > + "%s(): ahbptr: 0x%08lx\n", __func__, ch_regs->ahb_ptr); > + > + /* Start dma */ > + csr |= CSR_ENB; > + tdc_write(tdc, APB_DMA_CHAN_CSR, csr); > +} > + > +static void tegra_dma_configure_for_next(struct tegra_dma_channel *tdc, > + struct tegra_dma_sg_req *nsg_req) > +{ > + unsigned long status; > + > + /* > + * The dma controller reloads the new configuration for next transfer > + * after last burst of current transfer completes. > + * If there is no IEC status then this makes sure that last burst > + * has not be completed. There may be case that last burst is on > + * flight and so it can complete but because dma is paused, it > + * will not generates interrupt as well as not reload the new > + * configuration. > + * If there is already IEC status then interrupt handler need to > + * load new configuration. > + */ > + tegra_dma_pause(tdc, false); > + status = tdc_read(tdc, APB_DMA_CHAN_STA); > + > + /* > + * If interrupt is pending then do nothing as the ISR will handle > + * the programing for new request. > + */ > + if (status & STA_ISE_EOC) { > + dev_err(tdc2dev(tdc), > + "Skipping new configuration as interrupt is pending\n"); > + goto exit_config; > + } > + > + /* Safe to program new configuration */ > + tdc_write(tdc, APB_DMA_CHAN_APB_PTR, nsg_req->ch_regs.apb_ptr); > + tdc_write(tdc, APB_DMA_CHAN_AHB_PTR, nsg_req->ch_regs.ahb_ptr); > + tdc_write(tdc, APB_DMA_CHAN_CSR, nsg_req->ch_regs.csr | CSR_ENB); > + nsg_req->configured = true; > + > +exit_config: > + tegra_dma_resume(tdc); > +} > + > +static void tdc_start_head_req(struct tegra_dma_channel *tdc) > +{ > + struct tegra_dma_sg_req *sg_req; > + > + if (list_empty(&tdc->pending_sg_req)) > + return; > + > + sg_req = list_first_entry(&tdc->pending_sg_req, > + typeof(*sg_req), node); > + tegra_dma_start(tdc, sg_req); > + sg_req->configured = true; > + tdc->busy = true; > +} > + > +static void tdc_configure_next_head_desc(struct tegra_dma_channel *tdc) > +{ > + struct tegra_dma_sg_req *hsgreq; > + struct tegra_dma_sg_req *hnsgreq; > + > + if (list_empty(&tdc->pending_sg_req)) > + return; > + > + hsgreq = list_first_entry(&tdc->pending_sg_req, typeof(*hsgreq), node); > + if (!list_is_last(&hsgreq->node, &tdc->pending_sg_req)) { > + hnsgreq = list_first_entry(&hsgreq->node, > + typeof(*hnsgreq), node); > + tegra_dma_configure_for_next(tdc, hnsgreq); > + } > +} > + > +static inline int get_current_xferred_count(struct tegra_dma_channel *tdc, > + struct tegra_dma_sg_req *sg_req, unsigned long status) > +{ > + return sg_req->req_len - ((status & STA_COUNT_MASK) + 4); > +} > + > +static void tegra_dma_abort_all(struct tegra_dma_channel *tdc) > +{ > + struct tegra_dma_sg_req *sgreq; > + struct tegra_dma_desc *dma_desc; > + while (!list_empty(&tdc->pending_sg_req)) { > + sgreq = list_first_entry(&tdc->pending_sg_req, > + typeof(*sgreq), node); > + list_del(&sgreq->node); > + list_add_tail(&sgreq->node, &tdc->free_sg_req); > + if (sgreq->last_sg) { > + dma_desc = sgreq->dma_desc; > + dma_desc->dma_status = DMA_ERROR; > + tegra_dma_desc_done_locked(tdc, dma_desc); > + > + /* Add in cb list if it is not there. */ > + if (!dma_desc->cb_due) { > + list_add_tail(&dma_desc->cb_node, > + &tdc->cb_desc); > + dma_desc->cb_due = true; > + } > + dma_cookie_complete(&dma_desc->txd); > + } > + } > + tdc->dma_mode = DMA_MODE_NONE; > +} > + > +static bool handle_continuous_head_request(struct tegra_dma_channel *tdc, > + struct tegra_dma_sg_req *last_sg_req, bool to_terminate) > +{ > + struct tegra_dma_sg_req *hsgreq = NULL; > + > + if (list_empty(&tdc->pending_sg_req)) { > + dev_err(tdc2dev(tdc), > + "%s(): Dma is running without any req list\n", > + __func__); > + tegra_dma_stop(tdc); > + return false; > + } > + > + /* > + * Check that head req on list should be in flight. > + * If it is not in flight then abort transfer as > + * transfer looping can not continue. > + */ > + hsgreq = list_first_entry(&tdc->pending_sg_req, typeof(*hsgreq), node); > + if (!hsgreq->configured) { > + tegra_dma_stop(tdc); > + dev_err(tdc2dev(tdc), > + "Error in dma transfer loop, aborting dma\n"); > + tegra_dma_abort_all(tdc); > + return false; > + } > + > + /* Configure next request in single buffer mode */ > + if (!to_terminate && (tdc->dma_mode == DMA_MODE_CYCLE)) > + tdc_configure_next_head_desc(tdc); > + return true; > +} > + > +static void handle_once_dma_done(struct tegra_dma_channel *tdc, > + bool to_terminate) > +{ > + struct tegra_dma_sg_req *sgreq; > + struct tegra_dma_desc *dma_desc; > + > + tdc->busy = false; > + sgreq = list_first_entry(&tdc->pending_sg_req, typeof(*sgreq), node); > + dma_desc = sgreq->dma_desc; > + dma_desc->bytes_transferred += sgreq->req_len; > + > + list_del(&sgreq->node); > + if (sgreq->last_sg) { > + dma_cookie_complete(&dma_desc->txd); > + list_add_tail(&dma_desc->cb_node, &tdc->cb_desc); > + dma_desc->cb_due = true; > + tegra_dma_desc_done_locked(tdc, dma_desc); > + } > + list_add_tail(&sgreq->node, &tdc->free_sg_req); > + > + /* Do not start dma if it is going to be terminate */ > + if (to_terminate || list_empty(&tdc->pending_sg_req)) > + return; > + > + tdc_start_head_req(tdc); > + return; > +} > + > +static void handle_cont_sngl_cycle_dma_done(struct tegra_dma_channel *tdc, > + bool to_terminate) > +{ > + struct tegra_dma_sg_req *sgreq; > + struct tegra_dma_desc *dma_desc; > + bool st; > + > + sgreq = list_first_entry(&tdc->pending_sg_req, typeof(*sgreq), node); > + dma_desc = sgreq->dma_desc; > + dma_desc->bytes_transferred += sgreq->req_len; > + > + /* Callback need to be call */ > + list_add_tail(&dma_desc->cb_node, &tdc->cb_desc); > + dma_desc->cb_due = true; > + > + /* If not last req then put at end of pending list */ > + if (!list_is_last(&sgreq->node, &tdc->pending_sg_req)) { > + list_del(&sgreq->node); > + list_add_tail(&sgreq->node, &tdc->pending_sg_req); > + sgreq->configured = false; > + st = handle_continuous_head_request(tdc, sgreq, to_terminate); > + if (!st) > + dma_desc->dma_status = DMA_ERROR; > + } > + return; > +} > + > +static void handle_cont_dbl_cycle_dma_done(struct tegra_dma_channel *tdc, > + bool to_terminate) > +{ > + struct tegra_dma_sg_req *hsgreq; > + struct tegra_dma_sg_req *hnsgreq; > + struct tegra_dma_desc *dma_desc; > + > + hsgreq = list_first_entry(&tdc->pending_sg_req, typeof(*hsgreq), node); > + dma_desc = hsgreq->dma_desc; > + dma_desc->bytes_transferred += hsgreq->req_len; > + > + if (!hsgreq->half_done) { > + if (!list_is_last(hsgreq->node.next, &tdc->pending_sg_req) && > + !to_terminate) { > + hnsgreq = list_first_entry(&hsgreq->node, > + typeof(*hnsgreq), node); > + tegra_dma_configure_for_next(tdc, hnsgreq); > + } > + hsgreq->half_done = true; > + list_add_tail(&dma_desc->cb_node, &tdc->cb_desc); > + dma_desc->cb_due = true; > + } else { > + hsgreq->half_done = false; > + list_add_tail(&dma_desc->cb_node, &tdc->cb_desc); > + dma_desc->cb_due = true; > + > + /* > + * If this is not last entry then put the req in end of > + * list for next cycle. > + */ > + if (!list_is_last(hsgreq->node.next, &tdc->pending_sg_req)) { > + list_del(&hsgreq->node); > + list_add_tail(&hsgreq->node, &tdc->pending_sg_req); > + hsgreq->configured = false; > + } > + } > + return; > +} > + > +static void tegra_dma_tasklet(unsigned long data) > +{ > + struct tegra_dma_channel *tdc = (struct tegra_dma_channel *)data; > + unsigned long flags; > + dma_async_tx_callback callback = NULL; > + void *callback_param = NULL; > + struct tegra_dma_desc *dma_desc; > + struct list_head cb_dma_desc_list; > + > + INIT_LIST_HEAD(&cb_dma_desc_list); > + spin_lock_irqsave(&tdc->lock, flags); > + if (list_empty(&tdc->cb_desc)) { > + spin_unlock_irqrestore(&tdc->lock, flags); > + return; > + } > + list_splice_init(&tdc->cb_desc, &cb_dma_desc_list); > + spin_unlock_irqrestore(&tdc->lock, flags); > + > + while (!list_empty(&cb_dma_desc_list)) { > + dma_desc = list_first_entry(&cb_dma_desc_list, > + typeof(*dma_desc), cb_node); > + list_del(&dma_desc->cb_node); > + > + callback = dma_desc->txd.callback; > + callback_param = dma_desc->txd.callback_param; > + dma_desc->cb_due = false; > + if (callback) > + callback(callback_param); > + } > +} > + > +static irqreturn_t tegra_dma_isr(int irq, void *dev_id) > +{ > + struct tegra_dma_channel *tdc = dev_id; > + unsigned long status; > + unsigned long flags; > + > + spin_lock_irqsave(&tdc->lock, flags); > + > + status = tdc_read(tdc, APB_DMA_CHAN_STA); > + if (status & STA_ISE_EOC) { > + tdc_write(tdc, APB_DMA_CHAN_STA, status); > + if (!list_empty(&tdc->cb_desc)) { > + dev_err(tdc2dev(tdc), > + "Int before tasklet handled, Stopping DMA %d\n", > + tdc->id); > + tegra_dma_stop(tdc); > + tdc->isr_handler(tdc, true); > + tegra_dma_abort_all(tdc); > + /* Schedule tasklet to make callback */ > + tasklet_schedule(&tdc->tasklet); > + goto end; > + } > + tdc->isr_handler(tdc, false); > + tasklet_schedule(&tdc->tasklet); > + } else { > + dev_info(tdc2dev(tdc), > + "Interrupt is already handled %d status 0x%08lx\n", > + tdc->id, status); > + } > + > +end: > + spin_unlock_irqrestore(&tdc->lock, flags); > + return IRQ_HANDLED; > +} > + > +static dma_cookie_t tegra_dma_tx_submit(struct dma_async_tx_descriptor *txd) > +{ > + struct tegra_dma_desc *dma_desc = txd_to_tegra_dma_desc(txd); > + struct tegra_dma_channel *tdc = to_tegra_dma_chan(txd->chan); > + unsigned long flags; > + dma_cookie_t cookie; > + > + spin_lock_irqsave(&tdc->lock, flags); > + dma_desc->dma_status = DMA_IN_PROGRESS; > + cookie = dma_cookie_assign(&dma_desc->txd); > + dma_desc->cookie = dma_desc->txd.cookie; > + list_splice_tail_init(&dma_desc->tx_list, &tdc->pending_sg_req); > + spin_unlock_irqrestore(&tdc->lock, flags); > + return cookie; > +} > + > +static void tegra_dma_issue_pending(struct dma_chan *dc) > +{ > + struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc); > + unsigned long flags; > + > + spin_lock_irqsave(&tdc->lock, flags); > + if (list_empty(&tdc->pending_sg_req)) { > + dev_err(tdc2dev(tdc), > + "No requests for channel %d\n", tdc->id); > + goto end; > + } > + if (!tdc->busy) { > + tdc_start_head_req(tdc); > + > + /* Continuous single mode: Configure next req */ > + if (DMA_MODE_CYCLE) { > + /* > + * Wait for 1 burst time for configure dma for > + * next transfer. > + */ > + udelay(DMA_BUSRT_COMPLETE_TIME); > + tdc_configure_next_head_desc(tdc); > + } > + } > +end: > + spin_unlock_irqrestore(&tdc->lock, flags); > + return; > +} > + > +static void tegra_dma_terminate_all(struct dma_chan *dc) > +{ > + struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc); > + struct tegra_dma_sg_req *sgreq; > + struct tegra_dma_desc *dma_desc; > + unsigned long flags; > + unsigned long status; > + struct list_head new_list; > + dma_async_tx_callback callback = NULL; > + void *callback_param = NULL; > + struct list_head cb_dma_desc_list; > + bool was_busy; > + > + INIT_LIST_HEAD(&new_list); > + INIT_LIST_HEAD(&cb_dma_desc_list); > + > + spin_lock_irqsave(&tdc->lock, flags); > + if (list_empty(&tdc->pending_sg_req)) { > + spin_unlock_irqrestore(&tdc->lock, flags); > + return; > + } > + > + if (!tdc->busy) { > + list_splice_init(&tdc->cb_desc, &cb_dma_desc_list); > + goto skip_dma_stop; > + } > + > + /* Pause dma before checking the queue status */ > + tegra_dma_pause(tdc, true); > + > + status = tdc_read(tdc, APB_DMA_CHAN_STA); > + if (status & STA_ISE_EOC) { > + dev_dbg(tdc2dev(tdc), "%s():handling isr\n", __func__); > + tdc->isr_handler(tdc, true); > + status = tdc_read(tdc, APB_DMA_CHAN_STA); > + } > + list_splice_init(&tdc->cb_desc, &cb_dma_desc_list); > + > + was_busy = tdc->busy; > + tegra_dma_stop(tdc); > + if (!list_empty(&tdc->pending_sg_req) && was_busy) { > + sgreq = list_first_entry(&tdc->pending_sg_req, > + typeof(*sgreq), node); > + sgreq->dma_desc->bytes_transferred += > + get_current_xferred_count(tdc, sgreq, status); > + } > + tegra_dma_resume(tdc); > + > +skip_dma_stop: > + tegra_dma_abort_all(tdc); > + /* Ignore callbacks pending list */ > + INIT_LIST_HEAD(&tdc->cb_desc); > + spin_unlock_irqrestore(&tdc->lock, flags); > + > + /* Call callbacks if was pending before aborting requests */ > + while (!list_empty(&cb_dma_desc_list)) { > + dma_desc = list_first_entry(&cb_dma_desc_list, > + typeof(*dma_desc), cb_node); > + list_del(&dma_desc->cb_node); > + callback = dma_desc->txd.callback; > + callback_param = dma_desc->txd.callback_param; > + if (callback) > + callback(callback_param); > + } > +} > + > +static enum dma_status tegra_dma_tx_status(struct dma_chan *dc, > + dma_cookie_t cookie, struct dma_tx_state *txstate) > +{ > + struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc); > + struct tegra_dma_desc *dma_desc; > + struct tegra_dma_sg_req *sg_req; > + enum dma_status ret; > + unsigned long flags; > + > + spin_lock_irqsave(&tdc->lock, flags); > + > + ret = dma_cookie_status(dc, cookie, txstate); > + if (ret != DMA_SUCCESS) > + goto check_pending_q; > + > + if (list_empty(&tdc->wait_ack_dma_desc)) > + goto check_pending_q; > + > + /* Check on wait_ack desc status */ > + list_for_each_entry(dma_desc, &tdc->wait_ack_dma_desc, node) { > + if (dma_desc->cookie == cookie) { > + dma_set_residue(txstate, > + dma_desc->bytes_requested - > + dma_desc->bytes_transferred); > + ret = dma_desc->dma_status; > + goto end; > + } > + } > + > +check_pending_q: > + if (list_empty(&tdc->pending_sg_req)) > + goto end; > + > + /* May be this is in head list of pending list */ > + list_for_each_entry(sg_req, &tdc->pending_sg_req, node) { > + dma_desc = sg_req->dma_desc; > + if (dma_desc->txd.cookie == cookie) { > + dma_set_residue(txstate, > + dma_desc->bytes_requested - > + dma_desc->bytes_transferred); > + ret = dma_desc->dma_status; > + goto end; > + } > + } > + dev_info(tdc2dev(tdc), "%s(): cookie does not found\n", __func__); > +end: > + spin_unlock_irqrestore(&tdc->lock, flags); > + return ret; > +} > + > +static int tegra_dma_device_control(struct dma_chan *dc, enum dma_ctrl_cmd cmd, > + unsigned long arg) > +{ > + switch (cmd) { > + case DMA_SLAVE_CONFIG: > + return tegra_dma_slave_config(dc, > + (struct dma_slave_config *)arg); > + > + case DMA_TERMINATE_ALL: > + tegra_dma_terminate_all(dc); > + return 0; > + default: > + break; > + } > + > + return -ENXIO; > +} > + > +static inline int get_bus_width(enum dma_slave_buswidth slave_bw) > +{ > + BUG_ON(!slave_bw); > + switch (slave_bw) { > + case DMA_SLAVE_BUSWIDTH_1_BYTE: > + return APB_SEQ_BUS_WIDTH_8; > + case DMA_SLAVE_BUSWIDTH_2_BYTES: > + return APB_SEQ_BUS_WIDTH_16; > + case DMA_SLAVE_BUSWIDTH_4_BYTES: > + return APB_SEQ_BUS_WIDTH_32; > + case DMA_SLAVE_BUSWIDTH_8_BYTES: > + return APB_SEQ_BUS_WIDTH_64; > + default: > + BUG(); > + } > +} > + > +static inline int get_burst_size(struct tegra_dma_channel *tdc, int len) > +{ > + switch (tdc->dma_slave.burst_size) { > + case TEGRA_DMA_BURST_1: > + return AHB_SEQ_BURST_1; > + case TEGRA_DMA_BURST_4: > + return AHB_SEQ_BURST_4; > + case TEGRA_DMA_BURST_8: > + return AHB_SEQ_BURST_8; > + case TEGRA_DMA_AUTO: > + if (len & 0xF) > + return AHB_SEQ_BURST_1; > + else if ((len >> 4) & 0x1) > + return AHB_SEQ_BURST_4; > + else > + return AHB_SEQ_BURST_8; > + } > + WARN(1, KERN_WARNING "Invalid burst option\n"); > + return AHB_SEQ_BURST_1; > +} > + > +static bool init_dma_mode(struct tegra_dma_channel *tdc, > + enum dma_transfer_mode new_mode) > +{ > + if (tdc->dma_mode == DMA_MODE_NONE) { > + tdc->dma_mode = new_mode; > + switch (new_mode) { > + case DMA_MODE_ONCE: > + tdc->isr_handler = handle_once_dma_done; > + break; > + case DMA_MODE_CYCLE: > + tdc->isr_handler = handle_cont_sngl_cycle_dma_done; > + break; > + case DMA_MODE_CYCLE_HALF_NOTIFY: > + tdc->isr_handler = handle_cont_dbl_cycle_dma_done; > + break; > + default: > + break; > + } > + } else { > + if (new_mode != tdc->dma_mode) > + return false; > + } > + return true; > +} > + > +static struct dma_async_tx_descriptor *tegra_dma_prep_slave_sg( > + struct dma_chan *dc, struct scatterlist *sgl, unsigned int sg_len, > + enum dma_transfer_direction direction, unsigned long flags, > + void *context) > +{ > + struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc); > + struct tegra_dma_desc *dma_desc; > + unsigned int i; > + struct scatterlist *sg; > + unsigned long csr, ahb_seq, apb_ptr, apb_seq; > + struct list_head req_list; > + struct tegra_dma_sg_req *sg_req = NULL; > + > + if (!tdc->config_init) { > + dev_err(tdc2dev(tdc), "dma channel is not configured\n"); > + return NULL; > + } > + if (sg_len < 1) { > + dev_err(tdc2dev(tdc), "Invalid segment length %d\n", sg_len); > + return NULL; > + } > + > + INIT_LIST_HEAD(&req_list); > + > + ahb_seq = AHB_SEQ_INTR_ENB; > + ahb_seq |= AHB_SEQ_WRAP_NONE << AHB_SEQ_WRAP_SHIFT; > + ahb_seq |= AHB_SEQ_BUS_WIDTH_32; > + > + csr = CSR_ONCE | CSR_FLOW; > + csr |= tdc->dma_slave.dma_req_id << CSR_REQ_SEL_SHIFT; > + if (flags & DMA_PREP_INTERRUPT) > + csr |= CSR_IE_EOC; > + > + apb_seq = APB_SEQ_WRAP_WORD_1; > + > + switch (direction) { > + case DMA_MEM_TO_DEV: > + apb_ptr = tdc->dma_sconfig.dst_addr; > + apb_seq |= get_bus_width(tdc->dma_sconfig.dst_addr_width); > + csr |= CSR_DIR; > + break; > + > + case DMA_DEV_TO_MEM: > + apb_ptr = tdc->dma_sconfig.src_addr; > + apb_seq |= get_bus_width(tdc->dma_sconfig.src_addr_width); > + break; you dont support DMA_MEM_TO_DEV? > + default: > + dev_err(tdc2dev(tdc), "Dma direction is not supported\n"); > + return NULL; > + } > + > + dma_desc = tegra_dma_desc_get(tdc); > + if (!dma_desc) { > + dev_err(tdc2dev(tdc), "Dma descriptors not available\n"); > + goto fail; > + } > + INIT_LIST_HEAD(&dma_desc->tx_list); > + INIT_LIST_HEAD(&dma_desc->cb_node); > + dma_desc->bytes_requested = 0; > + dma_desc->bytes_transferred = 0; > + dma_desc->dma_status = DMA_IN_PROGRESS; > + > + /* Make transfer requests */ > + for_each_sg(sgl, sg, sg_len, i) { > + u32 len, mem; > + > + mem = sg_phys(sg); > + len = sg_dma_len(sg); > + > + if ((len & 3) || (mem & 3) || > + (len > tdc->tdma->chip_data.max_dma_count)) { > + dev_err(tdc2dev(tdc), > + "Dma length/memory address is not correct\n"); > + goto fail; > + } > + > + sg_req = tegra_dma_sg_req_get(tdc); > + if (!sg_req) { > + dev_err(tdc2dev(tdc), "Dma sg-req not available\n"); > + goto fail; > + } > + > + ahb_seq |= get_burst_size(tdc, len); > + dma_desc->bytes_requested += len; > + > + sg_req->ch_regs.apb_ptr = apb_ptr; > + sg_req->ch_regs.ahb_ptr = mem; > + sg_req->ch_regs.csr = csr | ((len - 4) & 0xFFFC); > + sg_req->ch_regs.apb_seq = apb_seq; > + sg_req->ch_regs.ahb_seq = ahb_seq; > + sg_req->configured = false; > + sg_req->last_sg = false; > + sg_req->dma_desc = dma_desc; > + sg_req->req_len = len; > + > + list_add_tail(&sg_req->node, &dma_desc->tx_list); > + } > + sg_req->last_sg = true; > + dma_desc->ack_reqd = (flags & DMA_CTRL_ACK) ? false : true; > + if (dma_desc->ack_reqd) > + dma_desc->txd.flags = DMA_CTRL_ACK; > + > + /* > + * Make sure that mode should not be conflicting with currently > + * configured mode. > + */ > + if (!init_dma_mode(tdc, DMA_MODE_ONCE)) { > + dev_err(tdc2dev(tdc), "Conflict in dma modes\n"); > + goto fail; > + } > + > + return &dma_desc->txd; > + > +fail: > + tegra_dma_desc_put(tdc, dma_desc); > + return NULL; > +} > + > +struct dma_async_tx_descriptor *tegra_dma_prep_dma_cyclic( > + struct dma_chan *dc, dma_addr_t buf_addr, size_t buf_len, > + size_t period_len, enum dma_transfer_direction direction, > + void *context) > +{ > + struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc); > + struct tegra_dma_desc *dma_desc = NULL; > + struct tegra_dma_sg_req *sg_req = NULL; > + unsigned long csr, ahb_seq, apb_ptr, apb_seq; > + int len; > + bool half_buffer_notify; > + enum dma_transfer_mode new_mode; > + size_t remain_len; > + dma_addr_t mem = buf_addr; > + > + if (!buf_len) { > + dev_err(tdc2dev(tdc), > + "Buffer length is invalid len %d\n", buf_len); > + } > + > + if (!tdc->config_init) { > + dev_err(tdc2dev(tdc), > + "DMA is not configured for slave\n"); > + return NULL; > + } > + > + if (tdc->busy) { > + dev_err(tdc2dev(tdc), > + "DMA is already started, can not accept any more requests\n"); > + return NULL; > + } > + > + /* > + * We only support cyclic transfer when buf_len is multiple of > + * period_len. > + * With period of buf_len, it will set dma mode DMA_MODE_CYCLE > + * with one request. > + * With period of buf_len/2, it will set dma mode > + * DMA_MODE_CYCLE_HALF_NOTIFY with one requsts. > + * Othercase, the transfer is broken in smaller requests of size > + * of period_len and the transfer continues forever in cyclic way > + * dma mode of DMA_MODE_CYCLE. > + * If period_len is zero then assume dma mode DMA_MODE_CYCLE. > + * We also allow to take more number of requests till dma is > + * not started. The driver will loop over all requests. > + * Once dma is started then new requests can be queued only after > + * terminating the dma. > + */ > + if (!period_len) > + period_len = buf_len; i am not sure about this assignment here. Why should period length be ZERO? > + > + if (buf_len % period_len) { > + dev_err(tdc2dev(tdc), > + "buf_len %d should be multiple of period_len %d\n", > + buf_len, period_len); > + return NULL; > + } I am assuming you are also putting this as a constraint in sound driver. > + > + half_buffer_notify = (buf_len == (2 * period_len)) ? true : false; > + len = (half_buffer_notify) ? buf_len / 2 : period_len; > + if ((len & 3) || (buf_addr & 3) || > + (len > tdc->tdma->chip_data.max_dma_count)) { > + dev_err(tdc2dev(tdc), > + "Dma length/memory address is not correct\n"); not supported would be apt > + return NULL; > + } > + > + ahb_seq = AHB_SEQ_INTR_ENB; > + ahb_seq |= AHB_SEQ_WRAP_NONE << AHB_SEQ_WRAP_SHIFT; > + ahb_seq |= AHB_SEQ_BUS_WIDTH_32; > + if (half_buffer_notify) > + ahb_seq |= AHB_SEQ_DBL_BUF; > + > + csr = CSR_FLOW | CSR_IE_EOC; > + csr |= tdc->dma_slave.dma_req_id << CSR_REQ_SEL_SHIFT; > + > + apb_seq = APB_SEQ_WRAP_WORD_1; > + > + switch (direction) { > + case DMA_MEM_TO_DEV: > + apb_ptr = tdc->dma_sconfig.dst_addr; > + apb_seq |= get_bus_width(tdc->dma_sconfig.dst_addr_width); > + csr |= CSR_DIR; > + break; > + > + case DMA_DEV_TO_MEM: > + apb_ptr = tdc->dma_sconfig.src_addr; > + apb_seq |= get_bus_width(tdc->dma_sconfig.src_addr_width); > + break; > + default: > + dev_err(tdc2dev(tdc), "Dma direction is not supported\n"); > + return NULL; > + } > + > + dma_desc = tegra_dma_desc_get(tdc); > + if (!dma_desc) { > + dev_err(tdc2dev(tdc), "not enough descriptors available\n"); > + goto fail; > + } > + INIT_LIST_HEAD(&dma_desc->tx_list); > + > + dma_desc->bytes_transferred = 0; > + dma_desc->bytes_requested = buf_len; > + remain_len = (half_buffer_notify) ? len : buf_len; > + ahb_seq |= get_burst_size(tdc, len); > + > + while (remain_len) { > + sg_req = tegra_dma_sg_req_get(tdc); > + if (!sg_req) { > + dev_err(tdc2dev(tdc), "Dma sg-req not available\n"); > + goto fail; > + } > + > + ahb_seq |= get_burst_size(tdc, len); > + sg_req->ch_regs.apb_ptr = apb_ptr; > + sg_req->ch_regs.ahb_ptr = mem; > + sg_req->ch_regs.csr = csr | ((len - 4) & 0xFFFC); > + sg_req->ch_regs.apb_seq = apb_seq; > + sg_req->ch_regs.ahb_seq = ahb_seq; > + sg_req->configured = false; > + sg_req->half_done = false; > + sg_req->last_sg = false; > + sg_req->dma_desc = dma_desc; > + sg_req->req_len = len; > + > + list_add_tail(&sg_req->node, &dma_desc->tx_list); > + remain_len -= len; > + mem += len; > + } > + sg_req->last_sg = true; > + dma_desc->ack_reqd = true; > + dma_desc->txd.flags = DMA_CTRL_ACK; > + > + /* > + * We can not change the dma mode once it is initialized > + * until all desc are terminated. > + */ > + new_mode = (half_buffer_notify) ? > + DMA_MODE_CYCLE_HALF_NOTIFY : DMA_MODE_CYCLE; > + if (!init_dma_mode(tdc, new_mode)) { > + dev_err(tdc2dev(tdc), "Conflict in dma modes\n"); > + goto fail; > + } > + > + return &dma_desc->txd; > + > +fail: > + tegra_dma_desc_put(tdc, dma_desc); > + return NULL; > +} > + > +static int tegra_dma_alloc_chan_resources(struct dma_chan *dc) > +{ > + struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc); > + int total_desc; > + > + total_desc = allocate_tegra_desc(tdc, DMA_NR_DESCS_PER_CHANNEL, > + DMA_NR_DESCS_PER_CHANNEL * DMA_NR_REQ_PER_DESC); > + dma_cookie_init(&tdc->dma_chan); > + dev_dbg(tdc2dev(tdc), > + "%s(): allocated %d descriptors\n", __func__, total_desc); > + tdc->config_init = false; > + return total_desc; > +} > + > +static void tegra_dma_free_chan_resources(struct dma_chan *dc) > +{ > + struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc); > + struct tegra_dma_chan_mem_alloc *mptr; > + > + dev_dbg(tdc2dev(tdc), > + "%s(): channel %d and desc freeing %d\n", > + __func__, tdc->id, tdc->descs_allocated); > + if (tdc->busy) > + tegra_dma_terminate_all(dc); > + > + INIT_LIST_HEAD(&tdc->pending_sg_req); > + INIT_LIST_HEAD(&tdc->free_sg_req); > + INIT_LIST_HEAD(&tdc->alloc_ptr_list); > + INIT_LIST_HEAD(&tdc->free_dma_desc); > + INIT_LIST_HEAD(&tdc->wait_ack_dma_desc); > + INIT_LIST_HEAD(&tdc->cb_desc); > + tdc->descs_allocated = 0; > + tdc->config_init = false; > + while (!list_empty(&tdc->alloc_ptr_list)) { > + mptr = list_first_entry(&tdc->alloc_ptr_list, > + typeof(*mptr), node); > + list_del(&mptr->node); > + kfree(mptr); > + } > +} > + > +/* Tegra20 specific dma controller information */ > +static struct tegra_dma_chip_data tegra20_chip_data = { > + .nr_channels = 16, > + .max_dma_count = 1024UL * 64, > +}; > + > +/* Tegra30 specific dma controller information */ > +static struct tegra_dma_chip_data tegra30_chip_data = { > + .nr_channels = 32, > + .max_dma_count = 1024UL * 64, > +}; > + > +#if defined(CONFIG_OF) > +/* Match table for of_platform binding */ > +static const struct of_device_id tegra_dma_of_match[] __devinitconst = { > + { .compatible = "nvidia,tegra30-apbdma", .data = &tegra30_chip_data, }, > + { .compatible = "nvidia,tegra20-apbdma", .data = &tegra20_chip_data, }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, tegra_dma_of_match); > +#else > +#define tegra_dma_of_match NULL > +#endif > + > +static struct platform_device_id dma_id_table[] = { > + {.name = "tegra30-apbdma", .driver_data = (ulong)&tegra30_chip_data, }, > + {.name = "tegra20-apbdma", .driver_data = (ulong)&tegra20_chip_data, }, > + {}, > +}; > + > +static bool tdma_volatile_reg(struct device *dev, unsigned int reg) > +{ > + unsigned int chan_reg; > + > + if (reg < DMA_CHANNEL_BASE_ADDRESS_OFFSET) > + return false; > + > + chan_reg = (reg - DMA_CHANNEL_BASE_ADDRESS_OFFSET) % > + DMA_CHANNEL_REGISTER_SIZE; > + switch (chan_reg) { > + case APB_DMA_CHAN_STA: > + case APB_DMA_CHAN_CSR: > + return true; > + } > + return false; > +} > + > +static bool tdma_wr_rd_reg(struct device *dev, unsigned int reg) > +{ > + unsigned int chan_reg; > + > + /* Dma base registers */ > + if (reg < DMA_CHANNEL_BASE_ADDRESS_OFFSET) { > + switch (reg) { > + case APB_DMA_GEN: > + case APB_DMA_CNTRL: > + case APB_DMA_IRQ_MASK: > + case APB_DMA_IRQ_MASK_SET: > + return true; > + default: > + return false; > + } > + } > + > + /* Channel registers */ > + chan_reg = (reg - DMA_CHANNEL_BASE_ADDRESS_OFFSET) % > + DMA_CHANNEL_REGISTER_SIZE; > + switch (chan_reg) { > + case APB_DMA_CHAN_CSR: > + case APB_DMA_CHAN_STA: > + case APB_DMA_CHAN_APB_SEQ: > + case APB_DMA_CHAN_APB_PTR: > + case APB_DMA_CHAN_AHB_SEQ: > + case APB_DMA_CHAN_AHB_PTR: > + return true; > + default: > + return false; > + } > +} > + > +static struct regmap_config tdma_regmap_config = { > + .name = "tegra-apbdma", > + .reg_bits = 32, > + .val_bits = 32, > + .reg_stride = 4, > + .volatile_reg = tdma_volatile_reg, > + .writeable_reg = tdma_wr_rd_reg, > + .readable_reg = tdma_wr_rd_reg, > + .cache_type = REGCACHE_RBTREE, > +}; > + > +static int __devinit tegra_dma_probe(struct platform_device *pdev) > +{ > + struct resource *res; > + struct tegra_dma *tdma; > + size_t size; > + int ret; > + int i; > + struct tegra_dma_chip_data *chip_data = NULL; > + > +#if defined(CONFIG_OF) > + { > + const struct of_device_id *match; > + match = of_match_device(of_match_ptr(tegra_dma_of_match), > + &pdev->dev); > + if (match) > + chip_data = match->data; > + } > +#else > + chip_data = (struct tegra_dma_chip_data *)pdev->id_entry->driver_data; > +#endif > + if (!chip_data) { > + dev_err(&pdev->dev, "Error: Chip data is not valid\n"); > + return -EINVAL; > + } > + > + size = sizeof(struct tegra_dma); > + size += chip_data->nr_channels * sizeof(struct tegra_dma_channel); > + tdma = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); > + if (!tdma) { > + dev_err(&pdev->dev, "Error: memory allocation failed\n"); > + return -ENOMEM; > + } > + > + tdma->dev = &pdev->dev; > + memcpy(&tdma->chip_data, chip_data, sizeof(*chip_data)); > + platform_set_drvdata(pdev, tdma); > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + if (!res) { > + dev_err(&pdev->dev, "no mem resource for DMA\n"); > + return -EINVAL; > + } > + > + tdma->base_addr = devm_request_and_ioremap(&pdev->dev, res); > + if (!tdma->base_addr) { > + dev_err(&pdev->dev, > + "Cannot request memregion/iomap dma address\n"); > + return -EADDRNOTAVAIL; > + } > + > + /* Dma base register */ > + tdma_regmap_config.max_register = resource_size(res); > + tdma->regmap_dma = devm_regmap_init_mmio(&pdev->dev, tdma->base_addr, > + (const struct regmap_config *)&tdma_regmap_config); > + if (IS_ERR(tdma->regmap_dma)) { > + dev_err(&pdev->dev, "regmap init failed\n"); > + return PTR_ERR(tdma->regmap_dma); > + } > + > + /* Clock */ > + tdma->dma_clk = clk_get(&pdev->dev, "clk"); > + if (IS_ERR(tdma->dma_clk)) { > + dev_err(&pdev->dev, "Error: Missing controller clock"); > + return PTR_ERR(tdma->dma_clk); > + } > + > + spin_lock_init(&tdma->global_lock); > + > + INIT_LIST_HEAD(&tdma->dma_dev.channels); > + for (i = 0; i < chip_data->nr_channels; i++) { > + struct tegra_dma_channel *tdc = &tdma->channels[i]; > + char irq_name[30]; > + > + tdc->chan_base_offset = DMA_CHANNEL_BASE_ADDRESS_OFFSET + > + i * DMA_CHANNEL_REGISTER_SIZE; > + > + res = platform_get_resource(pdev, IORESOURCE_IRQ, i); > + if (!res) { > + ret = -EINVAL; > + dev_err(&pdev->dev, > + "Irq resource not found for channel %d\n", i); > + goto err_irq; > + } > + tdc->irq = res->start; > + snprintf(irq_name, sizeof(irq_name), "tegra_dma_chan.%d", i); > + ret = devm_request_irq(&pdev->dev, tdc->irq, > + tegra_dma_isr, 0, irq_name, tdc); > + if (ret) { > + dev_err(&pdev->dev, > + "request_irq failed for channel %d error %d\n", > + i, ret); > + goto err_irq; > + } > + > + tdc->dma_chan.device = &tdma->dma_dev; > + dma_cookie_init(&tdc->dma_chan); > + list_add_tail(&tdc->dma_chan.device_node, > + &tdma->dma_dev.channels); > + tdc->tdma = tdma; > + tdc->id = i; > + > + tasklet_init(&tdc->tasklet, > + tegra_dma_tasklet, (unsigned long)tdc); > + spin_lock_init(&tdc->lock); > + > + INIT_LIST_HEAD(&tdc->pending_sg_req); > + INIT_LIST_HEAD(&tdc->free_sg_req); > + INIT_LIST_HEAD(&tdc->alloc_ptr_list); > + INIT_LIST_HEAD(&tdc->free_dma_desc); > + INIT_LIST_HEAD(&tdc->wait_ack_dma_desc); > + INIT_LIST_HEAD(&tdc->cb_desc); > + } > + > + dma_cap_set(DMA_SLAVE, tdma->dma_dev.cap_mask); > + dma_cap_set(DMA_PRIVATE, tdma->dma_dev.cap_mask); > + tdma->dma_dev.dev = &pdev->dev; > + tdma->dma_dev.device_alloc_chan_resources = > + tegra_dma_alloc_chan_resources; > + tdma->dma_dev.device_free_chan_resources = > + tegra_dma_free_chan_resources; > + tdma->dma_dev.device_prep_slave_sg = tegra_dma_prep_slave_sg; > + tdma->dma_dev.device_prep_dma_cyclic = tegra_dma_prep_dma_cyclic; > + tdma->dma_dev.device_control = tegra_dma_device_control; > + tdma->dma_dev.device_tx_status = tegra_dma_tx_status; > + tdma->dma_dev.device_issue_pending = tegra_dma_issue_pending; > + > + ret = dma_async_device_register(&tdma->dma_dev); > + if (ret < 0) { > + dev_err(&pdev->dev, > + "Error in registering Tegra APB DMA driver %d\n", ret); > + goto err_irq; > + } > + dev_info(&pdev->dev, "Tegra APB DMA Controller, %d channels\n", > + chip_data->nr_channels); > + pm_runtime_enable(&pdev->dev); > + pm_runtime_get_sync(&pdev->dev); > + > + /* Reset dma controller */ > + tegra_periph_reset_assert(tdma->dma_clk); > + tegra_periph_reset_deassert(tdma->dma_clk); > + > + /* Enable global dma registers */ > + tdma_write(tdma, APB_DMA_GEN, GEN_ENABLE); > + tdma_write(tdma, APB_DMA_CNTRL, 0); > + tdma_write(tdma, APB_DMA_IRQ_MASK_SET, 0xFFFFFFFFul); > + return 0; > + > +err_irq: > + while (--i >= 0) { > + struct tegra_dma_channel *tdc = &tdma->channels[i]; > + tasklet_kill(&tdc->tasklet); > + } > + > + pm_runtime_disable(&pdev->dev); > + clk_put(tdma->dma_clk); > + return ret; > +} > + > +static int __exit tegra_dma_remove(struct platform_device *pdev) > +{ > + struct tegra_dma *tdma = platform_get_drvdata(pdev); > + int i; > + struct tegra_dma_channel *tdc; > + > + dma_async_device_unregister(&tdma->dma_dev); > + > + for (i = 0; i < tdma->chip_data.nr_channels; ++i) { > + tdc = &tdma->channels[i]; > + tasklet_kill(&tdc->tasklet); > + } > + > + pm_runtime_disable(&pdev->dev); > + clk_put(tdma->dma_clk); > + > + return 0; > +} > + > +static int tegra_dma_runtime_idle(struct device *dev) > +{ > + struct platform_device *pdev = to_platform_device(dev); > + struct tegra_dma *tdma = platform_get_drvdata(pdev); > + > + regcache_cache_only(tdma->regmap_dma, true); > + clk_disable(tdma->dma_clk); > + return 0; > +} > + > +static int tegra_dma_runtime_resume(struct device *dev) > +{ > + struct platform_device *pdev = to_platform_device(dev); > + struct tegra_dma *tdma = platform_get_drvdata(pdev); > + clk_enable(tdma->dma_clk); > + regcache_cache_only(tdma->regmap_dma, false); > + return 0; > +} > + > +static int tegra_dma_suspend_noirq(struct device *dev) > +{ > + tegra_dma_runtime_idle(dev); > + return 0; > +} > + > +static int tegra_dma_resume_noirq(struct device *dev) > +{ > + struct platform_device *pdev = to_platform_device(dev); > + struct tegra_dma *tdma = platform_get_drvdata(pdev); > + > + tegra_dma_runtime_resume(dev); > + > + /* > + * After resume, dma register will not be sync with the cached value. > + * Making sure they are in sync. > + */ > + regcache_mark_dirty(tdma->regmap_dma); > + regcache_sync(tdma->regmap_dma); > + return 0; > +} > + > +static const struct dev_pm_ops tegra_dma_dev_pm_ops = { > + .suspend_noirq = tegra_dma_suspend_noirq, > + .resume_noirq = tegra_dma_resume_noirq, > + .runtime_idle = tegra_dma_runtime_idle, > + .runtime_resume = tegra_dma_runtime_resume, > +}; > + > +static struct platform_driver tegra_dmac_driver = { > + .driver = { > + .name = "tegra-apbdma", > + .owner = THIS_MODULE, > + .pm = &tegra_dma_dev_pm_ops, > + .of_match_table = tegra_dma_of_match, > + }, > + .probe = tegra_dma_probe, > + .remove = __exit_p(tegra_dma_remove), > + .id_table = dma_id_table, > +}; > + > +static int __init tegra_dmac_init(void) > +{ > + return platform_driver_register(&tegra_dmac_driver); > +} > +arch_initcall_sync(tegra_dmac_init); > + > +static void __exit tegra_dmac_exit(void) > +{ > + platform_driver_unregister(&tegra_dmac_driver); > +} > +module_exit(tegra_dmac_exit); > + > +MODULE_DESCRIPTION("NVIDIA Tegra DMA Controller driver"); > +MODULE_AUTHOR("Laxman Dewangan <ldewangan@xxxxxxxxxx>"); > +MODULE_LICENSE("GPL v2"); > +MODULE_ALIAS("platform:tegra-apbdma"); > diff --git a/include/linux/tegra_dma.h b/include/linux/tegra_dma.h > new file mode 100644 > index 0000000..e94aac3 > --- /dev/null > +++ b/include/linux/tegra_dma.h > @@ -0,0 +1,95 @@ > +/* > + * Dma driver for Nvidia's Tegra dma controller. > + * > + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. > + * > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + * > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + */ > + > +#ifndef LINUX_TEGRA_DMA_H > +#define LINUX_TEGRA_DMA_H > + > +/* > + * tegra_dma_burst_size: Burst size of dma. > + * @TEGRA_DMA_AUTO: Based on transfer size, select the burst size. > + * If it is multple of 32 bytes then burst size will be 32 bytes else > + * If it is multiple of 16 bytes then burst size will be 16 bytes else > + * If it is multiple of 4 bytes then burst size will be 4 bytes. > + * @TEGRA_DMA_BURST_1: Burst size is 1 word/4 bytes. > + * @TEGRA_DMA_BURST_4: Burst size is 4 word/16 bytes. > + * @TEGRA_DMA_BURST_8: Burst size is 8 words/32 bytes. > + */ > +enum tegra_dma_burst_size { > + TEGRA_DMA_AUTO, > + TEGRA_DMA_BURST_1, > + TEGRA_DMA_BURST_4, > + TEGRA_DMA_BURST_8, > +}; why should this be global, clinet should pass them as defined in dmaengine.h > + > +/* Dma slave requestor */ > +enum tegra_dma_requestor { > + TEGRA_DMA_REQ_SEL_CNTR, > + TEGRA_DMA_REQ_SEL_I2S_2, > + TEGRA_DMA_REQ_SEL_APBIF_CH0 = TEGRA_DMA_REQ_SEL_I2S_2, > + TEGRA_DMA_REQ_SEL_I2S_1, > + TEGRA_DMA_REQ_SEL_APBIF_CH1 = TEGRA_DMA_REQ_SEL_I2S_1, > + TEGRA_DMA_REQ_SEL_SPD_I, > + TEGRA_DMA_REQ_SEL_APBIF_CH2 = TEGRA_DMA_REQ_SEL_SPD_I, > + TEGRA_DMA_REQ_SEL_UI_I, > + TEGRA_DMA_REQ_SEL_APBIF_CH3 = TEGRA_DMA_REQ_SEL_UI_I, > + TEGRA_DMA_REQ_SEL_MIPI, > + TEGRA_DMA_REQ_SEL_I2S2_2, > + TEGRA_DMA_REQ_SEL_I2S2_1, > + TEGRA_DMA_REQ_SEL_UARTA, > + TEGRA_DMA_REQ_SEL_UARTB, > + TEGRA_DMA_REQ_SEL_UARTC, > + TEGRA_DMA_REQ_SEL_SPI, > + TEGRA_DMA_REQ_SEL_DTV = TEGRA_DMA_REQ_SEL_SPI, > + TEGRA_DMA_REQ_SEL_AC97, > + TEGRA_DMA_REQ_SEL_ACMODEM, > + TEGRA_DMA_REQ_SEL_SL4B, > + TEGRA_DMA_REQ_SEL_SL2B1, > + TEGRA_DMA_REQ_SEL_SL2B2, > + TEGRA_DMA_REQ_SEL_SL2B3, > + TEGRA_DMA_REQ_SEL_SL2B4, > + TEGRA_DMA_REQ_SEL_UARTD, > + TEGRA_DMA_REQ_SEL_UARTE, > + TEGRA_DMA_REQ_SEL_I2C, > + TEGRA_DMA_REQ_SEL_I2C2, > + TEGRA_DMA_REQ_SEL_I2C3, > + TEGRA_DMA_REQ_SEL_DVC_I2C, > + TEGRA_DMA_REQ_SEL_OWR, > + TEGRA_DMA_REQ_SEL_I2C4, > + TEGRA_DMA_REQ_SEL_SL2B5, > + TEGRA_DMA_REQ_SEL_SL2B6, > + TEGRA_DMA_REQ_SEL_INVALID, > +}; > + > +/** > + * struct tegra_dma_slave - Controller-specific information about a slave > + * After requesting a dma channel by client through interface > + * dma_request_channel(), the chan->private should be initialized with > + * this structure. > + * Once the chan->private is got initialized with proper client data, > + * client need to call dmaengine_slave_config() to configure dma channel. > + * > + * @dma_dev: required DMA master client device. > + * @dm_req_id: Peripheral dma requestor ID. > + */ > +struct tegra_dma_slave { > + struct device *client_dev; > + enum tegra_dma_requestor dma_req_id; > + enum tegra_dma_burst_size burst_size; pls remove > +}; > + > +#endif /* LINUX_TEGRA_DMA_H */ Please also update the driver to use the cookie helpers in drivers/dma/dmaengine.h -- ~Vinod -- To unsubscribe from this list: send the line "unsubscribe linux-tegra" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html