From 5d113262ee9e074e0d36362d878892819bfdeb47 Mon Sep 17 00:00:00 2001 From: Paul Walmsley Date: Thu, 3 Sep 2009 20:13:53 +0300 Subject: OMAP: powerdomain: Fix overflow when doing powerdomain deps lookups. At the end of the list pd is a pointer to a NULL struct, so checking if the address == NULL doesn't help here. In fact the original code will just keep running past the struct to read who knows what in memory. This case manifests itself when from clkdms_setup() when enabling auto idle for a clock domain and the clockdomain usecount is greater than 0. When _clkdm_add_autodeps() tries to add the a dependency that does not exist in the powerdomain->wkdep_srcs array the for loop will run past the wkdep_srcs array. Currently in linux-omap you won't hit this because the not found case is never executed, unless you start modifying powerdomains and their wakeup/sleep deps. Signed-off-by: Mike Chan Signed-off-by: Paul Walmsley diff --git a/arch/arm/mach-omap2/powerdomain.c b/arch/arm/mach-omap2/powerdomain.c index 983f1cb..66206b6 100644 --- a/arch/arm/mach-omap2/powerdomain.c +++ b/arch/arm/mach-omap2/powerdomain.c @@ -83,7 +83,7 @@ static struct powerdomain *_pwrdm_deps_lookup(struct powerdomain *pwrdm, if (!pwrdm || !deps || !omap_chip_is(pwrdm->omap_chip)) return ERR_PTR(-EINVAL); - for (pd = deps; pd; pd++) { + for (pd = deps; pd->pwrdm_name; pd++) { if (!omap_chip_is(pd->omap_chip)) continue; @@ -96,7 +96,7 @@ static struct powerdomain *_pwrdm_deps_lookup(struct powerdomain *pwrdm, } - if (!pd) + if (!pd->pwrdm_name) return ERR_PTR(-ENOENT); return pd->pwrdm; -- cgit v0.10.2 From 6dda2d4b1306c19e39496e9bb305424d1d547013 Mon Sep 17 00:00:00 2001 From: Tero Kristo Date: Thu, 3 Sep 2009 20:13:56 +0300 Subject: OMAP: SDRC: Add several new register definitions Add missing SDRC register offset macros. Signed-off-by: Tero Kristo Signed-off-by: Kevin Hilman [paul@pwsan.com: added commit message] Signed-off-by: Paul Walmsley diff --git a/arch/arm/plat-omap/include/mach/sdrc.h b/arch/arm/plat-omap/include/mach/sdrc.h index 93f70d2..1c09c78 100644 --- a/arch/arm/plat-omap/include/mach/sdrc.h +++ b/arch/arm/plat-omap/include/mach/sdrc.h @@ -21,19 +21,28 @@ /* SDRC register offsets - read/write with sdrc_{read,write}_reg() */ #define SDRC_SYSCONFIG 0x010 +#define SDRC_CS_CFG 0x040 +#define SDRC_SHARING 0x044 +#define SDRC_ERR_TYPE 0x04C #define SDRC_DLLA_CTRL 0x060 #define SDRC_DLLA_STATUS 0x064 #define SDRC_DLLB_CTRL 0x068 #define SDRC_DLLB_STATUS 0x06C #define SDRC_POWER 0x070 +#define SDRC_MCFG_0 0x080 #define SDRC_MR_0 0x084 +#define SDRC_EMR2_0 0x08c #define SDRC_ACTIM_CTRL_A_0 0x09c #define SDRC_ACTIM_CTRL_B_0 0x0a0 #define SDRC_RFR_CTRL_0 0x0a4 +#define SDRC_MANUAL_0 0x0a8 +#define SDRC_MCFG_1 0x0B0 #define SDRC_MR_1 0x0B4 +#define SDRC_EMR2_1 0x0BC #define SDRC_ACTIM_CTRL_A_1 0x0C4 #define SDRC_ACTIM_CTRL_B_1 0x0C8 #define SDRC_RFR_CTRL_1 0x0D4 +#define SDRC_MANUAL_1 0x0D8 /* * These values represent the number of memory clock cycles between -- cgit v0.10.2 From 11b66383aa722a3321d2aeec09808c5b140ad396 Mon Sep 17 00:00:00 2001 From: Sanjeev Premi Date: Thu, 3 Sep 2009 20:13:58 +0300 Subject: OMAP3 clock: Fixed processing of bootarg 'mpurate' The argument 'mpurate' had no effect on the MPU frequency. This patch fixes the same. Signed-off-by: Sanjeev Premi Signed-off-by: Paul Walmsley diff --git a/arch/arm/mach-omap2/clock34xx.c b/arch/arm/mach-omap2/clock34xx.c index cd7819c..fafcd32 100644 --- a/arch/arm/mach-omap2/clock34xx.c +++ b/arch/arm/mach-omap2/clock34xx.c @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -1067,17 +1068,17 @@ static int __init omap2_clk_arch_init(void) return -EINVAL; /* REVISIT: not yet ready for 343x */ -#if 0 - if (clk_set_rate(&virt_prcm_set, mpurate)) - printk(KERN_ERR "Could not find matching MPU rate\n"); -#endif + if (clk_set_rate(&dpll1_ck, mpurate)) + printk(KERN_ERR "*** Unable to set MPU rate\n"); recalculate_root_clocks(); - printk(KERN_INFO "Switched to new clocking rate (Crystal/DPLL3/MPU): " + printk(KERN_INFO "Switched to new clocking rate (Crystal/Core/MPU): " "%ld.%01ld/%ld/%ld MHz\n", - (osc_sys_ck.rate / 1000000), (osc_sys_ck.rate / 100000) % 10, - (core_ck.rate / 1000000), (dpll1_fck.rate / 1000000)) ; + (osc_sys_ck.rate / 1000000), ((osc_sys_ck.rate / 100000) % 10), + (core_ck.rate / 1000000), (arm_fck.rate / 1000000)) ; + + calibrate_delay(); return 0; } @@ -1136,7 +1137,7 @@ int __init omap2_clk_init(void) recalculate_root_clocks(); - printk(KERN_INFO "Clocking rate (Crystal/DPLL/ARM core): " + printk(KERN_INFO "Clocking rate (Crystal/Core/MPU): " "%ld.%01ld/%ld/%ld MHz\n", (osc_sys_ck.rate / 1000000), (osc_sys_ck.rate / 100000) % 10, (core_ck.rate / 1000000), (arm_fck.rate / 1000000)); -- cgit v0.10.2 From 19f4d3a90bf2815cb97b66e603b5e0311d234458 Mon Sep 17 00:00:00 2001 From: Paul Walmsley Date: Thu, 3 Sep 2009 20:14:00 +0300 Subject: OMAP clock: associate MPU clocks with the mpu_clkdm All MPU-related clocks should be in the mpu_clkdm. This is needed for the upcoming omap_hwmod patches, which needs to know the clockdomain that arm_fck is in. Signed-off-by: Paul Walmsley diff --git a/arch/arm/mach-omap2/clock34xx.h b/arch/arm/mach-omap2/clock34xx.h index 57cc272..9a823c3 100644 --- a/arch/arm/mach-omap2/clock34xx.h +++ b/arch/arm/mach-omap2/clock34xx.h @@ -1020,6 +1020,7 @@ static struct clk arm_fck = { .clksel_reg = OMAP_CM_REGADDR(MPU_MOD, OMAP3430_CM_IDLEST_PLL), .clksel_mask = OMAP3430_ST_MPU_CLK_MASK, .clksel = arm_fck_clksel, + .clkdm_name = "mpu_clkdm", .recalc = &omap2_clksel_recalc, }; -- cgit v0.10.2 From 08e3d5f28d4d696dc559898676019cbd36501025 Mon Sep 17 00:00:00 2001 From: Paul Walmsley Date: Thu, 3 Sep 2009 20:14:00 +0300 Subject: OMAP3 clock: remove superfluous calls to omap2_init_clk_clkdm omap2_init_clk_clkdm() is called as part of the chip architecture-specific initialization code, so calling it again from the struct clk init pointer just wastes cycles. Signed-off-by: Paul Walmsley diff --git a/arch/arm/mach-omap2/clock34xx.h b/arch/arm/mach-omap2/clock34xx.h index 9a823c3..c811978 100644 --- a/arch/arm/mach-omap2/clock34xx.h +++ b/arch/arm/mach-omap2/clock34xx.h @@ -1156,7 +1156,6 @@ static struct clk gfx_cg1_ck = { .name = "gfx_cg1_ck", .ops = &clkops_omap2_dflt_wait, .parent = &gfx_l3_fck, /* REVISIT: correct? */ - .init = &omap2_init_clk_clkdm, .enable_reg = OMAP_CM_REGADDR(GFX_MOD, CM_FCLKEN), .enable_bit = OMAP3430ES1_EN_2D_SHIFT, .clkdm_name = "gfx_3430es1_clkdm", @@ -1167,7 +1166,6 @@ static struct clk gfx_cg2_ck = { .name = "gfx_cg2_ck", .ops = &clkops_omap2_dflt_wait, .parent = &gfx_l3_fck, /* REVISIT: correct? */ - .init = &omap2_init_clk_clkdm, .enable_reg = OMAP_CM_REGADDR(GFX_MOD, CM_FCLKEN), .enable_bit = OMAP3430ES1_EN_3D_SHIFT, .clkdm_name = "gfx_3430es1_clkdm", @@ -1211,7 +1209,6 @@ static struct clk sgx_ick = { .name = "sgx_ick", .ops = &clkops_omap2_dflt_wait, .parent = &l3_ick, - .init = &omap2_init_clk_clkdm, .enable_reg = OMAP_CM_REGADDR(OMAP3430ES2_SGX_MOD, CM_ICLKEN), .enable_bit = OMAP3430ES2_CM_ICLKEN_SGX_EN_SGX_SHIFT, .clkdm_name = "sgx_clkdm", @@ -1224,7 +1221,6 @@ static struct clk d2d_26m_fck = { .name = "d2d_26m_fck", .ops = &clkops_omap2_dflt_wait, .parent = &sys_ck, - .init = &omap2_init_clk_clkdm, .enable_reg = OMAP_CM_REGADDR(CORE_MOD, CM_FCLKEN1), .enable_bit = OMAP3430ES1_EN_D2D_SHIFT, .clkdm_name = "d2d_clkdm", @@ -1235,7 +1231,6 @@ static struct clk modem_fck = { .name = "modem_fck", .ops = &clkops_omap2_dflt_wait, .parent = &sys_ck, - .init = &omap2_init_clk_clkdm, .enable_reg = OMAP_CM_REGADDR(CORE_MOD, CM_FCLKEN1), .enable_bit = OMAP3430_EN_MODEM_SHIFT, .clkdm_name = "d2d_clkdm", @@ -1623,7 +1618,6 @@ static struct clk core_l3_ick = { .name = "core_l3_ick", .ops = &clkops_null, .parent = &l3_ick, - .init = &omap2_init_clk_clkdm, .clkdm_name = "core_l3_clkdm", .recalc = &followparent_recalc, }; @@ -1692,7 +1686,6 @@ static struct clk core_l4_ick = { .name = "core_l4_ick", .ops = &clkops_null, .parent = &l4_ick, - .init = &omap2_init_clk_clkdm, .clkdm_name = "core_l4_clkdm", .recalc = &followparent_recalc, }; @@ -2090,7 +2083,6 @@ static struct clk dss_tv_fck = { .name = "dss_tv_fck", .ops = &clkops_omap2_dflt, .parent = &omap_54m_fck, - .init = &omap2_init_clk_clkdm, .enable_reg = OMAP_CM_REGADDR(OMAP3430_DSS_MOD, CM_FCLKEN), .enable_bit = OMAP3430_EN_TV_SHIFT, .clkdm_name = "dss_clkdm", @@ -2101,7 +2093,6 @@ static struct clk dss_96m_fck = { .name = "dss_96m_fck", .ops = &clkops_omap2_dflt, .parent = &omap_96m_fck, - .init = &omap2_init_clk_clkdm, .enable_reg = OMAP_CM_REGADDR(OMAP3430_DSS_MOD, CM_FCLKEN), .enable_bit = OMAP3430_EN_TV_SHIFT, .clkdm_name = "dss_clkdm", @@ -2112,7 +2103,6 @@ static struct clk dss2_alwon_fck = { .name = "dss2_alwon_fck", .ops = &clkops_omap2_dflt, .parent = &sys_ck, - .init = &omap2_init_clk_clkdm, .enable_reg = OMAP_CM_REGADDR(OMAP3430_DSS_MOD, CM_FCLKEN), .enable_bit = OMAP3430_EN_DSS2_SHIFT, .clkdm_name = "dss_clkdm", @@ -2124,7 +2114,6 @@ static struct clk dss_ick_3430es1 = { .name = "dss_ick", .ops = &clkops_omap2_dflt, .parent = &l4_ick, - .init = &omap2_init_clk_clkdm, .enable_reg = OMAP_CM_REGADDR(OMAP3430_DSS_MOD, CM_ICLKEN), .enable_bit = OMAP3430_CM_ICLKEN_DSS_EN_DSS_SHIFT, .clkdm_name = "dss_clkdm", @@ -2136,7 +2125,6 @@ static struct clk dss_ick_3430es2 = { .name = "dss_ick", .ops = &clkops_omap3430es2_dss_usbhost_wait, .parent = &l4_ick, - .init = &omap2_init_clk_clkdm, .enable_reg = OMAP_CM_REGADDR(OMAP3430_DSS_MOD, CM_ICLKEN), .enable_bit = OMAP3430_CM_ICLKEN_DSS_EN_DSS_SHIFT, .clkdm_name = "dss_clkdm", @@ -2160,7 +2148,6 @@ static struct clk cam_ick = { .name = "cam_ick", .ops = &clkops_omap2_dflt, .parent = &l4_ick, - .init = &omap2_init_clk_clkdm, .enable_reg = OMAP_CM_REGADDR(OMAP3430_CAM_MOD, CM_ICLKEN), .enable_bit = OMAP3430_EN_CAM_SHIFT, .clkdm_name = "cam_clkdm", @@ -2171,7 +2158,6 @@ static struct clk csi2_96m_fck = { .name = "csi2_96m_fck", .ops = &clkops_omap2_dflt, .parent = &core_96m_fck, - .init = &omap2_init_clk_clkdm, .enable_reg = OMAP_CM_REGADDR(OMAP3430_CAM_MOD, CM_FCLKEN), .enable_bit = OMAP3430_EN_CSI2_SHIFT, .clkdm_name = "cam_clkdm", @@ -2184,7 +2170,6 @@ static struct clk usbhost_120m_fck = { .name = "usbhost_120m_fck", .ops = &clkops_omap2_dflt, .parent = &dpll5_m2_ck, - .init = &omap2_init_clk_clkdm, .enable_reg = OMAP_CM_REGADDR(OMAP3430ES2_USBHOST_MOD, CM_FCLKEN), .enable_bit = OMAP3430ES2_EN_USBHOST2_SHIFT, .clkdm_name = "usbhost_clkdm", @@ -2195,7 +2180,6 @@ static struct clk usbhost_48m_fck = { .name = "usbhost_48m_fck", .ops = &clkops_omap3430es2_dss_usbhost_wait, .parent = &omap_48m_fck, - .init = &omap2_init_clk_clkdm, .enable_reg = OMAP_CM_REGADDR(OMAP3430ES2_USBHOST_MOD, CM_FCLKEN), .enable_bit = OMAP3430ES2_EN_USBHOST1_SHIFT, .clkdm_name = "usbhost_clkdm", @@ -2207,7 +2191,6 @@ static struct clk usbhost_ick = { .name = "usbhost_ick", .ops = &clkops_omap3430es2_dss_usbhost_wait, .parent = &l4_ick, - .init = &omap2_init_clk_clkdm, .enable_reg = OMAP_CM_REGADDR(OMAP3430ES2_USBHOST_MOD, CM_ICLKEN), .enable_bit = OMAP3430ES2_EN_USBHOST_SHIFT, .clkdm_name = "usbhost_clkdm", @@ -2269,7 +2252,6 @@ static struct clk gpt1_fck = { static struct clk wkup_32k_fck = { .name = "wkup_32k_fck", .ops = &clkops_null, - .init = &omap2_init_clk_clkdm, .parent = &omap_32k_fck, .clkdm_name = "wkup_clkdm", .recalc = &followparent_recalc, @@ -2384,7 +2366,6 @@ static struct clk per_96m_fck = { .name = "per_96m_fck", .ops = &clkops_null, .parent = &omap_96m_alwon_fck, - .init = &omap2_init_clk_clkdm, .clkdm_name = "per_clkdm", .recalc = &followparent_recalc, }; @@ -2393,7 +2374,6 @@ static struct clk per_48m_fck = { .name = "per_48m_fck", .ops = &clkops_null, .parent = &omap_48m_fck, - .init = &omap2_init_clk_clkdm, .clkdm_name = "per_clkdm", .recalc = &followparent_recalc, }; -- cgit v0.10.2 From c0407a96d04794be586eab4a412320079446cf93 Mon Sep 17 00:00:00 2001 From: Paul Walmsley Date: Thu, 3 Sep 2009 20:14:01 +0300 Subject: OMAP2/3 PM: create the OMAP PM interface and add a default OMAP PM no-op layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The interface provides device drivers, CPUFreq, and DSPBridge with a means of controlling OMAP power management parameters that are not yet supported by the Linux PM PMQoS interface. Copious documentation is in the patch in Documentation/arm/OMAP/omap_pm and the interface header file, arch/arm/plat-omap/include/mach/omap-pm.h. Thanks to Rajendra Nayak for adding CORE (VDD2) OPP support and moving the OPP table initialization earlier in the event that the clock code needs them. Thanks to Tero Kristo for fixing the parameter check in omap_pm_set_min_bus_tput(). Jouni signed off on Tero's patch. Signed-off-by: Paul Walmsley Signed-off-by: Kevin Hilman Signed-off-by: Rajendra Nayak Signed-off-by: Tero Kristo Signed-off-by: Jouni Högander Cc: Tony Lindgren Cc: Igor Stoppa Cc: Richard Woodruff Cc: Anand Sawant Cc: Sakari Poussa Cc: Veeramanikandan Raju Cc: Karthik Dasu diff --git a/Documentation/arm/OMAP/omap_pm b/Documentation/arm/OMAP/omap_pm new file mode 100644 index 0000000..5389440 --- /dev/null +++ b/Documentation/arm/OMAP/omap_pm @@ -0,0 +1,129 @@ + +The OMAP PM interface +===================== + +This document describes the temporary OMAP PM interface. Driver +authors use these functions to communicate minimum latency or +throughput constraints to the kernel power management code. +Over time, the intention is to merge features from the OMAP PM +interface into the Linux PM QoS code. + +Drivers need to express PM parameters which: + +- support the range of power management parameters present in the TI SRF; + +- separate the drivers from the underlying PM parameter + implementation, whether it is the TI SRF or Linux PM QoS or Linux + latency framework or something else; + +- specify PM parameters in terms of fundamental units, such as + latency and throughput, rather than units which are specific to OMAP + or to particular OMAP variants; + +- allow drivers which are shared with other architectures (e.g., + DaVinci) to add these constraints in a way which won't affect non-OMAP + systems, + +- can be implemented immediately with minimal disruption of other + architectures. + + +This document proposes the OMAP PM interface, including the following +five power management functions for driver code: + +1. Set the maximum MPU wakeup latency: + (*pdata->set_max_mpu_wakeup_lat)(struct device *dev, unsigned long t) + +2. Set the maximum device wakeup latency: + (*pdata->set_max_dev_wakeup_lat)(struct device *dev, unsigned long t) + +3. Set the maximum system DMA transfer start latency (CORE pwrdm): + (*pdata->set_max_sdma_lat)(struct device *dev, long t) + +4. Set the minimum bus throughput needed by a device: + (*pdata->set_min_bus_tput)(struct device *dev, u8 agent_id, unsigned long r) + +5. Return the number of times the device has lost context + (*pdata->get_dev_context_loss_count)(struct device *dev) + + +Further documentation for all OMAP PM interface functions can be +found in arch/arm/plat-omap/include/mach/omap-pm.h. + + +The OMAP PM layer is intended to be temporary +--------------------------------------------- + +The intention is that eventually the Linux PM QoS layer should support +the range of power management features present in OMAP3. As this +happens, existing drivers using the OMAP PM interface can be modified +to use the Linux PM QoS code; and the OMAP PM interface can disappear. + + +Driver usage of the OMAP PM functions +------------------------------------- + +As the 'pdata' in the above examples indicates, these functions are +exposed to drivers through function pointers in driver .platform_data +structures. The function pointers are initialized by the board-*.c +files to point to the corresponding OMAP PM functions: +.set_max_dev_wakeup_lat will point to +omap_pm_set_max_dev_wakeup_lat(), etc. Other architectures which do +not support these functions should leave these function pointers set +to NULL. Drivers should use the following idiom: + + if (pdata->set_max_dev_wakeup_lat) + (*pdata->set_max_dev_wakeup_lat)(dev, t); + +The most common usage of these functions will probably be to specify +the maximum time from when an interrupt occurs, to when the device +becomes accessible. To accomplish this, driver writers should use the +set_max_mpu_wakeup_lat() function to to constrain the MPU wakeup +latency, and the set_max_dev_wakeup_lat() function to constrain the +device wakeup latency (from clk_enable() to accessibility). For +example, + + /* Limit MPU wakeup latency */ + if (pdata->set_max_mpu_wakeup_lat) + (*pdata->set_max_mpu_wakeup_lat)(dev, tc); + + /* Limit device powerdomain wakeup latency */ + if (pdata->set_max_dev_wakeup_lat) + (*pdata->set_max_dev_wakeup_lat)(dev, td); + + /* total wakeup latency in this example: (tc + td) */ + +The PM parameters can be overwritten by calling the function again +with the new value. The settings can be removed by calling the +function with a t argument of -1 (except in the case of +set_max_bus_tput(), which should be called with an r argument of 0). + +The fifth function above, omap_pm_get_dev_context_loss_count(), +is intended as an optimization to allow drivers to determine whether the +device has lost its internal context. If context has been lost, the +driver must restore its internal context before proceeding. + + +Other specialized interface functions +------------------------------------- + +The five functions listed above are intended to be usable by any +device driver. DSPBridge and CPUFreq have a few special requirements. +DSPBridge expresses target DSP performance levels in terms of OPP IDs. +CPUFreq expresses target MPU performance levels in terms of MPU +frequency. The OMAP PM interface contains functions for these +specialized cases to convert that input information (OPPs/MPU +frequency) into the form that the underlying power management +implementation needs: + +6. (*pdata->dsp_get_opp_table)(void) + +7. (*pdata->dsp_set_min_opp)(u8 opp_id) + +8. (*pdata->dsp_get_opp)(void) + +9. (*pdata->cpu_get_freq_table)(void) + +10. (*pdata->cpu_set_freq)(unsigned long f) + +11. (*pdata->cpu_get_freq)(void) diff --git a/arch/arm/mach-omap2/io.c b/arch/arm/mach-omap2/io.c index e9b9bcb..4bfe873 100644 --- a/arch/arm/mach-omap2/io.c +++ b/arch/arm/mach-omap2/io.c @@ -36,6 +36,7 @@ #ifndef CONFIG_ARCH_OMAP4 /* FIXME: Remove this once clkdev is ready */ #include "clock.h" +#include #include #include "powerdomains.h" @@ -281,9 +282,12 @@ void __init omap2_init_common_hw(struct omap_sdrc_params *sdrc_cs0, { omap2_mux_init(); #ifndef CONFIG_ARCH_OMAP4 /* FIXME: Remove this once the clkdev is ready */ + /* The OPP tables have to be registered before a clk init */ + omap_pm_if_early_init(mpu_opps, dsp_opps, l3_opps); pwrdm_init(powerdomains_omap); clkdm_init(clockdomains_omap, clkdm_pwrdm_autodeps); omap2_clk_init(); + omap_pm_if_init(); omap2_sdrc_init(sdrc_cs0, sdrc_cs1); _omap2_init_reprogram_sdrc(); #endif diff --git a/arch/arm/plat-omap/Kconfig b/arch/arm/plat-omap/Kconfig index ab9f9ef..64b3f52 100644 --- a/arch/arm/plat-omap/Kconfig +++ b/arch/arm/plat-omap/Kconfig @@ -187,6 +187,19 @@ config OMAP_SERIAL_WAKE to data on the serial RX line. This allows you to wake the system from serial console. +choice + prompt "OMAP PM layer selection" + depends on ARCH_OMAP + default OMAP_PM_NOOP + +config OMAP_PM_NONE + bool "No PM layer" + +config OMAP_PM_NOOP + bool "No-op/debug PM layer" + +endchoice + endmenu endif diff --git a/arch/arm/plat-omap/Makefile b/arch/arm/plat-omap/Makefile index 769a4c2..5e10d0a 100644 --- a/arch/arm/plat-omap/Makefile +++ b/arch/arm/plat-omap/Makefile @@ -26,3 +26,4 @@ obj-y += $(i2c-omap-m) $(i2c-omap-y) # OMAP mailbox framework obj-$(CONFIG_OMAP_MBOX_FWK) += mailbox.o +obj-$(CONFIG_OMAP_PM_NOOP) += omap-pm-noop.o \ No newline at end of file diff --git a/arch/arm/plat-omap/include/mach/omap-pm.h b/arch/arm/plat-omap/include/mach/omap-pm.h new file mode 100644 index 0000000..3ee41d7 --- /dev/null +++ b/arch/arm/plat-omap/include/mach/omap-pm.h @@ -0,0 +1,301 @@ +/* + * omap-pm.h - OMAP power management interface + * + * Copyright (C) 2008-2009 Texas Instruments, Inc. + * Copyright (C) 2008-2009 Nokia Corporation + * Paul Walmsley + * + * Interface developed by (in alphabetical order): Karthik Dasu, Jouni + * Högander, Tony Lindgren, Rajendra Nayak, Sakari Poussa, + * Veeramanikandan Raju, Anand Sawant, Igor Stoppa, Paul Walmsley, + * Richard Woodruff + */ + +#ifndef ASM_ARM_ARCH_OMAP_OMAP_PM_H +#define ASM_ARM_ARCH_OMAP_OMAP_PM_H + +#include +#include + +#include "powerdomain.h" + +/** + * struct omap_opp - clock frequency-to-OPP ID table for DSP, MPU + * @rate: target clock rate + * @opp_id: OPP ID + * @min_vdd: minimum VDD1 voltage (in millivolts) for this OPP + * + * Operating performance point data. Can vary by OMAP chip and board. + */ +struct omap_opp { + unsigned long rate; + u8 opp_id; + u16 min_vdd; +}; + +extern struct omap_opp *mpu_opps; +extern struct omap_opp *dsp_opps; +extern struct omap_opp *l3_opps; + +/* + * agent_id values for use with omap_pm_set_min_bus_tput(): + * + * OCP_INITIATOR_AGENT is only valid for devices that can act as + * initiators -- it represents the device's L3 interconnect + * connection. OCP_TARGET_AGENT represents the device's L4 + * interconnect connection. + */ +#define OCP_TARGET_AGENT 1 +#define OCP_INITIATOR_AGENT 2 + +/** + * omap_pm_if_early_init - OMAP PM init code called before clock fw init + * @mpu_opp_table: array ptr to struct omap_opp for MPU + * @dsp_opp_table: array ptr to struct omap_opp for DSP + * @l3_opp_table : array ptr to struct omap_opp for CORE + * + * Initialize anything that must be configured before the clock + * framework starts. The "_if_" is to avoid name collisions with the + * PM idle-loop code. + */ +int __init omap_pm_if_early_init(struct omap_opp *mpu_opp_table, + struct omap_opp *dsp_opp_table, + struct omap_opp *l3_opp_table); + +/** + * omap_pm_if_init - OMAP PM init code called after clock fw init + * + * The main initialization code. OPP tables are passed in here. The + * "_if_" is to avoid name collisions with the PM idle-loop code. + */ +int __init omap_pm_if_init(void); + +/** + * omap_pm_if_exit - OMAP PM exit code + * + * Exit code; currently unused. The "_if_" is to avoid name + * collisions with the PM idle-loop code. + */ +void omap_pm_if_exit(void); + +/* + * Device-driver-originated constraints (via board-*.c files, platform_data) + */ + + +/** + * omap_pm_set_max_mpu_wakeup_lat - set the maximum MPU wakeup latency + * @dev: struct device * requesting the constraint + * @t: maximum MPU wakeup latency in microseconds + * + * Request that the maximum interrupt latency for the MPU to be no + * greater than 't' microseconds. "Interrupt latency" in this case is + * defined as the elapsed time from the occurrence of a hardware or + * timer interrupt to the time when the device driver's interrupt + * service routine has been entered by the MPU. + * + * It is intended that underlying PM code will use this information to + * determine what power state to put the MPU powerdomain into, and + * possibly the CORE powerdomain as well, since interrupt handling + * code currently runs from SDRAM. Advanced PM or board*.c code may + * also configure interrupt controller priorities, OCP bus priorities, + * CPU speed(s), etc. + * + * This function will not affect device wakeup latency, e.g., time + * elapsed from when a device driver enables a hardware device with + * clk_enable(), to when the device is ready for register access or + * other use. To control this device wakeup latency, use + * set_max_dev_wakeup_lat() + * + * Multiple calls to set_max_mpu_wakeup_lat() will replace the + * previous t value. To remove the latency target for the MPU, call + * with t = -1. + * + * No return value. + */ +void omap_pm_set_max_mpu_wakeup_lat(struct device *dev, long t); + + +/** + * omap_pm_set_min_bus_tput - set minimum bus throughput needed by device + * @dev: struct device * requesting the constraint + * @tbus_id: interconnect to operate on (OCP_{INITIATOR,TARGET}_AGENT) + * @r: minimum throughput (in KiB/s) + * + * Request that the minimum data throughput on the OCP interconnect + * attached to device 'dev' interconnect agent 'tbus_id' be no less + * than 'r' KiB/s. + * + * It is expected that the OMAP PM or bus code will use this + * information to set the interconnect clock to run at the lowest + * possible speed that satisfies all current system users. The PM or + * bus code will adjust the estimate based on its model of the bus, so + * device driver authors should attempt to specify an accurate + * quantity for their device use case, and let the PM or bus code + * overestimate the numbers as necessary to handle request/response + * latency, other competing users on the system, etc. On OMAP2/3, if + * a driver requests a minimum L4 interconnect speed constraint, the + * code will also need to add an minimum L3 interconnect speed + * constraint, + * + * Multiple calls to set_min_bus_tput() will replace the previous rate + * value for this device. To remove the interconnect throughput + * restriction for this device, call with r = 0. + * + * No return value. + */ +void omap_pm_set_min_bus_tput(struct device *dev, u8 agent_id, unsigned long r); + + +/** + * omap_pm_set_max_dev_wakeup_lat - set the maximum device enable latency + * @dev: struct device * + * @t: maximum device wakeup latency in microseconds + * + * Request that the maximum amount of time necessary for a device to + * become accessible after its clocks are enabled should be no greater + * than 't' microseconds. Specifically, this represents the time from + * when a device driver enables device clocks with clk_enable(), to + * when the register reads and writes on the device will succeed. + * This function should be called before clk_disable() is called, + * since the power state transition decision may be made during + * clk_disable(). + * + * It is intended that underlying PM code will use this information to + * determine what power state to put the powerdomain enclosing this + * device into. + * + * Multiple calls to set_max_dev_wakeup_lat() will replace the + * previous wakeup latency values for this device. To remove the wakeup + * latency restriction for this device, call with t = -1. + * + * No return value. + */ +void omap_pm_set_max_dev_wakeup_lat(struct device *dev, long t); + + +/** + * omap_pm_set_max_sdma_lat - set the maximum system DMA transfer start latency + * @dev: struct device * + * @t: maximum DMA transfer start latency in microseconds + * + * Request that the maximum system DMA transfer start latency for this + * device 'dev' should be no greater than 't' microseconds. "DMA + * transfer start latency" here is defined as the elapsed time from + * when a device (e.g., McBSP) requests that a system DMA transfer + * start or continue, to the time at which data starts to flow into + * that device from the system DMA controller. + * + * It is intended that underlying PM code will use this information to + * determine what power state to put the CORE powerdomain into. + * + * Since system DMA transfers may not involve the MPU, this function + * will not affect MPU wakeup latency. Use set_max_cpu_lat() to do + * so. Similarly, this function will not affect device wakeup latency + * -- use set_max_dev_wakeup_lat() to affect that. + * + * Multiple calls to set_max_sdma_lat() will replace the previous t + * value for this device. To remove the maximum DMA latency for this + * device, call with t = -1. + * + * No return value. + */ +void omap_pm_set_max_sdma_lat(struct device *dev, long t); + + +/* + * DSP Bridge-specific constraints + */ + +/** + * omap_pm_dsp_get_opp_table - get OPP->DSP clock frequency table + * + * Intended for use by DSPBridge. Returns an array of OPP->DSP clock + * frequency entries. The final item in the array should have .rate = + * .opp_id = 0. + */ +const struct omap_opp *omap_pm_dsp_get_opp_table(void); + +/** + * omap_pm_dsp_set_min_opp - receive desired OPP target ID from DSP Bridge + * @opp_id: target DSP OPP ID + * + * Set a minimum OPP ID for the DSP. This is intended to be called + * only from the DSP Bridge MPU-side driver. Unfortunately, the only + * information that code receives from the DSP/BIOS load estimator is the + * target OPP ID; hence, this interface. No return value. + */ +void omap_pm_dsp_set_min_opp(u8 opp_id); + +/** + * omap_pm_dsp_get_opp - report the current DSP OPP ID + * + * Report the current OPP for the DSP. Since on OMAP3, the DSP and + * MPU share a single voltage domain, the OPP ID returned back may + * represent a higher DSP speed than the OPP requested via + * omap_pm_dsp_set_min_opp(). + * + * Returns the current VDD1 OPP ID, or 0 upon error. + */ +u8 omap_pm_dsp_get_opp(void); + + +/* + * CPUFreq-originated constraint + * + * In the future, this should be handled by custom OPP clocktype + * functions. + */ + +/** + * omap_pm_cpu_get_freq_table - return a cpufreq_frequency_table array ptr + * + * Provide a frequency table usable by CPUFreq for the current chip/board. + * Returns a pointer to a struct cpufreq_frequency_table array or NULL + * upon error. + */ +struct cpufreq_frequency_table **omap_pm_cpu_get_freq_table(void); + +/** + * omap_pm_cpu_set_freq - set the current minimum MPU frequency + * @f: MPU frequency in Hz + * + * Set the current minimum CPU frequency. The actual CPU frequency + * used could end up higher if the DSP requested a higher OPP. + * Intended to be called by plat-omap/cpu_omap.c:omap_target(). No + * return value. + */ +void omap_pm_cpu_set_freq(unsigned long f); + +/** + * omap_pm_cpu_get_freq - report the current CPU frequency + * + * Returns the current MPU frequency, or 0 upon error. + */ +unsigned long omap_pm_cpu_get_freq(void); + + +/* + * Device context loss tracking + */ + +/** + * omap_pm_get_dev_context_loss_count - return count of times dev has lost ctx + * @dev: struct device * + * + * This function returns the number of times that the device @dev has + * lost its internal context. This generally occurs on a powerdomain + * transition to OFF. Drivers use this as an optimization to avoid restoring + * context if the device hasn't lost it. To use, drivers should initially + * call this in their context save functions and store the result. Early in + * the driver's context restore function, the driver should call this function + * again, and compare the result to the stored counter. If they differ, the + * driver must restore device context. If the number of context losses + * exceeds the maximum positive integer, the function will wrap to 0 and + * continue counting. Returns the number of context losses for this device, + * or -EINVAL upon error. + */ +int omap_pm_get_dev_context_loss_count(struct device *dev); + + +#endif diff --git a/arch/arm/plat-omap/omap-pm-noop.c b/arch/arm/plat-omap/omap-pm-noop.c new file mode 100644 index 0000000..e98f0a2 --- /dev/null +++ b/arch/arm/plat-omap/omap-pm-noop.c @@ -0,0 +1,296 @@ +/* + * omap-pm-noop.c - OMAP power management interface - dummy version + * + * This code implements the OMAP power management interface to + * drivers, CPUIdle, CPUFreq, and DSP Bridge. It is strictly for + * debug/demonstration use, as it does nothing but printk() whenever a + * function is called (when DEBUG is defined, below) + * + * Copyright (C) 2008-2009 Texas Instruments, Inc. + * Copyright (C) 2008-2009 Nokia Corporation + * Paul Walmsley + * + * Interface developed by (in alphabetical order): + * Karthik Dasu, Tony Lindgren, Rajendra Nayak, Sakari Poussa, Veeramanikandan + * Raju, Anand Sawant, Igor Stoppa, Paul Walmsley, Richard Woodruff + */ + +#undef DEBUG + +#include +#include +#include + +/* Interface documentation is in mach/omap-pm.h */ +#include + +#include + +struct omap_opp *dsp_opps; +struct omap_opp *mpu_opps; +struct omap_opp *l3_opps; + +/* + * Device-driver-originated constraints (via board-*.c files) + */ + +void omap_pm_set_max_mpu_wakeup_lat(struct device *dev, long t) +{ + if (!dev || t < -1) { + WARN_ON(1); + return; + }; + + if (t == -1) + pr_debug("OMAP PM: remove max MPU wakeup latency constraint: " + "dev %s\n", dev_name(dev)); + else + pr_debug("OMAP PM: add max MPU wakeup latency constraint: " + "dev %s, t = %ld usec\n", dev_name(dev), t); + + /* + * For current Linux, this needs to map the MPU to a + * powerdomain, then go through the list of current max lat + * constraints on the MPU and find the smallest. If + * the latency constraint has changed, the code should + * recompute the state to enter for the next powerdomain + * state. + * + * TI CDP code can call constraint_set here. + */ +} + +void omap_pm_set_min_bus_tput(struct device *dev, u8 agent_id, unsigned long r) +{ + if (!dev || (agent_id != OCP_INITIATOR_AGENT && + agent_id != OCP_TARGET_AGENT)) { + WARN_ON(1); + return; + }; + + if (r == 0) + pr_debug("OMAP PM: remove min bus tput constraint: " + "dev %s for agent_id %d\n", dev_name(dev), agent_id); + else + pr_debug("OMAP PM: add min bus tput constraint: " + "dev %s for agent_id %d: rate %ld KiB\n", + dev_name(dev), agent_id, r); + + /* + * This code should model the interconnect and compute the + * required clock frequency, convert that to a VDD2 OPP ID, then + * set the VDD2 OPP appropriately. + * + * TI CDP code can call constraint_set here on the VDD2 OPP. + */ +} + +void omap_pm_set_max_dev_wakeup_lat(struct device *dev, long t) +{ + if (!dev || t < -1) { + WARN_ON(1); + return; + }; + + if (t == -1) + pr_debug("OMAP PM: remove max device latency constraint: " + "dev %s\n", dev_name(dev)); + else + pr_debug("OMAP PM: add max device latency constraint: " + "dev %s, t = %ld usec\n", dev_name(dev), t); + + /* + * For current Linux, this needs to map the device to a + * powerdomain, then go through the list of current max lat + * constraints on that powerdomain and find the smallest. If + * the latency constraint has changed, the code should + * recompute the state to enter for the next powerdomain + * state. Conceivably, this code should also determine + * whether to actually disable the device clocks or not, + * depending on how long it takes to re-enable the clocks. + * + * TI CDP code can call constraint_set here. + */ +} + +void omap_pm_set_max_sdma_lat(struct device *dev, long t) +{ + if (!dev || t < -1) { + WARN_ON(1); + return; + }; + + if (t == -1) + pr_debug("OMAP PM: remove max DMA latency constraint: " + "dev %s\n", dev_name(dev)); + else + pr_debug("OMAP PM: add max DMA latency constraint: " + "dev %s, t = %ld usec\n", dev_name(dev), t); + + /* + * For current Linux PM QOS params, this code should scan the + * list of maximum CPU and DMA latencies and select the + * smallest, then set cpu_dma_latency pm_qos_param + * accordingly. + * + * For future Linux PM QOS params, with separate CPU and DMA + * latency params, this code should just set the dma_latency param. + * + * TI CDP code can call constraint_set here. + */ + +} + + +/* + * DSP Bridge-specific constraints + */ + +const struct omap_opp *omap_pm_dsp_get_opp_table(void) +{ + pr_debug("OMAP PM: DSP request for OPP table\n"); + + /* + * Return DSP frequency table here: The final item in the + * array should have .rate = .opp_id = 0. + */ + + return NULL; +} + +void omap_pm_dsp_set_min_opp(u8 opp_id) +{ + if (opp_id == 0) { + WARN_ON(1); + return; + } + + pr_debug("OMAP PM: DSP requests minimum VDD1 OPP to be %d\n", opp_id); + + /* + * + * For l-o dev tree, our VDD1 clk is keyed on OPP ID, so we + * can just test to see which is higher, the CPU's desired OPP + * ID or the DSP's desired OPP ID, and use whichever is + * highest. + * + * In CDP12.14+, the VDD1 OPP custom clock that controls the DSP + * rate is keyed on MPU speed, not the OPP ID. So we need to + * map the OPP ID to the MPU speed for use with clk_set_rate() + * if it is higher than the current OPP clock rate. + * + */ +} + + +u8 omap_pm_dsp_get_opp(void) +{ + pr_debug("OMAP PM: DSP requests current DSP OPP ID\n"); + + /* + * For l-o dev tree, call clk_get_rate() on VDD1 OPP clock + * + * CDP12.14+: + * Call clk_get_rate() on the OPP custom clock, map that to an + * OPP ID using the tables defined in board-*.c/chip-*.c files. + */ + + return 0; +} + +/* + * CPUFreq-originated constraint + * + * In the future, this should be handled by custom OPP clocktype + * functions. + */ + +struct cpufreq_frequency_table **omap_pm_cpu_get_freq_table(void) +{ + pr_debug("OMAP PM: CPUFreq request for frequency table\n"); + + /* + * Return CPUFreq frequency table here: loop over + * all VDD1 clkrates, pull out the mpu_ck frequencies, build + * table + */ + + return NULL; +} + +void omap_pm_cpu_set_freq(unsigned long f) +{ + if (f == 0) { + WARN_ON(1); + return; + } + + pr_debug("OMAP PM: CPUFreq requests CPU frequency to be set to %lu\n", + f); + + /* + * For l-o dev tree, determine whether MPU freq or DSP OPP id + * freq is higher. Find the OPP ID corresponding to the + * higher frequency. Call clk_round_rate() and clk_set_rate() + * on the OPP custom clock. + * + * CDP should just be able to set the VDD1 OPP clock rate here. + */ +} + +unsigned long omap_pm_cpu_get_freq(void) +{ + pr_debug("OMAP PM: CPUFreq requests current CPU frequency\n"); + + /* + * Call clk_get_rate() on the mpu_ck. + */ + + return 0; +} + +/* + * Device context loss tracking + */ + +int omap_pm_get_dev_context_loss_count(struct device *dev) +{ + if (!dev) { + WARN_ON(1); + return -EINVAL; + }; + + pr_debug("OMAP PM: returning context loss count for dev %s\n", + dev_name(dev)); + + /* + * Map the device to the powerdomain. Return the powerdomain + * off counter. + */ + + return 0; +} + + +/* Should be called before clk framework init */ +int __init omap_pm_if_early_init(struct omap_opp *mpu_opp_table, + struct omap_opp *dsp_opp_table, + struct omap_opp *l3_opp_table) +{ + mpu_opps = mpu_opp_table; + dsp_opps = dsp_opp_table; + l3_opps = l3_opp_table; + return 0; +} + +/* Must be called after clock framework is initialized */ +int __init omap_pm_if_init(void) +{ + return 0; +} + +void omap_pm_if_exit(void) +{ + /* Deallocate CPUFreq frequency table here */ +} + -- cgit v0.10.2 From 71348bcaac6f4c372525d4d62e88a82a7330435b Mon Sep 17 00:00:00 2001 From: Paul Walmsley Date: Thu, 3 Sep 2009 20:14:02 +0300 Subject: OMAP2/3/4 PRCM: add module IDLEST wait code After a hardware module's clocks are enabled, Linux must wait for it to indicate readiness via its IDLEST bit before attempting to access the device, otherwise register accesses to the device may trigger an abort. This has traditionally been implemented in the clock framework, but this is the wrong place for it: the clock framework doesn't know which module clocks must be enabled for a module to leave idle; and if a module is not in smart-idle mode, it may never leave idle at all. This type of information is best stored in a per-hardware module data structure (coming in a following patch), rather than a per-clock data structure. The new code will use these new functions to handle waiting for modules to enable. Once hardware module data is filled in for all of the on-chip devices, the clock framework code to handle IDLEST waiting can be removed. Signed-off-by: Paul Walmsley diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile index 8850a24..59d730f 100644 --- a/arch/arm/mach-omap2/Makefile +++ b/arch/arm/mach-omap2/Makefile @@ -35,6 +35,11 @@ obj-$(CONFIG_ARCH_OMAP3) += pm34xx.o sleep34xx.o obj-$(CONFIG_PM_DEBUG) += pm-debug.o endif +# PRCM +obj-$(CONFIG_ARCH_OMAP2) += cm.o +obj-$(CONFIG_ARCH_OMAP3) += cm.o +obj-$(CONFIG_ARCH_OMAP4) += cm4xxx.o + # Clock framework obj-$(CONFIG_ARCH_OMAP2) += clock24xx.o obj-$(CONFIG_ARCH_OMAP3) += clock34xx.o diff --git a/arch/arm/mach-omap2/cm.c b/arch/arm/mach-omap2/cm.c new file mode 100644 index 0000000..8eb2dab --- /dev/null +++ b/arch/arm/mach-omap2/cm.c @@ -0,0 +1,70 @@ +/* + * OMAP2/3 CM module functions + * + * Copyright (C) 2009 Nokia Corporation + * Paul Walmsley + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "cm.h" +#include "cm-regbits-24xx.h" +#include "cm-regbits-34xx.h" + +/* MAX_MODULE_READY_TIME: max milliseconds for module to leave idle */ +#define MAX_MODULE_READY_TIME 20000 + +static const u8 cm_idlest_offs[] = { + CM_IDLEST1, CM_IDLEST2, OMAP2430_CM_IDLEST3 +}; + +/** + * omap2_cm_wait_idlest_ready - wait for a module to leave idle or standby + * @prcm_mod: PRCM module offset + * @idlest_id: CM_IDLESTx register ID (i.e., x = 1, 2, 3) + * @idlest_shift: shift of the bit in the CM_IDLEST* register to check + * + * XXX document + */ +int omap2_cm_wait_module_ready(s16 prcm_mod, u8 idlest_id, u8 idlest_shift) +{ + int ena = 0, i = 0; + u8 cm_idlest_reg; + u32 mask; + + if (!idlest_id || (idlest_id > ARRAY_SIZE(cm_idlest_offs))) + return -EINVAL; + + cm_idlest_reg = cm_idlest_offs[idlest_id - 1]; + + if (cpu_is_omap24xx()) + ena = idlest_shift; + else if (cpu_is_omap34xx()) + ena = 0; + else + BUG(); + + mask = 1 << idlest_shift; + + /* XXX should be OMAP2 CM */ + while (((cm_read_mod_reg(prcm_mod, cm_idlest_reg) & mask) != ena) && + (i++ < MAX_MODULE_READY_TIME)) + udelay(1); + + return (i < MAX_MODULE_READY_TIME) ? 0 : -EBUSY; +} + diff --git a/arch/arm/mach-omap2/cm.h b/arch/arm/mach-omap2/cm.h index 18d9a12..cfd0b72 100644 --- a/arch/arm/mach-omap2/cm.h +++ b/arch/arm/mach-omap2/cm.h @@ -98,6 +98,10 @@ extern u32 cm_read_mod_reg(s16 module, u16 idx); extern void cm_write_mod_reg(u32 val, s16 module, u16 idx); extern u32 cm_rmw_mod_reg_bits(u32 mask, u32 bits, s16 module, s16 idx); +extern int omap2_cm_wait_module_ready(s16 prcm_mod, u8 idlest_id, + u8 idlest_shift); +extern int omap4_cm_wait_module_ready(u32 prcm_mod, u8 prcm_dev_offs); + static inline u32 cm_set_mod_reg_bits(u32 bits, s16 module, s16 idx) { return cm_rmw_mod_reg_bits(bits, bits, module, idx); diff --git a/arch/arm/mach-omap2/cm4xxx.c b/arch/arm/mach-omap2/cm4xxx.c new file mode 100644 index 0000000..e4ebd6d --- /dev/null +++ b/arch/arm/mach-omap2/cm4xxx.c @@ -0,0 +1,68 @@ +/* + * OMAP4 CM module functions + * + * Copyright (C) 2009 Nokia Corporation + * Paul Walmsley + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "cm.h" +#include "cm-regbits-4xxx.h" + +/* XXX move this to cm.h */ +/* MAX_MODULE_READY_TIME: max milliseconds for module to leave idle */ +#define MAX_MODULE_READY_TIME 20000 + +/* + * OMAP4_PRCM_CM_CLKCTRL_IDLEST_MASK: isolates the IDLEST field in the + * CM_CLKCTRL register. + */ +#define OMAP4_PRCM_CM_CLKCTRL_IDLEST_MASK (0x2 << 16) + +/* + * OMAP4 prcm_mod u32 fields contain packed data: the CM ID in bit 16 and + * the PRCM module offset address (from the CM module base) in bits 15-0. + */ +#define OMAP4_PRCM_MOD_CM_ID_SHIFT 16 +#define OMAP4_PRCM_MOD_OFFS_MASK 0xffff + +/** + * omap4_cm_wait_idlest_ready - wait for a module to leave idle or standby + * @prcm_mod: PRCM module offset (XXX example) + * @prcm_dev_offs: PRCM device offset (e.g. MCASP XXX example) + * + * XXX document + */ +int omap4_cm_wait_idlest_ready(u32 prcm_mod, u8 prcm_dev_offs) +{ + int i = 0; + u8 cm_id; + u16 prcm_mod_offs; + u32 mask = OMAP4_PRCM_CM_CLKCTRL_IDLEST_MASK; + + cm_id = prcm_mod >> OMAP4_PRCM_MOD_CM_ID_SHIFT; + prcm_mod_offs = prcm_mod & OMAP4_PRCM_MOD_OFFS_MASK; + + while (((omap4_cm_read_mod_reg(cm_id, prcm_mod_offs, prcm_dev_offs, + OMAP4_CM_CLKCTRL_DREG) & mask) != 0) && + (i++ < MAX_MODULE_READY_TIME)) + udelay(1); + + return (i < MAX_MODULE_READY_TIME) ? 0 : -EBUSY; +} + -- cgit v0.10.2 From b3c6df3ab2b17cd7ddf927d39a64f235b25ac8d4 Mon Sep 17 00:00:00 2001 From: Paul Walmsley Date: Thu, 3 Sep 2009 20:14:02 +0300 Subject: OMAP2/3 board-*.c files: read bootloader configuration earlier Most board-*.c files read configuration data from the bootloader in their .init_machine() function. This needs to happen earlier, at some point before omap2_init_common_hw() is called. This is because a future patch will use the bootloader serial console port information to enable the UART clocks earlier, immediately after omap2_clk_init(). This is in turn necessary since otherwise clock tree usecounts on clocks like dpll4_m2x2_ck will be bogus, which can cause the currently-active console UART clock to be disabled during boot. Signed-off-by: Paul Walmsley diff --git a/arch/arm/mach-omap2/board-2430sdp.c b/arch/arm/mach-omap2/board-2430sdp.c index 7f5a74d..42217b3 100644 --- a/arch/arm/mach-omap2/board-2430sdp.c +++ b/arch/arm/mach-omap2/board-2430sdp.c @@ -139,18 +139,19 @@ static inline void board_smc91x_init(void) #endif +static struct omap_board_config_kernel sdp2430_config[] = { + {OMAP_TAG_LCD, &sdp2430_lcd_config}, +}; + static void __init omap_2430sdp_init_irq(void) { + omap_board_config = sdp2430_config; + omap_board_config_size = ARRAY_SIZE(sdp2430_config); omap2_init_common_hw(NULL, NULL); omap_init_irq(); omap_gpio_init(); } -static struct omap_board_config_kernel sdp2430_config[] = { - {OMAP_TAG_LCD, &sdp2430_lcd_config}, -}; - - static struct twl4030_gpio_platform_data sdp2430_gpio_data = { .gpio_base = OMAP_MAX_GPIO_LINES, .irq_base = TWL4030_GPIO_IRQ_BASE, @@ -200,8 +201,6 @@ static void __init omap_2430sdp_init(void) omap2430_i2c_init(); platform_add_devices(sdp2430_devices, ARRAY_SIZE(sdp2430_devices)); - omap_board_config = sdp2430_config; - omap_board_config_size = ARRAY_SIZE(sdp2430_config); omap_serial_init(); twl4030_mmc_init(mmc); usb_musb_init(); diff --git a/arch/arm/mach-omap2/board-3430sdp.c b/arch/arm/mach-omap2/board-3430sdp.c index 7e9b76c..4500e7f 100644 --- a/arch/arm/mach-omap2/board-3430sdp.c +++ b/arch/arm/mach-omap2/board-3430sdp.c @@ -167,13 +167,6 @@ static struct platform_device *sdp3430_devices[] __initdata = { &sdp3430_lcd_device, }; -static void __init omap_3430sdp_init_irq(void) -{ - omap2_init_common_hw(hyb18m512160af6_sdrc_params, NULL); - omap_init_irq(); - omap_gpio_init(); -} - static struct omap_lcd_config sdp3430_lcd_config __initdata = { .ctrl_name = "internal", }; @@ -182,6 +175,15 @@ static struct omap_board_config_kernel sdp3430_config[] __initdata = { { OMAP_TAG_LCD, &sdp3430_lcd_config }, }; +static void __init omap_3430sdp_init_irq(void) +{ + omap_board_config = sdp3430_config; + omap_board_config_size = ARRAY_SIZE(sdp3430_config); + omap2_init_common_hw(hyb18m512160af6_sdrc_params, NULL); + omap_init_irq(); + omap_gpio_init(); +} + static int sdp3430_batt_table[] = { /* 0 C*/ 30800, 29500, 28300, 27100, @@ -477,8 +479,6 @@ static void __init omap_3430sdp_init(void) { omap3430_i2c_init(); platform_add_devices(sdp3430_devices, ARRAY_SIZE(sdp3430_devices)); - omap_board_config = sdp3430_config; - omap_board_config_size = ARRAY_SIZE(sdp3430_config); if (omap_rev() > OMAP3430_REV_ES1_0) ts_gpio = SDP3430_TS_GPIO_IRQ_SDPV2; else diff --git a/arch/arm/mach-omap2/board-apollon.c b/arch/arm/mach-omap2/board-apollon.c index 615f21d..7a2b54c 100644 --- a/arch/arm/mach-omap2/board-apollon.c +++ b/arch/arm/mach-omap2/board-apollon.c @@ -248,14 +248,6 @@ out: clk_put(gpmc_fck); } -static void __init omap_apollon_init_irq(void) -{ - omap2_init_common_hw(NULL, NULL); - omap_init_irq(); - omap_gpio_init(); - apollon_init_smc91x(); -} - static struct omap_usb_config apollon_usb_config __initdata = { .register_dev = 1, .hmc_mode = 0x14, /* 0:dev 1:host1 2:disable */ @@ -271,6 +263,16 @@ static struct omap_board_config_kernel apollon_config[] = { { OMAP_TAG_LCD, &apollon_lcd_config }, }; +static void __init omap_apollon_init_irq(void) +{ + omap_board_config = apollon_config; + omap_board_config_size = ARRAY_SIZE(apollon_config); + omap2_init_common_hw(NULL, NULL); + omap_init_irq(); + omap_gpio_init(); + apollon_init_smc91x(); +} + static void __init apollon_led_init(void) { /* LED0 - AA10 */ @@ -319,8 +321,6 @@ static void __init omap_apollon_init(void) * if not needed. */ platform_add_devices(apollon_devices, ARRAY_SIZE(apollon_devices)); - omap_board_config = apollon_config; - omap_board_config_size = ARRAY_SIZE(apollon_config); omap_serial_init(); } diff --git a/arch/arm/mach-omap2/board-generic.c b/arch/arm/mach-omap2/board-generic.c index 3e401c5..2e09a1c 100644 --- a/arch/arm/mach-omap2/board-generic.c +++ b/arch/arm/mach-omap2/board-generic.c @@ -31,19 +31,19 @@ #include #include +static struct omap_board_config_kernel generic_config[] = { +}; + static void __init omap_generic_init_irq(void) { + omap_board_config = generic_config; + omap_board_config_size = ARRAY_SIZE(generic_config); omap2_init_common_hw(NULL, NULL); omap_init_irq(); } -static struct omap_board_config_kernel generic_config[] = { -}; - static void __init omap_generic_init(void) { - omap_board_config = generic_config; - omap_board_config_size = ARRAY_SIZE(generic_config); omap_serial_init(); } diff --git a/arch/arm/mach-omap2/board-h4.c b/arch/arm/mach-omap2/board-h4.c index b6501d2..eaa02d0 100644 --- a/arch/arm/mach-omap2/board-h4.c +++ b/arch/arm/mach-omap2/board-h4.c @@ -268,14 +268,6 @@ static void __init h4_init_flash(void) h4_flash_resource.end = base + SZ_64M - 1; } -static void __init omap_h4_init_irq(void) -{ - omap2_init_common_hw(NULL, NULL); - omap_init_irq(); - omap_gpio_init(); - h4_init_flash(); -} - static struct omap_lcd_config h4_lcd_config __initdata = { .ctrl_name = "internal", }; @@ -317,6 +309,16 @@ static struct omap_board_config_kernel h4_config[] = { { OMAP_TAG_LCD, &h4_lcd_config }, }; +static void __init omap_h4_init_irq(void) +{ + omap_board_config = h4_config; + omap_board_config_size = ARRAY_SIZE(h4_config); + omap2_init_common_hw(NULL, NULL); + omap_init_irq(); + omap_gpio_init(); + h4_init_flash(); +} + static struct at24_platform_data m24c01 = { .byte_len = SZ_1K / 8, .page_size = 16, @@ -361,8 +363,6 @@ static void __init omap_h4_init(void) ARRAY_SIZE(h4_i2c_board_info)); platform_add_devices(h4_devices, ARRAY_SIZE(h4_devices)); - omap_board_config = h4_config; - omap_board_config_size = ARRAY_SIZE(h4_config); omap_usb_init(&h4_usb_config); omap_serial_init(); } diff --git a/arch/arm/mach-omap2/board-ldp.c b/arch/arm/mach-omap2/board-ldp.c index 2dd6806..ec6854c 100644 --- a/arch/arm/mach-omap2/board-ldp.c +++ b/arch/arm/mach-omap2/board-ldp.c @@ -268,14 +268,6 @@ static inline void __init ldp_init_smsc911x(void) gpio_direction_input(eth_gpio); } -static void __init omap_ldp_init_irq(void) -{ - omap2_init_common_hw(NULL, NULL); - omap_init_irq(); - omap_gpio_init(); - ldp_init_smsc911x(); -} - static struct platform_device ldp_lcd_device = { .name = "ldp_lcd", .id = -1, @@ -289,6 +281,16 @@ static struct omap_board_config_kernel ldp_config[] __initdata = { { OMAP_TAG_LCD, &ldp_lcd_config }, }; +static void __init omap_ldp_init_irq(void) +{ + omap_board_config = ldp_config; + omap_board_config_size = ARRAY_SIZE(ldp_config); + omap2_init_common_hw(NULL, NULL); + omap_init_irq(); + omap_gpio_init(); + ldp_init_smsc911x(); +} + static struct twl4030_usb_data ldp_usb_data = { .usb_mode = T2_USB_MODE_ULPI, }; @@ -372,8 +374,6 @@ static void __init omap_ldp_init(void) { omap_i2c_init(); platform_add_devices(ldp_devices, ARRAY_SIZE(ldp_devices)); - omap_board_config = ldp_config; - omap_board_config_size = ARRAY_SIZE(ldp_config); ts_gpio = 54; ldp_spi_board_info[0].irq = gpio_to_irq(ts_gpio); spi_register_board_info(ldp_spi_board_info, diff --git a/arch/arm/mach-omap2/board-omap3beagle.c b/arch/arm/mach-omap2/board-omap3beagle.c index d79ea8d..500c995 100644 --- a/arch/arm/mach-omap2/board-omap3beagle.c +++ b/arch/arm/mach-omap2/board-omap3beagle.c @@ -281,17 +281,6 @@ static int __init omap3_beagle_i2c_init(void) return 0; } -static void __init omap3_beagle_init_irq(void) -{ - omap2_init_common_hw(mt46h32m32lf6_sdrc_params, - mt46h32m32lf6_sdrc_params); - omap_init_irq(); -#ifdef CONFIG_OMAP_32K_TIMER - omap2_gp_clockevent_set_gptimer(12); -#endif - omap_gpio_init(); -} - static struct gpio_led gpio_leds[] = { { .name = "beagleboard::usr0", @@ -349,6 +338,19 @@ static struct omap_board_config_kernel omap3_beagle_config[] __initdata = { { OMAP_TAG_LCD, &omap3_beagle_lcd_config }, }; +static void __init omap3_beagle_init_irq(void) +{ + omap_board_config = omap3_beagle_config; + omap_board_config_size = ARRAY_SIZE(omap3_beagle_config); + omap2_init_common_hw(mt46h32m32lf6_sdrc_params, + mt46h32m32lf6_sdrc_params); + omap_init_irq(); +#ifdef CONFIG_OMAP_32K_TIMER + omap2_gp_clockevent_set_gptimer(12); +#endif + omap_gpio_init(); +} + static struct platform_device *omap3_beagle_devices[] __initdata = { &omap3_beagle_lcd_device, &leds_gpio, @@ -398,8 +400,6 @@ static void __init omap3_beagle_init(void) omap3_beagle_i2c_init(); platform_add_devices(omap3_beagle_devices, ARRAY_SIZE(omap3_beagle_devices)); - omap_board_config = omap3_beagle_config; - omap_board_config_size = ARRAY_SIZE(omap3_beagle_config); omap_serial_init(); omap_cfg_reg(J25_34XX_GPIO170); diff --git a/arch/arm/mach-omap2/board-omap3evm.c b/arch/arm/mach-omap2/board-omap3evm.c index 3e04353..d50b9be 100644 --- a/arch/arm/mach-omap2/board-omap3evm.c +++ b/arch/arm/mach-omap2/board-omap3evm.c @@ -274,18 +274,20 @@ struct spi_board_info omap3evm_spi_board_info[] = { }, }; +static struct omap_board_config_kernel omap3_evm_config[] __initdata = { + { OMAP_TAG_LCD, &omap3_evm_lcd_config }, +}; + static void __init omap3_evm_init_irq(void) { + omap_board_config = omap3_evm_config; + omap_board_config_size = ARRAY_SIZE(omap3_evm_config); omap2_init_common_hw(mt46h32m32lf6_sdrc_params, NULL); omap_init_irq(); omap_gpio_init(); omap3evm_init_smc911x(); } -static struct omap_board_config_kernel omap3_evm_config[] __initdata = { - { OMAP_TAG_LCD, &omap3_evm_lcd_config }, -}; - static struct platform_device *omap3_evm_devices[] __initdata = { &omap3_evm_lcd_device, &omap3evm_smc911x_device, @@ -296,8 +298,6 @@ static void __init omap3_evm_init(void) omap3_evm_i2c_init(); platform_add_devices(omap3_evm_devices, ARRAY_SIZE(omap3_evm_devices)); - omap_board_config = omap3_evm_config; - omap_board_config_size = ARRAY_SIZE(omap3_evm_config); spi_register_board_info(omap3evm_spi_board_info, ARRAY_SIZE(omap3evm_spi_board_info)); diff --git a/arch/arm/mach-omap2/board-omap3pandora.c b/arch/arm/mach-omap2/board-omap3pandora.c index 8236708..b43f6e3 100644 --- a/arch/arm/mach-omap2/board-omap3pandora.c +++ b/arch/arm/mach-omap2/board-omap3pandora.c @@ -305,14 +305,6 @@ static int __init omap3pandora_i2c_init(void) return 0; } -static void __init omap3pandora_init_irq(void) -{ - omap2_init_common_hw(mt46h32m32lf6_sdrc_params, - mt46h32m32lf6_sdrc_params); - omap_init_irq(); - omap_gpio_init(); -} - static void __init omap3pandora_ads7846_init(void) { int gpio = OMAP3_PANDORA_TS_GPIO; @@ -375,6 +367,16 @@ static struct omap_board_config_kernel omap3pandora_config[] __initdata = { { OMAP_TAG_LCD, &omap3pandora_lcd_config }, }; +static void __init omap3pandora_init_irq(void) +{ + omap_board_config = omap3pandora_config; + omap_board_config_size = ARRAY_SIZE(omap3pandora_config); + omap2_init_common_hw(mt46h32m32lf6_sdrc_params, + mt46h32m32lf6_sdrc_params); + omap_init_irq(); + omap_gpio_init(); +} + static struct platform_device *omap3pandora_devices[] __initdata = { &omap3pandora_lcd_device, &pandora_leds_gpio, @@ -386,8 +388,6 @@ static void __init omap3pandora_init(void) omap3pandora_i2c_init(); platform_add_devices(omap3pandora_devices, ARRAY_SIZE(omap3pandora_devices)); - omap_board_config = omap3pandora_config; - omap_board_config_size = ARRAY_SIZE(omap3pandora_config); omap_serial_init(); spi_register_board_info(omap3pandora_spi_board_info, ARRAY_SIZE(omap3pandora_spi_board_info)); diff --git a/arch/arm/mach-omap2/board-overo.c b/arch/arm/mach-omap2/board-overo.c index eb78e6e..9917d2f 100644 --- a/arch/arm/mach-omap2/board-overo.c +++ b/arch/arm/mach-omap2/board-overo.c @@ -357,14 +357,6 @@ static int __init overo_i2c_init(void) return 0; } -static void __init overo_init_irq(void) -{ - omap2_init_common_hw(mt46h32m32lf6_sdrc_params, - mt46h32m32lf6_sdrc_params); - omap_init_irq(); - omap_gpio_init(); -} - static struct platform_device overo_lcd_device = { .name = "overo_lcd", .id = -1, @@ -378,6 +370,16 @@ static struct omap_board_config_kernel overo_config[] __initdata = { { OMAP_TAG_LCD, &overo_lcd_config }, }; +static void __init overo_init_irq(void) +{ + omap_board_config = overo_config; + omap_board_config_size = ARRAY_SIZE(overo_config); + omap2_init_common_hw(mt46h32m32lf6_sdrc_params, + mt46h32m32lf6_sdrc_params); + omap_init_irq(); + omap_gpio_init(); +} + static struct platform_device *overo_devices[] __initdata = { &overo_lcd_device, }; @@ -386,8 +388,6 @@ static void __init overo_init(void) { overo_i2c_init(); platform_add_devices(overo_devices, ARRAY_SIZE(overo_devices)); - omap_board_config = overo_config; - omap_board_config_size = ARRAY_SIZE(overo_config); omap_serial_init(); overo_flash_init(); usb_musb_init(); diff --git a/arch/arm/mach-omap2/board-rx51.c b/arch/arm/mach-omap2/board-rx51.c index c0d1340..f9196c3 100644 --- a/arch/arm/mach-omap2/board-rx51.c +++ b/arch/arm/mach-omap2/board-rx51.c @@ -56,6 +56,8 @@ static struct omap_board_config_kernel rx51_config[] = { static void __init rx51_init_irq(void) { + omap_board_config = rx51_config; + omap_board_config_size = ARRAY_SIZE(rx51_config); omap2_init_common_hw(NULL, NULL); omap_init_irq(); omap_gpio_init(); @@ -65,8 +67,6 @@ extern void __init rx51_peripherals_init(void); static void __init rx51_init(void) { - omap_board_config = rx51_config; - omap_board_config_size = ARRAY_SIZE(rx51_config); omap_serial_init(); usb_musb_init(); rx51_peripherals_init(); diff --git a/arch/arm/mach-omap2/board-zoom2.c b/arch/arm/mach-omap2/board-zoom2.c index dabba27..324009e 100644 --- a/arch/arm/mach-omap2/board-zoom2.c +++ b/arch/arm/mach-omap2/board-zoom2.c @@ -90,13 +90,6 @@ static struct twl4030_keypad_data zoom2_kp_twl4030_data = { .rep = 1, }; -static void __init omap_zoom2_init_irq(void) -{ - omap2_init_common_hw(NULL, NULL); - omap_init_irq(); - omap_gpio_init(); -} - static struct omap_board_config_kernel zoom2_config[] __initdata = { }; @@ -212,6 +205,15 @@ static struct twl4030_usb_data zoom2_usb_data = { .usb_mode = T2_USB_MODE_ULPI, }; +static void __init omap_zoom2_init_irq(void) +{ + omap_board_config = zoom2_config; + omap_board_config_size = ARRAY_SIZE(zoom2_config); + omap2_init_common_hw(NULL, NULL); + omap_init_irq(); + omap_gpio_init(); +} + static struct twl4030_gpio_platform_data zoom2_gpio_data = { .gpio_base = OMAP_MAX_GPIO_LINES, .irq_base = TWL4030_GPIO_IRQ_BASE, @@ -262,8 +264,6 @@ extern int __init omap_zoom2_debugboard_init(void); static void __init omap_zoom2_init(void) { omap_i2c_init(); - omap_board_config = zoom2_config; - omap_board_config_size = ARRAY_SIZE(zoom2_config); omap_serial_init(); omap_zoom2_debugboard_init(); usb_musb_init(); diff --git a/arch/arm/mach-omap2/io.c b/arch/arm/mach-omap2/io.c index 4bfe873..470b1d1 100644 --- a/arch/arm/mach-omap2/io.c +++ b/arch/arm/mach-omap2/io.c @@ -32,6 +32,7 @@ #include #include #include +#include #ifndef CONFIG_ARCH_OMAP4 /* FIXME: Remove this once clkdev is ready */ #include "clock.h" @@ -287,6 +288,7 @@ void __init omap2_init_common_hw(struct omap_sdrc_params *sdrc_cs0, pwrdm_init(powerdomains_omap); clkdm_init(clockdomains_omap, clkdm_pwrdm_autodeps); omap2_clk_init(); + omap_serial_early_init(); omap_pm_if_init(); omap2_sdrc_init(sdrc_cs0, sdrc_cs1); _omap2_init_reprogram_sdrc(); diff --git a/arch/arm/mach-omap2/serial.c b/arch/arm/mach-omap2/serial.c index dd3c735..ca28424 100644 --- a/arch/arm/mach-omap2/serial.c +++ b/arch/arm/mach-omap2/serial.c @@ -552,7 +552,7 @@ static struct omap_uart_state omap_uart[OMAP_MAX_NR_PORTS] = { }, }; -void __init omap_serial_init(void) +void __init omap_serial_early_init(void) { int i; char name[16]; @@ -595,6 +595,18 @@ void __init omap_serial_init(void) p->irq += 32; omap_uart_enable_clocks(uart); + } +} + +void __init omap_serial_init(void) +{ + int i; + + for (i = 0; i < OMAP_MAX_NR_PORTS; i++) { + struct omap_uart_state *uart = &omap_uart[i]; + struct platform_device *pdev = &uart->pdev; + struct device *dev = &pdev->dev; + omap_uart_reset(uart); omap_uart_idle_init(uart); diff --git a/arch/arm/plat-omap/include/mach/serial.h b/arch/arm/plat-omap/include/mach/serial.h index def0529..e249186 100644 --- a/arch/arm/plat-omap/include/mach/serial.h +++ b/arch/arm/plat-omap/include/mach/serial.h @@ -13,6 +13,8 @@ #ifndef __ASM_ARCH_SERIAL_H #define __ASM_ARCH_SERIAL_H +#include + #if defined(CONFIG_ARCH_OMAP1) /* OMAP1 serial ports */ #define OMAP_UART1_BASE 0xfffb0000 @@ -53,6 +55,7 @@ }) #ifndef __ASSEMBLER__ +extern void __init omap_serial_early_init(void); extern void omap_serial_init(void); extern int omap_uart_can_sleep(void); extern void omap_uart_check_wakeup(void); -- cgit v0.10.2 From 63c852384125ca0eff0e695f92cbbd439bed4aa3 Mon Sep 17 00:00:00 2001 From: Paul Walmsley Date: Thu, 3 Sep 2009 20:14:03 +0300 Subject: OMAP2/3/4: create omap_hwmod layer OMAP SoCs can be considered a collection of hardware IP blocks connected by various interconnects. The bus topology and device integration data is somewhat more complex than platform_device can encode. This patch creates code and structures to manage information about OMAP on-chip devices ("hardware modules") and their integration to the rest of the chip. Hardware module data is intended to be generated dynamically from the TI hardware database for the OMAP4 chips and beyond, easing Linux support for new chip variants. This code currently: - resets and configures all hardware modules upon startup, reducing bootloader dependencies; - provides hooks for Linux driver model code to enable, idle, and shutdown hardware modules (forthcoming patch); - waits for hardware modules to leave idle once their clocks are enabled and OCP_SYSCONFIG bits are set appropriately. - provides a means to pass arbitrary IP block configuration data (e.g., FIFO size) to the device driver (via the dev_attr void pointer) In the future this code is intended to: - estimate interconnect bandwidth and latency characteristics to ensure constraints are satisfied during DVFS - provide *GRPSEL bit data to the powerdomain code - handle pin/ball muxing for devices - generate IO mapping information dynamically - supply device firewall configuration data - provide hardware module data to other on-chip coprocessor software - allow the removal of the "disable unused clocks" code in the OMAP2/3 clock code This patch represents a collaborative effort involving many people from TI, Nokia, and the Linux-OMAP community. Signed-off-by: Paul Walmsley Cc: Benoit Cousson Cc: Kevin Hilman Cc: Tony Lindgren Cc: Rajendra Nayak Cc: Vikram Pandita Cc: Sakari Poussa Cc: Anand Sawant Cc: Santosh Shilimkar Cc: Eric Thomas Cc: Richard Woodruff diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile index 59d730f..8cb1677 100644 --- a/arch/arm/mach-omap2/Makefile +++ b/arch/arm/mach-omap2/Makefile @@ -5,7 +5,7 @@ # Common support obj-y := id.o io.o control.o mux.o devices.o serial.o gpmc.o timer-gp.o -omap-2-3-common = irq.o sdrc.o +omap-2-3-common = irq.o sdrc.o omap_hwmod.o prcm-common = prcm.o powerdomain.o clock-common = clock.o clockdomain.o diff --git a/arch/arm/mach-omap2/omap_hwmod.c b/arch/arm/mach-omap2/omap_hwmod.c new file mode 100644 index 0000000..3b10ad6 --- /dev/null +++ b/arch/arm/mach-omap2/omap_hwmod.c @@ -0,0 +1,1555 @@ +/* + * omap_hwmod implementation for OMAP2/3/4 + * + * Copyright (C) 2009 Nokia Corporation + * Paul Walmsley + * With fixes and testing from Kevin Hilman + * + * Created in collaboration with (alphabetical order): Benoit Cousson, + * Kevin Hilman, Tony Lindgren, Rajendra Nayak, Vikram Pandita, Sakari + * Poussa, Anand Sawant, Santosh Shilimkar, Richard Woodruff + * + * This program is free software; you can redistribute it and/or modify + * 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: + * - 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) + * - OMAP4430 Multimedia Device Silicon Revision 1.0 (SWPU140) + * - Open Core Protocol Specification 2.2 + * + * To do: + * - pin mux handling + * - handle IO mapping + * - bus throughput & module latency measurement code + * + * XXX add tests at the beginning of each function to ensure the hwmod is + * in the appropriate state + * XXX error return values should be checked to ensure that they are + * appropriate + */ +#undef DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "cm.h" + +/* Maximum microseconds to wait for OMAP module to reset */ +#define MAX_MODULE_RESET_WAIT 10000 + +/* Name of the OMAP hwmod for the MPU */ +#define MPU_INITIATOR_NAME "mpu_hwmod" + +/* omap_hwmod_list contains all registered struct omap_hwmods */ +static LIST_HEAD(omap_hwmod_list); + +static DEFINE_MUTEX(omap_hwmod_mutex); + +/* mpu_oh: used to add/remove MPU initiator from sleepdep list */ +static struct omap_hwmod *mpu_oh; + +/* inited: 0 if omap_hwmod_init() has not yet been called; 1 otherwise */ +static u8 inited; + + +/* Private functions */ + +/** + * _update_sysc_cache - return the module OCP_SYSCONFIG register, keep copy + * @oh: struct omap_hwmod * + * + * Load the current value of the hwmod OCP_SYSCONFIG register into the + * struct omap_hwmod for later use. Returns -EINVAL if the hwmod has no + * OCP_SYSCONFIG register or 0 upon success. + */ +static int _update_sysc_cache(struct omap_hwmod *oh) +{ + if (!oh->sysconfig) { + WARN(!oh->sysconfig, "omap_hwmod: %s: cannot read " + "OCP_SYSCONFIG: not defined on hwmod\n", oh->name); + return -EINVAL; + } + + /* XXX ensure module interface clock is up */ + + oh->_sysc_cache = omap_hwmod_readl(oh, oh->sysconfig->sysc_offs); + + oh->_int_flags |= _HWMOD_SYSCONFIG_LOADED; + + return 0; +} + +/** + * _write_sysconfig - write a value to the module's OCP_SYSCONFIG register + * @v: OCP_SYSCONFIG value to write + * @oh: struct omap_hwmod * + * + * Write @v into the module OCP_SYSCONFIG register, if it has one. No + * return value. + */ +static void _write_sysconfig(u32 v, struct omap_hwmod *oh) +{ + if (!oh->sysconfig) { + WARN(!oh->sysconfig, "omap_hwmod: %s: cannot write " + "OCP_SYSCONFIG: not defined on hwmod\n", oh->name); + return; + } + + /* XXX ensure module interface clock is up */ + + if (oh->_sysc_cache != v) { + oh->_sysc_cache = v; + omap_hwmod_writel(v, oh, oh->sysconfig->sysc_offs); + } +} + +/** + * _set_master_standbymode: set the OCP_SYSCONFIG MIDLEMODE field in @v + * @oh: struct omap_hwmod * + * @standbymode: MIDLEMODE field bits + * @v: pointer to register contents to modify + * + * Update the master standby mode bits in @v to be @standbymode for + * the @oh hwmod. Does not write to the hardware. Returns -EINVAL + * upon error or 0 upon success. + */ +static int _set_master_standbymode(struct omap_hwmod *oh, u8 standbymode, + u32 *v) +{ + if (!oh->sysconfig || + !(oh->sysconfig->sysc_flags & SYSC_HAS_MIDLEMODE)) + return -EINVAL; + + *v &= ~SYSC_MIDLEMODE_MASK; + *v |= __ffs(standbymode) << SYSC_MIDLEMODE_SHIFT; + + return 0; +} + +/** + * _set_slave_idlemode: set the OCP_SYSCONFIG SIDLEMODE field in @v + * @oh: struct omap_hwmod * + * @idlemode: SIDLEMODE field bits + * @v: pointer to register contents to modify + * + * Update the slave idle mode bits in @v to be @idlemode for the @oh + * hwmod. Does not write to the hardware. Returns -EINVAL upon error + * or 0 upon success. + */ +static int _set_slave_idlemode(struct omap_hwmod *oh, u8 idlemode, u32 *v) +{ + if (!oh->sysconfig || + !(oh->sysconfig->sysc_flags & SYSC_HAS_SIDLEMODE)) + return -EINVAL; + + *v &= ~SYSC_SIDLEMODE_MASK; + *v |= __ffs(idlemode) << SYSC_SIDLEMODE_SHIFT; + + return 0; +} + +/** + * _set_clockactivity: set OCP_SYSCONFIG.CLOCKACTIVITY bits in @v + * @oh: struct omap_hwmod * + * @clockact: CLOCKACTIVITY field bits + * @v: pointer to register contents to modify + * + * Update the clockactivity mode bits in @v to be @clockact for the + * @oh hwmod. Used for additional powersaving on some modules. Does + * not write to the hardware. Returns -EINVAL upon error or 0 upon + * success. + */ +static int _set_clockactivity(struct omap_hwmod *oh, u8 clockact, u32 *v) +{ + if (!oh->sysconfig || + !(oh->sysconfig->sysc_flags & SYSC_HAS_CLOCKACTIVITY)) + return -EINVAL; + + *v &= ~SYSC_CLOCKACTIVITY_MASK; + *v |= clockact << SYSC_CLOCKACTIVITY_SHIFT; + + return 0; +} + +/** + * _set_softreset: set OCP_SYSCONFIG.CLOCKACTIVITY bits in @v + * @oh: struct omap_hwmod * + * @v: pointer to register contents to modify + * + * Set the SOFTRESET bit in @v for hwmod @oh. Returns -EINVAL upon + * error or 0 upon success. + */ +static int _set_softreset(struct omap_hwmod *oh, u32 *v) +{ + if (!oh->sysconfig || + !(oh->sysconfig->sysc_flags & SYSC_HAS_SOFTRESET)) + return -EINVAL; + + *v |= SYSC_SOFTRESET_MASK; + + return 0; +} + +/** + * _enable_wakeup: set OCP_SYSCONFIG.ENAWAKEUP bit in the hardware + * @oh: struct omap_hwmod * + * + * Allow the hardware module @oh to send wakeups. Returns -EINVAL + * upon error or 0 upon success. + */ +static int _enable_wakeup(struct omap_hwmod *oh) +{ + u32 v; + + if (!oh->sysconfig || + !(oh->sysconfig->sysc_flags & SYSC_HAS_ENAWAKEUP)) + return -EINVAL; + + v = oh->_sysc_cache; + v |= SYSC_ENAWAKEUP_MASK; + _write_sysconfig(v, oh); + + /* XXX test pwrdm_get_wken for this hwmod's subsystem */ + + oh->_int_flags |= _HWMOD_WAKEUP_ENABLED; + + return 0; +} + +/** + * _disable_wakeup: clear OCP_SYSCONFIG.ENAWAKEUP bit in the hardware + * @oh: struct omap_hwmod * + * + * Prevent the hardware module @oh to send wakeups. Returns -EINVAL + * upon error or 0 upon success. + */ +static int _disable_wakeup(struct omap_hwmod *oh) +{ + u32 v; + + if (!oh->sysconfig || + !(oh->sysconfig->sysc_flags & SYSC_HAS_ENAWAKEUP)) + return -EINVAL; + + v = oh->_sysc_cache; + v &= ~SYSC_ENAWAKEUP_MASK; + _write_sysconfig(v, oh); + + /* XXX test pwrdm_get_wken for this hwmod's subsystem */ + + oh->_int_flags &= ~_HWMOD_WAKEUP_ENABLED; + + return 0; +} + +/** + * _add_initiator_dep: prevent @oh from smart-idling while @init_oh is active + * @oh: struct omap_hwmod * + * + * Prevent the hardware module @oh from entering idle while the + * hardare module initiator @init_oh is active. Useful when a module + * will be accessed by a particular initiator (e.g., if a module will + * be accessed by the IVA, there should be a sleepdep between the IVA + * initiator and the module). Only applies to modules in smart-idle + * mode. Returns -EINVAL upon error or passes along + * pwrdm_add_sleepdep() value upon success. + */ +static int _add_initiator_dep(struct omap_hwmod *oh, struct omap_hwmod *init_oh) +{ + if (!oh->_clk) + return -EINVAL; + + return pwrdm_add_sleepdep(oh->_clk->clkdm->pwrdm.ptr, + init_oh->_clk->clkdm->pwrdm.ptr); +} + +/** + * _del_initiator_dep: allow @oh to smart-idle even if @init_oh is active + * @oh: struct omap_hwmod * + * + * Allow the hardware module @oh to enter idle while the hardare + * module initiator @init_oh is active. Useful when a module will not + * be accessed by a particular initiator (e.g., if a module will not + * be accessed by the IVA, there should be no sleepdep between the IVA + * initiator and the module). Only applies to modules in smart-idle + * mode. Returns -EINVAL upon error or passes along + * pwrdm_add_sleepdep() value upon success. + */ +static int _del_initiator_dep(struct omap_hwmod *oh, struct omap_hwmod *init_oh) +{ + if (!oh->_clk) + return -EINVAL; + + return pwrdm_del_sleepdep(oh->_clk->clkdm->pwrdm.ptr, + init_oh->_clk->clkdm->pwrdm.ptr); +} + +/** + * _init_main_clk - get a struct clk * for the the hwmod's main functional clk + * @oh: struct omap_hwmod * + * + * Called from _init_clocks(). Populates the @oh _clk (main + * functional clock pointer) if a main_clk is present. Returns 0 on + * success or -EINVAL on error. + */ +static int _init_main_clk(struct omap_hwmod *oh) +{ + struct clk *c; + int ret = 0; + + if (!oh->clkdev_con_id) + return 0; + + c = clk_get_sys(oh->clkdev_dev_id, oh->clkdev_con_id); + WARN(IS_ERR(c), "omap_hwmod: %s: cannot clk_get main_clk %s.%s\n", + oh->name, oh->clkdev_dev_id, oh->clkdev_con_id); + if (IS_ERR(c)) + ret = -EINVAL; + oh->_clk = c; + + return ret; +} + +/** + * _init_interface_clk - get a struct clk * for the the hwmod's interface clks + * @oh: struct omap_hwmod * + * + * Called from _init_clocks(). Populates the @oh OCP slave interface + * clock pointers. Returns 0 on success or -EINVAL on error. + */ +static int _init_interface_clks(struct omap_hwmod *oh) +{ + struct omap_hwmod_ocp_if *os; + struct clk *c; + int i; + int ret = 0; + + if (oh->slaves_cnt == 0) + return 0; + + for (i = 0, os = *oh->slaves; i < oh->slaves_cnt; i++, os++) { + if (!os->clkdev_con_id) + continue; + + c = clk_get_sys(os->clkdev_dev_id, os->clkdev_con_id); + WARN(IS_ERR(c), "omap_hwmod: %s: cannot clk_get " + "interface_clk %s.%s\n", oh->name, + os->clkdev_dev_id, os->clkdev_con_id); + if (IS_ERR(c)) + ret = -EINVAL; + os->_clk = c; + } + + return ret; +} + +/** + * _init_opt_clk - get a struct clk * for the the hwmod's optional clocks + * @oh: struct omap_hwmod * + * + * Called from _init_clocks(). Populates the @oh omap_hwmod_opt_clk + * clock pointers. Returns 0 on success or -EINVAL on error. + */ +static int _init_opt_clks(struct omap_hwmod *oh) +{ + struct omap_hwmod_opt_clk *oc; + struct clk *c; + int i; + int ret = 0; + + for (i = oh->opt_clks_cnt, oc = oh->opt_clks; i > 0; i--, oc++) { + c = clk_get_sys(oc->clkdev_dev_id, oc->clkdev_con_id); + WARN(IS_ERR(c), "omap_hwmod: %s: cannot clk_get opt_clk " + "%s.%s\n", oh->name, oc->clkdev_dev_id, + oc->clkdev_con_id); + if (IS_ERR(c)) + ret = -EINVAL; + oc->_clk = c; + } + + return ret; +} + +/** + * _enable_clocks - enable hwmod main clock and interface clocks + * @oh: struct omap_hwmod * + * + * Enables all clocks necessary for register reads and writes to succeed + * on the hwmod @oh. Returns 0. + */ +static int _enable_clocks(struct omap_hwmod *oh) +{ + struct omap_hwmod_ocp_if *os; + int i; + + pr_debug("omap_hwmod: %s: enabling clocks\n", oh->name); + + if (oh->_clk && !IS_ERR(oh->_clk)) + clk_enable(oh->_clk); + + if (oh->slaves_cnt > 0) { + for (i = 0, os = *oh->slaves; i < oh->slaves_cnt; i++, os++) { + struct clk *c = os->_clk; + + if (c && !IS_ERR(c) && (os->flags & OCPIF_SWSUP_IDLE)) + clk_enable(c); + } + } + + /* The opt clocks are controlled by the device driver. */ + + return 0; +} + +/** + * _disable_clocks - disable hwmod main clock and interface clocks + * @oh: struct omap_hwmod * + * + * Disables the hwmod @oh main functional and interface clocks. Returns 0. + */ +static int _disable_clocks(struct omap_hwmod *oh) +{ + struct omap_hwmod_ocp_if *os; + int i; + + pr_debug("omap_hwmod: %s: disabling clocks\n", oh->name); + + if (oh->_clk && !IS_ERR(oh->_clk)) + clk_disable(oh->_clk); + + if (oh->slaves_cnt > 0) { + for (i = 0, os = *oh->slaves; i < oh->slaves_cnt; i++, os++) { + struct clk *c = os->_clk; + + if (c && !IS_ERR(c) && (os->flags & OCPIF_SWSUP_IDLE)) + clk_disable(c); + } + } + + /* The opt clocks are controlled by the device driver. */ + + return 0; +} + +/** + * _find_mpu_port_index - find hwmod OCP slave port ID intended for MPU use + * @oh: struct omap_hwmod * + * + * Returns the array index of the OCP slave port that the MPU + * addresses the device on, or -EINVAL upon error or not found. + */ +static int _find_mpu_port_index(struct omap_hwmod *oh) +{ + struct omap_hwmod_ocp_if *os; + int i; + int found = 0; + + if (!oh || oh->slaves_cnt == 0) + return -EINVAL; + + for (i = 0, os = *oh->slaves; i < oh->slaves_cnt; i++, os++) { + if (os->user & OCP_USER_MPU) { + found = 1; + break; + } + } + + if (found) + pr_debug("omap_hwmod: %s: MPU OCP slave port ID %d\n", + oh->name, i); + else + pr_debug("omap_hwmod: %s: no MPU OCP slave port found\n", + oh->name); + + return (found) ? i : -EINVAL; +} + +/** + * _find_mpu_rt_base - find hwmod register target base addr accessible by MPU + * @oh: struct omap_hwmod * + * + * Return the virtual address of the base of the register target of + * device @oh, or NULL on error. + */ +static void __iomem *_find_mpu_rt_base(struct omap_hwmod *oh, u8 index) +{ + struct omap_hwmod_ocp_if *os; + struct omap_hwmod_addr_space *mem; + int i; + int found = 0; + + if (!oh || oh->slaves_cnt == 0) + return NULL; + + os = *oh->slaves + index; + + for (i = 0, mem = os->addr; i < os->addr_cnt; i++, mem++) { + if (mem->flags & ADDR_TYPE_RT) { + found = 1; + break; + } + } + + /* XXX use ioremap() instead? */ + + if (found) + pr_debug("omap_hwmod: %s: MPU register target at va %p\n", + oh->name, OMAP2_IO_ADDRESS(mem->pa_start)); + else + pr_debug("omap_hwmod: %s: no MPU register target found\n", + oh->name); + + return (found) ? OMAP2_IO_ADDRESS(mem->pa_start) : NULL; +} + +/** + * _sysc_enable - 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 + * idle; otherwise, configure it for smart-idle. If module is marked + * 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) +{ + u8 idlemode; + u32 v; + + if (!oh->sysconfig) + return; + + v = oh->_sysc_cache; + + if (oh->sysconfig->sysc_flags & SYSC_HAS_SIDLEMODE) { + idlemode = (oh->flags & HWMOD_SWSUP_SIDLE) ? + HWMOD_IDLEMODE_NO : HWMOD_IDLEMODE_SMART; + _set_slave_idlemode(oh, idlemode, &v); + } + + if (oh->sysconfig->sysc_flags & SYSC_HAS_MIDLEMODE) { + idlemode = (oh->flags & HWMOD_SWSUP_MSTANDBY) ? + HWMOD_IDLEMODE_NO : HWMOD_IDLEMODE_SMART; + _set_master_standbymode(oh, idlemode, &v); + } + + /* XXX OCP AUTOIDLE bit? */ + + if (oh->flags & HWMOD_SET_DEFAULT_CLOCKACT && + oh->sysconfig->sysc_flags & SYSC_HAS_CLOCKACTIVITY) + _set_clockactivity(oh, oh->sysconfig->clockact, &v); + + _write_sysconfig(v, oh); +} + +/** + * _sysc_idle - 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 + * idle; otherwise, configure it for smart-idle. If module is marked + * 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) +{ + u8 idlemode; + u32 v; + + if (!oh->sysconfig) + return; + + v = oh->_sysc_cache; + + if (oh->sysconfig->sysc_flags & SYSC_HAS_SIDLEMODE) { + idlemode = (oh->flags & HWMOD_SWSUP_SIDLE) ? + HWMOD_IDLEMODE_FORCE : HWMOD_IDLEMODE_SMART; + _set_slave_idlemode(oh, idlemode, &v); + } + + if (oh->sysconfig->sysc_flags & SYSC_HAS_MIDLEMODE) { + idlemode = (oh->flags & HWMOD_SWSUP_MSTANDBY) ? + HWMOD_IDLEMODE_FORCE : HWMOD_IDLEMODE_SMART; + _set_master_standbymode(oh, idlemode, &v); + } + + _write_sysconfig(v, oh); +} + +/** + * _sysc_shutdown - 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) +{ + u32 v; + + if (!oh->sysconfig) + return; + + v = oh->_sysc_cache; + + if (oh->sysconfig->sysc_flags & SYSC_HAS_SIDLEMODE) + _set_slave_idlemode(oh, HWMOD_IDLEMODE_FORCE, &v); + + if (oh->sysconfig->sysc_flags & SYSC_HAS_MIDLEMODE) + _set_master_standbymode(oh, HWMOD_IDLEMODE_FORCE, &v); + + /* XXX clear OCP AUTOIDLE bit? */ + + _write_sysconfig(v, oh); +} + +/** + * _lookup - find an omap_hwmod by name + * @name: find an omap_hwmod by name + * + * Return a pointer to an omap_hwmod by name, or NULL if not found. + * Caller must hold omap_hwmod_mutex. + */ +static struct omap_hwmod *_lookup(const char *name) +{ + struct omap_hwmod *oh, *temp_oh; + + oh = NULL; + + list_for_each_entry(temp_oh, &omap_hwmod_list, node) { + if (!strcmp(name, temp_oh->name)) { + oh = temp_oh; + break; + } + } + + return oh; +} + +/** + * _init_clocks - clk_get() all clocks associated with this hwmod + * @oh: struct omap_hwmod * + * + * Called by omap_hwmod_late_init() (after omap2_clk_init()). + * Resolves all clock names embedded in the hwmod. Must be called + * with omap_hwmod_mutex held. Returns -EINVAL if the omap_hwmod + * has not yet been registered or if the clocks have already been + * initialized, 0 on success, or a non-zero error on failure. + */ +static int _init_clocks(struct omap_hwmod *oh) +{ + int ret = 0; + + if (!oh || (oh->_state != _HWMOD_STATE_REGISTERED)) + return -EINVAL; + + pr_debug("omap_hwmod: %s: looking up clocks\n", oh->name); + + ret |= _init_main_clk(oh); + ret |= _init_interface_clks(oh); + ret |= _init_opt_clks(oh); + + oh->_state = _HWMOD_STATE_CLKS_INITED; + + return ret; +} + +/** + * _wait_target_ready - wait for a module to leave slave idle + * @oh: struct omap_hwmod * + * + * Wait for a module @oh to leave slave idle. Returns 0 if the module + * does not have an IDLEST bit or if the module successfully leaves + * slave idle; otherwise, pass along the return value of the + * appropriate *_cm_wait_module_ready() function. + */ +static int _wait_target_ready(struct omap_hwmod *oh) +{ + struct omap_hwmod_ocp_if *os; + int ret; + + if (!oh) + return -EINVAL; + + if (oh->_int_flags & _HWMOD_NO_MPU_PORT) + return 0; + + os = *oh->slaves + oh->_mpu_port_index; + + if (!(os->flags & OCPIF_HAS_IDLEST)) + return 0; + + /* XXX check module SIDLEMODE */ + + /* XXX check clock enable states */ + + if (cpu_is_omap24xx() || cpu_is_omap34xx()) { + ret = omap2_cm_wait_module_ready(oh->prcm.omap2.module_offs, + oh->prcm.omap2.idlest_reg_id, + oh->prcm.omap2.idlest_idle_bit); +#if 0 + } else if (cpu_is_omap44xx()) { + ret = omap4_cm_wait_module_ready(oh->prcm.omap4.module_offs, + oh->prcm.omap4.device_offs); +#endif + } else { + BUG(); + }; + + return ret; +} + +/** + * _reset - reset an omap_hwmod + * @oh: struct omap_hwmod * + * + * Resets an omap_hwmod @oh via the OCP_SYSCONFIG bit. hwmod must be + * enabled for this to work. Must be called with omap_hwmod_mutex + * held. 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. + */ +static int _reset(struct omap_hwmod *oh) +{ + u32 r, v; + int c; + + if (!oh->sysconfig || + !(oh->sysconfig->sysc_flags & SYSC_HAS_SOFTRESET) || + (oh->sysconfig->sysc_flags & SYSS_MISSING)) + 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); + return -EINVAL; + } + + pr_debug("omap_hwmod: %s: resetting\n", oh->name); + + v = oh->_sysc_cache; + r = _set_softreset(oh, &v); + if (r) + return r; + _write_sysconfig(v, oh); + + c = 0; + while (c < MAX_MODULE_RESET_WAIT && + !(omap_hwmod_readl(oh, oh->sysconfig->syss_offs) & + SYSS_RESETDONE_MASK)) { + udelay(1); + c++; + } + + if (c == MAX_MODULE_RESET_WAIT) + WARN(1, "omap_hwmod: %s: failed to reset in %d usec\n", + oh->name, MAX_MODULE_RESET_WAIT); + else + pr_debug("omap_hwmod: %s: reset in %d usec\n", oh->name, + MAX_MODULE_RESET_WAIT); + + /* + * XXX add _HWMOD_STATE_WEDGED for modules that don't come back from + * _wait_target_ready() or _reset() + */ + + return (c == MAX_MODULE_RESET_WAIT) ? -ETIMEDOUT : 0; +} + +/** + * _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. Must be called with omap_hwmod_mutex held. + * 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; + + 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; + } + + pr_debug("omap_hwmod: %s: enabling\n", oh->name); + + /* XXX mux balls */ + + _add_initiator_dep(oh, mpu_oh); + _enable_clocks(oh); + + if (oh->sysconfig) { + if (!(oh->_int_flags & _HWMOD_SYSCONFIG_LOADED)) + _update_sysc_cache(oh); + _sysc_enable(oh); + } + + r = _wait_target_ready(oh); + if (!r) + oh->_state = _HWMOD_STATE_ENABLED; + + 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) +{ + 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; + } + + pr_debug("omap_hwmod: %s: idling\n", oh->name); + + if (oh->sysconfig) + _sysc_idle(oh); + _del_initiator_dep(oh, mpu_oh); + _disable_clocks(oh); + + oh->_state = _HWMOD_STATE_IDLE; + + return 0; +} + +/** + * _shutdown - shutdown an omap_hwmod + * @oh: struct omap_hwmod * + * + * Shut down an omap_hwmod @oh. This should be called when the driver + * used for the hwmod is removed or unloaded or if the driver is not + * used by the system. Returns -EINVAL if the hwmod is in the wrong + * state or returns 0. + */ +static int _shutdown(struct omap_hwmod *oh) +{ + if (oh->_state != _HWMOD_STATE_IDLE && + oh->_state != _HWMOD_STATE_ENABLED) { + WARN(1, "omap_hwmod: %s: disabled state can only be entered " + "from idle, or enabled state\n", oh->name); + return -EINVAL; + } + + pr_debug("omap_hwmod: %s: disabling\n", oh->name); + + if (oh->sysconfig) + _sysc_shutdown(oh); + _del_initiator_dep(oh, mpu_oh); + /* XXX what about the other system initiators here? DMA, tesla, d2d */ + _disable_clocks(oh); + /* XXX Should this code also force-disable the optional clocks? */ + + /* XXX mux any associated balls to safe mode */ + + oh->_state = _HWMOD_STATE_DISABLED; + + return 0; +} + +/** + * _write_clockact_lock - set the module's clockactivity bits + * @oh: struct omap_hwmod * + * @clockact: CLOCKACTIVITY field bits + * + * Writes the CLOCKACTIVITY bits @clockact to the hwmod @oh + * OCP_SYSCONFIG register. Returns -EINVAL if the hwmod is in the + * wrong state or returns 0. + */ +static int _write_clockact_lock(struct omap_hwmod *oh, u8 clockact) +{ + u32 v; + + if (!oh->sysconfig || + !(oh->sysconfig->sysc_flags & SYSC_HAS_CLOCKACTIVITY)) + return -EINVAL; + + mutex_lock(&omap_hwmod_mutex); + v = oh->_sysc_cache; + _set_clockactivity(oh, clockact, &v); + _write_sysconfig(v, oh); + mutex_unlock(&omap_hwmod_mutex); + + return 0; +} + + +/** + * _setup - do initial configuration of omap_hwmod + * @oh: struct omap_hwmod * + * + * Writes the CLOCKACTIVITY bits @clockact to the hwmod @oh + * OCP_SYSCONFIG register. Must be called with omap_hwmod_mutex + * held. Returns -EINVAL if the hwmod is in the wrong state or returns + * 0. + */ +static int _setup(struct omap_hwmod *oh) +{ + struct omap_hwmod_ocp_if *os; + int i; + + if (!oh) + return -EINVAL; + + /* Set iclk autoidle mode */ + if (oh->slaves_cnt > 0) { + for (i = 0, os = *oh->slaves; i < oh->slaves_cnt; i++, os++) { + struct clk *c = os->_clk; + + if (!c || IS_ERR(c)) + continue; + + if (os->flags & OCPIF_SWSUP_IDLE) { + /* XXX omap_iclk_deny_idle(c); */ + } else { + /* XXX omap_iclk_allow_idle(c); */ + clk_enable(c); + } + } + } + + oh->_state = _HWMOD_STATE_INITIALIZED; + + _enable(oh); + + if (!(oh->flags & HWMOD_INIT_NO_RESET)) + _reset(oh); + + /* XXX OCP AUTOIDLE bit? */ + /* XXX OCP ENAWAKEUP bit? */ + + if (!(oh->flags & HWMOD_INIT_NO_IDLE)) + _idle(oh); + + return 0; +} + + + +/* Public functions */ + +u32 omap_hwmod_readl(struct omap_hwmod *oh, u16 reg_offs) +{ + return __raw_readl(oh->_rt_va + reg_offs); +} + +void omap_hwmod_writel(u32 v, struct omap_hwmod *oh, u16 reg_offs) +{ + __raw_writel(v, oh->_rt_va + reg_offs); +} + +/** + * omap_hwmod_register - register a struct omap_hwmod + * @oh: struct omap_hwmod * + * + * Registers the omap_hwmod @oh. Returns -EEXIST if an omap_hwmod already + * has been registered by the same name; -EINVAL if the omap_hwmod is in the + * wrong state, or 0 on success. + * + * XXX The data should be copied into bootmem, so the original data + * should be marked __initdata and freed after init. This would allow + * unneeded omap_hwmods to be freed on multi-OMAP configurations. Note + * that the copy process would be relatively complex due to the large number + * of substructures. + */ +int omap_hwmod_register(struct omap_hwmod *oh) +{ + int ret, ms_id; + + if (!oh || (oh->_state != _HWMOD_STATE_UNKNOWN)) + return -EINVAL; + + mutex_lock(&omap_hwmod_mutex); + + pr_debug("omap_hwmod: %s: registering\n", oh->name); + + if (_lookup(oh->name)) { + ret = -EEXIST; + goto ohr_unlock; + } + + ms_id = _find_mpu_port_index(oh); + if (!IS_ERR_VALUE(ms_id)) { + oh->_mpu_port_index = ms_id; + oh->_rt_va = _find_mpu_rt_base(oh, oh->_mpu_port_index); + } else { + oh->_int_flags |= _HWMOD_NO_MPU_PORT; + } + + list_add_tail(&oh->node, &omap_hwmod_list); + + oh->_state = _HWMOD_STATE_REGISTERED; + + ret = 0; + +ohr_unlock: + mutex_unlock(&omap_hwmod_mutex); + return ret; +} + +/** + * omap_hwmod_lookup - look up a registered omap_hwmod by name + * @name: name of the omap_hwmod to look up + * + * Given a @name of an omap_hwmod, return a pointer to the registered + * struct omap_hwmod *, or NULL upon error. + */ +struct omap_hwmod *omap_hwmod_lookup(const char *name) +{ + struct omap_hwmod *oh; + + if (!name) + return NULL; + + mutex_lock(&omap_hwmod_mutex); + oh = _lookup(name); + mutex_unlock(&omap_hwmod_mutex); + + return oh; +} + +/** + * omap_hwmod_for_each - call function for each registered omap_hwmod + * @fn: pointer to a callback function + * + * Call @fn for each registered omap_hwmod, passing @data to each + * function. @fn must return 0 for success or any other value for + * failure. If @fn returns non-zero, the iteration across omap_hwmods + * will stop and the non-zero return value will be passed to the + * caller of omap_hwmod_for_each(). @fn is called with + * omap_hwmod_for_each() held. + */ +int omap_hwmod_for_each(int (*fn)(struct omap_hwmod *oh)) +{ + struct omap_hwmod *temp_oh; + int ret; + + if (!fn) + return -EINVAL; + + mutex_lock(&omap_hwmod_mutex); + list_for_each_entry(temp_oh, &omap_hwmod_list, node) { + ret = (*fn)(temp_oh); + if (ret) + break; + } + mutex_unlock(&omap_hwmod_mutex); + + return ret; +} + + +/** + * omap_hwmod_init - init omap_hwmod code and register hwmods + * @ohs: pointer to an array of omap_hwmods to register + * + * Intended to be called early in boot before the clock framework is + * initialized. If @ohs is not null, will register all omap_hwmods + * listed in @ohs that are valid for this chip. Returns -EINVAL if + * omap_hwmod_init() has already been called or 0 otherwise. + */ +int omap_hwmod_init(struct omap_hwmod **ohs) +{ + struct omap_hwmod *oh; + int r; + + if (inited) + return -EINVAL; + + inited = 1; + + if (!ohs) + return 0; + + oh = *ohs; + while (oh) { + if (omap_chip_is(oh->omap_chip)) { + r = omap_hwmod_register(oh); + WARN(r, "omap_hwmod: %s: omap_hwmod_register returned " + "%d\n", oh->name, r); + } + oh = *++ohs; + } + + return 0; +} + +/** + * omap_hwmod_late_init - do some post-clock framework initialization + * + * Must be called after omap2_clk_init(). Resolves the struct clk names + * to struct clk pointers for each registered omap_hwmod. Also calls + * _setup() on each hwmod. Returns 0. + */ +int omap_hwmod_late_init(void) +{ + int r; + + /* XXX check return value */ + r = omap_hwmod_for_each(_init_clocks); + WARN(r, "omap_hwmod: omap_hwmod_late_init(): _init_clocks failed\n"); + + mpu_oh = omap_hwmod_lookup(MPU_INITIATOR_NAME); + WARN(!mpu_oh, "omap_hwmod: could not find MPU initiator hwmod %s\n", + MPU_INITIATOR_NAME); + + omap_hwmod_for_each(_setup); + + return 0; +} + +/** + * omap_hwmod_unregister - unregister an omap_hwmod + * @oh: struct omap_hwmod * + * + * Unregisters a previously-registered omap_hwmod @oh. There's probably + * no use case for this, so it is likely to be removed in a later version. + * + * XXX Free all of the bootmem-allocated structures here when that is + * implemented. Make it clear that core code is the only code that is + * expected to unregister modules. + */ +int omap_hwmod_unregister(struct omap_hwmod *oh) +{ + if (!oh) + return -EINVAL; + + pr_debug("omap_hwmod: %s: unregistering\n", oh->name); + + mutex_lock(&omap_hwmod_mutex); + list_del(&oh->node); + mutex_unlock(&omap_hwmod_mutex); + + return 0; +} + +/** + * omap_hwmod_enable - enable an omap_hwmod + * @oh: struct omap_hwmod * + * + * Enable an omap_hwomd @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) +{ + int r; + + if (!oh) + return -EINVAL; + + mutex_lock(&omap_hwmod_mutex); + r = _enable(oh); + mutex_unlock(&omap_hwmod_mutex); + + return r; +} + +/** + * omap_hwmod_idle - idle an omap_hwmod + * @oh: struct omap_hwmod * + * + * Idle an omap_hwomd @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) +{ + if (!oh) + return -EINVAL; + + mutex_lock(&omap_hwmod_mutex); + _idle(oh); + mutex_unlock(&omap_hwmod_mutex); + + return 0; +} + +/** + * omap_hwmod_shutdown - shutdown an omap_hwmod + * @oh: struct omap_hwmod * + * + * Shutdown an omap_hwomd @oh. Intended to be called by + * omap_device_shutdown(). Returns -EINVAL on error or passes along + * the return value from _shutdown(). + */ +int omap_hwmod_shutdown(struct omap_hwmod *oh) +{ + if (!oh) + return -EINVAL; + + mutex_lock(&omap_hwmod_mutex); + _shutdown(oh); + mutex_unlock(&omap_hwmod_mutex); + + return 0; +} + +/** + * omap_hwmod_enable_clocks - enable main_clk, all interface clocks + * @oh: struct omap_hwmod *oh + * + * Intended to be called by the omap_device code. + */ +int omap_hwmod_enable_clocks(struct omap_hwmod *oh) +{ + mutex_lock(&omap_hwmod_mutex); + _enable_clocks(oh); + mutex_unlock(&omap_hwmod_mutex); + + return 0; +} + +/** + * omap_hwmod_disable_clocks - disable main_clk, all interface clocks + * @oh: struct omap_hwmod *oh + * + * Intended to be called by the omap_device code. + */ +int omap_hwmod_disable_clocks(struct omap_hwmod *oh) +{ + mutex_lock(&omap_hwmod_mutex); + _disable_clocks(oh); + mutex_unlock(&omap_hwmod_mutex); + + return 0; +} + +/** + * omap_hwmod_ocp_barrier - wait for posted writes against the hwmod to complete + * @oh: struct omap_hwmod *oh + * + * Intended to be called by drivers and core code when all posted + * writes to a device must complete before continuing further + * execution (for example, after clearing some device IRQSTATUS + * register bits) + * + * XXX what about targets with multiple OCP threads? + */ +void omap_hwmod_ocp_barrier(struct omap_hwmod *oh) +{ + BUG_ON(!oh); + + if (!oh->sysconfig || !oh->sysconfig->sysc_flags) { + WARN(1, "omap_device: %s: OCP barrier impossible due to " + "device configuration\n", oh->name); + return; + } + + /* + * Forces posted writes to complete on the OCP thread handling + * register writes + */ + omap_hwmod_readl(oh, oh->sysconfig->sysc_offs); +} + +/** + * omap_hwmod_reset - reset the hwmod + * @oh: struct omap_hwmod * + * + * Under some conditions, a driver may wish to reset the entire device. + * Called from omap_device code. Returns -EINVAL on error or passes along + * the return value from _reset()/_enable(). + */ +int omap_hwmod_reset(struct omap_hwmod *oh) +{ + int r; + + if (!oh || !(oh->_state & _HWMOD_STATE_ENABLED)) + return -EINVAL; + + mutex_lock(&omap_hwmod_mutex); + r = _reset(oh); + if (!r) + r = _enable(oh); + mutex_unlock(&omap_hwmod_mutex); + + return r; +} + +/** + * omap_hwmod_count_resources - count number of struct resources needed by hwmod + * @oh: struct omap_hwmod * + * @res: pointer to the first element of an array of struct resource to fill + * + * Count the number of struct resource array elements necessary to + * contain omap_hwmod @oh resources. Intended to be called by code + * that registers omap_devices. Intended to be used to determine the + * size of a dynamically-allocated struct resource array, before + * calling omap_hwmod_fill_resources(). Returns the number of struct + * resource array elements needed. + * + * XXX This code is not optimized. It could attempt to merge adjacent + * resource IDs. + * + */ +int omap_hwmod_count_resources(struct omap_hwmod *oh) +{ + int ret, i; + + ret = oh->mpu_irqs_cnt + oh->sdma_chs_cnt; + + for (i = 0; i < oh->slaves_cnt; i++) + ret += (*oh->slaves + i)->addr_cnt; + + return ret; +} + +/** + * omap_hwmod_fill_resources - fill struct resource array with hwmod data + * @oh: struct omap_hwmod * + * @res: pointer to the first element of an array of struct resource to fill + * + * Fill the struct resource array @res with resource data from the + * omap_hwmod @oh. Intended to be called by code that registers + * omap_devices. See also omap_hwmod_count_resources(). Returns the + * number of array elements filled. + */ +int omap_hwmod_fill_resources(struct omap_hwmod *oh, struct resource *res) +{ + int i, j; + int r = 0; + + /* For each IRQ, DMA, memory area, fill in array.*/ + + for (i = 0; i < oh->mpu_irqs_cnt; i++) { + (res + r)->start = *(oh->mpu_irqs + i); + (res + r)->end = *(oh->mpu_irqs + i); + (res + r)->flags = IORESOURCE_IRQ; + r++; + } + + for (i = 0; i < oh->sdma_chs_cnt; i++) { + (res + r)->name = (oh->sdma_chs + i)->name; + (res + r)->start = (oh->sdma_chs + i)->dma_ch; + (res + r)->end = (oh->sdma_chs + i)->dma_ch; + (res + r)->flags = IORESOURCE_DMA; + r++; + } + + for (i = 0; i < oh->slaves_cnt; i++) { + struct omap_hwmod_ocp_if *os; + + os = *oh->slaves + i; + + for (j = 0; j < os->addr_cnt; j++) { + (res + r)->start = (os->addr + j)->pa_start; + (res + r)->end = (os->addr + j)->pa_end; + (res + r)->flags = IORESOURCE_MEM; + r++; + } + } + + return r; +} + +/** + * omap_hwmod_get_pwrdm - return pointer to this module's main powerdomain + * @oh: struct omap_hwmod * + * + * Return the powerdomain pointer associated with the OMAP module + * @oh's main clock. If @oh does not have a main clk, return the + * powerdomain associated with the interface clock associated with the + * module's MPU port. (XXX Perhaps this should use the SDMA port + * instead?) Returns NULL on error, or a struct powerdomain * on + * success. + */ +struct powerdomain *omap_hwmod_get_pwrdm(struct omap_hwmod *oh) +{ + struct clk *c; + + if (!oh) + return NULL; + + if (oh->_clk) { + c = oh->_clk; + } else { + if (oh->_int_flags & _HWMOD_NO_MPU_PORT) + return NULL; + c = oh->slaves[oh->_mpu_port_index]->_clk; + } + + return c->clkdm->pwrdm.ptr; + +} + +/** + * omap_hwmod_add_initiator_dep - add sleepdep from @init_oh to @oh + * @oh: struct omap_hwmod * + * @init_oh: struct omap_hwmod * (initiator) + * + * Add a sleep dependency between the initiator @init_oh and @oh. + * Intended to be called by DSP/Bridge code via platform_data for the + * DSP case; and by the DMA code in the sDMA case. DMA code, *Bridge + * code needs to add/del initiator dependencies dynamically + * before/after accessing a device. Returns the return value from + * _add_initiator_dep(). + * + * XXX Keep a usecount in the clockdomain code + */ +int omap_hwmod_add_initiator_dep(struct omap_hwmod *oh, + struct omap_hwmod *init_oh) +{ + return _add_initiator_dep(oh, init_oh); +} + +/* + * XXX what about functions for drivers to save/restore ocp_sysconfig + * for context save/restore operations? + */ + +/** + * omap_hwmod_del_initiator_dep - remove sleepdep from @init_oh to @oh + * @oh: struct omap_hwmod * + * @init_oh: struct omap_hwmod * (initiator) + * + * Remove a sleep dependency between the initiator @init_oh and @oh. + * Intended to be called by DSP/Bridge code via platform_data for the + * DSP case; and by the DMA code in the sDMA case. DMA code, *Bridge + * code needs to add/del initiator dependencies dynamically + * before/after accessing a device. Returns the return value from + * _del_initiator_dep(). + * + * XXX Keep a usecount in the clockdomain code + */ +int omap_hwmod_del_initiator_dep(struct omap_hwmod *oh, + struct omap_hwmod *init_oh) +{ + return _del_initiator_dep(oh, init_oh); +} + +/** + * omap_hwmod_set_clockact_none - set clockactivity test to BOTH + * @oh: struct omap_hwmod * + * + * On some modules, this function can affect the wakeup latency vs. + * power consumption balance. Intended to be called by the + * omap_device layer. Passes along the return value from + * _write_clockact_lock(). + */ +int omap_hwmod_set_clockact_both(struct omap_hwmod *oh) +{ + return _write_clockact_lock(oh, CLOCKACT_TEST_BOTH); +} + +/** + * omap_hwmod_set_clockact_none - set clockactivity test to MAIN + * @oh: struct omap_hwmod * + * + * On some modules, this function can affect the wakeup latency vs. + * power consumption balance. Intended to be called by the + * omap_device layer. Passes along the return value from + * _write_clockact_lock(). + */ +int omap_hwmod_set_clockact_main(struct omap_hwmod *oh) +{ + return _write_clockact_lock(oh, CLOCKACT_TEST_MAIN); +} + +/** + * omap_hwmod_set_clockact_none - set clockactivity test to ICLK + * @oh: struct omap_hwmod * + * + * On some modules, this function can affect the wakeup latency vs. + * power consumption balance. Intended to be called by the + * omap_device layer. Passes along the return value from + * _write_clockact_lock(). + */ +int omap_hwmod_set_clockact_iclk(struct omap_hwmod *oh) +{ + return _write_clockact_lock(oh, CLOCKACT_TEST_ICLK); +} + +/** + * omap_hwmod_set_clockact_none - set clockactivity test to NONE + * @oh: struct omap_hwmod * + * + * On some modules, this function can affect the wakeup latency vs. + * power consumption balance. Intended to be called by the + * omap_device layer. Passes along the return value from + * _write_clockact_lock(). + */ +int omap_hwmod_set_clockact_none(struct omap_hwmod *oh) +{ + return _write_clockact_lock(oh, CLOCKACT_TEST_NONE); +} + +/** + * omap_hwmod_enable_wakeup - allow device to wake up the system + * @oh: struct omap_hwmod * + * + * Sets the module OCP socket ENAWAKEUP bit to allow the module to + * send wakeups to the PRCM. Eventually this should sets PRCM wakeup + * registers to cause the PRCM to receive wakeup events from the + * module. Does not set any wakeup routing registers beyond this + * point - if the module is to wake up any other module or subsystem, + * that must be set separately. Called by omap_device code. Returns + * -EINVAL on error or 0 upon success. + */ +int omap_hwmod_enable_wakeup(struct omap_hwmod *oh) +{ + if (!oh->sysconfig || + !(oh->sysconfig->sysc_flags & SYSC_HAS_ENAWAKEUP)) + return -EINVAL; + + mutex_lock(&omap_hwmod_mutex); + _enable_wakeup(oh); + mutex_unlock(&omap_hwmod_mutex); + + return 0; +} + +/** + * omap_hwmod_disable_wakeup - prevent device from waking the system + * @oh: struct omap_hwmod * + * + * Clears the module OCP socket ENAWAKEUP bit to prevent the module + * from sending wakeups to the PRCM. Eventually this should clear + * PRCM wakeup registers to cause the PRCM to ignore wakeup events + * from the module. Does not set any wakeup routing registers beyond + * this point - if the module is to wake up any other module or + * subsystem, that must be set separately. Called by omap_device + * code. Returns -EINVAL on error or 0 upon success. + */ +int omap_hwmod_disable_wakeup(struct omap_hwmod *oh) +{ + if (!oh->sysconfig || + !(oh->sysconfig->sysc_flags & SYSC_HAS_ENAWAKEUP)) + return -EINVAL; + + mutex_lock(&omap_hwmod_mutex); + _disable_wakeup(oh); + mutex_unlock(&omap_hwmod_mutex); + + return 0; +} diff --git a/arch/arm/plat-omap/include/mach/omap_hwmod.h b/arch/arm/plat-omap/include/mach/omap_hwmod.h new file mode 100644 index 0000000..1f79c20 --- /dev/null +++ b/arch/arm/plat-omap/include/mach/omap_hwmod.h @@ -0,0 +1,447 @@ +/* + * omap_hwmod macros, structures + * + * Copyright (C) 2009 Nokia Corporation + * Paul Walmsley + * + * Created in collaboration with (alphabetical order): Benoit Cousson, + * Kevin Hilman, Tony Lindgren, Rajendra Nayak, Vikram Pandita, Sakari + * Poussa, Anand Sawant, Santosh Shilimkar, Richard Woodruff + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * These headers and macros are used to define OMAP on-chip module + * data and their integration with other OMAP modules and Linux. + * + * References: + * - 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) + * - OMAP4430 Multimedia Device Silicon Revision 1.0 (SWPU140) + * - Open Core Protocol Specification 2.2 + * + * To do: + * - add interconnect error log structures + * - add pinmuxing + * - init_conn_id_bit (CONNID_BIT_VECTOR) + * - implement default hwmod SMS/SDRC flags? + * + */ +#ifndef __ARCH_ARM_PLAT_OMAP_INCLUDE_MACH_OMAP_HWMOD_H +#define __ARCH_ARM_PLAT_OMAP_INCLUDE_MACH_OMAP_HWMOD_H + +#include +#include + +#include + +struct omap_device; + +/* OCP SYSCONFIG bit shifts/masks */ +#define SYSC_MIDLEMODE_SHIFT 12 +#define SYSC_MIDLEMODE_MASK (0x3 << SYSC_MIDLEMODE_SHIFT) +#define SYSC_CLOCKACTIVITY_SHIFT 8 +#define SYSC_CLOCKACTIVITY_MASK (0x3 << SYSC_CLOCKACTIVITY_SHIFT) +#define SYSC_SIDLEMODE_SHIFT 3 +#define SYSC_SIDLEMODE_MASK (0x3 << SYSC_SIDLEMODE_SHIFT) +#define SYSC_ENAWAKEUP_SHIFT 2 +#define SYSC_ENAWAKEUP_MASK (1 << SYSC_ENAWAKEUP_SHIFT) +#define SYSC_SOFTRESET_SHIFT 1 +#define SYSC_SOFTRESET_MASK (1 << SYSC_SOFTRESET_SHIFT) + +/* OCP SYSSTATUS bit shifts/masks */ +#define SYSS_RESETDONE_SHIFT 0 +#define SYSS_RESETDONE_MASK (1 << SYSS_RESETDONE_SHIFT) + +/* Master standby/slave idle mode flags */ +#define HWMOD_IDLEMODE_FORCE (1 << 0) +#define HWMOD_IDLEMODE_NO (1 << 1) +#define HWMOD_IDLEMODE_SMART (1 << 2) + + +/** + * struct omap_hwmod_dma_info - MPU address space handled by the hwmod + * @name: name of the DMA channel (module local name) + * @dma_ch: DMA channel ID + * + * @name should be something short, e.g., "tx" or "rx". It is for use + * by platform_get_resource_byname(). It is defined locally to the + * hwmod. + */ +struct omap_hwmod_dma_info { + const char *name; + u16 dma_ch; +}; + +/** + * struct omap_hwmod_opt_clk - optional clocks used by this hwmod + * @role: "sys", "32k", "tv", etc -- for use in clk_get() + * @clkdev_dev_id: opt clock: clkdev dev_id string + * @clkdev_con_id: opt clock: clkdev con_id string + * @_clk: pointer to the struct clk (filled in at runtime) + * + * The module's interface clock and main functional clock should not + * be added as optional clocks. + */ +struct omap_hwmod_opt_clk { + const char *role; + const char *clkdev_dev_id; + const char *clkdev_con_id; + struct clk *_clk; +}; + + +/* omap_hwmod_omap2_firewall.flags bits */ +#define OMAP_FIREWALL_L3 (1 << 0) +#define OMAP_FIREWALL_L4 (1 << 1) + +/** + * struct omap_hwmod_omap2_firewall - OMAP2/3 device firewall data + * @l3_perm_bit: bit shift for L3_PM_*_PERMISSION_* + * @l4_fw_region: L4 firewall region ID + * @l4_prot_group: L4 protection group ID + * @flags: (see omap_hwmod_omap2_firewall.flags macros above) + */ +struct omap_hwmod_omap2_firewall { + u8 l3_perm_bit; + u8 l4_fw_region; + u8 l4_prot_group; + u8 flags; +}; + + +/* + * omap_hwmod_addr_space.flags bits + * + * ADDR_MAP_ON_INIT: Map this address space during omap_hwmod init. + * ADDR_TYPE_RT: Address space contains module register target data. + */ +#define ADDR_MAP_ON_INIT (1 << 0) +#define ADDR_TYPE_RT (1 << 1) + +/** + * struct omap_hwmod_addr_space - MPU address space handled by the hwmod + * @pa_start: starting physical address + * @pa_end: ending physical address + * @flags: (see omap_hwmod_addr_space.flags macros above) + * + * Address space doesn't necessarily follow physical interconnect + * structure. GPMC is one example. + */ +struct omap_hwmod_addr_space { + u32 pa_start; + u32 pa_end; + u8 flags; +}; + + +/* + * omap_hwmod_ocp_if.user bits: these indicate the initiators that use this + * interface to interact with the hwmod. Used to add sleep dependencies + * when the module is enabled or disabled. + */ +#define OCP_USER_MPU (1 << 0) +#define OCP_USER_SDMA (1 << 1) + +/* omap_hwmod_ocp_if.flags bits */ +#define OCPIF_HAS_IDLEST (1 << 0) +#define OCPIF_SWSUP_IDLE (1 << 1) +#define OCPIF_CAN_BURST (1 << 2) + +/** + * struct omap_hwmod_ocp_if - OCP interface data + * @master: struct omap_hwmod that initiates OCP transactions on this link + * @slave: struct omap_hwmod that responds to OCP transactions on this link + * @addr: address space associated with this link + * @clkdev_dev_id: interface clock: clkdev dev_id string + * @clkdev_con_id: interface clock: clkdev con_id string + * @_clk: pointer to the interface struct clk (filled in at runtime) + * @fw: interface firewall data + * @addr_cnt: ARRAY_SIZE(@addr) + * @width: OCP data width + * @thread_cnt: number of threads + * @max_burst_len: maximum burst length in @width sized words (0 if unlimited) + * @user: initiators using this interface (see OCP_USER_* macros above) + * @flags: OCP interface flags (see OCPIF_* macros above) + * + * It may also be useful to add a tag_cnt field for OCP2.x devices. + * + * Parameter names beginning with an underscore are managed internally by + * the omap_hwmod code and should not be set during initialization. + */ +struct omap_hwmod_ocp_if { + struct omap_hwmod *master; + struct omap_hwmod *slave; + struct omap_hwmod_addr_space *addr; + const char *clkdev_dev_id; + const char *clkdev_con_id; + struct clk *_clk; + union { + struct omap_hwmod_omap2_firewall omap2; + } fw; + u8 addr_cnt; + u8 width; + u8 thread_cnt; + u8 max_burst_len; + u8 user; + u8 flags; +}; + + +/* Macros for use in struct omap_hwmod_sysconfig */ + +/* Flags for use in omap_hwmod_sysconfig.idlemodes */ +#define MASTER_STANDBY_SHIFT 2 +#define SLAVE_IDLE_SHIFT 0 +#define SIDLE_FORCE (HWMOD_IDLEMODE_FORCE << SLAVE_IDLE_SHIFT) +#define SIDLE_NO (HWMOD_IDLEMODE_NO << SLAVE_IDLE_SHIFT) +#define SIDLE_SMART (HWMOD_IDLEMODE_SMART << SLAVE_IDLE_SHIFT) +#define MSTANDBY_FORCE (HWMOD_IDLEMODE_FORCE << MASTER_STANDBY_SHIFT) +#define MSTANDBY_NO (HWMOD_IDLEMODE_NO << MASTER_STANDBY_SHIFT) +#define MSTANDBY_SMART (HWMOD_IDLEMODE_SMART << MASTER_STANDBY_SHIFT) + +/* omap_hwmod_sysconfig.sysc_flags capability flags */ +#define SYSC_HAS_AUTOIDLE (1 << 0) +#define SYSC_HAS_SOFTRESET (1 << 1) +#define SYSC_HAS_ENAWAKEUP (1 << 2) +#define SYSC_HAS_EMUFREE (1 << 3) +#define SYSC_HAS_CLOCKACTIVITY (1 << 4) +#define SYSC_HAS_SIDLEMODE (1 << 5) +#define SYSC_HAS_MIDLEMODE (1 << 6) +#define SYSS_MISSING (1 << 7) + +/* omap_hwmod_sysconfig.clockact flags */ +#define CLOCKACT_TEST_BOTH 0x0 +#define CLOCKACT_TEST_MAIN 0x1 +#define CLOCKACT_TEST_ICLK 0x2 +#define CLOCKACT_TEST_NONE 0x3 + +/** + * struct omap_hwmod_sysconfig - hwmod OCP_SYSCONFIG/OCP_SYSSTATUS data + * @rev_offs: IP block revision register offset (from module base addr) + * @sysc_offs: OCP_SYSCONFIG register offset (from module base addr) + * @syss_offs: OCP_SYSSTATUS register offset (from module base addr) + * @idlemodes: One or more of {SIDLE,MSTANDBY}_{OFF,FORCE,SMART} + * @sysc_flags: SYS{C,S}_HAS* flags indicating SYSCONFIG bits supported + * @clockact: the default value of the module CLOCKACTIVITY bits + * + * @clockact describes to the module which clocks are likely to be + * disabled when the PRCM issues its idle request to the module. Some + * modules have separate clockdomains for the interface clock and main + * functional clock, and can check whether they should acknowledge the + * idle request based on the internal module functionality that has + * been associated with the clocks marked in @clockact. This field is + * only used if HWMOD_SET_DEFAULT_CLOCKACT is set (see below) + * + */ +struct omap_hwmod_sysconfig { + u16 rev_offs; + u16 sysc_offs; + u16 syss_offs; + u8 idlemodes; + u8 sysc_flags; + u8 clockact; +}; + +/** + * struct omap_hwmod_omap2_prcm - OMAP2/3-specific PRCM data + * @module_offs: PRCM submodule offset from the start of the PRM/CM + * @prcm_reg_id: PRCM register ID (e.g., 3 for CM_AUTOIDLE3) + * @module_bit: register bit shift for AUTOIDLE, WKST, WKEN, GRPSEL regs + * @idlest_reg_id: IDLEST register ID (e.g., 3 for CM_IDLEST3) + * @idlest_idle_bit: register bit shift for CM_IDLEST slave idle bit + * @idlest_stdby_bit: register bit shift for CM_IDLEST master standby bit + * + * @prcm_reg_id and @module_bit are specific to the AUTOIDLE, WKST, + * WKEN, GRPSEL registers. In an ideal world, no extra information + * would be needed for IDLEST information, but alas, there are some + * exceptions, so @idlest_reg_id, @idlest_idle_bit, @idlest_stdby_bit + * are needed for the IDLEST registers (c.f. 2430 I2CHS, 3430 USBHOST) + */ +struct omap_hwmod_omap2_prcm { + s16 module_offs; + u8 prcm_reg_id; + u8 module_bit; + u8 idlest_reg_id; + u8 idlest_idle_bit; + u8 idlest_stdby_bit; +}; + + +/** + * struct omap_hwmod_omap4_prcm - OMAP4-specific PRCM data + * @module_offs: PRCM submodule offset from the start of the PRM/CM1/CM2 + * @device_offs: device register offset from @module_offs + * @submodule_wkdep_bit: bit shift of the WKDEP range + */ +struct omap_hwmod_omap4_prcm { + u32 module_offs; + u16 device_offs; + u8 submodule_wkdep_bit; +}; + + +/* + * omap_hwmod.flags definitions + * + * HWMOD_SWSUP_SIDLE: omap_hwmod code should manually bring module in and out + * of idle, rather than relying on module smart-idle + * HWMOD_SWSUP_MSTDBY: omap_hwmod code should manually bring module in and out + * of standby, rather than relying on module smart-standby + * HWMOD_INIT_NO_RESET: don't reset this module at boot - important for + * SDRAM controller, etc. + * HWMOD_INIT_NO_IDLE: don't idle this module at boot - important for SDRAM + * controller, etc. + * HWMOD_SET_DEFAULT_CLOCKACT: program CLOCKACTIVITY bits at startup + */ +#define HWMOD_SWSUP_SIDLE (1 << 0) +#define HWMOD_SWSUP_MSTANDBY (1 << 1) +#define HWMOD_INIT_NO_RESET (1 << 2) +#define HWMOD_INIT_NO_IDLE (1 << 3) +#define HWMOD_SET_DEFAULT_CLOCKACT (1 << 4) + +/* + * omap_hwmod._int_flags definitions + * These are for internal use only and are managed by the omap_hwmod code. + * + * _HWMOD_NO_MPU_PORT: no path exists for the MPU to write to this module + * _HWMOD_WAKEUP_ENABLED: set when the omap_hwmod code has enabled ENAWAKEUP + * _HWMOD_SYSCONFIG_LOADED: set when the OCP_SYSCONFIG value has been cached + */ +#define _HWMOD_NO_MPU_PORT (1 << 0) +#define _HWMOD_WAKEUP_ENABLED (1 << 1) +#define _HWMOD_SYSCONFIG_LOADED (1 << 2) + +/* + * omap_hwmod._state definitions + * + * INITIALIZED: reset (optionally), initialized, enabled, disabled + * (optionally) + * + * + */ +#define _HWMOD_STATE_UNKNOWN 0 +#define _HWMOD_STATE_REGISTERED 1 +#define _HWMOD_STATE_CLKS_INITED 2 +#define _HWMOD_STATE_INITIALIZED 3 +#define _HWMOD_STATE_ENABLED 4 +#define _HWMOD_STATE_IDLE 5 +#define _HWMOD_STATE_DISABLED 6 + +/** + * struct omap_hwmod - integration data for OMAP hardware "modules" (IP blocks) + * @name: name of the hwmod + * @od: struct omap_device currently associated with this hwmod (internal use) + * @mpu_irqs: ptr to an array of MPU IRQs (see also mpu_irqs_cnt) + * @sdma_chs: ptr to an array of SDMA channel IDs (see also sdma_chs_cnt) + * @prcm: PRCM data pertaining to this hwmod + * @clkdev_dev_id: main clock: clkdev dev_id string + * @clkdev_con_id: main clock: clkdev con_id string + * @_clk: pointer to the main struct clk (filled in at runtime) + * @opt_clks: other device clocks that drivers can request (0..*) + * @masters: ptr to array of OCP ifs that this hwmod can initiate on + * @slaves: ptr to array of OCP ifs that this hwmod can respond on + * @sysconfig: device SYSCONFIG/SYSSTATUS register data + * @dev_attr: arbitrary device attributes that can be passed to the driver + * @_sysc_cache: internal-use hwmod flags + * @_rt_va: cached register target start address (internal use) + * @_mpu_port_index: cached MPU register target slave ID (internal use) + * @msuspendmux_reg_id: CONTROL_MSUSPENDMUX register ID (1-6) + * @msuspendmux_shift: CONTROL_MSUSPENDMUX register bit shift + * @mpu_irqs_cnt: number of @mpu_irqs + * @sdma_chs_cnt: number of @sdma_chs + * @opt_clks_cnt: number of @opt_clks + * @master_cnt: number of @master entries + * @slaves_cnt: number of @slave entries + * @response_lat: device OCP response latency (in interface clock cycles) + * @_int_flags: internal-use hwmod flags + * @_state: internal-use hwmod state + * @flags: hwmod flags (documented below) + * @omap_chip: OMAP chips this hwmod is present on + * @node: list node for hwmod list (internal use) + * + * @clkdev_dev_id, @clkdev_con_id, and @clk all refer to this module's "main + * clock," which for our purposes is defined as "the functional clock needed + * for register accesses to complete." Modules may not have a main clock if + * the interface clock also serves as a main clock. + * + * Parameter names beginning with an underscore are managed internally by + * the omap_hwmod code and should not be set during initialization. + */ +struct omap_hwmod { + const char *name; + struct omap_device *od; + u8 *mpu_irqs; + struct omap_hwmod_dma_info *sdma_chs; + union { + struct omap_hwmod_omap2_prcm omap2; + struct omap_hwmod_omap4_prcm omap4; + } prcm; + const char *clkdev_dev_id; + const char *clkdev_con_id; + struct clk *_clk; + struct omap_hwmod_opt_clk *opt_clks; + struct omap_hwmod_ocp_if **masters; /* connect to *_IA */ + struct omap_hwmod_ocp_if **slaves; /* connect to *_TA */ + struct omap_hwmod_sysconfig *sysconfig; + void *dev_attr; + u32 _sysc_cache; + void __iomem *_rt_va; + struct list_head node; + u16 flags; + u8 _mpu_port_index; + u8 msuspendmux_reg_id; + u8 msuspendmux_shift; + u8 response_lat; + u8 mpu_irqs_cnt; + u8 sdma_chs_cnt; + u8 opt_clks_cnt; + u8 masters_cnt; + u8 slaves_cnt; + u8 hwmods_cnt; + u8 _int_flags; + u8 _state; + const struct omap_chip_id omap_chip; +}; + +int omap_hwmod_init(struct omap_hwmod **ohs); +int omap_hwmod_register(struct omap_hwmod *oh); +int omap_hwmod_unregister(struct omap_hwmod *oh); +struct omap_hwmod *omap_hwmod_lookup(const char *name); +int omap_hwmod_for_each(int (*fn)(struct omap_hwmod *oh)); +int omap_hwmod_late_init(void); + +int omap_hwmod_enable(struct omap_hwmod *oh); +int omap_hwmod_idle(struct omap_hwmod *oh); +int omap_hwmod_shutdown(struct omap_hwmod *oh); + +int omap_hwmod_enable_clocks(struct omap_hwmod *oh); +int omap_hwmod_disable_clocks(struct omap_hwmod *oh); + +int omap_hwmod_reset(struct omap_hwmod *oh); +void omap_hwmod_ocp_barrier(struct omap_hwmod *oh); + +void omap_hwmod_writel(u32 v, struct omap_hwmod *oh, u16 reg_offs); +u32 omap_hwmod_readl(struct omap_hwmod *oh, u16 reg_offs); + +int omap_hwmod_count_resources(struct omap_hwmod *oh); +int omap_hwmod_fill_resources(struct omap_hwmod *oh, struct resource *res); + +struct powerdomain *omap_hwmod_get_pwrdm(struct omap_hwmod *oh); + +int omap_hwmod_add_initiator_dep(struct omap_hwmod *oh, + struct omap_hwmod *init_oh); +int omap_hwmod_del_initiator_dep(struct omap_hwmod *oh, + struct omap_hwmod *init_oh); + +int omap_hwmod_set_clockact_both(struct omap_hwmod *oh); +int omap_hwmod_set_clockact_main(struct omap_hwmod *oh); +int omap_hwmod_set_clockact_iclk(struct omap_hwmod *oh); +int omap_hwmod_set_clockact_none(struct omap_hwmod *oh); + +int omap_hwmod_enable_wakeup(struct omap_hwmod *oh); +int omap_hwmod_disable_wakeup(struct omap_hwmod *oh); + +#endif -- cgit v0.10.2 From 02bfc030e4417003b7a94393102c92e39b2dfa65 Mon Sep 17 00:00:00 2001 From: Paul Walmsley Date: Thu, 3 Sep 2009 20:14:05 +0300 Subject: OMAP: omap_hwmod: call omap_hwmod init at boot; create interconnects Connect the omap_hwmod code to the kernel boot. Create some basic interconnect and device structures for OMAP2/3 chips. Signed-off-by: Paul Walmsley diff --git a/arch/arm/mach-omap2/io.c b/arch/arm/mach-omap2/io.c index 470b1d1..7574b6f 100644 --- a/arch/arm/mach-omap2/io.c +++ b/arch/arm/mach-omap2/io.c @@ -39,12 +39,16 @@ #include #include - #include "powerdomains.h" #include #include "clockdomains.h" #endif +#include +#include "omap_hwmod_2420.h" +#include "omap_hwmod_2430.h" +#include "omap_hwmod_34xx.h" + /* * The machine specific code may provide the extra mapping besides the * default mapping provided here. @@ -281,6 +285,16 @@ static int __init _omap2_init_reprogram_sdrc(void) void __init omap2_init_common_hw(struct omap_sdrc_params *sdrc_cs0, struct omap_sdrc_params *sdrc_cs1) { + struct omap_hwmod **hwmods = NULL; + + if (cpu_is_omap2420()) + hwmods = omap2420_hwmods; + else if (cpu_is_omap2430()) + hwmods = omap2430_hwmods; + else if (cpu_is_omap34xx()) + hwmods = omap34xx_hwmods; + + omap_hwmod_init(hwmods); omap2_mux_init(); #ifndef CONFIG_ARCH_OMAP4 /* FIXME: Remove this once the clkdev is ready */ /* The OPP tables have to be registered before a clk init */ @@ -289,6 +303,7 @@ void __init omap2_init_common_hw(struct omap_sdrc_params *sdrc_cs0, clkdm_init(clockdomains_omap, clkdm_pwrdm_autodeps); omap2_clk_init(); omap_serial_early_init(); + omap_hwmod_late_init(); omap_pm_if_init(); omap2_sdrc_init(sdrc_cs0, sdrc_cs1); _omap2_init_reprogram_sdrc(); diff --git a/arch/arm/mach-omap2/omap_hwmod.c b/arch/arm/mach-omap2/omap_hwmod.c index 3b10ad6..d2e0f1c 100644 --- a/arch/arm/mach-omap2/omap_hwmod.c +++ b/arch/arm/mach-omap2/omap_hwmod.c @@ -765,8 +765,7 @@ static int _reset(struct omap_hwmod *oh) WARN(1, "omap_hwmod: %s: failed to reset in %d usec\n", oh->name, MAX_MODULE_RESET_WAIT); else - pr_debug("omap_hwmod: %s: reset in %d usec\n", oh->name, - MAX_MODULE_RESET_WAIT); + pr_debug("omap_hwmod: %s: reset in %d usec\n", oh->name, c); /* * XXX add _HWMOD_STATE_WEDGED for modules that don't come back from diff --git a/arch/arm/mach-omap2/omap_hwmod_2420.h b/arch/arm/mach-omap2/omap_hwmod_2420.h new file mode 100644 index 0000000..767e496 --- /dev/null +++ b/arch/arm/mach-omap2/omap_hwmod_2420.h @@ -0,0 +1,141 @@ +/* + * omap_hwmod_2420.h - hardware modules present on the OMAP2420 chips + * + * Copyright (C) 2009 Nokia Corporation + * Paul Walmsley + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * XXX handle crossbar/shared link difference for L3? + * + */ +#ifndef __ARCH_ARM_PLAT_OMAP_INCLUDE_MACH_OMAP_HWMOD2420_H +#define __ARCH_ARM_PLAT_OMAP_INCLUDE_MACH_OMAP_HWMOD2420_H + +#ifdef CONFIG_ARCH_OMAP2420 + +#include +#include +#include +#include + +#include "prm-regbits-24xx.h" + +static struct omap_hwmod omap2420_mpu_hwmod; +static struct omap_hwmod omap2420_l3_hwmod; +static struct omap_hwmod omap2420_l4_core_hwmod; + +/* L3 -> L4_CORE interface */ +static struct omap_hwmod_ocp_if omap2420_l3__l4_core = { + .master = &omap2420_l3_hwmod, + .slave = &omap2420_l4_core_hwmod, + .user = OCP_USER_MPU | OCP_USER_SDMA, +}; + +/* MPU -> L3 interface */ +static struct omap_hwmod_ocp_if omap2420_mpu__l3 = { + .master = &omap2420_mpu_hwmod, + .slave = &omap2420_l3_hwmod, + .user = OCP_USER_MPU, +}; + +/* Slave interfaces on the L3 interconnect */ +static struct omap_hwmod_ocp_if *omap2420_l3_slaves[] = { + &omap2420_mpu__l3, +}; + +/* Master interfaces on the L3 interconnect */ +static struct omap_hwmod_ocp_if *omap2420_l3_masters[] = { + &omap2420_l3__l4_core, +}; + +/* L3 */ +static struct omap_hwmod omap2420_l3_hwmod = { + .name = "l3_hwmod", + .masters = omap2420_l3_masters, + .masters_cnt = ARRAY_SIZE(omap2420_l3_masters), + .slaves = omap2420_l3_slaves, + .slaves_cnt = ARRAY_SIZE(omap2420_l3_slaves), + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP2420) +}; + +static struct omap_hwmod omap2420_l4_wkup_hwmod; + +/* L4_CORE -> L4_WKUP interface */ +static struct omap_hwmod_ocp_if omap2420_l4_core__l4_wkup = { + .master = &omap2420_l4_core_hwmod, + .slave = &omap2420_l4_wkup_hwmod, + .user = OCP_USER_MPU | OCP_USER_SDMA, +}; + +/* Slave interfaces on the L4_CORE interconnect */ +static struct omap_hwmod_ocp_if *omap2420_l4_core_slaves[] = { + &omap2420_l3__l4_core, +}; + +/* Master interfaces on the L4_CORE interconnect */ +static struct omap_hwmod_ocp_if *omap2420_l4_core_masters[] = { + &omap2420_l4_core__l4_wkup, +}; + +/* L4 CORE */ +static struct omap_hwmod omap2420_l4_core_hwmod = { + .name = "l4_core_hwmod", + .masters = omap2420_l4_core_masters, + .masters_cnt = ARRAY_SIZE(omap2420_l4_core_masters), + .slaves = omap2420_l4_core_slaves, + .slaves_cnt = ARRAY_SIZE(omap2420_l4_core_slaves), + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP2420) +}; + +/* Slave interfaces on the L4_WKUP interconnect */ +static struct omap_hwmod_ocp_if *omap2420_l4_wkup_slaves[] = { + &omap2420_l4_core__l4_wkup, +}; + +/* Master interfaces on the L4_WKUP interconnect */ +static struct omap_hwmod_ocp_if *omap2420_l4_wkup_masters[] = { +}; + +/* L4 WKUP */ +static struct omap_hwmod omap2420_l4_wkup_hwmod = { + .name = "l4_wkup_hwmod", + .masters = omap2420_l4_wkup_masters, + .masters_cnt = ARRAY_SIZE(omap2420_l4_wkup_masters), + .slaves = omap2420_l4_wkup_slaves, + .slaves_cnt = ARRAY_SIZE(omap2420_l4_wkup_slaves), + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP2420) +}; + +/* Master interfaces on the MPU device */ +static struct omap_hwmod_ocp_if *omap2420_mpu_masters[] = { + &omap2420_mpu__l3, +}; + +/* MPU */ +static struct omap_hwmod omap2420_mpu_hwmod = { + .name = "mpu_hwmod", + .clkdev_dev_id = NULL, + .clkdev_con_id = "mpu_ck", + .masters = omap2420_mpu_masters, + .masters_cnt = ARRAY_SIZE(omap2420_mpu_masters), + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP2420), +}; + +static __initdata struct omap_hwmod *omap2420_hwmods[] = { + &omap2420_l3_hwmod, + &omap2420_l4_core_hwmod, + &omap2420_l4_wkup_hwmod, + &omap2420_mpu_hwmod, + NULL, +}; + +#else +# define omap2420_hwmods 0 +#endif + +#endif + + diff --git a/arch/arm/mach-omap2/omap_hwmod_2430.h b/arch/arm/mach-omap2/omap_hwmod_2430.h new file mode 100644 index 0000000..a412be6 --- /dev/null +++ b/arch/arm/mach-omap2/omap_hwmod_2430.h @@ -0,0 +1,143 @@ +/* + * omap_hwmod_2430.h - hardware modules present on the OMAP2430 chips + * + * Copyright (C) 2009 Nokia Corporation + * Paul Walmsley + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * XXX handle crossbar/shared link difference for L3? + * + */ +#ifndef __ARCH_ARM_PLAT_OMAP_INCLUDE_MACH_OMAP_HWMOD2430_H +#define __ARCH_ARM_PLAT_OMAP_INCLUDE_MACH_OMAP_HWMOD2430_H + +#ifdef CONFIG_ARCH_OMAP2430 + +#include +#include +#include +#include + +#include "prm-regbits-24xx.h" + +static struct omap_hwmod omap2430_mpu_hwmod; +static struct omap_hwmod omap2430_l3_hwmod; +static struct omap_hwmod omap2430_l4_core_hwmod; + +/* L3 -> L4_CORE interface */ +static struct omap_hwmod_ocp_if omap2430_l3__l4_core = { + .master = &omap2430_l3_hwmod, + .slave = &omap2430_l4_core_hwmod, + .user = OCP_USER_MPU | OCP_USER_SDMA, +}; + +/* MPU -> L3 interface */ +static struct omap_hwmod_ocp_if omap2430_mpu__l3 = { + .master = &omap2430_mpu_hwmod, + .slave = &omap2430_l3_hwmod, + .user = OCP_USER_MPU, +}; + +/* Slave interfaces on the L3 interconnect */ +static struct omap_hwmod_ocp_if *omap2430_l3_slaves[] = { + &omap2430_mpu__l3, +}; + +/* Master interfaces on the L3 interconnect */ +static struct omap_hwmod_ocp_if *omap2430_l3_masters[] = { + &omap2430_l3__l4_core, +}; + +/* L3 */ +static struct omap_hwmod omap2430_l3_hwmod = { + .name = "l3_hwmod", + .masters = omap2430_l3_masters, + .masters_cnt = ARRAY_SIZE(omap2430_l3_masters), + .slaves = omap2430_l3_slaves, + .slaves_cnt = ARRAY_SIZE(omap2430_l3_slaves), + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP2430) +}; + +static struct omap_hwmod omap2430_l4_wkup_hwmod; +static struct omap_hwmod omap2430_mmc1_hwmod; +static struct omap_hwmod omap2430_mmc2_hwmod; + +/* L4_CORE -> L4_WKUP interface */ +static struct omap_hwmod_ocp_if omap2430_l4_core__l4_wkup = { + .master = &omap2430_l4_core_hwmod, + .slave = &omap2430_l4_wkup_hwmod, + .user = OCP_USER_MPU | OCP_USER_SDMA, +}; + +/* Slave interfaces on the L4_CORE interconnect */ +static struct omap_hwmod_ocp_if *omap2430_l4_core_slaves[] = { + &omap2430_l3__l4_core, +}; + +/* Master interfaces on the L4_CORE interconnect */ +static struct omap_hwmod_ocp_if *omap2430_l4_core_masters[] = { + &omap2430_l4_core__l4_wkup, +}; + +/* L4 CORE */ +static struct omap_hwmod omap2430_l4_core_hwmod = { + .name = "l4_core_hwmod", + .masters = omap2430_l4_core_masters, + .masters_cnt = ARRAY_SIZE(omap2430_l4_core_masters), + .slaves = omap2430_l4_core_slaves, + .slaves_cnt = ARRAY_SIZE(omap2430_l4_core_slaves), + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP2430) +}; + +/* Slave interfaces on the L4_WKUP interconnect */ +static struct omap_hwmod_ocp_if *omap2430_l4_wkup_slaves[] = { + &omap2430_l4_core__l4_wkup, +}; + +/* Master interfaces on the L4_WKUP interconnect */ +static struct omap_hwmod_ocp_if *omap2430_l4_wkup_masters[] = { +}; + +/* L4 WKUP */ +static struct omap_hwmod omap2430_l4_wkup_hwmod = { + .name = "l4_wkup_hwmod", + .masters = omap2430_l4_wkup_masters, + .masters_cnt = ARRAY_SIZE(omap2430_l4_wkup_masters), + .slaves = omap2430_l4_wkup_slaves, + .slaves_cnt = ARRAY_SIZE(omap2430_l4_wkup_slaves), + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP2430) +}; + +/* Master interfaces on the MPU device */ +static struct omap_hwmod_ocp_if *omap2430_mpu_masters[] = { + &omap2430_mpu__l3, +}; + +/* MPU */ +static struct omap_hwmod omap2430_mpu_hwmod = { + .name = "mpu_hwmod", + .clkdev_dev_id = NULL, + .clkdev_con_id = "mpu_ck", + .masters = omap2430_mpu_masters, + .masters_cnt = ARRAY_SIZE(omap2430_mpu_masters), + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP2430), +}; + +static __initdata struct omap_hwmod *omap2430_hwmods[] = { + &omap2430_l3_hwmod, + &omap2430_l4_core_hwmod, + &omap2430_l4_wkup_hwmod, + &omap2430_mpu_hwmod, + NULL, +}; + +#else +# define omap2430_hwmods 0 +#endif + +#endif + + diff --git a/arch/arm/mach-omap2/omap_hwmod_34xx.h b/arch/arm/mach-omap2/omap_hwmod_34xx.h new file mode 100644 index 0000000..1e069f8 --- /dev/null +++ b/arch/arm/mach-omap2/omap_hwmod_34xx.h @@ -0,0 +1,168 @@ +/* + * omap_hwmod_34xx.h - hardware modules present on the OMAP34xx chips + * + * Copyright (C) 2009 Nokia Corporation + * Paul Walmsley + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#ifndef __ARCH_ARM_PLAT_OMAP_INCLUDE_MACH_OMAP_HWMOD34XX_H +#define __ARCH_ARM_PLAT_OMAP_INCLUDE_MACH_OMAP_HWMOD34XX_H + +#ifdef CONFIG_ARCH_OMAP34XX + +#include +#include +#include +#include + +#include "prm-regbits-34xx.h" + +static struct omap_hwmod omap34xx_mpu_hwmod; +static struct omap_hwmod omap34xx_l3_hwmod; +static struct omap_hwmod omap34xx_l4_core_hwmod; +static struct omap_hwmod omap34xx_l4_per_hwmod; + +/* L3 -> L4_CORE interface */ +static struct omap_hwmod_ocp_if omap34xx_l3__l4_core = { + .master = &omap34xx_l3_hwmod, + .slave = &omap34xx_l4_core_hwmod, + .user = OCP_USER_MPU | OCP_USER_SDMA, +}; + +/* L3 -> L4_PER interface */ +static struct omap_hwmod_ocp_if omap34xx_l3__l4_per = { + .master = &omap34xx_l3_hwmod, + .slave = &omap34xx_l4_per_hwmod, + .user = OCP_USER_MPU | OCP_USER_SDMA, +}; + +/* MPU -> L3 interface */ +static struct omap_hwmod_ocp_if omap34xx_mpu__l3 = { + .master = &omap34xx_mpu_hwmod, + .slave = &omap34xx_l3_hwmod, + .user = OCP_USER_MPU, +}; + +/* Slave interfaces on the L3 interconnect */ +static struct omap_hwmod_ocp_if *omap34xx_l3_slaves[] = { + &omap34xx_mpu__l3, +}; + +/* Master interfaces on the L3 interconnect */ +static struct omap_hwmod_ocp_if *omap34xx_l3_masters[] = { + &omap34xx_l3__l4_core, + &omap34xx_l3__l4_per, +}; + +/* L3 */ +static struct omap_hwmod omap34xx_l3_hwmod = { + .name = "l3_hwmod", + .masters = omap34xx_l3_masters, + .masters_cnt = ARRAY_SIZE(omap34xx_l3_masters), + .slaves = omap34xx_l3_slaves, + .slaves_cnt = ARRAY_SIZE(omap34xx_l3_slaves), + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) +}; + +static struct omap_hwmod omap34xx_l4_wkup_hwmod; + +/* L4_CORE -> L4_WKUP interface */ +static struct omap_hwmod_ocp_if omap34xx_l4_core__l4_wkup = { + .master = &omap34xx_l4_core_hwmod, + .slave = &omap34xx_l4_wkup_hwmod, + .user = OCP_USER_MPU | OCP_USER_SDMA, +}; + +/* Slave interfaces on the L4_CORE interconnect */ +static struct omap_hwmod_ocp_if *omap34xx_l4_core_slaves[] = { + &omap34xx_l3__l4_core, +}; + +/* Master interfaces on the L4_CORE interconnect */ +static struct omap_hwmod_ocp_if *omap34xx_l4_core_masters[] = { + &omap34xx_l4_core__l4_wkup, +}; + +/* L4 CORE */ +static struct omap_hwmod omap34xx_l4_core_hwmod = { + .name = "l4_core_hwmod", + .masters = omap34xx_l4_core_masters, + .masters_cnt = ARRAY_SIZE(omap34xx_l4_core_masters), + .slaves = omap34xx_l4_core_slaves, + .slaves_cnt = ARRAY_SIZE(omap34xx_l4_core_slaves), + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) +}; + +/* Slave interfaces on the L4_PER interconnect */ +static struct omap_hwmod_ocp_if *omap34xx_l4_per_slaves[] = { + &omap34xx_l3__l4_per, +}; + +/* Master interfaces on the L4_PER interconnect */ +static struct omap_hwmod_ocp_if *omap34xx_l4_per_masters[] = { +}; + +/* L4 PER */ +static struct omap_hwmod omap34xx_l4_per_hwmod = { + .name = "l4_per_hwmod", + .masters = omap34xx_l4_per_masters, + .masters_cnt = ARRAY_SIZE(omap34xx_l4_per_masters), + .slaves = omap34xx_l4_per_slaves, + .slaves_cnt = ARRAY_SIZE(omap34xx_l4_per_slaves), + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) +}; + +/* Slave interfaces on the L4_WKUP interconnect */ +static struct omap_hwmod_ocp_if *omap34xx_l4_wkup_slaves[] = { + &omap34xx_l4_core__l4_wkup, +}; + +/* Master interfaces on the L4_WKUP interconnect */ +static struct omap_hwmod_ocp_if *omap34xx_l4_wkup_masters[] = { +}; + +/* L4 WKUP */ +static struct omap_hwmod omap34xx_l4_wkup_hwmod = { + .name = "l4_wkup_hwmod", + .masters = omap34xx_l4_wkup_masters, + .masters_cnt = ARRAY_SIZE(omap34xx_l4_wkup_masters), + .slaves = omap34xx_l4_wkup_slaves, + .slaves_cnt = ARRAY_SIZE(omap34xx_l4_wkup_slaves), + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) +}; + +/* Master interfaces on the MPU device */ +static struct omap_hwmod_ocp_if *omap34xx_mpu_masters[] = { + &omap34xx_mpu__l3, +}; + +/* MPU */ +static struct omap_hwmod omap34xx_mpu_hwmod = { + .name = "mpu_hwmod", + .clkdev_dev_id = NULL, + .clkdev_con_id = "arm_fck", + .masters = omap34xx_mpu_masters, + .masters_cnt = ARRAY_SIZE(omap34xx_mpu_masters), + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), +}; + +static __initdata struct omap_hwmod *omap34xx_hwmods[] = { + &omap34xx_l3_hwmod, + &omap34xx_l4_core_hwmod, + &omap34xx_l4_per_hwmod, + &omap34xx_l4_wkup_hwmod, + &omap34xx_mpu_hwmod, + NULL, +}; + +#else +# define omap34xx_hwmods 0 +#endif + +#endif + + -- cgit v0.10.2 From b04b65ab5b4fc86af073424af6295be4e067adf3 Mon Sep 17 00:00:00 2001 From: Paul Walmsley Date: Thu, 3 Sep 2009 20:14:05 +0300 Subject: OMAP2/3/4 core: create omap_device layer The omap_device code provides a mapping of omap_hwmod structures into the platform_device system, and includes some details on external (board-level) integration. This allows drivers to enable, idle, and shutdown on-chip device resources, including clocks, regulators, etc. The resources enabled and idled are dependent on the device's maximum wakeup latency constraint (if present). At the moment, omap_device functions are intended to be called from platform_data function pointers. Ideally in the future these functions will be called from either subarchitecture-specific platform_data activate, deactivate functions, or via an custom bus/device type for OMAP. Signed-off-by: Paul Walmsley Cc: Benoit Cousson Cc: Kevin Hilman Cc: Tony Lindgren Cc: Rajendra Nayak Cc: Vikram Pandita Cc: Sakari Poussa Cc: Anand Sawant Cc: Santosh Shilimkar Cc: Eric Thomas Cc: Richard Woodruff diff --git a/arch/arm/plat-omap/Makefile b/arch/arm/plat-omap/Makefile index 5e10d0a..98f0191 100644 --- a/arch/arm/plat-omap/Makefile +++ b/arch/arm/plat-omap/Makefile @@ -12,6 +12,10 @@ obj- := # OCPI interconnect support for 1710, 1610 and 5912 obj-$(CONFIG_ARCH_OMAP16XX) += ocpi.o +# omap_device support (OMAP2+ only at the moment) +obj-$(CONFIG_ARCH_OMAP2) += omap_device.o +obj-$(CONFIG_ARCH_OMAP3) += omap_device.o + obj-$(CONFIG_OMAP_MCBSP) += mcbsp.o obj-$(CONFIG_OMAP_IOMMU) += iommu.o iovmm.o obj-$(CONFIG_OMAP_IOMMU_DEBUG) += iommu-debug.o diff --git a/arch/arm/plat-omap/include/mach/omap_device.h b/arch/arm/plat-omap/include/mach/omap_device.h new file mode 100644 index 0000000..bd0e136 --- /dev/null +++ b/arch/arm/plat-omap/include/mach/omap_device.h @@ -0,0 +1,141 @@ +/* + * omap_device headers + * + * Copyright (C) 2009 Nokia Corporation + * Paul Walmsley + * + * Developed in collaboration with (alphabetical order): Benoit + * Cousson, Kevin Hilman, Tony Lindgren, Rajendra Nayak, Vikram + * Pandita, Sakari Poussa, Anand Sawant, Santosh Shilimkar, Richard + * Woodruff + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Eventually this type of functionality should either be + * a) implemented via arch-specific pointers in platform_device + * or + * b) implemented as a proper omap_bus/omap_device in Linux, no more + * platform_device + * + * omap_device differs from omap_hwmod in that it includes external + * (e.g., board- and system-level) integration details. omap_hwmod + * stores hardware data that is invariant for a given OMAP chip. + * + * To do: + * - GPIO integration + * - regulator integration + * + */ +#ifndef __ARCH_ARM_PLAT_OMAP_INCLUDE_MACH_OMAP_DEVICE_H +#define __ARCH_ARM_PLAT_OMAP_INCLUDE_MACH_OMAP_DEVICE_H + +#include +#include + +#include + +/* omap_device._state values */ +#define OMAP_DEVICE_STATE_UNKNOWN 0 +#define OMAP_DEVICE_STATE_ENABLED 1 +#define OMAP_DEVICE_STATE_IDLE 2 +#define OMAP_DEVICE_STATE_SHUTDOWN 3 + +/** + * struct omap_device - omap_device wrapper for platform_devices + * @pdev: platform_device + * @hwmods: (one .. many per omap_device) + * @hwmods_cnt: ARRAY_SIZE() of @hwmods + * @pm_lats: ptr to an omap_device_pm_latency table + * @pm_lats_cnt: ARRAY_SIZE() of what is passed to @pm_lats + * @pm_lat_level: array index of the last odpl entry executed - -1 if never + * @dev_wakeup_lat: dev wakeup latency in microseconds + * @_dev_wakeup_lat_limit: dev wakeup latency limit in usec - set by OMAP PM + * @_state: one of OMAP_DEVICE_STATE_* (see above) + * @flags: device flags + * + * Integrates omap_hwmod data into Linux platform_device. + * + * Field names beginning with underscores are for the internal use of + * the omap_device code. + * + */ +struct omap_device { + struct platform_device pdev; + struct omap_hwmod **hwmods; + struct omap_device_pm_latency *pm_lats; + u32 dev_wakeup_lat; + u32 _dev_wakeup_lat_limit; + u8 pm_lats_cnt; + s8 pm_lat_level; + u8 hwmods_cnt; + u8 _state; +}; + +/* Device driver interface (call via platform_data fn ptrs) */ + +int omap_device_enable(struct platform_device *pdev); +int omap_device_idle(struct platform_device *pdev); +int omap_device_shutdown(struct platform_device *pdev); + +/* Core code interface */ + +int omap_device_count_resources(struct omap_device *od); +int omap_device_fill_resources(struct omap_device *od, struct resource *res); + +struct omap_device *omap_device_build(const char *pdev_name, int pdev_id, + struct omap_hwmod *oh, void *pdata, + int pdata_len, + struct omap_device_pm_latency *pm_lats, + int pm_lats_cnt); + +struct omap_device *omap_device_build_ss(const char *pdev_name, int pdev_id, + struct omap_hwmod **oh, int oh_cnt, + void *pdata, int pdata_len, + struct omap_device_pm_latency *pm_lats, + int pm_lats_cnt); + +int omap_device_register(struct omap_device *od); + +/* OMAP PM interface */ +int omap_device_align_pm_lat(struct platform_device *pdev, + u32 new_wakeup_lat_limit); +struct powerdomain *omap_device_get_pwrdm(struct omap_device *od); + +/* Other */ + +int omap_device_idle_hwmods(struct omap_device *od); +int omap_device_enable_hwmods(struct omap_device *od); + +int omap_device_disable_clocks(struct omap_device *od); +int omap_device_enable_clocks(struct omap_device *od); + + +/* + * Entries should be kept in latency order ascending + * + * deact_lat is the maximum number of microseconds required to complete + * deactivate_func() at the device's slowest OPP. + * + * act_lat is the maximum number of microseconds required to complete + * activate_func() at the device's slowest OPP. + * + * This will result in some suboptimal power management decisions at fast + * OPPs, but avoids having to recompute all device power management decisions + * if the system shifts from a fast OPP to a slow OPP (in order to meet + * latency requirements). + * + * XXX should deactivate_func/activate_func() take platform_device pointers + * rather than omap_device pointers? + */ +struct omap_device_pm_latency { + u32 deactivate_lat; + int (*deactivate_func)(struct omap_device *od); + u32 activate_lat; + int (*activate_func)(struct omap_device *od); +}; + + +#endif + diff --git a/arch/arm/plat-omap/omap_device.c b/arch/arm/plat-omap/omap_device.c new file mode 100644 index 0000000..2c409fc --- /dev/null +++ b/arch/arm/plat-omap/omap_device.c @@ -0,0 +1,687 @@ +/* + * omap_device implementation + * + * Copyright (C) 2009 Nokia Corporation + * Paul Walmsley + * + * Developed in collaboration with (alphabetical order): Benoit + * Cousson, Kevin Hilman, Tony Lindgren, Rajendra Nayak, Vikram + * Pandita, Sakari Poussa, Anand Sawant, Santosh Shilimkar, Richard + * Woodruff + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This code provides a consistent interface for OMAP device drivers + * to control power management and interconnect properties of their + * devices. + * + * In the medium- to long-term, this code should either be + * a) implemented via arch-specific pointers in platform_data + * or + * b) implemented as a proper omap_bus/omap_device in Linux, no more + * platform_data func pointers + * + * + * Guidelines for usage by driver authors: + * + * 1. These functions are intended to be used by device drivers via + * function pointers in struct platform_data. As an example, + * omap_device_enable() should be passed to the driver as + * + * struct foo_driver_platform_data { + * ... + * int (*device_enable)(struct platform_device *pdev); + * ... + * } + * + * Note that the generic "device_enable" name is used, rather than + * "omap_device_enable". This is so other architectures can pass in their + * own enable/disable functions here. + * + * This should be populated during device setup: + * + * ... + * pdata->device_enable = omap_device_enable; + * ... + * + * 2. Drivers should first check to ensure the function pointer is not null + * before calling it, as in: + * + * if (pdata->device_enable) + * pdata->device_enable(pdev); + * + * This allows other architectures that don't use similar device_enable()/ + * device_shutdown() functions to execute normally. + * + * ... + * + * Suggested usage by device drivers: + * + * During device initialization: + * device_enable() + * + * During device idle: + * (save remaining device context if necessary) + * device_idle(); + * + * During device resume: + * device_enable(); + * (restore context if necessary) + * + * During device shutdown: + * device_shutdown() + * (device must be reinitialized at this point to use it again) + * + */ +#undef DEBUG + +#include +#include +#include +#include + +#include +#include + +/* These parameters are passed to _omap_device_{de,}activate() */ +#define USE_WAKEUP_LAT 0 +#define IGNORE_WAKEUP_LAT 1 + +/* XXX this should be moved into a separate file */ +#if defined(CONFIG_ARCH_OMAP2420) +# define OMAP_32KSYNCT_BASE 0x48004000 +#elif defined(CONFIG_ARCH_OMAP2430) +# define OMAP_32KSYNCT_BASE 0x49020000 +#elif defined(CONFIG_ARCH_OMAP3430) +# define OMAP_32KSYNCT_BASE 0x48320000 +#else +# error Unknown OMAP device +#endif + +/* Private functions */ + +/** + * _read_32ksynct - read the OMAP 32K sync timer + * + * Returns the current value of the 32KiHz synchronization counter. + * XXX this should be generalized to simply read the system clocksource. + * XXX this should be moved to a separate synctimer32k.c file + */ +static u32 _read_32ksynct(void) +{ + if (!cpu_class_is_omap2()) + BUG(); + + return __raw_readl(OMAP2_IO_ADDRESS(OMAP_32KSYNCT_BASE + 0x010)); +} + +/** + * _omap_device_activate - increase device readiness + * @od: struct omap_device * + * @ignore_lat: increase to latency target (0) or full readiness (1)? + * + * Increase readiness of omap_device @od (thus decreasing device + * wakeup latency, but consuming more power). If @ignore_lat is + * IGNORE_WAKEUP_LAT, make the omap_device fully active. Otherwise, + * if @ignore_lat is USE_WAKEUP_LAT, and the device's maximum wakeup + * latency is greater than the requested maximum wakeup latency, step + * backwards in the omap_device_pm_latency table to ensure the + * device's maximum wakeup latency is less than or equal to the + * requested maximum wakeup latency. Returns 0. + */ +static int _omap_device_activate(struct omap_device *od, u8 ignore_lat) +{ + u32 a, b; + + pr_debug("omap_device: %s: activating\n", od->pdev.name); + + while (od->pm_lat_level > 0) { + struct omap_device_pm_latency *odpl; + int act_lat = 0; + + od->pm_lat_level--; + + odpl = od->pm_lats + od->pm_lat_level; + + if (!ignore_lat && + (od->dev_wakeup_lat <= od->_dev_wakeup_lat_limit)) + break; + + a = _read_32ksynct(); + + /* XXX check return code */ + odpl->activate_func(od); + + b = _read_32ksynct(); + + act_lat = (b - a) >> 15; /* 32KiHz cycles to microseconds */ + + pr_debug("omap_device: %s: pm_lat %d: activate: elapsed time " + "%d usec\n", od->pdev.name, od->pm_lat_level, act_lat); + + WARN(act_lat > odpl->activate_lat, "omap_device: %s.%d: " + "activate step %d took longer than expected (%d > %d)\n", + od->pdev.name, od->pdev.id, od->pm_lat_level, + act_lat, odpl->activate_lat); + + od->dev_wakeup_lat -= odpl->activate_lat; + } + + return 0; +} + +/** + * _omap_device_deactivate - decrease device readiness + * @od: struct omap_device * + * @ignore_lat: decrease to latency target (0) or full inactivity (1)? + * + * Decrease readiness of omap_device @od (thus increasing device + * wakeup latency, but conserving power). If @ignore_lat is + * IGNORE_WAKEUP_LAT, make the omap_device fully inactive. Otherwise, + * if @ignore_lat is USE_WAKEUP_LAT, and the device's maximum wakeup + * latency is less than the requested maximum wakeup latency, step + * forwards in the omap_device_pm_latency table to ensure the device's + * maximum wakeup latency is less than or equal to the requested + * maximum wakeup latency. Returns 0. + */ +static int _omap_device_deactivate(struct omap_device *od, u8 ignore_lat) +{ + u32 a, b; + + pr_debug("omap_device: %s: deactivating\n", od->pdev.name); + + while (od->pm_lat_level < od->pm_lats_cnt) { + struct omap_device_pm_latency *odpl; + int deact_lat = 0; + + odpl = od->pm_lats + od->pm_lat_level; + + if (!ignore_lat && + ((od->dev_wakeup_lat + odpl->activate_lat) > + od->_dev_wakeup_lat_limit)) + break; + + a = _read_32ksynct(); + + /* XXX check return code */ + odpl->deactivate_func(od); + + b = _read_32ksynct(); + + deact_lat = (b - a) >> 15; /* 32KiHz cycles to microseconds */ + + pr_debug("omap_device: %s: pm_lat %d: deactivate: elapsed time " + "%d usec\n", od->pdev.name, od->pm_lat_level, + deact_lat); + + WARN(deact_lat > odpl->deactivate_lat, "omap_device: %s.%d: " + "deactivate step %d took longer than expected (%d > %d)\n", + od->pdev.name, od->pdev.id, od->pm_lat_level, + deact_lat, odpl->deactivate_lat); + + od->dev_wakeup_lat += odpl->activate_lat; + + od->pm_lat_level++; + } + + return 0; +} + +static inline struct omap_device *_find_by_pdev(struct platform_device *pdev) +{ + return container_of(pdev, struct omap_device, pdev); +} + + +/* Public functions for use by core code */ + +/** + * omap_device_count_resources - count number of struct resource entries needed + * @od: struct omap_device * + * + * Count the number of struct resource entries needed for this + * omap_device @od. Used by omap_device_build_ss() to determine how + * much memory to allocate before calling + * omap_device_fill_resources(). Returns the count. + */ +int omap_device_count_resources(struct omap_device *od) +{ + struct omap_hwmod *oh; + int c = 0; + int i; + + for (i = 0, oh = *od->hwmods; i < od->hwmods_cnt; i++, oh++) + c += omap_hwmod_count_resources(oh); + + pr_debug("omap_device: %s: counted %d total resources across %d " + "hwmods\n", od->pdev.name, c, od->hwmods_cnt); + + return c; +} + +/** + * omap_device_fill_resources - fill in array of struct resource + * @od: struct omap_device * + * @res: pointer to an array of struct resource to be filled in + * + * Populate one or more empty struct resource pointed to by @res with + * the resource data for this omap_device @od. Used by + * omap_device_build_ss() after calling omap_device_count_resources(). + * Ideally this function would not be needed at all. If omap_device + * replaces platform_device, then we can specify our own + * get_resource()/ get_irq()/etc functions that use the underlying + * omap_hwmod information. Or if platform_device is extended to use + * subarchitecture-specific function pointers, the various + * platform_device functions can simply call omap_device internal + * functions to get device resources. Hacking around the existing + * platform_device code wastes memory. Returns 0. + */ +int omap_device_fill_resources(struct omap_device *od, struct resource *res) +{ + struct omap_hwmod *oh; + int c = 0; + int i, r; + + for (i = 0, oh = *od->hwmods; i < od->hwmods_cnt; i++, oh++) { + r = omap_hwmod_fill_resources(oh, res); + res += r; + c += r; + } + + return 0; +} + +/** + * omap_device_build - build and register an omap_device with one omap_hwmod + * @pdev_name: name of the platform_device driver to use + * @pdev_id: this platform_device's connection ID + * @oh: ptr to the single omap_hwmod that backs this omap_device + * @pdata: platform_data ptr to associate with the platform_device + * @pdata_len: amount of memory pointed to by @pdata + * @pm_lats: pointer to a omap_device_pm_latency array for this device + * @pm_lats_cnt: ARRAY_SIZE() of @pm_lats + * + * Convenience function for building and registering a single + * omap_device record, which in turn builds and registers a + * platform_device record. See omap_device_build_ss() for more + * information. Returns ERR_PTR(-EINVAL) if @oh is NULL; otherwise, + * passes along the return value of omap_device_build_ss(). + */ +struct omap_device *omap_device_build(const char *pdev_name, int pdev_id, + struct omap_hwmod *oh, void *pdata, + int pdata_len, + struct omap_device_pm_latency *pm_lats, + int pm_lats_cnt) +{ + struct omap_hwmod *ohs[] = { oh }; + + if (!oh) + return ERR_PTR(-EINVAL); + + return omap_device_build_ss(pdev_name, pdev_id, ohs, 1, pdata, + pdata_len, pm_lats, pm_lats_cnt); +} + +/** + * omap_device_build_ss - build and register an omap_device with multiple hwmods + * @pdev_name: name of the platform_device driver to use + * @pdev_id: this platform_device's connection ID + * @oh: ptr to the single omap_hwmod that backs this omap_device + * @pdata: platform_data ptr to associate with the platform_device + * @pdata_len: amount of memory pointed to by @pdata + * @pm_lats: pointer to a omap_device_pm_latency array for this device + * @pm_lats_cnt: ARRAY_SIZE() of @pm_lats + * + * Convenience function for building and registering an omap_device + * subsystem record. Subsystem records consist of multiple + * omap_hwmods. This function in turn builds and registers a + * platform_device record. Returns an ERR_PTR() on error, or passes + * along the return value of omap_device_register(). + */ +struct omap_device *omap_device_build_ss(const char *pdev_name, int pdev_id, + struct omap_hwmod **ohs, int oh_cnt, + void *pdata, int pdata_len, + struct omap_device_pm_latency *pm_lats, + int pm_lats_cnt) +{ + int ret = -ENOMEM; + struct omap_device *od; + char *pdev_name2; + struct resource *res = NULL; + int res_count; + struct omap_hwmod **hwmods; + + if (!ohs || oh_cnt == 0 || !pdev_name) + return ERR_PTR(-EINVAL); + + if (!pdata && pdata_len > 0) + return ERR_PTR(-EINVAL); + + pr_debug("omap_device: %s: building with %d hwmods\n", pdev_name, + oh_cnt); + + od = kzalloc(sizeof(struct omap_device), GFP_KERNEL); + if (!od) + return ERR_PTR(-ENOMEM); + + od->hwmods_cnt = oh_cnt; + + hwmods = kzalloc(sizeof(struct omap_hwmod *) * oh_cnt, + GFP_KERNEL); + if (!hwmods) + goto odbs_exit1; + + memcpy(hwmods, ohs, sizeof(struct omap_hwmod *) * oh_cnt); + od->hwmods = hwmods; + + pdev_name2 = kzalloc(strlen(pdev_name) + 1, GFP_KERNEL); + if (!pdev_name2) + goto odbs_exit2; + strcpy(pdev_name2, pdev_name); + + od->pdev.name = pdev_name2; + od->pdev.id = pdev_id; + + res_count = omap_device_count_resources(od); + if (res_count > 0) { + res = kzalloc(sizeof(struct resource) * res_count, GFP_KERNEL); + if (!res) + goto odbs_exit3; + } + omap_device_fill_resources(od, res); + + od->pdev.num_resources = res_count; + od->pdev.resource = res; + + platform_device_add_data(&od->pdev, pdata, pdata_len); + + od->pm_lats = pm_lats; + od->pm_lats_cnt = pm_lats_cnt; + + ret = omap_device_register(od); + if (ret) + goto odbs_exit4; + + return od; + +odbs_exit4: + kfree(res); +odbs_exit3: + kfree(pdev_name2); +odbs_exit2: + kfree(hwmods); +odbs_exit1: + kfree(od); + + pr_err("omap_device: %s: build failed (%d)\n", pdev_name, ret); + + return ERR_PTR(ret); +} + +/** + * omap_device_register - register an omap_device with one omap_hwmod + * @od: struct omap_device * to register + * + * Register the omap_device structure. This currently just calls + * platform_device_register() on the underlying platform_device. + * Returns the return value of platform_device_register(). + */ +int omap_device_register(struct omap_device *od) +{ + pr_debug("omap_device: %s: registering\n", od->pdev.name); + + return platform_device_register(&od->pdev); +} + + +/* Public functions for use by device drivers through struct platform_data */ + +/** + * omap_device_enable - fully activate an omap_device + * @od: struct omap_device * to activate + * + * Do whatever is necessary for the hwmods underlying omap_device @od + * to be accessible and ready to operate. This generally involves + * enabling clocks, setting SYSCONFIG registers; and in the future may + * involve remuxing pins. Device drivers should call this function + * (through platform_data function pointers) where they would normally + * enable clocks, etc. Returns -EINVAL if called when the omap_device + * is already enabled, or passes along the return value of + * _omap_device_activate(). + */ +int omap_device_enable(struct platform_device *pdev) +{ + int ret; + struct omap_device *od; + + od = _find_by_pdev(pdev); + + if (od->_state == OMAP_DEVICE_STATE_ENABLED) { + WARN(1, "omap_device: %s.%d: omap_device_enable() called from " + "invalid state\n", od->pdev.name, od->pdev.id); + return -EINVAL; + } + + /* Enable everything if we're enabling this device from scratch */ + if (od->_state == OMAP_DEVICE_STATE_UNKNOWN) + od->pm_lat_level = od->pm_lats_cnt; + + ret = _omap_device_activate(od, IGNORE_WAKEUP_LAT); + + od->dev_wakeup_lat = 0; + od->_dev_wakeup_lat_limit = INT_MAX; + od->_state = OMAP_DEVICE_STATE_ENABLED; + + return ret; +} + +/** + * omap_device_idle - idle an omap_device + * @od: struct omap_device * to idle + * + * Idle omap_device @od by calling as many .deactivate_func() entries + * in the omap_device's pm_lats table as is possible without exceeding + * the device's maximum wakeup latency limit, pm_lat_limit. Device + * drivers should call this function (through platform_data function + * pointers) where they would normally disable clocks after operations + * complete, etc.. Returns -EINVAL if the omap_device is not + * currently enabled, or passes along the return value of + * _omap_device_deactivate(). + */ +int omap_device_idle(struct platform_device *pdev) +{ + int ret; + struct omap_device *od; + + od = _find_by_pdev(pdev); + + if (od->_state != OMAP_DEVICE_STATE_ENABLED) { + WARN(1, "omap_device: %s.%d: omap_device_idle() called from " + "invalid state\n", od->pdev.name, od->pdev.id); + return -EINVAL; + } + + ret = _omap_device_deactivate(od, USE_WAKEUP_LAT); + + od->_state = OMAP_DEVICE_STATE_IDLE; + + return ret; +} + +/** + * omap_device_shutdown - shut down an omap_device + * @od: struct omap_device * to shut down + * + * Shut down omap_device @od by calling all .deactivate_func() entries + * in the omap_device's pm_lats table and then shutting down all of + * the underlying omap_hwmods. Used when a device is being "removed" + * or a device driver is being unloaded. Returns -EINVAL if the + * omap_device is not currently enabled or idle, or passes along the + * return value of _omap_device_deactivate(). + */ +int omap_device_shutdown(struct platform_device *pdev) +{ + int ret, i; + struct omap_device *od; + struct omap_hwmod *oh; + + od = _find_by_pdev(pdev); + + if (od->_state != OMAP_DEVICE_STATE_ENABLED && + od->_state != OMAP_DEVICE_STATE_IDLE) { + WARN(1, "omap_device: %s.%d: omap_device_shutdown() called " + "from invalid state\n", od->pdev.name, od->pdev.id); + return -EINVAL; + } + + ret = _omap_device_deactivate(od, IGNORE_WAKEUP_LAT); + + for (i = 0, oh = *od->hwmods; i < od->hwmods_cnt; i++, oh++) + omap_hwmod_shutdown(oh); + + od->_state = OMAP_DEVICE_STATE_SHUTDOWN; + + return ret; +} + +/** + * omap_device_align_pm_lat - activate/deactivate device to match wakeup lat lim + * @od: struct omap_device * + * + * When a device's maximum wakeup latency limit changes, call some of + * the .activate_func or .deactivate_func function pointers in the + * omap_device's pm_lats array to ensure that the device's maximum + * wakeup latency is less than or equal to the new latency limit. + * Intended to be called by OMAP PM code whenever a device's maximum + * wakeup latency limit changes (e.g., via + * omap_pm_set_dev_wakeup_lat()). Returns 0 if nothing needs to be + * done (e.g., if the omap_device is not currently idle, or if the + * wakeup latency is already current with the new limit) or passes + * along the return value of _omap_device_deactivate() or + * _omap_device_activate(). + */ +int omap_device_align_pm_lat(struct platform_device *pdev, + u32 new_wakeup_lat_limit) +{ + int ret = -EINVAL; + struct omap_device *od; + + od = _find_by_pdev(pdev); + + if (new_wakeup_lat_limit == od->dev_wakeup_lat) + return 0; + + od->_dev_wakeup_lat_limit = new_wakeup_lat_limit; + + if (od->_state != OMAP_DEVICE_STATE_IDLE) + return 0; + else if (new_wakeup_lat_limit > od->dev_wakeup_lat) + ret = _omap_device_deactivate(od, USE_WAKEUP_LAT); + else if (new_wakeup_lat_limit < od->dev_wakeup_lat) + ret = _omap_device_activate(od, USE_WAKEUP_LAT); + + return ret; +} + +/** + * omap_device_get_pwrdm - return the powerdomain * associated with @od + * @od: struct omap_device * + * + * Return the powerdomain associated with the first underlying + * omap_hwmod for this omap_device. Intended for use by core OMAP PM + * code. Returns NULL on error or a struct powerdomain * upon + * success. + */ +struct powerdomain *omap_device_get_pwrdm(struct omap_device *od) +{ + /* + * XXX Assumes that all omap_hwmod powerdomains are identical. + * This may not necessarily be true. There should be a sanity + * check in here to WARN() if any difference appears. + */ + if (!od->hwmods_cnt) + return NULL; + + return omap_hwmod_get_pwrdm(od->hwmods[0]); +} + +/* + * Public functions intended for use in omap_device_pm_latency + * .activate_func and .deactivate_func function pointers + */ + +/** + * omap_device_enable_hwmods - call omap_hwmod_enable() on all hwmods + * @od: struct omap_device *od + * + * Enable all underlying hwmods. Returns 0. + */ +int omap_device_enable_hwmods(struct omap_device *od) +{ + struct omap_hwmod *oh; + int i; + + for (i = 0, oh = *od->hwmods; i < od->hwmods_cnt; i++, oh++) + omap_hwmod_enable(oh); + + /* XXX pass along return value here? */ + return 0; +} + +/** + * omap_device_idle_hwmods - call omap_hwmod_idle() on all hwmods + * @od: struct omap_device *od + * + * Idle all underlying hwmods. Returns 0. + */ +int omap_device_idle_hwmods(struct omap_device *od) +{ + struct omap_hwmod *oh; + int i; + + for (i = 0, oh = *od->hwmods; i < od->hwmods_cnt; i++, oh++) + omap_hwmod_idle(oh); + + /* XXX pass along return value here? */ + return 0; +} + +/** + * omap_device_disable_clocks - disable all main and interface clocks + * @od: struct omap_device *od + * + * Disable the main functional clock and interface clock for all of the + * omap_hwmods associated with the omap_device. Returns 0. + */ +int omap_device_disable_clocks(struct omap_device *od) +{ + struct omap_hwmod *oh; + int i; + + for (i = 0, oh = *od->hwmods; i < od->hwmods_cnt; i++, oh++) + omap_hwmod_disable_clocks(oh); + + /* XXX pass along return value here? */ + return 0; +} + +/** + * omap_device_enable_clocks - enable all main and interface clocks + * @od: struct omap_device *od + * + * Enable the main functional clock and interface clock for all of the + * omap_hwmods associated with the omap_device. Returns 0. + */ +int omap_device_enable_clocks(struct omap_device *od) +{ + struct omap_hwmod *oh; + int i; + + for (i = 0, oh = *od->hwmods; i < od->hwmods_cnt; i++, oh++) + omap_hwmod_enable_clocks(oh); + + /* XXX pass along return value here? */ + return 0; +} -- cgit v0.10.2 From ca4caa4e1d45f9542fa54263974d0ef637157b4a Mon Sep 17 00:00:00 2001 From: Hiroshi DOYU Date: Thu, 3 Sep 2009 20:14:06 +0300 Subject: OMAP clock: use debugfs_remove_recursive() for rewinding Rewinding each debugfs entries to unregister if an error happens. Signed-off-by: Hiroshi DOYU Signed-off-by: Paul Walmsley diff --git a/arch/arm/plat-omap/clock.c b/arch/arm/plat-omap/clock.c index e8c327a..bf880e9 100644 --- a/arch/arm/plat-omap/clock.c +++ b/arch/arm/plat-omap/clock.c @@ -488,7 +488,7 @@ static int __init clk_debugfs_init(void) } return 0; err_out: - debugfs_remove(clk_debugfs_root); /* REVISIT: Cleanup correctly */ + debugfs_remove_recursive(clk_debugfs_root); return err; } late_initcall(clk_debugfs_init); -- cgit v0.10.2