From patchwork Wed Mar 1 21:20:07 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Philipp Tomsich X-Patchwork-Id: 735641 X-Patchwork-Delegate: jagannadh.teki@gmail.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.denx.de (dione.denx.de [81.169.180.215]) by ozlabs.org (Postfix) with ESMTP id 3vcDCg6Qbzz9sCX for ; Mon, 6 Mar 2017 19:55:59 +1100 (AEDT) Received: by lists.denx.de (Postfix, from userid 105) id 9E72CC21E1B; Mon, 6 Mar 2017 08:54:43 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on lists.denx.de X-Spam-Level: X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=unavailable autolearn_force=no version=3.4.0 Received: from lists.denx.de (localhost [IPv6:::1]) by lists.denx.de (Postfix) with ESMTP id 0AB1CC21E2E; Mon, 6 Mar 2017 08:41:25 +0000 (UTC) Received: by lists.denx.de (Postfix, from userid 105) id 9E34FC21CD8; Wed, 1 Mar 2017 21:20:43 +0000 (UTC) Received: from mail.theobroma-systems.com (vegas.theobroma-systems.com [144.76.126.164]) by lists.denx.de (Postfix) with ESMTPS id BEBDDC21C83 for ; Wed, 1 Mar 2017 21:20:31 +0000 (UTC) Received: from [86.59.122.178] (port=37205 helo=android.lan) by mail.theobroma-systems.com with esmtpsa (TLS1.2:DHE_RSA_AES_256_CBC_SHA256:256) (Exim 4.80) (envelope-from ) id 1cjBfh-0003sr-SS; Wed, 01 Mar 2017 22:20:30 +0100 From: Philipp Tomsich To: u-boot@lists.denx.de Date: Wed, 1 Mar 2017 22:20:07 +0100 Message-Id: <1488403208-55963-9-git-send-email-philipp.tomsich@theobroma-systems.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1488403208-55963-1-git-send-email-philipp.tomsich@theobroma-systems.com> References: <1488403208-55963-1-git-send-email-philipp.tomsich@theobroma-systems.com> MIME-Version: 1.0 X-Mailman-Approved-At: Mon, 06 Mar 2017 08:41:02 +0000 Cc: Andre Przywara , Klaus Goger , Maxime Ripard , Philipp Tomsich , Jagan Teki Subject: [U-Boot] [PATCH v3 8/9] sunxi: add clock driver (UCLASS_CLK) support for sunxi X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.18 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" When CONFIG_CLK is defined, we now provide support for the basic clock configuration of peripherals on sunxi: * clk-sunxi-ccu.c implements the CCU based (new-style) binding based on the Linux implementation. And for handling the binding of the always-on (R_*) subsystems: * clk-sunxi-mod.c implements support for module clocks, which performs parent selection (determined via the device-tree) and determines/configures a pre-divider and divider when setting a clock-rate * clk-sunxi-gate.c: implements an clk-gate to gate individual modules (i.e. 'allwinner,sunxi-multi-bus-gates-clk') Signed-off-by: Philipp Tomsich --- drivers/clk/Makefile | 1 + drivers/clk/sunxi/Makefile | 30 ++ drivers/clk/sunxi/ccu-compatibility.h | 232 ++++++++ drivers/clk/sunxi/ccu-runtime-divider.c | 115 ++++ drivers/clk/sunxi/ccu-runtime-fixedfactor.c | 17 + drivers/clk/sunxi/ccu-sun50i-a64.c | 810 ++++++++++++++++++++++++++++ drivers/clk/sunxi/ccu-sun50i-a64.h | 64 +++ drivers/clk/sunxi/ccu_common.c | 27 + drivers/clk/sunxi/ccu_common.h | 67 +++ drivers/clk/sunxi/ccu_div.c | 134 +++++ drivers/clk/sunxi/ccu_div.h | 170 ++++++ drivers/clk/sunxi/ccu_frac.c | 106 ++++ drivers/clk/sunxi/ccu_frac.h | 46 ++ drivers/clk/sunxi/ccu_gate.c | 80 +++ drivers/clk/sunxi/ccu_gate.h | 45 ++ drivers/clk/sunxi/ccu_mp.c | 159 ++++++ drivers/clk/sunxi/ccu_mp.h | 70 +++ drivers/clk/sunxi/ccu_mult.h | 39 ++ drivers/clk/sunxi/ccu_mux.c | 201 +++++++ drivers/clk/sunxi/ccu_mux.h | 105 ++++ drivers/clk/sunxi/ccu_nk.c | 154 ++++++ drivers/clk/sunxi/ccu_nk.h | 64 +++ drivers/clk/sunxi/ccu_nkm.c | 184 +++++++ drivers/clk/sunxi/ccu_nkm.h | 84 +++ drivers/clk/sunxi/ccu_nkmp.c | 171 ++++++ drivers/clk/sunxi/ccu_nkmp.h | 64 +++ drivers/clk/sunxi/ccu_nm.c | 148 +++++ drivers/clk/sunxi/ccu_nm.h | 84 +++ drivers/clk/sunxi/clk-sunxi-ccu.c | 550 +++++++++++++++++++ drivers/clk/sunxi/clk-sunxi-gate.c | 92 ++++ drivers/clk/sunxi/clk-sunxi-mod.c | 241 +++++++++ include/dt-bindings/clock/sun50i-a64-ccu.h | 134 +++++ 32 files changed, 4488 insertions(+) create mode 100644 drivers/clk/sunxi/Makefile create mode 100644 drivers/clk/sunxi/ccu-compatibility.h create mode 100644 drivers/clk/sunxi/ccu-runtime-divider.c create mode 100644 drivers/clk/sunxi/ccu-runtime-fixedfactor.c create mode 100644 drivers/clk/sunxi/ccu-sun50i-a64.c create mode 100644 drivers/clk/sunxi/ccu-sun50i-a64.h create mode 100644 drivers/clk/sunxi/ccu_common.c create mode 100644 drivers/clk/sunxi/ccu_common.h create mode 100644 drivers/clk/sunxi/ccu_div.c create mode 100644 drivers/clk/sunxi/ccu_div.h create mode 100644 drivers/clk/sunxi/ccu_frac.c create mode 100644 drivers/clk/sunxi/ccu_frac.h create mode 100644 drivers/clk/sunxi/ccu_gate.c create mode 100644 drivers/clk/sunxi/ccu_gate.h create mode 100644 drivers/clk/sunxi/ccu_mp.c create mode 100644 drivers/clk/sunxi/ccu_mp.h create mode 100644 drivers/clk/sunxi/ccu_mult.h create mode 100644 drivers/clk/sunxi/ccu_mux.c create mode 100644 drivers/clk/sunxi/ccu_mux.h create mode 100644 drivers/clk/sunxi/ccu_nk.c create mode 100644 drivers/clk/sunxi/ccu_nk.h create mode 100644 drivers/clk/sunxi/ccu_nkm.c create mode 100644 drivers/clk/sunxi/ccu_nkm.h create mode 100644 drivers/clk/sunxi/ccu_nkmp.c create mode 100644 drivers/clk/sunxi/ccu_nkmp.h create mode 100644 drivers/clk/sunxi/ccu_nm.c create mode 100644 drivers/clk/sunxi/ccu_nm.h create mode 100644 drivers/clk/sunxi/clk-sunxi-ccu.c create mode 100644 drivers/clk/sunxi/clk-sunxi-gate.c create mode 100644 drivers/clk/sunxi/clk-sunxi-mod.c create mode 100644 include/dt-bindings/clock/sun50i-a64-ccu.h diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 884c21c..7ae8029 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_CLK) += clk-uclass.o clk_fixed_rate.o obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/ +obj-$(CONFIG_ARCH_SUNXI) += sunxi/ obj-$(CONFIG_SANDBOX) += clk_sandbox.o obj-$(CONFIG_SANDBOX) += clk_sandbox_test.o obj-$(CONFIG_MACH_PIC32) += clk_pic32.o diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile new file mode 100644 index 0000000..8124b99 --- /dev/null +++ b/drivers/clk/sunxi/Makefile @@ -0,0 +1,30 @@ +# +# SPDX-License-Identifier: GPL-2.0+ +# + +# Legacy clock drivers (used for the always-on subsystem) +obj-y += clk-sunxi-mod.o +obj-y += clk-sunxi-gate.o + +# CCU modules (ported from Linux) +obj-y += \ + ccu_common.o \ + ccu_div.o \ + ccu_frac.o \ + ccu_gate.o \ + ccu_mp.o \ + ccu_mux.o \ + ccu_nk.o \ + ccu_nkm.o \ + ccu_nkmp.o \ + ccu_nm.o + +# CCU runtime +obj-y += ccu-runtime-divider.o \ + ccu-runtime-fixedfactor.o + +obj-y += clk-sunxi-ccu.o + +# CCU per-cpu config +obj-$(CONFIG_MACH_SUN50I) += ccu-sun50i-a64.o + diff --git a/drivers/clk/sunxi/ccu-compatibility.h b/drivers/clk/sunxi/ccu-compatibility.h new file mode 100644 index 0000000..a5bb9ca --- /dev/null +++ b/drivers/clk/sunxi/ccu-compatibility.h @@ -0,0 +1,232 @@ +/* + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _SUNXI_CLK_CCU_COMPATIBILITY_H_ +#define _SUNXI_CLK_CCU_COMPATIBILITY_H_ + +/* + * The defines in this file provide for compatibility with data structures + * and definitions used in the Linux kernel and originate there in the file + * include/linux/clk-provider.h + */ + +#include +#include +#include +#include +#include + +/* + * flags used across common struct clk. these flags should only affect the + * top-level framework. custom flags for dealing with hardware specifics + * belong in struct clk_foo + */ +#define CLK_SET_RATE_GATE BIT(0) /* must be gated across rate change */ +#define CLK_SET_PARENT_GATE BIT(1) /* must be gated across re-parent */ +#define CLK_SET_RATE_PARENT BIT(2) /* propagate rate change up one level */ +#define CLK_IGNORE_UNUSED BIT(3) /* do not gate even if unused */ +/* unused */ +#define CLK_IS_BASIC BIT(5) /* Basic clk, can't do a to_clk_foo() */ +#define CLK_GET_RATE_NOCACHE BIT(6) /* do not use the cached clk rate */ +#define CLK_SET_RATE_NO_REPARENT BIT(7) /* don't re-parent on rate change */ +#define CLK_GET_ACCURACY_NOCACHE BIT(8) /* do not use the cached clk accuracy */ +#define CLK_RECALC_NEW_RATES BIT(9) /* recalc rates after notifications */ +#define CLK_SET_RATE_UNGATE BIT(10) /* clock needs to run to set rate */ +#define CLK_IS_CRITICAL BIT(11) /* do not gate, ever */ +/* parents need enable during gate/ungate, set rate and re-parent */ +#define CLK_OPS_PARENT_ENABLE BIT(12) + +struct clk_hw; + +/** + * struct clk_rate_request - Structure encoding the clk constraints that + * a clock user might require. + * + * @rate: Requested clock rate. This field will be adjusted by + * clock drivers according to hardware capabilities. + * @min_rate: Minimum rate imposed by clk users. + * @max_rate: Maximum rate imposed by clk users. + * @best_parent_rate: The best parent rate a parent can provide to fulfill the + * requested constraints. + * @best_parent_hw: The most appropriate parent clock that fulfills the + * requested constraints. + * + */ +struct clk_rate_request { + unsigned long rate; + unsigned long min_rate; + unsigned long max_rate; + unsigned long best_parent_rate; + struct clk_hw *best_parent_hw; +}; + +/** + * struct sunxi_ccu_clk_ops - Callback operations for hardware clocks + * + * Based on 'struct clk_ops' in linux/clk-provider.h with all callbacks + * that are not used by the CCU driver implementation removed. + * + * For full documentation on each callback, please refer to the Linux + * source tree. + */ +struct sunxi_ccu_clk_ops { + int (*enable)(struct clk_hw *hw); + void (*disable)(struct clk_hw *hw); + int (*is_enabled)(struct clk_hw *hw); + unsigned long (*recalc_rate)(struct clk_hw *hw, + unsigned long parent_rate); + long (*round_rate)(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate); + int (*determine_rate)(struct clk_hw *hw, + struct clk_rate_request *req); + int (*set_parent)(struct clk_hw *hw, u8 index); + u8 (*get_parent)(struct clk_hw *hw); + int (*set_rate)(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate); +}; + +/** + * struct clk_hw - handle for traversing from a struct clk to its corresponding + * hardware-specific structure, adapted for use with U-Boot and the CCU driver. + * + * The key differences to the Linux implementation relate to our driver being + * much simpler than their shared clock infrastructure, so we don't cache much + * info (i.e. no struct clock_core), but need a pointer back to our device + * driver instance. + */ + +struct clk_hw { + struct udevice *dev; + struct clk_hw **parents; + const struct clk_init_data *init; +}; + +/** + * struct clk_init_data - holds init data that's common to all clocks and is + * shared between the clock provider and the common clock framework. + * + * @name: clock name + * @ops: operations this clock supports + * @parent_names: array of string names for all possible parents + * @num_parents: number of possible parents + * @flags: framework-level hints and quirks + */ +struct clk_init_data { + const char *name; + const struct sunxi_ccu_clk_ops *ops; + const char * const *parent_names; + u8 num_parents; + unsigned long flags; +}; + +struct clk_div_table { + unsigned int val; + unsigned int div; +}; + +/** + * struct clk_divider - adjustable divider clock + * + * @hw: handle between common and hardware-specific interfaces + * @reg: register containing the divider + * @shift: shift to the divider bit field + * @width: width of the divider bit field + * @table: array of value/divider pairs, last entry should have div = 0 + * @lock: register lock + * + * Clock with an adjustable divider affecting its output frequency. Implements + * .recalc_rate, .set_rate and .round_rate + * + * Flags: + * CLK_DIVIDER_ONE_BASED - by default the divisor is the value read from the + * register plus one. If CLK_DIVIDER_ONE_BASED is set then the divider is + * the raw value read from the register, with the value of zero considered + * invalid, unless CLK_DIVIDER_ALLOW_ZERO is set. + * CLK_DIVIDER_POWER_OF_TWO - clock divisor is 2 raised to the value read from + * the hardware register + * CLK_DIVIDER_ALLOW_ZERO - Allow zero divisors. For dividers which have + * CLK_DIVIDER_ONE_BASED set, it is possible to end up with a zero divisor. + * Some hardware implementations gracefully handle this case and allow a + * zero divisor by not modifying their input clock + * (divide by one / bypass). + * CLK_DIVIDER_HIWORD_MASK - The divider settings are only in lower 16-bit + * of this register, and mask of divider bits are in higher 16-bit of this + * register. While setting the divider bits, higher 16-bit should also be + * updated to indicate changing divider bits. + * CLK_DIVIDER_ROUND_CLOSEST - Makes the best calculated divider to be rounded + * to the closest integer instead of the up one. + * CLK_DIVIDER_READ_ONLY - The divider settings are preconfigured and should + * not be changed by the clock framework. + * CLK_DIVIDER_MAX_AT_ZERO - For dividers which are like CLK_DIVIDER_ONE_BASED + * except when the value read from the register is zero, the divisor is + * 2^width of the field. + */ +struct clk_divider { + struct clk_hw hw; + void __iomem *reg; + u8 shift; + u8 width; + u8 flags; + const struct clk_div_table *table; +}; + +#define to_clk_divider(_hw) container_of(_hw, struct clk_divider, hw) + +#define CLK_DIVIDER_ONE_BASED BIT(0) +#define CLK_DIVIDER_POWER_OF_TWO BIT(1) +#define CLK_DIVIDER_ALLOW_ZERO BIT(2) +#define CLK_DIVIDER_HIWORD_MASK BIT(3) +#define CLK_DIVIDER_ROUND_CLOSEST BIT(4) +#define CLK_DIVIDER_READ_ONLY BIT(5) +#define CLK_DIVIDER_MAX_AT_ZERO BIT(6) + +unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate, + unsigned int val, const struct clk_div_table *table, + unsigned long flags); +int divider_get_val(unsigned long rate, unsigned long parent_rate, + const struct clk_div_table *table, u8 width, + unsigned long flags); + +/** + * struct clk_fixed_factor - fixed multiplier and divider clock + * + * @hw: handle between common and hardware-specific interfaces + * @mult: multiplier + * @div: divider + * + * Clock with a fixed multiplier and divider. The output frequency is the + * parent clock rate divided by div and multiplied by mult. + * Implements .recalc_rate, .set_rate and .round_rate + */ + +struct clk_fixed_factor { + struct clk_hw hw; + unsigned int mult; + unsigned int div; +}; + +#define to_clk_fixed_factor(_hw) container_of(_hw, struct clk_fixed_factor, hw) + +extern const struct sunxi_ccu_clk_ops clk_fixed_factor_ops; + + +/* helper functions */ +const char *clk_hw_get_name(const struct clk_hw *hw); +unsigned int clk_hw_get_num_parents(const struct clk_hw *hw); +struct clk_hw *clk_hw_get_parent(const struct clk_hw *hw); +struct clk_hw *clk_hw_get_parent_by_index(const struct clk_hw *hw, + unsigned int index); +unsigned long clk_hw_get_rate(const struct clk_hw *hw); +unsigned long clk_hw_get_flags(const struct clk_hw *hw); +bool clk_hw_is_enabled(const struct clk_hw *hw); +int __clk_mux_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req); +void clk_hw_reparent(struct clk_hw *hw, struct clk_hw *new_parent); + +struct clk_hw_onecell_data { + unsigned int num; + struct clk_hw *hws[]; +}; + +#endif diff --git a/drivers/clk/sunxi/ccu-runtime-divider.c b/drivers/clk/sunxi/ccu-runtime-divider.c new file mode 100644 index 0000000..7ddaf39 --- /dev/null +++ b/drivers/clk/sunxi/ccu-runtime-divider.c @@ -0,0 +1,115 @@ +/* + * Derived from drivers/clk/clk-divider.c in Linux. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include + +#include "ccu_common.h" + +#define div_mask(width) ((1 << (width)) - 1) + +static unsigned int _get_table_div(const struct clk_div_table *table, + unsigned int val) +{ + const struct clk_div_table *clkt; + + for (clkt = table; clkt->div; clkt++) + if (clkt->val == val) + return clkt->div; + return 0; +} + +static unsigned int _get_div(const struct clk_div_table *table, + unsigned int val, unsigned long flags, u8 width) +{ + if (flags & CLK_DIVIDER_ONE_BASED) + return val; + if (flags & CLK_DIVIDER_POWER_OF_TWO) + return 1 << val; + if (flags & CLK_DIVIDER_MAX_AT_ZERO) + return val ? val : div_mask(width) + 1; + if (table) + return _get_table_div(table, val); + return val + 1; +} + +static bool _is_valid_table_div(const struct clk_div_table *table, + unsigned int div) +{ + const struct clk_div_table *clkt; + + for (clkt = table; clkt->div; clkt++) + if (clkt->div == div) + return true; + return false; +} + +static bool _is_valid_div(const struct clk_div_table *table, unsigned int div, + unsigned long flags) +{ + if (flags & CLK_DIVIDER_POWER_OF_TWO) + return is_power_of_2(div); + if (table) + return _is_valid_table_div(table, div); + return true; +} + + +static unsigned int _get_table_val(const struct clk_div_table *table, + unsigned int div) +{ + const struct clk_div_table *clkt; + + for (clkt = table; clkt->div; clkt++) + if (clkt->div == div) + return clkt->val; + return 0; +} + +static unsigned int _get_val(const struct clk_div_table *table, + unsigned int div, unsigned long flags, u8 width) +{ + if (flags & CLK_DIVIDER_ONE_BASED) + return div; + if (flags & CLK_DIVIDER_POWER_OF_TWO) + return __ffs(div); + if (flags & CLK_DIVIDER_MAX_AT_ZERO) + return (div == div_mask(width) + 1) ? 0 : div; + if (table) + return _get_table_val(table, div); + return div - 1; +} + +int divider_get_val(unsigned long rate, unsigned long parent_rate, + const struct clk_div_table *table, u8 width, + unsigned long flags) +{ + unsigned int div, value; + + div = DIV_ROUND_UP_ULL((u64)parent_rate, rate); + + if (!_is_valid_div(table, div, flags)) + return -EINVAL; + + value = _get_val(table, div, flags, width); + + return min_t(unsigned int, value, div_mask(width)); +} + +unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate, + unsigned int val, + const struct clk_div_table *table, + unsigned long flags) +{ + struct clk_divider *divider = to_clk_divider(hw); + unsigned int div; + + div = _get_div(table, val, flags, divider->width); + if (!div) + return parent_rate; + + return DIV_ROUND_UP_ULL((u64)parent_rate, div); +} diff --git a/drivers/clk/sunxi/ccu-runtime-fixedfactor.c b/drivers/clk/sunxi/ccu-runtime-fixedfactor.c new file mode 100644 index 0000000..53a727b --- /dev/null +++ b/drivers/clk/sunxi/ccu-runtime-fixedfactor.c @@ -0,0 +1,17 @@ +/* + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include "ccu_common.h" + +static unsigned long clk_fixed_factor_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_fixed_factor *cff = to_clk_fixed_factor(hw); + return (parent_rate * cff->mult) / cff->div; +} + +const struct sunxi_ccu_clk_ops clk_fixed_factor_ops = { + .recalc_rate = clk_fixed_factor_recalc_rate, +}; diff --git a/drivers/clk/sunxi/ccu-sun50i-a64.c b/drivers/clk/sunxi/ccu-sun50i-a64.c new file mode 100644 index 0000000..8b78eb8 --- /dev/null +++ b/drivers/clk/sunxi/ccu-sun50i-a64.c @@ -0,0 +1,810 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu-sun50i-a64.c in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include + +#include "ccu_common.h" + +#include "ccu_div.h" +#include "ccu_gate.h" +#include "ccu_mp.h" +#include "ccu_mult.h" +#include "ccu_nk.h" +#include "ccu_nkm.h" +#include "ccu_nkmp.h" +#include "ccu_nm.h" + +#include "ccu-sun50i-a64.h" + +static struct ccu_nkmp pll_cpux_clk = { + .enable = BIT(31), + .lock = BIT(28), + .n = _SUNXI_CCU_MULT(8, 5), + .k = _SUNXI_CCU_MULT(4, 2), + .m = _SUNXI_CCU_DIV(0, 2), + .p = _SUNXI_CCU_DIV_MAX(16, 2, 4), + .common = { + .reg = 0x000, + .hw.init = CLK_HW_INIT("pll-cpux", + "osc24M", + &ccu_nkmp_ops, + CLK_SET_RATE_UNGATE), + }, +}; + +/* + * The Audio PLL is supposed to have 4 outputs: 3 fixed factors from + * the base (2x, 4x and 8x), and one variable divider (the one true + * pll audio). + * + * We don't have any need for the variable divider for now, so we just + * hardcode it to match with the clock names + */ +#define SUN50I_A64_PLL_AUDIO_REG 0x008 + +static SUNXI_CCU_NM_WITH_GATE_LOCK(pll_audio_base_clk, "pll-audio-base", + "osc24M", 0x008, + 8, 7, /* N */ + 0, 5, /* M */ + BIT(31), /* gate */ + BIT(28), /* lock */ + CLK_SET_RATE_UNGATE); + +static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_video0_clk, "pll-video0", + "osc24M", 0x010, + 8, 7, /* N */ + 0, 4, /* M */ + BIT(24), /* frac enable */ + BIT(25), /* frac select */ + 270000000, /* frac rate 0 */ + 297000000, /* frac rate 1 */ + BIT(31), /* gate */ + BIT(28), /* lock */ + CLK_SET_RATE_UNGATE); + +static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_ve_clk, "pll-ve", + "osc24M", 0x018, + 8, 7, /* N */ + 0, 4, /* M */ + BIT(24), /* frac enable */ + BIT(25), /* frac select */ + 270000000, /* frac rate 0 */ + 297000000, /* frac rate 1 */ + BIT(31), /* gate */ + BIT(28), /* lock */ + CLK_SET_RATE_UNGATE); + +static SUNXI_CCU_NKM_WITH_GATE_LOCK(pll_ddr0_clk, "pll-ddr0", + "osc24M", 0x020, + 8, 5, /* N */ + 4, 2, /* K */ + 0, 2, /* M */ + BIT(31), /* gate */ + BIT(28), /* lock */ + CLK_SET_RATE_UNGATE); + + +static struct ccu_nk pll_periph0_clk = { + .enable = BIT(31), + .lock = BIT(28), + .n = _SUNXI_CCU_MULT(8, 5), + .k = _SUNXI_CCU_MULT_MIN(4, 2, 2), + .fixed_post_div = 2, + .common = { + .reg = 0x028, + .features = CCU_FEATURE_FIXED_POSTDIV, + .hw.init = CLK_HW_INIT("pll-periph0", "osc24M", + &ccu_nk_ops, CLK_SET_RATE_UNGATE), + }, +}; + +static struct ccu_nk pll_periph1_clk = { + .enable = BIT(31), + .lock = BIT(28), + .n = _SUNXI_CCU_MULT(8, 5), + .k = _SUNXI_CCU_MULT_MIN(4, 2, 2), + .fixed_post_div = 2, + .common = { + .reg = 0x02c, + .features = CCU_FEATURE_FIXED_POSTDIV, + .hw.init = CLK_HW_INIT("pll-periph1", "osc24M", + &ccu_nk_ops, CLK_SET_RATE_UNGATE), + }, +}; + +static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_video1_clk, "pll-video1", + "osc24M", 0x030, + 8, 7, /* N */ + 0, 4, /* M */ + BIT(24), /* frac enable */ + BIT(25), /* frac select */ + 270000000, /* frac rate 0 */ + 297000000, /* frac rate 1 */ + BIT(31), /* gate */ + BIT(28), /* lock */ + CLK_SET_RATE_UNGATE); + +static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_gpu_clk, "pll-gpu", + "osc24M", 0x038, + 8, 7, /* N */ + 0, 4, /* M */ + BIT(24), /* frac enable */ + BIT(25), /* frac select */ + 270000000, /* frac rate 0 */ + 297000000, /* frac rate 1 */ + BIT(31), /* gate */ + BIT(28), /* lock */ + CLK_SET_RATE_UNGATE); + +/* + * The output function can be changed to something more complex that + * we do not handle yet. + * + * Hardcode the mode so that we don't fall in that case. + */ +#define SUN50I_A64_PLL_MIPI_REG 0x040 + +static struct ccu_nkm pll_mipi_clk = { + .enable = BIT(31), + .lock = BIT(28), + .n = _SUNXI_CCU_MULT(8, 4), + .k = _SUNXI_CCU_MULT_MIN(4, 2, 2), + .m = _SUNXI_CCU_DIV(0, 4), + .common = { + .reg = 0x040, + .hw.init = CLK_HW_INIT("pll-mipi", "pll-video0", + &ccu_nkm_ops, CLK_SET_RATE_UNGATE), + }, +}; + +static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_hsic_clk, "pll-hsic", + "osc24M", 0x044, + 8, 7, /* N */ + 0, 4, /* M */ + BIT(24), /* frac enable */ + BIT(25), /* frac select */ + 270000000, /* frac rate 0 */ + 297000000, /* frac rate 1 */ + BIT(31), /* gate */ + BIT(28), /* lock */ + CLK_SET_RATE_UNGATE); + +static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_de_clk, "pll-de", + "osc24M", 0x048, + 8, 7, /* N */ + 0, 4, /* M */ + BIT(24), /* frac enable */ + BIT(25), /* frac select */ + 270000000, /* frac rate 0 */ + 297000000, /* frac rate 1 */ + BIT(31), /* gate */ + BIT(28), /* lock */ + CLK_SET_RATE_UNGATE); + +static SUNXI_CCU_NM_WITH_GATE_LOCK(pll_ddr1_clk, "pll-ddr1", + "osc24M", 0x04c, + 8, 7, /* N */ + 0, 2, /* M */ + BIT(31), /* gate */ + BIT(28), /* lock */ + CLK_SET_RATE_UNGATE); + +static const char * const cpux_parents[] = { "osc32k", "osc24M", + "pll-cpux", "pll-cpux" }; +static SUNXI_CCU_MUX(cpux_clk, "cpux", cpux_parents, + 0x050, 16, 2, CLK_SET_RATE_PARENT | CLK_IS_CRITICAL); + +static SUNXI_CCU_M(axi_clk, "axi", "cpux", 0x050, 0, 2, 0); + +static const char * const ahb1_parents[] = { "osc32k", "osc24M", + "axi", "pll-periph0" }; +static struct ccu_div ahb1_clk = { + .div = _SUNXI_CCU_DIV_FLAGS(4, 2, CLK_DIVIDER_POWER_OF_TWO), + + .mux = { + .shift = 12, + .width = 2, + + .variable_prediv = { + .index = 3, + .shift = 6, + .width = 2, + }, + }, + + .common = { + .reg = 0x054, + .features = CCU_FEATURE_VARIABLE_PREDIV, + .hw.init = CLK_HW_INIT_PARENTS("ahb1", + ahb1_parents, + &ccu_div_ops, + 0), + }, +}; + +static struct clk_div_table apb1_div_table[] = { + { .val = 0, .div = 2 }, + { .val = 1, .div = 2 }, + { .val = 2, .div = 4 }, + { .val = 3, .div = 8 }, + { /* Sentinel */ }, +}; +static SUNXI_CCU_DIV_TABLE(apb1_clk, "apb1", "ahb1", + 0x054, 8, 2, apb1_div_table, 0); + +static const char * const apb2_parents[] = { "osc32k", "osc24M", + "pll-periph0-2x", + "pll-periph0-2x" }; +static SUNXI_CCU_MP_WITH_MUX(apb2_clk, "apb2", apb2_parents, 0x058, + 0, 5, /* M */ + 16, 2, /* P */ + 24, 2, /* mux */ + 0); + +static const char * const ahb2_parents[] = { "ahb1", "pll-periph0" }; +static const struct ccu_mux_fixed_prediv ahb2_fixed_predivs[] = { + { .index = 1, .div = 2 }, +}; +static struct ccu_mux ahb2_clk = { + .mux = { + .shift = 0, + .width = 1, + .fixed_predivs = ahb2_fixed_predivs, + .n_predivs = ARRAY_SIZE(ahb2_fixed_predivs), + }, + + .common = { + .reg = 0x05c, + .features = CCU_FEATURE_FIXED_PREDIV, + .hw.init = CLK_HW_INIT_PARENTS("ahb2", + ahb2_parents, + &ccu_mux_ops, + 0), + }, +}; + +static SUNXI_CCU_GATE(bus_mipi_dsi_clk, "bus-mipi-dsi", "ahb1", + 0x060, BIT(1), 0); +static SUNXI_CCU_GATE(bus_ce_clk, "bus-ce", "ahb1", + 0x060, BIT(5), 0); +static SUNXI_CCU_GATE(bus_dma_clk, "bus-dma", "ahb1", + 0x060, BIT(6), 0); +static SUNXI_CCU_GATE(bus_mmc0_clk, "bus-mmc0", "ahb1", + 0x060, BIT(8), 0); +static SUNXI_CCU_GATE(bus_mmc1_clk, "bus-mmc1", "ahb1", + 0x060, BIT(9), 0); +static SUNXI_CCU_GATE(bus_mmc2_clk, "bus-mmc2", "ahb1", + 0x060, BIT(10), 0); +static SUNXI_CCU_GATE(bus_nand_clk, "bus-nand", "ahb1", + 0x060, BIT(13), 0); +static SUNXI_CCU_GATE(bus_dram_clk, "bus-dram", "ahb1", + 0x060, BIT(14), 0); +static SUNXI_CCU_GATE(bus_emac_clk, "bus-emac", "ahb2", + 0x060, BIT(17), 0); +static SUNXI_CCU_GATE(bus_ts_clk, "bus-ts", "ahb1", + 0x060, BIT(18), 0); +static SUNXI_CCU_GATE(bus_hstimer_clk, "bus-hstimer", "ahb1", + 0x060, BIT(19), 0); +static SUNXI_CCU_GATE(bus_spi0_clk, "bus-spi0", "ahb1", + 0x060, BIT(20), 0); +static SUNXI_CCU_GATE(bus_spi1_clk, "bus-spi1", "ahb1", + 0x060, BIT(21), 0); +static SUNXI_CCU_GATE(bus_otg_clk, "bus-otg", "ahb1", + 0x060, BIT(23), 0); +static SUNXI_CCU_GATE(bus_ehci0_clk, "bus-ehci0", "ahb1", + 0x060, BIT(24), 0); +static SUNXI_CCU_GATE(bus_ehci1_clk, "bus-ehci1", "ahb2", + 0x060, BIT(25), 0); +static SUNXI_CCU_GATE(bus_ohci0_clk, "bus-ohci0", "ahb1", + 0x060, BIT(28), 0); +static SUNXI_CCU_GATE(bus_ohci1_clk, "bus-ohci1", "ahb2", + 0x060, BIT(29), 0); + +static SUNXI_CCU_GATE(bus_ve_clk, "bus-ve", "ahb1", + 0x064, BIT(0), 0); +static SUNXI_CCU_GATE(bus_tcon0_clk, "bus-tcon0", "ahb1", + 0x064, BIT(3), 0); +static SUNXI_CCU_GATE(bus_tcon1_clk, "bus-tcon1", "ahb1", + 0x064, BIT(4), 0); +static SUNXI_CCU_GATE(bus_deinterlace_clk, "bus-deinterlace", "ahb1", + 0x064, BIT(5), 0); +static SUNXI_CCU_GATE(bus_csi_clk, "bus-csi", "ahb1", + 0x064, BIT(8), 0); +static SUNXI_CCU_GATE(bus_hdmi_clk, "bus-hdmi", "ahb1", + 0x064, BIT(11), 0); +static SUNXI_CCU_GATE(bus_de_clk, "bus-de", "ahb1", + 0x064, BIT(12), 0); +static SUNXI_CCU_GATE(bus_gpu_clk, "bus-gpu", "ahb1", + 0x064, BIT(20), 0); +static SUNXI_CCU_GATE(bus_msgbox_clk, "bus-msgbox", "ahb1", + 0x064, BIT(21), 0); +static SUNXI_CCU_GATE(bus_spinlock_clk, "bus-spinlock", "ahb1", + 0x064, BIT(22), 0); + +static SUNXI_CCU_GATE(bus_codec_clk, "bus-codec", "apb1", + 0x068, BIT(0), 0); +static SUNXI_CCU_GATE(bus_spdif_clk, "bus-spdif", "apb1", + 0x068, BIT(1), 0); +static SUNXI_CCU_GATE(bus_pio_clk, "bus-pio", "apb1", + 0x068, BIT(5), 0); +static SUNXI_CCU_GATE(bus_ths_clk, "bus-ths", "apb1", + 0x068, BIT(8), 0); +static SUNXI_CCU_GATE(bus_i2s0_clk, "bus-i2s0", "apb1", + 0x068, BIT(12), 0); +static SUNXI_CCU_GATE(bus_i2s1_clk, "bus-i2s1", "apb1", + 0x068, BIT(13), 0); +static SUNXI_CCU_GATE(bus_i2s2_clk, "bus-i2s2", "apb1", + 0x068, BIT(14), 0); + +static SUNXI_CCU_GATE(bus_i2c0_clk, "bus-i2c0", "apb2", + 0x06c, BIT(0), 0); +static SUNXI_CCU_GATE(bus_i2c1_clk, "bus-i2c1", "apb2", + 0x06c, BIT(1), 0); +static SUNXI_CCU_GATE(bus_i2c2_clk, "bus-i2c2", "apb2", + 0x06c, BIT(2), 0); +static SUNXI_CCU_GATE(bus_scr_clk, "bus-scr", "apb2", + 0x06c, BIT(5), 0); +static SUNXI_CCU_GATE(bus_uart0_clk, "bus-uart0", "apb2", + 0x06c, BIT(16), 0); +static SUNXI_CCU_GATE(bus_uart1_clk, "bus-uart1", "apb2", + 0x06c, BIT(17), 0); +static SUNXI_CCU_GATE(bus_uart2_clk, "bus-uart2", "apb2", + 0x06c, BIT(18), 0); +static SUNXI_CCU_GATE(bus_uart3_clk, "bus-uart3", "apb2", + 0x06c, BIT(19), 0); +static SUNXI_CCU_GATE(bus_uart4_clk, "bus-uart4", "apb2", + 0x06c, BIT(20), 0); + +static SUNXI_CCU_GATE(bus_dbg_clk, "bus-dbg", "ahb1", + 0x070, BIT(7), 0); + +static struct clk_div_table ths_div_table[] = { + { .val = 0, .div = 1 }, + { .val = 1, .div = 2 }, + { .val = 2, .div = 4 }, + { .val = 3, .div = 6 }, +}; +static const char * const ths_parents[] = { "osc24M" }; +static struct ccu_div ths_clk = { + .enable = BIT(31), + .div = _SUNXI_CCU_DIV_TABLE(0, 2, ths_div_table), + .mux = _SUNXI_CCU_MUX(24, 2), + .common = { + .reg = 0x074, + .hw.init = CLK_HW_INIT_PARENTS("ths", + ths_parents, + &ccu_div_ops, + 0), + }, +}; + +static const char * const mod0_default_parents[] = { "osc24M", "pll-periph0", + "pll-periph1" }; +static SUNXI_CCU_MP_WITH_MUX_GATE(nand_clk, "nand", mod0_default_parents, 0x080, + 0, 4, /* M */ + 16, 2, /* P */ + 24, 2, /* mux */ + BIT(31), /* gate */ + 0); + +static const char * const mmc_default_parents[] = { "osc24M", "pll-periph0-2x", + "pll-periph1-2x" }; +static SUNXI_CCU_MP_WITH_MUX_GATE(mmc0_clk, "mmc0", mmc_default_parents, 0x088, + 0, 4, /* M */ + 16, 2, /* P */ + 24, 2, /* mux */ + BIT(31), /* gate */ + 0); + +static SUNXI_CCU_MP_WITH_MUX_GATE(mmc1_clk, "mmc1", mmc_default_parents, 0x08c, + 0, 4, /* M */ + 16, 2, /* P */ + 24, 2, /* mux */ + BIT(31), /* gate */ + 0); + +static SUNXI_CCU_MP_WITH_MUX_GATE(mmc2_clk, "mmc2", mmc_default_parents, 0x090, + 0, 4, /* M */ + 16, 2, /* P */ + 24, 2, /* mux */ + BIT(31), /* gate */ + 0); + +static const char * const ts_parents[] = { "osc24M", "pll-periph0", }; +static SUNXI_CCU_MP_WITH_MUX_GATE(ts_clk, "ts", ts_parents, 0x098, + 0, 4, /* M */ + 16, 2, /* P */ + 24, 4, /* mux */ + BIT(31), /* gate */ + 0); + +static SUNXI_CCU_MP_WITH_MUX_GATE(ce_clk, "ce", mmc_default_parents, 0x09c, + 0, 4, /* M */ + 16, 2, /* P */ + 24, 2, /* mux */ + BIT(31), /* gate */ + 0); + +static SUNXI_CCU_MP_WITH_MUX_GATE(spi0_clk, "spi0", mod0_default_parents, 0x0a0, + 0, 4, /* M */ + 16, 2, /* P */ + 24, 2, /* mux */ + BIT(31), /* gate */ + 0); + +static SUNXI_CCU_MP_WITH_MUX_GATE(spi1_clk, "spi1", mod0_default_parents, 0x0a4, + 0, 4, /* M */ + 16, 2, /* P */ + 24, 2, /* mux */ + BIT(31), /* gate */ + 0); + +static const char * const i2s_parents[] = { "pll-audio-8x", "pll-audio-4x", + "pll-audio-2x", "pll-audio" }; +static SUNXI_CCU_MUX_WITH_GATE(i2s0_clk, "i2s0", i2s_parents, + 0x0b0, 16, 2, BIT(31), CLK_SET_RATE_PARENT); + +static SUNXI_CCU_MUX_WITH_GATE(i2s1_clk, "i2s1", i2s_parents, + 0x0b4, 16, 2, BIT(31), CLK_SET_RATE_PARENT); + +static SUNXI_CCU_MUX_WITH_GATE(i2s2_clk, "i2s2", i2s_parents, + 0x0b8, 16, 2, BIT(31), CLK_SET_RATE_PARENT); + +static SUNXI_CCU_M_WITH_GATE(spdif_clk, "spdif", "pll-audio", + 0x0c0, 0, 4, BIT(31), CLK_SET_RATE_PARENT); + +static SUNXI_CCU_GATE(usb_phy0_clk, "usb-phy0", "osc24M", + 0x0cc, BIT(8), 0); +static SUNXI_CCU_GATE(usb_phy1_clk, "usb-phy1", "osc24M", + 0x0cc, BIT(9), 0); +static SUNXI_CCU_GATE(usb_hsic_clk, "usb-hsic", "pll-hsic", + 0x0cc, BIT(10), 0); +static SUNXI_CCU_GATE(usb_hsic_12m_clk, "usb-hsic-12M", "osc12M", + 0x0cc, BIT(11), 0); +static SUNXI_CCU_GATE(usb_ohci0_clk, "usb-ohci0", "osc12M", + 0x0cc, BIT(16), 0); +static SUNXI_CCU_GATE(usb_ohci1_clk, "usb-ohci1", "usb-ohci0", + 0x0cc, BIT(17), 0); + +static const char * const dram_parents[] = { "pll-ddr0", "pll-ddr1" }; +static SUNXI_CCU_M_WITH_MUX(dram_clk, "dram", dram_parents, + 0x0f4, 0, 4, 20, 2, CLK_IS_CRITICAL); + +static SUNXI_CCU_GATE(dram_ve_clk, "dram-ve", "dram", + 0x100, BIT(0), 0); +static SUNXI_CCU_GATE(dram_csi_clk, "dram-csi", "dram", + 0x100, BIT(1), 0); +static SUNXI_CCU_GATE(dram_deinterlace_clk, "dram-deinterlace", "dram", + 0x100, BIT(2), 0); +static SUNXI_CCU_GATE(dram_ts_clk, "dram-ts", "dram", + 0x100, BIT(3), 0); + +static const char * const de_parents[] = { "pll-periph0-2x", "pll-de" }; +static SUNXI_CCU_M_WITH_MUX_GATE(de_clk, "de", de_parents, + 0x104, 0, 4, 24, 3, BIT(31), 0); + +static const char * const tcon0_parents[] = { "pll-mipi", "pll-video0-2x" }; +static const u8 tcon0_table[] = { 0, 2, }; +static SUNXI_CCU_MUX_TABLE_WITH_GATE(tcon0_clk, "tcon0", tcon0_parents, + tcon0_table, 0x118, 24, 3, BIT(31), + CLK_SET_RATE_PARENT); + +static const char * const tcon1_parents[] = { "pll-video0", "pll-video1" }; +static const u8 tcon1_table[] = { 0, 2, }; +static struct ccu_div tcon1_clk = { + .enable = BIT(31), + .div = _SUNXI_CCU_DIV(0, 4), + .mux = _SUNXI_CCU_MUX_TABLE(24, 2, tcon1_table), + .common = { + .reg = 0x11c, + .hw.init = CLK_HW_INIT_PARENTS("tcon1", + tcon1_parents, + &ccu_div_ops, + CLK_SET_RATE_PARENT), + }, +}; + +static const char * const deinterlace_parents[] = { "pll-periph0", "pll-periph1" }; +static SUNXI_CCU_M_WITH_MUX_GATE(deinterlace_clk, "deinterlace", deinterlace_parents, + 0x124, 0, 4, 24, 3, BIT(31), 0); + +static SUNXI_CCU_GATE(csi_misc_clk, "csi-misc", "osc24M", + 0x130, BIT(31), 0); + +static const char * const csi_sclk_parents[] = { "pll-periph0", "pll-periph1" }; +static SUNXI_CCU_M_WITH_MUX_GATE(csi_sclk_clk, "csi-sclk", csi_sclk_parents, + 0x134, 16, 4, 24, 3, BIT(31), 0); + +static const char * const csi_mclk_parents[] = { "osc24M", "pll-video1", "pll-periph1" }; +static SUNXI_CCU_M_WITH_MUX_GATE(csi_mclk_clk, "csi-mclk", csi_mclk_parents, + 0x134, 0, 5, 8, 3, BIT(15), 0); + +static SUNXI_CCU_M_WITH_GATE(ve_clk, "ve", "pll-ve", + 0x13c, 16, 3, BIT(31), 0); + +static SUNXI_CCU_GATE(ac_dig_clk, "ac-dig", "pll-audio", + 0x140, BIT(31), CLK_SET_RATE_PARENT); + +static SUNXI_CCU_GATE(ac_dig_4x_clk, "ac-dig-4x", "pll-audio-4x", + 0x140, BIT(30), CLK_SET_RATE_PARENT); + +static SUNXI_CCU_GATE(avs_clk, "avs", "osc24M", + 0x144, BIT(31), 0); + +static const char * const hdmi_parents[] = { "pll-video0", "pll-video1" }; +static SUNXI_CCU_M_WITH_MUX_GATE(hdmi_clk, "hdmi", hdmi_parents, + 0x150, 0, 4, 24, 2, BIT(31), CLK_SET_RATE_PARENT); + +static SUNXI_CCU_GATE(hdmi_ddc_clk, "hdmi-ddc", "osc24M", + 0x154, BIT(31), 0); + +static const char * const mbus_parents[] = { "osc24M", "pll-periph0-2x", + "pll-ddr0", "pll-ddr1" }; +static SUNXI_CCU_M_WITH_MUX_GATE(mbus_clk, "mbus", mbus_parents, + 0x15c, 0, 3, 24, 2, BIT(31), CLK_IS_CRITICAL); + +static const char * const dsi_dphy_parents[] = { "pll-video0", "pll-periph0" }; +static const u8 dsi_dphy_table[] = { 0, 2, }; +static SUNXI_CCU_M_WITH_MUX_TABLE_GATE(dsi_dphy_clk, "dsi-dphy", + dsi_dphy_parents, dsi_dphy_table, + 0x168, 0, 4, 8, 2, BIT(31), CLK_SET_RATE_PARENT); + +static SUNXI_CCU_M_WITH_GATE(gpu_clk, "gpu", "pll-gpu", + 0x1a0, 0, 3, BIT(31), CLK_SET_RATE_PARENT); + +/* Fixed Factor clocks */ +static CLK_FIXED_FACTOR(osc12M_clk, "osc12M", "osc24M", 1, 2, 0); + +/* We hardcode the divider to 4 for now */ +static CLK_FIXED_FACTOR(pll_audio_clk, "pll-audio", + "pll-audio-base", 4, 1, CLK_SET_RATE_PARENT); +static CLK_FIXED_FACTOR(pll_audio_2x_clk, "pll-audio-2x", + "pll-audio-base", 2, 1, CLK_SET_RATE_PARENT); +static CLK_FIXED_FACTOR(pll_audio_4x_clk, "pll-audio-4x", + "pll-audio-base", 1, 1, CLK_SET_RATE_PARENT); +static CLK_FIXED_FACTOR(pll_audio_8x_clk, "pll-audio-8x", + "pll-audio-base", 1, 2, CLK_SET_RATE_PARENT); +static CLK_FIXED_FACTOR(pll_periph0_2x_clk, "pll-periph0-2x", + "pll-periph0", 1, 2, 0); +static CLK_FIXED_FACTOR(pll_periph1_2x_clk, "pll-periph1-2x", + "pll-periph1", 1, 2, 0); +static CLK_FIXED_FACTOR(pll_video0_2x_clk, "pll-video0-2x", + "pll-video0", 1, 2, CLK_SET_RATE_PARENT); + +static struct ccu_common *sun50i_a64_ccu_clks[] = { + &pll_cpux_clk.common, + &pll_audio_base_clk.common, + &pll_video0_clk.common, + &pll_ve_clk.common, + &pll_ddr0_clk.common, + &pll_periph0_clk.common, + &pll_periph1_clk.common, + &pll_video1_clk.common, + &pll_gpu_clk.common, + &pll_mipi_clk.common, + &pll_hsic_clk.common, + &pll_de_clk.common, + &pll_ddr1_clk.common, + &cpux_clk.common, + &axi_clk.common, + &ahb1_clk.common, + &apb1_clk.common, + &apb2_clk.common, + &ahb2_clk.common, + &bus_mipi_dsi_clk.common, + &bus_ce_clk.common, + &bus_dma_clk.common, + &bus_mmc0_clk.common, + &bus_mmc1_clk.common, + &bus_mmc2_clk.common, + &bus_nand_clk.common, + &bus_dram_clk.common, + &bus_emac_clk.common, + &bus_ts_clk.common, + &bus_hstimer_clk.common, + &bus_spi0_clk.common, + &bus_spi1_clk.common, + &bus_otg_clk.common, + &bus_ehci0_clk.common, + &bus_ehci1_clk.common, + &bus_ohci0_clk.common, + &bus_ohci1_clk.common, + &bus_ve_clk.common, + &bus_tcon0_clk.common, + &bus_tcon1_clk.common, + &bus_deinterlace_clk.common, + &bus_csi_clk.common, + &bus_hdmi_clk.common, + &bus_de_clk.common, + &bus_gpu_clk.common, + &bus_msgbox_clk.common, + &bus_spinlock_clk.common, + &bus_codec_clk.common, + &bus_spdif_clk.common, + &bus_pio_clk.common, + &bus_ths_clk.common, + &bus_i2s0_clk.common, + &bus_i2s1_clk.common, + &bus_i2s2_clk.common, + &bus_i2c0_clk.common, + &bus_i2c1_clk.common, + &bus_i2c2_clk.common, + &bus_scr_clk.common, + &bus_uart0_clk.common, + &bus_uart1_clk.common, + &bus_uart2_clk.common, + &bus_uart3_clk.common, + &bus_uart4_clk.common, + &bus_dbg_clk.common, + &ths_clk.common, + &nand_clk.common, + &mmc0_clk.common, + &mmc1_clk.common, + &mmc2_clk.common, + &ts_clk.common, + &ce_clk.common, + &spi0_clk.common, + &spi1_clk.common, + &i2s0_clk.common, + &i2s1_clk.common, + &i2s2_clk.common, + &spdif_clk.common, + &usb_phy0_clk.common, + &usb_phy1_clk.common, + &usb_hsic_clk.common, + &usb_hsic_12m_clk.common, + &usb_ohci0_clk.common, + &usb_ohci1_clk.common, + &dram_clk.common, + &dram_ve_clk.common, + &dram_csi_clk.common, + &dram_deinterlace_clk.common, + &dram_ts_clk.common, + &de_clk.common, + &tcon0_clk.common, + &tcon1_clk.common, + &deinterlace_clk.common, + &csi_misc_clk.common, + &csi_sclk_clk.common, + &csi_mclk_clk.common, + &ve_clk.common, + &ac_dig_clk.common, + &ac_dig_4x_clk.common, + &avs_clk.common, + &hdmi_clk.common, + &hdmi_ddc_clk.common, + &mbus_clk.common, + &dsi_dphy_clk.common, + &gpu_clk.common, +}; + +static struct clk_hw_onecell_data sun50i_a64_hw_clks = { + .hws = { + [CLK_OSC_12M] = &osc12M_clk.hw, + [CLK_PLL_CPUX] = &pll_cpux_clk.common.hw, + [CLK_PLL_AUDIO_BASE] = &pll_audio_base_clk.common.hw, + [CLK_PLL_AUDIO] = &pll_audio_clk.hw, + [CLK_PLL_AUDIO_2X] = &pll_audio_2x_clk.hw, + [CLK_PLL_AUDIO_4X] = &pll_audio_4x_clk.hw, + [CLK_PLL_AUDIO_8X] = &pll_audio_8x_clk.hw, + [CLK_PLL_VIDEO0] = &pll_video0_clk.common.hw, + [CLK_PLL_VIDEO0_2X] = &pll_video0_2x_clk.hw, + [CLK_PLL_VE] = &pll_ve_clk.common.hw, + [CLK_PLL_DDR0] = &pll_ddr0_clk.common.hw, + [CLK_PLL_PERIPH0] = &pll_periph0_clk.common.hw, + [CLK_PLL_PERIPH0_2X] = &pll_periph0_2x_clk.hw, + [CLK_PLL_PERIPH1] = &pll_periph1_clk.common.hw, + [CLK_PLL_PERIPH1_2X] = &pll_periph1_2x_clk.hw, + [CLK_PLL_VIDEO1] = &pll_video1_clk.common.hw, + [CLK_PLL_GPU] = &pll_gpu_clk.common.hw, + [CLK_PLL_MIPI] = &pll_mipi_clk.common.hw, + [CLK_PLL_HSIC] = &pll_hsic_clk.common.hw, + [CLK_PLL_DE] = &pll_de_clk.common.hw, + [CLK_PLL_DDR1] = &pll_ddr1_clk.common.hw, + [CLK_CPUX] = &cpux_clk.common.hw, + [CLK_AXI] = &axi_clk.common.hw, + [CLK_AHB1] = &ahb1_clk.common.hw, + [CLK_APB1] = &apb1_clk.common.hw, + [CLK_APB2] = &apb2_clk.common.hw, + [CLK_AHB2] = &ahb2_clk.common.hw, + [CLK_BUS_MIPI_DSI] = &bus_mipi_dsi_clk.common.hw, + [CLK_BUS_CE] = &bus_ce_clk.common.hw, + [CLK_BUS_DMA] = &bus_dma_clk.common.hw, + [CLK_BUS_MMC0] = &bus_mmc0_clk.common.hw, + [CLK_BUS_MMC1] = &bus_mmc1_clk.common.hw, + [CLK_BUS_MMC2] = &bus_mmc2_clk.common.hw, + [CLK_BUS_NAND] = &bus_nand_clk.common.hw, + [CLK_BUS_DRAM] = &bus_dram_clk.common.hw, + [CLK_BUS_EMAC] = &bus_emac_clk.common.hw, + [CLK_BUS_TS] = &bus_ts_clk.common.hw, + [CLK_BUS_HSTIMER] = &bus_hstimer_clk.common.hw, + [CLK_BUS_SPI0] = &bus_spi0_clk.common.hw, + [CLK_BUS_SPI1] = &bus_spi1_clk.common.hw, + [CLK_BUS_OTG] = &bus_otg_clk.common.hw, + [CLK_BUS_EHCI0] = &bus_ehci0_clk.common.hw, + [CLK_BUS_EHCI1] = &bus_ehci1_clk.common.hw, + [CLK_BUS_OHCI0] = &bus_ohci0_clk.common.hw, + [CLK_BUS_OHCI1] = &bus_ohci1_clk.common.hw, + [CLK_BUS_VE] = &bus_ve_clk.common.hw, + [CLK_BUS_TCON0] = &bus_tcon0_clk.common.hw, + [CLK_BUS_TCON1] = &bus_tcon1_clk.common.hw, + [CLK_BUS_DEINTERLACE] = &bus_deinterlace_clk.common.hw, + [CLK_BUS_CSI] = &bus_csi_clk.common.hw, + [CLK_BUS_HDMI] = &bus_hdmi_clk.common.hw, + [CLK_BUS_DE] = &bus_de_clk.common.hw, + [CLK_BUS_GPU] = &bus_gpu_clk.common.hw, + [CLK_BUS_MSGBOX] = &bus_msgbox_clk.common.hw, + [CLK_BUS_SPINLOCK] = &bus_spinlock_clk.common.hw, + [CLK_BUS_CODEC] = &bus_codec_clk.common.hw, + [CLK_BUS_SPDIF] = &bus_spdif_clk.common.hw, + [CLK_BUS_PIO] = &bus_pio_clk.common.hw, + [CLK_BUS_THS] = &bus_ths_clk.common.hw, + [CLK_BUS_I2S0] = &bus_i2s0_clk.common.hw, + [CLK_BUS_I2S1] = &bus_i2s1_clk.common.hw, + [CLK_BUS_I2S2] = &bus_i2s2_clk.common.hw, + [CLK_BUS_I2C0] = &bus_i2c0_clk.common.hw, + [CLK_BUS_I2C1] = &bus_i2c1_clk.common.hw, + [CLK_BUS_I2C2] = &bus_i2c2_clk.common.hw, + [CLK_BUS_UART0] = &bus_uart0_clk.common.hw, + [CLK_BUS_UART1] = &bus_uart1_clk.common.hw, + [CLK_BUS_UART2] = &bus_uart2_clk.common.hw, + [CLK_BUS_UART3] = &bus_uart3_clk.common.hw, + [CLK_BUS_UART4] = &bus_uart4_clk.common.hw, + [CLK_BUS_SCR] = &bus_scr_clk.common.hw, + [CLK_BUS_DBG] = &bus_dbg_clk.common.hw, + [CLK_THS] = &ths_clk.common.hw, + [CLK_NAND] = &nand_clk.common.hw, + [CLK_MMC0] = &mmc0_clk.common.hw, + [CLK_MMC1] = &mmc1_clk.common.hw, + [CLK_MMC2] = &mmc2_clk.common.hw, + [CLK_TS] = &ts_clk.common.hw, + [CLK_CE] = &ce_clk.common.hw, + [CLK_SPI0] = &spi0_clk.common.hw, + [CLK_SPI1] = &spi1_clk.common.hw, + [CLK_I2S0] = &i2s0_clk.common.hw, + [CLK_I2S1] = &i2s1_clk.common.hw, + [CLK_I2S2] = &i2s2_clk.common.hw, + [CLK_SPDIF] = &spdif_clk.common.hw, + [CLK_USB_PHY0] = &usb_phy0_clk.common.hw, + [CLK_USB_PHY1] = &usb_phy1_clk.common.hw, + [CLK_USB_HSIC] = &usb_hsic_clk.common.hw, + [CLK_USB_HSIC_12M] = &usb_hsic_12m_clk.common.hw, + [CLK_USB_OHCI0] = &usb_ohci0_clk.common.hw, + [CLK_USB_OHCI1] = &usb_ohci1_clk.common.hw, + [CLK_DRAM] = &dram_clk.common.hw, + [CLK_DRAM_VE] = &dram_ve_clk.common.hw, + [CLK_DRAM_CSI] = &dram_csi_clk.common.hw, + [CLK_DRAM_DEINTERLACE] = &dram_deinterlace_clk.common.hw, + [CLK_DRAM_TS] = &dram_ts_clk.common.hw, + [CLK_DE] = &de_clk.common.hw, + [CLK_TCON0] = &tcon0_clk.common.hw, + [CLK_TCON1] = &tcon1_clk.common.hw, + [CLK_DEINTERLACE] = &deinterlace_clk.common.hw, + [CLK_CSI_MISC] = &csi_misc_clk.common.hw, + [CLK_CSI_SCLK] = &csi_sclk_clk.common.hw, + [CLK_CSI_MCLK] = &csi_mclk_clk.common.hw, + [CLK_VE] = &ve_clk.common.hw, + [CLK_AC_DIG] = &ac_dig_clk.common.hw, + [CLK_AC_DIG_4X] = &ac_dig_4x_clk.common.hw, + [CLK_AVS] = &avs_clk.common.hw, + [CLK_HDMI] = &hdmi_clk.common.hw, + [CLK_HDMI_DDC] = &hdmi_ddc_clk.common.hw, + [CLK_MBUS] = &mbus_clk.common.hw, + [CLK_DSI_DPHY] = &dsi_dphy_clk.common.hw, + [CLK_GPU] = &gpu_clk.common.hw, + }, + .num = CLK_NUMBER, +}; + +const struct sunxi_ccu_desc sun50i_a64_ccu_desc = { + .ccu_clks = sun50i_a64_ccu_clks, + .num_ccu_clks = ARRAY_SIZE(sun50i_a64_ccu_clks), + + .hw_clks = &sun50i_a64_hw_clks, +}; + diff --git a/drivers/clk/sunxi/ccu-sun50i-a64.h b/drivers/clk/sunxi/ccu-sun50i-a64.h new file mode 100644 index 0000000..daf45e2 --- /dev/null +++ b/drivers/clk/sunxi/ccu-sun50i-a64.h @@ -0,0 +1,64 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu-sun50i-a64.h in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _CCU_SUN50I_A64_H_ +#define _CCU_SUN50I_A64_H_ + +#include +#include + +#define CLK_OSC_12M 0 +#define CLK_PLL_CPUX 1 +#define CLK_PLL_AUDIO_BASE 2 +#define CLK_PLL_AUDIO 3 +#define CLK_PLL_AUDIO_2X 4 +#define CLK_PLL_AUDIO_4X 5 +#define CLK_PLL_AUDIO_8X 6 +#define CLK_PLL_VIDEO0 7 +#define CLK_PLL_VIDEO0_2X 8 +#define CLK_PLL_VE 9 +#define CLK_PLL_DDR0 10 +#define CLK_PLL_PERIPH0 11 +#define CLK_PLL_PERIPH0_2X 12 +#define CLK_PLL_PERIPH1 13 +#define CLK_PLL_PERIPH1_2X 14 +#define CLK_PLL_VIDEO1 15 +#define CLK_PLL_GPU 16 +#define CLK_PLL_MIPI 17 +#define CLK_PLL_HSIC 18 +#define CLK_PLL_DE 19 +#define CLK_PLL_DDR1 20 +#define CLK_CPUX 21 +#define CLK_AXI 22 +#define CLK_APB 23 +#define CLK_AHB1 24 +#define CLK_APB1 25 +#define CLK_APB2 26 +#define CLK_AHB2 27 + +/* All the bus gates are exported */ + +/* The first bunch of module clocks are exported */ + +#define CLK_USB_OHCI0_12M 90 + +#define CLK_USB_OHCI1_12M 92 + +#define CLK_DRAM 94 + +/* All the DRAM gates are exported */ + +/* Some more module clocks are exported */ + +#define CLK_MBUS 112 + +/* And the DSI and GPU module clock is exported */ + +#define CLK_NUMBER (CLK_GPU + 1) + +#endif /* _CCU_SUN50I_A64_H_ */ diff --git a/drivers/clk/sunxi/ccu_common.c b/drivers/clk/sunxi/ccu_common.c new file mode 100644 index 0000000..6dba33c --- /dev/null +++ b/drivers/clk/sunxi/ccu_common.c @@ -0,0 +1,27 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_common.c in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * Ported to U-Boot by + * Theobroma Systems Design und Consulting GmbH + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include + +#include "ccu_common.h" + +void ccu_helper_wait_for_lock(struct ccu_common *common, u32 lock) +{ + u32 reg; + + if (!lock) + return; + + WARN_ON(readl_poll_timeout(common->base + common->reg, reg, + reg & lock, 70000)); +} diff --git a/drivers/clk/sunxi/ccu_common.h b/drivers/clk/sunxi/ccu_common.h new file mode 100644 index 0000000..719690e --- /dev/null +++ b/drivers/clk/sunxi/ccu_common.h @@ -0,0 +1,67 @@ +/* + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _SUNXI_CLK_CCU_COMMON_H_ +#define _SUNXI_CLK_CCU_COMMON_H_ + +#include "ccu-compatibility.h" +#include + +#define CCU_FEATURE_FRACTIONAL BIT(0) +#define CCU_FEATURE_VARIABLE_PREDIV BIT(1) +#define CCU_FEATURE_FIXED_PREDIV BIT(2) +#define CCU_FEATURE_FIXED_POSTDIV BIT(3) + +#define CLK_HW_INIT(_name, _parent, _ops, _flags) \ + &(struct clk_init_data) { \ + .flags = _flags, \ + .name = _name, \ + .parent_names = (const char *[]) { _parent }, \ + .num_parents = 1, \ + .ops = _ops, \ + } + +#define CLK_HW_INIT_PARENTS(_name, _parents, _ops, _flags) \ + &(struct clk_init_data) { \ + .flags = _flags, \ + .name = _name, \ + .parent_names = _parents, \ + .num_parents = ARRAY_SIZE(_parents), \ + .ops = _ops, \ + } + +#define CLK_FIXED_FACTOR(_struct, _name, _parent, \ + _div, _mult, _flags) \ + struct clk_fixed_factor _struct = { \ + .div = _div, \ + .mult = _mult, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &clk_fixed_factor_ops, \ + _flags), \ + } + +struct ccu_common { + void __iomem *base; + u16 reg; + + unsigned long features; + struct clk_hw hw; +}; + +static inline struct ccu_common *hw_to_ccu_common(struct clk_hw *hw) +{ + return container_of(hw, struct ccu_common, hw); +} + +struct sunxi_ccu_desc { + struct ccu_common **ccu_clks; + unsigned long num_ccu_clks; + + struct clk_hw_onecell_data *hw_clks; +}; + +void ccu_helper_wait_for_lock(struct ccu_common *common, u32 lock); + +#endif /* _COMMON_H_ */ diff --git a/drivers/clk/sunxi/ccu_div.c b/drivers/clk/sunxi/ccu_div.c new file mode 100644 index 0000000..b22770c --- /dev/null +++ b/drivers/clk/sunxi/ccu_div.c @@ -0,0 +1,134 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_div.c in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include + +#include "ccu_gate.h" +#include "ccu_div.h" + +static unsigned long ccu_div_round_rate(struct ccu_mux_internal *mux, + unsigned long parent_rate, + unsigned long rate, + void *data) +{ + struct ccu_div *cd = data; + unsigned long val; + + /* + * We can't use divider_round_rate that assumes that there's + * several parents, while we might be called to evaluate + * several different parents. + */ + val = divider_get_val(rate, parent_rate, cd->div.table, cd->div.width, + cd->div.flags); + + return divider_recalc_rate(&cd->common.hw, parent_rate, val, + cd->div.table, cd->div.flags); +} + +static void ccu_div_disable(struct clk_hw *hw) +{ + struct ccu_div *cd = hw_to_ccu_div(hw); + + return ccu_gate_helper_disable(&cd->common, cd->enable); +} + +static int ccu_div_enable(struct clk_hw *hw) +{ + struct ccu_div *cd = hw_to_ccu_div(hw); + + return ccu_gate_helper_enable(&cd->common, cd->enable); +} + +static int ccu_div_is_enabled(struct clk_hw *hw) +{ + struct ccu_div *cd = hw_to_ccu_div(hw); + + return ccu_gate_helper_is_enabled(&cd->common, cd->enable); +} + +static unsigned long ccu_div_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_div *cd = hw_to_ccu_div(hw); + unsigned long val; + u32 reg; + + reg = readl(cd->common.base + cd->common.reg); + val = reg >> cd->div.shift; + val &= (1 << cd->div.width) - 1; + + ccu_mux_helper_adjust_parent_for_prediv(&cd->common, &cd->mux, -1, + &parent_rate); + + return divider_recalc_rate(hw, parent_rate, val, cd->div.table, + cd->div.flags); +} + +static int ccu_div_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct ccu_div *cd = hw_to_ccu_div(hw); + + return ccu_mux_helper_determine_rate(&cd->common, &cd->mux, + req, ccu_div_round_rate, cd); +} + +static int ccu_div_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ccu_div *cd = hw_to_ccu_div(hw); + unsigned long flags; + unsigned long val; + u32 reg; + + ccu_mux_helper_adjust_parent_for_prediv(&cd->common, &cd->mux, -1, + &parent_rate); + + val = divider_get_val(rate, parent_rate, cd->div.table, cd->div.width, + cd->div.flags); + + spin_lock_irqsave(cd->common.lock, flags); + + reg = readl(cd->common.base + cd->common.reg); + reg &= ~GENMASK(cd->div.width + cd->div.shift - 1, cd->div.shift); + + writel(reg | (val << cd->div.shift), + cd->common.base + cd->common.reg); + + spin_unlock_irqrestore(cd->common.lock, flags); + + return 0; +} + +static u8 ccu_div_get_parent(struct clk_hw *hw) +{ + struct ccu_div *cd = hw_to_ccu_div(hw); + + return ccu_mux_helper_get_parent(&cd->common, &cd->mux); +} + +static int ccu_div_set_parent(struct clk_hw *hw, u8 index) +{ + struct ccu_div *cd = hw_to_ccu_div(hw); + + return ccu_mux_helper_set_parent(&cd->common, &cd->mux, index); +} + +const struct sunxi_ccu_clk_ops ccu_div_ops = { + .disable = ccu_div_disable, + .enable = ccu_div_enable, + .is_enabled = ccu_div_is_enabled, + + .get_parent = ccu_div_get_parent, + .set_parent = ccu_div_set_parent, + + .determine_rate = ccu_div_determine_rate, + .recalc_rate = ccu_div_recalc_rate, + .set_rate = ccu_div_set_rate, +}; diff --git a/drivers/clk/sunxi/ccu_div.h b/drivers/clk/sunxi/ccu_div.h new file mode 100644 index 0000000..9f38314 --- /dev/null +++ b/drivers/clk/sunxi/ccu_div.h @@ -0,0 +1,170 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_div.h in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _CCU_DIV_H_ +#define _CCU_DIV_H_ + +#include "ccu_common.h" +#include "ccu_mux.h" + +/** + * struct ccu_div_internal - Internal divider description + * @shift: Bit offset of the divider in its register + * @width: Width of the divider field in its register + * @max: Maximum value allowed for that divider. This is the + * arithmetic value, not the maximum value to be set in the + * register. + * @flags: clk_divider flags to apply on this divider + * @table: Divider table pointer (if applicable) + * + * That structure represents a single divider, and is meant to be + * embedded in other structures representing the various clock + * classes. + * + * It is basically a wrapper around the clk_divider functions + * arguments. + */ +struct ccu_div_internal { + u8 shift; + u8 width; + + u32 max; + + u32 flags; + + struct clk_div_table *table; +}; + +#define _SUNXI_CCU_DIV_TABLE_FLAGS(_shift, _width, _table, _flags) \ + { \ + .shift = _shift, \ + .width = _width, \ + .flags = _flags, \ + .table = _table, \ + } + +#define _SUNXI_CCU_DIV_TABLE(_shift, _width, _table) \ + _SUNXI_CCU_DIV_TABLE_FLAGS(_shift, _width, _table, 0) + +#define _SUNXI_CCU_DIV_MAX_FLAGS(_shift, _width, _max, _flags) \ + { \ + .shift = _shift, \ + .width = _width, \ + .flags = _flags, \ + .max = _max, \ + } + +#define _SUNXI_CCU_DIV_FLAGS(_shift, _width, _flags) \ + _SUNXI_CCU_DIV_MAX_FLAGS(_shift, _width, 0, _flags) + +#define _SUNXI_CCU_DIV_MAX(_shift, _width, _max) \ + _SUNXI_CCU_DIV_MAX_FLAGS(_shift, _width, _max, 0) + +#define _SUNXI_CCU_DIV(_shift, _width) \ + _SUNXI_CCU_DIV_FLAGS(_shift, _width, 0) + +struct ccu_div { + u32 enable; + + struct ccu_div_internal div; + struct ccu_mux_internal mux; + struct ccu_common common; +}; + +#define SUNXI_CCU_DIV_TABLE_WITH_GATE(_struct, _name, _parent, _reg, \ + _shift, _width, \ + _table, _gate, _flags) \ + struct ccu_div _struct = { \ + .div = _SUNXI_CCU_DIV_TABLE(_shift, _width, \ + _table), \ + .enable = _gate, \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &ccu_div_ops, \ + _flags), \ + } \ + } + + +#define SUNXI_CCU_DIV_TABLE(_struct, _name, _parent, _reg, \ + _shift, _width, \ + _table, _flags) \ + SUNXI_CCU_DIV_TABLE_WITH_GATE(_struct, _name, _parent, _reg, \ + _shift, _width, _table, 0, \ + _flags) + +#define SUNXI_CCU_M_WITH_MUX_TABLE_GATE(_struct, _name, \ + _parents, _table, \ + _reg, \ + _mshift, _mwidth, \ + _muxshift, _muxwidth, \ + _gate, _flags) \ + struct ccu_div _struct = { \ + .enable = _gate, \ + .div = _SUNXI_CCU_DIV(_mshift, _mwidth), \ + .mux = _SUNXI_CCU_MUX_TABLE(_muxshift, _muxwidth, _table), \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT_PARENTS(_name, \ + _parents, \ + &ccu_div_ops, \ + _flags), \ + }, \ + } + +#define SUNXI_CCU_M_WITH_MUX_GATE(_struct, _name, _parents, _reg, \ + _mshift, _mwidth, _muxshift, _muxwidth, \ + _gate, _flags) \ + SUNXI_CCU_M_WITH_MUX_TABLE_GATE(_struct, _name, \ + _parents, NULL, \ + _reg, _mshift, _mwidth, \ + _muxshift, _muxwidth, \ + _gate, _flags) + +#define SUNXI_CCU_M_WITH_MUX(_struct, _name, _parents, _reg, \ + _mshift, _mwidth, _muxshift, _muxwidth, \ + _flags) \ + SUNXI_CCU_M_WITH_MUX_TABLE_GATE(_struct, _name, \ + _parents, NULL, \ + _reg, _mshift, _mwidth, \ + _muxshift, _muxwidth, \ + 0, _flags) + + +#define SUNXI_CCU_M_WITH_GATE(_struct, _name, _parent, _reg, \ + _mshift, _mwidth, _gate, \ + _flags) \ + struct ccu_div _struct = { \ + .enable = _gate, \ + .div = _SUNXI_CCU_DIV(_mshift, _mwidth), \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &ccu_div_ops, \ + _flags), \ + }, \ + } + +#define SUNXI_CCU_M(_struct, _name, _parent, _reg, _mshift, _mwidth, \ + _flags) \ + SUNXI_CCU_M_WITH_GATE(_struct, _name, _parent, _reg, \ + _mshift, _mwidth, 0, _flags) + +static inline struct ccu_div *hw_to_ccu_div(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_div, common); +} + +extern const struct sunxi_ccu_clk_ops ccu_div_ops; + +#endif /* _CCU_DIV_H_ */ diff --git a/drivers/clk/sunxi/ccu_frac.c b/drivers/clk/sunxi/ccu_frac.c new file mode 100644 index 0000000..baecf95 --- /dev/null +++ b/drivers/clk/sunxi/ccu_frac.c @@ -0,0 +1,106 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_common.c in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include "ccu_frac.h" + +bool ccu_frac_helper_is_enabled(struct ccu_common *common, + struct ccu_frac_internal *cf) +{ + if (!(common->features & CCU_FEATURE_FRACTIONAL)) + return false; + + return !(readl(common->base + common->reg) & cf->enable); +} + +void ccu_frac_helper_enable(struct ccu_common *common, + struct ccu_frac_internal *cf) +{ + unsigned long flags; + u32 reg; + + if (!(common->features & CCU_FEATURE_FRACTIONAL)) + return; + + spin_lock_irqsave(common->lock, flags); + reg = readl(common->base + common->reg); + writel(reg & ~cf->enable, common->base + common->reg); + spin_unlock_irqrestore(common->lock, flags); +} + +void ccu_frac_helper_disable(struct ccu_common *common, + struct ccu_frac_internal *cf) +{ + unsigned long flags; + u32 reg; + + if (!(common->features & CCU_FEATURE_FRACTIONAL)) + return; + + spin_lock_irqsave(common->lock, flags); + reg = readl(common->base + common->reg); + writel(reg | cf->enable, common->base + common->reg); + spin_unlock_irqrestore(common->lock, flags); +} + +bool ccu_frac_helper_has_rate(struct ccu_common *common, + struct ccu_frac_internal *cf, + unsigned long rate) +{ + if (!(common->features & CCU_FEATURE_FRACTIONAL)) + return false; + + return (cf->rates[0] == rate) || (cf->rates[1] == rate); +} + +unsigned long ccu_frac_helper_read_rate(struct ccu_common *common, + struct ccu_frac_internal *cf) +{ + u32 reg; + + printk("%s: Read fractional\n", clk_hw_get_name(&common->hw)); + + if (!(common->features & CCU_FEATURE_FRACTIONAL)) + return 0; + + printk("%s: clock is fractional (rates %lu and %lu)\n", + clk_hw_get_name(&common->hw), cf->rates[0], cf->rates[1]); + + reg = readl(common->base + common->reg); + + printk("%s: clock reg is 0x%x (select is 0x%x)\n", + clk_hw_get_name(&common->hw), reg, cf->select); + + return (reg & cf->select) ? cf->rates[1] : cf->rates[0]; +} + +int ccu_frac_helper_set_rate(struct ccu_common *common, + struct ccu_frac_internal *cf, + unsigned long rate) +{ + unsigned long flags; + u32 reg, sel; + + if (!(common->features & CCU_FEATURE_FRACTIONAL)) + return -EINVAL; + + if (cf->rates[0] == rate) + sel = 0; + else if (cf->rates[1] == rate) + sel = cf->select; + else + return -EINVAL; + + spin_lock_irqsave(common->lock, flags); + reg = readl(common->base + common->reg); + reg &= ~cf->select; + writel(reg | sel, common->base + common->reg); + spin_unlock_irqrestore(common->lock, flags); + + return 0; +} diff --git a/drivers/clk/sunxi/ccu_frac.h b/drivers/clk/sunxi/ccu_frac.h new file mode 100644 index 0000000..aad82fa --- /dev/null +++ b/drivers/clk/sunxi/ccu_frac.h @@ -0,0 +1,46 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_common.h in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _CCU_FRAC_H_ +#define _CCU_FRAC_H_ + +#include "ccu_common.h" + +struct ccu_frac_internal { + u32 enable; + u32 select; + + unsigned long rates[2]; +}; + +#define _SUNXI_CCU_FRAC(_enable, _select, _rate1, _rate2) \ + { \ + .enable = _enable, \ + .select = _select, \ + .rates = { _rate1, _rate2 }, \ + } + +bool ccu_frac_helper_is_enabled(struct ccu_common *common, + struct ccu_frac_internal *cf); +void ccu_frac_helper_enable(struct ccu_common *common, + struct ccu_frac_internal *cf); +void ccu_frac_helper_disable(struct ccu_common *common, + struct ccu_frac_internal *cf); + +bool ccu_frac_helper_has_rate(struct ccu_common *common, + struct ccu_frac_internal *cf, + unsigned long rate); + +unsigned long ccu_frac_helper_read_rate(struct ccu_common *common, + struct ccu_frac_internal *cf); + +int ccu_frac_helper_set_rate(struct ccu_common *common, + struct ccu_frac_internal *cf, + unsigned long rate); + +#endif /* _CCU_FRAC_H_ */ diff --git a/drivers/clk/sunxi/ccu_gate.c b/drivers/clk/sunxi/ccu_gate.c new file mode 100644 index 0000000..75d685f --- /dev/null +++ b/drivers/clk/sunxi/ccu_gate.c @@ -0,0 +1,80 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_gate.c in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include + +#include "ccu_gate.h" + +void ccu_gate_helper_disable(struct ccu_common *common, u32 gate) +{ + unsigned long flags; + u32 reg; + + if (!gate) + return; + + spin_lock_irqsave(common->lock, flags); + + reg = readl(common->base + common->reg); + writel(reg & ~gate, common->base + common->reg); + + spin_unlock_irqrestore(common->lock, flags); +} + +static void ccu_gate_disable(struct clk_hw *hw) +{ + struct ccu_gate *cg = hw_to_ccu_gate(hw); + + return ccu_gate_helper_disable(&cg->common, cg->enable); +} + +int ccu_gate_helper_enable(struct ccu_common *common, u32 gate) +{ + unsigned long flags; + u32 reg; + + if (!gate) + return 0; + + spin_lock_irqsave(common->lock, flags); + + reg = readl(common->base + common->reg); + writel(reg | gate, common->base + common->reg); + + spin_unlock_irqrestore(common->lock, flags); + + return 0; +} + +static int ccu_gate_enable(struct clk_hw *hw) +{ + struct ccu_gate *cg = hw_to_ccu_gate(hw); + + return ccu_gate_helper_enable(&cg->common, cg->enable); +} + +int ccu_gate_helper_is_enabled(struct ccu_common *common, u32 gate) +{ + if (!gate) + return 1; + + return readl(common->base + common->reg) & gate; +} + +static int ccu_gate_is_enabled(struct clk_hw *hw) +{ + struct ccu_gate *cg = hw_to_ccu_gate(hw); + + return ccu_gate_helper_is_enabled(&cg->common, cg->enable); +} + +const struct sunxi_ccu_clk_ops ccu_gate_ops = { + .disable = ccu_gate_disable, + .enable = ccu_gate_enable, + .is_enabled = ccu_gate_is_enabled, +}; diff --git a/drivers/clk/sunxi/ccu_gate.h b/drivers/clk/sunxi/ccu_gate.h new file mode 100644 index 0000000..7b48f13 --- /dev/null +++ b/drivers/clk/sunxi/ccu_gate.h @@ -0,0 +1,45 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_gate.h in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _CCU_GATE_H_ +#define _CCU_GATE_H_ + +#include "ccu_common.h" + +struct ccu_gate { + u32 enable; + + struct ccu_common common; +}; + +#define SUNXI_CCU_GATE(_struct, _name, _parent, _reg, _gate, _flags) \ + struct ccu_gate _struct = { \ + .enable = _gate, \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &ccu_gate_ops, \ + _flags), \ + } \ + } + +static inline struct ccu_gate *hw_to_ccu_gate(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_gate, common); +} + +void ccu_gate_helper_disable(struct ccu_common *common, u32 gate); +int ccu_gate_helper_enable(struct ccu_common *common, u32 gate); +int ccu_gate_helper_is_enabled(struct ccu_common *common, u32 gate); + +extern const struct sunxi_ccu_clk_ops ccu_gate_ops; + +#endif /* _CCU_GATE_H_ */ diff --git a/drivers/clk/sunxi/ccu_mp.c b/drivers/clk/sunxi/ccu_mp.c new file mode 100644 index 0000000..d5410e0 --- /dev/null +++ b/drivers/clk/sunxi/ccu_mp.c @@ -0,0 +1,159 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_mp.c in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include + +#include "ccu_gate.h" +#include "ccu_mp.h" + +static void ccu_mp_find_best(unsigned long parent, unsigned long rate, + unsigned int max_m, unsigned int max_p, + unsigned int *m, unsigned int *p) +{ + unsigned long best_rate = 0; + unsigned int best_m = 0, best_p = 0; + unsigned int _m, _p; + + for (_p = 1; _p <= max_p; _p <<= 1) { + for (_m = 1; _m <= max_m; _m++) { + unsigned long tmp_rate = parent / _p / _m; + + if (tmp_rate > rate) + continue; + + if ((rate - tmp_rate) < (rate - best_rate)) { + best_rate = tmp_rate; + best_m = _m; + best_p = _p; + } + } + } + + *m = best_m; + *p = best_p; +} + +static unsigned long ccu_mp_round_rate(struct ccu_mux_internal *mux, + unsigned long parent_rate, + unsigned long rate, + void *data) +{ + struct ccu_mp *cmp = data; + unsigned int max_m, max_p; + unsigned int m, p; + + max_m = cmp->m.max ?: 1 << cmp->m.width; + max_p = cmp->p.max ?: 1 << ((1 << cmp->p.width) - 1); + + ccu_mp_find_best(parent_rate, rate, max_m, max_p, &m, &p); + + return parent_rate / p / m; +} + +static void ccu_mp_disable(struct clk_hw *hw) +{ + struct ccu_mp *cmp = hw_to_ccu_mp(hw); + + return ccu_gate_helper_disable(&cmp->common, cmp->enable); +} + +static int ccu_mp_enable(struct clk_hw *hw) +{ + struct ccu_mp *cmp = hw_to_ccu_mp(hw); + + return ccu_gate_helper_enable(&cmp->common, cmp->enable); +} + +static int ccu_mp_is_enabled(struct clk_hw *hw) +{ + struct ccu_mp *cmp = hw_to_ccu_mp(hw); + + return ccu_gate_helper_is_enabled(&cmp->common, cmp->enable); +} + +static unsigned long ccu_mp_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_mp *cmp = hw_to_ccu_mp(hw); + unsigned int m, p; + u32 reg; + + reg = readl(cmp->common.base + cmp->common.reg); + + m = reg >> cmp->m.shift; + m &= (1 << cmp->m.width) - 1; + + p = reg >> cmp->p.shift; + p &= (1 << cmp->p.width) - 1; + + return (parent_rate >> p) / (m + 1); +} + +static int ccu_mp_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct ccu_mp *cmp = hw_to_ccu_mp(hw); + + return ccu_mux_helper_determine_rate(&cmp->common, &cmp->mux, + req, ccu_mp_round_rate, cmp); +} + +static int ccu_mp_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ccu_mp *cmp = hw_to_ccu_mp(hw); + unsigned long flags; + unsigned int max_m, max_p; + unsigned int m, p; + u32 reg; + + max_m = cmp->m.max ?: 1 << cmp->m.width; + max_p = cmp->p.max ?: 1 << ((1 << cmp->p.width) - 1); + + ccu_mp_find_best(parent_rate, rate, max_m, max_p, &m, &p); + + spin_lock_irqsave(cmp->common.lock, flags); + + reg = readl(cmp->common.base + cmp->common.reg); + reg &= ~GENMASK(cmp->m.width + cmp->m.shift - 1, cmp->m.shift); + reg &= ~GENMASK(cmp->p.width + cmp->p.shift - 1, cmp->p.shift); + + writel(reg | (ilog2(p) << cmp->p.shift) | ((m - 1) << cmp->m.shift), + cmp->common.base + cmp->common.reg); + + spin_unlock_irqrestore(cmp->common.lock, flags); + + return 0; +} + +static u8 ccu_mp_get_parent(struct clk_hw *hw) +{ + struct ccu_mp *cmp = hw_to_ccu_mp(hw); + + return ccu_mux_helper_get_parent(&cmp->common, &cmp->mux); +} + +static int ccu_mp_set_parent(struct clk_hw *hw, u8 index) +{ + struct ccu_mp *cmp = hw_to_ccu_mp(hw); + + return ccu_mux_helper_set_parent(&cmp->common, &cmp->mux, index); +} + +const struct sunxi_ccu_clk_ops ccu_mp_ops = { + .disable = ccu_mp_disable, + .enable = ccu_mp_enable, + .is_enabled = ccu_mp_is_enabled, + + .get_parent = ccu_mp_get_parent, + .set_parent = ccu_mp_set_parent, + + .determine_rate = ccu_mp_determine_rate, + .recalc_rate = ccu_mp_recalc_rate, + .set_rate = ccu_mp_set_rate, +}; diff --git a/drivers/clk/sunxi/ccu_mp.h b/drivers/clk/sunxi/ccu_mp.h new file mode 100644 index 0000000..36267cd --- /dev/null +++ b/drivers/clk/sunxi/ccu_mp.h @@ -0,0 +1,70 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_mp.h in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _CCU_MP_H_ +#define _CCU_MP_H_ + +#include "ccu_common.h" +#include "ccu_div.h" +#include "ccu_mult.h" +#include "ccu_mux.h" + +/* + * struct ccu_mp - Definition of an M-P clock + * + * Clocks based on the formula parent >> P / M + */ +struct ccu_mp { + u32 enable; + + struct ccu_div_internal m; + struct ccu_div_internal p; + struct ccu_mux_internal mux; + struct ccu_common common; +}; + +#define SUNXI_CCU_MP_WITH_MUX_GATE(_struct, _name, _parents, _reg, \ + _mshift, _mwidth, \ + _pshift, _pwidth, \ + _muxshift, _muxwidth, \ + _gate, _flags) \ + struct ccu_mp _struct = { \ + .enable = _gate, \ + .m = _SUNXI_CCU_DIV(_mshift, _mwidth), \ + .p = _SUNXI_CCU_DIV(_pshift, _pwidth), \ + .mux = _SUNXI_CCU_MUX(_muxshift, _muxwidth), \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT_PARENTS(_name, \ + _parents, \ + &ccu_mp_ops, \ + _flags), \ + } \ + } + +#define SUNXI_CCU_MP_WITH_MUX(_struct, _name, _parents, _reg, \ + _mshift, _mwidth, \ + _pshift, _pwidth, \ + _muxshift, _muxwidth, \ + _flags) \ + SUNXI_CCU_MP_WITH_MUX_GATE(_struct, _name, _parents, _reg, \ + _mshift, _mwidth, \ + _pshift, _pwidth, \ + _muxshift, _muxwidth, \ + 0, _flags) + +static inline struct ccu_mp *hw_to_ccu_mp(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_mp, common); +} + +extern const struct sunxi_ccu_clk_ops ccu_mp_ops; + +#endif /* _CCU_MP_H_ */ diff --git a/drivers/clk/sunxi/ccu_mult.h b/drivers/clk/sunxi/ccu_mult.h new file mode 100644 index 0000000..2d77fdc --- /dev/null +++ b/drivers/clk/sunxi/ccu_mult.h @@ -0,0 +1,39 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_mult.h in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _CCU_MULT_H_ +#define _CCU_MULT_H_ + +#include "ccu_common.h" +#include "ccu_mux.h" + +struct ccu_mult_internal { + u8 shift; + u8 width; + u8 min; +}; + +#define _SUNXI_CCU_MULT_MIN(_shift, _width, _min) \ + { \ + .shift = _shift, \ + .width = _width, \ + .min = _min, \ + } + +#define _SUNXI_CCU_MULT(_shift, _width) \ + _SUNXI_CCU_MULT_MIN(_shift, _width, 1) + +struct ccu_mult { + u32 enable; + + struct ccu_mult_internal mult; + struct ccu_mux_internal mux; + struct ccu_common common; +}; + +#endif /* _CCU_MULT_H_ */ diff --git a/drivers/clk/sunxi/ccu_mux.c b/drivers/clk/sunxi/ccu_mux.c new file mode 100644 index 0000000..ba055b8 --- /dev/null +++ b/drivers/clk/sunxi/ccu_mux.c @@ -0,0 +1,201 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_mux.c in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include + +#include "ccu_gate.h" +#include "ccu_mux.h" + +void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common, + struct ccu_mux_internal *cm, + int parent_index, + unsigned long *parent_rate) +{ + u16 prediv = 1; + u32 reg; + int i; + + if (!((common->features & CCU_FEATURE_FIXED_PREDIV) || + (common->features & CCU_FEATURE_VARIABLE_PREDIV))) + return; + + reg = readl(common->base + common->reg); + if (parent_index < 0) { + parent_index = reg >> cm->shift; + parent_index &= (1 << cm->width) - 1; + } + + if (common->features & CCU_FEATURE_FIXED_PREDIV) + for (i = 0; i < cm->n_predivs; i++) + if (parent_index == cm->fixed_predivs[i].index) + prediv = cm->fixed_predivs[i].div; + + if (common->features & CCU_FEATURE_VARIABLE_PREDIV) + if (parent_index == cm->variable_prediv.index) { + u8 div; + + div = reg >> cm->variable_prediv.shift; + div &= (1 << cm->variable_prediv.width) - 1; + prediv = div + 1; + } + + *parent_rate = *parent_rate / prediv; +} + +int ccu_mux_helper_determine_rate(struct ccu_common *common, + struct ccu_mux_internal *cm, + struct clk_rate_request *req, + unsigned long (*round)(struct ccu_mux_internal *, + unsigned long, + unsigned long, + void *), + void *data) +{ + unsigned long best_parent_rate = 0, best_rate = 0; + struct clk_hw *best_parent, *hw = &common->hw; + unsigned int i; + + for (i = 0; i < clk_hw_get_num_parents(hw); i++) { + unsigned long tmp_rate, parent_rate; + struct clk_hw *parent; + + parent = clk_hw_get_parent_by_index(hw, i); + if (!parent) + continue; + + parent_rate = clk_hw_get_rate(parent); + ccu_mux_helper_adjust_parent_for_prediv(common, cm, i, + &parent_rate); + + tmp_rate = round(cm, clk_hw_get_rate(parent), req->rate, data); + if (tmp_rate == req->rate) { + best_parent = parent; + best_parent_rate = parent_rate; + best_rate = tmp_rate; + goto out; + } + + if ((req->rate - tmp_rate) < (req->rate - best_rate)) { + best_rate = tmp_rate; + best_parent_rate = parent_rate; + best_parent = parent; + } + } + + if (best_rate == 0) + return -EINVAL; + +out: + req->best_parent_hw = best_parent; + req->best_parent_rate = best_parent_rate; + req->rate = best_rate; + return 0; +} + +u8 ccu_mux_helper_get_parent(struct ccu_common *common, + struct ccu_mux_internal *cm) +{ + u32 reg; + u8 parent; + + reg = readl(common->base + common->reg); + parent = reg >> cm->shift; + parent &= (1 << cm->width) - 1; + + if (cm->table) { + int num_parents = clk_hw_get_num_parents(&common->hw); + int i; + + for (i = 0; i < num_parents; i++) + if (cm->table[i] == parent) + return i; + } + + return parent; +} + +int ccu_mux_helper_set_parent(struct ccu_common *common, + struct ccu_mux_internal *cm, + u8 index) +{ + unsigned long flags; + u32 reg; + + if (cm->table) + index = cm->table[index]; + + spin_lock_irqsave(common->lock, flags); + + reg = readl(common->base + common->reg); + reg &= ~GENMASK(cm->width + cm->shift - 1, cm->shift); + writel(reg | (index << cm->shift), common->base + common->reg); + + spin_unlock_irqrestore(common->lock, flags); + + return 0; +} + +static void ccu_mux_disable(struct clk_hw *hw) +{ + struct ccu_mux *cm = hw_to_ccu_mux(hw); + + return ccu_gate_helper_disable(&cm->common, cm->enable); +} + +static int ccu_mux_enable(struct clk_hw *hw) +{ + struct ccu_mux *cm = hw_to_ccu_mux(hw); + + return ccu_gate_helper_enable(&cm->common, cm->enable); +} + +static int ccu_mux_is_enabled(struct clk_hw *hw) +{ + struct ccu_mux *cm = hw_to_ccu_mux(hw); + + return ccu_gate_helper_is_enabled(&cm->common, cm->enable); +} + +static u8 ccu_mux_get_parent(struct clk_hw *hw) +{ + struct ccu_mux *cm = hw_to_ccu_mux(hw); + + return ccu_mux_helper_get_parent(&cm->common, &cm->mux); +} + +static int ccu_mux_set_parent(struct clk_hw *hw, u8 index) +{ + struct ccu_mux *cm = hw_to_ccu_mux(hw); + + return ccu_mux_helper_set_parent(&cm->common, &cm->mux, index); +} + +static unsigned long ccu_mux_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_mux *cm = hw_to_ccu_mux(hw); + + ccu_mux_helper_adjust_parent_for_prediv(&cm->common, &cm->mux, -1, + &parent_rate); + + return parent_rate; +} + +const struct sunxi_ccu_clk_ops ccu_mux_ops = { + .disable = ccu_mux_disable, + .enable = ccu_mux_enable, + .is_enabled = ccu_mux_is_enabled, + + .get_parent = ccu_mux_get_parent, + .set_parent = ccu_mux_set_parent, + + .determine_rate = __clk_mux_determine_rate, + .recalc_rate = ccu_mux_recalc_rate, +}; + diff --git a/drivers/clk/sunxi/ccu_mux.h b/drivers/clk/sunxi/ccu_mux.h new file mode 100644 index 0000000..9d094be --- /dev/null +++ b/drivers/clk/sunxi/ccu_mux.h @@ -0,0 +1,105 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_mux.h in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _CCU_MUX_H_ +#define _CCU_MUX_H_ + +#include "ccu_common.h" + +struct ccu_mux_fixed_prediv { + u8 index; + u16 div; +}; + +struct ccu_mux_internal { + u8 shift; + u8 width; + const u8 *table; + + const struct ccu_mux_fixed_prediv *fixed_predivs; + u8 n_predivs; + + struct { + u8 index; + u8 shift; + u8 width; + } variable_prediv; +}; + +#define _SUNXI_CCU_MUX_TABLE(_shift, _width, _table) \ + { \ + .shift = _shift, \ + .width = _width, \ + .table = _table, \ + } + +#define _SUNXI_CCU_MUX(_shift, _width) \ + _SUNXI_CCU_MUX_TABLE(_shift, _width, NULL) + +struct ccu_mux { + u16 reg; + u32 enable; + + struct ccu_mux_internal mux; + struct ccu_common common; +}; + +#define SUNXI_CCU_MUX_TABLE_WITH_GATE(_struct, _name, _parents, _table, \ + _reg, _shift, _width, _gate, \ + _flags) \ + struct ccu_mux _struct = { \ + .enable = _gate, \ + .mux = _SUNXI_CCU_MUX_TABLE(_shift, _width, _table), \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT_PARENTS(_name, \ + _parents, \ + &ccu_mux_ops, \ + _flags), \ + } \ + } + +#define SUNXI_CCU_MUX_WITH_GATE(_struct, _name, _parents, _reg, \ + _shift, _width, _gate, _flags) \ + SUNXI_CCU_MUX_TABLE_WITH_GATE(_struct, _name, _parents, NULL, \ + _reg, _shift, _width, _gate, \ + _flags) + +#define SUNXI_CCU_MUX(_struct, _name, _parents, _reg, _shift, _width, \ + _flags) \ + SUNXI_CCU_MUX_TABLE_WITH_GATE(_struct, _name, _parents, NULL, \ + _reg, _shift, _width, 0, _flags) + +static inline struct ccu_mux *hw_to_ccu_mux(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_mux, common); +} + +extern const struct sunxi_ccu_clk_ops ccu_mux_ops; + +void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common, + struct ccu_mux_internal *cm, + int parent_index, + unsigned long *parent_rate); +int ccu_mux_helper_determine_rate(struct ccu_common *common, + struct ccu_mux_internal *cm, + struct clk_rate_request *req, + unsigned long (*round)(struct ccu_mux_internal *, + unsigned long, + unsigned long, + void *), + void *data); +u8 ccu_mux_helper_get_parent(struct ccu_common *common, + struct ccu_mux_internal *cm); +int ccu_mux_helper_set_parent(struct ccu_common *common, + struct ccu_mux_internal *cm, + u8 index); + +#endif /* _CCU_MUX_H_ */ diff --git a/drivers/clk/sunxi/ccu_nk.c b/drivers/clk/sunxi/ccu_nk.c new file mode 100644 index 0000000..d841dd2 --- /dev/null +++ b/drivers/clk/sunxi/ccu_nk.c @@ -0,0 +1,154 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_nk.c in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include + +#include "ccu_gate.h" +#include "ccu_nk.h" + +struct _ccu_nk { + unsigned long n, min_n, max_n; + unsigned long k, min_k, max_k; +}; + +static void ccu_nk_find_best(unsigned long parent, unsigned long rate, + struct _ccu_nk *nk) +{ + unsigned long best_rate = 0; + unsigned int best_k = 0, best_n = 0; + unsigned int _k, _n; + + for (_k = nk->min_k; _k <= nk->max_k; _k++) { + for (_n = nk->min_n; _n <= nk->max_n; _n++) { + unsigned long tmp_rate = parent * _n * _k; + + if (tmp_rate > rate) + continue; + + if ((rate - tmp_rate) < (rate - best_rate)) { + best_rate = tmp_rate; + best_k = _k; + best_n = _n; + } + } + } + + nk->k = best_k; + nk->n = best_n; +} + +static void ccu_nk_disable(struct clk_hw *hw) +{ + struct ccu_nk *nk = hw_to_ccu_nk(hw); + + return ccu_gate_helper_disable(&nk->common, nk->enable); +} + +static int ccu_nk_enable(struct clk_hw *hw) +{ + struct ccu_nk *nk = hw_to_ccu_nk(hw); + + return ccu_gate_helper_enable(&nk->common, nk->enable); +} + +static int ccu_nk_is_enabled(struct clk_hw *hw) +{ + struct ccu_nk *nk = hw_to_ccu_nk(hw); + + return ccu_gate_helper_is_enabled(&nk->common, nk->enable); +} + +static unsigned long ccu_nk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_nk *nk = hw_to_ccu_nk(hw); + unsigned long rate, n, k; + u32 reg; + + reg = readl(nk->common.base + nk->common.reg); + + n = reg >> nk->n.shift; + n &= (1 << nk->n.width) - 1; + + k = reg >> nk->k.shift; + k &= (1 << nk->k.width) - 1; + + rate = parent_rate * (n + 1) * (k + 1); + + if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV) + rate /= nk->fixed_post_div; + + return rate; +} + +static long ccu_nk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct ccu_nk *nk = hw_to_ccu_nk(hw); + struct _ccu_nk _nk; + + if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV) + rate *= nk->fixed_post_div; + + _nk.min_n = nk->n.min; + _nk.max_n = 1 << nk->n.width; + _nk.min_k = nk->k.min; + _nk.max_k = 1 << nk->k.width; + + ccu_nk_find_best(*parent_rate, rate, &_nk); + rate = *parent_rate * _nk.n * _nk.k; + + if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV) + rate = rate / nk->fixed_post_div; + + return rate; +} + +static int ccu_nk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ccu_nk *nk = hw_to_ccu_nk(hw); + unsigned long flags; + struct _ccu_nk _nk; + u32 reg; + + if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV) + rate = rate * nk->fixed_post_div; + + _nk.min_n = nk->n.min; + _nk.max_n = 1 << nk->n.width; + _nk.min_k = nk->k.min; + _nk.max_k = 1 << nk->k.width; + + ccu_nk_find_best(parent_rate, rate, &_nk); + + spin_lock_irqsave(nk->common.lock, flags); + + reg = readl(nk->common.base + nk->common.reg); + reg &= ~GENMASK(nk->n.width + nk->n.shift - 1, nk->n.shift); + reg &= ~GENMASK(nk->k.width + nk->k.shift - 1, nk->k.shift); + + writel(reg | ((_nk.k - 1) << nk->k.shift) | ((_nk.n - 1) << nk->n.shift), + nk->common.base + nk->common.reg); + + spin_unlock_irqrestore(nk->common.lock, flags); + + ccu_helper_wait_for_lock(&nk->common, nk->lock); + + return 0; +} + +const struct sunxi_ccu_clk_ops ccu_nk_ops = { + .disable = ccu_nk_disable, + .enable = ccu_nk_enable, + .is_enabled = ccu_nk_is_enabled, + + .recalc_rate = ccu_nk_recalc_rate, + .round_rate = ccu_nk_round_rate, + .set_rate = ccu_nk_set_rate, +}; diff --git a/drivers/clk/sunxi/ccu_nk.h b/drivers/clk/sunxi/ccu_nk.h new file mode 100644 index 0000000..c0de93f --- /dev/null +++ b/drivers/clk/sunxi/ccu_nk.h @@ -0,0 +1,64 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_nk.h in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _CCU_NK_H_ +#define _CCU_NK_H_ + +#include "ccu_common.h" +#include "ccu_div.h" +#include "ccu_mult.h" + +/* + * struct ccu_nk - Definition of an N-K clock + * + * Clocks based on the formula parent * N * K + */ +struct ccu_nk { + u16 reg; + u32 enable; + u32 lock; + + struct ccu_mult_internal n; + struct ccu_mult_internal k; + + unsigned int fixed_post_div; + + struct ccu_common common; +}; + +#define SUNXI_CCU_NK_WITH_GATE_LOCK_POSTDIV(_struct, _name, _parent, _reg, \ + _nshift, _nwidth, \ + _kshift, _kwidth, \ + _gate, _lock, _postdiv, \ + _flags) \ + struct ccu_nk _struct = { \ + .enable = _gate, \ + .lock = _lock, \ + .k = _SUNXI_CCU_MULT(_kshift, _kwidth), \ + .n = _SUNXI_CCU_MULT(_nshift, _nwidth), \ + .fixed_post_div = _postdiv, \ + .common = { \ + .reg = _reg, \ + .features = CCU_FEATURE_FIXED_POSTDIV, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &ccu_nk_ops, \ + _flags), \ + }, \ + } + +static inline struct ccu_nk *hw_to_ccu_nk(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_nk, common); +} + +extern const struct sunxi_ccu_clk_ops ccu_nk_ops; + +#endif /* _CCU_NK_H_ */ diff --git a/drivers/clk/sunxi/ccu_nkm.c b/drivers/clk/sunxi/ccu_nkm.c new file mode 100644 index 0000000..03835a2 --- /dev/null +++ b/drivers/clk/sunxi/ccu_nkm.c @@ -0,0 +1,184 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_nkm.h in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include + +#include "ccu_gate.h" +#include "ccu_nkm.h" + +struct _ccu_nkm { + unsigned long n, min_n, max_n; + unsigned long k, min_k, max_k; + unsigned long m, min_m, max_m; +}; + +static void ccu_nkm_find_best(unsigned long parent, unsigned long rate, + struct _ccu_nkm *nkm) +{ + unsigned long best_rate = 0; + unsigned long best_n = 0, best_k = 0, best_m = 0; + unsigned long _n, _k, _m; + + for (_k = nkm->min_k; _k <= nkm->max_k; _k++) { + for (_n = nkm->min_n; _n <= nkm->max_n; _n++) { + for (_m = nkm->min_m; _m <= nkm->max_m; _m++) { + unsigned long tmp_rate; + + tmp_rate = parent * _n * _k / _m; + + if (tmp_rate > rate) + continue; + if ((rate - tmp_rate) < (rate - best_rate)) { + best_rate = tmp_rate; + best_n = _n; + best_k = _k; + best_m = _m; + } + } + } + } + + nkm->n = best_n; + nkm->k = best_k; + nkm->m = best_m; +} + +static void ccu_nkm_disable(struct clk_hw *hw) +{ + struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); + + return ccu_gate_helper_disable(&nkm->common, nkm->enable); +} + +static int ccu_nkm_enable(struct clk_hw *hw) +{ + struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); + + return ccu_gate_helper_enable(&nkm->common, nkm->enable); +} + +static int ccu_nkm_is_enabled(struct clk_hw *hw) +{ + struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); + + return ccu_gate_helper_is_enabled(&nkm->common, nkm->enable); +} + +static unsigned long ccu_nkm_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); + unsigned long n, m, k; + u32 reg; + + reg = readl(nkm->common.base + nkm->common.reg); + + n = reg >> nkm->n.shift; + n &= (1 << nkm->n.width) - 1; + + k = reg >> nkm->k.shift; + k &= (1 << nkm->k.width) - 1; + + m = reg >> nkm->m.shift; + m &= (1 << nkm->m.width) - 1; + + return parent_rate * (n + 1) * (k + 1) / (m + 1); +} + +static unsigned long ccu_nkm_round_rate(struct ccu_mux_internal *mux, + unsigned long parent_rate, + unsigned long rate, + void *data) +{ + struct ccu_nkm *nkm = data; + struct _ccu_nkm _nkm; + + _nkm.min_n = nkm->n.min; + _nkm.max_n = 1 << nkm->n.width; + _nkm.min_k = nkm->k.min; + _nkm.max_k = 1 << nkm->k.width; + _nkm.min_m = 1; + _nkm.max_m = nkm->m.max ?: 1 << nkm->m.width; + + ccu_nkm_find_best(parent_rate, rate, &_nkm); + + return parent_rate * _nkm.n * _nkm.k / _nkm.m; +} + +static int ccu_nkm_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); + + return ccu_mux_helper_determine_rate(&nkm->common, &nkm->mux, + req, ccu_nkm_round_rate, nkm); +} + +static int ccu_nkm_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); + struct _ccu_nkm _nkm; + unsigned long flags; + u32 reg; + + _nkm.min_n = nkm->n.min; + _nkm.max_n = 1 << nkm->n.width; + _nkm.min_k = nkm->k.min; + _nkm.max_k = 1 << nkm->k.width; + _nkm.min_m = 1; + _nkm.max_m = nkm->m.max ?: 1 << nkm->m.width; + + ccu_nkm_find_best(parent_rate, rate, &_nkm); + + spin_lock_irqsave(nkm->common.lock, flags); + + reg = readl(nkm->common.base + nkm->common.reg); + reg &= ~GENMASK(nkm->n.width + nkm->n.shift - 1, nkm->n.shift); + reg &= ~GENMASK(nkm->k.width + nkm->k.shift - 1, nkm->k.shift); + reg &= ~GENMASK(nkm->m.width + nkm->m.shift - 1, nkm->m.shift); + + reg |= (_nkm.n - 1) << nkm->n.shift; + reg |= (_nkm.k - 1) << nkm->k.shift; + reg |= (_nkm.m - 1) << nkm->m.shift; + + writel(reg, nkm->common.base + nkm->common.reg); + + spin_unlock_irqrestore(nkm->common.lock, flags); + + ccu_helper_wait_for_lock(&nkm->common, nkm->lock); + + return 0; +} + +static u8 ccu_nkm_get_parent(struct clk_hw *hw) +{ + struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); + + return ccu_mux_helper_get_parent(&nkm->common, &nkm->mux); +} + +static int ccu_nkm_set_parent(struct clk_hw *hw, u8 index) +{ + struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); + + return ccu_mux_helper_set_parent(&nkm->common, &nkm->mux, index); +} + +const struct sunxi_ccu_clk_ops ccu_nkm_ops = { + .disable = ccu_nkm_disable, + .enable = ccu_nkm_enable, + .is_enabled = ccu_nkm_is_enabled, + + .get_parent = ccu_nkm_get_parent, + .set_parent = ccu_nkm_set_parent, + + .determine_rate = ccu_nkm_determine_rate, + .recalc_rate = ccu_nkm_recalc_rate, + .set_rate = ccu_nkm_set_rate, +}; diff --git a/drivers/clk/sunxi/ccu_nkm.h b/drivers/clk/sunxi/ccu_nkm.h new file mode 100644 index 0000000..403fb3e --- /dev/null +++ b/drivers/clk/sunxi/ccu_nkm.h @@ -0,0 +1,84 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_nkm.h in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _CCU_NKM_H_ +#define _CCU_NKM_H_ + +#include "ccu_common.h" +#include "ccu_div.h" +#include "ccu_mult.h" + +/* + * struct ccu_nkm - Definition of an N-K-M clock + * + * Clocks based on the formula parent * N * K / M + */ +struct ccu_nkm { + u32 enable; + u32 lock; + + struct ccu_mult_internal n; + struct ccu_mult_internal k; + struct ccu_div_internal m; + struct ccu_mux_internal mux; + + struct ccu_common common; +}; + +#define SUNXI_CCU_NKM_WITH_MUX_GATE_LOCK(_struct, _name, _parents, _reg, \ + _nshift, _nwidth, \ + _kshift, _kwidth, \ + _mshift, _mwidth, \ + _muxshift, _muxwidth, \ + _gate, _lock, _flags) \ + struct ccu_nkm _struct = { \ + .enable = _gate, \ + .lock = _lock, \ + .k = _SUNXI_CCU_MULT(_kshift, _kwidth), \ + .n = _SUNXI_CCU_MULT(_nshift, _nwidth), \ + .m = _SUNXI_CCU_DIV(_mshift, _mwidth), \ + .mux = _SUNXI_CCU_MUX(_muxshift, _muxwidth), \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT_PARENTS(_name, \ + _parents, \ + &ccu_nkm_ops, \ + _flags), \ + }, \ + } + +#define SUNXI_CCU_NKM_WITH_GATE_LOCK(_struct, _name, _parent, _reg, \ + _nshift, _nwidth, \ + _kshift, _kwidth, \ + _mshift, _mwidth, \ + _gate, _lock, _flags) \ + struct ccu_nkm _struct = { \ + .enable = _gate, \ + .lock = _lock, \ + .k = _SUNXI_CCU_MULT(_kshift, _kwidth), \ + .n = _SUNXI_CCU_MULT(_nshift, _nwidth), \ + .m = _SUNXI_CCU_DIV(_mshift, _mwidth), \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &ccu_nkm_ops, \ + _flags), \ + }, \ + } + +static inline struct ccu_nkm *hw_to_ccu_nkm(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_nkm, common); +} + +extern const struct sunxi_ccu_clk_ops ccu_nkm_ops; + +#endif /* _CCU_NKM_H_ */ diff --git a/drivers/clk/sunxi/ccu_nkmp.c b/drivers/clk/sunxi/ccu_nkmp.c new file mode 100644 index 0000000..1ea3058 --- /dev/null +++ b/drivers/clk/sunxi/ccu_nkmp.c @@ -0,0 +1,171 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_nkmp.c in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include + +#include "ccu_gate.h" +#include "ccu_nkmp.h" + +struct _ccu_nkmp { + unsigned long n, min_n, max_n; + unsigned long k, min_k, max_k; + unsigned long m, min_m, max_m; + unsigned long p, min_p, max_p; +}; + +static void ccu_nkmp_find_best(unsigned long parent, unsigned long rate, + struct _ccu_nkmp *nkmp) +{ + unsigned long best_rate = 0; + unsigned long best_n = 0, best_k = 0, best_m = 0, best_p = 0; + unsigned long _n, _k, _m, _p; + + for (_k = nkmp->min_k; _k <= nkmp->max_k; _k++) { + for (_n = nkmp->min_n; _n <= nkmp->max_n; _n++) { + for (_m = nkmp->min_m; _m <= nkmp->max_m; _m++) { + for (_p = nkmp->min_p; _p <= nkmp->max_p; _p <<= 1) { + unsigned long tmp_rate; + + tmp_rate = parent * _n * _k / (_m * _p); + + if (tmp_rate > rate) + continue; + + if ((rate - tmp_rate) < (rate - best_rate)) { + best_rate = tmp_rate; + best_n = _n; + best_k = _k; + best_m = _m; + best_p = _p; + } + } + } + } + } + + nkmp->n = best_n; + nkmp->k = best_k; + nkmp->m = best_m; + nkmp->p = best_p; +} + +static void ccu_nkmp_disable(struct clk_hw *hw) +{ + struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); + + return ccu_gate_helper_disable(&nkmp->common, nkmp->enable); +} + +static int ccu_nkmp_enable(struct clk_hw *hw) +{ + struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); + + return ccu_gate_helper_enable(&nkmp->common, nkmp->enable); +} + +static int ccu_nkmp_is_enabled(struct clk_hw *hw) +{ + struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); + + return ccu_gate_helper_is_enabled(&nkmp->common, nkmp->enable); +} + +static unsigned long ccu_nkmp_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); + unsigned long n, m, k, p; + u32 reg; + + reg = readl(nkmp->common.base + nkmp->common.reg); + + n = reg >> nkmp->n.shift; + n &= (1 << nkmp->n.width) - 1; + + k = reg >> nkmp->k.shift; + k &= (1 << nkmp->k.width) - 1; + + m = reg >> nkmp->m.shift; + m &= (1 << nkmp->m.width) - 1; + + p = reg >> nkmp->p.shift; + p &= (1 << nkmp->p.width) - 1; + + return (parent_rate * (n + 1) * (k + 1) >> p) / (m + 1); +} + +static long ccu_nkmp_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); + struct _ccu_nkmp _nkmp; + + _nkmp.min_n = nkmp->n.min; + _nkmp.max_n = 1 << nkmp->n.width; + _nkmp.min_k = nkmp->k.min; + _nkmp.max_k = 1 << nkmp->k.width; + _nkmp.min_m = 1; + _nkmp.max_m = nkmp->m.max ?: 1 << nkmp->m.width; + _nkmp.min_p = 1; + _nkmp.max_p = nkmp->p.max ?: 1 << ((1 << nkmp->p.width) - 1); + + ccu_nkmp_find_best(*parent_rate, rate, &_nkmp); + + return *parent_rate * _nkmp.n * _nkmp.k / (_nkmp.m * _nkmp.p); +} + +static int ccu_nkmp_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); + struct _ccu_nkmp _nkmp; + unsigned long flags = 0; + u32 reg; + + _nkmp.min_n = 1; + _nkmp.max_n = 1 << nkmp->n.width; + _nkmp.min_k = 1; + _nkmp.max_k = 1 << nkmp->k.width; + _nkmp.min_m = 1; + _nkmp.max_m = nkmp->m.max ?: 1 << nkmp->m.width; + _nkmp.min_p = 1; + _nkmp.max_p = nkmp->p.max ?: 1 << ((1 << nkmp->p.width) - 1); + + ccu_nkmp_find_best(parent_rate, rate, &_nkmp); + + spin_lock_irqsave(nkmp->common.lock, flags); + + reg = readl(nkmp->common.base + nkmp->common.reg); + reg &= ~GENMASK(nkmp->n.width + nkmp->n.shift - 1, nkmp->n.shift); + reg &= ~GENMASK(nkmp->k.width + nkmp->k.shift - 1, nkmp->k.shift); + reg &= ~GENMASK(nkmp->m.width + nkmp->m.shift - 1, nkmp->m.shift); + reg &= ~GENMASK(nkmp->p.width + nkmp->p.shift - 1, nkmp->p.shift); + + reg |= (_nkmp.n - 1) << nkmp->n.shift; + reg |= (_nkmp.k - 1) << nkmp->k.shift; + reg |= (_nkmp.m - 1) << nkmp->m.shift; + reg |= ilog2(_nkmp.p) << nkmp->p.shift; + + writel(reg, nkmp->common.base + nkmp->common.reg); + + spin_unlock_irqrestore(nkmp->common.lock, flags); + + ccu_helper_wait_for_lock(&nkmp->common, nkmp->lock); + + return 0; +} + +const struct sunxi_ccu_clk_ops ccu_nkmp_ops = { + .disable = ccu_nkmp_disable, + .enable = ccu_nkmp_enable, + .is_enabled = ccu_nkmp_is_enabled, + + .recalc_rate = ccu_nkmp_recalc_rate, + .round_rate = ccu_nkmp_round_rate, + .set_rate = ccu_nkmp_set_rate, +}; diff --git a/drivers/clk/sunxi/ccu_nkmp.h b/drivers/clk/sunxi/ccu_nkmp.h new file mode 100644 index 0000000..701e4f5 --- /dev/null +++ b/drivers/clk/sunxi/ccu_nkmp.h @@ -0,0 +1,64 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_nkmp.c in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _CCU_NKMP_H_ +#define _CCU_NKMP_H_ + +#include "ccu_common.h" +#include "ccu_div.h" +#include "ccu_mult.h" + +/* + * struct ccu_nkmp - Definition of an N-K-M-P clock + * + * Clocks based on the formula parent * N * K >> P / M + */ +struct ccu_nkmp { + u32 enable; + u32 lock; + + struct ccu_mult_internal n; + struct ccu_mult_internal k; + struct ccu_div_internal m; + struct ccu_div_internal p; + + struct ccu_common common; +}; + +#define SUNXI_CCU_NKMP_WITH_GATE_LOCK(_struct, _name, _parent, _reg, \ + _nshift, _nwidth, \ + _kshift, _kwidth, \ + _mshift, _mwidth, \ + _pshift, _pwidth, \ + _gate, _lock, _flags) \ + struct ccu_nkmp _struct = { \ + .enable = _gate, \ + .lock = _lock, \ + .n = _SUNXI_CCU_MULT(_nshift, _nwidth), \ + .k = _SUNXI_CCU_MULT(_kshift, _kwidth), \ + .m = _SUNXI_CCU_DIV(_mshift, _mwidth), \ + .p = _SUNXI_CCU_DIV(_pshift, _pwidth), \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &ccu_nkmp_ops, \ + _flags), \ + }, \ + } + +static inline struct ccu_nkmp *hw_to_ccu_nkmp(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_nkmp, common); +} + +extern const struct sunxi_ccu_clk_ops ccu_nkmp_ops; + +#endif /* _CCU_NKMP_H_ */ diff --git a/drivers/clk/sunxi/ccu_nm.c b/drivers/clk/sunxi/ccu_nm.c new file mode 100644 index 0000000..0293f56 --- /dev/null +++ b/drivers/clk/sunxi/ccu_nm.c @@ -0,0 +1,148 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_mp.c in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include + +#include "ccu_frac.h" +#include "ccu_gate.h" +#include "ccu_nm.h" + +struct _ccu_nm { + unsigned long n, min_n, max_n; + unsigned long m, min_m, max_m; +}; + +static void ccu_nm_find_best(unsigned long parent, unsigned long rate, + struct _ccu_nm *nm) +{ + unsigned long best_rate = 0; + unsigned long best_n = 0, best_m = 0; + unsigned long _n, _m; + + for (_n = nm->min_n; _n <= nm->max_n; _n++) { + for (_m = nm->min_m; _m <= nm->max_m; _m++) { + unsigned long tmp_rate = parent * _n / _m; + + if (tmp_rate > rate) + continue; + + if ((rate - tmp_rate) < (rate - best_rate)) { + best_rate = tmp_rate; + best_n = _n; + best_m = _m; + } + } + } + + nm->n = best_n; + nm->m = best_m; +} + +static void ccu_nm_disable(struct clk_hw *hw) +{ + struct ccu_nm *nm = hw_to_ccu_nm(hw); + + return ccu_gate_helper_disable(&nm->common, nm->enable); +} + +static int ccu_nm_enable(struct clk_hw *hw) +{ + struct ccu_nm *nm = hw_to_ccu_nm(hw); + + return ccu_gate_helper_enable(&nm->common, nm->enable); +} + +static int ccu_nm_is_enabled(struct clk_hw *hw) +{ + struct ccu_nm *nm = hw_to_ccu_nm(hw); + + return ccu_gate_helper_is_enabled(&nm->common, nm->enable); +} + +static unsigned long ccu_nm_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_nm *nm = hw_to_ccu_nm(hw); + unsigned long n, m; + u32 reg; + + if (ccu_frac_helper_is_enabled(&nm->common, &nm->frac)) + return ccu_frac_helper_read_rate(&nm->common, &nm->frac); + + reg = readl(nm->common.base + nm->common.reg); + + n = reg >> nm->n.shift; + n &= (1 << nm->n.width) - 1; + + m = reg >> nm->m.shift; + m &= (1 << nm->m.width) - 1; + + return parent_rate * (n + 1) / (m + 1); +} + +static long ccu_nm_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct ccu_nm *nm = hw_to_ccu_nm(hw); + struct _ccu_nm _nm; + + _nm.min_n = nm->n.min; + _nm.max_n = 1 << nm->n.width; + _nm.min_m = 1; + _nm.max_m = nm->m.max ?: 1 << nm->m.width; + + ccu_nm_find_best(*parent_rate, rate, &_nm); + + return *parent_rate * _nm.n / _nm.m; +} + +static int ccu_nm_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ccu_nm *nm = hw_to_ccu_nm(hw); + struct _ccu_nm _nm; + unsigned long flags; + u32 reg; + + if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate)) + return ccu_frac_helper_set_rate(&nm->common, &nm->frac, rate); + else + ccu_frac_helper_disable(&nm->common, &nm->frac); + + _nm.min_n = 1; + _nm.max_n = 1 << nm->n.width; + _nm.min_m = 1; + _nm.max_m = nm->m.max ?: 1 << nm->m.width; + + ccu_nm_find_best(parent_rate, rate, &_nm); + + spin_lock_irqsave(nm->common.lock, flags); + + reg = readl(nm->common.base + nm->common.reg); + reg &= ~GENMASK(nm->n.width + nm->n.shift - 1, nm->n.shift); + reg &= ~GENMASK(nm->m.width + nm->m.shift - 1, nm->m.shift); + + writel(reg | ((_nm.m - 1) << nm->m.shift) | ((_nm.n - 1) << nm->n.shift), + nm->common.base + nm->common.reg); + + spin_unlock_irqrestore(nm->common.lock, flags); + + ccu_helper_wait_for_lock(&nm->common, nm->lock); + + return 0; +} + +const struct sunxi_ccu_clk_ops ccu_nm_ops = { + .disable = ccu_nm_disable, + .enable = ccu_nm_enable, + .is_enabled = ccu_nm_is_enabled, + + .recalc_rate = ccu_nm_recalc_rate, + .round_rate = ccu_nm_round_rate, + .set_rate = ccu_nm_set_rate, +}; diff --git a/drivers/clk/sunxi/ccu_nm.h b/drivers/clk/sunxi/ccu_nm.h new file mode 100644 index 0000000..b9187e0 --- /dev/null +++ b/drivers/clk/sunxi/ccu_nm.h @@ -0,0 +1,84 @@ +/* + * Derived from drivers/clk/sunxi-ng/ccu_nm.h in Linux, which is + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _CCU_NM_H_ +#define _CCU_NM_H_ + +#include "ccu_common.h" +#include "ccu_div.h" +#include "ccu_frac.h" +#include "ccu_mult.h" + +/* + * struct ccu_nm - Definition of an N-M clock + * + * Clocks based on the formula parent * N / M + */ +struct ccu_nm { + u32 enable; + u32 lock; + + struct ccu_mult_internal n; + struct ccu_div_internal m; + struct ccu_frac_internal frac; + + struct ccu_common common; +}; + +#define SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(_struct, _name, _parent, _reg, \ + _nshift, _nwidth, \ + _mshift, _mwidth, \ + _frac_en, _frac_sel, \ + _frac_rate_0, _frac_rate_1, \ + _gate, _lock, _flags) \ + struct ccu_nm _struct = { \ + .enable = _gate, \ + .lock = _lock, \ + .n = _SUNXI_CCU_MULT(_nshift, _nwidth), \ + .m = _SUNXI_CCU_DIV(_mshift, _mwidth), \ + .frac = _SUNXI_CCU_FRAC(_frac_en, _frac_sel, \ + _frac_rate_0, \ + _frac_rate_1), \ + .common = { \ + .reg = _reg, \ + .features = CCU_FEATURE_FRACTIONAL, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &ccu_nm_ops, \ + _flags), \ + }, \ + } + +#define SUNXI_CCU_NM_WITH_GATE_LOCK(_struct, _name, _parent, _reg, \ + _nshift, _nwidth, \ + _mshift, _mwidth, \ + _gate, _lock, _flags) \ + struct ccu_nm _struct = { \ + .enable = _gate, \ + .lock = _lock, \ + .n = _SUNXI_CCU_MULT(_nshift, _nwidth), \ + .m = _SUNXI_CCU_DIV(_mshift, _mwidth), \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &ccu_nm_ops, \ + _flags), \ + }, \ + } + +static inline struct ccu_nm *hw_to_ccu_nm(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_nm, common); +} + +extern const struct sunxi_ccu_clk_ops ccu_nm_ops; + +#endif /* _CCU_NM_H_ */ diff --git a/drivers/clk/sunxi/clk-sunxi-ccu.c b/drivers/clk/sunxi/clk-sunxi-ccu.c new file mode 100644 index 0000000..8a26dd4 --- /dev/null +++ b/drivers/clk/sunxi/clk-sunxi-ccu.c @@ -0,0 +1,550 @@ +/* + * (C) 2017 Theobroma Systems Design und Consulting GmbH + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include + +#include "ccu_common.h" + +DECLARE_GLOBAL_DATA_PTR; + +struct sunxi_clk_priv { + uint32_t *base; + size_t size; + struct clk hosc_clk; + struct clk losc_clk; + + struct clk_hw hosc, losc; +}; + +static ulong ccu_parent_recalc_rate(struct clk_hw *hw, ulong parent_rate) +{ + struct clk clk; + + if (clk_get_by_output_name(clk_hw_get_name(hw), &clk) < 0) { + error("%s: unknown clk %s\n", __func__, clk_hw_get_name(hw)); + return -ENOENT; + } + + return clk_get_rate(&clk); +} + +static struct sunxi_ccu_clk_ops parent_clk_ops = { + .recalc_rate = ccu_parent_recalc_rate, +}; + +static struct clk_init_data hosc_init_data = { + .name = "osc24M", + .ops = &parent_clk_ops, +}; + +static struct clk_init_data losc_init_data = { + .name = "osc32k", + .ops = &parent_clk_ops, +}; + +static inline +const struct sunxi_ccu_clk_ops *clk_hw_get_ops(const struct clk_hw *hw_clk) +{ + return hw_clk->init->ops; +} + +static inline +const char *clk_hw_get_parent_name_by_index(const struct clk_hw *hw_clk, + unsigned int index) +{ + return hw_clk->init->parent_names[index]; +} + +struct clk_hw *clk_hw_get_parent_by_index(const struct clk_hw *hw, + unsigned int index) +{ + if (!hw->parents) + return NULL; + + return hw->parents[index]; +} + +struct clk_hw *clk_hw_get_parent(const struct clk_hw *hw) +{ + const struct sunxi_ccu_clk_ops *ops = clk_hw_get_ops(hw); + u8 idx = 0; + + if (ops->get_parent) + idx = ops->get_parent((struct clk_hw *)hw); + + return clk_hw_get_parent_by_index(hw, idx); +} + +static int clk_hw_get_index_by_parent(struct clk_hw *hw, + struct clk_hw *parent) +{ + int i; + + for (i = 0; i < clk_hw_get_num_parents(hw); ++i) + if (!strcmp(clk_hw_get_parent_name_by_index(hw, i), + clk_hw_get_name(parent))) + return i; + + return -ENOENT; +} + +const char *clk_hw_get_name(const struct clk_hw *hw_clk) +{ + return hw_clk->init->name; +} + +unsigned int clk_hw_get_num_parents(const struct clk_hw *hw_clk) +{ + return hw_clk->init->num_parents; +} + +unsigned long clk_hw_get_flags(const struct clk_hw *hw_clk) +{ + return hw_clk->init->flags; +} + +bool clk_hw_is_enabled(const struct clk_hw *hw) +{ + const struct sunxi_ccu_clk_ops *ops = clk_hw_get_ops(hw); + struct clk_hw *parent = clk_hw_get_parent(hw); + + if (parent) + if (!clk_hw_is_enabled(parent)) + return false; + + if (ops->is_enabled) + return ops->is_enabled((struct clk_hw *)hw); + + return false; +} + +static int clk_hw_enable(struct clk_hw *hw) +{ + const struct sunxi_ccu_clk_ops *ops = clk_hw_get_ops(hw); + struct clk_hw *parent = clk_hw_get_parent(hw); + + debug("%s: clk name %s\n", __func__, clk_hw_get_name(hw)); + + /* + * If this clock is already enabled, we assume that its parent + * clocks are also enabled. + */ + if (ops->is_enabled && ops->is_enabled(hw)) + return 0; + + /* Walk the tree and enable the parents */ + if (parent) + clk_hw_enable(parent); + + if (ops->enable) + ops->enable(hw); + + return 0; +} + +static int clk_hw_disable(struct clk_hw *hw_clk) +{ + const struct sunxi_ccu_clk_ops *ops = clk_hw_get_ops(hw_clk); + + debug("%s: clk name %s\n", __func__, clk_hw_get_name(hw_clk)); + + /* Don't try to disable it, if this clock is not enabled. */ + if (ops->is_enabled && !ops->is_enabled(hw_clk)) + return 0; + + if (ops->disable) + ops->disable(hw_clk); + + return 0; +} + +static int clk_hw_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_hw *parent; + const struct sunxi_ccu_clk_ops *ops; + + if (!hw) + return 0; + + parent = clk_hw_get_parent(hw); + + req->best_parent_hw = NULL; + req->best_parent_rate = 0; + if (parent) { + req->best_parent_hw = parent; + req->best_parent_rate = clk_hw_get_rate(parent); + } + + ops = clk_hw_get_ops(hw); + if (ops->determine_rate) { + return ops->determine_rate(hw, req); + } else if (ops->round_rate) { + long rate = ops->round_rate(hw, req->rate, + &req->best_parent_rate); + + if (rate < 0) + return rate; + + req->rate = rate; + } else if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) { + return clk_hw_determine_rate(parent, req); + } else { + req->rate = clk_hw_get_rate(hw); + } + + return 0; +} + +int __clk_mux_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_hw *parent, *best_parent = NULL; + int i, num_parents, ret; + unsigned long best = 0; + struct clk_rate_request parent_req = *req; + + /* if NO_REPARENT flag set, pass through to current parent */ + if (clk_hw_get_flags(hw) & CLK_SET_RATE_NO_REPARENT) { + parent = clk_hw_get_parent(hw); + + if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) { + ret = clk_hw_determine_rate(parent, &parent_req); + if (ret) + return ret; + + best = parent_req.rate; + } else if (parent) { + best = clk_hw_get_rate(parent); + } else { + best = clk_hw_get_rate(hw); + } + + goto out; + } + + /* find the parent that can provide the fastest rate <= rate */ + num_parents = clk_hw_get_num_parents(hw); + for (i = 0; i < num_parents; i++) { + parent = clk_hw_get_parent_by_index(hw, i); + if (!parent) + continue; + + if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) { + parent_req = *req; + ret = clk_hw_determine_rate(parent, &parent_req); + if (ret) + continue; + } else { + parent_req.rate = clk_hw_get_rate(parent); + } + + if (parent_req.rate <= req->rate && parent_req.rate > best) { + best_parent = parent; + best = parent_req.rate; + } + } + + if (!best_parent) + return -EINVAL; + +out: + if (best_parent) + req->best_parent_hw = best_parent; + req->best_parent_rate = best; + req->rate = best; + + return 0; +} + +void clk_hw_reparent(struct clk_hw *hw, struct clk_hw *parent) +{ + const struct sunxi_ccu_clk_ops *ops = clk_hw_get_ops(hw); + int parent_idx = clk_hw_get_index_by_parent(hw, parent); + + if (!ops->set_parent) + return; + + ops->set_parent(hw, parent_idx); +} + +unsigned long clk_hw_get_rate(const struct clk_hw *hw) +{ + const struct sunxi_ccu_clk_ops *ops = clk_hw_get_ops(hw); + struct clk_hw *parent = clk_hw_get_parent(hw); + ulong parent_rate = 0; + ulong rate = 0; + + debug("%s(%s)\n", __func__, clk_hw_get_name(hw)); + + if (parent) + parent_rate = clk_hw_get_rate(parent); + + rate = parent_rate; + if (ops->recalc_rate) + rate = ops->recalc_rate((struct clk_hw *)hw, parent_rate); + + return rate; +} + +static struct clk_hw *ccu_parent_by_name(struct udevice *dev, const char *name) +{ + const struct sunxi_ccu_desc *desc = + (const struct sunxi_ccu_desc *)dev_get_driver_data(dev); + struct sunxi_clk_priv *priv = dev_get_priv(dev); + int i; + + debug("%s(%s)\n", __func__, name); + + for (i = 0; i < desc->hw_clks->num ; i++) { + struct clk_hw *hw = desc->hw_clks->hws[i]; + + if (!hw) + continue; + + if (!strcmp(name, clk_hw_get_name(hw))) + return hw; + } + + if (!strcmp(name, clk_hw_get_name(&priv->hosc))) + return &priv->hosc; + + if (!strcmp(name, clk_hw_get_name(&priv->losc))) + return &priv->losc; + + debug("%s: - not found => returning NULL\n", __func__); + return NULL; +} + +static ulong sunxi_clk_ccu_set_rate(struct clk *clk, ulong rate) +{ + const struct sunxi_ccu_desc *desc = + (const struct sunxi_ccu_desc *)dev_get_driver_data(clk->dev); + struct clk_hw *hw = desc->hw_clks->hws[clk->id]; + const struct sunxi_ccu_clk_ops *ops = clk_hw_get_ops(hw); + struct clk_rate_request req = { .rate = rate }; + + debug("%s (%s): id %ld rate %ld\n", + clk->dev->name, __func__, clk->id, rate); + + if (!hw) + return -ENOENT; + + /* bail early, if nothing to do */ + if (rate == clk_hw_get_rate(hw)) + return rate; + + /* enable the clock implicitly if CLK_SET_RATE_UNGATE is set */ + if (clk_hw_get_flags(hw) & CLK_SET_RATE_UNGATE) + clk_hw_enable(hw); + + /* Find the best clock rate (and possibly parent) */ + if (clk_hw_determine_rate(hw, &req) < 0) + return -EINVAL; + + /* Change the parent, if necessary */ + if (req.best_parent_hw && + req.best_parent_hw != clk_hw_get_parent(hw)) + clk_hw_reparent(hw, req.best_parent_hw); + + /* Now set the new rate */ + if (ops->set_rate) + ops->set_rate(hw, req.rate, req.best_parent_rate); + else + return -ENOSYS; + + /* Finally recalculate the rate and return it */ + return clk_hw_get_rate(hw); +} + +static ulong sunxi_clk_ccu_get_rate(struct clk *clk) +{ + const struct sunxi_ccu_desc *desc = + (const struct sunxi_ccu_desc *)dev_get_driver_data(clk->dev); + struct clk_hw *hw_clk = desc->hw_clks->hws[clk->id]; + ulong rate; + + if (!hw_clk) + return -ENOENT; + + rate = clk_hw_get_rate(hw_clk); + + debug("%s(%s): hw_clk name %s rate %ld\n", + clk->dev->name, __func__, clk_hw_get_name(hw_clk), rate); + + return rate; +} + +static int sunxi_clk_ccu_enable(struct clk *clk) +{ + const struct sunxi_ccu_desc *desc = + (const struct sunxi_ccu_desc *)dev_get_driver_data(clk->dev); + struct clk_hw *hw = desc->hw_clks->hws[clk->id]; + + return clk_hw_enable(hw); +} + +static int sunxi_clk_ccu_disable(struct clk *clk) +{ + const struct sunxi_ccu_desc *desc = + (const struct sunxi_ccu_desc *)dev_get_driver_data(clk->dev); + struct clk_hw *hw = desc->hw_clks->hws[clk->id]; + + return clk_hw_disable(hw); +} + +static struct clk_ops sunxi_clk_ccu_ops = { + .enable = sunxi_clk_ccu_enable, + .disable = sunxi_clk_ccu_disable, + .set_rate = sunxi_clk_ccu_set_rate, + .get_rate = sunxi_clk_ccu_get_rate, +}; + +static int sunxi_clk_ccu_probe(struct udevice *dev) +{ + struct sunxi_clk_priv *priv = dev_get_priv(dev); + const struct sunxi_ccu_desc *desc = + (const struct sunxi_ccu_desc *)dev_get_driver_data(dev); + fdt_addr_t addr; + fdt_size_t size; + int i; + + debug("%s: %s\n", dev->name, __func__); + + addr = fdtdec_get_addr_size_auto_noparent(gd->fdt_blob, dev->of_offset, + "reg", 0, &size, false); + if (addr == FDT_ADDR_T_NONE) { + debug("%s: could not get addr\n", dev->name); + return -EINVAL; + } + + priv->base = (void *)addr; + priv->size = size; + + /* + * Get any parent clocks defined, so searching their output + * names can work later on... + */ + clk_get_by_name(dev, "hosc", &priv->hosc_clk); + clk_get_by_name(dev, "losc", &priv->losc_clk); + + priv->hosc.dev = NULL; + priv->hosc.init = &hosc_init_data; + priv->losc.dev = NULL; + priv->losc.init = &losc_init_data; + + /* + * All clocks in the ccu_clks array of the descriptor have a + * ccu_common structure; propagate our base address into these. + */ + for (i = 0; i < desc->num_ccu_clks; i++) { + struct ccu_common *cclk = desc->ccu_clks[i]; + + if (!cclk) + continue; + + cclk->base = (void *)addr; + } + + /* + * We need to walk both lists, as they may not have the same + * entries (i.e. only nodes that have a ccu_common are present + * in the ccu_clks). + */ + for (i = 0; i < desc->hw_clks->num ; i++) { + struct clk_hw *hw = desc->hw_clks->hws[i]; + + if (!hw) + continue; + + hw->dev = dev; + if (clk_hw_get_num_parents(hw)) + hw->parents = calloc(hw->init->num_parents, + sizeof(struct clk_hw *)); + + debug("%s: init clk %s\n", __func__, clk_hw_get_name(hw)); + + for (int j = 0; j < clk_hw_get_num_parents(hw); ++j) { + const char *parent_name = hw->init->parent_names[j]; + hw->parents[j] = ccu_parent_by_name(dev, parent_name); + } + } + + return 0; +} + +#if defined(CONFIG_CMD_CLK) +static int sunxi_clk_ccu_dump(struct udevice *dev) +{ + const struct sunxi_ccu_desc *desc = + (const struct sunxi_ccu_desc *)dev_get_driver_data(dev); + int i; + + printf("%3s %20s %5s %12s %20s\n", + "id", "clk", "on?", "freq", "selected parent"); + for (i = 0; i < desc->hw_clks->num ; i++) { + struct clk_hw *hw = desc->hw_clks->hws[i]; + struct clk_hw *parent; + + if (!hw) + continue; + + parent = clk_hw_get_parent(hw); + printf("%3d %20s %5s %12lu %20s\n", i, + clk_hw_get_name(hw), + clk_hw_is_enabled(hw) ? "YES" : "---", + clk_hw_get_rate(hw), + parent ? clk_hw_get_name(parent) : ""); + } + + return 0; +} + +/** + * soc_clk_dump() - Print clock frequencies + * Returns zero on success + * + * Implementation for the clk dump command. + */ +int soc_clk_dump(void) +{ + struct udevice *dev; + + for (uclass_first_device(UCLASS_CLK, &dev); + dev; + uclass_next_device(&dev)) { + /* if this is not this driver, skip */ + if (strcmp(dev->driver->name, "sunxi_clk_ccu") != 0) + continue; + + sunxi_clk_ccu_dump(dev); + } + + return 0; +} +#endif + +#if defined(CONFIG_MACH_SUN50I) +extern const struct sunxi_ccu_desc sun50i_a64_ccu_desc; +#endif + +static const struct udevice_id sunxi_clk_ccu_ids[] = { +#if defined(CONFIG_MACH_SUN50I) + { .compatible = "allwinner,sun50i-a64-ccu", + .data = (ulong)&sun50i_a64_ccu_desc }, +#endif + {} +}; + +U_BOOT_DRIVER(sunxi_clk_ccu) = { + .name = "sunxi_clk_ccu", + .id = UCLASS_CLK, + .of_match = sunxi_clk_ccu_ids, + .ops = &sunxi_clk_ccu_ops, + .probe = sunxi_clk_ccu_probe, + .priv_auto_alloc_size = sizeof(struct sunxi_clk_priv), +}; diff --git a/drivers/clk/sunxi/clk-sunxi-gate.c b/drivers/clk/sunxi/clk-sunxi-gate.c new file mode 100644 index 0000000..d6333cc --- /dev/null +++ b/drivers/clk/sunxi/clk-sunxi-gate.c @@ -0,0 +1,92 @@ +/* + * (C) 2017 Theobroma Systems Design und Consulting GmbH + * + * SPDX-License-Identifier: GPL-2.0+ + * + */ + +#include +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +struct sunxi_clk_priv { + uint32_t *base; + size_t size; +}; + +static int sunxi_gate_update(struct clk *clk, bool enable) +{ + struct sunxi_clk_priv *priv = dev_get_priv(clk->dev); + unsigned long id = clk->id; + uint32_t offset = id / 32; + uint32_t bit = id % 32; + + debug("%s (%s): id %ld base %p offset %x bit %d enable %d\n", + clk->dev->name, __func__, id, priv->base, offset, + bit, enable); + + if (enable) + setbits_le32(priv->base + offset, BIT(bit)); + else + clrbits_le32(priv->base + offset, BIT(bit)); + + return -EINVAL; +} + +static int sunxi_gate_enable(struct clk *clk) +{ + return sunxi_gate_update(clk, true); +} + +static int sunxi_gate_disable(struct clk *clk) +{ + return sunxi_gate_update(clk, false); +} + +static struct clk_ops sunxi_clk_gate_ops = { + .enable = sunxi_gate_enable, + .disable = sunxi_gate_disable, +}; + +static int sunxi_clk_gate_probe(struct udevice *dev) +{ + struct sunxi_clk_priv *priv = dev_get_priv(dev); + fdt_addr_t addr; + fdt_size_t size; + + debug("%s: %s\n", dev->name, __func__); + + addr = fdtdec_get_addr_size_auto_noparent(gd->fdt_blob, dev->of_offset, + "reg", 0, &size, false); + if (addr == FDT_ADDR_T_NONE) { + debug("%s: could not get addr\n", dev->name); + return -EINVAL; + } + + priv->base = (void *)addr; + priv->size = size; + + return 0; +} + +static const struct udevice_id sunxi_clk_gate_ids[] = { + { .compatible = "allwinner,sunxi-multi-bus-gates-clk" }, + {} +}; + +U_BOOT_DRIVER(sunxi_clk_gate) = { + .name = "sunxi_clk_gate", + .id = UCLASS_CLK, + .of_match = sunxi_clk_gate_ids, + .ops = &sunxi_clk_gate_ops, + .probe = sunxi_clk_gate_probe, + .priv_auto_alloc_size = sizeof(struct sunxi_clk_priv), +}; + + diff --git a/drivers/clk/sunxi/clk-sunxi-mod.c b/drivers/clk/sunxi/clk-sunxi-mod.c new file mode 100644 index 0000000..4e70cc9 --- /dev/null +++ b/drivers/clk/sunxi/clk-sunxi-mod.c @@ -0,0 +1,241 @@ +/* + * (C) 2017 Theobroma Systems Design und Consulting GmbH + * + * With sun4i_a10_get_mod0_factors(...) adapted from + * linux/drivers/clk/sunxi/clk-mod0.c + * which is + * Copyright 2013 Emilio López + * + * SPDX-License-Identifier: GPL-2.0+ + * + */ + +#include +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +struct sunxi_clk_priv { + void *reg; + int num_parents; + struct clk parent[4]; +}; + +#define SRCSHIFT (24) +#define SRCMASK (0x3 << SRCSHIFT) +#define SRC(n) (n << SRCSHIFT) +#define PREDIVMASK (0x3 << 16) +#define PREDIV(n) (n << 16) +#define DIVMASK (0xf << 0) +#define DIV(n) (n) + +static ulong sunxi_mod_get_rate(struct clk *clk) +{ + struct sunxi_clk_priv *priv = dev_get_priv(clk->dev); + u32 active_parent; + ulong rate = -EINVAL; + u32 regval = readl(priv->reg); + + /* if not enabled, return 0 */ + if (regval & BIT(31)) + return 0; + + active_parent = (readl(priv->reg) >> 24) & 0x3; + if (active_parent < priv->num_parents) + rate = clk_get_rate(&priv->parent[active_parent]); + + return rate; +} + +/** + * sun4i_a10_get_mod0_factors() + * - calculates m, n factors for MOD0-style clocks + * + * MOD0 rate is calculated as follows: + * rate = (parent_rate >> p) / (m + 1); + */ + +struct factors_request { + unsigned long rate; + unsigned long parent_rate; + u8 parent_index; + u8 n; + u8 k; + u8 m; + u8 p; +}; + +static void sun4i_a10_get_mod0_factors(struct factors_request *req) +{ + u8 div, calcm, calcp; + + /* These clocks can only divide, so we will never be able to + * achieve frequencies higher than the parent frequency */ + if (req->rate >= req->parent_rate) { + req->rate = req->parent_rate; + req->m = 0; + req->p = 0; + } + + div = DIV_ROUND_UP(req->parent_rate, req->rate); + + if (div < 16) + calcp = 0; + else if (div / 2 < 16) + calcp = 1; + else if (div / 4 < 16) + calcp = 2; + else + calcp = 3; + + calcm = DIV_ROUND_UP(div, 1 << calcp); + /* clamp calcm to 16, as that is the largest possible divider */ + if (calcm > 16) + calcm = 16; + + req->rate = (req->parent_rate >> calcp) / calcm; + req->m = calcm - 1; + req->p = calcp; +} + +static ulong sunxi_mod_set_rate(struct clk *clk, ulong rate) +{ + struct sunxi_clk_priv *priv = dev_get_priv(clk->dev); + ulong best_rate = 0; + int i; + + debug("%s (%s): id %ld rate %ld base %p\n", + clk->dev->name, __func__, clk->id, rate, priv->reg); + + /* check if the current rate is already the target rate */ + if (sunxi_mod_get_rate(clk) == rate) + return rate; + + /* find the parent (iterate through) which allows us to have: + * fastest rate <= rate + */ + for (i = 0; i < priv->num_parents; ++i) { + ulong parent_rate = clk_get_rate(&priv->parent[i]); + struct factors_request req = { + .rate = rate, + .parent_rate = parent_rate, + .parent_index = i + }; + + debug("%s (%s): parent %d rate %ld\n", + clk->dev->name, __func__, i, parent_rate); + + if (parent_rate == -ENOSYS) { + debug("%s: parent %d does not support get_rate\n", + clk->dev->name, i); + continue; + } + + if (parent_rate == 0) { + debug("%s: parent %d seems disabled (rate == 0)\n", + clk->dev->name, i); + continue; + } + + /* We recalculate the dividers, even if the parent's + * rate is less than the requested rate + */ + sun4i_a10_get_mod0_factors(&req); + + if (req.rate > rate) { + debug("%s: rate %ld for parent %i exceeds rate\n", + clk->dev->name, req.rate, i); + continue; + } + + if (req.rate > best_rate) { + debug("%s: new best => parent %d P %d M %d rate %ld\n", + clk->dev->name, i, req.p, req.m, req.rate); + + clrsetbits_le32(priv->reg, + SRCMASK | PREDIVMASK | DIVMASK, + SRC(i) | PREDIV(req.p) | DIV(req.m)); + best_rate = req.rate; + + /* don't continue, if this is the requested rate */ + if (best_rate == rate) + break; + } + } + + return best_rate; +} + +static int sunxi_mod_enable(struct clk *clk) +{ + struct sunxi_clk_priv *priv = dev_get_priv(clk->dev); + + setbits_le32(priv->reg, BIT(31)); + return 0; +} + +static int sunxi_mod_disable(struct clk *clk) +{ + struct sunxi_clk_priv *priv = dev_get_priv(clk->dev); + + clrbits_le32(priv->reg, BIT(31)); + return 0; +} + +static struct clk_ops sunxi_clk_mod_ops = { + .set_rate = sunxi_mod_set_rate, + .get_rate = sunxi_mod_get_rate, + .enable = sunxi_mod_enable, + .disable = sunxi_mod_disable, +}; + +static int sunxi_clk_mod_probe(struct udevice *dev) +{ + struct sunxi_clk_priv *priv = dev_get_priv(dev); + fdt_addr_t addr; + fdt_size_t size; + int i; + + debug("%s: %s\n", dev->name, __func__); + + addr = fdtdec_get_addr_size_auto_noparent(gd->fdt_blob, dev->of_offset, + "reg", 0, &size, false); + if (addr == FDT_ADDR_T_NONE) { + debug("%s: could not get addr\n", dev->name); + return -EINVAL; + } + + priv->reg = (void *)addr; + + for (i = 0; i < 4; ++i) { + int ret = clk_get_by_index(dev, i, &priv->parent[i]); + if (ret != 0) + break; + }; + priv->num_parents = i; + + debug("%s: reg %p num-parents %d\n", + dev->name, priv->reg, priv->num_parents); + return 0; +} + +static const struct udevice_id sunxi_clk_mod_ids[] = { + { .compatible = "allwinner,sun4i-a10-mod0-clk" }, + {} +}; + +U_BOOT_DRIVER(sunxi_clk_mod) = { + .name = "sunxi_clk_mod", + .id = UCLASS_CLK, + .of_match = sunxi_clk_mod_ids, + .ops = &sunxi_clk_mod_ops, + .probe = sunxi_clk_mod_probe, + .priv_auto_alloc_size = sizeof(struct sunxi_clk_priv), +}; + + diff --git a/include/dt-bindings/clock/sun50i-a64-ccu.h b/include/dt-bindings/clock/sun50i-a64-ccu.h new file mode 100644 index 0000000..370c0a0 --- /dev/null +++ b/include/dt-bindings/clock/sun50i-a64-ccu.h @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * + * This file is dual-licensed: you can use it either under the terms + * of the GPL or the X11 license, at your option. Note that this dual + * licensing only applies to this file, and not this project as a + * whole. + * + * a) This file 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 file 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. + * + * Or, alternatively, + * + * b) Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _DT_BINDINGS_CLK_SUN50I_A64_H_ +#define _DT_BINDINGS_CLK_SUN50I_A64_H_ + +#define CLK_BUS_MIPI_DSI 28 +#define CLK_BUS_CE 29 +#define CLK_BUS_DMA 30 +#define CLK_BUS_MMC0 31 +#define CLK_BUS_MMC1 32 +#define CLK_BUS_MMC2 33 +#define CLK_BUS_NAND 34 +#define CLK_BUS_DRAM 35 +#define CLK_BUS_EMAC 36 +#define CLK_BUS_TS 37 +#define CLK_BUS_HSTIMER 38 +#define CLK_BUS_SPI0 39 +#define CLK_BUS_SPI1 40 +#define CLK_BUS_OTG 41 +#define CLK_BUS_EHCI0 42 +#define CLK_BUS_EHCI1 43 +#define CLK_BUS_OHCI0 44 +#define CLK_BUS_OHCI1 45 +#define CLK_BUS_VE 46 +#define CLK_BUS_TCON0 47 +#define CLK_BUS_TCON1 48 +#define CLK_BUS_DEINTERLACE 49 +#define CLK_BUS_CSI 50 +#define CLK_BUS_HDMI 51 +#define CLK_BUS_DE 52 +#define CLK_BUS_GPU 53 +#define CLK_BUS_MSGBOX 54 +#define CLK_BUS_SPINLOCK 55 +#define CLK_BUS_CODEC 56 +#define CLK_BUS_SPDIF 57 +#define CLK_BUS_PIO 58 +#define CLK_BUS_THS 59 +#define CLK_BUS_I2S0 60 +#define CLK_BUS_I2S1 61 +#define CLK_BUS_I2S2 62 +#define CLK_BUS_I2C0 63 +#define CLK_BUS_I2C1 64 +#define CLK_BUS_I2C2 65 +#define CLK_BUS_SCR 66 +#define CLK_BUS_UART0 67 +#define CLK_BUS_UART1 68 +#define CLK_BUS_UART2 69 +#define CLK_BUS_UART3 70 +#define CLK_BUS_UART4 71 +#define CLK_BUS_DBG 72 +#define CLK_THS 73 +#define CLK_NAND 74 +#define CLK_MMC0 75 +#define CLK_MMC1 76 +#define CLK_MMC2 77 +#define CLK_TS 78 +#define CLK_CE 79 +#define CLK_SPI0 80 +#define CLK_SPI1 81 +#define CLK_I2S0 82 +#define CLK_I2S1 83 +#define CLK_I2S2 84 +#define CLK_SPDIF 85 +#define CLK_USB_PHY0 86 +#define CLK_USB_PHY1 87 +#define CLK_USB_HSIC 88 +#define CLK_USB_HSIC_12M 89 + +#define CLK_USB_OHCI0 91 + +#define CLK_USB_OHCI1 93 + +#define CLK_DRAM_VE 95 +#define CLK_DRAM_CSI 96 +#define CLK_DRAM_DEINTERLACE 97 +#define CLK_DRAM_TS 98 +#define CLK_DE 99 +#define CLK_TCON0 100 +#define CLK_TCON1 101 +#define CLK_DEINTERLACE 102 +#define CLK_CSI_MISC 103 +#define CLK_CSI_SCLK 104 +#define CLK_CSI_MCLK 105 +#define CLK_VE 106 +#define CLK_AC_DIG 107 +#define CLK_AC_DIG_4X 108 +#define CLK_AVS 109 +#define CLK_HDMI 110 +#define CLK_HDMI_DDC 111 + +#define CLK_DSI_DPHY 113 +#define CLK_GPU 114 + +#endif /* _DT_BINDINGS_CLK_SUN50I_H_ */