/*      $NetBSD: vga.c,v 1.6 2009/03/18 10:22:34 cegger Exp $   */

/*-
* Copyright (C) 1995-1997 Gary Thomas ([email protected])
* All rights reserved.
*
* VGA 'glass TTY' emulator
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
*    must display the following acknowledgement:
*      This product includes software developed by Gary Thomas.
* 4. The name of the author may not be used to endorse or promote products
*    derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifdef CONS_VGA
#include <lib/libsa/stand.h>
#include <lib/libkern/libkern.h>
#include "boot.h"

#define COL             80
#define ROW             25
#define CHR             2
#define MONO_BASE       0x3B4
#define MONO_BUF        0xB0000
#define CGA_BASE        0x3D4
#define CGA_BUF         0xB8000

u_char background = 0;  /* Black */
u_char foreground = 7;  /* White */

u_int addr_6845;
u_short *Crtat;
int lastpos;
int scroll;

/*
* The current state of virtual displays
*/
struct screen {
       u_short *cp;            /* the current character address */
       enum state {
               NORMAL,                 /* no pending escape */
               ESC,                    /* saw ESC */
               EBRAC,                  /* saw ESC[ */
               EBRACEQ                 /* saw ESC[= */
       } state;                /* command parser state */
       int     cx;             /* the first escape seq argument */
       int     cy;             /* the second escap seq argument */
       int     *accp;          /* pointer to the current processed argument */
       int     row;            /* current column */
       int     so;             /* standout mode */
       u_short color;          /* normal character color */
       u_short color_so;       /* standout color */
       u_short save_color;     /* saved normal color */
       u_short save_color_so;  /* saved standout color */
} screen;

/*
* Color and attributes for normal, standout and kernel output
* are stored in the least-significant byte of a u_short
* so they don't have to be shifted for use.
* This is all byte-order dependent.
*/
#define CATTR(x) (x)            /* store color/attributes un-shifted */
#define ATTR_ADDR(which) (((u_char *)&(which))+1) /* address of attributes */

u_short pccolor;                /* color/attributes for tty output */
u_short pccolor_so;             /* color/attributes, standout mode */

static void cursor(void);
static void initscreen(void);
void fillw(u_short, u_short *, int);
void video_on(void);
void video_off(void);

/*
* cursor() sets an offset (0-1999) into the 80x25 text area
*/
static void
cursor(void)
{
       int pos = screen.cp - Crtat;

       if (lastpos != pos) {
               outb(addr_6845, 14);
               outb(addr_6845+1, pos >> 8);
               outb(addr_6845, 15);
               outb(addr_6845+1, pos);
               lastpos = pos;
       }
}

static void
initscreen(void)
{
       struct screen *d = &screen;

       pccolor = CATTR((background<<4)|foreground);
       pccolor_so = CATTR((foreground<<4)|background);
       d->color = pccolor;
       d->save_color = pccolor;
       d->color_so = pccolor_so;
       d->save_color_so = pccolor_so;
}


#define wrtchar(c, d) { \
       *(d->cp) = c; \
       d->cp++; \
       d->row++; \
}

void
fillw(u_short val, u_short *buf, int num)
{
       /* Need to byte swap value */
       u_short tmp;

       tmp = val;
       while (num-- > 0)
               *buf++ = tmp;
}

/*
* vga_putc (nee sput) has support for emulation of the 'ibmpc' termcap entry.
* This is a bare-bones implementation of a bare-bones entry
* One modification: Change li#24 to li#25 to reflect 25 lines
* "ca" is the color/attributes value (left-shifted by 8)
* or 0 if the current regular color for that screen is to be used.
*/
void
vga_putc(int c)
{
       struct screen *d = &screen;
       u_short *base;
       int i, j;
       u_short *pp;

       base = Crtat;

       switch (d->state) {
       case NORMAL:
               switch (c) {
               case 0x0:               /* Ignore pad characters */
                       return;

               case 0x1B:
                       d->state = ESC;
                       break;

               case '\t':
                       do {
                               wrtchar(d->color | ' ', d);
                       } while (d->row % 8);
                       break;

               case '\b':  /* non-destructive backspace */
                       if (d->cp > base) {
                               d->cp--;
                               d->row--;
                               if (d->row < 0)
                                       d->row += COL;  /* prev column */
                       }
                       break;

               case '\n':
                       d->cp += COL;
               case '\r':
                       d->cp -= d->row;
                       d->row = 0;
                       break;

               case '\007':
                       break;

               default:
                       if (d->so) {
                               wrtchar(d->color_so|(c<<8), d);
                       } else {
                               wrtchar(d->color | (c<<8), d);
                       }
                       if (d->row >= COL)
                               d->row = 0;
                       break;
               }
               break;

       case EBRAC:
               /*
                * In this state, the action at the end of the switch
                * on the character type is to go to NORMAL state,
                * and intermediate states do a return rather than break.
                */
               switch (c) {
               case 'm':
                       d->so = d->cx;
                       break;

               case 'A': /* back one row */
                       if (d->cp >= base + COL)
                               d->cp -= COL;
                       break;

               case 'B': /* down one row */
                       d->cp += COL;
                       break;

               case 'C': /* right cursor */
                       d->cp++;
                       d->row++;
                       break;

               case 'D': /* left cursor */
                       if (d->cp > base) {
                               d->cp--;
                               d->row--;
                               if (d->row < 0)
                                       d->row += COL;  /* prev column ??? */
                       }
                       break;

               case 'J': /* Clear to end of display */
                       fillw(d->color|(' '<<8), d->cp, base + COL * ROW - d->cp);
                       break;

               case 'K': /* Clear to EOL */
                       fillw(d->color|(' '<<8), d->cp, COL - (d->cp - base) % COL);
                       break;

               case 'H': /* Cursor move */
                       if (d->cx > ROW)
                               d->cx = ROW;
                       if (d->cy > COL)
                               d->cy = COL;
                       if (d->cx == 0 || d->cy == 0) {
                               d->cp = base;
                               d->row = 0;
                       } else {
                               d->cp = base + (d->cx - 1) * COL + d->cy - 1;
                               d->row = d->cy - 1;
                       }
                       break;

               case '_': /* set cursor */
                       if (d->cx)
                               d->cx = 1;              /* block */
                       else
                               d->cx = 12;     /* underline */
                       outb(addr_6845, 10);
                       outb(addr_6845+1, d->cx);
                       outb(addr_6845, 11);
                       outb(addr_6845+1, 13);
                       break;

               case ';': /* Switch params in cursor def */
                       d->accp = &d->cy;
                       return;

               case '=': /* ESC[= color change */
                       d->state = EBRACEQ;
                       return;

               case 'L':       /* Insert line */
                       i = (d->cp - base) / COL;
                       /* avoid deficiency of bcopy implementation */
                       /* XXX: comment and hack relevant? */
                       pp = base + COL * (ROW-2);
                       for (j = ROW - 1 - i; j--; pp -= COL)
                               memmove(pp + COL, pp, COL * CHR);
                       fillw(d->color|(' '<<8), base + i * COL, COL);
                       break;

               case 'M':       /* Delete line */
                       i = (d->cp - base) / COL;
                       pp = base + i * COL;
                       memmove(pp, pp + COL, (ROW-1 - i)*COL*CHR);
                       fillw(d->color|(' '<<8), base + COL * (ROW - 1), COL);
                       break;

               default: /* Only numbers valid here */
                       if ((c >= '0') && (c <= '9')) {
                               *(d->accp) *= 10;
                               *(d->accp) += c - '0';
                               return;
                       } else
                               break;
               }
               d->state = NORMAL;
               break;

       case EBRACEQ: {
               /*
                * In this state, the action at the end of the switch
                * on the character type is to go to NORMAL state,
                * and intermediate states do a return rather than break.
                */
               u_char *colp;

               /*
                * Set foreground/background color
                * for normal mode, standout mode
                * or kernel output.
                * Based on code from kentp@svmp03.
                */
               switch (c) {
               case 'F':
                       colp = ATTR_ADDR(d->color);
       do_fg:
                       *colp = (*colp & 0xf0) | (d->cx);
                       break;

               case 'G':
                       colp = ATTR_ADDR(d->color);
       do_bg:
                       *colp = (*colp & 0xf) | (d->cx << 4);
                       break;

               case 'H':
                       colp = ATTR_ADDR(d->color_so);
                       goto do_fg;

               case 'I':
                       colp = ATTR_ADDR(d->color_so);
                       goto do_bg;

               case 'S':
                       d->save_color = d->color;
                       d->save_color_so = d->color_so;
                       break;

               case 'R':
                       d->color = d->save_color;
                       d->color_so = d->save_color_so;
                       break;

               default: /* Only numbers valid here */
                       if ((c >= '0') && (c <= '9')) {
                               d->cx *= 10;
                               d->cx += c - '0';
                               return;
                       } else
                               break;
               }
               d->state = NORMAL;
           }
           break;

       case ESC:
               switch (c) {
               case 'c':       /* Clear screen & home */
                       fillw(d->color|(' '<<8), base, COL * ROW);
                       d->cp = base;
                       d->row = 0;
                       d->state = NORMAL;
                       break;
               case '[':       /* Start ESC [ sequence */
                       d->state = EBRAC;
                       d->cx = 0;
                       d->cy = 0;
                       d->accp = &d->cx;
                       break;
               default: /* Invalid, clear state */
                       d->state = NORMAL;
                       break;
               }
               break;
       }
       if (d->cp >= base + (COL * ROW)) { /* scroll check */
               memmove(base, base + COL, COL * (ROW - 1) * CHR);
               fillw(d->color|(' '<<8), base + COL * (ROW - 1), COL);
               d->cp -= COL;
       }
       cursor();
}

void
vga_puts(char *s)
{
       char c;
       while ((c = *s++)) {
               vga_putc(c);
       }
}

void
video_on(void)
{

       /* Enable video */
       outb(0x3C4, 0x01);
       outb(0x3C5, inb(0x3C5) & ~0x20);
}

void
video_off(void)
{

       /* Disable video */
       outb(0x3C4, 0x01);
       outb(0x3C5, inb(0x3C5) | 0x20);
}

void
vga_init(u_char *ISA_mem)
{
       struct screen *d = &screen;

       memset(d, 0, sizeof (screen));
       video_on();

       d->cp = Crtat = (u_short *)&ISA_mem[0x0B8000];
       addr_6845 = CGA_BASE;
       initscreen();
       fillw(pccolor|(' '<<8), d->cp, COL * ROW);
}
#endif /* CONS_VGA */