/* * All modifications in this file to the original code are * (C) Copyright 1992, ..., 2004 the "DOSEMU-Development-Team". * * for details see file COPYING in the DOSEMU distribution */ /* * s3.c * * This file contains support for VGA-cards with an S3 chip. * * (C) 1994 Christoph Niemann * Christoph.Niemann@linux.org * * The programming information was taken from the file vgadoc3.zip * by Finn Thoegersen and from the german computer magazine c't, * nov. 1992, p 242. * * Additional information came from the SuperProbe package of * XFree86 version 2.1.1 and from the S3-driver in XFree86. */ #define S3_C #include #include #include "config.h" #include "emu.h" #include "port.h" #include "video.h" #include "vc.h" #include "vga.h" #include "s3.h" #include "priv.h" static int s3_chip = 0; static int s3_chiprev = 0; static int s3_memsize = 0; static int s3_series = 0; static int s3_Ramdac = 0; static int s3_8514_base = BASE_8514_1; #if 0 /* code calling this array is #if 0'ed out too. Don't know why -- Bart */ static unsigned short s3BtLowBits[] = { 0x3C8, 0x3C9, 0x3C6, 0x3C7 }; #endif #define IS_TRIO (s3_series==S3_928 && (s3_chip&0xC0F0)==0x00E0) static void out_crt(const int index, const int value) { port_out(index, CRT_I); port_out(value, CRT_D); } static int in_crt(const int index) { port_out(index, CRT_I); return (port_in(CRT_D) & 0xff); } static void out_seq(const int index,int value) { value=(value<<8)+index; port_out_w(value, 0x3c4); } static int in_seq(const int index) { port_out(index, 0x3c4); return (port_in(0x3c5) & 0xff); } /* * These functions are from XFree-86: * /mit/server/ddx/x386/common_hw/ATTDac.c */ static void setdaccomm(unsigned char value) { port_in(0x3c6); port_in(0x3c6); port_in(0x3c6); port_in(0x3c6); port_out(value, 0x3c6); port_in(0x3c8); } static unsigned char getdaccomm(void) { unsigned char ret; port_in(0x3c6); port_in(0x3c6); port_in(0x3c6); port_in(0x3c6); ret = port_in(0x3c6); port_in(0x3c8); return ret; } static unsigned char dactocomm(void) { port_in(0x3c6); port_in(0x3c6); port_in(0x3c6); return port_in(0x3c6); } #define S + 256 static short s3_crt_regs[0x40] = { /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ /* 3 */ 4, 5, 6, 7, 8, -1, -1, -1, 0, 1, 9, 10, 11, -1, -1, -1, /* 4 */ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, /* 5 */ 30 S,31 S, -1,39 S,32 S,33 S, -1, -1,34 S,35 S,36 S, -1, -1,37 S,38 S, -1, /* 6 */ 40 S,41 S,42 S,43 S,44 S,45 S, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; #undef S /* * Lock S3's registers. * There are more locks, but this should suffice. * * More complete Extended VGA Register Lock Documentation, as of Ferraro: * * Register Bit Controls Access To: Function * CR33 1 CR7 bits 1 and 6 1=disable write protect * setting of CR11 bit 7 * CR33 4 Ramdac Register 1=disable writes * CR33 6 Palette/Overscan Registers 1=lock * CR34 5 Memory Configuration bit 5 1=lock * CR34 7 Misc Register bit 3-2 (Clock) 1=lock * CR35 4 Vertical Timing Registers 1=lock * CR35 5 Horizontal Timing Registers 1=lock * * XXXX mostly, need to lock the enhanced command regs on the 805 (and * probably below) to avoid display corruption. */ static void s3_lock(void) { out_crt(0x39, 0x00); /* Lock system control regs. */ out_crt(0x38, 0x00); /* Lock special regs. */ } static void s3_lock_enh(void) { if (s3_chip > S3_911) out_crt(0x40, in_crt(0x40) & ~0x01); /* Lock enhanced command regs. */ s3_lock(); } /* * Unlock S3's registers. * There are more locks, but this should suffice. */ static void s3_unlock(void) { out_crt(0x38, 0x48); /* Unlock special regs. */ out_crt(0x39, 0xa5); /* Unlock system control regs. */ } static void s3_unlock_enh(void) { s3_unlock(); if (s3_chip > S3_911) out_crt(0x40, in_crt(0x40) | 0x01); /* Unlock enhanced command regs. */ } static void s3_save_ext_regs(u_char xregs[], u_short xregs16[]) { int i; v_printf("s3_save_ext_regs(): start.\n"); emu_video_retrace_off(); /* * Save status of the extensions enable/disable regs */ xregs[0] = in_crt(0x38); xregs[1] = in_crt(0x39); /* * Enable extensions */ s3_unlock_enh(); xregs[13] = port_in(0x3cc); for (i = 0; i < 5; i++) xregs[i + 4] = in_crt(0x30 + i); xregs[9] = in_crt(0x3a); xregs[10] = in_crt(0x3b); xregs[11] = in_crt(0x3c); for (i = 0; i < 0x10; i++) xregs[i + 14] = in_crt(0x40 + i); if (s3_chip > S3_911) { /* * I have an S3-911, so this part is untested. * This part has now been tested with an S3-928 PCI */ v_printf("s3_save_ext_regs(): saving additional regs\n"); xregs[30] = in_crt(0x50); xregs[31] = in_crt(0x51); xregs[32] = in_crt(0x54); xregs[33] = in_crt(0x55); xregs[34] = in_crt(0x58); xregs[35] = in_crt(0x59); xregs[36] = in_crt(0x5a); xregs[37] = in_crt(0x5d); xregs[38] = in_crt(0x5e); xregs[39] = in_crt(0x53); for (i = 0; i < 6; i++) xregs[40 + i] = in_crt(0x60 + i); /* s3 trio extended registers */ /* get S3 ext. SR registers */ if (IS_TRIO) { xregs[50] = in_seq(0x08); out_seq(0x08, 0x06); /* unlock extended seq regs */ xregs[51] = in_seq(0x09); xregs[52] = in_seq(0x0A); xregs[53] = in_seq(0x0D); xregs[54] = in_seq(0x10); xregs[55] = in_seq(0x11); xregs[56] = in_seq(0x12); xregs[57] = in_seq(0x13); xregs[58] = in_seq(0x15); xregs[59] = in_seq(0x18); out_seq(0x08, xregs[50]); /* restore lock */ } } if (s3_Ramdac == S3_NORMAL_DAC) { v_printf("S3: Saving Ramdac data\n"); xregs[49] = getdaccomm(); setdaccomm((xregs[49] | 0x10)); (void)dactocomm(); port_out(0x08, 0x3c7); xregs[48] = port_in(0x3c8); setdaccomm(xregs[49]); } port_in(0x3da); /* reset flip-flop */ port_out(0x36, 0x3c0); xregs[12] = port_in(0x3c1); port_out(xregs[12], 0x3c0); /* * restore the status of the extensions */ s3_lock_enh(); out_crt(0x38, xregs[0]); out_crt(0x39, xregs[1]); xregs[2] = port_in(0x102); port_out(1, 0x102); if (can_do_root_stuff) { priv_iopl(3); xregs16[0] = port_in_w(0x8000 | s3_8514_base); /* CUR_Y */ xregs16[1] = port_in_w(0x8400 | s3_8514_base); /* CUR_X */ xregs16[2] = port_in_w(0x8800 | s3_8514_base); xregs16[3] = port_in_w(0x8c00 | s3_8514_base); priv_iopl(0); } else { xregs16[0] = std_port_inw(0x8000 | s3_8514_base); /* CUR_Y */ xregs16[1] = std_port_inw(0x8400 | s3_8514_base); /* CUR_X */ xregs16[2] = std_port_inw(0x8800 | s3_8514_base); xregs16[3] = std_port_inw(0x8c00 | s3_8514_base); } port_out(xregs[2], 0x102); emu_video_retrace_on(); } static void s3_restore_ext_regs(u_char xregs[], u_short xregs16[]) { int i; v_printf("s3_restore_ext_regs(): start.\n"); emu_video_retrace_off(); /* * Enable extensions */ s3_unlock_enh(); port_out(xregs[13], 0x3c2); for (i = 0; i < 5; i++) out_crt(0x30 + i, xregs[i + 4]); out_crt(0x3a, xregs[9]); out_crt(0x3b, xregs[10]); out_crt(0x3c, xregs[11]); for (i = 0; i < 0x10; i++) out_crt(0x40 + i, xregs[i + 14]); if (s3_chip > S3_911) { v_printf("s3_restore_ext_regs(): restoring additional regs\n"); out_crt(0x50, xregs[30]); out_crt(0x51, xregs[31]); out_crt(0x54, xregs[32]); out_crt(0x55, xregs[33]); out_crt(0x58, xregs[34]); out_crt(0x59, xregs[35]); out_crt(0x5a, xregs[36]); out_crt(0x5d, xregs[37]); out_crt(0x5e, xregs[38]); out_crt(0x53, xregs[39]); for (i = 0; i < 6; i++) out_crt(0x60 + i, xregs[40 + i]); /* s3 trio extended registers */ /* set S3 ext. SR registers */ if (IS_TRIO) { out_seq(0x08, 0x06); /* unlock extended seq regs */ out_seq(0x09, xregs[51]); out_seq(0x0A, xregs[52]); out_seq(0x0D, xregs[53]); out_seq(0x10, xregs[54]); out_seq(0x11, xregs[55]); out_seq(0x12, xregs[56]); out_seq(0x13, xregs[57]); out_seq(0x15, xregs[58]); out_seq(0x18, xregs[59]); out_seq(0x08, xregs[50]); /* restore lock */ } } if (s3_Ramdac == S3_NORMAL_DAC) { int c; v_printf("S3: Restoring Ramdac data\n"); c=getdaccomm(); setdaccomm( c | 0x10 ); (void)dactocomm(); port_out(0x08, 0x3c7); port_out(xregs[48], 0x3c8); setdaccomm(c); setdaccomm(xregs[49]); } port_in(0x3da); port_out(0x36, 0x3c0); port_out(xregs[12], 0x3c0); s3_lock_enh(); out_crt(0x38, xregs[0]); out_crt(0x39, xregs[1]); port_out(1, 0x102); if (can_do_root_stuff) { priv_iopl(3); port_out_w(xregs16[0], 0x8000 | s3_8514_base); port_out_w(xregs16[1], 0x8400 | s3_8514_base); port_out_w(xregs16[2], 0x8800 | s3_8514_base); port_out_w(xregs16[3], 0x8c00 | s3_8514_base); priv_iopl(0); } else { std_port_outw(0x8000 | s3_8514_base, xregs16[0]); std_port_outw(0x8400 | s3_8514_base, xregs16[1]); std_port_outw(0x8800 | s3_8514_base, xregs16[2]); std_port_outw(0x8c00 | s3_8514_base, xregs16[3]); } port_out(xregs[2], 0x102); emu_video_retrace_on(); } static void s3_set_bank(u_char bank) { /* * For S3 chips the memory bank is always the same for * reading and writing */ int mode = in_crt(0x38); int old_reg; v_printf("s3_set_bank(%d)\n", bank); out_crt(0x38, 0x48); old_reg = in_crt(0x35); out_crt(0x35, (bank & 0x0f) + (old_reg & 0xf0)); if ((bank & 0xf0) && (s3_chip > S3_911)) { /* * This is untested (I don't have > 1 MB VRAM) */ int mode2 = in_crt(0x39); out_crt(0x39, 0xa5); old_reg = in_crt(0x51); out_crt(0x51, (bank & 0xf0) >> 2); out_crt(0x39, mode2); } out_crt(0x38, mode); } static u_char s3_ext_video_port_in(ioport_t port) { int x_reg; switch(port) { case CRT_DC: case CRT_DM: if (dosemu_regs.regs[CRTI] < 0x70 && dosemu_regs.regs[CRTI] >= 0x30 && (x_reg = s3_crt_regs[dosemu_regs.regs[CRTI] - 0x30]) != -1) { /* * some registers are available only on `advanced' * s3 cards. */ if ((x_reg & 0xff) && (s3_chip <= S3_911)) break; x_reg &= 0xff; v_printf("S3: Read on CRT_D 0x%02x got 0x%02x\n", dosemu_regs.regs[CRTI], dosemu_regs.xregs[x_reg]); return dosemu_regs.xregs[x_reg]; } break; } v_printf("S3: Bad Read on port 0x%04x\n", port); return 0; } static void s3_ext_video_port_out(ioport_t port, u_char value) { int x_reg; switch(port & 0x02ff) { case CRT_DC: case CRT_DM: if (dosemu_regs.regs[CRTI] < 0x70 && dosemu_regs.regs[CRTI] >= 0x30 && (x_reg = s3_crt_regs[dosemu_regs.regs[CRTI] - 0x30]) != -1) { /* * some registers are available only on `advanced' * s3 cards. */ if ((x_reg & 0xff) && (s3_chip <= S3_911)) break; x_reg &= 0xff; v_printf("S3: Write on CRT_D 0x%02x of 0x%02x\n", dosemu_regs.regs[CRTI], value); dosemu_regs.xregs[x_reg] = value; return; } break; case BASE_8514_1: case BASE_8514_2: break; switch(port & 0xfc00) { case 0x8000: *((unsigned char *) (dosemu_regs.xregs16)) = value; v_printf("S3: Write on 8514 reg 0x%04x of 0x%02x\n", port, value); break; case 0x8400: *((unsigned char *) (&dosemu_regs.xregs16[1])) = value; v_printf("S3: Write on 8514 reg 0x%04x of 0x%02x\n", port, value); break; case 0x8800: *((unsigned char *) (&dosemu_regs.xregs16[2])) = value; v_printf("S3: Write on 8514 reg 0x%04x of 0x%02x\n", port, value); break; case 0x8c00: *((unsigned char *) (&dosemu_regs.xregs16[3])) = value; v_printf("S3: Write on 8514 reg 0x%04x of 0x%02x\n", port, value); break; } break; case BASE_8514_1 + 1: case BASE_8514_2 + 1: break; } v_printf("S3: Bad Write on port 0x%04x with value 0x%02x\n", port, value); } #if 0 /* code calling this function is #if 0'ed out too. Don't know why -- Bart */ /* * this function was taken from XFree86, s3BtCursor.c */ static unsigned char s3InBtReg(unsigned short reg) { unsigned char tmp, ret; tmp = in_crt(0x55) & 0xFC; out_crt(0x55, tmp | ((reg & 0x0C) >> 2)); ret = port_in(s3BtLowBits[reg & 0x03]); out_crt(0x55, tmp); return ret; } static void s3OutBtReg(unsigned short reg, unsigned char mask, unsigned char data) { unsigned char tmp; unsigned char tmp1 = 0x00; tmp = in_crt(0x55) & 0xFC; out_crt(0x55, tmp | ((reg & 0x0C) >> 2)); if (mask != 0x00) tmp1 = port_in(s3BtLowBits[reg & 0x03]) & mask; port_out(tmp1 | data, s3BtLowBits[reg & 0x03]); /* Now clear 2 high-order bits so that other things work */ out_crt(0x55, tmp); } static unsigned char s3InBtStatReg(void) { unsigned char tmp, ret; tmp = s3InBtReg(BT_COMMAND_REG_0); if ((tmp & 0x80) == 0x80) { /* Behind Command Register 3 */ tmp = s3InBtReg(BT_WRITE_ADDR); s3OutBtReg(BT_WRITE_ADDR, 0x00, 0x00); ret = s3InBtReg(BT_STATUS_REG); s3OutBtReg(BT_WRITE_ADDR, 0x00, tmp); } else { ret = s3InBtReg(BT_STATUS_REG); } return(ret); } #endif void vga_init_s3(void) { emu_iodev_t io_device; int mode, mode2, subtype; char *name = NULL; save_ext_regs = s3_save_ext_regs; restore_ext_regs = s3_restore_ext_regs; set_bank_read = s3_set_bank; set_bank_write = s3_set_bank; ext_video_port_in = s3_ext_video_port_in; ext_video_port_out = s3_ext_video_port_out; mode = in_crt(0x38); out_crt(0x38, 0x48); s3_chip = in_crt(0x30); s3_chiprev = s3_chip & 0x0F; name = NULL; switch(s3_chip & 0xF0) { case 0x80: s3_series = S3_911; switch(s3_chiprev) { case 0x01: name = "911"; break; case 0x02: name = "911A/924"; break; } break; case 0x90: /* 928 */ s3_series = S3_928; switch(s3_chiprev) { case 0x00: name = "928C"; break; case 0x01: name = "928D"; break; case 0x04: case 0x05: name = "928E"; break; } break; case 0xA0: /* 801 / 805 */ subtype = in_crt(0x36) & 0x03; if ((subtype & 0x02) == 0) { /* EISA or VLB => 805 */ s3_series = S3_805; switch (s3_chiprev) { case 0x00: name = "805A/B"; break; case 0x02: case 0x03: case 0x04: name = "805C"; break; case 0x05: name = "805D"; break; case 0x08: name = "805I"; break; } } else if (subtype == 3) { /* ISA => 801 */ s3_series = S3_801; switch(s3_chiprev) { case 0x00: name = "801A/B"; break; case 0x02: case 0x03: case 0x04: name = "801C"; break; case 0x05: name = "801D"; break; case 0x08: name = "801I"; break; } } break; case 0xB0: name = "928"; s3_series = S3_928; break; case 0xC0: name = "864"; s3_series = S3_928; break; case 0xD0: name = "964"; s3_series = S3_928; break; case 0xE0: case 0xF0: s3_chip |= (in_crt(0x2E) << 8); s3_chiprev |= (in_crt(0x2F) << 4); switch (s3_chip & 0xFFF0) { case 0x10E0: name = "TRIO32"; break; case 0x11E0: name = "TRIO64"; break; case 0x80E0: name = "866"; break; case 0x90E0: name = "868"; break; case 0xF0E0: name = "968"; break; } s3_series = S3_928; break; } if (name) v_printf("S3 chip 86c%s rev %d detected.\n", name, s3_chiprev); else v_printf("Unknown S3-chip, type = 0x%x\n", s3_chip); #if 1 s3_Ramdac = S3_NORMAL_DAC; #else s3_Ramdac = 0; if (s3_series != S3_928) s3_Ramdac = S3_NORMAL_DAC; if (!s3_Ramdac) { /* probe for Ramdac Bt485/AT&T20C505 */ unsigned char tmp1, tmp2; out_crt(0x39, 0xA5); tmp1 = port_in(S3_DAC_INDEX_REG); port_out(0x0f, S3_DAC_INDEX_REG); tmp2 = s3InBtStatReg(); if ((tmp2 & 0x80) == 0x80) { if ((tmp2 & 0xF0) == 0xD0) s3_Ramdac = S3_ATT20C505_DAC; else s3_Ramdac = S3_BT485_DAC; } port_out(tmp1, S3_DAC_INDEX_REG); } if (!s3_Ramdac) { /* Probe for a Ti3020 */ unsigned char tmp1, tmp2; tmp1 = in_crt(0x55); out_crt(0x55, (tmp1 & 0xfc) | 0x01); tmp2 = port_in(S3_DAC_INDEX_REG); port_out(S3_TI3020_ID2, S3_DAC_INDEX_REG); if (port_in(S3_DAC_DATA_REG) == S3_TI3020_ID) s3_Ramdac = S3_TI3020_DAC; port_out(tmp2, S3_DAC_INDEX_REG); out_crt(0x55, tmp1); } if (!s3_Ramdac) s3_Ramdac = S3_NORMAL_DAC; v_printf("Detected a "); switch(s3_Ramdac) { case S3_NORMAL_DAC: v_printf("normal"); break; case S3_ATT20C505_DAC: v_printf("AT&T 20C505"); break; case S3_BT485_DAC: v_printf("BrookTree Bt485"); break; case S3_TI3020_DAC: v_printf("TI ViewPoint 3020"); break; } v_printf(" RAMDAC\n"); #endif mode2 = in_crt(0x39); out_crt(0x39, 0xa5); if (in_crt(0x43) & 0x10) s3_8514_base = BASE_8514_2; else s3_8514_base = BASE_8514_1; out_crt(0x39, mode2); if (in_crt(0x36) & 0x20) s3_memsize = 512; else { if (s3_chip > S3_911) switch((in_crt(0x36) & 0xc0) >> 6) { case 0: s3_memsize = 4096; break; case 1: s3_memsize = 3072; break; case 2: s3_memsize = 2048; break; case 3: s3_memsize = 1024; break; } else s3_memsize = 1024; } v_printf("S3 base address: 0x%x\n", s3_8514_base); /* register high S3 video ports */ io_device.irq = EMU_NO_IRQ; io_device.fd = -1; io_device.handler_name = "std port io"; io_device.start_addr = 0x8000 | s3_8514_base; io_device.end_addr = (0x8000 | s3_8514_base) + 1; port_register_handler(io_device, 0); io_device.start_addr = 0x8400 | s3_8514_base; io_device.end_addr = (0x8400 | s3_8514_base) + 1; port_register_handler(io_device, 0); io_device.start_addr = 0x8800 | s3_8514_base; io_device.end_addr = (0x8800 | s3_8514_base) + 1; port_register_handler(io_device, 0); io_device.start_addr = 0x8c00 | s3_8514_base; io_device.end_addr = (0x8c00 | s3_8514_base) + 1; port_register_handler(io_device, 0); v_printf("S3 memory size : %d kbyte\n", s3_memsize); config.gfxmemsize = s3_memsize; v_8514_base = s3_8514_base; out_crt(0x38, mode); } #undef S3_C