| // Copyright 2017 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include <reg.h> |
| #include <stdio.h> |
| #include <trace.h> |
| #include <string.h> |
| #include <lib/cbuf.h> |
| #include <kernel/thread.h> |
| #include <dev/interrupt.h> |
| #include <dev/uart.h> |
| |
| #include <mdi/mdi.h> |
| #include <mdi/mdi-defs.h> |
| #include <pdev/driver.h> |
| #include <pdev/uart.h> |
| |
| #define S905_UART_WFIFO (0x0) |
| #define S905_UART_RFIFO (0x4) |
| #define S905_UART_CONTROL (0x8) |
| #define S905_UART_STATUS (0xc) |
| #define S905_UART_IRQ_CONTROL (0x10) |
| #define S905_UART_REG5 (0x14) |
| |
| |
| #define S905_UART_CONTROL_INVRTS (1 << 31) |
| #define S905_UART_CONTROL_MASKERR (1 << 30) |
| #define S905_UART_CONTROL_INVCTS (1 << 29) |
| #define S905_UART_CONTROL_TXINTEN (1 << 28) |
| #define S905_UART_CONTROL_RXINTEN (1 << 27) |
| #define S905_UART_CONTROL_INVTX (1 << 26) |
| #define S905_UART_CONTROL_INVRX (1 << 25) |
| #define S905_UART_CONTROL_CLRERR (1 << 24) |
| #define S905_UART_CONTROL_RSTRX (1 << 23) |
| #define S905_UART_CONTROL_RSTTX (1 << 22) |
| #define S905_UART_CONTROL_XMITLEN (1 << 20) |
| #define S905_UART_CONTROL_XMITLEN_MASK (0x3 << 20) |
| #define S905_UART_CONTROL_PAREN (1 << 19) |
| #define S905_UART_CONTROL_PARTYPE (1 << 18) |
| #define S905_UART_CONTROL_STOPLEN (1 << 16) |
| #define S905_UART_CONTROL_STOPLEN_MASK (0x3 << 16) |
| #define S905_UART_CONTROL_TWOWIRE (1 << 15) |
| #define S905_UART_CONTROL_RXEN (1 << 13) |
| #define S905_UART_CONTROL_TXEN (1 << 12) |
| #define S905_UART_CONTROL_BAUD0 (1 << 0) |
| #define S905_UART_CONTROL_BAUD0_MASK (0xfff << 0) |
| |
| #define S905_UART_STATUS_RXBUSY (1 << 26) |
| #define S905_UART_STATUS_TXBUSY (1 << 25) |
| #define S905_UART_STATUS_RXOVRFLW (1 << 24) |
| #define S905_UART_STATUS_CTSLEVEL (1 << 23) |
| #define S905_UART_STATUS_TXEMPTY (1 << 22) |
| #define S905_UART_STATUS_TXFULL (1 << 21) |
| #define S905_UART_STATUS_RXEMPTY (1 << 20) |
| #define S905_UART_STATUS_RXFULL (1 << 19) |
| #define S905_UART_STATUS_TXOVRFLW (1 << 18) |
| #define S905_UART_STATUS_FRAMEERR (1 << 17) |
| #define S905_UART_STATUS_PARERR (1 << 16) |
| #define S905_UART_STATUS_TXCOUNT_POS (8) |
| #define S905_UART_STATUS_TXCOUNT_MASK (0x7f << S905_UART_STATUS_TXCOUNT_POS) |
| #define S905_UART_STATUS_RXCOUNT_POS (0) |
| #define S905_UART_STATUS_RXCOUNT_MASK (0x7f << S905_UART_STATUS_RXCOUNT_POS) |
| |
| #define UARTREG(base, reg) (*(volatile uint32_t*)((base) + (reg))) |
| |
| #define RXBUF_SIZE 128 |
| #define NUM_UART 5 |
| |
| #define S905_UART0_OFFSET (0x011084c0) |
| #define S905_UART1_OFFSET (0x011084dc) |
| #define S905_UART2_OFFSET (0x01108700) |
| #define S905_UART0_AO_OFFSET (0x081004c0) |
| #define S905_UART1_AO_OFFSET (0x081004e0) |
| |
| |
| |
| static cbuf_t uart_rx_buf; |
| static bool initialized = false; |
| static uintptr_t s905_uart_base = 0; |
| static uint32_t s905_uart_irq = 0; |
| |
| |
| |
| static enum handler_return uart_irq(void *arg) |
| { |
| uintptr_t base = (uintptr_t)arg; |
| |
| /* read interrupt status and mask */ |
| |
| while ( (UARTREG(base, S905_UART_STATUS) & S905_UART_STATUS_RXCOUNT_MASK) > 0 ) { |
| if (cbuf_space_avail(&uart_rx_buf) == 0) { |
| break; |
| } |
| char c = UARTREG(base, S905_UART_RFIFO); |
| cbuf_write_char(&uart_rx_buf, c, false); |
| } |
| |
| return true; |
| } |
| |
| static void s905_uart_init(mdi_node_ref_t* node, uint level) |
| { |
| assert(s905_uart_base); |
| assert(s905_uart_irq); |
| |
| // create circular buffer to hold received data |
| cbuf_initialize(&uart_rx_buf, RXBUF_SIZE); |
| |
| //reset the port |
| UARTREG(s905_uart_base,S905_UART_CONTROL) |= S905_UART_CONTROL_RSTRX | |
| S905_UART_CONTROL_RSTTX | |
| S905_UART_CONTROL_CLRERR; |
| UARTREG(s905_uart_base,S905_UART_CONTROL) &= ~(S905_UART_CONTROL_RSTRX | |
| S905_UART_CONTROL_RSTTX | |
| S905_UART_CONTROL_CLRERR); |
| // enable rx and tx |
| UARTREG(s905_uart_base,S905_UART_CONTROL) |= S905_UART_CONTROL_TXEN | |
| S905_UART_CONTROL_RXEN; |
| |
| UARTREG(s905_uart_base,S905_UART_CONTROL) |= S905_UART_CONTROL_INVRTS | |
| S905_UART_CONTROL_RXINTEN | |
| S905_UART_CONTROL_TWOWIRE; |
| |
| // Set to interrupt every 1 rx byte |
| uint32_t temp2 = UARTREG(s905_uart_base,S905_UART_IRQ_CONTROL); |
| temp2 &= 0xffff0000; |
| temp2 |= (1 << 8) | ( 1 ); |
| UARTREG(s905_uart_base,S905_UART_IRQ_CONTROL) = temp2; |
| |
| register_int_handler(s905_uart_irq, &uart_irq, (void *)s905_uart_base); |
| |
| initialized = true; |
| |
| // enable interrupt |
| unmask_interrupt(s905_uart_irq); |
| } |
| |
| /* panic-time getc/putc */ |
| static int s905_uart_pputc(char c) |
| { |
| if (!s905_uart_base) |
| return 0; |
| |
| /* spin while fifo is full */ |
| while (UARTREG(s905_uart_base, S905_UART_STATUS) & S905_UART_STATUS_TXFULL) |
| ; |
| UARTREG(s905_uart_base, S905_UART_WFIFO) = c; |
| |
| return 1; |
| } |
| |
| static int s905_uart_pgetc(void) |
| { |
| if (!s905_uart_base) |
| return 0; |
| |
| if ((UARTREG(s905_uart_base, S905_UART_STATUS) & S905_UART_STATUS_RXEMPTY) == 0) { |
| return UARTREG(s905_uart_base, S905_UART_RFIFO); |
| } else { |
| return -1; |
| } |
| } |
| |
| |
| static int s905_uart_putc(char c) |
| { |
| if (!s905_uart_base) |
| return 0; |
| |
| /* spin while fifo is full */ |
| while (UARTREG(s905_uart_base, S905_UART_STATUS) & S905_UART_STATUS_TXFULL) |
| ; |
| UARTREG(s905_uart_base, S905_UART_WFIFO) = c; |
| |
| return 1; |
| } |
| |
| static int s905_uart_getc(bool wait) |
| { |
| if (!s905_uart_base) |
| return -1; |
| |
| if (initialized) { |
| // do cbuf stuff here |
| char c; |
| if (cbuf_read_char(&uart_rx_buf, &c, false) == 1) |
| return c; |
| return -1; |
| |
| } else { |
| //Interupts not online yet, use the panic calls for now. |
| return s905_uart_pgetc(); |
| } |
| } |
| |
| static const struct pdev_uart_ops s905_uart_ops = { |
| .putc = s905_uart_putc, |
| .getc = s905_uart_getc, |
| .pputc = s905_uart_pputc, |
| .pgetc = s905_uart_pgetc, |
| }; |
| |
| static void s905_uart_init_early(mdi_node_ref_t* node, uint level) |
| { |
| s905_uart_base = 0; |
| s905_uart_irq = 0; |
| |
| mdi_node_ref_t child; |
| mdi_each_child(node, &child) { |
| switch (mdi_id(&child)) { |
| case MDI_BASE_VIRT: |
| if(mdi_node_uint64(&child, &s905_uart_base) != ZX_OK) |
| return; |
| break; |
| case MDI_IRQ: |
| if(mdi_node_uint32(&child, &s905_uart_irq) != ZX_OK) |
| return; |
| break; |
| |
| } |
| } |
| if ((s905_uart_base == 0) || (s905_uart_irq == 0)) |
| return; |
| |
| pdev_register_uart(&s905_uart_ops); |
| } |
| |
| LK_PDEV_INIT(s905_uart_init_early, MDI_S905_UART, s905_uart_init_early, LK_INIT_LEVEL_PLATFORM_EARLY); |
| LK_PDEV_INIT(s905_uart_init, MDI_S905_UART, s905_uart_init, LK_INIT_LEVEL_PLATFORM); |