Add Milbeaut AXI DMA controller. This DMA controller has only capable of memory to memory transfer. Signed-off-by: Kazuhiro Kasai <kasai.kazuhiro@xxxxxxxxxxxxx> --- drivers/dma/Kconfig | 8 + drivers/dma/Makefile | 1 + drivers/dma/xdmac-milbeaut.c | 353 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 362 insertions(+) create mode 100644 drivers/dma/xdmac-milbeaut.c diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 0b1dfb5..733fe5f 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -612,6 +612,14 @@ config UNIPHIER_MDMAC UniPhier platform. This DMA controller is used as the external DMA engine of the SD/eMMC controllers of the LD4, Pro4, sLD8 SoCs. +config XDMAC_MILBEAUT + tristate "Milbeaut AXI DMA support" + depends on ARCH_MILBEAUT || COMPILE_TEST + select DMA_ENGINE + help + Support for Milbeaut AXI DMA controller driver. The DMA controller + has only memory to memory capability. + config XGENE_DMA tristate "APM X-Gene DMA support" depends on ARCH_XGENE || COMPILE_TEST diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index 6126e1c..4aab810 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -72,6 +72,7 @@ obj-$(CONFIG_TEGRA20_APB_DMA) += tegra20-apb-dma.o obj-$(CONFIG_TEGRA210_ADMA) += tegra210-adma.o obj-$(CONFIG_TIMB_DMA) += timb_dma.o obj-$(CONFIG_UNIPHIER_MDMAC) += uniphier-mdmac.o +obj-$(CONFIG_XDMAC_MILBEAUT) += xdmac-milbeaut.o obj-$(CONFIG_XGENE_DMA) += xgene-dma.o obj-$(CONFIG_ZX_DMA) += zx_dma.o obj-$(CONFIG_ST_FDMA) += st_fdma.o diff --git a/drivers/dma/xdmac-milbeaut.c b/drivers/dma/xdmac-milbeaut.c new file mode 100644 index 0000000..7035c61 --- /dev/null +++ b/drivers/dma/xdmac-milbeaut.c @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Socionext Inc. + */ + +#include <linux/bitfield.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of_dma.h> +#include <linux/platform_device.h> + +#include "dmaengine.h" + +/* global register */ +#define M10V_XDACS 0x00 + +/* channel local register */ +#define M10V_XDTBC 0x10 +#define M10V_XDSSA 0x14 +#define M10V_XDDSA 0x18 +#define M10V_XDSAC 0x1C +#define M10V_XDDAC 0x20 +#define M10V_XDDCC 0x24 +#define M10V_XDDES 0x28 +#define M10V_XDDPC 0x2C +#define M10V_XDDSD 0x30 + +#define M10V_XDACS_XE BIT(28) + +#define M10V_XDSAC_SBS GENMASK(17, 16) +#define M10V_XDSAC_SBL GENMASK(11, 8) + +#define M10V_XDDAC_DBS GENMASK(17, 16) +#define M10V_XDDAC_DBL GENMASK(11, 8) + +#define M10V_XDDES_CE BIT(28) +#define M10V_XDDES_SE BIT(24) +#define M10V_XDDES_SA BIT(15) +#define M10V_XDDES_TF GENMASK(23, 20) +#define M10V_XDDES_EI BIT(1) +#define M10V_XDDES_TI BIT(0) + +#define M10V_XDDSD_IS_MASK GENMASK(3, 0) +#define M10V_XDDSD_IS_NORMAL 0x8 + +#define M10V_XDMAC_BUSWIDTHS (BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \ + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \ + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) | \ + BIT(DMA_SLAVE_BUSWIDTH_8_BYTES)) + +#define M10V_XDMAC_CHAN_BASE(base, i) ((base) + (i) * 0x30) + +#define to_m10v_dma_chan(c) container_of((c), struct m10v_dma_chan, chan) + +struct m10v_dma_desc { + struct dma_async_tx_descriptor txd; + size_t len; + dma_addr_t src; + dma_addr_t dst; +}; + +struct m10v_dma_chan { + struct dma_chan chan; + struct m10v_dma_device *mdmac; + void __iomem *regs; + int irq; + struct m10v_dma_desc mdesc; + spinlock_t lock; +}; + +struct m10v_dma_device { + struct dma_device dmac; + void __iomem *regs; + unsigned int channels; + struct m10v_dma_chan mchan[0]; +}; + +static void m10v_xdmac_enable_dma(struct m10v_dma_device *mdmac) +{ + unsigned int val; + + val = readl(mdmac->regs + M10V_XDACS); + val &= ~M10V_XDACS_XE; + val |= FIELD_PREP(M10V_XDACS_XE, 1); + writel(val, mdmac->regs + M10V_XDACS); +} + +static void m10v_xdmac_disable_dma(struct m10v_dma_device *mdmac) +{ + unsigned int val; + + val = readl(mdmac->regs + M10V_XDACS); + val &= ~M10V_XDACS_XE; + val |= FIELD_PREP(M10V_XDACS_XE, 0); + writel(val, mdmac->regs + M10V_XDACS); +} + +static void m10v_xdmac_config_chan(struct m10v_dma_chan *mchan) +{ + u32 val; + + val = mchan->mdesc.len - 1; + writel(val, mchan->regs + M10V_XDTBC); + + val = mchan->mdesc.src; + writel(val, mchan->regs + M10V_XDSSA); + + val = mchan->mdesc.dst; + writel(val, mchan->regs + M10V_XDDSA); + + val = readl(mchan->regs + M10V_XDSAC); + val &= ~(M10V_XDSAC_SBS | M10V_XDSAC_SBL); + val |= FIELD_PREP(M10V_XDSAC_SBS, 0x3) | + FIELD_PREP(M10V_XDSAC_SBL, 0xf); + writel(val, mchan->regs + M10V_XDSAC); + + val = readl(mchan->regs + M10V_XDDAC); + val &= ~(M10V_XDDAC_DBS | M10V_XDDAC_DBL); + val |= FIELD_PREP(M10V_XDDAC_DBS, 0x3) | + FIELD_PREP(M10V_XDDAC_DBL, 0xf); + writel(val, mchan->regs + M10V_XDDAC); +} + +static void m10v_xdmac_enable_chan(struct m10v_dma_chan *mchan) +{ + u32 val; + + val = readl(mchan->regs + M10V_XDDES); + val &= ~(M10V_XDDES_CE | + M10V_XDDES_SE | + M10V_XDDES_TF | + M10V_XDDES_EI | + M10V_XDDES_TI); + val |= FIELD_PREP(M10V_XDDES_CE, 1) | + FIELD_PREP(M10V_XDDES_SE, 1) | + FIELD_PREP(M10V_XDDES_TF, 1) | + FIELD_PREP(M10V_XDDES_EI, 1) | + FIELD_PREP(M10V_XDDES_TI, 1); + writel(val, mchan->regs + M10V_XDDES); +} + +static void m10v_xdmac_disable_chan(struct m10v_dma_chan *mchan) +{ + u32 val; + + val = readl(mchan->regs + M10V_XDDES); + val &= ~M10V_XDDES_CE; + val |= FIELD_PREP(M10V_XDDES_CE, 0); + writel(val, mchan->regs + M10V_XDDES); +} + +static irqreturn_t m10v_xdmac_irq(int irq, void *data) +{ + struct m10v_dma_chan *mchan = data; + unsigned long flags; + u32 val; + + val = readl(mchan->regs + M10V_XDDSD); + val = FIELD_GET(M10V_XDDSD_IS_MASK, val); + + if (val != M10V_XDDSD_IS_NORMAL) + dev_err(mchan->chan.device->dev, "XDMAC error with status: %x", val); + + val = FIELD_PREP(M10V_XDDSD_IS_MASK, 0x0); + writel(val, mchan->regs + M10V_XDDSD); + + spin_lock_irqsave(&mchan->lock, flags); + dma_cookie_complete(&mchan->mdesc.txd); + spin_unlock_irqrestore(&mchan->lock, flags); + + if (mchan->mdesc.txd.flags & DMA_PREP_INTERRUPT) + dmaengine_desc_get_callback_invoke(&mchan->mdesc.txd, NULL); + + return IRQ_HANDLED; +} + +static void m10v_xdmac_issue_pending(struct dma_chan *chan) +{ + struct m10v_dma_chan *mchan = to_m10v_dma_chan(chan); + + m10v_xdmac_config_chan(mchan); + + m10v_xdmac_enable_chan(mchan); +} + +static dma_cookie_t m10v_xdmac_tx_submit(struct dma_async_tx_descriptor *txd) +{ + struct m10v_dma_chan *mchan = to_m10v_dma_chan(txd->chan); + dma_cookie_t cookie; + unsigned long flags; + + spin_lock_irqsave(&mchan->lock, flags); + cookie = dma_cookie_assign(txd); + spin_unlock_irqrestore(&mchan->lock, flags); + + return cookie; +} + +static struct dma_async_tx_descriptor * +m10v_xdmac_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dst, + dma_addr_t src, size_t len, unsigned long flags) +{ + struct m10v_dma_chan *mchan = to_m10v_dma_chan(chan); + + dma_async_tx_descriptor_init(&mchan->mdesc.txd, chan); + mchan->mdesc.txd.tx_submit = m10v_xdmac_tx_submit; + mchan->mdesc.txd.callback = NULL; + mchan->mdesc.txd.flags = flags; + mchan->mdesc.txd.cookie = -EBUSY; + + mchan->mdesc.len = len; + mchan->mdesc.src = src; + mchan->mdesc.dst = dst; + + return &mchan->mdesc.txd; +} + +static int m10v_xdmac_device_terminate_all(struct dma_chan *chan) +{ + struct m10v_dma_chan *mchan = to_m10v_dma_chan(chan); + + m10v_xdmac_disable_chan(mchan); + + return 0; +} + +static int m10v_xdmac_alloc_chan_resources(struct dma_chan *chan) +{ + struct m10v_dma_chan *mchan = to_m10v_dma_chan(chan); + unsigned long flags; + + spin_lock_irqsave(&mchan->lock, flags); + dma_cookie_init(chan); + spin_unlock_irqrestore(&mchan->lock, flags); + + return 1; +} + +static int m10v_xdmac_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct m10v_dma_chan *mchan; + struct m10v_dma_device *mdmac; + struct resource *res; + unsigned int channels; + int ret, i; + + ret = device_property_read_u32(&pdev->dev, "dma-channels", &channels); + if (ret) { + dev_err(&pdev->dev, "get dma-channels failed\n"); + return ret; + } + + mdmac = devm_kzalloc(&pdev->dev, + struct_size(mdmac, mchan, channels), + GFP_KERNEL); + if (!mdmac) + return -ENOMEM; + + mdmac->channels = channels; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mdmac->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mdmac->regs)) + return PTR_ERR(mdmac->regs); + + INIT_LIST_HEAD(&mdmac->dmac.channels); + for (i = 0; i < mdmac->channels; i++) { + mchan = &mdmac->mchan[i]; + mchan->irq = platform_get_irq(pdev, i); + ret = devm_request_irq(&pdev->dev, mchan->irq, m10v_xdmac_irq, + IRQF_SHARED, dev_name(&pdev->dev), mchan); + if (ret) { + dev_err(&pdev->dev, "failed to request IRQ\n"); + return ret; + } + mchan->mdmac = mdmac; + mchan->chan.device = &mdmac->dmac; + list_add_tail(&mchan->chan.device_node, + &mdmac->dmac.channels); + + mchan->regs = M10V_XDMAC_CHAN_BASE(mdmac->regs, i); + spin_lock_init(&mchan->lock); + } + + dma_cap_set(DMA_MEMCPY, mdmac->dmac.cap_mask); + + mdmac->dmac.device_alloc_chan_resources = m10v_xdmac_alloc_chan_resources; + mdmac->dmac.device_prep_dma_memcpy = m10v_xdmac_prep_dma_memcpy; + mdmac->dmac.device_issue_pending = m10v_xdmac_issue_pending; + mdmac->dmac.device_tx_status = dma_cookie_status; + mdmac->dmac.device_terminate_all = m10v_xdmac_device_terminate_all; + mdmac->dmac.src_addr_widths = M10V_XDMAC_BUSWIDTHS; + mdmac->dmac.dst_addr_widths = M10V_XDMAC_BUSWIDTHS; + mdmac->dmac.residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR; + mdmac->dmac.dev = &pdev->dev; + + platform_set_drvdata(pdev, mdmac); + + m10v_xdmac_enable_dma(mdmac); + + ret = dmaenginem_async_device_register(&mdmac->dmac); + if (ret) { + dev_err(&pdev->dev, "failed to register dmaengine device\n"); + return ret; + } + + ret = of_dma_controller_register(np, of_dma_simple_xlate, mdmac); + if (ret) { + dev_err(&pdev->dev, "failed to register OF DMA controller\n"); + return ret; + } + + return 0; +} + +static int m10v_xdmac_remove(struct platform_device *pdev) +{ + struct m10v_dma_chan *mchan; + struct m10v_dma_device *mdmac = platform_get_drvdata(pdev); + int i; + + m10v_xdmac_disable_dma(mdmac); + + for (i = 0; i < mdmac->channels; i++) { + mchan = &mdmac->mchan[i]; + devm_free_irq(&pdev->dev, mchan->irq, mchan); + } + + of_dma_controller_free(pdev->dev.of_node); + + return 0; +} + +static const struct of_device_id m10v_xdmac_dt_ids[] = { + {.compatible = "socionext,milbeaut-m10v-xdmac",}, + {}, +}; +MODULE_DEVICE_TABLE(of, m10v_xdmac_dt_ids); + +static struct platform_driver m10v_xdmac_driver = { + .driver = { + .name = "m10v-xdmac", + .of_match_table = of_match_ptr(m10v_xdmac_dt_ids), + }, + .probe = m10v_xdmac_probe, + .remove = m10v_xdmac_remove, +}; +module_platform_driver(m10v_xdmac_driver); + +MODULE_AUTHOR("Kazuhiro Kasai <kasai.kazuhiro@xxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("Socionext Milbeaut XDMAC driver"); +MODULE_LICENSE("GPL v2"); -- 1.9.1