From cd07e01d0c6c207a6138e9d5229dfa8c88ab3879 Mon Sep 17 00:00:00 2001 From: Alison Wang Date: Thu, 4 Aug 2011 09:59:40 +0800 Subject: [PATCH 07/52] Add eDMA support for MCF5445x Add MCF5445x on-chip eDMA controller driver. Signed-off-by: Alison Wang --- arch/m68k/include/asm/mcf_edma.h | 246 +++++++++ drivers/Makefile | 2 + drivers/dma/Kconfig | 15 + drivers/dma/Makefile | 4 + drivers/dma/mcf_edma.c | 1029 ++++++++++++++++++++++++++++++++++++++ drivers/dma/mcf_edma_test.c | 276 ++++++++++ 6 files changed, 1572 insertions(+), 0 deletions(-) create mode 100644 arch/m68k/include/asm/mcf_edma.h create mode 100644 drivers/dma/mcf_edma.c create mode 100644 drivers/dma/mcf_edma_test.c --- /dev/null +++ b/arch/m68k/include/asm/mcf_edma.h @@ -0,0 +1,246 @@ +/* + * mcf_edma.h - Coldfire eDMA driver header file. + * + * Copyright (C) 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + * + * Add support for m5441x platform (Lanttor.Guo@freescale.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. + */ + +#ifndef _MCF_EDMA_H +#define _MCF_EDMA_H + +#include +#include +#if defined(CONFIG_M5445X) +#include +#elif defined(CONFIG_M5441X) +#include +#endif +#include + +#define MCF_EDMA_INT0_CHANNEL_BASE (8) +#define MCF_EDMA_INT0_CONTROLLER_BASE (64) +#define MCF_EDMA_INT0_BASE (MCF_EDMA_INT0_CHANNEL_BASE + \ + MCF_EDMA_INT0_CONTROLLER_BASE) +#define MCF_EDMA_INT0_NUM (16) +#define MCF_EDMA_INT0_END (MCF_EDMA_INT0_NUM) + +#if defined(CONFIG_M5441X) +#define MCF_EDMA_INT1_CHANNEL_BASE (8) +#define MCF_EDMA_INT1_CONTROLLER_BASE (128) +#define MCF_EDMA_INT1_BASE (MCF_EDMA_INT1_CHANNEL_BASE + \ + MCF_EDMA_INT1_CONTROLLER_BASE) +#define MCF_EDMA_INT1_NUM (40) +#define MCF_EDMA_INT1_END (MCF_EDMA_INT0_END + MCF_EDMA_INT1_NUM) + +#define MCF_EDMA_INT2_CHANNEL_BASE (0) +#define MCF_EDMA_INT2_CONTROLLER_BASE (192) +#define MCF_EDMA_INT2_BASE (MCF_EDMA_INT2_CHANNEL_BASE + \ + MCF_EDMA_INT2_CONTROLLER_BASE) +#define MCF_EDMA_INT2_NUM (8) +#define MCF_EDMA_INT2_END (MCF_EDMA_INT1_END + MCF_EDMA_INT2_NUM) + +#endif + +#if defined(CONFIG_M5445X) +#define MCF_EDMA_CHANNELS (16) /* 0-15 */ +#elif defined(CONFIG_M5441X) +#define MCF_EDMA_CHANNELS (64) /* 0-63 */ +#endif + +#define MCF_EDMA_CHANNEL_ANY (0xFF) +#define MCF_EDMA_INT_ERR (16) /* edma error interrupt */ + +#define MCF_EDMA_TCD_PER_CHAN 256 + +#ifdef CONFIG_M54455 +/* eDMA engine TCD memory description */ + +struct TCD { + u32 saddr; + u16 attr; + u16 soff; + u32 nbytes; + u32 slast; + u32 daddr; + u16 citer; + u16 doff; + u32 dlast_sga; + u16 biter; + u16 csr; +} __packed; + +struct fsl_edma_requestbuf { + dma_addr_t saddr; + dma_addr_t daddr; + u32 soff; + u32 doff; + u32 attr; + u32 minor_loop; + u32 len; +}; + +/* + * config the eDMA to use the TCD sg feature + * + * @channel: which channel. in fact this function is designed to satisfy + * the ATA driver TCD SG need, i.e. by now it is a special + * func, because it need prev alloc channel TCD physical memory + * first, we add the ATA's in the eDMA init only + * @buf: buffer array to fill the TCDs + * @nents: the size of the buf + */ +void mcf_edma_sg_config(int channel, struct fsl_edma_requestbuf *buf, + int nents); + +/* + * The zero-copy version of mcf_edma_sg_config() + */ +void mcf_edma_sglist_config(int channel, struct scatterlist *sgl, int n_elem, + int dma_dir, u32 addr, u32 attr, + u32 soff, u32 doff, u32 nbytes); +#endif + +/* Setup transfer control descriptor (TCD) + * channel - descriptor number + * source - source address + * dest - destination address + * attr - attributes + * soff - source offset + * nbytes - number of bytes to be transfered in minor loop + * slast - last source address adjustment + * citer - major loop count + * biter - begining minor loop count + * doff - destination offset + * dlast_sga - last destination address adjustment + * major_int - generate interrupt after each major loop + * disable_req - disable DMA request after major loop + */ +void mcf_edma_set_tcd_params(int channel, u32 source, u32 dest, + u32 attr, u32 soff, u32 nbytes, u32 slast, + u32 citer, u32 biter, u32 doff, u32 dlast_sga, + int major_int, int disable_req); + +/* Setup transfer control descriptor (TCD) and enable halfway irq + * channel - descriptor number + * source - source address + * dest - destination address + * attr - attributes + * soff - source offset + * nbytes - number of bytes to be transfered in minor loop + * slast - last source address adjustment + * biter - major loop count + * doff - destination offset + * dlast_sga - last destination address adjustment + * disable_req - disable DMA request after major loop + */ +void mcf_edma_set_tcd_params_halfirq(int channel, u32 source, u32 dest, + u32 attr, u32 soff, u32 nbytes, u32 slast, + u32 biter, u32 doff, u32 dlast_sga, + int disable_req); + +/* check if dma is done + * channel - descriptor number + * return 1 if done + */ +int mcf_edma_check_done(int channel); + +/* Starts eDMA transfer on specified channel + * channel - eDMA TCD number + */ +static inline void +mcf_edma_start_transfer(int channel) +{ + MCF_EDMA_SERQ = channel; + MCF_EDMA_SSRT = channel; +} + +/* Restart eDMA transfer from halfirq + * channel - eDMA TCD number + */ +static inline void +mcf_edma_confirm_halfirq(int channel) +{ + /*MCF_EDMA_TCD_CSR(channel) = 7;*/ + MCF_EDMA_SSRT = channel; +} + +/* Starts eDMA transfer on specified channel based on peripheral request + * channel - eDMA TCD number + */ +static inline void mcf_edma_enable_transfer(int channel) +{ + MCF_EDMA_SERQ = channel; +} + + +/* Stops eDMA transfer + * channel - eDMA TCD number + */ +static inline void +mcf_edma_stop_transfer(int channel) +{ + MCF_EDMA_CINT = channel; + MCF_EDMA_CERQ = channel; +} + +/* Confirm that interrupt has been handled + * channel - eDMA TCD number + */ +static inline void +mcf_edma_confirm_interrupt_handled(int channel) +{ + MCF_EDMA_CINT = channel; +} + +/** + * mcf_edma_request_channel - Request an eDMA channel + * @channel: channel number. In case it is equal to EDMA_CHANNEL_ANY + * it will be allocated a first free eDMA channel. + * @handler: dma handler + * @error_handler: dma error handler + * @irq_level: irq level for the dma handler + * @arg: argument to pass back + * @lock: optional spinlock to hold over interrupt + * @device_id: device id + * + * Returns allocatedd channel number if success or + * a negative value if failure. + */ +int mcf_edma_request_channel(int channel, + irqreturn_t(*handler) (int, void *), + void (*error_handler) (int, void *), + u8 irq_level, + void *arg, + spinlock_t *lock, const char *device_id); + +/** + * Update the channel callback/arg + * @channel: channel number + * @handler: dma handler + * @error_handler: dma error handler + * @arg: argument to pass back + * + * Returns 0 if success or a negative value if failure + */ +int mcf_edma_set_callback(int channel, + irqreturn_t(*handler) (int, void *), + void (*error_handler) (int, void *), void *arg); + +/** + * Free the edma channel + * @channel: channel number + * @arg: argument created with + * + * Returns 0 if success or a negative value if failure + */ +int mcf_edma_free_channel(int channel, void *arg); + +void mcf_edma_dump_channel(int channel); + +#endif /* _MCF_EDMA_H */ --- a/drivers/Makefile +++ b/drivers/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_FB_I810) += video obj-$(CONFIG_FB_INTEL) += video/intelfb/ obj-$(CONFIG_PARPORT) += parport/ +obj-$(CONFIG_COLDFIRE_EDMA) += dma/ obj-y += base/ block/ misc/ mfd/ nfc/ obj-$(CONFIG_NUBUS) += nubus/ obj-y += macintosh/ @@ -114,6 +115,7 @@ obj-$(CONFIG_BCMA) += bcma/ obj-$(CONFIG_VHOST_NET) += vhost/ obj-$(CONFIG_VLYNQ) += vlynq/ obj-$(CONFIG_STAGING) += staging/ +obj-$(CONFIG_MCD_DMA) += dma/ obj-y += platform/ obj-y += ieee802154/ #common clk code --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -114,6 +114,21 @@ config MPC512X_DMA ---help--- Enable support for the Freescale MPC512x built-in DMA engine. +config COLDFIRE_EDMA + tristate "Coldfire eDMA support" + default y + depends on COLDFIRE && (M5445X || M5441X) + help + Enable support for Coldfire eDMA controller. For example + used by Coldfire SSI Audio device driver. + +config COLDFIRE_EDMA_TEST + tristate "Coldfire eDMA simple test module" + default m + depends on COLDFIRE_EDMA + help + This is simple eDMA test module. + config MV_XOR bool "Marvell XOR engine support" depends on PLAT_ORION --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -13,10 +13,14 @@ obj-$(CONFIG_INTEL_IOATDMA) += ioat/ obj-$(CONFIG_INTEL_IOP_ADMA) += iop-adma.o obj-$(CONFIG_FSL_DMA) += fsldma.o obj-$(CONFIG_MPC512X_DMA) += mpc512x_dma.o +obj-$(CONFIG_COLDFIRE_EDMA) += mcf_edma.o +obj-$(CONFIG_COLDFIRE_EDMA_TEST) += mcf_edma_test.o obj-$(CONFIG_MV_XOR) += mv_xor.o obj-$(CONFIG_DW_DMAC) += dw_dmac.o obj-$(CONFIG_AT_HDMAC) += at_hdmac.o obj-$(CONFIG_MX3_IPU) += ipu/ +obj-$(CONFIG_MCD_DMA) += mcddma.o +mcddma-objs := MCD_dmaApi.o MCD_tasks.o MCD_tasksInit.o obj-$(CONFIG_TXX9_DMAC) += txx9dmac.o obj-$(CONFIG_SH_DMAE) += shdma.o obj-$(CONFIG_COH901318) += coh901318.o coh901318_lli.o --- /dev/null +++ b/drivers/dma/mcf_edma.c @@ -0,0 +1,1029 @@ +/* + * mcf_edma.c - eDMA driver for Coldfire. + * + * Copyright (C) 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + * Author: Andrey Butok + * Yaroslav Vinogradov + * Lanttor.Guo@freescale.com add m5441x platform support. + * + * 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 + * + *************************************************************************** + * Changes: + * v0.002 29 February 2008 Andrey Butok, Freescale Semiconductor + * Added support of atomatic channel allocation from the + * channel pool. + * v0.001 12 February 2008 Andrey Butok + * Initial Release - developed on uClinux with 2.6.23 kernel. + * Based on coldfire_edma.c code + * of Yaroslav Vinogradov (Freescale Semiconductor, Inc.) + * + * NOTE: This driver was tested on MCF52277 platform. + * It should also work on other Coldfire platdorms with eDMA module. + * + * TBD: Try to make it more general. + * Try to integrate with current API + * or use Intel DMA API + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Please add here processors that were tested with this driver */ +#if !defined(CONFIG_M5227x) && !defined(CONFIG_M5445X) && \ + !defined(CONFIG_M5441X) +#error "The driver is not tested/designed for your processor!" +#endif + +#define MCF_EDMA_DRIVER_VERSION "Revision: 0.003" +#define MCF_EDMA_DRIVER_AUTHOR "Freescale Semiconductor Inc, Andrey Butok" +#define MCF_EDMA_DRIVER_DESC "Coldfire EDMA driver." +#define MCF_EDMA_DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC +#define MCF_EDMA_DRIVER_LICENSE "GPL" +#define MCF_EDMA_DRIVER_NAME "mcf_edma" + +#define MCF_EDMA_DEV_MINOR (1) + +#undef MCF_EDMA_DEBUG + +#ifdef MCF_EDMA_DEBUG +#define DBG(fmt, args...) printk(KERN_INFO "[%s] " fmt, \ + __func__, ## args) +#else +#define DBG(fmt, args...) do {} while (0) +#endif + +#define ERR(format, arg...) printk(KERN_ERR "%s:%s: " format "\n", \ + __FILE__, __func__ , ## arg) +#define INFO(stuff...) printk(KERN_INFO MCF_EDMA_DRIVER_NAME \ + ": " stuff) + +/* DMA channel pool used for atomtic channel allocation. + * You can edit this list. First candidates are "Not used/Reserved" channels */ +u8 mcf_edma_channel_pool[] = { 1, /* Not used */ + 0, /* External DMA request */ + 5, /* UART1 Receive */ + 6, /* UART1 Transmit */ + 7, /* UART2 Receive */ + 8, /* UART2 Transmit */ +#if defined(CONFIG_M5441X) + 16, + 55, + 56, + 63, +#endif +}; + +/* + * Callback handler data for each TCD + */ +struct mcf_edma_isr_record { + irqreturn_t(*irq_handler) (int, void *); /* interrupt handler */ + void (*error_handler) (int, void *); /* error interrupt handler */ + void *arg; /* argument to pass back */ + int allocated; /* busy flag */ + spinlock_t *lock; /* spin lock (optional) */ + const char *device_id; /* dev id string, used in procfs */ +}; + +/* + * Device structure + */ +struct mcf_edma_dev { + struct cdev cdev; /* character device */ + struct mcf_edma_isr_record dma_interrupt_handlers[MCF_EDMA_CHANNELS]; +}; + +/* allocated major device number */ +static int mcf_edma_major; + +/* device driver structure */ +static struct mcf_edma_dev *mcf_edma_devp; + +#ifdef CONFIG_M54455 +/* PATA controller structure */ +static struct { + struct TCD *pata_tcd_va; + dma_addr_t pata_tcd_pa; +} fsl_pata_dma_tcd; +#endif + +/* device driver file operations */ +const struct file_operations mcf_edma_fops = { + .owner = THIS_MODULE, +}; + +/** + * mcf_edma_isr - eDMA channel interrupt handler + * @irq: interrupt number + * @dev_id: argument + */ +static irqreturn_t +mcf_edma_isr(int irq, void *dev_id) +{ + int channel = -1; + int result = IRQ_HANDLED; + +#if defined(CONFIG_M5445X) + channel = irq - MCF_EDMA_INT0_BASE; +#elif defined(CONFIG_M5441X) + if (irq >= MCF_EDMA_INT0_BASE && + irq < MCF_EDMA_INT0_BASE + MCF_EDMA_INT0_NUM) + channel = irq - MCF_EDMA_INT0_BASE; + else if (irq >= MCF_EDMA_INT1_BASE && + irq < MCF_EDMA_INT1_BASE + MCF_EDMA_INT1_NUM) + channel = irq - MCF_EDMA_INT1_BASE + MCF_EDMA_INT0_END; + else if (irq == MCF_EDMA_INT2_BASE && + irq < MCF_EDMA_INT2_BASE + MCF_EDMA_INT2_NUM) { + int i; + for (i = 0; i < MCF_EDMA_INT2_NUM; i++) { + if ((MCF_EDMA_INTH >> 24) & (0x1 << i)) { + channel = irq - MCF_EDMA_INT2_BASE + + MCF_EDMA_INT1_END + i; + break; + } + } + } else { + ERR("Bad irq number at isr!\n"); + return result; + } +#endif + + DBG("\n"); + + if ((mcf_edma_devp != NULL) && + (mcf_edma_devp->dma_interrupt_handlers[channel].irq_handler)) { + /* call user irq handler */ + if (mcf_edma_devp->dma_interrupt_handlers[channel].lock) + spin_lock(mcf_edma_devp-> + dma_interrupt_handlers[channel].lock); + + result = + mcf_edma_devp->dma_interrupt_handlers[channel]. + irq_handler(channel, + mcf_edma_devp->dma_interrupt_handlers[channel]. + arg); + + if (mcf_edma_devp->dma_interrupt_handlers[channel].lock) + spin_unlock(mcf_edma_devp-> + dma_interrupt_handlers[channel].lock); + } else { + /* no irq handler so just ack it */ + mcf_edma_confirm_interrupt_handled(channel); + ERR(" No handler for DMA channel (%d)\n", channel); + } + + return result; +} + +/** + * mcf_edma_error_isr - eDMA error interrupt handler + * @irq: interrupt number + * @dev_id: argument + */ +static irqreturn_t +mcf_edma_error_isr(int irq, void *dev_id) +{ + int i; + +#if defined(CONFIG_M5445X) + u16 err; + + err = MCF_EDMA_ERR; + for (i = 0; i < MCF_EDMA_CHANNELS; i++) { + if (err & (1 << i)) { + if (mcf_edma_devp != NULL && + mcf_edma_devp->dma_interrupt_handlers[i]. + error_handler) + mcf_edma_devp->dma_interrupt_handlers[i]. + error_handler(i, + mcf_edma_devp-> + dma_interrupt_handlers[i]. + arg); + else + ERR(" DMA error on channel (%d)\n", i); + } + } +#elif defined(CONFIG_M5441X) + u32 errl, errh; + + errl = MCF_EDMA_ERRL; + errh = MCF_EDMA_ERRH; + + for (i = 0; i < MCF_EDMA_CHANNELS; i++) { + if ((errl & (1 << i)) || (errh & (1 << (i - 32)))) { + if (mcf_edma_devp != NULL && + mcf_edma_devp->dma_interrupt_handlers[i]. + error_handler) + mcf_edma_devp->dma_interrupt_handlers[i]. + error_handler(i, mcf_edma_devp-> + dma_interrupt_handlers[i].arg); + else + ERR(" DMA error on channel (%d)\n", i); + } + } +#endif + MCF_EDMA_CERR = MCF_EDMA_CERR_CAER; + return IRQ_HANDLED; +} + +/** + * mcf_edma_check_done - Check if channel is finished or not + * @channel: channel number + * return: 0 if not done yet + */ +int +mcf_edma_check_done(int channel) +{ + if (channel < 0 || channel > MCF_EDMA_CHANNELS) + return 1; + + return MCF_EDMA_TCD_CSR(channel) & MCF_EDMA_TCD_CSR_DONE; +} +EXPORT_SYMBOL(mcf_edma_check_done); + +/** + * mcf_edma_set_tcd_params - Set transfer control descriptor (TCD) + * @channel: channel number + * @source: source address + * @dest: destination address + * @attr: attributes + * @soff: source offset + * @nbytes: number of bytes to be transfered in minor loop + * @slast: last source address adjustment + * @citer: major loop count + * @biter: beginning major loop count + * @doff: destination offset + * @dlast_sga: last destination address adjustment + * @major_int: generate interrupt after each major loop + * @disable_req: disable DMA request after major loop + */ +void +mcf_edma_set_tcd_params(int channel, u32 source, u32 dest, + u32 attr, u32 soff, u32 nbytes, u32 slast, + u32 citer, u32 biter, u32 doff, u32 dlast_sga, + int major_int, int disable_req) +{ + DBG("(%d)\n", channel); + + if (channel < 0 || channel > MCF_EDMA_CHANNELS) + return; + + MCF_EDMA_TCD_SADDR(channel) = source; + MCF_EDMA_TCD_DADDR(channel) = dest; + MCF_EDMA_TCD_ATTR(channel) = attr; + MCF_EDMA_TCD_SOFF(channel) = MCF_EDMA_TCD_SOFF_SOFF(soff); + MCF_EDMA_TCD_NBYTES(channel) = MCF_EDMA_TCD_NBYTES_NBYTES(nbytes); + MCF_EDMA_TCD_SLAST(channel) = MCF_EDMA_TCD_SLAST_SLAST(slast); + MCF_EDMA_TCD_CITER(channel) = MCF_EDMA_TCD_CITER_CITER(citer); + MCF_EDMA_TCD_BITER(channel) = MCF_EDMA_TCD_BITER_BITER(biter); + MCF_EDMA_TCD_DOFF(channel) = MCF_EDMA_TCD_DOFF_DOFF(doff); + MCF_EDMA_TCD_DLAST_SGA(channel) = + MCF_EDMA_TCD_DLAST_SGA_DLAST_SGA(dlast_sga); + MCF_EDMA_TCD_CSR(channel) = 0x0000; + + /* interrupt at the end of major loop */ + if (major_int) + MCF_EDMA_TCD_CSR(channel) |= MCF_EDMA_TCD_CSR_INT_MAJOR; + else + MCF_EDMA_TCD_CSR(channel) &= ~MCF_EDMA_TCD_CSR_INT_MAJOR; + + /* disable request at the end of major loop of transfer or not */ + if (disable_req) + MCF_EDMA_TCD_CSR(channel) |= MCF_EDMA_TCD_CSR_D_REQ; + else + MCF_EDMA_TCD_CSR(channel) &= ~MCF_EDMA_TCD_CSR_D_REQ; + + /* enable error interrupt */ + MCF_EDMA_SEEI = MCF_EDMA_SEEI_SEEI(channel); +} +EXPORT_SYMBOL(mcf_edma_set_tcd_params); +#ifdef CONFIG_M54455 +/** + * mcf_edma_sg_config - config an eDMA channel to use the S/G tcd feature + * @channel: channel number + * @buf: the array of tcd sg + * @nents: number of tcd sg array, the max is 256 set but can modify + * + * limitation: + * currently this function is only for PATA RX/TX on MCF54455, + * so eDMA init does not allocate TCD memory for other memory + * + * TODO: + * any one who need this feature shoule add his own TCD memory init + */ +void mcf_edma_sg_config(int channel, struct fsl_edma_requestbuf *buf, + int nents) +{ + struct TCD *vtcd = (struct TCD *)fsl_pata_dma_tcd.pata_tcd_va; + u32 ptcd = fsl_pata_dma_tcd.pata_tcd_pa; + struct fsl_edma_requestbuf *pb = buf; + int i; + + if (channel < MCF_EDMA_CHAN_ATA_RX || channel > MCF_EDMA_CHAN_ATA_TX) { + printk(KERN_ERR "mcf edma sg config err, not support\n"); + return; + } + if (nents > MCF_EDMA_TCD_PER_CHAN) { + printk(KERN_ERR "Too many SGs, please confirm.%d > %d\n", + nents, MCF_EDMA_TCD_PER_CHAN); + return; + } + + /* build our tcd sg array */ + for (i = 0; i < nents; i++) { + memset(vtcd, 0 , sizeof(struct TCD)); + vtcd->saddr = pb->saddr; + vtcd->daddr = pb->daddr; + vtcd->attr = pb->attr; + vtcd->soff = pb->soff; + vtcd->doff = pb->doff; + vtcd->nbytes = pb->minor_loop; + vtcd->citer = vtcd->biter = pb->len/pb->minor_loop; + + if (i != nents - 1) { + vtcd->csr |= MCF_EDMA_TCD_CSR_E_SG;/* we are tcd sg */ + vtcd->dlast_sga = + (u32)(ptcd + (i + 1)*sizeof(struct TCD)); + } else { + /*this is the last sg, so enable the major int*/ + vtcd->csr |= MCF_EDMA_TCD_CSR_INT_MAJOR + |MCF_EDMA_TCD_CSR_D_REQ; + } + pb++; + vtcd++; + } + + /* Now setup the firset TCD for this sg to the edma enginee */ + vtcd = fsl_pata_dma_tcd.pata_tcd_va; + + MCF_EDMA_TCD_CSR(channel) = 0x0000; + MCF_EDMA_TCD_SADDR(channel) = vtcd->saddr; + MCF_EDMA_TCD_DADDR(channel) = vtcd->daddr; + MCF_EDMA_TCD_ATTR(channel) = vtcd->attr; + MCF_EDMA_TCD_SOFF(channel) = MCF_EDMA_TCD_SOFF_SOFF(vtcd->soff); + MCF_EDMA_TCD_NBYTES(channel) = MCF_EDMA_TCD_NBYTES_NBYTES(vtcd->nbytes); + MCF_EDMA_TCD_SLAST(channel) = MCF_EDMA_TCD_SLAST_SLAST(vtcd->slast); + MCF_EDMA_TCD_CITER(channel) = MCF_EDMA_TCD_CITER_CITER(vtcd->citer); + MCF_EDMA_TCD_BITER(channel) = MCF_EDMA_TCD_BITER_BITER(vtcd->biter); + MCF_EDMA_TCD_DOFF(channel) = MCF_EDMA_TCD_DOFF_DOFF(vtcd->doff); + MCF_EDMA_TCD_DLAST_SGA(channel) = + MCF_EDMA_TCD_DLAST_SGA_DLAST_SGA(vtcd->dlast_sga); + MCF_EDMA_TCD_CSR(channel) |= vtcd->csr; +} +EXPORT_SYMBOL(mcf_edma_sg_config); + +/** + * The zero-copy version of mcf_edma_sg_config + * dma_dir : indicate teh addr direction + */ +void mcf_edma_sglist_config(int channel, struct scatterlist *sgl, int n_elem, + int dma_dir, u32 addr, u32 attr, + u32 soff, u32 doff, u32 nbytes) +{ + struct TCD *vtcd = (struct TCD *)fsl_pata_dma_tcd.pata_tcd_va; + u32 ptcd = fsl_pata_dma_tcd.pata_tcd_pa; + struct scatterlist *sg; + u32 si; + + if (channel < MCF_EDMA_CHAN_ATA_RX || channel > MCF_EDMA_CHAN_ATA_TX) { + printk(KERN_ERR "mcf edma sg config err, not support\n"); + return; + } + if (n_elem > MCF_EDMA_TCD_PER_CHAN) { + printk(KERN_ERR "Too many SGs, please confirm.%d > %d\n", + n_elem, MCF_EDMA_TCD_PER_CHAN); + return; + } + + /* build our tcd sg array */ + if (dma_dir == DMA_TO_DEVICE) { /* write */ + for_each_sg(sgl, sg, n_elem, si) { + memset(vtcd, 0 , sizeof(struct TCD)); + vtcd->saddr = sg_dma_address(sg); + vtcd->daddr = addr; + vtcd->attr = attr; + vtcd->soff = soff; + vtcd->doff = doff; + vtcd->nbytes = nbytes; + vtcd->citer = vtcd->biter = sg_dma_len(sg)/nbytes; + + if (si != n_elem - 1) { + /* we are tcd sg */ + vtcd->csr |= MCF_EDMA_TCD_CSR_E_SG; + vtcd->dlast_sga = (u32)(ptcd + (si + 1) * \ + sizeof(struct TCD)); + } else { + /*this is the last sg, so enable the major int*/ + vtcd->csr |= MCF_EDMA_TCD_CSR_INT_MAJOR + |MCF_EDMA_TCD_CSR_D_REQ; + } + vtcd++; + } + } else { + for_each_sg(sgl, sg, n_elem, si) { + memset(vtcd, 0 , sizeof(struct TCD)); + vtcd->daddr = sg_dma_address(sg); + vtcd->saddr = addr; + vtcd->attr = attr; + vtcd->soff = soff; + vtcd->doff = doff; + vtcd->nbytes = nbytes; + vtcd->citer = vtcd->biter = sg_dma_len(sg)/nbytes; + + if (si != n_elem - 1) { + /* we are tcd sg */ + vtcd->csr |= MCF_EDMA_TCD_CSR_E_SG; + vtcd->dlast_sga = (u32)(ptcd + (si + 1) * \ + sizeof(struct TCD)); + } else { + /*this is the last sg, so enable the major int*/ + vtcd->csr |= MCF_EDMA_TCD_CSR_INT_MAJOR + |MCF_EDMA_TCD_CSR_D_REQ; + } + vtcd++; + } + } + + /* Now setup the firset TCD for this sg to the edma enginee */ + vtcd = fsl_pata_dma_tcd.pata_tcd_va; + + MCF_EDMA_TCD_CSR(channel) = 0x0000; + MCF_EDMA_TCD_SADDR(channel) = vtcd->saddr; + MCF_EDMA_TCD_DADDR(channel) = vtcd->daddr; + MCF_EDMA_TCD_ATTR(channel) = vtcd->attr; + MCF_EDMA_TCD_SOFF(channel) = MCF_EDMA_TCD_SOFF_SOFF(vtcd->soff); + MCF_EDMA_TCD_NBYTES(channel) = MCF_EDMA_TCD_NBYTES_NBYTES(vtcd->nbytes); + MCF_EDMA_TCD_SLAST(channel) = MCF_EDMA_TCD_SLAST_SLAST(vtcd->slast); + MCF_EDMA_TCD_CITER(channel) = MCF_EDMA_TCD_CITER_CITER(vtcd->citer); + MCF_EDMA_TCD_BITER(channel) = MCF_EDMA_TCD_BITER_BITER(vtcd->biter); + MCF_EDMA_TCD_DOFF(channel) = MCF_EDMA_TCD_DOFF_DOFF(vtcd->doff); + MCF_EDMA_TCD_DLAST_SGA(channel) = + MCF_EDMA_TCD_DLAST_SGA_DLAST_SGA(vtcd->dlast_sga); + + MCF_EDMA_TCD_CSR(channel) |= vtcd->csr; +} +EXPORT_SYMBOL(mcf_edma_sglist_config); +#endif +/** + * mcf_edma_set_tcd_params_halfirq - Set TCD AND enable half irq + * @channel: channel number + * @source: source address + * @dest: destination address + * @attr: attributes + * @soff: source offset + * @nbytes: number of bytes to be transfered in minor loop + * @slast: last source address adjustment + * @biter: beginning major loop count + * @doff: destination offset + * @dlast_sga: last destination address adjustment + * @disable_req: disable DMA request after major loop + */ +void +mcf_edma_set_tcd_params_halfirq(int channel, u32 source, u32 dest, + u32 attr, u32 soff, u32 nbytes, u32 slast, + u32 biter, u32 doff, u32 dlast_sga, + int disable_req) +{ + DBG("(%d)\n", channel); + + if (channel < 0 || channel > MCF_EDMA_CHANNELS) + return; + + mcf_edma_set_tcd_params(channel, source, dest, + attr, soff, nbytes, slast, + biter, biter, doff, dlast_sga, + 1/*0*/, disable_req); + + if (biter < 2) + printk(KERN_ERR "MCF_EDMA: Request for halfway irq denied\n"); + + /* interrupt midway through major loop */ + MCF_EDMA_TCD_CSR(channel) |= MCF_EDMA_TCD_CSR_INT_HALF; +} +EXPORT_SYMBOL(mcf_edma_set_tcd_params_halfirq); + +/** + * mcf_edma_request_channel - Request an eDMA channel + * @channel: channel number. In case it is equal to EDMA_CHANNEL_ANY + * it will be allocated a first free eDMA channel. + * @handler: dma handler + * @error_handler: dma error handler + * @irq_level: irq level for the dma handler + * @arg: argument to pass back + * @lock: optional spinlock to hold over interrupt + * @device_id: device id + * + * Returns allocatedd channel number if success or + * a negative value if failure. + */ +int +mcf_edma_request_channel(int channel, + irqreturn_t(*handler) (int, void *), + void (*error_handler) (int, void *), + u8 irq_level, + void *arg, spinlock_t *lock, const char *device_id) +{ + DBG("\n channel=%d\n", channel); + + if (mcf_edma_devp != NULL + && ((channel >= 0 && channel <= MCF_EDMA_CHANNELS) + || (channel == MCF_EDMA_CHANNEL_ANY))) { + if (channel == MCF_EDMA_CHANNEL_ANY) { + int i; + for (i = 0; i < sizeof(mcf_edma_channel_pool); i++) { + if (mcf_edma_devp->dma_interrupt_handlers + [mcf_edma_channel_pool[i]].allocated == + 0) { + channel = mcf_edma_channel_pool[i]; + break; + } + }; + if (channel == MCF_EDMA_CHANNEL_ANY) + return -EBUSY; + } else { + if (mcf_edma_devp->dma_interrupt_handlers[channel]. + allocated) + return -EBUSY; + } + + mcf_edma_devp->dma_interrupt_handlers[channel].allocated = 1; + mcf_edma_devp->dma_interrupt_handlers[channel].irq_handler = + handler; + mcf_edma_devp->dma_interrupt_handlers[channel].error_handler = + error_handler; + mcf_edma_devp->dma_interrupt_handlers[channel].arg = arg; + mcf_edma_devp->dma_interrupt_handlers[channel].lock = lock; + mcf_edma_devp->dma_interrupt_handlers[channel].device_id = + device_id; + + /* Initalize interrupt controller to allow eDMA interrupts */ +#if defined(CONFIG_M5445X) + MCF_INTC0_ICR(MCF_EDMA_INT0_CHANNEL_BASE + channel) = irq_level; + MCF_INTC0_CIMR = MCF_EDMA_INT0_CHANNEL_BASE + channel; +#elif defined(CONFIG_M5441X) + if (channel >= 0 && channel < MCF_EDMA_INT0_END) { + MCF_INTC0_ICR(MCF_EDMA_INT0_CHANNEL_BASE + channel) = + irq_level; + MCF_INTC0_CIMR = MCF_EDMA_INT0_CHANNEL_BASE + channel; + } else if (channel >= MCF_EDMA_INT0_END && + channel < MCF_EDMA_INT1_END) { + MCF_INTC1_ICR(MCF_EDMA_INT1_CHANNEL_BASE + + (channel - MCF_EDMA_INT0_END)) = irq_level; + MCF_INTC1_CIMR = MCF_EDMA_INT1_CHANNEL_BASE + + (channel - MCF_EDMA_INT0_END); + } else if (channel >= MCF_EDMA_INT1_END && + channel < MCF_EDMA_INT2_END) { + MCF_INTC2_ICR(MCF_EDMA_INT2_CHANNEL_BASE) = irq_level; + MCF_INTC2_CIMR = MCF_EDMA_INT2_CHANNEL_BASE; + } else + ERR("Bad channel number!\n"); +#endif + return channel; + } + return -EINVAL; +} +EXPORT_SYMBOL(mcf_edma_request_channel); + +/** + * mcf_edma_set_callback - Update the channel callback/arg + * @channel: channel number + * @handler: dma handler + * @error_handler: dma error handler + * @arg: argument to pass back + * + * Returns 0 if success or a negative value if failure + */ +int +mcf_edma_set_callback(int channel, + irqreturn_t(*handler) (int, void *), + void (*error_handler) (int, void *), void *arg) +{ + DBG("\n"); + + if (mcf_edma_devp != NULL && channel >= 0 + && channel <= MCF_EDMA_CHANNELS + && mcf_edma_devp->dma_interrupt_handlers[channel].allocated) { + mcf_edma_devp->dma_interrupt_handlers[channel].irq_handler = + handler; + mcf_edma_devp->dma_interrupt_handlers[channel].error_handler = + error_handler; + mcf_edma_devp->dma_interrupt_handlers[channel].arg = arg; + return 0; + } + return -EINVAL; +} +EXPORT_SYMBOL(mcf_edma_set_callback); + +/** + * mcf_edma_free_channel - Free the edma channel + * @channel: channel number + * @arg: argument created with + * + * Returns 0 if success or a negative value if failure + */ +int +mcf_edma_free_channel(int channel, void *arg) +{ + DBG("\n"); + + if (mcf_edma_devp != NULL && channel >= 0 + && channel <= MCF_EDMA_CHANNELS) { + if (mcf_edma_devp->dma_interrupt_handlers[channel].allocated) { +#if 1 + if (mcf_edma_devp->dma_interrupt_handlers[channel]. + arg != arg) + return -EBUSY; +#endif + + mcf_edma_devp->dma_interrupt_handlers[channel]. + allocated = 0; + mcf_edma_devp->dma_interrupt_handlers[channel].arg = + NULL; + mcf_edma_devp->dma_interrupt_handlers[channel]. + irq_handler = NULL; + mcf_edma_devp->dma_interrupt_handlers[channel]. + error_handler = NULL; + mcf_edma_devp->dma_interrupt_handlers[channel].lock = + NULL; + } + + /* make sure error interrupt is disabled */ + MCF_EDMA_CEEI = MCF_EDMA_CEEI_CEEI(channel); + + return 0; + } + return -EINVAL; +} +EXPORT_SYMBOL(mcf_edma_free_channel); + +/** + * mcf_edma_cleanup - cleanup driver allocated resources + */ +static void +mcf_edma_cleanup(void) +{ + dev_t devno; + int i; + + DBG("\n"); + + /* disable all error ints */ + MCF_EDMA_CEEI = MCF_EDMA_CEEI_CAEE; + + /* free interrupts/memory */ + if (mcf_edma_devp) { + for (i = 0; i < MCF_EDMA_CHANNELS; i++) { + #if defined(CONFIG_M5445X) + free_irq(MCF_EDMA_INT0_BASE + i, mcf_edma_devp); + #elif defined(CONFIG_M5441X) + if (i >= 0 && i < MCF_EDMA_INT0_END) + free_irq(MCF_EDMA_INT0_BASE + i, mcf_edma_devp); + else if (i >= MCF_EDMA_INT0_END && + i <= MCF_EDMA_INT1_END) + free_irq(MCF_EDMA_INT1_BASE + + (i - MCF_EDMA_INT0_END), mcf_edma_devp); + else if (i >= MCF_EDMA_INT1_END && + i < MCF_EDMA_INT2_END) { + free_irq(MCF_EDMA_INT2_BASE, mcf_edma_devp); + break; + } else { + ERR("Bad irq number!\n"); + return; + } + #endif + } + + free_irq(MCF_EDMA_INT0_BASE + MCF_EDMA_INT_ERR, mcf_edma_devp); + cdev_del(&mcf_edma_devp->cdev); + kfree(mcf_edma_devp); + } + + /* unregister character device */ + devno = MKDEV(mcf_edma_major, 0); + unregister_chrdev_region(devno, 1); +} + +/** + * mcf_edma_dump_channel - dump a channel information + */ +void +mcf_edma_dump_channel(int channel) +{ + printk(KERN_DEBUG "EDMA Channel %d\n", channel); + printk(KERN_DEBUG " TCD Base = 0x%x\n", + (int)&MCF_EDMA_TCD_SADDR(channel)); + printk(KERN_DEBUG " SRCADDR = 0x%lx\n", + MCF_EDMA_TCD_SADDR(channel)); + printk(KERN_DEBUG " SRCOFF = 0x%x\n", + MCF_EDMA_TCD_SOFF(channel)); + printk(KERN_DEBUG " XFR ATTRIB = 0x%x\n", + MCF_EDMA_TCD_ATTR(channel)); + printk(KERN_DEBUG " SRCLAST = 0x%lx\n", + MCF_EDMA_TCD_SLAST(channel)); + printk(KERN_DEBUG " DSTADDR = 0x%lx\n", + MCF_EDMA_TCD_DADDR(channel)); + printk(KERN_DEBUG " MINOR BCNT = 0x%lx\n", + MCF_EDMA_TCD_NBYTES(channel)); + printk(KERN_DEBUG " CUR_LOOP_CNT = 0x%x\n", + MCF_EDMA_TCD_CITER(channel)&0x1ff); + printk(KERN_DEBUG " BEG_LOOP_CNT = 0x%x\n", + MCF_EDMA_TCD_BITER(channel)&0x1ff); + printk(KERN_DEBUG " STATUS = 0x%x\n", + MCF_EDMA_TCD_CSR(channel)); + +} +EXPORT_SYMBOL(mcf_edma_dump_channel); + +#ifdef CONFIG_PROC_FS +/* + * proc file system support + */ + +#define FREE_CHANNEL "free" +#define DEVICE_UNKNOWN "device unknown" + +/** + * mcf_edma_proc_show - print out proc info + * @m: seq_file + * @v: + */ +static int +mcf_edma_proc_show(struct seq_file *m, void *v) +{ + int i; + + if (mcf_edma_devp == NULL) + return 0; + + for (i = 0; i < MCF_EDMA_CHANNELS; i++) { + if (mcf_edma_devp->dma_interrupt_handlers[i].allocated) { + if (mcf_edma_devp->dma_interrupt_handlers[i].device_id) + seq_printf(m, "%2d: %s\n", i, + mcf_edma_devp-> + dma_interrupt_handlers[i]. + device_id); + else + seq_printf(m, "%2d: %s\n", i, DEVICE_UNKNOWN); + } else + seq_printf(m, "%2d: %s\n", i, FREE_CHANNEL); + } + return 0; +} + +/** + * mcf_edma_proc_open - open the proc file + * @inode: inode ptr + * @file: file ptr + */ +static int +mcf_edma_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, mcf_edma_proc_show, NULL); +} + +static const struct file_operations mcf_edma_proc_operations = { + .open = mcf_edma_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** + * mcf_edma_proc_init - initialize proc filesystem + */ +static int __init +mcf_edma_proc_init(void) +{ + struct proc_dir_entry *e; + + e = create_proc_entry("edma", 0, NULL); + if (e) + e->proc_fops = &mcf_edma_proc_operations; + + return 0; +} + +#endif + +/** + * mcf_edma_init - eDMA module init + */ +static int __init +mcf_edma_init(void) +{ + dev_t dev; + int result; + int i; +#ifdef CONFIG_M54455 + u32 offset; +#endif + +#if defined(CONFIG_M5441X) + /* edma group priority, default grp0 > grp1 > grp2 > grp3 */ + u32 grp0_pri = MCF_EDMA_CR_GRP0PRI(0x00); + u32 grp1_pri = MCF_EDMA_CR_GRP1PRI(0x01); + u32 grp2_pri = MCF_EDMA_CR_GRP2PRI(0x02); + u32 grp3_pri = MCF_EDMA_CR_GRP3PRI(0x03); +#endif + + DBG("Entry\n"); + + /* allocate free major number */ + result = + alloc_chrdev_region(&dev, MCF_EDMA_DEV_MINOR, 1, + MCF_EDMA_DRIVER_NAME); + if (result < 0) { + ERR("Error %d can't get major number.\n", result); + return result; + } + mcf_edma_major = MAJOR(dev); + + /* allocate device driver structure */ + mcf_edma_devp = kmalloc(sizeof(struct mcf_edma_dev), GFP_KERNEL); + if (!mcf_edma_devp) { + result = -ENOMEM; + goto fail; + } + + /* init handlers (no handlers for beginning) */ + for (i = 0; i < MCF_EDMA_CHANNELS; i++) { + mcf_edma_devp->dma_interrupt_handlers[i].irq_handler = NULL; + mcf_edma_devp->dma_interrupt_handlers[i].error_handler = NULL; + mcf_edma_devp->dma_interrupt_handlers[i].arg = NULL; + mcf_edma_devp->dma_interrupt_handlers[i].allocated = 0; + mcf_edma_devp->dma_interrupt_handlers[i].lock = NULL; + mcf_edma_devp->dma_interrupt_handlers[i].device_id = NULL; + MCF_EDMA_TCD_CSR(i) = 0x0000; + } + + /* register char device */ + cdev_init(&mcf_edma_devp->cdev, &mcf_edma_fops); + mcf_edma_devp->cdev.owner = THIS_MODULE; + mcf_edma_devp->cdev.ops = &mcf_edma_fops; + result = cdev_add(&mcf_edma_devp->cdev, dev, 1); + if (result) { + ERR("Error %d adding coldfire-dma device.\n", result); + result = -ENODEV; + goto fail; + } + + /* request/enable irq for each eDMA channel */ +#if defined(CONFIG_M5445X) + for (i = 0; i < MCF_EDMA_CHANNELS; i++) { + result = request_irq(MCF_EDMA_INT0_BASE + i, + mcf_edma_isr, IRQF_DISABLED, + MCF_EDMA_DRIVER_NAME, mcf_edma_devp); + if (result) { + ERR("Cannot request irq %d\n", + (MCF_EDMA_INT0_BASE + i)); + result = -EBUSY; + goto fail; + } + } +#elif defined(CONFIG_M5441X) + for (i = 0; i < MCF_EDMA_CHANNELS; i++) { + if (i >= 0 && i < MCF_EDMA_INT0_END) { + result = request_irq(MCF_EDMA_INT0_BASE + i, + mcf_edma_isr, IRQF_DISABLED, + MCF_EDMA_DRIVER_NAME, + mcf_edma_devp); + + if (result) { + ERR("Cannot request irq %d\n", + (MCF_EDMA_INT0_BASE + i)); + result = -EBUSY; + goto fail; + } + } else if (i >= MCF_EDMA_INT0_END && i < MCF_EDMA_INT1_END) { + result = request_irq(MCF_EDMA_INT1_BASE + + (i - MCF_EDMA_INT0_END), + mcf_edma_isr, IRQF_DISABLED, + MCF_EDMA_DRIVER_NAME, + mcf_edma_devp); + + if (result) { + ERR("Cannot request irq %d\n", + (MCF_EDMA_INT1_BASE + + (i - MCF_EDMA_INT0_END))); + result = -EBUSY; + goto fail; + } + } else if (i >= MCF_EDMA_INT1_END && MCF_EDMA_INT2_END) { + result = request_irq(MCF_EDMA_INT2_BASE, + mcf_edma_isr, IRQF_DISABLED, + MCF_EDMA_DRIVER_NAME, + mcf_edma_devp); + if (result) { + ERR("Cannot request irq %d\n", + MCF_EDMA_INT2_BASE); + result = -EBUSY; + goto fail; + } + break; + } else { + ERR(" Cannot request irq because of wrong number!\n"); + result = -EBUSY; + goto fail; + } + } +#endif + + /* request error interrupt */ + result = request_irq(MCF_EDMA_INT0_BASE + MCF_EDMA_INT_ERR, + mcf_edma_error_isr, IRQF_DISABLED, + MCF_EDMA_DRIVER_NAME, mcf_edma_devp); + if (result) { + ERR("Cannot request irq %d\n", + (MCF_EDMA_INT0_BASE + MCF_EDMA_INT_ERR)); + result = -EBUSY; + goto fail; + } + +#if defined(CONFIG_M5445X) + MCF_EDMA_CR = 0; +#elif defined(CONFIG_M5441X) + MCF_EDMA_CR = (0 | grp0_pri | grp1_pri | grp2_pri | grp3_pri); + DBG("MCF_EDMA_CR = %lx\n", MCF_EDMA_CR); +#endif + +#ifdef CONFIG_M54455 + fsl_pata_dma_tcd.pata_tcd_va = (struct TCD *) dma_alloc_coherent(NULL, + MCF_EDMA_TCD_PER_CHAN + 1, + &fsl_pata_dma_tcd.pata_tcd_pa, + GFP_KERNEL); + + if (!fsl_pata_dma_tcd.pata_tcd_va) { + printk(KERN_INFO "MCF eDMA alllocate tcd memeory failed\n"); + goto fail; + } + + + offset = (fsl_pata_dma_tcd.pata_tcd_pa & (sizeof(struct TCD)-1)) ; + if (offset) { + /* + * up align the addr to 32B to match the eDMA enginee require, + * ie. sizeof tcd boundary + * */ + printk(KERN_INFO "pata tcd original:pa-%x[%x]\n", + fsl_pata_dma_tcd.pata_tcd_pa, + (u32)fsl_pata_dma_tcd.pata_tcd_va); + + fsl_pata_dma_tcd.pata_tcd_pa += sizeof(struct TCD) - offset; + fsl_pata_dma_tcd.pata_tcd_va += sizeof(struct TCD) - offset; + + printk(KERN_INFO "pata tcd realigned:pa-%x[%x]\n", + fsl_pata_dma_tcd.pata_tcd_pa, + (u32)fsl_pata_dma_tcd.pata_tcd_va); + } +#endif +#ifdef CONFIG_PROC_FS + mcf_edma_proc_init(); +#endif + + INFO("Initialized successfully\n"); + return 0; +fail: + mcf_edma_cleanup(); + return result; +} + +/** + * mcf_edma_exit - eDMA module exit + */ +static void __exit +mcf_edma_exit(void) +{ + mcf_edma_cleanup(); +} + +#ifdef CONFIG_COLDFIRE_EDMA_MODULE +module_init(mcf_edma_init); +module_exit(mcf_edma_exit); +#else +/* get us in early */ +postcore_initcall(mcf_edma_init); +#endif + +MODULE_DESCRIPTION(MCF_EDMA_DRIVER_INFO); +MODULE_AUTHOR(MCF_EDMA_DRIVER_AUTHOR); +MODULE_LICENSE(MCF_EDMA_DRIVER_LICENSE); --- /dev/null +++ b/drivers/dma/mcf_edma_test.c @@ -0,0 +1,276 @@ +/* + * mcf_edma_test.c - simple test/example module for Coldfire eDMA. + * + * Copyright (C) 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + * Author: Andrey Butok + * + * 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 + * + *************************************************************************** + * Changes: + * v0.001 29 February 2008 Andrey Butok + * Initial Release + * + * NOTE: This module tests eDMA driver performing + * a simple memory to memory transfer with a 32 bit + * source and destination transfer size that generates + * an interrupt when the transfer is complete. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MCF_EDMA_TEST_DRIVER_VERSION "Revision: 0.001" +#define MCF_EDMA_TEST_DRIVER_AUTHOR \ + "Freescale Semiconductor Inc, Andrey Butok" +#define MCF_EDMA_TEST_DRIVER_DESC \ + "Simple testing module for Coldfire eDMA " +#define MCF_EDMA_TEST_DRIVER_INFO \ + MCF_EDMA_TEST_DRIVER_VERSION " " MCF_EDMA_TEST_DRIVER_DESC +#define MCF_EDMA_TEST_DRIVER_LICENSE "GPL" +#define MCF_EDMA_TEST_DRIVER_NAME "mcf_edma_test" + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +#define TEST_HALFIRQ + +/* Global variable used to signal main process when interrupt is recognized */ +static volatile int mcf_edma_test_interrupt; +volatile int *mcf_edma_test_interrupt_p = + (volatile int *) &mcf_edma_test_interrupt; + +/********************************************************************/ +static irqreturn_t +mcf_edma_test_handler(int channel, void *dev_id) +{ + int done = mcf_edma_check_done(channel); + + /* Clear interrupt flag */ + mcf_edma_confirm_interrupt_handled(channel); + + if (done) { + printk(KERN_INFO "DMA Finished\n"); + + /* Set interrupt status flag to TRUE */ + mcf_edma_test_interrupt = TRUE; + } else { + printk(KERN_INFO "DMA Halfway Done\n"); + + /* restart DMA. */ + mcf_edma_confirm_halfirq(channel); + } + + return IRQ_HANDLED; +} + +static void +mcf_edma_test_error_handler(int channel, void *dev_id) +{ + printk(KERN_INFO "DMA ERROR: Channel = %d\n", channel); + printk(KERN_INFO " EDMA_ES = 0x%lx\n", (MCF_EDMA_ES)); + mcf_edma_dump_channel(channel); +} + +/********************************************************************/ + +int +mcf_edma_test_block_compare(u8 *block1, u8 *block2, u32 size) +{ + u32 i; + + for (i = 0; i < (size); i++) { + if ((*(u8 *) (block1 + i)) != (*(u8 *) (block2 + i))) { + printk(KERN_INFO "Data Mismatch index=0x%x len=0x%x " + "block1=0x%p block2=0x%p\n", + i, size, block1, block2); + return FALSE; + } + } + + return TRUE; +} + +/********************************************************************/ + +void +mcf_edma_test_run(void) +{ + u16 byte_count; + u32 i, j; + u8 *start_address; + u8 *dest_address; + u32 test_data; + int channel; + u32 allocated_channels_low = 0; + u32 allocated_channels_high = 0; + + printk(KERN_INFO "\n===============================================\n"); + printk(KERN_INFO "\nStarting eDMA transfer test!\n"); + + /* Initialize test variables */ + byte_count = 0x2000; + test_data = 0xA5A5A5A5; + + /* DMA buffer must be from GFP_DMA zone, so it will not be cached */ + start_address = kmalloc(byte_count, GFP_DMA); + if (start_address == NULL) { + printk(KERN_INFO MCF_EDMA_TEST_DRIVER_NAME + ": failed to allocate DMA[%d] buffer\n", byte_count); + goto err_out; + } + dest_address = kmalloc(byte_count, /*GFP_KERNEL*/GFP_DMA); + if (dest_address == NULL) { + printk(KERN_INFO MCF_EDMA_TEST_DRIVER_NAME + ": failed to allocate DMA[%d] buffer\n", byte_count); + goto err_free_mem; + } + + /* Test all automatically allocated DMA channels. The test data is + * complemented at the end of the loop, so that the testData value + * isn't the same twice in a row */ + for (i = 0; i < MCF_EDMA_CHANNELS; i++) { + /* request eDMA channel */ + channel = mcf_edma_request_channel(MCF_EDMA_CHANNEL_ANY, + mcf_edma_test_handler, + mcf_edma_test_error_handler, + 0x6, + NULL, + NULL, + MCF_EDMA_TEST_DRIVER_NAME); + if (channel < 0) + goto test_end; + + + if (channel >= 0 && channel < 32) + allocated_channels_low |= (1 << channel); + else if (channel >= 32 && channel < 64) + allocated_channels_high |= (1 << (channel - 32)); + + /* Initialize data for DMA to move */ + for (j = 0; j < byte_count; j = j + 4) { + *((u32 *) (start_address + j)) = test_data; + *((u32 *) (dest_address + j)) = ~test_data; + } + + /* Clear interrupt status indicator */ + mcf_edma_test_interrupt = FALSE; + + /* Configure DMA Channel TCD */ +#ifndef TEST_HALFIRQ + /* regular irq on completion */ + mcf_edma_set_tcd_params(channel, + (u32)virt_to_phys(start_address), + (u32)virt_to_phys(dest_address), + (0 | MCF_EDMA_TCD_ATTR_SSIZE_32BIT | + MCF_EDMA_TCD_ATTR_DSIZE_32BIT), 0x04, + byte_count, 0x0, 1, 1, 0x04, 0x0, 0x1, + 0x0); +#else + /* half completion irq */ + mcf_edma_set_tcd_params_halfirq(channel, + (u32)virt_to_phys(start_address), + (u32)virt_to_phys(dest_address), + (MCF_EDMA_TCD_ATTR_SSIZE_32BIT | + MCF_EDMA_TCD_ATTR_DSIZE_32BIT), + 0x04, /* soff */ + byte_count/2, /* bytes/loop */ + 0x0, /* slast */ + 2, /* loop count */ + 0x04, /* doff */ + 0x0, /* dlast_sga */ + 0x0); /* req dis */ +#endif + + printk(KERN_INFO "DMA Channel %d Bytes = 0x%x\n", + channel, byte_count); + /* Start DMA. */ + mcf_edma_start_transfer(channel); + + printk(KERN_INFO "DMA channel %d started.\n", channel); + + /* Wait for DMA to complete */ + while (!*mcf_edma_test_interrupt_p) + ; + + /* Test data */ + if (mcf_edma_test_block_compare + (start_address, dest_address, byte_count)) + printk(KERN_INFO "Data are moved correctly.\n"); + else + printk(KERN_INFO "ERROR!!! Data error!\n"); + + printk(KERN_INFO "DMA channel %d test complete.\n", channel); + printk(KERN_INFO "-------------------------------\n"); + + /* Complement test data so next channel test does not + * use same values */ + test_data = ~test_data; + } + +test_end: + printk(KERN_INFO "All tests are complete\n\n"); + printk(KERN_INFO + "It has been automatically allocated %d eDMA channels:\n", i); + for (i = 0; i < MCF_EDMA_CHANNELS; i++) { + if ((allocated_channels_low & (1 << i)) || + (allocated_channels_high & (1 << (i - 32)))) { + printk(KERN_INFO "%d,\n", i); + mcf_edma_free_channel(i, NULL); + } + } + printk(KERN_INFO "===============================================\n\n"); + + kfree(dest_address); +err_free_mem: + kfree(start_address); +err_out: + return; +} + +/********************************************************************/ + +static int __init +mcf_edma_test_init(void) +{ + mcf_edma_test_run(); + + /* We intentionaly return -EAGAIN to prevent keeping + * the module. It does all its work from init() + * and doesn't offer any runtime functionality */ + return -EAGAIN; +} + +static void __exit +mcf_edma_test_exit(void) +{ +} + +module_init(mcf_edma_test_init); +module_exit(mcf_edma_test_exit); + +MODULE_DESCRIPTION(MCF_EDMA_TEST_DRIVER_INFO); +MODULE_AUTHOR(MCF_EDMA_TEST_DRIVER_AUTHOR); +MODULE_LICENSE(MCF_EDMA_TEST_DRIVER_LICENSE);