/* $NetBSD: vreset.c,v 1.11 2023/05/06 21:34:40 andvar Exp $ */
/*-
* Copyright (c) 2006 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Tim Rightnour
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``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 FOUNDATION OR CONTRIBUTORS
* 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 VGA_RESET
#include <lib/libsa/stand.h>
#include "boot.h"
#include "iso_font.h"

#define VGA_SR_PORT     0x3c4
#define VGA_CR_PORT     0x3d4
#define VGA_CR_DATA     0x3d5
#define VGA_GR_PORT     0x3ce
#define VGA_GR_DATA     0x3cf
#define SRREGS  4
#define CRREGS  24
#define GRREGS  9
#define LINES 25
#define COLS 80
#define PCI_VENDOR_S3           0x5333
#define PCI_VENDOR_CIRRUS       0x1013
#define PCI_VENDOR_DIAMOND      0x100E
#define PCI_VENDOR_MATROX       0x102B
#define PCI_VENDOR_PARADISE     0x101C

static void write_attr(u_int8_t, u_int8_t, u_int8_t);
static void set_text_regs(void);
static void set_text_clut(int);
static void load_font(u_int8_t *);
static void unlock_S3(void);
static void clear_video_memory(void);

extern char *videomem;

typedef struct vga_reg {
       u_int8_t idx;
       u_int8_t val;
} vga_reg_t;

static vga_reg_t SR_regs[SRREGS] = {
       /* idx  val */
       { 0x1,  0x0 },  /* 01: clocking mode */
       { 0x2,  0x3 },  /* 02: map mask */
       { 0x3,  0x0 },  /* 03: character map select */
       { 0x4,  0x2 }   /* 04: memory mode */
};

static vga_reg_t CR_regs[CRREGS] = {
       /* idx  val */
       { 0x0,  0x61 }, /* 00: horizontal total */
       { 0x1,  0x4f }, /* 01: horizontal display-enable end */
       { 0x2,  0x50 }, /* 02: start horizontal blanking */
       { 0x3,  0x82 }, /* 03: display skew control / end horizontal blanking */
       { 0x4,  0x55 }, /* 04: start horizontal retrace pulse */
       { 0x5,  0x81 }, /* 05: horizontal retrace delay / end horiz. retrace */
       { 0x6,  0xf0 }, /* 06: vertical total */
       { 0x7,  0x1f }, /* 07: overflow register */
       { 0x8,  0x00 }, /* 08: preset row scan */
       { 0x9,  0x4f }, /* 09: overflow / maximum scan line */
       { 0xa,  0x0d }, /* 0A: cursor off / cursor start */
       { 0xb,  0x0e }, /* 0B: cursor skew / cursor end */
       { 0xc,  0x00 }, /* 0C: start regenerative buffer address high */
       { 0xd,  0x00 }, /* 0D: start regenerative buffer address low */
       { 0xe,  0x00 }, /* 0E: cursor location high */
       { 0xf,  0x00 }, /* 0F: cursor location low */
       { 0x10, 0x9a }, /* 10: vertical retrace start */
       { 0x11, 0x8c }, /* 11: vertical interrupt / vertical retrace end */
       { 0x12, 0x8f }, /* 12: vertical display enable end */
       { 0x13, 0x28 }, /* 13: logical line width */
       { 0x14, 0x1f }, /* 14: underline location */
       { 0x15, 0x97 }, /* 15: start vertical blanking */
       { 0x16, 0x00 }, /* 16: end vertical blanking */
       { 0x17, 0xa3 }, /* 17: CRT mode control */
};

static vga_reg_t GR_regs[GRREGS] = {
       /* idx  val */
       { 0x0,  0x00 }, /* 00: set/reset map */
       { 0x1,  0x00 }, /* 01: enable set/reset */
       { 0x2,  0x00 }, /* 02: color compare */
       { 0x3,  0x00 }, /* 03: data rotate */
       { 0x4,  0x00 }, /* 04: read map select */
       { 0x5,  0x10 }, /* 05: graphics mode */
       { 0x6,  0x0e }, /* 06: miscellaneous */
       { 0x7,  0x00 }, /* 07: color don't care */
       { 0x8,  0xff }, /* 08: bit mask */
};

/* video DAC palette registers */
/* XXX only set up 16 colors used by internal palette in ATC registers */
static const u_int8_t vga_dacpal[] = {
       /* R     G     B */
       0x00, 0x00, 0x00,       /* BLACK        */
       0x00, 0x00, 0x2a,       /* BLUE         */
       0x00, 0x2a, 0x00,       /* GREEN        */
       0x00, 0x2a, 0x2a,       /* CYAN         */
       0x2a, 0x00, 0x00,       /* RED          */
       0x2a, 0x00, 0x2a,       /* MAGENTA      */
       0x2a, 0x15, 0x00,       /* BROWN        */
       0x2a, 0x2a, 0x2a,       /* LIGHTGREY    */
       0x15, 0x15, 0x15,       /* DARKGREY     */
       0x15, 0x15, 0x3f,       /* LIGHTBLUE    */
       0x15, 0x3f, 0x15,       /* LIGHTGREEN   */
       0x15, 0x3f, 0x3f,       /* LIGHTCYAN    */
       0x3f, 0x15, 0x15,       /* LIGHTRED     */
       0x3f, 0x15, 0x3f,       /* LIGHTMAGENTA */
       0x3f, 0x3f, 0x15,       /* YELLOW       */
       0x3f, 0x3f, 0x3f        /* WHITE        */
};

static const u_int8_t vga_atc[] = {
       0x00,   /* 00: internal palette  0 */
       0x01,   /* 01: internal palette  1 */
       0x02,   /* 02: internal palette  2 */
       0x03,   /* 03: internal palette  3 */
       0x04,   /* 04: internal palette  4 */
       0x05,   /* 05: internal palette  5 */
       0x14,   /* 06: internal palette  6 */
       0x07,   /* 07: internal palette  7 */
       0x38,   /* 08: internal palette  8 */
       0x39,   /* 09: internal palette  9 */
       0x3a,   /* 0A: internal palette 10 */
       0x3b,   /* 0B: internal palette 11 */
       0x3c,   /* 0C: internal palette 12 */
       0x3d,   /* 0D: internal palette 13 */
       0x3e,   /* 0E: internal palette 14 */
       0x3f,   /* 0F: internal palette 15 */
       0x0c,   /* 10: attribute mode control */
       0x00,   /* 11: overscan color */
       0x0f,   /* 12: color plane enable */
       0x08,   /* 13: horizontal PEL panning */
       0x00    /* 14: color select */
};

void
vga_reset(u_char *ISA_mem)
{
       int slot, cardfound;

       /* check if we are in text mode, if so, punt */
       outb(VGA_GR_PORT, 0x06);
       if ((inb(VGA_GR_DATA) & 0x01) == 0)
               return;

       /* guess not, we lose. */
       slot = -1;
       while ((slot = scan_PCI(slot)) > -1) {
               cardfound = 0;
               switch (PCI_vendor(slot)) {
               case PCI_VENDOR_CIRRUS:
                       unlockVideo(slot);
                       outw(VGA_SR_PORT, 0x0612); /* unlock ext regs */
                       outw(VGA_SR_PORT, 0x0700); /* reset ext sequence mode */
                       cardfound++;
                       break;
               case PCI_VENDOR_PARADISE:
                       unlockVideo(slot);
                       outw(VGA_GR_PORT, 0x0f05); /* unlock registers */
                       outw(VGA_SR_PORT, 0x0648);
                       outw(VGA_CR_PORT, 0x2985);
                       outw(VGA_CR_PORT, 0x34a6);
                       outb(VGA_GR_PORT, 0x0b); /* disable linear addressing */
                       outb(VGA_GR_DATA, inb(VGA_GR_DATA) & ~0x30);
                       outw(VGA_SR_PORT, 0x1400);
                       outb(VGA_GR_PORT, 0x0e); /* disable 256 color mode */
                       outb(VGA_GR_DATA, inb(VGA_GR_DATA) & ~0x01);
                       outb(0xd00, 0xff); /* enable auto-centering */
                       if (!(inb(0xd01) & 0x03)) {
                               outb(VGA_CR_PORT, 0x33);
                               outb(VGA_CR_DATA, inb(VGA_CR_DATA) & ~0x90);
                               outb(VGA_CR_PORT, 0x32);
                               outb(VGA_CR_DATA, inb(VGA_CR_DATA) | 0x04);
                               outw(VGA_CR_PORT, 0x0250);
                               outw(VGA_CR_PORT, 0x07ba);
                               outw(VGA_CR_PORT, 0x0900);
                               outw(VGA_CR_PORT, 0x15e7);
                               outw(VGA_CR_PORT, 0x2a95);
                       }
                       outw(VGA_CR_PORT, 0x34a0);
                       cardfound++;
                       break;
               case PCI_VENDOR_S3:
                       unlockVideo(slot);
                       unlock_S3();
                       cardfound++;
                       break;
               default:
                       break;
               }
               if (cardfound) {
                       outw(VGA_SR_PORT, 0x0120); /* disable video */
                       set_text_regs();
                       set_text_clut(0);
                       load_font(ISA_mem);
                       set_text_regs();
                       outw(VGA_SR_PORT, 0x0100); /* re-enable video */
                       clear_video_memory();

                       if (PCI_vendor(slot) == PCI_VENDOR_S3)
                               outb(0x3c2, 0x63);      /* ??? */
                       delay(1000);
               }
       }
       return;
}

/* write something to a VGA attribute register */
static void
write_attr(u_int8_t index, u_int8_t data, u_int8_t videoOn)
{

       (void)inb(0x3da);       /* reset attr addr toggle */
       if (videoOn)
               outb(0x3c0, (index & 0x1F) | 0x20);
       else
               outb(0x3c0, (index & 0x1F));
       outb(0x3c0, data);
}

static void
set_text_regs(void)
{
       int i;

       for (i = 0; i < SRREGS; i++) {
               outb(VGA_SR_PORT, SR_regs[i].idx);
               outb(VGA_SR_PORT + 1, SR_regs[i].val);
       }
       for (i = 0; i < CRREGS; i++) {
               outb(VGA_CR_PORT, CR_regs[i].idx);
               outb(VGA_CR_PORT + 1, CR_regs[i].val);
       }
       for (i = 0; i < GRREGS; i++) {
               outb(VGA_GR_PORT, GR_regs[i].idx);
               outb(VGA_GR_PORT + 1, GR_regs[i].val);
       }

       outb(0x3c2, 0x67);  /* MISC */
       outb(0x3c6, 0xff);  /* MASK */

       for (i = 0; i < 0x14; i++)
               write_attr(i, vga_atc[i], 0);
       write_attr(0x14, 0x00, 1); /* color select; video on  */
}

static void
set_text_clut(int shift)
{
       int i;

       outb(0x3C6, 0xFF);
       inb(0x3C7);
       outb(0x3C8, 0);
       inb(0x3C7);

       for (i = 0; i < (16 * 3); ) {
               outb(0x3c9, vga_dacpal[i++] << shift);
               outb(0x3c9, vga_dacpal[i++] << shift);
               outb(0x3c9, vga_dacpal[i++] << shift);
       }
}

static void
load_font(u_int8_t *ISA_mem)
{
       int i, j;
       u_int8_t *font_page = (u_int8_t *)&ISA_mem[0xA0000];

       outb(0x3C2, 0x67);
       inb(0x3DA);  /* Reset Attr toggle */

       outb(0x3C0, 0x30);
       outb(0x3C0, 0x01);      /* graphics mode */
       outw(0x3C4, 0x0001);    /* reset sequencer */
       outw(0x3C4, 0x0204);    /* write to plane 2 */
       outw(0x3C4, 0x0406);    /* enable plane graphics */
       outw(0x3C4, 0x0003);    /* reset sequencer */
       outw(0x3CE, 0x0402);    /* read plane 2 */
       outw(0x3CE, 0x0500);    /* write mode 0, read mode 0 */
       outw(0x3CE, 0x0605);    /* set graphics mode */

       for (i = 0;  i < sizeof(font);  i += 16) {
               for (j = 0;  j < 16;  j++) {
                       __asm__ volatile("eieio" ::: "memory");
                       font_page[(2*i)+j] = font[i+j];
               }
       }
}

static void
unlock_S3(void)
{
       int s3_devid;

       outw(VGA_CR_PORT, 0x3848);
       outw(VGA_CR_PORT, 0x39a5);
       outb(VGA_CR_PORT, 0x2d);
       s3_devid = inb(VGA_CR_DATA) << 8;
       outb(VGA_CR_PORT, 0x2e);
       s3_devid |= inb(VGA_CR_DATA);

       if (s3_devid != 0x8812) {
               /* from the S3 manual */
               outb(0x46E8, 0x10);  /* Put into setup mode */
               outb(0x3C3, 0x10);
               outb(0x102, 0x01);   /* Enable registers */
               outb(0x46E8, 0x08);  /* Enable video */
               outb(0x3C3, 0x08);
               outb(0x4AE8, 0x00);
               outb(VGA_CR_PORT, 0x38);  /* Unlock all registers */
               outb(VGA_CR_DATA, 0x48);
               outb(VGA_CR_PORT, 0x39);
               outb(VGA_CR_DATA, 0xA5);
               outb(VGA_CR_PORT, 0x40);
               outb(VGA_CR_DATA, inb(0x3D5)|0x01);
               outb(VGA_CR_PORT, 0x33);
               outb(VGA_CR_DATA, inb(0x3D5)&~0x52);
               outb(VGA_CR_PORT, 0x35);
               outb(VGA_CR_DATA, inb(0x3D5)&~0x30);
               outb(VGA_CR_PORT, 0x3A);
               outb(VGA_CR_DATA, 0x00);
               outb(VGA_CR_PORT, 0x53);
               outb(VGA_CR_DATA, 0x00);
               outb(VGA_CR_PORT, 0x31);
               outb(VGA_CR_DATA, inb(0x3D5)&~0x4B);
               outb(VGA_CR_PORT, 0x58);

               outb(VGA_CR_DATA, 0);

               outb(VGA_CR_PORT, 0x54);
               outb(VGA_CR_DATA, 0x38);
               outb(VGA_CR_PORT, 0x60);
               outb(VGA_CR_DATA, 0x07);
               outb(VGA_CR_PORT, 0x61);
               outb(VGA_CR_DATA, 0x80);
               outb(VGA_CR_PORT, 0x62);
               outb(VGA_CR_DATA, 0xA1);
               outb(VGA_CR_PORT, 0x69);  /* High order bits for cursor address */
               outb(VGA_CR_DATA, 0);

               outb(VGA_CR_PORT, 0x32);
               outb(VGA_CR_DATA, inb(0x3D5)&~0x10);
       } else {
               /* IBM Portable 860 */
               outw(VGA_SR_PORT, 0x0806);
               outw(VGA_SR_PORT, 0x1041);
               outw(VGA_SR_PORT, 0x1128);
               outw(VGA_CR_PORT, 0x4000);
               outw(VGA_CR_PORT, 0x3100);
               outw(VGA_CR_PORT, 0x3a05);
               outw(VGA_CR_PORT, 0x6688);
               outw(VGA_CR_PORT, 0x5800); /* disable linear addressing */
               outw(VGA_CR_PORT, 0x4500); /* disable H/W cursor */
               outw(VGA_SR_PORT, 0x5410); /* enable auto-centering */
               outw(VGA_SR_PORT, 0x561f);
               outw(VGA_SR_PORT, 0x1b80); /* lock DCLK selection */
               outw(VGA_CR_PORT, 0x3900); /* lock S3 registers */
               outw(VGA_CR_PORT, 0x3800);
       }
}

static void
clear_video_memory(void)
{
       int i, j;

       for (i = 0;  i < LINES; i++) {
               for (j = 0; j < COLS; j++) {
                       videomem[((i * COLS)+j) * 2] = 0x20; /* space */
                       videomem[((i * COLS)+j) * 2 + 1] = 0x07; /* fg/bg */
               }
       }
}

#endif /* VGA_RESET */