
/*
 * tokenset v0.11 for Intel TokenExpress ISA Adapters.
 *
 * This should do everything that TOKENSET.EXE (from Intel) 
 * does under DOS.  Its got some bugs left, I know.  Fix them 
 * if you like.  Changes may not always take and the DMA might
 * get interpretted improperly.  Shrug.  I don't really understand
 * the chip to begin with.  I'm only guessing really.  
 *
 * If called with no parameters, it simply reads out the current
 * settings of the card and interprets them.  You can also
 * give options to change them (hopefully).  
 *
 * See the -h option.
 *
 * Compile with "gcc -Wall -Wstrict-prototypes -O2 -o tokenset tokenset.c"
 *
 * Distributed under the GNU Public License (www.fsf.org).
 *
 * Written by Adam Fritzler (mid@auk.cx), maintainer of the Linux tms380tr
 * device driver, which should also work with any card this works
 * with (assuming this works at all).
 *
 * 9Dec1999 -- !!!WARNING!!! This version MAY zero the eeprom on your board!
 * 
 */
#include <stdio.h>
#include <asm/io.h>
#include <unistd.h>
#include <stdlib.h>
#include <getopt.h>

static char version[]="tokenset v0.11 for Intel TokenExpress ISA adapters, (c)1999 Adam Fritzler (mid@auk.cx)";

/* Token ring adapter I/O addresses for normal mode. */
#define SIFDAT                  0L      /* SIF/DMA data. */
#define SIFINC                  2L      /* IO Word data with auto increment. */
#define SIFINH                  3L      /* IO Byte data with auto increment. */
#define SIFADR                  4L      /* SIF/DMA Address. */
#define SIFCMD                  6L      /* SIF Command. */
#define SIFSTS                  6L      /* SIF Status. */
#define SIFACL                  8L      /* SIF Adapter Control Register. */
#define SIFADD                  10L     /* SIF/DMA Address. */
#define SIFADX                  12L
#define DMALEN                  14L     /* SIF DMA length. */
#define POSREG                  16L     /* Adapter Program Option Select (POS)
                                         * Register: base IO address + 16 byte.
                                         */
#define POSREG_2                24L     /* only for TR4/16+ adapter
                                         * base IO address + 24 byte.
                                         */

int irqs[] = {9, 10, 11, 12, 15, 3, 5, 7, 0xff};
int dmas[] = {5, 6, 7, 0, 1, 3, -1, 0xff};
unsigned short ios[] = {0x0a20, 1, 0x0a24, 1, 0x0a50, 1, 0x0a54, 0xff};

#define IODEBUG1

/*
 * Debug wrappers for outb/inb.
 */
unsigned char inb_w(unsigned short addr)
{
#ifdef IODEBUG
  unsigned char val;
  val = inb(addr);
  printf("IODEBUG: 0x%03x -> %02x\n", addr, val); 
  return val;
#else
  return inb(addr);
#endif
}

void outb_w(unsigned char val, unsigned short addr)
{
#ifdef IODEBUG
  printf("IODEBUG: 0x%03x <- %02x\n", addr, val);
#endif
  outb(val, addr);
  return;
}

/*
 * This function tests if there's a TMS380 processor installed at the
 * given I/O address. Return negative if no adapter at IO addr.
 */
int tms380tr_probe(int ioaddr)
{
  unsigned char old, chk1, chk2;
  
  old = inb(ioaddr + SIFADR);     /* Get the old SIFADR value */
  
  chk1 = 0;       /* Begin with check value 0 */
  do {
    /* Write new SIFADR value */
    outb_w(chk1, ioaddr + SIFADR);
    
    /* Read, invert and write */
    chk2 = inb(ioaddr + SIFADD);
    chk2 ^= 0x0FE;
    outb(chk2, ioaddr + SIFADR);
    
    /* Read, invert and compare */
    chk2 = inb(ioaddr + SIFADD);
    chk2 ^= 0x0FE;
    if(chk1 != chk2)
      return (-1);    /* No adapter */
    
    chk1 -= 2;
  } while(chk1 != 0);     /* Repeat 128 times (all byte values) */
  
  /* Restore the SIFADR value */
  outb(old, ioaddr + SIFADR);
  
  return (1); /* card found */
}


unsigned short baseaddr;
unsigned short tmsaddr;

int wordstop(void)
{
  unsigned char chk;
  outb_w(0xa0, baseaddr+0x03);
  chk = inb_w(baseaddr+0x03);
  if (chk != 0xa0)
    {
      printf("ERROR: wordstop: did not get back a stop byte! (%02x != 0xa0)\n", chk);
      return -1;
    }
  return 0;
}

unsigned char setlines(unsigned char clk, unsigned char data)
{
  outb_w(0xb0 | (clk?2:0) | (data?1:0), baseaddr+0x03);
  return inb_w(baseaddr+0x03);
}

void write_zero(void)
{
  setlines(0, 0);
  setlines(1, 0);
  setlines(0, 0);
  return;
}

void write_one(void)
{
  setlines(0, 1);
  setlines(1, 1);
  setlines(0, 1);
}

/*
 * Write the rightmost ten bits of val. 
 */
void write_10bit(unsigned short val)
{
  int i;
  val <<= 6;
  for (i=0;i<10;i++)
    {
      if (val&0x8000)
	write_one();
      else
	write_zero();
      val <<= 1;
    }
  return;
}

unsigned char readbit(void)
{
  unsigned char chk1, chk2;
  outb_w(0xb0, baseaddr+0x03);
  chk1 = inb_w(baseaddr+0x03);
  chk2 = inb_w(baseaddr+0x03);

  if (chk1!=chk2)
    printf("ERROR: readbit: read two different values (%d vs %d)\n", chk1?1:0, chk2?1:0);
  
  setlines(1, (chk2&0x01)?1:0);

  return chk2&1;
}

unsigned int readword(void)
{
  unsigned short val = 0;
  int i;
  unsigned char curval = 0;
  
  for (i=0; i < 16; i++)
    {
      curval = readbit();
      //printf("read: %x\n", curval);
      if (i)
	val <<= 1;
      val |= curval;
    }
  return val;
}

unsigned short readword_byaddr(unsigned short addr)
{
  unsigned char par = 0;
  unsigned short word = 0;

  if (wordstop() == -1)
    return 0;  /* major problem ! */
  write_10bit(addr);
  par = readbit(); /* throaway */
  word = readword();
#ifdef IODEBUG
  printf("\n\nRead: parity: %01x, data: %04x\n", par, word);
#endif
  wordstop();
  return word;
}

/*
 * This does a simple probe to find the MB620228 config/glue chip.
 * Note that this has nothing to do with finding the actual
 * TMS380C16 except indirectly (the controller address depends
 * on the MB620228 address). The address is returned as both
 * the return value and in baseaddr. 
 *
 */
unsigned short configprobe(void)
{
  int i,j;
  unsigned char tmp,tmp2;
  unsigned short probelist[] = {
    0x0a20,  /* chip at 0xa30 */
    0x0a24,  /*         0xa40 */
    0x0a50,  /*         0xa60 */
    0x0a54,  /*         0xa70 */
    0}; 

  for (j = 0; probelist[j]!=0; j++)
    {
      baseaddr = probelist[j]; /* {get,set}reg() need this set */

      /*
       * Read the first value, take the lower nibble.  All subsequent
       * reads must end in the same nibble as that first read.
       *
       */
      for (i = 0; i < 5; i++)
	{
	  tmp = inb_w(baseaddr+0x02);

	  if (i==0)
	    tmp2 = tmp&0x0f;
	  
	  if (tmp2&0xf)
	    continue; /* gibberish */

	  if (!tmp&tmp2)
	    continue;  /* fail */
	}

      /* Initialization of card. (I think) */
      outb_w(0x06, baseaddr+0x02);
      tmp = inb_w(baseaddr+0x02);
      outb_w(tmp, baseaddr+0x02);
      
      return baseaddr;
    }
  return 0;
}

void readmac(unsigned char *mac)
{
  unsigned short tmp;
  int i,i2;
 
  /*
   * Since we can only read off the chip in words and MACs must
   * be stored in bytes, we seperate the sides at read time.
   * Also, they're given back in little-endian, so we do the
   * byte-swap while seperating.
   */
  for(i=0,i2=0;i<3;i++,i2+=2)
    {
      tmp = readword_byaddr(0x1bc+i);
      mac[i2] = tmp & 0xff;
      mac[i2+1] = tmp >> 8;
    }
  
  return;
}

/*
 * Determines TMS380 address based on MB620228 address.
 */
unsigned short gettmsaddr(unsigned short configaddr)
{
  switch (configaddr)
    {
    case 0x0a20: return 0x0a30;
    case 0x0a24: return 0x0a40;
    case 0x0a50: return 0x0a60;
    case 0x0a54: return 0x0a70;
    default: return 0;
    }
  return 0;
}

int main(int argc, char **argv)
{
  int i;
  unsigned char regs[8];
  char c;
  unsigned char newio = 0xff;
  unsigned char newdma = 0xff;
  unsigned char newirq = 0xff;
  unsigned char newspeed = 0xff;
  int changed = 0;
  unsigned char mac[6];

  while (1)
    {
      int option_index = 0;
      static struct option long_options[] = {
	{"io", 1, 0, 0},
	{"dma", 1, 0, 0},
	{"irq", 1, 0, 0},
	{"version", 0, 0, 0},
	{"help", 0, 0, 0},
	{"speed", 1, 0, 0},
	{0,0,0,0}
      };
      c = getopt_long(argc, argv, "hv", long_options, &option_index);
      if (c == -1)
	break;
      switch(c)
	{
	case 0:
	  switch(option_index)
	    {
	    case 0: /* io */
	      {
		if ((atoi(optarg) > 3) || atoi(optarg) < 0)
		  {
		    fprintf(stderr, "Invalid IO address specified\n");
		    return -1;
		  }
		newio = atoi(optarg);
	      }
	      break;
	    case 1: /* dma */
	      {
		int idx;
		for (idx = 0; dmas[idx] != 0xff; idx++)
		  {
		    if (dmas[idx]==atoi(optarg))
		      break;
		  }
		if (dmas[idx] == 0xff)
		  {
		    fprintf(stderr, "Invalid DMA specified\n");
		    return -1;
		  }
		newdma = idx;
	      }
	      break;
	    case 2: /* irq */
	      {
		int idx;
		for (idx = 0; irqs[idx] != 0xff; idx++)
		  {
		    if (irqs[idx]==atoi(optarg))
		      break;
		  }
		if (irqs[idx] == 0xff)
		  {
		    fprintf(stderr, "Invalid IRQ specified\n");
		    return -1;
		  }
		newirq = idx;
	      }
	      break;
	    case 3: /* version */
	      goto version;
	    case 4: /* help */
	      goto help;
	    case 5: /* speed */
	      if (atoi(optarg) == 4)
		newspeed = 0;
	      else if (atoi(optarg) == 16)
		newspeed = 1;
	      else
		{
		  fprintf(stderr, "Invalid speed specified -- only 4 and 16Mb/sec operation supported\n");
		  return -1;
		}
	      break;
	    }
	  break;
	version:
	case 'v':
	  printf("%s\n", version);
	  return 0;
	help:
	case 'h':
	  printf("%s\nIf called with no options, prints current settings.\n", version);
	  printf("!!!!WARNING: DO NOT RUN ON AN ACTIVE CARD! !!!!\n");
	  printf("Options:\n");
	  printf("\t--io\t\tSets I/O Address:\n");
	  printf("\t    \t\t\t=0 0x0A20 / 0x0A30\n");
	  printf("\t    \t\t\t=1 0x0A24 / 0x0A40\n");
	  printf("\t    \t\t\t=2 0x0A50 / 0x0A60\n");
	  printf("\t    \t\t\t=3 0x0A54 / 0x0A70\n");
	  printf("\t--dma\t\tSets DMA channel\n");
	  printf("\t--irq\t\tSets IRQ line\n");
	  printf("\t--speed\t\tSets ring speed (=4 or =16)\n");
	  printf("!!!!WARNING: DO NOT RUN ON AN ACTIVE CARD! !!!!\n");
	  printf("\n");
	  return 0;
	}
    }

  if (iopl(3)<0)
    {
      printf("Could not get permission.  Must be root or setuid.\n");
      return -1;
    }


  if ((baseaddr = configprobe()) == 0)
    {
      fprintf(stderr, "No cards found.\n");
      return -1;
    }
  else
    printf("Found card at 0x%03x.\n", baseaddr);
  
  readmac(mac);

  {
    int i;
    printf("MAC address: ");
    for (i=0;i<6;i++)
      printf("%02x ", mac[i]);
    printf("\n");
  }

  return 0;

#if 0  
  printf("Configuration register values: ");
  for (i = 0; i < 8; i++)
    printf("%02x ", regs[i] = getreg(i));
  printf("\n");

  if ((tmsaddr = gettmsaddr(baseaddr))==0)
    {
      fprintf(stderr, "Config chip located at invalid base address; probably a prober screwup (baseaddr=0x%03x).\n", baseaddr);
      return -1;
    }

  if (tms380tr_probe(tmsaddr)!=-1)
    printf("TMS380 chip found at 0x%04x\n", tmsaddr);
  else
    {
      fprintf(stderr, "TMS380 chip probe failed.\n");
      return -1;
    }

  if (newspeed != 0xff)
    {
      if ((newspeed == 0)&&(regs[0x03]&0x1))
	regs[0x03] ^= 0x01;
      changed++;
    }

  if (newirq != 0xff)
    {
      regs[0x02] &= 0xf8; /* kill current irq */
      regs[0x02] |= newirq; /* add in new one */
      changed++;
    }

  if (newdma != 0xff)
    {
      regs[0x02] &= 0x07; 
      regs[0x02] |= newdma<<3;
      changed++;
    }
  if (newio != 0xff)
    {
      regs[0x03] &= 0xf9;
      regs[0x03] |= newio<<1;
      changed++;
    }

  if (changed)
    {
      printf("Writing new values: ");
      for (i = 0; i < 8; i++)
	{
	  printf("%02x ", regs[i]);
	  setreg(i, regs[i]);
	}
      printf("\n");
    }

  
  if (changed)
    printf("\nNew Settings:\n");
  else
    printf("\nCurrent Settings:\n");

  /* IO address */
  printf("\tI/O:\n");
  printf("\t\tConfig: 0x%03x-0x%03x\n", baseaddr, baseaddr+4);
  printf("\t\tTMS380: 0x%03x-0x%03x\n", tmsaddr, tmsaddr+0x1f);

  /* IRQ */
  printf("\tIRQ: %d\n", irqs[regs[0x02]&0x7]);
  

  /* DMA */
  printf("\tDMA: %d %s\n", (regs[0x02]>>3)&0x1f, (dmas[(regs[0x02]>>3)&0x7]>0)?"(DMA mode)":"(PIO/pDMA mode)");

  /* Ring Speed */
  printf("\tRing Speed: %dMb/sec\n", (regs[0x03] & 0x01)?16:4);

  /* ROM address */
  printf("\tRPL ROM Address: (not implemented)\n");

  /* MAC address -- not sure how this goes on Intel cards yet */
  printf("\tRing Station Node Address: ");
#if 0
  tms380tr_read_addr();
  printf("\n");
#else
  printf("(not implemented)\n");
#endif

  printf("\n");
#endif

  return (0);
  
}

