/*
 * Sony CDU-31A CDROM interface device driver.
 *
 * Corey Minyard (minyard@wf-rch.cirr.com)
 *
 * Colossians 3:17
 *
 * The Sony interface device driver handles Sony interface CDROM
 * drives and provides a complete block-level interface as well as an
 * ioctl() interface compatible with the Sun (as specified in
 * include/linux/cdrom.h).  With this interface, CDROMs can be
 * accessed and standard audio CDs can be played back normally.
 *
 * WARNING - 	All autoprobes have been removed from the driver.
 *		You MUST configure the CDU31A via a LILO config
 *		at boot time or in lilo.conf.  I have the
 *		following in my lilo.conf:
 *
 *                append="cdu31a=0x1f88,0,PAS"
 *
 *		The first number is the I/O base address of the
 *		card.  The second is the interrupt (0 means none).
 *		The third should be "PAS" if on a Pro-Audio
 *		spectrum, or nothing if on something else.
 *
 * This interface is (unfortunately) a polled interface.  This is
 * because most Sony interfaces are set up with DMA and interrupts
 * disables.  Some (like mine) do not even have the capability to
 * handle interrupts or DMA.  For this reason you will see a lot of
 * the following:
 *
 *   retry_count = jiffies+ SONY_JIFFIES_TIMEOUT;
 *   while ((retry_count > jiffies) && (! <some condition to wait for))
 *   {
 *      while (handle_sony_cd_attention())
 *         ;
 *
 *      sony_sleep();
 *   }
 *   if (the condition not met)
 *   {
 *      return an error;
 *   }
 *
 * This ugly hack waits for something to happen, sleeping a little
 * between every try.  it also handles attentions, which are
 * asynchronous events from the drive informing the driver that a disk
 * has been inserted, removed, etc.
 *
 * NEWS FLASH - The driver now supports interrupts but they are
 * turned off by default.  Use of interrupts is highly encouraged, it
 * cuts CPU usage down to a reasonable level.  I had DMA in for a while
 * but PC DMA is just too slow.  Better to just insb() it.
 *
 * One thing about these drives: They talk in MSF (Minute Second Frame) format.
 * There are 75 frames a second, 60 seconds a minute, and up to 75 minutes on a
 * disk.  The funny thing is that these are sent to the drive in BCD, but the
 * interface wants to see them in decimal.  A lot of conversion goes on.
 *
 * DRIVER SPECIAL FEATURES
 * -----------------------
 *
 * This section describes features beyond the normal audio and CD-ROM
 * functions of the drive.
 *
 * 2048 byte buffer mode
 *
 * If a disk is mounted with -o block=2048, data is copied straight
 * from the drive data port to the buffer.  Otherwise, the readahead
 * buffer must be involved to hold the other 1K of data when a 1K
 * block operation is done.  Note that with 2048 byte blocks you
 * cannot execute files from the CD.
 *
 * XA compatibility
 *
 * The driver should support XA disks for both the CDU31A and CDU33A.
 * It does this transparently, the using program doesn't need to set it.
 *
 * Multi-Session
 *
 * A multi-session disk is treated like a partitioned disk, each session
 * has it's own minor device number, starting with 0.  The support is
 * pretty transparent, music, TOC operations, and read operations should
 * all work transparently on any session.  Note that since the driver
 * writer doesn't have a multi-session disk, this is all theoretical.
 * Also, music operation will obviously only work on one session at a
 * time.
 *
 * NOTE: At the current time, multi-session still doesn't work.  Maybe
 * I'll get a multi-session disk soon so I can play with it.
 * 
 * Raw sector I/O
 *
 * Using the CDROMREADAUDIO it is possible to read raw audio and data
 * tracks.  Both operations return 2352 bytes per sector.  On the data
 * tracks, the first 12 bytes is not returned by the drive and the value
 * of that data is indeterminate.
 *
 *
 *  Copyright (C) 1993  Corey Minyard
 *
 *  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.
 *
 */

/*
 *
 * Setting up the Sony CDU31A/CDU33A drive interface card.  If
 * You have another card, you are on your own.
 * 
 *      +----------+-----------------+----------------------+
 *      |  JP1     |  34 Pin Conn    |                      |
 *      |  JP2     +-----------------+                      |
 *      |  JP3                                              |
 *      |  JP4                                              |
 *      |                                                   +--+
 *      |                                                   |  +-+
 *      |                                                   |  | |  External
 *      |                                                   |  | |  Connector
 *      |                                                   |  | |
 *      |                                                   |  +-+
 *      |                                                   +--+
 *      |                                                   |
 *      |                                          +--------+
 *      |                                          |
 *      +------------------------------------------+
 * 
 *    JP1 sets the Base Address, using the following settings:
 * 
 *      Address         Pin 1           Pin 2
 *      -------         -----           -----
 *      0x320           Short           Short
 *      0x330           Short           Open
 *      0x340           Open            Short
 *      0x360           Open            Open
 * 
 *    JP2 and JP3 configure the DMA channel; they must be set the same.
 * 
 *      DMA             Pin 1           Pin 2           Pin 3
 *      ---             -----           -----           -----
 *      1               On              Off             On
 *      2               Off             On              Off
 *      3               Off             Off             On
 * 
 *    JP4 Configures the IRQ:
 * 
 *      IRQ     Pin 1           Pin 2           Pin 3           Pin 4
 *      ---     -----           -----           -----           -----
 *      3       Off             Off             On              Off
 *      4       Off             Off*            Off             On
 *      5       On              Off             Off             Off
 *      6       Off             On              Off             Off
 * 
 *              * The documentation states to set this for interrupt
 *                4, but I think that is a mistake.
 */

#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/hdreg.h>
#include <linux/genhd.h>
#include <linux/ioport.h>
#include <linux/string.h>
#include <linux/malloc.h>

#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>
#include <asm/dma.h>

#include <linux/cdrom.h>
#include <linux/cdu31a.h>

#define MAJOR_NR CDU31A_CDROM_MAJOR
#include "blk.h"

#define DEBUG 0

#define CDU31A_READAHEAD 64  /* 64 sector, 32kB, 16 reads read-ahead */
#define CDU31A_MAX_CONSECUTIVE_ATTENTIONS 10

/* Define the following if you have data corruption problems. */
#undef SONY_POLL_EACH_BYTE

/*
** Edit the following data to change interrupts, DMA channels, etc.
** Default is polled and no DMA.  DMA is not recommended for double-speed
** drives.
*/
static struct
{
   unsigned short base;         /* I/O Base Address */
   short          int_num;      /* Interrupt Number (-1 means scan for it,
                                   0 means don't use) */
} cdu31a_addresses[] =
{
#if 0	/* No autoconfig any more. See Note at beginning
	   of this file. */
   { 0x340,     0 },    /* Standard configuration Sony Interface */
   { 0x1f88,    0 },    /* Fusion CD-16 */
   { 0x230,     0 },    /* SoundBlaster 16 card */
   { 0x360,     0 },    /* Secondary standard Sony Interface */
   { 0x320,     0 },    /* Secondary standard Sony Interface */
   { 0x330,     0 },    /* Secondary standard Sony Interface */
   { 0x634,     0 },    /* Sound FX SC400 */
   { 0x654,     0 },    /* Sound FX SC400 */
#endif
   { 0 }
};

static int handle_sony_cd_attention(void);
static int read_subcode(void);
static void sony_get_toc(int dev);
static int scd_open(struct inode *inode, struct file *filp);
static void do_sony_cd_cmd(unsigned char cmd,
                           unsigned char *params,
                           unsigned int num_params,
                           unsigned char *result_buffer,
                           unsigned int *result_size);
static void size_to_buf(unsigned int size,
                        unsigned char *buf);

/* Parameters for the read-ahead. */
static unsigned int sony_next_block;      /* Next 512 byte block offset */
static unsigned int sony_blocks_left = 0; /* Number of 512 byte blocks left
                                             in the current read command. */


/* The base I/O address of the Sony Interface.  This is a variable (not a
   #define) so it can be easily changed via some future ioctl() */
static unsigned short sony_cd_base_io = 0;

/*
 * The following are I/O addresses of the various registers for the drive.  The
 * comment for the base address also applies here.
 */
static volatile unsigned short sony_cd_cmd_reg;
static volatile unsigned short sony_cd_param_reg;
static volatile unsigned short sony_cd_write_reg;
static volatile unsigned short sony_cd_control_reg;
static volatile unsigned short sony_cd_status_reg;
static volatile unsigned short sony_cd_result_reg;
static volatile unsigned short sony_cd_read_reg;
static volatile unsigned short sony_cd_fifost_reg;


static int sony_spun_up = 0;               /* Has the drive been spun up? */

static int sony_xa_mode = 0;               /* Is an XA disk in the drive
					      and the drive a CDU31A? */

static int sony_raw_data_mode = 1;         /* 1 if data tracks, 0 if audio.
                                              For raw data reads. */

static unsigned int sony_usage = 0;        /* How many processes have the
                                              drive open. */

static int sony_pas_init = 0;		   /* Initialize the Pro-Audio
					      Spectrum card? */

static struct s_sony_session_toc *(ses_tocs[MAX_TRACKS]); /* Points to the
							     table of
							     contents. */

static struct s_sony_subcode * volatile last_sony_subcode; /* Points to the last
                                                    subcode address read */

static volatile int sony_inuse = 0;  /* Is the drive in use?  Only one operation
					at a time allowed */

static struct wait_queue * sony_wait = NULL;	/* Things waiting for the drive */

static struct task_struct *has_cd_task = NULL;  /* The task that is currently
						   using the CDROM drive, or
						   NULL if none. */

static int is_double_speed = 0; /* Is the drive a CDU33A? */

/*
 * The audio status uses the values from read subchannel data as specified
 * in include/linux/cdrom.h.
 */
static volatile int sony_audio_status = CDROM_AUDIO_NO_STATUS;

/*
 * The following are a hack for pausing and resuming audio play.  The drive
 * does not work as I would expect it, if you stop it then start it again,
 * the drive seeks back to the beginning and starts over.  This holds the
 * position during a pause so a resume can restart it.  It uses the
 * audio status variable above to tell if it is paused.
 */
static unsigned volatile char cur_pos_msf[3] = { 0, 0, 0 };
static unsigned volatile char final_pos_msf[3] = { 0, 0, 0 };

/* What IRQ is the drive using?  0 if none. */
static int irq_used = 0;

/* The interrupt handler will wake this queue up when it gets an
   interrupts. */
static struct wait_queue *cdu31a_irq_wait = NULL;

static int curr_control_reg = 0; /* Current value of the control register */

/* A disk changed variable for every possible session.  When a disk change
   is detected, these will all be set to TRUE.  As the upper layers ask
   for disk_changed status for individual minor numbers, they will be
   cleared. */
static char disk_changed[MAX_TRACKS];

/* Variable for using the readahead buffer.  The readahead buffer
   is used for raw sector reads and for blocksizes that are smaller
   than 2048 bytes. */
static char *readahead_buffer = NULL; /* Used for 1024 byte blocksize. */
static int readahead_dataleft = 0;
static int readahead_bad = 0;


/*
 * This routine returns 1 if the disk has been changed since the last
 * check or 0 if it hasn't.
 */
static int
scd_disk_change(dev_t full_dev)
{
   int retval, target;


   target = MINOR(full_dev);

   if (target >= MAX_TRACKS) {
      printk("Sony CD-ROM request error: invalid device.\n");
      return 0;
   }

   retval = disk_changed[target];
   disk_changed[target] = 0;

   return retval;
}

static inline void
enable_interrupts(void)
{
   curr_control_reg |= (  SONY_ATTN_INT_EN_BIT
                        | SONY_RES_RDY_INT_EN_BIT
                        | SONY_DATA_RDY_INT_EN_BIT);
   outb(curr_control_reg, sony_cd_control_reg);
}

static inline void
disable_interrupts(void)
{
   curr_control_reg &= ~(  SONY_ATTN_INT_EN_BIT
                         | SONY_RES_RDY_INT_EN_BIT
                         | SONY_DATA_RDY_INT_EN_BIT);
   outb(curr_control_reg, sony_cd_control_reg);
}

static void
cdu31a_interrupt(int unused)
{
   disable_interrupts();
   if (cdu31a_irq_wait != NULL)
   {
      wake_up(&cdu31a_irq_wait);
   }
   else
   {
      printk("CDU31A: Got an interrupt but nothing was waiting\n");
   }
}

/*
 * Wait a little while (used for polling the drive).  If in initialization,
 * setting a timeout doesn't work, so just loop for a while.
 */
static inline void
sony_sleep(void)
{
   if (irq_used <= 0)
   {
      current->state = TASK_INTERRUPTIBLE;
      current->timeout = jiffies;
      schedule();
   }
   else /* Interrupt driven */
   {
      cli();
      enable_interrupts();
      interruptible_sleep_on(&cdu31a_irq_wait);
      sti();
   }
}


/*
 * The following are convenience routine to read various status and set
 * various conditions in the drive.
 */
static inline int
is_attention(void)
{
   return((inb(sony_cd_status_reg) & SONY_ATTN_BIT) != 0);
}

static inline int
is_busy(void)
{
   return((inb(sony_cd_status_reg) & SONY_BUSY_BIT) != 0);
}

static inline int
is_data_ready(void)
{
   return((inb(sony_cd_status_reg) & SONY_DATA_RDY_BIT) != 0);
}

static inline int
is_data_requested(void)
{
   return((inb(sony_cd_status_reg) & SONY_DATA_REQUEST_BIT) != 0);
}

static inline int
is_result_ready(void)
{
   return((inb(sony_cd_status_reg) & SONY_RES_RDY_BIT) != 0);
}

static inline int
is_param_write_rdy(void)
{
   return((inb(sony_cd_fifost_reg) & SONY_PARAM_WRITE_RDY_BIT) != 0);
}

static inline int
is_result_reg_not_empty(void)
{
   return((inb(sony_cd_fifost_reg) & SONY_RES_REG_NOT_EMP_BIT) != 0);
}

static inline void
reset_drive(void)
{
   curr_control_reg = 0;
   outb(SONY_DRIVE_RESET_BIT, sony_cd_control_reg);
}

static inline void
clear_attention(void)
{
   outb(curr_control_reg | SONY_ATTN_CLR_BIT, sony_cd_control_reg);
}

static inline void
clear_result_ready(void)
{
   outb(curr_control_reg | SONY_RES_RDY_CLR_BIT, sony_cd_control_reg);
}

static inline void
clear_data_ready(void)
{
   outb(curr_control_reg | SONY_DATA_RDY_CLR_BIT, sony_cd_control_reg);
}

static inline void
clear_param_reg(void)
{
   outb(curr_control_reg | SONY_PARAM_CLR_BIT, sony_cd_control_reg);
}

static inline unsigned char
read_status_register(void)
{
   return(inb(sony_cd_status_reg));
}

static inline unsigned char
read_result_register(void)
{
   return(inb(sony_cd_result_reg));
}

static inline unsigned char
read_data_register(void)
{
   return(inb(sony_cd_read_reg));
}

static inline void
write_param(unsigned char param)
{
   outb(param, sony_cd_param_reg);
}

static inline void
write_cmd(unsigned char cmd)
{
   outb(curr_control_reg | SONY_RES_RDY_INT_EN_BIT, sony_cd_control_reg);
   outb(cmd, sony_cd_cmd_reg);
}

/*
 * Set the drive parameters so the drive will auto-spin-up when a
 * disk is inserted.
 */
static void
set_drive_params(void)
{
   unsigned char res_reg[12];
   unsigned int res_size;
   unsigned char params[3];


   params[0] = SONY_SD_MECH_CONTROL;
   params[1] = 0x03; /* Set auto spin up and auto eject */
   if (is_double_speed)
   {
      params[1] |= 0x04; /* Set the drive to double speed if possible */
   }
   do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
                  params,
                  2,
                  res_reg,
                  &res_size);
   if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20))
   {
      printk("  Unable to set mechanical parameters: 0x%2.2x\n", res_reg[1]);
   }
}

/*
 * This code will reset the drive and attempt to restore sane parameters.
 */
static void
restart_on_error(void)
{
   unsigned char res_reg[12];
   unsigned int res_size;
   unsigned int retry_count;


   printk("cdu31a: Resetting drive on error\n");
   reset_drive();
   retry_count = jiffies + SONY_RESET_TIMEOUT;
   while ((retry_count > jiffies) && (!is_attention()))
   {
      sony_sleep();
   }
   set_drive_params();
   do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
   if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20))
   {
      printk("cdu31a: Unable to spin up drive: 0x%2.2x\n", res_reg[1]);
   }

   current->state = TASK_INTERRUPTIBLE;
   current->timeout = jiffies + 200;
   schedule();

   do_sony_cd_cmd(SONY_READ_TOC_CMD, NULL, 0, res_reg, &res_size);
   if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20))
   {
      printk("cdu31a: Unable to read TOC: 0x%2.2x\n", res_reg[1]);
   }
}

/*
 * This routine writes data to the parameter register.  Since this should
 * happen fairly fast, it is polled with no OS waits between.
 */
static int
write_params(unsigned char *params,
             int num_params)
{
   unsigned int retry_count;


   retry_count = SONY_READY_RETRIES;
   while ((retry_count > 0) && (!is_param_write_rdy()))
   {
      retry_count--;
   }
   if (!is_param_write_rdy())
   {
      return -EIO;
   }

   while (num_params > 0)
   {
      write_param(*params);
      params++;
      num_params--;
   }

   return 0;
}


/*
 * The following reads data from the command result register.  It is a
 * fairly complex routine, all status info flows back through this
 * interface.  The algorithm is stolen directly from the flowcharts in
 * the drive manual.
 */
static void
get_result(unsigned char *result_buffer,
           unsigned int *result_size)
{
   unsigned char a, b;
   int i;
   unsigned int retry_count;


   while (handle_sony_cd_attention())
      ;
   /* Wait for the result data to be ready */
   retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
   while ((retry_count > jiffies) && (is_busy() || (!(is_result_ready()))))
   {
      sony_sleep();

      while (handle_sony_cd_attention())
         ;
   }
   if (is_busy() || (!(is_result_ready())))
   {
#if DEBUG
      printk("CDU31A timeout out %d\n", __LINE__);
#endif
      result_buffer[0] = 0x20;
      result_buffer[1] = SONY_TIMEOUT_OP_ERR;
      *result_size = 2;
      return;
   }

   /*
    * Get the first two bytes.  This determines what else needs
    * to be done.
    */
   clear_result_ready();
   a = read_result_register();
   *result_buffer = a;
   result_buffer++;

   /* Check for block error status result. */
   if ((a & 0xf0) == 0x50)
   {
      *result_size = 1;
      return;
   }

   b = read_result_register();
   *result_buffer = b;
   result_buffer++;
   *result_size = 2;

   /*
    * 0x20 means an error occurred.  Byte 2 will have the error code.
    * Otherwise, the command succeeded, byte 2 will have the count of
    * how many more status bytes are coming.
    *
    * The result register can be read 10 bytes at a time, a wait for
    * result ready to be asserted must be done between every 10 bytes.
    */
   if ((a & 0xf0) != 0x20)
   {
      if (b > 8)
      {
         for (i=0; i<8; i++)
         {
            *result_buffer = read_result_register();
            result_buffer++;
            (*result_size)++;
         }
         b = b - 8;

         while (b > 10)
         {
            retry_count = SONY_READY_RETRIES;
            while ((retry_count > 0) && (!is_result_ready()))
            {
               retry_count--;
            }
            if (!is_result_ready())
            {
#if DEBUG
               printk("CDU31A timeout out %d\n", __LINE__);
#endif
               result_buffer[0] = 0x20;
               result_buffer[1] = SONY_TIMEOUT_OP_ERR;
               *result_size = 2;
               return;
            }

            clear_result_ready();
                                
            for (i=0; i<10; i++)
            {
               *result_buffer = read_result_register();
               result_buffer++;
               (*result_size)++;
            }
            b = b - 10;
         }

         if (b > 0)
         {
            retry_count = SONY_READY_RETRIES;
            while ((retry_count > 0) && (!is_result_ready()))
            {
               retry_count--;
            }
            if (!is_result_ready())
            {
#if DEBUG
               printk("CDU31A timeout out %d\n", __LINE__);
#endif
               result_buffer[0] = 0x20;
               result_buffer[1] = SONY_TIMEOUT_OP_ERR;
               *result_size = 2;
               return;
            }
         }
      }

      while (b > 0)
      {
         *result_buffer = read_result_register();
         result_buffer++;
         (*result_size)++;
         b--;
      }
   }
}

/*
 * Do a command that does not involve data transfer.  This routine must
 * be re-entrant from the same task to support being called from the
 * data operation code when an error occurs.
 */
static void
do_sony_cd_cmd(unsigned char cmd,
               unsigned char *params,
               unsigned int num_params,
               unsigned char *result_buffer,
               unsigned int *result_size)
{
   unsigned int retry_count;
   int num_retries;
   int recursive_call;


   cli();
   if (current != has_cd_task) /* Allow recursive calls to this routine */
   {
      while (sony_inuse)
      {
         interruptible_sleep_on(&sony_wait);
         if (current->signal & ~current->blocked)
         {
            result_buffer[0] = 0x20;
            result_buffer[1] = SONY_SIGNAL_OP_ERR;
            *result_size = 2;
            return;
         }
      }
      sony_inuse = 1;
      has_cd_task = current;
      recursive_call = 0;
   }
   else
   {
      recursive_call = 1;
   }
   sti();

   num_retries = 0;
retry_cd_operation:

   while (handle_sony_cd_attention())
      ;
   
   retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
   while ((retry_count > jiffies) && (is_busy()))
   {
      sony_sleep();
      
      while (handle_sony_cd_attention())
         ;
   }
   if (is_busy())
   {
#if DEBUG
      printk("CDU31A timeout out %d\n", __LINE__);
#endif
      result_buffer[0] = 0x20;
      result_buffer[1] = SONY_TIMEOUT_OP_ERR;
      *result_size = 2;
   }
   else
   {
      clear_result_ready();
      clear_param_reg();

      write_params(params, num_params);
      write_cmd(cmd);

      get_result(result_buffer, result_size);
   }

   if (   ((result_buffer[0] & 0xf0) == 0x20)
       && (num_retries < MAX_CDU31A_RETRIES))
   {
      num_retries++;
      current->state = TASK_INTERRUPTIBLE;
      current->timeout = jiffies + 10; /* Wait .1 seconds on retries */
      schedule();
      goto retry_cd_operation;
   }

   if (!recursive_call)
   {
      has_cd_task = NULL;
      sony_inuse = 0;
      wake_up_interruptible(&sony_wait);
   }
}


/*
 * Handle an attention from the drive.  This will return 1 if it found one
 * or 0 if not (if one is found, the caller might want to call again).
 *
 * This routine counts the number of consecutive times it is called
 * (since this is always called from a while loop until it returns
 * a 0), and returns a 0 if it happens too many times.  This will help
 * prevent a lockup.
 */
static int
handle_sony_cd_attention(void)
{
   unsigned char atten_code;
   static int num_consecutive_attentions = 0;
   int i;


   if (is_attention())
   {
      if (num_consecutive_attentions > CDU31A_MAX_CONSECUTIVE_ATTENTIONS)
      {
         printk("cdu31a: Too many consecutive attentions: %d\n",
                num_consecutive_attentions);
         num_consecutive_attentions = 0;
         return(0);
      }

      clear_attention();
      atten_code = read_result_register();

      switch (atten_code)
      {
       /* Someone changed the CD.  Mark it as changed */
      case SONY_MECH_LOADED_ATTN:
         for (i=0; i<MAX_TRACKS; i++)
         {
            disk_changed[i] = 1;
	    if (ses_tocs[i] != NULL)
	    {
	       kfree_s(ses_tocs[i], sizeof(struct s_sony_session_toc));
	       ses_tocs[i] = NULL;
	    }
         }
         sony_audio_status = CDROM_AUDIO_NO_STATUS;
         sony_blocks_left = 0;
         break;

      case SONY_SPIN_DOWN_COMPLETE_ATTN:
         /* Mark the disk as spun down. */
         sony_spun_up = 0;
         break;

      case SONY_AUDIO_PLAY_DONE_ATTN:
         sony_audio_status = CDROM_AUDIO_COMPLETED;
         read_subcode();
         break;

      case SONY_EJECT_PUSHED_ATTN:
         sony_audio_status = CDROM_AUDIO_INVALID;
         break;

      case SONY_LEAD_IN_ERR_ATTN:
      case SONY_LEAD_OUT_ERR_ATTN:
      case SONY_DATA_TRACK_ERR_ATTN:
      case SONY_AUDIO_PLAYBACK_ERR_ATTN:
         sony_audio_status = CDROM_AUDIO_ERROR;
         break;
      }

      num_consecutive_attentions++;
      return(1);
   }

   num_consecutive_attentions = 0;
   return(0);
}


/* Convert from an integer 0-99 to BCD */
static inline unsigned int
int_to_bcd(unsigned int val)
{
   int retval;


   retval = (val / 10) << 4;
   retval = retval | val % 10;
   return(retval);
}


/* Convert from BCD to an integer from 0-99 */
static unsigned int
bcd_to_int(unsigned int bcd)
{
   return((((bcd >> 4) & 0x0f) * 10) + (bcd & 0x0f));
}


/*
 * Convert a logical sector value (like the OS would want to use for
 * a block device) to an MSF format.
 */
static void
log_to_msf(unsigned int log, unsigned char *msf)
{
   log = log + LOG_START_OFFSET;
   msf[0] = int_to_bcd(log / 4500);
   log = log % 4500;
   msf[1] = int_to_bcd(log / 75);
   msf[2] = int_to_bcd(log % 75);
}


/*
 * Convert an MSF format to a logical sector.
 */
static unsigned int
msf_to_log(unsigned char *msf)
{
   unsigned int log;


   log = bcd_to_int(msf[2]);
   log += bcd_to_int(msf[1]) * 75;
   log += bcd_to_int(msf[0]) * 4500;
   log = log - LOG_START_OFFSET;

   return log;
}


/*
 * Take in integer size value and put it into a buffer like
 * the drive would want to see a number-of-sector value.
 */
static void
size_to_buf(unsigned int size,
            unsigned char *buf)
{
   buf[0] = size / 65536;
   size = size % 65536;
   buf[1] = size / 256;
   buf[2] = size % 256;
}

/* Starts a read operation. Returns 0 on success and 1 on failure. 
   The read operation used here allows multiple sequential sectors 
   to be read and status returned for each sector.  The driver will
   read the out one at a time as the requests come and abort the
   operation if the requested sector is not the next one from the
   drive. */
static int
start_request(unsigned int sector,
              unsigned int nsect,
              int          read_nsect_only,
	      int          dev)
{
   unsigned char params[6];
   unsigned int read_size;
   unsigned int retry_count;


   log_to_msf(sector, params);
   /* If requested, read exactly what was asked. */
   if (read_nsect_only)
   {
      read_size = nsect;
   }
   /*
    * If the full read-ahead would go beyond the end of the media, trim
    * it back to read just till the end of the media.
    */
   else if ((sector + nsect) >= ses_tocs[dev]->lead_out_start_lba)
   {
      read_size = ses_tocs[dev]->lead_out_start_lba - sector;
   }
   /* Read the full readahead amount. */
   else
   {
      read_size = CDU31A_READAHEAD;
   }
   size_to_buf(read_size, &params[3]);

   /*
    * Clear any outstanding attentions and wait for the drive to
    * complete any pending operations.
    */
   while (handle_sony_cd_attention())
      ;

   retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
   while ((retry_count > jiffies) && (is_busy()))
   {
      sony_sleep();
      
      while (handle_sony_cd_attention())
         ;
   }

   if (is_busy())
   {
      printk("CDU31A: Timeout while waiting to issue command\n");
      return(1);
   }
   else
   {
      /* Issue the command */
      clear_result_ready();
      clear_param_reg();

      write_params(params, 6);
      write_cmd(SONY_READ_BLKERR_STAT_CMD);

      sony_blocks_left = read_size * 4;
      sony_next_block = sector * 4;
      readahead_dataleft = 0;
      readahead_bad = 0;
      return(0);
   }
}

/* Abort a pending read operation.  Clear all the drive status and
   readahead variables. */
static void
abort_read(void)
{
   unsigned char result_reg[2];
   int           result_size;
   volatile int  val;


   do_sony_cd_cmd(SONY_ABORT_CMD, NULL, 0, result_reg, &result_size);
   if ((result_reg[0] & 0xf0) == 0x20)
   {
      printk("CDU31A: Error aborting read, error = 0x%2.2x\n",
             result_reg[1]);
   }

   while (is_result_reg_not_empty())
   {
      val = read_result_register();
   }
   clear_data_ready();
   clear_result_ready();
   /* Clear out the data */
   while (is_data_requested())
   {
      val = read_data_register();
   }

   sony_blocks_left = 0;
   readahead_dataleft = 0;
   readahead_bad = 0;
}

/* Actually get data and status from the drive. */
static void
input_data(char         *buffer,
           unsigned int bytesleft,
           unsigned int nblocks,
           unsigned int offset,
           unsigned int skip)
{
   int i;
   volatile unsigned char val;


   /* If an XA disk on a CDU31A, skip the first 12 bytes of data from
      the disk.  The real data is after that. */
   if (sony_xa_mode)
   {
      for(i=0; i<CD_XA_HEAD; i++)
      {
         val = read_data_register();
      }
   }

   clear_data_ready();

   if (bytesleft == 2048) /* 2048 byte direct buffer transfer */
   {
      insb(sony_cd_read_reg, buffer, 2048);
      readahead_dataleft = 0;
   }
   else
   {
      /* If the input read did not align with the beginning of the block,
	 skip the necessary bytes. */
      if (skip != 0)
      {
         insb(sony_cd_read_reg, readahead_buffer, skip);
      }

      /* Get the data into the buffer. */
      insb(sony_cd_read_reg, &buffer[offset], bytesleft);

      /* Get the rest of the data into the readahead buffer at the
	 proper location. */
      readahead_dataleft = (2048 - skip) - bytesleft;
      insb(sony_cd_read_reg,
           readahead_buffer + bytesleft,
           readahead_dataleft);
   }
   sony_blocks_left -= nblocks;
   sony_next_block += nblocks; 

   /* If an XA disk, we have to clear out the rest of the unused
      error correction data. */
   if (sony_xa_mode)
   {
      for(i=0; i<CD_XA_TAIL; i++)
      {
         val = read_data_register();
      }
   }
}

/* read data from the drive.  Note the nsect must be <= 4. */
static void
read_data_block(char          *buffer,
                unsigned int  block,
                unsigned int  nblocks,
                unsigned char res_reg[],
                int           *res_size)
{
   unsigned int retry_count;
   unsigned int bytesleft;
   unsigned int offset;
   unsigned int skip;


   res_reg[0] = 0;
   res_reg[1] = 0;
   *res_size = 0;
   bytesleft = nblocks * 512;
   offset = 0;

   /* If the data in the read-ahead does not match the block offset,
      then fix things up. */
   if (((block % 4) * 512) != ((2048 - readahead_dataleft) % 2048))
   {
      sony_next_block += block % 4;
      sony_blocks_left -= block % 4;
      skip = (block % 4) * 512;
   }
   else
   {
      skip = 0;
   }

   /* We have readahead data in the buffer, get that first before we
      decide if a read is necessary. */
   if (readahead_dataleft != 0)
   {
      if (bytesleft > readahead_dataleft)
      {
	 /* The readahead will not fill the requested buffer, but
	    get the data out of the readahead into the buffer. */
         memcpy(buffer,
                readahead_buffer + (2048 - readahead_dataleft),
                readahead_dataleft);
         readahead_dataleft = 0;
         bytesleft -= readahead_dataleft;
         offset += readahead_dataleft;
      }
      else
      {
	 /* The readahead will fill the whole buffer, get the data
	    and return. */
         memcpy(buffer,
                readahead_buffer + (2048 - readahead_dataleft),
                bytesleft);
         readahead_dataleft -= bytesleft;
         bytesleft = 0;
         sony_blocks_left -= nblocks;
         sony_next_block += nblocks;

	 /* If the data in the readahead is bad, return an error so the
	    driver will abort the buffer. */
         if (readahead_bad)
         {
            res_reg[0] = 0x20;
            res_reg[1] = SONY_BAD_DATA_ERR;
            *res_size = 2;
         }

         if (readahead_dataleft == 0)
         {
            readahead_bad = 0;
         }

         /* Final transfer is done for read command, get final result. */
         if (sony_blocks_left == 0)
         {
            get_result(res_reg, res_size);
         }
         return;
      }
   }

   /* Wait for the drive to tell us we have something */
   retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
   while ((retry_count > jiffies) && !(is_data_ready()))
   {
      while (handle_sony_cd_attention())
         ;

      sony_sleep();
   }
   if (!(is_data_ready()))
   {
      if (is_result_ready())
      {
         get_result(res_reg, res_size);
         if ((res_reg[0] & 0xf0) != 0x20)
         {
            printk("CDU31A: Got result that should have been error: %d\n",
                   res_reg[0]);
            res_reg[0] = 0x20;
            res_reg[1] = SONY_BAD_DATA_ERR;
            *res_size = 2;
         }
         abort_read();
      }
      else
      {
#if DEBUG
         printk("CDU31A timeout out %d\n", __LINE__);
#endif
         res_reg[0] = 0x20;
         res_reg[1] = SONY_TIMEOUT_OP_ERR;
         *res_size = 2;
         abort_read();
      }
   }
   else
   {
      input_data(buffer, bytesleft, nblocks, offset, skip);

      /* Wait for the status from the drive. */
      retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
      while ((retry_count > jiffies) && !(is_result_ready()))
      {
         while (handle_sony_cd_attention())
            ;

         sony_sleep();
      }

      if (!is_result_ready())
      {
#if DEBUG
         printk("CDU31A timeout out %d\n", __LINE__);
#endif
         res_reg[0] = 0x20;
         res_reg[1] = SONY_TIMEOUT_OP_ERR;
         *res_size = 2;
         abort_read();
      }
      else
      {
         get_result(res_reg, res_size);

	 /* If we got a buffer status, handle that. */
         if ((res_reg[0] & 0xf0) == 0x50)
         {

            if (   (res_reg[0] == SONY_NO_CIRC_ERR_BLK_STAT)
                || (res_reg[0] == SONY_NO_LECC_ERR_BLK_STAT)
                || (res_reg[0] == SONY_RECOV_LECC_ERR_BLK_STAT))
            {
	       /* The data was successful, but if data was read from
		  the readahead  and it was bad, set the whole
		  buffer as bad. */
               if (readahead_bad)
               {
                  readahead_bad = 0;
                  res_reg[0] = 0x20;
                  res_reg[1] = SONY_BAD_DATA_ERR;
                  *res_size = 2;
               }
            }
            else
            {
               printk("CDU31A: Data block error: 0x%x\n", res_reg[0]);
               res_reg[0] = 0x20;
               res_reg[1] = SONY_BAD_DATA_ERR;
               *res_size = 2;

               /* Data is in the readahead buffer but an error was returned.
                  Make sure future requests don't use the data. */
               if (bytesleft != 2048)
               {
                  readahead_bad = 1;
               }
            }

            /* Final transfer is done for read command, get final result. */
            if (sony_blocks_left == 0)
            {
               get_result(res_reg, res_size);
            }
         }
         else if ((res_reg[0] & 0xf0) != 0x20)
         {
            /* The drive gave me bad status, I don't know what to do.
               Reset the driver and return an error. */
            printk("CDU31A: Invalid block status: 0x%x\n", res_reg[0]);
            restart_on_error();
            res_reg[0] = 0x20;
            res_reg[1] = SONY_BAD_DATA_ERR;
            *res_size = 2;
         }
      }
   }
}

/*
 * The OS calls this to perform a read or write operation to the drive.
 * Write obviously fail.  Reads to a read ahead of sony_buffer_size
 * bytes to help speed operations.  This especially helps since the OS
 * uses 1024 byte blocks and the drive uses 2048 byte blocks.  Since most
 * data access on a CD is done sequentially, this saves a lot of operations.
 */
static void
do_cdu31a_request(void)
{
   int block;
   unsigned int dev;
   int nblock;
   unsigned char res_reg[12];
   unsigned int res_size;
   int num_retries;


   /* 
    * Make sure no one else is using the driver; wait for them
    * to finish if it is so.
    */
   cli();
   while (sony_inuse)
   {
      interruptible_sleep_on(&sony_wait);
      if (current->signal & ~current->blocked)
      {
         return;
      }
   }
   sony_inuse = 1;
   has_cd_task = current;
   sti();

   /* Get drive status before doing anything. */
   while (handle_sony_cd_attention())
      ;

   while (1)
   {
cdu31a_request_startover:
      /*
       * The beginning here is stolen from the hard disk driver.  I hope
       * its right.
       */
      if (!(CURRENT) || CURRENT->dev < 0)
      {
         goto end_do_cdu31a_request;
      }

      if (!sony_spun_up)
      {
         struct inode in;

         /* This is a kludge to get a valid dev in an inode that
            scd_open can take.  That's the only thing scd_open()
            uses the inode for. */
         in.i_rdev = CURRENT->dev;
         scd_open(&in,NULL);
      }

      /* I don't use INIT_REQUEST because it calls return, which would
         return without unlocking the device.  It shouldn't matter,
         but just to be safe... */
      if (MAJOR(CURRENT->dev) != MAJOR_NR)
      {
	 panic(DEVICE_NAME ": request list destroyed");
      }
      if (CURRENT->bh)
      {
	 if (!CURRENT->bh->b_lock)
         {
	    panic(DEVICE_NAME ": block not locked");
         }
      }

      dev = MINOR(CURRENT->dev);
      block = CURRENT->sector;
      nblock = CURRENT->nr_sectors;

      /* Check for multi-session disks. */
      if (dev > MAX_TRACKS)
      {
	 printk("CDU31A: Invalid device request: %d\n", dev);
	 end_request(0);
	 goto cdu31a_request_startover;
      }
      else if (ses_tocs[dev] == NULL)
      {
	 printk("CDU31A: session TOC not read: %d\n", dev);
	 end_request(0);
	 goto cdu31a_request_startover;
      }

      /* Check for base read of multi-session disk. */
      if ((dev != 0) && (block == 64))
      {
         if (ses_tocs[dev]->first_track_num == ses_tocs[dev]->last_track_num)
         {
            printk("CDU31A: Not a multi-session disk: %d\n", dev);
            end_request(0);
            goto cdu31a_request_startover;
         }

	 /* Offset the request into the session. */
	 block += (ses_tocs[dev]->start_track_lba * 4);
      }

      switch(CURRENT->cmd)
      {
      case READ:
         /*
          * If the block address is invalid or the request goes beyond the end of
          * the media, return an error.
          */
	 if ((block / 4) < ses_tocs[dev]->start_track_lba)
	 {
            printk("CDU31A: Request before beginning of media\n");
            end_request(0);
            goto cdu31a_request_startover;
	 }
         if ((block / 4) >= ses_tocs[dev]->lead_out_start_lba)
         {
            printk("CDU31A: Request past end of media\n");
            end_request(0);
            goto cdu31a_request_startover;
         }
         if (((block + nblock) / 4) >= ses_tocs[dev]->lead_out_start_lba)
         {
            printk("CDU31A: Request past end of media\n");
            end_request(0);
            goto cdu31a_request_startover;
         }

         num_retries = 0;

try_read_again:
         while (handle_sony_cd_attention())
            ;

         if (ses_tocs[dev] == NULL)
         {
	    printk("CDU31A: session TOC not read: %d\n", dev);
	    end_request(0);
	    goto cdu31a_request_startover;
         }

	 /* If no data is left to be read from the drive, start the
	    next request. */
         if (sony_blocks_left == 0)
         {
            if (start_request(block / 4, CDU31A_READAHEAD / 4, 0, dev))
            {
               end_request(0);
               goto cdu31a_request_startover;
            }
         }
	 /* If the requested block is not the next one waiting in
	    the driver, abort the current operation and start a
	    new one. */
         else if (block != sony_next_block)
         {
#if DEBUG
            printk("CDU31A Warning: Read for block %d, expected %d\n",
                   block,
                   sony_next_block);
#endif
            abort_read();
            if (ses_tocs[dev] == NULL)
            {
	       printk("CDU31A: session TOC not read: %d\n", dev);
	       end_request(0);
               goto cdu31a_request_startover;
            }
            else if (start_request(block / 4, CDU31A_READAHEAD / 4, 0, dev))
            {
               end_request(0);
               goto cdu31a_request_startover;
            }
         }

         read_data_block(CURRENT->buffer, block, nblock, res_reg, &res_size);
         if (res_reg[0] == 0x20)
         {
            if (num_retries > MAX_CDU31A_RETRIES)
            {
               end_request(0);
               goto cdu31a_request_startover;
            }

            num_retries++;
            if (res_reg[1] == SONY_NOT_SPIN_ERR)
            {
               do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
            }
            else
            {
               printk("CDU31A: Read error: 0x%2.2x\n", res_reg[1]);
            }
            goto try_read_again;
         }
         else
         {
            end_request(1);
         }
         break;
            
      case WRITE:
         end_request(0);
         break;
            
      default:
         panic("CDU31A: Unknown cmd");
      }
   }

end_do_cdu31a_request:
#if 0
   /* After finished, cancel any pending operations. */
   abort_read();
#endif

   has_cd_task = NULL;
   sony_inuse = 0;
   wake_up_interruptible(&sony_wait);
}

/* Copy overlapping buffers. */
static void
mcovlp(char *dst,
       char *src,
       int  size)
{
   src += (size - 1);
   dst += (size - 1);
   while (size > 0)
   {
      *dst = *src;
      size--;
      dst--;
      src--;
   }
}


/*
 * Read the table of contents from the drive and set up TOC if
 * successful.
 */
static void
sony_get_toc(int dev)
{
   unsigned int res_size;
   unsigned char parms[1];


   if (dev >= MAX_TRACKS)
   {
      printk("CDU31A: Request for invalid TOC track: %d\n", dev);
   }
   else if (ses_tocs[dev] == NULL)
   {
      ses_tocs[dev] = kmalloc(sizeof(struct s_sony_session_toc), 0);
      if (ses_tocs[dev] == NULL)
      {
	 printk("CDU31A: Unable to alloc mem for TOC\n");
      }
      else
      {
	 parms[0] = dev+1;
	 do_sony_cd_cmd(SONY_REQ_TOC_DATA_SPEC_CMD,
			parms,
			1, 
			(unsigned char *) ses_tocs[dev], 
			&res_size);
	 if ((res_size < 2) || ((ses_tocs[dev]->exec_status[0] & 0xf0) == 0x20))
	 {
	    kfree_s(ses_tocs[dev], sizeof(struct s_sony_session_toc));
	    ses_tocs[dev] = NULL;
	    return;
	 }

         /* For points that do not exist, move the data over them
            to the right location. */
         if (ses_tocs[dev]->pointb0 != 0xb0)
         {
            mcovlp(((char *) ses_tocs[dev]) + 27,
                   ((char *) ses_tocs[dev]) + 18,
                   res_size - 18);
            res_size += 9;
         }
         if (ses_tocs[dev]->pointb1 != 0xb1)
         {
            mcovlp(((char *) ses_tocs[dev]) + 36,
                   ((char *) ses_tocs[dev]) + 27,
                   res_size - 27);
            res_size += 9;
         }
         if (ses_tocs[dev]->pointb2 != 0xb2)
         {
            mcovlp(((char *) ses_tocs[dev]) + 45,
                   ((char *) ses_tocs[dev]) + 36,
                   res_size - 36);
            res_size += 9;
         }
         if (ses_tocs[dev]->pointb3 != 0xb3)
         {
            mcovlp(((char *) ses_tocs[dev]) + 54,
                   ((char *) ses_tocs[dev]) + 45,
                   res_size - 45);
            res_size += 9;
         }
         if (ses_tocs[dev]->pointb4 != 0xb4)
         {
            mcovlp(((char *) ses_tocs[dev]) + 63,
                   ((char *) ses_tocs[dev]) + 54,
                   res_size - 54);
            res_size += 9;
         }
         if (ses_tocs[dev]->pointc0 != 0xc0)
         {
            mcovlp(((char *) ses_tocs[dev]) + 72,
                   ((char *) ses_tocs[dev]) + 63,
                   res_size - 63);
            res_size += 9;
         }

	 ses_tocs[dev]->start_track_lba = msf_to_log(ses_tocs[dev]->tracks[0].track_start_msf);
	 ses_tocs[dev]->lead_out_start_lba = msf_to_log(ses_tocs[dev]->lead_out_start_msf);
#if DEBUG
	 printk("Disk session %d, start track: %d, stop track: %d\n",
		dev,
		ses_tocs[dev]->start_track_lba,
		ses_tocs[dev]->lead_out_start_lba);
#endif
      }
   }
}


/*
 * Search for a specific track in the table of contents.
 */
static int
find_track(int track,
	   int dev)
{
   int i;
   int num_tracks;


   num_tracks = ses_tocs[dev]->last_track_num - ses_tocs[dev]->first_track_num + 1;
   for (i = 0; i < num_tracks; i++)
   {
      if (ses_tocs[dev]->tracks[i].track == track)
      {
         return i;
      }
   }

   return -1;
}


/*
 * Read the subcode and put it int last_sony_subcode for future use.
 */
static int
read_subcode(void)
{
   unsigned int res_size;


   do_sony_cd_cmd(SONY_REQ_SUBCODE_ADDRESS_CMD,
                  NULL,
                  0, 
                  (unsigned char *) last_sony_subcode, 
                  &res_size);
   if ((res_size < 2) || ((last_sony_subcode->exec_status[0] & 0xf0) == 0x20))
   {
      printk("Sony CDROM error 0x%2.2x (read_subcode)\n",
             last_sony_subcode->exec_status[1]);
      return -EIO;
   }

   return 0;
}


/*
 * Get the subchannel info like the CDROMSUBCHNL command wants to see it.  If
 * the drive is playing, the subchannel needs to be read (since it would be
 * changing).  If the drive is paused or completed, the subcode information has
 * already been stored, just use that.  The ioctl call wants things in decimal
 * (not BCD), so all the conversions are done.
 */
static int
sony_get_subchnl_info(long arg,
		      int  dev)
{
   struct cdrom_subchnl schi;


   /* Get attention stuff */
   while (handle_sony_cd_attention())
      ;

   sony_get_toc(dev);
   if (ses_tocs[dev] == NULL)
   {
      return -EIO;
   }

   verify_area(VERIFY_READ, (char *) arg, sizeof(schi));
   verify_area(VERIFY_WRITE, (char *) arg, sizeof(schi));

   memcpy_fromfs(&schi, (char *) arg, sizeof(schi));
   
   switch (sony_audio_status)
   {
   case CDROM_AUDIO_PLAY:
      if (read_subcode() < 0)
      {
         return -EIO;
      }
      break;

   case CDROM_AUDIO_PAUSED:
   case CDROM_AUDIO_COMPLETED:
      break;

   case CDROM_AUDIO_NO_STATUS:
      schi.cdsc_audiostatus = sony_audio_status;
      memcpy_tofs((char *) arg, &schi, sizeof(schi));
      return 0;
      break;

   case CDROM_AUDIO_INVALID:
   case CDROM_AUDIO_ERROR:
   default:
      return -EIO;
   }

   schi.cdsc_audiostatus = sony_audio_status;
   schi.cdsc_adr = last_sony_subcode->address;
   schi.cdsc_ctrl = last_sony_subcode->control;
   schi.cdsc_trk = bcd_to_int(last_sony_subcode->track_num);
   schi.cdsc_ind = bcd_to_int(last_sony_subcode->index_num);
   if (schi.cdsc_format == CDROM_MSF)
   {
      schi.cdsc_absaddr.msf.minute = bcd_to_int(last_sony_subcode->abs_msf[0]);
      schi.cdsc_absaddr.msf.second = bcd_to_int(last_sony_subcode->abs_msf[1]);
      schi.cdsc_absaddr.msf.frame = bcd_to_int(last_sony_subcode->abs_msf[2]);

      schi.cdsc_reladdr.msf.minute = bcd_to_int(last_sony_subcode->rel_msf[0]);
      schi.cdsc_reladdr.msf.second = bcd_to_int(last_sony_subcode->rel_msf[1]);
      schi.cdsc_reladdr.msf.frame = bcd_to_int(last_sony_subcode->rel_msf[2]);
   }
   else if (schi.cdsc_format == CDROM_LBA)
   {
      schi.cdsc_absaddr.lba = msf_to_log(last_sony_subcode->abs_msf);
      schi.cdsc_reladdr.lba = msf_to_log(last_sony_subcode->rel_msf);
   }
   
   memcpy_tofs((char *) arg, &schi, sizeof(schi));
   return 0;
}

/* Get audio data from the drive.  This is fairly complex because I
   am looking for status and data at the same time, but if I get status
   then I just look for data.  I need to get the status immediately so
   the switch from audio to data tracks will happen quickly. */
static void
read_audio_data(char          *buffer,
                unsigned char res_reg[],
                int           *res_size)
{
   unsigned int retry_count;
   int result_read;


   res_reg[0] = 0;
   res_reg[1] = 0;
   *res_size = 0;
   result_read = 0;

   /* Wait for the drive to tell us we have something */
   retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
continue_read_audio_wait:
   while (   (retry_count > jiffies)
          && !(is_data_ready())
          && !(is_result_ready() || result_read))
   {
      while (handle_sony_cd_attention())
         ;

      sony_sleep();
   }
   if (!(is_data_ready()))
   {
      if (is_result_ready() && !result_read)
      {
         get_result(res_reg, res_size);

         /* Read block status and continue waiting for data. */
         if ((res_reg[0] & 0xf0) == 0x50)
         {
            result_read = 1;
            goto continue_read_audio_wait;
         }
         /* Invalid data from the drive.  Shut down the operation. */
         else if ((res_reg[0] & 0xf0) != 0x20)
         {
            printk("CDU31A: Got result that should have been error: %d\n",
                   res_reg[0]);
            res_reg[0] = 0x20;
            res_reg[1] = SONY_BAD_DATA_ERR;
            *res_size = 2;
         }
         abort_read();
      }
      else
      {
#if DEBUG
         printk("CDU31A timeout out %d\n", __LINE__);
#endif
         res_reg[0] = 0x20;
         res_reg[1] = SONY_TIMEOUT_OP_ERR;
         *res_size = 2;
         abort_read();
      }
   }
   else
   {
      clear_data_ready();

      /* If data block, then get 2340 bytes offset by 12. */
      if (sony_raw_data_mode)
      {
         insb(sony_cd_read_reg, buffer + CD_XA_HEAD, CD_FRAMESIZE_XA);
      }
      else
      {
         /* Audio gets the whole 2352 bytes. */
         insb(sony_cd_read_reg, buffer, CD_FRAMESIZE_RAW);
      }

      /* If I haven't already gotten the result, get it now. */
      if (!result_read)
      {
         /* Wait for the drive to tell us we have something */
         retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
         while ((retry_count > jiffies) && !(is_result_ready()))
         {
            while (handle_sony_cd_attention())
               ;

            sony_sleep();
         }

         if (!is_result_ready())
         {
#if DEBUG
            printk("CDU31A timeout out %d\n", __LINE__);
#endif
            res_reg[0] = 0x20;
            res_reg[1] = SONY_TIMEOUT_OP_ERR;
            *res_size = 2;
            abort_read();
            return;
         }
         else
         {
            get_result(res_reg, res_size);
         }
      }

      if ((res_reg[0] & 0xf0) == 0x50)
      {
         if (   (res_reg[0] == SONY_NO_CIRC_ERR_BLK_STAT)
             || (res_reg[0] == SONY_NO_LECC_ERR_BLK_STAT)
             || (res_reg[0] == SONY_RECOV_LECC_ERR_BLK_STAT)
             || (res_reg[0] == SONY_NO_ERR_DETECTION_STAT))
         {
            /* Ok, nothing to do. */
         }
         else
         {
            printk("CDU31A: Data block error: 0x%x\n", res_reg[0]);
            res_reg[0] = 0x20;
            res_reg[1] = SONY_BAD_DATA_ERR;
            *res_size = 2;
         }
      }
      else if ((res_reg[0] & 0xf0) != 0x20)
      {
         /* The drive gave me bad status, I don't know what to do.
            Reset the driver and return an error. */
         printk("CDU31A: Invalid block status: 0x%x\n", res_reg[0]);
         restart_on_error();
         res_reg[0] = 0x20;
         res_reg[1] = SONY_BAD_DATA_ERR;
         *res_size = 2;
      }
   }
}

/* Perform a raw data read.  This will automatically detect the
   track type and read the proper data (audio or data). */
static int
read_audio(struct cdrom_read_audio *ra,
           struct inode            *inode)
{
   int retval;
   unsigned char params[2];
   unsigned char res_reg[12];
   unsigned int res_size;
   unsigned int cframe;

   /* 
    * Make sure no one else is using the driver; wait for them
    * to finish if it is so.
    */
   cli();
   while (sony_inuse)
   {
      interruptible_sleep_on(&sony_wait);
      if (current->signal & ~current->blocked)
      {
         return -EAGAIN;
      }
   }
   sony_inuse = 1;
   has_cd_task = current;
   sti();

   if (!sony_spun_up)
   {
      scd_open (inode, NULL);
   }

   /* Set the drive to do raw operations. */
   params[0] = SONY_SD_DECODE_PARAM;
   params[1] = 0x06 | sony_raw_data_mode;
   do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
                  params,
                  2,
                  res_reg,
                  &res_size);
   if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20))
   {
      printk("CDU31A: Unable to set decode params: 0x%2.2x\n", res_reg[1]);
      return -EIO;
   }

   /* From here down, we have to goto exit_read_audio instead of returning
      because the drive parameters have to be set back to data before
      return. */

   retval = 0;
   /* start_request clears out any readahead data, so it should be safe. */
   if (start_request(ra->addr.lba, ra->nframes, 1, 0))
   {
      retval = -EIO;
      goto exit_read_audio;
   }

   /* For every requested frame. */
   cframe = 0;
   while (cframe < ra->nframes)
   {
      read_audio_data(readahead_buffer, res_reg, &res_size);
      if ((res_reg[0] & 0xf0) == 0x20)
      {
         if (res_reg[1] == SONY_BAD_DATA_ERR)
         {
            printk("CDU31A: Data error on audio sector %d\n",
                   ra->addr.lba + cframe);
         }
         else if (res_reg[1] == SONY_ILL_TRACK_R_ERR)
         {
            /* Illegal track type, change track types and start over. */
            sony_raw_data_mode = (sony_raw_data_mode) ? 0 : 1;

            /* Set the drive mode. */
            params[0] = SONY_SD_DECODE_PARAM;
            params[1] = 0x06 | sony_raw_data_mode;
            do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
                           params,
                           2,
                           res_reg,
                           &res_size);
            if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20))
            {
               printk("CDU31A: Unable to set decode params: 0x%2.2x\n", res_reg[1]);
               retval = -EIO;
               goto exit_read_audio;
            }

            /* Restart the request on the current frame. */
            if (start_request(ra->addr.lba + cframe, ra->nframes - cframe, 1, 0))
            {
               retval = -EIO;
               goto exit_read_audio;
            }

            /* Don't go back to the top because don't want to get into
               and infinite loop.  A lot of code gets duplicated, but
               that's no big deal, I don't guess. */
            read_audio_data(readahead_buffer, res_reg, &res_size);
            if ((res_reg[0] & 0xf0) == 0x20)
            {
               if (res_reg[1] == SONY_BAD_DATA_ERR)
               {
                  printk("CDU31A: Data error on audio sector %d\n",
                         ra->addr.lba + cframe);
               }
               else
               {
                  printk("CDU31A: Error reading audio data on sector %d: 0x%x\n",
                         ra->addr.lba + cframe,
                         res_reg[1]);
                  retval = -EIO;
                  goto exit_read_audio;
               }
            }
            else
            {
               memcpy_tofs((char *) (ra->buf + (CD_FRAMESIZE_RAW * cframe)),
                           (char *) readahead_buffer,
                           CD_FRAMESIZE_RAW);
            }
         }
         else
         {
            printk("CDU31A: Error reading audio data on sector %d: 0x%x\n",
                   ra->addr.lba + cframe,
                   res_reg[1]);
            retval = -EIO;
            goto exit_read_audio;
         }
      }
      else
      {
         memcpy_tofs((char *) (ra->buf + (CD_FRAMESIZE_RAW * cframe)),
                     (char *) readahead_buffer,
                     CD_FRAMESIZE_RAW);
      }

      cframe++;
   }

   get_result(res_reg, &res_size);
   if ((res_reg[0] & 0xf0) == 0x20)
   {
      printk("CDU31A: Error return from audio read: 0x%x\n",
             res_reg[1]);
      retval = -EIO;
      goto exit_read_audio;
   }

exit_read_audio:

   /* Set the drive mode back to the proper one for the disk. */
   params[0] = SONY_SD_DECODE_PARAM;
   if (!sony_xa_mode)
   {
      params[1] = 0x0f;
   }
   else
   {
      params[1] = 0x07;
   }
   do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
                  params,
                  2,
                  res_reg,
                  &res_size);
   if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20))
   {
      printk("CDU31A: Unable to reset decode params: 0x%2.2x\n", res_reg[1]);
      return -EIO;
   }

   has_cd_task = NULL;
   sony_inuse = 0;
   wake_up_interruptible(&sony_wait);

   return(retval);
}

/*
 * The big ugly ioctl handler.
 */
static int
scd_ioctl(struct inode *inode,
          struct file  *file,
          unsigned int  cmd,
          unsigned long arg)
{
   unsigned int dev;
   unsigned char res_reg[12];
   unsigned int res_size;
   unsigned char params[7];
   int i;


   if (!inode)
   {
      return -EINVAL;
   }
   dev = MINOR(inode->i_rdev);
   if (dev > MAX_TRACKS)
   {
      return -EINVAL;
   }

   switch (cmd)
   {
   case CDROMSTART:     /* Spin up the drive */
      do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
      if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20))
      {
         printk("Sony CDROM error 0x%2.2x (CDROMSTART)\n", res_reg[1]);
         return -EIO;
      }
      return 0;
      break;
      
   case CDROMSTOP:      /* Spin down the drive */
      do_sony_cd_cmd(SONY_AUDIO_STOP_CMD, NULL, 0, res_reg, &res_size);

      /*
       * Spin the drive down, ignoring the error if the disk was
       * already not spinning.
       */
      sony_audio_status = CDROM_AUDIO_NO_STATUS;
      do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size);
      if (   ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20))
          && (res_reg[1] != SONY_NOT_SPIN_ERR))
      {
         printk("Sony CDROM error 0x%2.2x (CDROMSTOP)\n", res_reg[1]);
         return -EIO;
      }
      
      return 0;
      break;

   case CDROMPAUSE:     /* Pause the drive */
      do_sony_cd_cmd(SONY_AUDIO_STOP_CMD, NULL, 0, res_reg, &res_size);
      if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20))
      {
         printk("Sony CDROM error 0x%2.2x (CDROMPAUSE)\n", res_reg[1]);
         return -EIO;
      }

      /* Get the current position and save it for resuming */
      if (read_subcode() < 0)
      {
         return -EIO;
      }
      cur_pos_msf[0] = last_sony_subcode->abs_msf[0];
      cur_pos_msf[1] = last_sony_subcode->abs_msf[1];
      cur_pos_msf[2] = last_sony_subcode->abs_msf[2];
      sony_audio_status = CDROM_AUDIO_PAUSED;
      return 0;
      break;

   case CDROMRESUME:    /* Start the drive after being paused */
      if (sony_audio_status != CDROM_AUDIO_PAUSED)
      {
         return -EINVAL;
      }
      
      do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
      
      /* Start the drive at the saved position. */
      params[1] = cur_pos_msf[0];
      params[2] = cur_pos_msf[1];
      params[3] = cur_pos_msf[2];
      params[4] = final_pos_msf[0];
      params[5] = final_pos_msf[1];
      params[6] = final_pos_msf[2];
      params[0] = 0x03;
      do_sony_cd_cmd(SONY_AUDIO_PLAYBACK_CMD, params, 7, res_reg, &res_size);
      if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20))
      {
         printk("Sony CDROM error 0x%2.2x (CDROMRESUME)\n", res_reg[1]);
         return -EIO;
      }
      sony_audio_status = CDROM_AUDIO_PLAY;
      return 0;
      break;

   case CDROMPLAYMSF:   /* Play starting at the given MSF address. */
      verify_area(VERIFY_READ, (char *) arg, 6);
      do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
      memcpy_fromfs(&(params[1]), (void *) arg, 6);
      
      /* The parameters are given in int, must be converted */
      for (i=1; i<7; i++)
      {
         params[i] = int_to_bcd(params[i]);
      }
      params[0] = 0x03;
      do_sony_cd_cmd(SONY_AUDIO_PLAYBACK_CMD, params, 7, res_reg, &res_size);
      if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20))
      {
         printk("Sony CDROM error 0x%2.2x (CDROMPLAYMSF)\n", res_reg[1]);
         return -EIO;
      }
      
      /* Save the final position for pauses and resumes */
      final_pos_msf[0] = params[4];
      final_pos_msf[1] = params[5];
      final_pos_msf[2] = params[6];
      sony_audio_status = CDROM_AUDIO_PLAY;
      return 0;
      break;

   case CDROMREADTOCHDR:        /* Read the table of contents header */
      {
         struct cdrom_tochdr *hdr;
         struct cdrom_tochdr loc_hdr;
         
         sony_get_toc(dev);
         if (ses_tocs[dev] == NULL)
         {
            return -EIO;
         }
         
         hdr = (struct cdrom_tochdr *) arg;
         verify_area(VERIFY_WRITE, hdr, sizeof(*hdr));
         loc_hdr.cdth_trk0 = bcd_to_int(ses_tocs[dev]->first_track_num);
         loc_hdr.cdth_trk1 = bcd_to_int(ses_tocs[dev]->last_track_num);
         memcpy_tofs(hdr, &loc_hdr, sizeof(*hdr));
      }
      return 0;
      break;

   case CDROMREADTOCENTRY:      /* Read a given table of contents entry */
      {
         struct cdrom_tocentry *entry;
         struct cdrom_tocentry loc_entry;
         int track_idx;
         unsigned char *msf_val = NULL;
         
         sony_get_toc(dev);
         if (ses_tocs[dev] == NULL)
         {
            return -EIO;
         }
         
         entry = (struct cdrom_tocentry *) arg;
         verify_area(VERIFY_READ, entry, sizeof(*entry));
         verify_area(VERIFY_WRITE, entry, sizeof(*entry));
         
         memcpy_fromfs(&loc_entry, entry, sizeof(loc_entry));
         
         /* Lead out is handled separately since it is special. */
         if (loc_entry.cdte_track == CDROM_LEADOUT)
         {
            loc_entry.cdte_adr = ses_tocs[dev]->address2;
            loc_entry.cdte_ctrl = ses_tocs[dev]->control2;
            msf_val = ses_tocs[dev]->lead_out_start_msf;
         }
         else
         {
            track_idx = find_track(int_to_bcd(loc_entry.cdte_track), dev);
            if (track_idx < 0)
            {
               return -EINVAL;
            }
            
            loc_entry.cdte_adr = ses_tocs[dev]->tracks[track_idx].address;
            loc_entry.cdte_ctrl = ses_tocs[dev]->tracks[track_idx].control;
            msf_val = ses_tocs[dev]->tracks[track_idx].track_start_msf;
         }
         
         /* Logical buffer address or MSF format requested? */
         if (loc_entry.cdte_format == CDROM_LBA)
         {
            loc_entry.cdte_addr.lba = msf_to_log(msf_val);
         }
         else if (loc_entry.cdte_format == CDROM_MSF)
         {
            loc_entry.cdte_addr.msf.minute = bcd_to_int(*msf_val);
            loc_entry.cdte_addr.msf.second = bcd_to_int(*(msf_val+1));
            loc_entry.cdte_addr.msf.frame = bcd_to_int(*(msf_val+2));
         }
         memcpy_tofs(entry, &loc_entry, sizeof(*entry));
      }
      return 0;
      break;

   case CDROMPLAYTRKIND:     /* Play a track.  This currently ignores index. */
      {
         struct cdrom_ti ti;
         int track_idx;
         
         sony_get_toc(dev);
         if (ses_tocs[dev] == NULL)
         {
            return -EIO;
         }
         
         verify_area(VERIFY_READ, (char *) arg, sizeof(ti));
         
         memcpy_fromfs(&ti, (char *) arg, sizeof(ti));
         if (   (ti.cdti_trk0 < ses_tocs[dev]->first_track_num)
             || (ti.cdti_trk0 > ses_tocs[dev]->last_track_num)
             || (ti.cdti_trk1 < ti.cdti_trk0))
         {
            return -EINVAL;
         }
         
         track_idx = find_track(int_to_bcd(ti.cdti_trk0), dev);
         if (track_idx < 0)
         {
            return -EINVAL;
         }
         params[1] = ses_tocs[dev]->tracks[track_idx].track_start_msf[0];
         params[2] = ses_tocs[dev]->tracks[track_idx].track_start_msf[1];
         params[3] = ses_tocs[dev]->tracks[track_idx].track_start_msf[2];
         
         /*
          * If we want to stop after the last track, use the lead-out
          * MSF to do that.
          */
         if (ti.cdti_trk1 >= bcd_to_int(ses_tocs[dev]->last_track_num))
         {
            log_to_msf(msf_to_log(ses_tocs[dev]->lead_out_start_msf)-1,
                       &(params[4]));
         }
         else
         {
            track_idx = find_track(int_to_bcd(ti.cdti_trk1+1), dev);
            if (track_idx < 0)
            {
               return -EINVAL;
            }
            log_to_msf(msf_to_log(ses_tocs[dev]->tracks[track_idx].track_start_msf)-1,
                       &(params[4]));
         }
         params[0] = 0x03;
         
         do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
         
         do_sony_cd_cmd(SONY_AUDIO_PLAYBACK_CMD, params, 7, res_reg, &res_size);
         if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20))
         {
            printk("Params: %x %x %x %x %x %x %x\n", params[0], params[1],
                   params[2], params[3], params[4], params[5], params[6]);
            printk("Sony CDROM error 0x%2.2x (CDROMPLAYTRKIND\n", res_reg[1]);
            return -EIO;
         }
         
         /* Save the final position for pauses and resumes */
         final_pos_msf[0] = params[4];
         final_pos_msf[1] = params[5];
         final_pos_msf[2] = params[6];
         sony_audio_status = CDROM_AUDIO_PLAY;
         return 0;
      }
     
   case CDROMSUBCHNL:   /* Get subchannel info */
      return sony_get_subchnl_info(arg, dev);

   case CDROMVOLCTRL:   /* Volume control.  What volume does this change, anyway? */
      {
         struct cdrom_volctrl volctrl;
         
         verify_area(VERIFY_READ, (char *) arg, sizeof(volctrl));
         
         memcpy_fromfs(&volctrl, (char *) arg, sizeof(volctrl));
         params[0] = SONY_SD_AUDIO_VOLUME;
         params[1] = volctrl.channel0;
         params[2] = volctrl.channel1;
         do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD, params, 3, res_reg, &res_size);
         if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20))
         {
            printk("Sony CDROM error 0x%2.2x (CDROMVOLCTRL)\n", res_reg[1]);
            return -EIO;
         }
      }
      return 0;

   case CDROMEJECT:     /* Eject the drive */
      do_sony_cd_cmd(SONY_AUDIO_STOP_CMD, NULL, 0, res_reg, &res_size);
      do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size);

      sony_audio_status = CDROM_AUDIO_INVALID;
      do_sony_cd_cmd(SONY_EJECT_CMD, NULL, 0, res_reg, &res_size);
      if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20))
      {
         printk("Sony CDROM error 0x%2.2x (CDROMEJECT)\n", res_reg[1]);
         return -EIO;
      }
      return 0;
      break;
     
    case CDROMREADAUDIO:      /* Read 2352 byte audio tracks and 2340 byte
				 raw data tracks. */
      {
         struct cdrom_read_audio ra;


         sony_get_toc(dev);
         if (ses_tocs[dev] == NULL)
         {
            return -EIO;
         }
         
         verify_area(VERIFY_READ, (char *) arg, sizeof(ra));
         memcpy_fromfs(&ra, (char *) arg, sizeof(ra));

         verify_area(VERIFY_WRITE, ra.buf, CD_FRAMESIZE_RAW * ra.nframes);

         if (ra.addr_format == CDROM_LBA)
         {
            if (   (ra.addr.lba >= ses_tocs[dev]->lead_out_start_lba)
                || (ra.addr.lba + ra.nframes >= ses_tocs[dev]->lead_out_start_lba))
            {
               return -EINVAL;
            }
         }
         else if (ra.addr_format == CDROM_MSF)
         {
            if (   (ra.addr.msf.minute >= 75)
                || (ra.addr.msf.second >= 60)
                || (ra.addr.msf.frame >= 75))
            {
               return -EINVAL;
            }

            ra.addr.lba = (  (ra.addr.msf.minute * 4500)
                           + (ra.addr.msf.second * 75)
                           + ra.addr.msf.frame);
            if (   (ra.addr.lba >= ses_tocs[dev]->lead_out_start_lba)
                || (ra.addr.lba + ra.nframes >= ses_tocs[dev]->lead_out_start_lba))
            {
               return -EINVAL;
            }

            /* I know, this can go negative on an unsigned.  However,
               the first thing done to the data is to add this value,
               so this should compensate and allow direct msf access. */
            ra.addr.lba -= LOG_START_OFFSET;
         }
         else
         {
            return -EINVAL;
         }

         return(read_audio(&ra, inode));
      }
      return 0;
      break;

   default:
      return -EINVAL;
   }
}


/*
 * Open the drive for operations.  Spin the drive up and read the table of
 * contents if these have not already been done.
 */
static int
scd_open(struct inode *inode,
         struct file *filp)
{
   unsigned char res_reg[12];
   unsigned int res_size;
   int num_spin_ups;
   unsigned char params[2];
   int dev;


   if ((filp) && filp->f_mode & 2)
      return -EROFS;

   if (!sony_spun_up)
   {
      num_spin_ups = 0;

respinup_on_open:
      do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);

      /* The drive sometimes returns error 0.  I don't know why, but ignore
         it.  It seems to mean the drive has already done the operation. */
      if ((res_size < 2) || ((res_reg[0] != 0) && (res_reg[1] != 0)))
      {
         printk("Sony CDROM error 0x%2.2x (scd_open, spin up)\n", res_reg[1]);
         return -EIO;
      }
      
      do_sony_cd_cmd(SONY_READ_TOC_CMD, NULL, 0, res_reg, &res_size);

      /* The drive sometimes returns error 0.  I don't know why, but ignore
         it.  It seems to mean the drive has already done the operation. */
      if ((res_size < 2) || ((res_reg[0] != 0) && (res_reg[1] != 0)))
      {
         /* If the drive is already playing, its ok.  */
         if ((res_reg[1] == SONY_AUDIO_PLAYING_ERR) || (res_reg[1] == 0))
         {
            goto drive_spinning;
         }

         /* If the drive says it is not spun up (even though we just did it!)
            then retry the operation at least a few times. */
         if (   (res_reg[1] == SONY_NOT_SPIN_ERR)
             && (num_spin_ups < MAX_CDU31A_RETRIES))
         {
            num_spin_ups++;
            goto respinup_on_open;
         }

         printk("Sony CDROM error 0x%2.2x (scd_open, read toc)\n", res_reg[1]);
         do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size);
         
         return -EIO;
      }

      dev = MINOR(inode->i_rdev);
      sony_get_toc(dev);
      if (ses_tocs[dev] == NULL)
      {
         do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size);
         return -EIO;
      }

      /* For XA on the CDU31A only, we have to do special reads.
         The CDU33A handles XA automagically. */
      if (   (ses_tocs[dev]->disk_type == SONY_XA_DISK_TYPE)
          && (!is_double_speed))
      {
         params[0] = SONY_SD_DECODE_PARAM;
         params[1] = 0x07;
         do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
                        params,
                        2,
                        res_reg,
                        &res_size);
         if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20))
         {
            printk("CDU31A: Unable to set XA params: 0x%2.2x\n", res_reg[1]);
         }
         sony_xa_mode = 1;
      }
      /* A non-XA disk.  Set the parms back if necessary. */
      else if (sony_xa_mode)
      {
         params[0] = SONY_SD_DECODE_PARAM;
         params[1] = 0x0f;
         do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
                        params,
                        2,
                        res_reg,
                        &res_size);
         if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20))
         {
            printk("CDU31A: Unable to reset XA params: 0x%2.2x\n", res_reg[1]);
         }
         sony_xa_mode = 0;
      }

      sony_spun_up = 1;
   }

drive_spinning:

   /* If filp is not NULL (standard open), try a disk change. */
   if (filp)
   {
      check_disk_change(inode->i_rdev);
   }

   sony_usage++;

   return 0;
}


/*
 * Close the drive.  Spin it down if no task is using it.  The spin
 * down will fail if playing audio, so audio play is OK.
 */
static void
scd_release(struct inode *inode,
         struct file *filp)
{
   unsigned char res_reg[12];
   unsigned int  res_size;


   if (sony_usage > 0)
   {
      sony_usage--;
   }
   if (sony_usage == 0)
   {
      sync_dev(inode->i_rdev);
      do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size);

      sony_spun_up = 0;
   }
}


static struct file_operations scd_fops = {
   NULL,                   /* lseek - default */
   block_read,             /* read - general block-dev read */
   block_write,            /* write - general block-dev write */
   NULL,                   /* readdir - bad */
   NULL,                   /* select */
   scd_ioctl,              /* ioctl */
   NULL,                   /* mmap */
   scd_open,               /* open */
   scd_release,            /* release */
   NULL,                   /* fsync */
   NULL,                   /* fasync */
   scd_disk_change,        /* media_change */
   NULL                    /* revalidate */
};


/* The different types of disc loading mechanisms supported */
static char *load_mech[] = { "caddy", "tray", "pop-up", "unknown" };

static void
get_drive_configuration(unsigned short base_io,
                        unsigned char res_reg[],
                        unsigned int *res_size)
{
   int retry_count;


   /* Set the base address */
   sony_cd_base_io = base_io;

   /* Set up all the register locations */
   sony_cd_cmd_reg = sony_cd_base_io + SONY_CMD_REG_OFFSET;
   sony_cd_param_reg = sony_cd_base_io + SONY_PARAM_REG_OFFSET;
   sony_cd_write_reg = sony_cd_base_io + SONY_WRITE_REG_OFFSET;
   sony_cd_control_reg = sony_cd_base_io + SONY_CONTROL_REG_OFFSET;
   sony_cd_status_reg = sony_cd_base_io + SONY_STATUS_REG_OFFSET;
   sony_cd_result_reg = sony_cd_base_io + SONY_RESULT_REG_OFFSET;
   sony_cd_read_reg = sony_cd_base_io + SONY_READ_REG_OFFSET;
   sony_cd_fifost_reg = sony_cd_base_io + SONY_FIFOST_REG_OFFSET;

   /*
    * Check to see if anything exists at the status register location.
    * I don't know if this is a good way to check, but it seems to work
    * ok for me.
    */
   if (read_status_register() != 0xff)
   {
      /*
       * Reset the drive and wait for attention from it (to say its reset).
       * If you don't wait, the next operation will probably fail.
       */
      reset_drive();
      retry_count = jiffies + SONY_RESET_TIMEOUT;
      while ((retry_count > jiffies) && (!is_attention()))
      {
         sony_sleep();
      }

#if 0
      /* If attention is never seen probably not a CDU31a present */
      if (!is_attention())
      {
         res_reg[0] = 0x20;
         return;
      }
#endif

      /*
       * Get the drive configuration.
       */
      do_sony_cd_cmd(SONY_REQ_DRIVE_CONFIG_CMD,
                     NULL,
                     0,
                     (unsigned char *) res_reg,
                     res_size);
      return;
   }

   /* Return an error */
   res_reg[0] = 0x20;
}

/*
 * Set up base I/O and interrupts, called from main.c.
 */
void
cdu31a_setup(char *strings,
	     int  *ints)
{
   if (ints[0] > 0)
   {
      sony_cd_base_io = ints[1];
   }
   if (ints[0] > 1)
   {
      irq_used = ints[2];
   }
   if ((strings != NULL) && (*strings != '\0'))
   {
      if (strcmp(strings, "PAS") == 0)
      {
	 sony_pas_init = 1;
      }
      else
      {
	 printk("CDU31A: Unknown interface type: %s\n", strings);
      }
   }
}

static int cdu31a_block_size;

/*
 * Initialize the driver.
 */
unsigned long
cdu31a_init(unsigned long mem_start, unsigned long mem_end)
{
   struct s_sony_drive_config drive_config;
   unsigned int res_size;
   int i;
   int drive_found;
   int tmp_irq;


   /*
    * According to Alex Freed (freed@europa.orion.adobe.com), this is
    * required for the Fusion CD-16 package.  If the sound driver is
    * loaded, it should work fine, but just in case...
    *
    * The following turn on the CD-ROM interface for a Fusion CD-16.
    */
   if (sony_pas_init)
   {
      outb(0xbc, 0x9a01);
      outb(0xe2, 0x9a01);
   }

   drive_found = 0;

   /* Setting the base I/O address to 0xffff will disable it. */
   if (sony_cd_base_io == 0xffff)
   {
   }
   else if (sony_cd_base_io != 0)
   {
      tmp_irq = irq_used; /* Need IRQ 0 because we can't sleep here. */
      irq_used = 0;

      get_drive_configuration(sony_cd_base_io,
			      drive_config.exec_status,
			      &res_size);
      if ((res_size > 2) && ((drive_config.exec_status[0] & 0xf0) == 0x00))
      {
	 drive_found = 1;
      }

      irq_used = tmp_irq;
   }
   else
   {
      irq_used = 0;
      i = 0;
      while (   (cdu31a_addresses[i].base != 0)
	     && (!drive_found))
      {
	 if (check_region(cdu31a_addresses[i].base, 4)) {
	    i++;
	    continue;
	 }
	 get_drive_configuration(cdu31a_addresses[i].base,
				 drive_config.exec_status,
				 &res_size);
	 if ((res_size > 2) && ((drive_config.exec_status[0] & 0xf0) == 0x00))
	 {
	    drive_found = 1;
	    irq_used = cdu31a_addresses[i].int_num;
	 }
	 else
	 {
	    i++;
	 }
      }
   }

   if (drive_found)
   {
      snarf_region(sony_cd_base_io, 4);
      
      if (register_blkdev(MAJOR_NR,"cdu31a",&scd_fops))
      {
	 printk("Unable to get major %d for CDU-31a\n", MAJOR_NR);
	 return mem_start;
      }

      if (SONY_HWC_DOUBLE_SPEED(drive_config))
      {
	 is_double_speed = 1;
      }

      /* A negative irq_used will attempt an autoirq. */
      if (irq_used < 0)
      {
	 autoirq_setup(0);
	 enable_interrupts();
	 reset_drive();
	 tmp_irq = autoirq_report(10);
	 disable_interrupts();
	 
         irq_used = 0;
	 set_drive_params();
	 irq_used = tmp_irq;
      }
      else
      {
         tmp_irq = irq_used; /* Need IRQ 0 because we can't sleep here. */
         irq_used = 0;

	 set_drive_params();

         irq_used = tmp_irq;
      }
      
      if (irq_used > 0)
      {
	 if (request_irq(irq_used, cdu31a_interrupt, SA_INTERRUPT, "cdu31a"))
	 {
	    printk("Unable to grab IRQ%d for the CDU31A driver\n", irq_used);
	    irq_used = 0;
	 }
      }
      
      printk("Sony I/F CDROM : %8.8s %16.16s %8.8s\n",
	     drive_config.vendor_id,
	     drive_config.product_id,
	     drive_config.product_rev_level);
      printk("  Capabilities: %s",
	     load_mech[SONY_HWC_GET_LOAD_MECH(drive_config)]);
      if (SONY_HWC_AUDIO_PLAYBACK(drive_config))
      {
	 printk(", audio");
      }
      if (SONY_HWC_EJECT(drive_config))
      {
	 printk(", eject");
      }
      if (SONY_HWC_LED_SUPPORT(drive_config))
      {
	 printk(", LED");
      }
      if (SONY_HWC_ELECTRIC_VOLUME(drive_config))
      {
	 printk(", elec. Vol");
      }
      if (SONY_HWC_ELECTRIC_VOLUME_CTL(drive_config))
      {
	 printk(", sep. Vol");
      }
      if (is_double_speed)
      {
	 printk(", double speed");
      }
      if (irq_used > 0)
      {
	 printk(", irq %d", irq_used);
      }
      printk("\n");

      blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
      read_ahead[MAJOR_NR] = CDU31A_READAHEAD;
      cdu31a_block_size = 1024; /* 1kB default block size */
      /* use 'mount -o block=2048' */
      blksize_size[MAJOR_NR] = &cdu31a_block_size;
      
      last_sony_subcode = (struct s_sony_subcode *) mem_start;
      mem_start += sizeof(*last_sony_subcode);
      readahead_buffer = (unsigned char *) mem_start;
      mem_start += CD_FRAMESIZE_RAW;
   }


   for (i=0; i<MAX_TRACKS; i++)
   {
      disk_changed[i] = 1;
   }
   
   return mem_start;
}
