From 75bd2ec1a65a30094f630f9c5bf3ecfe9549496f Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Mon, 24 Nov 2014 18:05:15 +0800 Subject: clk: sunxi: Remove ahb1_sdram from sun6i/sun8i protected clocks list The ahb1_sdram clock gate is only used for accessing the sdram controller's registers over the bus. It is not used for actually clock the controller or the dram, hence it does not need to be protected. This also gets rid of the problem when the protected ahb1_sdram gate is prepared/enabled while it is still an orphan, and the operation is not propagated to the correct parent. This was confirmed on my A23 tablet and my A31 Hummingbird. Signed-off-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard --- drivers/clk/sunxi/clk-sunxi.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c index 570202582dcf..ecee2cd68d93 100644 --- a/drivers/clk/sunxi/clk-sunxi.c +++ b/drivers/clk/sunxi/clk-sunxi.c @@ -1217,7 +1217,6 @@ CLK_OF_DECLARE(sun7i_a20_clk_init, "allwinner,sun7i-a20", sun5i_init_clocks); static const char *sun6i_critical_clocks[] __initdata = { "cpu", - "ahb1_sdram", }; static void __init sun6i_init_clocks(struct device_node *node) -- cgit v1.2.3-55-g7522 From 7954dfaee386d45d6ec655e5153ad67edf311a56 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Wed, 26 Nov 2014 15:16:52 +0800 Subject: clk: sunxi: unify sun6i AHB1 clock with proper PLL6 pre-divider This patch unifies the sun6i AHB1 clock, originally supported with separate mux and divider clks. It also adds support for the pre-divider on the PLL6 input, thus allowing the clock to be muxed to PLL6 with proper clock rate calculation. Signed-off-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard --- Documentation/devicetree/bindings/clock/sunxi.txt | 2 +- drivers/clk/sunxi/clk-sunxi.c | 208 ++++++++++++++++++++++ 2 files changed, 209 insertions(+), 1 deletion(-) (limited to 'drivers/clk') diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt index 67b2b99f2b33..9dc4f55a04ad 100644 --- a/Documentation/devicetree/bindings/clock/sunxi.txt +++ b/Documentation/devicetree/bindings/clock/sunxi.txt @@ -26,7 +26,7 @@ Required properties: "allwinner,sun5i-a10s-ahb-gates-clk" - for the AHB gates on A10s "allwinner,sun7i-a20-ahb-gates-clk" - for the AHB gates on A20 "allwinner,sun6i-a31-ar100-clk" - for the AR100 on A31 - "allwinner,sun6i-a31-ahb1-mux-clk" - for the AHB1 multiplexer on A31 + "allwinner,sun6i-a31-ahb1-clk" - for the AHB1 clock on A31 "allwinner,sun6i-a31-ahb1-gates-clk" - for the AHB1 gates on A31 "allwinner,sun8i-a23-ahb1-gates-clk" - for the AHB1 gates on A23 "allwinner,sun9i-a80-ahb0-gates-clk" - for the AHB0 gates on A80 diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c index ecee2cd68d93..cc5eab2d0469 100644 --- a/drivers/clk/sunxi/clk-sunxi.c +++ b/drivers/clk/sunxi/clk-sunxi.c @@ -20,11 +20,219 @@ #include #include #include +#include #include "clk-factors.h" static DEFINE_SPINLOCK(clk_lock); +/** + * sun6i_a31_ahb1_clk_setup() - Setup function for a31 ahb1 composite clk + */ + +#define SUN6I_AHB1_MAX_PARENTS 4 +#define SUN6I_AHB1_MUX_PARENT_PLL6 3 +#define SUN6I_AHB1_MUX_SHIFT 12 +/* un-shifted mask is what mux_clk expects */ +#define SUN6I_AHB1_MUX_MASK 0x3 +#define SUN6I_AHB1_MUX_GET_PARENT(reg) ((reg >> SUN6I_AHB1_MUX_SHIFT) & \ + SUN6I_AHB1_MUX_MASK) + +#define SUN6I_AHB1_DIV_SHIFT 4 +#define SUN6I_AHB1_DIV_MASK (0x3 << SUN6I_AHB1_DIV_SHIFT) +#define SUN6I_AHB1_DIV_GET(reg) ((reg & SUN6I_AHB1_DIV_MASK) >> \ + SUN6I_AHB1_DIV_SHIFT) +#define SUN6I_AHB1_DIV_SET(reg, div) ((reg & ~SUN6I_AHB1_DIV_MASK) | \ + (div << SUN6I_AHB1_DIV_SHIFT)) +#define SUN6I_AHB1_PLL6_DIV_SHIFT 6 +#define SUN6I_AHB1_PLL6_DIV_MASK (0x3 << SUN6I_AHB1_PLL6_DIV_SHIFT) +#define SUN6I_AHB1_PLL6_DIV_GET(reg) ((reg & SUN6I_AHB1_PLL6_DIV_MASK) >> \ + SUN6I_AHB1_PLL6_DIV_SHIFT) +#define SUN6I_AHB1_PLL6_DIV_SET(reg, div) ((reg & ~SUN6I_AHB1_PLL6_DIV_MASK) | \ + (div << SUN6I_AHB1_PLL6_DIV_SHIFT)) + +struct sun6i_ahb1_clk { + struct clk_hw hw; + void __iomem *reg; +}; + +#define to_sun6i_ahb1_clk(_hw) container_of(_hw, struct sun6i_ahb1_clk, hw) + +static unsigned long sun6i_ahb1_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct sun6i_ahb1_clk *ahb1 = to_sun6i_ahb1_clk(hw); + unsigned long rate; + u32 reg; + + /* Fetch the register value */ + reg = readl(ahb1->reg); + + /* apply pre-divider first if parent is pll6 */ + if (SUN6I_AHB1_MUX_GET_PARENT(reg) == SUN6I_AHB1_MUX_PARENT_PLL6) + parent_rate /= SUN6I_AHB1_PLL6_DIV_GET(reg) + 1; + + /* clk divider */ + rate = parent_rate >> SUN6I_AHB1_DIV_GET(reg); + + return rate; +} + +static long sun6i_ahb1_clk_round(unsigned long rate, u8 *divp, u8 *pre_divp, + u8 parent, unsigned long parent_rate) +{ + u8 div, calcp, calcm = 1; + + /* + * clock can only divide, so we will never be able to achieve + * frequencies higher than the parent frequency + */ + if (parent_rate && rate > parent_rate) + rate = parent_rate; + + div = DIV_ROUND_UP(parent_rate, rate); + + /* calculate pre-divider if parent is pll6 */ + if (parent == SUN6I_AHB1_MUX_PARENT_PLL6) { + if (div < 4) + calcp = 0; + else if (div / 2 < 4) + calcp = 1; + else if (div / 4 < 4) + calcp = 2; + else + calcp = 3; + + calcm = DIV_ROUND_UP(div, 1 << calcp); + } else { + calcp = __roundup_pow_of_two(div); + calcp = calcp > 3 ? 3 : calcp; + } + + /* we were asked to pass back divider values */ + if (divp) { + *divp = calcp; + *pre_divp = calcm - 1; + } + + return (parent_rate / calcm) >> calcp; +} + +static long sun6i_ahb1_clk_determine_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *best_parent_rate, + struct clk_hw **best_parent_clk) +{ + struct clk *clk = hw->clk, *parent, *best_parent = NULL; + int i, num_parents; + unsigned long parent_rate, best = 0, child_rate, best_child_rate = 0; + + /* find the parent that can help provide the fastest rate <= rate */ + num_parents = __clk_get_num_parents(clk); + for (i = 0; i < num_parents; i++) { + parent = clk_get_parent_by_index(clk, i); + if (!parent) + continue; + if (__clk_get_flags(clk) & CLK_SET_RATE_PARENT) + parent_rate = __clk_round_rate(parent, rate); + else + parent_rate = __clk_get_rate(parent); + + child_rate = sun6i_ahb1_clk_round(rate, NULL, NULL, i, + parent_rate); + + if (child_rate <= rate && child_rate > best_child_rate) { + best_parent = parent; + best = parent_rate; + best_child_rate = child_rate; + } + } + + if (best_parent) + *best_parent_clk = __clk_get_hw(best_parent); + *best_parent_rate = best; + + return best_child_rate; +} + +static int sun6i_ahb1_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct sun6i_ahb1_clk *ahb1 = to_sun6i_ahb1_clk(hw); + unsigned long flags; + u8 div, pre_div, parent; + u32 reg; + + spin_lock_irqsave(&clk_lock, flags); + + reg = readl(ahb1->reg); + + /* need to know which parent is used to apply pre-divider */ + parent = SUN6I_AHB1_MUX_GET_PARENT(reg); + sun6i_ahb1_clk_round(rate, &div, &pre_div, parent, parent_rate); + + reg = SUN6I_AHB1_DIV_SET(reg, div); + reg = SUN6I_AHB1_PLL6_DIV_SET(reg, pre_div); + writel(reg, ahb1->reg); + + spin_unlock_irqrestore(&clk_lock, flags); + + return 0; +} + +static const struct clk_ops sun6i_ahb1_clk_ops = { + .determine_rate = sun6i_ahb1_clk_determine_rate, + .recalc_rate = sun6i_ahb1_clk_recalc_rate, + .set_rate = sun6i_ahb1_clk_set_rate, +}; + +static void __init sun6i_ahb1_clk_setup(struct device_node *node) +{ + struct clk *clk; + struct sun6i_ahb1_clk *ahb1; + struct clk_mux *mux; + const char *clk_name = node->name; + const char *parents[SUN6I_AHB1_MAX_PARENTS]; + void __iomem *reg; + int i = 0; + + reg = of_io_request_and_map(node, 0, of_node_full_name(node)); + + /* we have a mux, we will have >1 parents */ + while (i < SUN6I_AHB1_MAX_PARENTS && + (parents[i] = of_clk_get_parent_name(node, i)) != NULL) + i++; + + of_property_read_string(node, "clock-output-names", &clk_name); + + ahb1 = kzalloc(sizeof(struct sun6i_ahb1_clk), GFP_KERNEL); + if (!ahb1) + return; + + mux = kzalloc(sizeof(struct clk_mux), GFP_KERNEL); + if (!mux) { + kfree(ahb1); + return; + } + + /* set up clock properties */ + mux->reg = reg; + mux->shift = SUN6I_AHB1_MUX_SHIFT; + mux->mask = SUN6I_AHB1_MUX_MASK; + mux->lock = &clk_lock; + ahb1->reg = reg; + + clk = clk_register_composite(NULL, clk_name, parents, i, + &mux->hw, &clk_mux_ops, + &ahb1->hw, &sun6i_ahb1_clk_ops, + NULL, NULL, 0); + + if (!IS_ERR(clk)) { + of_clk_add_provider(node, of_clk_src_simple_get, clk); + clk_register_clkdev(clk, clk_name, NULL); + } +} +CLK_OF_DECLARE(sun6i_a31_ahb1, "allwinner,sun6i-a31-ahb1-clk", sun6i_ahb1_clk_setup); + /* Maximum number of parents our clocks have */ #define SUNXI_MAX_PARENTS 5 -- cgit v1.2.3-55-g7522 From 7c74c220e9c6d756953235e1f8e5b704569ea613 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 23 Nov 2014 14:38:07 +0100 Subject: clk: sunxi: Give sunxi_factors_register a registers parameter Before this commit sunxi_factors_register uses of_iomap(node, 0) to get the clk registers. The sun6i prcm has factor clocks, for which we want to use sunxi_factors_register, but of_iomap(node, 0) does not work for the prcm factor clocks, because the prcm uses the mfd framework, so the registers are not part of the dt-node, instead they are added to the platform_device, as platform_device resources. This commit makes getting the registers the callers duty, so that sunxi_factors_register can be used with mfd instantiated platform device too. While at it also add error checking to the of_iomap calls. This commit also drops the __init function from sunxi_factors_register since platform driver probe functions are not __init. Signed-off-by: Hans de Goede Signed-off-by: Maxime Ripard --- drivers/clk/sunxi/clk-factors.c | 10 ++++------ drivers/clk/sunxi/clk-factors.h | 7 ++++--- drivers/clk/sunxi/clk-mod0.c | 24 ++++++++++++++++++++++-- drivers/clk/sunxi/clk-sun8i-mbus.c | 13 +++++++++++-- drivers/clk/sunxi/clk-sunxi.c | 11 ++++++++++- 5 files changed, 51 insertions(+), 14 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi/clk-factors.c b/drivers/clk/sunxi/clk-factors.c index 62e08fb58554..a9ebbd207d58 100644 --- a/drivers/clk/sunxi/clk-factors.c +++ b/drivers/clk/sunxi/clk-factors.c @@ -156,9 +156,10 @@ static const struct clk_ops clk_factors_ops = { .set_rate = clk_factors_set_rate, }; -struct clk * __init sunxi_factors_register(struct device_node *node, - const struct factors_data *data, - spinlock_t *lock) +struct clk *sunxi_factors_register(struct device_node *node, + const struct factors_data *data, + spinlock_t *lock, + void __iomem *reg) { struct clk *clk; struct clk_factors *factors; @@ -168,11 +169,8 @@ struct clk * __init sunxi_factors_register(struct device_node *node, struct clk_hw *mux_hw = NULL; const char *clk_name = node->name; const char *parents[FACTORS_MAX_PARENTS]; - void __iomem *reg; int i = 0; - reg = of_iomap(node, 0); - /* if we have a mux, we will have >1 parents */ while (i < FACTORS_MAX_PARENTS && (parents[i] = of_clk_get_parent_name(node, i)) != NULL) diff --git a/drivers/clk/sunxi/clk-factors.h b/drivers/clk/sunxi/clk-factors.h index 912238fde132..171085ab5513 100644 --- a/drivers/clk/sunxi/clk-factors.h +++ b/drivers/clk/sunxi/clk-factors.h @@ -36,8 +36,9 @@ struct clk_factors { spinlock_t *lock; }; -struct clk * __init sunxi_factors_register(struct device_node *node, - const struct factors_data *data, - spinlock_t *lock); +struct clk *sunxi_factors_register(struct device_node *node, + const struct factors_data *data, + spinlock_t *lock, + void __iomem *reg); #endif diff --git a/drivers/clk/sunxi/clk-mod0.c b/drivers/clk/sunxi/clk-mod0.c index da0524eaee94..658d74f39451 100644 --- a/drivers/clk/sunxi/clk-mod0.c +++ b/drivers/clk/sunxi/clk-mod0.c @@ -79,7 +79,17 @@ static DEFINE_SPINLOCK(sun4i_a10_mod0_lock); static void __init sun4i_a10_mod0_setup(struct device_node *node) { - sunxi_factors_register(node, &sun4i_a10_mod0_data, &sun4i_a10_mod0_lock); + void __iomem *reg; + + reg = of_iomap(node, 0); + if (!reg) { + pr_err("Could not get registers for mod0-clk: %s\n", + node->name); + return; + } + + sunxi_factors_register(node, &sun4i_a10_mod0_data, + &sun4i_a10_mod0_lock, reg); } CLK_OF_DECLARE(sun4i_a10_mod0, "allwinner,sun4i-a10-mod0-clk", sun4i_a10_mod0_setup); @@ -87,7 +97,17 @@ static DEFINE_SPINLOCK(sun5i_a13_mbus_lock); static void __init sun5i_a13_mbus_setup(struct device_node *node) { - struct clk *mbus = sunxi_factors_register(node, &sun4i_a10_mod0_data, &sun5i_a13_mbus_lock); + struct clk *mbus; + void __iomem *reg; + + reg = of_iomap(node, 0); + if (!reg) { + pr_err("Could not get registers for a13-mbus-clk\n"); + return; + } + + mbus = sunxi_factors_register(node, &sun4i_a10_mod0_data, + &sun5i_a13_mbus_lock, reg); /* The MBUS clocks needs to be always enabled */ __clk_get(mbus); diff --git a/drivers/clk/sunxi/clk-sun8i-mbus.c b/drivers/clk/sunxi/clk-sun8i-mbus.c index ef49786eefd3..14cd026064bf 100644 --- a/drivers/clk/sunxi/clk-sun8i-mbus.c +++ b/drivers/clk/sunxi/clk-sun8i-mbus.c @@ -69,8 +69,17 @@ static DEFINE_SPINLOCK(sun8i_a23_mbus_lock); static void __init sun8i_a23_mbus_setup(struct device_node *node) { - struct clk *mbus = sunxi_factors_register(node, &sun8i_a23_mbus_data, - &sun8i_a23_mbus_lock); + struct clk *mbus; + void __iomem *reg; + + reg = of_iomap(node, 0); + if (!reg) { + pr_err("Could not get registers for a23-mbus-clk\n"); + return; + } + + mbus = sunxi_factors_register(node, &sun8i_a23_mbus_data, + &sun8i_a23_mbus_lock, reg); /* The MBUS clocks needs to be always enabled */ __clk_get(mbus); diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c index cc5eab2d0469..9ba2c5ff2aeb 100644 --- a/drivers/clk/sunxi/clk-sunxi.c +++ b/drivers/clk/sunxi/clk-sunxi.c @@ -728,7 +728,16 @@ static const struct factors_data sun7i_a20_out_data __initconst = { static struct clk * __init sunxi_factors_clk_setup(struct device_node *node, const struct factors_data *data) { - return sunxi_factors_register(node, data, &clk_lock); + void __iomem *reg; + + reg = of_iomap(node, 0); + if (!reg) { + pr_err("Could not get registers for factors-clk: %s\n", + node->name); + return NULL; + } + + return sunxi_factors_register(node, data, &clk_lock, reg); } -- cgit v1.2.3-55-g7522 From 66e79cf17ea636dbe28ee4ca01ccda638645f522 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Thu, 27 Nov 2014 17:29:30 +0800 Subject: clk: sunxi: Fix factor clocks usage for sun9i core clocks The sunxi factor clocks usage was changed in clk: sunxi: Give sunxi_factors_register a registers parameter However the sun9i core clocks were not fixed up in that patch, resulting in breakage. This patch fixes that. Signed-off-by: Chen-Yu Tsai Cc: Hans de Goede Signed-off-by: Maxime Ripard --- drivers/clk/sunxi/clk-sun9i-core.c | 62 ++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 6 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi/clk-sun9i-core.c b/drivers/clk/sunxi/clk-sun9i-core.c index 3cb9036d91bb..9b5e7a1f72d0 100644 --- a/drivers/clk/sunxi/clk-sun9i-core.c +++ b/drivers/clk/sunxi/clk-sun9i-core.c @@ -89,7 +89,17 @@ static DEFINE_SPINLOCK(sun9i_a80_pll4_lock); static void __init sun9i_a80_pll4_setup(struct device_node *node) { - sunxi_factors_register(node, &sun9i_a80_pll4_data, &sun9i_a80_pll4_lock); + void __iomem *reg; + + reg = of_io_request_and_map(node, 0, of_node_full_name(node)); + if (!reg) { + pr_err("Could not get registers for a80-pll4-clk: %s\n", + node->name); + return; + } + + sunxi_factors_register(node, &sun9i_a80_pll4_data, + &sun9i_a80_pll4_lock, reg); } CLK_OF_DECLARE(sun9i_a80_pll4, "allwinner,sun9i-a80-pll4-clk", sun9i_a80_pll4_setup); @@ -139,8 +149,18 @@ static DEFINE_SPINLOCK(sun9i_a80_gt_lock); static void __init sun9i_a80_gt_setup(struct device_node *node) { - struct clk *gt = sunxi_factors_register(node, &sun9i_a80_gt_data, - &sun9i_a80_gt_lock); + void __iomem *reg; + struct clk *gt; + + reg = of_io_request_and_map(node, 0, of_node_full_name(node)); + if (!reg) { + pr_err("Could not get registers for a80-gt-clk: %s\n", + node->name); + return; + } + + gt = sunxi_factors_register(node, &sun9i_a80_gt_data, + &sun9i_a80_gt_lock, reg); /* The GT bus clock needs to be always enabled */ __clk_get(gt); @@ -194,7 +214,17 @@ static DEFINE_SPINLOCK(sun9i_a80_ahb_lock); static void __init sun9i_a80_ahb_setup(struct device_node *node) { - sunxi_factors_register(node, &sun9i_a80_ahb_data, &sun9i_a80_ahb_lock); + void __iomem *reg; + + reg = of_io_request_and_map(node, 0, of_node_full_name(node)); + if (!reg) { + pr_err("Could not get registers for a80-ahb-clk: %s\n", + node->name); + return; + } + + sunxi_factors_register(node, &sun9i_a80_ahb_data, + &sun9i_a80_ahb_lock, reg); } CLK_OF_DECLARE(sun9i_a80_ahb, "allwinner,sun9i-a80-ahb-clk", sun9i_a80_ahb_setup); @@ -210,7 +240,17 @@ static DEFINE_SPINLOCK(sun9i_a80_apb0_lock); static void __init sun9i_a80_apb0_setup(struct device_node *node) { - sunxi_factors_register(node, &sun9i_a80_apb0_data, &sun9i_a80_apb0_lock); + void __iomem *reg; + + reg = of_io_request_and_map(node, 0, of_node_full_name(node)); + if (!reg) { + pr_err("Could not get registers for a80-apb0-clk: %s\n", + node->name); + return; + } + + sunxi_factors_register(node, &sun9i_a80_apb0_data, + &sun9i_a80_apb0_lock, reg); } CLK_OF_DECLARE(sun9i_a80_apb0, "allwinner,sun9i-a80-apb0-clk", sun9i_a80_apb0_setup); @@ -266,6 +306,16 @@ static DEFINE_SPINLOCK(sun9i_a80_apb1_lock); static void __init sun9i_a80_apb1_setup(struct device_node *node) { - sunxi_factors_register(node, &sun9i_a80_apb1_data, &sun9i_a80_apb1_lock); + void __iomem *reg; + + reg = of_io_request_and_map(node, 0, of_node_full_name(node)); + if (!reg) { + pr_err("Could not get registers for a80-apb1-clk: %s\n", + node->name); + return; + } + + sunxi_factors_register(node, &sun9i_a80_apb1_data, + &sun9i_a80_apb1_lock, reg); } CLK_OF_DECLARE(sun9i_a80_apb1, "allwinner,sun9i-a80-apb1-clk", sun9i_a80_apb1_setup); -- cgit v1.2.3-55-g7522 From 6ea3953da4e645fb4c6bff19b542eee10970505c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 20 Dec 2014 11:36:49 +0100 Subject: clk: sunxi: Make the mod0 clk driver also a platform driver With the prcm in sun6i (and some later SoCs) some mod0 clocks are instantiated through the mfd framework, and as such do not work with of_clk_declare, since they do not have registers assigned to them yet at of_clk_declare init time. Silence the error on not finding registers in the of_clk_declare mod0 clk setup method, and also register mod0-clk support as a platform driver to work properly with mfd instantiated mod0 clocks. Signed-off-by: Hans de Goede Signed-off-by: Maxime Ripard --- drivers/clk/sunxi/clk-mod0.c | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi/clk-mod0.c b/drivers/clk/sunxi/clk-mod0.c index 658d74f39451..bf8fcd8c940e 100644 --- a/drivers/clk/sunxi/clk-mod0.c +++ b/drivers/clk/sunxi/clk-mod0.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "clk-factors.h" @@ -67,7 +68,7 @@ static struct clk_factors_config sun4i_a10_mod0_config = { .pwidth = 2, }; -static const struct factors_data sun4i_a10_mod0_data __initconst = { +static const struct factors_data sun4i_a10_mod0_data = { .enable = 31, .mux = 24, .muxmask = BIT(1) | BIT(0), @@ -83,8 +84,11 @@ static void __init sun4i_a10_mod0_setup(struct device_node *node) reg = of_iomap(node, 0); if (!reg) { - pr_err("Could not get registers for mod0-clk: %s\n", - node->name); + /* + * This happens with mod0 clk nodes instantiated through + * mfd, as those do not have their resources assigned at + * CLK_OF_DECLARE time yet, so do not print an error. + */ return; } @@ -93,6 +97,39 @@ static void __init sun4i_a10_mod0_setup(struct device_node *node) } CLK_OF_DECLARE(sun4i_a10_mod0, "allwinner,sun4i-a10-mod0-clk", sun4i_a10_mod0_setup); +static int sun4i_a10_mod0_clk_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct resource *r; + void __iomem *reg; + + if (!np) + return -ENODEV; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + reg = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(reg)) + return PTR_ERR(reg); + + sunxi_factors_register(np, &sun4i_a10_mod0_data, + &sun4i_a10_mod0_lock, reg); + return 0; +} + +static const struct of_device_id sun4i_a10_mod0_clk_dt_ids[] = { + { .compatible = "allwinner,sun4i-a10-mod0-clk" }, + { /* sentinel */ } +}; + +static struct platform_driver sun4i_a10_mod0_clk_driver = { + .driver = { + .name = "sun4i-a10-mod0-clk", + .of_match_table = sun4i_a10_mod0_clk_dt_ids, + }, + .probe = sun4i_a10_mod0_clk_probe, +}; +module_platform_driver(sun4i_a10_mod0_clk_driver); + static DEFINE_SPINLOCK(sun5i_a13_mbus_lock); static void __init sun5i_a13_mbus_setup(struct device_node *node) -- cgit v1.2.3-55-g7522 From 3ec72fabcc6f4f5c786c50e08b59e1251d0fdfeb Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Tue, 6 Jan 2015 10:35:12 +0800 Subject: clk: sunxi: Propagate rate changes to parent for mux clocks The cpu clock on sunxi machines is just a mux clock, which is normally fed by the main PLL, but can be muxed to the main or low power oscillator. Make the mux clock propagate rate changes to its parent, so we can change the clock rate of the PLL, and thus actually implement rate changing on the cpu clock. This patch also removes the no reparenting limit. Signed-off-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard --- drivers/clk/sunxi/clk-sunxi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c index 9ba2c5ff2aeb..04e0b334c678 100644 --- a/drivers/clk/sunxi/clk-sunxi.c +++ b/drivers/clk/sunxi/clk-sunxi.c @@ -778,7 +778,7 @@ static void __init sunxi_mux_clk_setup(struct device_node *node, of_property_read_string(node, "clock-output-names", &clk_name); clk = clk_register_mux(NULL, clk_name, parents, i, - CLK_SET_RATE_NO_REPARENT, reg, + CLK_SET_RATE_PARENT, reg, data->shift, SUNXI_MUX_GATE_WIDTH, 0, &clk_lock); -- cgit v1.2.3-55-g7522 From 6b0b8ccff002414fab08a080c7a8a6ee3db22c0d Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Sun, 7 Dec 2014 17:43:04 +0100 Subject: clk: sunxi: Rework MMC phase clocks Instead of having three different clocks for the main MMC clock and the two phase sub-clocks, which involved having three different drivers sharing the same register, rework it to have the same single driver registering three different clocks. Signed-off-by: Maxime Ripard Reviewed-by: Chen-Yu Tsai Tested-by: Chen-Yu Tsai Acked-by: Mike Turquette --- Documentation/devicetree/bindings/clock/sunxi.txt | 13 ++- drivers/clk/sunxi/clk-mod0.c | 131 ++++++++++++---------- 2 files changed, 77 insertions(+), 67 deletions(-) (limited to 'drivers/clk') diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt index 9dc4f55a04ad..e4c42276c577 100644 --- a/Documentation/devicetree/bindings/clock/sunxi.txt +++ b/Documentation/devicetree/bindings/clock/sunxi.txt @@ -55,8 +55,7 @@ Required properties: "allwinner,sun6i-a31-apb2-gates-clk" - for the APB2 gates on A31 "allwinner,sun8i-a23-apb2-gates-clk" - for the APB2 gates on A23 "allwinner,sun5i-a13-mbus-clk" - for the MBUS clock on A13 - "allwinner,sun4i-a10-mmc-output-clk" - for the MMC output clock on A10 - "allwinner,sun4i-a10-mmc-sample-clk" - for the MMC sample clock on A10 + "allwinner,sun4i-a10-mmc-clk" - for the MMC clock "allwinner,sun4i-a10-mod0-clk" - for the module 0 family of clocks "allwinner,sun8i-a23-mbus-clk" - for the MBUS clock on A23 "allwinner,sun7i-a20-out-clk" - for the external output clocks @@ -95,6 +94,10 @@ For "allwinner,sun6i-a31-pll6-clk", there are 2 outputs. The first output is the normal PLL6 output, or "pll6". The second output is rate doubled PLL6, or "pll6x2". +The "allwinner,sun4i-a10-mmc-clk" has three different outputs: the +main clock, with the ID 0, and the output and sample clocks, with the +IDs 1 and 2, respectively. + For example: osc24M: clk@01c20050 { @@ -138,11 +141,11 @@ cpu: cpu@01c20054 { }; mmc0_clk: clk@01c20088 { - #clock-cells = <0>; - compatible = "allwinner,sun4i-mod0-clk"; + #clock-cells = <1>; + compatible = "allwinner,sun4i-a10-mmc-clk"; reg = <0x01c20088 0x4>; clocks = <&osc24M>, <&pll6 1>, <&pll5 1>; - clock-output-names = "mmc0"; + clock-output-names = "mmc0", "mmc0_output", "mmc0_sample"; }; mii_phy_tx_clk: clk@2 { diff --git a/drivers/clk/sunxi/clk-mod0.c b/drivers/clk/sunxi/clk-mod0.c index bf8fcd8c940e..e37eb6fe19e8 100644 --- a/drivers/clk/sunxi/clk-mod0.c +++ b/drivers/clk/sunxi/clk-mod0.c @@ -152,14 +152,10 @@ static void __init sun5i_a13_mbus_setup(struct device_node *node) } CLK_OF_DECLARE(sun5i_a13_mbus, "allwinner,sun5i-a13-mbus-clk", sun5i_a13_mbus_setup); -struct mmc_phase_data { - u8 offset; -}; - struct mmc_phase { struct clk_hw hw; + u8 offset; void __iomem *reg; - struct mmc_phase_data *data; spinlock_t *lock; }; @@ -175,7 +171,7 @@ static int mmc_get_phase(struct clk_hw *hw) u8 delay; value = readl(phase->reg); - delay = (value >> phase->data->offset) & 0x3; + delay = (value >> phase->offset) & 0x3; if (!delay) return 180; @@ -263,8 +259,8 @@ static int mmc_set_phase(struct clk_hw *hw, int degrees) spin_lock_irqsave(phase->lock, flags); value = readl(phase->reg); - value &= ~GENMASK(phase->data->offset + 3, phase->data->offset); - value |= delay << phase->data->offset; + value &= ~GENMASK(phase->offset + 3, phase->offset); + value |= delay << phase->offset; writel(value, phase->reg); spin_unlock_irqrestore(phase->lock, flags); @@ -276,66 +272,77 @@ static const struct clk_ops mmc_clk_ops = { .set_phase = mmc_set_phase, }; -static void __init sun4i_a10_mmc_phase_setup(struct device_node *node, - struct mmc_phase_data *data) -{ - const char *parent_names[1] = { of_clk_get_parent_name(node, 0) }; - struct clk_init_data init = { - .num_parents = 1, - .parent_names = parent_names, - .ops = &mmc_clk_ops, - }; - - struct mmc_phase *phase; - struct clk *clk; - - phase = kmalloc(sizeof(*phase), GFP_KERNEL); - if (!phase) - return; - - phase->hw.init = &init; +static DEFINE_SPINLOCK(sun4i_a10_mmc_lock); - phase->reg = of_iomap(node, 0); - if (!phase->reg) - goto err_free; +static void __init sun4i_a10_mmc_setup(struct device_node *node) +{ + struct clk_onecell_data *clk_data; + const char *parent; + void __iomem *reg; + int i; - phase->data = data; - phase->lock = &sun4i_a10_mod0_lock; + reg = of_io_request_and_map(node, 0, of_node_full_name(node)); + if (IS_ERR(reg)) { + pr_err("Couldn't map the %s clock registers\n", node->name); + return; + } - if (of_property_read_string(node, "clock-output-names", &init.name)) - init.name = node->name; + clk_data = kmalloc(sizeof(*clk_data), GFP_KERNEL); + if (!clk_data) + return; - clk = clk_register(NULL, &phase->hw); - if (IS_ERR(clk)) - goto err_unmap; + clk_data->clks = kcalloc(3, sizeof(*clk_data->clks), GFP_KERNEL); + if (!clk_data->clks) + goto err_free_data; + + clk_data->clk_num = 3; + clk_data->clks[0] = sunxi_factors_register(node, + &sun4i_a10_mod0_data, + &sun4i_a10_mmc_lock, reg); + if (!clk_data->clks[0]) + goto err_free_clks; + + parent = __clk_get_name(clk_data->clks[0]); + + for (i = 1; i < 3; i++) { + struct clk_init_data init = { + .num_parents = 1, + .parent_names = &parent, + .ops = &mmc_clk_ops, + }; + struct mmc_phase *phase; + + phase = kmalloc(sizeof(*phase), GFP_KERNEL); + if (!phase) + continue; + + phase->hw.init = &init; + phase->reg = reg; + phase->lock = &sun4i_a10_mmc_lock; + + if (i == 1) + phase->offset = 8; + else + phase->offset = 20; + + if (of_property_read_string_index(node, "clock-output-names", + i, &init.name)) + init.name = node->name; + + clk_data->clks[i] = clk_register(NULL, &phase->hw); + if (IS_ERR(clk_data->clks[i])) { + kfree(phase); + continue; + } + } - of_clk_add_provider(node, of_clk_src_simple_get, clk); + of_clk_add_provider(node, of_clk_src_onecell_get, clk_data); return; -err_unmap: - iounmap(phase->reg); -err_free: - kfree(phase); -} - - -static struct mmc_phase_data mmc_output_clk = { - .offset = 8, -}; - -static struct mmc_phase_data mmc_sample_clk = { - .offset = 20, -}; - -static void __init sun4i_a10_mmc_output_setup(struct device_node *node) -{ - sun4i_a10_mmc_phase_setup(node, &mmc_output_clk); -} -CLK_OF_DECLARE(sun4i_a10_mmc_output, "allwinner,sun4i-a10-mmc-output-clk", sun4i_a10_mmc_output_setup); - -static void __init sun4i_a10_mmc_sample_setup(struct device_node *node) -{ - sun4i_a10_mmc_phase_setup(node, &mmc_sample_clk); +err_free_clks: + kfree(clk_data->clks); +err_free_data: + kfree(clk_data); } -CLK_OF_DECLARE(sun4i_a10_mmc_sample, "allwinner,sun4i-a10-mmc-sample-clk", sun4i_a10_mmc_sample_setup); +CLK_OF_DECLARE(sun4i_a10_mmc, "allwinner,sun4i-a10-mmc-clk", sun4i_a10_mmc_setup); -- cgit v1.2.3-55-g7522 From a7d19057e7160a566bad9b2ba070a391fb78df96 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Sat, 12 Jul 2014 12:10:04 +0200 Subject: clk: sunxi: Remove custom phase function Now that we don't have any user left for our custom phase function, we can safely remove this hack from the code. Signed-off-by: Maxime Ripard Reviewed-by: Chen-Yu Tsai Tested-by: Chen-Yu Tsai --- drivers/clk/sunxi/clk-sunxi.c | 37 ------------------------------------- include/linux/clk/sunxi.h | 22 ---------------------- 2 files changed, 59 deletions(-) delete mode 100644 include/linux/clk/sunxi.h (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c index 04e0b334c678..d43c7949a07c 100644 --- a/drivers/clk/sunxi/clk-sunxi.c +++ b/drivers/clk/sunxi/clk-sunxi.c @@ -562,43 +562,6 @@ static void sun7i_a20_get_out_factors(u32 *freq, u32 parent_rate, *p = calcp; } -/** - * clk_sunxi_mmc_phase_control() - configures MMC clock phase control - */ - -void clk_sunxi_mmc_phase_control(struct clk *clk, u8 sample, u8 output) -{ - #define to_clk_composite(_hw) container_of(_hw, struct clk_composite, hw) - #define to_clk_factors(_hw) container_of(_hw, struct clk_factors, hw) - - struct clk_hw *hw = __clk_get_hw(clk); - struct clk_composite *composite = to_clk_composite(hw); - struct clk_hw *rate_hw = composite->rate_hw; - struct clk_factors *factors = to_clk_factors(rate_hw); - unsigned long flags = 0; - u32 reg; - - if (factors->lock) - spin_lock_irqsave(factors->lock, flags); - - reg = readl(factors->reg); - - /* set sample clock phase control */ - reg &= ~(0x7 << 20); - reg |= ((sample & 0x7) << 20); - - /* set output clock phase control */ - reg &= ~(0x7 << 8); - reg |= ((output & 0x7) << 8); - - writel(reg, factors->reg); - - if (factors->lock) - spin_unlock_irqrestore(factors->lock, flags); -} -EXPORT_SYMBOL(clk_sunxi_mmc_phase_control); - - /** * sunxi_factors_clk_setup() - Setup function for factor clocks */ diff --git a/include/linux/clk/sunxi.h b/include/linux/clk/sunxi.h deleted file mode 100644 index aed28c4451d9..000000000000 --- a/include/linux/clk/sunxi.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2013 - Hans de Goede - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#ifndef __LINUX_CLK_SUNXI_H_ -#define __LINUX_CLK_SUNXI_H_ - -#include - -void clk_sunxi_mmc_phase_control(struct clk *clk, u8 sample, u8 output); - -#endif -- cgit v1.2.3-55-g7522 From eb378df79e80772c1cbed32882b7378eb6f6c52c Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Tue, 13 Jan 2015 09:37:23 +0800 Subject: clk: sunxi: Add a common setup function for mmc module clocks The only difference between module clocks on different platforms is the width of the mux register bits and the valid values, which are passed in through struct factors_data. The phase clocks parts are identical. This patch generalizes the setup function, so most of the code can be reused when adding sun9i support, which has a wider mux register. Signed-off-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard --- drivers/clk/sunxi/clk-mod0.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi/clk-mod0.c b/drivers/clk/sunxi/clk-mod0.c index e37eb6fe19e8..4430d1398ce6 100644 --- a/drivers/clk/sunxi/clk-mod0.c +++ b/drivers/clk/sunxi/clk-mod0.c @@ -272,9 +272,16 @@ static const struct clk_ops mmc_clk_ops = { .set_phase = mmc_set_phase, }; -static DEFINE_SPINLOCK(sun4i_a10_mmc_lock); - -static void __init sun4i_a10_mmc_setup(struct device_node *node) +/* + * sunxi_mmc_setup - Common setup function for mmc module clocks + * + * The only difference between module clocks on different platforms is the + * width of the mux register bits and the valid values, which are passed in + * through struct factors_data. The phase clocks parts are identical. + */ +static void __init sunxi_mmc_setup(struct device_node *node, + const struct factors_data *data, + spinlock_t *lock) { struct clk_onecell_data *clk_data; const char *parent; @@ -296,9 +303,7 @@ static void __init sun4i_a10_mmc_setup(struct device_node *node) goto err_free_data; clk_data->clk_num = 3; - clk_data->clks[0] = sunxi_factors_register(node, - &sun4i_a10_mod0_data, - &sun4i_a10_mmc_lock, reg); + clk_data->clks[0] = sunxi_factors_register(node, data, lock, reg); if (!clk_data->clks[0]) goto err_free_clks; @@ -318,7 +323,7 @@ static void __init sun4i_a10_mmc_setup(struct device_node *node) phase->hw.init = &init; phase->reg = reg; - phase->lock = &sun4i_a10_mmc_lock; + phase->lock = lock; if (i == 1) phase->offset = 8; @@ -345,4 +350,11 @@ err_free_clks: err_free_data: kfree(clk_data); } + +static DEFINE_SPINLOCK(sun4i_a10_mmc_lock); + +static void __init sun4i_a10_mmc_setup(struct device_node *node) +{ + sunxi_mmc_setup(node, &sun4i_a10_mod0_data, &sun4i_a10_mmc_lock); +} CLK_OF_DECLARE(sun4i_a10_mmc, "allwinner,sun4i-a10-mmc-clk", sun4i_a10_mmc_setup); -- cgit v1.2.3-55-g7522 From 61af4d8dceeb179b62cb342f4008ce3774d3d1fd Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Sat, 17 Jan 2015 13:19:26 +0800 Subject: clk: sunxi: Add mod0 and mmc module clock support for A80 The module 0 style clocks, or storage module clocks as named in the official SDK, are almost the same as the module 0 clocks on earlier Allwinner SoCs. The only difference is wider mux register bits. As with earlier Allwinner SoCs, mmc module clocks are a special case of mod0 clocks, with phase controls for 2 child clocks, output and sample. This patch adds support for both. Signed-off-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard --- Documentation/devicetree/bindings/clock/sunxi.txt | 7 +++-- drivers/clk/sunxi/clk-mod0.c | 32 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) (limited to 'drivers/clk') diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt index e4c42276c577..0dfd018ba47b 100644 --- a/Documentation/devicetree/bindings/clock/sunxi.txt +++ b/Documentation/devicetree/bindings/clock/sunxi.txt @@ -56,7 +56,9 @@ Required properties: "allwinner,sun8i-a23-apb2-gates-clk" - for the APB2 gates on A23 "allwinner,sun5i-a13-mbus-clk" - for the MBUS clock on A13 "allwinner,sun4i-a10-mmc-clk" - for the MMC clock + "allwinner,sun9i-a80-mmc-clk" - for mmc module clocks on A80 "allwinner,sun4i-a10-mod0-clk" - for the module 0 family of clocks + "allwinner,sun9i-a80-mod0-clk" - for module 0 (storage) clocks on A80 "allwinner,sun8i-a23-mbus-clk" - for the MBUS clock on A23 "allwinner,sun7i-a20-out-clk" - for the external output clocks "allwinner,sun7i-a20-gmac-clk" - for the GMAC clock module on A20/A31 @@ -72,7 +74,8 @@ Required properties for all clocks: - #clock-cells : from common clock binding; shall be set to 0 except for the following compatibles where it shall be set to 1: "allwinner,*-gates-clk", "allwinner,sun4i-pll5-clk", - "allwinner,sun4i-pll6-clk", "allwinner,sun6i-a31-pll6-clk" + "allwinner,sun4i-pll6-clk", "allwinner,sun6i-a31-pll6-clk", + "allwinner,*-usb-clk", "allwinner,*-mmc-clk" - clock-output-names : shall be the corresponding names of the outputs. If the clock module only has one output, the name shall be the module name. @@ -94,7 +97,7 @@ For "allwinner,sun6i-a31-pll6-clk", there are 2 outputs. The first output is the normal PLL6 output, or "pll6". The second output is rate doubled PLL6, or "pll6x2". -The "allwinner,sun4i-a10-mmc-clk" has three different outputs: the +The "allwinner,*-mmc-clk" clocks have three different outputs: the main clock, with the ID 0, and the output and sample clocks, with the IDs 1 and 2, respectively. diff --git a/drivers/clk/sunxi/clk-mod0.c b/drivers/clk/sunxi/clk-mod0.c index 4430d1398ce6..ec8f5a1fca09 100644 --- a/drivers/clk/sunxi/clk-mod0.c +++ b/drivers/clk/sunxi/clk-mod0.c @@ -130,6 +130,30 @@ static struct platform_driver sun4i_a10_mod0_clk_driver = { }; module_platform_driver(sun4i_a10_mod0_clk_driver); +static const struct factors_data sun9i_a80_mod0_data __initconst = { + .enable = 31, + .mux = 24, + .muxmask = BIT(3) | BIT(2) | BIT(1) | BIT(0), + .table = &sun4i_a10_mod0_config, + .getter = sun4i_a10_get_mod0_factors, +}; + +static void __init sun9i_a80_mod0_setup(struct device_node *node) +{ + void __iomem *reg; + + reg = of_io_request_and_map(node, 0, of_node_full_name(node)); + if (IS_ERR(reg)) { + pr_err("Could not get registers for mod0-clk: %s\n", + node->name); + return; + } + + sunxi_factors_register(node, &sun9i_a80_mod0_data, + &sun4i_a10_mod0_lock, reg); +} +CLK_OF_DECLARE(sun9i_a80_mod0, "allwinner,sun9i-a80-mod0-clk", sun9i_a80_mod0_setup); + static DEFINE_SPINLOCK(sun5i_a13_mbus_lock); static void __init sun5i_a13_mbus_setup(struct device_node *node) @@ -358,3 +382,11 @@ static void __init sun4i_a10_mmc_setup(struct device_node *node) sunxi_mmc_setup(node, &sun4i_a10_mod0_data, &sun4i_a10_mmc_lock); } CLK_OF_DECLARE(sun4i_a10_mmc, "allwinner,sun4i-a10-mmc-clk", sun4i_a10_mmc_setup); + +static DEFINE_SPINLOCK(sun9i_a80_mmc_lock); + +static void __init sun9i_a80_mmc_setup(struct device_node *node) +{ + sunxi_mmc_setup(node, &sun9i_a80_mod0_data, &sun9i_a80_mmc_lock); +} +CLK_OF_DECLARE(sun9i_a80_mmc, "allwinner,sun9i-a80-mmc-clk", sun9i_a80_mmc_setup); -- cgit v1.2.3-55-g7522 From 7a6fca879f59824963cd456d8cc5db24ac5acfc0 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Tue, 20 Jan 2015 23:46:31 +0800 Subject: clk: sunxi: Add driver for A80 MMC config clocks/resets On the A80 SoC, the 4 mmc controllers each have a separate register controlling their register access clocks and reset controls. These registers in turn share a ahb clock gate and reset control. This patch adds a platform device driver for these controls. It requires both clocks and reset controls to be available, so using CLK_OF_DECLARE might not be the best way. Signed-off-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard --- Documentation/devicetree/bindings/clock/sunxi.txt | 25 ++- drivers/clk/sunxi/Makefile | 1 + drivers/clk/sunxi/clk-sun9i-mmc.c | 219 ++++++++++++++++++++++ 3 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 drivers/clk/sunxi/clk-sun9i-mmc.c (limited to 'drivers/clk') diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt index 0dfd018ba47b..60b44285250d 100644 --- a/Documentation/devicetree/bindings/clock/sunxi.txt +++ b/Documentation/devicetree/bindings/clock/sunxi.txt @@ -57,6 +57,7 @@ Required properties: "allwinner,sun5i-a13-mbus-clk" - for the MBUS clock on A13 "allwinner,sun4i-a10-mmc-clk" - for the MMC clock "allwinner,sun9i-a80-mmc-clk" - for mmc module clocks on A80 + "allwinner,sun9i-a80-mmc-config-clk" - for mmc gates + resets on A80 "allwinner,sun4i-a10-mod0-clk" - for the module 0 family of clocks "allwinner,sun9i-a80-mod0-clk" - for module 0 (storage) clocks on A80 "allwinner,sun8i-a23-mbus-clk" - for the MBUS clock on A23 @@ -75,7 +76,8 @@ Required properties for all clocks: the following compatibles where it shall be set to 1: "allwinner,*-gates-clk", "allwinner,sun4i-pll5-clk", "allwinner,sun4i-pll6-clk", "allwinner,sun6i-a31-pll6-clk", - "allwinner,*-usb-clk", "allwinner,*-mmc-clk" + "allwinner,*-usb-clk", "allwinner,*-mmc-clk", + "allwinner,*-mmc-config-clk" - clock-output-names : shall be the corresponding names of the outputs. If the clock module only has one output, the name shall be the module name. @@ -83,6 +85,10 @@ Required properties for all clocks: And "allwinner,*-usb-clk" clocks also require: - reset-cells : shall be set to 1 +The "allwinner,sun9i-a80-mmc-config-clk" clock also requires: +- #reset-cells : shall be set to 1 +- resets : shall be the reset control phandle for the mmc block. + For "allwinner,sun7i-a20-gmac-clk", the parent clocks shall be fixed rate dummy clocks at 25 MHz and 125 MHz, respectively. See example. @@ -101,6 +107,10 @@ The "allwinner,*-mmc-clk" clocks have three different outputs: the main clock, with the ID 0, and the output and sample clocks, with the IDs 1 and 2, respectively. +The "allwinner,sun9i-a80-mmc-config-clk" clock has one clock/reset output +per mmc controller. The number of outputs is determined by the size of +the address block, which is related to the overall mmc block. + For example: osc24M: clk@01c20050 { @@ -176,3 +186,16 @@ gmac_clk: clk@01c20164 { clocks = <&mii_phy_tx_clk>, <&gmac_int_tx_clk>; clock-output-names = "gmac"; }; + +mmc_config_clk: clk@01c13000 { + compatible = "allwinner,sun9i-a80-mmc-config-clk"; + reg = <0x01c13000 0x10>; + clocks = <&ahb0_gates 8>; + clock-names = "ahb"; + resets = <&ahb0_resets 8>; + reset-names = "ahb"; + #clock-cells = <1>; + #reset-cells = <1>; + clock-output-names = "mmc0_config", "mmc1_config", + "mmc2_config", "mmc3_config"; +}; diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile index a66953c0f430..3a5292e3fcf8 100644 --- a/drivers/clk/sunxi/Makefile +++ b/drivers/clk/sunxi/Makefile @@ -8,6 +8,7 @@ obj-y += clk-a20-gmac.o obj-y += clk-mod0.o obj-y += clk-sun8i-mbus.o obj-y += clk-sun9i-core.o +obj-y += clk-sun9i-mmc.o obj-$(CONFIG_MFD_SUN6I_PRCM) += \ clk-sun6i-ar100.o clk-sun6i-apb0.o clk-sun6i-apb0-gates.o \ diff --git a/drivers/clk/sunxi/clk-sun9i-mmc.c b/drivers/clk/sunxi/clk-sun9i-mmc.c new file mode 100644 index 000000000000..710c273648d7 --- /dev/null +++ b/drivers/clk/sunxi/clk-sun9i-mmc.c @@ -0,0 +1,219 @@ +/* + * Copyright 2015 Chen-Yu Tsai + * + * Chen-Yu Tsai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SUN9I_MMC_WIDTH 4 + +#define SUN9I_MMC_GATE_BIT 16 +#define SUN9I_MMC_RESET_BIT 18 + +struct sun9i_mmc_clk_data { + spinlock_t lock; + void __iomem *membase; + struct clk *clk; + struct reset_control *reset; + struct clk_onecell_data clk_data; + struct reset_controller_dev rcdev; +}; + +static int sun9i_mmc_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct sun9i_mmc_clk_data *data = container_of(rcdev, + struct sun9i_mmc_clk_data, + rcdev); + unsigned long flags; + void __iomem *reg = data->membase + SUN9I_MMC_WIDTH * id; + u32 val; + + clk_prepare_enable(data->clk); + spin_lock_irqsave(&data->lock, flags); + + val = readl(reg); + writel(val & ~BIT(SUN9I_MMC_RESET_BIT), reg); + + spin_unlock_irqrestore(&data->lock, flags); + clk_disable_unprepare(data->clk); + + return 0; +} + +static int sun9i_mmc_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct sun9i_mmc_clk_data *data = container_of(rcdev, + struct sun9i_mmc_clk_data, + rcdev); + unsigned long flags; + void __iomem *reg = data->membase + SUN9I_MMC_WIDTH * id; + u32 val; + + clk_prepare_enable(data->clk); + spin_lock_irqsave(&data->lock, flags); + + val = readl(reg); + writel(val | BIT(SUN9I_MMC_RESET_BIT), reg); + + spin_unlock_irqrestore(&data->lock, flags); + clk_disable_unprepare(data->clk); + + return 0; +} + +static struct reset_control_ops sun9i_mmc_reset_ops = { + .assert = sun9i_mmc_reset_assert, + .deassert = sun9i_mmc_reset_deassert, +}; + +static int sun9i_a80_mmc_config_clk_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct sun9i_mmc_clk_data *data; + struct clk_onecell_data *clk_data; + const char *clk_name = np->name; + const char *clk_parent; + struct resource *r; + int count, i, ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + spin_lock_init(&data->lock); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + /* one clock/reset pair per word */ + count = DIV_ROUND_UP((r->end - r->start + 1), SUN9I_MMC_WIDTH); + data->membase = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(data->membase)) + return PTR_ERR(data->membase); + + clk_data = &data->clk_data; + clk_data->clk_num = count; + clk_data->clks = devm_kcalloc(&pdev->dev, count, sizeof(struct clk *), + GFP_KERNEL); + if (!clk_data->clks) + return -ENOMEM; + + data->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(data->clk)) { + dev_err(&pdev->dev, "Could not get clock\n"); + return PTR_ERR(data->clk); + } + + data->reset = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(data->reset)) { + dev_err(&pdev->dev, "Could not get reset control\n"); + return PTR_ERR(data->reset); + } + + ret = reset_control_deassert(data->reset); + if (ret) { + dev_err(&pdev->dev, "Reset deassert err %d\n", ret); + return ret; + } + + clk_parent = __clk_get_name(data->clk); + for (i = 0; i < count; i++) { + of_property_read_string_index(np, "clock-output-names", + i, &clk_name); + + clk_data->clks[i] = clk_register_gate(&pdev->dev, clk_name, + clk_parent, 0, + data->membase + SUN9I_MMC_WIDTH * i, + SUN9I_MMC_GATE_BIT, 0, + &data->lock); + + if (IS_ERR(clk_data->clks[i])) { + ret = PTR_ERR(clk_data->clks[i]); + goto err_clk_register; + } + } + + ret = of_clk_add_provider(np, of_clk_src_onecell_get, clk_data); + if (ret) + goto err_clk_provider; + + data->rcdev.owner = THIS_MODULE; + data->rcdev.nr_resets = count; + data->rcdev.ops = &sun9i_mmc_reset_ops; + data->rcdev.of_node = pdev->dev.of_node; + + ret = reset_controller_register(&data->rcdev); + if (ret) + goto err_rc_reg; + + platform_set_drvdata(pdev, data); + + return 0; + +err_rc_reg: + of_clk_del_provider(np); + +err_clk_provider: + for (i = 0; i < count; i++) + clk_unregister(clk_data->clks[i]); + +err_clk_register: + reset_control_assert(data->reset); + + return ret; +} + +static int sun9i_a80_mmc_config_clk_remove(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct sun9i_mmc_clk_data *data = platform_get_drvdata(pdev); + struct clk_onecell_data *clk_data = &data->clk_data; + int i; + + reset_controller_unregister(&data->rcdev); + of_clk_del_provider(np); + for (i = 0; i < clk_data->clk_num; i++) + clk_unregister(clk_data->clks[i]); + + reset_control_assert(data->reset); + + return 0; +} + +static const struct of_device_id sun9i_a80_mmc_config_clk_dt_ids[] = { + { .compatible = "allwinner,sun9i-a80-mmc-config-clk" }, + { /* sentinel */ } +}; + +static struct platform_driver sun9i_a80_mmc_config_clk_driver = { + .driver = { + .name = "sun9i-a80-mmc-config-clk", + .of_match_table = sun9i_a80_mmc_config_clk_dt_ids, + }, + .probe = sun9i_a80_mmc_config_clk_probe, + .remove = sun9i_a80_mmc_config_clk_remove, +}; +module_platform_driver(sun9i_a80_mmc_config_clk_driver); + +MODULE_AUTHOR("Chen-Yu Tsai "); +MODULE_DESCRIPTION("Allwinner A80 MMC clock/reset Driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-55-g7522 From 6424e0aeebc4d21f14d5bfcbc8436c2836c38a75 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 24 Jan 2015 12:56:31 +0100 Subject: clk: sunxi: rewrite sun9i_a80_get_pll4_factors() The old implementation of sun9i_a80_get_pll4_factors() has several issues, it checks against 256 / 512 in various places where it should use 255 / 511, it does the wrong thing for low frequencies which are an even multiple of 6 MHz, e.g. if you ask it for 72 MHz it will result in 144 Mhz, and it does not take into account that n must be at least 12. Moreover it is quite hard to read / follow it. This commit rewrites it to be correct in all cases, and makes it much easier to follow the code / to read. Cc: Chen-Yu Tsai Signed-off-by: Hans de Goede Signed-off-by: Maxime Ripard --- drivers/clk/sunxi/clk-sun9i-core.c | 57 +++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 28 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi/clk-sun9i-core.c b/drivers/clk/sunxi/clk-sun9i-core.c index 9b5e7a1f72d0..d8da77d72861 100644 --- a/drivers/clk/sunxi/clk-sun9i-core.c +++ b/drivers/clk/sunxi/clk-sun9i-core.c @@ -24,50 +24,51 @@ /** - * sun9i_a80_get_pll4_factors() - calculates n, p, m factors for PLL1 + * sun9i_a80_get_pll4_factors() - calculates n, p, m factors for PLL4 * PLL4 rate is calculated as follows * rate = (parent_rate * n >> p) / (m + 1); - * parent_rate is always 24Mhz + * parent_rate is always 24MHz * * p and m are named div1 and div2 in Allwinner's SDK */ static void sun9i_a80_get_pll4_factors(u32 *freq, u32 parent_rate, - u8 *n, u8 *k, u8 *m, u8 *p) + u8 *n_ret, u8 *k, u8 *m_ret, u8 *p_ret) { - int div; + int n; + int m = 1; + int p = 1; - /* Normalize value to a 6M multiple */ - div = DIV_ROUND_UP(*freq, 6000000); + /* Normalize value to a 6 MHz multiple (24 MHz / 4) */ + n = DIV_ROUND_UP(*freq, 6000000); - /* divs above 256 cannot be odd */ - if (div > 256) - div = round_up(div, 2); + /* If n is too large switch to steps of 12 MHz */ + if (n > 255) { + m = 0; + n = (n + 1) / 2; + } + + /* If n is still too large switch to steps of 24 MHz */ + if (n > 255) { + p = 0; + n = (n + 1) / 2; + } - /* divs above 512 must be a multiple of 4 */ - if (div > 512) - div = round_up(div, 4); + /* n must be between 12 and 255 */ + if (n > 255) + n = 255; + else if (n < 12) + n = 12; - *freq = 6000000 * div; + *freq = ((24000000 * n) >> p) / (m + 1); /* we were called to round the frequency, we can now return */ - if (n == NULL) + if (n_ret == NULL) return; - /* p will be 1 for divs under 512 */ - if (div < 512) - *p = 1; - else - *p = 0; - - /* m will be 1 if div is odd */ - if (div & 1) - *m = 1; - else - *m = 0; - - /* calculate a suitable n based on m and p */ - *n = div / (*p + 1) / (*m + 1); + *n_ret = n; + *m_ret = m; + *p_ret = p; } static struct clk_factors_config sun9i_a80_pll4_config = { -- cgit v1.2.3-55-g7522 From 76820fcf7aa5a418b69cb7bed31b62d1feb1d6ad Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 24 Jan 2015 12:56:32 +0100 Subject: sunxi: clk: Set sun6i-pll1 n_start = 1 For all pll-s on sun6i n == 0 means use a multiplier of 1, rather then 0 as it means on sun4i / sun5i / sun7i. n_start = 1 is already correctly set for sun6i pll6, but was missing for pll1, this commit fixes this. Cc: Chen-Yu Tsai Signed-off-by: Hans de Goede Signed-off-by: Maxime Ripard --- drivers/clk/sunxi/clk-sunxi.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c index d43c7949a07c..9b79f8907cc5 100644 --- a/drivers/clk/sunxi/clk-sunxi.c +++ b/drivers/clk/sunxi/clk-sunxi.c @@ -584,6 +584,7 @@ static struct clk_factors_config sun6i_a31_pll1_config = { .kwidth = 2, .mshift = 0, .mwidth = 2, + .n_start = 1, }; static struct clk_factors_config sun8i_a23_pll1_config = { -- cgit v1.2.3-55-g7522