gint/src/keysc/keysc.c

310 lines
7.4 KiB
C
Raw Normal View History

//---
// gint:keysc - The SH7305 and I/O Key Scan Interfaces
//---
#include <gint/drivers.h>
#include <gint/gint.h>
#include <gint/timer.h>
#include <gint/clock.h>
#include <gint/keyboard.h>
#include <gint/defs/attributes.h>
#include <gint/defs/types.h>
#include <gint/drivers/iokbd.h>
#include <gint/hardware.h>
#include <stdarg.h>
//---
// Keyboard buffer
//---
/* 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
core, tmu: add gint_switch(), return to menu, and improve timer code * Add the gint_switch() function which executes user-provided code from the system (CASIOWIN) context. * Added interrupt masks to the core context (should have been there long ago). * Added the gint_osmenu() function that switches out of gint to invoke GetKeyWait() and inject KEY_CTRL_MENU to trigger the main menu. This uses many CASIOWIN syscalls, but we don't care because gint is unloaded. Trickery is used to catch the key following the return in the add-in and/or display a new application frame before GetKeyWait() even finishes after coming back. This is only available on fx9860g for now. * Removed any public syscall definition to clear up interfaces. * Patched the DMA interruption problem in a weird way on fxcg50, a driver function will be used to do that properly eventually. * Changed the driver model to save driver contexts in preallocated spaces instead of on the stack for overall less risk. * Enabled return-to-menu with the MENU key on fx9860g in getkey(). * Changed the keyboard driver to emit releases before presses, as a return-to-menu acts as a press+release of different keys in a single driver frame, which confuses getkey(). * Fixed a really stupid bug in memcpy() that made the function really not work. Improvements in the timer driver: * Expose ETMU modules as SH7705_TMU and SH7305_TMU in <gint/mpu/tmu.h>. * Remove the timer_t structures, using SH*_ETMU and SH*_TMU instead. Only interrupt gate entries are left hardcoded. * Discovered that not only every write to the TCNT or TCR of an ETMU takes about 1/32k of a second (hinting at registers being powered by the same clock as the timer), but every write occuring while a previous write is pending is *lost*. This led to terrible bugs when switching ETMU contexts too fast in gint_switch(). * Removed an internal timer_address() function. * Overall simplified the handling of timers and the initialization step.
2020-05-10 14:03:41 +02:00
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.) */
2019-07-17 00:34:10 +02:00
GDATA 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. */
GDATA static uint8_t current[12] = { 0 };
core, tmu: add gint_switch(), return to menu, and improve timer code * Add the gint_switch() function which executes user-provided code from the system (CASIOWIN) context. * Added interrupt masks to the core context (should have been there long ago). * Added the gint_osmenu() function that switches out of gint to invoke GetKeyWait() and inject KEY_CTRL_MENU to trigger the main menu. This uses many CASIOWIN syscalls, but we don't care because gint is unloaded. Trickery is used to catch the key following the return in the add-in and/or display a new application frame before GetKeyWait() even finishes after coming back. This is only available on fx9860g for now. * Removed any public syscall definition to clear up interfaces. * Patched the DMA interruption problem in a weird way on fxcg50, a driver function will be used to do that properly eventually. * Changed the driver model to save driver contexts in preallocated spaces instead of on the stack for overall less risk. * Enabled return-to-menu with the MENU key on fx9860g in getkey(). * Changed the keyboard driver to emit releases before presses, as a return-to-menu acts as a press+release of different keys in a single driver frame, which confuses getkey(). * Fixed a really stupid bug in memcpy() that made the function really not work. Improvements in the timer driver: * Expose ETMU modules as SH7705_TMU and SH7305_TMU in <gint/mpu/tmu.h>. * Remove the timer_t structures, using SH*_ETMU and SH*_TMU instead. Only interrupt gate entries are left hardcoded. * Discovered that not only every write to the TCNT or TCR of an ETMU takes about 1/32k of a second (hinting at registers being powered by the same clock as the timer), but every write occuring while a previous write is pending is *lost*. This led to terrible bugs when switching ETMU contexts too fast in gint_switch(). * Removed an internal timer_address() function. * Overall simplified the handling of timers and the initialization step.
2020-05-10 14:03:41 +02:00
/* A driver event, which is a common change in a full row */
typedef struct
{
uint16_t time; /* Locally unique time identifier */
uint8_t row; /* Row number */
uint8_t changed; /* Keys that changed state */
core, tmu: add gint_switch(), return to menu, and improve timer code * Add the gint_switch() function which executes user-provided code from the system (CASIOWIN) context. * Added interrupt masks to the core context (should have been there long ago). * Added the gint_osmenu() function that switches out of gint to invoke GetKeyWait() and inject KEY_CTRL_MENU to trigger the main menu. This uses many CASIOWIN syscalls, but we don't care because gint is unloaded. Trickery is used to catch the key following the return in the add-in and/or display a new application frame before GetKeyWait() even finishes after coming back. This is only available on fx9860g for now. * Removed any public syscall definition to clear up interfaces. * Patched the DMA interruption problem in a weird way on fxcg50, a driver function will be used to do that properly eventually. * Changed the driver model to save driver contexts in preallocated spaces instead of on the stack for overall less risk. * Enabled return-to-menu with the MENU key on fx9860g in getkey(). * Changed the keyboard driver to emit releases before presses, as a return-to-menu acts as a press+release of different keys in a single driver frame, which confuses getkey(). * Fixed a really stupid bug in memcpy() that made the function really not work. Improvements in the timer driver: * Expose ETMU modules as SH7705_TMU and SH7305_TMU in <gint/mpu/tmu.h>. * Remove the timer_t structures, using SH*_ETMU and SH*_TMU instead. Only interrupt gate entries are left hardcoded. * Discovered that not only every write to the TCNT or TCR of an ETMU takes about 1/32k of a second (hinting at registers being powered by the same clock as the timer), but every write occuring while a previous write is pending is *lost*. This led to terrible bugs when switching ETMU contexts too fast in gint_switch(). * Removed an internal timer_address() function. * Overall simplified the handling of timers and the initialization step.
2020-05-10 14:03:41 +02:00
uint8_t kind; /* 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 */
GDATA static int buffer_start = 0;
GDATA static int buffer_end = 0;
/* Current time, in keyboard-scanning ticks */
GDATA 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;
}
/* keysc_frame(): Generate driver events from KEYSC state */
2019-07-17 00:34:10 +02:00
static void keysc_frame(void)
{
GALIGNED(2) uint8_t scan[12] = { 0 };
/* First scan the key matrix: from I/O ports on SH3, KEYSC on SH4 */
if(isSH3()) iokbd_scan(scan);
else
{
volatile uint16_t *KEYSC = (void *)0xa44b0000;
uint16_t *array = (void *)&scan;
for(int i = 0; i < 6; i++) array[i] = KEYSC[i];
}
core, tmu: add gint_switch(), return to menu, and improve timer code * Add the gint_switch() function which executes user-provided code from the system (CASIOWIN) context. * Added interrupt masks to the core context (should have been there long ago). * Added the gint_osmenu() function that switches out of gint to invoke GetKeyWait() and inject KEY_CTRL_MENU to trigger the main menu. This uses many CASIOWIN syscalls, but we don't care because gint is unloaded. Trickery is used to catch the key following the return in the add-in and/or display a new application frame before GetKeyWait() even finishes after coming back. This is only available on fx9860g for now. * Removed any public syscall definition to clear up interfaces. * Patched the DMA interruption problem in a weird way on fxcg50, a driver function will be used to do that properly eventually. * Changed the driver model to save driver contexts in preallocated spaces instead of on the stack for overall less risk. * Enabled return-to-menu with the MENU key on fx9860g in getkey(). * Changed the keyboard driver to emit releases before presses, as a return-to-menu acts as a press+release of different keys in a single driver frame, which confuses getkey(). * Fixed a really stupid bug in memcpy() that made the function really not work. Improvements in the timer driver: * Expose ETMU modules as SH7705_TMU and SH7305_TMU in <gint/mpu/tmu.h>. * Remove the timer_t structures, using SH*_ETMU and SH*_TMU instead. Only interrupt gate entries are left hardcoded. * Discovered that not only every write to the TCNT or TCR of an ETMU takes about 1/32k of a second (hinting at registers being powered by the same clock as the timer), but every write occuring while a previous write is pending is *lost*. This led to terrible bugs when switching ETMU contexts too fast in gint_switch(). * Removed an internal timer_address() function. * Overall simplified the handling of timers and the initialization step.
2020-05-10 14:03:41 +02:00
/* Compare new data with the internal state. Push releases before
presses so that a key change occuring 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++)
{
core, tmu: add gint_switch(), return to menu, and improve timer code * Add the gint_switch() function which executes user-provided code from the system (CASIOWIN) context. * Added interrupt masks to the core context (should have been there long ago). * Added the gint_osmenu() function that switches out of gint to invoke GetKeyWait() and inject KEY_CTRL_MENU to trigger the main menu. This uses many CASIOWIN syscalls, but we don't care because gint is unloaded. Trickery is used to catch the key following the return in the add-in and/or display a new application frame before GetKeyWait() even finishes after coming back. This is only available on fx9860g for now. * Removed any public syscall definition to clear up interfaces. * Patched the DMA interruption problem in a weird way on fxcg50, a driver function will be used to do that properly eventually. * Changed the driver model to save driver contexts in preallocated spaces instead of on the stack for overall less risk. * Enabled return-to-menu with the MENU key on fx9860g in getkey(). * Changed the keyboard driver to emit releases before presses, as a return-to-menu acts as a press+release of different keys in a single driver frame, which confuses getkey(). * Fixed a really stupid bug in memcpy() that made the function really not work. Improvements in the timer driver: * Expose ETMU modules as SH7705_TMU and SH7305_TMU in <gint/mpu/tmu.h>. * Remove the timer_t structures, using SH*_ETMU and SH*_TMU instead. Only interrupt gate entries are left hardcoded. * Discovered that not only every write to the TCNT or TCR of an ETMU takes about 1/32k of a second (hinting at registers being powered by the same clock as the timer), but every write occuring while a previous write is pending is *lost*. This led to terrible bugs when switching ETMU contexts too fast in gint_switch(). * Removed an internal timer_address() function. * Overall simplified the handling of timers and the initialization step.
2020-05-10 14:03:41 +02:00
int diff = ~scan[row] & state[row];
if(!diff) continue;
/* Update internal status if the event could be pushed */
core, tmu: add gint_switch(), return to menu, and improve timer code * Add the gint_switch() function which executes user-provided code from the system (CASIOWIN) context. * Added interrupt masks to the core context (should have been there long ago). * Added the gint_osmenu() function that switches out of gint to invoke GetKeyWait() and inject KEY_CTRL_MENU to trigger the main menu. This uses many CASIOWIN syscalls, but we don't care because gint is unloaded. Trickery is used to catch the key following the return in the add-in and/or display a new application frame before GetKeyWait() even finishes after coming back. This is only available on fx9860g for now. * Removed any public syscall definition to clear up interfaces. * Patched the DMA interruption problem in a weird way on fxcg50, a driver function will be used to do that properly eventually. * Changed the driver model to save driver contexts in preallocated spaces instead of on the stack for overall less risk. * Enabled return-to-menu with the MENU key on fx9860g in getkey(). * Changed the keyboard driver to emit releases before presses, as a return-to-menu acts as a press+release of different keys in a single driver frame, which confuses getkey(). * Fixed a really stupid bug in memcpy() that made the function really not work. Improvements in the timer driver: * Expose ETMU modules as SH7705_TMU and SH7305_TMU in <gint/mpu/tmu.h>. * Remove the timer_t structures, using SH*_ETMU and SH*_TMU instead. Only interrupt gate entries are left hardcoded. * Discovered that not only every write to the TCNT or TCR of an ETMU takes about 1/32k of a second (hinting at registers being powered by the same clock as the timer), but every write occuring while a previous write is pending is *lost*. This led to terrible bugs when switching ETMU contexts too fast in gint_switch(). * Removed an internal timer_address() function. * Overall simplified the handling of timers and the initialization step.
2020-05-10 14:03:41 +02:00
driver_event_t ev = { time, row, diff, 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, row, diff, KEYEV_DOWN };
if(!buffer_push(ev)) state[row] |= scan[row];
}
}
/* pollevent() - poll the next keyboard event */
key_event_t pollevent(void)
{
/* Every time a driver event is unqueued, its key events are generated
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,
core, tmu: add gint_switch(), return to menu, and improve timer code * Add the gint_switch() function which executes user-provided code from the system (CASIOWIN) context. * Added interrupt masks to the core context (should have been there long ago). * Added the gint_osmenu() function that switches out of gint to invoke GetKeyWait() and inject KEY_CTRL_MENU to trigger the main menu. This uses many CASIOWIN syscalls, but we don't care because gint is unloaded. Trickery is used to catch the key following the return in the add-in and/or display a new application frame before GetKeyWait() even finishes after coming back. This is only available on fx9860g for now. * Removed any public syscall definition to clear up interfaces. * Patched the DMA interruption problem in a weird way on fxcg50, a driver function will be used to do that properly eventually. * Changed the driver model to save driver contexts in preallocated spaces instead of on the stack for overall less risk. * Enabled return-to-menu with the MENU key on fx9860g in getkey(). * Changed the keyboard driver to emit releases before presses, as a return-to-menu acts as a press+release of different keys in a single driver frame, which confuses getkey(). * Fixed a really stupid bug in memcpy() that made the function really not work. Improvements in the timer driver: * Expose ETMU modules as SH7705_TMU and SH7305_TMU in <gint/mpu/tmu.h>. * Remove the timer_t structures, using SH*_ETMU and SH*_TMU instead. Only interrupt gate entries are left hardcoded. * Discovered that not only every write to the TCNT or TCR of an ETMU takes about 1/32k of a second (hinting at registers being powered by the same clock as the timer), but every write occuring while a previous write is pending is *lost*. This led to terrible bugs when switching ETMU contexts too fast in gint_switch(). * Removed an internal timer_address() function. * Overall simplified the handling of timers and the initialization step.
2020-05-10 14:03:41 +02:00
.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 */
key_event_t waitevent(volatile int *timeout)
{
2019-07-17 00:23:21 +02:00
while(1)
{
key_event_t ev = pollevent();
if(ev.type != KEYEV_NONE) return ev;
2019-07-17 00:23:21 +02:00
if(timeout && *timeout) break;
sleep();
}
key_event_t ev = { .type = KEYEV_NONE, .time = time };
return ev;
}
/* clearevents(): Read all events waiting in the queue */
void clearevents(void)
{
while(pollevent().type != KEYEV_NONE);
}
//---
// Immediate key access
//---
/* keydown(): Current key state */
int keydown(int key)
{
int row = (key >> 4) ^ 1;
int col = 0x80 >> (key & 0x7);
return (current[row] & col) != 0;
}
/* keydown_all(): Check a set of keys for simultaneous input
Returns non-zero if all provided keys are down. The list should end with an
integer 0 as terminator. */
int keydown_all(int key, ...)
{
va_list args;
va_start(args, key);
int st = 1;
while(key && st)
{
st = keydown(key);
key = va_arg(args, int);
}
va_end(args);
return st;
}
/* keydown_any(): Check a set of keys for any input
Returns nonzero if any one of the specified keys is currently pressed. THe
sequence should be terminated by a 0 integer. */
int keydown_any(int key, ...)
{
va_list args;
va_start(args, key);
int st = 0;
while(key && !st)
{
st = keydown(key);
key = va_arg(args, int);
}
va_end(args);
return st;
}
//---
// Driver initialization and status
//---
static int callback(GUNUSED volatile void *arg)
{
keysc_frame();
time++;
return 0;
}
/* init() - setup the support timer */
static void init(void)
{
int tid = isSH3() ? 3 : 8;
/* Configure the timer to do 128 keyboard scans per second. This
frequency *must* be high for the KEYSC interface to work! */
/* Note: the supporting timer always runs at 32768 Hz. */
int delay = 32768 / KEYBOARD_SCAN_FREQUENCY;
if(!delay) delay = 1;
2019-07-17 00:23:21 +02:00
/* Set the default repeat times (milliseconds) */
2019-07-18 01:29:12 +02:00
getkey_repeat(400, 40);
2019-07-17 00:23:21 +02:00
timer_setup(tid, delay, 0, callback, NULL);
timer_start(tid);
gint[HWKBD] = HW_LOADED | (isSH3() ? HWKBD_IO : HWKBD_KSI);
gint[HWKBDSF] = KEYBOARD_SCAN_FREQUENCY;
}
/* unload() - stop the support timer */
static void unload(void)
{
int tid = isSH3() ? 3 : 8;
timer_stop(tid);
}
#ifdef GINT_BOOT_LOG
/* keysc_status() - status string of the driver */
static const char *keysc_status(void)
{
static char str[3] = "Sw";
if(isSH3()) str[0] = 's';
return str;
}
#endif /* GINT_BOOT_LOG */
//---
// Driver structure definition
//---
gint_driver_t drv_keysc = {
.name = "KEYSC",
.init = init,
.status = GINT_DRIVER_STATUS(keysc_status),
.unload = unload,
};
GINT_DECLARE_DRIVER(4, drv_keysc);