fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / arch / arm / mach-pxa / ssp.c
index 50132a7..6cc2027 100644 (file)
  *
  *  Revision history:
  *   22nd Aug 2003 Initial version.
- *
+ *   20th Dec 2004 Added ssp_config for changing port config without
+ *                 closing the port.
+ *    4th Aug 2005 Added option to disable irq handler registration and
+ *                 cleaned up irq and clock detection.
  */
 
 #include <linux/module.h>
 #include <linux/interrupt.h>
 #include <linux/ioport.h>
 #include <linux/init.h>
+#include <linux/mutex.h>
 #include <asm/io.h>
 #include <asm/irq.h>
 #include <asm/hardware.h>
 #include <asm/arch/ssp.h>
 #include <asm/arch/pxa-regs.h>
 
-static irqreturn_t ssp_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+#define PXA_SSP_PORTS  3
+
+#define TIMEOUT 100000
+
+struct ssp_info_ {
+       int irq;
+       u32 clock;
+};
+
+/*
+ * SSP port clock and IRQ settings
+ */
+static const struct ssp_info_ ssp_info[PXA_SSP_PORTS] = {
+#if defined (CONFIG_PXA27x)
+       {IRQ_SSP,       CKEN23_SSP1},
+       {IRQ_SSP2,      CKEN3_SSP2},
+       {IRQ_SSP3,      CKEN4_SSP3},
+#else
+       {IRQ_SSP,       CKEN3_SSP},
+       {IRQ_NSSP,      CKEN9_NSSP},
+       {IRQ_ASSP,      CKEN10_ASSP},
+#endif
+};
+
+static DEFINE_MUTEX(mutex);
+static int use_count[PXA_SSP_PORTS] = {0, 0, 0};
+
+static irqreturn_t ssp_interrupt(int irq, void *dev_id)
 {
        struct ssp_dev *dev = (struct ssp_dev*) dev_id;
        unsigned int status = SSSR_P(dev->port);
@@ -63,13 +94,18 @@ static irqreturn_t ssp_interrupt(int irq, void *dev_id, struct pt_regs *regs)
  * The caller is expected to perform the necessary locking.
  *
  * Returns:
- *   %-ETIMEDOUT       timeout occurred (for future)
+ *   %-ETIMEDOUT       timeout occurred
  *   0                 success
  */
 int ssp_write_word(struct ssp_dev *dev, u32 data)
 {
-       while (!(SSSR_P(dev->port) & SSSR_TNF))
+       int timeout = TIMEOUT;
+
+       while (!(SSSR_P(dev->port) & SSSR_TNF)) {
+               if (!--timeout)
+                       return -ETIMEDOUT;
                cpu_relax();
+       }
 
        SSDR_P(dev->port) = data;
 
@@ -88,15 +124,21 @@ int ssp_write_word(struct ssp_dev *dev, u32 data)
  * The caller is expected to perform the necessary locking.
  *
  * Returns:
- *   %-ETIMEDOUT       timeout occurred (for future)
+ *   %-ETIMEDOUT       timeout occurred
  *   32-bit data       success
  */
-int ssp_read_word(struct ssp_dev *dev)
+int ssp_read_word(struct ssp_dev *dev, u32 *data)
 {
-       while (!(SSSR_P(dev->port) & SSSR_RNE))
+       int timeout = TIMEOUT;
+
+       while (!(SSSR_P(dev->port) & SSSR_RNE)) {
+               if (!--timeout)
+                       return -ETIMEDOUT;
                cpu_relax();
+       }
 
-       return SSDR_P(dev->port);
+       *data = SSDR_P(dev->port);
+       return 0;
 }
 
 /**
@@ -107,13 +149,21 @@ int ssp_read_word(struct ssp_dev *dev)
  *
  * The caller is expected to perform the necessary locking.
  */
-void ssp_flush(struct ssp_dev *dev)
+int ssp_flush(struct ssp_dev *dev)
 {
+       int timeout = TIMEOUT * 2;
+
        do {
                while (SSSR_P(dev->port) & SSSR_RNE) {
+                       if (!--timeout)
+                               return -ETIMEDOUT;
                        (void) SSDR_P(dev->port);
                }
+               if (!--timeout)
+                       return -ETIMEDOUT;
        } while (SSSR_P(dev->port) & SSSR_BSY);
+
+       return 0;
 }
 
 /**
@@ -170,6 +220,30 @@ void ssp_restore_state(struct ssp_dev *dev, struct ssp_state *ssp)
        SSCR0_P(dev->port) = ssp->cr0;
 }
 
+/**
+ * ssp_config - configure SSP port settings
+ * @mode: port operating mode
+ * @flags: port config flags
+ * @psp_flags: port PSP config flags
+ * @speed: port speed
+ *
+ * Port MUST be disabled by ssp_disable before making any config changes.
+ */
+int ssp_config(struct ssp_dev *dev, u32 mode, u32 flags, u32 psp_flags, u32 speed)
+{
+       dev->mode = mode;
+       dev->flags = flags;
+       dev->psp_flags = psp_flags;
+       dev->speed = speed;
+
+       /* set up port type, speed, port settings */
+       SSCR0_P(dev->port) = (dev->speed | dev->mode);
+       SSCR1_P(dev->port) = dev->flags;
+       SSPSP_P(dev->port) = dev->psp_flags;
+
+       return 0;
+}
+
 /**
  * ssp_init - setup the SSP port
  *
@@ -180,82 +254,46 @@ void ssp_restore_state(struct ssp_dev *dev, struct ssp_state *ssp)
  *   %-EBUSY   if the resources are already in use
  *   %0                on success
  */
-int ssp_init(struct ssp_dev *dev, u32 port, u32 mode, u32 flags, u32 psp_flags,
-                                               u32 speed)
+int ssp_init(struct ssp_dev *dev, u32 port, u32 init_flags)
 {
-       int ret, irq;
+       int ret;
 
-       if (!request_mem_region(__PREG(SSCR0_P(port)), 0x2c, "SSP")) {
+       if (port > PXA_SSP_PORTS || port == 0)
+               return -ENODEV;
+
+       mutex_lock(&mutex);
+       if (use_count[port - 1]) {
+               mutex_unlock(&mutex);
                return -EBUSY;
        }
+       use_count[port - 1]++;
 
-       switch (port) {
-               case 1:
-                       irq = IRQ_SSP;
-                       break;
-#if defined (CONFIG_PXA27x)
-               case 2:
-                       irq = IRQ_SSP2;
-                       break;
-               case 3:
-                       irq = IRQ_SSP3;
-                       break;
-#else
-               case 2:
-                       irq = IRQ_NSSP;
-                       break;
-               case 3:
-                       irq = IRQ_ASSP;
-                       break;
-#endif
-               default:
-                       return -ENODEV;
+       if (!request_mem_region(__PREG(SSCR0_P(port)), 0x2c, "SSP")) {
+               use_count[port - 1]--;
+               mutex_unlock(&mutex);
+               return -EBUSY;
        }
-
        dev->port = port;
-       dev->mode = mode;
-       dev->flags = flags;
-       dev->psp_flags = psp_flags;
-       dev->speed = speed;
 
-       /* set up port type, speed, port settings */
-       SSCR0_P(dev->port) = (dev->speed | dev->mode);
-       SSCR1_P(dev->port) = dev->flags;
-       SSPSP_P(dev->port) = dev->psp_flags;
-
-       ret = request_irq(irq, ssp_interrupt, 0, "SSP", dev);
-       if (ret)
-               goto out_region;
+       /* do we need to get irq */
+       if (!(init_flags & SSP_NO_IRQ)) {
+               ret = request_irq(ssp_info[port-1].irq, ssp_interrupt,
+                               0, "SSP", dev);
+               if (ret)
+                       goto out_region;
+               dev->irq = ssp_info[port-1].irq;
+       } else
+               dev->irq = 0;
 
        /* turn on SSP port clock */
-       switch (dev->port) {
-#if defined (CONFIG_PXA27x)
-               case 1:
-                       pxa_set_cken(CKEN23_SSP1, 1);
-                       break;
-               case 2:
-                       pxa_set_cken(CKEN3_SSP2, 1);
-                       break;
-               case 3:
-                       pxa_set_cken(CKEN4_SSP3, 1);
-                       break;
-#else
-               case 1:
-                       pxa_set_cken(CKEN3_SSP, 1);
-                       break;
-               case 2:
-                       pxa_set_cken(CKEN9_NSSP, 1);
-                       break;
-               case 3:
-                       pxa_set_cken(CKEN10_ASSP, 1);
-                       break;
-#endif
-       }
-
+       pxa_set_cken(ssp_info[port-1].clock, 1);
+       mutex_unlock(&mutex);
        return 0;
 
 out_region:
-       release_mem_region(__PREG(SSCR0_P(dev->port)), 0x2c);
+       release_mem_region(__PREG(SSCR0_P(port)), 0x2c);
+       use_count[port - 1]--;
+       mutex_unlock(&mutex);
        return ret;
 }
 
@@ -266,46 +304,20 @@ out_region:
  */
 void ssp_exit(struct ssp_dev *dev)
 {
-       int irq;
-
+       mutex_lock(&mutex);
        SSCR0_P(dev->port) &= ~SSCR0_SSE;
 
-       /* find irq, save power and turn off SSP port clock */
-       switch (dev->port) {
-#if defined (CONFIG_PXA27x)
-               case 1:
-                       irq = IRQ_SSP;
-                       pxa_set_cken(CKEN23_SSP1, 0);
-                       break;
-               case 2:
-                       irq = IRQ_SSP2;
-                       pxa_set_cken(CKEN3_SSP2, 0);
-                       break;
-               case 3:
-                       irq = IRQ_SSP3;
-                       pxa_set_cken(CKEN4_SSP3, 0);
-                       break;
-#else
-               case 1:
-                       irq = IRQ_SSP;
-                       pxa_set_cken(CKEN3_SSP, 0);
-                       break;
-               case 2:
-                       irq = IRQ_NSSP;
-                       pxa_set_cken(CKEN9_NSSP, 0);
-                       break;
-               case 3:
-                       irq = IRQ_ASSP;
-                       pxa_set_cken(CKEN10_ASSP, 0);
-                       break;
-#endif
-               default:
-                       printk(KERN_WARNING "SSP: tried to close invalid port\n");
-                       return;
+       if (dev->port > PXA_SSP_PORTS || dev->port == 0) {
+               printk(KERN_WARNING "SSP: tried to close invalid port\n");
+               return;
        }
 
-       free_irq(irq, dev);
+       pxa_set_cken(ssp_info[dev->port-1].clock, 0);
+       if (dev->irq)
+               free_irq(dev->irq, dev);
        release_mem_region(__PREG(SSCR0_P(dev->port)), 0x2c);
+       use_count[dev->port - 1]--;
+       mutex_unlock(&mutex);
 }
 
 EXPORT_SYMBOL(ssp_write_word);
@@ -317,3 +329,9 @@ EXPORT_SYMBOL(ssp_save_state);
 EXPORT_SYMBOL(ssp_restore_state);
 EXPORT_SYMBOL(ssp_init);
 EXPORT_SYMBOL(ssp_exit);
+EXPORT_SYMBOL(ssp_config);
+
+MODULE_DESCRIPTION("PXA SSP driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
+