220 lines
6.5 KiB
Plaintext
220 lines
6.5 KiB
Plaintext
;;********************************************************************************
|
|
;; @file
|
|
;; @brief PS/2 Keyboard driver
|
|
;; @ingroup drivers
|
|
;; @details
|
|
;; Support for a PS2 Keyboard using the shift register of a 6522 VIA
|
|
;; @section reading Reading a scancode/command answer
|
|
;; Pressing a key causes 11 bits to be sent: 1 start - 8 scancode - 1 parity - 1 stop
|
|
;; The VIA is set up to interrupt after 8 bits have been shifted into the shift register
|
|
;; from the external clock pulses of the keyboard (additional hardware required to
|
|
;; address the hardware bug of the VIA, where bit get lost when the external clock
|
|
;; transition happens during falling edge of PHI2). After reading the shift register,
|
|
;; the VIAs T2 timer is set to interrupt after the last 3 bits have been shifted in,
|
|
;; which takes about ~230ms.
|
|
;; T2 is used because it leaves to more versatile T1 available for something else
|
|
;;
|
|
;; @section processing Processing a scancode
|
|
;; The scancode may be processed by storing the address of a handler subroutine in
|
|
;; `ps2kb::scancode_handler`. This handler can load the scancode byte from `ps2kb::scancode`.
|
|
;;
|
|
;; To use a different layout, change the enum K and the CHARS_NOMOD and CHARS_MODSHIFT
|
|
;; @warning
|
|
;; The value with which the timer is loaded must depends on the clock frequency
|
|
;; of the keyboard and the computer
|
|
;;********************************************************************************
|
|
.ifndef INCLUDE_PS2_KEYBOARD
|
|
INCLUDE_PS2_KEYBOARD = 1
|
|
.include "system.h65"
|
|
|
|
|
|
;;********************************************************************************
|
|
;; @brief PS/2 Keyboard
|
|
;; This requires the data line to be hooked up to `CB2` and the clock to `CB1`.
|
|
;; The VIA will be set to shift in the data into the shift register under the external clock.
|
|
;; @see @ref via_hardware_bug "VIA external clock bug"
|
|
;; @include keyboard
|
|
;; @ingroup drivers
|
|
;;********************************************************************************
|
|
.scope ps2kb
|
|
Import ps2kb, init, begin_receive, scancode, status, scancode_handler
|
|
Import ps2kb, _receive_irq_shift_reg_handler, _receive_irq_timer_handler, _send_byte, _send_irq_shift_reg_handler, _send_irq_timer_handler,
|
|
Import ps2kb, send_command, send_cmd, send_data, cmd_response, response_length, FMT_CMD_FAIL
|
|
|
|
;; Base address of the VIA the keyboard is conencted to
|
|
VIA = IO1
|
|
|
|
;; @brief #clock cycles to to wait for the last 3 bits after the first 8 have been shifted in
|
|
;; @details
|
|
;; The last 3 bits take about 230 ms.
|
|
;; Calculate the appropriate value using: @f$ N_\text{cycles} = 230 \times f_\text{in MHZ} @f$
|
|
;; - 230 \@ 1MHz
|
|
;; - 400 \@ 1.84 MHz
|
|
;; @clock_dependent
|
|
TIMER_RECV = 400
|
|
;; @brief #clock cycles to wait after loading the SR a second time while sending a command to the keyboard
|
|
;; @details
|
|
;; Enough time must pass for one bit must be shifted out (parity),
|
|
;; but at most two (parity+stop).
|
|
;; After that, the interrupt must have happened because the keyboard will pull data low.
|
|
;; At that point, the shift register needs to be set to input again
|
|
;;
|
|
;; Values that seem to work:
|
|
;; - 230 \@ 1MHz
|
|
;; - 400 \@ 1.84 MHz
|
|
;; @clock_dependent
|
|
TIMER_SEND = 400
|
|
|
|
PULL_REG = IO::RANH
|
|
PULL_DDR = IO::DDRA
|
|
; use RA4 to pull the clock low
|
|
PULL_MASK_CLK = %00010000
|
|
|
|
; using ff because 0 is a possible data byte (set led)
|
|
NO_DATA = $ff ;; indicates that no data byte should be send
|
|
NO_RESPONSE = $ff ;; indicates a command did not receive a response (yet)
|
|
ACK = $fa ;; successful transmission
|
|
RESEND = $fe ;; unsuccessful transmission
|
|
|
|
|
|
.enum STATUS
|
|
RECEIVE_KEYS = %10000000 ;;< keyboard sends scancodes
|
|
RECEIVE_ANSWER = %01000000 ;;< keyboard replies to a command
|
|
SEND_CMD = %00100000 ;;< host sends/sent the command byte
|
|
SEND_DATA = %00010000 ;;< host sends/sent the data byte
|
|
SEND_RECV = %00001000 ;;< keyboard sends additional data byte
|
|
SEND = SEND_CMD | SEND_DATA | SEND_RECV ;;< host is sending something, only used for checking status
|
|
NONE = %00000000
|
|
.endenum
|
|
|
|
.enum TYPEMATIC
|
|
REPEAT_MASK = %00011111 ;;< 00000 = 30Hz, ..., 11111 = 2Hz
|
|
DELAY250 = %00000000
|
|
DELAY500 = %00100000
|
|
DELAY750 = %01000000
|
|
DELAY1000 = %01100000
|
|
.endenum
|
|
.endscope
|
|
|
|
; see https://wiki.osdev.org/PS/2_Keyboard
|
|
.macro ps2kb_CmdEcho
|
|
lda #$ee
|
|
ldx #ps2kb::NO_DATA
|
|
ldy #0
|
|
jsr ps2kb::send_command
|
|
.endmacro
|
|
|
|
.macro ps2kb_CmdSetLeds mask
|
|
lda #$ed
|
|
.ifnblank mask
|
|
ldx #mask
|
|
.endif
|
|
ldy #0
|
|
jsr ps2kb::send_command
|
|
.endmacro
|
|
|
|
.macro ps2kb_CmdGetScancodeSet
|
|
lda #$f0
|
|
ldx #0
|
|
ldy #1
|
|
jsr ps2kb::send_command
|
|
.endmacro
|
|
|
|
.macro ps2kb_CmdSetScancodeSet set
|
|
.assert set = 1 .or set = 2 .or set = 3, warning, "ps2kb_CmdSetScancodeSet: invalid scancode set"
|
|
lda #$f0
|
|
ldx #set
|
|
ldy #0
|
|
jsr ps2kb::send_command
|
|
.endmacro
|
|
|
|
.macro ps2kb_CmdIdentify
|
|
lda #$f2
|
|
ldx #ps2kb::NO_DATA
|
|
ldy #2
|
|
jsr ps2kb::send_command
|
|
.endmacro
|
|
|
|
.macro ps2kb_CmdTypematicSettings settings
|
|
lda #$f3
|
|
ldx #settings
|
|
ldy #0
|
|
jsr ps2kb::send_command
|
|
.endmacro
|
|
|
|
.macro ps2kb_CmdEnable
|
|
lda #$f4
|
|
ldx #ps2kb::NO_DATA
|
|
ldy #0
|
|
jsr ps2kb::send_command
|
|
.endmacro
|
|
|
|
.macro ps2kb_CmdDisable
|
|
lda #$f5
|
|
ldx #ps2kb::NO_DATA
|
|
ldy #0
|
|
jsr ps2kb::send_command
|
|
.endmacro
|
|
|
|
.macro ps2kb_CmdSetTypematic scancode
|
|
ldy #0
|
|
.ifblank scancode
|
|
lda #$f7
|
|
ldx #ps2kb::NO_DATA
|
|
.else
|
|
lda #$fb
|
|
ldx #scancode
|
|
.endif
|
|
jsr ps2kb::send_command
|
|
.endmacro
|
|
|
|
.macro ps2kb_CmdSetMakeRelease scancode
|
|
ldy #0
|
|
.ifblank scancode
|
|
lda #$f8
|
|
ldx #ps2kb::NO_DATA
|
|
.else
|
|
lda #$fc
|
|
ldx #scancode
|
|
.endif
|
|
jsr ps2kb::send_command
|
|
.endmacro
|
|
|
|
.macro ps2kb_CmdSetMake scancode
|
|
ldy #0
|
|
.ifblank scancode
|
|
lda #$f9
|
|
ldx #ps2kb::NO_DATA
|
|
.else
|
|
lda #$fd
|
|
ldx #scancode
|
|
.endif
|
|
jsr ps2kb::send_command
|
|
.endmacro
|
|
|
|
.macro ps2kb_CmdSelfTest
|
|
lda #$ff
|
|
ldx #ps2kb::NO_DATA
|
|
ldy #1
|
|
jsr ps2kb::send_command
|
|
.endmacro
|
|
|
|
.macro ps2kb_WaitFinishCmd
|
|
:
|
|
wai
|
|
lda ps2kb::status
|
|
and #ps2kb::STATUS::SEND
|
|
bne :-
|
|
.endmacro
|
|
|
|
|
|
;;********************************************************************************
|
|
;; @note You need to manually include `string.h65` when using this macro!
|
|
;;********************************************************************************
|
|
.macro ps2kb_PrintCmdFailed
|
|
Printf ps2kb::FMT_CMD_FAIL,ps2kb::send_cmd,ps2kb::send_data,ps2kb::cmd_response,ps2kb::cmd_response+1,ps2kb::cmd_response+2
|
|
.endmacro
|
|
|
|
|
|
.endif ; guard
|