--- drivers/spi/Kconfig | 6 drivers/spi/Makefile | 1 drivers/spi/spi_ep93xx.c | 679 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 686 insertions(+) --- linux-2.6.32.orig/drivers/spi/Kconfig +++ linux-2.6.32/drivers/spi/Kconfig @@ -98,10 +98,16 @@ config SPI_BUTTERFLY This uses a custom parallel port cable to connect to an AVR Butterfly , an inexpensive battery powered microcontroller evaluation board. This same cable can be used to flash new firmware. +config SPI_EP93XX + tristate "EP93xx SSP SPI master" + depends on SPI_MASTER && ARCH_EP93XX && EXPERIMENTAL + help + This enables the EP93xx SPI master controller. + config SPI_GPIO tristate "GPIO-based bitbanging SPI Master" depends on GENERIC_GPIO select SPI_BITBANG help --- linux-2.6.32.orig/drivers/spi/Makefile +++ linux-2.6.32/drivers/spi/Makefile @@ -31,10 +31,11 @@ obj-$(CONFIG_SPI_S3C24XX_GPIO) += spi_s obj-$(CONFIG_SPI_S3C24XX) += spi_s3c24xx.o obj-$(CONFIG_SPI_TXX9) += spi_txx9.o obj-$(CONFIG_SPI_XILINX) += xilinx_spi.o obj-$(CONFIG_SPI_SH_SCI) += spi_sh_sci.o obj-$(CONFIG_SPI_STMP3XXX) += spi_stmp.o +obj-$(CONFIG_SPI_EP93XX) += spi_ep93xx.o # ... add above this line ... # SPI protocol drivers (device/link on bus) obj-$(CONFIG_SPI_SPIDEV) += spidev.o obj-$(CONFIG_SPI_TLE62X0) += tle62x0.o --- /dev/null +++ linux-2.6.32/drivers/spi/spi_ep93xx.c @@ -0,0 +1,679 @@ +/* + * linux/drivers/spi/spi_ep93xx.c + * + * Copyright (C) 2007 Manfred Gruber + * Small changes by Peter Ivanov to support MMC over SPI, 2008 + * SIM.ONE changes by Nuccio Raciti Simplemachine + * + * Based on pxa2xx_spi.c/spi_imx.c and bitbang.c driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +/* #define SPI_EP93XX_DEBUG */ + +#define DEFINE_SSP_REG(reg, off) \ + static inline u32 read_##reg(void *p) \ + { return __raw_readl(p + (off)); } \ + static inline void write_##reg(u32 v, void *p) \ + { __raw_writel(v, p + (off)); } + +DEFINE_SSP_REG(SSPCR0, 0x00) +DEFINE_SSP_REG(SSPCR1, 0x04) +DEFINE_SSP_REG(SSPDR, 0x08) +DEFINE_SSP_REG(SSPSR, 0x0c) +DEFINE_SSP_REG(SSPCPSR, 0x10) +DEFINE_SSP_REG(SSPIIR, 0x14) +DEFINE_SSP_REG(SSPICR, 0x14) + +/* Bits in SSPCR0 */ +#define SSPCR0_DSS_MASK 0x0000000f +#define SSPCR0_FRF_MASK 0x00000030 +#define SSPCR0_FRF_SHIFT 4 +#define SSPCR0_FRF_MOTOROLA (0 << SSPCR0_FRF_SHIFT) +#define SSPCR0_FRF_TI (1 << SSPCR0_FRF_SHIFT) +#define SSPCR0_FRF_NI (2 << SSPCR0_FRF_SHIFT) +#define SSPCR0_SPO 0x00000040 +#define SSPCR0_SPH 0x00000080 +#define SSPCR0_SCR_MASK 0x0000ff00 +#define SSPCR0_SCR_SHIFT 8 + +/* Bits in SSPCR1 */ +#define SSPC1_RIE 0x00000001 +#define SSPC1_TIE 0x00000002 +#define SSPC1_RORIE 0x00000004 +#define SSPC1_LBM 0x00000008 +#define SSPC1_SSE 0x00000010 +#define SSPC1_MS 0x00000020 +#define SSPC1_SOD 0x00000040 + +/* Bits in SSPSR */ +#define SSPSR_TFE 0x00000001 /* TX FIFO is empty */ +#define SSPSR_TNF 0x00000002 /* TX FIFO is not full */ +#define SSPSR_RNE 0x00000004 /* RX FIFO is not empty */ +#define SSPSR_RFF 0x00000008 /* RX FIFO is full */ +#define SSPSR_BSY 0x00000010 /* SSP is busy */ +#define SSPSR_MASK 0x0000001F /* SSP is busy */ + +/* Bits in SSPCPSR */ +#define SSPCPSR_SCR_MASK 0x000000ff + +/* Bits in SSPIIR */ +#define SSPIIR_RIS 0x00000001 /* RX FIFO IRQ status */ +#define SSPIIR_TIS 0x00000002 /* TX FIFO is not full */ +#define SSPIIR_RORIS 0x00000004 /* RX FIFO is full */ + +#define SPI_SSPCLK 7.4e6 +#define SPI_SSPCLK_REV_E2 14.8e6 /* only for chip Rev E2 */ +#define SPI_MAX_SPEED 3.7e6 +#define SPI_MAX_SPEED_REV_E2 7.4e6 /* only for chip Rev E2 */ +#define SPI_CPSDVR_DIV_MIN 2 +#define SPI_CPSDVR_DIV_MAX 254 +#define SPI_SCR_DIV_MIN 0 +#define SPI_SCR_DIV_MAX 255 +#define SPI_DATARATE_OK 0 +#define SPI_DATARATE_NOK -1 + +struct driver_data { + /* Driver model hookup */ + struct platform_device *pdev; + + /* SPI framework hookup */ + struct spi_master *master; + + /* SSP register addresses */ + void *ioaddr; + + /* SSP irq */ + int irq; + + struct list_head queue; + + /* SSP spinlock */ + spinlock_t lock; + + struct workqueue_struct *workqueue; + struct work_struct work; + + u8 busy; + u8 use_dma; +}; + +static unsigned ep93xx_txrx_8(struct spi_device *spi, struct spi_transfer *t) +{ + struct driver_data *drv_data; + const u8 *tx = t->tx_buf; + u8 *rx = t->rx_buf; + unsigned count = t->len; + u8 byte; + int busy; + + drv_data = spi_master_get_devdata(spi->master); + +#ifdef SPI_EP93XX_DEBUG + dev_info(&spi->dev, + "ep93xx_txrx_8: t->len %u \n", t->len); +#endif + + while (likely(count > 0)) { + byte = 0; + if (tx) { + byte = *tx++; +#ifdef SPI_EP93XX_DEBUG + dev_info(&spi->dev, + "ep93xx_txrx_8: write 0x%x \n", byte); +#endif + } + + write_SSPDR(byte, drv_data->ioaddr); + busy = read_SSPSR(drv_data->ioaddr); + while (busy & SSPSR_BSY) { + cpu_relax(); + busy = read_SSPSR(drv_data->ioaddr); +#ifdef SPI_EP93XX_DEBUG + dev_info(&spi->dev, + "ep93xx_txrx_8: delay. SSPSR: 0x%X\n", busy); +#endif + } + byte = read_SSPDR(drv_data->ioaddr); + + if (rx) { + *rx++ = byte; +#ifdef SPI_EP93XX_DEBUG + dev_info(&spi->dev, + "ep93xx_txrx_8: read 0x%x \n", byte); +#endif + } + count -= 1; + } + return t->len - count; +} + + +static unsigned ep93xx_txrx_16(struct spi_device *spi, struct spi_transfer *t) +{ + + struct driver_data *drv_data; + const u16 *tx = t->tx_buf; + u16 *rx = t->rx_buf; + unsigned count = t->len; + u16 word; + int busy; + + drv_data = spi_master_get_devdata(spi->master); + +#ifdef SPI_EP93XX_DEBUG + dev_info(&spi->dev, + "ep93xx_txrx_16: t->len %u \n", t->len); +#endif + while (likely(count > 0)) { + word = 0; + if (tx) { + word = *tx++; +#ifdef SPI_EP93XX_DEBUG + dev_info(&spi->dev, + "ep93xx_txrx_16: write 0x%x \n", word); +#endif + } + + write_SSPDR(word, drv_data->ioaddr); + busy = read_SSPSR(drv_data->ioaddr); + while (busy & SSPSR_BSY) { + cpu_relax(); + busy = read_SSPSR(drv_data->ioaddr); +#ifdef SPI_EP93XX_DEBUG + dev_info(&spi->dev, + "ep93xx_txrx_8: delay.\n"); +#endif + } + + word = read_SSPDR(drv_data->ioaddr); + + if (rx) { + *rx++ = word; +#ifdef SPI_EP93XX_DEBUG + dev_info(&spi->dev, + "ep93xx_txrx_16: read 0x%x \n", word); +#endif + } + count -= 2; + } + return t->len - count; +} + +static u32 spi_data_rate(u32 speed_hz, u32 *div_cpsdvr, u32 *div_scr, + struct driver_data *drv_data, struct spi_device *spi) +{ + unsigned int spi_sspclk = SPI_SSPCLK; + unsigned int bus_speed_max = SPI_MAX_SPEED; + unsigned int bus_hz_tmp = 0; + u32 div_cpsdvr_tmp; + u32 div_scr_tmp; + u32 rv = SPI_DATARATE_NOK; + int chip_rev; + + /* Checking CHIP_ID */ + chip_rev = (__raw_readl (EP93XX_SYSCON_CHIP_ID) >> 28) & 0xF; + if (chip_rev == 7) + { + /* Chip version: Rev E2 */ + /* This device has double speed SSP clock */ + spi_sspclk = SPI_SSPCLK_REV_E2; + bus_speed_max = SPI_MAX_SPEED_REV_E2; +#ifdef SPI_EP93XX_DEBUG + dev_info(&spi->dev, + "Chip Rev E2 detected! This device has double speed SSP clock.\n"); +#endif + } + + *div_cpsdvr = SPI_CPSDVR_DIV_MAX; + *div_scr = SPI_SCR_DIV_MAX; + + for (div_cpsdvr_tmp = SPI_CPSDVR_DIV_MIN; + div_cpsdvr_tmp <= SPI_CPSDVR_DIV_MAX && rv; div_cpsdvr_tmp++) { + for (div_scr_tmp = SPI_SCR_DIV_MIN; + div_scr_tmp <= SPI_SCR_DIV_MAX && rv; div_scr_tmp++) { + bus_hz_tmp = spi_sspclk / (div_cpsdvr_tmp * (1 + div_scr_tmp)); + if (bus_hz_tmp <= speed_hz && bus_hz_tmp <= bus_speed_max) { + *div_cpsdvr = div_cpsdvr_tmp; + *div_scr = div_scr_tmp; + rv = SPI_DATARATE_OK; + } + } + } +#ifdef SPI_EP93XX_DEBUG + dev_info(&spi->dev, + "Needed SPI bus frequency: %i Hz\n", speed_hz); + dev_info(&spi->dev, + "Actual SPI bus frequency: %i Hz\n", bus_hz_tmp); +#endif + return rv; +} + +/* Supported modes (returns -EINVAL if not supported mode requested) */ +#define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH) + +static int ep93xx_spi_setup(struct spi_device *spi) +{ + struct driver_data *drv_data; + u16 val; + u32 div_scr; + u32 div_cpsdvr; + unsigned int bits = spi->bits_per_word; + unsigned long speed_hz = spi->max_speed_hz; + + drv_data = spi_master_get_devdata(spi->master); + + /* enable SSP */ + write_SSPCR1(SSPC1_SSE, drv_data->ioaddr); + /* Enable SSP and loopback mode (only for testing!) */ + /* write_SSPCR1(SSPC1_SSE | SSPC1_LBM, drv_data->ioaddr); */ + + if (bits == 0) + bits = 8; + if (bits < 4 || bits > 16) { + dev_err(&spi->dev, + "setup invalid bits_per_word %u (4 to 16)\n", bits); + return -EINVAL; + } else { + val = read_SSPCR0(drv_data->ioaddr); + val = val & ~SSPCR0_DSS_MASK ; + val = val | (bits-1); + write_SSPCR0(val, drv_data->ioaddr); +#ifdef SPI_EP93XX_DEBUG + dev_info (&spi->dev, "Bits per word: %i\n", bits); +#endif + } + + if (spi->mode & ~MODEBITS) { + dev_err(&spi->dev, "unsupported mode bits: %x\n", + spi->mode & ~MODEBITS); + return -EINVAL; + } else { + val = read_SSPCR0(drv_data->ioaddr); + val = val & ~SSPCR0_SPO; + val = val & ~SSPCR0_SPH; + if (spi->mode & SPI_CPOL) + { + val = val | SSPCR0_SPO; + } +#ifdef SPI_EP93XX_DEBUG + dev_info (&spi->dev, "Clock polarity (CPOL): %s\n", (spi->mode & SPI_CPHA) ? "1" : "0"); +#endif + if (spi->mode & SPI_CPHA) + { + val = val | SSPCR0_SPH; + } +#ifdef SPI_EP93XX_DEBUG + dev_info (&spi->dev, "Clock phase (CPHA): %s\n", (spi->mode & SPI_CPHA) ? "1" : "0"); +#endif + write_SSPCR0(val, drv_data->ioaddr); + } + + if (SPI_DATARATE_OK == (spi_data_rate(speed_hz, &div_cpsdvr, + &div_scr, drv_data, spi))) { + + val = read_SSPCPSR(drv_data->ioaddr); + val = val & ~SSPCPSR_SCR_MASK; + val = val | div_cpsdvr; +#ifdef SPI_EP93XX_DEBUG + dev_info (&spi->dev, "SSPCPSR: 0x%X\n", val); +#endif + write_SSPCPSR(val, drv_data->ioaddr); + + val = read_SSPCR0(drv_data->ioaddr); + val = val & ~SSPCR0_SCR_MASK; + val = val | (div_scr << SSPCR0_SCR_SHIFT); +#ifdef SPI_EP93XX_DEBUG + dev_info (&spi->dev, "SSPCR0: 0x%X (div_scr: 0x%X)\n", val, div_scr); +#endif + write_SSPCR0(val, drv_data->ioaddr); + } else + return -EINVAL; + + /* reenable */ + val = read_SSPCR1(drv_data->ioaddr); + val = val & ~SSPC1_SSE; + write_SSPCR1(val, drv_data->ioaddr); + val = read_SSPCR1(drv_data->ioaddr); + val = val | SSPC1_SSE; + write_SSPCR1(val, drv_data->ioaddr); +#ifdef SPI_EP93XX_DEBUG + dev_info (&spi->dev, "Loopback mode: %s\n", (val & SSPC1_LBM) ? "On" : "Off"); +#endif + + return 0; +} + +static int ep93xx_spi_transfer(struct spi_device *spi, struct spi_message *m) +{ + struct driver_data *drv_data; + unsigned long flags; + int status = 0; + + m->actual_length = 0; + m->status = -EINPROGRESS; + + drv_data = spi_master_get_devdata(spi->master); + + spin_lock_irqsave(&drv_data->lock, flags); + if (!spi->max_speed_hz) + status = -ENETDOWN; + else { + list_add_tail(&m->queue, &drv_data->queue); + queue_work(drv_data->workqueue, &drv_data->work); + } + spin_unlock_irqrestore(&drv_data->lock, flags); + return status; +} + +static void ep93xx_work(struct work_struct *work) +{ + struct driver_data *drv_data = + container_of(work, struct driver_data, work); + unsigned long flags; + + spin_lock_irqsave(&drv_data->lock, flags); + drv_data->busy = 1; + + while (!list_empty(&drv_data->queue)) { + struct spi_message *m; + struct spi_device *spi; + struct spi_transfer *t = NULL; + int status; + + m = container_of(drv_data->queue.next, struct spi_message, + queue); + list_del_init(&m->queue); + spin_unlock_irqrestore(&drv_data->lock, flags); + + spi = m->spi; + status = 0; + + list_for_each_entry(t, &m->transfers, transfer_list) { + + if (!t->tx_buf && !t->rx_buf && t->len) { + status = -EINVAL; + break; + } + + if (t->len) { + if (!m->is_dma_mapped) { + t->rx_dma = 0; + t->tx_dma = 0; + } + if (t->bits_per_word <= 8) + status = ep93xx_txrx_8(spi, t); + else + status = ep93xx_txrx_16(spi, t); + } + + if (status != t->len) { + if (status > 0) + status = -EMSGSIZE; + break; + } + m->actual_length += status; + status = 0; + + /* protocol tweaks before next transfer */ + if (t->delay_usecs) + udelay(t->delay_usecs); + + if (t->transfer_list.next == &m->transfers) + break; + } + + m->status = status; + m->complete(m->context); + + spin_lock_irqsave(&drv_data->lock, flags); + } + drv_data->busy = 0; + spin_unlock_irqrestore(&drv_data->lock, flags); +} + +static irqreturn_t ssp_int(int irq, void *dev_id) +{ + struct driver_data *drv_data = dev_id; + u8 status; + status = read_SSPIIR(drv_data->ioaddr); + + if (status & SSPIIR_RORIS) { + dev_err(&drv_data->pdev->dev, "SPI rx overrun.\n"); + + /* We clear the overrun here ! */ + write_SSPICR(0, drv_data->ioaddr); + } + + /* RX interrupt */ + if (status & SSPIIR_RIS) + dev_info(&drv_data->pdev->dev, "SPI RX interrupt\n"); + + /* TX interrupt */ + if (status & SSPIIR_TIS) + dev_info(&drv_data->pdev->dev, "SPI TX interrupt\n"); + + write_SSPICR(0, drv_data->ioaddr); + return IRQ_HANDLED; +} + +static int __init ep93xx_spi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct spi_master *master; + struct driver_data *drv_data = 0; + struct resource *memory_resource; + int status = 0; + u16 val; + + /* Allocate master with space for drv_data and null dma buffer */ + master = spi_alloc_master(dev, sizeof(struct driver_data)); + if (!master) { + dev_err(&pdev->dev, "cannot alloc spi_master\n"); + return -ENOMEM; + } + drv_data = spi_master_get_devdata(master); + drv_data->master = master; + drv_data->pdev = pdev; + + master->num_chipselect = EP93XX_GPIO_LINE_H(7) + 1; + master->bus_num = pdev->id; + master->setup = ep93xx_spi_setup; + master->transfer = ep93xx_spi_transfer; + + spin_lock_init(&drv_data->lock); + INIT_LIST_HEAD(&drv_data->queue); + + /* Setup register addresses */ + memory_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!memory_resource) { + dev_err(&pdev->dev, "memory resources not defined\n"); + status = -EIO; + goto out_error_master_alloc; + } else { + drv_data->ioaddr = ioremap(memory_resource->start, + memory_resource->end - memory_resource->start); + if (drv_data->ioaddr == NULL) { + dev_err(&pdev->dev, "ioremap failed\n"); + status = -EIO; + goto out_error_master_alloc; + } + } + + /* Attach to IRQ */ + drv_data->irq = platform_get_irq(pdev, 0); + if (drv_data->irq < 0) + return drv_data->irq; + + if (drv_data->irq <= 0) { + dev_err(&pdev->dev, "IRQ resource not defined\n"); + status = -ENODEV; + goto out_error_master_alloc; + } + + status = request_irq(drv_data->irq, ssp_int, 0, "ep93xx-spi", drv_data); + if (status < 0) { + dev_err(&pdev->dev, "cannot get SPI IRQ 0\n"); + goto out_error_master_alloc; + } + + /* SSP default configuration, enable */ + write_SSPCR1(SSPC1_SSE, drv_data->ioaddr); + + /* run as master */ + val = read_SSPCR1(drv_data->ioaddr); + val = val & ~SSPC1_MS; + write_SSPCR1(val, drv_data->ioaddr); + + /* frame format to Motorola SPI Format */ + val = read_SSPCR0(drv_data->ioaddr); + val = val & ~SSPCR0_FRF_MASK ; + val = val | SSPCR0_FRF_MOTOROLA; + write_SSPCR0(val, drv_data->ioaddr); + + /* enable interrupts */ + val = read_SSPCR1(drv_data->ioaddr); + /* for now only overrun is handled */ + /* val = val | SSPC1_RIE | SSPC1_TIE | SSPC1_RORIE; */ + val = val | SSPC1_RORIE; + write_SSPCR1(val, drv_data->ioaddr); + + /* SSP default configuration, re enable */ + val = read_SSPCR1(drv_data->ioaddr); + val = val & ~SSPC1_SSE; + write_SSPCR1(val, drv_data->ioaddr); + val = read_SSPCR1(drv_data->ioaddr); + val = val | SSPC1_SSE; + write_SSPCR1(val, drv_data->ioaddr); + + /* Register with the SPI framework */ + platform_set_drvdata(pdev, drv_data); + status = spi_register_master(master); + if (status != 0) { + dev_err(&pdev->dev, "cannot register SPI master\n"); + goto out_error_master_alloc; + } else + dev_info(&pdev->dev, "SPI Controller initalized\n"); + + INIT_WORK(&drv_data->work, ep93xx_work); + spin_lock_init(&drv_data->lock); + INIT_LIST_HEAD(&drv_data->queue); + + /* this task is the only thing to touch the SPI bits */ + drv_data->busy = 0; + drv_data->workqueue = create_singlethread_workqueue( + dev_name(drv_data->master->dev.parent)); + if (drv_data->workqueue == NULL) { + status = -EBUSY; + goto out_error_free_irq; + } + + return status; + +out_error_free_irq: + free_irq(drv_data->irq, master); +out_error_master_alloc: + if (drv_data->ioaddr != NULL) + iounmap(drv_data->ioaddr); + spi_master_put(master); + return status; +} + +static int __exit ep93xx_spi_remove(struct platform_device *pdev) +{ + struct driver_data *drv_data = platform_get_drvdata(pdev); + u8 val; + + WARN_ON(!list_empty(&drv_data->queue)); + + destroy_workqueue(drv_data->workqueue); + + /* switch off SSP*/ + val = read_SSPCR1(drv_data->ioaddr); + val = val & ~SSPC1_SSE; + write_SSPCR1(val, drv_data->ioaddr); + + /* release irqs */ + if (drv_data->irq > 0) + free_irq(drv_data->irq, drv_data); + + /* Disconnect from the SPI framework */ + spi_unregister_master(drv_data->master); + spi_master_put(drv_data->master); + + if (drv_data->ioaddr != NULL) + iounmap(drv_data->ioaddr); + + /* Prevent double remove */ + platform_set_drvdata(pdev, NULL); + return 0; +} + +#ifdef CONFIG_PM +static int ep93xx_spi_suspend(struct platform_device *pdev, pm_message_t msg) +{ + return 0; +} + +static int ep93xx_spi_resume(struct platform_device *pdev) +{ + return 0; +} + +#else +#define ep93xx_spi_suspend NULL +#define ep93xx_spi_resume NULL +#endif + +struct platform_driver ep93xx_spi_device = { + .remove = __exit_p(ep93xx_spi_remove), +#ifdef CONFIG_PM + .suspend = ep93xx_spi_suspend, + .resume = ep93xx_spi_resume, +#endif + .driver = { + .name = "ep93xx-spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, +}; + +int __init ep93xx_spi_init(void) +{ + return platform_driver_probe(&ep93xx_spi_device, ep93xx_spi_probe); +} + +void __exit ep93xx_spi_exit(void) +{ + platform_driver_unregister(&ep93xx_spi_device); +} + +module_init(ep93xx_spi_init); +module_exit(ep93xx_spi_exit); + +MODULE_DESCRIPTION("EP93XX SPI Driver"); +MODULE_AUTHOR("Manfred Gruber, "); +MODULE_LICENSE("GPL");