// license:BSD-3-Clause
// copyright-holders:Sandro Ronco
/***************************************************************************
Hitachi HD44352 LCD controller
***************************************************************************/
#include "emu.h"
#include "video/hd44352.h"
#define LCD_BYTE_INPUT 0x01
#define LCD_BYTE_OUTPUT 0x02
#define LCD_CHAR_OUTPUT 0x03
#define LCD_ON_OFF 0x04
#define LCD_CURSOR_GRAPHIC 0x06
#define LCD_CURSOR_CHAR 0x07
#define LCD_SCROLL_CHAR_WIDTH 0x08
#define LCD_CURSOR_STATUS 0x09
#define LCD_USER_CHARACTER 0x0b
#define LCD_CONTRAST 0x0c
#define LCD_IRQ_FREQUENCY 0x0d
#define LCD_CURSOR_POSITION 0x0e
// devices
DEFINE_DEVICE_TYPE(HD44352, hd44352_device, "hd44352", "Hitachi HD44352 LCD Controller")
//**************************************************************************
// live device
//**************************************************************************
//-------------------------------------------------
// hd44352_device - constructor
//-------------------------------------------------
hd44352_device::hd44352_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock):
device_t(mconfig, HD44352, tag, owner, clock),
m_on_cb(*this),
m_char_rom(*this, DEVICE_SELF)
{
}
//-------------------------------------------------
// device_validity_check - perform validity checks
// on this device
//-------------------------------------------------
void hd44352_device::device_validity_check(validity_checker &valid) const
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void hd44352_device::device_start()
{
m_on_cb.resolve_safe();
m_on_timer = timer_alloc(ON_TIMER);
m_on_timer->adjust(attotime::from_hz(m_clock/16384), 0, attotime::from_hz(m_clock/16384));
save_item( NAME(m_control_lines));
save_item( NAME(m_data_bus));
save_item( NAME(m_state));
save_item( NAME(m_offset));
save_item( NAME(m_char_width));
save_item( NAME(m_bank));
save_item( NAME(m_lcd_on));
save_item( NAME(m_scroll));
save_item( NAME(m_contrast));
save_item( NAME(m_byte_count));
save_item( NAME(m_cursor_status));
save_item( NAME(m_cursor_x));
save_item( NAME(m_cursor_y));
save_item( NAME(m_cursor_lcd));
save_item( NAME(m_video_ram[0]));
save_item( NAME(m_video_ram[1]));
save_item( NAME(m_par));
save_item( NAME(m_cursor));
save_item( NAME(m_custom_char[0]));
save_item( NAME(m_custom_char[1]));
save_item( NAME(m_custom_char[2]));
save_item( NAME(m_custom_char[3]));
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void hd44352_device::device_reset()
{
memset(m_video_ram, 0x00, sizeof(m_video_ram));
memset(m_par, 0x00, sizeof(m_par));
memset(m_custom_char, 0x00, sizeof(m_custom_char));
memset(m_cursor, 0x00, sizeof(m_cursor));
m_control_lines = 0;
m_data_bus = 0xff;
m_state = 0;
m_bank = 0;
m_offset = 0;
m_char_width = 6;
m_lcd_on = 0;
m_scroll = 0;
m_byte_count = 0;
m_cursor_status = 0;
m_cursor_x = 0;
m_cursor_y = 0;
m_cursor_lcd = 0;
m_contrast = 0;
}
//-------------------------------------------------
// device_timer - handler timer events
//-------------------------------------------------
void hd44352_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
switch(id)
{
case ON_TIMER:
if (m_control_lines & 0x40)
{
m_on_cb(ASSERT_LINE);
m_on_cb(CLEAR_LINE);
}
break;
}
}
//**************************************************************************
// device interface
//**************************************************************************
uint32_t hd44352_device::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
uint8_t cw = m_char_width;
bitmap.fill(0, cliprect);
if (m_control_lines&0x80 && m_lcd_on)
{
for (int a=0; a<2; a++)
for (int py=0; py<4; py++)
for (int px=0; px<16; px++)
if (BIT(m_cursor_status, 4) && px == m_cursor_x && py == m_cursor_y && a == m_cursor_lcd)
{
//draw the cursor
for (int c=0; c<cw; c++)
{
uint8_t d = compute_newval((m_cursor_status>>5) & 0x07, m_video_ram[a][py*16*cw + px*cw + c + m_scroll * 48], m_cursor[c]);
for (int b=0; b<8; b++)
{
bitmap.pix16(py*8 + b, a*cw*16 + px*cw + c) = BIT(d, 7-b);
}
}
}
else
{
for (int c=0; c<cw; c++)
{
uint8_t d = m_video_ram[a][py*16*cw + px*cw + c + m_scroll * 48];
for (int b=0; b<8; b++)
{
bitmap.pix16(py*8 + b, a*cw*16 + px*cw + c) = BIT(d, 7-b);
}
}
}
}
return 0;
}
void hd44352_device::control_write(uint8_t data)
{
if(m_control_lines == data)
m_state = 0;
m_control_lines = data;
}
uint8_t hd44352_device::compute_newval(uint8_t type, uint8_t oldval, uint8_t newval)
{
switch(type & 0x07)
{
case 0x00:
return (~oldval) & newval;
case 0x01:
return oldval ^ newval;
case 0x03:
return oldval & (~newval);
case 0x04:
return newval;
case 0x05:
return oldval | newval;
case 0x07:
return oldval;
case 0x02:
case 0x06:
default:
return 0;
}
}
uint8_t hd44352_device::get_char(uint16_t pos)
{
switch ((uint8_t)pos/8)
{
case 0xcf:
return m_custom_char[0][pos%8];
case 0xdf:
return m_custom_char[1][pos%8];
case 0xef:
return m_custom_char[2][pos%8];
case 0xff:
return m_custom_char[3][pos%8];
default:
return m_char_rom[pos];
}
}
void hd44352_device::data_write(uint8_t data)
{
// verify that controller is active
if (!(m_control_lines&0x80))
return;
if (m_control_lines & 0x01)
{
if (!(m_control_lines&0x02) && !(m_control_lines&0x04))
return;
switch (m_state)
{
case 0: //parameter 0
m_par[m_state++] = data;
break;
case 1: //parameter 1
m_par[m_state++] = data;
break;
case 2: //parameter 2
m_par[m_state++] = data;
break;
}
switch (m_par[0] & 0x0f)
{
case LCD_BYTE_INPUT:
case LCD_CHAR_OUTPUT:
{
if (m_state == 1)
m_bank = BIT(data, 4);
else if (m_state == 2)
m_offset = ((data>>1)&0x3f) % 48 + (BIT(data,7) * 48);
else if (m_state == 3)
m_offset += ((data & 0x03) * 96);
}
break;
case LCD_BYTE_OUTPUT:
{
if (m_state == 1)
m_bank = BIT(data, 4);
else if (m_state == 2)
m_offset = ((data>>1)&0x3f) % 48 + (BIT(data,7) * 48);
else if (m_state == 3)
m_offset += ((data & 0x03) * 96);
}
break;
case LCD_ON_OFF:
{
if (m_state == 1)
m_lcd_on = BIT(data, 4);
m_data_bus = 0xff;
m_state = 0;
}
break;
case LCD_SCROLL_CHAR_WIDTH:
{
if (m_state == 1)
{
m_char_width = 8-((data>>4)&3);
m_scroll = ((data>>6)&3);
}
m_data_bus = 0xff;
m_state = 0;
}
break;
case LCD_CURSOR_STATUS:
{
if (m_state == 1)
m_cursor_status = data;
m_data_bus = 0xff;
m_state = 0;
}
break;
case LCD_CONTRAST:
{
if (m_state == 1)
m_contrast = (m_contrast & 0x00ffff) | (data<<16);
else if (m_state == 2)
m_contrast = (m_contrast & 0xff00ff) | (data<<8);
else if (m_state == 3)
{
m_contrast = (m_contrast & 0xffff00) | (data<<0);
m_state = 0;
}
m_data_bus = 0xff;
}
break;
case LCD_IRQ_FREQUENCY:
{
if (m_state == 1)
{
uint32_t on_timer_rate;
switch((data>>4) & 0x0f)
{
case 0x00: on_timer_rate = 16384; break;
case 0x01: on_timer_rate = 8; break;
case 0x02: on_timer_rate = 16; break;
case 0x03: on_timer_rate = 32; break;
case 0x04: on_timer_rate = 64; break;
case 0x05: on_timer_rate = 128; break;
case 0x06: on_timer_rate = 256; break;
case 0x07: on_timer_rate = 512; break;
case 0x08: on_timer_rate = 1024; break;
case 0x09: on_timer_rate = 2048; break;
case 0x0a: on_timer_rate = 4096; break;
case 0x0b: on_timer_rate = 4096; break;
default: on_timer_rate = 8192; break;
}
m_on_timer->adjust(attotime::from_hz(m_clock/on_timer_rate), 0, attotime::from_hz(m_clock/on_timer_rate));
}
m_data_bus = 0xff;
m_state = 0;
}
break;
case LCD_CURSOR_POSITION:
{
if (m_state == 1)
m_cursor_lcd = BIT(data, 4); //0:left lcd 1:right lcd;
else if (m_state == 2)
m_cursor_x = ((data>>1)&0x3f) % 48 + (BIT(data,7) * 48);
else if (m_state == 3)
{
m_cursor_y = data & 0x03;
m_state = 0;
}
m_data_bus = 0xff;
}
break;
}
m_byte_count = 0;
m_data_bus = 0xff;
}
else
{
switch (m_par[0] & 0x0f)
{
case LCD_BYTE_INPUT:
{
if (((m_par[0]>>5) & 0x07) != 0x03)
break;
m_offset %= 0x180;
m_data_bus = ((m_video_ram[m_bank][m_offset]<<4)&0xf0) | ((m_video_ram[m_bank][m_offset]>>4)&0x0f);
m_offset++; m_byte_count++;
}
break;
case LCD_BYTE_OUTPUT:
{
m_offset %= 0x180;
m_video_ram[m_bank][m_offset] = compute_newval((m_par[0]>>5) & 0x07, m_video_ram[m_bank][m_offset], data);
m_offset++; m_byte_count++;
m_data_bus = 0xff;
}
break;
case LCD_CHAR_OUTPUT:
{
int char_pos = data*8;
for (int i=0; i<m_char_width; i++)
{
m_offset %= 0x180;
m_video_ram[m_bank][m_offset] = compute_newval((m_par[0]>>5) & 0x07, m_video_ram[m_bank][m_offset], get_char(char_pos));
m_offset++; char_pos++;
}
m_byte_count++;
m_data_bus = 0xff;
}
break;
case LCD_CURSOR_GRAPHIC:
if (m_byte_count<8)
{
m_cursor[m_byte_count] = data;
m_byte_count++;
m_data_bus = 0xff;
}
break;
case LCD_CURSOR_CHAR:
if (m_byte_count<1)
{
uint8_t char_code = ((data<<4)&0xf0) | ((data>>4)&0x0f);
for (int i=0; i<8; i++)
m_cursor[i] = get_char(char_code*8 + i);
m_byte_count++;
m_data_bus = 0xff;
}
break;
case LCD_USER_CHARACTER:
if (m_byte_count<8)
{
m_custom_char[(m_par[1]&0x03)][m_byte_count] = data;
m_byte_count++;
m_data_bus = 0xff;
}
break;
default:
m_data_bus = 0xff;
}
m_state=0;
}
}
uint8_t hd44352_device::data_read()
{
return m_data_bus;
}