collapseos/emul/z80/t6a04.c

143 lines
4.0 KiB
C

#include <string.h>
#include "t6a04.h"
void t6a04_init(T6A04 *lcd)
{
memset(lcd->ram, 0, T6A04_RAMSIZE);
lcd->enabled = false;
lcd->incmode = T6A04_XINC;
lcd->offset = 0;
lcd->currow = 0;
lcd->curcol = 0;
lcd->just_moved = true;
lcd->has8bitmode = false;
}
uint8_t t6a04_cmd_rd(T6A04 *lcd)
{
return 0; // we are always ready for a new cmd
}
/*
* 0x00/0x01: 6/8 bit mode
* 0x02/0x03: enable/disable
* 0x04-0x07: incmodes
* 0x20-0x34: set col
* 0x40-0x7f: set Z offset
* 0x80-0xbf: set row
* 0xc0-0xff: set contrast
*/
void t6a04_cmd_wr(T6A04 *lcd, uint8_t val)
{
if ((val & 0xc0) == 0xc0) {
// contrast, ignoring
} else if (val & 0x80) {
lcd->currow = val & 0x3f;
lcd->just_moved = true;
} else if (val & 0x40) {
lcd->offset = val & 0x3f;
} else if (val & 0x20) {
lcd->curcol = val & 0x1f;
lcd->just_moved = true;
} else if (val & 0x18) {
// stuff we don't emulate
} else if (val & 0x04) {
lcd->incmode = val & 0x03;
} else if (val & 0x02) {
lcd->enabled = val & 0x01;
} else {
lcd->has8bitmode = val;
}
}
// Advance current position according to current incmode
static void _advance(T6A04 *lcd)
{
uint8_t maxY = lcd->has8bitmode ? 14 : 19;
switch (lcd->incmode) {
case T6A04_XDEC:
lcd->currow = (lcd->currow-1) & 0x3f;
break;
case T6A04_XINC:
lcd->currow = (lcd->currow+1) & 0x3f;
break;
case T6A04_YDEC:
if (lcd->curcol == 0) {
lcd->curcol = maxY;
} else {
lcd->curcol--;
}
break;
case T6A04_YINC:
if (lcd->curcol < maxY) {
lcd->curcol++;
} else {
lcd->curcol = 0;
}
break;
}
}
uint8_t t6a04_data_rd(T6A04 *lcd)
{
uint8_t res;
if (lcd->just_moved) {
// After a move command, the first read op is a noop.
lcd->just_moved = false;
return 0;
}
if (lcd->has8bitmode) {
int pos = lcd->currow * T6A04_ROWSIZE + lcd->curcol;
res = lcd->ram[pos];
} else {
// 6bit mode is a bit more complicated because the 6-bit number often
// spans two bytes. We manage this by loading two bytes into a uint16_t
// and then shift it right properly.
// bitpos represents the leftmost bit of our 6bit number.
int bitpos = lcd->curcol * 6;
// offset represents the shift right we need to perform from the two
// bytes following bitpos/8 so that we can have our number with a 6-bit
// mask.
// Example, col 3 has a bitpos of 18, which means that it loads bytes 2
// and 3. Its bits would be in bit pos 14:8, which means it has an
// offset of 8. There is always an offset and its always in the 3-10
// range
int offset = 10 - (bitpos % 8); // 10 is for 16bit - 6bit
int pos = (lcd->currow * T6A04_ROWSIZE) + (bitpos / 8);
uint16_t word = lcd->ram[pos] << 8;
word |= lcd->ram[pos+1];
res = (word >> offset) & 0x3f;
}
_advance(lcd);
return res;
}
void t6a04_data_wr(T6A04 *lcd, uint8_t val)
{
lcd->just_moved = false;
if (lcd->has8bitmode) {
int pos = lcd->currow * T6A04_ROWSIZE + lcd->curcol;
lcd->ram[pos] = val;
} else {
// See comments in t6a04_data_rd().
int bitpos = lcd->curcol * 6;
int offset = 10 - (bitpos % 8);
int pos = (lcd->currow * T6A04_ROWSIZE) + (bitpos / 8);
uint16_t word = lcd->ram[pos] << 8;
word |= lcd->ram[pos+1];
// word contains our current ram value. Let's fit val in this.
word &= ~(0x003f << offset);
word |= val << offset;
lcd->ram[pos] = word >> 8;
lcd->ram[pos+1] = word & 0xff;
}
_advance(lcd);
}
bool t6a04_pixel(T6A04 *lcd, uint8_t y, uint8_t x)
{
x = (x + lcd->offset) & 0x3f;
uint8_t val = lcd->ram[x * T6A04_ROWSIZE + (y / 8)];
return (val >> (7 - (y % 8))) & 1;
}