/*
 * attremu.c
 *
 * Attribute controller emulator for VGAEmu
 *
 * Copyright (C) 1995 1996, Erik Mouw and Arjan Filius
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 * email: J.A.K.Mouw@et.tudelft.nl, I.A.Filius@et.tudelft.nl
 *
 *
 * This code emulates the Atrribute Controller for VGAemu.
 * The Attribute Controller is part of the VGA (Video Graphics Array,
 * a video adapter for IBM compatible PC's).
 *
 * For an excellent reference to programming SVGA cards see Finn Thøgersen's
 * VGADOC4, available at http://www.datashopper.dk/~finth
 *
 * VGADOC says about the attribute controller:
 * Port 3C0h is special in that it is both address and data-write register.
 * Data reads happen from port 3C1h. An internal flip-flop remembers whether
 * it is currently acting as an address or data register.
 * Reading port 3DAh will reset the flip-flop to address mode.
 *
 * What the DOC does not say, but my card does:
 * - index reg, bit 5 affects only the palette regs (index < 16) (Not even the
 *   overscan color reg!)
 * - a read from an inaccessible reg (or a nonexistent reg) returns the index reg
 * - all unspecified bits are zero and cannot be set
 * -- sw
 *
 * DANG_BEGIN_MODULE
 *
 * REMARK
 * The Attribute Controller emulator for VGAemu.
 * /REMARK
 *
 * DANG_END_MODULE
 *
 * DANG_BEGIN_CHANGELOG
 *
 * 1998/09/20: Added proper init values for all graphics modes.
 * -- sw (Steffen Winterfeldt <wfeldt@suse.de>)
 *
 * 1998/10/25: Restructured code, removed unnecessary parts, added some code.
 * The emulation is now (as far as I could test) identical to my VGA chip's
 * controller (S3 968).
 * -- sw
 *
 * 1999/01/05: Moved Attr_get_input_status_1() into a separate file (miscemu.c).
 * -- sw
 *
 * DANG_END_CHANGELOG
 */


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * some configurable options
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/*
 * Debug level for the Attribute Controller.
 * 0 - normal / 1 - useful / 2 - too much
 */
#define DEBUG_ATTR	0


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define ATTR_INDEX_FLIPFLOP	0
#define ATTR_DATA_FLIPFLOP	1

#define ATTR_MODE_CTL	0x10
#define ATTR_OVERSCAN	0x11
#define ATTR_COL_PLANE	0x12
#define ATTR_HOR_PAN	0x13
#define ATTR_COL_SELECT	0x14

#if !defined True
#define False 0
#define True 1
#endif

#define attr_msg(x...) v_printf("VGAEmu: " x)

#if DEBUG_ATTR >= 1
#define attr_deb(x...) v_printf("VGAEmu: " x)
#else
#define attr_deb(x...)
#endif


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "emu.h"
#include "vgaemu.h"


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static unsigned char attr_ival[9][ATTR_MAX_INDEX + 1] = {
  {	/* 0  TEXT, 4 bits */
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07,
    0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
    0x0c, 0x00, 0x0f, 0x08, 0x00
  },
  {	/* 1  CGA, 2 bits */
    0x00, 0x13, 0x15, 0x17, 0x02, 0x04, 0x06, 0x07,
    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
    0x01, 0x00, 0x03, 0x00, 0x00
  },
  {	/* 2  CGA: PL1, 1 bit (mode 0x06) */
    0x00, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17,
    0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17,
    0x01, 0x00, 0x01, 0x00, 0x00
  },
  {	/* 3  TEXT, mono */
    0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
    0x10, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
    0x0e, 0x00, 0x0f, 0x08, 0x00
  },
  {	/* 4  EGA: PL4, 4 bits (modes 0x0d, 0x0e) */
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
    0x01, 0x00, 0x0f, 0x00, 0x00
  },
  {	/* 5  EGA: PL2, 2 bits (mode 0x0f) */
    0x00, 0x08, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00,
    0x00, 0x08, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00,
    0x0b, 0x00, 0x05, 0x00, 0x00
  },
  {	/* 6  VGA: PL4, 4 bit */
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07,
    0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
    0x01, 0x00, 0x0f, 0x00, 0x00
  },
  {	/* 7  VGA: PL1, 1 bit */
    0x00, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
    0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
    0x01, 0x00, 0x01, 0x00, 0x00
  },
  {	/* 8  P8, 8 bit */
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
    0x41, 0x00, 0x0f, 0x00, 0x00
  }
};


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/*
 * Set unspecified bits to zero.
 */
static unsigned char clear_undef_bits(unsigned char i, unsigned char v)
{
  unsigned char m;

  if(i > ATTR_MAX_INDEX) return v;

  switch(i) {
    case ATTR_MODE_CTL  : m = ~0x10; break;
    case ATTR_HOR_PAN   :
    case ATTR_COL_SELECT: m = 0x0f; break;
    default             : m = 0x3f;
  }

  return v & m;
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/*
 * DANG_BEGIN_FUNCTION Attr_init
 *
 * Initializes the attribute controller.
 * This is an interface function.
 *
 * DANG_END_FUNCTION
 *
 */
void Attr_init(void)
{
  int i = 0, j;

  switch(vga.VGA_mode) {
    case 0x06: i = 2; break;
    case 0x0d:
    case 0x0e: i = 4; break;
    case 0x0f: i = 5; break;
    default:
      switch(vga.color_bits) {
        case  1: if(vga.mode_type == PL1)  i = 7; break;
        case  2: if(vga.mode_type == CGA)  i = 1; break;
        case  4: if(vga.mode_type == TEXT) i = 0;
                 if(vga.mode_type == TEXT_MONO) i = 3;
                 if(vga.mode_type == PL4)  i = 6;
                 break;
        case  8:
        case 15:
        case 16:
        case 24:
        case 32: i = 8;
      }
  }

  for(j = 0; j <= ATTR_MAX_INDEX; j++) {
    vga.attr.data[j] = attr_ival[i][j];
    vga.attr.dirty[j] = True;
  }

  vga.color_modified = True;
  vga.attr.index = 0;
  vga.attr.cpu_video = 0x20;
  vga.attr.flipflop = ATTR_INDEX_FLIPFLOP;

  attr_msg("Attr_init done\n");
}


/*
 * DANG_BEGIN_FUNCTION Attr_get_entry
 *
 * Directly reads the Attribute Controller's registers.
 * This is an interface function.
 *
 * DANG_END_FUNCTION
 *
 */
unsigned char Attr_get_entry(unsigned char index)
{
  unsigned char u;

  u = index <= ATTR_MAX_INDEX ? vga.attr.data[index] : 0xff;

  attr_deb("Attr_get_entry: data[0x%02x] = 0x%02x\n", (unsigned) index, (unsigned) u);

  return u;
}


/*
 * DANG_BEGIN_FUNCTION Attr_set_entry
 *
 * Directly sets the Attribute Controller's registers.
 * This is an interface function.
 *
 * DANG_END_FUNCTION
 *
 */
void Attr_set_entry(unsigned char index, unsigned char value)
{
  unsigned char old_cpu_video = vga.attr.cpu_video;
  attr_deb("Attr_set_entry: data[0x%02x] = 0x%02x\n", (unsigned) index, (unsigned) value);

  if(index > ATTR_MAX_INDEX) return;

  vga.attr.index = index;
  vga.attr.cpu_video = 0;
  vga.attr.flipflop = ATTR_DATA_FLIPFLOP;
  Attr_write_value(value);
  vga.attr.cpu_video = old_cpu_video;
}

/*
 * DANG_BEGIN_FUNCTION Attr_read_value
 *
 * Emulates reads from the attribute controller.
 * This is a hardware emulation function.
 *
 * DANG_END_FUNCTION
 *
 */
unsigned char Attr_read_value(void)
{
  unsigned i = vga.attr.index;
  unsigned char uc = i | vga.attr.cpu_video;

  if(i <= ATTR_MAX_INDEX && (vga.attr.cpu_video == 0 ||i > 15)) {
    uc = vga.attr.data[i];

    attr_deb("Attr_read_value: attr[0x%02x] = 0x%02x\n", i, (unsigned) uc);
  }
  else {
    attr_deb("Attr_read_value: data reg inaccessible, index = 0x%02x\n", (unsigned) uc);
  }

  return uc;
}


/*
 * DANG_BEGIN_FUNCTION Attr_write_value
 *
 * Emulates writes to attribute controller combined index and data
 * register. Read VGADOC for details.
 * This is a hardware emulation function.
 *
 * DANG_END_FUNCTION
 *
 */
void Attr_write_value(unsigned char data)
{
  unsigned i, j;

  if(vga.attr.flipflop == ATTR_INDEX_FLIPFLOP) {
    vga.attr.flipflop = ATTR_DATA_FLIPFLOP;

    vga.attr.index = data & 0x1f;
    vga.attr.cpu_video = data & 0x20;

    attr_deb(
      "Attr_write_value: index = 0x%02x\n",
      (unsigned) (vga.attr.index | vga.attr.cpu_video)
    );

    attr_deb("Attr_write_value: %svideo access\n", vga.attr.cpu_video ? "" : "no ");

    i = vga.config.video_off;
    vga.config.video_off = (vga.config.video_off & ~1) + (((vga.attr.cpu_video >> 5) & 1) ^ 1);
    if(i != vga.config.video_off) {
      attr_deb("Attr_write_value: video signal turned %s\n", vga.config.video_off ? "off" : "on");
    }

  }
  else {	/* Attr_flipflop == ATTR_DATA_FLIPFLOP */
    vga.attr.flipflop = ATTR_INDEX_FLIPFLOP;

    i = vga.attr.index;
    data = clear_undef_bits(i, data);
    if(i <= ATTR_MAX_INDEX && (vga.attr.cpu_video == 0 || i > 15)) {
      if (vga.attr.data[i] != data) {
        vga.attr.data[i] = data;
        vga.attr.dirty[i] = True;
        vga.color_modified = True;
	if (i == ATTR_COL_PLANE)
	  vgaemu_adj_cfg(CFG_MODE_CONTROL, 0);
      }
      if(i == ATTR_MODE_CTL || i == ATTR_COL_SELECT) {
        for(j = 0; j < 16; j++) vga.attr.dirty[j] = True;
      }
      /* bits: 0x04 - line graphics copy 8th->9th column...    */
      /*       0x02 - mono mode  /  0x01 - graphics mode       */
      /*       0x20 - use line compare pixel panning...        */
      /*       0x40 - VGA 8bit colors (not 4bit)               */
      /*       0x80 - VGA 16*16 attribs*pages (not 64*4)       */
      if ((i == ATTR_MODE_CTL) && (data & 0x20))
        v_printf("Horizontal panning with line compare NOT IMPLEMENTED\n");
      if ((i == ATTR_MODE_CTL) && (data & 0x08))
        v_printf("Blinking ignored, will use 16 color background\n");
      attr_deb("Attr_write_value: attr[0x%02x] = 0x%02x\n", i, (unsigned) data);
    }
    else {
      attr_deb("Attr_write_value: data ignored\n");
    }
  }
}


/*
 * DANG_BEGIN_FUNCTION Attr_get_index
 *
 * Returns the current index of the attribute controller.
 * This is a hardware emulation function, though in fact this function
 * is undefined in a real attribute controller.
 * Well, it is exactly what my VGA board (S3) does. -- sw
 * This is a hardware emulation function.
 *
 * DANG_END_FUNCTION
 *
 */
unsigned char Attr_get_index(void)
{
  unsigned char uc = vga.attr.index | vga.attr.cpu_video;

  attr_deb("Attr_get_index: index = 0x%02x\n", (unsigned) uc);

  return uc;
}


