blob: e12180eab69c4356c97d05fbe32c264c581b0f89 [file] [log] [blame]
// Copyright 2019 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
// TODO(gkalsi): Unify the two UART codepaths and use the port parameter to
// select between the real uart and the miniuart.
#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
#define UART_MR1 0x0000
#define UART_MR1_RX_RDY_CTL (1 << 7)
#define UART_MR2 0x0004
#define UART_DM_IPR 0x0018
#define UART_DM_DMRX 0x0034
#define UART_DM_N0_CHARS_FOR_TX 0x0040
#define UART_DM_SR 0x00A4
#define UART_DM_SR_RXRDY (1 << 0)
#define UART_DM_SR_RXFULL (1 << 1)
#define UART_DM_SR_TXRDY (1 << 2)
#define UART_DM_SR_TXEMT (1 << 3)
#define UART_DM_SR_OVERRUN (1 << 4)
#define UART_DM_SR_PAR_FRAME_ERR (1 << 5)
#define UART_DM_SR_RX_BREAK (1 << 6)
#define UART_DM_SR_HUNT_CHAR (1 << 7)
#define UART_DM_CR 0x00A8
#define UART_DM_CR_RX_EN (1 << 0)
#define UART_DM_CR_RX_DISABLE (1 << 1)
#define UART_DM_CR_TX_EN (1 << 2)
#define UART_DM_CR_TX_DISABLE (1 << 3)
#define UART_DM_CR_CMD_RESET_RX (1 << 4)
#define UART_DM_CR_CMD_RESET_TX (2 << 4)
#define UART_DM_CR_CMD_RESET_ERR (3 << 4)
#define UART_DM_CR_CMD_RESET_BRK_CHG_INT (4 << 4)
#define UART_DM_CR_CMD_START_BRK (5 << 4)
#define UART_DM_CR_CMD_STOP_BRK (6 << 4)
#define UART_DM_CR_CMD_RESET_CTS_N (7 << 4)
#define UART_DM_CR_CMD_RESET_STALE_INT (8 << 4)
#define UART_DM_CR_CMD_SET_RFR (13 << 4)
#define UART_DM_CR_CMD_RESET_RFR (14 << 4)
#define UART_DM_CR_CMD_CLEAR_TX_ERROR (16 << 4)
#define UART_DM_CR_CMD_CLEAR_TX_DONE (17 << 4)
#define UART_DM_CR_CMD_RESET_BRK_START_INT (18 << 4)
#define UART_DM_CR_CMD_RESET_BRK_END_INT (19 << 4)
#define UART_DM_CR_CMD_RESET_PAR_FRAME_ERR_INT (20 << 4)
#define UART_DM_CR_CMD_CLEAR_TX_WR_ERROR_IRQ (25 << 4)
#define UART_DM_CR_CMD_CLEAR_RX_RD_ERROR_IRQ (26 << 4)
#define UART_DM_CR_CMD_CLEAR_TX_COMP_IRQ (27 << 4)
#define UART_DM_CR_CMD_CLEAR_WWT_IRQ (28 << 4)
#define UART_DM_CR_CMD_CLEAR_NO_FINISH_CMD_VIO_IRQ (30 << 4)
#define UART_DM_CR_CMD_RESET_TX_READY (3 << 8)
#define UART_DM_CR_CMD_FORCE_STALE (4 << 8)
#define UART_DM_CR_CMD_ENABLE_STALE_EVENT (5 << 8)
#define UART_DM_CR_CMD_DISABLE_STALE_EVENT (6 << 8)
#define UART_DM_RXFS 0x0050
#define UART_DM_RXFS_RX_BUFFER_STATE(r) ((r >> 7) & 7)
#define UART_DM_RXFS_FIFO_STATE(r) ((r >> 14) | (r & 0x3F))
#define UART_DM_MISR 0x00AC
#define UART_DM_IMR 0x00B0
#define UART_DM_ISR 0x00B4
#define UART_IRQ_TXLEV (1 << 0)
#define UART_IRQ_RXHUNT (1 << 1)
#define UART_IRQ_RXBREAK_CHANGE (1 << 2)
#define UART_IRQ_RXSTALE (1 << 3)
#define UART_IRQ_RXLEV (1 << 4)
#define UART_IRQ_DELTA_CTS (1 << 5)
#define UART_IRQ_CURRENT_CTS (1 << 6)
#define UART_IRQ_TX_READY (1 << 7)
#define UART_IRQ_TX_ERROR (1 << 8)
#define UART_IRQ_TX_DONE (1 << 9)
#define UART_IRQ_RXBREAK_START (1 << 10)
#define UART_IRQ_RXBREAK_END (1 << 11)
#define UART_IRQ_PAR_FRAME_ERR_IRQ (1 << 12)
#define UART_IRQ_TX_WR_ERROR_IRQ (1 << 13)
#define UART_IRQ_RX_RD_ERROR_IRQ (1 << 14)
#define UART_IRQ_TXCOMP_IRQ (1 << 15)
#define UART_IRQ_WWT_IRQ (1 << 16)
#define UART_IRQ_NO_FINISH_CMD_VIOL (1 << 17)
#define UART_DM_TF 0x0100
#define UART_DM_RF(n) (0x0140 + 4 * (n))
#define RXBUF_SIZE 128
// clang-format on
// values read from zbi
static uint64_t uart_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;
static inline uint32_t uart_read(int offset) {
return readl(uart_base + offset);
}
static inline void uart_write(uint32_t val, int offset) {
writel(val, uart_base + offset);
}
static inline void yield(void)
{
__asm__ volatile("yield" ::: "memory");
}
// panic-time getc/putc
static int msm_pputc(char c) {
if (!uart_base) {
return -1;
}
// spin while fifo is full
while (!(uart_read(UART_DM_SR) & UART_DM_SR_TXEMT)) {
yield();
}
uart_write(UART_DM_CR_CMD_RESET_TX_READY, UART_DM_N0_CHARS_FOR_TX);
uart_write(1, UART_DM_N0_CHARS_FOR_TX);
uart_read(UART_DM_N0_CHARS_FOR_TX);
// wait for TX ready
while (!(uart_read(UART_DM_SR) & UART_DM_SR_TXRDY)) {
yield();
}
uart_write(c, UART_DM_TF);
return 1;
}
static int msm_pgetc(void)
{
cbuf_t* rxbuf = &uart_rx_buf;
char c;
int count = 0;
uint32_t val, rxfs, sr;
char* bytes;
// see if we have chars left from previous read
if (cbuf_read_char(rxbuf, &c, false) == 1) {
return c;
}
if ((uart_read(UART_DM_SR) & UART_DM_SR_OVERRUN)) {
uart_write(UART_DM_CR_CMD_RESET_ERR, UART_DM_CR);
}
do {
rxfs = uart_read(UART_DM_RXFS);
sr = uart_read(UART_DM_SR);
count = UART_DM_RXFS_RX_BUFFER_STATE(rxfs);
if (!(sr & UART_DM_SR_RXRDY) && !count) {
return -1;
}
} while (count == 0);
uart_write(UART_DM_CR_CMD_FORCE_STALE, UART_DM_CR);
val = uart_read(UART_DM_RF(0));
uart_read(UART_DM_RF(1));
uart_write(UART_DM_CR_CMD_RESET_STALE_INT, UART_DM_CR);
uart_write(0xffffff, UART_DM_DMRX);
bytes = (char*)&val;
c = bytes[0];
// save remaining chars for next call
for (int i = 1; i < count; i++) {
cbuf_write_char(rxbuf, bytes[i]);
}
return c;
}
static interrupt_eoi uart_irq_handler(void* arg) {
uint32_t misr = uart_read(UART_DM_MISR);
while (uart_read(UART_DM_SR) & UART_DM_SR_RXRDY) {
uint32_t rxfs = uart_read(UART_DM_RXFS);
// count is number of words in RX fifo that have data
int count = UART_DM_RXFS_FIFO_STATE(rxfs);
for (int i = 0; i < count; i++) {
uint32_t val = uart_read(UART_DM_RF(0));
char* bytes = (char *)&val;
for (int j = 0; j < 4; j++) {
// Unfortunately there is no documented way to get number of bytes in each word
// so we just need to ignore zero bytes here.
// Apparently this problem doesn't exist in DMA mode.
char ch = bytes[j];
if (ch) {
cbuf_write_char(&uart_rx_buf, ch);
} else {
break;
}
}
}
}
if (misr & UART_IRQ_RXSTALE) {
uart_write(UART_DM_CR_CMD_RESET_STALE_INT, UART_DM_CR);
}
// ask to receive more
uart_write(0xFFFFFF, UART_DM_DMRX);
uart_write(UART_DM_CR_CMD_ENABLE_STALE_EVENT, UART_DM_CR);
return IRQ_EOI_DEACTIVATE;
}
static void msm_uart_init(const void* driver_data, uint32_t length) {
uint32_t temp;
// disable interrupts
uart_write(0, UART_DM_IMR);
uart_write(UART_DM_CR_TX_EN | UART_DM_CR_RX_EN, UART_DM_CR);
uart_write(UART_DM_CR_CMD_RESET_TX, UART_DM_CR);
uart_write(UART_DM_CR_CMD_RESET_RX, UART_DM_CR);
uart_write(UART_DM_CR_CMD_RESET_ERR, UART_DM_CR);
uart_write(UART_DM_CR_CMD_RESET_BRK_CHG_INT, UART_DM_CR);
uart_write(UART_DM_CR_CMD_RESET_CTS_N, UART_DM_CR);
uart_write(UART_DM_CR_CMD_SET_RFR, UART_DM_CR);
uart_write(UART_DM_CR_CMD_CLEAR_TX_DONE, UART_DM_CR);
uart_write(0xFFFFFF, UART_DM_DMRX);
uart_write(UART_DM_CR_CMD_ENABLE_STALE_EVENT, UART_DM_CR);
temp = uart_read(UART_MR1);
temp |= UART_MR1_RX_RDY_CTL;
uart_write(temp, UART_MR1);
cbuf_initialize(&uart_rx_buf, RXBUF_SIZE);
// enable RX interrupt
uart_write(UART_IRQ_RXSTALE, UART_DM_IMR);
register_int_handler(uart_irq, &uart_irq_handler, nullptr);
unmask_interrupt(uart_irq);
}
static int msm_getc(bool wait) {
char ch;
size_t count = cbuf_read_char(&uart_rx_buf, &ch, wait);
return (count == 1 ? ch : -1);
}
static void msm_start_panic(void) {
uart_tx_irq_enabled = false;
}
static void msm_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 (!(uart_read(UART_DM_SR) & UART_DM_SR_TXEMT)) {
spin_unlock_irqrestore(&uart_spinlock, state);
if (block) {
// TODO(voydanoff) 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;
msm_pputc('\r');
} else {
copied_CR = false;
msm_pputc(*str++);
len--;
}
}
spin_unlock_irqrestore(&uart_spinlock, state);
}
static const struct pdev_uart_ops uart_ops = {
.getc = msm_getc,
.pputc = msm_pputc,
.pgetc = msm_pgetc,
.start_panic = msm_start_panic,
.dputs = msm_dputs,
};
static void msm_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);
uart_base = periph_paddr_to_vaddr(driver->mmio_phys);
uart_irq = driver->irq;
ASSERT(uart_base);
ASSERT(uart_irq);
pdev_register_uart(&uart_ops);
}
LK_PDEV_INIT(msm_uart_init_early, KDRV_MSM_UART, msm_uart_init_early, LK_INIT_LEVEL_PLATFORM_EARLY);
LK_PDEV_INIT(msm_uart_init, KDRV_MSM_UART, msm_uart_init, LK_INIT_LEVEL_PLATFORM);