From 5d029123e474e9739e95f00fa787833393315dd8 Mon Sep 17 00:00:00 2001 From: Jimmy Brush Date: Tue, 25 Apr 2023 22:48:10 +1000 Subject: [PATCH] Add pine64 star64 platform This adds a timer and serial driver to libplatsupport. Co-developed-by: Ivan-Velickovic Signed-off-by: Jimmy Brush --- .../star64/platsupport/plat/serial.h | 39 +++ .../star64/platsupport/plat/timer.h | 71 +++++ libplatsupport/src/plat/star64/chardev.c | 49 ++++ libplatsupport/src/plat/star64/ltimer.c | 265 ++++++++++++++++++ libplatsupport/src/plat/star64/serial.c | 68 +++++ libplatsupport/src/plat/star64/timer.c | 149 ++++++++++ 6 files changed, 641 insertions(+) create mode 100644 libplatsupport/plat_include/star64/platsupport/plat/serial.h create mode 100644 libplatsupport/plat_include/star64/platsupport/plat/timer.h create mode 100644 libplatsupport/src/plat/star64/chardev.c create mode 100644 libplatsupport/src/plat/star64/ltimer.c create mode 100644 libplatsupport/src/plat/star64/serial.c create mode 100644 libplatsupport/src/plat/star64/timer.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..5c23b9cc4 --- /dev/null +++ b/libplatsupport/plat_include/star64/platsupport/plat/serial.h @@ -0,0 +1,39 @@ +/* + * Copyright 2023, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once +#include + +/* The StarFive JH7110 SoC contains five 8250 compatible UARTs. */ + +enum chardev_id { + UART0, + UART1, + UART2, + UART3, + UART4, + UART5, + PS_SERIAL_DEFAULT = UART0 +}; + +#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 + +/* 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/plat_include/star64/platsupport/plat/timer.h b/libplatsupport/plat_include/star64/platsupport/plat/timer.h new file mode 100644 index 000000000..d6cef86eb --- /dev/null +++ b/libplatsupport/plat_include/star64/platsupport/plat/timer.h @@ -0,0 +1,71 @@ +/* + * Copyright 2023, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ +#pragma once + +#include +#include + +/* + * The JH7110 SoC contains a timer with four 32-bit counters. Each one of these + * counters is referred to as a "channel". + */ + +/* Channel information */ +#define STARFIVE_TIMER_NUM_CHANNELS 4 +#define STARFIVE_TIMER_CHANNEL_REGISTERS_LEN_IN_BYTES 0x40 +#define STARFIVE_TIMER_CHANNEL_0_IRQ 69 +#define STARFIVE_TIMER_CHANNEL_1_IRQ 70 +#define STARFIVE_TIMER_CHANNEL_2_IRQ 71 +#define STARFIVE_TIMER_CHANNEL_3_IRQ 72 + +/* This information comes from the DTS file */ +#define STARFIVE_TIMER_BASE 0x13050000 +#define STARFIVE_TIMER_REGISTER_WINDOW_LEN_IN_BYTES 0x10000 +#define STARFIVE_TIMER_TICKS_PER_SECOND 0x16e3600 + +#define STARFIVE_TIMER_MAX_TICKS 0xffffffff + +/* Register value constants */ +#define STARFIVE_TIMER_MODE_CONTINUOUS 0 +#define STARFIVE_TIMER_MODE_SINGLE 1 +#define STARFIVE_TIMER_DISABLED 0 +#define STARFIVE_TIMER_ENABLED 1 +#define STARFIVE_TIMER_INTERRUPT_UNMASKED 0 +#define STARFIVE_TIMER_INTERRUPT_MASKED 1 +#define STARFIVE_TIMER_INTCLR_BUSY BIT(1) + +typedef struct { + /* Registers */ + /* this register doesn't seem to do anything */ + uint32_t status; + uint32_t ctrl; + uint32_t load; + uint32_t unknown1; + uint32_t enable; + uint32_t reload; + uint32_t value; + uint32_t unknown2; + uint32_t intclr; + uint32_t intmask; +} starfive_timer_regs_t; + +typedef struct { + volatile starfive_timer_regs_t *regs; + /* + * Stores the number of times the continuous counter timer has elapsed and started over. + * This allows us to count to a higher number than allowed by the hardware. + */ + uint32_t value_h; +} starfive_timer_t; + +void starfive_timer_start(starfive_timer_t *timer); +void starfive_timer_stop(starfive_timer_t *timer); +void starfive_timer_handle_irq(starfive_timer_t *timer); +uint64_t starfive_timer_get_time(starfive_timer_t *timer); +void starfive_timer_reset(starfive_timer_t *timer); +int starfive_timer_set_timeout(starfive_timer_t *timer, uint64_t ns, bool is_periodic); +void starfive_timer_disable_all_channels(void *vaddr); +void starfive_timer_init(starfive_timer_t *timer, void *vaddr, uint64_t channel); diff --git a/libplatsupport/src/plat/star64/chardev.c b/libplatsupport/src/plat/star64/chardev.c new file mode 100644 index 000000000..df48708f4 --- /dev/null +++ b/libplatsupport/src/plat/star64/chardev.c @@ -0,0 +1,49 @@ +/* + * 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 = BIT(12), \ + .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/ltimer.c b/libplatsupport/src/plat/star64/ltimer.c new file mode 100644 index 000000000..5850cb9c5 --- /dev/null +++ b/libplatsupport/src/plat/star64/ltimer.c @@ -0,0 +1,265 @@ +/* + * Copyright 2023, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "../../ltimer.h" + +enum { + COUNTER_TIMER, + TIMEOUT_TIMER, + NUM_TIMERS +}; + +typedef struct { + void *vaddr; + starfive_timer_t timer[NUM_TIMERS]; + irq_id_t irq_id[NUM_TIMERS]; + timer_callback_data_t callback_data[NUM_TIMERS]; + ltimer_callback_fn_t user_callback; + void *user_callback_token; + ps_io_ops_t ops; +} starfive_ltimer_t; + +/* See timer.h for an explanation of the driver. */ + +/* Each channel IRQ is edge-triggered. */ +static ps_irq_t irqs[] = { + { + .type = PS_TRIGGER, + .trigger.number = STARFIVE_TIMER_CHANNEL_0_IRQ, + .trigger.trigger = 1 + }, + { + .type = PS_TRIGGER, + .trigger.number = STARFIVE_TIMER_CHANNEL_1_IRQ, + .trigger.trigger = 1 + }, +}; + +static pmem_region_t pmems[] = { + { + .type = PMEM_TYPE_DEVICE, + .base_addr = STARFIVE_TIMER_BASE, + .length = STARFIVE_TIMER_REGISTER_WINDOW_LEN_IN_BYTES, + }, +}; + +#define NUM_IRQS ARRAY_SIZE(irqs) +#define NUM_PMEMS ARRAY_SIZE(pmems) + +static size_t get_num_irqs(void *data) +{ + return NUM_IRQS; +} + +static int get_nth_irq(void *data, size_t n, ps_irq_t *irq) +{ + assert(n < NUM_IRQS); + assert(irq); + + *irq = irqs[n]; + return 0; +} + +static size_t get_num_pmems(void *data) +{ + return NUM_PMEMS; +} + +static int get_nth_pmem(void *data, size_t n, pmem_region_t *paddr) +{ + assert(n < NUM_PMEMS); + assert(paddr); + + *paddr = pmems[n]; + return 0; +} + +static int ltimer_handle_irq(void *data, ps_irq_t *irq) +{ + assert(data); + assert(irq); + + starfive_ltimer_t *timers = (starfive_ltimer_t *)data; + long irq_number = irq->irq.number; + ltimer_event_t event; + + if (irq_number == irqs[COUNTER_TIMER].irq.number) { + starfive_timer_handle_irq(&timers->timer[COUNTER_TIMER]); + event = LTIMER_OVERFLOW_EVENT; + } else if (irq_number == irqs[TIMEOUT_TIMER].irq.number) { + starfive_timer_handle_irq(&timers->timer[TIMEOUT_TIMER]); + event = LTIMER_TIMEOUT_EVENT; + } else { + ZF_LOGE("Invalid IRQ number %ld received.", irq_number); + return EINVAL; + } + + if (timers->user_callback) { + timers->user_callback(timers->user_callback_token, event); + } + + return 0; +} + +static int get_time(void *data, uint64_t *time) +{ + assert(data); + assert(time); + + starfive_ltimer_t *timers = (starfive_ltimer_t *)data; + *time = starfive_timer_get_time(&timers->timer[COUNTER_TIMER]); + + return 0; +} + +static int set_timeout(void *data, uint64_t ns, timeout_type_t type) +{ + assert(data); + starfive_ltimer_t *timers = (starfive_ltimer_t *)data; + + switch (type) { + case TIMEOUT_ABSOLUTE: { + uint64_t time = starfive_timer_get_time(&timers->timer[COUNTER_TIMER]); + if (time >= ns) { + ZF_LOGE("Requested time %"PRIu64" earlier than current time %"PRIu64, ns, time); + return ETIME; + } + return starfive_timer_set_timeout(&timers->timer[TIMEOUT_TIMER], ns - time, false); + } + case TIMEOUT_RELATIVE: + return starfive_timer_set_timeout(&timers->timer[TIMEOUT_TIMER], ns, false); + case TIMEOUT_PERIODIC: + return starfive_timer_set_timeout(&timers->timer[TIMEOUT_TIMER], ns, true); + } + + return EINVAL; +} + +static int reset(void *data) +{ + assert(data); + starfive_ltimer_t *timers = (starfive_ltimer_t *)data; + + starfive_timer_stop(&timers->timer[COUNTER_TIMER]); + starfive_timer_reset(&timers->timer[COUNTER_TIMER]); + starfive_timer_start(&timers->timer[COUNTER_TIMER]); + + starfive_timer_stop(&timers->timer[TIMEOUT_TIMER]); + starfive_timer_reset(&timers->timer[TIMEOUT_TIMER]); + + return 0; +} + +static void destroy(void *data) +{ + assert(data); + starfive_ltimer_t *timers = (starfive_ltimer_t *)data; + int error; + + starfive_timer_stop(&timers->timer[COUNTER_TIMER]); + starfive_timer_stop(&timers->timer[TIMEOUT_TIMER]); + + ps_pmem_unmap(&timers->ops, pmems[0], timers->vaddr); + + error = ps_irq_unregister(&timers->ops.irq_ops, timers->irq_id[COUNTER_TIMER]); + ZF_LOGE_IF(error, "Failed to uregister counter timer IRQ"); + + error = ps_irq_unregister(&timers->ops.irq_ops, timers->irq_id[TIMEOUT_TIMER]); + ZF_LOGE_IF(error, "Failed to uregister timeout timer IRQ"); + + error = ps_free(&timers->ops.malloc_ops, sizeof(*timers), timers); + ZF_LOGE_IF(error, "Failed to free device struct memory"); +} + +static int register_interrupt(ltimer_t *ltimer, + ps_io_ops_t ops, + starfive_ltimer_t *timers, + int id) +{ + assert(timers); + assert(id >= 0 && id < NUM_TIMERS); + + timers->callback_data[id].ltimer = ltimer; + timers->callback_data[id].irq = &irqs[id]; + timers->callback_data[id].irq_handler = ltimer_handle_irq; + + timers->irq_id[id] = ps_irq_register(&ops.irq_ops, + irqs[id], + handle_irq_wrapper, + &timers->callback_data[id]); + + if (timers->irq_id[id] < 0) { + ZF_LOGE("Unable to register irq %lu", irqs[id].trigger.number); + return EINVAL; + } + + return 0; +} + +int ltimer_default_init(ltimer_t *ltimer, + ps_io_ops_t ops, + ltimer_callback_fn_t callback, + void *callback_token) +{ + assert(ltimer); + + ltimer->get_num_irqs = get_num_irqs; + ltimer->get_nth_irq = get_nth_irq; + ltimer->get_num_pmems = get_num_pmems; + ltimer->get_nth_pmem = get_nth_pmem; + ltimer->get_time = get_time; + ltimer->set_timeout = set_timeout; + ltimer->reset = reset; + ltimer->destroy = destroy; + + int error = ps_calloc(&ops.malloc_ops, 1, sizeof(starfive_ltimer_t), <imer->data); + if (error) { + ZF_LOGE("Memory allocation failed with error %d", error); + return error; + } + + starfive_ltimer_t *timers = ltimer->data; + + timers->ops = ops; + timers->user_callback = callback; + timers->user_callback_token = callback_token; + + error = register_interrupt(ltimer, ops, timers, COUNTER_TIMER); + if (error) { + return error; + } + + error = register_interrupt(ltimer, ops, timers, TIMEOUT_TIMER); + if (error) { + return error; + } + + timers->vaddr = ps_pmem_map(&ops, pmems[0], false, PS_MEM_NORMAL); + if (timers->vaddr == NULL) { + ZF_LOGE("Unable to map physical memory at 0x%"PRIx64" length 0x%"PRIx64, + pmems[0].base_addr, + pmems[0].length); + destroy(ltimer->data); + return EINVAL; + } + + starfive_timer_disable_all_channels(timers->vaddr); + + starfive_timer_init(&timers->timer[COUNTER_TIMER], timers->vaddr, COUNTER_TIMER); + starfive_timer_start(&timers->timer[COUNTER_TIMER]); + + starfive_timer_init(&timers->timer[TIMEOUT_TIMER], timers->vaddr, TIMEOUT_TIMER); + + return 0; +} diff --git a/libplatsupport/src/plat/star64/serial.c b/libplatsupport/src/plat/star64/serial.c new file mode 100644 index 000000000..54f1dad8e --- /dev/null +++ b/libplatsupport/src/plat/star64/serial.c @@ -0,0 +1,68 @@ +/* + * Copyright 2023, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include "../../chardev.h" + +#define UART_THR 0x00 /* UART Transmit Holding Register */ +#define UART_LSR 0x14 /* UART Line Status Register */ +#define UART_LSR_THRE 0x20 /* Transmit Holding Register Empty */ + +#define REG_PTR(base, off) ((volatile uint32_t *)((base) + (off))) + +int uart_getchar(ps_chardevice_t *d) +{ + while ((*REG_PTR(d->vaddr, UART_LSR) & BIT(0))); + return *REG_PTR(d->vaddr, UART_THR); +} + +int uart_putchar(ps_chardevice_t *d, int c) +{ + if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) { + uart_putchar(d, '\r'); + } + + while ((*REG_PTR(d->vaddr, UART_LSR) & UART_LSR_THRE) == 0); + + /* Add character to the buffer. */ + *REG_PTR(d->vaddr, UART_THR) = c; + + return c; +} + +static void uart_handle_irq(ps_chardevice_t *dev) +{ + /* This UART driver is not interrupt driven, there is nothing to do here. */ +} + +int uart_init(const struct dev_defn *defn, + const ps_io_ops_t *ops, + ps_chardevice_t *dev) +{ + memset(dev, 0, sizeof(*dev)); + void *vaddr = chardev_map(defn, ops); + if (vaddr == NULL) { + ZF_LOGE("Unable to map chardev"); + return -1; + } + + /* Set up all the device properties. */ + dev->id = defn->id; + dev->vaddr = 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; + + *REG_PTR(dev->vaddr, 0x8) = 1; + + return 0; +} diff --git a/libplatsupport/src/plat/star64/timer.c b/libplatsupport/src/plat/star64/timer.c new file mode 100644 index 000000000..ba25e972e --- /dev/null +++ b/libplatsupport/src/plat/star64/timer.c @@ -0,0 +1,149 @@ +/* + * Copyright 2023, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +#define MAX_TIMEOUT_NS (STARFIVE_TIMER_MAX_TICKS * (NS_IN_S / STARFIVE_TIMER_TICKS_PER_SECOND)) + +static void print_regs(starfive_timer_t *timer) +{ + printf(" Timer Channel Register State\n"); + printf(" Interrupt status: 0x%08x @ [%p]\n", timer->regs->status, &timer->regs->status); + printf(" Control: 0x%08x @ [%p]\n", timer->regs->ctrl, &timer->regs->ctrl); + printf(" Load: 0x%08x @ [%p]\n", timer->regs->load, &timer->regs->load); + printf(" Enable: 0x%08x @ [%p]\n", timer->regs->enable, &timer->regs->enable); + printf(" Reload: 0x%08x @ [%p]\n", timer->regs->reload, &timer->regs->reload); + printf(" Value: 0x%08x @ [%p]\n", timer->regs->value, &timer->regs->value); + printf(" Clear Interrupt: 0x%08x @ [%p]\n", timer->regs->intclr, &timer->regs->intclr); + printf(" Mask Interrupt: 0x%08x @ [%p]\n", timer->regs->intmask, &timer->regs->intmask); +} + +void starfive_timer_start(starfive_timer_t *timer) +{ + assert(timer); + assert(timer->regs); + + timer->regs->enable = STARFIVE_TIMER_ENABLED; +} + +void starfive_timer_stop(starfive_timer_t *timer) +{ + assert(timer); + assert(timer->regs); + + timer->regs->enable = STARFIVE_TIMER_DISABLED; +} + +void starfive_timer_handle_irq(starfive_timer_t *timer) +{ + assert(timer); + assert(timer->regs); + + timer->value_h += 1; + + while (timer->regs->intclr & STARFIVE_TIMER_INTCLR_BUSY) { + /* + * Hardware will not currently accept writes to this register. + * Wait for this bit to be unset by hardware. + */ + } + + timer->regs->intclr = 1; +} + +uint64_t starfive_timer_get_time(starfive_timer_t *timer) +{ + assert(timer); + assert(timer->regs); + + /* the timer value counts down from the load value */ + uint64_t value_l = (uint64_t)(STARFIVE_TIMER_MAX_TICKS - timer->regs->value); + uint64_t value_h = (uint64_t)timer->value_h; + + /* Include unhandled interrupt in value_h */ + if (timer->regs->intclr == 1) { + value_h += 1; + } + + uint64_t value_ticks = (value_h << 32) | value_l; + + /* convert from ticks to nanoseconds */ + uint64_t value_whole_seconds = value_ticks / STARFIVE_TIMER_TICKS_PER_SECOND; + uint64_t value_subsecond_ticks = value_ticks % STARFIVE_TIMER_TICKS_PER_SECOND; + uint64_t value_subsecond_ns = + (value_subsecond_ticks * NS_IN_S) / STARFIVE_TIMER_TICKS_PER_SECOND; + uint64_t value_ns = value_whole_seconds * NS_IN_S + value_subsecond_ns; + + return value_ns; +} + +void starfive_timer_reset(starfive_timer_t *timer) +{ + assert(timer); + assert(timer->regs); + assert(timer->regs->enable == STARFIVE_TIMER_DISABLED); + + timer->regs->ctrl = STARFIVE_TIMER_MODE_CONTINUOUS; + timer->regs->load = STARFIVE_TIMER_MAX_TICKS; + timer->value_h = 0; +} + +int starfive_timer_set_timeout(starfive_timer_t *timer, uint64_t ns, bool is_periodic) +{ + starfive_timer_stop(timer); + timer->value_h = 0; + + if (is_periodic) { + timer->regs->ctrl = STARFIVE_TIMER_MODE_CONTINUOUS; + } else { + timer->regs->ctrl = STARFIVE_TIMER_MODE_SINGLE; + } + + uint64_t ticks_whole_seconds = (ns / NS_IN_S) * STARFIVE_TIMER_TICKS_PER_SECOND; + uint64_t ticks_remainder = (ns % NS_IN_S) * STARFIVE_TIMER_TICKS_PER_SECOND / NS_IN_S; + uint64_t num_ticks = ticks_whole_seconds + ticks_remainder; + + if (num_ticks > STARFIVE_TIMER_MAX_TICKS) { + ZF_LOGE("Requested timeout of %"PRIu64" ns exceeds hardware limit of %"PRIu64" ns", + ns, + MAX_TIMEOUT_NS); + return -EINVAL; + } + + timer->regs->load = num_ticks; + starfive_timer_start(timer); + + return 0; +} + +void starfive_timer_disable_all_channels(void *vaddr) +{ + assert(vaddr); + + for (int x = 0; x < STARFIVE_TIMER_NUM_CHANNELS; x++) { + uint32_t *enable = vaddr + 0x10 + x * STARFIVE_TIMER_CHANNEL_REGISTERS_LEN_IN_BYTES; + *enable = STARFIVE_TIMER_DISABLED; + } +} + +void starfive_timer_init(starfive_timer_t *timer, void *vaddr, uint64_t channel) +{ + assert(timer); + assert(vaddr); + assert(channel >= 0 && channel < STARFIVE_TIMER_NUM_CHANNELS); + + timer->regs = vaddr + STARFIVE_TIMER_CHANNEL_REGISTERS_LEN_IN_BYTES * channel; + timer->regs->enable = STARFIVE_TIMER_DISABLED; + timer->regs->ctrl = STARFIVE_TIMER_MODE_CONTINUOUS; + timer->regs->load = STARFIVE_TIMER_MAX_TICKS; + timer->regs->intmask = STARFIVE_TIMER_INTERRUPT_UNMASKED; + timer->value_h = 0; +}