blob: 6fa12f0c122e2a764ce45d7c6ed2af3922ff1ab9 [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 <lib/cbuf.h>
#include <lib/debuglog.h>
#include <lib/zircon-internal/macros.h>
#include <lib/zx/status.h>
#include <reg.h>
#include <stdio.h>
#include <string.h>
#include <trace.h>
#include <zircon/boot/driver-config.h>
#include <arch/arm64/periphmap.h>
#include <dev/interrupt.h>
#include <dev/uart.h>
#include <kernel/lockdep.h>
#include <kernel/thread.h>
#include <pdev/driver.h>
#include <pdev/uart.h>
// clang-format off
#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)
// clang-format on
static Cbuf uart_rx_buf;
static bool initialized = false;
static vaddr_t s905_uart_base = 0;
static uint32_t s905_uart_irq = 0;
/*
* Tx driven irq:
* According to the meson s905 UART spec
* https://dn.odroid.com/S905/DataSheet/S905_Public_Datasheet_V1.1.4.pdf
* 1) Tx Fifo depth is 64 bytes
* 2) The Misc register (aka irq control), by default will
* interrupt when the # of bytes in the fifo falls below 32
* but this can be changed if necessary (XMIT_IRQ_CNT).
* But no need to change this right now.
* 3) UART status register (TXCOUNT_MASK) holds the # of bytes
* in the Tx FIFO. More usefully, the TXFULL bit tells us when
* the Tx FIFO is full. We can use this to continue shoving
* data into the FIFO.
* 4) Setting TXINTEN will generate an interrupt each time a byte is
* read from the Tx FIFO. So we can leave the interrupt unmasked.
*/
static bool uart_tx_irq_enabled = false;
static AutounsignalEvent uart_dputc_event{true};
namespace {
DECLARE_SINGLETON_SPINLOCK_WITH_TYPE(uart_spinlock, MonitoredSpinLock);
} // namespace
static inline void uartreg_and_eq(uintptr_t base, ptrdiff_t reg, uint32_t flags) {
volatile uint32_t* ptr = reinterpret_cast<volatile uint32_t*>(base + reg);
*ptr = *ptr & flags;
}
static inline void uartreg_or_eq(uintptr_t base, ptrdiff_t reg, uint32_t flags) {
volatile uint32_t* ptr = reinterpret_cast<volatile uint32_t*>(base + reg);
*ptr = *ptr | flags;
}
static interrupt_eoi 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 (uart_rx_buf.Full()) {
// Drop the data if our buffer is full
// NOTE: This breaks flow control, but allows
// serial to work when disconnecting/reconnecting the cable.
__UNUSED char c = static_cast<char>(UARTREG(base, S905_UART_RFIFO));
continue;
}
char c = static_cast<char>(UARTREG(base, S905_UART_RFIFO));
uart_rx_buf.WriteChar(c);
}
/* handle any framing/parity errors */
if (UARTREG(base, S905_UART_STATUS) & (S905_UART_STATUS_FRAMEERR | S905_UART_STATUS_PARERR)) {
/* clear the status by writing to the control register */
uartreg_or_eq(base, S905_UART_CONTROL, S905_UART_CONTROL_CLRERR);
}
/* handle TX */
if (UARTREG(s905_uart_base, S905_UART_CONTROL) & S905_UART_CONTROL_TXINTEN) {
Guard<MonitoredSpinLock, NoIrqSave> guard{uart_spinlock::Get(), SOURCE_TAG};
if (!(UARTREG(s905_uart_base, S905_UART_STATUS) & S905_UART_STATUS_TXFULL))
/* Signal any waiting Tx */
{
uart_dputc_event.Signal();
}
}
return IRQ_EOI_DEACTIVATE;
}
static void s905_uart_init(const void* driver_data, uint32_t length) {
DEBUG_ASSERT(s905_uart_base);
DEBUG_ASSERT(s905_uart_irq);
// create circular buffer to hold received data
uart_rx_buf.Initialize(RXBUF_SIZE, malloc(RXBUF_SIZE));
// reset the port
uartreg_or_eq(s905_uart_base, S905_UART_CONTROL,
S905_UART_CONTROL_RSTRX | S905_UART_CONTROL_RSTTX | S905_UART_CONTROL_CLRERR);
uartreg_and_eq(s905_uart_base, S905_UART_CONTROL,
~(S905_UART_CONTROL_RSTRX | S905_UART_CONTROL_RSTTX | S905_UART_CONTROL_CLRERR));
// enable rx and tx
uartreg_or_eq(s905_uart_base, S905_UART_CONTROL, S905_UART_CONTROL_TXEN | S905_UART_CONTROL_RXEN);
uint32_t val;
val = S905_UART_CONTROL_INVRTS | S905_UART_CONTROL_RXINTEN | S905_UART_CONTROL_TWOWIRE;
if (dlog_bypass() == false) {
val |= S905_UART_CONTROL_TXINTEN;
}
uartreg_or_eq(s905_uart_base, S905_UART_CONTROL, val);
// 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;
zx_status_t status =
register_permanent_int_handler(s905_uart_irq, &uart_irq, (void*)s905_uart_base);
DEBUG_ASSERT(status == ZX_OK);
initialized = true;
if (dlog_bypass() == true) {
uart_tx_irq_enabled = false;
} else {
/* start up tx driven output */
printf("UART: started IRQ driven TX\n");
uart_tx_irq_enabled = true;
}
// enable interrupt
unmask_interrupt(s905_uart_irq);
}
/* panic-time getc/putc */
static void s905_uart_pputc(char c) {
/* 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;
}
static int s905_uart_pgetc() {
if ((UARTREG(s905_uart_base, S905_UART_STATUS) & S905_UART_STATUS_RXEMPTY) == 0) {
return UARTREG(s905_uart_base, S905_UART_RFIFO);
} else {
return ZX_ERR_INTERNAL;
}
}
static int s905_uart_getc(bool wait) {
if (initialized) {
// do cbuf stuff here
zx::status<char> result = uart_rx_buf.ReadChar(wait);
if (result.is_ok()) {
return result.value();
}
return result.error_value();
} else {
// Interrupts not online yet, use the panic calls for now.
return s905_uart_pgetc();
}
}
/*
* Keeping this simple for now, we try to write 1 byte at a time
* to the Tx FIFO. Blocking or spinning if the Tx FIFO is full.
* The event is signaled up from the interrupt handler, when a
* byte is read from the Tx FIFO.
* (setting TXINTEN results in the generation of an interrupt
* each time a byte is read from the Tx FIFO).
*/
static void s905_dputs(const char* str, size_t len, bool block, bool map_NL) {
bool copied_CR = false;
if (!uart_tx_irq_enabled) {
block = false;
}
Guard<MonitoredSpinLock, IrqSave> guard{uart_spinlock::Get(), SOURCE_TAG};
while (len > 0) {
/* Is FIFO Full ? */
while (UARTREG(s905_uart_base, S905_UART_STATUS) & S905_UART_STATUS_TXFULL) {
guard.CallUnlocked([&block]() {
if (block) {
uart_dputc_event.Wait();
} else {
arch::Yield();
}
});
}
if (*str == '\n' && map_NL && !copied_CR) {
copied_CR = true;
UARTREG(s905_uart_base, S905_UART_WFIFO) = '\r';
} else {
copied_CR = false;
UARTREG(s905_uart_base, S905_UART_WFIFO) = *str++;
len--;
}
}
}
static void s905_uart_start_panic() { uart_tx_irq_enabled = false; }
static const struct pdev_uart_ops s905_uart_ops = {
.getc = s905_uart_getc,
.pputc = s905_uart_pputc,
.pgetc = s905_uart_pgetc,
.start_panic = s905_uart_start_panic,
.dputs = s905_dputs,
};
static void s905_uart_init_early(const void* driver_data, uint32_t length) {
ASSERT(length >= sizeof(dcfg_simple_t));
auto driver = static_cast<const dcfg_simple_t*>(driver_data);
ASSERT(driver->mmio_phys && driver->irq);
s905_uart_base = periph_paddr_to_vaddr(driver->mmio_phys);
ASSERT(s905_uart_base);
s905_uart_irq = driver->irq;
pdev_register_uart(&s905_uart_ops);
}
LK_PDEV_INIT(s905_uart_init_early, KDRV_AMLOGIC_UART, s905_uart_init_early,
LK_INIT_LEVEL_PLATFORM_EARLY)
LK_PDEV_INIT(s905_uart_init, KDRV_AMLOGIC_UART, s905_uart_init, LK_INIT_LEVEL_PLATFORM)