Merge branch 'devel' of master.kernel.org:/home/rmk/linux-2.6-arm
[linux-2.6.git] / arch / arm / plat-omap / mcbsp.c
index e42fa7c..88ac976 100644 (file)
@@ -198,6 +198,170 @@ void omap_mcbsp_config(unsigned int id, const struct omap_mcbsp_reg_cfg *config)
 }
 EXPORT_SYMBOL(omap_mcbsp_config);
 
+#ifdef CONFIG_ARCH_OMAP34XX
+/*
+ * omap_mcbsp_set_tx_threshold configures how to deal
+ * with transmit threshold. the threshold value and handler can be
+ * configure in here.
+ */
+void omap_mcbsp_set_tx_threshold(unsigned int id, u16 threshold)
+{
+       struct omap_mcbsp *mcbsp;
+       void __iomem *io_base;
+
+       if (!cpu_is_omap34xx())
+               return;
+
+       if (!omap_mcbsp_check_valid_id(id)) {
+               printk(KERN_ERR "%s: Invalid id (%d)\n", __func__, id + 1);
+               return;
+       }
+       mcbsp = id_to_mcbsp_ptr(id);
+       io_base = mcbsp->io_base;
+
+       OMAP_MCBSP_WRITE(io_base, THRSH2, threshold);
+}
+EXPORT_SYMBOL(omap_mcbsp_set_tx_threshold);
+
+/*
+ * omap_mcbsp_set_rx_threshold configures how to deal
+ * with receive threshold. the threshold value and handler can be
+ * configure in here.
+ */
+void omap_mcbsp_set_rx_threshold(unsigned int id, u16 threshold)
+{
+       struct omap_mcbsp *mcbsp;
+       void __iomem *io_base;
+
+       if (!cpu_is_omap34xx())
+               return;
+
+       if (!omap_mcbsp_check_valid_id(id)) {
+               printk(KERN_ERR "%s: Invalid id (%d)\n", __func__, id + 1);
+               return;
+       }
+       mcbsp = id_to_mcbsp_ptr(id);
+       io_base = mcbsp->io_base;
+
+       OMAP_MCBSP_WRITE(io_base, THRSH1, threshold);
+}
+EXPORT_SYMBOL(omap_mcbsp_set_rx_threshold);
+
+/*
+ * omap_mcbsp_get_max_tx_thres just return the current configured
+ * maximum threshold for transmission
+ */
+u16 omap_mcbsp_get_max_tx_threshold(unsigned int id)
+{
+       struct omap_mcbsp *mcbsp;
+
+       if (!omap_mcbsp_check_valid_id(id)) {
+               printk(KERN_ERR "%s: Invalid id (%d)\n", __func__, id + 1);
+               return -ENODEV;
+       }
+       mcbsp = id_to_mcbsp_ptr(id);
+
+       return mcbsp->max_tx_thres;
+}
+EXPORT_SYMBOL(omap_mcbsp_get_max_tx_threshold);
+
+/*
+ * omap_mcbsp_get_max_rx_thres just return the current configured
+ * maximum threshold for reception
+ */
+u16 omap_mcbsp_get_max_rx_threshold(unsigned int id)
+{
+       struct omap_mcbsp *mcbsp;
+
+       if (!omap_mcbsp_check_valid_id(id)) {
+               printk(KERN_ERR "%s: Invalid id (%d)\n", __func__, id + 1);
+               return -ENODEV;
+       }
+       mcbsp = id_to_mcbsp_ptr(id);
+
+       return mcbsp->max_rx_thres;
+}
+EXPORT_SYMBOL(omap_mcbsp_get_max_rx_threshold);
+
+/*
+ * omap_mcbsp_get_dma_op_mode just return the current configured
+ * operating mode for the mcbsp channel
+ */
+int omap_mcbsp_get_dma_op_mode(unsigned int id)
+{
+       struct omap_mcbsp *mcbsp;
+       int dma_op_mode;
+
+       if (!omap_mcbsp_check_valid_id(id)) {
+               printk(KERN_ERR "%s: Invalid id (%u)\n", __func__, id + 1);
+               return -ENODEV;
+       }
+       mcbsp = id_to_mcbsp_ptr(id);
+
+       spin_lock_irq(&mcbsp->lock);
+       dma_op_mode = mcbsp->dma_op_mode;
+       spin_unlock_irq(&mcbsp->lock);
+
+       return dma_op_mode;
+}
+EXPORT_SYMBOL(omap_mcbsp_get_dma_op_mode);
+
+static inline void omap34xx_mcbsp_request(struct omap_mcbsp *mcbsp)
+{
+       /*
+        * Enable wakup behavior, smart idle and all wakeups
+        * REVISIT: some wakeups may be unnecessary
+        */
+       if (cpu_is_omap34xx()) {
+               u16 syscon;
+
+               syscon = OMAP_MCBSP_READ(mcbsp->io_base, SYSCON);
+               syscon &= ~(ENAWAKEUP | SIDLEMODE(0x03) | CLOCKACTIVITY(0x03));
+
+               spin_lock_irq(&mcbsp->lock);
+               if (mcbsp->dma_op_mode == MCBSP_DMA_MODE_THRESHOLD) {
+                       syscon |= (ENAWAKEUP | SIDLEMODE(0x02) |
+                                       CLOCKACTIVITY(0x02));
+                       OMAP_MCBSP_WRITE(mcbsp->io_base, WAKEUPEN,
+                                       XRDYEN | RRDYEN);
+               } else {
+                       syscon |= SIDLEMODE(0x01);
+               }
+               spin_unlock_irq(&mcbsp->lock);
+
+               OMAP_MCBSP_WRITE(mcbsp->io_base, SYSCON, syscon);
+       }
+}
+
+static inline void omap34xx_mcbsp_free(struct omap_mcbsp *mcbsp)
+{
+       /*
+        * Disable wakup behavior, smart idle and all wakeups
+        */
+       if (cpu_is_omap34xx()) {
+               u16 syscon;
+
+               syscon = OMAP_MCBSP_READ(mcbsp->io_base, SYSCON);
+               syscon &= ~(ENAWAKEUP | SIDLEMODE(0x03) | CLOCKACTIVITY(0x03));
+               /*
+                * HW bug workaround - If no_idle mode is taken, we need to
+                * go to smart_idle before going to always_idle, or the
+                * device will not hit retention anymore.
+                */
+               syscon |= SIDLEMODE(0x02);
+               OMAP_MCBSP_WRITE(mcbsp->io_base, SYSCON, syscon);
+
+               syscon &= ~(SIDLEMODE(0x03));
+               OMAP_MCBSP_WRITE(mcbsp->io_base, SYSCON, syscon);
+
+               OMAP_MCBSP_WRITE(mcbsp->io_base, WAKEUPEN, 0);
+       }
+}
+#else
+static inline void omap34xx_mcbsp_request(struct omap_mcbsp *mcbsp) {}
+static inline void omap34xx_mcbsp_free(struct omap_mcbsp *mcbsp) {}
+#endif
+
 /*
  * We can choose between IRQ based or polled IO.
  * This needs to be called before omap_mcbsp_request().
@@ -257,6 +421,9 @@ int omap_mcbsp_request(unsigned int id)
        clk_enable(mcbsp->iclk);
        clk_enable(mcbsp->fclk);
 
+       /* Do procedure specific to omap34xx arch, if applicable */
+       omap34xx_mcbsp_request(mcbsp);
+
        /*
         * Make sure that transmitter, receiver and sample-rate generator are
         * not running before activating IRQs.
@@ -305,6 +472,9 @@ void omap_mcbsp_free(unsigned int id)
        if (mcbsp->pdata && mcbsp->pdata->ops && mcbsp->pdata->ops->free)
                mcbsp->pdata->ops->free(id);
 
+       /* Do procedure specific to omap34xx arch, if applicable */
+       omap34xx_mcbsp_free(mcbsp);
+
        clk_disable(mcbsp->fclk);
        clk_disable(mcbsp->iclk);
 
@@ -328,14 +498,15 @@ void omap_mcbsp_free(unsigned int id)
 EXPORT_SYMBOL(omap_mcbsp_free);
 
 /*
- * Here we start the McBSP, by enabling the sample
- * generator, both transmitter and receivers,
- * and the frame sync.
+ * Here we start the McBSP, by enabling transmitter, receiver or both.
+ * If no transmitter or receiver is active prior calling, then sample-rate
+ * generator and frame sync are started.
  */
-void omap_mcbsp_start(unsigned int id)
+void omap_mcbsp_start(unsigned int id, int tx, int rx)
 {
        struct omap_mcbsp *mcbsp;
        void __iomem *io_base;
+       int idle;
        u16 w;
 
        if (!omap_mcbsp_check_valid_id(id)) {
@@ -348,32 +519,58 @@ void omap_mcbsp_start(unsigned int id)
        mcbsp->rx_word_length = (OMAP_MCBSP_READ(io_base, RCR1) >> 5) & 0x7;
        mcbsp->tx_word_length = (OMAP_MCBSP_READ(io_base, XCR1) >> 5) & 0x7;
 
-       /* Start the sample generator */
-       w = OMAP_MCBSP_READ(io_base, SPCR2);
-       OMAP_MCBSP_WRITE(io_base, SPCR2, w | (1 << 6));
+       idle = !((OMAP_MCBSP_READ(io_base, SPCR2) |
+                 OMAP_MCBSP_READ(io_base, SPCR1)) & 1);
+
+       if (idle) {
+               /* Start the sample generator */
+               w = OMAP_MCBSP_READ(io_base, SPCR2);
+               OMAP_MCBSP_WRITE(io_base, SPCR2, w | (1 << 6));
+       }
 
        /* Enable transmitter and receiver */
+       tx &= 1;
        w = OMAP_MCBSP_READ(io_base, SPCR2);
-       OMAP_MCBSP_WRITE(io_base, SPCR2, w | 1);
+       OMAP_MCBSP_WRITE(io_base, SPCR2, w | tx);
 
+       rx &= 1;
        w = OMAP_MCBSP_READ(io_base, SPCR1);
-       OMAP_MCBSP_WRITE(io_base, SPCR1, w | 1);
+       OMAP_MCBSP_WRITE(io_base, SPCR1, w | rx);
 
-       udelay(100);
+       /*
+        * Worst case: CLKSRG*2 = 8000khz: (1/8000) * 2 * 2 usec
+        * REVISIT: 100us may give enough time for two CLKSRG, however
+        * due to some unknown PM related, clock gating etc. reason it
+        * is now at 500us.
+        */
+       udelay(500);
 
-       /* Start frame sync */
-       w = OMAP_MCBSP_READ(io_base, SPCR2);
-       OMAP_MCBSP_WRITE(io_base, SPCR2, w | (1 << 7));
+       if (idle) {
+               /* Start frame sync */
+               w = OMAP_MCBSP_READ(io_base, SPCR2);
+               OMAP_MCBSP_WRITE(io_base, SPCR2, w | (1 << 7));
+       }
+
+       if (cpu_is_omap2430() || cpu_is_omap34xx()) {
+               /* Release the transmitter and receiver */
+               w = OMAP_MCBSP_READ(io_base, XCCR);
+               w &= ~(tx ? XDISABLE : 0);
+               OMAP_MCBSP_WRITE(io_base, XCCR, w);
+               w = OMAP_MCBSP_READ(io_base, RCCR);
+               w &= ~(rx ? RDISABLE : 0);
+               OMAP_MCBSP_WRITE(io_base, RCCR, w);
+       }
 
        /* Dump McBSP Regs */
        omap_mcbsp_dump_reg(id);
 }
 EXPORT_SYMBOL(omap_mcbsp_start);
 
-void omap_mcbsp_stop(unsigned int id)
+void omap_mcbsp_stop(unsigned int id, int tx, int rx)
 {
        struct omap_mcbsp *mcbsp;
        void __iomem *io_base;
+       int idle;
        u16 w;
 
        if (!omap_mcbsp_check_valid_id(id)) {
@@ -385,16 +582,33 @@ void omap_mcbsp_stop(unsigned int id)
        io_base = mcbsp->io_base;
 
        /* Reset transmitter */
+       tx &= 1;
+       if (cpu_is_omap2430() || cpu_is_omap34xx()) {
+               w = OMAP_MCBSP_READ(io_base, XCCR);
+               w |= (tx ? XDISABLE : 0);
+               OMAP_MCBSP_WRITE(io_base, XCCR, w);
+       }
        w = OMAP_MCBSP_READ(io_base, SPCR2);
-       OMAP_MCBSP_WRITE(io_base, SPCR2, w & ~(1));
+       OMAP_MCBSP_WRITE(io_base, SPCR2, w & ~tx);
 
        /* Reset receiver */
+       rx &= 1;
+       if (cpu_is_omap2430() || cpu_is_omap34xx()) {
+               w = OMAP_MCBSP_READ(io_base, RCCR);
+               w |= (tx ? RDISABLE : 0);
+               OMAP_MCBSP_WRITE(io_base, RCCR, w);
+       }
        w = OMAP_MCBSP_READ(io_base, SPCR1);
-       OMAP_MCBSP_WRITE(io_base, SPCR1, w & ~(1));
+       OMAP_MCBSP_WRITE(io_base, SPCR1, w & ~rx);
 
-       /* Reset the sample rate generator */
-       w = OMAP_MCBSP_READ(io_base, SPCR2);
-       OMAP_MCBSP_WRITE(io_base, SPCR2, w & ~(1 << 6));
+       idle = !((OMAP_MCBSP_READ(io_base, SPCR2) |
+                 OMAP_MCBSP_READ(io_base, SPCR1)) & 1);
+
+       if (idle) {
+               /* Reset the sample rate generator */
+               w = OMAP_MCBSP_READ(io_base, SPCR2);
+               OMAP_MCBSP_WRITE(io_base, SPCR2, w & ~(1 << 6));
+       }
 }
 EXPORT_SYMBOL(omap_mcbsp_stop);
 
@@ -883,6 +1097,149 @@ void omap_mcbsp_set_spi_mode(unsigned int id,
 }
 EXPORT_SYMBOL(omap_mcbsp_set_spi_mode);
 
+#ifdef CONFIG_ARCH_OMAP34XX
+#define max_thres(m)                   (mcbsp->pdata->buffer_size)
+#define valid_threshold(m, val)                ((val) <= max_thres(m))
+#define THRESHOLD_PROP_BUILDER(prop)                                   \
+static ssize_t prop##_show(struct device *dev,                         \
+                       struct device_attribute *attr, char *buf)       \
+{                                                                      \
+       struct omap_mcbsp *mcbsp = dev_get_drvdata(dev);                \
+                                                                       \
+       return sprintf(buf, "%u\n", mcbsp->prop);                       \
+}                                                                      \
+                                                                       \
+static ssize_t prop##_store(struct device *dev,                                \
+                               struct device_attribute *attr,          \
+                               const char *buf, size_t size)           \
+{                                                                      \
+       struct omap_mcbsp *mcbsp = dev_get_drvdata(dev);                \
+       unsigned long val;                                              \
+       int status;                                                     \
+                                                                       \
+       status = strict_strtoul(buf, 0, &val);                          \
+       if (status)                                                     \
+               return status;                                          \
+                                                                       \
+       if (!valid_threshold(mcbsp, val))                               \
+               return -EDOM;                                           \
+                                                                       \
+       mcbsp->prop = val;                                              \
+       return size;                                                    \
+}                                                                      \
+                                                                       \
+static DEVICE_ATTR(prop, 0644, prop##_show, prop##_store);
+
+THRESHOLD_PROP_BUILDER(max_tx_thres);
+THRESHOLD_PROP_BUILDER(max_rx_thres);
+
+static const char *dma_op_modes[] = {
+       "element", "threshold", "frame",
+};
+
+static ssize_t dma_op_mode_show(struct device *dev,
+                       struct device_attribute *attr, char *buf)
+{
+       struct omap_mcbsp *mcbsp = dev_get_drvdata(dev);
+       int dma_op_mode, i = 0;
+       ssize_t len = 0;
+       const char * const *s;
+
+       spin_lock_irq(&mcbsp->lock);
+       dma_op_mode = mcbsp->dma_op_mode;
+       spin_unlock_irq(&mcbsp->lock);
+
+       for (s = &dma_op_modes[i]; i < ARRAY_SIZE(dma_op_modes); s++, i++) {
+               if (dma_op_mode == i)
+                       len += sprintf(buf + len, "[%s] ", *s);
+               else
+                       len += sprintf(buf + len, "%s ", *s);
+       }
+       len += sprintf(buf + len, "\n");
+
+       return len;
+}
+
+static ssize_t dma_op_mode_store(struct device *dev,
+                               struct device_attribute *attr,
+                               const char *buf, size_t size)
+{
+       struct omap_mcbsp *mcbsp = dev_get_drvdata(dev);
+       const char * const *s;
+       int i = 0;
+
+       for (s = &dma_op_modes[i]; i < ARRAY_SIZE(dma_op_modes); s++, i++)
+               if (sysfs_streq(buf, *s))
+                       break;
+
+       if (i == ARRAY_SIZE(dma_op_modes))
+               return -EINVAL;
+
+       spin_lock_irq(&mcbsp->lock);
+       if (!mcbsp->free) {
+               size = -EBUSY;
+               goto unlock;
+       }
+       mcbsp->dma_op_mode = i;
+
+unlock:
+       spin_unlock_irq(&mcbsp->lock);
+
+       return size;
+}
+
+static DEVICE_ATTR(dma_op_mode, 0644, dma_op_mode_show, dma_op_mode_store);
+
+static const struct attribute *additional_attrs[] = {
+       &dev_attr_max_tx_thres.attr,
+       &dev_attr_max_rx_thres.attr,
+       &dev_attr_dma_op_mode.attr,
+       NULL,
+};
+
+static const struct attribute_group additional_attr_group = {
+       .attrs = (struct attribute **)additional_attrs,
+};
+
+static inline int __devinit omap_additional_add(struct device *dev)
+{
+       return sysfs_create_group(&dev->kobj, &additional_attr_group);
+}
+
+static inline void __devexit omap_additional_remove(struct device *dev)
+{
+       sysfs_remove_group(&dev->kobj, &additional_attr_group);
+}
+
+static inline void __devinit omap34xx_device_init(struct omap_mcbsp *mcbsp)
+{
+       mcbsp->dma_op_mode = MCBSP_DMA_MODE_ELEMENT;
+       if (cpu_is_omap34xx()) {
+               mcbsp->max_tx_thres = max_thres(mcbsp);
+               mcbsp->max_rx_thres = max_thres(mcbsp);
+               /*
+                * REVISIT: Set dmap_op_mode to THRESHOLD as default
+                * for mcbsp2 instances.
+                */
+               if (omap_additional_add(mcbsp->dev))
+                       dev_warn(mcbsp->dev,
+                               "Unable to create additional controls\n");
+       } else {
+               mcbsp->max_tx_thres = -EINVAL;
+               mcbsp->max_rx_thres = -EINVAL;
+       }
+}
+
+static inline void __devexit omap34xx_device_exit(struct omap_mcbsp *mcbsp)
+{
+       if (cpu_is_omap34xx())
+               omap_additional_remove(mcbsp->dev);
+}
+#else
+static inline void __devinit omap34xx_device_init(struct omap_mcbsp *mcbsp) {}
+static inline void __devexit omap34xx_device_exit(struct omap_mcbsp *mcbsp) {}
+#endif /* CONFIG_ARCH_OMAP34XX */
+
 /*
  * McBSP1 and McBSP3 are directly mapped on 1610 and 1510.
  * 730 has only 2 McBSP, and both of them are MPU peripherals.
@@ -953,6 +1310,10 @@ static int __devinit omap_mcbsp_probe(struct platform_device *pdev)
        mcbsp->dev = &pdev->dev;
        mcbsp_ptr[id] = mcbsp;
        platform_set_drvdata(pdev, mcbsp);
+
+       /* Initialize mcbsp properties for OMAP34XX if needed / applicable */
+       omap34xx_device_init(mcbsp);
+
        return 0;
 
 err_fclk:
@@ -976,6 +1337,8 @@ static int __devexit omap_mcbsp_remove(struct platform_device *pdev)
                                mcbsp->pdata->ops->free)
                        mcbsp->pdata->ops->free(mcbsp->id);
 
+               omap34xx_device_exit(mcbsp);
+
                clk_disable(mcbsp->fclk);
                clk_disable(mcbsp->iclk);
                clk_put(mcbsp->fclk);