From bc755a3b8859e7307a8b10f39ca4cb6401c51987 Mon Sep 17 00:00:00 2001 From: Kurt Mahan Date: Tue, 27 Nov 2007 14:39:37 -0700 Subject: [PATCH] Add M5445x SPI support. LTIBName: m5445x-spi Signed-off-by: Kurt Mahan --- arch/m68k/configs/m54455evb_defconfig | 24 +- drivers/spi/Kconfig | 36 + drivers/spi/Makefile | 4 + drivers/spi/coldfire_edma.c | 358 ++++++++ drivers/spi/spi-m5445x.c | 156 ++++ drivers/spi/spi_coldfire.c | 1552 +++++++++++++++++++++++++++++++++ drivers/spi/ssi_audio.c | 906 +++++++++++++++++++ include/asm-m68k/coldfire_edma.h | 101 ++- include/linux/spi/mcfqspi.h | 80 ++ 9 files changed, 3196 insertions(+), 21 deletions(-) create mode 100644 drivers/spi/coldfire_edma.c create mode 100644 drivers/spi/spi-m5445x.c create mode 100644 drivers/spi/spi_coldfire.c create mode 100644 drivers/spi/ssi_audio.c create mode 100644 include/linux/spi/mcfqspi.h --- a/arch/m68k/configs/m54455evb_defconfig +++ b/arch/m68k/configs/m54455evb_defconfig @@ -321,6 +321,8 @@ CONFIG_MTD_PHYSMAP_BANKWIDTH=1 # # Self-contained MTD device drivers # +# CONFIG_MTD_DATAFLASH is not set +# CONFIG_MTD_M25P80 is not set # CONFIG_MTD_SLRAM is not set # CONFIG_MTD_PHRAM is not set # CONFIG_MTD_MTDRAM is not set @@ -497,8 +499,26 @@ CONFIG_UNIX98_PTYS=y # # SPI support # -# CONFIG_SPI is not set -# CONFIG_SPI_MASTER is not set +CONFIG_SPI=y +# CONFIG_SPI_DEBUG is not set +CONFIG_COLDFIRE_EDMA=y +CONFIG_SPI_MASTER=y + +# +# SPI Master Controller Drivers +# +# CONFIG_SPI_BITBANG is not set +CONFIG_SPI_COLDFIRE=y +CONFIG_SPI_COLDFIRE_DSPI_EDMA=y + +# +# SPI Protocol Masters +# +# CONFIG_SPI_AT25 is not set +# CONFIG_SPI_SPIDEV is not set +# CONFIG_SPI_TLE62X0 is not set +CONFIG_SPI_COLDFIRE_SSI_AUDIO=y +# CONFIG_SSIAUDIO_USE_EDMA is not set # CONFIG_W1 is not set # CONFIG_POWER_SUPPLY is not set # CONFIG_HWMON is not set --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -35,6 +35,15 @@ config SPI_DEBUG Say "yes" to enable debug messaging (like dev_dbg and pr_debug), sysfs, and debugfs support in SPI controller and protocol drivers. +config COLDFIRE_EDMA + tristate "Coldfire eDMA" + depends on COLDFIRE && EXPERIMENTAL + help + Support for Coldfire eDMA controller. Required for example + by SSI audio device driver. + + + # # MASTER side ... talking to discrete SPI slave chips including microcontrollers # @@ -113,6 +122,21 @@ config SPI_GPIO If unsure, say N. +config SPI_COLDFIRE + tristate "Coldfire QSPI/DSPI SPI Master" + depends on SPI_MASTER && COLDFIRE && EXPERIMENTAL + help + SPI driver for Freescale Coldfire QSPI module in master mode. + Tested with the 5282 processor, but should also work with other + Coldfire variants. + +config SPI_COLDFIRE_DSPI_EDMA + boolean "Coldfire DSPI master driver uses eDMA" + depends on SPI_MASTER && COLDFIRE && SPI_COLDFIRE && EXPERIMENTAL && COLDFIRE_EDMA + default n + help + Say "yes" if you want DSPI master driver to use eDMA for transfers. + config SPI_IMX tristate "Freescale iMX SPI controller" depends on SPI_MASTER && ARCH_IMX && EXPERIMENTAL @@ -255,6 +279,18 @@ config SPI_TLE62X0 # # Add new SPI protocol masters in alphabetical order above this line # +config SPI_COLDFIRE_SSI_AUDIO + tristate "Coldfire SSI AUDIO" + depends on SPI_MASTER && SPI_COLDFIRE && EXPERIMENTAL + help + SSI audio device driver + +config SSIAUDIO_USE_EDMA + boolean "Coldfire DSPI master driver uses eDMA" + default y + depends on EXPERIMENTAL && COLDFIRE_EDMA && SPI_COLDFIRE_SSI_AUDIO + help + Say "yes" if you want SSI audio driver to use eDMA for SSI transfers. # (slave support would go here) --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -6,6 +6,8 @@ ifeq ($(CONFIG_SPI_DEBUG),y) EXTRA_CFLAGS += -DDEBUG endif +obj-$(CONFIG_COLDFIRE_EDMA) += coldfire_edma.o + # small core, mostly translating board-specific # config declarations into driver model code obj-$(CONFIG_SPI_MASTER) += spi.o @@ -16,6 +18,7 @@ obj-$(CONFIG_SPI_BFIN) += spi_bfin5xx. obj-$(CONFIG_SPI_BITBANG) += spi_bitbang.o obj-$(CONFIG_SPI_AU1550) += au1550_spi.o obj-$(CONFIG_SPI_BUTTERFLY) += spi_butterfly.o +obj-$(CONFIG_SPI_COLDFIRE) += spi_coldfire.o spi-m5445x.o obj-$(CONFIG_SPI_GPIO) += spi_gpio.o obj-$(CONFIG_SPI_IMX) += spi_imx.o obj-$(CONFIG_SPI_LM70_LLP) += spi_lm70llp.o @@ -35,6 +38,7 @@ obj-$(CONFIG_SPI_SH_SCI) += spi_sh_sci. obj-$(CONFIG_SPI_AT25) += at25.o obj-$(CONFIG_SPI_SPIDEV) += spidev.o obj-$(CONFIG_SPI_TLE62X0) += tle62x0.o +obj-$(CONFIG_SPI_COLDFIRE_SSI_AUDIO) += ssi_audio.o # ... add above this line ... # SPI slave controller drivers (upstream link) --- /dev/null +++ b/drivers/spi/coldfire_edma.c @@ -0,0 +1,358 @@ +/* + * + * coldfire_edma.c - eDMA driver for Coldfire MCF5445x + * + * Yaroslav Vinogradov yaroslav.vinogradov@freescale.com + * + * Copyright Freescale Semiconductor, Inc. 2007 + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* callback handler data for each TCD */ +struct edma_isr_record { + edma_irq_handler irq_handler; /* interrupt handler */ + edma_error_handler error_handler; /* error interrupt handler */ + void* dev; /* device used for the channel */ + int allocated; /* busy flag */ + spinlock_t *lock; /* spin lock (if needs to be locked in interrupt) */ + const char* device_id; /* device id string, used in proc file system */ +}; + +/* device structure */ +struct coldfire_edma_dev { + struct cdev cdev; /* character device */ + struct edma_isr_record dma_interrupt_handlers[EDMA_CHANNELS]; /* channel handlers */ +}; + +/* allocated major device number */ +static int coldfire_dma_major; +/* device driver structure */ +static struct coldfire_edma_dev* devp = NULL; + +/* device driver file operations */ +struct file_operations coldfire_edma_fops = { + .owner = THIS_MODULE, +}; + +/* eDMA channel interrupt handler */ +static int dmaisr(int irq, void *dev_id) +{ + int channel = irq - EDMA_INT_CONTROLLER_BASE - EDMA_INT_CHANNEL_BASE; + int result = IRQ_HANDLED; + + if (devp!=NULL && devp->dma_interrupt_handlers[channel].lock) { + spin_lock(devp->dma_interrupt_handlers[channel].lock); + } + + if (devp!=NULL && devp->dma_interrupt_handlers[channel].irq_handler) { + result = devp->dma_interrupt_handlers[channel].irq_handler(channel, + devp->dma_interrupt_handlers[channel].dev); + } else { + confirm_edma_interrupt_handled(channel); + printk(EDMA_DRIVER_NAME ": No handler for DMA channel %d\n", channel); + } + + if (devp!=NULL && devp->dma_interrupt_handlers[channel].lock) { + spin_unlock(devp->dma_interrupt_handlers[channel].lock); + } + + return result; +} + +/* eDMA error interrupt handler */ +static int dma_error_isr(int irq, void* dev_id) +{ + u16 err; + int i; + + err = MCF_EDMA_ERR; + for (i=0;idma_interrupt_handlers[i].error_handler) { + devp->dma_interrupt_handlers[i].error_handler(i, devp->dma_interrupt_handlers[i].dev); + } else { + printk(KERN_WARNING EDMA_DRIVER_NAME ": DMA error on channel %d\n", i); + } + } + } + + MCF_EDMA_CERR = MCF_EDMA_CERR_CAER; + return IRQ_HANDLED; +} + +/* sets channel parameters */ +void set_edma_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) +{ + + if (channel<0 || channel>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); + /* 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; + } + +} +EXPORT_SYMBOL(set_edma_params); + +/* init eDMA controller */ +void init_edma(void) +{ + MCF_EDMA_CR = 0; +} +EXPORT_SYMBOL(init_edma); + +/* request eDMA channel */ +int request_edma_channel(int channel, + edma_irq_handler handler, + edma_error_handler error_handler, + void* dev, + spinlock_t *lock, + const char* device_id ) +{ + if (devp!=NULL && channel>=0 && channel<=EDMA_CHANNELS) { + if (devp->dma_interrupt_handlers[channel].allocated) { + return -EBUSY; + } + devp->dma_interrupt_handlers[channel].allocated = 1; + devp->dma_interrupt_handlers[channel].irq_handler = handler; + devp->dma_interrupt_handlers[channel].error_handler = error_handler; + devp->dma_interrupt_handlers[channel].dev = dev; + devp->dma_interrupt_handlers[channel].lock = lock; + devp->dma_interrupt_handlers[channel].device_id = device_id; + return 0; + } + return -EINVAL; +} +EXPORT_SYMBOL(request_edma_channel); + +/* free eDMA channel */ +int free_edma_channel(int channel, void* dev) +{ + if (devp!=NULL && channel>=0 && channel<=EDMA_CHANNELS) { + if (devp->dma_interrupt_handlers[channel].allocated) { + if (devp->dma_interrupt_handlers[channel].dev != dev) { + return -EBUSY; + } + devp->dma_interrupt_handlers[channel].allocated = 0; + devp->dma_interrupt_handlers[channel].dev = NULL; + devp->dma_interrupt_handlers[channel].irq_handler = NULL; + devp->dma_interrupt_handlers[channel].error_handler = NULL; + devp->dma_interrupt_handlers[channel].lock = NULL; + } + return 0; + } + return -EINVAL; +} +EXPORT_SYMBOL(free_edma_channel); + +/* clean-up device driver allocated resources */ +static void coldfire_edma_cleanup(void) +{ + dev_t devno; + int i; + + /* free interrupts/memory */ + if (devp) { + for (i=0;icdev); + kfree(devp); + } + + /* unregister character device */ + devno = MKDEV(coldfire_dma_major, 0); + unregister_chrdev_region(devno, 1); +} + +#ifdef CONFIG_PROC_FS +/* proc file system support */ + +#define FREE_CHANNEL "free" +#define DEVICE_UNKNOWN "device unknown" + +static int proc_edma_show(struct seq_file *m, void *v) +{ + int i; + + if (devp==NULL) return 0; + + for (i = 0 ; i < EDMA_CHANNELS ; i++) { + if (devp->dma_interrupt_handlers[i].allocated) { + if (devp->dma_interrupt_handlers[i].device_id) + seq_printf(m, "%2d: %s\n", i, 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; +} + +static int proc_edma_open(struct inode *inode, struct file *file) +{ + return single_open(file, proc_edma_show, NULL); +} + +static const struct file_operations proc_edma_operations = { + .open = proc_edma_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init proc_edma_init(void) +{ + struct proc_dir_entry *e; + + e = create_proc_entry("edma", 0, NULL); + if (e) + e->proc_fops = &proc_edma_operations; + + return 0; +} + +#endif + +/* initializes device driver */ +static int __init coldfire_edma_init(void) +{ + dev_t dev; + int result; + int i; + + /* allocate free major number */ + result = alloc_chrdev_region(&dev, DMA_DEV_MINOR, 1, EDMA_DRIVER_NAME); + if (result<0) { + printk(KERN_WARNING EDMA_DRIVER_NAME": can't get major %d\n", result); + return result; + } + coldfire_dma_major = MAJOR(dev); + + /* allocate device driver structure */ + devp = kmalloc(sizeof(struct coldfire_edma_dev), GFP_KERNEL); + if (!devp) { + result = -ENOMEM; + goto fail; + } + + /* init handlers (no handlers for beggining) */ + for (i=0;idma_interrupt_handlers[i].irq_handler = NULL; + devp->dma_interrupt_handlers[i].error_handler = NULL; + devp->dma_interrupt_handlers[i].dev = NULL; + devp->dma_interrupt_handlers[i].allocated = 0; + devp->dma_interrupt_handlers[i].lock = NULL; + devp->dma_interrupt_handlers[i].device_id = NULL; + } + + /* register char device */ + cdev_init(&devp->cdev, &coldfire_edma_fops); + devp->cdev.owner = THIS_MODULE; + devp->cdev.ops = &coldfire_edma_fops; + result = cdev_add(&devp->cdev, dev, 1); + if (result) { + printk(KERN_NOTICE EDMA_DRIVER_NAME": Error %d adding coldfire-dma device\n", result); + result = -ENODEV; + goto fail; + } + + /* request/enable irq for each eDMA channel */ + for (i=0;i +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define SPI_NUM_CHIPSELECTS 0x10 +#define SPI_PAR_VAL (0 | MCF_GPIO_PAR_DSPI_PCS5_PCS5 | MCF_GPIO_PAR_DSPI_PCS2_PCS2 \ + | MCF_GPIO_PAR_DSPI_PCS1_PCS1 | MCF_GPIO_PAR_DSPI_PCS0_PCS0 | MCF_GPIO_PAR_DSPI_SIN_SIN \ + | MCF_GPIO_PAR_DSPI_SOUT_SOUT | MCF_GPIO_PAR_DSPI_SCK_SCK) + +#define MCF5445x_DSPI_IRQ_SOURCE (31) +#define MCF5445x_DSPI_IRQ_VECTOR (64 + MCF5445x_DSPI_IRQ_SOURCE) + +#define MCF5445x_DSPI_PAR (0xFC0A4063) +#define MCF5445x_DSPI_MCR (0xFC05C000) +#define MCF5445x_INTC0_ICR (0xFC048040) +#define MCF5445x_INTC0_IMRL (0xFC04800C) + + +#define M5445x_AUDIO_IRQ_SOURCE 49 +#define M5445x_AUDIO_IRQ_VECTOR (128+M5445x_AUDIO_IRQ_SOURCE) +#define M5445x_AUDIO_IRQ_LEVEL 4 + +void coldfire_qspi_cs_control(u8 cs, u8 command) +{ +} + +#if defined(CONFIG_SPI_COLDFIRE_SSI_AUDIO) +static struct coldfire_spi_chip ssi_audio_chip_info = { + .mode = SPI_MODE_0, + .bits_per_word = 16, + .del_cs_to_clk = 16, + .del_after_trans = 16, + .void_write_data = 0 +}; + +#endif + +static struct spi_board_info spi_board_info[] = { + +#if defined(CONFIG_SPI_COLDFIRE_SSI_AUDIO) + { + .modalias = "ssi_audio", + .max_speed_hz = 300000, + .bus_num = 1, + .chip_select = 5, + .irq = M5445x_AUDIO_IRQ_VECTOR, + .platform_data = NULL, + .controller_data = &ssi_audio_chip_info + } +#endif + +}; + +static struct coldfire_spi_master coldfire_master_info = { + .bus_num = 1, + .num_chipselect = SPI_NUM_CHIPSELECTS, + .irq_source = MCF5445x_DSPI_IRQ_SOURCE, + .irq_vector = MCF5445x_DSPI_IRQ_VECTOR, + .irq_mask = (0x01 << MCF5445x_DSPI_IRQ_SOURCE), + .irq_lp = 0x2, /* Level */ + .par_val = SPI_PAR_VAL, +// .par_val16 = SPI_PAR_VAL, + .cs_control = coldfire_qspi_cs_control, +}; + +static struct resource coldfire_spi_resources[] = { + [0] = { + .name = "qspi-par", + .start = MCF5445x_DSPI_PAR, + .end = MCF5445x_DSPI_PAR, + .flags = IORESOURCE_MEM + }, + + [1] = { + .name = "qspi-module", + .start = MCF5445x_DSPI_MCR, + .end = MCF5445x_DSPI_MCR + 0xB8, + .flags = IORESOURCE_MEM + }, + + [2] = { + .name = "qspi-int-level", + .start = MCF5445x_INTC0_ICR + MCF5445x_DSPI_IRQ_SOURCE, + .end = MCF5445x_INTC0_ICR + MCF5445x_DSPI_IRQ_SOURCE, + .flags = IORESOURCE_MEM + }, + + [3] = { + .name = "qspi-int-mask", + .start = MCF5445x_INTC0_IMRL, + .end = MCF5445x_INTC0_IMRL, + .flags = IORESOURCE_MEM + } +}; + +static struct platform_device coldfire_spi = { + .name = "spi_coldfire", //"coldfire-qspi", + .id = -1, + .resource = coldfire_spi_resources, + .num_resources = ARRAY_SIZE(coldfire_spi_resources), + .dev = { + .platform_data = &coldfire_master_info, + } +}; + +static int __init spi_dev_init(void) +{ + int retval = 0; + + retval = platform_device_register(&coldfire_spi); + + if (retval < 0) { + printk(KERN_ERR "SPI-m5445x: platform_device_register failed with code=%d\n", retval); + goto out; + } + + if (ARRAY_SIZE(spi_board_info)) + retval = spi_register_board_info(spi_board_info, ARRAY_SIZE(spi_board_info)); + + +out: + return retval; +} + +arch_initcall(spi_dev_init); --- /dev/null +++ b/drivers/spi/spi_coldfire.c @@ -0,0 +1,1552 @@ +/****************************************************************************/ + +/* + * spi_coldfire.c - Master QSPI/DSPI controller for the ColdFire processors + * + * (C) Copyright 2005, Intec Automation, + * Mike Lavender (mike@steroidmicros) + * + * (C) Copyright 2007, Freescale Inc, + * Yaroslav Vinogradov (yaroslav.vinogradov@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. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ +/* ------------------------------------------------------------------------- */ + + +/****************************************************************************/ + +/* + * Includes + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#if defined(CONFIG_M54455) + #define SPI_DSPI + #if defined(CONFIG_SPI_COLDFIRE_DSPI_EDMA) + #define SPI_DSPI_EDMA + #ifdef CONFIG_MMU + #define SPI_USE_MMU + #endif + #endif +#endif + +#ifdef SPI_DSPI +#include + + +#endif + +#if defined(SPI_DSPI_EDMA) + +/* edma buffer size in transfer units (32bits) */ +#define EDMA_BUFFER_SIZE (PAGE_SIZE/4) +#define EDMA_BUFSIZE_KMALLOC (EDMA_BUFFER_SIZE*4) + +#define DSPI_DMA_RX_TCD 12 +#define DSPI_DMA_TX_TCD 13 + + +#include +#include +#endif + + +MODULE_AUTHOR("Mike Lavender"); +MODULE_DESCRIPTION("ColdFire QSPI Contoller"); +MODULE_LICENSE("GPL"); + +#define DRIVER_NAME "Coldfire QSPI/DSPI" + +/****************************************************************************/ + +/* + * Local constants and macros + */ + +#define QSPI_RAM_SIZE 0x10 /* 16 word table */ + +#define QSPI_TRANSMIT_RAM 0x00 +#define QSPI_RECEIVE_RAM 0x10 +#define QSPI_COMMAND_RAM 0x20 + +#define QSPI_COMMAND 0x7000 /* 15: X = Continuous CS + * 14: 1 = Get BITSE from QMR[BITS] + * 13: 1 = Get DT from QDLYR[DTL] + * 12: 1 = Get DSK from QDLYR[QCD] + * 8-11: XXXX = next 4 bytes for CS + * 0-7: 0000 0000 Reserved + */ + +#define QIR_WCEF 0x0008 /* write collison */ +#define QIR_ABRT 0x0004 /* abort */ +#define QIR_SPIF 0x0001 /* finished */ + +#define QIR_WCEFE 0x0800 +#define QIR_ABRTE 0x0400 +#define QIR_SPIFE 0x0100 + +#define QIR_WCEFB 0x8000 +#define QIR_ABRTB 0x4000 +#define QIR_ABRTL 0x1000 + +#define QMR_BITS 0x3C00 +#define QMR_BITS_8 0x2000 + +#define QCR_CONT 0x8000 + +#define QDLYR_SPE 0x8000 + +#define QWR_ENDQP_MASK 0x0F00 +#define QWR_CSIV 0x1000 /* 1 = active low chip selects */ + + +#define START_STATE ((void*)0) +#define RUNNING_STATE ((void*)1) +#define DONE_STATE ((void*)2) +#define ERROR_STATE ((void*)-1) + +#define QUEUE_RUNNING 0 +#define QUEUE_STOPPED 1 + +/****************************************************************************/ + +/* + * Local Data Structures + */ + +struct transfer_state { + u32 index; + u32 len; + void *tx; + void *tx_end; + void *rx; + void *rx_end; + char flags; +#define TRAN_STATE_RX_VOID 0x01 +#define TRAN_STATE_TX_VOID 0x02 +#define TRAN_STATE_WORD_ODD_NUM 0x04 + u8 cs; + u16 void_write_data; + unsigned cs_change:1; +}; + +typedef struct { + unsigned master:1; + unsigned dohie:1; + unsigned bits:4; + unsigned cpol:1; + unsigned cpha:1; + unsigned baud:8; +} QMR; + +typedef struct { + unsigned spe:1; + unsigned qcd:7; + unsigned dtl:8; +} QDLYR; + +typedef struct { + unsigned halt:1; + unsigned wren:1; + unsigned wrto:1; + unsigned csiv:1; + unsigned endqp:4; + unsigned cptqp:4; + unsigned newqp:4; +} QWR; + + +typedef struct { + unsigned master:1; + unsigned cont_scke:1; + unsigned dconf:2; + unsigned frz:1; + unsigned mtfe:1; + unsigned pcsse:1; + unsigned rooe:1; + unsigned pcsis:8; + unsigned reserved15:1; + unsigned mdis:1; + unsigned dis_tx:1; + unsigned dis_rxf:1; + unsigned clr_tx:1; + unsigned clr_rxf:1; + unsigned smpl_pt:2; + unsigned reserved71:7; + unsigned halt:1; +} DSPI_MCR; + +typedef struct { + unsigned dbr:1; + unsigned fmsz:4; + unsigned cpol:1; + unsigned cpha:1; + unsigned lsbfe:1; + unsigned pcssck:2; + unsigned pasc:2; + unsigned pdt:2; + unsigned pbr:2; + unsigned cssck:4; + unsigned asc:4; + unsigned dt:4; + unsigned br:4; +} DSPI_CTAR; + +struct chip_data { +#if defined(SPI_DSPI) + /* dspi data */ + union { + u32 mcr_val; + DSPI_MCR mcr; + }; + union { + u32 ctar_val; + DSPI_CTAR ctar; + }; +#else + union { + u16 qmr_val; + QMR qmr; + }; + union { + u16 qdlyr_val; + QDLYR qdlyr; + }; + union { + u16 qwr_val; + QWR qwr; + }; +#endif + + u16 void_write_data; +}; + + +struct driver_data { + /* Driver model hookup */ + struct platform_device *pdev; + + /* SPI framework hookup */ + struct spi_master *master; + + /* Driver message queue */ + struct workqueue_struct *workqueue; + struct work_struct pump_messages; + spinlock_t lock; + struct list_head queue; + int busy; + int run; + + /* Message Transfer pump */ + struct tasklet_struct pump_transfers; + + /* Current message transfer state info */ + struct spi_message* cur_msg; + struct spi_transfer* cur_transfer; + struct chip_data *cur_chip; + size_t len; + void *tx; + void *tx_end; + void *rx; + void *rx_end; + char flags; +#define TRAN_STATE_RX_VOID 0x01 +#define TRAN_STATE_TX_VOID 0x02 +#define TRAN_STATE_WORD_ODD_NUM 0x04 + u8 cs; + u16 void_write_data; + unsigned cs_change:1; + + u32 trans_cnt; + u32 wce_cnt; + u32 abrt_cnt; +#if defined(SPI_DSPI) + u32 *mcr; /* DSPI MCR register */ + u32 *ctar; /* DSPI CTAR register */ + u32 *dspi_dtfr; /* DSPI DTFR register */ + u32 *dspi_drfr; /* DSPI DRFR register */ + u32 *dspi_rser; /* DSPI RSER register */ + u32 *dspi_sr; /* DSPI status register */ + u8 dspi_ctas; /* DSPI CTAS value*/ + +#if defined(SPI_DSPI_EDMA) + void* edma_tx_buf; + void* edma_rx_buf; +#endif + + +#else + u16 *qmr; /* QSPI mode register */ + u16 *qdlyr; /* QSPI delay register */ + u16 *qwr; /* QSPI wrap register */ + u16 *qir; /* QSPI interrupt register */ + u16 *qar; /* QSPI address register */ + u16 *qdr; /* QSPI data register */ + u16 *qcr; /* QSPI command register */ +#endif + u8 *par; /* Pin assignment register */ + u8 *int_icr; /* Interrupt level and priority register */ + u32 *int_mr; /* Interrupt mask register */ + void (*cs_control)(u8 cs, u8 command); +}; + +#define DSPI_CS(cs) ((1<<(cs))<<16) + + +/****************************************************************************/ + +/* + * SPI local functions + */ + +//#define SPI_COLDFIRE_DEBUG + +static void *next_transfer(struct driver_data *drv_data) +{ + struct spi_message *msg = drv_data->cur_msg; + struct spi_transfer *trans = drv_data->cur_transfer; + + /* Move to next transfer */ + if (trans->transfer_list.next != &msg->transfers) { + drv_data->cur_transfer = + list_entry(trans->transfer_list.next, + struct spi_transfer, + transfer_list); + return RUNNING_STATE; + } else + return DONE_STATE; +} + + +#define DSPI_BITS MCF_DSPI_DCTAR_FMSZ(15) +#define DSPI_BITS_16 MCF_DSPI_DCTAR_FMSZ(15) +#define DSPI_BITS_8 MCF_DSPI_DCTAR_FMSZ(7) +#define DSPI_FIFO_SIZE 16 + +static inline int is_word_transfer(struct driver_data *drv_data) +{ +#if defined(SPI_DSPI) + return ((*drv_data->ctar & DSPI_BITS_16) == DSPI_BITS_8) ? 0 : 1; +#else + return ((*drv_data->qmr & QMR_BITS) == QMR_BITS_8) ? 0 : 1; +#endif +} + +static void inline set_8bit_transfer_mode(struct driver_data *drv_data) +{ +#if defined(SPI_DSPI) + *drv_data->ctar |= (*drv_data->ctar & ~DSPI_BITS) | DSPI_BITS_8; +#else + *drv_data->qmr |= (*drv_data->qmr & ~QMR_BITS) | QMR_BITS_8; +#endif +} + +static void inline set_16bit_transfer_mode(struct driver_data *drv_data) +{ +#if defined(SPI_DSPI) + *drv_data->ctar |= (*drv_data->ctar & ~DSPI_BITS) | DSPI_BITS_16; +#else + *drv_data->qmr |= (*drv_data->qmr & ~QMR_BITS); +#endif +} + +static int write(struct driver_data *drv_data) +{ + int tx_count = 0; +#ifndef SPI_DSPI + int cmd_count = 0; +#endif + int tx_word; + +#if defined(SPI_DSPI) + +#if defined(SPI_DSPI_EDMA) + u32* edma_wr; +#endif + + u16 d16; + u8 d8; + u32 dspi_pushr; + int first = 1; +#endif + + tx_word = is_word_transfer(drv_data); + + // If we are in word mode, but only have a single byte to transfer + // then switch to byte mode temporarily. Will switch back at the + // end of the transfer. + if (tx_word && ((drv_data->tx_end - drv_data->tx) == 1)) { + drv_data->flags |= TRAN_STATE_WORD_ODD_NUM; + set_8bit_transfer_mode(drv_data); + tx_word = 0; + } + + +#if defined(SPI_DSPI) + +#if defined(SPI_DSPI_EDMA) + edma_wr = (u32*)(drv_data->edma_tx_buf); +#endif + + +#if defined(SPI_DSPI_EDMA) + while ((drv_data->tx < drv_data->tx_end) && (tx_count < EDMA_BUFFER_SIZE)) { +#else + while ((drv_data->tx < drv_data->tx_end) && (tx_count < DSPI_FIFO_SIZE)) { +#endif + if (tx_word) { + if ((drv_data->tx_end - drv_data->tx) == 1) + break; + if (!(drv_data->flags & TRAN_STATE_TX_VOID)) { + d16 = *(u16 *)drv_data->tx; + } else { + d16 = drv_data->void_write_data; + } + + dspi_pushr = MCF_DSPI_DTFR_TXDATA(d16) + | DSPI_CS(drv_data->cs) + | MCF_DSPI_DTFR_CTAS(drv_data->dspi_ctas) + //| MCF_DSPI_DTFR_CONT + ; + + drv_data->tx += 2; + +#if defined(SPI_DSPI_EDMA) + if (drv_data->tx == drv_data->tx_end || tx_count==EDMA_BUFFER_SIZE-1) { +#else + if (drv_data->tx == drv_data->tx_end || tx_count==DSPI_FIFO_SIZE-1) { +#endif + // last transfer in queue + dspi_pushr |= MCF_DSPI_DTFR_EOQ; + if (drv_data->cs_change) { + dspi_pushr &= ~MCF_DSPI_DTFR_CONT; + } + } + + if (first) { + first = 0; + dspi_pushr |= MCF_DSPI_DTFR_CTCNT; // clear counter + } +#if defined(SPI_DSPI_EDMA) + *edma_wr = dspi_pushr; + edma_wr++; +#else + *drv_data->dspi_dtfr = dspi_pushr; + //MCF_DSPI_DTFR = dspi_pushr; +#endif + + + } else { + if (!(drv_data->flags & TRAN_STATE_TX_VOID)) { + d8 = *(u8 *)drv_data->tx; + } else { + d8 = *(u8 *)&drv_data->void_write_data; + } + + dspi_pushr = MCF_DSPI_DTFR_TXDATA(d8) + | DSPI_CS(drv_data->cs) + /* | MCF_DSPI_DTFR_PCS5 | */ + | MCF_DSPI_DTFR_CTAS(drv_data->dspi_ctas) + | MCF_DSPI_DTFR_CONT; + + drv_data->tx++; + + if (drv_data->tx == drv_data->tx_end || tx_count==DSPI_FIFO_SIZE-1) { + // last transfer in queue + dspi_pushr |= MCF_DSPI_DTFR_EOQ; + if (drv_data->cs_change) { + dspi_pushr &= ~MCF_DSPI_DTFR_CONT; + } + } + + if (first) { + first = 0; + dspi_pushr |= MCF_DSPI_DTFR_CTCNT; // clear counter + } + +#if defined(SPI_DSPI_EDMA) + *edma_wr = dspi_pushr; + edma_wr++; +#else + *drv_data->dspi_dtfr = dspi_pushr; + //MCF_DSPI_DTFR = dspi_pushr; +#endif + + } + tx_count++; + } + +#if defined(SPI_DSPI_EDMA) + + if (tx_count>0) { + + // TODO: initiate eDMA transfer + set_edma_params(DSPI_DMA_TX_TCD, +#ifdef SPI_USE_MMU + virt_to_phys(drv_data->edma_tx_buf), +#else + drv_data->edma_tx_buf, +#endif + (u32)drv_data->dspi_dtfr, + MCF_EDMA_TCD_ATTR_SSIZE_32BIT | MCF_EDMA_TCD_ATTR_DSIZE_32BIT, + 4, // soff + 4, // nbytes + 0, // slast + tx_count, // citer + tx_count, // biter + 0, // doff + 0, // dlastsga + 0, // major_int + 1 // disable_req + ); + + set_edma_params(DSPI_DMA_RX_TCD, + (u32)drv_data->dspi_drfr, +#ifdef SPI_USE_MMU + virt_to_phys(drv_data->edma_rx_buf), +#else + drv_data->edma_rx_buf, +#endif + MCF_EDMA_TCD_ATTR_SSIZE_32BIT | MCF_EDMA_TCD_ATTR_DSIZE_32BIT, + 0, // soff + 4, // nbytes + 0, // slast + tx_count, // citer + tx_count, // biter + 4, // doff + 0, // dlastsga + 0, // major_int + 1 // disable_req + ); + + + start_edma_transfer(DSPI_DMA_TX_TCD); // transmit SPI data + start_edma_transfer(DSPI_DMA_RX_TCD); // receive SPI data + } +#endif + +#else + + *drv_data->qar = QSPI_TRANSMIT_RAM; + while ((drv_data->tx < drv_data->tx_end) && (tx_count < QSPI_RAM_SIZE)) { + if (tx_word) { + if ((drv_data->tx_end - drv_data->tx) == 1) + break; + + if (!(drv_data->flags & TRAN_STATE_TX_VOID)) + *drv_data->qdr = *(u16 *)drv_data->tx; + else + *drv_data->qdr = drv_data->void_write_data; + drv_data->tx += 2; + } else { + if (!(drv_data->flags & TRAN_STATE_TX_VOID)) + *drv_data->qdr = *(u8 *)drv_data->tx; + else + *drv_data->qdr = *(u8 *)&drv_data->void_write_data; + drv_data->tx++; + } + tx_count++; + } + + + *drv_data->qar = QSPI_COMMAND_RAM; + while (cmd_count < tx_count) { + u16 qcr = QSPI_COMMAND + | QCR_CONT + | (~((0x01 << drv_data->cs) << 8) & 0x0F00); + + if ( (cmd_count == tx_count - 1) + && (drv_data->tx == drv_data->tx_end) + && (drv_data->cs_change) ) { + qcr &= ~QCR_CONT; + } + *drv_data->qcr = qcr; + cmd_count++; + } + + *drv_data->qwr = (*drv_data->qwr & ~QWR_ENDQP_MASK) | ((cmd_count - 1) << 8); + + /* Fire it up! */ + *drv_data->qdlyr |= QDLYR_SPE; +#endif + + return tx_count; +} + + +static int read(struct driver_data *drv_data) +{ + int rx_count = 0; + int rx_word; +#if defined(SPI_DSPI_EDMA) + u32* rx_edma; +#endif + u16 d; + rx_word = is_word_transfer(drv_data); + +#if defined(SPI_DSPI) + +#if defined(SPI_DSPI_EDMA) + rx_edma = (u32*) drv_data->edma_tx_buf; + while ((drv_data->rx < drv_data->rx_end) && (rx_count < EDMA_BUFFER_SIZE)) { +#else + while ((drv_data->rx < drv_data->rx_end) && (rx_count < DSPI_FIFO_SIZE)) { +#endif + if (rx_word) { + if ((drv_data->rx_end - drv_data->rx) == 1) + break; +#if defined(SPI_DSPI_EDMA) + d = MCF_DSPI_DRFR_RXDATA(*rx_edma); + rx_edma++; +#else + d = MCF_DSPI_DRFR_RXDATA(*drv_data->dspi_drfr); +#endif + + if (!(drv_data->flags & TRAN_STATE_RX_VOID)) + *(u16 *)drv_data->rx = d; + drv_data->rx += 2; + } else { +#if defined(SPI_DSPI_EDMA) + d = MCF_DSPI_DRFR_RXDATA(*rx_edma); + rx_edma++; +#else + d = MCF_DSPI_DRFR_RXDATA(*drv_data->dspi_drfr); +#endif + if (!(drv_data->flags & TRAN_STATE_RX_VOID)) + *(u8 *)drv_data->rx = d; + drv_data->rx++; + } + rx_count++; + } + + +#else + + *drv_data->qar = QSPI_RECEIVE_RAM; + while ((drv_data->rx < drv_data->rx_end) && (rx_count < QSPI_RAM_SIZE)) { + if (rx_word) { + if ((drv_data->rx_end - drv_data->rx) == 1) + break; + + if (!(drv_data->flags & TRAN_STATE_RX_VOID)) + *(u16 *)drv_data->rx = *drv_data->qdr; + drv_data->rx += 2; + } else { + if (!(drv_data->flags & TRAN_STATE_RX_VOID)) + *(u8 *)drv_data->rx = *drv_data->qdr; + drv_data->rx++; + } + rx_count++; + } +#endif + + return rx_count; +} + + +static inline void qspi_setup_chip(struct driver_data *drv_data) +{ + struct chip_data *chip = drv_data->cur_chip; + +#if defined(SPI_DSPI) + + *drv_data->mcr = chip->mcr_val; + + // TODO: remove later + chip->ctar_val = 0x78560118; + + *drv_data->ctar = chip->ctar_val; + *drv_data->dspi_rser = 0 + | MCF_DSPI_DRSER_EOQFE +#if defined(SPI_DSPI_EDMA) + | MCF_DSPI_DRSER_TFFFE + | MCF_DSPI_DRSER_TFFFS +#endif + ; + + +#else + *drv_data->qmr = chip->qmr_val; + *drv_data->qdlyr = chip->qdlyr_val; + *drv_data->qwr = chip->qwr_val; + + /* + * Enable all the interrupts and clear all the flags + */ + *drv_data->qir = (QIR_SPIFE | QIR_ABRTE | QIR_WCEFE) + | (QIR_WCEFB | QIR_ABRTB | QIR_ABRTL) + | (QIR_SPIF | QIR_ABRT | QIR_WCEF); +#endif +} + +#if defined(SPI_DSPI_EDMA) +static int edma_tx_handler(int channel, void* dev) +{ + if (channel == DSPI_DMA_TX_TCD) { + stop_edma_transfer(DSPI_DMA_TX_TCD); + } + return IRQ_HANDLED; +} + +static int edma_rx_handler(int channel, void* dev) +{ + if (channel == DSPI_DMA_RX_TCD) { + stop_edma_transfer(DSPI_DMA_RX_TCD); + } + + return IRQ_HANDLED; +} +#endif + +static irqreturn_t qspi_interrupt(int irq, void *dev_id) +{ + struct driver_data *drv_data = (struct driver_data *)dev_id; + struct spi_message *msg = drv_data->cur_msg; +#if defined(SPI_DSPI) +#if !defined(SPI_DSPI_EDMA) + u32 irq_status = *drv_data->dspi_sr; +#endif +#else + u16 irq_status = *drv_data->qir; +#endif + + /* Clear all flags immediately */ +#if defined(SPI_DSPI) + *drv_data->dspi_sr = MCF_DSPI_DSR_EOQF; +#else + *drv_data->qir |= (QIR_SPIF | QIR_ABRT | QIR_WCEF); +#endif + + if (!drv_data->cur_msg || !drv_data->cur_msg->state) { +#if !defined(SPI_DSPI_EDMA) + /* if eDMA is used it happens some time (at least once)*/ + printk(KERN_ERR "coldfire-qspi: bad message or transfer " + "state in interrupt handler. IRQ status=%x\n", irq_status); +#endif + return IRQ_NONE; + } + +#if !defined(SPI_DSPI) + if (irq_status & QIR_SPIF) { +#endif + /* + * Read the data into the buffer and reload and start + * queue with new data if not finished. If finished + * then setup the next transfer + */ + read(drv_data); + + if (drv_data->rx == drv_data->rx_end) { + /* + * Finished now - fall through and schedule next + * transfer tasklet + */ + if (drv_data->flags & TRAN_STATE_WORD_ODD_NUM) { + //*drv_data->qmr &= ~QMR_BITS; + set_16bit_transfer_mode(drv_data); + } + + msg->state = next_transfer(drv_data); + msg->actual_length += drv_data->len; + } else { + /* not finished yet - keep going */ + write(drv_data); + return IRQ_HANDLED; + } +#if !defined(SPI_DSPI) + } else { + if (irq_status & QIR_WCEF) + drv_data->wce_cnt++; + + if (irq_status & QIR_ABRT) + drv_data->abrt_cnt++; + + msg->state = ERROR_STATE; + } +#endif + + tasklet_schedule(&drv_data->pump_transfers); + + return IRQ_HANDLED; +} + +/* caller already set message->status; dma and pio irqs are blocked */ +static void giveback(struct driver_data *drv_data) +{ + struct spi_transfer* last_transfer; + unsigned long flags; + struct spi_message *msg; + + spin_lock_irqsave(&drv_data->lock, flags); + msg = drv_data->cur_msg; + drv_data->cur_msg = NULL; + drv_data->cur_transfer = NULL; + drv_data->cur_chip = NULL; + queue_work(drv_data->workqueue, &drv_data->pump_messages); + spin_unlock_irqrestore(&drv_data->lock, flags); + + last_transfer = list_entry(msg->transfers.prev, + struct spi_transfer, + transfer_list); + + if (!last_transfer->cs_change) + drv_data->cs_control(drv_data->cs, QSPI_CS_DROP); + + msg->state = NULL; + if (msg->complete) + msg->complete(msg->context); +} + + +static void pump_transfers(unsigned long data) +{ + struct driver_data *drv_data = (struct driver_data *)data; + struct spi_message *message = NULL; + struct spi_transfer *transfer = NULL; + struct spi_transfer *previous = NULL; + struct chip_data *chip = NULL; + unsigned long flags; + + /* Get current state information */ + message = drv_data->cur_msg; + transfer = drv_data->cur_transfer; + chip = drv_data->cur_chip; + + /* Handle for abort */ + if (message->state == ERROR_STATE) { + message->status = -EIO; + giveback(drv_data); + return; + } + + /* Handle end of message */ + if (message->state == DONE_STATE) { + message->status = 0; + giveback(drv_data); + return; + } + + if (message->state == START_STATE) { + qspi_setup_chip(drv_data); + + if (drv_data->cs_control) { + //printk( "m s\n" ); + drv_data->cs_control(message->spi->chip_select, QSPI_CS_ASSERT); + } + } + + /* Delay if requested at end of transfer*/ + if (message->state == RUNNING_STATE) { + previous = list_entry(transfer->transfer_list.prev, + struct spi_transfer, + transfer_list); + + if (drv_data->cs_control && transfer->cs_change) + drv_data->cs_control(message->spi->chip_select, QSPI_CS_DROP); + + if (previous->delay_usecs) + udelay(previous->delay_usecs); + + if (drv_data->cs_control && transfer->cs_change) + drv_data->cs_control(message->spi->chip_select, QSPI_CS_ASSERT); + } + + drv_data->flags = 0; + drv_data->tx = (void *)transfer->tx_buf; + drv_data->tx_end = drv_data->tx + transfer->len; + drv_data->rx = transfer->rx_buf; + drv_data->rx_end = drv_data->rx + transfer->len; + drv_data->len = transfer->len; + if (!drv_data->rx) + drv_data->flags |= TRAN_STATE_RX_VOID; + if (!drv_data->tx) + drv_data->flags |= TRAN_STATE_TX_VOID; + drv_data->cs = message->spi->chip_select; + drv_data->cs_change = transfer->cs_change; + drv_data->void_write_data = chip->void_write_data; + + message->state = RUNNING_STATE; + + /* Go baby, go */ + local_irq_save(flags); + write(drv_data); + local_irq_restore(flags); +} + + +static void pump_messages(struct work_struct * work) +{ + struct driver_data *drv_data; + unsigned long flags; + + drv_data = container_of(work, struct driver_data, pump_messages); + + /* Lock queue and check for queue work */ + spin_lock_irqsave(&drv_data->lock, flags); + if (list_empty(&drv_data->queue) || drv_data->run == QUEUE_STOPPED) { + drv_data->busy = 0; + spin_unlock_irqrestore(&drv_data->lock, flags); + return; + } + + /* Make sure we are not already running a message */ + if (drv_data->cur_msg) { + spin_unlock_irqrestore(&drv_data->lock, flags); + return; + } + + /* Extract head of queue */ + drv_data->cur_msg = list_entry(drv_data->queue.next, + struct spi_message, queue); + list_del_init(&drv_data->cur_msg->queue); + + /* Initial message state*/ + drv_data->cur_msg->state = START_STATE; + drv_data->cur_transfer = list_entry(drv_data->cur_msg->transfers.next, + struct spi_transfer, + transfer_list); + + /* Setup the SPI Registers using the per chip configuration */ + drv_data->cur_chip = spi_get_ctldata(drv_data->cur_msg->spi); + + /* Mark as busy and launch transfers */ + tasklet_schedule(&drv_data->pump_transfers); + + drv_data->busy = 1; + spin_unlock_irqrestore(&drv_data->lock, flags); +} + +/****************************************************************************/ + +/* + * SPI master implementation + */ + +static int transfer(struct spi_device *spi, struct spi_message *msg) +{ + struct driver_data *drv_data = spi_master_get_devdata(spi->master); + unsigned long flags; + + spin_lock_irqsave(&drv_data->lock, flags); + + if (drv_data->run == QUEUE_STOPPED) { + spin_unlock_irqrestore(&drv_data->lock, flags); + return -ESHUTDOWN; + } + + msg->actual_length = 0; + msg->status = -EINPROGRESS; + msg->state = START_STATE; + + list_add_tail(&msg->queue, &drv_data->queue); + + if (drv_data->run == QUEUE_RUNNING && !drv_data->busy) + queue_work(drv_data->workqueue, &drv_data->pump_messages); + + spin_unlock_irqrestore(&drv_data->lock, flags); + + return 0; +} + + +static int setup(struct spi_device *spi) +{ + struct coldfire_spi_chip *chip_info; + struct chip_data *chip; +#ifndef SPI_DSPI + u32 baud_divisor = 255; +#endif + + chip_info = (struct coldfire_spi_chip *)spi->controller_data; + + /* Only alloc on first setup */ + chip = spi_get_ctldata(spi); + if (chip == NULL) { + chip = kcalloc(1, sizeof(struct chip_data), GFP_KERNEL); + if (!chip) + return -ENOMEM; + spi->mode = chip_info->mode; + spi->bits_per_word = chip_info->bits_per_word; + } + +#if defined(SPI_DSPI) + chip->mcr.master = 1; + chip->mcr.cont_scke = 0; + chip->mcr.dconf = 0; + chip->mcr.frz = 0; + chip->mcr.mtfe = 1; + chip->mcr.pcsse = 0; + chip->mcr.rooe = 0; + chip->mcr.pcsis = 0xFF; + chip->mcr.reserved15 = 0; + chip->mcr.mdis = 0; + chip->mcr.dis_tx = 0; + chip->mcr.dis_rxf = 0; + chip->mcr.clr_tx = 1; + chip->mcr.clr_rxf = 1; + chip->mcr.smpl_pt = 0; + chip->mcr.reserved71 = 0; + chip->mcr.halt = 0; + + if ((spi->bits_per_word >= 4) && (spi->bits_per_word <= 16)) { + chip->ctar.fmsz = spi->bits_per_word-1; + } else { + printk(KERN_ERR "coldfire-qspi: invalid wordsize\n"); + kfree(chip); + return -ENODEV; + } + + if (spi->mode & SPI_CPHA) + chip->ctar.cpha = 1; + else + chip->ctar.cpha = 0; + + if (spi->mode & SPI_CPOL) + chip->ctar.cpol = 1; + else + chip->ctar.cpol = 0; + + if (spi->mode & SPI_LSB_FIRST) + chip->ctar.lsbfe = 1; + else + chip->ctar.lsbfe = 0; + + /* This values are default for audio device */ + chip->ctar.dbr = 0; + chip->ctar.pbr = 2; + chip->ctar.br = 8; + + /* This values are default for audio device */ + chip->ctar.pcssck = 1; + chip->ctar.pasc = 1; + chip->ctar.pdt = 1; + chip->ctar.cssck = 0; + chip->ctar.asc = 1; + chip->ctar.dt = 1; + + chip->void_write_data = chip_info->void_write_data; + +#else + + chip->qwr.csiv = 1; // Chip selects are active low + chip->qmr.master = 1; // Must set to master mode + chip->qmr.dohie = 1; // Data output high impediance enabled + chip->void_write_data = chip_info->void_write_data; + + chip->qdlyr.qcd = chip_info->del_cs_to_clk; + chip->qdlyr.dtl = chip_info->del_after_trans; + + if (spi->max_speed_hz != 0) + baud_divisor = (MCF_CLK/(2*spi->max_speed_hz)); + + if (baud_divisor < 2) + baud_divisor = 2; + + if (baud_divisor > 255) + baud_divisor = 255; + + chip->qmr.baud = baud_divisor; + + //printk( "QSPI: spi->max_speed_hz %d\n", spi->max_speed_hz ); + //printk( "QSPI: Baud set to %d\n", chip->qmr.baud ); + + if (spi->mode & SPI_CPHA) + chip->qmr.cpha = 1; + + if (spi->mode & SPI_CPOL) + chip->qmr.cpol = 1; + + if (spi->bits_per_word == 16) { + chip->qmr.bits = 0; + } else if ((spi->bits_per_word >= 8) && (spi->bits_per_word <= 15)) { + chip->qmr.bits = spi->bits_per_word; + } else { + printk(KERN_ERR "coldfire-qspi: invalid wordsize\n"); + kfree(chip); + return -ENODEV; + } + +#endif + + spi_set_ctldata(spi, chip); + + return 0; +} + +static int init_queue(struct driver_data *drv_data) +{ + INIT_LIST_HEAD(&drv_data->queue); + spin_lock_init(&drv_data->lock); + + drv_data->run = QUEUE_STOPPED; + drv_data->busy = 0; + + tasklet_init(&drv_data->pump_transfers, + pump_transfers, (unsigned long)drv_data); + + INIT_WORK(&drv_data->pump_messages, pump_messages/*, drv_data*/); + + drv_data->workqueue = create_singlethread_workqueue( + drv_data->master->cdev.dev->bus_id); + if (drv_data->workqueue == NULL) + return -EBUSY; + + return 0; +} + +static int start_queue(struct driver_data *drv_data) +{ + unsigned long flags; + + spin_lock_irqsave(&drv_data->lock, flags); + + if (drv_data->run == QUEUE_RUNNING || drv_data->busy) { + spin_unlock_irqrestore(&drv_data->lock, flags); + return -EBUSY; + } + + drv_data->run = QUEUE_RUNNING; + drv_data->cur_msg = NULL; + drv_data->cur_transfer = NULL; + drv_data->cur_chip = NULL; + spin_unlock_irqrestore(&drv_data->lock, flags); + + queue_work(drv_data->workqueue, &drv_data->pump_messages); + + return 0; +} + +static int stop_queue(struct driver_data *drv_data) +{ + unsigned long flags; + unsigned limit = 500; + int status = 0; + + spin_lock_irqsave(&drv_data->lock, flags); + + /* This is a bit lame, but is optimized for the common execution path. + * A wait_queue on the drv_data->busy could be used, but then the common + * execution path (pump_messages) would be required to call wake_up or + * friends on every SPI message. Do this instead */ + drv_data->run = QUEUE_STOPPED; + while (!list_empty(&drv_data->queue) && drv_data->busy && limit--) { + spin_unlock_irqrestore(&drv_data->lock, flags); + msleep(10); + spin_lock_irqsave(&drv_data->lock, flags); + } + + if (!list_empty(&drv_data->queue) || drv_data->busy) + status = -EBUSY; + + spin_unlock_irqrestore(&drv_data->lock, flags); + + return status; +} + +static int destroy_queue(struct driver_data *drv_data) +{ + int status; + + status = stop_queue(drv_data); + if (status != 0) + return status; + + destroy_workqueue(drv_data->workqueue); + + return 0; +} + + +static void cleanup(const struct spi_device *spi) +{ + struct chip_data *chip = spi_get_ctldata((struct spi_device *)spi); + + dev_dbg(&spi->dev, "spi_device %u.%u cleanup\n", + spi->master->bus_num, spi->chip_select); + + kfree(chip); +} + + +/****************************************************************************/ + +/* + * Generic Device driver routines and interface implementation + */ + +static int coldfire_spi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct coldfire_spi_master *platform_info; + struct spi_master *master; + struct driver_data *drv_data = 0; + struct resource *memory_resource; + int irq; + int status = 0; + int i; + +#if defined(SPI_DSPI_EDMA) + init_edma(); +#endif + + platform_info = (struct coldfire_spi_master *)pdev->dev.platform_data; + + master = spi_alloc_master(dev, sizeof(struct driver_data)); + if (!master) + return -ENOMEM; + + drv_data = class_get_devdata(&master->cdev); + drv_data->master = master; + + INIT_LIST_HEAD(&drv_data->queue); + spin_lock_init(&drv_data->lock); + + master->bus_num = platform_info->bus_num; + master->num_chipselect = platform_info->num_chipselect; + master->cleanup = cleanup; + master->setup = setup; + master->transfer = transfer; + + drv_data->cs_control = platform_info->cs_control; + if (drv_data->cs_control) + for(i = 0; i < master->num_chipselect; i++) + drv_data->cs_control(i, QSPI_CS_INIT | QSPI_CS_DROP); + + /* Setup register addresses */ + memory_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi-module"); + if (!memory_resource) { + dev_dbg(dev, "can not find platform module memory\n"); + goto out_error_master_alloc; + } + +#if defined(SPI_DSPI_EDMA) + drv_data->edma_tx_buf = kmalloc(EDMA_BUFSIZE_KMALLOC, GFP_DMA); + if (!drv_data->edma_tx_buf) { + dev_dbg(dev, "cannot allocate eDMA TX memory\n"); + goto out_error_master_alloc; + } + drv_data->edma_rx_buf = kmalloc(EDMA_BUFSIZE_KMALLOC, GFP_DMA); + if (!drv_data->edma_rx_buf) { + kfree(drv_data->edma_tx_buf); + dev_dbg(dev, "cannot allocate eDMA RX memory\n"); + goto out_error_master_alloc; + } +#endif + +#if defined(SPI_DSPI) + + drv_data->mcr = (void *)(memory_resource->start + 0x00000000); + drv_data->ctar = (void *)(memory_resource->start + 0x0000000C); + drv_data->dspi_sr = (void *)(memory_resource->start + 0x0000002C); + drv_data->dspi_rser = (void *)(memory_resource->start + 0x00000030); + drv_data->dspi_dtfr = (void *)(memory_resource->start + 0x00000034); + drv_data->dspi_drfr = (void *)(memory_resource->start + 0x00000038); + +#else + + drv_data->qmr = (void *)(memory_resource->start + 0x00000000); + drv_data->qdlyr = (void *)(memory_resource->start + 0x00000004); + drv_data->qwr = (void *)(memory_resource->start + 0x00000008); + drv_data->qir = (void *)(memory_resource->start + 0x0000000c); + drv_data->qar = (void *)(memory_resource->start + 0x00000010); + drv_data->qdr = (void *)(memory_resource->start + 0x00000014); + drv_data->qcr = (void *)(memory_resource->start + 0x00000014); + +#endif + + /* Setup register addresses */ + memory_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi-par"); + if (!memory_resource) { + dev_dbg(dev, "can not find platform par memory\n"); + goto out_error_master_alloc; + } + + drv_data->par = (void *)memory_resource->start; + + /* Setup register addresses */ + memory_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi-int-level"); + if (!memory_resource) { + dev_dbg(dev, "can not find platform par memory\n"); + goto out_error_master_alloc; + } + + drv_data->int_icr = (void *)memory_resource->start; + + /* Setup register addresses */ + memory_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi-int-mask"); + if (!memory_resource) { + dev_dbg(dev, "can not find platform par memory\n"); + goto out_error_master_alloc; + } + + drv_data->int_mr = (void *)memory_resource->start; + + irq = platform_info->irq_vector; + + status = request_irq(platform_info->irq_vector, qspi_interrupt, SA_INTERRUPT, dev->bus_id, drv_data); + if (status < 0) { + dev_err(&pdev->dev, "unable to attach ColdFire QSPI interrupt\n"); + goto out_error_master_alloc; + } + + /* Now that we have all the addresses etc. Let's set it up */ + // TODO: + //*drv_data->par = platform_info->par_val; + + MCF_GPIO_PAR_DSPI = 0 + | MCF_GPIO_PAR_DSPI_PCS5_PCS5 + | MCF_GPIO_PAR_DSPI_PCS2_PCS2 + | MCF_GPIO_PAR_DSPI_PCS1_PCS1 + | MCF_GPIO_PAR_DSPI_PCS0_PCS0 + | MCF_GPIO_PAR_DSPI_SIN_SIN + | MCF_GPIO_PAR_DSPI_SOUT_SOUT + | MCF_GPIO_PAR_DSPI_SCK_SCK; + + *drv_data->int_icr = platform_info->irq_lp; + *drv_data->int_mr &= ~platform_info->irq_mask; + +#ifdef SPI_DSPI + drv_data->dspi_ctas = 0; // TODO: change later +#endif + + /* Initial and start queue */ + status = init_queue(drv_data); + if (status != 0) { + dev_err(&pdev->dev, "problem initializing queue\n"); + goto out_error_irq_alloc; + } + status = start_queue(drv_data); + if (status != 0) { + dev_err(&pdev->dev, "problem starting queue\n"); + goto out_error_irq_alloc; + } + + /* Register with the SPI framework */ + platform_set_drvdata(pdev, drv_data); + status = spi_register_master(master); + if (status != 0) { + dev_err(&pdev->dev, "problem registering spi master\n"); + status = -EINVAL; + goto out_error_queue_alloc; + } + +#if defined(SPI_DSPI_EDMA) + if (request_edma_channel(DSPI_DMA_TX_TCD, + edma_tx_handler, + NULL, + pdev, + NULL, /* spinlock */ + DRIVER_NAME + )!=0) + { + dev_err(&pdev->dev, "problem requesting edma transmit channel\n"); + status = -EINVAL; + goto out_error_queue_alloc; + } + + if (request_edma_channel(DSPI_DMA_RX_TCD, + edma_rx_handler, + NULL, + pdev, + NULL, /* spinlock */ + DRIVER_NAME + )!=0) + { + dev_err(&pdev->dev, "problem requesting edma receive channel\n"); + status = -EINVAL; + goto out_edma_transmit; + } +#endif + + printk( "SPI: Coldfire master initialized\n" ); + //dev_info(&pdev->dev, "driver initialized\n"); + return status; + +#if defined(SPI_DSPI_EDMA) +out_edma_transmit: + free_edma_channel(DSPI_DMA_TX_TCD, pdev); +#endif + +out_error_queue_alloc: + destroy_queue(drv_data); + +out_error_irq_alloc: + free_irq(irq, drv_data); + +out_error_master_alloc: + spi_master_put(master); + return status; + +} + +static int coldfire_spi_remove(struct platform_device *pdev) +{ + struct driver_data *drv_data = platform_get_drvdata(pdev); + int irq; + int status = 0; + + if (!drv_data) + return 0; + +#if defined(SPI_DSPI_EDMA) + free_edma_channel(DSPI_DMA_TX_TCD, pdev); + free_edma_channel(DSPI_DMA_RX_TCD, pdev); +#endif + + /* Remove the queue */ + status = destroy_queue(drv_data); + if (status != 0) + return status; + + /* Disable the SSP at the peripheral and SOC level */ + /*write_SSCR0(0, drv_data->ioaddr); + pxa_set_cken(drv_data->master_info->clock_enable, 0);*/ + + /* Release DMA */ + /*if (drv_data->master_info->enable_dma) { + if (drv_data->ioaddr == SSP1_VIRT) { + DRCMRRXSSDR = 0; + DRCMRTXSSDR = 0; + } else if (drv_data->ioaddr == SSP2_VIRT) { + DRCMRRXSS2DR = 0; + DRCMRTXSS2DR = 0; + } else if (drv_data->ioaddr == SSP3_VIRT) { + DRCMRRXSS3DR = 0; + DRCMRTXSS3DR = 0; + } + pxa_free_dma(drv_data->tx_channel); + pxa_free_dma(drv_data->rx_channel); + }*/ + + /* Release IRQ */ + irq = platform_get_irq(pdev, 0); + if (irq >= 0) + free_irq(irq, drv_data); + + /* Disconnect from the SPI framework */ + spi_unregister_master(drv_data->master); + + /* Prevent double remove */ + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static void coldfire_spi_shutdown(struct platform_device *pdev) +{ + int status = 0; + + if ((status = coldfire_spi_remove(pdev)) != 0) + dev_err(&pdev->dev, "shutdown failed with %d\n", status); +} + + +#ifdef CONFIG_PM +static int suspend_devices(struct device *dev, void *pm_message) +{ + pm_message_t *state = pm_message; + + if (dev->power.power_state.event != state->event) { + dev_warn(dev, "pm state does not match request\n"); + return -1; + } + + return 0; +} + +static int coldfire_spi_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct driver_data *drv_data = platform_get_drvdata(pdev); + int status = 0; + + /* Check all childern for current power state */ + if (device_for_each_child(&pdev->dev, &state, suspend_devices) != 0) { + dev_warn(&pdev->dev, "suspend aborted\n"); + return -1; + } + + status = stop_queue(drv_data); + if (status != 0) + return status; + /*write_SSCR0(0, drv_data->ioaddr); + pxa_set_cken(drv_data->master_info->clock_enable, 0);*/ + + return 0; +} + +static int coldfire_spi_resume(struct platform_device *pdev) +{ + struct driver_data *drv_data = platform_get_drvdata(pdev); + int status = 0; + + /* Enable the SSP clock */ + /*pxa_set_cken(drv_data->master_info->clock_enable, 1);*/ + + /* Start the queue running */ + status = start_queue(drv_data); + if (status != 0) { + dev_err(&pdev->dev, "problem starting queue (%d)\n", status); + return status; + } + + return 0; +} +#else +#define coldfire_spi_suspend NULL +#define coldfire_spi_resume NULL +#endif /* CONFIG_PM */ + +static struct platform_driver driver = { + .driver = { + .name = "spi_coldfire", + .bus = &platform_bus_type, + .owner = THIS_MODULE, + }, + .probe = coldfire_spi_probe, + .remove = __devexit_p(coldfire_spi_remove), + .shutdown = coldfire_spi_shutdown, + .suspend = coldfire_spi_suspend, + .resume = coldfire_spi_resume, +}; + +static int __init coldfire_spi_init(void) +{ + platform_driver_register(&driver); + + return 0; +} +module_init(coldfire_spi_init); + +static void __exit coldfire_spi_exit(void) +{ + platform_driver_unregister(&driver); +} +module_exit(coldfire_spi_exit); --- /dev/null +++ b/drivers/spi/ssi_audio.c @@ -0,0 +1,906 @@ +/* + * MCF5445x audio driver. + * + * Yaroslav Vinogradov yaroslav.vinogradov@freescale.com + * Copyright Freescale Semiconductor, Inc. 2006 + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define SOUND_DEVICE_NAME "sound" +#define DRIVER_NAME "ssi_audio" + + +/* #define AUDIO_DEBUG */ + +#ifdef CONFIG_MMU +#define USE_MMU +#endif + +#define MAX_SPEED_HZ 12000000 + +#define M5445x_AUDIO_IRQ_SOURCE 49 +#define M5445x_AUDIO_IRQ_VECTOR (128+M5445x_AUDIO_IRQ_SOURCE) +#define M5445x_AUDIO_IRQ_LEVEL 5 + +/* TLV320DAC23 audio chip registers */ + +#define CODEC_LEFT_IN_REG (0x00) +#define CODEC_RIGHT_IN_REG (0x01) +#define CODEC_LEFT_HP_VOL_REG (0x02) +#define CODEC_RIGHT_HP_VOL_REG (0x03) +#define CODEC_ANALOG_APATH_REG (0x04) +#define CODEC_DIGITAL_APATH_REG (0x05) +#define CODEC_POWER_DOWN_REG (0x06) +#define CODEC_DIGITAL_IF_FMT_REG (0x07) +#define CODEC_SAMPLE_RATE_REG (0x08) +#define CODEC_DIGITAL_IF_ACT_REG (0x09) +#define CODEC_RESET_REG (0x0f) + +#define CODEC_SAMPLE_8KHZ (0x0C) +#define CODEC_SAMPLE_16KHZ (0x58) +#define CODEC_SAMPLE_22KHZ (0x62) +#define CODEC_SAMPLE_32KHZ (0x18) +#define CODEC_SAMPLE_44KHZ (0x22) +#define CODEC_SAMPLE_48KHZ (0x00) + +/* Audio buffer data size */ +#define BUFSIZE (64*1024) +/* DMA transfer size */ +#define DMASIZE (16*1024) + +/* transmit eDMA channel for SSI channel 0 */ +#define DMA_TCD 10 +/* transmit eDMA channel for SSI channel 1 */ +#define DMA_TCD2 11 + +struct ssi_audio { + struct spi_device *spi; + u32 speed; + u32 stereo; + u32 bits; + u32 format; + u8 isopen; + u8 dmaing; + u8 ssi_enabled; + u8 channel; + spinlock_t lock; + u8* audio_buf; +}; + +static struct ssi_audio* audio_device = NULL; +volatile u32 audio_start; +volatile u32 audio_count; +volatile u32 audio_append; +volatile u32 audio_appstart; +volatile u32 audio_txbusy; + +struct ssi_audio_format { + unsigned int format; + unsigned int bits; +} ssi_audio_formattable[] = { + { AFMT_MU_LAW, 8 }, + { AFMT_A_LAW, 8 }, + { AFMT_IMA_ADPCM, 8 }, + { AFMT_U8, 8 }, + { AFMT_S16_LE, 16 }, + { AFMT_S16_BE, 16 }, + { AFMT_S8, 8 }, + { AFMT_U16_LE, 16 }, + { AFMT_U16_BE, 16 }, +}; + +#define FORMATSIZE (sizeof(ssi_audio_formattable) / sizeof(struct ssi_audio_format)) + +static void ssi_audio_setsamplesize(int val) +{ + int i; + + if (audio_device == NULL) return; + + for (i = 0; (i < FORMATSIZE); i++) { + if (ssi_audio_formattable[i].format == val) { + audio_device->format = ssi_audio_formattable[i].format; + audio_device->bits = ssi_audio_formattable[i].bits; + break; + } + } + +#ifdef AUDIO_DEBUG + printk(DRIVER_NAME ":ssi_audio_setsamplesize %d %d\n", audio_device->format, audio_device->bits); +#endif +} + +static void ssi_audio_txdrain(void) +{ +#ifdef AUDIO_DEBUG + printk(DRIVER_NAME ":ssi_audio_txdrain()\n"); +#endif + + if (audio_device == NULL) return; + + while (!signal_pending(current)) { + if (audio_txbusy == 0) + break; + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(1); + } +} + +#ifdef CONFIG_SSIAUDIO_USE_EDMA +/* + * Configure and start DMA engine. + */ +void __inline__ ssi_audio_dmarun(void) +{ + set_edma_params(DMA_TCD, +#ifdef USE_MMU + virt_to_phys(&(audio_device->audio_buf[audio_start])), +#else + (u32)&(audio_device->audio_buf[audio_start]), +#endif + (u32)&MCF_SSI_TX0, + MCF_EDMA_TCD_ATTR_SSIZE_32BIT | MCF_EDMA_TCD_ATTR_DSIZE_32BIT, + 8, + 4, + 0, + audio_count/8, + audio_count/8, + 0, + 0, + 0, // major_int + 0 // disable_req + ); + + set_edma_params(DMA_TCD2, +#ifdef USE_MMU + virt_to_phys(&(audio_device->audio_buf[audio_start+4])), +#else + (u32)&(audio_device->audio_buf[audio_start+4]), +#endif + (u32)&MCF_SSI_TX1, + MCF_EDMA_TCD_ATTR_SSIZE_32BIT | MCF_EDMA_TCD_ATTR_DSIZE_32BIT, + 8, + 4, + 0, + audio_count/8, + audio_count/8, + 0, + 0, + 1, // major_int + 0 // disable_req + ); + + audio_device->dmaing = 1; + audio_txbusy = 1; + + start_edma_transfer(DMA_TCD); + start_edma_transfer(DMA_TCD2); +#if 0 + MCF_EDMA_ERQ |= (1<dmaing) + return; + + /* Set DMA buffer size */ + audio_count = (audio_append >= audio_appstart) ? + (audio_append - audio_appstart) : + (BUFSIZE - audio_appstart); + if (audio_count > DMASIZE) + audio_count = DMASIZE; + + /* Adjust pointers and counters accordingly */ + audio_appstart += audio_count; + if (audio_appstart >= BUFSIZE) + audio_appstart = 0; + + if (audio_count > 0) + ssi_audio_dmarun(); + else { + audio_txbusy = 0; +#ifdef AUDIO_DEBUG + printk(DRIVER_NAME ":DMA buffer is empty!\n"); +#endif + } +} + +void __inline__ stop_dma(void) { + stop_edma_transfer(DMA_TCD); + stop_edma_transfer(DMA_TCD2); +} + +static int ssi_audio_dma_handler_empty(int channel, void *dev_id) +{ + return IRQ_HANDLED; +} + +static int ssi_audio_dma_handler(int channel, void *dev_id) +{ +#ifdef AUDIO_DEBUG + printk(DRIVER_NAME ":ssi_audio_dma_handler(channel=%d)\n", channel); +#endif + + /* Clear DMA interrupt */ + stop_dma(); + + audio_device->dmaing = 0; + + /* Update data pointers and counts */ + audio_start += audio_count; + if (audio_start >= BUFSIZE) + audio_start = 0; + audio_count = 0; + + /* Start new DMA buffer if we can */ + ssi_audio_dmabuf(); + + return IRQ_HANDLED; +} + +static void init_dma(void) +{ + /* SSI DMA Signals mapped to DMA request */ + MCF_CCM_MISCCR &= ~MCF_CCM_MISCCR_TIMDMA; + init_edma(); +} + +#endif /* CONFIG_SSIAUDIO_USE_EDMA */ + + +/* Write CODEC register using SPI + * address - CODEC register address + * data - data to be written into register + */ +static int codec_write(u8 addr, u16 data) +{ + u16 spi_word; + + if (audio_device==NULL || audio_device->spi==NULL) + return -ENODEV; + + spi_word = ((addr & 0x7F)<<9)|(data & 0x1FF); + return spi_write(audio_device->spi, (const u8*)&spi_word, sizeof(spi_word)); +} + +static inline void enable_ssi(void) +{ + if (audio_device==NULL || audio_device->ssi_enabled) return; + audio_device->ssi_enabled = 1; + MCF_SSI_CR |= MCF_SSI_CR_SSI_EN; /* enable SSI module */ + MCF_SSI_CR |= MCF_SSI_CR_TE; /* enable tranmitter */ +} + +static inline void disable_ssi(void) +{ + if (audio_device==NULL || audio_device->ssi_enabled==0) return; + MCF_SSI_CR &= ~MCF_SSI_CR_TE; /* disable transmitter */ + MCF_SSI_CR &= ~MCF_SSI_CR_SSI_EN; /* disable SSI module */ + audio_device->ssi_enabled = 0; +} + +/* Audio CODEC initialization */ +/* TODO: also the SSI frequency/dividers must be adjusted */ +static void adjust_codec_speed(void) { +#ifdef AUDIO_DEBUG + printk(DRIVER_NAME ":adjust_codec_speed: %d\n", audio_device->speed); +#endif + + if (audio_device->speed == 8000) { + codec_write(CODEC_SAMPLE_RATE_REG,CODEC_SAMPLE_8KHZ); + } else if (audio_device->speed == 16000) { + codec_write(CODEC_SAMPLE_RATE_REG,CODEC_SAMPLE_16KHZ); + } else if (audio_device->speed == 22000) { + codec_write(CODEC_SAMPLE_RATE_REG,CODEC_SAMPLE_22KHZ); + } else if (audio_device->speed == 44000 || audio_device->speed == 44100) { + codec_write(CODEC_SAMPLE_RATE_REG,CODEC_SAMPLE_44KHZ); + } else if (audio_device->speed == 48000) { + codec_write(CODEC_SAMPLE_RATE_REG,CODEC_SAMPLE_48KHZ); + } else { + /* default 44KHz */ + codec_write(CODEC_SAMPLE_RATE_REG,CODEC_SAMPLE_44KHZ); + } +} + +static void codec_reset(void) +{ + codec_write(CODEC_RESET_REG, 0); /* reset the audio chip */ + udelay(1500); /* wait for reset */ +} + +static void init_audio_codec(void) +{ +#ifdef AUDIO_DEBUG + printk(DRIVER_NAME ":init_audio_codec()\n"); +#endif + codec_reset(); + + codec_write(CODEC_LEFT_IN_REG, 0x017); + codec_write(CODEC_RIGHT_IN_REG, 0x017); + codec_write(CODEC_POWER_DOWN_REG, 0x000); /* Turn off line input */ + codec_write(CODEC_DIGITAL_IF_FMT_REG, 0x00A); /* I2S slave mode */ + /* codec_write(CODEC_DIGITAL_IF_FMT_REG, 0x042); // I2S master mode */ + codec_write(CODEC_DIGITAL_APATH_REG, 0x007); /* Set A path */ + + /* set sample rate */ + adjust_codec_speed(); + + codec_write(CODEC_LEFT_HP_VOL_REG, 0x075); /* set volume */ + codec_write(CODEC_RIGHT_HP_VOL_REG, 0x075); /* set volume */ + codec_write(CODEC_DIGITAL_IF_ACT_REG, 1); /* Activate digital interface */ + codec_write(CODEC_ANALOG_APATH_REG, 0x0F2); +} + + +static void chip_init(void) +{ +#ifdef CONFIG_SSIAUDIO_USE_EDMA + init_dma(); +#endif + + /* Enable the SSI pins */ + MCF_GPIO_PAR_SSI = ( 0 + | MCF_GPIO_PAR_SSI_MCLK + | MCF_GPIO_PAR_SSI_STXD(3) + | MCF_GPIO_PAR_SSI_SRXD(3) + | MCF_GPIO_PAR_SSI_FS(3) + | MCF_GPIO_PAR_SSI_BCLK(3) ); + +} + +static void init_ssi(void) +{ +#ifdef AUDIO_DEBUG + printk(DRIVER_NAME ":init_ssi()\n"); +#endif + + /* Dividers are for MCF54445 on 266Mhz, the output is 44.1Khz*/ + /* Enable SSI clock in CCM */ + MCF_CCM_CDR = MCF_CCM_CDR_SSIDIV(47); + + /* Issue a SSI reset */ + MCF_SSI_CR &= ~MCF_SSI_CR_SSI_EN; /* disable SSI module */ + + /* SSI module uses internal CPU clock */ + MCF_CCM_MISCCR |= MCF_CCM_MISCCR_SSISRC; + + MCF_CCM_MISCCR |= MCF_CCM_MISCCR_SSIPUE; + MCF_CCM_MISCCR |= MCF_CCM_MISCCR_SSIPUS_UP; + + MCF_SSI_CR = 0 + | MCF_SSI_CR_CIS + | MCF_SSI_CR_TCH /* Enable two channel mode */ + | MCF_SSI_CR_MCE /* Set clock out on SSI_MCLK pin */ + | MCF_SSI_CR_I2S_MASTER /* Set I2S master mode */ + | MCF_SSI_CR_SYN /* Enable synchronous mode */ + | MCF_SSI_CR_NET + ; + + MCF_SSI_TCR = 0 + | MCF_SSI_TCR_TXDIR /* internally generated bit clock */ + | MCF_SSI_TCR_TFDIR /* internally generated frame sync */ + | MCF_SSI_TCR_TSCKP /* Clock data on falling edge of bit clock */ + | MCF_SSI_TCR_TFSI /* Frame sync active low */ + | MCF_SSI_TCR_TEFS /* TX frame sync 1 bit before data */ + | MCF_SSI_TCR_TFEN0 /* TX FIFO 0 enabled */ + | MCF_SSI_TCR_TFEN1 /* TX FIFO 1 enabled */ + | MCF_SSI_TCR_TXBIT0 + ; + + MCF_SSI_CCR = MCF_SSI_CCR_WL(7) /* 16 bit word length */ + | MCF_SSI_CCR_DC(1) /* Frame rate divider */ + | MCF_SSI_CCR_PM(0) + | MCF_SSI_CCR_DIV2 + ; + + MCF_SSI_FCSR = 0 + | MCF_SSI_FCSR_TFWM0(0) + | MCF_SSI_FCSR_TFWM1(0) + ; + + MCF_SSI_IER = 0 // interrupts +#ifndef CONFIG_SSIAUDIO_USE_EDMA + | MCF_SSI_IER_TIE /* transmit interrupts */ + | MCF_SSI_IER_TFE0 /* transmit FIFO 0 empty */ + | MCF_SSI_IER_TFE1 /* transmit FIFO 1 empty */ +#else + | MCF_SSI_IER_TDMAE /* DMA request enabled */ + | MCF_SSI_IER_TFE0 /* transmit FIFO 0 empty */ + | MCF_SSI_IER_TFE1 /* transmit FIFO 1 empty */ +#endif + ; + +#ifndef CONFIG_SSIAUDIO_USE_EDMA + /* enable IRQ: SSI interrupt */ + MCF_INTC1_ICR(M5445x_AUDIO_IRQ_SOURCE) = M5445x_AUDIO_IRQ_LEVEL; + MCF_INTC1_CIMR = M5445x_AUDIO_IRQ_SOURCE; +#endif +} + +#ifndef CONFIG_SSIAUDIO_USE_EDMA +/* interrupt for SSI */ +static int ssi_audio_isr(int irq, void *dev_id) +{ + unsigned long *bp; + + if (audio_txbusy==0) { + return IRQ_HANDLED; + } + + spin_lock(&(audio_device->lock)); + + if (audio_start == audio_append) { + disable_ssi(); + audio_txbusy = 0; + } else { + if (MCF_SSI_ISR & (MCF_SSI_ISR_TFE0|MCF_SSI_ISR_TFE1)) { + bp = (unsigned long *) &audio_device->audio_buf[audio_start]; + if (audio_device->channel) { + MCF_SSI_TX1 = *bp; + audio_device->channel = 0; + } else { + MCF_SSI_TX0 = *bp; + audio_device->channel = 1; + } + audio_start += 4; + if (audio_start >= BUFSIZE) + audio_start = 0; + } + } + + spin_unlock(&(audio_device->lock)); + + return IRQ_HANDLED; +} +#endif + +/* Set initial driver playback defaults. */ +static void init_driver_variables(void) +{ + audio_device->speed = 44100; + audio_device->format = AFMT_S16_LE; + audio_device->bits = 16; + audio_device->stereo = 1; + audio_device->ssi_enabled = 0; + + audio_start = 0; + audio_count = 0; + audio_append = 0; + audio_appstart = 0; + audio_txbusy = 0; + audio_device->dmaing = 0; +} + +/* open audio device */ +static int ssi_audio_open(struct inode *inode, struct file *filp) +{ +#ifdef AUDIO_DEBUG + printk(DRIVER_NAME ":ssi_audio_open()\n"); +#endif + + if (audio_device==NULL) return (-ENODEV); + + if (audio_device->isopen) + return(-EBUSY); + + spin_lock(&(audio_device->lock)); + + audio_device->isopen = 1; + + init_driver_variables(); + init_ssi(); + init_audio_codec(); + + spin_unlock(&(audio_device->lock)); + + udelay(100); + + return 0; +} + +/* close audio device */ +static int ssi_audio_close(struct inode *inode, struct file *filp) +{ +#ifdef AUDIO_DEBUG + printk(DRIVER_NAME ":ssi_audio_close()\n"); +#endif + + if (audio_device==NULL) return (-ENODEV); + + ssi_audio_txdrain(); + + spin_lock(&(audio_device->lock)); + +#ifdef CONFIG_SSIAUDIO_USE_EDMA + stop_dma(); +#endif + disable_ssi(); + codec_reset(); + init_driver_variables(); + audio_device->isopen = 0; + + spin_unlock(&(audio_device->lock)); + return 0; +} + +/* write to audio device */ +static ssize_t ssi_audio_write(struct file *filp, const char *buf, size_t count, loff_t *ppos) +{ + unsigned long *dp, *buflp; + unsigned short *bufwp; + unsigned char *bufbp; + unsigned int slen, bufcnt, i, s, e; + +#ifdef AUDIO_DEBUG + printk(DRIVER_NAME ":ssi_audio_write(buf=%x,count=%d)\n", (int) buf, count); +#endif + + if (audio_device==NULL) return (-ENODEV); + + if (count <= 0) + return 0; + + spin_lock(&(audio_device->lock)); + + buflp = (unsigned long *) buf; + bufwp = (unsigned short *) buf; + bufbp = (unsigned char *) buf; + + bufcnt = count & ~0x3; + + bufcnt <<= 1; + if (audio_device->stereo == 0) + bufcnt <<= 1; + if (audio_device->bits == 8) + bufcnt <<= 1; + +tryagain: + /* + * Get a snapshot of buffer, so we can figure out how + * much data we can fit in... + */ + s = audio_start; + e = audio_append; + dp = (unsigned long *) &(audio_device->audio_buf[e]); + + slen = ((s > e) ? (s - e) : (BUFSIZE - (e - s))) - 4; + if (slen > bufcnt) + slen = bufcnt; + if ((BUFSIZE - e) < slen) + slen = BUFSIZE - e; + + if (slen == 0) { + if (signal_pending(current)) + return(-ERESTARTSYS); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + goto tryagain; + } + + /* For DMA we need to have data as 32 bit + values (since SSI TX register is 32 bit). + So, the incomming 16 bit data must be put to buffer as 32 bit values. + Also, the endianess is converted if needed + */ + if (audio_device->stereo) { + if (audio_device->bits == 16) { + if (audio_device->format==AFMT_S16_LE) { + /*- convert endianess, probably could be done by SSI also */ + for (i = 0; (i < slen); i += 4) { + unsigned short val = le16_to_cpu((*bufwp++)); + *dp++ = val; + } + } else { + for (i = 0; (i < slen); i += 4) { + *dp++ = *bufwp++; + } + } + } else { + for (i = 0; (i < slen); i += 4) { + *dp = (((unsigned long) *bufbp++) << 24); + *dp++ |= (((unsigned long) *bufbp++) << 8); + } + } + } else { + if (audio_device->bits == 16) { + for (i = 0; (i < slen); i += 4) { + *dp++ = (((unsigned long)*bufwp)<<16) | *bufwp; + bufwp++; + } + } else { + for (i = 0; (i < slen); i += 4) { + *dp++ = (((unsigned long) *bufbp) << 24) | + (((unsigned long) *bufbp) << 8); + bufbp++; + } + } + } + + e += slen; + if (e >= BUFSIZE) + e = 0; + audio_append = e; + + /* If not outputing audio, then start now */ + if (audio_txbusy == 0) { + audio_txbusy++; + audio_device->channel = 0; + enable_ssi(); +#ifdef CONFIG_SSIAUDIO_USE_EDMA + ssi_audio_dmabuf(); /* start first DMA transfer */ +#endif + } + + bufcnt -= slen; + + if (bufcnt > 0) + goto tryagain; + + spin_unlock(&(audio_device->lock)); + + return count; +} + +/* ioctl: control the driver */ +static int ssi_audio_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) +{ + long val; + int rc = 0; + +#ifdef AUDIO_DEBUG + printk(DRIVER_NAME ":ssi_audio_ioctl(cmd=%x,arg=%x)\n", (int) cmd, (int) arg); +#endif + + if (audio_device==NULL) return (-ENODEV); + + switch (cmd) { + + case SNDCTL_DSP_SPEED: + if (access_ok(VERIFY_READ, (void *) arg, sizeof(val))) { + get_user(val, (unsigned long *) arg); +#ifdef AUDIO_DEBUG + printk(DRIVER_NAME ":ssi_audio_ioctl: SNDCTL_DSP_SPEED: %ld\n", val); +#endif + ssi_audio_txdrain(); + audio_device->speed = val; + init_audio_codec(); + } else { + rc = -EINVAL; + } + break; + + case SNDCTL_DSP_SAMPLESIZE: + if (access_ok(VERIFY_READ, (void *) arg, sizeof(val))) { + get_user(val, (unsigned long *) arg); + ssi_audio_txdrain(); + ssi_audio_setsamplesize(val); + } else { + rc = -EINVAL; + } + break; + + case SNDCTL_DSP_STEREO: + if (access_ok(VERIFY_READ, (void *) arg, sizeof(val))) { + get_user(val, (unsigned long *) arg); + ssi_audio_txdrain(); + audio_device->stereo = val; + } else { + rc = -EINVAL; + } + break; + + case SNDCTL_DSP_GETBLKSIZE: + if (access_ok(VERIFY_WRITE, (void *) arg, sizeof(long))) + put_user(BUFSIZE, (long *) arg); + else + rc = -EINVAL; + break; + + case SNDCTL_DSP_SYNC: + ssi_audio_txdrain(); + break; + + default: + rc = -EINVAL; + break; + } + + return rc; +} + +/****************************************************************************/ + +struct file_operations ssi_audio_fops = { + open: ssi_audio_open, /* open */ + release: ssi_audio_close, /* close */ + write: ssi_audio_write, /* write */ + ioctl: ssi_audio_ioctl, /* ioctl */ +}; + +/* initialize audio driver */ +static int __devinit ssi_audio_probe(struct spi_device *spi) +{ + struct ssi_audio *audio; + int err; + +#ifdef AUDIO_DEBUG + printk(DRIVER_NAME": probe\n"); +#endif + + if (!spi->irq) { + dev_dbg(&spi->dev, "no IRQ?\n"); + return -ENODEV; + } + + /* don't exceed max specified sample rate */ + if (spi->max_speed_hz > MAX_SPEED_HZ) { + dev_dbg(&spi->dev, "f(sample) %d KHz?\n", + (spi->max_speed_hz)/1000); + return -EINVAL; + } + + /* register charcter device */ + if (register_chrdev(SOUND_MAJOR, SOUND_DEVICE_NAME, &ssi_audio_fops) < 0) { + printk(KERN_WARNING DRIVER_NAME ": failed to register major %d\n", SOUND_MAJOR); + dev_dbg(&spi->dev, DRIVER_NAME ": failed to register major %d\n", SOUND_MAJOR); + return -ENODEV; + } + + audio = kzalloc(sizeof(struct ssi_audio), GFP_KERNEL); + if (!audio) { + err = -ENOMEM; + goto err_out; + } + + /* DMA buffer must be from GFP_DMA zone, so it will not be cached */ + audio->audio_buf = kmalloc(BUFSIZE, GFP_DMA); + if (audio->audio_buf == NULL) { + dev_dbg(&spi->dev, DRIVER_NAME ": failed to allocate DMA[%d] buffer\n", BUFSIZE); + err = -ENOMEM; + goto err_free_mem; + } + + audio_device = audio; + + dev_set_drvdata(&spi->dev, audio); + spi->dev.power.power_state = PMSG_ON; + + audio->spi = spi; + +#ifndef CONFIG_SSIAUDIO_USE_EDMA + if (request_irq(spi->irq, ssi_audio_isr, SA_INTERRUPT, spi->dev.bus_id, audio)) { + dev_dbg(&spi->dev, "irq %d busy?\n", spi->irq); + err = -EBUSY; + goto err_free_mem; + } + +#else + /* request 2 eDMA channels since two channel output mode is used */ + if (request_edma_channel(DMA_TCD, + ssi_audio_dma_handler_empty, + NULL, + audio, + &(audio_device->lock), + DRIVER_NAME + )!=0) + { + dev_dbg(&spi->dev, "DMA channel %d busy?\n", DMA_TCD); + err = -EBUSY; + goto err_free_mem; + } + if (request_edma_channel(DMA_TCD2, + ssi_audio_dma_handler, + NULL, + audio, + &(audio_device->lock), + DRIVER_NAME + )!=0) + { + dev_dbg(&spi->dev, "DMA channel %d busy?\n", DMA_TCD2); + err = -EBUSY; + goto err_free_mem; + } + +#endif + chip_init(); + printk(DRIVER_NAME ": Probed successfully\n"); + + return 0; + + err_free_mem: + kfree(audio); + audio_device = NULL; + err_out: + unregister_chrdev(SOUND_MAJOR, SOUND_DEVICE_NAME); + return err; +} + +static int __devexit ssi_audio_remove(struct spi_device *spi) +{ + struct ssi_audio *audio = dev_get_drvdata(&spi->dev); + + ssi_audio_txdrain(); +#ifndef CONFIG_SSIAUDIO_USE_EDMA + free_irq(spi->irq, audio); +#else + free_edma_channel(DMA_TCD, audio); + free_edma_channel(DMA_TCD2, audio); +#endif + kfree(audio->audio_buf); + kfree(audio); + audio_device = NULL; + unregister_chrdev(SOUND_MAJOR, SOUND_DEVICE_NAME); + dev_dbg(&spi->dev, "unregistered audio\n"); + return 0; +} + +static int ssi_audio_suspend(struct spi_device *spi, pm_message_t message) { + return 0; +} + +static int ssi_audio_resume(struct spi_device *spi) { + return 0; +} + +static struct spi_driver ssi_audio_driver = { + .driver = { + .name = DRIVER_NAME, + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = ssi_audio_probe, + .remove = __devexit_p(ssi_audio_remove), + .suspend = ssi_audio_suspend, + .resume = ssi_audio_resume, +}; + +static int __init ssi_audio_init(void) +{ + return spi_register_driver(&ssi_audio_driver); +} +module_init(ssi_audio_init); + +static void __exit ssi_audio_exit(void) +{ + spi_unregister_driver(&ssi_audio_driver); +} +module_exit(ssi_audio_exit); + +MODULE_DESCRIPTION("SSI/I2S Audio Driver"); +MODULE_LICENSE("GPL"); --- a/include/asm-m68k/coldfire_edma.h +++ b/include/asm-m68k/coldfire_edma.h @@ -1,39 +1,102 @@ +/* + * coldfire_edma.h - eDMA driver for Coldfire MCF5445x + * + * Yaroslav Vinogradov yaroslav.vinogradov@freescale.com + * + * Copyright Freescale Semiconductor, Inc. 2007 + * + * 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 _LINUX_COLDFIRE_DMA_H #define _LINUX_COLDFIRE_DMA_H #include +#include -#define EDMA_DRIVER_NAME "ColdFire-eDMA" -#define DMA_DEV_MINOR 1 +#define EDMA_DRIVER_NAME "ColdFire-eDMA" +#define DMA_DEV_MINOR 1 #define EDMA_INT_CHANNEL_BASE 8 #define EDMA_INT_CONTROLLER_BASE 64 #define EDMA_CHANNELS 16 - + #define EDMA_IRQ_LEVEL 5 - + typedef irqreturn_t (*edma_irq_handler)(int, void *); typedef void (*edma_error_handler)(int, void *); - + +/* 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 - beggining 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 set_edma_params(int channel, u32 source, u32 dest, - u32 attr, u32 soff, u32 nbytes, u32 slast, - u32 citer, u32 biter, u32 doff, u32 dlast_sga); - -void start_edma_transfer(int channel, int major_int); - -void stop_edma_transfer(int channel); - -void confirm_edma_interrupt_handled(int channel); - + u32 attr, u32 soff, u32 nbytes, u32 slast, + u32 citer, u32 biter, u32 doff, u32 dlast_sga, + int major_int, int disable_req); + +/* Starts eDMA transfer on specified channel + * channel - eDMA TCD number + */ +static inline void start_edma_transfer(int channel) +{ + MCF_EDMA_SERQ = channel; + MCF_EDMA_SSRT = channel; +} + +/* Stops eDMA transfer + * channel - eDMA TCD number + */ +static inline void stop_edma_transfer(int channel) +{ + MCF_EDMA_CINT = channel; + MCF_EDMA_CERQ = channel; +} + + +/* Confirm that interrupt has been handled + * channel - eDMA TCD number + */ +static inline void confirm_edma_interrupt_handled(int channel) +{ + MCF_EDMA_CINT = channel; +} + +/* Initialize eDMA controller */ void init_edma(void); - -int request_edma_channel(int channel, - edma_irq_handler handler, - edma_error_handler error_handler, - void *dev, - spinlock_t *lock, - const char *device_id); - + +/* Request eDMA channel: + * channel - eDMA TCD number + * handler - channel IRQ callback + * error_handler - error interrupt handler callback for channel + * dev - device + * lock - spinlock to be locked (can be NULL) + * device_id - device driver name for proc file system output + */ +int request_edma_channel(int channel, + edma_irq_handler handler, + edma_error_handler error_handler, + void *dev, + spinlock_t *lock, + const char *device_id); + +/* Free eDMA channel + * channel - eDMA TCD number + * dev - device + */ int free_edma_channel(int channel, void *dev); - #endif --- /dev/null +++ b/include/linux/spi/mcfqspi.h @@ -0,0 +1,80 @@ +/****************************************************************************/ + +/* + * mcfqspi.c - Master QSPI controller for the ColdFire processors + * + * (C) Copyright 2005, Intec Automation, + * Mike Lavender (mike@steroidmicros) + * + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ +/* ------------------------------------------------------------------------- */ + +#ifndef MCFQSPI_H_ +#define MCFQSPI_H_ + +#define QSPI_CS_INIT 0x01 +#define QSPI_CS_ASSERT 0x02 +#define QSPI_CS_DROP 0x04 + +#define QSPIIOCS_DOUT_HIZ 1 /* QMR[DOHIE] set hi-z dout between transfers */ +#define QSPIIOCS_BITS 2 /* QMR[BITS] set transfer size */ +#define QSPIIOCG_BITS 3 /* QMR[BITS] get transfer size */ +#define QSPIIOCS_CPOL 4 /* QMR[CPOL] set SCK inactive state */ +#define QSPIIOCS_CPHA 5 /* QMR[CPHA] set SCK phase, 1=rising edge */ +#define QSPIIOCS_BAUD 6 /* QMR[BAUD] set SCK baud rate divider */ +#define QSPIIOCS_QCD 7 /* QDLYR[QCD] set start delay */ +#define QSPIIOCS_DTL 8 /* QDLYR[DTL] set after delay */ +#define QSPIIOCS_CONT 9 /* continuous CS asserted during transfer */ +#define QSPIIOCS_READDATA 10 /* set data send during read */ +#define QSPIIOCS_ODD_MOD 11 /* if length of buffer is a odd number, 16-bit transfers */ + /* are finalized with a 8-bit transfer */ +#define QSPIIOCS_DSP_MOD 12 /* transfers are bounded to 15/30 bytes (a multiple of 3 bytes = 1 DSPword) */ +#define QSPIIOCS_POLL_MOD 13 /* driver uses polling instead of interrupts */ + +#define QSPIIOCS_SET_CSIV 14 /* sets CSIV flag (cs inactive level) */ + +#ifdef CONFIG_M520x +#undef MCF_GPIO_PAR_QSPI +#define MCF_GPIO_PAR_QSPI (0xA4034) +#endif + +struct coldfire_spi_master { + u16 bus_num; + u16 num_chipselect; + u8 irq_source; + u32 irq_vector; + u32 irq_mask; + u8 irq_lp; + u8 par_val; + u16 par_val16; + void (*cs_control)(u8 cs, u8 command); +}; + + +struct coldfire_spi_chip { + u8 mode; + u8 bits_per_word; + u8 del_cs_to_clk; + u8 del_after_trans; + u16 void_write_data; +}; + +typedef struct qspi_read_data { + __u32 length; + __u8 *buf; /* data to send during read */ + unsigned int loop : 1; +} qspi_read_data; +#endif /*MCFQSPI_H_*/