From 5665ef5e26fa8e3fd802153af908a39ea64c55c7 Mon Sep 17 00:00:00 2001 From: Alex Frid Date: Wed, 18 Jan 2012 21:11:14 -0800 Subject: ARM: tegra: clock: Fix Tegra3 clock locking Added locking for non-atomic access to shared registers for the following clocks: - secondary PLL dividers - audio doublers - cml (sata/pcie) clock controls Added locking for peripheral clocks secondary reference counting (register access is atomic, but some clocks may share an enable bit). Updated comments for external output clocks (shared access already protected). Reviewed-on: http://git-master/r/76163 Change-Id: If656bf13d966bf4590d55c5509860110efea937b Signed-off-by: Alex Frid Signed-off-by: Varun Wadekar Reviewed-on: http://git-master/r/76814 Reviewed-by: Automatic_Commit_Validation_User --- arch/arm/mach-tegra/tegra3_clocks.c | 62 +++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/arch/arm/mach-tegra/tegra3_clocks.c b/arch/arm/mach-tegra/tegra3_clocks.c index 3fe531fe2e5b..0e67f48c4f2e 100644 --- a/arch/arm/mach-tegra/tegra3_clocks.c +++ b/arch/arm/mach-tegra/tegra3_clocks.c @@ -1,7 +1,7 @@ /* * arch/arm/mach-tegra/tegra3_clocks.c * - * Copyright (C) 2010-2011 NVIDIA Corporation + * Copyright (C) 2010-2012 NVIDIA Corporation * * 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 @@ -391,8 +391,10 @@ static void __iomem *misc_gp_hidrev_base = IO_ADDRESS(TEGRA_APB_MISC_BASE); /* * Some peripheral clocks share an enable bit, so refcount the enable bits - * in registers CLK_ENABLE_L, ... CLK_ENABLE_W + * in registers CLK_ENABLE_L, ... CLK_ENABLE_W, and protect refcount updates + * with lock */ +static DEFINE_SPINLOCK(periph_refcount_lock); static int tegra_periph_clk_enable_refcount[CLK_OUT_ENB_NUM * 32]; #define clk_writel(value, reg) \ @@ -1906,7 +1908,9 @@ static struct clk_ops tegra_plle_ops = { .disable = tegra3_plle_clk_disable, }; -/* Clock divider ops */ +/* Clock divider ops (non-atomic shared register access) */ +static DEFINE_SPINLOCK(pll_div_lock); + static void tegra3_pll_div_clk_init(struct clk *c) { if (c->flags & DIV_U71) { @@ -1939,9 +1943,11 @@ static int tegra3_pll_div_clk_enable(struct clk *c) { u32 val; u32 new_val; + unsigned long flags; pr_debug("%s: %s\n", __func__, c->name); if (c->flags & DIV_U71) { + spin_lock_irqsave(&pll_div_lock, flags); val = clk_readl(c->reg); new_val = val >> c->reg_shift; new_val &= 0xFFFF; @@ -1951,6 +1957,7 @@ static int tegra3_pll_div_clk_enable(struct clk *c) val &= ~(0xFFFF << c->reg_shift); val |= new_val << c->reg_shift; clk_writel_delay(val, c->reg); + spin_unlock_irqrestore(&pll_div_lock, flags); return 0; } else if (c->flags & DIV_2) { return 0; @@ -1962,9 +1969,11 @@ static void tegra3_pll_div_clk_disable(struct clk *c) { u32 val; u32 new_val; + unsigned long flags; pr_debug("%s: %s\n", __func__, c->name); if (c->flags & DIV_U71) { + spin_lock_irqsave(&pll_div_lock, flags); val = clk_readl(c->reg); new_val = val >> c->reg_shift; new_val &= 0xFFFF; @@ -1974,6 +1983,7 @@ static void tegra3_pll_div_clk_disable(struct clk *c) val &= ~(0xFFFF << c->reg_shift); val |= new_val << c->reg_shift; clk_writel_delay(val, c->reg); + spin_unlock_irqrestore(&pll_div_lock, flags); } } @@ -1983,12 +1993,14 @@ static int tegra3_pll_div_clk_set_rate(struct clk *c, unsigned long rate) u32 new_val; int divider_u71; unsigned long parent_rate = clk_get_rate(c->parent); + unsigned long flags; pr_debug("%s: %s %lu\n", __func__, c->name, rate); if (c->flags & DIV_U71) { divider_u71 = clk_div71_get_divider( parent_rate, rate, c->flags, ROUND_DIVIDER_UP); if (divider_u71 >= 0) { + spin_lock_irqsave(&pll_div_lock, flags); val = clk_readl(c->reg); new_val = val >> c->reg_shift; new_val &= 0xFFFF; @@ -2002,6 +2014,7 @@ static int tegra3_pll_div_clk_set_rate(struct clk *c, unsigned long rate) clk_writel_delay(val, c->reg); c->div = divider_u71 + 2; c->mul = 2; + spin_unlock_irqrestore(&pll_div_lock, flags); return 0; } } else if (c->flags & DIV_2) @@ -2122,11 +2135,16 @@ static void tegra3_periph_clk_init(struct clk *c) static int tegra3_periph_clk_enable(struct clk *c) { + unsigned long flags; pr_debug("%s on clock %s\n", __func__, c->name); + spin_lock_irqsave(&periph_refcount_lock, flags); + tegra_periph_clk_enable_refcount[c->u.periph.clk_num]++; - if (tegra_periph_clk_enable_refcount[c->u.periph.clk_num] > 1) + if (tegra_periph_clk_enable_refcount[c->u.periph.clk_num] > 1) { + spin_unlock_irqrestore(&periph_refcount_lock, flags); return 0; + } clk_writel_delay(PERIPH_CLK_TO_BIT(c), PERIPH_CLK_TO_ENB_SET_REG(c)); if (!(c->flags & PERIPH_NO_RESET) && !(c->flags & PERIPH_MANUAL_RESET)) { @@ -2135,14 +2153,17 @@ static int tegra3_periph_clk_enable(struct clk *c) clk_writel(PERIPH_CLK_TO_BIT(c), PERIPH_CLK_TO_RST_CLR_REG(c)); } } + spin_unlock_irqrestore(&periph_refcount_lock, flags); return 0; } static void tegra3_periph_clk_disable(struct clk *c) { - unsigned long val; + unsigned long val, flags; pr_debug("%s on clock %s\n", __func__, c->name); + spin_lock_irqsave(&periph_refcount_lock, flags); + if (c->refcnt) tegra_periph_clk_enable_refcount[c->u.periph.clk_num]--; @@ -2156,6 +2177,7 @@ static void tegra3_periph_clk_disable(struct clk *c) clk_writel_delay( PERIPH_CLK_TO_BIT(c), PERIPH_CLK_TO_ENB_CLR_REG(c)); } + spin_unlock_irqrestore(&periph_refcount_lock, flags); } static void tegra3_periph_clk_reset(struct clk *c, bool assert) @@ -2434,7 +2456,7 @@ static struct clk_ops tegra_pciex_clk_ops = { .reset = tegra3_periph_clk_reset, }; -/* Output clock ops */ +/* Output clock ops (non-atomic shared register access) */ static DEFINE_SPINLOCK(clk_out_lock); @@ -2597,7 +2619,9 @@ static struct clk_ops tegra_emc_clk_ops = { .shared_bus_update = &tegra3_clk_shared_bus_update, }; -/* Clock doubler ops */ +/* Clock doubler ops (non-atomic shared register access) */ +static DEFINE_SPINLOCK(doubler_lock); + static void tegra3_clk_double_init(struct clk *c) { u32 val = clk_readl(c->reg); @@ -2612,17 +2636,23 @@ static int tegra3_clk_double_set_rate(struct clk *c, unsigned long rate) { u32 val; unsigned long parent_rate = clk_get_rate(c->parent); + unsigned long flags; + if (rate == parent_rate) { + spin_lock_irqsave(&doubler_lock, flags); val = clk_readl(c->reg) | (0x1 << c->reg_shift); clk_writel(val, c->reg); c->mul = 1; c->div = 1; + spin_unlock_irqrestore(&doubler_lock, flags); return 0; } else if (rate == 2 * parent_rate) { + spin_lock_irqsave(&doubler_lock, flags); val = clk_readl(c->reg) & (~(0x1 << c->reg_shift)); clk_writel(val, c->reg); c->mul = 2; c->div = 1; + spin_unlock_irqrestore(&doubler_lock, flags); return 0; } return -EINVAL; @@ -2706,7 +2736,9 @@ static struct clk_ops tegra_audio_sync_clk_ops = { .set_parent = tegra3_audio_sync_clk_set_parent, }; -/* cml0 (pcie), and cml1 (sata) clock ops */ +/* cml0 (pcie), and cml1 (sata) clock ops (non-atomic shared register access) */ +static DEFINE_SPINLOCK(cml_lock); + static void tegra3_cml_clk_init(struct clk *c) { u32 val = clk_readl(c->reg); @@ -2715,17 +2747,27 @@ static void tegra3_cml_clk_init(struct clk *c) static int tegra3_cml_clk_enable(struct clk *c) { - u32 val = clk_readl(c->reg); + u32 val; + unsigned long flags; + + spin_lock_irqsave(&cml_lock, flags); + val = clk_readl(c->reg); val |= (0x1 << c->u.periph.clk_num); clk_writel(val, c->reg); + spin_unlock_irqrestore(&cml_lock, flags); return 0; } static void tegra3_cml_clk_disable(struct clk *c) { - u32 val = clk_readl(c->reg); + u32 val; + unsigned long flags; + + spin_lock_irqsave(&cml_lock, flags); + val = clk_readl(c->reg); val &= ~(0x1 << c->u.periph.clk_num); clk_writel(val, c->reg); + spin_unlock_irqrestore(&cml_lock, flags); } static struct clk_ops tegra_cml_clk_ops = { -- cgit v1.2.3