/* cdrom.c:
 *
 * Basic interaction with the CDROM drive.
 *
 * ----------------------------------------------------------------------
 * This code is (C) Copyright 1993 by Frank Munkert.
 * All rights reserved.
 * This software may be freely distributed and redistributed for
 * non-commercial purposes, provided this notice is included.
 * ----------------------------------------------------------------------
 * History:
 * 
 * 27-Sep-93   fmu   - Added support for multi-LUN devices.
 * 24-Sep-93   fmu   - SCSI buffers may now reside in fast or chip memory.
 *                   - TD_CHANGESTATE instead of CMD_READ in Test_Unit_Ready
 */

#include <clib/exec_protos.h>
#include <clib/alib_protos.h>

#include <devices/trackdisk.h>

#include "cdrom.h"

CDROM *Open_CDROM (char *p_device, int p_scsi_id, int p_use_trackdisk,
		   int p_use_fast_mem)
{
  CDROM *cd;
  int i;

  cd = AllocMem (sizeof (CDROM),
  	         MEMF_PUBLIC | MEMF_CLEAR |
		 (p_use_fast_mem ? MEMF_FAST : MEMF_CHIP));
  if (!cd)
    return NULL;

  cd->buffer_data = AllocMem (SCSI_BUFSIZE * SCSI_BUFFERS + 15,
  			      MEMF_PUBLIC |
			      (p_use_fast_mem ? MEMF_FAST : MEMF_CHIP));
  if (!cd->buffer_data) {
    FreeMem (cd, sizeof (CDROM));
    return NULL;
  }

  /* make the buffer quad-word aligned. This greatly helps 
   * performance on '040-powered systems with DMA SCSI
   * controllers.
   */

  cd->buffers[0] = (UBYTE *)(((ULONG)cd->buffer_data + 15) & ~15);

  for (i=1; i<SCSI_BUFFERS; i++)
    cd->buffers[i] = cd->buffers[0] + i * SCSI_BUFSIZE;

  cd->port = CreateMsgPort ();
  if (!cd->port) {
    FreeMem (cd->buffer_data, SCSI_BUFSIZE * SCSI_BUFFERS + 15);
    FreeMem (cd, sizeof (CDROM));
    return NULL;
  }

  cd->scsireq = CreateIORequest (cd->port, sizeof (struct IOStdReq));
  if (!cd->scsireq) {
    DeletePort (cd->port);
    FreeMem (cd->buffer_data, SCSI_BUFSIZE * SCSI_BUFFERS + 15);
    FreeMem (cd, sizeof (CDROM));
    return NULL;
  }

  if (OpenDevice ((UBYTE *) p_device, p_scsi_id,
                  (struct IORequest *) cd->scsireq, 0)) {
    DeleteIORequest (cd->scsireq);
    DeleteMsgPort (cd->port);
    FreeMem (cd->buffer_data, SCSI_BUFSIZE * SCSI_BUFFERS + 15);
    FreeMem (cd, sizeof (CDROM));
    return NULL;
  }

  for (i=0; i<SCSI_BUFFERS; i++)
    cd->current_sectors[i] = -1;
  
  cd->use_trackdisk = p_use_trackdisk;

  /* The LUN is the 2nd digit of the SCSI id number: */
  cd->lun = (p_scsi_id / 10) % 10;

  return cd;
}

int Do_SCSI_Command (CDROM *p_cd, unsigned char *p_buf, long p_buf_length,
		     unsigned char *p_command, int p_length)
{
  p_cd->scsireq->io_Length   = sizeof (struct SCSICmd);
  p_cd->scsireq->io_Data     = (APTR) &p_cd->cmd;
  p_cd->scsireq->io_Command  = HD_SCSICMD;

  p_cd->cmd.scsi_Data        = (UWORD *) p_buf;
  p_cd->cmd.scsi_Length      = p_buf_length;
  p_cd->cmd.scsi_Flags       = SCSIF_AUTOSENSE | SCSIF_READ;
  p_cd->cmd.scsi_SenseData   = (UBYTE *) p_cd->sense;
  p_cd->cmd.scsi_SenseLength = 18;
  p_cd->cmd.scsi_SenseActual = 0;
  p_cd->cmd.scsi_Command     = (UBYTE *) p_command;
  p_cd->cmd.scsi_CmdLength   = p_length;

  p_command[1] |= p_cd->lun << 5;

  DoIO ((struct IORequest *) p_cd->scsireq);
  if (p_cd->cmd.scsi_Status) {
    int i;
    for (i=0; i<SCSI_BUFFERS; i++)
      p_cd->current_sectors[i] = -1;
    return 0;
  } else
    return 1;
}

int Read_From_Drive (CDROM *p_cd, unsigned char *p_buf, long p_buf_length,
		     long p_sector, int p_number_of_sectors)
{
  static unsigned char cmd[10] = { 0x28, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

  if (p_cd->use_trackdisk) {
    p_cd->scsireq->io_Length   = 2048 * p_number_of_sectors;
    p_cd->scsireq->io_Data     = (APTR) p_buf;
    p_cd->scsireq->io_Offset   = (ULONG) p_sector * 2048;
    p_cd->scsireq->io_Command  = CMD_READ;

    DoIO ((struct IORequest *) p_cd->scsireq);
    if (p_cd->scsireq->io_Error) {
      int i;
      for (i=0; i<SCSI_BUFFERS; i++)
        p_cd->current_sectors[i] = -1;
      return 0;
    } else
      return 1;
  } else {
    cmd[5] = p_sector & 0xff;
    cmd[4] = (p_sector >> 8) & 0xff;
    cmd[3] = (p_sector >> 16) & 0x1f;

    cmd[8] = p_number_of_sectors;
  
    return Do_SCSI_Command (p_cd, p_buf, p_buf_length, cmd, sizeof (cmd));
  }
}

int Read_Sector_With_Lookahead (CDROM *p_cd, long p_sector, long p_last_sector)
{
  int status;
  int i;
  static int loc = 0;

  for (i=0; i<SCSI_BUFFERS; i++)
    if (p_cd->current_sectors[i] == p_sector) {
      p_cd->buffer = p_cd->buffers[i];
      return 1;
    }

  if (p_sector >= p_last_sector) {

    loc++;
    if (loc == SCSI_BUFFERS)
      loc=0;

    status = Read_From_Drive (p_cd, p_cd->buffers[loc], SCSI_BUFSIZE,
    			      p_sector, 1);
    if (status) {
      p_cd->current_sectors[loc] = p_sector;
      p_cd->buffer = p_cd->buffers[loc];
    }
  
  } else {

    int len = p_last_sector - p_sector + 1;
    
    if (len > SCSI_BUFFERS)
      len = SCSI_BUFFERS;

    status = Read_From_Drive (p_cd, p_cd->buffers[0], SCSI_BUFSIZE * len,
    			      p_sector, len);
    if (status) {
      for (i=0; i<len; i++)
        p_cd->current_sectors[i] = p_sector + i;
      p_cd->buffer = p_cd->buffers[0];
    }

  }

  return status;
}

int Read_Sector (CDROM *p_cd, long p_sector)
{
  return Read_Sector_With_Lookahead (p_cd, p_sector, p_sector);
}

int Test_Unit_Ready (CDROM *p_cd)
{
  p_cd->current_sectors[0] = -1;
  if (p_cd->use_trackdisk) {
    p_cd->scsireq->io_Command = TD_CHANGESTATE;

    if (!DoIO ((struct IORequest *) p_cd->scsireq)) {
      if (!p_cd->scsireq->io_Actual)
        return 1;
    }
    return 0;
  } else {
    static unsigned char cmd[6] = { 0, 0, 0, 0, 0, 0 };

    return Do_SCSI_Command (p_cd, p_cd->buffers[0], SCSI_BUFSIZE, cmd, 6);
  }
}

void Cleanup_CDROM (CDROM *p_cd)
{
  CloseDevice ((struct IORequest *) p_cd->scsireq);
  DeleteIORequest (p_cd->scsireq);
  DeleteMsgPort (p_cd->port);
  FreeMem (p_cd->buffer_data, SCSI_BUFSIZE * SCSI_BUFFERS + 15);
  FreeMem (p_cd, sizeof (CDROM));
}
