/*
 *	I created this device driver from the Linux driver for
 *	Sony CD-ROM drives. For now I don't have documentation
 *	for the Sony drives, the Linux device driver was my only
 *	reference. So many thanks to Corey Minyard who distributed
 *	his driver under the GNU copyleft, which allowed me to
 *	use it as a base for the COHERENT driver. I had to change
 *	a lot because the OS's are pretty different, however,
 *	his original Copyright will follow.
 *
 *	December 1993, Udo Munk (udo@umunk.GUN.de or udo@mwc.com)
 */

/*
 * 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.
 *
 * This interface is (unfortunatly) 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
 * asyncronous events from the drive informing the driver that a disk
 * has been inserted, removed, etc.
 *
 * 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.
 *
 *  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.
 *
 */

#include <sys/types.h>
#include <kernel/v_types.h>
#include <sys/coherent.h>
#include <sys/devices.h>
#include <sys/io.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <sys/cdrom.h>
#include <sys/sched.h>
#include <kernel/timeout.h>
#include "cdu31.h"

#define VERSION	"v1.3"
#define DEBUG

/* -------------------------------------------------------------------- */
/* Variables								*/
/* -------------------------------------------------------------------- */

extern int CDU31_SDBASE;			/* base I/O address */
extern int CDU31_BUFFERS;			/* size of read-ahead buffer */
static int initialized = 0;			/* has the drive been initialized? */
static int usage = 0;				/* number of opens */
static int audio_status = CDROM_AUDIO_NO_STATUS; /* status read from subchannel */
static int disc_changed = 1;			/* has the disk been changed? */
static int toc_read = 0;			/* has the table of contents been read? */
static int spun_up = 0;				/* has the drive been spun up? */
static int first_block = -1;			/* first OS block in the read-ahead buffer */
static int last_block = -1;			/* last OS block in the read-ahead buffer */
static int buffer_size;				/* size of the buffer */
static int buffer_sectors;			/* no of 2048 byte sectors in the buffer */
static char *buffer_cache;			/* read-ahead buffer cache */
static struct s_sony_subcode last_sony_subcode; /* last subcode readed */
static struct s_sony_toc sony_toc;		/* table of contents */

static char *polling_event = "sony_polling";	/* the event we are waiting on when polling */
static TIM polling_timer;			/* the timer for polling the drive */
static char *polling_text = "scdread";		/* text for ps when polling the drive */

/*
 * The following is 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 };

/* -------------------------------------------------------------------- */
/* Forward declarations							*/
/* -------------------------------------------------------------------- */

void cdu31_open(), cdu31_close(), cdu31_read(), cdu31_ioctl(), cdu31_load(),
     cdu31_unload();

void get_drive_configuration();
void sony_get_toc();
void do_sony_cd_cmd();
void get_data();
void reset_drive();
void clear_result_ready();
void clear_param_reg();
void clear_attention();
void clear_data_ready();
void write_param();
void write_cmd();
void get_result();
void log_to_msf();
void size_to_buf();
unsigned char read_result_register();
unsigned char read_status_register();
unsigned char read_data_register();
unsigned int msf_to_log();
unsigned int bcd_to_int();
unsigned int int_to_bcd();

/* -------------------------------------------------------------------- */
/* CON structure for device						*/
/* -------------------------------------------------------------------- */

CON	cdu31con = {
	DFCHR,				/* Flags */
	CDU31_MAJOR,			/* Major index */
	cdu31_open,			/* Open */
	cdu31_close,			/* Close */
	NULL,				/* Block */
	cdu31_read,			/* Read */
	NULL,				/* Write */
	cdu31_ioctl,			/* Ioctl */
	NULL,				/* Powerfail */
	NULL,				/* Timeout */
	cdu31_load,			/* Load */
	cdu31_unload			/* Unload */
};

/* -------------------------------------------------------------------- */
/* Device driver functions						*/
/* -------------------------------------------------------------------- */

/*
 * Open the drive for operations. Spin the drive up and read the table of
 * contents if these have not already been done.
 */
static void
cdu31_open(dev, mode)
dev_t dev;
int mode;
{
	unsigned char res_reg[2];
	unsigned int res_size;

	if (mode != IPR) {
		/* what, you want to write on a CD-ROM ??? */
#ifdef DEBUG
		devmsg(dev, "Sony CD-ROM error, can't open with mode %x\n", mode);
#endif
		set_user_error(ENODEV);
		return;
	}

	if (minor(dev)) {
		/* we can handle only one drive with minor device 0 */
#ifdef DEBUG
		devmsg(dev, "Sony CD-ROM error, minor device %d not supported\n", dev & 0xff);
#endif
		set_user_error(ENXIO);
		return;
	}

	if (!spun_up) {

spin_up_again:

		do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, &res_reg[0], &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))) {
#ifdef DEBUG
			devmsg(dev, "Sony CDROM error 0x%x (cdu31_open, spin up)\n", res_reg[1]);
#endif
			set_user_error(EIO);
			return;
		}
      
		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;
			/* My drive sometimes reports a not spin error, retry */
			if (res_reg[1] == SONY_NOT_SPIN_ERR)
				goto spin_up_again;
#ifdef DEBUG
			devmsg(dev, "Sony CDROM error 0x%x (cdu31_open, read toc)\n", res_reg[1]);
#endif
			do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, &res_reg[0], &res_size);
		 
			set_user_error(EIO);
			return;
		}

		sony_get_toc();
		if (!toc_read) {
			do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, &res_reg[0], &res_size);
			set_user_error(EIO);
			return;
		}

		spun_up = 1;
	}

drive_spinning:
	usage++;
}

/*
 * 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
cdu31_close(dev, mode)
dev_t dev;
int mode;
{
	unsigned char res_reg[2];
	unsigned int res_size;

	if (usage > 0)
		usage--;
	if (usage == 0) {
		do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, &res_reg[0], &res_size);
		spun_up = 0;
	}
}

static void
cdu31_read(dev, iop)
dev_t dev;
IO *iop;
{
	unsigned char params[10];
	unsigned char res_reg[2];
	unsigned int res_size;
	register unsigned int block;
	register unsigned int nsect;
	unsigned int read_size;
	int copyoff;

	/*
	 * The offset to read from must be on a block boundary
	 */
	if (iop->io_seek % 512) {
#ifdef DEBUG
		devmsg(dev, "Sony CD-ROM error, read offset isn't at a block boundary\n");
#endif
		set_user_error(EINVAL);
		return;
	}

	/*
	 * Compute the OS number of the block to read
	 */
	block = iop->io_seek / 512;

	/*
	 * If the block address is invalid or the request goes beyond the end of
	 * the media, return an error.
	 */
	if ((block / 4) >= sony_toc.lead_out_start_lba) {
#ifdef DEBUG
		devmsg(dev, "Sony CD-ROM error, can't read beyond end of media\n");
#endif
		set_user_error(EINVAL);
		return;
	}

	/*
	 * Compute number of OS sectors to read
	 */
	nsect = iop->io_ioc / 512;
	if ((nsect == 0) && (iop->io_ioc > 0))
		nsect++;

	if (((block + nsect) / 4) >= sony_toc.lead_out_start_lba) {
#ifdef DEBUG
		devmsg(dev, "Sony CD-ROM error, can't read beyond end of media\n");
#endif
		set_user_error(EINVAL);
		return;
	}

	while (nsect > 0) {
		/*
		 * If the requested sector is not currently in the read-ahead
		 * buffer, it must be read in.
		 */
		if ((block < first_block) || (block > last_block)) {
			first_block = block;
			log_to_msf(block / 4, &params[0]);
			/*
			 * 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.
			 */
			if (((block / 4) + buffer_sectors) >= sony_toc.lead_out_start_lba) {
				last_block = (sony_toc.lead_out_start_lba * 4) - 1;
				read_size = sony_toc.lead_out_start_lba - (block / 4);
			} else {
				last_block = first_block + (buffer_sectors * 4) - 1;
				read_size = buffer_sectors;
			}
			size_to_buf(read_size, &params[3]);
			/*
			 * Read the data.
			 */
			get_data(buffer_cache, params, (read_size * 2048), &res_reg[0], &res_size);
			if ((res_size < 2) || (res_reg[0] != 0)) {
#ifdef DEBUG
				devmsg(dev, "Sony CDROM Read error: 0x%x\n", res_reg[1]);
#endif
				first_block = -1;
				last_block = -1;
				set_user_error(EIO);
				return;
			}
		}
		/*
		 * The data is in memory now, copy it to the user buffer and
		 * advance to the next block to read.
		 */
		copyoff = (block - first_block) * 512;
		iowrite(iop, buffer_cache + copyoff, 512);
		block++;
		nsect--;
	}
}

static void
cdu31_ioctl(dev, com, arg)
dev_t dev;
int com;
char *arg;
{
	unsigned char res_reg[2];
	unsigned int res_size;
	register int i;
	int track_idx;
	unsigned char params[7];
	unsigned char *msf_val = NULL;
	struct cdrom_ti ti;
	struct cdrom_tochdr loc_hdr;
	struct cdrom_tocentry loc_entry;
	struct cdrom_volctrl volctrl;

	switch(com) {

	case CDROMREADTOCHDR:        /* Read the table of contents header */
		sony_get_toc();
		if (!toc_read) {
			set_user_error(EIO);
			return;
		}
		loc_hdr.cdth_trk0 = bcd_to_int(sony_toc.first_track_num);
		loc_hdr.cdth_trk1 = bcd_to_int(sony_toc.last_track_num);
		kucopy(&loc_hdr, arg, sizeof(loc_hdr));
		break;

	case CDROMREADTOCENTRY:      /* Read a given table of contents entry */
		sony_get_toc();
		if (!toc_read) {
			set_user_error(EIO);
			return;
		}
		ukcopy(arg, &loc_entry, sizeof(loc_entry));
		/* Lead out is handled separately since it is special. */
		if (loc_entry.cdte_track == CDROM_LEADOUT) {
			loc_entry.cdte_adr = sony_toc.address2;
			loc_entry.cdte_ctrl = sony_toc.control2;
			msf_val = sony_toc.lead_out_start_msf;
		} else {
			track_idx = find_track(int_to_bcd(loc_entry.cdte_track));
			if (track_idx < 0) {
				set_user_error(EINVAL);
				return;
			}
			loc_entry.cdte_adr = sony_toc.tracks[track_idx].address;
			loc_entry.cdte_ctrl = sony_toc.tracks[track_idx].control;
			msf_val = sony_toc.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));
		}
		kucopy(&loc_entry, arg, sizeof(loc_entry));
		break;

	case CDROMSTART:     /* Spin up the drive */
		do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, &res_reg[0], &res_size);
		if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20)) {
#ifdef DEBUG
			devmsg(dev, "Sony CDROM error 0x%x (CDROMSTART)\n", res_reg[1]);
#endif
			set_user_error(EIO);
			return;
		}
		break;

	case CDROMSTOP:      /* Spin down the drive */
		do_sony_cd_cmd(SONY_AUDIO_STOP_CMD, NULL, 0, &res_reg[0], &res_size);
		/*
		 * Spin the drive down, ignoring the error if the disk was
		 * already not spinning.
		 */
		audio_status = CDROM_AUDIO_NO_STATUS;
		do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, &res_reg[0], &res_size);
		if (((res_size < 2) || ((res_reg[0] & 0x20) == 0x20))
		    && (res_reg[1] != SONY_NOT_SPIN_ERR)) {
#ifdef DEBUG
			devmsg(dev, "Sony CDROM error 0x%x (CDROMSTOP)\n", res_reg[1]);
#endif
			set_user_error(EIO);
			return;
		}
		break;

	case CDROMPAUSE:     /* Pause the drive */
		do_sony_cd_cmd(SONY_AUDIO_STOP_CMD, NULL, 0, &res_reg[0], &res_size);
		if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20)) {
#ifdef DEBUG
			devmsg(dev, "Sony CDROM error 0x%x (CDROMPAUSE)\n", res_reg[1]);
#endif
			set_user_error(EIO);
			return;
		}
		/* Get the current position and save it for resuming */
		if (read_subcode() < 0) {
			set_user_error(EIO);
			return;
		}
		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];
		audio_status = CDROM_AUDIO_PAUSED;
		break;

	case CDROMRESUME:    /* Start the drive after being paused */
		if (audio_status != CDROM_AUDIO_PAUSED) {
			set_user_error(EINVAL);
			return;
		}
		do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, &res_reg[0], &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[0], &res_size);
		if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20)) {
#ifdef DEBUG
			devmsg(dev, "Sony CDROM error 0x%x (CDROMRESUME)\n", res_reg[1]);
#endif
			set_user_error(EIO);
			return;
		}
		audio_status = CDROM_AUDIO_PLAY;
		break;

	case CDROMPLAYTRKIND:     /* Play a track. This currently ignores index. */
		sony_get_toc();
		if (!toc_read) {
			set_user_error(EIO);
			return;
		}
		ukcopy(arg, &ti, sizeof(ti));
		if ((ti.cdti_trk0 < sony_toc.first_track_num)
		    || (ti.cdti_trk0 > sony_toc.last_track_num)
		    || (ti.cdti_trk1 < ti.cdti_trk0)) {
			set_user_error(EINVAL);
			return;
		}
		track_idx = find_track(int_to_bcd(ti.cdti_trk0));
		if (track_idx < 0) {
			set_user_error(EINVAL);
			return;
		}
		params[1] = sony_toc.tracks[track_idx].track_start_msf[0];
		params[2] = sony_toc.tracks[track_idx].track_start_msf[1];
		params[3] = sony_toc.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(sony_toc.last_track_num))
			log_to_msf(msf_to_log(sony_toc.lead_out_start_msf)-1,
				   &(params[4]));
		else {
			track_idx = find_track(int_to_bcd(ti.cdti_trk1+1));
			if (track_idx < 0) {
				set_user_error(EINVAL);
				return;
			}
			log_to_msf(msf_to_log(sony_toc.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] & 0x20) == 0x20)) {
#ifdef DEBUG
			devmsg(dev, "Params: %x %x %x %x %x %x %x\n", params[0], params[1],
				params[2], params[3], params[4], params[5], params[6]);
			devmsg(dev, "Sony CDROM error 0x%x (CDROMPLAYTRKIND\n", res_reg[1]);
#endif
			set_user_error(EIO);
			return;
		}
		/* 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];
		audio_status = CDROM_AUDIO_PLAY;
		break;

	case CDROMPLAYMSF:   /* Play starting at the given MSF address. */
		do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, &res_reg[0], &res_size);
		ukcopy(arg, &(params[1]), 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[0], &res_size);
		if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20)) {
#ifdef DEBUG
			devmsg(dev, "Sony CDROM error 0x%x (CDROMPLAYMSF)\n", res_reg[1]);
#endif
			set_user_error(EIO);
			return;
		}
		/* 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];
		audio_status = CDROM_AUDIO_PLAY;
		break;

	case CDROMVOLCTRL:   /* Volume control */
		ukcopy(arg, &volctrl, 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[0], &res_size);
		if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20)) {
#ifdef DEBUG
			devmsg(dev, "Sony CDROM error 0x%x (CDROMVOLCTRL)\n", res_reg[1]);
#endif
			set_user_error(EIO);
			return;
		}
		break;

	case CDROMSUBCHNL:   /* Get subchannel info */
		sony_get_subchnl_info(arg);
		break;

	case CDROMEJECT:     /* Eject the drive */
		do_sony_cd_cmd(SONY_AUDIO_STOP_CMD, NULL, 0, &res_reg[0], &res_size);
		do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, &res_reg[0], &res_size);
		audio_status = CDROM_AUDIO_INVALID;
		do_sony_cd_cmd(SONY_EJECT_CMD, NULL, 0, &res_reg[0], &res_size);
		if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20)) {
#ifdef DEBUG
			devmsg(dev, "Sony CDROM error 0x%x (CDROMEJECT)\n", res_reg[1]);
#endif
			set_user_error(EIO);
			return;
		}
		break;

	default:
		set_user_error(EINVAL);
		break;
	}
}

static void
cdu31_load()
{
	struct s_sony_drive_config drive_config;
	unsigned char params[3];
	unsigned char res_reg[2];
	unsigned int res_size;
	char buf[20];
	char *load_mech[4] = { "caddy", "tray", "pop-up", "unknown" };
	unsigned int mem_size[4] = { 8192, 32768, 65536, 8192 };
	void trim_string();

	/*
	 * 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.
	 */
	outb(0x9a01, 0xbc);
	outb(0x9a01, 0xe2);

	printf("Sony CD-ROM Driver %s\n", VERSION);

	get_drive_configuration(CDU31_SDBASE, &drive_config, &res_size);
	if ((res_size > 2) && ((drive_config.exec_status[0] & 0x20) == 0x00)) {
		if (CDU31_BUFFERS == 0) {
			buffer_size = mem_size[SONY_HWC_GET_BUF_MEM_SIZE(drive_config)];
			buffer_sectors = buffer_size / 2048;
		} else {
			buffer_sectors = (CDU31_BUFFERS > 32) ? 32 : CDU31_BUFFERS;
			buffer_size = buffer_sectors * 2048;
		}
		if ((buffer_cache = kalloc(buffer_size)) == NULL)
			panic("Sony CD-ROM driver can't allocate read-ahead buffer cache\n");

		trim_string(drive_config.vendor_id, &buf[0], sizeof(drive_config.vendor_id));
		printf("%s ", &buf[0]);
		trim_string(drive_config.product_id, &buf[0], sizeof(drive_config.product_id));
		printf("%s ", &buf[0]);
		trim_string(drive_config.product_rev_level, &buf[0], sizeof(drive_config.product_rev_level));
		printf("%s ", &buf[0]);
		printf("with %s load mechanism\n", load_mech[SONY_HWC_GET_LOAD_MECH(drive_config)]);
		printf("using %d byte buffer", buffer_size);
		if (SONY_HWC_AUDIO_PLAYBACK(drive_config))
			printf(", capable of audio playback");
		printf("\n");

		params[0] = SONY_SD_MECH_CONTROL;
		params[1] = 0x03;
		do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD, &params[0], 2, &res_reg[0], &res_size);
		if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20))
			printf("Unable to set mechanical parameters: 0x%x\n", res_reg[1]);

		initialized = 1;
	} else
		printf("No Sony CD-ROM drive found\n");
}

static void
cdu31_unload()
{
	kfree(buffer_cache);
}

/* -------------------------------------------------------------------- */
/* Timing functions for device polling and busy waiting			*/
/* -------------------------------------------------------------------- */

static void
awake_sony_driver(event)
int event;
{
	wakeup(event);
}

static void
sony_sleep(event, timer, text)
char *event;
TIM *timer;
char *text;
{
	/* while the kernel is initializing we can do busy waiting only */
	if (!initialized)
		busyWait(NULL, 1);
	else {
		timeout(timer, 1, awake_sony_driver, event);
		x_sleep(event, pridisk, slpriNoSig, text);
	}
}

/* -------------------------------------------------------------------- */
/* Support functions for the device driver functions to access CDROM	*/
/* -------------------------------------------------------------------- */

/*
 * Get the drive configuration
 */
void
get_drive_configuration(base_io, res_reg, res_size)
unsigned short base_io;
unsigned char *res_reg;
unsigned int *res_size;
{
	register int retry_count;

	/*
	 * 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 = NO_OF_SLEEPWAITS;
		while ((retry_count) && (!is_attention())) {
			sony_sleep(polling_event, &polling_timer, polling_text);
			retry_count--;
		}

		/* If attention is never seen probably not a CDU31A present */
		if (!is_attention()) {
#ifdef DEBUG
			printf("Sony CD-ROM error, can't get attention from drive\n");
#endif
			res_reg[0] = 0x20;
			return;
		}

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

	/* Return an error */
#ifdef DEBUG
	printf("Sony CD-ROM error, controler not found at address %x\n",
	       CDU31_SDBASE + SONY_STATUS_REG_OFFSET);
#endif
	res_reg[0] = 0x20;
}

/*
 * Read the table of contents from the drive and set toc_read if
 * successful.
 */
static void
sony_get_toc()
{
	unsigned int res_size;

	if (!toc_read) {
		do_sony_cd_cmd(SONY_REQ_TOC_DATA_CMD, NULL, 0, (unsigned char *)&sony_toc, &res_size);
		if ((res_size < 2) || ((sony_toc.exec_status[0] & 0x20) == 0x20))
			return;
		sony_toc.lead_out_start_lba = msf_to_log(sony_toc.lead_out_start_msf);
		toc_read = 1;
	}
}

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

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

/*
 * Do a command that does not involve data transfer.
 */
static void
do_sony_cd_cmd(cmd, params, num_params, result_buffer, result_size)
unsigned char cmd;
unsigned char *params;
unsigned int num_params;
unsigned char *result_buffer;
unsigned int *result_size;
{
	register unsigned int retry_count;
	int num_retries = 0;

retry_cd_operation:

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

	retry_count = NO_OF_SLEEPWAITS;
	while ((retry_count) && (is_busy())) {
		sony_sleep(polling_event, &polling_timer, polling_text);
		while (handle_sony_cd_attention())
			;
		retry_count--;
	}
	if (is_busy()) {
		result_buffer[0] = 0x20;
		result_buffer[1] = SONY_TIMEOUT_OP_ERR;
		*result_size = 2;
		goto do_cmd_end;
	}

	clear_result_ready();
	clear_param_reg();

	if (num_params > 0)
		write_params(params, num_params);
	write_cmd(cmd);

	get_result(result_buffer, result_size);

do_cmd_end:
	if (((result_buffer[0] & 0x20) == 0x20) && (num_retries < MAX_RETRIES)) {
		num_retries++;
		goto retry_cd_operation;
	}
}

/*
 * This routine issues a read data command and gets the data. I don't
 * really like the way this is done (I would prefer for do_sony_cmd() to
 * handle it automatically) but I found that the drive returns status
 * when it finishes reading (not when the host has read all the data)
 * or after it gets an error. This means that the status can be
 * received at any time and should be handled immediately (at least
 * between every 2048 byte block) to check for errors, we can't wait
 * until all the data is read.
 */
static void
get_data(data, params, data_size, result_buffer, result_size)
unsigned char *data;
unsigned char *params;
unsigned int data_size;
unsigned char *result_buffer;
unsigned int *result_size;
{
	register int i;
	unsigned int cur_offset;
	register unsigned int retry_count;
	int result_read;
	int num_retries = 0;

retry_data_operation:

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

	retry_count = NO_OF_SLEEPWAITS;
	while ((retry_count) && (is_busy())) {
		sony_sleep(polling_event, &polling_timer, polling_text);
		while (handle_sony_cd_attention())
			;
		retry_count--;
	}
	if (is_busy()) {
		result_buffer[0] = 0x20;
		result_buffer[1] = SONY_TIMEOUT_OP_ERR;
		*result_size = 2;
		goto get_data_end;
	}

	/* Issue the command */
	clear_result_ready();
	clear_param_reg();

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

	/*
	 * Read the data from the drive one 2048 byte sector at a time.  Handle
	 * any results received between sectors, if an error result is returned
	 * terminate the operation immediately.
	 */
	cur_offset = 0;
	result_read = 0;
	while (data_size > 0) {
		/* Wait for the drive to tell us we have something */
		retry_count = NO_OF_SLEEPWAITS;
		while ((retry_count) && (!(is_result_ready() || is_data_ready()))) {
			while (handle_sony_cd_attention())
				;
			sony_sleep(polling_event, &polling_timer, polling_text);
			retry_count--;
		}
		if (!(is_result_ready() || is_data_ready())) {
			result_buffer[0] = 0x20;
			result_buffer[1] = SONY_TIMEOUT_OP_ERR;
			*result_size = 2;
			goto get_data_end;
		}
      
		/* Handle results first */
		if (is_result_ready()) {
			result_read = 1;
			get_result(result_buffer, result_size);
			if ((*result_size < 2) || (result_buffer[0] != 0))
				goto get_data_end;
		} else { /* Handle data next */
			/*
			 * The drive has to be polled for status on a byte-by-byte basis
			 * to know if the data is ready. Yuck. I really wish I could use DMA.
			 */
			clear_data_ready();
			for (i = 0; i < 2048; i++) {
				retry_count = NO_OF_SLEEPWAITS;
				while ((retry_count) && (!is_data_requested())) {
					while (handle_sony_cd_attention())
						;
					sony_sleep(polling_event, &polling_timer, polling_text);
					retry_count--;
				}
				if (!is_data_requested()) {
					result_buffer[0] = 0x20;
					result_buffer[1] = SONY_TIMEOUT_OP_ERR;
					*result_size = 2;
					goto get_data_end;
				}
            
				*data = read_data_register();
				data++;
				data_size--;
			}
			cur_offset = cur_offset + 2048;
		}
	}

	/* Make sure the result has been read */
	if (!result_read)
		get_result(result_buffer, result_size);

get_data_end:
	if (((result_buffer[0] & 0x20) == 0x20) && (num_retries < MAX_RETRIES)) {
		num_retries++;
		goto retry_data_operation;
	}
}

/*
 * Handle an attention from the drive.
 */
static int
handle_sony_cd_attention()
{
	unsigned char atten_code;
	unsigned char res_reg[2];
	unsigned int res_size;

	if (is_attention()) {
		clear_attention();
		atten_code = read_result_register();
		switch (atten_code) {

		case SONY_MECH_LOADED_ATTN: /* Someone changed the CD.  Mark it as changed */
			disc_changed = 1;
			toc_read = 0;
			audio_status = CDROM_AUDIO_NO_STATUS;
			first_block = -1;
			last_block = -1;
			if (initialized) {
				do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, &res_reg[0], &res_size);
				sony_get_toc();
			}
			break;

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

		case SONY_EJECT_PUSHED_ATTN:
			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:
			audio_status = CDROM_AUDIO_ERROR;
			break;
		}
		return(1);
	}
	return(0);
}

/*
 * 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(params, num_params)
unsigned char *params;
int num_params;
{
	int is_param_write_rdy();

	busyWait(is_param_write_rdy, NO_OF_BUSYWAITS);

	if (!is_param_write_rdy())
		return(1);

	while (num_params > 0) {
		write_param(*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(result_buffer, result_size)
unsigned char *result_buffer;
unsigned int *result_size;
{
	unsigned char a, b;
	register int i;
	register unsigned int retry_count;
	int is_result_ready();

	while (handle_sony_cd_attention())
		;

	/* Wait for the result data to be ready */
	retry_count = NO_OF_SLEEPWAITS;
	while ((retry_count) && (is_busy() || (!(is_result_ready())))) {
		sony_sleep(polling_event, &polling_timer, polling_text);
		while (handle_sony_cd_attention())
			;
		retry_count--;
	}

	if (is_busy() || (!(is_result_ready()))) {
		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++;
	b = read_result_register();
	*result_buffer = b;
	result_buffer++;
	*result_size = 2;

	/*
	 * 0x20 means an error occured.  Byte 2 will have the error code.
	 * Otherwise, the command succeded, 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) {
				busyWait(is_result_ready, NO_OF_BUSYWAITS);
				if (!is_result_ready()) {
					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) {
				busyWait(is_result_ready, NO_OF_BUSYWAITS);
				if (!is_result_ready()) {
					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--;
		}
	}
}

/*
 * 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(arg)
char *arg;
{
	struct cdrom_subchnl schi;

	while (handle_sony_cd_attention())
		;

	sony_get_toc();
	if (!toc_read)
		return(1);

	ukcopy(arg, &schi, sizeof(schi));
   
	switch (audio_status) {

	case CDROM_AUDIO_PLAY:
		if (read_subcode() < 0)
			return(1);
		break;

	case CDROM_AUDIO_PAUSED:
	case CDROM_AUDIO_COMPLETED:
		break;

	case CDROM_AUDIO_NO_STATUS:
		schi.cdsc_audiostatus = audio_status;
		kucopy(&schi, arg, sizeof(schi));
		return(0);
		break;

	case CDROM_AUDIO_INVALID:
	case CDROM_AUDIO_ERROR:
	default:
		return(1);
		break;
	}

	schi.cdsc_audiostatus = 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);
	}
   
	kucopy(&schi, arg, sizeof(schi));
	return(0);
}

/*
 * Read the subcode and put it into last_sony_subcode for future use.
 */
static int
read_subcode()
{
	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] & 0x20) == 0x20)) {
#ifdef DEBUG
		printf("Sony CDROM error 0x%x (read_subcode)\n",
			last_sony_subcode.exec_status[1]);
#endif
		return(1);
	}
	return(0);
}

/*
 * Read various status from drive
 */

static int
is_attention()
{
	return((inb(CDU31_SDBASE + SONY_STATUS_REG_OFFSET) & SONY_ATTN_BIT) != 0);
}

static int
is_busy()
{
	return((inb(CDU31_SDBASE + SONY_STATUS_REG_OFFSET) & SONY_BUSY_BIT) != 0);
}

static int
is_param_write_rdy()
{
	return((inb(CDU31_SDBASE + SONY_FIFOST_REG_OFFSET) & SONY_PARAM_WRITE_RDY_BIT) != 0);
}

static int
is_result_ready()
{
	return((inb(CDU31_SDBASE + SONY_STATUS_REG_OFFSET) & SONY_RES_RDY_BIT) != 0);
}

static int
is_data_requested()
{
	return((inb(CDU31_SDBASE + SONY_STATUS_REG_OFFSET) & SONY_DATA_REQUEST_BIT) != 0);
}

static int
is_data_ready()
{
	return((inb(CDU31_SDBASE + SONY_STATUS_REG_OFFSET) & SONY_DATA_RDY_BIT) != 0);
}

static unsigned char
read_result_register()
{
	return(inb(CDU31_SDBASE + SONY_RESULT_REG_OFFSET));
}

static unsigned char
read_status_register()
{
	return(inb(CDU31_SDBASE + SONY_STATUS_REG_OFFSET));
}

static unsigned char
read_data_register()
{
	return(inb(CDU31_SDBASE + SONY_READ_REG_OFFSET));
}

/*
 * Set conditions in drive
 */
static void
reset_drive()
{
	outb(CDU31_SDBASE + SONY_CONTROL_REG_OFFSET, SONY_DRIVE_RESET_BIT);
}

static void
clear_result_ready()
{
	outb(CDU31_SDBASE + SONY_CONTROL_REG_OFFSET, SONY_RES_RDY_CLR_BIT);
}

static void
clear_param_reg()
{
	outb(CDU31_SDBASE + SONY_CONTROL_REG_OFFSET, SONY_PARAM_CLR_BIT);
}

static void
clear_attention()
{
	outb(CDU31_SDBASE + SONY_CONTROL_REG_OFFSET, SONY_ATTN_CLR_BIT);
}

static void
clear_data_ready()
{
	outb(CDU31_SDBASE + SONY_CONTROL_REG_OFFSET, SONY_DATA_RDY_CLR_BIT);
}

static void
write_param(param)
unsigned char param;
{
	outb(CDU31_SDBASE + SONY_PARAM_REG_OFFSET, param);
}

static void
write_cmd(cmd)
unsigned char cmd;
{
	outb(CDU31_SDBASE + SONY_CMD_REG_OFFSET, cmd);
	outb(CDU31_SDBASE + SONY_CONTROL_REG_OFFSET, SONY_RES_RDY_INT_EN_BIT);
}

/* -------------------------------------------------------------------- */
/* Conversion functions							*/
/* -------------------------------------------------------------------- */

/*
 * Convert a MSF format to a logical sector.
 */
static unsigned int
msf_to_log(msf)
unsigned char *msf;
{
	register 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);
}

/*
 * Convert a logical sector value (like the OS would want to use for
 * a block device) to a MSF format.
 */
static void
log_to_msf(log, 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);
}

/*
 * 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(size, buf)
unsigned int size;
unsigned char *buf;
{
	buf[0] = size / 65536;
	size = size % 65536;
	buf[1] = size / 256;
	buf[2] = size % 256;
}

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

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

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

/*
 * Trim the drive vendor strings
 */
static void
trim_string(from, to, len)
char *to;
char *from;
int len;
{
	register char *p;

	bcopy(from, to, len);
	p = to + len - 1;
	while (*p == ' ')
		p--;
	*(p + 1) = '\0';
}
