// license:BSD-3-Clause
// copyright-holders:Wilbert Pol, hap
/***************************************************************************
Thomson EF9340 + EF9341 teletext graphics, this device is a combination of chips.
Minimal components:
- Thomson EF9340 "VIN"
- Thomson EF9341 "GEN" (CPU connects to this one)
- 2*1KB RAM, A for attributes, B for character codes
There's also an optional extended character memory, it can be RAM or ROM.
This is implemented with a callback. The datasheet explains how to hook up
1KB RAM, but it's possible to have more.
TODO:
- busy state (right now it is immediate)
- internal display timing (on g7400, most of it is done externally)
- window boxing
- Y zoom
- RES(restart) pin
***************************************************************************/
#include "emu.h"
#include "ef9340_1.h"
#include "screen.h"
//#define VERBOSE (LOG_GENERAL)
#include "logmacro.h"
// device type definition
DEFINE_DEVICE_TYPE(EF9340_1, ef9340_1_device, "ef9340_1", "Thomson EF9340+EF9341")
ef9340_1_device::ef9340_1_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
: device_t(mconfig, EF9340_1, tag, owner, clock)
, device_video_interface(mconfig, *this)
, m_charset(*this, "ef9340_1")
, m_write_exram(*this)
, m_read_exram(*this, 0xff)
{
m_offset_x = 0;
m_offset_y = 0;
}
ROM_START( ef9340_1 )
ROM_REGION( 0xa00, "ef9340_1", 0 )
ROM_LOAD( "charset_ef9340_1.rom", 0x0000, 0x0a00, BAD_DUMP CRC(8de85988) SHA1(f8e3892234da6626eb4302e171179ada5a51fca8) ) // taken from datasheet
ROM_END
const tiny_rom_entry *ef9340_1_device::device_rom_region() const
{
return ROM_NAME( ef9340_1 );
}
void ef9340_1_device::device_start()
{
// let the screen create our temporary bitmap with the screen's dimensions
screen().register_screen_bitmap(m_tmp_bitmap);
m_line_timer = timer_alloc(FUNC(ef9340_1_device::draw_scanline), this);
m_line_timer->adjust(screen().time_until_pos(0, 0), 0, screen().scan_period());
m_blink_timer = timer_alloc(FUNC(ef9340_1_device::blink_update), this);
m_blink_timer->adjust(screen().time_until_pos(0, 0), 0, screen().frame_period());
// zerofill
m_ef9341.TA = 0;
m_ef9341.TB = 0;
m_ef9341.busy = false;
m_ef9340.X = 0;
m_ef9340.Y = 0;
m_ef9340.Y0 = 0;
m_ef9340.R = 0;
m_ef9340.M = 0;
m_ef9340.blink = false;
m_ef9340.blink_prescaler = 0;
m_ef9340.h_parity = false;
memset(m_ram_a, 0, sizeof(m_ram_a));
memset(m_ram_b, 0, sizeof(m_ram_b));
// register our state
save_item(NAME(m_ef9341.TA));
save_item(NAME(m_ef9341.TB));
save_item(NAME(m_ef9341.busy));
save_item(NAME(m_ef9340.X));
save_item(NAME(m_ef9340.Y));
save_item(NAME(m_ef9340.Y0));
save_item(NAME(m_ef9340.R));
save_item(NAME(m_ef9340.M));
save_item(NAME(m_ef9340.blink));
save_item(NAME(m_ef9340.blink_prescaler));
save_item(NAME(m_ef9340.h_parity));
save_item(NAME(m_ram_a));
save_item(NAME(m_ram_b));
}
TIMER_CALLBACK_MEMBER(ef9340_1_device::blink_update)
{
// blink rate is approximately 0.5s
m_ef9340.blink_prescaler = (m_ef9340.blink_prescaler + 1) & 0x1f;
if (m_ef9340.R & 0x40 && m_ef9340.blink_prescaler == 24)
m_ef9340.blink_prescaler = 0;
if (m_ef9340.blink_prescaler == 0)
m_ef9340.blink = !m_ef9340.blink;
}
u16 ef9340_1_device::ef9340_get_c_addr(u8 x, u8 y)
{
if ((y & 0x18) == 0x18)
{
return 0x318 | ((x & 0x38) << 2) | (x & 0x07);
}
if (x & 0x20)
{
return 0x300 | ((y & 0x07) << 5) | (y & 0x18) | (x & 0x07);
}
return (y & 0x1f) << 5 | (x & 0x1f);
}
void ef9340_1_device::ef9340_inc_c()
{
m_ef9340.X++;
if (m_ef9340.X == 40 || m_ef9340.X == 48 || m_ef9340.X == 56 || m_ef9340.X == 64)
{
m_ef9340.Y = (m_ef9340.Y + 1) & 0x1f;
if (m_ef9340.Y == 24)
{
m_ef9340.Y = 0;
}
m_ef9340.X = 0;
}
}
void ef9340_1_device::ef9341_write(u8 command, u8 b, u8 data)
{
LOG("ef9341 %s write, t%s, data %02X\n", command ? "command" : "data", b ? "B" : "A", data);
if (command)
{
if (b)
{
m_ef9341.TB = data;
m_ef9341.busy = true;
switch (m_ef9341.TB & 0xe0)
{
case 0x00: // begin row
m_ef9340.X = 0;
m_ef9340.Y = m_ef9341.TA & 0x1f;
break;
case 0x20: // load Y
m_ef9340.Y = m_ef9341.TA & 0x1f;
break;
case 0x40: // load X
m_ef9340.X = m_ef9341.TA & 0x3f;
break;
case 0x60: // increment C
ef9340_inc_c();
break;
case 0x80: // load M
m_ef9340.M = m_ef9341.TA;
break;
case 0xa0: // load R
m_ef9340.R = m_ef9341.TA;
break;
case 0xc0: // load Y0
m_ef9340.Y0 = m_ef9341.TA & 0x3f;
break;
case 0xe0: // not interpreted
break;
}
m_ef9341.busy = false;
}
else
{
m_ef9341.TA = data;
}
}
else
{
if (b)
{
u16 addr = ef9340_get_c_addr(m_ef9340.X, m_ef9340.Y) & 0x3ff;
m_ef9341.TB = data;
m_ef9341.busy = true;
switch (m_ef9340.M & 0xe0)
{
case 0x00: // write
m_ram_a[addr] = m_ef9341.TA;
m_ram_b[addr] = m_ef9341.TB;
ef9340_inc_c();
break;
case 0x40: // write without increment
m_ram_a[addr] = m_ef9341.TA;
m_ram_b[addr] = m_ef9341.TB;
break;
case 0x80: // write slice
{
u8 a = m_ram_a[addr];
u8 b = m_ram_b[addr];
u8 slice = m_ef9340.M & 0x0f;
if (b >= 0xa0)
m_write_exram(a << 12 | b << 4 | slice, m_ef9341.TA);
// increment slice number
m_ef9340.M = (m_ef9340.M & 0xf0) | ((slice + 1) % 10);
break;
}
default: // illegal or read command
break;
}
m_ef9341.busy = false;
}
else
{
m_ef9341.TA = data;
}
}
}
u8 ef9340_1_device::ef9341_read(u8 command, u8 b)
{
u8 data;
LOG("ef9341 %s read, t%s\n", command ? "command" : "data", b ? "B" : "A");
if (command)
{
if (b)
{
data = 0;
}
else
{
data = (m_ef9341.busy) ? 0x80 : 0;
}
}
else
{
if (b)
{
u16 addr = ef9340_get_c_addr(m_ef9340.X, m_ef9340.Y) & 0x3ff;
data = m_ef9341.TB;
m_ef9341.busy = true;
switch (m_ef9340.M & 0xe0)
{
case 0x20: // read
m_ef9341.TA = m_ram_a[addr];
m_ef9341.TB = m_ram_b[addr];
ef9340_inc_c();
break;
case 0x60: // read without increment
m_ef9341.TA = m_ram_a[addr];
m_ef9341.TB = m_ram_b[addr];
break;
case 0xa0: // read slice
{
u8 a = m_ram_a[addr];
u8 b = m_ram_b[addr];
u8 slice = m_ef9340.M & 0x0f;
m_ef9341.TA = 0xff;
m_ef9341.TB = 0xff;
if (b >= 0xa0)
m_ef9341.TA = m_read_exram(a << 12 | b << 4 | slice);
else if (b < 0x80 && slice < 10)
m_ef9341.TA = m_charset[(((a & 0x80) | (b & 0x7f)) * 10) + slice];
// increment slice number
m_ef9340.M = (m_ef9340.M & 0xf0) | ((slice + 1) % 10);
break;
}
default: // illegal or write command
break;
}
m_ef9341.busy = false;
}
else
{
data = m_ef9341.TA;
}
}
return data;
}
TIMER_CALLBACK_MEMBER(ef9340_1_device::draw_scanline)
{
int vpos = screen().vpos();
vpos -= m_offset_y;
if (vpos < 0)
return;
int slice = vpos % 10;
bool dh = false;
if (vpos == 0)
m_ef9340.h_parity = false;
// display automaton active at 40-290, or 32-242
int max_vpos = (m_ef9340.R & 0x40) ? 250 : 210;
if (m_ef9340.R & 0x01 && vpos < max_vpos)
{
int y_row = 0;
u16 char_data = 0x00;
u8 fg = 0;
u8 bg = 0;
bool underline = false;
bool blank = false;
bool w_parity = false;
if (vpos < 10)
{
// service row
if (m_ef9340.R & 0x08)
{
// service row is enabled
y_row = 31;
}
else
{
// service row is disabled
for (int i = 0; i < 40 * 8; i++)
m_tmp_bitmap.pix(m_offset_y + vpos, m_offset_x + i) = 8;
return;
}
}
else
{
// displaying regular row
y_row = ((m_ef9340.Y0 & 0x1f) + (vpos - 10) / 10) % 24;
}
for (int x = 0; x < 40; x++)
{
int s = slice;
u16 addr = ef9340_get_c_addr(x, y_row);
u8 a = m_ram_a[addr];
u8 b = m_ram_b[addr];
bool blink = m_ef9340.R & 0x80 && m_ef9340.blink;
bool cursor = m_ef9340.R & 0x10 && x == m_ef9340.X && y_row == m_ef9340.Y;
bool invert = cursor && !blink;
bool dw = false;
bool del = false;
if (a & 0x80)
{
// graphics
if ((b & 0xe0) != 0x80)
{
fg = a & 0x07;
bg = a >> 4 & 0x07;
if (b & 0x80)
char_data = m_read_exram(a << 12 | b << 4 | s);
else
char_data = m_charset[((b | 0x80) * 10) + s];
}
// illegal
else
char_data = 0xff;
}
else
{
// alphanumeric
if ((b & 0xe0) != 0x80)
{
// double height
if (a & 0x10)
{
dh = true;
if (m_ef9340.h_parity)
s += 10;
if (s > 0)
s = (s - 1) / 2;
}
fg = a & 0x07;
u16 c = 0;
if (b & 0x80)
c = m_read_exram(a << 12 | b << 4 | s);
else if (s == 9 && underline)
c = 0xff;
else
c = m_charset[((b & 0x7f) * 10) + s];
// double width
dw = bool(a & 0x20);
if (dw)
{
if (!w_parity)
char_data = bitswap<16>(c,7,7,6,6,5,5,4,4,3,3,2,2,1,1,0,0);
}
else
char_data = c;
// inverted
if (a & 0x40)
{
invert = !invert;
blink = m_ef9340.R & 0x80 && !m_ef9340.blink;
}
}
// deliminator
else
{
fg = a & 0x07;
bg = a >> 4 & 0x07;
char_data = 0xff;
del = true;
}
}
// blink character
if (blink && !cursor && (b & 0xe0) != 0x80 && ~a & 0x08)
char_data &= ~0xff;
if (invert)
char_data ^= 0xff;
if (dw)
w_parity = !w_parity;
else
w_parity = false;
for (int i = 0; i < 8; i++)
{
u16 d = blank ? 0 : (char_data & 1) ? fg : bg;
m_tmp_bitmap.pix(m_offset_y + vpos, m_offset_x + x*8 + i) = d | 8;
char_data >>= 1;
}
if (del)
{
blank = m_ef9340.R & 0x04 && b & 0x01;
underline = bool(b & 0x04);
}
}
}
else
{
for (int i = 0; i < 40 * 8; i++)
m_tmp_bitmap.pix(m_offset_y + vpos, m_offset_x + i) = 0;
}
// determine next h parity
if (vpos >= 10 && slice == 9)
{
if (dh)
m_ef9340.h_parity = !m_ef9340.h_parity;
else
m_ef9340.h_parity = false;
}
}
u32 ef9340_1_device::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
// note: palette d3 is transparency (datasheet calls it "I"), this handler masks it off
for (int y = cliprect.min_y; y <= cliprect.max_y; y++)
for (int x = cliprect.min_x; x <= cliprect.max_x; x++)
bitmap.pix(y, x) = m_tmp_bitmap.pix(y, x) & 7;
return 0;
}