PolarFire SoC: Initial platform UART support Signed-off-by: Jesse Millwood <jesse.millwood@dornerworks.com>
diff --git a/libplatsupport/plat_include/polarfire/platsupport/plat/icicle_mss.h b/libplatsupport/plat_include/polarfire/platsupport/plat/icicle_mss.h new file mode 100644 index 0000000..7ef92f1 --- /dev/null +++ b/libplatsupport/plat_include/polarfire/platsupport/plat/icicle_mss.h
@@ -0,0 +1,17 @@ +/* + * Copyright 2020, DornerWorks + * + * This software may be distributed and modified according to the terms of + * the BSD 2-Clause license. Note that NO WARRANTY is provided. + * See "LICENSE_BSD2.txt" for details. + * + * @TAG(DORNERWORKS_BSD) + */ + +/* These values are grabbed from the ICICLE_MSS_0.xml file that is generated from + * the Libero SoC IDE. This file can be found in various ICICLE Kit example projects + * including the Yocto recipes */ + +/* MSS_APB_AHB_CLK in ICICLE_MSS_0.xml */ +#define POLARFIRE_PCLK 150000000 +#define POLARFIRE_BAUD 115200
diff --git a/libplatsupport/plat_include/polarfire/platsupport/plat/serial.h b/libplatsupport/plat_include/polarfire/platsupport/plat/serial.h new file mode 100644 index 0000000..24232e5 --- /dev/null +++ b/libplatsupport/plat_include/polarfire/platsupport/plat/serial.h
@@ -0,0 +1,104 @@ +/* + * Copyright 2020, DornerWorks + * + * This software may be distributed and modified according to the terms of + * the BSD 2-Clause license. Note that NO WARRANTY is provided. + * See "LICENSE_BSD2.txt" for details. + * + * @TAG(DORNERWORKS_BSD) + */ + +#pragma once +#include <autoconf.h> + +enum chardev_id { + UART0, + UART1, + UART2, + UART3, + PS_SERIAL_DEFAULT = UART3 +}; + +/* + * Depending on whether or not the Divisor Latch Access Bit (DLAB) is set + * in the line_control (LCR) register (bit 7) the first two registers will + * have different functions. + * If DLAB is not set in the LCR: + * reg[0]: receive or transmit buffer + * If written to: tranmit buffer + * If read : receive buffer + * reg[1]: interrupt enable register + * If the DLAB is set in the LCR: + * reg[0]: LSB of the divisor + * reg[1]: MSB of the divisor + * The interrupt id and fifo control registers are related + * If the 3rd register is written to, it affects the fifo control + * If it is read from, the interrupt id register contents are returned + */ +struct uart { + union { + uint32_t rx_buffer; + uint32_t tx_buffer; + uint32_t divisor_latch_lsb; + }; + union { + uint32_t interrupt_enable; + uint32_t divisor_latch_msb; + }; + union { + uint32_t interrupt_id; + uint32_t fifo_control; + }; + uint32_t line_control ; + uint32_t modem_control ; + uint32_t line_status; + uint32_t modem_status; + uint32_t scratch; + uint32_t RESERVED ; + uint32_t multi_mode_interrupt_enable; + uint32_t multi_mode_interrupt_id; + uint32_t RESERVED_1 ; + uint32_t multi_mode_control_0; + uint32_t multi_mode_control_1; + uint32_t multi_mode_control_2; + uint32_t fractional_divisor; + uint32_t glitch_filter; + uint32_t transmitter_time_guard; + uint32_t receiver_time_out; + uint32_t address; +}; +typedef volatile struct uart uart_regs_t; + + +#define UART0_PADDR 0x20000000 +#define UART1_PADDR 0x20100000 +#define UART2_PADDR 0x20102000 +#define UART3_PADDR 0x20104000 + +#define UART0_IRQ 90 +#define UART1_IRQ 91 +#define UART2_IRQ 92 +#define UART3_IRQ 93 + +#define DEFAULT_SERIAL_PADDR UART3_PADDR +#define DEFAULT_SERIAL_INTERRUPT UART3_IRQ + +/* Relevant Register Masks * +/ +/* Line Control Register */ +#define LCR_WORD_LEN_5 0b00 +#define LCR_WORD_LEN_6 0b01 +#define LCR_WORD_LEN_7 0b10 +#define LCR_WORD_LEN_8 0b11 +#define LCR_DIV_LATCH_MASK (1 << 7) + +/* Modem Control Register */ +#define MCR_DTR_MASK (1 << 0) +#define MCR_RTS_MASK (1 << 1) + +/* Multi Mode Control Register 0 */ +#define MM0_ENABLE_FRAC_MASK (1 << 7) + +/* Line Status Register */ +#define LSR_DATA_READY_MASK (1 << 0) +#define LSR_TX_HOLD_REG_EMPTY_MASK (1 << 5)
diff --git a/libplatsupport/src/plat/polarfire/chardev.c b/libplatsupport/src/plat/polarfire/chardev.c new file mode 100644 index 0000000..835ff0a --- /dev/null +++ b/libplatsupport/src/plat/polarfire/chardev.c
@@ -0,0 +1,52 @@ +/* + * Copyright 2020, DornerWorks + * + * This software may be distributed and modified according to the terms of + * the BSD 2-Clause license. Note that NO WARRANTY is provided. + * See "LICENSE_BSD2.txt" for details. + * + * @TAG(DORNERWORKS_BSD) + */ + +/** + * Contains the definition for all character devices on this platform. + * Currently this is just a simple patch. + */ + +#include "../../chardev.h" +#include "../../common.h" +#include <utils/util.h> + + +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}; + +#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) +}; + + +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/polarfire/uart.c b/libplatsupport/src/plat/polarfire/uart.c new file mode 100644 index 0000000..de7182b --- /dev/null +++ b/libplatsupport/src/plat/polarfire/uart.c
@@ -0,0 +1,126 @@ +/* + * Copyright 2020, DornerWorks + * + * This software may be distributed and modified according to the terms of + * the BSD 2-Clause license. Note that NO WARRANTY is provided. + * See "LICENSE_BSD2.txt" for details. + * + * @TAG(DORNERWORKS_BSD) + */ + +#include <autoconf.h> + +#include <stdlib.h> +#include <platsupport/serial.h> +#include <platsupport/plat/serial.h> +#include <platsupport/plat/icicle_mss.h> +#include <string.h> + +#include "../../chardev.h" + +static inline uart_regs_t *uart_get_priv(ps_chardevice_t *d) +{ + return (uart_regs_t *)d->vaddr; +} + +int uart_getchar(ps_chardevice_t *d) +{ + uart_regs_t *regs = uart_get_priv(d); + if (regs->line_status & LSR_DATA_READY_MASK) { + return regs->rx_buffer; + } + return -1; +} + +static void busy_wait_fifo_empty_and_tx_char(uart_regs_t *regs, int c) +{ + // wait until FIFO empty + while ((regs->line_status & LSR_TX_HOLD_REG_EMPTY_MASK) == 0) { + // busy loop + } + + regs->tx_buffer = c; +} + +int uart_putchar(ps_chardevice_t *d, int c) +{ + uart_regs_t *regs = uart_get_priv(d); + + // turn LF into CR+LF if SERIAL_AUTO_CR is active + if ((c == '\n') && (d->flags & SERIAL_AUTO_CR)) { + busy_wait_fifo_empty_and_tx_char(regs, '\r'); + } + + busy_wait_fifo_empty_and_tx_char(regs, c); + return c; +} + +static void uart_handle_irq(ps_chardevice_t *d UNUSED) +{ + // IRQs are cleared when the TX/RX watermark conditions are no longer met + // so there is nothing to do here. +} + +int uart_init(const struct dev_defn *defn, + const ps_io_ops_t *ops, + ps_chardevice_t *dev) +{ + uart_regs_t *regs; + uint32_t divisor; + uint32_t divisor_by_128; + uint32_t divisor_by_64; + uint32_t fractional_divisor; + /* Attempt to map the virtual address, assure this works */ + void *vaddr = chardev_map(defn, ops); + if (vaddr == NULL) { + return -1; + } + + memset(dev, 0, sizeof(*dev)); + + /* 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; + + regs = uart_get_priv(dev); + + regs->line_control = LCR_WORD_LEN_8; + regs->modem_control = 0; + regs->multi_mode_control_0 = 0; + regs->multi_mode_control_1 = 0; + regs->multi_mode_control_2 = 0; + regs->glitch_filter = 0; + regs->transmitter_time_guard = 0; + regs->receiver_time_out = 0; + + /* + * Configure baud rate divisors. This uses the fractional baud rate divisor + * where possible to provide the most accurate baud rate possible. + * This algorithm is taken from the Microchip MSS UART Driver (LIC:MIT) + */ + divisor_by_128 = (uint32_t)((8UL * POLARFIRE_PCLK) / POLARFIRE_BAUD); + divisor_by_64 = divisor_by_128 / 2u; + divisor = divisor_by_64 / 64u; + fractional_divisor = divisor_by_64 - (divisor * 64u); + fractional_divisor += (divisor_by_128 - (divisor * 128u)) + - (fractional_divisor * 2u); + + // div: 81, frac: 24 => 115207 + + regs->line_control |= LCR_DIV_LATCH_MASK; + regs->divisor_latch_msb = (uint8_t)(divisor >> 8); + regs->divisor_latch_lsb = (uint8_t)divisor; + regs->line_control &= ~LCR_DIV_LATCH_MASK; + + regs->multi_mode_control_0 |= MM0_ENABLE_FRAC_MASK; + regs->fractional_divisor = (uint8_t)fractional_divisor; + regs->line_control = LCR_WORD_LEN_8; + + return 0; +}