diff options
Diffstat (limited to 'target/linux/ipq806x/patches/0045-dmaengine-add-Qualcomm-BAM-dma-driver.patch')
-rw-r--r-- | target/linux/ipq806x/patches/0045-dmaengine-add-Qualcomm-BAM-dma-driver.patch | 1174 |
1 files changed, 1174 insertions, 0 deletions
diff --git a/target/linux/ipq806x/patches/0045-dmaengine-add-Qualcomm-BAM-dma-driver.patch b/target/linux/ipq806x/patches/0045-dmaengine-add-Qualcomm-BAM-dma-driver.patch new file mode 100644 index 0000000000..6dcba78ce5 --- /dev/null +++ b/target/linux/ipq806x/patches/0045-dmaengine-add-Qualcomm-BAM-dma-driver.patch @@ -0,0 +1,1174 @@ +From 23dd43da9c885789b3d5aceed3e401345f8e8106 Mon Sep 17 00:00:00 2001 +From: Andy Gross <agross@codeaurora.org> +Date: Sat, 29 Mar 2014 18:53:16 +0530 +Subject: [PATCH 045/182] dmaengine: add Qualcomm BAM dma driver + +Add the DMA engine driver for the QCOM Bus Access Manager (BAM) DMA controller +found in the MSM 8x74 platforms. + +Each BAM DMA device is associated with a specific on-chip peripheral. Each +channel provides a uni-directional data transfer engine that is capable of +transferring data between the peripheral and system memory (System mode), or +between two peripherals (BAM2BAM). + +The initial release of this driver only supports slave transfers between +peripherals and system memory. + +Signed-off-by: Andy Gross <agross@codeaurora.org> +Tested-by: Stanimir Varbanov <svarbanov@mm-sol.com> +Signed-off-by: Vinod Koul <vinod.koul@intel.com> +--- + drivers/dma/Kconfig | 9 + + drivers/dma/Makefile | 2 + + drivers/dma/qcom_bam_dma.c | 1111 ++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 1122 insertions(+) + create mode 100644 drivers/dma/qcom_bam_dma.c + +diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig +index 605b016..f87cef9 100644 +--- a/drivers/dma/Kconfig ++++ b/drivers/dma/Kconfig +@@ -401,4 +401,13 @@ config DMATEST + config DMA_ENGINE_RAID + bool + ++config QCOM_BAM_DMA ++ tristate "QCOM BAM DMA support" ++ depends on ARCH_QCOM || (COMPILE_TEST && OF && ARM) ++ select DMA_ENGINE ++ select DMA_VIRTUAL_CHANNELS ++ ---help--- ++ Enable support for the QCOM BAM DMA controller. This controller ++ provides DMA capabilities for a variety of on-chip devices. ++ + endif +diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile +index a029d0f4..5150c82 100644 +--- a/drivers/dma/Makefile ++++ b/drivers/dma/Makefile +@@ -44,3 +44,5 @@ obj-$(CONFIG_DMA_JZ4740) += dma-jz4740.o + obj-$(CONFIG_TI_CPPI41) += cppi41.o + obj-$(CONFIG_K3_DMA) += k3dma.o + obj-$(CONFIG_MOXART_DMA) += moxart-dma.o ++obj-$(CONFIG_FSL_EDMA) += fsl-edma.o ++obj-$(CONFIG_QCOM_BAM_DMA) += qcom_bam_dma.o +diff --git a/drivers/dma/qcom_bam_dma.c b/drivers/dma/qcom_bam_dma.c +new file mode 100644 +index 0000000..82c9231 +--- /dev/null ++++ b/drivers/dma/qcom_bam_dma.c +@@ -0,0 +1,1111 @@ ++/* ++ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ */ ++/* ++ * QCOM BAM DMA engine driver ++ * ++ * QCOM BAM DMA blocks are distributed amongst a number of the on-chip ++ * peripherals on the MSM 8x74. The configuration of the channels are dependent ++ * on the way they are hard wired to that specific peripheral. The peripheral ++ * device tree entries specify the configuration of each channel. ++ * ++ * The DMA controller requires the use of external memory for storage of the ++ * hardware descriptors for each channel. The descriptor FIFO is accessed as a ++ * circular buffer and operations are managed according to the offset within the ++ * FIFO. After pipe/channel reset, all of the pipe registers and internal state ++ * are back to defaults. ++ * ++ * During DMA operations, we write descriptors to the FIFO, being careful to ++ * handle wrapping and then write the last FIFO offset to that channel's ++ * P_EVNT_REG register to kick off the transaction. The P_SW_OFSTS register ++ * indicates the current FIFO offset that is being processed, so there is some ++ * indication of where the hardware is currently working. ++ */ ++ ++#include <linux/kernel.h> ++#include <linux/io.h> ++#include <linux/init.h> ++#include <linux/slab.h> ++#include <linux/module.h> ++#include <linux/interrupt.h> ++#include <linux/dma-mapping.h> ++#include <linux/scatterlist.h> ++#include <linux/device.h> ++#include <linux/platform_device.h> ++#include <linux/of.h> ++#include <linux/of_address.h> ++#include <linux/of_irq.h> ++#include <linux/of_dma.h> ++#include <linux/clk.h> ++#include <linux/dmaengine.h> ++ ++#include "dmaengine.h" ++#include "virt-dma.h" ++ ++struct bam_desc_hw { ++ u32 addr; /* Buffer physical address */ ++ u16 size; /* Buffer size in bytes */ ++ u16 flags; ++}; ++ ++#define DESC_FLAG_INT BIT(15) ++#define DESC_FLAG_EOT BIT(14) ++#define DESC_FLAG_EOB BIT(13) ++ ++struct bam_async_desc { ++ struct virt_dma_desc vd; ++ ++ u32 num_desc; ++ u32 xfer_len; ++ struct bam_desc_hw *curr_desc; ++ ++ enum dma_transfer_direction dir; ++ size_t length; ++ struct bam_desc_hw desc[0]; ++}; ++ ++#define BAM_CTRL 0x0000 ++#define BAM_REVISION 0x0004 ++#define BAM_SW_REVISION 0x0080 ++#define BAM_NUM_PIPES 0x003C ++#define BAM_TIMER 0x0040 ++#define BAM_TIMER_CTRL 0x0044 ++#define BAM_DESC_CNT_TRSHLD 0x0008 ++#define BAM_IRQ_SRCS 0x000C ++#define BAM_IRQ_SRCS_MSK 0x0010 ++#define BAM_IRQ_SRCS_UNMASKED 0x0030 ++#define BAM_IRQ_STTS 0x0014 ++#define BAM_IRQ_CLR 0x0018 ++#define BAM_IRQ_EN 0x001C ++#define BAM_CNFG_BITS 0x007C ++#define BAM_IRQ_SRCS_EE(ee) (0x0800 + ((ee) * 0x80)) ++#define BAM_IRQ_SRCS_MSK_EE(ee) (0x0804 + ((ee) * 0x80)) ++#define BAM_P_CTRL(pipe) (0x1000 + ((pipe) * 0x1000)) ++#define BAM_P_RST(pipe) (0x1004 + ((pipe) * 0x1000)) ++#define BAM_P_HALT(pipe) (0x1008 + ((pipe) * 0x1000)) ++#define BAM_P_IRQ_STTS(pipe) (0x1010 + ((pipe) * 0x1000)) ++#define BAM_P_IRQ_CLR(pipe) (0x1014 + ((pipe) * 0x1000)) ++#define BAM_P_IRQ_EN(pipe) (0x1018 + ((pipe) * 0x1000)) ++#define BAM_P_EVNT_DEST_ADDR(pipe) (0x182C + ((pipe) * 0x1000)) ++#define BAM_P_EVNT_REG(pipe) (0x1818 + ((pipe) * 0x1000)) ++#define BAM_P_SW_OFSTS(pipe) (0x1800 + ((pipe) * 0x1000)) ++#define BAM_P_DATA_FIFO_ADDR(pipe) (0x1824 + ((pipe) * 0x1000)) ++#define BAM_P_DESC_FIFO_ADDR(pipe) (0x181C + ((pipe) * 0x1000)) ++#define BAM_P_EVNT_TRSHLD(pipe) (0x1828 + ((pipe) * 0x1000)) ++#define BAM_P_FIFO_SIZES(pipe) (0x1820 + ((pipe) * 0x1000)) ++ ++/* BAM CTRL */ ++#define BAM_SW_RST BIT(0) ++#define BAM_EN BIT(1) ++#define BAM_EN_ACCUM BIT(4) ++#define BAM_TESTBUS_SEL_SHIFT 5 ++#define BAM_TESTBUS_SEL_MASK 0x3F ++#define BAM_DESC_CACHE_SEL_SHIFT 13 ++#define BAM_DESC_CACHE_SEL_MASK 0x3 ++#define BAM_CACHED_DESC_STORE BIT(15) ++#define IBC_DISABLE BIT(16) ++ ++/* BAM REVISION */ ++#define REVISION_SHIFT 0 ++#define REVISION_MASK 0xFF ++#define NUM_EES_SHIFT 8 ++#define NUM_EES_MASK 0xF ++#define CE_BUFFER_SIZE BIT(13) ++#define AXI_ACTIVE BIT(14) ++#define USE_VMIDMT BIT(15) ++#define SECURED BIT(16) ++#define BAM_HAS_NO_BYPASS BIT(17) ++#define HIGH_FREQUENCY_BAM BIT(18) ++#define INACTIV_TMRS_EXST BIT(19) ++#define NUM_INACTIV_TMRS BIT(20) ++#define DESC_CACHE_DEPTH_SHIFT 21 ++#define DESC_CACHE_DEPTH_1 (0 << DESC_CACHE_DEPTH_SHIFT) ++#define DESC_CACHE_DEPTH_2 (1 << DESC_CACHE_DEPTH_SHIFT) ++#define DESC_CACHE_DEPTH_3 (2 << DESC_CACHE_DEPTH_SHIFT) ++#define DESC_CACHE_DEPTH_4 (3 << DESC_CACHE_DEPTH_SHIFT) ++#define CMD_DESC_EN BIT(23) ++#define INACTIV_TMR_BASE_SHIFT 24 ++#define INACTIV_TMR_BASE_MASK 0xFF ++ ++/* BAM NUM PIPES */ ++#define BAM_NUM_PIPES_SHIFT 0 ++#define BAM_NUM_PIPES_MASK 0xFF ++#define PERIPH_NON_PIPE_GRP_SHIFT 16 ++#define PERIPH_NON_PIP_GRP_MASK 0xFF ++#define BAM_NON_PIPE_GRP_SHIFT 24 ++#define BAM_NON_PIPE_GRP_MASK 0xFF ++ ++/* BAM CNFG BITS */ ++#define BAM_PIPE_CNFG BIT(2) ++#define BAM_FULL_PIPE BIT(11) ++#define BAM_NO_EXT_P_RST BIT(12) ++#define BAM_IBC_DISABLE BIT(13) ++#define BAM_SB_CLK_REQ BIT(14) ++#define BAM_PSM_CSW_REQ BIT(15) ++#define BAM_PSM_P_RES BIT(16) ++#define BAM_AU_P_RES BIT(17) ++#define BAM_SI_P_RES BIT(18) ++#define BAM_WB_P_RES BIT(19) ++#define BAM_WB_BLK_CSW BIT(20) ++#define BAM_WB_CSW_ACK_IDL BIT(21) ++#define BAM_WB_RETR_SVPNT BIT(22) ++#define BAM_WB_DSC_AVL_P_RST BIT(23) ++#define BAM_REG_P_EN BIT(24) ++#define BAM_PSM_P_HD_DATA BIT(25) ++#define BAM_AU_ACCUMED BIT(26) ++#define BAM_CMD_ENABLE BIT(27) ++ ++#define BAM_CNFG_BITS_DEFAULT (BAM_PIPE_CNFG | \ ++ BAM_NO_EXT_P_RST | \ ++ BAM_IBC_DISABLE | \ ++ BAM_SB_CLK_REQ | \ ++ BAM_PSM_CSW_REQ | \ ++ BAM_PSM_P_RES | \ ++ BAM_AU_P_RES | \ ++ BAM_SI_P_RES | \ ++ BAM_WB_P_RES | \ ++ BAM_WB_BLK_CSW | \ ++ BAM_WB_CSW_ACK_IDL | \ ++ BAM_WB_RETR_SVPNT | \ ++ BAM_WB_DSC_AVL_P_RST | \ ++ BAM_REG_P_EN | \ ++ BAM_PSM_P_HD_DATA | \ ++ BAM_AU_ACCUMED | \ ++ BAM_CMD_ENABLE) ++ ++/* PIPE CTRL */ ++#define P_EN BIT(1) ++#define P_DIRECTION BIT(3) ++#define P_SYS_STRM BIT(4) ++#define P_SYS_MODE BIT(5) ++#define P_AUTO_EOB BIT(6) ++#define P_AUTO_EOB_SEL_SHIFT 7 ++#define P_AUTO_EOB_SEL_512 (0 << P_AUTO_EOB_SEL_SHIFT) ++#define P_AUTO_EOB_SEL_256 (1 << P_AUTO_EOB_SEL_SHIFT) ++#define P_AUTO_EOB_SEL_128 (2 << P_AUTO_EOB_SEL_SHIFT) ++#define P_AUTO_EOB_SEL_64 (3 << P_AUTO_EOB_SEL_SHIFT) ++#define P_PREFETCH_LIMIT_SHIFT 9 ++#define P_PREFETCH_LIMIT_32 (0 << P_PREFETCH_LIMIT_SHIFT) ++#define P_PREFETCH_LIMIT_16 (1 << P_PREFETCH_LIMIT_SHIFT) ++#define P_PREFETCH_LIMIT_4 (2 << P_PREFETCH_LIMIT_SHIFT) ++#define P_WRITE_NWD BIT(11) ++#define P_LOCK_GROUP_SHIFT 16 ++#define P_LOCK_GROUP_MASK 0x1F ++ ++/* BAM_DESC_CNT_TRSHLD */ ++#define CNT_TRSHLD 0xffff ++#define DEFAULT_CNT_THRSHLD 0x4 ++ ++/* BAM_IRQ_SRCS */ ++#define BAM_IRQ BIT(31) ++#define P_IRQ 0x7fffffff ++ ++/* BAM_IRQ_SRCS_MSK */ ++#define BAM_IRQ_MSK BAM_IRQ ++#define P_IRQ_MSK P_IRQ ++ ++/* BAM_IRQ_STTS */ ++#define BAM_TIMER_IRQ BIT(4) ++#define BAM_EMPTY_IRQ BIT(3) ++#define BAM_ERROR_IRQ BIT(2) ++#define BAM_HRESP_ERR_IRQ BIT(1) ++ ++/* BAM_IRQ_CLR */ ++#define BAM_TIMER_CLR BIT(4) ++#define BAM_EMPTY_CLR BIT(3) ++#define BAM_ERROR_CLR BIT(2) ++#define BAM_HRESP_ERR_CLR BIT(1) ++ ++/* BAM_IRQ_EN */ ++#define BAM_TIMER_EN BIT(4) ++#define BAM_EMPTY_EN BIT(3) ++#define BAM_ERROR_EN BIT(2) ++#define BAM_HRESP_ERR_EN BIT(1) ++ ++/* BAM_P_IRQ_EN */ ++#define P_PRCSD_DESC_EN BIT(0) ++#define P_TIMER_EN BIT(1) ++#define P_WAKE_EN BIT(2) ++#define P_OUT_OF_DESC_EN BIT(3) ++#define P_ERR_EN BIT(4) ++#define P_TRNSFR_END_EN BIT(5) ++#define P_DEFAULT_IRQS_EN (P_PRCSD_DESC_EN | P_ERR_EN | P_TRNSFR_END_EN) ++ ++/* BAM_P_SW_OFSTS */ ++#define P_SW_OFSTS_MASK 0xffff ++ ++#define BAM_DESC_FIFO_SIZE SZ_32K ++#define MAX_DESCRIPTORS (BAM_DESC_FIFO_SIZE / sizeof(struct bam_desc_hw) - 1) ++#define BAM_MAX_DATA_SIZE (SZ_32K - 8) ++ ++struct bam_chan { ++ struct virt_dma_chan vc; ++ ++ struct bam_device *bdev; ++ ++ /* configuration from device tree */ ++ u32 id; ++ ++ struct bam_async_desc *curr_txd; /* current running dma */ ++ ++ /* runtime configuration */ ++ struct dma_slave_config slave; ++ ++ /* fifo storage */ ++ struct bam_desc_hw *fifo_virt; ++ dma_addr_t fifo_phys; ++ ++ /* fifo markers */ ++ unsigned short head; /* start of active descriptor entries */ ++ unsigned short tail; /* end of active descriptor entries */ ++ ++ unsigned int initialized; /* is the channel hw initialized? */ ++ unsigned int paused; /* is the channel paused? */ ++ unsigned int reconfigure; /* new slave config? */ ++ ++ struct list_head node; ++}; ++ ++static inline struct bam_chan *to_bam_chan(struct dma_chan *common) ++{ ++ return container_of(common, struct bam_chan, vc.chan); ++} ++ ++struct bam_device { ++ void __iomem *regs; ++ struct device *dev; ++ struct dma_device common; ++ struct device_dma_parameters dma_parms; ++ struct bam_chan *channels; ++ u32 num_channels; ++ ++ /* execution environment ID, from DT */ ++ u32 ee; ++ ++ struct clk *bamclk; ++ int irq; ++ ++ /* dma start transaction tasklet */ ++ struct tasklet_struct task; ++}; ++ ++/** ++ * bam_reset_channel - Reset individual BAM DMA channel ++ * @bchan: bam channel ++ * ++ * This function resets a specific BAM channel ++ */ ++static void bam_reset_channel(struct bam_chan *bchan) ++{ ++ struct bam_device *bdev = bchan->bdev; ++ ++ lockdep_assert_held(&bchan->vc.lock); ++ ++ /* reset channel */ ++ writel_relaxed(1, bdev->regs + BAM_P_RST(bchan->id)); ++ writel_relaxed(0, bdev->regs + BAM_P_RST(bchan->id)); ++ ++ /* don't allow cpu to reorder BAM register accesses done after this */ ++ wmb(); ++ ++ /* make sure hw is initialized when channel is used the first time */ ++ bchan->initialized = 0; ++} ++ ++/** ++ * bam_chan_init_hw - Initialize channel hardware ++ * @bchan: bam channel ++ * ++ * This function resets and initializes the BAM channel ++ */ ++static void bam_chan_init_hw(struct bam_chan *bchan, ++ enum dma_transfer_direction dir) ++{ ++ struct bam_device *bdev = bchan->bdev; ++ u32 val; ++ ++ /* Reset the channel to clear internal state of the FIFO */ ++ bam_reset_channel(bchan); ++ ++ /* ++ * write out 8 byte aligned address. We have enough space for this ++ * because we allocated 1 more descriptor (8 bytes) than we can use ++ */ ++ writel_relaxed(ALIGN(bchan->fifo_phys, sizeof(struct bam_desc_hw)), ++ bdev->regs + BAM_P_DESC_FIFO_ADDR(bchan->id)); ++ writel_relaxed(BAM_DESC_FIFO_SIZE, bdev->regs + ++ BAM_P_FIFO_SIZES(bchan->id)); ++ ++ /* enable the per pipe interrupts, enable EOT, ERR, and INT irqs */ ++ writel_relaxed(P_DEFAULT_IRQS_EN, bdev->regs + BAM_P_IRQ_EN(bchan->id)); ++ ++ /* unmask the specific pipe and EE combo */ ++ val = readl_relaxed(bdev->regs + BAM_IRQ_SRCS_MSK_EE(bdev->ee)); ++ val |= BIT(bchan->id); ++ writel_relaxed(val, bdev->regs + BAM_IRQ_SRCS_MSK_EE(bdev->ee)); ++ ++ /* don't allow cpu to reorder the channel enable done below */ ++ wmb(); ++ ++ /* set fixed direction and mode, then enable channel */ ++ val = P_EN | P_SYS_MODE; ++ if (dir == DMA_DEV_TO_MEM) ++ val |= P_DIRECTION; ++ ++ writel_relaxed(val, bdev->regs + BAM_P_CTRL(bchan->id)); ++ ++ bchan->initialized = 1; ++ ++ /* init FIFO pointers */ ++ bchan->head = 0; ++ bchan->tail = 0; ++} ++ ++/** ++ * bam_alloc_chan - Allocate channel resources for DMA channel. ++ * @chan: specified channel ++ * ++ * This function allocates the FIFO descriptor memory ++ */ ++static int bam_alloc_chan(struct dma_chan *chan) ++{ ++ struct bam_chan *bchan = to_bam_chan(chan); ++ struct bam_device *bdev = bchan->bdev; ++ ++ if (bchan->fifo_virt) ++ return 0; ++ ++ /* allocate FIFO descriptor space, but only if necessary */ ++ bchan->fifo_virt = dma_alloc_writecombine(bdev->dev, BAM_DESC_FIFO_SIZE, ++ &bchan->fifo_phys, GFP_KERNEL); ++ ++ if (!bchan->fifo_virt) { ++ dev_err(bdev->dev, "Failed to allocate desc fifo\n"); ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++/** ++ * bam_free_chan - Frees dma resources associated with specific channel ++ * @chan: specified channel ++ * ++ * Free the allocated fifo descriptor memory and channel resources ++ * ++ */ ++static void bam_free_chan(struct dma_chan *chan) ++{ ++ struct bam_chan *bchan = to_bam_chan(chan); ++ struct bam_device *bdev = bchan->bdev; ++ u32 val; ++ unsigned long flags; ++ ++ vchan_free_chan_resources(to_virt_chan(chan)); ++ ++ if (bchan->curr_txd) { ++ dev_err(bchan->bdev->dev, "Cannot free busy channel\n"); ++ return; ++ } ++ ++ spin_lock_irqsave(&bchan->vc.lock, flags); ++ bam_reset_channel(bchan); ++ spin_unlock_irqrestore(&bchan->vc.lock, flags); ++ ++ dma_free_writecombine(bdev->dev, BAM_DESC_FIFO_SIZE, bchan->fifo_virt, ++ bchan->fifo_phys); ++ bchan->fifo_virt = NULL; ++ ++ /* mask irq for pipe/channel */ ++ val = readl_relaxed(bdev->regs + BAM_IRQ_SRCS_MSK_EE(bdev->ee)); ++ val &= ~BIT(bchan->id); ++ writel_relaxed(val, bdev->regs + BAM_IRQ_SRCS_MSK_EE(bdev->ee)); ++ ++ /* disable irq */ ++ writel_relaxed(0, bdev->regs + BAM_P_IRQ_EN(bchan->id)); ++} ++ ++/** ++ * bam_slave_config - set slave configuration for channel ++ * @chan: dma channel ++ * @cfg: slave configuration ++ * ++ * Sets slave configuration for channel ++ * ++ */ ++static void bam_slave_config(struct bam_chan *bchan, ++ struct dma_slave_config *cfg) ++{ ++ memcpy(&bchan->slave, cfg, sizeof(*cfg)); ++ bchan->reconfigure = 1; ++} ++ ++/** ++ * bam_prep_slave_sg - Prep slave sg transaction ++ * ++ * @chan: dma channel ++ * @sgl: scatter gather list ++ * @sg_len: length of sg ++ * @direction: DMA transfer direction ++ * @flags: DMA flags ++ * @context: transfer context (unused) ++ */ ++static struct dma_async_tx_descriptor *bam_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 bam_chan *bchan = to_bam_chan(chan); ++ struct bam_device *bdev = bchan->bdev; ++ struct bam_async_desc *async_desc; ++ struct scatterlist *sg; ++ u32 i; ++ struct bam_desc_hw *desc; ++ unsigned int num_alloc = 0; ++ ++ ++ if (!is_slave_direction(direction)) { ++ dev_err(bdev->dev, "invalid dma direction\n"); ++ return NULL; ++ } ++ ++ /* calculate number of required entries */ ++ for_each_sg(sgl, sg, sg_len, i) ++ num_alloc += DIV_ROUND_UP(sg_dma_len(sg), BAM_MAX_DATA_SIZE); ++ ++ /* allocate enough room to accomodate the number of entries */ ++ async_desc = kzalloc(sizeof(*async_desc) + ++ (num_alloc * sizeof(struct bam_desc_hw)), GFP_NOWAIT); ++ ++ if (!async_desc) ++ goto err_out; ++ ++ async_desc->num_desc = num_alloc; ++ async_desc->curr_desc = async_desc->desc; ++ async_desc->dir = direction; ++ ++ /* fill in temporary descriptors */ ++ desc = async_desc->desc; ++ for_each_sg(sgl, sg, sg_len, i) { ++ unsigned int remainder = sg_dma_len(sg); ++ unsigned int curr_offset = 0; ++ ++ do { ++ desc->addr = sg_dma_address(sg) + curr_offset; ++ ++ if (remainder > BAM_MAX_DATA_SIZE) { ++ desc->size = BAM_MAX_DATA_SIZE; ++ remainder -= BAM_MAX_DATA_SIZE; ++ curr_offset += BAM_MAX_DATA_SIZE; ++ } else { ++ desc->size = remainder; ++ remainder = 0; ++ } ++ ++ async_desc->length += desc->size; ++ desc++; ++ } while (remainder > 0); ++ } ++ ++ return vchan_tx_prep(&bchan->vc, &async_desc->vd, flags); ++ ++err_out: ++ kfree(async_desc); ++ return NULL; ++} ++ ++/** ++ * bam_dma_terminate_all - terminate all transactions on a channel ++ * @bchan: bam dma channel ++ * ++ * Dequeues and frees all transactions ++ * No callbacks are done ++ * ++ */ ++static void bam_dma_terminate_all(struct bam_chan *bchan) ++{ ++ unsigned long flag; ++ LIST_HEAD(head); ++ ++ /* remove all transactions, including active transaction */ ++ spin_lock_irqsave(&bchan->vc.lock, flag); ++ if (bchan->curr_txd) { ++ list_add(&bchan->curr_txd->vd.node, &bchan->vc.desc_issued); ++ bchan->curr_txd = NULL; ++ } ++ ++ vchan_get_all_descriptors(&bchan->vc, &head); ++ spin_unlock_irqrestore(&bchan->vc.lock, flag); ++ ++ vchan_dma_desc_free_list(&bchan->vc, &head); ++} ++ ++/** ++ * bam_control - DMA device control ++ * @chan: dma channel ++ * @cmd: control cmd ++ * @arg: cmd argument ++ * ++ * Perform DMA control command ++ * ++ */ ++static int bam_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, ++ unsigned long arg) ++{ ++ struct bam_chan *bchan = to_bam_chan(chan); ++ struct bam_device *bdev = bchan->bdev; ++ int ret = 0; ++ unsigned long flag; ++ ++ switch (cmd) { ++ case DMA_PAUSE: ++ spin_lock_irqsave(&bchan->vc.lock, flag); ++ writel_relaxed(1, bdev->regs + BAM_P_HALT(bchan->id)); ++ bchan->paused = 1; ++ spin_unlock_irqrestore(&bchan->vc.lock, flag); ++ break; ++ ++ case DMA_RESUME: ++ spin_lock_irqsave(&bchan->vc.lock, flag); ++ writel_relaxed(0, bdev->regs + BAM_P_HALT(bchan->id)); ++ bchan->paused = 0; ++ spin_unlock_irqrestore(&bchan->vc.lock, flag); ++ break; ++ ++ case DMA_TERMINATE_ALL: ++ bam_dma_terminate_all(bchan); ++ break; ++ ++ case DMA_SLAVE_CONFIG: ++ spin_lock_irqsave(&bchan->vc.lock, flag); ++ bam_slave_config(bchan, (struct dma_slave_config *)arg); ++ spin_unlock_irqrestore(&bchan->vc.lock, flag); ++ break; ++ ++ default: ++ ret = -ENXIO; ++ break; ++ } ++ ++ return ret; ++} ++ ++/** ++ * process_channel_irqs - processes the channel interrupts ++ * @bdev: bam controller ++ * ++ * This function processes the channel interrupts ++ * ++ */ ++static u32 process_channel_irqs(struct bam_device *bdev) ++{ ++ u32 i, srcs, pipe_stts; ++ unsigned long flags; ++ struct bam_async_desc *async_desc; ++ ++ srcs = readl_relaxed(bdev->regs + BAM_IRQ_SRCS_EE(bdev->ee)); ++ ++ /* return early if no pipe/channel interrupts are present */ ++ if (!(srcs & P_IRQ)) ++ return srcs; ++ ++ for (i = 0; i < bdev->num_channels; i++) { ++ struct bam_chan *bchan = &bdev->channels[i]; ++ ++ if (!(srcs & BIT(i))) ++ continue; ++ ++ /* clear pipe irq */ ++ pipe_stts = readl_relaxed(bdev->regs + ++ BAM_P_IRQ_STTS(i)); ++ ++ writel_relaxed(pipe_stts, bdev->regs + ++ BAM_P_IRQ_CLR(i)); ++ ++ spin_lock_irqsave(&bchan->vc.lock, flags); ++ async_desc = bchan->curr_txd; ++ ++ if (async_desc) { ++ async_desc->num_desc -= async_desc->xfer_len; ++ async_desc->curr_desc += async_desc->xfer_len; ++ bchan->curr_txd = NULL; ++ ++ /* manage FIFO */ ++ bchan->head += async_desc->xfer_len; ++ bchan->head %= MAX_DESCRIPTORS; ++ ++ /* ++ * if complete, process cookie. Otherwise ++ * push back to front of desc_issued so that ++ * it gets restarted by the tasklet ++ */ ++ if (!async_desc->num_desc) ++ vchan_cookie_complete(&async_desc->vd); ++ else ++ list_add(&async_desc->vd.node, ++ &bchan->vc.desc_issued); ++ } ++ ++ spin_unlock_irqrestore(&bchan->vc.lock, flags); ++ } ++ ++ return srcs; ++} ++ ++/** ++ * bam_dma_irq - irq handler for bam controller ++ * @irq: IRQ of interrupt ++ * @data: callback data ++ * ++ * IRQ handler for the bam controller ++ */ ++static irqreturn_t bam_dma_irq(int irq, void *data) ++{ ++ struct bam_device *bdev = data; ++ u32 clr_mask = 0, srcs = 0; ++ ++ srcs |= process_channel_irqs(bdev); ++ ++ /* kick off tasklet to start next dma transfer */ ++ if (srcs & P_IRQ) ++ tasklet_schedule(&bdev->task); ++ ++ if (srcs & BAM_IRQ) ++ clr_mask = readl_relaxed(bdev->regs + BAM_IRQ_STTS); ++ ++ /* don't allow reorder of the various accesses to the BAM registers */ ++ mb(); ++ ++ writel_relaxed(clr_mask, bdev->regs + BAM_IRQ_CLR); ++ ++ return IRQ_HANDLED; ++} ++ ++/** ++ * bam_tx_status - returns status of transaction ++ * @chan: dma channel ++ * @cookie: transaction cookie ++ * @txstate: DMA transaction state ++ * ++ * Return status of dma transaction ++ */ ++static enum dma_status bam_tx_status(struct dma_chan *chan, dma_cookie_t cookie, ++ struct dma_tx_state *txstate) ++{ ++ struct bam_chan *bchan = to_bam_chan(chan); ++ struct virt_dma_desc *vd; ++ int ret; ++ size_t residue = 0; ++ unsigned int i; ++ unsigned long flags; ++ ++ ret = dma_cookie_status(chan, cookie, txstate); ++ if (ret == DMA_COMPLETE) ++ return ret; ++ ++ if (!txstate) ++ return bchan->paused ? DMA_PAUSED : ret; ++ ++ spin_lock_irqsave(&bchan->vc.lock, flags); ++ vd = vchan_find_desc(&bchan->vc, cookie); ++ if (vd) ++ residue = container_of(vd, struct bam_async_desc, vd)->length; ++ else if (bchan->curr_txd && bchan->curr_txd->vd.tx.cookie == cookie) ++ for (i = 0; i < bchan->curr_txd->num_desc; i++) ++ residue += bchan->curr_txd->curr_desc[i].size; ++ ++ spin_unlock_irqrestore(&bchan->vc.lock, flags); ++ ++ dma_set_residue(txstate, residue); ++ ++ if (ret == DMA_IN_PROGRESS && bchan->paused) ++ ret = DMA_PAUSED; ++ ++ return ret; ++} ++ ++/** ++ * bam_apply_new_config ++ * @bchan: bam dma channel ++ * @dir: DMA direction ++ */ ++static void bam_apply_new_config(struct bam_chan *bchan, ++ enum dma_transfer_direction dir) ++{ ++ struct bam_device *bdev = bchan->bdev; ++ u32 maxburst; ++ ++ if (dir == DMA_DEV_TO_MEM) ++ maxburst = bchan->slave.src_maxburst; ++ else ++ maxburst = bchan->slave.dst_maxburst; ++ ++ writel_relaxed(maxburst, bdev->regs + BAM_DESC_CNT_TRSHLD); ++ ++ bchan->reconfigure = 0; ++} ++ ++/** ++ * bam_start_dma - start next transaction ++ * @bchan - bam dma channel ++ */ ++static void bam_start_dma(struct bam_chan *bchan) ++{ ++ struct virt_dma_desc *vd = vchan_next_desc(&bchan->vc); ++ struct bam_device *bdev = bchan->bdev; ++ struct bam_async_desc *async_desc; ++ struct bam_desc_hw *desc; ++ struct bam_desc_hw *fifo = PTR_ALIGN(bchan->fifo_virt, ++ sizeof(struct bam_desc_hw)); ++ ++ lockdep_assert_held(&bchan->vc.lock); ++ ++ if (!vd) ++ return; ++ ++ list_del(&vd->node); ++ ++ async_desc = container_of(vd, struct bam_async_desc, vd); ++ bchan->curr_txd = async_desc; ++ ++ /* on first use, initialize the channel hardware */ ++ if (!bchan->initialized) ++ bam_chan_init_hw(bchan, async_desc->dir); ++ ++ /* apply new slave config changes, if necessary */ ++ if (bchan->reconfigure) ++ bam_apply_new_config(bchan, async_desc->dir); ++ ++ desc = bchan->curr_txd->curr_desc; ++ ++ if (async_desc->num_desc > MAX_DESCRIPTORS) ++ async_desc->xfer_len = MAX_DESCRIPTORS; ++ else ++ async_desc->xfer_len = async_desc->num_desc; ++ ++ /* set INT on last descriptor */ ++ desc[async_desc->xfer_len - 1].flags |= DESC_FLAG_INT; ++ ++ if (bchan->tail + async_desc->xfer_len > MAX_DESCRIPTORS) { ++ u32 partial = MAX_DESCRIPTORS - bchan->tail; ++ ++ memcpy(&fifo[bchan->tail], desc, ++ partial * sizeof(struct bam_desc_hw)); ++ memcpy(fifo, &desc[partial], (async_desc->xfer_len - partial) * ++ sizeof(struct bam_desc_hw)); ++ } else { ++ memcpy(&fifo[bchan->tail], desc, ++ async_desc->xfer_len * sizeof(struct bam_desc_hw)); ++ } ++ ++ bchan->tail += async_desc->xfer_len; ++ bchan->tail %= MAX_DESCRIPTORS; ++ ++ /* ensure descriptor writes and dma start not reordered */ ++ wmb(); ++ writel_relaxed(bchan->tail * sizeof(struct bam_desc_hw), ++ bdev->regs + BAM_P_EVNT_REG(bchan->id)); ++} ++ ++/** ++ * dma_tasklet - DMA IRQ tasklet ++ * @data: tasklet argument (bam controller structure) ++ * ++ * Sets up next DMA operation and then processes all completed transactions ++ */ ++static void dma_tasklet(unsigned long data) ++{ ++ struct bam_device *bdev = (struct bam_device *)data; ++ struct bam_chan *bchan; ++ unsigned long flags; ++ unsigned int i; ++ ++ /* go through the channels and kick off transactions */ ++ for (i = 0; i < bdev->num_channels; i++) { ++ bchan = &bdev->channels[i]; ++ spin_lock_irqsave(&bchan->vc.lock, flags); ++ ++ if (!list_empty(&bchan->vc.desc_issued) && !bchan->curr_txd) ++ bam_start_dma(bchan); ++ spin_unlock_irqrestore(&bchan->vc.lock, flags); ++ } ++} ++ ++/** ++ * bam_issue_pending - starts pending transactions ++ * @chan: dma channel ++ * ++ * Calls tasklet directly which in turn starts any pending transactions ++ */ ++static void bam_issue_pending(struct dma_chan *chan) ++{ ++ struct bam_chan *bchan = to_bam_chan(chan); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&bchan->vc.lock, flags); ++ ++ /* if work pending and idle, start a transaction */ ++ if (vchan_issue_pending(&bchan->vc) && !bchan->curr_txd) ++ bam_start_dma(bchan); ++ ++ spin_unlock_irqrestore(&bchan->vc.lock, flags); ++} ++ ++/** ++ * bam_dma_free_desc - free descriptor memory ++ * @vd: virtual descriptor ++ * ++ */ ++static void bam_dma_free_desc(struct virt_dma_desc *vd) ++{ ++ struct bam_async_desc *async_desc = container_of(vd, ++ struct bam_async_desc, vd); ++ ++ kfree(async_desc); ++} ++ ++static struct dma_chan *bam_dma_xlate(struct of_phandle_args *dma_spec, ++ struct of_dma *of) ++{ ++ struct bam_device *bdev = container_of(of->of_dma_data, ++ struct bam_device, common); ++ unsigned int request; ++ ++ if (dma_spec->args_count != 1) ++ return NULL; ++ ++ request = dma_spec->args[0]; ++ if (request >= bdev->num_channels) ++ return NULL; ++ ++ return dma_get_slave_channel(&(bdev->channels[request].vc.chan)); ++} ++ ++/** ++ * bam_init ++ * @bdev: bam device ++ * ++ * Initialization helper for global bam registers ++ */ ++static int bam_init(struct bam_device *bdev) ++{ ++ u32 val; ++ ++ /* read revision and configuration information */ ++ val = readl_relaxed(bdev->regs + BAM_REVISION) >> NUM_EES_SHIFT; ++ val &= NUM_EES_MASK; ++ ++ /* check that configured EE is within range */ ++ if (bdev->ee >= val) ++ return -EINVAL; ++ ++ val = readl_relaxed(bdev->regs + BAM_NUM_PIPES); ++ bdev->num_channels = val & BAM_NUM_PIPES_MASK; ++ ++ /* s/w reset bam */ ++ /* after reset all pipes are disabled and idle */ ++ val = readl_relaxed(bdev->regs + BAM_CTRL); ++ val |= BAM_SW_RST; ++ writel_relaxed(val, bdev->regs + BAM_CTRL); ++ val &= ~BAM_SW_RST; ++ writel_relaxed(val, bdev->regs + BAM_CTRL); ++ ++ /* make sure previous stores are visible before enabling BAM */ ++ wmb(); ++ ++ /* enable bam */ ++ val |= BAM_EN; ++ writel_relaxed(val, bdev->regs + BAM_CTRL); ++ ++ /* set descriptor threshhold, start with 4 bytes */ ++ writel_relaxed(DEFAULT_CNT_THRSHLD, bdev->regs + BAM_DESC_CNT_TRSHLD); ++ ++ /* Enable default set of h/w workarounds, ie all except BAM_FULL_PIPE */ ++ writel_relaxed(BAM_CNFG_BITS_DEFAULT, bdev->regs + BAM_CNFG_BITS); ++ ++ /* enable irqs for errors */ ++ writel_relaxed(BAM_ERROR_EN | BAM_HRESP_ERR_EN, ++ bdev->regs + BAM_IRQ_EN); ++ ++ /* unmask global bam interrupt */ ++ writel_relaxed(BAM_IRQ_MSK, bdev->regs + BAM_IRQ_SRCS_MSK_EE(bdev->ee)); ++ ++ return 0; ++} ++ ++static void bam_channel_init(struct bam_device *bdev, struct bam_chan *bchan, ++ u32 index) ++{ ++ bchan->id = index; ++ bchan->bdev = bdev; ++ ++ vchan_init(&bchan->vc, &bdev->common); ++ bchan->vc.desc_free = bam_dma_free_desc; ++} ++ ++static int bam_dma_probe(struct platform_device *pdev) ++{ ++ struct bam_device *bdev; ++ struct resource *iores; ++ int ret, i; ++ ++ bdev = devm_kzalloc(&pdev->dev, sizeof(*bdev), GFP_KERNEL); ++ if (!bdev) ++ return -ENOMEM; ++ ++ bdev->dev = &pdev->dev; ++ ++ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ bdev->regs = devm_ioremap_resource(&pdev->dev, iores); ++ if (IS_ERR(bdev->regs)) ++ return PTR_ERR(bdev->regs); ++ ++ bdev->irq = platform_get_irq(pdev, 0); ++ if (bdev->irq < 0) ++ return bdev->irq; ++ ++ ret = of_property_read_u32(pdev->dev.of_node, "qcom,ee", &bdev->ee); ++ if (ret) { ++ dev_err(bdev->dev, "Execution environment unspecified\n"); ++ return ret; ++ } ++ ++ bdev->bamclk = devm_clk_get(bdev->dev, "bam_clk"); ++ if (IS_ERR(bdev->bamclk)) ++ return PTR_ERR(bdev->bamclk); ++ ++ ret = clk_prepare_enable(bdev->bamclk); ++ if (ret) { ++ dev_err(bdev->dev, "failed to prepare/enable clock\n"); ++ return ret; ++ } ++ ++ ret = bam_init(bdev); ++ if (ret) ++ goto err_disable_clk; ++ ++ tasklet_init(&bdev->task, dma_tasklet, (unsigned long)bdev); ++ ++ bdev->channels = devm_kcalloc(bdev->dev, bdev->num_channels, ++ sizeof(*bdev->channels), GFP_KERNEL); ++ ++ if (!bdev->channels) { ++ ret = -ENOMEM; ++ goto err_disable_clk; ++ } ++ ++ /* allocate and initialize channels */ ++ INIT_LIST_HEAD(&bdev->common.channels); ++ ++ for (i = 0; i < bdev->num_channels; i++) ++ bam_channel_init(bdev, &bdev->channels[i], i); ++ ++ ret = devm_request_irq(bdev->dev, bdev->irq, bam_dma_irq, ++ IRQF_TRIGGER_HIGH, "bam_dma", bdev); ++ if (ret) ++ goto err_disable_clk; ++ ++ /* set max dma segment size */ ++ bdev->common.dev = bdev->dev; ++ bdev->common.dev->dma_parms = &bdev->dma_parms; ++ ret = dma_set_max_seg_size(bdev->common.dev, BAM_MAX_DATA_SIZE); ++ if (ret) { ++ dev_err(bdev->dev, "cannot set maximum segment size\n"); ++ goto err_disable_clk; ++ } ++ ++ platform_set_drvdata(pdev, bdev); ++ ++ /* set capabilities */ ++ dma_cap_zero(bdev->common.cap_mask); ++ dma_cap_set(DMA_SLAVE, bdev->common.cap_mask); ++ ++ /* initialize dmaengine apis */ ++ bdev->common.device_alloc_chan_resources = bam_alloc_chan; ++ bdev->common.device_free_chan_resources = bam_free_chan; ++ bdev->common.device_prep_slave_sg = bam_prep_slave_sg; ++ bdev->common.device_control = bam_control; ++ bdev->common.device_issue_pending = bam_issue_pending; ++ bdev->common.device_tx_status = bam_tx_status; ++ bdev->common.dev = bdev->dev; ++ ++ ret = dma_async_device_register(&bdev->common); ++ if (ret) { ++ dev_err(bdev->dev, "failed to register dma async device\n"); ++ goto err_disable_clk; ++ } ++ ++ ret = of_dma_controller_register(pdev->dev.of_node, bam_dma_xlate, ++ &bdev->common); ++ if (ret) ++ goto err_unregister_dma; ++ ++ return 0; ++ ++err_unregister_dma: ++ dma_async_device_unregister(&bdev->common); ++err_disable_clk: ++ clk_disable_unprepare(bdev->bamclk); ++ return ret; ++} ++ ++static int bam_dma_remove(struct platform_device *pdev) ++{ ++ struct bam_device *bdev = platform_get_drvdata(pdev); ++ u32 i; ++ ++ of_dma_controller_free(pdev->dev.of_node); ++ dma_async_device_unregister(&bdev->common); ++ ++ /* mask all interrupts for this execution environment */ ++ writel_relaxed(0, bdev->regs + BAM_IRQ_SRCS_MSK_EE(bdev->ee)); ++ ++ devm_free_irq(bdev->dev, bdev->irq, bdev); ++ ++ for (i = 0; i < bdev->num_channels; i++) { ++ bam_dma_terminate_all(&bdev->channels[i]); ++ tasklet_kill(&bdev->channels[i].vc.task); ++ ++ dma_free_writecombine(bdev->dev, BAM_DESC_FIFO_SIZE, ++ bdev->channels[i].fifo_virt, ++ bdev->channels[i].fifo_phys); ++ } ++ ++ tasklet_kill(&bdev->task); ++ ++ clk_disable_unprepare(bdev->bamclk); ++ ++ return 0; ++} ++ ++static const struct of_device_id bam_of_match[] = { ++ { .compatible = "qcom,bam-v1.4.0", }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, bam_of_match); ++ ++static struct platform_driver bam_dma_driver = { ++ .probe = bam_dma_probe, ++ .remove = bam_dma_remove, ++ .driver = { ++ .name = "bam-dma-engine", ++ .owner = THIS_MODULE, ++ .of_match_table = bam_of_match, ++ }, ++}; ++ ++module_platform_driver(bam_dma_driver); ++ ++MODULE_AUTHOR("Andy Gross <agross@codeaurora.org>"); ++MODULE_DESCRIPTION("QCOM BAM DMA engine driver"); ++MODULE_LICENSE("GPL v2"); +-- +1.7.10.4 + |