diff --git a/zircon/kernel/dev/uart/dw8250/BUILD.gn b/zircon/kernel/dev/uart/dw8250/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..0537e5a03868818fe2e43e979f53f4e32dd2a63e --- /dev/null +++ b/zircon/kernel/dev/uart/dw8250/BUILD.gn @@ -0,0 +1,15 @@ +# Copyright 2019 The Fuchsia Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +source_set("dw8250") { + sources = [ + "uart.cpp", + ] + deps = [ + "$zx/kernel/dev/pdev", + "$zx/kernel/dev/pdev/uart", + "$zx/kernel/lib/cbuf", + "$zx/kernel/lib/debuglog", + ] +} diff --git a/zircon/kernel/dev/uart/dw8250/uart.cpp b/zircon/kernel/dev/uart/dw8250/uart.cpp new file mode 100644 index 0000000000000000000000000000000000000000..90a60fe70e1c077652749f722fdc0247e670eca9 --- /dev/null +++ b/zircon/kernel/dev/uart/dw8250/uart.cpp @@ -0,0 +1,254 @@ +// 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 + +#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> + +// DW8250 implementation + +// 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) +// clang-format on + +#define RXBUF_SIZE 32 + +// 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 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 dw8250_uart_irq(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 dw8250_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 dw8250_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 dw8250_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 dw8250_uart_pgetc(); + } +} + +static void dw8250_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; + dw8250_uart_pputc('\r'); + } else { + copied_CR = false; + dw8250_uart_pputc(*str++); + len--; + } + } + spin_unlock_irqrestore(&uart_spinlock, state); +} + +static void dw8250_uart_init(const void* driver_data, uint32_t length) { + return; + // 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 = register_int_handler(uart_irq, &dw8250_uart_irq, NULL); + DEBUG_ASSERT(status == ZX_OK); + + // enable interrupt + unmask_interrupt(uart_irq); + + UARTREG(UART_IER) |= UART_IER_ERBFI; // Enable RX interrupt. + initialized = true; + + // Start up tx driven output. + uart_tx_irq_enabled = true; +} + +static void dw8250_start_panic() { + uart_tx_irq_enabled = false; +} + +static const struct pdev_uart_ops uart_ops = { + .getc = dw8250_uart_getc, + .pputc = dw8250_uart_pputc, + .pgetc = dw8250_uart_pgetc, + .start_panic = dw8250_start_panic, + .dputs = dw8250_dputs, +}; + +extern "C" void uart_mark(unsigned char x); +static void dw8250_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(dw8250_uart_init_early, KDRV_DW8250_UART, dw8250_uart_init_early, LK_INIT_LEVEL_PLATFORM_EARLY) +LK_PDEV_INIT(dw8250_uart_init, KDRV_DW8250_UART, dw8250_uart_init, LK_INIT_LEVEL_PLATFORM) diff --git a/zircon/kernel/platform/generic-arm/BUILD.gn b/zircon/kernel/platform/generic-arm/BUILD.gn index da0558aa574c3df648b80835163c2565d89cf1b3..d0ab3f5d7682e33b0aae62f3097d6b871f4eae24 100644 --- a/zircon/kernel/platform/generic-arm/BUILD.gn +++ b/zircon/kernel/platform/generic-arm/BUILD.gn @@ -22,6 +22,7 @@ source_set("generic-arm") { "$zx/kernel/dev/psci", "$zx/kernel/dev/timer/arm_generic", "$zx/kernel/dev/uart/amlogic_s905", + "$zx/kernel/dev/uart/dw8250", "$zx/kernel/dev/uart/msm", "$zx/kernel/dev/uart/mt8167", "$zx/kernel/dev/uart/nxp-imx", diff --git a/zircon/system/public/zircon/boot/driver-config.h b/zircon/system/public/zircon/boot/driver-config.h index 0708e4e2e07c3ed174884f011743d1f65006d948..87d0078ff0c8c10008260dff2d3f05cad1226fb6 100644 --- a/zircon/system/public/zircon/boot/driver-config.h +++ b/zircon/system/public/zircon/boot/driver-config.h @@ -21,6 +21,7 @@ #define KDRV_AMLOGIC_HDCP 0x484C4D41 // 'AMLH' #define KDRV_MSM_UART 0x554D534D // 'MSMU' #define KDRV_MSM_POWER 1347244877 // 'MSMP' +#define KDRV_DW8250_UART 0x44573855 // 'DW8U' // kernel driver struct that can be used for simple drivers // used by KDRV_PL011_UART, KDRV_AMLOGIC_UART and KDRV_NXP_IMX_UART