|
@@ -1993,3 +1993,200 @@ static int _reset(struct omap_hwmod *oh)
|
|
|
/**
|
|
|
* _reconfigure_io_chain - clear any I/O chain wakeups and reconfigure chain
|
|
|
*
|
|
|
+ * Call the appropriate PRM function to clear any logged I/O chain
|
|
|
+ * wakeups and to reconfigure the chain. This apparently needs to be
|
|
|
+ * done upon every mux change. Since hwmods can be concurrently
|
|
|
+ * enabled and idled, hold a spinlock around the I/O chain
|
|
|
+ * reconfiguration sequence. No return value.
|
|
|
+ *
|
|
|
+ * XXX When the PRM code is moved to drivers, this function can be removed,
|
|
|
+ * as the PRM infrastructure should abstract this.
|
|
|
+ */
|
|
|
+static void _reconfigure_io_chain(void)
|
|
|
+{
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ spin_lock_irqsave(&io_chain_lock, flags);
|
|
|
+
|
|
|
+ if (cpu_is_omap34xx() && omap3_has_io_chain_ctrl())
|
|
|
+ omap3xxx_prm_reconfigure_io_chain();
|
|
|
+ else if (cpu_is_omap44xx())
|
|
|
+ omap44xx_prm_reconfigure_io_chain();
|
|
|
+
|
|
|
+ spin_unlock_irqrestore(&io_chain_lock, flags);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * _omap4_update_context_lost - increment hwmod context loss counter if
|
|
|
+ * hwmod context was lost, and clear hardware context loss reg
|
|
|
+ * @oh: hwmod to check for context loss
|
|
|
+ *
|
|
|
+ * If the PRCM indicates that the hwmod @oh lost context, increment
|
|
|
+ * our in-memory context loss counter, and clear the RM_*_CONTEXT
|
|
|
+ * bits. No return value.
|
|
|
+ */
|
|
|
+static void _omap4_update_context_lost(struct omap_hwmod *oh)
|
|
|
+{
|
|
|
+ if (oh->prcm.omap4.flags & HWMOD_OMAP4_NO_CONTEXT_LOSS_BIT)
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (!prm_was_any_context_lost_old(oh->clkdm->pwrdm.ptr->prcm_partition,
|
|
|
+ oh->clkdm->pwrdm.ptr->prcm_offs,
|
|
|
+ oh->prcm.omap4.context_offs))
|
|
|
+ return;
|
|
|
+
|
|
|
+ oh->prcm.omap4.context_lost_counter++;
|
|
|
+ prm_clear_context_loss_flags_old(oh->clkdm->pwrdm.ptr->prcm_partition,
|
|
|
+ oh->clkdm->pwrdm.ptr->prcm_offs,
|
|
|
+ oh->prcm.omap4.context_offs);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * _omap4_get_context_lost - get context loss counter for a hwmod
|
|
|
+ * @oh: hwmod to get context loss counter for
|
|
|
+ *
|
|
|
+ * Returns the in-memory context loss counter for a hwmod.
|
|
|
+ */
|
|
|
+static int _omap4_get_context_lost(struct omap_hwmod *oh)
|
|
|
+{
|
|
|
+ return oh->prcm.omap4.context_lost_counter;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * _enable - enable an omap_hwmod
|
|
|
+ * @oh: struct omap_hwmod *
|
|
|
+ *
|
|
|
+ * Enables an omap_hwmod @oh such that the MPU can access the hwmod's
|
|
|
+ * register target. Returns -EINVAL if the hwmod is in the wrong
|
|
|
+ * state or passes along the return value of _wait_target_ready().
|
|
|
+ */
|
|
|
+static int _enable(struct omap_hwmod *oh)
|
|
|
+{
|
|
|
+ int r;
|
|
|
+ int hwsup = 0;
|
|
|
+
|
|
|
+ pr_debug("omap_hwmod: %s: enabling\n", oh->name);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * hwmods with HWMOD_INIT_NO_IDLE flag set are left in enabled
|
|
|
+ * state at init. Now that someone is really trying to enable
|
|
|
+ * them, just ensure that the hwmod mux is set.
|
|
|
+ */
|
|
|
+ if (oh->_int_flags & _HWMOD_SKIP_ENABLE) {
|
|
|
+ /*
|
|
|
+ * If the caller has mux data populated, do the mux'ing
|
|
|
+ * which wouldn't have been done as part of the _enable()
|
|
|
+ * done during setup.
|
|
|
+ */
|
|
|
+ if (oh->mux)
|
|
|
+ omap_hwmod_mux(oh->mux, _HWMOD_STATE_ENABLED);
|
|
|
+
|
|
|
+ oh->_int_flags &= ~_HWMOD_SKIP_ENABLE;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (oh->_state != _HWMOD_STATE_INITIALIZED &&
|
|
|
+ oh->_state != _HWMOD_STATE_IDLE &&
|
|
|
+ oh->_state != _HWMOD_STATE_DISABLED) {
|
|
|
+ WARN(1, "omap_hwmod: %s: enabled state can only be entered from initialized, idle, or disabled state\n",
|
|
|
+ oh->name);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If an IP block contains HW reset lines and all of them are
|
|
|
+ * asserted, we let integration code associated with that
|
|
|
+ * block handle the enable. We've received very little
|
|
|
+ * information on what those driver authors need, and until
|
|
|
+ * detailed information is provided and the driver code is
|
|
|
+ * posted to the public lists, this is probably the best we
|
|
|
+ * can do.
|
|
|
+ */
|
|
|
+ if (_are_all_hardreset_lines_asserted(oh))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ /* Mux pins for device runtime if populated */
|
|
|
+ if (oh->mux && (!oh->mux->enabled ||
|
|
|
+ ((oh->_state == _HWMOD_STATE_IDLE) &&
|
|
|
+ oh->mux->pads_dynamic))) {
|
|
|
+ omap_hwmod_mux(oh->mux, _HWMOD_STATE_ENABLED);
|
|
|
+ _reconfigure_io_chain();
|
|
|
+ }
|
|
|
+
|
|
|
+ _add_initiator_dep(oh, mpu_oh);
|
|
|
+
|
|
|
+ if (oh->clkdm) {
|
|
|
+ /*
|
|
|
+ * A clockdomain must be in SW_SUP before enabling
|
|
|
+ * completely the module. The clockdomain can be set
|
|
|
+ * in HW_AUTO only when the module become ready.
|
|
|
+ */
|
|
|
+ hwsup = clkdm_in_hwsup(oh->clkdm) &&
|
|
|
+ !clkdm_missing_idle_reporting(oh->clkdm);
|
|
|
+ r = clkdm_hwmod_enable(oh->clkdm, oh);
|
|
|
+ if (r) {
|
|
|
+ WARN(1, "omap_hwmod: %s: could not enable clockdomain %s: %d\n",
|
|
|
+ oh->name, oh->clkdm->name, r);
|
|
|
+ return r;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ _enable_clocks(oh);
|
|
|
+ if (soc_ops.enable_module)
|
|
|
+ soc_ops.enable_module(oh);
|
|
|
+
|
|
|
+ if (soc_ops.update_context_lost)
|
|
|
+ soc_ops.update_context_lost(oh);
|
|
|
+
|
|
|
+ r = (soc_ops.wait_target_ready) ? soc_ops.wait_target_ready(oh) :
|
|
|
+ -EINVAL;
|
|
|
+ if (!r) {
|
|
|
+ /*
|
|
|
+ * Set the clockdomain to HW_AUTO only if the target is ready,
|
|
|
+ * assuming that the previous state was HW_AUTO
|
|
|
+ */
|
|
|
+ if (oh->clkdm && hwsup)
|
|
|
+ clkdm_allow_idle(oh->clkdm);
|
|
|
+
|
|
|
+ oh->_state = _HWMOD_STATE_ENABLED;
|
|
|
+
|
|
|
+ /* Access the sysconfig only if the target is ready */
|
|
|
+ if (oh->class->sysc) {
|
|
|
+ if (!(oh->_int_flags & _HWMOD_SYSCONFIG_LOADED))
|
|
|
+ _update_sysc_cache(oh);
|
|
|
+ _enable_sysc(oh);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (soc_ops.disable_module)
|
|
|
+ soc_ops.disable_module(oh);
|
|
|
+ _disable_clocks(oh);
|
|
|
+ pr_debug("omap_hwmod: %s: _wait_target_ready: %d\n",
|
|
|
+ oh->name, r);
|
|
|
+
|
|
|
+ if (oh->clkdm)
|
|
|
+ clkdm_hwmod_disable(oh->clkdm, oh);
|
|
|
+ }
|
|
|
+
|
|
|
+ return r;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * _idle - idle an omap_hwmod
|
|
|
+ * @oh: struct omap_hwmod *
|
|
|
+ *
|
|
|
+ * Idles an omap_hwmod @oh. This should be called once the hwmod has
|
|
|
+ * no further work. Returns -EINVAL if the hwmod is in the wrong
|
|
|
+ * state or returns 0.
|
|
|
+ */
|
|
|
+static int _idle(struct omap_hwmod *oh)
|
|
|
+{
|
|
|
+ pr_debug("omap_hwmod: %s: idling\n", oh->name);
|
|
|
+
|
|
|
+ if (oh->_state != _HWMOD_STATE_ENABLED) {
|
|
|
+ WARN(1, "omap_hwmod: %s: idle state can only be entered from enabled state\n",
|
|
|
+ oh->name);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (_are_all_hardreset_lines_asserted(oh))
|
|
|
+ return 0;
|