/* * Sony CDU-31A CDROM interface device driver. * * Corey Minyard (minyard@wf-rch.cirr.com) * * Colossians 3:17 * * The Sony interface device driver handles Sony interface CDROM * drives and provides a complete block-level interface as well as an * ioctl() interface compatible with the Sun (as specified in * include/linux/cdrom.h). With this interface, CDROMs can be * accessed and standard audio CDs can be played back normally. * * This interface is (unfortunatly) a polled interface. This is * because most Sony interfaces are set up with DMA and interrupts * disables. Some (like mine) do not even have the capability to * handle interrupts or DMA. For this reason you will see a lot of * the following: * * retry_count = jiffies+ SONY_JIFFIES_TIMEOUT; * while ((retry_count > jiffies) && (! #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAJOR_NR CDU31A_CDROM_MAJOR #include "blk.h" #define CDU31A_MAX_CONSECUTIVE_ATTENTIONS 10 static unsigned short cdu31a_addresses[] = { 0x340, /* Standard configuration Sony Interface */ 0x1f88, /* Fusion CD-16 */ 0x230, /* SoundBlaster 16 card */ 0x360, /* Secondary standard Sony Interface */ 0x320, /* Secondary standard Sony Interface */ 0x330, /* Secondary standard Sony Interface */ 0 }; static int handle_sony_cd_attention(void); static int read_subcode(void); static void sony_get_toc(void); static int scd_open(struct inode *inode, struct file *filp); static void do_sony_cd_cmd(unsigned char cmd, unsigned char *params, unsigned int num_params, unsigned char *result_buffer, unsigned int *result_size); static void size_to_buf(unsigned int size, unsigned char *buf); /* The base I/O address of the Sony Interface. This is a variable (not a #define) so it can be easily changed via some future ioctl() */ static unsigned short sony_cd_base_io = 0; /* * The following are I/O addresses of the various registers for the drive. The * comment for the base address also applies here. */ static volatile unsigned short sony_cd_cmd_reg; static volatile unsigned short sony_cd_param_reg; static volatile unsigned short sony_cd_write_reg; static volatile unsigned short sony_cd_control_reg; static volatile unsigned short sony_cd_status_reg; static volatile unsigned short sony_cd_result_reg; static volatile unsigned short sony_cd_read_reg; static volatile unsigned short sony_cd_fifost_reg; static int sony_disc_changed = 1; /* Has the disk been changed since the last check? */ static int sony_toc_read = 0; /* Has the table of contents been read? */ static int sony_spun_up = 0; /* Has the drive been spun up? */ static unsigned int sony_buffer_size; /* Size in bytes of the read-ahead buffer. */ static unsigned int sony_buffer_sectors; /* Size (in 2048 byte records) of the read-ahead buffer. */ static unsigned int sony_usage = 0; /* How many processes have the drive open. */ static volatile int sony_first_block = -1; /* First OS block (512 byte) in the read-ahead buffer */ static volatile int sony_last_block = -1; /* Last OS block (512 byte) in the read-ahead buffer */ static struct s_sony_toc *sony_toc; /* Points to the table of contents. */ static struct s_sony_subcode * volatile last_sony_subcode; /* Points to the last subcode address read */ static unsigned char * volatile sony_buffer; /* Points to the read-ahead buffer */ static volatile int sony_inuse = 0; /* Is the drive in use? Only one operation at a time allowed */ static struct wait_queue * sony_wait = NULL; static struct task_struct *has_cd_task = NULL; /* The task that is currently using the CDROM drive, or NULL if none. */ /* * The audio status uses the values from read subchannel data as specified * in include/linux/cdrom.h. */ static volatile int sony_audio_status = CDROM_AUDIO_NO_STATUS; /* * The following are a hack for pausing and resuming audio play. The drive * does not work as I would expect it, if you stop it then start it again, * the drive seeks back to the beginning and starts over. This holds the * position during a pause so a resume can restart it. It uses the * audio status variable above to tell if it is paused. */ unsigned volatile char cur_pos_msf[3] = { 0, 0, 0 }; unsigned volatile char final_pos_msf[3] = { 0, 0, 0 }; /* * This routine returns 1 if the disk has been changed since the last * check or 0 if it hasn't. Setting flag to 0 resets the changed flag. */ int check_cdu31a_media_change(int full_dev, int flag) { int retval, target; target = MINOR(full_dev); if (target > 0) { printk("Sony CD-ROM request error: invalid device.\n"); return 0; } retval = sony_disc_changed; if (!flag) { sony_disc_changed = 0; } return retval; } /* * Wait a little while (used for polling the drive). If in initialization, * setting a timeout doesn't work, so just loop for a while. */ static inline void sony_sleep(void) { current->state = TASK_INTERRUPTIBLE; current->timeout = jiffies; schedule(); } /* * The following are convenience routine to read various status and set * various conditions in the drive. */ static inline int is_attention(void) { return((inb(sony_cd_status_reg) & SONY_ATTN_BIT) != 0); } static inline int is_busy(void) { return((inb(sony_cd_status_reg) & SONY_BUSY_BIT) != 0); } static inline int is_data_ready(void) { return((inb(sony_cd_status_reg) & SONY_DATA_RDY_BIT) != 0); } static inline int is_data_requested(void) { return((inb(sony_cd_status_reg) & SONY_DATA_REQUEST_BIT) != 0); } static inline int is_result_ready(void) { return((inb(sony_cd_status_reg) & SONY_RES_RDY_BIT) != 0); } static inline int is_param_write_rdy(void) { return((inb(sony_cd_fifost_reg) & SONY_PARAM_WRITE_RDY_BIT) != 0); } static inline void reset_drive(void) { outb(SONY_DRIVE_RESET_BIT, sony_cd_control_reg); } static inline void clear_attention(void) { outb(SONY_ATTN_CLR_BIT, sony_cd_control_reg); } static inline void clear_result_ready(void) { outb(SONY_RES_RDY_CLR_BIT, sony_cd_control_reg); } static inline void clear_data_ready(void) { outb(SONY_DATA_RDY_CLR_BIT, sony_cd_control_reg); } static inline void clear_param_reg(void) { outb(SONY_PARAM_CLR_BIT, sony_cd_control_reg); } static inline unsigned char read_status_register(void) { return(inb(sony_cd_status_reg)); } static inline unsigned char read_result_register(void) { return(inb(sony_cd_result_reg)); } static inline unsigned char read_data_register(void) { return(inb(sony_cd_read_reg)); } static inline void write_param(unsigned char param) { outb(param, sony_cd_param_reg); } static inline void write_cmd(unsigned char cmd) { outb(cmd, sony_cd_cmd_reg); outb(SONY_RES_RDY_INT_EN_BIT, sony_cd_control_reg); } /* * Set the drive parameters so the drive will auto-spin-up when a * disk is inserted. */ static void set_drive_params(void) { unsigned char res_reg[2]; unsigned int res_size; unsigned char params[3]; params[0] = SONY_SD_MECH_CONTROL; params[1] = 0x03; do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD, params, 2, res_reg, &res_size); if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20)) { printk(" Unable to set mechanical parameters: 0x%2.2x\n", res_reg[1]); } } /* * This code will reset the drive and attempt to restore sane parameters. */ static void restart_on_error(void) { unsigned char res_reg[2]; unsigned int res_size; unsigned int retry_count; printk("cdu31a: Resetting drive on error\n"); reset_drive(); retry_count = jiffies + SONY_RESET_TIMEOUT; while ((retry_count > jiffies) && (!is_attention())) { sony_sleep(); } set_drive_params(); do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size); if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20)) { printk("cdu31a: Unable to spin up drive: 0x%2.2x\n", res_reg[1]); } current->state = TASK_INTERRUPTIBLE; current->timeout = jiffies + 200; schedule(); do_sony_cd_cmd(SONY_READ_TOC_CMD, NULL, 0, res_reg, &res_size); if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20)) { printk("cdu31a: Unable to read TOC: 0x%2.2x\n", res_reg[1]); } sony_get_toc(); if (!sony_toc_read) { printk("cdu31a: Unable to get TOC data\n"); } } /* * This routine writes data to the parameter register. Since this should * happen fairly fast, it is polled with no OS waits between. */ static int write_params(unsigned char *params, int num_params) { unsigned int retry_count; retry_count = SONY_READY_RETRIES; while ((retry_count > 0) && (!is_param_write_rdy())) { retry_count--; } if (!is_param_write_rdy()) { return -EIO; } while (num_params > 0) { write_param(*params); params++; num_params--; } return 0; } /* * The following reads data from the command result register. It is a * fairly complex routine, all status info flows back through this * interface. The algorithm is stolen directly from the flowcharts in * the drive manual. */ static void get_result(unsigned char *result_buffer, unsigned int *result_size) { unsigned char a, b; int i; unsigned int retry_count; while (handle_sony_cd_attention()) ; /* Wait for the result data to be ready */ retry_count = jiffies + SONY_JIFFIES_TIMEOUT; while ((retry_count > jiffies) && (is_busy() || (!(is_result_ready())))) { sony_sleep(); while (handle_sony_cd_attention()) ; } if (is_busy() || (!(is_result_ready()))) { result_buffer[0] = 0x20; result_buffer[1] = SONY_TIMEOUT_OP_ERR; *result_size = 2; return; } /* * Get the first two bytes. This determines what else needs * to be done. */ clear_result_ready(); a = read_result_register(); *result_buffer = a; result_buffer++; b = read_result_register(); *result_buffer = b; result_buffer++; *result_size = 2; /* * 0x20 means an error occured. Byte 2 will have the error code. * Otherwise, the command succeded, byte 2 will have the count of * how many more status bytes are coming. * * The result register can be read 10 bytes at a time, a wait for * result ready to be asserted must be done between every 10 bytes. */ if ((a & 0xf0) != 0x20) { if (b > 8) { for (i=0; i<8; i++) { *result_buffer = read_result_register(); result_buffer++; (*result_size)++; } b = b - 8; while (b > 10) { retry_count = SONY_READY_RETRIES; while ((retry_count > 0) && (!is_result_ready())) { retry_count--; } if (!is_result_ready()) { result_buffer[0] = 0x20; result_buffer[1] = SONY_TIMEOUT_OP_ERR; *result_size = 2; return; } clear_result_ready(); for (i=0; i<10; i++) { *result_buffer = read_result_register(); result_buffer++; (*result_size)++; } b = b - 10; } if (b > 0) { retry_count = SONY_READY_RETRIES; while ((retry_count > 0) && (!is_result_ready())) { retry_count--; } if (!is_result_ready()) { result_buffer[0] = 0x20; result_buffer[1] = SONY_TIMEOUT_OP_ERR; *result_size = 2; return; } } } while (b > 0) { *result_buffer = read_result_register(); result_buffer++; (*result_size)++; b--; } } } /* * Read in a 2048 byte block of data. */ static void read_data_block(unsigned char *data, unsigned char *result_buffer, unsigned int *result_size) { int i; unsigned int retry_count; for (i=0; i<2048; i++) { retry_count = jiffies + SONY_JIFFIES_TIMEOUT; while ((retry_count > jiffies) && (!is_data_requested())) { while (handle_sony_cd_attention()) ; sony_sleep(); } if (!is_data_requested()) { result_buffer[0] = 0x20; result_buffer[1] = SONY_TIMEOUT_OP_ERR; *result_size = 2; return; } *data = read_data_register(); data++; } } /* * This routine issues a read data command and gets the data. I don't * really like the way this is done (I would prefer for do_sony_cmd() to * handle it automatically) but I found that the drive returns status * when it finishes reading (not when the host has read all the data) * or after it gets an error. This means that the status can be * received at any time and should be handled immediately (at least * between every 2048 byte block) to check for errors, we can't wait * until all the data is read. * * This routine returns the total number of sectors read. It will * not return an error if it reads at least one sector successfully. */ static unsigned int get_data(unsigned char *orig_data, unsigned char *params, /* 6 bytes with the MSF start address and number of sectors to read. */ unsigned int orig_data_size, unsigned char *result_buffer, unsigned int *result_size) { unsigned int cur_offset; unsigned int retry_count; int result_read; int num_retries; unsigned int num_sectors_read = 0; unsigned char *data = orig_data; unsigned int data_size = orig_data_size; cli(); while (sony_inuse) { interruptible_sleep_on(&sony_wait); if (current->signal & ~current->blocked) { result_buffer[0] = 0x20; result_buffer[1] = SONY_SIGNAL_OP_ERR; *result_size = 2; return 0; } } sony_inuse = 1; has_cd_task = current; sti(); num_retries = 0; retry_data_operation: result_buffer[0] = 0; result_buffer[1] = 0; /* * Clear any outstanding attentions and wait for the drive to * complete any pending operations. */ while (handle_sony_cd_attention()) ; retry_count = jiffies + SONY_JIFFIES_TIMEOUT; while ((retry_count > jiffies) && (is_busy())) { sony_sleep(); while (handle_sony_cd_attention()) ; } if (is_busy()) { result_buffer[0] = 0x20; result_buffer[1] = SONY_TIMEOUT_OP_ERR; *result_size = 2; } else { /* Issue the command */ clear_result_ready(); clear_param_reg(); write_params(params, 6); write_cmd(SONY_READ_CMD); /* * Read the data from the drive one 2048 byte sector at a time. Handle * any results received between sectors, if an error result is returned * terminate the operation immediately. */ cur_offset = 0; result_read = 0; while ((data_size > 0) && (result_buffer[0] == 0)) { /* Wait for the drive to tell us we have something */ retry_count = jiffies + SONY_JIFFIES_TIMEOUT; while ((retry_count > jiffies) && (!(is_result_ready() || is_data_ready()))) { while (handle_sony_cd_attention()) ; sony_sleep(); } if (!(is_result_ready() || is_data_ready())) { result_buffer[0] = 0x20; result_buffer[1] = SONY_TIMEOUT_OP_ERR; *result_size = 2; } /* Handle results first */ else if (is_result_ready()) { result_read = 1; get_result(result_buffer, result_size); } else /* Handle data next */ { /* * The drive has to be polled for status on a byte-by-byte basis * to know if the data is ready. Yuck. I really wish I could use DMA. */ clear_data_ready(); read_data_block(data, result_buffer, result_size); data += 2048; data_size -= 2048; cur_offset = cur_offset + 2048; num_sectors_read++; } } /* Make sure the result has been read */ if (!result_read) { get_result(result_buffer, result_size); } } if ( ((result_buffer[0] & 0x20) == 0x20) && (result_buffer[1] != SONY_NOT_SPIN_ERR) /* No retry when not spin */ && (num_retries < MAX_CDU31A_RETRIES)) { /* * If an error occurs, go back and only read one sector at the * given location. Hopefully the error occurred on an unused * sector after the first one. It is hard to say which sector * the error occurred on because the drive returns status before * the data transfer is finished and doesn't say which sector. */ data_size = 2048; data = orig_data; num_sectors_read = 0; size_to_buf(1, ¶ms[3]); num_retries++; /* Issue a reset on an error (the second time), othersize just delay */ if (num_retries == 2) { restart_on_error(); } else { current->state = TASK_INTERRUPTIBLE; current->timeout = jiffies + 10; schedule(); } /* Restart the operation. */ goto retry_data_operation; } has_cd_task = NULL; sony_inuse = 0; wake_up_interruptible(&sony_wait); return(num_sectors_read); } /* * Do a command that does not involve data transfer. This routine must * be re-entrant from the same task to support being called from the * data operation code when an error occurs. */ static void do_sony_cd_cmd(unsigned char cmd, unsigned char *params, unsigned int num_params, unsigned char *result_buffer, unsigned int *result_size) { unsigned int retry_count; int num_retries; int recursive_call; cli(); if (current != has_cd_task) /* Allow recursive calls to this routine */ { while (sony_inuse) { interruptible_sleep_on(&sony_wait); if (current->signal & ~current->blocked) { result_buffer[0] = 0x20; result_buffer[1] = SONY_SIGNAL_OP_ERR; *result_size = 2; return; } } sony_inuse = 1; has_cd_task = current; recursive_call = 0; } else { recursive_call = 1; } sti(); num_retries = 0; retry_cd_operation: while (handle_sony_cd_attention()) ; retry_count = jiffies + SONY_JIFFIES_TIMEOUT; while ((retry_count > jiffies) && (is_busy())) { sony_sleep(); while (handle_sony_cd_attention()) ; } if (is_busy()) { result_buffer[0] = 0x20; result_buffer[1] = SONY_TIMEOUT_OP_ERR; *result_size = 2; } else { clear_result_ready(); clear_param_reg(); write_params(params, num_params); write_cmd(cmd); get_result(result_buffer, result_size); } if ( ((result_buffer[0] & 0x20) == 0x20) && (num_retries < MAX_CDU31A_RETRIES)) { num_retries++; current->state = TASK_INTERRUPTIBLE; current->timeout = jiffies + 10; /* Wait .1 seconds on retries */ schedule(); goto retry_cd_operation; } if (!recursive_call) { has_cd_task = NULL; sony_inuse = 0; wake_up_interruptible(&sony_wait); } } /* * Handle an attention from the drive. This will return 1 if it found one * or 0 if not (if one is found, the caller might want to call again). * * This routine counts the number of consecutive times it is called * (since this is always called from a while loop until it returns * a 0), and returns a 0 if it happens too many times. This will help * prevent a lockup. */ static int handle_sony_cd_attention(void) { unsigned char atten_code; static int num_consecutive_attentions = 0; if (is_attention()) { if (num_consecutive_attentions > CDU31A_MAX_CONSECUTIVE_ATTENTIONS) { printk("cdu31a: Too many consecutive attentions: %d\n", num_consecutive_attentions); num_consecutive_attentions = 0; return(0); } clear_attention(); atten_code = read_result_register(); switch (atten_code) { /* Someone changed the CD. Mark it as changed */ case SONY_MECH_LOADED_ATTN: sony_disc_changed = 1; sony_toc_read = 0; sony_audio_status = CDROM_AUDIO_NO_STATUS; sony_first_block = -1; sony_last_block = -1; break; case SONY_AUDIO_PLAY_DONE_ATTN: sony_audio_status = CDROM_AUDIO_COMPLETED; read_subcode(); break; case SONY_EJECT_PUSHED_ATTN: sony_audio_status = CDROM_AUDIO_INVALID; break; case SONY_LEAD_IN_ERR_ATTN: case SONY_LEAD_OUT_ERR_ATTN: case SONY_DATA_TRACK_ERR_ATTN: case SONY_AUDIO_PLAYBACK_ERR_ATTN: sony_audio_status = CDROM_AUDIO_ERROR; break; } num_consecutive_attentions++; return(1); } num_consecutive_attentions = 0; return(0); } /* Convert from an integer 0-99 to BCD */ static inline unsigned int int_to_bcd(unsigned int val) { int retval; retval = (val / 10) << 4; retval = retval | val % 10; return(retval); } /* Convert from BCD to an integer from 0-99 */ static unsigned int bcd_to_int(unsigned int bcd) { return((((bcd >> 4) & 0x0f) * 10) + (bcd & 0x0f)); } /* * Convert a logical sector value (like the OS would want to use for * a block device) to an MSF format. */ static void log_to_msf(unsigned int log, unsigned char *msf) { log = log + LOG_START_OFFSET; msf[0] = int_to_bcd(log / 4500); log = log % 4500; msf[1] = int_to_bcd(log / 75); msf[2] = int_to_bcd(log % 75); } /* * Convert an MSF format to a logical sector. */ static unsigned int msf_to_log(unsigned char *msf) { unsigned int log; log = bcd_to_int(msf[2]); log += bcd_to_int(msf[1]) * 75; log += bcd_to_int(msf[0]) * 4500; log = log - LOG_START_OFFSET; return log; } /* * Take in integer size value and put it into a buffer like * the drive would want to see a number-of-sector value. */ static void size_to_buf(unsigned int size, unsigned char *buf) { buf[0] = size / 65536; size = size % 65536; buf[1] = size / 256; buf[2] = size % 256; } /* * The OS calls this to perform a read or write operation to the drive. * Write obviously fail. Reads to a read ahead of sony_buffer_size * bytes to help speed operations. This especially helps since the OS * uses 1024 byte blocks and the drive uses 2048 byte blocks. Since most * data access on a CD is done sequentially, this saves a lot of operations. */ static void do_cdu31a_request(void) { int block; unsigned int dev; int nsect; unsigned char params[10]; unsigned char res_reg[2]; unsigned int res_size; int copyoff; int spin_up_retry; unsigned int read_size; if (!sony_spun_up) { scd_open (NULL,NULL); } while (1) { cdu31a_request_startover: /* * The beginning here is stolen from the hard disk driver. I hope * its right. */ if (!(CURRENT) || CURRENT->dev < 0) { return; } INIT_REQUEST; dev = MINOR(CURRENT->dev); block = CURRENT->sector; nsect = CURRENT->nr_sectors; if (dev != 0) { end_request(0); goto cdu31a_request_startover; } switch(CURRENT->cmd) { case READ: /* * If the block address is invalid or the request goes beyond the end of * the media, return an error. */ if ((block / 4) >= sony_toc->lead_out_start_lba) { end_request(0); goto cdu31a_request_startover; } if (((block + nsect) / 4) >= sony_toc->lead_out_start_lba) { end_request(0); goto cdu31a_request_startover; } while (nsect > 0) { /* * If the requested sector is not currently in the read-ahead buffer, * it must be read in. */ if ((block < sony_first_block) || (block > sony_last_block)) { sony_first_block = (block / 4) * 4; log_to_msf(block/4, params); /* * If the full read-ahead would go beyond the end of the media, trim * it back to read just till the end of the media. */ if (((block / 4) + sony_buffer_sectors) >= sony_toc->lead_out_start_lba) { read_size = sony_toc->lead_out_start_lba - (block / 4); } else { read_size = sony_buffer_sectors; } size_to_buf(read_size, ¶ms[3]); /* * Read the data. If the drive was not spinning, spin it up and try * once more. I know, the goto is ugly, but I am too lazy to fix it. */ spin_up_retry = 0; try_read_again: sony_last_block = sony_first_block + (get_data(sony_buffer, params, (read_size * 2048), res_reg, &res_size) * 4) - 1; if ((res_size < 2) || (res_reg[0] != 0)) { if ((res_reg[1] == SONY_NOT_SPIN_ERR) && (!spin_up_retry)) { do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size); spin_up_retry = 1; goto try_read_again; } printk("Sony CDROM Read error: 0x%2.2x\n", res_reg[1]); sony_first_block = -1; sony_last_block = -1; end_request(0); goto cdu31a_request_startover; } } /* * The data is in memory now, copy it to the buffer and advance to the * next block to read. */ copyoff = (block - sony_first_block) * 512; memcpy(CURRENT->buffer, sony_buffer+copyoff, 512); block += 1; nsect -= 1; CURRENT->buffer += 512; } end_request(1); break; case WRITE: end_request(0); break; default: panic("Unkown SONY CD cmd"); } } } /* * Read the table of contents from the drive and set sony_toc_read if * successful. */ static void sony_get_toc(void) { unsigned int res_size; if (!sony_toc_read) { do_sony_cd_cmd(SONY_REQ_TOC_DATA_CMD, NULL, 0, (unsigned char *) sony_toc, &res_size); if ((res_size < 2) || ((sony_toc->exec_status[0] & 0x20) == 0x20)) { return; } sony_toc->lead_out_start_lba = msf_to_log(sony_toc->lead_out_start_msf); sony_toc_read = 1; } } /* * Search for a specific track in the table of contents. */ static int find_track(int track) { int i; int num_tracks; num_tracks = sony_toc->last_track_num + sony_toc->first_track_num + 1; for (i = 0; i < num_tracks; i++) { if (sony_toc->tracks[i].track == track) { return i; } } return -1; } /* * Read the subcode and put it int last_sony_subcode for future use. */ static int read_subcode(void) { unsigned int res_size; do_sony_cd_cmd(SONY_REQ_SUBCODE_ADDRESS_CMD, NULL, 0, (unsigned char *) last_sony_subcode, &res_size); if ((res_size < 2) || ((last_sony_subcode->exec_status[0] & 0x20) == 0x20)) { printk("Sony CDROM error 0x%2.2x (read_subcode)\n", last_sony_subcode->exec_status[1]); return -EIO; } return 0; } /* * Get the subchannel info like the CDROMSUBCHNL command wants to see it. If * the drive is playing, the subchannel needs to be read (since it would be * changing). If the drive is paused or completed, the subcode information has * already been stored, just use that. The ioctl call wants things in decimal * (not BCD), so all the conversions are done. */ static int sony_get_subchnl_info(long arg) { struct cdrom_subchnl schi; /* Get attention stuff */ while (handle_sony_cd_attention()) ; sony_get_toc(); if (!sony_toc_read) { return -EIO; } verify_area(VERIFY_READ, (char *) arg, sizeof(schi)); verify_area(VERIFY_WRITE, (char *) arg, sizeof(schi)); memcpy_fromfs(&schi, (char *) arg, sizeof(schi)); switch (sony_audio_status) { case CDROM_AUDIO_PLAY: if (read_subcode() < 0) { return -EIO; } break; case CDROM_AUDIO_PAUSED: case CDROM_AUDIO_COMPLETED: break; case CDROM_AUDIO_NO_STATUS: schi.cdsc_audiostatus = sony_audio_status; memcpy_tofs((char *) arg, &schi, sizeof(schi)); return 0; break; case CDROM_AUDIO_INVALID: case CDROM_AUDIO_ERROR: default: return -EIO; } schi.cdsc_audiostatus = sony_audio_status; schi.cdsc_adr = last_sony_subcode->address; schi.cdsc_ctrl = last_sony_subcode->control; schi.cdsc_trk = bcd_to_int(last_sony_subcode->track_num); schi.cdsc_ind = bcd_to_int(last_sony_subcode->index_num); if (schi.cdsc_format == CDROM_MSF) { schi.cdsc_absaddr.msf.minute = bcd_to_int(last_sony_subcode->abs_msf[0]); schi.cdsc_absaddr.msf.second = bcd_to_int(last_sony_subcode->abs_msf[1]); schi.cdsc_absaddr.msf.frame = bcd_to_int(last_sony_subcode->abs_msf[2]); schi.cdsc_reladdr.msf.minute = bcd_to_int(last_sony_subcode->rel_msf[0]); schi.cdsc_reladdr.msf.second = bcd_to_int(last_sony_subcode->rel_msf[1]); schi.cdsc_reladdr.msf.frame = bcd_to_int(last_sony_subcode->rel_msf[2]); } else if (schi.cdsc_format == CDROM_LBA) { schi.cdsc_absaddr.lba = msf_to_log(last_sony_subcode->abs_msf); schi.cdsc_reladdr.lba = msf_to_log(last_sony_subcode->rel_msf); } memcpy_tofs((char *) arg, &schi, sizeof(schi)); return 0; } /* * The big ugly ioctl handler. */ static int scd_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { unsigned int dev; unsigned char res_reg[2]; unsigned int res_size; unsigned char params[7]; int i; if (!inode) { return -EINVAL; } dev = MINOR(inode->i_rdev) >> 6; if (dev != 0) { return -EINVAL; } switch (cmd) { case CDROMSTART: /* Spin up the drive */ do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size); if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20)) { printk("Sony CDROM error 0x%2.2x (CDROMSTART)\n", res_reg[1]); return -EIO; } return 0; break; case CDROMSTOP: /* Spin down the drive */ do_sony_cd_cmd(SONY_AUDIO_STOP_CMD, NULL, 0, res_reg, &res_size); /* * Spin the drive down, ignoring the error if the disk was * already not spinning. */ sony_audio_status = CDROM_AUDIO_NO_STATUS; do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size); if ( ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20)) && (res_reg[1] != SONY_NOT_SPIN_ERR)) { printk("Sony CDROM error 0x%2.2x (CDROMSTOP)\n", res_reg[1]); return -EIO; } return 0; break; case CDROMPAUSE: /* Pause the drive */ do_sony_cd_cmd(SONY_AUDIO_STOP_CMD, NULL, 0, res_reg, &res_size); if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20)) { printk("Sony CDROM error 0x%2.2x (CDROMPAUSE)\n", res_reg[1]); return -EIO; } /* Get the current position and save it for resuming */ if (read_subcode() < 0) { return -EIO; } cur_pos_msf[0] = last_sony_subcode->abs_msf[0]; cur_pos_msf[1] = last_sony_subcode->abs_msf[1]; cur_pos_msf[2] = last_sony_subcode->abs_msf[2]; sony_audio_status = CDROM_AUDIO_PAUSED; return 0; break; case CDROMRESUME: /* Start the drive after being paused */ if (sony_audio_status != CDROM_AUDIO_PAUSED) { return -EINVAL; } do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size); /* Start the drive at the saved position. */ params[1] = cur_pos_msf[0]; params[2] = cur_pos_msf[1]; params[3] = cur_pos_msf[2]; params[4] = final_pos_msf[0]; params[5] = final_pos_msf[1]; params[6] = final_pos_msf[2]; params[0] = 0x03; do_sony_cd_cmd(SONY_AUDIO_PLAYBACK_CMD, params, 7, res_reg, &res_size); if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20)) { printk("Sony CDROM error 0x%2.2x (CDROMRESUME)\n", res_reg[1]); return -EIO; } sony_audio_status = CDROM_AUDIO_PLAY; return 0; break; case CDROMPLAYMSF: /* Play starting at the given MSF address. */ verify_area(VERIFY_READ, (char *) arg, 6); do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size); memcpy_fromfs(&(params[1]), (void *) arg, 6); /* The parameters are given in int, must be converted */ for (i=1; i<7; i++) { params[i] = int_to_bcd(params[i]); } params[0] = 0x03; do_sony_cd_cmd(SONY_AUDIO_PLAYBACK_CMD, params, 7, res_reg, &res_size); if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20)) { printk("Sony CDROM error 0x%2.2x (CDROMPLAYMSF)\n", res_reg[1]); return -EIO; } /* Save the final position for pauses and resumes */ final_pos_msf[0] = params[4]; final_pos_msf[1] = params[5]; final_pos_msf[2] = params[6]; sony_audio_status = CDROM_AUDIO_PLAY; return 0; break; case CDROMREADTOCHDR: /* Read the table of contents header */ { struct cdrom_tochdr *hdr; struct cdrom_tochdr loc_hdr; sony_get_toc(); if (!sony_toc_read) { return -EIO; } hdr = (struct cdrom_tochdr *) arg; verify_area(VERIFY_WRITE, hdr, sizeof(*hdr)); loc_hdr.cdth_trk0 = bcd_to_int(sony_toc->first_track_num); loc_hdr.cdth_trk1 = bcd_to_int(sony_toc->last_track_num); memcpy_tofs(hdr, &loc_hdr, sizeof(*hdr)); } return 0; break; case CDROMREADTOCENTRY: /* Read a given table of contents entry */ { struct cdrom_tocentry *entry; struct cdrom_tocentry loc_entry; int track_idx; unsigned char *msf_val = NULL; sony_get_toc(); if (!sony_toc_read) { return -EIO; } entry = (struct cdrom_tocentry *) arg; verify_area(VERIFY_READ, entry, sizeof(*entry)); verify_area(VERIFY_WRITE, entry, sizeof(*entry)); memcpy_fromfs(&loc_entry, entry, sizeof(loc_entry)); /* Lead out is handled separately since it is special. */ if (loc_entry.cdte_track == CDROM_LEADOUT) { loc_entry.cdte_adr = sony_toc->address2; loc_entry.cdte_ctrl = sony_toc->control2; msf_val = sony_toc->lead_out_start_msf; } else { track_idx = find_track(int_to_bcd(loc_entry.cdte_track)); if (track_idx < 0) { return -EINVAL; } loc_entry.cdte_adr = sony_toc->tracks[track_idx].address; loc_entry.cdte_ctrl = sony_toc->tracks[track_idx].control; msf_val = sony_toc->tracks[track_idx].track_start_msf; } /* Logical buffer address or MSF format requested? */ if (loc_entry.cdte_format == CDROM_LBA) { loc_entry.cdte_addr.lba = msf_to_log(msf_val); } else if (loc_entry.cdte_format == CDROM_MSF) { loc_entry.cdte_addr.msf.minute = bcd_to_int(*msf_val); loc_entry.cdte_addr.msf.second = bcd_to_int(*(msf_val+1)); loc_entry.cdte_addr.msf.frame = bcd_to_int(*(msf_val+2)); } memcpy_tofs(entry, &loc_entry, sizeof(*entry)); } return 0; break; case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */ { struct cdrom_ti ti; int track_idx; sony_get_toc(); if (!sony_toc_read) { return -EIO; } verify_area(VERIFY_READ, (char *) arg, sizeof(ti)); memcpy_fromfs(&ti, (char *) arg, sizeof(ti)); if ( (ti.cdti_trk0 < sony_toc->first_track_num) || (ti.cdti_trk0 > sony_toc->last_track_num) || (ti.cdti_trk1 < ti.cdti_trk0)) { return -EINVAL; } track_idx = find_track(int_to_bcd(ti.cdti_trk0)); if (track_idx < 0) { return -EINVAL; } params[1] = sony_toc->tracks[track_idx].track_start_msf[0]; params[2] = sony_toc->tracks[track_idx].track_start_msf[1]; params[3] = sony_toc->tracks[track_idx].track_start_msf[2]; /* * If we want to stop after the last track, use the lead-out * MSF to do that. */ if (ti.cdti_trk1 >= bcd_to_int(sony_toc->last_track_num)) { log_to_msf(msf_to_log(sony_toc->lead_out_start_msf)-1, &(params[4])); } else { track_idx = find_track(int_to_bcd(ti.cdti_trk1+1)); if (track_idx < 0) { return -EINVAL; } log_to_msf(msf_to_log(sony_toc->tracks[track_idx].track_start_msf)-1, &(params[4])); } params[0] = 0x03; do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size); do_sony_cd_cmd(SONY_AUDIO_PLAYBACK_CMD, params, 7, res_reg, &res_size); if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20)) { printk("Params: %x %x %x %x %x %x %x\n", params[0], params[1], params[2], params[3], params[4], params[5], params[6]); printk("Sony CDROM error 0x%2.2x (CDROMPLAYTRKIND\n", res_reg[1]); return -EIO; } /* Save the final position for pauses and resumes */ final_pos_msf[0] = params[4]; final_pos_msf[1] = params[5]; final_pos_msf[2] = params[6]; sony_audio_status = CDROM_AUDIO_PLAY; return 0; } case CDROMSUBCHNL: /* Get subchannel info */ return sony_get_subchnl_info(arg); case CDROMVOLCTRL: /* Volume control. What volume does this change, anyway? */ { struct cdrom_volctrl volctrl; verify_area(VERIFY_READ, (char *) arg, sizeof(volctrl)); memcpy_fromfs(&volctrl, (char *) arg, sizeof(volctrl)); params[0] = SONY_SD_AUDIO_VOLUME; params[1] = volctrl.channel0; params[2] = volctrl.channel1; do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD, params, 3, res_reg, &res_size); if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20)) { printk("Sony CDROM error 0x%2.2x (CDROMVOLCTRL)\n", res_reg[1]); return -EIO; } } return 0; case CDROMEJECT: /* Eject the drive */ do_sony_cd_cmd(SONY_AUDIO_STOP_CMD, NULL, 0, res_reg, &res_size); do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size); sony_audio_status = CDROM_AUDIO_INVALID; do_sony_cd_cmd(SONY_EJECT_CMD, NULL, 0, res_reg, &res_size); if ((res_size < 2) || ((res_reg[0] & 0x20) == 0x20)) { printk("Sony CDROM error 0x%2.2x (CDROMEJECT)\n", res_reg[1]); return -EIO; } return 0; break; default: return -EINVAL; } } /* * Open the drive for operations. Spin the drive up and read the table of * contents if these have not already been done. */ static int scd_open(struct inode *inode, struct file *filp) { unsigned char res_reg[2]; unsigned int res_size; int num_spin_ups; if (!sony_spun_up) { num_spin_ups = 0; respinup_on_open: do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size); /* The drive sometimes returns error 0. I don't know why, but ignore it. It seems to mean the drive has already done the operation. */ if ((res_size < 2) || ((res_reg[0] != 0) && (res_reg[1] != 0))) { printk("Sony CDROM error 0x%2.2x (scd_open, spin up)\n", res_reg[1]); return -EIO; } do_sony_cd_cmd(SONY_READ_TOC_CMD, NULL, 0, res_reg, &res_size); /* The drive sometimes returns error 0. I don't know why, but ignore it. It seems to mean the drive has already done the operation. */ if ((res_size < 2) || ((res_reg[0] != 0) && (res_reg[1] != 0))) { /* If the drive is already playing, its ok. */ if ((res_reg[1] == SONY_AUDIO_PLAYING_ERR) || (res_reg[1] == 0)) { goto drive_spinning; } /* If the drive says it is not spun up (even though we just did it!) then retry the operation at least a few times. */ if ( (res_reg[1] == SONY_NOT_SPIN_ERR) && (num_spin_ups < MAX_CDU31A_RETRIES)) { num_spin_ups++; goto respinup_on_open; } printk("Sony CDROM error 0x%2.2x (scd_open, read toc)\n", res_reg[1]); do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size); return -EIO; } sony_get_toc(); if (!sony_toc_read) { do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size); return -EIO; } sony_spun_up = 1; } drive_spinning: if (inode) { check_disk_change(inode->i_rdev); } sony_usage++; return 0; } /* * Close the drive. Spin it down if no task is using it. The spin * down will fail if playing audio, so audio play is OK. */ static void scd_release(struct inode *inode, struct file *filp) { unsigned char res_reg[2]; unsigned int res_size; if (sony_usage > 0) { sony_usage--; } if (sony_usage == 0) { sync_dev(inode->i_rdev); do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size); sony_spun_up = 0; } } static struct file_operations scd_fops = { NULL, /* lseek - default */ block_read, /* read - general block-dev read */ block_write, /* write - general block-dev write */ NULL, /* readdir - bad */ NULL, /* select */ scd_ioctl, /* ioctl */ NULL, /* mmap */ scd_open, /* open */ scd_release, /* release */ NULL /* fsync */ }; /* The different types of disc loading mechanisms supported */ static char *load_mech[] = { "caddy", "tray", "pop-up", "unknown" }; /* Read-ahead buffer sizes for different drives. These are just arbitrary values, I don't know what is really optimum. */ static unsigned int mem_size[] = { 16384, 16384, 16384, 2048 }; void get_drive_configuration(unsigned short base_io, unsigned char res_reg[], unsigned int *res_size) { int retry_count; /* Set the base address */ sony_cd_base_io = base_io; /* Set up all the register locations */ sony_cd_cmd_reg = sony_cd_base_io + SONY_CMD_REG_OFFSET; sony_cd_param_reg = sony_cd_base_io + SONY_PARAM_REG_OFFSET; sony_cd_write_reg = sony_cd_base_io + SONY_WRITE_REG_OFFSET; sony_cd_control_reg = sony_cd_base_io + SONY_CONTROL_REG_OFFSET; sony_cd_status_reg = sony_cd_base_io + SONY_STATUS_REG_OFFSET; sony_cd_result_reg = sony_cd_base_io + SONY_RESULT_REG_OFFSET; sony_cd_read_reg = sony_cd_base_io + SONY_READ_REG_OFFSET; sony_cd_fifost_reg = sony_cd_base_io + SONY_FIFOST_REG_OFFSET; /* * Check to see if anything exists at the status register location. * I don't know if this is a good way to check, but it seems to work * ok for me. */ if (read_status_register() != 0xff) { /* * Reset the drive and wait for attention from it (to say its reset). * If you don't wait, the next operation will probably fail. */ reset_drive(); retry_count = jiffies + SONY_RESET_TIMEOUT; while ((retry_count > jiffies) && (!is_attention())) { sony_sleep(); } /* If attention is never seen probably not a CDU31a present */ if (!is_attention()) { res_reg[0] = 0x20; return; } /* * Get the drive configuration. */ do_sony_cd_cmd(SONY_REQ_DRIVE_CONFIG_CMD, NULL, 0, (unsigned char *) res_reg, res_size); return; } /* Return an error */ res_reg[0] = 0x20; } /* * Initialize the driver. */ unsigned long cdu31a_init(unsigned long mem_start, unsigned long mem_end) { struct s_sony_drive_config drive_config; unsigned int res_size; int i; int drive_found; /* * According to Alex Freed (freed@europa.orion.adobe.com), this is * required for the Fusion CD-16 package. If the sound driver is * loaded, it should work fine, but just in case... * * The following turn on the CD-ROM interface for a Fusion CD-16. */ outb(0xbc, 0x9a01); outb(0xe2, 0x9a01); i = 0; drive_found = 0; while ( (cdu31a_addresses[i] != 0) && (!drive_found)) { if (check_region(cdu31a_addresses[i], 4)) { i++; continue; } get_drive_configuration(cdu31a_addresses[i], drive_config.exec_status, &res_size); if ((res_size > 2) && ((drive_config.exec_status[0] & 0x20) == 0x00)) { drive_found = 1; snarf_region(cdu31a_addresses[i], 4); if (register_blkdev(MAJOR_NR,"cdu31a",&scd_fops)) { printk("Unable to get major %d for CDU-31a\n", MAJOR_NR); return mem_start; } sony_buffer_size = mem_size[SONY_HWC_GET_BUF_MEM_SIZE(drive_config)]; sony_buffer_sectors = sony_buffer_size / 2048; printk("Sony I/F CDROM : %8.8s %16.16s %8.8s with %s load mechanism\n", drive_config.vendor_id, drive_config.product_id, drive_config.product_rev_level, load_mech[SONY_HWC_GET_LOAD_MECH(drive_config)]); printk(" using %d byte buffer", sony_buffer_size); if (SONY_HWC_AUDIO_PLAYBACK(drive_config)) { printk(", capable of audio playback"); } printk("\n"); set_drive_params(); blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; read_ahead[MAJOR_NR] = 8; /* 8 sector (4kB) read-ahead */ sony_toc = (struct s_sony_toc *) mem_start; mem_start += sizeof(*sony_toc); last_sony_subcode = (struct s_sony_subcode *) mem_start; mem_start += sizeof(*last_sony_subcode); sony_buffer = (unsigned char *) mem_start; mem_start += sony_buffer_size; } i++; } return mem_start; }