diff options
Diffstat (limited to 'drivers/clk')
-rw-r--r-- | drivers/clk/Kconfig | 1 | ||||
-rw-r--r-- | drivers/clk/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/sifive/Kconfig | 19 | ||||
-rw-r--r-- | drivers/clk/sifive/Makefile | 5 | ||||
-rw-r--r-- | drivers/clk/sifive/analogbits-wrpll-cln28hpc.h | 101 | ||||
-rw-r--r-- | drivers/clk/sifive/fu540-prci.c | 604 | ||||
-rw-r--r-- | drivers/clk/sifive/wrpll-cln28hpc.c | 390 |
7 files changed, 1121 insertions, 0 deletions
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 51c931b906..ff60fc5c45 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -105,6 +105,7 @@ source "drivers/clk/mvebu/Kconfig" source "drivers/clk/owl/Kconfig" source "drivers/clk/renesas/Kconfig" source "drivers/clk/sunxi/Kconfig" +source "drivers/clk/sifive/Kconfig" source "drivers/clk/tegra/Kconfig" source "drivers/clk/uniphier/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 6a4ff9143b..de3d60ed05 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_CLK_HSDK) += clk-hsdk-cgu.o obj-$(CONFIG_CLK_MPC83XX) += mpc83xx_clk.o obj-$(CONFIG_CLK_OWL) += owl/ obj-$(CONFIG_CLK_RENESAS) += renesas/ +obj-$(CONFIG_CLK_SIFIVE) += sifive/ obj-$(CONFIG_ARCH_SUNXI) += sunxi/ obj-$(CONFIG_CLK_STM32F) += clk_stm32f.o obj-$(CONFIG_CLK_STM32MP1) += clk_stm32mp1.o diff --git a/drivers/clk/sifive/Kconfig b/drivers/clk/sifive/Kconfig new file mode 100644 index 0000000000..81fc9f8fda --- /dev/null +++ b/drivers/clk/sifive/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 + +config CLK_ANALOGBITS_WRPLL_CLN28HPC + bool + +config CLK_SIFIVE + bool "SiFive SoC driver support" + depends on CLK + help + SoC drivers for SiFive Linux-capable SoCs. + +config CLK_SIFIVE_FU540_PRCI + bool "PRCI driver for SiFive FU540 SoCs" + depends on CLK_SIFIVE + select CLK_ANALOGBITS_WRPLL_CLN28HPC + help + Supports the Power Reset Clock interface (PRCI) IP block found in + FU540 SoCs. If this kernel is meant to run on a SiFive FU540 SoC, + enable this driver. diff --git a/drivers/clk/sifive/Makefile b/drivers/clk/sifive/Makefile new file mode 100644 index 0000000000..1155e07e37 --- /dev/null +++ b/drivers/clk/sifive/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-$(CONFIG_CLK_ANALOGBITS_WRPLL_CLN28HPC) += wrpll-cln28hpc.o + +obj-$(CONFIG_CLK_SIFIVE_FU540_PRCI) += fu540-prci.o diff --git a/drivers/clk/sifive/analogbits-wrpll-cln28hpc.h b/drivers/clk/sifive/analogbits-wrpll-cln28hpc.h new file mode 100644 index 0000000000..4432e24749 --- /dev/null +++ b/drivers/clk/sifive/analogbits-wrpll-cln28hpc.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2019 Western Digital Corporation or its affiliates. + * + * Copyright (C) 2018 SiFive, Inc. + * Wesley Terpstra + * Paul Walmsley + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __LINUX_CLK_ANALOGBITS_WRPLL_CLN28HPC_H +#define __LINUX_CLK_ANALOGBITS_WRPLL_CLN28HPC_H + +#include <linux/types.h> + +/* DIVQ_VALUES: number of valid DIVQ values */ +#define DIVQ_VALUES 6 + +/* + * Bit definitions for struct analogbits_wrpll_cfg.flags + * + * WRPLL_FLAGS_BYPASS_FLAG: if set, the PLL is either in bypass, or should be + * programmed to enter bypass + * WRPLL_FLAGS_RESET_FLAG: if set, the PLL is in reset + * WRPLL_FLAGS_INT_FEEDBACK_FLAG: if set, the PLL is configured for internal + * feedback mode + * WRPLL_FLAGS_EXT_FEEDBACK_FLAG: if set, the PLL is configured for external + * feedback mode (not yet supported by this driver) + * + * The flags WRPLL_FLAGS_INT_FEEDBACK_FLAG and WRPLL_FLAGS_EXT_FEEDBACK_FLAG are + * mutually exclusive. If both bits are set, or both are zero, the struct + * analogbits_wrpll_cfg record is uninitialized or corrupt. + */ +#define WRPLL_FLAGS_BYPASS_SHIFT 0 +#define WRPLL_FLAGS_BYPASS_MASK BIT(WRPLL_FLAGS_BYPASS_SHIFT) +#define WRPLL_FLAGS_RESET_SHIFT 1 +#define WRPLL_FLAGS_RESET_MASK BIT(WRPLL_FLAGS_RESET_SHIFT) +#define WRPLL_FLAGS_INT_FEEDBACK_SHIFT 2 +#define WRPLL_FLAGS_INT_FEEDBACK_MASK BIT(WRPLL_FLAGS_INT_FEEDBACK_SHIFT) +#define WRPLL_FLAGS_EXT_FEEDBACK_SHIFT 3 +#define WRPLL_FLAGS_EXT_FEEDBACK_MASK BIT(WRPLL_FLAGS_EXT_FEEDBACK_SHIFT) + +/** + * struct analogbits_wrpll_cfg - WRPLL configuration values + * @divr: reference divider value (6 bits), as presented to the PLL signals. + * @divf: feedback divider value (9 bits), as presented to the PLL signals. + * @divq: output divider value (3 bits), as presented to the PLL signals. + * @flags: PLL configuration flags. See above for more information. + * @range: PLL loop filter range. See below for more information. + * @_output_rate_cache: cached output rates, swept across DIVQ. + * @_parent_rate: PLL refclk rate for which values are valid + * @_max_r: maximum possible R divider value, given @parent_rate + * @_init_r: initial R divider value to start the search from + * + * @divr, @divq, @divq, @range represent what the PLL expects to see + * on its input signals. Thus @divr and @divf are the actual divisors + * minus one. @divq is a power-of-two divider; for example, 1 = + * divide-by-2 and 6 = divide-by-64. 0 is an invalid @divq value. + * + * When initially passing a struct analogbits_wrpll_cfg record, the + * record should be zero-initialized with the exception of the @flags + * field. The only flag bits that need to be set are either + * WRPLL_FLAGS_INT_FEEDBACK or WRPLL_FLAGS_EXT_FEEDBACK. + * + * Field names beginning with an underscore should be considered + * private to the wrpll-cln28hpc.c code. + */ +struct analogbits_wrpll_cfg { + u8 divr; + u8 divq; + u8 range; + u8 flags; + u16 divf; + u32 _output_rate_cache[DIVQ_VALUES]; + unsigned long _parent_rate; + u8 _max_r; + u8 _init_r; +}; + +/* + * Function prototypes + */ + +int analogbits_wrpll_configure_for_rate(struct analogbits_wrpll_cfg *c, + u32 target_rate, + unsigned long parent_rate); + +unsigned int analogbits_wrpll_calc_max_lock_us(struct analogbits_wrpll_cfg *c); + +unsigned long analogbits_wrpll_calc_output_rate(struct analogbits_wrpll_cfg *c, + unsigned long parent_rate); + +#endif /* __LINUX_CLK_ANALOGBITS_WRPLL_CLN28HPC_H */ diff --git a/drivers/clk/sifive/fu540-prci.c b/drivers/clk/sifive/fu540-prci.c new file mode 100644 index 0000000000..e1b5f8e6a9 --- /dev/null +++ b/drivers/clk/sifive/fu540-prci.c @@ -0,0 +1,604 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019 Western Digital Corporation or its affiliates. + * + * Copyright (C) 2018 SiFive, Inc. + * Wesley Terpstra + * Paul Walmsley + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * The FU540 PRCI implements clock and reset control for the SiFive + * FU540-C000 chip. This driver assumes that it has sole control + * over all PRCI resources. + * + * This driver is based on the PRCI driver written by Wesley Terpstra. + * + * Refer, commit 999529edf517ed75b56659d456d221b2ee56bb60 of: + * https://github.com/riscv/riscv-linux + * + * References: + * - SiFive FU540-C000 manual v1p0, Chapter 7 "Clocking and Reset" + */ + +#include <asm/io.h> +#include <clk-uclass.h> +#include <clk.h> +#include <common.h> +#include <div64.h> +#include <dm.h> +#include <errno.h> + +#include <linux/math64.h> +#include <dt-bindings/clk/sifive-fu540-prci.h> + +#include "analogbits-wrpll-cln28hpc.h" + +/* + * EXPECTED_CLK_PARENT_COUNT: how many parent clocks this driver expects: + * hfclk and rtcclk + */ +#define EXPECTED_CLK_PARENT_COUNT 2 + +/* + * Register offsets and bitmasks + */ + +/* COREPLLCFG0 */ +#define PRCI_COREPLLCFG0_OFFSET 0x4 +#define PRCI_COREPLLCFG0_DIVR_SHIFT 0 +#define PRCI_COREPLLCFG0_DIVR_MASK (0x3f << PRCI_COREPLLCFG0_DIVR_SHIFT) +#define PRCI_COREPLLCFG0_DIVF_SHIFT 6 +#define PRCI_COREPLLCFG0_DIVF_MASK (0x1ff << PRCI_COREPLLCFG0_DIVF_SHIFT) +#define PRCI_COREPLLCFG0_DIVQ_SHIFT 15 +#define PRCI_COREPLLCFG0_DIVQ_MASK (0x7 << PRCI_COREPLLCFG0_DIVQ_SHIFT) +#define PRCI_COREPLLCFG0_RANGE_SHIFT 18 +#define PRCI_COREPLLCFG0_RANGE_MASK (0x7 << PRCI_COREPLLCFG0_RANGE_SHIFT) +#define PRCI_COREPLLCFG0_BYPASS_SHIFT 24 +#define PRCI_COREPLLCFG0_BYPASS_MASK (0x1 << PRCI_COREPLLCFG0_BYPASS_SHIFT) +#define PRCI_COREPLLCFG0_FSE_SHIFT 25 +#define PRCI_COREPLLCFG0_FSE_MASK (0x1 << PRCI_COREPLLCFG0_FSE_SHIFT) +#define PRCI_COREPLLCFG0_LOCK_SHIFT 31 +#define PRCI_COREPLLCFG0_LOCK_MASK (0x1 << PRCI_COREPLLCFG0_LOCK_SHIFT) + +/* DDRPLLCFG0 */ +#define PRCI_DDRPLLCFG0_OFFSET 0xc +#define PRCI_DDRPLLCFG0_DIVR_SHIFT 0 +#define PRCI_DDRPLLCFG0_DIVR_MASK (0x3f << PRCI_DDRPLLCFG0_DIVR_SHIFT) +#define PRCI_DDRPLLCFG0_DIVF_SHIFT 6 +#define PRCI_DDRPLLCFG0_DIVF_MASK (0x1ff << PRCI_DDRPLLCFG0_DIVF_SHIFT) +#define PRCI_DDRPLLCFG0_DIVQ_SHIFT 15 +#define PRCI_DDRPLLCFG0_DIVQ_MASK (0x7 << PRCI_DDRPLLCFG0_DIVQ_SHIFT) +#define PRCI_DDRPLLCFG0_RANGE_SHIFT 18 +#define PRCI_DDRPLLCFG0_RANGE_MASK (0x7 << PRCI_DDRPLLCFG0_RANGE_SHIFT) +#define PRCI_DDRPLLCFG0_BYPASS_SHIFT 24 +#define PRCI_DDRPLLCFG0_BYPASS_MASK (0x1 << PRCI_DDRPLLCFG0_BYPASS_SHIFT) +#define PRCI_DDRPLLCFG0_FSE_SHIFT 25 +#define PRCI_DDRPLLCFG0_FSE_MASK (0x1 << PRCI_DDRPLLCFG0_FSE_SHIFT) +#define PRCI_DDRPLLCFG0_LOCK_SHIFT 31 +#define PRCI_DDRPLLCFG0_LOCK_MASK (0x1 << PRCI_DDRPLLCFG0_LOCK_SHIFT) + +/* DDRPLLCFG1 */ +#define PRCI_DDRPLLCFG1_OFFSET 0x10 +#define PRCI_DDRPLLCFG1_CKE_SHIFT 24 +#define PRCI_DDRPLLCFG1_CKE_MASK (0x1 << PRCI_DDRPLLCFG1_CKE_SHIFT) + +/* GEMGXLPLLCFG0 */ +#define PRCI_GEMGXLPLLCFG0_OFFSET 0x1c +#define PRCI_GEMGXLPLLCFG0_DIVR_SHIFT 0 +#define PRCI_GEMGXLPLLCFG0_DIVR_MASK \ + (0x3f << PRCI_GEMGXLPLLCFG0_DIVR_SHIFT) +#define PRCI_GEMGXLPLLCFG0_DIVF_SHIFT 6 +#define PRCI_GEMGXLPLLCFG0_DIVF_MASK \ + (0x1ff << PRCI_GEMGXLPLLCFG0_DIVF_SHIFT) +#define PRCI_GEMGXLPLLCFG0_DIVQ_SHIFT 15 +#define PRCI_GEMGXLPLLCFG0_DIVQ_MASK (0x7 << PRCI_GEMGXLPLLCFG0_DIVQ_SHIFT) +#define PRCI_GEMGXLPLLCFG0_RANGE_SHIFT 18 +#define PRCI_GEMGXLPLLCFG0_RANGE_MASK \ + (0x7 << PRCI_GEMGXLPLLCFG0_RANGE_SHIFT) +#define PRCI_GEMGXLPLLCFG0_BYPASS_SHIFT 24 +#define PRCI_GEMGXLPLLCFG0_BYPASS_MASK \ + (0x1 << PRCI_GEMGXLPLLCFG0_BYPASS_SHIFT) +#define PRCI_GEMGXLPLLCFG0_FSE_SHIFT 25 +#define PRCI_GEMGXLPLLCFG0_FSE_MASK \ + (0x1 << PRCI_GEMGXLPLLCFG0_FSE_SHIFT) +#define PRCI_GEMGXLPLLCFG0_LOCK_SHIFT 31 +#define PRCI_GEMGXLPLLCFG0_LOCK_MASK (0x1 << PRCI_GEMGXLPLLCFG0_LOCK_SHIFT) + +/* GEMGXLPLLCFG1 */ +#define PRCI_GEMGXLPLLCFG1_OFFSET 0x20 +#define PRCI_GEMGXLPLLCFG1_CKE_SHIFT 24 +#define PRCI_GEMGXLPLLCFG1_CKE_MASK (0x1 << PRCI_GEMGXLPLLCFG1_CKE_SHIFT) + +/* CORECLKSEL */ +#define PRCI_CORECLKSEL_OFFSET 0x24 +#define PRCI_CORECLKSEL_CORECLKSEL_SHIFT 0 +#define PRCI_CORECLKSEL_CORECLKSEL_MASK \ + (0x1 << PRCI_CORECLKSEL_CORECLKSEL_SHIFT) + +/* DEVICESRESETREG */ +#define PRCI_DEVICESRESETREG_OFFSET 0x28 +#define PRCI_DEVICESRESETREG_DDR_CTRL_RST_N_SHIFT 0 +#define PRCI_DEVICESRESETREG_DDR_CTRL_RST_N_MASK \ + (0x1 << PRCI_DEVICESRESETREG_DDR_CTRL_RST_N_SHIFT) +#define PRCI_DEVICESRESETREG_DDR_AXI_RST_N_SHIFT 1 +#define PRCI_DEVICESRESETREG_DDR_AXI_RST_N_MASK \ + (0x1 << PRCI_DEVICESRESETREG_DDR_AXI_RST_N_SHIFT) +#define PRCI_DEVICESRESETREG_DDR_AHB_RST_N_SHIFT 2 +#define PRCI_DEVICESRESETREG_DDR_AHB_RST_N_MASK \ + (0x1 << PRCI_DEVICESRESETREG_DDR_AHB_RST_N_SHIFT) +#define PRCI_DEVICESRESETREG_DDR_PHY_RST_N_SHIFT 3 +#define PRCI_DEVICESRESETREG_DDR_PHY_RST_N_MASK \ + (0x1 << PRCI_DEVICESRESETREG_DDR_PHY_RST_N_SHIFT) +#define PRCI_DEVICESRESETREG_GEMGXL_RST_N_SHIFT 5 +#define PRCI_DEVICESRESETREG_GEMGXL_RST_N_MASK \ + (0x1 << PRCI_DEVICESRESETREG_GEMGXL_RST_N_SHIFT) + +/* CLKMUXSTATUSREG */ +#define PRCI_CLKMUXSTATUSREG_OFFSET 0x2c +#define PRCI_CLKMUXSTATUSREG_TLCLKSEL_STATUS_SHIFT 1 +#define PRCI_CLKMUXSTATUSREG_TLCLKSEL_STATUS_MASK \ + (0x1 << PRCI_CLKMUXSTATUSREG_TLCLKSEL_STATUS_SHIFT) + +/* + * Private structures + */ + +/** + * struct __prci_data - per-device-instance data + * @va: base virtual address of the PRCI IP block + * @parent: parent clk instance + * + * PRCI per-device instance data + */ +struct __prci_data { + void *base; + struct clk parent; +}; + +/** + * struct __prci_wrpll_data - WRPLL configuration and integration data + * @c: WRPLL current configuration record + * @bypass: fn ptr to code to bypass the WRPLL (if applicable; else NULL) + * @no_bypass: fn ptr to code to not bypass the WRPLL (if applicable; else NULL) + * @cfg0_offs: WRPLL CFG0 register offset (in bytes) from the PRCI base address + * + * @bypass and @no_bypass are used for WRPLL instances that contain a separate + * external glitchless clock mux downstream from the PLL. The WRPLL internal + * bypass mux is not glitchless. + */ +struct __prci_wrpll_data { + struct analogbits_wrpll_cfg c; + void (*bypass)(struct __prci_data *pd); + void (*no_bypass)(struct __prci_data *pd); + u8 cfg0_offs; +}; + +struct __prci_clock; + +struct __prci_clock_ops { + int (*set_rate)(struct __prci_clock *pc, + unsigned long rate, + unsigned long parent_rate); + unsigned long (*round_rate)(struct __prci_clock *pc, + unsigned long rate, + unsigned long *parent_rate); + unsigned long (*recalc_rate)(struct __prci_clock *pc, + unsigned long parent_rate); +}; + +/** + * struct __prci_clock - describes a clock device managed by PRCI + * @name: user-readable clock name string - should match the manual + * @parent_name: parent name for this clock + * @ops: struct clk_ops for the Linux clock framework to use for control + * @hw: Linux-private clock data + * @pwd: WRPLL-specific data, associated with this clock (if not NULL) + * @pd: PRCI-specific data associated with this clock (if not NULL) + * + * PRCI clock data. Used by the PRCI driver to register PRCI-provided + * clocks to the Linux clock infrastructure. + */ +struct __prci_clock { + const char *name; + const char *parent_name; + const struct __prci_clock_ops *ops; + struct __prci_wrpll_data *pwd; + struct __prci_data *pd; +}; + +/* + * Private functions + */ + +/** + * __prci_readl() - read from a PRCI register + * @pd: PRCI context + * @offs: register offset to read from (in bytes, from PRCI base address) + * + * Read the register located at offset @offs from the base virtual + * address of the PRCI register target described by @pd, and return + * the value to the caller. + * + * Context: Any context. + * + * Return: the contents of the register described by @pd and @offs. + */ +static u32 __prci_readl(struct __prci_data *pd, u32 offs) +{ + return readl(pd->base + offs); +} + +static void __prci_writel(u32 v, u32 offs, struct __prci_data *pd) +{ + return writel(v, pd->base + offs); +} + +/* WRPLL-related private functions */ + +/** + * __prci_wrpll_unpack() - unpack WRPLL configuration registers into parameters + * @c: ptr to a struct analogbits_wrpll_cfg record to write config into + * @r: value read from the PRCI PLL configuration register + * + * Given a value @r read from an FU540 PRCI PLL configuration register, + * split it into fields and populate it into the WRPLL configuration record + * pointed to by @c. + * + * The COREPLLCFG0 macros are used below, but the other *PLLCFG0 macros + * have the same register layout. + * + * Context: Any context. + */ +static void __prci_wrpll_unpack(struct analogbits_wrpll_cfg *c, u32 r) +{ + u32 v; + + v = r & PRCI_COREPLLCFG0_DIVR_MASK; + v >>= PRCI_COREPLLCFG0_DIVR_SHIFT; + c->divr = v; + + v = r & PRCI_COREPLLCFG0_DIVF_MASK; + v >>= PRCI_COREPLLCFG0_DIVF_SHIFT; + c->divf = v; + + v = r & PRCI_COREPLLCFG0_DIVQ_MASK; + v >>= PRCI_COREPLLCFG0_DIVQ_SHIFT; + c->divq = v; + + v = r & PRCI_COREPLLCFG0_RANGE_MASK; + v >>= PRCI_COREPLLCFG0_RANGE_SHIFT; + c->range = v; + + c->flags &= (WRPLL_FLAGS_INT_FEEDBACK_MASK | + WRPLL_FLAGS_EXT_FEEDBACK_MASK); + + if (r & PRCI_COREPLLCFG0_FSE_MASK) + c->flags |= WRPLL_FLAGS_INT_FEEDBACK_MASK; + else + c->flags |= WRPLL_FLAGS_EXT_FEEDBACK_MASK; +} + +/** + * __prci_wrpll_pack() - pack PLL configuration parameters into a register value + * @c: pointer to a struct analogbits_wrpll_cfg record containing the PLL's cfg + * + * Using a set of WRPLL configuration values pointed to by @c, + * assemble a PRCI PLL configuration register value, and return it to + * the caller. + * + * Context: Any context. Caller must ensure that the contents of the + * record pointed to by @c do not change during the execution + * of this function. + * + * Returns: a value suitable for writing into a PRCI PLL configuration + * register + */ +static u32 __prci_wrpll_pack(struct analogbits_wrpll_cfg *c) +{ + u32 r = 0; + + r |= c->divr << PRCI_COREPLLCFG0_DIVR_SHIFT; + r |= c->divf << PRCI_COREPLLCFG0_DIVF_SHIFT; + r |= c->divq << PRCI_COREPLLCFG0_DIVQ_SHIFT; + r |= c->range << PRCI_COREPLLCFG0_RANGE_SHIFT; + if (c->flags & WRPLL_FLAGS_INT_FEEDBACK_MASK) + r |= PRCI_COREPLLCFG0_FSE_MASK; + + return r; +} + +/** + * __prci_wrpll_read_cfg() - read the WRPLL configuration from the PRCI + * @pd: PRCI context + * @pwd: PRCI WRPLL metadata + * + * Read the current configuration of the PLL identified by @pwd from + * the PRCI identified by @pd, and store it into the local configuration + * cache in @pwd. + * + * Context: Any context. Caller must prevent the records pointed to by + * @pd and @pwd from changing during execution. + */ +static void __prci_wrpll_read_cfg(struct __prci_data *pd, + struct __prci_wrpll_data *pwd) +{ + __prci_wrpll_unpack(&pwd->c, __prci_readl(pd, pwd->cfg0_offs)); +} + +/** + * __prci_wrpll_write_cfg() - write WRPLL configuration into the PRCI + * @pd: PRCI context + * @pwd: PRCI WRPLL metadata + * @c: WRPLL configuration record to write + * + * Write the WRPLL configuration described by @c into the WRPLL + * configuration register identified by @pwd in the PRCI instance + * described by @c. Make a cached copy of the WRPLL's current + * configuration so it can be used by other code. + * + * Context: Any context. Caller must prevent the records pointed to by + * @pd and @pwd from changing during execution. + */ +static void __prci_wrpll_write_cfg(struct __prci_data *pd, + struct __prci_wrpll_data *pwd, + struct analogbits_wrpll_cfg *c) +{ + __prci_writel(__prci_wrpll_pack(c), pwd->cfg0_offs, pd); + + memcpy(&pwd->c, c, sizeof(struct analogbits_wrpll_cfg)); +} + +/* Core clock mux control */ + +/** + * __prci_coreclksel_use_hfclk() - switch the CORECLK mux to output HFCLK + * @pd: struct __prci_data * for the PRCI containing the CORECLK mux reg + * + * Switch the CORECLK mux to the HFCLK input source; return once complete. + * + * Context: Any context. Caller must prevent concurrent changes to the + * PRCI_CORECLKSEL_OFFSET register. + */ +static void __prci_coreclksel_use_hfclk(struct __prci_data *pd) +{ + u32 r; + + r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET); + r |= PRCI_CORECLKSEL_CORECLKSEL_MASK; + __prci_writel(r, PRCI_CORECLKSEL_OFFSET, pd); + + r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET); /* barrier */ +} + +/** + * __prci_coreclksel_use_corepll() - switch the CORECLK mux to output COREPLL + * @pd: struct __prci_data * for the PRCI containing the CORECLK mux reg + * + * Switch the CORECLK mux to the PLL output clock; return once complete. + * + * Context: Any context. Caller must prevent concurrent changes to the + * PRCI_CORECLKSEL_OFFSET register. + */ +static void __prci_coreclksel_use_corepll(struct __prci_data *pd) +{ + u32 r; + + r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET); + r &= ~PRCI_CORECLKSEL_CORECLKSEL_MASK; + __prci_writel(r, PRCI_CORECLKSEL_OFFSET, pd); + + r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET); /* barrier */ +} + +static unsigned long sifive_fu540_prci_wrpll_recalc_rate( + struct __prci_clock *pc, + unsigned long parent_rate) +{ + struct __prci_wrpll_data *pwd = pc->pwd; + + return analogbits_wrpll_calc_output_rate(&pwd->c, parent_rate); +} + +static unsigned long sifive_fu540_prci_wrpll_round_rate( + struct __prci_clock *pc, + unsigned long rate, + unsigned long *parent_rate) +{ + struct __prci_wrpll_data *pwd = pc->pwd; + struct analogbits_wrpll_cfg c; + + memcpy(&c, &pwd->c, sizeof(c)); + + analogbits_wrpll_configure_for_rate(&c, rate, *parent_rate); + + return analogbits_wrpll_calc_output_rate(&c, *parent_rate); +} + +static int sifive_fu540_prci_wrpll_set_rate(struct __prci_clock *pc, + unsigned long rate, + unsigned long parent_rate) +{ + struct __prci_wrpll_data *pwd = pc->pwd; + struct __prci_data *pd = pc->pd; + int r; + + r = analogbits_wrpll_configure_for_rate(&pwd->c, rate, parent_rate); + if (r) + return -ERANGE; + + if (pwd->bypass) + pwd->bypass(pd); + + __prci_wrpll_write_cfg(pd, pwd, &pwd->c); + + udelay(analogbits_wrpll_calc_max_lock_us(&pwd->c)); + + if (pwd->no_bypass) + pwd->no_bypass(pd); + + return 0; +} + +static const struct __prci_clock_ops sifive_fu540_prci_wrpll_clk_ops = { + .set_rate = sifive_fu540_prci_wrpll_set_rate, + .round_rate = sifive_fu540_prci_wrpll_round_rate, + .recalc_rate = sifive_fu540_prci_wrpll_recalc_rate, +}; + +static const struct __prci_clock_ops sifive_fu540_prci_wrpll_ro_clk_ops = { + .recalc_rate = sifive_fu540_prci_wrpll_recalc_rate, +}; + +/* TLCLKSEL clock integration */ + +static unsigned long sifive_fu540_prci_tlclksel_recalc_rate( + struct __prci_clock *pc, + unsigned long parent_rate) +{ + struct __prci_data *pd = pc->pd; + u32 v; + u8 div; + + v = __prci_readl(pd, PRCI_CLKMUXSTATUSREG_OFFSET); + v &= PRCI_CLKMUXSTATUSREG_TLCLKSEL_STATUS_MASK; + div = v ? 1 : 2; + + return div_u64(parent_rate, div); +} + +static const struct __prci_clock_ops sifive_fu540_prci_tlclksel_clk_ops = { + .recalc_rate = sifive_fu540_prci_tlclksel_recalc_rate, +}; + +/* + * PRCI integration data for each WRPLL instance + */ + +static struct __prci_wrpll_data __prci_corepll_data = { + .cfg0_offs = PRCI_COREPLLCFG0_OFFSET, + .bypass = __prci_coreclksel_use_hfclk, + .no_bypass = __prci_coreclksel_use_corepll, +}; + +static struct __prci_wrpll_data __prci_ddrpll_data = { + .cfg0_offs = PRCI_DDRPLLCFG0_OFFSET, +}; + +static struct __prci_wrpll_data __prci_gemgxlpll_data = { + .cfg0_offs = PRCI_GEMGXLPLLCFG0_OFFSET, +}; + +/* + * List of clock controls provided by the PRCI + */ + +static struct __prci_clock __prci_init_clocks[] = { + [PRCI_CLK_COREPLL] = { + .name = "corepll", + .parent_name = "hfclk", + .ops = &sifive_fu540_prci_wrpll_clk_ops, + .pwd = &__prci_corepll_data, + }, + [PRCI_CLK_DDRPLL] = { + .name = "ddrpll", + .parent_name = "hfclk", + .ops = &sifive_fu540_prci_wrpll_ro_clk_ops, + .pwd = &__prci_ddrpll_data, + }, + [PRCI_CLK_GEMGXLPLL] = { + .name = "gemgxlpll", + .parent_name = "hfclk", + .ops = &sifive_fu540_prci_wrpll_clk_ops, + .pwd = &__prci_gemgxlpll_data, + }, + [PRCI_CLK_TLCLK] = { + .name = "tlclk", + .parent_name = "corepll", + .ops = &sifive_fu540_prci_tlclksel_clk_ops, + }, +}; + +static ulong sifive_fu540_prci_get_rate(struct clk *clk) +{ + struct __prci_clock *pc; + + if (ARRAY_SIZE(__prci_init_clocks) <= clk->id) + return -ENXIO; + + pc = &__prci_init_clocks[clk->id]; + if (!pc->pd || !pc->ops->recalc_rate) + return -ENXIO; + + return pc->ops->recalc_rate(pc, clk_get_rate(&pc->pd->parent)); +} + +static ulong sifive_fu540_prci_set_rate(struct clk *clk, ulong rate) +{ + int err; + struct __prci_clock *pc; + + if (ARRAY_SIZE(__prci_init_clocks) <= clk->id) + return -ENXIO; + + pc = &__prci_init_clocks[clk->id]; + if (!pc->pd || !pc->ops->set_rate) + return -ENXIO; + + err = pc->ops->set_rate(pc, rate, clk_get_rate(&pc->pd->parent)); + if (err) + return err; + + return rate; +} + +static int sifive_fu540_prci_probe(struct udevice *dev) +{ + int i, err; + struct __prci_clock *pc; + struct __prci_data *pd = dev_get_priv(dev); + + pd->base = (void *)dev_read_addr(dev); + if (IS_ERR(pd->base)) + return PTR_ERR(pd->base); + + err = clk_get_by_index(dev, 0, &pd->parent); + if (err) + return err; + + for (i = 0; i < ARRAY_SIZE(__prci_init_clocks); ++i) { + pc = &__prci_init_clocks[i]; + pc->pd = pd; + if (pc->pwd) + __prci_wrpll_read_cfg(pd, pc->pwd); + } + + return 0; +} + +static struct clk_ops sifive_fu540_prci_ops = { + .set_rate = sifive_fu540_prci_set_rate, + .get_rate = sifive_fu540_prci_get_rate, +}; + +static const struct udevice_id sifive_fu540_prci_ids[] = { + { .compatible = "sifive,fu540-c000-prci0" }, + { .compatible = "sifive,aloeprci0" }, + { } +}; + +U_BOOT_DRIVER(sifive_fu540_prci) = { + .name = "sifive-fu540-prci", + .id = UCLASS_CLK, + .of_match = sifive_fu540_prci_ids, + .probe = sifive_fu540_prci_probe, + .ops = &sifive_fu540_prci_ops, + .priv_auto_alloc_size = sizeof(struct __prci_data), +}; diff --git a/drivers/clk/sifive/wrpll-cln28hpc.c b/drivers/clk/sifive/wrpll-cln28hpc.c new file mode 100644 index 0000000000..d377849693 --- /dev/null +++ b/drivers/clk/sifive/wrpll-cln28hpc.c @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019 Western Digital Corporation or its affiliates. + * + * Copyright (C) 2018 SiFive, Inc. + * Wesley Terpstra + * Paul Walmsley + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This library supports configuration parsing and reprogramming of + * the CLN28HPC variant of the Analog Bits Wide Range PLL. The + * intention is for this library to be reusable for any device that + * integrates this PLL; thus the register structure and programming + * details are expected to be provided by a separate IP block driver. + * + * The bulk of this code is primarily useful for clock configurations + * that must operate at arbitrary rates, as opposed to clock configurations + * that are restricted by software or manufacturer guidance to a small, + * pre-determined set of performance points. + * + * References: + * - Analog Bits "Wide Range PLL Datasheet", version 2015.10.01 + * - SiFive FU540-C000 Manual v1p0, Chapter 7 "Clocking and Reset" + */ + +#include <linux/bug.h> +#include <linux/err.h> +#include <linux/log2.h> +#include <linux/math64.h> + +#include "analogbits-wrpll-cln28hpc.h" + +/* MIN_INPUT_FREQ: minimum input clock frequency, in Hz (Fref_min) */ +#define MIN_INPUT_FREQ 7000000 + +/* MAX_INPUT_FREQ: maximum input clock frequency, in Hz (Fref_max) */ +#define MAX_INPUT_FREQ 600000000 + +/* MIN_POST_DIVIDE_REF_FREQ: minimum post-divider reference frequency, in Hz */ +#define MIN_POST_DIVR_FREQ 7000000 + +/* MAX_POST_DIVIDE_REF_FREQ: maximum post-divider reference frequency, in Hz */ +#define MAX_POST_DIVR_FREQ 200000000 + +/* MIN_VCO_FREQ: minimum VCO frequency, in Hz (Fvco_min) */ +#define MIN_VCO_FREQ 2400000000UL + +/* MAX_VCO_FREQ: maximum VCO frequency, in Hz (Fvco_max) */ +#define MAX_VCO_FREQ 4800000000ULL + +/* MAX_DIVQ_DIVISOR: maximum output divisor. Selected by DIVQ = 6 */ +#define MAX_DIVQ_DIVISOR 64 + +/* MAX_DIVR_DIVISOR: maximum reference divisor. Selected by DIVR = 63 */ +#define MAX_DIVR_DIVISOR 64 + +/* MAX_LOCK_US: maximum PLL lock time, in microseconds (tLOCK_max) */ +#define MAX_LOCK_US 70 + +/* + * ROUND_SHIFT: number of bits to shift to avoid precision loss in the rounding + * algorithm + */ +#define ROUND_SHIFT 20 + +/* + * Private functions + */ + +/** + * __wrpll_calc_filter_range() - determine PLL loop filter bandwidth + * @post_divr_freq: input clock rate after the R divider + * + * Select the value to be presented to the PLL RANGE input signals, based + * on the input clock frequency after the post-R-divider @post_divr_freq. + * This code follows the recommendations in the PLL datasheet for filter + * range selection. + * + * Return: The RANGE value to be presented to the PLL configuration inputs, + * or -1 upon error. + */ +static int __wrpll_calc_filter_range(unsigned long post_divr_freq) +{ + u8 range; + + if (post_divr_freq < MIN_POST_DIVR_FREQ || + post_divr_freq > MAX_POST_DIVR_FREQ) { + WARN(1, "%s: post-divider reference freq out of range: %lu", + __func__, post_divr_freq); + return -1; + } + + if (post_divr_freq < 11000000) + range = 1; + else if (post_divr_freq < 18000000) + range = 2; + else if (post_divr_freq < 30000000) + range = 3; + else if (post_divr_freq < 50000000) + range = 4; + else if (post_divr_freq < 80000000) + range = 5; + else if (post_divr_freq < 130000000) + range = 6; + else + range = 7; + + return range; +} + +/** + * __wrpll_calc_fbdiv() - return feedback fixed divide value + * @c: ptr to a struct analogbits_wrpll_cfg record to read from + * + * The internal feedback path includes a fixed by-two divider; the + * external feedback path does not. Return the appropriate divider + * value (2 or 1) depending on whether internal or external feedback + * is enabled. This code doesn't test for invalid configurations + * (e.g. both or neither of WRPLL_FLAGS_*_FEEDBACK are set); it relies + * on the caller to do so. + * + * Context: Any context. Caller must protect the memory pointed to by + * @c from simultaneous modification. + * + * Return: 2 if internal feedback is enabled or 1 if external feedback + * is enabled. + */ +static u8 __wrpll_calc_fbdiv(struct analogbits_wrpll_cfg *c) +{ + return (c->flags & WRPLL_FLAGS_INT_FEEDBACK_MASK) ? 2 : 1; +} + +/** + * __wrpll_calc_divq() - determine DIVQ based on target PLL output clock rate + * @target_rate: target PLL output clock rate + * @vco_rate: pointer to a u64 to store the computed VCO rate into + * + * Determine a reasonable value for the PLL Q post-divider, based on the + * target output rate @target_rate for the PLL. Along with returning the + * computed Q divider value as the return value, this function stores the + * desired target VCO rate into the variable pointed to by @vco_rate. + * + * Context: Any context. Caller must protect the memory pointed to by + * @vco_rate from simultaneous access or modification. + * + * Return: a positive integer DIVQ value to be programmed into the hardware + * upon success, or 0 upon error (since 0 is an invalid DIVQ value) + */ +static u8 __wrpll_calc_divq(u32 target_rate, u64 *vco_rate) +{ + u64 s; + u8 divq = 0; + + if (!vco_rate) { + WARN_ON(1); + goto wcd_out; + } + + s = div_u64(MAX_VCO_FREQ, target_rate); + if (s <= 1) { + divq = 1; + *vco_rate = MAX_VCO_FREQ; + } else if (s > MAX_DIVQ_DIVISOR) { + divq = ilog2(MAX_DIVQ_DIVISOR); + *vco_rate = MIN_VCO_FREQ; + } else { + divq = ilog2(s); + *vco_rate = target_rate << divq; + } + +wcd_out: + return divq; +} + +/** + * __wrpll_update_parent_rate() - update PLL data when parent rate changes + * @c: ptr to a struct analogbits_wrpll_cfg record to write PLL data to + * @parent_rate: PLL input refclk rate (pre-R-divider) + * + * Pre-compute some data used by the PLL configuration algorithm when + * the PLL's reference clock rate changes. The intention is to avoid + * computation when the parent rate remains constant - expected to be + * the common case. + * + * Returns: 0 upon success or -1 if the reference clock rate is out of range. + */ +static int __wrpll_update_parent_rate(struct analogbits_wrpll_cfg *c, + unsigned long parent_rate) +{ + u8 max_r_for_parent; + + if (parent_rate > MAX_INPUT_FREQ || parent_rate < MIN_POST_DIVR_FREQ) + return -1; + + c->_parent_rate = parent_rate; + max_r_for_parent = div_u64(parent_rate, MIN_POST_DIVR_FREQ); + c->_max_r = min_t(u8, MAX_DIVR_DIVISOR, max_r_for_parent); + + /* Round up */ + c->_init_r = div_u64(parent_rate + MAX_POST_DIVR_FREQ - 1, + MAX_POST_DIVR_FREQ); + + return 0; +} + +/* + * Public functions + */ + +/** + * analogbits_wrpll_configure() - compute PLL configuration for a target rate + * @c: ptr to a struct analogbits_wrpll_cfg record to write into + * @target_rate: target PLL output clock rate (post-Q-divider) + * @parent_rate: PLL input refclk rate (pre-R-divider) + * + * Given a pointer to a PLL context @c, a desired PLL target output + * rate @target_rate, and a reference clock input rate @parent_rate, + * compute the appropriate PLL signal configuration values. PLL + * reprogramming is not glitchless, so the caller should switch any + * downstream logic to a different clock source or clock-gate it + * before presenting these values to the PLL configuration signals. + * + * The caller must pass this function a pre-initialized struct + * analogbits_wrpll_cfg record: either initialized to zero (with the + * exception of the .name and .flags fields) or read from the PLL. + * + * Context: Any context. Caller must protect the memory pointed to by @c + * from simultaneous access or modification. + * + * Return: 0 upon success; anything else upon failure. + */ +int analogbits_wrpll_configure_for_rate(struct analogbits_wrpll_cfg *c, + u32 target_rate, + unsigned long parent_rate) +{ + unsigned long ratio; + u64 target_vco_rate, delta, best_delta, f_pre_div, vco, vco_pre; + u32 best_f, f, post_divr_freq, fbcfg; + u8 fbdiv, divq, best_r, r; + + if (!c) + return -1; + + if (c->flags == 0) { + WARN(1, "%s called with uninitialized PLL config", __func__); + return -1; + } + + fbcfg = WRPLL_FLAGS_INT_FEEDBACK_MASK | WRPLL_FLAGS_EXT_FEEDBACK_MASK; + if ((c->flags & fbcfg) == fbcfg) { + WARN(1, "%s called with invalid PLL config", __func__); + return -1; + } + + if (c->flags == WRPLL_FLAGS_EXT_FEEDBACK_MASK) { + WARN(1, "%s: external feedback mode not currently supported", + __func__); + return -1; + } + + /* Initialize rounding data if it hasn't been initialized already */ + if (parent_rate != c->_parent_rate) { + if (__wrpll_update_parent_rate(c, parent_rate)) { + pr_err("%s: PLL input rate is out of range\n", + __func__); + return -1; + } + } + + c->flags &= ~WRPLL_FLAGS_RESET_MASK; + + /* Put the PLL into bypass if the user requests the parent clock rate */ + if (target_rate == parent_rate) { + c->flags |= WRPLL_FLAGS_BYPASS_MASK; + return 0; + } + c->flags &= ~WRPLL_FLAGS_BYPASS_MASK; + + /* Calculate the Q shift and target VCO rate */ + divq = __wrpll_calc_divq(target_rate, &target_vco_rate); + if (divq == 0) + return -1; + c->divq = divq; + + /* Precalculate the pre-Q divider target ratio */ + ratio = div64_u64((target_vco_rate << ROUND_SHIFT), parent_rate); + + fbdiv = __wrpll_calc_fbdiv(c); + best_r = 0; + best_f = 0; + best_delta = MAX_VCO_FREQ; + + /* + * Consider all values for R which land within + * [MIN_POST_DIVR_FREQ, MAX_POST_DIVR_FREQ]; prefer smaller R + */ + for (r = c->_init_r; r <= c->_max_r; ++r) { + /* What is the best F we can pick in this case? */ + f_pre_div = ratio * r; + f = (f_pre_div + (1 << ROUND_SHIFT)) >> ROUND_SHIFT; + f >>= (fbdiv - 1); + + post_divr_freq = div_u64(parent_rate, r); + vco_pre = fbdiv * post_divr_freq; + vco = vco_pre * f; + + /* Ensure rounding didn't take us out of range */ + if (vco > target_vco_rate) { + --f; + vco = vco_pre * f; + } else if (vco < MIN_VCO_FREQ) { + ++f; + vco = vco_pre * f; + } + + delta = abs(target_rate - vco); + if (delta < best_delta) { + best_delta = delta; + best_r = r; + best_f = f; + } + } + + c->divr = best_r - 1; + c->divf = best_f - 1; + + post_divr_freq = div_u64(parent_rate, best_r); + + /* Pick the best PLL jitter filter */ + c->range = __wrpll_calc_filter_range(post_divr_freq); + + return 0; +} + +/** + * analogbits_wrpll_calc_output_rate() - calculate the PLL's target output rate + * @c: ptr to a struct analogbits_wrpll_cfg record to read from + * @parent_rate: PLL refclk rate + * + * Given a pointer to the PLL's current input configuration @c and the + * PLL's input reference clock rate @parent_rate (before the R + * pre-divider), calculate the PLL's output clock rate (after the Q + * post-divider) + * + * Context: Any context. Caller must protect the memory pointed to by @c + * from simultaneous modification. + * + * Return: the PLL's output clock rate, in Hz. + */ +unsigned long analogbits_wrpll_calc_output_rate(struct analogbits_wrpll_cfg *c, + unsigned long parent_rate) +{ + u8 fbdiv; + u64 n; + + WARN(c->flags & WRPLL_FLAGS_EXT_FEEDBACK_MASK, + "external feedback mode not yet supported"); + + fbdiv = __wrpll_calc_fbdiv(c); + n = parent_rate * fbdiv * (c->divf + 1); + n = div_u64(n, (c->divr + 1)); + n >>= c->divq; + + return n; +} + +/** + * analogbits_wrpll_calc_max_lock_us() - return the time for the PLL to lock + * @c: ptr to a struct analogbits_wrpll_cfg record to read from + * + * Return the minimum amount of time (in microseconds) that the caller + * must wait after reprogramming the PLL to ensure that it is locked + * to the input frequency and stable. This is likely to depend on the DIVR + * value; this is under discussion with the manufacturer. + * + * Return: the minimum amount of time the caller must wait for the PLL + * to lock (in microseconds) + */ +unsigned int analogbits_wrpll_calc_max_lock_us(struct analogbits_wrpll_cfg *c) +{ + return MAX_LOCK_US; +} |