; ***************************************************************************
; copyright   : (C) 2025 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 AVR_MODULES_COM2W_COM2W0_H
#define AVR_MODULES_COM2W_COM2W0_H


.dseg

com2w0_iface: .byte COM2W_IFACE_SIZE



.cseg




; ---------------------------------------------------------------------------
; @routine COM2W0_Init
;
; @clobbers 

COM2W0_Init:
  ldi yl, LOW(com2w0_iface)
  ldi yh, HIGH(com2w0_iface)
  rcall NET_Interface_Init                 ; (R16, R17, X)
  ldi r16, COM2W_MODE_IDLE
  rcall com2wSetMode                       ; (R17)

  ; setup CLK line (as input, disable internal pull-up resistor)
  cbi COM_CLK0_DDR, COM_CLK0_PIN           ; set CLK as input
.ifdef COM_CLK0_PUE
  inr r16, COM_CLK0_PUE
  cbr r16, (1<<COM_CLK0_PIN)               ; disable pullup on CLK
  outr COM_CLK0_PUE, r16
.else
  cbi COM_CLK0_OUTPUT, COM_CLK0_PIN        ; disable pullup on CLK
.endif

  ; setup DATA line (as input, disable internal pull-up resistor)
  cbi COM_DATA0_DDR, COM_DATA0_PIN         ; set DATA as input
.ifdef COM_DATA0_PUE
  inr r16, COM_DATA0_PUE
  cbr r16, (1<<COM_DATA0_PIN)              ; disable pullup on DATA
  outr COM_DATA0_PUE, r16
.else
  cbi COM_DATA0_OUTPUT, COM_DATA0_PIN      ; disable pullup on DATA
.endif

  ; setup pin-change interrupt for CLK
  rcall com2w0EnableClkIrq

  inr r16, COM_IRQ_ADDR_CLK0
  sbr r16, (1<<COM_IRQ_BIT_CLK0)           ; enable pin change irq for ATTN line
  outr COM_IRQ_ADDR_CLK0, r16

  inr r16, GIMSK                           ; enable pin change irq PCIE0 or PCIE1
  sbr r16, (1<<COM_IRQ_GIMSK_CLK0)
  outr GIMSK, r16
  ldi r16, (1<<COM_IRQ_GIFR_CLK0)          ; clear pending irq by writing 1 to ATTN bit
  outr GIFR, r16

  ret
; @end



; ---------------------------------------------------------------------------
; @routine COM2W0_Periodically @global
;
; @clobbers R16, Y

COM2W0_Periodically:
  ldi yl, LOW(com2w0_iface)
  ldi yh, HIGH(com2w0_iface)
  rcall com2wPeriodically
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2w0EnableClkIrq
;
; @clobbers 

com2w0EnableClkIrq:
  push r16
    inr r16, COM_IRQ_ADDR_CLK0
    sbr r16, (1<<COM_IRQ_BIT_CLK0)          ; enable pin change irq for CLK line
    outr COM_IRQ_ADDR_CLK0, r16
  pop r16
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2w0DisableClkIrq
;
; @clobbers none

com2w0DisableClkIrq:
  push r16
    inr r16, COM_IRQ_ADDR_CLK0
    cbr r16, (1<<COM_IRQ_BIT_CLK0)        ; disable pin change irq for CLK line
    outr COM_IRQ_ADDR_CLK0, r16
  pop r16
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2w0ClkSetHigh
;
; @clobbers none

com2w0ClkSetHigh:
  cbi COM_CLK0_DDR, COM_CLK0_PIN           ; set CLK as input
.ifdef COM_CLK0_PUE
;  cbi COM_CLK0_PUE, COM_CLK0_PIN           ; disable pullup on CLK
.else
  cbi COM_CLK0_OUTPUT, COM_CLK0_PIN        ; disable pullup on CLK
.endif
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2w0ClkSetLow
;
; @clobbers none

com2w0ClkSetLow:
  sbi COM_CLK0_DDR, COM_CLK0_PIN          ; set CLK as output
  cbi COM_CLK0_OUTPUT, COM_CLK0_PIN       ; set CLK low
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2w0DataSetHigh
;
; @clobbers none

com2w0DataSetHigh:
  cbi COM_DATA0_DDR, COM_DATA0_PIN           ; set DATA as input
.ifdef COM_DATA0_PUE
;  cbi COM_DATA0_PUE, COM_CLK0_PIN           ; disable pullup on DATA
.else
  cbi COM_DATA0_OUTPUT, COM_DATA0_PIN        ; disable pullup on DATA
.endif
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2w0DataSetLow
;
; @clobbers none

com2w0DataSetLow:
  sbi COM_DATA0_DDR, COM_DATA0_PIN          ; set DATA as output
  cbi COM_DATA0_OUTPUT, COM_DATA0_PIN       ; set DATA low
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2w0ReadNextBit
;
; @param r17 data from COM_DATAn_INPUT
; @clobbers r16, r17, r18, r20, r22

com2w0ReadNextBit:
;  ldi r20, 50                           ; wait for up to 250us for clock rise
;  rcall com2w0WaitForClockHighMulti5Us  ; (R20, R22)
;  brcc com2w0ReadNextBit_end
  ; clock is high now, read bit
  ; reset read timer (for leaving skipping mode)
  clr r16
  std Y+NET_IFACE_OFFS_READTIMER, r16
  ; check mode
  ldd r16, Y+COM2W_IFACE_OFFS_MODE
  cpi r16, COM2W_MODE_READING
  brne com2w0ReadNextBit_end
  ; handle received bit
  ldd r16, Y+COM2W_IFACE_OFFS_CURRBYTE
  ldd r18, Y+COM2W_IFACE_OFFS_BITCOUNTER
  andi r17, (1<<COM_DATA0_PIN)
  clc
  breq com2w0ReadNextBit_clockData
  sec
com2w0ReadNextBit_clockData:
  ror r16
  std Y+COM2W_IFACE_OFFS_CURRBYTE, r16
  inc r18                                ; bit counter
  std Y+COM2W_IFACE_OFFS_BITCOUNTER, r18
  cpi r18, 8
  brne com2w0ReadNextBit_end
  ; write byte into buffer
  push xl
    push xh
      rcall com2wByteRecvd               ; (r16, r17, r18, X)
    pop xh
  pop xl
  ; prepare for next byte
  clr r16
  std Y+COM2W_IFACE_OFFS_BITCOUNTER, r16
  std Y+COM2W_IFACE_OFFS_CURRBYTE, r16
com2w0ReadNextBit_end:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2w0WaitForClockHighMulti5Us
;
; Wait for high CLK
;
; @param  R20 multiple of 5us to wait (e.g. "2" for "10" us, max: 64)
; @return CFLAG set if okay (state reached), cleared on error
; @clobbers: r20, r22

com2w0WaitForClockHighMulti5Us:
.if clock == 8000000
  add r20, r20 ; *2
  add r20, r20 ; *4
  add r20, r20 ; *8
.endif
.elif clock == 1000000
; nothing to do
.else
  .error "Unhandled clock speed"
.endif

com2w0WaitForClockHighMulti5Us_loop:                   ; 5 cycles per loop
    sbic COM_CLK0_INPUT, COM_CLK0_PIN                  ; +2 if skipped, +1 if not
      rjmp com2w0WaitForClockHighMulti5Us_stateReached ; +2
    dec r20                                            ; +1
  brne com2w0WaitForClockHighMulti5Us_loop             ; +2
  clc                                                  ; +1
  ret                                                  ; +4
com2w0WaitForClockHighMulti5Us_stateReached:
  sec                                                  ; +1
  ret                                                  ; +4
; @end



; ---------------------------------------------------------------------------
; @routine com2w0WaitForClockLowMulti5Us
;
; Wait for low CLK
;
; @param  R20 multiple of 5us to wait (e.g. "2" for "10" us, max: 64)
; @return CFLAG set if okay (state reached), cleared on error
; @clobbers: r20, r22

com2w0WaitForClockLowMulti5Us:
.if clock == 8000000
  add r20, r20 ; *2
  add r20, r20 ; *4
  add r20, r20 ; *8
.endif
.elif clock == 1000000
; nothing to do
.else
  .error "Unhandled clock speed"
.endif

com2w0WaitForClockLowMulti5Us_loop:                    ; 5 cycles per loop
    sbis COM_CLK0_INPUT, COM_CLK0_PIN                  ; +2 if skipped, +1 if not
      rjmp com2w0WaitForClockLowMulti5Us_stateReached  ; +2
    dec r20                                            ; +1
  brne com2w0WaitForClockLowMulti5Us_loop              ; +2
  clc                                                  ; +1
  ret                                                  ; +4
com2w0WaitForClockLowMulti5Us_stateReached:
  sec                                                  ; +1
  ret                                                  ; +4
; @end



; ---------------------------------------------------------------------------
; @routine com2w0SendMsg
;
; @param X pointer to bytes to send
; @param Y pointer to interface data in SRAM
; @return CFLAG set if message sent, cleared otherwise
; @clobbers R16, R18 (R20, R22, R24, R25, X)

com2w0SendMsg:
  ldi r20, 11                            ; wait for about 55us for clock low
  rcall com2w0WaitForClockLowMulti5Us    ; (R20, R22)
  brcs com2w0SendMsg_busy                ; CLK got low while waiting, so line is busy
  push r15
    in r15, SREG
    cli                                  ; atomic disable irq and set CLK low
    rcall com2w0DisableClkIrq            ; (none)
    rcall com2w0ClkSetLow                ; reserve bus (none)
    out SREG, r15
  pop r15
  adiw xh:xl, NETMSG_OFFS_MSGLEN
  ld r18, X
  sbiw xh:xl, NETMSG_OFFS_MSGLEN
  inc r18                                ; adjust for DESTADDR
  inc r18                                ; adjust for MSGLEN
  inc r18                                ; adjust for CRCBYTE
  rcall com2wWaitTime1                   ; longer wait period (R22)
  rcall com2w0SendBytes                  ; (r16, r17, r18, r22, X)
  rcall com2w0ClkSetHigh                 ; make sure bus is released
  rcall com2w0DataSetHigh

  rcall com2w0EnableClkIrq               ; (none)
  ldi r16, NET_IFACE_OFFS_PACKETSOUT_LOW
  rcall NET_Interface_IncCounter16       ; (R24, R25)
  sec
  ret
com2w0SendMsg_busy:
  ldi r16, NET_IFACE_OFFS_ERR_BUSY_LOW
  rcall NET_Interface_IncCounter16       ; (R24, R25)
  clc
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2w0SendBytes
;
; @param  R18 number of bytes to send
; @param X pointer to bytes to send
; @param Y pointer to interface data in SRAM
; @clobbers: r16, r18, X (r17, r22)

com2w0SendBytes:
com2w0SendBytes_loop:
    rcall com2w0ClkSetLow                     ; (none)
    rcall com2wWaitTime1                      ; longer wait period (R22)
    ld r16, X+
    rcall com2w0SendByte                      ; (R16, R17, R22)
    dec r18
  brne com2w0SendBytes_loop
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2w0SendByte
;
; @param  R16 byte to send
; @clobbers: r16, r17 (r22)

com2w0SendByte:
  ldi r17, 8
com2w0SendByte_loop:
    rcall com2w0ClkSetLow
    rcall com2wWaitTime1                        ; longer wait period (R22)
    lsr r16
    brcs com2w0SendByte_send1
    rcall com2w0DataSetLow
    rjmp com2w0SendByte_sent
com2w0SendByte_send1:
    rcall com2w0DataSetHigh
com2w0SendByte_sent:
    rcall com2wWaitTime2                        ; shorter wait period (R22)
    push r15
      in r15, SREG
      cli                                       ; ensure time period by disabling irqs
      rcall com2w0ClkSetHigh
      rcall com2wWaitTime1                      ; longer wait period (R22)
      out SREG, r15
    pop r15
    dec r17
  brne com2w0SendByte_loop
  ret
; @end



; ---------------------------------------------------------------------------
; @routine COM2W0_Run @global
;
; @return CFLAG set if something done, cleared otherwise
; @clobbers all

COM2W0_Run:
  ldi yl, LOW(com2w0_iface)
  ldi yh, HIGH(com2w0_iface)
  rjmp com2w0RunMode          ; (all but Y)
; @end



; ---------------------------------------------------------------------------
; @routine com2w0RunMode
;
; @clobbers all

com2w0RunMode:
  cpi r16, COM2W_MODE_NUM
  brcs COM2W0_Run_jump
  ldi r16, COM2W_MODE_IDLE              ; unknown mode, set to idle
  rcall com2wSetMode                    ; (R17)
  sec
  ret
COM2W0_Run_jump:
  ldi zl, LOW(com2w0ModeJumpTable)
  ldi zh, HIGH(com2w0ModeJumpTable)
  add zl, r16
  adc zh, r16
  sub zh, r16
  ijmp
com2w0ModeJumpTable:
  rjmp com2w0RunIdle
  rjmp com2w0RunReading
  rjmp com2w0RunSkipping
  rjmp com2w0RunWriting
; @end



; ---------------------------------------------------------------------------
; @routine com2w0RunIdle
;
; @param Y pointer to interface data in SRAM
; @clobbers R16, R17, R22, R24, R25, X

com2w0RunIdle:
;rjmp com2w0RunIdle_end ; DEBUG
  push r15
    in r15, SREG
    cli
    ; look for outbound message
    rcall NET_Interface_PeekNextOutgoingMsgNum ; r16=msgNum
    brcs com2w0RunIdle_haveMsg
    out SREG, r15
  pop r15
  clc
  rjmp com2w0RunIdle_end

com2w0RunIdle_haveMsg:
    mov r24, r16
    ldi r16, COM2W_MODE_WRITING
    rcall com2wSetMode                         ; (R17)
    mov r16, r24
    out SREG, r15
  pop r15

  push r16
    rcall NET_Buffer_Locate                    ; (R17)
    adiw xh:xl, 1
    rcall com2w0SendMsg                        ; (R16, R17, R22, R24, R25, X)
    push r15                           
      in r15, SREG                             ; save SREG (no CLI, we want to save CFLAG only)
      ldi r16, COM2W_MODE_IDLE
      rcall com2wSetMode                       ; (R17)
      out SREG, r15                            ; restore SREG
    pop r15
  pop r16
  brcc com2w0RunIdle_end

  push r15
    in r15, SREG
    cli
    rcall NET_Interface_GetNextOutgoingMsgNum  ; take current msg off the queue
    rcall NET_Buffer_ReleaseByNum              ; (R16, X)
    out SREG, r15
  pop r15
  sec
com2w0RunIdle_end:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2w0RunReading
;
; @param Y pointer to interface data in SRAM
; @clobbers none

com2w0RunReading:
  ; check for timeout (Y+NET_IFACE_OFFS_READTIMER)
  ldd r16, Y+NET_IFACE_OFFS_READTIMER
  cpi r16, COM2W_READING_MAXREADCOUNTER
  brcc com2w0RunReading_goIdle
  ldd r16, Y+COM2W_IFACE_OFFS_MODECOUNTER
  cpi r16, COM2W_READING_MAXMODECOUNTER
  brcc com2w0RunReading_goIdle
  clc
  rjmp com2w0RunReading_end
com2w0RunReading_goIdle:
  ldi r16, NET_IFACE_OFFS_ERR_IO_LOW
  rcall NET_Interface_IncCounter16     ; (R24, R25)
  ldi r16, COM2W_MODE_IDLE
  rcall com2wSetMode                   ; (r17)
  sec
com2w0RunReading_end:
  ret
; @end


; ---------------------------------------------------------------------------
; @routine com2w0RunSkipping
;
; @param Y pointer to interface data in SRAM
; @clobbers r16 (r17)

com2w0RunSkipping:
  ; check for timeout (Y+NET_IFACE_OFFS_READTIMER)
  ldd r16, Y+NET_IFACE_OFFS_READTIMER
  cpi r16, COM2W_SKIPPING_MAXREADCOUNTER
  brcc com2w0RunSkipping_goIdle
  ldd r16, Y+COM2W_IFACE_OFFS_MODECOUNTER
  cpi r16, COM2W_SKIPPING_MAXMODECOUNTER
  brcc com2w0RunSkipping_goIdle
  clc
  rjmp com2w0RunSkipping_end
com2w0RunSkipping_goIdle:
  ldi r16, COM2W_MODE_IDLE
  rcall com2wSetMode                   ; (r17)
  sec
com2w0RunSkipping_end:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2w0RunWriting
;
; @param Y pointer to interface data in SRAM
; @clobbers none

com2w0RunWriting:
  ; TODO: check for timeout
  clc
  ret
; @end



; ---------------------------------------------------------------------------
; @routine COM2W0_ClkChangeIsr @global @isr
;
; @clobbers none

COM2W0_ClkChangeIsr:
  push r15
    in r15, SREG
    push r16
      push r17
        inr r16, COM_CLK0_INPUT                ; read clk state early
        inr r17, COM_DATA0_INPUT               ; read data state early
        rcall COM2W0_HandleClockInterrupt
      pop r17
    pop r16
    out SREG, r15
  pop r15
  reti
; @end



; ---------------------------------------------------------------------------
; @routine COM2W0_HandleClockInterrupt @global
;
; @param r16 data from COM_CLKn_INPUT
; @param r17 data from COM_DATAn_INPUT
; @clobbers none

COM2W0_HandleClockInterrupt:
  push r16
    push r17
      push r18
        push xl
          push xh
            push yl
              push yh
                ldi yl, LOW(com2w0_iface)
                ldi yh, HIGH(com2w0_iface)
                rcall com2w0ActOnClock       ; (r16, r17, r18, X)
              pop yh
            pop yl
          pop xh
        pop xl
      pop r18
    pop r17
  pop r16
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2w0ActOnClock
;
; @param r16 data from COM_CLKn_INPUT
; @param r17 data from COM_DATAn_INPUT
; @clobbers r16 (r17, r18, X)

com2w0ActOnClock:
  andi r16, (1<<COM_CLK0_PIN)
  brne com2w0ActOnClock_clockHigh
  ; clock low
  ldd r18, Y+COM2W_IFACE_OFFS_MODE
  cpi r18, COM2W_MODE_IDLE
  breq com2w0ActOnClock_startReading
  cpi r18, COM2W_MODE_SKIPPING
  breq com2w0ActOnClock_skipping
  rjmp com2w0ActOnClock_end
com2w0ActOnClock_skipping:
;  ldd r18, Y+COM2W_IFACE_OFFS_MODECOUNTER
;  cpi r18, COM2W_SKIPPING_MAXMODECOUNTER
;  brcc com2w0ActOnClock_startReading
  rjmp com2w0ActOnClock_end
com2w0ActOnClock_startReading:
  rcall com2wStartReading           ; (r16, r17, X)
  rjmp com2w0ActOnClock_end
com2w0ActOnClock_clockHigh:
  ldd r18, Y+COM2W_IFACE_OFFS_MODE
  cpi r18, COM2W_MODE_READING
  brne com2w0ActOnClock_end

  push r20
    push r22
      rcall com2w0ReadNextBit            ; (r16, r17, r18, r20, r22)
    pop r22
  pop r20

com2w0ActOnClock_end:
  ret
; @end





#endif ; AVR_MODULES_COM2W_COM2W0_H

