/*
 * VIDIX driver for VIA Cyberblade/i1 chipsets.
 * Brightness/Contrast controls disabled for the time being - they were
 * seriously degrading picture quality, especially with TV-Out.
 *
 * Copyright (C) 2002 Alastair M. Robinson
 * http://www.blackfiveservices.co.uk/EPIAVidix.shtml
 * based on Permedia 3 driver by Måns Rullgård
 * thanks to Gilles Frattini for bugfixes
 *
 * This file is part of MPlayer.
 *
 * MPlayer 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.
 *
 * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/* To Do:
 *   Implement Hue/Saturation controls
 *   Support / Test multiple frames
 *   Test colour-key code more extensively
 */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <unistd.h>

#include "config.h"
#include "vidix.h"
#include "fourcc.h"
#include "dha.h"
#include "pci_ids.h"
#include "pci_names.h"
#include "mp_msg.h"

#include "cyberblade_regs.h"

static pciinfo_t pci_info;

static char save_colourkey[6];
static char *cyberblade_mem;

#ifdef DEBUG_LOGFILE
static FILE *logfile=0;
#define LOGWRITE(x) {if(logfile) fprintf(logfile,x);}
#else
#define LOGWRITE(x)
#endif

/* Helper functions for reading registers. */

static void CROUTW(int reg,int val)
{
	CROUTB(reg,val&255);
	CROUTB(reg+1,(val>>8)&255);
}

static void SROUTW(int reg,int val)
{
	SROUTB(reg,val&255);
	SROUTB(reg+1,(val>>8)&255);
}

static const vidix_capability_t cyberblade_cap =
{
	"Trident CyberBlade i1 driver",
	"Alastair M. Robinson <blackfive@fakenhamweb.co.uk>",
	TYPE_OUTPUT,
	{ 0, 0, 0, 0 },
	1024,
	1024,
	4,
	4,
	-1,
	FLAG_UPSCALER|FLAG_DOWNSCALER,
	VENDOR_TRIDENT,
	-1,
	{ 0, 0, 0, 0 }
};

static unsigned short cyberblade_card_ids[] =
{
	DEVICE_TRIDENT_CYBERBLADE_I7,
	DEVICE_TRIDENT_CYBERBLADE_I7D,
	DEVICE_TRIDENT_CYBERBLADE_I1,
	DEVICE_TRIDENT_CYBERBLADE_I12,
	DEVICE_TRIDENT_CYBERBLADE_I13,
	DEVICE_TRIDENT_CYBERBLADE_XPAI1
};


static int find_chip(unsigned chip_id)
{
  unsigned i;
  for(i = 0;i < sizeof(cyberblade_card_ids)/sizeof(unsigned short);i++)
  {
    if(chip_id == cyberblade_card_ids[i]) return i;
  }
  return -1;
}

static int cyberblade_probe(int verbose, int force)
{
	pciinfo_t lst[MAX_PCI_DEVICES];
	unsigned i,num_pci;
	int err;
	err = pci_scan(lst,&num_pci);
	if(err)
	{
		mp_msg(MSGT_VO, MSGL_STATUS, "[cyberblade] Error occurred during pci scan: %s\n",strerror(err));
		return err;
	}
	else
	{
		err = ENXIO;
		for(i=0; i < num_pci; i++)
		{
			if(lst[i].vendor == VENDOR_TRIDENT)
			{
				int idx;
				const char *dname;
				idx = find_chip(lst[i].device);
				if(idx == -1)
					continue;
				dname = pci_device_name(VENDOR_TRIDENT, lst[i].device);
				dname = dname ? dname : "Unknown chip";
				mp_msg(MSGT_VO, MSGL_STATUS, "[cyberblade] Found chip: %s\n", dname);
#if 0
                                if ((lst[i].command & PCI_COMMAND_IO) == 0)
				{
					mp_msg(MSGT_VO, MSGL_STATUS, "[cyberblade] Device is disabled, ignoring\n");
					continue;
				}
#endif
				err = 0;
				memcpy(&pci_info, &lst[i], sizeof(pciinfo_t));
				break;
			}
		}
	}

	if(err && verbose) mp_msg(MSGT_VO, MSGL_STATUS, "[cyberblade] Can't find chip\n");
		return err;
}


static int cyberblade_init(void)
{
	cyberblade_mem = map_phys_mem(pci_info.base0, 0x800000);
	enable_app_io();
	save_colourkey[0]=SRINB(0x50);
	save_colourkey[1]=SRINB(0x51);
	save_colourkey[2]=SRINB(0x52);
	save_colourkey[3]=SRINB(0x54);
	save_colourkey[4]=SRINB(0x55);
	save_colourkey[5]=SRINB(0x56);
#ifdef DEBUG_LOGFILE
	logfile=fopen("/tmp/cyberblade_vidix.log","w");
#endif
	return 0;
}

static void cyberblade_destroy(void)
{
	int protect;
#ifdef DEBUG_LOGFILE
	if(logfile)
		fclose(logfile);
#endif
	protect=SRINB(0x11);
	SROUTB(0x11, 0x92);
	CROUTB(0x8E, 0xc4); /* Disable overlay */
	SROUTB(0x50,save_colourkey[0]);
	SROUTB(0x51,save_colourkey[1]);
	SROUTB(0x52,save_colourkey[2]);
	SROUTB(0x54,save_colourkey[3]);
	SROUTB(0x55,save_colourkey[4]);
	SROUTB(0x56,save_colourkey[5]);
	SROUTB(0x11, protect);
	disable_app_io();
	unmap_phys_mem(cyberblade_mem, 0x800000);
}


static int cyberblade_get_caps(vidix_capability_t *to)
{
	memcpy(to, &cyberblade_cap, sizeof(vidix_capability_t));
	to->device_id = pci_info.device;
	return 0;
}


static int is_supported_fourcc(uint32_t fourcc)
{
	switch(fourcc)
	{
		case IMGFMT_YUY2:
		case IMGFMT_YV12:
		case IMGFMT_I420:
		case IMGFMT_YVU9:
		case IMGFMT_BGR16:
			return 1;
		default:
			return 0;
	}
}

static int cyberblade_query_fourcc(vidix_fourcc_t *to)
{
	if(is_supported_fourcc(to->fourcc))
	{
		to->depth = VID_DEPTH_ALL;
		to->flags = VID_CAP_EXPAND | VID_CAP_SHRINK | VID_CAP_COLORKEY;
		return 0;
	}
	return ENOSYS;
}


static int frames[VID_PLAY_MAXFRAMES];

static vidix_grkey_t cyberblade_grkey;

static int cyberblade_get_gkeys(vidix_grkey_t *grkey)
{
	memcpy(grkey, &cyberblade_grkey, sizeof(vidix_grkey_t));
	return 0;
}

static int cyberblade_set_gkeys(const vidix_grkey_t *grkey)
{
	int pixfmt=CRINB(0x38);
	int protect;
	memcpy(&cyberblade_grkey, grkey, sizeof(vidix_grkey_t));

	protect=SRINB(0x11);
	SROUTB(0x11, 0x92);

	if(pixfmt&0x28) /* 32 or 24 bpp */
	{
		SROUTB(0x50, cyberblade_grkey.ckey.blue); /* Colour Key */
		SROUTB(0x51, cyberblade_grkey.ckey.green); /* Colour Key */
		SROUTB(0x52, cyberblade_grkey.ckey.red); /* Colour Key */
		SROUTB(0x54, 0xff); /* Colour Key Mask */
		SROUTB(0x55, 0xff); /* Colour Key Mask */
		SROUTB(0x56, 0xff); /* Colour Key Mask */
	}
	else
	{
		int tmp=((cyberblade_grkey.ckey.blue & 0xF8)>>3)
			| ((cyberblade_grkey.ckey.green & 0xfc)<<3)
			| ((cyberblade_grkey.ckey.red & 0xf8)<<8);
		SROUTB(0x50, tmp&0xff); /* Colour Key */
		SROUTB(0x51, (tmp>>8)&0xff); /* Colour Key */
		SROUTB(0x52, 0); /* Colour Key */
		SROUTB(0x54, 0xff); /* Colour Key Mask */
		SROUTB(0x55, 0xff); /* Colour Key Mask */
		SROUTB(0x56, 0x00); /* Colour Key Mask */
	}
	SROUTB(0x11,protect);
	return 0;
}


static vidix_video_eq_t equal =
{
	VEQ_CAP_BRIGHTNESS | VEQ_CAP_SATURATION | VEQ_CAP_HUE,
	300, 100, 0, 0, 0, 0, 0, 0
};

static int cyberblade_get_eq( vidix_video_eq_t * eq)
{
  memcpy(eq,&equal,sizeof(vidix_video_eq_t));
  return 0;
}

static int cyberblade_set_eq( const vidix_video_eq_t * eq)
{
	int br,sat,cr,protect;
	if(eq->cap & VEQ_CAP_BRIGHTNESS) equal.brightness = eq->brightness;
	if(eq->cap & VEQ_CAP_CONTRAST) equal.contrast   = eq->contrast;
	if(eq->cap & VEQ_CAP_SATURATION) equal.saturation = eq->saturation;
	if(eq->cap & VEQ_CAP_HUE)        equal.hue        = eq->hue;
	if(eq->cap & VEQ_CAP_RGB_INTENSITY)
	{
		equal.red_intensity   = eq->red_intensity;
		equal.green_intensity = eq->green_intensity;
		equal.blue_intensity  = eq->blue_intensity;
	}
	equal.flags = eq->flags;

	cr = (equal.contrast) * 31 / 2000; cr+=16;
	if (cr < 0) cr = 0; if(cr > 7) cr = 7;
	cr=cr<<4 | cr;

	br = (equal.brightness+1000) * 63 / 2000;
	if (br < 0) br = 0; if(br > 63) br = 63;
	if(br>32) br-=32; else br+=32;

	sat = (equal.saturation + 1000) * 16 / 2000;
	if (sat < 0) sat = 0; if(sat > 31) sat = 31;

	protect=SRINB(0x11);
	SROUTB(0x11, 0x92);

	SROUTB(0xBC,cr);
	SROUTW(0xB0,(br<<10)|4);

	SROUTB(0x11, protect);

	return 0;
}


static int YOffs,UOffs,VOffs;

static int cyberblade_config_playback(vidix_playback_t *info)
{
	int src_w, drw_w;
	int src_h, drw_h;
	int hscale,vscale;
	long base0;
	int y_pitch = 0, uv_pitch = 0;
	int protect=0;
	int layout=0;
	unsigned int i;

	if(!is_supported_fourcc(info->fourcc))
		return -1;

	src_w = info->src.w;
	src_h = info->src.h;

	drw_w = info->dest.w;
	drw_h = info->dest.h;

	switch(info->fourcc)
	{
		case IMGFMT_YUY2:
		case IMGFMT_BGR16:
			y_pitch = (src_w*2 + 15) & ~15;
			uv_pitch = 0;
			YOffs=VOffs=UOffs=info->offset.y = info->offset.v = info->offset.u = 0;
			info->frame_size = y_pitch*src_h;
			layout=0x0; /* packed */
			break;
		case IMGFMT_YV12:
		case IMGFMT_I420:
			y_pitch = (src_w+15) & ~15;
			uv_pitch = ((src_w/2)+7) & ~7;
			YOffs=info->offset.y = 0;
			VOffs=info->offset.v = y_pitch*src_h;
			UOffs=info->offset.u = info->offset.v+(uv_pitch)*(src_h/2);
			info->frame_size = y_pitch*src_h + 2*uv_pitch*(src_h/2);
			layout=0x1; /* planar, 4:1:1 */
			break;
		case IMGFMT_YVU9:
			y_pitch = (src_w+15) & ~15;
			uv_pitch = ((src_w/4)+3) & ~3;
			YOffs=info->offset.y = 0;
			VOffs=info->offset.v = y_pitch*src_h;
			UOffs=info->offset.u = info->offset.v+(uv_pitch)*(src_h/4);
			info->frame_size = y_pitch*src_h + 2*uv_pitch*(src_h/4);
			layout=0x51; /* planar, 16:1:1 */
			break;
	}

	/* Assume we have 2 MB to play with */
	info->num_frames = 0x200000 / info->frame_size;
	if(info->num_frames > VID_PLAY_MAXFRAMES)
		info->num_frames = VID_PLAY_MAXFRAMES;

	/* Start at 6 MB. Let's hope it's not in use. */
	base0 = 0x600000;
	info->dga_addr = cyberblade_mem + base0;

	info->dest.pitch.y = 16;
	info->dest.pitch.u = 16;
	info->dest.pitch.v = 16;

	for(i = 0; i < info->num_frames; i++)
	{
		info->offsets[i] = info->frame_size * i;
		frames[i] = base0+info->offsets[i];
	}

	OUTPORT8(0x3d4,0x39);
	OUTPORT8(0x3d5,INPORT(0x3d5)|1);

	SRINB(0x0b); /* Select new mode */

	/* Unprotect hardware registers... */
	protect=SRINB(0x11);
	SROUTB(0x11, 0x92);

	SROUTB(0x57, 0xc0); /* Playback key function */
	SROUTB(0x21, 0x34); /* Signature control */
	SROUTB(0x37, 0x30); /* Video key mode */

        cyberblade_set_gkeys(&cyberblade_grkey);

	/* compute_scale_factor(&src_w, &drw_w, &shrink, &zoom); */
	{
		int HTotal,VTotal,HSync,VSync,Overflow,HDisp,VDisp;
		int HWinStart,VWinStart;
		int tx1,ty1,tx2,ty2;

		HTotal=CRINB(0x00);
		HSync=CRINB(0x04);
		VTotal=CRINB(0x06);
		VSync=CRINB(0x10);
		Overflow=CRINB(0x07);
		HTotal <<=3;
		HSync <<=3;
		VTotal |= (Overflow & 1) <<8;
		VTotal |= (Overflow & 0x20) <<4;
		VTotal +=4;
		VSync |= (Overflow & 4) <<6;
		VSync |= (Overflow & 0x80) <<2;

		if(CRINB(0xd1)&0x80)
		{
			int TVHTotal,TVVTotal,TVHSyncStart,TVVSyncStart,TVOverflow;
			LOGWRITE("[cyberblade] Using TV-CRTC\n");

    			HDisp=(1+CRINB(0x01))*8;
    			VDisp=1+CRINB(0x12);
    			Overflow=CRINB(0x07);
    			VDisp |= (Overflow & 2) <<7;
    			VDisp |= (Overflow & 0x40) << 3;

    			TVHTotal=CRINB(0xe0)*8;
    			TVVTotal=CRINB(0xe6);
    			TVOverflow=CRINB(0xe7);
    			if(TVOverflow&0x20) TVVTotal|=512;
    			if(TVOverflow&0x01) TVVTotal|=256;
    			TVHTotal+=40; TVVTotal+=2;

    			TVHSyncStart=CRINB(0xe4)*8;
    			TVVSyncStart=CRINB(0xf0);
    			if(TVOverflow&0x80) TVVSyncStart|=512;
			if(TVOverflow&0x04) TVVSyncStart|=256;

			HWinStart=(TVHTotal-HDisp)&15;
			HWinStart|=(HTotal-HDisp)&15;
			HWinStart+=(TVHTotal-TVHSyncStart)-49;
		}
		else
		{
			LOGWRITE("[cyberblade] Using Standard CRTC\n");
			HWinStart=(HTotal-HSync)+15;
		}
                VWinStart=(VTotal-VSync)-8;

		mp_msg(MSGT_VO, MSGL_STATUS, "[cyberblade] HTotal: 0x%x, HSStart: 0x%x\n",HTotal,HSync);
		mp_msg(MSGT_VO, MSGL_STATUS, "[cyberblade] VTotal: 0x%x, VStart: 0x%x\n",VTotal,VSync);
		tx1=HWinStart+info->dest.x;
		ty1=VWinStart+info->dest.y;
		tx2=tx1+info->dest.w;
		ty2=ty1+info->dest.h;

		CROUTW(0x86,tx1);
		CROUTW(0x88,ty1);
		CROUTW(0x8a,tx2);
		CROUTW(0x8c,ty2+3);
	}

	if(src_w==drw_w)
		hscale=0;
	else if(src_w<drw_w)
	{
		hscale=((src_w<<10)/(drw_w-2)) & 0x1fff;
	}
	else
	{
		hscale=0x8000 | ((((src_w/drw_w)-1)&7)<<10) | (((drw_w<<10)/src_w) & 0x3ff);
	}

	vscale=(src_h<<10)/(drw_h);
	if(drw_h<src_h)
		vscale=0x8000|((drw_h<<10)/(src_h));

	/* Write scale factors to hardware */

	CROUTW(0x80,hscale); /* Horizontal Scale */
	CROUTW(0x82,vscale); /* Vertical Scale */

	/* Now set the start address and data layout */
	{
		int lb = (y_pitch+2) >> 2;
		CROUTB(0x95, ((lb & 0x100)>>1) | 0x08 ); /* Linebuffer level bit 8 & threshold */
		CROUTB(0x96, (lb & 0xFF)); /* Linebuffer level */

		CROUTB(0x97, 0x00); /* VDE Flags */
		CROUTB(0xBA, 0x00); /* Chroma key */
		CROUTB(0xBB, 0x00); /* Chroma key */
		CROUTB(0xBC, 0xFF); /* Chroma key */
		CROUTB(0xBD, 0xFF); /* Chroma key */
		CROUTB(0xBE, 0x04); /* Capture control */

		if(src_w > 384)
			layout|=4; /* 2x line buffers */
		SROUTB(0x97, layout);

		CROUTW(0x90,y_pitch); /* Y Bytes per row */
		SROUTW(0x9A,uv_pitch); /* UV Bytes per row */

		switch(info->fourcc)
		{
			case IMGFMT_BGR16:
				CROUTB(0x8F, 0x24); /* VDE Flags - Edge Recovery & CSC Bypass */
				CROUTB(0xBF, 0x02); /* Video format - RGB16 */
				SROUTB(0xBE, 0x0); /* HSCB disabled */
				break;
			default:
				CROUTB(0x8F, 0x20); /* VDE Flags - Edge Recovery */
				CROUTB(0xBF, 0x00); /* Video format - YUV */
				SROUTB(0xBE, 0x00); /* HSCB disable - was 0x03*/
				break;
		}

		CROUTB(0x92, ((base0+info->offset.y) >> 3) &0xff); /* Lower 8 bits of start address */
		CROUTB(0x93, ((base0+info->offset.y) >> 11) &0xff); /* Mid 8 bits of start address */
		CROUTB(0x94, ((base0+info->offset.y) >> 19) &0xf); /* Upper 4 bits of start address */
		SROUTB(0x80, ((base0+info->offset.v) >> 3) &0xff); /* Lower 8 bits of start address */
		SROUTB(0x81, ((base0+info->offset.v) >> 11) &0xff); /* Mid 8 bits of start address */
		SROUTB(0x82, ((base0+info->offset.v) >> 19) &0xf); /* Upper 4 bits of start address */
		SROUTB(0x83, ((base0+info->offset.u) >> 3) &0xff); /* Lower 8 bits of start address */
		SROUTB(0x84, ((base0+info->offset.u) >> 11) &0xff); /* Mid 8 bits of start address */
		SROUTB(0x85, ((base0+info->offset.u) >> 19) &0xf); /* Upper 4 bits of start address */
	}

	cyberblade_set_eq(&equal);

	/* Protect hardware registers again */
	SROUTB(0x11, protect);
	return 0;
}


static int cyberblade_playback_on(void)
{
	LOGWRITE("Enable overlay\n");
	CROUTB(0x8E, 0xd4); /* VDE Flags*/

	return 0;
}


static int cyberblade_playback_off(void)
{
        LOGWRITE("Disable overlay\n");
	CROUTB(0x8E, 0xc4); /* VDE Flags*/

	return 0;
}


static int cyberblade_frame_sel(unsigned int frame)
{
	int protect;
        LOGWRITE("Frame select\n");
	protect=SRINB(0x11);
	SROUTB(0x11, 0x92);
	/* Set overlay address to that of selected frame */
	CROUTB(0x92, ((frames[frame]+YOffs) >> 3) &0xff); /* Lower 8 bits of start address */
	CROUTB(0x93, ((frames[frame]+YOffs) >> 11) &0xff); /* Mid 8 bits of start address */
	CROUTB(0x94, ((frames[frame]+YOffs) >> 19) &0xf); /* Upper 4 bits of start address */
	SROUTB(0x80, ((frames[frame]+VOffs) >> 3) &0xff); /* Lower 8 bits of start address */
	SROUTB(0x81, ((frames[frame]+VOffs) >> 11) &0xff); /* Mid 8 bits of start address */
	SROUTB(0x82, ((frames[frame]+VOffs) >> 19) &0xf); /* Upper 4 bits of start address */
	SROUTB(0x83, ((frames[frame]+UOffs) >> 3) &0xff); /* Lower 8 bits of start address */
	SROUTB(0x84, ((frames[frame]+UOffs) >> 11) &0xff); /* Mid 8 bits of start address */
	SROUTB(0x85, ((frames[frame]+UOffs) >> 19) &0xf); /* Upper 4 bits of start address */
	SROUTB(0x11, protect);
	return 0;
}

const VDXDriver cyberblade_drv = {
  "cyberblade",
  NULL,
  .probe = cyberblade_probe,
  .get_caps = cyberblade_get_caps,
  .query_fourcc = cyberblade_query_fourcc,
  .init = cyberblade_init,
  .destroy = cyberblade_destroy,
  .config_playback = cyberblade_config_playback,
  .playback_on = cyberblade_playback_on,
  .playback_off = cyberblade_playback_off,
  .frame_sel = cyberblade_frame_sel,
  .get_eq = cyberblade_get_eq,
  .set_eq = cyberblade_set_eq,
  .get_gkey = cyberblade_get_gkeys,
  .set_gkey = cyberblade_set_gkeys,
};
