linux/arch/arm/mach-lh7a40x/ssp-cpld.c
<<
>>
Prefs
   1/* arch/arm/mach-lh7a40x/ssp-cpld.c
   2 *
   3 *  Copyright (C) 2004,2005 Marc Singer
   4 *
   5 *  This program is free software; you can redistribute it and/or
   6 *  modify it under the terms of the GNU General Public License
   7 *  version 2 as published by the Free Software Foundation.
   8 *
   9 * SSP/SPI driver for the CardEngine CPLD.
  10 *
  11 */
  12
  13/* NOTES
  14   -----
  15
  16   o *** This driver is cribbed from the 7952x implementation.
  17         Some comments may not apply.
  18
  19   o This driver contains sufficient logic to control either the
  20     serial EEPROMs or the audio codec.  It is included in the kernel
  21     to support the codec.  The EEPROMs are really the responsibility
  22     of the boot loader and should probably be left alone.
  23
  24   o The code must be augmented to cope with multiple, simultaneous
  25     clients.
  26     o The audio codec writes to the codec chip whenever playback
  27       starts.
  28     o The touchscreen driver writes to the ads chip every time it
  29       samples.
  30     o The audio codec must write 16 bits, but the touch chip writes
  31       are 8 bits long.
  32     o We need to be able to keep these configurations separate while
  33       simultaneously active.
  34
  35 */
  36
  37#include <linux/module.h>
  38#include <linux/kernel.h>
  39//#include <linux/sched.h>
  40#include <linux/errno.h>
  41#include <linux/interrupt.h>
  42//#include <linux/ioport.h>
  43#include <linux/init.h>
  44#include <linux/delay.h>
  45#include <linux/spinlock.h>
  46#include <linux/io.h>
  47
  48#include <asm/irq.h>
  49#include <mach/hardware.h>
  50
  51#include <mach/ssp.h>
  52
  53//#define TALK
  54
  55#if defined (TALK)
  56#define PRINTK(f...)            printk (f)
  57#else
  58#define PRINTK(f...)            do {} while (0)
  59#endif
  60
  61#if defined (CONFIG_ARCH_LH7A400)
  62# define CPLD_SPID              __REGP16(CPLD06_VIRT) /* SPI data */
  63# define CPLD_SPIC              __REGP16(CPLD08_VIRT) /* SPI control */
  64# define CPLD_SPIC_CS_CODEC     (1<<0)
  65# define CPLD_SPIC_CS_TOUCH     (1<<1)
  66# define CPLD_SPIC_WRITE        (0<<2)
  67# define CPLD_SPIC_READ         (1<<2)
  68# define CPLD_SPIC_DONE         (1<<3) /* r/o */
  69# define CPLD_SPIC_LOAD         (1<<4)
  70# define CPLD_SPIC_START        (1<<4)
  71# define CPLD_SPIC_LOADED       (1<<5) /* r/o */
  72#endif
  73
  74#define CPLD_SPI                __REGP16(CPLD0A_VIRT) /* SPI operation */
  75#define CPLD_SPI_CS_EEPROM      (1<<3)
  76#define CPLD_SPI_SCLK           (1<<2)
  77#define CPLD_SPI_TX_SHIFT       (1)
  78#define CPLD_SPI_TX             (1<<CPLD_SPI_TX_SHIFT)
  79#define CPLD_SPI_RX_SHIFT       (0)
  80#define CPLD_SPI_RX             (1<<CPLD_SPI_RX_SHIFT)
  81
  82/* *** FIXME: these timing values are substantially larger than the
  83   *** chip requires. We may implement an nsleep () function. */
  84#define T_SKH   1               /* Clock time high (us) */
  85#define T_SKL   1               /* Clock time low (us) */
  86#define T_CS    1               /* Minimum chip select low time (us)  */
  87#define T_CSS   1               /* Minimum chip select setup time (us)  */
  88#define T_DIS   1               /* Data setup time (us) */
  89
  90         /* EEPROM SPI bits */
  91#define P_START         (1<<9)
  92#define P_WRITE         (1<<7)
  93#define P_READ          (2<<7)
  94#define P_ERASE         (3<<7)
  95#define P_EWDS          (0<<7)
  96#define P_WRAL          (0<<7)
  97#define P_ERAL          (0<<7)
  98#define P_EWEN          (0<<7)
  99#define P_A_EWDS        (0<<5)
 100#define P_A_WRAL        (1<<5)
 101#define P_A_ERAL        (2<<5)
 102#define P_A_EWEN        (3<<5)
 103
 104struct ssp_configuration {
 105        int device;
 106        int mode;
 107        int speed;
 108        int frame_size_write;
 109        int frame_size_read;
 110};
 111
 112static struct ssp_configuration ssp_configuration;
 113static spinlock_t ssp_lock;
 114
 115static void enable_cs (void)
 116{
 117        switch (ssp_configuration.device) {
 118        case DEVICE_EEPROM:
 119                CPLD_SPI |= CPLD_SPI_CS_EEPROM;
 120                break;
 121        }
 122        udelay (T_CSS);
 123}
 124
 125static void disable_cs (void)
 126{
 127        switch (ssp_configuration.device) {
 128        case DEVICE_EEPROM:
 129                CPLD_SPI &= ~CPLD_SPI_CS_EEPROM;
 130                break;
 131        }
 132        udelay (T_CS);
 133}
 134
 135static void pulse_clock (void)
 136{
 137        CPLD_SPI |=  CPLD_SPI_SCLK;
 138        udelay (T_SKH);
 139        CPLD_SPI &= ~CPLD_SPI_SCLK;
 140        udelay (T_SKL);
 141}
 142
 143
 144/* execute_spi_command
 145
 146   sends an spi command to a device.  It first sends cwrite bits from
 147   v.  If cread is greater than zero it will read cread bits
 148   (discarding the leading 0 bit) and return them.  If cread is less
 149   than zero it will check for completetion status and return 0 on
 150   success or -1 on timeout.  If cread is zero it does nothing other
 151   than sending the command.
 152
 153   On the LPD7A400, we can only read or write multiples of 8 bits on
 154   the codec and the touch screen device.  Here, we round up.
 155
 156*/
 157
 158static int execute_spi_command (int v, int cwrite, int cread)
 159{
 160        unsigned long l = 0;
 161
 162#if defined (CONFIG_MACH_LPD7A400)
 163        /* The codec and touch devices cannot be bit-banged.  Instead,
 164         * the CPLD provides an eight-bit shift register and a crude
 165         * interface.  */
 166        if (   ssp_configuration.device == DEVICE_CODEC
 167            || ssp_configuration.device == DEVICE_TOUCH) {
 168                int select = 0;
 169
 170                PRINTK ("spi(%d %d.%d) 0x%04x",
 171                        ssp_configuration.device, cwrite, cread,
 172                        v);
 173#if defined (TALK)
 174                if (ssp_configuration.device == DEVICE_CODEC)
 175                        PRINTK (" 0x%03x -> %2d", v & 0x1ff, (v >> 9) & 0x7f);
 176#endif
 177                PRINTK ("\n");
 178
 179                if (ssp_configuration.device == DEVICE_CODEC)
 180                        select = CPLD_SPIC_CS_CODEC;
 181                if (ssp_configuration.device == DEVICE_TOUCH)
 182                        select = CPLD_SPIC_CS_TOUCH;
 183                if (cwrite) {
 184                        for (cwrite = (cwrite + 7)/8; cwrite-- > 0; ) {
 185                                CPLD_SPID = (v >> (8*cwrite)) & 0xff;
 186                                CPLD_SPIC = select | CPLD_SPIC_LOAD;
 187                                while (!(CPLD_SPIC & CPLD_SPIC_LOADED))
 188                                        ;
 189                                CPLD_SPIC = select;
 190                                while (!(CPLD_SPIC & CPLD_SPIC_DONE))
 191                                        ;
 192                        }
 193                        v = 0;
 194                }
 195                if (cread) {
 196                        mdelay (2);     /* *** FIXME: required by ads7843? */
 197                        v = 0;
 198                        for (cread = (cread + 7)/8; cread-- > 0;) {
 199                                CPLD_SPID = 0;
 200                                CPLD_SPIC = select | CPLD_SPIC_READ
 201                                        | CPLD_SPIC_START;
 202                                while (!(CPLD_SPIC & CPLD_SPIC_LOADED))
 203                                        ;
 204                                CPLD_SPIC = select | CPLD_SPIC_READ;
 205                                while (!(CPLD_SPIC & CPLD_SPIC_DONE))
 206                                        ;
 207                                v = (v << 8) | CPLD_SPID;
 208                        }
 209                }
 210                return v;
 211        }
 212#endif
 213
 214        PRINTK ("spi(%d) 0x%04x -> 0x%x\r\n", ssp_configuration.device,
 215                v & 0x1ff, (v >> 9) & 0x7f);
 216
 217        enable_cs ();
 218
 219        v <<= CPLD_SPI_TX_SHIFT; /* Correction for position of SPI_TX bit */
 220        while (cwrite--) {
 221                CPLD_SPI
 222                        = (CPLD_SPI & ~CPLD_SPI_TX)
 223                        | ((v >> cwrite) & CPLD_SPI_TX);
 224                udelay (T_DIS);
 225                pulse_clock ();
 226        }
 227
 228        if (cread < 0) {
 229                int delay = 10;
 230                disable_cs ();
 231                udelay (1);
 232                enable_cs ();
 233
 234                l = -1;
 235                do {
 236                        if (CPLD_SPI & CPLD_SPI_RX) {
 237                                l = 0;
 238                                break;
 239                        }
 240                } while (udelay (1), --delay);
 241        }
 242        else
 243        /* We pulse the clock before the data to skip the leading zero. */
 244                while (cread-- > 0) {
 245                        pulse_clock ();
 246                        l = (l<<1)
 247                                | (((CPLD_SPI & CPLD_SPI_RX)
 248                                    >> CPLD_SPI_RX_SHIFT) & 0x1);
 249                }
 250
 251        disable_cs ();
 252        return l;
 253}
 254
 255static int ssp_init (void)
 256{
 257        spin_lock_init (&ssp_lock);
 258        memset (&ssp_configuration, 0, sizeof (ssp_configuration));
 259        return 0;
 260}
 261
 262
 263/* ssp_chip_select
 264
 265   drops the chip select line for the CPLD shift-register controlled
 266   devices.  It doesn't enable chip
 267
 268*/
 269
 270static void ssp_chip_select (int enable)
 271{
 272#if defined (CONFIG_MACH_LPD7A400)
 273        int select;
 274
 275        if (ssp_configuration.device == DEVICE_CODEC)
 276                select = CPLD_SPIC_CS_CODEC;
 277        else if (ssp_configuration.device == DEVICE_TOUCH)
 278                select = CPLD_SPIC_CS_TOUCH;
 279        else
 280                return;
 281
 282        if (enable)
 283                CPLD_SPIC = select;
 284        else
 285                CPLD_SPIC = 0;
 286#endif
 287}
 288
 289static void ssp_acquire (void)
 290{
 291        spin_lock (&ssp_lock);
 292}
 293
 294static void ssp_release (void)
 295{
 296        ssp_chip_select (0);    /* just in case */
 297        spin_unlock (&ssp_lock);
 298}
 299
 300static int ssp_configure (int device, int mode, int speed,
 301                           int frame_size_write, int frame_size_read)
 302{
 303        ssp_configuration.device                = device;
 304        ssp_configuration.mode                  = mode;
 305        ssp_configuration.speed                 = speed;
 306        ssp_configuration.frame_size_write      = frame_size_write;
 307        ssp_configuration.frame_size_read       = frame_size_read;
 308
 309        return 0;
 310}
 311
 312static int ssp_read (void)
 313{
 314        return execute_spi_command (0, 0, ssp_configuration.frame_size_read);
 315}
 316
 317static int ssp_write (u16 data)
 318{
 319        execute_spi_command (data, ssp_configuration.frame_size_write, 0);
 320        return 0;
 321}
 322
 323static int ssp_write_read (u16 data)
 324{
 325        return execute_spi_command (data, ssp_configuration.frame_size_write,
 326                                    ssp_configuration.frame_size_read);
 327}
 328
 329struct ssp_driver lh7a40x_cpld_ssp_driver = {
 330        .init           = ssp_init,
 331        .acquire        = ssp_acquire,
 332        .release        = ssp_release,
 333        .configure      = ssp_configure,
 334        .chip_select    = ssp_chip_select,
 335        .read           = ssp_read,
 336        .write          = ssp_write,
 337        .write_read     = ssp_write_read,
 338};
 339
 340
 341MODULE_AUTHOR("Marc Singer");
 342MODULE_DESCRIPTION("LPD7A40X CPLD SPI driver");
 343MODULE_LICENSE("GPL");
 344