diff options
author | Marc Singer <elf@buici.com> | 2006-05-16 11:41:32 +0100 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2006-06-18 16:16:49 +0100 |
commit | c97898614bf0ac9605333a2c99bdbcf4276a22bd (patch) | |
tree | 9b947f1f4dd0f1e14ef0579cf4e48078ac7a2009 /arch/arm/mach-lh7a40x/ssp-cpld.c | |
parent | 903e2bbda92e5a14f8050154046a14230abb800b (diff) |
[ARM] 3405/1: lpd7a40x: CPLD ssp driver
Patch from Marc Singer
Driver for operating SSP devices through LPD7A40X CPLD chip. This
driver is used by the audio codecs.
Signed-off-by: Marc Singer <elf@buici.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'arch/arm/mach-lh7a40x/ssp-cpld.c')
-rw-r--r-- | arch/arm/mach-lh7a40x/ssp-cpld.c | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/arch/arm/mach-lh7a40x/ssp-cpld.c b/arch/arm/mach-lh7a40x/ssp-cpld.c new file mode 100644 index 000000000000..a10830186dac --- /dev/null +++ b/arch/arm/mach-lh7a40x/ssp-cpld.c @@ -0,0 +1,343 @@ +/* arch/arm/mach-lh7a40x/ssp-cpld.c + * + * Copyright (C) 2004,2005 Marc Singer + * + * 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. + * + * SSP/SPI driver for the CardEngine CPLD. + * + */ + +/* NOTES + ----- + + o *** This driver is cribbed from the 7952x implementation. + Some comments may not apply. + + o This driver contains sufficient logic to control either the + serial EEPROMs or the audio codec. It is included in the kernel + to support the codec. The EEPROMs are really the responsibility + of the boot loader and should probably be left alone. + + o The code must be augmented to cope with multiple, simultaneous + clients. + o The audio codec writes to the codec chip whenever playback + starts. + o The touchscreen driver writes to the ads chip every time it + samples. + o The audio codec must write 16 bits, but the touch chip writes + are 8 bits long. + o We need to be able to keep these configurations separate while + simultaneously active. + + */ + +#include <linux/module.h> +#include <linux/kernel.h> +//#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +//#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/spinlock.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/hardware.h> + +#include <asm/arch/ssp.h> + +//#define TALK + +#if defined (TALK) +#define PRINTK(f...) printk (f) +#else +#define PRINTK(f...) do {} while (0) +#endif + +#if defined (CONFIG_ARCH_LH7A400) +# define CPLD_SPID __REGP16(CPLD06_VIRT) /* SPI data */ +# define CPLD_SPIC __REGP16(CPLD08_VIRT) /* SPI control */ +# define CPLD_SPIC_CS_CODEC (1<<0) +# define CPLD_SPIC_CS_TOUCH (1<<1) +# define CPLD_SPIC_WRITE (0<<2) +# define CPLD_SPIC_READ (1<<2) +# define CPLD_SPIC_DONE (1<<3) /* r/o */ +# define CPLD_SPIC_LOAD (1<<4) +# define CPLD_SPIC_START (1<<4) +# define CPLD_SPIC_LOADED (1<<5) /* r/o */ +#endif + +#define CPLD_SPI __REGP16(CPLD0A_VIRT) /* SPI operation */ +#define CPLD_SPI_CS_EEPROM (1<<3) +#define CPLD_SPI_SCLK (1<<2) +#define CPLD_SPI_TX_SHIFT (1) +#define CPLD_SPI_TX (1<<CPLD_SPI_TX_SHIFT) +#define CPLD_SPI_RX_SHIFT (0) +#define CPLD_SPI_RX (1<<CPLD_SPI_RX_SHIFT) + +/* *** FIXME: these timing values are substantially larger than the + *** chip requires. We may implement an nsleep () function. */ +#define T_SKH 1 /* Clock time high (us) */ +#define T_SKL 1 /* Clock time low (us) */ +#define T_CS 1 /* Minimum chip select low time (us) */ +#define T_CSS 1 /* Minimum chip select setup time (us) */ +#define T_DIS 1 /* Data setup time (us) */ + + /* EEPROM SPI bits */ +#define P_START (1<<9) +#define P_WRITE (1<<7) +#define P_READ (2<<7) +#define P_ERASE (3<<7) +#define P_EWDS (0<<7) +#define P_WRAL (0<<7) +#define P_ERAL (0<<7) +#define P_EWEN (0<<7) +#define P_A_EWDS (0<<5) +#define P_A_WRAL (1<<5) +#define P_A_ERAL (2<<5) +#define P_A_EWEN (3<<5) + +struct ssp_configuration { + int device; + int mode; + int speed; + int frame_size_write; + int frame_size_read; +}; + +static struct ssp_configuration ssp_configuration; +static spinlock_t ssp_lock; + +static void enable_cs (void) +{ + switch (ssp_configuration.device) { + case DEVICE_EEPROM: + CPLD_SPI |= CPLD_SPI_CS_EEPROM; + break; + } + udelay (T_CSS); +} + +static void disable_cs (void) +{ + switch (ssp_configuration.device) { + case DEVICE_EEPROM: + CPLD_SPI &= ~CPLD_SPI_CS_EEPROM; + break; + } + udelay (T_CS); +} + +static void pulse_clock (void) +{ + CPLD_SPI |= CPLD_SPI_SCLK; + udelay (T_SKH); + CPLD_SPI &= ~CPLD_SPI_SCLK; + udelay (T_SKL); +} + + +/* execute_spi_command + + sends an spi command to a device. It first sends cwrite bits from + v. If cread is greater than zero it will read cread bits + (discarding the leading 0 bit) and return them. If cread is less + than zero it will check for completetion status and return 0 on + success or -1 on timeout. If cread is zero it does nothing other + than sending the command. + + On the LPD7A400, we can only read or write multiples of 8 bits on + the codec and the touch screen device. Here, we round up. + +*/ + +static int execute_spi_command (int v, int cwrite, int cread) +{ + unsigned long l = 0; + +#if defined (CONFIG_MACH_LPD7A400) + /* The codec and touch devices cannot be bit-banged. Instead, + * the CPLD provides an eight-bit shift register and a crude + * interface. */ + if ( ssp_configuration.device == DEVICE_CODEC + || ssp_configuration.device == DEVICE_TOUCH) { + int select = 0; + + PRINTK ("spi(%d %d.%d) 0x%04x", + ssp_configuration.device, cwrite, cread, + v); +#if defined (TALK) + if (ssp_configuration.device == DEVICE_CODEC) + PRINTK (" 0x%03x -> %2d", v & 0x1ff, (v >> 9) & 0x7f); +#endif + PRINTK ("\n"); + + if (ssp_configuration.device == DEVICE_CODEC) + select = CPLD_SPIC_CS_CODEC; + if (ssp_configuration.device == DEVICE_TOUCH) + select = CPLD_SPIC_CS_TOUCH; + if (cwrite) { + for (cwrite = (cwrite + 7)/8; cwrite-- > 0; ) { + CPLD_SPID = (v >> (8*cwrite)) & 0xff; + CPLD_SPIC = select | CPLD_SPIC_LOAD; + while (!(CPLD_SPIC & CPLD_SPIC_LOADED)) + ; + CPLD_SPIC = select; + while (!(CPLD_SPIC & CPLD_SPIC_DONE)) + ; + } + v = 0; + } + if (cread) { + mdelay (2); /* *** FIXME: required by ads7843? */ + v = 0; + for (cread = (cread + 7)/8; cread-- > 0;) { + CPLD_SPID = 0; + CPLD_SPIC = select | CPLD_SPIC_READ + | CPLD_SPIC_START; + while (!(CPLD_SPIC & CPLD_SPIC_LOADED)) + ; + CPLD_SPIC = select | CPLD_SPIC_READ; + while (!(CPLD_SPIC & CPLD_SPIC_DONE)) + ; + v = (v << 8) | CPLD_SPID; + } + } + return v; + } +#endif + + PRINTK ("spi(%d) 0x%04x -> 0x%x\r\n", ssp_configuration.device, + v & 0x1ff, (v >> 9) & 0x7f); + + enable_cs (); + + v <<= CPLD_SPI_TX_SHIFT; /* Correction for position of SPI_TX bit */ + while (cwrite--) { + CPLD_SPI + = (CPLD_SPI & ~CPLD_SPI_TX) + | ((v >> cwrite) & CPLD_SPI_TX); + udelay (T_DIS); + pulse_clock (); + } + + if (cread < 0) { + int delay = 10; + disable_cs (); + udelay (1); + enable_cs (); + + l = -1; + do { + if (CPLD_SPI & CPLD_SPI_RX) { + l = 0; + break; + } + } while (udelay (1), --delay); + } + else + /* We pulse the clock before the data to skip the leading zero. */ + while (cread-- > 0) { + pulse_clock (); + l = (l<<1) + | (((CPLD_SPI & CPLD_SPI_RX) + >> CPLD_SPI_RX_SHIFT) & 0x1); + } + + disable_cs (); + return l; +} + +static int ssp_init (void) +{ + spin_lock_init (&ssp_lock); + memset (&ssp_configuration, 0, sizeof (ssp_configuration)); + return 0; +} + + +/* ssp_chip_select + + drops the chip select line for the CPLD shift-register controlled + devices. It doesn't enable chip + +*/ + +static void ssp_chip_select (int enable) +{ +#if defined (CONFIG_MACH_LPD7A400) + int select; + + if (ssp_configuration.device == DEVICE_CODEC) + select = CPLD_SPIC_CS_CODEC; + else if (ssp_configuration.device == DEVICE_TOUCH) + select = CPLD_SPIC_CS_TOUCH; + else + return; + + if (enable) + CPLD_SPIC = select; + else + CPLD_SPIC = 0; +#endif +} + +static void ssp_acquire (void) +{ + spin_lock (&ssp_lock); +} + +static void ssp_release (void) +{ + ssp_chip_select (0); /* just in case */ + spin_unlock (&ssp_lock); +} + +static int ssp_configure (int device, int mode, int speed, + int frame_size_write, int frame_size_read) +{ + ssp_configuration.device = device; + ssp_configuration.mode = mode; + ssp_configuration.speed = speed; + ssp_configuration.frame_size_write = frame_size_write; + ssp_configuration.frame_size_read = frame_size_read; + + return 0; +} + +static int ssp_read (void) +{ + return execute_spi_command (0, 0, ssp_configuration.frame_size_read); +} + +static int ssp_write (u16 data) +{ + execute_spi_command (data, ssp_configuration.frame_size_write, 0); + return 0; +} + +static int ssp_write_read (u16 data) +{ + return execute_spi_command (data, ssp_configuration.frame_size_write, + ssp_configuration.frame_size_read); +} + +struct ssp_driver lh7a40x_cpld_ssp_driver = { + .init = ssp_init, + .acquire = ssp_acquire, + .release = ssp_release, + .configure = ssp_configure, + .chip_select = ssp_chip_select, + .read = ssp_read, + .write = ssp_write, + .write_read = ssp_write_read, +}; + + +MODULE_AUTHOR("Marc Singer"); +MODULE_DESCRIPTION("LPD7A40X CPLD SPI driver"); +MODULE_LICENSE("GPL"); |