#include "platform.h"
/*
* The UART control registers are memory-mapped at address UART0.
* This macro returns the address of one of the registers.
*
* code
#define UART_REG(reg) ((volatile uint8_t *)(UART0 + reg))
*/
/*
uart_puts
void uart_puts(char *s)
{
while (*s) {
uart_putc(*s++);
}
}
*/
#define THR 0
#define BOOT_STATUS_RELOCATE_DONE 1
.macro UART_REG reg:req
\reg(UART0)
.endm
/*
* Reference
* [1]: TECHNICAL DATA ON 16550, http://byterunner.com/16550.html
*/
/*
* UART control registers map. see [1] "PROGRAMMING TABLE"
* note some are reused by multiple functions
* 0 (write mode): THR/DLL
* 1 (write mode): IER/DLM
*/
#define RHR 0 // Receive Holding Register (read mode)
#define THR 0 // Transmit Holding Register (write mode)
#define DLL 0 // LSB of Divisor Latch (write mode)
#define IER 1 // Interrupt Enable Register (write mode)
#define DLM 1 // MSB of Divisor Latch (write mode)
#define FCR 2 // FIFO Control Register (write mode)
#define ISR 2 // Interrupt Status Register (read mode)
#define LCR 3 // Line Control Register
#define MCR 4 // Modem Control Register
#define LSR 5 // Line Status Register
#define MSR 6 // Modem Status Register
#define SPR 7 // ScratchPad Register
/*
* POWER UP DEFAULTS
* IER = 0: TX/RX holding register interrupts are both disabled
* ISR = 1: no interrupt penting
* LCR = 0
* MCR = 0
* LSR = 60 HEX
* MSR = BITS 0-3 = 0, BITS 4-7 = inputs
* FCR = 0
* TX = High
* OP1 = High
* OP2 = High
* RTS = High
* DTR = High
* RXRDY = High
* TXRDY = Low
* INT = Low
*/
/*
* LINE STATUS REGISTER (LSR)
* LSR BIT 0:
* 0 = no data in receive holding register or FIFO.
* 1 = data has been receive and saved in the receive holding register or FIFO.
* ......
* LSR BIT 5:
* 0 = transmit holding register is full. 16550 will not accept any data for transmission.
* 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character.
* ......
#define LSR_RX_READY (1 << 0)
#define LSR_TX_IDLE (1 << 5)
*/
.macro LSR_RX_READY
li s1, 1
slli s1, s1, 0
.endm
.macro LSR_TX_IDLE
li s1, 1
slli s1, s1, 5
.endm
.macro uart_read_reg regs:vararg
li s1, UART0
lb s0, \regs(s1)
.endm
.macro uart_write_reg reg,val
li s1, UART0
sb \val, \reg(s1)
.endm
.global uart_puts
uart_puts:
add sp, sp,-16
sw s0, THR(sp)
sw s1, 4(sp)
sw s2, 8(sp)
sw x1, 12(sp)
mv s2, a0
putc_loop:
lb s0, 0(s2)
beqz s0,uart_puts_exit
lb s1, 0(s2)
addi s2,s2,1
mv a0, s1
call uart_putc
j putc_loop
uart_puts_exit:
lw x1, 12(sp)
lw s2, 8(sp)
lw s1, 4(sp)
lw s0, 0(sp)
add sp,sp,16
ret
/*
int uart_putc(char ch)
{
while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0);
return uart_write_reg(THR, ch);
}
*/
.global uart_putc
uart_putc:
add sp, sp,-12
sw s0, 0(sp)
sw s1, 4(sp)
sw s2, 8(sp)
mv s2, a0
check_lsr_tx_idle:
uart_read_reg LSR
LSR_RX_READY
and s0, s0, s1
beq s0,s1,check_lsr_tx_idle
uart_write_reg THR, s2
lw s2, 8(sp)
lw s1, 4(sp)
lw s0, 0(sp)
add sp,sp,12
ret
#if 0
void uart_init()
{
/* disable interrupts. */
uart_write_reg(IER, 0x00);
/*
* Setting baud rate. Just a demo here if we care about the divisor,
* but for our purpose [QEMU-virt], this doesn't really do anything.
*
* Notice that the divisor register DLL (divisor latch least) and DLM (divisor
* latch most) have the same base address as the receiver/transmitter and the
* interrupt enable register. To change what the base address points to, we
* open the "divisor latch" by writing 1 into the Divisor Latch Access Bit
* (DLAB), which is bit index 7 of the Line Control Register (LCR).
*
* Regarding the baud rate value, see [1] "BAUD RATE GENERATOR PROGRAMMING TABLE".
* We use 38.4K when 1.8432 MHZ crystal, so the corresponding value is 3.
* And due to the divisor register is two bytes (16 bits), so we need to
* split the value of 3(0x0003) into two bytes, DLL stores the low byte,
* DLM stores the high byte.
*/
uint8_t lcr = uart_read_reg(LCR);
uart_write_reg(LCR, lcr | (1 << 7));
uart_write_reg(DLL, 0x03);
uart_write_reg(DLM, 0x00);
/*
* Continue setting the asynchronous data communication format.
* - number of the word length: 8 bits
* - number of stop bits:1 bit when word length is 8 bits
* - no parity
* - no break control
* - disabled baud latch
*/
lcr = 0;
uart_write_reg(LCR, lcr | (3 << 0));
}
#endif
.global uart_init
uart_init:
add sp, sp,-12
sw s0, THR(sp)
sw s1, 4(sp)
sw s2, 8(sp)
li s0, 0x00
uart_write_reg IER s0
li s2, 1
slli s2,s2,7
uart_read_reg LCR
and s0,s0,s2
uart_write_reg LCR s0
li s0, 0x03
uart_write_reg DLL s0
li s0, 0x00
uart_write_reg DLM s0
li s0,3
uart_write_reg LCR s0
lw s2, 8(sp)
lw s1, 4(sp)
lw s0, 0(sp)
add sp,sp,12
ret