keydev: add a keyboard device layer to abstract away globals

The new keyboard device (keydev) interface implements the kernel's view
of a keyboard providing input events. Its main role is to abstract all
the globals of the KEYSC driver and getkey functions into a separate
object: the "keyboard device".

The device implements event transformations such as modifiers and
repeats, instead of leaving them to getkey. While this can seem
surprising at first, a real keyboard controller is responsible for
repeats and modifier actions depend on the state of the keyboard which
is only tracked in real-time.

In this commit, getkey() has not changed yet apart from indirectly using
the keydev interface with pollevent(). It will be changed soon to use
event transforms in keydev_read(), and will be left in charge of
providing repeat profiles, handling return-to-menu, backlight changes
and timeouts, all of which are user convenience features.
This commit is contained in:
Lephe 2021-03-05 09:31:34 +01:00
parent 97ca5ba82f
commit dd564f094a
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
7 changed files with 647 additions and 166 deletions

View file

@ -29,6 +29,7 @@ set(SOURCES_COMMON
src/keysc/getkey.c src/keysc/getkey.c
src/keysc/iokbd.c src/keysc/iokbd.c
src/keysc/keycodes.c src/keysc/keycodes.c
src/keysc/keydev.c
src/keysc/keysc.c src/keysc/keysc.c
src/kprint/kprint.c src/kprint/kprint.c
src/kprint/kformat_fp.c src/kprint/kformat_fp.c

View file

@ -28,6 +28,8 @@
GCC cannot optimize access size, and reads to memory-mapped I/O with invalid GCC cannot optimize access size, and reads to memory-mapped I/O with invalid
access sizes silently fail - honestly you don't want this to happen */ access sizes silently fail - honestly you don't want this to happen */
#define GPACKED(x) __attribute__((packed, aligned(x))) #define GPACKED(x) __attribute__((packed, aligned(x)))
/* Packed enumerations */
#define GPACKEDENUM __attribute__((packed))
/* Transparent unions */ /* Transparent unions */
#define GTRANSPARENT __attribute__((transparent_union)) #define GTRANSPARENT __attribute__((transparent_union))

View file

@ -11,6 +11,8 @@
#include <stddef.h> #include <stddef.h>
/* For all fixed-width integer types */ /* For all fixed-width integer types */
#include <stdint.h> #include <stdint.h>
/* For human-readable boolean types */
#include <stdbool.h>
/* Fixed-width types for bit fields are quite meaningless */ /* Fixed-width types for bit fields are quite meaningless */
typedef unsigned int uint; typedef unsigned int uint;

View file

@ -0,0 +1,264 @@
//---
// gint:keydev - Kernel's keyboard input devices
//---
#include <gint/keyboard.h>
#ifndef GINT_KEYDEV
#define GINT_KEYDEV
/* Size of the buffer event queue */
#define KEYBOARD_QUEUE_SIZE 32
/* keydev_event_t: Change of state in a full row */
typedef struct {
/* Device time for the event */
uint16_t time;
/* Keys that changed state */
uint8_t changed;
/* Row number */
uint8_t row :4;
/* Type of change, either KEYEV_DOWN or KEYEV_UP */
uint8_t kind :4;
} keydev_event_t;
/* Event transforms
Every keyboard input device has a built-in event stream editor that can
transform the events in various ways to change the abstraction details, from
the raw pollevent() with no transforms to the highest-level getkey().
Because many transforms like <Instant Modifiers> need to observe the state
of the keyboard at the time of the event to transform, this process needs to
be performed right when events are requested and cannot be done later
without keeping around a lot of information about past states.
Just as switching from pollevent() to getkey() requires some caution,
changing the transform settings while keys are pressed or modifiers are
active minimally impacts the output of the editor. Here is a summary of the
interactions:
* <Delete Modifiers> and <Delete Releases> are only concerned about the
current event and can be turned ON and OFF at any time without problems.
* <Instant SHIFT> and <Instant ALPHA> only look at the state of the keyboard
with keydev_keydown() and are not affected by other options.
* <Delayed SHIFT> and <Delayed ALPHA> only trigger when a SHIFT or ALPHA
press-release is performed when the keyboard is idle.
-> If turned ON while a key is pressed, nothing happens until a further
SHIFT or ALPHA key press while the keyboard is idle.
-> If turned OFF while (1) a modifier key is being pressed, or (2) a
modifier key has been pressed and released (charging up the delayed
modifier), the charge is lost.
Thus turning delayed modifiers ON->OFF->ON drops the current charge.
* <Repeats> has the most interactions.
-> Toggling <Instant SHIFT> or <Instant ALPHA> during a repeat takes
effect immediately, so a streak can start with instant modifiers
disabled, enable them mid-flight, and start producing repeating events
with instant modifiers.
-> Switching <Delayed SHIFT> or <Delayed ALPHA> during a modified streak
drops the modifier immediately.
-> Switching ON any modifier transform breaks a SHIFT or ALPHA streak.
-> Switching OFF all modifier transforms allows a currently-pressed SHIFT
or ALPHA key to start a repeat streak, which (due to the way candidates
for repeats are considered) can only be used to turn a incomplete
delayed modifier charge or an unused instant modifier into a streak.
Generally speaking changing options while keys are pressed is safe, but it
is better to do it while the device is idle for more consistency.
pollevent() provides the raw events and bypasses the transforms. However,
the information needed to resume transforming is still tracked when
generating raw events: for instance, which key is a candidate for repetition
is still tracked (by contrast, getkey() needs special code to catch up if
pollevent() unqueued events that it needed). Delayed modifiers are not
tracked because they can be charged only if the transform is enabled when
the modifier key is pressed. */
enum {
/* Delayed SHIFT: Pressing then immediately releasing SHIFT when the
keyboard is idle applies SHIFT to the next repeat streak. */
KEYDEV_TR_DELAYED_SHIFT = 0x01,
/* Delayed ALPHA: Idem with the ALPHA key */
KEYDEV_TR_DELAYED_ALPHA = 0x02,
/* Instant SHIFT: Each individual event of every repeat streak gets
SHIFT applied if SHIFT is pressed at the time of the repeat. */
KEYDEV_TR_INSTANT_SHIFT = 0x04,
/* Instant ALPHA: Idem with the ALPHA key */
KEYDEV_TR_INSTANT_ALPHA = 0x08,
/* Repeats: Keys are repeated according to a repeat filter function */
KEYDEV_TR_REPEATS = 0x10,
/* Delete Modifiers: Remove modifier keys from generated events, which
are generally useless when delayed/instant SHIFT/ALPHA are used */
KEYDEV_TR_DELETE_MODIFIERS = 0x20,
/* Delete Releases: Remove KEYEV_UP events */
KEYDEV_TR_DELETE_RELEASES = 0x40,
};
/* keydev_transform_t: Full specification for transforms on the event stream */
typedef struct {
/* List of enabled transforms. The order is cannot be changed because
any other order than the default produces useless results. This is
an OR-combination of KEYDEV_TR_* flags. */
int enabled;
/* Repeater function. This function is called whenever (key) is pressed
(KEYEV_DOWN) or repeated (KEYEV_HOLD), and should indicate how long
to wait until the next repeat. This setting is meaningful only if
<Repeats> is enabled.
-> (key) is the key currently being repeated.
-> (duration) is the time since it was first pressed (us).
-> (count) is the number of repeats produced so far.
The function should either return -1 to disable all further repeats,
or a positive number of microseconds until the next repeat. Note
that the precision of the delay is limited by the speed at which the
keyboard is scanned, which is nowhere near microsecond-level. By
default (128 Hz) the precision is about 7.8 ms. */
int (*repeater)(int key, int duration, int count);
} GPACKEDENUM keydev_transform_t;
/* keydev_t: Keyboard device
This structure represents the state and settings of a keyboard device that
provides input to the kernel and applications. The notion of keyboard device
is useful for demo/replays that input events without the physical keyboard,
and a couple of corner uses like control over USB.
The keyboard device has built-in event transformations, which modifty the
stream of events by adding information, combining modifiers, and removing
undesired events. Because the event transformation reky on the current state
of the keyboard, they must be run by the driver whenever events are read, so
they are tied to the device.
The default keyboard functions pollevent(), waitevent(), getkey() and
keydown() are shortcuts for keydev functions using the physical keyboard as
their input. */
typedef struct {
/* Latest state of keys we are aware of. At every processing step, the
different between this and the fresh information is queued and this
is updated. state_now is identical to the real state obtained from
the device unless earlier events failed to be queued, in which case
a difference is maintained so they will be reconsidered later. */
GALIGNED(4) uint8_t state_now[12];
/* State of keys based on produced events. (state_queue + queue) is
always identical to (state_now). When the queue is empty both states
are the same. This is the user's view of the keyboard. */
GALIGNED(4) uint8_t state_queue[12];
/* Current device time in scanning-ticks */
uint time;
/* Last time when repeats were considered */
uint time_repeats;
/* Event queue (circular buffer) */
keydev_event_t queue[KEYBOARD_QUEUE_SIZE];
/* Next event in queue, position after last event in queue */
int8_t queue_next;
int8_t queue_end;
/* Number of events lost because of missing queue space */
uint events_lost;
/* Crafted events waiting to be picked up */
key_event_t out[8];
int out_size;
/* Event transforms */
keydev_transform_t tr;
// <Delayed Modifiers>
/* delayed_* is set when the delayed modifier is active (after a press/
release). pressed_* is set between the press and the release. */
uint pressed_shift :1;
uint pressed_alpha :1;
uint delayed_shift :1;
uint delayed_alpha :1;
uint :4;
// <Repeats>
/* Candidate key for repeats (or 0 if no key is candidate yet) */
int rep_key;
/* Number of repeats alreay sent */
int rep_count;
/* Time since key was first pressed (us) */
int rep_time;
/* Delay until next repeat, set by the repeat planner (us) */
int rep_delay;
} keydev_t;
/* keydev_std(): Standard keyboard input device
Returns the keyboard device structure for the calculator's keyboard. */
keydev_t *keydev_std(void);
//---
// Functions for keyboard device drivers
//---
/* keydev_init(): Initialize a keyboard device to its idle state */
void keydev_init(keydev_t *d);
/* keydev_process_state(): Process the new keyboard states for events
This function compares the provided scanned state with the stored state and
generates events to catch up. This should be used by scan-based keyboard
devices after a scan. */
void keydev_process_state(keydev_t *d, uint8_t state[12]);
/* keydev_process_key(): Process a new key state for events
This function compares the provided key state with the stored state and
generates events to catch up. This should be used by event-based keyboard
devices (such as demo replays) to feed in new events. */
void keydev_process_key(keydev_t *d, int keycode, bool state);
/* keydev_tick(): Prepare the next tick
This function maintains time trackers in the device and should be called in
each frame after the scanning is finished and the keydev_process_*()
functions have been called. The timer elapsed since the last tick should
be specified as well. */
void keydev_tick(keydev_t *d, uint us);
//---
// Low-level API to read events from the device
//---
/* keydev_unqueue_event(): Retrieve the next keyboard event in queue
This source provides the queued KEYEV_UP and KEYEV_DOWN events generated
from the regular scans in chronological order. It does not generate the
KEYEV_HOLD events though, keyev_repeat_event() must be used to obtain these
at the end of every tick. */
key_event_t keydev_unqueue_event(keydev_t *d);
/* keydev_repeat_event(): Generate a repeat event if applicable
At the end of every scan tick (or later if the application is unable to keep
up), this source will generate a repeat event if the repeat transform is
enabled and the conditions for a repeat are satisfied. */
key_event_t keydev_repeat_event(keydev_t *d);
/* keydev_idle(): Check if all keys are released
A list of keys to ignore can be specified as variable arguments. The list
must be terminated by a 0 keycode. */
bool keydev_idle(keydev_t *d, ...);
//---
// High-level API to read from the device
//---
/* keydev_keydown(): Check if a key is down according to generated events */
bool keydev_keydown(keydev_t *d, int key);
/* keydev_transform(): Obtain current transform parameters */
keydev_transform_t keydev_transform(keydev_t *d);
/* keydev_set_transform(): Set transform parameters */
void keydev_set_transform(keydev_t *d, keydev_transform_t tr);
/* keydev_read(): Retrieve the next transformed event */
key_event_t keydev_read(keydev_t *d);
#endif /* GINT_KEYDEV */

View file

@ -114,12 +114,6 @@ enum
KEYEV_HOLD = 3, /* A key that was pressed has been held down */ KEYEV_HOLD = 3, /* A key that was pressed has been held down */
}; };
/* Size of the buffer event queue, can be customized using gint's configure
script before compiling the library. Better be a power of 2. */
#ifndef KEYBOARD_QUEUE_SIZE
#define KEYBOARD_QUEUE_SIZE 32
#endif
/* Keyboard frequency analysis is a runtime setting since gint 2.4. This macro /* Keyboard frequency analysis is a runtime setting since gint 2.4. This macro
is preserved for compatibility until gint 3. */ is preserved for compatibility until gint 3. */
#define KEYBOARD_SCAN_FREQUENCY keysc_scan_frequency() #define KEYBOARD_SCAN_FREQUENCY keysc_scan_frequency()

349
src/keysc/keydev.c Normal file
View file

@ -0,0 +1,349 @@
//---
// gint:keydev - Generic input handling on keyboard devices
//---
#include <gint/keyboard.h>
#include <gint/drivers/keydev.h>
#include <gint/defs/types.h>
#include <gint/defs/util.h>
#include <gint/std/string.h>
#include <stdarg.h>
void keydev_init(keydev_t *d)
{
memset(d, 0, sizeof *d);
}
//---
// Driver event generation
//---
/* queue_push(): Add an event in a device's buffer
Returns false if the event cannot be pushed. */
static bool queue_push(keydev_t *d, keydev_event_t e)
{
int next = (d->queue_end + 1) % KEYBOARD_QUEUE_SIZE;
if(next == d->queue_next)
{
d->events_lost++;
return false;
}
d->queue[d->queue_end] = e;
d->queue_end = next;
return true;
}
/* queue_poll(): Generate key events from the buffer
Sets (*e) and returns true on success, otherwise false. */
static bool queue_poll(keydev_t *d, keydev_event_t *e)
{
if(d->queue_next == d->queue_end) return false;
*e = d->queue[d->queue_next];
d->queue_next = (d->queue_next + 1) % KEYBOARD_QUEUE_SIZE;
return true;
}
/* keydev_process_state(): Process the new keyboard states for events */
void keydev_process_state(keydev_t *d, uint8_t scan[12])
{
/* Compare new data with the internal state. Push releases before
presses so that a key change occurring within a single analysis
frame can be performed. This happens all the time when going back to
the main MENU via gint_osmenu() on a keybind. */
for(int row = 0; row < 12; row++)
{
int diff = ~scan[row] & d->state_now[row];
if(!diff) continue;
/* Update internal status if the event can be pushed */
keydev_event_t e = { d->time, diff, row, KEYEV_UP };
if(queue_push(d, e)) d->state_now[row] &= scan[row];
}
for(int row = 0; row < 12; row++)
{
int diff = scan[row] & ~d->state_now[row];
if(!diff) continue;
keydev_event_t e = { d->time, diff, row, KEYEV_DOWN };
if(queue_push(d, e)) d->state_now[row] |= scan[row];
}
}
/* keydev_process_key(): Process a new key state for events */
void keydev_process_key(keydev_t *d, int keycode, bool state)
{
/* If the key has changed state, push an event */
int row = (keycode >> 4) ^ 1;
int col = 0x80 >> (keycode & 0x7);
int prev = d->state_now[row] & col;
if(state && !prev)
{
keydev_event_t e = { d->time, col, row, KEYEV_DOWN };
if(queue_push(d, e)) d->state_now[row] |= col;
}
else if(!state && prev)
{
keydev_event_t e = { d->time, col, row, KEYEV_UP };
if(queue_push(d, e)) d->state_now[row] &= ~col;
}
}
void keydev_tick(keydev_t *d, uint us)
{
d->time++;
if(d->rep_key != 0)
{
if(d->rep_delay >= 0)
d->rep_delay = max(d->rep_delay - (int)us, 0);
d->rep_time += us;
}
}
//---
// Keyboard event generation
//---
static bool can_repeat(keydev_t *d, int key)
{
int tr = d->tr.enabled;
int shift = tr & (KEYDEV_TR_DELAYED_SHIFT | KEYDEV_TR_INSTANT_SHIFT);
int alpha = tr & (KEYDEV_TR_DELAYED_ALPHA | KEYDEV_TR_INSTANT_ALPHA);
return !(key == KEY_SHIFT && shift) && !(key == KEY_ALPHA && alpha);
}
/* keydev_unqueue_event(): Retrieve the next keyboard event in queue */
key_event_t keydev_unqueue_event(keydev_t *d)
{
/* Every device event is unfolded into up to 8 keyboard events, stored
temporarily in the driver's structure. */
keydev_event_t e;
key_event_t kev = { .type = KEYEV_NONE, .time = d->time };
/* If there are no events, generate some */
if(d->out_size == 0)
{
if(!queue_poll(d, &e)) return kev;
int changed = e.changed;
kev.type = e.kind;
for(int code = ((e.row ^ 1) << 4) | 0x7; code & 0x7; code--)
{
if(changed & 1)
{
kev.key = code;
d->out[d->out_size++] = kev;
}
changed >>= 1;
}
}
/* Return one of the available events */
kev = d->out[--(d->out_size)];
/* Update the event state accordingly */
int row = (kev.key >> 4) ^ 1;
int col = 0x80 >> (kev.key & 0x7);
if(kev.type == KEYEV_DOWN)
{
d->state_queue[row] |= col;
/* Mark this key as the currently repeating one */
if(d->rep_key == 0 && can_repeat(d, kev.key))
{
d->rep_key = kev.key;
d->rep_count = -1;
d->rep_time = 0;
d->rep_delay = 0;
}
}
if(kev.type == KEYEV_UP)
{
d->state_queue[row] &= ~col;
/* End the current repeating streak */
if(d->rep_key == kev.key)
{
d->rep_key = 0;
d->rep_count = -1;
d->rep_time = -1;
d->rep_delay = -1;
d->delayed_shift = 0;
d->delayed_alpha = 0;
}
}
return kev;
}
/* keydev_repeat_event(): Generate a repeat event if applicable */
key_event_t keydev_repeat_event(keydev_t *d)
{
key_event_t e = { .type = KEYEV_NONE, .time = d->time };
/* <Repeats> is disabled */
if(!(d->tr.enabled & KEYDEV_TR_REPEATS)) return e;
/* No key is being repeated, or it's too early */
if(!d->rep_key || d->rep_delay != 0) return e;
/* Key is blocked by transform options modified during the streak */
if(!can_repeat(d, d->rep_key)) return e;
/* Plan the next repeat the currently-pressed key */
int elapsed = (int16_t)(d->time - d->rep_time);
d->rep_delay = -1;
d->rep_count++;
/* Returning < 0 will block further repeats */
if(d->tr.repeater)
d->rep_delay = d->tr.repeater(d->rep_key,elapsed,d->rep_count);
/* Don't return an event on the first call (it's a KEYEV_DOWN) */
if(!d->rep_count) return e;
e.key = d->rep_key;
e.type = KEYEV_HOLD;
return e;
}
/* keydev_keydown(): Check if a key is down according to generated events */
bool keydev_keydown(keydev_t *d, int key)
{
int row = (key >> 4) ^ 1;
int col = 0x80 >> (key & 0x7);
return (d->state_queue[row] & col) != 0;
}
/* keydev_idle(): Check if all keys are released */
bool keydev_idle(keydev_t *d, ...)
{
uint32_t *state = (void *)d->state_queue;
uint32_t check[3] = { state[0], state[1], state[2] };
int key;
va_list args;
va_start(args, d);
while((key = va_arg(args, int)))
{
int row = (key >> 4) ^ 1;
int col = 0x80 >> (key & 0x7);
((uint8_t *)check)[row] &= ~col;
}
va_end(args);
return (check[0] == 0) && (check[1] == 0) && (check[2] == 0);
}
//---
// Event transforms
//---
/* keydev_transform(): Obtain current transform parameters */
keydev_transform_t keydev_transform(keydev_t *d)
{
return d->tr;
}
/* keydev_set_transform(): Set transform parameters */
void keydev_set_transform(keydev_t *d, keydev_transform_t tr)
{
int change = d->tr.enabled ^ tr.enabled;
if(change & KEYDEV_TR_DELAYED_SHIFT)
{
d->pressed_shift = 0;
d->delayed_shift = 0;
}
if(change & KEYDEV_TR_DELAYED_ALPHA)
{
d->pressed_alpha = 0;
d->delayed_alpha = 0;
}
d->tr = tr;
}
/* keydev_read(): Retrieve the next transformed event */
key_event_t keydev_read(keydev_t *d)
{
#define opt(NAME) (d->tr.enabled & KEYDEV_TR_ ## NAME)
key_event_t e;
while(1)
{
/* Repeat at the end of every tick, or if we're late */
bool empty = (d->queue_next == d->queue_end) && !d->out_size;
bool end_of_tick = (d->time_repeats == d->time - 1) && empty;
bool late_repeat = (d->time_repeats <= d->time - 2);
if(end_of_tick || late_repeat)
e = keydev_repeat_event(d);
if(e.type == KEYEV_NONE)
e = keydev_unqueue_event(d);
if(e.type == KEYEV_NONE)
return e;
int k = e.key;
// <Instant SHIFT> and <Instant ALPHA>
if(e.type == KEYEV_DOWN || e.type == KEYEV_HOLD)
{
if(opt(INSTANT_SHIFT) && k != KEY_SHIFT)
e.shift |= keydev_keydown(d, KEY_SHIFT);
if(opt(INSTANT_ALPHA) && k != KEY_ALPHA)
e.alpha |= keydev_keydown(d, KEY_ALPHA);
}
// <Delayed SHIFT> and <Delayed ALPHA>
if(opt(DELAYED_SHIFT))
{
if(e.type == KEYEV_DOWN && k == KEY_SHIFT)
{
d->pressed_shift |= keydev_idle(d,KEY_SHIFT,0);
}
else if(e.type != KEYEV_UP && k == d->rep_key)
{
e.shift |= d->delayed_shift;
d->pressed_shift = 0;
}
else if(e.type == KEYEV_UP && d->pressed_shift)
{
d->pressed_shift = 0;
d->delayed_shift = 1;
}
}
if(opt(DELAYED_ALPHA))
{
if(e.type == KEYEV_DOWN && k == KEY_ALPHA)
{
d->pressed_alpha |= keydev_idle(d,KEY_ALPHA,0);
}
else if(e.type != KEYEV_UP && k == d->rep_key)
{
e.alpha |= d->delayed_alpha;
d->pressed_alpha = 0;
}
else if(e.type == KEYEV_UP && d->pressed_alpha)
{
d->pressed_alpha = 0;
d->delayed_alpha = 1;
}
}
// <Delete Modifiers>
if(opt(DELETE_MODIFIERS) && !can_repeat(d, k)) continue;
// <Delete Releases>
if(opt(DELETE_RELEASES) && e.type == KEYEV_UP) continue;
return e;
}
#undef opt
}

View file

@ -7,6 +7,7 @@
#include <gint/timer.h> #include <gint/timer.h>
#include <gint/clock.h> #include <gint/clock.h>
#include <gint/keyboard.h> #include <gint/keyboard.h>
#include <gint/drivers/keydev.h>
#include <gint/defs/attributes.h> #include <gint/defs/attributes.h>
#include <gint/defs/types.h> #include <gint/defs/types.h>
@ -18,78 +19,23 @@
#include <stdarg.h> #include <stdarg.h>
/* Keyboard scan frequency in Hertz. Start with 128 Hz, this frequency *must /* Keyboard scan frequency in Hertz. Start with 128 Hz, this frequency *must
be high* for the keyboard to work! */ be high* for the keyboard to work! Reading at low frequencies produces a lot
of artifacts. See https://www.casiopeia.net/forum/viewtopic.php?p=20592. */
static int scan_frequency = 128; static int scan_frequency = 128;
/* Approximation in microseconds, used by the timer and repeat delays */
static uint32_t scan_frequency_us = 7812; /* 1000000 / scan_frequency */
/* Keyboard scanner timer */ /* Keyboard scanner timer */
static int keysc_tid = -1; static int keysc_tid = -1;
/* Keyboard input device for this Key Scan Interface */
static keydev_t dev_keysc;
//--- /* keydev_std(): Standard keyboard input device */
// Keyboard buffer keydev_t *keydev_std(void)
//---
/* The driver's internal state. At each step of time, this file compares the
internal state with the hardware state and generates events accordingly.
Events can be seen as a delta-encoding of the keyboard state over time.
To ensure that adding pending events to the last-read state always gives the
internal driver state, this array is not updated if the generation of an
event fails. (Most likely the even will be regenerated at the next scan.) */
static volatile uint8_t state[12] = { 0 };
/* The driver's current event state. This state corresponds to the sum of all
events sent to the user so far.When the event queue is empty, this is equal
to [state]. For each generated event, this array is updated to reflect the
user's view of the keyboard. */
static uint8_t current[12] = { 0 };
/* A driver event, which is a common change in a full row */
typedef struct
{ {
uint16_t time; /* Locally unique time identifier */ return &dev_keysc;
uint8_t changed; /* Keys that changed state */
uint8_t row :4; /* Row number */
uint8_t kind :4; /* Type of change, either KEYEV_DOWN or KEYEV_UP */
} driver_event_t;
/* The keyboard event buffer. This is a circular list defined by [buffer_start]
and [buffer_end]. To avoid an ambiguity when start == end, there must always
be at least one free entry. */
GBSS static driver_event_t buffer[KEYBOARD_QUEUE_SIZE];
/* Buffer bounds */
static int buffer_start = 0;
static int buffer_end = 0;
/* Current time, in keyboard-scanning ticks */
static uint time = 0;
/* buffer_push(): Add an event in the keyboard buffer
Returns non-zero if the event cannot be pushed. */
static int buffer_push(driver_event_t ev)
{
int next = (buffer_end + 1) % KEYBOARD_QUEUE_SIZE;
if(next == buffer_start) return 1;
buffer[buffer_end] = ev;
buffer_end = next;
return 0;
} }
/* buffer_poll(): Generate key events from the buffer
Sets [ev] and returns zero on success, otherwise non-zero. */
static int buffer_poll(driver_event_t *ev)
{
if(buffer_start == buffer_end) return 1;
*ev = buffer[buffer_start];
buffer_start = (buffer_start + 1) % KEYBOARD_QUEUE_SIZE;
return 0;
}
//---
// Keyboard scanning
//---
/* keysc_scan_frequency(): Get the current keyboard scan frequency in Hertz */ /* keysc_scan_frequency(): Get the current keyboard scan frequency in Hertz */
int keysc_scan_frequency(void) int keysc_scan_frequency(void)
{ {
@ -99,9 +45,7 @@ int keysc_scan_frequency(void)
/* keysc_scan_frequency_us(): Get keyboard scan delay in microseconds */ /* keysc_scan_frequency_us(): Get keyboard scan delay in microseconds */
uint32_t keysc_scan_frequency_us(void) uint32_t keysc_scan_frequency_us(void)
{ {
int delay = 1000000 / scan_frequency; return scan_frequency_us;
if(delay == 0) delay = 1;
return delay;
} }
/* keysc_set_scan_frequency(): Set the keyboard scan frequency in Hertz */ /* keysc_set_scan_frequency(): Set the keyboard scan frequency in Hertz */
@ -110,20 +54,21 @@ void keysc_set_scan_frequency(int freq)
if(freq < 64) freq = 64; if(freq < 64) freq = 64;
if(freq > 32768) freq = 32768; if(freq > 32768) freq = 32768;
scan_frequency = freq; scan_frequency = freq;
scan_frequency_us = 1000000 / freq;
if(keysc_tid < 0) return;
uint32_t TCOR = timer_delay(keysc_tid, keysc_scan_frequency_us(), 0);
timer_reload(keysc_tid, TCOR);
getkey_refresh_delays(); getkey_refresh_delays();
if(keysc_tid < 0) return;
uint32_t TCOR = timer_delay(keysc_tid, scan_frequency_us, 0);
timer_reload(keysc_tid, TCOR);
} }
/* keysc_frame(): Generate driver events from KEYSC state */ /* keysc_tick(): Update the keyboard to the next state */
static void keysc_frame(void) static int keysc_tick(void)
{ {
GALIGNED(2) uint8_t scan[12] = { 0 }; GALIGNED(2) uint8_t scan[12] = { 0 };
/* First scan the key matrix: from I/O ports on SH3, KEYSC on SH4 */ /* Scan the key matrix: from I/O ports on SH3, KEYSC on SH4 */
if(isSH3()) iokbd_scan(scan); if(isSH3()) iokbd_scan(scan);
else else
{ {
@ -133,82 +78,15 @@ static void keysc_frame(void)
for(int i = 0; i < 6; i++) array[i] = KEYSC[i]; for(int i = 0; i < 6; i++) array[i] = KEYSC[i];
} }
/* Compare new data with the internal state. Push releases before keydev_process_state(&dev_keysc, scan);
presses so that a key change occurring within a single analysis keydev_tick(&dev_keysc, scan_frequency_us);
frame can be performed. This happens all the time when going back to return TIMER_CONTINUE;
the main MENU via gint_osmenu() on a keybind. */
for(int row = 0; row < 12; row++)
{
int diff = ~scan[row] & state[row];
if(!diff) continue;
/* Update internal status if the event could be pushed */
driver_event_t ev = { time, diff, row, KEYEV_UP };
if(!buffer_push(ev)) state[row] &= scan[row];
}
for(int row = 0; row < 12; row++)
{
int diff = scan[row] & ~state[row];
if(!diff) continue;
driver_event_t ev = { time, diff, row, KEYEV_DOWN };
if(!buffer_push(ev)) state[row] |= scan[row];
}
} }
/* pollevent() - poll the next keyboard event */ /* pollevent() - poll the next keyboard event */
key_event_t pollevent(void) key_event_t pollevent(void)
{ {
/* Every time a driver event is unqueued, its key events are generated return keydev_unqueue_event(&dev_keysc);
and put in this small buffer */
static key_event_t events[8];
/* Number of pending events in the previous buffer */
static int events_pending = 0;
/* Use pending events first, if they exist */
if(events_pending > 0)
{
key_event_t ev = events[--events_pending];
/* Update the current state buffer according to [ev] */
int row = (ev.key >> 4) ^ 1;
int col = 0x80 >> (ev.key & 0x7);
if(ev.type == KEYEV_DOWN) current[row] |= col;
if(ev.type == KEYEV_UP) current[row] &= ~col;
return ev;
}
/* If not key event is pending, generate a chunk by polling the driver
event queue */
driver_event_t ev;
if(buffer_poll(&ev))
{
key_event_t ev = { .type = KEYEV_NONE, .time = time };
return ev;
}
/* Generate new key events and return the first of them*/
int changed = ev.changed;
for(int code = ((ev.row ^ 1) << 4) | 0x7; code & 0x7; code--)
{
if(changed & 1)
{
key_event_t keyev = {
.time = ev.time,
.type = ev.kind,
.key = code,
};
events[events_pending++] = keyev;
}
changed >>= 1;
}
/* Call recursively to avoid duplicating the event emission code */
return pollevent();
} }
/* waitevent() - wait for the next keyboard event */ /* waitevent() - wait for the next keyboard event */
@ -223,7 +101,7 @@ key_event_t waitevent(volatile int *timeout)
sleep(); sleep();
} }
key_event_t ev = { .type = KEYEV_NONE, .time = time }; key_event_t ev = { .type = KEYEV_NONE, .time = dev_keysc.time };
return ev; return ev;
} }
@ -240,10 +118,7 @@ void clearevents(void)
/* keydown(): Current key state */ /* keydown(): Current key state */
int keydown(int key) int keydown(int key)
{ {
int row = (key >> 4) ^ 1; return keydev_keydown(&dev_keysc, key);
int col = 0x80 >> (key & 0x7);
return (current[row] & col) != 0;
} }
/* keydown_all(): Check a set of keys for simultaneous input /* keydown_all(): Check a set of keys for simultaneous input
@ -288,21 +163,15 @@ int keydown_any(int key, ...)
// Driver initialization // Driver initialization
//--- //---
static int callback(void)
{
keysc_frame();
time++;
return 0;
}
/* init() - setup the support timer */
static void init(void) static void init(void)
{ {
keydev_init(&dev_keysc);
/* Set the default repeat times (milliseconds) */ /* Set the default repeat times (milliseconds) */
getkey_repeat(400, 40); getkey_repeat(400, 40);
/* The timer will be stopped when the timer driver is unloaded */ /* The timer will be stopped when the timer driver is unloaded */
keysc_tid = timer_setup(TIMER_ANY,keysc_scan_frequency_us(),callback); keysc_tid = timer_setup(TIMER_ANY, scan_frequency_us, keysc_tick);
if(keysc_tid >= 0) timer_start(keysc_tid); if(keysc_tid >= 0) timer_start(keysc_tid);
gint[HWKBD] = HW_LOADED | (isSH3() ? HWKBD_IO : HWKBD_KSI); gint[HWKBD] = HW_LOADED | (isSH3() ? HWKBD_IO : HWKBD_KSI);