[PATCHv6 04/45] CLK: TI: add support for OMAP gate clock
From: Tero Kristo <hidden>
Date: 2013-08-29 13:15:56
Also in:
linux-devicetree, linux-omap
Subsystem:
arm port, common clk framework, omap clock framework support, omap2+ support, open firmware and flattened device tree bindings, the rest, ti clock driver · Maintainers:
Russell King, Michael Turquette, Stephen Boyd, Paul Walmsley, Aaro Koskinen, Andreas Kemnade, Kevin Hilman, Roger Quadros, Tony Lindgren, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Linus Torvalds, Tero Kristo
This patch adds support for TI specific gate clocks. These behave as basic gate-clock, but have different ops / hw-ops for controlling the actual gate, for example waiting until the clock is ready. Several sub-types are supported: - ti,gate-clock: basic gate clock with default ops/hwops - ti,clkdm-gate-clock: clockdomain level gate control - ti,dss-gate-clock: gate clock with DSS specific hardware handling - ti,am35xx-gate-clock: gate clock with AM35xx specific hardware handling - ti,hsdiv-gate-clock: gate clock with OMAP36xx hardware errata handling Signed-off-by: Tero Kristo <redacted> --- .../devicetree/bindings/clock/ti/gate.txt | 73 ++++++++ arch/arm/mach-omap2/clock.h | 28 ---- drivers/clk/ti/Makefile | 2 +- drivers/clk/ti/gate.c | 174 ++++++++++++++++++++ include/linux/clk/ti.h | 35 ++++ 5 files changed, 283 insertions(+), 29 deletions(-) create mode 100644 Documentation/devicetree/bindings/clock/ti/gate.txt create mode 100644 drivers/clk/ti/gate.c
diff --git a/Documentation/devicetree/bindings/clock/ti/gate.txt b/Documentation/devicetree/bindings/clock/ti/gate.txt
new file mode 100644
index 0000000..338b112
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/ti/gate.txt@@ -0,0 +1,73 @@ +Binding for Texas Instruments gate clock. + +This binding uses the common clock binding[1]. This clock is +quite much similar to the basic gate-clock [2], however, +it supports a number of additional features. If no register +is provided for this clock, the code assumes that a clockdomain +will be controlled instead and the corresponding hw-ops for +that is used. + +[1] Documentation/devicetree/bindings/clock/clock-bindings.txt +[2] Documentation/devicetree/bindings/clock/gate-clock.txt +[3] Documentation/devicetree/bindings/clock/ti/clockdomain.txt + +Required properties: +- compatible : shall be one of: + "ti,gate-clock" - basic gate clock + "ti,dss-gate-clock" - gate clock with DSS specific hardware handling + "ti,am35xx-gate-clock" - gate clock with AM35xx specific hardware handling + "ti,clkdm-gate-clock" - clockdomain gate clock, which derives its functional + clock directly from a clockdomain, see [3] how + to map clockdomains properly + "ti,hsdiv-gate-clock" - gate clock with OMAP36xx specific hardware handling, + required for a hardware errata +- #clock-cells : from common clock binding; shall be set to 0 +- clocks : link to phandle of parent clock +- reg : base address for register controlling adjustable gate, not needed for + ti,clkdm-gate-clock type +- ti,enable-bit : bit shift for programming the clock gate, not needed for + ti,clkdm-gate-clock type + +Optional properties: +- ti,set-bit-to-disable : inverts default gate programming. Setting the bit + gates the clock and clearing the bit ungates the clock. + +Examples: + mmchs2_fck: mmchs2_fck at 48004a00 { + #clock-cells = <0>; + compatible = "ti,gate-clock"; + clocks = <&core_96m_fck>; + reg = <0x48004a00 0x4>; + ti,enable-bit = <25>; + }; + + dss1_alwon_fck_3430es2: dss1_alwon_fck_3430es2 at 48004e00 { + #clock-cells = <0>; + compatible = "ti,dss-gate-clock"; + clocks = <&dpll4_m4x2_ck>; + reg = <0x48004e00 0x4>; + ti,enable-bit = <0>; + }; + + emac_ick: emac_ick at 4800259c { + #clock-cells = <0>; + compatible = "ti,am35xx-gate-clock"; + clocks = <&ipss_ick>; + reg = <0x4800259c 0x4>; + ti,enable-bit = <1>; + }; + + emu_src_ck: emu_src_ck { + #clock-cells = <0>; + compatible = "ti,clkdm-gate-clock"; + clocks = <&emu_src_mux_ck>; + }; + + dpll4_m2x2_ck: dpll4_m2x2_ck at 48004d00 { + #clock-cells = <0>; + compatible = "ti,hsdiv-gate-clock"; + clocks = <&dpll4_m2x2_mul_ck>; + ti,enable-bit = <0x1b>; + reg = <0x48004d00 0x4>; + ti,set-bit-to-disable; + };
diff --git a/arch/arm/mach-omap2/clock.h b/arch/arm/mach-omap2/clock.h
index 079536a..f0b7218 100644
--- a/arch/arm/mach-omap2/clock.h
+++ b/arch/arm/mach-omap2/clock.h@@ -179,25 +179,6 @@ struct clksel { const struct clksel_rate *rates; }; -/* - * struct clk.flags possibilities - * - * XXX document the rest of the clock flags here - * - * CLOCK_CLKOUTX2: (OMAP4 only) DPLL CLKOUT and CLKOUTX2 GATE_CTRL - * bits share the same register. This flag allows the - * omap4_dpllmx*() code to determine which GATE_CTRL bit field - * should be used. This is a temporary solution - a better approach - * would be to associate clock type-specific data with the clock, - * similar to the struct dpll_data approach. - */ -#define ENABLE_REG_32BIT (1 << 0) /* Use 32-bit access */ -#define CLOCK_IDLE_CONTROL (1 << 1) -#define CLOCK_NO_IDLE_PARENT (1 << 2) -#define ENABLE_ON_INIT (1 << 3) /* Enable upon framework init */ -#define INVERT_ENABLE (1 << 4) /* 0 enables, 1 disables */ -#define CLOCK_CLKOUTX2 (1 << 5) - struct clk_hw_omap_ops { void (*find_idlest)(struct clk_hw_omap *oclk, void __iomem **idlest_reg,
@@ -260,9 +241,6 @@ extern void omap2_clkt_iclk_deny_idle(struct clk_hw_omap *clk); unsigned long omap2_get_dpll_rate(struct clk_hw_omap *clk); -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);
@@ -292,15 +270,12 @@ extern const struct clksel_rate dsp_ick_rates[]; extern struct clk dummy_ck; extern const struct clk_hw_omap_ops clkhwops_iclk_wait; -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_omap3430es2_ssi_wait; extern const struct clk_hw_omap_ops clkhwops_omap3430es2_iclk_ssi_wait; -extern const struct clk_hw_omap_ops clkhwops_omap3430es2_dss_usbhost_wait; extern const struct clk_hw_omap_ops clkhwops_omap3430es2_iclk_dss_usbhost_wait; extern const struct clk_hw_omap_ops clkhwops_omap3430es2_iclk_hsotgusb_wait; extern const struct clk_hw_omap_ops clkhwops_omap3430es2_hsotgusb_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_hw_omap_ops clkhwops_apll54; extern const struct clk_hw_omap_ops clkhwops_apll96;
@@ -318,8 +293,5 @@ extern const struct clksel_rate div31_1to31_rates[]; extern int am33xx_clk_init(void); -extern int omap2_clkops_enable_clkdm(struct clk_hw *hw); -extern void omap2_clkops_disable_clkdm(struct clk_hw *hw); - extern void omap_clocks_register(struct omap_clk *oclks, int cnt); #endif
diff --git a/drivers/clk/ti/Makefile b/drivers/clk/ti/Makefile
index 533efb4..57bfbaa 100644
--- a/drivers/clk/ti/Makefile
+++ b/drivers/clk/ti/Makefile@@ -1,3 +1,3 @@ ifneq ($(CONFIG_OF),) -obj-y += clk.o dpll.o autoidle.o +obj-y += clk.o dpll.o autoidle.o gate.o endif
diff --git a/drivers/clk/ti/gate.c b/drivers/clk/ti/gate.c
new file mode 100644
index 0000000..c9fcbb2
--- /dev/null
+++ b/drivers/clk/ti/gate.c@@ -0,0 +1,174 @@ +/* + * OMAP gate clock support + * + * Copyright (C) 2013 Texas Instruments, Inc. + * + * 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/clk-provider.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/clk/ti.h> + +#define to_clk_divider(_hw) container_of(_hw, struct clk_divider, hw) + +static int omap36xx_gate_clk_enable_with_hsdiv_restore(struct clk_hw *clk); + +static const struct clk_ops omap_gate_clkdm_clk_ops = { + .init = &omap2_init_clk_clkdm, + .enable = &omap2_clkops_enable_clkdm, + .disable = &omap2_clkops_disable_clkdm, +}; + +static const struct clk_ops omap_gate_clk_ops = { + .init = &omap2_init_clk_clkdm, + .enable = &omap2_dflt_clk_enable, + .disable = &omap2_dflt_clk_disable, + .is_enabled = &omap2_dflt_clk_is_enabled, +}; + +static const struct clk_ops omap_gate_clk_hsdiv_restore_ops = { + .init = &omap2_init_clk_clkdm, + .enable = &omap36xx_gate_clk_enable_with_hsdiv_restore, + .disable = &omap2_dflt_clk_disable, + .is_enabled = &omap2_dflt_clk_is_enabled, +}; + +/** + * omap36xx_gate_clk_enable_with_hsdiv_restore - enable clocks suffering + * from HSDivider PWRDN problem Implements Errata ID: i556. + * @clk: DPLL output struct clk + * + * 3630 only: dpll3_m3_ck, dpll4_m2_ck, dpll4_m3_ck, dpll4_m4_ck, + * dpll4_m5_ck & dpll4_m6_ck dividers gets loaded with reset + * valueafter their respective PWRDN bits are set. Any dummy write + * (Any other value different from the Read value) to the + * corresponding CM_CLKSEL register will refresh the dividers. + */ +static int omap36xx_gate_clk_enable_with_hsdiv_restore(struct clk_hw *clk) +{ + struct clk_divider *parent; + struct clk_hw *parent_hw; + u32 dummy_v, orig_v; + int ret; + + /* Clear PWRDN bit of HSDIVIDER */ + ret = omap2_dflt_clk_enable(clk); + + /* Parent is the x2 node, get parent of parent for the m2 div */ + parent_hw = __clk_get_hw(__clk_get_parent(__clk_get_parent(clk->clk))); + parent = to_clk_divider(parent_hw); + + /* Restore the dividers */ + if (!ret) { + orig_v = __raw_readl(parent->reg); + dummy_v = orig_v; + + /* Write any other value different from the Read value */ + dummy_v ^= (1 << parent->shift); + __raw_writel(dummy_v, parent->reg); + + /* Write the original divider */ + __raw_writel(orig_v, parent->reg); + } + + return ret; +} + +static void __init _of_omap_gate_clk_setup(struct device_node *node, + void __iomem *reg, + const struct clk_ops *ops, + const struct clk_hw_omap_ops *hw_ops) +{ + struct clk *clk; + struct clk_init_data init = { 0 }; + struct clk_hw_omap *clk_hw; + const char *clk_name = node->name; + const char *parent_name; + u32 val; + + clk_hw = kzalloc(sizeof(*clk_hw), GFP_KERNEL); + if (!clk_hw) { + pr_err("%s: could not allocate clk_hw_omap\n", __func__); + return; + } + + clk_hw->hw.init = &init; + + of_property_read_string(node, "clock-output-names", &clk_name); + + init.name = clk_name; + init.ops = ops; + clk_hw->enable_reg = reg; + if (!of_property_read_u32(node, "ti,enable-bit", &val)) + clk_hw->enable_bit = val; + clk_hw->ops = hw_ops; + + parent_name = of_clk_get_parent_name(node, 0); + init.parent_names = &parent_name; + init.num_parents = 1; + + if (of_property_read_bool(node, "ti,set-rate-parent")) + init.flags |= CLK_SET_RATE_PARENT; + + if (of_property_read_bool(node, "ti,set-bit-to-disable")) + clk_hw->flags |= INVERT_ENABLE; + + clk = clk_register(NULL, &clk_hw->hw); + + if (!IS_ERR(clk)) + of_clk_add_provider(node, of_clk_src_simple_get, clk); +} + +static void __init of_omap_clkdm_gate_clk_setup(struct device_node *node) +{ + _of_omap_gate_clk_setup(node, NULL, &omap_gate_clkdm_clk_ops, NULL); +} +CLK_OF_DECLARE(omap_clkdm_gate_clk, "ti,clkdm-gate-clock", + of_omap_clkdm_gate_clk_setup); + +static void __init of_omap_hsdiv_gate_clk_setup(struct device_node *node) +{ + void __iomem *reg = of_iomap(node, 0); + _of_omap_gate_clk_setup(node, reg, &omap_gate_clk_hsdiv_restore_ops, + &clkhwops_wait); +} +CLK_OF_DECLARE(omap_hsdiv_gate_clk, "ti,hsdiv-gate-clock", + of_omap_hsdiv_gate_clk_setup); + +static void __init of_omap_gate_clk_setup(struct device_node *node) +{ + void __iomem *reg = of_iomap(node, 0); + _of_omap_gate_clk_setup(node, reg, &omap_gate_clk_ops, &clkhwops_wait); +} +CLK_OF_DECLARE(omap_gate_clk, "ti,gate-clock", of_omap_gate_clk_setup); + +static void __init of_omap_am35xx_gate_clk_setup(struct device_node *node) +{ + void __iomem *reg = of_iomap(node, 0); + _of_omap_gate_clk_setup(node, reg, &omap_gate_clk_ops, + &clkhwops_am35xx_ipss_module_wait); +} +CLK_OF_DECLARE(omap_am35xx_gate_clk, "ti,am35xx-gate-clock", + of_omap_am35xx_gate_clk_setup); + +static void __init of_omap_dss_gate_clk_setup(struct device_node *node) +{ + void __iomem *reg = of_iomap(node, 0); + _of_omap_gate_clk_setup(node, reg, &omap_gate_clk_ops, + &clkhwops_omap3430es2_dss_usbhost_wait); +} +CLK_OF_DECLARE(omap_dss_gate_clk, "ti,dss-gate-clock", + of_omap_dss_gate_clk_setup);
diff --git a/include/linux/clk/ti.h b/include/linux/clk/ti.h
index e2473b8..5fe2d43 100644
--- a/include/linux/clk/ti.h
+++ b/include/linux/clk/ti.h@@ -132,6 +132,33 @@ struct clk_hw_omap { const struct clk_hw_omap_ops *ops; }; +/* + * struct clk_hw_omap.flags possibilities + * + * XXX document the rest of the clock flags here + * + * ENABLE_REG_32BIT: (OMAP1 only) clock control register must be accessed + * with 32bit ops, by default OMAP1 uses 16bit ops. + * CLOCK_IDLE_CONTROL: (OMAP1 only) clock has autoidle support. + * CLOCK_NO_IDLE_PARENT: (OMAP1 only) when clock is enabled, its parent + * clock is put to no-idle mode. + * ENABLE_ON_INIT: Clock is enabled on init. + * INVERT_ENABLE: By default, clock enable bit behavior is '1' enable, '0' + * disable. This inverts the behavior making '0' enable and '1' disable. + * CLOCK_CLKOUTX2: (OMAP4 only) DPLL CLKOUT and CLKOUTX2 GATE_CTRL + * bits share the same register. This flag allows the + * omap4_dpllmx*() code to determine which GATE_CTRL bit field + * should be used. This is a temporary solution - a better approach + * would be to associate clock type-specific data with the clock, + * similar to the struct dpll_data approach. + */ +#define ENABLE_REG_32BIT (1 << 0) /* Use 32-bit access */ +#define CLOCK_IDLE_CONTROL (1 << 1) +#define CLOCK_NO_IDLE_PARENT (1 << 2) +#define ENABLE_ON_INIT (1 << 3) /* Enable upon framework init */ +#define INVERT_ENABLE (1 << 4) /* 0 enables, 1 disables */ +#define CLOCK_CLKOUTX2 (1 << 5) + /* CM_CLKEN_PLL*.EN* bit values - not all are available for every DPLL */ #define DPLL_LOW_POWER_STOP 0x1 #define DPLL_LOW_POWER_BYPASS 0x5
@@ -176,8 +203,13 @@ long omap2_dpll_round_rate(struct clk_hw *hw, unsigned long target_rate, void omap2_init_clk_clkdm(struct clk_hw *clk); unsigned long omap3_clkoutx2_recalc(struct clk_hw *hw, unsigned long parent_rate); +int omap2_clkops_enable_clkdm(struct clk_hw *hw); +void omap2_clkops_disable_clkdm(struct clk_hw *hw); int omap3_dpll4_set_rate(struct clk_hw *clk, unsigned long rate, unsigned long parent_rate); +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 omap_dt_clocks_register(struct omap_dt_clk *oclks); #ifdef CONFIG_OF
@@ -190,5 +222,8 @@ static inline void of_omap_clk_deny_autoidle_all(void) { } 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_omap3430es2_dss_usbhost_wait; +extern const struct clk_hw_omap_ops clkhwops_am35xx_ipss_module_wait; #endif
--
1.7.9.5