Stashing the state.
This commit is contained in:
parent
930b40bef2
commit
546b3002a4
19 changed files with 1138 additions and 5 deletions
|
@ -4,7 +4,7 @@
|
|||
# 2021 flabbergast@drak.xyz
|
||||
# Unlicense: https://unlicense.org/
|
||||
#
|
||||
.ifdef compressed_isa
|
||||
.if compressed_isa
|
||||
.balign 2, 0
|
||||
.else
|
||||
.balign 4, 0
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# 2021 flabbergast@drak.xyz
|
||||
# Unlicense: https://unlicense.org/
|
||||
#
|
||||
.ifdef compressed_isa
|
||||
.if compressed_isa
|
||||
.balign 2, 0
|
||||
.else
|
||||
.balign 4, 0
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# 2021 flabbergast@drak.xyz
|
||||
# Unlicense: https://unlicense.org/
|
||||
#
|
||||
.ifdef compressed_isa
|
||||
.if compressed_isa
|
||||
.balign 2, 0
|
||||
.else
|
||||
.balign 4, 0
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# p_i2c0.s: basic i2c driver
|
||||
# p_i2c.s: basic i2c driver
|
||||
#
|
||||
# 2021 flabbergast@drak.xyz
|
||||
# Unlicense: https://unlicense.org/
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# 2021 flabbergast@drak.xyz
|
||||
# Unlicense: https://unlicense.org/
|
||||
#
|
||||
.ifdef compressed_isa
|
||||
.if compressed_isa
|
||||
.balign 2, 0
|
||||
.else
|
||||
.balign 4, 0
|
||||
|
|
|
@ -132,3 +132,34 @@
|
|||
.equ I2C_SR_AL, (1 << 5)
|
||||
.equ I2C_SR_TIP, (1 << 1)
|
||||
.equ I2C_SR_IP, (1 << 0)
|
||||
|
||||
.equ QSPI0_BASE, 0x10014000
|
||||
.equ SPI1_BASE, 0x10024000
|
||||
.equ SPI2_BASE, 0x10034000
|
||||
.equ SPI_SCKDIV, 0x00
|
||||
.equ SPI_SCKMODE, 0x04
|
||||
.equ SPI_CSID, 0x10
|
||||
.equ SPI_CSDEF, 0x14
|
||||
.equ SPI_CSMODE, 0x18
|
||||
.equ SPI_DELAY0, 0x28
|
||||
.equ SPI_DELAY1, 0x2C
|
||||
.equ SPI_FMT, 0x40
|
||||
.equ SPI_TXDATA, 0x48
|
||||
.equ SPI_RXDATA, 0x4C
|
||||
.equ SPI_TXMARK, 0x50
|
||||
.equ SPI_RXMARK, 0x54
|
||||
.equ SPI_FCTRL, 0x60
|
||||
.equ SPI_FFMT, 0x64
|
||||
.equ SPI_IE, 0x70
|
||||
.equ SPI_IP, 0x74
|
||||
|
||||
.equ PWM0_BASE, 0x10015000
|
||||
.equ PWM1_BASE, 0x10025000
|
||||
.equ PWM2_BASE, 0x10035000
|
||||
.equ PWMCFG, 0x00
|
||||
.equ PWMCOUNT, 0x08
|
||||
.equ PWMS, 0x10
|
||||
.equ PWMCMP0, 0x20
|
||||
.equ PWMCMP1, 0x24
|
||||
.equ PWMCMP2, 0x28
|
||||
.equ PWMCMP3, 0x2C
|
||||
|
|
31
work/Makefile
Normal file
31
work/Makefile
Normal file
|
@ -0,0 +1,31 @@
|
|||
PROG ?= main
|
||||
|
||||
JLINKEXE ?= ~/.platformio/packages/tool-jlink/JLinkExe
|
||||
|
||||
PREFIX ?= riscv32-elf-
|
||||
ARCH ?= rv32imac
|
||||
|
||||
AFLAGS = --warn --fatal-warnings -g -L
|
||||
LDFLAGS = -m elf32lriscv -b elf32-littleriscv --discard-none
|
||||
|
||||
all: $(PROG).hex
|
||||
|
||||
$(PROG).o: $(PROG).s p_spi.s
|
||||
$(PREFIX)as -march=$(ARCH) $(AFLAGS) $(PROG).s -o $(PROG).o
|
||||
|
||||
$(PROG).hex: memmap $(PROG).o
|
||||
$(PREFIX)ld $(LDFLAGS) -o $(PROG).elf -T memmap $(PROG).o
|
||||
$(PREFIX)objdump -Mnumeric -D $(PROG).elf > $(PROG).list
|
||||
$(PREFIX)objcopy $(PROG).elf $(PROG).bin -O binary
|
||||
$(PREFIX)objcopy $(PROG).elf $(PROG).hex -O ihex
|
||||
$(PREFIX)size $(PROG).elf
|
||||
|
||||
flash: $(PROG).hex
|
||||
echo -e "loadfile $(PROG).hex\nr\ng\nexit\n" | $(JLINKEXE) -device FE310 -if JTAG -speed 4000 -si JTAG -jtagconf -1,-1 -autoconnect 1
|
||||
|
||||
clean:
|
||||
rm -f *.bin
|
||||
rm -f *.hex
|
||||
rm -f *.o
|
||||
rm -f *.elf
|
||||
rm -f *.list
|
13
work/README.md
Normal file
13
work/README.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# 08-i2c
|
||||
|
||||
Add a basic i2c driver. Really only 1 function, `i2c_rw` which writes some bytes to the i2c bus, and then reads some bytes from it (either/both phases can be skipped).
|
||||
|
||||
The "demo" code in `main.s` is for messing around with a 24C02 eeprom (256 bytes, so just 1 byte addressing).
|
||||
|
||||
|
||||
|
||||
### Lessons learned
|
||||
|
||||
- Having a debugger is a godsend; I should not ever try to start learning on a bare chip.
|
||||
- When I write new registers into `platform_regs.inc` by hand, re-check at least 17 times.
|
||||
- Re-check push/pop patterns, especially when returning early, e.g. from a macro.
|
50
work/macros.s
Normal file
50
work/macros.s
Normal file
|
@ -0,0 +1,50 @@
|
|||
#
|
||||
# macros.s
|
||||
#
|
||||
# 2021 flabbergast <flabbergast@drak.xyz>
|
||||
# Unlicense: https://unlicense.org/
|
||||
#
|
||||
|
||||
# designate some RAM memory to a symbol
|
||||
.macro ramallot Name, Size
|
||||
.equ \Name, rampointer
|
||||
.set rampointer, rampointer + \Size
|
||||
.endm
|
||||
|
||||
# stack: push, pop
|
||||
.macro push reg
|
||||
addi sp, sp, -4
|
||||
sw \reg, 0(sp)
|
||||
.endm
|
||||
.macro pop reg
|
||||
lw \reg, 0(sp)
|
||||
addi sp, sp, 4
|
||||
.endm
|
||||
|
||||
# insert a sized string
|
||||
.macro sized_string Text
|
||||
.byte 8f - 7f # Compute length of string.
|
||||
7: .ascii "\Text"
|
||||
.if compressed_isa
|
||||
8: .balign 2, 0 # Realign
|
||||
.else
|
||||
8: .balign 4, 0 # Realign
|
||||
.endif
|
||||
.endm
|
||||
|
||||
# data stack
|
||||
# _s11_ used as the data stack pointer!!
|
||||
.macro pushda reg
|
||||
addi s11, s11, -4
|
||||
sw \reg, 0(s11)
|
||||
.endm
|
||||
.macro popda reg
|
||||
lw \reg, 0(s11)
|
||||
addi s11, s11, 4
|
||||
.endm
|
||||
.macro pushdaconst const
|
||||
push t0 # yep ... should rethink this
|
||||
li t0, \const # but it should being used very little
|
||||
pushda t0
|
||||
pop t0
|
||||
.endm
|
98
work/main.s
Normal file
98
work/main.s
Normal file
|
@ -0,0 +1,98 @@
|
|||
#
|
||||
# main.s
|
||||
#
|
||||
# 2021 flabbergast <flabbergast@drak.xyz>
|
||||
# Unlicense: https://unlicense.org/
|
||||
#
|
||||
|
||||
.option norelax
|
||||
.equ compressed_isa, 1
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# macros and RAM variables
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
.include "macros.s"
|
||||
|
||||
.set rampointer, 0x80000000 # start allocating at the beginning of RAM
|
||||
# end of RAM is +0x4000
|
||||
|
||||
ramallot data_stack_begin, 128 # 32 values
|
||||
ramallot data_stack_end, 0
|
||||
|
||||
ramallot return_stack_begin, 512
|
||||
ramallot return_stack_end, 0
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Execution begins here
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
.text
|
||||
j Reset
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Static data
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Include any supporting code
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
.include "platform_regs.inc"
|
||||
.include "p_interrupts.s"
|
||||
.include "p_clock.s"
|
||||
.include "p_delay.s"
|
||||
.include "p_uart0.s"
|
||||
.include "p_i2c.s"
|
||||
.include "p_spi.s"
|
||||
|
||||
.include "write.s"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
Reset: # Execution begins here
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# set up stacks
|
||||
li sp, return_stack_end
|
||||
li s11, data_stack_end
|
||||
# set up interrupt and exception handler (ours needs sp)
|
||||
csrci mstatus, 8 # disable interrupts
|
||||
la t0, irq_handler
|
||||
csrw mtvec, t0
|
||||
csrsi mstatus, 8 # enable interrupts
|
||||
|
||||
# the board (usually) runs on 13.8MHz HFROSC on boot?
|
||||
jal clock_extcryst # switch to external crystal, 16MHz
|
||||
jal uart_init # initialise UART0 (115200baud)
|
||||
# if you change clock, you'll need to adjust the
|
||||
# baud divisor in p_uart0.s
|
||||
jal write_init # set up default "emit" over uart
|
||||
jal delay_init # enable the timer interrupt
|
||||
# jal i2c_init # init i2c: adjust prescaler if you change clock!
|
||||
|
||||
puts "Welcome!\n"
|
||||
|
||||
|
||||
## chase again
|
||||
li s1, GPIO_BASE
|
||||
li s2, 0b111111<<18 # pins 18-23
|
||||
sw s2, GPIO_OUTPUT_EN(s1) # set as outputs
|
||||
|
||||
li a0, 200 # delay in ms
|
||||
li s2, 0b1<<18 # start with pin 18
|
||||
li s3, 0b1<<23 # end with pin 23
|
||||
1:
|
||||
sw s2, GPIO_OUTPUT_VAL(s1) # make the pin high
|
||||
li a0, 200
|
||||
jal delay
|
||||
slli s2, s2, 1 # move one to the left
|
||||
bleu s2, s3, 1b # did we move past last pin?
|
||||
li s2, 0b1<<18 # back to the first pin
|
||||
|
||||
j 1b
|
||||
|
||||
|
||||
|
||||
end:
|
||||
j end
|
1
work/memmap
Symbolic link
1
work/memmap
Symbolic link
|
@ -0,0 +1 @@
|
|||
../memmap
|
69
work/p_clock.s
Normal file
69
work/p_clock.s
Normal file
|
@ -0,0 +1,69 @@
|
|||
#
|
||||
# p_clock.s: functions for setting up the main clock
|
||||
#
|
||||
# 2021 flabbergast@drak.xyz
|
||||
# Unlicense: https://unlicense.org/
|
||||
#
|
||||
|
||||
|
||||
.if compressed_isa
|
||||
.balign 2, 0
|
||||
.else
|
||||
.balign 4, 0
|
||||
.endif
|
||||
|
||||
.include "platform_regs.inc"
|
||||
|
||||
|
||||
# switch to external crystal (16MHz on red-v thing)
|
||||
clock_extcryst:
|
||||
li t0, PRCI_BASE
|
||||
1: # Wait for crystal to oscillate
|
||||
lw t1, PRCI_HFXOSCCFG(t0) # hfxoscen(bit30) is on by default on reset
|
||||
bgtz t1, 1b # ... hfxoscrdy(bit31): makes value negative
|
||||
|
||||
li t1, 0x00070df1 # 0x00060df1 | (1<<16) | (1<<17) | (1<<18)
|
||||
# = Reset value | PLLSEL | PLLREFSEL | PLLBYPASS
|
||||
sw t1, PRCI_PLLCFG(t0) # Select crystal as the main clock source
|
||||
ret
|
||||
|
||||
|
||||
# switch to 64MHz PLL driven by 16MHz external crystal
|
||||
clock_pll_64mhz:
|
||||
li t0, PRCI_BASE
|
||||
1: # Wait for crystal to oscillate
|
||||
lw t1, PRCI_HFXOSCCFG(t0) # hfxoscen(bit30) is on by default on reset
|
||||
bgtz t1, 1b # ... hfxoscrdy(bit31): makes value negative
|
||||
|
||||
# Set PLL to 64 MHz (don't activate); 64 = 16 /(2^1) *(2*(31+1)) /(2^3)
|
||||
li t1, 0x20df1 # pllr=1, pllf=0x1f, pllq=0x3 (reset vals)
|
||||
sw t1, PRCI_PLLCFG(t0) # pllsel=0, pllrefsel=1, pllbypass=0(!)
|
||||
|
||||
2: # Wait for PLL to lock: plllock(bit31): sign bit
|
||||
lw t1, PRCI_PLLCFG(t0)
|
||||
bgtz t1, 2b
|
||||
|
||||
li t1, 0x30df1 # pllsel=1 (on top of the above)
|
||||
sw t1, PRCI_PLLCFG(t0)
|
||||
ret
|
||||
|
||||
|
||||
# switch to 320MHz PLL driven by 16MHz external crystal
|
||||
# (the board gets quite warm!)
|
||||
clock_pll_320mhz:
|
||||
li t0, PRCI_BASE
|
||||
1: # Wait for crystal to oscillate
|
||||
lw t1, PRCI_HFXOSCCFG(t0) # hfxoscen(bit30) is on by default on reset
|
||||
bgtz t1, 1b # ... hfxoscrdy(bit31): makes value negative
|
||||
|
||||
# Set PLL to 320 MHz (don't activate); 320 = 16 /(2^1) *(2*(39+1)) /(2^1)
|
||||
li t1, 0x20671 # pllr=1, pllf=0x27(!), pllq=1(!)
|
||||
sw t1, PRCI_PLLCFG(t0) # pllsel=0, pllrefsel=1, pllbypass=0(!)
|
||||
|
||||
2: # Wait for PLL to lock: plllock(bit31): sign bit
|
||||
lw t1, PRCI_PLLCFG(t0)
|
||||
bgtz t1, 2b
|
||||
|
||||
li t1, 0x30671 # pllsel=1 (on top of the above)
|
||||
sw t1, PRCI_PLLCFG(t0)
|
||||
ret
|
92
work/p_delay.s
Normal file
92
work/p_delay.s
Normal file
|
@ -0,0 +1,92 @@
|
|||
#
|
||||
# p_delay.s: functions for waiting
|
||||
#
|
||||
# 2021 flabbergast@drak.xyz
|
||||
# Unlicense: https://unlicense.org/
|
||||
#
|
||||
.if compressed_isa
|
||||
.balign 2, 0
|
||||
.else
|
||||
.balign 4, 0
|
||||
.endif
|
||||
|
||||
.include "platform_regs.inc"
|
||||
.equ MTIME_FREQUENCY, 33 # 32_768 Hz clock
|
||||
|
||||
# enable timer interrupt and put a 'safe' value into MTIMECMP
|
||||
delay_init:
|
||||
# clear the interrupt by writing a high value to CLINT_MTIMECMP.h
|
||||
li t0, CLINT_BASE+CLINT_MTIMECMP
|
||||
li t1, 0xFFFFF000
|
||||
sw t1, 4(t0)
|
||||
li t0, 1<<7
|
||||
csrs mie, t0 # set MTIE bit
|
||||
ret
|
||||
|
||||
|
||||
# a0: delay value in milliseconds
|
||||
# (should be less than 0xFFFF_FFFF/33/4)
|
||||
# this version uses interrupts if enabled,
|
||||
# i.e. it waits with wfi, rather than a busy loop
|
||||
delay:
|
||||
li t0, CLINT_BASE+CLINT_MTIME+8 # load addresses
|
||||
li t4, CLINT_BASE+CLINT_MTIMECMP
|
||||
lw t1, -8(t0) # load the current value of the timer.l
|
||||
lw t3, -4(t0) # load current timer.h
|
||||
|
||||
li t2, MTIME_FREQUENCY # let our clock frequency
|
||||
mul t2, t2, a0 # multiply milliseconds with frequency
|
||||
add t2, t1, t2 # target mtime is now in t2
|
||||
bgeu t2, t1, 4f # _not_ overflown back to zero?
|
||||
# if overflown, we assume that (signed)t1<0 and t2>0
|
||||
addi t3, t3, 1 # overflown => increase target time.h by one
|
||||
4:
|
||||
sw t2, 0(t4) # set MTIMECMP.l (assuming .h is still very big)
|
||||
sw t3, 4(t4) # set MTIMECMP.h
|
||||
sltu t3, t1, t2 # keep the "overflown" flag in t3
|
||||
1: # main waiting loop
|
||||
csrr t1, mstatus # check if interrupts are enabled
|
||||
andi t1, t1, 8
|
||||
beqz t1, 3f
|
||||
wfi # if they are, wait for one
|
||||
3:
|
||||
lw t1, -8(t0) # read mtime value again
|
||||
bnez t3, 2f # check overflow
|
||||
bltu t1, t2, 1b # no overflow: unsigned comparison
|
||||
2:
|
||||
blt t1, t2, 1b # overflow: (signed)t1 can be <0
|
||||
|
||||
li t1, 0xFFFFF000 # clear timer interrupt
|
||||
sw t1, 4(t4) # ... by writing big into MTIMECMP.h
|
||||
ret
|
||||
|
||||
|
||||
# a0: delay value in milliseconds
|
||||
# (should be less than 0xFFFF_FFFF/33/4)
|
||||
# delay:
|
||||
# li t0, CLINT_BASE+CLINT_MTIME+8 # load the timer registers base
|
||||
# lw t1, -8(t0) # load the current value of the timer (lsw)
|
||||
#
|
||||
# li t2, MTIME_FREQUENCY # let our clock frequency
|
||||
# mul t2, t2, a0 # multiply milliseconds with frequency
|
||||
# add t2, t1, t2 # target mtime is now in t2
|
||||
# bltu t2, t1, 2f # overflown back to zero?
|
||||
# # if yes, assume that (signed)t1<0 and t2>0
|
||||
#
|
||||
# 1:
|
||||
# lw t1, -8(t0) # read mtime value again
|
||||
# bltu t1, t2, 1b # keep looping until timout
|
||||
# ret
|
||||
#
|
||||
# 2: # overflown branch
|
||||
# lw t1, -8(t0) # read mtime; t1 can be "<0"
|
||||
# blt t1, t2, 2b # do signed compare
|
||||
# ret
|
||||
|
||||
|
||||
# a0: (delay in cycles)/2
|
||||
busy_delay_cycles:
|
||||
1:
|
||||
addi a0, a0, -1
|
||||
bnez a0, 1b
|
||||
ret
|
163
work/p_i2c.s
Normal file
163
work/p_i2c.s
Normal file
|
@ -0,0 +1,163 @@
|
|||
#
|
||||
# p_i2c0.s: basic i2c driver
|
||||
#
|
||||
# 2021 flabbergast@drak.xyz
|
||||
# Unlicense: https://unlicense.org/
|
||||
#
|
||||
# no description in the manual, refers to https://opencores.org/projects/i2c
|
||||
# (that requires registration which requires manual approval from someone)
|
||||
# here's a copy of i2cspecs.pdf:
|
||||
# https://pilvi.disasm.info/index.php/s/MqvdhL3lBqFxY0P/download
|
||||
#
|
||||
# prescaler:
|
||||
# - max 0xFFFF
|
||||
# - (clock_rate/(5*baud)) - 1
|
||||
# - on 16MHz core, 400kHz I2C => 7
|
||||
# - on 16MHz core, 100kHz I2C => 31
|
||||
# - set when CONTROL_EN is off
|
||||
|
||||
|
||||
# sample usage:
|
||||
# jal i2c_init (somewhere at the beginning)
|
||||
#
|
||||
# ## i2c bus scanner
|
||||
# li s1, 1 # start with address
|
||||
# li s2, 127 # end with address
|
||||
# 2:
|
||||
# mv a0, s1
|
||||
# li a2, 0
|
||||
# li a4, 0
|
||||
# jal i2c_rw # try address
|
||||
# bltz a0, 3f # no response=>skip
|
||||
# pushda s1 # print message
|
||||
# puts "Found a device at %B\n"
|
||||
# 3:
|
||||
# li a0, 2 # wait a little
|
||||
# jal delay # ... seems to be too fast otherwise
|
||||
# addi s1, s1, 1 # inc counter
|
||||
# bleu s1, s2, 2b # repeat
|
||||
|
||||
.if compressed_isa
|
||||
.balign 2, 0
|
||||
.else
|
||||
.balign 4, 0
|
||||
.endif
|
||||
|
||||
.include "platform_regs.inc"
|
||||
|
||||
i2c_init:
|
||||
li t0, GPIO_BASE # UART0 RX/TX are selected in IOF_SEL (IOF0) on Reset.
|
||||
li t2, (1<<12)|(1<<13) # 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, I2C0_BASE
|
||||
sb zero, I2C_CTR(t0) # control: disable en, ie
|
||||
sb zero, I2C_PRERhi(t0) # prescaler MSB
|
||||
li t1, 31
|
||||
sb t1, I2C_PRERlo(t0) # prescaler LSB
|
||||
li t1, I2C_CTR_EN
|
||||
sb t1, I2C_CTR(t0) # enable module; irq disabled
|
||||
ret
|
||||
|
||||
.macro _wait_for_tip
|
||||
1: # wait-for-transfer loop
|
||||
lbu t1, I2C_SR(t0) # status register
|
||||
andi t1, t1, I2C_SR_TIP # "transfer-in-progress" bit
|
||||
bnez t1, 1b # wait for TIP to clear
|
||||
.endm
|
||||
|
||||
.macro _check_ack
|
||||
# check ack
|
||||
lbu t1, I2C_SR(t0) # status again
|
||||
andi t1, t1, I2C_SR_RXACK # ack received?
|
||||
beqz t1, 1f # should be 0
|
||||
li t1, I2C_CR_STOP # slave did not respond, so...
|
||||
sb t1, I2C_CR(t0) # set stop condition on the bus
|
||||
li a0, -1 # set 'fail' return value
|
||||
ret
|
||||
1:
|
||||
.endm
|
||||
|
||||
# read/write bytes through I2C
|
||||
# expects:
|
||||
# - a0: I2C address
|
||||
# - a1: pointer to data to write
|
||||
# - a2: how many bytes to write
|
||||
# - a3: pointer to data to read
|
||||
# - a4: how many bytes to read
|
||||
# returns: a0: 0 success, -1 problem
|
||||
# a1 and a3 point to 1 byte after the last used
|
||||
# note: both r/w can have 0 bytes to r/w
|
||||
# in which case that part is skipped
|
||||
# note: does not jal anything, so does not push/pop ra!
|
||||
# if you want to e.g. puts, need to add this,
|
||||
# including in the macros above!
|
||||
i2c_rw:
|
||||
li t0, I2C0_BASE
|
||||
slli a0, a0, 1
|
||||
|
||||
# if zero write nonzero read, goto read
|
||||
bnez a2, 1f # writing some bytes=>skip
|
||||
bnez a4, 5f # reading=>goto reading part
|
||||
|
||||
1: # writing
|
||||
sb a0, I2C_TXR(t0) # write the address to tx register
|
||||
li t1, I2C_CR_START|I2C_CR_WRITE
|
||||
sb t1, I2C_CR(t0) # start cond, write to bus
|
||||
_wait_for_tip
|
||||
_check_ack
|
||||
|
||||
add t2, a1, a2 # t2: end of loop pointer
|
||||
|
||||
bnez a2, 3f # writing some bytes=>skip
|
||||
li t1, I2C_CR_STOP # here: 0 write, 0 read
|
||||
sb t1, I2C_CR(t0) # just set stop
|
||||
j 9f # and exit
|
||||
|
||||
3: # write loop
|
||||
lbu t1, 0(a1) # read current byte to send
|
||||
sb t1, I2C_TXR(t0) # write data to tx register
|
||||
li t1, I2C_CR_WRITE
|
||||
addi a1, a1, 1 # inc address
|
||||
bltu a1, t2, 1f # if not the last byte, skip
|
||||
bnez a4, 1f # subsequent read => do not set stop cond
|
||||
ori t1, t1, I2C_CR_STOP # last byte => stop condition
|
||||
1:
|
||||
sb t1, I2C_CR(t0) # do the deed
|
||||
_wait_for_tip
|
||||
_check_ack
|
||||
bltu a1, t2, 3b # repeat for the next byte
|
||||
|
||||
beqz a4, 9f # nothing to read=>goto end
|
||||
|
||||
5: # reading
|
||||
ori t1, a0, 1 # address | read_bit
|
||||
sb t1, I2C_TXR(t0) # write the address to tx register
|
||||
li t1, I2C_CR_START|I2C_CR_WRITE
|
||||
sb t1, I2C_CR(t0) # start cond, write to bus
|
||||
_wait_for_tip
|
||||
_check_ack
|
||||
|
||||
add t2, a3, a4 # t2: end of loop pointer
|
||||
addi t2, t2, -1 # one less (to detect the last byte)
|
||||
6: # read loop
|
||||
li t1, I2C_CR_READ
|
||||
bne a3, t2, 1f # if not the last byte=>skip
|
||||
ori t1, t1, I2C_CR_STOP|I2C_CR_ACK # last byte=>stop and nack
|
||||
1:
|
||||
sb t1, I2C_CR(t0)
|
||||
_wait_for_tip
|
||||
lbu t1, I2C_RXR(t0) # read the received byte
|
||||
sb t1, 0(a3) # store it
|
||||
addi a3, a3, 1 # inc pointer
|
||||
bleu a3, t2, 6b # repeat for the next byte
|
||||
|
||||
9: # end
|
||||
li a0, 0
|
||||
ret
|
114
work/p_interrupts.s
Normal file
114
work/p_interrupts.s
Normal file
|
@ -0,0 +1,114 @@
|
|||
#
|
||||
# interrupts.s: basic irq handling setup
|
||||
#
|
||||
# 2021 flabbergast@drak.xyz
|
||||
# Unlicense: https://unlicense.org/
|
||||
#
|
||||
# ! when you edit the irq handler, don't forget to push/pop
|
||||
# any extra registers that you use !
|
||||
# - we don't use the vectored interrupt handling, so
|
||||
# there is a single function that's executed on any
|
||||
# interrupt or exception (which needs to make decisions
|
||||
# based on mcause)
|
||||
# - the address of this fn should be loaded into mtvec
|
||||
#
|
||||
# (SiFive FE310 only has 3 interrupt sources (sw, timer, plic),
|
||||
# so any vector table would only have 3 "used" entries.)
|
||||
#
|
||||
|
||||
.include "platform_regs.inc"
|
||||
|
||||
# irq handler needs to be always 4-aligned
|
||||
.balign 4, 0
|
||||
irq_handler:
|
||||
addi sp, sp, 9*(-4)
|
||||
sw ra, 8*4(sp) # x1
|
||||
sw s1, 7*4(sp) # x9
|
||||
sw t6, 6*4(sp) # x31
|
||||
sw t5, 5*4(sp) # x30
|
||||
sw t4, 4*4(sp) # x29
|
||||
sw t3, 3*4(sp) # x28
|
||||
sw t2, 2*4(sp) # x7
|
||||
sw t1, 1*4(sp) # x6
|
||||
sw t0, 0*4(sp) # x5
|
||||
|
||||
# check if it's an interrupt or an exception
|
||||
csrr t0, mcause
|
||||
blt t0, zero, .interrupts
|
||||
|
||||
|
||||
## exceptions
|
||||
.exceptions:
|
||||
# read CSR and stall
|
||||
csrr t1, mepc # tells us where it occured
|
||||
# t0 still contains mcause
|
||||
jal zero, .exceptions
|
||||
|
||||
|
||||
## interrupts
|
||||
.interrupts:
|
||||
andi t0, t0, 0xF # FE310 only has irqs 3,7,11
|
||||
srli t0, t0, 2
|
||||
bne t0, zero, 1f
|
||||
|
||||
## 3, software interrupt
|
||||
|
||||
jal zero, irq_handler_exit
|
||||
1:
|
||||
srli t0, t0, 1
|
||||
bne t0, zero, 1f
|
||||
|
||||
## 7, timer interrupt
|
||||
|
||||
# clear the interrupt by writing a high value to CLINT_MTIMECMP.h
|
||||
li t0, CLINT_BASE+CLINT_MTIMECMP
|
||||
li t1, 0xFFFFF000
|
||||
sw t1, 4(t0)
|
||||
|
||||
jal zero, irq_handler_exit
|
||||
1:
|
||||
|
||||
## 11, external interrupt
|
||||
|
||||
# # use PLIC claim/complete mechanism
|
||||
# li t0, PLIC_BASE+PLIC_CLAIM-4 # easier load
|
||||
# lw s1, 4(t0) # claim: highest pending irq id
|
||||
#
|
||||
# # UART0
|
||||
# li t1, 3 # UART0 is source 3
|
||||
# bne t1, s1, 1f # if not UART0, skip
|
||||
# jal ra, irq_handler_uart0
|
||||
#
|
||||
# 1:
|
||||
# # any other PLIC sources should go here
|
||||
#
|
||||
# li t0, PLIC_BASE+PLIC_CLAIM-4
|
||||
# sw s1, 4(t0) # complete the claimed irq
|
||||
|
||||
# exit
|
||||
irq_handler_exit:
|
||||
lw t0, 0*4(sp) # x5
|
||||
lw t1, 1*4(sp) # x6
|
||||
lw t2, 2*4(sp) # x7
|
||||
lw t3, 3*4(sp) # x28
|
||||
lw t4, 4*4(sp) # x29
|
||||
lw t5, 5*4(sp) # x30
|
||||
lw t6, 6*4(sp) # x31
|
||||
lw s1, 7*4(sp) # x9
|
||||
lw ra, 8*4(sp) # x1
|
||||
addi sp, sp, 9*4
|
||||
mret
|
||||
|
||||
|
||||
# enable enternal interrupts (PLIC)
|
||||
plic_init:
|
||||
li t0, PLIC_BASE+PLIC_THRESHOLD
|
||||
sw zero, 0(t0) # set PLIC threshold 0 (all irqs unmasked)
|
||||
|
||||
li t0, PLIC_BASE+PLIC_ENABLE1
|
||||
sw zero, 0(t0) # disable all PLIC irq sources
|
||||
sw zero, 4(t0) # (a lot seem enabled after reset?)
|
||||
|
||||
li t0, 1<<11
|
||||
csrs mie, t0 # set MEIE(bit11)
|
||||
ret
|
105
work/p_spi.s
Normal file
105
work/p_spi.s
Normal file
|
@ -0,0 +1,105 @@
|
|||
#
|
||||
# p_spi.s: basic SPI driver
|
||||
#
|
||||
# 2021 flabbergast@drak.xyz
|
||||
# Unlicense: https://unlicense.org/
|
||||
#
|
||||
# It seems that QSPI0 peripheral is used to map the flash
|
||||
# from which the firmware runs. So not touching that.
|
||||
# Other than that, there is only SPI1.
|
||||
#
|
||||
# There are 3 possible hardware comtrolled CS pins; each
|
||||
# has selectable "always asserted" (HOLD) mode, or normal
|
||||
# "AUTO" mode (sel/desel every byte). Possible to also mark
|
||||
# it "OFF" and fiddle with the pin(s) manually.
|
||||
# This driver will use hw control on CS0 = IO2 pin.
|
||||
# (MOSI=IO3, MISO=IO4, SCK=IO5)
|
||||
#
|
||||
# Adjust the usual stuff in _init: phase/polarity (0/0),
|
||||
# clock, #bits-per-frame (8), msb/lsb
|
||||
#
|
||||
# clock: sckdiv is 12 bits wide
|
||||
# spi clock = core_clock / ( 2*(sckdiv+1) )
|
||||
# (default sckdiv = 3)
|
||||
#
|
||||
|
||||
# sample usage:
|
||||
# jal spi_init (somewhere at the beginning)
|
||||
|
||||
.equ spi_hw_cs, 1
|
||||
|
||||
.if compressed_isa
|
||||
.balign 2, 0
|
||||
.else
|
||||
.balign 4, 0
|
||||
.endif
|
||||
|
||||
.include "platform_regs.inc"
|
||||
|
||||
spi_init:
|
||||
# set up pins
|
||||
li t0, GPIO_BASE # UART0 RX/TX are selected in IOF_SEL (IOF0) on Reset.
|
||||
.if spi_hw_cs
|
||||
li t2, (1<<2)|(1<<3)|(1<<4)|(1<<5) # SS0|MOSI|MISO|SCK
|
||||
.else
|
||||
li t2, (1<<3)|(1<<4)|(1<<5) # MOSI|MISO|SCK
|
||||
.endif
|
||||
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
|
||||
.if spi_hw_cs
|
||||
.else
|
||||
# set up the gpio controlled CS pin
|
||||
li t2, 1<<23
|
||||
lw t1, GPIO_OUTPUT_VAL(t0)
|
||||
or t1, t1, t2
|
||||
sw t1, GPIO_OUTPUT_VAL(t0)
|
||||
lw t1, GPIO_OUTPUT_EN(t0)
|
||||
or t1, t1, t2
|
||||
sw t1, GPIO_OUTPUT_EN(t0)
|
||||
.endif
|
||||
|
||||
# set up SPI peripheral
|
||||
li t0, SPI1_BASE
|
||||
li t1, 7 # sckdiv: 1MHz (from 16MHz main clock)
|
||||
sw t1, SPI_SCKDIV(t0)
|
||||
# sw zero, SPI_SCKMODE(t0) # pol(inact_cs=0), pha(sample=lead,shift=trail)
|
||||
.if spi_hw_cs
|
||||
# li t1, 0b1111
|
||||
# sw t1, SPI_CSDEF(t0) # all CS pins high when inactive
|
||||
li t1, 0b0001
|
||||
sw t1, SPI_CSID(t0) # which CSn pin to use
|
||||
sw zero, SPI_CSMODE(t0) # AUTO mode for CS (hw-controlled, on/off every transfer)
|
||||
.else
|
||||
li t1, 3
|
||||
sw t1, SPI_CSMODE(t0) # OFF mode for CS (no hw CS control)
|
||||
.endif
|
||||
li t1, 0x80000 # dir=Tx, endian=MSB, len=8, proto=Single
|
||||
sw t1, SPI_FMT(t0)
|
||||
ret
|
||||
|
||||
|
||||
# transfer one byte out&in
|
||||
# a0: byte to send
|
||||
# returns incoming byte in a0
|
||||
spi_rw:
|
||||
.if spi_hw_cs
|
||||
.else
|
||||
.endif
|
||||
li t0, SPI1_BASE+SPI_TXDATA
|
||||
1: # repeat until accepted
|
||||
amoor.w t1, a0, (t0) # (t0)->t1, then write (t0)|a0 into (t0)
|
||||
bnez t1, 1b # non-zero means fifo full
|
||||
|
||||
addi t0, t0, SPI_RXDATA-SPI_TXDATA
|
||||
2: # wait until we get a byte from rx fifo
|
||||
lw a0, 0(t0) # bit31="empty", bit7-bit0 data
|
||||
bltz a0, 2b
|
||||
.if spi_hw_cs
|
||||
.else
|
||||
.endif
|
||||
ret
|
76
work/p_uart0.s
Normal file
76
work/p_uart0.s
Normal file
|
@ -0,0 +1,76 @@
|
|||
#
|
||||
# p_uart0.s: basic uart0
|
||||
#
|
||||
# 2021 flabbergast@drak.xyz
|
||||
# Unlicense: https://unlicense.org/
|
||||
#
|
||||
.if compressed_isa
|
||||
.balign 2, 0
|
||||
.else
|
||||
.balign 4, 0
|
||||
.endif
|
||||
|
||||
.include "platform_regs.inc"
|
||||
|
||||
# UART0: RX:pin16 TX:pin17
|
||||
|
||||
# init
|
||||
# !! need to change baud setting if the main clock changes
|
||||
# clobbers: t0, t1, t2
|
||||
uart_init:
|
||||
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_TXCTRL(t0) # enable transmit
|
||||
sw t1, UART0_RXCTRL(t0) # enable receive
|
||||
ret
|
||||
|
||||
# emit one byte
|
||||
# a0: (unsigned!) byte to send
|
||||
# blocking until byte sent
|
||||
# clobbers: t0, t1
|
||||
uart_emit:
|
||||
li t0, UART0_BASE+UART0_TXDATA
|
||||
1: # repeat until accepted
|
||||
amoor.w t1, a0, (t0) # (t0)->t1, then write (t0)|a0 into (t0)
|
||||
bnez t1, 1b # non-zero means tx fifo full
|
||||
ret
|
||||
|
||||
# check if ready to transmit
|
||||
# returns: a0 non-zero if tx fifo full
|
||||
# clobbers: t0
|
||||
q_uart_emit:
|
||||
li t0, UART0_BASE
|
||||
lw a0, UART0_TXDATA(t0) # bit31 indicates "full"
|
||||
ret
|
||||
|
||||
# receive a byte
|
||||
# returns: a0: received byte
|
||||
# blocking until available
|
||||
# clobbers: t0
|
||||
uart_recv:
|
||||
li t0, UART0_BASE
|
||||
1: # wait until we get a byte from rx fifo
|
||||
lw a0, UART0_RXDATA(t0) # bit31="empty", bit7-bit0 data
|
||||
bltz a0, 1b
|
||||
ret
|
||||
|
||||
# check if there is an incoming byte waiting
|
||||
# returns: a0 non-zero if rx fifo empty
|
||||
# clobbers: t0
|
||||
q_uart_recv:
|
||||
li t0, UART0_BASE
|
||||
lw a0, UART0_RXDATA(t0) # bit31 indicates "empty"...
|
||||
ret # (there can be data in b7-b0 even if "empty")
|
1
work/platform_regs.inc
Symbolic link
1
work/platform_regs.inc
Symbolic link
|
@ -0,0 +1 @@
|
|||
../platform_regs.inc
|
289
work/write.s
Normal file
289
work/write.s
Normal file
|
@ -0,0 +1,289 @@
|
|||
#
|
||||
# write.s: printing strings
|
||||
#
|
||||
# 2021 flabbergast@drak.xyz
|
||||
# Unlicense: https://unlicense.org/
|
||||
#
|
||||
# assumes: stack macros, ramallot macro, uart_emit symbol
|
||||
# if 'fixed_emit'==0, assumes that an address for "emit" is
|
||||
# at 'write_emit_routine_p'
|
||||
# (and assumes that the routine expects the byte in a0)
|
||||
#
|
||||
# uses sized strings: b_len b_c1 .. b_cn
|
||||
#
|
||||
# we use the stack/memory here - the expectation is that
|
||||
# "emit" is 'slow' anyway, so don't worry about speed
|
||||
# can use up to 48 bytes on return stack (temporarily of course)
|
||||
#
|
||||
.if compressed_isa
|
||||
.balign 2, 0
|
||||
.else
|
||||
.balign 4, 0
|
||||
.endif
|
||||
|
||||
# either just always do uart_emit, or use a pointer to RAM to
|
||||
# store an "emit" routine address
|
||||
.equ fixed_emit, 0
|
||||
.if fixed_emit
|
||||
.equ emit_routine, uart_emit
|
||||
.else
|
||||
# reserve a location where the address of an "emit" routine is stored
|
||||
ramallot write_emit_routine_p, 4
|
||||
.endif
|
||||
|
||||
# record the default "emit" routine
|
||||
# clobbers: t0, t1
|
||||
write_init:
|
||||
.if fixed_emit
|
||||
.else
|
||||
la t0, uart_emit # let write.s know what to use for printing
|
||||
li t1, write_emit_routine_p
|
||||
sw t0, (t1)
|
||||
.endif
|
||||
ret
|
||||
|
||||
# print a single hexadecimal digit (internal)
|
||||
# expects:
|
||||
# - a0: value to print (assumed 0-15)
|
||||
# - s7: "emit" routine to use
|
||||
_write_custom_hexdigit:
|
||||
addi a0, a0, -10 # check if we're adding '0' or 'a'
|
||||
bltz a0, 1f
|
||||
addi a0, a0, 'a' # a-f
|
||||
jr s7 # jump to "emit" routine
|
||||
1:
|
||||
addi a0, a0, 10+'0' # 0-9
|
||||
jr s7 # jump to "emit" routine
|
||||
|
||||
# print a hexadecimal unsigned value, up to some #of digits
|
||||
# expects:
|
||||
# - a0: value to print
|
||||
# - a1: shift to 1st nibble to print (e.g. 16bit->12)
|
||||
# - a2: if non zero, print leading zero digits
|
||||
write_value_hex:
|
||||
push ra # push return addr onto stack; we are doing calls
|
||||
push s0 # we'll use s0 (as temp)
|
||||
push s7 # s7 as well (for "emit" address)
|
||||
|
||||
.if fixed_emit
|
||||
la s7, emit_routine
|
||||
.else
|
||||
li s7, write_emit_routine_p
|
||||
lw s7, 0(s7) # load "emit" address into s7
|
||||
.endif
|
||||
|
||||
or s0, a0, a2 # for checking if a0==a2==0
|
||||
bnez s0, 1f # if s0==0, then set a1=0 (only printing the last zero)
|
||||
li a1, 0
|
||||
1:
|
||||
bnez a1, 2f # if a1==0, set a2=1 (always print the last digit)
|
||||
li a2, 1
|
||||
2:
|
||||
mv s0, a0 # save the value that we're printing
|
||||
|
||||
3: # main digit printing loop
|
||||
srl a0, s0, a1 # extract the nibble into a0
|
||||
andi a0, a0, 0xF # ...
|
||||
bnez a0, 4f # it's not zero, we're printing it
|
||||
beqz a2, 5f # not requesting to print zero
|
||||
4:
|
||||
li a2, 1 # if we're printing a digit, we're printing the rest
|
||||
jal _write_custom_hexdigit
|
||||
5:
|
||||
addi a1, a1, -4 # move to the next nibble if a1 didn't become negative yet
|
||||
bgez a1, 3b
|
||||
|
||||
pop s7
|
||||
pop s0
|
||||
pop ra # restore the return address
|
||||
ret
|
||||
|
||||
# print a byte, hexadecimal, unsigned
|
||||
# expects:
|
||||
# - a0: value to print (lowest byte printed)
|
||||
# - a2: if non zero, always print two digits
|
||||
# clobbers: a1
|
||||
write_byte_hex:
|
||||
li a1, 4
|
||||
j write_value_hex
|
||||
|
||||
# print bytes from memory, hex, padded
|
||||
# expects:
|
||||
# - a0: number of bytes
|
||||
# - a1: address to memory
|
||||
write_bytes_hex:
|
||||
push ra # push return addr onto stack; we are doing calls
|
||||
push s0 # preserve s0
|
||||
push s1 # preserve s1
|
||||
mv s1, a1 # we'll use it for looping
|
||||
li a2, 1 # want padding with zeroes
|
||||
add s0, s1, a0 # compute final address
|
||||
1: # byte printing loop
|
||||
lbu a0, 0(s1) # get the byte
|
||||
li a1, 4 # want to print two nibbles
|
||||
jal write_value_hex
|
||||
addi s1, s1, 1 # increment the address pointer
|
||||
bgeu s0, s1, 1b # loop if we're not beyond s0 yet
|
||||
pop s1 # restore s1
|
||||
pop s0 # restore s0
|
||||
pop ra # restore the return address
|
||||
ret
|
||||
|
||||
# output a fixed string
|
||||
.macro puts Text
|
||||
jal _puts
|
||||
sized_string "\Text"
|
||||
.endm
|
||||
|
||||
# output a string, address in ra
|
||||
# need to also fix ra
|
||||
# clobbers: a0
|
||||
_puts:
|
||||
push t6 # let's not clobber
|
||||
mv a0, ra
|
||||
lbu t6, 0(ra) # get the string length
|
||||
addi ra, ra, 1 # skip size byte
|
||||
add ra, ra, t6 # skip string
|
||||
# round up to the next half/word boundary
|
||||
.if compressed_isa
|
||||
andi t6, ra, 1
|
||||
add ra, ra, t6
|
||||
.else
|
||||
andi t6, ra, 1
|
||||
add ra, ra, t6
|
||||
andi t6, ra, 2
|
||||
add ra, ra, t6
|
||||
.endif
|
||||
pop t6
|
||||
# ! will continue to write_str belo |