📄 cdu31a.c
字号:
/*
* Sony CDU-31A CDROM interface device driver.
*
* Corey Minyard (minyard@wf-rch.cirr.com)
*
* Colossians 3:17
*
* The Sony interface device driver handles Sony interface CDROM
* drives and provides a complete block-level interface as well as an
* ioctl() interface compatible with the Sun (as specified in
* include/linux/cdrom.h). With this interface, CDROMs can be
* accessed and standard audio CDs can be played back normally.
*
* This interface is (unfortunatly) a polled interface. This is
* because most Sony interfaces are set up with DMA and interrupts
* disables. Some (like mine) do not even have the capability to
* handle interrupts or DMA. For this reason you will see a lot of
* the following:
*
* retry_count = jiffies+ SONY_JIFFIES_TIMEOUT;
* while ((retry_count > jiffies) && (! <some condition to wait for))
* {
* while (handle_sony_cd_attention())
* ;
*
* sony_sleep();
* }
* if (the condition not met)
* {
* return an error;
* }
*
* This ugly hack waits for something to happen, sleeping a little
* between every try. it also handles attentions, which are
* asyncronous events from the drive informing the driver that a disk
* has been inserted, removed, etc.
*
* One thing about these drives: They talk in MSF (Minute Second Frame) format.
* There are 75 frames a second, 60 seconds a minute, and up to 75 minutes on a
* disk. The funny thing is that these are sent to the drive in BCD, but the
* interface wants to see them in decimal. A lot of conversion goes on.
*
* Copyright (C) 1993 Corey Minyard
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <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/hdreg.h>
#include <linux/genhd.h>
#include <linux/ioport.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>
#include <linux/cdrom.h>
#include <linux/cdu31a.h>
#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
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -