gint/src/r61523/r61523.c
2025-07-02 16:59:35 +08:00

318 lines
7.1 KiB
C

// Renesas R61523 driver-
#include <gint/defs/types.h>
#include <gint/defs/util.h>
// #include <gint/hardware.h>
#include <gint/drivers.h>
// #include <gint/dma.h>
#include <gint/drivers/r61523.h>
#include <gint/video.h>
#include <gint/image.h>
#include <gint/config.h>
#if GINT_HW_CP
/* Registers */
#define REG_HRANGE 0x2a
#define REG_VRANGE 0x2b
#define REG_DATA 0x2c
#define REG_BACKLIGHT_CONTROL 0xb9
#define REG_DEVICE_CODE_READ 0xbf
#define REG_DEVICE_CODE_VARIANT 0xda
/* Interface with the controller */
static volatile uint16_t *DISPLAY = (void *)0xb4000000;
/* Bit 4 of Port R controls the RS bit of the display driver */
static volatile uint8_t *PRDR = (void *)0xa405013c;
/* Screen variant information
* This number is 0x00 for the old R61523 that everyone knows, and 0x16 or
* 0x52 for the newer ones found in recent FXCP400 devices. */
static uint16_t r61523_variant = 0;
/* Select a register */
GINLINE static void select(uint16_t reg)
{
/* Clear RS and write the register number */
*PRDR &= ~0x10;
synco();
*DISPLAY = reg;
synco();
/* Set RS=1 to allow consecutive reads/writes after a select() */
*PRDR |= 0x10;
synco();
}
GINLINE static void write(uint16_t data)
{
*DISPLAY = data;
}
GINLINE static uint16_t read(void)
{
return *DISPLAY;
}
static void read_Nu16(uint16_t *array, int N)
{
for(int i = 0; i < N; i++)
array[i] = *DISPLAY;
}
//---
// Generic functions
//---
/* r61523_identify() - identify screen hardware information
*
* notes
* Since recent versions of the FXCP400, new screens are used that break
* the current driver. These screens are particular since they have the
* same manufacturer / device code, but they use an undocumented register
* (0xda) to determine the variant.
*
* The register 0xda should be read twice. Casio ignores the first reading
* and only keep track of the second. So, let's do the same here. */
void r61523_identify(
uint32_t *manufacturerCode,
uint16_t *deviceCode,
uint16_t *variant
) {
uint16_t packets[5];
if (manufacturerCode != NULL || deviceCode != NULL)
{
select(REG_DEVICE_CODE_READ);
read_Nu16(packets, 5);
if(manufacturerCode)
*manufacturerCode = (packets[1] << 16) | packets[2];
if(deviceCode)
*deviceCode = (packets[3] << 16) | packets[4];
}
if (variant)
{
select(REG_DEVICE_CODE_VARIANT);
*variant = read();
*variant = read();
}
}
//---
// Display control
//---
void r61523_get_backlight(bool *EN2, int *level, int *PWM_div, bool *dimming)
{
int8_t volatile *PNDR = (void *)0xa4050138;
*EN2 = *PNDR & 0x10;
uint16_t packets[5];
select(REG_BACKLIGHT_CONTROL);
read_Nu16(packets, 5);
*level = packets[2];
*PWM_div = packets[3];
*dimming = packets[4] & 1;
}
void r61523_set_backlight(bool EN2, int level, int PWM_div, bool dimming)
{
int8_t volatile *PNDR = (void *)0xa4050138;
if(EN2)
*PNDR |= 0x10;
else
*PNDR &= 0xef;
synco();
select(REG_BACKLIGHT_CONTROL);
/* Default value of PWMON */
write(0);
synco();
write(level);
synco();
write(PWM_div);
synco();
write(0b1000 | dimming);
synco();
}
void r61523_get_display_timing(bool mode, bool *waveform, int *CPL, int *BP, int *FP)
{
uint16_t packets[6];
select(mode ? 195 : 193);
read_Nu16(packets, 6);
*waveform = packets[1];
*CPL = packets[3];
*BP = packets[4];
*FP = packets[5];
}
void r61523_set_display_timing(bool mode, bool waveform, int CPL, int BP, int FP)
{
select(mode ? 195 : 193);
write(waveform);
synco();
read();
write(CPL);
synco();
write(BP);
synco();
write(FP);
synco();
}
//---
// Window management
//---
void r61523_win_set(int x1, int x2, int y1, int y2)
{
if (r61523_variant != 0x16 || r61523_variant != 0x52)
{
/* R61523 has a 360x640 area; the CP-400 uses the top-right corner
* for its 320x528 display, so skip over the first 40 columns */
x1 += 40;
x2 += 40;
}
uint16_t volatile *DISPLAY = (void *)0xb4000000;
select(REG_HRANGE);
/* Upper half has 2 bits (total 10 bits = 1024) */
*DISPLAY = (x1 >> 8) & 3;
synco();
*DISPLAY = x1 & 0xff;
*DISPLAY = (x2 >> 8) & 3;
synco();
*DISPLAY = x2 & 0xff;
synco();
select(REG_VRANGE);
*DISPLAY = (y1 >> 8) & 3;
synco();
*DISPLAY = y1 & 0xff;
synco();
*DISPLAY = (y2 >> 8) & 3;
synco();
*DISPLAY = y2 & 0xff;
synco();
}
void r61523_display(uint16_t *vram)
{
r61523_win_set(0, 319, 0, 527);
select(44);
int row_offset = 0;
uint16_t volatile *DISPLAY = (void *)0xb4000000;
for(int y = 0; y < 528; y++) {
uint16_t *r5 = (void *)vram + row_offset;
for(int x = 0; x < 320; x++)
*DISPLAY = *r5++;
row_offset += 2 * 320;
}
}
void r61523_display_rect(
uint16_t *vram, int xmin, int xmax, int ymin, int ymax)
{
// dma_transfer_wait(0);
r61523_win_set(xmin, xmax, ymin, ymax);
select(44);
vram += 320 * ymin + xmin;
uint16_t volatile *DISPLAY = (void *)0xb4000000;
for(int y = 0; y < ymax - ymin + 1; y++) {
for(int x = 0; x < xmax - xmin + 1; x++)
*DISPLAY = vram[x];
vram += 320;
}
}
void r61523_set_pixel(int x, int y, int color)
{
if((unsigned)x >= 320 || (unsigned)y >= 528)
return;
// dma_transfer_wait(0);
r61523_win_set(x, x, y, y);
select(44);
uint16_t volatile *DISPLAY = (void *)0xb4000000;
*DISPLAY = color;
}
static bool r61523_update(int x, int y, image_t const *fb, int flags)
{
if(fb->format != IMAGE_RGB565)
return false;
// TODO: r61523_update: DMA support
// unless VIDEO_UPDATE_FOREIGN_WORLD is set
(void)flags;
uint w = fb->width;
uint h = fb->height;
// TODO: r61523_update: sub-rectangle support
if(x != 0 || y != 0 || w != 320 || h != 528)
return false;
// TODO: r61523_update: stride support!
if(fb->stride != 320 * 2)
return false;
r61523_display(fb->data);
return true;
}
//---
// Driver metadata
//---
/* constructor() - determine which variant of screen we have */
static void constructor(void)
{
r61523_identify(NULL, NULL, &r61523_variant);
}
/* As far as I can tell there's no way to read the current window from the
controller so this driver is completely stateless for now. */
gint_driver_t drv_r61523 = {
.name = "R61523",
.constructor = constructor,
};
GINT_DECLARE_DRIVER(26, drv_r61523);
//---
// Video driver interface
//---
static video_mode_t r61523_modes[] = {
/* Standard full-screen full-color mode */
{ 320, 528, IMAGE_RGB565, -1 },
{ 0 }
};
video_interface_t r61523_video = {
.driver = &drv_r61523,
.modes = r61523_modes,
.mode_get = NULL, // TODO
.mode_set = NULL, // TODO
.brightness_min = 0, // TODO
.brightness_max = 0, // TODO
.brightness_set = NULL,
.update = r61523_update,
};
#endif /* GINT_HW_CP */