Drivers: Added start of 16550 UART driver

Seems to work well enough to take over the OpenSBI putchar

TODO: Add some kind of flush() to avoid trashing data still in the FIFO
when we take over from OpenSBI
This commit is contained in:
Thomas Muller 2022-07-31 23:29:24 -04:00
parent 259d929c13
commit 80dc438fd0
Signed by: thomas
GPG key ID: AF006EB730564952
6 changed files with 314 additions and 3 deletions

View file

@ -0,0 +1,161 @@
#include <stdint.h>
namespace drivers {
namespace uart_16550 {
struct [[gnu::packed]] IER {
uint8_t data_ready : 1;
uint8_t thr_empty : 1;
uint8_t receiver_line_status : 1;
uint8_t modem_status : 1;
uint8_t : 2;
uint8_t dma_rx_end : 1;
uint8_t dma_tx_end : 1;
};
struct [[gnu::packed]]ISR {
uint8_t status : 1;
uint8_t fifos_enabled : 2;
uint8_t dma_tx_end : 1;
uint8_t dma_rx_end : 1;
uint8_t id_code : 3;
};
struct [[gnu::packed]] FCR {
uint8_t fifo_enable : 1;
uint8_t rx_fifo_reset : 1;
uint8_t tx_fifo_reset : 1;
uint8_t dma_mode : 1;
uint8_t enable_dma_end : 1;
uint8_t : 1;
uint8_t rx_fifo_trigger_level : 2;
};
struct [[gnu::packed]] LCR {
uint8_t word_length : 2;
uint8_t stop_bits : 1;
uint8_t parity_enable : 1;
uint8_t even_parity : 1;
uint8_t force_parity : 1;
uint8_t set_break : 1;
uint8_t dlab : 1;
};
struct [[gnu::packed]] MCR {
uint8_t dtr : 1;
uint8_t rts : 1;
uint8_t out_1 : 1;
uint8_t out2_int_enable : 1;
uint8_t loopback : 1;
uint8_t : 3;
};
struct [[gnu::packed]] LSR {
uint8_t data_ready : 1;
uint8_t overrun : 1;
uint8_t parity : 1;
uint8_t framing : 1;
uint8_t break_interrupt : 1;
uint8_t thr_empty : 1;
uint8_t tx_empty : 1;
uint8_t fifo_data_error : 1;
};
struct [[gnu::packed]] PSD {
uint8_t prescaler_division_factor: 4;
uint8_t : 4;
};
struct [[gnu::packed]] MSR {
uint8_t delta_cts : 1;
uint8_t delta_dsr : 1;
uint8_t trailing_edge_ri : 1;
uint8_t delta_cd : 1;
uint8_t cts : 1;
uint8_t dsr : 1;
uint8_t ri : 1;
uint8_t cd : 1;
};
// TEMP
#define UART_16550_32_BIT
#ifdef UART_16550_32_BIT
struct [[gnu::packed]] Registers {
// 0x00
union {
uint8_t rhr;
uint8_t thr;
uint8_t dcl;
};
uint32_t : 24;
// 0x04
union {
IER ier;
uint8_t dch;
};
uint32_t : 24;
// 0x08
union {
ISR isr;
FCR fcr;
};
uint32_t : 24;
// 0x0C
LCR lcr;
uint32_t : 24;
// 0x10
MCR mcr;
uint32_t : 24;
// 0x14
union {
LSR lsr;
PSD psd;
};
uint32_t : 24;
// 0x18
MSR msr;
uint32_t : 24;
// 0x1C
uint8_t spr;
uint32_t : 24;
};
#else
struct [[gnu::packed]] Registers {
// 0x00
union {
uint8_t rhr;
uint8_t thr;
uint8_t dcl;
};
// 0x01
union {
IER ier;
uint8_t dch;
};
// 0x02
union {
ISR isr;
FCR fcr;
};
// 0x03
LCR lcr;
// 0x04
MCR mcr;
// 0x05
union {
LSR lsr;
PSD psd;
};
// 0x06
MSR msr;
// 0x07
uint8_t spr;
};
#endif
} // End namespace uart_16550
} // End namespace drivers

View file

@ -0,0 +1,116 @@
#pragma once
#include "registers.h"
#include <stdint.h>
namespace drivers {
namespace uart_16550 {
enum class DataBits {
BITS_5,
BITS_6,
BITS_7,
BITS_8,
};
enum class StopBits {
STOP_1,
STOP_1_5,
STOP_2,
};
enum class Parity {
NONE,
EVEN,
ODD,
};
struct Uart16550 {
Uart16550(void * const addr) :
m_registers(*reinterpret_cast<Registers *>(addr)) {
}
void init(
const int baud_rate,
const DataBits data_bits,
const Parity parity,
const StopBits stop_bits) const volatile {
switch(data_bits) {
case DataBits::BITS_5:
m_registers.lcr.word_length = 0;
break;
case DataBits::BITS_6:
m_registers.lcr.word_length = 1;
break;
case DataBits::BITS_7:
m_registers.lcr.word_length = 2;
break;
case DataBits::BITS_8:
m_registers.lcr.word_length = 3;
break;
}
switch(parity) {
case Parity::NONE:
m_registers.lcr.parity_enable = 0;
break;
case Parity::EVEN:
m_registers.lcr.parity_enable = 1;
m_registers.lcr.even_parity = 1;
break;
case Parity::ODD:
m_registers.lcr.parity_enable = 1;
m_registers.lcr.even_parity = 0;
break;
}
switch(stop_bits) {
case StopBits::STOP_1:
m_registers.lcr.stop_bits = 0;
break;
case StopBits::STOP_1_5:
// TODO: Throw error if data_bits isnt BITS_5
m_registers.lcr.stop_bits = 0;
break;
case StopBits::STOP_2:
// TODO: Throw error if data_bits is BITS_5
m_registers.lcr.stop_bits = 1;
break;
}
// Assert DLAB to allow setting the divisor
// TODO: Assert CHCFG_AT_BUSY (UART_HALT[1])?
m_registers.lcr.dlab = 1;
// TODO: Get actual clock from TCC
const uint16_t divisor = 24000000 / baud_rate / 16;
m_registers.dch = divisor >> 8;
m_registers.dcl = divisor & 0xFF;
// TODO: Assert CHANGE_UPDATE (UART_HALT[2])?
// TODO: Wait for CHANGE_UPDATE (UART_HALT[2]) to be deasserted?
// Deassert DLAB to return to normal operation
m_registers.lcr.dlab = 0;
// Disable interrupts
// TODO: Move to its own function
// TODO: Cheese?
reinterpret_cast<volatile uint8_t &>(m_registers.ier) = 0;
// Enable FIFO
// TODO: Move to its own function
m_registers.fcr.fifo_enable = 1;
}
void write(const char c) {
while(m_registers.lsr.thr_empty == 0);
m_registers.thr = c;
}
private:
volatile Registers &m_registers;
};
} // End namespace uart_16550
} // End namespace drivers

View file

@ -0,0 +1,9 @@
kernel_sources += [
files(
'uart_16550.cpp',
),
]
kernel_includes += include_directories(
'include'
)

View file

@ -0,0 +1,8 @@
#include "uart_16550/uart_16550.h"
namespace drivers {
namespace uart_16550 {
} // End namespace uart_16550
} // End namespace drivers

View file

@ -14,6 +14,7 @@
#include <opensbi/extensions/pmu.h> #include <opensbi/extensions/pmu.h>
#include <sunxi_d1_pinctrl/sunxi_d1_pinctrl.h> #include <sunxi_d1_pinctrl/sunxi_d1_pinctrl.h>
#include <uart_16550/uart_16550.h>
void fmt(const char *f, ...) { void fmt(const char *f, ...) {
namespace legacy = drivers::opensbi::legacy; namespace legacy = drivers::opensbi::legacy;
@ -107,21 +108,36 @@ extern "C" void kmain(unsigned long hart, void *dtb) {
fmt("imp id: {}\n", ret.value); fmt("imp id: {}\n", ret.value);
} }
fmt("uintmax: {} bits\n", sizeof(uintmax_t) * 8); fmt("uintmax: {} bits\n", sizeof(uintmax_t) * 8);
fmt("HART: {}, DTB: {}\n", hart, dtb); fmt("HART: {}, DTB: {}\n", hart, dtb);
// ==================
// == Pinctrl test ==
// ==================
namespace sunxi_d1_pinctrl = drivers::sunxi_d1_pinctrl; namespace sunxi_d1_pinctrl = drivers::sunxi_d1_pinctrl;
sunxi_d1_pinctrl::SunxiD1Pinctrl pinctrl((void *)0x2000000); sunxi_d1_pinctrl::SunxiD1Pinctrl pinctrl((void *)0x2000000);
pinctrl.set_pin_mode(sunxi_d1_pinctrl::Bank::PC, 1, 1); pinctrl.set_pin_mode(sunxi_d1_pinctrl::Bank::PC, 1, 1);
pinctrl.set_pin_state(sunxi_d1_pinctrl::Bank::PC, 1, true); pinctrl.set_pin_state(sunxi_d1_pinctrl::Bank::PC, 1, true);
// ===============
// == UART test ==
// ===============
namespace uart_16550 = drivers::uart_16550;
uart_16550::Uart16550 uart((void *)0x02500000);
// uart_16550::Uart16550 uart((void *)0x02500400);
uart.init(115200, uart_16550::DataBits::BITS_8, uart_16550::Parity::NONE, uart_16550::StopBits::STOP_1);
while(true) {
uart.write('X');
uart.write('\n');
uint32_t a = 0;
for(a = 0; a < 0x10000000; a++);
}
fmt("HALT\n"); fmt("HALT\n");
while(1); while(1);
namespace hsm = drivers::opensbi::hsm; namespace hsm = drivers::opensbi::hsm;
hsm::hart_stop(); hsm::hart_stop();
while(1);
} }

View file

@ -13,6 +13,7 @@ kernel_cpp_args = []
kernel_link_args = ['-nostdlib'] kernel_link_args = ['-nostdlib']
subdir('drivers/opensbi') subdir('drivers/opensbi')
subdir('drivers/uart_16550')
subdir('drivers/sunxi_d1_pinctrl') subdir('drivers/sunxi_d1_pinctrl')
# subdir('common/dtb') # subdir('common/dtb')