; ***************************************************************************
; copyright   : (C) 2026 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.   *
; ***************************************************************************

#ifndef AQH_AVR_MODULES_MULTIFAN_MAIN_ASM
#define AQH_AVR_MODULES_MULTIFAN_MAIN_ASM



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


.equ MULTIFAN_FAN_OFFS_COUNTER_LO         = 0
.equ MULTIFAN_FAN_OFFS_COUNTER_HI         = 1
.equ MULTIFAN_FAN_OFFS_CURR_FREQUENCY_LO  = 2
.equ MULTIFAN_FAN_OFFS_CURR_FREQUENCY_HI  = 3
.equ MULTIFAN_FAN_OFFS_DUTYCYCLE          = 4  ; 0-99  (*40/100=PWM value)
.equ MULTIFAN_FAN_SIZE                    = 5



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

.dseg

multiFanDataBegin:
  multiFanPrevPortVal: .byte 1
  multiFanObjects:     .byte (MULTIFAN_FANS*MULTIFAN_FAN_SIZE)
multiFanDataEnd:



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

.cseg


MultiFan_Init:
  ldi xl, LOW(multiFanDataBegin)
  ldi xh, HIGH(multiFanDataBegin)
  ldi r17, (multiFanDataEnd-multiFanDataBegin)
  clr r16
  bigcall Utils_FillSram
  
  ldi r16, 0xff
  sts multiFanPrevPortVal, r16

  rcall multiFanSetupTimer
  rcall multiFanSetupPins

.ifdef MULTIFAN_SETUPIRQ_FN
  bigcall MULTIFAN_SETUPIRQ_FN
.endif

  sec
  ret
; @end



; ---------------------------------------------------------------------------
; @routine MultiFan_OnEverySecond @global
;
; @clobbers r16, r17, r18, r19, r24, Y

MultiFan_OnEverySecond:
  ldi yl, LOW(multiFanObjects)
  ldi yh, HIGH(multiFanObjects)
  ldi r24, MULTIFAN_FANS
  push r15
    inr r15, SREG
MultiFan_OnEverySecond_loop:
      cli
      ldd r16, Y+MULTIFAN_FAN_OFFS_COUNTER_LO
      ldd r17, Y+MULTIFAN_FAN_OFFS_COUNTER_HI
      ; clear counter for next round
      clr r18
      std Y+MULTIFAN_FAN_OFFS_COUNTER_LO, r18
      std Y+MULTIFAN_FAN_OFFS_COUNTER_HI, r18
      outr SREG, r15 ; restore IRQ flag

      ; counter*30=RPM
      mov r18, r16
      mov r19, r17
      lsl r16        ; x2
      rol r17
      add r16, r18   ; x3
      adc r17, r19
      lsl r16        ; x6
      rol r17
      add r16, r18   ; x7
      adc r17, r19
      lsl r16        ; x14
      rol r17
      add r16, r18   ; x15
      adc r17, r19
      lsl r16        ; x30
      rol r17
      
      std Y+MULTIFAN_FAN_OFFS_CURR_FREQUENCY_LO, r16
      std Y+MULTIFAN_FAN_OFFS_CURR_FREQUENCY_HI, r17

      adiw yh:yl, MULTIFAN_FAN_SIZE
      dec r24
    brne MultiFan_OnEverySecond_loop

  pop r15
  ret
; @end



; ---------------------------------------------------------------------------
; @routine MultiFan_CheckPort @global
;
; @param r16 new port value (e.g. PINB)
; @param r18 port change value (0 in a bit pos means: bit flipped to 0)
; @clobbers r24, r25, Y

MultiFan_CheckPort:
  ldi yl, LOW(multiFanObjects)
  ldi yh, HIGH(multiFanObjects)

  sbrs r18, MULTIFAN_TACHO1_PIN
    rcall multiFanIncCounter    ; (r24, r25)

.if MULTIFAN_FANS >1
  adiw yh:yl, MULTIFAN_FAN_SIZE
  sbrs r18, MULTIFAN_TACHO2_PIN
    rcall multiFanIncCounter    ; (r24, r25)
.endif

.if MULTIFAN_FANS >2
  adiw yh:yl, MULTIFAN_FAN_SIZE
  sbrs r18, MULTIFAN_TACHO3_PIN
    rcall multiFanIncCounter
.endif

  ret
; @end



MultiFan_SetFan1Duty:
  ldi yl, LOW(multiFanObjects)
  ldi yh, HIGH(multiFanObjects)
  std Y+MULTIFAN_FAN_OFFS_DUTYCYCLE, r16

  rcall multiFanCalcTimerValue ; r17:r16=timer value
  ; set OC register
  outr TC1H, r17
  outr OCR1A, r16
  ret
; @end



.if MULTIFAN_FANS > 1
MultiFan_SetFan2Duty:
  ldi yl, LOW(multiFanObjects+MULTIFAN_FAN_SIZE)
  ldi yh, HIGH(multiFanObjects+MULTIFAN_FAN_SIZE)
  std Y+MULTIFAN_FAN_OFFS_DUTYCYCLE, r16
  
  rcall multiFanCalcTimerValue ; r17:r16=timer value
  ; set OC register
  outr TC1H, r17
  outr OCR1B, r16
  ret
; @end
.endif



.if MULTIFAN_FANS > 2
MultiFan_SetFan3Duty:
  ldi yl, LOW(multiFanObjects+(MULTIFAN_FAN_SIZE*2))
  ldi yh, HIGH(multiFanObjects+(MULTIFAN_FAN_SIZE*2))
  std Y+MULTIFAN_FAN_OFFS_DUTYCYCLE, r16
  
  rcall multiFanCalcTimerValue ; r17:r16=timer value
  ; set OC register
  outr TC1H, r17
  outr OCR1D, r16
  ret
; @end
.endif


; ---------------------------------------------------------------------------
; @routine multiFanCalcTimerValue
;
; @param r16 duty value (0-99)
; @return r17:r16 timer value
; @clobbers r16, r17, r18, r19, r20, r21, r25

multiFanCalcTimerValue:
  ; x40
  clr r17
  mov r18, r16
  mov r19, r17
  lsl r16      ; x2
  rol r17
  lsl r16      ; x4
  rol r17
  add r16, r18 ; x5
  adc r17, r19
  lsl r16      ; x10
  rol r17
  lsl r16      ; x20
  rol r17
  lsl r16      ; x40
  rol r17
  ; /100
  mov r20, r16
  mov r21, r17
  ldi r22, 100
  clr r23
  bigjmp Utils_Divu16_16_16 ; r17:r16=result
; @end




; ---------------------------------------------------------------------------
; @routine multiFanIncCounter
;
; @param Y pointer to FAN object
; @clobbers r24, r25

multiFanIncCounter:
  ldd r24, Y+MULTIFAN_FAN_OFFS_COUNTER_LO
  ldd r25, Y+MULTIFAN_FAN_OFFS_COUNTER_HI
  adiw r25:r24, 1
  std Y+MULTIFAN_FAN_OFFS_COUNTER_LO, r24
  std Y+MULTIFAN_FAN_OFFS_COUNTER_HI, r25
  ret
; @end



; ---------------------------------------------------------------------------
; @routine multiFanSetupPins
;
; @clobbers r16

multiFanSetupPins:
  ; set pins for PWM to output
  inr r16, MULTIFAN_PWM_DDR
.if MULTIFAN_FANS == 1
  ori r16, (1<<MULTIFAN_PWM1_PIN) | (0<<MULTIFAN_PWM2_PIN) | (0<<MULTIFAN_PWM3_PIN)
.elif MULTIFAN_FANS == 2
  ori r16, (1<<MULTIFAN_PWM1_PIN) | (1<<MULTIFAN_PWM2_PIN) | (0<<MULTIFAN_PWM3_PIN)
.elif MULTIFAN_FANS == 3
  ori r16, (1<<MULTIFAN_PWM1_PIN) | (1<<MULTIFAN_PWM2_PIN) | (1<<MULTIFAN_PWM3_PIN)
.else
  .error "Invalid number of fans (only 1-3 allowed)
.endif
  outr MULTIFAN_PWM_DDR, r16

  ; set pins for Tacho to input
  inr r16, MULTIFAN_TACHO_DDR
.if MULTIFAN_FANS == 1
  cbr r16, (1<<MULTIFAN_TACHO1_PIN) | (0<<MULTIFAN_TACHO2_PIN) | (0<<MULTIFAN_TACHO3_PIN)
.elif MULTIFAN_FANS == 2
  cbr r16, (1<<MULTIFAN_TACHO1_PIN) | (1<<MULTIFAN_TACHO2_PIN) | (0<<MULTIFAN_TACHO3_PIN)
.elif MULTIFAN_FANS == 3
  cbr r16, (1<<MULTIFAN_TACHO1_PIN) | (1<<MULTIFAN_TACHO2_PIN) | (1<<MULTIFAN_TACHO3_PIN)
.else
  .error "Invalid number of fans (only 1-3 allowed)
.endif
  outr MULTIFAN_PWM_DDR, r16

  ret
; @end



; ---------------------------------------------------------------------------
; @routine multiFanSetupTimer
;
; @clobbers r16, r17

multiFanSetupTimer:
.if clock == 1000000
  ; prescaler 1
  ldi r16, (0<<PSR1)|(0<<DTPS11)|(0<<DTPS10) | (0<<CS13)|(0<<CS12)|(0<<CS11)|(1<<CS10)
.elif clock == 8000000
  ; prescaler 8
  ldi r16, (0<<PSR1)|(0<<DTPS11)|(0<<DTPS10) | (0<<CS13)|(1<<CS12)|(0<<CS11)|(0<<CS10)
.else
  .error "Unhandled clock"
.endif
  outr TCCR1B, r16
  ; select fast PWM, enable PIN output (don't activate negated pin outputs)
.if MULTIFAN_FANS == 1
  ldi r16, (1<<COM1A1)|(0<<COM1A0) | (0<<COM1B1)|(0<<COM1B0) | (1<<PWM1A) | (0<<PWM1B)
  outr TCCR1A, r16
  ldi r16, (1<<COM1A1S)|(0<<COM1A0S) | (1<<COM1B1S)|(0<<COM1B0S) | (0<<PWM1D)
  outr TCCR1C, r16
  ldi r16, (0<<FPIE1)|(0<<FPEN1)|(0<<FPNC1)|(0<<FPES1)(0<<FPAC1)|(0<<FPF1) | (0<<WGM11)|(0<<WGM10)
  outr TCCR1D, r16
.elif MULTIFAN_FANS == 2
  ldi r16, (1<<COM1A1)|(0<<COM1A0) | (1<<COM1B1)|(0<<COM1B0) | (1<<PWM1A) | (1<<PWM1B)
  outr TCCR1A, r16
  ldi r16, (1<<COM1A1S)|(0<<COM1A0S) | (1<<COM1B1S)|(0<<COM1B0S) | (0<<COM1D1)|(0<<COM1D0) | (0<<PWM1D)
  outr TCCR1C, r16
  ldi r16, (0<<FPIE1)|(0<<FPEN1)|(0<<FPNC1)|(0<<FPES1)(0<<FPAC1)|(0<<FPF1) | (0<<WGM11)|(0<<WGM10)
  outr TCCR1D, r16
.elif MULTIFAN_FANS == 3
  ldi r16, (1<<COM1A1) |(0<<COM1A0)  | (1<<COM1B1) |(0<<COM1B0)  | (1<<PWM1A) | (1<<PWM1B)
  outr TCCR1A, r16
  ldi r16, (1<<COM1A1S)|(0<<COM1A0S) | (1<<COM1B1S)|(0<<COM1B0S) | (1<<COM1D1)|(0<<COM1D0) | (1<<PWM1D)
  outr TCCR1C, r16
  ldi r16, (0<<FPIE1)|(0<<FPEN1)|(0<<FPNC1)|(0<<FPES1)|(0<<FPAC1)|(0<<FPF1) | (0<<WGM11)|(0<<WGM10)
  outr TCCR1D, r16
.else
  .error "Invalid number of fans (only 1-3 allowed)
.endif
  ; 1MHz/40: 25kHz
  clr r17
  ldi r16, 40-1
  outr TC1H, r17
  outr OCR1C, r16
  ; preset fans to 50 %
  ldi r16, 20
  outr TC1H, r17
  outr OCR1A, r16
.if MULTIFAN_FANS > 1
  outr TC1H, r17
  outr OCR1B, r16
.endif
.if MULTIFAN_FANS > 2
  outr TC1H, r17
  outr OCR1D, r16
.endif

 ret
; @end



; ---------------------------------------------------------------------------
; @routine multiFanPinChangeIsr @global @isr
;

multiFanPinChangeIsr:
  push r15
    in r15, SREG
    push r16
      in r16, MULTIFAN_TACHO_INPUT
      push r18
        lds r18, multiFanPrevPortVal
        sts multiFanPrevPortVal, r16
        eor r18, r16                  ; r18: changes in bits=1
        com r18                       ; r18: changes in bits=0
        or r18, r16                   ; r18: 0=bit flipped to 0

        push r24
          push r25
            push yl
              push yh
                rcall MultiFan_CheckPort ; (r24, r25, Y)
              pop yh
            pop yl
          pop r25
        pop r24
      pop r18
    pop r16
    out SREG, r15
  pop r15
  reti
; @end
    







#endif
