mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-11-25 04:50:57 +07:00
7d12e780e0
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
1690 lines
48 KiB
C
1690 lines
48 KiB
C
/*
|
|
* Sony CDU-535 interface device driver
|
|
*
|
|
* This is a modified version of the CDU-31A device driver (see below).
|
|
* Changes were made using documentation for the CDU-531 (which Sony
|
|
* assures me is very similar to the 535) and partial disassembly of the
|
|
* DOS driver. I used Minyard's driver and replaced the CDU-31A
|
|
* commands with the CDU-531 commands. This was complicated by a different
|
|
* interface protocol with the drive. The driver is still polled.
|
|
*
|
|
* Data transfer rate is about 110 Kb/sec, theoretical maximum is 150 Kb/sec.
|
|
* I tried polling without the sony_sleep during the data transfers but
|
|
* it did not speed things up any.
|
|
*
|
|
* 1993-05-23 (rgj) changed the major number to 21 to get rid of conflict
|
|
* with CDU-31A driver. This is the also the number from the Linux
|
|
* Device Driver Registry for the Sony Drive. Hope nobody else is using it.
|
|
*
|
|
* 1993-08-29 (rgj) remove the configuring of the interface board address
|
|
* from the top level configuration, you have to modify it in this file.
|
|
*
|
|
* 1995-01-26 Made module-capable (Joel Katz <Stimpson@Panix.COM>)
|
|
*
|
|
* 1995-05-20
|
|
* Modified to support CDU-510/515 series
|
|
* (Claudio Porfiri<C.Porfiri@nisms.tei.ericsson.se>)
|
|
* Fixed to report verify_area() failures
|
|
* (Heiko Eissfeldt <heiko@colossus.escape.de>)
|
|
*
|
|
* 1995-06-01
|
|
* More changes to support CDU-510/515 series
|
|
* (Claudio Porfiri<C.Porfiri@nisms.tei.ericsson.se>)
|
|
*
|
|
* November 1999 -- Make kernel-parameter implementation work with 2.3.x
|
|
* Removed init_module & cleanup_module in favor of
|
|
* module_init & module_exit.
|
|
* Torben Mathiasen <tmm@image.dk>
|
|
*
|
|
* September 2003 - Fix SMP support by removing cli/sti calls.
|
|
* Using spinlocks with a wait_queue instead.
|
|
* Felipe Damasio <felipewd@terra.com.br>
|
|
*
|
|
* Things to do:
|
|
* - handle errors and status better, put everything into a single word
|
|
* - use interrupts (code mostly there, but a big hole still missing)
|
|
* - handle multi-session CDs?
|
|
* - use DMA?
|
|
*
|
|
* Known Bugs:
|
|
* -
|
|
*
|
|
* Ken Pizzini (ken@halcyon.com)
|
|
*
|
|
* Original by:
|
|
* Ron Jeppesen (ronj.an@site007.saic.com)
|
|
*
|
|
*
|
|
*------------------------------------------------------------------------
|
|
* Sony CDROM interface device driver.
|
|
*
|
|
* Corey Minyard (minyard@wf-rch.cirr.com) (CDU-535 complaints to Ken above)
|
|
*
|
|
* 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 (unfortunately) 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 bit of
|
|
* the following:
|
|
*
|
|
* snap = jiffies;
|
|
* while (jiffies-snap < SONY_JIFFIES_TIMEOUT)
|
|
* {
|
|
* if (some_condition())
|
|
* break;
|
|
* sony_sleep();
|
|
* }
|
|
* if (some_condition not met)
|
|
* {
|
|
* return an_error;
|
|
* }
|
|
*
|
|
* This ugly hack waits for something to happen, sleeping a little
|
|
* between every try. (The conditional is written so that jiffies
|
|
* wrap-around is handled properly.)
|
|
*
|
|
* 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/module.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/interrupt.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/hdreg.h>
|
|
#include <linux/genhd.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/init.h>
|
|
|
|
#define REALLY_SLOW_IO
|
|
#include <asm/system.h>
|
|
#include <asm/io.h>
|
|
#include <asm/uaccess.h>
|
|
|
|
#include <linux/cdrom.h>
|
|
|
|
#define MAJOR_NR CDU535_CDROM_MAJOR
|
|
#include <linux/blkdev.h>
|
|
|
|
#define sony535_cd_base_io sonycd535 /* for compatible parameter passing with "insmod" */
|
|
#include "sonycd535.h"
|
|
|
|
/*
|
|
* this is the base address of the interface card for the Sony CDU-535
|
|
* CDROM drive. If your jumpers are set for an address other than
|
|
* this one (the default), change the following line to the
|
|
* proper address.
|
|
*/
|
|
#ifndef CDU535_ADDRESS
|
|
# define CDU535_ADDRESS 0x340
|
|
#endif
|
|
#ifndef CDU535_INTERRUPT
|
|
# define CDU535_INTERRUPT 0
|
|
#endif
|
|
#ifndef CDU535_HANDLE
|
|
# define CDU535_HANDLE "cdu535"
|
|
#endif
|
|
#ifndef CDU535_MESSAGE_NAME
|
|
# define CDU535_MESSAGE_NAME "Sony CDU-535"
|
|
#endif
|
|
|
|
#define CDU535_BLOCK_SIZE 2048
|
|
|
|
#ifndef MAX_SPINUP_RETRY
|
|
# define MAX_SPINUP_RETRY 3 /* 1 is sufficient for most drives... */
|
|
#endif
|
|
#ifndef RETRY_FOR_BAD_STATUS
|
|
# define RETRY_FOR_BAD_STATUS 100 /* in 10th of second */
|
|
#endif
|
|
|
|
#ifndef DEBUG
|
|
# define DEBUG 1
|
|
#endif
|
|
|
|
/*
|
|
* SONY535_BUFFER_SIZE determines the size of internal buffer used
|
|
* by the drive. It must be at least 2K and the larger the buffer
|
|
* the better the transfer rate. It does however take system memory.
|
|
* On my system I get the following transfer rates using dd to read
|
|
* 10 Mb off /dev/cdrom.
|
|
*
|
|
* 8K buffer 43 Kb/sec
|
|
* 16K buffer 66 Kb/sec
|
|
* 32K buffer 91 Kb/sec
|
|
* 64K buffer 111 Kb/sec
|
|
* 128K buffer 123 Kb/sec
|
|
* 512K buffer 123 Kb/sec
|
|
*/
|
|
#define SONY535_BUFFER_SIZE (64*1024)
|
|
|
|
/*
|
|
* if LOCK_DOORS is defined then the eject button is disabled while
|
|
* the device is open.
|
|
*/
|
|
#ifndef NO_LOCK_DOORS
|
|
# define LOCK_DOORS
|
|
#endif
|
|
|
|
static int read_subcode(void);
|
|
static void sony_get_toc(void);
|
|
static int cdu_open(struct inode *inode, struct file *filp);
|
|
static inline unsigned int int_to_bcd(unsigned int val);
|
|
static unsigned int bcd_to_int(unsigned int bcd);
|
|
static int do_sony_cmd(Byte * cmd, int nCmd, Byte status[2],
|
|
Byte * response, int n_response, int ignoreStatusBit7);
|
|
|
|
/* 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 int sony535_cd_base_io = CDU535_ADDRESS;
|
|
module_param(sony535_cd_base_io, int, 0);
|
|
|
|
/*
|
|
* The following are I/O addresses of the various registers for the drive. The
|
|
* comment for the base address also applies here.
|
|
*/
|
|
static unsigned short select_unit_reg;
|
|
static unsigned short result_reg;
|
|
static unsigned short command_reg;
|
|
static unsigned short read_status_reg;
|
|
static unsigned short data_reg;
|
|
|
|
static DEFINE_SPINLOCK(sonycd535_lock); /* queue lock */
|
|
static struct request_queue *sonycd535_queue;
|
|
|
|
static int initialized; /* Has the drive been initialized? */
|
|
static int sony_disc_changed = 1; /* Has the disk been changed
|
|
since the last check? */
|
|
static int sony_toc_read; /* Has the table of contents been
|
|
read? */
|
|
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; /* How many processes have the
|
|
drive open. */
|
|
|
|
static int sony_first_block = -1; /* First OS block (512 byte) in
|
|
the read-ahead buffer */
|
|
static int sony_last_block = -1; /* Last OS block (512 byte) in
|
|
the read-ahead buffer */
|
|
|
|
static struct s535_sony_toc *sony_toc; /* Points to the table of
|
|
contents. */
|
|
|
|
static struct s535_sony_subcode *last_sony_subcode; /* Points to the last
|
|
subcode address read */
|
|
static Byte **sony_buffer; /* Points to the pointers
|
|
to the sector buffers */
|
|
|
|
static int sony_inuse; /* is the drive in use? Only one
|
|
open at a time allowed */
|
|
|
|
/*
|
|
* The audio status uses the values from read subchannel data as specified
|
|
* in include/linux/cdrom.h.
|
|
*/
|
|
static 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.
|
|
* I just kept the CDU-31A driver behavior rather than using the PAUSE
|
|
* command on the CDU-535.
|
|
*/
|
|
static Byte cur_pos_msf[3];
|
|
static Byte final_pos_msf[3];
|
|
|
|
/* What IRQ is the drive using? 0 if none. */
|
|
static int sony535_irq_used = CDU535_INTERRUPT;
|
|
|
|
/* The interrupt handler will wake this queue up when it gets an interrupt. */
|
|
static DECLARE_WAIT_QUEUE_HEAD(cdu535_irq_wait);
|
|
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
static int
|
|
cdu535_check_media_change(struct gendisk *disk)
|
|
{
|
|
/* if driver is not initialized, always return 0 */
|
|
int retval = initialized ? sony_disc_changed : 0;
|
|
sony_disc_changed = 0;
|
|
return retval;
|
|
}
|
|
|
|
static inline void
|
|
enable_interrupts(void)
|
|
{
|
|
#ifdef USE_IRQ
|
|
/*
|
|
* This code was taken from cdu31a.c; it will not
|
|
* directly work for the cdu535 as written...
|
|
*/
|
|
curr_control_reg |= ( SONY_ATTN_INT_EN_BIT
|
|
| SONY_RES_RDY_INT_EN_BIT
|
|
| SONY_DATA_RDY_INT_EN_BIT);
|
|
outb(curr_control_reg, sony_cd_control_reg);
|
|
#endif
|
|
}
|
|
|
|
static inline void
|
|
disable_interrupts(void)
|
|
{
|
|
#ifdef USE_IRQ
|
|
/*
|
|
* This code was taken from cdu31a.c; it will not
|
|
* directly work for the cdu535 as written...
|
|
*/
|
|
curr_control_reg &= ~(SONY_ATTN_INT_EN_BIT
|
|
| SONY_RES_RDY_INT_EN_BIT
|
|
| SONY_DATA_RDY_INT_EN_BIT);
|
|
outb(curr_control_reg, sony_cd_control_reg);
|
|
#endif
|
|
}
|
|
|
|
static irqreturn_t
|
|
cdu535_interrupt(int irq, void *dev_id)
|
|
{
|
|
disable_interrupts();
|
|
if (waitqueue_active(&cdu535_irq_wait)) {
|
|
wake_up(&cdu535_irq_wait);
|
|
return IRQ_HANDLED;
|
|
}
|
|
printk(CDU535_MESSAGE_NAME
|
|
": Got an interrupt but nothing was waiting\n");
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
|
|
/*
|
|
* Wait a little while.
|
|
*/
|
|
static inline void
|
|
sony_sleep(void)
|
|
{
|
|
if (sony535_irq_used <= 0) { /* poll */
|
|
yield();
|
|
} else { /* Interrupt driven */
|
|
DEFINE_WAIT(wait);
|
|
|
|
spin_lock_irq(&sonycd535_lock);
|
|
enable_interrupts();
|
|
prepare_to_wait(&cdu535_irq_wait, &wait, TASK_INTERRUPTIBLE);
|
|
spin_unlock_irq(&sonycd535_lock);
|
|
schedule();
|
|
finish_wait(&cdu535_irq_wait, &wait);
|
|
}
|
|
}
|
|
|
|
/*------------------start of SONY CDU535 very specific ---------------------*/
|
|
|
|
/****************************************************************************
|
|
* void select_unit( int unit_no )
|
|
*
|
|
* Select the specified unit (0-3) so that subsequent commands reference it
|
|
****************************************************************************/
|
|
static void
|
|
select_unit(int unit_no)
|
|
{
|
|
unsigned int select_mask = ~(1 << unit_no);
|
|
outb(select_mask, select_unit_reg);
|
|
}
|
|
|
|
/***************************************************************************
|
|
* int read_result_reg( Byte *data_ptr )
|
|
*
|
|
* Read a result byte from the Sony CDU controller, store in location pointed
|
|
* to by data_ptr. Return zero on success, TIME_OUT if we did not receive
|
|
* data.
|
|
***************************************************************************/
|
|
static int
|
|
read_result_reg(Byte *data_ptr)
|
|
{
|
|
unsigned long snap;
|
|
int read_status;
|
|
|
|
snap = jiffies;
|
|
while (jiffies-snap < SONY_JIFFIES_TIMEOUT) {
|
|
read_status = inb(read_status_reg);
|
|
if ((read_status & SONY535_RESULT_NOT_READY_BIT) == 0) {
|
|
#if DEBUG > 1
|
|
printk(CDU535_MESSAGE_NAME
|
|
": read_result_reg(): readStatReg = 0x%x\n", read_status);
|
|
#endif
|
|
*data_ptr = inb(result_reg);
|
|
return 0;
|
|
} else {
|
|
sony_sleep();
|
|
}
|
|
}
|
|
printk(CDU535_MESSAGE_NAME " read_result_reg: TIME OUT!\n");
|
|
return TIME_OUT;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* int read_exec_status( Byte status[2] )
|
|
*
|
|
* Read the execution status of the last command and put into status.
|
|
* Handles reading second status word if available. Returns 0 on success,
|
|
* TIME_OUT on failure.
|
|
****************************************************************************/
|
|
static int
|
|
read_exec_status(Byte status[2])
|
|
{
|
|
status[1] = 0;
|
|
if (read_result_reg(&(status[0])) != 0)
|
|
return TIME_OUT;
|
|
if ((status[0] & 0x80) != 0) { /* byte two follows */
|
|
if (read_result_reg(&(status[1])) != 0)
|
|
return TIME_OUT;
|
|
}
|
|
#if DEBUG > 1
|
|
printk(CDU535_MESSAGE_NAME ": read_exec_status: read 0x%x 0x%x\n",
|
|
status[0], status[1]);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* int check_drive_status( void )
|
|
*
|
|
* Check the current drive status. Using this before executing a command
|
|
* takes care of the problem of unsolicited drive status-2 messages.
|
|
* Add a check of the audio status if we think the disk is playing.
|
|
****************************************************************************/
|
|
static int
|
|
check_drive_status(void)
|
|
{
|
|
Byte status, e_status[2];
|
|
int CDD, ATN;
|
|
Byte cmd;
|
|
|
|
select_unit(0);
|
|
if (sony_audio_status == CDROM_AUDIO_PLAY) { /* check status */
|
|
outb(SONY535_REQUEST_AUDIO_STATUS, command_reg);
|
|
if (read_result_reg(&status) == 0) {
|
|
switch (status) {
|
|
case 0x0:
|
|
break; /* play in progress */
|
|
case 0x1:
|
|
break; /* paused */
|
|
case 0x3: /* audio play completed */
|
|
case 0x5: /* play not requested */
|
|
sony_audio_status = CDROM_AUDIO_COMPLETED;
|
|
read_subcode();
|
|
break;
|
|
case 0x4: /* error during play */
|
|
sony_audio_status = CDROM_AUDIO_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* now check drive status */
|
|
outb(SONY535_REQUEST_DRIVE_STATUS_2, command_reg);
|
|
if (read_result_reg(&status) != 0)
|
|
return TIME_OUT;
|
|
|
|
#if DEBUG > 1
|
|
printk(CDU535_MESSAGE_NAME ": check_drive_status() got 0x%x\n", status);
|
|
#endif
|
|
|
|
if (status == 0)
|
|
return 0;
|
|
|
|
ATN = status & 0xf;
|
|
CDD = (status >> 4) & 0xf;
|
|
|
|
switch (ATN) {
|
|
case 0x0:
|
|
break; /* go on to CDD stuff */
|
|
case SONY535_ATN_BUSY:
|
|
if (initialized)
|
|
printk(CDU535_MESSAGE_NAME " error: drive busy\n");
|
|
return CD_BUSY;
|
|
case SONY535_ATN_EJECT_IN_PROGRESS:
|
|
printk(CDU535_MESSAGE_NAME " error: eject in progress\n");
|
|
sony_audio_status = CDROM_AUDIO_INVALID;
|
|
return CD_BUSY;
|
|
case SONY535_ATN_RESET_OCCURRED:
|
|
case SONY535_ATN_DISC_CHANGED:
|
|
case SONY535_ATN_RESET_AND_DISC_CHANGED:
|
|
#if DEBUG > 0
|
|
printk(CDU535_MESSAGE_NAME " notice: reset occurred or disc changed\n");
|
|
#endif
|
|
sony_disc_changed = 1;
|
|
sony_toc_read = 0;
|
|
sony_audio_status = CDROM_AUDIO_NO_STATUS;
|
|
sony_first_block = -1;
|
|
sony_last_block = -1;
|
|
if (initialized) {
|
|
cmd = SONY535_SPIN_UP;
|
|
do_sony_cmd(&cmd, 1, e_status, NULL, 0, 0);
|
|
sony_get_toc();
|
|
}
|
|
return 0;
|
|
default:
|
|
printk(CDU535_MESSAGE_NAME " error: drive busy (ATN=0x%x)\n", ATN);
|
|
return CD_BUSY;
|
|
}
|
|
switch (CDD) { /* the 531 docs are not helpful in decoding this */
|
|
case 0x0: /* just use the values from the DOS driver */
|
|
case 0x2:
|
|
case 0xa:
|
|
break; /* no error */
|
|
case 0xc:
|
|
printk(CDU535_MESSAGE_NAME
|
|
": check_drive_status(): CDD = 0xc! Not properly handled!\n");
|
|
return CD_BUSY; /* ? */
|
|
default:
|
|
return CD_BUSY;
|
|
}
|
|
return 0;
|
|
} /* check_drive_status() */
|
|
|
|
/*****************************************************************************
|
|
* int do_sony_cmd( Byte *cmd, int n_cmd, Byte status[2],
|
|
* Byte *response, int n_response, int ignore_status_bit7 )
|
|
*
|
|
* Generic routine for executing commands. The command and its parameters
|
|
* should be placed in the cmd[] array, number of bytes in the command is
|
|
* stored in nCmd. The response from the command will be stored in the
|
|
* response array. The number of bytes you expect back (excluding status)
|
|
* should be passed in n_response. Finally, some
|
|
* commands set bit 7 of the return status even when there is no second
|
|
* status byte, on these commands set ignoreStatusBit7 TRUE.
|
|
* If the command was sent and data received back, then we return 0,
|
|
* else we return TIME_OUT. You still have to check the status yourself.
|
|
* You should call check_drive_status() before calling this routine
|
|
* so that you do not lose notifications of disk changes, etc.
|
|
****************************************************************************/
|
|
static int
|
|
do_sony_cmd(Byte * cmd, int n_cmd, Byte status[2],
|
|
Byte * response, int n_response, int ignore_status_bit7)
|
|
{
|
|
int i;
|
|
|
|
/* write out the command */
|
|
for (i = 0; i < n_cmd; i++)
|
|
outb(cmd[i], command_reg);
|
|
|
|
/* read back the status */
|
|
if (read_result_reg(status) != 0)
|
|
return TIME_OUT;
|
|
if (!ignore_status_bit7 && ((status[0] & 0x80) != 0)) {
|
|
/* get second status byte */
|
|
if (read_result_reg(status + 1) != 0)
|
|
return TIME_OUT;
|
|
} else {
|
|
status[1] = 0;
|
|
}
|
|
#if DEBUG > 2
|
|
printk(CDU535_MESSAGE_NAME ": do_sony_cmd %x: %x %x\n",
|
|
*cmd, status[0], status[1]);
|
|
#endif
|
|
|
|
/* do not know about when I should read set of data and when not to */
|
|
if ((status[0] & ((ignore_status_bit7 ? 0x7f : 0xff) & 0x8f)) != 0)
|
|
return 0;
|
|
|
|
/* else, read in rest of data */
|
|
for (i = 0; 0 < n_response; n_response--, i++)
|
|
if (read_result_reg(response + i) != 0)
|
|
return TIME_OUT;
|
|
return 0;
|
|
} /* do_sony_cmd() */
|
|
|
|
/**************************************************************************
|
|
* int set_drive_mode( int mode, Byte status[2] )
|
|
*
|
|
* Set the drive mode to the specified value (mode=0 is audio, mode=e0
|
|
* is mode-1 CDROM
|
|
**************************************************************************/
|
|
static int
|
|
set_drive_mode(int mode, Byte status[2])
|
|
{
|
|
Byte cmd_buff[2];
|
|
Byte ret_buff[1];
|
|
|
|
cmd_buff[0] = SONY535_SET_DRIVE_MODE;
|
|
cmd_buff[1] = mode;
|
|
return do_sony_cmd(cmd_buff, 2, status, ret_buff, 1, 1);
|
|
}
|
|
|
|
/***************************************************************************
|
|
* int seek_and_read_N_blocks( Byte params[], int n_blocks, Byte status[2],
|
|
* Byte *data_buff, int buff_size )
|
|
*
|
|
* Read n_blocks of data from the CDROM starting at position params[0:2],
|
|
* number of blocks in stored in params[3:5] -- both these are already
|
|
* int bcd format.
|
|
* Transfer the data into the buffer pointed at by data_buff. buff_size
|
|
* gives the number of bytes available in the buffer.
|
|
* The routine returns number of bytes read in if successful, otherwise
|
|
* it returns one of the standard error returns.
|
|
***************************************************************************/
|
|
static int
|
|
seek_and_read_N_blocks(Byte params[], int n_blocks, Byte status[2],
|
|
Byte **buff, int buf_size)
|
|
{
|
|
Byte cmd_buff[7];
|
|
int i;
|
|
int read_status;
|
|
unsigned long snap;
|
|
Byte *data_buff;
|
|
int sector_count = 0;
|
|
|
|
if (buf_size < CDU535_BLOCK_SIZE * n_blocks)
|
|
return NO_ROOM;
|
|
|
|
set_drive_mode(SONY535_CDROM_DRIVE_MODE, status);
|
|
|
|
/* send command to read the data */
|
|
cmd_buff[0] = SONY535_SEEK_AND_READ_N_BLOCKS_1;
|
|
for (i = 0; i < 6; i++)
|
|
cmd_buff[i + 1] = params[i];
|
|
for (i = 0; i < 7; i++)
|
|
outb(cmd_buff[i], command_reg);
|
|
|
|
/* read back the data one block at a time */
|
|
while (0 < n_blocks--) {
|
|
/* wait for data to be ready */
|
|
int data_valid = 0;
|
|
snap = jiffies;
|
|
while (jiffies-snap < SONY_JIFFIES_TIMEOUT) {
|
|
read_status = inb(read_status_reg);
|
|
if ((read_status & SONY535_RESULT_NOT_READY_BIT) == 0) {
|
|
read_exec_status(status);
|
|
return BAD_STATUS;
|
|
}
|
|
if ((read_status & SONY535_DATA_NOT_READY_BIT) == 0) {
|
|
/* data is ready, read it */
|
|
data_buff = buff[sector_count++];
|
|
for (i = 0; i < CDU535_BLOCK_SIZE; i++)
|
|
*data_buff++ = inb(data_reg); /* unrolling this loop does not seem to help */
|
|
data_valid = 1;
|
|
break; /* exit the timeout loop */
|
|
}
|
|
sony_sleep(); /* data not ready, sleep a while */
|
|
}
|
|
if (!data_valid)
|
|
return TIME_OUT; /* if we reach this stage */
|
|
}
|
|
|
|
/* read all the data, now read the status */
|
|
if ((i = read_exec_status(status)) != 0)
|
|
return i;
|
|
return CDU535_BLOCK_SIZE * sector_count;
|
|
} /* seek_and_read_N_blocks() */
|
|
|
|
/****************************************************************************
|
|
* int request_toc_data( Byte status[2], struct s535_sony_toc *toc )
|
|
*
|
|
* Read in the table of contents data. Converts all the bcd data
|
|
* into integers in the toc structure.
|
|
****************************************************************************/
|
|
static int
|
|
request_toc_data(Byte status[2], struct s535_sony_toc *toc)
|
|
{
|
|
int to_status;
|
|
int i, j, n_tracks, track_no;
|
|
int first_track_num, last_track_num;
|
|
Byte cmd_no = 0xb2;
|
|
Byte track_address_buffer[5];
|
|
|
|
/* read the fixed portion of the table of contents */
|
|
if ((to_status = do_sony_cmd(&cmd_no, 1, status, (Byte *) toc, 15, 1)) != 0)
|
|
return to_status;
|
|
|
|
/* convert the data into integers so we can use them */
|
|
first_track_num = bcd_to_int(toc->first_track_num);
|
|
last_track_num = bcd_to_int(toc->last_track_num);
|
|
n_tracks = last_track_num - first_track_num + 1;
|
|
|
|
/* read each of the track address descriptors */
|
|
for (i = 0; i < n_tracks; i++) {
|
|
/* read the descriptor into a temporary buffer */
|
|
for (j = 0; j < 5; j++) {
|
|
if (read_result_reg(track_address_buffer + j) != 0)
|
|
return TIME_OUT;
|
|
if (j == 1) /* need to convert from bcd */
|
|
track_no = bcd_to_int(track_address_buffer[j]);
|
|
}
|
|
/* copy the descriptor to proper location - sonycd.c just fills */
|
|
memcpy(toc->tracks + i, track_address_buffer, 5);
|
|
}
|
|
return 0;
|
|
} /* request_toc_data() */
|
|
|
|
/***************************************************************************
|
|
* int spin_up_drive( Byte status[2] )
|
|
*
|
|
* Spin up the drive (unless it is already spinning).
|
|
***************************************************************************/
|
|
static int
|
|
spin_up_drive(Byte status[2])
|
|
{
|
|
Byte cmd;
|
|
|
|
/* first see if the drive is already spinning */
|
|
cmd = SONY535_REQUEST_DRIVE_STATUS_1;
|
|
if (do_sony_cmd(&cmd, 1, status, NULL, 0, 0) != 0)
|
|
return TIME_OUT;
|
|
if ((status[0] & SONY535_STATUS1_NOT_SPINNING) == 0)
|
|
return 0; /* it's already spinning */
|
|
|
|
/* otherwise, give the spin-up command */
|
|
cmd = SONY535_SPIN_UP;
|
|
return do_sony_cmd(&cmd, 1, status, NULL, 0, 0);
|
|
}
|
|
|
|
/*--------------------end of SONY CDU535 very specific ---------------------*/
|
|
|
|
/* 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, Byte *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(Byte *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, Byte *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
|
|
* may use 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_cdu535_request(request_queue_t * q)
|
|
{
|
|
struct request *req;
|
|
unsigned int read_size;
|
|
int block;
|
|
int nsect;
|
|
int copyoff;
|
|
int spin_up_retry;
|
|
Byte params[10];
|
|
Byte status[2];
|
|
Byte cmd[2];
|
|
|
|
while (1) {
|
|
req = elv_next_request(q);
|
|
if (!req)
|
|
return;
|
|
|
|
block = req->sector;
|
|
nsect = req->nr_sectors;
|
|
if (!blk_fs_request(req)) {
|
|
end_request(req, 0);
|
|
continue;
|
|
}
|
|
if (rq_data_dir(req) == WRITE) {
|
|
end_request(req, 0);
|
|
continue;
|
|
}
|
|
/*
|
|
* If the block address is invalid or the request goes beyond
|
|
* the end of the media, return an error.
|
|
*/
|
|
if (sony_toc->lead_out_start_lba <= (block/4)) {
|
|
end_request(req, 0);
|
|
return;
|
|
}
|
|
if (sony_toc->lead_out_start_lba <= ((block + nsect) / 4)) {
|
|
end_request(req, 0);
|
|
return;
|
|
}
|
|
while (0 < nsect) {
|
|
/*
|
|
* If the requested sector is not currently in
|
|
* the read-ahead buffer, it must be read in.
|
|
*/
|
|
if ((block < sony_first_block) || (sony_last_block < 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 (sony_toc->lead_out_start_lba <= ((block / 4) + sony_buffer_sectors)) {
|
|
sony_last_block = (sony_toc->lead_out_start_lba * 4) - 1;
|
|
read_size = sony_toc->lead_out_start_lba - (block / 4);
|
|
} else {
|
|
sony_last_block = sony_first_block + (sony_buffer_sectors * 4) - 1;
|
|
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 some more.
|
|
*/
|
|
for (spin_up_retry=0 ;; ++spin_up_retry) {
|
|
/* This loop has been modified to support the Sony
|
|
* CDU-510/515 series, thanks to Claudio Porfiri
|
|
* <C.Porfiri@nisms.tei.ericsson.se>.
|
|
*/
|
|
/*
|
|
* This part is to deal with very slow hardware. We
|
|
* try at most MAX_SPINUP_RETRY times to read the same
|
|
* block. A check for seek_and_read_N_blocks' result is
|
|
* performed; if the result is wrong, the CDROM's engine
|
|
* is restarted and the operation is tried again.
|
|
*/
|
|
/*
|
|
* 1995-06-01: The system got problems when downloading
|
|
* from Slackware CDROM, the problem seems to be:
|
|
* seek_and_read_N_blocks returns BAD_STATUS and we
|
|
* should wait for a while before retrying, so a new
|
|
* part was added to discriminate the return value from
|
|
* seek_and_read_N_blocks for the various cases.
|
|
*/
|
|
int readStatus = seek_and_read_N_blocks(params, read_size,
|
|
status, sony_buffer, (read_size * CDU535_BLOCK_SIZE));
|
|
if (0 <= readStatus) /* Good data; common case, placed first */
|
|
break;
|
|
if (readStatus == NO_ROOM || spin_up_retry == MAX_SPINUP_RETRY) {
|
|
/* give up */
|
|
if (readStatus == NO_ROOM)
|
|
printk(CDU535_MESSAGE_NAME " No room to read from CD\n");
|
|
else
|
|
printk(CDU535_MESSAGE_NAME " Read error: 0x%.2x\n",
|
|
status[0]);
|
|
sony_first_block = -1;
|
|
sony_last_block = -1;
|
|
end_request(req, 0);
|
|
return;
|
|
}
|
|
if (readStatus == BAD_STATUS) {
|
|
/* Sleep for a while, then retry */
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
spin_unlock_irq(&sonycd535_lock);
|
|
schedule_timeout(RETRY_FOR_BAD_STATUS*HZ/10);
|
|
spin_lock_irq(&sonycd535_lock);
|
|
}
|
|
#if DEBUG > 0
|
|
printk(CDU535_MESSAGE_NAME
|
|
" debug: calling spin up when reading data!\n");
|
|
#endif
|
|
cmd[0] = SONY535_SPIN_UP;
|
|
do_sony_cmd(cmd, 1, status, NULL, 0, 0);
|
|
}
|
|
}
|
|
/*
|
|
* The data is in memory now, copy it to the buffer and advance to the
|
|
* next block to read.
|
|
*/
|
|
copyoff = block - sony_first_block;
|
|
memcpy(req->buffer,
|
|
sony_buffer[copyoff / 4] + 512 * (copyoff % 4), 512);
|
|
|
|
block += 1;
|
|
nsect -= 1;
|
|
req->buffer += 512;
|
|
}
|
|
|
|
end_request(req, 1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read the table of contents from the drive and set sony_toc_read if
|
|
* successful.
|
|
*/
|
|
static void
|
|
sony_get_toc(void)
|
|
{
|
|
Byte status[2];
|
|
if (!sony_toc_read) {
|
|
/* do not call check_drive_status() from here since it can call this routine */
|
|
if (request_toc_data(status, sony_toc) < 0)
|
|
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. track is
|
|
* passed in bcd format
|
|
*/
|
|
static int
|
|
find_track(int track)
|
|
{
|
|
int i;
|
|
int num_tracks;
|
|
|
|
|
|
num_tracks = bcd_to_int(sony_toc->last_track_num) -
|
|
bcd_to_int(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)
|
|
{
|
|
Byte cmd = SONY535_REQUEST_SUB_Q_DATA;
|
|
Byte status[2];
|
|
int dsc_status;
|
|
|
|
if (check_drive_status() != 0)
|
|
return -EIO;
|
|
|
|
if ((dsc_status = do_sony_cmd(&cmd, 1, status, (Byte *) last_sony_subcode,
|
|
sizeof(struct s535_sony_subcode), 1)) != 0) {
|
|
printk(CDU535_MESSAGE_NAME " error 0x%.2x, %d (read_subcode)\n",
|
|
status[0], dsc_status);
|
|
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(void __user *arg)
|
|
{
|
|
struct cdrom_subchnl schi;
|
|
|
|
/* Get attention stuff */
|
|
if (check_drive_status() != 0)
|
|
return -EIO;
|
|
|
|
sony_get_toc();
|
|
if (!sony_toc_read) {
|
|
return -EIO;
|
|
}
|
|
if (copy_from_user(&schi, arg, sizeof schi))
|
|
return -EFAULT;
|
|
|
|
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;
|
|
if (copy_to_user(arg, &schi, sizeof schi))
|
|
return -EFAULT;
|
|
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);
|
|
}
|
|
return copy_to_user(arg, &schi, sizeof schi) ? -EFAULT : 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* The big ugly ioctl handler.
|
|
*/
|
|
static int
|
|
cdu_ioctl(struct inode *inode,
|
|
struct file *file,
|
|
unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
Byte status[2];
|
|
Byte cmd_buff[10], params[10];
|
|
int i;
|
|
int dsc_status;
|
|
void __user *argp = (void __user *)arg;
|
|
|
|
if (check_drive_status() != 0)
|
|
return -EIO;
|
|
|
|
switch (cmd) {
|
|
case CDROMSTART: /* Spin up the drive */
|
|
if (spin_up_drive(status) < 0) {
|
|
printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMSTART)\n",
|
|
status[0]);
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
break;
|
|
|
|
case CDROMSTOP: /* Spin down the drive */
|
|
cmd_buff[0] = SONY535_HOLD;
|
|
do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0);
|
|
|
|
/*
|
|
* Spin the drive down, ignoring the error if the disk was
|
|
* already not spinning.
|
|
*/
|
|
sony_audio_status = CDROM_AUDIO_NO_STATUS;
|
|
cmd_buff[0] = SONY535_SPIN_DOWN;
|
|
dsc_status = do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0);
|
|
if (((dsc_status < 0) && (dsc_status != BAD_STATUS)) ||
|
|
((status[0] & ~(SONY535_STATUS1_NOT_SPINNING)) != 0)) {
|
|
printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMSTOP)\n",
|
|
status[0]);
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
break;
|
|
|
|
case CDROMPAUSE: /* Pause the drive */
|
|
cmd_buff[0] = SONY535_HOLD; /* CDU-31 driver uses AUDIO_STOP, not pause */
|
|
if (do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0) != 0) {
|
|
printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPAUSE)\n",
|
|
status[0]);
|
|
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 */
|
|
set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status);
|
|
|
|
if (sony_audio_status != CDROM_AUDIO_PAUSED) {
|
|
return -EINVAL;
|
|
}
|
|
spin_up_drive(status);
|
|
|
|
/* Start the drive at the saved position. */
|
|
cmd_buff[0] = SONY535_PLAY_AUDIO;
|
|
cmd_buff[1] = 0; /* play back starting at this address */
|
|
cmd_buff[2] = cur_pos_msf[0];
|
|
cmd_buff[3] = cur_pos_msf[1];
|
|
cmd_buff[4] = cur_pos_msf[2];
|
|
cmd_buff[5] = SONY535_PLAY_AUDIO;
|
|
cmd_buff[6] = 2; /* set ending address */
|
|
cmd_buff[7] = final_pos_msf[0];
|
|
cmd_buff[8] = final_pos_msf[1];
|
|
cmd_buff[9] = final_pos_msf[2];
|
|
if ((do_sony_cmd(cmd_buff, 5, status, NULL, 0, 0) != 0) ||
|
|
(do_sony_cmd(cmd_buff + 5, 5, status, NULL, 0, 0) != 0)) {
|
|
printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMRESUME)\n",
|
|
status[0]);
|
|
return -EIO;
|
|
}
|
|
sony_audio_status = CDROM_AUDIO_PLAY;
|
|
return 0;
|
|
break;
|
|
|
|
case CDROMPLAYMSF: /* Play starting at the given MSF address. */
|
|
if (copy_from_user(params, argp, 6))
|
|
return -EFAULT;
|
|
spin_up_drive(status);
|
|
set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status);
|
|
|
|
/* The parameters are given in int, must be converted */
|
|
for (i = 0; i < 3; i++) {
|
|
cmd_buff[2 + i] = int_to_bcd(params[i]);
|
|
cmd_buff[7 + i] = int_to_bcd(params[i + 3]);
|
|
}
|
|
cmd_buff[0] = SONY535_PLAY_AUDIO;
|
|
cmd_buff[1] = 0; /* play back starting at this address */
|
|
/* cmd_buff[2-4] are filled in for loop above */
|
|
cmd_buff[5] = SONY535_PLAY_AUDIO;
|
|
cmd_buff[6] = 2; /* set ending address */
|
|
/* cmd_buff[7-9] are filled in for loop above */
|
|
if ((do_sony_cmd(cmd_buff, 5, status, NULL, 0, 0) != 0) ||
|
|
(do_sony_cmd(cmd_buff + 5, 5, status, NULL, 0, 0) != 0)) {
|
|
printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPLAYMSF)\n",
|
|
status[0]);
|
|
return -EIO;
|
|
}
|
|
/* Save the final position for pauses and resumes */
|
|
final_pos_msf[0] = cmd_buff[7];
|
|
final_pos_msf[1] = cmd_buff[8];
|
|
final_pos_msf[2] = cmd_buff[9];
|
|
sony_audio_status = CDROM_AUDIO_PLAY;
|
|
return 0;
|
|
break;
|
|
|
|
case CDROMREADTOCHDR: /* Read the table of contents header */
|
|
{
|
|
struct cdrom_tochdr __user *hdr = argp;
|
|
struct cdrom_tochdr loc_hdr;
|
|
|
|
sony_get_toc();
|
|
if (!sony_toc_read)
|
|
return -EIO;
|
|
loc_hdr.cdth_trk0 = bcd_to_int(sony_toc->first_track_num);
|
|
loc_hdr.cdth_trk1 = bcd_to_int(sony_toc->last_track_num);
|
|
if (copy_to_user(hdr, &loc_hdr, sizeof *hdr))
|
|
return -EFAULT;
|
|
}
|
|
return 0;
|
|
break;
|
|
|
|
case CDROMREADTOCENTRY: /* Read a given table of contents entry */
|
|
{
|
|
struct cdrom_tocentry __user *entry = argp;
|
|
struct cdrom_tocentry loc_entry;
|
|
int track_idx;
|
|
Byte *msf_val = NULL;
|
|
|
|
sony_get_toc();
|
|
if (!sony_toc_read) {
|
|
return -EIO;
|
|
}
|
|
|
|
if (copy_from_user(&loc_entry, entry, sizeof loc_entry))
|
|
return -EFAULT;
|
|
|
|
/* Lead out is handled separately since it is special. */
|
|
if (loc_entry.cdte_track == CDROM_LEADOUT) {
|
|
loc_entry.cdte_adr = 0 /*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 = 0 /*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));
|
|
}
|
|
if (copy_to_user(entry, &loc_entry, sizeof *entry))
|
|
return -EFAULT;
|
|
}
|
|
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;
|
|
|
|
if (copy_from_user(&ti, argp, sizeof ti))
|
|
return -EFAULT;
|
|
if ((ti.cdti_trk0 < sony_toc->first_track_num)
|
|
|| (sony_toc->last_track_num < ti.cdti_trk0)
|
|
|| (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 (bcd_to_int(sony_toc->last_track_num) <= ti.cdti_trk1) {
|
|
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;
|
|
|
|
spin_up_drive(status);
|
|
|
|
set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status);
|
|
|
|
/* Start the drive at the saved position. */
|
|
cmd_buff[0] = SONY535_PLAY_AUDIO;
|
|
cmd_buff[1] = 0; /* play back starting at this address */
|
|
cmd_buff[2] = params[1];
|
|
cmd_buff[3] = params[2];
|
|
cmd_buff[4] = params[3];
|
|
cmd_buff[5] = SONY535_PLAY_AUDIO;
|
|
cmd_buff[6] = 2; /* set ending address */
|
|
cmd_buff[7] = params[4];
|
|
cmd_buff[8] = params[5];
|
|
cmd_buff[9] = params[6];
|
|
if ((do_sony_cmd(cmd_buff, 5, status, NULL, 0, 0) != 0) ||
|
|
(do_sony_cmd(cmd_buff + 5, 5, status, NULL, 0, 0) != 0)) {
|
|
printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPLAYTRKIND)\n",
|
|
status[0]);
|
|
printk("... Params: %x %x %x %x %x %x %x\n",
|
|
params[0], params[1], params[2],
|
|
params[3], params[4], params[5], params[6]);
|
|
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(argp);
|
|
|
|
case CDROMVOLCTRL: /* Volume control. What volume does this change, anyway? */
|
|
{
|
|
struct cdrom_volctrl volctrl;
|
|
|
|
if (copy_from_user(&volctrl, argp, sizeof volctrl))
|
|
return -EFAULT;
|
|
cmd_buff[0] = SONY535_SET_VOLUME;
|
|
cmd_buff[1] = volctrl.channel0;
|
|
cmd_buff[2] = volctrl.channel1;
|
|
if (do_sony_cmd(cmd_buff, 3, status, NULL, 0, 0) != 0) {
|
|
printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMVOLCTRL)\n",
|
|
status[0]);
|
|
return -EIO;
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
case CDROMEJECT: /* Eject the drive */
|
|
cmd_buff[0] = SONY535_STOP;
|
|
do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0);
|
|
cmd_buff[0] = SONY535_SPIN_DOWN;
|
|
do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0);
|
|
|
|
sony_audio_status = CDROM_AUDIO_INVALID;
|
|
cmd_buff[0] = SONY535_EJECT_CADDY;
|
|
if (do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0) != 0) {
|
|
printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMEJECT)\n",
|
|
status[0]);
|
|
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
|
|
cdu_open(struct inode *inode,
|
|
struct file *filp)
|
|
{
|
|
Byte status[2], cmd_buff[2];
|
|
|
|
if (sony_inuse)
|
|
return -EBUSY;
|
|
if (check_drive_status() != 0)
|
|
return -EIO;
|
|
sony_inuse = 1;
|
|
|
|
if (spin_up_drive(status) != 0) {
|
|
printk(CDU535_MESSAGE_NAME " error 0x%.2x (cdu_open, spin up)\n",
|
|
status[0]);
|
|
sony_inuse = 0;
|
|
return -EIO;
|
|
}
|
|
sony_get_toc();
|
|
if (!sony_toc_read) {
|
|
cmd_buff[0] = SONY535_SPIN_DOWN;
|
|
do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0);
|
|
sony_inuse = 0;
|
|
return -EIO;
|
|
}
|
|
check_disk_change(inode->i_bdev);
|
|
sony_usage++;
|
|
|
|
#ifdef LOCK_DOORS
|
|
/* disable the eject button while mounted */
|
|
cmd_buff[0] = SONY535_DISABLE_EJECT_BUTTON;
|
|
do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0);
|
|
#endif
|
|
|
|
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 int
|
|
cdu_release(struct inode *inode,
|
|
struct file *filp)
|
|
{
|
|
Byte status[2], cmd_no;
|
|
|
|
sony_inuse = 0;
|
|
|
|
if (0 < sony_usage) {
|
|
sony_usage--;
|
|
}
|
|
if (sony_usage == 0) {
|
|
check_drive_status();
|
|
|
|
if (sony_audio_status != CDROM_AUDIO_PLAY) {
|
|
cmd_no = SONY535_SPIN_DOWN;
|
|
do_sony_cmd(&cmd_no, 1, status, NULL, 0, 0);
|
|
}
|
|
#ifdef LOCK_DOORS
|
|
/* enable the eject button after umount */
|
|
cmd_no = SONY535_ENABLE_EJECT_BUTTON;
|
|
do_sony_cmd(&cmd_no, 1, status, NULL, 0, 0);
|
|
#endif
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static struct block_device_operations cdu_fops =
|
|
{
|
|
.owner = THIS_MODULE,
|
|
.open = cdu_open,
|
|
.release = cdu_release,
|
|
.ioctl = cdu_ioctl,
|
|
.media_changed = cdu535_check_media_change,
|
|
};
|
|
|
|
static struct gendisk *cdu_disk;
|
|
|
|
/*
|
|
* Initialize the driver.
|
|
*/
|
|
static int __init sony535_init(void)
|
|
{
|
|
struct s535_sony_drive_config drive_config;
|
|
Byte cmd_buff[3];
|
|
Byte ret_buff[2];
|
|
Byte status[2];
|
|
unsigned long snap;
|
|
int got_result = 0;
|
|
int tmp_irq;
|
|
int i;
|
|
int err;
|
|
|
|
/* Setting the base I/O address to 0 will disable it. */
|
|
if ((sony535_cd_base_io == 0xffff)||(sony535_cd_base_io == 0))
|
|
return 0;
|
|
|
|
/* Set up all the register locations */
|
|
result_reg = sony535_cd_base_io;
|
|
command_reg = sony535_cd_base_io;
|
|
data_reg = sony535_cd_base_io + 1;
|
|
read_status_reg = sony535_cd_base_io + 2;
|
|
select_unit_reg = sony535_cd_base_io + 3;
|
|
|
|
#ifndef USE_IRQ
|
|
sony535_irq_used = 0; /* polling only until this is ready... */
|
|
#endif
|
|
/* we need to poll until things get initialized */
|
|
tmp_irq = sony535_irq_used;
|
|
sony535_irq_used = 0;
|
|
|
|
#if DEBUG > 0
|
|
printk(KERN_INFO CDU535_MESSAGE_NAME ": probing base address %03X\n",
|
|
sony535_cd_base_io);
|
|
#endif
|
|
/* look for the CD-ROM, follows the procedure in the DOS driver */
|
|
inb(select_unit_reg);
|
|
/* wait for 40 18 Hz ticks (reverse-engineered from DOS driver) */
|
|
schedule_timeout_interruptible((HZ+17)*40/18);
|
|
inb(result_reg);
|
|
|
|
outb(0, read_status_reg); /* does a reset? */
|
|
snap = jiffies;
|
|
while (jiffies-snap < SONY_JIFFIES_TIMEOUT) {
|
|
select_unit(0);
|
|
if (inb(result_reg) != 0xff) {
|
|
got_result = 1;
|
|
break;
|
|
}
|
|
sony_sleep();
|
|
}
|
|
|
|
if (!got_result || check_drive_status() == TIME_OUT)
|
|
goto Enodev;
|
|
|
|
/* CD-ROM drive responded -- get the drive configuration */
|
|
cmd_buff[0] = SONY535_INQUIRY;
|
|
if (do_sony_cmd(cmd_buff, 1, status, (Byte *)&drive_config, 28, 1) != 0)
|
|
goto Enodev;
|
|
|
|
/* was able to get the configuration,
|
|
* set drive mode as rest of init
|
|
*/
|
|
#if DEBUG > 0
|
|
/* 0x50 == CADDY_NOT_INSERTED | NOT_SPINNING */
|
|
if ( (status[0] & 0x7f) != 0 && (status[0] & 0x7f) != 0x50 )
|
|
printk(CDU535_MESSAGE_NAME
|
|
"Inquiry command returned status = 0x%x\n", status[0]);
|
|
#endif
|
|
/* now ready to use interrupts, if available */
|
|
sony535_irq_used = tmp_irq;
|
|
|
|
/* A negative sony535_irq_used will attempt an autoirq. */
|
|
if (sony535_irq_used < 0) {
|
|
unsigned long irq_mask, delay;
|
|
|
|
irq_mask = probe_irq_on();
|
|
enable_interrupts();
|
|
outb(0, read_status_reg); /* does a reset? */
|
|
delay = jiffies + HZ/10;
|
|
while (time_before(jiffies, delay)) ;
|
|
|
|
sony535_irq_used = probe_irq_off(irq_mask);
|
|
disable_interrupts();
|
|
}
|
|
if (sony535_irq_used > 0) {
|
|
if (request_irq(sony535_irq_used, cdu535_interrupt,
|
|
IRQF_DISABLED, CDU535_HANDLE, NULL)) {
|
|
printk("Unable to grab IRQ%d for the " CDU535_MESSAGE_NAME
|
|
" driver; polling instead.\n", sony535_irq_used);
|
|
sony535_irq_used = 0;
|
|
}
|
|
}
|
|
cmd_buff[0] = SONY535_SET_DRIVE_MODE;
|
|
cmd_buff[1] = 0x0; /* default audio */
|
|
if (do_sony_cmd(cmd_buff, 2, status, ret_buff, 1, 1) != 0)
|
|
goto Enodev_irq;
|
|
|
|
/* set the drive mode successful, we are set! */
|
|
sony_buffer_size = SONY535_BUFFER_SIZE;
|
|
sony_buffer_sectors = sony_buffer_size / CDU535_BLOCK_SIZE;
|
|
|
|
printk(KERN_INFO CDU535_MESSAGE_NAME " I/F CDROM : %8.8s %16.16s %4.4s",
|
|
drive_config.vendor_id,
|
|
drive_config.product_id,
|
|
drive_config.product_rev_level);
|
|
printk(" base address %03X, ", sony535_cd_base_io);
|
|
if (tmp_irq > 0)
|
|
printk("IRQ%d, ", tmp_irq);
|
|
printk("using %d byte buffer\n", sony_buffer_size);
|
|
|
|
if (register_blkdev(MAJOR_NR, CDU535_HANDLE)) {
|
|
err = -EIO;
|
|
goto out1;
|
|
}
|
|
sonycd535_queue = blk_init_queue(do_cdu535_request, &sonycd535_lock);
|
|
if (!sonycd535_queue) {
|
|
err = -ENOMEM;
|
|
goto out1a;
|
|
}
|
|
|
|
blk_queue_hardsect_size(sonycd535_queue, CDU535_BLOCK_SIZE);
|
|
sony_toc = kmalloc(sizeof(struct s535_sony_toc), GFP_KERNEL);
|
|
err = -ENOMEM;
|
|
if (!sony_toc)
|
|
goto out2;
|
|
last_sony_subcode = kmalloc(sizeof(struct s535_sony_subcode), GFP_KERNEL);
|
|
if (!last_sony_subcode)
|
|
goto out3;
|
|
sony_buffer = kmalloc(sizeof(Byte *) * sony_buffer_sectors, GFP_KERNEL);
|
|
if (!sony_buffer)
|
|
goto out4;
|
|
for (i = 0; i < sony_buffer_sectors; i++) {
|
|
sony_buffer[i] = kmalloc(CDU535_BLOCK_SIZE, GFP_KERNEL);
|
|
if (!sony_buffer[i]) {
|
|
while (--i>=0)
|
|
kfree(sony_buffer[i]);
|
|
goto out5;
|
|
}
|
|
}
|
|
initialized = 1;
|
|
|
|
cdu_disk = alloc_disk(1);
|
|
if (!cdu_disk)
|
|
goto out6;
|
|
cdu_disk->major = MAJOR_NR;
|
|
cdu_disk->first_minor = 0;
|
|
cdu_disk->fops = &cdu_fops;
|
|
sprintf(cdu_disk->disk_name, "cdu");
|
|
|
|
if (!request_region(sony535_cd_base_io, 4, CDU535_HANDLE)) {
|
|
printk(KERN_WARNING"sonycd535: Unable to request region 0x%x\n",
|
|
sony535_cd_base_io);
|
|
goto out7;
|
|
}
|
|
cdu_disk->queue = sonycd535_queue;
|
|
add_disk(cdu_disk);
|
|
return 0;
|
|
|
|
out7:
|
|
put_disk(cdu_disk);
|
|
out6:
|
|
for (i = 0; i < sony_buffer_sectors; i++)
|
|
kfree(sony_buffer[i]);
|
|
out5:
|
|
kfree(sony_buffer);
|
|
out4:
|
|
kfree(last_sony_subcode);
|
|
out3:
|
|
kfree(sony_toc);
|
|
out2:
|
|
blk_cleanup_queue(sonycd535_queue);
|
|
out1a:
|
|
unregister_blkdev(MAJOR_NR, CDU535_HANDLE);
|
|
out1:
|
|
if (sony535_irq_used)
|
|
free_irq(sony535_irq_used, NULL);
|
|
return err;
|
|
Enodev_irq:
|
|
if (sony535_irq_used)
|
|
free_irq(sony535_irq_used, NULL);
|
|
Enodev:
|
|
printk("Did not find a " CDU535_MESSAGE_NAME " drive\n");
|
|
return -EIO;
|
|
}
|
|
|
|
#ifndef MODULE
|
|
|
|
/*
|
|
* accept "kernel command line" parameters
|
|
* (added by emoenke@gwdg.de)
|
|
*
|
|
* use: tell LILO:
|
|
* sonycd535=0x320
|
|
*
|
|
* the address value has to be the existing CDROM port address.
|
|
*/
|
|
static int __init
|
|
sonycd535_setup(char *strings)
|
|
{
|
|
int ints[3];
|
|
(void)get_options(strings, ARRAY_SIZE(ints), ints);
|
|
/* if IRQ change and default io base desired,
|
|
* then call with io base of 0
|
|
*/
|
|
if (ints[0] > 0)
|
|
if (ints[1] != 0)
|
|
sony535_cd_base_io = ints[1];
|
|
if (ints[0] > 1)
|
|
sony535_irq_used = ints[2];
|
|
if ((strings != NULL) && (*strings != '\0'))
|
|
printk(CDU535_MESSAGE_NAME
|
|
": Warning: Unknown interface type: %s\n", strings);
|
|
|
|
return 1;
|
|
}
|
|
|
|
__setup("sonycd535=", sonycd535_setup);
|
|
|
|
#endif /* MODULE */
|
|
|
|
static void __exit
|
|
sony535_exit(void)
|
|
{
|
|
int i;
|
|
|
|
release_region(sony535_cd_base_io, 4);
|
|
for (i = 0; i < sony_buffer_sectors; i++)
|
|
kfree(sony_buffer[i]);
|
|
kfree(sony_buffer);
|
|
kfree(last_sony_subcode);
|
|
kfree(sony_toc);
|
|
del_gendisk(cdu_disk);
|
|
put_disk(cdu_disk);
|
|
blk_cleanup_queue(sonycd535_queue);
|
|
if (unregister_blkdev(MAJOR_NR, CDU535_HANDLE) == -EINVAL)
|
|
printk("Uh oh, couldn't unregister " CDU535_HANDLE "\n");
|
|
else
|
|
printk(KERN_INFO CDU535_HANDLE " module released\n");
|
|
}
|
|
|
|
module_init(sony535_init);
|
|
module_exit(sony535_exit);
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS_BLOCKDEV_MAJOR(CDU535_CDROM_MAJOR);
|