1// Copyright 2016 The Fuchsia Authors 2// Copyright (c) 2014-2015 Travis Geiselbrecht 3// 4// Use of this source code is governed by a MIT-style 5// license that can be found in the LICENSE file or at 6// https://opensource.org/licenses/MIT 7 8#include <reg.h> 9#include <stdio.h> 10#include <trace.h> 11#include <arch/arm64/periphmap.h> 12#include <lib/cbuf.h> 13#include <lib/debuglog.h> 14#include <kernel/thread.h> 15#include <dev/interrupt.h> 16#include <dev/uart.h> 17#include <platform/debug.h> 18#include <pdev/driver.h> 19#include <pdev/uart.h> 20#include <zircon/boot/driver-config.h> 21 22/* PL011 implementation */ 23#define UART_DR (0x00) 24#define UART_RSR (0x04) 25#define UART_FR (0x18) 26#define UART_ILPR (0x20) 27#define UART_IBRD (0x24) 28#define UART_FBRD (0x28) 29#define UART_LCRH (0x2c) 30#define UART_CR (0x30) 31#define UART_IFLS (0x34) 32#define UART_IMSC (0x38) 33#define UART_TRIS (0x3c) 34#define UART_TMIS (0x40) 35#define UART_ICR (0x44) 36#define UART_DMACR (0x48) 37 38#define UARTREG(base, reg) (*REG32((base) + (reg))) 39 40#define RXBUF_SIZE 16 41 42// values read from zbi 43static vaddr_t uart_base = 0; 44static uint32_t uart_irq = 0; 45 46static cbuf_t uart_rx_buf; 47 48/* 49 * Tx driven irq: 50 * NOTE: For the pl011, txim is the "ready to transmit" interrupt. So we must 51 * mask it when we no longer care about it and unmask it when we start 52 * xmitting. 53 */ 54static bool uart_tx_irq_enabled = false; 55static event_t uart_dputc_event = EVENT_INITIAL_VALUE(uart_dputc_event, 56 true, 57 EVENT_FLAG_AUTOUNSIGNAL); 58 59static spin_lock_t uart_spinlock = SPIN_LOCK_INITIAL_VALUE; 60 61static inline void pl011_mask_tx(void) 62{ 63 UARTREG(uart_base, UART_IMSC) &= ~(1<<5); 64} 65 66static inline void pl011_unmask_tx(void) 67{ 68 UARTREG(uart_base, UART_IMSC) |= (1<<5); 69} 70 71static void pl011_uart_irq(void *arg) 72{ 73 /* read interrupt status and mask */ 74 uint32_t isr = UARTREG(uart_base, UART_TMIS); 75 76 if (isr & ((1<<4) | (1<<6))) { // rxmis 77 /* while fifo is not empty, read chars out of it */ 78 while ((UARTREG(uart_base, UART_FR) & (1<<4)) == 0) { 79 /* if we're out of rx buffer, mask the irq instead of handling it */ 80 if (cbuf_space_avail(&uart_rx_buf) == 0) { 81 UARTREG(uart_base, UART_IMSC) &= ~((1<<4)|(1<<6)); // !rxim 82 break; 83 } 84 85 char c = UARTREG(uart_base, UART_DR); 86 cbuf_write_char(&uart_rx_buf, c); 87 } 88 } 89 spin_lock(&uart_spinlock); 90 if (isr & (1<<5)) { 91 /* 92 * Signal any waiting Tx and mask Tx interrupts once we 93 * wakeup any blocked threads 94 */ 95 event_signal(&uart_dputc_event, true); 96 pl011_mask_tx(); 97 } 98 spin_unlock(&uart_spinlock); 99} 100 101static void pl011_uart_init(const void* driver_data, uint32_t length) 102{ 103 // create circular buffer to hold received data 104 cbuf_initialize(&uart_rx_buf, RXBUF_SIZE); 105 106 // assumes interrupts are contiguous 107 zx_status_t status = register_int_handler(uart_irq, &pl011_uart_irq, NULL); 108 DEBUG_ASSERT(status == ZX_OK); 109 110 // clear all irqs 111 UARTREG(uart_base, UART_ICR) = 0x3ff; 112 113 // set fifo trigger level 114 UARTREG(uart_base, UART_IFLS) = 0; // 1/8 rxfifo, 1/8 txfifo 115 116 // enable rx interrupt 117 UARTREG(uart_base, UART_IMSC) = (1 << 4 ) | // rxim 118 (1 << 6); // rtim 119 120 // enable receive 121 UARTREG(uart_base, UART_CR) |= (1<<9); // rxen 122 123 // enable interrupt 124 unmask_interrupt(uart_irq); 125 126 if (dlog_bypass() == true) 127 uart_tx_irq_enabled = false; 128 else { 129 /* start up tx driven output */ 130 printf("UART: started IRQ driven TX\n"); 131 uart_tx_irq_enabled = true; 132 } 133} 134 135static int pl011_uart_getc(bool wait) 136{ 137 char c; 138 if (cbuf_read_char(&uart_rx_buf, &c, wait) == 1) { 139 UARTREG(uart_base, UART_IMSC) |= ((1<<4)|(1<<6)); // rxim 140 return c; 141 } 142 143 return ZX_ERR_INTERNAL; 144} 145 146/* panic-time getc/putc */ 147static int pl011_uart_pputc(char c) 148{ 149 /* spin while fifo is full */ 150 while (UARTREG(uart_base, UART_FR) & (1<<5)) 151 ; 152 UARTREG(uart_base, UART_DR) = c; 153 154 return 1; 155} 156 157static int pl011_uart_pgetc(void) 158{ 159 if ((UARTREG(uart_base, UART_FR) & (1<<4)) == 0) { 160 return UARTREG(uart_base, UART_DR); 161 } else { 162 return -1; 163 } 164} 165 166static void pl011_dputs(const char* str, size_t len, 167 bool block, bool map_NL) 168{ 169 spin_lock_saved_state_t state; 170 bool copied_CR = false; 171 172 if (!uart_tx_irq_enabled) 173 block = false; 174 spin_lock_irqsave(&uart_spinlock, state); 175 while (len > 0) { 176 // Is FIFO Full ? 177 while (UARTREG(uart_base, UART_FR) & (1<<5)) { 178 if (block) { 179 /* Unmask Tx interrupts before we block on the event */ 180 pl011_unmask_tx(); 181 spin_unlock_irqrestore(&uart_spinlock, state); 182 event_wait(&uart_dputc_event); 183 } else { 184 spin_unlock_irqrestore(&uart_spinlock, state); 185 arch_spinloop_pause(); 186 } 187 spin_lock_irqsave(&uart_spinlock, state); 188 } 189 if (!copied_CR && map_NL && *str == '\n') { 190 copied_CR = true; 191 UARTREG(uart_base, UART_DR) = '\r'; 192 } else { 193 copied_CR = false; 194 UARTREG(uart_base, UART_DR) = *str++; 195 len--; 196 } 197 } 198 spin_unlock_irqrestore(&uart_spinlock, state); 199} 200 201static void pl011_start_panic(void) 202{ 203 uart_tx_irq_enabled = false; 204} 205 206static const struct pdev_uart_ops uart_ops = { 207 .getc = pl011_uart_getc, 208 .pputc = pl011_uart_pputc, 209 .pgetc = pl011_uart_pgetc, 210 .start_panic = pl011_start_panic, 211 .dputs = pl011_dputs, 212}; 213 214static void pl011_uart_init_early(const void* driver_data, uint32_t length) { 215 ASSERT(length >= sizeof(dcfg_simple_t)); 216 const dcfg_simple_t* driver = driver_data; 217 ASSERT(driver->mmio_phys && driver->irq); 218 219 uart_base = periph_paddr_to_vaddr(driver->mmio_phys); 220 ASSERT(uart_base); 221 uart_irq = driver->irq; 222 223 UARTREG(uart_base, UART_CR) = (1<<8)|(1<<0); // tx_enable, uarten 224 225 pdev_register_uart(&uart_ops); 226} 227 228LK_PDEV_INIT(pl011_uart_init_early, KDRV_PL011_UART, pl011_uart_init_early, LK_INIT_LEVEL_PLATFORM_EARLY); 229LK_PDEV_INIT(pl011_uart_init, KDRV_PL011_UART, pl011_uart_init, LK_INIT_LEVEL_PLATFORM); 230