From b44f4ab8a61fb90c91851f121be96cc02c258a65 Mon Sep 17 00:00:00 2001 From: Axel Heider Date: Tue, 9 May 2023 17:32:59 +0200 Subject: [PATCH] wip JH7110 (VisionFive, Star64) --- .../star64/platsupport/plat/serial.h | 46 +++++++ libplatsupport/src/plat/star64/chardev.c | 48 +++++++ libplatsupport/src/plat/star64/serial.c | 126 ++++++++++++++++++ 3 files changed, 220 insertions(+) create mode 100644 libplatsupport/plat_include/star64/platsupport/plat/serial.h create mode 100644 libplatsupport/src/plat/star64/chardev.c create mode 100644 libplatsupport/src/plat/star64/serial.c diff --git a/libplatsupport/plat_include/star64/platsupport/plat/serial.h b/libplatsupport/plat_include/star64/platsupport/plat/serial.h new file mode 100644 index 000000000..a815367c5 --- /dev/null +++ b/libplatsupport/plat_include/star64/platsupport/plat/serial.h @@ -0,0 +1,46 @@ +/* + * Copyright 2023, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + + +#define UART0_PADDR 0x10000000 +#define UART1_PADDR 0x10010000 +#define UART2_PADDR 0x10020000 +#define UART3_PADDR 0x12000000 +#define UART4_PADDR 0x12010000 +#define UART5_PADDR 0x12020000 + +#define UART0_IRQ 32 +#define UART1_IRQ 33 +#define UART2_IRQ 34 +#define UART3_IRQ 45 +#define UART4_IRQ 46 +#define UART5_IRQ 47 + +enum chardev_id { + UART0, + UART1, + UART2, + UART3, + UART4, + UART5, + /* Aliases */ + PS_SERIAL0 = UART0, + PS_SERIAL1 = UART1, + PS_SERIAL2 = UART2, + PS_SERIAL3 = UART3, + PS_SERIAL4 = UART4, + PS_SERIAL5 = UART5, + /* defaults */ + PS_SERIAL_DEFAULT = PS_SERIAL0 +}; + + +/* The default serial device corresponds to the UART available via the GPIO + * pins of the Star64 Model-A. */ +#define DEFAULT_SERIAL_PADDR UART0_PADDR +#define DEFAULT_SERIAL_INTERRUPT UART0_IRQ diff --git a/libplatsupport/src/plat/star64/chardev.c b/libplatsupport/src/plat/star64/chardev.c new file mode 100644 index 000000000..0b78dfdf1 --- /dev/null +++ b/libplatsupport/src/plat/star64/chardev.c @@ -0,0 +1,48 @@ +/* + * Copyright 2023, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "../../chardev.h" +#include "../../common.h" +#include + +static const int uart0_irqs[] = {UART0_IRQ, -1}; +static const int uart1_irqs[] = {UART1_IRQ, -1}; +static const int uart2_irqs[] = {UART2_IRQ, -1}; +static const int uart3_irqs[] = {UART3_IRQ, -1}; +static const int uart4_irqs[] = {UART4_IRQ, -1}; +static const int uart5_irqs[] = {UART5_IRQ, -1}; + +/* + * Despite each UART being 0x10000 in size (according to the device tree) we + * only need to map in the first page for the driver to functon. + */ +#define UART_DEFN(devid) { \ + .id = UART##devid, \ + .paddr = UART##devid##_PADDR, \ + .size = PAGE_SIZE_4K, \ + .irqs = uart##devid##_irqs, \ + .init_fn = &uart_init \ +} + +const struct dev_defn dev_defn[] = { + UART_DEFN(0), + UART_DEFN(1), + UART_DEFN(2), + UART_DEFN(3), + UART_DEFN(4), + UART_DEFN(5), +}; + +struct ps_chardevice* +ps_cdev_init(enum chardev_id id, const ps_io_ops_t* o, struct ps_chardevice* d) { + unsigned int i; + for (i = 0; i < ARRAY_SIZE(dev_defn); i++) { + if (dev_defn[i].id == id) { + return (dev_defn[i].init_fn(dev_defn + i, o, d)) ? NULL : d; + } + } + return NULL; +} diff --git a/libplatsupport/src/plat/star64/serial.c b/libplatsupport/src/plat/star64/serial.c new file mode 100644 index 000000000..30d119d6e --- /dev/null +++ b/libplatsupport/src/plat/star64/serial.c @@ -0,0 +1,126 @@ +/* + * Copyright 2023, UNSW + * Copyright 2022, HENSOLDT Cyber GmbH + * + * SPDX-License-Identifier: BSD-2-Clause + * + * + * The JH7110 SoC use on the Star64 emulates a 16550 compatible UART, where the + * register width is 32-bit. + */ + +#include +#include +#include + +#define NS16550_WITH_REG32 +#include + +#include "../../chardev.h" + +static ns16550_regs_t *uart_get_regs(ps_chardevice_t *dev) +{ + return (ns16550_regs_t *)(dev->vaddr); +} + +/* + ******************************************************************************* + * UART access API + ******************************************************************************* + */ + +int uart_putchar(ps_chardevice_t *dev, int c) +{ + ns16550_regs_t *regs = uart_get_regs(dev); + + /* There is no way to check for "TX ready", the only thing we have is a + * check for "TX FIFO empty". This is not optimal, as we might wait here + * even if there is space in the FIFO. Seems the 16550 was built based on + * the idea that software keeps track of the FIFO usage. A driver would + * know how much space is left in the FIFO, so it can write new data + * either immediately or buffer it. If the FIFO empty interrupt arrives, + * data can be written from the buffer to fill the FIFO. + * However, since QEMU does not emulate a FIFO, we can just implement a + * simple model here and block - expecting to never block practically. + */ + while (!ns16550_is_tx_empty(regs)) { + /* busy waiting loop */ + } + + /* Extract the byte to send, drop any flags. */ + uint8_t byte = (uint8_t)c; + + /* If SERIAL_AUTO_CR is enabled, a CR is sent before any LF. */ + if ((byte == '\n') && (dev->flags & SERIAL_AUTO_CR)) { + ns16550_tx_byte(regs, '\r'); + /* Since we have blocked until the FIFO is empty, we don't have to wait + * here. + */ + } + + ns16550_tx_byte(regs, byte); + + return byte; +} + +int uart_getchar(ps_chardevice_t *dev) +{ + ns16550_regs_t *regs = uart_get_regs(dev); + return ns16550_get_char_or_EOF(regs); +} + +static void uart_handle_irq(ps_chardevice_t *dev) +{ + /* No IRQ handling required here. */ +} + +static void ns16550_init(ns16550_regs_t *regs) +{ + /* disable interrupts */ + regs->dlm_ier = 0; + + + regs->lcr = NS16550_LCR_DLAB; /* baud rate divisor setup */ + // uint16_t clk_divisor = ??? + // regs->dlm_ier = (clk_divisor >> 8) & 0xFF; + // regs->rbr_dll_thr = clk_divisor & 0xFF; + regs->lcr = 0x03; /* set 8N1, clear DLAB to end baud rate divisor setup */ + + /* enable and reset FIFOs, interrupt for each byte */ + regs->iir_fcr = NS16550_FCR_ENABLE_FIFOS + | NS16550_FCR_RESET_RX_FIFO + | NS16550_FCR_RESET_TX_FIFO + | NS16550_FCR_TRIGGER_1; + + /* enable RX interrupts */ + regs->dlm_ier = NS16550_IER_ERBFI; +} + +int uart_init(const struct dev_defn *defn, + const ps_io_ops_t *ops, + ps_chardevice_t *dev) +{ + memset(dev, 0, sizeof(*dev)); + + /* Map device. */ + void *vaddr = chardev_map(defn, ops); + if (vaddr == NULL) { + return -1; + } + + /* Set up all the device properties. */ + dev->id = defn->id; + dev->vaddr = (void *)vaddr; + dev->read = &uart_read; + dev->write = &uart_write; + dev->handle_irq = &uart_handle_irq; + dev->irqs = defn->irqs; + dev->ioops = *ops; + dev->flags = SERIAL_AUTO_CR; + + /* Initialize the device. */ + ns16550_regs_t *regs = uart_get_regs(dev); + ns16550_init(regs); + + return 0; +}