blob: 6b7c47b5c3a11a02c891bbb7b131b767a5459079 [file] [log] [blame]
// 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);