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


; ***************************************************************************
; Module implementing DISPLAY_INPUT using displays with XPT2046 touch
; controllers.
;
; Implements:
;  - Display_InputGetState
; Defines:
; - DISPLAY_IFLAGS_PRESSED_BIT
; - DISPLAY_IFLAGS_CHGCOORD_BIT
; - DISPLAY_IFLAGS_CHGPRESS_BIT
; Needs:
; - DISPLAY_WIDTH
; - DISPLAY_HEIGHT
; ***************************************************************************




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

.equ DISPLAY_IFLAGS_PRESSED_BIT    = 7
.equ DISPLAY_IFLAGS_CHGCOORD_BIT   = 6
.equ DISPLAY_IFLAGS_CHGPRESS_BIT   = 5



.equ XPT2046_SPIMODE = (0<<SPIHW_MODE_SPEED0_BIT) | \
                       (0<<SPIHW_MODE_SPEED1_BIT) | \
                       (1<<SPIHW_MODE_DOUBLESPEED_BIT) | \
                       (0<<SPIHW_MODE_DATAORDER_BIT) | \
                       (0<<SPIHW_MODE_CPOL_BIT) | \
                       (0<<SPIHW_MODE_CPHA_BIT)


.equ XPT2046_CMD_READ_X  = 0xd1
.equ XPT2046_CMD_READ_Y  = 0x91
.equ XPT2046_CMD_READ_Z1 = 0xb1
.equ XPT2046_CMD_READ_Z2 = 0xc1

.equ XPT2046_AVERAGE     = 2
.equ XPT2046_Z_THRESHOLD = 50
.equ XPT2046_TIMER_VALUE = 3   ; every 300ms

.equ XPT2046_CALIB_XLEFT   = 0x0c0
.equ XPT2046_CALIB_XRIGHT  = 0x780

.equ XPT2046_CALIB_YTOP    = 0x0c0
.equ XPT2046_CALIB_YBOTTOM = 0x7a0


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

.dseg

xpt2046ValueBuffer: .byte (XPT2046_AVERAGE*2)
xpt2046RawX:        .byte 2
xpt2046RawY:        .byte 2
xpt2046CurrentX:    .byte 2
xpt2046CurrentY:    .byte 2
xpt2046CurrentZ:    .byte 2
xpt2046Flags:       .byte 1
xpt2046Timer:       .byte 1

xpt2046LeftX:       .byte 2
xpt2046FactorX:     .byte 2

xpt2046TopY:        .byte 2
xpt2046FactorY:     .byte 2


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

.cseg

; ---------------------------------------------------------------------------
; @routine XPT2046_Init @global

XPT2046_Init:
  clr r16
  sts xpt2046CurrentX, r16
  sts xpt2046CurrentX+1, r16

  sts xpt2046CurrentY, r16
  sts xpt2046CurrentY+1, r16
  
  sts xpt2046CurrentZ, r16
  sts xpt2046CurrentZ+1, r16
  
  sts xpt2046Flags, r16
  
  sts xpt2046Timer, r16

  rcall xpt2046CalcCalibration

  ret
; @end



; ---------------------------------------------------------------------------
; @routine XPT2046_Every100ms @global

XPT2046_Every100ms:
  lds r16, xpt2046Timer
  inc r16
  cpi r16, XPT2046_TIMER_VALUE
  brcs XPT2046_Every100ms_store
  rcall xpt2046UpdateValues
  clr r16
XPT2046_Every100ms_store:
  sts xpt2046Timer, r16
  ret
; @end



; ---------------------------------------------------------------------------
; @routine Display_InputGetState @global
;
; Get current state, clears stored flags DISPLAY_IFLAGS_CHGCOORD_BIT
; and DISPLAY_IFLAGS_CHGPRESS_BIT.
;
; @return r16     flags
; @return r5:r4   X
; @return r7:r6   Y
; @clobber r17

Display_InputGetState:
  lds r16, xpt2046Flags
  mov r17, r16
  cbr r17, (1<<DISPLAY_IFLAGS_CHGCOORD_BIT) | (1<<DISPLAY_IFLAGS_CHGPRESS_BIT)
  sts xpt2046Flags, r17
  lds r4, xpt2046CurrentX
  lds r5, xpt2046CurrentX+1
  lds r6, xpt2046CurrentY
  lds r7, xpt2046CurrentY+1
  ret
; @end



; ---------------------------------------------------------------------------
; @routine xpt2046CalcCalibration
;
xpt2046CalcCalibration:
  ; calibrate X (hardcoded for now)
  ldi r18, LOW(XPT2046_CALIB_XLEFT)
  ldi r19, HIGH(XPT2046_CALIB_XLEFT)
  ldi r20, LOW(XPT2046_CALIB_XRIGHT)
  ldi r21, HIGH(XPT2046_CALIB_XRIGHT)
  ldi r22, LOW(DISPLAY_HEIGHT)
  ldi r23, HIGH(DISPLAY_HEIGHT)
  rcall xpt2046CalcCalibX

  ; calibrate Y (hardcoded for now)
  ldi r18, LOW(XPT2046_CALIB_YTOP)
  ldi r19, HIGH(XPT2046_CALIB_YTOP)
  ldi r20, LOW(XPT2046_CALIB_YBOTTOM)
  ldi r21, HIGH(XPT2046_CALIB_YBOTTOM)
  ldi r22, LOW(DISPLAY_WIDTH)
  ldi r23, HIGH(DISPLAY_WIDTH)
  rcall xpt2046CalcCalibY
  ret
; @end



; ---------------------------------------------------------------------------
; @routine xpt2046CalcCalibX
;
; @param r19:r18: xLeft
; @param r21:r20  xRight
; @param r23:r22  display hardware width

xpt2046CalcCalibX:
  sts xpt2046LeftX, r18
  sts xpt2046LeftX+1, r19
  
  mov r24, r20
  mov r25, r21
  sub r24, r18
  sbc r25, r19
  lsl r24      ; *2
  rol r25
  lsl r24      ; *4
  rol r25
  lsl r24      ; *8
  rol r25
  mov r20, r24
  mov r21, r25
  bigcall Utils_Divu16_16_16     ; R17:R16=result
  sts xpt2046FactorX, r16
  sts xpt2046FactorX+1, r17
  ret
; @end



; ---------------------------------------------------------------------------
; @routine xpt2046CalcCalibY
;
; @param r19:r18: yTop
; @param r21:r20  yBottom
; @param r23:r22  display hardware height

xpt2046CalcCalibY:
  sts xpt2046TopY, r18
  sts xpt2046TopY+1, r19
  
  mov r24, r20
  mov r25, r21
  sub r24, r18
  sbc r25, r19
  lsl r24      ; *2
  rol r25
  lsl r24      ; *4
  rol r25
  lsl r24      ; *8
  rol r25
  mov r20, r24
  mov r21, r25
  bigcall Utils_Divu16_16_16     ; R17:R16=result
  sts xpt2046FactorY, r16
  sts xpt2046FactorY+1, r17
  ret
; @end




; ---------------------------------------------------------------------------
; @routine xpt2046UpdateValues

xpt2046UpdateValues:
  push r15
    in r15, SREG
    cli

    lds r23, xpt2046Flags
    rcall xpt2046BeginSpi          ; (R16, R17)

    rcall xpt2046UpdateZ

    mov r16, r23
    andi r16, (1<<DISPLAY_IFLAGS_PRESSED_BIT) ; only read coords if pressed
    breq xpt2046UpdateValues_storeFlags

    rcall xpt2046UpdateRawX
    rcall xpt2046UpdateRawY

    rcall xpt2046CalcX
    rcall xpt2046CalcY

xpt2046UpdateValues_storeFlags:
    sts xpt2046Flags, r23
    rcall xpt2046EndSpi            ; (R16, R17)

    out SREG, r15
  pop r15
  ret
; @end



; ---------------------------------------------------------------------------
; @routine xpt2046CalcX
;
; @param r23 current xpt2046Flag (modified by routine)
; @clobbers (r16, r17, r18, r19, r20, r21, r22)

xpt2046CalcX:
  push r23
    lds r20, xpt2046RawY
    lds r21, xpt2046RawY+1
    lds r16, xpt2046TopY
    lds r17, xpt2046TopY+1
    sub r20, r16
    sbc r21, r17
    brcc xpt2046CalcX_trans
    clr r20
    clr r21
xpt2046CalcX_trans:
    lsl r20      ; *2
    rol r21
    lsl r20      ; *4
    rol r21
    lsl r20      ; *8
    rol r21
    lds r22, xpt2046FactorY
    lds r23, xpt2046FactorY+1
    bigcall Utils_Divu16_16_16
    lds r18, xpt2046CurrentX
    lds r19, xpt2046CurrentX+1
    sts xpt2046CurrentX, r16
    sts xpt2046CurrentX+1, r17
  pop r23
  sub r16, r18
  sbc r17, r19
  breq xpt2046CalcX_ret
  ori r23, (1<<DISPLAY_IFLAGS_CHGCOORD_BIT)
xpt2046CalcX_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine xpt2046CalcY
;
; @param r23 current xpt2046Flag (modified by routine)
; @clobbers (r16, r17, r18, r19, r20, r21, r22)

xpt2046CalcY:
  push r23
    lds r20, xpt2046RawX
    lds r21, xpt2046RawX+1
    lds r16, xpt2046LeftX
    lds r17, xpt2046LeftX+1
    sub r20, r16
    sbc r21, r17
    brcc xpt2046CalcY_trans
    clr r20
    clr r21
xpt2046CalcY_trans:
    lsl r20      ; *2
    rol r21
    lsl r20      ; *4
    rol r21
    lsl r20      ; *8
    rol r21
    lds r22, xpt2046FactorX
    lds r23, xpt2046FactorX+1
    bigcall Utils_Divu16_16_16
    lds r18, xpt2046CurrentY
    lds r19, xpt2046CurrentY+1
    sts xpt2046CurrentY, r16
    sts xpt2046CurrentY+1, r17
  pop r23
  sub r16, r18
  sbc r17, r19
  breq xpt2046CalcY_ret
  ori r23, (1<<DISPLAY_IFLAGS_CHGCOORD_BIT)
xpt2046CalcY_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine xpt2046UpdateRawX
;
; @clobbers (r16, r17, r18, r19, r20, r21)

xpt2046UpdateRawX:
  ; dummy read
  ldi r16, XPT2046_CMD_READ_X
  rcall xpt2046SendCommandRecv12Bit  ; (R16)
  
  ldi r16, XPT2046_CMD_READ_X
  rcall xpt2046ReadAvgValues     ; (r16, r18, r19, r20, r21)
  rcall xpt2046AverageValues     ; (r16, r17, r21)

  sts xpt2046RawX, r18
  sts xpt2046RawX+1, r19
  ret
; @end



; ---------------------------------------------------------------------------
; @routine xpt2046UpdateRawY
;
; @param r23 current xpt2046Flag (modified by routine)
; @clobbers (r16, r18, r19, r20, r21)

xpt2046UpdateRawY:
  ; read Y
  ldi r16, XPT2046_CMD_READ_Y
  rcall xpt2046ReadAvgValues     ; (r16, r18, r19, r20, r21)
  rcall xpt2046AverageValues     ; (r16, r17, r21)
  sts xpt2046RawY, r18
  sts xpt2046RawY+1, r19
  ret
; @end



; ---------------------------------------------------------------------------
; @routine xpt2046UpdateZ
;
; @param r23 current xpt2046Flag (modified by routine)
; @clobbers (r16, r18, r19, r20, r21)

xpt2046UpdateZ:
  ldi r16, XPT2046_CMD_READ_Z1
  rcall xpt2046SendCommandRecv12Bit  ; (R16)
  sts xpt2046CurrentZ, r18
  sts xpt2046CurrentZ+1, r19
  
  ldi r16, LOW(XPT2046_Z_THRESHOLD)
  ldi r17, HIGH(XPT2046_Z_THRESHOLD)
  sub r18, r16
  sbc r19, r17
  mov r16, r23
  brcc xpt2046UpdateZ_aboveThreshold
  cbr r23, (1<<DISPLAY_IFLAGS_PRESSED_BIT) ; Z below threshold, not pressed
  rjmp xpt2046UpdateZ_checkPressChg
xpt2046UpdateZ_aboveThreshold:
  ori r23, (1<<DISPLAY_IFLAGS_PRESSED_BIT)
xpt2046UpdateZ_checkPressChg:
  eor r16, r23
  andi r16, (1<<DISPLAY_IFLAGS_PRESSED_BIT)
  breq xpt2046UpdateZ_ret
  ori r23, (1<<DISPLAY_IFLAGS_CHGPRESS_BIT)
xpt2046UpdateZ_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine xpt2046AverageValues
;
; @return r19:r18 averaged value
; @clobbers r16, r17, r21

xpt2046AverageValues:
  ldi xl, LOW(xpt2046ValueBuffer)
  ldi xh, HIGH(xpt2046ValueBuffer)
  ldi r21, XPT2046_AVERAGE
  clr r18
  clr r19
xpt2046AverageValues_loop:
    ld r16, X+
    ld r17, X+
    add r18, r16
    adc r19, r17
    dec r21
  brne xpt2046AverageValues_loop

.if (XPT2046_AVERAGE == 2)
  lsr r19
  ror r18
.elif (XPT2046_AVERAGE == 4)
  lsr r19
  ror r18
  lsr r19
  ror r18
.else
  .error "XPT2046_AVERAGE is not 4"
.endif

  ret
; @end



; ---------------------------------------------------------------------------
; @routine xpt2046ReadAvgValues
;
; @param r16 command
; @clobbers r16, r18, r19, r20, r21

xpt2046ReadAvgValues:
  ldi xl, LOW(xpt2046ValueBuffer)
  ldi xh, HIGH(xpt2046ValueBuffer)
  mov r20, r16
  ldi r21, XPT2046_AVERAGE
xpt2046ReadAvgValues_loop:
    mov r16, r20
    rcall xpt2046SendCommandRecv12Bit  ; (R16)
    st X+, r18
    st X+, r19
    dec r21
  brne xpt2046ReadAvgValues_loop
  ret
; @end



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

xpt2046BeginSpi:
  ldi r16, XPT2046_SPIMODE
  ldi r17, XPT2046_DEVICENUM
  rjmp SPIHW_MasterStart                   ; (R17)
; @end



; ---------------------------------------------------------------------------
; @routine xpt2046EndSpi
;
; @clobbers r16

xpt2046EndSpi:
  rjmp SPIHW_MasterStop                    ; (R16)
; @end



; ---------------------------------------------------------------------------
; @routine xpt2046SendCommandRecv12Bit
;
; @param r16 command
; @return r19:18 data
; @clobbers r16

xpt2046SendCommandRecv12Bit:
  rcall xpt2046SendCommandRecv16Bit
  lsr r19  ; >>1
  ror r18
  lsr r19  ; >>2
  ror r18
  lsr r19  ; >>3
  ror r18
  lsr r19  ; >>4
  ror r18
  ret
; @end



; ---------------------------------------------------------------------------
; @routine xpt2046SendCommandRecv16Bit
;
; @param r16 command
; @return r19:18 data
; @clobbers r16

xpt2046SendCommandRecv16Bit:
  cbi SPIHW_SS_OUTPUT, SPIHW_SS_PIN       ; SS low
  rcall SPIHW_MasterTransfer              ; (R16)

  clr r16
  rcall SPIHW_MasterTransfer              ; (R16)
  mov r19, r16
  clr r16
  rcall SPIHW_MasterTransfer              ; (R16)
  mov r18, r16
  sbi SPIHW_SS_OUTPUT, SPIHW_SS_PIN       ; SS high
  ret
; @end



; ---------------------------------------------------------------------------
; @routine xpt2046SendCommandRecv8Bit
;
; @param r16 command
; @return r16 data
; @clobbers r16

xpt2046SendCommandRecv8Bit:
  cbi SPIHW_SS_OUTPUT, SPIHW_SS_PIN       ; SS low
  rcall SPIHW_MasterTransfer              ; (R16)
  mov r18, r16
  sbi SPIHW_SS_OUTPUT, SPIHW_SS_PIN       ; SS high
  ret
; @end







#endif ; AQH_AVR_XPT2046_MAIN_ASM

