/*
 * Copyright (C) 2004-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#ifdef STATE

struct {
	/* Internal Registers */
	unsigned int scanline;
	unsigned long offset;

	/* General or External Registers */
	/* p. 342 */
	unsigned char misc_output;			/* p. 343 */
	unsigned char feature_control;			/* p. 346 */
	unsigned char input_status_0;			/* p. 346 */
	unsigned char input_status_1;			/* p. 347 */

	/* Attribute Controller */
	/* p. 404 */
	unsigned char attr_reg_03c0;				/* p. 404 */
	unsigned char attr_reg_03c0_state;

	/*00-0f*/unsigned char attr_palette[16];		/* p. 406 */
	/*10*/	unsigned char attr_mode_control;		/* p. 408 */
	/*11*/	unsigned char attr_overscan_color;		/* p. 411 */
	/*12*/	unsigned char attr_color_plane_enable;		/* p. 413 */
	/*13*/	unsigned char attr_horizontal_pixel_panning;	/* p. 415 */
	/*14*/	unsigned char attr_color_select;		/* p. 417 */

	/* Sequencer */
	/* p. 349 */
	unsigned char seq_reg_03c4;

	/*00*/	unsigned char seq_reset;			/* p. 349 */
	/*01*/	unsigned char seq_clocking_mode;		/* p. 351 */
	/*02*/	unsigned char seq_map_mask;			/* p. 353 */
	/*03*/	unsigned char seq_character_map_select;		/* p. 355 */
	/*04*/	unsigned char seq_memory_mode;			/* p. 357 */

	/* Color Registers */
	/* p. 418 */
	unsigned char col_reg_03c7;				/* p. 419 */
	unsigned char col_reg_03c7_rgb;
	unsigned char col_reg_03c8;				/* p. 418 */
	unsigned char col_reg_03c8_rgb;
	unsigned char col_pel_mask;				/* p. 421 */

	/* unsigned char col_colregs[256][3]; */

	/* Graphics Controller */
	/* p. 386 */
	unsigned char gr_reg_03ce;

	/*00*/	unsigned char gr_set_reset;			/* p. 388 */
	/*01*/	unsigned char gr_enable_set_reset;		/* p. 390 */
	/*02*/	unsigned char gr_color_compare;			/* p. 390 */
	/*03*/	unsigned char gr_data_rotate;			/* p. 392 */
	/*04*/	unsigned char gr_read_map_select;		/* p. 395 */
	/*05*/	unsigned char gr_mode;				/* p. 396 */
	/*06*/	unsigned char gr_misc;				/* p. 401 */
	/*07*/	unsigned char gr_color_dont_care;		/* p. 402 */
	/*08*/	unsigned char gr_bit_mask;			/* p. 403 */

	unsigned char gr_latch[4];

	/* CRT Controller */
	/* p. 358 */
	unsigned char crt_reg_03d4;				/* p. 358 */

	/*00*/	unsigned char crt_horizontal_total;		/* p. 359 */
	/*01*/	unsigned char crt_horizontal_display_end;	/* p. 360 */
	/*02*/	unsigned char crt_start_horizontal_blanking;	/* p. 361 */
	/*03*/	unsigned char crt_end_horizontal_blanking;	/* p. 362 */
	/*04*/	unsigned char crt_start_horizontal_retrace;	/* p. 363 */
	/*05*/	unsigned char crt_end_horizontal_retrace;	/* p. 364 */
	/*06*/	unsigned char crt_vertical_total;		/* p. 365 */
	/*07*/	unsigned char crt_overflow;			/* p. 365 */
	/*08*/	unsigned char crt_preset_row_scan;		/* p. 367 */
	/*09*/	unsigned char crt_maximum_scan_line;		/* p. 369 */
	/*0a*/	unsigned char crt_cursor_start;			/* p. 370 */
	/*0b*/	unsigned char crt_cursor_end;			/* p. 371 */
	/*0c/0d*/unsigned short crt_top_pos;			/* p. 371-373 */
	/*0e/0f*/unsigned short crt_cur_pos;			/* p. 373-374 */
	/*10*/	unsigned char crt_vertical_retrace_start;	/* p. 374 */
	/*11*/	unsigned char crt_vertical_retrace_end;		/* p. 375 */
	/*12*/	unsigned char crt_vertical_display_end;		/* p. 378 */
	/*13*/	unsigned char crt_offset;			/* p. 379 */
	/*14*/	unsigned char crt_underline_location;		/* p. 379 */
	/*15*/	unsigned char crt_start_vertical_blank;		/* p. 380 */
	/*16*/	unsigned char crt_end_vertical_blank;		/* p. 381 */
	/*17*/	unsigned char crt_mode_control;			/* p. 382 */
	/*18*/	unsigned char crt_line_compare;			/* p. 385 */
} NAME;

#endif /* STATE */

#ifdef BEHAVIOUR

#ifndef ARCH_VGA_PORT
#error arch_vga.c needs ARCH_VGA_PORT defined
#endif
#ifndef REFRESH_CYCLES
#error arch_vga.c needs REFRESH_CYCLES defined
#endif

#define DEBUG	0

#define TILE_WIDTH 8

static const struct {
	unsigned long start;
	unsigned long end;
} NAME_(memory_location)[4] = {
	{ 0xa0000, 0xbffff },
	{ 0xa0000, 0xaffff },
	{ 0xb0000, 0xb7fff },
	{ 0xb8000, 0xbffff }
};

static inline unsigned char
NAME_(readb)(struct cpssp *cpssp, unsigned long pa)
{
#if 2 <= DEBUG
	faum_log(FAUM_LOG_DEBUG, "vga", "",
		 "%s: pa=0x%05lx\n", __FUNCTION__, pa);
#endif
	assert(0 <= pa && pa < SZ_VIDEO_MEMORY_LOW);
	
	/* FIXME: byte/word switch in CRT-Controller Reg 0x17 Bit 6 */
	
	if (cpssp->NAME.seq_memory_mode & 0x08) {
		/* Chain 4 Mode */
		/* In this mode, the planes are made to look as if they were
		 * sequential. Easiest mode for the user... */
		unsigned char i;
		
		for (i = 0; i < 4; i++) {
			cpssp->NAME.gr_latch[i] =
				video_readb(cpssp, (pa / 4) + (i * 0x10000));
		}
#if 1 <= DEBUG
		faum_log(FAUM_LOG_DEBUG, "vga", "",
			 "%s: pa=0x%08lx chain4 val=%02x\n",
			 __FUNCTION__,
			 (pa / 4) + ((pa % 4) * 0x10000),
			  cpssp->NAME.gr_latch[i]);
#endif
		return cpssp->NAME.gr_latch[cpssp->NAME.gr_read_map_select & 3];
		
	} else if ((cpssp->NAME.gr_mode >> 4) & 1) {
		/* Odd/even mode. */
		unsigned char i;

		pa = (pa >> 1) | ((pa & 1) << 16);
#if 0
		/* FIXME This should actually work but doesn't, could be our
		 * initialization does this wrong and inits it to 1? */
		pa |= (((cpssp->NAME.misc_output >> 5) & 1) << 17);
#endif
		for (i = 0; i < 4; i++) {
			cpssp->NAME.gr_latch[i] =
				video_readb(cpssp, (pa % 0x10000) + (i * 0x10000));
		}
#if 1 <= DEBUG
		faum_log(FAUM_LOG_DEBUG, "vga", "",
			 "%s: pa=0x%08lx odd/even miscout=%1d val=%02x\n",
			 __FUNCTION__, pa, (cpssp->NAME.misc_output >> 5) & 1,
			 cpssp->NAME.gr_latch[pa / 0x10000]);
#endif
		return cpssp->NAME.gr_latch[pa / 0x10000];

	} else {
		/* Sequential mode. */

		/* FIXME VOSSI */
		if (pa < 0xa0000 || 0xb0000 <= pa) {
#if 1 <= DEBUG
			faum_log(FAUM_LOG_DEBUG, "vga", "",
				 "%s: pa=0x%04lx rmode=%1d",
				 __FUNCTION__, pa, ((cpssp->NAME.gr_mode >> 3) & 1));
#endif
#if 0
			faum_cont(FAUM_LOG_DEBUG, "\n");
			return;
#endif
		}

		cpssp->NAME.gr_latch[0] = video_readb(cpssp, pa + 0x00000);
		cpssp->NAME.gr_latch[1] = video_readb(cpssp, pa + 0x10000);
		cpssp->NAME.gr_latch[2] = video_readb(cpssp, pa + 0x20000);
		cpssp->NAME.gr_latch[3] = video_readb(cpssp, pa + 0x30000);
		if ((cpssp->NAME.gr_mode >> 3) & 1) {
			/*
			 * Read Mode 1
			 */
			unsigned char val0;
			unsigned char val1;
			unsigned char val2;
			unsigned char val3;
			unsigned char cmp;
			unsigned char res;
			int i;

			res = 0x00;
			val0 = cpssp->NAME.gr_latch[0];
			val1 = cpssp->NAME.gr_latch[1];
			val2 = cpssp->NAME.gr_latch[2];
			val3 = cpssp->NAME.gr_latch[3];
			for (i = 0; i < 8; i++) {
				res <<= 1;
				cmp = ((val3 & 0x80) >> 4)
				    | ((val2 & 0x80) >> 5)
				    | ((val1 & 0x80) >> 6)
				    | ((val0 & 0x80) >> 7);
				if ((cmp & cpssp->NAME.gr_color_dont_care)
				 == (cpssp->NAME.gr_color_compare
				     & cpssp->NAME.gr_color_dont_care)) {
					res |= 0x01;
				}
				val0 <<= 1;
				val1 <<= 1;
				val2 <<= 1;
				val3 <<= 1;
			}

#if 1 <= DEBUG
			faum_cont(FAUM_LOG_DEBUG, " val=0x%02x\n", res);
#endif
			return res;

		} else {
			/*
			 * Read Mode 0
			 */
#if 1 <= DEBUG
			faum_cont(FAUM_LOG_DEBUG, " val=0x%02x\n",
				  cpssp->NAME.gr_latch[cpssp->NAME.gr_read_map_select & 3]);
#endif
			return cpssp->NAME.gr_latch[cpssp->NAME.gr_read_map_select & 3];
		}
	}
}

static inline void
NAME_(writeb)(struct cpssp *cpssp, unsigned long pa, unsigned char val)
{
#if 2 <= DEBUG
	faum_log(FAUM_LOG_DEBUG, "vga", "",
		 "%s: pa=0x%05lx, val=0x%02x\n",
		 __FUNCTION__, pa, val);
#endif
	assert(0 <= pa && pa < SZ_VIDEO_MEMORY_LOW);

	if ((cpssp->NAME.seq_memory_mode >> 3) & 1) {
		/* Chain 4 Mode */
		pa = ((pa >> 2) & 0xffff) | ((pa & 3) << 16);
#if 1 <= DEBUG
		faum_log(FAUM_LOG_DEBUG, "vga", "",
			 "%s: pa=0x%08lx chain4 val=%02x\n",
			 __FUNCTION__, pa, val);
#endif
		if (cpssp->NAME.seq_map_mask & (1 << (pa / 0x10000))) {
			video_writeb(cpssp, pa, val);
		}

	} else if (!((cpssp->NAME.seq_memory_mode >> 2) & 1)
		&& ((cpssp->NAME.gr_mode >> 4) & 1)) {
		/* Odd/even mode. */
		pa = ((pa >> 1) & 0xffff) | ((pa & 1) << 16);
#if 0
		/* FIXME This should actually work but doesn't! */
		pa |= (((cpssp->NAME.misc_output >> 5) & 1) << 17);
#endif
#if 2 <= DEBUG
		faum_log(FAUM_LOG_DEBUG, "vga", "",
			 "%s: odd/even -> 0x%05lx, bitplane %swriteable\n",
			 __FUNCTION__, pa,
			 (cpssp->NAME.seq_map_mask & (1 << (pa / 0x10000)))
			  ? "" : "not ");
#endif
		/* we still are bound to obey the map_mask register!
		 * (PC Intern 3.0, p. 405) */
		if (cpssp->NAME.seq_map_mask & (1 << (pa / 0x10000))) {
			video_writeb(cpssp, pa, val);
		}

	} else {
		/* Sequential mode. */
		unsigned char val0;
		unsigned char val1;
		unsigned char val2;
		unsigned char val3;

		/* FIXME VOSSI */
		if (/* pa < 0x00000000 || */ 0x00010000 <= pa) {
#if 1 <= DEBUG
			faum_log(FAUM_LOG_WARNING, "vga", "",
				 "WARNING: %s: pa=0x%08lx\n",
				 __FUNCTION__, pa);
#endif
			return;
		}

		/* Rotate */
		if (((cpssp->NAME.gr_mode & 3) == 0)
		 || ((cpssp->NAME.gr_mode & 3) == 3)) {
			val = (val >> (cpssp->NAME.gr_data_rotate & 7))
			    | (val << (8 - (cpssp->NAME.gr_data_rotate & 7)));
		}

		switch (cpssp->NAME.gr_mode & 3) {
		case 0:
			/*
			 * Write Mode 0
			 */
			/* Set/Reset */
			if (cpssp->NAME.gr_enable_set_reset & 0x01) {
				if (cpssp->NAME.gr_set_reset & 0x01) {
					val0 = 0xff;
				} else {
					val0 = 0x00;
				}
			} else {
				val0 = val;
			}
			if (cpssp->NAME.gr_enable_set_reset & 0x02) {
				if (cpssp->NAME.gr_set_reset & 0x02) {
					val1 = 0xff;
				} else {
					val1 = 0x00;
				}
			} else {
				val1 = val;
			}
			if (cpssp->NAME.gr_enable_set_reset & 0x04) {
				if (cpssp->NAME.gr_set_reset & 0x04) {
					val2 = 0xff;
				} else {
					val2 = 0x00;
				}
			} else {
				val2 = val;
			}
			if (cpssp->NAME.gr_enable_set_reset & 0x08) {
				if (cpssp->NAME.gr_set_reset & 0x08) {
					val3 = 0xff;
				} else {
					val3 = 0x00;
				}
			} else {
				val3 = val;
			}
			break;

		case 1:
			/*
			 * Write Mode 1
			 */
			val0 = cpssp->NAME.gr_latch[0];
			val1 = cpssp->NAME.gr_latch[1];
			val2 = cpssp->NAME.gr_latch[2];
			val3 = cpssp->NAME.gr_latch[3];
			break;

		case 2:
			/*
			 * Write Mode 2
			 */
			if (val & 0x01) {
				val0 = 0xff;
			} else {
				val0 = 0x00;
			}
			if (val & 0x02) {
				val1 = 0xff;
			} else {
				val1 = 0x00;
			}
			if (val & 0x04) {
				val2 = 0xff;
			} else {
				val2 = 0x00;
			}
			if (val & 0x08) {
				val3 = 0xff;
			} else {
				val3 = 0x00;
			}
			break;

		case 3:
			/*
			 * Write Mode 3
			 */
			if (cpssp->NAME.gr_set_reset & 0x01) {
				val0 = 0xff;
			} else {
				val0 = 0x00;
			}
			if (cpssp->NAME.gr_set_reset & 0x02) {
				val1 = 0xff;
			} else {
				val1 = 0x00;
			}
			if (cpssp->NAME.gr_set_reset & 0x04) {
				val2 = 0xff;
			} else {
				val2 = 0x00;
			}
			if (cpssp->NAME.gr_set_reset & 0x08) {
				val3 = 0xff;
			} else {
				val3 = 0x00;
			}

			break;

		default:
			assert(0);
		}

		/* ALU */
		if (((cpssp->NAME.gr_mode & 3) == 0)
		 || ((cpssp->NAME.gr_mode & 3) == 2)) {
			switch ((cpssp->NAME.gr_data_rotate >> 3) & 3) {
			case 0:	/* NOP */
				/* nothing to do... */
				break;
			case 1:	/* AND */
				val0 &= cpssp->NAME.gr_latch[0];
				val1 &= cpssp->NAME.gr_latch[1];
				val2 &= cpssp->NAME.gr_latch[2];
				val3 &= cpssp->NAME.gr_latch[3];
				break;
			case 2:	/* OR */
				val0 |= cpssp->NAME.gr_latch[0];
				val1 |= cpssp->NAME.gr_latch[1];
				val2 |= cpssp->NAME.gr_latch[2];
				val3 |= cpssp->NAME.gr_latch[3];
				break;
			case 3:	/* XOR */
				val0 ^= cpssp->NAME.gr_latch[0];
				val1 ^= cpssp->NAME.gr_latch[1];
				val2 ^= cpssp->NAME.gr_latch[2];
				val3 ^= cpssp->NAME.gr_latch[3];
				break;
			default: /* Cannot happen */
				assert(0);
			}
		}

		/* Bit Mask */
		if ((cpssp->NAME.gr_mode & 3) == 0
		 || (cpssp->NAME.gr_mode & 3) == 2) {
			val0 = (val0 & cpssp->NAME.gr_bit_mask)
			     | (cpssp->NAME.gr_latch[0] & ~cpssp->NAME.gr_bit_mask);
			val1 = (val1 & cpssp->NAME.gr_bit_mask)
			     | (cpssp->NAME.gr_latch[1] & ~cpssp->NAME.gr_bit_mask);
			val2 = (val2 & cpssp->NAME.gr_bit_mask)
			     | (cpssp->NAME.gr_latch[2] & ~cpssp->NAME.gr_bit_mask);
			val3 = (val3 & cpssp->NAME.gr_bit_mask)
			     | (cpssp->NAME.gr_latch[3] & ~cpssp->NAME.gr_bit_mask);
		} else if ((cpssp->NAME.gr_mode & 3) == 3) {
			val = val & cpssp->NAME.gr_bit_mask;
			val0 = (val0 & val)
			     | (cpssp->NAME.gr_latch[0] & ~val);
			val1 = (val1 & val)
			     | (cpssp->NAME.gr_latch[1] & ~val);
			val2 = (val2 & val)
			     | (cpssp->NAME.gr_latch[2] & ~val);
			val3 = (val3 & val)
			     | (cpssp->NAME.gr_latch[3] & ~val);
		}

		/* Plane Select determines to which planes the result
		 * gets written */
		if (cpssp->NAME.seq_map_mask & 0x01) {
			video_writeb(cpssp, pa + 0x00000, val0);
		}
		if (cpssp->NAME.seq_map_mask & 0x02) {
			video_writeb(cpssp, pa + 0x10000, val1);
		}
		if (cpssp->NAME.seq_map_mask & 0x04) {
			video_writeb(cpssp, pa + 0x20000, val2);
		}
		if (cpssp->NAME.seq_map_mask & 0x08) {
			video_writeb(cpssp, pa + 0x30000, val3);
		}
#if 3 <= DEBUG
		faum_log(FAUM_LOG_DEBUG, "vga", "",
				"%s: "
				"bit_mask=0x%02x"
				" map_mask=0x%02x"
				" rot=%1d,%1d"
				" val0=0x%02x"
				" val1=0x%02x"
				" val2=0x%02x"
				" val3=0x%02x"
				" wrmode=%1d"
				"\n",
				__FUNCTION__,
				cpssp->NAME.gr_bit_mask,
				cpssp->NAME.seq_map_mask,
				(cpssp->NAME.gr_data_rotate & 7),
				((cpssp->NAME.gr_data_rotate >> 3) & 3),
				val0,
				val1,
				val2,
				val3,
				(cpssp->NAME.gr_mode & 3)
		);
#endif
	}
}

#ifdef NEED_MEM_LEN
static inline int
NAME_(read)(struct cpssp *cpssp, unsigned long pa, void *_to, int len)
{
	unsigned char *to = (unsigned char *) _to;
	unsigned int mm;
	unsigned long start;
	unsigned long end;

	mm = (cpssp->NAME.gr_misc >> 2) & 3;
	start = NAME_(memory_location)[mm].start;
	end = NAME_(memory_location)[mm].end;

	if (start <= pa && pa <= end) {
		/*
		 * Low VGA memory
		 *
		 * Simulate access.
		 */
		pa -= start;

		while (0 < len) {
			*to = NAME_(readb)(cpssp, pa);
			pa++;
			to++;
			len--;
		}

		return 0;

	} else {
		/* Don't know... */
		return 1;
	}
}

static inline int
NAME_(write)(struct cpssp *cpssp, unsigned long pa, const void *_from, int len)
{
	const unsigned char *from = (const unsigned char *) _from;
	unsigned int mm;
	unsigned long start;
	unsigned long end;

	mm = (cpssp->NAME.gr_misc >> 2) & 3;
	start = NAME_(memory_location)[mm].start;
	end = NAME_(memory_location)[mm].end;

	if (start <= pa && pa <= end) {
		/*
		 * Low VGA memory
		 *
		 * Simulate access.
		 */
		pa -= start;

		while (0 < len) {
			NAME_(writeb)(cpssp, pa, *from);
			pa++;
			from++;
			len--;
		}

		return 0;

	} else {
		/* Don't know... */
		return 1;
	}
}
#endif /* NEED_MEM_LEN */

static inline int
NAME_(mr)(void *_cpssp, uint32_t addr, unsigned int bs, uint16_t *valp)
{
	struct cpssp *cpssp = _cpssp;
	unsigned int mm;
	uint32_t start;
	uint32_t end;

	mm = (cpssp->NAME.gr_misc >> 2) & 3;
	start = NAME_(memory_location)[mm].start;
	end = NAME_(memory_location)[mm].end;

	if (start <= addr && addr < end) {
		addr -= start; /* FIXME */
		*valp = 0;
		if ((bs >> 0) & 1) {
			*valp |= NAME_(readb)(cpssp, addr + 0) << 0;
		}
		if ((bs >> 1) & 1) {
			*valp |= NAME_(readb)(cpssp, addr + 1) << 8;
		}
		return 0;

	} else {
		return 1;
	}
}

static inline int
NAME_(mw)(void *_cpssp, uint32_t addr, unsigned int bs, uint16_t val)
{
	struct cpssp *cpssp = _cpssp;
	unsigned int mm;
	uint32_t start;
	uint32_t end;

	mm = (cpssp->NAME.gr_misc >> 2) & 3;
	start = NAME_(memory_location)[mm].start;
	end = NAME_(memory_location)[mm].end;

	if (start <= addr && addr < end) {
		addr -= start; /* FIXME */
		if ((bs >> 0) & 1) {
			NAME_(writeb)(cpssp, addr + 0, (val >> 0) & 0xff);
		}
		if ((bs >> 1) & 1) {
			NAME_(writeb)(cpssp, addr + 1, (val >> 8) & 0xff);
		}
		return 0;

	} else {
		return 1;
	}
}

static inline int
NAME_(map_r)(
	struct cpssp *cpssp,
	uint32_t addr,
	int (**cfp)(void *, uint32_t, unsigned int, uint16_t *),
	void **csp,
	char **haddrp
)
{
	unsigned int mm;
	uint32_t start;
	uint32_t end;

	mm = (cpssp->NAME.gr_misc >> 2) & 3;
	start = NAME_(memory_location)[mm].start;
	end = NAME_(memory_location)[mm].end;

	if (start <= addr && addr < end) {
		/*
		 * Low VGA memory
		 *
		 * Simulate access.
		 */
		*cfp = NAME_(mr);
		*csp = cpssp;
		*haddrp = NULL;
		return 0;

	} else {
		/* Don't know... */
		return 1;
	}
}

static inline int
NAME_(map_w)(
	struct cpssp *cpssp,
	uint32_t addr,
	int (**cfp)(void *, uint32_t, unsigned int, uint16_t),
	void **csp,
	char **haddrp
)
{
	unsigned int mm;
	uint32_t start;
	uint32_t end;

	mm = (cpssp->NAME.gr_misc >> 2) & 3;
	start = NAME_(memory_location)[mm].start;
	end = NAME_(memory_location)[mm].end;

	if (start <= addr && addr < end) {
		/*
		 * Low VGA memory
		 *
		 * Simulate access.
		 */
		*cfp = NAME_(mw);
		*csp = cpssp;
		*haddrp = NULL;
		return 0;

	} else {
		/* Don't know... */
		return 1;
	}
}

static inline unsigned char
NAME_(_inb)(struct cpssp *cpssp, unsigned short port)
{
	unsigned char value;

#if 1 <= DEBUG
	fprintf(stderr, "%s: port=0x%04x\n", __FUNCTION__, port + 0x03c0);
#endif

	switch (port + 0x03c0) {
	case 0x03c0:
	case 0x03c1:
		/* Attribute Controller Data Read */
		switch (cpssp->NAME.attr_reg_03c0 & 0x1f) {
		case 0x00 ... 0x0f:	/* palette */
			value = cpssp->NAME.attr_palette[cpssp->NAME.attr_reg_03c0 & 0x0f];
			break;
		case 0x10:	/* mode control */
			value = cpssp->NAME.attr_mode_control;
			break;
		case 0x11:	/* overscan color */
			value = cpssp->NAME.attr_overscan_color;
			break;
		case 0x12:	/* color plane enable */
			value = cpssp->NAME.attr_color_plane_enable;
			break;
		case 0x13:	/* horizontal pixel panning */
			value = cpssp->NAME.attr_horizontal_pixel_panning;
			break;
		case 0x14:	/* color select */
			value = cpssp->NAME.attr_color_select;
			break;
		case 0x15 ... 0x1f:
#if 1 <= DEBUG
			fprintf(stderr, "WARNING: %s: port 0x03c0=0x%02x\n",
					__FUNCTION__, cpssp->NAME.attr_reg_03c0);
#endif
			value = 0x00;
			break;
		default:
			assert(0);
			value = 0;	/* to make gcc happy */
			break;
		}
		break;

	case 0x03c2:
		/* Input Status #0 Register */
		/* p. 346 */
		value = cpssp->NAME.input_status_0;
		cpssp->NAME.input_status_0 ^= 0x80;	/* toggle vertical retrace */
		break;

	case 0x03c4:
		/* Sequencer Index */
		value = cpssp->NAME.seq_reg_03c4;
		break;

	case 0x03c5:
		/* Sequencer Data Register */
		switch (cpssp->NAME.seq_reg_03c4) {
		case 0x00:	/* reset */
				/* p. 349 */
			value = cpssp->NAME.seq_reset;
			break;
		case 0x01:	/* clocking mode */
				/* p. 351 */
			value = cpssp->NAME.seq_clocking_mode;
			break;
		case 0x02:	/* map mask */
				/* p. 353 */
			value = cpssp->NAME.seq_map_mask;
			break;
		case 0x03:	/* character map select */
				/* p. 355 */
			value = cpssp->NAME.seq_character_map_select;
			break;
		case 0x04:	/* memory mode */
				/* p. 357 */
			value = cpssp->NAME.seq_memory_mode;
			break;
		default:
#if 1 <= DEBUG
			fprintf(stderr, "WARNING: %s: port 0x03c4=0x%02x\n",
					__FUNCTION__, cpssp->NAME.seq_reg_03c4);
#endif
			value = 0x00;
			break;
		}
		break;

	case 0x03c6:
		/* PEL Mask Register */
		/* p. 421 */
		value = cpssp->NAME.col_pel_mask;
		break;

	case 0x03c7:
		/* DAC State Register */
		/* p. 420 */
		goto unknown;

	case 0x03c9:
		/* PEL Data Read Register */
		/* p. 419 */
		value = video_col_get(cpssp, cpssp->NAME.col_reg_03c7, cpssp->NAME.col_reg_03c7_rgb) >> 2;
		cpssp->NAME.col_reg_03c7_rgb++;
		if (cpssp->NAME.col_reg_03c7_rgb == 3) {
			cpssp->NAME.col_reg_03c7_rgb = 0;
			cpssp->NAME.col_reg_03c7++;
		}
		break;

	case 0x03ca:
		/* Feature Control */
		/* p. 346 */
		value = cpssp->NAME.feature_control;
		break;

	case 0x03cc:
		/* Misc Output Read (VGA only) */
		/* p. 343 */
		value = cpssp->NAME.misc_output;
		break;

	case 0x03ce:
		/* Graphics Controller Index */
		value = cpssp->NAME.gr_reg_03ce & 0x3f;
		break;

	case 0x03cf:
		/* Graphics Controller Data Register */
		switch (cpssp->NAME.gr_reg_03ce) {
		case 0x00:	/* set/reset */
			value = cpssp->NAME.gr_set_reset;
			break;
		case 0x01:	/* enable set/reset */
			value = cpssp->NAME.gr_enable_set_reset;
			break;
		case 0x02:	/* color compare */
			value = cpssp->NAME.gr_color_compare;
			break;
		case 0x03:	/* data rotate */
			value = cpssp->NAME.gr_data_rotate;
			break;
		case 0x04:	/* read map select */
			value = cpssp->NAME.gr_read_map_select;
			break;
		case 0x05:	/* mode */
			value = cpssp->NAME.gr_mode;
			break;
		case 0x06:	/* misc */
			value = cpssp->NAME.gr_misc;
			break;
		case 0x07:	/* color don't care */
			value = cpssp->NAME.gr_color_dont_care;
			break;
		case 0x08:	/* bit mask */
			value = cpssp->NAME.gr_bit_mask;
			break;
		case 0x09 ... 0x3f:
			value = 0x00;
			break;
		default:
			assert(0);	/* Cannot happen. */
		}

		break;

	case 0x03d4:
		/* CRT Controller Index Register */
		/* p. 358 */
		value = cpssp->NAME.crt_reg_03d4;
		break;

	case 0x03d5:
		/* CRT Controller Data Register */
		/* p. 358 */
		switch (cpssp->NAME.crt_reg_03d4) {
		case 0x00:
			value = cpssp->NAME.crt_horizontal_total;
			break;
		case 0x01:
			value = cpssp->NAME.crt_horizontal_display_end;
			break;
		case 0x02:
			value = cpssp->NAME.crt_start_horizontal_blanking;
			break;
		case 0x03:
			value = cpssp->NAME.crt_end_horizontal_blanking;
			break;
		case 0x04:
			value = cpssp->NAME.crt_start_horizontal_retrace;
			break;
		case 0x05:
			value = cpssp->NAME.crt_end_horizontal_retrace;
			break;
		case 0x06:
			value = cpssp->NAME.crt_vertical_total;
			break;
		case 0x07:
			value = cpssp->NAME.crt_overflow;
			break;
		case 0x08:
			value = cpssp->NAME.crt_preset_row_scan;
			break;
		case 0x09:
			value = cpssp->NAME.crt_maximum_scan_line;
			break;
		case 0x0a:
			value = cpssp->NAME.crt_cursor_start;
			break;
		case 0x0b:
			value = cpssp->NAME.crt_cursor_end;
			break;
		case 0x0c:
			value = cpssp->NAME.crt_top_pos >> 8;
			break;
		case 0x0d:
			value = cpssp->NAME.crt_top_pos >> 0;
			break;
		case 0x0e:
			value = cpssp->NAME.crt_cur_pos >> 8;
			break;
		case 0x0f:
			value = cpssp->NAME.crt_cur_pos >> 0;
			break;
		case 0x10:
			value = cpssp->NAME.crt_vertical_retrace_start;
			break;
		case 0x11:
			value = cpssp->NAME.crt_vertical_retrace_end;
			break;
		case 0x12:
			value = cpssp->NAME.crt_vertical_display_end;
			break;
		case 0x13:
			value = cpssp->NAME.crt_offset;
			break;
		case 0x14:
			value = cpssp->NAME.crt_underline_location;
			break;
		case 0x15:
			value = cpssp->NAME.crt_start_vertical_blank;
			break;
		case 0x16:
			value = cpssp->NAME.crt_end_vertical_blank;
			break;
		case 0x17:
			value = cpssp->NAME.crt_mode_control;
			break;
		case 0x18:
			value = cpssp->NAME.crt_line_compare;
			break;
		default:
#if 1 <= DEBUG
			fprintf(stderr, "WARNING: %s: port 0x03d4=0x%02x\n",
					__FUNCTION__, cpssp->NAME.crt_reg_03d4);
#endif
			value = 0x00;
			break;
		}
		break;

	case 0x03da:
		/* Input Status #1 Register */
		/* p. ????? - FIXME VOSSI */
		cpssp->NAME.attr_reg_03c0_state = 0;

		/* p. 347 */
#if 0
		vga_tick();
#else
		/*
		 * toggle between
		 * - display mode
		 * - horizontal retrace time
		 * - vertical retrace time
		 */
		switch (cpssp->NAME.input_status_1 & 0x09) {
		case 0x00: /* display mode */
			cpssp->NAME.input_status_1 ^= 0x01;
			break;
		case 0x01: /* horizontal retrace time */
			cpssp->NAME.input_status_1 ^= 0x08;
			break;
		case 0x09: /* vertical retrace time */
			cpssp->NAME.input_status_1 ^= 0x09;
			break;
		default:
			assert(0);
		}
#endif
		value = cpssp->NAME.input_status_1;
		break;

	default:
	unknown:
#if 1 <= DEBUG
		fprintf(stderr, "WARNING: %s: port=0x%04x\n",
				__FUNCTION__, port + 0x03c0);
#endif
		value = 0x00;
	}

#if 1 <= DEBUG
	fprintf(stderr, "%s: port=0x%04x value=0x%02x\n",
			__FUNCTION__, port + 0x03c0, value);
#endif

	return value;
}

static inline void
NAME_(_outb)(struct cpssp *cpssp, unsigned char value, unsigned short port)
{
#if 1 <= DEBUG
	fprintf(stderr, "%s: port=0x%04x value=0x%02x\n",
			__FUNCTION__, port + 0x03c0, value);
#endif

	switch (port + 0x03c0) {
	case 0x03c0:
	case 0x03c1:
		/* Attribute Controller Index & Data Write */
		switch (cpssp->NAME.attr_reg_03c0_state) {
		case 0:
			cpssp->NAME.attr_reg_03c0 = value;
			cpssp->NAME.attr_reg_03c0_state = 1;
			break;
		case 1:
			switch (cpssp->NAME.attr_reg_03c0 & 0x1f) {
			case 0x00 ... 0x0f: /* palette - p. 406 */
				cpssp->NAME.attr_palette[cpssp->NAME.attr_reg_03c0 & 0x0f]
					= value & 0x3f;
				break;
			case 0x10:	/* mode control - p. 408 */
				cpssp->NAME.attr_mode_control = value & 0xef;
				break;
			case 0x11:	/* overscan color - p. 411 */
				cpssp->NAME.attr_overscan_color = value & 0x3f;
				break;
			case 0x12:	/* color plane enable - p. 413 */
				cpssp->NAME.attr_color_plane_enable = value & 0x3f;
				break;
			case 0x13:	/* horizontal pixel panning - p. 415 */
				cpssp->NAME.attr_horizontal_pixel_panning = value & 0x0f;
				break;
			case 0x14:	/* color select - p. 417 */
				cpssp->NAME.attr_color_select = value & 0x0f;
				break;
			case 0x15 ... 0x1f:
#if 1 <= DEBUG
				fprintf(stderr, "WARNING: %s: port 0x03c0=0x%02x\n",
						__FUNCTION__, cpssp->NAME.attr_reg_03c0);
#endif
				break;
			default:
				assert(0);
				break;
			}
			cpssp->NAME.attr_reg_03c0_state = 0;
			break;
		default:
			assert(0);
		}
		break;

	case 0x03c2:
		/* Misc Output Write Register */
		/* p. 343 */
		cpssp->NAME.misc_output = value;
		break;

	case 0x03c3:
		goto unknown;

	case 0x03c4:
		/* Sequencer Index */
		cpssp->NAME.seq_reg_03c4 = value;
		break;

	case 0x03c5:
		/* Sequencer Data */
		switch (cpssp->NAME.seq_reg_03c4) {
		case 0x00:	/* reset - p. 349 */
			cpssp->NAME.seq_reset = value & 0x03;
			break;
		case 0x01:	/* clocking mode - p. 351 */
			cpssp->NAME.seq_clocking_mode = value & 0x3f;
			break;
		case 0x02:	/* map mask - p. 349 */
			cpssp->NAME.seq_map_mask = value & 0x0f;
			break;
		case 0x03:	/* character map select - p. 355 */
			cpssp->NAME.seq_character_map_select = value & 0x3f;
			break;
		case 0x04:	/* memory mode - p. 357 */
			cpssp->NAME.seq_memory_mode = value & 0x0f;
			break;
		default:
#if 1 <= DEBUG
			fprintf(stderr, "WARNING: %s: port 0x03c4=0x%02x\n",
					__FUNCTION__, cpssp->NAME.seq_reg_03c4);
#endif
			break;
		}
		break;

	case 0x03c6:
		/* PEL Mask Register */
		/* p. 421 */
		cpssp->NAME.col_pel_mask = value;
		break;

	case 0x03c7:
		/* PEL Read Index Register */
		/* p. 419 */
		cpssp->NAME.col_reg_03c7 = value;
		cpssp->NAME.col_reg_03c7_rgb = 0;
		break;

	case 0x03c8:
		/* PEL Write Index Register */
		/* p. 418 */
		cpssp->NAME.col_reg_03c8 = value;
		cpssp->NAME.col_reg_03c8_rgb = 0;
		break;

	case 0x03c9:
		/* PEL Data Write Register */
		/* p. 419 */
		video_col_set(cpssp, cpssp->NAME.col_reg_03c8, cpssp->NAME.col_reg_03c8_rgb,
			value << 2);
		cpssp->NAME.col_reg_03c8_rgb++;
		if (cpssp->NAME.col_reg_03c8_rgb == 3) {
			cpssp->NAME.col_reg_03c8_rgb = 0;
			cpssp->NAME.col_reg_03c8++;
		}
		break;

	case 0x03ca:
		/* EGA-specific */
		/* Graphics enable processor 1 */
		goto unknown;

	case 0x03cb:
		goto unknown;

	case 0x03cd:
		goto unknown;

	case 0x03ce:
		/* Graphics Controller Index */
		cpssp->NAME.gr_reg_03ce = value & 0x3f;
		break;

	case 0x03cf:
		/* Graphics Controller Data */
		switch (cpssp->NAME.gr_reg_03ce) {
		case 0x00:	/* set/reset - p. 388 */
			cpssp->NAME.gr_set_reset = value & 0x0f;
			break;
		case 0x01:	/* enable set/reset - p. 390 */
			cpssp->NAME.gr_enable_set_reset = value & 0x0f;
			break;
		case 0x02:	/* color compare - p. 390 */
			cpssp->NAME.gr_color_compare = value & 0x0f;
			break;
		case 0x03:	/* data rotate - p. 392 */
			cpssp->NAME.gr_data_rotate = value & 0x3f;
			break;
		case 0x04:	/* read map select - p. 395 */
			cpssp->NAME.gr_read_map_select = value & 0x03;
			break;
		case 0x05:	/* mode - p. 396 */
			cpssp->NAME.gr_mode = value & 0x7f;
			break;
		case 0x06:	/* misc - p. 401 */
			cpssp->NAME.gr_misc = value & 0x0f;
			/* Invalidate regions? FIXME */
			break;
		case 0x07:	/* color don't care - p. 402 */
			cpssp->NAME.gr_color_dont_care = value & 0x0f;
			break;
		case 0x08:	/* bit mask - p. 403 */
			cpssp->NAME.gr_bit_mask = value & 0xff;
			break;
		case 0x09 ... 0x3f:
			/* Do nothing... */
			break;
		default:
			assert(0);	/* Cannot happen. */
		}
		break;

	case 0x03d4:
		/* CRT Controller Index */
		cpssp->NAME.crt_reg_03d4 = value;
		break;

	case 0x03d5:
		/* CRT Controller Data */
		switch (cpssp->NAME.crt_reg_03d4) {
		case 0x00:	/* horizontal total - p. 359 */
			cpssp->NAME.crt_horizontal_total = value & 0xff;
			break;
		case 0x01:	/* horizontal display end - p. 360 */
			cpssp->NAME.crt_horizontal_display_end = value & 0xff;
			break;
		case 0x02:	/* start_horizontal blanking - p. 361 */
			cpssp->NAME.crt_start_horizontal_blanking = value & 0xff;
			break;
		case 0x03:	/* end horizontal blanking - p. 362 */
			cpssp->NAME.crt_end_horizontal_blanking = value & 0xff;
			break;
		case 0x04:	/* start horizontal retrace - p. 363 */
			cpssp->NAME.crt_start_horizontal_retrace = value & 0xff;
			break;
		case 0x05:	/* end horizontal retrace - p. 364 */
			cpssp->NAME.crt_end_horizontal_retrace = value & 0xff;
			break;
		case 0x06:	/* vertical total - p. 365 */
			cpssp->NAME.crt_vertical_total = value & 0xff;
			break;
		case 0x07:	/* overflow - p. 365 */
			cpssp->NAME.crt_overflow = value & 0xff;
			break;
		case 0x08:	/* preset row scan - p. 367 */
			cpssp->NAME.crt_preset_row_scan = value & 0x7f;
			break;
		case 0x09:	/* maximum scan line - p. 369 */
			cpssp->NAME.crt_maximum_scan_line = value & 0xff;
			break;
		case 0x0a:	/* cursor start - p. 370 */
			cpssp->NAME.crt_cursor_start = value & 0x3f;
			break;
		case 0x0b:	/* cursor end - p. 371 */
			cpssp->NAME.crt_cursor_end = value & 0x7f;
			break;
		case 0x0c:	/* top position (high byte) - p. 371 */
			cpssp->NAME.crt_top_pos &= 0x00ff;
			cpssp->NAME.crt_top_pos |= ((unsigned short) value << 8);
			break;
		case 0x0d:	/* top position (low byte) - p. 373 */
			cpssp->NAME.crt_top_pos &= 0xff00;
			cpssp->NAME.crt_top_pos |= ((unsigned short) value << 0);
			break;
		case 0x0e:	/* cursor position (high byte) - p. 373 */
			cpssp->NAME.crt_cur_pos &= 0x00ff;
			cpssp->NAME.crt_cur_pos |= ((unsigned short) value << 8);
			break;
		case 0x0f:	/* cursor position (low byte) - p. 374 */
			cpssp->NAME.crt_cur_pos &= 0xff00;
			cpssp->NAME.crt_cur_pos |= ((unsigned short) value << 0);
			break;
		case 0x10:	/* vertical retrace start - p. 374 */
			cpssp->NAME.crt_vertical_retrace_start = value & 0xff;
			break;
		case 0x11:	/* vertical retrace end - p. 375 */
			cpssp->NAME.crt_vertical_retrace_end = value & 0xff;
			break;
		case 0x12:	/* vertical display end - p. 378 */
			cpssp->NAME.crt_vertical_display_end = value & 0xff;
			break;
		case 0x13:	/* offset - p. 379 */
			cpssp->NAME.crt_offset = value & 0xff;
			break;
		case 0x14:	/* underline location - p. 379 */
			cpssp->NAME.crt_underline_location = value & 0x7f;
			break;
		case 0x15:	/* start vertical blanking - p. 380 */
			cpssp->NAME.crt_start_vertical_blank = value & 0xff;
			break;
		case 0x16:	/* end vertical blanking - p. 381 */
			cpssp->NAME.crt_end_vertical_blank = value & 0x7f;
			break;
		case 0x17:	/* mode control - p. 382 */
			cpssp->NAME.crt_mode_control = value & 0xff;
			break;
		case 0x18:	/* line compare - p. 385 */
			cpssp->NAME.crt_line_compare = value & 0xff;
			break;
		default:
#if 1 <= DEBUG
			fprintf(stderr, "WARNING: %s: port 0x03d4=0x%02x\n",
					__FUNCTION__, cpssp->NAME.crt_reg_03d4);
#endif
			break;
		}
		break;

	case 0x03d6:
	case 0x03d7:
	case 0x03d8:
	case 0x03d9:
		goto unknown;

	case 0x03da:
		/* Feature Control */
		/* p. 346 */
		cpssp->NAME.feature_control = value & 0x0b;
		break;

	case 0x03db:
	case 0x03dc:
	case 0x03dd:
	case 0x03de:
	case 0x03df:
		goto unknown;

	default:
unknown:	;
#if 1 <= DEBUG
		fprintf(stderr, "WARNING: %s: value=0x%02x port=0x%04x\n",
				__FUNCTION__, value, port + 0x03c0);
#endif
		break;
	}
}

static inline int
NAME_(inb)(struct cpssp *cpssp, unsigned char *valuep, unsigned short port)
{

	if (ARCH_VGA_PORT <= port
	 && port < ARCH_VGA_PORT + 0x20) {
		*valuep = NAME_(_inb)(cpssp, port - ARCH_VGA_PORT);
		return 0;

	} else {
		return 1;
	}
}

static inline int
NAME_(outb)(struct cpssp *cpssp, unsigned char value, unsigned short port)
{
	if (ARCH_VGA_PORT <= port
	 && port < ARCH_VGA_PORT + 0x20) {
		 NAME_(_outb)(cpssp, value, port - ARCH_VGA_PORT);
		 return 0;

	 } else {
		 return 1;
	 }
}

static inline int
NAME_(outw)(struct cpssp *cpssp, unsigned short value, unsigned short port)
{
	int ret0;
	int ret1;

	ret0 = NAME_(outb)(cpssp, (value >> 0) & 0xff, port++);
	ret1 = NAME_(outb)(cpssp, (value >> 8) & 0xff, port++);

	return ret0 | ret1;
}

static inline void
NAME_(gen_text_line)(
	struct cpssp *cpssp,
	struct sig_video *video,
	unsigned int j,
	unsigned char ch,
	unsigned char attr,
	unsigned char start,
	unsigned char end
)
{
	unsigned char mapA;
	unsigned char mapB;
	unsigned char bg_col;
	unsigned char bg_r, bg_g, bg_b;
	unsigned char fg_col;
	unsigned char fg_r, fg_g, fg_b;
	unsigned char map;
	unsigned short bits;
	int i;


	mapA = (((cpssp->NAME.seq_character_map_select >> 5) & 1) << 0)
	     | (((cpssp->NAME.seq_character_map_select >> 2) & 3) << 1);
	mapB = (((cpssp->NAME.seq_character_map_select >> 4) & 1) << 0)
	     | (((cpssp->NAME.seq_character_map_select >> 0) & 3) << 1);

	bg_col = (attr >> 4) & 0x0f;
	fg_col = (attr >> 0) & 0x0f;

	if (mapA != mapB && (cpssp->NAME.seq_memory_mode >> 1) & 1) {
		map = ((attr >> 3) & 0x01) ? mapA : mapB;
		fg_col &= 0x07;
	} else {
		map = mapA;
	}

	if ((cpssp->NAME.attr_mode_control >> 7) & 1) {
		bg_col = cpssp->NAME.attr_palette[bg_col] & 0x0f;
		bg_col |= (cpssp->NAME.attr_color_select & 0x0f) << 4;
		fg_col = cpssp->NAME.attr_palette[fg_col] & 0x0f;
		fg_col |= (cpssp->NAME.attr_color_select & 0x0f) << 4;
	} else {
		bg_col = cpssp->NAME.attr_palette[bg_col] & 0x3f;
		bg_col |= (cpssp->NAME.attr_color_select & 0x0c) << 4;
		fg_col = cpssp->NAME.attr_palette[fg_col] & 0x3f;
		fg_col |= (cpssp->NAME.attr_color_select & 0x0c) << 4;
	}

	bg_r = video_col_get(cpssp, bg_col, 0);
	bg_g = video_col_get(cpssp, bg_col, 1);
	bg_b = video_col_get(cpssp, bg_col, 2);
	fg_r = video_col_get(cpssp, fg_col, 0);
	fg_g = video_col_get(cpssp, fg_col, 1);
	fg_b = video_col_get(cpssp, fg_col, 2);

	if (start != 0xff && start <= j && j <= end) {
		/* cursor */
		bits = 0xff;

	} else {
		/* font */
		/* read font 8x16 data */
		bits = video_readb(cpssp, 0x20000 | (map << 13) | (ch << 5) | j); 
	}

	for (i = 0; i < 8; i++) {
		if (bits & 0x80) {
			sig_video_out(video, cpssp, fg_r, fg_g, fg_b);
		} else {
			sig_video_out(video, cpssp, bg_r, bg_g, bg_b);
		}

		bits <<= 1;
	}

#if 0
	/* enabling this will break some experiments... */
	if ((cpssp->NAME.seq_clocking_mode & 0x01) == 0) {
		/* Generate 9th dot. */
		if (0xc0 <= ch && ch <= 0xdf && (bits & 0x0100) && (cpssp->NAME.attr_mode_control >> 2) & 1) {
			sig_video_out(video, cpssp, fg_r, fg_g, fg_b);
		} else {
			sig_video_out(video, cpssp, bg_r, bg_g, bg_b);
		}
	}
#endif
}

static inline void
NAME_(gen_text)(struct cpssp *cpssp, struct sig_video *video)
{
	uint32_t tv;
	int inv;
	int blink;
	unsigned int hde;
	unsigned int vde;
	unsigned int lc;
	unsigned int msl;
	unsigned int x;
	unsigned int y1;
	unsigned short offset1;
	unsigned int scan_count;

	tv = (uint32_t) time_virt();

	/* the state of blinking */
	inv = (tv >> 31) & 1;
	/* blinking turned on? */
	blink = (cpssp->NAME.attr_mode_control >> 3) & 1;

	/*
	 * Get number of scan lines.
	 */
	vde = cpssp->NAME.crt_vertical_display_end;
	vde |= ((cpssp->NAME.crt_overflow >> 1) & 1) << 8;
	vde |= ((cpssp->NAME.crt_overflow >> 6) & 1) << 9;
	vde++;

	/*
	 * Get number of characters per row.
	 */
	hde = cpssp->NAME.crt_horizontal_display_end + 1;

	/*
	 * Get the line where the screen is split
	 */
	lc = cpssp->NAME.crt_line_compare;
	lc |= ((cpssp->NAME.crt_overflow >> 4) & 1) << 8;
	lc |= ((cpssp->NAME.crt_maximum_scan_line >> 6) & 1) << 9;

	/*
	 * Get maximum scan line.
	 */
	msl = (cpssp->NAME.crt_maximum_scan_line & 0x1f) + 1;

	y1 = cpssp->NAME.scanline % msl;

	scan_count = 1 + vde / REFRESH_CYCLES;
	while (scan_count && cpssp->NAME.scanline < vde) {
		if (cpssp->NAME.scanline == lc) { 
			y1 = 0;
			cpssp->NAME.offset = 0x00000;
		} 

		offset1 = cpssp->NAME.offset;
		for (x = 0; x < hde; x++) {
			unsigned char start;
			unsigned char end;
			unsigned char ch;
			unsigned char attr;

			ch = video_readb(cpssp, 0x00000 + offset1);
			attr = video_readb(cpssp, 0x10000 + offset1);

			/* blinking */
			if ((attr & 0x80) && blink) {
				if (inv) {
					attr &= 0x70;
					attr |= attr >> 4;
				} else {
					attr &= 0x7f;
				}
			}

			/* cursor */
			if (cpssp->NAME.crt_cur_pos == offset1
			 && (! ((cpssp->NAME.crt_cursor_start >> 5) & 1))
			 && inv) {
				/* Should be 0x1f instead of 0x0f - FIXME VOSSI */
				start = cpssp->NAME.crt_cursor_start & 0x0f;
				end = cpssp->NAME.crt_cursor_end & 0x0f;
			} else {
				start = 0xff;
				end = 0xff;
			}

			NAME_(gen_text_line)(cpssp, video, y1, ch, attr, start, end);

			offset1++;
			offset1 &= 0x3fff;	/* Correct? FIXME VOSSI */
		}

		cpssp->NAME.scanline++;
		scan_count--;
		sig_video_hor_retrace(video, cpssp);

		y1++;
		if (y1 == msl) {
			y1 = 0;
			cpssp->NAME.offset = offset1;
		}
	}

	if (cpssp->NAME.scanline >= vde) {
		cpssp->NAME.scanline = 0;
		cpssp->NAME.offset = cpssp->NAME.crt_top_pos;
		sig_video_vert_retrace(video, cpssp);
	}
}

static inline void
NAME_(gen_planar4_line)(
	struct cpssp *cpssp,
	struct sig_video *video,
	unsigned short offset
)
{
	uint32_t bits;
	int j;

	bits = (video_readb(cpssp, 3 * 65536 + offset) << 24)
	     | (video_readb(cpssp, 2 * 65536 + offset) << 16)
	     | (video_readb(cpssp, 1 * 65536 + offset) <<  8)
	     | (video_readb(cpssp, 0 * 65536 + offset) <<  0);

	for (j = 0; j < TILE_WIDTH; j++) {
		unsigned char col;

		col = ((bits >> (24 + 4)) & 0x08)
		    | ((bits >> (16 + 5)) & 0x04)
		    | ((bits >> ( 8 + 6)) & 0x02)
		    | ((bits >> ( 0 + 7)) & 0x01);
		col &= cpssp->NAME.attr_color_plane_enable;

		if ((cpssp->NAME.attr_mode_control >> 7) & 1) {
			col = cpssp->NAME.attr_palette[col] & 0x0f;
			col |= (cpssp->NAME.attr_color_select & 0x0f) << 4;
		} else {
			col = cpssp->NAME.attr_palette[col] & 0x3f;
			col |= (cpssp->NAME.attr_color_select & 0x0c) << 4;
		}

		sig_video_out(video, cpssp,
				video_col_get(cpssp, col, 0),
				video_col_get(cpssp, col, 1),
				video_col_get(cpssp, col, 2));

		bits <<= 1;
	}
}

static inline void
NAME_(gen_planar4)(struct cpssp *cpssp, struct sig_video *video)
{
	unsigned int hde;
	unsigned int vde;
	unsigned int x;
	unsigned int scan_count;

	/*
	 * Get number of scan lines.
	 */
	vde = cpssp->NAME.crt_vertical_display_end;
	vde |= ((cpssp->NAME.crt_overflow >> 1) & 1) << 8;
	vde |= ((cpssp->NAME.crt_overflow >> 6) & 1) << 9;
	vde++;

	/*
	 * Get number of characters per row.
	 */
	hde = cpssp->NAME.crt_horizontal_display_end + 1;

	scan_count = 1 + vde / REFRESH_CYCLES;
	while (scan_count && cpssp->NAME.scanline < vde) {
		for (x = 0; x < hde; x++) {
			NAME_(gen_planar4_line)(cpssp, video, cpssp->NAME.offset);
			cpssp->NAME.offset++;
		}

		cpssp->NAME.scanline++;
		scan_count--;
		sig_video_hor_retrace(video, cpssp);
	}

	if (cpssp->NAME.scanline >= vde) {
		cpssp->NAME.scanline = 0;
		cpssp->NAME.offset = 0;
		sig_video_vert_retrace(video, cpssp);
	}
}

static inline void
NAME_(gen_256colmode)(struct cpssp *cpssp, struct sig_video *video)
{
	unsigned int hde;
	unsigned int vde;
	unsigned int x;
	unsigned int scan_count;

	/*
	 * Get number of scan lines.
	 */
	vde = cpssp->NAME.crt_vertical_display_end;
	vde |= ((cpssp->NAME.crt_overflow >> 1) & 1) << 8;
	vde |= ((cpssp->NAME.crt_overflow >> 6) & 1) << 9;
	vde++; /* The register contains (lines - 1) so we have to add 1 */

	/* Get number of characters per row. */
	if (((cpssp->NAME.misc_output >> 2) & 3) == 1) {
		hde = 360;
	} else {
		hde = 320;
	}

	scan_count = 1 + vde / REFRESH_CYCLES;
	while (scan_count && cpssp->NAME.scanline < vde) {
		for (x = 0; x < hde; x++) {
			unsigned char col;

			col = video_readb(cpssp, (cpssp->NAME.offset % 4) * 0x10000 + (cpssp->NAME.offset / 4));
			sig_video_out(video, cpssp,
				video_col_get(cpssp, col, 0),
				video_col_get(cpssp, col, 1),
				video_col_get(cpssp, col, 2));
			sig_video_out(video, cpssp,
				video_col_get(cpssp, col, 0),
				video_col_get(cpssp, col, 1),
				video_col_get(cpssp, col, 2));
			cpssp->NAME.offset++;
		}
		if ((cpssp->NAME.crt_maximum_scan_line & 0x80) && (cpssp->NAME.scanline & 1)) {
			/* VGA actually gerates 320x200 as 320x400 by doubling
			 * each line. */
			cpssp->NAME.offset -= hde;
		}
		if (0x40000 <= cpssp->NAME.offset) {
			cpssp->NAME.offset -= 0x40000;
		}

		cpssp->NAME.scanline++;
		scan_count--;
		sig_video_hor_retrace(video, cpssp);
	}

	if (cpssp->NAME.scanline >= vde) {
		cpssp->NAME.scanline = 0;
		cpssp->NAME.offset = cpssp->NAME.crt_top_pos * 4;
		sig_video_vert_retrace(video, cpssp);
	}
}

static inline void
NAME_(gen)(struct cpssp *cpssp, struct sig_video *video)
{
	int mode;
	int depth;

	/*
	 * Determine video mode by register contents.
	 */
	if (cpssp->NAME.gr_misc & 1) {
		/* Graphic mode. */
		if (cpssp->NAME.gr_mode & 0x40) {
			mode = 0x13;
		} else {
			mode = 0x12;
		}
	} else {
		/* Text mode. */
		mode = 0x03;
	}

	switch (mode) {
	/* text mode 80x25 */
	case 0x03:
		depth = 0;
		break;

	/* graphic mode 640x480x4 */
	case 0x12:
		depth = 4;
		break;
	/* graphic mode 320x200x8 */
	case 0x13:
		depth = 8;
		break;

	default:
		assert(0);	/* FIXME VOSSI */
	}

	switch (depth) {
	case 0:
		NAME_(gen_text)(cpssp, video);
		break;
	case 4:
		NAME_(gen_planar4)(cpssp, video);
		break;
	case 8:
		NAME_(gen_256colmode)(cpssp, video);
		break;
	default:
		assert(0);	/* FIXME VOSSI */
	}
}

static inline void
NAME_(init)(struct cpssp *cpssp)
{
}

#undef TILE_WIDTH
#undef DEBUG

#endif /* BEHAVIOUR */
