Add Chips & Technologies 82C425 CGA LCD/CRT controller emulation

The 82C425 is a CGA-compatible display controller chip. On top of being
able to drive a regular CRT display like an ordinary CGA card, it can
be configured to drive a monochrome 640x200 LCD panel instead.

The chip along with a LCD panel are notably used in the Victor V86P
laptop comupter.

When driving a monochrome LCD, the controller is able to employ some clever
tricks to compensate for he lack of color: by alternately turning dots on
and off with various duty cycles it can achieve displaying 4 or 8 shades
of gray. It can also enhance contrast between the text glyphs and their
background when it's less than the configured minimum (with "SMARTMAP"
algorithm).

The emulation is fairly complete. The 320x200 graphical mode uses 4 gray
shades along with stretching the pixels horziontally much like the real
hardware would. SMARTMAP is implemented for text mode and also matches
the real hardware pretty closely.

The missing bits are:

 * Configurable blink rates
 * Mapping the character map into host address space

The code is based on the T1000 display controller emulation and
still bears strong resemblance to it.
This commit is contained in:
Lubomir Rintel
2021-03-31 23:42:39 +02:00
parent ad5e0943f1
commit a05f5d493f
4 changed files with 671 additions and 1 deletions

View File

@@ -249,6 +249,9 @@ extern const device_t compaq_cga_2_device;
extern const device_t ogc_device;
extern const device_t ogc_m24_device;
/* Chips & Technologies 82C425 */
extern const device_t f82c425_video_device;
/* NCR NGA */
extern const device_t nga_device;

View File

@@ -22,7 +22,7 @@ add_library(vid OBJECT video.c vid_table.c vid_cga.c vid_cga_comp.c
vid_av9194.c vid_icd2061.c vid_ics2494.c vid_ics2595.c vid_cl54xx.c
vid_et4000.c vid_sc1148x_ramdac.c vid_sc1502x_ramdac.c vid_et4000w32.c
vid_stg_ramdac.c vid_ht216.c vid_oak_oti.c vid_paradise.c vid_rtg310x.c
vid_ti_cf62011.c vid_tvga.c vid_tgui9440.c vid_tkd8001_ramdac.c
vid_f82c425.c vid_ti_cf62011.c vid_tvga.c vid_tgui9440.c vid_tkd8001_ramdac.c
vid_att20c49x_ramdac.c vid_s3.c vid_s3_virge.c vid_ibm_rgb528_ramdac.c
vid_sdac_ramdac.c vid_ogc.c vid_nga.c vid_tvp3026_ramdac.c)

666
src/video/vid_f82c425.c Normal file
View File

@@ -0,0 +1,666 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* Chips & Technologies 82C425 display controller emulation,
* with support for 640x200 LCD and SMARTMAP text contrast
* enhancement.
*
* Relevant literature:
*
* [1] Chips and Technologies, Inc., 82C425 CGA LCD/CRT Controller,
* Data Sheet, Revision No. 2.2, September 1991.
* <https://archive.org/download/82C425/82C425.pdf>
*
* [2] Pleva et al., COLOR TO MONOCHROME CONVERSION,
* U.S. Patent 4,977,398, Dec. 11, 1990.
* <https://pimg-fpiw.uspto.gov/fdd/98/773/049/0.pdf>
*
* Based on Toshiba T1000 plasma display emulation code.
*
* Authors: Fred N. van Kempen, <decwiz@yahoo.com>
* Miran Grca, <mgrca8@gmail.com>
* Sarah Walker, <tommowalker@tommowalker.co.uk>
* Lubomir Rintel, <lkundrak@v3.sk>
*
* Copyright 2018,2019 Fred N. van Kempen.
* Copyright 2018,2019 Miran Grca.
* Copyright 2018,2019 Sarah Walker.
* Copyright 2021 Lubomir Rintel.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the:
*
* Free Software Foundation, Inc.
* 59 Temple Place - Suite 330
* Boston, MA 02111-1307
* USA.
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/io.h>
#include <86box/mem.h>
#include <86box/timer.h>
#include "cpu.h"
#include <86box/video.h>
#include <86box/vid_cga.h>
#define F82C425_XSIZE 640
#define F82C425_YSIZE 200
/* Mapping of attributes to colours */
static uint32_t smartmap[256][2];
static uint32_t colormap[4];
static video_timings_t timing_f82c425 = {VIDEO_ISA, 8,16,32, 8,16,32};
static uint8_t st_video_options;
static uint8_t st_enabled = 1;
static int8_t st_display_internal = -1;
void f82c425_video_options_set(uint8_t options)
{
st_video_options = options;
}
void f82c425_video_enable(uint8_t enabled)
{
st_enabled = enabled;
}
void f82c425_display_set(uint8_t internal)
{
st_display_internal = (int8_t)internal;
}
uint8_t f82c425_display_get()
{
return (uint8_t)st_display_internal;
}
typedef struct f82c425_t
{
mem_mapping_t mapping;
cga_t cga;
uint8_t crtcreg;
uint64_t dispontime, dispofftime;
int linepos, displine;
int dispon;
uint8_t video_options;
uint8_t *vram;
/* Registers specific to 82C425. */
uint8_t ac_limit;
uint8_t threshold;
uint8_t shift;
uint8_t hsync;
uint8_t vsync_blink;
uint8_t timing;
uint8_t function;
} f82c425_t;
/* Convert IRGB representation to RGBI,
* useful in SMARTMAP calculations. */
static inline uint8_t f82c425_rgbi(uint8_t irgb)
{
return ((irgb & 0x7) << 1) | (irgb >> 3);
}
/* Convert IRGB SMARTMAP output to a RGB representation of one of 4/8 grey
* shades we'd see on an actual V86P display: with some bias toward lighter
* shades and a backlight with yellow/green-ish tint. */
static inline uint32_t f82c425_makecol(uint8_t rgbi, int gs4, int inv)
{
uint8_t c;
gs4 = 1 + !!gs4;
if (!inv)
{
rgbi = 15 - rgbi;
}
c = 0x10 * gs4 * ((rgbi >> gs4) + 2);
return makecol(c, c + 0x08, c - 0x20);
}
/* Saturating/non-saturating addition for SMARTMAP(see below). */
static inline int f82c425_smartmap_add(int a, int b, int sat)
{
int c = a + b;
/* (SATURATING OR NON SATURATING) */
if (sat)
{
if (c < 0)
c = 0;
else if (c > 15)
c = 15;
}
return c & 0xf;
}
/* Calculate and cache mapping of CGA text color attribute to a
* shade of gray enhanced via the SMARTMAP algorithm.
*
* This is a straightforward implementation of the algorithm as described
* in U.S. Patent 4,977,398 [2]. The comments in capitals refer to portions
* of a figure on page 4. */
static void f82c425_smartmap(f82c425_t *f82c425)
{
int i;
for (i = 0; i < 256; i++) {
uint8_t bg = f82c425_rgbi(i >> 4);
uint8_t fg = f82c425_rgbi(i & 0xf);
/* FIG._4. */
if (abs(bg - fg) <= (f82c425->threshold & 0x0f))
{
/* FOREGROUND=BACKGROUND */
if (bg == fg)
{
/* SPECIAL CASE */
if (f82c425->shift == 0xff)
{
/* CHECK MOST SIGNIFICANT BIT */
if (fg & 0x8)
{
/* FULL WHITE */
fg = bg = 15;
}
else
{
/* FULL BLACK */
fg = bg = 0;
}
}
}
else
{
uint8_t sat = f82c425->threshold & 0x10;
/* DETERMINE WHICH IS LIGHT */
if (fg > bg)
{
fg = f82c425_smartmap_add(fg, f82c425->shift & 0x0f, sat);
bg = f82c425_smartmap_add(bg, -(f82c425->shift >> 4), sat);
}
else
{
fg = f82c425_smartmap_add(fg, -(f82c425->shift & 0x0f), sat);
bg = f82c425_smartmap_add(bg, f82c425->shift >> 4, sat);
}
}
}
smartmap[i][0] = f82c425_makecol(bg, f82c425->threshold & 0x20, f82c425->function & 0x80);
smartmap[i][1] = f82c425_makecol(fg, f82c425->threshold & 0x20, f82c425->function & 0x80);
}
}
/* Calculate mapping of 320x200 graphical mode colors. */
static void f82c425_colormap(f82c425_t *f82c425)
{
int i;
for (i = 0; i < 4; i++)
colormap[i] = f82c425_makecol(5 * i, 0, f82c425->function & 0x80);
}
static void f82c425_out(uint16_t addr, uint8_t val, void *p)
{
f82c425_t *f82c425 = (f82c425_t *)p;
if (addr == 0x3d4)
f82c425->crtcreg = val;
if (f82c425->function & 0x01 == 0 && (f82c425->crtcreg != 0xdf || addr != 0x3d5))
return;
if (addr != 0x3d5 || f82c425->crtcreg <= 31)
{
cga_out(addr, val, &f82c425->cga);
return;
}
switch (f82c425->crtcreg)
{
case 0xd9:
f82c425->ac_limit = val;
break;
case 0xda:
f82c425->threshold = val;
f82c425_smartmap(f82c425);
break;
case 0xdb:
f82c425->shift = val;
f82c425_smartmap(f82c425);
break;
case 0xdc:
f82c425->hsync = val;
break;
case 0xdd:
f82c425->vsync_blink = val;
break;
case 0xde:
f82c425->timing = val;
break;
case 0xdf:
f82c425->function = val;
f82c425_smartmap(f82c425);
f82c425_colormap(f82c425);
break;
}
}
static uint8_t f82c425_in(uint16_t addr, void *p)
{
f82c425_t *f82c425 = (f82c425_t *)p;
if (f82c425->function & 0x01 == 0)
return 0xff;
if (addr == 0x3d4)
return f82c425->crtcreg;
if (addr != 0x3d5 || f82c425->crtcreg <= 31)
return cga_in(addr, &f82c425->cga);
switch (f82c425->crtcreg)
{
case 0xd9:
return f82c425->ac_limit;
case 0xda:
return f82c425->threshold;
case 0xdb:
return f82c425->shift;
case 0xdc:
return f82c425->hsync;
case 0xdd:
return f82c425->vsync_blink;
case 0xde:
return f82c425->timing;
case 0xdf:
return f82c425->function;
}
}
static void f82c425_write(uint32_t addr, uint8_t val, void *p)
{
f82c425_t *f82c425 = (f82c425_t *)p;
f82c425->vram[addr & 0x3fff] = val;
cycles -= 4;
}
static uint8_t f82c425_read(uint32_t addr, void *p)
{
f82c425_t *f82c425 = (f82c425_t *)p;
cycles -= 4;
return f82c425->vram[addr & 0x3fff];
}
static void f82c425_recalctimings(f82c425_t *f82c425)
{
double disptime;
double _dispontime, _dispofftime;
if (f82c425->function & 0x08)
{
cga_recalctimings(&f82c425->cga);
return;
}
disptime = 651;
_dispontime = 640;
_dispofftime = disptime - _dispontime;
f82c425->dispontime = (uint64_t)(_dispontime * xt_cpu_multi);
f82c425->dispofftime = (uint64_t)(_dispofftime * xt_cpu_multi);
}
/* Draw a row of text. */
static void f82c425_text_row(f82c425_t *f82c425)
{
uint32_t colors[2];
int x, c;
uint8_t chr, attr;
int drawcursor;
int cursorline;
int blink;
uint16_t addr;
uint8_t sc;
uint16_t ma = (f82c425->cga.crtc[0x0d] | (f82c425->cga.crtc[0x0c] << 8)) & 0x3fff;
uint16_t ca = (f82c425->cga.crtc[0x0f] | (f82c425->cga.crtc[0x0e] << 8)) & 0x3fff;
uint8_t sl = f82c425->cga.crtc[9] + 1;
int columns = f82c425->cga.crtc[1];
sc = (f82c425->displine) & 7;
addr = ((ma & ~1) + (f82c425->displine >> 3) * columns) * 2;
ma += (f82c425->displine >> 3) * columns;
if ((f82c425->cga.crtc[0x0a] & 0x60) == 0x20)
{
cursorline = 0;
}
else
{
cursorline = ((f82c425->cga.crtc[0x0a] & 0x0F) <= sc) &&
((f82c425->cga.crtc[0x0b] & 0x0F) >= sc);
}
for (x = 0; x < columns; x++)
{
chr = f82c425->vram[(addr + 2 * x) & 0x3FFF];
attr = f82c425->vram[(addr + 2 * x + 1) & 0x3FFF];
drawcursor = ((ma == ca) && cursorline &&
(f82c425->cga.cgamode & 0x8) && (f82c425->cga.cgablink & 0x10));
blink = ((f82c425->cga.cgablink & 0x10) && (f82c425->cga.cgamode & 0x20) &&
(attr & 0x80) && !drawcursor);
if (drawcursor)
{
colors[0] = smartmap[~attr & 0xff][0];
colors[1] = smartmap[~attr & 0xff][1];
}
else
{
colors[0] = smartmap[attr][0];
colors[1] = smartmap[attr][1];
}
if (blink)
colors[1] = colors[0];
if (f82c425->cga.cgamode & 0x01)
{
/* High resolution (80 cols) */
for (c = 0; c < sl; c++)
{
((uint32_t *)buffer32->line[f82c425->displine])[(x << 3) + c] =
colors[(fontdat[chr][sc] & (1 <<(c ^ 7))) ? 1 : 0];
}
}
else
{
/* Low resolution (40 columns, stretch pixels horizontally) */
for (c = 0; c < sl; c++)
{
((uint32_t *)buffer32->line[f82c425->displine])[(x << 4) + c*2] =
((uint32_t *)buffer32->line[f82c425->displine])[(x << 4) + c*2+1] =
colors[(fontdat[chr][sc] & (1 <<(c ^ 7))) ? 1 : 0];
}
}
++ma;
}
}
/* Draw a line in CGA 640x200 mode */
static void f82c425_cgaline6(f82c425_t *f82c425)
{
int x, c;
uint8_t dat;
uint16_t addr;
uint16_t ma = (f82c425->cga.crtc[0x0d] | (f82c425->cga.crtc[0x0c] << 8)) & 0x3fff;
addr = ((f82c425->displine) & 1) * 0x2000 +
(f82c425->displine >> 1) * 80 +
((ma & ~1) << 1);
for (x = 0; x < 80; x++)
{
dat = f82c425->vram[addr & 0x3FFF];
addr++;
for (c = 0; c < 8; c++)
{
((uint32_t *)buffer32->line[f82c425->displine])[x*8+c] =
colormap[dat & 0x80 ? 3 : 0];
dat = dat << 1;
}
}
}
/* Draw a line in CGA 320x200 mode. */
static void f82c425_cgaline4(f82c425_t *f82c425)
{
int x, c;
uint8_t dat, pattern;
uint16_t addr;
uint16_t ma = (f82c425->cga.crtc[0x0d] | (f82c425->cga.crtc[0x0c] << 8)) & 0x3fff;
addr = ((f82c425->displine) & 1) * 0x2000 +
(f82c425->displine >> 1) * 80 +
((ma & ~1) << 1);
for (x = 0; x < 80; x++)
{
dat = f82c425->vram[addr & 0x3FFF];
addr++;
for (c = 0; c < 4; c++)
{
pattern = (dat & 0xC0) >> 6;
if (!(f82c425->cga.cgamode & 0x08)) pattern = 0;
((uint32_t *)buffer32->line[f82c425->displine])[x*8+2*c] =
((uint32_t *)buffer32->line[f82c425->displine])[x*8+2*c+1] =
colormap[pattern & 3];
dat = dat << 2;
}
}
}
static void f82c425_poll(void *p)
{
f82c425_t *f82c425 = (f82c425_t *)p;
if (f82c425->video_options != st_video_options ||
!!(f82c425->function & 1) != st_enabled)
{
f82c425->video_options = st_video_options;
f82c425->function &= ~1;
f82c425->function |= st_enabled ? 1 : 0;
if (f82c425->function & 0x01)
mem_mapping_enable(&f82c425->mapping);
else
mem_mapping_disable(&f82c425->mapping);
}
/* Switch between internal LCD and external CRT display. */
if (st_display_internal != -1 && st_display_internal != !!(f82c425->function & 0x08))
{
if (st_display_internal)
{
f82c425->function &= ~0x08;
f82c425->timing &= ~0x20;
}
else
{
f82c425->function |= 0x08;
f82c425->timing |= 0x20;
}
f82c425_recalctimings(f82c425);
}
if (f82c425->function & 0x08)
{
cga_poll(&f82c425->cga);
return;
}
if (!f82c425->linepos)
{
timer_advance_u64(&f82c425->cga.timer, f82c425->dispofftime);
f82c425->cga.cgastat |= 1;
f82c425->linepos = 1;
if (f82c425->dispon)
{
if (f82c425->displine == 0)
{
video_wait_for_buffer();
}
switch (f82c425->cga.cgamode & 0x13)
{
case 0x12:
f82c425_cgaline6(f82c425);
break;
case 0x02:
f82c425_cgaline4(f82c425);
break;
case 0x00:
case 0x01:
f82c425_text_row(f82c425);
break;
}
}
f82c425->displine++;
/* Hardcode a fixed refresh rate and VSYNC timing */
if (f82c425->displine >= 216)
{
/* End of VSYNC */
f82c425->displine = 0;
f82c425->cga.cgastat &= ~8;
f82c425->dispon = 1;
}
else
if (f82c425->displine == (f82c425->cga.crtc[9] + 1) * f82c425->cga.crtc[6])
{
/* Start of VSYNC */
f82c425->cga.cgastat |= 8;
f82c425->dispon = 0;
}
}
else
{
if (f82c425->dispon)
f82c425->cga.cgastat &= ~1;
timer_advance_u64(&f82c425->cga.timer, f82c425->dispontime);
f82c425->linepos = 0;
if (f82c425->displine == 200)
{
/* Hardcode 640x200 window size */
if ((F82C425_XSIZE != xsize) || (F82C425_YSIZE != ysize) || video_force_resize_get())
{
xsize = F82C425_XSIZE;
ysize = F82C425_YSIZE;
set_screen_size(xsize, ysize);
if (video_force_resize_get())
video_force_resize_set(0);
}
video_blit_memtoscreen(0, 0, 0, ysize, xsize, ysize);
frames++;
/* Fixed 640x200 resolution */
video_res_x = F82C425_XSIZE;
video_res_y = F82C425_YSIZE;
switch (f82c425->cga.cgamode & 0x12)
{
case 0x12:
video_bpp = 1;
break;
case 0x02:
video_bpp = 2;
break;
default:
video_bpp = 0;
}
f82c425->cga.cgablink++;
}
}
}
static void *f82c425_init(const device_t *info)
{
f82c425_t *f82c425 = malloc(sizeof(f82c425_t));
memset(f82c425, 0, sizeof(f82c425_t));
cga_init(&f82c425->cga);
video_inform(VIDEO_FLAG_TYPE_CGA, &timing_f82c425);
/* Initialize registers that don't default to zero. */
f82c425->hsync = 0x40;
f82c425->vsync_blink = 0x72;
/* 16k video RAM */
f82c425->vram = malloc(0x4000);
timer_set_callback(&f82c425->cga.timer, f82c425_poll);
timer_set_p(&f82c425->cga.timer, f82c425);
/* Occupy memory between 0xB8000 and 0xBFFFF */
mem_mapping_add(&f82c425->mapping, 0xb8000, 0x8000, f82c425_read, NULL, NULL, f82c425_write, NULL, NULL, NULL, 0, f82c425);
/* Respond to CGA I/O ports */
io_sethandler(0x03d0, 0x000c, f82c425_in, NULL, NULL, f82c425_out, NULL, NULL, f82c425);
/* Initialize color maps for text & graphic modes */
f82c425_smartmap(f82c425);
f82c425_colormap(f82c425);
/* Start off in 80x25 text mode */
f82c425->cga.cgastat = 0xF4;
f82c425->cga.vram = f82c425->vram;
f82c425->video_options = 0x01;
return f82c425;
}
static void f82c425_close(void *p)
{
f82c425_t *f82c425 = (f82c425_t *)p;
free(f82c425->vram);
free(f82c425);
}
static void f82c425_speed_changed(void *p)
{
f82c425_t *f82c425 = (f82c425_t *)p;
f82c425_recalctimings(f82c425);
}
const device_t f82c425_video_device = {
"82C425 CGA LCD/CRT Controller",
0, 0,
f82c425_init, f82c425_close, NULL,
{ NULL },
f82c425_speed_changed,
NULL,
NULL
};

View File

@@ -773,6 +773,7 @@ VIDOBJ := video.o \
vid_paradise.o \
vid_rtg310x.o \
vid_ti_cf62011.o \
vid_f82c425.o \
vid_tvga.o \
vid_tgui9440.o vid_tkd8001_ramdac.o \
vid_att20c49x_ramdac.o \