; ARM assembly tutorial for Asmonite, example 20: an interrupt of a peripheral (Raspberry Pi Pico 2, Arm Cortex-M33).
; Adapted from the ARM-ASM-Tutorial by Niklas Guertler (example BlinkTIM1, section "Peripheral
; interrupts"); see LICENSE.txt.
;
; The interrupt comes from the TIMER0 of the RP2350 now, not from the SysTick of the core. TIMER0 counts
; microseconds (64 bit); an alarm fires when the low 32 bits reach the value in ALARM0. Three switches
; must be on for the interrupt to reach the core:
;   1. the alarm itself: TIMER0.INTE bit 0
;   2. the interrupt controller of the core (NVIC): TIMER0_IRQ_0 is interrupt 0, NVIC_ISER0 bit 0
;   3. its entry in the vector table: entry 16 + 0 = 16
; The handler clears the interrupt (TIMER0.INTR bit 0, write 1), sets the next alarm and toggles the LED.
; The microsecond comes from the TICKS block: TIMER0 gets a tick every CYCLES cycles of clk_ref. After a reset
; clk_ref runs from the ring oscillator, whose frequency is not exact, so the program first starts the 12 MHz
; crystal and lets clk_ref run from it (steps 1 and 2 of 21_clock.asm); then CYCLES = 12 is one microsecond.
.target pico2
.arch thumb
.imagebase 0x20000000

RESETS_BASE         equ 0x40020000
RESETS_RESET_DONE   equ 0x08
RESET_BITS          equ (1 << 6) | (1 << 9)     ; IO_BANK0, PADS_BANK0
IO_BANK0_BASE       equ 0x40028000
GPIO25_CTRL         equ 0xcc
FUNCSEL_SIO         equ 5
PADS_BANK0_BASE     equ 0x40038000
PADS_GPIO25         equ 0x68
PAD_ISO             equ 1 << 8
SIO_BASE            equ 0xd0000000
SIO_GPIO_OUT_SET    equ 0x18
SIO_GPIO_OUT_CLR    equ 0x20
SIO_GPIO_OE_SET     equ 0x38
ALIAS_CLR           equ 0x3000
RESET_TIMER0        equ 1 << 23
TIMER0_BASE         equ 0x400b0000
TIMER_ALARM0        equ 0x10
TIMER_TIMERAWL      equ 0x28
TIMER_INTR          equ 0x3c
TIMER_INTE          equ 0x40
TICKS_BASE          equ 0x40108000
TICKS_TIMER0_CTRL   equ 0x18
TICKS_TIMER0_CYCLES equ 0x1c
CLK_REF_MHZ         equ 12            ; clk_ref in MHz: the crystal of the Pico 2
XOSC_BASE           equ 0x40048000
XOSC_CTRL           equ 0x00
XOSC_STATUS         equ 0x04
XOSC_STARTUP        equ 0x0c
XOSC_RANGE_1_15MHZ  equ 0xaa0
XOSC_ENABLE_1_15MHZ equ (0xfab << 12) | 0xaa0
XOSC_STARTUP_DELAY  equ 282           ; 282 * 256 cycles: about 6 ms (as the Pico SDK)
CLOCKS_BASE         equ 0x40010000
CLK_REF_CTRL        equ 0x30
CLK_REF_SELECTED    equ 0x38
SCS_BASE            equ 0xe000e000
NVIC_ISER0          equ 0x100
SCB_VTOR            equ 0xd08
INTERVAL_US         equ 250000        ; a quarter of a second
SIO_GPIO_OUT_XOR    equ 0x28
SRAM_END            equ 0x20082000

vectors:
    dd SRAM_END                 ;  0: the stack pointer at the start
    dd _start + 1               ;  1: reset
    dd hang + 1                 ;  2: NMI
    dd hang + 1                 ;  3: HardFault
    dd hang + 1, hang + 1, hang + 1, hang + 1   ; 4 .. 7: MemManage, BusFault, UsageFault, SecureFault
    dd 0, 0, 0                  ;  8 .. 10: reserved
    dd hang + 1, hang + 1, 0    ; 11 .. 13: SVCall, DebugMonitor, reserved
    dd hang + 1, hang + 1       ; 14, 15: PendSV, SysTick
    dd timer_handler + 1        ; 16: interrupt 0, TIMER0_IRQ_0

_start:
    ldr  r0, lit_scs
    adr  r1, vectors
    str  r1, [r0, #SCB_VTOR]
    bl   setup_led
    bl   clk_ref_xosc

    ; TIMER0 out of reset
    ldr  r1, lit_resets_clr
    ldr  r0, lit_reset_timer0
    str  r0, [r1]
    ldr  r1, lit_resets
timer_reset_wait:
    ldr  r2, [r1, #RESETS_RESET_DONE]
    tst  r2, r0
    beq  timer_reset_wait

    ; one tick each microsecond
    ldr  r1, lit_ticks
    movs r0, #CLK_REF_MHZ
    str  r0, [r1, #TICKS_TIMER0_CYCLES]
    movs r0, #1
    str  r0, [r1, #TICKS_TIMER0_CTRL]

    ; the first alarm, the interrupt on in the timer and in the NVIC
    ldr  r4, lit_timer0
    ldr  r0, [r4, #TIMER_TIMERAWL]
    ldr  r1, lit_interval
    add  r0, r0, r1
    str  r0, [r4, #TIMER_ALARM0]
    movs r0, #1
    str  r0, [r4, #TIMER_INTE]
    ldr  r1, lit_scs
    str  r0, [r1, #NVIC_ISER0]  ; bit 0: interrupt 0
    cpsie i                     ; allow interrupts (PRIMASK = 0): the boot ROM may leave them off
sleep_loop:
    wfi
    b    sleep_loop

; ---- interrupt 0: the alarm 0 of TIMER0 ----
timer_handler:
    ldr  r0, lit_timer0
    movs r1, #1
    str  r1, [r0, #TIMER_INTR]  ; clear the interrupt
    ldr  r1, [r0, #TIMER_ALARM0]
    ldr  r2, lit_interval
    add  r1, r1, r2
    str  r1, [r0, #TIMER_ALARM0]  ; the next alarm, INTERVAL_US after this one
    ldr  r0, lit_sio
    ldr  r1, lit_led_bit
    str  r1, [r0, #SIO_GPIO_OUT_XOR]  ; toggle the LED
    bx   lr

; ---- start the crystal, clk_ref from it (12 MHz); changes r0, r1 ----
clk_ref_xosc:
    ldr  r1, lit_xosc
    movw r0, #XOSC_RANGE_1_15MHZ
    str  r0, [r1, #XOSC_CTRL]   ; the frequency range first
    movw r0, #XOSC_STARTUP_DELAY
    str  r0, [r1, #XOSC_STARTUP]
    ldr  r0, lit_xosc_enable
    str  r0, [r1, #XOSC_CTRL]   ; then on
xosc_wait:
    ldr  r0, [r1, #XOSC_STATUS]
    cmp  r0, #0
    bge  xosc_wait              ; bit 31 STABLE
    ldr  r1, lit_clocks
    movs r0, #2                 ; SRC 2: XOSC
    str  r0, [r1, #CLK_REF_CTRL]
ref_wait:
    ldr  r0, [r1, #CLK_REF_SELECTED]
    cmp  r0, #1 << 2
    bne  ref_wait
    bx   lr

hang:
    b    hang

; ---- IO_BANK0 and PADS_BANK0 out of reset, GPIO 25: SIO, output; changes r0 .. r2 ----
setup_led:
    ldr  r1, lit_resets_clr
    ldr  r0, lit_reset_bits
    str  r0, [r1]
    ldr  r1, lit_resets
setup_wait:
    ldr  r2, [r1, #RESETS_RESET_DONE]
    and  r2, r2, r0
    cmp  r2, r0
    bne  setup_wait
    ldr  r1, lit_pads_clr
    mov  r0, #PAD_ISO
    str  r0, [r1, #PADS_GPIO25]
    ldr  r1, lit_io_bank0
    movs r0, #FUNCSEL_SIO
    str  r0, [r1, #GPIO25_CTRL]
    ldr  r1, lit_sio
    ldr  r0, lit_led_bit
    str  r0, [r1, #SIO_GPIO_OE_SET]
    bx   lr

    align 4
lit_resets:      dd RESETS_BASE
lit_resets_clr:  dd RESETS_BASE + ALIAS_CLR
lit_reset_bits:  dd RESET_BITS
lit_pads_clr:    dd PADS_BANK0_BASE + ALIAS_CLR
lit_io_bank0:    dd IO_BANK0_BASE
lit_sio:         dd SIO_BASE
lit_led_bit:     dd 1 << 25
lit_scs:          dd SCS_BASE
lit_reset_timer0: dd RESET_TIMER0
lit_ticks:        dd TICKS_BASE
lit_timer0:       dd TIMER0_BASE
lit_interval:     dd INTERVAL_US
lit_xosc:         dd XOSC_BASE
lit_xosc_enable:  dd XOSC_ENABLE_1_15MHZ
lit_clocks:       dd CLOCKS_BASE
