diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2021-05-17 16:23:50 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2021-05-17 16:23:50 +0200 |
commit | c5e0e697de769d0e78a00b1cb47fe864fade9974 (patch) | |
tree | 1a9177fe4fb08156bee02cdd2df0dbed61f2a12a /drivers | |
parent | ec8f2fe6b1bc9e69a725863a61a6b84ab7a91989 (diff) | |
parent | b383d7739c355fee9f81986e6e8d030185373ca5 (diff) | |
download | barebox-c5e0e697de769d0e78a00b1cb47fe864fade9974.tar.gz barebox-c5e0e697de769d0e78a00b1cb47fe864fade9974.tar.xz |
Merge branch 'for-next/riscv'
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/clk/Kconfig | 2 | ||||
-rw-r--r-- | drivers/clk/Makefile | 2 | ||||
-rw-r--r-- | drivers/clk/analogbits/Makefile | 3 | ||||
-rw-r--r-- | drivers/clk/analogbits/wrpll-cln28hpc.c | 367 | ||||
-rw-r--r-- | drivers/clk/sifive/Kconfig | 18 | ||||
-rw-r--r-- | drivers/clk/sifive/Makefile | 2 | ||||
-rw-r--r-- | drivers/clk/sifive/fu540-prci.c | 87 | ||||
-rw-r--r-- | drivers/clk/sifive/fu540-prci.h | 16 | ||||
-rw-r--r-- | drivers/clk/sifive/fu740-prci.c | 121 | ||||
-rw-r--r-- | drivers/clk/sifive/fu740-prci.h | 21 | ||||
-rw-r--r-- | drivers/clk/sifive/sifive-prci.c | 581 | ||||
-rw-r--r-- | drivers/clk/sifive/sifive-prci.h | 298 | ||||
-rw-r--r-- | drivers/gpio/Kconfig | 7 | ||||
-rw-r--r-- | drivers/gpio/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpio/gpio-generic.c | 445 | ||||
-rw-r--r-- | drivers/gpio/gpio-mpc8xxx.c | 6 | ||||
-rw-r--r-- | drivers/gpio/gpio-sifive.c | 87 | ||||
-rw-r--r-- | drivers/serial/Kconfig | 9 | ||||
-rw-r--r-- | drivers/serial/Makefile | 1 | ||||
-rw-r--r-- | drivers/serial/serial_sifive.c | 171 |
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, ®s->txctrl); + writel(UART_RXCTRL_RXEN, ®s->rxctrl); + writel(0, ®s->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(®s->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(®s->txfifo) & UART_TXFIFO_FULL) + ; + + writel(ch, ®s->txfifo); +} + +static int sifive_serial_tstc(struct console_device *cdev) +{ + struct sifive_serial_regs __iomem *regs = to_priv(cdev)->regs; + + return readl(®s->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(®s->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); |