| // Copyright 2016 The Fuchsia Authors |
| // Copyright (c) 2014-2015 Travis Geiselbrecht |
| // |
| // 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> |
| |
| // PL011 implementation |
| |
| // clang-format off |
| #define UART_DR (0x00) |
| #define UART_RSR (0x04) |
| #define UART_FR (0x18) |
| #define UART_ILPR (0x20) |
| #define UART_IBRD (0x24) |
| #define UART_FBRD (0x28) |
| #define UART_LCRH (0x2c) |
| #define UART_CR (0x30) |
| #define UART_IFLS (0x34) |
| #define UART_IMSC (0x38) |
| #define UART_TRIS (0x3c) |
| #define UART_TMIS (0x40) |
| #define UART_ICR (0x44) |
| #define UART_DMACR (0x48) |
| // clang-format on |
| |
| #define UARTREG(base, reg) (*REG32((base) + (reg))) |
| |
| #define RXBUF_SIZE 16 |
| |
| // values read from zbi |
| static vaddr_t uart_base = 0; |
| static uint32_t uart_irq = 0; |
| |
| static cbuf_t uart_rx_buf; |
| |
| /* |
| * Tx driven irq: |
| * NOTE: For the pl011, txim is the "ready to transmit" interrupt. So we must |
| * mask it when we no longer care about it and unmask it when we start |
| * xmitting. |
| */ |
| 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 void pl011_mask_tx() { |
| UARTREG(uart_base, UART_IMSC) &= ~(1 << 5); |
| } |
| |
| static inline void pl011_unmask_tx() { |
| UARTREG(uart_base, UART_IMSC) |= (1 << 5); |
| } |
| |
| static interrupt_eoi pl011_uart_irq(void* arg) { |
| /* read interrupt status and mask */ |
| uint32_t isr = UARTREG(uart_base, UART_TMIS); |
| |
| if (isr & ((1 << 4) | (1 << 6))) { // rxmis |
| /* while fifo is not empty, read chars out of it */ |
| while ((UARTREG(uart_base, UART_FR) & (1 << 4)) == 0) { |
| /* if we're out of rx buffer, mask the irq instead of handling it */ |
| if (cbuf_space_avail(&uart_rx_buf) == 0) { |
| UARTREG(uart_base, UART_IMSC) &= ~((1 << 4) | (1 << 6)); // !rxim |
| break; |
| } |
| |
| char c = static_cast<char>(UARTREG(uart_base, UART_DR)); |
| cbuf_write_char(&uart_rx_buf, c); |
| } |
| } |
| spin_lock(&uart_spinlock); |
| if (isr & (1 << 5)) { |
| /* |
| * Signal any waiting Tx and mask Tx interrupts once we |
| * wakeup any blocked threads |
| */ |
| event_signal(&uart_dputc_event, true); |
| pl011_mask_tx(); |
| } |
| spin_unlock(&uart_spinlock); |
| |
| return IRQ_EOI_DEACTIVATE; |
| } |
| |
| static void pl011_uart_init(const void* driver_data, uint32_t length) { |
| // create circular buffer to hold received data |
| cbuf_initialize(&uart_rx_buf, RXBUF_SIZE); |
| |
| // assumes interrupts are contiguous |
| zx_status_t status = register_int_handler(uart_irq, &pl011_uart_irq, NULL); |
| DEBUG_ASSERT(status == ZX_OK); |
| |
| // clear all irqs |
| UARTREG(uart_base, UART_ICR) = 0x3ff; |
| |
| // set fifo trigger level |
| UARTREG(uart_base, UART_IFLS) = 0; // 1/8 rxfifo, 1/8 txfifo |
| |
| // enable rx interrupt |
| UARTREG(uart_base, UART_IMSC) = (1 << 4) | // rxim |
| (1 << 6); // rtim |
| |
| // enable receive |
| UARTREG(uart_base, UART_CR) |= (1 << 9); // rxen |
| |
| // enable interrupt |
| unmask_interrupt(uart_irq); |
| |
| 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; |
| } |
| } |
| |
| static int pl011_uart_getc(bool wait) { |
| char c; |
| if (cbuf_read_char(&uart_rx_buf, &c, wait) == 1) { |
| UARTREG(uart_base, UART_IMSC) |= ((1 << 4) | (1 << 6)); // rxim |
| return c; |
| } |
| |
| return ZX_ERR_INTERNAL; |
| } |
| |
| /* panic-time getc/putc */ |
| static int pl011_uart_pputc(char c) { |
| /* spin while fifo is full */ |
| while (UARTREG(uart_base, UART_FR) & (1 << 5)) |
| ; |
| UARTREG(uart_base, UART_DR) = c; |
| |
| return 1; |
| } |
| |
| static int pl011_uart_pgetc() { |
| if ((UARTREG(uart_base, UART_FR) & (1 << 4)) == 0) { |
| return UARTREG(uart_base, UART_DR); |
| } else { |
| return -1; |
| } |
| } |
| |
| static void pl011_dputs(const char* str, size_t len, |
| bool block, bool map_NL) { |
| spin_lock_saved_state_t state; |
| bool copied_CR = false; |
| |
| if (!uart_tx_irq_enabled) { |
| block = false; |
| } |
| spin_lock_irqsave(&uart_spinlock, state); |
| while (len > 0) { |
| // Is FIFO Full ? |
| while (UARTREG(uart_base, UART_FR) & (1 << 5)) { |
| if (block) { |
| /* Unmask Tx interrupts before we block on the event */ |
| pl011_unmask_tx(); |
| spin_unlock_irqrestore(&uart_spinlock, state); |
| event_wait(&uart_dputc_event); |
| } else { |
| spin_unlock_irqrestore(&uart_spinlock, state); |
| arch_spinloop_pause(); |
| } |
| spin_lock_irqsave(&uart_spinlock, state); |
| } |
| if (!copied_CR && map_NL && *str == '\n') { |
| copied_CR = true; |
| UARTREG(uart_base, UART_DR) = '\r'; |
| } else { |
| copied_CR = false; |
| UARTREG(uart_base, UART_DR) = *str++; |
| len--; |
| } |
| } |
| spin_unlock_irqrestore(&uart_spinlock, state); |
| } |
| |
| static void pl011_start_panic() { |
| uart_tx_irq_enabled = false; |
| } |
| |
| static const struct pdev_uart_ops uart_ops = { |
| .getc = pl011_uart_getc, |
| .pputc = pl011_uart_pputc, |
| .pgetc = pl011_uart_pgetc, |
| .start_panic = pl011_start_panic, |
| .dputs = pl011_dputs, |
| }; |
| |
| static void pl011_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; |
| |
| UARTREG(uart_base, UART_CR) = (1 << 8) | (1 << 0); // tx_enable, uarten |
| |
| pdev_register_uart(&uart_ops); |
| } |
| |
| LK_PDEV_INIT(pl011_uart_init_early, KDRV_PL011_UART, pl011_uart_init_early, LK_INIT_LEVEL_PLATFORM_EARLY); |
| LK_PDEV_INIT(pl011_uart_init, KDRV_PL011_UART, pl011_uart_init, LK_INIT_LEVEL_PLATFORM); |