/*
 * y701ini.c - version 1.0, March 1999
 *
 * Init a soundcard with Yamaha Y701 chip (OPL3-SA)
 *
 * (c) Ivan Popov <pin@math.chalmers.se> 1999
 *
 * Then you may use the card as two independent cards,
 * namely CS4231-based one (16 bit duplex),
 * and SoundBlaster-Pro (8 bit non-duplex, input not connected on my card).
 *
 * Suitable sound modules are
 * oss:  sb + ad1848 + opl3
 *       load sb first (if at all), otherwise it may crash the system (why?! :)
 * alsa: card-cs4231 + sb8
 *       card-cs4231 module wants different port address
 *       than you tell to y701ini (and oss ad1848 module),
 *       you have to add 4, and it's not a bug:
 *              y701ini wss_io=0x530 ...   ad1848 io=0x530 ...
 *       but    y701ini wss_io=0x530 ...   card-cs4231 snd_port=0x534 ...
 *                                                              ^^^^^
 * A hack (this program), but it works well. Reasonably self-documented :)
 *
 * Please mention me as the author,
 * otherwise do what you want with this program.
 *
 * For those who do not know (yet) - the language below is Esperanto.
 */
/*
 * Tiu cxi programo inicas sonkarton kun Yamaha-701 mikrocirkvito.
 *
 * La parametroj estas sekvaj:
 *
 * wss-adreso:      unu el 0x530,0xe80,0xf40,0x604,-
 * wss-interrompo:  unu el 7,9,10,11
 * wss-dma:         unu el 0,1,3,0+1,1+0,3+0
 * mpu-adreso:      unu el 0x330,0x332,0x334,0x300,-
 * mpu-interrompo:  unu el 5,7,9,10
 * sb-adreso:       unu el 0x220,0x240,-
 * sb-interrompo:   unu el 5,7,9,10,11
 * sb-dma:          unu el 0,1,3
 *
 * bedauxre pro la abunda uzo de la angla en tiu cxi programo...
 *
 * Ivan Popov, marto 1999
 */

#define IOFILE "/dev/port"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <unistd.h>

#include <stdio.h>

static int ports;

static unsigned int
i8( int port )
{
  char v8 = 0x17;

  if( lseek(ports, (off_t) port, SEEK_SET) == -1 ){
    perror("8-bit port lseek error");
    exit( 2 );
  }
  if( read(ports,&v8,1) != 1 ){
    perror("8-bit port read error");
    exit( 2 );
  }
  return (unsigned int) (unsigned char) v8;
}

static void
o8( int port, int value )
{
  char v8 = value;

  if( lseek(ports, (off_t) port, SEEK_SET) == -1 ){
    perror("8-bit port lseek error");
    exit( 2 );
  }
  if( write(ports,&value,1) != 1 ){
    perror("8-bit port write error");
    exit( 2 );
  }
}

static void
o16( int port, int value )
{
  char v16[2];

  v16[0] = (value & 0xff);
  v16[1] = ((value >> 8) & 0xff);

  if( lseek(ports, (off_t) port, SEEK_SET) == -1 ){
    perror("16-bit port lseek error");
    exit( 2 );
  }
  if( write(ports,&value,2) != 2 ){
    perror("16-bit port write error");
    exit( 2 );
  }
}

static void
mperror( char *s )
{
  fprintf( stderr, "%s\n", s );
  exit( 1 );
}

/**************************************/

int
opl3sa_read(int addr)
{
  o8( 0xf86, 0x1d ); /* password */
  o8( 0xf86, addr ); /* address */
  return i8( 0xf87 ); /* data */
}

void
opl3sa_write(int addr, int value)
{
  o8( 0xf86, 0x1d ); /* password */
  o8( 0xf86, addr ); /* address */
  o8( 0xf87, value ); /* data */
}

/**************************************/

main(int argc, char **argv)
{
  char *p;
  int wss_value, wss_addr, wss_irq_value, wss_dma_value,
  mpu_value, mpu_irq_value, sb_value, sb_irq_value, dma_value;
  int wss_flag, mpu_flag, sb_flag;

  if( argc != 9 ){
    mperror("\
exactly 8 arguments required:\n\
 wss_io=xxx wss_irq=xxx wss_dma=xxx\n\
 mpu_io=xxx mpu_irq=xxx\n\
 sb_io=xxx sb_irq=xxx sb_dma=xxx");
  }

  p = argv[1];
  wss_flag = 1;
  if(        !strcmp(p,"wss_io=0x530") ){
    wss_value = 0x24;
    wss_addr=0x530;
  } else if( !strcmp(p,"wss_io=0xe80") ){
    wss_value = (0x24|0x08);
    wss_addr=0xe80;
  } else if( !strcmp(p,"wss_io=0xf40") ){
    wss_value = (0x24|0x10);
    wss_addr=0xf40;
  } else if( !strcmp(p,"wss_io=0x604") ){
    wss_value = (0x24|0x18);
    wss_addr=0x604;
  } else if( !strcmp(p,"wss_io=-") ){
    wss_flag = 0;
  } else {
    mperror("wss_io value must be one of 0x530,0xe80,0xf40,0x604,-");
  }

  if( wss_flag ){
    p = argv[2];
    if(        !strcmp(p,"wss_irq=7") ){
      wss_irq_value = 0x08;
    } else if( !strcmp(p,"wss_irq=9") ){
      wss_irq_value = 0x10;
    } else if( !strcmp(p,"wss_irq=10") ){
      wss_irq_value = 0x18;
    } else if( !strcmp(p,"wss_irq=11") ){
      wss_irq_value = 0x20;
    } else {
      mperror("wss_irq value must be one of 7,9,10,11");
    }

    p = argv[3];
    if(        !strcmp(p,"wss_dma=0") ){
      wss_dma_value = 0x01;
    } else if( !strcmp(p,"wss_dma=1") ){
      wss_dma_value = 0x02;
    } else if( !strcmp(p,"wss_dma=3") ){
      wss_dma_value = 0x03;
    } else if( !strcmp(p,"wss_dma=0+1") ){
      wss_dma_value = 0x05;
    } else if( !strcmp(p,"wss_dma=1+0") ){
      wss_dma_value = 0x06;
    } else if( !strcmp(p,"wss_dma=3+0") ){
      wss_dma_value = 0x07;
    } else {
      mperror("wss_dma value must be one of 0,1,3,0+1,1+0,3+0");
    }
  }

  p = argv[4];
  mpu_flag = 1;
  if(        !strcmp(p,"mpu_io=0x330") ){
    mpu_value = 0x00;
  } else if( !strcmp(p,"mpu_io=0x332") ){
    mpu_value = 0x20;
  } else if( !strcmp(p,"mpu_io=0x334") ){
    mpu_value = 0x40;
  } else if( !strcmp(p,"mpu_io=0x300") ){
    mpu_value = 0x60;
  } else if( !strcmp(p,"mpu_io=-") ){
    mpu_flag = 0;
  } else {
    mperror("mpu_io value must be one of 0x330,0x332,0x334,0x300,-");
  }

  if( mpu_flag ){
    p = argv[5];
    if(        !strcmp(p,"mpu_irq=5") ){
      mpu_irq_value = 1;
    } else if( !strcmp(p,"mpu_irq=7") ){
      mpu_irq_value = 2;
    } else if( !strcmp(p,"mpu_irq=9") ){
      mpu_irq_value = 3;
    } else if( !strcmp(p,"mpu_irq=10") ){
      mpu_irq_value = 4;
    } else {
      mperror("mpu_irq value must be one of 5,7,9,10");
    }
  }

  p = argv[6];
  sb_flag = 1;
  if(        !strcmp(p,"sb_io=0x220") ){
    sb_value = 0x00;
  } else if( !strcmp(p,"sb_io=0x240") ){
    sb_value = 0x20;
  } else if( !strcmp(p,"sb_io=-") ){
    sb_flag = 0;
  } else {
    mperror("sb_io value must be one of 0x220,0x240,-");
  }

  if( sb_flag ){
    p = argv[7];
    if(        !strcmp(p,"sb_irq=5") ){
      sb_irq_value = 1;
    } else if( !strcmp(p,"sb_irq=7") ){
      sb_irq_value = 2;
    } else if( !strcmp(p,"sb_irq=9") ){
      sb_irq_value = 3;
    } else if( !strcmp(p,"sb_irq=10") ){
      sb_irq_value = 4;
    } else if( !strcmp(p,"sb_irq=11") ){
      sb_irq_value = 5;
    } else {
      mperror("sb_irq value must be one of 5,7,9,10,11");
    }

    p = argv[8];
    if(        !strcmp(p,"sb_dma=0") ){
      dma_value = 1;
    } else if( !strcmp(p,"sb_dma=1") ){
      dma_value = 2;
    } else if( !strcmp(p,"sb_dma=3") ){
      dma_value = 3;
    } else {
      mperror("sb_dma value must be one of 0,1,3");
    }
  }

  if( (ports=open(IOFILE,O_RDWR)) == -1 ){
    perror(IOFILE);
    exit( 1 );
  }

  opl3sa_write(0x01, 0x00);       /* Disable MSS */
  opl3sa_write(0x02, 0x00);       /* Disable SB */
  opl3sa_write(0x03, 0x00);       /* Disable MPU */

  if ( wss_flag ){                              /* Setup MSS */
    opl3sa_write(0x01, wss_value);
    o8( wss_addr, wss_irq_value|0x40 );         /* Setup irq */
    o8( wss_addr, wss_irq_value|wss_dma_value); /* Setup dma */
  }

  if( sb_flag ){                                /* Setup SB */
    opl3sa_write(0x02, 0x40|sb_value|(sb_irq_value<<2)|dma_value);
  }

  if( mpu_flag ){                               /* Setup MPU */
    mpu_value |= 0x83; /* MPU & OPL3 (synth) & game port enable */
    mpu_value |= mpu_irq_value << 2;
    opl3sa_write(0x03, mpu_value);
  }

  return 0;
}
