gint/src/t6k11/t6k11.c

264 lines
5.7 KiB
C
Raw Normal View History

//---
// gint:t6k11 - Toshiba T6K11 driver
//---
#include <gint/drivers.h>
#include <gint/drivers/t6k11.h>
#include <gint/defs/attributes.h>
#include <gint/defs/types.h>
#include <gint/hardware.h>
//---
// Device specification sheet
//---
/* This version number is 1 for the old T6K11 everyone knows, and 2 for the
newer one found in the Graph 35+E II. Documentation is available only for
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
version 1. Dumps of Bdisp_PutDisp_DD() are used to drive version 2. */
static int t6k11_version = 1;
/* Screen registers on the original T6K11. Registers 8..11 and 13..15 are test
registers and must not be used! */
enum {
reg_display = 0,
reg_counter = 1,
reg_analog = 2,
reg_alternate = 3,
reg_yaddr = 4, /* These two use the same register */
reg_xaddr = 4, /* (interpretation depends on count mode) */
reg_zaddr = 5,
reg_contrast = 6,
reg_data = 7,
reg_daconv = 12,
};
/* Automatic counter increment during read/write */
enum {
cnt_ydown = 0,
cnt_yup = 1,
cnt_xdown = 2,
cnt_xup = 3,
};
//---
// Device communication primitives
//---
/* I/O may be performed either with RS = 0 or RS = 1. The memory-mapping of the
device I/O maps bit 16 of the address to pin RS. There may be other mapped
pins in the address. (?) */
/* RS = 0: Register selection */
GDATA static volatile uint8_t *sel = (void *)0xb4000000;
/* RS = 1: Command data or vram data */
GDATA static volatile uint8_t *cmd = (void *)0xb4010000;
/* command() - send a command to set the value of a register
@reg Register number
@data Value to set in reg */
GINLINE static void command(uint8_t reg, uint8_t data)
{
*sel = reg;
*cmd = data;
}
/* status() - read the status byte from the display driver */
GINLINE static uint8_t status(void)
{
return *sel;
}
/* write_row() - send 16 bytes to the display driver
@buf Buffer to take data from */
GINLINE static void write_row(const uint8_t *buf)
{
/* Unroll the loop for more speed */
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
*cmd = *buf++;
}
//---
// Driver functions
//---
/* t6k11_display() - send vram data to the LCD device */
void t6k11_display_v1(const void *vram, int y1, int y2, size_t stride)
{
for(int y = y1; y < y2; y++)
{
/* Set the X-address register for this row */
command(reg_xaddr, y | 0xc0);
/* Use Y-Up mode */
command(reg_counter, cnt_yup);
/* Start counting Y from 0 */
command(reg_yaddr, 0);
/* Send the row's data to the device */
*sel = reg_data;
write_row(vram);
vram += stride;
}
}
void t6k11_display_v2(const void *vram, int y1, int y2, size_t stride)
{
for(int y = y1; y < y2; y++)
{
command(8, y | 0x80);
command(8, 4);
*sel = 10;
write_row(vram);
vram += stride;
}
}
void t6k11_display(const void *vram, int y1, int y2, size_t stride)
{
if(t6k11_version == 1) t6k11_display_v1(vram, y1, y2, stride);
if(t6k11_version == 2) t6k11_display_v2(vram, y1, y2, stride);
}
/* t6k11_contrast() - change the contrast setting */
void t6k11_contrast(int contrast)
{
if(contrast < 0) contrast = 0;
if(contrast > 32) contrast = 32;
/* Reasonable values for contrast, taken from the diagnostic mode of an
fx9860G II with OS 02.05.2201, are in range 0x97 .. 0xb7.
This means VLC0 = 2 and CONTRAST in [23..55] */
command(reg_contrast, 0x97 + contrast);
/* TODO: Turns out that different models, notably screens without
TODO: backlight, will have different ranges. Plus we might want to
TODO: use transitions to extreme contrast settings for visual
TODO: effects.
TODO: Extend the available range of contrast settings. */
}
/* t6k11_backlight() - manage the screen backlight */
void t6k11_backlight(int setting)
{
volatile uint8_t *port;
uint8_t mask;
/* This setting is mapped to an I/O port:
- On SH3, bit 7 of port G
- On SH4, bit 4 of port N */
if(isSH3())
{
port = (void *)0xa400012c;
mask = 0x80;
}
else
{
port = (void *)0xa4050138;
mask = 0x10;
}
if(!setting) *port &= ~mask;
if(setting > 0) *port |= mask;
if(setting < 0) *port ^= mask;
}
//---
// Context system for this driver
//---
typedef struct
{
/* Some status bits, obtained by using the STRD command */
uint8_t strd;
/* There *are* other parameters that are affected by the driver, but
they cannot be read, so I can't determine the system's setting */
} GPACKED(1) ctx_t;
/* Pre-allocate a context in gint's uninitialized section */
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
GBSS static ctx_t sys_ctx, gint_ctx;
static void ctx_save(void *buf)
{
if(gint[HWCALC] == HWCALC_G35PE2) return;
ctx_t *ctx = buf;
ctx->strd = status();
}
static void ctx_restore(void *buf)
{
if(gint[HWCALC] == HWCALC_G35PE2) return;
ctx_t *ctx = buf;
/* Set an X-address of 0 with the original display mode */
uint8_t nf = (ctx->strd & 0x04) >> 2;
command(reg_xaddr, 0x80 | (nf << 6));
/* Restore the counter mode */
uint8_t cnt = (ctx->strd & 0x03);
command(reg_counter, cnt);
}
//---
// Driver initialization
//---
static void init(void)
{
gint[HWDD] = HW_LOADED | HWDD_LIGHT;
if(gint[HWCALC] == HWCALC_G35PE2) t6k11_version = 2;
}
//---
// Driver status string
//---
#ifdef GINT_BOOT_LOG
/* t6k11_status() - status string of the driver */
static const char *t6k11_status(void)
{
/* TODO: t6k11: Detect backlight existence */
return "6K11-cB";
}
#endif /* GINT_BOOT_LOG */
//---
// Driver structure definition
//---
gint_driver_t drv_t6k11 = {
.name = "T6K11",
.init = init,
.status = GINT_DRIVER_STATUS(t6k11_status),
.sys_ctx = &sys_ctx,
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
.gint_ctx = &gint_ctx,
.ctx_save = ctx_save,
.ctx_restore = ctx_restore,
};
GINT_DECLARE_DRIVER(5, drv_t6k11);