.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::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