/*
 * This file has been modified for the cdrkit suite.
 *
 * The behaviour and appearence of the program code below can differ to a major
 * extent from the version distributed by the original author(s).
 *
 * For details, see Changelog file distributed with the cdrkit package. If you
 * received this file from another source then ask the distributing person for
 * a log of modifications.
 *
 */

/* @(#)drv_sony.c	1.72 05/05/16 Copyright 1997-2005 J. Schilling */
/*
 *	CDR device implementation for
 *	Sony
 *
 *	Copyright (c) 1997-2005 J. Schilling
 */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * 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.
 */

/*#define	SONY_DEBUG*/

#include <mconfig.h>

#include <stdio.h>
#include <stdxlib.h>
#include <unixstd.h>	/* Include sys/types.h to make off_t available */
#include <standard.h>
#include <fctldefs.h>
#include <errno.h>
#include <strdefs.h>
#include <timedefs.h>

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

#include <usal/usalcmd.h>
#include <usal/scsidefs.h>
#include <usal/scsireg.h>
#include <usal/scsitransp.h>

#include "wodim.h"

#ifdef	SONY_DEBUG
#	define		inc_verbose()	usalp->verbose++
#	define		dec_verbose()	usalp->verbose--
#else
#	define		inc_verbose()
#	define		dec_verbose()
#endif

extern	int	debug;
extern	int	lverbose;

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

struct sony_924_mode_page_20 {	/* mastering information */
		MP_P_CODE;		/* parsave & pagecode */
	Uchar	p_len;			/* 0x06 = 6 Bytes */
	Uchar	subcode_header_off;
	Ucbit	res3_0		: 1;
	Ucbit	speudo		: 1;
	Ucbit	res3_2		: 1;
	Ucbit	c2po		: 1;
	Ucbit	subcode_ecc	: 1;
	Ucbit	res3_567	: 3;
	Uchar	res_4;
	Uchar	cue_sheet_opt;
	Uchar	res[2];
};

#else				/* Motorola byteorder */

struct sony_924_mode_page_20 {	/* mastering information */
		MP_P_CODE;		/* parsave & pagecode */
	Uchar	p_len;			/* 0x06 = 6 Bytes */
	Uchar	subcode_header_off;
	Ucbit	res3_567	: 3;
	Ucbit	subcode_ecc	: 1;
	Ucbit	c2po		: 1;
	Ucbit	res3_2		: 1;
	Ucbit	speudo		: 1;
	Ucbit	res3_0		: 1;
	Uchar	res_4;
	Uchar	cue_sheet_opt;
	Uchar	res[2];
};
#endif

struct sony_924_mode_page_22 {	/* disk information */
		MP_P_CODE;		/* parsave & pagecode */
	Uchar	p_len;			/* 0x1E = 30 Bytes */
	Uchar	disk_style;
	Uchar	disk_type;
	Uchar	first_track;
	Uchar	last_track;
	Uchar	numsess;
	Uchar	res_7;
	Uchar	disk_appl_code[4];
	Uchar	last_start_time[4];
	Uchar	disk_status;
	Uchar	num_valid_nra;
	Uchar	track_info_track;
	Uchar	post_gap;
	Uchar	disk_id_code[4];
	Uchar	lead_in_start[4];
	Uchar	res[4];
};

struct sony_924_mode_page_23 {	/* track information */
		MP_P_CODE;		/* parsave & pagecode */
	Uchar	p_len;			/* 0x22 = 34 Bytes */
	Uchar	res_2;
	Uchar	track_num;
	Uchar	data_form;
	Uchar	write_method;
	Uchar	session;
	Uchar	track_status;
	Uchar	start_lba[4];
	Uchar	next_recordable_addr[4];
	Uchar	blank_area_cap[4];
	Uchar	fixed_packet_size[4];
	Uchar	res_24;
	Uchar	starting_msf[3];
	Uchar	res_28;
	Uchar	ending_msf[3];
	Uchar	res_32;
	Uchar	next_rec_time[3];
};

struct sony_924_mode_page_31 {	/* drive speed */
		MP_P_CODE;		/* parsave & pagecode */
	Uchar	p_len;			/* 0x02 = 2 Bytes */
	Uchar	speed;
	Uchar	res;
};

struct cdd_52x_mode_data {
	struct scsi_mode_header	header;
	union cdd_pagex	{
		struct sony_924_mode_page_20	page_s20;
		struct sony_924_mode_page_22	page_s22;
		struct sony_924_mode_page_23	page_s23;
		struct sony_924_mode_page_31	page_s31;
	} pagex;
};

struct sony_write_parameter {
	Uchar	res0;			/* Reserved (must be zero)	*/
	Uchar	len;			/* Parameter length 0x32 == 52	*/
	Uchar	res2;			/* Reserved (must be zero)	*/
#if defined(_BIT_FIELDS_LTOH)	/* Intel byteorder */
	Ucbit	res3_05		: 6;	/* Reserved			*/
	Ucbit	ms		: 2;	/* Multi session mode		*/
#else				/* Motorola byteorder */
	Ucbit	ms		: 2;	/* Multi session mode		*/
	Ucbit	res3_05		: 6;	/* Reserved			*/
#endif
	Uchar	resx[12];
#if defined(_BIT_FIELDS_LTOH)	/* Intel byteorder */
	Ucbit	res16_06	: 7;	/* Reserved			*/
	Ucbit	mcval		: 1;	/* MCN valid			*/
#else				/* Motorola byteorder */
	Ucbit	mcval		: 1;	/* MCN valid			*/
	Ucbit	res16_06	: 7;	/* Reserved			*/
#endif
	Uchar	mcn[15];
#if defined(_BIT_FIELDS_LTOH)	/* Intel byteorder */
	Ucbit	res32_06	: 7;	/* Reserved			*/
	Ucbit	icval		: 1;	/* ISRC valid			*/
#else				/* Motorola byteorder */
	Ucbit	icval		: 1;	/* ISRC valid			*/
	Ucbit	res32_06	: 7;	/* Reserved			*/
#endif
	Uchar	isrc[15];
	Uchar	subheader[4];
};

struct sony_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..5 Main channel Format	*/
					/* Bit 6..7 SubChannel format	*/
	Uchar	cs_zero;		/* Reserved or MCN/ISRC		*/
	Uchar	cs_min;			/* Absolute time minutes	*/
	Uchar	cs_sec;			/* Absolute time seconds	*/
	Uchar	cs_frame;		/* Absolute time frames		*/
};


#define	strbeg(s1, s2)	(strstr((s2), (s1)) == (s2))

static	int	write_start_sony(SCSI *usalp, caddr_t bp, int size);
static	int	write_continue_sony(SCSI *usalp, caddr_t bp, long sectaddr, 
											  long size, int blocks, BOOL islast);
static	int	discontinue_sony(SCSI *usalp);
static	int	write_track_sony(SCSI *usalp, long track, int sectype);
static	int	close_track_sony(SCSI *usalp, cdr_t *dp, track_t *trackp);
static	int	flush_sony(SCSI *usalp, int track);
static	int	finalize_sony(SCSI *usalp, cdr_t *dp, track_t *trackp);
static	int	recover_sony(SCSI *usalp, cdr_t *dp, int track);
static	int	set_wr_parameter_sony(SCSI *usalp, caddr_t bp, int size);
static	int	next_wr_addr_sony(SCSI *usalp, track_t *trackp, long *ap);
static	int	reserve_track_sony(SCSI *usalp, unsigned long len);
static	int	init_sony(SCSI *usalp, cdr_t *dp);
static	int	getdisktype_sony(SCSI *usalp, cdr_t *dp);
static	void	di_to_dstat_sony(struct sony_924_mode_page_22 *dip, 
										  dstat_t *dsp);
static	int	speed_select_sony(SCSI *usalp, cdr_t *dp, int *speedp);
static	int	next_writable_address_sony(SCSI *usalp, long *ap, int track, 
														int sectype, int tracktype);
static	int	new_track_sony(SCSI *usalp, int track, int sectype, 
										int tracktype);
static	int	open_track_sony(SCSI *usalp, cdr_t *dp, track_t *trackp);
static	int	open_session_sony(SCSI *usalp, cdr_t *dp, track_t *trackp);
static	int	abort_session_sony(SCSI *usalp, cdr_t *dp);
static	int	get_page22_sony(SCSI *usalp, char *mode);
static	int	gen_cue_sony(track_t *trackp, void *vcuep, BOOL needgap);
static	void	fillcue(struct sony_cue *cp, int ca, int tno, int idx, int dataform, int scms, msf_t *mp);
static	int	send_cue_sony(SCSI *usalp, cdr_t *dp, track_t *trackp);
static	int	write_leadin_sony(SCSI *usalp, cdr_t *dp, track_t *trackp);
static	int	sony_attach(SCSI *usalp, cdr_t *dp);
#ifdef	SONY_DEBUG
static	void	print_sony_mp22(struct sony_924_mode_page_22 *xp, int len);
static	void	print_sony_mp23(struct sony_924_mode_page_23 *xp, int len);
#endif
static	int	buf_cap_sony(SCSI *usalp, long *, long *);

cdr_t	cdr_sony_cdu924 = {
	0, 0,
	CDR_TAO|CDR_SAO|CDR_CADDYLOAD|CDR_SWABAUDIO,
	CDR_CDRW_NONE,
	2, 4,
	"sony_cdu924",
	"driver for Sony CDU-924 / CDU-948",
	0,
	(dstat_t *)0,
	drive_identify,
	sony_attach,
	init_sony,
	getdisktype_sony,
	scsi_load,
	scsi_unload,
	buf_cap_sony,
	cmd_dummy,					/* recovery_needed */
	recover_sony,
	speed_select_sony,
	select_secsize,
	next_wr_addr_sony,
	reserve_track_sony,
	write_continue_sony,
	gen_cue_sony,
	send_cue_sony,
	write_leadin_sony,
	open_track_sony,
	close_track_sony,
	open_session_sony,
	cmd_dummy,
	abort_session_sony,
	read_session_offset_philips,
	finalize_sony,
	cmd_dummy,					/* stats	*/
	blank_dummy,
	format_dummy,
	(int(*)(SCSI *, caddr_t, int, int))NULL,	/* no OPC	*/
	cmd_dummy,					/* opt1		*/
	cmd_dummy,					/* opt2		*/
};

static int
write_start_sony(SCSI *usalp, caddr_t bp, int size)
{
	register struct	usal_cmd	*scmd = usalp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = size;
	scmd->flags = SCG_DISRE_ENA|SCG_CMD_RETRY;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->sense_len = 26;
	scmd->cdb.g1_cdb.cmd = 0xE0;
	scmd->cdb.g1_cdb.lun = usal_lun(usalp);
	g0_cdbaddr(&scmd->cdb.g0_cdb, size); /* Hack, but Sony is silly */

	usalp->cmdname = "write_start";

	if (usal_cmd(usalp) < 0)
		return (-1);
	return (0);
}

static int
write_continue_sony(SCSI *usalp, 
                    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 */)
{
	register struct	usal_cmd	*scmd = usalp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = size;
	scmd->flags = SCG_DISRE_ENA|SCG_CMD_RETRY;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0xE1;
	scmd->cdb.g1_cdb.lun = usal_lun(usalp);
	g0_cdbaddr(&scmd->cdb.g0_cdb, size); /* Hack, but Sony is silly */

	usalp->cmdname = "write_continue";

	if (usal_cmd(usalp) < 0) {
		/*
		 * XXX This seems to happen only sometimes.
		 */
		if (usal_sense_code(usalp) != 0x80)
			return (-1);
	}
	return (size - usal_getresid(usalp));
}

static int
discontinue_sony(SCSI *usalp)
{
	register struct	usal_cmd	*scmd = usalp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->flags = SCG_DISRE_ENA|SCG_CMD_RETRY;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0xE2;
	scmd->cdb.g1_cdb.lun = usal_lun(usalp);

	usalp->cmdname = "discontinue";

	if (usal_cmd(usalp) < 0)
		return (-1);
	return (0);
}

static int
write_track_sony(SCSI *usalp, 
                 long track     /* track number 0 == new track */, 
                 int sectype    /* no sectype for Sony write track */)
{
	register struct	usal_cmd	*scmd = usalp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->flags = SCG_DISRE_ENA|SCG_CMD_RETRY;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0xF5;
	scmd->cdb.g1_cdb.lun = usal_lun(usalp);
	g1_cdbaddr(&scmd->cdb.g1_cdb, track);

	usalp->cmdname = "write_track";

	if (usal_cmd(usalp) < 0)
		return (-1);
	return (0);
}

/* XXX NOCH NICHT FERTIG */
static int
close_track_sony(SCSI *usalp, cdr_t *dp, track_t *trackp)
{
	register struct	usal_cmd	*scmd = usalp->scmd;
	int	track = 0;

	if (!is_tao(trackp) && !is_packet(trackp))
		return (0);

	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 = 0xF0;
	scmd->cdb.g1_cdb.lun = usal_lun(usalp);
	g1_cdbaddr(&scmd->cdb.g1_cdb, track);
/* XXX Padding ??? (bit 0 in addr[0] / CDB[2]) */

	usalp->cmdname = "close_track";

	if (usal_cmd(usalp) < 0)
		return (-1);

	/*
	 * Clear the silly "error situation" from Sony´ dummy write end
	 * but notify if real errors occurred.
	 */
	usalp->silent++;
	if (test_unit_ready(usalp) < 0 && usal_sense_code(usalp) != 0xD4) {
		usalp->cmdname = "close_track/test_unit_ready";
		usal_printerr(usalp);
	}
	usalp->silent--;

	return (0);
}

static int
finalize_sony(SCSI *usalp, cdr_t *dp, track_t *trackp)
{
	register struct	usal_cmd	*scmd = usalp->scmd;
	int	dummy = track_base(trackp)->tracktype & TOCF_DUMMY;

	if (!is_tao(trackp) && !is_packet(trackp)) {
		wait_unit_ready(usalp, 240);
		return (0);
	}
	if (dummy) {
		printf("Fixating is not possible in dummy write mode.\n");
		return (0);
	}
	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 = 0xF1;
	scmd->cdb.g1_cdb.lun = usal_lun(usalp);
	scmd->cdb.g1_cdb.count[1] = ((track_base(trackp)->tracktype & TOCF_MULTI) ? 1 : 0);
/* XXX Padding ??? (bit 0 in addr[0] / CDB[2]) */

	usalp->cmdname = "finalize";

	if (usal_cmd(usalp) < 0)
		return (-1);
	return (0);
}

static int
flush_sony(SCSI *usalp, int track)
{
	register struct	usal_cmd	*scmd = usalp->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 = 0xF2;
	scmd->cdb.g1_cdb.lun = usal_lun(usalp);
	scmd->cdb.cmd_cdb[5] = track;
/* XXX POE ???	   (bit 1 in addr[0] / CDB[2]) */
/* XXX Padding ??? (bit 0 in addr[0] / CDB[2]) */
/* XXX Partial flush ??? (CDB[3]) */

	usalp->cmdname = "flush";

	if (usal_cmd(usalp) < 0)
		return (-1);
	return (0);
}

static int
recover_sony(SCSI *usalp, cdr_t *dp, int track)
{
	register struct	usal_cmd	*scmd = usalp->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 = 0xF6;
	scmd->cdb.g1_cdb.lun = usal_lun(usalp);
	scmd->cdb.g1_cdb.addr[3] = track;

	usalp->cmdname = "recover";

	if (usal_cmd(usalp) < 0)
		return (-1);
	return (0);
}

static int
set_wr_parameter_sony(SCSI *usalp, caddr_t bp, int size)
{
	register struct	usal_cmd	*scmd = usalp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = size;
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0xF8;
	scmd->cdb.g1_cdb.lun = usal_lun(usalp);
	g1_cdblen(&scmd->cdb.g1_cdb, size);

	usalp->cmdname = "set_write_parameter";

	if (usal_cmd(usalp) < 0)
		return (-1);
	return (0);
}

static int
next_wr_addr_sony(SCSI *usalp, track_t *trackp, long *ap)
{
	if (next_writable_address_sony(usalp, ap, 0, 0, 0) < 0)
		return (-1);
	return (0);
}

static int
reserve_track_sony(SCSI *usalp, unsigned long len)
{
	register struct	usal_cmd	*scmd = usalp->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 = 0xF3;
	scmd->cdb.g1_cdb.lun = usal_lun(usalp);
	i_to_4_byte(&scmd->cdb.g1_cdb.addr[3], len);

	usalp->cmdname = "reserve_track";

	if (usal_cmd(usalp) < 0)
		return (-1);
	return (0);
}

static int
init_sony(SCSI *usalp, cdr_t *dp)
{
	return (speed_select_sony(usalp, dp, NULL));
}


#define	IS(what, flag)	printf("  Is %s%s\n", flag?"":"not ", what);

static int
getdisktype_sony(SCSI *usalp, cdr_t *dp)
{
	dstat_t	*dsp = dp->cdr_dstat;
	long	dummy;
	long	lst;
	msf_t	msf;

	char			mode[256];
	struct scsi_mode_page_header	*mp;
	struct sony_924_mode_page_22	*xp;

	dummy = get_page22_sony(usalp, mode);
	if (dummy >= 0) {
		mp = (struct scsi_mode_page_header *)
			(mode + sizeof (struct scsi_mode_header) +
			((struct scsi_mode_header *)mode)->blockdesc_len);

		xp = (struct sony_924_mode_page_22 *)mp;

		if (xp->disk_appl_code[0] == 0xFF)
			dummy = -1;
	} else {
		return (drive_getdisktype(usalp, dp));
	}

	if ((dp->cdr_dstat->ds_cdrflags & RF_PRATIP) != 0 && dummy >= 0) {

		printf("ATIP info from disk:\n");
		printf("  Indicated writing power: %d\n",
				(unsigned)(xp->disk_appl_code[1] & 0x70) >> 4);
		IS("unrestricted", xp->disk_appl_code[2] & 0x40);
		printf("  Disk application code: %d\n", xp->disk_appl_code[2] & 0x3F);
		msf.msf_min = xp->lead_in_start[1];
		msf.msf_sec = xp->lead_in_start[2];
		msf.msf_frame = xp->lead_in_start[3];
		lst = msf_to_lba(msf.msf_min, msf.msf_sec, msf.msf_frame, FALSE);
		if (lst  < -150) {
			/*
			 * The Sony CDU 920 seems to deliver 00:00/00 for
			 * lead-in start time, dont use it.
			 */
			printf("  ATIP start of lead in:  %ld (%02d:%02d/%02d)\n",
				msf_to_lba(msf.msf_min, msf.msf_sec, msf.msf_frame, FALSE),
				msf.msf_min, msf.msf_sec, msf.msf_frame);
		}
		msf.msf_min = xp->last_start_time[1];
		msf.msf_sec = xp->last_start_time[2];
		msf.msf_frame = xp->last_start_time[3];
		printf("  ATIP start of lead out: %ld (%02d:%02d/%02d)\n",
			msf_to_lba(msf.msf_min, msf.msf_sec, msf.msf_frame, TRUE),
			msf.msf_min, msf.msf_sec, msf.msf_frame);
		if (lst  < -150) {
			/*
			 * The Sony CDU 920 seems to deliver 00:00/00 for
			 * lead-in start time, dont use it.
			 */
			msf.msf_min = xp->lead_in_start[1];
			msf.msf_sec = xp->lead_in_start[2];
			msf.msf_frame = xp->lead_in_start[3];
			pr_manufacturer(&msf,
					FALSE,	/* Always not erasable */
					(xp->disk_appl_code[2] & 0x40) != 0);
		}
	}
	if (dummy >= 0)
		di_to_dstat_sony(xp, dsp);
	return (drive_getdisktype(usalp, dp));
}

static void
di_to_dstat_sony(struct sony_924_mode_page_22 *dip, dstat_t *dsp)
{
	msf_t	msf;

	dsp->ds_diskid = a_to_u_4_byte(dip->disk_id_code);
#ifdef	PROTOTYPES
	if (dsp->ds_diskid != 0xFFFFFFFFUL)
#else
	if (dsp->ds_diskid != (Ulong)0xFFFFFFFF)
#endif
		dsp->ds_flags |= DSF_DID_V;
	dsp->ds_diskstat = (dip->disk_status >> 6) & 0x03;
#ifdef	XXX
	/*
	 * There seems to be no MMC equivalent...
	 */
	dsp->ds_sessstat = dip->sess_status;
#endif

	dsp->ds_maxblocks = msf_to_lba(dip->last_start_time[1],
					dip->last_start_time[2],
					dip->last_start_time[3], TRUE);
	/*
	 * Check for 0xFF:0xFF/0xFF which is an indicator for a complete disk
	 */
	if (dsp->ds_maxblocks == 716730)
		dsp->ds_maxblocks = -1L;

	if (dsp->ds_first_leadin == 0) {
		dsp->ds_first_leadin = msf_to_lba(dip->lead_in_start[1],
						dip->lead_in_start[2],
						dip->lead_in_start[3], FALSE);
		/*
		 * Check for illegal values (> 0)
		 * or for empty field (-150) with CDU-920.
		 */
		if (dsp->ds_first_leadin > 0 || dsp->ds_first_leadin == -150)
			dsp->ds_first_leadin = 0;
	}

	if (dsp->ds_last_leadout == 0 && dsp->ds_maxblocks >= 0)
		dsp->ds_last_leadout = dsp->ds_maxblocks;

	msf.msf_min = dip->lead_in_start[1];
	msf.msf_sec = dip->lead_in_start[2];
	msf.msf_frame = dip->lead_in_start[3];
	dsp->ds_maxrblocks = disk_rcap(&msf, dsp->ds_maxblocks,
					FALSE,	/* Always not erasable */
					(dip->disk_appl_code[2] & 0x40) != 0);
}


int	sony_speeds[] = {
		-1,		/* Speed null is not allowed */
		0,		/* Single speed */
		1,		/* Double speed */
		-1,		/* Three times */
		3,		/* Quad speed */
};

static int
speed_select_sony(SCSI *usalp, cdr_t *dp, int *speedp)
{
	struct cdd_52x_mode_data md;
	int	count;
	int	err;
	int	speed = 1;
	BOOL	dummy = (dp->cdr_cmdflags & F_DUMMY) != 0;

	if (speedp) {
		speed = *speedp;
		if (speed < 1 || speed > 4 || sony_speeds[speed] < 0)
			return (-1);
	}

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

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

	md.pagex.page_s20.p_code = 0x20;
	md.pagex.page_s20.p_len =  0x06;
	md.pagex.page_s20.speudo = dummy?1:0;

	/*
	 * Set Cue sheet option. This is documented for the 924 and
	 * seems to be supported for the 948 too.
	 */
	md.pagex.page_s20.cue_sheet_opt = 0x03;

	err = mode_select(usalp, (Uchar *)&md, count, 0, 1);
	if (err < 0)
		return (err);

	if (speedp == 0)
		return (0);

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

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

	md.pagex.page_s31.p_code = 0x31;
	md.pagex.page_s31.p_len =  0x02;
	md.pagex.page_s31.speed = sony_speeds[speed];

	return (mode_select(usalp, (Uchar *)&md, count, 0, 1));
}

static int
next_writable_address_sony(SCSI *usalp, long *ap, int track, int sectype, 
                           int tracktype)
{
	struct	scsi_mode_page_header *mp;
	char			mode[256];
	int			len = 0x30;
	int			page = 0x23;
	struct sony_924_mode_page_23	*xp;

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

	inc_verbose();
	if (!get_mode_params(usalp, page, "CD track information",
			(Uchar *)mode, (Uchar *)0, (Uchar *)0, (Uchar *)0, &len)) {
		dec_verbose();
		return (-1);
	}
	dec_verbose();
	if (len == 0)
		return (-1);

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


	xp = (struct sony_924_mode_page_23 *)mp;

#ifdef	SONY_DEBUG
	print_sony_mp23(xp, len);
#endif
	if (ap)
		*ap = a_to_4_byte(xp->next_recordable_addr);
	return (0);
}


static int
new_track_sony(SCSI *usalp, int track, int sectype, int tracktype)
{
	struct	scsi_mode_page_header *mp;
	char			mode[256];
	int			len = 0x30;
	int			page = 0x23;
	struct sony_924_mode_page_23	*xp;
	int	i;

	fillbytes((caddr_t)mode, sizeof (mode), '\0');
	get_page22_sony(usalp, mode);

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

	inc_verbose();
	if (!get_mode_params(usalp, page, "CD track information",
			(Uchar *)mode, (Uchar *)0, (Uchar *)0, (Uchar *)0, &len)) {
		dec_verbose();
		return (-1);
	}
	dec_verbose();
	if (len == 0)
		return (-1);

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


	xp = (struct sony_924_mode_page_23 *)mp;

#ifdef	SONY_DEBUG
	print_sony_mp23(xp, len);
#endif

	xp->write_method = 0;	/* Track at one recording */

	if (sectype & ST_AUDIOMASK) {
		xp->data_form = (sectype & ST_MASK) == ST_AUDIO_PRE ? 0x02 : 0x00;
	} else {
		if (tracktype == TOC_ROM) {
			xp->data_form = (sectype & ST_MASK) == ST_ROM_MODE1 ? 0x10 : 0x11;
		} else if (tracktype == TOC_XA1) {
			xp->data_form = 0x12;
		} else if (tracktype == TOC_XA2) {
			xp->data_form = 0x12;
		} else if (tracktype == TOC_CDI) {
			xp->data_form = 0x12;
		}
	}

	((struct scsi_modesel_header *)mode)->sense_data_len	= 0;
	((struct scsi_modesel_header *)mode)->res2		= 0;

	i = ((struct scsi_mode_header *)mode)->blockdesc_len;
	if (i > 0) {
		i_to_3_byte(
			((struct scsi_mode_data *)mode)->blockdesc.nlblock,
								0);
	}

	if (mode_select(usalp, (Uchar *)mode, len, 0, usalp->inq->data_format >= 2) < 0) {
		return (-1);
	}

	return (0);
}

static int
open_track_sony(SCSI *usalp, cdr_t *dp, track_t *trackp)
{
	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(usalp, dp, trackp,
				trackp->trackstart-trackp->pregapsize,
				(Llong)trackp->pregapsize*trackp->secsize,
					FALSE, 0);
		}
		return (0);
	}

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

	if (new_track_sony(usalp, trackp->trackno, trackp->sectype, trackp->tracktype & TOC_MASK) < 0)
		return (-1);

	if (write_track_sony(usalp, 0L, trackp->sectype) < 0)
		return (-1);

	return (0);
}

static int
open_session_sony(SCSI *usalp, cdr_t *dp, track_t *trackp)
{
	struct	scsi_mode_page_header *mp;
	char			mode[256];
	int	i;
	int	len = 0x30;
	struct sony_924_mode_page_22	*xp;

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

	if ((len = get_page22_sony(usalp, mode)) < 0)
		return (-1);

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

	xp = (struct sony_924_mode_page_22 *)mp;

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

	if (is_tao(track_base(trackp))) {
#ifdef	__needed__
		if ((track_base(trackp)->tracktype & TOC_MASK) == TOC_DA)
			xp->disk_style = 0x80;
		else
			xp->disk_style = 0xC0;
#endif
	} else if (is_sao(track_base(trackp))) {
		/*
		 * We may only change this value if the disk is empty.
		 * i.e. when disk_status & 0xC0 == 0x00
		 */
		if ((xp->disk_status & 0xC0) != 0) {
			if (xp->disk_style != 0x00)
				errmsgno(EX_BAD, "Cannot change disk stile for recorded disk.\n");
		}
		xp->disk_style = 0x00;
	}

	((struct scsi_modesel_header *)mode)->sense_data_len	= 0;
	((struct scsi_modesel_header *)mode)->res2		= 0;

	i = ((struct scsi_mode_header *)mode)->blockdesc_len;
	if (i > 0) {
		i_to_3_byte(
			((struct scsi_mode_data *)mode)->blockdesc.nlblock,
								0);
	}

	if (mode_select(usalp, (Uchar *)mode, len, 0, usalp->inq->data_format >= 2) < 0) {
		return (-1);
	}
/*
 * XXX set write parameter für SAO mit Multi Session (948 only?)
 * XXX set_wr_parameter_sony(usalp, bp, size);
 */
	return (0);
}

static int
abort_session_sony(SCSI *usalp, cdr_t *dp)
{
	return (discontinue_sony(usalp));
}

static int
get_page22_sony(SCSI *usalp, char *mode)
{
	struct	scsi_mode_page_header *mp;
	int	len = 0x30;
	int	page = 0x22;
	struct sony_924_mode_page_22	*xp;

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

	inc_verbose();
	if (!get_mode_params(usalp, page, "CD disk information",
			(Uchar *)mode, (Uchar *)0, (Uchar *)0, (Uchar *)0, &len)) {
		dec_verbose();
		return (-1);
	}
	dec_verbose();
	if (len == 0)
		return (-1);

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

	xp = (struct sony_924_mode_page_22 *)mp;

#ifdef	SONY_DEBUG
	print_sony_mp22(xp, len);
#endif
	return (len);
}

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

static Uchar	db2df[] = {
	0x01,			/*  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			*/
	0x11,			/*  8 2048 bytes Mode 1 (ISO/IEC 10149)		*/
	0xFF,			/*  9 2336 bytes Mode 2 (ISO/IEC 10149)		*/
	0xFF,			/* 10 2048 bytes Mode 2! (CD-ROM XA form 1)	*/
	0xFF,			/* 11 2056 bytes Mode 2 (CD-ROM XA form 1)	*/
	0xFF,			/* 12 2324 bytes Mode 2 (CD-ROM XA form 2)	*/
	0xFF,			/* 13 2332 bytes Mode 2 (CD-ROM XA 1/2+subhdr)	*/
	0xFF,			/* 14 -    Reserved				*/
	0xFF,			/* 15 -    Vendor specific			*/
};

static int
gen_cue_sony(track_t *trackp, void *vcuep, BOOL needgap)
{
	int	tracks = trackp->tracks;
	int	i;
	struct sony_cue	**cuep = vcuep;
	struct sony_cue	*cue;
	struct sony_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 = db2df[trackp[i].dbtype & 0x0F];

#ifdef	__supported__
		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);
			}
		}
#endif
		if (i == 0) {	/* Lead in */
			df &= ~7;
			if (trackp[0].flags & TI_TEXT)	/* CD-Text in Lead-in*/
				df |= 0xC0;
			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 = db2df[trackp[tracks+1].dbtype & 0x0F];
	df &= ~7;
	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++) {
			usal_prbytes("", (Uchar *)&cue[i], 8);
		}
	}
	if (cuep)
		*cuep = cue;
	else
		free(cue);
	return (ncue);
}


static void
fillcue(struct sony_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);
	cp->cs_dataform = dataform;
	cp->cs_zero = scms;
	cp->cs_min = to_bcd(mp->msf_min);
	cp->cs_sec = to_bcd(mp->msf_sec);
	cp->cs_frame = to_bcd(mp->msf_frame);
}

static int
send_cue_sony(SCSI *usalp, cdr_t *dp, track_t *trackp)
{
	struct sony_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);

	usalp->silent++;
	ret  = write_start_sony(usalp, (caddr_t)cp, ncue*8);
	usalp->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  = write_start_sony(usalp, (caddr_t)cp, ncue*8);
		free(cp);
	}
	if (ret >= 0 && lverbose) {
		gettimeofday(&stoptime, (struct timezone *)0);
		prtimediff("Write Lead-in time: ", &starttime, &stoptime);
	}
	return (ret);
}

static int
write_leadin_sony(SCSI *usalp, cdr_t *dp, track_t *trackp)
{
	Uint	i;
	long	startsec = 0L;

/*	if (flags & F_SAO) {*/
	if (wm_base(dp->cdr_dstat->ds_wrmode) == WM_SAO) {
		if (debug || lverbose) {
			printf("Sending CUE sheet...\n");
			flush();
		}
		if (trackp[0].flags & TI_TEXT) {
			if (dp->cdr_speeddef != 4) {
				errmsgno(EX_BAD,
				"The CDU-924 does not support CD-Text, disabling.\n");

				trackp[0].flags &= ~TI_TEXT;
			}
		}
		if ((*dp->cdr_send_cue)(usalp, dp, trackp) < 0) {
			errmsgno(EX_BAD, "Cannot send CUE sheet.\n");
			return (-1);
		}

		if (trackp[0].flags & TI_TEXT) {
			startsec = dp->cdr_dstat->ds_first_leadin;
			printf("SAO startsec: %ld\n", startsec);
		} else {
			startsec = -150;
		}
		if (debug)
			printf("SAO startsec: %ld\n", startsec);

		if (trackp[0].flags & TI_TEXT) {
			if (startsec > 0) {
				errmsgno(EX_BAD, "CD-Text must be in first session.\n");
				return (-1);
			}
			if (debug || lverbose)
				printf("Writing lead-in...\n");
			if (write_cdtext(usalp, dp, startsec) < 0)
				return (-1);

			dp->cdr_dstat->ds_cdrflags |= RF_LEADIN;
		} else for (i = 1; i <= trackp->tracks; i++) {
			trackp[i].trackstart += startsec +150;
		}
	}
	return (0);
}

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

static const char *sd_cdu_924_error_str[] = {

	"\200\000write complete",				/* 80 00 */
	"\201\000logical unit is reserved",			/* 81 00 */
	"\205\000audio address not valid",			/* 85 00 */
	"\210\000illegal cue sheet",				/* 88 00 */
	"\211\000inappropriate command",			/* 89 00 */

	"\266\000media load mechanism failed",			/* B6 00 */
	"\271\000audio play operation aborted",			/* B9 00 */
	"\277\000buffer overflow for read all subcodes command", /* BF 00 */
	"\300\000unrecordable disk",				/* C0 00 */
	"\301\000illegal track status",				/* C1 00 */
	"\302\000reserved track present",			/* C2 00 */
	"\303\000buffer data size error",			/* C3 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 at once recording",	/* C5 00 */
	"\306\001unwritten area encountered",			/* C6 01 */
	"\306\002link blocks encountered",			/* C6 02 */
	"\306\003nonexistent block encountered",		/* C6 03 */
	"\307\000disk style mismatch",				/* C7 00 */
	"\310\000no table of contents",				/* C8 00 */
	"\311\000illegal block length for write command",	/* C9 00 */
	"\312\000power calibration error",			/* CA 00 */
	"\313\000write error",					/* CB 00 */
	"\313\001write error track recovered",			/* CB 01 */
	"\314\000not enough space",				/* CC 00 */
	"\315\000no track present to finalize",			/* CD 00 */
	"\316\000unrecoverable track descriptor encountered",	/* CE 00 */
	"\317\000damaged track present",			/* CF 00 */
	"\320\000pma area full",				/* D0 00 */
	"\321\000pca area full",				/* D1 00 */
	"\322\000unrecoverable damaged track cause too small writing area",	/* D2 00 */
	"\323\000no bar code",					/* D3 00 */
	"\323\001not enough bar code margin",			/* D3 01 */
	"\323\002no bar code start pattern",			/* D3 02 */
	"\323\003illegal bar code length",			/* D3 03 */
	"\323\004illegal bar code format",			/* D3 04 */
	"\324\000exit from pseudo track at once recording",	/* D4 00 */
	NULL
};

static int
sony_attach(SCSI *usalp, cdr_t *dp)
{
	if (usalp->inq != NULL) {
		if (strbeg("CD-R   CDU94", usalp->inq->prod_ident)) {
			dp->cdr_speeddef = 4;
		}
	}
	usal_setnonstderrs(usalp, sd_cdu_924_error_str);
	return (0);
}

#ifdef	SONY_DEBUG
static void
print_sony_mp22(struct sony_924_mode_page_22 *xp, int len)
{
	printf("disk style: %X\n", xp->disk_style);
	printf("disk type: %X\n", xp->disk_type);
	printf("first track: %X\n", xp->first_track);
	printf("last track: %X\n", xp->last_track);
	printf("numsess:    %X\n", xp->numsess);
	printf("disk appl code: %lX\n", a_to_u_4_byte(xp->disk_appl_code));
	printf("last start time: %lX\n", a_to_u_4_byte(xp->last_start_time));
	printf("disk status: %X\n", xp->disk_status);
	printf("num valid nra: %X\n", xp->num_valid_nra);
	printf("track info track: %X\n", xp->track_info_track);
	printf("post gap: %X\n", xp->post_gap);
	printf("disk id code: %lX\n", a_to_u_4_byte(xp->disk_id_code));
	printf("lead in start: %lX\n", a_to_u_4_byte(xp->lead_in_start));
}

static void
print_sony_mp23(struct sony_924_mode_page_23 *xp, int len)
{
	printf("len: %d\n", len);

	printf("track num: %X\n", xp->track_num);
	printf("data form: %X\n", xp->data_form);
	printf("write method: %X\n", xp->write_method);
	printf("session: %X\n", xp->session);
	printf("track status: %X\n", xp->track_status);

/*
 * XXX Check for signed/unsigned a_to_*() conversion.
 */
	printf("start lba: %lX\n", a_to_4_byte(xp->start_lba));
	printf("next recordable addr: %lX\n", a_to_4_byte(xp->next_recordable_addr));
	printf("blank area cap: %lX\n", a_to_u_4_byte(xp->blank_area_cap));
	printf("fixed packet size: %lX\n", a_to_u_4_byte(xp->fixed_packet_size));
	printf("starting msf: %lX\n", a_to_u_4_byte(xp->starting_msf));
	printf("ending msf: %lX\n", a_to_u_4_byte(xp->ending_msf));
	printf("next rec time: %lX\n", a_to_u_4_byte(xp->next_rec_time));
}
#endif

static int
buf_cap_sony(SCSI *usalp, long *sp, long *fp)
{
	char	resp[8];
	Ulong	freespace;
	Ulong	bufsize;
	int	per;
	register struct	usal_cmd	*scmd = usalp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = (caddr_t)resp;
	scmd->size = sizeof (resp);
	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 = 0xEC;		/* Read buffer cap */
	scmd->cdb.g1_cdb.lun = usal_lun(usalp);

	usalp->cmdname = "read buffer cap sony";

	if (usal_cmd(usalp) < 0)
		return (-1);

	bufsize   = a_to_u_3_byte(&resp[1]);
	freespace = a_to_u_3_byte(&resp[5]);
	if (sp)
		*sp = bufsize;
	if (fp)
		*fp = freespace;

	if (usalp->verbose || (sp == 0 && fp == 0))
		printf("BFree: %ld K BSize: %ld K\n", freespace >> 10, bufsize >> 10);

	if (bufsize == 0)
		return (0);
	per = (100 * (bufsize - freespace)) / bufsize;
	if (per < 0)
		return (0);
	if (per > 100)
		return (100);
	return (per);
}
