/*
 * Copyright (C) 2003-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.
 *
 * Implementation of a Cirrus Logic CL-GD5446
 *
 * Inspiration for the bitblitter and some other details
 * came from qemu's cirrus_vga. Thanks for that!
 *
 * LIMITATIONS:
 * - Doesn't handle monochrome register shift 3dx -> 3bx.
 * - Nothing of the video support stuff is implemented.
 * - No DDC2B support.
 *
 * STILL NOT TESTED:
 * - some bitblts
 * - system-to-screen bitblts
 * - byte-swapping apertures
 */

/*
 * Debugging.
 * If CIRRUS_DEBUG is:
 *   0: important warnings only
 *   1: all warnings
 *   2: debugging
 *   3: overkill ;-)
 */

#define CIRRUS_DEBUG 0
/*
#define CIRRUS_DEBUG_BITBLT
#define CIRRUS_DEBUG_GEN
#define CIRRUS_DEBUG_CONFIGSPACE
*/

#include <assert.h>
#include <stdio.h>
#include <string.h>

#include "fixme.h"
#include "glue.h"

#include "chip_cirrus_gd5446.h"

#define COMP		chip_cirrus_gd5446
#define COMP_(x)	chip_cirrus_gd5446_ ## x
#define SCOMP		"chip_cirrus_gd5446"

/*
 * With a given VSYNC rate, a total refresh rate of
 *
 *         VSYNC * (REFRESH_CYCLES + 1) Hz
 *
 * is resulting.
 */
#define VSYNC 5
#define REFRESH_CYCLES 1024

/*
 * vga_core defines
 */
#define PA_VIDEO_MEMORY_LOW   0x000a0000
#define SZ_VIDEO_MEMORY_LOW   (128*1024)

/*
 * cirrus defines
 */
#define CIRRUS_IOPORT		0x3c0    /* standard for all vgas is 3c0-3df */

#define CIRRUS_MAX_VRAM_SIZE	0x400000
#define CIRRUS_MAX_VRAM_MASK	(0x400000 - 1)

#define DISPLAY_MEMORY_SIZE	0x2000000	/* 32 MB */

#define REGISTER_MEMORY_SIZE	0x01000	/* 4 KB */

#define BIOS_MEMORY_SIZE	0x8000	/* 32 KB */

#define BANK_MEMORY_ADDRESS	0xa0000	/* 0xa0000 - 0xaffff */
#define BANK_MEMORY_SIZE	0x10000

#define LEGACY_MMIO_ADDRESS	0xb8000	/* 0xb8000 - 0xbffff */
#define LEGACY_MMIO_SIZE	0x08000

/* Defines for bitblt mode register */
#define BITBLT_COLOR_EXPAND			0x80
#define BITBLT_8X8_PATTERN_COPY			0x40
#define BITBLT_COLOR_EXPAND_WIDTH		0x30
#define BITBLT_TRANSPARENCY			0x08
#define BITBLT_SOURCE_SYSTEM_MEMORY		0x04
#define BITBLT_REVERSE				0x01

/* Defines for bitblt extended mode register */
#define BITBLT_SYNCHRONOUS_DISPLAY_SWITCHING	0x10
#define BITBLT_BACKGROUND_ONLY_CLIPPING		0x08
#define BITBLT_SOLID_COLOR_FILL			0x04
#define BITBLT_INVERT_COLOR_EXPAND		0x02
#define BITBLT_32BIT_GRANULARITY		0x01

/* Defines for bitblt start/status register */
#define BITBLT_AUTOSTART			0x80
#define BITBLT_SYSTEM_SOURCE_LOCATION		0x40
#define BITBLT_BUFFERED_REGISTER_STATUS		0x10
#define BITBLT_RESET				0x04
#define BITBLT_START				0x02
#define BITBLT_STATUS				0x01

/* Defines for bitblt destination left-side clipping register */
#define BITBLT_SYSTEM_TO_SCREEN_DWORD_POINTER	0x60

/* Buffer for one line of source data (system-to-screen operation) */
#define BITBLT_LINE_BUFFER_SIZE			(2048 * 4)
#define BITBLT_LINE_BUFFER_START		CIRRUS_MAX_VRAM_SIZE

struct cpssp {
	struct process process;

	unsigned int state_power;
	struct sig_pci_bus *port_pci_bus;
	struct sig_video *port_video;
	struct sig_i2c_bus *port_ddc;
	struct sig_cs16 *port_mem_cs[8];
	struct sig_cs *port_rom_cs;

	enum {
		VGA,
		CIRRUS
	} mode;

	/* Config Space */
	uint8_t io_space;
	uint8_t memory_space;
	uint8_t dac_shadowing;
	uint32_t memory_address;
	uint32_t register_address;
	uint8_t rom_enabled;
	uint32_t rom_address;
	uint8_t interrupt_line;

	unsigned int x;
	unsigned int y;
	uint32_t offset_line;
	uint32_t offset_cur;

	uint8_t colregs[259][3];       /* 256 + 3 dac extended colors */

	/* BitBLT Registers */
	/* (index 0x3ce, value 0x3cf) */
	uint8_t blt_color_exp_fg_bg[6];             /* 0x10-0x15 5.1  */
	uint8_t blt_width[2];                       /* 0x20 0x21 5.2  */
	uint8_t blt_height[2];                      /* 0x22 0x23 5.3  */
	uint8_t blt_dest_pitch[2];                  /* 0x24 0x25 5.4  */
	uint8_t blt_source_pitch[2];                /* 0x26 0x27 5.5  */
	uint8_t blt_dest_start[3];                  /* 0x28-0x2a 5.6  */
	uint8_t blt_source_start[3];                /* 0x2c-0x2e 5.7  */
	uint8_t blt_dest_left_side_clipping;        /* 0x2f      5.8  */
	uint8_t blt_mode;                           /* 0x30      5.9  */
	uint8_t blt_start_status;                   /* 0x31      5.10 */
	uint8_t blt_rop;                            /* 0x32      5.11 */
	uint8_t blt_mode_extensions;                /* 0x33      5.12 */
	uint8_t blt_transp_blt_key_color[2];        /* 0x34 0x35 5.13 */

	/* Miscellaneous Extension Registers */
	/* (index 0x3c4, value 0x3c5) */
	uint8_t ext_key;                            /* 0x06      8.1  */
	uint8_t ext_ext_sequencer_mode;             /* 0x07      8.2  */
	uint8_t ext_ddc2b_eeprom_control;           /* 0x08      8.3  */
	uint8_t ext_scratch_pad_01[2];              /* 0x09 0x0a 8.4  */
	uint8_t ext_vclk_numerator[4];              /* 0x0b-0x0e 8.5  */
	uint8_t ext_dram_control;                   /* 0x0f      8.6  */
	uint8_t dram_bank_switch_control;           /* 0x0f[7]   8.6  */
	uint8_t dram_data_bus_width;                /* 0x0f[4:3] 8.6  */
	uint8_t ext_graph_curs_x_pos;               /* 0x10      8.7  */
	uint8_t ext_graph_curs_y_pos;               /* 0x11      8.8  */
	uint8_t ext_graph_curs_attr;                /* 0x12      8.9  */
	uint8_t ext_graph_curs_pattern_addr_offset; /* 0x13      8.10 */
	uint8_t ext_scratch_pad_23[2];              /* 0x14 0x15 8.11 */
	uint8_t ext_display_fifo_threshold_control; /* 0x16      8.12 */
	uint8_t ext_config_rb_ext_control;          /* 0x17      8.13 */
	uint8_t dram_bank_size;                     /* 0x17[7]   8.13 */
	uint8_t dram_bank_swap;                     /* 0x17[1]   8.13 */
	uint8_t ext_sig_gen_control;                /* 0x18      8.14 */
	uint8_t ext_sig_gen_result_lb;              /* 0x19      8.15 */
	uint8_t ext_sig_gen_result_hb;              /* 0x1a      8.16 */
	uint8_t ext_vclk_denominator[4];            /* 0x1b-0x1e 8.17 */
	uint8_t ext_mclk_select;                    /* 0x1f      8.18 */
	/* (index 0x3ce, value 0x3cf) */
	uint8_t ext_offset_reg_0;                   /* 0x09      8.19 */
	uint8_t ext_offset_reg_1;                   /* 0x0a      8.20 */
	uint8_t ext_graph_contr_mode_ext;           /* 0x0b      8.21 */
	uint8_t ext_color_chroma_key_comp;          /* 0x0c      8.22 */
	uint8_t ext_color_mask_chroma_key;          /* 0x0d      8.23 */
	uint8_t ext_power_management;               /* 0x0e      8.24 */
	/* readonline, generated from scanline */         /* 0x16      8.25 */
	uint8_t ext_act_disp_line_rb_1;             /* 0x17      8.26 */
	uint8_t ext_ext_dram_controls;              /* 0x18      8.27 */
	uint8_t ext_gpio_port_config;               /* 0x19      8.28 */
	uint8_t ext_scratch_pad_45[2];              /* 0x1a 0x1b 8.29 */
	/* (index 03d4, value 0x3d5) */
	uint8_t ext_interlace_end;                  /* 0x19      8.30 */
	uint8_t ext_misc_control;                   /* 0x1a      8.31 */
	uint8_t ext_ext_disp_controls;              /* 0x1b      8.32 */
	uint8_t ext_sync_adjust_genlock;            /* 0x1c      8.33 */
	uint8_t ext_overlay_ext_control;            /* 0x1d      8.34 */
	uint8_t ext_part_status;                    /* 0x25      8.35 */
	uint8_t ext_id;                             /* 0x27      8.36 */
	/* (register 0x3c6) (same as 4.14 pixel mask) */
	uint8_t ext_hidden_dac;                     /*           8.37 */
	uint8_t ext_hidden_dac_counter;

	/* current position of hw cursor */
	unsigned int hw_cursor_x;
	unsigned int hw_cursor_y;

	/* shadow versions of SR0 and SR1 for BitBLT color expansion */
	uint8_t shadow_gr0;
	uint8_t shadow_gr1;

	/* state of the bitblt engine */
	struct bitblt {
		uint32_t fg_color;
		uint32_t bg_color;
		unsigned int width;
		unsigned int height;
		int dest_pitch; /* pitches may be negative */
		int src_pitch;
		uint32_t destination_pointer;
		uint32_t source_pointer;
		uint8_t mode;
		uint8_t color_expand_width;
		uint8_t mode_extensions;
		uint8_t dest_left_side_clipping;
		uint8_t rop;
		uint8_t key_color[2];

		void (*function)(struct cpssp *cpssp);

		/* we cache scanlines for system-to-screen bitblts */
		uint32_t line_pointer;
		uint32_t line_pointer_end;
		uint32_t src_counter;

		/* line buffer for bitblt engine */
		uint8_t line_buffer[BITBLT_LINE_BUFFER_SIZE];
	} bitblt;

	/* state of the i2c/ddc bus */
	struct {
		/* value of the clock signal */
		bool clk;
		/* value of the data signal */
		bool data;
	} i2c_ddc_bus;

#define STATE
#define NAME		vga
#define NAME_(x)	vga_ ## x
#define SNAME		"vga"
#define NEED_MEM_BS
#include "arch_vga.c"
#undef NEED_MEM_BS
#undef SNAME
#undef NAME_
#undef NAME
#undef STATE
};

static void
COMP_(flush)(struct cpssp *cpssp)
{
	sig_pci_bus_unmap(cpssp->port_pci_bus, cpssp,
			PA_VIDEO_MEMORY_LOW, SZ_VIDEO_MEMORY_LOW);
	sig_pci_bus_unmap(cpssp->port_pci_bus, cpssp,
			cpssp->memory_address, DISPLAY_MEMORY_SIZE);
	sig_pci_bus_unmap(cpssp->port_pci_bus, cpssp,
			cpssp->register_address, REGISTER_MEMORY_SIZE);
}

/*
 * Memory and color register access functions.
 * (Needed by both vga core and native cirrus core.)
 */

static inline __attribute__((always_inline)) uint8_t
video_col_get(struct cpssp * cpssp, unsigned int nr, unsigned int rgb)
{
	assert(nr < 259);
	assert(rgb < 3);

	return cpssp->colregs[nr][rgb];
}

static inline __attribute__((always_inline)) void
video_col_set(struct cpssp * cpssp, unsigned int nr, unsigned int rgb, uint8_t val)
{
	assert(nr < 259);
	assert(rgb < 3);

	cpssp->colregs[nr][rgb] = val;
}

/* 
 * Valid memory configurations, due to gd5446trm:
 *
 * (The mappings from linear frame buffer adresses
 * to the memory chip array represent the current
 * implementation below and are tested against the
 * memory probing algorithm of a real Cirrus VGA BIOS.)
 *
 * 1MB: 2 x 256Kx16, 32 bit mode, half a bank
 *      address mask: 0x0fffff, mode: B32
 *       2            1
 *      10 9876 5432 1098 7654 3210
 *      00 1111 1111 1111 1111 1111
 *      -- RRRR RRRR RCCC CCCC CCMS
 *          RRR RRRR RRCC CCCC CCCS (addr)
 *                               1M (chip)
 *
 * 2MB: 4 x 256Kx16, 64 bit mode, 1 bank
 *      address mask: 0x1fffff, mode: B64_2MB_1B
 *       2            1
 *      10 9876 5432 1098 7654 3210
 *      01 1111 1111 1111 1111 1111
 *      -R RRRR RRRR CCCC CCCC CMMS
 *          RRR RRRR RRCC CCCC CCCS (addr)
 *                               MM (chip)
 *
 * 4MB: 8 x 256Kx16, 64 bit mode, 2 banks
 *      address mask: 0x3fffff, mode: B64_2MB_2B
 *       2            1
 *      10 9876 5432 1098 7654 3210
 *      11 1111 1111 1111 1111 1111
 *      BR RRRR RRRR CCCC CCCC CMMS
 *          RRR RRRR RRCC CCCC CCCS (addr)
 *                              BMM (chip)
 *
 * 1MB: 4 x 128Kx16, 64 bit mode, 1 bank
 *      address mask: 0x0fffff, mode: B64_1MB_1B
 *       2            1
 *      10 9876 5432 1098 7654 3210
 *      00 1111 1111 1111 1111 1111
 *      -- RRRR RRRR RCCC CCCC CMMS
 *           RR RRRR RRRC CCCC CCCS (addr)
 *                               MM (chip)
 *
 * 2MB: 8 x 128Kx16, 64 bit mode, 2 banks
 *      address mask: 0x1fffff, mode: B64_1MB_2B 
 *       2            1
 *      10 9876 5432 1098 7654 3210
 *      01 1111 1111 1111 1111 1111
 *      -B RRRR RRRR RCCC CCCC CMMS
 *           RR RRRR RRRC CCCC CCCS (addr)
 *                              BMM (chip)
 *
 * 3MB: 4 x 128Kx16, 4 x 256Kx16, 64 bit mode, 2 banks, bank swap
 *      address mask: 0x3fffff, mode: B64_SWAP
 *       2            1
 *      10 9876 5432 1098 7654 3210
 *      11 1111 1111 1111 1111 1111
 *      0R RRRR RRRR CCCC CCCC CMMS (Bank 1, 256Kx16s)
 *          RRR RRRR RRCC CCCC CCCS (addr)
 *                              1MM (chip)
 *      1- RRRR RRRR RCCC CCCC CMMS (Bank 0, 128Kx16s)
 *           RR RRRR RRRC CCCC CCCS (addr)
 *                              0MM (chip)
 *
 * S = byte select (within chip)
 * M = module (=chip) number (within bank)
 * C = column address
 * R = row address
 * B = bank select
 * - = don't care
 */

#define ADDRESS_MAP \
	switch (cpssp->dram_data_bus_width) {			\
	case 0b00:						\
	case 0b10:						\
		/* 32-bit Bus */				\
		chip = (pa & 2) >> 1 | 2;			\
		addr = (pa & 1)					\
			| ((pa & 0x0ffffc) >> 1);		\
		break;						\
								\
	case 0b01:						\
	case 0b11:						\
		/* 64-bit Bus */				\
		chip = ((pa & 6) >> 1)				\
			| ((pa >> (19 - cpssp->dram_bank_size)) & 4);\
		addr = (pa & 1)					\
			| ((pa & 0x1ffff8) >> 2);		\
		break;						\
								\
	default: assert(0);					\
	}							\
	/* Second Bank Disabled? */				\
	chip &= cpssp->dram_bank_switch_control << 2 | 0b011;	\
	/* Bank Swap? */					\
	chip ^= cpssp->dram_bank_swap << 2;

/*
 * There is still one quite ugly hack:
 * the bit blitter line buffer memory is made
 * accessible through the same video_read/video_write
 * functions as the graphics memory to allow a
 * transparent implementation of the blitting
 * functions. (But, of course, the bit blitter line
 * buffer is _not_ directly accessible via the PCI
 * bus, only indirectly when writing to the system-
 * to-screen aperture!)
 */
static uint8_t 
video_readb(struct cpssp * cpssp, uint32_t pa)
{
	int chip;
	uint32_t addr;
	uint16_t val;

	if (pa < BITBLT_LINE_BUFFER_START) {

		ADDRESS_MAP;

		sig_cs16_mr(cpssp->port_mem_cs[chip], cpssp,
				addr & ~1, 1 << (addr & 1), &val);
		return val >> (8 * (addr & 1));

	} else {
		pa -= BITBLT_LINE_BUFFER_START;
		assert(pa < BITBLT_LINE_BUFFER_SIZE);
		return cpssp->bitblt.line_buffer[pa];
	}
}

static uint16_t
video_readw(struct cpssp * cpssp, uint32_t pa)
{
	int chip;
	uint32_t addr;
	uint16_t val;

	if (pa < BITBLT_LINE_BUFFER_START) {
		/*
		 * Access might be unaligned during resolution switch.
		 * Just continue.
		 */
		pa &= ~1;

		ADDRESS_MAP;

		sig_cs16_mr(cpssp->port_mem_cs[chip], cpssp, addr, 0b11, &val);
		return val;

	} else {
		pa -= BITBLT_LINE_BUFFER_START;
		assert(pa < BITBLT_LINE_BUFFER_SIZE);
		return *((uint16_t*)&cpssp->bitblt.line_buffer[pa]);
	}
}

static uint32_t
video_readl(struct cpssp * cpssp, uint32_t pa)
{
	int chip;
	uint32_t addr;
	uint16_t val_lo;
	uint16_t val_hi;

	if (pa < BITBLT_LINE_BUFFER_START) {
		/*
		 * Access might be unaligned during resolution switch.
		 * Just continue.
		 */
		pa &= ~3;

		ADDRESS_MAP;

		sig_cs16_mr(cpssp->port_mem_cs[chip+0], cpssp, addr, 0b11, &val_lo);
		sig_cs16_mr(cpssp->port_mem_cs[chip+1], cpssp, addr, 0b11, &val_hi);
		return (val_lo << 0) | (val_hi << 16);

	} else {
		pa -= BITBLT_LINE_BUFFER_START;
		assert(pa < BITBLT_LINE_BUFFER_SIZE);
		return *((uint32_t*)&cpssp->bitblt.line_buffer[pa]);
	}
}

static void
video_writeb(struct cpssp * cpssp, uint32_t pa, uint8_t val)
{
	int chip;
	uint32_t addr;
	uint16_t val16;

	ADDRESS_MAP;

	val16 = val << (8 * (addr & 1));
	sig_cs16_mw(cpssp->port_mem_cs[chip], cpssp,
			addr & ~1, 1 << (addr & 1), val16);
}

static void
video_writew(struct cpssp * cpssp, uint32_t pa, uint16_t val)
{
	int chip;
	uint32_t addr;

	assert((pa & 1) == 0);

	ADDRESS_MAP;

	sig_cs16_mw(cpssp->port_mem_cs[chip], cpssp, addr, 0b11, val);
}

static void
video_writel(struct cpssp * cpssp, uint32_t pa, uint32_t val)
{
	int chip;
	uint32_t addr;

	assert((pa & 3) == 0);

	ADDRESS_MAP;

	sig_cs16_mw(cpssp->port_mem_cs[chip+0], cpssp,
			addr, 0b11, (uint16_t)((val >>  0) & 0xffff));
	sig_cs16_mw(cpssp->port_mem_cs[chip+1], cpssp,
			addr, 0b11, (uint16_t)((val >> 16) & 0xffff));
}

#undef ADDRESS_MAP

static void
COMP_(fifo_put)(struct cpssp *cpssp, uint32_t val)
{
	uint8_t *ptr;

	ptr = &cpssp->bitblt.line_buffer[cpssp->bitblt.line_pointer - BITBLT_LINE_BUFFER_START];
	*ptr++ = (val >> 0) & 0xff;
	*ptr++ = (val >> 8) & 0xff;
	*ptr++ = (val >> 16) & 0xff;
	*ptr++ = (val >> 24) & 0xff;
	cpssp->bitblt.line_pointer += 4;
}


/*
 * Include core VGA functionality
 */

#define ARCH_VGA_PORT CIRRUS_IOPORT
#define BEHAVIOUR
#define NAME		vga
#define NAME_(x)	vga_ ## x
#define SNAME		"vga"
#define NEED_MEM_BS
#include "arch_vga.c"
#undef NEED_MEM_BS
#undef SNAME
#undef NAME_
#undef NAME
#undef BEHAVIOUR
#undef ARCH_VGA_PORT

/*************************************************************************/
/*                                                                       */
/* The BitBLT engine                                                     */
/*                                                                       */
/*************************************************************************/

/*
 * Needed to concatenate function names.
 */
#define xpaste(front, back) front ## back
#define paste(front, back) xpaste(front, back)

/* Needed for the transparent simple forward or backwards rop */
#define CIRRUS_IS_8BPP (  ( ((cpssp->ext_ext_sequencer_mode & 0x0e) >> 1) \
			    == 0) \
		       || ( ((cpssp->ext_ext_sequencer_mode & 0x0e) >> 1) \
			    == 1))
#define CIRRUS_IS_16BPP (((cpssp->ext_ext_sequencer_mode & 0x0e) >> 1) == 3)

static void
_cirrus_bitblt_reset(struct cpssp *cpssp);

/* generate all blitter functions, see trm table 9.11 */

/*
 * s=1 s=1 s=0 s=0
 * d=1 d=0 d=1 d=0
 *
 *  0   0   0   0
 */
#define ROP_NAME zero
#define ROP_CODE(d, s) d = 0
#include "chip_cirrus_gd5446_rop.c"
#undef ROP_NAME
#undef ROP_CODE

/*  0   0   0   1  */
#define ROP_NAME notsrc_and_notdst
#define ROP_CODE(d, s) d = (~(s)) & (~(d))
#include "chip_cirrus_gd5446_rop.c"
#undef ROP_NAME
#undef ROP_CODE

/*  0   0   1   0  */
#define ROP_NAME notsrc_and_dst
#define ROP_CODE(d, s) d = (~(s)) & (d)
#include "chip_cirrus_gd5446_rop.c"
#undef ROP_NAME
#undef ROP_CODE

/*  0   0   1   1  */
#define ROP_NAME notsrc
#define ROP_CODE(d, s) d = (~(s))
#include "chip_cirrus_gd5446_rop.c"
#undef ROP_NAME
#undef ROP_CODE

/*  0   1   0   0  */
#define ROP_NAME src_and_notdst
#define ROP_CODE(d, s) d = (s) & (~(d))
#include "chip_cirrus_gd5446_rop.c"
#undef ROP_NAME
#undef ROP_CODE

/*  0   1   0   1  */
#define ROP_NAME notdst
#define ROP_CODE(d, s) d = ~(d)
#include "chip_cirrus_gd5446_rop.c"
#undef ROP_NAME
#undef ROP_CODE

/*  0   1   1   0  */
#define ROP_NAME src_xor_dst
#define ROP_CODE(d, s) d = (s) ^ (d)
#include "chip_cirrus_gd5446_rop.c"
#undef ROP_NAME
#undef ROP_CODE

/*  0   1   1   1  */
#define ROP_NAME notsrc_or_notdst
#define ROP_CODE(d, s) d = (~(s)) | (~(d))
#include "chip_cirrus_gd5446_rop.c"
#undef ROP_NAME
#undef ROP_CODE

/*  1   0   0   0  */
#define ROP_NAME src_and_dst
#define ROP_CODE(d, s) d = (s) & (d)
#include "chip_cirrus_gd5446_rop.c"
#undef ROP_NAME
#undef ROP_CODE

/*  1   0   0   1  */
#define ROP_NAME src_notxor_dst
#define ROP_CODE(d, s) d = ~((s) ^ (d))
#include "chip_cirrus_gd5446_rop.c"
#undef ROP_NAME
#undef ROP_CODE

/*  1   0   1   0
 * dst would have no effect -> this is the nop
 */

/*  1   0   1   1  */
#define ROP_NAME notsrc_or_dst
#define ROP_CODE(d, s) d = (~(s)) | (d)
#include "chip_cirrus_gd5446_rop.c"
#undef ROP_NAME
#undef ROP_CODE

/*  1   1   0   0  */
#define ROP_NAME src
#define ROP_CODE(d, s) d = s
#include "chip_cirrus_gd5446_rop.c"
#undef ROP_NAME
#undef ROP_CODE

/*  1   1   0   1  */
#define ROP_NAME src_or_notdst
#define ROP_CODE(d, s) d = (s) | (~(d))
#include "chip_cirrus_gd5446_rop.c"
#undef ROP_NAME
#undef ROP_CODE

/*  1   1   1   0  */
#define ROP_NAME src_or_dst
#define ROP_CODE(d, s) d = (s) | (d)
#include "chip_cirrus_gd5446_rop.c"
#undef ROP_NAME
#undef ROP_CODE

/*  1   1   1   1  */
#define ROP_NAME one
#define ROP_CODE(d, s) d = ~0
#include "chip_cirrus_gd5446_rop.c"
#undef ROP_NAME
#undef ROP_CODE

static void
_cirrus_bitblt_nop(struct cpssp *cpssp)
{
}

typedef void (*_cirrus_bitblt_function_type)(struct cpssp *cpssp);

static const _cirrus_bitblt_function_type
_cirrus_bitblt_forward[16] = {
	_cirrus_bitblt_forward_zero,
	_cirrus_bitblt_forward_notsrc_and_notdst,
	_cirrus_bitblt_forward_notsrc_and_dst,
	_cirrus_bitblt_forward_notsrc,
	_cirrus_bitblt_forward_src_and_notdst,
	_cirrus_bitblt_forward_notdst,
	_cirrus_bitblt_forward_src_xor_dst,
	_cirrus_bitblt_forward_notsrc_or_notdst,
	_cirrus_bitblt_forward_src_and_dst,
	_cirrus_bitblt_forward_src_notxor_dst,
	_cirrus_bitblt_nop,
	_cirrus_bitblt_forward_notsrc_or_dst,
	_cirrus_bitblt_forward_src,
	_cirrus_bitblt_forward_src_or_notdst,
	_cirrus_bitblt_forward_src_or_dst,
	_cirrus_bitblt_forward_one
};

static const _cirrus_bitblt_function_type
_cirrus_bitblt_backward[16] = {
	_cirrus_bitblt_backward_zero,
	_cirrus_bitblt_backward_notsrc_and_notdst,
	_cirrus_bitblt_backward_notsrc_and_dst,
	_cirrus_bitblt_backward_notsrc,
	_cirrus_bitblt_backward_src_and_notdst,
	_cirrus_bitblt_backward_notdst,
	_cirrus_bitblt_backward_src_xor_dst,
	_cirrus_bitblt_backward_notsrc_or_notdst,
	_cirrus_bitblt_backward_src_and_dst,
	_cirrus_bitblt_backward_src_notxor_dst,
	_cirrus_bitblt_nop,
	_cirrus_bitblt_backward_notsrc_or_dst,
	_cirrus_bitblt_backward_src,
	_cirrus_bitblt_backward_src_or_notdst,
	_cirrus_bitblt_backward_src_or_dst,
	_cirrus_bitblt_backward_one
};

static const _cirrus_bitblt_function_type
_cirrus_bitblt_transparent_forward[16] = {
	_cirrus_bitblt_transparent_forward_zero,
	_cirrus_bitblt_transparent_forward_notsrc_and_notdst,
	_cirrus_bitblt_transparent_forward_notsrc_and_dst,
	_cirrus_bitblt_transparent_forward_notsrc,
	_cirrus_bitblt_transparent_forward_src_and_notdst,
	_cirrus_bitblt_transparent_forward_notdst,
	_cirrus_bitblt_transparent_forward_src_xor_dst,
	_cirrus_bitblt_transparent_forward_notsrc_or_notdst,
	_cirrus_bitblt_transparent_forward_src_and_dst,
	_cirrus_bitblt_transparent_forward_src_notxor_dst,
	_cirrus_bitblt_nop,
	_cirrus_bitblt_transparent_forward_notsrc_or_dst,
	_cirrus_bitblt_transparent_forward_src,
	_cirrus_bitblt_transparent_forward_src_or_notdst,
	_cirrus_bitblt_transparent_forward_src_or_dst,
	_cirrus_bitblt_transparent_forward_one
};

static const _cirrus_bitblt_function_type
_cirrus_bitblt_transparent_backward[16] = {
	_cirrus_bitblt_transparent_backward_zero,
	_cirrus_bitblt_transparent_backward_notsrc_and_notdst,
	_cirrus_bitblt_transparent_backward_notsrc_and_dst,
	_cirrus_bitblt_transparent_backward_notsrc,
	_cirrus_bitblt_transparent_backward_src_and_notdst,
	_cirrus_bitblt_transparent_backward_notdst,
	_cirrus_bitblt_transparent_backward_src_xor_dst,
	_cirrus_bitblt_transparent_backward_notsrc_or_notdst,
	_cirrus_bitblt_transparent_backward_src_and_dst,
	_cirrus_bitblt_transparent_backward_src_notxor_dst,
	_cirrus_bitblt_nop,
	_cirrus_bitblt_transparent_backward_notsrc_or_dst,
	_cirrus_bitblt_transparent_backward_src,
	_cirrus_bitblt_transparent_backward_src_or_notdst,
	_cirrus_bitblt_transparent_backward_src_or_dst,
	_cirrus_bitblt_transparent_backward_one
};

#define ROP_DEPTHIFY(function) { function ## _8, \
				 function ## _16,\
				 function ## _24,\
				 function ## _32 }

static const _cirrus_bitblt_function_type
_cirrus_bitblt_color_fill[16][4] = {
	ROP_DEPTHIFY(_cirrus_bitblt_color_fill_zero),
	ROP_DEPTHIFY(_cirrus_bitblt_color_fill_notsrc_and_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_fill_notsrc_and_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_fill_notsrc),
	ROP_DEPTHIFY(_cirrus_bitblt_color_fill_src_and_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_fill_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_fill_src_xor_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_fill_notsrc_or_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_fill_src_and_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_fill_src_notxor_dst),
	{_cirrus_bitblt_nop,
	 _cirrus_bitblt_nop,
	 _cirrus_bitblt_nop,
	 _cirrus_bitblt_nop },
	ROP_DEPTHIFY(_cirrus_bitblt_color_fill_notsrc_or_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_fill_src),
	ROP_DEPTHIFY(_cirrus_bitblt_color_fill_src_or_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_fill_src_or_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_fill_one)
};

static const  _cirrus_bitblt_function_type
_cirrus_bitblt_color_expansion[16][4] = {
	ROP_DEPTHIFY(_cirrus_bitblt_color_expansion_zero),
	ROP_DEPTHIFY(_cirrus_bitblt_color_expansion_notsrc_and_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_expansion_notsrc_and_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_expansion_notsrc),
	ROP_DEPTHIFY(_cirrus_bitblt_color_expansion_src_and_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_expansion_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_expansion_src_xor_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_expansion_notsrc_or_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_expansion_src_and_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_expansion_src_notxor_dst),
	{_cirrus_bitblt_nop,
	 _cirrus_bitblt_nop,
	 _cirrus_bitblt_nop,
	 _cirrus_bitblt_nop },
	ROP_DEPTHIFY(_cirrus_bitblt_color_expansion_notsrc_or_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_expansion_src),
	ROP_DEPTHIFY(_cirrus_bitblt_color_expansion_src_or_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_expansion_src_or_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_expansion_one)
};

static const  _cirrus_bitblt_function_type
_cirrus_bitblt_transparent_color_expansion[16][4] = {
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_expansion_zero),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_expansion_notsrc_and_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_expansion_notsrc_and_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_expansion_notsrc),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_expansion_src_and_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_expansion_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_expansion_src_xor_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_expansion_notsrc_or_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_expansion_src_and_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_expansion_src_notxor_dst),
	{_cirrus_bitblt_nop,
	 _cirrus_bitblt_nop,
	 _cirrus_bitblt_nop,
	 _cirrus_bitblt_nop },
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_expansion_notsrc_or_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_expansion_src),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_expansion_src_or_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_expansion_src_or_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_expansion_one)
};

static const  _cirrus_bitblt_function_type
_cirrus_bitblt_color_pattern_expansion[16][4] = {
	ROP_DEPTHIFY(_cirrus_bitblt_color_pattern_expansion_zero),
	ROP_DEPTHIFY(_cirrus_bitblt_color_pattern_expansion_notsrc_and_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_pattern_expansion_notsrc_and_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_pattern_expansion_notsrc),
	ROP_DEPTHIFY(_cirrus_bitblt_color_pattern_expansion_src_and_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_pattern_expansion_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_pattern_expansion_src_xor_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_pattern_expansion_notsrc_or_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_pattern_expansion_src_and_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_pattern_expansion_src_notxor_dst),
	{_cirrus_bitblt_nop,
	 _cirrus_bitblt_nop,
	 _cirrus_bitblt_nop,
	 _cirrus_bitblt_nop },
	ROP_DEPTHIFY(_cirrus_bitblt_color_pattern_expansion_notsrc_or_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_pattern_expansion_src),
	ROP_DEPTHIFY(_cirrus_bitblt_color_pattern_expansion_src_or_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_pattern_expansion_src_or_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_color_pattern_expansion_one)
};

static const  _cirrus_bitblt_function_type
_cirrus_bitblt_transparent_color_pattern_expansion[16][4] = {
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_pattern_expansion_zero),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_pattern_expansion_notsrc_and_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_pattern_expansion_notsrc_and_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_pattern_expansion_notsrc),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_pattern_expansion_src_and_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_pattern_expansion_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_pattern_expansion_src_xor_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_pattern_expansion_notsrc_or_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_pattern_expansion_src_and_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_pattern_expansion_src_notxor_dst),
	{_cirrus_bitblt_nop,
	 _cirrus_bitblt_nop,
	 _cirrus_bitblt_nop,
	 _cirrus_bitblt_nop },
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_pattern_expansion_notsrc_or_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_pattern_expansion_src),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_pattern_expansion_src_or_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_pattern_expansion_src_or_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_transparent_color_pattern_expansion_one)
};

static const  _cirrus_bitblt_function_type
_cirrus_bitblt_pattern_fill[16][4] = {
	ROP_DEPTHIFY(_cirrus_bitblt_pattern_fill_zero),
	ROP_DEPTHIFY(_cirrus_bitblt_pattern_fill_notsrc_and_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_pattern_fill_notsrc_and_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_pattern_fill_notsrc),
	ROP_DEPTHIFY(_cirrus_bitblt_pattern_fill_src_and_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_pattern_fill_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_pattern_fill_src_xor_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_pattern_fill_notsrc_or_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_pattern_fill_src_and_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_pattern_fill_src_notxor_dst),
	{_cirrus_bitblt_nop,
	 _cirrus_bitblt_nop,
	 _cirrus_bitblt_nop,
	 _cirrus_bitblt_nop },
	ROP_DEPTHIFY(_cirrus_bitblt_pattern_fill_notsrc_or_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_pattern_fill_src),
	ROP_DEPTHIFY(_cirrus_bitblt_pattern_fill_src_or_notdst),
	ROP_DEPTHIFY(_cirrus_bitblt_pattern_fill_src_or_dst),
	ROP_DEPTHIFY(_cirrus_bitblt_pattern_fill_one)
};

#undef ROP_DEPTHIFY

/*
 * All raster operations assume that source_pointer and destination_pointer
 * point to completly mapped memory areas.
 */

static void
_cirrus_bitblt_reset(struct cpssp *cpssp)
{
#ifdef CIRRUS_DEBUG_BITBLT
	faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "", "\n");
#endif
	cpssp->blt_start_status &=
		~(BITBLT_BUFFERED_REGISTER_STATUS | BITBLT_RESET
		  | BITBLT_START | BITBLT_STATUS);

	cpssp->bitblt.line_pointer = BITBLT_LINE_BUFFER_START;
	cpssp->bitblt.line_pointer_end = BITBLT_LINE_BUFFER_START;
	cpssp->bitblt.src_counter = 0;
}

static void
_cirrus_bitblt_screen_to_screen(struct cpssp *cpssp)
{
#ifdef CIRRUS_DEBUG_BITBLT
	faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "", "\n");
#endif
	(*cpssp->bitblt.function)(cpssp);
	_cirrus_bitblt_reset(cpssp);
}

static void
_cirrus_bitblt_system_to_screen(struct cpssp *cpssp)
{
#ifdef CIRRUS_DEBUG_BITBLT
	faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "", "\n");
#endif
	/* source pitch is a don't care, calculate it to length
	   of one src line in bytes */
	if (cpssp->bitblt.mode & BITBLT_COLOR_EXPAND) {

		int bpl; /* source bits needed per line */

		bpl = cpssp->bitblt.width /
			cpssp->bitblt.color_expand_width;

		if (cpssp->bitblt.mode_extensions & BITBLT_32BIT_GRANULARITY) {

			/* position of the first byte within the first dword
			   of each destination scanline */
			bpl += ((cpssp->bitblt.dest_left_side_clipping
				 & BITBLT_SYSTEM_TO_SCREEN_DWORD_POINTER) >> 5)
				* 8;

			/* how many bytes needed per scanline? */
			cpssp->bitblt.src_pitch = ((bpl + 31) >> 5) * 4;

		} else {
			/* 8bit granularity */
			cpssp->bitblt.src_pitch = (bpl + 7) >> 3;
		}
	} else {
		/* always align to 32bits */
		cpssp->bitblt.src_pitch =
			(cpssp->bitblt.width + 3) & ~3;
	}

	if (BITBLT_LINE_BUFFER_SIZE < cpssp->bitblt.src_pitch) {
		/* this should never happen */
#ifdef CIRRUS_DEBUG_BITBLT
		faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
			 "System-to-screen line buffer too small, required "
			 "are %d bytes\n", cpssp->bitblt.src_pitch);
#endif
		_cirrus_bitblt_reset(cpssp);
		return;
	}

	/* set up bitblt engine for single line execution */
	cpssp->bitblt.src_counter = /* total bytes for rop */
		cpssp->bitblt.src_pitch * cpssp->bitblt.height;

	cpssp->bitblt.line_pointer = BITBLT_LINE_BUFFER_START;
	cpssp->bitblt.line_pointer_end =
		BITBLT_LINE_BUFFER_START + cpssp->bitblt.src_pitch;

	cpssp->bitblt.height = 1;
	cpssp->bitblt.source_pointer = BITBLT_LINE_BUFFER_START;
}

static void
_cirrus_bitblt_system_to_screen_next(struct cpssp *cpssp)
{
#ifdef CIRRUS_DEBUG_BITBLT
	faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "", "\n");
#endif

	if (cpssp->bitblt.src_counter <= 0) {
		/* no sys2scr bitblt set up? */
#ifdef CIRRUS_DEBUG_BITBLT
		faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
			 "No system-to-screen bitblt in progress? Resetting.\n");
#endif
		_cirrus_bitblt_reset(cpssp);
		return;
	}

	/* rop the line */
	(*cpssp->bitblt.function)(cpssp);

	/* update state of bitblt engine */
	cpssp->bitblt.destination_pointer += cpssp->bitblt.dest_pitch;
	cpssp->bitblt.src_counter -= cpssp->bitblt.src_pitch;

	if (cpssp->bitblt.src_counter <= 0) {
		/* finished */
		_cirrus_bitblt_reset(cpssp);
		return;
	}

	if (!(cpssp->bitblt.mode & BITBLT_COLOR_EXPAND)
	    || (cpssp->bitblt.mode_extensions & BITBLT_32BIT_GRANULARITY)) {
		/* dword granularity:
		 * drop remaining bytes of line buffer */
		cpssp->bitblt.line_pointer = BITBLT_LINE_BUFFER_START;

	} else {
		/* byte granularity:
		 * copy remaining bytes of line buffer to front */
		memmove(cpssp->bitblt.line_buffer,
				&cpssp->bitblt.line_buffer[
				        BITBLT_LINE_BUFFER_START
					- cpssp->bitblt.line_pointer_end],
				cpssp->bitblt.line_pointer
				- cpssp->bitblt.line_pointer_end);

		cpssp->bitblt.line_pointer = BITBLT_LINE_BUFFER_START;
	}
}

static void
_cirrus_bitblt_do_solid_color_fill(struct cpssp *cpssp)
{
#ifdef CIRRUS_DEBUG_BITBLT
	faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "", "\n");
#endif
	cpssp->bitblt.function =
		_cirrus_bitblt_color_fill[cpssp->bitblt.rop]
		[cpssp->bitblt.color_expand_width - 1];
	_cirrus_bitblt_screen_to_screen(cpssp);
}

static void
_cirrus_bitblt_do_color_expansion(struct cpssp *cpssp)
{
#ifdef CIRRUS_DEBUG_BITBLT
	faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "", "\n");
#endif
	if (cpssp->bitblt.mode & BITBLT_TRANSPARENCY) {
		cpssp->bitblt.function =
			_cirrus_bitblt_transparent_color_expansion
			[cpssp->bitblt.rop]
			[cpssp->bitblt.color_expand_width - 1];
	} else {
		cpssp->bitblt.function =
			_cirrus_bitblt_color_expansion
			[cpssp->bitblt.rop]
			[cpssp->bitblt.color_expand_width - 1];
	}

	if (cpssp->bitblt.mode & BITBLT_SOURCE_SYSTEM_MEMORY) {
		_cirrus_bitblt_system_to_screen(cpssp);
	} else {
		_cirrus_bitblt_screen_to_screen(cpssp);
	}
}

static void
_cirrus_bitblt_do_pattern_fill(struct cpssp *cpssp)
{
#ifdef CIRRUS_DEBUG_BITBLT
	faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "", "\n");
#endif
	if (cpssp->bitblt.mode & BITBLT_COLOR_EXPAND) {
		if (cpssp->bitblt.mode & BITBLT_TRANSPARENCY) {
			cpssp->bitblt.function =
				_cirrus_bitblt_transparent_color_pattern_expansion
				[cpssp->bitblt.rop]
				[cpssp->bitblt.color_expand_width - 1];
		} else {
			cpssp->bitblt.function =
				_cirrus_bitblt_color_pattern_expansion
				[cpssp->bitblt.rop]
				[cpssp->bitblt.color_expand_width - 1];
		}
	} else {
		cpssp->bitblt.function =
			_cirrus_bitblt_pattern_fill
			[cpssp->bitblt.rop]
			[cpssp->bitblt.color_expand_width - 1];
	}

	if (cpssp->bitblt.mode & BITBLT_SOURCE_SYSTEM_MEMORY) {
		/* system_to_screen is not allowed */
#ifdef CIRRUS_DEBUG_BITBLT
		faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
			 "System-to-screen pattern fills are not allowed!\n");
#endif
		_cirrus_bitblt_reset(cpssp);
	} else {
		_cirrus_bitblt_screen_to_screen(cpssp);
	}
}

static void
_cirrus_bitblt_do_simple_rop(struct cpssp *cpssp)
{
#ifdef CIRRUS_DEBUG_BITBLT
	faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "", "\n");
#endif
	if ((cpssp->bitblt.mode & BITBLT_TRANSPARENCY)
	 && (CIRRUS_IS_8BPP || CIRRUS_IS_16BPP)) {
		if (cpssp->bitblt.mode & BITBLT_REVERSE) {
			cpssp->bitblt.dest_pitch = -cpssp->bitblt.dest_pitch;
			cpssp->bitblt.src_pitch = -cpssp->bitblt.src_pitch;
			cpssp->bitblt.function =
				_cirrus_bitblt_transparent_backward[cpssp->bitblt.rop];
		} else {
			cpssp->bitblt.function =
				_cirrus_bitblt_transparent_forward[cpssp->bitblt.rop];
		}
	} else {
		if (cpssp->bitblt.mode & BITBLT_REVERSE) {
			cpssp->bitblt.dest_pitch = -cpssp->bitblt.dest_pitch;
			cpssp->bitblt.src_pitch = -cpssp->bitblt.src_pitch;
			cpssp->bitblt.function =
				_cirrus_bitblt_backward[cpssp->bitblt.rop];
		} else {
			cpssp->bitblt.function =
				_cirrus_bitblt_forward[cpssp->bitblt.rop];
		}
	}

	if (cpssp->bitblt.mode & BITBLT_SOURCE_SYSTEM_MEMORY) {
		_cirrus_bitblt_system_to_screen(cpssp);
	} else {
		_cirrus_bitblt_screen_to_screen(cpssp);
	}
}

static void
_cirrus_bitblt_start(struct cpssp *cpssp)
{
	uint32_t dest_offset, src_offset;

	/* bitblitter busy now... */
	cpssp->blt_start_status |= BITBLT_STATUS;

	/*
	 * first initialize struct bitblt
	 */

	/* trm 5.2 */
	cpssp->bitblt.width =
		(cpssp->blt_width[0]
		 | ((cpssp->blt_width[1] & 0x1f) << 8)) + 1;

	/* trm 5.3 */
	cpssp->bitblt.height =
		(cpssp->blt_height[0]
		 | ((cpssp->blt_height[1] & 0x7) << 8)) + 1;

	/* trm 5.4 */
	cpssp->bitblt.dest_pitch = cpssp->blt_dest_pitch[0]
		| ((cpssp->blt_dest_pitch[1] & 0x1f) << 8);

	/* trm 5.5 */
	cpssp->bitblt.src_pitch = cpssp->blt_source_pitch[0]
		| ((cpssp->blt_source_pitch[1] & 0x1f) << 8);

	/* trm 5.6 */
	dest_offset = cpssp->blt_dest_start[0]
		| (cpssp->blt_dest_start[1] << 8)
		| ((cpssp->blt_dest_start[2] & 0x3f) << 16);
	cpssp->bitblt.destination_pointer = dest_offset;

	/* trm 5.7 */
	src_offset = cpssp->blt_source_start[0]
		| (cpssp->blt_source_start[1] << 8)
		| ((cpssp->blt_source_start[2] & 0x3f) << 16);
	if (cpssp->blt_mode & BITBLT_8X8_PATTERN_COPY) {
		/* see trm 9.4.8 */
		src_offset &= ~0x07UL;
	}
	cpssp->bitblt.source_pointer = src_offset;

	/* trm 5.8 */
	cpssp->bitblt.dest_left_side_clipping =
		cpssp->blt_dest_left_side_clipping;

	/* trm 5.9 */
	cpssp->bitblt.mode = cpssp->blt_mode;
	cpssp->bitblt.color_expand_width =
		((cpssp->blt_mode & BITBLT_COLOR_EXPAND_WIDTH) >> 4) + 1;

	/* trm 5.12 */
	cpssp->bitblt.mode_extensions = cpssp->blt_mode_extensions;

	/* trm 5.13 */
	cpssp->bitblt.key_color[0] =
		cpssp->blt_transp_blt_key_color[0];
	cpssp->bitblt.key_color[1] =
		cpssp->blt_transp_blt_key_color[1];

	/* trm 5.11 */
	switch (cpssp->blt_rop) {
	case 0x00:
		cpssp->bitblt.rop = 0;  /* zero */
		break;
	case 0x90:
		cpssp->bitblt.rop = 1;  /* notsrc_and_notdst */
		break;
	case 0x50:
		cpssp->bitblt.rop = 2;  /* notsrc_and_dst */
		break;
	case 0xd0:
		cpssp->bitblt.rop = 3;  /* notsrc */
		break;
	case 0x09:
		cpssp->bitblt.rop = 4;  /* src_and_notdst */
		break;
	case 0x0b:
		cpssp->bitblt.rop = 5;  /* notdst */
		break;
	case 0x59:
		cpssp->bitblt.rop = 6;  /* src_xor_dst */
		break;
	case 0xda:
		cpssp->bitblt.rop = 7;  /* notsrc_or_notdst */
		break;
	case 0x05:
		cpssp->bitblt.rop = 8;  /* src_and_dst */
		break;
	case 0x95:
		cpssp->bitblt.rop = 9;  /* src_notxor_dst */
		break;
	case 0x06:
		cpssp->bitblt.rop = 10; /* dst (nop) */
		break;
	case 0xd6:
		cpssp->bitblt.rop = 11; /* notsrc_or_dst */
		break;
	case 0x0d:
		cpssp->bitblt.rop = 12; /* src */
		break;
	case 0xad:
		cpssp->bitblt.rop = 13; /* src_or_notdst */
		break;
	case 0x6d:
		cpssp->bitblt.rop = 14; /* src_or_dst */
		break;
	case 0x0e:
		cpssp->bitblt.rop = 15; /* one */
		break;
	default:
#ifdef CIRRUS_DEBUG_BITBLT
		faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
			 "Ignoring unknown raster operation 0x%02hx\n",
			 cpssp->blt_rop);
#endif
		_cirrus_bitblt_reset(cpssp);
		return;
	}

	/* trm 5.1 and trm 9.4.7 */
	switch (cpssp->bitblt.color_expand_width) {
	case 1:                 /* 8bpp */
		cpssp->bitblt.fg_color = cpssp->shadow_gr1;
		cpssp->bitblt.bg_color = cpssp->shadow_gr0;
		break;
	case 2:                 /* 16bpp */
		cpssp->bitblt.fg_color = cpssp->shadow_gr1
			| (cpssp->blt_color_exp_fg_bg[1] << 8);
		cpssp->bitblt.bg_color = cpssp->shadow_gr0
			| (cpssp->blt_color_exp_fg_bg[0] << 8);
		break;
	case 3:                 /* 24bpp */
		cpssp->bitblt.fg_color = cpssp->shadow_gr1
			| (cpssp->blt_color_exp_fg_bg[1] << 8)
			| (cpssp->blt_color_exp_fg_bg[3] << 16);
		/* 24bpp color expansion must use transparency */
		cpssp->bitblt.bg_color = 0;
		break;
	case 4:                 /* 32bpp */
		cpssp->bitblt.fg_color = cpssp->shadow_gr1
			| (cpssp->blt_color_exp_fg_bg[1] << 8)
			| (cpssp->blt_color_exp_fg_bg[3] << 16)
			| (cpssp->blt_color_exp_fg_bg[5] << 24);
		cpssp->bitblt.bg_color = cpssp->shadow_gr0
			| (cpssp->blt_color_exp_fg_bg[0] << 8)
			| (cpssp->blt_color_exp_fg_bg[2] << 16)
			| (cpssp->blt_color_exp_fg_bg[4] << 24);
		break;
	default:
		assert(0); /* impossible */
	}

#ifdef CIRRUS_DEBUG_BITBLT
	fprintf(stderr,
		 "Initialized struct bitblt:\n"
		 "  fg_color = 0x%08lx\n"
		 "  bg_color = 0x%08lx\n"
		 "  width    = %d\n"
		 "  height   = %d\n"
		 "  dest_pitch = %d\n"
		 "  src_pitch  = %d\n"
		 "  destination_pointer = &vram[%d]\n"
		 "  source_pointer      = &vram[%d]\n"
		 "  mode = 0x%02hx\n"
		 "  color_expand_width = %d (in bytes)\n"
		 "  mode_extensions    = 0x%02hx\n"
		 "  dest_left_side_clipping = 0x%02hx\n"
		 "  rop = %d\n"
		 "  key_color[0] = 0x%02hx\n"
		 "  key_color[1] = 0x%02hx\n",
		 cpssp->bitblt.fg_color,
		 cpssp->bitblt.bg_color,
		 cpssp->bitblt.width,
		 cpssp->bitblt.height,
		 cpssp->bitblt.dest_pitch,
		 cpssp->bitblt.src_pitch,
		 (int) dest_offset,
		 (int) src_offset,
		 cpssp->bitblt.mode,
		 cpssp->bitblt.color_expand_width,
		 cpssp->bitblt.mode_extensions,
		 cpssp->bitblt.dest_left_side_clipping,
		 cpssp->bitblt.rop,
		 cpssp->bitblt.key_color[0],
		 cpssp->bitblt.key_color[1]
		);
#endif

	/*
	 * second: what are we going to do now?
	 */

	if ((cpssp->bitblt.mode_extensions & BITBLT_SOLID_COLOR_FILL)
	 && ((cpssp->bitblt.mode & (BITBLT_COLOR_EXPAND
					| BITBLT_8X8_PATTERN_COPY
					| BITBLT_TRANSPARENCY))
				== (BITBLT_COLOR_EXPAND | BITBLT_8X8_PATTERN_COPY))) {
		_cirrus_bitblt_do_solid_color_fill(cpssp);

	} else if ((cpssp->bitblt.mode & (BITBLT_COLOR_EXPAND
					| BITBLT_8X8_PATTERN_COPY))
				== BITBLT_COLOR_EXPAND) {
		_cirrus_bitblt_do_color_expansion(cpssp);

	} else if (cpssp->bitblt.mode & BITBLT_8X8_PATTERN_COPY) {
		_cirrus_bitblt_do_pattern_fill(cpssp);

	} else {
		_cirrus_bitblt_do_simple_rop(cpssp);
	}
}

#undef CIRRUS_IS_8BPP
#undef CIRRUS_IS_16BPP

/*************************************************************************/
/*                                                                       */
/* Functions for some special registers                                  */
/*                                                                       */
/*************************************************************************/

static void
_cirrus_write_blt_start(struct cpssp *cpssp, uint8_t val)
{
	uint8_t old_val;

	old_val = cpssp->blt_start_status;
	cpssp->blt_start_status = val;

	if (((old_val & BITBLT_RESET) == 0)
	 && (val & BITBLT_RESET)) {
		_cirrus_bitblt_reset(cpssp);
	} else if (((old_val & BITBLT_START) == 0)
		   && (val & BITBLT_START)) {
		_cirrus_bitblt_start(cpssp);
	}
}

static uint8_t
_cirrus_read_hidden_dac(struct cpssp *cpssp)
{
	uint8_t value;

	if (cpssp->ext_hidden_dac_counter == 4) {
		value = cpssp->ext_hidden_dac;
		cpssp->ext_hidden_dac_counter = 0;
#if (1 < CIRRUS_DEBUG)
		faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
			 " 0x%02hx\n", value);
#endif
	} else {
		value = cpssp->vga.col_pel_mask;
		cpssp->ext_hidden_dac_counter++;
	}
	return value;
}

static void
_cirrus_write_hidden_dac(struct cpssp *cpssp, uint8_t val)
{
	if (cpssp->ext_hidden_dac_counter == 4) {
		cpssp->ext_hidden_dac = val;
#if (1 < CIRRUS_DEBUG)
		faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
			 " 0x%02hx\n", val);
#endif
	} else {
		cpssp->vga.col_pel_mask = val;
	}
	cpssp->ext_hidden_dac_counter = 0;
}

/*
 * Bit 1 of SR12 is
 * 1: Three extra DAC LUT entries available
 *    (x0 cursor background, xf cursor foreground, x2 overscan border color)
 * 0: Above not available -> standard vga
 */
#define ALLOW_ACCESS_TO_DAC_EXTENDED_COLORS 0x02
#define DAC_EXTENDED_COLOR_MASK             0x0f

static uint8_t
_cirrus_read_palette_data(struct cpssp *cpssp)
{
	uint8_t value;

	if (cpssp->ext_graph_curs_attr & ALLOW_ACCESS_TO_DAC_EXTENDED_COLORS) {
		switch (cpssp->vga.col_reg_03c7 & DAC_EXTENDED_COLOR_MASK) {
		case 0x00:      /* cursor background */
			value = video_col_get(cpssp, 256, cpssp->vga.col_reg_03c7_rgb)
				>> 2;
			break;
		case 0x0f:      /* cursor foreground */
			value = video_col_get(cpssp, 257, cpssp->vga.col_reg_03c7_rgb)
				>> 2;
			break;
		case 0x02:      /* overscan color */
			value = video_col_get(cpssp, 258, cpssp->vga.col_reg_03c7_rgb)
				>> 2;
			break;
		default:
			faum_log(FAUM_LOG_WARNING, __FUNCTION__, "",
				 "Read access to dac extended colors but "
				 "col_reg_03c7 is 0x%02hx\n",
				 (int) (cpssp->vga.col_reg_03c7 &
					DAC_EXTENDED_COLOR_MASK));
			value = 0x00;
		}
	} else {
		value = video_col_get(cpssp, cpssp->vga.col_reg_03c7,
					cpssp->vga.col_reg_03c7_rgb) >> 2;
	}

	cpssp->vga.col_reg_03c7_rgb++;
	if (cpssp->vga.col_reg_03c7_rgb == 3) {
		cpssp->vga.col_reg_03c7_rgb = 0;
		cpssp->vga.col_reg_03c7++;
	}

	return value;
}

static void
_cirrus_write_palette_data(struct cpssp *cpssp, uint8_t val)
{
	if (cpssp->ext_graph_curs_attr & ALLOW_ACCESS_TO_DAC_EXTENDED_COLORS) {
		switch (cpssp->vga.col_reg_03c8 & DAC_EXTENDED_COLOR_MASK) {
		case 0x00:
			video_col_set(cpssp, 256, cpssp->vga.col_reg_03c8_rgb, val << 2);
			break;
		case 0x0f:
			video_col_set(cpssp, 257, cpssp->vga.col_reg_03c8_rgb, val << 2);
			break;
		case 0x02:
			video_col_set(cpssp, 258, cpssp->vga.col_reg_03c8_rgb, val << 2);
			break;
		default:
			faum_log(FAUM_LOG_WARNING, __FUNCTION__, "",
				 "Write access to dac extended colors but "
				 "col_reg_03c8 is 0x%02hx\n",
				 (int) (cpssp->vga.col_reg_03c8 &
					DAC_EXTENDED_COLOR_MASK));
			break;
		}
	} else {
		video_col_set(cpssp, cpssp->vga.col_reg_03c8,
				cpssp->vga.col_reg_03c8_rgb, val << 2);
	}

	cpssp->vga.col_reg_03c8_rgb++;
	if (cpssp->vga.col_reg_03c8_rgb == 3) {
		cpssp->vga.col_reg_03c8_rgb = 0;
		cpssp->vga.col_reg_03c8++;
	}
}

#undef ALLOW_ACCESS_TO_DAC_EXTENDED_COLORS
#undef DAC_EXTENDED_COLOR_MASK

/*************************************************************************/
/*                                                                       */
/* Memory space access functions                                         */
/*                                                                       */
/*************************************************************************/

static uint8_t
COMP_(vga_inb)(struct cpssp *cpssp, uint16_t port)
{
	uint8_t value;

	switch (port) {
	case 0x3c4 - CIRRUS_IOPORT:
		/* sequencer index */
		/* support readback of cursor positions */
		switch (cpssp->vga.seq_reg_03c4) {
		case 0x10:      /* trm 8.7 */
		case 0x30:
		case 0x50:
		case 0xf0:
			value = cpssp->hw_cursor_x & 0x07;
			break;
		case 0x11:      /* trm 8.8 */
		case 0x31:
		case 0x51:
		case 0xf1:
			value = cpssp->hw_cursor_y & 0x07;
			break;
		default:
			value = cpssp->vga.seq_reg_03c4;
			break;
		}
		break;

	case 0x3c5 - CIRRUS_IOPORT:
		/* sequencer register */
		switch (cpssp->vga.seq_reg_03c4) {
		case 0x06:
			/* legacy unlock extensions */
			value = (cpssp->ext_key == 0x12) ? 0x12 : 0x0f;
			break;
		case 0x07:
			value = cpssp->ext_ext_sequencer_mode;
			break;
		case 0x08:
			switch ((cpssp->ext_ddc2b_eeprom_control >> 6) & 1) {
			case 0:
				/* EEPROM Mode */
				/* FIXME */
				value = cpssp->ext_ddc2b_eeprom_control;
				break;
			case 1:
				/* DDC Mode */
				value = cpssp->ext_ddc2b_eeprom_control;
				value |= cpssp->i2c_ddc_bus.clk << 2;
				value |= cpssp->i2c_ddc_bus.data << 7;
				break;
			default: assert(0);
			}
			break;
		case 0x09:
			value = cpssp->ext_scratch_pad_01[0];
#if (1 < CIRRUS_DEBUG)
			faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
					"scratch pad 0 = 0x%02x\n",
					value);
#endif
			break;
		case 0x0a:
			value = cpssp->ext_scratch_pad_01[1];
#if (1 < CIRRUS_DEBUG)
			faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
					"scratch pad 1 = 0x%02x\n",
					value);
#endif
			break;
		case 0x0b:
			value = cpssp->ext_vclk_numerator[0];
			break;
		case 0x0c:
			value = cpssp->ext_vclk_numerator[1];
			break;
		case 0x0d:
			value = cpssp->ext_vclk_numerator[2];
			break;
		case 0x0e:
			value = cpssp->ext_vclk_numerator[3];
			break;
		case 0x0f:
			value = cpssp->ext_dram_control
				| cpssp->dram_data_bus_width << 3
				| cpssp->dram_bank_switch_control << 7;
			break;
		case 0x10:
		case 0x30:
		case 0x50:
		case 0x70:
		case 0x90:
		case 0xb0:
		case 0xd0:
		case 0xf0:
			value = cpssp->ext_graph_curs_x_pos;
			break;
		case 0x11:
		case 0x31:
		case 0x51:
		case 0x71:
		case 0x91:
		case 0xb1:
		case 0xd1:
		case 0xf1:
			value = cpssp->ext_graph_curs_y_pos;
			break;
		case 0x12:
			value = cpssp->ext_graph_curs_attr;
			break;
		case 0x13:
			value = cpssp->ext_graph_curs_pattern_addr_offset;
			break;
		case 0x14:
			value = cpssp->ext_scratch_pad_23[0];
#if (1 < CIRRUS_DEBUG)
			faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
					"scratch pad 2 = 0x%02x\n",
					value);
#endif
			break;
		case 0x15:
			value = cpssp->ext_scratch_pad_23[1];
#if (1 < CIRRUS_DEBUG)
			faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
					"scratch pad 3 = 0x%02x\n",
					value);
#endif
			break;
		case 0x16:
			value = cpssp->ext_display_fifo_threshold_control;
			break;
		case 0x17:
			value = cpssp->ext_config_rb_ext_control
				| cpssp->dram_bank_size << 7
				| cpssp->dram_bank_swap << 1;
			break;
		case 0x18:
			value = cpssp->ext_sig_gen_control;
			break;
		case 0x19:
			value = cpssp->ext_sig_gen_result_lb;
			break;
		case 0x1a:
			value = cpssp->ext_sig_gen_result_hb;
			break;
		case 0x1b:
			value = cpssp->ext_vclk_denominator[0];
			break;
		case 0x1c:
			value = cpssp->ext_vclk_denominator[1];
			break;
		case 0x1d:
			value = cpssp->ext_vclk_denominator[2];
			break;
		case 0x1e:
			value = cpssp->ext_vclk_denominator[3];
			break;
		case 0x1f:
			value = cpssp->ext_mclk_select;
			break;
		default:
			goto umvga_handled;
		}
		break;

	case 0x3c6 - CIRRUS_IOPORT:
		/* pixel mask & hidden dac */
		value = _cirrus_read_hidden_dac(cpssp);
		break;

	case 0x3c9 - CIRRUS_IOPORT:
		/* palette data */
		value = _cirrus_read_palette_data(cpssp);
		break;

	case 0x3cf - CIRRUS_IOPORT:
		/* graphics register */
		switch (cpssp->vga.gr_reg_03ce) {
		case 0x00:
			value = cpssp->shadow_gr0;
			break;
		case 0x01:
			value = cpssp->shadow_gr1;
			break;
		case 0x09:
			value = cpssp->ext_offset_reg_0;
			break;
		case 0x0a:
			value = cpssp->ext_offset_reg_1;
			break;
		case 0x0b:
			value = cpssp->ext_graph_contr_mode_ext;
			break;
		case 0x0c:
			value = cpssp->ext_color_chroma_key_comp;
			break;
		case 0x0d:
			value = cpssp->ext_color_mask_chroma_key;
			break;
		case 0x0e:
			value = cpssp->ext_power_management;
			break;
		case 0x10:
			value = cpssp->blt_color_exp_fg_bg[0];
			break;
		case 0x11:
			value = cpssp->blt_color_exp_fg_bg[1];
			break;
		case 0x12:
			value = cpssp->blt_color_exp_fg_bg[2];
			break;
		case 0x13:
			value = cpssp->blt_color_exp_fg_bg[3];
			break;
		case 0x14:
			value = cpssp->blt_color_exp_fg_bg[4];
			break;
		case 0x15:
			value = cpssp->blt_color_exp_fg_bg[5];
			break;
		case 0x16:
			value = cpssp->y & 0x00ff;
			break;
		case 0x17:
			value = cpssp->ext_act_disp_line_rb_1
				| ((cpssp->y & 0x0300) >> 8);
			break;
		case 0x18:
			value = cpssp->ext_ext_dram_controls;
			break;
		case 0x19:
			value = cpssp->ext_gpio_port_config;
			break;
		case 0x1a:
			value = cpssp->ext_scratch_pad_45[0];
#if (1 < CIRRUS_DEBUG)
			faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
					"scratch pad 4 = 0x%02x\n",
					value);
#endif
			break;
		case 0x1b:
			value = cpssp->ext_scratch_pad_45[1];
#if (1 < CIRRUS_DEBUG)
			faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
					"scratch pad 5 = 0x%02x\n",
					value);
#endif
			break;
		case 0x20:
			value = cpssp->blt_width[0];
			break;
		case 0x21:
			value = cpssp->blt_width[1];
			break;
		case 0x22:
			value = cpssp->blt_height[0];
			break;
		case 0x23:
			value = cpssp->blt_height[1];
			break;
		case 0x24:
			value = cpssp->blt_dest_pitch[0];
			break;
		case 0x25:
			value = cpssp->blt_dest_pitch[1];
			break;
		case 0x26:
			value = cpssp->blt_source_pitch[0];
			break;
		case 0x27:
			value = cpssp->blt_source_pitch[1];
			break;
		case 0x28:
			value = cpssp->blt_dest_start[0];
			break;
		case 0x29:
			value = cpssp->blt_dest_start[1];
			break;
		case 0x2a:
			value = cpssp->blt_dest_start[2];
			break;
		case 0x2c:
			value = cpssp->blt_source_start[0];
			break;
		case 0x2d:
			value = cpssp->blt_source_start[1];
			break;
		case 0x2e:
			value = cpssp->blt_source_start[2];
			break;
		case 0x2f:
			value = cpssp->blt_dest_left_side_clipping;
			break;
		case 0x30:
			value = cpssp->blt_mode;
			break;
		case 0x31:
			value = cpssp->blt_start_status;
			break;
		case 0x32:
			value = cpssp->blt_rop;
			break;
		case 0x33:
			value = cpssp->blt_mode_extensions;
			break;
		case 0x34:
			value = cpssp->blt_transp_blt_key_color[0];
			break;
		case 0x35:
			value = cpssp->blt_transp_blt_key_color[1];
			break;
		default:
			goto umvga_handled;
		}
		break;

	case 0x3d5 - CIRRUS_IOPORT:
		/* crtc register */
		switch (cpssp->vga.crt_reg_03d4) {
		case 0x19:
			value = cpssp->ext_interlace_end;
			break;
		case 0x1a:
			value = cpssp->ext_misc_control;
			break;
		case 0x1b:
			value = cpssp->ext_ext_disp_controls;
			break;
		case 0x1c:
			value = cpssp->ext_sync_adjust_genlock;
			break;
		case 0x1d:
			value = cpssp->ext_overlay_ext_control;
			break;
		case 0x22:
			/* FIXME graphics data latches readback */
			faum_log(FAUM_LOG_WARNING, __FUNCTION__, "",
				 "CR22 read unimplemented :-(\n");
			value = 0xff;
			break;
		case 0x24:
			/* state=0 -> index write; state=1 -> data write */
			value = cpssp->vga.attr_reg_03c0_state << 7;
			break;
		case 0x25:
			value = cpssp->ext_part_status;
			break;
		case 0x26:
			/* attribute controller index readback */
			value = cpssp->vga.attr_reg_03c0 & 0x1f;
			break;
		case 0x27:
			value = cpssp->ext_id;
			break;
		default:
			goto umvga_handled;
		}
		break;

	default:
	umvga_handled:
		value = vga__inb(cpssp, port);
	}

	return value;
}

static void
COMP_(vga_outb)(
	struct cpssp *cpssp,
	uint16_t port,
	uint8_t val
)
{
	switch (port) {
	case 0x3c5 - CIRRUS_IOPORT:
		/* sequencer register */
		switch (cpssp->vga.seq_reg_03c4) {
		case 0x06:
			cpssp->ext_key = val;
			break;
		case 0x07:
			if (val & 0x01) {
#if (0 < CIRRUS_DEBUG)
				if (cpssp->mode == VGA)
					faum_log(FAUM_LOG_INFO, __FUNCTION__,
						"", "Switching to CIRRUS "
						"mode: SR7 <- 0x%02hx\n", val);
#endif
				cpssp->mode = CIRRUS;
				COMP_(flush)(cpssp);
			} else {
#if (0 < CIRRUS_DEBUG)
				if (cpssp->mode == CIRRUS)
					faum_log(FAUM_LOG_INFO, __FUNCTION__,
						"", "Switching to VGA "
						"mode: SR7 <- 0x%02hx\n", val);
#endif
				cpssp->mode = VGA;
			}
			if ((val & 0x0f) == 0x03) {
				/* this is a hack to make switching back to
				   console for X's cirrus driver work */
				/* clock doubled 8bpp, see 9.2 in trm */
				if (cpssp->mode == CIRRUS)
					faum_log(FAUM_LOG_WARNING, __FUNCTION__,
						"",
						"Clock-doubled 8bpp, switching "
						" back to VGA legacy mode.\n");
				cpssp->mode = VGA;
				COMP_(flush)(cpssp);
			}
			cpssp->ext_ext_sequencer_mode = val;
			break;
		case 0x08:
			val &= ~(1 << 2); /* Read-only */
			val &= ~(1 << 7); /* Read-only */
			cpssp->ext_ddc2b_eeprom_control = val;

			if ((val >> 6) & 1) {
				/* ddc mode */
				bool data;
				bool clock;

				data = (val >> 1) & 1;
				clock = (val >> 0) & 1;

				/* propagate to i2c/ddc bus */
				sig_i2c_bus_set_data(cpssp->port_ddc, cpssp, data);
				sig_i2c_bus_set_clk(cpssp->port_ddc, cpssp, clock);
			}
			break;
		case 0x09:
			cpssp->ext_scratch_pad_01[0] = val;
#if (1 < CIRRUS_DEBUG)
			faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
					"scratch pad 0 = 0x%02x\n",
					val);
#endif
			break;
		case 0x0a:
			cpssp->ext_scratch_pad_01[1] = val;
#if (1 < CIRRUS_DEBUG)
			faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
					"scratch pad 1 = 0x%02x\n",
					val);
#endif
			break;
		case 0x0b:
			cpssp->ext_vclk_numerator[0] = val;
			break;
		case 0x0c:
			cpssp->ext_vclk_numerator[1] = val;
			break;
		case 0x0d:
			cpssp->ext_vclk_numerator[2] = val;
			break;
		case 0x0e:
			cpssp->ext_vclk_numerator[3] = val;
			break;
		case 0x0f:
			cpssp->ext_dram_control = val & 0x67;
			cpssp->dram_data_bus_width = (val >> 3) & 0b11;
			cpssp->dram_bank_switch_control = (val >> 7) & 0b1;
			break;
		case 0x10:
		case 0x30:
		case 0x50:
		case 0x70:
		case 0x90:
		case 0xb0:
		case 0xd0:
		case 0xf0:
			cpssp->ext_graph_curs_x_pos = val;
			cpssp->hw_cursor_x =
				(val << 3) | (cpssp->vga.seq_reg_03c4 >> 5);
			break;
		case 0x11:
		case 0x31:
		case 0x51:
		case 0x71:
		case 0x91:
		case 0xb1:
		case 0xd1:
		case 0xf1:
			cpssp->ext_graph_curs_y_pos = val;
			cpssp->hw_cursor_y =
				(val << 3) | (cpssp->vga.seq_reg_03c4 >> 5);
			break;
		case 0x12:
			cpssp->ext_graph_curs_attr = val;
			break;
		case 0x13:
			cpssp->ext_graph_curs_pattern_addr_offset = val;
			break;
		case 0x14:
			cpssp->ext_scratch_pad_23[0] = val;
#if (1 < CIRRUS_DEBUG)
			faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
					"scratch pad 2 = 0x%02x\n",
					val);
#endif
			break;
		case 0x15:
			cpssp->ext_scratch_pad_23[1] = val;
#if (1 < CIRRUS_DEBUG)
			faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
					"scratch pad 3 = 0x%02x\n",
					val);
#endif
			break;
		case 0x16:
			cpssp->ext_display_fifo_threshold_control = val;
			break;
		case 0x17:
			cpssp->ext_config_rb_ext_control =
				(cpssp->ext_config_rb_ext_control & 0x38)
				| (val & 0x4c);
			cpssp->dram_bank_size = (val >> 7) & 0b1;
			cpssp->dram_bank_swap = (val >> 1) & 0b1;
			break;
		case 0x18:
			cpssp->ext_sig_gen_control = val;
			break;
		case 0x19:
			cpssp->ext_sig_gen_result_lb = val;
			break;
		case 0x1a:
			cpssp->ext_sig_gen_result_hb = val;
			break;
		case 0x1b:
			cpssp->ext_vclk_denominator[0] = val;
			break;
		case 0x1c:
			cpssp->ext_vclk_denominator[1] = val;
			break;
		case 0x1d:
			cpssp->ext_vclk_denominator[2] = val;
			break;
		case 0x1e:
			cpssp->ext_vclk_denominator[3] = val;
			break;
		case 0x1f:
			cpssp->ext_mclk_select = val;
			break;
		default:
			goto umvga_handled;
		}
		break;

	case 0x3c6 - CIRRUS_IOPORT:
		/* pixel mask & hidden dac */
		_cirrus_write_hidden_dac(cpssp, val);
		break;

	case 0x3c9 - CIRRUS_IOPORT:
		/* palette data */
		_cirrus_write_palette_data(cpssp, val);
		break;

	case 0x3cf - CIRRUS_IOPORT:
		/* graphics register */
		/* If you change the bitblt register writes don't forget
		   COMP_(mmio_bitblt_write)(). */
		switch (cpssp->vga.gr_reg_03ce) {
		case 0x00:
			cpssp->shadow_gr0 = val;
			goto umvga_handled;
		case 0x01:
			cpssp->shadow_gr1 = val;
			goto umvga_handled;
		case 0x05:
			cpssp->vga.gr_mode = val & 0x7f;
			break;
		case 0x09:
			cpssp->ext_offset_reg_0 = val;
			break;
		case 0x0a:
			cpssp->ext_offset_reg_1 = val;
			break;
		case 0x0b:
			if ((cpssp->ext_graph_contr_mode_ext & 0x04)
			 && (! (val & 0x04))) {
				/* side-effect see trm 8.21 */
				cpssp->vga.gr_set_reset &= 0x0f;
				cpssp->vga.gr_enable_set_reset &= 0x0f;
			}
			cpssp->ext_graph_contr_mode_ext = val;
			break;
		case 0x0c:
			cpssp->ext_color_chroma_key_comp = val;
			break;
		case 0x0d:
			cpssp->ext_color_mask_chroma_key = val;
			break;
		case 0x0e:
			cpssp->ext_power_management = val;
			break;
		case 0x10:
			cpssp->blt_color_exp_fg_bg[0] = val;
			break;
		case 0x11:
			cpssp->blt_color_exp_fg_bg[1] = val;
			break;
		case 0x12:
			cpssp->blt_color_exp_fg_bg[2] = val;
			break;
		case 0x13:
			cpssp->blt_color_exp_fg_bg[3] = val;
			break;
		case 0x14:
			cpssp->blt_color_exp_fg_bg[4] = val;
			break;
		case 0x15:
			cpssp->blt_color_exp_fg_bg[5] = val;
			break;
		case 0x16:
			/* readonly */
			break;
		case 0x17:
			/* all bits except 6,5,2 are readonly */
			cpssp->ext_act_disp_line_rb_1 = val & 0x64;
			break;
		case 0x18:
			cpssp->ext_ext_dram_controls = val;
			break;
		case 0x19:
			cpssp->ext_gpio_port_config = val;
			break;
		case 0x1a:
			cpssp->ext_scratch_pad_45[0] = val;
#if (1 < CIRRUS_DEBUG)
			faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
					"scratch pad 4 = 0x%02x\n",
					val);
#endif
			break;
		case 0x1b:
			cpssp->ext_scratch_pad_45[1] = val;
#if (1 < CIRRUS_DEBUG)
			faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
					"scratch pad 5 = 0x%02x\n",
					val);
#endif
			break;
		case 0x20:
			cpssp->blt_width[0] = val;
			break;
		case 0x21:
			cpssp->blt_width[1] = val & 0x1f;
			break;
		case 0x22:
			cpssp->blt_height[0] = val;
			break;
		case 0x23:
			cpssp->blt_height[1] = val & 0x07;
			break;
		case 0x24:
			cpssp->blt_dest_pitch[0] = val;
			break;
		case 0x25:
			cpssp->blt_dest_pitch[1] = val & 0x1f;
			break;
		case 0x26:
			cpssp->blt_source_pitch[0] = val;
			break;
		case 0x27:
			cpssp->blt_source_pitch[1] = val & 0x1f;
			break;
		case 0x28:
			cpssp->blt_dest_start[0] = val;
			break;
		case 0x29:
			cpssp->blt_dest_start[1] = val;
			break;
		case 0x2a:
			cpssp->blt_dest_start[2] = val & 0x3f;
			if (cpssp->blt_start_status & BITBLT_AUTOSTART) {
#ifdef CIRRUS_DEBUG_BITBLT
				faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
					 "Wrote GR2A. Autostarting BitBLT...\n");
#endif

				/* no need to set GR31[4], since the following,
				   when it returns, already buffered the
				   bitblt registers */
				_cirrus_bitblt_start(cpssp);
			}
			break;
		case 0x2c:
			cpssp->blt_source_start[0] = val;
			break;
		case 0x2d:
			cpssp->blt_source_start[1] = val;
			break;
		case 0x2e:
			cpssp->blt_source_start[2] = val & 0x3f;
			break;
		case 0x2f:
			cpssp->blt_dest_left_side_clipping = val;
			break;
		case 0x30:
			cpssp->blt_mode = val;
			break;
		case 0x31:
			_cirrus_write_blt_start(cpssp, val);
			break;
		case 0x32:
			cpssp->blt_rop = val;
			break;
		case 0x33:
			if ((cpssp->ext_power_management & 0x20)
			 || (cpssp->blt_start_status & 0x80)) {
				cpssp->blt_mode_extensions = val;
			} else {
#ifdef CIRRUS_DEBUG_BITBLT
				faum_log(FAUM_LOG_WARNING, __FUNCTION__, "",
					 "GR33 is write protected\n");
#endif
			}
			break;
		case 0x34:
			cpssp->blt_transp_blt_key_color[0] = val;
			break;
		case 0x35:
			cpssp->blt_transp_blt_key_color[1] = val;
			break;
		default:
			goto umvga_handled;
		}
		break;

	case 0x3d5 - CIRRUS_IOPORT:
		/* crtc register */
		switch (cpssp->vga.crt_reg_03d4) {
		case 0x19:
			cpssp->ext_interlace_end = val;
			break;
		case 0x1a:
			cpssp->ext_misc_control = val;
			break;
		case 0x1b:
			cpssp->ext_ext_disp_controls = val;
			break;
		case 0x1c:
			cpssp->ext_sync_adjust_genlock = val;
			break;
		case 0x1d:
			cpssp->ext_overlay_ext_control = val;
			break;
		case 0x22:
		case 0x24:
		case 0x25:
		case 0x26:
		case 0x27:
			/* these are all readonly registers */
			goto outb_readonly;
		default:
			goto umvga_handled;
		}
		break;

	outb_readonly:
#if (0 < CIRRUS_DEBUG)
		faum_log(FAUM_LOG_WARNING, __FUNCTION__, "", "Readonly port "
			 "write attempt (port=0x%04hx val=0x%02hx)\n",
			 port, val);
#endif
		break;

	default:
	umvga_handled:
		vga__outb(cpssp, val, port);
	}
}

static int
COMP_(ior)(
	void *_cpssp,
	uint32_t port,
	unsigned int bs,
	uint32_t *valp
)
{
	struct cpssp *cpssp = (struct cpssp *) _cpssp;

	if (! cpssp->io_space
	 || (port & ~(0x20 - 1)) != CIRRUS_IOPORT) {
		return 1;
	}

	port &= 0x20 - 1;

	*valp = 0x00000000;
	if ((bs >> 0) & 1) {
		*valp |= COMP_(vga_inb)(cpssp, port + 0) << 0;
	}
	if ((bs >> 1) & 1) {
		*valp |= COMP_(vga_inb)(cpssp, port + 1) << 8;
	}
	if ((bs >> 2) & 1) {
		*valp |= COMP_(vga_inb)(cpssp, port + 2) << 16;
	}
	if ((bs >> 3) & 1) {
		*valp |= COMP_(vga_inb)(cpssp, port + 3) << 24;
	}

	return 0;
}

static int
COMP_(iow)(
	void *_cpssp,
	uint32_t port,
	unsigned int bs,
	uint32_t val
)
{
	struct cpssp *cpssp = (struct cpssp *) _cpssp;

	if (! cpssp->io_space
	 || (port & ~(0x20 - 1)) != CIRRUS_IOPORT) {
		return 1;
	}

	if (port == 0x3dc
	 && bs == 0xc) {
		/*
		 * This is a hack.
		 * If you kill f.e. Xvesa you'd be stuck in an
		 * extended Cirrus mode. There are no VESA
		 * Function 02 calls or so to get you back to a VGA
		 * mode. Instead you seem to get:
		 * outb(0x03, 0x3de) seems to be legacy mode
		 * outb(0x00, 0x3df) ???
		 */
		val >>= 16;

		if (val < 0x100) {
#if (0 < CIRRUS_DEBUG)
			faum_log(FAUM_LOG_INFO, __FUNCTION__, "",
				 "Switching (back) to VGA mode "
				 "(val=0x%04hx).\n", val);
#endif
			cpssp->mode                     = VGA;
			COMP_(flush)(cpssp);
			cpssp->ext_ext_sequencer_mode   = val;
			cpssp->ext_offset_reg_0         = 0;
			cpssp->ext_offset_reg_1         = 0;
			cpssp->ext_graph_contr_mode_ext = 0;
			cpssp->ext_misc_control         = 0;
			cpssp->ext_ext_disp_controls    = 0;
			cpssp->ext_overlay_ext_control  = 0;
		} else {
#if (0 < CIRRUS_DEBUG)
			faum_log(FAUM_LOG_INFO, __FUNCTION__, "",
				 "NOT switching (back) to VGA mode "
				 "(val=0x%04hx).\n", val);
#endif
		}
		return 0;
	}

	port &= 0x20 - 1;

	if ((bs >> 0) & 1) {
		COMP_(vga_outb)(cpssp, port + 0, (val >> 0) & 0xff);
	}
	if ((bs >> 1) & 1) {
		COMP_(vga_outb)(cpssp, port + 1, (val >> 8) & 0xff);
	}
	if ((bs >> 2) & 1) {
		COMP_(vga_outb)(cpssp, port + 2, (val >> 16) & 0xff);
	}
	if ((bs >> 3) & 1) {
		COMP_(vga_outb)(cpssp, port + 3, (val >> 24) & 0xff);
	}

	return 0;
}

static void
COMP_(mmio_vga_mr)(
	struct cpssp *cpssp,
	uint32_t addr,
	unsigned int bs,
	uint32_t *valp
)
{
	addr &= 0x20 - 1;
	if ((bs >> 0) & 1) {
		*valp |= COMP_(vga_inb)(cpssp, addr + 0) << 0;
	}
	if ((bs >> 1) & 1) {
		*valp |= COMP_(vga_inb)(cpssp, addr + 1) << 8;
	}
	if ((bs >> 2) & 1) {
		*valp |= COMP_(vga_inb)(cpssp, addr + 2) << 16;
	}
	if ((bs >> 3) & 1) {
		*valp |= COMP_(vga_inb)(cpssp, addr + 3) << 24;
	}
}

static void
COMP_(mmio_vga_mw)(
	struct cpssp *cpssp,
	uint32_t addr,
	unsigned int bs,
	uint32_t val
)
{
	addr &= 0x20 - 1;
	if ((bs >> 0) & 1) {
		COMP_(vga_outb)(cpssp, addr + 0, (val >> 0) & 0xff);
	}
	if ((bs >> 1) & 1) {
		COMP_(vga_outb)(cpssp, addr + 1, (val >> 8) & 0xff);
	}
	if ((bs >> 2) & 1) {
		COMP_(vga_outb)(cpssp, addr + 2, (val >> 16) & 0xff);
	}
	if ((bs >> 3) & 1) {
		COMP_(vga_outb)(cpssp, addr + 3, (val >> 24) & 0xff);
	}
}

static void
COMP_(mmio_bitblt_mr)(
	struct cpssp *cpssp,
	uint32_t addr,
	unsigned int bs,
	uint32_t *valp
)
{
	addr &= 0xff;

	/*
	 * See table 9-4 in trm.
	 */
	*valp = 0;
	switch (addr | 0x100) {
	case 0x100:
		/* GR0 */
		*valp |= cpssp->shadow_gr0 << 0;
		/* GR10 */
		*valp |= cpssp->blt_color_exp_fg_bg[0] << 8;
		/* GR12 */
		*valp |= cpssp->blt_color_exp_fg_bg[2] << 16;
		/* GR14 */
		*valp |= cpssp->blt_color_exp_fg_bg[4] << 24;
		break;
	case 0x104:
		/* GR1 */
		*valp |= cpssp->shadow_gr1 << 0;
		/* GR11 */
		*valp |= cpssp->blt_color_exp_fg_bg[1] << 8;
		/* GR13 */
		*valp |= cpssp->blt_color_exp_fg_bg[3] << 16;
		/* GR15 */
		*valp |= cpssp->blt_color_exp_fg_bg[5] << 24;
		break;
	case 0x108:
		/* GR20 */
		*valp |= cpssp->blt_width[0] << 0;
		/* GR21 */
		*valp |= cpssp->blt_width[1] << 8;
		/* GR22 */
		*valp |= cpssp->blt_height[0] << 16;
		/* GR23 */
		*valp |= cpssp->blt_height[1] << 24;
		break;
	case 0x10C:
		/* GR24 */
		*valp |= cpssp->blt_dest_pitch[0] << 0;
		/* GR25 */
		*valp |= cpssp->blt_dest_pitch[1] << 8;
		/* GR26 */
		*valp |= cpssp->blt_source_pitch[0] << 16;
		/* GR27 */
		*valp |= cpssp->blt_source_pitch[1] << 24;
		break;
	case 0x110:
		/* GR28 */
		*valp |= cpssp->blt_dest_start[0] << 0;
		/* GR29 */
		*valp |= cpssp->blt_dest_start[1] << 8;
		/* GR2A */
		*valp |= cpssp->blt_dest_start[2] << 16;
		/* GR2B */
		/* Reserved */
		break;
	case 0x114:
		/* GR2C */
		*valp |= cpssp->blt_source_start[0] << 0;
		/* GR2D */
		*valp |= cpssp->blt_source_start[1] << 8;
		/* GR2E */
		*valp |= cpssp->blt_source_start[2] << 16;
		/* GR2F */
		*valp |= cpssp->blt_dest_left_side_clipping << 24;
		break;
	case 0x118:
		/* GR30 */
		*valp |= cpssp->blt_mode << 0;
		/* GR31 */
		/* Reserved */
		/* GR32 */
		*valp |= cpssp->blt_rop << 16;
		/* GR33 */
		*valp |= cpssp->blt_mode_extensions << 24;
		break;
	case 0x11C:
		/* GR34 */
		*valp |= cpssp->blt_transp_blt_key_color[0] << 0;
		/* GR35 */
		*valp |= cpssp->blt_transp_blt_key_color[1] << 8;
		/* Reserved */
		/* Reserved */
		break;
	case 0x120 ... 0x13c:
		/* Reserved */
		/* Reserved */
		/* Reserved */
		/* Reserved */
		break;
	case 0x140:
		/* GR31 */
		*valp |= cpssp->blt_start_status << 0;
		/* Reserved */
		/* Reserved */
		/* Reserved */
		break;
	case 0x144 ... 0x1fc:
		/* Reserved */
		/* Reserved */
		/* Reserved */
		/* Reserved */
		break;
	default:
		assert(0); /* Cannot happen. */
	};
}

static void
COMP_(mmio_bitblt_mw)(
	struct cpssp *cpssp,
	uint32_t addr,
	unsigned int bs,
	uint32_t val
)
{
	addr &= 0xff;

	switch (addr | 0x100) {
	case 0x100:
		if ((bs >> 0) & 1) {
			/* GR0 */
			cpssp->shadow_gr0 = (val >> 0) & 0xff;
		}
		if ((bs >> 1) & 1) {
			/* GR10 */
			cpssp->blt_color_exp_fg_bg[0] = (val >> 8) & 0xff;
		}
		if ((bs >> 2) & 1) {
			/* GR12 */
			cpssp->blt_color_exp_fg_bg[2] = (val >> 16) & 0xff;
		}
		if ((bs >> 3) & 1) {
			/* GR14 */
			cpssp->blt_color_exp_fg_bg[4] = (val >> 24) & 0xff;
		}
		break;
	case 0x104:
		if ((bs >> 0) & 1) {
			/* GR1 */
			cpssp->shadow_gr1 = (val >> 0) & 0xff;
		}
		if ((bs >> 1) & 1) {
			/* GR11 */
			cpssp->blt_color_exp_fg_bg[1] = (val >> 8) & 0xff;
		}
		if ((bs >> 2) & 1) {
			/* GR13 */
			cpssp->blt_color_exp_fg_bg[3] = (val >> 16) & 0xff;
		}
		if ((bs >> 3) & 1) {
			/* GR15 */
			cpssp->blt_color_exp_fg_bg[5] = (val >> 24) & 0xff;
		}
		break;
	case 0x108:
		if ((bs >> 0) & 1) {
			/* GR20 */
			cpssp->blt_width[0] = (val >> 0) & 0xff;
		}
		if ((bs >> 1) & 1) {
			/* GR21 */
			cpssp->blt_width[1] = (val >> 8) & 0x1f;
		}
		if ((bs >> 2) & 1) {
			/* GR22 */
			cpssp->blt_height[0] = (val >> 16) & 0xff;
		}
		if ((bs >> 3) & 1) {
			/* GR23 */
			cpssp->blt_height[1] = (val >> 24) & 0x07;
		}
		break;
	case 0x10C:
		if ((bs >> 0) & 1) {
			/* GR24 */
			cpssp->blt_dest_pitch[0] = (val >> 0) & 0xff;
		}
		if ((bs >> 1) & 1) {
			/* GR25 */
			cpssp->blt_dest_pitch[1] = (val >> 8) & 0x1f;
		}
		if ((bs >> 2) & 1) {
			/* GR26 */
			cpssp->blt_source_pitch[0] = (val >> 16) & 0xff;
		}
		if ((bs >> 3) & 1) {
			/* GR27 */
			cpssp->blt_source_pitch[1] = (val >> 24) & 0x1f;
		}
		break;
	case 0x110:
		if ((bs >> 0) & 1) {
			/* GR28 */
			cpssp->blt_dest_start[0] = (val >> 0) & 0xff;
		}
		if ((bs >> 1) & 1) {
			/* GR29 */
			cpssp->blt_dest_start[1] = (val >> 8) & 0xff;
		}
		if ((bs >> 2) & 1) {
			/* GR2A */
			cpssp->blt_dest_start[2] = (val >> 16) & 0x3f;
			if (cpssp->blt_start_status & BITBLT_AUTOSTART) {
				_cirrus_bitblt_start(cpssp);
			}
		}
		if ((bs >> 3) & 1) {
			/* GR2B */
			/* Reserved */
		}
		break;
	case 0x114:
		if ((bs >> 0) & 1) {
			/* GR2C */
			cpssp->blt_source_start[0] = (val >> 0) & 0xff;
		}
		if ((bs >> 1) & 1) {
			/* GR2D */
			cpssp->blt_source_start[1] = (val >> 8) & 0xff;
		}
		if ((bs >> 2) & 1) {
			/* GR2E */
			cpssp->blt_source_start[2] = (val >> 16) & 0x3f;
		}
		if ((bs >> 3) & 1) {
			/* GR2F */
			cpssp->blt_dest_left_side_clipping = (val >> 24) & 0xff;
		}
		break;
	case 0x118:
		if ((bs >> 0) & 1) {
			/* GR30 */
			cpssp->blt_mode = (val >> 0) & 0xff;
		}
		if ((bs >> 1) & 1) {
			/* Reserved */
		}
		if ((bs >> 2) & 1) {
			/* GR32 */
			cpssp->blt_rop = (val >> 16) & 0xff;
		}
		if ((bs >> 3) & 1) {
			/* GR33 */
			if ((cpssp->ext_power_management & 0x20)
			 || (cpssp->blt_start_status & 0x80)) {
				cpssp->blt_mode_extensions = (val >> 24) & 0xff;
			} else {
				fprintf(stderr, "%s: GR33 is write protected!\n",
						__FUNCTION__);
			}
		}
		break;
	case 0x11C:
		if ((bs >> 0) & 1) {
			/* GR34 */
			cpssp->blt_transp_blt_key_color[0] = (val >> 0) & 0xff;
		}
		if ((bs >> 1) & 1) {
			/* GR35 */
			cpssp->blt_transp_blt_key_color[1] = (val >> 8) & 0xff;
		}
		if ((bs >> 2) & 1) {
			/* Reserved */
		}
		if ((bs >> 3) & 1) {
			/* Reserved */
		}
		break;
	case 0x120 ... 0x13c:
		if ((bs >> 0) & 1) {
			/* Reserved */
		}
		if ((bs >> 1) & 1) {
			/* Reserved */
		}
		if ((bs >> 2) & 1) {
			/* Reserved */
		}
		if ((bs >> 3) & 1) {
			/* Reserved */
		}
		break;
	case 0x140:
		if ((bs >> 0) & 1) {
			/* GR31 */
			_cirrus_write_blt_start(cpssp, (val >> 0) & 0xff);
		}
		if ((bs >> 1) & 1) {
			/* Reserved */
		}
		if ((bs >> 2) & 1) {
			/* Reserved */
		}
		if ((bs >> 3) & 1) {
			/* Reserved */
		}
		break;
	case 0x144 ... 0x1fc:
		if ((bs >> 0) & 1) {
			/* Reserved */
		}
		if ((bs >> 1) & 1) {
			/* Reserved */
		}
		if ((bs >> 2) & 1) {
			/* Reserved */
		}
		if ((bs >> 3) & 1) {
			/* Reserved */
		}
		break;
	default:
		assert(0); /* Cannot happen. */
	}
}

static void
COMP_(linear_fb_mr)(
	struct cpssp *cpssp,
	uint32_t addr,
	unsigned int bs,
	uint32_t *valp
)
{
	uint8_t aperture;
	uint32_t val;

	aperture = (addr >> 22) & 0b111;
	addr &= (1 << 22) - 1;

	if ((cpssp->ext_ext_sequencer_mode & 0xf0) != 0
	 && (1 << 22) - 0x100 <= addr
	 && (cpssp->ext_config_rb_ext_control & 0x44) == 0x44) {
		/* Top 0x100 bytes are mapped to BitBlt registers. */
		COMP_(mmio_bitblt_mr)(cpssp, addr & (0x100 - 1), bs, valp);

	} else {
		switch (aperture) {
		case 0:
			/* No Swap */
			*valp = video_readl(cpssp, addr);
			break;
		case 1:
			/* Word Swap */
			val = video_readl(cpssp, addr);
			*valp = ((val << 8) & 0xff000000)
				| ((val >> 8) & 0x00ff0000)
				| ((val << 8) & 0x0000ff00)
				| ((val >> 8) & 0x000000ff);
			break;
		case 2:
			/* DWord Swap */
			val = video_readl(cpssp, addr);
			*valp = ((val << 24) & 0xff000000)
				| ((val << 8) & 0x00ff0000)
				| ((val >> 8) & 0x0000ff00)
				| ((val >> 24) & 0x000000ff);
			break;
		case 3:
			/* Video Aperture */
			fixme();
			break;
		case 4:
			/* No-Swap System-to-Screen BitBlt */
			COMP_(mmio_bitblt_mr)(cpssp, addr & (0x100 - 1), bs, valp);
			break;
		case 5:
			/* Word-Swap System-to-Screen BitBlt */
			COMP_(mmio_bitblt_mr)(cpssp, addr & (0x100 - 1), bs, &val);
			*valp = ((val << 8) & 0xff000000)
				| ((val >> 8) & 0x00ff0000)
				| ((val << 8) & 0x0000ff00)
				| ((val >> 8) & 0x000000ff);
			break;
		case 6:
			/* DWord-Swap System-to-Screen BitBlt */
			COMP_(mmio_bitblt_mr)(cpssp, addr & (0x100 - 1), bs, &val);
			*valp = ((val << 24) & 0xff000000)
				| ((val << 8) & 0x00ff0000)
				| ((val >> 8) & 0x0000ff00)
				| ((val >> 24) & 0x000000ff);
			break;
		case 7:
			/* Unused Aperture */
			*valp = 0; /* FIXME */
			fprintf(stderr, "WARNING: %s line %d: aperture=%d, addr=0x08%x\n",
					__FUNCTION__, __LINE__, aperture, addr);
			break;
		default:
			assert(0); /* Cannot happen. */
		}
	}
}

static void
COMP_(linear_fb_mw)(
	struct cpssp *cpssp,
	uint32_t addr,
	unsigned int bs,
	uint32_t val
)
{
	uint8_t aperture;

	if (unlikely(0 < cpssp->bitblt.src_counter
	 && !((cpssp->bitblt.mode & BITBLT_COLOR_EXPAND)
	  && (cpssp->blt_start_status & BITBLT_SYSTEM_SOURCE_LOCATION)))) {
		/* System-to-Screen BitBlt (Compatibility Mode) */
		goto bitblt_write;
	}

	aperture = (addr >> 22) & 0b111;
	addr &= (1 << 22) - 1;

	if ((cpssp->ext_ext_sequencer_mode & 0xf0) != 0
	 && (1 << 22) - 0x100 <= addr
	 && (cpssp->ext_config_rb_ext_control & 0x44) == 0x44) {
		COMP_(mmio_bitblt_mw)(cpssp, addr & (0x100 - 1), bs, val);

	} else {
		switch (aperture) {
		case 0:
			/* No-Swap */
		fb_write:;
			if ((bs >> 0) & 1) {
				video_writeb(cpssp, addr + 0, (val >> 0) & 0xff);
			}
			if ((bs >> 1) & 1) {
				video_writeb(cpssp, addr + 1, (val >> 8) & 0xff);
			}
			if ((bs >> 2) & 1) {
				video_writeb(cpssp, addr + 2, (val >> 16) & 0xff);
			}
			if ((bs >> 3) & 1) {
				video_writeb(cpssp, addr + 3, (val >> 24) & 0xff);
			}
			break;
		case 1:
			/* Word-Swap */
			val = ((val << 8) & 0xff000000)
				| ((val >> 8) & 0x00ff0000)
				| ((val << 8) & 0x0000ff00)
				| ((val >> 8) & 0x000000ff);
			goto fb_write;
		case 2:
			/* DWord-Swap */
			val = ((val << 24) & 0xff000000)
				| ((val << 8) & 0x00ff0000)
				| ((val >> 8) & 0x0000ff00)
				| ((val >> 24) & 0x000000ff);
			goto fb_write;
		case 3:
			/* Video Aperture */
			fixme();
			break;
		case 4:
			/* No-Swap System-to-Screen BitBlt */
		bitblt_write:;
			COMP_(fifo_put)(cpssp, val);
			if (cpssp->bitblt.line_pointer_end 
					<= cpssp->bitblt.line_pointer)
				_cirrus_bitblt_system_to_screen_next(cpssp);
			break;
		case 5:
			/* Word-Swap System-to-Screen BitBlt */
			val = ((val << 8) & 0xff000000)
				| ((val >> 8) & 0x00ff0000)
				| ((val << 8) & 0x0000ff00)
				| ((val >> 8) & 0x000000ff);
			goto bitblt_write;
		case 6:
			/* DWord-Swap System-to-Screen BitBlt */
			val = ((val << 24) & 0xff000000)
				| ((val << 8) & 0x00ff0000)
				| ((val >> 8) & 0x0000ff00)
				| ((val >> 24) & 0x000000ff);
			goto bitblt_write;
		case 7:
			/* Unused Aperture */
			break;
		default:
			assert(0);
		}
	}
}

static uint32_t 
_cirrus_vga_page_get_address(struct cpssp *cpssp, uint32_t cpu_addr)
{
	uint32_t address;

	if (!(cpssp->ext_graph_contr_mode_ext & 0x01)) {

		/* GRB[0] is 0 -> single page */

		if (!(cpssp->ext_graph_contr_mode_ext & 0x20)) {
			/* GRB[5] is 0 -> 4KB granularity */
			address = ((cpssp->ext_offset_reg_0 << 12) /* GR9 */
				   + cpu_addr)
				& 0xfffff; /* truncate to 20 bits */

		} else {
			/* GRB[5] is 1 -> 16KB granularity */
			address = ((cpssp->ext_offset_reg_0 << 14) /* GR9 */
				   + cpu_addr)
				& 0x3fffff; /* truncate to 22 bits */
		}
	} else {

		/* GRB[0] is 1 -> dual page */

		if (!(cpssp->ext_graph_contr_mode_ext & 0x20)) {
			/* GRB[5] is 0 -> 4KB granularity */

			if (!(cpu_addr >> 15)) {
				/* cpu_addr[15] is 0 -> offset register 0 */
				address = ((cpssp->ext_offset_reg_0 << 12) /* GR9 */
					   + (cpu_addr & 0x7fff)) /* cpu_addr[0:14] */
					& 0xfffff; /* truncate to 20 bits */

			} else {
				/* cpu_addr[15] is 1 -> offset register 1 */
				address = ((cpssp->ext_offset_reg_1 << 12) /* GRA */
					   + (cpu_addr & 0x7fff)) /* cpu_addr[0:14] */
					& 0xfffff; /* truncate to 20 bits */
			}

		} else {
			/* GRB[5] is 1 -> 16KB granularity */

			if (!(cpu_addr >> 15)) {
				/* cpu_addr[15] is 0 -> offset register 0 */
				address = ((cpssp->ext_offset_reg_0 << 14) /* GR9 */
					   + (cpu_addr & 0x7fff)) /* cpu_addr[0:14] */
					& 0x3fffff; /* truncate to 22 bits */

			} else {
				/* cpu_addr[15] is 1 -> offset register 1 */
				address = ((cpssp->ext_offset_reg_1 << 14) /* GRA */
					   + (cpu_addr & 0x7fff)) /* cpu_addr[0:14] */
					& 0x3fffff; /* truncate to 22 bits */
			}
		}
	}

	if ((cpssp->ext_graph_contr_mode_ext & 0x14) == 0x14) {

		/* GRB[2] and GRB[4] enabled -> BY16 addressing */
		address <<= 4;

	} else if (cpssp->ext_graph_contr_mode_ext & 0x02) {

		/* GRB[1] enabled -> BY8 addressing */
		address <<= 3;
	}

	address &= CIRRUS_MAX_VRAM_MASK;

	return address;
}

static uint8_t
COMP_(page_readb)(struct cpssp *cpssp, uint32_t offset)
{
	uint32_t address;

	address = _cirrus_vga_page_get_address(cpssp, offset);

#if (2 < CIRRUS_DEBUG)
	faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
		 "offset 0x%08lx == address 0x%08lx -> value 0x%02hx\n",
		 offset, address, video_readb(cpssp, address));
#endif

	return video_readb(cpssp, address);
}

static void
_cirrus_vga_by8_writeb(
	struct cpssp *cpssp,
	uint32_t offset,
	uint8_t val,
	int mode
)
{
	uint32_t to;
	int x;

#if (1 < CIRRUS_DEBUG)
	faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
		 "0x%08lx <- 0x%02hx mode=%d\n",
		 offset, val, mode);
#endif

	to = offset;

	for (x = 0; x < 8; x++) {
		if (val & 0x80) {
			video_writeb(cpssp, to, cpssp->shadow_gr1);
		} else if (mode == 5) {
			video_writeb(cpssp, to, cpssp->shadow_gr0);
		}

		val <<= 1;
		to++;
	}
}

static void
_cirrus_vga_by16_writeb(
	struct cpssp *cpssp,
	uint32_t offset,
	uint8_t val,
	int mode)
{
	uint32_t to;
	int x;

#if (1 < CIRRUS_DEBUG)
	faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
		 "0x%08lx <- 0x%02hx mode=%d\n",
		 offset, val, mode);
#endif

	to = offset;

	for (x = 0; x < 8; x++) {

		if (val & 0x80) {
			video_writeb(cpssp, to+0, cpssp->shadow_gr1);
			video_writeb(cpssp, to+1, cpssp->blt_color_exp_fg_bg[1]);
		} else if (mode == 5) {
			video_writeb(cpssp, to+0, cpssp->shadow_gr0);
			video_writeb(cpssp, to+1, cpssp->blt_color_exp_fg_bg[0]);
		}

		val <<= 1;
		to += 2;
	}
}

static void
COMP_(page_writeb)(
	struct cpssp *cpssp,
	uint32_t offset,
	uint8_t value
)
{
	uint32_t address;
	uint8_t mode;

	address = _cirrus_vga_page_get_address(cpssp, offset);
	mode = cpssp->vga.gr_mode & 0x07; /* GR5[write mode] */

	if (mode < 4
	 || 5 < mode
	 || (! (cpssp->ext_graph_contr_mode_ext & 0x04))) {
		/* GRB[2] is 0 -> Extended write modes not enabled */
#if (2 < CIRRUS_DEBUG)
		faum_log(FAUM_LOG_DEBUG, __FUNCTION__, "",
			 "offset 0x%08lx == address 0x%08lx <- value 0x%02hx\n",
			 offset, address, value);
#endif
		video_writeb(cpssp, address, value);

	} else {

		/* GR5[write mode] is 4 or 5 */
		if ((cpssp->ext_graph_contr_mode_ext & 0x14) != 0x14) {
			_cirrus_vga_by8_writeb(cpssp, address, value, mode);

		} else {
			_cirrus_vga_by16_writeb(cpssp, address, value, mode);
		}
	}
}

static void
COMP_(page_mr)(
	struct cpssp *cpssp,
	uint32_t addr,
	unsigned int bs,
	uint32_t *valp
)
{
	addr &= BANK_MEMORY_SIZE - 1;
	*valp = 0;
	if ((bs >> 0) & 1) {
		*valp |= COMP_(page_readb)(cpssp, addr + 0) << 0;
	}
	if ((bs >> 1) & 1) {
		*valp |= COMP_(page_readb)(cpssp, addr + 1) << 8;
	}
	if ((bs >> 2) & 1) {
		*valp |= COMP_(page_readb)(cpssp, addr + 2) << 16;
	}
	if ((bs >> 3) & 1) {
		*valp |= COMP_(page_readb)(cpssp, addr + 3) << 24;
	}
}

static void
COMP_(page_mw)(
	struct cpssp *cpssp,
	uint32_t addr,
	unsigned int bs,
	uint32_t val
)
{
	addr &= BANK_MEMORY_SIZE - 1;
	if ((bs >> 0) & 1) {
		COMP_(page_writeb)(cpssp, addr + 0, (val >> 0) & 0xff);
	}
	if ((bs >> 1) & 1) {
		COMP_(page_writeb)(cpssp, addr + 1, (val >> 8) & 0xff);
	}
	if ((bs >> 2) & 1) {
		COMP_(page_writeb)(cpssp, addr + 2, (val >> 16) & 0xff);
	}
	if ((bs >> 3) & 1) {
		COMP_(page_writeb)(cpssp, addr + 3, (val >> 24) & 0xff);
	}
}

static enum {
	REG_VGA,
	REG_DISPLAY,
	REG_BANK,
	REG_REGISTER_VGA,
	REG_REGISTER_BITBLT,
	REG_LEGACY,
	REG_BIOS,
} cirrus_vga_region(struct cpssp *cpssp, uint32_t addr)
{
	/*
	 * VGA display memory access?
	 */
	if ((addr & ~(0x20000 - 1)) == 0xa0000
	 && cpssp->mode == VGA) {
		return REG_VGA;
	}
	/*
	 * Display memory access?
	 */
	if (cpssp->memory_space
	 && (addr & ~(DISPLAY_MEMORY_SIZE - 1)) == cpssp->memory_address) {
		return REG_DISPLAY;
	}
	/*
         * Bank-based addressing (between 0xa0000 and 0xaffff)?
         */
        if ((addr & ~(BANK_MEMORY_SIZE - 1)) == BANK_MEMORY_ADDRESS
         && cpssp->mode == CIRRUS) {
		return REG_BANK;
	}
	/*
         * Register memory-mapped i/o access?
         */
        if (cpssp->memory_space
	 && (addr & ~(REGISTER_MEMORY_SIZE - 1)) == cpssp->register_address) {
		if (addr < cpssp->register_address + 0x20) {
			return REG_REGISTER_VGA;
		} else if (cpssp->register_address + 0x100 <= addr
			&& addr <= cpssp->register_address + 0x200) {
			return REG_REGISTER_BITBLT;
		} else {
			return -1;
		}
	}
	/*
         * Legacy memory-mapped i/o at 0xb8000?
         */
        if ((addr & ~(LEGACY_MMIO_SIZE - 1)) == LEGACY_MMIO_ADDRESS) {
		if (((cpssp->ext_ext_sequencer_mode & 0xf0) == 0
		  && (cpssp->ext_config_rb_ext_control & 0x04) == 0)
		 || ((cpssp->ext_ext_sequencer_mode & 0xf0)
		  && (cpssp->ext_config_rb_ext_control & 0x44) != 0x04)) {
			return -1;
		} else {
			return REG_LEGACY;
		}
	}
	/*
         * VGA BIOS access over PCI30 configured window?
         * Will be used during POST to copy rom to 0xc000
         */
        if (cpssp->rom_enabled
	 && (addr & ~(BIOS_MEMORY_SIZE - 1)) == cpssp->rom_address) {
		return REG_BIOS;
	}
	return -1;
}

static int
COMP_(mr)(void *_cpssp, uint32_t addr, unsigned int bs, uint32_t *valp)
{
	struct cpssp *cpssp = (struct cpssp *) _cpssp;
	int ret;

	switch (cirrus_vga_region(cpssp, addr)) {
	case REG_VGA: {
		uint16_t val0, val1;

		val0 = 0; val1 = 0;
		ret = vga_mr(cpssp, addr + 0, (bs >> 0) & 3, &val0)
		    | vga_mr(cpssp, addr + 2, (bs >> 2) & 3, &val1);
		*valp = (val1 << 16) | (val0 << 0);
		ret = 0;
		break;
	    }
	case REG_DISPLAY:
		COMP_(linear_fb_mr)(cpssp, addr, bs, valp);
		ret = 0;
		break;

	case REG_BANK:
		COMP_(page_mr)(cpssp, addr, bs, valp);
		ret = 0;
		break;

	case REG_REGISTER_VGA:
		COMP_(mmio_vga_mr)(cpssp, addr, bs, valp);
		ret = 0;
		break;

	case REG_REGISTER_BITBLT:
		COMP_(mmio_bitblt_mr)(cpssp, addr, bs, valp);
		ret = 0;
		break;

	case REG_LEGACY:
		COMP_(mmio_bitblt_mr)(cpssp, addr, bs, valp);
		ret = 0;
		break;

	case REG_BIOS:
		/* VGA BIOS is mapped directly */
		assert(0);
		ret = 1; /* Just to make gcc happy... */
		break;

	default:
		ret = 1;
		break;
	}
	return ret;
}

static int
COMP_(mw)(void *_cpssp, uint32_t addr, unsigned int bs, uint32_t val)
{
	struct cpssp *cpssp = (struct cpssp *) _cpssp;
	int ret;

	switch (cirrus_vga_region(cpssp, addr)) {
	case REG_VGA: {
		uint16_t val0, val1;

		val0 = (val >>  0) & 0xffff;
		val1 = (val >> 16) & 0xffff;
		ret = vga_mw(cpssp, addr + 0, (bs >> 0) & 3, val0)
		    | vga_mw(cpssp, addr + 2, (bs >> 2) & 3, val1);
		ret = 0;
		break;
	    }
	case REG_DISPLAY:
		COMP_(linear_fb_mw)(cpssp, addr, bs, val);
		ret = 0;
		break;

	case REG_BANK:
		COMP_(page_mw)(cpssp, addr, bs, val);
		ret = 0;
		break;

	case REG_REGISTER_VGA:
		COMP_(mmio_vga_mw)(cpssp, addr, bs, val);
		ret = 0;
		break;

	case REG_REGISTER_BITBLT:
		COMP_(mmio_bitblt_mw)(cpssp, addr, bs, val);
		ret = 0;
		break;

	case REG_LEGACY:
		COMP_(mmio_bitblt_mw)(cpssp, addr, bs, val);
		ret = 0;
		break;

	case REG_BIOS:
		/* VGA BIOS is mapped directly */
		assert(0);
		ret = 1; /* Just to make gcc happy... */
		break;

	default:
		ret = 1;
		break;
	}
	return ret;
}

static int
COMP_(map_r)(
	void *_cpssp,
	uint32_t addr,
	int (**cfp)(void *, uint32_t, unsigned int, uint32_t *),
	void **csp,
	char **haddrp
)
{
	struct cpssp *cpssp = _cpssp;

	switch (cirrus_vga_region(cpssp, addr)) {
	case REG_VGA:
		/* Split 32 bit access into 16 bit pieces. */
		/*FALLTHROUGH*/
	case REG_DISPLAY:
	case REG_BANK:
	case REG_REGISTER_VGA:
	case REG_REGISTER_BITBLT:
	case REG_LEGACY:
		*cfp = COMP_(mr);
		*csp = cpssp;
		*haddrp = NULL;
		return 0;

	case REG_BIOS:
		/* Map the BIOS directly */
		return sig_cs_map_r(cpssp->port_rom_cs, cpssp,
				addr & (BIOS_MEMORY_SIZE - 1),
				cfp, csp, haddrp);

	default:
		return 1;
	}
}

static int
COMP_(map_w)(
	void *_cpssp,
	uint32_t addr,
	int (**cfp)(void *, uint32_t, unsigned int, uint32_t),
	void **csp,
	char **haddrp
)
{
	struct cpssp *cpssp = _cpssp;

	switch (cirrus_vga_region(cpssp, addr)) {
	case REG_VGA:
		/* Split 32 bit access into 16 bit pieces. */
		/*FALLTHROUGH*/
	case REG_DISPLAY:
	case REG_BANK:
	case REG_REGISTER_VGA:
	case REG_REGISTER_BITBLT:
	case REG_LEGACY:
		*cfp = COMP_(mw);
		*csp = cpssp;
		*haddrp = NULL;
		return 0;

	case REG_BIOS:
		/* Map the BIOS directly */
		return sig_cs_map_w(cpssp->port_rom_cs, cpssp,
				addr & (BIOS_MEMORY_SIZE - 1),
				cfp, csp, haddrp);

	default:
		return 1;
	}
}

/*************************************************************************/
/*                                                                       */
/* PCI configuration space read and write functions                      */
/*                                                                       */
/*************************************************************************/

static int
COMP_(c0r)(
	void *_cpssp,
	uint32_t addr,
	unsigned int bs,
	uint32_t *valp
)
{
	struct cpssp *cpssp = (struct cpssp *) _cpssp;

	assert(! (addr & 3));

	if ((addr >> 8) & 7) {
		/* Only function 0 supported. */
		return 1;
	}
	addr &= 0xff;

	*valp = 0x00000000;
	switch (addr) {
	case 0x00:
		/* Vendor ID Register */
		*valp |= 0x1013 << 0;

		/* Product ID Register */
		*valp |= 0x00b8 << 16;
		break;

	case 0x04:
		/* Command Register */
		*valp |= cpssp->io_space << 0;
		*valp |= cpssp->memory_space << 1;
		*valp |= 0b000 << 2;
		*valp |= cpssp->dac_shadowing << 5;
		*valp |= 0b0000000000 << 6; /* Reserved */

		/* Status Register */
		*valp |= 0b000000000 << (16+0); /* Reserved */
		*valp |= 0b01 << (16+9); /* DEVSEL Timing */
		*valp |= 0b00000 << (16+11); /* Reserved */
		break;

	case 0x08:
		/* Revision ID Register */
		*valp |= 0x01 << 0;

		/* Class ID Register */
		*valp |= 0x00 << 8;
		*valp |= 0x00 << 16;
		*valp |= 0x03 << 24; /* VGA */
		break;

	case 0x0c:
		/* Cache Line Size Register */
		*valp |= 0x00 << 0; /* Reserved */

		/* Latency Timer Register */
		*valp |= 0x00 << 8; /* Reserved */

		/* Header Type Register */
		*valp |= 0x00 << 16; /* Reserved */

		/* BIST Register */
		*valp |= 0x00 << 24; /* Reserved */
		break;

	case 0x10:
		/* Base Address 0 Register (Linear Framebuffer) */
		*valp |= 0b0 << 0; /* Read-only */
		*valp |= 0x000000 << 1; /* Read-only */
		*valp |= cpssp->memory_address << (25-25);
		break;

	case 0x14:
		/* Base Address 1 Register (VGA/BitBlt Registers) */
		*valp |= 0b0 << 0; /* Read-only */
		*valp |= 0b00000000000 << 1; /* Read-only */
		*valp |= cpssp->register_address << (12-12);
		break;

	case 0x18:
		/* Base Address 2 Register (GPIO) */
		/*
		 * General purpose i/o is disabled (as if CF4 and CF8 were 1,
		 * see appendix B11 in trm).
		 * Bit 0 readonly one, bits 1-4 reserved zero,
		 * bits 5-31 readonly all zeros.
		 */
		*valp |= 0x00000000 << 0;
		break;

	case 0x1c ... 0x28:
		/* Reserved */
		*valp |= 0x00000000 << 0;
		break;

	case 0x2c:
		/* Subsystem Vendor ID Register */
		/*
		 * NOTE: in original cirrus cards/bioses, the subsystem
		 * vendor id is taken from the addresses 0x7ffc to 0x7ffd
		 * of the bios rom to make it customizable by vendors.
		 */
		*valp |= 0x0000 << 0;

		/* Subsystem Device ID Register */
		/*
		 * NOTE: in original cirrus cards/bioses, the subsystem id
		 * is taken from the address 0x7ffe of the bios rom
		 * and the pixel bus on reset to make it customizable by
		 * vendors.
		 */
		*valp |= 0x0000 << 16;
		break;

	case 0x30:
		/* Expansion ROM Base Address */
		*valp |= cpssp->rom_enabled << 0;
		*valp |= cpssp->rom_address << (1-1);
		break;

	case 0x34 ... 0x38:
		/* Reserved */
		*valp |= 0x00000000 << 0;
		break;

	case 0x3c:
		/* Interrupt Line */
		*valp |= cpssp->interrupt_line << 0;

		/* PCI Interrupt Pin */
		*valp |= 0x00 << 8; /* FIXME */

		/* Min Gnt Register */
		*valp |= 0x00 << 16;

		/* Max Lat Register */
		*valp |= 0x00 << 24;
		break;

	default:
		/* Reserved */
		*valp |= 0x00000000 << 0;
		break;
	}

	return 0;
}

static int
COMP_(c0w)(
	void *_cpssp,
	uint32_t addr,
	unsigned int bs,
	uint32_t val
)
{
	struct cpssp *cpssp = (struct cpssp *) _cpssp;

	assert(! (addr & 3));

	if ((addr >> 8) & 7) {
		/* Only function 0 supported. */
		return 1;
	}
	addr &= 0xff;

	switch (addr) {
	case 0x00:
		/* Vendor ID Register */
		if ((bs >> 0) & 1) {
			/* Read-only */
		}
		if ((bs >> 1) & 1) {
			/* Read-only */
		}

		/* Product ID Register */
		if ((bs >> 2) & 1) {
			/* Read-only */
		}
		if ((bs >> 3) & 1) {
			/* Read-only */
		}
		break;

	case 0x04:
		/* Command Register */
		if ((bs >> 0) & 1) {
			cpssp->io_space = (val >> 0) & 1;
                        cpssp->memory_space = (val >> 1) & 1;
			/* Bit 2-4: Reserved */
                        cpssp->dac_shadowing = (val >> 5) & 1;
			/* Bit 6-7: Reserved */
		}
		if ((bs >> 1) & 1) {
			/* Bit 8-15: Reserved */
		}

		/* Status Register */
		if ((bs >> 2) & 1) {
			/* Bit 0-7: Reserved */
		}
		if ((bs >> 3) & 1) {
			/* Bit 8: Reserved */
			/* Bit 9-10: Read-only */
			/* Bit 11-15: Reserved */
		}
		break;

	case 0x08:
		/* Revision ID Register */
		if ((bs >> 0) & 1) {
			/* Read-only */
		}

		/* Class ID Register */
		if ((bs >> 1) & 1) {
			/* Read-only */
		}
		if ((bs >> 2) & 1) {
			/* Read-only */
		}
		if ((bs >> 3) & 1) {
			/* Read-only */
		}
		break;

	case 0x0c:
		/* Cache Line Size Register */
		/* Reserved */

		/* Latency Timer Register */
		/* Reserved */

		/* Header Type Register */
		/* Reserved */

		/* BIST Register */
		/* Reserved */
		break;

	case 0x10:
		/* Base Address 0 Register (Linear Framebuffer) */
		sig_pci_bus_unmap(cpssp->port_pci_bus, cpssp,
			cpssp->memory_address, DISPLAY_MEMORY_SIZE);
		if ((bs >> 0) & 1) {
			/* Read-only */
		}
		if ((bs >> 1) & 1) {
			/* Read-only */
		}
		if ((bs >> 2) & 1) {
			/* Read-only */
		}
		if ((bs >> 3) & 1) {
			/* Bit 24: Read-only */
			cpssp->memory_address &= ~(0xfe << 24);
			cpssp->memory_address |= val & (0xfe << 24);
		}
		sig_pci_bus_unmap(cpssp->port_pci_bus, cpssp,
			cpssp->memory_address, DISPLAY_MEMORY_SIZE);
		break;

	case 0x14:
		/* Base Address 1 Register (VGA/BitBlt Registers) */
		sig_pci_bus_unmap(cpssp->port_pci_bus, cpssp,
				cpssp->register_address, REGISTER_MEMORY_SIZE);
		if ((bs >> 0) & 1) {
			/* Read-only */
		}
		if ((bs >> 1) & 1) {
			/* Bit 8-11: Read-only */
			cpssp->register_address &= ~(0xf0 << 8);
			cpssp->register_address |= val & (0xf0 << 8);
		}
		if ((bs >> 2) & 1) {
			cpssp->register_address &= ~(0xff << 16);
			cpssp->register_address |= val & (0xff << 16);
		}
		if ((bs >> 3) & 1) {
			cpssp->register_address &= ~(0xff << 24);
			cpssp->register_address |= val & (0xff << 24);
		}
		sig_pci_bus_unmap(cpssp->port_pci_bus, cpssp,
				cpssp->register_address, REGISTER_MEMORY_SIZE);
		break;

	case 0x18:
		/* Base Address 2 Register (GPIO) */
		/*
		 * General purpose i/o is disabled (as if CF4 and CF8 were 1,
		 * see appendix B11 in trm).
		 * Bit 0 readonly one, bits 1-4 reserved zero,
		 * bits 5-31 readonly all zeros.
		 */
		break;

	case 0x1c ... 0x28:
		/* Reserved */
		break;

	case 0x2c:
		/* Subsystem Vendor ID Register */
		/* Read-only */

		/* Subsystem Device ID Register */
		/* Read-only */
		break;

	case 0x30:
		/* Expansion ROM Base Address */
		sig_pci_bus_unmap(cpssp->port_pci_bus, cpssp,
				cpssp->rom_address, BIOS_MEMORY_SIZE);
		if ((bs >> 0) & 1) {
			cpssp->rom_enabled = (val >> 0) & 0b1;
			/* Bit 1-7: Read-only */
		}
		if ((bs >> 1) & 1) {
			/* Bit 8-14: Read-only */
			cpssp->rom_address &= ~(0x80 << 8);
			cpssp->rom_address |= val & (0x80 << 8);
		}
		if ((bs >> 2) & 1) {
			cpssp->rom_address &= ~(0xff << 16);
			cpssp->rom_address |= val & (0xff << 16);
		}
		if ((bs >> 3) & 1) {
			cpssp->rom_address &= ~(0xff << 24);
			cpssp->rom_address |= val & (0xff << 24);
		}
		sig_pci_bus_unmap(cpssp->port_pci_bus, cpssp,
				cpssp->rom_address, BIOS_MEMORY_SIZE);
		break;

	case 0x34 ... 0x38:
		/* Reserved */
		break;

	case 0x3c:
		/* Interrupt Line */
		if ((bs >> 0) & 1) {
			cpssp->interrupt_line = (val >> 0) & 0xff;
		}

		/* PCI Interrupt Pin */
		/* Read-only */

		/* Min Gnt Register */
		/* Read-only */

		/* Max Lat Register */
		/* Read-only */
		break;

	default:
		/* Reserved */
		break;
	}

	return 0;
}

/*************************************************************************/
/*                                                                       */
/* I/O space access functions                                            */
/*                                                                       */
/*************************************************************************/

static void
COMP_(ddc_data_event)(void *_cpssp, bool data)
{
	struct cpssp *cpssp = (struct cpssp*)_cpssp;
	cpssp->i2c_ddc_bus.data = data;
}

static void
COMP_(ddc_clk_event)(void *_cpssp, bool clk)
{
	struct cpssp *cpssp = (struct cpssp*)_cpssp;
	cpssp->i2c_ddc_bus.clk = clk;
}

#define CIRRUS_IO_RESPONSIBLE 0
#define CIRRUS_IO_IGNORE      1

#define PCI_VENDOR_ID_CIRRUS         0x1013
#define PCI_DEVICE_ID_CIRRUS_GD5446  0x00b8
#define PCI_REVISION_ID_CIRRUS       0x01
#define PCI_CLASS_PROG_CIRRUS        0x00
#define PCI_SUBSYSTEM_VENDOR_DEFAULT 0x0000

static void
COMP_(power_set)(void *_cpssp, unsigned int val)
{
	struct cpssp *cpssp = (struct cpssp *) _cpssp;

	cpssp->state_power = val;
}

static void
COMP_(n_reset_set)(void *_cpssp, unsigned int n_val)
{
	struct cpssp *cpssp = _cpssp;

	/*
	 * Initialize config space
	 */
	cpssp->io_space = 0;
	cpssp->memory_space = 0;
	cpssp->dac_shadowing = 0;
	cpssp->memory_address = 0x00000000;
	cpssp->register_address = 0x00000000;
	cpssp->rom_enabled = 0;
	cpssp->rom_address = 0x00000000;
	cpssp->interrupt_line = 0x00;

	cpssp->x = 0;
	cpssp->y = 0;

	/* When loaded with 0x12 reading returns 0x12, else reading
	   returns 0xff. Reset to 0x0f. */
	cpssp->ext_key = 0x0f;				/* SR6 */
	cpssp->ext_mclk_select = 0x2d;			/* SR1F */
	cpssp->ext_ext_dram_controls = 0x0f;		/* GR18 */

	/* FIXME: default to 4MB (8 x 256Kx16) */
	cpssp->ext_dram_control = 0x00;			/* SRF */
	cpssp->dram_bank_switch_control = 0b1;
	cpssp->dram_data_bus_width = 0b11;
	cpssp->ext_config_rb_ext_control = 0x20;	/* SR17 */
	cpssp->dram_bank_size = 0b0;
	cpssp->dram_bank_swap = 0b0;

	cpssp->ext_part_status = 0x40;			/* CR25 */
	/* device id of gd5446 */
	cpssp->ext_id = PCI_DEVICE_ID_CIRRUS_GD5446;	/* CR27 */

	/*
	 * Initialize rest of struct cpssp
	 */
	/* Hidden DAC only accessed every fourth read - counting necessary */
	cpssp->ext_hidden_dac = 0;
	cpssp->ext_hidden_dac_counter = 0;

	/* Current mode */
	cpssp->mode = VGA;
	COMP_(flush)(cpssp);
}

/*************************************************************************/
/*                                                                       */
/* Screen update functions                                               */
/*                                                                       */
/*************************************************************************/

/* GEN_RGB will be run once per pixel and assumes a variable
 * x, which holds the current column, and cpssp->offset to
 * point to the beginning of the current scan line data
 * within the frame buffer; the resulting pixel color data
 * will be assigned to the variables r, g, b.
 */

/* trm 9.3.2.1 */
#define GEN_NAME COMP_(vga_compatibility)
#define GEN_RGB \
	uint8_t col; \
	\
	col = video_readb(cpssp, cpssp->offset_cur); \
	r = video_col_get(cpssp, col, 0); \
	g = video_col_get(cpssp, col, 1); \
	b = video_col_get(cpssp, col, 2); \
	cpssp->offset_cur++
#include "chip_cirrus_gd5446_gen.c"
#undef GEN_RGB
#undef GEN_NAME

/* trm 9.3.2.2 */
#define GEN_NAME COMP_(8bpp_grayscale)
#define GEN_RGB \
	r = g = b = video_readb(cpssp, cpssp->offset_cur); \
	cpssp->offset_cur++
#include "chip_cirrus_gd5446_gen.c"
#undef GEN_RGB
#undef GEN_NAME

/* trm 9.3.2.3 */
#define GEN_NAME COMP_(8bpp_direct_color)
#define GEN_RGB \
	uint8_t col; \
	\
	col = video_readb(cpssp, cpssp->offset_cur);\
	r = (col & 0xe0) << 0; \
	g = (col & 0x1c) << 3; \
	b = (col & 0x03) << 6; \
	cpssp->offset_cur++
#include "chip_cirrus_gd5446_gen.c"
#undef GEN_RGB
#undef GEN_NAME

/* trm 9.3.2.5 */
#define GEN_NAME COMP_(555_mode)
#define GEN_RGB \
	uint16_t col; \
	\
	col = video_readw(cpssp, cpssp->offset_cur);\
	r = ((col >> 10) & 0x1f) << 3; \
	g = ((col >>  5) & 0x1f) << 3; \
	b = ((col >>  0) & 0x1f) << 3; \
	cpssp->offset_cur += 2
#include "chip_cirrus_gd5446_gen.c"
#undef GEN_RGB
#undef GEN_NAME

/* trm 9.3.2.6 */
#define GEN_NAME COMP_(555_mix_mode)
#define GEN_RGB \
	uint16_t col; \
	\
	col = video_readw(cpssp, cpssp->offset_cur); \
	if (col & 0x8000) { \
		col &= 0x00ff; \
		r = video_col_get(cpssp, col, 0); \
		g = video_col_get(cpssp, col, 1); \
		b = video_col_get(cpssp, col, 2); \
	} else { \
		r = ((col >> 10) & 0x1f) << 3; \
		g = ((col >>  5) & 0x1f) << 3; \
		b = ((col >>  0) & 0x1f) << 3; \
	} \
	cpssp->offset_cur += 2
#include "chip_cirrus_gd5446_gen.c"
#undef GEN_RGB
#undef GEN_NAME

/* trm 9.3.2.7 */
#define GEN_NAME COMP_(xga)
#define GEN_RGB \
	uint16_t col; \
	\
	col = video_readw(cpssp, cpssp->offset_cur); \
	r = ((col >> 11) & 0x1f) << 3; \
	g = ((col >>  5) & 0x3f) << 2; \
	b = ((col >>  0) & 0x1f) << 3; \
	cpssp->offset_cur += 2
#include "chip_cirrus_gd5446_gen.c"
#undef GEN_RGB
#undef GEN_NAME

/* trm 9.3.2.10 */
#define GEN_NAME COMP_(888_mode)
#define GEN_RGB \
	b = video_readb(cpssp, cpssp->offset_cur++); \
	g = video_readb(cpssp, cpssp->offset_cur++); \
	r = video_readb(cpssp, cpssp->offset_cur++)
#include "chip_cirrus_gd5446_gen.c"
#undef GEN_RGB
#undef GEN_NAME

/* trm 9.3.2.11 */
#define GEN_NAME COMP_(8888_alpha_mode)
#define GEN_RGB \
	uint32_t col; \
	\
	col = video_readl(cpssp, cpssp->offset_cur); \
	b = (uint8_t) ((col >>  0) & 0xff); \
	g = (uint8_t) ((col >>  8) & 0xff); \
	r = (uint8_t) ((col >> 16) & 0xff); \
	cpssp->offset_cur += 4
#include "chip_cirrus_gd5446_gen.c"
#undef GEN_RGB
#undef GEN_NAME

static uint32_t 
COMP_(get_display_start_offset)(struct cpssp *cpssp)
{
	uint32_t addr;

	addr = cpssp->vga.crt_top_pos
		| ((cpssp->ext_ext_disp_controls   & 0x01) << 16)
		| ((cpssp->ext_ext_disp_controls   & 0x0c) << 15)
		| ((cpssp->ext_overlay_ext_control & 0x80) << 12);

	assert(addr < CIRRUS_MAX_VRAM_SIZE);
	return addr;
}


static unsigned int
COMP_(get_display_pitch)(struct cpssp *cpssp)
{
	unsigned int pitch;

	pitch = cpssp->vga.crt_offset
		| ((cpssp->ext_ext_disp_controls & 0x10) << 4);
	pitch <<= 3;

	return pitch;
}

static enum pixel_formats {
	UNKNOWN,
	VGA_COMPATIBILITY,
	EIGHT_BPP_GRAYSCALE,
	EIGHT_BPP_DIRECT_COLOR,
	FIFTEEN_BPP_MODE,
	FIFTEEN_BPP_MIX_MODE,
	XGA,
	TWENTYFOUR_BPP_MODE,
	THIRTYTWO_BPP_ALPHA_MODE,
	POWER_OFF
} COMP_(get_pixel_format)(struct cpssp *cpssp)
{
	/* trm table 9-8, SR7[3:1] 8.2, HDR 8.37 */
	int bpp;

	bpp = UNKNOWN;

	switch ((cpssp->ext_ext_sequencer_mode & 0x0e) >> 1) {

	case 0x0:               /* 8bpp */

		if (!(cpssp->ext_hidden_dac & 0x80)) {
			/* VGA compatibility or Palette mode > 85 MHz */
			bpp = VGA_COMPATIBILITY;

		} else {
			switch (cpssp->ext_hidden_dac & 0xcf) {
			case 0xc8:
				/* 8-bpp grayscale */
				bpp = EIGHT_BPP_GRAYSCALE;
				break;
			case 0xc9:
				/* 8-bpp direct color */
				bpp = EIGHT_BPP_DIRECT_COLOR;
				break;
			case 0xc6:
			case 0xc7:
				/* Power-down DAC */
				bpp = POWER_OFF;
				break;
			default:
				faum_log(FAUM_LOG_WARNING, __FUNCTION__, "",
					 "Unknown hidden-dac-register value "
					 "0x%02hx (SR7 = 0x%02hx)\n",
					 cpssp->ext_hidden_dac,
					 cpssp->ext_ext_sequencer_mode);
				break;
			}
		}
		break;

	case 0x1:         /* clock doubled 8bpp, see 9.2 in gd544xtrm.pdf */
		/* must never get here, only vgacore can draw this mode */
		assert(0);
		break;

	case 0x3:         /* 16bpp */

		if (!(cpssp->ext_hidden_dac & 0x80)) {
			/* VGA compatibility or Palette mode > 85 MHz */
			bpp = VGA_COMPATIBILITY;

		} else if ((cpssp->ext_hidden_dac & 0xc0) == 0x80) {
			/* 5:5:5 mode with 32K colors (Sierra)  */
			bpp = FIFTEEN_BPP_MODE;

		} else if ((cpssp->ext_hidden_dac & 0xd0) == 0x90
			   || (cpssp->ext_hidden_dac & 0xdf) == 0xd0) {
			/* 5:5:5 with 256-Color Mix mode (Sierra) */
			bpp = FIFTEEN_BPP_MIX_MODE;

		} else {
			switch (cpssp->ext_hidden_dac & 0xcf) {
			case 0xc0:
				/* 5:5:5 mode with 32K colors (Sierra) */
				bpp = FIFTEEN_BPP_MODE;
				break;
			case 0xc1:
				/* 5:6:5 mode with 64K colors (XGA) */
				bpp = XGA;
				break;
			case 0xc6:
			case 0xc7:
				/* Power-down DAC */
				bpp = POWER_OFF;
				break;
			default:
				faum_log(FAUM_LOG_WARNING, __FUNCTION__, "",
					 "Unknown hidden-dac-register value "
					 "0x%02hx (SR7 = 0x%02hx)\n",
					 cpssp->ext_hidden_dac,
					 cpssp->ext_ext_sequencer_mode);
				break;
			}
		}
		break;

	case 0x2:              /* 24bpp */

		switch (cpssp->ext_hidden_dac & 0xcf) {
		case 0xc5:
			/* 8:8:8 mode with 16.8M colors */
			bpp = TWENTYFOUR_BPP_MODE;
			break;
		case 0xc6:
		case 0xc7:
			/* Power-down DAC */
			bpp = POWER_OFF;
			break;
		default:
			faum_log(FAUM_LOG_WARNING, __FUNCTION__, "",
				 "Unknown hidden-dac-register value "
				 "0x%02hx (SR7 = 0x%02hx)\n",
				 cpssp->ext_hidden_dac,
				 cpssp->ext_ext_sequencer_mode);
			break;
		}
		break;

	case 0x4:              /* 32bpp */

		switch (cpssp->ext_hidden_dac & 0xcf) {
		case 0xc5:
			/* 8:8:8:8 mode with 16.8M colors and alpha */
			bpp = THIRTYTWO_BPP_ALPHA_MODE;
			break;
		case 0xc6:
		case 0xc7:
			/* Power-down DAC */
			bpp = POWER_OFF;
			break;
		default:
			faum_log(FAUM_LOG_WARNING, __FUNCTION__, "",
				 "Unknown hidden-dac-register value "
				 "0x%02hx (SR7 = 0x%02hx)\n",
				 cpssp->ext_hidden_dac,
				 cpssp->ext_ext_sequencer_mode);
			break;
		}
		break;

	default:
		faum_log(FAUM_LOG_WARNING, __FUNCTION__, "",
			 "Unknown sequencer-mode-register[3:1] "
			 "value 0x%1hx\n",
			 (cpssp->ext_ext_sequencer_mode & 0x0e) >> 1);
		break;
	}

	return bpp;
}

static void
COMP_(get_resolution)(
	struct cpssp *cpssp,
	unsigned int *hor_end,
	unsigned int *vert_end
)
{
	unsigned int w, h;

	w = cpssp->vga.crt_horizontal_display_end;
	/* FIXME clock doubling? trm 4.22 */

	h = cpssp->vga.crt_vertical_display_end
		| ((cpssp->vga.crt_overflow & 0x02) << 7)
		| ((cpssp->vga.crt_overflow & 0x40) << 3);

	if (cpssp->ext_misc_control & 0x01) {
		/* interlaced mode */
		h *= 2;
	}

	*hor_end = w;
	*vert_end = h;
}

static void
COMP_(video_cirrus)(struct cpssp *cpssp, struct sig_video *video)
{
	unsigned int hor_end, vert_end;
	int pixel_format;
	uint32_t start_offset;
	unsigned int pitch;

	start_offset = COMP_(get_display_start_offset)(cpssp);
	pitch = COMP_(get_display_pitch)(cpssp);
	pixel_format = COMP_(get_pixel_format)(cpssp);
	COMP_(get_resolution)(cpssp, &hor_end, &vert_end);

	switch (pixel_format) {
	case VGA_COMPATIBILITY:
		COMP_(vga_compatibility)(cpssp, video, hor_end, vert_end,
						start_offset, pitch);
		break;
	case EIGHT_BPP_GRAYSCALE:
		COMP_(8bpp_grayscale)(cpssp, video, hor_end, vert_end,
						start_offset, pitch);
		break;
	case EIGHT_BPP_DIRECT_COLOR:
		COMP_(8bpp_direct_color)(cpssp, video, hor_end, vert_end,
						start_offset, pitch);
		break;
	case FIFTEEN_BPP_MODE:
		COMP_(555_mode)(cpssp, video, hor_end, vert_end,
					start_offset, pitch);
		break;
	case FIFTEEN_BPP_MIX_MODE:
		COMP_(555_mix_mode)(cpssp, video, hor_end, vert_end,
					start_offset, pitch);
		break;
	case XGA:
		COMP_(xga)(cpssp, video, hor_end, vert_end,
					start_offset, pitch);
		break;
	case TWENTYFOUR_BPP_MODE:
		COMP_(888_mode)(cpssp, video, hor_end, vert_end,
					start_offset, pitch);
		break;
	case THIRTYTWO_BPP_ALPHA_MODE:
		COMP_(8888_alpha_mode)(cpssp, video, hor_end, vert_end,
					start_offset, pitch);
		break;
	default:
		/* Don't fail... */
		break;
	}
}

static void __attribute__((__noreturn__))
COMP_(video)(void *_cpssp)
{
	struct cpssp *cpssp = (struct cpssp *) _cpssp;

again:	;
	if (! cpssp->state_power
	 || (cpssp->ext_power_management & 0x18) == 0x18) {
		/* Power-off or Stand-by */
		sig_video_no_sync(cpssp->port_video, cpssp);

	} else {
		switch (cpssp->mode) {
		case CIRRUS:
			/* CIRRUS Mode */
			COMP_(video_cirrus)(cpssp, cpssp->port_video);
			break;
		case VGA:
			/* VGA Mode */
			vga_gen(cpssp, cpssp->port_video);
			break;
		default:
			assert(0); /* Mustn't happen. */
		}
	}

	sched_delay(TIME_HZ / ((REFRESH_CYCLES + 1) * VSYNC));

	goto again;
}

/*************************************************************************/
/*                                                                       */
/* Map ressources and connect to busses                                  */
/*                                                                       */
/*************************************************************************/

void *
COMP_(create)(
	const char *name,
	struct sig_manage *port_manage,
	struct sig_std_logic *port_power,
	struct sig_std_logic *port_reset_hash_,
	struct sig_pci_idsel *port_idsel,
	struct sig_pci_bus *port_pci_bus,
	struct sig_video *port_video,
	struct sig_i2c_bus *port_ddc,
	struct sig_cs16 *port_mem0_cs,
	struct sig_cs16 *port_mem1_cs,
	struct sig_cs16 *port_mem2_cs,
	struct sig_cs16 *port_mem3_cs,
	struct sig_cs16 *port_mem4_cs,
	struct sig_cs16 *port_mem5_cs,
	struct sig_cs16 *port_mem6_cs,
	struct sig_cs16 *port_mem7_cs,
	struct sig_cs *port_rom_cs
)
{
	static const struct sig_std_logic_funcs power_funcs = {
		.boolean_or_set = COMP_(power_set),
	};
	static const struct sig_std_logic_funcs reset_hash__funcs = {
		.boolean_or_set = COMP_(n_reset_set),
	};
	static const struct sig_pci_idsel_funcs idsel_funcs = {
		.c0r =		COMP_(c0r),
		.c0w =		COMP_(c0w),
	};
	static const struct sig_pci_bus_funcs pci_bus_funcs = {
		.ior =		COMP_(ior),
		.iow =		COMP_(iow),

		.mr =		COMP_(mr),
		.mw =		COMP_(mw),
		.map_r =	COMP_(map_r),
		.map_w =	COMP_(map_w),
	};
	static const struct sig_i2c_bus_funcs ddc_funcs = {
		.data_event = COMP_(ddc_data_event),
		.clk_event = COMP_(ddc_clk_event),
	};
	struct cpssp *cpssp;

	cpssp = shm_alloc(sizeof(*cpssp));
	assert(cpssp);

	/* initialize vga legacy core */
	vga_init(cpssp);

	/* initialize ddc/i2c bus state (drawn high by bus) */
	cpssp->i2c_ddc_bus.clk = true;
	cpssp->i2c_ddc_bus.data = true;

	/* Out */
	/* Call */
	sig_pci_idsel_connect(port_idsel, cpssp, &idsel_funcs);

	cpssp->port_pci_bus = port_pci_bus;
	sig_pci_bus_connect(port_pci_bus, cpssp, &pci_bus_funcs);

	cpssp->port_video = port_video;
	cpssp->port_ddc = port_ddc;
	sig_i2c_bus_connect_raw(port_ddc, cpssp, &ddc_funcs);

	/* memory chip connections */
	/* (swap memory lines within bank to
	 *  ease mapping of linear adresses to
	 *  chips)
	 */
	/* first bank */
	cpssp->port_mem_cs[0] = port_mem3_cs;
	cpssp->port_mem_cs[1] = port_mem2_cs;
	cpssp->port_mem_cs[2] = port_mem1_cs;
	cpssp->port_mem_cs[3] = port_mem0_cs;
	/* second bank */
	cpssp->port_mem_cs[4] = port_mem7_cs;
	cpssp->port_mem_cs[5] = port_mem6_cs;
	cpssp->port_mem_cs[6] = port_mem5_cs;
	cpssp->port_mem_cs[7] = port_mem4_cs;

	cpssp->port_rom_cs = port_rom_cs;

	/* In */
	cpssp->state_power = 0;
	sig_std_logic_connect_in(port_power, cpssp, &power_funcs);

	sig_std_logic_connect_in(port_reset_hash_, cpssp, &reset_hash__funcs);

	cpssp->process.inst_hz = 100 * 1024*1024;
	sched_process_init(&cpssp->process, COMP_(video), cpssp);

	return cpssp;
}

void
COMP_(destroy)(void *_cpssp)
{
	struct cpssp *cpssp = _cpssp;

	shm_free(cpssp);
}

void
COMP_(suspend)(void *_cpssp, FILE *fp)
{
	struct cpssp *cpssp = _cpssp;
	
	generic_suspend(cpssp, sizeof(*cpssp), fp);
}

void
COMP_(resume)(void *_cpssp, FILE *fp)
{
	struct cpssp *cpssp = _cpssp;
	
	generic_resume(cpssp, sizeof(*cpssp), fp);
}
