; ***************************************************************************
; 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_UART_HW2_COMONUART0_H
#define AVR_MODULES_UART_HW2_COMONUART0_H


.dseg

comOnUart0_iface: .byte UART_HW2_IFACE_SIZE



.cseg



; ---------------------------------------------------------------------------
; @routine ComOnUart0_Init @global
;
; @clobbers R16, R17, Y (X)

ComOnUart0_Init:
  rcall comOnUart0StopRx
  rcall comOnUart0StopTx
  
  ldi yl, LOW(comOnUart0_iface)
  ldi yh, HIGH(comOnUart0_iface)
  rcall NET_Interface_Init                  ; (R16, R17, X)
  ldi r16, UART_HW2_MODE_IDLE
  std Y+UART_HW2_IFACE_OFFS_MODE, r16
  clr r16
  std Y+NET_IFACE_OFFS_IFACENUM, r16
  rcall comOnUart0SetAttnInput

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

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

  ; set baudrate
.if clock == 8000000
  ldi r16, 25 ; (19.2Kb/s at 8MHz)
  ldi r17, 0
.endif

.if clock == 1000000
  ldi r16, 2  ; (19.2Kb/s at 1MHz)
  ldi r17, 0  
.endif

  outr UBRR0H, r17
  outr UBRR0L, r16

  ; set character format 
  ldi r16, (1<<USBS0)|(3<<UCSZ00)
  outr UCSR0C, r16
  sec
  ret
; @end



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

ComOnUart0_Periodically:
  push r15
    in r15, SREG
    cli
    ldi yl, LOW(comOnUart0_iface)
    ldi yh, HIGH(comOnUart0_iface)
    rcall NET_Interface_Periodically
    ldd r16, Y+UART_HW2_IFACE_OFFS_MODECOUNTER
    inc r16
    breq ComOnUart0_Periodically_end
    std Y+UART_HW2_IFACE_OFFS_MODECOUNTER, r16
ComOnUart0_Periodically_end:
    out SREG, r15
  pop r15
  ret
; @end



; ---------------------------------------------------------------------------
; @routine comOnUart0SetMode
;
; @param R16 mode
; @clobbers R17

comOnUart0SetMode:
  push r15
    in r15, SREG
    cli
    ldd r17, Y+UART_HW2_IFACE_OFFS_MODE
    cp r16, r17
    breq comOnUart0SetMode_end
    std Y+UART_HW2_IFACE_OFFS_MODE, r16
    clr r17
    std Y+UART_HW2_IFACE_OFFS_MODECOUNTER, r17
comOnUart0SetMode_end:
    out SREG, r15
  pop r15
  ret
; @end



; ---------------------------------------------------------------------------
; @routine comOnUart0StartReading
;
; @param Y pointer to interface data in SRAM
; @clobbers R16, R17, X

comOnUart0StartReading:
  mov xl, yl
  mov xh, yh
  adiw xh:xl, UART_HW2_IFACE_OFFS_BUFFER
  std Y+UART_HW2_IFACE_OFFS_BUFPOS_LOW, xl
  std Y+UART_HW2_IFACE_OFFS_BUFPOS_HIGH, xh
  ldi r16, UART_HW2_BUFFER_SIZE
  std Y+UART_HW2_IFACE_OFFS_BUFLEFT, r16
  clr r16
  std Y+UART_HW2_IFACE_OFFS_BUFUSED, r16
  ldi r16, UART_HW2_MODE_READING
  rcall comOnUart0SetMode                     ; (R17)
  rcall comOnUart0StartRx                     ; should be the last call here
  ret
; @end



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

ComOnUart0_Run:
  ldi yl, LOW(comOnUart0_iface)
  ldi yh, HIGH(comOnUart0_iface)
  ldd r16, Y+UART_HW2_IFACE_OFFS_MODE
  clr r18
ComOnUart0_Run_loop:
    push r18
      push r16                   ; current state
        rcall comOnUart0RunMode  ; (all but Y)
      pop r17                    ; previous state (pop from r16 into r17)
    pop r18
    ; read new state
    ldd r16, Y+UART_HW2_IFACE_OFFS_MODE
    cp r16, r17
    breq ComOnUart0_Run_loopEnd
    ldi r18, 1
  rjmp ComOnUart0_Run_loop  ; state changed, run again
ComOnUart0_Run_loopEnd:
  tst r18
  clc
  breq ComOnUart0_Run_end
  sec
ComOnUart0_Run_end:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine comOnUart0RunMode
;
; @clobbers all

comOnUart0RunMode:
  cpi r16, UART_HW2_MODE_NUM
  brcs ComOnUart0_Run_jump
  ldi r16, UART_HW2_MODE_IDLE              ; unknown mode, set to idle
  rcall comOnUart0SetMode                  ; (R17)
  ret
ComOnUart0_Run_jump:
  ldi zl, LOW(comOnUart0ModeJumpTable)
  ldi zh, HIGH(comOnUart0ModeJumpTable)
  add zl, r16
  adc zh, r16
  sub zh, r16
  ijmp
comOnUart0ModeJumpTable:
  rjmp comOnUart0RunIdle
  rjmp comOnUart0RunReading
  rjmp comOnUart0RunSkipping
  rjmp comOnUart0RunWriting
; @end



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

comOnUart0RunIdle:
  push r15
    inr r15, SREG
    cli
    ; look for outbound message
    rcall NET_Interface_PeekNextOutgoingMsgNum ; r16=msgNum
    brcs comOnUart0RunIdle_haveMsg
    out SREG, r15
  pop r15
  rjmp comOnUart0RunIdle_end

comOnUart0RunIdle_haveMsg:
    mov r24, r16
    ldi r16, UART_HW2_MODE_WRITING
    rcall comOnUart0SetMode                    ; (R17)
    mov r16, r24
    out SREG, r15
  pop r15

  push r16
    rcall NET_Buffer_Locate                    ; (R17)
    adiw xh:xl, 1
    rcall comOnUart0SendMsg                    ; (R16, R17, R22, R24, R25, X)
    push r15
      inr r15, SREG                            ; save SREG (no CLI, we want to save CFLAG only)
      ldi r16, UART_HW2_MODE_IDLE
      rcall comOnUart0SetMode                  ; (R17)
      out SREG, r15                            ; restore SREG
    pop r15
  pop r16
  brcc comOnUart0RunIdle_end

  push r15
    inr 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
comOnUart0RunIdle_end:
  ret
; @end



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

comOnUart0RunReading:
  ; TODO: check for timeout
  ret
; @end


; ---------------------------------------------------------------------------
; @routine comOnUart0RunSkipping
;
; @param Y pointer to interface data in SRAM
; @clobbers R16 (R17)

comOnUart0RunSkipping:
  rcall comOnUart0IsAttnLow            ; (none)
  brcs comOnUart0RunSkipping_end       ; still low
  ldi r16, UART_HW2_MODE_IDLE          ; is high now, leave skipping mode
  rcall comOnUart0SetMode              ; (R17)
comOnUart0RunSkipping_end:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine comOnUart0HandleMsgReceived
;
; @param Y pointer to interface data in SRAM
; @clobbers R16, R17, R18, X, Z (R19, R20, R24, R25)

comOnUart0HandleMsgReceived:
  mov xl, yl
  mov xh, yh
  adiw xh:xl, UART_HW2_IFACE_OFFS_BUFFER
  mov zl, xl                                    ; Z=buffer in IFACE
  mov zh, xh
  rcall NETMSG_CheckMessageInBuffer             ; (R16, R17, R18, R19, R20, X)
  brcc comOnUart0RunMsgReceived_econtent
  ; msg valid, alloc buffer
  rcall NET_Buffer_Alloc                        ; X=buffer, R16=bufnum (R16, R17, X)
  brcc comOnUart0RunMsgReceived_enobuf
  mov r18, r16                                  ; buffer num
  rcall NET_Interface_SetIfaceNumInBuffer       ; (R16, R17)
  adiw xh:xl, 1                                 ; skip buffer header
  ldd r17, Y+UART_HW2_IFACE_OFFS_BUFUSED        ; always is at least 2 here
comOnUart0RunMsgReceived_copyLoop:
    ld r16, Z+
    st X+, r16
    dec r17
  brne comOnUart0RunMsgReceived_copyLoop
  mov r16, r18                                  ; buffer num
  rcall NET_AddIncomingMsgNum                   ; (R17, R18, X)
  brcc comOnUart0RunMsgReceived_enoadd
  ldi r16, NET_IFACE_OFFS_PACKETSIN_LOW
  rcall NET_Interface_IncCounter16              ; (R24, R25)
  rjmp comOnUart0RunMsgReceived_setIdleAndEnd
comOnUart0RunMsgReceived_enoadd:
  rcall NET_Buffer_ReleaseByNum
  rjmp comOnUart0RunMsgReceived_enobuf
comOnUart0RunMsgReceived_enobuf:
  ldi r16, NET_IFACE_OFFS_ERR_NOBUF_LOW
  rjmp comOnUart0RunMsgReceived_err
comOnUart0RunMsgReceived_econtent:
  ldi r16, NET_IFACE_OFFS_ERR_CONTENT_LOW
comOnUart0RunMsgReceived_err:
  rcall NET_Interface_IncCounter16              ; (R24, R25)
comOnUart0RunMsgReceived_setIdleAndEnd:
  ldi r16, UART_HW2_MODE_IDLE
  rcall comOnUart0SetMode                       ; (R17)
comOnUart0RunMsgReceived_end:
  ret
; @end



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

comOnUart0RunWriting:
  ; TODO: check for timeout
  ret
; @end



; ---------------------------------------------------------------------------
; @routine comOnUart0AcquireAttn
;
; Set ATTN low only if it is high.
; @return CFLAG set if ATTN acquired (including waiting time), cleared otherwise
;
; @clobbers R22

comOnUart0AcquireAttn:
  rcall comOnUart0IsAttnLow                  ; (none)
  brcc comOnUart0AcquireAttn_isHigh
  clc
  rjmp comOnUart0AcquireAttn_end
comOnUart0AcquireAttn_isHigh:
  ; set ATTN low
  rcall comOnUart0SetAttnLow                 ; (none)
  Utils_WaitNanoSecs COM_BIT_LENGTH, 0, r22  ; wait for one bit duration
  Utils_WaitNanoSecs COM_BIT_LENGTH, 0, r22  ; wait for one bit duration
  sec
comOnUart0AcquireAttn_end:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine comOnUart0StartRx
;
; @clobbers R16

comOnUart0StartRx:
  inr r16, UCSR0B
  sbr r16, (1<<RXCIE0) | (1<<RXEN0)  ; enable RX complete interrupt, enable receive
  outr UCSR0B, r16
  ret
; @end



; ---------------------------------------------------------------------------
; @routine comOnUart0StopRx
;
; @clobbers R16

comOnUart0StopRx:
  inr r16, UCSR0B
  cbr r16, (1<<RXCIE0 | (1<<RXEN0))   ; disable RX complete interrupt, disable receive
  outr UCSR0B, r16
  ret
; @end



; ---------------------------------------------------------------------------
; @routine comOnUart0StartTx
;
; @clobbers R16

comOnUart0StartTx:
  inr r16, UCSR0A
  cbr r16, (1<<TXC0)                  ; clear TXCn interrupt
  outr UCSR0A, r16
  inr r16, UCSR0B
  cbr r16, (1<<UDRIE0) | (1<<TXCIE0)  ; disable TX UDRE and TXC0 interrupt, enable transceive
  sbr r16, (1<<TXEN0)                 ; enable TX UDRE and TXC0 interrupt, enable transceive
  outr UCSR0B, r16
  ret
; @end



; ---------------------------------------------------------------------------
; @routine comOnUart0StopTx
;
; @clobbers R16

comOnUart0StopTx:
  inr r16, UCSR0B
  cbr r16, (1<<UDRIE0) | (1<<TXCIE0) | (1<<TXEN0)  ; disable TX UDRE and TXC0 interrupt, disable transceive
  outr UCSR0B, r16
  ret
; @end



; ---------------------------------------------------------------------------
; @routine comOnUart0SetAttnInput
;
; Set ATTN line as input (making it effectively HIGH due to pull-up resistor on
; the line)
;
; @clobbers none

comOnUart0SetAttnInput:
  cbi COM_ATTN0_DDR, COM_ATTN0_PIN           ; set ATTN as input
.ifdef COM_ATTN0_PUE
;  cbi COM_ATTN0_PUE, COM_ATTN_PIN           ; disable pullup on ATTN
.else
  cbi COM_ATTN0_OUTPUT, COM_ATTN0_PIN        ; disable pullup on ATTN
.endif

  push r16
    inr r16, COM_IRQ_ADDR_ATTN0
    sbr r16, (1<<COM_IRQ_BIT_ATTN0)          ; enable pin change irq for ATTN line
    outr COM_IRQ_ADDR_ATTN0, r16
  pop r16

  ret
; @end



; ---------------------------------------------------------------------------
; @routine comOnUart0SetAttnLow
;
; Set ATTN line low.
;
; @clobbers none

comOnUart0SetAttnLow:
  push r16
    inr r16, COM_IRQ_ADDR_ATTN0
    cbr r16, (1<<COM_IRQ_BIT_ATTN0)           ; disable pin change irq for ATTN line
    outr COM_IRQ_ADDR_ATTN0, r16
  pop r16
  sbi COM_ATTN0_DDR, COM_ATTN0_PIN          ; set ATTN as output
  cbi COM_ATTN0_OUTPUT, COM_ATTN0_PIN       ; set ATTN low
  ret
; @end


; ---------------------------------------------------------------------------
; @routine comOnUart0IsAttnLow
;
; Check whether ATTN is low
;
; @return CFLAG set if ATTN1 is low
; @clobbers none

comOnUart0IsAttnLow:
  clc
  sbis COM_ATTN0_INPUT, COM_ATTN0_PIN        ; ATTN high?
    sec                                      ; no
  ret
; @end


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

ComOnUart0_RxCharIsr:
  push r15
    in r15, SREG
    push r16
      push r17
        push r18
          push r19
            push r20
              push r24
                push r25
                  push xl
                    push xh
                      push yl
                        push yh
                          push zl
                            push zh
                              ldi yl, LOW(comOnUart0_iface)
                              ldi yh, HIGH(comOnUart0_iface)
                              rcall comOnUart0RxCharIsr ; (R16, R17, R18, R19, R20, R24, R25, X, Z)
                            pop zh
                          pop zl
                        pop yh
                      pop yl
                    pop xh
                  pop xl
                pop r25
              pop r24
            pop r20
          pop r19
        pop r18
      pop r17
    pop r16
    out SREG, r15
  pop r15
  reti
; @end



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


ComOnUart0_AttnChangeIsr:
  push r15
    in r15, SREG
    rcall ComOnUart0_HandleAttnChange
    out SREG, r15
  pop r15
  reti
; @end



; ---------------------------------------------------------------------------
; @routine ComOnUart0_HandleAttnChange @global
;
; @clobbers none

ComOnUart0_HandleAttnChange:
  push r16
    push r17
      push xl
        push xh
          push yl
            push yh
              ldi yl, LOW(comOnUart0_iface)
              ldi yh, HIGH(comOnUart0_iface)
              rcall comOnUart0ActOnAttn      ; (R16, R17, X)
            pop yh
          pop yl
        pop xh
      pop xl
    pop r17
  pop r16
  ret
; @end



; ---------------------------------------------------------------------------
; @routine comOnUart0ActOnAttn @global
;
; @param Y pointer to interface data in SRAM
; @clobbers R16 (R17, X)

comOnUart0ActOnAttn:
  ldd r16, Y+UART_HW2_IFACE_OFFS_MODE
  rcall comOnUart0IsAttnLow              ; (none)
  brcc comOnUart0ActOnAttn_attnHigh
  ; ATTN low
  cpi r16, UART_HW2_MODE_IDLE
  brne comOnUart0ActOnAttn_end           ; not idle
  rcall comOnUart0StartReading           ; (R16, R17, X)
  rjmp comOnUart0ActOnAttn_end
  ; ATTN high
comOnUart0ActOnAttn_attnHigh:
  cpi r16, UART_HW2_MODE_SKIPPING        
  brne comOnUart0ActOnAttn_end
  ldi r16, UART_HW2_MODE_IDLE            ; leave skipping mode
  rcall comOnUart0SetMode                ; (R17)
comOnUart0ActOnAttn_end:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine comOnUart0RxCharIsr @global
;
; @param Y pointer to interface data in SRAM
; @clobbers R16, R17, R18, R19, R20, R24, R25, X, Z

comOnUart0RxCharIsr:
  ; check for errors
  inr r16, UCSR0A                              ; check for errors
  andi r16, (1<<FE0) | (1<<DOR0) | (1<<UPE0)
  brne comOnUart0RxCharIsr_hwerr

  inr r16, UCSR0A                              ; read status: data available?
  sbrs r16, RXC0
    rjmp comOnUart0RxCharIsr_end               ; jmp if no data
  inr r16, UDR0                                ; r16=received char
  ; check mode
  ldd r17, Y+UART_HW2_IFACE_OFFS_MODE
  cpi r17, UART_HW2_MODE_READING
  brne comOnUart0RxCharIsr_overrun
  ; store char
  ldd xl, Y+UART_HW2_IFACE_OFFS_BUFPOS_LOW
  ldd xh, Y+UART_HW2_IFACE_OFFS_BUFPOS_HIGH
  st X+, r16                                    ; last byte written
  std Y+UART_HW2_IFACE_OFFS_BUFPOS_LOW, xl
  std Y+UART_HW2_IFACE_OFFS_BUFPOS_HIGH, xh
  ; handle counters
  ldd r18, Y+UART_HW2_IFACE_OFFS_BUFUSED
  inc r18
  std Y+UART_HW2_IFACE_OFFS_BUFUSED, r18
  ldd r17, Y+UART_HW2_IFACE_OFFS_BUFLEFT
  dec r17
  std Y+UART_HW2_IFACE_OFFS_BUFLEFT, r17
  breq comOnUart0RxCharIsr_complete
  ; check msg size
  cpi r18, 2
  brne comOnUart0RxCharIsr_end
  ; determine msg size
  inc r16                                       ; last byte was payload length, add byte for crc
;  cp r16, r17                                   ; compare remaining length against remaining space
;  brcc comOnUart0RxCharIsr_emsgsize             ; msg too long
  cp r17, r16                                   ; compare remaining length against remaining space
  brcs comOnUart0RxCharIsr_emsgsize             ; msg too long

  std Y+UART_HW2_IFACE_OFFS_BUFLEFT, r16        ; set new number of bytes left
  tst r16
  brne comOnUart0RxCharIsr_end                  ; jmp if still bytes left to receive
comOnUart0RxCharIsr_complete:
  rcall comOnUart0StopRx
  rcall comOnUart0HandleMsgReceived             ; (R16, R17, R18, R19, R20, R24, R25, X, Z)
  
  rjmp comOnUart0RxCharIsr_end
comOnUart0RxCharIsr_hwerr:
  ldi r16, NET_IFACE_OFFS_ERR_IO_LOW
  rjmp comOnUart0RxCharIsr_err
comOnUart0RxCharIsr_overrun:
  ldi r16, NET_IFACE_OFFS_ERR_NOBUF_LOW
  rjmp comOnUart0RxCharIsr_err
comOnUart0RxCharIsr_emsgsize:
  ldi r16, NET_IFACE_OFFS_ERR_MSGSIZE_LOW
comOnUart0RxCharIsr_err:
  rcall NET_Interface_IncCounter16              ; (R24, R25)
  rcall comOnUart0StopRx                        ; (R16)
  ldi r16, UART_HW2_MODE_SKIPPING               ; skipping mode is left after ATTN becomes high again
  rcall comOnUart0SetMode                       ; (R17)
comOnUart0RxCharIsr_end:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine comOnUart0SendMsg
;
; @param Y pointer to interface data in SRAM
; @param X pointer to buffer to send
; @return CFLAG set if writing started, cleared otherwise
; @clobbers R16, R17, X (R22, R24, R25)

comOnUart0SendMsg:
  rcall comOnUart0AcquireAttn                ; (R22)
  brcc comOnUart0SendMsg_ebusy
  adiw xh:xl, NETMSG_OFFS_MSGLEN
  ld r17, X
  sbiw xh:xl, NETMSG_OFFS_MSGLEN
  subi r17, -3                               ; add dest addr, msglen, crc
  rcall comOnUart0StartTx                    ; (R16)
  ; TODO: check size!
  ; r17=number of bytes to write, X=buffer
comOnUart0SendMsg_loop:
    ; wait until transceiver ready
      inr r16, UCSR0A
      sbrs r16, UDRE0
    rjmp comOnUart0SendMsg_loop
    ; clear TXC flag by sending a 1
    sbr r16, (1<<TXC0)
    outr UCSR0A, r16
    ; write byte to uart data register
    ld r16, X+
    outr UDR0, r16
    dec r17
  brne comOnUart0SendMsg_loop
  ; wait until all data send (i.e. send buffer empty and all bits shifted out)
comOnUart0SendMsg_loopComplete:
    inr r16, UCSR0A
    sbrs r16, TXC0
  rjmp comOnUart0SendMsg_loopComplete
  rcall comOnUart0StopTx                     ; (R16)
  ldi r16, NET_IFACE_OFFS_PACKETSOUT_LOW
  rcall NET_Interface_IncCounter16           ; (R24, R25)
  rcall comOnUart0SetAttnInput               ; release ATTN (none)
  rcall Utils_WaitFor10MicroSecs             ; make sure ATTN is at least high for a short period (R22)
  sec
  rjmp comOnUart0SendMsg_end
comOnUart0SendMsg_ebusy:
  ldi r16, NET_IFACE_OFFS_ERR_BUSY_LOW
  rcall NET_Interface_IncCounter16           ; (R24, R25)
  clc
comOnUart0SendMsg_end:
  ret
; @end



#endif ; AVR_MODULES_UART_HW2_COMONUART0_H
