/* $XConsortium: s3ELSA.c,v 1.2 94/11/21 22:50:12 kaleb Exp $ */
/* $XFree86: xc/programs/Xserver/hw/xfree86/accel/s3/s3ELSA.c,v 3.6 1995/01/28 15:55:42 dawes Exp $ */
/* 
 * s3ELSA.c 
 * 
 * compile standalone program with   
 *
      rm -f s3ELSA.o
      make DEFINES=-DELSA_MAIN "LDLIBS=../../os-support/libxf86_os.a" s3ELSA 
      rm -f s3ELSA.o
 * 
 * 
 */

#ifdef ELSA_MAIN
#include <stdio.h>
#ifdef linux
#include <unistd.h>
#endif
#ifndef SVR4
#include <getopt.h>
#endif
#include <malloc.h>
#endif

#include "s3.h"
#include "s3ELSA.h"
#include "xf86_OSlib.h"

#define BIOS_BSIZE 512
#define BIOS_BASE  0xc0000


elsa_board_types_t elsa_board_types[] = {
   ELSA_WINNER_1000,	"ELSA Winner 1000",	0,
   ELSA_WINNER_1000VL,	"ELSA Winner 1000VL",	0,
   ELSA_WINNER_1000PCI,	"ELSA Winner 1000PCI",	0,
   ELSA_WINNER_1000ISA,	"ELSA Winner 1000ISA",	0,
   ELSA_WINNER_2000,	"ELSA Winner 2000",	0,
   ELSA_WINNER_2000VL,	"ELSA Winner 2000VL",	0,
   ELSA_WINNER_2000PCI,	"ELSA Winner 2000PCI",	0,
   ELSA_WINNER_1000PRO,	"ELSA Winner 1000PRO",	0,
   ELSA_WINNER_2000PRO,	"ELSA Winner 2000PRO",	0,
   0, 0, 0
};


static __inline__ void shift_out(int b) {
   int i,j; 
   outb(0x3d4,0x5c); i = (inb(0x3d5) & 0xaf) | ((b)?0x10:0x00); 
   j = i;  
   outb(0x3d5,j); outb(0x3d5,j); outb(0x3d5,j); outb(0x3d5,j); 
   j = i | 0x40;  
   outb(0x3d5,j); outb(0x3d5,j); outb(0x3d5,j); outb(0x3d5,j); 
} 

static __inline__ int shift_in() 
{ 
   int i,j; 
   outb(0x3d4,0x5c); i = inb(0x3d5) & 0xaf; 
   j = i;  
   outb(0x3d5,j); outb(0x3d5,j); outb(0x3d5,j); outb(0x3d5,j); 
   j = i | 0x40;  
   outb(0x3d5,j); outb(0x3d5,j); outb(0x3d5,j); outb(0x3d5,j); 
   return (inb(0x3c2) & 0x10) != 0;
} 

static int read_eeprom_byte(int r)
{
   int i,j;
   
   outb(0x3d4,0x5c); i = inb(0x3d5) |  0x20; outb(0x3d5,i);
   outb(0x3d5,i); outb(0x3d5,i); outb(0x3d5,i);

   shift_out(1);
   shift_out(1);
   shift_out(0);
   
   for(i=5; i>=0; i--)
      shift_out((r>>i) & 1);
   
#if 0
   shift_out(0);
#endif

   j=0;
   for(i=0; i<16; i++) 
      j = (j<<1) | shift_in();

   outb(0x3d4,0x5c); i = inb(0x3d5) & ~0x20; outb(0x3d5,i);
   outb(0x3d5,i); outb(0x3d5,i); outb(0x3d5,i);

   return j;
}


static int read_eeprom_data(unsigned short **pdata)
{
   int i;
   int sr01, cr45, cr50, cr55, cr5c;
   int ndata=6;
   unsigned short *data;
   elsa_eeprom_data_t *eedata;

#ifdef ELSA_MAIN
#ifdef linux
   iopl(3);
#endif
#endif

   outb(0x3d4,0x38); outb(0x3d5,0x48);
   outb(0x3d4,0x39); outb(0x3d5,0xa5);

   outb(0x3c4,0x01); sr01 = inb(0x3c5);
   outb(0x3d4,0x45); cr45 = inb(0x3d5);
   outb(0x3d4,0x50); cr50 = inb(0x3d5);
   outb(0x3d4,0x55); cr55 = inb(0x3d5);
   outb(0x3d4,0x5c); cr5c = inb(0x3d5);


   outb(0x3c4,0x01); i = inb(0x3c5) |  0x20; outb(0x3c5,i);
   outb(0x3d4,0x45); i = inb(0x3d5) & ~0x20; outb(0x3d5,i);
   outb(0x3d4,0x50); i = inb(0x3d5) & ~0x04; outb(0x3d5,i);
   outb(0x3d4,0x55); i = inb(0x3d5) & ~0x20; outb(0x3d5,i);
   outb(0x3d4,0x5c); i = inb(0x3d5) |  0x80; outb(0x3d5,i);
   outb(0x3d5,i); outb(0x3d5,i); outb(0x3d5,i);

   for(i=0; i<64; i++)
      shift_out(0);

   data = (unsigned short*) malloc(ndata*sizeof(unsigned short));

   for (i=0; i<ndata; i++)
      data[i] = read_eeprom_byte(i);

   eedata = (elsa_eeprom_data_t *) data;

   if (eedata->wnr_type == ('S' | '3'<<8)) {
      if (eedata->eeprom_size > ndata) {
	 ndata = eedata->eeprom_size;
	 data = (unsigned short*) realloc(data, ndata*sizeof(unsigned short));
	 for (; i<ndata; i++)
	    data[i] = read_eeprom_byte(i);
      }
   }

   outb(0x3d4,0x5c); outb(0x3d5,cr5c);
   outb(0x3d4,0x55); outb(0x3d5,cr55);
   outb(0x3d4,0x50); outb(0x3d5,cr50);
   outb(0x3d4,0x45); outb(0x3d5,cr45);
   outb(0x3c4,0x01); outb(0x3c5,sr01);

#if 0
   outb(0x3d4,0x39); outb(0x3d5,0x00);
   outb(0x3d4,0x38); outb(0x3d5,0x00);
#endif

#ifdef ELSA_MAIN
#ifdef linux
   iopl(0);
#endif
#endif

   eedata = (elsa_eeprom_data_t *) data;
   if (eedata->wnr_type == ('S' | '3'<<8)) {
      *pdata = data;
   }
   else {
      free(data);
      ndata = -1;
   }
   return ndata;
}

static int calc_crc16(int ndata, unsigned short *data)
{
   int i,j,s,crc16;
   unsigned short d;
   
   crc16 = 0;
   for(i=1; i<ndata; i++) {
      d = data[i];
      for (j=0; j<16; j++) {
	 s = (crc16>>1) + (crc16>>14) + (crc16>>15) + d + 1;
	 crc16 = (crc16<<1) | (s&1);
	 d >>= 1;
      }
   }
   return crc16;
}

static int check_ELSA_bios(int BIOSbase)
{
   unsigned char bios[BIOS_BSIZE];
   char *match = " ELSA GmbH";
   int i,l;
   
   if (xf86ReadBIOS(BIOSbase, 0, bios, BIOS_BSIZE) != BIOS_BSIZE)
      return -1;

   if ((bios[0] != 0x55) || (bios[1] != 0xaa))
      return -2;

   l = strlen(match);
   for (i=0; i<BIOS_BSIZE-l; i++) 
      if (bios[i] == match[0] && !memcmp(&bios[i],match,l))
	 return 1;
   return 0;
}


#ifdef ELSA_MAIN

void ErrorF(char *s, ...)
{
}

void main()
{
   int i;
   int ndata;
   unsigned short *data;
   unsigned short crc16;
   elsa_eeprom_data_t *eedata;
   elsa_eeprom_timing_t *eetim;
   unsigned long serno;

   if (check_ELSA_bios(BIOS_BASE) <= 0) {
      printf("no ELSA Bios detected\n");
      exit(1);
   }

   ndata = read_eeprom_data(&data);
   if (ndata<0) {
      printf("no ELSA Winner card detected\n");
      exit(1);
   }

   crc16 = calc_crc16(ndata,data);
   eedata = (elsa_eeprom_data_t *) data;


   printf("EEPROM size %d bytes\n",ndata);
   if (eedata->crc16 == crc16)
      printf("CRC ok (%04x)\n",crc16);
   else
      printf("CRC not ok (%04x != %04x)\n",eedata->crc16,crc16);
   printf("Chip type %s\n"
	  ,(eedata->wnr_type == 'S' | '3'<<8) ? "S3" : "unknown");
   for (i=0; elsa_board_types[i].code; i++)
      if (elsa_board_types[i].code == eedata->board_code) break;
   if (elsa_board_types[i].code)
      printf("%s\n",elsa_board_types[i].name);
   else 
      printf("unknown ELSA board code %x, please report\n", eedata->board_code);

   serno = (eedata->serno_h<<16) | eedata->serno_l;
   printf("Ser.No. %c-%04ld.%03ld.%03ld\n",
	  (char)('A' + ((serno>>27) & 0x0f)),
	  ((serno>>17) & 0x3ff) | ((serno>>21) & 0x400),
	  (serno & 0x1ffff) / 1000,
	  (serno & 0x1ffff) % 1000);
   printf("max pixel clock  %7.3f MHz\n"
	  ,(eedata->max_pixclock,eedata->max_pixclock*4)/1000.0);
   printf("max memory clock %7.3f MHz\n"
	  ,(eedata->max_memclock,eedata->max_memclock*4)/1000.0);
   printf("monitor size %d %d\n",eedata->xsize,eedata->ysize);
   printf("software version %d\n",eedata->sw_vers);

   for (i= 26; i<ndata-9; i+=9) {
      eetim  = (elsa_eeprom_timing_t *) (data + i);
#if 0
      printf("\ntiming  %d:\n",i);
      printf("\tbpp     %d\n",ELSA_TIM_bpp(*eetim));
      printf("\txres    %d\n",ELSA_TIM_xres(*eetim));
      printf("\tyres    %d\n",ELSA_TIM_yres(*eetim));
      printf("\tpixfrq  %1.3f MHz\n",(ELSA_TIM_pixfrq4(*eetim)*4)/1000.0);

      printf("\tflags   %d\n",ELSA_TIM_flags(*eetim));
      printf("\thtot    %d\n",ELSA_TIM_htot(*eetim));
      printf("\thfp     %d\n",ELSA_TIM_hfp(*eetim));
      printf("\thsw     %d\n",ELSA_TIM_hsw(*eetim));
      printf("\tvtot    %d\n",ELSA_TIM_vtot(*eetim));
      printf("\tvfp     %d\n",ELSA_TIM_vfp(*eetim));
      printf("\tvsw     %d\n",ELSA_TIM_vsw(*eetim));
#endif
      if (ELSA_ET_VM_VALID(eetim))
	 printf("\"%dx%dx%d\" \t %7.3f   %4d %4d %4d %4d   %4d %4d %4d %4d\n"
	     ,ELSA_TIM_xres(*eetim),ELSA_TIM_yres(*eetim),ELSA_TIM_bpp(*eetim)
	     ,(ELSA_TIM_pixfrq4(*eetim)*4)/1000.0
	     ,ELSA_TIM_xres(*eetim)
	     ,ELSA_TIM_xres(*eetim)+ELSA_TIM_hfp(*eetim)
	     ,ELSA_TIM_xres(*eetim)+ELSA_TIM_hfp(*eetim)+ELSA_TIM_hsw(*eetim)
	     ,ELSA_TIM_htot(*eetim)
	     ,ELSA_TIM_yres(*eetim)
	     ,ELSA_TIM_yres(*eetim)+ELSA_TIM_vfp(*eetim)
	     ,ELSA_TIM_yres(*eetim)+ELSA_TIM_vfp(*eetim)+ELSA_TIM_vsw(*eetim)
	     ,ELSA_TIM_vtot(*eetim)
	     );
   }   
   free(data);
}

#else
int s3DetectELSA(int BIOSbase, char **pcard, char **pserno, 
		 int *max_pix_clock, int *max_mem_clock)
{
   int i;
   int ndata;
   unsigned short *data;
   unsigned short crc16;
   elsa_eeprom_data_t *eedata;
   elsa_eeprom_timing_t *eetim;
   unsigned long serno;

   if (check_ELSA_bios(BIOSbase>0 ? BIOSbase : BIOS_BASE) <= 0) {
      return -1;
   }

   ndata = read_eeprom_data(&data);
   if (ndata<0) 
      return -2;

   crc16 = calc_crc16(ndata,data);
   eedata = (elsa_eeprom_data_t *) data;

   if (eedata->crc16 != crc16) {
      free(data);
      return -3;
   }

   if (eedata->wnr_type != ('S' | '3'<<8)) {
      free(data);
      return -4;
   }

   for (i=0; elsa_board_types[i].code; i++)
      if (elsa_board_types[i].code == eedata->board_code) break;

   if (pcard) {
      *pcard  = (char*) malloc(80);
      if (elsa_board_types[i].code)
	 sprintf(*pcard,"%s detected",elsa_board_types[i].name);
      else 
	 sprintf(*pcard,"unknown ELSA Winner board code %04x detected, please report\n"
		 , eedata->board_code);
   }

   if (pserno) {
      *pserno = (char*) malloc(20);
      serno = (eedata->serno_h<<16) | eedata->serno_l;
      sprintf(*pserno,"%c-%04ld.%03ld.%03ld",
	      (char)('A' + ((serno>>27) & 0x0f)),
	      ((serno>>17) & 0x3ff) | ((serno>>21) & 0x400),
	      (serno & 0x1ffff) / 1000,
	      (serno & 0x1ffff) % 1000);
   }

   if (max_pix_clock) 
      *max_pix_clock = eedata->max_pixclock * 4;
   if (max_mem_clock) 
      *max_mem_clock = eedata->max_memclock * 4;
   
   i = eedata->board_code;
   free(data);
   return i;
}

#endif
