1303 lines
30 KiB
C
1303 lines
30 KiB
C
/*
|
|
* 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.
|
|
*
|
|
* Emulation of the Amstrad series of PC's: PC1512, PC1640 and
|
|
* PC200, including their keyboard, mouse and video devices, as
|
|
* well as the PC2086 and PC3086 systems.
|
|
*
|
|
* PC1512: The PC1512 extends CGA with a bit-planar 640x200x16 mode.
|
|
* Most CRTC registers are fixed.
|
|
*
|
|
* The Technical Reference Manual lists the video waitstate
|
|
* time as between 12 and 46 cycles. We currently always use
|
|
* the lower number.
|
|
*
|
|
* PC1640: Mostly standard EGA, but with CGA & Hercules emulation.
|
|
*
|
|
* PC200: CGA with some NMI stuff. But we don't need that as it's only
|
|
* used for TV and LCD displays, and we're emulating a CRT.
|
|
*
|
|
* TODO: This module is not complete yet:
|
|
*
|
|
* PC1512: The BIOS assumes 512K RAM, because I cannot figure out how to
|
|
* read the status of the LK4 jumper on the mainboard, which is
|
|
* somehow linked to the bus gate array on the NDMACS line...
|
|
*
|
|
* PC1612: EGA mode does not seem to work in the PC1640; it works fine
|
|
* in alpha mode, but in highres ("ECD350") mode, it displays
|
|
* some semi-random junk. Video-memory pointer maybe?
|
|
*
|
|
* Version: @(#)m_amstrad.c 1.0.10 2018/02/02
|
|
*
|
|
* Authors: Sarah Walker, <http://pcem-emulator.co.uk/>
|
|
* Miran Grca, <mgrca8@gmail.com>
|
|
* Fred N. van Kempen, <decwiz@yahoo.com>
|
|
*
|
|
* Copyright 2008-2018 Sarah Walker.
|
|
* Copyright 2016-2018 Miran Grca.
|
|
* Copyright 2017,2018 Fred N. van Kempen.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <wchar.h>
|
|
#include "../86box.h"
|
|
#include "../cpu/cpu.h"
|
|
#include "../io.h"
|
|
#include "../nmi.h"
|
|
#include "../pic.h"
|
|
#include "../pit.h"
|
|
#include "../ppi.h"
|
|
#include "../mem.h"
|
|
#include "../rom.h"
|
|
#include "../timer.h"
|
|
#include "../device.h"
|
|
#include "../nvr.h"
|
|
#include "../keyboard.h"
|
|
#include "../mouse.h"
|
|
#include "../game/gameport.h"
|
|
#include "../lpt.h"
|
|
#include "../floppy/fdd.h"
|
|
#include "../floppy/fdc.h"
|
|
#include "../sound/sound.h"
|
|
#include "../sound/snd_speaker.h"
|
|
#include "../video/video.h"
|
|
#include "../video/vid_cga.h"
|
|
#include "../video/vid_ega.h"
|
|
#include "../video/vid_paradise.h"
|
|
#include "machine.h"
|
|
|
|
|
|
#define STAT_PARITY 0x80
|
|
#define STAT_RTIMEOUT 0x40
|
|
#define STAT_TTIMEOUT 0x20
|
|
#define STAT_LOCK 0x10
|
|
#define STAT_CD 0x08
|
|
#define STAT_SYSFLAG 0x04
|
|
#define STAT_IFULL 0x02
|
|
#define STAT_OFULL 0x01
|
|
|
|
|
|
typedef struct {
|
|
rom_t bios_rom; /* 1640 */
|
|
cga_t cga; /* 1640/200 */
|
|
ega_t ega; /* 1640 */
|
|
uint8_t crtc[32];
|
|
int crtcreg;
|
|
int cga_enabled; /* 1640 */
|
|
uint8_t cgacol,
|
|
cgamode,
|
|
stat;
|
|
uint8_t plane_write, /* 1512/200 */
|
|
plane_read, /* 1512/200 */
|
|
border; /* 1512/200 */
|
|
int linepos,
|
|
displine;
|
|
int sc, vc;
|
|
int cgadispon;
|
|
int con, coff,
|
|
cursoron,
|
|
cgablink;
|
|
int64_t vsynctime;
|
|
int vadj;
|
|
uint16_t ma, maback;
|
|
int dispon;
|
|
int blink;
|
|
int64_t dispontime, /* 1512/1640 */
|
|
dispofftime; /* 1512/1640 */
|
|
int64_t vidtime; /* 1512/1640 */
|
|
int firstline,
|
|
lastline;
|
|
uint8_t *vram;
|
|
} amsvid_t;
|
|
|
|
typedef struct {
|
|
/* Machine stuff. */
|
|
uint8_t dead;
|
|
uint8_t stat1,
|
|
stat2;
|
|
|
|
/* Keyboard stuff. */
|
|
int8_t wantirq;
|
|
uint8_t key_waiting;
|
|
uint8_t pa;
|
|
uint8_t pb;
|
|
|
|
/* Mouse stuff. */
|
|
uint8_t mousex,
|
|
mousey;
|
|
int oldb;
|
|
|
|
/* Video stuff. */
|
|
amsvid_t *vid;
|
|
} amstrad_t;
|
|
|
|
|
|
static uint8_t key_queue[16];
|
|
static int key_queue_start = 0,
|
|
key_queue_end = 0;
|
|
static uint8_t crtc_mask[32] = {
|
|
0xff, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0x7f, 0x7f,
|
|
0xf3, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff,
|
|
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
|
|
static void
|
|
recalc_timings_1512(amsvid_t *vid)
|
|
{
|
|
double _dispontime, _dispofftime, disptime;
|
|
|
|
disptime = 128; /*Fixed on PC1512*/
|
|
_dispontime = 80;
|
|
_dispofftime = disptime - _dispontime;
|
|
_dispontime *= CGACONST;
|
|
_dispofftime *= CGACONST;
|
|
vid->dispontime = (int64_t)(_dispontime * (1 << TIMER_SHIFT));
|
|
vid->dispofftime = (int64_t)(_dispofftime * (1 << TIMER_SHIFT));
|
|
}
|
|
|
|
|
|
static void
|
|
vid_out_1512(uint16_t addr, uint8_t val, void *priv)
|
|
{
|
|
amsvid_t *vid = (amsvid_t *)priv;
|
|
uint8_t old;
|
|
|
|
switch (addr) {
|
|
case 0x03d4:
|
|
vid->crtcreg = val & 31;
|
|
return;
|
|
|
|
case 0x03d5:
|
|
old = vid->crtc[vid->crtcreg];
|
|
vid->crtc[vid->crtcreg] = val & crtc_mask[vid->crtcreg];
|
|
if (old != val) {
|
|
if (vid->crtcreg < 0xe || vid->crtcreg > 0x10) {
|
|
fullchange = changeframecount;
|
|
recalc_timings_1512(vid);
|
|
}
|
|
}
|
|
return;
|
|
|
|
case 0x03d8:
|
|
if ((val & 0x12) == 0x12 && (vid->cgamode & 0x12) != 0x12) {
|
|
vid->plane_write = 0xf;
|
|
vid->plane_read = 0;
|
|
}
|
|
vid->cgamode = val;
|
|
return;
|
|
|
|
case 0x03d9:
|
|
vid->cgacol = val;
|
|
return;
|
|
|
|
case 0x03dd:
|
|
vid->plane_write = val;
|
|
return;
|
|
|
|
case 0x03de:
|
|
vid->plane_read = val & 3;
|
|
return;
|
|
|
|
case 0x03df:
|
|
vid->border = val;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
static uint8_t
|
|
vid_in_1512(uint16_t addr, void *priv)
|
|
{
|
|
amsvid_t *vid = (amsvid_t *)priv;
|
|
uint8_t ret = 0xff;
|
|
|
|
switch (addr) {
|
|
case 0x03d4:
|
|
ret = vid->crtcreg;
|
|
|
|
case 0x03d5:
|
|
ret = vid->crtc[vid->crtcreg];
|
|
|
|
case 0x03da:
|
|
ret = vid->stat;
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
|
|
static void
|
|
vid_write_1512(uint32_t addr, uint8_t val, void *priv)
|
|
{
|
|
amsvid_t *vid = (amsvid_t *)priv;
|
|
|
|
egawrites++;
|
|
cycles -= 12;
|
|
addr &= 0x3fff;
|
|
|
|
if ((vid->cgamode & 0x12) == 0x12) {
|
|
if (vid->plane_write & 1) vid->vram[addr] = val;
|
|
if (vid->plane_write & 2) vid->vram[addr | 0x4000] = val;
|
|
if (vid->plane_write & 4) vid->vram[addr | 0x8000] = val;
|
|
if (vid->plane_write & 8) vid->vram[addr | 0xc000] = val;
|
|
} else
|
|
vid->vram[addr] = val;
|
|
}
|
|
|
|
|
|
static uint8_t
|
|
vid_read_1512(uint32_t addr, void *priv)
|
|
{
|
|
amsvid_t *vid = (amsvid_t *)priv;
|
|
|
|
egareads++;
|
|
cycles -= 12;
|
|
addr &= 0x3fff;
|
|
|
|
if ((vid->cgamode & 0x12) == 0x12)
|
|
return(vid->vram[addr | (vid->plane_read << 14)]);
|
|
|
|
return(vid->vram[addr]);
|
|
}
|
|
|
|
|
|
static void
|
|
vid_poll_1512(void *priv)
|
|
{
|
|
amsvid_t *vid = (amsvid_t *)priv;
|
|
uint16_t ca = (vid->crtc[15] | (vid->crtc[14] << 8)) & 0x3fff;
|
|
int drawcursor;
|
|
int x, c;
|
|
uint8_t chr, attr;
|
|
uint16_t dat, dat2, dat3, dat4;
|
|
int cols[4];
|
|
int col;
|
|
int oldsc;
|
|
|
|
if (! vid->linepos) {
|
|
vid->vidtime += vid->dispofftime;
|
|
vid->stat |= 1;
|
|
vid->linepos = 1;
|
|
oldsc = vid->sc;
|
|
if (vid->dispon) {
|
|
if (vid->displine < vid->firstline) {
|
|
vid->firstline = vid->displine;
|
|
video_wait_for_buffer();
|
|
}
|
|
vid->lastline = vid->displine;
|
|
for (c = 0; c < 8; c++) {
|
|
if ((vid->cgamode & 0x12) == 0x12) {
|
|
buffer->line[vid->displine][c] = (vid->border & 15) + 16;
|
|
if (vid->cgamode & 1)
|
|
buffer->line[vid->displine][c + (vid->crtc[1] << 3) + 8] = 0;
|
|
else
|
|
buffer->line[vid->displine][c + (vid->crtc[1] << 4) + 8] = 0;
|
|
} else {
|
|
buffer->line[vid->displine][c] = (vid->cgacol & 15) + 16;
|
|
if (vid->cgamode & 1)
|
|
buffer->line[vid->displine][c + (vid->crtc[1] << 3) + 8] = (vid->cgacol & 15) + 16;
|
|
else
|
|
buffer->line[vid->displine][c + (vid->crtc[1] << 4) + 8] = (vid->cgacol & 15) + 16;
|
|
}
|
|
}
|
|
if (vid->cgamode & 1) {
|
|
for (x = 0; x < 80; x++) {
|
|
chr = vid->vram[ ((vid->ma << 1) & 0x3fff)];
|
|
attr = vid->vram[(((vid->ma << 1) + 1) & 0x3fff)];
|
|
drawcursor = ((vid->ma == ca) && vid->con && vid->cursoron);
|
|
if (vid->cgamode & 0x20) {
|
|
cols[1] = (attr & 15) + 16;
|
|
cols[0] = ((attr >> 4) & 7) + 16;
|
|
if ((vid->blink & 16) && (attr & 0x80) && !drawcursor)
|
|
cols[1] = cols[0];
|
|
} else {
|
|
cols[1] = (attr & 15) + 16;
|
|
cols[0] = (attr >> 4) + 16;
|
|
}
|
|
if (drawcursor) {
|
|
for (c = 0; c < 8; c++)
|
|
buffer->line[vid->displine][(x << 3) + c + 8] = cols[(fontdat[chr][vid->sc & 7] & (1 << (c ^ 7))) ? 1 : 0] ^ 15;
|
|
} else {
|
|
for (c = 0; c < 8; c++)
|
|
buffer->line[vid->displine][(x << 3) + c + 8] = cols[(fontdat[chr][vid->sc & 7] & (1 << (c ^ 7))) ? 1 : 0];
|
|
}
|
|
vid->ma++;
|
|
}
|
|
} else if (! (vid->cgamode & 2)) {
|
|
for (x = 0; x < 40; x++) {
|
|
chr = vid->vram[((vid->ma << 1) & 0x3fff)];
|
|
attr = vid->vram[(((vid->ma << 1) + 1) & 0x3fff)];
|
|
drawcursor = ((vid->ma == ca) && vid->con && vid->cursoron);
|
|
if (vid->cgamode & 0x20) {
|
|
cols[1] = (attr & 15) + 16;
|
|
cols[0] = ((attr >> 4) & 7) + 16;
|
|
if ((vid->blink & 16) && (attr & 0x80))
|
|
cols[1] = cols[0];
|
|
} else {
|
|
cols[1] = (attr & 15) + 16;
|
|
cols[0] = (attr >> 4) + 16;
|
|
}
|
|
vid->ma++;
|
|
if (drawcursor) {
|
|
for (c = 0; c < 8; c++)
|
|
buffer->line[vid->displine][(x << 4) + (c << 1) + 8] =
|
|
buffer->line[vid->displine][(x << 4) + (c << 1) + 1 + 8] = cols[(fontdat[chr][vid->sc & 7] & (1 << (c ^ 7))) ? 1 : 0] ^ 15;
|
|
} else {
|
|
for (c = 0; c < 8; c++)
|
|
buffer->line[vid->displine][(x << 4) + (c << 1) + 8] =
|
|
buffer->line[vid->displine][(x << 4) + (c << 1) + 1 + 8] = cols[(fontdat[chr][vid->sc & 7] & (1 << (c ^ 7))) ? 1 : 0];
|
|
}
|
|
}
|
|
} else if (! (vid->cgamode & 16)) {
|
|
cols[0] = (vid->cgacol & 15) | 16;
|
|
col = (vid->cgacol & 16) ? 24 : 16;
|
|
if (vid->cgamode & 4) {
|
|
cols[1] = col | 3;
|
|
cols[2] = col | 4;
|
|
cols[3] = col | 7;
|
|
} else if (vid->cgacol & 32) {
|
|
cols[1] = col | 3;
|
|
cols[2] = col | 5;
|
|
cols[3] = col | 7;
|
|
} else {
|
|
cols[1] = col | 2;
|
|
cols[2] = col | 4;
|
|
cols[3] = col | 6;
|
|
}
|
|
for (x = 0; x < 40; x++) {
|
|
dat = (vid->vram[((vid->ma << 1) & 0x1fff) + ((vid->sc & 1) * 0x2000)] << 8) | vid->vram[((vid->ma << 1) & 0x1fff) + ((vid->sc & 1) * 0x2000) + 1];
|
|
vid->ma++;
|
|
for (c = 0; c < 8; c++) {
|
|
buffer->line[vid->displine][(x << 4) + (c << 1) + 8] =
|
|
buffer->line[vid->displine][(x << 4) + (c << 1) + 1 + 8] = cols[dat >> 14];
|
|
dat <<= 2;
|
|
}
|
|
}
|
|
} else {
|
|
for (x = 0; x < 40; x++) {
|
|
ca = ((vid->ma << 1) & 0x1fff) + ((vid->sc & 1) * 0x2000);
|
|
dat = (vid->vram[ca] << 8) | vid->vram[ca + 1];
|
|
dat2 = (vid->vram[ca + 0x4000] << 8) | vid->vram[ca + 0x4001];
|
|
dat3 = (vid->vram[ca + 0x8000] << 8) | vid->vram[ca + 0x8001];
|
|
dat4 = (vid->vram[ca + 0xc000] << 8) | vid->vram[ca + 0xc001];
|
|
|
|
vid->ma++;
|
|
for (c = 0; c < 16; c++) {
|
|
buffer->line[vid->displine][(x << 4) + c + 8] = (((dat >> 15) | ((dat2 >> 15) << 1) | ((dat3 >> 15) << 2) | ((dat4 >> 15) << 3)) & (vid->cgacol & 15)) + 16;
|
|
dat <<= 1;
|
|
dat2 <<= 1;
|
|
dat3 <<= 1;
|
|
dat4 <<= 1;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
cols[0] = ((vid->cgamode & 0x12) == 0x12) ? 0 : (vid->cgacol & 15) + 16;
|
|
if (vid->cgamode & 1)
|
|
hline(buffer, 0, vid->displine, (vid->crtc[1] << 3) + 16, cols[0]);
|
|
else
|
|
hline(buffer, 0, vid->displine, (vid->crtc[1] << 4) + 16, cols[0]);
|
|
}
|
|
|
|
vid->sc = oldsc;
|
|
if (vid->vsynctime)
|
|
vid->stat |= 8;
|
|
vid->displine++;
|
|
if (vid->displine >= 360)
|
|
vid->displine = 0;
|
|
} else {
|
|
vid->vidtime += vid->dispontime;
|
|
if ((vid->lastline - vid->firstline) == 199)
|
|
vid->dispon = 0; /*Amstrad PC1512 always displays 200 lines, regardless of CRTC settings*/
|
|
if (vid->dispon)
|
|
vid->stat &= ~1;
|
|
vid->linepos = 0;
|
|
if (vid->vsynctime) {
|
|
vid->vsynctime--;
|
|
if (! vid->vsynctime)
|
|
vid->stat &= ~8;
|
|
}
|
|
if (vid->sc == (vid->crtc[11] & 31)) {
|
|
vid->con = 0;
|
|
vid->coff = 1;
|
|
}
|
|
if (vid->vadj) {
|
|
vid->sc++;
|
|
vid->sc &= 31;
|
|
vid->ma = vid->maback;
|
|
vid->vadj--;
|
|
if (! vid->vadj) {
|
|
vid->dispon = 1;
|
|
vid->ma = vid->maback = (vid->crtc[13] | (vid->crtc[12] << 8)) & 0x3fff;
|
|
vid->sc = 0;
|
|
}
|
|
} else if (vid->sc == vid->crtc[9]) {
|
|
vid->maback = vid->ma;
|
|
vid->sc = 0;
|
|
vid->vc++;
|
|
vid->vc &= 127;
|
|
|
|
if (vid->displine == 32) {
|
|
vid->vc = 0;
|
|
vid->vadj = 6;
|
|
if ((vid->crtc[10] & 0x60) == 0x20)
|
|
vid->cursoron = 0;
|
|
else
|
|
vid->cursoron = vid->blink & 16;
|
|
}
|
|
|
|
if (vid->displine >= 262) {
|
|
vid->dispon = 0;
|
|
vid->displine = 0;
|
|
vid->vsynctime = 46;
|
|
|
|
if (vid->cgamode&1)
|
|
x = (vid->crtc[1] << 3) + 16;
|
|
else
|
|
x = (vid->crtc[1] << 4) + 16;
|
|
vid->lastline++;
|
|
|
|
if ((x != xsize) || ((vid->lastline - vid->firstline) != ysize) || video_force_resize_get()) {
|
|
xsize = x;
|
|
ysize = vid->lastline - vid->firstline;
|
|
if (xsize < 64) xsize = 656;
|
|
if (ysize < 32) ysize = 200;
|
|
set_screen_size(xsize, (ysize << 1) + 16);
|
|
|
|
if (video_force_resize_get())
|
|
video_force_resize_set(0);
|
|
}
|
|
|
|
video_blit_memtoscreen_8(0, vid->firstline - 4, 0, (vid->lastline - vid->firstline) + 8, xsize, (vid->lastline - vid->firstline) + 8);
|
|
|
|
video_res_x = xsize - 16;
|
|
video_res_y = ysize;
|
|
if (vid->cgamode & 1) {
|
|
video_res_x /= 8;
|
|
video_res_y /= vid->crtc[9] + 1;
|
|
video_bpp = 0;
|
|
} else if (! (vid->cgamode & 2)) {
|
|
video_res_x /= 16;
|
|
video_res_y /= vid->crtc[9] + 1;
|
|
video_bpp = 0;
|
|
} else if (! (vid->cgamode & 16)) {
|
|
video_res_x /= 2;
|
|
video_bpp = 2;
|
|
} else {
|
|
video_bpp = 4;
|
|
}
|
|
|
|
vid->firstline = 1000;
|
|
vid->lastline = 0;
|
|
vid->blink++;
|
|
}
|
|
} else {
|
|
vid->sc++;
|
|
vid->sc &= 31;
|
|
vid->ma = vid->maback;
|
|
}
|
|
if (vid->sc == (vid->crtc[10] & 31))
|
|
vid->con = 1;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
vid_init_1512(amstrad_t *ams)
|
|
{
|
|
amsvid_t *vid;
|
|
|
|
/* Allocate a video controller block. */
|
|
vid = (amsvid_t *)malloc(sizeof(amsvid_t));
|
|
memset(vid, 0x00, sizeof(amsvid_t));
|
|
|
|
vid->vram = malloc(0x10000);
|
|
vid->cgacol = 7;
|
|
vid->cgamode = 0x12;
|
|
|
|
timer_add(vid_poll_1512, &vid->vidtime, TIMER_ALWAYS_ENABLED, vid);
|
|
mem_mapping_add(&vid->cga.mapping, 0xb8000, 0x08000,
|
|
vid_read_1512, NULL, NULL, vid_write_1512, NULL, NULL,
|
|
NULL, 0, vid);
|
|
io_sethandler(0x03d0, 16,
|
|
vid_in_1512, NULL, NULL, vid_out_1512, NULL, NULL, vid);
|
|
|
|
overscan_x = overscan_y = 16;
|
|
|
|
ams->vid = vid;
|
|
}
|
|
|
|
|
|
static void
|
|
vid_close_1512(void *priv)
|
|
{
|
|
amsvid_t *vid = (amsvid_t *)priv;
|
|
|
|
free(vid->vram);
|
|
|
|
free(vid);
|
|
}
|
|
|
|
|
|
static void
|
|
vid_speed_change_1512(void *priv)
|
|
{
|
|
amsvid_t *vid = (amsvid_t *)priv;
|
|
|
|
recalc_timings_1512(vid);
|
|
}
|
|
|
|
|
|
static device_t vid_1512_device = {
|
|
"Amstrad PC1512 (video)",
|
|
0, 0,
|
|
NULL, vid_close_1512, NULL,
|
|
NULL,
|
|
vid_speed_change_1512,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
|
|
static void
|
|
recalc_timings_1640(amsvid_t *vid)
|
|
{
|
|
cga_recalctimings(&vid->cga);
|
|
ega_recalctimings(&vid->ega);
|
|
|
|
if (vid->cga_enabled) {
|
|
overscan_x = overscan_y = 16;
|
|
|
|
vid->dispontime = vid->cga.dispontime;
|
|
vid->dispofftime = vid->cga.dispofftime;
|
|
} else {
|
|
overscan_x = 16; overscan_y = 28;
|
|
|
|
vid->dispontime = vid->ega.dispontime;
|
|
vid->dispofftime = vid->ega.dispofftime;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
vid_out_1640(uint16_t addr, uint8_t val, void *priv)
|
|
{
|
|
amsvid_t *vid = (amsvid_t *)priv;
|
|
|
|
switch (addr) {
|
|
case 0x03db:
|
|
vid->cga_enabled = val & 0x40;
|
|
if (vid->cga_enabled) {
|
|
mem_mapping_enable(&vid->cga.mapping);
|
|
mem_mapping_disable(&vid->ega.mapping);
|
|
} else {
|
|
mem_mapping_disable(&vid->cga.mapping);
|
|
switch (vid->ega.gdcreg[6] & 0xc) {
|
|
case 0x0: /*128k at A0000*/
|
|
mem_mapping_set_addr(&vid->ega.mapping,
|
|
0xa0000, 0x20000);
|
|
break;
|
|
|
|
case 0x4: /*64k at A0000*/
|
|
mem_mapping_set_addr(&vid->ega.mapping,
|
|
0xa0000, 0x10000);
|
|
break;
|
|
|
|
case 0x8: /*32k at B0000*/
|
|
mem_mapping_set_addr(&vid->ega.mapping,
|
|
0xb0000, 0x08000);
|
|
break;
|
|
|
|
case 0xC: /*32k at B8000*/
|
|
mem_mapping_set_addr(&vid->ega.mapping,
|
|
0xb8000, 0x08000);
|
|
break;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (vid->cga_enabled)
|
|
cga_out(addr, val, &vid->cga);
|
|
else
|
|
ega_out(addr, val, &vid->ega);
|
|
}
|
|
|
|
|
|
static uint8_t
|
|
vid_in_1640(uint16_t addr, void *priv)
|
|
{
|
|
amsvid_t *vid = (amsvid_t *)priv;
|
|
|
|
switch (addr) {
|
|
}
|
|
|
|
if (vid->cga_enabled)
|
|
return(cga_in(addr, &vid->cga));
|
|
else
|
|
return(ega_in(addr, &vid->ega));
|
|
}
|
|
|
|
|
|
static void
|
|
vid_poll_1640(void *priv)
|
|
{
|
|
amsvid_t *vid = (amsvid_t *)priv;
|
|
|
|
if (vid->cga_enabled) {
|
|
overscan_x = overscan_y = 16;
|
|
|
|
vid->cga.vidtime = vid->vidtime;
|
|
cga_poll(&vid->cga);
|
|
vid->vidtime = vid->cga.vidtime;
|
|
} else {
|
|
overscan_x = 16; overscan_y = 28;
|
|
|
|
vid->ega.vidtime = vid->vidtime;
|
|
ega_poll(&vid->ega);
|
|
vid->vidtime = vid->ega.vidtime;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
vid_init_1640(amstrad_t *ams)
|
|
{
|
|
amsvid_t *vid;
|
|
|
|
/* Allocate a video controller block. */
|
|
vid = (amsvid_t *)malloc(sizeof(amsvid_t));
|
|
memset(vid, 0x00, sizeof(amsvid_t));
|
|
|
|
rom_init(&vid->bios_rom, L"roms/machines/pc1640/40100",
|
|
0xc0000, 0x8000, 0x7fff, 0, 0);
|
|
|
|
ega_init(&vid->ega, 9, 0);
|
|
vid->cga.vram = vid->ega.vram;
|
|
vid->cga_enabled = 1;
|
|
cga_init(&vid->cga);
|
|
|
|
mem_mapping_add(&vid->cga.mapping, 0xb8000, 0x08000,
|
|
cga_read, NULL, NULL, cga_write, NULL, NULL, NULL, 0, &vid->cga);
|
|
mem_mapping_add(&vid->ega.mapping, 0, 0,
|
|
ega_read, NULL, NULL, ega_write, NULL, NULL, NULL, 0, &vid->ega);
|
|
io_sethandler(0x03a0, 64,
|
|
vid_in_1640, NULL, NULL, vid_out_1640, NULL, NULL, vid);
|
|
|
|
timer_add(vid_poll_1640, &vid->vidtime, TIMER_ALWAYS_ENABLED, vid);
|
|
|
|
overscan_x = overscan_y = 16;
|
|
|
|
ams->vid = vid;
|
|
}
|
|
|
|
|
|
static void
|
|
vid_close_1640(void *priv)
|
|
{
|
|
amsvid_t *vid = (amsvid_t *)priv;
|
|
|
|
free(vid->ega.vram);
|
|
|
|
free(vid);
|
|
}
|
|
|
|
|
|
static void
|
|
vid_speed_changed_1640(void *priv)
|
|
{
|
|
amsvid_t *vid = (amsvid_t *)priv;
|
|
|
|
recalc_timings_1640(vid);
|
|
}
|
|
|
|
|
|
static device_t vid_1640_device = {
|
|
"Amstrad PC1640 (video)",
|
|
0, 0,
|
|
NULL, vid_close_1640, NULL,
|
|
NULL,
|
|
vid_speed_changed_1640,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
|
|
static void
|
|
vid_out_200(uint16_t addr, uint8_t val, void *priv)
|
|
{
|
|
amsvid_t *vid = (amsvid_t *)priv;
|
|
cga_t *cga = &vid->cga;
|
|
uint8_t old;
|
|
|
|
switch (addr) {
|
|
case 0x03d5:
|
|
if (!(vid->plane_read & 0x40) && cga->crtcreg <= 11) {
|
|
if (vid->plane_read & 0x80)
|
|
nmi = 1;
|
|
|
|
vid->plane_write = 0x20 | (cga->crtcreg & 0x1f);
|
|
vid->border = val;
|
|
return;
|
|
}
|
|
old = cga->crtc[cga->crtcreg];
|
|
cga->crtc[cga->crtcreg] = val & crtc_mask[cga->crtcreg];
|
|
if (old != val) {
|
|
if (cga->crtcreg < 0xe || cga->crtcreg > 0x10) {
|
|
fullchange = changeframecount;
|
|
cga_recalctimings(cga);
|
|
}
|
|
}
|
|
return;
|
|
|
|
case 0x03d8:
|
|
old = cga->cgamode;
|
|
cga->cgamode = val;
|
|
if ((cga->cgamode ^ old) & 3)
|
|
cga_recalctimings(cga);
|
|
vid->plane_write |= 0x80;
|
|
if (vid->plane_read & 0x80)
|
|
nmi = 1;
|
|
return;
|
|
|
|
case 0x03de:
|
|
vid->plane_read = val;
|
|
vid->plane_write = 0x1f;
|
|
if (val & 0x80)
|
|
vid->plane_write |= 0x40;
|
|
return;
|
|
}
|
|
|
|
cga_out(addr, val, cga);
|
|
}
|
|
|
|
|
|
static uint8_t
|
|
vid_in_200(uint16_t addr, void *priv)
|
|
{
|
|
amsvid_t *vid = (amsvid_t *)priv;
|
|
cga_t *cga = &vid->cga;
|
|
uint8_t ret;
|
|
|
|
switch (addr) {
|
|
case 0x03d8:
|
|
return(cga->cgamode);
|
|
|
|
case 0x03dd:
|
|
ret = vid->plane_write;
|
|
vid->plane_write &= 0x1f;
|
|
nmi = 0;
|
|
return(ret);
|
|
|
|
case 0x03de:
|
|
return((vid->plane_read & 0xc7) | 0x10); /*External CGA*/
|
|
|
|
case 0x03df:
|
|
return(vid->border);
|
|
}
|
|
|
|
return(cga_in(addr, cga));
|
|
}
|
|
|
|
|
|
static void
|
|
vid_init_200(amstrad_t *ams)
|
|
{
|
|
amsvid_t *vid;
|
|
cga_t *cga;
|
|
|
|
/* Allocate a video controller block. */
|
|
vid = (amsvid_t *)malloc(sizeof(amsvid_t));
|
|
memset(vid, 0x00, sizeof(amsvid_t));
|
|
|
|
cga = &vid->cga;
|
|
cga->vram = malloc(0x4000);
|
|
cga_init(cga);
|
|
|
|
mem_mapping_add(&vid->cga.mapping, 0xb8000, 0x08000,
|
|
cga_read, NULL, NULL, cga_write, NULL, NULL, NULL, 0, cga);
|
|
io_sethandler(0x03d0, 16,
|
|
vid_in_200, NULL, NULL, vid_out_200, NULL, NULL, vid);
|
|
|
|
timer_add(cga_poll, &cga->vidtime, TIMER_ALWAYS_ENABLED, cga);
|
|
|
|
overscan_x = overscan_y = 16;
|
|
|
|
ams->vid = vid;
|
|
}
|
|
|
|
|
|
static void
|
|
vid_close_200(void *priv)
|
|
{
|
|
amsvid_t *vid = (amsvid_t *)priv;
|
|
|
|
free(vid->cga.vram);
|
|
|
|
free(vid);
|
|
}
|
|
|
|
|
|
static void
|
|
vid_speed_changed_200(void *priv)
|
|
{
|
|
amsvid_t *vid = (amsvid_t *)priv;
|
|
|
|
cga_recalctimings(&vid->cga);
|
|
}
|
|
|
|
|
|
static device_t vid_200_device = {
|
|
"Amstrad PC200 (video)",
|
|
0, 0,
|
|
NULL, vid_close_200, NULL,
|
|
NULL,
|
|
vid_speed_changed_200,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
|
|
static void
|
|
ms_write(uint16_t addr, uint8_t val, void *priv)
|
|
{
|
|
amstrad_t *ams = (amstrad_t *)priv;
|
|
|
|
if (addr == 0x78)
|
|
ams->mousex = 0;
|
|
else
|
|
ams->mousey = 0;
|
|
}
|
|
|
|
|
|
static uint8_t
|
|
ms_read(uint16_t addr, void *priv)
|
|
{
|
|
amstrad_t *ams = (amstrad_t *)priv;
|
|
|
|
if (addr == 0x78)
|
|
return(ams->mousex);
|
|
|
|
return(ams->mousey);
|
|
}
|
|
|
|
|
|
static int
|
|
ms_poll(int x, int y, int z, int b, void *priv)
|
|
{
|
|
amstrad_t *ams = (amstrad_t *)priv;
|
|
|
|
ams->mousex += x;
|
|
ams->mousey -= y;
|
|
|
|
if ((b & 1) && !(ams->oldb & 1))
|
|
keyboard_send(0x7e);
|
|
if ((b & 2) && !(ams->oldb & 2))
|
|
keyboard_send(0x7d);
|
|
if (!(b & 1) && (ams->oldb & 1))
|
|
keyboard_send(0xfe);
|
|
if (!(b & 2) && (ams->oldb & 2))
|
|
keyboard_send(0xfd);
|
|
|
|
ams->oldb = b;
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
static void
|
|
kbd_adddata(uint16_t val)
|
|
{
|
|
key_queue[key_queue_end] = val;
|
|
#if ENABLE_KEYBOARD_LOG
|
|
pclog("keyboard_amstrad : %02X added to key queue at %i\n",
|
|
val, key_queue_end);
|
|
#endif
|
|
key_queue_end = (key_queue_end + 1) & 0xf;
|
|
}
|
|
|
|
|
|
static void
|
|
kbd_adddata_ex(uint16_t val)
|
|
{
|
|
kbd_adddata_process(val, kbd_adddata);
|
|
}
|
|
|
|
|
|
static void
|
|
kbd_write(uint16_t port, uint8_t val, void *priv)
|
|
{
|
|
amstrad_t *ams = (amstrad_t *)priv;
|
|
#ifdef WALTJE
|
|
int i = 0;
|
|
#endif
|
|
|
|
#if ENABLE_KEYBOARD_LOG
|
|
pclog("keyboard_amstrad : write %04X %02X %02X\n", port, val, ams->pb);
|
|
#endif
|
|
|
|
switch (port) {
|
|
case 0x61:
|
|
/*
|
|
* PortB - System Control.
|
|
*
|
|
* 7 Enable Status-1/Disable Keyboard Code on Port A.
|
|
* 6 Enable incoming Keyboard Clock.
|
|
* 5 Prevent external parity errors from causing NMI.
|
|
* 4 Disable parity checking of on-board system Ram.
|
|
* 3 Undefined (Not Connected).
|
|
* 2 Enable Port C LSB / Disable MSB. (See 1.8.3)
|
|
* 1 Speaker Drive.
|
|
* 0 8253 GATE 2 (Speaker Modulate).
|
|
*
|
|
* This register is controlled by BIOS and/or ROS.
|
|
*/
|
|
#if ENABLE_KEYBOARD_LOG
|
|
pclog("AMSkb: write PB %02x (%02x)\n", val, ams->pb);
|
|
#endif
|
|
if (!(ams->pb & 0x40) && (val & 0x40)) { /*Reset keyboard*/
|
|
#if ENABLE_KEYBOARD_LOG
|
|
pclog("AMSkb: reset keyboard\n");
|
|
#endif
|
|
kbd_adddata(0xaa);
|
|
}
|
|
ams->pb = val;
|
|
ppi.pb = val;
|
|
|
|
timer_process();
|
|
timer_update_outstanding();
|
|
|
|
speaker_update();
|
|
speaker_gated = val & 0x01;
|
|
speaker_enable = val & 0x02;
|
|
if (speaker_enable)
|
|
was_speaker_enable = 1;
|
|
pit_set_gate(&pit, 2, val & 0x01);
|
|
|
|
if (val & 0x80) {
|
|
/* Keyboard enabled, so enable PA reading. */
|
|
ams->pa = 0x00;
|
|
}
|
|
break;
|
|
|
|
case 0x63:
|
|
break;
|
|
|
|
case 0x64:
|
|
#ifdef WALTJE
|
|
pclog("AMSkb: STAT1 = %02x (%02x)\n", val, ams->stat1);
|
|
#endif
|
|
ams->stat1 = val;
|
|
break;
|
|
|
|
case 0x65:
|
|
#ifdef WALTJE
|
|
pclog("AMSkb: STAT2 = %02x (%02x)\n", val, ams->stat2);
|
|
i = 512 + (((val & 0x1f) - 0x0e) * 32);
|
|
pclog("AMSkb: %d KB RAM installed.\n", i);
|
|
#endif
|
|
ams->stat2 = val;
|
|
break;
|
|
|
|
case 0x66:
|
|
#ifdef WALTJE
|
|
pclog("AMSkb: RESET REQUESTED !\n");
|
|
#endif
|
|
pc_reset(1);
|
|
break;
|
|
|
|
default:
|
|
pclog("AMSkb: bad keyboard write %04X %02X\n", port, val);
|
|
}
|
|
}
|
|
|
|
|
|
static uint8_t
|
|
kbd_read(uint16_t port, void *priv)
|
|
{
|
|
amstrad_t *ams = (amstrad_t *)priv;
|
|
uint8_t ret = 0xff;
|
|
|
|
switch (port) {
|
|
case 0x60:
|
|
if (ams->pb & 0x80) {
|
|
/*
|
|
* PortA - System Status 1
|
|
*
|
|
* 7 Always 0 (KBD7)
|
|
* 6 Second Floppy disk drive installed (KBD6)
|
|
* 5 DDM1 - Default Display Mode bit 1 (KBD5)
|
|
* 4 DDM0 - Default Display Mode bit 0 (KBD4)
|
|
* 3 Always 1 (KBD3)
|
|
* 2 Always 1 (KBD2)
|
|
* 1 8087 NDP installed (KBD1)
|
|
* 0 Always 1 (KBD0)
|
|
*
|
|
* DDM00
|
|
* 00 unknown, external color?
|
|
* 01 Color,alpha,40x25, bright white on black.
|
|
* 10 Color,alpha,80x25, bright white on black.
|
|
* 11 External Monochrome,80x25.
|
|
*
|
|
* Following a reset, the hardware selects VDU mode
|
|
* 2. The ROS then sets the initial VDU state based
|
|
* on the DDM value.
|
|
*/
|
|
ret = (0x0d | ams->stat1) & 0x7f;
|
|
} else {
|
|
ret = ams->pa;
|
|
if (key_queue_start == key_queue_end) {
|
|
ams->wantirq = 0;
|
|
} else {
|
|
ams->key_waiting = key_queue[key_queue_start];
|
|
key_queue_start = (key_queue_start + 1) & 0xf;
|
|
ams->wantirq = 1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x61:
|
|
ret = ams->pb;
|
|
break;
|
|
|
|
case 0x62:
|
|
/*
|
|
* PortC - System Status 2.
|
|
*
|
|
* 7 On-board system RAM parity error.
|
|
* 6 External parity error (I/OCHCK from expansion bus).
|
|
* 5 8253 PIT OUT2 output.
|
|
* 4 Undefined (Not Connected).
|
|
*-------------------------------------------
|
|
* LSB MSB (depends on PB2)
|
|
*-------------------------------------------
|
|
* 3 RAM3 Undefined
|
|
* 2 RAM2 Undefined
|
|
* 1 RAM1 Undefined
|
|
* 0 RAM0 RAM4
|
|
*
|
|
* PC7 is forced to 0 when on-board system RAM parity
|
|
* checking is disabled by PB4.
|
|
*
|
|
* RAM4:0
|
|
* 01110 512K bytes on-board.
|
|
* 01111 544K bytes (32K external).
|
|
* 10000 576K bytes (64K external).
|
|
* 10001 608K bytes (96K external).
|
|
* 10010 640K bytes (128K external or fitted on-board).
|
|
*/
|
|
if (ams->pb & 0x04)
|
|
ret = ams->stat2 & 0x0f;
|
|
else
|
|
ret = ams->stat2 >> 4;
|
|
ret |= (ppispeakon ? 0x20 : 0);
|
|
if (nmi)
|
|
ret |= 0x40;
|
|
break;
|
|
|
|
default:
|
|
pclog("AMDkb: bad keyboard read %04X\n", port);
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
|
|
static void
|
|
kbd_poll(void *priv)
|
|
{
|
|
amstrad_t *ams = (amstrad_t *)priv;
|
|
|
|
keyboard_delay += (1000 * TIMER_USEC);
|
|
|
|
if (ams->wantirq)
|
|
{
|
|
ams->wantirq = 0;
|
|
ams->pa = ams->key_waiting;
|
|
picint(2);
|
|
#if ENABLE_KEYBOARD_LOG
|
|
pclog("keyboard_amstrad : take IRQ\n");
|
|
#endif
|
|
}
|
|
|
|
if (key_queue_start != key_queue_end && !ams->pa) {
|
|
ams->key_waiting = key_queue[key_queue_start];
|
|
#if ENABLE_KEYBOARD_LOG
|
|
pclog("Reading %02X from the key queue at %i\n",
|
|
ams->key_waiting, key_queue_start);
|
|
#endif
|
|
key_queue_start = (key_queue_start + 1) & 0xf;
|
|
ams->wantirq = 1;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
ams_write(uint16_t port, uint8_t val, void *priv)
|
|
{
|
|
amstrad_t *ams = (amstrad_t *)priv;
|
|
|
|
switch (port) {
|
|
case 0xdead:
|
|
ams->dead = val;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static uint8_t
|
|
ams_read(uint16_t port, void *priv)
|
|
{
|
|
amstrad_t *ams = (amstrad_t *)priv;
|
|
uint8_t ret = 0xff;
|
|
|
|
switch (port) {
|
|
case 0x0379: /* printer control, also set LK1-3.
|
|
* 0 English Language.
|
|
* 1 German Language.
|
|
* 2 French Language.
|
|
* 3 Spanish Language.
|
|
* 4 Danish Language.
|
|
* 5 Swedish Language.
|
|
* 6 Italian Language.
|
|
* 7 Diagnostic Mode.
|
|
*/
|
|
ret = 0x02; /* ENGLISH. no Diags mode */
|
|
break;
|
|
|
|
case 0x037a: /* printer status */
|
|
switch(romset) {
|
|
case ROM_PC1512:
|
|
ret = 0x20;
|
|
break;
|
|
|
|
case ROM_PC200:
|
|
ret = 0x80;
|
|
break;
|
|
|
|
default:
|
|
ret = 0x00;
|
|
}
|
|
break;
|
|
|
|
case 0xdead:
|
|
ret = ams->dead;
|
|
break;
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
|
|
void
|
|
machine_amstrad_init(machine_t *model)
|
|
{
|
|
amstrad_t *ams;
|
|
|
|
ams = (amstrad_t *)malloc(sizeof(amstrad_t));
|
|
memset(ams, 0x00, sizeof(amstrad_t));
|
|
|
|
nvr_at_init(1);
|
|
|
|
machine_common_init(model);
|
|
|
|
nmi_init();
|
|
|
|
lpt2_remove_ams();
|
|
|
|
io_sethandler(0x0379, 2,
|
|
ams_read, NULL, NULL, NULL, NULL, NULL, ams);
|
|
|
|
io_sethandler(0xdead, 1,
|
|
ams_read, NULL, NULL, ams_write, NULL, NULL, ams);
|
|
|
|
io_sethandler(0x0078, 1,
|
|
ms_read, NULL, NULL, ms_write, NULL, NULL, ams);
|
|
|
|
io_sethandler(0x007a, 1,
|
|
ms_read, NULL, NULL, ms_write, NULL, NULL, ams);
|
|
|
|
// device_add(&fdc_at_actlow_device);
|
|
|
|
switch(romset) {
|
|
case ROM_PC1512:
|
|
device_add(&fdc_xt_device);
|
|
break;
|
|
|
|
case ROM_PC1640:
|
|
device_add(&fdc_xt_device);
|
|
break;
|
|
|
|
case ROM_PC200:
|
|
device_add(&fdc_xt_device);
|
|
break;
|
|
|
|
case ROM_PC2086:
|
|
device_add(&fdc_at_actlow_device);
|
|
break;
|
|
|
|
case ROM_PC3086:
|
|
device_add(&fdc_at_actlow_device);
|
|
break;
|
|
|
|
case ROM_MEGAPC:
|
|
device_add(&fdc_at_actlow_device);
|
|
break;
|
|
}
|
|
|
|
if (gfxcard == GFX_INTERNAL) switch(romset) {
|
|
case ROM_PC1512:
|
|
loadfont(L"roms/machines/pc1512/40078", 2);
|
|
vid_init_1512(ams);
|
|
device_add_ex(&vid_1512_device, ams->vid);
|
|
break;
|
|
|
|
case ROM_PC1640:
|
|
vid_init_1640(ams);
|
|
device_add_ex(&vid_1640_device, ams->vid);
|
|
break;
|
|
|
|
case ROM_PC200:
|
|
loadfont(L"roms/machines/pc200/40109.bin", 1);
|
|
vid_init_200(ams);
|
|
device_add_ex(&vid_200_device, ams->vid);
|
|
break;
|
|
|
|
case ROM_PC2086:
|
|
device_add(¶dise_pvga1a_pc2086_device);
|
|
break;
|
|
|
|
case ROM_PC3086:
|
|
device_add(¶dise_pvga1a_pc3086_device);
|
|
break;
|
|
|
|
case ROM_MEGAPC:
|
|
device_add(¶dise_wd90c11_megapc_device);
|
|
break;
|
|
}
|
|
|
|
/* Initialize the (custom) keyboard/mouse interface. */
|
|
ams->wantirq = 0;
|
|
io_sethandler(0x0060, 7,
|
|
kbd_read, NULL, NULL, kbd_write, NULL, NULL, ams);
|
|
timer_add(kbd_poll, &keyboard_delay, TIMER_ALWAYS_ENABLED, ams);
|
|
keyboard_set_table(scancode_xt);
|
|
keyboard_send = kbd_adddata_ex;
|
|
keyboard_scan = 1;
|
|
|
|
/* Tell mouse driver about our internal mouse. */
|
|
mouse_reset();
|
|
mouse_set_poll(ms_poll, ams);
|
|
|
|
if (joystick_type != 7)
|
|
device_add(&gameport_device);
|
|
}
|