/*============================================================================*
 *============================================================================*
 * Component:           plextor-tool
 * Filename:            cplextor-tool.c
 *                                                                             
 * Authors:             Georg Huettenegger
 *                                                                             
 * Date of Creation:    Fri Jul 30 16:04:28 1999
 *                                                                             
 * Last Modification:   Fri Jul 30 16:04:28 1999
 *                                                                             
 * Copyright:           Georg Huettenegger                                   
 *                                                                             
 *
 * 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 License, or (at your option) 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *                                                                             
 *============================================================================*
 *============================================================================*
 */

#include "cplextor-tool.h"

void about ()
{
  printf ("plextor-tool v %s by Georg Huettenegger (georg@ist.org)\n",
          VERSION);
}

void usage ()
{
  printf ("Usage: plextor-tool DEVICE [OPTIONS]\n");
#ifdef __linux__
  printf ("DEVICE is a file description e.g. /dev/sg0\n");
#endif
#if (defined WIN32) || (defined _WIN32)
  printf ("DEVICE consists of host adapter number and scsi id e.g. 1 6\n");
#endif
  printf ("-q        query properties of DEVICE, no other options possible\n");
  printf ("-s x      set speed of DEVICE to x (multiple of 150KB/sec)\n");
  printf ("          0 represents maximum speed of drive\n");
  printf ("-i x      set spindown time to 0|125ms|250ms|500ms|1sec|2sec|4sec|8sec|16sec|\n");
  printf ("          32sec|1min|2min|4min|8min|16min|32min, 0 represents infinite\n");
  printf ("-o x[,y]  set volume to x (x or y must be in the range 0..255)\n");
  printf ("          if both x and y are given x is the volume of the left channel and y\n");
  printf ("          the volume of the right channel.\n");
  printf ("For the next three options consult the README (the last two are only available\n");
  printf ("for PX-20TSx/PX-32XSx/PX-40TSx)\n");
  printf ("All three options are only avaible for Plextor(r) CD-ROM drives, not MMC-drives!\n");
  printf ("-m 0|1    controls whether drive slows down on read errors:\n");
  printf ("          0 slow-down (default), 1 don't slow down\n");
  printf ("-a 0|1    controls whether drives slows down when drive pauses\n");
  printf ("          to avoid vibrations: 0 speed-down (default), 1 don't speed down\n");
  printf ("-w 1|0    controls whether drive transfers data before maximum speed:\n");
  printf ("          1 waits for maximum speed (default), 0 transfers before maximum speed\n");
  printf ("-v        set verbose level to 1\n");
#if (defined WIN32) || (defined _WIN32)
  printf ("-f        force waiting for ASPI initialization\n");
#endif
  printf ("\n");
  printf ("plextor-tool -l [-v]\n");
  printf ("-l        list all known Plextor(r)/MMC CD-ROM/CD-R(W) drives\n");
  printf ("-v        set verbose level to 1\n");
  printf ("\n");
  printf ("plextor-tool [-?|-h|--help]\n");
  printf ("-? or -h or --help  print this message\n");
  printf ("\n");
  printf ("plextor-tool --version\n");
  printf ("--version print version information\n");
  printf ("\n");
#ifdef __linux__
  printf ("Example: plextor-tool /dev/sg0 -s 20 -i 8min\n");
#endif
#if (defined WIN32) || (defined _WIN32)
  printf ("Example: plextor-tool 1 6 -s 20 -i 8min\n");
#endif
}

void explain_abort (int ret_code)
{
  about ();
  printf ("\n");
  usage ();
  exit (ret_code);
}

const char * PrintPlextorSpeedString (unsigned char speed)
{
  switch (speed)
  {
    case 0:
      return "SingleSpeed";
    case 1:
      if (drive_type == PLEX_6)
        return "Reserved";
      else
        return "DoubleSpeed";
    case 2:
      if ( (drive_type == PLEX_4) || (drive_type == PLEX_4_5) )
        return "Maximum Speed";
      else
        return "4times Speed";
    case 3:
      switch (drive_type)
      {
        case PLEX_4:
        case PLEX_4_5:
        case PLEX_8:
          return "Maximum Speed";
        case PLEX_6:
          return "Reserved";
        default:
          return "8times Speed";
      }
    case 4:
      switch (drive_type)
      {
        case PLEX_4:
        case PLEX_4_5:
        case PLEX_6:
        case PLEX_8:
          return "Maximum Speed";
        default:
          return "8times Speed";
      }
    case 5:
      switch (drive_type)
      {
        case PLEX_4:
        case PLEX_4_5:
        case PLEX_6:
        case PLEX_8:
        case PLEX_12:
          return "Maximum Speed";
        case PLEX_20:
          return "Reserved";
        case PLEX_32:
          return "8times CAV (8x-20x)";
        case PLEX_40:
          return "10x-20x";
      }
    default:
      return "Maximum Speed";
  }
}

int PlextorFieldValueForSpeed (int speed)
{
  switch (speed)
  {
    case 0:
      return 0xFF;
    case 1:
      return 0;
    case 2:
      if (drive_type == PLEX_6)
        return -1;
      else
        return 1;
    case 4:
      if (drive_type < PLEX_6)
        return -1;
      else
        return 2;
    case 8:
      if (drive_type < PLEX_12)
        return -1;
      else
        return 3;
    case 20:
      if (drive_type < PLEX_32)
        return -1;
      else
        return 5;
    default:
      if (verbose)
        printf ("Unknown speed setting requested!\n");
      return -1;
  };
}

void PrintAvailableSpeedSettings ()
{
  printf ("Available speed settings are: ");
  if (!mmc_drive)
  {
    switch (drive_type)
    {
      case PLEX_4:
      case PLEX_4_5:
        printf ("1 2 0");
        break;
      case PLEX_6:
        printf ("1 4 0");
        break;
      case PLEX_8:
        printf ("1 2 4 0");
        break;
      case PLEX_12:
      case PLEX_20:
        printf ("1 2 4 8 0");
        break;
      case PLEX_32:
      case PLEX_40:
        printf ("1 2 4 8 20 0");
        break;
      case PLEX_0:
      default:
        printf ("unknown drive!!!");
        break;
    };
  }
  else
  {
    switch (mmc_drive)
    {
      case MMC_1:
        printf ("0");
        break;
      case MMC_2:
        printf ("1 0");
        break;
      case MMC_4:
        printf ("1 2 0");
        break;
      case MMC_6:
        printf ("1 2 4 0");
        break;
      case MMC_8:
        printf ("1 2 4 0");
        break;
      case MMC_12:
        printf ("1 2 4 8 0");
        break;
      case MMC_16:
        printf ("1 2 4 8 12 0");
        break;
      case MMC_20:
        printf ("1 2 4 8 12 0");
        break;
      case MMC_24:
        printf ("1 2 4 8 12 0");
        break;
      case MMC_32:
        printf ("1 2 4 8 12 20 0");
        break;
      case MMC_0:
      default:
        printf ("unknown drive!!!");
        break;
    }
  }
}

void list_plextor_mmc_drives ()
{
#ifdef __linux__
  linux_list_plextor_mmc_drives ();
#endif
#if (defined WIN32) || (defined _WIN32)
  windows_list_plextor_mmc_drives ();
#endif
}

/* the device to be queried is set with global variables */
void query_plextor_mmc_drive ()
{
  const unsigned char * result;
  
  if (!mmc_drive)
  {
    result = ModeSenseSpeed ();
    printf ("Current speed setting: %s\n(",
            PrintPlextorSpeedString (*(result+14)) );
    PrintAvailableSpeedSettings ();
    printf (")\n");
    printf ("Current speed mode setting: %d\n", *(result+15) & 0x1);
    if (drive_type > PLEX_12)
    {
      printf ("Current disable avoid vibration setting: %d\n",
              (*(result+15) & 0x4) ? 1 : 0);
      printf ("Current waiting setting: %d\n", (*(result+15) & 0x2) ? 0 : 1);
    }
  }
  else
  {
    result = ModeSenseMMCSpeeds ();
    printf ("Current speed setting (of MMC drive): %dx (max: %dx)\n(",
            ((*(result+26) << 8) + (*(result+27))) / 176,
            ((*(result+20) << 8) + (*(result+21))) / 176);
    PrintAvailableSpeedSettings ();
    printf (")\n");

    /* cd-r read cap: *(result+14) & 0x01
       cd-rw read cap: *(result+14) & 0x02
       cd-r write cap: *(result+15) & 0x01
       cd-rw write cap: *(result+15) & 0x02
       test-write cap: *(result+15) & 0x04
       load mech. upper 3 bits from *(result+16)
       max write: ((*(result+30) << 8) + (*(result+31))) / 176
       cur write: ((*(result+32) << 8) + (*(result+33))) / 176
    */

  }
  result = ModeSenseCDPars ();
  printf ("Current spin-down time: %s\n", 
          PrintInactivityString (*(result+15)));
  result = ModeSenseAudioControl ();
  printf ("Current volumes: left (port 0): %d, right (port 1): %d of 255\n",
	  *(result+21), *(result+23));
}

int main( int argc, char * argv[] )
{
  char **ap, *drive_name_part1 = NULL, *drive_name_part2 = NULL;
  const unsigned char *result;
  const unsigned char *speed_sense_result;
  int speed = -1, idle = -1, left_volume = -1, right_volume = -1;
  int list_drives = 0, query_drive = 0, set_speed = 0, set_spindown = 0;
  int set_speed_mode = 0, set_avoid_vibration = 0, set_waiting = 0;
  int set_volume = 0;
  
  /* first store program name in prog_name */
#if (defined WIN32) || (defined _WIN32)
  prog_name = strrchr (argv[0], '\\');
#else
  prog_name = strrchr (argv[0], '/');
#endif
  if (prog_name)
    ++prog_name;
  else
    prog_name = argv[0];

  /* next parse the command line arguments => sync with usage! */
  for (ap = argv + 1; ap < argv + argc; ++ap)
  {
    if ((*ap)[0] != '-')
    {
#ifdef __linux__
      if (drive_name_part1)
        explain_abort (1);
      else
      {
        drive_name_part1 = *ap;
      }
#endif
#if (defined WIN32) || (defined _WIN32)
    if (drive_name_part1 && drive_name_part2)
      explain_abort (1);
    else
    {
      if (drive_name_part1)
        drive_name_part2 = *ap;
      else
        drive_name_part1 = *ap;
    }
#endif
    }
    else
    {
      int j = 1;
      int goOn = 1;
      while ( (goOn) && (*ap) && ((*ap)[j]) )
      {
        switch ((*ap)[j])
        {
          case '?':
          case 'h':
            usage ();
            exit (0);
            
          case '-':
          {
            if (!strcmp ((*ap), "--version"))
            {
              about ();
              exit (0);
            }
            else
            {
              if (!strcmp ((*ap), "--help"))
              {
                usage ();
                exit (0);
              }
              else
                fprintf
                  (stderr, "Unknown option: %s ignored (or misinterpreted).\n",
                   (*ap));
            }
          }
          
          case 'l':
            list_drives = 1;
            break;
            
          case 'v':
            verbose = 1;
            break;
            
#if (defined WIN32) || (defined _WIN32)
          case 'f':
            force = 1;
            break;
#endif
            
          case 'q':
            query_drive = 1;
            break;
            
            /* from here on options need a second argument and make 
               fall through if to default if not available */
          case 's':
	  {
	    char * value = NULL;
	    if ((*ap)[2] != '\0')
	      value = (*ap)+2;
            else if (ap < argv + argc)
            {
              ++ap;
	      value = *ap;
            }
	    if (value == NULL)
	      break;
	    speed = atoi (value);
	    set_speed = 1;
	    goOn = 0;
	    break;
	  }
            
          case 'o':
	  {
	    char * value = NULL, * sep;
	    int found_sep = 0;
	    if ((*ap)[2] != '\0')
	      value = (*ap)+2;
            else if (ap < argv + argc)
            {
              ++ap;
	      value = *ap;
	    }
	    if (value == NULL)
	      break;
	    if ((sep = strchr(value, ',')) != NULL)
	    {
	      right_volume = atoi(sep+1);
	      sep[0] = '\0';
	      found_sep = 1;
	    }
	    left_volume = atoi (value);
	    if (found_sep == 0)
	      right_volume = left_volume;
	    set_volume = 1;
	    goOn = 0;
	    break;
	  }
            
          case 'i':
	  {
	    char * value = NULL;
	    if ((*ap)[2] != '\0')
	      value = (*ap)+2;
            else if (ap < argv + argc)
            {
              ++ap;
	      value = *ap;
	    }
	    if (value == NULL)
	      break;
	    idle = FieldValueForInactivityString(value);
	    if (idle == -1)
	      explain_abort (2);
	    set_spindown = 1;
	    goOn = 0;
	    break;
	  }
            
          case 'a':
	  {
	    char * value = NULL;
	    if ((*ap)[2] != '\0')
	      value = (*ap)+2;
            else if (ap < argv + argc)
            {
              ++ap;
	      value = *ap;
	    }
	    if (value == NULL)
	      break;
	    set_avoid_vibration = atoi (value) + 1;
	    goOn = 0;
	    break;
	  }

          case 'm':
	  {
	    char * value = NULL;
	    if ((*ap)[2] != '\0')
	      value = (*ap)+2;
            else if (ap < argv + argc)
            {
              ++ap;
	      value = *ap;
	    }
	    if (value == NULL)
	      break;
	    set_speed_mode = atoi (value) + 1;
	    goOn = 0;
	    break;
	  }
            
          case 'w':
	  {
	    char * value = NULL;
	    if ((*ap)[2] != '\0')
	      value = (*ap)+2;
            else if (ap < argv + argc)
            {
              ++ap;
	      value = *ap;
	    }
	    if (value == NULL)
	      break;
	    set_waiting = atoi (value) + 1;
	    goOn = 0;
	    break;
	  }

        
          default:
            fprintf (stderr, "Unknown/incomplete option: %s ignored\n", *ap);
        }
        ++j;
      }
    }
  }

#if (defined WIN32) || (defined _WIN32)
  if (!windows_initialize_scsi ())
  {
    printf ("Could not initialize correctly! Aborting!\n");
    exit (1);
  }
#endif

  if (list_drives)
  {
    list_plextor_mmc_drives ();
    if ( (query_drive) || (set_speed) || (set_spindown) || (set_waiting) ||
         (set_avoid_vibration) || (set_speed_mode) || (set_volume) )
    {
      printf ("Ignoring further options just listing!\n");
    }
    return 0;
  }

#ifdef __linux__
  if ( (drive_name_part2) || (!drive_name_part1) )
    explain_abort (3);
  drive_name_part2 = drive_name_part1;
#endif
#if (defined WIN32) || (defined _WIN32)
  if ( (!drive_name_part2) || (!drive_name_part1) )
    explain_abort (3);
#endif
  if (!(result = open_and_check_drive (drive_name_part1, drive_name_part2)))
  {
#ifdef __linux__
    if (geteuid ())
    {
      printf ("You are not super-user: Either given device is no Plextor"
              "(r)/MMC CD-ROM/CD-R(W) drive or you don't have permission"
              " to access the device!\n");
            
    }
    else
#endif
    {
      printf ("Given device is no Plextor(r)/MMC CD-ROM/CD-R(W) drive!\n");
    }
    exit (1);
  }

  if (query_drive)
  {
#ifdef __linux__
    printf ("Current settings for %.22s (%s, BIOS: %.4s):\n",
            result+INQUIRY_VENDOR, drive_name_part1, result+INQUIRY_BIOS_VER);
#endif
#if (defined WIN32) || (defined _WIN32)
    printf ("Current settings for %.22s (%s,%s; BIOS: %.4s):\n",
            result+INQUIRY_VENDOR,
            drive_name_part1, drive_name_part2,
            result+INQUIRY_BIOS_VER);
#endif
    query_plextor_mmc_drive ();
    if ( (set_speed) || (set_spindown) || (set_waiting) ||
         (set_avoid_vibration) || (set_speed_mode) || (set_volume) )
    {
      printf ("Ignoring further options just querying!\n");
    }
    return 0;
  }
  if (set_speed)
  {
    if (!mmc_drive)
    {
      /* only checked for plextor CD-ROM drives, mmc drives use nearest
         lower available speed (so user will see) */
      speed = PlextorFieldValueForSpeed (speed);
      if (speed == -1)
      {
        printf ("Invalid speed setting for %.22s:\n\n!",
                result+INQUIRY_VENDOR);
        PrintAvailableSpeedSettings ();
        printf ("\n\n");
        exit (1);
      }
      speed_sense_result = ModeSenseSpeed ();
    }
    else
    {
      if (speed > 372)
      {
        printf ("Invalid speed setting for %.22s (MMC drive)!\n\n",
                result+INQUIRY_VENDOR);
        PrintAvailableSpeedSettings ();
        printf ("\n\n");
        exit (1);
      }
      speed_sense_result = ModeSenseMMCSpeeds ();
    }
    if (verbose)
    {
      if (!mmc_drive)
        printf ("Current speed setting: %s\n",
                PrintPlextorSpeedString (*(speed_sense_result+14)) );
      else
        printf ("Current speed setting (of MMC drive): %dx (max: %dx)\n",
                  ((*(speed_sense_result+26) << 8)
                   + (*(speed_sense_result+27))) / 176,
                  ((*(speed_sense_result+20) << 8)
                   + (*(speed_sense_result+21))) / 176);
    }
    if (!mmc_drive)
    {
      if (ModeSelectSpeed ((unsigned char) speed, *(speed_sense_result+15)))
      {
        printf ("Speed setting failed!\n");
      }
      else
      {
        if (verbose)
          printf ("Speed setting was successful, now: %s\n",
                  PrintPlextorSpeedString ((unsigned char) speed));
      }
    }
    else
    {
      if (speed == 0)
        speed = 372;
      if (MMCSetReadSpeed (speed))
      {
        printf ("Speed setting failed!\n");
      }
      else
      {
        if (verbose)
        {
          speed_sense_result = ModeSenseMMCSpeeds ();
          printf ("Speed setting was successful, now: %dx (max: %dx)\n",
                  ((*(speed_sense_result+26) << 8)
                   + (*(speed_sense_result+27))) / 176,
                  ((*(speed_sense_result+20) << 8)
                   + (*(speed_sense_result+21))) / 176);
        }
      }
    }
  }
  if (set_speed_mode)
  {
    if (mmc_drive)
    {
      printf ("Speed mode option not available for your MMC drive!\n");
    }
    else
    {
      if ( (set_speed_mode > 2) || (set_speed_mode < 0) )
      {
        printf ("Invalid speed mode setting for %.22s:\n", result+INQUIRY_VENDOR);
        exit (1);
      }
      speed_sense_result = ModeSenseSpeed ();
      if (verbose)
        printf ("Current speed mode setting: %d\n",
                *(speed_sense_result+15) & 0x1);
      if (ModeSelectSpeed (*(speed_sense_result+14),
                           (unsigned char) ((*(speed_sense_result+15) & 0xFE) |
                                            ((set_speed_mode - 1)))))
      {
        printf ("Speed mode setting failed!\n");
      }
      else
      {
        if (verbose)
          printf ("Speed mode setting was successful, now: %d\n",
                  set_speed_mode - 1);
      }
    }
  }
  if (set_waiting)
  {
    if ((drive_type < PLEX_20) || mmc_drive)
    {
      printf ("Waiting option not available for your Plextor(r)/MMC drive!\n");
    }
    else
    {
      if ( (set_waiting > 2) || (set_waiting < 0) )
      {
        printf ("Invalid waiting setting for %.22s:\n", result+INQUIRY_VENDOR);
        exit (1);
      }
      speed_sense_result = ModeSenseSpeed ();
      if (verbose)
        printf ("Current waiting setting: %d\n",
                (*(speed_sense_result+15) & 0x2) ? 0 : 1);
      if (set_waiting == 2)
        set_waiting = 0;
      else
        set_waiting = 1;
      if (ModeSelectSpeed (*(speed_sense_result+14),
                           (unsigned char) ((*(speed_sense_result+15) & 0xFD) |
                           (set_waiting << 1))))
      {
        printf ("Waiting setting failed!\n");
      }
      else
      {
        if (verbose)
          printf ("Waiting setting was successful, now: %d\n",
                  set_waiting ? 0 : 1);
      }
    }
  }
  if (set_avoid_vibration)
  {
    if ((drive_type < PLEX_20) || mmc_drive)
    {
      printf ("Disable vibration option not available for your Plextor(r)/MMC"
              " drive!\n");
    }
    else
    {
      if ( (set_avoid_vibration > 2) || (set_avoid_vibration < 0) )
      {
        printf ("Invalid disable avoid vibration setting for %.22s:\n",
                result+INQUIRY_VENDOR);
        exit (1);
      }
      speed_sense_result = ModeSenseSpeed ();
      if (verbose)
        printf ("Current disable avoid vibration setting: %d\n",
                (*(speed_sense_result+15) & 0x4) ? 1 : 0);
      if (ModeSelectSpeed (*(speed_sense_result+14),
                           (unsigned char) ((*(speed_sense_result+15) & 0xFB) | 
                           ((set_avoid_vibration - 1) << 2))))
      {
        printf ("Avoid vibration setting failed!\n");
      }
      else
      {
        if (verbose)
          printf ("Avoid vibration setting was successful, now: %d\n",
                  set_avoid_vibration - 1);
      }
    }
  }
  if (set_spindown)
  {
    const unsigned char *inactivity_sense_result;
    inactivity_sense_result = ModeSenseCDPars ();
    if (verbose)
      printf ("Current spin-down time: %s\n", 
          PrintInactivityString (*(inactivity_sense_result+15)));
    if (ModeSelectCDPars ((unsigned char) idle, inactivity_sense_result+16))
    {
      printf ("Setting spin-down time failed!\n");
    }
    else
    {
      inactivity_sense_result = ModeSenseCDPars ();
      if (*(inactivity_sense_result+15) != idle)
      {
        printf ("Setting spin-down time failed to no apparent reason!\n");
      }
      else
      {
        if (verbose)
          printf ("Setting spin-down time was successful, now: %s\n",
                  PrintInactivityString ((unsigned char) idle));
      }
    }
  }
  if (set_volume)
  {
    const unsigned char *audio_control_result;
    if ( (left_volume < 0) || (left_volume > 255) ||
	 (right_volume < 0) || (right_volume > 255) )
    {
      printf ("Invalid volume setting (must be within [0..255]) for %.22s!",
	      result+INQUIRY_VENDOR);
      printf ("\n\n");
      exit (1);
    }
    audio_control_result = ModeSenseAudioControl ();
    if (verbose)
      printf ("Current volumes: left (port 0): %d, right (port 1): %d of 255"
	      "\n", *(audio_control_result+21), *(audio_control_result+23));
    if (ModeSelectAudioControl (left_volume, right_volume,
				audio_control_result))
    {
      printf ("Avoid vibration setting failed!\n");
    }
    else
    {
      if (verbose)
      {
	audio_control_result = ModeSenseAudioControl ();
	printf ("Current volumes: left (port 0): %d, right (port 1): %d of 255"
		"\n", *(audio_control_result+21), *(audio_control_result+23));
      }
    }
  }

#ifdef __linux__

#endif

#if (defined WIN32) || (defined _WIN32)
  TermASPI ();
#endif

  return 0;
}

/*============================================================================*
 *============================================================================*
 * EOF cplextor-tool.c
 *============================================================================*
 *============================================================================*
 */
