diff options
author | David Barksdale <amatus@amatus.name> | 2014-08-22 16:01:49 -0500 |
---|---|---|
committer | David Barksdale <amatus@amatus.name> | 2014-08-22 16:05:25 -0500 |
commit | 98494c9e596f37214a4507a92e06c2e59ea563fc (patch) | |
tree | a021c4af7453853a59704525e91b6f910b760d3b /drivers/dma | |
parent | 19583ca584d6f574384e17fe7613dfaeadcdc4a6 (diff) |
Ported over SATA and DMA drivers and configsHEADmybooklive-amatus
This kernel+dtb boots on my My Book Live and
is able to access the SATA hard drive.
Diffstat (limited to 'drivers/dma')
-rw-r--r-- | drivers/dma/Kconfig | 17 | ||||
-rw-r--r-- | drivers/dma/Makefile | 2 | ||||
-rw-r--r-- | drivers/dma/ppc4xx/Makefile | 2 | ||||
-rw-r--r-- | drivers/dma/ppc4xx/apm82181-adma.c | 2201 | ||||
-rw-r--r-- | drivers/dma/ppc4xx/ppc460ex_4chan_dma.c | 1110 | ||||
-rw-r--r-- | drivers/dma/ppc4xx/ppc460ex_4chan_dma.h | 531 |
6 files changed, 3862 insertions, 1 deletions
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 1eca7b9760e..d761ad3ba09 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -195,6 +195,23 @@ config AMCC_PPC440SPE_ADMA help Enable support for the AMCC PPC440SPe RAID engines. +config AMCC_PPC460EX_460GT_4CHAN_DMA + tristate "AMCC PPC460EX PPC460GT PLB DMA support" + depends on 460EX || 460GT || APM821xx + select DMA_ENGINE + default y + +config APM82181_ADMA + tristate "APM82181 Asynchonous DMA support" + depends on APM821xx + select ASYNC_CORE + select ASYNC_TX_DMA + select DMA_ENGINE + select ARCH_HAS_ASYNC_TX_FIND_CHANNEL + default y + ---help--- + Enable support for the APM82181 Asynchonous DMA engines. + config TIMB_DMA tristate "Timberdale FPGA DMA support" depends on MFD_TIMBERDALE diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index c779e1eb2db..7acb4c437fa 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -21,7 +21,7 @@ obj-$(CONFIG_MX3_IPU) += ipu/ obj-$(CONFIG_TXX9_DMAC) += txx9dmac.o obj-$(CONFIG_SH_DMAE_BASE) += sh/ obj-$(CONFIG_COH901318) += coh901318.o coh901318_lli.o -obj-$(CONFIG_AMCC_PPC440SPE_ADMA) += ppc4xx/ +obj-y += ppc4xx/ obj-$(CONFIG_IMX_SDMA) += imx-sdma.o obj-$(CONFIG_IMX_DMA) += imx-dma.o obj-$(CONFIG_MXS_DMA) += mxs-dma.o diff --git a/drivers/dma/ppc4xx/Makefile b/drivers/dma/ppc4xx/Makefile index b3d259b3e52..e7700985371 100644 --- a/drivers/dma/ppc4xx/Makefile +++ b/drivers/dma/ppc4xx/Makefile @@ -1 +1,3 @@ obj-$(CONFIG_AMCC_PPC440SPE_ADMA) += adma.o +obj-$(CONFIG_AMCC_PPC460EX_460GT_4CHAN_DMA) += ppc460ex_4chan_dma.o +obj-$(CONFIG_APM82181_ADMA) += apm82181-adma.o diff --git a/drivers/dma/ppc4xx/apm82181-adma.c b/drivers/dma/ppc4xx/apm82181-adma.c new file mode 100644 index 00000000000..c95e704b1d9 --- /dev/null +++ b/drivers/dma/ppc4xx/apm82181-adma.c @@ -0,0 +1,2201 @@ +/* + * Copyright(c) 2010 Applied Micro Circuits Corporation(AMCC). All rights reserved. + * + * Author: Tai Tri Nguyen <ttnguyen@appliedmicro.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. + */ + +/* + * This driver supports the asynchrounous DMA copy and RAID engines available + * on the AppliedMicro APM82181 Processor. + * Based on the Intel Xscale(R) family of I/O Processors (IOP 32x, 33x, 134x) + * ADMA driver written by D.Williams. + */ +#define ADMA_DEBUG +#undef ADMA_DEBUG + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/async_tx.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/proc_fs.h> +#include <linux/slab.h> +#include <asm/dcr.h> +#include <asm/dcr-regs.h> +#include <asm/apm82181-adma.h> +#include "../dmaengine.h" + +#define PPC4XX_EDMA "apm82181-adma: " +#ifdef ADMA_DEBUG +#define DBG(string, args...) \ + printk(PPC4XX_EDMA string ,##args) +#define INFO DBG("<%s> -- line %d\n",__func__,__LINE__); +#define ADMA_HEXDUMP(b, l) \ + print_hex_dump(KERN_CONT, "", DUMP_PREFIX_OFFSET, \ + 16, 1, (b), (l), false); +#else +#define DBG(string, args...) \ + {if (0) printk(KERN_INFO PPC4XX_EDMA string ,##args); 0; } +#define INFO DBG(""); +#define ADMA_HEXDUMP(b, l) \ + {if (0) print_hex_dump(KERN_CONT, "", DUMP_PREFIX_OFFSET, \ + 8, 1, (b), (l), false); 0;} +#endif + +#define MEM_HEXDUMP(b, l) \ + print_hex_dump(KERN_CONT, "", DUMP_PREFIX_OFFSET, \ + 16, 1, (b), (l), false); + +/* The list of channels exported by apm82181 ADMA */ +struct list_head +ppc_adma_chan_list = LIST_HEAD_INIT(ppc_adma_chan_list); + +/* This flag is set when want to refetch the xor chain in the interrupt + * handler + */ +static u32 do_xor_refetch = 0; + +/* Pointers to last submitted to DMA0/1/2/3 and XOR CDBs */ +static apm82181_desc_t *chan_last_sub[5]; +static apm82181_desc_t *chan_first_cdb[5]; + +/* Pointer to last linked and submitted xor CB */ +static apm82181_desc_t *xor_last_linked = NULL; +static apm82181_desc_t *xor_last_submit = NULL; + +/* /proc interface is used here to verify the h/w RAID 5 capabilities + */ +static struct proc_dir_entry *apm82181_proot; + +/* These are used in enable & check routines + */ +static u32 apm82181_xor_verified; +static u32 apm82181_memcpy_verified[4]; +static apm82181_ch_t *apm82181_dma_tchan[5]; +static struct completion apm82181_r5_test_comp; + +static inline int apm82181_chan_is_busy(apm82181_ch_t *chan); +#if 0 +static phys_addr_t fixup_bigphys_addr(phys_addr_t addr, phys_addr_t size) +{ + phys_addr_t page_4gb = 0; + + return (page_4gb | addr); +} +#endif +/** + * apm82181_adma_device_estimate - estimate the efficiency of processing + * the operation given on this channel. It's assumed that 'chan' is + * capable to process 'cap' type of operation. + * @chan: channel to use + * @cap: type of transaction + * @src_lst: array of source pointers + * @src_cnt: number of source operands + * @src_sz: size of each source operand + */ +int apm82181_adma_estimate (struct dma_chan *chan, + enum dma_transaction_type cap, struct page **src_lst, + int src_cnt, size_t src_sz) +{ + int ef = 1; + + /* channel idleness increases the priority */ + if (likely(ef) && + !apm82181_chan_is_busy(to_apm82181_adma_chan(chan))) + ef++; + else { + if(chan->chan_id !=APM82181_XOR_ID) + ef = -1; + } + return ef; +} + +/****************************************************************************** + * Command (Descriptor) Blocks low-level routines + ******************************************************************************/ +/** + * apm82181_desc_init_interrupt - initialize the descriptor for INTERRUPT + * pseudo operation + */ +static inline void apm82181_desc_init_interrupt (apm82181_desc_t *desc, + apm82181_ch_t *chan) +{ + xor_cb_t *p; + + switch (chan->device->id) { + case APM82181_PDMA0_ID: + case APM82181_PDMA1_ID: + case APM82181_PDMA2_ID: + case APM82181_PDMA3_ID: + BUG(); + break; + case APM82181_XOR_ID: + p = desc->hw_desc; + memset (desc->hw_desc, 0, sizeof(xor_cb_t)); + /* NOP with Command Block Complete Enable */ + p->cbc = XOR_CBCR_CBCE_BIT; + break; + default: + printk(KERN_ERR "Unsupported id %d in %s\n", chan->device->id, + __FUNCTION__); + break; + } +} + +/** + * apm82181_desc_init_xor - initialize the descriptor for XOR operation + */ +static inline void apm82181_desc_init_xor(apm82181_desc_t *desc, int src_cnt, + unsigned long flags) +{ + xor_cb_t *hw_desc = desc->hw_desc; + + memset (desc->hw_desc, 0, sizeof(xor_cb_t)); + desc->hw_next = NULL; + desc->src_cnt = src_cnt; + desc->dst_cnt = 1; + + hw_desc->cbc = XOR_CBCR_TGT_BIT | src_cnt; + if (flags & DMA_PREP_INTERRUPT) + /* Enable interrupt on complete */ + hw_desc->cbc |= XOR_CBCR_CBCE_BIT; +} + +/** + * apm82181_desc_init_memcpy - initialize the descriptor for MEMCPY operation + */ +static inline void apm82181_desc_init_memcpy(apm82181_desc_t *desc, + unsigned long flags) +{ + dma_cdb_t *hw_desc = desc->hw_desc; + + memset(hw_desc, 0, sizeof(dma_cdb_t)); + desc->hw_next = NULL; + desc->src_cnt = 1; + desc->dst_cnt = 1; + + if (flags & DMA_PREP_INTERRUPT) + set_bit(APM82181_DESC_INT, &desc->flags); + else + clear_bit(APM82181_DESC_INT, &desc->flags); + /* dma configuration for running */ + hw_desc->ctrl.tm = 2; /* soft init mem-mem mode */ + hw_desc->ctrl.pw = 4; /* transfer width 128 bytes */ + hw_desc->ctrl.ben = 1;/* buffer enable */ + hw_desc->ctrl.sai = 1;/* increase source addr */ + hw_desc->ctrl.dai = 1;/* increase dest addr */ + hw_desc->ctrl.tce = 1;/* chan stops when TC is reached */ + hw_desc->ctrl.cp = 3; /* hinghest priority */ + hw_desc->ctrl.sl = 0; /* source is in PLB */ + hw_desc->ctrl.pl = 0; /* dest is in PLB */ + hw_desc->cnt.tcie = 0;/* no interrupt on init */ + hw_desc->cnt.etie = 0; /* enable error interrupt */ + hw_desc->cnt.eie = 1; /* enable error interrupt */ + hw_desc->cnt.link = 0;/* not link to next cdb */ + hw_desc->cnt.sgl = 0; + hw_desc->ctrl.ce =1; /* enable channel */ + hw_desc->ctrl.cie =1; /* enable int channel */ +} + +/** + * apm82181_desc_init_memset - initialize the descriptor for MEMSET operation + */ +static inline void apm82181_desc_init_memset(apm82181_desc_t *desc, int value, + unsigned long flags) +{ + //dma_cdb_t *hw_desc = desc->hw_desc; + + memset (desc->hw_desc, 0, sizeof(dma_cdb_t)); + desc->hw_next = NULL; + desc->src_cnt = 1; + desc->dst_cnt = 1; + + if (flags & DMA_PREP_INTERRUPT) + set_bit(APM82181_DESC_INT, &desc->flags); + else + clear_bit(APM82181_DESC_INT, &desc->flags); + +} + + + +/** + * apm82181_desc_set_src_addr - set source address into the descriptor + */ +static inline void apm82181_desc_set_src_addr( apm82181_desc_t *desc, + apm82181_ch_t *chan, int src_idx, dma_addr_t addr) +{ + dma_cdb_t *dma_hw_desc; + xor_cb_t *xor_hw_desc; + + switch (chan->device->id) { + case APM82181_PDMA0_ID: + case APM82181_PDMA1_ID: + case APM82181_PDMA2_ID: + case APM82181_PDMA3_ID: + dma_hw_desc = desc->hw_desc; + dma_hw_desc->src_hi = (u32)(addr >> 32); + dma_hw_desc->src_lo = (u32)addr; + break; + case APM82181_XOR_ID: + xor_hw_desc = desc->hw_desc; + xor_hw_desc->ops[src_idx].h = (u32)(addr >>32); + xor_hw_desc->ops[src_idx].l = (u32)addr; + break; + } +} + +static void apm82181_adma_set_src(apm82181_desc_t *sw_desc, + dma_addr_t addr, int index) +{ + apm82181_ch_t *chan = to_apm82181_adma_chan(sw_desc->async_tx.chan); + + sw_desc = sw_desc->group_head; + + if (likely(sw_desc)) + apm82181_desc_set_src_addr(sw_desc, chan, index, addr); +} + +/** + * apm82181_desc_set_dest_addr - set destination address into the descriptor + */ +static inline void apm82181_desc_set_dest_addr(apm82181_desc_t *desc, + apm82181_ch_t *chan, dma_addr_t addr, u32 index) +{ + dma_cdb_t *dma_hw_desc; + xor_cb_t *xor_hw_desc; + + switch (chan->device->id) { + case APM82181_PDMA0_ID: + case APM82181_PDMA1_ID: + case APM82181_PDMA2_ID: + case APM82181_PDMA3_ID: + dma_hw_desc = desc->hw_desc; + dma_hw_desc->dest_hi = (u32)(addr >> 32); + dma_hw_desc->dest_lo = (u32)addr; + break; + case APM82181_XOR_ID: + xor_hw_desc = desc->hw_desc; + xor_hw_desc->cbtah = (u32)(addr >> 32); + xor_hw_desc->cbtal |= (u32)addr; + break; + } +} + +static int plbdma_get_transfer_width(dma_cdb_t *dma_hw_desc) +{ + switch (dma_hw_desc->ctrl.pw){ + case 0: + return 1; /* unit: bytes */ + case 1: + return 2; + case 2: + return 4; + case 3: + return 8; + case 4: + return 16; + } + return 0; +} +/** + * apm82181_desc_set_byte_count - set number of data bytes involved + * into the operation + */ +static inline void apm82181_desc_set_byte_count(apm82181_desc_t *desc, + apm82181_ch_t *chan, size_t byte_count) +{ + dma_cdb_t *dma_hw_desc; + xor_cb_t *xor_hw_desc; + int terminal_cnt, transfer_width = 0; + + DBG("<%s> byte_count %08x\n", __func__,byte_count); + switch (chan->device->id){ + case APM82181_PDMA0_ID: + case APM82181_PDMA1_ID: + case APM82181_PDMA2_ID: + case APM82181_PDMA3_ID: + dma_hw_desc = desc->hw_desc; + transfer_width = plbdma_get_transfer_width(dma_hw_desc); + terminal_cnt = byte_count/transfer_width; + dma_hw_desc->cnt.tc = terminal_cnt; + break; + case APM82181_XOR_ID: + xor_hw_desc = desc->hw_desc; + xor_hw_desc->cbbc = byte_count; + break; + } +} + +/** + * apm82181_xor_set_link - set link address in xor CB + */ +static inline void apm82181_xor_set_link (apm82181_desc_t *prev_desc, + apm82181_desc_t *next_desc) +{ + xor_cb_t *xor_hw_desc = prev_desc->hw_desc; + + if (unlikely(!next_desc || !(next_desc->phys))) { + printk(KERN_ERR "%s: next_desc=0x%p; next_desc->phys=0x%llx\n", + __func__, next_desc, + next_desc ? next_desc->phys : 0); + BUG(); + } + DBG("<%s>:next_desc->phys %llx\n", __func__,next_desc->phys); + xor_hw_desc->cbs = 0; + xor_hw_desc->cblal = (u32)next_desc->phys; + xor_hw_desc->cblah = (u32)(next_desc->phys >> 32); + xor_hw_desc->cbc |= XOR_CBCR_LNK_BIT; +} + +/** + * apm82181_desc_set_link - set the address of descriptor following this + * descriptor in chain + */ +static inline void apm82181_desc_set_link(apm82181_ch_t *chan, + apm82181_desc_t *prev_desc, apm82181_desc_t *next_desc) +{ + unsigned long flags; + apm82181_desc_t *tail = next_desc; + + if (unlikely(!prev_desc || !next_desc || + (prev_desc->hw_next && prev_desc->hw_next != next_desc))) { + /* If previous next is overwritten something is wrong. + * though we may refetch from append to initiate list + * processing; in this case - it's ok. + */ + printk(KERN_ERR "%s: prev_desc=0x%p; next_desc=0x%p; " + "prev->hw_next=0x%p\n", __FUNCTION__, prev_desc, + next_desc, prev_desc ? prev_desc->hw_next : 0); + BUG(); + } + + local_irq_save(flags); + + /* do s/w chaining both for DMA and XOR descriptors */ + prev_desc->hw_next = next_desc; + + switch (chan->device->id) { + case APM82181_PDMA0_ID: + case APM82181_PDMA1_ID: + case APM82181_PDMA2_ID: + case APM82181_PDMA3_ID: + break; + case APM82181_XOR_ID: + /* bind descriptor to the chain */ + while (tail->hw_next) + tail = tail->hw_next; + xor_last_linked = tail; + + if (prev_desc == xor_last_submit) + /* do not link to the last submitted CB */ + break; + apm82181_xor_set_link (prev_desc, next_desc); + break; + default: + BUG(); + } + + local_irq_restore(flags); +} + +/** + * apm82181_desc_get_src_addr - extract the source address from the descriptor + */ +static inline u32 apm82181_desc_get_src_addr(apm82181_desc_t *desc, + apm82181_ch_t *chan, int src_idx) +{ + dma_cdb_t *dma_hw_desc; + + dma_hw_desc = desc->hw_desc; + + switch (chan->device->id) { + case APM82181_PDMA0_ID: + case APM82181_PDMA1_ID: + case APM82181_PDMA2_ID: + case APM82181_PDMA3_ID: + break; + default: + return 0; + } + /* May have 0, 1, 2, or 3 sources */ + return (dma_hw_desc->src_lo); +} + +/** + * apm82181_desc_get_dest_addr - extract the destination address from the + * descriptor + */ +static inline u32 apm82181_desc_get_dest_addr(apm82181_desc_t *desc, + apm82181_ch_t *chan, int idx) +{ + dma_cdb_t *dma_hw_desc; + + dma_hw_desc = desc->hw_desc; + + switch (chan->device->id) { + case APM82181_PDMA0_ID: + case APM82181_PDMA1_ID: + case APM82181_PDMA2_ID: + case APM82181_PDMA3_ID: + break; + default: + return 0; + } + + /* May have 0, 1, 2, or 3 sources */ + return (dma_hw_desc->dest_lo); +} + +/** + * apm82181_desc_get_byte_count - extract the byte count from the descriptor + */ +static inline u32 apm82181_desc_get_byte_count(apm82181_desc_t *desc, + apm82181_ch_t *chan) +{ + dma_cdb_t *dma_hw_desc; + + dma_hw_desc = desc->hw_desc; + + switch (chan->device->id) { + case APM82181_PDMA0_ID: + case APM82181_PDMA1_ID: + case APM82181_PDMA2_ID: + case APM82181_PDMA3_ID: + break; + default: + return 0; + } + /* May have 0, 1, 2, or 3 sources */ + //return (dma_hw_desc->cnt); +} + + +/** + * apm82181_desc_get_link - get the address of the descriptor that + * follows this one + */ +static inline u32 apm82181_desc_get_link(apm82181_desc_t *desc, + apm82181_ch_t *chan) +{ + if (!desc->hw_next) + return 0; + + return desc->hw_next->phys; +} + +/** + * apm82181_desc_is_aligned - check alignment + */ +static inline int apm82181_desc_is_aligned(apm82181_desc_t *desc, + int num_slots) +{ + return (desc->idx & (num_slots - 1)) ? 0 : 1; +} + + + +/****************************************************************************** + * ADMA channel low-level routines + ******************************************************************************/ + +static inline phys_addr_t apm82181_chan_get_current_descriptor(apm82181_ch_t *chan); +static inline void apm82181_chan_append(apm82181_ch_t *chan); + +/* + * apm82181_adma_device_clear_eot_status - interrupt ack to XOR or DMA engine + */ +static inline void apm82181_adma_device_clear_eot_status (apm82181_ch_t *chan) +{ + u32 val ; + int idx = chan->device->id; + volatile xor_regs_t *xor_reg; + INFO; + switch (idx) { + case APM82181_PDMA0_ID: + case APM82181_PDMA1_ID: + case APM82181_PDMA2_ID: + case APM82181_PDMA3_ID: + val = mfdcr(DCR_DMA2P40_SR); + if(val & DMA_SR_RI(idx)){ + printk(KERN_ERR "Err occurred, DMA%d status: 0x%x\n", idx, val); + } + /* TC reached int, write back to clear */ + mtdcr(DCR_DMA2P40_SR, val); + break; + case APM82181_XOR_ID: + /* reset status bits to ack*/ + xor_reg = chan->device->xor_base; + + val = xor_reg->sr; + DBG("XOR engine status: 0x%08x\n", val); + xor_reg->sr = val; + + if (val & (XOR_IE_ICBIE_BIT|XOR_IE_ICIE_BIT|XOR_IE_RPTIE_BIT)) { + if (val & XOR_IE_RPTIE_BIT) { + /* Read PLB Timeout Error. + * Try to resubmit the CB + */ + INFO; + xor_reg->cblalr = xor_reg->ccbalr; + xor_reg->crsr |= XOR_CRSR_XAE_BIT; + } else + printk (KERN_ERR "XOR ERR 0x%x status\n", val); + break; + } + + /* if the XORcore is idle, but there are unprocessed CBs + * then refetch the s/w chain here + */ + if (!(xor_reg->sr & XOR_SR_XCP_BIT) && do_xor_refetch) { + apm82181_chan_append(chan); + } + break; + } +} + +/* + * apm82181_chan_is_busy - get the channel status + */ + +static inline int apm82181_chan_is_busy(apm82181_ch_t *chan) +{ + int busy = 0; + volatile xor_regs_t *xor_reg = chan->device->xor_base; + + switch (chan->device->id) { + case APM82181_PDMA0_ID: + case APM82181_PDMA1_ID: + case APM82181_PDMA2_ID: + case APM82181_PDMA3_ID: + if(mfdcr(DCR_DMA2P40_SR) & DMA_SR_CB(chan->device->id)) + busy = 1; + else + busy = 0; + break; + case APM82181_XOR_ID: + /* use the special status bit for the XORcore + */ + busy = (xor_reg->sr & XOR_SR_XCP_BIT) ? 1 : 0; + break; + default: + BUG(); + } + + return busy; +} + +/** + * apm82181_dma_put_desc - put PLB DMA 0/1/2/3 descriptor to FIFO + */ +static inline void apm82181_dma_put_desc(apm82181_ch_t *chan, + apm82181_desc_t *desc) +{ + dma_cdb_t *cdb = desc->hw_desc; + u32 sg_cmd = 0; + + /* Enable TC interrupt */ + if(test_bit(APM82181_DESC_INT, &desc->flags)) + cdb->cnt.tcie = 1; + else + cdb->cnt.tcie = 0; + /* Not link to next cdb */ + cdb->sg_hi = 0xffffffff; + cdb->sg_lo = 0xffffffff; + + chan_last_sub[chan->device->id] = desc; + + /* Update new cdb addr */ + mtdcr(DCR_DMA2P40_SGHx(chan->device->id), (u32)(desc->phys >> 32)); + mtdcr(DCR_DMA2P40_SGLx(chan->device->id), (u32)desc->phys); + + INFO; + DBG("slot id: %d addr: %llx\n", desc->idx, desc->phys); + DBG("S/G addr H: %08x addr L: %08x\n", + mfdcr(DCR_DMA2P40_SGHx(chan->device->id)), + mfdcr(DCR_DMA2P40_SGLx(chan->device->id))); + ADMA_HEXDUMP(cdb, 96); + /* Enable S/G */ + sg_cmd |= (DMA_SGC_SSG(chan->device->id) | DMA_SGC_EM_ALL); + sg_cmd |= DMA_SGC_SGL(chan->device->id, 0); /* S/G addr in PLB */ + + mtdcr(DCR_DMA2P40_SGC, sg_cmd); + DBG("S/G addr H: %08x addr L: %08x\n", + mfdcr(DCR_DMA2P40_SGHx(chan->device->id)), + mfdcr(DCR_DMA2P40_SGLx(chan->device->id))); + /* need to use variable for logging current CDB */ + chan->current_cdb_addr = desc->phys; + +} + +/** + * apm82181_chan_append - update the h/w chain in the channel + */ +static inline void apm82181_chan_append(apm82181_ch_t *chan) +{ + apm82181_desc_t *iter; + volatile xor_regs_t *xor_reg; + phys_addr_t cur_desc; + xor_cb_t *xcb; + unsigned long flags; + INFO; + + local_irq_save(flags); + + switch (chan->device->id) { + case APM82181_PDMA0_ID: + case APM82181_PDMA1_ID: + case APM82181_PDMA2_ID: + case APM82181_PDMA3_ID: + cur_desc = apm82181_chan_get_current_descriptor(chan); + DBG("current_desc %llx\n", cur_desc); + if (likely(cur_desc)) { + INFO; + iter = chan_last_sub[chan->device->id]; + BUG_ON(!iter); + } else { + INFO; + /* first peer */ + iter = chan_first_cdb[chan->device->id]; + BUG_ON(!iter); + INFO; + apm82181_dma_put_desc(chan, iter); + chan->hw_chain_inited = 1; + } + + /* is there something new to append */ + if (!iter->hw_next) + break; + + /* flush descriptors from the s/w queue to fifo */ + list_for_each_entry_continue(iter, &chan->chain, chain_node) { + apm82181_dma_put_desc(chan, iter); + if (!iter->hw_next) + break; + } + break; + case APM82181_XOR_ID: + /* update h/w links and refetch */ + if (!xor_last_submit->hw_next) + break; + xor_reg = chan->device->xor_base; + /* the last linked CDB has to generate an interrupt + * that we'd be able to append the next lists to h/w + * regardless of the XOR engine state at the moment of + * appending of these next lists + */ + xcb = xor_last_linked->hw_desc; + xcb->cbc |= XOR_CBCR_CBCE_BIT; + + if (!(xor_reg->sr & XOR_SR_XCP_BIT)) { + /* XORcore is idle. Refetch now */ + do_xor_refetch = 0; + apm82181_xor_set_link(xor_last_submit, + xor_last_submit->hw_next); + + xor_last_submit = xor_last_linked; + xor_reg->crsr |= XOR_CRSR_RCBE_BIT | XOR_CRSR_64BA_BIT; + } else { + /* XORcore is running. Refetch later in the handler */ + do_xor_refetch = 1; + } + + break; + } + + local_irq_restore(flags); +} + +/** + * apm82181_chan_get_current_descriptor - get the currently executed descriptor + */ +static inline phys_addr_t apm82181_chan_get_current_descriptor(apm82181_ch_t *chan) +{ + phys_addr_t curr_cdb_addr; + volatile xor_regs_t *xor_reg; + int idx = chan->device->id; + + if (unlikely(!chan->hw_chain_inited)) + /* h/w descriptor chain is not initialized yet */ + return 0; + switch(idx){ + case APM82181_PDMA0_ID: + case APM82181_PDMA1_ID: + case APM82181_PDMA2_ID: + case APM82181_PDMA3_ID: + curr_cdb_addr = chan->current_cdb_addr; + break; + case APM82181_XOR_ID: + xor_reg = chan->device->xor_base; + curr_cdb_addr = (dma_addr_t)xor_reg->ccbahr; + curr_cdb_addr = (curr_cdb_addr << 32) | xor_reg->ccbalr; + break; + default: + BUG(); + } + return curr_cdb_addr; +} + + +/****************************************************************************** + * ADMA device level + ******************************************************************************/ + +static int apm82181_adma_alloc_chan_resources(struct dma_chan *chan); +static dma_cookie_t apm82181_adma_tx_submit( + struct dma_async_tx_descriptor *tx); + +static void apm82181_adma_set_dest( + apm82181_desc_t *tx, + dma_addr_t addr, int index); + +/** + * apm82181_get_group_entry - get group entry with index idx + * @tdesc: is the last allocated slot in the group. + */ +static inline apm82181_desc_t * +apm82181_get_group_entry ( apm82181_desc_t *tdesc, u32 entry_idx) +{ + apm82181_desc_t *iter = tdesc->group_head; + int i = 0; + + if (entry_idx < 0 || entry_idx >= (tdesc->src_cnt + tdesc->dst_cnt)) { + printk("%s: entry_idx %d, src_cnt %d, dst_cnt %d\n", + __func__, entry_idx, tdesc->src_cnt, tdesc->dst_cnt); + BUG(); + } + list_for_each_entry(iter, &tdesc->group_list, chain_node) { + if (i++ == entry_idx) + break; + } + return iter; +} + +/** + * apm82181_adma_free_slots - flags descriptor slots for reuse + * @slot: Slot to free + * Caller must hold &apm82181_chan->lock while calling this function + */ +static void apm82181_adma_free_slots(apm82181_desc_t *slot, + apm82181_ch_t *chan) +{ + int stride = slot->slots_per_op; + + while (stride--) { + /*async_tx_clear_ack(&slot->async_tx);*/ /* Don't need to clear. It is hack*/ + slot->slots_per_op = 0; + slot = list_entry(slot->slot_node.next, + apm82181_desc_t, + slot_node); + } +} + +static void +apm82181_adma_unmap(apm82181_ch_t *chan, apm82181_desc_t *desc) +{ + u32 src_cnt, dst_cnt; + dma_addr_t addr; + /* + * get the number of sources & destination + * included in this descriptor and unmap + * them all + */ + src_cnt = 1; + dst_cnt = 1; +} +/** + * apm82181_adma_run_tx_complete_actions - call functions to be called + * upon complete + */ +static dma_cookie_t apm82181_adma_run_tx_complete_actions( + apm82181_desc_t *desc, + apm82181_ch_t *chan, + dma_cookie_t cookie) +{ + int i; + //enum dma_data_direction dir; + INFO; + BUG_ON(desc->async_tx.cookie < 0); + if (desc->async_tx.cookie > 0) { + cookie = desc->async_tx.cookie; + desc->async_tx.cookie = 0; + + /* call the callback (must not sleep or submit new + * operations to this channel) + */ + if (desc->async_tx.callback) + desc->async_tx.callback( + desc->async_tx.callback_param); + + dma_descriptor_unmap(&desc->async_tx); + /* unmap dma addresses + * (unmap_single vs unmap_page?) + * + * actually, ppc's dma_unmap_page() functions are empty, so + * the following code is just for the sake of completeness + */ + if (chan && chan->needs_unmap && desc->group_head && + desc->unmap_len) { + apm82181_desc_t *unmap = desc->group_head; + /* assume 1 slot per op always */ + u32 slot_count = unmap->slot_cnt; + + /* Run through the group list and unmap addresses */ + for (i = 0; i < slot_count; i++) { + BUG_ON(!unmap); + apm82181_adma_unmap(chan, unmap); + unmap = unmap->hw_next; + } + desc->group_head = NULL; + } + } + + /* run dependent operations */ + dma_run_dependencies(&desc->async_tx); + + return cookie; +} + +/** + * apm82181_adma_clean_slot - clean up CDB slot (if ack is set) + */ +static int apm82181_adma_clean_slot(apm82181_desc_t *desc, + apm82181_ch_t *chan) +{ + /* the client is allowed to attach dependent operations + * until 'ack' is set + */ + if (!async_tx_test_ack(&desc->async_tx)) + return 0; + + /* leave the last descriptor in the chain + * so we can append to it + */ + if (list_is_last(&desc->chain_node, &chan->chain) || + desc->phys == apm82181_chan_get_current_descriptor(chan)) + return 1; + + dev_dbg(chan->device->common.dev, "\tfree slot %llx: %d stride: %d\n", + desc->phys, desc->idx, desc->slots_per_op); + + list_del(&desc->chain_node); + apm82181_adma_free_slots(desc, chan); + return 0; +} + +/** + * __apm82181_adma_slot_cleanup - this is the common clean-up routine + * which runs through the channel CDBs list until reach the descriptor + * currently processed. When routine determines that all CDBs of group + * are completed then corresponding callbacks (if any) are called and slots + * are freed. + */ +static void __apm82181_adma_slot_cleanup(apm82181_ch_t *chan) +{ + apm82181_desc_t *iter, *_iter, *group_start = NULL; + dma_cookie_t cookie = 0; + phys_addr_t current_desc = apm82181_chan_get_current_descriptor(chan); + int busy = apm82181_chan_is_busy(chan); + int seen_current = 0, slot_cnt = 0, slots_per_op = 0; + + DBG("apm82181 adma%d: %s\n", + chan->device->id, __FUNCTION__); + DBG("current_desc %llx\n", current_desc); + + if (!current_desc) { + /* There were no transactions yet, so + * nothing to clean + */ + return; + } + + /* free completed slots from the chain starting with + * the oldest descriptor + */ + list_for_each_entry_safe(iter, _iter, &chan->chain, + chain_node) { + DBG(" cookie: %d slot: %d " + "busy: %d this_desc: %llx next_desc: %x cur: %llx ack: %d\n", + iter->async_tx.cookie, iter->idx, busy, iter->phys, + apm82181_desc_get_link(iter, chan), current_desc, + async_tx_test_ack(&iter->async_tx)); + prefetch(_iter); + prefetch(&_iter->async_tx); + + /* do not advance past the current descriptor loaded into the + * hardware channel,subsequent descriptors are either in process + * or have not been submitted + */ + if (seen_current) + break; + + /* stop the search if we reach the current descriptor and the + * channel is busy, or if it appears that the current descriptor + * needs to be re-read (i.e. has been appended to) + */ + if (iter->phys == current_desc) { + BUG_ON(seen_current++); + if (busy || apm82181_desc_get_link(iter, chan)) { + /* not all descriptors of the group have + * been completed; exit. + */ + break; + } + } + + /* detect the start of a group transaction */ + if (!slot_cnt && !slots_per_op) { + slot_cnt = iter->slot_cnt; + slots_per_op = iter->slots_per_op; + if (slot_cnt <= slots_per_op) { + slot_cnt = 0; + slots_per_op = 0; + } + } + + if (slot_cnt) { + if (!group_start) + group_start = iter; + slot_cnt -= slots_per_op; + } + + /* all the members of a group are complete */ + if (slots_per_op != 0 && slot_cnt == 0) { + apm82181_desc_t *grp_iter, *_grp_iter; + int end_of_chain = 0; + + /* clean up the group */ + slot_cnt = group_start->slot_cnt; + grp_iter = group_start; + list_for_each_entry_safe_from(grp_iter, _grp_iter, + &chan->chain, chain_node) { + + cookie = apm82181_adma_run_tx_complete_actions( + grp_iter, chan, cookie); + + slot_cnt -= slots_per_op; + end_of_chain = apm82181_adma_clean_slot( + grp_iter, chan); + if (end_of_chain && slot_cnt) { + /* Should wait for ZeroSum complete */ + if (cookie > 0) + chan->common.completed_cookie = cookie; + return; + } + + if (slot_cnt == 0 || end_of_chain) + break; + } + + /* the group should be complete at this point */ + BUG_ON(slot_cnt); + + slots_per_op = 0; + group_start = NULL; + if (end_of_chain) + break; + else + continue; + } else if (slots_per_op) /* wait for group completion */ + continue; + + cookie = apm82181_adma_run_tx_complete_actions(iter, chan, + cookie); + + if (apm82181_adma_clean_slot(iter, chan)) + break; + } + + BUG_ON(!seen_current); + + if (cookie > 0) { + chan->common.completed_cookie = cookie; + DBG("completed cookie %d\n", cookie); + } + +} + +/** + * apm82181_adma_tasklet - clean up watch-dog initiator + */ +static void apm82181_adma_tasklet (unsigned long data) +{ + apm82181_ch_t *chan = (apm82181_ch_t *) data; + spin_lock(&chan->lock); + INFO; + __apm82181_adma_slot_cleanup(chan); + spin_unlock(&chan->lock); +} + +/** + * apm82181_adma_slot_cleanup - clean up scheduled initiator + */ +static void apm82181_adma_slot_cleanup (apm82181_ch_t *chan) +{ + spin_lock_bh(&chan->lock); + __apm82181_adma_slot_cleanup(chan); + spin_unlock_bh(&chan->lock); +} + +/** + * apm82181_adma_alloc_slots - allocate free slots (if any) + */ +static apm82181_desc_t *apm82181_adma_alloc_slots( + apm82181_ch_t *chan, int num_slots, + int slots_per_op) +{ + apm82181_desc_t *iter = NULL, *_iter, *alloc_start = NULL; + struct list_head chain = LIST_HEAD_INIT(chain); + int slots_found, retry = 0; + + + BUG_ON(!num_slots || !slots_per_op); + /* start search from the last allocated descrtiptor + * if a contiguous allocation can not be found start searching + * from the beginning of the list + */ +retry: + slots_found = 0; + if (retry == 0) + iter = chan->last_used; + else + iter = list_entry(&chan->all_slots, apm82181_desc_t, + slot_node); + prefetch(iter); + DBG("---iter at %p idx %d\n ",iter,iter->idx); + list_for_each_entry_safe_continue(iter, _iter, &chan->all_slots, + slot_node) { + prefetch(_iter); + prefetch(&_iter->async_tx); + if (iter->slots_per_op) { + slots_found = 0; + continue; + } + + /* start the allocation if the slot is correctly aligned */ + if (!slots_found++) + alloc_start = iter; + if (slots_found == num_slots) { + apm82181_desc_t *alloc_tail = NULL; + apm82181_desc_t *last_used = NULL; + iter = alloc_start; + while (num_slots) { + int i; + + /* pre-ack all but the last descriptor */ + if (num_slots != slots_per_op) { + async_tx_ack(&iter->async_tx); + } + list_add_tail(&iter->chain_node, &chain); + alloc_tail = iter; + iter->async_tx.cookie = 0; + iter->hw_next = NULL; + iter->flags = 0; + iter->slot_cnt = num_slots; + for (i = 0; i < slots_per_op; i++) { + iter->slots_per_op = slots_per_op - i; + last_used = iter; + iter = list_entry(iter->slot_node.next, + apm82181_desc_t, + slot_node); + } + num_slots -= slots_per_op; + } + alloc_tail->group_head = alloc_start; + alloc_tail->async_tx.cookie = -EBUSY; + list_splice(&chain, &alloc_tail->group_list); + chan->last_used = last_used; + DBG("---slot allocated at %llx idx %d, hw_desc %p tx_ack %d\n", + alloc_tail->phys, alloc_tail->idx, alloc_tail->hw_desc, + async_tx_test_ack(&alloc_tail->async_tx)); + return alloc_tail; + } + } + if (!retry++) + goto retry; +#ifdef ADMA_DEBUG + static int empty_slot_cnt; + if(!(empty_slot_cnt%100)) + printk(KERN_INFO"No empty slots trying to free some\n"); + empty_slot_cnt++; +#endif + /* try to free some slots if the allocation fails */ + tasklet_schedule(&chan->irq_tasklet); + return NULL; +} + +/** + * apm82181_chan_xor_slot_count - get the number of slots necessary for + * XOR operation + */ +static inline int apm82181_chan_xor_slot_count(size_t len, int src_cnt, + int *slots_per_op) +{ + int slot_cnt; + + /* each XOR descriptor provides up to 16 source operands */ + slot_cnt = *slots_per_op = (src_cnt + XOR_MAX_OPS - 1)/XOR_MAX_OPS; + + if (likely(len <= APM82181_ADMA_XOR_MAX_BYTE_COUNT)) + return slot_cnt; + + printk(KERN_ERR "%s: len %d > max %d !!\n", + __func__, len, APM82181_ADMA_XOR_MAX_BYTE_COUNT); + BUG(); + return slot_cnt; +} + +/** + * apm82181_desc_init_null_xor - initialize the descriptor for NULL XOR + * pseudo operation + */ +static inline void apm82181_desc_init_null_xor(apm82181_desc_t *desc) +{ + memset (desc->hw_desc, 0, sizeof(xor_cb_t)); + desc->hw_next = NULL; + desc->src_cnt = 0; + desc->dst_cnt = 1; +} +/** + * apm82181_chan_set_first_xor_descriptor - initi XORcore chain + */ +static inline void apm82181_chan_set_first_xor_descriptor(apm82181_ch_t *chan, + apm82181_desc_t *next_desc) +{ + volatile xor_regs_t *xor_reg; + + xor_reg = chan->device->xor_base; + + if (xor_reg->sr & XOR_SR_XCP_BIT) + printk(KERN_INFO "%s: Warn: XORcore is running " + "when try to set the first CDB!\n", + __func__); + + xor_last_submit = xor_last_linked = next_desc; + + xor_reg->crsr = XOR_CRSR_64BA_BIT; + + xor_reg->cblalr = next_desc->phys; + xor_reg->cblahr = 0; + xor_reg->cbcr |= XOR_CBCR_LNK_BIT; + + chan->hw_chain_inited = 1; +} +/** + * apm82181_chan_start_null_xor - initiate the first XOR operation (DMA engines + * use FIFOs (as opposite to chains used in XOR) so this is a XOR + * specific operation) + */ +static void apm82181_chan_start_null_xor(apm82181_ch_t *chan) +{ + apm82181_desc_t *sw_desc, *group_start; + dma_cookie_t cookie; + int slot_cnt, slots_per_op; + volatile xor_regs_t *xor_reg = chan->device->xor_base; + + dev_dbg(chan->device->common.dev, + "apm82181 adma%d: %s\n", chan->device->id, __func__); + INFO; + spin_lock_bh(&chan->lock); + slot_cnt = apm82181_chan_xor_slot_count(0, 2, &slots_per_op); + sw_desc = apm82181_adma_alloc_slots(chan, slot_cnt, slots_per_op); + if (sw_desc) { + INFO; + group_start = sw_desc->group_head; + list_splice_init(&sw_desc->group_list, &chan->chain); + async_tx_ack(&sw_desc->async_tx); + apm82181_desc_init_null_xor(group_start); + INFO; + + cookie = chan->common.cookie; + cookie++; + if (cookie <= 1) + cookie = 2; + + /* initialize the completed cookie to be less than + * the most recently used cookie + */ + chan->common.completed_cookie = cookie - 1; + chan->common.cookie = sw_desc->async_tx.cookie = cookie; + + /* channel should not be busy */ + BUG_ON(apm82181_chan_is_busy(chan)); + + /* set the descriptor address */ + apm82181_chan_set_first_xor_descriptor(chan, sw_desc); + + /* run the descriptor */ + xor_reg->crsr = XOR_CRSR_64BA_BIT | XOR_CRSR_XAE_BIT; + } else + printk(KERN_ERR "apm82181 adma%d" + " failed to allocate null descriptor\n", + chan->device->id); + spin_unlock_bh(&chan->lock); +} + +/** + * apm82181_adma_alloc_chan_resources - allocate pools for CDB slots + */ +static int apm82181_adma_alloc_chan_resources(struct dma_chan *chan) +{ + apm82181_ch_t *apm82181_chan = to_apm82181_adma_chan(chan); + apm82181_desc_t *slot = NULL; + char *hw_desc; + int i, db_sz; + int init = apm82181_chan->slots_allocated ? 0 : 1; + + chan->chan_id = apm82181_chan->device->id; + + /* Allocate descriptor slots */ + i = apm82181_chan->slots_allocated; + if (apm82181_chan->device->id != APM82181_XOR_ID) + db_sz = sizeof (dma_cdb_t); + else + db_sz = sizeof (xor_cb_t); + + for (; i < (apm82181_chan->device->pool_size/db_sz); i++) { + slot = kzalloc(sizeof(apm82181_desc_t), GFP_KERNEL); + if (!slot) { + printk(KERN_INFO "APM82181/GT ADMA Channel only initialized" + " %d descriptor slots", i--); + break; + } + + hw_desc = (char *) apm82181_chan->device->dma_desc_pool_virt; + slot->hw_desc = (void *) &hw_desc[i * db_sz]; + dma_async_tx_descriptor_init(&slot->async_tx, chan); + slot->async_tx.tx_submit = apm82181_adma_tx_submit; + INIT_LIST_HEAD(&slot->chain_node); + INIT_LIST_HEAD(&slot->slot_node); + INIT_LIST_HEAD(&slot->group_list); + slot->phys = apm82181_chan->device->dma_desc_pool + i * db_sz; + slot->idx = i; + spin_lock_bh(&apm82181_chan->lock); + apm82181_chan->slots_allocated++; + list_add_tail(&slot->slot_node, &apm82181_chan->all_slots); + spin_unlock_bh(&apm82181_chan->lock); + } + + if (i && !apm82181_chan->last_used) { + apm82181_chan->last_used = + list_entry(apm82181_chan->all_slots.next, + apm82181_desc_t, + slot_node); + } + + printk("apm82181 adma%d: allocated %d descriptor slots\n", + apm82181_chan->device->id, i); + + /* initialize the channel and the chain with a null operation */ + if (init) { + switch (apm82181_chan->device->id) + { + apm82181_chan->hw_chain_inited = 0; + case APM82181_PDMA0_ID: + apm82181_dma_tchan[0] = apm82181_chan; + break; + case APM82181_PDMA1_ID: + apm82181_dma_tchan[1] = apm82181_chan; + break; + case APM82181_PDMA2_ID: + apm82181_dma_tchan[2] = apm82181_chan; + break; + case APM82181_PDMA3_ID: + apm82181_dma_tchan[3] = apm82181_chan; + break; + case APM82181_XOR_ID: + apm82181_dma_tchan[4] = apm82181_chan; + apm82181_chan_start_null_xor(apm82181_chan); + break; + default: + BUG(); + } + apm82181_chan->needs_unmap = 1; + } + + return (i > 0) ? i : -ENOMEM; +} + +/** + * apm82181_desc_assign_cookie - assign a cookie + */ +static dma_cookie_t apm82181_desc_assign_cookie(apm82181_ch_t *chan, + apm82181_desc_t *desc) +{ + dma_cookie_t cookie = chan->common.cookie; + cookie++; + if (cookie < 0) + cookie = 1; + chan->common.cookie = desc->async_tx.cookie = cookie; + return cookie; +} + + +/** + * apm82181_adma_check_threshold - append CDBs to h/w chain if threshold + * has been achieved + */ +static void apm82181_adma_check_threshold(apm82181_ch_t *chan) +{ + dev_dbg(chan->device->common.dev, "apm82181 adma%d: pending: %d\n", + chan->device->id, chan->pending); + INFO; + if (chan->pending >= APM82181_ADMA_THRESHOLD) { + chan->pending = 0; + apm82181_chan_append(chan); + } +} + +/** + * apm82181_adma_tx_submit - submit new descriptor group to the channel + * (it's not necessary that descriptors will be submitted to the h/w + * chains too right now) + */ +static dma_cookie_t apm82181_adma_tx_submit(struct dma_async_tx_descriptor *tx) +{ + apm82181_desc_t *sw_desc = tx_to_apm82181_adma_slot(tx); + apm82181_ch_t *chan = to_apm82181_adma_chan(tx->chan); + apm82181_desc_t *group_start, *old_chain_tail; + int slot_cnt; + int slots_per_op; + dma_cookie_t cookie; + group_start = sw_desc->group_head; + slot_cnt = group_start->slot_cnt; + slots_per_op = group_start->slots_per_op; + INFO; + spin_lock_bh(&chan->lock); + cookie = apm82181_desc_assign_cookie(chan, sw_desc); + + if (unlikely(list_empty(&chan->chain))) { + /* first peer */ + list_splice_init(&sw_desc->group_list, &chan->chain); + chan_first_cdb[chan->device->id] = group_start; + } else { + /* isn't first peer, bind CDBs to chain */ + old_chain_tail = list_entry(chan->chain.prev, + apm82181_desc_t, chain_node); + list_splice_init(&sw_desc->group_list, + &old_chain_tail->chain_node); + /* fix up the hardware chain */ + apm82181_desc_set_link(chan, old_chain_tail, group_start); + } + + /* increment the pending count by the number of operations */ + chan->pending += slot_cnt / slots_per_op; + apm82181_adma_check_threshold(chan); + spin_unlock_bh(&chan->lock); + + DBG("apm82181 adma%d:cookie: %d slot: %d tx %p\n", + chan->device->id, sw_desc->async_tx.cookie, sw_desc->idx, sw_desc); + return cookie; +} +/** + * apm82181_adma_prep_dma_xor - prepare CDB for a XOR operation + */ +static struct dma_async_tx_descriptor *apm82181_adma_prep_dma_xor( + struct dma_chan *chan, dma_addr_t dma_dest, + dma_addr_t *dma_src, unsigned int src_cnt, size_t len, + unsigned long flags) +{ + apm82181_ch_t *apm82181_chan = to_apm82181_adma_chan(chan); + apm82181_desc_t *sw_desc, *group_start; + int slot_cnt, slots_per_op; + +#ifdef ADMA_DEBUG + printk("\n%s(%d):\n\tsrc: ", __func__, + apm82181_chan->device->id); + for (slot_cnt=0; slot_cnt < src_cnt; slot_cnt++) + printk("0x%llx ", dma_src[slot_cnt]); + printk("\n\tdst: 0x%llx\n", dma_dest); +#endif + if (unlikely(!len)) + return NULL; + BUG_ON(unlikely(len > APM82181_ADMA_XOR_MAX_BYTE_COUNT)); + + dev_dbg(apm82181_chan->device->common.dev, + "apm82181 adma%d: %s src_cnt: %d len: %u int_en: %d\n", + apm82181_chan->device->id, __func__, src_cnt, len, + flags & DMA_PREP_INTERRUPT ? 1 : 0); + + spin_lock_bh(&apm82181_chan->lock); + slot_cnt = apm82181_chan_xor_slot_count(len, src_cnt, &slots_per_op); + sw_desc = apm82181_adma_alloc_slots(apm82181_chan, slot_cnt, + slots_per_op); + if (sw_desc) { + group_start = sw_desc->group_head; + apm82181_desc_init_xor(group_start, src_cnt, flags); + apm82181_adma_set_dest(group_start, dma_dest, 0); + while (src_cnt--) + apm82181_adma_set_src(group_start, + dma_src[src_cnt], src_cnt); + apm82181_desc_set_byte_count(group_start, apm82181_chan, len); + sw_desc->unmap_len = len; + sw_desc->async_tx.flags = flags; + } + spin_unlock_bh(&apm82181_chan->lock); + + return sw_desc ? &sw_desc->async_tx : NULL; +} +/** + * apm82181_adma_prep_dma_interrupt - prepare CDB for a pseudo DMA operation + */ +static struct dma_async_tx_descriptor *apm82181_adma_prep_dma_interrupt( + struct dma_chan *chan, unsigned long flags) +{ + apm82181_ch_t *apm82181_chan = to_apm82181_adma_chan(chan); + apm82181_desc_t *sw_desc, *group_start; + int slot_cnt, slots_per_op; + + dev_dbg(apm82181_chan->device->common.dev, + "apm82181 adma%d: %s\n", apm82181_chan->device->id, + __FUNCTION__); + spin_lock_bh(&apm82181_chan->lock); + slot_cnt = slots_per_op = 1; + sw_desc = apm82181_adma_alloc_slots(apm82181_chan, slot_cnt, + slots_per_op); + if (sw_desc) { + group_start = sw_desc->group_head; + apm82181_desc_init_interrupt(group_start, apm82181_chan); + group_start->unmap_len = 0; + sw_desc->async_tx.flags = flags; + } + spin_unlock_bh(&apm82181_chan->lock); + + return sw_desc ? &sw_desc->async_tx : NULL; +} + +/** + * apm82181_adma_prep_dma_memcpy - prepare CDB for a MEMCPY operation + */ +static struct dma_async_tx_descriptor *apm82181_adma_prep_dma_memcpy( + struct dma_chan *chan, dma_addr_t dma_dest, + dma_addr_t dma_src, size_t len, unsigned long flags) +{ + apm82181_ch_t *apm82181_chan = to_apm82181_adma_chan(chan); + apm82181_desc_t *sw_desc, *group_start; + int slot_cnt, slots_per_op; + if (unlikely(!len)) + return NULL; + BUG_ON(unlikely(len > APM82181_ADMA_DMA_MAX_BYTE_COUNT)); + + spin_lock_bh(&apm82181_chan->lock); + + dev_dbg(apm82181_chan->device->common.dev, + "apm82181 adma%d: %s len: %u int_en %d \n", + apm82181_chan->device->id, __FUNCTION__, len, + flags & DMA_PREP_INTERRUPT ? 1 : 0); + + slot_cnt = slots_per_op = 1; + sw_desc = apm82181_adma_alloc_slots(apm82181_chan, slot_cnt, + slots_per_op); + if (sw_desc) { + group_start = sw_desc->group_head; + flags |= DMA_PREP_INTERRUPT; + apm82181_desc_init_memcpy(group_start, flags); + apm82181_adma_set_dest(group_start, dma_dest, 0); + apm82181_adma_set_src(group_start, dma_src, 0); + apm82181_desc_set_byte_count(group_start, apm82181_chan, len); + sw_desc->unmap_len = len; + sw_desc->async_tx.flags = flags; + } + spin_unlock_bh(&apm82181_chan->lock); + return sw_desc ? &sw_desc->async_tx : NULL; +} + + +/** + * apm82181_adma_set_dest - set destination address into descriptor + */ +static void apm82181_adma_set_dest(apm82181_desc_t *sw_desc, + dma_addr_t addr, int index) +{ + apm82181_ch_t *chan = to_apm82181_adma_chan(sw_desc->async_tx.chan); + BUG_ON(index >= sw_desc->dst_cnt); + + switch (chan->device->id) { + case APM82181_PDMA0_ID: + case APM82181_PDMA1_ID: + case APM82181_PDMA2_ID: + case APM82181_PDMA3_ID: + /* to do: support transfers lengths > + * APM82181_ADMA_DMA/XOR_MAX_BYTE_COUNT + */ + apm82181_desc_set_dest_addr(sw_desc->group_head, + // chan, 0x8, addr, index); // Enabling HB bus + chan, addr, index); + break; + case APM82181_XOR_ID: + sw_desc = apm82181_get_group_entry(sw_desc, index); + apm82181_desc_set_dest_addr(sw_desc, chan, + addr, index); + break; + default: + BUG(); + } +} + + +/** + * apm82181_adma_free_chan_resources - free the resources allocated + */ +static void apm82181_adma_free_chan_resources(struct dma_chan *chan) +{ + apm82181_ch_t *apm82181_chan = to_apm82181_adma_chan(chan); + apm82181_desc_t *iter, *_iter; + int in_use_descs = 0; + + apm82181_adma_slot_cleanup(apm82181_chan); + + spin_lock_bh(&apm82181_chan->lock); + list_for_each_entry_safe(iter, _iter, &apm82181_chan->chain, + chain_node) { + in_use_descs++; + list_del(&iter->chain_node); + } + list_for_each_entry_safe_reverse(iter, _iter, + &apm82181_chan->all_slots, slot_node) { + list_del(&iter->slot_node); + kfree(iter); + apm82181_chan->slots_allocated--; + } + apm82181_chan->last_used = NULL; + + dev_dbg(apm82181_chan->device->common.dev, + "apm82181 adma%d %s slots_allocated %d\n", + apm82181_chan->device->id, + __FUNCTION__, apm82181_chan->slots_allocated); + spin_unlock_bh(&apm82181_chan->lock); + + /* one is ok since we left it on there on purpose */ + if (in_use_descs > 1) + printk(KERN_ERR "GT: Freeing %d in use descriptors!\n", + in_use_descs - 1); +} + +/** + * apm82181_adma_tx_status - poll the status of an ADMA transaction + * @chan: ADMA channel handle + * @cookie: ADMA transaction identifier + */ +static enum dma_status apm82181_adma_tx_status(struct dma_chan *chan, + dma_cookie_t cookie, struct dma_tx_state *txstate) +{ + apm82181_ch_t *apm82181_chan = to_apm82181_adma_chan(chan); + enum dma_status ret; + + ret = dma_cookie_status(chan, cookie, txstate); + if (ret == DMA_COMPLETE) + return ret; + + apm82181_adma_slot_cleanup(apm82181_chan); + + return dma_cookie_status(chan, cookie, txstate); +} + +/** + * apm82181_adma_eot_handler - end of transfer interrupt handler + */ +static irqreturn_t apm82181_adma_eot_handler(int irq, void *data) +{ + apm82181_ch_t *chan = data; + + dev_dbg(chan->device->common.dev, + "apm82181 adma%d: %s\n", chan->device->id, __FUNCTION__); + INFO; + if(chan->device->id == APM82181_XOR_ID) + tasklet_schedule(&chan->irq_tasklet); + apm82181_adma_device_clear_eot_status(chan); + + return IRQ_HANDLED; +} + +/** + * apm82181_adma_err_handler - DMA error interrupt handler; + * do the same things as a eot handler + */ +#if 0 +static irqreturn_t apm82181_adma_err_handler(int irq, void *data) +{ + apm82181_ch_t *chan = data; + dev_dbg(chan->device->common.dev, + "apm82181 adma%d: %s\n", chan->device->id, __FUNCTION__); + tasklet_schedule(&chan->irq_tasklet); + apm82181_adma_device_clear_eot_status(chan); + + return IRQ_HANDLED; +} +#endif +/** + * apm82181_test_callback - called when test operation has been done + */ +static void apm82181_test_callback (void *unused) +{ + complete(&apm82181_r5_test_comp); +} + +/** + * apm82181_adma_issue_pending - flush all pending descriptors to h/w + */ +static void apm82181_adma_issue_pending(struct dma_chan *chan) +{ + apm82181_ch_t *apm82181_chan = to_apm82181_adma_chan(chan); + + DBG("apm82181 adma%d: %s %d \n", apm82181_chan->device->id, + __FUNCTION__, apm82181_chan->pending); + if (apm82181_chan->pending) { + apm82181_chan->pending = 0; + apm82181_chan_append(apm82181_chan); + } +} + +static inline void xor_hw_init (apm82181_dev_t *adev) +{ + volatile xor_regs_t *xor_reg = adev->xor_base; + /* Reset XOR */ + xor_reg->crsr = XOR_CRSR_XASR_BIT; + xor_reg->crrr = XOR_CRSR_64BA_BIT; + + /* enable XOR engine interrupts */ + xor_reg->ier = XOR_IE_CBCIE_BIT | + XOR_IE_ICBIE_BIT | XOR_IE_ICIE_BIT | XOR_IE_RPTIE_BIT; +} + +/* + * Per channel probe + */ +static int apm82181_dma_per_chan_probe(struct platform_device *ofdev) +{ + int ret = 0, irq; + const u32 *index, *dcr_regs, *pool_size; + apm82181_plb_dma_t *pdma; + apm82181_dev_t *adev; + apm82181_ch_t *chan; + struct device_node *np = ofdev->dev.of_node; + struct resource res; + int len; + + INFO; + pdma = dev_get_drvdata(ofdev->dev.parent); + BUG_ON(!pdma); + if ((adev = kzalloc(sizeof(*adev), GFP_KERNEL)) == NULL) { + printk("ERROR:No Free memory for allocating dma channels\n"); + ret = -ENOMEM; + goto out; + } + adev->dev = &ofdev->dev; + index = of_get_property(np, "cell-index", NULL); + if(!index) { + printk(KERN_ERR "adma-channel: Device node %s has missing or invalid " + "cell-index property\n", np->full_name); + goto err; + } + adev->id = (int)*index; + /* The XOR engine/PLB DMA 4 channels have different resources/pool_sizes */ + if (adev->id != APM82181_XOR_ID){ + dcr_regs = of_get_property(np, "dcr-reg", &len); + if (!dcr_regs || (len != 2 * sizeof(u32))) { + printk(KERN_ERR "plb_dma channel%d: Can't get DCR register base !", + adev->id); + goto err; + } + adev->dcr_base = dcr_regs[0]; + + pool_size = of_get_property(np, "pool_size", NULL); + if(!pool_size) { + printk(KERN_ERR "plb_dma channel%d: Device node %s has missing or " + "invalid pool_size property\n", adev->id, np->full_name); + goto err; + } + adev->pool_size = *pool_size; + } else { + if (of_address_to_resource(np, 0, &res)) { + printk(KERN_ERR "adma_xor channel%d %s: could not get resource address.\n", + adev->id,np->full_name); + goto err; + } + + DBG("XOR resource start = %llx end = %llx\n", res.start, res.end); + adev->xor_base = ioremap(res.start, res.end - res.start + 1); + if (!adev->xor_base){ + printk(KERN_ERR "XOR engine registers memory mapping failed.\n"); + goto err; + } + adev->pool_size = PAGE_SIZE << 1; + } + + adev->pdma = pdma; + adev->ofdev = ofdev; + dev_set_drvdata(&(ofdev->dev),adev); + + switch (adev->id){ + case APM82181_PDMA0_ID: + case APM82181_PDMA1_ID: + case APM82181_PDMA2_ID: + case APM82181_PDMA3_ID: + dma_cap_set(DMA_MEMCPY,adev->cap_mask); + break; + case APM82181_XOR_ID: + dma_cap_set(DMA_XOR,adev->cap_mask); + dma_cap_set(DMA_INTERRUPT,adev->cap_mask); + break; + default: + BUG(); + } + /* XOR h/w configuration */ + if(adev->id == APM82181_XOR_ID) + xor_hw_init(adev); + /* allocate coherent memory for hardware descriptors + * note: writecombine gives slightly better performance, but + * requires that we explicitly drain the write buffer + */ + if ((adev->dma_desc_pool_virt = dma_alloc_coherent(&ofdev->dev, + adev->pool_size, &adev->dma_desc_pool, GFP_KERNEL)) == NULL) { + ret = -ENOMEM; + goto err_dma_alloc; + } + + adev->common.cap_mask = adev->cap_mask; + INIT_LIST_HEAD(&adev->common.channels); + /* set base routines */ + adev->common.device_alloc_chan_resources = + apm82181_adma_alloc_chan_resources; + adev->common.device_free_chan_resources = + apm82181_adma_free_chan_resources; + adev->common.device_tx_status = apm82181_adma_tx_status; + adev->common.device_issue_pending = apm82181_adma_issue_pending; + adev->common.dev = &ofdev->dev; + + /* set prep routines based on capability */ + if (dma_has_cap(DMA_MEMCPY, adev->common.cap_mask)) { + adev->common.device_prep_dma_memcpy = + apm82181_adma_prep_dma_memcpy; + } + + if (dma_has_cap(DMA_INTERRUPT, adev->common.cap_mask)) { + adev->common.device_prep_dma_interrupt = + apm82181_adma_prep_dma_interrupt; + } + + if (dma_has_cap(DMA_XOR, adev->common.cap_mask)) { + adev->common.max_xor = XOR_MAX_OPS; + adev->common.device_prep_dma_xor = + apm82181_adma_prep_dma_xor; + } + + /* create a channel */ + if ((chan = kzalloc(sizeof(*chan), GFP_KERNEL)) == NULL) { + ret = -ENOMEM; + goto err_chan_alloc; + } + tasklet_init(&chan->irq_tasklet, apm82181_adma_tasklet, + (unsigned long)chan); + + irq = irq_of_parse_and_map(np, 0); + switch (adev->id){ + case 0: + if (irq >= 0) { + ret = request_irq(irq, apm82181_adma_eot_handler, + IRQF_DISABLED, "adma-chan0", chan); + if (ret) { + printk("Failed to request IRQ %d\n",irq); + ret = -EIO; + goto err_irq; + } + } + break; + case 1: + if (irq >= 0) { + ret = request_irq(irq, apm82181_adma_eot_handler, + IRQF_DISABLED, "adma-chan1", chan); + if (ret) { + printk("Failed to request IRQ %d\n",irq); + ret = -EIO; + goto err_irq; + } + } + break; + case 2: + if (irq >= 0) { + ret = request_irq(irq, apm82181_adma_eot_handler, + IRQF_DISABLED, "adma-chan2", chan); + if (ret) { + printk("Failed to request IRQ %d\n",irq); + ret = -EIO; + goto err_irq; + } + } + break; + case 3: + if (irq >= 0) { + ret = request_irq(irq, apm82181_adma_eot_handler, + IRQF_DISABLED, "adma-chan3", chan); + if (ret) { + printk("Failed to request IRQ %d\n",irq); + ret = -EIO; + goto err_irq; + } + } + break; + case 4: + if (irq >= 0) { + ret = request_irq(irq, apm82181_adma_eot_handler, + IRQF_DISABLED, "adma-xor", chan); + if (ret) { + printk("Failed to request IRQ %d\n",irq); + ret = -EIO; + goto err_irq; + } + } + break; + default: + BUG(); + } + + spin_lock_init(&chan->lock); + chan->device = adev; + INIT_LIST_HEAD(&chan->chain); + INIT_LIST_HEAD(&chan->all_slots); + chan->common.device = &adev->common; + list_add_tail(&chan->common.device_node, &adev->common.channels); + adev->common.chancnt++; + + printk( "AMCC(R) APM82181 ADMA Engine found [%d]: " + "( capabilities: %s%s%s%s%s%s)\n", + adev->id, + dma_has_cap(DMA_PQ, adev->common.cap_mask) ? "pq_xor " : "", + dma_has_cap(DMA_PQ_VAL, adev->common.cap_mask) ? "pq_val " : + "", + dma_has_cap(DMA_XOR, adev->common.cap_mask) ? "xor " : "", + dma_has_cap(DMA_XOR_VAL, adev->common.cap_mask) ? "xor_val " : + "", + dma_has_cap(DMA_MEMCPY, adev->common.cap_mask) ? "memcpy " : "", + dma_has_cap(DMA_INTERRUPT, adev->common.cap_mask) ? "int " : ""); + INFO; + ret = dma_async_device_register(&adev->common); + if (ret) { + dev_err(&ofdev->dev, "failed to register dma async device"); + goto err_irq; + } + INFO; + + goto out; +err_irq: + kfree(chan); +err_chan_alloc: + dma_free_coherent(&ofdev->dev, adev->pool_size, + adev->dma_desc_pool_virt, adev->dma_desc_pool); +err_dma_alloc: + if (adev->xor_base) + iounmap(adev->xor_base); +err: + kfree(adev); +out: + return ret; +} + +static struct of_device_id dma_4chan_match[] = +{ + { + .compatible = "amcc,apm82181-adma", + }, + {}, +}; + +static struct of_device_id dma_per_chan_match[] = { + {.compatible = "amcc,apm82181-dma-4channel",}, + {.compatible = "amcc,xor",}, + {}, +}; +/* + * apm82181_adma_probe - probe the asynch device + */ +static int apm82181_pdma_probe(struct platform_device *ofdev) +{ + int ret = 0; + apm82181_plb_dma_t *pdma; + + if ((pdma = kzalloc(sizeof(*pdma), GFP_KERNEL)) == NULL) { + ret = -ENOMEM; + goto out; + } + pdma->dev = &ofdev->dev; + pdma->ofdev = ofdev; + printk(PPC4XX_EDMA "Probing AMCC APM82181 ADMA engines...\n"); + + dev_set_drvdata(&(ofdev->dev),pdma); + of_platform_bus_probe(ofdev->dev.of_node, dma_per_chan_match,&ofdev->dev); + +out: + return ret; +} + +/* + * apm82181_test_xor - test are RAID-5 XOR capability enabled successfully. + * For this we just perform one DMA XOR operation with the 3 sources + * to a destination + */ +static int apm82181_test_xor (apm82181_ch_t *chan) +{ + apm82181_desc_t *sw_desc, *group_start; + struct page *pg_src[3], *pg_dest; + char *a; + dma_addr_t dma_src_addr[3]; + dma_addr_t dma_dst_addr; + int rval = -EFAULT, i; + int len = PAGE_SIZE, src_cnt = 3; + int slot_cnt, slots_per_op; + INFO; + printk("ADMA channel %d XOR testing\n",chan->device->id); + for(i = 0; i < 3; i++){ + pg_src[i] = alloc_page(GFP_KERNEL); + if (!pg_src[i]) + return -ENOMEM; + } + pg_dest = alloc_page(GFP_KERNEL); + if (!pg_dest) + return -ENOMEM; + /* Fill the test page with ones */ + memset(page_address(pg_src[0]), 0xDA, len); + memset(page_address(pg_src[1]), 0xDA, len); + memset(page_address(pg_src[2]), 0x00, len); + memset(page_address(pg_dest), 0xA5, len); + for(i = 0; i < 3; i++){ + a = page_address(pg_src[i]); + printk("The virtual addr of src %d =%x\n",i, (unsigned int)a); + MEM_HEXDUMP(a,50); + } + a = page_address(pg_dest); + printk("The virtual addr of dest=%x\n", (unsigned int)a); + MEM_HEXDUMP(a,50); + + for(i = 0; i < 3; i++){ + dma_src_addr[i] = dma_map_page(chan->device->dev, pg_src[i], 0, len, + DMA_BIDIRECTIONAL); + } + dma_dst_addr = dma_map_page(chan->device->dev, pg_dest, 0, len, + DMA_BIDIRECTIONAL); + printk("dma_src_addr[0]: %llx; dma_src_addr[1]: %llx;\n " + "dma_src_addr[2]: %llx; dma_dst_addr %llx, len: %x\n", dma_src_addr[0], + dma_src_addr[1], dma_src_addr[2], dma_dst_addr, len); + + spin_lock_bh(&chan->lock); + slot_cnt = apm82181_chan_xor_slot_count(len, src_cnt, &slots_per_op); + sw_desc = apm82181_adma_alloc_slots(chan, slot_cnt, slots_per_op); + if (sw_desc) { + group_start = sw_desc->group_head; + apm82181_desc_init_xor(group_start, src_cnt, DMA_PREP_INTERRUPT); + /* Setup addresses */ + while (src_cnt--) + apm82181_adma_set_src(group_start, + dma_src_addr[src_cnt], src_cnt); + apm82181_adma_set_dest(group_start, dma_dst_addr, 0); + apm82181_desc_set_byte_count(group_start, chan, len); + sw_desc->unmap_len = PAGE_SIZE; + } else { + rval = -EFAULT; + spin_unlock_bh(&chan->lock); + goto exit; + } + spin_unlock_bh(&chan->lock); + + printk("Submit CDB...\n"); + MEM_HEXDUMP(sw_desc->hw_desc, 96); + async_tx_ack(&sw_desc->async_tx); + sw_desc->async_tx.callback = apm82181_test_callback; + sw_desc->async_tx.callback_param = NULL; + + init_completion(&apm82181_r5_test_comp); + apm82181_adma_tx_submit(&sw_desc->async_tx); + apm82181_adma_issue_pending(&chan->common); + //wait_for_completion(&apm82181_r5_test_comp); + /* wait for a while so that dma transaction finishes */ + mdelay(100); + /* Now check if the test page zeroed */ + a = page_address(pg_dest); + /* XOR result at destination */ + MEM_HEXDUMP(a,50); + if ((*(u32*)a) == 0x00000000 && memcmp(a, a+4, PAGE_SIZE-4)==0) { + /* page dest XOR is corect as expected - RAID-5 enabled */ + rval = 0; + } else { + /* RAID-5 was not enabled */ + rval = -EINVAL; + } + +exit: + dma_unmap_page(chan->device->dev, dma_src_addr[0], PAGE_SIZE, DMA_BIDIRECTIONAL); + dma_unmap_page(chan->device->dev, dma_src_addr[1], PAGE_SIZE, DMA_BIDIRECTIONAL); + dma_unmap_page(chan->device->dev, dma_src_addr[2], PAGE_SIZE, DMA_BIDIRECTIONAL); + dma_unmap_page(chan->device->dev, dma_dst_addr, PAGE_SIZE, DMA_BIDIRECTIONAL); + __free_page(pg_src[0]); + __free_page(pg_src[1]); + __free_page(pg_src[2]); + __free_page(pg_dest); + return rval; +} + + +/* + * apm82181_test_dma - test are RAID-5 capabilities enabled successfully. + * For this we just perform one WXOR operation with the same source + * and destination addresses, the GF-multiplier is 1; so if RAID-5 + o/of_platform_driver_unregister(&apm82181_pdma_driver); + * capabilities are enabled then we'll get src/dst filled with zero. + */ +static int apm82181_test_dma (apm82181_ch_t *chan) +{ + apm82181_desc_t *sw_desc; + struct page *pg_src, *pg_dest; + char *a, *d; + dma_addr_t dma_src_addr; + dma_addr_t dma_dst_addr; + int rval = -EFAULT; + int len = PAGE_SIZE; + + printk("PLB DMA channel %d memcpy testing\n",chan->device->id); + pg_src = alloc_page(GFP_KERNEL); + if (!pg_src) + return -ENOMEM; + pg_dest = alloc_page(GFP_KERNEL); + if (!pg_dest) + return -ENOMEM; + /* Fill the test page with ones */ + memset(page_address(pg_src), 0x77, len); + memset(page_address(pg_dest), 0xa5, len); + a = page_address(pg_src); + printk("The virtual addr of src =%x\n", (unsigned int)a); + MEM_HEXDUMP(a,50); + a = page_address(pg_dest); + printk("The virtual addr of dest=%x\n", (unsigned int)a); + MEM_HEXDUMP(a,50); + dma_src_addr = dma_map_page(chan->device->dev, pg_src, 0, len, + DMA_BIDIRECTIONAL); + dma_dst_addr = dma_map_page(chan->device->dev, pg_dest, 0, len, + DMA_BIDIRECTIONAL); + printk("dma_src_addr: %llx; dma_dst_addr %llx\n", dma_src_addr, dma_dst_addr); + + spin_lock_bh(&chan->lock); + sw_desc = apm82181_adma_alloc_slots(chan, 1, 1); + if (sw_desc) { + /* 1 src, 1 dst, int_ena */ + apm82181_desc_init_memcpy(sw_desc, DMA_PREP_INTERRUPT); + //apm82181_desc_init_memcpy(sw_desc, 0); + /* Setup adresses */ + apm82181_adma_set_src(sw_desc, dma_src_addr, 0); + apm82181_adma_set_dest(sw_desc, dma_dst_addr, 0); + apm82181_desc_set_byte_count(sw_desc, chan, len); + sw_desc->unmap_len = PAGE_SIZE; + } else { + rval = -EFAULT; + spin_unlock_bh(&chan->lock); + goto exit; + } + spin_unlock_bh(&chan->lock); + + printk("Submit CDB...\n"); + MEM_HEXDUMP(sw_desc->hw_desc, 96); + async_tx_ack(&sw_desc->async_tx); + sw_desc->async_tx.callback = apm82181_test_callback; + sw_desc->async_tx.callback_param = NULL; + + init_completion(&apm82181_r5_test_comp); + apm82181_adma_tx_submit(&sw_desc->async_tx); + apm82181_adma_issue_pending(&chan->common); + //wait_for_completion(&apm82181_r5_test_comp); + + a = page_address(pg_src); + d = page_address(pg_dest); + if (!memcmp(a, d, len)) { + rval = 0; + } else { + rval = -EINVAL; + } + + a = page_address(pg_src); + printk("\nAfter DMA done:"); + printk("\nsrc %x value:\n", (unsigned int)a); + MEM_HEXDUMP(a,96); + a = page_address(pg_dest); + printk("\ndest%x value:\n", (unsigned int)a); + MEM_HEXDUMP(a,96); + +exit: + __free_page(pg_src); + __free_page(pg_dest); + return rval; +} + +static struct platform_driver apm82181_pdma_driver = { + .driver = { + .name = "apm82181_plb_dma", + .owner = THIS_MODULE, + .of_match_table = dma_4chan_match, + }, + .probe = apm82181_pdma_probe, + //.remove = apm82181_pdma_remove, +}; +struct platform_driver apm82181_dma_per_chan_driver = { + .driver = { + .name = "apm82181-dma-4channel", + .owner = THIS_MODULE, + .of_match_table = dma_per_chan_match, + }, + .probe = apm82181_dma_per_chan_probe, +}; + +static int __init apm82181_adma_per_chan_init (void) +{ + int rval; + rval = platform_driver_register(&apm82181_dma_per_chan_driver); + return rval; +} + +static int __init apm82181_adma_init (void) +{ + int rval; + struct proc_dir_entry *p; + + rval = platform_driver_register(&apm82181_pdma_driver); + + return rval; +} + +#if 0 +static void __exit apm82181_adma_exit (void) +{ + of_unregister_platform_driver(&apm82181_pdma_driver); + return; +} +module_exit(apm82181_adma_exit); +#endif + +module_init(apm82181_adma_per_chan_init); +module_init(apm82181_adma_init); + +MODULE_AUTHOR("Tai Tri Nguyen<ttnguyen@appliedmicro.com>"); +MODULE_DESCRIPTION("APM82181 ADMA Engine Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/dma/ppc4xx/ppc460ex_4chan_dma.c b/drivers/dma/ppc4xx/ppc460ex_4chan_dma.c new file mode 100644 index 00000000000..821e279e0b7 --- /dev/null +++ b/drivers/dma/ppc4xx/ppc460ex_4chan_dma.c @@ -0,0 +1,1110 @@ +/* + * Copyright(c) 2008 Applied Micro Circuits Corporation(AMCC). 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 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. + */ + + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/async_tx.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/uaccess.h> +#include <linux/proc_fs.h> +#include <linux/slab.h> +#include <asm/dcr-regs.h> +#include <asm/dcr.h> +#include "ppc460ex_4chan_dma.h" + + + +#ifdef DEBUG_TEST +#define dma_pr printk +#else +#define dma_pr +#endif +#define TEST_SIZE 12 + + +ppc460ex_plb_dma_dev_t *adev; + + + +int ppc460ex_get_dma_channel(void) +{ + int i; + unsigned int status = 0; + status = mfdcr(DCR_DMA2P40_SR); + + for(i=0; i<MAX_PPC460EX_DMA_CHANNELS; i++) { + if ((status & (1 >> (20+i))) == 0) + return i; + } + return -ENODEV; +} + + +int ppc460ex_get_dma_status(void) +{ + return (mfdcr(DCR_DMA2P40_SR)); + +} + + +int ppc460ex_set_src_addr(int ch_id, phys_addr_t src_addr) +{ + + if (ch_id >= MAX_PPC460EX_DMA_CHANNELS) { + printk("%s: bad channel %d\n", __FUNCTION__, ch_id); + return DMA_STATUS_BAD_CHANNEL; + } + + +#ifdef PPC4xx_DMA_64BIT + mtdcr(DCR_DMA2P40_SAH0 + ch_id*8, src_addr >> 32); +#endif + mtdcr(DCR_DMA2P40_SAL0 + ch_id*8, (u32)src_addr); + + return DMA_STATUS_GOOD; +} + +int ppc460ex_set_dst_addr(int ch_id, phys_addr_t dst_addr) +{ + + if (ch_id >= MAX_PPC460EX_DMA_CHANNELS) { + printk(KERN_ERR "%s: bad channel %d\n", __FUNCTION__, ch_id); + return DMA_STATUS_BAD_CHANNEL; + } + +#ifdef PPC4xx_DMA_64BIT + mtdcr(DCR_DMA2P40_DAH0 + ch_id*8, dst_addr >> 32); +#endif + mtdcr(DCR_DMA2P40_DAL0 + ch_id*8, (u32)dst_addr); + + return DMA_STATUS_GOOD; +} + + + +/* + * Sets the dma mode for single DMA transfers only. + * For scatter/gather transfers, the mode is passed to the + * alloc_dma_handle() function as one of the parameters. + * + * The mode is simply saved and used later. This allows + * the driver to call set_dma_mode() and set_dma_addr() in + * any order. + * + * Valid mode values are: + * + * DMA_MODE_READ peripheral to memory + * DMA_MODE_WRITE memory to peripheral + * DMA_MODE_MM memory to memory + * DMA_MODE_MM_DEVATSRC device-paced memory to memory, device at src + * DMA_MODE_MM_DEVATDST device-paced memory to memory, device at dst + */ +int ppc460ex_set_dma_mode(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id, unsigned int mode) +{ + + ppc460ex_plb_dma_ch_t *dma_chan = adev->chan[ch_id]; + + if (ch_id >= MAX_PPC460EX_DMA_CHANNELS) { + printk("%s: bad channel %d\n", __FUNCTION__, dma_chan->chan_id); + return DMA_STATUS_BAD_CHANNEL; + } + + dma_chan->mode = mode; + return DMA_STATUS_GOOD; +} + + + + +/* + * Sets the DMA Count register. Note that 'count' is in bytes. + * However, the DMA Count register counts the number of "transfers", + * where each transfer is equal to the bus width. Thus, count + * MUST be a multiple of the bus width. + */ +void ppc460ex_set_dma_count(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id, unsigned int count) +{ + ppc460ex_plb_dma_ch_t *dma_chan = adev->chan[ch_id]; + +//#ifdef DEBUG_4xxDMA + + { + int error = 0; + switch (dma_chan->pwidth) { + case PW_8: + break; + case PW_16: + if (count & 0x1) + error = 1; + break; + case PW_32: + if (count & 0x3) + error = 1; + break; + case PW_64: + if (count & 0x7) + error = 1; + break; + + case PW_128: + if (count & 0xf) + error = 1; + break; + default: + printk("set_dma_count: invalid bus width: 0x%x\n", + dma_chan->pwidth); + return; + } + if (error) + printk + ("Warning: set_dma_count count 0x%x bus width %d\n", + count, dma_chan->pwidth); + } +//#endif + count = count >> dma_chan->shift; + //count = 10; + mtdcr(DCR_DMA2P40_CTC0 + (ch_id * 0x8), count); + +} + + + + +/* + * Enables the channel interrupt. + * + * If performing a scatter/gatter transfer, this function + * MUST be called before calling alloc_dma_handle() and building + * the sgl list. Otherwise, interrupts will not be enabled, if + * they were previously disabled. + */ +int ppc460ex_enable_dma_interrupt(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id) +{ + unsigned int control; + ppc460ex_plb_dma_ch_t *dma_chan = adev->chan[ch_id]; + + if (ch_id >= MAX_PPC460EX_DMA_CHANNELS) { + printk(KERN_ERR "%s: bad channel %d\n", __FUNCTION__, ch_id); + return DMA_STATUS_BAD_CHANNEL; + } + + dma_chan->int_enable = 1; + + + control = mfdcr(DCR_DMA2P40_CR0); + control |= DMA_CIE_ENABLE; /* Channel Interrupt Enable */ + mtdcr(DCR_DMA2P40_CR0, control); + + + +#if 1 + control = mfdcr(DCR_DMA2P40_CTC0); + control |= DMA_CTC_TCIE | DMA_CTC_ETIE| DMA_CTC_EIE; + mtdcr(DCR_DMA2P40_CTC0, control); + +#endif + + + return DMA_STATUS_GOOD; + +} + + +/* + * Disables the channel interrupt. + * + * If performing a scatter/gatter transfer, this function + * MUST be called before calling alloc_dma_handle() and building + * the sgl list. Otherwise, interrupts will not be disabled, if + * they were previously enabled. + */ +int ppc460ex_disable_dma_interrupt(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id) +{ + unsigned int control; + ppc460ex_plb_dma_ch_t *dma_chan = adev->chan[ch_id]; + + if (ch_id >= MAX_PPC460EX_DMA_CHANNELS) { + printk(KERN_ERR "%s: bad channel %d\n", __FUNCTION__, ch_id); + return DMA_STATUS_BAD_CHANNEL; + } + dma_chan->int_enable = 0; + control = mfdcr(DCR_DMA2P40_CR0 + (ch_id * 0x8)); + control &= ~DMA_CIE_ENABLE; /* Channel Interrupt Enable */ + mtdcr(DCR_DMA2P40_CR0 + (ch_id * 0x8), control); + + return DMA_STATUS_GOOD; +} + + +/* + * This function returns the channel configuration. + */ +int ppc460ex_get_channel_config(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id, + ppc460ex_plb_dma_ch_t *p_dma_ch) +{ + unsigned int polarity; + unsigned int control; + ppc460ex_plb_dma_ch_t *dma_chan = adev->chan[ch_id]; + + if (ch_id >= MAX_PPC460EX_DMA_CHANNELS) { + printk(KERN_ERR "%s: bad channel %d\n", __FUNCTION__, ch_id); + return DMA_STATUS_BAD_CHANNEL; + } + + memcpy(p_dma_ch, dma_chan, sizeof(ppc460ex_plb_dma_ch_t)); + + polarity = mfdcr(DCR_DMA2P40_POL); + + p_dma_ch->polarity = polarity & GET_DMA_POLARITY(ch_id); + control = mfdcr(DCR_DMA2P40_CR0 + (ch_id * 0x8)); + + p_dma_ch->cp = GET_DMA_PRIORITY(control); + p_dma_ch->pwidth = GET_DMA_PW(control); + p_dma_ch->psc = GET_DMA_PSC(control); + p_dma_ch->pwc = GET_DMA_PWC(control); + p_dma_ch->phc = GET_DMA_PHC(control); + p_dma_ch->ce = GET_DMA_CE_ENABLE(control); + p_dma_ch->int_enable = GET_DMA_CIE_ENABLE(control); + p_dma_ch->shift = GET_DMA_PW(control); + p_dma_ch->pf = GET_DMA_PREFETCH(control); + + return DMA_STATUS_GOOD; + +} + +/* + * Sets the priority for the DMA channel dmanr. + * Since this is setup by the hardware init function, this function + * can be used to dynamically change the priority of a channel. + * + * Acceptable priorities: + * + * PRIORITY_LOW + * PRIORITY_MID_LOW + * PRIORITY_MID_HIGH + * PRIORITY_HIGH + * + */ +int ppc460ex_set_channel_priority(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id, + unsigned int priority) +{ + unsigned int control; + + if (ch_id >= MAX_PPC460EX_DMA_CHANNELS) { + printk(KERN_ERR "%s: bad channel %d\n", __FUNCTION__, ch_id); + return DMA_STATUS_BAD_CHANNEL; + } + + if ((priority != PRIORITY_LOW) && + (priority != PRIORITY_MID_LOW) && + (priority != PRIORITY_MID_HIGH) && (priority != PRIORITY_HIGH)) { + printk("%s:bad priority: 0x%x\n", __FUNCTION__, priority); + } + + control = mfdcr(DCR_DMA2P40_CR0 + (ch_id * 0x8)); + control |= SET_DMA_PRIORITY(priority); + mtdcr(DCR_DMA2P40_CR0 + (ch_id * 0x8), control); + + return DMA_STATUS_GOOD; +} + +/* + * Returns the width of the peripheral attached to this channel. This assumes + * that someone who knows the hardware configuration, boot code or some other + * init code, already set the width. + * + * The return value is one of: + * PW_8 + * PW_16 + * PW_32 + * PW_64 + * + * The function returns 0 on error. + */ +unsigned int ppc460ex_get_peripheral_width(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id) +{ + unsigned int control; + + if (ch_id >= MAX_PPC460EX_DMA_CHANNELS) { + printk(KERN_ERR "%s: bad channel %d\n", __FUNCTION__, ch_id); + return DMA_STATUS_BAD_CHANNEL; + } + control = mfdcr(DCR_DMA2P40_CR0 + (ch_id * 0x8)); + return (GET_DMA_PW(control)); +} + +/* + * Enables the burst on the channel (BTEN bit in the control/count register) + * Note: + * For scatter/gather dma, this function MUST be called before the + * ppc4xx_alloc_dma_handle() func as the chan count register is copied into the + * sgl list and used as each sgl element is added. + */ +int ppc460ex_enable_burst(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id) +{ + unsigned int ctc; + if (ch_id >= MAX_PPC460EX_DMA_CHANNELS) { + printk(KERN_ERR "%s: bad channel %d\n", __FUNCTION__, ch_id); + return DMA_STATUS_BAD_CHANNEL; + } + + ctc = mfdcr(DCR_DMA2P40_CTC0 + (ch_id * 0x8)) | DMA_CTC_BTEN; + mtdcr(DCR_DMA2P40_CTC0 + (ch_id * 0x8), ctc); + return DMA_STATUS_GOOD; +} + + +/* + * Disables the burst on the channel (BTEN bit in the control/count register) + * Note: + * For scatter/gather dma, this function MUST be called before the + * ppc4xx_alloc_dma_handle() func as the chan count register is copied into the + * sgl list and used as each sgl element is added. + */ +int ppc460ex_disable_burst(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id) +{ + unsigned int ctc; + if (ch_id >= MAX_PPC460EX_DMA_CHANNELS) { + printk(KERN_ERR "%s: bad channel %d\n", __FUNCTION__, ch_id); + return DMA_STATUS_BAD_CHANNEL; + } + + ctc = mfdcr(DCR_DMA2P40_CTC0 + (ch_id * 0x8)) &~ DMA_CTC_BTEN; + mtdcr(DCR_DMA2P40_CTC0 + (ch_id * 0x8), ctc); + return DMA_STATUS_GOOD; +} + + +/* + * Sets the burst size (number of peripheral widths) for the channel + * (BSIZ bits in the control/count register)) + * must be one of: + * DMA_CTC_BSIZ_2 + * DMA_CTC_BSIZ_4 + * DMA_CTC_BSIZ_8 + * DMA_CTC_BSIZ_16 + * Note: + * For scatter/gather dma, this function MUST be called before the + * ppc4xx_alloc_dma_handle() func as the chan count register is copied into the + * sgl list and used as each sgl element is added. + */ +int ppc460ex_set_burst_size(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id, + unsigned int bsize) +{ + unsigned int ctc; + if (ch_id >= MAX_PPC460EX_DMA_CHANNELS) { + printk(KERN_ERR "%s: bad channel %d\n", __FUNCTION__, ch_id); + return DMA_STATUS_BAD_CHANNEL; + } + + ctc = mfdcr(DCR_DMA2P40_CTC0 + (ch_id * 0x8)) &~ DMA_CTC_BSIZ_MSK; + ctc |= (bsize & DMA_CTC_BSIZ_MSK); + mtdcr(DCR_DMA2P40_CTC0 + (ch_id * 0x8), ctc); + return DMA_STATUS_GOOD; +} + +/* + * Returns the number of bytes left to be transferred. + * After a DMA transfer, this should return zero. + * Reading this while a DMA transfer is still in progress will return + * unpredictable results. + */ +int ppc460ex_get_dma_residue(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id) +{ + unsigned int count; + ppc460ex_plb_dma_ch_t *dma_chan = adev->chan[ch_id]; + + if (ch_id >= MAX_PPC460EX_DMA_CHANNELS) { + printk(KERN_ERR "%s: bad channel %d\n", __FUNCTION__, ch_id); + return DMA_STATUS_BAD_CHANNEL; + } + + count = mfdcr(DCR_DMA2P40_CTC0 + (ch_id * 0x8)); + count &= DMA_CTC_TC_MASK ; + + return (count << dma_chan->shift); + +} + + +/* + * Configures a DMA channel, including the peripheral bus width, if a + * peripheral is attached to the channel, the polarity of the DMAReq and + * DMAAck signals, etc. This information should really be setup by the boot + * code, since most likely the configuration won't change dynamically. + * If the kernel has to call this function, it's recommended that it's + * called from platform specific init code. The driver should not need to + * call this function. + */ +int ppc460ex_init_dma_channel(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id, + ppc460ex_plb_dma_ch_t *p_init) +{ + unsigned int polarity; + uint32_t control = 0; + ppc460ex_plb_dma_ch_t *dma_chan = adev->chan[ch_id]; + + + DMA_MODE_READ = (unsigned long) DMA_TD; /* Peripheral to Memory */ + DMA_MODE_WRITE = 0; /* Memory to Peripheral */ + + if (!p_init) { + printk("%s: NULL p_init\n", __FUNCTION__); + return DMA_STATUS_NULL_POINTER; + } + + if (ch_id >= MAX_PPC460EX_DMA_CHANNELS) { + printk(KERN_ERR "%s: bad channel %d\n", __FUNCTION__, ch_id); + return DMA_STATUS_BAD_CHANNEL; + } +#if DCR_DMA2P40_POL > 0 + polarity = mfdcr(DCR_DMA2P40_POL); +#else + polarity = 0; +#endif + + p_init->int_enable = 0; + p_init->buffer_enable = 1; + p_init->etd_output = 1; + p_init->tce_enable = 1; + p_init->pl = 0; + p_init->dai = 1; + p_init->sai = 1; + /* Duc Dang: make channel priority to 2, original is 3 */ + p_init->cp = 2; + p_init->pwidth = PW_8; + p_init->psc = 0; + p_init->pwc = 0; + p_init->phc = 0; + p_init->pf = 1; + + + /* Setup the control register based on the values passed to + * us in p_init. Then, over-write the control register with this + * new value. + */ +#if 0 + control |= SET_DMA_CONTROL; +#endif + control = SET_DMA_CONTROL; + /* clear all polarity signals and then "or" in new signal levels */ + +//PMB - Workaround + //control = 0x81A2CD80; + //control = 0x81A00180; + + + polarity &= ~GET_DMA_POLARITY(ch_id); + polarity |= p_init->polarity; + +#if DCR_DMA2P40_POL > 0 + mtdcr(DCR_DMA2P40_POL, polarity); +#endif + mtdcr(DCR_DMA2P40_CR0 + (ch_id * 0x8), control); + + /* save these values in our dma channel structure */ + //memcpy(dma_chan, p_init, sizeof(ppc460ex_plb_dma_ch_t)); + /* + * The peripheral width values written in the control register are: + * PW_8 0 + * PW_16 1 + * PW_32 2 + * PW_64 3 + * PW_128 4 + * + * Since the DMA count register takes the number of "transfers", + * we need to divide the count sent to us in certain + * functions by the appropriate number. It so happens that our + * right shift value is equal to the peripheral width value. + */ + dma_chan->shift = p_init->pwidth; + dma_chan->sai = p_init->sai; + dma_chan->dai = p_init->dai; + dma_chan->tce_enable = p_init->tce_enable; + dma_chan->mode = DMA_MODE_MM; + /* + * Save the control word for easy access. + */ + dma_chan->control = control; + mtdcr(DCR_DMA2P40_SR, 0xffffffff); + + + return DMA_STATUS_GOOD; +} + + +int ppc460ex_enable_dma(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id) +{ + unsigned int control; + ppc460ex_plb_dma_ch_t *dma_chan = adev->chan[ch_id]; + unsigned int status_bits[] = { DMA_CS0 | DMA_TS0 | DMA_CH0_ERR, + DMA_CS1 | DMA_TS1 | DMA_CH1_ERR}; + + if (dma_chan->in_use) { + printk("%s:enable_dma: channel %d in use\n", __FUNCTION__, ch_id); + return DMA_STATUS_CHANNEL_NOTFREE; + } + + if (ch_id >= MAX_PPC460EX_DMA_CHANNELS) { + printk(KERN_ERR "%s: bad channel %d\n", __FUNCTION__, ch_id); + return DMA_STATUS_BAD_CHANNEL; + } + +#if 0 + if (dma_chan->mode == DMA_MODE_READ) { + /* peripheral to memory */ + ppc460ex_set_src_addr(ch_id, 0); + ppc460ex_set_dst_addr(ch_id, dma_chan->addr); + } else if (dma_chan->mode == DMA_MODE_WRITE) { + /* memory to peripheral */ + ppc460ex_set_src_addr(ch_id, dma_chan->addr); + ppc460ex_set_dst_addr(ch_id, 0); + } +#endif + /* for other xfer modes, the addresses are already set */ + control = mfdcr(DCR_DMA2P40_CR0 + (ch_id * 0x8)); + control &= ~(DMA_TM_MASK | DMA_TD); /* clear all mode bits */ + if (dma_chan->mode == DMA_MODE_MM) { + /* software initiated memory to memory */ + control |= DMA_ETD_OUTPUT | DMA_TCE_ENABLE; + control |= DMA_MODE_MM; + if (dma_chan->dai) { + control |= DMA_DAI; + } + if (dma_chan->sai) { + control |= DMA_SAI; + } + } + + mtdcr(DCR_DMA2P40_CR0 + (ch_id * 0x8), control); + /* + * Clear the CS, TS, RI bits for the channel from DMASR. This + * has been observed to happen correctly only after the mode and + * ETD/DCE bits in DMACRx are set above. Must do this before + * enabling the channel. + */ + mtdcr(DCR_DMA2P40_SR, status_bits[ch_id]); + /* + * For device-paced transfers, Terminal Count Enable apparently + * must be on, and this must be turned on after the mode, etc. + * bits are cleared above (at least on Redwood-6). + */ + + if ((dma_chan->mode == DMA_MODE_MM_DEVATDST) || + (dma_chan->mode == DMA_MODE_MM_DEVATSRC)) + control |= DMA_TCE_ENABLE; + + /* + * Now enable the channel. + */ + + control |= (dma_chan->mode | DMA_CE_ENABLE); + control |= DMA_BEN; + //control = 0xc4effec0; + + mtdcr(DCR_DMA2P40_CR0 + (ch_id * 0x8), control); + dma_chan->in_use = 1; + return 0; + +} + + +void +ppc460ex_disable_dma(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id) +{ + unsigned int control; + ppc460ex_plb_dma_ch_t *dma_chan = adev->chan[ch_id]; + + if (!dma_chan->in_use) { + printk("disable_dma: channel %d not in use\n", ch_id); + return; + } + + if (ch_id >= MAX_PPC460EX_DMA_CHANNELS) { + printk("disable_dma: bad channel: %d\n", ch_id); + return; + } + + control = mfdcr(DCR_DMA2P40_CR0 + (ch_id * 0x8)); + control &= ~DMA_CE_ENABLE; + mtdcr(DCR_DMA2P40_CR0 + (ch_id * 0x8), control); + + dma_chan->in_use = 0; +} + + + + +/* + * Clears the channel status bits + */ +int ppc460ex_clear_dma_status(unsigned int ch_id) +{ + if (ch_id >= MAX_PPC460EX_DMA_CHANNELS) { + printk("KERN_ERR %s: bad channel %d\n", __FUNCTION__, ch_id); + return DMA_STATUS_BAD_CHANNEL; + } + + mtdcr(DCR_DMA2P40_SR, ((u32)DMA_CH0_ERR | (u32)DMA_CS0 | (u32)DMA_TS0) >> ch_id); + return DMA_STATUS_GOOD; + +} + + +/** + * ppc460ex_dma_eot_handler - end of transfer interrupt handler + */ +irqreturn_t ppc460ex_4chan_dma_eot_handler(int irq, void *data) +{ + unsigned int data_read = 0; + unsigned int try_cnt = 0; + + //printk("transfer complete\n"); + data_read = mfdcr(DCR_DMA2P40_SR); + //printk("%s: status 0x%08x\n", __FUNCTION__, data_read); + + do{ + //while bit 3 TC done is 0 + data_read = mfdcr(DCR_DMA2P40_SR); + if (data_read & 0x00800000 ) {printk("test FAIL\n"); } //see if error bit is set + }while(((data_read & 0x80000000) != 0x80000000) && ++try_cnt <= 10);// TC is now 0 + + data_read = mfdcr(DCR_DMA2P40_SR); + while (data_read & 0x00000800){ //while channel is busy + data_read = mfdcr(DCR_DMA2P40_SR); + printk("%s: status for busy 0x%08x\n", __FUNCTION__, data_read); + } + mtdcr(DCR_DMA2P40_SR, 0xffffffff); + + + + return IRQ_HANDLED; +} + + + +static struct of_device_id dma_per_chan_match[] = { + { + .compatible = "amcc,dma-4channel", + }, + {}, +}; + + + + +#if 0 +/*** test code ***/ +static int ppc460ex_dma_memcpy_self_test(ppc460ex_plb_dma_dev_t *device, unsigned int dma_ch_id) +{ + ppc460ex_plb_dma_ch_t p_init; + int res = 0, i; + unsigned int control; + phys_addr_t *src; + phys_addr_t *dest; + + phys_addr_t *gap; + + phys_addr_t dma_dest, dma_src; + + src = kzalloc(TEST_SIZE, GFP_KERNEL); + if (!src) + return -ENOMEM; + gap = kzalloc(200, GFP_KERNEL); + if (!gap) + return -ENOMEM; + + + + dest = kzalloc(TEST_SIZE, GFP_KERNEL); + if (!dest) { + kfree(src); + return -ENOMEM; + } + + printk("src = 0x%08x\n", (unsigned int)src); + printk("gap = 0x%08x\n", (unsigned int)gap); + printk("dest = 0x%08x\n", (unsigned int)dest); + + /* Fill in src buffer */ + for (i = 0; i < TEST_SIZE; i++) + ((u8*)src)[i] = (u8)i; + + printk("dump src\n"); + DMA_HEXDUMP(src, TEST_SIZE); + DMA_HEXDUMP(dest, TEST_SIZE); +#if 1 + dma_src = dma_map_single(p_init.device->dev, src, TEST_SIZE, + DMA_TO_DEVICE); + dma_dest = dma_map_single(p_init.device->dev, dest, TEST_SIZE, + DMA_FROM_DEVICE); +#endif + printk("%s:channel = %d chan 0x%08x\n", __FUNCTION__, device->chan[dma_ch_id]->chan_id, + (unsigned int)(device->chan)); + + p_init.polarity = 0; + p_init.pwidth = PW_32; + p_init.in_use = 0; + p_init.sai = 1; + p_init.dai = 1; + res = ppc460ex_init_dma_channel(device, dma_ch_id, &p_init); + + if (res) { + printk("%32s: init_dma_channel return %d\n", + __FUNCTION__, res); + } + ppc460ex_clear_dma_status(dma_ch_id); + + ppc460ex_set_src_addr(dma_ch_id, dma_src); + ppc460ex_set_dst_addr(dma_ch_id, dma_dest); + + ppc460ex_set_dma_mode(device, dma_ch_id, DMA_MODE_MM); + ppc460ex_set_dma_count(device, dma_ch_id, TEST_SIZE); + + res = ppc460ex_enable_dma_interrupt(device, dma_ch_id); + if (res) { + printk("%32s: en/disable_dma_interrupt\n", + __FUNCTION__); + } + + + if (dma_ch_id == 0) + control = mfdcr(DCR_DMA2P40_CR0); + else if (dma_ch_id == 1) + control = mfdcr(DCR_DMA2P40_CR1); + + + control &= ~(SET_DMA_BEN(1)); + control &= ~(SET_DMA_PSC(3)); + control &= ~(SET_DMA_PWC(0x3f)); + control &= ~(SET_DMA_PHC(0x7)); + control &= ~(SET_DMA_PL(1)); + + + + if (dma_ch_id == 0) + mtdcr(DCR_DMA2P40_CR0, control); + else if (dma_ch_id == 1) + mtdcr(DCR_DMA2P40_CR1, control); + + + ppc460ex_enable_dma(device, dma_ch_id); + + + if (memcmp(src, dest, TEST_SIZE)) { + printk("Self-test copy failed compare, disabling\n"); + res = -ENODEV; + goto out; + } + + + return 0; + + out: kfree(src); + kfree(dest); + return res; + +} + + + +static int test1(void) +{ + void *src, *dest; + void *src1, *dest1; + int i; + unsigned int chan; + + src = kzalloc(TEST_SIZE, GFP_KERNEL); + if (!src) + return -ENOMEM; + + dest = kzalloc(TEST_SIZE, GFP_KERNEL); + if (!dest) { + kfree(src); + return -ENOMEM; + } + + src1 = kzalloc(TEST_SIZE, GFP_KERNEL); + if (!src1) + return -ENOMEM; + + dest1 = kzalloc(TEST_SIZE, GFP_KERNEL); + if (!dest1) { + kfree(src1); + return -ENOMEM; + } + + /* Fill in src buffer */ + for (i = 0; i < TEST_SIZE; i++) + ((u8*)src)[i] = (u8)i; + + /* Fill in src buffer */ + for (i = 0; i < TEST_SIZE; i++) + ((u8*)src1)[i] = (u8)0xaa; + +#ifdef DEBUG_TEST + DMA_HEXDUMP(src, TEST_SIZE); + DMA_HEXDUMP(dest, TEST_SIZE); + DMA_HEXDUMP(src1, TEST_SIZE); + DMA_HEXDUMP(dest1, TEST_SIZE); +#endif + chan = ppc460ex_get_dma_channel(); + +#ifdef ENABLE_SGL + test_sgdma_memcpy(src, dest, src1, dest1, TEST_SIZE, chan); +#endif + test_dma_memcpy(src, dest, TEST_SIZE, chan); + + + out: kfree(src); + kfree(dest); + kfree(src1); + kfree(dest1); + + return 0; + +} +#endif + + + +/******************************************************************************* + * Module Initialization Routine + ******************************************************************************* + */ +int ppc460ex_dma_per_chan_probe(struct platform_device *ofdev) +{ + int ret=0; + //ppc460ex_plb_dma_dev_t *adev; + ppc460ex_plb_dma_ch_t *new_chan; + int err; + + + + adev = dev_get_drvdata(ofdev->dev.parent); + BUG_ON(!adev); + /* create a device */ + if ((new_chan = kzalloc(sizeof(*new_chan), GFP_KERNEL)) == NULL) { + printk("ERROR:No Free memory for allocating dma channels\n"); + ret = -ENOMEM; + goto err; + } + + err = of_address_to_resource(ofdev->dev.of_node,0,&new_chan->reg); + if (err) { + printk("ERROR:Can't get %s property reg\n", __FUNCTION__); + goto err; + } + new_chan->device = adev; + new_chan->reg_base = ioremap(new_chan->reg.start,new_chan->reg.end - new_chan->reg.start + 1); +#if 1 + printk("PPC460ex PLB DMA engine @0x%02X_%08X size %d\n", + (u32)(new_chan->reg.start >> 32), + (u32)new_chan->reg.start, + (u32)(new_chan->reg.end - new_chan->reg.start + 1)); +#endif + + switch(new_chan->reg.start) { + case 0x100: + new_chan->chan_id = 0; + break; + case 0x108: + new_chan->chan_id = 1; + break; + case 0x110: + new_chan->chan_id = 2; + break; + case 0x118: + new_chan->chan_id = 3; + break; + } + new_chan->chan_id = ((new_chan->reg.start - 0x100)& 0xfff) >> 3; + printk("new_chan->chan_id 0x%x\n",new_chan->chan_id); + adev->chan[new_chan->chan_id] = new_chan; + printk("new_chan->chan->chan_id 0x%x\n",adev->chan[new_chan->chan_id]->chan_id); + //adev->chan[new_chan->chan_id]->reg_base = new_chan->reg_base; + + return 0; + + err: + return ret; + +} + +int ppc460ex_dma_4chan_probe(struct platform_device *ofdev) +{ + int ret=0, irq = 0; + //ppc460ex_plb_dma_dev_t *adev; + ppc460ex_plb_dma_ch_t *chan = NULL; + struct device_node *np = ofdev->dev.of_node; + + /* create a device */ + if ((adev = kzalloc(sizeof(*adev), GFP_KERNEL)) == NULL) { + ret = -ENOMEM; + goto err_adev_alloc; + } + adev->dev = &ofdev->dev; +#if !defined(CONFIG_APM821xx) + err = of_address_to_resource(np,0,&adev->reg); + if(err) { + printk(KERN_ERR"Can't get %s property 'reg'\n",ofdev->node->full_name); + } +#endif + printk(KERN_INFO"Probing AMCC DMA driver\n"); +#if !defined(CONFIG_APM821xx) + adev->reg_base = ioremap(adev->reg.start, adev->reg.end - adev->reg.start + 1); +#endif + +#if 1 + irq = of_irq_to_resource(np, 0, NULL); + if (irq >= 0) { + ret = request_irq(irq, ppc460ex_4chan_dma_eot_handler, + IRQF_DISABLED, "Peripheral DMA0-1", chan); + if (ret) { + ret = -EIO; + goto err_irq; + } + //irq = platform_get_irq(adev, 0); + /* only DMA engines have a separate err IRQ + * so it's Ok if irq < 0 in XOR case + */ + } else + ret = -ENXIO; + +#if !defined(CONFIG_APM821xx) + printk("PPC4xx PLB DMA engine @0x%02X_%08X size %d IRQ %d \n", + (u32)(adev->reg.start >> 32), + (u32)adev->reg.start, + (u32)(adev->reg.end - adev->reg.start + 1), + irq); +#else + printk("PPC4xx PLB DMA engine IRQ %d\n", irq); +#endif +#endif + dev_set_drvdata(&(ofdev->dev),adev); + of_platform_bus_probe(np,dma_per_chan_match,&ofdev->dev); + + + //ppc460ex_dma_memcpy_self_test(adev, 0); + //test1(); + + + return 0; + + +err_adev_alloc: + //release_mem_region(adev->reg.start, adev->reg.end - adev->reg.start); +err_irq: + kfree(chan); + + return ret; +} + + +static struct of_device_id dma_4chan_match[] = { + { + .compatible = "amcc,dma", + }, + {}, +}; + +struct platform_driver ppc460ex_dma_4chan_driver = { + .driver = { + .name = "plb_dma", + .owner = THIS_MODULE, + .of_match_table = dma_4chan_match, + }, + .probe = ppc460ex_dma_4chan_probe, +}; + +struct platform_driver ppc460ex_dma_per_chan_driver = { + .driver = { + .name = "dma-4channel", + .owner = THIS_MODULE, + .of_match_table = dma_per_chan_match, + }, + .probe = ppc460ex_dma_per_chan_probe, +}; + + +static int __init mod_init (void) +{ + printk("%s:%d\n", __FUNCTION__, __LINE__); + return platform_driver_register(&ppc460ex_dma_4chan_driver); + printk("here 2\n"); +} + +static void __exit mod_exit(void) +{ + platform_driver_unregister(&ppc460ex_dma_4chan_driver); +} + +static int __init ppc460ex_dma_per_chan_init (void) +{ + printk("%s:%d\n", __FUNCTION__, __LINE__); + return platform_driver_register(&ppc460ex_dma_per_chan_driver); + printk("here 3\n"); +} + +static void __exit ppc460ex_dma_per_chan_exit(void) +{ + platform_driver_unregister(&ppc460ex_dma_per_chan_driver); +} + +subsys_initcall(ppc460ex_dma_per_chan_init); +subsys_initcall(mod_init); + +//module_exit(mod_exit); + +//module_exit(ppc460ex_dma_per_chan_exit); + +MODULE_DESCRIPTION("AMCC PPC460EX 4 channel Engine Driver"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL_GPL(ppc460ex_get_dma_status); +EXPORT_SYMBOL_GPL(ppc460ex_set_src_addr); +EXPORT_SYMBOL_GPL(ppc460ex_set_dst_addr); +EXPORT_SYMBOL_GPL(ppc460ex_set_dma_mode); +EXPORT_SYMBOL_GPL(ppc460ex_set_dma_count); +EXPORT_SYMBOL_GPL(ppc460ex_enable_dma_interrupt); +EXPORT_SYMBOL_GPL(ppc460ex_init_dma_channel); +EXPORT_SYMBOL_GPL(ppc460ex_enable_dma); +EXPORT_SYMBOL_GPL(ppc460ex_disable_dma); +EXPORT_SYMBOL_GPL(ppc460ex_clear_dma_status); +EXPORT_SYMBOL_GPL(ppc460ex_get_dma_residue); +EXPORT_SYMBOL_GPL(ppc460ex_disable_dma_interrupt); +EXPORT_SYMBOL_GPL(ppc460ex_get_channel_config); +EXPORT_SYMBOL_GPL(ppc460ex_set_channel_priority); +EXPORT_SYMBOL_GPL(ppc460ex_get_peripheral_width); +EXPORT_SYMBOL_GPL(ppc460ex_enable_burst); +EXPORT_SYMBOL_GPL(ppc460ex_disable_burst); +EXPORT_SYMBOL_GPL(ppc460ex_set_burst_size); + +/************************************************************************/ diff --git a/drivers/dma/ppc4xx/ppc460ex_4chan_dma.h b/drivers/dma/ppc4xx/ppc460ex_4chan_dma.h new file mode 100644 index 00000000000..c9448f34de4 --- /dev/null +++ b/drivers/dma/ppc4xx/ppc460ex_4chan_dma.h @@ -0,0 +1,531 @@ + + +#include <linux/types.h> + + + + +#define DMA_HEXDUMP(b, l) \ + print_hex_dump(KERN_CONT, "", DUMP_PREFIX_OFFSET, \ + 16, 1, (b), (l), false); + + +#define MAX_PPC460EX_DMA_CHANNELS 4 + + +#define DCR_DMA0_BASE 0x200 +#define DCR_DMA1_BASE 0x208 +#define DCR_DMA2_BASE 0x210 +#define DCR_DMA3_BASE 0x218 +#define DCR_DMASR_BASE 0x220 + + + + + + +/* DMA Registers */ +#define DCR_DMA2P40_CR0 (DCR_DMA0_BASE + 0x0) /* DMA Channel Control 0 */ +#define DCR_DMA2P40_CTC0 (DCR_DMA0_BASE + 0x1) /* DMA Count 0 */ +#define DCR_DMA2P40_SAH0 (DCR_DMA0_BASE + 0x2) /* DMA Src Addr High 0 */ +#define DCR_DMA2P40_SAL0 (DCR_DMA0_BASE + 0x3) /* DMA Src Addr Low 0 */ +#define DCR_DMA2P40_DAH0 (DCR_DMA0_BASE + 0x4) /* DMA Dest Addr High 0 */ +#define DCR_DMA2P40_DAL0 (DCR_DMA0_BASE + 0x5) /* DMA Dest Addr Low 0 */ +#define DCR_DMA2P40_SGH0 (DCR_DMA0_BASE + 0x6) /* DMA SG Desc Addr High 0 */ +#define DCR_DMA2P40_SGL0 (DCR_DMA0_BASE + 0x7) /* DMA SG Desc Addr Low 0 */ + +#define DCR_DMA2P40_CR1 (DCR_DMA1_BASE + 0x0) /* DMA Channel Control 1 */ +#define DCR_DMA2P40_CTC1 (DCR_DMA1_BASE + 0x1) /* DMA Count 1 */ +#define DCR_DMA2P40_SAH1 (DCR_DMA1_BASE + 0x2) /* DMA Src Addr High 1 */ +#define DCR_DMA2P40_SAL1 (DCR_DMA1_BASE + 0x3) /* DMA Src Addr Low 1 */ +#define DCR_DMA2P40_DAH1 (DCR_DMA1_BASE + 0x4) /* DMA Dest Addr High 1 */ +#define DCR_DMA2P40_DAL1 (DCR_DMA1_BASE + 0x5) /* DMA Dest Addr Low 1 */ +#define DCR_DMA2P40_SGH1 (DCR_DMA1_BASE + 0x6) /* DMA SG Desc Addr High 1 */ +#define DCR_DMA2P40_SGL1 (DCR_DMA1_BASE + 0x7) /* DMA SG Desc Addr Low 1 */ + +#define DCR_DMA2P40_CR2 (DCR_DMA2_BASE + 0x0) /* DMA Channel Control 2 */ +#define DCR_DMA2P40_CTC2 (DCR_DMA2_BASE + 0x1) /* DMA Count 2 */ +#define DCR_DMA2P40_SAH2 (DCR_DMA2_BASE + 0x2) /* DMA Src Addr High 2 */ +#define DCR_DMA2P40_SAL2 (DCR_DMA2_BASE + 0x3) /* DMA Src Addr Low 2 */ +#define DCR_DMA2P40_DAH2 (DCR_DMA2_BASE + 0x4) /* DMA Dest Addr High 2 */ +#define DCR_DMA2P40_DAL2 (DCR_DMA2_BASE + 0x5) /* DMA Dest Addr Low 2 */ +#define DCR_DMA2P40_SGH2 (DCR_DMA2_BASE + 0x6) /* DMA SG Desc Addr High 2 */ +#define DCR_DMA2P40_SGL2 (DCR_DMA2_BASE + 0x7) /* DMA SG Desc Addr Low 2 */ + +#define DCR_DMA2P40_CR3 (DCR_DMA3_BASE + 0x0) /* DMA Channel Control 3 */ +#define DCR_DMA2P40_CTC3 (DCR_DMA3_BASE + 0x1) /* DMA Count 3 */ +#define DCR_DMA2P40_SAH3 (DCR_DMA3_BASE + 0x2) /* DMA Src Addr High 3 */ +#define DCR_DMA2P40_SAL3 (DCR_DMA3_BASE + 0x3) /* DMA Src Addr Low 3 */ +#define DCR_DMA2P40_DAH3 (DCR_DMA3_BASE + 0x4) /* DMA Dest Addr High 3 */ +#define DCR_DMA2P40_DAL3 (DCR_DMA3_BASE + 0x5) /* DMA Dest Addr Low 3 */ +#define DCR_DMA2P40_SGH3 (DCR_DMA3_BASE + 0x6) /* DMA SG Desc Addr High 3 */ +#define DCR_DMA2P40_SGL3 (DCR_DMA3_BASE + 0x7) /* DMA SG Desc Addr Low 3 */ + +#define DCR_DMA2P40_SR (DCR_DMASR_BASE + 0x0) /* DMA Status Register */ +#define DCR_DMA2P40_SGC (DCR_DMASR_BASE + 0x3) /* DMA Scatter/Gather Command */ +#define DCR_DMA2P40_SLP (DCR_DMASR_BASE + 0x5) /* DMA Sleep Register */ +#define DCR_DMA2P40_POL (DCR_DMASR_BASE + 0x6) /* DMA Polarity Register */ + + + +/* + * Function return status codes + * These values are used to indicate whether or not the function + * call was successful, or a bad/invalid parameter was passed. + */ +#define DMA_STATUS_GOOD 0 +#define DMA_STATUS_BAD_CHANNEL 1 +#define DMA_STATUS_BAD_HANDLE 2 +#define DMA_STATUS_BAD_MODE 3 +#define DMA_STATUS_NULL_POINTER 4 +#define DMA_STATUS_OUT_OF_MEMORY 5 +#define DMA_STATUS_SGL_LIST_EMPTY 6 +#define DMA_STATUS_GENERAL_ERROR 7 +#define DMA_STATUS_CHANNEL_NOTFREE 8 + +#define DMA_CHANNEL_BUSY 0x80000000 + +/* + * These indicate status as returned from the DMA Status Register. + */ +#define DMA_STATUS_NO_ERROR 0 +#define DMA_STATUS_CS 1 /* Count Status */ +#define DMA_STATUS_TS 2 /* Transfer Status */ +#define DMA_STATUS_DMA_ERROR 3 /* DMA Error Occurred */ +#define DMA_STATUS_DMA_BUSY 4 /* The channel is busy */ + +/* + * DMA Channel Control Registers + */ +#ifdef CONFIG_44x +#define PPC4xx_DMA_64BIT +#define DMA_CR_OFFSET 1 +#else +#define DMA_CR_OFFSET 0 +#endif + +#define DMA_CE_ENABLE (1<<31) /* DMA Channel Enable */ +#define SET_DMA_CE_ENABLE(x) (((x)&0x1)<<31) +#define GET_DMA_CE_ENABLE(x) (((x)&DMA_CE_ENABLE)>>31) + +#define DMA_CIE_ENABLE (1<<30) /* DMA Channel Interrupt Enable */ +#define SET_DMA_CIE_ENABLE(x) (((x)&0x1)<<30) +#define GET_DMA_CIE_ENABLE(x) (((x)&DMA_CIE_ENABLE)>>30) + +#define DMA_TD (1<<29) +#define SET_DMA_TD(x) (((x)&0x1)<<29) +#define GET_DMA_TD(x) (((x)&DMA_TD)>>29) + +#define DMA_PL (1<<28) /* Peripheral Location */ +#define SET_DMA_PL(x) (((x)&0x1)<<28) +#define GET_DMA_PL(x) (((x)&DMA_PL)>>28) + +#define EXTERNAL_PERIPHERAL 0 +#define INTERNAL_PERIPHERAL 1 + +#define SET_DMA_PW(x) (((x)&0x7)<<(26-DMA_CR_OFFSET)) /* Peripheral Width */ +#define DMA_PW_MASK SET_DMA_PW(7) +#define PW_8 0 +#define PW_16 1 +#define PW_32 2 +#define PW_64 3 +#define PW_128 4 + + +#define GET_DMA_PW(x) (((x)&DMA_PW_MASK)>>(26-DMA_CR_OFFSET)) + +#define DMA_DAI (1<<(25-DMA_CR_OFFSET)) /* Destination Address Increment */ +#define SET_DMA_DAI(x) (((x)&0x1)<<(25-DMA_CR_OFFSET)) + +#define DMA_SAI (1<<(24-DMA_CR_OFFSET)) /* Source Address Increment */ +#define SET_DMA_SAI(x) (((x)&0x1)<<(24-DMA_CR_OFFSET)) + +#define DMA_BEN (1<<(23-DMA_CR_OFFSET)) /* Buffer Enable */ +#define SET_DMA_BEN(x) (((x)&0x1)<<(23-DMA_CR_OFFSET)) + +#define SET_DMA_TM(x) (((x)&0x3)<<(21-DMA_CR_OFFSET)) /* Transfer Mode */ +#define DMA_TM_MASK SET_DMA_TM(3) +#define TM_PERIPHERAL 0 /* Peripheral */ +#define TM_RESERVED 1 /* Reserved */ +#define TM_S_MM 2 /* Memory to Memory */ +#define TM_D_MM 3 /* Device Paced Memory to Memory */ +#define GET_DMA_TM(x) (((x)&DMA_TM_MASK)>>(21-DMA_CR_OFFSET)) + +#define SET_DMA_PSC(x) (((x)&0x3)<<(19-DMA_CR_OFFSET)) /* Peripheral Setup Cycles */ +#define DMA_PSC_MASK SET_DMA_PSC(3) +#define GET_DMA_PSC(x) (((x)&DMA_PSC_MASK)>>(19-DMA_CR_OFFSET)) + +#define SET_DMA_PWC(x) (((x)&0x3F)<<(13-DMA_CR_OFFSET)) /* Peripheral Wait Cycles */ +#define DMA_PWC_MASK SET_DMA_PWC(0x3F) +#define GET_DMA_PWC(x) (((x)&DMA_PWC_MASK)>>(13-DMA_CR_OFFSET)) + +#define SET_DMA_PHC(x) (((x)&0x7)<<(10-DMA_CR_OFFSET)) /* Peripheral Hold Cycles */ +#define DMA_PHC_MASK SET_DMA_PHC(0x7) +#define GET_DMA_PHC(x) (((x)&DMA_PHC_MASK)>>(10-DMA_CR_OFFSET)) + +#define DMA_ETD_OUTPUT (1<<(9-DMA_CR_OFFSET)) /* EOT pin is a TC output */ +#define SET_DMA_ETD(x) (((x)&0x1)<<(9-DMA_CR_OFFSET)) + +#define DMA_TCE_ENABLE (1<<(8-DMA_CR_OFFSET)) +#define SET_DMA_TCE(x) (((x)&0x1)<<(8-DMA_CR_OFFSET)) + +#define DMA_DEC (1<<(2)) /* Address Decrement */ +#define SET_DMA_DEC(x) (((x)&0x1)<<2) +#define GET_DMA_DEC(x) (((x)&DMA_DEC)>>2) + + +/* + * Transfer Modes + * These modes are defined in a way that makes it possible to + * simply "or" in the value in the control register. + */ + +#define DMA_MODE_MM (SET_DMA_TM(TM_S_MM)) /* memory to memory */ + + /* Device-paced memory to memory, */ + /* device is at source address */ +#define DMA_MODE_MM_DEVATSRC (DMA_TD | SET_DMA_TM(TM_D_MM)) + + /* Device-paced memory to memory, */ + /* device is at destination address */ +#define DMA_MODE_MM_DEVATDST (SET_DMA_TM(TM_D_MM)) + +#define SGL_LIST_SIZE 16384 +#define DMA_PPC4xx_SIZE SGL_LIST_SIZE + +#define SET_DMA_PRIORITY(x) (((x)&0x3)<<(6-DMA_CR_OFFSET)) /* DMA Channel Priority */ +#define DMA_PRIORITY_MASK SET_DMA_PRIORITY(3) +#define PRIORITY_LOW 0 +#define PRIORITY_MID_LOW 1 +#define PRIORITY_MID_HIGH 2 +#define PRIORITY_HIGH 3 +#define GET_DMA_PRIORITY(x) (((x)&DMA_PRIORITY_MASK)>>(6-DMA_CR_OFFSET)) + + +#define SET_DMA_PREFETCH(x) (((x)&0x3)<<(4-DMA_CR_OFFSET)) /* Memory Read Prefetch */ +#define DMA_PREFETCH_MASK SET_DMA_PREFETCH(3) +#define PREFETCH_1 0 /* Prefetch 1 Double Word */ +#define PREFETCH_2 1 +#define PREFETCH_4 2 +#define GET_DMA_PREFETCH(x) (((x)&DMA_PREFETCH_MASK)>>(4-DMA_CR_OFFSET)) + +#define DMA_PCE (1<<(3-DMA_CR_OFFSET)) /* Parity Check Enable */ +#define SET_DMA_PCE(x) (((x)&0x1)<<(3-DMA_CR_OFFSET)) +#define GET_DMA_PCE(x) (((x)&DMA_PCE)>>(3-DMA_CR_OFFSET)) + +/* + * DMA Polarity Configuration Register + */ +#define DMAReq_ActiveLow(chan) (1<<(31-(chan*3))) +#define DMAAck_ActiveLow(chan) (1<<(30-(chan*3))) +#define EOT_ActiveLow(chan) (1<<(29-(chan*3))) /* End of Transfer */ + +/* + * DMA Sleep Mode Register + */ +#define SLEEP_MODE_ENABLE (1<<21) + +/* + * DMA Status Register + */ +#define DMA_CS0 (1<<31) /* Terminal Count has been reached */ +#define DMA_CS1 (1<<30) +#define DMA_CS2 (1<<29) +#define DMA_CS3 (1<<28) + +#define DMA_TS0 (1<<27) /* End of Transfer has been requested */ +#define DMA_TS1 (1<<26) +#define DMA_TS2 (1<<25) +#define DMA_TS3 (1<<24) + +#define DMA_CH0_ERR (1<<23) /* DMA Chanel 0 Error */ +#define DMA_CH1_ERR (1<<22) +#define DMA_CH2_ERR (1<<21) +#define DMA_CH3_ERR (1<<20) + +#define DMA_IN_DMA_REQ0 (1<<19) /* Internal DMA Request is pending */ +#define DMA_IN_DMA_REQ1 (1<<18) +#define DMA_IN_DMA_REQ2 (1<<17) +#define DMA_IN_DMA_REQ3 (1<<16) + +#define DMA_EXT_DMA_REQ0 (1<<15) /* External DMA Request is pending */ +#define DMA_EXT_DMA_REQ1 (1<<14) +#define DMA_EXT_DMA_REQ2 (1<<13) +#define DMA_EXT_DMA_REQ3 (1<<12) + +#define DMA_CH0_BUSY (1<<11) /* DMA Channel 0 Busy */ +#define DMA_CH1_BUSY (1<<10) +#define DMA_CH2_BUSY (1<<9) +#define DMA_CH3_BUSY (1<<8) + +#define DMA_SG0 (1<<7) /* DMA Channel 0 Scatter/Gather in progress */ +#define DMA_SG1 (1<<6) +#define DMA_SG2 (1<<5) +#define DMA_SG3 (1<<4) + +/* DMA Channel Count Register */ +#define DMA_CTC_TCIE (1<<29) /* Terminal Count Interrupt Enable */ +#define DMA_CTC_ETIE (1<<28) /* EOT Interupt Enable */ +#define DMA_CTC_EIE (1<<27) /* Error Interrupt Enable */ +#define DMA_CTC_BTEN (1<<23) /* Burst Enable/Disable bit */ +#define DMA_CTC_BSIZ_MSK (3<<21) /* Mask of the Burst size bits */ +#define DMA_CTC_BSIZ_2 (0) +#define DMA_CTC_BSIZ_4 (1<<21) +#define DMA_CTC_BSIZ_8 (2<<21) +#define DMA_CTC_BSIZ_16 (3<<21) +#define DMA_CTC_TC_MASK 0xFFFFF + +/* + * DMA SG Command Register + */ +#define SSG_ENABLE(chan) (1<<(31-chan)) /* Start Scatter Gather */ +#define SSG_MASK_ENABLE(chan) (1<<(15-chan)) /* Enable writing to SSG0 bit */ + + +/* + * DMA Scatter/Gather Descriptor Bit fields + */ +#define SG_LINK (1<<31) /* Link */ +#define SG_TCI_ENABLE (1<<29) /* Enable Terminal Count Interrupt */ +#define SG_ETI_ENABLE (1<<28) /* Enable End of Transfer Interrupt */ +#define SG_ERI_ENABLE (1<<27) /* Enable Error Interrupt */ +#define SG_COUNT_MASK 0xFFFF /* Count Field */ + +#define SET_DMA_CONTROL \ + (SET_DMA_CIE_ENABLE(p_init->int_enable) | /* interrupt enable */ \ + SET_DMA_BEN(p_init->buffer_enable) | /* buffer enable */\ + SET_DMA_ETD(p_init->etd_output) | /* end of transfer pin */ \ + SET_DMA_TCE(p_init->tce_enable) | /* terminal count enable */ \ + SET_DMA_PL(p_init->pl) | /* peripheral location */ \ + SET_DMA_DAI(p_init->dai) | /* dest addr increment */ \ + SET_DMA_SAI(p_init->sai) | /* src addr increment */ \ + SET_DMA_PRIORITY(p_init->cp) | /* channel priority */ \ + SET_DMA_PW(p_init->pwidth) | /* peripheral/bus width */ \ + SET_DMA_PSC(p_init->psc) | /* peripheral setup cycles */ \ + SET_DMA_PWC(p_init->pwc) | /* peripheral wait cycles */ \ + SET_DMA_PHC(p_init->phc) | /* peripheral hold cycles */ \ + SET_DMA_PREFETCH(p_init->pf) /* read prefetch */) + +#define GET_DMA_POLARITY(chan) (DMAReq_ActiveLow(chan) | DMAAck_ActiveLow(chan) | EOT_ActiveLow(chan)) + + +/** + * struct ppc460ex_dma_device - internal representation of an DMA device + * @pdev: Platform device + * @id: HW DMA Device selector + * @dma_desc_pool: base of DMA descriptor region (DMA address) + * @dma_desc_pool_virt: base of DMA descriptor region (CPU address) + * @common: embedded struct dma_device + */ +typedef struct ppc460ex_plb_dma_device { + //struct platform_device *pdev; + void __iomem *reg_base; + struct device *dev; + struct resource reg; /* Resource for register */ + int id; + struct ppc460ex_plb_dma_chan *chan[MAX_PPC460EX_DMA_CHANNELS]; + wait_queue_head_t queue; +} ppc460ex_plb_dma_dev_t; + +typedef uint32_t sgl_handle_t; +/** + * struct ppc460ex_dma_chan - internal representation of an ADMA channel + * @lock: serializes enqueue/dequeue operations to the slot pool + * @device: parent device + * @chain: device chain view of the descriptors + * @common: common dmaengine channel object members + * @all_slots: complete domain of slots usable by the channel + * @reg: Resource for register + * @pending: allows batching of hardware operations + * @completed_cookie: identifier for the most recently completed operation + * @slots_allocated: records the actual size of the descriptor slot pool + * @hw_chain_inited: h/w descriptor chain initialization flag + * @irq_tasklet: bottom half where ppc460ex_adma_slot_cleanup runs + * @needs_unmap: if buffers should not be unmapped upon final processing + */ +typedef struct ppc460ex_plb_dma_chan { + void __iomem *reg_base; + struct ppc460ex_plb_dma_device *device; + struct timer_list cleanup_watchdog; + struct resource reg; /* Resource for register */ + unsigned int chan_id; + struct tasklet_struct irq_tasklet; + sgl_handle_t *phandle; + unsigned short in_use; /* set when channel is being used, clr when + * available. + */ + /* + * Valid polarity settings: + * DMAReq_ActiveLow(n) + * DMAAck_ActiveLow(n) + * EOT_ActiveLow(n) + * + * n is 0 to max dma chans + */ + unsigned int polarity; + + char buffer_enable; /* Boolean: buffer enable */ + char tce_enable; /* Boolean: terminal count enable */ + char etd_output; /* Boolean: eot pin is a tc output */ + char pce; /* Boolean: parity check enable */ + + /* + * Peripheral location: + * INTERNAL_PERIPHERAL (UART0 on the 405GP) + * EXTERNAL_PERIPHERAL + */ + char pl; /* internal/external peripheral */ + + /* + * Valid pwidth settings: + * PW_8 + * PW_16 + * PW_32 + * PW_64 + */ + unsigned int pwidth; + + char dai; /* Boolean: dst address increment */ + char sai; /* Boolean: src address increment */ + + /* + * Valid psc settings: 0-3 + */ + unsigned int psc; /* Peripheral Setup Cycles */ + + /* + * Valid pwc settings: + * 0-63 + */ + unsigned int pwc; /* Peripheral Wait Cycles */ + + /* + * Valid phc settings: + * 0-7 + */ + unsigned int phc; /* Peripheral Hold Cycles */ + + /* + * Valid cp (channel priority) settings: + * PRIORITY_LOW + * PRIORITY_MID_LOW + * PRIORITY_MID_HIGH + * PRIORITY_HIGH + */ + unsigned int cp; /* channel priority */ + + /* + * Valid pf (memory read prefetch) settings: + * + * PREFETCH_1 + * PREFETCH_2 + * PREFETCH_4 + */ + unsigned int pf; /* memory read prefetch */ + + /* + * Boolean: channel interrupt enable + * NOTE: for sgl transfers, only the last descriptor will be setup to + * interrupt. + */ + char int_enable; + + char shift; /* easy access to byte_count shift, based on */ + /* the width of the channel */ + + uint32_t control; /* channel control word */ + + /* These variabled are used ONLY in single dma transfers */ + unsigned int mode; /* transfer mode */ + phys_addr_t addr; + char ce; /* channel enable */ + char int_on_final_sg;/* for scatter/gather - only interrupt on last sg */ + +} ppc460ex_plb_dma_ch_t; + +/* + * PPC44x DMA implementations have a slightly different + * descriptor layout. Probably moved about due to the + * change to 64-bit addresses and link pointer. I don't + * know why they didn't just leave control_count after + * the dst_addr. + */ +#ifdef PPC4xx_DMA_64BIT +typedef struct { + uint32_t control; + uint32_t control_count; + phys_addr_t src_addr; + phys_addr_t dst_addr; + phys_addr_t next; +} ppc_sgl_t; +#else +typedef struct { + uint32_t control; + phys_addr_t src_addr; + phys_addr_t dst_addr; + uint32_t control_count; + uint32_t next; +} ppc_sgl_t; +#endif + + + +typedef struct { + unsigned int ch_id; + uint32_t control; /* channel ctrl word; loaded from each descrptr */ + uint32_t sgl_control; /* LK, TCI, ETI, and ERI bits in sgl descriptor */ + dma_addr_t dma_addr; /* dma (physical) address of this list */ + dma_addr_t dummy; /*Dummy variable to allow quad word alignment*/ + ppc_sgl_t *phead; + dma_addr_t phead_dma; + ppc_sgl_t *ptail; + dma_addr_t ptail_dma; +} sgl_list_info_t; + +typedef struct { + phys_addr_t *src_addr; + phys_addr_t *dst_addr; + phys_addr_t dma_src_addr; + phys_addr_t dma_dst_addr; +} pci_alloc_desc_t; + +#define PPC460EX_DMA_SGXFR_COMPLETE(id) (!((1 << (11-id)) & mfdcr(DCR_DMA2P40_SR))) +#define PPC460EX_DMA_CHAN_BUSY(id) ( (1 << (11-id)) & mfdcr(DCR_DMA2P40_SR) ) +#define DMA_STATUS(id) (mfdcr(DCR_DMA2P40_SR)) +#define CLEAR_DMA_STATUS(id) (mtdcr(DCR_DMA2P40_SR, 0xFFFFFFFF)) +#define PPC460EX_DMA_SGSTAT_FREE(id) (!((1 << (7-id)) & mfdcr(DCR_DMA2P40_SR)) ) +#define PPC460EX_DMA_TC_REACHED(id) ( (1 << (31-id)) & mfdcr(DCR_DMA2P40_SR) ) +#define PPC460EX_DMA_CHAN_XFR_COMPLETE(id) ( (!PPC460EX_DMA_CHAN_BUSY(id)) && (PPC460EX_DMA_TC_REACHED(id)) ) +#define PPC460EX_DMA_CHAN_SGXFR_COMPLETE(id) ( (!PPC460EX_DMA_CHAN_BUSY(id)) && PPC460EX_DMA_SGSTAT_FREE(id) ) +#define PPC460EX_DMA_SG_IN_PROGRESS(id) ( (1 << (7-id)) | (1 << (11-id)) ) +#define PPC460EX_DMA_SG_OP_COMPLETE(id) ( (PPC460EX_DMA_SG_IN_PROGRESS(id) & DMA_STATUS(id) ) == 0) + +extern ppc460ex_plb_dma_dev_t *adev; +int ppc460ex_init_dma_channel(ppc460ex_plb_dma_dev_t *adev, + unsigned int ch_id, + ppc460ex_plb_dma_ch_t *p_init); + +int ppc460ex_set_src_addr(int ch_id, phys_addr_t src_addr); + +int ppc460ex_set_dst_addr(int ch_id, phys_addr_t dst_addr); + +int ppc460ex_set_dma_mode(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id, unsigned int mode); + +void ppc460ex_set_dma_count(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id, unsigned int count); + +int ppc460ex_enable_dma_interrupt(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id); + +int ppc460ex_enable_dma(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id); + +int ppc460ex_get_dma_channel(void); + +void ppc460ex_disable_dma(ppc460ex_plb_dma_dev_t *adev, unsigned int ch_id); + +int ppc460ex_clear_dma_status(unsigned int ch_id); + +#if 0 +extern int test_dma_memcpy(void *src, void *dst, unsigned int length, unsigned int dma_ch); + +extern int test_sgdma_memcpy(void *src, void *dst, void *src1, void *dst1, + unsigned int length, unsigned int dma_ch); +#endif |