Assembly examples for Red-V Thing Plus (SiFive FE310-G002)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

218 lines
6.6 KiB

#
# p_uart0irq.s: Buffered interrupt-driven UART driver
#
# 2021 flabbergast@drak.xyz
# Unlicense: https://unlicense.org/
#
# UART0: RX:pin16 TX:pin17
# This interrupt-driven version requires a few things at other places:
# - the irq_handler_ function here plugged properly into the
# interrupt system (in p_interrupts.s)
# - and of course enabled interrupts for operation
#
# This allocates some memory: two buffers and 4 pointers
#
# Most of the code deals with the ring buffers; only a couple of places
# are FE310 specific: uart_init; irq_handler, and those marked with "HW"
.if compressed_isa
.balign 2, 0
.else
.balign 4, 0
.endif
.include "platform_regs.inc"
.equ uart0_bufsize, 128
ramallot uart0_txbuf, uart0_bufsize
ramallot uart0_rxbuf, uart0_bufsize
# two ring buffer pointers: from, upto
ramallot uart0_tx_pointers, 4*2
ramallot uart0_rx_pointers, 4*2
# the actual capacity is one less than bufsize:
# from=upto means empty
# init
# !! need to change baud setting if the main clock changes
# clobbers: t0, t1, t2
uart_init:
# initialise ring buffer pointers
li t0, uart0_tx_pointers
li t1, uart0_txbuf
sw t1, 0(t0)
sw t1, 4(t0)
li t0, uart0_rx_pointers
li t1, uart0_rxbuf
sw t1, 0(t0)
sw t1, 4(t0)
# initialise hardware: pins and peripheral
li t0, GPIO_BASE # UART0 RX/TX are selected in IOF_SEL (IOF0) on Reset.
li t2, (1<<17)|(1<<16) # which bits
lw t1, GPIO_IOF_EN(t0) # set IOF_EN bits ... read existing
or t1, t1, t2 # twiddle bits
sw t1, GPIO_IOF_EN(t0) # write back
not t2, t2 # invert bits
lw t1, GPIO_IOF_SEL(t0) # clear IOF_SEL bits (tho they should be fine on Reset)
and t1, t1, t2 # twiddle bits
sw t1, GPIO_IOF_SEL(t0) # write back
li t0, UART0_BASE # setting baud rate
li t1, 139-1 # 16 MHz / 115200 Baud = 138.89
sw t1, UART0_DIV(t0)
li t1, 1
sw t1, UART0_RXCTRL(t0) # enable receive
li t1, 1|(0b001<<16) # also set txcnt watermark to 1
sw t1, UART0_TXCTRL(t0) # enable transmit
# interrupts: UART0 is PLIC source 3
li t0, PLIC_BASE
li t1, 4 # priority (0=off - 7=highest)
sw t1, 3*4(t0) # set priority
li t0, PLIC_BASE+PLIC_ENABLE1
li t1, 1<<3
amoor.w zero, t1, (t0) # set bit3 in PLIC_ENABLE1
li t0, UART0_BASE
li t1, 0b10 # tx irq dis, rx irq en
sw t1, UART0_IE(t0)
ret
# set up registers for tx ringbuffer
# t1:data_from t2:data_upto+1 t0:ptrs
# t3:end_of_tx_buffer
_uart_emit_setup:
li t0, uart0_tx_pointers
lw t1, 0(t0) # data_from
lw t2, 4(t0) # data_upto
li t3, uart0_txbuf+uart0_bufsize # end of buffer
addi t2, t2, 1 # try to move up
bltu t2, t3, 1f # if we're still in the buf, skip
addi t2, t2, -uart0_bufsize # back to the buf start
1:
jr t4
# transmit one byte
# a0: (unsigned!) byte to send
# adds the byte to queue (if there is space)
# clobbers: t0, t1, t2, t3, t4
uart_emit:
jal t4, _uart_emit_setup # regs: t1:data_from t2:data_upto+1 t0:ptrs
bne t1, t2, 1f # if we didn't reach data_from, skip
ret # nothing to do, the buffer is full
1: # store the byte:
lw t1, 4(t0) # get address where we write, reuse t1
sb a0, 0(t1) # write the new byte into the buffer
sw t2, 4(t0) # record the new data_upto
li t0, UART0_BASE+UART0_IE # HW: enable tx irq
li t1, 1 # txwm (bit0)
amoor.w zero, t1, (t0) # set the bit
ret
# check if space in the tx queue
# returns: a0 non-zero if tx fifo full
# (possible to discern the available space from the value...)
# clobbers: t0, t1, t2, t3, t4
q_uart_emit:
jal t4, _uart_emit_setup # regs: t1:data_from t2:data_upto+1 t0:ptrs
sub a0, t2, t1
ret
# receive a byte
# returns: a0: received byte
# returns -1 if the buffer is empty
# clobbers: t0, t1, t2
uart_recv:
li t0, uart0_rx_pointers
lw t1, 0(t0) # data_from
lw t2, 4(t0) # data_upto
bne t1, t2, 1f # if rxbuf non-empty, skip
li a0, -1
ret
1:
li t2, uart0_rxbuf+uart0_bufsize # end of buffer, reuse t2
lbu a0, 0(t1) # get the byte
addi t1, t1, 1 # move up
bltu t1, t2, 1f # if still in the buf, skip
addi t1, t1, -uart0_bufsize # back to the buf start
1:
sw t1, 0(t0)
ret
# check if there is an incoming byte waiting
# returns: a0 non-zero if rx fifo empty
# clobbers: t0, t1, t2
q_uart_recv:
li t0, uart0_rx_pointers
lw t1, 0(t0) # data_from
lw t2, 4(t0) # data_upto
sub a0, t1, t2 # zero if rxbuf empty
seqz a0, a0 # invert
ret
# main UART0 irq handler that gets called from p_interrupts.s
# single isr for both TX and RX
# (clebbers all temporaries)
irq_handler_uart0:
li t4, UART0_BASE
lw t5, UART0_IE(t4) # for TX need to check _also_ IE reg
andi t6, t5, 1
lw t5, UART0_IP(t4)
and t6, t5, t6 # txwm(bit0)
beqz t6, 2f # TX not pending, skip
.uart0_tx_isr:
## TX interrupt service routine
li t0, uart0_tx_pointers # check if data to transmit
lw t1, 0(t0) # data_from
lw t2, 4(t0) # data_upto
bne t1, t2, 1f # if data to transmit, skip
lw t1, UART0_IE(t4) # HW: disable tx irq
li t2, -2 # ! txwm (bit0)
and t1, t1, t2
sw t1, UART0_IE(t4)
j 2f
1:
lbu t2, 0(t1) # get the byte to transmit, reuse t2
sw t2, UART0_TXDATA(t4) # HW: send it out
addi t1, t1, 1 # try to move up
li t2, uart0_txbuf+uart0_bufsize # end of buffer, reuse t2
bltu t1, t2, 1f # we're still in the buf, skip
addi t1, t1, -uart0_bufsize # back to the buf start
1:
sw t1, 0(t0) # store the new data_from
## end of TX interrupt service routine
2:
andi t6, t5, 2 # rxwm(bit1)
beqz t6, 2f
.uart0_rx_isr:
## RX interrupt service routine
# silently drops bytes if rxbuf is full
# clobbers: t0, t1, t2, t3
li t0, uart0_rx_pointers
lw t1, 0(t0) # data_from
lw t2, 4(t0) # data_upto
li t3, uart0_rxbuf+uart0_bufsize # end of buffer
addi t2, t2, 1 # try to move up
bltu t2, t3, 1f # if we're still in the buf, skip
addi t2, t2, -uart0_bufsize # back to the buf start
1:
lw t3, UART0_RXDATA(t4) # HW: read; bit31="empty", bit7-bit0 data; reuse t3
bne t1, t2, 1f # if there is space if the buf, skip
ret # buffer full, ignore the received byte
1:
lw t1, 4(t0) # get the address where to write, reuse t1
sb t3, 0(t1) # write to buf
sw t2, 4(t0) # record the new data_upto
## end of RX interrupt service routine
2:
ret