; ***************************************************************************
; 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_FLASH_IO_COM2W_H
#define AVR_MODULES_FLASH_IO_COM2W_H



.equ COM2W_WAITTIME1 = 25000
.equ COM2W_WAITTIME2 = 15000




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

.cseg



; ---------------------------------------------------------------------------
; @routine ioRawInit
;
; Init raw message subsystem.
;

ioRawInit:
  ; setup CLK line (as input, disable internal pull-up resistor)
  cbi COM_CLK_DDR, COM_CLK_PIN           ; set CLK as input
.ifdef COM_CLK_PUE
  inr r16, COM_CLK_PUE
  cbr r16, (1<<COM_CLK_PIN)               ; disable pullup on CLK
  outr COM_CLK_PUE, r16
.else
  cbi COM_CLK_OUTPUT, COM_CLK_PIN        ; disable pullup on CLK
.endif

  ; setup DATA line (as input, disable internal pull-up resistor)
  cbi COM_DATA_DDR, COM_DATA_PIN         ; set DATA as input
.ifdef COM_DATA_PUE
  inr r16, COM_DATA_PUE
  cbr r16, (1<<COM_DATA_PIN)              ; disable pullup on DATA
  outr COM_DATA_PUE, r16
.else
  cbi COM_DATA_OUTPUT, COM_DATA_PIN      ; disable pullup on DATA
.endif

  ret
; @end



; ---------------------------------------------------------------------------
; @routine ioRawSendMsg
;
; Send a message
;

ioRawSendMsg:
ioRawSendMsg_loop:
    ldi xl, LOW(flashSendBuffer)
    ldi xh, HIGH(flashSendBuffer)
    ldi r19, FLASH_RECVBUFFER_MAXLEN
    rcall com2wSendMsg
  brcc ioRawSendMsg_loop
  ret
; @end



; ---------------------------------------------------------------------------
; @routine ioRawWaitForValidMsg
;
; Wait for valid incoming msg
;
; @return CFLAG set if okay (packet received), cleared on error

ioRawWaitForValidMsg:
  ; wait for CLK low
  ldi r17, 20                            ; wait for up to 10s
ioRawWaitForValidMsg_waitLowLoop:
.ifdef LED_PIN
    sbi LED_PIN, LED_PINNUM              ; toggle
.endif
    ldi r18, 5                           ; wait up to 500ms
    rcall com2wWaitForClockLowMulti100ms ; (r18, r19, r20, r22)
    brcs ioRawWaitForValidMsg_recvMsg
    dec r17
  brne ioRawWaitForValidMsg_waitLowLoop
  clc                                    ; no msg received
  rjmp ioRawWaitForValidMsg_end
ioRawWaitForValidMsg_recvMsg:
  ; receive message
  ldi xl, LOW(flashRecvBuffer)
  ldi xh, HIGH(flashRecvBuffer)
  ldi r18, NET_MAINTENANCE_ADDR
  ldi r19, FLASH_RECVBUFFER_MAXLEN
  rcall com2wRecvMsg
  brcc ioRawWaitForValidMsg_end
  ; validate CRC
  ldi xl, LOW(flashRecvBuffer)
  ldi xh, HIGH(flashRecvBuffer)
  rcall NETMSG_CheckMessageInBuffer      ; (R16, R17, R18, R19, R20, X)
ioRawWaitForValidMsg_end:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2wWaitForClockLowMulti100ms
;
; wait for multiple of 100ms
;
; @param R18 number of 100ms to wait
; @return CFLAG set if okay (state reached), cleared on error
; @clobbers r18, r19, r20, r22

com2wWaitForClockLowMulti100ms:
com2wWaitForClockLowMulti100ms_loop1:
    ldi r19, 100
com2wWaitForClockLowMulti100ms_loop2:
      ldi r20, 100                         ; wait for 1ms for clock low
      rcall com2wWaitForClockLowMulti10Us  ; (R20, R22)
      brcs com2wWaitForClockLowMulti100ms_ret
      dec r19
    brne com2wWaitForClockLowMulti100ms_loop2
    dec r18
  brne com2wWaitForClockLowMulti100ms_loop1
  clc
com2wWaitForClockLowMulti100ms_ret:
  ret
; @end






; ---------------------------------------------------------------------------
; @routine com2wSendMsg
;
; @param X pointer to bytes to send
; @return CFLAG set if message sent, cleared otherwise
; @clobbers R18, R20 (R16, R17, R20, R22, X)

com2wSendMsg:
  ldi r20, 6                            ; wait for about 60us for clock low
  rcall com2wWaitForClockLowMulti10Us
  brcs com2wSendMsg_busy                ; CLK got low while waiting, so line is busy
  rcall com2wClkSetLow                  ; reserve bus (none)
  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 com2wSendBytes                  ; (r16, r17, r18, r22, X)
  rcall com2wClkSetHigh                 ; make sure bus is released
  rcall com2wDataSetHigh
  sec
  ret
com2wSendMsg_busy:
  clc
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2wSendBytes
;
; @param  R18 number of bytes to send
; @param X pointer to bytes to send
; @clobbers: r16, r18, X (r17, r22)

com2wSendBytes:
com2wSendBytes_loop:
    rcall com2wClkSetLow                     ; (none)
    rcall com2wWaitTime1                     ; longer wait period (R22)
    ld r16, X+
    rcall com2wSendByte                      ; (R16, R17, R22)
    dec r18
  brne com2wSendBytes_loop
  ret
; @end



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

com2wSendByte:
  ldi r17, 8
com2wSendByte_loop:
    rcall com2wClkSetLow
    rcall com2wWaitTime2                      ; shorter wait period (R22)
    lsr r16
    brcs com2wSendByte_send1
    rcall com2wDataSetLow
    rjmp com2wSendByte_sent
com2wSendByte_send1:
    rcall com2wDataSetHigh
com2wSendByte_sent:
    Utils_WaitNanoSecs 5000, 0, r22           ; wait for very short time to ensure data is stable when clock rises
    rcall com2wClkSetHigh
    rcall com2wWaitTime2                      ; shorter wait period (R22)
    dec r17
  brne com2wSendByte_loop
  rcall com2wWaitTime2                        ; shorter wait period (R22)
  rcall com2wDataSetHigh
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2wRecvMsg
;
; Receive a packet into buffer pointed to by X.
; Expects interrupts to be disabled.
;
; @param R18 COM address to listen to
; @param R19 max buffer size
; @param X   buffer to receive to
; @return CFLAG set if msg received, cleared on error (see R16)
; @return R16   if CFLAG cleared: 0=message not for this node, otherwise error
; @clobbers r16, r17, r18, r19, r20, r22, r24, r25, X

com2wRecvMsg:
  mov r21, r18 ; address
  ; read destination address
  rcall com2wRecvByte                    ; (r17, r18, r20, r22)
  brcc com2wRecvMsg_eIo
  ; store in buffer
  subi r19, 1
  brcs com2wRecvMsg_eBadSize
  st X+, r16
  
  ; read remaining msg size
  rcall com2wRecvByte                    ; (r17, r18, r20, r22)
  brcc com2wRecvMsg_eIo
  ; store in buffer
  subi r19, 1
  brcs com2wRecvMsg_eBadSize
  st X+, r16
  
  inc r16                                 ; account for CRC byte
  sub r19, r16
  brcs com2wRecvMsg_eBadSize
  mov r19, r16
com2wRecvMsg_loop:
    rcall com2wRecvByte                  ; (r17, r18, r20, r22)
    brcc com2wRecvMsg_eIo
    st X+, r16
    dec r19
  brne com2wRecvMsg_loop
  sec
  rjmp com2wRecvMsg_end
com2wRecvMsg_eBadSize:
com2wRecvMsg_eIo:
  rcall com2wRecvByteWaitForQuietClk      ; (r18, r20, r22)
  clc
com2wRecvMsg_end:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2wRecvByteWaitForQuietClk
;
; Wait until the clock line is consistently high for 1ms.
;
; @clobbers r18, r20, r22

com2wRecvByteWaitForQuietClk:
  ldi r18, 200
com2wRecvByteWaitForQuietClk_loop:
      ldi r20, 100                          ; wait up to 1ms for clock high (R20, R22)
      rcall com2wWaitForClockLowMulti10Us
      brcs com2wRecvByteWaitForQuietClk_waitForLow
      dec r18
    brne com2wRecvByteWaitForQuietClk_loop
    rjmp com2wRecvByteWaitForQuietClk_ret
com2wRecvByteWaitForQuietClk_waitForLow:
    ldi r20, 100                          ; wait up to 1ms for clock low (R20, R22)
    rcall com2wWaitForClockLowMulti10Us
    brcc com2wRecvByteWaitForQuietClk_ret ; not low within 1ms, assume line is quiet
  brne com2wRecvByteWaitForQuietClk_loop
com2wRecvByteWaitForQuietClk_ret:
  ret



; ---------------------------------------------------------------------------
; @routine com2wRecvByte
;
; @return CFLAG set if byte received, cleared on error
; @return r16 byte received
; @clobbers r17, r18, r20, r22

com2wRecvByte:
  ldi r17, 8
  clr r16
com2wRecvByte_loop:
    ldi r20, 70                          ; wait up to 700us for clock low
    rcall com2wWaitForClockLowMulti10Us  ; (R20, R22)
    brcc com2wRecvByte_end
    ldi r20, 70                          ; wait up to 700us for clock high
    rcall com2wWaitForClockHighMulti10Us ; (R20, R22)
    brcc com2wRecvByte_end
    ; handle received bit
    inr r18, COM_DATA_INPUT
    andi r18, (1<<COM_DATA_PIN)
    clc
    breq com2wRecvByte_clockData
    sec
com2wRecvByte_clockData:
    ror r16
    dec r17
  brne com2wRecvByte_loop
  sec
com2wRecvByte_end:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2wClkSetHigh
;
; @clobbers none

com2wClkSetHigh:
  cbi COM_CLK_DDR, COM_CLK_PIN           ; set CLK as input
.ifdef COM_CLK_PUE
;  cbi COM_CLK_PUE, COM_CLK_PIN           ; disable pullup on CLK
.else
  cbi COM_CLK_OUTPUT, COM_CLK_PIN        ; disable pullup on CLK
.endif
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2wClkSetLow
;
; @clobbers none

com2wClkSetLow:
  sbi COM_CLK_DDR, COM_CLK_PIN          ; set CLK as output
  cbi COM_CLK_OUTPUT, COM_CLK_PIN       ; set CLK low
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2wDataSetHigh
;
; @clobbers none

com2wDataSetHigh:
  cbi COM_DATA_DDR, COM_DATA_PIN           ; set DATA as input
.ifdef COM_DATA_PUE
;  cbi COM_DATA_PUE, COM_CLK_PIN           ; disable pullup on DATA
.else
  cbi COM_DATA_OUTPUT, COM_DATA_PIN        ; disable pullup on DATA
.endif
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2wDataSetLow
;
; @clobbers none

com2wDataSetLow:
  sbi COM_DATA_DDR, COM_DATA_PIN          ; set DATA as output
  cbi COM_DATA_OUTPUT, COM_DATA_PIN       ; set DATA low
  ret
; @end



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

com2wWaitForClockLowMulti10Us:
.if clock > 1000000
; begin version for > 1000000 Hz
  ldi r22, clock/1000000
com2wWaitForClockLowMulti10Us_loop0:
    push r20
com2wWaitForClockLowMulti10Us_loop:                   ; 10 cycles per loop
        sbis COM_CLK_INPUT, COM_CLK_PIN                   ; +2 if skipped, +1 if taken
          rjmp com2wWaitForClockLowMulti10Us_stateReached ; (+2 if taken)
        sbis COM_CLK_INPUT, COM_CLK_PIN                   ; +2 if skipped, +1 if taken
          rjmp com2wWaitForClockLowMulti10Us_stateReached ; (+2 if taken)
        sbis COM_CLK_INPUT, COM_CLK_PIN                   ; +2 if skipped, +1 if taken
          rjmp com2wWaitForClockLowMulti10Us_stateReached ; (+2 if taken)
        nop                                             ; +1
        dec r20                                         ; +1
      brne com2wWaitForClockLowMulti10Us_loop           ; +2 if TRUE, +1 if FALSE
    pop r20
    dec r22                                           ; +1
  brne com2wWaitForClockLowMulti10Us_loop0            ; +1 if FALSE, +2 if TRUE
  clc                                                 ; +1
  ret                                                 ; +4
com2wWaitForClockLowMulti10Us_stateReached:
  pop r20
  sec                                                 ; +1
  ret                                                 ; +4
; end version for > 1000000 Hz
.elif clock < 1000000
  .error "Clock speed too low"
.else
; begin version for 1000000 Hz
com2wWaitForClockLowMulti10Us_loop:                   ; 10 cycles per loop
    sbis COM_CLK_INPUT, COM_CLK_PIN                   ; +2 if skipped, +1 if taken
      rjmp com2wWaitForClockLowMulti10Us_stateReached ; (+2 if taken)
    sbis COM_CLK_INPUT, COM_CLK_PIN                   ; +2 if skipped, +1 if taken
      rjmp com2wWaitForClockLowMulti10Us_stateReached ; (+2 if taken)
    sbis COM_CLK_INPUT, COM_CLK_PIN                   ; +2 if skipped, +1 if taken
      rjmp com2wWaitForClockLowMulti10Us_stateReached ; (+2 if taken)
    nop                                               ; +1
    dec r20                                           ; +1
  brne com2wWaitForClockLowMulti10Us_loop             ; +2 if TRUE, +1 if FALSE
  clc                                                 ; +1
  ret                                                 ; +4
com2wWaitForClockLowMulti10Us_stateReached:
  sec                                                 ; +1
  ret                                                 ; +4
; end version for 1000000 Hz
.endif
; @end



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

com2wWaitForClockHighMulti10Us:
.if clock > 1000000
; begin version for > 1000000 Hz
  ldi r22, clock/1000000
com2wWaitForClockHighMulti10Us_loop0:
    push r20
com2wWaitForClockHighMulti10Us_loop:                      ; 10 cycles per loop
        sbic COM_CLK_INPUT, COM_CLK_PIN                   ; +2 if skipped, +1 if taken
          rjmp com2wWaitForClockHighMulti10Us_stateReached ; (+2 if taken)
        sbic COM_CLK_INPUT, COM_CLK_PIN                   ; +2 if skipped, +1 if taken
          rjmp com2wWaitForClockHighMulti10Us_stateReached ; (+2 if taken)
        sbic COM_CLK_INPUT, COM_CLK_PIN                   ; +2 if skipped, +1 if taken
          rjmp com2wWaitForClockHighMulti10Us_stateReached ; (+2 if taken)
        nop                                               ; +1
        dec r20                                           ; +1
      brne com2wWaitForClockHighMulti10Us_loop            ; +2 if TRUE, +1 if FALSE
    pop r20
    dec r22                                               ; +1
  brne com2wWaitForClockHighMulti10Us_loop0               ; +1 if FALSE, +2 if TRUE
  clc                                                     ; +1
  ret                                                     ; +4
com2wWaitForClockHighMulti10Us_stateReached:
  pop r20
  sec                                                 ; +1
  ret                                                 ; +4
; end version for > 1000000 Hz
.elif clock < 1000000
  .error "Clock speed too low"
.else
; begin version for 1000000 Hz
com2wWaitForClockHighMulti10Us_loop:                  ; 10 cycles per loop
    sbic COM_CLK_INPUT, COM_CLK_PIN                   ; +2 if skipped, +1 if taken
      rjmp com2wWaitForClockHighMulti10Us_stateReached ; (+2 if taken)
    sbic COM_CLK_INPUT, COM_CLK_PIN                   ; +2 if skipped, +1 if taken
      rjmp com2wWaitForClockHighMulti10Us_stateReached ; (+2 if taken)
    sbic COM_CLK_INPUT, COM_CLK_PIN                   ; +2 if skipped, +1 if taken
      rjmp com2wWaitForClockHighMulti10Us_stateReached ; (+2 if taken)
    nop                                               ; +1
    dec r20                                           ; +1
  brne com2wWaitForClockHighMulti10Us_loop             ; +2 if TRUE, +1 if FALSE
  clc                                                 ; +1
  ret                                                 ; +4
com2wWaitForClockHighMulti10Us_stateReached:
  sec                                                 ; +1
  ret                                                 ; +4
; end version for 1000000 Hz
.endif
; @end



; ---------------------------------------------------------------------------
; @routine com2wWaitTime1
;
; waits for longer period (e.g. 30ns)
;
; @clobbers R22

com2wWaitTime1:
  Utils_WaitNanoSecs COM2W_WAITTIME1, 7, r22            ; wait for longer time (minus RCALL and RET)
  ret
; @end



; ---------------------------------------------------------------------------
; @routine com2wWaitTime2
;
; waits for shorter period (e.g. 10ns)
;
; @clobbers R22

com2wWaitTime2:
  Utils_WaitNanoSecs COM2W_WAITTIME2, 7, r22            ; wait for shorter time (minus RCALL and RET)
  ret
; @end




#endif ; AVR_MODULES_FLASH_IO_COM2W_H

