diff options
author | kaloz <kaloz@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2009-06-23 21:04:37 +0000 |
---|---|---|
committer | kaloz <kaloz@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2009-06-23 21:04:37 +0000 |
commit | 343c185b7d7383b1f5b5144e837045af28afc42b (patch) | |
tree | 6d3382662fa3ad4119d3a3cda223c53949ca4894 /target/linux/coldfire/patches/019-m5445x_spi.patch | |
parent | 145f9652a593d19b149d2f25febd4aa0c1ab57d1 (diff) |
use broken-out patches for the coldfire to make it easier to follow differences against the bsp
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@16547 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'target/linux/coldfire/patches/019-m5445x_spi.patch')
-rw-r--r-- | target/linux/coldfire/patches/019-m5445x_spi.patch | 3345 |
1 files changed, 3345 insertions, 0 deletions
diff --git a/target/linux/coldfire/patches/019-m5445x_spi.patch b/target/linux/coldfire/patches/019-m5445x_spi.patch new file mode 100644 index 0000000000..49b83e0b18 --- /dev/null +++ b/target/linux/coldfire/patches/019-m5445x_spi.patch @@ -0,0 +1,3345 @@ +From bc755a3b8859e7307a8b10f39ca4cb6401c51987 Mon Sep 17 00:00:00 2001 +From: Kurt Mahan <kmahan@freescale.com> +Date: Tue, 27 Nov 2007 14:39:37 -0700 +Subject: [PATCH] Add M5445x SPI support. + +LTIBName: m5445x-spi +Signed-off-by: Kurt Mahan <kmahan@freescale.com> +--- + 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 <linux/init.h> ++#include <linux/module.h> ++#include <asm/virtconvert.h> ++#include <asm/coldfire.h> ++#include <linux/fs.h> ++#include <linux/cdev.h> ++#include <linux/seq_file.h> ++#include <linux/proc_fs.h> ++#include <asm/mcf5445x_edma.h> ++#include <asm/mcf5445x_intc.h> ++#include <asm/coldfire_edma.h> ++ ++ ++/* 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;i<EDMA_CHANNELS;i++) { ++ if (err & (1<<i)) { ++ if (devp!=NULL && devp->dma_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;i<EDMA_CHANNELS;i++) ++ { ++ MCF_INTC0_SIMR = EDMA_INT_CHANNEL_BASE+i; ++ free_irq(EDMA_INT_CHANNEL_BASE+EDMA_INT_CONTROLLER_BASE+i, devp); ++ } ++ MCF_INTC0_SIMR = EDMA_INT_CHANNEL_BASE+EDMA_CHANNELS; ++ free_irq(EDMA_INT_CHANNEL_BASE+EDMA_INT_CONTROLLER_BASE+EDMA_CHANNELS, devp); ++ cdev_del(&devp->cdev); ++ 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;i<EDMA_CHANNELS;i++) { ++ devp->dma_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<EDMA_CHANNELS;i++) ++ { ++ result = request_irq(EDMA_INT_CHANNEL_BASE+EDMA_INT_CONTROLLER_BASE+i, ++ dmaisr, SA_INTERRUPT, EDMA_DRIVER_NAME, devp); ++ if (result) { ++ printk(KERN_WARNING EDMA_DRIVER_NAME": Cannot request irq %d\n", ++ EDMA_INT_CHANNEL_BASE+EDMA_INT_CONTROLLER_BASE+i); ++ result = -EBUSY; ++ goto fail; ++ } ++ ++ MCF_INTC0_ICR(EDMA_INT_CHANNEL_BASE+i) = EDMA_IRQ_LEVEL; ++ MCF_INTC0_CIMR = EDMA_INT_CHANNEL_BASE+i; ++ ++ } ++ ++ /* request error interrupt */ ++ result = request_irq(EDMA_INT_CHANNEL_BASE + EDMA_INT_CONTROLLER_BASE + EDMA_CHANNELS, ++ dma_error_isr, SA_INTERRUPT, EDMA_DRIVER_NAME, devp); ++ if (result) { ++ printk(KERN_WARNING EDMA_DRIVER_NAME": Cannot request irq %d\n", ++ EDMA_INT_CHANNEL_BASE+EDMA_INT_CONTROLLER_BASE+EDMA_CHANNELS); ++ result = -EBUSY; ++ goto fail; ++ } ++ ++ /* enable error interrupt in interrupt controller */ ++ MCF_INTC0_ICR(EDMA_INT_CHANNEL_BASE+EDMA_CHANNELS) = EDMA_IRQ_LEVEL; ++ MCF_INTC0_CIMR = EDMA_INT_CHANNEL_BASE+EDMA_CHANNELS; ++ ++#ifdef CONFIG_PROC_FS ++ proc_edma_init(); ++#endif ++ ++ printk(EDMA_DRIVER_NAME ": initialized successfully\n"); ++ ++ return 0; ++fail: ++ coldfire_edma_cleanup(); ++ return result; ++ ++} ++ ++static void __exit coldfire_edma_exit(void) ++{ ++ coldfire_edma_cleanup(); ++} ++ ++module_init(coldfire_edma_init); ++module_exit(coldfire_edma_exit); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Yaroslav Vinogradov, Freescale Inc."); ++MODULE_DESCRIPTION("eDMA library for Coldfire 5445x"); +--- /dev/null ++++ b/drivers/spi/spi-m5445x.c +@@ -0,0 +1,156 @@ ++/***************************************************************************/ ++/* ++ * linux/arch/m68k/coldfire/spi-m5445x.c ++ * ++ * Sub-architcture dependant initialization code for the Freescale ++ * 5445x SPI module ++ * ++ * 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 <linux/kernel.h> ++#include <linux/sched.h> ++#include <linux/param.h> ++#include <linux/init.h> ++#include <linux/interrupt.h> ++#include <linux/device.h> ++#include <linux/platform_device.h> ++#include <linux/spi/spi.h> ++ ++#include <asm/dma.h> ++#include <asm/traps.h> ++#include <asm/machdep.h> ++#include <asm/coldfire.h> ++#include <asm/mcfsim.h> ++#include <asm/mcfqspi.h> ++#include <asm/mcf5445x_gpio.h> ++ ++#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 <linux/autoconf.h> ++#include <linux/init.h> ++#include <linux/module.h> ++#include <linux/device.h> ++#include <linux/interrupt.h> ++#include <linux/platform_device.h> ++#include <linux/spi/spi.h> ++#include <linux/workqueue.h> ++#include <linux/delay.h> ++ ++#include <asm/delay.h> ++#include <asm/mcfsim.h> ++#include <asm/mcfqspi.h> ++#include <asm/coldfire.h> ++#include <asm/virtconvert.h> ++ ++#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 <asm/mcf5445x_dspi.h> ++ ++ ++#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 <asm/coldfire_edma.h> ++#include <asm/mcf5445x_edma.h> ++#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 <linux/device.h> ++#include <linux/init.h> ++#include <linux/delay.h> ++#include <linux/spi/spi.h> ++#include <linux/fs.h> ++#include <linux/kernel.h> ++#include <linux/major.h> ++#include <asm/mcfsim.h> ++#include <linux/interrupt.h> ++#include <linux/soundcard.h> ++#include <asm/uaccess.h> ++#include <asm/virtconvert.h> ++ ++#include <asm/coldfire.h> ++#include <asm/coldfire_edma.h> ++#include <asm/mcf5445x_ssi.h> ++#include <asm/mcf5445x_ccm.h> ++#include <asm/mcf5445x_gpio.h> ++ ++#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<<DMA_TCD) | (1<<DMA_TCD2); ++ MCF_EDMA_SSRT = DMA_TCD; ++ MCF_EDMA_SSRT = DMA_TCD2; ++#endif ++ ++} ++ ++/* ++ * Start DMA'ing a new buffer of data if any available. ++ */ ++static void ssi_audio_dmabuf(void) ++{ ++#ifdef AUDIO_DEBUG ++ printk(DRIVER_NAME ":ssi_audio_dmabuf(): append=%x start=%x\n", audio_append, audio_appstart); ++#endif ++ ++ /* If already running then nothing to do... */ ++ if (audio_device->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 <linux/interrupt.h> ++#include <asm/mcf5445x_edma.h> + +-#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_*/ |