// license:GPL-2.0+
// copyright-holders:Daniel Coulom,Sandro Ronco
/*********************************************************************

    ef9345.c

    Thomson EF9345 video controller emulator code

    This code is based on Daniel Coulom's implementation in DCVG5k
    and DCAlice released by Daniel Coulom under GPL license

*********************************************************************/

#include "emu.h"
#include "ef9345.h"

#define MODE24x40   0
#define MODEVAR40   1
#define MODE8x80    2
#define MODE12x80   3
#define MODE16x40   4

//**************************************************************************
//  GLOBAL VARIABLES
//**************************************************************************

// devices
const device_type EF9345 = &device_creator<ef9345_device>;

// default address map
static ADDRESS_MAP_START( ef9345, AS_0, 8, ef9345_device )
	AM_RANGE(0x0000, 0x3fff) AM_RAM
ADDRESS_MAP_END

//-------------------------------------------------
//  memory_space_config - return a description of
//  any address spaces owned by this device
//-------------------------------------------------

const address_space_config *ef9345_device::memory_space_config(address_spacenum spacenum) const
{
	return (spacenum == AS_0) ? &m_space_config : nullptr;
}

//**************************************************************************
//  INLINE HELPERS
//**************************************************************************

// calculate the internal RAM offset
inline uint16_t ef9345_device::indexram(uint8_t r)
{
	uint8_t x = m_registers[r];
	uint8_t y = m_registers[r - 1];
	if (y < 8)
		y &= 1;
	return ((x&0x3f) | ((x & 0x40) << 6) | ((x & 0x80) << 4) | ((y & 0x1f) << 6) | ((y & 0x20) << 8));
}

// calculate the internal ROM offset
inline uint16_t ef9345_device::indexrom(uint8_t r)
{
	uint8_t x = m_registers[r];
	uint8_t y = m_registers[r - 1];
	if (y < 8)
		y &= 1;
	return((x&0x3f)|((x&0x40)<<6)|((x&0x80)<<4)|((y&0x1f)<<6));
}

// increment x
inline void ef9345_device::inc_x(uint8_t r)
{
	uint8_t i = (m_registers[r] & 0x3f) + 1;
	if (i > 39)
	{
		i -= 40;
		m_state |= 0x40;
	}
	m_registers[r] = (m_registers[r] & 0xc0) | i;
}

// increment y
inline void ef9345_device::inc_y(uint8_t r)
{
	uint8_t i = (m_registers[r] & 0x1f) + 1;
	if (i > 31)
		i -= 24;
	m_registers[r] = (m_registers[r] & 0xe0) | i;
}


//**************************************************************************
//  live device
//**************************************************************************

//-------------------------------------------------
//  ef9345_device - constructor
//-------------------------------------------------

ef9345_device::ef9345_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
	device_t(mconfig, EF9345, "EF9345", tag, owner, clock, "ef9345", __FILE__),
	device_memory_interface(mconfig, *this),
	device_video_interface(mconfig, *this),
	m_space_config("videoram", ENDIANNESS_LITTLE, 8, 16, 0, nullptr, *ADDRESS_MAP_NAME(ef9345)),
	m_charset(*this, DEVICE_SELF),
	m_palette(*this, finder_base::DUMMY_TAG)
{
}

//-------------------------------------------------
//  static_set_palette_tag: Set the tag of the
//  palette device
//-------------------------------------------------

void ef9345_device::static_set_palette_tag(device_t &device, const char *tag)
{
	downcast<ef9345_device &>(device).m_palette.set_tag(tag);
}

//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void ef9345_device::device_start()
{
	m_busy_timer = timer_alloc(BUSY_TIMER);
	m_blink_timer = timer_alloc(BLINKING_TIMER);

	m_videoram = &space(0);

	m_screen_out.allocate(496, m_screen->height());

	m_blink_timer->adjust(attotime::from_msec(500), 0, attotime::from_msec(500));

	init_accented_chars();

	save_item(NAME(m_border));
	save_item(NAME(m_registers));
	save_item(NAME(m_last_dial));
	save_item(NAME(m_ram_base));
	save_item(NAME(m_bf));
	save_item(NAME(m_char_mode));
	save_item(NAME(m_state));
	save_item(NAME(m_tgs));
	save_item(NAME(m_mat));
	save_item(NAME(m_pat));
	save_item(NAME(m_dor));
	save_item(NAME(m_ror));
	save_item(NAME(m_block));
	save_item(NAME(m_blink));
	save_item(NAME(m_latchc0));
	save_item(NAME(m_latchm));
	save_item(NAME(m_latchi));
	save_item(NAME(m_latchu));

	save_item(NAME(m_screen_out));
}

//-------------------------------------------------
//  device_reset - device-specific reset
//-------------------------------------------------
void ef9345_device::device_reset()
{
	m_tgs = m_mat = m_pat = m_dor = m_ror = 0;
	m_state = 0;
	m_bf = 0;
	m_block = 0;
	m_blink = 0;
	m_latchc0 = 0;
	m_latchm = 0;
	m_latchi = 0;
	m_latchu = 0;
	m_char_mode = 0;

	memset(m_last_dial, 0, sizeof(m_last_dial));
	memset(m_registers, 0, sizeof(m_registers));
	memset(m_border, 0, sizeof(m_border));
	memset(m_border, 0, sizeof(m_ram_base));

	m_screen_out.fill(0);

	set_video_mode();
}

//-------------------------------------------------
//  device_timer - handler timer events
//-------------------------------------------------
void ef9345_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
	switch(id)
	{
		case BUSY_TIMER:
			m_bf = 0;
			break;

		case BLINKING_TIMER:
			m_blink = !m_blink;
			break;
	}
}


// set busy flag and timer to clear it
void ef9345_device::set_busy_flag(int period)
{
	m_bf = 1;
	m_busy_timer->adjust(attotime::from_usec(period));
}

// draw a char in 40 char line mode
void ef9345_device::draw_char_40(uint8_t *c, uint16_t x, uint16_t y)
{
	//verify size limit
	if (y * 10 >= m_screen->height() || x * 8 >= m_screen->width())
		return;

	const rgb_t *palette = m_palette->palette()->entry_list_raw();
	for(int i = 0; i < 10; i++)
		for(int j = 0; j < 8; j++)
				m_screen_out.pix32(y * 10 + i, x * 8 + j)  = palette[c[8 * i + j] & 0x07];
}

// draw a char in 80 char line mode
void ef9345_device::draw_char_80(uint8_t *c, uint16_t x, uint16_t y)
{
	// verify size limit
	if (y * 10 >= m_screen->height() || x * 6 >= m_screen->width())
		return;

	const rgb_t *palette = m_palette->palette()->entry_list_raw();
	for(int i = 0; i < 10; i++)
		for(int j = 0; j < 6; j++)
				m_screen_out.pix32(y * 10 + i, x * 6 + j)  = palette[c[6 * i + j] & 0x07];
}


// set then ef9345 mode
void ef9345_device::set_video_mode(void)
{
	m_char_mode = ((m_pat & 0x80) >> 5) | ((m_tgs & 0xc0) >> 6);
	uint16_t new_width = (m_char_mode == MODE12x80 || m_char_mode == MODE8x80) ? 492 : 336;

	if (m_screen->width() != new_width)
	{
		rectangle visarea = m_screen->visible_area();
		visarea.max_x = new_width - 1;

		m_screen->configure(new_width, m_screen->height(), visarea, m_screen->frame_period().attoseconds());
	}

	//border color
	memset(m_border, m_mat & 0x07, sizeof(m_border));

	//set the base for the m_videoram charset
	m_ram_base[0] = ((m_dor & 0x07) << 11);
	m_ram_base[1] = m_ram_base[0];
	m_ram_base[2] = ((m_dor & 0x30) << 8);
	m_ram_base[3] = m_ram_base[2] + 0x0800;

	//address of the current memory block
	m_block = 0x0800 * ((((m_ror & 0xf0) >> 4) | ((m_ror & 0x40) >> 5) | ((m_ror & 0x20) >> 3)) & 0x0c);
}

// initialize the ef9345 accented chars
void ef9345_device::init_accented_chars(void)
{
	uint16_t i, j;
	for(j = 0; j < 0x10; j++)
		for(i = 0; i < 0x200; i++)
			m_acc_char[(j << 9) + i] = m_charset[0x0600 + i];

	for(j = 0; j < 0x200; j += 0x40)
		for(i = 0; i < 4; i++)
		{
			m_acc_char[0x0200 + j + i +  4] |= 0x1c; //tilde
			m_acc_char[0x0400 + j + i +  4] |= 0x10; //acute
			m_acc_char[0x0400 + j + i +  8] |= 0x08; //acute
			m_acc_char[0x0600 + j + i +  4] |= 0x04; //grave
			m_acc_char[0x0600 + j + i +  8] |= 0x08; //grave

			m_acc_char[0x0a00 + j + i +  4] |= 0x1c; //tilde
			m_acc_char[0x0c00 + j + i +  4] |= 0x10; //acute
			m_acc_char[0x0c00 + j + i +  8] |= 0x08; //acute
			m_acc_char[0x0e00 + j + i +  4] |= 0x04; //grave
			m_acc_char[0x0e00 + j + i +  8] |= 0x08; //grave

			m_acc_char[0x1200 + j + i +  4] |= 0x08; //point
			m_acc_char[0x1400 + j + i +  4] |= 0x14; //trema
			m_acc_char[0x1600 + j + i + 32] |= 0x08; //cedilla
			m_acc_char[0x1600 + j + i + 36] |= 0x04; //cedilla

			m_acc_char[0x1a00 + j + i +  4] |= 0x08; //point
			m_acc_char[0x1c00 + j + i +  4] |= 0x14; //trema
			m_acc_char[0x1e00 + j + i + 32] |= 0x08; //cedilla
			m_acc_char[0x1e00 + j + i + 36] |= 0x04; //cedilla
		}
}

// read a char in charset or in m_videoram
uint8_t ef9345_device::read_char(uint8_t index, uint16_t addr)
{
	if (index < 0x04)
		return m_charset[0x0800*index + addr];
	else if (index < 0x08)
		return m_acc_char[0x0800*(index&3) + addr];
	else if (index < 0x0c)
		return m_videoram->read_byte(m_ram_base[index-8] + addr);
	else
		return m_videoram->read_byte(addr);
}

// calculate the dial position of the char
uint8_t ef9345_device::get_dial(uint8_t x, uint8_t attrib)
{
	if (x > 0 && m_last_dial[x-1] == 1)         //top right
		m_last_dial[x] = 2;
	else if (x > 0 && m_last_dial[x-1] == 5)    //half right
		m_last_dial[x] = 10;
	else if (m_last_dial[x] == 1)               //bottom left
		m_last_dial[x] = 4;
	else if (m_last_dial[x] == 2)               //bottom right
		m_last_dial[x] = 8;
	else if (m_last_dial[x] == 3)               //lower half
		m_last_dial[x] = 12;
	else if (attrib == 1)                       //Left half
		m_last_dial[x] = 5;
	else if (attrib == 2)                       //half high
		m_last_dial[x] = 3;
	else if (attrib == 3)                       //top left
		m_last_dial[x] = 1;
	else                                        //none
		m_last_dial[x] = 0;

	return m_last_dial[x];
}

// zoom the char
void ef9345_device::zoom(uint8_t *pix, uint16_t n)
{
	uint8_t i, j;
	if ((n & 0x0a) == 0)
		for(i = 0; i < 80; i += 8) // 1, 4, 5
			for(j = 7; j > 0; j--)
				pix[i + j] = pix[i + j / 2];

	if ((n & 0x05) == 0)
		for(i = 0; i < 80; i += 8) // 2, 8, 10
			for(j =0 ; j < 7; j++)
				pix[i + j] = pix[i + 4 + j / 2];

	if ((n & 0x0c) == 0)
		for(i = 0; i < 8; i++) // 1, 2, 3
			for(j = 9; j > 0; j--)
				pix[i + 8 * j] = pix[i + 8 * (j / 2)];

	if ((n & 0x03) == 0)
		for(i = 0; i < 8; i++) // 4, 8, 12
			for(j = 0; j < 9; j++)
				pix[i + 8 * j] = pix[i + 40 + 8 * (j / 2)];
}


// calculate the address of the char x,y
uint16_t ef9345_device::indexblock(uint16_t x, uint16_t y)
{
	uint16_t i = x, j;
	j = (y == 0) ? ((m_tgs & 0x20) >> 5) : ((m_ror & 0x1f) + y - 1);

	//right side of a double width character
	if ((m_tgs & 0x80) == 0 && x > 0)
	{
		if (m_last_dial[x - 1] == 1) i--;
		if (m_last_dial[x - 1] == 4) i--;
		if (m_last_dial[x - 1] == 5) i--;
	}

	return 0x40 * j + i;
}

// draw bichrome character (40 columns)
void ef9345_device::bichrome40(uint8_t type, uint16_t address, uint8_t dial, uint16_t iblock, uint16_t x, uint16_t y, uint8_t c0, uint8_t c1, uint8_t insert, uint8_t flash, uint8_t hided, uint8_t negative, uint8_t underline)
{
	uint16_t i;
	uint8_t pix[80];

	if (flash && m_pat & 0x40 && m_blink)
		c1 = c0;                    //flash
	if (hided && m_pat & 0x08)
		c1 = c0;                    //hided
	if (negative)                   //negative
	{
		i = c1;
		c1 = c0;
		c0 = i;
	}

	if ((m_pat & 0x30) == 0x30)
		insert = 0;                 //active area mark
	if (insert == 0)
		c1 += 8;                    //foreground color
	if ((m_pat & 0x30) == 0x00)
		insert = 1;                 //insert mode
	if (insert == 0)
		c0 += 8;                    //background color

	//draw the cursor
	i = (m_registers[6] & 0x1f);
	if (i < 8)
		i &= 1;

	if (iblock == 0x40 * i + (m_registers[7] & 0x3f))   //cursor position
	{
		switch(m_mat & 0x70)
		{
		case 0x40:                  //00 = fixed complemented
			c0 = (23 - c0) & 15;
			c1 = (23 - c1) & 15;
			break;
		case 0x50:                  //01 = fixed underlined
			underline = 1;
			break;
		case 0x60:                  //10 = flash complemented
			if (m_blink)
			{
				c0 = (23 - c0) & 15;
				c1 = (23 - c1) & 15;
			}
			break;
		case 0x70:                  //11 = flash underlined
			if (m_blink)
				underline = 1;
			break;
		}
	}

	// generate the pixel table
	for(i = 0; i < 40; i+=4)
	{
		uint8_t ch = read_char(type, address + i);

		for (uint8_t b=0; b<8; b++)
			pix[i*2 + b] = (ch & (1<<b)) ? c1 : c0;
	}

	//draw the underline
	if (underline)
		memset(&pix[72], c1, 8);

	if (dial > 0)
		zoom(pix, dial);

	//doubles the height of the char
	if (m_mat & 0x80)
		zoom(pix, (y & 0x01) ? 0x0c : 0x03);

	draw_char_40(pix, x + 1 , y + 1);
}

// draw quadrichrome character (40 columns)
void ef9345_device::quadrichrome40(uint8_t c, uint8_t b, uint8_t a, uint16_t x, uint16_t y)
{
	//C0-6= character code
	//B0= insert             not yet implemented !!!
	//B1= low resolution
	//B2= subset index (low resolution only)
	//B3-5 = set number
	//A0-6 = 4 color palette

	uint8_t i, j, n, col[8], pix[80];
	uint8_t lowresolution = (b & 0x02) >> 1, ramx, ramy, ramblock;
	uint16_t ramindex;

	//quadrichrome don't suppor double size
	m_last_dial[x] = 0;

	//initialize the color table
	for(j = 1, n = 0, i = 0; i < 8; i++)
	{
		col[n++] = (a & j) ? i : 7;
		j <<= 1;
	}

	//find block number in ram
	ramblock = 0;
	if (b & 0x20)   ramblock |= 4;      //B5
	if (b & 0x08)   ramblock |= 2;      //B3
	if (b & 0x10)   ramblock |= 1;      //B4

	//find character address in ram
	ramx = c & 0x03;
	ramy =(c & 0x7f) >> 2;
	ramindex = 0x0800 * ramblock + 0x40 * ramy + ramx;
	if (lowresolution) ramindex += 5 * (b & 0x04);

	//fill pixel table
	for(i = 0, j = 0; i < 10; i++)
	{
		uint8_t ch = read_char(0x0c, ramindex + 4 * (i >> lowresolution));
		pix[j] = pix[j + 1] = col[(ch & 0x03) >> 0]; j += 2;
		pix[j] = pix[j + 1] = col[(ch & 0x0c) >> 2]; j += 2;
		pix[j] = pix[j + 1] = col[(ch & 0x30) >> 4]; j += 2;
		pix[j] = pix[j + 1] = col[(ch & 0xc0) >> 6]; j += 2;
	}

	draw_char_40(pix, x + 1, y + 1);
}

// draw bichrome character (80 columns)
void ef9345_device::bichrome80(uint8_t c, uint8_t a, uint16_t x, uint16_t y)
{
	uint8_t c0, c1, pix[60];
	uint16_t i, j, d;

	c1 = (a & 1) ? (m_dor >> 4) & 7 : m_dor & 7;    //foreground color = DOR
	c0 =  m_mat & 7;                                //background color = MAT

	switch(c & 0x80)
	{
	case 0: //alphanumeric G0 set
		//A0: D = color set
		//A1: U = underline
		//A2: F = flash
		//A3: N = negative
		//C0-6: character code

		if ((a & 4) && (m_pat & 0x40) && (m_blink))
			c1 = c0;    //flash
		if (a & 8)      //negative
		{
			i = c1;
			c1 = c0;
			c0 = i;
		}

		d = ((c & 0x7f) >> 2) * 0x40 + (c & 0x03);  //char position

		for(i=0, j=0; i < 10; i++)
		{
			uint8_t ch = read_char(0, d + 4 * i);
			for (uint8_t b=0; b<6; b++)
				pix[j++] = (ch & (1<<b)) ? c1 : c0;
		}

		//draw the underline
		if (a & 2)
			memset(&pix[54], c1, 6);

		break;
	default: //dedicated mosaic set
		//A0: D = color set
		//A1-3: 3 blocks de 6 pixels
		//C0-6: 7 blocks de 6 pixels
		pix[ 0] = (c & 0x01) ? c1 : c0;
		pix[ 3] = (c & 0x02) ? c1 : c0;
		pix[12] = (c & 0x04) ? c1 : c0;
		pix[15] = (c & 0x08) ? c1 : c0;
		pix[24] = (c & 0x10) ? c1 : c0;
		pix[27] = (c & 0x20) ? c1 : c0;
		pix[36] = (c & 0x40) ? c1 : c0;
		pix[39] = (a & 0x02) ? c1 : c0;
		pix[48] = (a & 0x04) ? c1 : c0;
		pix[51] = (a & 0x08) ? c1 : c0;

		for(i = 0; i < 60; i += 12)
		{
			pix[i + 6] = pix[i];
			pix[i + 9] = pix[i + 3];
		}

		for(i = 0; i < 60; i += 3)
			pix[i + 2] = pix[i + 1] = pix[i];

		break;
	}

	draw_char_80(pix, x, y);
}

// generate 16 bits 40 columns char
void ef9345_device::makechar_16x40(uint16_t x, uint16_t y)
{
	uint8_t a, b, c0, c1, i, f, m, n, u, type, dial;
	uint16_t address, iblock;

	iblock = (m_mat & 0x80 && y > 1) ? indexblock(x, y / 2) : indexblock(x, y);
	a = m_videoram->read_byte(m_block + iblock);
	b = m_videoram->read_byte(m_block + iblock + 0x0800);

	dial = get_dial(x, (a & 0x80) ? 0 : (((a & 0x20) >> 5) | ((a & 0x10) >> 3)));

	//type and address of the char
	type = ((b & 0x80) >> 4) | ((a & 0x80) >> 6);
	address = ((b & 0x7f) >> 2) * 0x40 + (b & 0x03);

		//negative space
	if ((b & 0xe0) == 0x80)
	{
		address = 0;
		type = 3;
	}

	//reset attributes latch
	if (x == 0)
		m_latchm = m_latchi = m_latchu = m_latchc0 = 0;

	if (type == 4)
	{
		m_latchm = b & 1;
		m_latchi = (b & 2) >> 1;
		m_latchu = (b & 4) >> 2;
	}

	if (a & 0x80)
		m_latchc0 = (a & 0x70) >> 4;

	//char attributes
	c0 = m_latchc0;                         //background
	c1 = a & 0x07;                          //foreground
	i = m_latchi;                               //insert mode
	f  = (a & 0x08) >> 3;                   //flash
	m = m_latchm;                               //hided
	n  = (a & 0x80) ? 0: ((a & 0x40) >> 6); //negative
	u = m_latchu;                               //underline

	bichrome40(type, address, dial, iblock, x, y, c0, c1, i, f, m, n, u);
}

// generate 24 bits 40 columns char
void ef9345_device::makechar_24x40(uint16_t x, uint16_t y)
{
	uint8_t a, b, c, c0, c1, i, f, m, n, u, type, dial;
	uint16_t address, iblock;

	iblock = (m_mat & 0x80 && y > 1) ? indexblock(x, y / 2) : indexblock(x, y);
	c = m_videoram->read_byte(m_block + iblock);
	b = m_videoram->read_byte(m_block + iblock + 0x0800);
	a = m_videoram->read_byte(m_block + iblock + 0x1000);

	if ((b & 0xc0) == 0xc0)
	{
		quadrichrome40(c, b, a, x, y);
		return;
	}

	dial = get_dial(x, (b & 0x02) + ((b & 0x08) >> 3));

	//type and address of the char
	address = ((c & 0x7f) >> 2) * 0x40 + (c & 0x03);
	type = (b & 0xf0) >> 4;

	//char attributes
	c0 = a & 0x07;                  //background
	c1 = (a & 0x70) >> 4;           //foreground
	i = b & 0x01;                   //insert
	f = (a & 0x08) >> 3;            //flash
	m = (b & 0x04) >> 2;            //hided
	n = ((a & 0x80) >> 7);          //negative
	u = (((b & 0x60) == 0) || ((b & 0xc0) == 0x40)) ? ((b & 0x10) >> 4) : 0; //underline

	bichrome40(type, address, dial, iblock, x, y, c0, c1, i, f, m, n, u);
}

// generate 12 bits 80 columns char
void ef9345_device::makechar_12x80(uint16_t x, uint16_t y)
{
	uint16_t iblock = indexblock(x, y);
	bichrome80(m_videoram->read_byte(m_block + iblock), (m_videoram->read_byte(m_block + iblock + 0x1000) >> 4) & 0x0f, 2 * x + 1, y + 1);
	bichrome80(m_videoram->read_byte(m_block + iblock + 0x0800), m_videoram->read_byte(m_block + iblock + 0x1000) & 0x0f, 2 * x + 2, y + 1);
}

void ef9345_device::draw_border(uint16_t line)
{
	if (m_char_mode == MODE12x80 || m_char_mode == MODE8x80)
		for(int i = 0; i < 82; i++)
			draw_char_80(m_border, i, line);
	else
		for(int i = 0; i < 42; i++)
			draw_char_40(m_border, i, line);
}

void ef9345_device::makechar(uint16_t x, uint16_t y)
{
	switch (m_char_mode)
	{
		case MODE24x40:
			makechar_24x40(x, y);
			break;
		case MODEVAR40:
		case MODE8x80:
			logerror("Unemulated EF9345 mode: %02x\n", m_char_mode);
			break;
		case MODE12x80:
			makechar_12x80(x, y);
			break;
		case MODE16x40:
			makechar_16x40(x, y);
			break;
		default:
			logerror("Unknown EF9345 mode: %02x\n", m_char_mode);
			break;
	}
}

// Execute EF9345 command
void ef9345_device::ef9345_exec(uint8_t cmd)
{
	m_state = 0;
	if ((m_registers[5] & 0x3f) == 39) m_state |= 0x10; //S4(LXa) set
	if ((m_registers[7] & 0x3f) == 39) m_state |= 0x20; //S5(LXm) set

	uint16_t a = indexram(7);

	switch(cmd)
	{
		case 0x00:  //KRF: R1,R2,R3->ram
		case 0x01:  //KRF: R1,R2,R3->ram + increment
			set_busy_flag(4);
			m_videoram->write_byte(a, m_registers[1]);
			m_videoram->write_byte(a + 0x0800, m_registers[2]);
			m_videoram->write_byte(a + 0x1000, m_registers[3]);
			if (cmd&1) inc_x(7);
			break;
		case 0x02:  //KRG: R1,R2->ram
		case 0x03:  //KRG: R1,R2->ram + increment
			set_busy_flag(5.5);
			m_videoram->write_byte(a, m_registers[1]);
			m_videoram->write_byte(a + 0x0800, m_registers[2]);
			if (cmd&1) inc_x(7);
			break;
		case 0x08:  //KRF: ram->R1,R2,R3
		case 0x09:  //KRF: ram->R1,R2,R3 + increment
			set_busy_flag(7.5);
			m_registers[1] = m_videoram->read_byte(a);
			m_registers[2] = m_videoram->read_byte(a + 0x0800);
			m_registers[3] = m_videoram->read_byte(a + 0x1000);
			if (cmd&1) inc_x(7);
			break;
		case 0x0a:  //KRG: ram->R1,R2
		case 0x0b:  //KRG: ram->R1,R2 + increment
			set_busy_flag(7.5);
			m_registers[1] = m_videoram->read_byte(a);
			m_registers[2] = m_videoram->read_byte(a + 0x0800);
			if (cmd&1) inc_x(7);
			break;
		case 0x30:  //OCT: R1->RAM, main pointer
		case 0x31:  //OCT: R1->RAM, main pointer + inc
			set_busy_flag(4);
			m_videoram->write_byte(indexram(7), m_registers[1]);

			if (cmd&1)
			{
				inc_x(7);
				if ((m_registers[7] & 0x3f) == 0)
					inc_y(6);
			}
			break;
		case 0x34:  //OCT: R1->RAM, aux pointer
		case 0x35:  //OCT: R1->RAM, aux pointer + inc
			set_busy_flag(4);
			m_videoram->write_byte(indexram(5), m_registers[1]);

			if (cmd&1)
				inc_x(5);
			break;
		case 0x38:  //OCT: RAM->R1, main pointer
		case 0x39:  //OCT: RAM->R1, main pointer + inc
			set_busy_flag(4.5);
			m_registers[1] = m_videoram->read_byte(indexram(7));

			if (cmd&1)
			{
				inc_x(7);

				if ((m_registers[7] & 0x3f) == 0)
					inc_y(6);
			}
			break;
		case 0x3c:  //OCT: RAM->R1, aux pointer
		case 0x3d:  //OCT: RAM->R1, aux pointer + inc
			set_busy_flag(4.5);
			m_registers[1] = m_videoram->read_byte(indexram(5));

			if (cmd&1)
				inc_x(5);
			break;
		case 0x50:  //KRL: 80 uint8_t - 12 bits write
		case 0x51:  //KRL: 80 uint8_t - 12 bits write + inc
			set_busy_flag(12.5);
			m_videoram->write_byte(a, m_registers[1]);
			switch((a / 0x0800) & 1)
			{
				case 0:
				{
					uint8_t tmp_data = m_videoram->read_byte(a + 0x1000);
					m_videoram->write_byte(a + 0x1000, (tmp_data & 0x0f) | (m_registers[3] & 0xf0));
					break;
				}
				case 1:
				{
					uint8_t tmp_data = m_videoram->read_byte(a + 0x0800);
					m_videoram->write_byte(a + 0x0800, (tmp_data & 0xf0) | (m_registers[3] & 0x0f));
					break;
				}
			}
			if (cmd&1)
			{
				if ((m_registers[7] & 0x80) == 0x00) { m_registers[7] |= 0x80; return; }
				m_registers[7] &= 0x80;
				inc_x(7);
			}
			break;
		case 0x58:  //KRL: 80 uint8_t - 12 bits read
		case 0x59:  //KRL: 80 uint8_t - 12 bits read + inc
			set_busy_flag(11.5);
			m_registers[1] = m_videoram->read_byte(a);
			switch((a / 0x0800) & 1)
			{
				case 0:
					m_registers[3] = m_videoram->read_byte(a + 0x1000);
					break;
				case 1:
					m_registers[3] = m_videoram->read_byte(a + 0x0800);
					break;
			}
			if (cmd&1)
			{
				if ((m_registers[7] & 0x80) == 0x00)
				{
					m_registers[7] |= 0x80;
					break;
				}
				m_registers[7] &= 0x80;
				inc_x(7);
			}
			break;
		case 0x80:  //IND: R1->ROM (impossible ?)
			break;
		case 0x81:  //IND: R1->TGS
		case 0x82:  //IND: R1->MAT
		case 0x83:  //IND: R1->PAT
		case 0x84:  //IND: R1->DOR
		case 0x87:  //IND: R1->ROR
			set_busy_flag(2);
			switch(cmd&7)
			{
				case 1:     m_tgs = m_registers[1]; break;
				case 2:     m_mat = m_registers[1]; break;
				case 3:     m_pat = m_registers[1]; break;
				case 4:     m_dor = m_registers[1]; break;
				case 7:     m_ror = m_registers[1]; break;
			}
			set_video_mode();
			m_state &= 0x8f;  //reset S4(LXa), S5(LXm), S6(Al)
			break;
		case 0x88:  //IND: ROM->R1
		case 0x89:  //IND: TGS->R1
		case 0x8a:  //IND: MAT->R1
		case 0x8b:  //IND: PAT->R1
		case 0x8c:  //IND: DOR->R1
		case 0x8f:  //IND: ROR->R1
			set_busy_flag(3.5);
			switch(cmd&7)
			{
				case 0:     m_registers[1] = m_charset[indexrom(7) & 0x1fff]; break;
				case 1:     m_registers[1] = m_tgs; break;
				case 2:     m_registers[1] = m_mat; break;
				case 3:     m_registers[1] = m_pat; break;
				case 4:     m_registers[1] = m_dor; break;
				case 7:     m_registers[1] = m_ror; break;
			}
			m_state &= 0x8f;  //reset S4(LXa), S5(LXm), S6(Al)
			break;
		case 0x90:  //NOP: no operation
		case 0x91:  //NOP: no operation
		case 0x95:  //VRM: vertical sync mask reset
		case 0x99:  //VSM: vertical sync mask set
			break;
		case 0xb0:  //INY: increment Y
			set_busy_flag(2);
			inc_y(6);
			m_state &= 0x8f;  //reset S4(LXa), S5(LXm), S6(Al)
			break;
		case 0xd5:  //MVB: move buffer MP->AP stop
		case 0xd6:  //MVB: move buffer MP->AP nostop
		case 0xd9:  //MVB: move buffer AP->MP stop
		case 0xda:  //MVB: move buffer AP->MP nostop
		case 0xe5:  //MVD: move double buffer MP->AP stop
		case 0xe6:  //MVD: move double buffer MP->AP nostop
		case 0xe9:  //MVD: move double buffer AP->MP stop
		case 0xea:  //MVD: move double buffer AP->MP nostop
		case 0xf5:  //MVT: move triple buffer MP->AP stop
		case 0xf6:  //MVT: move triple buffer MP->AP nostop
		case 0xf9:  //MVT: move triple buffer AP->MP stop
		case 0xfa:  //MVT: move triple buffer AP->MP nostop
		{
			uint16_t i, a1, a2;
			uint8_t n = (cmd>>4) - 0x0c;
			uint8_t r1 = (cmd&0x04) ? 7 : 5;
			uint8_t r2 = (cmd&0x04) ? 5 : 7;
			int busy = 2;

			for(i = 0; i < 1280; i++)
			{
				a1 = indexram(r1); a2 = indexram(r2);
				m_videoram->write_byte(a2, m_videoram->read_byte(a1));

				if (n > 1) m_videoram->write_byte(a2 + 0x0800, m_videoram->read_byte(a1 + 0x0800));
				if (n > 2) m_videoram->write_byte(a2 + 0x1000, m_videoram->read_byte(a1 + 0x1000));

				inc_x(r1);
				inc_x(r2);
				if ((m_registers[5] & 0x3f) == 0 && (cmd&1))
					break;

				if ((m_registers[7] & 0x3f) == 0)
				{
					if (cmd&1)
						break;
					else
					inc_y(6);
				}

				busy += 4 * n;
			}
			m_state &= 0x8f;  //reset S4(LXa), S5(LXm), S6(Al)
			set_busy_flag(busy);
		}
		break;
		case 0x05:  //CLF: Clear page 24 bits
		case 0x07:  //CLG: Clear page 16 bits
		case 0x40:  //KRC: R1 -> ram
		case 0x41:  //KRC: R1 -> ram + inc
		case 0x48:  //KRC: 80 characters - 8 bits
		case 0x49:  //KRC: 80 characters - 8 bits
		default:
			logerror("Unemulated EF9345 cmd: %02x\n", cmd);
	}
}


/**************************************************************
            EF9345 interface
**************************************************************/

uint32_t ef9345_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
	copybitmap(bitmap, m_screen_out, 0, 0, 0, 0, cliprect);
	return 0;
}

void ef9345_device::update_scanline(uint16_t scanline)
{
	uint16_t i;

	if (scanline == 250)
		m_state &= 0xfb;

	set_busy_flag(104);

	if (m_char_mode == MODE12x80 || m_char_mode == MODE8x80)
	{
		draw_char_80(m_border, 0, (scanline / 10) + 1);
		draw_char_80(m_border, 81, (scanline / 10) + 1);
	}
	else
	{
		draw_char_40(m_border, 0, (scanline / 10) + 1);
		draw_char_40(m_border, 41, (scanline / 10) + 1);
	}

	if (scanline == 0)
	{
		m_state |= 0x04;
		draw_border(0);
		if (m_pat & 1)
			for(i = 0; i < 40; i++)
				makechar(i, (scanline / 10));
		else
			for(i = 0; i < 42; i++)
				draw_char_40(m_border, i, 1);
	}
	else if (scanline < 120)
	{
		if (m_pat & 2)
			for(i = 0; i < 40; i++)
				makechar(i, (scanline / 10));
		else
			draw_border(scanline / 10);
	}
	else if (scanline < 250)
	{
		if (m_pat & 4)
			for(i = 0; i < 40; i++)
				makechar(i, (scanline / 10));
		else
			draw_border(scanline / 10);

		if (scanline == 240)
			draw_border(26);
	}
}

READ8_MEMBER( ef9345_device::data_r )
{
	if (offset & 7)
		return m_registers[offset & 7];

	if (m_bf)
		m_state |= 0x80;
	else
		m_state &= 0x7f;

	return m_state;
}

WRITE8_MEMBER( ef9345_device::data_w )
{
	m_registers[offset & 7] = data;

	if (offset & 8)
		ef9345_exec(m_registers[0] & 0xff);
}
