6502-OS/system/ps2_keyboard.s65

314 lines
9.3 KiB
Plaintext
Raw Normal View History

2024-01-01 14:56:11 +01:00
.include "ps2_keyboard.h65"
.include "string.h65"
.include "lcd.h65"
.include "parity.h65"
Export ps2kb,init,begin_receive,send_command,scancode,status,scancode_handler
Export ps2kb, _receive_irq_shift_reg_handler, _receive_irq_timer_handler
.bss
status: .res 1
send_last_bits: .res 1
send_data: .res 1
send_cmd: .res 1
key_read: .res 2
scancode: .res 1
scancode_handler: .res 2
.code
;;********************************************************************************
;; @macro Enable the clock signal from the keyboard
;; @modifies: A
;; @details
;; Stop pulling the keyboards clock low
;;********************************************************************************
.macro _EnableClock
; set pin to input
lda ps2kb::VIA + ps2kb::CLK_PULL_DDR
and #<~ps2kb::CLK_PULL_MASK
sta ps2kb::VIA + ps2kb::CLK_PULL_DDR
.endmacro
.macro _DisableTimerIRQ
; set pin to input
lda ps2kb::VIA + ps2kb::CLK_PULL_DDR
and #<~ps2kb::CLK_PULL_MASK
sta ps2kb::VIA + ps2kb::CLK_PULL_DDR
.endmacro
;;********************************************************************************
;; @macro Disable the clock signal from the keyboard
;; @modifies: A
;; @details
;; Pulls the keyboards clock low
;;********************************************************************************
.macro _DisableClock
; set pin to output
lda ps2kb::VIA + ps2kb::CLK_PULL_DDR
ora #ps2kb::CLK_PULL_MASK
sta ps2kb::VIA + ps2kb::CLK_PULL_DDR
; set pin low
lda ps2kb::VIA + ps2kb::CLK_PULL_R
and #<~ps2kb::CLK_PULL_MASK
sta ps2kb::VIA + ps2kb::CLK_PULL_R
.endmacro
.proc init
stz key_read
stz key_read+1
stz scancode
stz status
; T2 oneshot mode, clear shift reg
lda ps2kb::VIA + IO::ACR
and #<~IO::ACR_MASK::SR
ora #IO::ACR::T2_IRQ_LOAD
sta ps2kb::VIA + IO::ACR
; the 3 last bits take about 230us, at @1MHz => wait 230 cycles and then the shift register
; (this could be shorter since the it takes a few cycles after the interrupt)
lda #<ps2kb::TIMER
sta ps2kb::VIA + IO::T2CL
; load this rts as scancode_handler
StoreDByte @rts, scancode_handler
@rts:
rts
.endproc
;;********************************************************************************
;; @function Start receiving data from the keyboard
;; @details
;; - use the shift register interrupts to read the first 8 bits
;; - configure timer for timing the read of the last 3 bits
;; @modifies: A
;;********************************************************************************
.proc begin_receive
; disable timer interrupts (this might be called while waiting on the last 3 bits)
IO_DisableIRQ ps2kb::VIA, IO::IRQ::T2
; set shift register to shift in under external clock on CB1
lda ps2kb::VIA + IO::ACR
and #<~IO::ACR_MASK::SR
ora #IO::ACR::SR_SIN_PHIE
sta ps2kb::VIA + IO::ACR
stz key_read
stz key_read+1
stz scancode
bit status
; set to RECEIVE_KEYS only if RECEIVE_ANSWER is not set
bvs @receive_answer
lda #ps2kb::STATUS::RECEIVE_KEYS
sta status
@receive_answer:
_EnableClock
; todo setup irq handlers
IO_EnableIRQ ps2kb::VIA, IO::IRQ::SR
; reset and start SR
stz ps2kb::VIA + IO::SR
rts
.endproc
;;********************************************************************************
;; @function Read the first 8 bits an
;; @modifies: A
;; @details
;; - read shift register
;; - disable shift register interrupts
;; - reset shift register
;; - enable timer 2 interrupts
;; - start timer 2
;; IO::SR has to be read before the next bit is shifted in, which happens ~75us after the irq
;; at 1MHz, handling this interrupt takes about 50us (without any additional debug code),
;; so it should work
;;********************************************************************************
.proc _receive_irq_shift_reg_handler
lda ps2kb::VIA + IO::SR
sta key_read
stz ps2kb::VIA + IO::SR
IO_DisableIRQ ps2kb::VIA, IO::IRQ::SR
IO_EnableIRQ ps2kb::VIA, IO::IRQ::T2
; start timer, low order count already in latch after init
lda #>ps2kb::TIMER
sta ps2kb::VIA + IO::T2CH
rts
.endproc
;;********************************************************************************
;; @function Read the last 3 bits after after timer 2 is up
;; @modifies: A
;; @details
;; - read shift register
;; - disable timer 2 interrupts
;; - enable shift register interrupts
;; - reset shift register
;; - jump to the subroutine pointed to by scancode_handler
;;********************************************************************************
.proc _receive_irq_timer_handler
lda ps2kb::VIA + IO::SR
sta key_read + 1
lda ps2kb::VIA + IO::T2CL ; clear interrupt flag
IO_DisableIRQ ps2kb::VIA, IO::IRQ::T2
; reset SR
; disabling shifting in acr seems necessary to reset - otherwise
; it continues counting and interrupts after the first 5 bits of the next keypress
lda #(IO::ACR::SR_SIN_PHIE | IO::ACR::T2_IRQ_LOAD)
trb ps2kb::VIA + IO::ACR
tsb ps2kb::VIA + IO::ACR
stz ps2kb::VIA + IO::SR
IO_EnableIRQ ps2kb::VIA, IO::IRQ::SR
lda key_read+1
ror ; stop bit -> C
sta key_read+1 ; parity in bit 0, D7 in bit 1
ror ; parity -> C
ror ; D7 -> C
lda key_read ; not affecting carry
rol ; C -> bit 0, startbit -> C
Reverse A
sta scancode
CalculateOddParity
eor key_read+1 ; if bit 0 is 1 - parity error
and #1 ; bit 1 is still D7
bne @parity_error
; check what to do with the scancode
bit status
bcc @status_not_receive_keys
jmp (scancode_handler)
@status_not_receive_keys:
bvc @status_ignore
jmp _process_cmd_answer
@status_ignore:
rts
@parity_error: ; TODO handle somehow
lda #$ff
sta scancode
DEBUG_LED_ON 2
rts
.endproc
;;********************************************************************************
;; @function Send a command to the keyboard
;; @modifies: A,X,Y
;; @param A: The command byte
;; @param X: The data byte or 0 if only the command byte should be sent
;;********************************************************************************
.proc send_command
sta send_cmd ; store if it needs to be resent
cpx #0
beq @jmp_send_byte
@store_data:
stx send_data
@jmp_send_byte:
jmp _send_byte
.endproc
;;********************************************************************************
;; @function Send a byte to the keyboard
;; @modifies: A,X,Y
;; @param A: The byte to send
;; @details
;; - pull clock low to stop keyboard transmissions
;; - setup shift register to shift out
;; - put startbit + D0-6 in shift register
;; - store D7 + parity + stopbit in memory
;; - TODO setup SR and T2 interrupt handlers
;;********************************************************************************
.proc _send_byte
pha
IO_DisableIRQ ps2kb::VIA, IO::IRQ::T2
_DisableClock
; (re)set shift register to shift out under external clock on CB1
lda ps2kb::VIA + IO::ACR
and #<~IO::ACR_MASK::SR
ora #IO::ACR::SR_SOUT_PHIE
sta ps2kb::VIA + IO::ACR
IO_EnableIRQ ps2kb::VIA, IO::IRQ::SR
pla
pha
; split into the 11 bits
CalculateOddParity
tax
pla
clc ; C = 0 (startbi)
rol ; C -> bit 0, D7 -> C
sta ps2kb::VIA + IO::SR
txa
ror ; C -> bit 0 (D7)
ora #%00000100 ; set stopbit
; A = 000001P7 where 7 = D7, P = Parity
sta send_last_bits ; loaded into SR by irq handler
; stop pulling clk low, which causes the startbit to be shifted out
_EnableClock
rts
.endproc
;;********************************************************************************
;; @function Send the lasts 3 bits
;; @modifies: A
;; @details
;; - load the remaining 3 bits of the transmission into the shift register
;; - disable shift register interrupts
;; - enable timer 2 interrupts
;; - start timer 2
;;********************************************************************************
.proc _send_irq_shift_reg_handler
lda send_last_bits
sta ps2kb::VIA + IO::SR
; disable SR interrupts
lda #IO::IRQ::SR
sta ps2kb::VIA + IO::IER
; enable timer interrupts
lda #(IO::IRQ::IRQ | IO::IRQ::T2)
sta ps2kb::VIA + IO::IER
; start timer, low order count already in latch after init
lda #>ps2kb::TIMER
sta ps2kb::VIA + IO::T2CH
rts
.endproc
;;********************************************************************************
;; @function Send databyte or reveice the keyboards answer
;; @modifies: A, X, Y
;; @details
;; - disable timer 2 interrupts
;; - if send_data is zero:
;; - begin_receive
;; - set status RECEIVE_ANSWER
;; - else
;; - send_byte send_data
;; - set send_data = 0
;;********************************************************************************
.proc _send_irq_timer_handler
lda ps2kb::VIA + IO::T2CL ; clear interrupt flag
IO_DisableIRQ ps2kb::VIA, IO::IRQ::T2
; no SR reset necessary, will be done in begin_receive or send_byte
lda send_data
beq @receive
stz send_data
jmp _send_byte
rts
@receive:
lda #ps2kb::STATUS::RECEIVE_ANSWER
sta status
jmp begin_receive
.endproc
.proc _process_cmd_answer
@success:
rts
.endproc