summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2021-05-17 16:23:50 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2021-05-17 16:23:50 +0200
commitc5e0e697de769d0e78a00b1cb47fe864fade9974 (patch)
tree1a9177fe4fb08156bee02cdd2df0dbed61f2a12a /drivers
parentec8f2fe6b1bc9e69a725863a61a6b84ab7a91989 (diff)
parentb383d7739c355fee9f81986e6e8d030185373ca5 (diff)
downloadbarebox-c5e0e697de769d0e78a00b1cb47fe864fade9974.tar.gz
barebox-c5e0e697de769d0e78a00b1cb47fe864fade9974.tar.xz
Merge branch 'for-next/riscv'
Diffstat (limited to 'drivers')
-rw-r--r--drivers/clk/Kconfig2
-rw-r--r--drivers/clk/Makefile2
-rw-r--r--drivers/clk/analogbits/Makefile3
-rw-r--r--drivers/clk/analogbits/wrpll-cln28hpc.c367
-rw-r--r--drivers/clk/sifive/Kconfig18
-rw-r--r--drivers/clk/sifive/Makefile2
-rw-r--r--drivers/clk/sifive/fu540-prci.c87
-rw-r--r--drivers/clk/sifive/fu540-prci.h16
-rw-r--r--drivers/clk/sifive/fu740-prci.c121
-rw-r--r--drivers/clk/sifive/fu740-prci.h21
-rw-r--r--drivers/clk/sifive/sifive-prci.c581
-rw-r--r--drivers/clk/sifive/sifive-prci.h298
-rw-r--r--drivers/gpio/Kconfig7
-rw-r--r--drivers/gpio/Makefile1
-rw-r--r--drivers/gpio/gpio-generic.c445
-rw-r--r--drivers/gpio/gpio-mpc8xxx.c6
-rw-r--r--drivers/gpio/gpio-sifive.c87
-rw-r--r--drivers/serial/Kconfig9
-rw-r--r--drivers/serial/Makefile1
-rw-r--r--drivers/serial/serial_sifive.c171
20 files changed, 2115 insertions, 130 deletions
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index c05e065651..d8649c3f9b 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -19,3 +19,5 @@ config CLK_SOCFPGA
bool
select COMMON_CLK_OF_PROVIDER
default y if ARCH_SOCFPGA && OFDEVICE
+
+source "drivers/clk/sifive/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 04c797e7e0..b0be8d1bd8 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -21,3 +21,5 @@ obj-$(CONFIG_ARCH_STM32MP) += clk-stm32mp1.o
obj-$(CONFIG_MACH_VEXPRESS) += vexpress/
obj-$(CONFIG_MACH_MIPS_LOONGSON)+= loongson/
obj-$(CONFIG_ARCH_LAYERSCAPE) += clk-qoric.o
+obj-y += analogbits/
+obj-$(CONFIG_CLK_SIFIVE) += sifive/
diff --git a/drivers/clk/analogbits/Makefile b/drivers/clk/analogbits/Makefile
new file mode 100644
index 0000000000..c893283ea5
--- /dev/null
+++ b/drivers/clk/analogbits/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-y += wrpll-cln28hpc.o
diff --git a/drivers/clk/analogbits/wrpll-cln28hpc.c b/drivers/clk/analogbits/wrpll-cln28hpc.c
new file mode 100644
index 0000000000..640af533d6
--- /dev/null
+++ b/drivers/clk/analogbits/wrpll-cln28hpc.c
@@ -0,0 +1,367 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018-2019 SiFive, Inc.
+ * Wesley Terpstra
+ * Paul Walmsley
+ *
+ * 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"
+ * https://static.dev.sifive.com/FU540-C000-v1.0.pdf
+ */
+
+#include <linux/kernel.h>
+#include <stdio.h>
+#include <printk.h>
+#include <linux/bug.h>
+#include <linux/err.h>
+#include <linux/log2.h>
+#include <linux/math64.h>
+#include <linux/clk/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 a negative return code upon error.
+ */
+static int __wrpll_calc_filter_range(unsigned long post_divr_freq)
+{
+ 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 -ERANGE;
+ }
+
+ switch (post_divr_freq) {
+ case 0 ... 10999999:
+ return 1;
+ case 11000000 ... 17999999:
+ return 2;
+ case 18000000 ... 29999999:
+ return 3;
+ case 30000000 ... 49999999:
+ return 4;
+ case 50000000 ... 79999999:
+ return 5;
+ case 80000000 ... 129999999:
+ return 6;
+ }
+
+ return 7;
+}
+
+/**
+ * __wrpll_calc_fbdiv() - return feedback fixed divide value
+ * @c: ptr to a struct 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(const struct 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 = (u64)target_rate << divq;
+ }
+
+wcd_out:
+ return divq;
+}
+
+/**
+ * __wrpll_update_parent_rate() - update PLL data when parent rate changes
+ * @c: ptr to a struct 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 -ERANGE if the reference clock rate is
+ * out of range.
+ */
+static int __wrpll_update_parent_rate(struct 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 -ERANGE;
+
+ 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);
+
+ c->init_r = DIV_ROUND_UP_ULL(parent_rate, MAX_POST_DIVR_FREQ);
+
+ return 0;
+}
+
+/**
+ * wrpll_configure() - compute PLL configuration for a target rate
+ * @c: ptr to a struct 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)
+ *
+ * Compute the appropriate PLL signal configuration values and store
+ * in PLL context @c. 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
+ * 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 wrpll_configure_for_rate(struct 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;
+ u8 fbdiv, divq, best_r, r;
+ int range;
+
+ if (c->flags == 0) {
+ WARN(1, "%s called with uninitialized PLL config", __func__);
+ return -EINVAL;
+ }
+
+ /* 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 -ERANGE;
+ }
+ }
+
+ 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)
+ 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) {
+ 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 */
+ range = __wrpll_calc_filter_range(post_divr_freq);
+ if (range < 0)
+ return range;
+ c->range = range;
+
+ return 0;
+}
+
+/**
+ * wrpll_calc_output_rate() - calculate the PLL's target output rate
+ * @c: ptr to a struct 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. The return value from
+ * this function is intended to be convenient to pass directly
+ * to the Linux clock framework; thus there is no explicit
+ * error return value.
+ */
+unsigned long wrpll_calc_output_rate(const struct wrpll_cfg *c,
+ unsigned long parent_rate)
+{
+ u8 fbdiv;
+ u64 n;
+
+ if (c->flags & WRPLL_FLAGS_EXT_FEEDBACK_MASK) {
+ WARN(1, "external feedback mode not yet supported");
+ return ULONG_MAX;
+ }
+
+ fbdiv = __wrpll_calc_fbdiv(c);
+ n = parent_rate * fbdiv * (c->divf + 1);
+ n = div_u64(n, c->divr + 1);
+ n >>= c->divq;
+
+ return n;
+}
+
+/**
+ * wrpll_calc_max_lock_us() - return the time for the PLL to lock
+ * @c: ptr to a struct 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 wrpll_calc_max_lock_us(const struct wrpll_cfg *c)
+{
+ return MAX_LOCK_US;
+}
diff --git a/drivers/clk/sifive/Kconfig b/drivers/clk/sifive/Kconfig
new file mode 100644
index 0000000000..6b3f4bc600
--- /dev/null
+++ b/drivers/clk/sifive/Kconfig
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0
+
+menuconfig CLK_SIFIVE
+ bool "SiFive SoC driver support"
+ depends on RISCV || COMPILE_TEST
+ help
+ SoC drivers for SiFive Linux-capable SoCs.
+
+if CLK_SIFIVE
+
+config CLK_SIFIVE_PRCI
+ bool "PRCI driver for SiFive SoCs"
+ help
+ Supports the Power Reset Clock interface (PRCI) IP block found in
+ FU540/FU740 SoCs. If this kernel is meant to run on a SiFive FU540/
+ FU740 SoCs, enable this driver.
+
+endif
diff --git a/drivers/clk/sifive/Makefile b/drivers/clk/sifive/Makefile
new file mode 100644
index 0000000000..7b06fc04e6
--- /dev/null
+++ b/drivers/clk/sifive/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_CLK_SIFIVE_PRCI) += sifive-prci.o fu540-prci.o fu740-prci.o
diff --git a/drivers/clk/sifive/fu540-prci.c b/drivers/clk/sifive/fu540-prci.c
new file mode 100644
index 0000000000..e6379dfd6a
--- /dev/null
+++ b/drivers/clk/sifive/fu540-prci.c
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018-2019 SiFive, Inc.
+ * Copyright (C) 2018-2019 Wesley Terpstra
+ * Copyright (C) 2018-2019 Paul Walmsley
+ * Copyright (C) 2020 Zong Li
+ *
+ * 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:
+ * https://github.com/riscv/riscv-linux/commit/999529edf517ed75b56659d456d221b2ee56bb60
+ *
+ * References:
+ * - SiFive FU540-C000 manual v1p0, Chapter 7 "Clocking and Reset"
+ */
+
+#include <dt-bindings/clock/sifive-fu540-prci.h>
+
+#include "fu540-prci.h"
+#include "sifive-prci.h"
+
+/* PRCI integration data for each WRPLL instance */
+
+static struct __prci_wrpll_data __prci_corepll_data = {
+ .cfg0_offs = PRCI_COREPLLCFG0_OFFSET,
+ .cfg1_offs = PRCI_COREPLLCFG1_OFFSET,
+ .enable_bypass = sifive_prci_coreclksel_use_hfclk,
+ .disable_bypass = sifive_prci_coreclksel_use_corepll,
+};
+
+static struct __prci_wrpll_data __prci_ddrpll_data = {
+ .cfg0_offs = PRCI_DDRPLLCFG0_OFFSET,
+ .cfg1_offs = PRCI_DDRPLLCFG1_OFFSET,
+};
+
+static struct __prci_wrpll_data __prci_gemgxlpll_data = {
+ .cfg0_offs = PRCI_GEMGXLPLLCFG0_OFFSET,
+ .cfg1_offs = PRCI_GEMGXLPLLCFG1_OFFSET,
+};
+
+/* Linux clock framework integration */
+
+static const struct clk_ops sifive_fu540_prci_wrpll_clk_ops = {
+ .set_rate = sifive_prci_wrpll_set_rate,
+ .round_rate = sifive_prci_wrpll_round_rate,
+ .recalc_rate = sifive_prci_wrpll_recalc_rate,
+ .enable = sifive_prci_clock_enable,
+ .disable = sifive_prci_clock_disable,
+ .is_enabled = sifive_clk_is_enabled,
+};
+
+static const struct clk_ops sifive_fu540_prci_wrpll_ro_clk_ops = {
+ .recalc_rate = sifive_prci_wrpll_recalc_rate,
+};
+
+static const struct clk_ops sifive_fu540_prci_tlclksel_clk_ops = {
+ .recalc_rate = sifive_prci_tlclksel_recalc_rate,
+};
+
+/* List of clock controls provided by the PRCI */
+struct __prci_clock __prci_init_clocks_fu540[] = {
+ [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,
+ },
+};
diff --git a/drivers/clk/sifive/fu540-prci.h b/drivers/clk/sifive/fu540-prci.h
new file mode 100644
index 0000000000..c220677dc0
--- /dev/null
+++ b/drivers/clk/sifive/fu540-prci.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 SiFive, Inc.
+ * Zong Li
+ */
+
+#ifndef __SIFIVE_CLK_FU540_PRCI_H
+#define __SIFIVE_CLK_FU540_PRCI_H
+
+#include "sifive-prci.h"
+
+#define NUM_CLOCK_FU540 4
+
+extern struct __prci_clock __prci_init_clocks_fu540[NUM_CLOCK_FU540];
+
+#endif /* __SIFIVE_CLK_FU540_PRCI_H */
diff --git a/drivers/clk/sifive/fu740-prci.c b/drivers/clk/sifive/fu740-prci.c
new file mode 100644
index 0000000000..14df75f7f6
--- /dev/null
+++ b/drivers/clk/sifive/fu740-prci.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 SiFive, Inc.
+ * Copyright (C) 2020 Zong Li
+ */
+
+#include <dt-bindings/clock/sifive-fu740-prci.h>
+
+#include "fu540-prci.h"
+#include "sifive-prci.h"
+
+/* PRCI integration data for each WRPLL instance */
+
+static struct __prci_wrpll_data __prci_corepll_data = {
+ .cfg0_offs = PRCI_COREPLLCFG0_OFFSET,
+ .cfg1_offs = PRCI_COREPLLCFG1_OFFSET,
+ .enable_bypass = sifive_prci_coreclksel_use_hfclk,
+ .disable_bypass = sifive_prci_coreclksel_use_final_corepll,
+};
+
+static struct __prci_wrpll_data __prci_ddrpll_data = {
+ .cfg0_offs = PRCI_DDRPLLCFG0_OFFSET,
+ .cfg1_offs = PRCI_DDRPLLCFG1_OFFSET,
+};
+
+static struct __prci_wrpll_data __prci_gemgxlpll_data = {
+ .cfg0_offs = PRCI_GEMGXLPLLCFG0_OFFSET,
+ .cfg1_offs = PRCI_GEMGXLPLLCFG1_OFFSET,
+};
+
+static struct __prci_wrpll_data __prci_dvfscorepll_data = {
+ .cfg0_offs = PRCI_DVFSCOREPLLCFG0_OFFSET,
+ .cfg1_offs = PRCI_DVFSCOREPLLCFG1_OFFSET,
+ .enable_bypass = sifive_prci_corepllsel_use_corepll,
+ .disable_bypass = sifive_prci_corepllsel_use_dvfscorepll,
+};
+
+static struct __prci_wrpll_data __prci_hfpclkpll_data = {
+ .cfg0_offs = PRCI_HFPCLKPLLCFG0_OFFSET,
+ .cfg1_offs = PRCI_HFPCLKPLLCFG1_OFFSET,
+ .enable_bypass = sifive_prci_hfpclkpllsel_use_hfclk,
+ .disable_bypass = sifive_prci_hfpclkpllsel_use_hfpclkpll,
+};
+
+static struct __prci_wrpll_data __prci_cltxpll_data = {
+ .cfg0_offs = PRCI_CLTXPLLCFG0_OFFSET,
+ .cfg1_offs = PRCI_CLTXPLLCFG1_OFFSET,
+};
+
+/* Linux clock framework integration */
+
+static const struct clk_ops sifive_fu740_prci_wrpll_clk_ops = {
+ .set_rate = sifive_prci_wrpll_set_rate,
+ .round_rate = sifive_prci_wrpll_round_rate,
+ .recalc_rate = sifive_prci_wrpll_recalc_rate,
+ .enable = sifive_prci_clock_enable,
+ .disable = sifive_prci_clock_disable,
+ .is_enabled = sifive_clk_is_enabled,
+};
+
+static const struct clk_ops sifive_fu740_prci_wrpll_ro_clk_ops = {
+ .recalc_rate = sifive_prci_wrpll_recalc_rate,
+};
+
+static const struct clk_ops sifive_fu740_prci_tlclksel_clk_ops = {
+ .recalc_rate = sifive_prci_tlclksel_recalc_rate,
+};
+
+static const struct clk_ops sifive_fu740_prci_hfpclkplldiv_clk_ops = {
+ .recalc_rate = sifive_prci_hfpclkplldiv_recalc_rate,
+};
+
+/* List of clock controls provided by the PRCI */
+struct __prci_clock __prci_init_clocks_fu740[] = {
+ [PRCI_CLK_COREPLL] = {
+ .name = "corepll",
+ .parent_name = "hfclk",
+ .ops = &sifive_fu740_prci_wrpll_clk_ops,
+ .pwd = &__prci_corepll_data,
+ },
+ [PRCI_CLK_DDRPLL] = {
+ .name = "ddrpll",
+ .parent_name = "hfclk",
+ .ops = &sifive_fu740_prci_wrpll_ro_clk_ops,
+ .pwd = &__prci_ddrpll_data,
+ },
+ [PRCI_CLK_GEMGXLPLL] = {
+ .name = "gemgxlpll",
+ .parent_name = "hfclk",
+ .ops = &sifive_fu740_prci_wrpll_clk_ops,
+ .pwd = &__prci_gemgxlpll_data,
+ },
+ [PRCI_CLK_DVFSCOREPLL] = {
+ .name = "dvfscorepll",
+ .parent_name = "hfclk",
+ .ops = &sifive_fu740_prci_wrpll_clk_ops,
+ .pwd = &__prci_dvfscorepll_data,
+ },
+ [PRCI_CLK_HFPCLKPLL] = {
+ .name = "hfpclkpll",
+ .parent_name = "hfclk",
+ .ops = &sifive_fu740_prci_wrpll_clk_ops,
+ .pwd = &__prci_hfpclkpll_data,
+ },
+ [PRCI_CLK_CLTXPLL] = {
+ .name = "cltxpll",
+ .parent_name = "hfclk",
+ .ops = &sifive_fu740_prci_wrpll_clk_ops,
+ .pwd = &__prci_cltxpll_data,
+ },
+ [PRCI_CLK_TLCLK] = {
+ .name = "tlclk",
+ .parent_name = "corepll",
+ .ops = &sifive_fu740_prci_tlclksel_clk_ops,
+ },
+ [PRCI_CLK_PCLK] = {
+ .name = "pclk",
+ .parent_name = "hfpclkpll",
+ .ops = &sifive_fu740_prci_hfpclkplldiv_clk_ops,
+ },
+};
diff --git a/drivers/clk/sifive/fu740-prci.h b/drivers/clk/sifive/fu740-prci.h
new file mode 100644
index 0000000000..13ef971f77
--- /dev/null
+++ b/drivers/clk/sifive/fu740-prci.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 SiFive, Inc.
+ * Zong Li
+ */
+
+#ifndef __SIFIVE_CLK_FU740_PRCI_H
+#define __SIFIVE_CLK_FU740_PRCI_H
+
+#include "sifive-prci.h"
+
+#define NUM_CLOCK_FU740 8
+
+extern struct __prci_clock __prci_init_clocks_fu740[NUM_CLOCK_FU740];
+
+static const struct prci_clk_desc prci_clk_fu740 = {
+ .clks = __prci_init_clocks_fu740,
+ .num_clks = ARRAY_SIZE(__prci_init_clocks_fu740),
+};
+
+#endif /* __SIFIVE_CLK_FU740_PRCI_H */
diff --git a/drivers/clk/sifive/sifive-prci.c b/drivers/clk/sifive/sifive-prci.c
new file mode 100644
index 0000000000..b452bbf8cc
--- /dev/null
+++ b/drivers/clk/sifive/sifive-prci.c
@@ -0,0 +1,581 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 SiFive, Inc.
+ * Copyright (C) 2020 Zong Li
+ */
+
+#include <common.h>
+#include <linux/list.h>
+#include <linux/clkdev.h>
+#include <linux/overflow.h>
+#include <printk.h>
+#include <clock.h>
+#include <io.h>
+#include <of.h>
+#include <driver.h>
+#include <init.h>
+#include "sifive-prci.h"
+#include "fu540-prci.h"
+#include "fu740-prci.h"
+
+static const struct prci_clk_desc prci_clk_fu540 = {
+ .clks = __prci_init_clocks_fu540,
+ .num_clks = ARRAY_SIZE(__prci_init_clocks_fu540),
+};
+
+/*
+ * 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->va + offs);
+}
+
+static void __prci_writel(u32 v, u32 offs, struct __prci_data *pd)
+{
+ writel(v, pd->va + offs);
+}
+
+/* WRPLL-related private functions */
+
+/**
+ * __prci_wrpll_unpack() - unpack WRPLL configuration registers into parameters
+ * @c: ptr to a struct wrpll_cfg record to write config into
+ * @r: value read from the PRCI PLL configuration register
+ *
+ * Given a value @r read from an FU740 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 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);
+
+ /* external feedback mode not supported */
+ c->flags |= WRPLL_FLAGS_INT_FEEDBACK_MASK;
+}
+
+/**
+ * __prci_wrpll_pack() - pack PLL configuration parameters into a register value
+ * @c: pointer to a struct 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(const struct 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;
+
+ /* external feedback mode not supported */
+ r |= PRCI_COREPLLCFG0_FSE_MASK;
+
+ return r;
+}
+
+/**
+ * __prci_wrpll_read_cfg0() - 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_cfg0(struct __prci_data *pd,
+ struct __prci_wrpll_data *pwd)
+{
+ __prci_wrpll_unpack(&pwd->c, __prci_readl(pd, pwd->cfg0_offs));
+}
+
+/**
+ * __prci_wrpll_write_cfg0() - 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_cfg0(struct __prci_data *pd,
+ struct __prci_wrpll_data *pwd,
+ struct wrpll_cfg *c)
+{
+ __prci_writel(__prci_wrpll_pack(c), pwd->cfg0_offs, pd);
+
+ memcpy(&pwd->c, c, sizeof(*c));
+}
+
+/**
+ * __prci_wrpll_write_cfg1() - write Clock enable/disable configuration
+ * into the PRCI
+ * @pd: PRCI context
+ * @pwd: PRCI WRPLL metadata
+ * @enable: Clock enable or disable value
+ */
+static void __prci_wrpll_write_cfg1(struct __prci_data *pd,
+ struct __prci_wrpll_data *pwd,
+ u32 enable)
+{
+ __prci_writel(enable, pwd->cfg1_offs, pd);
+}
+
+/*
+ * Linux clock framework integration
+ *
+ * See the Linux clock framework documentation for more information on
+ * these functions.
+ */
+
+unsigned long sifive_prci_wrpll_recalc_rate(struct clk *hw,
+ unsigned long parent_rate)
+{
+ struct __prci_clock *pc = clk_hw_to_prci_clock(hw);
+ struct __prci_wrpll_data *pwd = pc->pwd;
+
+ return wrpll_calc_output_rate(&pwd->c, parent_rate);
+}
+
+long sifive_prci_wrpll_round_rate(struct clk *hw,
+ unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct __prci_clock *pc = clk_hw_to_prci_clock(hw);
+ struct __prci_wrpll_data *pwd = pc->pwd;
+ struct wrpll_cfg c;
+
+ memcpy(&c, &pwd->c, sizeof(c));
+
+ wrpll_configure_for_rate(&c, rate, *parent_rate);
+
+ return wrpll_calc_output_rate(&c, *parent_rate);
+}
+
+int sifive_prci_wrpll_set_rate(struct clk *hw,
+ unsigned long rate, unsigned long parent_rate)
+{
+ struct __prci_clock *pc = clk_hw_to_prci_clock(hw);
+ struct __prci_wrpll_data *pwd = pc->pwd;
+ struct __prci_data *pd = pc->pd;
+ int r;
+
+ r = wrpll_configure_for_rate(&pwd->c, rate, parent_rate);
+ if (r)
+ return r;
+
+ if (pwd->enable_bypass)
+ pwd->enable_bypass(pd);
+
+ __prci_wrpll_write_cfg0(pd, pwd, &pwd->c);
+
+ udelay(wrpll_calc_max_lock_us(&pwd->c));
+
+ return 0;
+}
+
+int sifive_clk_is_enabled(struct clk *hw)
+{
+ struct __prci_clock *pc = clk_hw_to_prci_clock(hw);
+ struct __prci_wrpll_data *pwd = pc->pwd;
+ struct __prci_data *pd = pc->pd;
+ u32 r;
+
+ r = __prci_readl(pd, pwd->cfg1_offs);
+
+ if (r & PRCI_COREPLLCFG1_CKE_MASK)
+ return 1;
+ else
+ return 0;
+}
+
+int sifive_prci_clock_enable(struct clk *hw)
+{
+ struct __prci_clock *pc = clk_hw_to_prci_clock(hw);
+ struct __prci_wrpll_data *pwd = pc->pwd;
+ struct __prci_data *pd = pc->pd;
+
+ if (sifive_clk_is_enabled(hw))
+ return 0;
+
+ __prci_wrpll_write_cfg1(pd, pwd, PRCI_COREPLLCFG1_CKE_MASK);
+
+ if (pwd->disable_bypass)
+ pwd->disable_bypass(pd);
+
+ return 0;
+}
+
+void sifive_prci_clock_disable(struct clk *hw)
+{
+ struct __prci_clock *pc = clk_hw_to_prci_clock(hw);
+ struct __prci_wrpll_data *pwd = pc->pwd;
+ struct __prci_data *pd = pc->pd;
+ u32 r;
+
+ if (pwd->enable_bypass)
+ pwd->enable_bypass(pd);
+
+ r = __prci_readl(pd, pwd->cfg1_offs);
+ r &= ~PRCI_COREPLLCFG1_CKE_MASK;
+
+ __prci_wrpll_write_cfg1(pd, pwd, r);
+}
+
+/* TLCLKSEL clock integration */
+
+unsigned long sifive_prci_tlclksel_recalc_rate(struct clk *hw,
+ unsigned long parent_rate)
+{
+ struct __prci_clock *pc = clk_hw_to_prci_clock(hw);
+ 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);
+}
+
+/* HFPCLK clock integration */
+
+unsigned long sifive_prci_hfpclkplldiv_recalc_rate(struct clk *hw,
+ unsigned long parent_rate)
+{
+ struct __prci_clock *pc = clk_hw_to_prci_clock(hw);
+ struct __prci_data *pd = pc->pd;
+ u32 div = __prci_readl(pd, PRCI_HFPCLKPLLDIV_OFFSET);
+
+ return div_u64(parent_rate, div + 2);
+}
+
+/*
+ * Core clock mux control
+ */
+
+/**
+ * sifive_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.
+ */
+void sifive_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 */
+}
+
+/**
+ * sifive_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 COREPLL output clock; return once complete.
+ *
+ * Context: Any context. Caller must prevent concurrent changes to the
+ * PRCI_CORECLKSEL_OFFSET register.
+ */
+void sifive_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 */
+}
+
+/**
+ * sifive_prci_coreclksel_use_final_corepll() - switch the CORECLK mux to output
+ * FINAL_COREPLL
+ * @pd: struct __prci_data * for the PRCI containing the CORECLK mux reg
+ *
+ * Switch the CORECLK mux to the final COREPLL output clock; return once
+ * complete.
+ *
+ * Context: Any context. Caller must prevent concurrent changes to the
+ * PRCI_CORECLKSEL_OFFSET register.
+ */
+void sifive_prci_coreclksel_use_final_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 */
+}
+
+/**
+ * sifive_prci_corepllsel_use_dvfscorepll() - switch the COREPLL mux to
+ * output DVFS_COREPLL
+ * @pd: struct __prci_data * for the PRCI containing the COREPLL mux reg
+ *
+ * Switch the COREPLL mux to the DVFSCOREPLL output clock; return once complete.
+ *
+ * Context: Any context. Caller must prevent concurrent changes to the
+ * PRCI_COREPLLSEL_OFFSET register.
+ */
+void sifive_prci_corepllsel_use_dvfscorepll(struct __prci_data *pd)
+{
+ u32 r;
+
+ r = __prci_readl(pd, PRCI_COREPLLSEL_OFFSET);
+ r |= PRCI_COREPLLSEL_COREPLLSEL_MASK;
+ __prci_writel(r, PRCI_COREPLLSEL_OFFSET, pd);
+
+ r = __prci_readl(pd, PRCI_COREPLLSEL_OFFSET); /* barrier */
+}
+
+/**
+ * sifive_prci_corepllsel_use_corepll() - switch the COREPLL mux to
+ * output COREPLL
+ * @pd: struct __prci_data * for the PRCI containing the COREPLL mux reg
+ *
+ * Switch the COREPLL mux to the COREPLL output clock; return once complete.
+ *
+ * Context: Any context. Caller must prevent concurrent changes to the
+ * PRCI_COREPLLSEL_OFFSET register.
+ */
+void sifive_prci_corepllsel_use_corepll(struct __prci_data *pd)
+{
+ u32 r;
+
+ r = __prci_readl(pd, PRCI_COREPLLSEL_OFFSET);
+ r &= ~PRCI_COREPLLSEL_COREPLLSEL_MASK;
+ __prci_writel(r, PRCI_COREPLLSEL_OFFSET, pd);
+
+ r = __prci_readl(pd, PRCI_COREPLLSEL_OFFSET); /* barrier */
+}
+
+/**
+ * sifive_prci_hfpclkpllsel_use_hfclk() - switch the HFPCLKPLL mux to
+ * output HFCLK
+ * @pd: struct __prci_data * for the PRCI containing the HFPCLKPLL mux reg
+ *
+ * Switch the HFPCLKPLL mux to the HFCLK input source; return once complete.
+ *
+ * Context: Any context. Caller must prevent concurrent changes to the
+ * PRCI_HFPCLKPLLSEL_OFFSET register.
+ */
+void sifive_prci_hfpclkpllsel_use_hfclk(struct __prci_data *pd)
+{
+ u32 r;
+
+ r = __prci_readl(pd, PRCI_HFPCLKPLLSEL_OFFSET);
+ r |= PRCI_HFPCLKPLLSEL_HFPCLKPLLSEL_MASK;
+ __prci_writel(r, PRCI_HFPCLKPLLSEL_OFFSET, pd);
+
+ r = __prci_readl(pd, PRCI_HFPCLKPLLSEL_OFFSET); /* barrier */
+}
+
+/**
+ * sifive_prci_hfpclkpllsel_use_hfpclkpll() - switch the HFPCLKPLL mux to
+ * output HFPCLKPLL
+ * @pd: struct __prci_data * for the PRCI containing the HFPCLKPLL mux reg
+ *
+ * Switch the HFPCLKPLL mux to the HFPCLKPLL output clock; return once complete.
+ *
+ * Context: Any context. Caller must prevent concurrent changes to the
+ * PRCI_HFPCLKPLLSEL_OFFSET register.
+ */
+void sifive_prci_hfpclkpllsel_use_hfpclkpll(struct __prci_data *pd)
+{
+ u32 r;
+
+ r = __prci_readl(pd, PRCI_HFPCLKPLLSEL_OFFSET);
+ r &= ~PRCI_HFPCLKPLLSEL_HFPCLKPLLSEL_MASK;
+ __prci_writel(r, PRCI_HFPCLKPLLSEL_OFFSET, pd);
+
+ r = __prci_readl(pd, PRCI_HFPCLKPLLSEL_OFFSET); /* barrier */
+}
+
+/**
+ * __prci_register_clocks() - register clock controls in the PRCI
+ * @dev: Linux struct device
+ * @pd: The pointer for PRCI per-device instance data
+ * @desc: The pointer for the information of clocks of each SoCs
+ *
+ * Register the list of clock controls described in __prci_init_clocks[] with
+ * the Linux clock framework.
+ *
+ * Return: 0 upon success or a negative error code upon failure.
+ */
+static int __prci_register_clocks(struct device_d *dev, struct __prci_data *pd,
+ const struct prci_clk_desc *desc)
+{
+ struct __prci_clock *pic;
+ int parent_count, i, r;
+
+ parent_count = of_clk_get_parent_count(dev->device_node);
+ if (parent_count != EXPECTED_CLK_PARENT_COUNT) {
+ dev_err(dev, "expected only two parent clocks, found %d\n",
+ parent_count);
+ return -EINVAL;
+ }
+
+ /* Register PLLs */
+ for (i = 0; i < desc->num_clks; ++i) {
+ pic = &(desc->clks[i]);
+
+ pic->hw.name = pic->name;
+ pic->hw.parent_names = &pic->parent_name;
+ pic->hw.num_parents = 1;
+ pic->hw.ops = pic->ops;
+
+ pic->pd = pd;
+
+ if (pic->pwd)
+ __prci_wrpll_read_cfg0(pd, pic->pwd);
+
+ r = clk_register(&pic->hw);
+ if (r) {
+ dev_warn(dev, "Failed to register clock %s: %d\n",
+ pic->hw.name, r);
+ return r;
+ }
+
+ r = clk_register_clkdev(&pic->hw, pic->name, dev_name(dev));
+ if (r) {
+ dev_warn(dev, "Failed to register clkdev for %s: %d\n",
+ pic->hw.name, r);
+ return r;
+ }
+
+ pd->hw_clks.clks[i] = &pic->hw;
+ }
+
+ pd->hw_clks.clk_num = i;
+
+ r = of_clk_add_provider(dev->device_node, of_clk_src_onecell_get,
+ &pd->hw_clks);
+ if (r) {
+ dev_err(dev, "could not add hw_provider: %d\n", r);
+ return r;
+ }
+
+ return 0;
+}
+
+/**
+ * sifive_prci_init() - initialize prci data and check parent count
+ * @dev: platform device pointer for the prci
+ *
+ * Return: 0 upon success or a negative error code upon failure.
+ */
+static int sifive_prci_probe(struct device_d *dev)
+{
+ struct resource *res;
+ struct __prci_data *pd;
+ const struct prci_clk_desc *desc;
+ int r;
+
+ desc = device_get_match_data(dev);
+
+ pd = malloc(sizeof(*pd));
+ if (!pd)
+ return -ENOMEM;
+
+ pd->hw_clks.clk_num = desc->num_clks;
+ pd->hw_clks.clks = calloc(pd->hw_clks.clk_num, sizeof(*pd->hw_clks.clks));
+ if (!pd->hw_clks.clks)
+ return -ENOMEM;
+
+ res = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(res))
+ return PTR_ERR(res);
+
+ pd->va = IOMEM(res->start);
+
+ r = __prci_register_clocks(dev, pd, desc);
+ if (r) {
+ dev_err(dev, "could not register clocks: %d\n", r);
+ return r;
+ }
+
+ dev_dbg(dev, "SiFive PRCI probed\n");
+
+ return 0;
+}
+
+static const struct of_device_id sifive_prci_of_match[] = {
+ {.compatible = "sifive,fu540-c000-prci", .data = &prci_clk_fu540},
+ {.compatible = "sifive,fu740-c000-prci", .data = &prci_clk_fu740},
+ {}
+};
+
+static struct driver_d sifive_prci_driver = {
+ .name = "sifive-clk-prci",
+ .of_compatible = sifive_prci_of_match,
+ .probe = sifive_prci_probe,
+};
+core_platform_driver(sifive_prci_driver);
diff --git a/drivers/clk/sifive/sifive-prci.h b/drivers/clk/sifive/sifive-prci.h
new file mode 100644
index 0000000000..d851553818
--- /dev/null
+++ b/drivers/clk/sifive/sifive-prci.h
@@ -0,0 +1,298 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018-2019 SiFive, Inc.
+ * Wesley Terpstra
+ * Paul Walmsley
+ * Zong Li
+ */
+
+#ifndef __SIFIVE_CLK_SIFIVE_PRCI_H
+#define __SIFIVE_CLK_SIFIVE_PRCI_H
+
+#include <linux/clk/analogbits-wrpll-cln28hpc.h>
+#include <linux/clk.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)
+
+/* COREPLLCFG1 */
+#define PRCI_COREPLLCFG1_OFFSET 0x8
+#define PRCI_COREPLLCFG1_CKE_SHIFT 31
+#define PRCI_COREPLLCFG1_CKE_MASK (0x1 << PRCI_COREPLLCFG1_CKE_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 31
+#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 31
+#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)
+#define PRCI_DEVICESRESETREG_CHIPLINK_RST_N_SHIFT 6
+#define PRCI_DEVICESRESETREG_CHIPLINK_RST_N_MASK \
+ (0x1 << PRCI_DEVICESRESETREG_CHIPLINK_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)
+
+/* CLTXPLLCFG0 */
+#define PRCI_CLTXPLLCFG0_OFFSET 0x30
+#define PRCI_CLTXPLLCFG0_DIVR_SHIFT 0
+#define PRCI_CLTXPLLCFG0_DIVR_MASK (0x3f << PRCI_CLTXPLLCFG0_DIVR_SHIFT)
+#define PRCI_CLTXPLLCFG0_DIVF_SHIFT 6
+#define PRCI_CLTXPLLCFG0_DIVF_MASK (0x1ff << PRCI_CLTXPLLCFG0_DIVF_SHIFT)
+#define PRCI_CLTXPLLCFG0_DIVQ_SHIFT 15
+#define PRCI_CLTXPLLCFG0_DIVQ_MASK (0x7 << PRCI_CLTXPLLCFG0_DIVQ_SHIFT)
+#define PRCI_CLTXPLLCFG0_RANGE_SHIFT 18
+#define PRCI_CLTXPLLCFG0_RANGE_MASK (0x7 << PRCI_CLTXPLLCFG0_RANGE_SHIFT)
+#define PRCI_CLTXPLLCFG0_BYPASS_SHIFT 24
+#define PRCI_CLTXPLLCFG0_BYPASS_MASK (0x1 << PRCI_CLTXPLLCFG0_BYPASS_SHIFT)
+#define PRCI_CLTXPLLCFG0_FSE_SHIFT 25
+#define PRCI_CLTXPLLCFG0_FSE_MASK (0x1 << PRCI_CLTXPLLCFG0_FSE_SHIFT)
+#define PRCI_CLTXPLLCFG0_LOCK_SHIFT 31
+#define PRCI_CLTXPLLCFG0_LOCK_MASK (0x1 << PRCI_CLTXPLLCFG0_LOCK_SHIFT)
+
+/* CLTXPLLCFG1 */
+#define PRCI_CLTXPLLCFG1_OFFSET 0x34
+#define PRCI_CLTXPLLCFG1_CKE_SHIFT 31
+#define PRCI_CLTXPLLCFG1_CKE_MASK (0x1 << PRCI_CLTXPLLCFG1_CKE_SHIFT)
+
+/* DVFSCOREPLLCFG0 */
+#define PRCI_DVFSCOREPLLCFG0_OFFSET 0x38
+
+/* DVFSCOREPLLCFG1 */
+#define PRCI_DVFSCOREPLLCFG1_OFFSET 0x3c
+#define PRCI_DVFSCOREPLLCFG1_CKE_SHIFT 31
+#define PRCI_DVFSCOREPLLCFG1_CKE_MASK (0x1 << PRCI_DVFSCOREPLLCFG1_CKE_SHIFT)
+
+/* COREPLLSEL */
+#define PRCI_COREPLLSEL_OFFSET 0x40
+#define PRCI_COREPLLSEL_COREPLLSEL_SHIFT 0
+#define PRCI_COREPLLSEL_COREPLLSEL_MASK \
+ (0x1 << PRCI_COREPLLSEL_COREPLLSEL_SHIFT)
+
+/* HFPCLKPLLCFG0 */
+#define PRCI_HFPCLKPLLCFG0_OFFSET 0x50
+#define PRCI_HFPCLKPLL_CFG0_DIVR_SHIFT 0
+#define PRCI_HFPCLKPLL_CFG0_DIVR_MASK \
+ (0x3f << PRCI_HFPCLKPLLCFG0_DIVR_SHIFT)
+#define PRCI_HFPCLKPLL_CFG0_DIVF_SHIFT 6
+#define PRCI_HFPCLKPLL_CFG0_DIVF_MASK \
+ (0x1ff << PRCI_HFPCLKPLLCFG0_DIVF_SHIFT)
+#define PRCI_HFPCLKPLL_CFG0_DIVQ_SHIFT 15
+#define PRCI_HFPCLKPLL_CFG0_DIVQ_MASK \
+ (0x7 << PRCI_HFPCLKPLLCFG0_DIVQ_SHIFT)
+#define PRCI_HFPCLKPLL_CFG0_RANGE_SHIFT 18
+#define PRCI_HFPCLKPLL_CFG0_RANGE_MASK \
+ (0x7 << PRCI_HFPCLKPLLCFG0_RANGE_SHIFT)
+#define PRCI_HFPCLKPLL_CFG0_BYPASS_SHIFT 24
+#define PRCI_HFPCLKPLL_CFG0_BYPASS_MASK \
+ (0x1 << PRCI_HFPCLKPLLCFG0_BYPASS_SHIFT)
+#define PRCI_HFPCLKPLL_CFG0_FSE_SHIFT 25
+#define PRCI_HFPCLKPLL_CFG0_FSE_MASK \
+ (0x1 << PRCI_HFPCLKPLLCFG0_FSE_SHIFT)
+#define PRCI_HFPCLKPLL_CFG0_LOCK_SHIFT 31
+#define PRCI_HFPCLKPLL_CFG0_LOCK_MASK \
+ (0x1 << PRCI_HFPCLKPLLCFG0_LOCK_SHIFT)
+
+/* HFPCLKPLLCFG1 */
+#define PRCI_HFPCLKPLLCFG1_OFFSET 0x54
+#define PRCI_HFPCLKPLLCFG1_CKE_SHIFT 31
+#define PRCI_HFPCLKPLLCFG1_CKE_MASK \
+ (0x1 << PRCI_HFPCLKPLLCFG1_CKE_SHIFT)
+
+/* HFPCLKPLLSEL */
+#define PRCI_HFPCLKPLLSEL_OFFSET 0x58
+#define PRCI_HFPCLKPLLSEL_HFPCLKPLLSEL_SHIFT 0
+#define PRCI_HFPCLKPLLSEL_HFPCLKPLLSEL_MASK \
+ (0x1 << PRCI_HFPCLKPLLSEL_HFPCLKPLLSEL_SHIFT)
+
+/* HFPCLKPLLDIV */
+#define PRCI_HFPCLKPLLDIV_OFFSET 0x5c
+
+/* PRCIPLL */
+#define PRCI_PRCIPLL_OFFSET 0xe0
+
+/* PROCMONCFG */
+#define PRCI_PROCMONCFG_OFFSET 0xf0
+
+/*
+ * Private structures
+ */
+
+/**
+ * struct __prci_data - per-device-instance data
+ * @va: base virtual address of the PRCI IP block
+ * @hw_clks: encapsulates struct clk_hw records
+ *
+ * PRCI per-device instance data
+ */
+struct __prci_data {
+ void __iomem *va;
+ struct clk_onecell_data hw_clks;
+};
+
+/**
+ * struct __prci_wrpll_data - WRPLL configuration and integration data
+ * @c: WRPLL current configuration record
+ * @enable_bypass: fn ptr to code to bypass the WRPLL (if applicable; else NULL)
+ * @disable_bypass: fn ptr to code to not bypass the WRPLL (or NULL)
+ * @cfg0_offs: WRPLL CFG0 register offset (in bytes) from the PRCI base address
+ * @cfg1_offs: WRPLL CFG1 register offset (in bytes) from the PRCI base address
+ *
+ * @enable_bypass and @disable_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 wrpll_cfg c;
+ void (*enable_bypass)(struct __prci_data *pd);
+ void (*disable_bypass)(struct __prci_data *pd);
+ u8 cfg0_offs;
+ u8 cfg1_offs;
+};
+
+/**
+ * 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 clk_ops *ops;
+ struct clk hw;
+ struct __prci_wrpll_data *pwd;
+ struct __prci_data *pd;
+};
+
+#define clk_hw_to_prci_clock(pwd) container_of(pwd, struct __prci_clock, hw)
+
+/*
+ * struct prci_clk_desc - describes the information of clocks of each SoCs
+ * @clks: point to a array of __prci_clock
+ * @num_clks: the number of element of clks
+ */
+struct prci_clk_desc {
+ struct __prci_clock *clks;
+ size_t num_clks;
+};
+
+/* Core clock mux control */
+void sifive_prci_coreclksel_use_hfclk(struct __prci_data *pd);
+void sifive_prci_coreclksel_use_corepll(struct __prci_data *pd);
+void sifive_prci_coreclksel_use_final_corepll(struct __prci_data *pd);
+void sifive_prci_corepllsel_use_dvfscorepll(struct __prci_data *pd);
+void sifive_prci_corepllsel_use_corepll(struct __prci_data *pd);
+void sifive_prci_hfpclkpllsel_use_hfclk(struct __prci_data *pd);
+void sifive_prci_hfpclkpllsel_use_hfpclkpll(struct __prci_data *pd);
+
+/* Linux clock framework integration */
+long sifive_prci_wrpll_round_rate(struct clk *hw, unsigned long rate,
+ unsigned long *parent_rate);
+int sifive_prci_wrpll_set_rate(struct clk *hw, unsigned long rate,
+ unsigned long parent_rate);
+int sifive_clk_is_enabled(struct clk *hw);
+int sifive_prci_clock_enable(struct clk *hw);
+void sifive_prci_clock_disable(struct clk *hw);
+unsigned long sifive_prci_wrpll_recalc_rate(struct clk *hw,
+ unsigned long parent_rate);
+unsigned long sifive_prci_tlclksel_recalc_rate(struct clk *hw,
+ unsigned long parent_rate);
+unsigned long sifive_prci_hfpclkplldiv_recalc_rate(struct clk *hw,
+ unsigned long parent_rate);
+
+#endif /* __SIFIVE_CLK_SIFIVE_PRCI_H */
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 261b6e6662..a8ee9e58b8 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -164,6 +164,13 @@ config GPIO_SX150X
Say Y here to build support for the Semtec Sx150x I2C GPIO
expander chip.
+config GPIO_SIFIVE
+ bool "SiFive GPIO support"
+ depends on OF_GPIO
+ select GPIO_GENERIC
+ help
+ Say yes here to support the GPIO device on SiFive SoCs.
+
config GPIO_LIBFTDI1
bool "libftdi1 driver"
depends on SANDBOX
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 77dcf58f64..25e12105d8 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -23,3 +23,4 @@ obj-$(CONFIG_GPIO_DESIGNWARE) += gpio-dw.o
obj-$(CONFIG_GPIO_SX150X) += gpio-sx150x.o
obj-$(CONFIG_GPIO_VF610) += gpio-vf610.o
obj-$(CONFIG_GPIO_RASPBERRYPI_EXP) += gpio-raspberrypi-exp.o
+obj-$(CONFIG_GPIO_SIFIVE) += gpio-sifive.o
diff --git a/drivers/gpio/gpio-generic.c b/drivers/gpio/gpio-generic.c
index 8259b799f9..713085267a 100644
--- a/drivers/gpio/gpio-generic.c
+++ b/drivers/gpio/gpio-generic.c
@@ -7,63 +7,117 @@
*/
#include <init.h>
-#include <malloc.h>
#include <linux/err.h>
+#include <linux/bug.h>
+#include <linux/kernel.h>
+#include <linux/compiler.h>
+#include <linux/types.h>
+#include <errno.h>
#include <linux/log2.h>
-#include <linux/err.h>
+#include <linux/ioport.h>
+#include <io.h>
#include <linux/basic_mmio_gpio.h>
+#include <linux/slab.h>
+#include <linux/bitops.h>
+#include <driver.h>
+#include <of.h>
+#include <of_device.h>
-static void bgpio_write8(void __iomem *reg, unsigned int data)
+static void bgpio_write8(void __iomem *reg, unsigned long data)
{
writeb(data, reg);
}
-static unsigned int bgpio_read8(void __iomem *reg)
+static unsigned long bgpio_read8(void __iomem *reg)
{
return readb(reg);
}
-static void bgpio_write16(void __iomem *reg, unsigned int data)
+static void bgpio_write16(void __iomem *reg, unsigned long data)
{
writew(data, reg);
}
-static unsigned int bgpio_read16(void __iomem *reg)
+static unsigned long bgpio_read16(void __iomem *reg)
{
return readw(reg);
}
-static void bgpio_write32(void __iomem *reg, unsigned int data)
+static void bgpio_write32(void __iomem *reg, unsigned long data)
{
writel(data, reg);
}
-static unsigned int bgpio_read32(void __iomem *reg)
+static unsigned long bgpio_read32(void __iomem *reg)
{
return readl(reg);
}
-static unsigned int bgpio_pin2mask(struct bgpio_chip *bgc, unsigned int pin)
+#if BITS_PER_LONG >= 64
+static void bgpio_write64(void __iomem *reg, unsigned long data)
+{
+ writeq(data, reg);
+}
+
+static unsigned long bgpio_read64(void __iomem *reg)
+{
+ return readq(reg);
+}
+#endif /* BITS_PER_LONG >= 64 */
+
+static void bgpio_write16be(void __iomem *reg, unsigned long data)
+{
+ iowrite16be(data, reg);
+}
+
+static unsigned long bgpio_read16be(void __iomem *reg)
+{
+ return ioread16be(reg);
+}
+
+static void bgpio_write32be(void __iomem *reg, unsigned long data)
{
- return 1 << pin;
+ iowrite32be(data, reg);
}
-static unsigned int bgpio_pin2mask_be(struct bgpio_chip *bgc, unsigned int pin)
+static unsigned long bgpio_read32be(void __iomem *reg)
{
- return 1 << (bgc->bits - 1 - pin);
+ return ioread32be(reg);
+}
+
+static unsigned long bgpio_line2mask(struct bgpio_chip *bgc, unsigned int line)
+{
+ if (bgc->be_bits)
+ return BIT(bgc->bits - 1 - line);
+ return BIT(line);
+}
+
+static int bgpio_get_set(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct bgpio_chip *bgc = to_bgpio_chip(gc);
+ unsigned long pinmask = bgpio_line2mask(bgc, gpio);
+ bool dir = !!(bgc->dir & pinmask);
+
+ if (dir)
+ return !!(bgc->read_reg(bgc->reg_set) & pinmask);
+ else
+ return !!(bgc->read_reg(bgc->reg_dat) & pinmask);
}
static int bgpio_get(struct gpio_chip *gc, unsigned int gpio)
{
struct bgpio_chip *bgc = to_bgpio_chip(gc);
+ return !!(bgc->read_reg(bgc->reg_dat) & bgpio_line2mask(bgc, gpio));
+}
- return !!(bgc->read_reg(bgc->reg_dat) & bgc->pin2mask(bgc, gpio));
+static void bgpio_set_none(struct gpio_chip *gc, unsigned int gpio, int val)
+{
}
static void bgpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
{
struct bgpio_chip *bgc = to_bgpio_chip(gc);
- unsigned int mask = bgc->pin2mask(bgc, gpio);
+ unsigned long mask = bgpio_line2mask(bgc, gpio);
if (val)
bgc->data |= mask;
@@ -77,7 +131,7 @@ static void bgpio_set_with_clear(struct gpio_chip *gc, unsigned int gpio,
int val)
{
struct bgpio_chip *bgc = to_bgpio_chip(gc);
- unsigned int mask = bgc->pin2mask(bgc, gpio);
+ unsigned long mask = bgpio_line2mask(bgc, gpio);
if (val)
bgc->write_reg(bgc->reg_set, mask);
@@ -88,7 +142,7 @@ static void bgpio_set_with_clear(struct gpio_chip *gc, unsigned int gpio,
static void bgpio_set_set(struct gpio_chip *gc, unsigned int gpio, int val)
{
struct bgpio_chip *bgc = to_bgpio_chip(gc);
- unsigned int mask = bgc->pin2mask(bgc, gpio);
+ unsigned long mask = bgpio_line2mask(bgc, gpio);
if (val)
bgc->data |= mask;
@@ -103,6 +157,12 @@ static int bgpio_simple_dir_in(struct gpio_chip *gc, unsigned int gpio)
return 0;
}
+static int bgpio_dir_out_err(struct gpio_chip *gc, unsigned int gpio,
+ int val)
+{
+ return -EINVAL;
+}
+
static int bgpio_simple_dir_out(struct gpio_chip *gc, unsigned int gpio,
int val)
{
@@ -115,69 +175,115 @@ static int bgpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
{
struct bgpio_chip *bgc = to_bgpio_chip(gc);
- bgc->dir &= ~bgc->pin2mask(bgc, gpio);
- bgc->write_reg(bgc->reg_dir, bgc->dir);
+ bgc->dir &= ~bgpio_line2mask(bgc, gpio);
+
+ if (bgc->reg_dir_in)
+ bgc->write_reg(bgc->reg_dir_in, ~bgc->dir);
+ if (bgc->reg_dir_out)
+ bgc->write_reg(bgc->reg_dir_out, bgc->dir);
return 0;
}
-static int bgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
+static int bgpio_get_dir(struct gpio_chip *gc, unsigned int gpio)
{
struct bgpio_chip *bgc = to_bgpio_chip(gc);
- gc->ops->set(gc, gpio, val);
+ /* Return 0 if output, 1 if input */
+ if (bgc->dir_unreadable) {
+ if (bgc->dir & bgpio_line2mask(bgc, gpio))
+ return GPIOF_DIR_OUT;
+ return GPIOF_DIR_IN;
+ }
- bgc->dir |= bgc->pin2mask(bgc, gpio);
- bgc->write_reg(bgc->reg_dir, bgc->dir);
+ if (bgc->reg_dir_out) {
+ if (bgc->read_reg(bgc->reg_dir_out) & bgpio_line2mask(bgc, gpio))
+ return GPIOF_DIR_OUT;
+ return GPIOF_DIR_IN;
+ }
- return 0;
+ if (bgc->reg_dir_in)
+ if (!(bgc->read_reg(bgc->reg_dir_in) & bgpio_line2mask(bgc, gpio)))
+ return GPIOF_DIR_OUT;
+
+ return GPIOF_DIR_IN;
}
-static int bgpio_dir_in_inv(struct gpio_chip *gc, unsigned int gpio)
+static void bgpio_dir_out(struct bgpio_chip *bgc, unsigned int gpio, int val)
{
- struct bgpio_chip *bgc = to_bgpio_chip(gc);
-
- bgc->dir |= bgc->pin2mask(bgc, gpio);
- bgc->write_reg(bgc->reg_dir, bgc->dir);
+ bgc->dir |= bgpio_line2mask(bgc, gpio);
- return 0;
+ if (bgc->reg_dir_in)
+ bgc->write_reg(bgc->reg_dir_in, ~bgc->dir);
+ if (bgc->reg_dir_out)
+ bgc->write_reg(bgc->reg_dir_out, bgc->dir);
}
-static int bgpio_dir_out_inv(struct gpio_chip *gc, unsigned int gpio, int val)
+static int bgpio_dir_out_dir_first(struct gpio_chip *gc, unsigned int gpio,
+ int val)
{
struct bgpio_chip *bgc = to_bgpio_chip(gc);
+ bgpio_dir_out(bgc, gpio, val);
gc->ops->set(gc, gpio, val);
+ return 0;
+}
- bgc->dir &= ~bgc->pin2mask(bgc, gpio);
- bgc->write_reg(bgc->reg_dir, bgc->dir);
+static int bgpio_dir_out_val_first(struct gpio_chip *gc, unsigned int gpio,
+ int val)
+{
+ struct bgpio_chip *bgc = to_bgpio_chip(gc);
+ gc->ops->set(gc, gpio, val);
+ bgpio_dir_out(bgc, gpio, val);
return 0;
}
-static int bgpio_setup_accessors(struct device_d *dev, struct bgpio_chip *bgc,
- bool be)
+static int bgpio_setup_accessors(struct device_d *dev,
+ struct bgpio_chip *bgc,
+ bool byte_be)
{
+
switch (bgc->bits) {
case 8:
bgc->read_reg = bgpio_read8;
bgc->write_reg = bgpio_write8;
break;
case 16:
- bgc->read_reg = bgpio_read16;
- bgc->write_reg = bgpio_write16;
+ if (byte_be) {
+ bgc->read_reg = bgpio_read16be;
+ bgc->write_reg = bgpio_write16be;
+ } else {
+ bgc->read_reg = bgpio_read16;
+ bgc->write_reg = bgpio_write16;
+ }
break;
case 32:
- bgc->read_reg = bgpio_read32;
- bgc->write_reg = bgpio_write32;
+ if (byte_be) {
+ bgc->read_reg = bgpio_read32be;
+ bgc->write_reg = bgpio_write32be;
+ } else {
+ bgc->read_reg = bgpio_read32;
+ bgc->write_reg = bgpio_write32;
+ }
break;
+#if BITS_PER_LONG >= 64
+ case 64:
+ if (byte_be) {
+ dev_err(dev,
+ "64 bit big endian byte order unsupported\n");
+ return -EINVAL;
+ } else {
+ bgc->read_reg = bgpio_read64;
+ bgc->write_reg = bgpio_write64;
+ }
+ break;
+#endif /* BITS_PER_LONG >= 64 */
default:
- dev_err(dev, "Unsupported data width %u bits\n", bgc->bits);
+ dev_err(dev, "unsupported data width %u bits\n", bgc->bits);
return -EINVAL;
}
- bgc->pin2mask = be ? bgpio_pin2mask_be : bgpio_pin2mask;
-
return 0;
}
@@ -188,7 +294,7 @@ static int bgpio_setup_accessors(struct device_d *dev, struct bgpio_chip *bgc,
* - single input/output register resource (named "dat").
* - set/clear pair (named "set" and "clr").
* - single output register resource and single input resource ("set" and
- * dat").
+ * dat").
*
* For the single output register, this drives a 1 by setting a bit and a zero
* by clearing a bit. For the set clr pair, this drives a 1 by setting a bit
@@ -206,123 +312,211 @@ static int bgpio_setup_accessors(struct device_d *dev, struct bgpio_chip *bgc,
static int bgpio_setup_io(struct bgpio_chip *bgc,
void __iomem *dat,
void __iomem *set,
- void __iomem *clr)
+ void __iomem *clr,
+ unsigned long flags)
{
- if (!dat)
- return -EINVAL;
+ struct gpio_ops *ops = bgc->gc.ops;
bgc->reg_dat = dat;
+ if (!bgc->reg_dat)
+ return -EINVAL;
if (set && clr) {
bgc->reg_set = set;
bgc->reg_clr = clr;
- bgc->gc.ops->set = bgpio_set_with_clear;
+ ops->set = bgpio_set_with_clear;
} else if (set && !clr) {
bgc->reg_set = set;
- bgc->gc.ops->set = bgpio_set_set;
- } else
- bgc->gc.ops->set = bgpio_set;
+ ops->set = bgpio_set_set;
+ } else if (flags & BGPIOF_NO_OUTPUT) {
+ ops->set = bgpio_set_none;
+ } else {
+ ops->set = bgpio_set;
+ }
- bgc->gc.ops->get = bgpio_get;
+ if (!(flags & BGPIOF_UNREADABLE_REG_SET) && (flags & BGPIOF_READ_OUTPUT_REG_SET))
+ ops->get = bgpio_get_set;
+ else
+ ops->get = bgpio_get;
return 0;
}
static int bgpio_setup_direction(struct bgpio_chip *bgc,
void __iomem *dirout,
- void __iomem *dirin)
+ void __iomem *dirin,
+ unsigned long flags)
{
- if (dirout && dirin)
- return -EINVAL;
-
- if (dirout) {
- bgc->reg_dir = dirout;
- bgc->gc.ops->direction_output = bgpio_dir_out;
- bgc->gc.ops->direction_input = bgpio_dir_in;
- } else if (dirin) {
- bgc->reg_dir = dirin;
- bgc->gc.ops->direction_output = bgpio_dir_out_inv;
- bgc->gc.ops->direction_input = bgpio_dir_in_inv;
+ struct gpio_ops *ops = bgc->gc.ops;
+
+ if (dirout || dirin) {
+ bgc->reg_dir_out = dirout;
+ bgc->reg_dir_in = dirin;
+ if (flags & BGPIOF_NO_SET_ON_INPUT)
+ ops->direction_output = bgpio_dir_out_dir_first;
+ else
+ ops->direction_output = bgpio_dir_out_val_first;
+ ops->direction_input = bgpio_dir_in;
+ ops->get_direction = bgpio_get_dir;
} else {
- bgc->gc.ops->direction_output = bgpio_simple_dir_out;
- bgc->gc.ops->direction_input = bgpio_simple_dir_in;
+ if (flags & BGPIOF_NO_OUTPUT)
+ ops->direction_output = bgpio_dir_out_err;
+ else
+ ops->direction_output = bgpio_simple_dir_out;
+ ops->direction_input = bgpio_simple_dir_in;
}
return 0;
}
+static int bgpio_request(struct gpio_chip *chip, unsigned gpio_pin)
+{
+ if (gpio_pin < chip->ngpio)
+ return 0;
+
+ return -EINVAL;
+}
+
+/**
+ * bgpio_init() - Initialize generic GPIO accessor functions
+ * @bgc: the GPIO chip to set up
+ * @dev: the parent device of the new GPIO chip (compulsory)
+ * @sz: the size (width) of the MMIO registers in bytes, typically 1, 2 or 4
+ * @dat: MMIO address for the register to READ the value of the GPIO lines, it
+ * is expected that a 1 in the corresponding bit in this register means the
+ * line is asserted
+ * @set: MMIO address for the register to SET the value of the GPIO lines, it is
+ * expected that we write the line with 1 in this register to drive the GPIO line
+ * high.
+ * @clr: MMIO address for the register to CLEAR the value of the GPIO lines, it is
+ * expected that we write the line with 1 in this register to drive the GPIO line
+ * low. It is allowed to leave this address as NULL, in that case the SET register
+ * will be assumed to also clear the GPIO lines, by actively writing the line
+ * with 0.
+ * @dirout: MMIO address for the register to set the line as OUTPUT. It is assumed
+ * that setting a line to 1 in this register will turn that line into an
+ * output line. Conversely, setting the line to 0 will turn that line into
+ * an input.
+ * @dirin: MMIO address for the register to set this line as INPUT. It is assumed
+ * that setting a line to 1 in this register will turn that line into an
+ * input line. Conversely, setting the line to 0 will turn that line into
+ * an output.
+ * @flags: Different flags that will affect the behaviour of the device, such as
+ * endianness etc.
+ */
int bgpio_init(struct bgpio_chip *bgc, struct device_d *dev,
unsigned int sz, void __iomem *dat, void __iomem *set,
void __iomem *clr, void __iomem *dirout, void __iomem *dirin,
unsigned long flags)
{
+ struct gpio_ops *ops = &bgc->ops;
int ret;
- if ((sz > 4) || !is_power_of_2(sz))
+ if (!is_power_of_2(sz))
return -EINVAL;
bgc->bits = sz * 8;
- bgc->gc.ngpio = bgc->bits;
+ if (bgc->bits > BITS_PER_LONG)
+ return -EINVAL;
+
bgc->gc.base = -1;
+ bgc->gc.ngpio = bgc->bits;
bgc->gc.dev = dev;
- bgc->gc.ops = &bgc->ops;
+ bgc->gc.ops = ops;
+ ops->request = bgpio_request;
+ bgc->be_bits = !!(flags & BGPIOF_BIG_ENDIAN);
- ret = bgpio_setup_io(bgc, dat, set, clr);
+ ret = bgpio_setup_io(bgc, dat, set, clr, flags);
if (ret)
return ret;
- ret = bgpio_setup_accessors(dev, bgc, flags & BGPIOF_BIG_ENDIAN);
+ ret = bgpio_setup_accessors(dev, bgc, flags & BGPIOF_BIG_ENDIAN_BYTE_ORDER);
if (ret)
return ret;
- ret = bgpio_setup_direction(bgc, dirout, dirin);
+ ret = bgpio_setup_direction(bgc, dirout, dirin, flags);
if (ret)
return ret;
bgc->data = bgc->read_reg(bgc->reg_dat);
-
- if (bgc->gc.ops->set == bgpio_set_set && !(flags &
- BGPIOF_UNREADABLE_REG_SET))
+ if (ops->set == bgpio_set_set &&
+ !(flags & BGPIOF_UNREADABLE_REG_SET))
bgc->data = bgc->read_reg(bgc->reg_set);
- if (bgc->reg_dir && !(flags & BGPIOF_UNREADABLE_REG_DIR))
- bgc->dir = bgc->read_reg(bgc->reg_dir);
+ if (flags & BGPIOF_UNREADABLE_REG_DIR)
+ bgc->dir_unreadable = true;
+
+ /*
+ * Inspect hardware to find initial direction setting.
+ */
+ if ((bgc->reg_dir_out || bgc->reg_dir_in) &&
+ !(flags & BGPIOF_UNREADABLE_REG_DIR)) {
+ if (bgc->reg_dir_out)
+ bgc->dir = bgc->read_reg(bgc->reg_dir_out);
+ else if (bgc->reg_dir_in)
+ bgc->dir = ~bgc->read_reg(bgc->reg_dir_in);
+ /*
+ * If we have two direction registers, synchronise
+ * input setting to output setting, the library
+ * can not handle a line being input and output at
+ * the same time.
+ */
+ if (bgc->reg_dir_out && bgc->reg_dir_in)
+ bgc->write_reg(bgc->reg_dir_in, ~bgc->dir);
+ }
return ret;
}
+EXPORT_SYMBOL_GPL(bgpio_init);
void bgpio_remove(struct bgpio_chip *bgc)
{
gpiochip_remove(&bgc->gc);
free(bgc);
}
+EXPORT_SYMBOL_GPL(bgpio_remove);
#ifdef CONFIG_GPIO_GENERIC_PLATFORM
-static void __iomem *bgpio_map(struct device_d *dev, const char *name,
- resource_size_t sane_sz, int *err)
+static void __iomem *bgpio_map(struct device_d *dev,
+ const char *name,
+ resource_size_t sane_sz)
{
struct resource *r;
- struct resource *ret;
+ resource_size_t sz;
- *err = 0;
-
- r = dev_get_resource_by_name(dev, IORESOURCE_MEM, name);
+ r = dev_request_mem_resource_by_name(dev, name);
if (IS_ERR(r))
return NULL;
- if (resource_size(r) != sane_sz) {
- *err = -EINVAL;
- return NULL;
- }
+ sz = resource_size(r);
+ if (sz != sane_sz)
+ return IOMEM_ERR_PTR(-EINVAL);
+
+ return IOMEM(r->start);
+}
+
+static const struct of_device_id bgpio_of_match[];
+
+static struct bgpio_pdata *bgpio_parse_dt(struct device_d *dev,
+ unsigned long *flags)
+{
+ struct bgpio_pdata *pdata;
- ret = request_iomem_region(dev_name(dev), r->start, r->end);
- if (IS_ERR(ret)) {
- *err = PTR_ERR(ret);
+ if (!of_match_device(bgpio_of_match, dev))
return NULL;
- }
- return IOMEM(ret->start);
+ pdata = xzalloc(sizeof(struct bgpio_pdata));
+
+ pdata->base = -1;
+
+ if (of_device_is_big_endian(dev->device_node))
+ *flags |= BGPIOF_BIG_ENDIAN_BYTE_ORDER;
+
+ if (of_property_read_bool(dev->device_node, "no-output"))
+ *flags |= BGPIOF_NO_OUTPUT;
+
+ return pdata;
}
static int bgpio_dev_probe(struct device_d *dev)
@@ -337,35 +531,37 @@ static int bgpio_dev_probe(struct device_d *dev)
unsigned long flags = 0;
int err;
struct bgpio_chip *bgc;
- struct bgpio_pdata *pdata = dev->platform_data;
+ struct bgpio_pdata *pdata;
- r = dev_get_resource_by_name(dev, IORESOURCE_MEM, "dat");
- if (IS_ERR(r))
- return PTR_ERR(r);
+ pdata = bgpio_parse_dt(dev, &flags);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
- sz = resource_size(r);
+ r = dev_request_mem_resource_by_name(dev, "dat");
+ if (!r)
+ return -EINVAL;
- dat = bgpio_map(dev, "dat", sz, &err);
- if (!dat)
- return err ? err : -EINVAL;
+ sz = resource_size(r);
- set = bgpio_map(dev, "set", sz, &err);
- if (err)
- return err;
+ dat = bgpio_map(dev, "dat", sz);
+ if (IS_ERR(dat))
+ return PTR_ERR(dat);
- clr = bgpio_map(dev, "clr", sz, &err);
- if (err)
- return err;
+ set = bgpio_map(dev, "set", sz);
+ if (IS_ERR(set))
+ return PTR_ERR(set);
- dirout = bgpio_map(dev, "dirout", sz, &err);
- if (err)
- return err;
+ clr = bgpio_map(dev, "clr", sz);
+ if (IS_ERR(clr))
+ return PTR_ERR(clr);
- dirin = bgpio_map(dev, "dirin", sz, &err);
- if (err)
- return err;
+ dirout = bgpio_map(dev, "dirout", sz);
+ if (IS_ERR(dirout))
+ return PTR_ERR(dirout);
- dev_get_drvdata(dev, (const void **)&flags);
+ dirin = bgpio_map(dev, "dirin", sz);
+ if (IS_ERR(dirin))
+ return PTR_ERR(dirin);
bgc = xzalloc(sizeof(struct bgpio_chip));
@@ -373,11 +569,11 @@ static int bgpio_dev_probe(struct device_d *dev)
if (err)
return err;
- if (pdata) {
- bgc->gc.base = pdata->base;
- if (pdata->ngpio > 0)
- bgc->gc.ngpio = pdata->ngpio;
- }
+ bgc->gc.base = pdata->base;
+ bgc->gc.dev = dev;
+ bgc->gc.ops = &bgc->ops;
+ if (pdata->ngpio > 0)
+ bgc->gc.ngpio = pdata->ngpio;
dev->priv = bgc;
@@ -391,21 +587,15 @@ static void bgpio_dev_remove(struct device_d *dev)
bgpio_remove(bgc);
}
-static struct platform_device_id bgpio_id_table[] = {
+static const struct of_device_id bgpio_of_match[] = {
{
- .name = "basic-mmio-gpio",
- .driver_data = 0,
+ .compatible = "wd,mbl-gpio",
},
{
- .name = "basic-mmio-gpio-be",
- .driver_data = BGPIOF_BIG_ENDIAN,
+ .compatible = "brcm,bcm6345-gpio"
},
- { }
-};
-
-static struct of_device_id __maybe_unused bgpio_of_match[] = {
{
- .compatible = "wd,mbl-gpio",
+ .compatible = "ni,169445-nand-gpio"
}, {
/* sentinel */
}
@@ -413,8 +603,7 @@ static struct of_device_id __maybe_unused bgpio_of_match[] = {
static struct driver_d bgpio_driver = {
.name = "basic-mmio-gpio",
- .id_table = bgpio_id_table,
- .of_compatible = DRV_OF_COMPAT(bgpio_of_match),
+ .of_compatible = bgpio_of_match,
.probe = bgpio_dev_probe,
.remove = bgpio_dev_remove,
};
diff --git a/drivers/gpio/gpio-mpc8xxx.c b/drivers/gpio/gpio-mpc8xxx.c
index 1ef459684d..d48a8aa7fb 100644
--- a/drivers/gpio/gpio-mpc8xxx.c
+++ b/drivers/gpio/gpio-mpc8xxx.c
@@ -65,7 +65,8 @@ static int mpc8xxx_probe(struct device_d *dev)
ret = bgpio_init(bgc, dev, 4,
mpc8xxx_gc->regs + GPIO_DAT,
NULL, NULL,
- mpc8xxx_gc->regs + GPIO_DIR, NULL, 0);
+ mpc8xxx_gc->regs + GPIO_DIR, NULL,
+ BGPIOF_BIG_ENDIAN);
if (ret)
goto err;
dev_dbg(dev, "GPIO registers are LITTLE endian\n");
@@ -74,7 +75,8 @@ static int mpc8xxx_probe(struct device_d *dev)
mpc8xxx_gc->regs + GPIO_DAT,
NULL, NULL,
mpc8xxx_gc->regs + GPIO_DIR, NULL,
- BGPIOF_BIG_ENDIAN);
+ BGPIOF_BIG_ENDIAN
+ | BGPIOF_BIG_ENDIAN_BYTE_ORDER);
if (ret)
goto err;
dev_dbg(dev, "GPIO registers are BIG endian\n");
diff --git a/drivers/gpio/gpio-sifive.c b/drivers/gpio/gpio-sifive.c
new file mode 100644
index 0000000000..63f2c097e4
--- /dev/null
+++ b/drivers/gpio/gpio-sifive.c
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 SiFive
+ */
+
+#include <linux/basic_mmio_gpio.h>
+#include <printk.h>
+#include <driver.h>
+#include <errno.h>
+
+#define SIFIVE_GPIO_INPUT_VAL 0x00
+#define SIFIVE_GPIO_INPUT_EN 0x04
+#define SIFIVE_GPIO_OUTPUT_EN 0x08
+#define SIFIVE_GPIO_OUTPUT_VAL 0x0C
+#define SIFIVE_GPIO_RISE_IE 0x18
+#define SIFIVE_GPIO_FALL_IE 0x20
+#define SIFIVE_GPIO_HIGH_IE 0x28
+#define SIFIVE_GPIO_LOW_IE 0x30
+
+#define SIFIVE_GPIO_MAX 32
+
+static int __of_irq_count(struct device_node *np)
+{
+ unsigned npins = 0;
+
+ of_get_property(np, "interrupts", &npins);
+
+ return npins / sizeof(__be32);
+}
+
+static int sifive_gpio_probe(struct device_d *dev)
+{
+ struct bgpio_chip *bgc;
+ struct resource *res;
+ void __iomem *base;
+ int ret, ngpio;
+
+ bgc = xzalloc(sizeof(*bgc));
+
+ res = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(res)) {
+ dev_err(dev, "failed to request device memory\n");
+ return PTR_ERR(res);
+ }
+ base = IOMEM(res->start);
+
+ ngpio = __of_irq_count(dev->device_node);
+ if (ngpio > SIFIVE_GPIO_MAX) {
+ dev_err(dev, "Too many GPIO interrupts (max=%d)\n",
+ SIFIVE_GPIO_MAX);
+ return -ENXIO;
+ }
+
+ ret = bgpio_init(bgc, dev, 4,
+ base + SIFIVE_GPIO_INPUT_VAL,
+ base + SIFIVE_GPIO_OUTPUT_VAL,
+ NULL,
+ base + SIFIVE_GPIO_OUTPUT_EN,
+ base + SIFIVE_GPIO_INPUT_EN,
+ 0);
+ if (ret) {
+ dev_err(dev, "unable to init generic GPIO\n");
+ return ret;
+ }
+
+ /* Disable all GPIO interrupts */
+ writel(0, base + SIFIVE_GPIO_RISE_IE);
+ writel(0, base + SIFIVE_GPIO_FALL_IE);
+ writel(0, base + SIFIVE_GPIO_HIGH_IE);
+ writel(0, base + SIFIVE_GPIO_LOW_IE);
+
+ bgc->gc.ngpio = ngpio;
+ return gpiochip_add(&bgc->gc);
+}
+
+static const struct of_device_id sifive_gpio_match[] = {
+ { .compatible = "sifive,gpio0" },
+ { .compatible = "sifive,fu540-c000-gpio" },
+ { },
+};
+
+static struct driver_d sifive_gpio_driver = {
+ .name = "sifive_gpio",
+ .of_compatible = sifive_gpio_match,
+ .probe = sifive_gpio_probe,
+};
+postcore_platform_driver(sifive_gpio_driver);
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index db924efa02..b9750d1774 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -164,4 +164,13 @@ config VIRTIO_CONSOLE
Also serves as a general-purpose serial device for data
transfer between the guest and host.
+
+config SERIAL_SIFIVE
+ tristate "SiFive UART support"
+ depends on OFDEVICE
+ help
+ Select this option if you are building barebox for a device that
+ contains a SiFive UART IP block. This type of UART is present on
+ SiFive FU540 SoCs, among others.
+
endmenu
diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile
index 7ff41cd5c7..5120b17376 100644
--- a/drivers/serial/Makefile
+++ b/drivers/serial/Makefile
@@ -23,3 +23,4 @@ obj-$(CONFIG_DRIVER_SERIAL_EFI_STDIO) += efi-stdio.o
obj-$(CONFIG_DRIVER_SERIAL_DIGIC) += serial_digic.o
obj-$(CONFIG_DRIVER_SERIAL_LPUART) += serial_lpuart.o
obj-$(CONFIG_VIRTIO_CONSOLE) += virtio_console.o
+obj-$(CONFIG_SERIAL_SIFIVE) += serial_sifive.o
diff --git a/drivers/serial/serial_sifive.c b/drivers/serial/serial_sifive.c
new file mode 100644
index 0000000000..45f7c2bc9a
--- /dev/null
+++ b/drivers/serial/serial_sifive.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2018 Anup Patel <anup@brainfault.org>
+ */
+
+#include <common.h>
+#include <driver.h>
+#include <malloc.h>
+#include <io.h>
+#include <of.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+
+#define UART_TXFIFO_FULL 0x80000000
+#define UART_RXFIFO_EMPTY 0x80000000
+#define UART_RXFIFO_DATA 0x000000ff
+#define UART_TXCTRL_TXEN 0x1
+#define UART_RXCTRL_RXEN 0x1
+
+/* IP register */
+#define UART_IP_RXWM 0x2
+
+struct sifive_serial_regs {
+ u32 txfifo;
+ u32 rxfifo;
+ u32 txctrl;
+ u32 rxctrl;
+ u32 ie;
+ u32 ip;
+ u32 div;
+};
+
+struct sifive_serial_priv {
+ unsigned long freq;
+ struct sifive_serial_regs __iomem *regs;
+ struct console_device cdev;
+};
+
+#define to_priv(cdev) container_of(cdev, struct sifive_serial_priv, cdev)
+
+/**
+ * Find minimum divisor divides in_freq to max_target_hz;
+ * Based on uart driver n SiFive FSBL.
+ *
+ * f_baud = f_in / (div + 1) => div = (f_in / f_baud) - 1
+ * The nearest integer solution requires rounding up as to not exceed
+ * max_target_hz.
+ * div = ceil(f_in / f_baud) - 1
+ * = floor((f_in - 1 + f_baud) / f_baud) - 1
+ * This should not overflow as long as (f_in - 1 + f_baud) does not exceed
+ * 2^32 - 1, which is unlikely since we represent frequencies in kHz.
+ */
+static inline unsigned int uart_min_clk_divisor(unsigned long in_freq,
+ unsigned long max_target_hz)
+{
+ unsigned long quotient =
+ (in_freq + max_target_hz - 1) / (max_target_hz);
+ /* Avoid underflow */
+ if (quotient == 0)
+ return 0;
+ else
+ return quotient - 1;
+}
+
+static void sifive_serial_init(struct sifive_serial_regs __iomem *regs)
+{
+ writel(UART_TXCTRL_TXEN, &regs->txctrl);
+ writel(UART_RXCTRL_RXEN, &regs->rxctrl);
+ writel(0, &regs->ie);
+}
+
+static int sifive_serial_setbrg(struct console_device *cdev, int baudrate)
+{
+ struct sifive_serial_priv *priv = to_priv(cdev);
+
+ writel((uart_min_clk_divisor(priv->freq, baudrate)), &priv->regs->div);
+
+ return 0;
+}
+
+static int sifive_serial_getc(struct console_device *cdev)
+{
+ struct sifive_serial_regs __iomem *regs = to_priv(cdev)->regs;
+ u32 ch;
+
+ do {
+ ch = readl(&regs->rxfifo);
+ } while (ch & UART_RXFIFO_EMPTY);
+
+ return ch & UART_RXFIFO_DATA;
+}
+
+static void sifive_serial_putc(struct console_device *cdev, const char ch)
+{
+ struct sifive_serial_regs __iomem *regs = to_priv(cdev)->regs;
+
+ // TODO: how to check for !empty to utilize fifo?
+ while (readl(&regs->txfifo) & UART_TXFIFO_FULL)
+ ;
+
+ writel(ch, &regs->txfifo);
+}
+
+static int sifive_serial_tstc(struct console_device *cdev)
+{
+ struct sifive_serial_regs __iomem *regs = to_priv(cdev)->regs;
+
+ return readl(&regs->ip) & UART_IP_RXWM;
+}
+
+static void sifive_serial_flush(struct console_device *cdev)
+{
+ struct sifive_serial_regs __iomem *regs = to_priv(cdev)->regs;
+
+ while (readl(&regs->txfifo) & UART_TXFIFO_FULL)
+ ;
+}
+
+static int sifive_serial_probe(struct device_d *dev)
+{
+ struct sifive_serial_priv *priv;
+ struct resource *iores;
+ struct clk *clk;
+ u32 freq;
+ int ret;
+
+ clk = clk_get(dev, NULL);
+ if (!IS_ERR(clk)) {
+ freq = clk_get_rate(clk);
+ } else {
+ dev_dbg(dev, "failed to get clock. Fallback to device tree.\n");
+
+ ret = of_property_read_u32(dev->device_node, "clock-frequency", &freq);
+ if (ret) {
+ dev_warn(dev, "unknown clock frequency\n");
+ return ret;
+ }
+ }
+
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores))
+ return PTR_ERR(iores);
+
+ priv = xzalloc(sizeof(*priv));
+
+ priv->freq = freq;
+ priv->regs = IOMEM(iores->start);
+
+ priv->cdev.dev = dev;
+ priv->cdev.putc = sifive_serial_putc;
+ priv->cdev.getc = sifive_serial_getc;
+ priv->cdev.tstc = sifive_serial_tstc;
+ priv->cdev.flush = sifive_serial_flush;
+ priv->cdev.setbrg = sifive_serial_setbrg,
+
+ sifive_serial_init(priv->regs);
+
+ return console_register(&priv->cdev);
+}
+
+static __maybe_unused struct of_device_id sifive_serial_dt_ids[] = {
+ { .compatible = "sifive,uart0" },
+ { /* sentinel */ }
+};
+
+static struct driver_d serial_sifive_driver = {
+ .name = "serial_sifive",
+ .probe = sifive_serial_probe,
+ .of_compatible = sifive_serial_dt_ids,
+};
+console_platform_driver(serial_sifive_driver);