blob: 977e27927a211ab0fffcd37087f90e0c0fa86e9f [file] [log] [blame]
/*
* SiS 300/540/630[S]/730[S],
* SiS 315[E|PRO]/550/[M]65x/[M]66x[F|M|G]X/[M]74x[GX]/330/[M]76x[GX],
* XGI V3XT/V5/V8, Z7
* frame buffer driver for Linux kernels >= 2.4.14 and >=2.6.3
*
* Copyright (C) 2001-2005 Thomas Winischhofer, Vienna, Austria.
*
* 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 named License,
* or 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
*
* Author: Thomas Winischhofer <thomas@winischhofer.net>
*
* Author of (practically wiped) code base:
* SiS (www.sis.com)
* Copyright (C) 1999 Silicon Integrated Systems, Inc.
*
* See http://www.winischhofer.net/ for more information and updates
*
* Originally based on the VBE 2.0 compliant graphic boards framebuffer driver,
* which is (c) 1998 Gerd Knorr <kraxel@goldbach.in-berlin.de>
*
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/screen_info.h>
#include <linux/slab.h>
#include <linux/fb.h>
#include <linux/selection.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/vmalloc.h>
#include <linux/capability.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#ifdef CONFIG_MTRR
#include <asm/mtrr.h>
#endif
#include "sis.h"
#include "sis_main.h"
#if !defined(CONFIG_FB_SIS_300) && !defined(CONFIG_FB_SIS_315)
#warning Neither CONFIG_FB_SIS_300 nor CONFIG_FB_SIS_315 is set
#warning sisfb will not work!
#endif
static void sisfb_handle_command(struct sis_video_info *ivideo,
struct sisfb_cmd *sisfb_command);
/* ------------------ Internal helper routines ----------------- */
static void __init
sisfb_setdefaultparms(void)
{
sisfb_off = 0;
sisfb_parm_mem = 0;
sisfb_accel = -1;
sisfb_ypan = -1;
sisfb_max = -1;
sisfb_userom = -1;
sisfb_useoem = -1;
sisfb_mode_idx = -1;
sisfb_parm_rate = -1;
sisfb_crt1off = 0;
sisfb_forcecrt1 = -1;
sisfb_crt2type = -1;
sisfb_crt2flags = 0;
sisfb_pdc = 0xff;
sisfb_pdca = 0xff;
sisfb_scalelcd = -1;
sisfb_specialtiming = CUT_NONE;
sisfb_lvdshl = -1;
sisfb_dstn = 0;
sisfb_fstn = 0;
sisfb_tvplug = -1;
sisfb_tvstd = -1;
sisfb_tvxposoffset = 0;
sisfb_tvyposoffset = 0;
sisfb_nocrt2rate = 0;
#if !defined(__i386__) && !defined(__x86_64__)
sisfb_resetcard = 0;
sisfb_videoram = 0;
#endif
}
/* ------------- Parameter parsing -------------- */
static void sisfb_search_vesamode(unsigned int vesamode, bool quiet)
{
int i = 0, j = 0;
/* We don't know the hardware specs yet and there is no ivideo */
if(vesamode == 0) {
if(!quiet)
printk(KERN_ERR "sisfb: Invalid mode. Using default.\n");
sisfb_mode_idx = DEFAULT_MODE;
return;
}
vesamode &= 0x1dff; /* Clean VESA mode number from other flags */
while(sisbios_mode[i++].mode_no[0] != 0) {
if( (sisbios_mode[i-1].vesa_mode_no_1 == vesamode) ||
(sisbios_mode[i-1].vesa_mode_no_2 == vesamode) ) {
if(sisfb_fstn) {
if(sisbios_mode[i-1].mode_no[1] == 0x50 ||
sisbios_mode[i-1].mode_no[1] == 0x56 ||
sisbios_mode[i-1].mode_no[1] == 0x53)
continue;
} else {
if(sisbios_mode[i-1].mode_no[1] == 0x5a ||
sisbios_mode[i-1].mode_no[1] == 0x5b)
continue;
}
sisfb_mode_idx = i - 1;
j = 1;
break;
}
}
if((!j) && !quiet)
printk(KERN_ERR "sisfb: Invalid VESA mode 0x%x'\n", vesamode);
}
static void sisfb_search_mode(char *name, bool quiet)
{
unsigned int j = 0, xres = 0, yres = 0, depth = 0, rate = 0;
int i = 0;
char strbuf[16], strbuf1[20];
char *nameptr = name;
/* We don't know the hardware specs yet and there is no ivideo */
if(name == NULL) {
if(!quiet)
printk(KERN_ERR "sisfb: Internal error, using default mode.\n");
sisfb_mode_idx = DEFAULT_MODE;
return;
}
if(!strnicmp(name, sisbios_mode[MODE_INDEX_NONE].name, strlen(name))) {
if(!quiet)
printk(KERN_ERR "sisfb: Mode 'none' not supported anymore. Using default.\n");
sisfb_mode_idx = DEFAULT_MODE;
return;
}
if(strlen(name) <= 19) {
strcpy(strbuf1, name);
for(i = 0; i < strlen(strbuf1); i++) {
if(strbuf1[i] < '0' || strbuf1[i] > '9') strbuf1[i] = ' ';
}
/* This does some fuzzy mode naming detection */
if(sscanf(strbuf1, "%u %u %u %u", &xres, &yres, &depth, &rate) == 4) {
if((rate <= 32) || (depth > 32)) {
j = rate; rate = depth; depth = j;
}
sprintf(strbuf, "%ux%ux%u", xres, yres, depth);
nameptr = strbuf;
sisfb_parm_rate = rate;
} else if(sscanf(strbuf1, "%u %u %u", &xres, &yres, &depth) == 3) {
sprintf(strbuf, "%ux%ux%u", xres, yres, depth);
nameptr = strbuf;
} else {
xres = 0;
if((sscanf(strbuf1, "%u %u", &xres, &yres) == 2) && (xres != 0)) {
sprintf(strbuf, "%ux%ux8", xres, yres);
nameptr = strbuf;
} else {
sisfb_search_vesamode(simple_strtoul(name, NULL, 0), quiet);
return;
}
}
}
i = 0; j = 0;
while(sisbios_mode[i].mode_no[0] != 0) {
if(!strnicmp(nameptr, sisbios_mode[i++].name, strlen(nameptr))) {
if(sisfb_fstn) {
if(sisbios_mode[i-1].mode_no[1] == 0x50 ||
sisbios_mode[i-1].mode_no[1] == 0x56 ||
sisbios_mode[i-1].mode_no[1] == 0x53)
continue;
} else {
if(sisbios_mode[i-1].mode_no[1] == 0x5a ||
sisbios_mode[i-1].mode_no[1] == 0x5b)
continue;
}
sisfb_mode_idx = i - 1;
j = 1;
break;
}
}
if((!j) && !quiet)
printk(KERN_ERR "sisfb: Invalid mode '%s'\n", nameptr);
}
#ifndef MODULE
static void sisfb_get_vga_mode_from_kernel(void)
{
#ifdef CONFIG_X86
char mymode[32];
int mydepth = screen_info.lfb_depth;
if(screen_info.orig_video_isVGA != VIDEO_TYPE_VLFB) return;
if( (screen_info.lfb_width >= 320) && (screen_info.lfb_width <= 2048) &&
(screen_info.lfb_height >= 200) && (screen_info.lfb_height <= 1536) &&
(mydepth >= 8) && (mydepth <= 32) ) {
if(mydepth == 24) mydepth = 32;
sprintf(mymode, "%ux%ux%u", screen_info.lfb_width,
screen_info.lfb_height,
mydepth);
printk(KERN_DEBUG
"sisfb: Using vga mode %s pre-set by kernel as default\n",
mymode);
sisfb_search_mode(mymode, true);
}
#endif
return;
}
#endif
static void __init
sisfb_search_crt2type(const char *name)
{
int i = 0;
/* We don't know the hardware specs yet and there is no ivideo */
if(name == NULL) return;
while(sis_crt2type[i].type_no != -1) {
if(!strnicmp(name, sis_crt2type[i].name, strlen(sis_crt2type[i].name))) {
sisfb_crt2type = sis_crt2type[i].type_no;
sisfb_tvplug = sis_crt2type[i].tvplug_no;
sisfb_crt2flags = sis_crt2type[i].flags;
break;
}
i++;
}
sisfb_dstn = (sisfb_crt2flags & FL_550_DSTN) ? 1 : 0;
sisfb_fstn = (sisfb_crt2flags & FL_550_FSTN) ? 1 : 0;
if(sisfb_crt2type < 0)
printk(KERN_ERR "sisfb: Invalid CRT2 type: %s\n", name);
}
static void __init
sisfb_search_tvstd(const char *name)
{
int i = 0;
/* We don't know the hardware specs yet and there is no ivideo */
if(name == NULL)
return;
while(sis_tvtype[i].type_no != -1) {
if(!strnicmp(name, sis_tvtype[i].name, strlen(sis_tvtype[i].name))) {
sisfb_tvstd = sis_tvtype[i].type_no;
break;
}
i++;
}
}
static void __init
sisfb_search_specialtiming(const char *name)
{
int i = 0;
bool found = false;
/* We don't know the hardware specs yet and there is no ivideo */
if(name == NULL)
return;
if(!strnicmp(name, "none", 4)) {
sisfb_specialtiming = CUT_FORCENONE;
printk(KERN_DEBUG "sisfb: Special timing disabled\n");
} else {
while(mycustomttable[i].chipID != 0) {
if(!strnicmp(name,mycustomttable[i].optionName,
strlen(mycustomttable[i].optionName))) {
sisfb_specialtiming = mycustomttable[i].SpecialID;
found = true;
printk(KERN_INFO "sisfb: Special timing for %s %s forced (\"%s\")\n",
mycustomttable[i].vendorName,
mycustomttable[i].cardName,
mycustomttable[i].optionName);
break;
}
i++;
}
if(!found) {
printk(KERN_WARNING "sisfb: Invalid SpecialTiming parameter, valid are:");
printk(KERN_WARNING "\t\"none\" (to disable special timings)\n");
i = 0;
while(mycustomttable[i].chipID != 0) {
printk(KERN_WARNING "\t\"%s\" (for %s %s)\n",
mycustomttable[i].optionName,
mycustomttable[i].vendorName,
mycustomttable[i].cardName);
i++;
}
}
}
}
/* ----------- Various detection routines ----------- */
static void sisfb_detect_custom_timing(struct sis_video_info *ivideo)
{
unsigned char *biosver = NULL;
unsigned char *biosdate = NULL;
bool footprint;
u32 chksum = 0;
int i, j;
if(ivideo->SiS_Pr.UseROM) {
biosver = ivideo->SiS_Pr.VirtualRomBase + 0x06;
biosdate = ivideo->SiS_Pr.VirtualRomBase + 0x2c;
for(i = 0; i < 32768; i++)
chksum += ivideo->SiS_Pr.VirtualRomBase[i];
}
i = 0;
do {
if( (mycustomttable[i].chipID == ivideo->chip) &&
((!strlen(mycustomttable[i].biosversion)) ||
(ivideo->SiS_Pr.UseROM &&
(!strncmp(mycustomttable[i].biosversion, biosver,
strlen(mycustomttable[i].biosversion))))) &&
((!strlen(mycustomttable[i].biosdate)) ||
(ivideo->SiS_Pr.UseROM &&
(!strncmp(mycustomttable[i].biosdate, biosdate,
strlen(mycustomttable[i].biosdate))))) &&
((!mycustomttable[i].bioschksum) ||
(ivideo->SiS_Pr.UseROM &&
(mycustomttable[i].bioschksum == chksum))) &&
(mycustomttable[i].pcisubsysvendor == ivideo->subsysvendor) &&
(mycustomttable[i].pcisubsyscard == ivideo->subsysdevice) ) {
footprint = true;
for(j = 0; j < 5; j++) {
if(mycustomttable[i].biosFootprintAddr[j]) {
if(ivideo->SiS_Pr.UseROM) {
if(ivideo->SiS_Pr.VirtualRomBase[mycustomttable[i].biosFootprintAddr[j]] !=
mycustomttable[i].biosFootprintData[j]) {
footprint = false;
}
} else
footprint = false;
}
}
if(footprint) {
ivideo->SiS_Pr.SiS_CustomT = mycustomttable[i].SpecialID;
printk(KERN_DEBUG "sisfb: Identified [%s %s], special timing applies\n",
mycustomttable[i].vendorName,
mycustomttable[i].cardName);
printk(KERN_DEBUG "sisfb: [specialtiming parameter name: %s]\n",
mycustomttable[i].optionName);
break;
}
}
i++;
} while(mycustomttable[i].chipID);
}
static bool sisfb_interpret_edid(struct sisfb_monitor *monitor, u8 *buffer)
{
int i, j, xres, yres, refresh, index;
u32 emodes;
if(buffer[0] != 0x00 || buffer[1] != 0xff ||
buffer[2] != 0xff || buffer[3] != 0xff ||
buffer[4] != 0xff || buffer[5] != 0xff ||
buffer[6] != 0xff || buffer[7] != 0x00) {
printk(KERN_DEBUG "sisfb: Bad EDID header\n");
return false;
}
if(buffer[0x12] != 0x01) {
printk(KERN_INFO "sisfb: EDID version %d not supported\n",
buffer[0x12]);
return false;
}
monitor->feature = buffer[0x18];
if(!(buffer[0x14] & 0x80)) {
if(!(buffer[0x14] & 0x08)) {
printk(KERN_INFO
"sisfb: WARNING: Monitor does not support separate syncs\n");
}
}
if(buffer[0x13] >= 0x01) {
/* EDID V1 rev 1 and 2: Search for monitor descriptor
* to extract ranges
*/
j = 0x36;
for(i=0; i<4; i++) {
if(buffer[j] == 0x00 && buffer[j + 1] == 0x00 &&
buffer[j + 2] == 0x00 && buffer[j + 3] == 0xfd &&
buffer[j + 4] == 0x00) {
monitor->hmin = buffer[j + 7];
monitor->hmax = buffer[j + 8];
monitor->vmin = buffer[j + 5];
monitor->vmax = buffer[j + 6];
monitor->dclockmax = buffer[j + 9] * 10 * 1000;
monitor->datavalid = true;
break;
}
j += 18;
}
}
if(!monitor->datavalid) {
/* Otherwise: Get a range from the list of supported
* Estabished Timings. This is not entirely accurate,
* because fixed frequency monitors are not supported
* that way.
*/
monitor->hmin = 65535; monitor->hmax = 0;
monitor->vmin = 65535; monitor->vmax = 0;
monitor->dclockmax = 0;
emodes = buffer[0x23] | (buffer[0x24] << 8) | (buffer[0x25] << 16);
for(i = 0; i < 13; i++) {
if(emodes & sisfb_ddcsmodes[i].mask) {
if(monitor->hmin > sisfb_ddcsmodes[i].h) monitor->hmin = sisfb_ddcsmodes[i].h;
if(monitor->hmax < sisfb_ddcsmodes[i].h) monitor->hmax = sisfb_ddcsmodes[i].h + 1;
if(monitor->vmin > sisfb_ddcsmodes[i].v) monitor->vmin = sisfb_ddcsmodes[i].v;
if(monitor->vmax < sisfb_ddcsmodes[i].v) monitor->vmax = sisfb_ddcsmodes[i].v;
if(monitor->dclockmax < sisfb_ddcsmodes[i].d) monitor->dclockmax = sisfb_ddcsmodes[i].d;
}
}
index = 0x26;
for(i = 0; i < 8; i++) {
xres = (buffer[index] + 31) * 8;
switch(buffer[index + 1] & 0xc0) {
case 0xc0: yres = (xres * 9) / 16; break;
case 0x80: yres = (xres * 4) / 5; break;
case 0x40: yres = (xres * 3) / 4; break;
default: yres = xres; break;
}
refresh = (buffer[index + 1] & 0x3f) + 60;
if((xres >= 640) && (yres >= 480)) {
for(j = 0; j < 8; j++) {
if((xres == sisfb_ddcfmodes[j].x) &&
(yres == sisfb_ddcfmodes[j].y) &&
(refresh == sisfb_ddcfmodes[j].v)) {
if(monitor->hmin > sisfb_ddcfmodes[j].h) monitor->hmin = sisfb_ddcfmodes[j].h;
if(monitor->hmax < sisfb_ddcfmodes[j].h) monitor->hmax = sisfb_ddcfmodes[j].h + 1;
if(monitor->vmin > sisfb_ddcsmodes[j].v) monitor->vmin = sisfb_ddcsmodes[j].v;
if(monitor->vmax < sisfb_ddcsmodes[j].v) monitor->vmax = sisfb_ddcsmodes[j].v;
if(monitor->dclockmax < sisfb_ddcsmodes[j].d) monitor->dclockmax = sisfb_ddcsmodes[j].d;
}
}
}
index += 2;
}
if((monitor->hmin <= monitor->hmax) && (monitor->vmin <= monitor->vmax)) {
monitor->datavalid = true;
}
}
return monitor->datavalid;
}
static void sisfb_handle_ddc(struct sis_video_info *ivideo,
struct sisfb_monitor *monitor, int crtno)
{
unsigned short temp, i, realcrtno = crtno;
unsigned char buffer[256];
monitor->datavalid = false;
if(crtno) {
if(ivideo->vbflags & CRT2_LCD) realcrtno = 1;
else if(ivideo->vbflags & CRT2_VGA) realcrtno = 2;
else return;
}
if((ivideo->sisfb_crt1off) && (!crtno))
return;
temp = SiS_HandleDDC(&ivideo->SiS_Pr, ivideo->vbflags, ivideo->sisvga_engine,
realcrtno, 0, &buffer[0], ivideo->vbflags2);
if((!temp) || (temp == 0xffff)) {
printk(KERN_INFO "sisfb: CRT%d DDC probing failed\n", crtno + 1);
return;
} else {
printk(KERN_INFO "sisfb: CRT%d DDC supported\n", crtno + 1);
printk(KERN_INFO "sisfb: CRT%d DDC level: %s%s%s%s\n",
crtno + 1,
(temp & 0x1a) ? "" : "[none of the supported]",
(temp & 0x02) ? "2 " : "",
(temp & 0x08) ? "D&P" : "",
(temp & 0x10) ? "FPDI-2" : "");
if(temp & 0x02) {
i = 3; /* Number of retrys */
do {
temp = SiS_HandleDDC(&ivideo->SiS_Pr, ivideo->vbflags, ivideo->sisvga_engine,
realcrtno, 1, &buffer[0], ivideo->vbflags2);
} while((temp) && i--);
if(!temp) {
if(sisfb_interpret_edid(monitor, &buffer[0])) {
printk(KERN_INFO "sisfb: Monitor range H %d-%dKHz, V %d-%dHz, Max. dotclock %dMHz\n",
monitor->hmin, monitor->hmax, monitor->vmin, monitor->vmax,
monitor->dclockmax / 1000);
} else {
printk(KERN_INFO "sisfb: CRT%d DDC EDID corrupt\n", crtno + 1);
}
} else {
printk(KERN_INFO "sisfb: CRT%d DDC reading failed\n", crtno + 1);
}
} else {
printk(KERN_INFO "sisfb: VESA D&P and FPDI-2 not supported yet\n");
}
}
}
/* -------------- Mode validation --------------- */
static bool
sisfb_verify_rate(struct sis_video_info *ivideo, struct sisfb_monitor *monitor,
int mode_idx, int rate_idx, int rate)
{
int htotal, vtotal;
unsigned int dclock, hsync;
if(!monitor->datavalid)
return true;
if(mode_idx < 0)
return false;
/* Skip for 320x200, 320x240, 640x400 */
switch(sisbios_mode[mode_idx].mode_no[ivideo->mni]) {
case 0x59:
case 0x41:
case 0x4f:
case 0x50:
case 0x56:
case 0x53:
case 0x2f:
case 0x5d:
case 0x5e:
return true;
#ifdef CONFIG_FB_SIS_315
case 0x5a:
case 0x5b:
if(ivideo->sisvga_engine == SIS_315_VGA) return true;
#endif
}
if(rate < (monitor->vmin - 1))
return false;
if(rate > (monitor->vmax + 1))
return false;
if(sisfb_gettotalfrommode(&ivideo->SiS_Pr,
sisbios_mode[mode_idx].mode_no[ivideo->mni],
&htotal, &vtotal, rate_idx)) {
dclock = (htotal * vtotal * rate) / 1000;
if(dclock > (monitor->dclockmax + 1000))
return false;
hsync = dclock / htotal;
if(hsync < (monitor->hmin - 1))
return false;
if(hsync > (monitor->hmax + 1))
return false;
} else {
return false;
}
return true;
}
static int
sisfb_validate_mode(struct sis_video_info *ivideo, int myindex, u32 vbflags)
{
u16 xres=0, yres, myres;
#ifdef CONFIG_FB_SIS_300
if(ivideo->sisvga_engine == SIS_300_VGA) {
if(!(sisbios_mode[myindex].chipset & MD_SIS300))
return -1 ;
}
#endif
#ifdef CONFIG_FB_SIS_315
if(ivideo->sisvga_engine == SIS_315_VGA) {
if(!(sisbios_mode[myindex].chipset & MD_SIS315))
return -1;
}
#endif
myres = sisbios_mode[myindex].yres;
switch(vbflags & VB_DISPTYPE_DISP2) {
case CRT2_LCD:
xres = ivideo->lcdxres; yres = ivideo->lcdyres;
if((ivideo->SiS_Pr.SiS_CustomT != CUT_PANEL848) &&
(ivideo->SiS_Pr.SiS_CustomT != CUT_PANEL856)) {
if(sisbios_mode[myindex].xres > xres)
return -1;
if(myres > yres)
return -1;
}
if(ivideo->sisfb_fstn) {
if(sisbios_mode[myindex].xres == 320) {
if(myres == 240) {
switch(sisbios_mode[myindex].mode_no[1]) {
case 0x50: myindex = MODE_FSTN_8; break;
case 0x56: myindex = MODE_FSTN_16; break;
case 0x53: return -1;
}
}
}
}
if(SiS_GetModeID_LCD(ivideo->sisvga_engine, vbflags, sisbios_mode[myindex].xres,
sisbios_mode[myindex].yres, 0, ivideo->sisfb_fstn,
ivideo->SiS_Pr.SiS_CustomT, xres, yres, ivideo->vbflags2) < 0x14) {
return -1;
}
break;
case CRT2_TV:
if(SiS_GetModeID_TV(ivideo->sisvga_engine, vbflags, sisbios_mode[myindex].xres,
sisbios_mode[myindex].yres, 0, ivideo->vbflags2) < 0x14) {
return -1;
}
break;
case CRT2_VGA:
if(SiS_GetModeID_VGA2(ivideo->sisvga_engine, vbflags, sisbios_mode[myindex].xres,
sisbios_mode[myindex].yres, 0, ivideo->vbflags2) < 0x14) {
return -1;
}
break;
}
return myindex;
}
static u8
sisfb_search_refresh_rate(struct sis_video_info *ivideo, unsigned int rate, int mode_idx)
{
int i = 0;
u16 xres = sisbios_mode[mode_idx].xres;
u16 yres = sisbios_mode[mode_idx].yres;
ivideo->rate_idx = 0;
while((sisfb_vrate[i].idx != 0) && (sisfb_vrate[i].xres <= xres)) {
if((sisfb_vrate[i].xres == xres) && (sisfb_vrate[i].yres == yres)) {
if(sisfb_vrate[i].refresh == rate) {
ivideo->rate_idx = sisfb_vrate[i].idx;
break;
} else if(sisfb_vrate[i].refresh > rate) {
if((sisfb_vrate[i].refresh - rate) <= 3) {
DPRINTK("sisfb: Adjusting rate from %d up to %d\n",
rate, sisfb_vrate[i].refresh);
ivideo->rate_idx = sisfb_vrate[i].idx;
ivideo->refresh_rate = sisfb_vrate[i].refresh;
} else if((sisfb_vrate[i].idx != 1) &&
((rate - sisfb_vrate[i-1].refresh) <= 2)) {
DPRINTK("sisfb: Adjusting rate from %d down to %d\n",
rate, sisfb_vrate[i-1].refresh);
ivideo->rate_idx = sisfb_vrate[i-1].idx;
ivideo->refresh_rate = sisfb_vrate[i-1].refresh;
}
break;
} else if((rate - sisfb_vrate[i].refresh) <= 2) {
DPRINTK("sisfb: Adjusting rate from %d down to %d\n",
rate, sisfb_vrate[i].refresh);
ivideo->rate_idx = sisfb_vrate[i].idx;
break;
}
}
i++;
}
if(ivideo->rate_idx > 0) {
return ivideo->rate_idx;
} else {
printk(KERN_INFO "sisfb: Unsupported rate %d for %dx%d\n",
rate, xres, yres);
return 0;
}
}
static bool
sisfb_bridgeisslave(struct sis_video_info *ivideo)
{
unsigned char P1_00;
if(!(ivideo->vbflags2 & VB2_VIDEOBRIDGE))
return false;
P1_00 = SiS_GetReg(SISPART1, 0x00);
if( ((ivideo->sisvga_engine == SIS_300_VGA) && (P1_00 & 0xa0) == 0x20) ||
((ivideo->sisvga_engine == SIS_315_VGA) && (P1_00 & 0x50) == 0x10) ) {
return true;
} else {
return false;
}
}
static bool
sisfballowretracecrt1(struct sis_video_info *ivideo)
{
u8 temp;
temp = SiS_GetReg(SISCR, 0x17);
if(!(temp & 0x80))
return false;
temp = SiS_GetReg(SISSR, 0x1f);
if(temp & 0xc0)
return false;
return true;
}
static bool
sisfbcheckvretracecrt1(struct sis_video_info *ivideo)
{
if(!sisfballowretracecrt1(ivideo))
return false;
if (SiS_GetRegByte(SISINPSTAT) & 0x08)
return true;
else
return false;
}
static void
sisfbwaitretracecrt1(struct sis_video_info *ivideo)
{
int watchdog;
if(!sisfballowretracecrt1(ivideo))
return;
watchdog = 65536;
while ((!(SiS_GetRegByte(SISINPSTAT) & 0x08)) && --watchdog);
watchdog = 65536;
while ((SiS_GetRegByte(SISINPSTAT) & 0x08) && --watchdog);
}
static bool
sisfbcheckvretracecrt2(struct sis_video_info *ivideo)
{
unsigned char temp, reg;
switch(ivideo->sisvga_engine) {
case SIS_300_VGA: reg = 0x25; break;
case SIS_315_VGA: reg = 0x30; break;
default: return false;
}
temp = SiS_GetReg(SISPART1, reg);
if(temp & 0x02)
return true;
else
return false;
}
static bool
sisfb_CheckVBRetrace(struct sis_video_info *ivideo)
{
if(ivideo->currentvbflags & VB_DISPTYPE_DISP2) {
if(!sisfb_bridgeisslave(ivideo)) {
return sisfbcheckvretracecrt2(ivideo);
}
}
return sisfbcheckvretracecrt1(ivideo);
}
static u32
sisfb_setupvbblankflags(struct sis_video_info *ivideo, u32 *vcount, u32 *hcount)
{
u8 idx, reg1, reg2, reg3, reg4;
u32 ret = 0;
(*vcount) = (*hcount) = 0;
if((ivideo->currentvbflags & VB_DISPTYPE_DISP2) && (!(sisfb_bridgeisslave(ivideo)))) {
ret |= (FB_VBLANK_HAVE_VSYNC |
FB_VBLANK_HAVE_HBLANK |
FB_VBLANK_HAVE_VBLANK |
FB_VBLANK_HAVE_VCOUNT |
FB_VBLANK_HAVE_HCOUNT);
switch(ivideo->sisvga_engine) {
case SIS_300_VGA: idx = 0x25; break;
default:
case SIS_315_VGA: idx = 0x30; break;
}
reg1 = SiS_GetReg(SISPART1, (idx+0)); /* 30 */
reg2 = SiS_GetReg(SISPART1, (idx+1)); /* 31 */
reg3 = SiS_GetReg(SISPART1, (idx+2)); /* 32 */
reg4 = SiS_GetReg(SISPART1, (idx+3)); /* 33 */
if(reg1 & 0x01) ret |= FB_VBLANK_VBLANKING;
if(reg1 & 0x02) ret |= FB_VBLANK_VSYNCING;
if(reg4 & 0x80) ret |= FB_VBLANK_HBLANKING;
(*vcount) = reg3 | ((reg4 & 0x70) << 4);
(*hcount) = reg2 | ((reg4 & 0x0f) << 8);
} else if(sisfballowretracecrt1(ivideo)) {
ret |= (FB_VBLANK_HAVE_VSYNC |
FB_VBLANK_HAVE_VBLANK |
FB_VBLANK_HAVE_VCOUNT |
FB_VBLANK_HAVE_HCOUNT);
reg1 = SiS_GetRegByte(SISINPSTAT);
if(reg1 & 0x08) ret |= FB_VBLANK_VSYNCING;
if(reg1 & 0x01) ret |= FB_VBLANK_VBLANKING;
reg1 = SiS_GetReg(SISCR, 0x20);
reg1 = SiS_GetReg(SISCR, 0x1b);
reg2 = SiS_GetReg(SISCR, 0x1c);
reg3 = SiS_GetReg(SISCR, 0x1d);
(*vcount) = reg2 | ((reg3 & 0x07) << 8);
(*hcount) = (reg1 | ((reg3 & 0x10) << 4)) << 3;
}
return ret;
}
static int
sisfb_myblank(struct sis_video_info *ivideo, int blank)
{
u8 sr01, sr11, sr1f, cr63=0, p2_0, p1_13;
bool backlight = true;
switch(blank) {
case FB_BLANK_UNBLANK: /* on */
sr01 = 0x00;
sr11 = 0x00;
sr1f = 0x00;
cr63 = 0x00;
p2_0 = 0x20;
p1_13 = 0x00;
backlight = true;
break;
case FB_BLANK_NORMAL: /* blank */
sr01 = 0x20;
sr11 = 0x00;
sr1f = 0x00;
cr63 = 0x00;
p2_0 = 0x20;
p1_13 = 0x00;
backlight = true;
break;
case FB_BLANK_VSYNC_SUSPEND: /* no vsync */
sr01 = 0x20;
sr11 = 0x08;
sr1f = 0x80;
cr63 = 0x40;
p2_0 = 0x40;
p1_13 = 0x80;
backlight = false;
break;
case FB_BLANK_HSYNC_SUSPEND: /* no hsync */
sr01 = 0x20;
sr11 = 0x08;
sr1f = 0x40;
cr63 = 0x40;
p2_0 = 0x80;
p1_13 = 0x40;
backlight = false;
break;
case FB_BLANK_POWERDOWN: /* off */
sr01 = 0x20;
sr11 = 0x08;
sr1f = 0xc0;
cr63 = 0x40;
p2_0 = 0xc0;
p1_13 = 0xc0;
backlight = false;
break;
default:
return 1;
}
if(ivideo->currentvbflags & VB_DISPTYPE_CRT1) {
if( (!ivideo->sisfb_thismonitor.datavalid) ||
((ivideo->sisfb_thismonitor.datavalid) &&
(ivideo->sisfb_thismonitor.feature & 0xe0))) {
if(ivideo->sisvga_engine == SIS_315_VGA) {
SiS_SetRegANDOR(SISCR, ivideo->SiS_Pr.SiS_MyCR63, 0xbf, cr63);
}
if(!(sisfb_bridgeisslave(ivideo))) {
SiS_SetRegANDOR(SISSR, 0x01, ~0x20, sr01);
SiS_SetRegANDOR(SISSR, 0x1f, 0x3f, sr1f);
}
}
}
if(ivideo->currentvbflags & CRT2_LCD) {
if(ivideo->vbflags2 & VB2_SISLVDSBRIDGE) {
if(backlight) {
SiS_SiS30xBLOn(&ivideo->SiS_Pr);
} else {
SiS_SiS30xBLOff(&ivideo->SiS_Pr);
}
} else if(ivideo->sisvga_engine == SIS_315_VGA) {
#ifdef CONFIG_FB_SIS_315
if(ivideo->vbflags2 & VB2_CHRONTEL) {
if(backlight) {
SiS_Chrontel701xBLOn(&ivideo->SiS_Pr);
} else {
SiS_Chrontel701xBLOff(&ivideo->SiS_Pr);
}
}
#endif
}
if(((ivideo->sisvga_engine == SIS_300_VGA) &&
(ivideo->vbflags2 & (VB2_301|VB2_30xBDH|VB2_LVDS))) ||
((ivideo->sisvga_engine == SIS_315_VGA) &&
((ivideo->vbflags2 & (VB2_LVDS | VB2_CHRONTEL)) == VB2_LVDS))) {
SiS_SetRegANDOR(SISSR, 0x11, ~0x0c, sr11);
}
if(ivideo->sisvga_engine == SIS_300_VGA) {
if((ivideo->vbflags2 & VB2_30xB) &&
(!(ivideo->vbflags2 & VB2_30xBDH))) {
SiS_SetRegANDOR(SISPART1, 0x13, 0x3f, p1_13);
}
} else if(ivideo->sisvga_engine == SIS_315_VGA) {
if((ivideo->vbflags2 & VB2_30xB) &&
(!(ivideo->vbflags2 & VB2_30xBDH))) {
SiS_SetRegANDOR(SISPART2, 0x00, 0x1f, p2_0);
}
}
} else if(ivideo->currentvbflags & CRT2_VGA) {
if(ivideo->vbflags2 & VB2_30xB) {
SiS_SetRegANDOR(SISPART2, 0x00, 0x1f, p2_0);
}
}
return 0;
}
/* ------------- Callbacks from init.c/init301.c -------------- */
#ifdef CONFIG_FB_SIS_300
unsigned int
sisfb_read_nbridge_pci_dword(struct SiS_Private *SiS_Pr, int reg)
{
struct sis_video_info *ivideo = (struct sis_video_info *)SiS_Pr->ivideo;
u32 val = 0;
pci_read_config_dword(ivideo->nbridge, reg, &val);
return (unsigned int)val;
}
void
sisfb_write_nbridge_pci_dword(struct SiS_Private *SiS_Pr, int reg, unsigned int val)
{
struct sis_video_info *ivideo = (struct sis_video_info *)SiS_Pr->ivideo;
pci_write_config_dword(ivideo->nbridge, reg, (u32)val);
}
unsigned int
sisfb_read_lpc_pci_dword(struct SiS_Private *SiS_Pr, int reg)
{
struct sis_video_info *ivideo = (struct sis_video_info *)SiS_Pr->ivideo;
u32 val = 0;
if(!ivideo->lpcdev) return 0;
pci_read_config_dword(ivideo->lpcdev, reg, &val);
return (unsigned int)val;
}
#endif
#ifdef CONFIG_FB_SIS_315
void
sisfb_write_nbridge_pci_byte(struct SiS_Private *SiS_Pr, int reg, unsigned char val)
{
struct sis_video_info *ivideo = (struct sis_video_info *)SiS_Pr->ivideo;
pci_write_config_byte(ivideo->nbridge, reg, (u8)val);
}
unsigned int
sisfb_read_mio_pci_word(struct SiS_Private *SiS_Pr, int reg)
{
struct sis_video_info *ivideo = (struct sis_video_info *)SiS_Pr->ivideo;
u16 val = 0;
if(!ivideo->lpcdev) return 0;
pci_read_config_word(ivideo->lpcdev, reg, &val);
return (unsigned int)val;
}
#endif
/* ----------- FBDev related routines for all series ----------- */
static int
sisfb_get_cmap_len(const struct fb_var_screeninfo *var)
{
return (var->bits_per_pixel == 8) ? 256 : 16;
}
static void
sisfb_set_vparms(struct sis_video_info *ivideo)
{
switch(ivideo->video_bpp) {
case 8:
ivideo->DstColor = 0x0000;
ivideo->SiS310_AccelDepth = 0x00000000;
ivideo->video_cmap_len = 256;
break;
case 16:
ivideo->DstColor = 0x8000;
ivideo->SiS310_AccelDepth = 0x00010000;
ivideo->video_cmap_len = 16;
break;
case 32:
ivideo->DstColor = 0xC000;
ivideo->SiS310_AccelDepth = 0x00020000;
ivideo->video_cmap_len = 16;
break;
default:
ivideo->video_cmap_len = 16;
printk(KERN_ERR "sisfb: Unsupported depth %d", ivideo->video_bpp);
ivideo->accel = 0;
}
}
static int
sisfb_calc_maxyres(struct sis_video_info *ivideo, struct fb_var_screeninfo *var)
{
int maxyres = ivideo->sisfb_mem / (var->xres_virtual * (var->bits_per_pixel >> 3));
if(maxyres > 32767) maxyres = 32767;
return maxyres;
}
static void
sisfb_calc_pitch(struct sis_video_info *ivideo, struct fb_var_screeninfo *var)
{
ivideo->video_linelength = var->xres_virtual * (var->bits_per_pixel >> 3);
ivideo->scrnpitchCRT1 = ivideo->video_linelength;
if(!(ivideo->currentvbflags & CRT1_LCDA)) {
if((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) {
ivideo->scrnpitchCRT1 <<= 1;
}
}
}
static void
sisfb_set_pitch(struct sis_video_info *ivideo)
{
bool isslavemode = false;
unsigned short HDisplay1 = ivideo->scrnpitchCRT1 >> 3;
unsigned short HDisplay2 = ivideo->video_linelength >> 3;
if(sisfb_bridgeisslave(ivideo)) isslavemode = true;
/* We need to set pitch for CRT1 if bridge is in slave mode, too */
if((ivideo->currentvbflags & VB_DISPTYPE_DISP1) || (isslavemode)) {
SiS_SetReg(SISCR, 0x13, (HDisplay1 & 0xFF));
SiS_SetRegANDOR(SISSR, 0x0E, 0xF0, (HDisplay1 >> 8));
}
/* We must not set the pitch for CRT2 if bridge is in slave mode */
if((ivideo->currentvbflags & VB_DISPTYPE_DISP2) && (!isslavemode)) {
SiS_SetRegOR(SISPART1, ivideo->CRT2_write_enable, 0x01);
SiS_SetReg(SISPART1, 0x07, (HDisplay2 & 0xFF));
SiS_SetRegANDOR(SISPART1, 0x09, 0xF0, (HDisplay2 >> 8));
}
}
static void
sisfb_bpp_to_var(struct sis_video_info *ivideo, struct fb_var_screeninfo *var)
{
ivideo->video_cmap_len = sisfb_get_cmap_len(var);
switch(var->bits_per_pixel) {
case 8:
var->red.offset = var->green.offset = var->blue.offset = 0;
var->red.length = var->green.length = var->blue.length = 8;
break;
case 16:
var->red.offset = 11;
var->red.length = 5;
var->green.offset = 5;
var->green.length = 6;
var->blue.offset = 0;
var->blue.length = 5;
var->transp.offset = 0;
var->transp.length = 0;
break;
case 32:
var->red.offset = 16;
var->red.length = 8;
var->green.offset = 8;
var->green.length = 8;
var->blue.offset = 0;
var->blue.length = 8;
var->transp.offset = 24;
var->transp.length = 8;
break;
}
}
static int
sisfb_set_mode(struct sis_video_info *ivideo, int clrscrn)
{
unsigned short modeno = ivideo->mode_no;
/* >=2.6.12's fbcon clears the screen anyway */
modeno |= 0x80;
SiS_SetReg(SISSR, IND_SIS_PASSWORD, SIS_PASSWORD);
sisfb_pre_setmode(ivideo);
if(!SiSSetMode(&ivideo->SiS_Pr, modeno)) {
printk(KERN_ERR "sisfb: Setting mode[0x%x] failed\n", ivideo->mode_no);
return -EINVAL;
}
SiS_SetReg(SISSR, IND_SIS_PASSWORD, SIS_PASSWORD);
sisfb_post_setmode(ivideo);
return 0;
}
static int
sisfb_do_set_var(struct fb_var_screeninfo *var, int isactive, struct fb_info *info)
{
struct sis_video_info *ivideo = (struct sis_video_info *)info->par;
unsigned int htotal = 0, vtotal = 0;
unsigned int drate = 0, hrate = 0;
int found_mode = 0, ret;
int old_mode;
u32 pixclock;
htotal = var->left_margin + var->xres + var->right_margin + var->hsync_len;
vtotal = var->upper_margin + var->lower_margin + var->vsync_len;
pixclock = var->pixclock;
if((var->vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED) {
vtotal += var->yres;
vtotal <<= 1;
} else if((var->vmode & FB_VMODE_MASK) == FB_VMODE_DOUBLE) {
vtotal += var->yres;
vtotal <<= 2;
} else if((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) {
vtotal += var->yres;
vtotal <<= 1;
} else vtotal += var->yres;
if(!(htotal) || !(vtotal)) {
DPRINTK("sisfb: Invalid 'var' information\n");
return -EINVAL;
}
if(pixclock && htotal && vtotal) {
drate = 1000000000 / pixclock;
hrate = (drate * 1000) / htotal;
ivideo->refresh_rate = (unsigned int) (hrate * 2 / vtotal);
} else {
ivideo->refresh_rate = 60;
}
old_mode = ivideo->sisfb_mode_idx;
ivideo->sisfb_mode_idx = 0;
while( (sisbios_mode[ivideo->sisfb_mode_idx].mode_no[0] != 0) &&
(sisbios_mode[ivideo->sisfb_mode_idx].xres <= var->xres) ) {
if( (sisbios_mode[ivideo->sisfb_mode_idx].xres == var->xres) &&
(sisbios_mode[ivideo->sisfb_mode_idx].yres == var->yres) &&
(sisbios_mode[ivideo->sisfb_mode_idx].bpp == var->bits_per_pixel)) {
ivideo->mode_no = sisbios_mode[ivideo->sisfb_mode_idx].mode_no[ivideo->mni];
found_mode = 1;
break;
}
ivideo->sisfb_mode_idx++;
}
if(found_mode) {
ivideo->sisfb_mode_idx = sisfb_validate_mode(ivideo,
ivideo->sisfb_mode_idx, ivideo->currentvbflags);
} else {
ivideo->sisfb_mode_idx = -1;
}
if(ivideo->sisfb_mode_idx < 0) {
printk(KERN_ERR "sisfb: Mode %dx%dx%d not supported\n", var->xres,
var->yres, var->bits_per_pixel);
ivideo->sisfb_mode_idx = old_mode;
return -EINVAL;
}
ivideo->mode_no = sisbios_mode[ivideo->sisfb_mode_idx].mode_no[ivideo->mni];
if(sisfb_search_refresh_rate(ivideo, ivideo->refresh_rate, ivideo->sisfb_mode_idx) == 0) {
ivideo->rate_idx = sisbios_mode[ivideo->sisfb_mode_idx].rate_idx;
ivideo->refresh_rate = 60;
}
if(isactive) {
/* If acceleration to be used? Need to know
* before pre/post_set_mode()
*/
ivideo->accel = 0;
#if defined(FBINFO_HWACCEL_DISABLED) && defined(FBINFO_HWACCEL_XPAN)
#ifdef STUPID_ACCELF_TEXT_SHIT
if(var->accel_flags & FB_ACCELF_TEXT) {
info->flags &= ~FBINFO_HWACCEL_DISABLED;
} else {
info->flags |= FBINFO_HWACCEL_DISABLED;
}
#endif
if(!(info->flags & FBINFO_HWACCEL_DISABLED)) ivideo->accel = -1;
#else
if(var->accel_flags & FB_ACCELF_TEXT) ivideo->accel = -1;
#endif
if((ret = sisfb_set_mode(ivideo, 1))) {
return ret;
}
ivideo->video_bpp = sisbios_mode[ivideo->sisfb_mode_idx].bpp;
ivideo->video_width = sisbios_mode[ivideo->sisfb_mode_idx].xres;
ivideo->video_height = sisbios_mode[ivideo->sisfb_mode_idx].yres;
sisfb_calc_pitch(ivideo, var);
sisfb_set_pitch(ivideo);
sisfb_set_vparms(ivideo);
ivideo->current_width = ivideo->video_width;
ivideo->current_height = ivideo->video_height;
ivideo->current_bpp = ivideo->video_bpp;
ivideo->current_htotal = htotal;
ivideo->current_vtotal = vtotal;
ivideo->current_linelength = ivideo->video_linelength;
ivideo->current_pixclock = var->pixclock;
ivideo->current_refresh_rate = ivideo->refresh_rate;
ivideo->sisfb_lastrates[ivideo->mode_no] = ivideo->refresh_rate;
}
return 0;
}
static void
sisfb_set_base_CRT1(struct sis_video_info *ivideo, unsigned int base)
{
SiS_SetReg(SISSR, IND_SIS_PASSWORD, SIS_PASSWORD);
SiS_SetReg(SISCR, 0x0D, base & 0xFF);
SiS_SetReg(SISCR, 0x0C, (base >> 8) & 0xFF);
SiS_SetReg(SISSR, 0x0D, (base >> 16) & 0xFF);
if(ivideo->sisvga_engine == SIS_315_VGA) {
SiS_SetRegANDOR(SISSR, 0x37, 0xFE, (base >> 24) & 0x01);
}
}
static void
sisfb_set_base_CRT2(struct sis_video_info *ivideo, unsigned int base)
{
if(ivideo->currentvbflags & VB_DISPTYPE_DISP2) {
SiS_SetRegOR(SISPART1, ivideo->CRT2_write_enable, 0x01);
SiS_SetReg(SISPART1, 0x06, (base & 0xFF));
SiS_SetReg(SISPART1, 0x05, ((base >> 8) & 0xFF));
SiS_SetReg(SISPART1, 0x04, ((base >> 16) & 0xFF));
if(ivideo->sisvga_engine == SIS_315_VGA) {
SiS_SetRegANDOR(SISPART1, 0x02, 0x7F, ((base >> 24) & 0x01) << 7);
}
}
}
static int
sisfb_pan_var(struct sis_video_info *ivideo, struct fb_info *info,
struct fb_var_screeninfo *var)
{
ivideo->current_base = var->yoffset * info->var.xres_virtual
+ var->xoffset;
/* calculate base bpp dep. */
switch (info->var.bits_per_pixel) {
case 32:
break;
case 16:
ivideo->current_base >>= 1;
break;
case 8:
default:
ivideo->current_base >>= 2;
break;
}
ivideo->current_base += (ivideo->video_offset >> 2);
sisfb_set_base_CRT1(ivideo, ivideo->current_base);
sisfb_set_base_CRT2(ivideo, ivideo->current_base);
return 0;
}
static int
sisfb_open(struct fb_info *info, int user)
{
return 0;
}
static int
sisfb_release(struct fb_info *info, int user)
{
return 0;
}
static int
sisfb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue,
unsigned transp, struct fb_info *info)
{
struct sis_video_info *ivideo = (struct sis_video_info *)info->par;
if(regno >= sisfb_get_cmap_len(&info->var))
return 1;
switch(info->var.bits_per_pixel) {
case 8:
SiS_SetRegByte(SISDACA, regno);
SiS_SetRegByte(SISDACD, (red >> 10));
SiS_SetRegByte(SISDACD, (green >> 10));
SiS_SetRegByte(SISDACD, (blue >> 10));
if(ivideo->currentvbflags & VB_DISPTYPE_DISP2) {
SiS_SetRegByte(SISDAC2A, regno);
SiS_SetRegByte(SISDAC2D, (red >> 8));
SiS_SetRegByte(SISDAC2D, (green >> 8));
SiS_SetRegByte(SISDAC2D, (blue >> 8));
}
break;
case 16:
if (regno >= 16)
break;
((u32 *)(info->pseudo_palette))[regno] =
(red & 0xf800) |
((green & 0xfc00) >> 5) |
((blue & 0xf800) >> 11);
break;
case 32:
if (regno >= 16)
break;
red >>= 8;
green >>= 8;
blue >>= 8;
((u32 *)(info->pseudo_palette))[regno] =
(red << 16) | (green << 8) | (blue);
break;
}
return 0;
}
static int
sisfb_set_par(struct fb_info *info)
{
int err;
if((err = sisfb_do_set_var(&info->var, 1, info)))
return err;
sisfb_get_fix(&info->fix, -1, info);
return 0;
}
static int
sisfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
struct sis_video_info *ivideo = (struct sis_video_info *)info->par;
unsigned int htotal = 0, vtotal = 0, myrateindex = 0;
unsigned int drate = 0, hrate = 0, maxyres;
int found_mode = 0;
int refresh_rate, search_idx, tidx;
bool recalc_clock = false;
u32 pixclock;
htotal = var->left_margin + var->xres + var->right_margin + var->hsync_len;
vtotal = var->upper_margin + var->lower_margin + var->vsync_len;
pixclock = var->pixclock;
if((var->vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED) {
vtotal += var->yres;
vtotal <<= 1;
} else if((var->vmode & FB_VMODE_MASK) == FB_VMODE_DOUBLE) {
vtotal += var->yres;
vtotal <<= 2;
} else if((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) {
vtotal += var->yres;
vtotal <<= 1;
} else
vtotal += var->yres;
if(!(htotal) || !(vtotal)) {
SISFAIL("sisfb: no valid timing data");
}
search_idx = 0;
while( (sisbios_mode[search_idx].mode_no[0] != 0) &&
(sisbios_mode[search_idx].xres <= var->xres) ) {
if( (sisbios_mode[search_idx].xres == var->xres) &&
(sisbios_mode[search_idx].yres == var->yres) &&
(sisbios_mode[search_idx].bpp == var->bits_per_pixel)) {
if((tidx = sisfb_validate_mode(ivideo, search_idx,
ivideo->currentvbflags)) > 0) {
found_mode = 1;
search_idx = tidx;
break;
}
}
search_idx++;
}
if(!found_mode) {
search_idx = 0;
while(sisbios_mode[search_idx].mode_no[0] != 0) {
if( (var->xres <= sisbios_mode[search_idx].xres) &&
(var->yres <= sisbios_mode[search_idx].yres) &&
(var->bits_per_pixel == sisbios_mode[search_idx].bpp) ) {
if((tidx = sisfb_validate_mode(ivideo,search_idx,
ivideo->currentvbflags)) > 0) {
found_mode = 1;
search_idx = tidx;
break;
}
}
search_idx++;
}
if(found_mode) {
printk(KERN_DEBUG
"sisfb: Adapted from %dx%dx%d to %dx%dx%d\n",
var->xres, var->yres, var->bits_per_pixel,
sisbios_mode[search_idx].xres,
sisbios_mode[search_idx].yres,
var->bits_per_pixel);
var->xres = sisbios_mode[search_idx].xres;
var->yres = sisbios_mode[search_idx].yres;
} else {
printk(KERN_ERR
"sisfb: Failed to find supported mode near %dx%dx%d\n",
var->xres, var->yres, var->bits_per_pixel);
return -EINVAL;
}
}
if( ((ivideo->vbflags2 & VB2_LVDS) ||
((ivideo->vbflags2 & VB2_30xBDH) && (ivideo->currentvbflags & CRT2_LCD))) &&
(var->bits_per_pixel == 8) ) {
/* Slave modes on LVDS and 301B-DH */
refresh_rate = 60;
recalc_clock = true;
} else if( (ivideo->current_htotal == htotal) &&
(ivideo->current_vtotal == vtotal) &&
(ivideo->current_pixclock == pixclock) ) {
/* x=x & y=y & c=c -> assume depth change */
drate = 1000000000 / pixclock;
hrate = (drate * 1000) / htotal;
refresh_rate = (unsigned int) (hrate * 2 / vtotal);
} else if( ( (ivideo->current_htotal != htotal) ||
(ivideo->current_vtotal != vtotal) ) &&
(ivideo->current_pixclock == var->pixclock) ) {
/* x!=x | y!=y & c=c -> invalid pixclock */
if(ivideo->sisfb_lastrates[sisbios_mode[search_idx].mode_no[ivideo->mni]]) {
refresh_rate =
ivideo->sisfb_lastrates[sisbios_mode[search_idx].mode_no[ivideo->mni]];
} else if(ivideo->sisfb_parm_rate != -1) {
/* Sic, sisfb_parm_rate - want to know originally desired rate here */
refresh_rate = ivideo->sisfb_parm_rate;
} else {
refresh_rate = 60;
}
recalc_clock = true;
} else if((pixclock) && (htotal) && (vtotal)) {
drate = 1000000000 / pixclock;
hrate = (drate * 1000) / htotal;
refresh_rate = (unsigned int) (hrate * 2 / vtotal);
} else if(ivideo->current_refresh_rate) {
refresh_rate = ivideo->current_refresh_rate;
recalc_clock = true;
} else {
refresh_rate = 60;
recalc_clock = true;
}
myrateindex = sisfb_search_refresh_rate(ivideo, refresh_rate, search_idx);
/* Eventually recalculate timing and clock */
if(recalc_clock) {
if(!myrateindex) myrateindex = sisbios_mode[search_idx].rate_idx;
var->pixclock = (u32) (1000000000 / sisfb_mode_rate_to_dclock(&ivideo->SiS_Pr,
sisbios_mode[search_idx].mode_no[ivideo->mni],
myrateindex));
sisfb_mode_rate_to_ddata(&ivideo->SiS_Pr,
sisbios_mode[search_idx].mode_no[ivideo->mni],
myrateindex, var);
if((var->vmode & FB_VMODE_MASK) == FB_VMODE_DOUBLE) {
var->pixclock <<= 1;
}
}
if(ivideo->sisfb_thismonitor.datavalid) {
if(!sisfb_verify_rate(ivideo, &ivideo->sisfb_thismonitor, search_idx,
myrateindex, refresh_rate)) {
printk(KERN_INFO
"sisfb: WARNING: Refresh rate exceeds monitor specs!\n");
}
}
/* Adapt RGB settings */
sisfb_bpp_to_var(ivideo, var);
/* Sanity check for offsets */
if(var->xoffset < 0) var->xoffset = 0;
if(var->yoffset < 0) var->yoffset = 0;
if(var->xres > var->xres_virtual)
var->xres_virtual = var->xres;
if(ivideo->sisfb_ypan) {
maxyres = sisfb_calc_maxyres(ivideo, var);
if(ivideo->sisfb_max) {
var->yres_virtual = maxyres;
} else {
if(var->yres_virtual > maxyres) {
var->yres_virtual = maxyres;
}
}
if(var->yres_virtual <= var->yres) {
var->yres_virtual = var->yres;
}
} else {
if(var->yres != var->yres_virtual) {
var->yres_virtual = var->yres;
}
var->xoffset = 0;
var->yoffset = 0;
}
/* Truncate offsets to maximum if too high */
if(var->xoffset > var->xres_virtual - var->xres) {
var->xoffset = var->xres_virtual - var->xres - 1;
}
if(var->yoffset > var->yres_virtual - var->yres) {
var->yoffset = var->yres_virtual - var->yres - 1;
}
/* Set everything else to 0 */
var->red.msb_right =
var->green.msb_right =
var->blue.msb_right =
var->transp.offset =
var->transp.length =
var->transp.msb_right = 0;
return 0;
}
static int
sisfb_pan_display(struct fb_var_screeninfo *var, struct fb_info* info)
{
struct sis_video_info *ivideo = (struct sis_video_info *)info->par;
int err;
if (var->vmode & FB_VMODE_YWRAP)
return -EINVAL;
if (var->xoffset + info->var.xres > info->var.xres_virtual ||
var->yoffset + info->var.yres > info->var.yres_virtual)
return -EINVAL;
err = sisfb_pan_var(ivideo, info, var);
if (err < 0)
return err;
info->var.xoffset = var->xoffset;
info->var.yoffset = var->yoffset;
return 0;
}
static int
sisfb_blank(int blank, struct fb_info *info)
{
struct sis_video_info *ivideo = (struct sis_video_info *)info->par;
return sisfb_myblank(ivideo, blank);
}
/* ----------- FBDev related routines for all series ---------- */
static int sisfb_ioctl(struct fb_info *info, unsigned int cmd,
unsigned long arg)
{
struct sis_video_info *ivideo = (struct sis_video_info *)info->par;
struct sis_memreq sismemreq;
struct fb_vblank sisvbblank;
u32 gpu32 = 0;
#ifndef __user
#define __user
#endif
u32 __user *argp = (u32 __user *)arg;
switch(cmd) {
case FBIO_ALLOC:
if(!capable(CAP_SYS_RAWIO))
return -EPERM;
if(copy_from_user(&sismemreq, (void __user *)arg, sizeof(sismemreq)))
return -EFAULT;
sis_malloc(&sismemreq);
if(copy_to_user((void __user *)arg, &sismemreq, sizeof(sismemreq))) {
sis_free((u32)sismemreq.offset);
return -EFAULT;
}
break;
case FBIO_FREE:
if(!capable(CAP_SYS_RAWIO))
return -EPERM;
if(get_user(gpu32, argp))
return -EFAULT;
sis_free(gpu32);
break;
case FBIOGET_VBLANK:
memset(&sisvbblank, 0, sizeof(struct fb_vblank));
sisvbblank.count = 0;
sisvbblank.flags = sisfb_setupvbblankflags(ivideo, &sisvbblank.vcount, &sisvbblank.hcount);
if(copy_to_user((void __user *)arg, &sisvbblank, sizeof(sisvbblank)))
return -EFAULT;
break;
case SISFB_GET_INFO_SIZE:
return put_user(sizeof(struct sisfb_info), argp);
case SISFB_GET_INFO_OLD:
if(ivideo->warncount++ < 10)
printk(KERN_INFO
"sisfb: Deprecated ioctl call received - update your application!\n");
case SISFB_GET_INFO: /* For communication with X driver */
ivideo->sisfb_infoblock.sisfb_id = SISFB_ID;
ivideo->sisfb_infoblock.sisfb_version = VER_MAJOR;
ivideo->sisfb_infoblock.sisfb_revision = VER_MINOR;
ivideo->sisfb_infoblock.sisfb_patchlevel = VER_LEVEL;
ivideo->sisfb_infoblock.chip_id = ivideo->chip_id;
ivideo->sisfb_infoblock.sisfb_pci_vendor = ivideo->chip_vendor;
ivideo->sisfb_infoblock.memory = ivideo->video_size / 1024;
ivideo->sisfb_infoblock.heapstart = ivideo->heapstart / 1024;
if(ivideo->modechanged) {
ivideo->sisfb_infoblock.fbvidmode = ivideo->mode_no;
} else {
ivideo->sisfb_infoblock.fbvidmode = ivideo->modeprechange;
}
ivideo->sisfb_infoblock.sisfb_caps = ivideo->caps;
ivideo->sisfb_infoblock.sisfb_tqlen = ivideo->cmdQueueSize / 1024;
ivideo->sisfb_infoblock.sisfb_pcibus = ivideo->pcibus;
ivideo->sisfb_infoblock.sisfb_pcislot = ivideo->pcislot;
ivideo->sisfb_infoblock.sisfb_pcifunc = ivideo->pcifunc;
ivideo->sisfb_infoblock.sisfb_lcdpdc = ivideo->detectedpdc;
ivideo->sisfb_infoblock.sisfb_lcdpdca = ivideo->detectedpdca;
ivideo->sisfb_infoblock.sisfb_lcda = ivideo->detectedlcda;
ivideo->sisfb_infoblock.sisfb_vbflags = ivideo->vbflags;
ivideo->sisfb_infoblock.sisfb_currentvbflags = ivideo->currentvbflags;
ivideo->sisfb_infoblock.sisfb_scalelcd = ivideo->SiS_Pr.UsePanelScaler;
ivideo->sisfb_infoblock.sisfb_specialtiming = ivideo->SiS_Pr.SiS_CustomT;
ivideo->sisfb_infoblock.sisfb_haveemi = ivideo->SiS_Pr.HaveEMI ? 1 : 0;
ivideo->sisfb_infoblock.sisfb_haveemilcd = ivideo->SiS_Pr.HaveEMILCD ? 1 : 0;
ivideo->sisfb_infoblock.sisfb_emi30 = ivideo->SiS_Pr.EMI_30;
ivideo->sisfb_infoblock.sisfb_emi31 = ivideo->SiS_Pr.EMI_31;
ivideo->sisfb_infoblock.sisfb_emi32 = ivideo->SiS_Pr.EMI_32;
ivideo->sisfb_infoblock.sisfb_emi33 = ivideo->SiS_Pr.EMI_33;
ivideo->sisfb_infoblock.sisfb_tvxpos = (u16)(ivideo->tvxpos + 32);
ivideo->sisfb_infoblock.sisfb_tvypos = (u16)(ivideo->tvypos + 32);
ivideo->sisfb_infoblock.sisfb_heapsize = ivideo->sisfb_heap_size / 1024;
ivideo->sisfb_infoblock.sisfb_videooffset = ivideo->video_offset;
ivideo->sisfb_infoblock.sisfb_curfstn = ivideo->curFSTN;
ivideo->sisfb_infoblock.sisfb_curdstn = ivideo->curDSTN;
ivideo->sisfb_infoblock.sisfb_vbflags2 = ivideo->vbflags2;
ivideo->sisfb_infoblock.sisfb_can_post = ivideo->sisfb_can_post ? 1 : 0;
ivideo->sisfb_infoblock.sisfb_card_posted = ivideo->sisfb_card_posted ? 1 : 0;
ivideo->sisfb_infoblock.sisfb_was_boot_device = ivideo->sisfb_was_boot_device ? 1 : 0;
if(copy_to_user((void __user *)arg, &ivideo->sisfb_infoblock,
sizeof(ivideo->sisfb_infoblock)))
return -EFAULT;
break;
case SISFB_GET_VBRSTATUS_OLD:
if(ivideo->warncount++ < 10)
printk(KERN_INFO
"sisfb: Deprecated ioctl call received - update your application!\n");
case SISFB_GET_VBRSTATUS:
if(sisfb_CheckVBRetrace(ivideo))
return put_user((u32)1, argp);
else
return put_user((u32)0, argp);
case SISFB_GET_AUTOMAXIMIZE_OLD:
if(ivideo->warncount++ < 10)
printk(KERN_INFO
"sisfb: Deprecated ioctl call received - update your application!\n");
case SISFB_GET_AUTOMAXIMIZE:
if(ivideo->sisfb_max)
return put_user((u32)1, argp);
else
return put_user((u32)0, argp);
case SISFB_SET_AUTOMAXIMIZE_OLD:
if(ivideo->warncount++ < 10)
printk(KERN_INFO
"sisfb: Deprecated ioctl call received - update your application!\n");
case SISFB_SET_AUTOMAXIMIZE:
if(get_user(gpu32, argp))
return -EFAULT;
ivideo->sisfb_max = (gpu32) ? 1 : 0;
break;
case SISFB_SET_TVPOSOFFSET:
if(get_user(gpu32, argp))
return -EFAULT;
sisfb_set_TVxposoffset(ivideo, ((int)(gpu32 >> 16)) - 32);
sisfb_set_TVyposoffset(ivideo, ((int)(gpu32 & 0xffff)) - 32);
break;
case SISFB_GET_TVPOSOFFSET:
return put_user((u32)(((ivideo->tvxpos+32)<<16)|((ivideo->tvypos+32)&0xffff)),
argp);
case SISFB_COMMAND:
if(copy_from_user(&ivideo->sisfb_command, (void __user *)arg,
sizeof(struct sisfb_cmd)))
return -EFAULT;
sisfb_handle_command(ivideo, &ivideo->sisfb_command);
if(copy_to_user((void __user *)arg, &ivideo->sisfb_command,
sizeof(struct sisfb_cmd)))
return -EFAULT;
break;
case SISFB_SET_LOCK:
if(get_user(gpu32, argp))
return -EFAULT;
ivideo->sisfblocked = (gpu32) ? 1 : 0;
break;
default:
#ifdef SIS_NEW_CONFIG_COMPAT
return -ENOIOCTLCMD;
#else
return -EINVAL;
#endif
}
return 0;
}
static int
sisfb_get_fix(struct fb_fix_screeninfo *fix, int con, struct fb_info *info)
{
struct sis_video_info *ivideo = (struct sis_video_info *)info->par;
memset(fix, 0, sizeof(struct fb_fix_screeninfo));
strlcpy(fix->id, ivideo->myid, sizeof(fix->id));
mutex_lock(&info->mm_lock);
fix->smem_start = ivideo->video_base + ivideo->video_offset;
fix->smem_len = ivideo->sisfb_mem;
mutex_unlock(&info->mm_lock);
fix->type = FB_TYPE_PACKED_PIXELS;
fix->type_aux = 0;
fix->visual = (ivideo->video_bpp == 8) ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR;
fix->xpanstep = 1;
fix->ypanstep = (ivideo->sisfb_ypan) ? 1 : 0;
fix->ywrapstep = 0;
fix->line_length = ivideo->video_linelength;
fix->mmio_start = ivideo->mmio_base;
fix->mmio_len = ivideo->mmio_size;
if(ivideo->sisvga_engine == SIS_300_VGA) {
fix->accel = FB_ACCEL_SIS_GLAMOUR;
} else if((ivideo->chip == SIS_330) ||
(ivideo->chip == SIS_760) ||
(ivideo->chip == SIS_761)) {
fix->accel = FB_ACCEL_SIS_XABRE;
} else if(ivideo->chip == XGI_20) {
fix->accel = FB_ACCEL_XGI_VOLARI_Z;
} else if(ivideo->chip >= XGI_40) {
fix->accel = FB_ACCEL_XGI_VOLARI_V;
} else {
fix->accel = FB_ACCEL_SIS_GLAMOUR_2;
}
return 0;
}
/* ---------------- fb_ops structures ----------------- */
static struct fb_ops sisfb_ops = {
.owner = THIS_MODULE,
.fb_open = sisfb_open,
.fb_release = sisfb_release,
.fb_check_var = sisfb_check_var,
.fb_set_par = sisfb_set_par,
.fb_setcolreg = sisfb_setcolreg,
.fb_pan_display = sisfb_pan_display,
.fb_blank = sisfb_blank,
.fb_fillrect = fbcon_sis_fillrect,
.fb_copyarea = fbcon_sis_copyarea,
.fb_imageblit = cfb_imageblit,
.fb_sync = fbcon_sis_sync,
#ifdef SIS_NEW_CONFIG_COMPAT
.fb_compat_ioctl= sisfb_ioctl,
#endif
.fb_ioctl = sisfb_ioctl
};
/* ---------------- Chip generation dependent routines ---------------- */
static struct pci_dev *sisfb_get_northbridge(int basechipid)
{
struct pci_dev *pdev = NULL;
int nbridgenum, nbridgeidx, i;
static const unsigned short nbridgeids[] = {
PCI_DEVICE_ID_SI_540, /* for SiS 540 VGA */
PCI_DEVICE_ID_SI_630, /* for SiS 630/730 VGA */
PCI_DEVICE_ID_SI_730,
PCI_DEVICE_ID_SI_550, /* for SiS 550 VGA */
PCI_DEVICE_ID_SI_650, /* for SiS 650/651/740 VGA */
PCI_DEVICE_ID_SI_651,
PCI_DEVICE_ID_SI_740,
PCI_DEVICE_ID_SI_661, /* for SiS 661/741/660/760/761 VGA */
PCI_DEVICE_ID_SI_741,
PCI_DEVICE_ID_SI_660,
PCI_DEVICE_ID_SI_760,
PCI_DEVICE_ID_SI_761
};
switch(basechipid) {
#ifdef CONFIG_FB_SIS_300
case SIS_540: nbridgeidx = 0; nbridgenum = 1; break;
case SIS_630: nbridgeidx = 1; nbridgenum = 2; break;
#endif
#ifdef CONFIG_FB_SIS_315
case SIS_550: nbridgeidx = 3; nbridgenum = 1; break;
case SIS_650: nbridgeidx = 4; nbridgenum = 3; break;
case SIS_660: nbridgeidx = 7; nbridgenum = 5; break;
#endif
default: return NULL;
}
for(i = 0; i < nbridgenum; i++) {
if((pdev = pci_get_device(PCI_VENDOR_ID_SI,
nbridgeids[nbridgeidx+i], NULL)))
break;
}
return pdev;
}
static int sisfb_get_dram_size(struct sis_video_info *ivideo)
{
#if defined(CONFIG_FB_SIS_300) || defined(CONFIG_FB_SIS_315)
u8 reg;
#endif
ivideo->video_size = 0;
ivideo->UMAsize = ivideo->LFBsize = 0;
switch(ivideo->chip) {
#ifdef CONFIG_FB_SIS_300
case SIS_300:
reg = SiS_GetReg(SISSR, 0x14);
ivideo->video_size = ((reg & 0x3F) + 1) << 20;
break;
case SIS_540:
case SIS_630:
case SIS_730:
if(!ivideo->nbridge)
return -1;
pci_read_config_byte(ivideo->nbridge, 0x63, &reg);
ivideo->video_size = 1 << (((reg & 0x70) >> 4) + 21);
break;
#endif
#ifdef CONFIG_FB_SIS_315
case SIS_315H:
case SIS_315PRO:
case SIS_315:
reg = SiS_GetReg(SISSR, 0x14);
ivideo->video_size = (1 << ((reg & 0xf0) >> 4)) << 20;
switch((reg >> 2) & 0x03) {
case 0x01:
case 0x03:
ivideo->video_size <<= 1;
break;
case 0x02:
ivideo->video_size += (ivideo->video_size/2);
}
break;
case SIS_330:
reg = SiS_GetReg(SISSR, 0x14);
ivideo->video_size = (1 << ((reg & 0xf0) >> 4)) << 20;
if(reg & 0x0c) ivideo->video_size <<= 1;
break;
case SIS_550:
case SIS_650:
case SIS_740:
reg = SiS_GetReg(SISSR, 0x14);
ivideo->video_size = (((reg & 0x3f) + 1) << 2) << 20;
break;
case SIS_661:
case SIS_741:
reg = SiS_GetReg(SISCR, 0x79);
ivideo->video_size = (1 << ((reg & 0xf0) >> 4)) << 20;
break;
case SIS_660:
case SIS_760:
case SIS_761:
reg = SiS_GetReg(SISCR, 0x79);
reg = (reg & 0xf0) >> 4;
if(reg) {
ivideo->video_size = (1 << reg) << 20;
ivideo->UMAsize = ivideo->video_size;
}
reg = SiS_GetReg(SISCR, 0x78);
reg &= 0x30;
if(reg) {
if(reg == 0x10) {
ivideo->LFBsize = (32 << 20);
} else {
ivideo->LFBsize = (64 << 20);
}
ivideo->video_size += ivideo->LFBsize;
}
break;
case SIS_340:
case XGI_20:
case XGI_40:
reg = SiS_GetReg(SISSR, 0x14);
ivideo->video_size = (1 << ((reg & 0xf0) >> 4)) << 20;
if(ivideo->chip != XGI_20) {
reg = (reg & 0x0c) >> 2;
if(ivideo->revision_id == 2) {
if(reg & 0x01) reg = 0x02;
else reg = 0x00;
}
if(reg == 0x02) ivideo->video_size <<= 1;
else if(reg == 0x03) ivideo->video_size <<= 2;
}
break;
#endif
default:
return -1;
}
return 0;
}
/* -------------- video bridge device detection --------------- */
static void sisfb_detect_VB_connect(struct sis_video_info *ivideo)
{
u8 cr32, temp;
/* No CRT2 on XGI Z7 */
if(ivideo->chip == XGI_20) {
ivideo->sisfb_crt1off = 0;
return;
}
#ifdef CONFIG_FB_SIS_300
if(ivideo->sisvga_engine == SIS_300_VGA) {
temp = SiS_GetReg(SISSR, 0x17);
if((temp & 0x0F) && (ivideo->chip != SIS_300)) {
/* PAL/NTSC is stored on SR16 on such machines */
if(!(ivideo->vbflags & (TV_PAL | TV_NTSC | TV_PALM | TV_PALN))) {
temp = SiS_GetReg(SISSR, 0x16);
if(temp & 0x20)
ivideo->vbflags |= TV_PAL;
else
ivideo->vbflags |= TV_NTSC;
}
}
}
#endif
cr32 = SiS_GetReg(SISCR, 0x32);
if(cr32 & SIS_CRT1) {
ivideo->sisfb_crt1off = 0;
} else {
ivideo->sisfb_crt1off = (cr32 & 0xDF) ? 1 : 0;
}
ivideo->vbflags &= ~(CRT2_TV | CRT2_LCD | CRT2_VGA);
if(cr32 & SIS_VB_TV) ivideo->vbflags |= CRT2_TV;
if(cr32 & SIS_VB_LCD) ivideo->vbflags |= CRT2_LCD;
if(cr32 & SIS_VB_CRT2) ivideo->vbflags |= CRT2_VGA;
/* Check given parms for hardware compatibility.
* (Cannot do this in the search_xx routines since we don't
* know what hardware we are running on then)
*/
if(ivideo->chip != SIS_550) {
ivideo->sisfb_dstn = ivideo->sisfb_fstn = 0;
}
if(ivideo->sisfb_tvplug != -1) {
if( (ivideo->sisvga_engine != SIS_315_VGA) ||
(!(ivideo->vbflags2 & VB2_SISYPBPRBRIDGE)) ) {
if(ivideo->sisfb_tvplug & TV_YPBPR) {
ivideo->sisfb_tvplug = -1;
printk(KERN_ERR "sisfb: YPbPr not supported\n");
}
}
}
if(ivideo->sisfb_tvplug != -1) {
if( (ivideo->sisvga_engine != SIS_315_VGA) ||
(!(ivideo->vbflags2 & VB2_SISHIVISIONBRIDGE)) ) {
if(ivideo->sisfb_tvplug & TV_HIVISION) {
ivideo->sisfb_tvplug = -1;
printk(KERN_ERR "sisfb: HiVision not supported\n");
}
}
}
if(ivideo->sisfb_tvstd != -1) {
if( (!(ivideo->vbflags2 & VB2_SISBRIDGE)) &&
(!((ivideo->sisvga_engine == SIS_315_VGA) &&
(ivideo->vbflags2 & VB2_CHRONTEL))) ) {
if(ivideo->sisfb_tvstd & (TV_PALM | TV_PALN | TV_NTSCJ)) {
ivideo->sisfb_tvstd = -1;
printk(KERN_ERR "sisfb: PALM/PALN/NTSCJ not supported\n");
}
}
}
/* Detect/set TV plug & type */
if(ivideo->sisfb_tvplug != -1) {
ivideo->vbflags |= ivideo->sisfb_tvplug;
} else {
if(cr32 & SIS_VB_YPBPR) ivideo->vbflags |= (TV_YPBPR|TV_YPBPR525I); /* default: 480i */
else if(cr32 & SIS_VB_HIVISION) ivideo->vbflags |= TV_HIVISION;
else if(cr32 & SIS_VB_SCART) ivideo->vbflags |= TV_SCART;
else {
if(cr32 & SIS_VB_SVIDEO) ivideo->vbflags |= TV_SVIDEO;
if(cr32 & SIS_VB_COMPOSITE) ivideo->vbflags |= TV_AVIDEO;
}
}
if(!(ivideo->vbflags & (TV_YPBPR | TV_HIVISION))) {
if(ivideo->sisfb_tvstd != -1) {
ivideo->vbflags &= ~(TV_NTSC | TV_PAL | TV_PALM | TV_PALN | TV_NTSCJ);
ivideo->vbflags |= ivideo->sisfb_tvstd;
}
if(ivideo->vbflags & TV_SCART) {
ivideo->vbflags &= ~(TV_NTSC | TV_PALM | TV_PALN | TV_NTSCJ);
ivideo->vbflags |= TV_PAL;
}
if(!(ivideo->vbflags & (TV_PAL | TV_NTSC | TV_PALM | TV_PALN | TV_NTSCJ))) {
if(ivideo->sisvga_engine == SIS_300_VGA) {
temp = SiS_GetReg(SISSR, 0x38);
if(temp & 0x01) ivideo->vbflags |= TV_PAL;
else ivideo->vbflags |= TV_NTSC;
} else if((ivideo->chip <= SIS_315PRO) || (ivideo->chip >= SIS_330)) {
temp = SiS_GetReg(SISSR, 0x38);
if(temp & 0x01) ivideo->vbflags |= TV_PAL;
else ivideo->vbflags |= TV_NTSC;
} else {
temp = SiS_GetReg(SISCR, 0x79);
if(temp & 0x20) ivideo->vbflags |= TV_PAL;
else ivideo->vbflags |= TV_NTSC;
}
}
}
/* Copy forceCRT1 option to CRT1off if option is given */
if(ivideo->sisfb_forcecrt1 != -1) {
ivideo->sisfb_crt1off = (ivideo->sisfb_forcecrt1) ? 0 : 1;
}
}
/* ------------------ Sensing routines ------------------ */
static bool sisfb_test_DDC1(struct sis_video_info *ivideo)
{
unsigned short old;
int count = 48;
old = SiS_ReadDDC1Bit(&ivideo->SiS_Pr);
do {
if(old != SiS_ReadDDC1Bit(&ivideo->SiS_Pr)) break;
} while(count--);
return (count != -1);
}
static void sisfb_sense_crt1(struct sis_video_info *ivideo)
{
bool mustwait = false;
u8 sr1F, cr17;
#ifdef CONFIG_FB_SIS_315
u8 cr63=0;
#endif
u16 temp = 0xffff;
int i;
sr1F = SiS_GetReg(SISSR, 0x1F);
SiS_SetRegOR(SISSR, 0x1F, 0x04);
SiS_SetRegAND(SISSR, 0x1F, 0x3F);
if(sr1F & 0xc0) mustwait = true;
#ifdef CONFIG_FB_SIS_315
if(ivideo->sisvga_engine == SIS_315_VGA) {
cr63 = SiS_GetReg(SISCR, ivideo->SiS_Pr.SiS_MyCR63);
cr63 &= 0x40;
SiS_SetRegAND(SISCR, ivideo->SiS_Pr.SiS_MyCR63, 0xBF);
}
#endif
cr17 = SiS_GetReg(SISCR, 0x17);
cr17 &= 0x80;
if(!cr17) {
SiS_SetRegOR(SISCR, 0x17, 0x80);
mustwait = true;
SiS_SetReg(SISSR, 0x00, 0x01);
SiS_SetReg(SISSR, 0x00, 0x03);
}
if(mustwait) {
for(i=0; i < 10; i++) sisfbwaitretracecrt1(ivideo);
}
#ifdef CONFIG_FB_SIS_315
if(ivideo->chip >= SIS_330) {
SiS_SetRegAND(SISCR, 0x32, ~0x20);
if(ivideo->chip >= SIS_340) {
SiS_SetReg(SISCR, 0x57, 0x4a);
} else {
SiS_SetReg(SISCR, 0x57, 0x5f);
}
SiS_SetRegOR(SISCR, 0x53, 0x02);
while ((SiS_GetRegByte(SISINPSTAT)) & 0x01) break;
while (!((SiS_GetRegByte(SISINPSTAT)) & 0x01)) break;
if ((SiS_GetRegByte(SISMISCW)) & 0x10) temp = 1;
SiS_SetRegAND(SISCR, 0x53, 0xfd);
SiS_SetRegAND(SISCR, 0x57, 0x00);
}
#endif
if(temp == 0xffff) {
i = 3;
do {
temp = SiS_HandleDDC(&ivideo->SiS_Pr, ivideo->vbflags,
ivideo->sisvga_engine, 0, 0, NULL, ivideo->vbflags2);
} while(((temp == 0) || (temp == 0xffff)) && i--);
if((temp == 0) || (temp == 0xffff)) {
if(sisfb_test_DDC1(ivideo)) temp = 1;
}
}
if((temp) && (temp != 0xffff)) {
SiS_SetRegOR(SISCR, 0x32, 0x20);
}
#ifdef CONFIG_FB_SIS_315
if(ivideo->sisvga_engine == SIS_315_VGA) {
SiS_SetRegANDOR(SISCR, ivideo->SiS_Pr.SiS_MyCR63, 0xBF, cr63);
}
#endif
SiS_SetRegANDOR(SISCR, 0x17, 0x7F, cr17);
SiS_SetReg(SISSR, 0x1F, sr1F);
}
/* Determine and detect attached devices on SiS30x */
static void SiS_SenseLCD(struct sis_video_info *ivideo)
{
unsigned char buffer[256];
unsigned short temp, realcrtno, i;
u8 reg, cr37 = 0, paneltype = 0;
u16 xres, yres;
ivideo->SiS_Pr.PanelSelfDetected = false;
/* LCD detection only for TMDS bridges */
if(!(ivideo->vbflags2 & VB2_SISTMDSBRIDGE))
return;
if(ivideo->vbflags2 & VB2_30xBDH)
return;
/* If LCD already set up by BIOS, skip it */
reg = SiS_GetReg(SISCR, 0x32);
if(reg & 0x08)
return;
realcrtno = 1;
if(ivideo->SiS_Pr.DDCPortMixup)
realcrtno = 0;
/* Check DDC capabilities */
temp = SiS_HandleDDC(&ivideo->SiS_Pr, ivideo->vbflags, ivideo->sisvga_engine,
realcrtno, 0, &buffer[0], ivideo->vbflags2);
if((!temp) || (temp == 0xffff) || (!(temp & 0x02)))
return;
/* Read DDC data */
i = 3; /* Number of retrys */
do {
temp = SiS_HandleDDC(&ivideo->SiS_Pr, ivideo->vbflags,
ivideo->sisvga_engine, realcrtno, 1,
&buffer[0], ivideo->vbflags2);
} while((temp) && i--);
if(temp)
return;
/* No digital device */
if(!(buffer[0x14] & 0x80))
return;
/* First detailed timing preferred timing? */
if(!(buffer[0x18] & 0x02))
return;
xres = buffer[0x38] | ((buffer[0x3a] & 0xf0) << 4);
yres = buffer[0x3b] | ((buffer[0x3d] & 0xf0) << 4);
switch(xres) {
case 1024:
if(yres == 768)
paneltype = 0x02;
break;
case 1280:
if(yres == 1024)
paneltype = 0x03;
break;
case 1600:
if((yres == 1200) && (ivideo->vbflags2 & VB2_30xC))
paneltype = 0x0b;
break;
}
if(!paneltype)
return;
if(buffer[0x23])
cr37 |= 0x10;
if((buffer[0x47] & 0x18) == 0x18)
cr37 |= ((((buffer[0x47] & 0x06) ^ 0x06) << 5) | 0x20);
else
cr37 |= 0xc0;
SiS_SetReg(SISCR, 0x36, paneltype);
cr37 &= 0xf1;
SiS_SetRegANDOR(SISCR, 0x37, 0x0c, cr37);
SiS_SetRegOR(SISCR, 0x32, 0x08);
ivideo->SiS_Pr.PanelSelfDetected = true;
}
static int SISDoSense(struct sis_video_info *ivideo, u16 type, u16 test)
{
int temp, mytest, result, i, j;
for(j = 0; j < 10; j++) {
result = 0;
for(i = 0; i < 3; i++) {
mytest = test;
SiS_SetReg(SISPART4, 0x11, (type & 0x00ff));
temp = (type >> 8) | (mytest & 0x00ff);
SiS_SetRegANDOR(SISPART4, 0x10, 0xe0, temp);
SiS_DDC2Delay(&ivideo->SiS_Pr, 0x1500);
mytest >>= 8;
mytest &= 0x7f;
temp = SiS_GetReg(SISPART4, 0x03);
temp ^= 0x0e;
temp &= mytest;
if(temp == mytest) result++;
#if 1
SiS_SetReg(SISPART4, 0x11, 0x00);
SiS_SetRegAND(SISPART4, 0x10, 0xe0);
SiS_DDC2Delay(&ivideo->SiS_Pr, 0x1000);
#endif
}
if((result == 0) || (result >= 2)) break;
}
return result;
}
static void SiS_Sense30x(struct sis_video_info *ivideo)
{
u8 backupP4_0d,backupP2_00,backupP2_4d,backupSR_1e,biosflag=0;
u16 svhs=0, svhs_c=0;
u16 cvbs=0, cvbs_c=0;
u16 vga2=0, vga2_c=0;
int myflag, result;
char stdstr[] = "sisfb: Detected";
char tvstr[] = "TV connected to";
if(ivideo->vbflags2 & VB2_301) {
svhs = 0x00b9; cvbs = 0x00b3; vga2 = 0x00d1;
myflag = SiS_GetReg(SISPART4, 0x01);
if(myflag & 0x04) {
svhs = 0x00dd; cvbs = 0x00ee; vga2 = 0x00fd;
}
} else if(ivideo->vbflags2 & (VB2_301B | VB2_302B)) {
svhs = 0x016b; cvbs = 0x0174; vga2 = 0x0190;
} else if(ivideo->vbflags2 & (VB2_301LV | VB2_302LV)) {
svhs = 0x0200; cvbs = 0x0100;
} else if(ivideo->vbflags2 & (VB2_301C | VB2_302ELV | VB2_307T | VB2_307LV)) {
svhs = 0x016b; cvbs = 0x0110; vga2 = 0x0190;
} else
return;
vga2_c = 0x0e08; svhs_c = 0x0404; cvbs_c = 0x0804;
if(ivideo->vbflags & (VB2_301LV|VB2_302LV|VB2_302ELV|VB2_307LV)) {
svhs_c = 0x0408; cvbs_c = 0x0808;
}
biosflag = 2;
if(ivideo->haveXGIROM) {
biosflag = ivideo->bios_abase[0x58] & 0x03;
} else if(ivideo->newrom) {
if(ivideo->bios_abase[0x5d] & 0x04) biosflag |= 0x01;
} else if(ivideo->sisvga_engine == SIS_300_VGA) {
if(ivideo->bios_abase) {
biosflag = ivideo->bios_abase[0xfe] & 0x03;
}
}
if(ivideo->chip == SIS_300) {
myflag = SiS_GetReg(SISSR, 0x3b);
if(!(myflag & 0x01)) vga2 = vga2_c = 0;
}
if(!(ivideo->vbflags2 & VB2_SISVGA2BRIDGE)) {
vga2 = vga2_c = 0;
}
backupSR_1e = SiS_GetReg(SISSR, 0x1e);
SiS_SetRegOR(SISSR, 0x1e, 0x20);
backupP4_0d = SiS_GetReg(SISPART4, 0x0d);
if(ivideo->vbflags2 & VB2_30xC) {
SiS_SetRegANDOR(SISPART4, 0x0d, ~0x07, 0x01);
} else {
SiS_SetRegOR(SISPART4, 0x0d, 0x04);
}
SiS_DDC2Delay(&ivideo->SiS_Pr, 0x2000);
backupP2_00 = SiS_GetReg(SISPART2, 0x00);
SiS_SetReg(SISPART2, 0x00, ((backupP2_00 | 0x1c) & 0xfc));
backupP2_4d = SiS_GetReg(SISPART2, 0x4d);
if(ivideo->vbflags2 & VB2_SISYPBPRBRIDGE) {
SiS_SetReg(SISPART2, 0x4d, (backupP2_4d & ~0x10));
}
if(!(ivideo->vbflags2 & VB2_30xCLV)) {
SISDoSense(ivideo, 0, 0);
}
SiS_SetRegAND(SISCR, 0x32, ~0x14);
if(vga2_c || vga2) {
if(SISDoSense(ivideo, vga2, vga2_c)) {
if(biosflag & 0x01) {
printk(KERN_INFO "%s %s SCART output\n", stdstr, tvstr);
SiS_SetRegOR(SISCR, 0x32, 0x04);
} else {
printk(KERN_INFO "%s secondary VGA connection\n", stdstr);
SiS_SetRegOR(SISCR, 0x32, 0x10);
}
}
}
SiS_SetRegAND(SISCR, 0x32, 0x3f);
if(ivideo->vbflags2 & VB2_30xCLV) {
SiS_SetRegOR(SISPART4, 0x0d, 0x04);
}
if((ivideo->sisvga_engine == SIS_315_VGA) && (ivideo->vbflags2 & VB2_SISYPBPRBRIDGE)) {
SiS_SetReg(SISPART2, 0x4d, (backupP2_4d | 0x10));
SiS_DDC2Delay(&ivideo->SiS_Pr, 0x2000);
if((result = SISDoSense(ivideo, svhs, 0x0604))) {
if((result = SISDoSense(ivideo, cvbs, 0x0804))) {
printk(KERN_INFO "%s %s YPbPr component output\n", stdstr, tvstr);
SiS_SetRegOR(SISCR, 0x32, 0x80);
}
}
SiS_SetReg(SISPART2, 0x4d, backupP2_4d);
}
SiS_SetRegAND(SISCR, 0x32, ~0x03);
if(!(ivideo->vbflags & TV_YPBPR)) {
if((result = SISDoSense(ivideo, svhs, svhs_c))) {
printk(