/*******************************************************************************/
/* COS2000 - Compatible Operating System - LGPL v3 - Hordé Nicolas             */
/*                                                                             */
#include "vga.h"
#include "video.h"
#include "memory.h"
#include "asm.h"
#include "types.h"
#include "VGA/modes.c"

static videoinfos infos;

/*******************************************************************************/
/* Deplace l'adresse virtuelle en mode paginee */

void VGA_remap_memory(u32 vaddr)
{
	virtual_range_use_kernel(vaddr, GRPHSCREEN, ENDOFVMEM - GRPHSCREEN,
				 PAGE_NOFLAG);
}

/*******************************************************************************/
/* Detecte si le hardware est disponible, return NULL ou pointeur sur le type de pilote */
u8     *VGA_detect_hardware(void)
{
	return "LEGACY";
}

/*******************************************************************************/
/* Renvoie l'adresse du segment video */

u32 getbase(void)
{
	u32     base;
	outb(GRAPHICS, 6);
	base = inb(GRAPHICS + 1);
	base >>= 2;
	base &= 3;
	switch (base)
	{
		case 0:
		case 1:
			base = 0xA0000;
			break;
		case 2:
			base = 0xB0000;
			break;
		case 3:
			base = 0xB8000;
			break;
	}
	return base;
}

/*******************************************************************************/
/* Change le mode video courant */
/* ERR 0 aucune
/* ERR 1 mode non existant */

static u8 realsize;

u8 VGA_setvideo_mode(u8 mode)
{
	u32     index = 0;
	while (vgacapabilities[index].modenumber != 0xFF)
	{
		if (vgacapabilities[index].modenumber == mode)
		{
			infos.currentmode =
				vgacapabilities[index].modenumber;
			break;
		}
		index++;
	}
	if (infos.currentmode != mode)
		return 1;
	infos.currentwidth = vgacapabilities[index].width;
	infos.currentheight = vgacapabilities[index].height;
	infos.currentdepth = vgacapabilities[index].depth;
	infos.currentactivepage = 0;
	infos.currentshowedpage = 0;
	infos.currentcursorX = 0;
	infos.currentcursorY = 0;
	infos.currentfont1 = 0;
	infos.currentfont2 = 0;
	infos.isgraphic = vgacapabilities[index].graphic;
	infos.isblinking = false;
	infos.iscursorvisible = false;
	if (infos.isgraphic)
	{
		switch (infos.currentdepth)
		{
			case 1:
				/* mode N&B */
				infos.currentpitch = infos.currentwidth;
				realsize = 1;
				break;
			case 2:
				/* mode 4 couleurs */
				infos.currentpitch =
					(infos.currentwidth << 1);
				realsize = 2;
				break;
			case 4:
				/* mode 16 couleurs */
				infos.currentpitch = infos.currentwidth;
				realsize = 4;
				break;
			case 8:
				/* mode 256 couleurs */
				if (modes[index].
				    sequencer.Sequencer_Memory_Mode_Register
				    == 0x0E)
				{
					/* mode chainé (plus rapide mais limité en mémoire) */
					infos.currentpitch =
						infos.currentwidth;
					realsize = 8;
				}
				else
				{
					/* mode non chainé */
					infos.currentpitch =
						infos.currentwidth >> 2;
					realsize = 9;
				}
				break;
			default:
				break;
		}
		infos.pagesize = infos.currentheight * infos.currentpitch;
	}
	else
	{
		infos.currentpitch = infos.currentwidth * 2;
		infos.pagesize = infos.currentheight * infos.currentpitch;
		realsize = 0;
	}
	infos.pagesnumber = (PLANESIZE / infos.pagesize);
	infos.baseaddress =
		(modes[index].ctrc.Cursor_Location_High_Register << 8) +
		modes[index].ctrc.Cursor_Location_Low_Register + getbase();
	/* Initialise les registre "divers" */
	outb(MISC_WRITE, modes[index].misc);
	/* Initialise les registre d'etat */
	outb(STATE, 0x00);
	/* Initialise le séquenceur */
	outreg(SEQUENCER, &modes[index].sequencer, 5);
	/* Debloque le verouillage des registres controleur CRT */
	outb(CCRT, 0x11);
	outb(CCRT + 1, 0x0E);
	/* Initialise le controleur CRT */
	outreg(CCRT, &modes[index].ctrc, 25);
	/* Initialise le controleur graphique */
	outreg(GRAPHICS, &modes[index].graphic, 9);
	inb(STATE);
	/* Initialise le controleur d'attributs */
	outregsame(ATTRIBS, &modes[index].attribut, 21);
	inb(STATE);
	outb(ATTRIBS, 0x20);
	/* Initialise l'adresse des procedures de gestion graphique et les differentes
	   variables en fonction de la profondeur et du mode */
	return 0;
}

/*******************************************************************************/
/* Renvoie le nom du driver */
u8     *VGA_getvideo_drivername(void)
{
	return "VGA";
}

/*******************************************************************************/
/* Renvoie un pointeur sur la structure des capacités graphiques */

u8     *VGA_getvideo_capabilities(void)
{
	return vgacapabilities;
}

/*******************************************************************************/
/* Renvoie un pointeur sur l'état courant de la carte */
videoinfos *VGA_getvideo_info(void)
{
	return &infos;
}

/*******************************************************************************/
/* Effecture un mouvement de la mémoire centrale vers la mémoire video (linéarisée) */
u32 VGA_mem_to_video(void *src, u32 dst, u32 size, bool increment_src)
{
	u32     realdst =
		infos.baseaddress +
		infos.currentactivepage * infos.pagesize + dst;
	switch (realsize)
	{
		case 0:
			if (!increment_src)
			{
				u8      tmp = (u8) src;
				memset(realdst, tmp, size, 2);
			}
			else
			{
				memcpy(src, realdst, size, 2);
			}
			break;
		case 1:

			break;
		case 2:

			break;
		case 4:

			break;
		case 8:
			if (!increment_src)
			{
				u8      tmp = (u8) (src);
				if (size % 4 == 0)
				{
					u32     pattern =
						tmp + (tmp << 8) +
						(tmp << 16) + (tmp << 24);
					stosd(pattern, realdst,
					      (size >> 2));
				}
				else if (size % 2 == 0)
				{
					u32     pattern = tmp + (tmp << 8);
					stosw(pattern, realdst,
					      (size >> 1));
				}
				else
				{
					u32     pattern = tmp;
					stosb(pattern, realdst, size);
				}
			}
			else
			{
				if (size % 4 == 0)
				{
					movsd(src, realdst, size >> 2);
				}
				else if (size % 2 == 0)
				{
					movsw(src, realdst, size >> 1);
				}
				else
				{
					movsb(src, realdst, size);
				}
			}
			break;
		case 9:

			break;

	}

}

/*******************************************************************************/
/* Effecture un mouvement de la mémoire video (linéarisée) vers la mémoire centrale*/
u32 VGA_video_to_mem(u32 src, void *dst, u32 size)
{
	u32     realsrc =
		infos.baseaddress +
		infos.currentactivepage * infos.pagesize + src;
	switch (realsize)
	{
		case 0:
			memcpy(realsrc, dst, size, 2);
			break;
		case 1:

			break;
		case 2:

			break;
		case 4:

			break;
		case 8:

			break;
		case 9:

			break;

	}

}

/*******************************************************************************/
/* Effecture un mouvement de la mémoire video (linéarisé) vers la mémoire vidéo (linéarisée) */
u32 VGA_video_to_video(u32 src, u32 dst, u32 size)
{
	u32     base =
		infos.baseaddress +
		infos.currentactivepage * infos.pagesize;
	u32     realsrc = base + src;
	u32     realdst = base + dst;
	switch (realsize)
	{
		case 8:
		case 0:
			if (size % 4 == 0)
			{
				movsd(realsrc, realdst, size >> 2);
			}
			else if (size % 2 == 0)
			{
				movsw(realsrc, realdst, size >> 1);
			}
			else
			{
				movsb(realsrc, realdst, size);
			}
			break;
		case 1:

			break;
		case 2:

			break;
		case 4:

			break;
		case 9:

			break;

	}

}

/*******************************************************************************/
/* Fixe la page ecran de travail */

void VGA_page_set(u8 page)
{
	if (page < infos.pagesnumber)
		infos.currentactivepage = page;
}

/*******************************************************************************/
/* Affiche la page ecran specifié */

void VGA_page_show(u8 page)
{
	if (page < infos.pagesnumber)
	{
		u16     addr;
		addr = page * infos.pagesize / 2;
		outb(CCRT, 0x0C);
		outb(CCRT + 1, (addr >> 8));
		outb(CCRT, 0x0D);
		outb(CCRT + 1, (addr & 0xFF));
		infos.currentshowedpage = page;
	}
}

/*******************************************************************************/
/* Sépare l'écran en 2 a partir de la ligne Y */

static  splitY = 0;

void VGA_page_split(u16 y)
{
	if (y != 0)
	{
		u16     addr;
		if (!infos.isgraphic)
			addr = (y << 3);
		else
			addr = y;
		/* line compare pour ligne atteinte */
		outb(CCRT, 0x18);
		outb(CCRT + 1, (addr & 0xFF));
		/* overflow pour le bit 8 */

		outb(CCRT, 0x07);
		outb(CCRT + 1, (inb(CCRT + 1) & ~16) | ((addr >> 4) & 16));

		/*  Maximum Scan Line pour le bit 9 */

		outb(CCRT, 0x09);
		outb(CCRT + 1, (inb(CCRT + 1) & ~64) | ((addr >> 3) & 64));
		splitY = y;
	}
	else
	{
		/* line compare pour ligne atteinte */
		outb(CCRT, 0x18);
		outb(CCRT + 1, 0);
		/* overflow pour le bit 8 */

		outb(CCRT, 0x07);
		outb(CCRT + 1, inb(CCRT + 1) & ~16);

		/*  Maximum Scan Line pour le bit 9 */

		outb(CCRT, 0x09);
		outb(CCRT + 1, inb(CCRT + 1) & ~64);
		splitY = 0;
	}
}

/*******************************************************************************/
/* Attend la retrace verticale */

void VGA_wait_vretrace(void)
{
	while ((inb(STATE) & 8) == 0);
}

/*******************************************************************************/
/* Attend la retrace horizontale */

void VGA_wait_hretrace(void)
{
	while ((inb(STATE) & 1) == 0);
}

/*******************************************************************************/
/* Active l'affichage du curseur de texte */

void VGA_cursor_enable(void)
{
	if (!infos.isgraphic)
	{
		u8      curs;
		/* active le curseur hardware */
		outb(CCRT, 10);
		curs = inb(CCRT + 1) & ~32;
		outb(CCRT + 1, curs);
		infos.iscursorvisible = true;
	}
}

/*******************************************************************************/
/* Desactive l'affichage du curseur de texte */

void VGA_cursor_disable(void)
{
	if (!infos.isgraphic)
	{
		u8      curs;
		/* Desactive le curseur hardware */
		outb(CCRT, 10);
		curs = inb(CCRT + 1) | 32;
		outb(CCRT + 1, curs);
		infos.iscursorvisible = false;
	}
}

/*******************************************************************************/
/* Utilise le plan de bit spécifié */

void useplane(u8 plan)
{
	u8      mask;
	plan &= 3;
	mask = 1 << plan;
	/* choisi le plan de lecture */
	outb(GRAPHICS, 4);
	outb(GRAPHICS + 1, plan);
	/* choisi le plan d'ecriture */
	outb(SEQUENCER, 2);
	outb(SEQUENCER + 1, mask);
}

/*******************************************************************************/
/* fixe la position du curseur texte */

void VGA_cursor_set(u16 x, u16 y)
{
	if (!infos.isgraphic)
	{
		u16     pos;
		if (splitY == 0)
			pos = (infos.currentshowedpage * infos.pagesize /
			       2 + x + y * infos.currentwidth);
		else
			pos = (x + y * infos.currentwidth);
		outb(CCRT, 0x0F);
		outb(CCRT + 1, (u8) (pos & 0x00FF));
		outb(CCRT, 0x0E);
		outb(CCRT + 1, (u8) ((pos & 0xFF00) >> 8));
		infos.currentcursorX = x;
		infos.currentcursorY = y;
	}
}

/*******************************************************************************/
/* Charge une nouvelle police de caractère */
/* ERR 1 mode graphique activé*/

u32 VGA_font_load(u8 * def, u8 size, u8 font)
{
	if (infos.isgraphic)
		return 1;
	u8      oldregs[5] = { 0, 0, 0, 0, 0 };
	u8     *base;
	u16     i;
	if (font > 7)
		return 1;
	if (font < 4)
		base = (u8 *) (getbase() + (font << 14));
	else
		base = (u8 *) (getbase() +
			       ((((font - 4) << 1) + 1) << 13));
	/* sauve les anciens registres */
	outb(SEQUENCER, 2);
	oldregs[0] = inb(SEQUENCER + 1);
	outb(SEQUENCER, 4);
	oldregs[1] = inb(SEQUENCER + 1);
	/* Adressage paire/impair desactivé (lineaire) */
	outb(SEQUENCER + 1, oldregs[1] | 0x04);
	outb(GRAPHICS, 4);
	oldregs[2] = inb(GRAPHICS + 1);
	outb(GRAPHICS, 5);
	oldregs[3] = inb(GRAPHICS + 1);
	/* Adressage paire/impair desactivé (lineaire) */
	outb(GRAPHICS + 1, oldregs[3] & ~0x10);
	outb(GRAPHICS, 6);
	oldregs[4] = inb(GRAPHICS + 1);
	/* Adressage paire/impair desactivé (lineaire) */
	outb(GRAPHICS + 1, oldregs[4] & ~0x02);
	/* utilisation du plan N°2 */
	useplane(2);
	for (i = 0; i < 256; i++)
	{
		memcpy(def, base + i * 32, size, 1);
		def += size;
	}
	outb(SEQUENCER, 2);
	outb(SEQUENCER + 1, oldregs[0]);
	outb(SEQUENCER, 4);
	outb(SEQUENCER + 1, oldregs[1]);
	outb(GRAPHICS, 4);
	outb(GRAPHICS + 1, oldregs[2]);
	outb(GRAPHICS, 5);
	outb(GRAPHICS + 1, oldregs[3]);
	outb(GRAPHICS, 6);
	outb(GRAPHICS + 1, oldregs[4]);
	return 0;
}

/*******************************************************************************/
/* Fixe le N° de la police de caractère a utiliser */

void VGA_font1_set(u8 num)
{
	if (!infos.isgraphic)
	{
		num &= 0x07;
		outb(SEQUENCER, 3);
		outb(SEQUENCER + 1,
		     (inb(SEQUENCER + 1) & 0xEC) | ((num & 0x03) +
						    ((num & 0x04) << 2)));
		infos.currentfont1 = num;
	}
}

/*******************************************************************************/
/* Fixe le N° de la police de caractère a utiliser */

void VGA_font2_set(u8 num)
{
	if (!infos.isgraphic)
	{
		num &= 0x07;
		outb(SEQUENCER, 3);
		outb(SEQUENCER + 1,
		     (inb(SEQUENCER + 1) & 0xD3) | (((num & 0x03) << 2) +
						    ((num & 0x04) << 3)));
		infos.currentfont2 = num;
	}
}

/*******************************************************************************/
/* Autorise le clignotement */

void VGA_blink_enable(void)
{
	if (!infos.isgraphic)
	{
		outb(CCRT, 0x10);
		outb(CCRT + 1, (inb(SEQUENCER + 1) | 0x04));
		infos.isblinking = true;
	}
}

/*******************************************************************************/
/* Annule le clignotement */

void VGA_blink_disable(void)
{
	if (!infos.isgraphic)
	{
		outb(CCRT, 0x10);
		outb(CCRT + 1, (inb(SEQUENCER + 1) & ~0x04));
		infos.isblinking = false;
	}
}

/*******************************************************************************/
/* Envoie une série d'octet a destination d'une portion de mémoire
vers le registre spécifié */

void outreg(u16 port, u8 * src, u16 num)
{
	int     i;
	for (i = 0; i < num; i++)
	{
		outb(port, i);
		outb(port + 1, *src++);
	}
}

/*******************************************************************************/
/* Envoie une série d'octet a destination d'une portion de mémoire
vers le registre spécifié (accés data et index confondu) */

void outregsame(u16 port, u8 * src, u16 num)
{
	int     i;
	for (i = 0; i < num; i++)
	{
		inb(port);
		outb(port, i);
		outb(port, *src++);
	}
}

/*******************************************************************************/
/* Récupère une série d'octet en provenance d'un registre spécifié
vers portion de mémoire */

void inreg(u16 port, u8 * src, u16 num)
{
	int     i;
	for (i = 0; i < num; i++)
	{
		outb(port, i);
		*src++ = inb(port + 1);
	}
}

/*******************************************************************************/
/* Récupère une série d'octet en provenance d'un registre spécifié
vers portion de mémoire (accés data et index confondu) */

void inregsame(u16 port, u8 * src, u16 num)
{
	int     i;
	for (i = 0; i < num; i++)
	{
		inb(port);
		outb(port, i);
		*src++ = inb(port);
	}
}

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