mini-uart.zig (13617B)
1 const std = @import("std"); 2 3 const StackRingBuffer = @import("shared").StackRingBuffer; 4 5 const interrupts = @import("../interrupts.zig"); 6 const mem = @import("../mem.zig"); 7 8 const clock = @import("./clock.zig"); 9 const gpio = @import("./gpio.zig"); 10 11 pub const Error = error{ AlreadyInitialized, NotInitialized, InvalidReadLimit, Timeout } || gpio.Error; 12 13 // Page 8: Auxiliary peripherals Register Map 14 const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x0021_5000; 15 const AUX_ENABLES: usize = BASE_ADDRESS + 0x0004; 16 const AUX_MU_IO_REG: usize = BASE_ADDRESS + 0x0040; 17 const AUX_MU_IER_REG: usize = BASE_ADDRESS + 0x0044; 18 const AUX_MU_IIR_REG: usize = BASE_ADDRESS + 0x0048; 19 const AUX_MU_LCR_REG: usize = BASE_ADDRESS + 0x004C; 20 const AUX_MU_MCR_REG: usize = BASE_ADDRESS + 0x0050; 21 const AUX_MU_LSR_REG: usize = BASE_ADDRESS + 0x0054; 22 const AUX_MU_MSR_REG: usize = BASE_ADDRESS + 0x0058; 23 const AUX_MU_SCRATCH: usize = BASE_ADDRESS + 0x005C; 24 const AUX_MU_CNTL_REG: usize = BASE_ADDRESS + 0x0060; 25 const AUX_MU_STAT_REG: usize = BASE_ADDRESS + 0x0064; 26 const AUX_MU_BAUD_REG: usize = BASE_ADDRESS + 0x0068; 27 28 const CLOCK_FREQ = 250_000_000; 29 30 const TxPin = enum(u8) { 31 Gpio14 = 14, 32 Gpio32 = 32, 33 Gpio36 = 36, 34 }; 35 const RxPin = enum(u8) { 36 Gpio15 = 15, 37 Gpio33 = 33, 38 Gpio37 = 37, 39 }; 40 41 var initialized = false; 42 var rx_interrupts_enabled = false; 43 var tx_interrupts_enabled = false; 44 var use_tx_interrupts = false; 45 46 pub fn initialize(baud: u32, tx_pin: TxPin, rx_pin: RxPin) Error!void { 47 if (initialized) return Error.AlreadyInitialized; 48 49 // Set GPIO pins first (as specified by manual) 50 // Page 102 specifies which alt mode 51 const tx_fn: gpio.FunctionSelect = switch (tx_pin) { 52 .Gpio14 => .alt_fn_5, 53 .Gpio32 => .alt_fn_3, 54 .Gpio36 => .alt_fn_2, 55 }; 56 const rx_fn: gpio.FunctionSelect = switch (rx_pin) { 57 .Gpio15 => .alt_fn_5, 58 .Gpio33 => .alt_fn_3, 59 .Gpio37 => .alt_fn_2, 60 }; 61 62 // The error case is technically unreachable due to hardcoding gpio pin values 63 // But at the same time this function body may change with time so it's important 64 // to keep the Error 65 try gpio.fn_sel(@intFromEnum(tx_pin), tx_fn); 66 try gpio.fn_sel(@intFromEnum(rx_pin), rx_fn); 67 68 try gpio.set_pull(@intFromEnum(tx_pin), .Off); 69 try gpio.set_pull(@intFromEnum(rx_pin), .Off); 70 71 // AUX_ENABLES needs bit 0 set to 1 72 // Page 9: AUXENB Register 73 mem.put_u32(@ptrFromInt(AUX_ENABLES), 1); 74 75 // Disable interrupts 76 // Page 12: AUX_MU_IER_REG Register 77 // When bit 0/1 is 0, interrupts are disabled 78 mem.put_u32(@ptrFromInt(AUX_MU_IER_REG), 0); 79 80 // Disable RX/TX during configuration 81 // Page 17: AUX_MU_CNTL_REG Register 82 // When bit 0/1 is 0, UART receiver/transmitter is disabled 83 mem.put_u32(@ptrFromInt(AUX_MU_CNTL_REG), 0); 84 85 // Set data size to 8 bits 86 // Page 14: AUX_MU_LCR_REG Register 87 // When bit 0 is 1, UART is in 8-bit mode, else 7-bit 88 // Errata: "bit 1 must be set for 8 bit mode" 89 mem.put_u32(@ptrFromInt(AUX_MU_LCR_REG), 0b11); 90 91 // Put RTS high (indicate request to send) 92 // Page 14: AUX_MU_MCR_REG Register 93 // When bit 1 is 0, RTS line is high, else low 94 mem.put_u32(@ptrFromInt(AUX_MU_MCR_REG), 0); 95 96 // Clear FIFO 97 // Page 13: AUX_MU_IER_REG Register 98 // When bit 1/2 is 1, receive/transmit will be cleared 99 mem.put_u32(@ptrFromInt(AUX_MU_IIR_REG), 0b110); 100 101 // Set baud rate 102 // Page 11: 2.2.1 Mini UART Implementation Details 103 // The baudrate formula is given as (system_clock_freq)/(8 * (baudrate_reg + 1)) 104 mem.put_u32(@ptrFromInt(AUX_MU_BAUD_REG), CLOCK_FREQ / (8 * baud) - 1); 105 106 // Enable RX/TX again 107 // Page 17: AUX_MU_CNTL_REG Register 108 // When bit 0/1 is 1, UART receiver/transmitter is enabled 109 mem.put_u32(@ptrFromInt(AUX_MU_CNTL_REG), 0b11); 110 111 mem.barrier(.Write); 112 113 initialized = true; 114 } 115 116 pub fn set_rx_interrupts(state: bool) Error!void { 117 if (state) { 118 try enable_rx_interrupts(); 119 } else { 120 try disable_rx_interrupts(); 121 } 122 } 123 124 fn interrupt_state(tx: bool, rx: bool) void { // Enable RX/TX Interrupts 125 // Page 12: AUX_MU_IIR_REG Register 126 // Bit 0 - RX Interrupt 127 // Bit 1 - TX Interrupt 128 // Errata: "Bits 1:0 are swapped. bit 0 is receive 129 // interrupt and bit 1 is transmit." 130 // Errata: "Bits 3:2 are marked as don't care, but 131 // are actually required in order to receive interrupts." 132 if (tx and rx) { 133 mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b1111); 134 } else if (tx and !rx) { 135 mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b1110); 136 } else if (!tx and rx) { 137 mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b0101); 138 } else if (!tx and !rx) { 139 mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b0000); 140 } 141 } 142 143 // Separate function to allow TX used during early boot 144 fn enable_rx_interrupts() Error!void { 145 if (!initialized) return Error.NotInitialized; 146 if (rx_interrupts_enabled) return Error.AlreadyInitialized; 147 148 mem.barrier(.Write); 149 150 interrupts.set_exception_handler(.IRQ, uart_handler); 151 interrupts.enable_peripheral_interrupt(.AuxInterrupt); 152 153 interrupt_state(tx_interrupts_enabled, true); 154 155 rx_interrupts_enabled = true; 156 } 157 158 fn disable_rx_interrupts() Error!void { 159 if (!initialized) return Error.NotInitialized; 160 if (!rx_interrupts_enabled) return Error.NotInitialized; 161 162 mem.barrier(.Write); 163 164 interrupt_state(tx_interrupts_enabled, false); 165 166 rx_interrupts_enabled = false; 167 } 168 169 pub fn set_tx_interrupts(state: bool) void { 170 use_tx_interrupts = state; 171 172 if (state) { 173 interrupts.set_exception_handler(.IRQ, uart_handler); 174 interrupts.enable_peripheral_interrupt(.AuxInterrupt); 175 } else { 176 drain_write_queue(); 177 } 178 } 179 180 fn enable_tx_interrupt() !void { 181 if (!initialized) return Error.NotInitialized; 182 183 interrupt_state(true, rx_interrupts_enabled); 184 185 tx_interrupts_enabled = true; 186 } 187 188 fn disable_tx_interrupt() !void { 189 if (!initialized) return Error.NotInitialized; 190 191 interrupt_state(false, rx_interrupts_enabled); 192 193 tx_interrupts_enabled = false; 194 } 195 196 fn drain_write_queue() void { 197 const cs = mem.enter_critical_section(); 198 defer cs.exit(); 199 200 while (tx_list.pop()) |b| { 201 write_byte_sync(b); 202 } 203 } 204 205 var tx_list: StackRingBuffer(u8, 128) = .init(); 206 207 var rx_list: StackRingBuffer(u8, 128) = .init(); 208 var rx_writer: ?*std.Io.Writer = null; 209 var rx_writer_written: usize = 0; 210 211 pub fn switch_rx_writer(rx_w: ?*std.Io.Writer) !void { 212 const cs = mem.enter_critical_section(); 213 defer cs.exit(); 214 215 rx_writer_written = 0; 216 217 if (rx_w) |w| { 218 // Copy buffer into new writer 219 while (rx_list.pop()) |b| { 220 try w.writeByte(b); 221 rx_writer_written += 1; 222 } 223 } 224 225 rx_writer = rx_w; 226 } 227 228 pub fn get_rx_written() usize { 229 const cs = mem.enter_critical_section(); 230 defer cs.exit(); 231 232 return rx_writer_written; 233 } 234 235 noinline fn uart_handler(_: interrupts.Registers, _: interrupts.ExceptionVector) void { 236 mem.barrier(.Write); 237 defer mem.barrier(.Write); 238 239 if (!initialized) return; 240 if (!interrupts.pending_peripheral_interrupt(.AuxInterrupt)) return; 241 242 while (true) { 243 const IIR = mem.get_u32(@ptrFromInt(AUX_MU_IIR_REG)); 244 245 // UART interrupt pending 246 if ((IIR & 0b1) == 1) return; 247 248 switch (IIR & 0b110) { 249 // RX has byte 250 0b100 => { 251 const b = read_byte_raw(); 252 if (rx_writer) |w| { 253 rx_writer_written += 1; 254 w.writeByte(b) catch {}; 255 } else { 256 rx_list.push(b) catch {}; 257 } 258 }, 259 // TX has space 260 0b010 => { 261 if (tx_list.pop()) |byte| { 262 mem.put_u32_barrier(@ptrFromInt(AUX_MU_IO_REG), @as(u32, byte) & 0xFF); 263 } else { 264 disable_tx_interrupt() catch {}; 265 return; 266 } 267 }, 268 // Unknown 269 else => break, 270 } 271 } 272 } 273 274 pub fn is_initialized() bool { 275 return initialized; 276 } 277 278 const Status = packed struct(u32) { 279 rx_has_symbol: bool, 280 tx_has_space: bool, 281 rx_is_idle: bool, 282 tx_is_idle: bool, 283 rx_overrun: bool, 284 tx_full: bool, 285 rts_status: bool, 286 ctx_status: bool, 287 tx_is_empty: bool, 288 tx_done: bool, 289 _reserved_10_15: u6, 290 rx_fill_level: u4, 291 _reserved_20_23: u4, 292 tx_fill_level: u4, 293 _reserved_28_31: u4, 294 }; 295 296 pub fn status() Status { 297 return @bitCast(mem.get_u32_barrier(@ptrFromInt(AUX_MU_STAT_REG))); 298 } 299 300 pub fn read_queue_length() usize { 301 const cs = mem.enter_critical_section(); 302 defer cs.exit(); 303 304 return rx_list.length(); 305 } 306 307 pub fn write_queue_length() usize { 308 // Queuing disabled 309 const cs = mem.enter_critical_section(); 310 defer cs.exit(); 311 312 return tx_list.length(); 313 } 314 315 pub fn can_read() bool { 316 // Check if FIFO has data 317 // Page 15: AUX_MU_LSR_REG Register 318 // Bit 1 is set when FIFO holds at least 1 byte 319 return read_queue_length() > 0 or can_read_raw(); 320 } 321 322 // // TODO; is this necessary 323 inline fn can_read_raw() bool { 324 return (mem.get_u32_barrier(@ptrFromInt(AUX_MU_LSR_REG)) & 0x01) == 1; 325 } 326 327 pub fn read_byte_sync_timeout(timeout: ?u64) !u8 { 328 var wait_until: u64 = std.math.maxInt(u64); 329 330 if (timeout) |t| { 331 wait_until = clock.current_count() + t; 332 } 333 334 while (true) { 335 if (clock.current_count() > wait_until) { 336 return Error.Timeout; 337 } 338 339 if (!can_read()) continue; 340 341 if (read_queue_length() > 0) { 342 return read_byte_async().?; 343 } else { 344 return read_byte_raw(); 345 } 346 347 return; 348 } 349 } 350 351 pub fn read_byte_sync() u8 { 352 return read_byte_sync_timeout(null) catch unreachable; 353 } 354 355 pub fn read_byte_async() ?u8 { 356 const cs = mem.enter_critical_section(); 357 defer cs.exit(); 358 359 return rx_list.pop(); 360 } 361 362 inline fn read_byte_raw() u8 { 363 return @truncate(mem.get_u32_barrier(@ptrFromInt(AUX_MU_IO_REG))); 364 } 365 366 pub fn can_write() bool { 367 // Check if FIFO can accept data 368 // Page 15: AUX_MU_LSR_REG Register 369 // Bit 5 is set when FIFO can accept at least 1 byte 370 return (mem.get_u32_barrier(@ptrFromInt(AUX_MU_LSR_REG)) & 0x20) != 0; 371 } 372 373 // pub fn write_queued() usize { 374 // return tx_list.items.len; 375 // } 376 377 pub fn write_byte_async(byte: u8) !void { 378 const cs = mem.enter_critical_section(); 379 defer cs.exit(); 380 381 try tx_list.push(byte); 382 383 // Let TX start draining 384 enable_tx_interrupt() catch {}; 385 } 386 387 pub fn write_byte_sync_timeout(byte: u8, timeout: ?u64) !void { 388 var wait_until: u64 = std.math.maxInt(u64); 389 390 if (timeout) |t| { 391 wait_until = clock.current_count() + t; 392 } 393 394 while (true) { 395 if (clock.current_count() > wait_until) { 396 return Error.Timeout; 397 } 398 399 if (!can_write()) continue; 400 401 write_byte_raw(byte); 402 403 return; 404 } 405 } 406 407 pub fn write_byte_sync(byte: u8) void { 408 write_byte_sync_timeout(byte, null) catch {}; 409 } 410 411 inline fn write_byte_raw(byte: u8) void { 412 mem.put_u32_barrier(@ptrFromInt(AUX_MU_IO_REG), @as(u32, byte) & 0xFF); 413 } 414 415 pub fn write_byte(byte: u8) void { 416 if (use_tx_interrupts) { 417 write_byte_async(byte) catch { 418 write_byte_sync(byte); 419 }; 420 } else { 421 write_byte_sync(byte); 422 } 423 } 424 425 pub fn write_slice(bytes: []const u8) void { 426 for (bytes) |b| { 427 write_byte(b); 428 } 429 } 430 431 pub fn write_slice_sync(bytes: []const u8) void { 432 for (bytes) |b| { 433 write_byte_sync(b); 434 } 435 } 436 437 pub fn flush() void { 438 // Loop until there's nothing left in TX queue 439 while (true) { 440 const s = status(); 441 442 if (tx_interrupts_enabled == false and write_queue_length() > 0) { 443 drain_write_queue(); 444 } 445 446 if (s.tx_is_empty and s.tx_is_idle and write_queue_length() == 0) break; 447 } 448 } 449 450 fn writer_drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) !usize { 451 if (!initialized) return std.Io.Writer.Error.WriteFailed; 452 453 // We don't care about getting the "parent" 454 _ = io_w; 455 _ = splat; 456 457 write_slice(data[0]); 458 459 return data[0].len; 460 } 461 462 fn writer_flush(io_w: *std.Io.Writer) !void { 463 if (!initialized) return std.Io.Writer.Error.WriteFailed; 464 465 // We don't care about getting the "parent" 466 _ = io_w; 467 468 flush(); 469 } 470 471 pub fn print(comptime fmt: []const u8, args: anytype) void { 472 writer.print(fmt, args) catch {}; 473 } 474 475 const writer_vtable: std.Io.Writer.VTable = .{ .drain = writer_drain, .flush = writer_flush }; 476 pub var writer = std.Io.Writer{ .buffer = undefined, .vtable = &writer_vtable }; 477 478 fn stream(io_r: *std.Io.Reader, io_w: *std.Io.Writer, limit: std.Io.Limit) !usize { 479 // We don't care about getting the "parent" 480 _ = io_r; 481 482 if (limit.toInt()) |max| { 483 if (max > rx_list.items.len and rx_interrupts_enabled) { 484 // Use direct writes when there's a large buffer 485 try switch_rx_writer(io_w); 486 487 while (get_rx_written() < max) {} 488 489 try switch_rx_writer(null); 490 } else { 491 // Small buffers should just read blocking 492 for (0..max) |_| { 493 try io_w.writeByte(read_byte_sync()); 494 } 495 } 496 497 return max; 498 } else { 499 return std.Io.Reader.StreamError.ReadFailed; 500 } 501 } 502 503 const reader_vtable: std.Io.Reader.VTable = .{ .stream = stream }; 504 pub var reader = std.Io.Reader{ .buffer = undefined, .seek = 0, .end = 0, .vtable = &reader_vtable };