diff options
Diffstat (limited to 'arch/arm/mach-omap2/clock.c')
-rw-r--r-- | arch/arm/mach-omap2/clock.c | 470 |
1 files changed, 298 insertions, 172 deletions
diff --git a/arch/arm/mach-omap2/clock.c b/arch/arm/mach-omap2/clock.c index 961ac8f7e13d..e4ec3a69ee2e 100644 --- a/arch/arm/mach-omap2/clock.c +++ b/arch/arm/mach-omap2/clock.c @@ -15,27 +15,35 @@ #undef DEBUG #include <linux/kernel.h> +#include <linux/export.h> #include <linux/list.h> #include <linux/errno.h> #include <linux/err.h> #include <linux/delay.h> -#include <linux/clk.h> +#include <linux/clk-provider.h> #include <linux/io.h> #include <linux/bitops.h> #include <asm/cpu.h> -#include <plat/clock.h> -#include <plat/prcm.h> #include <trace/events/power.h> #include "soc.h" #include "clockdomain.h" #include "clock.h" -#include "cm2xxx_3xxx.h" +#include "cm.h" +#include "cm2xxx.h" +#include "cm3xxx.h" #include "cm-regbits-24xx.h" #include "cm-regbits-34xx.h" +#include "common.h" + +/* + * MAX_MODULE_ENABLE_WAIT: maximum of number of microseconds to wait + * for a module to indicate that it is no longer in idle + */ +#define MAX_MODULE_ENABLE_WAIT 100000 u16 cpu_mask; @@ -47,12 +55,69 @@ u16 cpu_mask; */ static bool clkdm_control = true; +static LIST_HEAD(clk_hw_omap_clocks); + +/* + * Used for clocks that have the same value as the parent clock, + * divided by some factor + */ +unsigned long omap_fixed_divisor_recalc(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_hw_omap *oclk; + + if (!hw) { + pr_warn("%s: hw is NULL\n", __func__); + return -EINVAL; + } + + oclk = to_clk_hw_omap(hw); + + WARN_ON(!oclk->fixed_div); + + return parent_rate / oclk->fixed_div; +} + /* * OMAP2+ specific clock functions */ /* Private functions */ + +/** + * _wait_idlest_generic - wait for a module to leave the idle state + * @reg: virtual address of module IDLEST register + * @mask: value to mask against to determine if the module is active + * @idlest: idle state indicator (0 or 1) for the clock + * @name: name of the clock (for printk) + * + * Wait for a module to leave idle, where its idle-status register is + * not inside the CM module. Returns 1 if the module left idle + * promptly, or 0 if the module did not leave idle before the timeout + * elapsed. XXX Deprecated - should be moved into drivers for the + * individual IP block that the IDLEST register exists in. + */ +static int _wait_idlest_generic(void __iomem *reg, u32 mask, u8 idlest, + const char *name) +{ + int i = 0, ena = 0; + + ena = (idlest) ? 0 : mask; + + omap_test_timeout(((__raw_readl(reg) & mask) == ena), + MAX_MODULE_ENABLE_WAIT, i); + + if (i < MAX_MODULE_ENABLE_WAIT) + pr_debug("omap clock: module associated with clock %s ready after %d loops\n", + name, i); + else + pr_err("omap clock: module associated with clock %s didn't enable in %d tries\n", + name, MAX_MODULE_ENABLE_WAIT); + + return (i < MAX_MODULE_ENABLE_WAIT) ? 1 : 0; +}; + /** * _omap2_module_wait_ready - wait for an OMAP module to leave IDLE * @clk: struct clk * belonging to the module @@ -63,10 +128,12 @@ static bool clkdm_control = true; * belong in the clock code and will be moved in the medium term to * module-dependent code. No return value. */ -static void _omap2_module_wait_ready(struct clk *clk) +static void _omap2_module_wait_ready(struct clk_hw_omap *clk) { void __iomem *companion_reg, *idlest_reg; - u8 other_bit, idlest_bit, idlest_val; + u8 other_bit, idlest_bit, idlest_val, idlest_reg_id; + s16 prcm_mod; + int r; /* Not all modules have multiple clocks that their IDLEST depends on */ if (clk->ops->find_companion) { @@ -76,9 +143,14 @@ static void _omap2_module_wait_ready(struct clk *clk) } clk->ops->find_idlest(clk, &idlest_reg, &idlest_bit, &idlest_val); - - omap2_cm_wait_idlest(idlest_reg, (1 << idlest_bit), idlest_val, - __clk_get_name(clk)); + r = cm_split_idlest_reg(idlest_reg, &prcm_mod, &idlest_reg_id); + if (r) { + /* IDLEST register not in the CM module */ + _wait_idlest_generic(idlest_reg, (1 << idlest_bit), idlest_val, + __clk_get_name(clk->hw.clk)); + } else { + cm_wait_module_ready(prcm_mod, idlest_reg_id, idlest_bit); + }; } /* Public functions */ @@ -91,15 +163,16 @@ static void _omap2_module_wait_ready(struct clk *clk) * clockdomain pointer, and save it into the struct clk. Intended to be * called during clk_register(). No return value. */ -void omap2_init_clk_clkdm(struct clk *clk) +void omap2_init_clk_clkdm(struct clk_hw *hw) { + struct clk_hw_omap *clk = to_clk_hw_omap(hw); struct clockdomain *clkdm; const char *clk_name; if (!clk->clkdm_name) return; - clk_name = __clk_get_name(clk); + clk_name = __clk_get_name(hw->clk); clkdm = clkdm_lookup(clk->clkdm_name); if (clkdm) { @@ -146,8 +219,8 @@ void __init omap2_clk_disable_clkdm_control(void) * associate this type of code with per-module data structures to * avoid this issue, and remove the casts. No return value. */ -void omap2_clk_dflt_find_companion(struct clk *clk, void __iomem **other_reg, - u8 *other_bit) +void omap2_clk_dflt_find_companion(struct clk_hw_omap *clk, + void __iomem **other_reg, u8 *other_bit) { u32 r; @@ -175,8 +248,8 @@ void omap2_clk_dflt_find_companion(struct clk *clk, void __iomem **other_reg, * register address ID (e.g., that CM_FCLKEN2 corresponds to * CM_IDLEST2). This is not true for all modules. No return value. */ -void omap2_clk_dflt_find_idlest(struct clk *clk, void __iomem **idlest_reg, - u8 *idlest_bit, u8 *idlest_val) +void omap2_clk_dflt_find_idlest(struct clk_hw_omap *clk, + void __iomem **idlest_reg, u8 *idlest_bit, u8 *idlest_val) { u32 r; @@ -198,16 +271,44 @@ void omap2_clk_dflt_find_idlest(struct clk *clk, void __iomem **idlest_reg, } -int omap2_dflt_clk_enable(struct clk *clk) +/** + * omap2_dflt_clk_enable - enable a clock in the hardware + * @hw: struct clk_hw * of the clock to enable + * + * Enable the clock @hw in the hardware. We first call into the OMAP + * clockdomain code to "enable" the corresponding clockdomain if this + * is the first enabled user of the clockdomain. Then program the + * hardware to enable the clock. Then wait for the IP block that uses + * this clock to leave idle (if applicable). Returns the error value + * from clkdm_clk_enable() if it terminated with an error, or -EINVAL + * if @hw has a null clock enable_reg, or zero upon success. + */ +int omap2_dflt_clk_enable(struct clk_hw *hw) { + struct clk_hw_omap *clk; u32 v; + int ret = 0; + + clk = to_clk_hw_omap(hw); + + if (clkdm_control && clk->clkdm) { + ret = clkdm_clk_enable(clk->clkdm, hw->clk); + if (ret) { + WARN(1, "%s: could not enable %s's clockdomain %s: %d\n", + __func__, __clk_get_name(hw->clk), + clk->clkdm->name, ret); + return ret; + } + } if (unlikely(clk->enable_reg == NULL)) { - pr_err("clock.c: Enable for %s without enable code\n", - clk->name); - return 0; /* REVISIT: -EINVAL */ + pr_err("%s: %s missing enable_reg\n", __func__, + __clk_get_name(hw->clk)); + ret = -EINVAL; + goto err; } + /* FIXME should not have INVERT_ENABLE bit here */ v = __raw_readl(clk->enable_reg); if (clk->flags & INVERT_ENABLE) v &= ~(1 << clk->enable_bit); @@ -216,22 +317,39 @@ int omap2_dflt_clk_enable(struct clk *clk) __raw_writel(v, clk->enable_reg); v = __raw_readl(clk->enable_reg); /* OCP barrier */ - if (clk->ops->find_idlest) + if (clk->ops && clk->ops->find_idlest) _omap2_module_wait_ready(clk); return 0; + +err: + if (clkdm_control && clk->clkdm) + clkdm_clk_disable(clk->clkdm, hw->clk); + return ret; } -void omap2_dflt_clk_disable(struct clk *clk) +/** + * omap2_dflt_clk_disable - disable a clock in the hardware + * @hw: struct clk_hw * of the clock to disable + * + * Disable the clock @hw in the hardware, and call into the OMAP + * clockdomain code to "disable" the corresponding clockdomain if all + * clocks/hwmods in that clockdomain are now disabled. No return + * value. + */ +void omap2_dflt_clk_disable(struct clk_hw *hw) { + struct clk_hw_omap *clk; u32 v; + clk = to_clk_hw_omap(hw); if (!clk->enable_reg) { /* - * 'Independent' here refers to a clock which is not + * 'independent' here refers to a clock which is not * controlled by its parent. */ - pr_err("clock: clk_disable called on independent clock %s which has no enable_reg\n", clk->name); + pr_err("%s: independent clock %s has no enable_reg\n", + __func__, __clk_get_name(hw->clk)); return; } @@ -242,191 +360,213 @@ void omap2_dflt_clk_disable(struct clk *clk) v &= ~(1 << clk->enable_bit); __raw_writel(v, clk->enable_reg); /* No OCP barrier needed here since it is a disable operation */ -} - -const struct clkops clkops_omap2_dflt_wait = { - .enable = omap2_dflt_clk_enable, - .disable = omap2_dflt_clk_disable, - .find_companion = omap2_clk_dflt_find_companion, - .find_idlest = omap2_clk_dflt_find_idlest, -}; -const struct clkops clkops_omap2_dflt = { - .enable = omap2_dflt_clk_enable, - .disable = omap2_dflt_clk_disable, -}; + if (clkdm_control && clk->clkdm) + clkdm_clk_disable(clk->clkdm, hw->clk); +} /** - * omap2_clk_disable - disable a clock, if the system is not using it - * @clk: struct clk * to disable + * omap2_clkops_enable_clkdm - increment usecount on clkdm of @hw + * @hw: struct clk_hw * of the clock being enabled * - * Decrements the usecount on struct clk @clk. If there are no users - * left, call the clkops-specific clock disable function to disable it - * in hardware. If the clock is part of a clockdomain (which they all - * should be), request that the clockdomain be disabled. (It too has - * a usecount, and so will not be disabled in the hardware until it no - * longer has any users.) If the clock has a parent clock (most of - * them do), then call ourselves, recursing on the parent clock. This - * can cause an entire branch of the clock tree to be powered off by - * simply disabling one clock. Intended to be called with the clockfw_lock - * spinlock held. No return value. + * Increment the usecount of the clockdomain of the clock pointed to + * by @hw; if the usecount is 1, the clockdomain will be "enabled." + * Only needed for clocks that don't use omap2_dflt_clk_enable() as + * their enable function pointer. Passes along the return value of + * clkdm_clk_enable(), -EINVAL if @hw is not associated with a + * clockdomain, or 0 if clock framework-based clockdomain control is + * not implemented. */ -void omap2_clk_disable(struct clk *clk) +int omap2_clkops_enable_clkdm(struct clk_hw *hw) { - if (clk->usecount == 0) { - WARN(1, "clock: %s: omap2_clk_disable() called, but usecount already 0?", clk->name); - return; - } + struct clk_hw_omap *clk; + int ret = 0; - pr_debug("clock: %s: decrementing usecount\n", clk->name); + clk = to_clk_hw_omap(hw); - clk->usecount--; - - if (clk->usecount > 0) - return; + if (unlikely(!clk->clkdm)) { + pr_err("%s: %s: no clkdm set ?!\n", __func__, + __clk_get_name(hw->clk)); + return -EINVAL; + } - pr_debug("clock: %s: disabling in hardware\n", clk->name); + if (unlikely(clk->enable_reg)) + pr_err("%s: %s: should use dflt_clk_enable ?!\n", __func__, + __clk_get_name(hw->clk)); - if (clk->ops && clk->ops->disable) { - trace_clock_disable(clk->name, 0, smp_processor_id()); - clk->ops->disable(clk); + if (!clkdm_control) { + pr_err("%s: %s: clkfw-based clockdomain control disabled ?!\n", + __func__, __clk_get_name(hw->clk)); + return 0; } - if (clkdm_control && clk->clkdm) - clkdm_clk_disable(clk->clkdm, clk); + ret = clkdm_clk_enable(clk->clkdm, hw->clk); + WARN(ret, "%s: could not enable %s's clockdomain %s: %d\n", + __func__, __clk_get_name(hw->clk), clk->clkdm->name, ret); - if (clk->parent) - omap2_clk_disable(clk->parent); + return ret; } /** - * omap2_clk_enable - request that the system enable a clock - * @clk: struct clk * to enable + * omap2_clkops_disable_clkdm - decrement usecount on clkdm of @hw + * @hw: struct clk_hw * of the clock being disabled * - * Increments the usecount on struct clk @clk. If there were no users - * previously, then recurse up the clock tree, enabling all of the - * clock's parents and all of the parent clockdomains, and finally, - * enabling @clk's clockdomain, and @clk itself. Intended to be - * called with the clockfw_lock spinlock held. Returns 0 upon success - * or a negative error code upon failure. + * Decrement the usecount of the clockdomain of the clock pointed to + * by @hw; if the usecount is 0, the clockdomain will be "disabled." + * Only needed for clocks that don't use omap2_dflt_clk_disable() as their + * disable function pointer. No return value. */ -int omap2_clk_enable(struct clk *clk) +void omap2_clkops_disable_clkdm(struct clk_hw *hw) { - int ret; - - pr_debug("clock: %s: incrementing usecount\n", clk->name); - - clk->usecount++; - - if (clk->usecount > 1) - return 0; + struct clk_hw_omap *clk; - pr_debug("clock: %s: enabling in hardware\n", clk->name); + clk = to_clk_hw_omap(hw); - if (clk->parent) { - ret = omap2_clk_enable(clk->parent); - if (ret) { - WARN(1, "clock: %s: could not enable parent %s: %d\n", - clk->name, clk->parent->name, ret); - goto oce_err1; - } + if (unlikely(!clk->clkdm)) { + pr_err("%s: %s: no clkdm set ?!\n", __func__, + __clk_get_name(hw->clk)); + return; } - if (clkdm_control && clk->clkdm) { - ret = clkdm_clk_enable(clk->clkdm, clk); - if (ret) { - WARN(1, "clock: %s: could not enable clockdomain %s: %d\n", - clk->name, clk->clkdm->name, ret); - goto oce_err2; - } - } + if (unlikely(clk->enable_reg)) + pr_err("%s: %s: should use dflt_clk_disable ?!\n", __func__, + __clk_get_name(hw->clk)); - if (clk->ops && clk->ops->enable) { - trace_clock_enable(clk->name, 1, smp_processor_id()); - ret = clk->ops->enable(clk); - if (ret) { - WARN(1, "clock: %s: could not enable: %d\n", - clk->name, ret); - goto oce_err3; - } + if (!clkdm_control) { + pr_err("%s: %s: clkfw-based clockdomain control disabled ?!\n", + __func__, __clk_get_name(hw->clk)); + return; } - return 0; - -oce_err3: - if (clkdm_control && clk->clkdm) - clkdm_clk_disable(clk->clkdm, clk); -oce_err2: - if (clk->parent) - omap2_clk_disable(clk->parent); -oce_err1: - clk->usecount--; - - return ret; + clkdm_clk_disable(clk->clkdm, hw->clk); } -/* Given a clock and a rate apply a clock specific rounding function */ -long omap2_clk_round_rate(struct clk *clk, unsigned long rate) +/** + * omap2_dflt_clk_is_enabled - is clock enabled in the hardware? + * @hw: struct clk_hw * to check + * + * Return 1 if the clock represented by @hw is enabled in the + * hardware, or 0 otherwise. Intended for use in the struct + * clk_ops.is_enabled function pointer. + */ +int omap2_dflt_clk_is_enabled(struct clk_hw *hw) { - if (clk->round_rate) - return clk->round_rate(clk, rate); + struct clk_hw_omap *clk = to_clk_hw_omap(hw); + u32 v; + + v = __raw_readl(clk->enable_reg); + + if (clk->flags & INVERT_ENABLE) + v ^= BIT(clk->enable_bit); + + v &= BIT(clk->enable_bit); - return clk->rate; + return v ? 1 : 0; } -/* Set the clock rate for a clock source */ -int omap2_clk_set_rate(struct clk *clk, unsigned long rate) +static int __initdata mpurate; + +/* + * By default we use the rate set by the bootloader. + * You can override this with mpurate= cmdline option. + */ +static int __init omap_clk_setup(char *str) { - int ret = -EINVAL; + get_option(&str, &mpurate); - pr_debug("clock: set_rate for clock %s to rate %ld\n", clk->name, rate); + if (!mpurate) + return 1; - /* dpll_ck, core_ck, virt_prcm_set; plus all clksel clocks */ - if (clk->set_rate) { - trace_clock_set_rate(clk->name, rate, smp_processor_id()); - ret = clk->set_rate(clk, rate); - } + if (mpurate < 1000) + mpurate *= 1000000; - return ret; + return 1; } +__setup("mpurate=", omap_clk_setup); -int omap2_clk_set_parent(struct clk *clk, struct clk *new_parent) +/** + * omap2_init_clk_hw_omap_clocks - initialize an OMAP clock + * @clk: struct clk * to initialize + * + * Add an OMAP clock @clk to the internal list of OMAP clocks. Used + * temporarily for autoidle handling, until this support can be + * integrated into the common clock framework code in some way. No + * return value. + */ +void omap2_init_clk_hw_omap_clocks(struct clk *clk) { - if (!clk->clksel) - return -EINVAL; + struct clk_hw_omap *c; - if (clk->parent == new_parent) - return 0; + if (__clk_get_flags(clk) & CLK_IS_BASIC) + return; - return omap2_clksel_set_parent(clk, new_parent); + c = to_clk_hw_omap(__clk_get_hw(clk)); + list_add(&c->node, &clk_hw_omap_clocks); } -/* - * OMAP2+ clock reset and init functions +/** + * omap2_clk_enable_autoidle_all - enable autoidle on all OMAP clocks that + * support it + * + * Enable clock autoidle on all OMAP clocks that have allow_idle + * function pointers associated with them. This function is intended + * to be temporary until support for this is added to the common clock + * code. Returns 0. */ +int omap2_clk_enable_autoidle_all(void) +{ + struct clk_hw_omap *c; + + list_for_each_entry(c, &clk_hw_omap_clocks, node) + if (c->ops && c->ops->allow_idle) + c->ops->allow_idle(c); + return 0; +} -#ifdef CONFIG_OMAP_RESET_CLOCKS -void omap2_clk_disable_unused(struct clk *clk) +/** + * omap2_clk_disable_autoidle_all - disable autoidle on all OMAP clocks that + * support it + * + * Disable clock autoidle on all OMAP clocks that have allow_idle + * function pointers associated with them. This function is intended + * to be temporary until support for this is added to the common clock + * code. Returns 0. + */ +int omap2_clk_disable_autoidle_all(void) { - u32 regval32, v; + struct clk_hw_omap *c; - v = (clk->flags & INVERT_ENABLE) ? (1 << clk->enable_bit) : 0; + list_for_each_entry(c, &clk_hw_omap_clocks, node) + if (c->ops && c->ops->deny_idle) + c->ops->deny_idle(c); + return 0; +} - regval32 = __raw_readl(clk->enable_reg); - if ((regval32 & (1 << clk->enable_bit)) == v) - return; +/** + * omap2_clk_enable_init_clocks - prepare & enable a list of clocks + * @clk_names: ptr to an array of strings of clock names to enable + * @num_clocks: number of clock names in @clk_names + * + * Prepare and enable a list of clocks, named by @clk_names. No + * return value. XXX Deprecated; only needed until these clocks are + * properly claimed and enabled by the drivers or core code that uses + * them. XXX What code disables & calls clk_put on these clocks? + */ +void omap2_clk_enable_init_clocks(const char **clk_names, u8 num_clocks) +{ + struct clk *init_clk; + int i; - pr_debug("Disabling unused clock \"%s\"\n", clk->name); - if (cpu_is_omap34xx()) { - omap2_clk_enable(clk); - omap2_clk_disable(clk); - } else { - clk->ops->disable(clk); + for (i = 0; i < num_clocks; i++) { + init_clk = clk_get(NULL, clk_names[i]); + clk_prepare_enable(init_clk); } - if (clk->clkdm != NULL) - pwrdm_state_switch(clk->clkdm->pwrdm.ptr); } -#endif + +const struct clk_hw_omap_ops clkhwops_wait = { + .find_idlest = omap2_clk_dflt_find_idlest, + .find_companion = omap2_clk_dflt_find_companion, +}; /** * omap2_clk_switch_mpurate_at_boot - switch ARM MPU rate by boot-time argument @@ -458,14 +598,12 @@ int __init omap2_clk_switch_mpurate_at_boot(const char *mpurate_ck_name) r = clk_set_rate(mpurate_ck, mpurate); if (IS_ERR_VALUE(r)) { WARN(1, "clock: %s: unable to set MPU rate to %d: %d\n", - mpurate_ck->name, mpurate, r); + mpurate_ck_name, mpurate, r); clk_put(mpurate_ck); return -EINVAL; } calibrate_delay(); - recalculate_root_clocks(); - clk_put(mpurate_ck); return 0; @@ -509,15 +647,3 @@ void __init omap2_clk_print_new_rates(const char *hfclkin_ck_name, (clk_get_rate(core_ck) / 1000000), (clk_get_rate(mpu_ck) / 1000000)); } - -/* Common data */ - -struct clk_functions omap2_clk_functions = { - .clk_enable = omap2_clk_enable, - .clk_disable = omap2_clk_disable, - .clk_round_rate = omap2_clk_round_rate, - .clk_set_rate = omap2_clk_set_rate, - .clk_set_parent = omap2_clk_set_parent, - .clk_disable_unused = omap2_clk_disable_unused, -}; - |