From patchwork Tue Jan 22 06:12:54 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Emilio_L=C3=B3pez?= X-Patchwork-Id: 214347 Return-Path: X-Original-To: incoming-imx@patchwork.ozlabs.org Delivered-To: patchwork-incoming-imx@bilbo.ozlabs.org Received: from merlin.infradead.org (merlin.infradead.org [IPv6:2001:4978:20e::2]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 189212C007C for ; Tue, 22 Jan 2013 17:22:32 +1100 (EST) Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1TxXCQ-0002Fe-0C; Tue, 22 Jan 2013 06:19:10 +0000 Received: from avas-mr21.fibertel.com.ar ([24.232.0.140]) by merlin.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1TxXC8-0002D9-QZ for linux-arm-kernel@lists.infradead.org; Tue, 22 Jan 2013 06:18:56 +0000 Received: from 201-212-118-238.prima.net.ar ([201.212.118.238]:54730 "EHLO desktop.lan" smtp-auth: "elopez" rhost-flags-OK-OK-OK-FAIL) by avas-mr21.fibertel.com.ar with ESMTPA id S1355178Ab3AVGSm; Tue, 22 Jan 2013 03:18:42 -0300 From: =?UTF-8?q?Emilio=20L=C3=B3pez?= To: Mike Turquette , Subject: [PATCH 1/3] clk: arm: sunxi: Add a new clock driver for sunxi SOCs Date: Tue, 22 Jan 2013 03:12:54 -0300 Message-Id: <1358835176-7197-2-git-send-email-emilio@elopez.com.ar> X-Mailer: git-send-email 1.8.1.1 In-Reply-To: <1358835176-7197-1-git-send-email-emilio@elopez.com.ar> References: <1358835176-7197-1-git-send-email-emilio@elopez.com.ar> MIME-Version: 1.0 X-Fib-Al-Info: Al X-Fib-Al-MRId: 9c11a3142173395dd228bda746a098e0 X-Fib-Al: noav X-Fib-Al-SA: analyzed X-Fib-Al-From: emilio@elopez.com.ar X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20130122_011853_739869_AA00E6C6 X-CRM114-Status: GOOD ( 24.21 ) X-Spam-Score: 3.3 (+++) X-Spam-Report: SpamAssassin version 3.3.2 on merlin.infradead.org summary: Content analysis details: (3.3 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no trust [24.232.0.140 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] 2.5 TAB_IN_FROM From starts with a tab 2.8 KB_DATE_CONTAINS_TAB KB_DATE_CONTAINS_TAB Cc: Alejandro Mery , =?UTF-8?q?Emilio=20L=C3=B3pez?= , Maxime Ripard , Stefan Roese , Ezequiel Garcia X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.14 Precedence: list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: linux-arm-kernel-bounces@lists.infradead.org Errors-To: linux-arm-kernel-bounces+incoming-imx=patchwork.ozlabs.org@lists.infradead.org List-Id: linux-imx-kernel.lists.patchwork.ozlabs.org This commit implements the base CPU clocks for sunxi devices. It has been tested using a slightly modified cpufreq driver from the linux-sunxi 3.0 tree. Additionally, document the new bindings introduced by this patch, and drop the (now unused) old sunxi clock driver. Idling: / # find /sys/kernel/debug/clk -name clk_rate -print -exec cat {} \; /sys/kernel/debug/clk/osc24M/pll1/cpu/axi/ahb/apb0/clk_rate 30000000 /sys/kernel/debug/clk/osc24M/pll1/cpu/axi/ahb/clk_rate 60000000 /sys/kernel/debug/clk/osc24M/pll1/cpu/axi/clk_rate 60000000 /sys/kernel/debug/clk/osc24M/pll1/cpu/clk_rate 60000000 /sys/kernel/debug/clk/osc24M/pll1/clk_rate 60000000 /sys/kernel/debug/clk/osc24M/clk_rate 24000000 /sys/kernel/debug/clk/osc32k/clk_rate 32768 After "yes >/dev/null &": / # find /sys/kernel/debug/clk -name clk_rate -print -exec cat {} \; /sys/kernel/debug/clk/osc24M/pll1/cpu/axi/ahb/apb0/clk_rate 84000000 /sys/kernel/debug/clk/osc24M/pll1/cpu/axi/ahb/clk_rate 168000000 /sys/kernel/debug/clk/osc24M/pll1/cpu/axi/clk_rate 336000000 /sys/kernel/debug/clk/osc24M/pll1/cpu/clk_rate 1008000000 /sys/kernel/debug/clk/osc24M/pll1/clk_rate 1008000000 /sys/kernel/debug/clk/osc24M/clk_rate 24000000 /sys/kernel/debug/clk/osc32k/clk_rate 32768 Signed-off-by: Emilio López --- Documentation/devicetree/bindings/clock/sunxi.txt | 47 ++++ drivers/clk/Makefile | 2 +- drivers/clk/clk-sunxi.c | 30 --- drivers/clk/sunxi/Makefile | 5 + drivers/clk/sunxi/clk-factors.c | 175 ++++++++++++++ drivers/clk/sunxi/clk-factors.h | 25 ++ drivers/clk/sunxi/clk-fixed-gate.c | 152 +++++++++++++ drivers/clk/sunxi/clk-fixed-gate.h | 13 ++ drivers/clk/sunxi/clk-sunxi.c | 265 ++++++++++++++++++++++ 9 files changed, 683 insertions(+), 31 deletions(-) create mode 100644 Documentation/devicetree/bindings/clock/sunxi.txt delete mode 100644 drivers/clk/clk-sunxi.c create mode 100644 drivers/clk/sunxi/Makefile create mode 100644 drivers/clk/sunxi/clk-factors.c create mode 100644 drivers/clk/sunxi/clk-factors.h create mode 100644 drivers/clk/sunxi/clk-fixed-gate.c create mode 100644 drivers/clk/sunxi/clk-fixed-gate.h create mode 100644 drivers/clk/sunxi/clk-sunxi.c diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt new file mode 100644 index 0000000..446c5ca --- /dev/null +++ b/Documentation/devicetree/bindings/clock/sunxi.txt @@ -0,0 +1,47 @@ +Device Tree Clock bindings for arch-sunxi + +This binding uses the common clock binding[1]. + +[1] Documentation/devicetree/bindings/clock/clock-bindings.txt + +Required properties: +- compatible : shall be one of the following: + "allwinner,sunxi-osc-clk" - for a gatable oscillator + "allwinner,sunxi-pll1-clk" - for the main PLL clock + "allwinner,sunxi-cpu-clk" - for the CPU multiplexer clock + "allwinner,sunxi-axi-clk" - for the sunxi AXI clock + "allwinner,sunxi-ahb-clk" - for the sunxi AHB clock + "allwinner,sunxi-apb0-clk" - for the sunxi APB0 clock + +Required properties for all clocks: +- reg : shall be the control register address for the clock. +- clocks : shall be the input parent clock(s) phandle for the clock, except for + the root gatable oscillator clock where it is not present +- #clock-cells : from common clock binding; shall be set to 0. + +Additionally, the gatable oscillator clock requires: +- clock-frequency : shall be the frequency of the oscillator. + + +For example: + +osc24M: osc24M { + #clock-cells = <0>; + compatible = "allwinner,sunxi-osc-clk"; + reg = <0x01c20050 0x4>; + clock-frequency = <24000000>; +}; + +pll1: pll1@01c20000 { + #clock-cells = <0>; + compatible = "allwinner,sunxi-pll1-clk"; + reg = <0x01c20000 0x4>; + clocks = <&osc24M>; +}; + +cpu: cpu@01c20054 { + #clock-cells = <0>; + compatible = "allwinner,sunxi-cpu-clk"; + reg = <0x01c20054 0x4>; + clocks = <&osc32k>, <&osc24M>, <&pll1>; +}; diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index ee90e87..129afed 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -20,7 +20,7 @@ endif obj-$(CONFIG_MACH_LOONGSON1) += clk-ls1x.o obj-$(CONFIG_ARCH_U8500) += ux500/ obj-$(CONFIG_ARCH_VT8500) += clk-vt8500.o -obj-$(CONFIG_ARCH_SUNXI) += clk-sunxi.o +obj-$(CONFIG_ARCH_SUNXI) += sunxi/ obj-$(CONFIG_ARCH_ZYNQ) += clk-zynq.o # Chip specific diff --git a/drivers/clk/clk-sunxi.c b/drivers/clk/clk-sunxi.c deleted file mode 100644 index 0e831b5..0000000 --- a/drivers/clk/clk-sunxi.c +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2012 Maxime Ripard - * - * Maxime Ripard - * - * 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 - -static const __initconst struct of_device_id clk_match[] = { - { .compatible = "fixed-clock", .data = of_fixed_clk_setup, }, - {} -}; - -void __init sunxi_init_clocks(void) -{ - of_clk_init(clk_match); -} diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile new file mode 100644 index 0000000..8e773be --- /dev/null +++ b/drivers/clk/sunxi/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for sunxi specific clk +# + +obj-y += clk-sunxi.o clk-factors.o clk-fixed-gate.o diff --git a/drivers/clk/sunxi/clk-factors.c b/drivers/clk/sunxi/clk-factors.c new file mode 100644 index 0000000..428b47d --- /dev/null +++ b/drivers/clk/sunxi/clk-factors.c @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2013 Emilio López + * + * 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. + * + * Adjustable factor-based clock implementation + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "clk-factors.h" + +/* + * DOC: basic adjustable factor-based clock that cannot gate + * + * Traits of this clock: + * prepare - clk_prepare only ensures that parents are prepared + * enable - clk_enable only ensures that parents are enabled + * rate - rate is adjustable. + * clk->rate = (parent->rate * N * (K + 1) >> P) / (M + 1) + * parent - fixed parent. No clk_set_parent support + */ + +struct clk_factors { + struct clk_hw hw; + void __iomem *reg; + struct clk_factors_config *config; + void (*get_factors) (u32 *rate, u8 *n, u8 *k, u8 *m, u8 *p); + spinlock_t *lock; +}; + +#define to_clk_factors(_hw) container_of(_hw, struct clk_factors, hw) + +#define SETMASK(len, pos) (((-1U) >> (31-len)) << (pos)) +#define CLRMASK(len, pos) (~(SETMASK(len, pos))) +#define FACTOR_GET(bit, len, reg) (((reg) & SETMASK(len, bit)) >> (bit)) + +#define FACTOR_SET(bit, len, reg, val) \ + (((reg) & CLRMASK(len, bit)) | (val << (bit))) + +static unsigned long clk_factors_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + u8 n, k, p, m; + u32 reg; + unsigned long rate; + struct clk_factors *factors = to_clk_factors(hw); + struct clk_factors_config *config = factors->config; + + /* Fetch the register value */ + reg = readl(factors->reg); + + /* Get each individual factor */ + n = FACTOR_GET(config->nshift, config->nwidth, reg); + k = FACTOR_GET(config->kshift, config->kwidth, reg); + m = FACTOR_GET(config->mshift, config->mwidth, reg); + p = FACTOR_GET(config->pshift, config->pwidth, reg); + + /* Calculate the rate */ + rate = (parent_rate * n * (k + 1) >> p) / (m + 1); + + return rate; +} + +static long clk_factors_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct clk_factors *factors = to_clk_factors(hw); + factors->get_factors((u32 *)&rate, NULL, NULL, NULL, NULL); + + return rate; +} + +static int clk_factors_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + u8 n, k, m, p; + u32 reg; + struct clk_factors *factors = to_clk_factors(hw); + struct clk_factors_config *config = factors->config; + unsigned long flags = 0; + + factors->get_factors((u32 *)&rate, &n, &k, &m, &p); + + if (factors->lock) + spin_lock_irqsave(factors->lock, flags); + + /* Fetch the register value */ + reg = readl(factors->reg); + + /* Set up the new factors */ + reg = FACTOR_SET(config->nshift, config->nwidth, reg, n); + reg = FACTOR_SET(config->kshift, config->kwidth, reg, k); + reg = FACTOR_SET(config->mshift, config->mwidth, reg, m); + reg = FACTOR_SET(config->pshift, config->pwidth, reg, p); + + /* Apply them now */ + writel(reg, factors->reg); + + /* delay 500us so pll stabilizes */ + __delay((rate >> 20) * 500 / 2); + + if (factors->lock) + spin_unlock_irqrestore(factors->lock, flags); + + return 0; +} + +static const struct clk_ops clk_factors_ops = { + .recalc_rate = clk_factors_recalc_rate, + .round_rate = clk_factors_round_rate, + .set_rate = clk_factors_set_rate, +}; + +/** + * clk_register_factors - register a factors clock with + * the clock framework + * @dev: device registering this clock + * @name: name of this clock + * @parent_name: name of clock's parent + * @flags: framework-specific flags + * @reg: register address to adjust factors + * @config: shift and width of factors n, k, m and p + * @get_factors: function to calculate the factors for a given frequency + * @lock: shared register lock for this clock + */ +struct clk *clk_register_factors(struct device *dev, const char *name, + const char *parent_name, + unsigned long flags, void __iomem *reg, + struct clk_factors_config *config, + void (*get_factors) (u32 *rate, u8 *n, u8 *k, + u8 *m, u8 *p), + spinlock_t *lock) +{ + struct clk_factors *factors; + struct clk *clk; + struct clk_init_data init; + + /* allocate the factors */ + factors = kzalloc(sizeof(struct clk_factors), GFP_KERNEL); + if (!factors) { + pr_err("%s: could not allocate factors clk\n", __func__); + return ERR_PTR(-ENOMEM); + } + + init.name = name; + init.ops = &clk_factors_ops; + init.flags = flags; + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + + /* struct clk_factors assignments */ + factors->reg = reg; + factors->config = config; + factors->lock = lock; + factors->hw.init = &init; + factors->get_factors = get_factors; + + /* register the clock */ + clk = clk_register(dev, &factors->hw); + + if (IS_ERR(clk)) + kfree(factors); + + return clk; +} diff --git a/drivers/clk/sunxi/clk-factors.h b/drivers/clk/sunxi/clk-factors.h new file mode 100644 index 0000000..a24c889 --- /dev/null +++ b/drivers/clk/sunxi/clk-factors.h @@ -0,0 +1,25 @@ +#ifndef __MACH_SUNXI_CLK_FACTORS_H +#define __MACH_SUNXI_CLK_FACTORS_H + +#include +#include + +struct clk_factors_config { + u8 nshift; + u8 nwidth; + u8 kshift; + u8 kwidth; + u8 mshift; + u8 mwidth; + u8 pshift; + u8 pwidth; +}; + +struct clk *clk_register_factors(struct device *dev, const char *name, + const char *parent_name, + unsigned long flags, void __iomem *reg, + struct clk_factors_config *config, + void (*get_factors) (u32 *rate, u8 *n, u8 *k, + u8 *m, u8 *p), + spinlock_t *lock); +#endif diff --git a/drivers/clk/sunxi/clk-fixed-gate.c b/drivers/clk/sunxi/clk-fixed-gate.c new file mode 100644 index 0000000..b16eda5 --- /dev/null +++ b/drivers/clk/sunxi/clk-fixed-gate.c @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2013 Emilio López + * + * Based on drivers/clk/clk-gate.c, + * + * Copyright (C) 2010-2011 Canonical Ltd + * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd + * + * 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. + * + * Fixed rate, gated clock implementation + */ + +#include +#include +#include +#include +#include +#include + +#include "clk-fixed-gate.h" + +/** + * DOC: fixed rate clock which can gate and ungate it's ouput + * + * Traits of this clock: + * prepare - clk_(un)prepare only ensures parent is (un)prepared + * enable - clk_enable and clk_disable are functional & control gating + * rate - rate is always a fixed value. No clk_set_rate support + * parent - fixed parent. No clk_set_parent support + */ + +struct clk_fixed_gate { + struct clk_hw hw; + u8 bit_idx; + u8 flags; + unsigned long fixed_rate; + void __iomem *reg; + spinlock_t *lock; +}; + +#define to_clk_fixed_gate(_hw) container_of(_hw, struct clk_fixed_gate, hw) + +static void clk_fixed_gate_endisable(struct clk_hw *hw, int enable) +{ + struct clk_fixed_gate *gate = to_clk_fixed_gate(hw); + unsigned long flags = 0; + u32 reg; + + if (gate->lock) + spin_lock_irqsave(gate->lock, flags); + + reg = readl(gate->reg); + + if (enable) + reg |= BIT(gate->bit_idx); + else + reg &= ~BIT(gate->bit_idx); + + writel(reg, gate->reg); + + if (gate->lock) + spin_unlock_irqrestore(gate->lock, flags); +} + +static int clk_fixed_gate_enable(struct clk_hw *hw) +{ + clk_fixed_gate_endisable(hw, 1); + + return 0; +} + +static void clk_fixed_gate_disable(struct clk_hw *hw) +{ + clk_fixed_gate_endisable(hw, 0); +} + +static int clk_fixed_gate_is_enabled(struct clk_hw *hw) +{ + u32 reg; + struct clk_fixed_gate *gate = to_clk_fixed_gate(hw); + + reg = readl(gate->reg); + + reg &= BIT(gate->bit_idx); + + return reg ? 1 : 0; +} + +static unsigned long clk_fixed_gate_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return to_clk_fixed_gate(hw)->fixed_rate; +} + +static const struct clk_ops clk_fixed_gate_ops = { + .enable = clk_fixed_gate_enable, + .disable = clk_fixed_gate_disable, + .is_enabled = clk_fixed_gate_is_enabled, + .recalc_rate = clk_fixed_gate_recalc_rate, +}; + +/** + * clk_register_fixed_gate - register a fixed rate, + * gate clock with the clock framework + * @dev: device that is registering this clock + * @name: name of this clock + * @parent_name: name of this clock's parent + * @flags: framework-specific flags for this clock + * @reg: register address to control gating of this clock + * @bit_idx: which bit in the register controls gating of this clock + * @lock: shared register lock for this clock + */ +struct clk *clk_register_fixed_gate(struct device *dev, const char *name, + const char *parent_name, + unsigned long flags, void __iomem *reg, + u8 bit_idx, unsigned long fixed_rate, + spinlock_t *lock) +{ + struct clk_fixed_gate *gate; + struct clk *clk; + struct clk_init_data init; + + /* allocate the gate */ + gate = kzalloc(sizeof(struct clk_fixed_gate), GFP_KERNEL); + if (!gate) { + pr_err("%s: could not allocate fixed gated clk\n", __func__); + return ERR_PTR(-ENOMEM); + } + + init.name = name; + init.ops = &clk_fixed_gate_ops; + init.flags = flags | CLK_IS_BASIC; + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + + /* struct clk_fixed_gate assignments */ + gate->fixed_rate = fixed_rate; + gate->reg = reg; + gate->bit_idx = bit_idx; + gate->lock = lock; + gate->hw.init = &init; + + clk = clk_register(dev, &gate->hw); + + if (IS_ERR(clk)) + kfree(gate); + + return clk; +} diff --git a/drivers/clk/sunxi/clk-fixed-gate.h b/drivers/clk/sunxi/clk-fixed-gate.h new file mode 100644 index 0000000..29d9ed3 --- /dev/null +++ b/drivers/clk/sunxi/clk-fixed-gate.h @@ -0,0 +1,13 @@ +#ifndef __MACH_SUNXI_CLK_FIXED_GATE_H +#define __MACH_SUNXI_CLK_FIXED_GATE_H + +#include +#include + +struct clk *clk_register_fixed_gate(struct device *dev, const char *name, + const char *parent_name, + unsigned long flags, void __iomem *reg, + u8 bit_idx, unsigned long fixed_rate, + spinlock_t *lock); + +#endif diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c new file mode 100644 index 0000000..cb587a0 --- /dev/null +++ b/drivers/clk/sunxi/clk-sunxi.c @@ -0,0 +1,265 @@ +/* + * Copyright 2013 Emilio López + * + * Emilio López + * + * 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 "clk-factors.h" +#include "clk-fixed-gate.h" + +static DEFINE_SPINLOCK(clk_lock); + +/** + * sunxi_osc_clk_setup() - Setup function for gatable oscillator + */ + +#define SUNXI_OSC24M_GATE 0 + +static void __init sunxi_osc_clk_setup(struct device_node *node) +{ + struct clk *clk; + const char *clk_name = node->name; + void *reg; + u32 rate; + + reg = of_iomap(node, 0); + + if (of_property_read_u32(node, "clock-frequency", &rate)) + return; + + clk = clk_register_fixed_gate(NULL, clk_name, NULL, + CLK_IS_ROOT | CLK_IGNORE_UNUSED, + reg, SUNXI_OSC24M_GATE, rate, &clk_lock); + + if (clk) { + of_clk_add_provider(node, of_clk_src_simple_get, clk); + clk_register_clkdev(clk, clk_name, NULL); + } +} + + + +/** + * sunxi_get_pll1_factors() - calculates n, k, m, p factors for PLL1 + * PLL1 rate is calculated as follows + * rate = (parent_rate * n * (k + 1) >> p) / (m + 1); + * parent_rate is always 24Mhz + */ + +static void sunxi_get_pll1_factors(u32 *freq, u8 *n, u8 *k, u8 *m, u8 *p) +{ + u8 div; + + /* Normalize value to a 6M multiple */ + div = *freq / 6000000; + *freq = 6000000 * div; + + /* we were called to round the frequency, we can now return */ + if (n == NULL) + return; + + /* m is always zero for pll1 */ + *m = 0; + + /* k is 1 only on these cases */ + if (*freq >= 768000000 || *freq == 42000000 || *freq == 54000000) + *k = 1; + else + *k = 0; + + /* p will be 3 for divs under 10 */ + if (div < 10) + *p = 3; + + /* p will be 2 for divs between 10 - 20 and odd divs under 32 */ + else if (div < 20 || (div < 32 && (div & 1))) + *p = 2; + + /* p will be 1 for even divs under 32, divs under 40 and odd pairs + * of divs between 40-62 */ + else if (div < 40 || (div < 64 && (div & 2))) + *p = 1; + + /* any other entries have p = 0 */ + else + *p = 0; + + /* calculate a suitable n based on k and p */ + div <<= *p; + div /= (*k + 1); + *n = div / 4; +} + +/** + * sunxi_pll1_clk_setup() - Setup function for PLL1 clock + */ + +struct clk_factors_config pll1_config = { + .nshift = 8, + .nwidth = 5, + .kshift = 4, + .kwidth = 2, + .mshift = 0, + .mwidth = 2, + .pshift = 16, + .pwidth = 2, +}; + +static void __init sunxi_pll1_clk_setup(struct device_node *node) +{ + struct clk *clk; + const char *clk_name = node->name; + const char *parent; + void *reg; + + reg = of_iomap(node, 0); + + parent = of_clk_get_parent_name(node, 0); + + clk = clk_register_factors(NULL, clk_name, parent, CLK_IGNORE_UNUSED, + reg, &pll1_config, sunxi_get_pll1_factors, + &clk_lock); + + if (clk) { + of_clk_add_provider(node, of_clk_src_simple_get, clk); + clk_register_clkdev(clk, clk_name, NULL); + } +} + + + +/** + * sunxi_cpu_clk_setup() - Setup function for CPU mux + */ + +#define SUNXI_CPU_GATE 16 +#define SUNXI_CPU_GATE_WIDTH 2 + +static void __init sunxi_cpu_clk_setup(struct device_node *node) +{ + struct clk *clk; + const char *clk_name = node->name; + const char **parents = kmalloc(sizeof(char *) * 5, GFP_KERNEL); + void *reg; + int i = 0; + + reg = of_iomap(node, 0); + + while (i < 5 && (parents[i] = of_clk_get_parent_name(node, i)) != NULL) + i++; + + clk = clk_register_mux(NULL, clk_name, parents, i, 0, reg, + SUNXI_CPU_GATE, SUNXI_CPU_GATE_WIDTH, + 0, &clk_lock); + + if (clk) { + of_clk_add_provider(node, of_clk_src_simple_get, clk); + clk_register_clkdev(clk, clk_name, NULL); + } +} + + + +/** + * sunxi_divider_clk_setup() - Setup function for simple divider clocks + */ + +#define SUNXI_DIVISOR_WIDTH 2 + +struct div_data { + u8 div; + u8 pow; +}; + +static const __initconst struct div_data axi_data = { + .div = 0, + .pow = 0, +}; + +static const __initconst struct div_data ahb_data = { + .div = 4, + .pow = 1, +}; + +static const __initconst struct div_data apb0_data = { + .div = 8, + .pow = 1, +}; + +static void __init sunxi_divider_clk_setup(struct device_node *node, u8 shift, + u8 power_of_two) +{ + struct clk *clk; + const char *clk_name = node->name; + const char *clk_parent; + void *reg; + + reg = of_iomap(node, 0); + + clk_parent = of_clk_get_parent_name(node, 0); + + clk = clk_register_divider(NULL, clk_name, clk_parent, 0, + reg, shift, SUNXI_DIVISOR_WIDTH, + power_of_two ? CLK_DIVIDER_POWER_OF_TWO : 0, + &clk_lock); + if (clk) { + of_clk_add_provider(node, of_clk_src_simple_get, clk); + clk_register_clkdev(clk, clk_name, NULL); + } +} + + +/* Matches for of_clk_init */ +static const __initconst struct of_device_id clk_match[] = { + {.compatible = "fixed-clock", .data = of_fixed_clk_setup,}, + {.compatible = "allwinner,sunxi-osc-clk", .data = sunxi_osc_clk_setup,}, + {.compatible = "allwinner,sunxi-pll1-clk", .data = sunxi_pll1_clk_setup,}, + {.compatible = "allwinner,sunxi-cpu-clk", .data = sunxi_cpu_clk_setup,}, + {} +}; + +/* Matches for divider clocks */ +static const __initconst struct of_device_id clk_div_match[] = { + {.compatible = "allwinner,sunxi-axi-clk", .data = &axi_data,}, + {.compatible = "allwinner,sunxi-ahb-clk", .data = &ahb_data,}, + {.compatible = "allwinner,sunxi-apb0-clk", .data = &apb0_data,}, + {} +}; + +static void __init of_sunxi_divider_clock_setup(void) +{ + struct device_node *np; + const struct div_data *data; + const struct of_device_id *match; + + for_each_matching_node(np, clk_div_match) { + match = of_match_node(clk_div_match, np); + data = match->data; + sunxi_divider_clk_setup(np, data->div, data->pow); + } +} + +void __init sunxi_init_clocks(void) +{ + /* Register all the simple sunxi clocks on DT */ + of_clk_init(clk_match); + + /* Register divider clocks */ + of_sunxi_divider_clock_setup(); +}