// license:BSD-3-Clause
// copyright-holders:Curt Coder, Nigel Barnes
/**********************************************************************
Mullard SAA5050 Teletext Character Generator emulation
http://www.bighole.nl/pub/mirror/homepage.ntlworld.com/kryten_droid/teletext/spec/teletext_spec_1974.htm
**********************************************************************/
/*
TODO:
- interlace, use CRS to output odd/even fields
- remote controller input
- boxing
*/
#include "emu.h"
#include "saa5050.h"
//**************************************************************************
// DEVICE DEFINITIONS
//**************************************************************************
DEFINE_DEVICE_TYPE(SAA5050, saa5050_device, "saa5050", "SAA5050 Teletext Character Generator")
DEFINE_DEVICE_TYPE(SAA5051, saa5051_device, "saa5051", "SAA5051 Teletext Character Generator")
DEFINE_DEVICE_TYPE(SAA5052, saa5052_device, "saa5052", "SAA5052 Teletext Character Generator")
DEFINE_DEVICE_TYPE(SAA5053, saa5053_device, "saa5053", "SAA5053 Teletext Character Generator")
DEFINE_DEVICE_TYPE(SAA5054, saa5054_device, "saa5054", "SAA5054 Teletext Character Generator")
DEFINE_DEVICE_TYPE(SAA5055, saa5055_device, "saa5055", "SAA5055 Teletext Character Generator")
DEFINE_DEVICE_TYPE(SAA5056, saa5056_device, "saa5056", "SAA5056 Teletext Character Generator")
DEFINE_DEVICE_TYPE(SAA5057, saa5057_device, "saa5057", "SAA5057 Teletext Character Generator")
//-------------------------------------------------
// ROM( saa5050 )
//-------------------------------------------------
ROM_START( saa5050 )
ROM_REGION( 0x500, "chargen", 0 )
ROM_LOAD("saa5050", 0x0140, 0x03c0, CRC(201490f3) SHA1(6c8daba70374e5aa3a6402f24cdc5f8677d58a0f)) // verified both from datasheet listing and decap
ROM_END
//-------------------------------------------------
// ROM( saa5051 )
//-------------------------------------------------
ROM_START( saa5051 )
ROM_REGION( 0x500, "chargen", 0 )
ROM_LOAD("saa5051", 0x0140, 0x03c0, BAD_DUMP CRC(0e55088b) SHA1(07cd9b7edbed6ef7b527622533d6957f5d56aa91)) // verified from datasheet listing
ROM_END
//-------------------------------------------------
// ROM( saa5052 )
//-------------------------------------------------
ROM_START( saa5052 )
ROM_REGION( 0x500, "chargen", 0 )
ROM_LOAD("saa5052", 0x0140, 0x03c0, BAD_DUMP CRC(2eb76737) SHA1(ec4bc515e28e851a6433f7ca0a11ede0f1d21a68))
ROM_END
//-------------------------------------------------
// ROM( saa5053 )
//-------------------------------------------------
ROM_START( saa5053 )
ROM_REGION( 0x500, "chargen", 0 )
ROM_LOAD("saa5053", 0x0140, 0x03c0, BAD_DUMP CRC(46288c33) SHA1(1e471a1b5670d7163e9f62d31be7cab0330a07cd))
ROM_END
//-------------------------------------------------
// ROM( saa5054 )
//-------------------------------------------------
ROM_START( saa5054 )
ROM_REGION( 0x500, "chargen", 0 )
ROM_LOAD("saa5054", 0x0140, 0x03c0, BAD_DUMP CRC(56298472) SHA1(7a273ad7270507dca4ce621fc1e6b51a1ac25085))
ROM_END
//-------------------------------------------------
// ROM( saa5055 )
//-------------------------------------------------
ROM_START( saa5055 )
ROM_REGION( 0x500, "chargen", 0 )
ROM_LOAD("saa5055", 0x0140, 0x03c0, BAD_DUMP CRC(f95b9c8c) SHA1(c5ce7fe84df6de6a317fa0e87bda413c82c04618))
ROM_END
//-------------------------------------------------
// ROM( saa5056 )
//-------------------------------------------------
ROM_START( saa5056 )
ROM_REGION( 0x500, "chargen", 0 )
ROM_LOAD("saa5056", 0x0140, 0x03c0, BAD_DUMP CRC(86ab8b85) SHA1(2d1ff08b4dda15cf70832881750a962189455f41))
ROM_END
//-------------------------------------------------
// ROM( saa5057 )
//-------------------------------------------------
ROM_START( saa5057 )
ROM_REGION( 0x500, "chargen", 0 )
ROM_LOAD("saa5057", 0x0140, 0x03c0, BAD_DUMP CRC(d6664fb3) SHA1(5a93445dde03066073e2909a935900e5f8439d81))
ROM_END
//-------------------------------------------------
// rom_region - device-specific ROM region
//-------------------------------------------------
const tiny_rom_entry *saa5050_device::device_rom_region() const
{
return ROM_NAME( saa5050 );
}
const tiny_rom_entry *saa5051_device::device_rom_region() const
{
return ROM_NAME( saa5051 );
}
const tiny_rom_entry *saa5052_device::device_rom_region() const
{
return ROM_NAME( saa5052 );
}
const tiny_rom_entry *saa5053_device::device_rom_region() const
{
return ROM_NAME( saa5053 );
}
const tiny_rom_entry *saa5054_device::device_rom_region() const
{
return ROM_NAME( saa5054 );
}
const tiny_rom_entry *saa5055_device::device_rom_region() const
{
return ROM_NAME( saa5055 );
}
const tiny_rom_entry *saa5056_device::device_rom_region() const
{
return ROM_NAME( saa5056 );
}
const tiny_rom_entry *saa5057_device::device_rom_region() const
{
return ROM_NAME( saa5057 );
}
#define ALPHANUMERIC 0x01
#define CONTIGUOUS 0x02
#define SEPARATED 0x03
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
//-------------------------------------------------
// saa5050_device - constructor
//-------------------------------------------------
saa5050_device::saa5050_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) :
device_t(mconfig, type, tag, owner, clock),
m_char_rom(*this, "chargen"),
m_read_d(*this),
m_frame_count(0),
m_cols(0),
m_rows(0),
m_size(0)
{
}
saa5050_device::saa5050_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
saa5050_device(mconfig, SAA5050, tag, owner, clock)
{
}
saa5051_device::saa5051_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: saa5050_device(mconfig, SAA5051, tag, owner, clock)
{
}
saa5052_device::saa5052_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: saa5050_device(mconfig, SAA5052, tag, owner, clock)
{
}
saa5053_device::saa5053_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: saa5050_device(mconfig, SAA5053, tag, owner, clock)
{
}
saa5054_device::saa5054_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: saa5050_device(mconfig, SAA5054, tag, owner, clock)
{
}
saa5055_device::saa5055_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: saa5050_device(mconfig, SAA5055, tag, owner, clock)
{
}
saa5056_device::saa5056_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: saa5050_device(mconfig, SAA5056, tag, owner, clock)
{
}
saa5057_device::saa5057_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: saa5050_device(mconfig, SAA5057, tag, owner, clock)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void saa5050_device::device_start()
{
// resolve callbacks
m_read_d.resolve_safe(0);
// register for state saving
save_item(NAME(m_code));
save_item(NAME(m_held_char));
save_item(NAME(m_char_data));
save_item(NAME(m_bit));
save_item(NAME(m_color));
save_item(NAME(m_ra));
save_item(NAME(m_bg));
save_item(NAME(m_fg));
save_item(NAME(m_prev_col));
save_item(NAME(m_graphics));
save_item(NAME(m_separated));
save_item(NAME(m_flash));
save_item(NAME(m_boxed));
save_item(NAME(m_double_height));
save_item(NAME(m_double_height_old));
save_item(NAME(m_double_height_bottom_row));
save_item(NAME(m_double_height_prev_row));
save_item(NAME(m_hold_char));
save_item(NAME(m_hold_clear));
save_item(NAME(m_hold_off));
save_item(NAME(m_frame_count));
}
//-------------------------------------------------
// device_start - device-specific reset
//-------------------------------------------------
void saa5050_device::device_reset()
{
m_ra = 0;
m_double_height = false;
m_double_height_bottom_row = false;
}
//-------------------------------------------------
// process_control_character -
//-------------------------------------------------
void saa5050_device::process_control_character(uint8_t data)
{
m_hold_clear = false;
m_hold_off = false;
switch (data)
{
case ALPHA_RED:
case ALPHA_GREEN:
case ALPHA_YELLOW:
case ALPHA_BLUE:
case ALPHA_MAGENTA:
case ALPHA_CYAN:
case ALPHA_WHITE:
m_graphics = false;
m_hold_clear = true;
m_fg = data & 0x07;
set_next_chartype();
break;
case FLASH:
m_flash = true;
break;
case STEADY:
m_flash = false;
break;
case END_BOX:
case START_BOX:
// TODO
break;
case NORMAL_HEIGHT:
case DOUBLE_HEIGHT:
m_double_height = !!(data & 1);
if (m_double_height) m_double_height_prev_row = true;
break;
case GRAPHICS_RED:
case GRAPHICS_GREEN:
case GRAPHICS_YELLOW:
case GRAPHICS_BLUE:
case GRAPHICS_MAGENTA:
case GRAPHICS_CYAN:
case GRAPHICS_WHITE:
m_graphics = true;
m_fg = data & 0x07;
set_next_chartype();
break;
case CONCEAL_DISPLAY:
m_fg = m_prev_col = m_bg;
break;
case CONTIGUOUS_GFX:
m_separated = false;
set_next_chartype();
break;
case SEPARATED_GFX:
m_separated = true;
set_next_chartype();
break;
case BLACK_BACKGROUND:
m_bg = 0;
break;
case NEW_BACKGROUND:
m_bg = m_fg;
break;
case HOLD_GRAPHICS:
m_hold_char = true;
break;
case RELEASE_GRAPHICS:
m_hold_off = true;
break;
}
}
void saa5050_device::set_next_chartype()
{
if (m_graphics)
{
if (m_separated)
m_next_chartype = SEPARATED;
else
m_next_chartype = CONTIGUOUS;
}
else
{
m_next_chartype = ALPHANUMERIC;
}
};
//-------------------------------------------------
// get_gfx_data - graphics generator
//-------------------------------------------------
uint16_t saa5050_device::get_gfx_data(uint8_t data, offs_t row, bool separated)
{
uint16_t c = 0;
switch (row >> 1)
{
case 0: case 1:
c += (data & 0x01) ? 0xfc0 : 0; // bit 1 top left
c += (data & 0x02) ? 0x03f : 0; // bit 2 top right
if (separated) c &= 0x3cf;
break;
case 2:
if (separated) break;
c += (data & 0x01) ? 0xfc0 : 0; // bit 1 top left
c += (data & 0x02) ? 0x03f : 0; // bit 2 top right
break;
case 3: case 4: case 5:
c += (data & 0x04) ? 0xfc0 : 0; // bit 3 middle left
c += (data & 0x08) ? 0x03f : 0; // bit 4 middle right
if (separated) c &= 0x3cf;
break;
case 6:
if (separated) break;
c += (data & 0x04) ? 0xfc0 : 0; // bit 3 middle left
c += (data & 0x08) ? 0x03f : 0; // bit 4 middle right
break;
case 7: case 8:
c += (data & 0x10) ? 0xfc0 : 0; // bit 5 bottom left
c += (data & 0x40) ? 0x03f : 0; // bit 7 bottom right
if (separated) c &= 0x3cf;
break;
case 9:
if (separated) break;
c += (data & 0x10) ? 0xfc0 : 0; // bit 5 bottom left
c += (data & 0x40) ? 0x03f : 0; // bit 7 bottom right
break;
}
return c;
}
//-------------------------------------------------
// get_rom_data - read rom
//-------------------------------------------------
uint16_t saa5050_device::get_rom_data(uint8_t data, offs_t row)
{
uint16_t c;
if (row < 0 || row >= 20)
{
c = 0;
}
else
{
c = m_char_rom[(data * 10) + (row >> 1)];
c = ((c & 0x01) * 0x03) + ((c & 0x02) * 0x06) + ((c & 0x04) * 0x0c) + ((c & 0x08) * 0x18) + ((c & 0x10) * 0x30);
}
return c;
}
//-------------------------------------------------
// character_rounding
//-------------------------------------------------
uint16_t saa5050_device::character_rounding(uint16_t a, uint16_t b)
{
return a | ((a >> 1) & b & ~(b >> 1)) | ((a << 1) & b & ~(b << 1));
}
//-------------------------------------------------
// get_character_data -
//-------------------------------------------------
void saa5050_device::get_character_data(uint8_t data)
{
m_double_height_old = m_double_height;
m_prev_col = m_fg;
m_curr_chartype = m_next_chartype;
if (data < 0x20)
{
process_control_character(data);
if (m_hold_char && m_double_height == m_double_height_old)
{
data = m_held_char;
if (data >= 0x40 && data < 0x60) data = 0x20;
m_curr_chartype = m_held_chartype;
}
else
{
data = 0x20;
}
}
else if (m_graphics)
{
m_held_char = data;
m_held_chartype = m_curr_chartype;
}
offs_t ra = m_ra;
if (m_double_height_old)
{
ra >>= 1;
if (m_double_height_bottom_row) ra += 10;
}
if (m_flash && (m_frame_count > 38)) data = 0x20;
if (m_double_height_bottom_row && !m_double_height) data = 0x20;
if (m_hold_off)
{
m_hold_char = false;
m_held_char = 32;
}
if (m_hold_clear)
{
m_held_char = 32;
}
if (m_curr_chartype == ALPHANUMERIC || !BIT(data,5))
m_char_data = character_rounding(get_rom_data(data, ra), get_rom_data(data, ra + ((ra & 1) ? 1 : -1)));
else
m_char_data = get_gfx_data(data, ra, (m_curr_chartype == SEPARATED));
}
//-------------------------------------------------
// crs_w - character rounding select
//-------------------------------------------------
WRITE_LINE_MEMBER( saa5050_device::crs_w )
{
m_crs = !(state & 1);
}
//-------------------------------------------------
// dew_w - data entry window
//-------------------------------------------------
WRITE_LINE_MEMBER( saa5050_device::dew_w )
{
if (state)
{
m_ra = 19;
m_frame_count++;
if (m_frame_count > 50) m_frame_count = 0;
}
}
//-------------------------------------------------
// lose_w - load output shift register enable
//-------------------------------------------------
WRITE_LINE_MEMBER( saa5050_device::lose_w )
{
if (state)
{
m_ra++;
m_ra %= 20;
if (!m_ra)
{
if (m_double_height_bottom_row)
m_double_height_bottom_row = false;
else
m_double_height_bottom_row = m_double_height_prev_row;
}
m_fg = 7;
m_bg = 0;
m_graphics = false;
m_separated = false;
m_flash = false;
m_boxed = false;
m_hold_char = false;
m_held_char = 0x20;
m_next_chartype = ALPHANUMERIC;
m_held_chartype = ALPHANUMERIC;
m_double_height = false;
m_double_height_prev_row = false;
m_bit = 11;
}
}
//-------------------------------------------------
// write - character data write
//-------------------------------------------------
void saa5050_device::write(uint8_t data)
{
m_code = data & 0x7f;
}
//-------------------------------------------------
// f1_w - character clock
//-------------------------------------------------
WRITE_LINE_MEMBER( saa5050_device::f1_w )
{
if (state)
{
get_character_data(m_code);
}
}
//-------------------------------------------------
// tr6_w - pixel clock
//-------------------------------------------------
WRITE_LINE_MEMBER( saa5050_device::tr6_w )
{
if (state)
{
m_color = BIT(m_char_data, m_bit) ? m_prev_col : m_bg;
m_bit--;
if (m_bit < 0) m_bit = 11;
}
}
//-------------------------------------------------
// get_rgb - get output color
//-------------------------------------------------
int saa5050_device::get_rgb()
{
return m_color;
}
//-------------------------------------------------
// screen_update -
//-------------------------------------------------
uint32_t saa5050_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
dew_w(1);
dew_w(0);
for (int y = 0; y < m_rows * 20; y++)
{
int sy = y / 20;
int x = 0;
lose_w(1);
lose_w(0);
int ssy = m_double_height_bottom_row ? sy - 1 : sy;
offs_t video_ram_addr = ssy * m_size;
for (int sx = 0; sx < m_cols; sx++)
{
uint8_t code = m_read_d(video_ram_addr++);
write(code & 0x7f);
f1_w(1);
f1_w(0);
for (int bit = 0; bit < 12; bit++)
{
tr6_w(1);
tr6_w(0);
int color = get_rgb();
if (BIT(code, 7)) color ^= 0x07;
int r = BIT(color, 0) * 0xff;
int g = BIT(color, 1) * 0xff;
int b = BIT(color, 2) * 0xff;
rgb_t rgb = rgb_t(r, g, b);
bitmap.pix32(y, x++) = rgb;
}
}
}
return 0;
}