|
@@ -1820,3 +1820,176 @@ static int _omap4_disable_module(struct omap_hwmod *oh)
|
|
|
pr_warn("omap_hwmod: %s: _wait_target_disable failed\n",
|
|
|
oh->name);
|
|
|
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * _am33xx_disable_module - enable CLKCTRL modulemode on AM33XX
|
|
|
+ * @oh: struct omap_hwmod *
|
|
|
+ *
|
|
|
+ * Disable the PRCM module mode related to the hwmod @oh.
|
|
|
+ * Return EINVAL if the modulemode is not supported and 0 in case of success.
|
|
|
+ */
|
|
|
+static int _am33xx_disable_module(struct omap_hwmod *oh)
|
|
|
+{
|
|
|
+ int v;
|
|
|
+
|
|
|
+ if (!oh->clkdm || !oh->prcm.omap4.modulemode)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ pr_debug("omap_hwmod: %s: %s\n", oh->name, __func__);
|
|
|
+
|
|
|
+ if (_are_any_hardreset_lines_asserted(oh))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ am33xx_cm_module_disable(oh->clkdm->cm_inst, oh->clkdm->clkdm_offs,
|
|
|
+ oh->prcm.omap4.clkctrl_offs);
|
|
|
+
|
|
|
+ v = _am33xx_wait_target_disable(oh);
|
|
|
+ if (v)
|
|
|
+ pr_warn("omap_hwmod: %s: _wait_target_disable failed\n",
|
|
|
+ oh->name);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * _ocp_softreset - reset an omap_hwmod via the OCP_SYSCONFIG bit
|
|
|
+ * @oh: struct omap_hwmod *
|
|
|
+ *
|
|
|
+ * Resets an omap_hwmod @oh via the OCP_SYSCONFIG bit. hwmod must be
|
|
|
+ * enabled for this to work. Returns -ENOENT if the hwmod cannot be
|
|
|
+ * reset this way, -EINVAL if the hwmod is in the wrong state,
|
|
|
+ * -ETIMEDOUT if the module did not reset in time, or 0 upon success.
|
|
|
+ *
|
|
|
+ * In OMAP3 a specific SYSSTATUS register is used to get the reset status.
|
|
|
+ * Starting in OMAP4, some IPs do not have SYSSTATUS registers and instead
|
|
|
+ * use the SYSCONFIG softreset bit to provide the status.
|
|
|
+ *
|
|
|
+ * Note that some IP like McBSP do have reset control but don't have
|
|
|
+ * reset status.
|
|
|
+ */
|
|
|
+static int _ocp_softreset(struct omap_hwmod *oh)
|
|
|
+{
|
|
|
+ u32 v;
|
|
|
+ int c = 0;
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ if (!oh->class->sysc ||
|
|
|
+ !(oh->class->sysc->sysc_flags & SYSC_HAS_SOFTRESET))
|
|
|
+ return -ENOENT;
|
|
|
+
|
|
|
+ /* clocks must be on for this operation */
|
|
|
+ if (oh->_state != _HWMOD_STATE_ENABLED) {
|
|
|
+ pr_warn("omap_hwmod: %s: reset can only be entered from enabled state\n",
|
|
|
+ oh->name);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* For some modules, all optionnal clocks need to be enabled as well */
|
|
|
+ if (oh->flags & HWMOD_CONTROL_OPT_CLKS_IN_RESET)
|
|
|
+ _enable_optional_clocks(oh);
|
|
|
+
|
|
|
+ pr_debug("omap_hwmod: %s: resetting via OCP SOFTRESET\n", oh->name);
|
|
|
+
|
|
|
+ v = oh->_sysc_cache;
|
|
|
+ ret = _set_softreset(oh, &v);
|
|
|
+ if (ret)
|
|
|
+ goto dis_opt_clks;
|
|
|
+ _write_sysconfig(v, oh);
|
|
|
+
|
|
|
+ if (oh->class->sysc->srst_udelay)
|
|
|
+ udelay(oh->class->sysc->srst_udelay);
|
|
|
+
|
|
|
+ c = _wait_softreset_complete(oh);
|
|
|
+ if (c == MAX_MODULE_SOFTRESET_WAIT)
|
|
|
+ pr_warning("omap_hwmod: %s: softreset failed (waited %d usec)\n",
|
|
|
+ oh->name, MAX_MODULE_SOFTRESET_WAIT);
|
|
|
+ else
|
|
|
+ pr_debug("omap_hwmod: %s: softreset in %d usec\n", oh->name, c);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * XXX add _HWMOD_STATE_WEDGED for modules that don't come back from
|
|
|
+ * _wait_target_ready() or _reset()
|
|
|
+ */
|
|
|
+
|
|
|
+ ret = (c == MAX_MODULE_SOFTRESET_WAIT) ? -ETIMEDOUT : 0;
|
|
|
+
|
|
|
+dis_opt_clks:
|
|
|
+ if (oh->flags & HWMOD_CONTROL_OPT_CLKS_IN_RESET)
|
|
|
+ _disable_optional_clocks(oh);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * _reset - reset an omap_hwmod
|
|
|
+ * @oh: struct omap_hwmod *
|
|
|
+ *
|
|
|
+ * Resets an omap_hwmod @oh. If the module has a custom reset
|
|
|
+ * function pointer defined, then call it to reset the IP block, and
|
|
|
+ * pass along its return value to the caller. Otherwise, if the IP
|
|
|
+ * block has an OCP_SYSCONFIG register with a SOFTRESET bitfield
|
|
|
+ * associated with it, call a function to reset the IP block via that
|
|
|
+ * method, and pass along the return value to the caller. Finally, if
|
|
|
+ * the IP block has some hardreset lines associated with it, assert
|
|
|
+ * all of those, but do _not_ deassert them. (This is because driver
|
|
|
+ * authors have expressed an apparent requirement to control the
|
|
|
+ * deassertion of the hardreset lines themselves.)
|
|
|
+ *
|
|
|
+ * The default software reset mechanism for most OMAP IP blocks is
|
|
|
+ * triggered via the OCP_SYSCONFIG.SOFTRESET bit. However, some
|
|
|
+ * hwmods cannot be reset via this method. Some are not targets and
|
|
|
+ * therefore have no OCP header registers to access. Others (like the
|
|
|
+ * IVA) have idiosyncratic reset sequences. So for these relatively
|
|
|
+ * rare cases, custom reset code can be supplied in the struct
|
|
|
+ * omap_hwmod_class .reset function pointer.
|
|
|
+ *
|
|
|
+ * _set_dmadisable() is called to set the DMADISABLE bit so that it
|
|
|
+ * does not prevent idling of the system. This is necessary for cases
|
|
|
+ * where ROMCODE/BOOTLOADER uses dma and transfers control to the
|
|
|
+ * kernel without disabling dma.
|
|
|
+ *
|
|
|
+ * Passes along the return value from either _ocp_softreset() or the
|
|
|
+ * custom reset function - these must return -EINVAL if the hwmod
|
|
|
+ * cannot be reset this way or if the hwmod is in the wrong state,
|
|
|
+ * -ETIMEDOUT if the module did not reset in time, or 0 upon success.
|
|
|
+ */
|
|
|
+static int _reset(struct omap_hwmod *oh)
|
|
|
+{
|
|
|
+ int i, r;
|
|
|
+
|
|
|
+ pr_debug("omap_hwmod: %s: resetting\n", oh->name);
|
|
|
+
|
|
|
+ if (oh->class->reset) {
|
|
|
+ r = oh->class->reset(oh);
|
|
|
+ } else {
|
|
|
+ if (oh->rst_lines_cnt > 0) {
|
|
|
+ for (i = 0; i < oh->rst_lines_cnt; i++)
|
|
|
+ _assert_hardreset(oh, oh->rst_lines[i].name);
|
|
|
+ return 0;
|
|
|
+ } else {
|
|
|
+ r = _ocp_softreset(oh);
|
|
|
+ if (r == -ENOENT)
|
|
|
+ r = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ _set_dmadisable(oh);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * OCP_SYSCONFIG bits need to be reprogrammed after a
|
|
|
+ * softreset. The _enable() function should be split to avoid
|
|
|
+ * the rewrite of the OCP_SYSCONFIG register.
|
|
|
+ */
|
|
|
+ if (oh->class->sysc) {
|
|
|
+ _update_sysc_cache(oh);
|
|
|
+ _enable_sysc(oh);
|
|
|
+ }
|
|
|
+
|
|
|
+ return r;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * _reconfigure_io_chain - clear any I/O chain wakeups and reconfigure chain
|
|
|
+ *
|