; kbd
;
; Control TI-84+'s keyboard.
;
; *** Constants ***
.equ	KBD_PORT	0x01

; Keys that have a special meaning in GetC. All >= 0x80. They are interpreted
; by GetC directly and are never returned as-is.
.equ	KBD_KEY_ALPHA	0x80
.equ	KBD_KEY_2ND	0x81

; *** Variables ***
; active long-term modifiers, such as a-lock
; bit 0: A-Lock
.equ	KBD_MODS	KBD_RAMSTART
.equ	KBD_RAMEND	@+1

; *** Code ***

kbdInit:
	ld	a, 1		; begin with A-Lock on
	ld	(KBD_MODS), a
	ret

; Wait for a digit to be pressed and sets the A register ASCII value
; corresponding to that key press.
;
; This routine waits for a key to be pressed, but before that, it waits for
; all keys to be de-pressed. It does that to ensure that two calls to
; waitForKey only go through after two actual key presses (otherwise, the user
; doesn't have enough time to de-press the button before the next waitForKey
; routine registers the same key press as a second one).
;
; Sending 0xff to the port resets the keyboard, and then we have to send groups
; we want to "listen" to, with a 0 in the group bit. Thus, to know if *any* key
; is pressed, we send 0xff to reset the keypad, then 0x00 to select all groups,
; if the result isn't 0xff, at least one key is pressed.
kbdGetC:
	push	bc
	push	hl

	; During this GetC loop, register C holds the modificators
	; bit 0: Alpha
	; bit 1: 2nd
	; Initial value should be zero, but if A-Lock is on, it's 1
	ld	a, (KBD_MODS)
	and	1
	ld	c, a

	; loop until a digit is pressed
.loop:
	ld	hl, .dtbl
	; we go through the 7 rows of the table
	ld	b, 7
	; is alpha mod enabled?
	bit	0, c
	jr	z, .inner	; unset? skip next
	ld	hl, .atbl	; set? we're in alpha mode
.inner:
	ld	a, (hl)		; group mask
	call	.get
	cp	0xff
	jr	nz, .something
	; nothing for that group, let's scan the next group
	ld	a, 9
	call	addHL		; go to next row
	djnz	.inner
	; found nothing, loop
	jr	.loop
.something:
	; We have something on that row! Let's find out which char. Register A
	; currently contains a mask with the pressed char bit unset.
	ld	b, 8
	inc	hl
.findchar:
	rrca			; is next bit unset?
	jr	nc, .gotit	; yes? we have our char!
	inc	hl
	djnz	.findchar
.gotit:
	ld	a, (hl)
	or	a		; is char 0?
	jr	z, .loop	; yes? unsupported. loop.
	call	.debounce
	cp	KBD_KEY_ALPHA
	jr	c, .result	; A < 0x80? valid char, return it.
	jr	z, .handleAlpha
	cp	KBD_KEY_2ND
	jr	z, .handle2nd
	jp	.loop
.handleAlpha:
	; Toggle Alpha bit in C. Also, if 2ND bit is set, toggle A-Lock mod.
	ld	a, 1	; mask for Alpha
	xor	c
	ld	c, a
	bit	1, c		; 2nd set?
	jp	z, .loop	; unset? loop
	; we've just hit Alpha with 2nd set. Toggle A-Lock and set Alpha to
	; the value A-Lock has.
	ld	a, (KBD_MODS)
	xor	1
	ld	(KBD_MODS), a
	ld	c, a
	jp	.loop
.handle2nd:
	; toggle 2ND bit in C
	ld	a, 2	; mask for 2ND
	xor	c
	ld	c, a
	jp	.loop

.result:
	; We have our result in A, *almost* time to return it. One last thing:
	; Are in in both Alpha and 2nd mode? If yes, then it means that we
	; should return the upcase version of our letter (if it's a letter).
	bit	0, c
	jr	z, .end		; nope
	bit	1, c
	jr	z, .end		; nope
	; yup, we have Alpha + 2nd. Upcase!
	call	upcase
.end:
	pop	hl
	pop	bc
	ret
.get:
	ex	af, af'
	ld	a, 0xff
	di
	out	(KBD_PORT), a
	ex	af, af'
	out	(KBD_PORT), a
	in	a, (KBD_PORT)
	ei
	ret
.debounce:
	; wait until all keys are de-pressed
	; To avoid repeat keys, we require 64 subsequent polls to indicate all
	; depressed keys
	push	af		; --> lvl 1
	push	bc		; --> lvl 2
.pressed:
	ld	b, 64
.wait:
	xor	a
	call	.get
	inc	a		; if a was 0xff, will become 0 (nz test)
	jr	nz, .pressed	; non-zero? something is pressed
	djnz	.wait

	pop	bc		; <-- lvl 2
	pop	af		; <-- lvl 1
	ret

; digits table. each row represents a group. first item is group mask.
; 0 means unsupported. no group 7 because it has no keys.
.dtbl:
	.db	0xfe, 0, 0, 0, 0, 0, 0, 0, 0
	.db	0xfd, 0x0d, '+' ,'-' ,'*', '/', '^', 0, 0
	.db	0xfb, 0, '3', '6', '9', ')', 0, 0, 0
	.db	0xf7, '.', '2', '5', '8', '(', 0, 0, 0
	.db	0xef, '0', '1', '4', '7', ',', 0, 0, 0
	.db	0xdf, 0, 0, 0, 0, 0, 0, 0, KBD_KEY_ALPHA
	.db	0xbf, 0, 0, 0, 0, 0, KBD_KEY_2ND, 0, 0x7f

; alpha table. same as .dtbl, for when we're in alpha mode.
.atbl:
	.db	0xfe, 0, 0, 0, 0, 0, 0, 0, 0
	.db	0xfd, 0x0d, '"' ,'w' ,'r', 'm', 'h', 0, 0
	.db	0xfb, '?', 0, 'v', 'q', 'l', 'g', 0, 0
	.db	0xf7, ':', 'z', 'u', 'p', 'k', 'f', 'c', 0
	.db	0xef, ' ', 'y', 't', 'o', 'j', 'e', 'b', 0
	.db	0xdf, 0, 'x', 's', 'n', 'i', 'd', 'a', KBD_KEY_ALPHA
	.db	0xbf, 0, 0, 0, 0, 0, KBD_KEY_2ND, 0, 0x7f