From: Charlie Paul <cpaul.windri...@gmail.com> Changes to the LSI dma to support the axxia 5500 board.
Signed-off-by: Charlie Paul <cpaul.windri...@gmail.com> --- drivers/dma/Kconfig | 9 + drivers/dma/Makefile | 1 + drivers/dma/lsi-dma32.c | 879 +++++++++++++++++++++++++++++++++++++++++++++++ drivers/dma/lsi-dma32.h | 221 ++++++++++++ 4 files changed, 1110 insertions(+) create mode 100644 drivers/dma/lsi-dma32.c create mode 100644 drivers/dma/lsi-dma32.h diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 605b016..bc4561f 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -89,6 +89,15 @@ config AT_HDMAC help Support the Atmel AHB DMA controller. +config LSI_DMA + tristate "LSI General Purpose DMA support" + select DMA_ENGINE + select DMA_VIRTUAL_CHANNELS + select ASYNC_TX_ENABLE_CHANNEL_SWITCH + help + Enable support for the LSI General Purpose DMA controller found + on ACP34xx, AXM25xx and AXM55xx devices. + config FSL_DMA tristate "Freescale Elo series DMA support" depends on FSL_SOC diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index a029d0f4..a824a9f 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_INTEL_MID_DMAC) += intel_mid_dma.o obj-$(CONFIG_DMATEST) += dmatest.o obj-$(CONFIG_INTEL_IOATDMA) += ioat/ obj-$(CONFIG_INTEL_IOP_ADMA) += iop-adma.o +obj-$(CONFIG_LSI_DMA) += lsi-dma32.o obj-$(CONFIG_FSL_DMA) += fsldma.o obj-$(CONFIG_MPC512X_DMA) += mpc512x_dma.o obj-$(CONFIG_PPC_BESTCOMM) += bestcomm/ diff --git a/drivers/dma/lsi-dma32.c b/drivers/dma/lsi-dma32.c new file mode 100644 index 0000000..924d3af --- /dev/null +++ b/drivers/dma/lsi-dma32.c @@ -0,0 +1,879 @@ +/* + * Driver for the LSI DMA controller DMA-32. + * + * The driver is based on: + * + * lsi-dma32.c - + * lsi-dma.c - Copyright 2011 Mentor Graphics + * acp_gpdma.c - Copyright (c) 2011, Ericsson AB + * Niclas Bengtsson <niklas.x.bengts...@ericsson.com> + * Kerstin Jonsson <kerstin.jons...@ericsson.com> + * + * This 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. + * You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/export.h> +#include <linux/stat.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of.h> +#include <linux/delay.h> +#include <asm/page.h> +#include <linux/bitops.h> +#include <linux/atomic.h> +#include <linux/sizes.h> +#include "virt-dma.h" +#include "lsi-dma32.h" + +#ifdef DEBUG +#define engine_dbg(engine, fmt, ...) \ + do { \ + struct gpdma_engine *_e = engine; \ + (void)_e; \ + pr_debug("dma0: " fmt, ##__VA_ARGS__); \ + } while (0) + +#define ch_dbg(dmac, fmt, ...) \ + do { \ + struct gpdma_channel *_c = dmac; \ + (void)_c; \ + pr_debug("dma0ch%d: [%s] " fmt, \ + dmac->id, __func__, ##__VA_ARGS__); \ + } while (0) +#else +#define engine_dbg(engine, fmt, ...) do {} while (0) +#define ch_dbg(dmac, fmt, ...) do {} while (0) +#endif + +static unsigned int burst = 5; +module_param(burst, uint, 0644); +MODULE_PARM_DESC(burst, + "Preferred burst setting (0=SINGLE,3=INCR4,5=INCR8,7=INCR16)"); + +static void reset_channel(struct gpdma_channel *dmac) +{ + const int WAIT = 1024; + int i; + + /* Pause channel */ + writel(DMA_STATUS_CH_PAUS_WR_EN | DMA_STATUS_CH_PAUSE, + dmac->base+DMA_STATUS); + /* Memory Barrier */ + wmb(); + + /* Disable channel */ + writel(0, dmac->base+DMA_CHANNEL_CONFIG); + for (i = 0; readl(dmac->base+DMA_CHANNEL_CONFIG) && i < WAIT; i++) + cpu_relax(); + if (i == WAIT) + ch_dbg(dmac, "Failed to DISABLE channel\n"); + + /* Clear FIFO */ + writel(DMA_CONFIG_CLEAR_FIFO, dmac->base+DMA_CHANNEL_CONFIG); + for (i = 0; readl(dmac->base+DMA_CHANNEL_CONFIG) && i < WAIT; i++) + cpu_relax(); + if (i == WAIT) + ch_dbg(dmac, "Failed to clear FIFO\n"); +} + +static void soft_reset(struct gpdma_engine *engine) +{ + int i; + u32 cfg; + + /* Reset all channels */ + for (i = 0; i < engine->chip->num_channels; i++) + reset_channel(&engine->channel[i]); + + /* Reset GPDMA by writing Magic Number to reset reg */ + writel(GPDMA_MAGIC, engine->gbase + SOFT_RESET); + /* Memory Barrier */ + wmb(); + + cfg = (engine->pool.phys & 0xfff00000) | GEN_CONFIG_EXT_MEM; + + if (engine->chip->flags & LSIDMA_EDGE_INT) { + for (i = 0; i < engine->chip->num_channels; i++) + cfg |= GEN_CONFIG_INT_EDGE(i); + engine_dbg(engine, "Using edge-triggered interrupts\n"); + } + writel(cfg, engine->gbase + GEN_CONFIG); + engine_dbg(engine, "engine->desc.phys & 0xfff00000 == %llx\n", + (engine->pool.phys & 0xfff00000)); + + engine->ch_busy = 0; +} + +static int alloc_desc_table(struct gpdma_engine *engine) +{ + /* + * For controllers that doesn't support full descriptor addresses, all + * descriptors must be in the same 1 MB page, i.e address bits 31..20 + * must be the same for all descriptors. + */ + u32 order = 20 - PAGE_SHIFT; + int i; + + if (engine->chip->flags & LSIDMA_NEXT_FULL) { + /* + * Controller can do full descriptor addresses, then we need no + * special alignment on the descriptor block. + */ + order = get_order(GPDMA_MAX_DESCRIPTORS * + sizeof(struct gpdma_desc)); + } + + engine->pool.va = (struct gpdma_desc *) + __get_free_pages(GFP_KERNEL|GFP_DMA, order); + if (!engine->pool.va) + return -ENOMEM; + engine->pool.order = order; + engine->pool.phys = virt_to_phys(engine->pool.va); + engine_dbg(engine, "order=%d pa=%#llx va=%p\n", + engine->pool.order, engine->pool.phys, engine->pool.va); + + INIT_LIST_HEAD(&engine->free_list); + for (i = 0; i < GPDMA_MAX_DESCRIPTORS; i++) { + struct gpdma_desc *desc = &engine->pool.va[i]; + async_tx_ack(&desc->vdesc.tx); + desc->engine = engine; + list_add_tail(&desc->vdesc.node, &engine->free_list); + } + + return 0; +} + +static void free_desc_table(struct gpdma_engine *engine) +{ + if (engine->pool.va) + free_pages((unsigned long)engine->pool.va, engine->pool.order); +} + +static struct gpdma_desc *get_descriptor(struct gpdma_engine *engine) +{ + unsigned long flags; + struct gpdma_desc *new = NULL, *desc, *tmp; + + spin_lock_irqsave(&engine->lock, flags); + list_for_each_entry_safe(desc, tmp, &engine->free_list, vdesc.node) { + if (async_tx_test_ack(&desc->vdesc.tx)) { + list_del(&desc->vdesc.node); + new = desc; + new->chain = NULL; + break; + } + } + spin_unlock_irqrestore(&engine->lock, flags); + + return new; +} + +/** + * init_descriptor - Fill out all descriptor fields + */ +static void init_descriptor(struct gpdma_desc *desc, + dma_addr_t src, u32 src_acc, + dma_addr_t dst, u32 dst_acc, + size_t len) +{ + u32 src_count = len >> src_acc; + u32 dst_count = len >> dst_acc; + u32 rot_len = (2 * (1 << src_acc)) - 1; + + BUG_ON(src_count * (1<<src_acc) != len); + BUG_ON(dst_count * (1<<dst_acc) != len); + + desc->src = src; + desc->dst = dst; + + desc->hw.src_x_ctr = cpu_to_le16(src_count - 1); + desc->hw.src_y_ctr = 0; + desc->hw.src_x_mod = cpu_to_le32(1 << src_acc); + desc->hw.src_y_mod = 0; + desc->hw.src_addr = cpu_to_le32(src & 0xffffffff); + desc->hw.src_data_mask = ~0; + desc->hw.src_access = cpu_to_le16((rot_len << 6) | + (src_acc << 3) | + (burst & 7)); + desc->hw.dst_access = cpu_to_le16((dst_acc << 3) | + (burst & 7)); + desc->hw.ch_config = cpu_to_le32(DMA_CONFIG_ONE_SHOT(1)); + desc->hw.next_ptr = 0; + desc->hw.dst_x_ctr = cpu_to_le16(dst_count - 1); + desc->hw.dst_y_ctr = 0; + desc->hw.dst_x_mod = cpu_to_le32(1 << dst_acc); + desc->hw.dst_y_mod = 0; + desc->hw.dst_addr = cpu_to_le32(dst & 0xffffffff); +} + +static phys_addr_t desc_to_paddr(const struct gpdma_channel *dmac, + const struct gpdma_desc *desc) +{ + phys_addr_t paddr = virt_to_phys(&desc->hw); + WARN_ON(paddr & 0xf); + if (dmac->engine->chip->flags & LSIDMA_NEXT_FULL) + paddr |= 0x8; + else + paddr &= 0xfffff; + + return paddr; +} + +static void free_descriptor(struct virt_dma_desc *vd) +{ + struct gpdma_desc *desc = to_gpdma_desc(vd); + struct gpdma_engine *engine = desc->engine; + unsigned long flags; + + BUG_ON(desc == NULL); + + spin_lock_irqsave(&engine->lock, flags); + while (desc) { + list_add_tail(&desc->vdesc.node, &engine->free_list); + desc = desc->chain; + } + spin_unlock_irqrestore(&engine->lock, flags); +} + +static int segment_match(struct gpdma_engine *engine, struct gpdma_desc *desc) +{ + unsigned int gpreg_dma = readl(engine->gpreg); + unsigned int seg_src = (gpreg_dma >> 0) & 0x3f; + unsigned int seg_dst = (gpreg_dma >> 8) & 0x3f; + int rVal; + + rVal = (seg_src == ((desc->src >> 32) & 0x3f) && + seg_dst == ((desc->dst >> 32) & 0x3f)); + return rVal; +} + +static void gpdma_start(struct gpdma_channel *dmac) +{ + struct virt_dma_desc *vdesc; + struct gpdma_desc *desc; + phys_addr_t paddr; + + vdesc = vchan_next_desc(&dmac->vc); + if (!vdesc) { + clear_bit(dmac->id, &dmac->engine->ch_busy); + dmac->active = NULL; + return; + } + + /* Remove from list and mark as active */ + list_del(&vdesc->node); + desc = to_gpdma_desc(vdesc); + dmac->active = desc; + + if (!(dmac->engine->chip->flags & LSIDMA_SEG_REGS)) { + /* + * No segment registers -> descriptor address bits must match + * running descriptor on any other channel. + */ + if (dmac->engine->ch_busy && !segment_match(dmac->engine, desc)) + return; + } + + /* Physical address of descriptor to load */ + paddr = desc_to_paddr(dmac, desc); + writel((u32)paddr, dmac->base + DMA_NXT_DESCR); + + if (dmac->engine->chip->flags & LSIDMA_SEG_REGS) { + /* Segment bits [39..32] of descriptor, src and dst addresses */ + writel(paddr >> 32, dmac->base + DMA_DESCR_ADDR_SEG); + writel(desc->src >> 32, dmac->base + DMA_SRC_ADDR_SEG); + writel(desc->dst >> 32, dmac->base + DMA_DST_ADDR_SEG); + } else { + unsigned int seg_src = (desc->src >> 32) & 0x3f; + unsigned int seg_dst = (desc->dst >> 32) & 0x3f; + writel((seg_dst << 8) | seg_src, dmac->engine->gpreg); + } + /* Memory Barrier */ + wmb(); + writel(DMA_CONFIG_DSC_LOAD, dmac->base + DMA_CHANNEL_CONFIG); + set_bit(dmac->id, &dmac->engine->ch_busy); +} + +static irqreturn_t gpdma_isr_err(int irqno, void *_engine) +{ + struct gpdma_engine *engine = _engine; + u32 status = readl(engine->gbase + GEN_STAT); + u32 ch = (status & GEN_STAT_CH0_ERROR) ? 0 : 1; + struct gpdma_channel *dmac = &engine->channel[ch]; + + if (0 == (status & (GEN_STAT_CH0_ERROR | GEN_STAT_CH1_ERROR))) + return IRQ_NONE; + + /* Read the channel status bits and dump the error */ + status = readl(dmac->base + DMA_STATUS); + pr_err("dma: channel%d error %08x\n", dmac->id, status); + /* Clear the error indication */ + writel(DMA_STATUS_ERROR, dmac->base+DMA_STATUS); + + return IRQ_HANDLED; +} + +static irqreturn_t gpdma_isr(int irqno, void *_dmac) +{ + struct gpdma_channel *dmac = _dmac; + struct gpdma_desc *desc = dmac->active; + u32 status; + u32 error; + + status = readl(dmac->base+DMA_STATUS); + error = status & DMA_STATUS_ERROR; + writel(DMA_STATUS_CLEAR, dmac->base+DMA_STATUS); + + ch_dbg(dmac, "irq%u channel status %08x, error %08x\n", + irqno, status, error); + + WARN_ON((status & DMA_STATUS_CH_ACTIVE) != 0); + + if (error) { + if (error & DMA_STATUS_UNALIGNED_ERR) { + dev_warn(dmac->engine->dev, + "Unaligned transaction on ch%d (status=%#x)\n", + dmac->id, status); + reset_channel(dmac); + } else { + dev_warn(dmac->engine->dev, + "DMA transaction error on ch%d (status=%#x)\n", + dmac->id, status); + } + } + + BUG_ON(desc == NULL); + + spin_lock(&dmac->vc.lock); + vchan_cookie_complete(&desc->vdesc); + dmac->active = NULL; + if (vchan_next_desc(&dmac->vc)) { + gpdma_start(dmac); + } else { + /* Stop channel */ + writel(0, dmac->base + DMA_CHANNEL_CONFIG); + writel(DMA_CONFIG_CLEAR_FIFO, dmac->base + DMA_CHANNEL_CONFIG); + clear_bit(dmac->id, &dmac->engine->ch_busy); + } + spin_unlock(&dmac->vc.lock); + + return IRQ_HANDLED; +} + +static void flush_channel(struct gpdma_channel *dmac) +{ + unsigned long flags; + LIST_HEAD(head); + + spin_lock_irqsave(&dmac->vc.lock, flags); + reset_channel(dmac); + if (dmac->active) { + free_descriptor(&dmac->active->vdesc); + dmac->active = NULL; + } + vchan_get_all_descriptors(&dmac->vc, &head); + spin_unlock_irqrestore(&dmac->vc.lock, flags); + vchan_dma_desc_free_list(&dmac->vc, &head); +} + +/* + * Perform soft reset procedure on DMA Engine. Needed occasionally to work + * around nasty bug ACP3400 sRIO HW. + */ +static ssize_t __ref +reset_engine(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gpdma_engine *engine = dev_get_drvdata(dev); + int i; + + if (!engine) + return -EINVAL; + + /* Disable interrupts and tasklet and acquire each channel lock */ + for (i = 0; i < engine->chip->num_channels; i++) { + struct gpdma_channel *dmac = &engine->channel[i]; + disable_irq(dmac->irq); + spin_lock(&dmac->vc.lock); + tasklet_disable(&dmac->vc.task); + } + + soft_reset(engine); + + for (i = 0; i < engine->chip->num_channels; i++) { + struct gpdma_channel *dmac = &engine->channel[i]; + tasklet_enable(&dmac->vc.task); + enable_irq(dmac->irq); + /* Restart any active jobs */ + if (dmac->active) { + struct gpdma_desc *active = dmac->active; + dmac->active = NULL; + list_add(&active->vdesc.node, &dmac->vc.desc_submitted); + if (vchan_issue_pending(&dmac->vc)) + gpdma_start(dmac); + } + spin_unlock(&dmac->vc.lock); + } + + return count; +} +static DEVICE_ATTR(soft_reset, S_IWUSR, NULL, reset_engine); + +/* + *=========================================================================== + * + * DMA DEVICE INTERFACE + * + *=========================================================================== + * + */ + +/** + * gpdma_alloc_chan_resources - Allocate resources and return the number of + * allocated descriptors. + * + */ +static int gpdma_alloc_chan_resources(struct dma_chan *chan) +{ + struct gpdma_channel *dmac = to_gpdma_chan(chan); + + (void) dmac; + return 1; +} + +/** + * gpdma_free_chan_resources - Release DMA channel's resources. + * + */ +static void gpdma_free_chan_resources(struct dma_chan *chan) +{ + struct gpdma_channel *dmac = to_gpdma_chan(chan); + + (void) dmac; +} + +/** + * gpdma_prep_sg - Prepares a transfer using sg lists. + * + */ +static struct dma_async_tx_descriptor * +gpdma_prep_sg(struct dma_chan *chan, + struct scatterlist *dst_sg, unsigned int dst_nents, + struct scatterlist *src_sg, unsigned int src_nents, + unsigned long flags) +{ + struct gpdma_channel *dmac = to_gpdma_chan(chan); + struct gpdma_desc *first = NULL, *prev = NULL, *new; + size_t dst_avail, src_avail; + dma_addr_t dst, src; + u32 src_acc, dst_acc; + size_t len; + + if (dst_nents == 0 || src_nents == 0) + return NULL; + + if (dst_sg == NULL || src_sg == NULL) + return NULL; + + dst_avail = sg_dma_len(dst_sg); + src_avail = sg_dma_len(src_sg); + + /* Loop until we run out of entries... */ + for (;;) { + /* Descriptor count is limited to 64K */ + len = min_t(size_t, src_avail, dst_avail); + len = min_t(size_t, len, (size_t)SZ_64K); + + if (len > 0) { + dst = sg_dma_address(dst_sg) + + sg_dma_len(dst_sg) - dst_avail; + src = sg_dma_address(src_sg) + + sg_dma_len(src_sg) - src_avail; + + src_acc = min(ffs((u32)src | len) - 1, 4); + dst_acc = min(ffs((u32)dst | len) - 1, 4); + + new = get_descriptor(dmac->engine); + if (!new) { + ch_dbg(dmac, "ERROR: No descriptor\n"); + goto fail; + } + + init_descriptor(new, src, src_acc, dst, dst_acc, len); + + /* Link descriptors together */ + if (!first) { + first = new; + } else { + prev->hw.next_ptr = desc_to_paddr(dmac, new); + prev->chain = new; + } + prev = new; + + /* update metadata */ + dst_avail -= len; + src_avail -= len; + } + + /* dst: Advance to next sg-entry */ + if (dst_avail == 0) { + /* no more entries: we're done */ + if (dst_nents == 0) + break; + /* fetch the next entry: if there are no more: done */ + dst_sg = sg_next(dst_sg); + if (dst_sg == NULL) + break; + + dst_nents--; + dst_avail = sg_dma_len(dst_sg); + } + + /* src: Advance to next sg-entry */ + if (src_avail == 0) { + /* no more entries: we're done */ + if (src_nents == 0) + break; + /* fetch the next entry: if there are no more: done */ + src_sg = sg_next(src_sg); + if (src_sg == NULL) + break; + + src_nents--; + src_avail = sg_dma_len(src_sg); + } + } + + /* Interrupt on last descriptor in chain */ + prev->hw.ch_config |= cpu_to_le32(DMA_CONFIG_END); + + return vchan_tx_prep(&dmac->vc, &first->vdesc, flags); + +fail: + if (first) + free_descriptor(&first->vdesc); + return NULL; +} + +/** + * gpdma_prep_memcpy - Prepares a memcpy operation. + * + */ +static struct dma_async_tx_descriptor * +gpdma_prep_memcpy(struct dma_chan *chan, + dma_addr_t dst, + dma_addr_t src, + size_t size, + unsigned long dma_flags) +{ + struct gpdma_channel *dmac = to_gpdma_chan(chan); + struct gpdma_desc *first = NULL, *prev = NULL, *new; + u32 src_acc, dst_acc; + size_t len; + + if (size == 0) + return NULL; + + do { + new = get_descriptor(dmac->engine); + if (new == NULL) { + ch_dbg(dmac, "ERROR: No descriptor\n"); + goto fail; + } + + len = min_t(size_t, size, (size_t)SZ_64K); + + /* Maximize access width based on address and length alignmet */ + src_acc = min(ffs((u32)src | len) - 1, 4); + dst_acc = min(ffs((u32)dst | len) - 1, 4); + + init_descriptor(new, src, src_acc, dst, dst_acc, len); + + if (!first) { + first = new; + } else { + prev->hw.next_ptr = desc_to_paddr(dmac, new); + prev->chain = new; + } + prev = new; + + size -= len; + src += len; + dst += len; + + } while (size > 0); + + prev->hw.ch_config |= cpu_to_le32(DMA_CONFIG_END); + + return vchan_tx_prep(&dmac->vc, &first->vdesc, DMA_CTRL_ACK); + +fail: + if (first) + free_descriptor(&first->vdesc); + return NULL; +} + +/** + * gpdma_issue_pending - Push pending transactions to hardware. + * + */ +static void gpdma_issue_pending(struct dma_chan *chan) +{ + struct gpdma_channel *dmac = to_gpdma_chan(chan); + unsigned long flags; + + spin_lock_irqsave(&dmac->vc.lock, flags); + if (vchan_issue_pending(&dmac->vc) && !dmac->active) + gpdma_start(dmac); + spin_unlock_irqrestore(&dmac->vc.lock, flags); +} + +/** + * gpdma_tx_status - Poll for transaction completion, the optional txstate + * parameter can be supplied with a pointer to get a struct with auxiliary + * transfer status information, otherwise the call will just return a simple + * status code. + */ +static enum dma_status gpdma_tx_status(struct dma_chan *chan, + dma_cookie_t cookie, + struct dma_tx_state *txstate) +{ + return dma_cookie_status(chan, cookie, txstate); +} + + +/** + * gpdma_device_control - Manipulate all pending operations on a channel, + * returns zero or error code. + * + */ +static int gpdma_device_control(struct dma_chan *chan, + enum dma_ctrl_cmd cmd, + unsigned long arg) +{ + struct gpdma_channel *dmac = to_gpdma_chan(chan); + + if (!dmac) + return -EINVAL; + + switch (cmd) { + case DMA_TERMINATE_ALL: + flush_channel(dmac); + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int setup_channel(struct gpdma_channel *dmac, struct device_node *child) +{ + struct gpdma_engine *engine = dmac->engine; + int rc; + + dmac->base = engine->iobase + dmac->id * engine->chip->chregs_offset; + dev_dbg(engine->dev, "channel%d base @ %p\n", dmac->id, dmac->base); + + /* Find the IRQ line, if it exists in the device tree */ + dmac->irq = irq_of_parse_and_map(child, 0); + dev_dbg(engine->dev, "channel %d, irq %d\n", dmac->id, dmac->irq); + rc = devm_request_irq(engine->dev, dmac->irq, gpdma_isr, 0, + "lsi-dma", dmac); + if (rc) { + dev_err(engine->dev, "failed to request_irq, error = %d\n", rc); + return rc; + } + /* Initialize the virt-channel */ + dmac->vc.desc_free = free_descriptor; + vchan_init(&dmac->vc, &engine->dma_device); + + return 0; +} + +static struct lsidma_hw lsi_dma32 = { + .num_channels = 2, + .chregs_offset = 0x80, + .genregs_offset = 0xF00, + .flags = (LSIDMA_NEXT_FULL | + LSIDMA_SEG_REGS) +}; + +static struct lsidma_hw lsi_dma31 = { + .num_channels = 4, + .chregs_offset = 0x40, + .genregs_offset = 0x400, + .flags = 0 +}; + +static const struct of_device_id gpdma_of_ids[] = { + { + .compatible = "lsi,dma32", + .data = &lsi_dma32 + }, + { + .compatible = "lsi,dma31", + .data = &lsi_dma31 + }, + { + .compatible = "gp-dma,acp-dma", + .data = &lsi_dma31 + }, + { + .compatible = "gp-dma,acp-gpdma", + .data = &lsi_dma31 + }, + { } +}; + +static int gpdma_of_probe(struct platform_device *op) +{ + struct gpdma_engine *engine; + struct dma_device *dma; + struct device_node *child; + struct resource *res; + const struct of_device_id *match; + int rc = -ENOMEM; + int id = 0; + + match = of_match_device(gpdma_of_ids, &op->dev); + if (!match) + return -EINVAL; + + engine = devm_kzalloc(&op->dev, sizeof(*engine), GFP_KERNEL); + if (!engine) + return -ENOMEM; + + spin_lock_init(&engine->lock); + engine->dev = &op->dev; + engine->chip = (struct lsidma_hw *)match->data; + + /* Initialize dma_device struct */ + dma = &engine->dma_device; + dma->dev = &op->dev; + dma_cap_zero(dma->cap_mask); + dma_cap_set(DMA_MEMCPY, dma->cap_mask); + dma_cap_set(DMA_SG, dma->cap_mask); + dma->copy_align = 2; + dma->chancnt = engine->chip->num_channels; + dma->device_alloc_chan_resources = gpdma_alloc_chan_resources; + dma->device_free_chan_resources = gpdma_free_chan_resources; + dma->device_tx_status = gpdma_tx_status; + dma->device_prep_dma_memcpy = gpdma_prep_memcpy; + dma->device_prep_dma_sg = gpdma_prep_sg; + dma->device_issue_pending = gpdma_issue_pending; + dma->device_control = gpdma_device_control; + INIT_LIST_HEAD(&dma->channels); + + /* Map device I/O memory + */ + res = platform_get_resource(op, IORESOURCE_MEM, 0); + engine->iobase = devm_ioremap_resource(&op->dev, res); + if (IS_ERR(engine->iobase)) + return PTR_ERR(engine->iobase); + dev_dbg(&op->dev, "mapped base @ %p\n", engine->iobase); + + res = platform_get_resource(op, IORESOURCE_MEM, 1); + if (res) { + engine->gpreg = devm_ioremap_nocache(&op->dev, + res->start, + resource_size(res)); + if (IS_ERR(engine->gpreg)) + return PTR_ERR(engine->gpreg); + dev_dbg(&op->dev, "mapped gpreg @ %p\n", engine->gpreg); + } + + engine->err_irq = platform_get_irq(op, 1); + if (engine->err_irq) { + rc = devm_request_irq(&op->dev, engine->err_irq, + gpdma_isr_err, 0, "lsi-dma-err", engine); + if (rc) { + dev_err(engine->dev, "failed to request irq%d\n", + engine->err_irq); + engine->err_irq = 0; + } + } + + /* General registes at device specific offset */ + engine->gbase = engine->iobase + engine->chip->genregs_offset; + + rc = alloc_desc_table(engine); + if (rc) + return rc; + + /* Setup channels */ + for_each_child_of_node(op->dev.of_node, child) { + struct gpdma_channel *dmac = &engine->channel[id]; + + if (id >= engine->chip->num_channels) { + dev_dbg(engine->dev, "Too many channels (%d)\n", id); + return -ENODEV; + } + + dmac->id = id; + dmac->engine = engine; + rc = setup_channel(dmac, child); + if (rc) + return rc; + ++id; + } + + soft_reset(engine); + + rc = dma_async_device_register(&engine->dma_device); + if (rc) { + dev_err(engine->dev, "unable to register\n"); + return rc; + } + + device_create_file(&op->dev, &dev_attr_soft_reset); + dev_set_drvdata(&op->dev, engine); + + return 0; +} + +static int gpdma_of_remove(struct platform_device *op) +{ + struct gpdma_engine *engine = dev_get_drvdata(&op->dev); + + dev_dbg(&op->dev, "%s\n", __func__); + + device_remove_file(&op->dev, &dev_attr_soft_reset); + dma_async_device_unregister(&engine->dma_device); + free_desc_table(engine); + dev_set_drvdata(&op->dev, NULL); + + return 0; +} + +static struct platform_driver gpdma_of_driver = { + .driver = { + .name = "lsi-dma32", + .owner = THIS_MODULE, + .of_match_table = gpdma_of_ids, + }, + .probe = gpdma_of_probe, + .remove = gpdma_of_remove, +}; + +module_platform_driver(gpdma_of_driver); + +MODULE_DESCRIPTION("LSI DMA driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/dma/lsi-dma32.h b/drivers/dma/lsi-dma32.h new file mode 100644 index 0000000..c5a2701 --- /dev/null +++ b/drivers/dma/lsi-dma32.h @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2012 Ericsson AB. All rights reserved. + * + * Author: + * Kerstin Jonsson <kerstin.jons...@ericsson.com>, Feb 2012 + * + * This 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. + * + */ +#ifndef __LSI_DMA32_H +#define __LSI_DMA32_H + +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/dma-mapping.h> +#include <linux/device.h> +#include <linux/spinlock.h> +#include <linux/types.h> +#include "virt-dma.h" + +#define MAX_GPDMA_CHANNELS 4 +#define GPDMA_MAX_DESCRIPTORS 128 +#define GPDMA_MAGIC 0xABCD1234UL + +#define DMA_X_SRC_COUNT 0x00 +#define DMA_Y_SRC_COUNT 0x04 +#define DMA_X_MODIF_SRC 0x08 +#define DMA_Y_MODIF_SRC 0x0c +#define DMA_SRC_CUR_ADDR 0x10 +#define DMA_SRC_ACCESS 0x14 +#define DMA_SRC_ACCESS_BURST_TYPE (1<<15) +#define DMA_SRC_ACCESS_TAIL_LENGTH(x) (((x) & 0xF) << 11) +#define DMA_SRC_ACCESS_ROTATOR_LENGTH(x) (((x) & 1F) << 6) +#define DMA_SRC_ACCESS_SRC_SIZE(x) (((x) & 7) << 3) +#define DMA_SRC_ACCESS_SRC_BURST(x) (((x) & 7) << 0) +#define DMA_SRC_MASK 0x18 +#define DMA_X_DST_COUNT 0x1c +#define DMA_Y_DST_COUNT 0x20 +#define DMA_X_MODIF_DST 0x24 +#define DMA_Y_MODIF_DST 0x28 +#define DMA_DST_CUR_ADDR 0x2C +#define DMA_DST_ACCESS 0x30 +#define DMA_DST_ACCESS_DST_SIZE(x) (((x) & 7) << 3) +#define DMA_DST_ACCESS_DST_BURST(x) (((x) & 7) << 0) +#define DMA_NXT_DESCR 0x34 +#define DMA_CHANNEL_CONFIG 0x38 +#define DMA_CONFIG_DST_SPACE(x) (((x) & 7) << 26) +#define DMA_CONFIG_SRC_SPACE(x) (((x) & 7) << 23) +#define DMA_CONFIG_PRIORITY_ROW (1<<21) +#define DMA_CONFIG_PRIORITY (1<<20) +#define DMA_CONFIG_LAST_BLOCK (1<<15) +#define DMA_CONFIG_CLEAR_FIFO (1<<14) +#define DMA_CONFIG_START_MEM_LOAD (1<<13) +#define DMA_CONFIG_STOP_DST_EOB (1<<11) +#define DMA_CONFIG_FULL_DESCR_ADDR (1<<8) +#define DMA_CONFIG_INT_DST_EOT (1<<7) +#define DMA_CONFIG_INT_DST_EOB (1<<6) +#define DMA_CONFIG_WAIT_FOR_TASK_CNT2 (1<<5) +#define DMA_CONFIG_TASK_CNT2_RESET (1<<4) +#define DMA_CONFIG_WAIT_FOR_TASK_CNT1 (1<<3) +#define DMA_CONFIG_TASK_CNT1_RESET (1<<2) +#define DMA_CONFIG_TX_EN (1<<1) +#define DMA_CONFIG_CHAN_EN (1<<0) +#define DMA_STATUS 0x3C +#define DMA_STATUS_WAIT_TASK_CNT2 (1<<20) +#define DMA_STATUS_TASK_CNT2_OVERFLOW (1<<19) +#define DMA_STATUS_WAIT_TASK_CNT1 (1<<18) +#define DMA_STATUS_TASK_CNT1_OVERFLOW (1<<17) +#define DMA_STATUS_CH_PAUS_WR_EN (1<<16) +#define DMA_STATUS_ERR_ACC_DESCR (1<<14) +#define DMA_STATUS_ERR_ACC_DST (1<<13) +#define DMA_STATUS_ERR_ACC_SRC (1<<12) +#define DMA_STATUS_ERR_OVERFLOW (1<<9) +#define DMA_STATUS_ERR_UNDERFLOW (1<<8) +#define DMA_STATUS_CH_PAUSE (1<<7) +#define DMA_STATUS_CH_WAITING (1<<5) +#define DMA_STATUS_CH_ACTIVE (1<<4) +#define DMA_STATUS_TR_COMPLETE (1<<3) +#define DMA_STATUS_BLK_COMPLETE (1<<2) +#define DMA_STATUS_UNALIGNED_READ (1<<1) +#define DMA_STATUS_UNALIGNED_WRITE (1<<0) +#define DMA_STATUS_UNALIGNED_ERR (DMA_STATUS_UNALIGNED_READ | \ + DMA_STATUS_UNALIGNED_WRITE) +#define DMA_TASK_CNT_1 0x40 +#define DMA_TASK_CNT_2 0x44 +#define DMA_MODE_CONFIG 0x48 +#define DMA_CURR_DESCR 0x4c +#define DMA_PREV_DESCR 0x50 +#define DMA_SRC_ADDR_SEG 0x54 +#define DMA_DST_ADDR_SEG 0x58 +#define DMA_DESCR_ADDR_SEG 0x5c + +#define DMA_STATUS_ERROR (DMA_STATUS_ERR_ACC_DESCR | \ + DMA_STATUS_ERR_ACC_DST | \ + DMA_STATUS_ERR_ACC_SRC | \ + DMA_STATUS_ERR_OVERFLOW | \ + DMA_STATUS_ERR_UNDERFLOW | \ + DMA_STATUS_UNALIGNED_ERR) + +#define DMA_STATUS_CLEAR (DMA_STATUS_CH_PAUS_WR_EN | \ + DMA_STATUS_TR_COMPLETE | \ + DMA_STATUS_BLK_COMPLETE) + +#define DMA_CONFIG_END (DMA_CONFIG_LAST_BLOCK | \ + DMA_CONFIG_INT_DST_EOT) + +#define DMA_CONFIG_ONE_SHOT(__ext) (DMA_CONFIG_DST_SPACE((__ext)) | \ + DMA_CONFIG_SRC_SPACE((__ext)) | \ + DMA_CONFIG_TX_EN | \ + DMA_CONFIG_CHAN_EN) + +#define DMA_CONFIG_DSC_LOAD (DMA_CONFIG_START_MEM_LOAD | \ + DMA_CONFIG_FULL_DESCR_ADDR | \ + DMA_CONFIG_CHAN_EN) + +#define GEN_STAT 0x0 +#define GEN_STAT_CH0_ACTIVE (1<<0) +#define GEN_STAT_CH1_ACTIVE (1<<2) +#define GEN_STAT_CH1_ACTIVE (1<<2) +#define GEN_STAT_CH0_ERROR (1<<16) +#define GEN_STAT_CH1_ERROR (1<<17) +#define GEN_CONFIG 0x4 +#define GEN_CONFIG_EXT_MEM (1<<19) +#define GEN_CONFIG_INT_EDGE(_ch) (1<<(_ch)) +#define SOFT_RESET 0x8 + +#define GPDMA_GEN_STAT(__p) ((__p)->gbase + GEN_STAT) +#define GPDMA_GEN_CONFIG(__p) ((__p)->gbase + GEN_CONFIG) +#define GPDMA_SOFT_RESET(__p) ((__p)->gbase + SOFT_RESET) + + +struct descriptor { + u16 src_x_ctr; + u16 src_y_ctr; + s32 src_x_mod; + s32 src_y_mod; + u32 src_addr; + u32 src_data_mask; + u16 src_access; + u16 dst_access; + u32 ch_config; + u32 next_ptr; + u16 dst_x_ctr; + u16 dst_y_ctr; + s32 dst_x_mod; + s32 dst_y_mod; + u32 dst_addr; +} __aligned(32); + +struct gpdma_engine; + +struct gpdma_desc { + struct descriptor hw; + struct gpdma_desc *chain; + dma_addr_t src; + dma_addr_t dst; + struct gpdma_engine *engine; + struct virt_dma_desc vdesc; +} __aligned(32); + +static struct gpdma_desc *to_gpdma_desc(struct virt_dma_desc *vdesc) +{ + return container_of(vdesc, struct gpdma_desc, vdesc); +} + +struct gpdma_channel { + /* Back reference to DMA engine */ + struct gpdma_engine *engine; + /* Channel registers */ + void __iomem *base; + /* Channel id */ + int id; + /* IRQ number as passed to request_irq() */ + int irq; + /* Currently running descriptor */ + struct gpdma_desc *active; + /* Channel parameters (DMA engine framework) */ + struct virt_dma_chan vc; +}; + +static inline struct gpdma_channel *to_gpdma_chan(struct dma_chan *chan) +{ + return container_of(chan, struct gpdma_channel, vc.chan); +} + +struct lsidma_hw { + unsigned int num_channels; + unsigned int chregs_offset; + unsigned int genregs_offset; + unsigned int flags; +#define LSIDMA_NEXT_FULL (1<<0) +#define LSIDMA_SEG_REGS (1<<1) +#define LSIDMA_EDGE_INT (1<<2) +}; + +struct gpdma_engine { + struct device *dev; + struct lsidma_hw *chip; + struct gpdma_channel channel[MAX_GPDMA_CHANNELS]; + /** Bit mask where bit[n] == 1 if channel busy */ + unsigned long ch_busy; + int err_irq; + void __iomem *iobase; + void __iomem *gbase; + void __iomem *gpreg; + spinlock_t lock; + struct list_head free_list; + struct { + u32 order; + dma_addr_t phys; + struct gpdma_desc *va; + } pool; + struct dma_device dma_device; +}; + +#define desc_to_engine(n) container_of(n, struct gpdma_engine, desc) + +#endif -- 1.7.9.5 -- _______________________________________________ linux-yocto mailing list linux-yocto@yoctoproject.org https://lists.yoctoproject.org/listinfo/linux-yocto