On 28-09-23, 20:19, Keguang Zhang wrote: > This patch adds DMA Engine driver for Loongson1 SoCs. > > Signed-off-by: Keguang Zhang <keguang.zhang@xxxxxxxxx> > --- > V4 -> V5: > Add DT support > Use DT data instead of platform data > Use chan_id of struct dma_chan instead of own id > Use of_dma_xlate_by_chan_id() instead of ls1x_dma_filter() > Update the author information to my official name > V3 -> V4: > Use dma_slave_map to find the proper channel. > Explicitly call devm_request_irq() and tasklet_kill(). > Fix namespace issue. > Some minor fixes and cleanups. > V2 -> V3: > Rename ls1x_dma_filter_fn to ls1x_dma_filter. > V1 -> V2: > Change the config from 'DMA_LOONGSON1' to 'LOONGSON1_DMA', > and rearrange it in alphabetical order in Kconfig and Makefile. > Fix comment style. > > drivers/dma/Kconfig | 9 + > drivers/dma/Makefile | 1 + > drivers/dma/loongson1-dma.c | 492 ++++++++++++++++++++++++++++++++++++ > 3 files changed, 502 insertions(+) > create mode 100644 drivers/dma/loongson1-dma.c > > diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig > index 4ccae1a3b884..0b0d5c61b4a0 100644 > --- a/drivers/dma/Kconfig > +++ b/drivers/dma/Kconfig > @@ -369,6 +369,15 @@ config K3_DMA > Support the DMA engine for Hisilicon K3 platform > devices. > > +config LOONGSON1_DMA > + tristate "Loongson1 DMA support" > + depends on MACH_LOONGSON32 > + select DMA_ENGINE > + select DMA_VIRTUAL_CHANNELS > + help > + This selects support for the DMA controller in Loongson1 SoCs, > + which is required by Loongson1 NAND and AC97 support. > + > config LPC18XX_DMAMUX > bool "NXP LPC18xx/43xx DMA MUX for PL080" > depends on ARCH_LPC18XX || COMPILE_TEST > diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile > index 83553a97a010..887103db5ee3 100644 > --- a/drivers/dma/Makefile > +++ b/drivers/dma/Makefile > @@ -47,6 +47,7 @@ obj-$(CONFIG_INTEL_IDMA64) += idma64.o > obj-$(CONFIG_INTEL_IOATDMA) += ioat/ > obj-y += idxd/ > obj-$(CONFIG_K3_DMA) += k3dma.o > +obj-$(CONFIG_LOONGSON1_DMA) += loongson1-dma.o > obj-$(CONFIG_LPC18XX_DMAMUX) += lpc18xx-dmamux.o > obj-$(CONFIG_MILBEAUT_HDMAC) += milbeaut-hdmac.o > obj-$(CONFIG_MILBEAUT_XDMAC) += milbeaut-xdmac.o > diff --git a/drivers/dma/loongson1-dma.c b/drivers/dma/loongson1-dma.c > new file mode 100644 > index 000000000000..b589103d5ae0 > --- /dev/null > +++ b/drivers/dma/loongson1-dma.c > @@ -0,0 +1,492 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * DMA Driver for Loongson-1 SoC > + * > + * Copyright (C) 2015-2023 Keguang Zhang <keguang.zhang@xxxxxxxxx> > + */ > + > +#include <linux/dmapool.h> > +#include <linux/init.h> > +#include <linux/interrupt.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/of_dma.h> > +#include <linux/platform_device.h> > +#include <linux/slab.h> > + > +#include "dmaengine.h" > +#include "virt-dma.h" > + > +/* Loongson 1 DMA Register Definitions */ > +#define LS1X_DMA_CTRL 0x0 > + > +/* DMA Control Register Bits */ > +#define LS1X_DMA_STOP BIT(4) > +#define LS1X_DMA_START BIT(3) > + > +#define LS1X_DMA_ADDR_MASK GENMASK(31, 6) > + > +/* DMA Command Register Bits */ > +#define LS1X_DMA_RAM2DEV BIT(12) > +#define LS1X_DMA_TRANS_OVER BIT(3) > +#define LS1X_DMA_SINGLE_TRANS_OVER BIT(2) > +#define LS1X_DMA_INT BIT(1) > +#define LS1X_DMA_INT_MASK BIT(0) > + > +#define LS1X_DMA_MAX_CHANNELS 3 > + > +struct ls1x_dma_lli { > + u32 next; /* next descriptor address */ > + u32 saddr; /* memory DMA address */ > + u32 daddr; /* device DMA address */ > + u32 length; > + u32 stride; > + u32 cycles; > + u32 cmd; > +} __aligned(64); > + > +struct ls1x_dma_hwdesc { > + struct ls1x_dma_lli *lli; > + dma_addr_t phys; > +}; > + > +struct ls1x_dma_desc { > + struct virt_dma_desc vdesc; > + struct ls1x_dma_chan *chan; > + > + enum dma_transfer_direction dir; > + enum dma_transaction_type type; > + > + unsigned int nr_descs; /* number of descriptors */ > + unsigned int nr_done; /* number of completed descriptors */ > + struct ls1x_dma_hwdesc hwdesc[]; /* DMA coherent descriptors */ > +}; > + > +struct ls1x_dma_chan { > + struct virt_dma_chan vchan; > + struct dma_pool *desc_pool; > + struct dma_slave_config cfg; > + > + void __iomem *reg_base; > + int irq; > + > + struct ls1x_dma_desc *desc; > +}; > + > +struct ls1x_dma { > + struct dma_device ddev; > + void __iomem *reg_base; > + > + unsigned int nr_chans; > + struct ls1x_dma_chan chan[]; > +}; > + > +#define to_ls1x_dma_chan(dchan) \ > + container_of(dchan, struct ls1x_dma_chan, vchan.chan) > + > +#define to_ls1x_dma_desc(vdesc) \ > + container_of(vdesc, struct ls1x_dma_desc, vdesc) > + > +/* macros for registers read/write */ > +#define chan_readl(chan, off) \ > + readl((chan)->reg_base + (off)) > + > +#define chan_writel(chan, off, val) \ > + writel((val), (chan)->reg_base + (off)) > + > +static inline struct device *chan2dev(struct dma_chan *chan) > +{ > + return &chan->dev->device; > +} > + > +static void ls1x_dma_free_chan_resources(struct dma_chan *dchan) > +{ > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan); > + > + vchan_free_chan_resources(&chan->vchan); > + dma_pool_destroy(chan->desc_pool); > + chan->desc_pool = NULL; > +} > + > +static int ls1x_dma_alloc_chan_resources(struct dma_chan *dchan) > +{ > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan); > + > + chan->desc_pool = dma_pool_create(dma_chan_name(dchan), > + dchan->device->dev, > + sizeof(struct ls1x_dma_lli), > + __alignof__(struct ls1x_dma_lli), 0); > + if (!chan->desc_pool) > + return -ENOMEM; > + > + return 0; > +} > + > +static void ls1x_dma_free_desc(struct virt_dma_desc *vdesc) > +{ > + struct ls1x_dma_desc *desc = to_ls1x_dma_desc(vdesc); > + > + if (desc->nr_descs) { > + unsigned int i = desc->nr_descs; > + struct ls1x_dma_hwdesc *hwdesc; > + > + do { > + hwdesc = &desc->hwdesc[--i]; > + dma_pool_free(desc->chan->desc_pool, hwdesc->lli, > + hwdesc->phys); > + } while (i); > + } > + > + kfree(desc); > +} > + > +static struct ls1x_dma_desc *ls1x_dma_alloc_desc(struct ls1x_dma_chan *chan, > + int sg_len) > +{ > + struct ls1x_dma_desc *desc; > + > + desc = kzalloc(struct_size(desc, hwdesc, sg_len), GFP_NOWAIT); why do you need a helper to do kzalloc? > + > + return desc; > +} > + > +static struct dma_async_tx_descriptor * > +ls1x_dma_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl, > + unsigned int sg_len, > + enum dma_transfer_direction direction, > + unsigned long flags, void *context) > +{ > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan); > + struct dma_slave_config *cfg = &chan->cfg; > + struct ls1x_dma_desc *desc; > + struct scatterlist *sg; > + unsigned int dev_addr, bus_width, cmd, i; > + > + if (!is_slave_direction(direction)) { > + dev_err(chan2dev(dchan), "invalid DMA direction!\n"); > + return NULL; > + } > + > + dev_dbg(chan2dev(dchan), "sg_len=%d, dir=%s, flags=0x%lx\n", sg_len, > + direction == DMA_MEM_TO_DEV ? "to device" : "from device", > + flags); > + > + switch (direction) { > + case DMA_MEM_TO_DEV: > + dev_addr = cfg->dst_addr; > + bus_width = cfg->dst_addr_width; > + cmd = LS1X_DMA_RAM2DEV | LS1X_DMA_INT; > + break; > + case DMA_DEV_TO_MEM: > + dev_addr = cfg->src_addr; > + bus_width = cfg->src_addr_width; > + cmd = LS1X_DMA_INT; > + break; > + default: > + dev_err(chan2dev(dchan), > + "unsupported DMA transfer direction! %d\n", direction); > + return NULL; > + } > + > + /* allocate DMA descriptor */ > + desc = ls1x_dma_alloc_desc(chan, sg_len); > + if (!desc) > + return NULL; > + > + for_each_sg(sgl, sg, sg_len, i) { > + dma_addr_t buf_addr = sg_dma_address(sg); > + size_t buf_len = sg_dma_len(sg); > + struct ls1x_dma_hwdesc *hwdesc = &desc->hwdesc[i]; > + struct ls1x_dma_lli *lli; > + > + if (!is_dma_copy_aligned(dchan->device, buf_addr, 0, buf_len)) { > + dev_err(chan2dev(dchan), "%s: buffer is not aligned!\n", > + __func__); > + goto err; > + } > + > + /* allocate HW DMA descriptors */ > + lli = dma_pool_alloc(chan->desc_pool, GFP_NOWAIT, > + &hwdesc->phys); > + if (!lli) { > + dev_err(chan2dev(dchan), > + "%s: failed to alloc HW DMA descriptor!\n", > + __func__); > + goto err; > + } > + hwdesc->lli = lli; > + > + /* config HW DMA descriptors */ > + lli->saddr = buf_addr; > + lli->daddr = dev_addr; > + lli->length = buf_len / bus_width; > + lli->stride = 0; > + lli->cycles = 1; > + lli->cmd = cmd; > + lli->next = 0; > + > + if (i) > + desc->hwdesc[i - 1].lli->next = hwdesc->phys; > + > + dev_dbg(chan2dev(dchan), > + "hwdesc=%px, saddr=%08x, daddr=%08x, length=%u\n", > + hwdesc, buf_addr, dev_addr, buf_len); > + } > + > + /* config DMA descriptor */ > + desc->chan = chan; > + desc->dir = direction; > + desc->type = DMA_SLAVE; > + desc->nr_descs = sg_len; > + desc->nr_done = 0; > + > + return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags); > +err: > + desc->nr_descs = i; > + ls1x_dma_free_desc(&desc->vdesc); > + return NULL; > +} > + > +static int ls1x_dma_slave_config(struct dma_chan *dchan, > + struct dma_slave_config *config) > +{ > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan); > + > + chan->cfg = *config; You are using only addr and width, why keep full structure? -- ~Vinod