/* @(#)drv_7501.c	1.15 04/03/02 Copyright 2003-2004 J. Schilling */
#ifndef lint
static	char sccsid[] =
	"@(#)drv_7501.c	1.15 04/03/02 Copyright 2003-2004 J. Schilling";
#endif
/*
 *	Device driver for the Masushita CW-7501
 *
 *	Copyright (c) 2003-2004 J. Schilling
 *
 * Mode Pages:
 *	0x01	error recovery		Seite 100
 *	0x02	disconnect/reconnect	Seite 107
 *	0x0D	CD-ROM device parameter	Seite 110
 *	0x0E	CD-ROM Audio control	Seite 112
 *	0x20	Speed & Tray position	Seite 115
 *	0x21	Media catalog number	Seite 124
 *	0x22	ISRC			Seite 125
 *	0x23	Dummy/Write Information	Seite 126
 *	0x24	CD-R disk information	Seite 127
 */
/*
 * 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, 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; see the file COPYING.  If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifndef	DEBUG
#define	DEBUG
#endif
#include <mconfig.h>

#include <stdio.h>
#include <standard.h>
#include <stdxlib.h>
#include <unixstd.h>
#include <errno.h>
#include <strdefs.h>
#include <timedefs.h>

#include <utypes.h>
#include <btorder.h>
#include <intcvt.h>
#include <schily.h>

#include <scg/scgcmd.h>
#include <scg/scsidefs.h>
#include <scg/scsireg.h>
#include <scg/scsitransp.h>

#include <libport.h>

#include "cdrecord.h"

extern	int	silent;
extern	int	debug;
extern	int	verbose;
extern	int	lverbose;
extern	int	xdebug;

#if defined(_BIT_FIELDS_LTOH)	/* Intel byteorder */

struct cw7501_mode_page_20 {	/* Speed control */
		MP_P_CODE;		/* parsave & pagecode */
	Uchar	p_len;			/* 0x02 = 2 Bytes */
	Uchar	speed;
	Ucbit	res		: 7;
	Ucbit	traypos		: 1;
};

#else				/* Motorola byteorder */

struct cw7501_mode_page_20 {	/* Speed control */
		MP_P_CODE;		/* parsave & pagecode */
	Uchar	p_len;			/* 0x02 = 2 Bytes */
	Uchar	speed;
	Ucbit	traypos		: 1;
	Ucbit	res		: 7;
};
#endif

#if defined(_BIT_FIELDS_LTOH)	/* Intel byteorder */

struct cw7501_mode_page_21 {	/* MCN */
		MP_P_CODE;		/* parsave & pagecode */
	Uchar	p_len;			/* 0x12 = 20 Bytes */
	Ucbit	control		: 4;
	Ucbit	addr		: 4;
	Uchar	res_3;
	Uchar	res_4;
	Uchar	mcn[15];
};

#else				/* Motorola byteorder */

struct cw7501_mode_page_21 {	/* MCN */
		MP_P_CODE;		/* parsave & pagecode */
	Uchar	p_len;			/* 0x12 = 20 Bytes */
	Ucbit	addr		: 4;
	Ucbit	control		: 4;
	Uchar	res_3;
	Uchar	res_4;
	Uchar	mcn[15];
};
#endif


#if defined(_BIT_FIELDS_LTOH)	/* Intel byteorder */

struct cw7501_mode_page_22 {	/* ISRC */
		MP_P_CODE;		/* parsave & pagecode */
	Uchar	p_len;			/* 0x12 = 20 Bytes */
	Ucbit	control		: 4;
	Ucbit	addr		: 4;
	Uchar	trackno;
	Uchar	res_4;
	Uchar	isrc[15];
};

#else				/* Motorola byteorder */

struct cw7501_mode_page_22 {	/* ISRC */
		MP_P_CODE;		/* parsave & pagecode */
	Uchar	p_len;			/* 0x12 = 20 Bytes */
	Ucbit	addr		: 4;
	Ucbit	control		: 4;
	Uchar	trackno;
	Uchar	res_4;
	Uchar	isrc[15];
};
#endif

#if defined(_BIT_FIELDS_LTOH)	/* Intel byteorder */

struct cw7501_mode_page_23 {	/* Dummy / Write information */
		MP_P_CODE;		/* parsave & pagecode */
	Uchar	p_len;			/* 0x02 = 2 Bytes */
	Uchar	res;
	Ucbit	autopg		: 1;
	Ucbit	dummy		: 1;
	Ucbit	res3_72		: 6;
};

#else				/* Motorola byteorder */

struct cw7501_mode_page_23 {	/* Dummy / Write information */
		MP_P_CODE;		/* parsave & pagecode */
	Uchar	p_len;			/* 0x02 = 2 Bytes */
	Uchar	res;
	Ucbit	res3_72		: 6;
	Ucbit	dummy		: 1;
	Ucbit	autopg		: 1;
};
#endif

struct cw7501_mode_page_24 {	/* CD-R Disk information */
		MP_P_CODE;		/* parsave & pagecode */
	Uchar	p_len;			/* 0x0A = 12 Bytes */
	Uchar	disktype;
	Uchar	res;
	Uchar	appl_code[4];
	Uchar	disk_id[4];
};

struct cw7501_mode_data {
	struct scsi_mode_header	header;
	union cdd_pagex	{
		struct cw7501_mode_page_20	page20;
		struct cw7501_mode_page_21	page21;
		struct cw7501_mode_page_22	page22;
		struct cw7501_mode_page_23	page23;
		struct cw7501_mode_page_24	page24;
	} pagex;
};

/*
 * Mode for read track information
 */
#define	TI_TRACKINFO_R	0
#define	TI_NWA		1
#define	TI_PMA		2
#define	TI_TRACKINFO	3

struct cw7501_nwa {
	Uchar	nwa_length[2];
	Uchar	nwa_res;
	Uchar	nwa_trackno;
	Uchar	nwa_nwa[4];
	Uchar	nwa_freeblocks[4];
};

struct cw7501_cue {
	Uchar	cs_ctladr;		/* CTL/ADR for this track	*/
	Uchar	cs_tno;			/* This track number		*/
	Uchar	cs_index;		/* Index within this track	*/
	Uchar	cs_dataform;		/* Data form 			*/
					/* Bit 0..3 Physical Format	*/
					/* Bit 4 Alt Copy (SCMS)	*/
					/* Bit 5 SubC Audio + RAW96 sub */
	Uchar	cs_extension;		/* Reserved or MCN/ISRC		*/
	Uchar	cs_min;			/* Absolute time minutes	*/
	Uchar	cs_sec;			/* Absolute time seconds	*/
	Uchar	cs_frame;		/* Absolute time frames		*/
};


LOCAL	int	cw7501_attach		__PR((SCSI *scgp, cdr_t *dp));
LOCAL	int	cw7501_init		__PR((SCSI *scgp, cdr_t *dp));
LOCAL	int	cw7501_getdisktype	__PR((SCSI *scgp, cdr_t *dp));
LOCAL	int	cw7501_speed_select	__PR((SCSI *scgp, cdr_t *dp, int *speedp));
LOCAL	int	cw7501_next_wr_addr	__PR((SCSI *scgp, track_t *trackp, long *ap));
LOCAL	int	cw7501_write		__PR((SCSI *scgp, caddr_t bp, long sectaddr, long size, int blocks, BOOL islast));
LOCAL	int	cw7501_write_leadin	__PR((SCSI *scgp, cdr_t *dp, track_t *trackp));
LOCAL	int	cw7501_open_track	__PR((SCSI *scgp, cdr_t *dp, track_t *trackp));
LOCAL	int	cw7501_close_track	__PR((SCSI *scgp, cdr_t *dp, track_t *trackp));
LOCAL	int	cw7501_open_session	__PR((SCSI *scgp, cdr_t *dp, track_t *trackp));
LOCAL	int	cw7501_gen_cue		__PR((track_t *trackp, void *vcuep, BOOL needgap));
LOCAL	void	fillcue			__PR((struct cw7501_cue *cp, int ca, int tno, int idx, int dataform, int scms, msf_t *mp));
LOCAL	int	cw7501_send_cue		__PR((SCSI *scgp, cdr_t *dp, track_t *trackp));
LOCAL	int	cw7501_fixate		__PR((SCSI *scgp, cdr_t *dp, track_t *trackp));
LOCAL	int	cw7501_rezero		__PR((SCSI *scgp, int reset, int dwreset));
LOCAL	int	cw7501_read_trackinfo	__PR((SCSI *scgp, Uchar *bp, int count, int track, int mode));
LOCAL	int	cw7501_write_dao	__PR((SCSI *scgp, Uchar *bp, int len, int disktype));
LOCAL	int	cw7501_reserve_track	__PR((SCSI *scgp, unsigned long));
LOCAL	int	cw7501_set_mode		__PR((SCSI *scgp, int phys_form, int control,
						int subc, int alt, int trackno, int tindex,
						int packet_size, int write_mode));
LOCAL	int	cw7501_finalize		__PR((SCSI *scgp, int pad, int fixed));


cdr_t	cdr_cw7501 = {
	0, 0,
	/*
	 * Prinzipiell geht auch: CDR_PACKET & CDR_SRAW96R
	 */
	CDR_TAO|CDR_SAO|CDR_TRAYLOAD,
	2, 2,
	"cw_7501",
	"driver for Matsushita/Panasonic CW-7501",
	0,
	(dstat_t *)0,
	drive_identify,
	cw7501_attach,
	cw7501_init,
	cw7501_getdisktype,
	scsi_load,
	scsi_unload,
	buf_dummy,				/* RD buffer cap not supp. */
	cmd_dummy,					/* recovery_needed */
	(int(*)__PR((SCSI *, cdr_t *, int)))cmd_dummy,	/* recover	*/
	cw7501_speed_select,
	select_secsize,
	cw7501_next_wr_addr,
	cw7501_reserve_track,
	cw7501_write,
	cw7501_gen_cue,
	cw7501_send_cue,
	cw7501_write_leadin,
	cw7501_open_track,
	cw7501_close_track,
	cw7501_open_session,
	cmd_dummy,				/* close seession	*/
	cmd_dummy,					/* abort	*/
	read_session_offset,
	cw7501_fixate,
	cmd_dummy,					/* stats	*/
	blank_dummy,
	format_dummy,
	(int(*)__PR((SCSI *, caddr_t, int, int)))NULL,	/* no OPC	*/
	cmd_dummy,					/* opt1		*/
	cmd_dummy,					/* opt2		*/
};

static const char *sd_cw7501_error_str[] = {
	"\100\201diagnostic failure on ROM",			/* 40 81 */
	"\100\202diagnostic failure on CPU internal RAM",	/* 40 82 */
	"\100\203diagnostic failure on BUFFER RAM",		/* 40 83 */
	"\100\204diagnostic failure on internal SCSI controller",	/* 40 84 */
	"\100\205diagnostic failure on system mechanism",	/* 40 85 */

	"\210\000Illegal Que Sheet (DAO parameter)",		/* 88 00 */
	"\211\000Inappropriate command",			/* 89 00 */

	"\250\000Audio Play operation Not in Progress",		/* A8 00 */
	"\251\000Buffer Overrun",				/* A9 00 */

	"\300\000Unrecordable Disk",				/* C0 00 */
	"\301\000Illegal Track Status",				/* C1 00 */
	"\302\000Reserved track Status",			/* C2 00 */
	"\304\000Illegal Reserve Length for Reserve Track Command",	/* C4 00 */
	"\304\001Illegal Data Form for Reserve Track Command",	/* C4 01 */
	"\304\002Unable to Reserve Track, Because Track Mode has been Changed",	/* C4 02 */

	"\305\000Buffer error during recording",		/* C5 00 */
	"\307\000Disk Style mismatch",				/* C7 00 */
	"\312\000Power Calibration error",			/* CA 00 */
	"\313\000Write error (Fatal Error/Time out)",		/* CB 00 */
	"\314\000Not enough space (Leadin/Leadout space)",	/* CC 00 */
	"\315\000No track present to finalize",			/* CD 00 */
	"\317\000Unable to recover damaged disk",		/* CF 00 */

	"\320\000PMA area full (1000 blocks)",			/* D0 00 */
	"\321\000PCA area full (100 counts)",			/* D1 00 */
	"\322\000Recovery failed",				/* D2 00 */
	"\323\000Recovery needed",				/* D3 00 */
	NULL
};

LOCAL int
cw7501_attach(scgp, dp)
	SCSI	*scgp;
	cdr_t	*dp;
{
	scg_setnonstderrs(scgp, sd_cw7501_error_str);
	return (0);
}

LOCAL int
cw7501_init(scgp, dp)
	SCSI	*scgp;
	cdr_t	*dp;
{
	return (cw7501_speed_select(scgp, dp, NULL));
}

LOCAL int
cw7501_getdisktype(scgp, dp)
	SCSI	*scgp;
	cdr_t	*dp;
{
	Ulong	maxb = 0;
	Uchar	buf[256];
	int	ret;
	dstat_t	*dsp = dp->cdr_dstat;

	if (xdebug > 0) {
		scgp->silent++;
		fillbytes((caddr_t)buf, sizeof (buf), '\0');
		ret = cw7501_read_trackinfo(scgp, buf, 32, 0, 0);
		if (ret >= 0)
		scg_prbytes("TI EXIST-R   (0): ", buf, 32 -scg_getresid(scgp));

		fillbytes((caddr_t)buf, sizeof (buf), '\0');
		ret = cw7501_read_trackinfo(scgp, buf, 32, 0, 1);
		if (ret >= 0)
		scg_prbytes("TI NWA       (1): ", buf, 32 -scg_getresid(scgp));

		fillbytes((caddr_t)buf, sizeof (buf), '\0');
		ret = cw7501_read_trackinfo(scgp, buf, 32, 0, 2);
		if (ret >= 0)
		scg_prbytes("TI PMA       (2): ", buf, 32 -scg_getresid(scgp));

		fillbytes((caddr_t)buf, sizeof (buf), '\0');
		ret = cw7501_read_trackinfo(scgp, buf, 32, 0, 3);
		if (ret >= 0)
		scg_prbytes("TI EXIST-ROM (3): ", buf, 32 -scg_getresid(scgp));
		scgp->silent--;
	}

	fillbytes((caddr_t)buf, sizeof (buf), '\0');

	scgp->silent++;
	ret = cw7501_read_trackinfo(scgp, buf, 12, 0, TI_NWA);
	if (ret < 0 &&
			(dsp->ds_cdrflags & (RF_WRITE|RF_BLANK)) == RF_WRITE) {

		/*
		 * Try to clear the dummy bit to reset the virtual
		 * drive status. Not all drives support it even though
		 * it is mentioned in the MMC standard.
		 */
		if (lverbose)
			printf("Trying to clear drive status.\n");
		cw7501_rezero(scgp, 0, 1);
		wait_unit_ready(scgp, 60);
		ret = cw7501_read_trackinfo(scgp, buf, 12, 0, TI_NWA);
	}
	scgp->silent--;

	if (ret >= 0) {
		maxb = a_to_u_4_byte(&buf[8]);
		if (maxb != 0)
			maxb -= 150;
	}
	dsp->ds_maxblocks = maxb;

	return (drive_getdisktype(scgp, dp));
}


LOCAL int
cw7501_speed_select(scgp, dp, speedp)
	SCSI	*scgp;
	cdr_t	*dp;
	int	*speedp;
{
	struct	scsi_mode_page_header	*mp;
	char				mode[256];
	int				len = 20;
	int				page = 0x20;
	struct cw7501_mode_page_20	*xp20;
	struct cw7501_mode_data		md;
	int				count;
	int				speed = 1;
	BOOL				dummy = (dp->cdr_cmdflags & F_DUMMY) != 0;

	if (speedp) {
		speed = *speedp;
	} else {
		fillbytes((caddr_t)mode, sizeof (mode), '\0');

		if (!get_mode_params(scgp, page, "Speed information",
			(Uchar *)mode, (Uchar *)0, (Uchar *)0, (Uchar *)0, &len)) {
			return (-1);
		}
		if (len == 0)
			return (-1);

		mp = (struct scsi_mode_page_header *)
			(mode + sizeof (struct scsi_mode_header) +
			((struct scsi_mode_header *)mode)->blockdesc_len);

		xp20  = (struct cw7501_mode_page_20 *)mp;
		speed = xp20->speed;
	}

	fillbytes((caddr_t)&md, sizeof (md), '\0');

	count  = sizeof (struct scsi_mode_header) +
		sizeof (struct cw7501_mode_page_20);

	md.pagex.page20.p_code = 0x20;
	md.pagex.page20.p_len =  0x02;
	md.pagex.page20.speed = speed;

	if (mode_select(scgp, (Uchar *)&md, count, 0, scgp->inq->data_format >= 2) < 0)
		return (-1);

	fillbytes((caddr_t)&md, sizeof (md), '\0');

	count  = sizeof (struct scsi_mode_header) +
		sizeof (struct cw7501_mode_page_23);

	md.pagex.page23.p_code = 0x23;
	md.pagex.page23.p_len =  0x02;
	md.pagex.page23.dummy = dummy?1:0;

	return (mode_select(scgp, (Uchar *)&md, count, 0, scgp->inq->data_format >= 2));
}

LOCAL int
cw7501_next_wr_addr(scgp, trackp, ap)
	SCSI	*scgp;
	track_t	*trackp;
	long	*ap;
{
	struct cw7501_nwa	*nwa;
	Uchar	buf[256];
	long	next_addr;
	int	result = -1;


	/*
	 * Reading info for current track may require doing the read_track_info
	 * with either the track number (if the track is currently being written)
	 * or with 0 (if the track hasn't been started yet and is invisible
	 */
	nwa = (struct cw7501_nwa *)buf;

	if (trackp != 0 && trackp->track > 0 && is_packet(trackp)) {
		fillbytes((caddr_t)buf, sizeof (buf), '\0');

		scgp->silent++;
		result = cw7501_read_trackinfo(scgp, buf, sizeof (*nwa),
							trackp->trackno,
							TI_NWA);
		scgp->silent--;
	}

	if (result < 0) {
		if (cw7501_read_trackinfo(scgp, buf, sizeof (*nwa),
							0, TI_NWA) < 0)
			return (-1);
	}
	if (scgp->verbose)
		scg_prbytes("track info:", buf,
				12-scg_getresid(scgp));
	next_addr = a_to_4_byte(&nwa->nwa_nwa);
	/*
	 * XXX Für TAO definitiv notwendig.
	 * XXX ABhängig von Auto-Pregap?
	 */
	/* XXX */ next_addr += 150;
	if (ap)
		*ap = next_addr;
	return (0);
}

LOCAL int
cw7501_write(scgp, bp, sectaddr, size, blocks, islast)
	SCSI	*scgp;
	caddr_t	bp;		/* address of buffer */
	long	sectaddr;	/* disk address (sector) to put */
	long	size;		/* number of bytes to transfer */
	int	blocks;		/* sector count */
	BOOL	islast;		/* last write for track */
{
	if (lverbose > 1 && islast)
		printf("\nWriting last record for this track.\n");

	return (write_xg0(scgp, bp, 0, size, blocks));
}

LOCAL int
cw7501_write_leadin(scgp, dp, trackp)
	SCSI	*scgp;
	cdr_t	*dp;
	track_t *trackp;
{
	Uint	i;
	long	startsec = 0L;

	if (wm_base(dp->cdr_dstat->ds_wrmode) == WM_SAO) {
		if (debug || lverbose) {
			printf("Sending CUE sheet...\n");
			flush();
		}
		if ((*dp->cdr_send_cue)(scgp, dp, trackp) < 0) {
			errmsgno(EX_BAD, "Cannot send CUE sheet.\n");
			return (-1);
		}

		/*
		 * Next writable address function does not work in DAO
		 * mode for this writer, so we just assume -150.
		 */
		startsec = -150;
		if (debug)
			printf("SAO startsec: %ld\n", startsec);

		if (trackp[0].flags & TI_TEXT) {
			errmsgno(EX_BAD, "CD-Text unsupported in CW-7501 - ignoring.\n");
		} else for (i = 1; i <= trackp->tracks; i++) {
			trackp[i].trackstart += startsec +150;
		}
	}
	return (0);
}

LOCAL Uchar	db2phys[] = {
	0x00,			/*  0 2352 bytes of raw data			*/
	0xFF,			/*  1 2368 bytes (raw data + P/Q Subchannel)	*/
	0xFF,			/*  2 2448 bytes (raw data + P-W Subchannel)	*/
	0xFF,			/*  3 2448 bytes (raw data + P-W raw Subchannel)*/
	0xFF,			/*  4 -    Reserved				*/
	0xFF,			/*  5 -    Reserved				*/
	0xFF,			/*  6 -    Reserved				*/
	0xFF,			/*  7 -    Vendor specific			*/
	0x02,			/*  8 2048 bytes Mode 1 (ISO/IEC 10149)		*/
	0x03,			/*  9 2336 bytes Mode 2 (ISO/IEC 10149)		*/
	0xFF,			/* 10 2048 bytes Mode 2 (CD-ROM XA form 1)	*/
	0x04,			/* 11 2056 bytes Mode 2 (CD-ROM XA form 1)	*/
	0xFF,			/* 12 2324 bytes Mode 2 (CD-ROM XA form 2)	*/
	0x08,			/* 13 2332 bytes Mode 2 (CD-ROM XA 1/2+subhdr)	*/
	0xFF,			/* 14 -    Reserved				*/
	0xFF,			/* 15 -    Vendor specific			*/
};

LOCAL int
cw7501_open_track(scgp, dp, trackp)
	SCSI	*scgp;
	cdr_t	*dp;
	track_t *trackp;
{
	struct	scsi_mode_page_header	*mp;
	Uchar				mode[256];
	int				len = 0;
	int				page = 0x23;
	struct cw7501_mode_page_23	*xp23;

	if (!is_tao(trackp) && !is_packet(trackp)) {
		if (trackp->pregapsize > 0 && (trackp->flags & TI_PREGAP) == 0) {
			if (lverbose) {
				printf("Writing pregap for track %d at %ld\n",
					(int)trackp->trackno,
					trackp->trackstart-trackp->pregapsize);
			}
			/*
			 * XXX Do we need to check isecsize too?
			 */
			pad_track(scgp, dp, trackp,
				trackp->trackstart-trackp->pregapsize,
				(Llong)trackp->pregapsize*trackp->secsize,
					FALSE, 0);
		}
		return (0);
	}

	if (select_secsize(scgp, trackp->secsize) < 0)
		return (-1);

	if (!get_mode_params(scgp, page, "Dummy/autopg information",
			(Uchar *)mode, (Uchar *)0, (Uchar *)0, (Uchar *)0, &len)) {
		return (-1);
	}
	if (len == 0)
		return (-1);

	mp = (struct scsi_mode_page_header *)
		(mode + sizeof (struct scsi_mode_header) +
		((struct scsi_mode_header *)mode)->blockdesc_len);

	xp23  = (struct cw7501_mode_page_23 *)mp;
	xp23->autopg = 1;
	if (!set_mode_params(scgp, "Dummy/autopg page", mode, len, 0, trackp->secsize))
		return (-1);

	/*
	 * Set write modes for next track.
	 */
	if (cw7501_set_mode(scgp, db2phys[trackp->dbtype & 0x0F],
			st2mode[trackp->sectype&ST_MASK] | (is_copy(trackp) ? TM_ALLOW_COPY : 0),
			0, is_scms(trackp) ? 1 : 0,
			trackp->trackno, 1, 0,
			/* write mode TAO */ 0x01) < 0)
		return (-1);

	return (0);
}


LOCAL int
cw7501_close_track(scgp, dp, trackp)
	SCSI	*scgp;
	cdr_t	*dp;
	track_t	*trackp;
{
	if (!is_tao(trackp) && !is_packet(trackp)) {
		return (0);
	}
	return (scsi_flush_cache(scgp, FALSE));
}

LOCAL int
cw7501_open_session(scgp, dp, trackp)
	SCSI	*scgp;
	cdr_t	*dp;
	track_t	*trackp;
{
	struct cw7501_mode_data		md;
	int				count;

	if (select_secsize(scgp, 2048) < 0)
		return (-1);

	/*
	 * Disable Auto Pregap when writing in SAO mode.
	 */
	if (!is_tao(trackp) && !is_packet(trackp)) {
		struct	scsi_mode_page_header	*mp;
		Uchar				mode[256];
		int				len = 0;
		int				page = 0x23;
		struct cw7501_mode_page_23	*xp23;

		if (!get_mode_params(scgp, page, "Dummy/autopg information",
				(Uchar *)mode, (Uchar *)0, (Uchar *)0, (Uchar *)0, &len)) {
			return (-1);
		}
		if (len == 0)
			return (-1);

		mp = (struct scsi_mode_page_header *)
			(mode + sizeof (struct scsi_mode_header) +
			((struct scsi_mode_header *)mode)->blockdesc_len);

		xp23  = (struct cw7501_mode_page_23 *)mp;
		xp23->autopg = 0;
		if (!set_mode_params(scgp, "Dummy/autopg page", mode, len, 0, trackp->secsize))
			return (-1);

		return (0);
	}

	/*
	 * Set Disk Type and Disk ID.
	 */
	fillbytes((caddr_t)&md, sizeof (md), '\0');

	count  = sizeof (struct scsi_mode_header) +
		sizeof (struct cw7501_mode_page_24);

	md.pagex.page24.p_code = 0x24;
	md.pagex.page24.p_len =  0x0A;
	md.pagex.page24.disktype = toc2sess[track_base(trackp)->tracktype & TOC_MASK];
	i_to_4_byte(&md.pagex.page24.disk_id, 0x12345);

	return (mode_select(scgp, (Uchar *)&md, count, 0, scgp->inq->data_format >= 2));
}

LOCAL int
cw7501_fixate(scgp, dp, trackp)
	SCSI	*scgp;
	cdr_t	*dp;
	track_t	*trackp;
{
	if (!is_tao(trackp) && !is_packet(trackp)) {
		return (scsi_flush_cache(scgp, FALSE));
	}
	/*
	 * 0x00	Finalize Disk (not appendable)
	 * 0x01	Finalize Session (allow next session)
	 * 0x10	Finalize track (variable packet writing) - Must fluch cache before
	 */
	return (cw7501_finalize(scgp, 0, (track_base(trackp)->tracktype & TOCF_MULTI) ? 0x01 : 0x00));
}

/*--------------------------------------------------------------------------*/

LOCAL int
cw7501_gen_cue(trackp, vcuep, needgap)
	track_t	*trackp;
	void	*vcuep;
	BOOL	needgap;
{
	int	tracks = trackp->tracks;
	int	i;
	struct cw7501_cue	**cuep = vcuep;
	struct cw7501_cue	*cue;
	struct cw7501_cue	*cp;
	int	ncue = 0;
	int	icue = 0;
	int	pgsize;
	msf_t	m;
	int	ctl;
	int	df;
	int	scms;

	cue = malloc(1);

	for (i = 0; i <= tracks; i++) {
		ctl = (st2mode[trackp[i].sectype & ST_MASK]) << 4;
		if (is_copy(&trackp[i]))
			ctl |= TM_ALLOW_COPY << 4;
		df = db2phys[trackp[i].dbtype & 0x0F];

		if (trackp[i].isrc) {	/* MCN or ISRC */
			ncue += 2;
			cue = realloc(cue, ncue * sizeof (*cue));
			cp = &cue[icue++];
			if (i == 0) {
				cp->cs_ctladr = 0x02;
				movebytes(&trackp[i].isrc[0], &cp->cs_tno, 7);
				cp = &cue[icue++];
				cp->cs_ctladr = 0x02;
				movebytes(&trackp[i].isrc[7], &cp->cs_tno, 7);
			} else {
				cp->cs_ctladr = 0x03;
				cp->cs_tno = i;
				movebytes(&trackp[i].isrc[0], &cp->cs_index, 6);
				cp = &cue[icue++];
				cp->cs_ctladr = 0x03;
				cp->cs_tno = i;
				movebytes(&trackp[i].isrc[6], &cp->cs_index, 6);
			}
		}
		if (i == 0) {	/* Lead in */
			lba_to_msf(-150, &m);
			cue = realloc(cue, ++ncue * sizeof (*cue));
			cp = &cue[icue++];
			fillcue(cp, ctl|0x01, i, 0, df, 0, &m);
		} else {
			scms = 0;

			if (is_scms(&trackp[i]))
				scms = 0x80;
			pgsize = trackp[i].pregapsize;
			if (pgsize == 0 && needgap)
				pgsize++;
			lba_to_msf(trackp[i].trackstart-pgsize, &m);
			cue = realloc(cue, ++ncue * sizeof (*cue));
			cp = &cue[icue++];
			fillcue(cp, ctl|0x01, i, 0, df, scms, &m);

			if (trackp[i].nindex == 1) {
				lba_to_msf(trackp[i].trackstart, &m);
				cue = realloc(cue, ++ncue * sizeof (*cue));
				cp = &cue[icue++];
				fillcue(cp, ctl|0x01, i, 1, df, scms, &m);
			} else {
				int	idx;
				long	*idxlist;

				ncue += trackp[i].nindex;
				idxlist = trackp[i].tindex;
				cue = realloc(cue, ncue * sizeof (*cue));

				for (idx = 1; idx <= trackp[i].nindex; idx++) {
					lba_to_msf(trackp[i].trackstart + idxlist[idx], &m);
					cp = &cue[icue++];
					fillcue(cp, ctl|0x01, i, idx, df, scms, &m);
				}
			}
		}
	}
	/* Lead out */
	ctl = (st2mode[trackp[tracks+1].sectype & ST_MASK]) << 4;
	df = db2phys[trackp[tracks+1].dbtype & 0x0F];
	lba_to_msf(trackp[tracks+1].trackstart, &m);
	cue = realloc(cue, ++ncue * sizeof (*cue));
	cp = &cue[icue++];
	fillcue(cp, ctl|0x01, 0xAA, 1, df, 0, &m);

	if (lverbose > 1) {
		for (i = 0; i < ncue; i++) {
			scg_prbytes("", (Uchar *)&cue[i], 8);
		}
	}
	if (cuep)
		*cuep = cue;
	else
		free(cue);
	return (ncue);
}

LOCAL void
fillcue(cp, ca, tno, idx, dataform, scms, mp)
	struct cw7501_cue *cp;	/* The target cue entry		*/
	int	ca;		/* Control/adr for this entry	*/
	int	tno;		/* Track number for this entry	*/
	int	idx;		/* Index for this entry		*/
	int	dataform;	/* Data format for this entry	*/
	int	scms;		/* Serial copy management	*/
	msf_t	*mp;		/* MSF value for this entry	*/
{
	cp->cs_ctladr = ca;
	if (tno <= 99)
		cp->cs_tno = to_bcd(tno);
	else
		cp->cs_tno = tno;
	cp->cs_index = to_bcd(idx);
	if (scms != 0)
		dataform |= 0x10;
	cp->cs_dataform = dataform;
	cp->cs_extension = 0;
	cp->cs_min = to_bcd(mp->msf_min);
	cp->cs_sec = to_bcd(mp->msf_sec);
	cp->cs_frame = to_bcd(mp->msf_frame);
}

LOCAL int
cw7501_send_cue(scgp, dp, trackp)
	SCSI	*scgp;
	cdr_t	*dp;
	track_t	*trackp;
{
	struct cw7501_cue *cp;
	int		ncue;
	int		ret;
	Uint		i;
	struct timeval starttime;
	struct timeval stoptime;
	int		disktype;

	disktype = toc2sess[track_base(trackp)->tracktype & TOC_MASK];

	for (i = 1; i <= trackp->tracks; i++) {
		if (trackp[i].tracksize < (tsize_t)0) {
			errmsgno(EX_BAD, "Track %d has unknown length.\n", i);
			return (-1);
		}
	}
	ncue = (*dp->cdr_gen_cue)(trackp, &cp, FALSE);

	starttime.tv_sec = 0;
	starttime.tv_usec = 0;
	stoptime = starttime;
	gettimeofday(&starttime, (struct timezone *)0);

	scgp->silent++;
	ret = cw7501_write_dao(scgp, (Uchar *)cp, ncue*8, disktype);
	scgp->silent--;
	free(cp);
	if (ret < 0) {
		errmsgno(EX_BAD, "CUE sheet not accepted. Retrying with minimum pregapsize = 1.\n");
		ncue = (*dp->cdr_gen_cue)(trackp, &cp, TRUE);
		ret  = cw7501_write_dao(scgp, (Uchar *)cp, ncue*8, disktype);
		free(cp);
	}
	if (ret >= 0 && lverbose) {
		gettimeofday(&stoptime, (struct timezone *)0);
		prtimediff("Write Lead-in time: ", &starttime, &stoptime);
	}
	return (ret);
}

/*--------------------------------------------------------------------------*/
LOCAL int
cw7501_rezero(scgp, reset, dwreset)
	SCSI	*scgp;
	int	reset;
	int	dwreset;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = (caddr_t)0;
	scmd->size = 0;
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G0_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g0_cdb.cmd = SC_REZERO_UNIT;
	scmd->cdb.g0_cdb.lun = scg_lun(scgp);
	scmd->cdb.cmd_cdb[5] |= reset ? 0x80 : 0;
	scmd->cdb.cmd_cdb[5] |= dwreset ? 0x40 : 0;

	scgp->cmdname = "cw7501 rezero";

	return (scg_cmd(scgp));
}


LOCAL int
cw7501_read_trackinfo(scgp, bp, count, track, mode)
	SCSI	*scgp;
	Uchar	*bp;
	int	count;
	int	track;
	int	mode;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t) scmd, sizeof (*scmd), '\0');
	scmd->addr = (caddr_t)bp;
	scmd->size = count;
	scmd->flags = SCG_RECV_DATA | SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0xE9;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	scmd->cdb.cmd_cdb[6] = track;
	g1_cdblen(&scmd->cdb.g1_cdb, count);
	scmd->cdb.cmd_cdb[9] = (mode & 3) << 6;

	scgp->cmdname = "cw7501 read_track_information";

	if (scg_cmd(scgp) < 0)
		return (-1);

	return (0);
}

LOCAL int
cw7501_write_dao(scgp, bp, len, disktype)
	SCSI	*scgp;
	Uchar	*bp;
	int	len;
	int	disktype;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = (caddr_t)bp;
	scmd->size = len;
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0xE6;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	scmd->cdb.cmd_cdb[2] = disktype;
	g1_cdblen(&scmd->cdb.g1_cdb, len);

	scgp->cmdname = "cw7501 write_dao";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

/*
 * XXX CW-7501 also needs "control", so we need to make a different
 * XXX driver interface.
 */
LOCAL int
cw7501_reserve_track(scgp, len)
	SCSI	*scgp;
	unsigned long len;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0xE7;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
/*	scmd->cdb.cmd_cdb[2] = control & 0x0F;*/
	i_to_4_byte(&scmd->cdb.cmd_cdb[5], len);

	scgp->cmdname = "cw7501 reserve_track";

	comerrno(EX_BAD, "Control (as in set mode) missing.\n");

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

LOCAL int
cw7501_set_mode(scgp, phys_form, control, subc, alt, trackno, tindex, packet_size, write_mode)
	SCSI	*scgp;
	int	phys_form;
	int	control;
	int	subc;
	int	alt;
	int	trackno;
	int	tindex;
	int	packet_size;
	int	write_mode;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0xE2;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	scmd->cdb.cmd_cdb[2] = phys_form & 0x0F;
	scmd->cdb.cmd_cdb[3] = (control & 0x0F) << 4;
	scmd->cdb.cmd_cdb[3] |= subc ? 2 : 0;
	scmd->cdb.cmd_cdb[3] |= alt ? 1 : 0;
	scmd->cdb.cmd_cdb[4] = trackno;
	scmd->cdb.cmd_cdb[5] = tindex;
	i_to_3_byte(&scmd->cdb.cmd_cdb[6], packet_size);
	scmd->cdb.cmd_cdb[9] = (write_mode & 0x03) << 6;

	scgp->cmdname = "cw7501 set_mode";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

LOCAL int
cw7501_finalize(scgp, pad, fixed)
	SCSI	*scgp;
	int	pad;
	int	fixed;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->timeout = 8 * 60;		/* Needs up to 4 minutes */
	scmd->cdb.g1_cdb.cmd = 0xE3;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	scmd->cdb.cmd_cdb[1] = pad ? 1 : 0;
	scmd->cdb.cmd_cdb[8] = fixed & 0x03;

	scgp->cmdname = "cw7501 finalize";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}
