diff options
author | Stephen Boyd <sboyd@codeaurora.org> | 2015-07-28 11:58:26 -0700 |
---|---|---|
committer | Stephen Boyd <sboyd@codeaurora.org> | 2015-07-28 11:58:26 -0700 |
commit | 4d341056202a4725777ed99897f6456405819cd4 (patch) | |
tree | 45fb9d229bacf995aabcad282937568e5201b5a7 /drivers/clk/ti | |
parent | e306479ac252928b84cc563c6e790f9b7e7ae427 (diff) | |
parent | 989feafb84118a840ff21250a1e5f516f43e3dbb (diff) |
Merge branch 'for-4.2/ti-clk-move' of https://github.com/t-kristo/linux-pm into clk-next
From Tero Kristo:
"This pull request contains the TI clock driver set to move the
clock implementations under clock driver. Some small portions of
the clock driver code still remain under mach-omap2 after this,
it should be decided whether this code is now obsolete and should
be deleted or should someone try to fix it."
Slight merge conflicts with determine_rate prototype changes.
Diffstat (limited to 'drivers/clk/ti')
-rw-r--r-- | drivers/clk/ti/Makefile | 17 | ||||
-rw-r--r-- | drivers/clk/ti/apll.c | 2 | ||||
-rw-r--r-- | drivers/clk/ti/autoidle.c | 121 | ||||
-rw-r--r-- | drivers/clk/ti/clk-2xxx.c | 2 | ||||
-rw-r--r-- | drivers/clk/ti/clk-33xx.c | 2 | ||||
-rw-r--r-- | drivers/clk/ti/clk-3xxx.c | 244 | ||||
-rw-r--r-- | drivers/clk/ti/clk-43xx.c | 2 | ||||
-rw-r--r-- | drivers/clk/ti/clk-44xx.c | 2 | ||||
-rw-r--r-- | drivers/clk/ti/clk-54xx.c | 2 | ||||
-rw-r--r-- | drivers/clk/ti/clk-7xx.c | 3 | ||||
-rw-r--r-- | drivers/clk/ti/clk-816x.c | 2 | ||||
-rw-r--r-- | drivers/clk/ti/clk.c | 145 | ||||
-rw-r--r-- | drivers/clk/ti/clkt_dflt.c | 316 | ||||
-rw-r--r-- | drivers/clk/ti/clkt_dpll.c | 369 | ||||
-rw-r--r-- | drivers/clk/ti/clkt_iclk.c | 101 | ||||
-rw-r--r-- | drivers/clk/ti/clock.h | 105 | ||||
-rw-r--r-- | drivers/clk/ti/clockdomain.c | 78 | ||||
-rw-r--r-- | drivers/clk/ti/dpll3xxx.c | 820 | ||||
-rw-r--r-- | drivers/clk/ti/dpll44xx.c | 227 | ||||
-rw-r--r-- | drivers/clk/ti/fixed-factor.c | 2 |
20 files changed, 2543 insertions, 19 deletions
diff --git a/drivers/clk/ti/Makefile b/drivers/clk/ti/Makefile index 105ffd0f5e79..9b93e6904359 100644 --- a/drivers/clk/ti/Makefile +++ b/drivers/clk/ti/Makefile @@ -1,16 +1,19 @@ obj-y += clk.o autoidle.o clockdomain.o clk-common = dpll.o composite.o divider.o gate.o \ - fixed-factor.o mux.o apll.o -obj-$(CONFIG_SOC_AM33XX) += $(clk-common) clk-33xx.o + fixed-factor.o mux.o apll.o \ + clkt_dpll.o clkt_iclk.o clkt_dflt.o +obj-$(CONFIG_SOC_AM33XX) += $(clk-common) clk-33xx.o dpll3xxx.o obj-$(CONFIG_SOC_TI81XX) += $(clk-common) fapll.o clk-816x.o obj-$(CONFIG_ARCH_OMAP2) += $(clk-common) interface.o clk-2xxx.o obj-$(CONFIG_ARCH_OMAP3) += $(clk-common) interface.o \ - clk-3xxx.o -obj-$(CONFIG_ARCH_OMAP4) += $(clk-common) clk-44xx.o -obj-$(CONFIG_SOC_OMAP5) += $(clk-common) clk-54xx.o + clk-3xxx.o dpll3xxx.o +obj-$(CONFIG_ARCH_OMAP4) += $(clk-common) clk-44xx.o \ + dpll3xxx.o dpll44xx.o +obj-$(CONFIG_SOC_OMAP5) += $(clk-common) clk-54xx.o \ + dpll3xxx.o dpll44xx.o obj-$(CONFIG_SOC_DRA7XX) += $(clk-common) clk-7xx.o \ - clk-dra7-atl.o -obj-$(CONFIG_SOC_AM43XX) += $(clk-common) clk-43xx.o + clk-dra7-atl.o dpll3xxx.o dpll44xx.o +obj-$(CONFIG_SOC_AM43XX) += $(clk-common) dpll3xxx.o clk-43xx.o ifdef CONFIG_ATAGS obj-$(CONFIG_ARCH_OMAP3) += clk-3xxx-legacy.o diff --git a/drivers/clk/ti/apll.c b/drivers/clk/ti/apll.c index 49baf3831546..594b759f02ee 100644 --- a/drivers/clk/ti/apll.c +++ b/drivers/clk/ti/apll.c @@ -27,6 +27,8 @@ #include <linux/clk/ti.h> #include <linux/delay.h> +#include "clock.h" + #define APLL_FORCE_LOCK 0x1 #define APLL_AUTO_IDLE 0x2 #define MAX_APLL_WAIT_TRIES 1000000 diff --git a/drivers/clk/ti/autoidle.c b/drivers/clk/ti/autoidle.c index e75c64c9e81c..94f0dcd94181 100644 --- a/drivers/clk/ti/autoidle.c +++ b/drivers/clk/ti/autoidle.c @@ -22,6 +22,8 @@ #include <linux/of_address.h> #include <linux/clk/ti.h> +#include "clock.h" + struct clk_ti_autoidle { void __iomem *reg; u8 shift; @@ -33,8 +35,47 @@ struct clk_ti_autoidle { #define AUTOIDLE_LOW 0x1 static LIST_HEAD(autoidle_clks); +static LIST_HEAD(clk_hw_omap_clocks); -static void ti_allow_autoidle(struct clk_ti_autoidle *clk) +/** + * omap2_clk_deny_idle - disable autoidle on an OMAP clock + * @clk: struct clk * to disable autoidle for + * + * Disable autoidle on an OMAP clock. + */ +int omap2_clk_deny_idle(struct clk *clk) +{ + struct clk_hw_omap *c; + + if (__clk_get_flags(clk) & CLK_IS_BASIC) + return -EINVAL; + + c = to_clk_hw_omap(__clk_get_hw(clk)); + if (c->ops && c->ops->deny_idle) + c->ops->deny_idle(c); + return 0; +} + +/** + * omap2_clk_allow_idle - enable autoidle on an OMAP clock + * @clk: struct clk * to enable autoidle for + * + * Enable autoidle on an OMAP clock. + */ +int omap2_clk_allow_idle(struct clk *clk) +{ + struct clk_hw_omap *c; + + if (__clk_get_flags(clk) & CLK_IS_BASIC) + return -EINVAL; + + c = to_clk_hw_omap(__clk_get_hw(clk)); + if (c->ops && c->ops->allow_idle) + c->ops->allow_idle(c); + return 0; +} + +static void _allow_autoidle(struct clk_ti_autoidle *clk) { u32 val; @@ -48,7 +89,7 @@ static void ti_allow_autoidle(struct clk_ti_autoidle *clk) ti_clk_ll_ops->clk_writel(val, clk->reg); } -static void ti_deny_autoidle(struct clk_ti_autoidle *clk) +static void _deny_autoidle(struct clk_ti_autoidle *clk) { u32 val; @@ -63,31 +104,31 @@ static void ti_deny_autoidle(struct clk_ti_autoidle *clk) } /** - * of_ti_clk_allow_autoidle_all - enable autoidle for all clocks + * _clk_generic_allow_autoidle_all - enable autoidle for all clocks * * Enables hardware autoidle for all registered DT clocks, which have * the feature. */ -void of_ti_clk_allow_autoidle_all(void) +static void _clk_generic_allow_autoidle_all(void) { struct clk_ti_autoidle *c; list_for_each_entry(c, &autoidle_clks, node) - ti_allow_autoidle(c); + _allow_autoidle(c); } /** - * of_ti_clk_deny_autoidle_all - disable autoidle for all clocks + * _clk_generic_deny_autoidle_all - disable autoidle for all clocks * * Disables hardware autoidle for all registered DT clocks, which have * the feature. */ -void of_ti_clk_deny_autoidle_all(void) +static void _clk_generic_deny_autoidle_all(void) { struct clk_ti_autoidle *c; list_for_each_entry(c, &autoidle_clks, node) - ti_deny_autoidle(c); + _deny_autoidle(c); } /** @@ -131,3 +172,67 @@ int __init of_ti_clk_autoidle_setup(struct device_node *node) return 0; } + +/** + * 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) +{ + struct clk_hw_omap *c; + + if (__clk_get_flags(clk) & CLK_IS_BASIC) + return; + + c = to_clk_hw_omap(__clk_get_hw(clk)); + list_add(&c->node, &clk_hw_omap_clocks); +} + +/** + * 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); + + _clk_generic_allow_autoidle_all(); + + return 0; +} + +/** + * 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) +{ + struct clk_hw_omap *c; + + list_for_each_entry(c, &clk_hw_omap_clocks, node) + if (c->ops && c->ops->deny_idle) + c->ops->deny_idle(c); + + _clk_generic_deny_autoidle_all(); + + return 0; +} diff --git a/drivers/clk/ti/clk-2xxx.c b/drivers/clk/ti/clk-2xxx.c index c808ab3d2bb2..bd8790be2ab1 100644 --- a/drivers/clk/ti/clk-2xxx.c +++ b/drivers/clk/ti/clk-2xxx.c @@ -19,6 +19,8 @@ #include <linux/clk-provider.h> #include <linux/clk/ti.h> +#include "clock.h" + static struct ti_dt_clk omap2xxx_clks[] = { DT_CLK(NULL, "func_32k_ck", "func_32k_ck"), DT_CLK(NULL, "secure_32k_ck", "secure_32k_ck"), diff --git a/drivers/clk/ti/clk-33xx.c b/drivers/clk/ti/clk-33xx.c index 028b33783d38..733f9d374d0f 100644 --- a/drivers/clk/ti/clk-33xx.c +++ b/drivers/clk/ti/clk-33xx.c @@ -19,6 +19,8 @@ #include <linux/clk-provider.h> #include <linux/clk/ti.h> +#include "clock.h" + static struct ti_dt_clk am33xx_clks[] = { DT_CLK(NULL, "clk_32768_ck", "clk_32768_ck"), DT_CLK(NULL, "clk_rc32k_ck", "clk_rc32k_ck"), diff --git a/drivers/clk/ti/clk-3xxx.c b/drivers/clk/ti/clk-3xxx.c index 757636d166cf..6e33332b6b34 100644 --- a/drivers/clk/ti/clk-3xxx.c +++ b/drivers/clk/ti/clk-3xxx.c @@ -19,6 +19,226 @@ #include <linux/clk-provider.h> #include <linux/clk/ti.h> +#include "clock.h" + +/* + * DPLL5_FREQ_FOR_USBHOST: USBHOST and USBTLL are the only clocks + * that are sourced by DPLL5, and both of these require this clock + * to be at 120 MHz for proper operation. + */ +#define DPLL5_FREQ_FOR_USBHOST 120000000 + +#define OMAP3430ES2_ST_DSS_IDLE_SHIFT 1 +#define OMAP3430ES2_ST_HSOTGUSB_IDLE_SHIFT 5 +#define OMAP3430ES2_ST_SSI_IDLE_SHIFT 8 + +#define OMAP34XX_CM_IDLEST_VAL 1 + +/* + * In AM35xx IPSS, the {ICK,FCK} enable bits for modules are exported + * in the same register at a bit offset of 0x8. The EN_ACK for ICK is + * at an offset of 4 from ICK enable bit. + */ +#define AM35XX_IPSS_ICK_MASK 0xF +#define AM35XX_IPSS_ICK_EN_ACK_OFFSET 0x4 +#define AM35XX_IPSS_ICK_FCK_OFFSET 0x8 +#define AM35XX_IPSS_CLK_IDLEST_VAL 0 + +#define AM35XX_ST_IPSS_SHIFT 5 + +/** + * omap3430es2_clk_ssi_find_idlest - return CM_IDLEST info for SSI + * @clk: struct clk * being enabled + * @idlest_reg: void __iomem ** to store CM_IDLEST reg address into + * @idlest_bit: pointer to a u8 to store the CM_IDLEST bit shift into + * @idlest_val: pointer to a u8 to store the CM_IDLEST indicator + * + * The OMAP3430ES2 SSI target CM_IDLEST bit is at a different shift + * from the CM_{I,F}CLKEN bit. Pass back the correct info via + * @idlest_reg and @idlest_bit. No return value. + */ +static void omap3430es2_clk_ssi_find_idlest(struct clk_hw_omap *clk, + void __iomem **idlest_reg, + u8 *idlest_bit, + u8 *idlest_val) +{ + u32 r; + + r = (((__force u32)clk->enable_reg & ~0xf0) | 0x20); + *idlest_reg = (__force void __iomem *)r; + *idlest_bit = OMAP3430ES2_ST_SSI_IDLE_SHIFT; + *idlest_val = OMAP34XX_CM_IDLEST_VAL; +} + +const struct clk_hw_omap_ops clkhwops_omap3430es2_ssi_wait = { + .find_idlest = omap3430es2_clk_ssi_find_idlest, + .find_companion = omap2_clk_dflt_find_companion, +}; + +const struct clk_hw_omap_ops clkhwops_omap3430es2_iclk_ssi_wait = { + .allow_idle = omap2_clkt_iclk_allow_idle, + .deny_idle = omap2_clkt_iclk_deny_idle, + .find_idlest = omap3430es2_clk_ssi_find_idlest, + .find_companion = omap2_clk_dflt_find_companion, +}; + +/** + * omap3430es2_clk_dss_usbhost_find_idlest - CM_IDLEST info for DSS, USBHOST + * @clk: struct clk * being enabled + * @idlest_reg: void __iomem ** to store CM_IDLEST reg address into + * @idlest_bit: pointer to a u8 to store the CM_IDLEST bit shift into + * @idlest_val: pointer to a u8 to store the CM_IDLEST indicator + * + * Some OMAP modules on OMAP3 ES2+ chips have both initiator and + * target IDLEST bits. For our purposes, we are concerned with the + * target IDLEST bits, which exist at a different bit position than + * the *CLKEN bit position for these modules (DSS and USBHOST) (The + * default find_idlest code assumes that they are at the same + * position.) No return value. + */ +static void omap3430es2_clk_dss_usbhost_find_idlest(struct clk_hw_omap *clk, + void __iomem **idlest_reg, + u8 *idlest_bit, + u8 *idlest_val) +{ + u32 r; + + r = (((__force u32)clk->enable_reg & ~0xf0) | 0x20); + *idlest_reg = (__force void __iomem *)r; + /* USBHOST_IDLE has same shift */ + *idlest_bit = OMAP3430ES2_ST_DSS_IDLE_SHIFT; + *idlest_val = OMAP34XX_CM_IDLEST_VAL; +} + +const struct clk_hw_omap_ops clkhwops_omap3430es2_dss_usbhost_wait = { + .find_idlest = omap3430es2_clk_dss_usbhost_find_idlest, + .find_companion = omap2_clk_dflt_find_companion, +}; + +const struct clk_hw_omap_ops clkhwops_omap3430es2_iclk_dss_usbhost_wait = { + .allow_idle = omap2_clkt_iclk_allow_idle, + .deny_idle = omap2_clkt_iclk_deny_idle, + .find_idlest = omap3430es2_clk_dss_usbhost_find_idlest, + .find_companion = omap2_clk_dflt_find_companion, +}; + +/** + * omap3430es2_clk_hsotgusb_find_idlest - return CM_IDLEST info for HSOTGUSB + * @clk: struct clk * being enabled + * @idlest_reg: void __iomem ** to store CM_IDLEST reg address into + * @idlest_bit: pointer to a u8 to store the CM_IDLEST bit shift into + * @idlest_val: pointer to a u8 to store the CM_IDLEST indicator + * + * The OMAP3430ES2 HSOTGUSB target CM_IDLEST bit is at a different + * shift from the CM_{I,F}CLKEN bit. Pass back the correct info via + * @idlest_reg and @idlest_bit. No return value. + */ +static void omap3430es2_clk_hsotgusb_find_idlest(struct clk_hw_omap *clk, + void __iomem **idlest_reg, + u8 *idlest_bit, + u8 *idlest_val) +{ + u32 r; + + r = (((__force u32)clk->enable_reg & ~0xf0) | 0x20); + *idlest_reg = (__force void __iomem *)r; + *idlest_bit = OMAP3430ES2_ST_HSOTGUSB_IDLE_SHIFT; + *idlest_val = OMAP34XX_CM_IDLEST_VAL; +} + +const struct clk_hw_omap_ops clkhwops_omap3430es2_iclk_hsotgusb_wait = { + .allow_idle = omap2_clkt_iclk_allow_idle, + .deny_idle = omap2_clkt_iclk_deny_idle, + .find_idlest = omap3430es2_clk_hsotgusb_find_idlest, + .find_companion = omap2_clk_dflt_find_companion, +}; + +const struct clk_hw_omap_ops clkhwops_omap3430es2_hsotgusb_wait = { + .find_idlest = omap3430es2_clk_hsotgusb_find_idlest, + .find_companion = omap2_clk_dflt_find_companion, +}; + +/** + * am35xx_clk_find_idlest - return clock ACK info for AM35XX IPSS + * @clk: struct clk * being enabled + * @idlest_reg: void __iomem ** to store CM_IDLEST reg address into + * @idlest_bit: pointer to a u8 to store the CM_IDLEST bit shift into + * @idlest_val: pointer to a u8 to store the CM_IDLEST indicator + * + * The interface clocks on AM35xx IPSS reflects the clock idle status + * in the enable register itsel at a bit offset of 4 from the enable + * bit. A value of 1 indicates that clock is enabled. + */ +static void am35xx_clk_find_idlest(struct clk_hw_omap *clk, + void __iomem **idlest_reg, + u8 *idlest_bit, + u8 *idlest_val) +{ + *idlest_reg = (__force void __iomem *)(clk->enable_reg); + *idlest_bit = clk->enable_bit + AM35XX_IPSS_ICK_EN_ACK_OFFSET; + *idlest_val = AM35XX_IPSS_CLK_IDLEST_VAL; +} + +/** + * am35xx_clk_find_companion - find companion clock to @clk + * @clk: struct clk * to find the companion clock of + * @other_reg: void __iomem ** to return the companion clock CM_*CLKEN va in + * @other_bit: u8 ** to return the companion clock bit shift in + * + * Some clocks don't have companion clocks. For example, modules with + * only an interface clock (such as HECC) don't have a companion + * clock. Right now, this code relies on the hardware exporting a bit + * in the correct companion register that indicates that the + * nonexistent 'companion clock' is active. Future patches will + * associate this type of code with per-module data structures to + * avoid this issue, and remove the casts. No return value. + */ +static void am35xx_clk_find_companion(struct clk_hw_omap *clk, + void __iomem **other_reg, + u8 *other_bit) +{ + *other_reg = (__force void __iomem *)(clk->enable_reg); + if (clk->enable_bit & AM35XX_IPSS_ICK_MASK) + *other_bit = clk->enable_bit + AM35XX_IPSS_ICK_FCK_OFFSET; + else + *other_bit = clk->enable_bit - AM35XX_IPSS_ICK_FCK_OFFSET; +} + +const struct clk_hw_omap_ops clkhwops_am35xx_ipss_module_wait = { + .find_idlest = am35xx_clk_find_idlest, + .find_companion = am35xx_clk_find_companion, +}; + +/** + * am35xx_clk_ipss_find_idlest - return CM_IDLEST info for IPSS + * @clk: struct clk * being enabled + * @idlest_reg: void __iomem ** to store CM_IDLEST reg address into + * @idlest_bit: pointer to a u8 to store the CM_IDLEST bit shift into + * @idlest_val: pointer to a u8 to store the CM_IDLEST indicator + * + * The IPSS target CM_IDLEST bit is at a different shift from the + * CM_{I,F}CLKEN bit. Pass back the correct info via @idlest_reg + * and @idlest_bit. No return value. + */ +static void am35xx_clk_ipss_find_idlest(struct clk_hw_omap *clk, + void __iomem **idlest_reg, + u8 *idlest_bit, + u8 *idlest_val) +{ + u32 r; + + r = (((__force u32)clk->enable_reg & ~0xf0) | 0x20); + *idlest_reg = (__force void __iomem *)r; + *idlest_bit = AM35XX_ST_IPSS_SHIFT; + *idlest_val = OMAP34XX_CM_IDLEST_VAL; +} + +const struct clk_hw_omap_ops clkhwops_am35xx_ipss_wait = { + .allow_idle = omap2_clkt_iclk_allow_idle, + .deny_idle = omap2_clkt_iclk_deny_idle, + .find_idlest = am35xx_clk_ipss_find_idlest, + .find_companion = omap2_clk_dflt_find_companion, +}; static struct ti_dt_clk omap3xxx_clks[] = { DT_CLK(NULL, "apb_pclk", "dummy_apb_pclk"), @@ -324,6 +544,30 @@ enum { OMAP3_SOC_OMAP3630, }; +/** + * omap3_clk_lock_dpll5 - locks DPLL5 + * + * Locks DPLL5 to a pre-defined frequency. This is required for proper + * operation of USB. + */ +void __init omap3_clk_lock_dpll5(void) +{ + struct clk *dpll5_clk; + struct clk *dpll5_m2_clk; + + dpll5_clk = clk_get(NULL, "dpll5_ck"); + clk_set_rate(dpll5_clk, DPLL5_FREQ_FOR_USBHOST); + clk_prepare_enable(dpll5_clk); + + /* Program dpll5_m2_clk divider for no division */ + dpll5_m2_clk = clk_get(NULL, "dpll5_m2_ck"); + clk_prepare_enable(dpll5_m2_clk); + clk_set_rate(dpll5_m2_clk, DPLL5_FREQ_FOR_USBHOST); + + clk_disable_unprepare(dpll5_m2_clk); + clk_disable_unprepare(dpll5_clk); +} + static int __init omap3xxx_dt_clk_init(int soc_type) { if (soc_type == OMAP3_SOC_AM35XX || soc_type == OMAP3_SOC_OMAP3630 || diff --git a/drivers/clk/ti/clk-43xx.c b/drivers/clk/ti/clk-43xx.c index 3795fce8a830..894316738459 100644 --- a/drivers/clk/ti/clk-43xx.c +++ b/drivers/clk/ti/clk-43xx.c @@ -19,6 +19,8 @@ #include <linux/clk-provider.h> #include <linux/clk/ti.h> +#include "clock.h" + static struct ti_dt_clk am43xx_clks[] = { DT_CLK(NULL, "clk_32768_ck", "clk_32768_ck"), DT_CLK(NULL, "clk_rc32k_ck", "clk_rc32k_ck"), diff --git a/drivers/clk/ti/clk-44xx.c b/drivers/clk/ti/clk-44xx.c index 581db7711f51..7a8b51b35f9f 100644 --- a/drivers/clk/ti/clk-44xx.c +++ b/drivers/clk/ti/clk-44xx.c @@ -16,6 +16,8 @@ #include <linux/clkdev.h> #include <linux/clk/ti.h> +#include "clock.h" + /* * OMAP4 ABE DPLL default frequency. In OMAP4460 TRM version V, section * "3.6.3.2.3 CM1_ABE Clock Generator" states that the "DPLL_ABE_X2_CLK diff --git a/drivers/clk/ti/clk-54xx.c b/drivers/clk/ti/clk-54xx.c index 96c69a335975..59ce2fa2c104 100644 --- a/drivers/clk/ti/clk-54xx.c +++ b/drivers/clk/ti/clk-54xx.c @@ -17,6 +17,8 @@ #include <linux/io.h> #include <linux/clk/ti.h> +#include "clock.h" + #define OMAP5_DPLL_ABE_DEFFREQ 98304000 /* diff --git a/drivers/clk/ti/clk-7xx.c b/drivers/clk/ti/clk-7xx.c index 63b8323df918..9b5b289e6334 100644 --- a/drivers/clk/ti/clk-7xx.c +++ b/drivers/clk/ti/clk-7xx.c @@ -16,11 +16,12 @@ #include <linux/clkdev.h> #include <linux/clk/ti.h> +#include "clock.h" + #define DRA7_DPLL_ABE_DEFFREQ 180633600 #define DRA7_DPLL_GMAC_DEFFREQ 1000000000 #define DRA7_DPLL_USB_DEFFREQ 960000000 - static struct ti_dt_clk dra7xx_clks[] = { DT_CLK(NULL, "atl_clkin0_ck", "atl_clkin0_ck"), DT_CLK(NULL, "atl_clkin1_ck", "atl_clkin1_ck"), diff --git a/drivers/clk/ti/clk-816x.c b/drivers/clk/ti/clk-816x.c index 9451e651a1ff..c69352b24dba 100644 --- a/drivers/clk/ti/clk-816x.c +++ b/drivers/clk/ti/clk-816x.c @@ -14,6 +14,8 @@ #include <linux/clk-provider.h> #include <linux/clk/ti.h> +#include "clock.h" + static struct ti_dt_clk dm816x_clks[] = { DT_CLK(NULL, "sys_clkin", "sys_clkin_ck"), DT_CLK(NULL, "timer_sys_ck", "sys_clkin_ck"), diff --git a/drivers/clk/ti/clk.c b/drivers/clk/ti/clk.c index 64bb5e8a3b8c..3343bde47fe4 100644 --- a/drivers/clk/ti/clk.c +++ b/drivers/clk/ti/clk.c @@ -21,6 +21,8 @@ #include <linux/of.h> #include <linux/of_address.h> #include <linux/list.h> +#include <linux/regmap.h> +#include <linux/bootmem.h> #include "clock.h" @@ -30,6 +32,63 @@ struct ti_clk_ll_ops *ti_clk_ll_ops; static struct device_node *clocks_node_ptr[CLK_MAX_MEMMAPS]; +struct ti_clk_features ti_clk_features; + +struct clk_iomap { + struct regmap *regmap; + void __iomem *mem; +}; + +static struct clk_iomap *clk_memmaps[CLK_MAX_MEMMAPS]; + +static void clk_memmap_writel(u32 val, void __iomem *reg) +{ + struct clk_omap_reg *r = (struct clk_omap_reg *)® + struct clk_iomap *io = clk_memmaps[r->index]; + + if (io->regmap) + regmap_write(io->regmap, r->offset, val); + else + writel_relaxed(val, io->mem + r->offset); +} + +static u32 clk_memmap_readl(void __iomem *reg) +{ + u32 val; + struct clk_omap_reg *r = (struct clk_omap_reg *)® + struct clk_iomap *io = clk_memmaps[r->index]; + + if (io->regmap) + regmap_read(io->regmap, r->offset, &val); + else + val = readl_relaxed(io->mem + r->offset); + + return val; +} + +/** + * ti_clk_setup_ll_ops - setup low level clock operations + * @ops: low level clock ops descriptor + * + * Sets up low level clock operations for TI clock driver. This is used + * to provide various callbacks for the clock driver towards platform + * specific code. Returns 0 on success, -EBUSY if ll_ops have been + * registered already. + */ +int ti_clk_setup_ll_ops(struct ti_clk_ll_ops *ops) +{ + if (ti_clk_ll_ops) { + pr_err("Attempt to register ll_ops multiple times.\n"); + return -EBUSY; + } + + ti_clk_ll_ops = ops; + ops->clk_readl = clk_memmap_readl; + ops->clk_writel = clk_memmap_writel; + + return 0; +} + /** * ti_dt_clocks_register - register DT alias clocks during boot * @oclks: list of clocks to register @@ -138,28 +197,61 @@ void __iomem *ti_clk_get_reg_addr(struct device_node *node, int index) } /** - * ti_dt_clk_init_provider - init master clock provider + * omap2_clk_provider_init - init master clock provider * @parent: master node * @index: internal index for clk_reg_ops + * @syscon: syscon regmap pointer for accessing clock registers + * @mem: iomem pointer for the clock provider memory area, only used if + * syscon is not provided * * Initializes a master clock IP block. This basically sets up the * mapping from clocks node to the memory map index. All the clocks * are then initialized through the common of_clk_init call, and the * clocks will access their memory maps based on the node layout. + * Returns 0 in success. */ -void ti_dt_clk_init_provider(struct device_node *parent, int index) +int __init omap2_clk_provider_init(struct device_node *parent, int index, + struct regmap *syscon, void __iomem *mem) { struct device_node *clocks; + struct clk_iomap *io; /* get clocks for this parent */ clocks = of_get_child_by_name(parent, "clocks"); if (!clocks) { pr_err("%s missing 'clocks' child node.\n", parent->name); - return; + return -EINVAL; } /* add clocks node info */ clocks_node_ptr[index] = clocks; + + io = kzalloc(sizeof(*io), GFP_KERNEL); + + io->regmap = syscon; + io->mem = mem; + + clk_memmaps[index] = io; + + return 0; +} + +/** + * omap2_clk_legacy_provider_init - initialize a legacy clock provider + * @index: index for the clock provider + * @mem: iomem pointer for the clock provider memory area + * + * Initializes a legacy clock provider memory mapping. + */ +void __init omap2_clk_legacy_provider_init(int index, void __iomem *mem) +{ + struct clk_iomap *io; + + io = memblock_virt_alloc(sizeof(*io), 0); + + io->mem = mem; + + clk_memmaps[index] = io; } /** @@ -311,3 +403,50 @@ int __init ti_clk_register_legacy_clks(struct ti_clk_alias *clks) return 0; } #endif + +/** + * ti_clk_setup_features - setup clock features flags + * @features: features definition to use + * + * Initializes the clock driver features flags based on platform + * provided data. No return value. + */ +void __init ti_clk_setup_features(struct ti_clk_features *features) +{ + memcpy(&ti_clk_features, features, sizeof(*features)); +} + +/** + * ti_clk_get_features - get clock driver features flags + * + * Get TI clock driver features description. Returns a pointer + * to the current feature setup. + */ +const struct ti_clk_features *ti_clk_get_features(void) +{ + return &ti_clk_features; +} + +/** + * 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; + + for (i = 0; i < num_clocks; i++) { + init_clk = clk_get(NULL, clk_names[i]); + if (WARN(IS_ERR(init_clk), "could not find init clock %s\n", + clk_names[i])) + continue; + clk_prepare_enable(init_clk); + } +} diff --git a/drivers/clk/ti/clkt_dflt.c b/drivers/clk/ti/clkt_dflt.c new file mode 100644 index 000000000000..a176b8ac8dd0 --- /dev/null +++ b/drivers/clk/ti/clkt_dflt.c @@ -0,0 +1,316 @@ +/* + * Default clock type + * + * Copyright (C) 2005-2008, 2015 Texas Instruments, Inc. + * Copyright (C) 2004-2010 Nokia Corporation + * + * Contacts: + * Richard Woodruff <r-woodruff2@ti.com> + * Paul Walmsley + * Tero Kristo <t-kristo@ti.com> + * + * 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 program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/clk/ti.h> +#include <linux/delay.h> + +#include "clock.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 + +/* + * CM module register offsets, used for calculating the companion + * register addresses. + */ +#define CM_FCLKEN 0x0000 +#define CM_ICLKEN 0x0010 + +/** + * _wait_idlest_generic - wait for a module to leave the idle state + * @clk: module clock to wait for (needed for register offsets) + * @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(struct clk_hw_omap *clk, void __iomem *reg, + u32 mask, u8 idlest, const char *name) +{ + int i = 0, ena = 0; + + ena = (idlest) ? 0 : mask; + + /* Wait until module enters enabled state */ + for (i = 0; i < MAX_MODULE_ENABLE_WAIT; i++) { + if ((ti_clk_ll_ops->clk_readl(reg) & mask) == ena) + break; + udelay(1); + } + + 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 + * + * If the necessary clocks for the OMAP hardware IP block that + * corresponds to clock @clk are enabled, then wait for the module to + * indicate readiness (i.e., to leave IDLE). This code does not + * 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_hw_omap *clk) +{ + void __iomem *companion_reg, *idlest_reg; + 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) { + clk->ops->find_companion(clk, &companion_reg, &other_bit); + if (!(ti_clk_ll_ops->clk_readl(companion_reg) & + (1 << other_bit))) + return; + } + + clk->ops->find_idlest(clk, &idlest_reg, &idlest_bit, &idlest_val); + r = ti_clk_ll_ops->cm_split_idlest_reg(idlest_reg, &prcm_mod, + &idlest_reg_id); + if (r) { + /* IDLEST register not in the CM module */ + _wait_idlest_generic(clk, idlest_reg, (1 << idlest_bit), + idlest_val, __clk_get_name(clk->hw.clk)); + } else { + ti_clk_ll_ops->cm_wait_module_ready(0, prcm_mod, idlest_reg_id, + idlest_bit); + } +} + +/** + * omap2_clk_dflt_find_companion - find companion clock to @clk + * @clk: struct clk * to find the companion clock of + * @other_reg: void __iomem ** to return the companion clock CM_*CLKEN va in + * @other_bit: u8 ** to return the companion clock bit shift in + * + * Note: We don't need special code here for INVERT_ENABLE for the + * time being since INVERT_ENABLE only applies to clocks enabled by + * CM_CLKEN_PLL + * + * Convert CM_ICLKEN* <-> CM_FCLKEN*. This conversion assumes it's + * just a matter of XORing the bits. + * + * Some clocks don't have companion clocks. For example, modules with + * only an interface clock (such as MAILBOXES) don't have a companion + * clock. Right now, this code relies on the hardware exporting a bit + * in the correct companion register that indicates that the + * nonexistent 'companion clock' is active. Future patches will + * 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_hw_omap *clk, + void __iomem **other_reg, u8 *other_bit) +{ + u32 r; + + /* + * Convert CM_ICLKEN* <-> CM_FCLKEN*. This conversion assumes + * it's just a matter of XORing the bits. + */ + r = ((__force u32)clk->enable_reg ^ (CM_FCLKEN ^ CM_ICLKEN)); + + *other_reg = (__force void __iomem *)r; + *other_bit = clk->enable_bit; +} + +/** + * omap2_clk_dflt_find_idlest - find CM_IDLEST reg va, bit shift for @clk + * @clk: struct clk * to find IDLEST info for + * @idlest_reg: void __iomem ** to return the CM_IDLEST va in + * @idlest_bit: u8 * to return the CM_IDLEST bit shift in + * @idlest_val: u8 * to return the idle status indicator + * + * Return the CM_IDLEST register address and bit shift corresponding + * to the module that "owns" this clock. This default code assumes + * that the CM_IDLEST bit shift is the CM_*CLKEN bit shift, and that + * the IDLEST register address ID corresponds to the CM_*CLKEN + * 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_hw_omap *clk, + void __iomem **idlest_reg, u8 *idlest_bit, + u8 *idlest_val) +{ + u32 r; + + r = (((__force u32)clk->enable_reg & ~0xf0) | 0x20); + *idlest_reg = (__force void __iomem *)r; + *idlest_bit = clk->enable_bit; + + /* + * 24xx uses 0 to indicate not ready, and 1 to indicate ready. + * 34xx reverses this, just to keep us on our toes + * AM35xx uses both, depending on the module. + */ + *idlest_val = ti_clk_get_features()->cm_idlest_val; +} + +/** + * 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; + bool clkdm_control; + + if (ti_clk_get_features()->flags & TI_CLK_DISABLE_CLKDM_CONTROL) + clkdm_control = false; + else + clkdm_control = true; + + clk = to_clk_hw_omap(hw); + + if (clkdm_control && clk->clkdm) { + ret = ti_clk_ll_ops->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)) { + 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 = ti_clk_ll_ops->clk_readl(clk->enable_reg); + if (clk->flags & INVERT_ENABLE) + v &= ~(1 << clk->enable_bit); + else + v |= (1 << clk->enable_bit); + ti_clk_ll_ops->clk_writel(v, clk->enable_reg); + v = ti_clk_ll_ops->clk_readl(clk->enable_reg); /* OCP barrier */ + + if (clk->ops && clk->ops->find_idlest) + _omap2_module_wait_ready(clk); + + return 0; + +err: + if (clkdm_control && clk->clkdm) + ti_clk_ll_ops->clkdm_clk_disable(clk->clkdm, hw->clk); + return ret; +} + +/** + * 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 + * controlled by its parent. + */ + pr_err("%s: independent clock %s has no enable_reg\n", + __func__, __clk_get_name(hw->clk)); + return; + } + + v = ti_clk_ll_ops->clk_readl(clk->enable_reg); + if (clk->flags & INVERT_ENABLE) + v |= (1 << clk->enable_bit); + else + v &= ~(1 << clk->enable_bit); + ti_clk_ll_ops->clk_writel(v, clk->enable_reg); + /* No OCP barrier needed here since it is a disable operation */ + + if (!(ti_clk_get_features()->flags & TI_CLK_DISABLE_CLKDM_CONTROL) && + clk->clkdm) + ti_clk_ll_ops->clkdm_clk_disable(clk->clkdm, hw->clk); +} + +/** + * 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) +{ + struct clk_hw_omap *clk = to_clk_hw_omap(hw); + u32 v; + + v = ti_clk_ll_ops->clk_readl(clk->enable_reg); + + if (clk->flags & INVERT_ENABLE) + v ^= BIT(clk->enable_bit); + + v &= BIT(clk->enable_bit); + + return v ? 1 : 0; +} + +const struct clk_hw_omap_ops clkhwops_wait = { + .find_idlest = omap2_clk_dflt_find_idlest, + .find_companion = omap2_clk_dflt_find_companion, +}; diff --git a/drivers/clk/ti/clkt_dpll.c b/drivers/clk/ti/clkt_dpll.c new file mode 100644 index 000000000000..a01fc7f305c1 --- /dev/null +++ b/drivers/clk/ti/clkt_dpll.c @@ -0,0 +1,369 @@ +/* + * OMAP2/3/4 DPLL clock functions + * + * Copyright (C) 2005-2008 Texas Instruments, Inc. + * Copyright (C) 2004-2010 Nokia Corporation + * + * Contacts: + * Richard Woodruff <r-woodruff2@ti.com> + * 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. + */ +#undef DEBUG + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/clk/ti.h> + +#include <asm/div64.h> + +#include "clock.h" + +/* DPLL rate rounding: minimum DPLL multiplier, divider values */ +#define DPLL_MIN_MULTIPLIER 2 +#define DPLL_MIN_DIVIDER 1 + +/* Possible error results from _dpll_test_mult */ +#define DPLL_MULT_UNDERFLOW -1 + +/* + * Scale factor to mitigate roundoff errors in DPLL rate rounding. + * The higher the scale factor, the greater the risk of arithmetic overflow, + * but the closer the rounded rate to the target rate. DPLL_SCALE_FACTOR + * must be a power of DPLL_SCALE_BASE. + */ +#define DPLL_SCALE_FACTOR 64 +#define DPLL_SCALE_BASE 2 +#define DPLL_ROUNDING_VAL ((DPLL_SCALE_BASE / 2) * \ + (DPLL_SCALE_FACTOR / DPLL_SCALE_BASE)) + +/* + * DPLL valid Fint frequency range for OMAP36xx and OMAP4xxx. + * From device data manual section 4.3 "DPLL and DLL Specifications". + */ +#define OMAP3PLUS_DPLL_FINT_JTYPE_MIN 500000 +#define OMAP3PLUS_DPLL_FINT_JTYPE_MAX 2500000 + +/* _dpll_test_fint() return codes */ +#define DPLL_FINT_UNDERFLOW -1 +#define DPLL_FINT_INVALID -2 + +/* Private functions */ + +/* + * _dpll_test_fint - test whether an Fint value is valid for the DPLL + * @clk: DPLL struct clk to test + * @n: divider value (N) to test + * + * Tests whether a particular divider @n will result in a valid DPLL + * internal clock frequency Fint. See the 34xx TRM 4.7.6.2 "DPLL Jitter + * Correction". Returns 0 if OK, -1 if the enclosing loop can terminate + * (assuming that it is counting N upwards), or -2 if the enclosing loop + * should skip to the next iteration (again assuming N is increasing). + */ +static int _dpll_test_fint(struct clk_hw_omap *clk, unsigned int n) +{ + struct dpll_data *dd; + long fint, fint_min, fint_max; + int ret = 0; + + dd = clk->dpll_data; + + /* DPLL divider must result in a valid jitter correction val */ + fint = __clk_get_rate(__clk_get_parent(clk->hw.clk)) / n; + + if (dd->flags & DPLL_J_TYPE) { + fint_min = OMAP3PLUS_DPLL_FINT_JTYPE_MIN; + fint_max = OMAP3PLUS_DPLL_FINT_JTYPE_MAX; + } else { + fint_min = ti_clk_get_features()->fint_min; + fint_max = ti_clk_get_features()->fint_max; + } + + if (!fint_min || !fint_max) { + WARN(1, "No fint limits available!\n"); + return DPLL_FINT_INVALID; + } + + if (fint < ti_clk_get_features()->fint_min) { + pr_debug("rejecting n=%d due to Fint failure, lowering max_divider\n", + n); + dd->max_divider = n; + ret = DPLL_FINT_UNDERFLOW; + } else if (fint > ti_clk_get_features()->fint_max) { + pr_debug("rejecting n=%d due to Fint failure, boosting min_divider\n", + n); + dd->min_divider = n; + ret = DPLL_FINT_INVALID; + } else if (fint > ti_clk_get_features()->fint_band1_max && + fint < ti_clk_get_features()->fint_band2_min) { + pr_debug("rejecting n=%d due to Fint failure\n", n); + ret = DPLL_FINT_INVALID; + } + + return ret; +} + +static unsigned long _dpll_compute_new_rate(unsigned long parent_rate, + unsigned int m, unsigned int n) +{ + unsigned long long num; + + num = (unsigned long long)parent_rate * m; + do_div(num, n); + return num; +} + +/* + * _dpll_test_mult - test a DPLL multiplier value + * @m: pointer to the DPLL m (multiplier) value under test + * @n: current DPLL n (divider) value under test + * @new_rate: pointer to storage for the resulting rounded rate + * @target_rate: the desired DPLL rate + * @parent_rate: the DPLL's parent clock rate + * + * This code tests a DPLL multiplier value, ensuring that the + * resulting rate will not be higher than the target_rate, and that + * the multiplier value itself is valid for the DPLL. Initially, the + * integer pointed to by the m argument should be prescaled by + * multiplying by DPLL_SCALE_FACTOR. The code will replace this with + * a non-scaled m upon return. This non-scaled m will result in a + * new_rate as close as possible to target_rate (but not greater than + * target_rate) given the current (parent_rate, n, prescaled m) + * triple. Returns DPLL_MULT_UNDERFLOW in the event that the + * non-scaled m attempted to underflow, which can allow the calling + * function to bail out early; or 0 upon success. + */ +static int _dpll_test_mult(int *m, int n, unsigned long *new_rate, + unsigned long target_rate, + unsigned long parent_rate) +{ + int r = 0, carry = 0; + + /* Unscale m and round if necessary */ + if (*m % DPLL_SCALE_FACTOR >= DPLL_ROUNDING_VAL) + carry = 1; + *m = (*m / DPLL_SCALE_FACTOR) + carry; + + /* + * The new rate must be <= the target rate to avoid programming + * a rate that is impossible for the hardware to handle + */ + *new_rate = _dpll_compute_new_rate(parent_rate, *m, n); + if (*new_rate > target_rate) { + (*m)--; + *new_rate = 0; + } + + /* Guard against m underflow */ + if (*m < DPLL_MIN_MULTIPLIER) { + *m = DPLL_MIN_MULTIPLIER; + *new_rate = 0; + r = DPLL_MULT_UNDERFLOW; + } + + if (*new_rate == 0) + *new_rate = _dpll_compute_new_rate(parent_rate, *m, n); + + return r; +} + +/** + * _omap2_dpll_is_in_bypass - check if DPLL is in bypass mode or not + * @v: bitfield value of the DPLL enable + * + * Checks given DPLL enable bitfield to see whether the DPLL is in bypass + * mode or not. Returns 1 if the DPLL is in bypass, 0 otherwise. + */ +static int _omap2_dpll_is_in_bypass(u32 v) +{ + u8 mask, val; + + mask = ti_clk_get_features()->dpll_bypass_vals; + + /* + * Each set bit in the mask corresponds to a bypass value equal + * to the bitshift. Go through each set-bit in the mask and + * compare against the given register value. + */ + while (mask) { + val = __ffs(mask); + mask ^= (1 << val); + if (v == val) + return 1; + } + + return 0; +} + +/* Public functions */ +u8 omap2_init_dpll_parent(struct clk_hw *hw) +{ + struct clk_hw_omap *clk = to_clk_hw_omap(hw); + u32 v; + struct dpll_data *dd; + + dd = clk->dpll_data; + if (!dd) + return -EINVAL; + + v = ti_clk_ll_ops->clk_readl(dd->control_reg); + v &= dd->enable_mask; + v >>= __ffs(dd->enable_mask); + + /* Reparent the struct clk in case the dpll is in bypass */ + if (_omap2_dpll_is_in_bypass(v)) + return 1; + + return 0; +} + +/** + * omap2_get_dpll_rate - returns the current DPLL CLKOUT rate + * @clk: struct clk * of a DPLL + * + * DPLLs can be locked or bypassed - basically, enabled or disabled. + * When locked, the DPLL output depends on the M and N values. When + * bypassed, on OMAP2xxx, the output rate is either the 32KiHz clock + * or sys_clk. Bypass rates on OMAP3 depend on the DPLL: DPLLs 1 and + * 2 are bypassed with dpll1_fclk and dpll2_fclk respectively + * (generated by DPLL3), while DPLL 3, 4, and 5 bypass rates are sys_clk. + * Returns the current DPLL CLKOUT rate (*not* CLKOUTX2) if the DPLL is + * locked, or the appropriate bypass rate if the DPLL is bypassed, or 0 + * if the clock @clk is not a DPLL. + */ +unsigned long omap2_get_dpll_rate(struct clk_hw_omap *clk) +{ + long long dpll_clk; + u32 dpll_mult, dpll_div, v; + struct dpll_data *dd; + + dd = clk->dpll_data; + if (!dd) + return 0; + + /* Return bypass rate if DPLL is bypassed */ + v = ti_clk_ll_ops->clk_readl(dd->control_reg); + v &= dd->enable_mask; + v >>= __ffs(dd->enable_mask); + + if (_omap2_dpll_is_in_bypass(v)) + return __clk_get_rate(dd->clk_bypass); + + v = ti_clk_ll_ops->clk_readl(dd->mult_div1_reg); + dpll_mult = v & dd->mult_mask; + dpll_mult >>= __ffs(dd->mult_mask); + dpll_div = v & dd->div1_mask; + dpll_div >>= __ffs(dd->div1_mask); + + dpll_clk = (long long)__clk_get_rate(dd->clk_ref) * dpll_mult; + do_div(dpll_clk, dpll_div + 1); + + return dpll_clk; +} + +/* DPLL rate rounding code */ + +/** + * omap2_dpll_round_rate - round a target rate for an OMAP DPLL + * @clk: struct clk * for a DPLL + * @target_rate: desired DPLL clock rate + * + * Given a DPLL and a desired target rate, round the target rate to a + * possible, programmable rate for this DPLL. Attempts to select the + * minimum possible n. Stores the computed (m, n) in the DPLL's + * dpll_data structure so set_rate() will not need to call this + * (expensive) function again. Returns ~0 if the target rate cannot + * be rounded, or the rounded rate upon success. + */ +long omap2_dpll_round_rate(struct clk_hw *hw, unsigned long target_rate, + unsigned long *parent_rate) +{ + struct clk_hw_omap *clk = to_clk_hw_omap(hw); + int m, n, r, scaled_max_m; + int min_delta_m = INT_MAX, min_delta_n = INT_MAX; + unsigned long scaled_rt_rp; + unsigned long new_rate = 0; + struct dpll_data *dd; + unsigned long ref_rate; + long delta; + long prev_min_delta = LONG_MAX; + const char *clk_name; + + if (!clk || !clk->dpll_data) + return ~0; + + dd = clk->dpll_data; + + ref_rate = __clk_get_rate(dd->clk_ref); + clk_name = __clk_get_name(hw->clk); + pr_debug("clock: %s: starting DPLL round_rate, target rate %lu\n", + clk_name, target_rate); + + scaled_rt_rp = target_rate / (ref_rate / DPLL_SCALE_FACTOR); + scaled_max_m = dd->max_multiplier * DPLL_SCALE_FACTOR; + + dd->last_rounded_rate = 0; + + for (n = dd->min_divider; n <= dd->max_divider; n++) { + /* Is the (input clk, divider) pair valid for the DPLL? */ + r = _dpll_test_fint(clk, n); + if (r == DPLL_FINT_UNDERFLOW) + break; + else if (r == DPLL_FINT_INVALID) + continue; + + /* Compute the scaled DPLL multiplier, based on the divider */ + m = scaled_rt_rp * n; + + /* + * Since we're counting n up, a m overflow means we + * can bail out completely (since as n increases in + * the next iteration, there's no way that m can + * increase beyond the current m) + */ + if (m > scaled_max_m) + break; + + r = _dpll_test_mult(&m, n, &new_rate, target_rate, + ref_rate); + + /* m can't be set low enough for this n - try with a larger n */ + if (r == DPLL_MULT_UNDERFLOW) + continue; + + /* skip rates above our target rate */ + delta = target_rate - new_rate; + if (delta < 0) + continue; + + if (delta < prev_min_delta) { + prev_min_delta = delta; + min_delta_m = m; + min_delta_n = n; + } + + pr_debug("clock: %s: m = %d: n = %d: new_rate = %lu\n", + clk_name, m, n, new_rate); + + if (delta == 0) + break; + } + + if (prev_min_delta == LONG_MAX) { + pr_debug("clock: %s: cannot round to rate %lu\n", + clk_name, target_rate); + return ~0; + } + + dd->last_rounded_m = min_delta_m; + dd->last_rounded_n = min_delta_n; + dd->last_rounded_rate = target_rate - prev_min_delta; + + return dd->last_rounded_rate; +} diff --git a/drivers/clk/ti/clkt_iclk.c b/drivers/clk/ti/clkt_iclk.c new file mode 100644 index 000000000000..38c36908cf88 --- /dev/null +++ b/drivers/clk/ti/clkt_iclk.c @@ -0,0 +1,101 @@ +/* + * OMAP2/3 interface clock control + * + * Copyright (C) 2011 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. + */ +#undef DEBUG + +#include <linux/kernel.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/clk/ti.h> + +#include "clock.h" + +/* Register offsets */ +#define OMAP24XX_CM_FCLKEN2 0x04 +#define CM_AUTOIDLE 0x30 +#define CM_ICLKEN 0x10 +#define CM_IDLEST 0x20 + +#define OMAP24XX_CM_IDLEST_VAL 0 + +/* Private functions */ + +/* XXX */ +void omap2_clkt_iclk_allow_idle(struct clk_hw_omap *clk) +{ + u32 v; + void __iomem *r; + + r = (__force void __iomem *) + ((__force u32)clk->enable_reg ^ (CM_AUTOIDLE ^ CM_ICLKEN)); + + v = ti_clk_ll_ops->clk_readl(r); + v |= (1 << clk->enable_bit); + ti_clk_ll_ops->clk_writel(v, r); +} + +/* XXX */ +void omap2_clkt_iclk_deny_idle(struct clk_hw_omap *clk) +{ + u32 v; + void __iomem *r; + + r = (__force void __iomem *) + ((__force u32)clk->enable_reg ^ (CM_AUTOIDLE ^ CM_ICLKEN)); + + v = ti_clk_ll_ops->clk_readl(r); + v &= ~(1 << clk->enable_bit); + ti_clk_ll_ops->clk_writel(v, r); +} + +/** + * omap2430_clk_i2chs_find_idlest - return CM_IDLEST info for 2430 I2CHS + * @clk: struct clk * being enabled + * @idlest_reg: void __iomem ** to store CM_IDLEST reg address into + * @idlest_bit: pointer to a u8 to store the CM_IDLEST bit shift into + * @idlest_val: pointer to a u8 to store the CM_IDLEST indicator + * + * OMAP2430 I2CHS CM_IDLEST bits are in CM_IDLEST1_CORE, but the + * CM_*CLKEN bits are in CM_{I,F}CLKEN2_CORE. This custom function + * passes back the correct CM_IDLEST register address for I2CHS + * modules. No return value. + */ +static void omap2430_clk_i2chs_find_idlest(struct clk_hw_omap *clk, + void __iomem **idlest_reg, + u8 *idlest_bit, + u8 *idlest_val) +{ + u32 r; + + r = ((__force u32)clk->enable_reg ^ (OMAP24XX_CM_FCLKEN2 ^ CM_IDLEST)); + *idlest_reg = (__force void __iomem *)r; + *idlest_bit = clk->enable_bit; + *idlest_val = OMAP24XX_CM_IDLEST_VAL; +} + +/* Public data */ + +const struct clk_hw_omap_ops clkhwops_iclk = { + .allow_idle = omap2_clkt_iclk_allow_idle, + .deny_idle = omap2_clkt_iclk_deny_idle, +}; + +const struct clk_hw_omap_ops clkhwops_iclk_wait = { + .allow_idle = omap2_clkt_iclk_allow_idle, + .deny_idle = omap2_clkt_iclk_deny_idle, + .find_idlest = omap2_clk_dflt_find_idlest, + .find_companion = omap2_clk_dflt_find_companion, +}; + +/* 2430 I2CHS has non-standard IDLEST register */ +const struct clk_hw_omap_ops clkhwops_omap2430_i2chs_wait = { + .find_idlest = omap2430_clk_i2chs_find_idlest, + .find_companion = omap2_clk_dflt_find_companion, +}; diff --git a/drivers/clk/ti/clock.h b/drivers/clk/ti/clock.h index 404158d2d7f8..d8aafd333058 100644 --- a/drivers/clk/ti/clock.h +++ b/drivers/clk/ti/clock.h @@ -154,6 +154,35 @@ struct ti_clk_dpll { u8 recal_st_bit; }; +/* Composite clock component types */ +enum { + CLK_COMPONENT_TYPE_GATE = 0, + CLK_COMPONENT_TYPE_DIVIDER, + CLK_COMPONENT_TYPE_MUX, + CLK_COMPONENT_TYPE_MAX, +}; + +/** + * struct ti_dt_clk - OMAP DT clock alias declarations + * @lk: clock lookup definition + * @node_name: clock DT node to map to + */ +struct ti_dt_clk { + struct clk_lookup lk; + char *node_name; +}; + +#define DT_CLK(dev, con, name) \ + { \ + .lk = { \ + .dev_id = dev, \ + .con_id = con, \ + }, \ + .node_name = name, \ + } + +typedef void (*ti_of_clk_init_cb_t)(struct clk_hw *, struct device_node *); + struct clk *ti_clk_register_gate(struct ti_clk *setup); struct clk *ti_clk_register_interface(struct ti_clk *setup); struct clk *ti_clk_register_mux(struct ti_clk *setup); @@ -169,4 +198,80 @@ void ti_clk_patch_legacy_clks(struct ti_clk **patch); struct clk *ti_clk_register_clk(struct ti_clk *setup); int ti_clk_register_legacy_clks(struct ti_clk_alias *clks); +void __iomem *ti_clk_get_reg_addr(struct device_node *node, int index); +void ti_dt_clocks_register(struct ti_dt_clk *oclks); +int ti_clk_retry_init(struct device_node *node, struct clk_hw *hw, + ti_of_clk_init_cb_t func); +int ti_clk_add_component(struct device_node *node, struct clk_hw *hw, int type); + +void omap2_init_clk_hw_omap_clocks(struct clk *clk); +int of_ti_clk_autoidle_setup(struct device_node *node); +void omap2_clk_enable_init_clocks(const char **clk_names, u8 num_clocks); + +extern const struct clk_hw_omap_ops clkhwops_omap3_dpll; +extern const struct clk_hw_omap_ops clkhwops_omap4_dpllmx; +extern const struct clk_hw_omap_ops clkhwops_wait; +extern const struct clk_hw_omap_ops clkhwops_iclk; +extern const struct clk_hw_omap_ops clkhwops_iclk_wait; +extern const struct clk_hw_omap_ops clkhwops_omap2430_i2chs_wait; +extern const struct clk_hw_omap_ops clkhwops_omap3430es2_dss_usbhost_wait; +extern const struct clk_hw_omap_ops clkhwops_omap3430es2_iclk_hsotgusb_wait; +extern const struct clk_hw_omap_ops clkhwops_omap3430es2_iclk_dss_usbhost_wait; +extern const struct clk_hw_omap_ops clkhwops_omap3430es2_iclk_ssi_wait; +extern const struct clk_hw_omap_ops clkhwops_am35xx_ipss_module_wait; +extern const struct clk_hw_omap_ops clkhwops_am35xx_ipss_wait; + +extern const struct clk_ops ti_clk_divider_ops; +extern const struct clk_ops ti_clk_mux_ops; + +int omap2_clkops_enable_clkdm(struct clk_hw *hw); +void omap2_clkops_disable_clkdm(struct clk_hw *hw); + +int omap2_dflt_clk_enable(struct clk_hw *hw); +void omap2_dflt_clk_disable(struct clk_hw *hw); +int omap2_dflt_clk_is_enabled(struct clk_hw *hw); +void omap2_clk_dflt_find_companion(struct clk_hw_omap *clk, + void __iomem **other_reg, + u8 *other_bit); +void omap2_clk_dflt_find_idlest(struct clk_hw_omap *clk, + void __iomem **idlest_reg, + u8 *idlest_bit, u8 *idlest_val); + +void omap2_clkt_iclk_allow_idle(struct clk_hw_omap *clk); +void omap2_clkt_iclk_deny_idle(struct clk_hw_omap *clk); + +u8 omap2_init_dpll_parent(struct clk_hw *hw); +int omap3_noncore_dpll_enable(struct clk_hw *hw); +void omap3_noncore_dpll_disable(struct clk_hw *hw); +int omap3_noncore_dpll_set_parent(struct clk_hw *hw, u8 index); +int omap3_noncore_dpll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate); +int omap3_noncore_dpll_set_rate_and_parent(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate, + u8 index); +int omap3_noncore_dpll_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req); +long omap2_dpll_round_rate(struct clk_hw *hw, unsigned long target_rate, + unsigned long *parent_rate); +unsigned long omap3_clkoutx2_recalc(struct clk_hw *hw, + unsigned long parent_rate); + +unsigned long omap3_dpll_recalc(struct clk_hw *hw, unsigned long parent_rate); +int omap3_dpll4_set_rate(struct clk_hw *clk, unsigned long rate, + unsigned long parent_rate); +int omap3_dpll4_set_rate_and_parent(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate, u8 index); +void omap3_clk_lock_dpll5(void); + +unsigned long omap4_dpll_regm4xen_recalc(struct clk_hw *hw, + unsigned long parent_rate); +long omap4_dpll_regm4xen_round_rate(struct clk_hw *hw, + unsigned long target_rate, + unsigned long *parent_rate); +int omap4_dpll_regm4xen_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req); + +extern struct ti_clk_ll_ops *ti_clk_ll_ops; + #endif diff --git a/drivers/clk/ti/clockdomain.c b/drivers/clk/ti/clockdomain.c index b82ef07f3403..fc517453321b 100644 --- a/drivers/clk/ti/clockdomain.c +++ b/drivers/clk/ti/clockdomain.c @@ -21,9 +21,87 @@ #include <linux/of_address.h> #include <linux/clk/ti.h> +#include "clock.h" + #undef pr_fmt #define pr_fmt(fmt) "%s: " fmt, __func__ +/** + * omap2_clkops_enable_clkdm - increment usecount on clkdm of @hw + * @hw: struct clk_hw * of the clock being enabled + * + * 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. + */ +int omap2_clkops_enable_clkdm(struct clk_hw *hw) +{ + struct clk_hw_omap *clk; + int ret = 0; + + clk = to_clk_hw_omap(hw); + + if (unlikely(!clk->clkdm)) { + pr_err("%s: %s: no clkdm set ?!\n", __func__, + __clk_get_name(hw->clk)); + return -EINVAL; + } + + if (unlikely(clk->enable_reg)) + pr_err("%s: %s: should use dflt_clk_enable ?!\n", __func__, + __clk_get_name(hw->clk)); + + if (ti_clk_get_features()->flags & TI_CLK_DISABLE_CLKDM_CONTROL) { + pr_err("%s: %s: clkfw-based clockdomain control disabled ?!\n", + __func__, __clk_get_name(hw->clk)); + return 0; + } + + ret = ti_clk_ll_ops->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); + + return ret; +} + +/** + * omap2_clkops_disable_clkdm - decrement usecount on clkdm of @hw + * @hw: struct clk_hw * of the clock being disabled + * + * 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. + */ +void omap2_clkops_disable_clkdm(struct clk_hw *hw) +{ + struct clk_hw_omap *clk; + + clk = to_clk_hw_omap(hw); + + if (unlikely(!clk->clkdm)) { + pr_err("%s: %s: no clkdm set ?!\n", __func__, + __clk_get_name(hw->clk)); + return; + } + + if (unlikely(clk->enable_reg)) + pr_err("%s: %s: should use dflt_clk_disable ?!\n", __func__, + __clk_get_name(hw->clk)); + + if (ti_clk_get_features()->flags & TI_CLK_DISABLE_CLKDM_CONTROL) { + pr_err("%s: %s: clkfw-based clockdomain control disabled ?!\n", + __func__, __clk_get_name(hw->clk)); + return; + } + + ti_clk_ll_ops->clkdm_clk_disable(clk->clkdm, hw->clk); +} + static void __init of_ti_clockdomain_setup(struct device_node *node) { struct clk *clk; diff --git a/drivers/clk/ti/dpll3xxx.c b/drivers/clk/ti/dpll3xxx.c new file mode 100644 index 000000000000..b0aa87b35f24 --- /dev/null +++ b/drivers/clk/ti/dpll3xxx.c @@ -0,0 +1,820 @@ +/* + * OMAP3/4 - specific DPLL control functions + * + * Copyright (C) 2009-2010 Texas Instruments, Inc. + * Copyright (C) 2009-2010 Nokia Corporation + * + * Written by Paul Walmsley + * Testing and integration fixes by Jouni Högander + * + * 36xx support added by Vishwanath BS, Richard Woodruff, and Nishanth + * Menon + * + * Parts of this code are based on code written by + * Richard Woodruff, Tony Lindgren, Tuukka Tikkanen, Karthik Dasu + * + * 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 <linux/kernel.h> +#include <linux/device.h> +#include <linux/list.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/clkdev.h> +#include <linux/clk/ti.h> + +#include "clock.h" + +/* CM_AUTOIDLE_PLL*.AUTO_* bit values */ +#define DPLL_AUTOIDLE_DISABLE 0x0 +#define DPLL_AUTOIDLE_LOW_POWER_STOP 0x1 + +#define MAX_DPLL_WAIT_TRIES 1000000 + +#define OMAP3XXX_EN_DPLL_LOCKED 0x7 + +/* Forward declarations */ +static u32 omap3_dpll_autoidle_read(struct clk_hw_omap *clk); +static void omap3_dpll_deny_idle(struct clk_hw_omap *clk); +static void omap3_dpll_allow_idle(struct clk_hw_omap *clk); + +/* Private functions */ + +/* _omap3_dpll_write_clken - write clken_bits arg to a DPLL's enable bits */ +static void _omap3_dpll_write_clken(struct clk_hw_omap *clk, u8 clken_bits) +{ + const struct dpll_data *dd; + u32 v; + + dd = clk->dpll_data; + + v = ti_clk_ll_ops->clk_readl(dd->control_reg); + v &= ~dd->enable_mask; + v |= clken_bits << __ffs(dd->enable_mask); + ti_clk_ll_ops->clk_writel(v, dd->control_reg); +} + +/* _omap3_wait_dpll_status: wait for a DPLL to enter a specific state */ +static int _omap3_wait_dpll_status(struct clk_hw_omap *clk, u8 state) +{ + const struct dpll_data *dd; + int i = 0; + int ret = -EINVAL; + const char *clk_name; + + dd = clk->dpll_data; + clk_name = __clk_get_name(clk->hw.clk); + + state <<= __ffs(dd->idlest_mask); + + while (((ti_clk_ll_ops->clk_readl(dd->idlest_reg) & dd->idlest_mask) + != state) && i < MAX_DPLL_WAIT_TRIES) { + i++; + udelay(1); + } + + if (i == MAX_DPLL_WAIT_TRIES) { + pr_err("clock: %s failed transition to '%s'\n", + clk_name, (state) ? "locked" : "bypassed"); + } else { + pr_debug("clock: %s transition to '%s' in %d loops\n", + clk_name, (state) ? "locked" : "bypassed", i); + + ret = 0; + } + + return ret; +} + +/* From 3430 TRM ES2 4.7.6.2 */ +static u16 _omap3_dpll_compute_freqsel(struct clk_hw_omap *clk, u8 n) +{ + unsigned long fint; + u16 f = 0; + + fint = __clk_get_rate(clk->dpll_data->clk_ref) / n; + + pr_debug("clock: fint is %lu\n", fint); + + if (fint >= 750000 && fint <= 1000000) + f = 0x3; + else if (fint > 1000000 && fint <= 1250000) + f = 0x4; + else if (fint > 1250000 && fint <= 1500000) + f = 0x5; + else if (fint > 1500000 && fint <= 1750000) + f = 0x6; + else if (fint > 1750000 && fint <= 2100000) + f = 0x7; + else if (fint > 7500000 && fint <= 10000000) + f = 0xB; + else if (fint > 10000000 && fint <= 12500000) + f = 0xC; + else if (fint > 12500000 && fint <= 15000000) + f = 0xD; + else if (fint > 15000000 && fint <= 17500000) + f = 0xE; + else if (fint > 17500000 && fint <= 21000000) + f = 0xF; + else + pr_debug("clock: unknown freqsel setting for %d\n", n); + + return f; +} + +/* + * _omap3_noncore_dpll_lock - instruct a DPLL to lock and wait for readiness + * @clk: pointer to a DPLL struct clk + * + * Instructs a non-CORE DPLL to lock. Waits for the DPLL to report + * readiness before returning. Will save and restore the DPLL's + * autoidle state across the enable, per the CDP code. If the DPLL + * locked successfully, return 0; if the DPLL did not lock in the time + * allotted, or DPLL3 was passed in, return -EINVAL. + */ +static int _omap3_noncore_dpll_lock(struct clk_hw_omap *clk) +{ + const struct dpll_data *dd; + u8 ai; + u8 state = 1; + int r = 0; + + pr_debug("clock: locking DPLL %s\n", __clk_get_name(clk->hw.clk)); + + dd = clk->dpll_data; + state <<= __ffs(dd->idlest_mask); + + /* Check if already locked */ + if ((ti_clk_ll_ops->clk_readl(dd->idlest_reg) & dd->idlest_mask) == + state) + goto done; + + ai = omap3_dpll_autoidle_read(clk); + + if (ai) + omap3_dpll_deny_idle(clk); + + _omap3_dpll_write_clken(clk, DPLL_LOCKED); + + r = _omap3_wait_dpll_status(clk, 1); + + if (ai) + omap3_dpll_allow_idle(clk); + +done: + return r; +} + +/* + * _omap3_noncore_dpll_bypass - instruct a DPLL to bypass and wait for readiness + * @clk: pointer to a DPLL struct clk + * + * Instructs a non-CORE DPLL to enter low-power bypass mode. In + * bypass mode, the DPLL's rate is set equal to its parent clock's + * rate. Waits for the DPLL to report readiness before returning. + * Will save and restore the DPLL's autoidle state across the enable, + * per the CDP code. If the DPLL entered bypass mode successfully, + * return 0; if the DPLL did not enter bypass in the time allotted, or + * DPLL3 was passed in, or the DPLL does not support low-power bypass, + * return -EINVAL. + */ +static int _omap3_noncore_dpll_bypass(struct clk_hw_omap *clk) +{ + int r; + u8 ai; + + if (!(clk->dpll_data->modes & (1 << DPLL_LOW_POWER_BYPASS))) + return -EINVAL; + + pr_debug("clock: configuring DPLL %s for low-power bypass\n", + __clk_get_name(clk->hw.clk)); + + ai = omap3_dpll_autoidle_read(clk); + + _omap3_dpll_write_clken(clk, DPLL_LOW_POWER_BYPASS); + + r = _omap3_wait_dpll_status(clk, 0); + + if (ai) + omap3_dpll_allow_idle(clk); + + return r; +} + +/* + * _omap3_noncore_dpll_stop - instruct a DPLL to stop + * @clk: pointer to a DPLL struct clk + * + * Instructs a non-CORE DPLL to enter low-power stop. Will save and + * restore the DPLL's autoidle state across the stop, per the CDP + * code. If DPLL3 was passed in, or the DPLL does not support + * low-power stop, return -EINVAL; otherwise, return 0. + */ +static int _omap3_noncore_dpll_stop(struct clk_hw_omap *clk) +{ + u8 ai; + + if (!(clk->dpll_data->modes & (1 << DPLL_LOW_POWER_STOP))) + return -EINVAL; + + pr_debug("clock: stopping DPLL %s\n", __clk_get_name(clk->hw.clk)); + + ai = omap3_dpll_autoidle_read(clk); + + _omap3_dpll_write_clken(clk, DPLL_LOW_POWER_STOP); + + if (ai) + omap3_dpll_allow_idle(clk); + + return 0; +} + +/** + * _lookup_dco - Lookup DCO used by j-type DPLL + * @clk: pointer to a DPLL struct clk + * @dco: digital control oscillator selector + * @m: DPLL multiplier to set + * @n: DPLL divider to set + * + * See 36xx TRM section 3.5.3.3.3.2 "Type B DPLL (Low-Jitter)" + * + * XXX This code is not needed for 3430/AM35xx; can it be optimized + * out in non-multi-OMAP builds for those chips? + */ +static void _lookup_dco(struct clk_hw_omap *clk, u8 *dco, u16 m, u8 n) +{ + unsigned long fint, clkinp; /* watch out for overflow */ + + clkinp = __clk_get_rate(__clk_get_parent(clk->hw.clk)); + fint = (clkinp / n) * m; + + if (fint < 1000000000) + *dco = 2; + else + *dco = 4; +} + +/** + * _lookup_sddiv - Calculate sigma delta divider for j-type DPLL + * @clk: pointer to a DPLL struct clk + * @sd_div: target sigma-delta divider + * @m: DPLL multiplier to set + * @n: DPLL divider to set + * + * See 36xx TRM section 3.5.3.3.3.2 "Type B DPLL (Low-Jitter)" + * + * XXX This code is not needed for 3430/AM35xx; can it be optimized + * out in non-multi-OMAP builds for those chips? + */ +static void _lookup_sddiv(struct clk_hw_omap *clk, u8 *sd_div, u16 m, u8 n) +{ + unsigned long clkinp, sd; /* watch out for overflow */ + int mod1, mod2; + + clkinp = __clk_get_rate(__clk_get_parent(clk->hw.clk)); + + /* + * target sigma-delta to near 250MHz + * sd = ceil[(m/(n+1)) * (clkinp_MHz / 250)] + */ + clkinp /= 100000; /* shift from MHz to 10*Hz for 38.4 and 19.2 */ + mod1 = (clkinp * m) % (250 * n); + sd = (clkinp * m) / (250 * n); + mod2 = sd % 10; + sd /= 10; + + if (mod1 || mod2) + sd++; + *sd_div = sd; +} + +/* + * _omap3_noncore_dpll_program - set non-core DPLL M,N values directly + * @clk: struct clk * of DPLL to set + * @freqsel: FREQSEL value to set + * + * Program the DPLL with the last M, N values calculated, and wait for + * the DPLL to lock. Returns -EINVAL upon error, or 0 upon success. + */ +static int omap3_noncore_dpll_program(struct clk_hw_omap *clk, u16 freqsel) +{ + struct dpll_data *dd = clk->dpll_data; + u8 dco, sd_div; + u32 v; + + /* 3430 ES2 TRM: 4.7.6.9 DPLL Programming Sequence */ + _omap3_noncore_dpll_bypass(clk); + + /* + * Set jitter correction. Jitter correction applicable for OMAP343X + * only since freqsel field is no longer present on other devices. + */ + if (ti_clk_get_features()->flags & TI_CLK_DPLL_HAS_FREQSEL) { + v = ti_clk_ll_ops->clk_readl(dd->control_reg); + v &= ~dd->freqsel_mask; + v |= freqsel << __ffs(dd->freqsel_mask); + ti_clk_ll_ops->clk_writel(v, dd->control_reg); + } + + /* Set DPLL multiplier, divider */ + v = ti_clk_ll_ops->clk_readl(dd->mult_div1_reg); + + /* Handle Duty Cycle Correction */ + if (dd->dcc_mask) { + if (dd->last_rounded_rate >= dd->dcc_rate) + v |= dd->dcc_mask; /* Enable DCC */ + else + v &= ~dd->dcc_mask; /* Disable DCC */ + } + + v &= ~(dd->mult_mask | dd->div1_mask); + v |= dd->last_rounded_m << __ffs(dd->mult_mask); + v |= (dd->last_rounded_n - 1) << __ffs(dd->div1_mask); + + /* Configure dco and sd_div for dplls that have these fields */ + if (dd->dco_mask) { + _lookup_dco(clk, &dco, dd->last_rounded_m, dd->last_rounded_n); + v &= ~(dd->dco_mask); + v |= dco << __ffs(dd->dco_mask); + } + if (dd->sddiv_mask) { + _lookup_sddiv(clk, &sd_div, dd->last_rounded_m, + dd->last_rounded_n); + v &= ~(dd->sddiv_mask); + v |= sd_div << __ffs(dd->sddiv_mask); + } + + ti_clk_ll_ops->clk_writel(v, dd->mult_div1_reg); + + /* Set 4X multiplier and low-power mode */ + if (dd->m4xen_mask || dd->lpmode_mask) { + v = ti_clk_ll_ops->clk_readl(dd->control_reg); + + if (dd->m4xen_mask) { + if (dd->last_rounded_m4xen) + v |= dd->m4xen_mask; + else + v &= ~dd->m4xen_mask; + } + + if (dd->lpmode_mask) { + if (dd->last_rounded_lpmode) + v |= dd->lpmode_mask; + else + v &= ~dd->lpmode_mask; + } + + ti_clk_ll_ops->clk_writel(v, dd->control_reg); + } + + /* We let the clock framework set the other output dividers later */ + + /* REVISIT: Set ramp-up delay? */ + + _omap3_noncore_dpll_lock(clk); + + return 0; +} + +/* Public functions */ + +/** + * omap3_dpll_recalc - recalculate DPLL rate + * @clk: DPLL struct clk + * + * Recalculate and propagate the DPLL rate. + */ +unsigned long omap3_dpll_recalc(struct clk_hw *hw, unsigned long parent_rate) +{ + struct clk_hw_omap *clk = to_clk_hw_omap(hw); + + return omap2_get_dpll_rate(clk); +} + +/* Non-CORE DPLL (e.g., DPLLs that do not control SDRC) clock functions */ + +/** + * omap3_noncore_dpll_enable - instruct a DPLL to enter bypass or lock mode + * @clk: pointer to a DPLL struct clk + * + * Instructs a non-CORE DPLL to enable, e.g., to enter bypass or lock. + * The choice of modes depends on the DPLL's programmed rate: if it is + * the same as the DPLL's parent clock, it will enter bypass; + * otherwise, it will enter lock. This code will wait for the DPLL to + * indicate readiness before returning, unless the DPLL takes too long + * to enter the target state. Intended to be used as the struct clk's + * enable function. If DPLL3 was passed in, or the DPLL does not + * support low-power stop, or if the DPLL took too long to enter + * bypass or lock, return -EINVAL; otherwise, return 0. + */ +int omap3_noncore_dpll_enable(struct clk_hw *hw) +{ + struct clk_hw_omap *clk = to_clk_hw_omap(hw); + int r; + struct dpll_data *dd; + struct clk_hw *parent; + + dd = clk->dpll_data; + if (!dd) + return -EINVAL; + + if (clk->clkdm) { + r = ti_clk_ll_ops->clkdm_clk_enable(clk->clkdm, hw->clk); + if (r) { + WARN(1, + "%s: could not enable %s's clockdomain %s: %d\n", + __func__, __clk_get_name(hw->clk), + clk->clkdm_name, r); + return r; + } + } + + parent = __clk_get_hw(__clk_get_parent(hw->clk)); + + if (__clk_get_rate(hw->clk) == __clk_get_rate(dd->clk_bypass)) { + WARN_ON(parent != __clk_get_hw(dd->clk_bypass)); + r = _omap3_noncore_dpll_bypass(clk); + } else { + WARN_ON(parent != __clk_get_hw(dd->clk_ref)); + r = _omap3_noncore_dpll_lock(clk); + } + + return r; +} + +/** + * omap3_noncore_dpll_disable - instruct a DPLL to enter low-power stop + * @clk: pointer to a DPLL struct clk + * + * Instructs a non-CORE DPLL to enter low-power stop. This function is + * intended for use in struct clkops. No return value. + */ +void omap3_noncore_dpll_disable(struct clk_hw *hw) +{ + struct clk_hw_omap *clk = to_clk_hw_omap(hw); + + _omap3_noncore_dpll_stop(clk); + if (clk->clkdm) + ti_clk_ll_ops->clkdm_clk_disable(clk->clkdm, hw->clk); +} + +/* Non-CORE DPLL rate set code */ + +/** + * omap3_noncore_dpll_determine_rate - determine rate for a DPLL + * @hw: pointer to the clock to determine rate for + * @req: target rate request + * + * Determines which DPLL mode to use for reaching a desired target rate. + * Checks whether the DPLL shall be in bypass or locked mode, and if + * locked, calculates the M,N values for the DPLL via round-rate. + * Returns a 0 on success, negative error value in failure. + */ +int omap3_noncore_dpll_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_hw_omap *clk = to_clk_hw_omap(hw); + struct dpll_data *dd; + + if (!req->rate) + return -EINVAL; + + dd = clk->dpll_data; + if (!dd) + return -EINVAL; + + if (__clk_get_rate(dd->clk_bypass) == req->rate && + (dd->modes & (1 << DPLL_LOW_POWER_BYPASS))) { + req->best_parent_hw = __clk_get_hw(dd->clk_bypass); + } else { + req->rate = omap2_dpll_round_rate(hw, req->rate, + &req->best_parent_rate); + req->best_parent_hw = __clk_get_hw(dd->clk_ref); + } + + req->best_parent_rate = req->rate; + + return 0; +} + +/** + * omap3_noncore_dpll_set_parent - set parent for a DPLL clock + * @hw: pointer to the clock to set parent for + * @index: parent index to select + * + * Sets parent for a DPLL clock. This sets the DPLL into bypass or + * locked mode. Returns 0 with success, negative error value otherwise. + */ +int omap3_noncore_dpll_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_hw_omap *clk = to_clk_hw_omap(hw); + int ret; + + if (!hw) + return -EINVAL; + + if (index) + ret = _omap3_noncore_dpll_bypass(clk); + else + ret = _omap3_noncore_dpll_lock(clk); + + return ret; +} + +/** + * omap3_noncore_dpll_set_rate - set rate for a DPLL clock + * @hw: pointer to the clock to set parent for + * @rate: target rate for the clock + * @parent_rate: rate of the parent clock + * + * Sets rate for a DPLL clock. First checks if the clock parent is + * reference clock (in bypass mode, the rate of the clock can't be + * changed) and proceeds with the rate change operation. Returns 0 + * with success, negative error value otherwise. + */ +int omap3_noncore_dpll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_hw_omap *clk = to_clk_hw_omap(hw); + struct dpll_data *dd; + u16 freqsel = 0; + int ret; + + if (!hw || !rate) + return -EINVAL; + + dd = clk->dpll_data; + if (!dd) + return -EINVAL; + + if (__clk_get_hw(__clk_get_parent(hw->clk)) != + __clk_get_hw(dd->clk_ref)) + return -EINVAL; + + if (dd->last_rounded_rate == 0) + return -EINVAL; + + /* Freqsel is available only on OMAP343X devices */ + if (ti_clk_get_features()->flags & TI_CLK_DPLL_HAS_FREQSEL) { + freqsel = _omap3_dpll_compute_freqsel(clk, dd->last_rounded_n); + WARN_ON(!freqsel); + } + + pr_debug("%s: %s: set rate: locking rate to %lu.\n", __func__, + __clk_get_name(hw->clk), rate); + + ret = omap3_noncore_dpll_program(clk, freqsel); + + return ret; +} + +/** + * omap3_noncore_dpll_set_rate_and_parent - set rate and parent for a DPLL clock + * @hw: pointer to the clock to set rate and parent for + * @rate: target rate for the DPLL + * @parent_rate: clock rate of the DPLL parent + * @index: new parent index for the DPLL, 0 - reference, 1 - bypass + * + * Sets rate and parent for a DPLL clock. If new parent is the bypass + * clock, only selects the parent. Otherwise proceeds with a rate + * change, as this will effectively also change the parent as the + * DPLL is put into locked mode. Returns 0 with success, negative error + * value otherwise. + */ +int omap3_noncore_dpll_set_rate_and_parent(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate, + u8 index) +{ + int ret; + + if (!hw || !rate) + return -EINVAL; + + /* + * clk-ref at index[0], in which case we only need to set rate, + * the parent will be changed automatically with the lock sequence. + * With clk-bypass case we only need to change parent. + */ + if (index) + ret = omap3_noncore_dpll_set_parent(hw, index); + else + ret = omap3_noncore_dpll_set_rate(hw, rate, parent_rate); + + return ret; +} + +/* DPLL autoidle read/set code */ + +/** + * omap3_dpll_autoidle_read - read a DPLL's autoidle bits + * @clk: struct clk * of the DPLL to read + * + * Return the DPLL's autoidle bits, shifted down to bit 0. Returns + * -EINVAL if passed a null pointer or if the struct clk does not + * appear to refer to a DPLL. + */ +static u32 omap3_dpll_autoidle_read(struct clk_hw_omap *clk) +{ + const struct dpll_data *dd; + u32 v; + + if (!clk || !clk->dpll_data) + return -EINVAL; + + dd = clk->dpll_data; + + if (!dd->autoidle_reg) + return -EINVAL; + + v = ti_clk_ll_ops->clk_readl(dd->autoidle_reg); + v &= dd->autoidle_mask; + v >>= __ffs(dd->autoidle_mask); + + return v; +} + +/** + * omap3_dpll_allow_idle - enable DPLL autoidle bits + * @clk: struct clk * of the DPLL to operate on + * + * Enable DPLL automatic idle control. This automatic idle mode + * switching takes effect only when the DPLL is locked, at least on + * OMAP3430. The DPLL will enter low-power stop when its downstream + * clocks are gated. No return value. + */ +static void omap3_dpll_allow_idle(struct clk_hw_omap *clk) +{ + const struct dpll_data *dd; + u32 v; + + if (!clk || !clk->dpll_data) + return; + + dd = clk->dpll_data; + + if (!dd->autoidle_reg) + return; + + /* + * REVISIT: CORE DPLL can optionally enter low-power bypass + * by writing 0x5 instead of 0x1. Add some mechanism to + * optionally enter this mode. + */ + v = ti_clk_ll_ops->clk_readl(dd->autoidle_reg); + v &= ~dd->autoidle_mask; + v |= DPLL_AUTOIDLE_LOW_POWER_STOP << __ffs(dd->autoidle_mask); + ti_clk_ll_ops->clk_writel(v, dd->autoidle_reg); +} + +/** + * omap3_dpll_deny_idle - prevent DPLL from automatically idling + * @clk: struct clk * of the DPLL to operate on + * + * Disable DPLL automatic idle control. No return value. + */ +static void omap3_dpll_deny_idle(struct clk_hw_omap *clk) +{ + const struct dpll_data *dd; + u32 v; + + if (!clk || !clk->dpll_data) + return; + + dd = clk->dpll_data; + + if (!dd->autoidle_reg) + return; + + v = ti_clk_ll_ops->clk_readl(dd->autoidle_reg); + v &= ~dd->autoidle_mask; + v |= DPLL_AUTOIDLE_DISABLE << __ffs(dd->autoidle_mask); + ti_clk_ll_ops->clk_writel(v, dd->autoidle_reg); +} + +/* Clock control for DPLL outputs */ + +/* Find the parent DPLL for the given clkoutx2 clock */ +static struct clk_hw_omap *omap3_find_clkoutx2_dpll(struct clk_hw *hw) +{ + struct clk_hw_omap *pclk = NULL; + struct clk *parent; + + /* Walk up the parents of clk, looking for a DPLL */ + do { + do { + parent = __clk_get_parent(hw->clk); + hw = __clk_get_hw(parent); + } while (hw && (__clk_get_flags(hw->clk) & CLK_IS_BASIC)); + if (!hw) + break; + pclk = to_clk_hw_omap(hw); + } while (pclk && !pclk->dpll_data); + + /* clk does not have a DPLL as a parent? error in the clock data */ + if (!pclk) { + WARN_ON(1); + return NULL; + } + + return pclk; +} + +/** + * omap3_clkoutx2_recalc - recalculate DPLL X2 output virtual clock rate + * @clk: DPLL output struct clk + * + * Using parent clock DPLL data, look up DPLL state. If locked, set our + * rate to the dpll_clk * 2; otherwise, just use dpll_clk. + */ +unsigned long omap3_clkoutx2_recalc(struct clk_hw *hw, + unsigned long parent_rate) +{ + const struct dpll_data *dd; + unsigned long rate; + u32 v; + struct clk_hw_omap *pclk = NULL; + + if (!parent_rate) + return 0; + + pclk = omap3_find_clkoutx2_dpll(hw); + + if (!pclk) + return 0; + + dd = pclk->dpll_data; + + WARN_ON(!dd->enable_mask); + + v = ti_clk_ll_ops->clk_readl(dd->control_reg) & dd->enable_mask; + v >>= __ffs(dd->enable_mask); + if ((v != OMAP3XXX_EN_DPLL_LOCKED) || (dd->flags & DPLL_J_TYPE)) + rate = parent_rate; + else + rate = parent_rate * 2; + return rate; +} + +/* OMAP3/4 non-CORE DPLL clkops */ +const struct clk_hw_omap_ops clkhwops_omap3_dpll = { + .allow_idle = omap3_dpll_allow_idle, + .deny_idle = omap3_dpll_deny_idle, +}; + +/** + * omap3_dpll4_set_rate - set rate for omap3 per-dpll + * @hw: clock to change + * @rate: target rate for clock + * @parent_rate: rate of the parent clock + * + * Check if the current SoC supports the per-dpll reprogram operation + * or not, and then do the rate change if supported. Returns -EINVAL + * if not supported, 0 for success, and potential error codes from the + * clock rate change. + */ +int omap3_dpll4_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + /* + * According to the 12-5 CDP code from TI, "Limitation 2.5" + * on 3430ES1 prevents us from changing DPLL multipliers or dividers + * on DPLL4. + */ + if (ti_clk_get_features()->flags & TI_CLK_DPLL4_DENY_REPROGRAM) { + pr_err("clock: DPLL4 cannot change rate due to silicon 'Limitation 2.5' on 3430ES1.\n"); + return -EINVAL; + } + + return omap3_noncore_dpll_set_rate(hw, rate, parent_rate); +} + +/** + * omap3_dpll4_set_rate_and_parent - set rate and parent for omap3 per-dpll + * @hw: clock to change + * @rate: target rate for clock + * @parent_rate: rate of the parent clock + * @index: parent index, 0 - reference clock, 1 - bypass clock + * + * Check if the current SoC support the per-dpll reprogram operation + * or not, and then do the rate + parent change if supported. Returns + * -EINVAL if not supported, 0 for success, and potential error codes + * from the clock rate change. + */ +int omap3_dpll4_set_rate_and_parent(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate, u8 index) +{ + if (ti_clk_get_features()->flags & TI_CLK_DPLL4_DENY_REPROGRAM) { + pr_err("clock: DPLL4 cannot change rate due to silicon 'Limitation 2.5' on 3430ES1.\n"); + return -EINVAL; + } + + return omap3_noncore_dpll_set_rate_and_parent(hw, rate, parent_rate, + index); +} diff --git a/drivers/clk/ti/dpll44xx.c b/drivers/clk/ti/dpll44xx.c new file mode 100644 index 000000000000..73af77a90586 --- /dev/null +++ b/drivers/clk/ti/dpll44xx.c @@ -0,0 +1,227 @@ +/* + * OMAP4-specific DPLL control functions + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Rajendra Nayak + * + * 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 <linux/kernel.h> +#include <linux/errno.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/clk/ti.h> + +#include "clock.h" + +/* + * Maximum DPLL input frequency (FINT) and output frequency (FOUT) that + * can supported when using the DPLL low-power mode. Frequencies are + * defined in OMAP4430/60 Public TRM section 3.6.3.3.2 "Enable Control, + * Status, and Low-Power Operation Mode". + */ +#define OMAP4_DPLL_LP_FINT_MAX 1000000 +#define OMAP4_DPLL_LP_FOUT_MAX 100000000 + +/* + * Bitfield declarations + */ +#define OMAP4430_DPLL_CLKOUT_GATE_CTRL_MASK BIT(8) +#define OMAP4430_DPLL_CLKOUTX2_GATE_CTRL_MASK BIT(10) +#define OMAP4430_DPLL_REGM4XEN_MASK BIT(11) + +/* Static rate multiplier for OMAP4 REGM4XEN clocks */ +#define OMAP4430_REGM4XEN_MULT 4 + +static void omap4_dpllmx_allow_gatectrl(struct clk_hw_omap *clk) +{ + u32 v; + u32 mask; + + if (!clk || !clk->clksel_reg) + return; + + mask = clk->flags & CLOCK_CLKOUTX2 ? + OMAP4430_DPLL_CLKOUTX2_GATE_CTRL_MASK : + OMAP4430_DPLL_CLKOUT_GATE_CTRL_MASK; + + v = ti_clk_ll_ops->clk_readl(clk->clksel_reg); + /* Clear the bit to allow gatectrl */ + v &= ~mask; + ti_clk_ll_ops->clk_writel(v, clk->clksel_reg); +} + +static void omap4_dpllmx_deny_gatectrl(struct clk_hw_omap *clk) +{ + u32 v; + u32 mask; + + if (!clk || !clk->clksel_reg) + return; + + mask = clk->flags & CLOCK_CLKOUTX2 ? + OMAP4430_DPLL_CLKOUTX2_GATE_CTRL_MASK : + OMAP4430_DPLL_CLKOUT_GATE_CTRL_MASK; + + v = ti_clk_ll_ops->clk_readl(clk->clksel_reg); + /* Set the bit to deny gatectrl */ + v |= mask; + ti_clk_ll_ops->clk_writel(v, clk->clksel_reg); +} + +const struct clk_hw_omap_ops clkhwops_omap4_dpllmx = { + .allow_idle = omap4_dpllmx_allow_gatectrl, + .deny_idle = omap4_dpllmx_deny_gatectrl, +}; + +/** + * omap4_dpll_lpmode_recalc - compute DPLL low-power setting + * @dd: pointer to the dpll data structure + * + * Calculates if low-power mode can be enabled based upon the last + * multiplier and divider values calculated. If low-power mode can be + * enabled, then the bit to enable low-power mode is stored in the + * last_rounded_lpmode variable. This implementation is based upon the + * criteria for enabling low-power mode as described in the OMAP4430/60 + * Public TRM section 3.6.3.3.2 "Enable Control, Status, and Low-Power + * Operation Mode". + */ +static void omap4_dpll_lpmode_recalc(struct dpll_data *dd) +{ + long fint, fout; + + fint = __clk_get_rate(dd->clk_ref) / (dd->last_rounded_n + 1); + fout = fint * dd->last_rounded_m; + + if ((fint < OMAP4_DPLL_LP_FINT_MAX) && (fout < OMAP4_DPLL_LP_FOUT_MAX)) + dd->last_rounded_lpmode = 1; + else + dd->last_rounded_lpmode = 0; +} + +/** + * omap4_dpll_regm4xen_recalc - compute DPLL rate, considering REGM4XEN bit + * @clk: struct clk * of the DPLL to compute the rate for + * + * Compute the output rate for the OMAP4 DPLL represented by @clk. + * Takes the REGM4XEN bit into consideration, which is needed for the + * OMAP4 ABE DPLL. Returns the DPLL's output rate (before M-dividers) + * upon success, or 0 upon error. + */ +unsigned long omap4_dpll_regm4xen_recalc(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_hw_omap *clk = to_clk_hw_omap(hw); + u32 v; + unsigned long rate; + struct dpll_data *dd; + + if (!clk || !clk->dpll_data) + return 0; + + dd = clk->dpll_data; + + rate = omap2_get_dpll_rate(clk); + + /* regm4xen adds a multiplier of 4 to DPLL calculations */ + v = ti_clk_ll_ops->clk_readl(dd->control_reg); + if (v & OMAP4430_DPLL_REGM4XEN_MASK) + rate *= OMAP4430_REGM4XEN_MULT; + + return rate; +} + +/** + * omap4_dpll_regm4xen_round_rate - round DPLL rate, considering REGM4XEN bit + * @clk: struct clk * of the DPLL to round a rate for + * @target_rate: the desired rate of the DPLL + * + * Compute the rate that would be programmed into the DPLL hardware + * for @clk if set_rate() were to be provided with the rate + * @target_rate. Takes the REGM4XEN bit into consideration, which is + * needed for the OMAP4 ABE DPLL. Returns the rounded rate (before + * M-dividers) upon success, -EINVAL if @clk is null or not a DPLL, or + * ~0 if an error occurred in omap2_dpll_round_rate(). + */ +long omap4_dpll_regm4xen_round_rate(struct clk_hw *hw, + unsigned long target_rate, + unsigned long *parent_rate) +{ + struct clk_hw_omap *clk = to_clk_hw_omap(hw); + struct dpll_data *dd; + long r; + + if (!clk || !clk->dpll_data) + return -EINVAL; + + dd = clk->dpll_data; + + dd->last_rounded_m4xen = 0; + + /* + * First try to compute the DPLL configuration for + * target rate without using the 4X multiplier. + */ + r = omap2_dpll_round_rate(hw, target_rate, NULL); + if (r != ~0) + goto out; + + /* + * If we did not find a valid DPLL configuration, try again, but + * this time see if using the 4X multiplier can help. Enabling the + * 4X multiplier is equivalent to dividing the target rate by 4. + */ + r = omap2_dpll_round_rate(hw, target_rate / OMAP4430_REGM4XEN_MULT, + NULL); + if (r == ~0) + return r; + + dd->last_rounded_rate *= OMAP4430_REGM4XEN_MULT; + dd->last_rounded_m4xen = 1; + +out: + omap4_dpll_lpmode_recalc(dd); + + return dd->last_rounded_rate; +} + +/** + * omap4_dpll_regm4xen_determine_rate - determine rate for a DPLL + * @hw: pointer to the clock to determine rate for + * @req: target rate request + * + * Determines which DPLL mode to use for reaching a desired rate. + * Checks whether the DPLL shall be in bypass or locked mode, and if + * locked, calculates the M,N values for the DPLL via round-rate. + * Returns 0 on success and a negative error value otherwise. + */ +int omap4_dpll_regm4xen_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_hw_omap *clk = to_clk_hw_omap(hw); + struct dpll_data *dd; + + if (!req->rate) + return -EINVAL; + + dd = clk->dpll_data; + if (!dd) + return -EINVAL; + + if (__clk_get_rate(dd->clk_bypass) == req->rate && + (dd->modes & (1 << DPLL_LOW_POWER_BYPASS))) { + req->best_parent_hw = __clk_get_hw(dd->clk_bypass); + } else { + req->rate = omap4_dpll_regm4xen_round_rate(hw, req->rate, + &req->best_parent_rate); + req->best_parent_hw = __clk_get_hw(dd->clk_ref); + } + + req->best_parent_rate = req->rate; + + return 0; +} diff --git a/drivers/clk/ti/fixed-factor.c b/drivers/clk/ti/fixed-factor.c index c2c8a287408c..3cd406768909 100644 --- a/drivers/clk/ti/fixed-factor.c +++ b/drivers/clk/ti/fixed-factor.c @@ -22,6 +22,8 @@ #include <linux/of_address.h> #include <linux/clk/ti.h> +#include "clock.h" + #undef pr_fmt #define pr_fmt(fmt) "%s: " fmt, __func__ |