blob: b4ab1880bc93a4e1dd7bcd7b61a7cea5278992af [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
/* Registers */
#define MX8_URXD (0x00)
#define MX8_UTXD (0x40)
#define MX8_UCR1 (0x80)
#define MX8_UCR2 (0x84)
#define MX8_UCR3 (0x88)
#define MX8_UCR4 (0x8C)
#define MX8_UFCR (0x90)
#define MX8_USR1 (0x94)
#define MX8_USR2 (0x98)
#define MX8_UTS (0xB4)
/* UCR1 Bit Definition */
#define UCR1_TRDYEN (1 << 13)
#define UCR1_RRDYEN (1 << 9)
#define UCR1_UARTEN (1 << 0)
/* UCR2 Bit Definition */
#define UCR2_TXEN (1 << 2)
#define UCR2_RXEN (1 << 1)
#define UCR2_SRST (1 << 0)
/* UFCR Bit Definition */
#define UFCR_TXTL(x) (x << 10)
#define UFCR_RXTL(x) (x << 0)
#define UFCR_MASK (0x3f)
/* USR1 Bit Definition */
#define USR1_TRDY (1 << 13)
#define USR1_RRDY (1 << 9)
/* USR2 Bit Definition */
#define USR2_TXFE (1 << 14)
/* UTS Bit Definition */
#define UTS_TXEMPTY (1 << 6)
#define UTS_RXEMPTY (1 << 5)
#define UTS_TXFULL (1 << 4)
#define UTS_RXFULL (1 << 3)
#define RXBUF_SIZE 32
// clang-format on
// values read from zbi
static bool initialized = false;
static vaddr_t uart_base = 0;
static uint32_t uart_irq = 0;
static cbuf_t uart_rx_buf;
// static cbuf_t uart_tx_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)))
static interrupt_eoi uart_irq_handler(void* arg) {
/* read interrupt status and mask */
while ((UARTREG(MX8_USR1) & USR1_RRDY)) {
if (cbuf_space_avail(&uart_rx_buf) == 0) {
break;
}
char c = UARTREG(MX8_URXD) & 0xFF;
cbuf_write_char(&uart_rx_buf, c);
}
/* Signal if anyone is waiting to TX */
if (UARTREG(MX8_UCR1) & UCR1_TRDYEN) {
spin_lock(&uart_spinlock);
if (!(UARTREG(MX8_USR2) & UTS_TXFULL)) {
// signal
event_signal(&uart_dputc_event, true);
}
spin_unlock(&uart_spinlock);
}
return IRQ_EOI_DEACTIVATE;
}
/* panic-time getc/putc */
static int imx_uart_pputc(char c) {
if (!uart_base) {
return -1;
}
/* spin while fifo is full */
while (UARTREG(MX8_UTS) & UTS_TXFULL)
;
UARTREG(MX8_UTXD) = c;
return 1;
}
static int imx_uart_pgetc() {
if (!uart_base) {
return ZX_ERR_NOT_SUPPORTED;
}
if ((UARTREG(MX8_UTS) & UTS_RXEMPTY)) {
return ZX_ERR_INTERNAL;
}
return UARTREG(MX8_URXD);
}
static int imx_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 imx_uart_pgetc();
}
}
static void imx_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(MX8_UTS) & UTS_TXFULL)) {
spin_unlock_irqrestore(&uart_spinlock, state);
if (block) {
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;
imx_uart_pputc('\r');
} else {
copied_CR = false;
imx_uart_pputc(*str++);
len--;
}
}
spin_unlock_irqrestore(&uart_spinlock, state);
}
static void imx_start_panic() {
uart_tx_irq_enabled = false;
}
static const struct pdev_uart_ops uart_ops = {
.getc = imx_uart_getc,
.pputc = imx_uart_pputc,
.pgetc = imx_uart_pgetc,
.start_panic = imx_start_panic,
.dputs = imx_dputs,
};
static void imx_uart_init(const void* driver_data, uint32_t length) {
uint32_t regVal;
// create circular buffer to hold received data
cbuf_initialize(&uart_rx_buf, RXBUF_SIZE);
// register uart irq
register_int_handler(uart_irq, &uart_irq_handler, NULL);
// set rx fifo threshold to 1 character
regVal = UARTREG(MX8_UFCR);
regVal &= ~UFCR_RXTL(UFCR_MASK);
regVal &= ~UFCR_TXTL(UFCR_MASK);
regVal |= UFCR_RXTL(1);
regVal |= UFCR_TXTL(0x2);
UARTREG(MX8_UFCR) = regVal;
// enable rx interrupt
regVal = UARTREG(MX8_UCR1);
regVal |= UCR1_RRDYEN;
if (dlog_bypass() == false) {
// enable tx interrupt
regVal |= UCR1_TRDYEN;
}
UARTREG(MX8_UCR1) = regVal;
// enable rx and tx transmisster
regVal = UARTREG(MX8_UCR2);
regVal |= UCR2_RXEN | UCR2_TXEN;
UARTREG(MX8_UCR2) = regVal;
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;
}
initialized = true;
// enable interrupts
unmask_interrupt(uart_irq);
}
static void imx_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);
uart_base = periph_paddr_to_vaddr(driver->mmio_phys);
ASSERT(uart_base);
uart_irq = driver->irq;
pdev_register_uart(&uart_ops);
}
LK_PDEV_INIT(imx_uart_init_early, KDRV_NXP_IMX_UART, imx_uart_init_early, LK_INIT_LEVEL_PLATFORM_EARLY);
LK_PDEV_INIT(imx_uart_init, KDRV_NXP_IMX_UART, imx_uart_init, LK_INIT_LEVEL_PLATFORM);