; ***************************************************************************
; 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.   *
; ***************************************************************************





; ***************************************************************************
; macros



; ---------------------------------------------------------------------------
; Utils_WaitNanoSecs  waittime_in_ns , cyles_already_used , waitcount_register 
;
;     cycles already used will be subtracted from the delay
;     the waittime resolution is 1 cycle (delay from exact to +1 cycle)
;     the maximum delay at 20MHz (50ns/clock) is 38350ns
;     waitcount register must specify an immediate register
; taken from https://www.mikrocontroller.net/articles/AVR_Assembler_Makros#Verz%C3%B6gerung_um_X_Nanosekunden
;
.set     Osc_Hz         = clock                    ; 1 MHz
.set     cycle_time_ns  = (1000000000 / Osc_Hz)      ; clock duration
.macro   Utils_WaitNanoSecs
  .set cycles = ((@0 + cycle_time_ns - 1) / cycle_time_ns - @1)
  .if (cycles > (255 * 3 + 2))
    .error "MACRO Utils_WaitNanoSecs - too many cycles to burn"
  .else
    .if (cycles > 6)
      .set  loop_cycles = (cycles / 3)      
      ldi   @2,loop_cycles
      dec   @2
      brne  pc-1
      .set  cycles = (cycles - (loop_cycles * 3))
    .endif
    .if (cycles > 0)
      .if   (cycles & 4)
        rjmp  pc+1
        rjmp  pc+1
      .endif
      .if   (cycles & 2)
        rjmp  pc+1
      .endif
      .if   (cycles & 1)
        nop
      .endif
    .endif
  .endif
.endmacro




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


.dseg

utilsDataBegin:
  utilsSeed:     .byte 2
utilsDataEnd:



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


.cseg

UTILS_BEGIN:


utilsDateString:    .db "%YEAR%-%MONTH%-%DAY%-%HOUR%:%MINUTE%", 0, 0




; ---------------------------------------------------------------------------
; Utils_Init
;
; IN:
; OUT:
; - CFLAG: set if okay, clear on error
; USED: R16, R17, R18, R19, X, (R20, R21, R24, R25, X, Z)

Utils_Init:
  rcall utilsSetupSeed                   ; (R16, R18, R19, R20, R21, R24, R25, X, Z)

  ; preset SRAM data area
  ldi xh, HIGH(utilsDataBegin)
  ldi xl, LOW(utilsDataBegin)
  clr r16
  ldi r17, (utilsDataEnd-utilsDataBegin)
  rcall Utils_FillSram                   ; (r17, x)

  sts utilsSeed, r18
  sts utilsSeed+1, r19

  sec
  ret



; ---------------------------------------------------------------------------
; setup seed
;
; IN:
; OUT:
; REGS: R16, R18, R19, R20, R21, R24, R25, X, Z

utilsSetupSeed:
  rcall Utils_ReadSeed
  mov r20, r18
  mov r21, r19

  ; default initial seed
  ldi r18, 0xe1
  ldi r19, 0xac

  ; work stored seed into it
  mov r16, r20
  rcall utilsWorkByteIntoSeed
  mov r16, r21
  rcall utilsWorkByteIntoSeed

  ; work date string into seed
  ldi zl, LOW(utilsDateString*2)
  ldi zh, HIGH(utilsDateString*2)
  rcall utilsWorkProgStringIntoSeed  ; (R16, Z)

  ; work sram content into seed
  rcall utilsWorkSramContentIntoSeed ; (R16, R24, R25, X)
  
  ; store seed in EEPROM
  rcall Utils_WriteSeed              ; (R16, R17, X)
  ret



; ---------------------------------------------------------------------------
; utilsWorkProgStringIntoSeed
;
; IN:
; - Z  : pointer to string to work into seed
; - R18: low byte of current seed
; - R19: high byte of current seed
; OUT:
; - R18: low byte of updated seed
; - R19: high byte of updated seed
; USED: R16, Z

utilsWorkProgStringIntoSeed:
    lpm r16, Z+
    tst r16
    breq utilsWorkProgStringIntoSeed_done
    rcall utilsWorkByteIntoSeed
  rjmp utilsWorkProgStringIntoSeed
utilsWorkProgStringIntoSeed_done:
  ret



; ---------------------------------------------------------------------------
; utilsWorkSramContentIntoSeed
; IN:
; - Z  : pointer to string to work into seed
; - R18: low byte of current seed
; - R19: high byte of current seed
; OUT:
; - R18: low byte of updated seed
; - R19: high byte of updated seed
; USED: R16, R24, R25, X

utilsWorkSramContentIntoSeed:
  ldi xl, LOW(SRAM_START)
  ldi xh, HIGH(SRAM_START)
  ldi r24, LOW(RAMEND-SRAM_START)
  ldi r25, HIGH(RAMEND-SRAM_START)
utilsWorkSramContentIntoSeed_loop:
    ld r16, X+
    rcall utilsWorkByteIntoSeed
    sbiw r25:r24, 1
  brne utilsWorkSramContentIntoSeed_loop
  ret



; ---------------------------------------------------------------------------
; utilsWorkByteIntoSeed
;
; IN:
; - R16: byte to work into the seed
; - R18: low byte of current seed
; - R19: high byte of current seed
; OUT:
; - R18: low byte of updated seed
; - R19: high byte of updated seed
; USED:

utilsWorkByteIntoSeed:
    eor r18, r16
    clc
    sbrc r19, 7
      sec        ; only executed if bit 7 is set in r19
    rol r18
    rol r19
    ret


; ---------------------------------------------------------------------------
; Utils_Fini
;
; IN:
; OUT:
; - CFLAG: set if okay, clear on error
; USED: R16, R17, R18, X, Y


Utils_Fini:
  sec
  ret



; ---------------------------------------------------------------------------
; Utils_FillSram
;
; IN:
; - x: pointer to SRAM to fill
; - r16: value to fill the SRAM with
; - r17: size of area to fill
; OUT:
; - nothing
; MODIFIED REGISTERS: r17, x

Utils_FillSram:
  tst r17
  breq Utils_FillSram_end

Utils_FillSram_loop:
    st x+, r16
    dec r17
  brne Utils_FillSram_loop
Utils_FillSram_end:
  ret



#if 0
; ---------------------------------------------------------------------------
; Increment a 32 bit counter at the address given by X.
; IN:
; - X: Address of the 4 byte counter (1. byte is LSB)
; OUT:
; - nothing
; MODIFIED REGISTERS: r18, r19, r20, r21, 22

Utils_IncrementCounter32:
  ld r18, x+
  ld r19, x+
  ld r20, x+
  ld r21, x
  ldi r22, 1
  add r18, r22
  clr r22       ; doesn't affect carry flag
  adc r19, r22
  adc r20, r22
  adc r21, r22
  st x, r21
  st -x, r20
  st -x, r19
  st -x, r18
  ret
#endif



; ---------------------------------------------------------------------------
; Increment a 16 bit counter at the address given by X.
; IN:
; - X: Address of the 2 byte counter (1. byte is LSB)
; OUT:
; - nothing
; MODIFIED REGISTERS: r18, r19, 22

Utils_IncrementCounter16:
  ld r18, x+
  ld r19, x
  ldi r22, 1
  add r18, r22
  clr r22       ; doesn't affect carry flag
  adc r19, r22
  st x, r19
  st -x, r18
  ret




; ---------------------------------------------------------------------------
; Utils_ReadEepromIncr
;
; Read a byte from EEPROM (see example in ATtiny24/44/84 manual p.19).
;
; IN:
; - X: EEPROM Address to read from
; OUT:
; - R16: byte read
; - X: EEPROM Address incremented
; MODIFIED REGISTERS: R16

Utils_ReadEepromIncr:
  sbic EECR, EEPE          ; wait for previous write to complete (if any)
    rjmp Utils_ReadEepromIncr
  out EEARH, xh            ; set EEPROM address
  out EEARL, xl
  sbi EECR, EERE           ; start EEPROM read by writing EERE
  in r16, EEDR             ; read data from data register
  adiw xh:xl, 1
  ret



; ---------------------------------------------------------------------------
; Utils_WriteEepromIncr
;
; Write a byte to EEPROM (see example in ATtiny24/44/84 manual p.18).
;
; IN:
; - R16: byte to write
; - X: EEPROM Address to write to
; OUT:
; - X: EEPROM Address incremented
; MODIFIED REGISTERS: R17

Utils_WriteEepromIncr:
  sbic EECR, EEPE          ; wait for previous write to complete (if any)
    rjmp Utils_WriteEepromIncr
  ldi r17, (0<<EEPM1) | (0<<EEPM0) ; set programming mode
  out EECR, r17
  out EEARH, xh                    ; set EEPROM address
  out EEARL, xl
  out EEDR,  r16                   ; write data to data register
  sbi EECR, EEMPE                  ; write logical one to EEMPE
  sbi EECR, EEPE                   ; start EEPROM write by setting EEPE
  adiw xh:xl, 1
  ret



; ---------------------------------------------------------------------------
; Utils_PseudoRandom
;
; Generate a pseudo random number.
; (see https://www.avrfreaks.net/s/topic/a5C3l000000URNfEAO/t119045?comment=P-1021038)
;
; IN:
; OUT:
; - R16: 8 bit pseudo random number
; REGS: R16, R17, R18, R19

Utils_PseudoRandom:
  lds r16, utilsSeed
  lds r17, utilsSeed+1
  ldi r18, 0x9c
  ldi r19, 8
Utils_PseudoRandom_step:
    lsr   r17
    ror   r16
    brcc  Utils_PseudoRandom_nomask
    eor   r17,    r18
Utils_PseudoRandom_nomask:
    dec   r19
  brne  Utils_PseudoRandom_step
  sts utilsSeed, r16
  sts utilsSeed+1, r17
  ret                    ; result in r16



; ---------------------------------------------------------------------------
; Update seed in EEPROM.
;
; IN:
; OUT:
; REGS: R18, R19, (R16, R17, X)

Utils_UpdateSeedInEeprom:
  lds r18, utilsSeed
  lds r19, utilsSeed+1
  rcall Utils_WriteSeed ; (R16, R17, X)
  ret



; ---------------------------------------------------------------------------
; Utils_ReadUid
;
; Read UID from EEPROM.
;
; IN:
; OUT:
; - R18:R19:R20:R21: UID
; REGS: R16, X

Utils_ReadUid:
  push r15
    in r15, SREG
    cli
    ldi xl, LOW(EEPROM_OFFS_UUID)
    ldi xh, HIGH(EEPROM_OFFS_UUID)
    rcall Utils_ReadEepromIncr ; (R16)
    mov r18, r16
    rcall Utils_ReadEepromIncr ; (R16)
    mov r19, r16
    rcall Utils_ReadEepromIncr ; (R16)
    mov r20, r16
    rcall Utils_ReadEepromIncr ; (R16)
    mov r21, r16
  pop r15
  out SREG, r15
  ret



; ---------------------------------------------------------------------------
; Utils_SetupUid
;
; Reads UID from EEPROM. If not set generate a new one and store it in EEPROM.
;
; IN:
; OUT:
; - CFLAG set if new generated, cleared if there already was one.
; REGS: R15, R16, R18, R19, R20, R21, X (R17)

Utils_SetupUid:
  in r15, SREG
  cli
    rcall Utils_ReadUid            ; (R16, X)
    mov r16, r18                   ; all 0x00?
    or r16, r19
    or r16, r20
    or r16, r21
    breq Utils_SetupUid_generate   ; yep, go generate one
    mov r16, r18                   ; all 0xff?
    and r16, r19
    and r16, r20
    and r16, r21
    inc r16
    breq Utils_SetupUid_generate   ; yep, go generate one
  out SREG, r15
  clc
  ret
Utils_SetupUid_generate:
    ldi xl, LOW(EEPROM_OFFS_UUID)
    ldi xh, HIGH(EEPROM_OFFS_UUID)
    rcall Utils_PseudoRandom       ; byte 0 (R16, R17, R18, R19)
    inc r16
    rcall Utils_WriteEepromIncr        ; (R17)
    rcall Utils_PseudoRandom       ; byte 1
    rcall Utils_WriteEepromIncr
    rcall Utils_PseudoRandom       ; byte 2
    rcall Utils_WriteEepromIncr
    rcall Utils_PseudoRandom       ; byte 3
    rcall Utils_WriteEepromIncr
  
    rcall Utils_UpdateSeedInEeprom ; (R16, R17, R18, R19, X)
  
  out SREG, r15
  sec
  ret



; ---------------------------------------------------------------------------
; Utils_ReadSeed
;
; Read seed from EEPROM.
;
; IN:
; OUT:
; - R18:R19: seed read
; REGS: R16, X

Utils_ReadSeed:
  push r15
    in r15, SREG
    cli
    ldi xl, LOW(EEPROM_OFFS_SEED)
    ldi xh, HIGH(EEPROM_OFFS_SEED)
    rcall Utils_ReadEepromIncr ; (R16)
    mov r18, r16
    rcall Utils_ReadEepromIncr ; (R16)
    mov r19, r16
  pop r15
  out SREG, r15
  ret



; ---------------------------------------------------------------------------
; Utils_WriteSeed
;
; Read seed from EEPROM.
;
; IN:
; - R18:R19: seed to write
; OUT:
; REGS: R16, X, (R17)
Utils_WriteSeed:
  push r15
    in r15, SREG
    cli
    ldi xl, LOW(EEPROM_OFFS_SEED)
    ldi xh, HIGH(EEPROM_OFFS_SEED)
    mov r16, r18
    rcall Utils_WriteEepromIncr       ; (R17)
    mov r16, r19
    rcall Utils_WriteEepromIncr       ; (R17)
  pop r15
  out SREG, r15
  ret



UTILS_END:
.equ MODULE_SIZE_UTILS = UTILS_END-UTILS_BEGIN

