From patchwork Sat Mar 3 08:59:05 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?=C3=81lvaro_Fern=C3=A1ndez_Rojas?= X-Patchwork-Id: 880998 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=lists.denx.de (client-ip=81.169.180.215; helo=lists.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="Da7UZqqE"; dkim-atps=neutral Received: from lists.denx.de (dione.denx.de [81.169.180.215]) by ozlabs.org (Postfix) with ESMTP id 3ztgC719mwz9sWS for ; Sat, 3 Mar 2018 20:01:39 +1100 (AEDT) Received: by lists.denx.de (Postfix, from userid 105) id C4749C2205F; Sat, 3 Mar 2018 09:00:37 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on lists.denx.de X-Spam-Level: X-Spam-Status: No, score=0.0 required=5.0 tests=FREEMAIL_FROM, RCVD_IN_DNSWL_BLOCKED, RCVD_IN_MSPIKE_H2, T_DKIM_INVALID autolearn=unavailable autolearn_force=no version=3.4.0 Received: from lists.denx.de (localhost [IPv6:::1]) by lists.denx.de (Postfix) with ESMTP id 6601FC22043; Sat, 3 Mar 2018 08:59:41 +0000 (UTC) Received: by lists.denx.de (Postfix, from userid 105) id BA7E3C21FB9; Sat, 3 Mar 2018 08:59:34 +0000 (UTC) Received: from mail-wr0-f194.google.com (mail-wr0-f194.google.com [209.85.128.194]) by lists.denx.de (Postfix) with ESMTPS id 1A2AAC21FCC for ; Sat, 3 Mar 2018 08:59:30 +0000 (UTC) Received: by mail-wr0-f194.google.com with SMTP id o76so12334230wrb.7 for ; Sat, 03 Mar 2018 00:59:30 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=M7jGGflEB0o7L3C+Bd5L330SAhQ9j+ncPqjEtBtSNwc=; b=Da7UZqqE/WdjNt912Rf/XunxXf69jsT31YFIjt4hVxzfZ7LuRXUH1OVu0HyGeuLwZo fKfpe/2RZUf3FTRWjrfrvcwT64Nb5Q4lRABvXTtYMc9Nx6WiiDDzMn9oTFZztMBHcFiF dCiywxlnAU5oJhtK6RM1kKg4mjIqJXKyvn1L9ANXWQZxxHQZWhObl4jrotJUdl+JmaZv 6AO7wFwr/qNSgvI4hIQ4Cu72VlaT0LdFEF4vLEeeQp9nK0WPwIblvm9r9t0zQ2jYVkqY xeh9UI0IcMFdD1/+1jdWRlVcHy96YGmDl7tvCxLT9prh4ZTyL9q8IhLQANuA82ErlFIq maVw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=M7jGGflEB0o7L3C+Bd5L330SAhQ9j+ncPqjEtBtSNwc=; b=flUyoPH+zvE8HbWQLHsqI8fO4uSXHpjHIMUVPN1f1QlApTPn1yTdcn4ioOkSwCetyF EFiXbrXRUFifvBZCeFB2ESD/9Znl74tfcslW2QXM/wscIBUJAuCEzNin1TbiFIbdQH3j IxIe+yT2OPzMGlay7JElWnmln7CVqnThsOr+E/AcKFvg1WjG2F2Rh8iWscQgKEaPVGVA yHiWizFXvoXvxgNzxasyTcVDv1lQNtou/8SjIlZnusEpZH7agpDqRdp4seK0J2a8HwMo KcSEs8B7WtlnmCclH84Tqry77jC8F9DvNAu8xFekiYYRvPgFk71w/Kh8gmgYyamBHSAI jgaw== X-Gm-Message-State: AElRT7G55trAMIQWisz4wi9k0SJlIKMdlT3FZ4B0u3xfKDE+0xHEVmE4 uxuUgALGfV0cVBui1xzUiyk3Ppbd X-Google-Smtp-Source: AG47ELuCAyvSjpTIR9xddQXcAOJa9XswO0AqziIx4L4TP+nM0NVWSSpRdb+tqaCpFJEiBlCePngEAQ== X-Received: by 10.223.225.3 with SMTP id d3mr3026712wri.56.1520067569102; Sat, 03 Mar 2018 00:59:29 -0800 (PST) Received: from skynet.lan (175.red-2-137-31.dynamicip.rima-tde.net. [2.137.31.175]) by smtp.gmail.com with ESMTPSA id w74sm4922292wmd.1.2018.03.03.00.59.28 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sat, 03 Mar 2018 00:59:28 -0800 (PST) From: =?utf-8?q?=C3=81lvaro_Fern=C3=A1ndez_Rojas?= To: u-boot@lists.denx.de, daniel.schwierzeck@gmail.com, sjg@chromium.org, jagan@openedev.com, vigneshr@ti.com, grygorii.strashko@ti.com Date: Sat, 3 Mar 2018 09:59:05 +0100 Message-Id: <20180303085917.8293-4-noltari@gmail.com> X-Mailer: git-send-email 2.11.0 In-Reply-To: <20180303085917.8293-1-noltari@gmail.com> References: <20180212163858.25601-1-noltari@gmail.com> <20180303085917.8293-1-noltari@gmail.com> MIME-Version: 1.0 Subject: [U-Boot] [RFC v4 03/15] dma: add bcm6348-iudma support X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.18 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" BCM6348 IUDMA controller is present on multiple BMIPS (BCM63xx) SoCs. Signed-off-by: Álvaro Fernández Rojas --- v4: Fix issues reported by Grygorii Strashko and other fixes: - Remove usage of net_rx_packets as buffer. - Allocate dynamic rx buffer. - Check dma errors and discard invalid packets. v3: no changes v2: Fix dma rx burst config and select DMA_CHANNELS. drivers/dma/Kconfig | 9 + drivers/dma/Makefile | 1 + drivers/dma/bcm6348-iudma.c | 545 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 555 insertions(+) create mode 100644 drivers/dma/bcm6348-iudma.c diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 21b2c0dcaa..9afa158b51 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -19,6 +19,15 @@ config DMA_CHANNELS Enable channels support for DMA. Some DMA controllers have multiple channels which can either transfer data to/from different devices. +config BCM6348_IUDMA + bool "BCM6348 IUDMA driver" + depends on ARCH_BMIPS + select DMA_CHANNELS + help + Enable the BCM6348 IUDMA driver. + This driver support data transfer from devices to + memory and from memory to devices. + config TI_EDMA3 bool "TI EDMA3 driver" help diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index 39b78b2a3d..b2b4147349 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_DMA) += dma-uclass.o obj-$(CONFIG_FSLDMAFEC) += MCD_tasksInit.o MCD_dmaApi.o MCD_tasks.o obj-$(CONFIG_APBH_DMA) += apbh_dma.o +obj-$(CONFIG_BCM6348_IUDMA) += bcm6348-iudma.o obj-$(CONFIG_FSL_DMA) += fsl_dma.o obj-$(CONFIG_TI_KSNAV) += keystone_nav.o keystone_nav_cfg.o obj-$(CONFIG_TI_EDMA3) += ti-edma3.o diff --git a/drivers/dma/bcm6348-iudma.c b/drivers/dma/bcm6348-iudma.c new file mode 100644 index 0000000000..d88345e45c --- /dev/null +++ b/drivers/dma/bcm6348-iudma.c @@ -0,0 +1,545 @@ +/* + * Copyright (C) 2018 Álvaro Fernández Rojas + * + * Derived from linux/drivers/dma/bcm63xx-iudma.c: + * Copyright (C) 2015 Simon Arlott + * + * Derived from linux/drivers/net/ethernet/broadcom/bcm63xx_enet.c: + * Copyright (C) 2008 Maxime Bizon + * + * Derived from bcm963xx_4.12L.06B_consumer/shared/opensource/include/bcm963xx/63268_map_part.h: + * Copyright (C) 2000-2010 Broadcom Corporation + * + * Derived from bcm963xx_4.12L.06B_consumer/bcmdrivers/opensource/net/enet/impl4/bcmenet.c: + * Copyright (C) 2010 Broadcom Corporation + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DMA_RX_DESC 4 +#define DMA_TX_DESC 1 + +DECLARE_GLOBAL_DATA_PTR; + +#define ALIGN_END_ADDR(type, ptr, size) \ + ((unsigned long)(ptr) + roundup((size) * sizeof(type), \ + ARCH_DMA_MINALIGN)) + +/* DMA Channels */ +#define DMA_CHAN_FLOWC(x) ((x) >> 1) +#define DMA_CHAN_FLOWC_MAX 8 +#define DMA_CHAN_MAX 16 +#define DMA_CHAN_SIZE 0x10 +#define DMA_CHAN_TOUT 500 + +/* DMA Global Configuration register */ +#define DMA_CFG_REG 0x00 +#define DMA_CFG_ENABLE_SHIFT 0 +#define DMA_CFG_ENABLE_MASK (1 << DMA_CFG_ENABLE_SHIFT) +#define DMA_CFG_FLOWC_ENABLE(x) BIT(DMA_CHAN_FLOWC(x) + 1) +#define DMA_CFG_NCHANS_SHIFT 24 +#define DMA_CFG_NCHANS_MASK (0xf << DMA_CFG_NCHANS_SHIFT) + +/* DMA Global Flow Control Threshold registers */ +#define DMA_FLOWC_THR_LO_REG(x) (0x04 + DMA_CHAN_FLOWC(x) * 0x0c) +#define DMA_FLOWC_THR_LO_SHIFT 0 +#define DMA_FLOWC_THR_LO_MASK (5 << DMA_FLOWC_THR_LO_SHIFT) + +#define DMA_FLOWC_THR_HI_REG(x) (0x08 + DMA_CHAN_FLOWC(x) * 0x0c) +#define DMA_FLOWC_THR_HI_SHIFT 0 +#define DMA_FLOWC_THR_HI_MASK (10 << DMA_FLOWC_THR_HI_SHIFT) + +/* DMA Global Flow Control Buffer Allocation registers */ +#define DMA_FLOWC_ALLOC_REG(x) (0x0c + DMA_CHAN_FLOWC(x) * 0x0c) +#define DMA_FLOWC_ALLOC_FORCE_SHIFT 31 +#define DMA_FLOWC_ALLOC_FORCE_MASK (1 << DMA_FLOWC_ALLOC_FORCE_SHIFT) + +/* DMA Global Reset register */ +#define DMA_RST_REG 0x34 +#define DMA_RST_CHAN_SHIFT 0 +#define DMA_RST_CHAN_MASK(x) (1 << x) + +/* DMA Channel Configuration register */ +#define DMAC_CFG_REG(x) (DMA_CHAN_SIZE * (x) + 0x00) +#define DMAC_CFG_ENABLE_SHIFT 0 +#define DMAC_CFG_ENABLE_MASK (1 << DMAC_CFG_ENABLE_SHIFT) +#define DMAC_CFG_PKT_HALT_SHIFT 1 +#define DMAC_CFG_PKT_HALT_MASK (1 << DMAC_CFG_PKT_HALT_SHIFT) +#define DMAC_CFG_BRST_HALT_SHIFT 2 +#define DMAC_CFG_BRST_HALT_MASK (1 << DMAC_CFG_BRST_HALT_SHIFT) + +/* DMA Channel Interrupts registers */ +#define DMAC_IR_ST_REG(x) (DMA_CHAN_SIZE * (x) + 0x04) +#define DMAC_IR_EN_REG(x) (DMA_CHAN_SIZE * (x) + 0x08) + +#define DMAC_IR_DONE_SHIFT 2 +#define DMAC_IR_DONE_MASK (1 << DMAC_IR_DONE_SHIFT) + +/* DMA Channel Max Burst Length register */ +#define DMAC_BURST_REG(x) (DMA_CHAN_SIZE * (x) + 0x0c) +#define DMAC_BURST_MAX_SHIFT 0 +#define DMAC_BURST_MAX_MASK (16 << DMAC_BURST_MAX_SHIFT) + +/* DMA SRAM Descriptor Ring Start register */ +#define DMAS_RSTART_REG(x) (DMA_CHAN_SIZE * (x) + 0x00) + +/* DMA SRAM State/Bytes done/ring offset register */ +#define DMAS_STATE_DATA_REG(x) (DMA_CHAN_SIZE * (x) + 0x04) + +/* DMA SRAM Buffer Descriptor status and length register */ +#define DMAS_DESC_LEN_STATUS_REG(x) (DMA_CHAN_SIZE * (x) + 0x08) + +/* DMA SRAM Buffer Descriptor status and length register */ +#define DMAS_DESC_BASE_BUFPTR_REG(x) (DMA_CHAN_SIZE * (x) + 0x0c) + +struct bcm6348_dma_desc { + uint16_t length; + + uint16_t status; +#define DMAD_ST_OV_ERR_SHIFT 0 +#define DMAD_ST_OV_ERR_MASK (1 << DMAD_ST_OV_ERR_SHIFT) +#define DMAD_ST_CRC_ERR_SHIFT 1 +#define DMAD_ST_CRC_ERR_MASK (1 << DMAD_ST_CRC_ERR_SHIFT) +#define DMAD_ST_RX_ERR_SHIFT 2 +#define DMAD_ST_RX_ERR_MASK (1 << DMAD_ST_RX_ERR_SHIFT) +#define DMAD_ST_OS_ERR_SHIFT 4 +#define DMAD_ST_OS_ERR_MASK (1 << DMAD_ST_OS_ERR_SHIFT) +#define DMAD_ST_CRC_SHIFT 8 +#define DMAD_ST_CRC_MASK (1 << DMAD_ST_CRC_SHIFT) +#define DMAD_ST_UN_ERR_SHIFT 9 +#define DMAD_ST_UN_ERR_MASK (1 << DMAD_ST_UN_ERR_SHIFT) +#define DMAD_ST_WRAP_SHIFT 12 +#define DMAD_ST_WRAP_MASK (1 << DMAD_ST_WRAP_SHIFT) +#define DMAD_ST_SOP_SHIFT 13 +#define DMAD_ST_SOP_MASK (1 << DMAD_ST_SOP_SHIFT) +#define DMAD_ST_EOP_SHIFT 14 +#define DMAD_ST_EOP_MASK (1 << DMAD_ST_EOP_SHIFT) +#define DMAD_ST_OWN_SHIFT 15 +#define DMAD_ST_OWN_MASK (1 << DMAD_ST_OWN_SHIFT) + +#define DMAD_ST_ERR_MASK (DMAD_ST_OV_ERR_MASK | \ + DMAD_ST_CRC_ERR_MASK | \ + DMAD_ST_RX_ERR_MASK | \ + DMAD_ST_OS_ERR_MASK | \ + DMAD_ST_UN_ERR_MASK) + + uint32_t address; +} __attribute__((aligned(1))); + +struct bcm6348_chan_priv { + void __iomem *dma_buff; + void __iomem *dma_ring; + uint8_t dma_ring_size; + uint8_t desc_id; +}; + +struct bcm6348_iudma_priv { + void __iomem *base; + void __iomem *chan; + void __iomem *sram; + struct bcm6348_chan_priv **ch_priv; + uint8_t n_channels; +}; + +static inline bool bcm6348_iudma_chan_is_rx(uint8_t ch) +{ + return !(ch & 1); +} + +static void bcm6348_iudma_chan_stop(struct bcm6348_iudma_priv *priv, + uint8_t ch) +{ + unsigned int timeout = DMA_CHAN_TOUT; + + /* disable dma channel interrupts */ + writel_be(0, priv->chan + DMAC_IR_EN_REG(ch)); + + do { + uint32_t cfg, halt; + + if (timeout > DMA_CHAN_TOUT / 2) + halt = DMAC_CFG_PKT_HALT_MASK; + else + halt = DMAC_CFG_BRST_HALT_MASK; + + /* try to stop dma channel */ + writel_be(halt, priv->chan + DMAC_CFG_REG(ch)); + mb(); + + /* check if channel was stopped */ + cfg = readl_be(priv->chan + DMAC_CFG_REG(ch)); + if (!(cfg & DMAC_CFG_ENABLE_MASK)) + break; + + udelay(1); + } while (--timeout); + + if (!timeout) + pr_err("unable to stop channel %u\n", ch); + + /* reset dma channel */ + setbits_be32(priv->base + DMA_RST_REG, DMA_RST_CHAN_MASK(ch)); + mb(); + clrbits_be32(priv->base + DMA_RST_REG, DMA_RST_CHAN_MASK(ch)); +} + +static int bcm6348_iudma_disable(struct dma *dma) +{ + struct bcm6348_iudma_priv *priv = dev_get_priv(dma->dev); + + bcm6348_iudma_chan_stop(priv, dma->id); + + if (bcm6348_iudma_chan_is_rx(dma->id)) + writel_be(DMA_FLOWC_ALLOC_FORCE_MASK, + DMA_FLOWC_ALLOC_REG(dma->id)); + + return 0; +} + +static int bcm6348_iudma_enable(struct dma *dma) +{ + struct bcm6348_iudma_priv *priv = dev_get_priv(dma->dev); + struct bcm6348_chan_priv *ch_priv = priv->ch_priv[dma->id]; + struct bcm6348_dma_desc *dma_desc; + void __iomem *dma_buff = ch_priv->dma_buff; + uint8_t i; + + /* init dma rings */ + dma_desc = ch_priv->dma_ring; + for (i = 0; i < ch_priv->dma_ring_size; i++) { + if (bcm6348_iudma_chan_is_rx(dma->id)) { + dma_desc->status = DMAD_ST_OWN_MASK; + dma_desc->length = PKTSIZE_ALIGN; + dma_desc->address = virt_to_phys(dma_buff); + dma_buff += PKTSIZE_ALIGN; + } else { + dma_desc->status = 0; + dma_desc->length = 0; + dma_desc->address = 0; + } + + if (i == ch_priv->dma_ring_size - 1) + dma_desc->status |= DMAD_ST_WRAP_MASK; + + if (bcm6348_iudma_chan_is_rx(dma->id)) + writel_be(1, + priv->base + DMA_FLOWC_ALLOC_REG(dma->id)); + + dma_desc++; + } + + /* init to first descriptor */ + ch_priv->desc_id = 0; + + /* force cache writeback */ + flush_dcache_range((ulong)ch_priv->dma_ring, + ALIGN_END_ADDR(struct bcm6348_dma_desc, ch_priv->dma_ring, + ch_priv->dma_ring_size)); + + /* clear sram */ + writel_be(0, priv->sram + DMAS_STATE_DATA_REG(dma->id)); + writel_be(0, priv->sram + DMAS_DESC_LEN_STATUS_REG(dma->id)); + writel_be(0, priv->sram + DMAS_DESC_BASE_BUFPTR_REG(dma->id)); + + /* set dma ring start */ + writel_be(virt_to_phys(ch_priv->dma_ring), + priv->sram + DMAS_RSTART_REG(dma->id)); + + /* set flow control */ + if (bcm6348_iudma_chan_is_rx(dma->id)) { + u32 val; + + setbits_be32(priv->base + DMA_CFG_REG, + DMA_CFG_FLOWC_ENABLE(dma->id)); + + val = ch_priv->dma_ring_size / 3; + writel_be(val, priv->base + DMA_FLOWC_THR_LO_REG(dma->id)); + + val = (ch_priv->dma_ring_size * 2) / 3; + writel_be(val, priv->base + DMA_FLOWC_THR_HI_REG(dma->id)); + } + + /* set dma max burst */ + writel_be(ch_priv->dma_ring_size, + priv->chan + DMAC_BURST_REG(dma->id)); + + /* clear interrupts */ + writel_be(DMAC_IR_DONE_MASK, priv->chan + DMAC_IR_ST_REG(dma->id)); + writel_be(0, priv->chan + DMAC_IR_EN_REG(dma->id)); + + setbits_be32(priv->chan + DMAC_CFG_REG(dma->id), DMAC_CFG_ENABLE_MASK); + + return 0; +} + +static int bcm6348_iudma_request(struct dma *dma) +{ + struct bcm6348_iudma_priv *priv = dev_get_priv(dma->dev); + struct bcm6348_chan_priv *ch_priv; + + /* check if channel is valid */ + if (dma->id >= priv->n_channels) + return -ENODEV; + + /* alloc channel private data */ + priv->ch_priv[dma->id] = calloc(1, sizeof(struct bcm6348_chan_priv)); + if (!priv->ch_priv[dma->id]) + return -ENOMEM; + ch_priv = priv->ch_priv[dma->id]; + + /* alloc dma ring */ + if (bcm6348_iudma_chan_is_rx(dma->id)) + ch_priv->dma_ring_size = DMA_RX_DESC; + else + ch_priv->dma_ring_size = DMA_TX_DESC; + + ch_priv->dma_ring = + malloc_cache_aligned(sizeof(struct bcm6348_dma_desc) * + ch_priv->dma_ring_size); + if (!ch_priv->dma_ring) + return -ENOMEM; + + if (bcm6348_iudma_chan_is_rx(dma->id)) { + ch_priv->dma_buff = + malloc_cache_aligned(PKTSIZE_ALIGN * + ch_priv->dma_ring_size); + if (!ch_priv->dma_buff) + return -ENOMEM; + } else { + ch_priv->dma_buff = NULL; + } + + return 0; +} + +static int bcm6348_iudma_receive(struct dma *dma, void **dst) +{ + struct bcm6348_iudma_priv *priv = dev_get_priv(dma->dev); + struct bcm6348_chan_priv *ch_priv = priv->ch_priv[dma->id]; + struct bcm6348_dma_desc *dma_desc; + void __iomem *dma_buff; + int ret; + + /* get dma ring descriptor address */ + dma_desc = ch_priv->dma_ring; + dma_desc += ch_priv->desc_id; + + /* invalidate cache data */ + invalidate_dcache_range((ulong)dma_desc, + ALIGN_END_ADDR(struct bcm6348_dma_desc, dma_desc, 1)); + + /* check dma own */ + if (dma_desc->status & DMAD_ST_OWN_MASK) + return -EAGAIN; + + /* check pkt */ + if (!(dma_desc->status & DMAD_ST_EOP_MASK) || + !(dma_desc->status & DMAD_ST_SOP_MASK) || + (dma_desc->status & DMAD_ST_ERR_MASK)) { + pr_err("invalid pkt received (ch=%ld)\n", dma->id); + ret = -EAGAIN; + } else { + uint16_t length; + + /* get dma buff descriptor address */ + dma_buff = phys_to_virt(dma_desc->address); + + /* invalidate cache data */ + invalidate_dcache_range((ulong)dma_buff, + (ulong)(dma_buff + PKTSIZE_ALIGN)); + + /* remove crc */ + length = dma_desc->length - 4; + + /* copy data */ + memcpy(*dst, dma_buff, length); + + ret = length; + } + + /* reinit dma descriptor */ + dma_desc->length = PKTSIZE_ALIGN; + dma_desc->status = (dma_desc->status & DMAD_ST_WRAP_MASK) | + DMAD_ST_OWN_MASK; + + /* flush cache */ + flush_dcache_range((ulong)dma_desc, + ALIGN_END_ADDR(struct bcm6348_dma_desc, dma_desc, 1)); + + /* set flow control buffer alloc */ + writel_be(1, priv->base + DMA_FLOWC_ALLOC_REG(dma->id)); + + /* enable dma */ + setbits_be32(priv->chan + DMAC_CFG_REG(dma->id), DMAC_CFG_ENABLE_MASK); + + /* set interrupt */ + writel_be(DMAC_IR_DONE_MASK, priv->chan + DMAC_IR_EN_REG(dma->id)); + + /* increment dma descriptor */ + ch_priv->desc_id = (ch_priv->desc_id + 1) % ch_priv->dma_ring_size; + + return ret; +} + +static int bcm6348_iudma_send(struct dma *dma, void *src, size_t len) +{ + struct bcm6348_iudma_priv *priv = dev_get_priv(dma->dev); + struct bcm6348_chan_priv *ch_priv = priv->ch_priv[dma->id]; + struct bcm6348_dma_desc *dma_desc; + uint16_t val; + + /* get dma ring descriptor address */ + dma_desc = ch_priv->dma_ring; + dma_desc += ch_priv->desc_id; + + dma_desc->address = virt_to_phys(src); + + /* config dma descriptor */ + val = (DMAD_ST_OWN_MASK | + DMAD_ST_EOP_MASK | + DMAD_ST_CRC_MASK | + DMAD_ST_SOP_MASK); + if (ch_priv->desc_id == ch_priv->dma_ring_size - 1) + val |= DMAD_ST_WRAP_MASK; + + dma_desc->length = len; + dma_desc->status = val; + + /* flush cache */ + flush_dcache_range((ulong)src, (ulong)src + PKTSIZE_ALIGN); + + /* flush cache */ + flush_dcache_range((ulong)dma_desc, + ALIGN_END_ADDR(struct bcm6348_dma_desc, dma_desc, 1)); + + /* enable dma */ + setbits_be32(priv->chan + DMAC_CFG_REG(dma->id), DMAC_CFG_ENABLE_MASK); + + /* set interrupt */ + writel_be(DMAC_IR_DONE_MASK, priv->chan + DMAC_IR_EN_REG(dma->id)); + + /* poll dma status */ + do { + /* invalidate cache */ + invalidate_dcache_range((ulong)dma_desc, + ALIGN_END_ADDR(struct bcm6348_dma_desc, dma_desc, 1)); + + if (!(dma_desc->status & DMAD_ST_OWN_MASK)) + break; + } while(1); + + /* increment dma descriptor */ + ch_priv->desc_id = (ch_priv->desc_id + 1) % ch_priv->dma_ring_size; + + return 0; +} + +static const struct dma_ops bcm6348_iudma_ops = { + .disable = bcm6348_iudma_disable, + .enable = bcm6348_iudma_enable, + .request = bcm6348_iudma_request, + .receive = bcm6348_iudma_receive, + .send = bcm6348_iudma_send, +}; + +static const struct udevice_id bcm6348_iudma_ids[] = { + { .compatible = "brcm,bcm6348-iudma", }, + { /* sentinel */ } +}; + +static int bcm6348_iudma_probe(struct udevice *dev) +{ + struct dma_dev_priv *uc_priv = dev_get_uclass_priv(dev); + struct bcm6348_iudma_priv *priv = dev_get_priv(dev); + fdt_addr_t addr; + uint8_t ch; + int i; + + uc_priv->supported = DMA_SUPPORTS_DEV_TO_MEM | + DMA_SUPPORTS_MEM_TO_DEV; + + /* try to enable clocks */ + for (i = 0; ; i++) { + struct clk clk; + int ret; + + ret = clk_get_by_index(dev, i, &clk); + if (ret < 0) + break; + if (clk_enable(&clk)) + pr_err("failed to enable clock %d\n", i); + clk_free(&clk); + } + + /* try to perform resets */ + for (i = 0; ; i++) { + struct reset_ctl reset; + int ret; + + ret = reset_get_by_index(dev, i, &reset); + if (ret < 0) + break; + if (reset_deassert(&reset)) + pr_err("failed to deassert reset %d\n", i); + reset_free(&reset); + } + + /* dma global base address */ + addr = devfdt_get_addr_name(dev, "dma"); + if (addr == FDT_ADDR_T_NONE) + return -EINVAL; + priv->base = ioremap(addr, 0); + + /* dma channels base address */ + addr = devfdt_get_addr_name(dev, "dma-channels"); + if (addr == FDT_ADDR_T_NONE) + return -EINVAL; + priv->chan = ioremap(addr, 0); + + /* dma sram base address */ + addr = devfdt_get_addr_name(dev, "dma-sram"); + if (addr == FDT_ADDR_T_NONE) + return -EINVAL; + priv->sram = ioremap(addr, 0); + + /* disable dma controller */ + clrbits_be32(priv->base + DMA_CFG_REG, DMA_CFG_ENABLE_MASK); + + /* get number of channels */ + priv->n_channels = fdtdec_get_uint(gd->fdt_blob, dev_of_offset(dev), + "dma-channels", 8); + if (priv->n_channels > DMA_CHAN_MAX) + return -EINVAL; + + /* alloc channel private data pointers */ + priv->ch_priv = calloc(priv->n_channels, + sizeof(struct bcm6348_chan_priv*)); + if (!priv->ch_priv) + return -ENOMEM; + + /* stop dma channels */ + for (ch = 0; ch < priv->n_channels; ch++) + bcm6348_iudma_chan_stop(priv, ch); + + /* enable dma controller */ + setbits_be32(priv->base + DMA_CFG_REG, DMA_CFG_ENABLE_MASK); + + return 0; +} + +U_BOOT_DRIVER(bcm6348_iudma) = { + .name = "bcm6348_iudma", + .id = UCLASS_DMA, + .of_match = bcm6348_iudma_ids, + .ops = &bcm6348_iudma_ops, + .priv_auto_alloc_size = sizeof(struct bcm6348_iudma_priv), + .probe = bcm6348_iudma_probe, +};