From patchwork Fri Jun 8 07:35:32 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ike Panhc X-Patchwork-Id: 163724 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from chlorine.canonical.com (chlorine.canonical.com [91.189.94.204]) by ozlabs.org (Postfix) with ESMTP id 9993AB72B9 for ; Fri, 8 Jun 2012 17:35:51 +1000 (EST) Received: from localhost ([127.0.0.1] helo=chlorine.canonical.com) by chlorine.canonical.com with esmtp (Exim 4.71) (envelope-from ) id 1SctjQ-00005Z-Mw; Fri, 08 Jun 2012 07:35:40 +0000 Received: from youngberry.canonical.com ([91.189.89.112]) by chlorine.canonical.com with esmtp (Exim 4.71) (envelope-from ) id 1SctjM-0008UI-Eo for kernel-team@lists.ubuntu.com; Fri, 08 Jun 2012 07:35:36 +0000 Received: from [210.242.151.101] (helo=canonical.com) by youngberry.canonical.com with esmtpsa (TLS1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1SctjL-0003PL-7b for kernel-team@lists.ubuntu.com; Fri, 08 Jun 2012 07:35:36 +0000 From: Ike Panhc To: kernel-team@lists.ubuntu.com Subject: [PATCH 05/10] UBUNTU: SAUCE: clk: add highbank clock support Date: Fri, 8 Jun 2012 15:35:32 +0800 Message-Id: <1339140932-10336-1-git-send-email-ike.pan@canonical.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1339140847-10159-1-git-send-email-ike.pan@canonical.com> References: <1339140847-10159-1-git-send-email-ike.pan@canonical.com> X-BeenThere: kernel-team@lists.ubuntu.com X-Mailman-Version: 2.1.13 Precedence: list List-Id: Kernel team discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: kernel-team-bounces@lists.ubuntu.com Errors-To: kernel-team-bounces@lists.ubuntu.com From: Rob Herring BugLink: http://launchpad.net/bugs/1008345 This adds real clock support to Calxeda Highbank SOC using the common clock infrastructure. Signed-off-by: Rob Herring Signed-off-by: Ike Panhc --- arch/arm/Kconfig | 1 + arch/arm/boot/dts/highbank.dts | 93 +++++++++- arch/arm/mach-highbank/Makefile | 2 +- arch/arm/mach-highbank/clock.c | 62 ------- arch/arm/mach-highbank/highbank.c | 16 ++ drivers/clk/Makefile | 2 + drivers/clk/clk-highbank.c | 336 +++++++++++++++++++++++++++++++++++++ 7 files changed, 448 insertions(+), 64 deletions(-) delete mode 100644 arch/arm/mach-highbank/clock.c create mode 100644 drivers/clk/clk-highbank.c diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 9469ca1..1d1b02c 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -363,6 +363,7 @@ config ARCH_HIGHBANK select ARM_TIMER_SP804 select CACHE_L2X0 select CLKDEV_LOOKUP + select COMMON_CLK select CPU_V7 select GENERIC_CLOCKEVENTS select HAVE_ARM_SCU diff --git a/arch/arm/boot/dts/highbank.dts b/arch/arm/boot/dts/highbank.dts index e735731..067681c 100644 --- a/arch/arm/boot/dts/highbank.dts +++ b/arch/arm/boot/dts/highbank.dts @@ -1,5 +1,5 @@ /* - * Copyright 2011 Calxeda, Inc. + * Copyright 2011-2012 Calxeda, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, @@ -24,6 +24,7 @@ compatible = "calxeda,highbank"; #address-cells = <1>; #size-cells = <1>; + clock-ranges; cpus { #address-cells = <1>; @@ -33,24 +34,32 @@ compatible = "arm,cortex-a9"; reg = <0>; next-level-cache = <&L2>; + clocks = <&a9pll>; + clock-names = "cpu"; }; cpu@1 { compatible = "arm,cortex-a9"; reg = <1>; next-level-cache = <&L2>; + clocks = <&a9pll>; + clock-names = "cpu"; }; cpu@2 { compatible = "arm,cortex-a9"; reg = <2>; next-level-cache = <&L2>; + clocks = <&a9pll>; + clock-names = "cpu"; }; cpu@3 { compatible = "arm,cortex-a9"; reg = <3>; next-level-cache = <&L2>; + clocks = <&a9pll>; + clock-names = "cpu"; }; }; @@ -75,12 +84,14 @@ compatible = "arm,cortex-a9-twd-timer"; reg = <0xfff10600 0x20>; interrupts = <1 13 0xf01>; + clocks = <&a9periphclk>; }; watchdog@fff10620 { compatible = "arm,cortex-a9-twd-wdt"; reg = <0xfff10620 0x20>; interrupts = <1 14 0xf01>; + clocks = <&a9periphclk>; }; intc: interrupt-controller@fff11000 { @@ -116,12 +127,15 @@ compatible = "calxeda,hb-sdhci"; reg = <0xffe0e000 0x1000>; interrupts = <0 90 4>; + clocks = <&eclk>; }; ipc@fff20000 { compatible = "arm,pl320", "arm,primecell"; reg = <0xfff20000 0x1000>; interrupts = <0 7 4>; + clocks = <&pclk>; + clock-names = "apb_pclk"; }; ipc-keys { @@ -134,6 +148,8 @@ gpio-controller; reg = <0xfff30000 0x1000>; interrupts = <0 14 4>; + clocks = <&pclk>; + clock-names = "apb_pclk"; }; gpiof: gpio@fff31000 { @@ -142,6 +158,8 @@ gpio-controller; reg = <0xfff31000 0x1000>; interrupts = <0 15 4>; + clocks = <&pclk>; + clock-names = "apb_pclk"; }; gpiog: gpio@fff32000 { @@ -150,6 +168,8 @@ gpio-controller; reg = <0xfff32000 0x1000>; interrupts = <0 16 4>; + clocks = <&pclk>; + clock-names = "apb_pclk"; }; gpioh: gpio@fff33000 { @@ -158,24 +178,32 @@ gpio-controller; reg = <0xfff33000 0x1000>; interrupts = <0 17 4>; + clocks = <&pclk>; + clock-names = "apb_pclk"; }; timer { compatible = "arm,sp804", "arm,primecell"; reg = <0xfff34000 0x1000>; interrupts = <0 18 4>; + clocks = <&pclk>, <&pclk>; + clock-names = "apb_pclk", "bus"; }; rtc@fff35000 { compatible = "arm,pl031", "arm,primecell"; reg = <0xfff35000 0x1000>; interrupts = <0 19 4>; + clocks = <&pclk>; + clock-names = "apb_pclk"; }; serial@fff36000 { compatible = "arm,pl011", "arm,primecell"; reg = <0xfff36000 0x1000>; interrupts = <0 20 4>; + clocks = <&pclk>, <&pclk>; + clock-names = "apb_pclk", "bus"; }; smic@fff3a000 { @@ -190,12 +218,75 @@ sregs@fff3c000 { compatible = "calxeda,hb-sregs"; reg = <0xfff3c000 0x1000>; + + clocks { + #address-cells = <1>; + #size-cells = <0>; + + osc: oscillator { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <33333000>; + }; + + ddrpll: ddrpll { + #clock-cells = <0>; + compatible = "calxeda,hb-pll-clock"; + clocks = <&osc>; + reg = <0x108>; + }; + + a9pll: a9pll { + #clock-cells = <0>; + compatible = "calxeda,hb-pll-clock"; + clocks = <&osc>; + reg = <0x100>; + }; + + a9periphclk: a9periphclk { + #clock-cells = <0>; + compatible = "calxeda,hb-a9periph-clock"; + clocks = <&a9pll>; + reg = <0x104>; + clock-divider = <4>; + }; + + a9bclk: a9bclk { + #clock-cells = <0>; + compatible = "calxeda,hb-a9bus-clock"; + clocks = <&a9pll>; + reg = <0x104>; + clock-divider = <4>; + }; + + emmcpll: emmcpll { + #clock-cells = <0>; + compatible = "calxeda,hb-pll-clock"; + clocks = <&osc>; + reg = <0x10C>; + }; + + eclk: eclk { + #clock-cells = <0>; + compatible = "calxeda,hb-emmc-clock"; + clocks = <&emmcpll>; + reg = <0x114>; + }; + + pclk: pclk { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <150000000>; + }; + }; }; dma@fff3d000 { compatible = "arm,pl330", "arm,primecell"; reg = <0xfff3d000 0x1000>; interrupts = <0 92 4>; + clocks = <&pclk>; + clock-names = "apb_pclk"; }; ethernet@fff50000 { diff --git a/arch/arm/mach-highbank/Makefile b/arch/arm/mach-highbank/Makefile index 3d4126a..b894708 100644 --- a/arch/arm/mach-highbank/Makefile +++ b/arch/arm/mach-highbank/Makefile @@ -1,4 +1,4 @@ -obj-y := clock.o highbank.o system.o smc.o +obj-y := highbank.o system.o smc.o plus_sec := $(call as-instr,.arch_extension sec,+sec) AFLAGS_smc.o :=-Wa,-march=armv7-a$(plus_sec) diff --git a/arch/arm/mach-highbank/clock.c b/arch/arm/mach-highbank/clock.c deleted file mode 100644 index c25a2ae..0000000 --- a/arch/arm/mach-highbank/clock.c +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2011 Calxeda, Inc. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope 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. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - */ -#include -#include -#include -#include -#include - -struct clk { - unsigned long rate; -}; - -int clk_enable(struct clk *clk) -{ - return 0; -} - -void clk_disable(struct clk *clk) -{} - -unsigned long clk_get_rate(struct clk *clk) -{ - return clk->rate; -} - -long clk_round_rate(struct clk *clk, unsigned long rate) -{ - return clk->rate; -} - -int clk_set_rate(struct clk *clk, unsigned long rate) -{ - return 0; -} - -static struct clk eclk = { .rate = 200000000 }; -static struct clk pclk = { .rate = 150000000 }; - -static struct clk_lookup lookups[] = { - { .clk = &pclk, .con_id = "apb_pclk", }, - { .clk = &pclk, .dev_id = "sp804", }, - { .clk = &eclk, .dev_id = "ffe0e000.sdhci", }, - { .clk = &pclk, .dev_id = "fff36000.serial", }, -}; - -void __init highbank_clocks_init(void) -{ - clkdev_add_table(lookups, ARRAY_SIZE(lookups)); -} diff --git a/arch/arm/mach-highbank/highbank.c b/arch/arm/mach-highbank/highbank.c index 0ab6b34..2df9213 100644 --- a/arch/arm/mach-highbank/highbank.c +++ b/arch/arm/mach-highbank/highbank.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -106,6 +107,16 @@ static void __init highbank_init_irq(void) #endif } +static struct clk_lookup lookups[] = { + { + .dev_id = "sp804", + .con_id = NULL, + }, { + .dev_id = "smp_twd", + .con_id = NULL, + } +}; + static void __init highbank_timer_init(void) { int irq; @@ -123,6 +134,11 @@ static void __init highbank_timer_init(void) irq = irq_of_parse_and_map(np, 0); highbank_clocks_init(); + lookups[0].clk = of_clk_get(np, 0); + + np = of_find_compatible_node(NULL, NULL, "arm,smp-twd"); + lookups[1].clk = of_clk_get(np, 0); + clkdev_add_table(lookups, ARRAY_SIZE(lookups)); sp804_clocksource_and_sched_clock_init(timer_base + 0x20, "timer1"); sp804_clockevents_init(timer_base, irq, "timer0"); diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 1f736bc..70c713f 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -2,3 +2,5 @@ obj-$(CONFIG_CLKDEV_LOOKUP) += clkdev.o obj-$(CONFIG_COMMON_CLK) += clk.o clk-fixed-rate.o clk-gate.o \ clk-mux.o clk-divider.o + +obj-$(CONFIG_ARCH_HIGHBANK) += clk-highbank.o diff --git a/drivers/clk/clk-highbank.c b/drivers/clk/clk-highbank.c new file mode 100644 index 0000000..970f6f8 --- /dev/null +++ b/drivers/clk/clk-highbank.c @@ -0,0 +1,336 @@ +/* + * Copyright 2011-2012 Calxeda, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +extern void __iomem *sregs_base; + +#define HB_PLL_LOCK_500 0x20000000 +#define HB_PLL_LOCK 0x10000000 +#define HB_PLL_DIVF_SHIFT 20 +#define HB_PLL_DIVF_MASK 0x0ff00000 +#define HB_PLL_DIVQ_SHIFT 16 +#define HB_PLL_DIVQ_MASK 0x00070000 +#define HB_PLL_DIVR_SHIFT 8 +#define HB_PLL_DIVR_MASK 0x00001f00 +#define HB_PLL_RANGE_SHIFT 4 +#define HB_PLL_RANGE_MASK 0x00000070 +#define HB_PLL_BYPASS 0x00000008 +#define HB_PLL_RESET 0x00000004 +#define HB_PLL_EXT_BYPASS 0x00000002 +#define HB_PLL_EXT_ENA 0x00000001 + +#define HB_PLL_VCO_MIN_FREQ 2133000000 +#define HB_PLL_MAX_FREQ HB_PLL_VCO_MIN_FREQ +#define HB_PLL_MIN_FREQ (HB_PLL_VCO_MIN_FREQ / 64) + +#define HB_A9_BCLK_DIV_MASK 0x00000006 +#define HB_A9_BCLK_DIV_SHIFT 1 + +struct hb_clk { + struct clk_hw hw; + void __iomem *reg; + char *parent_name; +}; +#define to_hb_clk(p) container_of(p, struct hb_clk, hw) + +static int clk_pll_prepare(struct clk_hw *clk) + { + struct hb_clk *hbclk = to_hb_clk(clk); + u32 reg; + + reg = readl(hbclk->reg); + reg &= ~HB_PLL_RESET; + writel(reg, hbclk->reg); + + while ((readl(hbclk->reg) & HB_PLL_LOCK) == 0) + ; + while ((readl(hbclk->reg) & HB_PLL_LOCK_500) == 0) + ; + + return 0; +} + +static void clk_pll_unprepare(struct clk_hw *clk) +{ + struct hb_clk *hbclk = to_hb_clk(clk); + u32 reg; + + reg = readl(hbclk->reg); + reg |= HB_PLL_RESET; + writel(reg, hbclk->reg); +} + +static int clk_pll_enable(struct clk_hw *clk) +{ + struct hb_clk *hbclk = to_hb_clk(clk); + u32 reg; + + reg = readl(hbclk->reg); + reg |= HB_PLL_EXT_ENA; + writel(reg, hbclk->reg); + + return 0; +} + +static void clk_pll_disable(struct clk_hw *clk) +{ + struct hb_clk *hbclk = to_hb_clk(clk); + u32 reg; + + reg = readl(hbclk->reg); + reg &= ~HB_PLL_EXT_ENA; + writel(reg, hbclk->reg); +} + +static unsigned long clk_pll_recalc_rate(struct clk_hw *clk, + unsigned long parent_rate) +{ + struct hb_clk *hbclk = to_hb_clk(clk); + unsigned long divf, divq, vco_freq, reg; + + reg = readl(hbclk->reg); + if (reg & HB_PLL_EXT_BYPASS) + return parent_rate; + + divf = (reg & HB_PLL_DIVF_MASK) >> HB_PLL_DIVF_SHIFT; + divq = (reg & HB_PLL_DIVQ_MASK) >> HB_PLL_DIVQ_SHIFT; + vco_freq = parent_rate * (divf + 1); + + return vco_freq / (1 << divq); +} + +static void clk_pll_calc(unsigned long rate, unsigned long ref_freq, + u32 *pdivq, u32 *pdivf) +{ + u32 divq, divf; + unsigned long vco_freq; + + if (rate < HB_PLL_MIN_FREQ) + rate = HB_PLL_MIN_FREQ; + if (rate > HB_PLL_MAX_FREQ) + rate = HB_PLL_MAX_FREQ; + + for (divq = 1; divq <= 6; divq++) { + if ((rate * (1 << divq)) >= HB_PLL_VCO_MIN_FREQ) + break; + } + + vco_freq = rate * (1 << divq); + divf = (vco_freq + (ref_freq / 2)) / ref_freq; + divf--; + + *pdivq = divq; + *pdivf = divf; +} + +static long clk_pll_round_rate(struct clk_hw *clk, unsigned long rate, + unsigned long *unused) +{ + u32 divq, divf; + unsigned long ref_freq = __clk_get_rate(__clk_get_parent(clk->clk)); + + clk_pll_calc(rate, ref_freq, &divq, &divf); + + return (ref_freq * (divf + 1)) / (1 << divq); +} + +static int clk_pll_set_rate(struct clk_hw *clk, unsigned long rate) +{ + struct hb_clk *hbclk = to_hb_clk(clk); + unsigned long parent_rate; + u32 divq, divf; + u32 reg; + + parent_rate = __clk_get_rate(__clk_get_parent(clk->clk)); + clk_pll_calc(rate, parent_rate, &divq, &divf); + + reg = readl(hbclk->reg); + if (divf != ((reg & HB_PLL_DIVF_MASK) >> HB_PLL_DIVF_SHIFT)) { + /* Need to re-lock PLL, so put it into bypass mode */ + reg |= HB_PLL_EXT_BYPASS; + writel(reg | HB_PLL_EXT_BYPASS, hbclk->reg); + + writel(reg | HB_PLL_RESET, hbclk->reg); + reg &= ~(HB_PLL_DIVF_MASK | HB_PLL_DIVQ_MASK); + reg |= (divf << HB_PLL_DIVF_SHIFT) | (divq << HB_PLL_DIVQ_SHIFT); + writel(reg | HB_PLL_RESET, hbclk->reg); + writel(reg, hbclk->reg); + + while ((readl(hbclk->reg) & HB_PLL_LOCK) == 0) + ; + while ((readl(hbclk->reg) & HB_PLL_LOCK_500) == 0) + ; + reg |= HB_PLL_EXT_ENA; + reg &= ~HB_PLL_EXT_BYPASS; + } else { + reg &= ~HB_PLL_DIVQ_MASK; + reg |= divq << HB_PLL_DIVQ_SHIFT; + } + writel(reg, hbclk->reg); + + return 0; +} + +static const struct clk_ops clk_pll_ops = { + .prepare = clk_pll_prepare, + .unprepare = clk_pll_unprepare, + .enable = clk_pll_enable, + .disable = clk_pll_disable, + .recalc_rate = clk_pll_recalc_rate, + .round_rate = clk_pll_round_rate, + .set_rate = clk_pll_set_rate, +}; + +static unsigned long clk_cpu_periphclk_recalc_rate(struct clk_hw *clk, + unsigned long parent_rate) +{ + return parent_rate / 2; +} + +static const struct clk_ops a9periphclk_ops = { + .recalc_rate = clk_cpu_periphclk_recalc_rate, +}; + +static unsigned long clk_cpu_a9bclk_recalc_rate(struct clk_hw *clk, + unsigned long parent_rate) +{ + struct hb_clk *hbclk = to_hb_clk(clk); + u32 div = (readl(hbclk->reg) & HB_A9_BCLK_DIV_MASK) >> HB_A9_BCLK_DIV_SHIFT; + + return parent_rate / (div + 2); +} + +static const struct clk_ops a9bclk_ops = { + .recalc_rate = clk_cpu_a9bclk_recalc_rate, +}; + +static unsigned long clk_periclk_recalc_rate(struct clk_hw *clk, + unsigned long parent_rate) +{ + struct hb_clk *hbclk = to_hb_clk(clk); + u32 div; + + div = readl(hbclk->reg) & 0x1f; + div++; + div *= 2; + + return parent_rate / div; +} + +static long clk_periclk_round_rate(struct clk_hw *clk, unsigned long rate, + unsigned long *unused) +{ + unsigned long parent_rate = __clk_get_rate(__clk_get_parent(clk->clk)); + u32 div; + + div = parent_rate / rate; + div++; + div &= ~0x1; + + return parent_rate / div; +} + +static int clk_periclk_set_rate(struct clk_hw *clk, unsigned long rate) +{ + struct hb_clk *hbclk = to_hb_clk(clk); + u32 div; + + div = __clk_get_rate(__clk_get_parent(clk->clk)) / rate; + if (div & 0x1) + return -EINVAL; + + writel(div >> 1, hbclk->reg); + return 0; +} + +static const struct clk_ops periclk_ops = { + .recalc_rate = clk_periclk_recalc_rate, + .round_rate = clk_periclk_round_rate, + .set_rate = clk_periclk_set_rate, +}; + +static void __init hb_clk_init(struct device_node *node, const struct clk_ops *ops) +{ + u32 reg; + struct clk *clk; + struct hb_clk *hb_clk; + const char *clk_name = node->name; + int rc; + + rc = of_property_read_u32(node, "reg", ®); + if (WARN_ON(rc)) + return; + + hb_clk = kzalloc(sizeof(*hb_clk), GFP_KERNEL); + if (WARN_ON(!hb_clk)) + return; + + hb_clk->reg = sregs_base + reg; + + of_property_read_string(node, "clock-outputs", &clk_name); + + hb_clk->parent_name = of_clk_get_parent_name(node, 0); + + printk("registering clk %s, parent = %s\n", clk_name, hb_clk->parent_name); + clk = clk_register(NULL, clk_name, ops, &hb_clk->hw, + &hb_clk->parent_name, 1, 0); + if (WARN_ON(!clk)) { + kfree(hb_clk); + return; + } + rc = of_clk_add_provider(node, NULL, clk); +} + +static void __init hb_pll_init(struct device_node *node) +{ + hb_clk_init(node, &clk_pll_ops); +} + +static void __init hb_a9periph_init(struct device_node *node) +{ + hb_clk_init(node, &a9periphclk_ops); +} + +static void __init hb_a9bus_init(struct device_node *node) +{ + hb_clk_init(node, &a9bclk_ops); +} + +static void __init hb_emmc_init(struct device_node *node) +{ + hb_clk_init(node, &periclk_ops); +} + +static const __initconst struct of_device_id clk_match[] = { + { .compatible = "fixed-clock", .data = of_fixed_clk_setup, }, + { .compatible = "calxeda,hb-pll-clock", .data = hb_pll_init, }, + { .compatible = "calxeda,hb-a9periph-clock", .data = hb_a9periph_init, }, + { .compatible = "calxeda,hb-a9bus-clock", .data = hb_a9bus_init, }, + { .compatible = "calxeda,hb-emmc-clock", .data = hb_emmc_init, }, + {} +}; + +void __init highbank_clocks_init(void) +{ + of_clk_init(clk_match); +}