/*
 * (C) Copyright 2001
 * Denis Peter, MPL AG Switzerland
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * 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 USA
 *
 * Note: Parts of these software are imported from 
 *       - UBL, The Universal Talkware Boot Loader
 *         Copyright (C) 2000 Universal Talkware Inc.
 *       - Linux
 *
 * 
 */

#include <ppcboot.h>
#include <command.h>
#include <asm/processor.h>
#include <devices.h>
#include "video.h"
#ifdef CONFIG_4xx
#include <405gp_pci.h>
#endif
#include "vga_table.h"
 

#ifdef CONFIG_VIDEO_CT69000
#define VIDEO_VEND_ID 0x102C
#define VIDEO_DEV_ID  0x00C0
#else
#error  CONFIG_VIDEO_CT69000 must be defined
#endif

/* 
 * Routine for resent board info to video
 * resides in pip405.c
 */
extern void video_write_board_info(void); 

#undef VGA_DEBUG

#ifdef VGA_DEBUG
#define	PRINTF(fmt,args...)	printf (fmt ,##args)
#else
#define PRINTF(fmt,args...)
#endif

#define VGA_MAXROWS	25
#define VGA_MAXCOLS 80

#define CRTC_CURSH	14 /* cursor high pos */
#define CRTC_CURSL	15 /* cursor low pos */

/* description of the hardware layout */

#define ATTRI_INDEX		CFG_ISA_IO_BASE_ADDRESS | 0x3c0				/* Index and Data write port of the attribute Registers */
#define ATTRI_DATA		CFG_ISA_IO_BASE_ADDRESS | 0x3c1				/* Data port of the attribute Registers */
#define STATUS_REG0		CFG_ISA_IO_BASE_ADDRESS | 0x3c2				/* Status Register 0 (read only) */
#define MSR_REG_W			CFG_ISA_IO_BASE_ADDRESS | 0x3c2				/* Misc. Output Register (write only) */
#define SEQ_INDEX			CFG_ISA_IO_BASE_ADDRESS | 0x3c4				/* Index port of the Sequencer Controller */
#define SEQ_DATA			CFG_ISA_IO_BASE_ADDRESS | 0x3c5				/* Data port of the Sequencer Controller */
#define COL_PAL_MASK	CFG_ISA_IO_BASE_ADDRESS | 0x3c6				/* Color Palette Mask */
#define COL_PAL_STAT	CFG_ISA_IO_BASE_ADDRESS | 0x3c7				/* Color Palette Status (read only) */
#define COL_PAL_IND_R	CFG_ISA_IO_BASE_ADDRESS | 0x3c7				/* Color Palette Read Mode Index (write only) */
#define COL_PAL_IND_W	CFG_ISA_IO_BASE_ADDRESS | 0x3c8				/* Color Palette Write Mode Index */
#define COL_PAL_DATA	CFG_ISA_IO_BASE_ADDRESS | 0x3c9				/* Color Palette Data Port */
#define FCR_REG_R			CFG_ISA_IO_BASE_ADDRESS | 0x3ca				/* Feature Control Register (read only) */
#define MSR_REG_R			CFG_ISA_IO_BASE_ADDRESS | 0x3cc				/* Misc. Output Register (read only) */
#define GR_INDEX			CFG_ISA_IO_BASE_ADDRESS | 0x3ce				/* Index port of the Graphic Controller Registers */
#define GR_DATA				CFG_ISA_IO_BASE_ADDRESS | 0x3cf				/* Data port of the Graphic Controller Registers */
#define FP_INDEX			CFG_ISA_IO_BASE_ADDRESS | 0x3d0				/* Index port of the Flat panel Registers */
#define FP_DATA				CFG_ISA_IO_BASE_ADDRESS | 0x3d1				/* Data port of the Flat panel Registers */
#define MR_INDEX			CFG_ISA_IO_BASE_ADDRESS | 0x3d2				/* Index Port of the Multimedia Extension */				
#define MR_DATA				CFG_ISA_IO_BASE_ADDRESS | 0x3d3				/* Data Port of the Multimedia Extension */
#define CRT_INDEX			CFG_ISA_IO_BASE_ADDRESS | 0x3d4				/* Index port of the CRT Controller */
#define CRT_DATA			CFG_ISA_IO_BASE_ADDRESS | 0x3d5				/* Data port of the CRT Controller */
#define XREG_INDEX		CFG_ISA_IO_BASE_ADDRESS | 0x3d6				/* Extended Register index */
#define XREG_DATA			CFG_ISA_IO_BASE_ADDRESS | 0x3d7				/* Extended Register data */
#define STATUS_REG1		CFG_ISA_IO_BASE_ADDRESS | 0x3da				/* Input Status Register 1 (read only) */
#define FCR_REG_W			CFG_ISA_IO_BASE_ADDRESS | 0x3da				/* Feature Control Register (write only) */


static unsigned char * video_fb;		/* Frame buffer */

/* current hardware state */

static int	video_row;
static int	video_col;
static unsigned char video_attr;

static unsigned long font_base_addr;
/**********************************************************************
 * some forward declerations...
 */
int video_init(int busdevfunc);
void vga_set_attrib(void);
void vga_set_crt(void);
void vga_set_dac(void);
void vga_set_gr(void);
void vga_set_seq(void);
void vga_set_xreg(void);
void vga_write_sr(unsigned char reg,unsigned char val);
void vga_write_gr(unsigned char reg,unsigned char val);
void vga_write_cr(unsigned char reg,unsigned char val);
void vga_set_font(void);

/***************************************************************************
 * Init VGA Device
 */

int drv_video_init(void)
{
	int error, devices = 1 ;
  device_t vgadev ;
	int	busdevfunc;

	busdevfunc=PCI_Find_Device(VIDEO_VEND_ID,VIDEO_DEV_ID); /* get PCI Device ID */
	if(busdevfunc==-1) {
		printf("Error VGA Controller (%04X,%04X) not found\n",VIDEO_VEND_ID,VIDEO_DEV_ID);
		return -1;
	}
	video_init(busdevfunc);
	video_write_board_info();
  memset (&vgadev, 0, sizeof(vgadev));

  strcpy(vgadev.name, "vga");
  vgadev.flags =  DEV_FLAGS_OUTPUT | DEV_FLAGS_SYSTEM;
  vgadev.putc = video_putc;
  vgadev.puts = video_puts;
  vgadev.getc = NULL;
  vgadev.tstc = NULL;

  error = device_register (&vgadev);
		
  return (error == 0) ? devices : error ;
}


/***********************************************************
 * VGA Initializing
 */

int video_init(int busdevfunc)
{
  font_base_addr = PCI_Read_CFG_Reg(busdevfunc, PCI_CFG_BASE_ADDRESS_0, 4);

  video_fb = (char*)font_base_addr; /* we look into the big linaer memory area */

  /* set the extended Registers */
	vga_set_xreg();

 	/* set IO Addresses to 0x3Dx (color mode ) */
	out8(MSR_REG_W,0x01);

  /* Feature Control Register:
     Bits 7-4 Reserved = 0
     Bit  3   Vertical Sync select = 1 = Enabled
     Bits 2-0 Reserved = 010 = as read from memory.
  */
  out8(FCR_REG_W,0x02);


  /* Miscelaneous output Register:
     Bits 7-6 (num lines) = 01 = VGA 400 lines,
     Bit  5   (Odd/Even Page) =  1 = Sleect high page of memory,
     Bit  4   reserved = 0,
     Bits 3-2 (Clocl Select) = 01 = 28.322Mhz
     Bit  1 = Display Ram Enable = 1 = Enable processor access.
     Bit  0 = Io Address Select = 1 = Color Graphics Enulation.
  */
  out8(MSR_REG_W,0x67);
  /* set the palette */
  vga_set_dac();
  /* set the attributes (before we bring up the engine
     then we dont have to wait for refresh).
  */
  vga_set_attrib();

  /* set the crontroller register. */
  vga_set_crt();

	vga_write_sr(0x00,0x01); /* synchronous reset */
	vga_write_sr(0x01,0x00); /* clocking mode */
	vga_write_sr(0x02,0x03); /* write to map 0, 1 */
	vga_write_sr(0x03,0x00); /* select character map 0 */
	vga_write_sr(0x04,0x03); /* even-odd addressing */
	vga_write_sr(0x00,0x03); /* clear synchronous reset */

  vga_set_seq(); /* Set the extended sr's. */

  vga_set_gr(); /* Set the graphic registers. */

  /* load the font */
  vga_set_font();

  /* initialize the rol/col counts and the text attribute. */
  video_row=0;
  video_col=0;
  video_attr = VGA_ATTR_CLR_WHT;

  /* Clear the video ram */
  video_clear();

  return 1;
}

void vga_set_font(void)
{
	int i,j;
	char *fontmap;
	fontmap = (char *)font_base_addr;
	
	vga_write_sr(0x00,0x01); /* synchronous reset */
	vga_write_sr(0x04,0x06); /* sequential addressing */
	vga_write_sr(0x02,0x04); /* write to map 2 */
	vga_write_sr(0x00,0x03); /* clear synchronous reset */

	vga_write_gr(0x04,0x02); /* select map 2 */
	vga_write_gr(0x05,0x00); /* disable odd-even addressing */
	vga_write_gr(0x06,0x00); /* map start at 0xa0000 */
	
	for(i=0;i<0x100;i++) {
		for(j=0;j<0x10;j++) {
			*((char *)fontmap+i*32+j)=(char)fontdata_8x16[i*16+j];
		}
	}
	vga_write_sr(0x00,0x01); /* synchronous reset */
	vga_write_sr(0x02,0x03); /* write to map 0 and 1 */
	vga_write_sr(0x04,0x03); /* odd-even addressing */
	vga_write_sr(0x03,0x00); /* Character map 0 & 1 */
	vga_write_sr(0x00,0x03); /* clear synchronous reset */

	vga_write_gr(0x04,0x00); /* select map 0 for CPU */
	vga_write_gr(0x05,0x10); /* enable odd-even addressing */
	vga_write_gr(0x06,0x0E); /* map start at 0xb8000 */
}


/* since we are BIG endian, swap attributes and char */
unsigned short vga_swap_short(unsigned short val)
{
	unsigned short swapped;
	swapped = ((val & 0xff)<<8) | ((val & 0xff00)>>8);
	return swapped;
}

/****************************************************
 * Routines usable Outside world
 */
 
/* scolls the text up row rows */
void video_scroll(int row)
{
  unsigned short clear = ((unsigned short)video_attr << 8) | (' ');
  unsigned short* addr16 = &((unsigned short *)video_fb)[(VGA_MAXROWS-row)*VGA_MAXCOLS];
  int i;

	clear=vga_swap_short(clear);
  memcpy(video_fb, video_fb+row*(VGA_MAXCOLS*2), (VGA_MAXROWS-row)*(VGA_MAXCOLS*2));
  for (i = 0 ; i < row * VGA_MAXCOLS ; i++)
    addr16[i] = clear;
  video_row-=row;
  video_col=0;
}


unsigned long video_cursor(int col, int row)
{
   unsigned short off = row * VGA_MAXCOLS + col ;
   unsigned long saved = (video_col << 16) | (video_row & 0xFFFF);
   video_col = col;
   video_row = row;
   
   vga_write_cr(CRTC_CURSH,(unsigned char)((off & 0xff00)>>8)); /* Cursor pos. high */
   vga_write_cr(CRTC_CURSL,(unsigned char)(off & 0xff));          /* Cursor pos. low */
   return saved;
}

void video_set_lxy(unsigned long lxy)
{
  int col = (lxy >> 16) & 0xFFFF;
  int row = lxy & 0xFFFF;
  video_cursor(col,row);
}

unsigned long video_get_lxy(void)
{
   return (video_col << 16) | (video_row & 0xFFFF);
}

void video_clear(void)
{
  int i;
  unsigned short clear = ((unsigned short)video_attr << 8) | (' ');
  unsigned short * addr16 = (unsigned short * )video_fb;
	clear=vga_swap_short(clear);
  video_row = video_col = 0;
  for (i = 0 ; i < 2000 ; i++) {
    addr16[i] = clear;
  }
}

void video_copy(unsigned short *buffer)
{
  int i;
  unsigned short * addr16 = (unsigned short * )video_fb;
  for (i = 0 ; i < 2000 ; i++) {
    buffer[i] = addr16[i];
  }
}

void video_write(unsigned short *buffer)
{
  int i;
  unsigned short * addr16 = (unsigned short *)video_fb;
  for (i = 0 ; i < 2000 ; i++) {
    addr16[i] = buffer[i];
  }
}

void video_putc(char ch)
{
   char* addr;
   
   switch (ch) {
    case '\n':
      video_col=0;
      video_row++;
      break;
    case '\r':
      video_col=0;
      break;
    case '\t':
      video_col += 8 - video_col % 8;
      break;
    case '\a':
/*      beep(); */
      break;
    case '\b':
      if(video_col)
				video_col--;
      else 
				return;
      break;
    default:
      addr = video_fb + 2 * video_row * 80 + 2 * video_col;
      
      *((char *)addr+1) = (char) video_attr;
      *((char *)addr) = (char) ch;
      video_col++;
      if (video_col > (VGA_MAXCOLS-1)) {
	 			video_row++;
	 			video_col=0;
      }
   }

   /* If we're on the bottom of the secreen, wrap one row */
   if (video_row > (VGA_MAXROWS-1))
     video_scroll(1);
    video_cursor(video_col, video_row);
}


unsigned char video_set_attr(unsigned char attr) 
{
  unsigned char saved_attr = video_attr;
  video_attr = attr;
  return saved_attr;
}

unsigned char video_set_attr_xy(unsigned char attr, int x, int y) 
{
  unsigned char *addr = video_fb + (x * 80 + y) * 2 + 1;
  unsigned char saved_attr = *addr;
  *addr = attr;
  return saved_attr;
}

/* put char at xy */
void video_putc_xy(char ch, int x, int y) 
{
  video_col = x;
  video_row = y;
  video_putc(ch);
}

/* put char at xy relative to the position */
void video_putc_rxy(char ch, int x, int y) 
{
  video_col += x;
  video_row += y;
  video_putc(ch);
}

/* put char with attribute at xy */
void video_putc_axy(char ch, char attr, int x, int y) 
{
  unsigned char saved_attr = video_set_attr(attr);
  video_col = x;
  video_row = y;
  video_putc(ch);
  video_set_attr(saved_attr);
}

void video_puts(const char *s)
{
   while(*s) {
      video_putc(*s);
      s++;
   }
}

void video_puts_a(const char *s, char attr) 
{
  unsigned char saved_attr = video_set_attr(attr);
  video_puts(s);
  video_set_attr(saved_attr);
}

void video_puts_xy(const char *s, int x, int y) 
{
  video_cursor(x,y);
  video_puts(s);
}

void video_puts_axy(const char *s, char attr, int x, int y) 
{
  unsigned char saved_attr = video_set_attr(attr);
  video_puts_xy(s, x, y);
  video_set_attr(saved_attr);
}

void video_wipe_ca_area(unsigned char ch, char attr, int x, int y, int w, int h) 
{
  int r, c;
  /* better to do this as word writes */ 
  unsigned short * addr16 = (unsigned short  *)video_fb + (y * 80 + x);
  unsigned short charattr = (unsigned short)ch << 8 | attr;
	charattr=vga_swap_short(charattr);
  for (r = 0 ; r < h ; r++, addr16 += 80) {
    for (c = 0 ; c < w ; c++) {
      addr16[c] = charattr;
    }
  }
}

void video_wipe_a_area(unsigned char attr, int x, int y, int w, int h) 
{
  int r, c;
  /* better to do this as word writes */ 
  unsigned short * addr16 = (unsigned short *)video_fb + (y * 80 + x);
  for (r = 0 ; r < h ; r++, addr16 += 80) {
    for (c = 0 ; c < w ; c++) {
      ((char*)addr16)[c*2+1] = attr;
    }
  }
}

void video_wipe_c_area(unsigned char ch, int x, int y, int w, int h) 
{
  int r, c;
  /* better to do this as word writes */ 
  unsigned short * addr16 = (unsigned short  *)video_fb + (y * 80 + x);
  for (r = 0 ; r < h ; r++, addr16 += 80) {
    for (c = 0 ; c < w ; c++) {
      ((char*)addr16)[c*2] = ch;
    }
  }
}


/*
tl t tr
l    l
bl b br
*/
typedef struct {
  unsigned char tl; /* top left corner */
  unsigned char t;  /* top edge */
  unsigned char tr; /* top right corner */
  unsigned char l;  /* left edge */
  unsigned char r;  /* right edge */
  unsigned char bl; /* bottom left corner */
  unsigned char b;  /* bottom edge */
  unsigned char br; /* bottom right corner */
} box_chars_t;

box_chars_t sbox_chars = {
  0xDA, 0xC4, 0xBF,
  0xB3,       0xB3,
  0xC0, 0xC4, 0xD9
};

box_chars_t dbox_chars = {
  0xC9, 0xCD, 0xBB,
  0xBA,       0xBA,
  0xC8, 0xCD, 0xBC
};

static char cmap[] = "0123456789ABCDEF";
void video_putchex(char c) 
{
  video_putc(cmap[(c >> 4 ) & 0xF]);
  video_putc(cmap[c & 0xF]);
}

void video_putchexl(char c) 
{
  video_putc(cmap[c & 0xF]);
}

void video_putchexh(char c) 
{
  video_putc(cmap[(c >> 4) & 0xF]);
}

#define VGA_CELL_CA(a,c) (((unsigned short)c<<8)|a) /* for BIG endians */

void video_gbox_area(box_chars_t *box_chars_p, int x, int y, int w, int h) 
{
  int r, c;
  /* better to do this as word writes */ 
  unsigned short* addr16 = (unsigned short *)video_fb + (y * VGA_MAXCOLS + x);
  for (r = 0 ; r < h ; r++, addr16 += VGA_MAXCOLS) {
    if (r == 0) {
      addr16[0]   = VGA_CELL_CA(video_attr, box_chars_p->tl);
      addr16[w-1] = VGA_CELL_CA(video_attr, box_chars_p->tr);
      for (c = 1 ; c < w - 1 ; c++)
				addr16[c] = VGA_CELL_CA(video_attr, box_chars_p->t);
    } else if (r == h - 1) {
      addr16[0] = VGA_CELL_CA(video_attr, box_chars_p->bl);
      addr16[w-1] = VGA_CELL_CA(video_attr, box_chars_p->br);
      for (c = 1 ; c < w - 1 ; c++)
				addr16[c] = VGA_CELL_CA(video_attr, box_chars_p->b);
    } else {
      addr16[0] = VGA_CELL_CA(video_attr, box_chars_p->l);
      addr16[w-1] = VGA_CELL_CA(video_attr, box_chars_p->r);
    }
  }
}

/* Writes a box on the screen */
void video_box_area(int x, int y, int w, int h) {
  video_gbox_area(&sbox_chars, x, y, w, h);
}
/*writes a box with double lines on the screen */
void video_dbox_area(int x, int y, int w, int h) {
  video_gbox_area(&dbox_chars, x, y, w, h);
}

/* routines to set the VGA registers */

/* set attributes */
void vga_set_attrib(void)
{
	int i;
	unsigned char status;
	
	status=in8(STATUS_REG1);
	i=0;
	
	while(attr[i].reg!=0xFF) {
		out8(ATTRI_INDEX,attr[i].reg);
		out8(ATTRI_INDEX,attr[i].val); /* Attribute uses index for index and data */
		i++;
	}
	out8(ATTRI_INDEX,0x20); /* unblank the screen */
}

/* set CRT Controller Registers */
void vga_set_crt(void)
{
	int i;
	i=0;
	while(crtc[i].reg!=0xFF) {
		out8(CRT_INDEX,crtc[i].reg);
		out8(CRT_DATA,crtc[i].val); 
		i++;
	}
}
/* Set Palette Registers (DAC) */
void vga_set_dac(void)
{
	int i;
	for(i=0;i<256;i++) {
		out8(COL_PAL_IND_W,(unsigned char)i);
		out8(COL_PAL_DATA,dac[i][0]); /* red */
		out8(COL_PAL_DATA,dac[i][1]); /* green */
		out8(COL_PAL_DATA,dac[i][2]); /* blue */
	}
	out8(COL_PAL_MASK,0xff); /* set mask */
}
/* set Graphic Controller Register */
void vga_set_gr(void)
{
	int i;
	i=0;
	while(grmr[i].reg!=0xFF) {
		out8(GR_INDEX,grmr[i].reg);
		out8(GR_DATA,grmr[i].val); 
		i++;
	}
}

/* Set Sequencer Registers */
void vga_set_seq(void)
{
	int i;
	i=0;
	while(seq[i].reg!=0xFF) {
		out8(SEQ_INDEX,seq[i].reg);
		out8(SEQ_DATA,seq[i].val); 
		i++;
	}
}


/* Set Extension Registers */
void vga_set_xreg(void)
{
	int i;
	i=0;
	while(xreg[i].reg!=0xFF) {
		out8(XREG_INDEX,xreg[i].reg);
		out8(XREG_DATA,xreg[i].val); 
		i++;
	}
}

/************************************************************
 * some helping routines
 */
 
void vga_write_sr(unsigned char reg,unsigned char val)
{
	out8(SEQ_INDEX,reg);
	out8(SEQ_DATA,val); 
}

 
void vga_write_gr(unsigned char reg,unsigned char val)
{
	out8(GR_INDEX,reg);
	out8(GR_DATA,val); 
}

void vga_write_cr(unsigned char reg,unsigned char val)
{
	out8(CRT_INDEX,reg);
	out8(CRT_DATA,val); 
}


#if 0
void video_dump_reg(void)
{
	/* first dump attributes */
	int i;
	unsigned char status;

	
	printf("Extended Regs:\n");	
	i=0;
	while(xreg[i].reg!=0xFF) {
		out8(XREG_INDEX,xreg[i].reg);
		status=in8(XREG_DATA);
		printf("XR%02X is %02X, should be %02X\n",xreg[i].reg,status,xreg[i].val);
		i++;
	}
	printf("Sequencer Regs:\n");	
	i=0;
	while(seq[i].reg!=0xFF) {
		out8(SEQ_INDEX,seq[i].reg);
		status=in8(SEQ_DATA);
		printf("SR%02X is %02X, should be %02X\n",seq[i].reg,status,seq[i].val);
		i++;
	}
	printf("Graphic Regs:\n");	
	i=0;
	while(grmr[i].reg!=0xFF) {
		out8(GR_INDEX,grmr[i].reg);
		status=in8(GR_DATA);
		printf("GR%02X is %02X, should be %02X\n",grmr[i].reg,status,grmr[i].val);
		i++;
	}
	printf("CRT Regs:\n");	
	i=0;
	while(crtc[i].reg!=0xFF) {
		out8(CRT_INDEX,crtc[i].reg);
		status=in8(CRT_DATA);
		printf("CR%02X is %02X, should be %02X\n",crtc[i].reg,status,crtc[i].val);
		i++;
	}
	printf("Attributes:\n");	
	status=in8(STATUS_REG1);
	i=0;
	while(attr[i].reg!=0xFF) {
		out8(ATTRI_INDEX,attr[i].reg);
		status=in8(ATTRI_DATA);
		out8(ATTRI_INDEX,attr[i].val); /* Attribute uses index for index and data */
		printf("AR%02X is %02X, should be %02X\n",attr[i].reg,status,attr[i].val);
		i++;
	}
}
#endif





















