clk: Add SiFive FU540 PRCI clock driver
authorAnup Patel <Anup.Patel@wdc.com>
Mon, 25 Feb 2019 08:14:49 +0000 (08:14 +0000)
committerAndes <uboot@andestech.com>
Wed, 27 Feb 2019 01:12:33 +0000 (09:12 +0800)
Add driver code for the SiFive FU540 PRCI IP block.  This IP block
handles reset and clock control for the SiFive FU540 device and
implements SoC-level clock tree controls and dividers.

Based on code written by Wesley Terpstra <wesley@sifive.com>
found in commit 999529edf517ed75b56659d456d221b2ee56bb60 of:
https://github.com/riscv/riscv-linux

Boot and PLL rate change were tested on a SiFive HiFive Unleashed
board.

Signed-off-by: Paul Walmsley <paul.walmsley@sifive.com>
Signed-off-by: Atish Patra <atish.patra@wdc.com>
Signed-off-by: Anup Patel <anup.patel@wdc.com>
Reviewed-by: Alexander Graf <agraf@suse.de>
drivers/clk/Kconfig
drivers/clk/Makefile
drivers/clk/sifive/Kconfig [new file with mode: 0644]
drivers/clk/sifive/Makefile [new file with mode: 0644]
drivers/clk/sifive/analogbits-wrpll-cln28hpc.h [new file with mode: 0644]
drivers/clk/sifive/fu540-prci.c [new file with mode: 0644]
drivers/clk/sifive/wrpll-cln28hpc.c [new file with mode: 0644]
include/dt-bindings/clk/sifive-fu540-prci.h [new file with mode: 0644]

index 51c931b906294257ba5d9ddaec5d1eaf528c1404..ff60fc5c45f5dfb3a4facc223a1e42812a864255 100644 (file)
@@ -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"
 
index 6a4ff9143b93a7aa36b22575ebb0cde0df7c400f..de3d60ed05d21d6e151095f83e4c07d6e7f47259 100644 (file)
@@ -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 (file)
index 0000000..81fc9f8
--- /dev/null
@@ -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 (file)
index 0000000..1155e07
--- /dev/null
@@ -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 (file)
index 0000000..4432e24
--- /dev/null
@@ -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 (file)
index 0000000..e1b5f8e
--- /dev/null
@@ -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 (file)
index 0000000..d377849
--- /dev/null
@@ -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;
+}
diff --git a/include/dt-bindings/clk/sifive-fu540-prci.h b/include/dt-bindings/clk/sifive-fu540-prci.h
new file mode 100644 (file)
index 0000000..531523e
--- /dev/null
@@ -0,0 +1,29 @@
+/* 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_SIFIVE_FU540_PRCI_H
+#define __LINUX_CLK_SIFIVE_FU540_PRCI_H
+
+/* Clock indexes for use by Device Tree data */
+
+#define PRCI_CLK_COREPLL               0
+#define PRCI_CLK_DDRPLL                        1
+#define PRCI_CLK_GEMGXLPLL             2
+#define PRCI_CLK_TLCLK                 3
+
+#endif