/*3456789012345678901234567890123456789012345678901234567890123456789012345678*/
/*
    linux/kernel/blk_drv/sbpcd.c 

    - CD-ROM device driver for the whole Kotobuki/Matsushita/Panasonic
      CR-5xx drives family
    - for SoundBlaster ("Pro" or "16 ASP" or compatible) cards
    - works with "no-sound" interfaces (Lasermate, ...), too

    Copyright (C) 1993  Eberhard Moenkeberg, emoenke@gwdg.de
      (I would prefer my fido address 2:241/3410.27, but currently
       the German Fido Fuehrers inhibit delivery of my mail)

    The FTP-home of this driver is ftp.gwdg.de:/pub/linux/cdrom.
    I will serve tsx-11.mit.edu, sunsite.unc.edu and nic.funet.fi, too.

    :-(
    This is release 0.4. I consider this "less than fully fit" - so think
    of it as ALPHA or BETA before you decide to give it a try.
    It works with my SbPro & drive CR-521 V2.11 from 2/92.
    :-)

    CHANGES:
    0.1->0.2:
         the "repeat:"-loop in do_sbpcd_request did not check for
              end-of-request_queue (resulting in kernel panic).
         flow control seems stable, but throughput is not better.  
    0.2->0.3:
         interrupt locking totally eliminated (maybe "inb" and "outb"
              are still locking) - 0.2 made keyboard-type-ahead losses.
         check_sbpcd_media_change added (to use by isofs/inode.c) - but
              it detects almost nothing.
    0.3->0.4:
         use MAJOR 25 definitely.
         almost total re-design to support double-speed drives and
              "naked" (no sound) interface cards.
         flow control should be exact now (tell me if not).
         don't occupy the SbPro IRQ line (not needed either); will
              live together with Hannu Savolainen's sndkit now.
	 speeded up data transfer to 150 kB/sec (who will tell me the
              rate of a "double-speed" drive?).
         give "SpinUp" command if necessary.
         first steps to support up to 4 drives (but currently only one).
         implemented audio capabilities - workman should work, xcdplayer
              gives some problems.
         this version is still consumpting too much CPU - timing and
              sleeping still has to be worked on.
         during "long" implied seeks, it seems possible that a ReadStatus
              command gets ignored. That gives the message "ResponseStatus
              timed out" (happens about 6 times here during a "ls -alR" of
              the YGGDRASIL LGX-Beta CD). Such a case is handled without
              data error, but it should get done better.

       special thanks to Kai Makisara (kai.makisara@vtt.fi) for his fine
       elaborated speed-up experiments (and the famous results!) and some
       additional hints and bug reports.
  

    8=}

    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
    (for example /usr/src/linux/COPYING); if not, write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/
/*****************************************************************************/
/*****************************************************************************/
/* put your changes here *****************************************************/
/*****************************************************************************/
/*****************************************************************************/
/*
 * kernel 0.99pl11 needs a different READ_DATA macro
 * set to 1 if you have pl11 or newer
 */
#define PL11 1

/* 
 * change this to select the type of your interface board:
 * select SBPRO for any compatible sound card
 * select LASERMATE for "poor" (no sound) interface cards
 * just set to 0 what you don't have, set yours to 1. 
 * mail me if it does not work with your board...
 * some "compatible" sound boards need to select LASERMATE !!!
 */
#define SBPRO     1
#define LASERMATE 0

static int card_type=SBPRO;

/*
 * put your (configured or default) I/O port base address here:
 * SBPRO addresses typically are 0x0220, 0x0240, ...
 * LASERMATE adresses typically are 0x0300, 0x0310, ...
 */
static int CARD_BASE=0x0220;

/*
 * you can set the interrupt number of your interface board here:
 * It is not used at this time. No need to set it correctly.
 */
static int SBPCD_INTR_NR =  -1;           

/*****************************************************************************/
/*****************************************************************************/
/* nothing to change below here if you are not experimenting *****************/
/*****************************************************************************/
/*****************************************************************************/

/*
 * steering the "printk" output (use with care)
 */
#define testi      0  /* interrupt trace */
#define testr      0  /* "read" status trace */
#define test_chk   0  /* "media check" trace */
#define testt      0  /* datarate timer test */
#define testini    0  /* initialization trace */
#define test_toc   0  /* tell TocEntry values */
#define test_ioctl 0  /* ioctl trace */
#define test_sta   0  /* "ResponseStatus" trace */
#define test_err   0  /* "xx_ReadError" trace */
#define test_cmd   0  /* "cmd_out" trace */

/* 
 * granted by the Linux Device Registrar
 */
#define MAJOR_NR 25  

/*
 * highest allowed drive number (MINOR)
 * currently only one drive, maybe later up to 4
 */
#define NR_SBPCD 0
 
/*
 * we try to never disable interrupts - seems to work
 */
#undef	SBPCD_DISABLE_IRQ

/*
 * we don't use the IRQ line - leave it free for the sound driver
 */
#define SBPCD_USE_IRQ	0

/*
 * still testing best timing of wait loops
 */
#define CDMKE

#undef FUTURE

/*=====================================================*/


#include <linux/config.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/cdrom.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>
#include "blk.h"


#ifndef CONFIG_BLK_DEV_SBPCD
#error "please, \"make config\" again -- enable Soundblaster/Lasermate CD-ROM"
#endif
#ifndef CONFIG_ISO9660_FS
#error "please, \"make config\" again -- iso9660 file system is necessary for CD-ROMs"
#endif



static int BASEADDR;
static int CDi_data;

#define CDo_command BASEADDR
#define CDi_info    BASEADDR
#define CDi_status  BASEADDR+1
#define CDo_reset   BASEADDR+2
#define CDo_enable  BASEADDR+3

#define MIXER_addr  BASEADDR-0x10+0x04
#define MIXER_CD_Volume 0x28
#define MIXER_data  BASEADDR-0x10+0x05
#define CDo_sel_d_i BASEADDR+1

#define OUT(x,y) outb(y,x)

/*
 * bits of flags_cmd_out:
 */
#define f_putcmd 0x80
#define f_respo2 0x40
#define f_lopsta 0x20
#define f_getsta 0x10
#define f_ResponseStatus 0x08
#define f_obey_p_check 0x04
#define f_bit1 0x02
#define f_wait_if_busy 0x01

/*
 * diskstate_flags:
 */
#define upc_bit 0x40
#define volume_bit 0x20
#define toc_bit 0x10
#define yy_8d_bit 0x08
#define cd_size_bit 0x04
#define subq_bit 0x02
#define frame_size_bit 0x01

/*
 * disk states (bits of diskstate_flags):
 */
#define upc_valid (diskstate_flags&upc_bit)
#define volume_valid (diskstate_flags&volume_bit)
#define toc_valid (diskstate_flags&toc_bit)
#define yy_8d_valid (diskstate_flags&yy_8d_bit)
#define cd_size_valid (diskstate_flags&cd_size_bit)
#define subq_valid (diskstate_flags&subq_bit)
#define frame_size_valid (diskstate_flags&frame_size_bit)


/*
 * bits of the status_byte (result of xx_ReadStatus):
 */
#define p_door_closed 0x80
#define p_caddy_in 0x40
#define p_spinning 0x20
#define p_check 0x10
#define p_busy_new 0x08
#define p_door_locked 0x04
#define p_bit_1 0x02
#define p_disk_ok 0x01
/*
 * "old" drives status result bits:
 */
#define p_caddin_old 0x40
#define p_success_old 0x08
#define p_busy_old 0x04

/*
 * used drive states:
 */
#define st_door_closed (status_byte&p_door_closed)
#define st_caddy_in (status_byte&p_caddy_in)
#define st_spinning (status_byte&p_spinning)
#define st_check (status_byte&p_check)
#define st_busy (status_byte&p_busy_new)
#define st_door_locked (status_byte&p_door_locked)
#define st_diskok (status_byte&p_disk_ok)

/*
 * bits of the CDi_status register:
 */
#define s_not_result_ready 0x04  /* 0: "result ready" */
#define s_not_data_ready 0x02    /* 0: "data ready"   */
#define s_attention 0x01         /* 1: "attention required" */
/*
 * usable as:
 */
#define DRV_ATTN               ((inb(CDi_status)&s_attention)!=0)
#define DATA_READY             ((inb(CDi_status)&s_not_data_ready)==0)
#define RESULT_READY           ((inb(CDi_status)&s_not_result_ready)==0)

/*
 * drive types (firmware versions):
 */
#define drv_199 0       /* <200 */
#define drv_200 1       /* <201 */
#define drv_201 2       /* <210 */
#define drv_210 3       /* <211 */
#define drv_211 4       /* <300 */
#define drv_300 5       /* else */
#define drv_099 0x10    /* new,  <100 */
#define drv_100 0x11    /* new, >=100 */
#define drv_new 0x10    /* all new drives have that bit set */
#define drv_old 0x00    /*  */

/*
 * drv_099 and drv_100 are the "new" drives
 */
#define new_drive (drv_type&0x10)

/*
 * audio states:
 */
#define audio_playing 2
#define audio_pausing 1

/*
 * drv_pattern, drv_options:
 */
#define speed_auto 0x80
#define speed_300 0x40
#define speed_150 0x20
#define sax_a 0x04
#define sax_xn2 0x02
#define sax_xn1 0x01

/*
 * values of cmd_type (0 else):
 */
#define cmd_type_READ_M1  0x01 /* "data mode 1": 2048 bytes per frame */
#define cmd_type_READ_M2  0x02 /* "data mode 2": 12+2048+280 bytes per frame */
#define cmd_type_READ_SC  0x04 /* "subchannel info": 96 bytes per frame */

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

sense_byte: used only if new_drive
                 only during cmd 09 00 xx ah al 00 00
            values: 00
                    82
                    from infobuf[0] after 85 00 00 00 00 00 00

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

#define CD_MINS                   75  /* minutes per CD                  */
#define CD_SECS                   60  /* seconds per minutes             */
#define CD_FRAMES                 75  /* frames per second               */
#define CD_FRAMESIZE            2048  /* bytes per frame, data mode      */
#define CD_FRAMESIZE_XA	        2340  /* bytes per frame, "xa" mode      */
#define CD_FRAMESIZE_RAW        2352  /* bytes per frame, "raw" mode     */
#define CD_BLOCK_OFFSET          150  /* offset of first logical frame   */


/* audio status (bin) */
#define aud_00 0x00 /* Audio status byte not supported or not valid */
#define audx11 0x0b /* Audio play operation in progress             */
#define audx12 0x0c /* Audio play operation paused                  */
#define audx13 0x0d /* Audio play operation successfully completed  */
#define audx14 0x0e /* Audio play operation stopped due to error    */
#define audx15 0x0f /* No current audio status to return            */

/* audio status (bcd) */
#define aud_11 0x11 /* Audio play operation in progress             */
#define aud_12 0x12 /* Audio play operation paused                  */
#define aud_13 0x13 /* Audio play operation successfully completed  */
#define aud_14 0x14 /* Audio play operation stopped due to error    */
#define aud_15 0x15 /* No current audio status to return            */

/*============================================================================
==============================================================================

COMMAND SET of "old" drives (the CR-562 family is different):

No.	Command			       Code
--------------------------------------------

Drive Commands:
 1	Seek				01	
 2	Read Data			02
 3	Read XA-Data			03
 4	Read Header			04
 5	Spin Up				05
 6	Spin Down			06
 7	Diagnostic			07
 8	Read UPC			08
 9	Read ISRC			09
10	Play Audio			0A
11	Play Audio MSF			0B
12	Play Audio Track/Index		0C

Status Commands:
13	Read Status			81	
14	Read Error			82
15	Read Drive Version		83
16	Mode Select			84
17	Mode Sense			85
18	Set XA Parameter		86
19	Read XA Parameter		87
20	Read Capacity			88
21	Read SUB_Q			89
22	Read Disc Code			8A
23	Read Disc Information		8B
24	Read TOC			8C
25	Pause/Resume			8D
26	Read Packet			8E
27	Read Path Check			00
 
 
all numbers (lba, msf-bin, msf-bcd, counts) to transfer high byte first

mnemo     7-byte command        #bytes response (r0...rn)
________ ____________________  ____ 

Read Status:
status:  81.                    (1)  one-byte command, gives the main
                                                          status byte
Read Error:
check1:  82 00 00 00 00 00 00.  (6)  r1: audio status

Read Packet:
check2:  8e xx 00 00 00 00 00. (xx)  gets xx bytes response, relating
                                        to commands 01 04 05 07 08 09

Play Audio:
play:    0a ll-bb-aa nn-nn-nn.  (0)  play audio, ll-bb-aa: starting block (lba),
                                                 nn-nn-nn: #blocks
Play Audio MSF:
         0b mm-ss-ff mm-ss-ff   (0)  play audio from/to

Play Audio Track/Index:
         0c ...

Pause/Resume:
pause:   8d pr 00 00 00 00 00.  (0)  pause (pr=00) 
                                     resume (pr=80) audio playing

Mode Select:
         84 00 nn-nn ??-?? 00   (0)  nn-nn: 2048 or 2340
                                     possibly defines transfer size

set_vol: 84 83 00 00 sw le 00.  (0)  sw(itch): lrxxxxxx (off=1)
                                     le(vel): min=0, max=FF, else half
				     (firmware 2.11)

Mode Sense:
get_vol: 85 03 00 00 00 00 00.  (2)  tell current audio volume setting

Read Disc Information:
tocdesc: 8b 00 00 00 00 00 00.  (6)  read the toc descriptor ("msf-bin"-format)

Read TOC:
tocent:  8c fl nn 00 00 00 00.  (8)  read toc entry #nn
                                       (fl=0:"lba"-, =2:"msf-bin"-format)

Read Capacity:
capacit: 88 00 00 00 00 00 00.  (5)  "read CD-ROM capacity"


Read Path Check:
ping:    00 00 00 00 00 00 00.  (2)  r0=AA, r1=55
                                     ("ping" if the drive is connected)

Read Drive Version:
ident:   83 00 00 00 00 00 00. (12)  gives "MATSHITAn.nn" 
                                     (n.nn = 2.01, 2.11., 3.00, ...)

Seek:
seek:    01 00 ll-bb-aa 00 00.  (0)  
seek:    01 02 mm-ss-ff 00 00.  (0)  

Read Data:
read:    02 xx-xx-xx nn-nn fl. (??)  read nn-nn blocks of 2048 bytes,
                                     starting at block xx-xx-xx  
                                     fl=0: "lba"-, =2:"msf-bcd"-coded xx-xx-xx

Read XA-Data:
read:    03 xx-xx-xx nn-nn fl. (??)  read nn-nn blocks of 2340 bytes, 
                                     starting at block xx-xx-xx  
                                     fl=0: "lba"-, =2:"msf-bcd"-coded xx-xx-xx

Read SUB_Q:
         89 fl 00 00 00 00 00. (13)  r0: audio status, r4-r7: lba/msf, 
                                       fl=0: "lba", fl=2: "msf"

Read Disc Code:
         8a 00 00 00 00 00 00. (14)  possibly extended "check condition"-info

Read Header:
         04 00 ll-bb-aa 00 00.  (0)   4 bytes response with "check2"
         04 02 mm-ss-ff 00 00.  (0)   4 bytes response with "check2"

Spin Up:
         05 00 ll-bb-aa 00 00.  (0)  possibly implies a "seek"

Spin Down:
         06 ...

Diagnostic:
         07 00 ll-bb-aa 00 00.  (2)   2 bytes response with "check2"
         07 02 mm-ss-ff 00 00.  (2)   2 bytes response with "check2"

Read UPC:
         08 00 ll-bb-aa 00 00. (16)  
         08 02 mm-ss-ff 00 00. (16)  

Read ISRC:
         09 00 ll-bb-aa 00 00. (15)  15 bytes response with "check2"
         09 02 mm-ss-ff 00 00. (15)  15 bytes response with "check2"

Set XA Parameter:
         86 ...

Read XA Parameter:
         87 ...

==============================================================================
============================================================================*/

/*==========================================================================*/
/*==========================================================================*/
/*==========================================================================*/
/*
 * use "REP INSB" for strobing the data in:
 */
#if PL11
#define READ_DATA(port, buf, nr) \
__asm__("cld;rep;insb": :"d" (port),"D" (buf),"c" (nr):"cx","di")
#else
#define READ_DATA(port, buf, nr) \
__asm__("cld;rep;insb"::"d" (port),"D" (buf),"c" (nr):"cx","di")
#endif PL11
/*==========================================================================*/
/*
 * to fork and execute a function after some elapsed time:
 * one "jifs" unit is 10 msec.
 */
#define SET_TIMER(func, jifs) \
        ((timer_table[SBPCD_TIMER].expires = jiffies + jifs), \
        (timer_table[SBPCD_TIMER].fn = func), \
        (timer_active |= 1<<SBPCD_TIMER))

#define CLEAR_TIMER	timer_active &= ~(1<<SBPCD_TIMER)

/*==========================================================================*/
/*
 * Creative Labs Programmers did this:
 */
#define MAX_TRACKS	120 /* why more than 99? */

/*==========================================================================*/
/*
 * structs for IOCTL functions:
 */
static struct cdrom_msf msf;
static struct cdrom_ti ti;
static struct cdrom_tochdr tochdr;
static struct cdrom_tocentry tocentry;
static struct cdrom_subchnl subchnl;
static struct cdrom_volctrl volctrl;

/*==========================================================================*/
/*
 * makes conversion easier, but is machine dependent:
 */
typedef union _msf {u_int n; u_char c[4]; } MSF;
typedef union _blk {u_int n; u_char c[4]; } BLK;

/*==========================================================================*/

#if FUTURE
static struct wait_queue *sbp_waitq = NULL;
#endif FUTURE

/*==========================================================================*/

#define SBP_BUFFER_FRAMES 4

static int sbpPresent = 0;

static u_char *sbp_buf; /* Pointer to internal data buffer, space allocated during sbpcd_init() */
static int sbp_first_frame = -1;  /* First frame in buffer */
static int sbp_last_frame = -1;   /* Last frame in buffer  */
static int sbp_read_frames = 0;   /* Number of frames being read to buffer */
static int sbp_current = 0;       /* Frame being currently read */
static int SbpTimeout, SbpTries;

/*
 * the forward references:
 */
static void sbp_start(void);
static void sbp_status(void);
static void sbp_read_cmd(void);
static void sbp_data(void);

/*==========================================================================*/
/*==========================================================================*/

static u_char ah, al;

/*==========================================================================*/
/*==========================================================================*/

static u_char drive_family[]="CR-5";
static u_char drive_vendor[]="MATSHITA";
static char drive_model[4];
static char firmware_version[4];

static u_int response_count=0;
static u_char flags_cmd_out;
static u_char cmd_type=0;
static u_int datarate=600000;
static u_int maxtim16,maxtim04,maxtim02,maxtim_8;

static u_char drvcmd[7];
static u_char infobuf[20];

static u_char timeout=0;

/*==========================================================================*/

static char drvcnt=1;
int ndrives=4;
static u_char drv_pattern[4]={ 0x80, 0x80, 0x80, 0x80 }; /* auto speed */
/*  /X:... drv_pattern[0] |= (sax_n1|sax_n2); */
/*  /A:... for (i=0;i<4;i++) drv_pattern[i] |= sax_a; */
/*  /N:... ndrives=i-'0'; */

static u_char current_drive=0;

/*==========================================================================*/
/*
 * drive space begins here (needed separate for each unit) 
 */
static u_char drv_minor=0;
static u_char drv_type=0;
static u_char drv_options=0;
static u_char status_byte=0;
static u_char diskstate_flags=0;
static u_char sense_byte=0;

static u_char CD_changed=1;

static u_char error_byte=0;

static u_char n_msf3_valid=0;
static u_int n_msf3=0;

/*
 * audio control:
 */
static u_char audio_state=0;

static u_int pos_audio_start=0;
static u_int pos_audio_end=0;

static char vol_chan0=0;
static u_char vol_ctrl0=0;
static char vol_chan1=0;
static u_char vol_ctrl1=0;
static char vol_chan2=0;
static u_char vol_ctrl2=0;
static char vol_chan3=0;
static u_char vol_ctrl3=0;

static u_char SubQ_audio;
static u_char SubQ_ctl_adr;
static u_char SubQ_trk;
static u_char SubQ_pnt_idx;
static u_int SubQ_run_tot=0;
static u_int SubQ_run_trk=0;
static u_char SubQ_whatisthis=0;

static u_char UPC_ctl_adr;
static u_char UPC_buf[7];

/*
 * result of ReadCapacity:
 */
static int CDsize_blk=0;
static int frame_size=CD_FRAMESIZE;
static int CDsize_frm=0;
static int cd_block;

/*
 * TOC Descriptor:
 */
static u_char xa_byte=0; /* 0x20: XA capabilities */
static u_char n_first_track=0; /* binary */
static u_char n_last_track=0; /* binary (not bcd), 0x01...0x63 */
static u_int size_msf=0; /* time of whole CD, position of LeadOut track */
static u_int size_blk=0;

/*
 * TOC entry:
 */
static u_char TocEnt_nixbyte=0; /* em */
static u_char TocEnt_ctl_adr=0;
static u_char TocEnt_number=0;
static u_char TocEnt_format=0; /* em */
static u_int TocEnt_address=0;
static u_char ored_ctl_adr=0; /* to detect if CDROM contains data tracks */

/*
 * buffer for the whole TOC:
 */
static struct { u_char nixbyte; /* em */
		u_char ctl_adr; /* 0x4x: data, 0x0x: audio */
		u_char number;
		u_char format; /* em */ /* 0x00: lba, 0x01: msf */
		u_int address;
              } TocBuffer[MAX_TRACKS+1]; /* last entry faked */ 

/*
 * drive space ends here (needed separate for each unit)
 */
/*==========================================================================*/
/*==========================================================================*/

/*==========================================================================*/
/*==========================================================================*/
/*
 *  convert logical_block_address to m-s-f_number (3 bytes only)
 */
void lba2msf(int lba, u_char *msf)
{
  lba += CD_BLOCK_OFFSET;
  msf[0] = lba / (CD_SECS*CD_FRAMES);
  lba %= CD_SECS*CD_FRAMES;
  msf[1] = lba / CD_FRAMES;
  msf[2] = lba % CD_FRAMES;
}
/*==========================================================================*/
/*==========================================================================*/
/*
 *  convert msf-bin to msf-bcd
 */
void bin2bcdx(u_char *p)  /* must work only up to 75 or 99 */
{ *p=((*p/10)<<4)|(*p%10); }
/*==========================================================================*/
/*==========================================================================*/
u_int blk2msf(u_int blk)
{
  MSF msf;
  u_int mm;

  msf.c[3] = 0;
  msf.c[2] = (blk + CD_BLOCK_OFFSET) / (CD_SECS * CD_FRAMES);
  mm = (blk + CD_BLOCK_OFFSET) % (CD_SECS * CD_FRAMES);
  msf.c[1] = mm / CD_FRAMES;
  msf.c[0] = mm % CD_FRAMES;
  return msf.n;
}
/*==========================================================================*/
u_int make16(u_char rh, u_char rl)
{ return (rh<<8)|rl; }
/*==========================================================================*/
u_int make32(u_int rh, u_int rl)
{ return (rh<<16)|rl; }
/*==========================================================================*/
u_char swap_nibbles(u_char i)
{ return ((i<<4)|(i>>4)); }
/*==========================================================================*/
u_char byt2bcd(u_char i)
{ return ((i/10)<<4)+i%10; }
/*==========================================================================*/
/*==========================================================================*/
u_char bcd2bin(u_char bcd)
{ return (bcd>>4)*10+(bcd&0x0F); }
/*==========================================================================*/
/*==========================================================================*/
int msf2blk(int msfx)
{
  MSF msf;
  int i;

  msf.n=msfx;
  i=(msf.c[2] * CD_SECS + msf.c[1]) * CD_FRAMES + msf.c[0] - CD_BLOCK_OFFSET;
  if (i<0) return 0;
  return i;
}
/*==========================================================================*/
/* evaluate xx_ReadError code (still mysterious) */ 
int sta2err(int sta)
{
  if (sta<=2) return sta;
  if (sta==0x05) return -4;
  if (sta==0x06) return -6;
  if (sta==0x0d) return -6;
  if (sta==0x0e) return -3;
  if (sta==0x14) return -3;
  if (sta==0x0c) return -11;
  if (sta==0x0f) return -11;
  if (sta==0x10) return -11;
  if (sta>=0x16) return -12;
  CD_changed=0xFF;
  if (sta==0x11) return -15;
  return -2;
}
/*==========================================================================*/
/*==========================================================================*/
void clr_cmdbuf(void)
{
  int i;

  for (i=0;i<7;i++) drvcmd[i]=0;
  cmd_type=0;
}
/*==========================================================================*/
/*==========================================================================*/
void mark_timeout(void)
{
#define TIMER_COUNT_1 110

  timeout=1;
#if testt
  printk("timer stopped (%d).\n", TIMER_COUNT_1);
#endif testt
}
/*==========================================================================*/
/*==========================================================================*/
void flush_status(void)
{
#ifdef CDMKE
  int i;

  for (i=maxtim02;i!=0;i--) inb(CDi_status);
#else
  timeout=0;
  SET_TIMER(mark_timeout,150);
  do { }
  while (!timeout);
  CLEAR_TIMER;
#endif CDMKE
}
/*==========================================================================*/
/*==========================================================================*/
int CDi_stat_loop(void)
{
  int i,j;

  for (i=maxtim16;i!=0;i--)
    { j=inb(CDi_status);
      if (!(j&s_not_data_ready)) return j;
      if (!(j&s_not_result_ready)) return j;
      if (!new_drive) if (j&s_attention) return j;
    }
  return -1;
}
/*==========================================================================*/
/*==========================================================================*/
int ResponseInfo(void)
{
  int i,j, st;
  
  for (i=0;i<response_count;i++)
    { for (j=maxtim_8;j!=0;j--)
	{ st=inb(CDi_status);
	  if (!(st&s_not_result_ready)) break;
	}
      if (j==0) return -1;
      infobuf[i]=inb(CDi_info);
    }
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
int EvaluateStatus(int st)
{
  if (!new_drive)
    {
      status_byte=0;
      if (st&p_caddin_old) status_byte |= p_door_closed|p_caddy_in;
      if (st&p_spinning) status_byte |= p_spinning;
      if (st&p_check) status_byte |= p_check;
      if (st&p_busy_old) status_byte |= p_busy_new;
      if (st&p_disk_ok) status_byte |= p_disk_ok;
    }
  else { status_byte=st;
	 st=p_success_old; /* for new drives: fake "successful" bit of old drives */
       }
  return st;
}
/*==========================================================================*/
/*==========================================================================*/
int ResponseStatus(void)
{
  int i,j;

#if 0
  printk("doing ResponseStatus...\n");
#endif 0

  if (flags_cmd_out&f_respo2) j=maxtim16;
  else j=maxtim04;

  for (;j!=0;j--)
    { i=inb(CDi_status);
      if (!(i&s_not_result_ready)) break;
    }
  if (j==0)
    {
#if test_sta
      printk("ResponseStatus: timeout\n");
#endif test_sta
      EvaluateStatus(0);
      return -1;
    }
  i=inb(CDi_info);
  i=EvaluateStatus(i);
  return i;
}
/*==========================================================================*/
/*==========================================================================*/
void xx_ReadStatus(void)
{
  int i;

#if 0
  printk("giving xx_ReadStatus command\n");
#endif 0

  if (!new_drive) OUT(CDo_command,0x81);
  else {
#ifdef	SBPCD_DISABLE_IRQ
        cli();
#endif	SBPCD_DISABLE_IRQ
	OUT(CDo_command,0x05);
	for (i=0;i<6;i++) OUT(CDo_command,0);
#ifdef	SBPCD_DISABLE_IRQ
        sti();
#endif	SBPCD_DISABLE_IRQ
       }
}
/*==========================================================================*/
/*==========================================================================*/
int xx_ReadError(void)
{
  int cmd_out(void);
  int i;

  clr_cmdbuf();

#if test_err
  printk("giving xx_ReadError command\n");
#endif test_err

  if (new_drive) { drvcmd[0]=0x82;
		   response_count=8;
		   flags_cmd_out=f_putcmd|f_ResponseStatus;
		 }
  else { drvcmd[0]=0x82;
	 response_count=6;
	 flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus;
       }
  i=cmd_out();
  error_byte=0;
#if test_err
  printk("xx_ReadError: cmd_out(82) returns %d (%02X)\n",i,i);
#endif test_err
  if (i<0) return i;
  if (new_drive) i=2;
  else i=1;
  error_byte=infobuf[i];
#if test_err
  printk("xx_ReadError: infobuf[%d] is %d (%02X)\n",i,error_byte,error_byte);
#endif test_err
  i=sta2err(infobuf[i]);
  return i;
}
/*==========================================================================*/
/*==========================================================================*/
int cmd_out(void)
{
  int i=0;

  if (flags_cmd_out&f_putcmd)
    { 
#if test_cmd
  printk("SBPCD: cmd_out: put");
  for (i=0;i<7;i++) printk(" %02X",drvcmd[i]);
  printk("\n");
#endif test_cmd

#ifdef	SBPCD_DISABLE_IRQ
      cli();
#endif	SBPCD_DISABLE_IRQ
      for (i=0;i<7;i++) OUT(CDo_command,drvcmd[i]);
#ifdef	SBPCD_DISABLE_IRQ
      sti();
#endif	SBPCD_DISABLE_IRQ
    }
  if (response_count!=0)
    { if (cmd_type!=0)
	{
  if (card_type==SBPRO) {
	  OUT(CDo_sel_d_i,0x01);
	  printk("SBPCD: misleaded to try ResponseData.\n");
	  OUT(CDo_sel_d_i,0x00);
  } else
	  printk("SBPCD: misleaded to try ResponseData.\n");
	}
      else i=ResponseInfo();
      if (i<0) { ah=0xFF; al=0x0c; return -1; }
    }
  if (flags_cmd_out&f_lopsta)
    { i=CDi_stat_loop();
      if ((i<0)||!(i&s_attention)) { ah=0xFF; al=0x0c; return -1; }
    }
  if (!(flags_cmd_out&f_getsta)) goto LOC_229;

LOC_228:
  xx_ReadStatus();

LOC_229:
  if (flags_cmd_out&f_ResponseStatus) 
    { i=ResponseStatus();  /* builds status_byte, returns orig. status or p_busy_new */
      if (i<0) { ah=0xFF; al=0x0c; return -1; }
      if (flags_cmd_out&(f_bit1|f_wait_if_busy))
	{ if (!st_check)
	    { if (flags_cmd_out&f_bit1) if (i&p_success_old) goto LOC_232;
	      if (!(flags_cmd_out&f_wait_if_busy)) goto LOC_228;
	      if (!st_busy) goto LOC_228;
	    }
	}
    }
LOC_232:
  if (!(flags_cmd_out&f_obey_p_check)) return 0;
  if (!st_check) return 0;
  i=xx_ReadError();
  return i;
}
/*==========================================================================*/
/*==========================================================================*/
int xx_Seek(u_int pos, char f_blk_msf)
{
  int i;

  clr_cmdbuf();
  if (f_blk_msf>1) return -3;
  if (!new_drive) { if (f_blk_msf==1) pos=msf2blk(pos);
		    drvcmd[2]=(pos>>16)&0x00FF;
		    drvcmd[3]=(pos>>8)&0x00FF;
		    drvcmd[4]=pos&0x00FF;
		    flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1;
		  }
  else { if (f_blk_msf==0) pos=blk2msf(pos);
	 drvcmd[1]=(pos>>16)&0x00FF;
	 drvcmd[2]=(pos>>8)&0x00FF;
	 drvcmd[3]=pos&0x00FF;
	 flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;
       }
  drvcmd[0]=0x01;
  response_count=0;
  i=cmd_out();
  return i;
}
/*==========================================================================*/
/*==========================================================================*/
int xx_SpinUp(void)
{
  int i;

  clr_cmdbuf();
  if (!new_drive) { drvcmd[0]=0x05;
		    flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1;
		  }
  else { drvcmd[0]=0x02;
	 flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;
       }
  response_count=0;
  i=cmd_out();
  return i;
}
/*==========================================================================*/
/*==========================================================================*/
int yy_SpinDown(void)
{
  int i;

  if (!new_drive) return -3;
  clr_cmdbuf();
  drvcmd[0]=0x06;
  flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;
  response_count=0;
  i=cmd_out();
  return i;
}
/*==========================================================================*/
/*==========================================================================*/
int yy_SetSpeed(u_char speed, u_char x1, u_char x2)
{
  int i;

  if (!new_drive) return -3;
  clr_cmdbuf();
  drvcmd[0]=0x09;
  drvcmd[1]=0x03;
  drvcmd[2]=speed;
  drvcmd[3]=x1;
  drvcmd[4]=x2;
  flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
  response_count=0;
  i=cmd_out();
  return i;
}
/*==========================================================================*/
/*==========================================================================*/
int xx_SetVolume(void)
{
  int i;
  u_char channel0,channel1,volume0,volume1;
  u_char control0,value0,control1,value1;

  diskstate_flags &= ~volume_bit;
  clr_cmdbuf();
  channel0=vol_chan0;
  volume0=vol_ctrl0;
  channel1=control1=vol_chan1;
  volume1=value1=vol_ctrl1;
  control0=value0=0;

  if (((drv_options&sax_a)!=0)&&(drv_type>=drv_211))
    { if ((volume0!=0)&&(volume1==0))
	{ volume1=volume0;
	  channel1=channel0;
	}
      else if ((volume0==0)&&(volume1!=0))
	{ volume0=volume1;
	  channel0=channel1;
	}
    }
  if (channel0>1) { channel0=0;
		    volume0=0;
		  }
  if (channel1>1) { channel1=1;
		    volume1=0;
		  }

  if (new_drive) { control0=channel0+1;
		   control1=channel1+1;
		   value0=(volume0>volume1)?volume0:volume1;
		   value1=value0;
		   if (volume0==0) control0=0;
		   if (volume1==0) control1=0;
		   drvcmd[0]=0x09;
		   drvcmd[1]=0x05;
		   drvcmd[3]=control0;
		   drvcmd[4]=value0;
		   drvcmd[5]=control1;
		   drvcmd[6]=value1;
		   flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
		 }
  else 
    { if (drv_type>=drv_300)
	{ control0=volume0&0xFC;
	  value0=volume1&0xFC;
	  if ((volume0!=0)&&(volume0<4)) control0 |= 0x04;
	  if ((volume1!=0)&&(volume1<4)) value0 |= 0x04;
	  if (channel0!=0) control0 |= 0x01;
	  if (channel1==1) value0 |= 0x01;
	}
      else
	{ value0=(volume0>volume1)?volume0:volume1;
	  if (drv_type<drv_211)
	    { if (channel0!=0)
		{ i=channel1; channel1=channel0; channel0=i;
		  i=volume1; volume1=volume0; volume0=i;
		}
	      if (channel0==channel1)
		{ if (channel0==0) { channel1=1; volume1=0; volume0=value0; }
		  else { channel0=0; volume0=0; volume1=value0; }
		}
	    }

	  if ((volume0!=0)&&(volume1!=0))
	    { if (volume0==0xFF) volume1=0xFF;
	      else if (volume1==0xFF) volume0=0xFF;
	    }
	  else if (drv_type<drv_201) volume0=volume1=value0;

	  if (drv_type>=drv_201)
	    { if (volume0==0) control0 |= 0x80;
	      if (volume1==0) control0 |= 0x40;
	    }
	  if (drv_type>=drv_211)
		{ if (channel0!=0) control0 |= 0x20;
		  if (channel1!=1) control0 |= 0x10;
		}
	}
      drvcmd[0]=0x84;
      drvcmd[1]=0x83;
      drvcmd[4]=control0;
      drvcmd[5]=value0;
      flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
    }

  response_count=0;
  i=cmd_out();
  if (i>0) return i;
  diskstate_flags |= volume_bit;
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
int GetStatus(void)
{
  int i;

  flags_cmd_out=f_getsta|f_ResponseStatus|f_obey_p_check;
  response_count=0;
  cmd_type=0;
  i=cmd_out();
  return i;
}
/*==========================================================================*/
/*==========================================================================*/
int xy_DriveReset(void)
{
  int i;

  if (!new_drive) OUT(CDo_reset,0x00);
  else { clr_cmdbuf();
	 drvcmd[0]=0x0A;
	 flags_cmd_out=f_putcmd;
	 response_count=0;
	 i=cmd_out();
       }
  flush_status();
  i=GetStatus();
  if (i>=0) return -1;
  if (error_byte!=aud_12) return -1;
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
int SetSpeed(void)
{
  int i, speed;

  if (!(drv_options&(speed_auto|speed_300|speed_150))) return 0;
  speed=0x80;
  if (!(drv_options&speed_auto)) { speed |= 0x40;
				   if (!(drv_options&speed_300)) speed=0;
				 }
  i=yy_SetSpeed(speed,0,0);
  return i;
}
/*==========================================================================*/
/*==========================================================================*/
int DriveReset(void)
{
  int i;

  i=xy_DriveReset();
  if (i<0) return -2;
  do { i=GetStatus();
       if ((i<0)&&(i!=-15)) return -2; /* i!=-15 is from sta2err */
       if (!st_caddy_in) break;
     }
  while (!st_diskok);
  CD_changed=1;
  i=SetSpeed();
  if (i<0) return -2;
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
int xx_Pause_Resume(int pau_res)
{
  int i;

  clr_cmdbuf();
  if (new_drive) { drvcmd[0]=0x0D;
		   flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;
		 }
  else { drvcmd[0]=0x8D;
	 flags_cmd_out=f_putcmd|f_respo2|f_getsta|f_ResponseStatus|f_obey_p_check;
       }
  if (pau_res!=1) drvcmd[1]=0x80;
  response_count=0;
  i=cmd_out();
  return i;
}
/*==========================================================================*/
/*==========================================================================*/
int yy_LockDoor(char lock)
{
  int i;

  if (!new_drive) return -3;
  clr_cmdbuf();
  drvcmd[0]=0x0C;
  if (lock==1) drvcmd[1]=0x01;
  flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
  response_count=0;
  i=cmd_out();
  return i;
}
/*==========================================================================*/
/*==========================================================================*/
int xx_ReadSubQ(void)
{
  int i,j;

  diskstate_flags &= ~subq_bit;
  clr_cmdbuf();
  if (new_drive) { drvcmd[0]=0x87;
		   flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
		   response_count=11;
		 }
  else { drvcmd[0]=0x89;
	 drvcmd[1]=0x02;
	 flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
	 response_count=13;
       }
  for (j=0;j<255;j++) { i=cmd_out();
			if (i<0) return i;
			if (infobuf[0]!=0) break;
			if (!st_spinning) { SubQ_ctl_adr=SubQ_trk=SubQ_pnt_idx=SubQ_whatisthis=0;
					    SubQ_run_tot=SubQ_run_trk=0;
					    return 0;
					  }
		      }
  SubQ_audio=infobuf[0];
  SubQ_ctl_adr=swap_nibbles(infobuf[1]);
  SubQ_trk=byt2bcd(infobuf[2]);
  SubQ_pnt_idx=byt2bcd(infobuf[3]);
  i=4;
  if (!new_drive) i=5;
  SubQ_run_tot=make32(make16(0,infobuf[i]),make16(infobuf[i+1],infobuf[i+2])); /* msf-bin */
  i=7;
  if (!new_drive) i=9;
  SubQ_run_trk=make32(make16(0,infobuf[i]),make16(infobuf[i+1],infobuf[i+2])); /* msf-bin */
  SubQ_whatisthis=infobuf[i+3];
  diskstate_flags |= subq_bit;
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
int xx_ModeSense(void)
{
  int i;

  diskstate_flags &= ~frame_size_bit;
  clr_cmdbuf();
  if (new_drive) { drvcmd[0]=0x84;
		   drvcmd[1]=0x00;
		   flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
		   response_count=5;
		 }
  else { drvcmd[0]=0x85;
	 drvcmd[1]=0x00;
	 flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
	 response_count=2;
       }
  i=cmd_out();
  if (i<0) return i;
  i=0;
  if (new_drive) sense_byte=infobuf[i++];
  frame_size=make16(infobuf[i],infobuf[i+1]);
  diskstate_flags |= frame_size_bit;
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
int xx_TellVolume(void)
{
  int i;
  u_char switches;
  u_char chan0,vol0,chan1,vol1;

  diskstate_flags &= ~volume_bit;
  clr_cmdbuf();
  if (new_drive) { drvcmd[0]=0x84;
		   drvcmd[1]=0x05;
		   response_count=5;
		   flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
		 }
  else { drvcmd[0]=0x85;
	 drvcmd[1]=0x03;
	 response_count=2;
	 flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
       }
  i=cmd_out();
  if (i<0) return i;
  if (new_drive) { chan0=infobuf[1]&0x0F;
		   vol0=infobuf[2];
		   chan1=infobuf[3]&0x0F;
		   vol1=infobuf[4];
		   if (chan0==0) { chan0=1; vol0=0; }
		   if (chan1==0) { chan1=2; vol1=0; }
		   chan0 >>= 1;
		   chan1 >>= 1;
		 }
  else { chan0=0; chan1=1;
	 vol0=vol1=infobuf[1];
	 if (drv_type>=drv_201)
	   { if (drv_type<drv_300)
	       { switches=infobuf[0];
		 if ((switches&0x80)!=0) vol0=0;
		 if ((switches&0x40)!=0) vol1=0;
		 if (drv_type>=drv_211)
		   { if ((switches&0x20)!=0) chan0=1;
		     if ((switches&0x10)!=0) chan1=0;
		   }
	       }
	     else { vol0=infobuf[0];
		    if ((vol0&0x01)!=0) chan0=1;
		    if ((vol1&0x01)==0) chan1=0;
		    vol0 &= 0xFC;
		    vol1 &= 0xFC;
		    if (vol0!=0) vol0 += 3;
		    if (vol1!=0) vol1 += 3;
		  }
	   }
       }
  vol_chan0=chan0;
  vol_ctrl0=vol0;
  vol_chan1=chan1;
  vol_ctrl1=vol1;
  vol_chan2=2;
  vol_ctrl2=0xFF;
  vol_chan3=3;
  vol_ctrl3=0xFF;
  diskstate_flags |= volume_bit;
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
int xx_ReadCapacity(void)
{
  int i;

  diskstate_flags &= ~cd_size_bit;
  clr_cmdbuf();
  if (new_drive) { drvcmd[0]=0x85;
		   flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
		 }
  else { drvcmd[0]=0x88;
	 flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
       }
  response_count=5;
  i=cmd_out();
  if (i<0) return i;
  CDsize_blk=make32(make16(0,infobuf[0]),make16(infobuf[1],infobuf[2]));
  if (new_drive) CDsize_blk=msf2blk(CDsize_blk);
  cd_block = make16(infobuf[3],infobuf[4]);
  CDsize_frm = (CDsize_blk * cd_block) / CD_FRAMESIZE;
  CDsize_blk += 151;
  diskstate_flags |= cd_size_bit;
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
int xx_ReadTocDescr(void)
{
  int i;

  diskstate_flags &= ~toc_bit;
  clr_cmdbuf();
  if (new_drive) { drvcmd[0]=0x8B;
		   flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
		 }
  else { drvcmd[0]=0x8B;
	 flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
       }
  response_count=6;
  i=cmd_out();
  if (i<0) return i;
  xa_byte=infobuf[0];
  n_first_track=infobuf[1];
  n_last_track=infobuf[2];
  size_msf=make32(make16(0,infobuf[3]),make16(infobuf[4],infobuf[5]));
  size_blk=msf2blk(size_msf);
  diskstate_flags |= toc_bit;
#if test_toc
  printk("SBPCD: TocDesc: %02X %02X %02X %08X\n",
	 xa_byte,n_first_track,n_last_track,size_msf);
#endif test_toc
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
int xx_ReadTocEntry(int num)
{
  int i;

  clr_cmdbuf();
  if (new_drive) { drvcmd[0]=0x8C;
		   flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
		 }
  else { drvcmd[0]=0x8C;
	 drvcmd[1]=0x02;
	 flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
       }
  drvcmd[2]=num;
  response_count=8;
  i=cmd_out();
  if (i<0) return i;
  TocEnt_nixbyte=infobuf[0];
  TocEnt_ctl_adr=swap_nibbles(infobuf[1]);
  TocEnt_number=infobuf[2];
  TocEnt_format=infobuf[3];
  if (new_drive) i=4;
  else i=5;
  TocEnt_address=make32(make16(0,infobuf[i]),make16(infobuf[i+1],infobuf[i+2]));
#if test_toc
  printk("SBPCD: TocEntry: %02X %02X %02X %02X %08X\n",
	 TocEnt_nixbyte,TocEnt_ctl_adr,TocEnt_number,TocEnt_format,TocEnt_address);
#endif test_toc
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
int xx_ReadPacket(void)
{
  int i;

  clr_cmdbuf();
  drvcmd[0]=0x8E;
  drvcmd[1]=response_count;
  flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
  i=cmd_out();
  return i;
}
/*==========================================================================*/
/*==========================================================================*/
int convert_UPC(u_char *p)
{
  int i;

  p++;
  if (!new_drive) p[13]=0;
  for (i=0;i<7;i++) { if (new_drive) UPC_buf[i]=swap_nibbles(*p++);
                      else { UPC_buf[i]=((*p++)<<4)&0xFF;
			     UPC_buf[i] |= *p++;
			   }
		    }
  UPC_buf[6] &= 0xF0;
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
int xx_ReadUPC(void)
{
  int i;

  diskstate_flags &= ~upc_bit;
  clr_cmdbuf();
  if (new_drive) { drvcmd[0]=0x88;
		   response_count=8;
		   flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
		 }
  else { drvcmd[0]=0x08;
	 response_count=0;
	 flags_cmd_out=f_putcmd|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1;
       }
  i=cmd_out();
  if (i<0) return i;
  if (!new_drive) { response_count=16;
		    i=xx_ReadPacket();
		    if (i<0) return i;
		  }
  UPC_ctl_adr=0;
  if (new_drive) i=0;
  else i=2;
  if ((infobuf[i]&0x80)!=0) { convert_UPC(&infobuf[i]);
			      UPC_ctl_adr &= 0xF0;
			      UPC_ctl_adr |= 0x02;
			    }
  diskstate_flags |= upc_bit;
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
int yy_8d(void)
{
  int i;

  diskstate_flags &= ~yy_8d_bit;
  n_msf3_valid=0;
  clr_cmdbuf();
  if (new_drive) { drvcmd[0]=0x8D;
		   response_count=6;
		   flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
		   i=cmd_out();
		   if (i<0) return i;
		   if ((infobuf[0]&0x80)!=0)
		     { n_msf3_valid=1;
		       n_msf3=make32(make16(0,infobuf[1]),make16(infobuf[2],infobuf[3]));
		       n_msf3=blk2msf(msf2blk(n_msf3)+16);
		     }
		 }
  diskstate_flags |= yy_8d_bit;
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
void check_datarate(void)
{
#ifdef CDMKE
  int i;

  timeout=0;
  datarate=0;

  /* set a timer to make (timeout!=0) after 1.1 seconds */
  SET_TIMER(mark_timeout,TIMER_COUNT_1);
#if testt
  printk("timer started (%d).\n", TIMER_COUNT_1);
#endif testt

  do { i=inb(CDi_status);
       datarate++;
#if testt
#else
       if (datarate>0x00FFFFFF) break;
#endif testt
     }
  while (!timeout); /* originally looping for 1.1 seconds */
  CLEAR_TIMER;
#if testt
  printk("datarate: %d\n", datarate);
#endif testt
  if (datarate<65536) datarate=65536;

maxtim16=datarate*16;
maxtim04=datarate*4;
maxtim02=datarate*2;
maxtim_8=datarate/8;

#else

maxtim16=4000000;
maxtim04=4000000;
maxtim_8=10000;

#endif CDMKE
}
/*==========================================================================*/
/*==========================================================================*/
int check_version(void)
{
  int i, j;

  /* clear any pending error state */
  clr_cmdbuf();
  drvcmd[0]=0x82;
  response_count=9;
  flags_cmd_out=f_putcmd;
  cmd_out();

  /* read drive version */
  clr_cmdbuf();
  for (i=0;i<12;i++) infobuf[i]=0;
  drvcmd[0]=0x83;
  response_count=12;
  flags_cmd_out=f_putcmd;
  i=cmd_out();
#if testini
  if (i<0) printk("cmd_83 returns %d\n",i);
#endif testini

#if testini
  for (i=0;i<12;i++) printk("%c",infobuf[i]);
  printk("\n");
#endif testini

  for (i=0;i<4;i++) if (infobuf[i]!=drive_family[i]) break;
  if (i==4) { drive_model[0]=infobuf[i++];
	      drive_model[1]=infobuf[i++];
	      drive_model[2]='-';
	      drive_model[3]='x';
	      drv_type=drv_new;
	    }
  else { for (i=0;i<8;i++) if (infobuf[i]!=drive_vendor[i]) break;
	 if (i!=8) return -1;
	 drive_model[0]='2';
	 drive_model[1]='x';
	 drive_model[2]='-';
	 drive_model[3]='x';
	 drv_type=drv_old;
       }
  for (j=0;j<4;j++) firmware_version[j]=infobuf[i+j];
  j=(firmware_version[0]&0x0F)*100+(firmware_version[2]&0x0F)*10+(firmware_version[3]&0x0F);
  if (new_drive) { if (j<100) drv_type=drv_099;
                   else drv_type=drv_100;
		 }
  else if (j<200) drv_type=drv_199;
  else if (j<201) drv_type=drv_200;
  else if (j<210) drv_type=drv_201;
  else if (j<211) drv_type=drv_210;
  else if (j<300) drv_type=drv_211;
  else drv_type=drv_300;
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
int switch_drive(int num)
{
  int i;

  current_drive=num;
/*
  dssi=&drv_minor;
  size=&drives_space-dssi;
  ax=current_drive*cx;
  esdi=&drives_space+ax;
  for (i=0;i<size;i++) esdi[i]=dssi[i];
  current_drive=num;
  dssi=&drives_space+num*size;
  esdi=&drv_minor;
  for (i=0;i<size;i++) esdi[i]=dssi[i];
*/
  i=drv_minor=num;
  if (card_type==SBPRO)
	  i=(i&0x01)<<1|(i&0x02)>>1;
  OUT(CDo_enable,i);
#if 0
  printk("switch_drive: drive %d activated.\n",i);
#endif 0
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
int check_drives(void)
{
  int i, j;

#if testini
  printk("check_drives entered.\n");
#endif testini

  current_drive=0;
  for (j=0;j<=NR_SBPCD;j++)
    { drv_minor=i=j;
  if (card_type==SBPRO)
      i=((j&0x01)<<1)|((j&0x02)>>1);
      OUT(CDo_enable,i);
#if 0
      printk("Check_drives: drive %d activated.\n",i);
#endif 0
      i=check_version();

#if testini
      printk("check_version returns %d.\n",i);
#endif testini

      if (i>=0) { drv_options=drv_pattern[drv_minor];
		  if (!new_drive) drv_options &= ~(speed_auto|speed_300|speed_150);
		  switch_drive(current_drive);
		  current_drive++;
		  printk("MINOR %d: %s%.4s (%.4s)\n",
			 drv_minor,drive_family,drive_model,firmware_version);
		}
      if (current_drive>=ndrives) break;
    }
  if (current_drive==0) return -1;
  ndrives=current_drive;
  if (ndrives>=4) current_drive--;
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
void timewait(void)
{ int i;
  for (i=0; i<65500; i++);
}
/*==========================================================================*/
/*==========================================================================*/
/*
 *  obtain if requested service disturbs current audio state
 */            
#if FUTURE
int obey_audio_state(u_char audio_state, u_char func,u_char subfunc)
{
  switch (audio_state)                   /* audio status from controller  */
    { case aud_11: /* "audio play in progress" */
      case audx11:
	switch (func)                      /* DOS command code */
	  { case cmd_07: /* input flush  */
	    case cmd_0d: /* open device  */
	    case cmd_0e: /* close device */
	    case cmd_0c: /* ioctl output */
	      return 1;
	    case cmd_03: /* ioctl input  */
	      switch (subfunc)
		/* DOS ioctl input subfunction */
		{ case cxi_00:
		  case cxi_06:
		  case cxi_09:
		    return 1;
		  default:
		    return ERROR15;
		  }
	      return 1;
	    default:
	      return ERROR15;
	    }
	return 1;
      case aud_12:                  /* "audio play paused"      */
      case audx12:
	return 1;
      default:
	return 2;
      }
}
#endif FUTURE
/*==========================================================================*/
/*==========================================================================*/
/* allowed is only
 * ioctl_o, flush_input, open_device, close_device, 
 * tell_address, tell_volume, tell_capabiliti,
 * tell_framesize, tell_CD_changed, tell_audio_posi
 */
int check_allowed1(u_char func1, u_char func2)
{
/*
  if (func1==ioctl_o) return 0;
  if (func1==read_long) return -1;
  if (func1==read_long_prefetch) return -1;
  if (func1==seek) return -1;
  if (func1==audio_play) return -1;
  if (func1==audio_pause) return -1;
  if (func1==audio_resume) return -1;
  if (func1!=ioctl_i) return 0;
  if (func2==tell_SubQ_run_tot) return -1;
  if (func2==tell_cdsize) return -1;
  if (func2==tell_TocDescrip) return -1;
  if (func2==tell_TocEntry) return -1;
  if (func2==tell_subQ_info) return -1;
  if (new_drive) if (func2==tell_SubChanInfo) return -1;
  if (func2==tell_UPC) return -1;
*/
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
int check_allowed2(u_char func1, u_char func2)
{
/*
  if (func1==read_long) return -1;
  if (func1==read_long_prefetch) return -1;
  if (func1==seek) return -1;
  if (func1==audio_play) return -1;
  if (func1!=ioctl_o) return 0;
  if (new_drive) { if (func2==EjectDisk) return -1;
                   if (func2==CloseTray) return -1;
		   }
*/
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
int check_allowed3(u_char func1, u_char func2)
{
/*
  if (func1==ioctl_i) { if (func2==tell_address) return 0;
                        if (func2==tell_capabiliti) return 0;
			if (func2==tell_CD_changed) return 0;
			if (!new_drive) if (func2==tell_SubChanInfo) return 0;
			return -1;
		      }
  if (func1==ioctl_o) { if (func2==DriveReset) return 0;
                        if (!new_drive) { if (func2==EjectDisk) return 0;
			                  if (func2==LockDoor) return 0;
					  if (func2==CloseTray) return 0;
					}
			return -1;
		      }
  if (func1==flush_input) return -1;
  if (func1==read_long) return -1;
  if (func1==read_long_prefetch) return -1;
  if (func1==seek) return -1;
  if (func1==audio_play) return -1;
  if (func1==audio_pause) return -1;
  if (func1==audio_resume) return -1;
*/
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
/*==========================================================================*/
int seek_pos_audio_end(void)
{
  int i;

  i=msf2blk(pos_audio_end)-1;
  if (i<0) return -1;
  i=xx_Seek(i,0);
  return i;
}
/*==========================================================================*/
/*==========================================================================*/
int ReadToC(void)
{
  int i, j;
  diskstate_flags &= ~toc_bit;
  ored_ctl_adr=0;
  for (j=n_first_track;j<=n_last_track;j++)
    { i=xx_ReadTocEntry(j);
      if (i<0) return i;
      TocBuffer[j].nixbyte=TocEnt_nixbyte;
      TocBuffer[j].ctl_adr=TocEnt_ctl_adr;
      TocBuffer[j].number=TocEnt_number;
      TocBuffer[j].format=TocEnt_format;
      TocBuffer[j].address=TocEnt_address;
      ored_ctl_adr |= TocEnt_ctl_adr;
    }
/* fake entry for LeadOut Track */
  TocBuffer[j].nixbyte=0;
  TocBuffer[j].ctl_adr=0;
  TocBuffer[j].number=0;
  TocBuffer[j].format=0;
  TocBuffer[j].address=size_msf;

  diskstate_flags |= toc_bit;
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
int DiskInfo(void)
{
  int i;

  i=SetSpeed();
  if (i<0) { printk("SBPCD: DiskInfo: SetSpeed returns %d\n", i);
	     return i;
	   }
  i=xx_ModeSense();
  if (i<0) { printk("SBPCD: DiskInfo: xx_ModeSense returns %d\n", i);
	     return i;
	   }
  i=xx_ReadCapacity();
  if (i<0) { printk("SBPCD: DiskInfo: ReadCapacity returns %d\n", i);
	     return i;
	   }
  i=xx_ReadTocDescr();
  if (i<0) { printk("SBPCD: DiskInfo: ReadTocDescr returns %d\n", i);
	     return i;
	   }
  i=ReadToC();
  if (i<0) { printk("SBPCD: DiskInfo: ReadToC returns %d\n", i);
	     return i;
	   }
  i=yy_8d();
  if (i<0) { printk("SBPCD: DiskInfo: yy_8d returns %d\n", i);
	     return i;
	   }
  i=xx_ReadTocEntry(n_first_track);
  if (i<0) { printk("SBPCD: DiskInfo: xx_ReadTocEntry(1) returns %d\n", i);
	     return i;
	   }
  i=xx_ReadUPC();
  if (i<0) { printk("SBPCD: DiskInfo: xx_ReadUPC returns %d\n", i);
	     return i;
	   }
  return 0;
}
/*==========================================================================*/
/*
 *  called always if driver gets entered
 *  returns 0 or ERROR2 or ERROR15
 */
int prepare(u_char func, u_char subfunc)
{
  int i;

  if (!new_drive)
    { i=inb(CDi_status);
      if (i&s_attention) GetStatus();
    }
  else GetStatus();
  if (CD_changed==0xFF)
    { diskstate_flags=0;
      audio_state=0;
      if (!st_diskok)
	{ i=check_allowed1(func,subfunc);
	  if (i<0) return -2;
	}
      else 
	{ i=check_allowed3(func,subfunc);
	  if (i<0) { CD_changed=1; return -15; }
	}
    }
  else
    { if (!st_diskok)
	{ diskstate_flags=0;
	  audio_state=0;
	  i=check_allowed1(func,subfunc);
	  if (i<0) return -2;
	}
      else
	{ if (st_busy)
	    { if (audio_state!=audio_pausing)
		{ i=check_allowed2(func,subfunc);
		  if (i<0) return -2;
		}
	    }
	  else
	    { if (audio_state==audio_playing) seek_pos_audio_end();
	      audio_state=0;
	    }
	  if (!frame_size_valid)
	    { i=DiskInfo();
	      if (i<0) { diskstate_flags=0;
			 audio_state=0;
			 i=check_allowed1(func,subfunc);
			 if (i<0) return -2;
		       }
	    }
	}
    }
  return 0;
}
/*==========================================================================*/
/*==========================================================================*/
int xx_PlayAudioMSF(int pos_audio_start,int pos_audio_end)
{
  int i;

  if (audio_state==audio_playing) return -EINVAL;
  clr_cmdbuf();
  if (new_drive) { drvcmd[0]=0x0E;
		   flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check|f_wait_if_busy;
		 }
  else { drvcmd[0]=0x0B;
	 flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_wait_if_busy;
       }
  drvcmd[1]=(pos_audio_start>>16)&0x00FF;
  drvcmd[2]=(pos_audio_start>>8)&0x00FF;
  drvcmd[3]=pos_audio_start&0x00FF;
  drvcmd[4]=(pos_audio_end>>16)&0x00FF;
  drvcmd[5]=(pos_audio_end>>8)&0x00FF;
  drvcmd[6]=pos_audio_end&0x00FF;
  response_count=0;
  i=cmd_out();
  return i;
}
/*==========================================================================*/
/*==========================================================================*/

/*==========================================================================*/
/*==========================================================================*/
/*
 * ioctl support, adopted from scsi/sr_ioctl.c and mcd.c
 */
static int sbpcd_ioctl(struct inode *inode,struct file *file,
                                      u_int cmd, u_long arg)
{
  int i, st;
  
#if test_ioctl-1
  printk("SBPCD_IOCTL: entered.\n");
#endif test_ioctl

  if (!inode) return -EINVAL;

  st=GetStatus();
  if (st<0) return -EIO;
  
  if (!toc_valid)
    { i=DiskInfo();
      if (i<0) return -EIO;	/* error reading TOC */
    }
  
  if (MINOR(inode->i_rdev)>NR_SBPCD) return -ENODEV;

#if test_ioctl-1
  printk("SBPCD_IOCTL: function request %04X\n", cmd);
#endif test_ioctl

  switch (cmd) 		/* Sun-compatible */
    {
    case CDROMPAUSE:     /* Pause the drive */
#if test_ioctl
      printk("SBPCD_IOCTL: CDROMPAUSE entered.\n");
#endif test_ioctl
      /* pause the drive unit when it is currently in play mode,         */
      /* or reset the starting and ending locations when in paused mode. */
      /* If applicable, at the next stopping point it reaches            */
      /* the drive will discontinue playing.                             */
      switch (audio_state)
	{ case audio_playing:
                              i=xx_Pause_Resume(1);
			      if (i<0) return -EIO;
			      audio_state=audio_pausing;
			      i=xx_ReadSubQ();
			      if (i<0) return -EIO;
			      pos_audio_start=SubQ_run_tot;
			      return 0;
	  case audio_pausing:
			      i=xx_Seek(pos_audio_start,1);
			      if (i<0) return -EIO;
			      return 0;
          default:
			      return -EINVAL;
	}
      
    case CDROMRESUME: /* resume paused audio play */
#if test_ioctl
      printk("SBPCD_IOCTL: CDROMRESUME entered.\n");
#endif test_ioctl
      /* resume playing audio tracks when a previous PLAY AUDIO call has  */
      /* been paused with a STOP AUDIO command.                           */
      /* It will resume playing from the starting location indicated by   */
      /* the Audio Status Info IOCTL.                                     */
      /* It will modify the Audio Paused bit returned by the Audio Status */
      /* Info IOCTL, and the busy bit in the status word.                 */
      if (audio_state!=audio_pausing) return -EINVAL;
      i=xx_Pause_Resume(3);
      if (i<0) return -EIO;
      audio_state=audio_playing;
      return 0;

    case CDROMPLAYMSF:
#if test_ioctl-1
      printk("SBPCD_IOCTL: CDROMPLAYMSF entered.\n");
#endif test_ioctl
      if (audio_state==audio_playing) return -EINVAL;
      st=verify_area(VERIFY_READ, (void *) arg, sizeof(struct cdrom_msf));
      if (st) return st;
      memcpy_fromfs(&msf, (void *) arg, sizeof(struct cdrom_msf));
      /* values come as msf-bin */
      pos_audio_start=(msf.cdmsf_min0<<16)|(msf.cdmsf_sec0<<8)|msf.cdmsf_frame0;
      pos_audio_end=(msf.cdmsf_min1<<16)|(msf.cdmsf_sec1<<8)|msf.cdmsf_frame1;
#if test_ioctl
      printk("SBPCD_IOCTL: CDROMPLAYMSF %08X %08X\n",pos_audio_start,pos_audio_end);
#endif test_ioctl
      i=xx_PlayAudioMSF(pos_audio_start,pos_audio_end);
#if test_ioctl
      printk("SBPCD_IOCTL: xx_PlayAudioMSF returns %d\n",i);
#endif test_ioctl
#if 0
      if (i<0) return -EIO;
#endif 0
      audio_state=audio_playing;
      return 0;

    case CDROMPLAYTRKIND:     /* Play a track.  This currently ignores index. */
#if test_ioctl
      printk("SBPCD_IOCTL: CDROMPLAYTRKIND entered.\n");
#endif test_ioctl
      st=verify_area(VERIFY_READ,(void *) arg,sizeof(struct cdrom_ti));
      if (st) return st;
      memcpy_fromfs(&ti,(void *) arg,sizeof(struct cdrom_ti));
#if test_ioctl
      printk("SBPCD_IOCTL: trk0: %d, ind0: %d, trk1:%d, ind1:%d\n",
	     ti.cdti_trk0,ti.cdti_ind0,ti.cdti_trk1,ti.cdti_ind1);
#endif test_ioctl
      if (ti.cdti_trk0<n_first_track) return -EINVAL;
      if (ti.cdti_trk0>n_last_track) return -EINVAL;
      if (ti.cdti_trk1<=ti.cdti_trk0) ti.cdti_trk1=ti.cdti_trk0;
      if (ti.cdti_trk1>n_last_track) ti.cdti_trk1=n_last_track;
      pos_audio_start=TocBuffer[ti.cdti_trk0].address;
      pos_audio_end=TocBuffer[ti.cdti_trk1+1].address;
      i=xx_PlayAudioMSF(pos_audio_start,pos_audio_end);
#if 0
      if (i<0) return -EIO;
#endif 0
      audio_state=audio_playing;
      return 0;
	    
    case CDROMREADTOCHDR:        /* Read the table of contents header */
#if test_ioctl
      printk("SBPCD_IOCTL: CDROMREADTOCHDR entered.\n");
#endif test_ioctl
      tochdr.cdth_trk0=n_first_track;
      tochdr.cdth_trk1=n_last_track;
      st=verify_area(VERIFY_WRITE, (void *) arg, sizeof(struct cdrom_tochdr));
      if (st) return st;
      memcpy_tofs((void *) arg, &tochdr, sizeof(struct cdrom_tochdr));
      return 0;

    case CDROMREADTOCENTRY:      /* Read an entry in the table of contents */
#if test_ioctl
      printk("SBPCD_IOCTL: CDROMREADTOCENTRY entered.\n");
#endif test_ioctl
      st=verify_area(VERIFY_READ, (void *) arg, sizeof(struct cdrom_tocentry));
      if (st) return st;
      memcpy_fromfs(&tocentry, (void *) arg, sizeof(struct cdrom_tocentry));
      i=tocentry.cdte_track;
      if (i==CDROM_LEADOUT) i=n_last_track+1;
      else if (i<n_first_track||i>n_last_track) return -EINVAL;
      tocentry.cdte_adr=TocBuffer[i].ctl_adr&0x0F;
      tocentry.cdte_ctrl=(TocBuffer[i].ctl_adr>>4)&0x0F;
      tocentry.cdte_datamode=TocBuffer[i].format;
      if (tocentry.cdte_format==CDROM_MSF) /* MSF-bin required */
	{ tocentry.cdte_addr.msf.minute=(TocBuffer[i].address>>16)&0x00FF;
	  tocentry.cdte_addr.msf.second=(TocBuffer[i].address>>8)&0x00FF;
	  tocentry.cdte_addr.msf.frame=TocBuffer[i].address&0x00FF;
	}
      else if (tocentry.cdte_format==CDROM_LBA) /* blk required */
	tocentry.cdte_addr.lba=msf2blk(TocBuffer[i].address);
      else return -EINVAL;
      st=verify_area(VERIFY_WRITE, (void *) arg, sizeof(struct cdrom_tocentry));
      if (st) return st;
      memcpy_tofs((void *) arg, &tocentry, sizeof(struct cdrom_tocentry));
      return 0;

    case CDROMSTOP:      /* Spin down the drive */
#if test_ioctl
      printk("SBPCD_IOCTL: CDROMSTOP entered.\n");
#endif test_ioctl
      DriveReset();
      audio_state=0;
      return 0;

    case CDROMSTART:  /* Spin up the drive */
#if test_ioctl
      printk("SBPCD_IOCTL: CDROMSTART entered.\n");
#endif test_ioctl
      i=xx_SpinUp();
      audio_state=0;
      return 0;
      
    case CDROMEJECT:
      if (!new_drive) return 0;
      i=yy_SpinDown();
      if (i<0) return -EIO;
      audio_state=0;
      return 0;
      
    case CDROMVOLCTRL:   /* Volume control */
#if test_ioctl
      printk("SBPCD_IOCTL: CDROMVOLCTRL entered.\n");
#endif test_ioctl
      st=verify_area(VERIFY_READ,(void *) arg,sizeof(volctrl));
      if (st) return st;
      memcpy_fromfs(&volctrl,(char *) arg,sizeof(volctrl));
      vol_chan0=0;
      vol_ctrl0=volctrl.channel0;
      vol_chan1=1;
      vol_ctrl1=volctrl.channel1;
      i=xx_SetVolume();
      return 0;

    case CDROMSUBCHNL:   /* Get subchannel info */
#if test_ioctl-1
      printk("SBPCD_IOCTL: CDROMSUBCHNL entered.\n");
#endif test_ioctl
      if ((st_spinning)||(!subq_valid)) { i=xx_ReadSubQ();
					  if (i<0) return -EIO;
					}
      st=verify_area(VERIFY_WRITE, (void *) arg, sizeof(struct cdrom_subchnl));
      if (st)	return st;
      memcpy_fromfs(&subchnl, (void *) arg, sizeof(struct cdrom_subchnl));
      subchnl.cdsc_audiostatus=SubQ_audio;
      subchnl.cdsc_adr=SubQ_ctl_adr;
      subchnl.cdsc_ctrl=SubQ_ctl_adr>>4;
      subchnl.cdsc_trk=bcd2bin(SubQ_trk);
      subchnl.cdsc_ind=bcd2bin(SubQ_pnt_idx);
      if (subchnl.cdsc_format==CDROM_LBA)
	{ subchnl.cdsc_absaddr.lba=msf2blk(SubQ_run_tot);
	  subchnl.cdsc_reladdr.lba=msf2blk(SubQ_run_trk);
	}
      else /* not only if (subchnl.cdsc_format==CDROM_MSF) */
	{ subchnl.cdsc_absaddr.msf.minute=(SubQ_run_tot>>16)&0x00FF;
	  subchnl.cdsc_absaddr.msf.second=(SubQ_run_tot>>8)&0x00FF;
	  subchnl.cdsc_absaddr.msf.frame=SubQ_run_tot&0x00FF;
	  subchnl.cdsc_reladdr.msf.minute=(SubQ_run_trk>>16)&0x00FF;
	  subchnl.cdsc_reladdr.msf.second=(SubQ_run_trk>>8)&0x00FF;
	  subchnl.cdsc_reladdr.msf.frame=SubQ_run_trk&0x00FF;
	}
      memcpy_tofs((void *) arg, &subchnl, sizeof(struct cdrom_subchnl));
      return 0;

    case CDROMREADMODE2:
#if test_ioctl
      printk("SBPCD_IOCTL: CDROMREADMODE2 entered.\n");
#endif test_ioctl
      return -EINVAL;

    case CDROMREADMODE1:
#if test_ioctl
      printk("SBPCD_IOCTL: CDROMREADMODE1 entered.\n");
#endif test_ioctl
      return -EINVAL;

    default:
#if test_ioctl
  printk("SBPCD_IOCTL: unknown function request %04X\n", cmd);
#endif test_ioctl
      return -EINVAL;
    } /* end switch(cmd) */
}
/*==========================================================================*/
/*==========================================================================*/
/*
 *  Take care of the different block sizes between cdrom and Linux.
 *  When Linux gets variable block sizes this will probably go away.
 */
static void sbp_transfer(void)
{
  long offs;
  
  while ((CURRENT->nr_sectors>0)&&(CURRENT->sector/4>=sbp_first_frame)&&
	 (CURRENT->sector/4<=sbp_last_frame))
    {
      offs = (CURRENT -> sector - sbp_first_frame * 4) * 512;
      memcpy(CURRENT -> buffer, sbp_buf + offs, 512);
      CURRENT -> nr_sectors--;
      CURRENT -> sector++;
      CURRENT -> buffer += 512;
    }
}
/*==========================================================================*/
/*==========================================================================*/
/*
 *  We seem to get never an interrupt.
 */
#if SBPCD_USE_IRQ
static void sbpcd_interrupt(int unused)
{
        int st;

        st = inb(CDi_status) & 0xFF;
#if testi
        printk("\n<SBPCD: INTERRUPT received - CDi_status=%02X>\n", st);
#endif
}
#endif SBPCD_USE_IRQ
/*==========================================================================*/
/*==========================================================================*/
/*
 *  I/O request routine called from Linux kernel.
 */
static void do_sbpcd_request(void)
{
  u_int block,dev;
  u_int nsect;
  int i;
  
  i=prepare(0,0); /* at moment not really a hassle check, but ... */
  if (i!=0) printk("SBPCD: \"prepare\" tells error %d -- ignored\n", i);

request_loop:
  if ((CURRENT==NULL)||(CURRENT->dev<0)) return;
  if (CURRENT -> sector == -1)	return;
  INIT_REQUEST;
  dev = MINOR(CURRENT->dev);
  block = CURRENT->sector;
  nsect = CURRENT->nr_sectors;

  if (CURRENT->cmd != READ) { printk("SBPCD: bad cmd %d\n", CURRENT -> cmd);
			      end_request(0);
			      goto request_loop;
			    }

  sbp_transfer();

  /* if we satisfied the request from the buffer, we're done. */

  if (CURRENT->nr_sectors == 0) { end_request(1);
				  goto request_loop;
				}

  if (!new_drive) if (!st_spinning) xx_SpinUp();
  sti(); /* ???????????????????????????????????????????? trial !!!!!!!!!!!!!!!!!!!!!! */
  SbpTries = 3;
  sbp_start();
}
/*==========================================================================*/
/*==========================================================================*/
/*
 * Start the I/O for the cdrom. Handle retry count.
 */
static void sbp_start(void)
{
  if (SbpTries == 0) { printk("sbpcd: sbp_start: failed after 3 tries\n");
		       end_request(0);
		       SET_TIMER(do_sbpcd_request, 1); /* wait a bit, try again */
		       return;
		     }

  SbpTries--;
  flags_cmd_out |= f_respo2;
  xx_ReadStatus();
  SbpTimeout = 1;
  SET_TIMER(sbp_status, 1);
}
/*==========================================================================*/
/*==========================================================================*/
/*
 * Called from the timer to check the results of the get-status cmd.
 * On success, send the set-mode-command-dummy (preliminary).
 */
static void sbp_status(void)
{
  int st;

  SbpTimeout--;
  st=ResponseStatus();
  if (st<0) { if (SbpTimeout == 0)
                { printk("SBPCD: sbp_status: ResponseStatus timed out.\n");
		  SET_TIMER(sbp_start, 1);        /* wait a bit, try again */
		  return;
                }
	      else { printk("SBPCD: sbp_status: %d\n",status_byte);
		     SET_TIMER(sbp_status, 1);
		     return;
		   }
	    }

  if (!st_spinning) printk("SBPCD: motor got off - ignoring.\n");

  if (st_check) 
    { printk("SBPCD: st_check detected - retrying.\n");
      end_request(0);
      do_sbpcd_request();
      return;
    }
  if (!st_door_closed)
    { printk("SBPCD: door is open - retrying.\n");
      end_request(0);
      do_sbpcd_request();
      return;
    }
  if (!st_caddy_in)
    { printk("SBPCD: disk removed - retrying.\n");
      end_request(0);
      do_sbpcd_request();
      return;
    }
  if (!st_diskok) 
    { printk("SBPCD: !st_diskok detected - retrying.\n");
      end_request(0);
      do_sbpcd_request();
      return;
    }
  if (st_busy) 
    { printk("SBPCD: st_busy detected - retrying.\n");
      end_request(0);
      do_sbpcd_request();
      return;
    }
                            /* if I knew the "set mode" command ... */
  SbpTimeout = 100;
  sbp_read_cmd();
}
/*==========================================================================*/
/*==========================================================================*/
/*
 *  Check the result of the dummy get_status command.  On success, send the
 *  read-data command. Just go_ahead as long as I dont know how to "set mode".
 */
static void sbp_read_cmd(void)
{
  int i;
  int block;

  SbpTimeout--;

  if (SbpTries == 0)
    { printk("sbpcd: sbp_start: sbp_read_cmd timed out\n");
      end_request(0);
      SET_TIMER(do_sbpcd_request, 1);   /* wait a bit, try again */
      return;
    }
  
  sbp_first_frame=sbp_last_frame=-1;      /* purge buffer */

  block=CURRENT->sector/4;
  if (block+SBP_BUFFER_FRAMES<=CDsize_frm) sbp_read_frames=SBP_BUFFER_FRAMES;
  else { sbp_read_frames=CDsize_frm-block; /* avoid reading past end of data */
	 if (sbp_read_frames<1) { printk("sbpcd: requested frame %d, CD size %d ???\n",
					 block, CDsize_frm);
				  sbp_read_frames=1;
				}
       }
  sbp_current = 0;

  flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;

  if (!new_drive)
    { if (drv_type>=drv_201)
	{ lba2msf(block,&drvcmd[1]); /* msf-bcd format required */
	  bin2bcdx(&drvcmd[1]);
	  bin2bcdx(&drvcmd[2]);
	  bin2bcdx(&drvcmd[3]);
	}
      else
	{ drvcmd[1]=(block>>16)&0x000000ff;
	  drvcmd[2]=(block>>8)&0x000000ff;
	  drvcmd[3]=block&0x000000ff;
	}
      drvcmd[4]=0;
      drvcmd[5]=sbp_read_frames;
      drvcmd[6]=(drv_type<drv_201)?0:2; /* flag "lba or msf-bcd format" */
      drvcmd[0]=0x02;              /* "read frames" command for old drives */
      flags_cmd_out |= f_lopsta|f_getsta|f_bit1;
    }
  else
    { /* if ((yy_8d_valid)&&(n_msf3_valid)&&(block==0x000210)) block=msf2blk(n_msf3); */
      lba2msf(block,&drvcmd[1]); /* msf-bin format required */
      drvcmd[4]=0;
      drvcmd[5]=0;
      drvcmd[6]=sbp_read_frames;
      drvcmd[0]=0x10;              /* "read frames" command for new drives */
    }
#ifdef	SBPCD_DISABLE_IRQ
  cli();
#endif	SBPCD_DISABLE_IRQ
  for (i=0;i<7;i++) OUT(CDo_command,drvcmd[i]);
#ifdef	SBPCD_DISABLE_IRQ
  sti();
#endif	SBPCD_DISABLE_IRQ

  SbpTimeout = 300;
  SET_TIMER(sbp_data, 1);
}
/*==========================================================================*/
/*==========================================================================*/
/*
 *  Check the completion of the read-data command.  On success, read
 *  the SBP_BUFFER_FRAMES * 2048 bytes of data from the disk into buffer.
 */
static void sbp_data(void)
{
int i, j=0, frame, try;
u_char *p;
int error_flag;

SbpTimeout--;
error_flag=0;

for (frame=sbp_current;frame<sbp_read_frames&&!error_flag; frame++)
	{
#ifdef	SBPCD_DISABLE_IRQ
        cli();
#endif	SBPCD_DISABLE_IRQ

#if 0000000
/*##this should work##########################################*/
        for (i=maxtim16;i!=0;i--)
	  { j=inb(CDi_status);
	    if (!(j&s_not_data_ready)) break;
	    if (!(j&s_not_result_ready)) break;
	    if (!new_drive) if (j&s_attention) break;
	  }
	if (i==0) { status_timeout; break; }
	if (j&s_not_data_ready) break;
/*############################################################*/
#endif 0000000
#if 0000000
/*##this would loop forever on pure audio cd)#################*/
	j=inb(CDi_status);
	if (!new_drive) if (j&s_attention) { error_flag++; break; }

	/* all necessary waits are left to the timer */
	if (j&s_not_data_ready)
	  { if (SbpTimeout==0) { printk("sbpcd: sbp_data: DATA_READY timeout - retrying\n");
				 SET_TIMER(sbp_start, 10);
			       }
	    else { /* printk("%d", sbp_current); */
	           SET_TIMER(sbp_data, 1);
		 }
#ifdef	SBPCD_DISABLE_IRQ
	    sti();
#endif	SBPCD_DISABLE_IRQ
	    return;
	  }
/*############################################################*/
#endif 0000000

	for (try=maxtim16;try!=0;try--)
	  { j=inb(CDi_status);
	    if (!(j&s_not_data_ready)) break;
	    if (!(j&s_not_result_ready)) break;
	    if (!new_drive) if (j&s_attention) break;
	  }
	if (try==0) { printk("SBPCD: sbp_data: CDi_status timeout.\n");
		      error_flag++;
		      break;
		    }
	if (j&s_not_data_ready)
	  { if ((ored_ctl_adr&0x40)==0)
	      printk("SBPCD: CD contains no data tracks.\n");
	    else printk("SBPCD: sbp_data: DATA_READY timeout.\n");
	    error_flag++;
	    break;
	  }

#ifdef	SBPCD_DISABLE_IRQ
	sti();
#endif	SBPCD_DISABLE_IRQ

	CLEAR_TIMER;
	error_flag=0;
	p = sbp_buf + frame *  CD_FRAMESIZE;

  if (card_type==SBPRO) {
	OUT(CDo_sel_d_i,0x01);
	READ_DATA(CDi_data, p, CD_FRAMESIZE);
	OUT(CDo_sel_d_i,0x00);
  } else
	READ_DATA(CDi_data, p, CD_FRAMESIZE);
  
	sbp_current++;
        }

#ifdef	SBPCD_DISABLE_IRQ
	sti();
#endif	SBPCD_DISABLE_IRQ

if (error_flag)    /* must have been spurious D_RDY or (ATTN&&!D_RDY) */
       {
       printk("SBPCD: read aborted by drive\n");
       i=DriveReset();                /* ugly fix to prevent a hang */
       if (SbpTries <= 1) { end_request(0);
			    do_sbpcd_request();
			  }
       else SET_TIMER(sbp_start, 10);
       return;
       }

/*
#define ATTN_DELAY_COUNT 1000
for (i=0; !DRV_ATTN && i < ATTN_DELAY_COUNT; i++);
*/

if (!new_drive) {
#ifdef	SBPCD_DISABLE_IRQ
		  cli();
#endif	SBPCD_DISABLE_IRQ
                  for (i=maxtim16;i!=0;i--)
		    { j=inb(CDi_status);
		      if (!(j&s_not_data_ready)) break;
		      if (!(j&s_not_result_ready)) break;
		      if (j&s_attention) break;
		    }
		  if (i==0) { printk("STATUS TIMEOUT AFTER READ"); }
		  if (!(j&s_attention))
		    { if (SbpTimeout == 0)
			{ printk("sbpcd: sbp_data: timeout waiting DRV_ATTN - retrying\n");
			  i=DriveReset();  /* ugly fix to prevent a hang */
			  SET_TIMER(sbp_start, 10);
			}
		      else { /* printk("a"); */
			     SET_TIMER(sbp_data, 1);
			   }
#ifdef	SBPCD_DISABLE_IRQ
		      sti();
#endif	SBPCD_DISABLE_IRQ
		      return;
		    }
#ifdef	SBPCD_DISABLE_IRQ
		  sti();
#endif	SBPCD_DISABLE_IRQ
		}

do { if (!(new_drive)) xx_ReadStatus();
     i=ResponseStatus();  /* builds status_byte, returns orig. status (old) or faked p_success_old (new) */
     if (i<0) { printk("SBPCD: xx_ReadStatus error after read: %02X\n",status_byte);
		end_request(0);
		do_sbpcd_request();
		return;
	      }
   }
while ((!new_drive)&&(!st_check)&&(!(i&p_success_old)));
if (st_check) { i=xx_ReadError();
		printk("SBPCD: xx_ReadError was necessary after read: %02X\n",i);
		end_request(0);
		do_sbpcd_request();
		return;
	      }

sbp_first_frame = CURRENT -> sector / 4;
sbp_last_frame = sbp_first_frame + sbp_read_frames - 1;
sbp_transfer();
end_request(1);
SET_TIMER(do_sbpcd_request, 1);
}

/*==========================================================================*/
/*==========================================================================*/
/*
 *  Open the device special file.  Check that a disk is in. Read TOC.
 */
int sbpcd_open(struct inode *ip, struct file *fp)
{
  int i;

  if (sbpPresent == 0) return -ENXIO;             /* no hardware */

  flags_cmd_out |= f_respo2;
  xx_ReadStatus();                         /* command: give 1-byte status */
  i=ResponseStatus();
  if (i<0)
    { printk("SBPCD: sbpcd_open: xx_ReadStatus timed out\n");
      return -EIO;                  /* drive doesn't respond */
    }
#if 00
  printk("sbpcd_open: status %02X\n", status_byte);
#endif 00
  if (!st_door_closed||!st_caddy_in)
    { printk("sbpcd_open: no disk in drive\n");
      return -EIO;
    }
#if 00
  if (!new_drive) if (!st_spinning) xx_SpinUp();
#endif 00
  i=DiskInfo();
#if 00
  if ((ored_ctl_adr&0x40)==0)
    printk("SBPCD: CD contains no data tracks.\n");
#endif 00
  return 0;
}

/*==========================================================================*/
/*==========================================================================*/
/*
 *  On close, we flush all sbp blocks from the buffer cache.
 */
static void sbpcd_release(struct inode * inode, struct file * file)
{
sbp_first_frame=sbp_last_frame=-1;
sync_dev(inode->i_rdev);
invalidate_buffers(inode->i_rdev);
diskstate_flags &= ~cd_size_bit;
}

/*==========================================================================*/
/*==========================================================================*/
/*
 *
 */
static struct file_operations sbpcd_fops = {
        NULL,                   /* lseek - default */
        block_read,             /* read - general block-dev read */
        block_write,            /* write - general block-dev write */
        NULL,                   /* readdir - bad */
        NULL,                   /* select */
        sbpcd_ioctl,            /* ioctl */
        NULL,                   /* mmap */
        sbpcd_open,             /* open */
        sbpcd_release           /* release */
};

/*==========================================================================*/
/*==========================================================================*/
/*
 *  SBP interrupt descriptor
 */
#if SBPCD_USE_IRQ
static struct sigaction sbpcd_sigaction = { sbpcd_interrupt,
                                            0,
                                            SA_INTERRUPT,
                                            NULL
                                          };
#endif SBPCD_USE_IRQ

/*==========================================================================*/
/*==========================================================================*/
/*==========================================================================*/
/*
 *  Test for presence of drive and initialize it.  Called at boot time.
 */
u_long sbpcd_init(u_long mem_start, u_long mem_end)
{
int i, j;

#if 0
printk("SBPCD: Trying to detect a ");
if (card_type== SBPRO)
	printk("SoundBlaster ");
else
	printk("LaserMate ");
printk("CD-ROM drive:\n");
#endif 0

printk("SBPCD: Trying to detect a SoundBlaster or LaserMate CD-ROM drive:\n");

blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
if (register_blkdev(MAJOR_NR, "sbpcd", &sbpcd_fops) != 0)
  {       printk("SBPCD: Unable to get major %d for such a CD-ROM\n", MAJOR_NR);
	  return mem_start;
  }
read_ahead[MAJOR_NR] = 4;
#if SBPCD_USE_IRQ
if (irqaction(SBPCD_INTR_NR, &sbpcd_sigaction))
                printk("SBPCD: Unable to get IRQ%d for sbpcd driver\n", SBPCD_INTR_NR);
#endif SBPCD_USE_IRQ

check_datarate();

                                     /* now, check for card and drive */
printk("SBPCD: - ");

i=check_drives();
if (i<0) { printk("SBPCD: No drive found.\n");
	   return mem_start;
	 }
sbpPresent = 1;
drvcnt=ndrives;
printk("SBPCD: %d CD-ROM drive(s) connected.\n", drvcnt);
for (i=0;i<150;i++) OUT(CDo_reset,0x00);
flush_status();

#if 0
printk("drvcnt: %d (1), drv_minor: %d(0)\n",drvcnt, drv_minor);
#endif 0
 
for (j=0;j<drvcnt;j++) { switch_drive(j);
			 xx_ReadStatus();
			 i=ResponseStatus();  /* returns orig. status or p_busy_new */
			 if (i<0) printk("init: ResponseStatus returns %02X\n",i);
			 else { if (st_check) { i=xx_ReadError();
#if 0
						printk("init: xx_ReadError returns %d\n",i);
#endif 0
					      }
			      }
#if 0
			 printk("first GetStatus gives %d\n",i);
#endif 0
			 if (error_byte==aud_12) { do { i=GetStatus();
#if 0
							 printk("second GetStatus gives %02X\n",i);
#endif 0
							 if (i<0) break;
							 if (!st_caddy_in) break;
						       }
						    while (!st_diskok);
				      }
			 i=SetSpeed();
			 if (i>=0) CD_changed=1;
		       }

if (card_type==SBPRO) {
OUT(MIXER_addr,MIXER_CD_Volume);
OUT(MIXER_data,0xCC); /* one nibble per channel */
}

sbp_buf=(u_char *)mem_start;
mem_start += SBP_BUFFER_FRAMES*CD_FRAMESIZE;
return mem_start;
}

void sbpcd_setup(char *str, int *ints)
{
        if (ints[0]>=1)
                SBPCD_INTR_NR = ints[1];
        if (ints[0]>=2)
                CARD_BASE = ints[2];
	if (!strcmp(str,"lasermate"))
	{	card_type=LASERMATE;
		BASEADDR = CARD_BASE;
		CDi_data = BASEADDR;
	}
	else
	{	BASEADDR = CARD_BASE+0x10;
		CDi_data = BASEADDR+2;
	}
}

/*==========================================================================*/
/*==========================================================================*/
/*
 * adopted from sr.c
 *
 * Check if the media has changed in the CD-ROM drive.
 * used externally (isofs/inode.c) - but still does not work.
 *
 */
int check_sbpcd_media_change(int full_dev, int unused)
{
  int st;

  if (MAJOR(full_dev) != MAJOR_NR) 
    { printk("SBPCD: CD-ROM request error: invalid device.\n");
      return -1;
    }
  
  xx_ReadStatus();                         /* command: give 1-byte status */
  st=ResponseStatus();
#if test_chk
  printk("SBPCD: media_check: %02X\n",status_byte);
#endif test_chk
  if (st<0) { printk("SBPCD: media_check: ResponseStatus error.\n");
	      return 1; /* status not obtainable */
	    }
#if test_chk
#if 0000
  if (CD_changed==0xFF) printk("SBPCD: media_check: \"changed\" assumed.\n");
#endif 0000
  if (!st_spinning) printk("SBPCD: media_check: motor off.\n");
  if (!st_door_closed) printk("SBPCD: media_check: door open.\n");
  if (!st_caddy_in) printk("SBPCD: media_check: no disk in drive.\n");
  if (!st_diskok) printk("SBPCD: media_check: !st_diskok.\n");
#endif test_chk

#if 0000
  if (CD_changed==0xFF)
    { CD_changed=1;
      return 1; /* driver had a change detected before */
    }
#endif 0000 /* seems to give additional errors at the moment */

  if (!st_diskok) return 1; /* disk not o.k. */
  if (!st_caddy_in) return 1; /* disk removed */
  if (!st_door_closed) return 1; /* door open */
  return 0;
}

/*==========================================================================*/
/*==========================================================================*/
/*
 *  Well, That's It for now.
 */
/*==========================================================================*/
/*==========================================================================*/
/*3456789012345678901234567890123456789012345678901234567890123456789012345678*/
