Hi Greg,

Access to the m25p80 flash and the mmc/sd card are both
over over the spi bus. This is some crude mechanism to
prevent conflicts between the two. Mike promised something
better in the future and I'll understand if you can't take this
one until then.

Cheers,
Steve

Greg Ungerer wrote:
Hi Steve,

Steve Bennett wrote:
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]>

Why is there mutex calls for the qspi module in platforms mtd
map driver?  This seems very out of place.

And the changes to drivers/spi/spi.c, I can't see that being
accepted in mainline.

Regards
Greg



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

_______________________________________________
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