/* Copyright (C) 1992, 1996, 1997 artofcode LLC.  All rights reserved.
  
  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.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/*$Id: gdevs3ga.c,v 1.2.6.1.2.1 2003/01/17 00:49:01 giles Exp $ */
/* S3 86C911 driver */
#include "gx.h"
#include "gserrors.h"
#include "gxdevice.h"
#include "gdevpcfb.h"
#include "gdevsvga.h"

/* Shared routines from gdevsvga.c */
extern int vesa_get_mode(P0());
extern void vesa_set_mode(P1(int));

/* Macro for casting gx_device argument */
#define fb_dev ((gx_device_svga *)dev)

/* ------ The S3 86C911 device ------ */

private dev_proc_open_device(s3_open);
private dev_proc_fill_rectangle(s3_fill_rectangle);
private dev_proc_copy_mono(s3_copy_mono);
private const gx_device_procs s3_procs =
{
    s3_open,
    NULL,			/* get_initial_matrix */
    NULL,			/* sync_output */
    NULL,			/* output_page */
    svga_close,
    svga_map_rgb_color,
    svga_map_color_rgb,
    s3_fill_rectangle,
    NULL,			/* tile_rectangle */
    s3_copy_mono,
    svga_copy_color,
/****** DOESN'T WORK ******/
    NULL,			/* draw_line */
    svga_get_bits
/****** DOESN'T WORK ******/
};
gx_device_svga far_data gs_s3vga_device =
svga_device(s3_procs, "s3vga", vesa_get_mode, vesa_set_mode, NULL);

/* Keep track of the character bitmap cache in off-screen memory. */
#define log2_cell_width 5
#define cell_width (1 << log2_cell_width)
#define cache_x_bits (log2_cache_width_bits - log2_cell_width)
#define log2_cell_height 5
#define cell_height (1 << log2_cell_height)
#define cache_y_bits (log2_cache_height - log2_cell_height)
#define log2_cache_width_bits 10
#define log2_cache_width_bytes (log2_cache_width_bits - 3)
#define log2_cache_height 8
#define log2_cache_capacity (cache_x_bits + cache_y_bits)
#define cache_capacity (1 << log2_cache_capacity)
private gx_bitmap_id cache_ids[cache_capacity];

/* Define additional registers and I/O addresses. */
#define crtc_addr 0x3d4		/* (color) */
#define crt_lock 0x35
#define crt_s3_lock1 0x38
#define crt_s3_lock2 0x39
#define s3_y_pos 0x82e8
#define s3_x_pos 0x86e8
#define s3_y_dest 0x8ae8
#define s3_x_dest 0x8ee8
#define s3_width 0x96e8
#define s3_status 0x9ae8	/* read only */
#define s3_command 0x9ae8	/* write only */
#define s3_back_color 0xa2e8
#define s3_fore_color 0xa6e8
#define s3_write_mask 0xaae8
#define s3_read_mask 0xaee8
#define s3_back_mix 0xb6e8
#define s3_fore_mix 0xbae8
#define s3_height 0xbee8
#define s3_mf_control 0xbee8
#  define mf_data_ones 0xa000
#  define mf_data_cpu 0xa080
#  define mf_data_display 0xa0c0
#define s3_pixel_data 0xe2e8
/* Wait for the command FIFO to empty. */
#define s3_wait_fifo()\
  while ( inport(s3_status) & 0xff )
/* Load the parameters for a rectangle operation. */
#define out_s3_rect(x, y, w, h)\
  (outport(s3_x_pos, x), outport(s3_y_pos, y),\
   outport(s3_width, (w) - 1), outport(s3_height, (h) - 1))

private int
s3_open(gx_device * dev)
{
    static const mode_info mode_table[] =
    {
	{640, 480, 0x201},
	{800, 600, 0x203},
	{1024, 768, 0x205},
	{-1, -1, -1}
    };
    int code = svga_find_mode(dev, mode_table);

    if (code < 0)
	return_error(gs_error_rangecheck);
    /* The enhanced modes all use a 1024-pixel raster. */
    fb_dev->raster = 1024;
    code = svga_open(dev);
    if (code < 0)
	return code;
    /* Clear the cache */
    {
	int i;

	for (i = 0; i < cache_capacity; i++)
	    cache_ids[i] = gx_no_bitmap_id;
    }
    return 0;
}

/* Fill a rectangle. */
int
s3_fill_rectangle(gx_device * dev, int x, int y, int w, int h,
		  gx_color_index color)
{
    fit_fill(dev, x, y, w, h);
    s3_wait_fifo();
    outport(s3_fore_mix, 0x27);
    outport(s3_fore_color, (int)color);
    outport(s3_mf_control, mf_data_ones);
    out_s3_rect(x, y, w, h);
    outport(s3_command, 0x40b3);
    return 0;
}

/* Copy a monochrome bitmap.  The colors are given explicitly. */
/* Color = gx_no_color_index means transparent (no effect on the image). */
private int
s3_copy_mono(gx_device * dev,
	     const byte * base, int sourcex, int raster, gx_bitmap_id id,
      int x, int y, int w, int h, gx_color_index czero, gx_color_index cone)
{
    int sbit;
    const byte *sptr;
    int run;
    byte lmask;
    byte lmerge = 0;
    int cache_index, cache_x, cache_y;
    int i, j;

    fit_copy(dev, base, sourcex, raster, id, x, y, w, h);
    sbit = sourcex & 7;
    sptr = base + (sourcex >> 3);
    run = (sbit + w + 7) >> 3;
    lmask = 0xff >> sbit;
    /* See whether the cache is applicable. */
    if (id != gx_no_bitmap_id && w <= cell_width - 7 &&
	h <= cell_height
	) {
	cache_index = (int)(id & (cache_capacity - 1));
	cache_x = ((cache_index & ((1 << cache_x_bits) - 1)) <<
		   log2_cell_width) + 7;
	cache_y = ((cache_index >> cache_x_bits) <<
		   log2_cell_height) + 768;
	if (cache_ids[cache_index] != id) {
	    cache_ids[cache_index] = id;
	    /* Copy the bitmap to the cache. */
	    s3_wait_fifo();
	    out_s3_rect(cache_x - sbit, cache_y, w + sbit, h);
	    outport(s3_fore_mix, 0x22);		/* 1s */
	    outport(s3_back_mix, 0x01);		/* 0s */
	    outport(s3_mf_control, mf_data_cpu);
	    outport(s3_command, 0x41b3);
	    {
		const int skip = raster - run;

		for (i = h; i > 0; i--, sptr += skip)
		    for (j = run; j > 0; j--, sptr++)
			outportb(s3_pixel_data, *sptr);
	    }
	}
	s3_wait_fifo();
    } else {
	cache_index = -1;
	if (lmask != 0xff) {	/* The hardware won't do the masking for us. */
	    if (czero != gx_no_color_index) {
		if (cone != gx_no_color_index) {
		    s3_fill_rectangle(dev, x, y, w, h, czero);
		    czero = gx_no_color_index;
		} else {
		    lmerge = ~lmask;
		}
	    }
	}
	s3_wait_fifo();
	out_s3_rect(x - sbit, y, w + sbit, h);
    }
    /* Load the colors for the real transfer. */
    if (cone != gx_no_color_index) {
	outport(s3_fore_mix, 0x27);
	outport(s3_fore_color, (int)cone);
    } else
	outport(s3_fore_mix, 0x63);
    if (czero != gx_no_color_index) {
	outport(s3_back_mix, 0x07);
	outport(s3_back_color, (int)czero);
    } else
	outport(s3_back_mix, 0x63);
    s3_wait_fifo();
    if (cache_index < 0) {	/* direct transfer */
	outport(s3_mf_control, mf_data_cpu);
	outport(s3_command, 0x41b3);
	if (run == 1 && !lmerge) {	/* special case for chars */
	    for (i = h; i > 0; i--, sptr += raster)
		outportb(s3_pixel_data, *sptr & lmask);
	} else {
	    const int skip = raster - run;

	    for (i = h; i > 0; i--, sptr += skip) {
		outportb(s3_pixel_data, (*sptr++ & lmask) | lmerge);
		for (j = run; j > 1; j--, sptr++)
		    outportb(s3_pixel_data, *sptr);
	    }
	}
    } else {			/* Copy the character from the cache to the screen. */
	out_s3_rect(cache_x, cache_y, w, h);
	outport(s3_x_dest, x);
	outport(s3_y_dest, y);
	outport(s3_mf_control, mf_data_display);
	outport(s3_command, 0xc0b3);
    }
    return 0;
}
