From ffcb31cdc3f20228ec0f82bcbc42162e336e2d87 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sat, 10 Apr 2021 13:06:35 +0200 Subject: PBL: fdt: implement fdt_device_get_match_data Currently, the generic DT image can't properly have a PBL console, because it's only known at runtime what system we are running on. As we already parse the FDT in the PBL to get the memory regions, we could extract the board compatible as well and determine which UART to use. Add a helper to achieve this. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20210410110638.2106658-1-ahmad@a3f.at Signed-off-by: Sascha Hauer --- include/pbl.h | 9 +++++++++ pbl/fdt.c | 35 +++++++++++++++++++++++++++++++++++ pbl/string.c | 12 ++++++++++++ 3 files changed, 56 insertions(+) diff --git a/include/pbl.h b/include/pbl.h index 194d5e7508..f58daec735 100644 --- a/include/pbl.h +++ b/include/pbl.h @@ -34,4 +34,13 @@ ssize_t pbl_fat_load(struct pbl_bio *, const char *filename, void *dest, size_t void fdt_find_mem(const void *fdt, unsigned long *membase, unsigned long *memsize); +struct fdt_device_id { + const char *compatible; + const void *data; +}; + +const void * +fdt_device_get_match_data(const void *fdt, const char *nodepath, + const struct fdt_device_id ids[]); + #endif /* __PBL_H__ */ diff --git a/pbl/fdt.c b/pbl/fdt.c index b4a40a514b..18ddb9f48a 100644 --- a/pbl/fdt.c +++ b/pbl/fdt.c @@ -68,3 +68,38 @@ err: pr_err("No memory, cannot continue\n"); while (1); } + +const void *fdt_device_get_match_data(const void *fdt, const char *nodepath, + const struct fdt_device_id ids[]) +{ + int node, length; + const char *list, *end; + const struct fdt_device_id *id; + + node = fdt_path_offset(fdt, nodepath); + if (node < 0) + return NULL; + + list = fdt_getprop(fdt, node, "compatible", &length); + if (!list) + return NULL; + + end = list + length; + + while (list < end) { + length = strnlen(list, end - list) + 1; + + /* Abort if the last string isn't properly NUL-terminated. */ + if (list + length > end) + return NULL; + + for (id = ids; id->compatible; id++) { + if (!strcasecmp(list, id->compatible)) + return id->data; + } + + list += length; + } + + return NULL; +} diff --git a/pbl/string.c b/pbl/string.c index e6c0997ebc..e96eb99fc2 100644 --- a/pbl/string.c +++ b/pbl/string.c @@ -7,6 +7,7 @@ #include #include #include +#include void *memcpy(void *__dest, __const void *__src, size_t __n) { @@ -98,6 +99,17 @@ int strcmp(const char *cs, const char *ct) return res; } +int strcasecmp(const char *s1, const char *s2) +{ + int c1, c2; + + do { + c1 = tolower(*s1++); + c2 = tolower(*s2++); + } while (c1 == c2 && c1 != 0); + return c1 - c2; +} + void *memchr(const void *s, int c, size_t count) { const unsigned char *p = s; -- cgit v1.2.3 From 6a1a4c73923f41d83249ca6271beb682a931b848 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sat, 10 Apr 2021 13:06:36 +0200 Subject: RISC-V: debug_ll: ns16550: split off debug_ll from generic parts The early NS16550 code is written for DEBUG_LL and can't be directly used with pbl_set_putc if it's disabled. Split off the generic parts into a new header that can be used by the virt board for PBL console. DEBUG_LL functionality is unaffected. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20210410110638.2106658-2-ahmad@a3f.at Signed-off-by: Sascha Hauer --- arch/riscv/include/asm/debug_ll_ns16550.h | 49 +++++++++++-------------------- arch/riscv/include/asm/ns16550.h | 48 ++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 32 deletions(-) create mode 100644 arch/riscv/include/asm/ns16550.h diff --git a/arch/riscv/include/asm/debug_ll_ns16550.h b/arch/riscv/include/asm/debug_ll_ns16550.h index 7d6d12df74..b09882ddad 100644 --- a/arch/riscv/include/asm/debug_ll_ns16550.h +++ b/arch/riscv/include/asm/debug_ll_ns16550.h @@ -35,19 +35,6 @@ #endif /* CONFIG_DEBUG_LL */ -#define UART_THR (0x0 << DEBUG_LL_UART_SHIFT) -#define UART_RBR (0x0 << DEBUG_LL_UART_SHIFT) -#define UART_DLL (0x0 << DEBUG_LL_UART_SHIFT) -#define UART_DLM (0x1 << DEBUG_LL_UART_SHIFT) -#define UART_LCR (0x3 << DEBUG_LL_UART_SHIFT) -#define UART_LSR (0x5 << DEBUG_LL_UART_SHIFT) - -#define UART_LCR_W 0x07 /* Set UART to 8,N,2 & DLAB = 0 */ -#define UART_LCR_DLAB 0x87 /* Set UART to 8,N,2 & DLAB = 1 */ - -#define UART_LSR_DR 0x01 /* UART received data present */ -#define UART_LSR_THRE 0x20 /* Xmit holding register empty */ - #if defined(DEBUG_LL_UART_IOSIZE32) #define UART_REG_L lw #define UART_REG_S sw @@ -62,31 +49,29 @@ #error "Please define DEBUG_LL_UART_IOSIZE{8,32}" #endif +#include + #ifndef __ASSEMBLY__ /* * C macros */ -#include - static inline void PUTC_LL(char ch) { #ifdef CONFIG_DEBUG_LL - while (!(__uart_read((u8 *)DEBUG_LL_UART_ADDR + UART_LSR) & UART_LSR_THRE)) - ; - __uart_write(ch, (u8 *)DEBUG_LL_UART_ADDR + UART_THR); -#endif /* CONFIG_DEBUG_LL */ + early_ns16550_putc(ch, DEBUG_LL_UART_ADDR, DEBUG_LL_UART_SHIFT, + __uart_read, __uart_write); +#endif } static inline void debug_ll_ns16550_init(void) { #ifdef CONFIG_DEBUG_LL - __uart_write(UART_LCR_DLAB, (u8 *)DEBUG_LL_UART_ADDR + UART_LCR); - __uart_write(DEBUG_LL_UART_DIVISOR & 0xff, (u8 *)DEBUG_LL_UART_ADDR + UART_DLL); - __uart_write((DEBUG_LL_UART_DIVISOR >> 8) & 0xff, (u8 *)DEBUG_LL_UART_ADDR + UART_DLM); - __uart_write(UART_LCR_W, (u8 *)DEBUG_LL_UART_ADDR + UART_LCR); -#endif /* CONFIG_DEBUG_LL */ + early_ns16550_init(DEBUG_LL_UART_ADDR, DEBUG_LL_UART_DIVISOR, + DEBUG_LL_UART_SHIFT, __uart_write); +#endif } + #else /* __ASSEMBLY__ */ /* * Macros for use in assembly language code @@ -97,15 +82,15 @@ static inline void debug_ll_ns16550_init(void) li t0, DEBUG_LL_UART_ADDR li t1, UART_LCR_DLAB /* DLAB on */ - UART_REG_S t1, UART_LCR(t0) /* Write it out */ + UART_REG_S t1, UART_LCR(DEBUG_LL_UART_SHIFT)(t0) /* Write it out */ li t1, \divisor - UART_REG_S t1, UART_DLL(t0) /* write low order byte */ + UART_REG_S t1, UART_DLL(DEBUG_LL_UART_SHIFT)(t0) /* write low order byte */ srl t1, t1, 8 - UART_REG_S t1, UART_DLM(t0) /* write high order byte */ + UART_REG_S t1, UART_DLM(DEBUG_LL_UART_SHIFT)(t0) /* write high order byte */ li t1, UART_LCR_W /* DLAB off */ - UART_REG_S t1, UART_LCR(t0) /* Write it out */ + UART_REG_S t1, UART_LCR(DEBUG_LL_UART_SHIFT)(t0) /* Write it out */ #endif /* CONFIG_DEBUG_LL */ .endm @@ -118,11 +103,11 @@ static inline void debug_ll_ns16550_init(void) li t0, DEBUG_LL_UART_ADDR 201: - UART_REG_L t1, UART_LSR(t0) /* get line status */ + UART_REG_L t1, UART_LSR(DEBUG_LL_UART_SHIFT)(t0) /* get line status */ andi t1, t1, UART_LSR_THRE /* check for transmitter empty */ beqz t1, 201b /* try again */ - UART_REG_S a0, UART_THR(t0) /* write the character */ + UART_REG_S a0, UART_THR(DEBUG_LL_UART_SHIFT)(t0) /* write the character */ #endif /* CONFIG_DEBUG_LL */ .endm @@ -158,7 +143,7 @@ static inline void debug_ll_ns16550_init(void) li t0, DEBUG_LL_UART_ADDR /* get line status and check for data present */ - UART_REG_L s0, UART_LSR(t0) + UART_REG_L s0, UART_LSR(DEBUG_LL_UART_SHIFT)(t0) andi s0, s0, UART_LSR_DR #endif /* CONFIG_DEBUG_LL */ @@ -177,7 +162,7 @@ static inline void debug_ll_ns16550_init(void) beqz s0, 204b /* read a character */ - UART_REG_L s0, UART_RBR(t0) + UART_REG_L s0, UART_RBR(DEBUG_LL_UART_SHIFT)(t0) #endif /* CONFIG_DEBUG_LL */ .endm diff --git a/arch/riscv/include/asm/ns16550.h b/arch/riscv/include/asm/ns16550.h new file mode 100644 index 0000000000..7f56692b77 --- /dev/null +++ b/arch/riscv/include/asm/ns16550.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2016, 2017 Antony Pavlov + */ + +/** @file + * This file contains declaration for early output support + */ +#ifndef __ASM_NS16550_H__ +#define __ASM_NS16550_H__ + +#include + +#define UART_THR(shift) (0x0 << shift) +#define UART_RBR(shift) (0x0 << shift) +#define UART_DLL(shift) (0x0 << shift) +#define UART_DLM(shift) (0x1 << shift) +#define UART_LCR(shift) (0x3 << shift) +#define UART_LSR(shift) (0x5 << shift) + +#define UART_LCR_W 0x07 /* Set UART to 8,N,2 & DLAB = 0 */ +#define UART_LCR_DLAB 0x87 /* Set UART to 8,N,2 & DLAB = 1 */ + +#define UART_LSR_DR 0x01 /* UART received data present */ +#define UART_LSR_THRE 0x20 /* Xmit holding register empty */ + +#ifndef __ASSEMBLY__ + +#include + +#define early_ns16550_putc(ch, base, shift, readx, writex) \ + do { \ + while (!(readx((u8 *)base + UART_LSR(shift)) & UART_LSR_THRE)) \ + ; \ + writex(ch, (u8 *)base + UART_THR(shift)); \ + } while (0) + +#define early_ns16550_init(base, divisor, shift, writex) \ + do { \ + writex(UART_LCR_DLAB, (u8 *)base + UART_LCR(shift)); \ + writex(divisor & 0xff, (u8 *)base + UART_DLL(shift)); \ + writex((divisor >> 8) & 0xff, (u8 *)base + UART_DLM(shift)); \ + writex(UART_LCR_W, (u8 *)base + UART_LCR(shift)); \ + } while (0) + +#endif + +#endif -- cgit v1.2.3 From b1c7e757443508a7e5e91df29c4d9b6275c38973 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sat, 10 Apr 2021 13:06:37 +0200 Subject: RISC-V: board-dt-2nd: add PBL console support for virt The Virt machine has a ns16550a UART at address 0x10000000. As we reuse the generic DT image for this platform, we can't use either DEBUG_LL or pbl_console as we would need to hardcode information on what UART is available where, which wouldn't be correct for other boards. However, if we parse the board compatible, we could match it with the appropriate PBL console implementation without sacrificing portability. Do so. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20210410110638.2106658-3-ahmad@a3f.at Signed-off-by: Sascha Hauer --- arch/riscv/boot/board-dt-2nd.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/arch/riscv/boot/board-dt-2nd.c b/arch/riscv/boot/board-dt-2nd.c index be28ea23cd..e9810f8add 100644 --- a/arch/riscv/boot/board-dt-2nd.c +++ b/arch/riscv/boot/board-dt-2nd.c @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include @@ -22,10 +22,29 @@ #include +static void virt_ns16550_putc(void *base, int ch) +{ + early_ns16550_putc(ch, base, 0, readb, writeb); +} + +static void virt_ns16550_init(void) +{ + void __iomem *base = IOMEM(0x10000000); + + early_ns16550_init(base, 3686400 / CONFIG_BAUDRATE, 0, writeb); + pbl_set_putc(virt_ns16550_putc, base); +} + +static const struct fdt_device_id console_ids[] = { + { .compatible = "riscv-virtio", .data = virt_ns16550_init }, + { /* sentinel */ } +}; + ENTRY_FUNCTION(start_dt_2nd, a0, _fdt, a2) { unsigned long membase, memsize, endmem, endfdt, uncompressed_len; struct fdt_header *fdt = (void *)_fdt; + void (*pbl_uart_init)(void); if (!fdt) hang(); @@ -33,6 +52,12 @@ ENTRY_FUNCTION(start_dt_2nd, a0, _fdt, a2) relocate_to_current_adr(); setup_c(); + pbl_uart_init = fdt_device_get_match_data(fdt, "/", console_ids); + if (pbl_uart_init) { + pbl_uart_init(); + putchar('>'); + } + fdt_find_mem(fdt, &membase, &memsize); endmem = membase + memsize; endfdt = _fdt + be32_to_cpu(fdt->totalsize); -- cgit v1.2.3 From 25760b40bfdada30b8d6f42f6326f1e5302fd026 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sat, 10 Apr 2021 13:06:38 +0200 Subject: RISC-V: delete unused mach-virt subdirectory The code within was unused and was kept only to make patching the machine for DEBUG_LL easier. Now that we have PBL console support, this is no longer needed, so remove it. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20210410110638.2106658-4-ahmad@a3f.at Signed-off-by: Sascha Hauer --- arch/riscv/Makefile | 1 - arch/riscv/mach-virt/Makefile | 3 --- arch/riscv/mach-virt/include/mach/debug_ll.h | 25 ------------------------- 3 files changed, 29 deletions(-) delete mode 100644 arch/riscv/mach-virt/Makefile delete mode 100644 arch/riscv/mach-virt/include/mach/debug_ll.h diff --git a/arch/riscv/Makefile b/arch/riscv/Makefile index aba4526bba..1a41d15477 100644 --- a/arch/riscv/Makefile +++ b/arch/riscv/Makefile @@ -20,7 +20,6 @@ cflags-y += $(riscv-cflags-y) LDFLAGS_barebox += -nostdlib machine-$(CONFIG_MACH_ERIZO) := erizo -machine-$(CONFIG_MACH_VIRT) := virt LDFLAGS_barebox += $(riscv-ldflags-y) diff --git a/arch/riscv/mach-virt/Makefile b/arch/riscv/mach-virt/Makefile deleted file mode 100644 index d9c51e74c3..0000000000 --- a/arch/riscv/mach-virt/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -# just to build a built-in.o. Otherwise compilation fails when no o-files is -# created. -obj- += dummy.o diff --git a/arch/riscv/mach-virt/include/mach/debug_ll.h b/arch/riscv/mach-virt/include/mach/debug_ll.h deleted file mode 100644 index 056b7a330b..0000000000 --- a/arch/riscv/mach-virt/include/mach/debug_ll.h +++ /dev/null @@ -1,25 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Copyright (C) 2017 Antony Pavlov - */ - -#ifndef __MACH_VIRT_DEBUG_LL__ -#define __MACH_VIRT_DEBUG_LL__ - -/** @file - * This File contains declaration for early output support - */ - -#include - -#define DEBUG_LL_UART_ADDR 0x10000000 -#define DEBUG_LL_UART_SHIFT 0 -#define DEBUG_LL_UART_IOSIZE8 - -#define DEBUG_LL_UART_CLK 0x00384000 -#define DEBUG_LL_UART_BPS CONFIG_BAUDRATE -#define DEBUG_LL_UART_DIVISOR (DEBUG_LL_UART_CLK / DEBUG_LL_UART_BPS) - -#include - -#endif /* __MACH_VIRT_DEBUG_LL__ */ -- cgit v1.2.3 From 6a4e7537943749d8c1708db2cbe52529b01794c7 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Tue, 27 Apr 2021 22:22:58 +0200 Subject: clk: add SiFive PRCI clock controller support Import U74 and U54 clock controller support from Linux v5.12. Unlike Linux, dependency wrpll-cln28hpc.c is compiled in unconditionally. Linker garbage collection will take care to omit it if unreferenced. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20210427202309.32077-1-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- drivers/clk/Kconfig | 2 + drivers/clk/Makefile | 2 + drivers/clk/analogbits/Makefile | 3 + drivers/clk/analogbits/wrpll-cln28hpc.c | 367 ++++++++++++++++ drivers/clk/sifive/Kconfig | 18 + drivers/clk/sifive/Makefile | 2 + drivers/clk/sifive/fu540-prci.c | 87 ++++ drivers/clk/sifive/fu540-prci.h | 16 + drivers/clk/sifive/fu740-prci.c | 121 ++++++ drivers/clk/sifive/fu740-prci.h | 21 + drivers/clk/sifive/sifive-prci.c | 581 ++++++++++++++++++++++++++ drivers/clk/sifive/sifive-prci.h | 298 +++++++++++++ include/linux/clk/analogbits-wrpll-cln28hpc.h | 79 ++++ 13 files changed, 1597 insertions(+) create mode 100644 drivers/clk/analogbits/Makefile create mode 100644 drivers/clk/analogbits/wrpll-cln28hpc.c create mode 100644 drivers/clk/sifive/Kconfig create mode 100644 drivers/clk/sifive/Makefile create mode 100644 drivers/clk/sifive/fu540-prci.c create mode 100644 drivers/clk/sifive/fu540-prci.h create mode 100644 drivers/clk/sifive/fu740-prci.c create mode 100644 drivers/clk/sifive/fu740-prci.h create mode 100644 drivers/clk/sifive/sifive-prci.c create mode 100644 drivers/clk/sifive/sifive-prci.h create mode 100644 include/linux/clk/analogbits-wrpll-cln28hpc.h 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 +#include +#include +#include +#include +#include +#include +#include + +/* 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 + +#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 + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#include + +/* + * 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/include/linux/clk/analogbits-wrpll-cln28hpc.h b/include/linux/clk/analogbits-wrpll-cln28hpc.h new file mode 100644 index 0000000000..03279097e1 --- /dev/null +++ b/include/linux/clk/analogbits-wrpll-cln28hpc.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018-2019 SiFive, Inc. + * Wesley Terpstra + * Paul Walmsley + */ + +#ifndef __LINUX_CLK_ANALOGBITS_WRPLL_CLN28HPC_H +#define __LINUX_CLK_ANALOGBITS_WRPLL_CLN28HPC_H + +#include + +/* DIVQ_VALUES: number of valid DIVQ values */ +#define DIVQ_VALUES 6 + +/* + * Bit definitions for struct wrpll_cfg.flags + * + * WRPLL_FLAGS_BYPASS_FLAG: if set, the PLL is either in bypass, or should be + * programmed to enter bypass + * WRPLL_FLAGS_RESET_FLAG: if set, the PLL is in reset + * WRPLL_FLAGS_INT_FEEDBACK_FLAG: if set, the PLL is configured for internal + * feedback mode + * WRPLL_FLAGS_EXT_FEEDBACK_FLAG: if set, the PLL is configured for external + * feedback mode (not yet supported by this driver) + */ +#define WRPLL_FLAGS_BYPASS_SHIFT 0 +#define WRPLL_FLAGS_BYPASS_MASK BIT(WRPLL_FLAGS_BYPASS_SHIFT) +#define WRPLL_FLAGS_RESET_SHIFT 1 +#define WRPLL_FLAGS_RESET_MASK BIT(WRPLL_FLAGS_RESET_SHIFT) +#define WRPLL_FLAGS_INT_FEEDBACK_SHIFT 2 +#define WRPLL_FLAGS_INT_FEEDBACK_MASK BIT(WRPLL_FLAGS_INT_FEEDBACK_SHIFT) +#define WRPLL_FLAGS_EXT_FEEDBACK_SHIFT 3 +#define WRPLL_FLAGS_EXT_FEEDBACK_MASK BIT(WRPLL_FLAGS_EXT_FEEDBACK_SHIFT) + +/** + * struct wrpll_cfg - WRPLL configuration values + * @divr: reference divider value (6 bits), as presented to the PLL signals + * @divf: feedback divider value (9 bits), as presented to the PLL signals + * @divq: output divider value (3 bits), as presented to the PLL signals + * @flags: PLL configuration flags. See above for more information + * @range: PLL loop filter range. See below for more information + * @output_rate_cache: cached output rates, swept across DIVQ + * @parent_rate: PLL refclk rate for which values are valid + * @max_r: maximum possible R divider value, given @parent_rate + * @init_r: initial R divider value to start the search from + * + * @divr, @divq, @divq, @range represent what the PLL expects to see + * on its input signals. Thus @divr and @divf are the actual divisors + * minus one. @divq is a power-of-two divider; for example, 1 = + * divide-by-2 and 6 = divide-by-64. 0 is an invalid @divq value. + * + * When initially passing a struct wrpll_cfg record, the + * record should be zero-initialized with the exception of the @flags + * field. The only flag bits that need to be set are either + * WRPLL_FLAGS_INT_FEEDBACK or WRPLL_FLAGS_EXT_FEEDBACK. + */ +struct wrpll_cfg { + u8 divr; + u8 divq; + u8 range; + u8 flags; + u16 divf; +/* private: */ + u32 output_rate_cache[DIVQ_VALUES]; + unsigned long parent_rate; + u8 max_r; + u8 init_r; +}; + +int wrpll_configure_for_rate(struct wrpll_cfg *c, u32 target_rate, + unsigned long parent_rate); + +unsigned int wrpll_calc_max_lock_us(const struct wrpll_cfg *c); + +unsigned long wrpll_calc_output_rate(const struct wrpll_cfg *c, + unsigned long parent_rate); + +#endif /* __LINUX_CLK_ANALOGBITS_WRPLL_CLN28HPC_H */ -- cgit v1.2.3 From 786da1e50a6dab25c446f05f58d38bb897d9156c Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Tue, 27 Apr 2021 22:22:59 +0200 Subject: serial: implement SiFive UART support Import serial driver from Linux v5.11. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20210427202309.32077-2-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- drivers/serial/Kconfig | 9 +++ drivers/serial/Makefile | 1 + drivers/serial/serial_sifive.c | 171 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 drivers/serial/serial_sifive.c 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 + */ + +#include +#include +#include +#include +#include +#include +#include + +#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); -- cgit v1.2.3 From 569042e154b7d211aea82c884607ee568bcd4d3d Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Tue, 27 Apr 2021 22:23:00 +0200 Subject: debug_ll: support to get rid of mach directories Linux support has no arch/riscv/mach-* directories. If we can get rid of them, we could multi-image build all images at once. Only thing holding us back is . Add as alternative. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20210427202309.32077-3-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- common/Kconfig | 4 ++++ include/debug_ll.h | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/common/Kconfig b/common/Kconfig index bddf802d3b..b10fb45b72 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -1462,6 +1462,10 @@ endmenu config HAS_DEBUG_LL bool +config HAS_ASM_DEBUG_LL + bool + select HAS_DEBUG_LL + config DDR_SPD bool select CRC_ITU_T diff --git a/include/debug_ll.h b/include/debug_ll.h index 5bd1afe6ac..735033b314 100644 --- a/include/debug_ll.h +++ b/include/debug_ll.h @@ -12,6 +12,9 @@ #define __INCLUDE_DEBUG_LL_H__ #ifdef CONFIG_HAS_DEBUG_LL +#ifdef CONFIG_HAS_ASM_DEBUG_LL +#include +#else /* * mach/debug_ll.h should implement PUTC_LL. This can be a macro or a static * inline function. Note that several SoCs expect the UART to be initialized @@ -21,6 +24,7 @@ */ #include #endif +#endif #if defined (CONFIG_DEBUG_LL) -- cgit v1.2.3 From 7d7cddb45acf915a7cb761b85c7a291d874195f5 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Tue, 27 Apr 2021 22:23:01 +0200 Subject: RISC-V: support multi-image for all machines We already got rid of arch/riscv/mach-virt. Now do the same for arch/riscv/mach-erizo. This will enable us to build images for all RISC-V boards at once. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20210427202309.32077-4-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- arch/riscv/Kconfig | 31 ++---------------------------- arch/riscv/Kconfig.socs | 24 +++++++++++++++++++++++ arch/riscv/Makefile | 2 +- arch/riscv/configs/erizo_generic_defconfig | 1 + arch/riscv/configs/virt32_defconfig | 2 +- arch/riscv/configs/virt64_defconfig | 2 +- arch/riscv/mach-erizo/Kconfig | 11 ----------- 7 files changed, 30 insertions(+), 43 deletions(-) create mode 100644 arch/riscv/Kconfig.socs delete mode 100644 arch/riscv/mach-erizo/Kconfig diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index c0583f3153..a4aa799acf 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -19,31 +19,6 @@ config ARCH_TEXT_BASE hex default 0x0 -menu "Machine selection" - -choice - prompt "System type" - default MACH_ERIZO - -config MACH_ERIZO - bool "erizo family" - select ARCH_RV32I - select HAS_DEBUG_LL - select HAS_NMON - select USE_COMPRESSED_DTB - select RISCV_M_MODE - select RISCV_TIMER - -config MACH_VIRT - bool "virt family" - select BOARD_RISCV_GENERIC_DT - select CLINT_TIMER - help - Generates an image tht can be be booted by QEMU. The image is called - barebox-dt-2nd.img - -endchoice - choice prompt "Base ISA" default ARCH_RV32I @@ -63,6 +38,8 @@ config ARCH_RV64I endchoice +source "arch/riscv/Kconfig.socs" + config CPU_SUPPORTS_32BIT_KERNEL bool @@ -82,8 +59,6 @@ config 64BIT select ARCH_DMA_ADDR_T_64BIT select PHYS_ADDR_T_64BIT -source "arch/riscv/mach-erizo/Kconfig" - config BOARD_RISCV_GENERIC_DT select BOARD_GENERIC_DT bool "Build generic RISC-V device tree 2nd stage image" @@ -93,8 +68,6 @@ config BOARD_RISCV_GENERIC_DT in a1 like the Kernel does, so it could be used anywhere where a Kernel image could be used. The image will be called images/barebox-dt-2nd.img -endmenu - menu "RISC-V specific settings" config RISCV_OPTIMZED_STRING_FUNCTIONS diff --git a/arch/riscv/Kconfig.socs b/arch/riscv/Kconfig.socs new file mode 100644 index 0000000000..b0e38d8f2c --- /dev/null +++ b/arch/riscv/Kconfig.socs @@ -0,0 +1,24 @@ +menu "SoC selection" + +config SOC_ERIZO + bool "Erizo SoC" + select ARCH_RV32I + select HAS_DEBUG_LL + select HAS_NMON + select USE_COMPRESSED_DTB + select RISCV_M_MODE + select RISCV_TIMER + +config BOARD_ERIZO_GENERIC + depends on SOC_ERIZO + def_bool y + +config SOC_VIRT + bool "QEMU Virt Machine" + select BOARD_RISCV_GENERIC_DT + select CLINT_TIMER + help + Generates an image tht can be be booted by QEMU. The image is called + barebox-dt-2nd.img + +endmenu diff --git a/arch/riscv/Makefile b/arch/riscv/Makefile index 1a41d15477..09a94d69b2 100644 --- a/arch/riscv/Makefile +++ b/arch/riscv/Makefile @@ -19,7 +19,7 @@ LDFLAGS_pbl += $(riscv-ldflags-y) cflags-y += $(riscv-cflags-y) LDFLAGS_barebox += -nostdlib -machine-$(CONFIG_MACH_ERIZO) := erizo +machine-$(CONFIG_SOC_ERIZO) := erizo LDFLAGS_barebox += $(riscv-ldflags-y) diff --git a/arch/riscv/configs/erizo_generic_defconfig b/arch/riscv/configs/erizo_generic_defconfig index 839b652ab5..247a179130 100644 --- a/arch/riscv/configs/erizo_generic_defconfig +++ b/arch/riscv/configs/erizo_generic_defconfig @@ -1,3 +1,4 @@ +CONFIG_SOC_ERIZO=y # CONFIG_GLOBALVAR is not set CONFIG_STACK_SIZE=0x20000 CONFIG_MALLOC_SIZE=0x100000 diff --git a/arch/riscv/configs/virt32_defconfig b/arch/riscv/configs/virt32_defconfig index 83e3ca1bad..218fee57b7 100644 --- a/arch/riscv/configs/virt32_defconfig +++ b/arch/riscv/configs/virt32_defconfig @@ -1,4 +1,4 @@ -CONFIG_MACH_VIRT=y +CONFIG_SOC_VIRT=y CONFIG_RISCV_OPTIMZED_STRING_FUNCTIONS=y CONFIG_STACK_SIZE=0x20000 CONFIG_MALLOC_SIZE=0x0 diff --git a/arch/riscv/configs/virt64_defconfig b/arch/riscv/configs/virt64_defconfig index 17ce16637d..04a4f1e2f4 100644 --- a/arch/riscv/configs/virt64_defconfig +++ b/arch/riscv/configs/virt64_defconfig @@ -1,4 +1,4 @@ -CONFIG_MACH_VIRT=y +CONFIG_SOC_VIRT=y CONFIG_ARCH_RV64I=y CONFIG_RISCV_OPTIMZED_STRING_FUNCTIONS=y CONFIG_STACK_SIZE=0x20000 diff --git a/arch/riscv/mach-erizo/Kconfig b/arch/riscv/mach-erizo/Kconfig deleted file mode 100644 index 2400b4437b..0000000000 --- a/arch/riscv/mach-erizo/Kconfig +++ /dev/null @@ -1,11 +0,0 @@ -if MACH_ERIZO - -choice - prompt "Board type" - -config BOARD_ERIZO_GENERIC - bool "erizo generic board" - -endchoice - -endif -- cgit v1.2.3 From 03b6feb99ad6ca9aed71bd0b56e660e4ebf23e2e Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Tue, 27 Apr 2021 22:23:02 +0200 Subject: RISC-V: erizo: restrict to RV32I Erizo is a RISC-V 32-bit softcore. Because ARCH_RV32I can be selected independently, a 64-bit barebox images could be built, but the image produced would be useless. Avoid this by not showing the SOC_ERIZO prompt when compiling for 64-bit. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20210427202309.32077-5-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- arch/riscv/Kconfig.socs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/riscv/Kconfig.socs b/arch/riscv/Kconfig.socs index b0e38d8f2c..093bca3380 100644 --- a/arch/riscv/Kconfig.socs +++ b/arch/riscv/Kconfig.socs @@ -2,7 +2,7 @@ menu "SoC selection" config SOC_ERIZO bool "Erizo SoC" - select ARCH_RV32I + depends on ARCH_RV32I select HAS_DEBUG_LL select HAS_NMON select USE_COMPRESSED_DTB -- cgit v1.2.3 From 60b6da3b17214d0efb27d5b42e804aa641bf5ceb Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Tue, 27 Apr 2021 22:23:03 +0200 Subject: RISC-V: erizo: drop mach-erizo directory With the recent changes, we can now delete mach-erizo. Do so. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20210427202309.32077-6-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- arch/riscv/Kconfig.socs | 2 +- arch/riscv/Makefile | 13 ----------- arch/riscv/boards/erizo/lowlevel.c | 2 +- arch/riscv/include/asm/debug_ll.h | 33 +++++++++++++++++++++++++++ arch/riscv/include/asm/debug_ll_ns16550.h | 2 ++ arch/riscv/mach-erizo/Makefile | 3 --- arch/riscv/mach-erizo/include/mach/debug_ll.h | 33 --------------------------- common/Kconfig | 5 ++++ 8 files changed, 42 insertions(+), 51 deletions(-) create mode 100644 arch/riscv/include/asm/debug_ll.h delete mode 100644 arch/riscv/mach-erizo/Makefile delete mode 100644 arch/riscv/mach-erizo/include/mach/debug_ll.h diff --git a/arch/riscv/Kconfig.socs b/arch/riscv/Kconfig.socs index 093bca3380..ea5ae0a6e9 100644 --- a/arch/riscv/Kconfig.socs +++ b/arch/riscv/Kconfig.socs @@ -3,7 +3,7 @@ menu "SoC selection" config SOC_ERIZO bool "Erizo SoC" depends on ARCH_RV32I - select HAS_DEBUG_LL + select HAS_ASM_DEBUG_LL select HAS_NMON select USE_COMPRESSED_DTB select RISCV_M_MODE diff --git a/arch/riscv/Makefile b/arch/riscv/Makefile index 09a94d69b2..0b1278936d 100644 --- a/arch/riscv/Makefile +++ b/arch/riscv/Makefile @@ -19,8 +19,6 @@ LDFLAGS_pbl += $(riscv-ldflags-y) cflags-y += $(riscv-cflags-y) LDFLAGS_barebox += -nostdlib -machine-$(CONFIG_SOC_ERIZO) := erizo - LDFLAGS_barebox += $(riscv-ldflags-y) ifndef CONFIG_MODULES @@ -31,21 +29,10 @@ endif KBUILD_BINARY := barebox.bin -machdirs := $(patsubst %,arch/riscv/mach-%/,$(machine-y)) - -KBUILD_CPPFLAGS += $(patsubst %,-I$(srctree)/%include,$(machdirs)) - archprepare: maketools PHONY += maketools -ifneq ($(machine-y),) -MACH := arch/riscv/mach-$(machine-y)/ -else -MACH := -endif - -common-y += $(MACH) common-y += arch/riscv/boards/ common-y += arch/riscv/cpu/ common-y += arch/riscv/lib/ diff --git a/arch/riscv/boards/erizo/lowlevel.c b/arch/riscv/boards/erizo/lowlevel.c index d9edb530b7..6acf15931c 100644 --- a/arch/riscv/boards/erizo/lowlevel.c +++ b/arch/riscv/boards/erizo/lowlevel.c @@ -8,7 +8,7 @@ ENTRY_FUNCTION(start_erizo_generic, a0, a1, a2) { extern char __dtb_z_erizo_generic_start[]; - debug_ll_ns16550_init(); + debug_ll_init(); putc_ll('>'); /* On POR, we are running from read-only memory here. */ diff --git a/arch/riscv/include/asm/debug_ll.h b/arch/riscv/include/asm/debug_ll.h new file mode 100644 index 0000000000..598921910a --- /dev/null +++ b/arch/riscv/include/asm/debug_ll.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2017 Antony Pavlov + */ + +#ifndef __ASM_DEBUG_LL__ +#define __ASM_DEBUG_LL__ + +/** @file + * This File contains declaration for early output support + */ + +#include + +#ifdef CONFIG_DEBUG_ERIZO + +#define DEBUG_LL_UART_ADDR 0x90000000 +#define DEBUG_LL_UART_SHIFT 2 +#define DEBUG_LL_UART_IOSIZE32 + +#define DEBUG_LL_UART_CLK (24000000 / 16) +#define DEBUG_LL_UART_BPS CONFIG_BAUDRATE +#define DEBUG_LL_UART_DIVISOR (DEBUG_LL_UART_CLK / DEBUG_LL_UART_BPS) + +#include + +#endif + +#ifndef debug_ll_init +#define debug_ll_init() (void)0 +#endif + +#endif /* __ASM_DEBUG_LL__ */ diff --git a/arch/riscv/include/asm/debug_ll_ns16550.h b/arch/riscv/include/asm/debug_ll_ns16550.h index b09882ddad..e208ef4fb1 100644 --- a/arch/riscv/include/asm/debug_ll_ns16550.h +++ b/arch/riscv/include/asm/debug_ll_ns16550.h @@ -168,4 +168,6 @@ static inline void debug_ll_ns16550_init(void) .endm #endif /* __ASSEMBLY__ */ +#define debug_ll_init debug_ll_ns16550_init + #endif /* __INCLUDE_RISCV_ASM_DEBUG_LL_NS16550_H__ */ diff --git a/arch/riscv/mach-erizo/Makefile b/arch/riscv/mach-erizo/Makefile deleted file mode 100644 index d9c51e74c3..0000000000 --- a/arch/riscv/mach-erizo/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -# just to build a built-in.o. Otherwise compilation fails when no o-files is -# created. -obj- += dummy.o diff --git a/arch/riscv/mach-erizo/include/mach/debug_ll.h b/arch/riscv/mach-erizo/include/mach/debug_ll.h deleted file mode 100644 index a20acfcdfb..0000000000 --- a/arch/riscv/mach-erizo/include/mach/debug_ll.h +++ /dev/null @@ -1,33 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) 2017 Antony Pavlov - * - * This file is part of barebox. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#ifndef __MACH_ERIZO_DEBUG_LL__ -#define __MACH_ERIZO_DEBUG_LL__ - -/** @file - * This File contains declaration for early output support - */ - -#include - -#define DEBUG_LL_UART_ADDR 0x90000000 -#define DEBUG_LL_UART_SHIFT 2 -#define DEBUG_LL_UART_IOSIZE32 - -#define DEBUG_LL_UART_CLK (24000000 / 16) -#define DEBUG_LL_UART_BPS CONFIG_BAUDRATE -#define DEBUG_LL_UART_DIVISOR (DEBUG_LL_UART_CLK / DEBUG_LL_UART_BPS) - -#include - -#endif /* __MACH_ERIZO_DEBUG_LL__ */ diff --git a/common/Kconfig b/common/Kconfig index b10fb45b72..6d7a1c6b04 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -1342,6 +1342,11 @@ config DEBUG_RPI3_MINI_UART help Say Y here if you want low-level debugging support on RaspberryPi 3 board mini UART. + +config DEBUG_ERIZO + bool "Erizo ns16550 port" + depends on SOC_ERIZO + endchoice config DEBUG_IMX_UART_PORT -- cgit v1.2.3 From 6fd5711a111e8c7c4e6ea09d962b6885da09f86b Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Tue, 27 Apr 2021 22:23:04 +0200 Subject: RISC-V: add SBI based cpuinfo SBI appeared to be especially useful to implement a generic console driver. However, SBI v0.2 removes these services without substitute. We might find other use for it later, but for now, add the bare minimum of querying the version of the running SBI implementation. The cpuinfo command is intentionally kept generic. It can later be extended to support CONFIG_RISCV_M_MODE as well. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20210427202309.32077-7-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- arch/riscv/include/asm/sbi.h | 32 ++++++++++++++++++----- arch/riscv/lib/Makefile | 2 ++ arch/riscv/lib/cpuinfo.c | 45 ++++++++++++++++++++++++++++++++ arch/riscv/lib/sbi.c | 62 ++++++++++++++++++++++++++++++++++++++++++++ commands/Kconfig | 7 +++++ 5 files changed, 142 insertions(+), 6 deletions(-) create mode 100644 arch/riscv/lib/cpuinfo.c create mode 100644 arch/riscv/lib/sbi.c diff --git a/arch/riscv/include/asm/sbi.h b/arch/riscv/include/asm/sbi.h index 99895d9c3b..ab1fc9a128 100644 --- a/arch/riscv/include/asm/sbi.h +++ b/arch/riscv/include/asm/sbi.h @@ -89,11 +89,32 @@ struct sbiret { long value; }; -void sbi_init(void); -struct sbiret sbi_ecall(int ext, int fid, unsigned long arg0, - unsigned long arg1, unsigned long arg2, - unsigned long arg3, unsigned long arg4, - unsigned long arg5); +static inline struct sbiret sbi_ecall(int ext, int fid, unsigned long arg0, + unsigned long arg1, unsigned long arg2, + unsigned long arg3, unsigned long arg4, + unsigned long arg5) +{ + struct sbiret ret; + + register uintptr_t a0 asm ("a0") = (uintptr_t)(arg0); + register uintptr_t a1 asm ("a1") = (uintptr_t)(arg1); + register uintptr_t a2 asm ("a2") = (uintptr_t)(arg2); + register uintptr_t a3 asm ("a3") = (uintptr_t)(arg3); + register uintptr_t a4 asm ("a4") = (uintptr_t)(arg4); + register uintptr_t a5 asm ("a5") = (uintptr_t)(arg5); + register uintptr_t a6 asm ("a6") = (uintptr_t)(fid); + register uintptr_t a7 asm ("a7") = (uintptr_t)(ext); + asm volatile ("ecall" + : "+r" (a0), "+r" (a1) + : "r" (a2), "r" (a3), "r" (a4), "r" (a5), "r" (a6), "r" (a7) + : "memory"); + ret.error = a0; + ret.value = a1; + + return ret; +} + +long __sbi_base_ecall(int fid); void sbi_console_putchar(int ch); int sbi_console_getchar(void); @@ -148,6 +169,5 @@ static inline unsigned long sbi_minor_version(void) int sbi_err_map_linux_errno(int err); #else /* CONFIG_RISCV_SBI */ static inline int sbi_remote_fence_i(const unsigned long *hart_mask) { return -1; } -static inline void sbi_init(void) {} #endif /* CONFIG_RISCV_SBI */ #endif /* _ASM_RISCV_SBI_H */ diff --git a/arch/riscv/lib/Makefile b/arch/riscv/lib/Makefile index a4eaa1005d..49750d576a 100644 --- a/arch/riscv/lib/Makefile +++ b/arch/riscv/lib/Makefile @@ -6,3 +6,5 @@ obj-y += dtb.o obj-pbl-y += sections.o setupc.o reloc.o sections.o runtime-offset.o obj-$(CONFIG_HAS_ARCH_SJLJ) += setjmp.o longjmp.o obj-$(CONFIG_RISCV_OPTIMZED_STRING_FUNCTIONS) += memcpy.o memset.o memmove.o +obj-$(CONFIG_RISCV_SBI) += sbi.o +obj-$(CONFIG_CMD_RISCV_CPUINFO) += cpuinfo.o diff --git a/arch/riscv/lib/cpuinfo.c b/arch/riscv/lib/cpuinfo.c new file mode 100644 index 0000000000..21b99a990a --- /dev/null +++ b/arch/riscv/lib/cpuinfo.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include + +static const char *implementations[] = { + [0] = "\"Berkeley Boot Loader (BBL)\" ", + [1] = "\"OpenSBI\" ", + [2] = "\"Xvisor\" ", + [3] = "\"KVM\" ", + [4] = "\"RustSBI\" ", + [5] = "\"Diosix\" ", +}; + +static int do_cpuinfo(int argc, char *argv[]) +{ + const char *implementation = ""; + unsigned long impid; + + printf("SBI specification v%lu.%lu detected\n", + sbi_major_version(), sbi_minor_version()); + + if (sbi_spec_is_0_1()) + return 0; + + impid = __sbi_base_ecall(SBI_EXT_BASE_GET_IMP_ID); + if (impid < ARRAY_SIZE(implementations)) + implementation = implementations[impid]; + + printf("SBI implementation ID=0x%lx %sVersion=0x%lx\n", + impid, implementation, __sbi_base_ecall(SBI_EXT_BASE_GET_IMP_VERSION)); + + printf("SBI Machine VENDORID=0x%lx ARCHID=0x%lx MIMPID=0x%lx\n", + __sbi_base_ecall(SBI_EXT_BASE_GET_MVENDORID), + __sbi_base_ecall(SBI_EXT_BASE_GET_MARCHID), + __sbi_base_ecall(SBI_EXT_BASE_GET_MIMPID)); + + return 0; +} + +BAREBOX_CMD_START(cpuinfo) + .cmd = do_cpuinfo, +BAREBOX_CMD_DESC("show CPU information") +BAREBOX_CMD_GROUP(CMD_GRP_INFO) + BAREBOX_CMD_END diff --git a/arch/riscv/lib/sbi.c b/arch/riscv/lib/sbi.c new file mode 100644 index 0000000000..973c9d9d0f --- /dev/null +++ b/arch/riscv/lib/sbi.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SBI initialilization and all extension implementation. + * + * Copyright (c) 2020 Western Digital Corporation or its affiliates. + */ + +#include +#include +#include +#include + +/* default SBI version is 0.1 */ +unsigned long sbi_spec_version = SBI_SPEC_VERSION_DEFAULT; +EXPORT_SYMBOL(sbi_spec_version); + +int sbi_err_map_linux_errno(int err) +{ + switch (err) { + case SBI_SUCCESS: + return 0; + case SBI_ERR_DENIED: + return -EPERM; + case SBI_ERR_INVALID_PARAM: + return -EINVAL; + case SBI_ERR_INVALID_ADDRESS: + return -EFAULT; + case SBI_ERR_NOT_SUPPORTED: + case SBI_ERR_FAILURE: + default: + return -ENOTSUPP; + }; +} +EXPORT_SYMBOL(sbi_err_map_linux_errno); + +long __sbi_base_ecall(int fid) +{ + struct sbiret ret; + + ret = sbi_ecall(SBI_EXT_BASE, fid, 0, 0, 0, 0, 0, 0); + if (!ret.error) + return ret.value; + else + return sbi_err_map_linux_errno(ret.error); +} + +static inline long sbi_get_spec_version(void) +{ + return __sbi_base_ecall(SBI_EXT_BASE_GET_SPEC_VERSION); +} + +static int sbi_init(void) +{ + int ret; + + ret = sbi_get_spec_version(); + if (ret > 0) + sbi_spec_version = ret; + return 0; + +} +core_initcall(sbi_init); diff --git a/commands/Kconfig b/commands/Kconfig index b3937fdd8d..5ae3cb3dd1 100644 --- a/commands/Kconfig +++ b/commands/Kconfig @@ -44,6 +44,13 @@ config CMD_ARM_CPUINFO D-cache: 8192 bytes (linelen = 8) Control register: M C W P D L I V RR DT IT U XP +config CMD_RISCV_CPUINFO + bool "cpuinfo command" + default y + depends on RISCV_SBI + help + Show SBI info about RISC-V CPU + config CMD_DEVINFO tristate default y -- cgit v1.2.3 From 01c1a0e8088da6ca464cfc0950669222a246d5a0 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Tue, 27 Apr 2021 22:23:05 +0200 Subject: gpio: gpio-generic-platform: remove unused non-DT support We have nothing in-tree matching against either "basic-mmio-gpio" or "basic-mmio-gpio-be" and none should be added, because new platforms should probe from device tree. Remove the unused the non-DT support. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20210427202309.32077-8-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- drivers/gpio/gpio-generic.c | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/drivers/gpio/gpio-generic.c b/drivers/gpio/gpio-generic.c index 8259b799f9..f381ddbf4e 100644 --- a/drivers/gpio/gpio-generic.c +++ b/drivers/gpio/gpio-generic.c @@ -337,7 +337,6 @@ 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; r = dev_get_resource_by_name(dev, IORESOURCE_MEM, "dat"); if (IS_ERR(r)) @@ -373,12 +372,6 @@ 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; - } - dev->priv = bgc; return gpiochip_add(&bgc->gc); @@ -391,18 +384,6 @@ static void bgpio_dev_remove(struct device_d *dev) bgpio_remove(bgc); } -static struct platform_device_id bgpio_id_table[] = { - { - .name = "basic-mmio-gpio", - .driver_data = 0, - }, - { - .name = "basic-mmio-gpio-be", - .driver_data = BGPIOF_BIG_ENDIAN, - }, - { } -}; - static struct of_device_id __maybe_unused bgpio_of_match[] = { { .compatible = "wd,mbl-gpio", @@ -413,8 +394,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, }; -- cgit v1.2.3 From 8f9ef2d9ab22f4d9e1047661994bc608e98fd467 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Tue, 27 Apr 2021 22:23:06 +0200 Subject: gpio: generic: sync with upstream Linux gpio-mmio driver The gpio-mmio driver in Linux v5.12 has evolved quite a bit since the last sync. It now supports big endian byte order, 64-bit registers as well as controllers that have both a dirin and dirout register. The latter is particularly interesting, because it's required for the SiFive GPIO controller ported in a later patch. This commit also touches gpio-mpc8xxx used on the LS1046A. Because bit and byte endianness can now be configured separately, the driver needs adjustment. We don't seem to support any boards that have the peripheral as little-endian, but this is fixed by this commit. Comparing other bgpio_init users with Linux shows no need for further fixups. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20210427202309.32077-9-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- drivers/gpio/gpio-generic.c | 431 +++++++++++++++++++++++++++++----------- drivers/gpio/gpio-mpc8xxx.c | 6 +- include/io.h | 2 + include/linux/basic_mmio_gpio.h | 14 +- 4 files changed, 337 insertions(+), 116 deletions(-) diff --git a/drivers/gpio/gpio-generic.c b/drivers/gpio/gpio-generic.c index f381ddbf4e..713085267a 100644 --- a/drivers/gpio/gpio-generic.c +++ b/drivers/gpio/gpio-generic.c @@ -7,63 +7,117 @@ */ #include -#include #include +#include +#include +#include +#include +#include #include -#include +#include +#include #include +#include +#include +#include +#include +#include -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) +{ + iowrite32be(data, reg); +} + +static unsigned long bgpio_read32be(void __iomem *reg) +{ + return ioread32be(reg); +} + +static unsigned long bgpio_line2mask(struct bgpio_chip *bgc, unsigned int line) { - return 1 << pin; + if (bgc->be_bits) + return BIT(bgc->bits - 1 - line); + return BIT(line); } -static unsigned int bgpio_pin2mask_be(struct bgpio_chip *bgc, unsigned int pin) +static int bgpio_get_set(struct gpio_chip *gc, unsigned int gpio) { - return 1 << (bgc->bits - 1 - pin); + 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; - - *err = 0; + resource_size_t sz; - 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[]; - ret = request_iomem_region(dev_name(dev), r->start, r->end); - if (IS_ERR(ret)) { - *err = PTR_ERR(ret); +static struct bgpio_pdata *bgpio_parse_dt(struct device_d *dev, + unsigned long *flags) +{ + struct bgpio_pdata *pdata; + + 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,34 +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; - 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); + + r = dev_request_mem_resource_by_name(dev, "dat"); + if (!r) + return -EINVAL; sz = resource_size(r); - dat = bgpio_map(dev, "dat", sz, &err); - if (!dat) - return err ? err : -EINVAL; + dat = bgpio_map(dev, "dat", sz); + if (IS_ERR(dat)) + return PTR_ERR(dat); - set = bgpio_map(dev, "set", sz, &err); - if (err) - return err; + set = bgpio_map(dev, "set", sz); + if (IS_ERR(set)) + return PTR_ERR(set); - clr = bgpio_map(dev, "clr", sz, &err); - if (err) - return err; - - 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)); @@ -372,6 +569,12 @@ static int bgpio_dev_probe(struct device_d *dev) if (err) return err; + 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; return gpiochip_add(&bgc->gc); @@ -384,9 +587,15 @@ static void bgpio_dev_remove(struct device_d *dev) bgpio_remove(bgc); } -static struct of_device_id __maybe_unused bgpio_of_match[] = { +static const struct of_device_id bgpio_of_match[] = { { .compatible = "wd,mbl-gpio", + }, + { + .compatible = "brcm,bcm6345-gpio" + }, + { + .compatible = "ni,169445-nand-gpio" }, { /* sentinel */ } 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/include/io.h b/include/io.h index 9130020722..79d8b56c4e 100644 --- a/include/io.h +++ b/include/io.h @@ -4,4 +4,6 @@ #include +#define IOMEM_ERR_PTR(err) (__force void __iomem *)ERR_PTR(err) + #endif /* __IO_H */ diff --git a/include/linux/basic_mmio_gpio.h b/include/linux/basic_mmio_gpio.h index e927194b51..34e2f470fb 100644 --- a/include/linux/basic_mmio_gpio.h +++ b/include/linux/basic_mmio_gpio.h @@ -27,13 +27,17 @@ struct bgpio_chip { struct gpio_chip gc; struct gpio_ops ops; - unsigned int (*read_reg)(void __iomem *reg); - void (*write_reg)(void __iomem *reg, unsigned int data); + unsigned long (*read_reg)(void __iomem *reg); + void (*write_reg)(void __iomem *reg, unsigned long data); void __iomem *reg_dat; void __iomem *reg_set; void __iomem *reg_clr; - void __iomem *reg_dir; + void __iomem *reg_dir_out; + void __iomem *reg_dir_in; + + bool dir_unreadable; + bool be_bits; /* Number of bits (GPIOs): * 8. */ int bits; @@ -65,5 +69,9 @@ void bgpio_remove(struct bgpio_chip *bgc); #define BGPIOF_BIG_ENDIAN BIT(0) #define BGPIOF_UNREADABLE_REG_SET BIT(1) /* reg_set is unreadable */ #define BGPIOF_UNREADABLE_REG_DIR BIT(2) /* reg_dir is unreadable */ +#define BGPIOF_BIG_ENDIAN_BYTE_ORDER BIT(3) +#define BGPIOF_READ_OUTPUT_REG_SET BIT(4) /* reg_set stores output value */ +#define BGPIOF_NO_OUTPUT BIT(5) /* only input */ +#define BGPIOF_NO_SET_ON_INPUT BIT(6) #endif /* __BASIC_MMIO_GPIO_H */ -- cgit v1.2.3 From 109a9487f5565e798129e69b6a8fc53b083be470 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Tue, 27 Apr 2021 22:23:07 +0200 Subject: gpio: add SiFive GPIO controller support The SiFive GPIO controller is a straight forward generic gpio-mmio controller. Only difference is that the number of GPIOs is described by the number of interrupts in the device tree. Import the Linux v5.12 driver to support it. Tested with gpio-restart on qemu-system-riscv64 -M sifive_u. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20210427202309.32077-10-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- drivers/gpio/Kconfig | 7 ++++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-sifive.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 drivers/gpio/gpio-sifive.c 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-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 +#include +#include +#include + +#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); -- cgit v1.2.3 From 9174376cd08e1e3e638cd852525cc529d25fde47 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Tue, 27 Apr 2021 22:23:09 +0200 Subject: RISC-V: sifive: add HiFive board support With the recently added SiFive support, we now have enough functionality to boot a HiFive board to shell: qemu-system-riscv64 -M sifive_u serial_stdio \ -kernel./images/barebox-hifive-unleashed.img Some more drivers need to be ported for this to be useful: - sifive,spi0 needed for talking to SD-Card - clocksource The riscv-timer seems to be 10x too fast Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20210427202309.32077-12-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- arch/riscv/Kconfig.socs | 19 +++++ arch/riscv/boards/Makefile | 1 + arch/riscv/boards/hifive/Makefile | 3 + arch/riscv/boards/hifive/lowlevel.c | 25 +++++++ arch/riscv/configs/sifive_defconfig | 128 ++++++++++++++++++++++++++++++++ arch/riscv/dts/Makefile | 2 + arch/riscv/dts/hifive-unleashed-a00.dts | 3 + arch/riscv/dts/hifive-unmatched-a00.dts | 3 + arch/riscv/include/asm/debug_ll.h | 14 ++++ common/Kconfig | 4 + images/Makefile.riscv | 5 ++ 11 files changed, 207 insertions(+) create mode 100644 arch/riscv/boards/hifive/Makefile create mode 100644 arch/riscv/boards/hifive/lowlevel.c create mode 100644 arch/riscv/configs/sifive_defconfig create mode 100644 arch/riscv/dts/hifive-unleashed-a00.dts create mode 100644 arch/riscv/dts/hifive-unmatched-a00.dts diff --git a/arch/riscv/Kconfig.socs b/arch/riscv/Kconfig.socs index ea5ae0a6e9..c6875738d0 100644 --- a/arch/riscv/Kconfig.socs +++ b/arch/riscv/Kconfig.socs @@ -21,4 +21,23 @@ config SOC_VIRT Generates an image tht can be be booted by QEMU. The image is called barebox-dt-2nd.img +config SOC_SIFIVE + bool "SiFive SoCs" + select CLK_SIFIVE + select CLK_SIFIVE_PRCI + select RISCV_TIMER + select HAS_MACB + select HAS_ASM_DEBUG_LL + help + This enables support for SiFive SoC platform hardware. + +if SOC_SIFIVE + +config BOARD_HIFIVE + bool "HiFive" + depends on ARCH_RV64I + select USE_COMPRESSED_DTB + +endif + endmenu diff --git a/arch/riscv/boards/Makefile b/arch/riscv/boards/Makefile index 2ce9af41e0..99f22f32b4 100644 --- a/arch/riscv/boards/Makefile +++ b/arch/riscv/boards/Makefile @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_BOARD_ERIZO_GENERIC) += erizo/ +obj-$(CONFIG_BOARD_HIFIVE) += hifive/ diff --git a/arch/riscv/boards/hifive/Makefile b/arch/riscv/boards/hifive/Makefile new file mode 100644 index 0000000000..3d217ffe0b --- /dev/null +++ b/arch/riscv/boards/hifive/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +pbl-y += lowlevel.o diff --git a/arch/riscv/boards/hifive/lowlevel.c b/arch/riscv/boards/hifive/lowlevel.c new file mode 100644 index 0000000000..1de13cac16 --- /dev/null +++ b/arch/riscv/boards/hifive/lowlevel.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include + +ENTRY_FUNCTION(start_hifive_unmatched, a0, a1, a2) +{ + extern char __dtb_z_hifive_unmatched_a00_start[]; + + putc_ll('>'); + + barebox_riscv_entry(0x80000000, SZ_128M, + __dtb_z_hifive_unmatched_a00_start + get_runtime_offset()); +} + +ENTRY_FUNCTION(start_hifive_unleashed, a0, a1, a2) +{ + extern char __dtb_z_hifive_unleashed_a00_start[]; + + putc_ll('>'); + + barebox_riscv_entry(0x80000000, SZ_128M, + __dtb_z_hifive_unleashed_a00_start + get_runtime_offset()); +} diff --git a/arch/riscv/configs/sifive_defconfig b/arch/riscv/configs/sifive_defconfig new file mode 100644 index 0000000000..59cfebf194 --- /dev/null +++ b/arch/riscv/configs/sifive_defconfig @@ -0,0 +1,128 @@ +CONFIG_ARCH_RV64I=y +CONFIG_SOC_SIFIVE=y +CONFIG_BOARD_HIFIVE=y +CONFIG_BOARD_RISCV_GENERIC_DT=y +CONFIG_RISCV_OPTIMZED_STRING_FUNCTIONS=y +CONFIG_STACK_SIZE=0x20000 +CONFIG_MALLOC_SIZE=0x0 +CONFIG_MALLOC_TLSF=y +CONFIG_KALLSYMS=y +CONFIG_RELOCATABLE=y +CONFIG_PANIC_HANG=y +CONFIG_HUSH_FANCY_PROMPT=y +CONFIG_CMDLINE_EDITING=y +CONFIG_AUTO_COMPLETE=y +CONFIG_MENU=y +CONFIG_IMD_TARGET=y +CONFIG_CONSOLE_ALLOW_COLOR=y +CONFIG_PBL_CONSOLE=y +CONFIG_PARTITION_DISK_EFI=y +CONFIG_DEFAULT_ENVIRONMENT_GENERIC_NEW=y +CONFIG_BAREBOXENV_TARGET=y +CONFIG_BAREBOXCRC32_TARGET=y +CONFIG_STATE=y +CONFIG_STATE_CRYPTO=y +CONFIG_BOOTCHOOSER=y +CONFIG_RESET_SOURCE=y +CONFIG_MACHINE_ID=y +CONFIG_CMD_DMESG=y +CONFIG_LONGHELP=y +CONFIG_CMD_IOMEM=y +CONFIG_CMD_IMD=y +CONFIG_CMD_MEMINFO=y +CONFIG_CMD_POLLER=y +CONFIG_CMD_SLICE=y +CONFIG_CMD_GO=y +CONFIG_CMD_LOADY=y +CONFIG_CMD_RESET=y +CONFIG_CMD_BOOTCHOOSER=y +CONFIG_CMD_EXPORT=y +CONFIG_CMD_PRINTENV=y +CONFIG_CMD_MAGICVAR=y +CONFIG_CMD_MAGICVAR_HELP=y +CONFIG_CMD_SAVEENV=y +CONFIG_CMD_CMP=y +CONFIG_CMD_FILETYPE=y +CONFIG_CMD_LN=y +CONFIG_CMD_MD5SUM=y +CONFIG_CMD_SHA1SUM=y +CONFIG_CMD_SHA256SUM=y +CONFIG_CMD_MSLEEP=y +CONFIG_CMD_SLEEP=y +CONFIG_CMD_DHCP=y +CONFIG_CMD_MIITOOL=y +CONFIG_CMD_PING=y +CONFIG_CMD_EDIT=y +CONFIG_CMD_SPLASH=y +CONFIG_CMD_FBTEST=y +CONFIG_CMD_READLINE=y +CONFIG_CMD_TIMEOUT=y +CONFIG_CMD_MEMTEST=y +CONFIG_CMD_MM=y +CONFIG_CMD_CLK=y +CONFIG_CMD_DETECT=y +CONFIG_CMD_FLASH=y +CONFIG_CMD_GPIO=y +CONFIG_CMD_I2C=y +CONFIG_CMD_POWEROFF=y +CONFIG_CMD_SPI=y +CONFIG_CMD_2048=y +CONFIG_CMD_BAREBOX_UPDATE=y +CONFIG_CMD_OF_DIFF=y +CONFIG_CMD_OF_NODE=y +CONFIG_CMD_OF_PROPERTY=y +CONFIG_CMD_OF_DISPLAY_TIMINGS=y +CONFIG_CMD_OF_FIXUP_STATUS=y +CONFIG_CMD_OF_OVERLAY=y +CONFIG_CMD_OFTREE=y +CONFIG_CMD_TIME=y +CONFIG_CMD_DHRYSTONE=y +CONFIG_NET=y +CONFIG_NET_NFS=y +CONFIG_NET_FASTBOOT=y +CONFIG_DRIVER_SERIAL_NS16550=y +CONFIG_VIRTIO_CONSOLE=y +CONFIG_SERIAL_SIFIVE=y +CONFIG_DRIVER_NET_MACB=y +CONFIG_DRIVER_SPI_GPIO=y +CONFIG_I2C=y +CONFIG_I2C_GPIO=y +CONFIG_MTD=y +# CONFIG_MTD_OOB_DEVICE is not set +CONFIG_MTD_CONCAT=y +CONFIG_MTD_M25P80=y +CONFIG_DRIVER_CFI=y +CONFIG_DRIVER_CFI_BANK_WIDTH_8=y +CONFIG_DISK=y +CONFIG_DISK_WRITE=y +CONFIG_VIRTIO_BLK=y +CONFIG_VIDEO=y +CONFIG_FRAMEBUFFER_CONSOLE=y +CONFIG_DRIVER_VIDEO_SIMPLEFB_CLIENT=y +CONFIG_CLOCKSOURCE_DUMMY_RATE=60000 +CONFIG_EEPROM_AT24=y +CONFIG_HWRNG=y +CONFIG_HW_RANDOM_VIRTIO=y +CONFIG_GPIO_SIFIVE=y +# CONFIG_PINCTRL is not set +CONFIG_SYSCON_REBOOT_MODE=y +CONFIG_POWER_RESET_SYSCON=y +CONFIG_POWER_RESET_SYSCON_POWEROFF=y +CONFIG_POWER_RESET_GPIO_RESTART=y +CONFIG_VIRTIO_MMIO=y +CONFIG_FS_EXT4=y +CONFIG_FS_TFTP=y +CONFIG_FS_NFS=y +CONFIG_FS_FAT=y +CONFIG_FS_FAT_WRITE=y +CONFIG_FS_FAT_LFN=y +CONFIG_FS_UIMAGEFS=y +CONFIG_FS_PSTORE=y +CONFIG_FS_SQUASHFS=y +CONFIG_ZLIB=y +CONFIG_BZLIB=y +CONFIG_LZ4_DECOMPRESS=y +CONFIG_ZSTD_DECOMPRESS=y +CONFIG_XZ_DECOMPRESS=y +CONFIG_BASE64=y +CONFIG_DIGEST_CRC32_GENERIC=y diff --git a/arch/riscv/dts/Makefile b/arch/riscv/dts/Makefile index 4041c34e0c..17fdc9445b 100644 --- a/arch/riscv/dts/Makefile +++ b/arch/riscv/dts/Makefile @@ -5,5 +5,7 @@ obj- += dummy.o pbl-$(CONFIG_BOARD_ERIZO_GENERIC) += erizo-generic.dtb.o +pbl-$(CONFIG_BOARD_HIFIVE) += hifive-unmatched-a00.dtb.o \ + hifive-unleashed-a00.dtb.o clean-files := *.dtb *.dtb.S .*.dtc .*.pre .*.dts diff --git a/arch/riscv/dts/hifive-unleashed-a00.dts b/arch/riscv/dts/hifive-unleashed-a00.dts new file mode 100644 index 0000000000..65694bfd24 --- /dev/null +++ b/arch/riscv/dts/hifive-unleashed-a00.dts @@ -0,0 +1,3 @@ +/* SPDX-License-Identifier: GPL-2.0 OR X11 */ + +#include diff --git a/arch/riscv/dts/hifive-unmatched-a00.dts b/arch/riscv/dts/hifive-unmatched-a00.dts new file mode 100644 index 0000000000..b8793e9105 --- /dev/null +++ b/arch/riscv/dts/hifive-unmatched-a00.dts @@ -0,0 +1,3 @@ +/* SPDX-License-Identifier: GPL-2.0 OR X11 */ + +#include diff --git a/arch/riscv/include/asm/debug_ll.h b/arch/riscv/include/asm/debug_ll.h index 598921910a..6904460af9 100644 --- a/arch/riscv/include/asm/debug_ll.h +++ b/arch/riscv/include/asm/debug_ll.h @@ -24,6 +24,20 @@ #include +#elif defined CONFIG_DEBUG_SIFIVE + +#include + +static inline void PUTC_LL(char ch) +{ + void __iomem *uart0 = IOMEM(0x10010000); + + while (readl(uart0) & 0x80000000) + ; + + writel(ch, uart0); +} + #endif #ifndef debug_ll_init diff --git a/common/Kconfig b/common/Kconfig index 6d7a1c6b04..a883f2f69c 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -1347,6 +1347,10 @@ config DEBUG_ERIZO bool "Erizo ns16550 port" depends on SOC_ERIZO +config DEBUG_SIFIVE + bool "SiFive serial0 port" + depends on SOC_SIFIVE + endchoice config DEBUG_IMX_UART_PORT diff --git a/images/Makefile.riscv b/images/Makefile.riscv index 463c6ce440..c44c683431 100644 --- a/images/Makefile.riscv +++ b/images/Makefile.riscv @@ -10,3 +10,8 @@ $(obj)/%.nmon: $(obj)/%.img FORCE pblb-$(CONFIG_BOARD_ERIZO_GENERIC) += start_erizo_generic FILE_barebox-erizo-generic.img = start_erizo_generic.pblb image-$(CONFIG_BOARD_ERIZO_GENERIC) += barebox-erizo-generic.img barebox-erizo-generic.nmon + +pblb-$(CONFIG_BOARD_HIFIVE) += start_hifive_unmatched start_hifive_unleashed +FILE_barebox-hifive-unmatched.img = start_hifive_unmatched.pblb +FILE_barebox-hifive-unleashed.img = start_hifive_unleashed.pblb +image-$(CONFIG_BOARD_HIFIVE) += barebox-hifive-unmatched.img barebox-hifive-unleashed.img -- cgit v1.2.3 From 7000b90984a0ca50443f1bd84c058e129c05f229 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Tue, 4 May 2021 12:45:11 +0200 Subject: bootm: move ARM64 Linux image parsing to common directory Linux on RISC-V adopts the same structure as on ARM64 for both 32- and 64-bit kernel images and it's likely future architectures will as well. In preparation for adding RISC-V Linux boot support, move the bulk of the code to a common location for reusability. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20210504104513.2640-1-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- arch/arm/lib64/armlinux.c | 74 ++++------------------------------------------- common/Makefile | 2 +- common/booti.c | 66 ++++++++++++++++++++++++++++++++++++++++++ include/bootm.h | 2 ++ 4 files changed, 74 insertions(+), 70 deletions(-) create mode 100644 common/booti.c diff --git a/arch/arm/lib64/armlinux.c b/arch/arm/lib64/armlinux.c index a5f122edcd..0ba4d30b8e 100644 --- a/arch/arm/lib64/armlinux.c +++ b/arch/arm/lib64/armlinux.c @@ -1,90 +1,26 @@ // SPDX-License-Identifier: GPL-2.0-only // SPDX-FileCopyrightText: 2018 Sascha Hauer -#include #include -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include -#include -#include -#include -#include -#include -#include static int do_bootm_linux(struct image_data *data) { - const void *kernel_header = - data->os_fit ? data->fit_kernel : data->os_header; void (*fn)(unsigned long dtb, unsigned long x1, unsigned long x2, unsigned long x3); - resource_size_t start, end; - unsigned long text_offset, image_size, devicetree, kernel; - unsigned long image_end; - int ret; - void *fdt; - - text_offset = le64_to_cpup(kernel_header + 8); - image_size = le64_to_cpup(kernel_header+ 16); - - ret = memory_bank_first_find_space(&start, &end); - if (ret) - goto out; - - kernel = ALIGN(start, SZ_2M) + text_offset; - - ret = bootm_load_os(data, kernel); - if (ret) - goto out; - - image_end = PAGE_ALIGN(kernel + image_size); - - if (bootm_has_initrd(data)) { - ret = bootm_load_initrd(data, image_end); - if (ret) - return ret; - - image_end += resource_size(data->initrd_res); - image_end = PAGE_ALIGN(image_end); - } + phys_addr_t devicetree; - devicetree = image_end; - - fdt = bootm_get_devicetree(data); - if (IS_ERR(fdt)) { - ret = PTR_ERR(fdt); - goto out; - } - - ret = bootm_load_devicetree(data, fdt, devicetree); - - free(fdt); - - if (ret) - goto out; - - printf("Loaded kernel to 0x%08lx, devicetree at 0x%08lx\n", - kernel, devicetree); + fn = booti_load_image(data, &devicetree); + if (IS_ERR(fn)) + return PTR_ERR(fn); shutdown_barebox(); - fn = (void *)kernel; - fn(devicetree, 0, 0, 0); - ret = -EINVAL; - -out: - return ret; + return -EINVAL; } static struct image_handler aarch64_linux_handler = { diff --git a/common/Makefile b/common/Makefile index c0b45d263e..2c679090a0 100644 --- a/common/Makefile +++ b/common/Makefile @@ -20,7 +20,7 @@ obj-$(CONFIG_BAREBOX_UPDATE) += bbu.o obj-$(CONFIG_BINFMT) += binfmt.o obj-$(CONFIG_BLOCK) += block.o obj-$(CONFIG_BLSPEC) += blspec.o -obj-$(CONFIG_BOOTM) += bootm.o +obj-$(CONFIG_BOOTM) += bootm.o booti.o obj-$(CONFIG_CMD_LOADS) += s_record.o obj-$(CONFIG_CMD_MEMTEST) += memtest.o obj-$(CONFIG_COMMAND_SUPPORT) += command.o diff --git a/common/booti.c b/common/booti.c new file mode 100644 index 0000000000..a2d63d8c31 --- /dev/null +++ b/common/booti.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2018 Sascha Hauer + +#include +#include +#include +#include + +void *booti_load_image(struct image_data *data, phys_addr_t *oftree) +{ + const void *kernel_header = + data->os_fit ? data->fit_kernel : data->os_header; + resource_size_t start, end; + unsigned long text_offset, image_size, devicetree, kernel; + unsigned long image_end; + int ret; + void *fdt; + + text_offset = le64_to_cpup(kernel_header + 8); + image_size = le64_to_cpup(kernel_header + 16); + + ret = memory_bank_first_find_space(&start, &end); + if (ret) + return ERR_PTR(ret); + + kernel = ALIGN(start, SZ_2M) + text_offset; + + ret = bootm_load_os(data, kernel); + if (ret) + return ERR_PTR(ret); + + image_end = PAGE_ALIGN(kernel + image_size); + + if (oftree) { + if (bootm_has_initrd(data)) { + ret = bootm_load_initrd(data, image_end); + if (ret) + return ERR_PTR(ret); + + image_end += resource_size(data->initrd_res); + image_end = PAGE_ALIGN(image_end); + } + + devicetree = image_end; + + fdt = bootm_get_devicetree(data); + if (IS_ERR(fdt)) + return fdt; + + ret = bootm_load_devicetree(data, fdt, devicetree); + + free(fdt); + + if (ret) + return ERR_PTR(ret); + + *oftree = devicetree; + } + + printf("Loaded kernel to 0x%08lx", kernel); + if (oftree) + printf(", devicetree at 0x%08lx", devicetree); + printf("\n"); + + return (void *)kernel; +} diff --git a/include/bootm.h b/include/bootm.h index 51e9b3d71a..655c5152d9 100644 --- a/include/bootm.h +++ b/include/bootm.h @@ -148,4 +148,6 @@ enum bootm_verify bootm_get_verify_mode(void); #define UIMAGE_SOME_ADDRESS (UIMAGE_INVALID_ADDRESS - 1) +void *booti_load_image(struct image_data *data, phys_addr_t *oftree); + #endif /* __BOOTM_H */ -- cgit v1.2.3 From 4a0edad5d6b057fc117ab01727cc4ab682f36d4d Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Tue, 4 May 2021 12:45:12 +0200 Subject: RISC-V: asm: barebox-riscv-head: use load-offset of 0 Incoming RISC-V bootm implementation will use the same bootm handler for booting both kernel and barebox. For this to work, the load offset in the header needs to make sense. As non-generic DT barebox images have enough knowledge about the platform to know where to place the stack, they don't require a load offset, thus set it to zero. Signed-off-by: Ahmad Fatoum Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20210504104513.2640-2-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- arch/riscv/include/asm/barebox-riscv-head.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/riscv/include/asm/barebox-riscv-head.h b/arch/riscv/include/asm/barebox-riscv-head.h index f681ec8bce..a4c33472cd 100644 --- a/arch/riscv/include/asm/barebox-riscv-head.h +++ b/arch/riscv/include/asm/barebox-riscv-head.h @@ -30,7 +30,7 @@ #ifndef __barebox_riscv_head #define __barebox_riscv_head() \ - __barebox_riscv_header("nop", 0x55555555FFFFFFFF, 0x0, "barebox", "RSCV") + __barebox_riscv_header("nop", 0x0, 0x0, "barebox", "RSCV") #endif #endif /* __ASM_RISCV_HEAD_H */ -- cgit v1.2.3 From b383d7739c355fee9f81986e6e8d030185373ca5 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Tue, 4 May 2021 12:45:13 +0200 Subject: RISC-V: add Linux kernel boot support The Linux kernel RISC-V header has the same structure as on ARM64. The barebox header for the architecture also follows the same structure. Add the architecture-specific glue for barebox to be able to boot both RISC-V Linux and barebox. Signed-off-by: Ahmad Fatoum Link: https://lore.barebox.org/20210504104513.2640-3-a.fatoum@pengutronix.de Signed-off-by: Sascha Hauer --- arch/riscv/lib/Makefile | 1 + arch/riscv/lib/bootm.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 arch/riscv/lib/bootm.c diff --git a/arch/riscv/lib/Makefile b/arch/riscv/lib/Makefile index 49750d576a..a399de7399 100644 --- a/arch/riscv/lib/Makefile +++ b/arch/riscv/lib/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_HAS_ARCH_SJLJ) += setjmp.o longjmp.o obj-$(CONFIG_RISCV_OPTIMZED_STRING_FUNCTIONS) += memcpy.o memset.o memmove.o obj-$(CONFIG_RISCV_SBI) += sbi.o obj-$(CONFIG_CMD_RISCV_CPUINFO) += cpuinfo.o +obj-$(CONFIG_BOOTM) += bootm.o diff --git a/arch/riscv/lib/bootm.c b/arch/riscv/lib/bootm.c new file mode 100644 index 0000000000..b3e41de4a8 --- /dev/null +++ b/arch/riscv/lib/bootm.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2018 Sascha Hauer + +#include +#include + +static int do_bootm_linux(struct image_data *data) +{ + void (*fn)(unsigned long a0, unsigned long dtb, unsigned long a2); + phys_addr_t devicetree; + + fn = booti_load_image(data, &devicetree); + if (IS_ERR(fn)) + return PTR_ERR(fn); + + shutdown_barebox(); + + fn(0, devicetree, 0); + + return -EINVAL; +} + +static struct image_handler riscv_linux_handler = { + .name = "RISC-V Linux image", + .bootm = do_bootm_linux, + .filetype = filetype_riscv_linux_image, +}; + +static struct image_handler riscv_fit_handler = { + .name = "FIT image", + .bootm = do_bootm_linux, + .filetype = filetype_oftree, +}; + +static struct image_handler riscv_barebox_handler = { + .name = "RISC-V barebox image", + .bootm = do_bootm_linux, + .filetype = filetype_riscv_barebox_image, +}; + +static int riscv_register_image_handler(void) +{ + register_image_handler(&riscv_linux_handler); + register_image_handler(&riscv_barebox_handler); + + if (IS_ENABLED(CONFIG_FITIMAGE)) + register_image_handler(&riscv_fit_handler); + + return 0; +} +late_initcall(riscv_register_image_handler); -- cgit v1.2.3