blob: 79ee9b8f4b35ec4d37c5c0012184172f867dc03c [file] [log] [blame]
// Copyright 2018 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 <arch/arm64/periphmap.h>
#include <dev/interrupt.h>
#include <dev/uart.h>
#include <kernel/thread.h>
#include <lib/cbuf.h>
#include <lib/debuglog.h>
#include <pdev/driver.h>
#include <pdev/uart.h>
#include <platform/debug.h>
#include <reg.h>
#include <stdio.h>
#include <trace.h>
#include <zircon/boot/driver-config.h>
// clang-format off
// UART Registers
#define UART_RBR (0x0) // RX Buffer Register (read-only)
#define UART_THR (0x0) // TX Buffer Register (write-only)
#define UART_IER (0x4) // Interrupt Enable Register
#define UART_IIR (0x8) // Interrupt Identification Register (read-only)
#define UART_FCR (0x8) // FIFO Control Register (write-only)
#define UART_LCR (0xc) // Line Control Register
#define UART_MCR (0x10) // Modem Control Register
#define UART_LSR (0x14) // Line Status Register
#define UART_MSR (0x18) // Modem Status Register
#define UART_SCR (0x1c) // Scratch Register
#define UART_DLL (0x0) // Divisor Latch LS (Only when LCR.DLAB = 1)
#define UART_DLM (0x4) // Divisor Latch MS (Only when LCR.DLAB = 1)
#define UART_EFR (0x8) // Enhanced Feature Register (Only when LCR = 0xbf)
#define UART_XON1 (0x10) // XON1 Char Register (Only when LCR = 0xbf)
#define UART_XON2 (0x14) // XON2 Char Register (Only when LCR = 0xbf)
#define UART_XOFF1 (0x18) // XOFF1 Char Register (Only when LCR = 0xbf)
#define UART_XOFF2 (0x1c) // XOFF2 Char Register (Only when LCR = 0xbf)
#define UART_AUTOBAUD_EN (0x20) // Auto Baud Detect Enable Register
#define UART_HIGHSPEED (0x24) // High Speed Mode Register
#define UART_SAMPLE_COUNT (0x28) // Sample Counter Register
#define UART_SAMPLE_POINT (0x2c) // Sample Point Register
#define UART_AUTOBAUD_REG (0x30) // Auto Baud Monitor Register
#define UART_RATE_FIX_AD (0x34) // Clock Rate Fix Register
#define UART_AUTOBAUD_SAMPLE (0x38) // Auto Baud Sample Register
#define UART_GUARD (0x3c) // Guard Time Added Register
#define UART_ESCAPE_DAT (0x40) // Escape Character Register
#define UART_ESCAPE_EN (0x44) // Escape Enable Register
#define UART_SLEEP_EN (0x48) // Sleep Enable Register
#define UART_VFIFO_EN (0x4c) // DMA Enable Register
#define UART_RXTRI_AD (0x50) // RX Trigger Address
// IER
#define UART_IER_ERBFI (1 << 0)
#define UART_IER_ETBEI (1 << 1)
#define UART_IER_ELSI (1 << 2)
#define UART_IER_EDSSI (1 << 3)
#define UART_IER_XOFFI (1 << 5)
#define UART_IER_RTSI (1 << 6)
#define UART_IER_CTSI (1 << 7)
#define UART_IIR_NO_INT_PENDING (0x01)
// IIR
#define UART_IIR_RLS (0x06) // Receiver Line Status
#define UART_IIR_RDA (0x04) // Receive Data Available
#define UART_IIR_CTI (0x0C) // Character Timeout Indicator
#define UART_IIR_THRE (0x02) // Transmit Holding Register Empty
#define UART_IIR_MS (0x00) // Check Modem Status Register
#define UART_IIR_SW_FLOW_CTRL (0x10) // Receive XOFF characters
#define UART_IIR_HW_FLOW_CTRL (0x20) // CTS or RTS Rising Edge
#define UART_IIR_FIFO_EN (0xc0)
#define UART_IIR_INT_MASK (0x1f)
// LSR
#define UART_LSR_DR (1 << 0)
#define UART_LSR_OE (1 << 1)
#define UART_LSR_PE (1 << 2)
#define UART_LSR_FE (1 << 3)
#define UART_LSR_BI (1 << 4)
#define UART_LSR_THRE (1 << 5)
#define UART_LSR_TEMT (1 << 6)
#define UART_LSR_FIFOERR (1 << 7)
// SOC Registers
#define SOC_INT_POL (0x620) // SOC Interrupt polarity registers start
// clang-format on
#define RXBUF_SIZE 32
// values read from zbi
static bool initialized = false;
static vaddr_t uart_base = 0;
static vaddr_t soc_base = 0;
static uint32_t uart_irq = 0;
static cbuf_t uart_rx_buf;
static bool uart_tx_irq_enabled = false;
static event_t uart_dputc_event = EVENT_INITIAL_VALUE(uart_dputc_event,
true,
EVENT_FLAG_AUTOUNSIGNAL);
static spin_lock_t uart_spinlock = SPIN_LOCK_INITIAL_VALUE;
#define UARTREG(reg) (*(volatile uint32_t*)((uart_base) + (reg)))
#define SOCREG(reg) (*(volatile uint32_t*)((soc_base) + (reg)))
static interrupt_eoi uart_irq_handler(void* arg) {
// read interrupt status and mask
while (UARTREG(UART_LSR) & UART_LSR_DR) {
if (cbuf_space_avail(&uart_rx_buf) == 0) {
break;
}
char c = UARTREG(UART_RBR) & 0xFF;
cbuf_write_char(&uart_rx_buf, c);
}
// Signal if anyone is waiting to TX
if (UARTREG(UART_LSR) & UART_LSR_THRE) {
UARTREG(UART_IER) &= ~UART_IER_ETBEI; // Disable TX interrupt
spin_lock(&uart_spinlock);
// TODO(andresoportus): Revisit all UART drivers usage of events, from event.h:
// 1. The reschedule flag is not supposed to be true in interrupt context.
// 2. FLAG_AUTOUNSIGNAL only wakes up one thread.
event_signal(&uart_dputc_event, true);
spin_unlock(&uart_spinlock);
}
return IRQ_EOI_DEACTIVATE;
}
// panic-time getc/putc
static int mt8167_uart_pputc(char c) {
if (!uart_base) {
return -1;
}
// spin while fifo is full
while (!(UARTREG(UART_LSR) & UART_LSR_THRE))
;
UARTREG(UART_THR) = c;
return 1;
}
static int mt8167_uart_pgetc() {
if (!uart_base) {
return ZX_ERR_NOT_SUPPORTED;
}
// spin while fifo is empty
while (!(UARTREG(UART_LSR) & UART_LSR_DR))
;
return UARTREG(UART_RBR);
}
static int mt8167_uart_getc(bool wait) {
if (!uart_base) {
return ZX_ERR_NOT_SUPPORTED;
}
if (initialized) {
char c;
if (cbuf_read_char(&uart_rx_buf, &c, wait) == 1) {
return c;
}
return ZX_ERR_INTERNAL;
} else {
// Interrupts are not enabled yet. Use panic calls for now
return mt8167_uart_pgetc();
}
}
static void mt8167_dputs(const char* str, size_t len,
bool block, bool map_NL) {
spin_lock_saved_state_t state;
bool copied_CR = false;
if (!uart_base) {
return;
}
if (!uart_tx_irq_enabled) {
block = false;
}
spin_lock_irqsave(&uart_spinlock, state);
while (len > 0) {
// is FIFO full?
while (!(UARTREG(UART_LSR) & UART_LSR_THRE)) {
spin_unlock_irqrestore(&uart_spinlock, state);
if (block) {
UARTREG(UART_IER) |= UART_IER_ETBEI; // Enable TX interrupt.
event_wait(&uart_dputc_event);
} else {
arch_spinloop_pause();
}
spin_lock_irqsave(&uart_spinlock, state);
}
if (*str == '\n' && map_NL && !copied_CR) {
copied_CR = true;
mt8167_uart_pputc('\r');
} else {
copied_CR = false;
mt8167_uart_pputc(*str++);
len--;
}
}
spin_unlock_irqrestore(&uart_spinlock, state);
}
static void mt8167_start_panic() {
uart_tx_irq_enabled = false;
}
static const struct pdev_uart_ops uart_ops = {
.getc = mt8167_uart_getc,
.pputc = mt8167_uart_pputc,
.pgetc = mt8167_uart_pgetc,
.start_panic = mt8167_start_panic,
.dputs = mt8167_dputs,
};
static void mt8167_uart_init(const void* driver_data, uint32_t length) {
// create circular buffer to hold received data
cbuf_initialize(&uart_rx_buf, RXBUF_SIZE);
if (dlog_bypass() == true) {
uart_tx_irq_enabled = false;
return;
}
zx_status_t status =
configure_interrupt(uart_irq, IRQ_TRIGGER_MODE_LEVEL, IRQ_POLARITY_ACTIVE_HIGH);
if (status != ZX_OK) {
printf("UART: configure_interrupt failed %d\n", status);
return;
}
status = register_int_handler(uart_irq, &uart_irq_handler, NULL);
if (status != ZX_OK) {
printf("UART: register_int_handler failed %d\n", status);
return;
}
status = unmask_interrupt(uart_irq);
if (status != ZX_OK) {
printf("UART: unmask_interrupt failed %d\n", status);
return;
}
UARTREG(UART_IER) |= UART_IER_ERBFI; // Enable RX interrupt.
initialized = true;
// Start up tx driven output.
printf("UART: starting IRQ driven TX\n");
uart_tx_irq_enabled = true;
}
static void mt8167_uart_init_early(const void* driver_data, uint32_t length) {
ASSERT(length >= sizeof(dcfg_soc_uart_t));
auto driver = static_cast<const dcfg_soc_uart_t*>(driver_data);
ASSERT(driver->soc_mmio_phys && driver->uart_mmio_phys && driver->irq);
soc_base = periph_paddr_to_vaddr(driver->soc_mmio_phys);
ASSERT(soc_base);
// Convert Level interrupt polarity in SOC from Low to High as needed by gicv2.
auto index = (driver->irq - 32); // index IRQ as SPI (-32 PPIs).
// 32 interrupts per register, one register every 4 bytes.
SOCREG(SOC_INT_POL + index / 32 * 4) = static_cast<uint32_t>(1) << (index % 32);
uart_base = periph_paddr_to_vaddr(driver->uart_mmio_phys);
ASSERT(uart_base);
uart_irq = driver->irq;
pdev_register_uart(&uart_ops);
}
LK_PDEV_INIT(mt8167_uart_init_early, KDRV_MT8167_UART, mt8167_uart_init_early, LK_INIT_LEVEL_PLATFORM_EARLY);
LK_PDEV_INIT(mt8167_uart_init, KDRV_MT8167_UART, mt8167_uart_init, LK_INIT_LEVEL_PLATFORM);