diff --git a/src/include/86box/video.h b/src/include/86box/video.h index 59fead3b9..2232667ef 100644 --- a/src/include/86box/video.h +++ b/src/include/86box/video.h @@ -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; diff --git a/src/video/CMakeLists.txt b/src/video/CMakeLists.txt index 4cbc18535..50572dbd8 100644 --- a/src/video/CMakeLists.txt +++ b/src/video/CMakeLists.txt @@ -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) diff --git a/src/video/vid_f82c425.c b/src/video/vid_f82c425.c new file mode 100644 index 000000000..243727c31 --- /dev/null +++ b/src/video/vid_f82c425.c @@ -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. + * + * + * [2] Pleva et al., COLOR TO MONOCHROME CONVERSION, + * U.S. Patent 4,977,398, Dec. 11, 1990. + * + * + * Based on Toshiba T1000 plasma display emulation code. + * + * Authors: Fred N. van Kempen, + * Miran Grca, + * Sarah Walker, + * Lubomir Rintel, + * + * 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 +#include +#include +#include +#include +#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 +}; diff --git a/src/win/Makefile.mingw b/src/win/Makefile.mingw index e050dab7c..2cf0d956c 100644 --- a/src/win/Makefile.mingw +++ b/src/win/Makefile.mingw @@ -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 \