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


; ***************************************************************************
; defines


.equ UART_ERROR_NOTFORME      = 1
.equ UART_ERROR_INVALID       = 2
.equ UART_ERROR_IO            = 3
.equ UART_ERROR_CONTENT       = 4



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

.cseg



UART_Init:
  ; 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 UBRRH, r17
  outr UBRRL, r16

  ; set character format 
.ifdef URSEL
;  ldi r16, (1<<URSEL) | (1<<USBS) | (1<<UCSZ1) | (1<<UCSZ0)
  ldi r16, (1<<URSEL) | (1<<USBS) | (3<<UCSZ0)
.else
  ldi r16, (1<<USBS) | (1<<UCSZ1) | (1<<UCSZ0)
.endif
  outr UCSRC, r16

  ret
; @end



; ---------------------------------------------------------------------------
; @routine UART_SendBytes @global
;
; @param X pointer data to send (in RAM)
; @param r17 number of bytes to send
; @clobbers R16, R17, X

UART_SendBytes:
  tst r17
  breq UART_SendBytes_secRet

  rcall UART_StartTx ; (R16)
  
  ; send bytes
UART_SendBytes_loop1:
      inr r16, UCSRA
      sbrs r16, UDRE
    rjmp UART_SendBytes_loop1
    ld r16, X+
    outr UDR, r16
    dec r17
  brne UART_SendBytes_loop1
  ; wait until all data sent
UART_SendBytes_loop2:
    inr r16, UCSRA
    sbrs r16, TXC
  rjmp UART_SendBytes_loop2
  rcall UART_StopTx
UART_SendBytes_secRet:
  sec
  ret
; @end



; ---------------------------------------------------------------------------
; @routine UART_RecvPacket @global
;
; @param X pointer buffer (in RAM)
; @param r17 maximum number of bytes to receive
; @param r18 network address to listen to
; @clobbers r16, r17, X

UART_RecvPacket:
  cpi r17, 3
  brcs UART_RecvPacket_invalid
  rcall uartRecvByteWithin10ms   ; recv destination address (R16)
  brcc UART_RecvPacket_ioError
  cp r16, r18
  breq UART_RecvPacket_forMe
  cpi r16, 0xff
  breq UART_RecvPacket_forMe
  ldi r16, UART_ERROR_NOTFORME
  rjmp UART_RecvPacket_clcRet
UART_RecvPacket_forMe:
  st X+, r16                       ; dest addr
  dec r17
  rcall uartRecvByteWithin10ms     ; msg len (R16)
  brcc UART_RecvPacket_ioError
  st X+, r16
  dec r17
  inc r16                          ; account for CRC
  cp r17, r16
  brcs UART_RecvPacket_badMsg
  mov r17, r16
UART_RecvPacket_loop:
    rcall uartRecvByteWithin10ms   ; (R16)
    brcc UART_RecvPacket_ioError
    st X+, r16
    dec r17
  brne UART_RecvPacket_loop
  sec
  ret
UART_RecvPacket_invalid:
  ldi r16, UART_ERROR_INVALID
  rjmp UART_RecvPacket_clcRet
UART_RecvPacket_badMsg:
  ldi r16, UART_ERROR_CONTENT
  rjmp UART_RecvPacket_clcRet
UART_RecvPacket_ioError:
  ldi r16, UART_ERROR_IO
UART_RecvPacket_clcRet:
  clc
  ret
; @end



; ---------------------------------------------------------------------------
; @routine uartRecvByteWithin10ms
;
; Wait up to 10ms for incoming byte and read it.
;
; @return CFLAG set if okay (data available), cleared on error
; @return r16 byte received (if CFLAG set)
; @clobbers: none

uartRecvByteWithin10ms:
  push r20
    push r22
      rcall uartWaitForData10ms                   ; (R20, R22)
    pop r22
  pop r20
  brcc uartRecvByteWithin10ms_end
  inr r16, UCSRA                              ; check for errors
  andi r16, (1<<FE) | (1<<DOR)
  brne uartRecvByteWithin10ms_error
  inr r16, UDR                                ; read data byte
  sec
  ret
uartRecvByteWithin10ms_error:
  clc
uartRecvByteWithin10ms_end:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine uartWaitForData10ms
;
; Wait for incoming data for max 10 milliseconds.
;
; @return CFLAG set if okay (data available), cleared on error
; @clobbers: r20, r22

uartWaitForData10ms:
.if clock == 8000000
  ldi r20, 80
.endif
.if clock == 1000000
  ldi r20, 10
.endif
uartWaitForData10ms_loop:
    push r20
      rcall uartWaitForData1000Cycles  ; (r20, r22)
    pop r20
    brcs uartWaitForData10ms_gotit
    dec r20
  brne uartWaitForData10ms_loop
  clc
uartWaitForData10ms_gotit:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine uartWaitForData1000Cycles
;
; Wait for incoming data for max 1000 clock cycles
; (about 1ms at 1MHz, 0.125 at 8MHz)
;
; @return CFLAG set if okay (packet received), cleared on error
; @clobbers: r20, r22

uartWaitForData1000Cycles:
  ldi r20, 140                         ; 1
uartWaitForData_loop:
    inr r22, UCSRA               ; 2
    sbrc r22, RXC                      ; 2/3
      rjmp uartWaitForData_gotit       ; 2
    dec r20                            ; 1
  brne uartWaitForData_loop            ; 1/2  -> 7 per loop, max about 1000
  clc                                  ; 1
  ret                                  ; 4
uartWaitForData_gotit:
  sec                                  ; 1
  ret                                  ; 4
; @end





; ---------------------------------------------------------------------------
; @routine UART_StartRx @global
;
; @clobbers R16

UART_StartRx:
  inr r16, UCSRA                      ; clear errors
  cbr r16, (1<<FE) | (1<<DOR) | (1<<UPE)
  sbr r16, (1<<RXC)
  outr UCSRA, r16

  inr r16, UCSRB
  sbr r16, (1<<RXEN)  ; enable receive
  outr UCSRB, r16

  ret
; @end



; ---------------------------------------------------------------------------
; @routine UART_StopRx @global
;
; @clobbers R16

UART_StopRx:
  inr r16, UCSRB
  cbr r16, (1<<RXCIE | (1<<RXEN))   ; disable RX complete interrupt, disable receive
  outr UCSRB, r16
  ret
; @end



; ---------------------------------------------------------------------------
; @routine UART_StartTx @global
;
; @param Y pointer to interface data in SRAM (see @ref UART_HW_IFACE_OFFS_STATE)
; @clobbers R16

UART_StartTx:
  inr r16, UCSRA
  sbr r16, (1<<TXC)      ; clear TXC interrupt
  outr UCSRA, r16

  inr r16, UCSRB
  sbr r16, (1<<TXEN)     ; enable transceive
  outr UCSRB, r16

  ret
; @end



; ---------------------------------------------------------------------------
; @routine UART_StopTx @global
;
; @param Y pointer to interface data in SRAM (see @ref UART_HW_IFACE_OFFS_STATE)
; @clobbers R16

UART_StopTx:
  inr r16, UCSRB
  cbr r16, (1<<UDRIE) | (1<<TXC) | (1<<TXEN)  ; disable TX UDRE and TXC1 interrupt, enable transceive
  outr UCSRB, r16

  ret
; @end



