/*  cdrdao - write audio CD-Rs in disc-at-once mode
 *
 *  Copyright (C) 1999-2000  Cameron G. MacKinnon <C_MacKinnon@yahoo.com>
 *
 *  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.
 */

/*
 * $Log: YamahaCDR10x.cc,v $
 * Revision 1.4  2000/12/17 10:51:23  andreasm
 * Default verbose level is now 2. Adaopted message levels to have finer
 * grained control about the amount of messages printed by cdrdao.
 * Added CD-TEXT writing support to the GenericMMCraw driver.
 * Fixed CD-TEXT cue sheet creating for the GenericMMC driver.
 *
 * Revision 1.3  2000/10/08 16:39:41  andreasm
 * Remote progress message now always contain the track relative and total
 * progress and the total number of processed tracks.
 *
 * Revision 1.2  2000/04/23 16:29:50  andreasm
 * Updated to state of my private development environment.
 *
 * Revision 1.5  1999/12/15 20:31:46  mueller
 * Added remote messages for 'read-cd' progress used by a GUI.
 *
 * Revision 1.4  1999/11/07 09:15:15  mueller
 * Release 1.1.3
 *
 * Revision 1.3  1999/04/05 11:04:10  mueller
 * Added driver option flags.
 *
 * Revision 1.2  1999/03/27 14:35:17  mueller
 * Added data track support.
 *
 * Revision 1.1  1999/02/28 10:27:49  mueller
 * Initial revision
 */

/* Cameron's log:
 *
 * Revision 0.4  1999/01/08 21:00:00  cmackin
 * Fixed BCD fields in Cue Sheet
 * Todo: BCD issues for ISRC and Catalog
 *       core dump when RO CD inserted for burn functions
 *
 * Revision 0.3  1999/01/08 12:00:00  cmackin
 * Added {g,s}etModePage6(...): Yamaha doesn't support the (10) cmds
 *
 * Revision 0.2  1999/01/08 09:00:00  cmackin
 * Added READ CD-ROM CAPACITY to diskInfo()
 *
 * Revision 0.1  1999/01/07 20:44:58  cmackin
 * Initial revision, based on GenericMMC
 *
 */

/* Driver for Yamaha CDR10X drives. 
 * Written by Cameron G. MacKinnon <C_MacKinnon@yahoo.com>.
 */

static char rcsid[] = "$Id: YamahaCDR10x.cc,v 1.4 2000/12/17 10:51:23 andreasm Exp $";

#include <config.h>

#include <unistd.h>
#include <string.h>
#include <assert.h>

#include "YamahaCDR10x.h"

#include "Toc.h"
#include "PQSubChannel16.h"
#include "util.h"
#include "port.h"


YamahaCDR10x::YamahaCDR10x(ScsiIf *scsiIf, unsigned long options)
  : CdrDriver(scsiIf, options)
{
  int i;

  driverName_ = "Yamaha CDR10x - Version 0.5 (data)";

  encodingMode_ = 1; 
  
  speed_ = 0;
  simulate_ = 1;

  scsiTimeout_ = 0;

  for (i = 0; i < maxScannedSubChannels_; i++)
    scannedSubChannels_[i] = new PQSubChannel16;

  // reads little endian samples
  audioDataByteOrder_ = 0;
}

// static constructor
CdrDriver *YamahaCDR10x::instance(ScsiIf *scsiIf, unsigned long options)
{
  return new YamahaCDR10x(scsiIf, options);
}

YamahaCDR10x::~YamahaCDR10x()
{
  int i;

  for (i = 0; i < maxScannedSubChannels_; i++) {
    delete scannedSubChannels_[i];
    scannedSubChannels_[i] = NULL;
  }
}

int YamahaCDR10x::multiSession(int m)
{
  if (m != 0) {
    return 1; // multi session mode is not supported
  }

  return 0;
}

// sets speed
// return: 0: OK
//         1: illegal speed
int YamahaCDR10x::speed(int s)
{
  if (s == 0 || s == 1 || s == 2 || s == 4)
    speed_ = s;
  else if (s == 3)
    speed_ = 2;
  else if (s > 4)
    speed_ = 4;
  else
    return 1;

  if (selectSpeed() != 0)
    return 1;
  
  return 0;
}

int YamahaCDR10x::speed()
{
  DriveInfo di;

  if (driveInfo(&di, 1) != 0) {
    return 0;
  }

  return speed2Mult(di.currentWriteSpeed);

}

// loads ('unload' == 0) or ejects ('unload' == 1) tray
// return: 0: OK
//         1: scsi command failed
int YamahaCDR10x::loadUnload(int unload) const
{
  unsigned char cmd[6];

  memset(cmd, 0, 6);

  cmd[0] = 0x1b; // START/STOP UNIT
  if (unload) {
    cmd[4] = 0x02; // LoUnlo=1, Start=0
  }
  else {
    cmd[4] = 0x01; // LoUnlo=0, Start=1: This is a caddy, and load is reserved
  }
  
  if (sendCmd(cmd, 6, NULL, 0, NULL, 0) != 0) {
    message(-2, "Cannot %s medium.", (unload ? "stop & unload" : "start"));
    return 1;
  }

  return 0;
}

// sets read/write speed
// return: 0: OK
//         1: scsi command failed
int YamahaCDR10x::selectSpeed()
{
  unsigned char mp[4];

  if (getModePage6(0x31/*drive configuration mode page*/, mp, 4,
		   NULL, NULL, 1) != 0) {
    message(-2, "Cannot retrieve drive configuration mode page (0x31).");
    return 1;
  }

  mp[3] &= 0x0f;
  if(speed_ == 0 || speed_ == 4) {
	mp[3] |= 0x20;  // read at 4x, write at 2x (CDR102) or 4x (CDR100)
  } else if(speed_ == 2) {
	mp[3] |= 0x10;  // read at 2x, write at 2x
  } else if(speed_ == 1) {
	;  		// read at 1x, write at 1x
  } else {
	message(-2, "Invalid write speed argument %d. 0, 1, 2 or 4 expected.",
		speed_);
	return 1;
  }

  if (setModePage6(mp, NULL, NULL, 1) != 0) {
    message(-2, "Cannot set drive configuration mode page for cd speed.");
    return 1;
  }

  return 0;
}

// Sets write parameters via mode pages 0x31 and 0x32.
// return: 0: OK
//         1: scsi command failed
int YamahaCDR10x::setWriteParameters()
{
  unsigned char mp[8];	// the larger of 4 and 8, gets used twice

  if (getModePage6(0x31/*drive configuration mode page*/, mp, 4,
		   NULL, NULL, 1) != 0) {
    message(-2, "Cannot retrieve drive configuration mode page (0x31).");
    return 1;
  }

  mp[3] &= 0xf0;	// save speed settings in high nibble
  mp[3] |= (simulate_ ? 1 : 0);

  if (setModePage6(mp, NULL, NULL, 1) != 0) {
    message(-2, "Cannot set drive configuration mode page (0x31).");
    return 1;
  }

  memset(mp, 0, 8);

  if (getModePage6(0x32/*write format mode page*/, mp, 8,
		   NULL, NULL, 1) != 0) {
    message(-2, "Cannot retrieve write format mode page (0x32).");
    return 1;
  }

  mp[2] &= 0xf0;	// RW subcode internally zero supplied
  mp[3] &= 0xf0;
  mp[3] |= 0x04;	// disc at once (single session)

  if (setModePage6(mp, NULL, NULL, 1) != 0) {
    message(-2, "Cannot set write format mode page (0x32).");
    return 1;
  }

  return 0;
}

void YamahaCDR10x::cueSheetDataType(TrackData::Mode mode,
				    unsigned char *dataType,
				    unsigned char *dataForm)
{
  switch (mode) {
  case TrackData::AUDIO:
    *dataType = 0;
    *dataForm = 0;
    break;
    
  case TrackData::MODE1:
  case TrackData::MODE1_RAW:
    *dataType = 2;
    *dataForm = 3;
    break;

  case TrackData::MODE2:
    *dataType = 3;
    *dataForm = 3;
    break;

  case TrackData::MODE2_RAW:
  case TrackData::MODE2_FORM1:
  case TrackData::MODE2_FORM2:
  case TrackData::MODE2_FORM_MIX:
    if (toc_->tocType() == Toc::CD_I)
      *dataType = 4;
    else
      *dataType = 5;
    *dataForm = 3;
    break;

  case TrackData::MODE0:
    message(-3, "Illegal mode in 'YamahaCDR10x::cueSheetDataType()'.");
    *dataType = 0;
    *dataForm = 0;
    break;
  }
}

// Creates cue sheet for current toc.
// cueSheetLen: filled with length of cue sheet in bytes
// return: newly allocated cue sheet buffer or 'NULL' on error
// CHECKS OUT WITH DOS-DAO AND MANUAL
unsigned char *YamahaCDR10x::createCueSheet(long *cueSheetLen)
{
  const Track *t;
  int trackNr;
  Msf start, end, index;
  unsigned char *cueSheet;
  long len = 2; // entries for lead-in, lead-out
  long n; // index into cue sheet
  unsigned char ctl; // control nibbles of cue sheet entry CTL/ADR
  long i;
  unsigned char dataForm;
  unsigned char dataType;

  TrackIterator itr(toc_);

  if (itr.first(start, end) == NULL) {
    return NULL;
  }

  if (toc_->catalogValid()) {
    len += 2;
  }

  for (t = itr.first(start, end), trackNr = 1;
       t != NULL; t = itr.next(start, end), trackNr++) {
    len += 2; // entry for track and mandatory pre-gap
    if (t->type() == TrackData::AUDIO && t->isrcValid()) {
      len += 2; // entries for ISRC code
    }
    len += t->nofIndices(); // entry for each index increment
  }

  cueSheet = new (unsigned char)[len * 8];
  n = 0;

  if (toc_->leadInMode() == TrackData::AUDIO)
    ctl = 0;
  else
    ctl = 0x40;

  if (toc_->catalogValid()) {
    // fill catalog number entry
    cueSheet[n*8] = 0x02 | ctl;
    cueSheet[(n+1)*8] = 0x02 | ctl;
    for (i = 1; i <= 13; i++) {
      if (i < 8) {
	cueSheet[n*8 + i] = toc_->catalog(i-1) + '0';
      }
      else {
	cueSheet[(n+1)*8 + i - 7] = toc_->catalog(i-1) + '0';
      }
    }
    cueSheet[(n+1)*8+7] = 0;
    n += 2;
  }

  cueSheetDataType(toc_->leadInMode(), &dataType, &dataForm);

  // entry for lead-in
  cueSheet[n*8] = 0x01 | ctl; // CTL/ADR
  cueSheet[n*8+1] = 0;        // Track number
  cueSheet[n*8+2] = 0;        // Index
  cueSheet[n*8+3] = dataType; // Data Type
  cueSheet[n*8+4] = 0;        // Data Form: always 0 for lead-in
  cueSheet[n*8+5] = 0;        // MIN
  cueSheet[n*8+6] = 0;        // SEC
  cueSheet[n*8+7] = 0;        // FRAME
  n++;
  
  for (t = itr.first(start, end), trackNr = 1;
       t != NULL;
       t = itr.next(start, end), trackNr++) {

    cueSheetDataType(t->type(), &dataType, &dataForm);

    ctl = trackCtl(t);

    if (t->type() == TrackData::AUDIO) {
      // set ISRC code for audio track
      if (t->isrcValid()) {
	cueSheet[n*8] = ctl | 0x03;
	cueSheet[n*8+1] = SubChannel::bcd(trackNr);
	cueSheet[n*8+2] = t->isrcCountry(0);
	cueSheet[n*8+3] = t->isrcCountry(1);
	cueSheet[n*8+4] = t->isrcOwner(0);  
	cueSheet[n*8+5] = t->isrcOwner(1);   
	cueSheet[n*8+6] = t->isrcOwner(2);  
	cueSheet[n*8+7] = t->isrcYear(0) + '0';
	n++;
	
	cueSheet[n*8] = ctl | 0x03;
	cueSheet[n*8+1] = SubChannel::bcd(trackNr);
	cueSheet[n*8+2] = t->isrcYear(1) + '0';
	cueSheet[n*8+3] = t->isrcSerial(0) + '0';
	cueSheet[n*8+4] = t->isrcSerial(1) + '0';
	cueSheet[n*8+5] = t->isrcSerial(2) + '0';
	cueSheet[n*8+6] = t->isrcSerial(3) + '0';
	cueSheet[n*8+7] = t->isrcSerial(4) + '0';
	n++;
      }
    }

    Msf tstart(start.lba() + 150); // start of index 1

    // start of pre-gap, atime equals index 1 if no pre-gap is defined
    Msf pstart(trackNr == 1 ? 0 : tstart.lba() - t->start().lba());

    // entry for pre-gap
    cueSheet[n*8]   = ctl | 0x01;
    cueSheet[n*8+1] = SubChannel::bcd(trackNr);
    cueSheet[n*8+2] = 0;        // Index 0 indicates pre-gap
    cueSheet[n*8+3] = dataType; // Data Type
    cueSheet[n*8+4] = dataForm; // Data Form
    cueSheet[n*8+5] = SubChannel::bcd(pstart.min());
    cueSheet[n*8+6] = SubChannel::bcd(pstart.sec());
    cueSheet[n*8+7] = SubChannel::bcd(pstart.frac());
    n++;

    cueSheet[n*8]   = ctl | 0x01;
    cueSheet[n*8+1] = SubChannel::bcd(trackNr);
    cueSheet[n*8+2] = 1;        // Index 1
    cueSheet[n*8+3] = dataType; // Data Type
    cueSheet[n*8+4] = dataForm; // Data Form
    cueSheet[n*8+5] = SubChannel::bcd(tstart.min());
    cueSheet[n*8+6] = SubChannel::bcd(tstart.sec());
    cueSheet[n*8+7] = SubChannel::bcd(tstart.frac());
    n++;

    for (i = 0; i < t->nofIndices(); i++) {
      index = tstart + t->getIndex(i);
      cueSheet[n*8]   = ctl | 0x01;
      cueSheet[n*8+1] = SubChannel::bcd(trackNr);
      cueSheet[n*8+2] = SubChannel::bcd(i+2); // Index
      cueSheet[n*8+3] = dataType;    // DataType
      cueSheet[n*8+4] = dataForm;    // Data Form
      cueSheet[n*8+5] = SubChannel::bcd(index.min());
      cueSheet[n*8+6] = SubChannel::bcd(index.sec());
      cueSheet[n*8+7] = SubChannel::bcd(index.frac());
      n++;
    }
  }

  assert(n == len - 1);

  // entry for lead out
  Msf lostart(toc_->length().lba() + 150);
  cueSheetDataType(toc_->leadOutMode(), &dataType, &dataForm);
  ctl = toc_->leadOutMode() == TrackData::AUDIO ? 0 : 0x40;
    
  cueSheet[n*8]   = ctl | 0x01;
  cueSheet[n*8+1] = 0xaa;	// This IS BCD
  cueSheet[n*8+2] = 1; // Index 1
  cueSheet[n*8+3] = dataType; // Data for lead-out
  cueSheet[n*8+4] = 0;        // Data Form, always 0 for lead-out
  cueSheet[n*8+5] = SubChannel::bcd(lostart.min());
  cueSheet[n*8+6] = SubChannel::bcd(lostart.sec());
  cueSheet[n*8+7] = SubChannel::bcd(lostart.frac());

  message(4, "\nCue Sheet:");
  message(4, "CTL/  TNO  INDEX  DATA  DATA  MIN  SEC  FRAME");
  message(4, "ADR   BCD  BCD    TYPE  Form  BCD  BCD  BCD <- Note");
  for (n = 0; n < len; n++) {
    message(4, "%02x    %02x    %02x     %02x    %02x   %02x   %02x   %02x",
	   cueSheet[n*8],
	   cueSheet[n*8+1], cueSheet[n*8+2], cueSheet[n*8+3], cueSheet[n*8+4],
	   cueSheet[n*8+5], cueSheet[n*8+6], cueSheet[n*8+7]);
  }

  *cueSheetLen = len * 8;
  return cueSheet;
}

// LOOKS GOOD
int YamahaCDR10x::sendCueSheet()
{
  unsigned char cmd[10];
  long cueSheetLen;
  unsigned char *cueSheet = createCueSheet(&cueSheetLen);

  if (cueSheet == NULL) {
    return 1;
  }

  if(cueSheetLen > 32768) {  // Limit imposed by drive.
    message(-2, "Cue sheet too long for this device: Limit is 32k bytes.");
    delete[] cueSheet;
    return 1;
  }

  memset(cmd, 0, 10);

  cmd[0] = 0xea; // Yamaha Vendor Specific WRITE TOC

  cmd[7] = cueSheetLen >> 8;
  cmd[8] = cueSheetLen;

  if (sendCmd(cmd, 10, cueSheet, cueSheetLen, NULL, 0) != 0) {
    message(-2, "Cannot send cue sheet.");
    delete[] cueSheet;
    return 1;
  }

  delete[] cueSheet;
  return 0;
}

// DOES NOT TALK TO DRIVE: JUST HOUSEKEEPING
int YamahaCDR10x::initDao(const Toc *toc)
{
  long n;

  toc_ = toc;

  blockLength_ = AUDIO_BLOCK_LEN;
  blocksPerWrite_ = scsiIf_->maxDataLen() / blockLength_;

  assert(blocksPerWrite_ > 0);

  if (selectSpeed() != 0) {
    return 1;
  }

  // allocate buffer for writing zeros
  n = blocksPerWrite_ * blockLength_;
  delete[] zeroBuffer_;
  zeroBuffer_ = new char[n];
  memset(zeroBuffer_, 0, n);

  return 0;
}

// LOOKS OK - dodgy WRITE TRACK parameters, but apparently how DAO16 does it
int YamahaCDR10x::startDao()
{
  unsigned char cmd[10];

  scsiTimeout_ = scsiIf_->timeout(3 * 60);

  if (setWriteParameters() != 0 ||
      sendCueSheet() != 0) {
    return 1;
  }

  memset(cmd, 0, 10);
  cmd[0] = 0xe6; 	// Yamaha WRITE TRACK(10)
  cmd[5] = 0;		// Track Number
  cmd[6] &= 0xdf;	// R Parity = 0;
  cmd[6] &= 0xef;	// Byte Swap = 0 = little endian
  cmd[6] &= 0xf7;	// Copy = 0
  cmd[6] &= 0xfb;	// Audio = 0 (false)
  cmd[6] &= 0xfc;	// Mode=0 NB This combination of mode/audio is reserved
  // so we end up sending a bare e6 command with nine bytes of nulls

  if (sendCmd(cmd, 10, NULL, 0, NULL, 0) != 0) {
	message(-2, "Could not send command 0xE6: WRITE TRACK(10)");
	return 1;
  }

  //message(2, "Writing lead-in and gap...");

  long lba = -150;

  if (writeZeros(toc_->leadInMode(), lba, 0, 150) != 0) {
    return 1;
  }
  
  return 0;
}

int YamahaCDR10x::finishDao()
{
  unsigned char cmd[6];
  const unsigned char *sense;
  int senseLen;

  message(2, "Flushing cache...");
  
  if (flushCache() != 0) {
    return 1;
  }

  memset(cmd, 0, 6);

  // wait until lead-out is written
  while(sendCmd(cmd, 6, NULL, 0, NULL, 0, 0) == 2
	&& (sense = scsiIf_->getSense(senseLen)) && senseLen 
	&& sense[2] == 0x0b  && sense[0xc] == 0x50) {
    sleep(1);
  }

  scsiIf_->timeout(scsiTimeout_);

  delete[] zeroBuffer_, zeroBuffer_ = NULL;

  return 0;
}

void YamahaCDR10x::abortDao()
{
  flushCache();
}

///////////////////////// FIXME THE TOP BAR //////////////////////////////

// NEEDS WORK
DiskInfo *YamahaCDR10x::diskInfo()
{
  unsigned char cmd[10];
  unsigned long dataLen = 8;
  unsigned char data[8];
  static DiskInfo di;
  //char spd;

  memset(&di, 0, sizeof(DiskInfo));

  di.valid.cdrw = 0; // by definition, for this device

  memset(cmd, 0, 10);
  memset(data, 0, dataLen);

  // This looks like CdrDevice::readCapacity, but with a difference: we
  // get data for a ROM, and we need to check that our disc is writable

  cmd[0] = 0x25; // READ CD-ROM CAPACITY
 
  if (sendCmd(cmd, 10, NULL, 0, data, dataLen) != 0) {
    message(-1, "Cannot read CD-ROM capacity.");
    return &di;
  }

  if(data[4] | data[5] | data[6] | data[7]) { // block length field, 0=writable
	di.capacity = 0;
  } else {	// disk is writable
	di.capacity = data[0]<<24 | data[1]<<16 | data[2]<<8 | data[3];
  }
  di.valid.capacity = 1;

  // FIXME: need to come up with empty, manufacturerID, recSpeed

  // maybe use READ DISC INFO (0x43) here? se p. 62
/////////////////////////////////////////
  
  return &di;
}


// tries to read catalog number from disk and adds it to 'toc'
// return: 1 if valid catalog number was found, else 0
int YamahaCDR10x::readCatalog(Toc *toc, long startLba, long endLba)
{
  unsigned char cmd[10];
  unsigned char data[24];
  char catalog[14];
  int i;

  memset(cmd, 0, 10);
  memset(data, 0, 24);

  cmd[0] = 0x42; // READ SUB-CHANNEL
  cmd[2] = 1 << 6;
  cmd[3] = 0x02; // get media catalog number
  cmd[8] = 24;   // transfer length

  if (sendCmd(cmd, 10, NULL, 0, data, 24) != 0) {
    message(-2, "Cannot get catalog number.");
    return 0;
  }

  if (data[8] & 0x80) {
    for (i = 0; i < 13; i++) {
      catalog[i] = data[0x09 + i];
    }
    catalog[13] = 0;

    if (toc->catalog(catalog) == 0) {
      return 1;
    }
  }

  return 0;
}

int YamahaCDR10x::readIsrc(int trackNr, char *buf)
{
  unsigned char cmd[10];
  unsigned char data[24];
  int i;

  buf[0] = 0;

  memset(cmd, 0, 10);
  memset(data, 0, 24);

  cmd[0] = 0x42; // READ SUB-CHANNEL
  cmd[2] = 1 << 6;
  cmd[3] = 0x03; // get ISRC
  cmd[6] = trackNr;
  cmd[8] = 24;   // transfer length

  if (sendCmd(cmd, 10, NULL, 0, data, 24) != 0) {
    message(-2, "Cannot get ISRC code.");
    return 0;
  }

  // FIXME this looks right, but the offsets could be wrong
  if (data[8] & 0x80) {
    for (i = 0; i < 12; i++) {
      buf[i] = data[0x09 + i];
    }
    buf[12] = 0;
  }

  return 0;
}

int YamahaCDR10x::analyzeTrack(TrackData::Mode mode, int trackNr,
			       long startLba,
			       long endLba, Msf *indexIncrements,
			       int *indexIncrementCnt, long *pregap,
			       char *isrcCode, unsigned char *ctl)
{
  int ret = analyzeTrackScan(mode, trackNr, startLba, endLba,
			     indexIncrements, indexIncrementCnt, pregap,
			     isrcCode, ctl);

  if (mode == TrackData::AUDIO)
    readIsrc(trackNr, isrcCode);
  else
    *isrcCode = 0;

  return ret;
}

int YamahaCDR10x::readSubChannels(long lba, long len, SubChannel ***chans,
				  Sample *audioData)
{
  int retries = 5;
  int i;
  unsigned char cmd[12];
  
  cmd[0] = 0xd8;  // Yamaha READ CD-DA
  cmd[1] = 0;
  cmd[2] = lba >> 24;
  cmd[3] = lba >> 16;
  cmd[4] = lba >> 8;
  cmd[5] = lba;
  cmd[6] = len >> 24;
  cmd[7] = len >> 16;
  cmd[8] = len >> 8;
  cmd[9] = len;
  cmd[10] = 0x01;  // 2352 + 10 Q (no CRC) + 6 NULL
  cmd[11] = 0;

  while (1) {
    if (sendCmd(cmd, 12, NULL, 0,
		transferBuffer_, len * (AUDIO_BLOCK_LEN + 16),
		retries == 0 ? 1 : 0) != 0) {
      if (retries == 0)
	return 1;
    }
    else {
      break;
    }

    retries--;
  }

  for (i = 0; i < len; i++) {
    PQSubChannel16 *chan = (PQSubChannel16*)scannedSubChannels_[i];
    unsigned char *p =
      transferBuffer_  + i * (AUDIO_BLOCK_LEN + 16) + AUDIO_BLOCK_LEN;

    // slightly reorder the sub-channel so it is suitable for 'PQSubChannel16'
    p[0] <<= 4;   // ctl/adr
    p[0] |= p[1];
    p[1] = p[2];  // track nr
    p[2] = p[3];  // index nr
    p[3] = p[4];  // min
    p[4] = p[5];  // sec
    p[5] = p[6];  // frame
    p[6] = 0;     // zero
    // p[7] is amin
    // p[8] is asec
    // p[9] is aframe

    chan->init(p);
    
    if (chan->checkConsistency())
      chan->calcCrc();
  }

  if (audioData != NULL) {
    unsigned char *p = transferBuffer_;

    for (i = 0; i < len; i++) {
      memcpy(audioData, p, AUDIO_BLOCK_LEN);

      p += AUDIO_BLOCK_LEN + 16;
      audioData += SAMPLES_PER_BLOCK;
    }
  }
  
  *chans = scannedSubChannels_;
  return 0;
}

// LOOKSGOOD, except for accurate audio stream bit
int YamahaCDR10x::driveInfo(DriveInfo *info, int showErrorMsg)
{
  unsigned char mp[4];

  if (getModePage6(0x31, mp, 4, NULL, NULL, showErrorMsg) != 0) {
    if (showErrorMsg) {
      message(-2, "Cannot retrieve drive configuration mode page (0x31).");
    }
    return 1;
  }

  // FIXME
  // info->accurateAudioStream = mp[5] & 0x02 ? 1 : 0;
  info->accurateAudioStream = 0;

  info->maxReadSpeed = mult2Speed(4);

  if(!strncmp(scsiIf_->product(), "CDR100", 6)) {
	info->maxWriteSpeed = mult2Speed(4);
	info->currentWriteSpeed = info->currentReadSpeed = 
		mult2Speed(1 << (mp[3] >> 4));
  } else {	// CDR102, crippled to 2x write
	info->maxWriteSpeed = mult2Speed(2);
	info->currentReadSpeed = mult2Speed(1 << (mp[3] >> 4));
	info->currentWriteSpeed = mult2Speed((mp[3] >> 4) ? 2 : 1);
  }

  return 0;
}

// writeData is overridden here because the CDR10x, while writing the disc
// lead-in with the buffer full, will return CHECK CONDITION with sense
// DRIVE BUSY and additional sense 0xb8 = WRITE LEAD-IN IN PROGRESS.
// This is normal operation and the write should be retried until successful.

// Writes data to target, the block length depends on the actual writing mode
// 'len' is number of blocks to write.
// 'lba' specifies the next logical block address for writing and is updated
// by this function.
// return: 0: OK
//         1: scsi command failed
int YamahaCDR10x::writeData(TrackData::Mode mode, long &lba, const char *buf,
			    long len)
{
  assert(blocksPerWrite_ > 0);
  int writeLen = 0;
  unsigned char cmd[10];
  int retry;
  int retval;
  long blockLength = blockSize(mode);

#if 0
  long sum, i;

  sum = 0;

  for (i = 0; i < len * blockLength; i++) {
    sum += buf[i];
  }

  message(0, "W: %ld: %ld, %ld, %ld", lba, blockLength, len, sum);
#endif

  memset(cmd, 0, 10);
  cmd[0] = 0x2a; // WRITE1
  
  while (len > 0) {
    writeLen = (len > blocksPerWrite_ ? blocksPerWrite_ : len);

    cmd[2] = lba >> 24;
    cmd[3] = lba >> 16;
    cmd[4] = lba >> 8;
    cmd[5] = lba;

    cmd[7] = writeLen >> 8;
    cmd[8] = writeLen & 0xff;

    do {
      retry = 0;
      retval = sendCmd(cmd, 10, (unsigned char *)buf, writeLen * blockLength,
		       NULL, 0, 0);
      if(retval == 2) {
        const unsigned char *sense;
        int senseLen;

        sense = scsiIf_->getSense(senseLen);

	// we spin on the write here, waiting a reasonable time in between
	// we should really use READ BLOCK LIMIT (0x05)
	if(senseLen && sense[2] == 9 && sense[0xc] == 0xb8) {
	  mSleep(40);
          retry = 1;
        }
	else {
	  scsiIf_->printError();
	}
      }
    } while(retry == 1);

    if(retval) {
      message(-2, "Write data failed.");
      return 1;
    }

    buf += writeLen * blockLength;

    lba += writeLen;
    len -= writeLen;
  }
      
  return 0;
}

CdRawToc *YamahaCDR10x::getRawToc(int sessionNr, int *len)
{
  // not supported by drive
  return NULL;
}


long YamahaCDR10x::readTrackData(TrackData::Mode mode, long lba, long len,
				 unsigned char *buf)
{
  unsigned char cmd[10];
  long blockLen = 2340;
  long i;
  TrackData::Mode actMode;
  int ok = 0;
  int softError;
  const unsigned char *sense;
  int senseLen;

  if (setBlockSize(blockLen) != 0)
    return 0;

  memset(cmd, 0, 10);

  cmd[0] = 0x28; // READ10
  cmd[2] = lba >> 24;
  cmd[3] = lba >> 16;
  cmd[4] = lba >> 8;
  cmd[5] = lba;

  while (len > 0 && !ok) {
    cmd[7] = len >> 8;
    cmd[8] = len;

    memset(transferBuffer_, 0, len * blockLen);
    switch (sendCmd(cmd, 10, NULL, 0, transferBuffer_, len * blockLen, 0)) {
    case 0:
      ok = 1;
      break;

    case 2:
      softError = 0;
      sense = scsiIf_->getSense(senseLen);

      if (senseLen > 0x0c) {
	if ((sense[2] &0x0f) == 5) {
	  switch (sense[12]) {
	  case 0x63: // end of user area encountered on this track
	  case 0x64: // Illegal mode for this track
	    softError = 1;
	    break;
	  }
	}
	else if ((sense[2] & 0x0f) == 3) { // Medium error
	  switch (sense[12]) {
	  case 0x02: // No seek complete, sector not found
	  case 0x11: // L-EC error
	    return -2;
	    break;
	  }
	}
      }

      if (!softError) {
	scsiIf_->printError();
	return -1;
      }
      break;

    default:
      message(-2, "Read error at LBA %ld, len %ld", lba, len);
      return -1;
      break;
    }

    if (!ok) {
      len--;
    }
  }

  unsigned char *sector = transferBuffer_;
  for (i = 0; i < len; i++) {
    actMode = determineSectorMode(sector);

    if (!(actMode == mode ||
	  (mode == TrackData::MODE2_FORM_MIX &&
	   (actMode == TrackData::MODE2_FORM1 ||
	    actMode == TrackData::MODE2_FORM2)) ||

	  (mode == TrackData::MODE1_RAW && actMode == TrackData::MODE1) ||

	  (mode == TrackData::MODE2_RAW &&
	   (actMode == TrackData::MODE2 ||
	    actMode == TrackData::MODE2_FORM1 ||
	    actMode == TrackData::MODE2_FORM2)))) {
      return i;
    }

    if (buf != NULL) {
      switch (mode) {
      case TrackData::MODE1:
	memcpy(buf, sector + 4, MODE1_BLOCK_LEN);
	buf += MODE1_BLOCK_LEN;
	break;
      case TrackData::MODE2:
      case TrackData::MODE2_FORM_MIX:
	memcpy(buf, sector + 4, MODE2_BLOCK_LEN);
	buf += MODE2_BLOCK_LEN;
	break;
      case TrackData::MODE2_FORM1:
	memcpy(buf, sector + 12, MODE2_FORM1_DATA_LEN);
	buf += MODE2_FORM1_DATA_LEN;
	break;
      case TrackData::MODE2_FORM2:
	memcpy(buf, sector + 12, MODE2_FORM2_DATA_LEN);
	buf += MODE2_FORM2_DATA_LEN;
	break;
      case TrackData::MODE1_RAW:
      case TrackData::MODE2_RAW:
	memcpy(buf, syncPattern, 12);
	memcpy(buf + 12, sector, 2340);
	buf += AUDIO_BLOCK_LEN;
	break;
      case TrackData::MODE0:
      case TrackData::AUDIO:
	message(-3, "YamahaCDR10x::readTrackData: Illegal mode.");
	return 0;
	break;
      }
    }

    sector += blockLen;
  }  

  return len;
}

Toc *YamahaCDR10x::readDiskToc(int session, const char *audioFilename)
{
  Toc *toc = CdrDriver::readDiskToc(session, audioFilename);

  setBlockSize(MODE1_BLOCK_LEN);
  
  return toc;
}

Toc *YamahaCDR10x::readDisk(int session, const char *fname)
{
  Toc *toc = CdrDriver::readDisk(session, fname);

  setBlockSize(MODE1_BLOCK_LEN);

  return toc;
}

int YamahaCDR10x::readAudioRange(ReadDiskInfo *rinfo, int fd, long start,
				 long end, int startTrack,
				 int endTrack, TrackInfo *info)
{
  if (!onTheFly_) {
    int t;

    message(1, "Analyzing...");
    
    for (t = startTrack; t <= endTrack; t++) {
      long totalProgress;

      totalProgress = t * 1000;
      totalProgress /= rinfo->tracks;
      sendReadCdProgressMsg(RCD_ANALYZING, rinfo->tracks, t + 1, 0,
			    totalProgress);

      message(1, "Track %d...", t + 1);
      info[t].isrcCode[0] = 0;
      readIsrc(t + 1, info[t].isrcCode);
      if (info[t].isrcCode[0] != 0)
	message(2, "Found ISRC code.");

      totalProgress = (t + 1) * 1000;
      totalProgress /= rinfo->tracks;
      sendReadCdProgressMsg(RCD_ANALYZING, rinfo->tracks, t + 1, 1000,
			    totalProgress);
    }

    message(1, "Reading...");
  }

  return CdrDriver::readAudioRangeParanoia(rinfo, fd, start, end, startTrack,
					   endTrack, info);
}
