]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - arch/arm/mach-omap2/omap_hwmod.c
Merge branch 'idle-release' of git://git.kernel.org/pub/scm/linux/kernel/git/lenb...
[mv-sheeva.git] / arch / arm / mach-omap2 / omap_hwmod.c
index f4a569c747000b6cfa9c9cf3e6029dcac164b23e..5a30658444d0f84d8a8c8bbb7553ee9db07a5959 100644 (file)
  * it under the terms of the GNU General Public License version 2 as
  * published by the Free Software Foundation.
  *
- * This code manages "OMAP modules" (on-chip devices) and their
- * integration with Linux device driver and bus code.
- *
- * References:
+ * Introduction
+ * ------------
+ * One way to view an OMAP SoC is as a collection of largely unrelated
+ * IP blocks connected by interconnects.  The IP blocks include
+ * devices such as ARM processors, audio serial interfaces, UARTs,
+ * etc.  Some of these devices, like the DSP, are created by TI;
+ * others, like the SGX, largely originate from external vendors.  In
+ * TI's documentation, on-chip devices are referred to as "OMAP
+ * modules."  Some of these IP blocks are identical across several
+ * OMAP versions.  Others are revised frequently.
+ *
+ * These OMAP modules are tied together by various interconnects.
+ * Most of the address and data flow between modules is via OCP-based
+ * interconnects such as the L3 and L4 buses; but there are other
+ * interconnects that distribute the hardware clock tree, handle idle
+ * and reset signaling, supply power, and connect the modules to
+ * various pads or balls on the OMAP package.
+ *
+ * OMAP hwmod provides a consistent way to describe the on-chip
+ * hardware blocks and their integration into the rest of the chip.
+ * This description can be automatically generated from the TI
+ * hardware database.  OMAP hwmod provides a standard, consistent API
+ * to reset, enable, idle, and disable these hardware blocks.  And
+ * hwmod provides a way for other core code, such as the Linux device
+ * code or the OMAP power management and address space mapping code,
+ * to query the hardware database.
+ *
+ * Using hwmod
+ * -----------
+ * Drivers won't call hwmod functions directly.  That is done by the
+ * omap_device code, and in rare occasions, by custom integration code
+ * in arch/arm/ *omap*.  The omap_device code includes functions to
+ * build a struct platform_device using omap_hwmod data, and that is
+ * currently how hwmod data is communicated to drivers and to the
+ * Linux driver model.  Most drivers will call omap_hwmod functions only
+ * indirectly, via pm_runtime*() functions.
+ *
+ * From a layering perspective, here is where the OMAP hwmod code
+ * fits into the kernel software stack:
+ *
+ *            +-------------------------------+
+ *            |      Device driver code       |
+ *            |      (e.g., drivers/)         |
+ *            +-------------------------------+
+ *            |      Linux driver model       |
+ *            |     (platform_device /        |
+ *            |  platform_driver data/code)   |
+ *            +-------------------------------+
+ *            | OMAP core-driver integration  |
+ *            |(arch/arm/mach-omap2/devices.c)|
+ *            +-------------------------------+
+ *            |      omap_device code         |
+ *            | (../plat-omap/omap_device.c)  |
+ *            +-------------------------------+
+ *   ---->    |    omap_hwmod code/data       |    <-----
+ *            | (../mach-omap2/omap_hwmod*)   |
+ *            +-------------------------------+
+ *            | OMAP clock/PRCM/register fns  |
+ *            | (__raw_{read,write}l, clk*)   |
+ *            +-------------------------------+
+ *
+ * Device drivers should not contain any OMAP-specific code or data in
+ * them.  They should only contain code to operate the IP block that
+ * the driver is responsible for.  This is because these IP blocks can
+ * also appear in other SoCs, either from TI (such as DaVinci) or from
+ * other manufacturers; and drivers should be reusable across other
+ * platforms.
+ *
+ * The OMAP hwmod code also will attempt to reset and idle all on-chip
+ * devices upon boot.  The goal here is for the kernel to be
+ * completely self-reliant and independent from bootloaders.  This is
+ * to ensure a repeatable configuration, both to ensure consistent
+ * runtime behavior, and to make it easier for others to reproduce
+ * bugs.
+ *
+ * OMAP module activity states
+ * ---------------------------
+ * The hwmod code considers modules to be in one of several activity
+ * states.  IP blocks start out in an UNKNOWN state, then once they
+ * are registered via the hwmod code, proceed to the REGISTERED state.
+ * Once their clock names are resolved to clock pointers, the module
+ * enters the CLKS_INITED state; and finally, once the module has been
+ * reset and the integration registers programmed, the INITIALIZED state
+ * is entered.  The hwmod code will then place the module into either
+ * the IDLE state to save power, or in the case of a critical system
+ * module, the ENABLED state.
+ *
+ * OMAP core integration code can then call omap_hwmod*() functions
+ * directly to move the module between the IDLE, ENABLED, and DISABLED
+ * states, as needed.  This is done during both the PM idle loop, and
+ * in the OMAP core integration code's implementation of the PM runtime
+ * functions.
+ *
+ * References
+ * ----------
+ * This is a partial list.
  * - OMAP2420 Multimedia Processor Silicon Revision 2.1.1, 2.2 (SWPU064)
  * - OMAP2430 Multimedia Device POP Silicon Revision 2.1 (SWPU090)
  * - OMAP34xx Multimedia Device Silicon Revision 3.1 (SWPU108)
@@ -92,7 +184,7 @@ static int _update_sysc_cache(struct omap_hwmod *oh)
 
        /* XXX ensure module interface clock is up */
 
-       oh->_sysc_cache = omap_hwmod_readl(oh, oh->class->sysc->sysc_offs);
+       oh->_sysc_cache = omap_hwmod_read(oh, oh->class->sysc->sysc_offs);
 
        if (!(oh->class->sysc->sysc_flags & SYSC_NO_CACHE))
                oh->_int_flags |= _HWMOD_SYSCONFIG_LOADED;
@@ -119,7 +211,7 @@ static void _write_sysconfig(u32 v, struct omap_hwmod *oh)
 
        if (oh->_sysc_cache != v) {
                oh->_sysc_cache = v;
-               omap_hwmod_writel(v, oh, oh->class->sysc->sysc_offs);
+               omap_hwmod_write(v, oh, oh->class->sysc->sysc_offs);
        }
 }
 
@@ -546,6 +638,36 @@ static int _disable_clocks(struct omap_hwmod *oh)
        return 0;
 }
 
+static void _enable_optional_clocks(struct omap_hwmod *oh)
+{
+       struct omap_hwmod_opt_clk *oc;
+       int i;
+
+       pr_debug("omap_hwmod: %s: enabling optional clocks\n", oh->name);
+
+       for (i = oh->opt_clks_cnt, oc = oh->opt_clks; i > 0; i--, oc++)
+               if (oc->_clk) {
+                       pr_debug("omap_hwmod: enable %s:%s\n", oc->role,
+                                oc->_clk->name);
+                       clk_enable(oc->_clk);
+               }
+}
+
+static void _disable_optional_clocks(struct omap_hwmod *oh)
+{
+       struct omap_hwmod_opt_clk *oc;
+       int i;
+
+       pr_debug("omap_hwmod: %s: disabling optional clocks\n", oh->name);
+
+       for (i = oh->opt_clks_cnt, oc = oh->opt_clks; i > 0; i--, oc++)
+               if (oc->_clk) {
+                       pr_debug("omap_hwmod: disable %s:%s\n", oc->role,
+                                oc->_clk->name);
+                       clk_disable(oc->_clk);
+               }
+}
+
 /**
  * _find_mpu_port_index - find hwmod OCP slave port ID intended for MPU use
  * @oh: struct omap_hwmod *
@@ -624,7 +746,7 @@ static void __iomem *_find_mpu_rt_base(struct omap_hwmod *oh, u8 index)
 }
 
 /**
- * _sysc_enable - try to bring a module out of idle via OCP_SYSCONFIG
+ * _enable_sysc - try to bring a module out of idle via OCP_SYSCONFIG
  * @oh: struct omap_hwmod *
  *
  * If module is marked as SWSUP_SIDLE, force the module out of slave
@@ -632,7 +754,7 @@ static void __iomem *_find_mpu_rt_base(struct omap_hwmod *oh, u8 index)
  * as SWSUP_MSUSPEND, force the module out of master standby;
  * otherwise, configure it for smart-standby.  No return value.
  */
-static void _sysc_enable(struct omap_hwmod *oh)
+static void _enable_sysc(struct omap_hwmod *oh)
 {
        u8 idlemode, sf;
        u32 v;
@@ -655,14 +777,6 @@ static void _sysc_enable(struct omap_hwmod *oh)
                _set_master_standbymode(oh, idlemode, &v);
        }
 
-       if (sf & SYSC_HAS_AUTOIDLE) {
-               idlemode = (oh->flags & HWMOD_NO_OCP_AUTOIDLE) ?
-                       0 : 1;
-               _set_module_autoidle(oh, idlemode, &v);
-       }
-
-       /* XXX OCP ENAWAKEUP bit? */
-
        /*
         * XXX The clock framework should handle this, by
         * calling into this code.  But this must wait until the
@@ -673,10 +787,25 @@ static void _sysc_enable(struct omap_hwmod *oh)
                _set_clockactivity(oh, oh->class->sysc->clockact, &v);
 
        _write_sysconfig(v, oh);
+
+       /* If slave is in SMARTIDLE, also enable wakeup */
+       if ((sf & SYSC_HAS_SIDLEMODE) && !(oh->flags & HWMOD_SWSUP_SIDLE))
+               _enable_wakeup(oh);
+
+       /*
+        * Set the autoidle bit only after setting the smartidle bit
+        * Setting this will not have any impact on the other modules.
+        */
+       if (sf & SYSC_HAS_AUTOIDLE) {
+               idlemode = (oh->flags & HWMOD_NO_OCP_AUTOIDLE) ?
+                       0 : 1;
+               _set_module_autoidle(oh, idlemode, &v);
+               _write_sysconfig(v, oh);
+       }
 }
 
 /**
- * _sysc_idle - try to put a module into idle via OCP_SYSCONFIG
+ * _idle_sysc - try to put a module into idle via OCP_SYSCONFIG
  * @oh: struct omap_hwmod *
  *
  * If module is marked as SWSUP_SIDLE, force the module into slave
@@ -684,7 +813,7 @@ static void _sysc_enable(struct omap_hwmod *oh)
  * as SWSUP_MSUSPEND, force the module into master standby; otherwise,
  * configure it for smart-standby.  No return value.
  */
-static void _sysc_idle(struct omap_hwmod *oh)
+static void _idle_sysc(struct omap_hwmod *oh)
 {
        u8 idlemode, sf;
        u32 v;
@@ -711,13 +840,13 @@ static void _sysc_idle(struct omap_hwmod *oh)
 }
 
 /**
- * _sysc_shutdown - force a module into idle via OCP_SYSCONFIG
+ * _shutdown_sysc - force a module into idle via OCP_SYSCONFIG
  * @oh: struct omap_hwmod *
  *
  * Force the module into slave idle and master suspend. No return
  * value.
  */
-static void _sysc_shutdown(struct omap_hwmod *oh)
+static void _shutdown_sysc(struct omap_hwmod *oh)
 {
        u32 v;
        u8 sf;
@@ -967,39 +1096,56 @@ static int _read_hardreset(struct omap_hwmod *oh, const char *name)
  * enabled for this to work.  Returns -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.
+ *
+ * In OMAP3 a specific SYSSTATUS register is used to get the reset status.
+ * Starting in OMAP4, some IPs does not have SYSSTATUS register and instead
+ * use the SYSCONFIG softreset bit to provide the status.
+ *
+ * Note that some IP like McBSP does have a reset control but no reset status.
  */
 static int _reset(struct omap_hwmod *oh)
 {
-       u32 r, v;
+       u32 v;
        int c = 0;
+       int ret = 0;
 
        if (!oh->class->sysc ||
-           !(oh->class->sysc->sysc_flags & SYSC_HAS_SOFTRESET) ||
-           (oh->class->sysc->sysc_flags & SYSS_MISSING))
+           !(oh->class->sysc->sysc_flags & SYSC_HAS_SOFTRESET))
                return -EINVAL;
 
        /* clocks must be on for this operation */
        if (oh->_state != _HWMOD_STATE_ENABLED) {
-               WARN(1, "omap_hwmod: %s: reset can only be entered from "
-                    "enabled state\n", oh->name);
+               pr_warning("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\n", oh->name);
 
        v = oh->_sysc_cache;
-       r = _set_softreset(oh, &v);
-       if (r)
-               return r;
+       ret = _set_softreset(oh, &v);
+       if (ret)
+               goto dis_opt_clks;
        _write_sysconfig(v, oh);
 
-       omap_test_timeout((omap_hwmod_readl(oh, oh->class->sysc->syss_offs) &
-                          SYSS_RESETDONE_MASK),
-                         MAX_MODULE_SOFTRESET_WAIT, c);
+       if (oh->class->sysc->sysc_flags & SYSS_HAS_RESET_STATUS)
+               omap_test_timeout((omap_hwmod_read(oh,
+                                                   oh->class->sysc->syss_offs)
+                                  & SYSS_RESETDONE_MASK),
+                                 MAX_MODULE_SOFTRESET_WAIT, c);
+       else if (oh->class->sysc->sysc_flags & SYSC_HAS_RESET_STATUS)
+               omap_test_timeout(!(omap_hwmod_read(oh,
+                                                    oh->class->sysc->sysc_offs)
+                                  & SYSC_TYPE2_SOFTRESET_MASK),
+                                 MAX_MODULE_SOFTRESET_WAIT, c);
 
        if (c == MAX_MODULE_SOFTRESET_WAIT)
-               WARN(1, "omap_hwmod: %s: softreset failed (waited %d usec)\n",
-                    oh->name, 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);
 
@@ -1008,7 +1154,13 @@ static int _reset(struct omap_hwmod *oh)
         * _wait_target_ready() or _reset()
         */
 
-       return (c == MAX_MODULE_SOFTRESET_WAIT) ? -ETIMEDOUT : 0;
+       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;
 }
 
 /**
@@ -1016,8 +1168,11 @@ static int _reset(struct omap_hwmod *oh)
  * @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().
+ * register target.  (This function has a full name --
+ * _omap_hwmod_enable() rather than simply _enable() -- because it is
+ * currently required by the pm34xx.c idle loop.)  Returns -EINVAL if
+ * the hwmod is in the wrong state or passes along the return value of
+ * _wait_target_ready().
  */
 int _omap_hwmod_enable(struct omap_hwmod *oh)
 {
@@ -1055,7 +1210,7 @@ int _omap_hwmod_enable(struct omap_hwmod *oh)
                if (oh->class->sysc) {
                        if (!(oh->_int_flags & _HWMOD_SYSCONFIG_LOADED))
                                _update_sysc_cache(oh);
-                       _sysc_enable(oh);
+                       _enable_sysc(oh);
                }
        } else {
                pr_debug("omap_hwmod: %s: _wait_target_ready: %d\n",
@@ -1066,12 +1221,14 @@ int _omap_hwmod_enable(struct omap_hwmod *oh)
 }
 
 /**
- * _idle - idle an omap_hwmod
+ * _omap_hwmod_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.
+ * no further work.  (This function has a full name --
+ * _omap_hwmod_idle() rather than simply _idle() -- because it is
+ * currently required by the pm34xx.c idle loop.)  Returns -EINVAL if
+ * the hwmod is in the wrong state or returns 0.
  */
 int _omap_hwmod_idle(struct omap_hwmod *oh)
 {
@@ -1084,7 +1241,7 @@ int _omap_hwmod_idle(struct omap_hwmod *oh)
        pr_debug("omap_hwmod: %s: idling\n", oh->name);
 
        if (oh->class->sysc)
-               _sysc_idle(oh);
+               _idle_sysc(oh);
        _del_initiator_dep(oh, mpu_oh);
        _disable_clocks(oh);
 
@@ -1114,7 +1271,7 @@ static int _shutdown(struct omap_hwmod *oh)
        pr_debug("omap_hwmod: %s: disabling\n", oh->name);
 
        if (oh->class->sysc)
-               _sysc_shutdown(oh);
+               _shutdown_sysc(oh);
 
        /*
         * If an IP contains only one HW reset line, then assert it
@@ -1198,16 +1355,16 @@ static int _setup(struct omap_hwmod *oh, void *data)
        }
 
        if (!(oh->flags & HWMOD_INIT_NO_RESET)) {
+               _reset(oh);
+
                /*
-                * XXX Do the OCP_SYSCONFIG bits need to be
-                * reprogrammed after a reset?  If not, then this can
-                * be removed.  If they do, then probably the
-                * _omap_hwmod_enable() function should be split to avoid the
-                * rewrite of the OCP_SYSCONFIG register.
+                * OCP_SYSCONFIG bits need to be reprogrammed after a softreset.
+                * The _omap_hwmod_enable() function should be split to
+                * avoid the rewrite of the OCP_SYSCONFIG register.
                 */
                if (oh->class->sysc) {
                        _update_sysc_cache(oh);
-                       _sysc_enable(oh);
+                       _enable_sysc(oh);
                }
        }
 
@@ -1221,14 +1378,20 @@ static int _setup(struct omap_hwmod *oh, void *data)
 
 /* Public functions */
 
-u32 omap_hwmod_readl(struct omap_hwmod *oh, u16 reg_offs)
+u32 omap_hwmod_read(struct omap_hwmod *oh, u16 reg_offs)
 {
-       return __raw_readl(oh->_mpu_rt_va + reg_offs);
+       if (oh->flags & HWMOD_16BIT_REG)
+               return __raw_readw(oh->_mpu_rt_va + reg_offs);
+       else
+               return __raw_readl(oh->_mpu_rt_va + reg_offs);
 }
 
-void omap_hwmod_writel(u32 v, struct omap_hwmod *oh, u16 reg_offs)
+void omap_hwmod_write(u32 v, struct omap_hwmod *oh, u16 reg_offs)
 {
-       __raw_writel(v, oh->_mpu_rt_va + reg_offs);
+       if (oh->flags & HWMOD_16BIT_REG)
+               __raw_writew(v, oh->_mpu_rt_va + reg_offs);
+       else
+               __raw_writel(v, oh->_mpu_rt_va + reg_offs);
 }
 
 /**
@@ -1463,7 +1626,7 @@ int omap_hwmod_unregister(struct omap_hwmod *oh)
  * omap_hwmod_enable - enable an omap_hwmod
  * @oh: struct omap_hwmod *
  *
- * Enable an omap_hwomd @oh.  Intended to be called by omap_device_enable().
+ * Enable an omap_hwmod @oh.  Intended to be called by omap_device_enable().
  * Returns -EINVAL on error or passes along the return value from _enable().
  */
 int omap_hwmod_enable(struct omap_hwmod *oh)
@@ -1485,7 +1648,7 @@ int omap_hwmod_enable(struct omap_hwmod *oh)
  * omap_hwmod_idle - idle an omap_hwmod
  * @oh: struct omap_hwmod *
  *
- * Idle an omap_hwomd @oh.  Intended to be called by omap_device_idle().
+ * Idle an omap_hwmod @oh.  Intended to be called by omap_device_idle().
  * Returns -EINVAL on error or passes along the return value from _idle().
  */
 int omap_hwmod_idle(struct omap_hwmod *oh)
@@ -1504,7 +1667,7 @@ int omap_hwmod_idle(struct omap_hwmod *oh)
  * omap_hwmod_shutdown - shutdown an omap_hwmod
  * @oh: struct omap_hwmod *
  *
- * Shutdown an omap_hwomd @oh.  Intended to be called by
+ * Shutdown an omap_hwmod @oh.  Intended to be called by
  * omap_device_shutdown().  Returns -EINVAL on error or passes along
  * the return value from _shutdown().
  */
@@ -1575,7 +1738,7 @@ void omap_hwmod_ocp_barrier(struct omap_hwmod *oh)
         * Forces posted writes to complete on the OCP thread handling
         * register writes
         */
-       omap_hwmod_readl(oh, oh->class->sysc->sysc_offs);
+       omap_hwmod_read(oh, oh->class->sysc->sysc_offs);
 }
 
 /**
@@ -1828,6 +1991,84 @@ int omap_hwmod_disable_wakeup(struct omap_hwmod *oh)
        return 0;
 }
 
+/**
+ * omap_hwmod_assert_hardreset - assert the HW reset line of submodules
+ * contained in the hwmod module.
+ * @oh: struct omap_hwmod *
+ * @name: name of the reset line to lookup and assert
+ *
+ * Some IP like dsp, ipu or iva contain processor that require
+ * an HW reset line to be assert / deassert in order to enable fully
+ * the IP.  Returns -EINVAL if @oh is null or if the operation is not
+ * yet supported on this OMAP; otherwise, passes along the return value
+ * from _assert_hardreset().
+ */
+int omap_hwmod_assert_hardreset(struct omap_hwmod *oh, const char *name)
+{
+       int ret;
+
+       if (!oh)
+               return -EINVAL;
+
+       mutex_lock(&oh->_mutex);
+       ret = _assert_hardreset(oh, name);
+       mutex_unlock(&oh->_mutex);
+
+       return ret;
+}
+
+/**
+ * omap_hwmod_deassert_hardreset - deassert the HW reset line of submodules
+ * contained in the hwmod module.
+ * @oh: struct omap_hwmod *
+ * @name: name of the reset line to look up and deassert
+ *
+ * Some IP like dsp, ipu or iva contain processor that require
+ * an HW reset line to be assert / deassert in order to enable fully
+ * the IP.  Returns -EINVAL if @oh is null or if the operation is not
+ * yet supported on this OMAP; otherwise, passes along the return value
+ * from _deassert_hardreset().
+ */
+int omap_hwmod_deassert_hardreset(struct omap_hwmod *oh, const char *name)
+{
+       int ret;
+
+       if (!oh)
+               return -EINVAL;
+
+       mutex_lock(&oh->_mutex);
+       ret = _deassert_hardreset(oh, name);
+       mutex_unlock(&oh->_mutex);
+
+       return ret;
+}
+
+/**
+ * omap_hwmod_read_hardreset - read the HW reset line state of submodules
+ * contained in the hwmod module
+ * @oh: struct omap_hwmod *
+ * @name: name of the reset line to look up and read
+ *
+ * Return the current state of the hwmod @oh's reset line named @name:
+ * returns -EINVAL upon parameter error or if this operation
+ * is unsupported on the current OMAP; otherwise, passes along the return
+ * value from _read_hardreset().
+ */
+int omap_hwmod_read_hardreset(struct omap_hwmod *oh, const char *name)
+{
+       int ret;
+
+       if (!oh)
+               return -EINVAL;
+
+       mutex_lock(&oh->_mutex);
+       ret = _read_hardreset(oh, name);
+       mutex_unlock(&oh->_mutex);
+
+       return ret;
+}
+
+
 /**
  * omap_hwmod_for_each_by_class - call @fn for each hwmod of class @classname
  * @classname: struct omap_hwmod_class name to search for