diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2009-12-16 10:28:56 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-12-16 10:28:56 -0800 |
commit | 7949456b1b96924c2d9ae5aea5fa7d4c81c946ed (patch) | |
tree | 819e64dcd686c8b53c698c164aea96a002e8b5f8 | |
parent | 60d9aa758c00f20ade0cb1951f6a934f628dd2d7 (diff) | |
parent | 12458ea06efd7b44281e68fe59c950ec7d59c649 (diff) |
Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/djbw/async_tx
* 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/djbw/async_tx:
ppc440spe-adma: adds updated ppc440spe adma driver
iop-adma.c: use resource_size()
dmaengine: clarify the meaning of the DMA_CTRL_ACK flag
sh: stylistic improvements for the DMA driver
dmaengine: fix dmatest to verify minimum transfer length and test buffer size
sh: DMA driver has to specify its alignment requirements
Add COH 901 318 DMA block driver v5
-rw-r--r-- | Documentation/powerpc/dts-bindings/4xx/ppc440spe-adma.txt | 93 | ||||
-rw-r--r-- | arch/arm/mach-u300/include/mach/coh901318.h | 281 | ||||
-rw-r--r-- | arch/powerpc/include/asm/async_tx.h | 47 | ||||
-rw-r--r-- | arch/powerpc/include/asm/dcr-regs.h | 23 | ||||
-rw-r--r-- | drivers/dma/Kconfig | 18 | ||||
-rw-r--r-- | drivers/dma/Makefile | 2 | ||||
-rw-r--r-- | drivers/dma/coh901318.c | 1325 | ||||
-rw-r--r-- | drivers/dma/coh901318_lli.c | 318 | ||||
-rw-r--r-- | drivers/dma/coh901318_lli.h | 124 | ||||
-rw-r--r-- | drivers/dma/dmatest.c | 16 | ||||
-rw-r--r-- | drivers/dma/iop-adma.c | 4 | ||||
-rw-r--r-- | drivers/dma/ppc4xx/Makefile | 1 | ||||
-rw-r--r-- | drivers/dma/ppc4xx/adma.c | 5027 | ||||
-rw-r--r-- | drivers/dma/ppc4xx/adma.h | 195 | ||||
-rw-r--r-- | drivers/dma/ppc4xx/dma.h | 223 | ||||
-rw-r--r-- | drivers/dma/ppc4xx/xor.h | 110 | ||||
-rw-r--r-- | drivers/dma/shdma.c | 36 | ||||
-rw-r--r-- | drivers/dma/shdma.h | 14 | ||||
-rw-r--r-- | include/linux/dmaengine.h | 2 |
19 files changed, 7828 insertions, 31 deletions
diff --git a/Documentation/powerpc/dts-bindings/4xx/ppc440spe-adma.txt b/Documentation/powerpc/dts-bindings/4xx/ppc440spe-adma.txt new file mode 100644 index 00000000000..515ebcf1b97 --- /dev/null +++ b/Documentation/powerpc/dts-bindings/4xx/ppc440spe-adma.txt @@ -0,0 +1,93 @@ +PPC440SPe DMA/XOR (DMA Controller and XOR Accelerator) + +Device nodes needed for operation of the ppc440spe-adma driver +are specified hereby. These are I2O/DMA, DMA and XOR nodes +for DMA engines and Memory Queue Module node. The latter is used +by ADMA driver for configuration of RAID-6 H/W capabilities of +the PPC440SPe. In addition to the nodes and properties described +below, the ranges property of PLB node must specify ranges for +DMA devices. + + i) The I2O node + + Required properties: + + - compatible : "ibm,i2o-440spe"; + - reg : <registers mapping> + - dcr-reg : <DCR registers range> + + Example: + + I2O: i2o@400100000 { + compatible = "ibm,i2o-440spe"; + reg = <0x00000004 0x00100000 0x100>; + dcr-reg = <0x060 0x020>; + }; + + + ii) The DMA node + + Required properties: + + - compatible : "ibm,dma-440spe"; + - cell-index : 1 cell, hardware index of the DMA engine + (typically 0x0 and 0x1 for DMA0 and DMA1) + - reg : <registers mapping> + - dcr-reg : <DCR registers range> + - interrupts : <interrupt mapping for DMA0/1 interrupts sources: + 2 sources: DMAx CS FIFO Needs Service IRQ (on UIC0) + and DMA Error IRQ (on UIC1). The latter is common + for both DMA engines>. + - interrupt-parent : needed for interrupt mapping + + Example: + + DMA0: dma0@400100100 { + compatible = "ibm,dma-440spe"; + cell-index = <0>; + reg = <0x00000004 0x00100100 0x100>; + dcr-reg = <0x060 0x020>; + interrupt-parent = <&DMA0>; + interrupts = <0 1>; + #interrupt-cells = <1>; + #address-cells = <0>; + #size-cells = <0>; + interrupt-map = < + 0 &UIC0 0x14 4 + 1 &UIC1 0x16 4>; + }; + + + iii) XOR Accelerator node + + Required properties: + + - compatible : "amcc,xor-accelerator"; + - reg : <registers mapping> + - interrupts : <interrupt mapping for XOR interrupt source> + - interrupt-parent : for interrupt mapping + + Example: + + xor-accel@400200000 { + compatible = "amcc,xor-accelerator"; + reg = <0x00000004 0x00200000 0x400>; + interrupt-parent = <&UIC1>; + interrupts = <0x1f 4>; + }; + + + iv) Memory Queue Module node + + Required properties: + + - compatible : "ibm,mq-440spe"; + - dcr-reg : <DCR registers range> + + Example: + + MQ0: mq { + compatible = "ibm,mq-440spe"; + dcr-reg = <0x040 0x020>; + }; + diff --git a/arch/arm/mach-u300/include/mach/coh901318.h b/arch/arm/mach-u300/include/mach/coh901318.h new file mode 100644 index 00000000000..f4cfee9c7d2 --- /dev/null +++ b/arch/arm/mach-u300/include/mach/coh901318.h @@ -0,0 +1,281 @@ +/* + * + * include/linux/coh901318.h + * + * + * Copyright (C) 2007-2009 ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + * DMA driver for COH 901 318 + * Author: Per Friden <per.friden@stericsson.com> + */ + +#ifndef COH901318_H +#define COH901318_H + +#include <linux/device.h> +#include <linux/dmaengine.h> + +#define MAX_DMA_PACKET_SIZE_SHIFT 11 +#define MAX_DMA_PACKET_SIZE (1 << MAX_DMA_PACKET_SIZE_SHIFT) + +/** + * struct coh901318_lli - linked list item for DMAC + * @control: control settings for DMAC + * @src_addr: transfer source address + * @dst_addr: transfer destination address + * @link_addr: physical address to next lli + * @virt_link_addr: virtual addres of next lli (only used by pool_free) + * @phy_this: physical address of current lli (only used by pool_free) + */ +struct coh901318_lli { + u32 control; + dma_addr_t src_addr; + dma_addr_t dst_addr; + dma_addr_t link_addr; + + void *virt_link_addr; + dma_addr_t phy_this; +}; +/** + * struct coh901318_params - parameters for DMAC configuration + * @config: DMA config register + * @ctrl_lli_last: DMA control register for the last lli in the list + * @ctrl_lli: DMA control register for an lli + * @ctrl_lli_chained: DMA control register for a chained lli + */ +struct coh901318_params { + u32 config; + u32 ctrl_lli_last; + u32 ctrl_lli; + u32 ctrl_lli_chained; +}; +/** + * struct coh_dma_channel - dma channel base + * @name: ascii name of dma channel + * @number: channel id number + * @desc_nbr_max: number of preallocated descriptortors + * @priority_high: prio of channel, 0 low otherwise high. + * @param: configuration parameters + * @dev_addr: physical address of periphal connected to channel + */ +struct coh_dma_channel { + const char name[32]; + const int number; + const int desc_nbr_max; + const int priority_high; + const struct coh901318_params param; + const dma_addr_t dev_addr; +}; + +/** + * dma_access_memory_state_t - register dma for memory access + * + * @dev: The dma device + * @active: 1 means dma intends to access memory + * 0 means dma wont access memory + */ +typedef void (*dma_access_memory_state_t)(struct device *dev, + bool active); + +/** + * struct powersave - DMA power save structure + * @lock: lock protecting data in this struct + * @started_channels: bit mask indicating active dma channels + */ +struct powersave { + spinlock_t lock; + u64 started_channels; +}; +/** + * struct coh901318_platform - platform arch structure + * @chans_slave: specifying dma slave channels + * @chans_memcpy: specifying dma memcpy channels + * @access_memory_state: requesting DMA memeory access (on / off) + * @chan_conf: dma channel configurations + * @max_channels: max number of dma chanenls + */ +struct coh901318_platform { + const int *chans_slave; + const int *chans_memcpy; + const dma_access_memory_state_t access_memory_state; + const struct coh_dma_channel *chan_conf; + const int max_channels; +}; + +/** + * coh901318_get_bytes_left() - Get number of bytes left on a current transfer + * @chan: dma channel handle + * return number of bytes left, or negative on error + */ +u32 coh901318_get_bytes_left(struct dma_chan *chan); + +/** + * coh901318_stop() - Stops dma transfer + * @chan: dma channel handle + * return 0 on success otherwise negative value + */ +void coh901318_stop(struct dma_chan *chan); + +/** + * coh901318_continue() - Resumes a stopped dma transfer + * @chan: dma channel handle + * return 0 on success otherwise negative value + */ +void coh901318_continue(struct dma_chan *chan); + +/** + * coh901318_filter_id() - DMA channel filter function + * @chan: dma channel handle + * @chan_id: id of dma channel to be filter out + * + * In dma_request_channel() it specifies what channel id to be requested + */ +bool coh901318_filter_id(struct dma_chan *chan, void *chan_id); + +/* + * DMA Controller - this access the static mappings of the coh901318 dma. + * + */ + +#define COH901318_MOD32_MASK (0x1F) +#define COH901318_WORD_MASK (0xFFFFFFFF) +/* INT_STATUS - Interrupt Status Registers 32bit (R/-) */ +#define COH901318_INT_STATUS1 (0x0000) +#define COH901318_INT_STATUS2 (0x0004) +/* TC_INT_STATUS - Terminal Count Interrupt Status Registers 32bit (R/-) */ +#define COH901318_TC_INT_STATUS1 (0x0008) +#define COH901318_TC_INT_STATUS2 (0x000C) +/* TC_INT_CLEAR - Terminal Count Interrupt Clear Registers 32bit (-/W) */ +#define COH901318_TC_INT_CLEAR1 (0x0010) +#define COH901318_TC_INT_CLEAR2 (0x0014) +/* RAW_TC_INT_STATUS - Raw Term Count Interrupt Status Registers 32bit (R/-) */ +#define COH901318_RAW_TC_INT_STATUS1 (0x0018) +#define COH901318_RAW_TC_INT_STATUS2 (0x001C) +/* BE_INT_STATUS - Bus Error Interrupt Status Registers 32bit (R/-) */ +#define COH901318_BE_INT_STATUS1 (0x0020) +#define COH901318_BE_INT_STATUS2 (0x0024) +/* BE_INT_CLEAR - Bus Error Interrupt Clear Registers 32bit (-/W) */ +#define COH901318_BE_INT_CLEAR1 (0x0028) +#define COH901318_BE_INT_CLEAR2 (0x002C) +/* RAW_BE_INT_STATUS - Raw Term Count Interrupt Status Registers 32bit (R/-) */ +#define COH901318_RAW_BE_INT_STATUS1 (0x0030) +#define COH901318_RAW_BE_INT_STATUS2 (0x0034) + +/* + * CX_CFG - Channel Configuration Registers 32bit (R/W) + */ +#define COH901318_CX_CFG (0x0100) +#define COH901318_CX_CFG_SPACING (0x04) +/* Channel enable activates tha dma job */ +#define COH901318_CX_CFG_CH_ENABLE (0x00000001) +#define COH901318_CX_CFG_CH_DISABLE (0x00000000) +/* Request Mode */ +#define COH901318_CX_CFG_RM_MASK (0x00000006) +#define COH901318_CX_CFG_RM_MEMORY_TO_MEMORY (0x0 << 1) +#define COH901318_CX_CFG_RM_PRIMARY_TO_MEMORY (0x1 << 1) +#define COH901318_CX_CFG_RM_MEMORY_TO_PRIMARY (0x1 << 1) +#define COH901318_CX_CFG_RM_PRIMARY_TO_SECONDARY (0x3 << 1) +#define COH901318_CX_CFG_RM_SECONDARY_TO_PRIMARY (0x3 << 1) +/* Linked channel request field. RM must == 11 */ +#define COH901318_CX_CFG_LCRF_SHIFT 3 +#define COH901318_CX_CFG_LCRF_MASK (0x000001F8) +#define COH901318_CX_CFG_LCR_DISABLE (0x00000000) +/* Terminal Counter Interrupt Request Mask */ +#define COH901318_CX_CFG_TC_IRQ_ENABLE (0x00000200) +#define COH901318_CX_CFG_TC_IRQ_DISABLE (0x00000000) +/* Bus Error interrupt Mask */ +#define COH901318_CX_CFG_BE_IRQ_ENABLE (0x00000400) +#define COH901318_CX_CFG_BE_IRQ_DISABLE (0x00000000) + +/* + * CX_STAT - Channel Status Registers 32bit (R/-) + */ +#define COH901318_CX_STAT (0x0200) +#define COH901318_CX_STAT_SPACING (0x04) +#define COH901318_CX_STAT_RBE_IRQ_IND (0x00000008) +#define COH901318_CX_STAT_RTC_IRQ_IND (0x00000004) +#define COH901318_CX_STAT_ACTIVE (0x00000002) +#define COH901318_CX_STAT_ENABLED (0x00000001) + +/* + * CX_CTRL - Channel Control Registers 32bit (R/W) + */ +#define COH901318_CX_CTRL (0x0400) +#define COH901318_CX_CTRL_SPACING (0x10) +/* Transfer Count Enable */ +#define COH901318_CX_CTRL_TC_ENABLE (0x00001000) +#define COH901318_CX_CTRL_TC_DISABLE (0x00000000) +/* Transfer Count Value 0 - 4095 */ +#define COH901318_CX_CTRL_TC_VALUE_MASK (0x00000FFF) +/* Burst count */ +#define COH901318_CX_CTRL_BURST_COUNT_MASK (0x0000E000) +#define COH901318_CX_CTRL_BURST_COUNT_64_BYTES (0x7 << 13) +#define COH901318_CX_CTRL_BURST_COUNT_48_BYTES (0x6 << 13) +#define COH901318_CX_CTRL_BURST_COUNT_32_BYTES (0x5 << 13) +#define COH901318_CX_CTRL_BURST_COUNT_16_BYTES (0x4 << 13) +#define COH901318_CX_CTRL_BURST_COUNT_8_BYTES (0x3 << 13) +#define COH901318_CX_CTRL_BURST_COUNT_4_BYTES (0x2 << 13) +#define COH901318_CX_CTRL_BURST_COUNT_2_BYTES (0x1 << 13) +#define COH901318_CX_CTRL_BURST_COUNT_1_BYTE (0x0 << 13) +/* Source bus size */ +#define COH901318_CX_CTRL_SRC_BUS_SIZE_MASK (0x00030000) +#define COH901318_CX_CTRL_SRC_BUS_SIZE_32_BITS (0x2 << 16) +#define COH901318_CX_CTRL_SRC_BUS_SIZE_16_BITS (0x1 << 16) +#define COH901318_CX_CTRL_SRC_BUS_SIZE_8_BITS (0x0 << 16) +/* Source address increment */ +#define COH901318_CX_CTRL_SRC_ADDR_INC_ENABLE (0x00040000) +#define COH901318_CX_CTRL_SRC_ADDR_INC_DISABLE (0x00000000) +/* Destination Bus Size */ +#define COH901318_CX_CTRL_DST_BUS_SIZE_MASK (0x00180000) +#define COH901318_CX_CTRL_DST_BUS_SIZE_32_BITS (0x2 << 19) +#define COH901318_CX_CTRL_DST_BUS_SIZE_16_BITS (0x1 << 19) +#define COH901318_CX_CTRL_DST_BUS_SIZE_8_BITS (0x0 << 19) +/* Destination address increment */ +#define COH901318_CX_CTRL_DST_ADDR_INC_ENABLE (0x00200000) +#define COH901318_CX_CTRL_DST_ADDR_INC_DISABLE (0x00000000) +/* Master Mode (Master2 is only connected to MSL) */ +#define COH901318_CX_CTRL_MASTER_MODE_MASK (0x00C00000) +#define COH901318_CX_CTRL_MASTER_MODE_M2R_M1W (0x3 << 22) +#define COH901318_CX_CTRL_MASTER_MODE_M1R_M2W (0x2 << 22) +#define COH901318_CX_CTRL_MASTER_MODE_M2RW (0x1 << 22) +#define COH901318_CX_CTRL_MASTER_MODE_M1RW (0x0 << 22) +/* Terminal Count flag to PER enable */ +#define COH901318_CX_CTRL_TCP_ENABLE (0x01000000) +#define COH901318_CX_CTRL_TCP_DISABLE (0x00000000) +/* Terminal Count flags to CPU enable */ +#define COH901318_CX_CTRL_TC_IRQ_ENABLE (0x02000000) +#define COH901318_CX_CTRL_TC_IRQ_DISABLE (0x00000000) +/* Hand shake to peripheral */ +#define COH901318_CX_CTRL_HSP_ENABLE (0x04000000) +#define COH901318_CX_CTRL_HSP_DISABLE (0x00000000) +#define COH901318_CX_CTRL_HSS_ENABLE (0x08000000) +#define COH901318_CX_CTRL_HSS_DISABLE (0x00000000) +/* DMA mode */ +#define COH901318_CX_CTRL_DDMA_MASK (0x30000000) +#define COH901318_CX_CTRL_DDMA_LEGACY (0x0 << 28) +#define COH901318_CX_CTRL_DDMA_DEMAND_DMA1 (0x1 << 28) +#define COH901318_CX_CTRL_DDMA_DEMAND_DMA2 (0x2 << 28) +/* Primary Request Data Destination */ +#define COH901318_CX_CTRL_PRDD_MASK (0x40000000) +#define COH901318_CX_CTRL_PRDD_DEST (0x1 << 30) +#define COH901318_CX_CTRL_PRDD_SOURCE (0x0 << 30) + +/* + * CX_SRC_ADDR - Channel Source Address Registers 32bit (R/W) + */ +#define COH901318_CX_SRC_ADDR (0x0404) +#define COH901318_CX_SRC_ADDR_SPACING (0x10) + +/* + * CX_DST_ADDR - Channel Destination Address Registers 32bit R/W + */ +#define COH901318_CX_DST_ADDR (0x0408) +#define COH901318_CX_DST_ADDR_SPACING (0x10) + +/* + * CX_LNK_ADDR - Channel Link Address Registers 32bit (R/W) + */ +#define COH901318_CX_LNK_ADDR (0x040C) +#define COH901318_CX_LNK_ADDR_SPACING (0x10) +#define COH901318_CX_LNK_LINK_IMMEDIATE (0x00000001) +#endif /* COH901318_H */ diff --git a/arch/powerpc/include/asm/async_tx.h b/arch/powerpc/include/asm/async_tx.h new file mode 100644 index 00000000000..8b2dc55d01a --- /dev/null +++ b/arch/powerpc/include/asm/async_tx.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008-2009 DENX Software Engineering. + * + * Author: Yuri Tikhonov <yur@emcraft.com> + * + * 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; either version 2 of the License, or (at your option) + * any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The full GNU General Public License is included in this distribution in the + * file called COPYING. + */ +#ifndef _ASM_POWERPC_ASYNC_TX_H_ +#define _ASM_POWERPC_ASYNC_TX_H_ + +#if defined(CONFIG_440SPe) || defined(CONFIG_440SP) +extern struct dma_chan * +ppc440spe_async_tx_find_best_channel(enum dma_transaction_type cap, + struct page **dst_lst, int dst_cnt, struct page **src_lst, + int src_cnt, size_t src_sz); + +#define async_tx_find_channel(dep, cap, dst_lst, dst_cnt, src_lst, \ + src_cnt, src_sz) \ + ppc440spe_async_tx_find_best_channel(cap, dst_lst, dst_cnt, src_lst, \ + src_cnt, src_sz) +#else + +#define async_tx_find_channel(dep, type, dst, dst_count, src, src_count, len) \ + __async_tx_find_channel(dep, type) + +struct dma_chan * +__async_tx_find_channel(struct async_submit_ctl *submit, + enum dma_transaction_type tx_type); + +#endif + +#endif diff --git a/arch/powerpc/include/asm/dcr-regs.h b/arch/powerpc/include/asm/dcr-regs.h index 828e3aa1f2f..380274de429 100644 --- a/arch/powerpc/include/asm/dcr-regs.h +++ b/arch/powerpc/include/asm/dcr-regs.h @@ -157,4 +157,27 @@ #define L2C_SNP_SSR_32G 0x0000f000 #define L2C_SNP_ESR 0x00000800 +/* + * DCR register offsets for 440SP/440SPe I2O/DMA controller. + * The base address is configured in the device tree. + */ +#define DCRN_I2O0_IBAL 0x006 +#define DCRN_I2O0_IBAH 0x007 +#define I2O_REG_ENABLE 0x00000001 /* Enable I2O/DMA access */ + +/* 440SP/440SPe Software Reset DCR */ +#define DCRN_SDR0_SRST 0x0200 +#define DCRN_SDR0_SRST_I2ODMA (0x80000000 >> 15) /* Reset I2O/DMA */ + +/* 440SP/440SPe Memory Queue DCR offsets */ +#define DCRN_MQ0_XORBA 0x04 +#define DCRN_MQ0_CF2H 0x06 +#define DCRN_MQ0_CFBHL 0x0f +#define DCRN_MQ0_BAUH 0x10 + +/* HB/LL Paths Configuration Register */ +#define MQ0_CFBHL_TPLM 28 +#define MQ0_CFBHL_HBCL 23 +#define MQ0_CFBHL_POLY 15 + #endif /* __DCR_REGS_H__ */ diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index eb140ff38c2..e02d74b1e89 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -111,6 +111,24 @@ config SH_DMAE help Enable support for the Renesas SuperH DMA controllers. +config COH901318 + bool "ST-Ericsson COH901318 DMA support" + select DMA_ENGINE + depends on ARCH_U300 + help + Enable support for ST-Ericsson COH 901 318 DMA. + +config AMCC_PPC440SPE_ADMA + tristate "AMCC PPC440SPe ADMA support" + depends on 440SPe || 440SP + select DMA_ENGINE + select ARCH_HAS_ASYNC_TX_FIND_CHANNEL + help + Enable support for the AMCC PPC440SPe RAID engines. + +config ARCH_HAS_ASYNC_TX_FIND_CHANNEL + bool + config DMA_ENGINE bool diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index eca71ba78ae..807053d4823 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -10,3 +10,5 @@ obj-$(CONFIG_AT_HDMAC) += at_hdmac.o obj-$(CONFIG_MX3_IPU) += ipu/ obj-$(CONFIG_TXX9_DMAC) += txx9dmac.o obj-$(CONFIG_SH_DMAE) += shdma.o +obj-$(CONFIG_COH901318) += coh901318.o coh901318_lli.o +obj-$(CONFIG_AMCC_PPC440SPE_ADMA) += ppc4xx/ diff --git a/drivers/dma/coh901318.c b/drivers/dma/coh901318.c new file mode 100644 index 00000000000..4a99cd94536 --- /dev/null +++ b/drivers/dma/coh901318.c @@ -0,0 +1,1325 @@ +/* + * driver/dma/coh901318.c + * + * Copyright (C) 2007-2009 ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + * DMA driver for COH 901 318 + * Author: Per Friden <per.friden@stericsson.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> /* printk() */ +#include <linux/fs.h> /* everything... */ +#include <linux/slab.h> /* kmalloc() */ +#include <linux/dmaengine.h> +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/irqreturn.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/debugfs.h> +#include <mach/coh901318.h> + +#include "coh901318_lli.h" + +#define COHC_2_DEV(cohc) (&cohc->chan.dev->device) + +#ifdef VERBOSE_DEBUG +#define COH_DBG(x) ({ if (1) x; 0; }) +#else +#define COH_DBG(x) ({ if (0) x; 0; }) +#endif + +struct coh901318_desc { + struct dma_async_tx_descriptor desc; + struct list_head node; + struct scatterlist *sg; + unsigned int sg_len; + struct coh901318_lli *data; + enum dma_data_direction dir; + int pending_irqs; + unsigned long flags; +}; + +struct coh901318_base { + struct device *dev; + void __iomem *virtbase; + struct coh901318_pool pool; + struct powersave pm; + struct dma_device dma_slave; + struct dma_device dma_memcpy; + struct coh901318_chan *chans; + struct coh901318_platform *platform; +}; + +struct coh901318_chan { + spinlock_t lock; + int allocated; + int completed; + int id; + int stopped; + + struct work_struct free_work; + struct dma_chan chan; + + struct tasklet_struct tasklet; + + struct list_head active; + struct list_head queue; + struct list_head free; + + unsigned long nbr_active_done; + unsigned long busy; + int pending_irqs; + + struct coh901318_base *base; +}; + +static void coh901318_list_print(struct coh901318_chan *cohc, + struct coh901318_lli *lli) +{ + struct coh901318_lli *l; + dma_addr_t addr = virt_to_phys(lli); + int i = 0; + + while (addr) { + l = phys_to_virt(addr); + dev_vdbg(COHC_2_DEV(cohc), "i %d, lli %p, ctrl 0x%x, src 0x%x" + ", dst 0x%x, link 0x%x link_virt 0x%p\n", + i, l, l->control, l->src_addr, l->dst_addr, + l->link_addr, phys_to_virt(l->link_addr)); + i++; + addr = l->link_addr; + } +} + +#ifdef CONFIG_DEBUG_FS + +#define COH901318_DEBUGFS_ASSIGN(x, y) (x = y) + +static struct coh901318_base *debugfs_dma_base; +static struct dentry *dma_dentry; + +static int coh901318_debugfs_open(struct inode *inode, struct file *file) +{ + + file->private_data = inode->i_private; + return 0; +} + +static int coh901318_debugfs_read(struct file *file, char __user *buf, + size_t count, loff_t *f_pos) +{ + u64 started_channels = debugfs_dma_base->pm.started_channels; + int pool_count = debugfs_dma_base->pool.debugfs_pool_counter; + int i; + int ret = 0; + char *dev_buf; + char *tmp; + int dev_size; + + dev_buf = kmalloc(4*1024, GFP_KERNEL); + if (dev_buf == NULL) + goto err_kmalloc; + tmp = dev_buf; + + tmp += sprintf(tmp, "DMA -- enable dma channels\n"); + + for (i = 0; i < debugfs_dma_base->platform->max_channels; i++) + if (started_channels & (1 << i)) + tmp += sprintf(tmp, "channel %d\n", i); + + tmp += sprintf(tmp, "Pool alloc nbr %d\n", pool_count); + dev_size = tmp - dev_buf; + + /* No more to read if offset != 0 */ + if (*f_pos > dev_size) + goto out; + + if (count > dev_size - *f_pos) + count = dev_size - *f_pos; + + if (copy_to_user(buf, dev_buf + *f_pos, count)) + ret = -EINVAL; + ret = count; + *f_pos += count; + + out: + kfree(dev_buf); + return ret; + + err_kmalloc: + return 0; +} + +static const struct file_operations coh901318_debugfs_status_operations = { + .owner = THIS_MODULE, + .open = coh901318_debugfs_open, + .read = coh901318_debugfs_read, +}; + + +static int __init init_coh901318_debugfs(void) +{ + + dma_dentry = debugfs_create_dir("dma", NULL); + + (void) debugfs_create_file("status", + S_IFREG | S_IRUGO, + dma_dentry, NULL, + &coh901318_debugfs_status_operations); + return 0; +} + +static void __exit exit_coh901318_debugfs(void) +{ + debugfs_remove_recursive(dma_dentry); +} + +module_init(init_coh901318_debugfs); +module_exit(exit_coh901318_debugfs); +#else + +#define COH901318_DEBUGFS_ASSIGN(x, y) + +#endif /* CONFIG_DEBUG_FS */ + +static inline struct coh901318_chan *to_coh901318_chan(struct dma_chan *chan) +{ + return container_of(chan, struct coh901318_chan, chan); +} + +static inline dma_addr_t +cohc_dev_addr(struct coh901318_chan *cohc) +{ + return cohc->base->platform->chan_conf[cohc->id].dev_addr; +} + +static inline const struct coh901318_params * +cohc_chan_param(struct coh901318_chan *cohc) +{ + return &cohc->base->platform->chan_conf[cohc->id].param; +} + +static inline const struct coh_dma_channel * +cohc_chan_conf(struct coh901318_chan *cohc) +{ + return &cohc->base->platform->chan_conf[cohc->id]; +} + +static void enable_powersave(struct coh901318_chan *cohc) +{ + unsigned long flags; + struct powersave *pm = &cohc->base->pm; + + spin_lock_irqsave(&pm->lock, flags); + + pm->started_channels &= ~(1ULL << cohc->id); + + if (!pm->started_channels) { + /* DMA no longer intends to access memory */ + cohc->base->platform->access_memory_state(cohc->base->dev, + false); + } + + spin_unlock_irqrestore(&pm->lock, flags); +} +static void disable_powersave(struct coh901318_chan *cohc) +{ + unsigned long flags; + struct powersave *pm = &cohc->base->pm; + + spin_lock_irqsave(&pm->lock, flags); + + if (!pm->started_channels) { + /* DMA intends to access memory */ + cohc->base->platform->access_memory_state(cohc->base->dev, + true); + } + + pm->started_channels |= (1ULL << cohc->id); + + spin_unlock_irqrestore(&pm->lock, flags); +} + +static inline int coh901318_set_ctrl(struct coh901318_chan *cohc, u32 control) +{ + int channel = cohc->id; + void __iomem *virtbase = cohc->base->virtbase; + + writel(control, + virtbase + COH901318_CX_CTRL + + COH901318_CX_CTRL_SPACING * channel); + return 0; +} + +static inline int coh901318_set_conf(struct coh901318_chan *cohc, u32 conf) +{ + int channel = cohc->id; + void __iomem *virtbase = cohc->base->virtbase; + + writel(conf, + virtbase + COH901318_CX_CFG + + COH901318_CX_CFG_SPACING*channel); + return 0; +} + + +static int coh901318_start(struct coh901318_chan *cohc) +{ + u32 val; + int channel = cohc->id; + void __iomem *virtbase = cohc->base->virtbase; + + disable_powersave(cohc); + + val = readl(virtbase + COH901318_CX_CFG + + COH901318_CX_CFG_SPACING * channel); + + /* Enable channel */ + val |= COH901318_CX_CFG_CH_ENABLE; + writel(val, virtbase + COH901318_CX_CFG + + COH901318_CX_CFG_SPACING * channel); + + return 0; +} + +static int coh901318_prep_linked_list(struct coh901318_chan *cohc, + struct coh901318_lli *data) +{ + int channel = cohc->id; + void __iomem *virtbase = cohc->base->virtbase; + + BUG_ON(readl(virtbase + COH901318_CX_STAT + + COH901318_CX_STAT_SPACING*channel) & + COH901318_CX_STAT_ACTIVE); + + writel(data->src_addr, + virtbase + COH901318_CX_SRC_ADDR + + COH901318_CX_SRC_ADDR_SPACING * channel); + + writel(data->dst_addr, virtbase + + COH901318_CX_DST_ADDR + + COH901318_CX_DST_ADDR_SPACING * channel); + + writel(data->link_addr, virtbase + COH901318_CX_LNK_ADDR + + COH901318_CX_LNK_ADDR_SPACING * channel); + + writel(data->control, virtbase + COH901318_CX_CTRL + + COH901318_CX_CTRL_SPACING * channel); + + return 0; +} +static dma_cookie_t +coh901318_assign_cookie(struct coh901318_chan *cohc, + struct coh901318_desc *cohd) +{ + dma_cookie_t cookie = cohc->chan.cookie; + + if (++cookie < 0) + cookie = 1; + + cohc->chan.cookie = cookie; + cohd->desc.cookie = cookie; + + return cookie; +} + +static struct coh901318_desc * +coh901318_desc_get(struct coh901318_chan *cohc) +{ + struct coh901318_desc *desc; + + if (list_empty(&cohc->free)) { + /* alloc new desc because we're out of used ones + * TODO: alloc a pile of descs instead of just one, + * avoid many small allocations. + */ + desc = kmalloc(sizeof(struct coh901318_desc), GFP_NOWAIT); + if (desc == NULL) + goto out; + INIT_LIST_HEAD(&desc->node); + } else { + /* Reuse an old desc. */ + desc = list_first_entry(&cohc->free, + struct coh901318_desc, + node); + list_del(&desc->node); + } + + out: + return desc; +} + +static void +coh901318_desc_free(struct coh901318_chan *cohc, struct coh901318_desc *cohd) +{ + list_add_tail(&cohd->node, &cohc->free); +} + +/* call with irq lock held */ +static void +coh901318_desc_submit(struct coh901318_chan *cohc, struct coh901318_desc *desc) +{ + list_add_tail(&desc->node, &cohc->active); + + BUG_ON(cohc->pending_irqs != 0); + + cohc->pending_irqs = desc->pending_irqs; +} + +static struct coh901318_desc * +coh901318_first_active_get(struct coh901318_chan *cohc) +{ + struct coh901318_desc *d; + + if (list_empty(&cohc->active)) + return NULL; + + d = list_first_entry(&cohc->active, + struct coh901318_desc, + node); + return d; +} + +static void +coh901318_desc_remove(struct coh901318_desc *cohd) +{ + list_del(&cohd->node); +} + +static void +coh901318_desc_queue(struct coh901318_chan *cohc, struct coh901318_desc *desc) +{ + list_add_tail(&desc->node, &cohc->queue); +} + +static struct coh901318_desc * +coh901318_first_queued(struct coh901318_chan *cohc) +{ + struct coh901318_desc *d; + + if (list_empty(&cohc->queue)) + return NULL; + + d = list_first_entry(&cohc->queue, + struct coh901318_desc, + node); + return d; +} + +/* + * DMA start/stop controls + */ +u32 coh901318_get_bytes_left(struct dma_chan *chan) +{ + unsigned long flags; + u32 ret; + struct coh901318_chan *cohc = to_coh901318_chan(chan); + + spin_lock_irqsave(&cohc->lock, flags); + + /* Read transfer count value */ + ret = readl(cohc->base->virtbase + + COH901318_CX_CTRL+COH901318_CX_CTRL_SPACING * + cohc->id) & COH901318_CX_CTRL_TC_VALUE_MASK; + + spin_unlock_irqrestore(&cohc->lock, flags); + + return ret; +} +EXPORT_SYMBOL(coh901318_get_bytes_left); + + +/* Stops a transfer without losing data. Enables power save. + Use this function in conjunction with coh901318_continue(..) +*/ +void coh901318_stop(struct dma_chan *chan) +{ + u32 val; + unsigned long flags; + struct coh901318_chan *cohc = to_coh901318_chan(chan); + int channel = cohc->id; + void __iomem *virtbase = cohc->base->virtbase; + + spin_lock_irqsave(&cohc->lock, flags); + + /* Disable channel in HW */ + val = readl(virtbase + COH901318_CX_CFG + + COH901318_CX_CFG_SPACING * channel); + + /* Stopping infinit transfer */ + if ((val & COH901318_CX_CTRL_TC_ENABLE) == 0 && + (val & COH901318_CX_CFG_CH_ENABLE)) + cohc->s |