; ***************************************************************************
; 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_100MS       = 1
.equ TIMER_FLAGS_1S          = 2




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

.dseg

timerModuleData:
timerModuleTickCounter:      .byte 1
timerModuleFlags:            .byte 1
timerModuleCounterSecs:      .byte 4

timerModuleDerivedCounters:  .byte 10 ; must correspond to entries in timer table!

timerModuleData_end:




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

.cseg

TIMER_BEGIN:


; number of entries here must be equal to bytes in timerModuleDerivedCounters!
timerTable:
  .db  5, 0, LOW(onEvery5s),  HIGH(onEvery5s)  ; 5s : overflow after 5*1s  (1)
  .db  2, 0, LOW(onEvery10s), HIGH(onEvery10s) ; 10s: overflow after 2*5s  (2)
  .db  3, 0, LOW(onEvery30s), HIGH(onEvery30s) ; 30s: overflow after 3*10s (3)
  .db  2, 0, LOW(onEvery1m),  HIGH(onEvery1m)  ; 1m : overflow after 2*30s (4)
  .db  5, 0, LOW(onEvery5m),  HIGH(onEvery5m)  ; 5m : overflow after 5*1m  (5)
  .db  3, 0, LOW(onEvery15m), HIGH(onEvery15m) ; 15m: overflow after 3*5m  (6)
  .db  2, 0, LOW(onEvery30m), HIGH(onEvery30m) ; 30m: overflow after 2*15m (7)
  .db  2, 0, LOW(onEvery1h),  HIGH(onEvery1h)  ;  1h: overflow after 2*30m (8)
  .db 12, 0, LOW(onEvery12h), HIGH(onEvery12h) ; 12h: overflow after 12*1h (9)
  .db  2, 0, LOW(onEvery1d),  HIGH(onEvery1d)  ;  1d: overflow after 2*12h (10)
  .db  0, 0



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

Timer_Init:            ; setup timer for 15.2588 interrupts per second (e.g. every 65.5 ms)
  ; 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
  
  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, r17, R19 (more depending on called routines)

Timer_Run:
  in r15, SREG
  cli
  lds r17, timerModuleFlags       ; get timer flags
  clr r16                         ; replace with 0 for next IRQ
  sts timerModuleFlags, r16
  mov r16, r17
  andi r16, TIMER_FLAGS_100MS     ; timer irq occurred?
  brne Timer_Run_100ms_passed     ; yes, go handle
  out SREG, r15                   ; restore global IRQ flag
  clc                             ; nothing done
  ret
Timer_Run_100ms_passed:
  mov r16, r17
  andi r16, TIMER_FLAGS_1S
  brne Timer_Run_1s_passed
  out SREG, r15                   ; restore global IRQ flag
  rcall onEvery100ms              ; only call this one
  sec
  ret
Timer_Run_1s_passed:
  rcall timerIncrementCounters    ; 1s passed, handle cascading counters, get number of callable handlers
  out SREG, r15                   ; restore global IRQ flag
  rcall timerCallFirstOnEveryHandlers
  sec
  ret



; ---------------------------------------------------------------------------
; Increment cascading timer counters.
;
; This routine starts with the first cascaded counter and increments it.
; If that counter value does not overflow (according to timer table entry) then
; handling the timer table is stopped.
; Otherwise the return value is incremented and the next counter entry is inspected
; until the full table is handled.
;
; The result value has to be interpreted as the number of entries started at the first
; which have overflowed.
;
; Example: A return value of 1 means that the first entry overflowed, meaning that 10s have
; elapsed.
; A return value of 2 means that the first entry overflowed (i.e. 10s elapsed) and also the nex
; entry overflowed (i.e. also 30s elapsed).
;
; IN:
; - nothing
; OUT:
; - R16: number of timer table entries which caused an overflow
; REGS: R16, R17, R18, X, Z

timerIncrementCounters:
  ldi xl, LOW(timerModuleDerivedCounters)  ; points to first counter: timerModuleCounterFor10s
  ldi xh, HIGH(timerModuleDerivedCounters)
  ldi zl, LOW(timerTable*2)
  ldi zh, HIGH(timerTable*2)
  
  clr r18
  
timerIncrementCounters_loop:
    lpm r17, Z    ; overflow value
    tst r17
    breq timerIncrementCounters_done ; end of list
    ld r16, X     ; get current counter value
    inc r16
    cp r16, r17   ; compare against overflow value
    brcs timerIncrementCounters_counterSmaller
    clr r16       ; overflow occurred
    st X+, r16
    ; next
    inc r18
    adiw zh:zl, 4
  rjmp timerIncrementCounters_loop

timerIncrementCounters_counterSmaller:
  st X, r16
timerIncrementCounters_done:
  mov r16, r18
  ret



; ---------------------------------------------------------------------------
; Call first N handlers from the timer table.
;
; IN:
; - R16: number of handlers to call (returned by timerIncrementCounters)
; OUT:
; - nothing
; REGS: depening on handler called (expect all registers to be clobbered)

timerCallFirstOnEveryHandlers:
  push r16
    rcall onEvery100ms                 ; always call this one
    rcall onEverySecond                ; and this
  pop r16

  tst r16
  breq timerCallFirstOnEveryHandlers_done
  
  ldi zl, LOW(timerTable*2)
  ldi zh, HIGH(timerTable*2)
  ldi xl, LOW(timerModuleDerivedCounters)  ; points to first counter: timerModuleCounterFor10s
  ldi xh, HIGH(timerModuleDerivedCounters)

timerCallFirstOnEveryHandlers_loop:
    lpm r17, Z                              ; overflow value
    tst r17                                 ; 0?
    breq timerCallFirstOnEveryHandlers_done ; yes: end of list
    adiw zh:zl, 2                           ; skip to handler address
    lpm r20, Z+                             ; read handler address
    lpm r21, Z+
    push r16                                ; call handler
      push xl
        push xh
          push zl
            push zh
              ld r16, X+                    ; current counter value
              rcall timerCallR21R20
            pop zh
          pop zl
        pop xh
      pop xl
    pop r16
    dec r16
  brne timerCallFirstOnEveryHandlers_loop

timerCallFirstOnEveryHandlers_done:
  ret


timerCallR21R20:
  push r20
  push r21
  ret



; ---------------------------------------------------------------------------
; OC0A interrupt handler
;
; Called every 100 milliseconds, sets flag TIMER_FLAGS_100MS.
; Increments an internal counter. If that counter reaches 10 the seconds counter is incremented and a the flag
; TIMER_FLAGS_1S is set.

timerIrqOC0A:
  push r15
    in r15, SREG

    push r16
      push r17
        lds r17, timerModuleFlags
        ori r17, TIMER_FLAGS_100MS
        lds r16, timerModuleTickCounter
        inc r16
        cpi r16, 10
        brcc timerIrq2_l1
        sts timerModuleTickCounter, r16
        rjmp timerIrq2_l2
timerIrq2_l1: ; increment seconds
        ; reset 100ms counter
        clr r16
        sts timerModuleTickCounter, r16
        ; increment seconds
        push xh
          push xl
            push r18
              ldi xh, HIGH(timerModuleCounterSecs)
              ldi xl, LOW(timerModuleCounterSecs)
              ldi r18, 1
              ld r16, X         ; byte 0
              adc r16, r18      ; inc by 1
              dec r18           ; doesn't affect carry flag! (r18 becomes 0 for following ADC)
              st X+, r16
              ld r16, X         ; byte 1
              adc r16, r18
              st X+, r16
              ld r16, X         ; byte 2
              adc r16, r18
              st X+, r16
              ld r16, X         ; byte 3
              adc r16, r18
              st X+, r16
            pop r18
          pop xl
        pop xh
        ori r17, TIMER_FLAGS_1S
timerIrq2_l2:
        sts timerModuleFlags, r17 ; store new timer flags
      pop r17
    pop r16
    out SREG, r15
  pop r15
  reti



TIMER_END:
.equ MODULE_SIZE_TIMER = TIMER_END-TIMER_BEGIN



