diff --git a/system/ps2_keyboard.s65 b/system/ps2_keyboard.s65 index a54eab6..880357f 100644 --- a/system/ps2_keyboard.s65 +++ b/system/ps2_keyboard.s65 @@ -2,22 +2,26 @@ .include "string.h65" .include "lcd.h65" .include "parity.h65" -Export ps2kb,init,begin_receive,send_command,scancode,status,scancode_handler +Export ps2kb, init, begin_receive, scancode, status, scancode_handler Export ps2kb, _receive_irq_shift_reg_handler, _receive_irq_timer_handler, _send_byte, _send_irq_shift_reg_handler, _send_irq_timer_handler, -Export ps2kb, send_last_bits, send_cmd, send_data, key_read, prev_status +Export ps2kb, send_command, send_cmd, send_data, cmd_response, response_length, FMT_CMD_FAIL .bss -status: .res 1 -prev_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 +status: .res 1 ; current status +prev_status: .res 1 ; status before sending command +send_last_bits: .res 1 ; last bits to load after 8 bits were shifted out +send_cmd: .res 1 ; command to send/last sent +send_data: .res 1 ; data byte to send/last sent or NO_DATA +expect_data_length: .res 1 ; number of data bytes to expect from keyboard +response_length: .res 1 ; number of response bytes from keyboard +cmd_response: .res 3 ; responses from keyboard +key_read: .res 2 ; first 8 bits, last 3 bits from scancode +scancode: .res 1 ; last received scancode +scancode_handler: .res 2 ; pointer to a function that handles new scancodes .code +; these are macros the save time during the interrupt handler (no jsr, rts) ;;******************************************************************************** ;; @macro Enable the clock signal from the keyboard ;; @modifies: A @@ -113,13 +117,14 @@ scancode_handler: .res 2 stz key_read stz key_read+1 stz scancode - bit status ; 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_RECV sta ps2kb::VIA + IO::T2CH rts @@ -201,41 +206,68 @@ scancode_handler: .res 2 sta scancode ; check parity - ; CalculateOddParity - ; eor key_read+1 ; if bit 0 is 1 - parity error - ; and #1 ; bit 1 is still D7 - ; bne @parity_error + CalculateOddParity + eor key_read+1 ; if bit 0 is 1 - parity error + and #1 ; bit 1 is still D7 + bne @parity_error +@handle_scancode: ; check what to do with the scancode bit status - bcc @status_not_receive_keys + bpl @status_not_receive_keys ; RECEIVE_KEYS jmp (scancode_handler) @status_not_receive_keys: - bvc @status_ignore + bvc @status_dont_handle ; RECEIVE_ANSWER jmp _process_cmd_answer -@status_ignore: +@status_dont_handle: rts -@parity_error: ; TODO handle somehow - lda #$ff +@parity_error: ; TODO handle somehow + lda #$fe ; fe means error/resend for commands sta scancode DEBUG_LED_ON 2 - rts + bra @handle_scancode .endproc ;;******************************************************************************** ;; @function Send a command to the keyboard +;; @details +;; No checks are done on the validity of the command and data byte. +;; You can send anything you want to the keyboard. +;; `send_command` will return immediately, as sending the command is asynchronous. +;; The command is done if `ps2kb::status & ps2kb::STATUS::SEND == 0`. +;; @section Response +;; The answers will be in the byte array ps2kb::cmd_response. +;; If the command did not have a data byte, cmd_response is: +;; - 0: command response +;; - 1: NO_RESPONSE or additional response byte (identify keyboard command only) +;; - 2: NO_RESPONSE or additional response byte (identify keyboard command only) + +;; If the command did have a data byte, cmd_response is: +;; - 0: 0xFA (ACK) or 0xFE (Resend/Error) +;; - 1: 0xFA (ACK) or 0xFE (Resend/Error) +;; - 2: NO_RESPONSE or additional response byte +;; +;; If ANY of the bytes in cmd_response is 0xFE, the command failed. +;; ;; @modifies: A,X,Y ;; @param A: The command byte ;; @param X: The data byte or NO_DATA if only the command byte should be sent +;; @param Y: The number of data bytes expected to receive. Must be one of {0, 1, 2} ;;******************************************************************************** .proc send_command + sty expect_data_length + stx send_data + stz response_length + ldy #ps2kb::NO_RESPONSE + sty cmd_response + sty cmd_response+1 + sty cmd_response+2 ldy status sty prev_status ldy #ps2kb::STATUS::SEND_CMD sty status sta send_cmd ; store if it needs to be resent - stx send_data jmp _send_byte .endproc @@ -278,8 +310,9 @@ scancode_handler: .res 2 lda # C lda #$ff ror ; Parity -> bit 7, rest = 1 - sta ps2kb::send_last_bits ; loaded into SR by irq handler + sta send_last_bits ; loaded into SR by irq handler pla sta ps2kb::VIA + IO::SR IO_EnableIRQ ps2kb::VIA, IO::IRQ::SR @@ -306,7 +339,7 @@ scancode_handler: .res 2 ;; - start timer 2 ;;******************************************************************************** .proc _send_irq_shift_reg_handler - lda ps2kb::send_last_bits + lda send_last_bits sta ps2kb::VIA + IO::SR IO_DisableIRQ ps2kb::VIA, IO::IRQ::SR @@ -319,34 +352,120 @@ scancode_handler: .res 2 ;;******************************************************************************** -;; @function Send databyte or reveice the keyboards answer -;; @modifies: A, X, Y +;; @function Setup VIA to receive the keyboard's answer +;; @modifies: A ;; @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 +;; - pull clock low +;; - or status with RECEIVE_ANSWER +;; - begin receive ;;******************************************************************************** .proc _send_irq_timer_handler - lda ps2kb::VIA + IO::T2CL ; clear interrupt flag + lda ps2kb::VIA + IO::T2CL ; clear interrupt flag IO_DisableIRQ ps2kb::VIA, IO::IRQ::T2 - lda ps2kb::send_data - beq @receive - stz ps2kb::send_data - jmp _send_byte - rts -@receive: - _DisableClock ; disable keyboard while setting up receive - lda #ps2kb::STATUS::RECEIVE_ANSWER - sta ps2kb::status + ; disable shift register + lda ps2kb::VIA + IO::ACR + and #<~IO::ACR_MASK::SR + sta ps2kb::VIA + IO::ACR + + _DisableClock ; disable keyboard while setting up receive + lda status + ora #ps2kb::STATUS::RECEIVE_ANSWER + sta status jmp ps2kb::begin_receive .endproc -.proc _process_cmd_answer - jmp ($3200) +;;******************************************************************************** +;; @function Process the response of a command +;; @details +;; Stores the answer in the `ps2kb::cmd_response` array. +;; If the response is $FE or command related transmissions are done (see below), +;; no further data will be sent or received and the `ps2kb::status` takes the previous value. +;; +;; - store response +;; - if response == 0xFE -> stop +;; - if status == SEND_CMD -> send databyte +;; - if statusk +;; You can send anything you want to the keyboard. +;; `send_command` will return immediately, as sending the command is asynchronous. +;; The command is done if `ps2kb::status & ps2kb::STATUS::SEND == 0`. +;; @section Response +;; The answers will be in the byte array ps2kb::cmd_response. +;; If the command did not have a data byte, cmd_response is: +;; - 0: command response +;; - 1: NO_RESPONSE or additional response byte (identify keyboard command only) +;; - 2: NO_RESPONSE or additional response byte (identify keyboard command only) + +;; If the command did have a data byte, cmd_response is: +;; - 0: 0xFA (ACK) or 0xFE (Resend/Error) +;; - 1: 0xFA (ACK) or 0xFE (Resend/Error) +;; - 2: NO_RESPONSE or additional response byte +;; +;; If ANY of the bytes in cmd_response is 0xFE, the command failed. +;; +;; @modifies: A,X,Y +;; @param A: The command byte +;; @param X: The data byte or NO_DATA if only the command byte should be sent +;; @param Y: The number of data bytes expected to receive. Must be one of {0, 1, 2} +;;******************************************************************************** +.proc _process_cmd_answer +@store_response: + ldx response_length + lda scancode + sta cmd_response,x + inx + stx response_length + stz scancode + ; check for resend + cmp #$fe + beq @cmd_fail + ; received something useful, check state + lda status + bit #ps2kb::STATUS::SEND_CMD + bne @cmd_sent + bit #ps2kb::STATUS::SEND_DATA + bne @everything_sent + ; status must be SEND_RECV + +@receive_data_response: + dec expect_data_length + bmi @cmd_done ; no more expected data + rts ; wait for another data byte + +@cmd_sent: + ; check if a data byte needs to be sent + lda send_data + beq @send_data ; if 0 (would fall through the cmp check TODO would it??) + cmp #ps2kb::NO_DATA + beq @everything_sent ; if not NO_DATA +@send_data: + lda #ps2kb::STATUS::SEND_DATA + sta status + lda send_data + jmp ps2kb::_send_byte ; send the data + +@everything_sent: ; check if additonal bytes are expected + dec expect_data_length + bmi @cmd_done ; expect_data_length was 0 + lda #(ps2kb::STATUS::SEND_RECV | ps2kb::STATUS::RECEIVE_ANSWER) + sta status + rts + +@cmd_fail: ; keyboard wont expect data byte/send an additional response byte +@cmd_done: + ; restore previous status + lda prev_status + sta status + ; disable the clock if the previous was not RECEIVE_KEYS + cmp #ps2kb::STATUS::RECEIVE_KEYS + beq @rts + _DisableClock +@rts: + rts .endproc + +.rodata +;; Can be used by other programs +FMT_CMD_FAIL: .asciiz "PS/2 Cmd fail: %x-%x > %x%x%x "