/*
 * DANG_BEGIN_MODULE
 *
 * Description:
 *   ATI VGA+ driver for DOSEMU.  Code taken from SVGALIB. 
 * 
 *   Original driver written by Scott D. Heavner (sdh@po.cwru.edu)
 *
 * DANG_END_MODULE
 *
 * $Date: $
 * $Source: $
 * $Revision: $
 * $State: $
 *
 * $Log: $
 */

#include "dosemu.h"
#include "base/port.h"
#include "base/detach.h"
#include "env/libvga.h"

#include <sys/mman.h>

#define ATI_UNKNOWN	0
#define	ATI_18800	1
#define	ATI_18800_1	2
#define	ATI_28800_2	3
#define	ATI_28800_4	4
#define	ATI_28800_5	5
#define ATI_68800	'a'

#define ATIREG(s)	(EXT+(s)-0xa0) 

#define MIN(a,b)	(((a)<(b))?(a):(b))
#define ABS(a)		(((a)<0)?(-(a)):(a))

static unsigned char ati_flags_42, ati_flags_44, ati_gate;
static short ati_base = 0;
static Bit8u ati_base_val = 0xb8;

static void ati_get_perm(void)
{
  if (set_ioperm(MIN(ati_base,0x3df), ABS(0x3df-ati_base), 1)) {
    error("VIDEO: call to get IO permissions for the ATI card failed\n");
    leavedos(1);
  }
}

static void ati_release_perm(void)
{
  if (set_ioperm(MIN(ati_base,0x3df), ABS(0x3df-ati_base), 0)) {
    error("VIDEO: call to release IO permissions for the ATI card failed\n");
    leavedos(1);
  }
}

static void ati_setpage(int page)
{
  port_out(0x04, GRA_I);
  port_out(page, GRA_D);
}

static Bit8u ati_inb(Bit32u port)
{
  if (port == ati_base) {
    return ati_base_val;
  }
  if (port == ati_base + 1) {
    if (ati_base_val == 0xb8)
      return dosemu_regs.regs[ATIREG(0xb8)];
    if (ati_base_val == 0xbe)
      return dosemu_regs.regs[ATIREG(0xbe)];
  }
  return 0xff;
}

static void ati_outb(Bit32u port, Bit8u val)
{
  if (port == ati_base) {
    ati_base_val = val;
  }
  else if (port == ati_base + 1) {
    if (ati_base_val == 0xb8)
      dosemu_regs.regs[ATIREG(0xb8)] = val;
    else if (ati_base_val == 0xbe)
      dosemu_regs.regs[ATIREG(0xbe)] = val;
  }
}

static void ati_saveregs(Bit16u *regs)
{
  outb(ati_base, 0xb8);
  regs[ATIREG(0xb8) - EXT] = inb(ati_base+1);

  if (ati_gate > ATI_18800) {
    outb(ati_base, 0xbe);
    regs[ATIREG(0xbe) - EXT] = inb(ati_base+1);
  }
}

static void ati_setregs(const Bit16u *regs)
{
  outb(ati_base, 0xb8);
  outb(ati_base+1, regs[ATIREG(0xb8) - EXT]);
   
  if (ati_gate > ATI_18800) {
    outb(ati_base, 0xbe);
    outb(ati_base+1, regs[ATIREG(0xbe) - EXT]);
  }
}

static Boolean ati_init(void)
{
  emu_iodev_t  io_device;

  io_device.read_portb   = ati_inb;
  io_device.write_portb  = ati_outb;
  io_device.read_portw   = NULL;
  io_device.write_portw  = NULL;
  io_device.handler_name = "ATI+";
  io_device.start_addr   = MIN(ati_base, 0x3df);
  io_device.end_addr     = ABS(ati_base - 0x3df);
  io_device.irq          = EMU_NO_IRQ;
  port_register_handler(io_device);

  return TRUE;
}

static Boolean ati_test(void)
{
  Boolean  result = FALSE;
  Bit8u   *vga_bios;

  vga_bios = (Bit8u *)mmap(0, 4096, PROT_READ, MAP_SHARED, mem_fd, 0xc0000);
  if ((off_t)vga_bios <= 0) {
    v_printf("VGA: ATI mmap failed!\n");
    return FALSE;
  }

  /* The first string id's an ATI product, the second ATI super VGA */
  if (!strncmp("761295520", (vga_bios+0x31), 9) &&
      !strncmp("31",(vga_bios+0x40),2)) {
    result = TRUE;
    memcpy(&ati_base, (vga_bios+0x10), 2);

    switch (vga_bios[0x43]) {		/* Identify Gate revision */
    case '1':
      ati_gate = ATI_18800;
      break;
    case '2':
      ati_gate = ATI_18800_1;
      break;
    case '3':
      ati_gate = ATI_28800_2;
      break;
    case '4':
      ati_gate = ATI_28800_4;
      break;
    case '5':
      ati_gate = ATI_28800_5;
      break;
    case 'a':
      ati_gate = ATI_68800;    /* Mach 32 -- handle elsewhere */
      result = FALSE;
      break;
    default:
      ati_gate = ATI_UNKNOWN;
    }

    ati_flags_42 = vga_bios[0x42];
    ati_flags_44 = vga_bios[0x44];
  }

  munmap((caddr_t) vga_bios, 4096);

  if (result)
    result = ati_init();
  return result;
}

DriverSpecs ati_driverspecs = {
	"ATI VGA+",
        ati_saveregs,
        ati_setregs,
        ati_test,
        ati_init,
        ati_setpage,
	ati_get_perm,
	ati_release_perm,
	ati_inb,
	ati_outb
};
