; ***************************************************************************
; copyright   : (C) 2023 by Martin Preuss
; email       : martin@libchipcard.de
;
; ***************************************************************************
; * This file is part of the project "AqHome".                              *
; * Please see toplevel file COPYING of that project for license details.   *
; ***************************************************************************





; ***************************************************************************
; defs

.equ TIMER_FLAGS_IF_ADDR = 1




; ***************************************************************************
; data

.dseg

timerModuleData:
timerModuleTickCounter:      .byte 1
timerTicksSinceLastRun:      .byte 2
timerModuleCounterSecs:      .byte 4
timerInterrupts:             .byte 2
timerModuleData_end:




; ***************************************************************************
; code

.cseg

TIMER_BEGIN:



; ---------------------------------------------------------------------------
; Timer_Init
;
; IN:
; - nothing
; OUT:
; - nothing
; USED: r16, r17, x

Timer_Init:            ; setup timer for IRQ every 100ms
  ; reset data in SDRAM
  ldi xh, HIGH(timerModuleData)
  ldi xl, LOW(timerModuleData)
  ldi r16, 0
  ldi r17, (timerModuleData_end-timerModuleData)
  rcall Utils_FillSram

; CTC mode (WGM2:0=2, OCR0A=value, OCF0A Flag =1,  -> IRQ_OC0A

  ; CMP-A interrupt about every 100ms
  ldi r16, (1<<CS02) | (1<<CS00)  ; Prescaler 1024
  out TCCR0B, r16
  
  ldi r16, (1<<WGM01) ; CTC mode
  out TCCR0A, r16
  
  ldi r16, 98-1      ; (1,000,000/1024)/10 = 97.65625
  out OCR0A, r16

  ldi r16, (1<<OCF0A) ; clear pending interrupts
  out TIFR0, r16
  
  ldi r16, (1<<OCIE0A)
  out TIMSK0, r16
  
  rcall timerInitTimers
  
  ret



; ---------------------------------------------------------------------------
; Timer_Fini
;
; IN:
; - nothing
; OUT:
; - nothing
; USED:

Timer_Fini:
  ret



; ---------------------------------------------------------------------------
; Timer_Run
;
; IN:
; - nothing
; OUT:
; - CFLAG: set if something done, cleared otherwise
; USED: r15, r16, r24, r25 (more depending on called routines)

Timer_Run:
  in r15, SREG
  cli
    lds r24, timerTicksSinceLastRun
    lds r25, timerTicksSinceLastRun+1
    clr r16                               ; replace with 0 for next IRQ
    sts timerTicksSinceLastRun, r16
    sts timerTicksSinceLastRun+1, r16
  out SREG, r15                           ; restore global IRQ flag
  sbiw r25:r24, 0
  clc                                     ; flag "nothing done"
  breq Timer_Run_End
Timer_Run_loop:                           ; for every occurred 100ms irq
    push r24
      push r25
        rcall onEvery100ms
      pop r25
    pop r24
    lds r16, timerModuleTickCounter
    inc r16
    cpi r16, 10
    brcc Timer_Run_SecondElapsed
    sts timerModuleTickCounter, r16
    rjmp Timer_Run_loop_end
Timer_Run_SecondElapsed:
    clr r16
    sts timerModuleTickCounter, r16
    push r24
      push r25
        rcall timerRunTimers
      pop r25
    pop r24
Timer_Run_loop_end:
    sbiw r25:r24, 1
  brne Timer_Run_loop
  sec
Timer_Run_End:
  ret



; ---------------------------------------------------------------------------
; Set timer value.
;
; Setting a timer to 0 effectively stops the timer.
;
; IN:
; - r16: new timer value (low)
; - r17: new timer value (high)
; - X  : pointer to timer value in SRAM
; OUT:
; - nothing
; REGS: X

Timer_SetValue:
  push r15
    in r15, SREG
    cli
    st X+, r16
    st X+, r17
    out SREG, r15
  pop r15
  ret



; ---------------------------------------------------------------------------
; Set timer value to 1s.
;
; IN:
; - X  : pointer to timer value in SRAM
; OUT:
; - nothing
; REGS: X

Timer_SetValueTo1s:
  push r16
    push r17
      ldi r16, 1
      clr r17
      rcall Timer_SetValue
    pop r17
  pop r16
  ret




timerInitTimers:
  ldi zl, LOW(timerList*2)
  ldi zh, HIGH(timerList*2)
  clr r16                      ; run var for pos in time table
  clr r17                      ; 0 for adc
timerInitTimers_loop:
    rcall timerReadTableEntry
    mov r18, xl
    or r18, xh
    breq timerInitTimers_end
    mov r18, r20
    or r18, r21
    breq timerInitTimers_writeInitial
    add r20, r16               ; add counter pos in table so that not all timers elapse at the same time
    adc r21, r17
timerInitTimers_writeInitial:
    st X+, r20
    st X, r21
    inc r16
    inc r16
  rjmp timerInitTimers_loop

timerInitTimers_end:
  ret



timerRunTimers:
  ldi xl, LOW(timerModuleCounterSecs)
  ldi xh, HIGH(timerModuleCounterSecs)
  rcall Utils_IncrementCounter32
  ldi zl, LOW(timerList*2)
  ldi zh, HIGH(timerList*2)
timerRunTimers_loop:
    rcall timerReadTableEntry
    mov r16, xl
    or r16, xh
    breq timerRunTimers_end
    mov r16, r22
    andi r16, TIMER_FLAGS_IF_ADDR
    breq timerRunTimers_l1         ; no need to check address
    lds r16, com2Address           ; check address
    tst r16
    breq timerRunTimers_loop       ; no address, ignore counter
timerRunTimers_l1:
    ld r24, X+
    ld r25, X
    sbiw r25:r24, 1
    brcs timerRunTimers_loop        ; overflow, so already was zero, ignore entry
    breq timerRunTimers_reachedZero ; reached zero
    st X, r25                  
    st -X, r24
  rjmp timerRunTimers_loop
timerRunTimers_reachedZero:
    st X, r21                       ; reset initial value
    st -X, r20
    push zl
      push zh
        rcall timerCallR19R18
      pop zh
    pop zl
  rjmp timerRunTimers_loop
timerRunTimers_end:
  ret



; ---------------------------------------------------------------------------
; read time table entry.
;
; IN:
; - Z      : pointer to time list entry (suitable for LPM)
; OUT:
; - r19:r18: handler routine
; - X      : SRAM address for counter
; - r21:r20: initial value
; - r22    : flags
; - r23    : reserved
timerReadTableEntry:
  lpm xl, Z+    ; SRAM address of counter
  lpm xh, Z+
  lpm r18, Z+   ; routine (low)
  lpm r19, Z+   ; routine (high)
  lpm r22, Z+   ; flags
  lpm r23, Z+   ; reserved
  lpm r20, Z+   ; initial value
  lpm r21, Z+
  ret



timerCallR19R18:
  push r18
  push r19
  ret



; ---------------------------------------------------------------------------
; OC0A interrupt handler
;
; Called every 100 milliseconds, increments timerTicksSinceLastRun. The rest is done outside ISR
; in Timer_Run.

timerIrqOC0A:
  push r15
    in r15, SREG

    push r24
      push r25
        lds r24, timerTicksSinceLastRun
        lds r25, timerTicksSinceLastRun+1
        adiw r25:r24, 1
        sts timerTicksSinceLastRun, r24
        sts timerTicksSinceLastRun+1, r25
        
        lds r24, timerInterrupts
        lds r25, timerInterrupts+1
        adiw r25:r24, 1
        sts timerInterrupts, r24
        sts timerInterrupts+1, r25

      pop r25
    pop r24
    out SREG, r15
  pop r15
  reti



TIMER_END:
.equ MODULE_SIZE_TIMER = TIMER_END-TIMER_BEGIN



