This patch adds support for the SPI driver for Freescale Coldfire QSPI module
in master mode. Tested with the 5282 processor, but should also work with other
Coldfire variants.

Signed-Off-By: Steve Bennett <[EMAIL PROTECTED]>

diff -urN uClinux-dist.orig/linux-2.6.x/drivers/mtd/devices/m25p80.c 
uClinux-dist/linux-2.6.x/drivers/mtd/devices/m25p80.c
--- uClinux-dist.orig/linux-2.6.x/drivers/mtd/devices/m25p80.c  2006-10-09 
10:01:47.000000000 +1000
+++ uClinux-dist/linux-2.6.x/drivers/mtd/devices/m25p80.c       2007-05-11 
16:12:09.000000000 +1000
@@ -201,6 +201,7 @@
        addr = instr->addr;
        len = instr->len;
 
+       qspi_mutex_down("m25p80");
        down(&flash->lock);
 
        /* now erase those sectors */
@@ -216,6 +217,7 @@
        }
 
        up(&flash->lock);
+       qspi_mutex_up("m25p80");
 
        instr->state = MTD_ERASE_DONE;
        mtd_erase_callback(instr);
@@ -260,6 +262,7 @@
        if (retlen)
                *retlen = 0;
 
+       qspi_mutex_down("m25p80");
        down(&flash->lock);
 
        /* Wait till previous write/erase is done. */
@@ -282,6 +285,7 @@
        *retlen = m.actual_length - sizeof(flash->command);
 
        up(&flash->lock);
+       qspi_mutex_up("m25p80");
 
        return 0;
 }
@@ -323,6 +327,7 @@
        t[1].tx_buf = buf;
        spi_message_add_tail(&t[1], &m);
 
+       qspi_mutex_down("m25p80");
        down(&flash->lock);
 
        /* Wait until finished previous write command. */
@@ -385,6 +390,7 @@
        }
 
        up(&flash->lock);
+       qspi_mutex_up("m25p80");
 
        return 0;
 }
diff -urN uClinux-dist.orig/linux-2.6.x/drivers/spi/Kconfig 
uClinux-dist/linux-2.6.x/drivers/spi/Kconfig
--- uClinux-dist.orig/linux-2.6.x/drivers/spi/Kconfig   2006-06-19 
11:02:15.000000000 +1000
+++ uClinux-dist/linux-2.6.x/drivers/spi/Kconfig        2007-05-11 
16:12:05.000000000 +1000
@@ -103,6 +103,15 @@
          GPIO lines to provide the SPI bus. This can be used where
          the inbuilt hardware cannot provide the transfer mode, or
          where the board is using non hardware connected pins.
+         
+config SPI_COLDFIRE
+       tristate "Coldfire QSPI 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.
+
 #
 # Add new SPI master controllers in alphabetical order above this line
 #
diff -urN uClinux-dist.orig/linux-2.6.x/drivers/spi/Makefile 
uClinux-dist/linux-2.6.x/drivers/spi/Makefile
--- uClinux-dist.orig/linux-2.6.x/drivers/spi/Makefile  2006-06-19 
11:02:15.000000000 +1000
+++ uClinux-dist/linux-2.6.x/drivers/spi/Makefile       2007-05-11 
16:12:05.000000000 +1000
@@ -17,6 +17,7 @@
 obj-$(CONFIG_SPI_MPC83xx)              += spi_mpc83xx.o
 obj-$(CONFIG_SPI_S3C24XX_GPIO)         += spi_s3c24xx_gpio.o
 obj-$(CONFIG_SPI_S3C24XX)              += spi_s3c24xx.o
+obj-$(CONFIG_SPI_COLDFIRE)             += spi_coldfire.o
 obj-$(CONFIG_MCFQSPI)                  += mcf_qspi.o
 obj-$(CONFIG_DS1305)                   += DS1305RTC.o
 #      ... add above this line ...
diff -urN uClinux-dist.orig/linux-2.6.x/drivers/spi/spi.c 
uClinux-dist/linux-2.6.x/drivers/spi/spi.c
--- uClinux-dist.orig/linux-2.6.x/drivers/spi/spi.c     2006-11-30 
09:27:55.000000000 +1000
+++ uClinux-dist/linux-2.6.x/drivers/spi/spi.c  2007-05-11 16:12:09.000000000 
+1000
@@ -25,6 +25,40 @@
 #include <linux/cache.h>
 #include <linux/spi/spi.h>
 
+#if 1
+
+EXPORT_SYMBOL(qspi_mutex_down);
+EXPORT_SYMBOL(qspi_mutex_up);
+
+static DECLARE_MUTEX(sem);
+
+ /**
+  * qspi_mutex_down.
+  * get in line for the qspi mutex
+  * the internal kernel calls do not hold the mutex themselves and so down/up
+  * must be called manually. This introduces a new level of complexity,
+  * but is required, as it may be necessary for some drivers to
+  * hold the mutex through more than one transaction.
+  */
+ void qspi_mutex_down(char *s){
+        //printk( "d:%s", s);
+        down_interruptible(&sem);
+        //printk( "-");
+ }
+
+ /**
+  * qspi_mutex up
+  * signal the qspi mutex.
+  * see qspi_mutex_down
+  */
+ void qspi_mutex_up(char *s){
+        //printk( "%s:", s);
+        up(&sem);
+        //printk( "u\n");
+ }
+
+ #endif
+
 
 /* SPI bustype and spi_master class are registered after board init code
  * provides the SPI device tables, ensuring that both are present by the
@@ -360,7 +394,7 @@
        if (!dev)
                return NULL;
 
-       master = kzalloc(size + sizeof *master, SLAB_KERNEL);
+       master = kzalloc(size + sizeof *master, GFP_KERNEL);
        if (!master)
                return NULL;
 
@@ -447,7 +481,9 @@
  */
 void spi_unregister_master(struct spi_master *master)
 {
-       (void) device_for_each_child(master->cdev.dev, NULL, __unregister);
+       int dummy;
+
+       dummy = device_for_each_child(master->cdev.dev, NULL, __unregister);
        class_device_unregister(&master->cdev);
 }
 EXPORT_SYMBOL_GPL(spi_unregister_master);
@@ -463,16 +499,20 @@
  */
 struct spi_master *spi_busnum_to_master(u16 bus_num)
 {
-       if (bus_num) {
-               char                    name[8];
-               struct kobject          *bus;
-
-               snprintf(name, sizeof name, "spi%u", bus_num);
-               bus = kset_find_obj(&spi_master_class.subsys.kset, name);
-               if (bus)
-                       return container_of(bus, struct spi_master, cdev.kobj);
+       struct class_device     *cdev;
+       struct spi_master       *master = NULL;
+       struct spi_master       *m;
+
+       down(&spi_master_class.sem);
+       list_for_each_entry(cdev, &spi_master_class.children, node) {
+               m = container_of(cdev, struct spi_master, cdev);
+               if (m->bus_num == bus_num) {
+                       master = spi_master_get(m);
+                       break;
+               }
        }
-       return NULL;
+       up(&spi_master_class.sem);
+       return master;
 }
 EXPORT_SYMBOL_GPL(spi_busnum_to_master);
 
@@ -607,7 +647,7 @@
 {
        int     status;
 
-       buf = kmalloc(SPI_BUFSIZ, SLAB_KERNEL);
+       buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
        if (!buf) {
                status = -ENOMEM;
                goto err0;
diff -urN uClinux-dist.orig/linux-2.6.x/drivers/spi/spi_coldfire.c 
uClinux-dist/linux-2.6.x/drivers/spi/spi_coldfire.c
--- uClinux-dist.orig/linux-2.6.x/drivers/spi/spi_coldfire.c    1970-01-01 
10:00:00.000000000 +1000
+++ uClinux-dist/linux-2.6.x/drivers/spi/spi_coldfire.c 2007-05-11 
16:12:05.000000000 +1000
@@ -0,0 +1,1000 @@
+/****************************************************************************/
+
+/*
+ *     coldfire.c - Master QSPI controller for the ColdFire processors
+ *
+ *     (C) Copyright 2005, Intec Automation,
+ *                         Mike Lavender ([EMAIL PROTECTED])
+ *
+
+     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/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>
+
+MODULE_AUTHOR("Mike Lavender");
+MODULE_DESCRIPTION("ColdFire QSPI Contoller");
+MODULE_LICENSE("GPL");
+
+/****************************************************************************/
+
+/*
+ * 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;
+
+
+struct chip_data {
+       union {
+               u16 qmr_val;
+               QMR qmr;
+       };
+       union {
+               u16 qdlyr_val;
+               QDLYR qdlyr;
+       };
+       union {
+               u16 qwr_val;
+               QWR qwr;
+       };
+       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;
+       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   */
+       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);
+};
+
+
+
+/****************************************************************************/
+
+/*
+ * 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;
+}
+
+static int write(struct driver_data *drv_data)
+{
+       int tx_count = 0;
+       int cmd_count = 0;
+       int tx_word = ((*drv_data->qmr & QMR_BITS) == QMR_BITS_8) ? 0 : 1;
+
+       // 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;
+               *drv_data->qmr |= (*drv_data->qmr & ~QMR_BITS) | QMR_BITS_8;
+               tx_word = 0;
+       }
+
+       *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;
+
+       return tx_count;
+}
+
+
+static int read(struct driver_data *drv_data)
+{
+       int rx_count = 0;
+       int rx_word = ((*drv_data->qmr & QMR_BITS) == QMR_BITS_8) ? 0 : 1;
+
+       *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++;
+       }
+
+       return rx_count;
+}
+
+
+static inline void qspi_setup_chip(struct driver_data *drv_data)
+{
+       struct chip_data *chip = drv_data->cur_chip;
+
+       *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);
+}
+
+
+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;
+       u16 irq_status = *drv_data->qir;
+
+       /* Clear all flags immediately */
+       *drv_data->qir |= (QIR_SPIF | QIR_ABRT | QIR_WCEF);
+
+       if (!drv_data->cur_msg || !drv_data->cur_msg->state) {
+               printk(KERN_ERR "coldfire-qspi: bad message or transfer "
+               "state in interrupt handler\n");
+               return IRQ_NONE;
+       }
+
+       if (irq_status & QIR_SPIF) {
+               /*
+                * 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;
+
+                       msg->state = next_transfer(drv_data);
+                       msg->actual_length += drv_data->len;
+                } else {
+                       /* not finished yet - keep going */
+                       write(drv_data);
+                       return IRQ_HANDLED;
+               }
+       } else {
+               if (irq_status & QIR_WCEF)
+                       drv_data->wce_cnt++;
+
+               if (irq_status & QIR_ABRT)
+                       drv_data->abrt_cnt++;
+
+               msg->state = ERROR_STATE;
+       }
+
+       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(void * data)
+{
+       struct driver_data *drv_data = data;
+       unsigned long flags;
+
+       /* 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;
+       u32 baud_divisor = 255;
+
+       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;
+       }
+
+       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;
+       }
+
+       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;
+
+       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;
+       }
+
+       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);
+
+       /* 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;
+
+       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 */
+        *drv_data->par = platform_info->par_val;
+        *drv_data->int_icr = platform_info->irq_lp;
+        *drv_data->int_mr &= ~platform_info->irq_mask;
+       
+       /* 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;
+       }
+       
+       printk( "SPI: Coldfire master initialized\n" );
+       //dev_info(&pdev->dev, "driver initialized\n");
+       return status;
+
+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;
+
+       /* 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);
diff -urN uClinux-dist.orig/linux-2.6.x/include/asm-m68knommu/mcfqspi.h 
uClinux-dist/linux-2.6.x/include/asm-m68knommu/mcfqspi.h
--- uClinux-dist.orig/linux-2.6.x/include/asm-m68knommu/mcfqspi.h       
2006-12-12 23:16:12.000000000 +1000
+++ uClinux-dist/linux-2.6.x/include/asm-m68knommu/mcfqspi.h    2007-05-11 
16:12:05.000000000 +1000
@@ -1,30 +1,52 @@
-#if !defined(MCFQSPI_H)
-#define MCFQSPI_H
+/****************************************************************************/
 
-#include <linux/types.h>
+/*
+ *     mcfqspi.c - Master QSPI controller for the ColdFire processors
+ *
+ *     (C) Copyright 2005, Intec Automation,
+ *                         Mike Lavender ([EMAIL PROTECTED])
+ *
+
+     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
+
+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;
+       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;
+};
 
-
-#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 DSP word) */
-#define QSPIIOCS_POLL_MOD       13      /* driver uses polling instead of 
interrupts */
-
-
-typedef struct qspi_read_data {
-        __u32 length;
-        __u8 *buf;                   /* data to send during read */
-        unsigned int loop : 1;
-} qspi_read_data;
-
-
-#endif  /* MCFQSPI_H */
+#endif /*MCFQSPI_H_*/
_______________________________________________
uClinux-dev mailing list
[email protected]
http://mailman.uclinux.org/mailman/listinfo/uclinux-dev
This message was resent by [email protected]
To unsubscribe see:
http://mailman.uclinux.org/mailman/options/uclinux-dev

Reply via email to