From patchwork Tue Jun 5 21:45:35 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Angelo Dureghello X-Patchwork-Id: 10449327 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 67DA860467 for ; Tue, 5 Jun 2018 21:46:23 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 4F0A02887A for ; Tue, 5 Jun 2018 21:46:23 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 404A329C0B; Tue, 5 Jun 2018 21:46:23 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.9 required=2.0 tests=BAYES_00, MAILING_LIST_MULTI, RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B0DD62887A for ; Tue, 5 Jun 2018 21:46:21 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753113AbeFEVqU (ORCPT ); Tue, 5 Jun 2018 17:46:20 -0400 Received: from ec2-18-194-220-216.eu-central-1.compute.amazonaws.com ([18.194.220.216]:49836 "EHLO sysam.it" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752979AbeFEVpj (ORCPT ); Tue, 5 Jun 2018 17:45:39 -0400 Received: from localhost (localhost [127.0.0.1]) by sysam.it (Postfix) with ESMTP id D31DA21953; Tue, 5 Jun 2018 21:45:37 +0000 (UTC) Received: from sysam.it ([127.0.0.1]) by localhost (sysam.it [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id x6J13FqHf_rh; Tue, 5 Jun 2018 21:45:36 +0000 (UTC) Received: from jerusalem (host209-239-dynamic.20-79-r.retail.telecomitalia.it [79.20.239.209]) by sysam.it (Postfix) with ESMTPSA id 46C282193B; Tue, 5 Jun 2018 21:45:36 +0000 (UTC) Date: Tue, 5 Jun 2018 23:45:35 +0200 From: Angelo Dureghello To: dmaengine@vger.kernel.org, vkoul@kernel.org Cc: linux-m68k@vger.kernel.org Subject: [PATCH v2 2/4] dmaengine: fsl-edma: add fsl-edma-common Message-ID: <20180605214535.GA2087@jerusalem> MIME-Version: 1.0 Content-Disposition: inline User-Agent: Mutt/1.9.5 (2018-04-13) Sender: dmaengine-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: dmaengine@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This patch adds a new fsl-edma-common module to allow new mcf-edma module code to use most of the fsl-edma code. Due to some differences between ColdFire edma (64 channels) and fsl-edma (32 channels), as register set offsets and some other points as the different interrupt organization and other minor things, a common module can collect most of the code for both 32 and 64 channel edmma module version. Signed-off-by: Angelo Dureghello --- Changes for v2: - patch splitted into 4 - add fsl-edma-common module --- drivers/dma/fsl-edma-common.c | 662 ++++++++++++++++++++++++++++++++++ drivers/dma/fsl-edma-common.h | 175 +++++++++ 2 files changed, 837 insertions(+) create mode 100644 drivers/dma/fsl-edma-common.c create mode 100644 drivers/dma/fsl-edma-common.h diff --git a/drivers/dma/fsl-edma-common.c b/drivers/dma/fsl-edma-common.c new file mode 100644 index 000000000000..c205cb07f133 --- /dev/null +++ b/drivers/dma/fsl-edma-common.c @@ -0,0 +1,662 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2013-2014 Freescale Semiconductor, Inc +// Copyright (c) 2017 Sysam, Angelo Dureghello +/* + * drivers/dma/fsl-edma-common.c + * + * Common code for Freescale the edma 32 or 64 channel version. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#include "fsl-edma-common.h" + +#define EDMA_CR 0x00 +#define EDMA_ES 0x04 +#define EDMA_ERQ 0x0C +#define EDMA_EEI 0x14 +#define EDMA_CEEI 0x18 +#define EDMA_SEEI 0x19 +#define EDMA_CERQ 0x1A +#define EDMA_SERQ 0x1B +#define EDMA_CINT 0x1F +#define EDMA_CERR 0x1E +#define EDMA_SSRT 0x1D +#define EDMA_CDNE 0x1C +#define EDMA_INTR 0x24 +#define EDMA_ERR 0x2C + +#define EDMA64_ERQH 0x08 +#define EDMA64_EEIH 0x10 +#define EDMA64_SERQ 0x18 +#define EDMA64_CERQ 0x19 +#define EDMA64_SEEI 0x1a +#define EDMA64_CEEI 0x1b +#define EDMA64_CINT 0x1c +#define EDMA64_CERR 0x1d +#define EDMA64_SSRT 0x1e +#define EDMA64_CDNE 0x1f +#define EDMA64_INTH 0x20 +#define EDMA64_INTL 0x24 +#define EDMA64_ERRH 0x28 +#define EDMA64_ERRL 0x2c + +#define EDMA_TCD 0x1000 +#define EDMA_TCD_SIZE 32 +#define EDMA_TCD_MEM_ALIGN EDMA_TCD_SIZE + +#define EDMA_TCD_ATTR_SSIZE_8BIT 0 +#define EDMA_TCD_ATTR_SSIZE_16BIT BIT(0) +#define EDMA_TCD_ATTR_SSIZE_32BIT BIT(1) +#define EDMA_TCD_ATTR_SSIZE_64BIT (BIT(1) | BIT(0)) +#define EDMA_TCD_ATTR_SSIZE_16BYTE BIT(2) +#define EDMA_TCD_ATTR_SSIZE_32BYTE (BIT(2) | BIT(0)) +#define EDMA_TCD_ATTR_DSIZE_8BIT (EDMA_TCD_ATTR_SSIZE_8BIT << 8) +#define EDMA_TCD_ATTR_DSIZE_16BIT (EDMA_TCD_ATTR_SSIZE_16BIT << 8) +#define EDMA_TCD_ATTR_DSIZE_32BIT (EDMA_TCD_ATTR_SSIZE_32BIT << 8) +#define EDMA_TCD_ATTR_DSIZE_64BIT (EDMA_TCD_ATTR_SSIZE_64BIT << 8) +#define EDMA_TCD_ATTR_DSIZE_16BYTE (EDMA_TCD_ATTR_SSIZE_16BYTE << 8) +#define EDMA_TCD_ATTR_DSIZE_32BYTE (EDMA_TCD_ATTR_SSIZE_32BYTE << 8) + +#define EDMA_TCD_CITER_CITER(x) ((x) & GENMASK(14, 0)) +#define EDMA_TCD_BITER_BITER(x) ((x) & GENMASK(14, 0)) + +#define EDMA_TCD_CSR_START BIT(0) +#define EDMA_TCD_CSR_INT_MAJOR BIT(1) +#define EDMA_TCD_CSR_INT_HALF BIT(2) +#define EDMA_TCD_CSR_D_REQ BIT(3) +#define EDMA_TCD_CSR_E_SG BIT(4) +#define EDMA_TCD_CSR_E_LINK BIT(5) +#define EDMA_TCD_CSR_ACTIVE BIT(6) +#define EDMA_TCD_CSR_DONE BIT(7) + +struct fsl_edma_chan *to_fsl_edma_chan(struct dma_chan *chan) +{ + return container_of(chan, struct fsl_edma_chan, vchan.chan); +} + +struct fsl_edma_desc *to_fsl_edma_desc(struct virt_dma_desc *vd) +{ + return container_of(vd, struct fsl_edma_desc, vdesc); +} + +/* + * R/W functions for big- or little-endian registers: + * The eDMA controller's endian is independent of the CPU core's endian. + * For the big-endian IP module, the offset for 8-bit or 16-bit registers + * should also be swapped opposite to that in little-endian IP. + */ +u32 edma_readl(struct fsl_edma_engine *edma, + void __iomem *addr) +{ + if (edma->big_endian) + return ioread32be(addr); + else + return ioread32(addr); +} + +void edma_writeb(struct fsl_edma_engine *edma, u8 val, + void __iomem *addr) +{ + /* swap the reg offset for these in big-endian mode */ + if (edma->big_endian) + iowrite8(val, (void __iomem *)((unsigned long)addr ^ 0x3)); + else + iowrite8(val, addr); +} + +void edma_writew(struct fsl_edma_engine *edma, u16 val, + void __iomem *addr) +{ + /* swap the reg offset for these in big-endian mode */ + if (edma->big_endian) + iowrite16be(val, (void __iomem *)((unsigned long)addr ^ 0x2)); + else + iowrite16(val, addr); +} + +void edma_writel(struct fsl_edma_engine *edma, u32 val, + void __iomem *addr) +{ + if (edma->big_endian) + iowrite32be(val, addr); + else + iowrite32(val, addr); +} + +static void fsl_edma_fill_tcd(struct fsl_edma_hw_tcd *tcd, + u32 src, u32 dst, u16 attr, u16 soff, + u32 nbytes, u32 slast, u16 citer, u16 biter, + u16 doff, u32 dlast_sga, bool major_int, + bool disable_req, bool enable_sg) +{ + u16 csr = 0; + + /* + * eDMA hardware SGs require the TCDs to be stored in little + * endian format irrespective of the register endian model. + * So we put the value in little endian in memory, waiting + * for fsl_edma_set_tcd_regs doing the swap. + */ + tcd->saddr = cpu_to_le32(src); + tcd->daddr = cpu_to_le32(dst); + tcd->attr = cpu_to_le16(attr); + tcd->soff = cpu_to_le16(soff); + tcd->nbytes = cpu_to_le32(nbytes); + tcd->slast = cpu_to_le32(slast); + tcd->citer = cpu_to_le16(EDMA_TCD_CITER_CITER(citer)); + tcd->doff = cpu_to_le16(doff); + tcd->dlast_sga = cpu_to_le32(dlast_sga); + tcd->biter = cpu_to_le16(EDMA_TCD_BITER_BITER(biter)); + + if (major_int) + csr |= EDMA_TCD_CSR_INT_MAJOR; + if (disable_req) + csr |= EDMA_TCD_CSR_D_REQ; + if (enable_sg) + csr |= EDMA_TCD_CSR_E_SG; + + tcd->csr = cpu_to_le16(csr); +} + +void fsl_edma_enable_request(struct fsl_edma_chan *fsl_chan) +{ + struct edma_regs *regs = &fsl_chan->edma->regs; + u32 ch = fsl_chan->vchan.chan.chan_id; + + if (fsl_chan->edma->version == v1) { + edma_writeb(fsl_chan->edma, EDMA_SEEI_SEEI(ch), regs->seei); + edma_writeb(fsl_chan->edma, ch, regs->serq); + } else { + /* ColdFire is big endian, and accesses natively + * big endian I/O peripherals + */ + iowrite8(EDMA_SEEI_SEEI(ch), regs->seei); + iowrite8(ch, regs->serq); + } +} + +void fsl_edma_disable_request(struct fsl_edma_chan *fsl_chan) +{ + struct edma_regs *regs = &fsl_chan->edma->regs; + u32 ch = fsl_chan->vchan.chan.chan_id; + + if (fsl_chan->edma->version == v1) { + edma_writeb(fsl_chan->edma, ch, regs->cerq); + edma_writeb(fsl_chan->edma, EDMA_CEEI_CEEI(ch), regs->ceei); + } else { + /* ColdFire is big endian, and accesses natively + * big endian I/O peripherals + */ + iowrite8(ch, regs->cerq); + iowrite8(EDMA_CEEI_CEEI(ch), regs->ceei); + } +} + +void fsl_edma_cleanup_vchan(struct dma_device *dmadev) +{ + struct fsl_edma_chan *chan, *_chan; + + list_for_each_entry_safe(chan, _chan, + &dmadev->channels, vchan.chan.device_node) { + list_del(&chan->vchan.chan.device_node); + tasklet_kill(&chan->vchan.task); + } +} + +static unsigned int fsl_edma_get_tcd_attr(enum dma_slave_buswidth addr_width) +{ + switch (addr_width) { + case 1: + return EDMA_TCD_ATTR_SSIZE_8BIT | EDMA_TCD_ATTR_DSIZE_8BIT; + case 2: + return EDMA_TCD_ATTR_SSIZE_16BIT | EDMA_TCD_ATTR_DSIZE_16BIT; + case 4: + return EDMA_TCD_ATTR_SSIZE_32BIT | EDMA_TCD_ATTR_DSIZE_32BIT; + case 8: + return EDMA_TCD_ATTR_SSIZE_64BIT | EDMA_TCD_ATTR_DSIZE_64BIT; + default: + return EDMA_TCD_ATTR_SSIZE_32BIT | EDMA_TCD_ATTR_DSIZE_32BIT; + } +} + +static struct fsl_edma_desc *fsl_edma_alloc_desc(struct fsl_edma_chan *fsl_chan, + int sg_len) +{ + struct fsl_edma_desc *fsl_desc; + int i; + + fsl_desc = kzalloc(sizeof(*fsl_desc) + + sizeof(struct fsl_edma_sw_tcd) * sg_len, GFP_NOWAIT); + if (!fsl_desc) + return NULL; + + fsl_desc->echan = fsl_chan; + fsl_desc->n_tcds = sg_len; + for (i = 0; i < sg_len; i++) { + fsl_desc->tcd[i].vtcd = dma_pool_alloc(fsl_chan->tcd_pool, + GFP_NOWAIT, &fsl_desc->tcd[i].ptcd); + if (!fsl_desc->tcd[i].vtcd) + goto err; + } + return fsl_desc; + +err: + while (--i >= 0) + dma_pool_free(fsl_chan->tcd_pool, fsl_desc->tcd[i].vtcd, + fsl_desc->tcd[i].ptcd); + kfree(fsl_desc); + return NULL; +} + +static void fsl_edma_set_tcd_regs(struct fsl_edma_chan *fsl_chan, + struct fsl_edma_hw_tcd *tcd) +{ + struct fsl_edma_engine *edma = fsl_chan->edma; + struct fsl_edma_hw_tcd *mtcd = edma->regs.tcd + + fsl_chan->vchan.chan.chan_id; + + /* + * TCD parameters are stored in struct fsl_edma_hw_tcd in little + * endian format. However, we need to load the TCD registers in + * big- or little-endian obeying the eDMA engine model endian. + */ + edma_writel(edma, le32_to_cpu(tcd->saddr), &mtcd->saddr); + edma_writel(edma, le32_to_cpu(tcd->daddr), &mtcd->daddr); + edma_writew(edma, le16_to_cpu(tcd->attr), &mtcd->attr); + edma_writew(edma, le16_to_cpu(tcd->soff), &mtcd->soff); + edma_writel(edma, le32_to_cpu(tcd->nbytes), &mtcd->nbytes); + edma_writel(edma, le32_to_cpu(tcd->slast), &mtcd->slast); + edma_writew(edma, le16_to_cpu(tcd->citer), &mtcd->citer); + edma_writew(edma, le16_to_cpu(tcd->biter), &mtcd->biter); + edma_writew(edma, le16_to_cpu(tcd->doff), &mtcd->doff); + edma_writel(edma, le32_to_cpu(tcd->dlast_sga), &mtcd->dlast_sga); + edma_writew(edma, le16_to_cpu(tcd->csr), &mtcd->csr); +} + +static size_t fsl_edma_desc_residue(struct fsl_edma_chan *fsl_chan, + struct virt_dma_desc *vdesc, bool in_progress) +{ + struct fsl_edma_desc *edesc = fsl_chan->edesc; + struct edma_regs *regs = &fsl_chan->edma->regs; + u32 ch = fsl_chan->vchan.chan.chan_id; + enum dma_transfer_direction dir = fsl_chan->dir; + dma_addr_t cur_addr, dma_addr; + size_t len, size; + int i; + + /* calculate the total size in this desc */ + for (len = i = 0; i < fsl_chan->edesc->n_tcds; i++) + len += le32_to_cpu(edesc->tcd[i].vtcd->nbytes) + * le16_to_cpu(edesc->tcd[i].vtcd->biter); + + if (!in_progress) + return len; + + if (dir == DMA_MEM_TO_DEV) + cur_addr = edma_readl(fsl_chan->edma, ®s->tcd[ch].saddr); + else + cur_addr = edma_readl(fsl_chan->edma, ®s->tcd[ch].daddr); + + /* figure out the finished and calculate the residue */ + for (i = 0; i < fsl_chan->edesc->n_tcds; i++) { + size = le32_to_cpu(edesc->tcd[i].vtcd->nbytes) + * le16_to_cpu(edesc->tcd[i].vtcd->biter); + if (dir == DMA_MEM_TO_DEV) + dma_addr = le32_to_cpu(edesc->tcd[i].vtcd->saddr); + else + dma_addr = le32_to_cpu(edesc->tcd[i].vtcd->daddr); + + len -= size; + if (cur_addr >= dma_addr && cur_addr < dma_addr + size) { + len += dma_addr + size - cur_addr; + break; + } + } + + return len; +} + +void fsl_edma_free_desc(struct virt_dma_desc *vdesc) +{ + struct fsl_edma_desc *mcf_desc; + int i; + + mcf_desc = to_fsl_edma_desc(vdesc); + + for (i = 0; i < mcf_desc->n_tcds; i++) + dma_pool_free(mcf_desc->echan->tcd_pool, mcf_desc->tcd[i].vtcd, + mcf_desc->tcd[i].ptcd); + kfree(mcf_desc); +} + +int fsl_edma_alloc_chan_resources(struct dma_chan *chan) +{ + struct fsl_edma_chan *mcf_chan = to_fsl_edma_chan(chan); + + mcf_chan->tcd_pool = dma_pool_create("tcd_pool", chan->device->dev, + sizeof(struct fsl_edma_hw_tcd), EDMA_TCD_MEM_ALIGN, 0); + + return 0; +} + +void fsl_edma_free_chan_resources(struct dma_chan *chan) +{ + struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan); + unsigned long flags; + LIST_HEAD(head); + + spin_lock_irqsave(&fsl_chan->vchan.lock, flags); + fsl_edma_disable_request(fsl_chan); + fsl_chan->edesc = NULL; + vchan_get_all_descriptors(&fsl_chan->vchan, &head); + spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags); + + vchan_dma_desc_free_list(&fsl_chan->vchan, &head); + dma_pool_destroy(fsl_chan->tcd_pool); + fsl_chan->tcd_pool = NULL; +} + +int fsl_edma_slave_config(struct dma_chan *chan, + struct dma_slave_config *config) +{ + struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan); + + memcpy(&fsl_chan->cfg, config, sizeof(*config)); + + return 0; +} + +struct dma_async_tx_descriptor *fsl_edma_prep_dma_cyclic( + struct dma_chan *chan, dma_addr_t dma_addr, size_t buf_len, + size_t period_len, enum dma_transfer_direction direction, + unsigned long flags) +{ + struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan); + struct fsl_edma_desc *fsl_desc; + dma_addr_t dma_buf_next; + int sg_len, i; + u32 src_addr, dst_addr, last_sg, nbytes, attr; + u16 soff, doff, iter; + + if (!is_slave_direction(direction)) + return NULL; + + sg_len = buf_len / period_len; + fsl_desc = fsl_edma_alloc_desc(fsl_chan, sg_len); + if (!fsl_desc) + return NULL; + + fsl_desc->iscyclic = true; + fsl_chan->dir = direction; + + dma_buf_next = dma_addr; + + for (i = 0; i < sg_len; i++) { + if (dma_buf_next >= dma_addr + buf_len) + dma_buf_next = dma_addr; + + /* get next sg's physical address */ + last_sg = fsl_desc->tcd[(i + 1) % sg_len].ptcd; + + if (direction == DMA_MEM_TO_DEV) { + nbytes = fsl_chan->cfg.dst_addr_width * + fsl_chan->cfg.dst_maxburst; + src_addr = dma_buf_next; + dst_addr = fsl_chan->cfg.dst_addr; + soff = fsl_chan->cfg.dst_addr_width; + doff = 0; + attr = fsl_edma_get_tcd_attr( + fsl_chan->cfg.dst_addr_width); + } else { + nbytes = fsl_chan->cfg.src_addr_width * + fsl_chan->cfg.src_maxburst; + src_addr = fsl_chan->cfg.src_addr; + dst_addr = dma_buf_next; + soff = 0; + doff = fsl_chan->cfg.src_addr_width; + attr = fsl_edma_get_tcd_attr( + fsl_chan->cfg.src_addr_width); + } + + iter = period_len / nbytes; + + fsl_edma_fill_tcd(fsl_desc->tcd[i].vtcd, + src_addr, dst_addr, + attr, soff, nbytes, 0, iter, + iter, doff, last_sg, true, false, true); + dma_buf_next += period_len; + } + + return vchan_tx_prep(&fsl_chan->vchan, &fsl_desc->vdesc, flags); +} + +struct dma_async_tx_descriptor *fsl_edma_prep_slave_sg( + struct dma_chan *chan, struct scatterlist *sgl, + unsigned int sg_len, enum dma_transfer_direction direction, + unsigned long flags, void *context) +{ + struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan); + struct fsl_edma_desc *fsl_desc; + struct scatterlist *sg; + u32 src_addr, dst_addr, last_sg, nbytes, attr; + u16 soff, doff, iter; + int i; + + if (!is_slave_direction(direction)) + return NULL; + + fsl_desc = fsl_edma_alloc_desc(fsl_chan, sg_len); + if (!fsl_desc) + return NULL; + + fsl_desc->iscyclic = false; + fsl_chan->dir = direction; + + for_each_sg(sgl, sg, sg_len, i) { + /* get next sg's physical address */ + last_sg = fsl_desc->tcd[(i + 1) % sg_len].ptcd; + + if (direction == DMA_MEM_TO_DEV) { + nbytes = fsl_chan->cfg.dst_addr_width * + fsl_chan->cfg.dst_maxburst; + src_addr = sg_dma_address(sg); + dst_addr = fsl_chan->cfg.dst_addr; + soff = fsl_chan->cfg.dst_addr_width; + doff = 0; + attr = fsl_edma_get_tcd_attr( + fsl_chan->cfg.dst_addr_width); + + } else { + nbytes = fsl_chan->cfg.src_addr_width * + fsl_chan->cfg.src_maxburst; + src_addr = fsl_chan->cfg.src_addr; + dst_addr = sg_dma_address(sg); + soff = 0; + doff = fsl_chan->cfg.src_addr_width; + attr = fsl_edma_get_tcd_attr( + fsl_chan->cfg.src_addr_width); + } + + iter = sg_dma_len(sg) / nbytes; + + if (i < sg_len - 1) { + last_sg = fsl_desc->tcd[(i + 1)].ptcd; + fsl_edma_fill_tcd(fsl_desc->tcd[i].vtcd, + src_addr, + dst_addr, attr, soff, + nbytes, 0, iter, iter, doff, last_sg, + false, false, true); + } else { + last_sg = 0; + fsl_edma_fill_tcd(fsl_desc->tcd[i].vtcd, + src_addr, + dst_addr, attr, soff, + nbytes, 0, iter, iter, doff, last_sg, + true, true, false); + } + } + + return vchan_tx_prep(&fsl_chan->vchan, &fsl_desc->vdesc, flags); +} + +enum dma_status fsl_edma_tx_status(struct dma_chan *chan, + dma_cookie_t cookie, struct dma_tx_state *txstate) +{ + struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan); + struct virt_dma_desc *vdesc; + enum dma_status status; + unsigned long flags; + + status = dma_cookie_status(chan, cookie, txstate); + if (status == DMA_COMPLETE) + return status; + + if (!txstate) + return fsl_chan->status; + + spin_lock_irqsave(&fsl_chan->vchan.lock, flags); + vdesc = vchan_find_desc(&fsl_chan->vchan, cookie); + if (fsl_chan->edesc && cookie == fsl_chan->edesc->vdesc.tx.cookie) + txstate->residue = + fsl_edma_desc_residue(fsl_chan, vdesc, true); + else if (vdesc) + txstate->residue = + fsl_edma_desc_residue(fsl_chan, vdesc, false); + else + txstate->residue = 0; + + spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags); + + return fsl_chan->status; +} + +int fsl_edma_pause(struct dma_chan *chan) +{ + struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan); + unsigned long flags; + + spin_lock_irqsave(&fsl_chan->vchan.lock, flags); + if (fsl_chan->edesc) { + fsl_edma_disable_request(fsl_chan); + fsl_chan->status = DMA_PAUSED; + fsl_chan->idle = true; + } + spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags); + return 0; +} + +int fsl_edma_resume(struct dma_chan *chan) +{ + struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan); + unsigned long flags; + + spin_lock_irqsave(&fsl_chan->vchan.lock, flags); + if (fsl_chan->edesc) { + fsl_edma_enable_request(fsl_chan); + fsl_chan->status = DMA_IN_PROGRESS; + fsl_chan->idle = false; + } + spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags); + return 0; +} + +int fsl_edma_terminate_all(struct dma_chan *chan) +{ + struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan); + unsigned long flags; + LIST_HEAD(head); + + spin_lock_irqsave(&fsl_chan->vchan.lock, flags); + fsl_edma_disable_request(fsl_chan); + fsl_chan->edesc = NULL; + fsl_chan->idle = true; + vchan_get_all_descriptors(&fsl_chan->vchan, &head); + spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags); + vchan_dma_desc_free_list(&fsl_chan->vchan, &head); + return 0; +} + +void fsl_edma_issue_pending(struct dma_chan *chan) +{ + struct fsl_edma_chan *mcf_chan = to_fsl_edma_chan(chan); + unsigned long flags; + + spin_lock_irqsave(&mcf_chan->vchan.lock, flags); + + if (vchan_issue_pending(&mcf_chan->vchan) && !mcf_chan->edesc) + fsl_edma_xfer_desc(mcf_chan); + + spin_unlock_irqrestore(&mcf_chan->vchan.lock, flags); +} + +void fsl_edma_xfer_desc(struct fsl_edma_chan *fsl_chan) +{ + struct virt_dma_desc *vdesc; + + vdesc = vchan_next_desc(&fsl_chan->vchan); + if (!vdesc) + return; + fsl_chan->edesc = to_fsl_edma_desc(vdesc); + fsl_edma_set_tcd_regs(fsl_chan, fsl_chan->edesc->tcd[0].vtcd); + fsl_edma_enable_request(fsl_chan); + fsl_chan->status = DMA_IN_PROGRESS; + fsl_chan->idle = false; +} + +/* + * On the 32 channels Vybrid/mpc577x edma version (here called "v1"), + * register offsets are different compared to ColdFire mcf5441x 64 channels + * edma (here called "v2"). + * + * This function sets up register offsets as per proper declared version + * so must be called in xxx_edma_probe() just after setting the + * edma "version" and "membase" appropriately. + */ +void fsl_edma_setup_regs(struct fsl_edma_engine *edma) +{ + edma->regs.cr = edma->membase + EDMA_CR; + edma->regs.es = edma->membase + EDMA_ES; + edma->regs.erql = edma->membase + EDMA_ERQ; + edma->regs.eeil = edma->membase + EDMA_EEI; + + edma->regs.serq = edma->membase + ((edma->version == v1) ? + EDMA_SERQ : EDMA64_SERQ); + edma->regs.cerq = edma->membase + ((edma->version == v1) ? + EDMA_CERQ : EDMA64_CERQ); + edma->regs.seei = edma->membase + ((edma->version == v1) ? + EDMA_SEEI : EDMA64_SEEI); + edma->regs.ceei = edma->membase + ((edma->version == v1) ? + EDMA_CEEI : EDMA64_CEEI); + edma->regs.cint = edma->membase + ((edma->version == v1) ? + EDMA_CINT : EDMA64_CINT); + edma->regs.cerr = edma->membase + ((edma->version == v1) ? + EDMA_CERR : EDMA64_CERR); + edma->regs.ssrt = edma->membase + ((edma->version == v1) ? + EDMA_SSRT : EDMA64_SSRT); + edma->regs.cdne = edma->membase + ((edma->version == v1) ? + EDMA_CDNE : EDMA64_CDNE); + edma->regs.intl = edma->membase + ((edma->version == v1) ? + EDMA_INTR : EDMA64_INTL); + edma->regs.errl = edma->membase + ((edma->version == v1) ? + EDMA_ERR : EDMA64_ERRL); + + if (edma->version == v2) { + edma->regs.erqh = edma->membase + EDMA64_ERQH; + edma->regs.eeih = edma->membase + EDMA64_EEIH; + edma->regs.errh = edma->membase + EDMA64_ERRH; + edma->regs.inth = edma->membase + EDMA64_INTH; + } + + edma->regs.tcd = edma->membase + EDMA_TCD; +} diff --git a/drivers/dma/fsl-edma-common.h b/drivers/dma/fsl-edma-common.h new file mode 100644 index 000000000000..e56f9e59e50b --- /dev/null +++ b/drivers/dma/fsl-edma-common.h @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * drivers/dma/fsl-edma-common.c + * + * Copyright 2013-2014 Freescale Semiconductor, Inc. + * Copyright 2018 Angelo Dureghello + * + * Common code for Freescale the edma 32 or 64 channel version. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _FSL_EDMA_COMMON_H_ +#define _FSL_EDMA_COMMON_H_ + +#include + +#include "virt-dma.h" + +#define DMAMUX_NR 2 + +#define EDMA_CR_EDBG BIT(1) +#define EDMA_CR_ERCA BIT(2) +#define EDMA_CR_ERGA BIT(3) +#define EDMA_CR_HOE BIT(4) +#define EDMA_CR_HALT BIT(5) +#define EDMA_CR_CLM BIT(6) +#define EDMA_CR_EMLM BIT(7) +#define EDMA_CR_ECX BIT(16) +#define EDMA_CR_CX BIT(17) + +#define EDMA_SEEI_SEEI(x) ((x) & GENMASK(6, 0)) +#define EDMA_CEEI_CEEI(x) ((x) & GENMASK(6, 0)) +#define EDMA_CINT_CINT(x) ((x) & GENMASK(6, 0)) +#define EDMA_CERR_CERR(x) ((x) & GENMASK(6, 0)) + +#define FSL_EDMA_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)) + +enum fsl_edma_pm_state { + RUNNING = 0, + SUSPENDED, +}; + +/* + * This are tcd regs, equal for both v32 and v64 + */ +struct fsl_edma_hw_tcd { + __le32 saddr; + __le16 soff; + __le16 attr; + __le32 nbytes; + __le32 slast; + __le32 daddr; + __le16 doff; + __le16 citer; + __le32 dlast_sga; + __le16 csr; + __le16 biter; +}; + +/* + * This are iomem pointers, for both v32 and v64. + */ +struct edma_regs { + void __iomem *cr; + void __iomem *es; + void __iomem *erq; + void __iomem *erqh; + void __iomem *erql; /* aka erq on v32 */ + void __iomem *eeih; + void __iomem *eeil; /* aka eei on v32 */ + void __iomem *seei; + void __iomem *ceei; + void __iomem *serq; + void __iomem *cerq; + void __iomem *cint; + void __iomem *cerr; + void __iomem *ssrt; + void __iomem *cdne; + void __iomem *inth; + void __iomem *intl; + void __iomem *errh; + void __iomem *errl; + struct fsl_edma_hw_tcd __iomem *tcd; +}; + +struct fsl_edma_sw_tcd { + dma_addr_t ptcd; + struct fsl_edma_hw_tcd *vtcd; +}; + +struct fsl_edma_chan { + struct virt_dma_chan vchan; + enum dma_status status; + enum fsl_edma_pm_state pm_state; + bool idle; + u32 slave_id; + struct fsl_edma_engine *edma; + struct fsl_edma_desc *edesc; + struct dma_pool *tcd_pool; + struct dma_slave_config cfg; + enum dma_transfer_direction dir; +}; + +struct fsl_edma_desc { + struct virt_dma_desc vdesc; + struct fsl_edma_chan *echan; + bool iscyclic; + unsigned int n_tcds; + struct fsl_edma_sw_tcd tcd[]; +}; + +enum edma_version { + v1, /* 32ch, Vybdir, mpc57x, etc */ + v2, /* 64ch Coldfire */ +}; + +struct fsl_edma_engine { + struct dma_device dma_dev; + struct edma_regs regs; + void __iomem *membase; + void __iomem *muxbase[DMAMUX_NR]; + struct clk *muxclk[DMAMUX_NR]; + struct mutex fsl_edma_mutex; + u32 n_chans; + int txirq; + int errirq; + bool big_endian; + enum edma_version version; + struct fsl_edma_chan chans[]; +}; + +u32 edma_readl(struct fsl_edma_engine *edma, void __iomem *addr); +void edma_writeb(struct fsl_edma_engine *edma, u8 val, void __iomem *addr); +void edma_writew(struct fsl_edma_engine *edma, u16 val, void __iomem *addr); +void edma_writel(struct fsl_edma_engine *edma, u32 val, void __iomem *addr); +struct fsl_edma_chan *to_fsl_edma_chan(struct dma_chan *chan); +struct fsl_edma_desc *to_fsl_edma_desc(struct virt_dma_desc *vd); +void fsl_edma_setup_regs(struct fsl_edma_engine *edma); +void fsl_edma_disable_request(struct fsl_edma_chan *fsl_chan); +void fsl_edma_free_desc(struct virt_dma_desc *vdesc); +void fsl_edma_xfer_desc(struct fsl_edma_chan *fsl_chan); +void fsl_edma_cleanup_vchan(struct dma_device *dmadev); + +/* Operations */ +int fsl_edma_alloc_chan_resources(struct dma_chan *chan); +void fsl_edma_free_chan_resources(struct dma_chan *chan); +int fsl_edma_slave_config(struct dma_chan *chan, + struct dma_slave_config *config); +struct dma_async_tx_descriptor *fsl_edma_prep_dma_cyclic( + struct dma_chan *chan, dma_addr_t dma_addr, size_t buf_len, + size_t period_len, enum dma_transfer_direction direction, + unsigned long flags); +struct dma_async_tx_descriptor *fsl_edma_prep_slave_sg( + struct dma_chan *chan, struct scatterlist *sgl, + unsigned int sg_len, enum dma_transfer_direction direction, + unsigned long flags, void *context); +enum dma_status fsl_edma_tx_status(struct dma_chan *chan, + dma_cookie_t cookie, struct dma_tx_state *txstate); +int fsl_edma_pause(struct dma_chan *chan); +int fsl_edma_resume(struct dma_chan *chan); +int fsl_edma_terminate_all(struct dma_chan *chan); +void fsl_edma_issue_pending(struct dma_chan *chan); + +#endif /* _FSL_EDMA_COMMON_H_ */ +