/*
 * 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.
 *
 * PPC512/640:	Portable with both CGA-compatible and MDA-compatible monitors.
 *
 * TODO:	This module is not complete yet:
 *
 * All models:	The internal mouse controller does not work correctly with
 *		version 7.04 of the mouse driver.
 *
 *
 *
 * Authors:	Sarah Walker, <http://pcem-emulator.co.uk/>
 *		Miran Grca, <mgrca8@gmail.com>
 *		Fred N. van Kempen, <decwiz@yahoo.com>
 *		John Elliott, <jce@seasip.info>
 *
 *		Copyright 2008-2019 Sarah Walker.
 *		Copyright 2016-2019 Miran Grca.
 *		Copyright 2017-2019 Fred N. van Kempen.
 *		Copyright 2019 John Elliott.
 */
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include "cpu.h"
#include <86box/timer.h>
#include <86box/io.h>
#include <86box/nmi.h>
#include <86box/pic.h>
#include <86box/pit.h>
#include <86box/ppi.h>
#include <86box/mem.h>
#include <86box/rom.h>
#include <86box/device.h>
#include <86box/nvr.h>
#include <86box/keyboard.h>
#include <86box/mouse.h>
#include <86box/gameport.h>
#include <86box/lpt.h>
#include <86box/fdd.h>
#include <86box/fdc.h>
#include <86box/sound.h>
#include <86box/snd_speaker.h>
#include <86box/video.h>
#include <86box/vid_cga.h>
#include <86box/vid_ega.h>
#include <86box/vid_mda.h>
#include <86box/machine.h>
#include <86box/m_amstrad.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 */
    mda_t	mda;			/* 1512/200/PPC512/640*/
    ega_t	ega;			/* 1640 */
    uint8_t	emulation;		/* Which display are we emulating? */
    uint8_t	dipswitches;		/* DIP switches 1-3 */
    uint8_t	crtc_index;		/* CRTC index readback
					 * Bit 7: CGA control port written
					 * Bit 6: Operation control port written
					 * Bit 5: CRTC register written
					 * Bits 0-4: Last CRTC register selected */
    uint8_t	operation_ctrl;
    uint8_t	reg_3df, type;
    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 */
		invert;			/* 512/640 */
    int		fontbase;		/* 1512/200 */
    int		linepos,
		displine;
    int		sc, vc;
    int		cgadispon;
    int		con, coff,
		cursoron,
		cgablink;
    int	vsynctime;
    int		vadj;
    uint16_t	ma, maback;
    int		dispon;
    int		blink;
    uint64_t	dispontime,		/* 1512/1640 */
		dispofftime;		/* 1512/1640 */
    pc_timer_t	timer;			/* 1512/1640 */
    int		firstline,
		lastline;
    uint8_t	*vram;
    void	*ams;
} amsvid_t;

typedef struct {
    /* Machine stuff. */
    uint8_t	dead;
    uint8_t	stat1,
		stat2;
    uint8_t	type,
		language;

    /* Keyboard stuff. */
    int8_t	wantirq;
    uint8_t	key_waiting;
    uint8_t	pa;
    uint8_t	pb;
    pc_timer_t	send_delay_timer;

    /* Mouse stuff. */
    uint8_t	mousex,
		mousey;
    int		oldb;

    /* Video stuff. */
    amsvid_t	*vid;
    fdc_t	*fdc;
} amstrad_t;


int		amstrad_latch;


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 video_timings_t timing_pc1512   = {VIDEO_BUS, 0,0,0, 0,0,0}; /*PC1512 video code handles waitstates itself*/
static video_timings_t timing_pc1640   = {VIDEO_ISA, 8,16,32, 8,16,32};
static video_timings_t timing_pc200    = {VIDEO_ISA, 8,16,32, 8,16,32};


enum
{
    AMS_PC1512,
    AMS_PC1640,
    AMS_PC200,
    AMS_PPC512,
    AMS_PC2086,
    AMS_PC3086
};


#ifdef ENABLE_AMSTRAD_LOG
int amstrad_do_log = ENABLE_AMSTRAD_LOG;


static void
amstrad_log(const char *fmt, ...)
{
   va_list ap;

   if (amstrad_do_log)
   {
	va_start(ap, fmt);
	pclog_ex(fmt, ap);
	va_end(ap);
   }
}
#else
#define amstrad_log(fmt, ...)
#endif


static void
recalc_timings_1512(amsvid_t *vid)
{
    double _dispontime, _dispofftime, disptime;

    disptime = /*128*/ 114; /*Fixed on PC1512*/
    _dispontime = 80;
    _dispofftime = disptime - _dispontime;
    _dispontime  *= CGACONST;
    _dispofftime *= CGACONST;
    vid->dispontime  = (uint64_t)_dispontime;
    vid->dispofftime = (uint64_t)_dispofftime;
}


static void
vid_out_1512(uint16_t addr, uint8_t val, void *priv)
{
    amsvid_t *vid = (amsvid_t *)priv;
    uint8_t old;

    if ((addr >= 0x3d0) && (addr <= 0x3d7))
	addr = (addr & 0xff9) | 0x004;

    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;

    if ((addr >= 0x3d0) && (addr <= 0x3d7))
	addr = (addr & 0xff9) | 0x004;

    switch (addr) {
	case 0x03d4:
		ret = vid->crtcreg;
		break;

	case 0x03d5:
		ret = vid->crtc[vid->crtcreg];
		break;

	case 0x03da:
		ret = vid->stat;
		break;
    }

    return(ret);
}


static void
vid_write_1512(uint32_t addr, uint8_t val, void *priv)
{
    amsvid_t *vid = (amsvid_t *)priv;

    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;

    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, xs_temp, ys_temp;
    uint8_t chr, attr;
    uint16_t dat, dat2, dat3, dat4;
    int cols[4];
    int col;
    int oldsc;

    if (! vid->linepos) {
	timer_advance_u64(&vid->timer, 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) {
				buffer32->line[(vid->displine << 1)][c] = buffer32->line[(vid->displine << 1) + 1][c] = (vid->border & 15) + 16;
				if (vid->cgamode & 1) {
					buffer32->line[(vid->displine << 1)][c + (vid->crtc[1] << 3) + 8] =
					buffer32->line[(vid->displine << 1) + 1][c + (vid->crtc[1] << 3) + 8] = 0;
				} else {
					buffer32->line[(vid->displine << 1)][c + (vid->crtc[1] << 4) + 8] =
					buffer32->line[(vid->displine << 1)+ 1][c + (vid->crtc[1] << 4) + 8] = 0;
				}
			} else {
				buffer32->line[(vid->displine << 1)][c] = buffer32->line[(vid->displine << 1) + 1][c] = (vid->cgacol & 15) + 16;
				if (vid->cgamode & 1) {
					buffer32->line[(vid->displine << 1)][c + (vid->crtc[1] << 3) + 8] =
					buffer32->line[(vid->displine << 1) + 1][c + (vid->crtc[1] << 3) + 8] = (vid->cgacol & 15) + 16;
				} else {
					buffer32->line[(vid->displine << 1)][c + (vid->crtc[1] << 4) + 8] =
					buffer32->line[(vid->displine << 1) + 1][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++) {
						buffer32->line[(vid->displine << 1)][(x << 3) + c + 8] =
						buffer32->line[(vid->displine << 1) + 1][(x << 3) + c + 8] =
							cols[(fontdat[vid->fontbase + chr][vid->sc & 7] & (1 << (c ^ 7))) ? 1 : 0] ^ 15;
					}
				} else {
					for (c = 0; c < 8; c++) {
						buffer32->line[(vid->displine << 1)][(x << 3) + c + 8] =
						buffer32->line[(vid->displine << 1) + 1][(x << 3) + c + 8] =
							cols[(fontdat[vid->fontbase + 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++) {
						buffer32->line[(vid->displine << 1)][(x << 4) + (c << 1) + 8] =
						buffer32->line[(vid->displine << 1)][(x << 4) + (c << 1) + 1 + 8] =
						buffer32->line[(vid->displine << 1) + 1][(x << 4) + (c << 1) + 8] =
						buffer32->line[(vid->displine << 1) + 1][(x << 4) + (c << 1) + 1 + 8] =
							cols[(fontdat[vid->fontbase + chr][vid->sc & 7] & (1 << (c ^ 7))) ? 1 : 0] ^ 15;
						}
				} else {
					for (c = 0; c < 8; c++) {
						buffer32->line[(vid->displine << 1)][(x << 4) + (c << 1) + 8] = 
						buffer32->line[(vid->displine << 1)][(x << 4) + (c << 1) + 1 + 8] =
						buffer32->line[(vid->displine << 1) + 1][(x << 4) + (c << 1) + 8] = 
						buffer32->line[(vid->displine << 1) + 1][(x << 4) + (c << 1) + 1 + 8] =
							cols[(fontdat[vid->fontbase + 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++) {
					buffer32->line[(vid->displine << 1)][(x << 4) + (c << 1) + 8] =
					buffer32->line[(vid->displine << 1)][(x << 4) + (c << 1) + 1 + 8] =
					buffer32->line[(vid->displine << 1) + 1][(x << 4) + (c << 1) + 8] =
					buffer32->line[(vid->displine << 1) + 1][(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++) {
					buffer32->line[(vid->displine << 1)][(x << 4) + c + 8] = buffer32->line[(vid->displine << 1) + 1][(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(buffer32, 0, (vid->displine << 1), (vid->crtc[1] << 3) + 16, cols[0]);
			hline(buffer32, 0, (vid->displine << 1) + 1, (vid->crtc[1] << 3) + 16, cols[0]);
		} else {
			hline(buffer32, 0, (vid->displine << 1), (vid->crtc[1] << 4) + 16, cols[0]);
			hline(buffer32, 0, (vid->displine << 1), (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 {
	timer_advance_u64(&vid->timer, 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++;

			xs_temp = x;
			ys_temp = (vid->lastline - vid->firstline) << 1;

			if ((xs_temp > 0) && (ys_temp > 0)) {
				if (xs_temp < 64) xs_temp = 656;
				if (ys_temp < 32) ys_temp = 400;
				if (!enable_overscan)
					xs_temp -= 16;

				if ((xs_temp != xsize) || (ys_temp != ysize) || video_force_resize_get()) {
					xsize = xs_temp;
					ysize = ys_temp;
					set_screen_size(xsize, ysize + (enable_overscan ? 16 : 0));

					if (video_force_resize_get())
						video_force_resize_set(0);
				}

				if (enable_overscan) {
					video_blit_memtoscreen_8(0, (vid->firstline - 4) << 1,
								 xsize, ((vid->lastline - vid->firstline) + 8) << 1);
				} else {
					video_blit_memtoscreen_8(8, vid->firstline << 1,
								 xsize, (vid->lastline - vid->firstline) << 1);
				}
			}

			video_res_x = xsize;
			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));

    video_inform(VIDEO_FLAG_TYPE_CGA, &timing_pc1512);

    vid->vram = malloc(0x10000);
    vid->cgacol = 7;
    vid->cgamode = 0x12;

    timer_add(&vid->timer, vid_poll_1512, vid, 1);
    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;

    vid->fontbase = (device_get_config_int("codepage") & 3) * 256;

    cga_palette = (device_get_config_int("display_type") << 1);
    cgapal_rebuild();

    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);
}


device_config_t vid_1512_config[] =
{
	{
		"display_type", "Display type", CONFIG_SELECTION, "", 0, "", { 0 },
		{
			{
				"PC-CM (Colour)", 0
			},
			{
				"PC-MM (Monochrome)", 3
			},
			{
				""
			}
		}
	},
	{
        "codepage", "Hardware font", CONFIG_SELECTION, "", 3, "", { 0 },
		{
			{
				"US English", 3
			},
			{
				"Danish", 1
			},
			{
				"Greek", 0
			},
			{
                ""
			}
		}
	},
	{
        "language", "BIOS language", CONFIG_SELECTION, "", 7, "", { 0 },
		{
			{
				"English", 7
			},
			{
				"German", 6
			},
			{
				"French", 5
			},
			{
				"Spanish", 4
			},
			{
				"Danish", 3
			},
			{
				"Swedish", 2
			},
			{
				"Italian", 1
			},
			{
				"Diagnostic mode", 0
			},
			{
                ""
			}
		}
	},
	{
			"", "", -1
	}
};


static const device_t vid_1512_device = {
    "Amstrad PC1512 (video)",
    "vid_1512",
    0, 0,
    NULL, vid_close_1512, NULL,
    { NULL },
    vid_speed_change_1512,
    NULL,
    vid_1512_config
};


const device_t *
pc1512_get_device(void)
{
    return(&vid_1512_device);
}


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) {
			timer_disable(&vid->ega.timer);
			timer_set_delay_u64(&vid->cga.timer, 0);
			mem_mapping_enable(&vid->cga.mapping);
			mem_mapping_disable(&vid->ega.mapping);
		} else {
			timer_disable(&vid->cga.timer);
			timer_set_delay_u64(&vid->ega.timer, 0);
			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;

    if (vid->cga_enabled)
	return(cga_in(addr, &vid->cga));
      else
	return(ega_in(addr, &vid->ega));
}


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, "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);
    timer_disable(&vid->ega.timer);

    video_inform(VIDEO_FLAG_TYPE_SPECIAL, &timing_pc1640);

    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);

    overscan_x = overscan_y = 16;

    vid->fontbase = 768;

    cga_palette = 0;
    cgapal_rebuild();

    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);
}


device_config_t vid_1640_config[] =
{
	{
        "language", "BIOS language", CONFIG_SELECTION, "", 7, "", { 0 },
		{
			{
				"English", 7
			},
			{
				"German", 6
			},
			{
				"French", 5
			},
			{
				"Spanish", 4
			},
			{
				"Danish", 3
			},
			{
				"Swedish", 2
			},
			{
				"Italian", 1
			},
			{
				"Diagnostic mode", 0
			},
			{
                ""
			}
		}
	},
        {
                "", "", -1
        }
};

static const device_t vid_1640_device = {
    "Amstrad PC1640 (video)",
    "vid_1640",
    0, 0,
    NULL, vid_close_1640, NULL,
    { NULL },
    vid_speed_changed_1640,
    NULL,
    vid_1640_config
};

const device_t *
pc1640_get_device(void)
{
    return(&vid_1640_device);
}

/* Display type */
#define PC200_CGA  0	/* CGA monitor */
#define PC200_MDA  1	/* MDA monitor */
#define PC200_TV   2	/* Television */
#define PC200_LCDC 3	/* PPC512 LCD as CGA*/
#define PC200_LCDM 4	/* PPC512 LCD as MDA*/

extern int nmi_mask;

static uint32_t blue, green;

static uint32_t lcdcols[256][2][2];


static void
ams_inform(amsvid_t *vid)
{
    switch (vid->emulation) {
	case PC200_CGA:
	case PC200_TV:
	case PC200_LCDC:
		video_inform(VIDEO_FLAG_TYPE_CGA, &timing_pc200);
		break;
	case PC200_MDA:
	case PC200_LCDM:
		video_inform(VIDEO_FLAG_TYPE_MDA, &timing_pc200);
		break;
    }
}


static void
vid_speed_changed_200(void *priv)
{
    amsvid_t *vid = (amsvid_t *)priv;

    cga_recalctimings(&vid->cga);
    mda_recalctimings(&vid->mda);
}


/* LCD colour mappings
 * 
 * 0 => solid green
 * 1 => blue on green
 * 2 => green on blue
 * 3 => solid blue
 */
static unsigned char mapping1[256] =
{
/*	0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F */
/*00*/	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/*10*/	2, 0, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1,
/*20*/	2, 2, 0, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1,
/*30*/	2, 2, 2, 0, 2, 2, 1, 1, 2, 2, 2, 1, 2, 2, 1, 1,
/*40*/	2, 2, 1, 1, 0, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1,
/*50*/	2, 2, 1, 1, 2, 0, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1,
/*60*/	2, 2, 2, 2, 2, 2, 0, 1, 2, 2, 2, 2, 2, 2, 1, 1,
/*70*/	2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 1,
/*80*/	2, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
/*90*/	2, 2, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1,
/*A0*/	2, 2, 2, 1, 2, 2, 1, 1, 2, 2, 0, 1, 2, 2, 1, 1,
/*B0*/	2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 0, 2, 2, 1, 1,
/*C0*/	2, 2, 1, 1, 2, 1, 1, 1, 2, 2, 1, 1, 0, 1, 1, 1,
/*D0*/	2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 0, 1, 1,
/*E0*/	2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 0, 1,
/*F0*/	2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0,
};

static unsigned char mapping2[256] =
{
/*	0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F */
/*00*/	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/*10*/	1, 3, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2,
/*20*/	1, 1, 3, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2,
/*30*/	1, 1, 1, 3, 1, 1, 2, 2, 1, 1, 1, 2, 1, 1, 2, 2,
/*40*/	1, 1, 2, 2, 3, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2,
/*50*/	1, 1, 2, 2, 1, 3, 2, 2, 1, 1, 2, 2, 1, 2, 2, 2,
/*60*/	1, 1, 1, 1, 1, 1, 3, 2, 1, 1, 1, 1, 1, 1, 2, 2,
/*70*/	1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 2,
/*80*/	2, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
/*90*/	1, 1, 2, 2, 2, 2, 2, 2, 1, 3, 2, 2, 2, 2, 2, 2,
/*A0*/	1, 1, 1, 2, 1, 1, 2, 2, 1, 1, 3, 2, 1, 1, 2, 2,
/*B0*/	1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 3, 1, 1, 2, 2,
/*C0*/	1, 1, 2, 2, 1, 2, 2, 2, 1, 1, 2, 2, 3, 2, 2, 2,
/*D0*/	1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 3, 2, 2,
/*E0*/	1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 3, 2,
/*F0*/	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3,
};


static void set_lcd_cols(uint8_t mode_reg)
{
    unsigned char *mapping = (mode_reg & 0x80) ? mapping2 : mapping1;
    int c;

    for (c = 0; c < 256; c++) {
	switch (mapping[c]) {
		case 0:
			lcdcols[c][0][0] = lcdcols[c][1][0] = green;
			lcdcols[c][0][1] = lcdcols[c][1][1] = green;
			break;

		case 1:
			lcdcols[c][0][0] = lcdcols[c][1][0] = 
					   lcdcols[c][1][1] = green;
			lcdcols[c][0][1] = blue;
			break;

		case 2:
			lcdcols[c][0][0] = lcdcols[c][1][0] = 
					   lcdcols[c][1][1] = blue;
			lcdcols[c][0][1] = green;
			break;

		case 3:
			lcdcols[c][0][0] = lcdcols[c][1][0] = blue;
			lcdcols[c][0][1] = lcdcols[c][1][1] = blue;
			break;
	}
    }
}


static uint8_t
vid_in_200(uint16_t addr, void *priv)
{
    amsvid_t *vid = (amsvid_t *)priv;
    cga_t *cga = &vid->cga;
    mda_t *mda = &vid->mda;
    uint8_t ret;

    switch (addr) {
	case 0x03b8:
		return(mda->ctrl);
		
	case 0x03d8:
		return(cga->cgamode);

	case 0x03dd:
		ret = vid->crtc_index;		/* Read NMI reason */
		vid->crtc_index &= 0x1f;	/* Reset NMI reason */
		nmi = 0;			/* And reset NMI flag */
		return(ret);

	case 0x03de:
		return((vid->operation_ctrl & 0xc7) | vid->dipswitches); /*External CGA*/

	case 0x03df:
		return(vid->reg_3df);
    }

    if (addr >= 0x3D0 && addr <= 0x3DF)
	return cga_in(addr, cga);

    if (addr >= 0x3B0 && addr <= 0x3BB)
	return mda_in(addr, mda);

    return 0xFF;
}


static void
vid_out_200(uint16_t addr, uint8_t val, void *priv)
{
    amsvid_t *vid = (amsvid_t *)priv;
    cga_t *cga = &vid->cga;
    mda_t *mda = &vid->mda;
    uint8_t old;

    switch (addr) {
/* 	MDA writes ============================================================== */
	case 0x3b1:
	case 0x3b3:
	case 0x3b5:
	case 0x3b7:
		/* Writes banned to CRTC registers 0-11? */
		if (!(vid->operation_ctrl & 0x40) && mda->crtcreg <= 11) {
			vid->crtc_index = 0x20 | (mda->crtcreg & 0x1f);
			if (vid->operation_ctrl & 0x80)
				nmi = 1;
			vid->reg_3df = val;
			return;
		}
		old = mda->crtc[mda->crtcreg];
		mda->crtc[mda->crtcreg] = val & crtc_mask[mda->crtcreg];
		if (old != val) {
			if (mda->crtcreg < 0xe || mda->crtcreg > 0x10) {
				fullchange = changeframecount;
				mda_recalctimings(mda);
			}
		}
		return;
	case 0x3b8:
		old = mda->ctrl;
		mda->ctrl = val;
		if ((mda->ctrl ^ old) & 3)
			mda_recalctimings(mda);
		vid->crtc_index &= 0x1F;
		vid->crtc_index |= 0x80;
		if (vid->operation_ctrl & 0x80)
			nmi = 1;
		return;

/* 	CGA writes ============================================================== */	
	case 0x03d1:
	case 0x03d3:
	case 0x03d5:
	case 0x03d7:
		if (!(vid->operation_ctrl & 0x40) && cga->crtcreg <= 11) {
			vid->crtc_index = 0x20 | (cga->crtcreg & 0x1f);
			if (vid->operation_ctrl & 0x80) 
				nmi = 1;
			vid->reg_3df = 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->crtc_index &= 0x1f;
		vid->crtc_index |= 0x80;
		if (vid->operation_ctrl & 0x80)
			nmi = 1;
		else
			set_lcd_cols(val);
		return;

/* 	PC200 control port writes ============================================== */
	case 0x03de:
		vid->crtc_index = 0x1f;
	/* 	NMI only seems to be triggered if the value being written has the high
	 * 	bit set (enable NMI). So it only protects writes to this port if you 
	 * 	let it? */
		if (val & 0x80) {
			vid->operation_ctrl = val;
			vid->crtc_index |= 0x40;
			nmi = 1;
			return;
		}
                timer_disable(&vid->cga.timer);
                timer_disable(&vid->mda.timer);
                timer_disable(&vid->timer);
		vid->operation_ctrl = val;
		/* Bits 0 and 1 control emulation and output mode */
		amstrad_log("emulation and mode = %02X\n", val & 0x03);
		if (val & 1)	/* Monitor */
			vid->emulation = (val & 2) ? PC200_MDA : PC200_CGA;
		else if (vid->type == AMS_PPC512)
			vid->emulation = (val & 2) ? PC200_LCDM : PC200_LCDC;
		else
			vid->emulation = PC200_TV;
		if (vid->emulation == PC200_CGA || vid->emulation == PC200_TV)
                        timer_advance_u64(&vid->cga.timer, 1);
		else if (vid->emulation == PC200_MDA)
                        timer_advance_u64(&vid->mda.timer, 1);
                else
                        timer_advance_u64(&vid->timer, 1);

		/* Bit 2 disables the IDA. We don't support dynamic enabling
		* and disabling of the IDA (instead, PCEM disconnects the 
		* IDA from the bus altogether) so don't implement this */	

		/* Enable the appropriate memory ranges depending whether 
		* the IDA is configured as MDA or CGA */
		if (vid->emulation == PC200_MDA || 
		    vid->emulation == PC200_LCDM) {
			mem_mapping_disable(&vid->cga.mapping);
			mem_mapping_enable(&vid->mda.mapping);
		}
		else {
			mem_mapping_disable(&vid->mda.mapping);
			mem_mapping_enable(&vid->cga.mapping);
		}
		return;
    }

    if (addr >= 0x3D0 && addr <= 0x3DF)
	cga_out(addr, val, cga);

    if (addr >= 0x3B0 && addr <= 0x3BB)
	mda_out(addr, val, mda);
}


static void 
lcd_draw_char_80(amsvid_t *vid, uint32_t *buffer, uint8_t chr, 
	uint8_t attr, int drawcursor, int blink, int sc,
	int mode160, uint8_t control)
{
    int c;
    uint8_t bits = fontdat[chr + vid->cga.fontbase][sc];
    uint8_t bright = 0;
    uint16_t mask;

    if (attr & 8) {	/* bright */
	/* The brightness algorithm appears to be: replace any bit sequence 011 
	 * with 001 (assuming an extra 0 to the left of the byte). 
	 */
	bright = bits;
	for (c = 0, mask = 0x100; c < 7; c++, mask >>= 1) {
		if (((bits & mask) == 0) && ((bits & (mask >> 1)) != 0) &&
		    ((bits & (mask >> 2)) != 0))
			bright &= ~(mask >> 1);
	}
	bits = bright;
    }

    if (drawcursor) bits ^= 0xFF;

    for (c = 0, mask = 0x80; c < 8; c++, mask >>= 1) {
	if (mode160) buffer[c] = (attr & mask) ? blue : green;
	else if (control & 0x20) /* blinking */
		buffer[c] = lcdcols[attr & 0x7F][blink][(bits & mask) ? 1 : 0];
	else	buffer[c] = lcdcols[attr][blink][(bits & mask) ? 1 : 0];
    }
}


static void 
lcd_draw_char_40(amsvid_t *vid, uint32_t *buffer, uint8_t chr, 
	uint8_t attr, int drawcursor, int blink, int sc,
	uint8_t control)
{
    int c;
    uint8_t bits = fontdat[chr + vid->cga.fontbase][sc];
    uint8_t mask = 0x80;

    if (attr & 8)	/* bright */
	bits = bits & (bits >> 1);
    if (drawcursor) bits ^= 0xFF;

    for (c = 0; c < 8; c++, mask >>= 1) {
	if (control & 0x20) {
		buffer[c*2] = buffer[c*2+1] =
			      lcdcols[attr & 0x7F][blink][(bits & mask) ? 1 : 0];
	} else {
		buffer[c*2] = buffer[c*2+1] =
			      lcdcols[attr][blink][(bits & mask) ? 1 : 0];
	}
    }
}


static void 
lcdm_poll(amsvid_t *vid)
{
    mda_t *mda = &vid->mda;
    uint16_t ca = (mda->crtc[15] | (mda->crtc[14] << 8)) & 0x3fff;
    int drawcursor;
    int x;
    int oldvc;
    uint8_t chr, attr;
    int oldsc;
    int blink;

    if (!mda->linepos) {
	timer_advance_u64(&vid->timer, mda->dispofftime);
	mda->stat |= 1;
	mda->linepos = 1;
	oldsc = mda->sc;
	if ((mda->crtc[8] & 3) == 3) 
		mda->sc = (mda->sc << 1) & 7;
	if (mda->dispon) {
		if (mda->displine < mda->firstline)
			mda->firstline = mda->displine;
		mda->lastline = mda->displine;
		for (x = 0; x < mda->crtc[1]; x++) {
			chr  = mda->vram[(mda->ma << 1) & 0xfff];
			attr = mda->vram[((mda->ma << 1) + 1) & 0xfff];
			drawcursor = ((mda->ma == ca) && mda->con && mda->cursoron);
			blink = ((mda->blink & 16) && (mda->ctrl & 0x20) && (attr & 0x80) && !drawcursor);

			lcd_draw_char_80(vid, &((uint32_t *)(buffer32->line[mda->displine]))[x * 8], chr, attr, drawcursor, blink, mda->sc, 0, mda->ctrl);
			mda->ma++;
		}
	}
	mda->sc = oldsc;
	if (mda->vc == mda->crtc[7] && !mda->sc)
		mda->stat |= 8;
	mda->displine++;
	if (mda->displine >= 500) 
		mda->displine=0;
    } else {
	timer_advance_u64(&vid->timer, mda->dispontime);
	if (mda->dispon) mda->stat&=~1;
	mda->linepos=0;
	if (mda->vsynctime) {
		mda->vsynctime--;
		if (!mda->vsynctime)
			mda->stat&=~8;
	}
	if (mda->sc == (mda->crtc[11] & 31) || ((mda->crtc[8] & 3) == 3 && mda->sc == ((mda->crtc[11] & 31) >> 1))) {
		mda->con = 0; 
		mda->coff = 1; 
	}
	if (mda->vadj) {
		mda->sc++;
		mda->sc &= 31;
		mda->ma = mda->maback;
		mda->vadj--;
		if (!mda->vadj) {
			mda->dispon = 1;
			mda->ma = mda->maback = (mda->crtc[13] | (mda->crtc[12] << 8)) & 0x3fff;
			mda->sc = 0;
		}
	} else if (mda->sc == mda->crtc[9] || ((mda->crtc[8] & 3) == 3 && mda->sc == (mda->crtc[9] >> 1))) {
		mda->maback = mda->ma;
		mda->sc = 0;
		oldvc = mda->vc;
		mda->vc++;
		mda->vc &= 127;
		if (mda->vc == mda->crtc[6]) 
		mda->dispon=0;
		if (oldvc == mda->crtc[4]) {
			mda->vc = 0;
			mda->vadj = mda->crtc[5];
			if (!mda->vadj) mda->dispon = 1;
			if (!mda->vadj) mda->ma = mda->maback = (mda->crtc[13] | (mda->crtc[12] << 8)) & 0x3fff;
			if ((mda->crtc[10] & 0x60) == 0x20) mda->cursoron = 0;
			else                                mda->cursoron = mda->blink & 16;
		}
		if (mda->vc == mda->crtc[7]) {
			mda->dispon = 0;
			mda->displine = 0;
			mda->vsynctime = 16;
			if (mda->crtc[7]) {
				x = mda->crtc[1] * 8;
				mda->lastline++;
				if ((x != xsize) || ((mda->lastline - mda->firstline) != ysize) || video_force_resize_get()) {
					xsize = x;
					ysize = mda->lastline - mda->firstline;
					if (xsize < 64) xsize = 656;
					if (ysize < 32) ysize = 200;
					set_screen_size(xsize, ysize);

					if (video_force_resize_get())
						video_force_resize_set(0);
				}
				video_blit_memtoscreen(0, mda->firstline, xsize, ysize);
				frames++;
				video_res_x = mda->crtc[1];
				video_res_y = mda->crtc[6];
				video_bpp = 0;
			}
			mda->firstline = 1000;
			mda->lastline = 0;
			mda->blink++;
		}
	} else {
		mda->sc++;
		mda->sc &= 31;
		mda->ma = mda->maback;
	}
	if ((mda->sc == (mda->crtc[10] & 31) || ((mda->crtc[8] & 3) == 3 && mda->sc == ((mda->crtc[10] & 31) >> 1))))
		mda->con = 1;
    }
}


static void 
lcdc_poll(amsvid_t *vid)
{
    cga_t *cga = &vid->cga;
    int drawcursor;
    int x, c, xs_temp, ys_temp;
    int oldvc;
    uint8_t chr, attr;
    uint16_t dat;
    int oldsc;
    uint16_t ca;
    int blink;

    ca = (cga->crtc[15] | (cga->crtc[14] << 8)) & 0x3fff;

    if (!cga->linepos) {
	timer_advance_u64(&vid->timer, cga->dispofftime);
	cga->cgastat |= 1;
	cga->linepos = 1;
	oldsc = cga->sc;
	if ((cga->crtc[8] & 3) == 3) 
		cga->sc = ((cga->sc << 1) + cga->oddeven) & 7;
	if (cga->cgadispon) {
		if (cga->displine < cga->firstline) {
			cga->firstline = cga->displine;
			video_wait_for_buffer();
		}
		cga->lastline = cga->displine;

		if (cga->cgamode & 1) {
			for (x = 0; x < cga->crtc[1]; x++) {
				chr = cga->charbuffer[x << 1];
				attr = cga->charbuffer[(x << 1) + 1];
				drawcursor = ((cga->ma == ca) && cga->con && cga->cursoron);
				blink = ((cga->cgablink & 16) && (cga->cgamode & 0x20) && (attr & 0x80) && !drawcursor);
				lcd_draw_char_80(vid, &(buffer32->line[(cga->displine << 1)])[x * 8], chr, attr, drawcursor, blink, cga->sc, cga->cgamode & 0x40, cga->cgamode);
				lcd_draw_char_80(vid, &(buffer32->line[(cga->displine << 1) + 1])[x * 8], chr, attr, drawcursor, blink, cga->sc, cga->cgamode & 0x40, cga->cgamode);
				cga->ma++;
			}
		} else if (!(cga->cgamode & 2)) {
			for (x = 0; x < cga->crtc[1]; x++) {
				chr  = cga->vram[((cga->ma << 1) & 0x3fff)];
				attr = cga->vram[(((cga->ma << 1) + 1) & 0x3fff)];
				drawcursor = ((cga->ma == ca) && cga->con && cga->cursoron);
				blink = ((cga->cgablink & 16) && (cga->cgamode & 0x20) && (attr & 0x80) && !drawcursor);
				lcd_draw_char_40(vid, &(buffer32->line[(cga->displine << 1)])[x * 16], chr, attr, drawcursor, blink, cga->sc, cga->cgamode);
				lcd_draw_char_40(vid, &(buffer32->line[(cga->displine << 1) + 1])[x * 16], chr, attr, drawcursor, blink, cga->sc, cga->cgamode);
				cga->ma++;
			}
		} else {	/* Graphics mode */
			for (x = 0; x < cga->crtc[1]; x++) {
				dat = (cga->vram[((cga->ma << 1) & 0x1fff) + ((cga->sc & 1) * 0x2000)] << 8) | cga->vram[((cga->ma << 1) & 0x1fff) + ((cga->sc & 1) * 0x2000) + 1];
				cga->ma++;
				for (c = 0; c < 16; c++) {
					buffer32->line[(cga->displine << 1)][(x << 4) + c] = buffer32->line[(cga->displine << 1) + 1][(x << 4) + c] =
						(dat & 0x8000) ? blue : green;
					dat <<= 1;
				}
			}
		}
	} else {
		if (cga->cgamode & 1) {
			hline(buffer32, 0, (cga->displine << 1), (cga->crtc[1] << 3), green);
			hline(buffer32, 0, (cga->displine << 1) + 1, (cga->crtc[1] << 3), green);
		} else {
			hline(buffer32, 0, (cga->displine << 1), (cga->crtc[1] << 4), green);
			hline(buffer32, 0, (cga->displine << 1) + 1, (cga->crtc[1] << 4), green);
		}
	}

	if (cga->cgamode & 1) x = (cga->crtc[1] << 3);
	else                  x = (cga->crtc[1] << 4);

	cga->sc = oldsc;
	if (cga->vc == cga->crtc[7] && !cga->sc)
		cga->cgastat |= 8;
	cga->displine++;
	if (cga->displine >= 360) 
		cga->displine = 0;
    } else {
	timer_advance_u64(&vid->timer, cga->dispontime);
	cga->linepos = 0;
	if (cga->vsynctime) {
		cga->vsynctime--;
		if (!cga->vsynctime)
			cga->cgastat &= ~8;
	}
	if (cga->sc == (cga->crtc[11] & 31) || ((cga->crtc[8] & 3) == 3 && cga->sc == ((cga->crtc[11] & 31) >> 1))) {
		cga->con = 0; 
		cga->coff = 1; 
	}
	if ((cga->crtc[8] & 3) == 3 && cga->sc == (cga->crtc[9] >> 1))
		cga->maback = cga->ma;
	if (cga->vadj) {
		cga->sc++;
		cga->sc &= 31;
		cga->ma = cga->maback;
		cga->vadj--;
		if (!cga->vadj) {
			cga->cgadispon = 1;
			cga->ma = cga->maback = (cga->crtc[13] | (cga->crtc[12] << 8)) & 0x3fff;
			cga->sc = 0;
		}
	} else if (cga->sc == cga->crtc[9]) {
		cga->maback = cga->ma;
		cga->sc = 0;
		oldvc = cga->vc;
		cga->vc++;
		cga->vc &= 127;

		if (cga->vc == cga->crtc[6]) 
			cga->cgadispon = 0;

		if (oldvc == cga->crtc[4]) {
			cga->vc = 0;
			cga->vadj = cga->crtc[5];
			if (!cga->vadj) cga->cgadispon = 1;
			if (!cga->vadj) cga->ma = cga->maback = (cga->crtc[13] | (cga->crtc[12] << 8)) & 0x3fff;
			if ((cga->crtc[10] & 0x60) == 0x20) cga->cursoron = 0;
			else                                cga->cursoron = cga->cgablink & 8;
		}

		if (cga->vc == cga->crtc[7]) {
			cga->cgadispon = 0;
			cga->displine = 0;
			cga->vsynctime = 16;
			if (cga->crtc[7]) {
				if (cga->cgamode & 1) x = (cga->crtc[1] << 3);
				else                  x = (cga->crtc[1] << 4);
				cga->lastline++;

				xs_temp = x;
				ys_temp = (cga->lastline - cga->firstline) << 1;

				if ((xs_temp > 0) && (ys_temp > 0)) {
					if (xs_temp < 64) xs_temp = 640;
					if (ys_temp < 32) ys_temp = 400;

					if ((cga->cgamode & 8) && ((xs_temp != xsize) || (ys_temp != ysize) || video_force_resize_get())) {
						xsize = xs_temp;
						ysize = ys_temp;
						set_screen_size(xsize, ysize);

						if (video_force_resize_get())
							video_force_resize_set(0);
					}

					video_blit_memtoscreen(0, cga->firstline << 1,
							       xsize, (cga->lastline - cga->firstline) << 1);
				}

				frames++;

				video_res_x = xsize;
				video_res_y = ysize;
				if (cga->cgamode & 1) {
					video_res_x /= 8;
					video_res_y /= cga->crtc[9] + 1;
					video_bpp = 0;
				} else if (!(cga->cgamode & 2)) {
					video_res_x /= 16;
					video_res_y /= cga->crtc[9] + 1;
					video_bpp = 0;
				} else if (!(cga->cgamode & 16)) {
					video_res_x /= 2;
					video_bpp = 2;
				} else
					video_bpp = 1;
			}
			cga->firstline = 1000;
			cga->lastline = 0;
			cga->cgablink++;
			cga->oddeven ^= 1;
		}
	} else {
		cga->sc++;
		cga->sc &= 31;
		cga->ma = cga->maback;
	}
	if (cga->cgadispon)
		cga->cgastat &= ~1;
	if ((cga->sc == (cga->crtc[10] & 31) || ((cga->crtc[8] & 3) == 3 && cga->sc == ((cga->crtc[10] & 31) >> 1)))) 
		cga->con = 1;
	if (cga->cgadispon && (cga->cgamode & 1)) {
		for (x = 0; x < (cga->crtc[1] << 1); x++)
			cga->charbuffer[x] = cga->vram[(((cga->ma << 1) + x) & 0x3fff)];
	}
    }
}


static void 
vid_poll_200(void *p)
{
    amsvid_t *vid = (amsvid_t *)p;

    switch (vid->emulation) {
	case PC200_LCDM:
		lcdm_poll(vid);
		return;
	case PC200_LCDC:	
		lcdc_poll(vid);
		return;
    }
}


static void
vid_init_200(amstrad_t *ams)
{
    amsvid_t *vid;
    cga_t *cga;
    mda_t *mda;

    /* Allocate a video controller block. */
    vid = (amsvid_t *)malloc(sizeof(amsvid_t));
    memset(vid, 0x00, sizeof(amsvid_t));

    vid->emulation = device_get_config_int("video_emulation");

    /* Default to CGA */
    vid->dipswitches = 0x10;	
    vid->type = ams->type;

    if (ams->type == AMS_PC200) switch (vid->emulation) {
	/* DIP switches for PC200. Switches 2,3 give video emulation.
	 * Switch 1 is 'swap floppy drives' (not implemented) */
	case PC200_CGA:  vid->dipswitches = 0x10; break;
	case PC200_MDA:  vid->dipswitches = 0x30; break;
	case PC200_TV:   vid->dipswitches = 0x00; break;
	/* The other combination is 'IDA disabled' (0x20) - see
	 * m_amstrad.c */
    } else switch (vid->emulation) {
	/* DIP switches for PPC512. Switch 1 is CRT/LCD. Switch 2
	 * is MDA / CGA. Switch 3 disables IDA, not implemented. */
	/* 1 = on, 0 = off
	   SW1: off = crt, on = lcd;
	   SW2: off = mda, on = cga;
	   SW3: off = disable built-in card, on = enable */
	case PC200_CGA:  vid->dipswitches = 0x08; break;
	case PC200_MDA:  vid->dipswitches = 0x18; break;
	case PC200_LCDC: vid->dipswitches = 0x00; break;
	case PC200_LCDM: vid->dipswitches = 0x10; break;
    }

    cga = &vid->cga;
    mda = &vid->mda;
    cga->vram = mda->vram = malloc(0x4000);
    cga_init(cga);
    mda_init(mda);

    cga_palette = (device_get_config_int("display_type") << 1);
    ams_inform(vid);

    /* Attribute 8 is white on black (on a real MDA it's black on black) */
    mda_setcol(0x08, 0, 1, 15);
    mda_setcol(0x88, 0, 1, 15);
    /* Attribute 64 is black on black (on a real MDA it's white on black) */
    mda_setcol(0x40, 0, 1, 0);
    mda_setcol(0xC0, 0, 1, 0);	

    cga->fontbase = (device_get_config_int("codepage") & 3) * 256;

    timer_add(&vid->timer, vid_poll_200, vid, 1);
    mem_mapping_add(&vid->mda.mapping, 0xb0000, 0x08000, 
		    mda_read, NULL, NULL, mda_write, NULL, NULL, NULL, 0, mda);
    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);
    io_sethandler(0x03b0, 0x000c, vid_in_200, NULL, NULL, vid_out_200, NULL, NULL, vid);

    overscan_x = overscan_y = 16;

    if (ams->type == AMS_PC200)
	vid->invert = 0;
    else
	vid->invert = device_get_config_int("invert");
    if (vid->invert) {
	blue = makecol(0x1C, 0x71, 0x31);
	green = makecol(0x0f, 0x21, 0x3f);
    } else {
	green = makecol(0x1C, 0x71, 0x31);
	blue = makecol(0x0f, 0x21, 0x3f);
    }
    cgapal_rebuild();
    set_lcd_cols(0);

    timer_disable(&vid->cga.timer);
    timer_disable(&vid->mda.timer);
    timer_disable(&vid->timer);
    if (vid->emulation == PC200_CGA || vid->emulation == PC200_TV)
	timer_enable(&vid->cga.timer);
    else if (vid->emulation == PC200_MDA)
	timer_enable(&vid->mda.timer);
    else
	timer_enable(&vid->timer);

    ams->vid = vid;
}


static void
vid_close_200(void *priv)
{
    amsvid_t *vid = (amsvid_t *)priv;

    if (vid->cga.vram != vid->mda.vram) {
	free(vid->cga.vram);
	free(vid->mda.vram);
    } else
	free(vid->cga.vram);

    vid->cga.vram = vid->mda.vram = NULL;

    free(vid);
}


device_config_t vid_200_config[] =
{
	/* TODO: Should have options here for:
	*
	*	> Display port (TTL or RF)
	*/
	{
		"video_emulation", "Display type", CONFIG_SELECTION, "", PC200_CGA, "", { 0 },
		{
			{
				"CGA monitor", PC200_CGA
			},
			{
				"MDA monitor", PC200_MDA
			},
			{
				"Television", PC200_TV
			},
			{
				""
			}
		}
	},
        {
		"display_type", "Monitor type", CONFIG_SELECTION, "", 0, "", { 0 },
		{
			{
					"RGB", 0
			},
			{
					"RGB (no brown)", 4
			},
			{
					"Green Monochrome", 1
			},
			{
					"Amber Monochrome", 2
			},
			{
					"White Monochrome", 3
			},
			{
				   ""
			}
		}
        },
        {
		"codepage", "Hardware font", CONFIG_SELECTION, "", 3, "", { 0 },
		{
			{
				"US English", 3
			},
			{
				"Portugese", 2
			},
			{
				"Norwegian", 1
			},
			{
				"Greek", 0
			},
			{
				""
			}
		}
	},
	{
        "language", "BIOS language", CONFIG_SELECTION, "", 7, "", { 0 },
		{
			{
				"English", 7
			},
			{
				"German", 6
			},
			{
				"French", 5
			},
			{
				"Spanish", 4
			},
			{
				"Danish", 3
			},
			{
				"Swedish", 2
			},
			{
				"Italian", 1
			},
			{
				"Diagnostic mode", 0
			},
			{
                ""
			}
		}
	},
        {
                "", "", -1
        }
};


static const device_t vid_200_device = {
    "Amstrad PC200 (video)",
    "vid_200",
    0, 0,
    NULL, vid_close_200, NULL,
    { NULL },
    vid_speed_changed_200,
    NULL,
    vid_200_config
};


const device_t *
pc200_get_device(void)
{
    return(&vid_200_device);
}


device_config_t vid_ppc512_config[] =
{
	/* TODO: Should have options here for:
	*
	*	> Display port (TTL or RF)
	*/
	{
		"video_emulation", "Display type", CONFIG_SELECTION, "", PC200_LCDC, "", { 0 },
		{
			{
				"CGA monitor", PC200_CGA
			},
			{
				"MDA monitor", PC200_MDA
			},
			{
				"LCD (CGA mode)", PC200_LCDC
			},
			{
				"LCD (MDA mode)", PC200_LCDM
			},
		{
				""
			}
		},
	},
        {
                "display_type", "Monitor type", CONFIG_SELECTION, "", 0, "", { 0 },
                {
                        {
                                "RGB", 0
                        },
                        {
                                "RGB (no brown)", 4
                        },
                        {
                                "Green Monochrome", 1
                        },
                        {
                                "Amber Monochrome", 2
                        },
                        {
                                "White Monochrome", 3
                        },
                        {
                                ""
                        }
                },
        },
        {
                "codepage", "Hardware font", CONFIG_SELECTION, "", 3, "", { 0 },
				{
					{
						"US English", 3
					},
					{
						"Portugese", 2
					},
					{
						"Norwegian",1
					},
					{
						"Greek", 0
					},
					{
						""
					}
				},
        },
	{
        "language", "BIOS language", CONFIG_SELECTION, "", 7, "", { 0 },
		{
			{
				"English", 7
			},
			{
				"German", 6
			},
			{
				"French", 5
			},
			{
				"Spanish", 4
			},
			{
				"Danish", 3
			},
			{
				"Swedish", 2
			},
			{
				"Italian", 1
			},
			{
				"Diagnostic mode", 0
			},
			{
                ""
			}
		}
	},
	{
		"invert", "Invert LCD colors", CONFIG_BINARY, "", 0
	},
        {
                "", "", -1
        }
};

static const device_t vid_ppc512_device = {
    "Amstrad PPC512 (video)",
    "vid_ppc512",
    0, 0,
    NULL, vid_close_200, NULL,
    { NULL },
    vid_speed_changed_200,
    NULL,
    vid_ppc512_config
};


const device_t *
ppc512_get_device(void)
{
    return(&vid_ppc512_device);
}


device_config_t vid_pc2086_config[] =
{
	{
        "language", "BIOS language", CONFIG_SELECTION, "", 7, "", { 0 },
		{
			{
				"English", 7
			},
			{
				"Diagnostic mode", 0
			},
			{
                ""
			}
		}
	},
        {
                "", "", -1
        }
};

static const device_t vid_pc2086_device = {
    "Amstrad PC2086",
    "vid_pc2086",
    0, 0,
    NULL, NULL, NULL,
    { NULL },
    NULL,
    NULL,
    vid_pc2086_config
};


const device_t *
pc2086_get_device(void)
{
    return(&vid_pc2086_device);
}


device_config_t vid_pc3086_config[] =
{
	{
        "language", "BIOS language", CONFIG_SELECTION, "", 7, "", { 0 },
		{
			{
				"English", 7
			},
			{
				"Diagnostic mode", 3
			},
			{
                ""
			}
		}
	},
        {
                "", "", -1
        }
};

static const device_t vid_pc3086_device = {
    "Amstrad PC3086",
    "vid_pc3086",
    0, 0,
    NULL, NULL, NULL,
    { NULL },
    NULL,
    NULL,
    vid_pc3086_config
};


const device_t *
pc3086_get_device(void)
{
    return(&vid_pc3086_device);
}


static void
ms_write(uint16_t addr, uint8_t val, void *priv)
{
    amstrad_t *ams = (amstrad_t *)priv;

    if ((addr == 0x78) || (addr == 0x79))
	ams->mousex = 0;
    else
	ams->mousey = 0;
}


static uint8_t
ms_read(uint16_t addr, void *priv)
{
    amstrad_t *ams = (amstrad_t *)priv;
    uint8_t ret;

    if ((addr == 0x78) || (addr == 0x79)) {
	ret = ams->mousex;
	ams->mousex = 0;
    } else {
	ret = ams->mousey;
	ams->mousey = 0;
    }

    return(ret);
}


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 & 1) && (ams->oldb & 1))
	keyboard_send(0xfe);

    if ((b & 2) && !(ams->oldb & 2))
	keyboard_send(0x7d);
    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;
    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;

    amstrad_log("keyboard_amstrad : write %04X %02X %02X\n", port, val, ams->pb);

    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.
		 */
		amstrad_log("AMSkb: write PB %02x (%02x)\n", val, ams->pb);
		if (!(ams->pb & 0x40) && (val & 0x40)) { /*Reset keyboard*/
			amstrad_log("AMSkb: reset keyboard\n");
			kbd_adddata(0xaa);
		}
		ams->pb = val;
		ppi.pb = val;

		speaker_update();
		speaker_gated = val & 0x01;
		speaker_enable = val & 0x02;
		if (speaker_enable) 
			was_speaker_enable = 1;
		pit_ctr_set_gate(&pit->counters[2], val & 0x01);

		if (val & 0x80) {
			/* Keyboard enabled, so enable PA reading. */
			ams->pa = 0x00;
		}
		break;

	case 0x63:
		break;

	case 0x64:
		ams->stat1 = val;
		break;

	case 0x65:
		ams->stat2 = val;
		break;

	case 0x66:
		softresetx86();
		cpu_set_edx();
		break;

	default:
		amstrad_log("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 = (ams->stat1 | 0x0d) & 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:
		amstrad_log("AMDkb: bad keyboard read %04X\n", port);
    }

    return(ret);
}


static void
kbd_poll(void *priv)
{
    amstrad_t *ams = (amstrad_t *)priv;

    timer_advance_u64(&ams->send_delay_timer, 1000 * TIMER_USEC);

    if (ams->wantirq) {
	ams->wantirq = 0;
	ams->pa = ams->key_waiting;
	picint(2);
    }

    if (key_queue_start != key_queue_end && !ams->pa) {
	ams->key_waiting = key_queue[key_queue_start];
	key_queue_start = (key_queue_start + 1) & 0x0f;
	ams->wantirq = 1;
    }
}


static void
ams_write(uint16_t port, uint8_t val, void *priv)
{
    amstrad_t *ams = (amstrad_t *)priv;

    switch (port) {
	case 0x0378:
	case 0x0379:
	case 0x037a:
		lpt_write(port, val, &lpt_ports[0]);
		break;

	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 0x0378:
		ret = lpt_read(port, &lpt_ports[0]);
		break;

	case 0x0379:	/* printer control, also set LK1-3.
			 * per John Elliott's site, this is xor'ed with 0x07
			 *   7	English Language.
			 *   6	German Language.
			 *   5	French Language.
			 *   4	Spanish Language.
			 *   3	Danish Language.
			 *   2	Swedish Language.
			 *   1	Italian Language.
			 *   0	Diagnostic Mode.
			 */
		ret = (lpt_read(port, &lpt_ports[0]) & 0xf8) | ams->language;
		break;

	case 0x037a:	/* printer status */
		ret = lpt_read(port, &lpt_ports[0]) & 0x1f;

		switch(ams->type) {
			case AMS_PC1512:
				ret |= 0x20;
				break;

			case AMS_PC200:
			case AMS_PPC512:
				if (video_is_cga())
					ret |= 0x80;
				else if (video_is_mda())
					ret |= 0xc0;

				if (fdc_read(0x037f, ams->fdc) & 0x80)
					ret |= 0x20;
				break;

			case AMS_PC1640:
				if (video_is_cga())
					ret |= 0x80;
				else if (video_is_mda())
					ret |= 0xc0;

				switch (amstrad_latch) {
					case AMSTRAD_NOLATCH:
						ret &= ~0x20;
						break;
					case AMSTRAD_SW9:
						ret &= ~0x20;
						break;
					case AMSTRAD_SW10:
						ret |= 0x20;
						break;
				}
				break;

			default:
				break;
		}
		break;

	case 0x03de:
		ret = 0x20;
		break;

	case 0xdead:
		ret = ams->dead;
		break;
    }

    return(ret);
}


static void
machine_amstrad_init(const machine_t *model, int type)
{
    amstrad_t *ams;

    ams = (amstrad_t *)malloc(sizeof(amstrad_t));
    memset(ams, 0x00, sizeof(amstrad_t));
    ams->type = type;

    device_add(&amstrad_nvr_device);

    machine_common_init(model);

    nmi_init();

    lpt1_remove_ams();
    lpt2_remove();

    io_sethandler(0x0378, 3,
		  ams_read, NULL, NULL, ams_write, NULL, NULL, ams);
    io_sethandler(0xdead, 1,
		  ams_read, NULL, NULL, ams_write, NULL, NULL, ams);

    switch(type) {
	case AMS_PC1512:
	case AMS_PC1640:
	case AMS_PC200:
	case AMS_PPC512:
		ams->fdc = device_add(&fdc_xt_device);
		break;

	case AMS_PC2086:
	case AMS_PC3086:
		ams->fdc = device_add(&fdc_at_actlow_device);
		break;
    }

    ams->language = 7;

    video_reset(gfxcard);

    if (gfxcard == VID_INTERNAL) switch(type) {
	case AMS_PC1512:
		loadfont("roms/machines/pc1512/40078", 8);
		device_context(&vid_1512_device);
		ams->language = device_get_config_int("language");
		vid_init_1512(ams);
		device_context_restore();
		device_add_ex(&vid_1512_device, ams->vid);
		break;
	
	case AMS_PPC512:
		loadfont("roms/machines/ppc512/40109", 1);
		device_context(&vid_ppc512_device);
		ams->language = device_get_config_int("language");
		vid_init_200(ams);
		device_context_restore();
		device_add_ex(&vid_ppc512_device, ams->vid);
		break;
	
	case AMS_PC1640:
		loadfont("roms/video/mda/mda.rom", 0);
		device_context(&vid_1640_device);
		ams->language = device_get_config_int("language");
		vid_init_1640(ams);
		device_context_restore();
		device_add_ex(&vid_1640_device, ams->vid);
		break;

	case AMS_PC200:
		loadfont("roms/machines/pc200/40109", 1);
		device_context(&vid_200_device);
		ams->language = device_get_config_int("language");
		vid_init_200(ams);
		device_context_restore();
		device_add_ex(&vid_200_device, ams->vid);
		break;

	case AMS_PC2086:
		device_context(&vid_pc2086_device);
		ams->language = device_get_config_int("language");
		device_context_restore();
		device_add(&paradise_pvga1a_pc2086_device);
		break;

	case AMS_PC3086:
		device_context(&vid_pc3086_device);
		ams->language = device_get_config_int("language");
		device_context_restore();
		device_add(&paradise_pvga1a_pc3086_device);
		break;
    } else if ((type == AMS_PC200) || (type == AMS_PPC512))
	io_sethandler(0x03de, 1,
		      ams_read, NULL, NULL, ams_write, NULL, NULL, ams);

    /* Initialize the (custom) keyboard/mouse interface. */
    ams->wantirq = 0;
    io_sethandler(0x0060, 7,
		  kbd_read, NULL, NULL, kbd_write, NULL, NULL, ams);
    timer_add(&ams->send_delay_timer, kbd_poll, ams, 1);
    keyboard_set_table(scancode_xt);
    keyboard_send = kbd_adddata_ex;
    keyboard_scan = 1;
    keyboard_set_is_amstrad(((type == AMS_PC1512) || (type == AMS_PC1640)) ? 0 : 1);

    io_sethandler(0x0078, 2,
		  ms_read, NULL, NULL, ms_write, NULL, NULL, ams);
    io_sethandler(0x007a, 2,
		  ms_read, NULL, NULL, ms_write, NULL, NULL, ams);

    if (mouse_type == MOUSE_TYPE_INTERNAL) {
	/* Tell mouse driver about our internal mouse. */
	mouse_reset();
	mouse_set_poll(ms_poll, ams);
    }

    standalone_gameport_type = &gameport_device;
}


int
machine_pc1512_init(const machine_t *model)
{
    int ret;

    ret = bios_load_interleaved("roms/machines/pc1512/40044",
				"roms/machines/pc1512/40043",
				0x000fc000, 16384, 0);
    ret &= rom_present("roms/machines/pc1512/40078");

    if (bios_only || !ret)
	return ret;

    machine_amstrad_init(model, AMS_PC1512);

    return ret;
}


int
machine_pc1640_init(const machine_t *model)
{
    int ret;

    ret = bios_load_interleaved("roms/machines/pc1640/40044.v3",
				"roms/machines/pc1640/40043.v3",
				0x000fc000, 16384, 0);
    ret &= rom_present("roms/machines/pc1640/40100");

    if (bios_only || !ret)
	return ret;

    machine_amstrad_init(model, AMS_PC1640);

    return ret;
}


int
machine_pc200_init(const machine_t *model)
{
    int ret;

    ret = bios_load_interleaved("roms/machines/pc200/pc20v2.1",
				"roms/machines/pc200/pc20v2.0",
				0x000fc000, 16384, 0);
    ret &= rom_present("roms/machines/pc200/40109");

    if (bios_only || !ret)
	return ret;

    machine_amstrad_init(model, AMS_PC200);

    return ret;
}


int
machine_ppc512_init(const machine_t *model)
{
    int ret;

    ret = bios_load_interleaved("roms/machines/ppc512/40107.v2",
				"roms/machines/ppc512/40108.v2",
				0x000fc000, 16384, 0);
    ret &= rom_present("roms/machines/ppc512/40109");

    if (bios_only || !ret)
	return ret;

    machine_amstrad_init(model, AMS_PPC512);

    return ret;
}


int
machine_pc2086_init(const machine_t *model)
{
    int ret;

    ret = bios_load_interleavedr("roms/machines/pc2086/40179.ic129",
				 "roms/machines/pc2086/40180.ic132",
				 0x000fc000, 65536, 0);
    ret &= rom_present("roms/machines/pc2086/40186.ic171");

    if (bios_only || !ret)
	return ret;

    machine_amstrad_init(model, AMS_PC2086);

    return ret;
}


int
machine_pc3086_init(const machine_t *model)
{
    int ret;

    ret = bios_load_linearr("roms/machines/pc3086/fc00.bin",
			    0x000fc000, 65536, 0);
    ret &= rom_present("roms/machines/pc3086/c000.bin");

    if (bios_only || !ret)
	return ret;

    machine_amstrad_init(model, AMS_PC3086);

    return ret;
}
