From patchwork Tue Dec 24 12:06:26 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Popov X-Patchwork-Id: 3402271 Return-Path: X-Original-To: patchwork-dmaengine@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id A4B43C0D4A for ; Tue, 24 Dec 2013 12:02:55 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 491FA205D3 for ; Tue, 24 Dec 2013 12:02:54 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 94194205FA for ; Tue, 24 Dec 2013 12:02:52 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752227Ab3LXMCw (ORCPT ); Tue, 24 Dec 2013 07:02:52 -0500 Received: from mail-lb0-f172.google.com ([209.85.217.172]:53265 "EHLO mail-lb0-f172.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752142Ab3LXMCu (ORCPT ); Tue, 24 Dec 2013 07:02:50 -0500 Received: by mail-lb0-f172.google.com with SMTP id x18so2859313lbi.3 for ; Tue, 24 Dec 2013 04:02:48 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:subject:date:message-id:in-reply-to:references; bh=rldz5bYUklI9QEzdZCldfUaoc9qHQ7OIXkl+puOr5JI=; b=XBKD6mR1XclMxFasv5bOqfco3L0U7kzd/Jpkv/njUHDqvW8TvtbuhmSGJewdPo3ccv mGDuZTt4vi7T02Z5RJgul8vCwn0tIr058hDjsw9SD8uQHZCu3dUOSb5Ltmk6jB3hrzil 8lxcFUaX9EQpwsR8pT/H17Rjs0X6qESelcSiYA1DKCvbuiM1GBQAus4JZeTquDwd7yjv QRle8gTjjC2JXXk+cIkbpPf2fiUIR/GhO0otBIk3qwfpL0zHkDNvLICbadMp1YK6wirC 6WIdOXpagBbrvUxVfDXxFETGnviWGXRwc8vnd5sTUyVvVR/i0fJx4I464Uoka9LENIYL O4tQ== X-Received: by 10.112.126.164 with SMTP id mz4mr773772lbb.52.1387886568514; Tue, 24 Dec 2013 04:02:48 -0800 (PST) Received: from a13xCCC.localdomain (mail.tecon.ru. [89.175.104.62]) by mx.google.com with ESMTPSA id sd11sm17736051lab.2.2013.12.24.04.02.47 for (version=TLSv1 cipher=RC4-SHA bits=128/128); Tue, 24 Dec 2013 04:02:48 -0800 (PST) From: Alexander Popov To: Gerhard Sittig , Dan Williams , Vinod Koul , Lars-Peter Clausen , Arnd Bergmann , Anatolij Gustschin , Alexander Popov , linuxppc-dev@lists.ozlabs.org, dmaengine@vger.kernel.org, devicetree@vger.kernel.org Subject: [PATCH RFC v6 2/5] dma: mpc512x: add support for peripheral transfers Date: Tue, 24 Dec 2013 16:06:26 +0400 Message-Id: <1387886789-20249-3-git-send-email-a13xp0p0v88@gmail.com> X-Mailer: git-send-email 1.7.11.3 In-Reply-To: <1387886789-20249-1-git-send-email-a13xp0p0v88@gmail.com> References: <1387886789-20249-1-git-send-email-a13xp0p0v88@gmail.com> Sender: dmaengine-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: dmaengine@vger.kernel.org X-Spam-Status: No, score=-7.3 required=5.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, T_DKIM_INVALID, UNPARSEABLE_RELAY autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Introduce support for slave s/g transfer preparation and the associated device control callback in the MPC512x DMA controller driver, which adds support for data transfers between memory and peripheral I/O to the previously supported mem-to-mem transfers. Signed-off-by: Alexander Popov --- drivers/dma/mpc512x_dma.c | 230 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 225 insertions(+), 5 deletions(-) diff --git a/drivers/dma/mpc512x_dma.c b/drivers/dma/mpc512x_dma.c index 2ce248b..a7e7749 100644 --- a/drivers/dma/mpc512x_dma.c +++ b/drivers/dma/mpc512x_dma.c @@ -2,6 +2,7 @@ * Copyright (C) Freescale Semicondutor, Inc. 2007, 2008. * Copyright (C) Semihalf 2009 * Copyright (C) Ilya Yanok, Emcraft Systems 2010 + * Copyright (C) Alexander Popov, Promcontroller 2013 * * Written by Piotr Ziecik . Hardware description * (defines, structures and comments) was taken from MPC5121 DMA driver @@ -29,8 +30,15 @@ */ /* - * This is initial version of MPC5121 DMA driver. Only memory to memory - * transfers are supported (tested using dmatest module). + * This version of MPC5121 DMA driver supports + * memory to memory data transfers (tested using dmatest module) and + * data transfers between memory and peripheral I/O memory + * by means of slave s/g with these limitations: + * - chunked transfers (transfers with more than one part) are refused + * as long as proper support for scatter/gather is missing; + * - transfers on MPC8308 always start from software as this SoC appears + * not to have external request lines for peripheral flow control; + * - minimal memory <-> I/O memory transfer size is 4 bytes. */ #include @@ -189,6 +197,7 @@ struct mpc_dma_desc { dma_addr_t tcd_paddr; int error; struct list_head node; + int will_access_peripheral; }; struct mpc_dma_chan { @@ -201,6 +210,10 @@ struct mpc_dma_chan { struct mpc_dma_tcd *tcd; dma_addr_t tcd_paddr; + /* Settings for access to peripheral FIFO */ + dma_addr_t per_paddr; /* FIFO address */ + u32 tcd_nunits; + /* Lock for this structure */ spinlock_t lock; }; @@ -251,8 +264,21 @@ static void mpc_dma_execute(struct mpc_dma_chan *mchan) struct mpc_dma_desc *mdesc; int cid = mchan->chan.chan_id; - /* Move all queued descriptors to active list */ - list_splice_tail_init(&mchan->queued, &mchan->active); + while (!list_empty(&mchan->queued)) { + mdesc = list_first_entry(&mchan->queued, + struct mpc_dma_desc, node); + + /* Grab either several mem-to-mem transfer descriptors + * or one peripheral transfer descriptor, + * don't mix mem-to-mem and peripheral transfer descriptors + * within the same 'active' list. */ + if (mdesc->will_access_peripheral) { + if (list_empty(&mchan->active)) + list_move_tail(&mdesc->node, &mchan->active); + break; + } else + list_move_tail(&mdesc->node, &mchan->active); + } /* Chain descriptors into one transaction */ list_for_each_entry(mdesc, &mchan->active, node) { @@ -278,7 +304,17 @@ static void mpc_dma_execute(struct mpc_dma_chan *mchan) if (first != prev) mdma->tcd[cid].e_sg = 1; - out_8(&mdma->regs->dmassrt, cid); + + if (mdma->is_mpc8308) { + /* MPC8308, no request lines, software initiated start */ + out_8(&mdma->regs->dmassrt, cid); + } else if (first->will_access_peripheral) { + /* peripherals involved, start by external request signal */ + out_8(&mdma->regs->dmaserq, cid); + } else { + /* memory to memory transfer, software initiated start */ + out_8(&mdma->regs->dmassrt, cid); + } } /* Handle interrupt on one half of DMA controller (32 channels) */ @@ -596,6 +632,7 @@ mpc_dma_prep_memcpy(struct dma_chan *chan, dma_addr_t dst, dma_addr_t src, } mdesc->error = 0; + mdesc->will_access_peripheral = 0; tcd = mdesc->tcd; /* Prepare Transfer Control Descriptor for this transaction */ @@ -643,6 +680,186 @@ mpc_dma_prep_memcpy(struct dma_chan *chan, dma_addr_t dst, dma_addr_t src, return &mdesc->desc; } +static struct dma_async_tx_descriptor * +mpc_dma_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 mpc_dma *mdma = dma_chan_to_mpc_dma(chan); + struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(chan); + struct mpc_dma_desc *mdesc = NULL; + dma_addr_t per_paddr; + u32 tcd_nunits; + struct mpc_dma_tcd *tcd; + unsigned long iflags; + struct scatterlist *sg; + size_t len; + int iter, i; + + /* currently there is no proper support for scatter/gather */ + if (sg_len != 1) + return NULL; + + for_each_sg(sgl, sg, sg_len, i) { + spin_lock_irqsave(&mchan->lock, iflags); + + mdesc = list_first_entry(&mchan->free, struct mpc_dma_desc, + node); + if (!mdesc) { + spin_unlock_irqrestore(&mchan->lock, iflags); + /* try to free completed descriptors */ + mpc_dma_process_completed(mdma); + return NULL; + } + + list_del(&mdesc->node); + + per_paddr = mchan->per_paddr; + tcd_nunits = mchan->tcd_nunits; + + spin_unlock_irqrestore(&mchan->lock, iflags); + + if (per_paddr == 0 || tcd_nunits == 0) + goto err_prep; + + mdesc->error = 0; + mdesc->will_access_peripheral = 1; + tcd = mdesc->tcd; + + /* Prepare Transfer Control Descriptor for this transaction */ + + memset(tcd, 0, sizeof(struct mpc_dma_tcd)); + + if (!IS_ALIGNED(sg_dma_address(sg), 4)) + goto err_prep; + + if (direction == DMA_DEV_TO_MEM) { + tcd->saddr = per_paddr; + tcd->daddr = sg_dma_address(sg); + tcd->soff = 0; + tcd->doff = 4; + } else if (direction == DMA_MEM_TO_DEV) { + tcd->saddr = sg_dma_address(sg); + tcd->daddr = per_paddr; + tcd->soff = 4; + tcd->doff = 0; + } else + goto err_prep; + + tcd->ssize = MPC_DMA_TSIZE_4; + tcd->dsize = MPC_DMA_TSIZE_4; + + len = sg_dma_len(sg); + tcd->nbytes = tcd_nunits * 4; + if (!IS_ALIGNED(len, tcd->nbytes)) + goto err_prep; + + iter = len / tcd->nbytes; + if (iter >= 1 << 15) { + /* len is too big */ + goto err_prep; + } else { + /* citer_linkch contains the high bits of iter */ + tcd->biter = iter & 0x1ff; + tcd->biter_linkch = iter >> 9; + tcd->citer = tcd->biter; + tcd->citer_linkch = tcd->biter_linkch; + } + + tcd->e_sg = 0; + tcd->d_req = 1; + + /* Place descriptor in prepared list */ + spin_lock_irqsave(&mchan->lock, iflags); + list_add_tail(&mdesc->node, &mchan->prepared); + spin_unlock_irqrestore(&mchan->lock, iflags); + } + + return &mdesc->desc; + +err_prep: + /* Put the descriptor back */ + spin_lock_irqsave(&mchan->lock, iflags); + list_add_tail(&mdesc->node, &mchan->free); + spin_unlock_irqrestore(&mchan->lock, iflags); + + return NULL; +} + +static int mpc_dma_device_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, + unsigned long arg) +{ + struct mpc_dma_chan *mchan; + struct mpc_dma *mdma; + struct dma_slave_config *cfg; + unsigned long flags; + + mchan = dma_chan_to_mpc_dma_chan(chan); + switch (cmd) { + case DMA_TERMINATE_ALL: + /* disable channel requests */ + mdma = dma_chan_to_mpc_dma(chan); + + spin_lock_irqsave(&mchan->lock, flags); + + out_8(&mdma->regs->dmacerq, chan->chan_id); + list_splice_tail_init(&mchan->prepared, &mchan->free); + list_splice_tail_init(&mchan->queued, &mchan->free); + list_splice_tail_init(&mchan->active, &mchan->free); + + spin_unlock_irqrestore(&mchan->lock, flags); + + return 0; + case DMA_SLAVE_CONFIG: + /* Constraints: + * - only transfers between a peripheral device and + * memory are supported; + * - minimal transfer size is 4 bytes and consequently + * source and destination addresses must be 4-byte aligned and + * transfer size must be aligned on (4 * maxburst) boundary; + * - RAM address is being incremented by minimal transfer size + * during the transfer; + * - peripheral port's address is constant during the transfer. + */ + + cfg = (void *)arg; + + if (cfg->direction != DMA_DEV_TO_MEM && + cfg->direction != DMA_MEM_TO_DEV) + return -EINVAL; + + if (cfg->src_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES && + cfg->dst_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES) + return -EINVAL; + + spin_lock_irqsave(&mchan->lock, flags); + + if (cfg->direction == DMA_DEV_TO_MEM) { + mchan->per_paddr = cfg->src_addr; + mchan->tcd_nunits = cfg->src_maxburst; + } else { + mchan->per_paddr = cfg->dst_addr; + mchan->tcd_nunits = cfg->dst_maxburst; + } + + if (!IS_ALIGNED(mchan->per_paddr, 4)) { + spin_unlock_irqrestore(&mchan->lock, flags); + return -EINVAL; + } + + if (mchan->tcd_nunits == 0) + mchan->tcd_nunits = 1; /* apply default */ + + spin_unlock_irqrestore(&mchan->lock, flags); + + return 0; + default: + return -ENOSYS; + } + + return -EINVAL; +} + static int mpc_dma_probe(struct platform_device *op) { struct device_node *dn = op->dev.of_node; @@ -727,9 +944,12 @@ static int mpc_dma_probe(struct platform_device *op) dma->device_issue_pending = mpc_dma_issue_pending; dma->device_tx_status = mpc_dma_tx_status; dma->device_prep_dma_memcpy = mpc_dma_prep_memcpy; + dma->device_prep_slave_sg = mpc_dma_prep_slave_sg; + dma->device_control = mpc_dma_device_control; INIT_LIST_HEAD(&dma->channels); dma_cap_set(DMA_MEMCPY, dma->cap_mask); + dma_cap_set(DMA_SLAVE, dma->cap_mask); for (i = 0; i < dma->chancnt; i++) { mchan = &mdma->channels[i];