]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - arch/arm/mach-omap2/omap_hwmod.c
ARM: OMAP: hwmod: add support for selecting mpu_irq for each wakeup pad
[karo-tx-linux.git] / arch / arm / mach-omap2 / omap_hwmod.c
index 6b3088db83b7e916314615b0f19023ab22805e07..7ea3df517d2b53f36263df2e747daf4e8f19fa1d 100644 (file)
 #include <linux/list.h>
 #include <linux/mutex.h>
 #include <linux/spinlock.h>
+#include <linux/slab.h>
 
 #include <plat/common.h>
 #include <plat/cpu.h>
@@ -380,6 +381,51 @@ static int _set_module_autoidle(struct omap_hwmod *oh, u8 autoidle,
        return 0;
 }
 
+/**
+ * _set_idle_ioring_wakeup - enable/disable IO pad wakeup on hwmod idle for mux
+ * @oh: struct omap_hwmod *
+ * @set_wake: bool value indicating to set (true) or clear (false) wakeup enable
+ *
+ * Set or clear the I/O pad wakeup flag in the mux entries for the
+ * hwmod @oh.  This function changes the @oh->mux->pads_dynamic array
+ * in memory.  If the hwmod is currently idled, and the new idle
+ * values don't match the previous ones, this function will also
+ * update the SCM PADCTRL registers.  Otherwise, if the hwmod is not
+ * currently idled, this function won't touch the hardware: the new
+ * mux settings are written to the SCM PADCTRL registers when the
+ * hwmod is idled.  No return value.
+ */
+static void _set_idle_ioring_wakeup(struct omap_hwmod *oh, bool set_wake)
+{
+       struct omap_device_pad *pad;
+       bool change = false;
+       u16 prev_idle;
+       int j;
+
+       if (!oh->mux || !oh->mux->enabled)
+               return;
+
+       for (j = 0; j < oh->mux->nr_pads_dynamic; j++) {
+               pad = oh->mux->pads_dynamic[j];
+
+               if (!(pad->flags & OMAP_DEVICE_PAD_WAKEUP))
+                       continue;
+
+               prev_idle = pad->idle;
+
+               if (set_wake)
+                       pad->idle |= OMAP_WAKEUP_EN;
+               else
+                       pad->idle &= ~OMAP_WAKEUP_EN;
+
+               if (prev_idle != pad->idle)
+                       change = true;
+       }
+
+       if (change && oh->_state == _HWMOD_STATE_IDLE)
+               omap_hwmod_mux(oh->mux, _HWMOD_STATE_IDLE);
+}
+
 /**
  * _enable_wakeup: set OCP_SYSCONFIG.ENAWAKEUP bit in the hardware
  * @oh: struct omap_hwmod *
@@ -749,7 +795,7 @@ static int _count_mpu_irqs(struct omap_hwmod *oh)
                ohii = &oh->mpu_irqs[i++];
        } while (ohii->irq != -1);
 
-       return i;
+       return i-1;
 }
 
 /**
@@ -772,7 +818,7 @@ static int _count_sdma_reqs(struct omap_hwmod *oh)
                ohdi = &oh->sdma_reqs[i++];
        } while (ohdi->dma_req != -1);
 
-       return i;
+       return i-1;
 }
 
 /**
@@ -795,7 +841,7 @@ static int _count_ocp_if_addr_spaces(struct omap_hwmod_ocp_if *os)
                mem = &os->addr[i++];
        } while (mem->pa_start != mem->pa_end);
 
-       return i;
+       return i-1;
 }
 
 /**
@@ -2416,6 +2462,7 @@ int omap_hwmod_enable_wakeup(struct omap_hwmod *oh)
        v = oh->_sysc_cache;
        _enable_wakeup(oh, &v);
        _write_sysconfig(v, oh);
+       _set_idle_ioring_wakeup(oh, true);
        spin_unlock_irqrestore(&oh->_lock, flags);
 
        return 0;
@@ -2446,6 +2493,7 @@ int omap_hwmod_disable_wakeup(struct omap_hwmod *oh)
        v = oh->_sysc_cache;
        _disable_wakeup(oh, &v);
        _write_sysconfig(v, oh);
+       _set_idle_ioring_wakeup(oh, false);
        spin_unlock_irqrestore(&oh->_lock, flags);
 
        return 0;
@@ -2662,3 +2710,57 @@ int omap_hwmod_no_setup_reset(struct omap_hwmod *oh)
 
        return 0;
 }
+
+/**
+ * omap_hwmod_pad_route_irq - route an I/O pad wakeup to a particular MPU IRQ
+ * @oh: struct omap_hwmod * containing hwmod mux entries
+ * @pad_idx: array index in oh->mux of the hwmod mux entry to route wakeup
+ * @irq_idx: the hwmod mpu_irqs array index of the IRQ to trigger on wakeup
+ *
+ * When an I/O pad wakeup arrives for the dynamic or wakeup hwmod mux
+ * entry number @pad_idx for the hwmod @oh, trigger the interrupt
+ * service routine for the hwmod's mpu_irqs array index @irq_idx.  If
+ * this function is not called for a given pad_idx, then the ISR
+ * associated with @oh's first MPU IRQ will be triggered when an I/O
+ * pad wakeup occurs on that pad.  Note that @pad_idx is the index of
+ * the _dynamic or wakeup_ entry: if there are other entries not
+ * marked with OMAP_DEVICE_PAD_WAKEUP or OMAP_DEVICE_PAD_REMUX, these
+ * entries are NOT COUNTED in the dynamic pad index.  This function
+ * must be called separately for each pad that requires its interrupt
+ * to be re-routed this way.  Returns -EINVAL if there is an argument
+ * problem or if @oh does not have hwmod mux entries or MPU IRQs;
+ * returns -ENOMEM if memory cannot be allocated; or 0 upon success.
+ *
+ * XXX This function interface is fragile.  Rather than using array
+ * indexes, which are subject to unpredictable change, it should be
+ * using hwmod IRQ names, and some other stable key for the hwmod mux
+ * pad records.
+ */
+int omap_hwmod_pad_route_irq(struct omap_hwmod *oh, int pad_idx, int irq_idx)
+{
+       int nr_irqs;
+
+       might_sleep();
+
+       if (!oh || !oh->mux || !oh->mpu_irqs || pad_idx < 0 ||
+           pad_idx >= oh->mux->nr_pads_dynamic)
+               return -EINVAL;
+
+       /* Check the number of available mpu_irqs */
+       for (nr_irqs = 0; oh->mpu_irqs[nr_irqs].irq >= 0; nr_irqs++)
+               ;
+
+       if (irq_idx >= nr_irqs)
+               return -EINVAL;
+
+       if (!oh->mux->irqs) {
+               /* XXX What frees this? */
+               oh->mux->irqs = kzalloc(sizeof(int) * oh->mux->nr_pads_dynamic,
+                       GFP_KERNEL);
+               if (!oh->mux->irqs)
+                       return -ENOMEM;
+       }
+       oh->mux->irqs[pad_idx] = irq_idx;
+
+       return 0;
+}