diff --git a/[refs] b/[refs]
index 3e8210c1e2cb..248891ff5b97 100644
--- a/[refs]
+++ b/[refs]
@@ -1,2 +1,2 @@
---
-refs/heads/master: 5f60cfd932b42c69ed3226400cb5eab152576c3a
+refs/heads/master: e934c82657529a85ca48a2fc3749c514df985e79
diff --git a/trunk/Documentation/DocBook/kernel-api.tmpl b/trunk/Documentation/DocBook/kernel-api.tmpl
index 8c5698a8c2e1..38f88b6ae405 100644
--- a/trunk/Documentation/DocBook/kernel-api.tmpl
+++ b/trunk/Documentation/DocBook/kernel-api.tmpl
@@ -643,15 +643,4 @@ X!Idrivers/video/console/fonts.c
!Edrivers/spi/spi.c
-
- splice API
- )
- splice is a method for moving blocks of data around inside the
- kernel, without continually transferring it between the kernel
- and user space.
-
-!Iinclude/linux/splice.h
-!Ffs/splice.c
-
-
diff --git a/trunk/Documentation/block/barrier.txt b/trunk/Documentation/block/barrier.txt
index 7d279f2f5bb2..a272c3db8094 100644
--- a/trunk/Documentation/block/barrier.txt
+++ b/trunk/Documentation/block/barrier.txt
@@ -82,12 +82,23 @@ including draining and flushing.
typedef void (prepare_flush_fn)(request_queue_t *q, struct request *rq);
int blk_queue_ordered(request_queue_t *q, unsigned ordered,
- prepare_flush_fn *prepare_flush_fn);
+ prepare_flush_fn *prepare_flush_fn,
+ unsigned gfp_mask);
+
+int blk_queue_ordered_locked(request_queue_t *q, unsigned ordered,
+ prepare_flush_fn *prepare_flush_fn,
+ unsigned gfp_mask);
+
+The only difference between the two functions is whether or not the
+caller is holding q->queue_lock on entry. The latter expects the
+caller is holding the lock.
@q : the queue in question
@ordered : the ordered mode the driver/device supports
@prepare_flush_fn : this function should prepare @rq such that it
flushes cache to physical medium when executed
+@gfp_mask : gfp_mask used when allocating data structures
+ for ordered processing
For example, SCSI disk driver's prepare_flush_fn looks like the
following.
@@ -95,10 +106,9 @@ following.
static void sd_prepare_flush(request_queue_t *q, struct request *rq)
{
memset(rq->cmd, 0, sizeof(rq->cmd));
- rq->cmd_type = REQ_TYPE_BLOCK_PC;
+ rq->flags |= REQ_BLOCK_PC;
rq->timeout = SD_TIMEOUT;
rq->cmd[0] = SYNCHRONIZE_CACHE;
- rq->cmd_len = 10;
}
The following seven ordered modes are supported. The following table
diff --git a/trunk/Documentation/power_supply_class.txt b/trunk/Documentation/power_supply_class.txt
deleted file mode 100644
index 9758cf433c06..000000000000
--- a/trunk/Documentation/power_supply_class.txt
+++ /dev/null
@@ -1,167 +0,0 @@
-Linux power supply class
-========================
-
-Synopsis
-~~~~~~~~
-Power supply class used to represent battery, UPS, AC or DC power supply
-properties to user-space.
-
-It defines core set of attributes, which should be applicable to (almost)
-every power supply out there. Attributes are available via sysfs and uevent
-interfaces.
-
-Each attribute has well defined meaning, up to unit of measure used. While
-the attributes provided are believed to be universally applicable to any
-power supply, specific monitoring hardware may not be able to provide them
-all, so any of them may be skipped.
-
-Power supply class is extensible, and allows to define drivers own attributes.
-The core attribute set is subject to the standard Linux evolution (i.e.
-if it will be found that some attribute is applicable to many power supply
-types or their drivers, it can be added to the core set).
-
-It also integrates with LED framework, for the purpose of providing
-typically expected feedback of battery charging/fully charged status and
-AC/USB power supply online status. (Note that specific details of the
-indication (including whether to use it at all) are fully controllable by
-user and/or specific machine defaults, per design principles of LED
-framework).
-
-
-Attributes/properties
-~~~~~~~~~~~~~~~~~~~~~
-Power supply class has predefined set of attributes, this eliminates code
-duplication across drivers. Power supply class insist on reusing its
-predefined attributes *and* their units.
-
-So, userspace gets predictable set of attributes and their units for any
-kind of power supply, and can process/present them to a user in consistent
-manner. Results for different power supplies and machines are also directly
-comparable.
-
-See drivers/power/ds2760_battery.c and drivers/power/pda_power.c for the
-example how to declare and handle attributes.
-
-
-Units
-~~~~~
-Quoting include/linux/power_supply.h:
-
- All voltages, currents, charges, energies, time and temperatures in µV,
- µA, µAh, µWh, seconds and tenths of degree Celsius unless otherwise
- stated. It's driver's job to convert its raw values to units in which
- this class operates.
-
-
-Attributes/properties detailed
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-~ ~ ~ ~ ~ ~ ~ Charge/Energy/Capacity - how to not confuse ~ ~ ~ ~ ~ ~ ~
-~ ~
-~ Because both "charge" (µAh) and "energy" (µWh) represents "capacity" ~
-~ of battery, this class distinguish these terms. Don't mix them! ~
-~ ~
-~ CHARGE_* attributes represents capacity in µAh only. ~
-~ ENERGY_* attributes represents capacity in µWh only. ~
-~ CAPACITY attribute represents capacity in *percents*, from 0 to 100. ~
-~ ~
-~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
-
-Postfixes:
-_AVG - *hardware* averaged value, use it if your hardware is really able to
-report averaged values.
-_NOW - momentary/instantaneous values.
-
-STATUS - this attribute represents operating status (charging, full,
-discharging (i.e. powering a load), etc.). This corresponds to
-BATTERY_STATUS_* values, as defined in battery.h.
-
-HEALTH - represents health of the battery, values corresponds to
-POWER_SUPPLY_HEALTH_*, defined in battery.h.
-
-VOLTAGE_MAX_DESIGN, VOLTAGE_MIN_DESIGN - design values for maximal and
-minimal power supply voltages. Maximal/minimal means values of voltages
-when battery considered "full"/"empty" at normal conditions. Yes, there is
-no direct relation between voltage and battery capacity, but some dumb
-batteries use voltage for very approximated calculation of capacity.
-Battery driver also can use this attribute just to inform userspace
-about maximal and minimal voltage thresholds of a given battery.
-
-CHARGE_FULL_DESIGN, CHARGE_EMPTY_DESIGN - design charge values, when
-battery considered full/empty.
-
-ENERGY_FULL_DESIGN, ENERGY_EMPTY_DESIGN - same as above but for energy.
-
-CHARGE_FULL, CHARGE_EMPTY - These attributes means "last remembered value
-of charge when battery became full/empty". It also could mean "value of
-charge when battery considered full/empty at given conditions (temperature,
-age)". I.e. these attributes represents real thresholds, not design values.
-
-ENERGY_FULL, ENERGY_EMPTY - same as above but for energy.
-
-CAPACITY - capacity in percents.
-CAPACITY_LEVEL - capacity level. This corresponds to
-POWER_SUPPLY_CAPACITY_LEVEL_*.
-
-TEMP - temperature of the power supply.
-TEMP_AMBIENT - ambient temperature.
-
-TIME_TO_EMPTY - seconds left for battery to be considered empty (i.e.
-while battery powers a load)
-TIME_TO_FULL - seconds left for battery to be considered full (i.e.
-while battery is charging)
-
-
-Battery <-> external power supply interaction
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Often power supplies are acting as supplies and supplicants at the same
-time. Batteries are good example. So, batteries usually care if they're
-externally powered or not.
-
-For that case, power supply class implements notification mechanism for
-batteries.
-
-External power supply (AC) lists supplicants (batteries) names in
-"supplied_to" struct member, and each power_supply_changed() call
-issued by external power supply will notify supplicants via
-external_power_changed callback.
-
-
-QA
-~~
-Q: Where is POWER_SUPPLY_PROP_XYZ attribute?
-A: If you cannot find attribute suitable for your driver needs, feel free
- to add it and send patch along with your driver.
-
- The attributes available currently are the ones currently provided by the
- drivers written.
-
- Good candidates to add in future: model/part#, cycle_time, manufacturer,
- etc.
-
-
-Q: I have some very specific attribute (e.g. battery color), should I add
- this attribute to standard ones?
-A: Most likely, no. Such attribute can be placed in the driver itself, if
- it is useful. Of course, if the attribute in question applicable to
- large set of batteries, provided by many drivers, and/or comes from
- some general battery specification/standard, it may be a candidate to
- be added to the core attribute set.
-
-
-Q: Suppose, my battery monitoring chip/firmware does not provides capacity
- in percents, but provides charge_{now,full,empty}. Should I calculate
- percentage capacity manually, inside the driver, and register CAPACITY
- attribute? The same question about time_to_empty/time_to_full.
-A: Most likely, no. This class is designed to export properties which are
- directly measurable by the specific hardware available.
-
- Inferring not available properties using some heuristics or mathematical
- model is not subject of work for a battery driver. Such functionality
- should be factored out, and in fact, apm_power, the driver to serve
- legacy APM API on top of power supply class, uses a simple heuristic of
- approximating remaining battery capacity based on its charge, current,
- voltage and so on. But full-fledged battery model is likely not subject
- for kernel at all, as it would require floating point calculation to deal
- with things like differential equations and Kalman filters. This is
- better be handled by batteryd/libbattery, yet to be written.
diff --git a/trunk/MAINTAINERS b/trunk/MAINTAINERS
index bc272bf0ff97..2c1dfb271613 100644
--- a/trunk/MAINTAINERS
+++ b/trunk/MAINTAINERS
@@ -1856,7 +1856,7 @@ W: http://www.openib.org/
T: git kernel.org:/pub/scm/linux/kernel/git/roland/infiniband.git
S: Supported
-INPUT (KEYBOARD, MOUSE, JOYSTICK, TOUCHSCREEN) DRIVERS
+INPUT (KEYBOARD, MOUSE, JOYSTICK) DRIVERS
P: Dmitry Torokhov
M: dmitry.torokhov@gmail.com
M: dtor@mail.ru
diff --git a/trunk/arch/s390/crypto/crypt_s390.h b/trunk/arch/s390/crypto/crypt_s390.h
index 95f5160df27f..2775d2618332 100644
--- a/trunk/arch/s390/crypto/crypt_s390.h
+++ b/trunk/arch/s390/crypto/crypt_s390.h
@@ -24,7 +24,7 @@
#define CRYPT_S390_PRIORITY 300
#define CRYPT_S390_COMPOSITE_PRIORITY 400
-/* s390 cryptographic operations */
+/* s930 cryptographic operations */
enum crypt_s390_operations {
CRYPT_S390_KM = 0x0100,
CRYPT_S390_KMC = 0x0200,
diff --git a/trunk/arch/s390/kernel/early.c b/trunk/arch/s390/kernel/early.c
index e6289ee74ecd..50538e545618 100644
--- a/trunk/arch/s390/kernel/early.c
+++ b/trunk/arch/s390/kernel/early.c
@@ -171,6 +171,37 @@ static inline int memory_fast_detect(void)
}
#endif
+#define ADDR2G (1UL << 31)
+
+static noinline __init unsigned long sclp_memory_detect(void)
+{
+ struct sclp_readinfo_sccb *sccb;
+ unsigned long long memsize;
+
+ sccb = &s390_readinfo_sccb;
+
+ if (sccb->header.response_code != 0x10)
+ return 0;
+
+ if (sccb->rnsize)
+ memsize = sccb->rnsize << 20;
+ else
+ memsize = sccb->rnsize2 << 20;
+ if (sccb->rnmax)
+ memsize *= sccb->rnmax;
+ else
+ memsize *= sccb->rnmax2;
+#ifndef CONFIG_64BIT
+ /*
+ * Can't deal with more than 2G in 31 bit addressing mode, so
+ * limit the value in order to avoid strange side effects.
+ */
+ if (memsize > ADDR2G)
+ memsize = ADDR2G;
+#endif
+ return (unsigned long) memsize;
+}
+
static inline __init unsigned long __tprot(unsigned long addr)
{
int cc = -1;
@@ -187,7 +218,6 @@ static inline __init unsigned long __tprot(unsigned long addr)
/* Checking memory in 128KB increments. */
#define CHUNK_INCR (1UL << 17)
-#define ADDR2G (1UL << 31)
static noinline __init void find_memory_chunks(unsigned long memsize)
{
@@ -263,7 +293,7 @@ static noinline __init void setup_lowcore_early(void)
*/
void __init startup_init(void)
{
- unsigned long long memsize;
+ unsigned long memsize;
ipl_save_parameters();
clear_bss_section();
@@ -275,17 +305,8 @@ void __init startup_init(void)
sort_main_extable();
setup_lowcore_early();
sclp_readinfo_early();
- sclp_facilities_detect();
memsize = sclp_memory_detect();
-#ifndef CONFIG_64BIT
- /*
- * Can't deal with more than 2G in 31 bit addressing mode, so
- * limit the value in order to avoid strange side effects.
- */
- if (memsize > ADDR2G)
- memsize = ADDR2G;
-#endif
if (memory_fast_detect() < 0)
- find_memory_chunks((unsigned long) memsize);
+ find_memory_chunks(memsize);
lockdep_on();
}
diff --git a/trunk/arch/s390/kernel/entry.S b/trunk/arch/s390/kernel/entry.S
index bc7ff3658c3d..6234c6978a1f 100644
--- a/trunk/arch/s390/kernel/entry.S
+++ b/trunk/arch/s390/kernel/entry.S
@@ -107,11 +107,6 @@ STACK_SIZE = 1 << STACK_SHIFT
l %r13,__LC_SVC_NEW_PSW+4 # load &system_call to %r13
.endm
- .macro SAVE_ALL_SVC psworg,savearea
- la %r12,\psworg
- l %r15,__LC_KERNEL_STACK # problem state -> load ksp
- .endm
-
.macro SAVE_ALL_SYNC psworg,savearea
la %r12,\psworg
tm \psworg+1,0x01 # test problem state bit
@@ -223,7 +218,7 @@ system_call:
STORE_TIMER __LC_SYNC_ENTER_TIMER
sysc_saveall:
SAVE_ALL_BASE __LC_SAVE_AREA
- SAVE_ALL_SVC __LC_SVC_OLD_PSW,__LC_SAVE_AREA
+ SAVE_ALL_SYNC __LC_SVC_OLD_PSW,__LC_SAVE_AREA
CREATE_STACK_FRAME __LC_SVC_OLD_PSW,__LC_SAVE_AREA
lh %r7,0x8a # get svc number from lowcore
#ifdef CONFIG_VIRT_CPU_ACCOUNTING
diff --git a/trunk/arch/s390/kernel/entry64.S b/trunk/arch/s390/kernel/entry64.S
index 2a7b1304418b..685f11faa4bc 100644
--- a/trunk/arch/s390/kernel/entry64.S
+++ b/trunk/arch/s390/kernel/entry64.S
@@ -99,11 +99,6 @@ _TIF_WORK_INT = (_TIF_SIGPENDING | _TIF_RESTORE_SIGMASK | _TIF_NEED_RESCHED | \
larl %r13,system_call
.endm
- .macro SAVE_ALL_SVC psworg,savearea
- la %r12,\psworg
- lg %r15,__LC_KERNEL_STACK # problem state -> load ksp
- .endm
-
.macro SAVE_ALL_SYNC psworg,savearea
la %r12,\psworg
tm \psworg+1,0x01 # test problem state bit
@@ -212,7 +207,7 @@ system_call:
STORE_TIMER __LC_SYNC_ENTER_TIMER
sysc_saveall:
SAVE_ALL_BASE __LC_SAVE_AREA
- SAVE_ALL_SVC __LC_SVC_OLD_PSW,__LC_SAVE_AREA
+ SAVE_ALL_SYNC __LC_SVC_OLD_PSW,__LC_SAVE_AREA
CREATE_STACK_FRAME __LC_SVC_OLD_PSW,__LC_SAVE_AREA
llgh %r7,__LC_SVC_INT_CODE # get svc number from lowcore
#ifdef CONFIG_VIRT_CPU_ACCOUNTING
diff --git a/trunk/arch/s390/kernel/ipl.c b/trunk/arch/s390/kernel/ipl.c
index 82b131ddd7ff..367caf92ea78 100644
--- a/trunk/arch/s390/kernel/ipl.c
+++ b/trunk/arch/s390/kernel/ipl.c
@@ -25,6 +25,10 @@
#define IPL_PARM_BLOCK_VERSION 0
+#define SCCB_VALID (s390_readinfo_sccb.header.response_code == 0x10)
+#define SCCB_LOADPARM (&s390_readinfo_sccb.loadparm)
+#define SCCB_FLAG (s390_readinfo_sccb.flags)
+
#define IPL_UNKNOWN_STR "unknown"
#define IPL_CCW_STR "ccw"
#define IPL_FCP_STR "fcp"
@@ -142,8 +146,6 @@ static struct ipl_parameter_block *dump_block_ccw;
static enum shutdown_action on_panic_action = SHUTDOWN_STOP;
-static struct sclp_ipl_info sclp_ipl_info;
-
int diag308(unsigned long subcode, void *addr)
{
register unsigned long _addr asm("0") = (unsigned long) addr;
@@ -373,9 +375,9 @@ static ssize_t ipl_ccw_loadparm_show(struct kset *kset, char *page)
{
char loadparm[LOADPARM_LEN + 1] = {};
- if (!sclp_ipl_info.is_valid)
+ if (!SCCB_VALID)
return sprintf(page, "#unknown#\n");
- memcpy(loadparm, &sclp_ipl_info.loadparm, LOADPARM_LEN);
+ memcpy(loadparm, SCCB_LOADPARM, LOADPARM_LEN);
EBCASC(loadparm, LOADPARM_LEN);
strstrip(loadparm);
return sprintf(page, "%s\n", loadparm);
@@ -908,9 +910,9 @@ static int __init reipl_ccw_init(void)
reipl_block_ccw->hdr.blk0_len = IPL_PARM_BLK0_CCW_LEN;
reipl_block_ccw->hdr.pbt = DIAG308_IPL_TYPE_CCW;
/* check if read scp info worked and set loadparm */
- if (sclp_ipl_info.is_valid)
+ if (SCCB_VALID)
memcpy(reipl_block_ccw->ipl_info.ccw.load_param,
- &sclp_ipl_info.loadparm, LOADPARM_LEN);
+ SCCB_LOADPARM, LOADPARM_LEN);
else
/* read scp info failed: set empty loadparm (EBCDIC blanks) */
memset(reipl_block_ccw->ipl_info.ccw.load_param, 0x40,
@@ -1005,7 +1007,7 @@ static int __init dump_fcp_init(void)
{
int rc;
- if (!sclp_ipl_info.has_dump)
+ if(!(SCCB_FLAG & 0x2) || !SCCB_VALID)
return 0; /* LDIPL DUMP is not installed */
if (!diag308_set_works)
return 0;
@@ -1086,7 +1088,6 @@ static int __init s390_ipl_init(void)
{
int rc;
- sclp_get_ipl_info(&sclp_ipl_info);
reipl_probe();
rc = ipl_init();
if (rc)
diff --git a/trunk/arch/s390/kernel/process.c b/trunk/arch/s390/kernel/process.c
index 441975b796fb..eb43c3b31269 100644
--- a/trunk/arch/s390/kernel/process.c
+++ b/trunk/arch/s390/kernel/process.c
@@ -93,8 +93,8 @@ void do_monitor_call(struct pt_regs *regs, long interruption_code)
/* disable monitor call class 0 */
__ctl_clear_bit(8, 15);
- atomic_notifier_call_chain(&idle_chain, S390_CPU_NOT_IDLE,
- (void *)(long) smp_processor_id());
+ atomic_notifier_call_chain(&idle_chain, CPU_NOT_IDLE,
+ (void *)(long) smp_processor_id());
}
extern void s390_handle_mcck(void);
@@ -115,7 +115,7 @@ static void default_idle(void)
}
rc = atomic_notifier_call_chain(&idle_chain,
- S390_CPU_IDLE, (void *)(long) cpu);
+ CPU_IDLE, (void *)(long) cpu);
if (rc != NOTIFY_OK && rc != NOTIFY_DONE)
BUG();
if (rc != NOTIFY_OK) {
diff --git a/trunk/arch/s390/kernel/smp.c b/trunk/arch/s390/kernel/smp.c
index 182c085ae4dd..8ff2feaf9b00 100644
--- a/trunk/arch/s390/kernel/smp.c
+++ b/trunk/arch/s390/kernel/smp.c
@@ -410,40 +410,58 @@ EXPORT_SYMBOL(smp_ctl_clear_bit);
unsigned int zfcpdump_prefix_array[NR_CPUS + 1] \
__attribute__((__section__(".data")));
-static void __init smp_get_save_area(unsigned int cpu, unsigned int phy_cpu)
+static void __init smp_get_save_areas(void)
{
+ unsigned int cpu, cpu_num, rc;
+ __u16 boot_cpu_addr;
+
if (ipl_info.type != IPL_TYPE_FCP_DUMP)
return;
- if (cpu >= NR_CPUS) {
- printk(KERN_WARNING "Registers for cpu %i not saved since dump "
- "kernel was compiled with NR_CPUS=%i\n", cpu, NR_CPUS);
- return;
- }
- zfcpdump_save_areas[cpu] = alloc_bootmem(sizeof(union save_area));
- __cpu_logical_map[1] = (__u16) phy_cpu;
- while (signal_processor(1, sigp_stop_and_store_status) == sigp_busy)
- cpu_relax();
- memcpy(zfcpdump_save_areas[cpu],
- (void *)(unsigned long) store_prefix() + SAVE_AREA_BASE,
- SAVE_AREA_SIZE);
-#ifdef CONFIG_64BIT
- /* copy original prefix register */
- zfcpdump_save_areas[cpu]->s390x.pref_reg = zfcpdump_prefix_array[cpu];
+ boot_cpu_addr = S390_lowcore.cpu_data.cpu_addr;
+ cpu_num = 1;
+ for (cpu = 0; cpu <= 65535; cpu++) {
+ if ((u16) cpu == boot_cpu_addr)
+ continue;
+ __cpu_logical_map[1] = (__u16) cpu;
+ if (signal_processor(1, sigp_sense) == sigp_not_operational)
+ continue;
+ if (cpu_num >= NR_CPUS) {
+ printk("WARNING: Registers for cpu %i are not "
+ "saved, since dump kernel was compiled with"
+ "NR_CPUS=%i!\n", cpu_num, NR_CPUS);
+ continue;
+ }
+ zfcpdump_save_areas[cpu_num] =
+ alloc_bootmem(sizeof(union save_area));
+ while (1) {
+ rc = signal_processor(1, sigp_stop_and_store_status);
+ if (rc != sigp_busy)
+ break;
+ cpu_relax();
+ }
+ memcpy(zfcpdump_save_areas[cpu_num],
+ (void *)(unsigned long) store_prefix() +
+ SAVE_AREA_BASE, SAVE_AREA_SIZE);
+#ifdef __s390x__
+ /* copy original prefix register */
+ zfcpdump_save_areas[cpu_num]->s390x.pref_reg =
+ zfcpdump_prefix_array[cpu_num];
#endif
+ cpu_num++;
+ }
}
union save_area *zfcpdump_save_areas[NR_CPUS + 1];
EXPORT_SYMBOL_GPL(zfcpdump_save_areas);
#else
-
-static inline void smp_get_save_area(unsigned int cpu, unsigned int phy_cpu) { }
-
-#endif /* CONFIG_ZFCPDUMP || CONFIG_ZFCPDUMP_MODULE */
+#define smp_get_save_areas() do { } while (0)
+#endif
/*
* Lets check how many CPUs we have.
*/
+
static unsigned int __init smp_count_cpus(void)
{
unsigned int cpu, num_cpus;
@@ -452,6 +470,7 @@ static unsigned int __init smp_count_cpus(void)
/*
* cpu 0 is the boot cpu. See smp_prepare_boot_cpu.
*/
+
boot_cpu_addr = S390_lowcore.cpu_data.cpu_addr;
current_thread_info()->cpu = 0;
num_cpus = 1;
@@ -461,11 +480,12 @@ static unsigned int __init smp_count_cpus(void)
__cpu_logical_map[1] = (__u16) cpu;
if (signal_processor(1, sigp_sense) == sigp_not_operational)
continue;
- smp_get_save_area(num_cpus, cpu);
num_cpus++;
}
+
printk("Detected %d CPU's\n", (int) num_cpus);
printk("Boot cpu address %2X\n", boot_cpu_addr);
+
return num_cpus;
}
@@ -586,6 +606,7 @@ void __init smp_setup_cpu_possible_map(void)
{
unsigned int phy_cpus, pos_cpus, cpu;
+ smp_get_save_areas();
phy_cpus = smp_count_cpus();
pos_cpus = min(phy_cpus + additional_cpus, (unsigned int) NR_CPUS);
diff --git a/trunk/arch/s390/kernel/time.c b/trunk/arch/s390/kernel/time.c
index 48dae49bc1ec..9c2872a7cca7 100644
--- a/trunk/arch/s390/kernel/time.c
+++ b/trunk/arch/s390/kernel/time.c
@@ -226,10 +226,10 @@ static int nohz_idle_notify(struct notifier_block *self,
unsigned long action, void *hcpu)
{
switch (action) {
- case S390_CPU_IDLE:
+ case CPU_IDLE:
stop_hz_timer();
break;
- case S390_CPU_NOT_IDLE:
+ case CPU_NOT_IDLE:
start_hz_timer();
break;
}
diff --git a/trunk/arch/s390/kernel/vtime.c b/trunk/arch/s390/kernel/vtime.c
index b6ed143e8597..1e1a6ee2cac1 100644
--- a/trunk/arch/s390/kernel/vtime.c
+++ b/trunk/arch/s390/kernel/vtime.c
@@ -545,10 +545,10 @@ static int vtimer_idle_notify(struct notifier_block *self,
unsigned long action, void *hcpu)
{
switch (action) {
- case S390_CPU_IDLE:
+ case CPU_IDLE:
stop_cpu_timer();
break;
- case S390_CPU_NOT_IDLE:
+ case CPU_NOT_IDLE:
start_cpu_timer();
break;
}
diff --git a/trunk/arch/s390/lib/Makefile b/trunk/arch/s390/lib/Makefile
index 52084436ab69..59aea65ce99f 100644
--- a/trunk/arch/s390/lib/Makefile
+++ b/trunk/arch/s390/lib/Makefile
@@ -4,7 +4,7 @@
EXTRA_AFLAGS := -traditional
-lib-y += delay.o string.o uaccess_std.o uaccess_pt.o
-obj-$(CONFIG_32BIT) += div64.o qrnnd.o
+lib-y += delay.o string.o uaccess_std.o uaccess_pt.o qrnnd.o
+obj-$(CONFIG_32BIT) += div64.o
lib-$(CONFIG_64BIT) += uaccess_mvcos.o
lib-$(CONFIG_SMP) += spinlock.o
diff --git a/trunk/block/Kconfig b/trunk/block/Kconfig
index 285935134bcd..a50f48111647 100644
--- a/trunk/block/Kconfig
+++ b/trunk/block/Kconfig
@@ -1,7 +1,7 @@
#
# Block layer core configuration
#
-menuconfig BLOCK
+config BLOCK
bool "Enable the block layer" if EMBEDDED
default y
help
@@ -49,6 +49,6 @@ config LSF
If unsure, say Y.
-endif # BLOCK
+endif
source block/Kconfig.iosched
diff --git a/trunk/block/cfq-iosched.c b/trunk/block/cfq-iosched.c
index e0aa4dad6742..baef5fc7cff8 100644
--- a/trunk/block/cfq-iosched.c
+++ b/trunk/block/cfq-iosched.c
@@ -92,8 +92,6 @@ struct cfq_data {
struct cfq_queue *active_queue;
struct cfq_io_context *active_cic;
- struct cfq_queue *async_cfqq[IOPRIO_BE_NR];
-
struct timer_list idle_class_timer;
sector_t last_position;
@@ -1353,8 +1351,8 @@ static void cfq_ioc_set_ioprio(struct io_context *ioc)
}
static struct cfq_queue *
-cfq_find_alloc_queue(struct cfq_data *cfqd, int is_sync,
- struct task_struct *tsk, gfp_t gfp_mask)
+cfq_get_queue(struct cfq_data *cfqd, int is_sync, struct task_struct *tsk,
+ gfp_t gfp_mask)
{
struct cfq_queue *cfqq, *new_cfqq = NULL;
struct cfq_io_context *cic;
@@ -1407,35 +1405,12 @@ cfq_find_alloc_queue(struct cfq_data *cfqd, int is_sync,
if (new_cfqq)
kmem_cache_free(cfq_pool, new_cfqq);
+ atomic_inc(&cfqq->ref);
out:
WARN_ON((gfp_mask & __GFP_WAIT) && !cfqq);
return cfqq;
}
-static struct cfq_queue *
-cfq_get_queue(struct cfq_data *cfqd, int is_sync, struct task_struct *tsk,
- gfp_t gfp_mask)
-{
- const int ioprio = task_ioprio(tsk);
- struct cfq_queue *cfqq = NULL;
-
- if (!is_sync)
- cfqq = cfqd->async_cfqq[ioprio];
- if (!cfqq)
- cfqq = cfq_find_alloc_queue(cfqd, is_sync, tsk, gfp_mask);
-
- /*
- * pin the queue now that it's allocated, scheduler exit will prune it
- */
- if (!is_sync && !cfqd->async_cfqq[ioprio]) {
- atomic_inc(&cfqq->ref);
- cfqd->async_cfqq[ioprio] = cfqq;
- }
-
- atomic_inc(&cfqq->ref);
- return cfqq;
-}
-
/*
* We drop cfq io contexts lazily, so we may find a dead one.
*/
@@ -2044,7 +2019,6 @@ static void cfq_exit_queue(elevator_t *e)
{
struct cfq_data *cfqd = e->elevator_data;
request_queue_t *q = cfqd->queue;
- int i;
cfq_shutdown_timer_wq(cfqd);
@@ -2061,13 +2035,6 @@ static void cfq_exit_queue(elevator_t *e)
__cfq_exit_single_io_context(cfqd, cic);
}
- /*
- * Put the async queues
- */
- for (i = 0; i < IOPRIO_BE_NR; i++)
- if (cfqd->async_cfqq[i])
- cfq_put_queue(cfqd->async_cfqq[i]);
-
spin_unlock_irq(q->queue_lock);
cfq_shutdown_timer_wq(cfqd);
diff --git a/trunk/block/elevator.c b/trunk/block/elevator.c
index 4769a25d7037..ce866eb75f6a 100644
--- a/trunk/block/elevator.c
+++ b/trunk/block/elevator.c
@@ -112,8 +112,12 @@ static inline int elv_try_merge(struct request *__rq, struct bio *bio)
static struct elevator_type *elevator_find(const char *name)
{
struct elevator_type *e;
+ struct list_head *entry;
+
+ list_for_each(entry, &elv_list) {
+
+ e = list_entry(entry, struct elevator_type, list);
- list_for_each_entry(e, &elv_list, list) {
if (!strcmp(e->elevator_name, name))
return e;
}
@@ -1112,11 +1116,14 @@ ssize_t elv_iosched_show(request_queue_t *q, char *name)
{
elevator_t *e = q->elevator;
struct elevator_type *elv = e->elevator_type;
- struct elevator_type *__e;
+ struct list_head *entry;
int len = 0;
spin_lock(&elv_list_lock);
- list_for_each_entry(__e, &elv_list, list) {
+ list_for_each(entry, &elv_list) {
+ struct elevator_type *__e;
+
+ __e = list_entry(entry, struct elevator_type, list);
if (!strcmp(elv->elevator_name, __e->elevator_name))
len += sprintf(name+len, "[%s] ", elv->elevator_name);
else
diff --git a/trunk/block/ll_rw_blk.c b/trunk/block/ll_rw_blk.c
index ef42bb2b12b6..c99b46354859 100644
--- a/trunk/block/ll_rw_blk.c
+++ b/trunk/block/ll_rw_blk.c
@@ -527,6 +527,8 @@ int blk_do_ordered(request_queue_t *q, struct request **rqp)
static int flush_dry_bio_endio(struct bio *bio, unsigned int bytes, int error)
{
request_queue_t *q = bio->bi_private;
+ struct bio_vec *bvec;
+ int i;
/*
* This is dry run, restore bio_sector and size. We'll finish
@@ -538,6 +540,13 @@ static int flush_dry_bio_endio(struct bio *bio, unsigned int bytes, int error)
if (bio->bi_size)
return 1;
+ /* Rewind bvec's */
+ bio->bi_idx = 0;
+ bio_for_each_segment(bvec, bio, i) {
+ bvec->bv_len += bvec->bv_offset;
+ bvec->bv_offset = 0;
+ }
+
/* Reset bio */
set_bit(BIO_UPTODATE, &bio->bi_flags);
bio->bi_size = q->bi_size;
@@ -1295,9 +1304,9 @@ static int blk_hw_contig_segment(request_queue_t *q, struct bio *bio,
if (unlikely(!bio_flagged(nxt, BIO_SEG_VALID)))
blk_recount_segments(q, nxt);
if (!BIOVEC_VIRT_MERGEABLE(__BVEC_END(bio), __BVEC_START(nxt)) ||
- BIOVEC_VIRT_OVERSIZE(bio->bi_hw_back_size + nxt->bi_hw_front_size))
+ BIOVEC_VIRT_OVERSIZE(bio->bi_hw_front_size + bio->bi_hw_back_size))
return 0;
- if (bio->bi_hw_back_size + nxt->bi_hw_front_size > q->max_segment_size)
+ if (bio->bi_size + nxt->bi_size > q->max_segment_size)
return 0;
return 1;
diff --git a/trunk/drivers/Kconfig b/trunk/drivers/Kconfig
index 7916f4b86d23..050323fd79e9 100644
--- a/trunk/drivers/Kconfig
+++ b/trunk/drivers/Kconfig
@@ -24,6 +24,8 @@ source "drivers/scsi/Kconfig"
source "drivers/ata/Kconfig"
+source "drivers/cdrom/Kconfig"
+
source "drivers/md/Kconfig"
source "drivers/message/fusion/Kconfig"
@@ -52,8 +54,6 @@ source "drivers/spi/Kconfig"
source "drivers/w1/Kconfig"
-source "drivers/power/Kconfig"
-
source "drivers/hwmon/Kconfig"
source "drivers/mfd/Kconfig"
diff --git a/trunk/drivers/Makefile b/trunk/drivers/Makefile
index 503d82569449..adad2f3d438a 100644
--- a/trunk/drivers/Makefile
+++ b/trunk/drivers/Makefile
@@ -61,7 +61,6 @@ obj-$(CONFIG_I2O) += message/
obj-$(CONFIG_RTC_LIB) += rtc/
obj-y += i2c/
obj-$(CONFIG_W1) += w1/
-obj-$(CONFIG_POWER_SUPPLY) += power/
obj-$(CONFIG_HWMON) += hwmon/
obj-$(CONFIG_PHONE) += telephony/
obj-$(CONFIG_MD) += md/
diff --git a/trunk/drivers/acorn/block/fd1772.c b/trunk/drivers/acorn/block/fd1772.c
index 423ed08fb6f7..674bf81c6e66 100644
--- a/trunk/drivers/acorn/block/fd1772.c
+++ b/trunk/drivers/acorn/block/fd1772.c
@@ -1246,7 +1246,7 @@ static void redo_fd_request(void)
del_timer(&motor_off_timer);
ReqCnt = 0;
- ReqCmd = rq_data_dir(CURRENT);
+ ReqCmd = CURRENT->cmd;
ReqBlock = CURRENT->sector;
ReqBuffer = CURRENT->buffer;
setup_req_params(drive);
diff --git a/trunk/drivers/acorn/block/mfmhd.c b/trunk/drivers/acorn/block/mfmhd.c
index d85520f78e68..689a4c3542ba 100644
--- a/trunk/drivers/acorn/block/mfmhd.c
+++ b/trunk/drivers/acorn/block/mfmhd.c
@@ -439,7 +439,7 @@ static void mfm_rw_intr(void)
a choice of command end or some data which is ready to be collected */
/* I think we have to transfer data while the interrupt line is on and its
not any other type of interrupt */
- if (rq_data_dir(CURRENT) == WRITE) {
+ if (CURRENT->cmd == WRITE) {
extern void hdc63463_writedma(void);
if ((hdc63463_dataleft <= 0) && (!(mfm_status & STAT_CED))) {
printk("mfm_rw_intr: Apparent DMA write request when no more to DMA\n");
@@ -799,7 +799,7 @@ static void issue_request(unsigned int block, unsigned int nsect,
raw_cmd.head = start_head;
raw_cmd.cylinder = track / p->heads;
raw_cmd.cmdtype = CURRENT->cmd;
- raw_cmd.cmdcode = rq_data_dir(CURRENT) == WRITE ? CMD_WD : CMD_RD;
+ raw_cmd.cmdcode = CURRENT->cmd == WRITE ? CMD_WD : CMD_RD;
raw_cmd.cmddata[0] = dev + 1; /* DAG: +1 to get US */
raw_cmd.cmddata[1] = raw_cmd.head;
raw_cmd.cmddata[2] = raw_cmd.cylinder >> 8;
@@ -830,7 +830,7 @@ static void issue_request(unsigned int block, unsigned int nsect,
hdc63463_dataleft = nsect * 256; /* Better way? */
DBG("mfm%c: %sing: CHS=%d/%d/%d, sectors=%d, buffer=0x%08lx (%p)\n",
- raw_cmd.dev + 'a', rq_data_dir(CURRENT) == READ ? "read" : "writ",
+ raw_cmd.dev + 'a', (CURRENT->cmd == READ) ? "read" : "writ",
raw_cmd.cylinder,
raw_cmd.head,
raw_cmd.sector, nsect, (unsigned long) Copy_buffer, CURRENT);
@@ -917,6 +917,13 @@ static void mfm_request(void)
DBG("mfm_request: block after offset=%d\n", block);
+ if (CURRENT->cmd != READ && CURRENT->cmd != WRITE) {
+ printk("unknown mfm-command %d\n", CURRENT->cmd);
+ end_request(CURRENT, 0);
+ Busy = 0;
+ printk("mfm: continue 4\n");
+ continue;
+ }
issue_request(block, nsect, CURRENT);
break;
diff --git a/trunk/drivers/block/Kconfig b/trunk/drivers/block/Kconfig
index 6e23af1ecbdb..b4c8319138b2 100644
--- a/trunk/drivers/block/Kconfig
+++ b/trunk/drivers/block/Kconfig
@@ -2,12 +2,9 @@
# Block device driver configuration
#
-menuconfig BLK_DEV
- bool "Block devices"
- depends on BLOCK
- default y
+if BLOCK
-if BLK_DEV
+menu "Block devices"
config BLK_DEV_FD
tristate "Normal floppy disk support"
@@ -59,9 +56,40 @@ config AMIGA_Z2RAM
To compile this driver as a module, choose M here: the
module will be called z2ram.
+config ATARI_ACSI
+ tristate "Atari ACSI support"
+ depends on ATARI && BROKEN
+ ---help---
+ This enables support for the Atari ACSI interface. The driver
+ supports hard disks and CD-ROMs, which have 512-byte sectors, or can
+ be switched to that mode. Due to the ACSI command format, only disks
+ up to 1 GB are supported. Special support for certain ACSI to SCSI
+ adapters, which could relax that, isn't included yet. The ACSI
+ driver is also the basis for certain other drivers for devices
+ attached to the ACSI bus: Atari SLM laser printer, BioNet-100
+ Ethernet, and PAMsNet Ethernet. If you want to use one of these
+ devices, you need ACSI support, too.
+
+ To compile this driver as a module, choose M here: the
+ module will be called acsi.
+
+comment "Some devices (e.g. CD jukebox) support multiple LUNs"
+ depends on ATARI && ATARI_ACSI
+
+config ACSI_MULTI_LUN
+ bool "Probe all LUNs on each ACSI device"
+ depends on ATARI_ACSI
+ help
+ If you have a ACSI device that supports more than one LUN (Logical
+ Unit Number), e.g. a CD jukebox, you should say Y here so that all
+ will be found by the ACSI driver. An ACSI device with multiple LUNs
+ acts logically like multiple ACSI devices. The vast majority of ACSI
+ devices have only one LUN, and so most people can say N here and
+ should in fact do so, because it is safer.
+
config ATARI_SLM
tristate "Atari SLM laser printer support"
- depends on ATARI
+ depends on ATARI && ATARI_ACSI!=n
help
If you have an Atari SLM laser printer, say Y to include support for
it in the kernel. Otherwise, say N. This driver is also available as
@@ -425,4 +453,6 @@ config ATA_OVER_ETH
source "drivers/s390/block/Kconfig"
-endif # BLK_DEV
+endmenu
+
+endif
diff --git a/trunk/drivers/block/Makefile b/trunk/drivers/block/Makefile
index e5f98acc5d52..dd88e33c1eb1 100644
--- a/trunk/drivers/block/Makefile
+++ b/trunk/drivers/block/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_MAC_FLOPPY) += swim3.o
obj-$(CONFIG_BLK_DEV_FD) += floppy.o
obj-$(CONFIG_AMIGA_FLOPPY) += amiflop.o
obj-$(CONFIG_ATARI_FLOPPY) += ataflop.o
+obj-$(CONFIG_ATARI_ACSI) += acsi.o
obj-$(CONFIG_ATARI_SLM) += acsi_slm.o
obj-$(CONFIG_AMIGA_Z2RAM) += z2ram.o
obj-$(CONFIG_BLK_DEV_RAM) += rd.o
diff --git a/trunk/drivers/block/acsi.c b/trunk/drivers/block/acsi.c
new file mode 100644
index 000000000000..e3d9152e231a
--- /dev/null
+++ b/trunk/drivers/block/acsi.c
@@ -0,0 +1,1825 @@
+/*
+ * acsi.c -- Device driver for Atari ACSI hard disks
+ *
+ * Copyright 1994 Roman Hodek
+ *
+ * Some parts are based on hd.c by Linus Torvalds
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ *
+ */
+
+/*
+ * Still to in this file:
+ * - If a command ends with an error status (!= 0), the following
+ * REQUEST SENSE commands (4 to fill the ST-DMA FIFO) are done by
+ * polling the _IRQ signal (not interrupt-driven). This should be
+ * avoided in future because it takes up a non-neglectible time in
+ * the interrupt service routine while interrupts are disabled.
+ * Maybe a timer interrupt will get lost :-(
+ */
+
+/*
+ * General notes:
+ *
+ * - All ACSI devices (disks, CD-ROMs, ...) use major number 28.
+ * Minors are organized like it is with SCSI: The upper 4 bits
+ * identify the device, the lower 4 bits the partition.
+ * The device numbers (the upper 4 bits) are given in the same
+ * order as the devices are found on the bus.
+ * - Up to 8 LUNs are supported for each target (if CONFIG_ACSI_MULTI_LUN
+ * is defined), but only a total of 16 devices (due to minor
+ * numbers...). Note that Atari allows only a maximum of 4 targets
+ * (i.e. controllers, not devices) on the ACSI bus!
+ * - A optimizing scheme similar to SCSI scatter-gather is implemented.
+ * - Removable media are supported. After a medium change to device
+ * is reinitialized (partition check etc.). Also, if the device
+ * knows the PREVENT/ALLOW MEDIUM REMOVAL command, the door should
+ * be locked and unlocked when mounting the first or unmounting the
+ * last filesystem on the device. The code is untested, because I
+ * don't have a removable hard disk.
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include /* for SCSI_IOCTL_GET_IDLUN */
+#include
+#include /* for HDIO_GETGEO */
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static void (*do_acsi)(void) = NULL;
+static struct request_queue *acsi_queue;
+#define QUEUE (acsi_queue)
+#define CURRENT elv_next_request(acsi_queue)
+
+#define DEBUG
+#undef DEBUG_DETECT
+#undef NO_WRITE
+
+#define MAX_ERRORS 8 /* Max read/write errors/sector */
+#define MAX_LUN 8 /* Max LUNs per target */
+#define MAX_DEV 16
+
+#define ACSI_BUFFER_SIZE (16*1024) /* "normal" ACSI buffer size */
+#define ACSI_BUFFER_MINSIZE (2048) /* min. buf size if ext. DMA */
+#define ACSI_BUFFER_SIZE_ORDER 2 /* order size for above */
+#define ACSI_BUFFER_MINSIZE_ORDER 0 /* order size for above */
+#define ACSI_BUFFER_SECTORS (ACSI_BUFFER_SIZE/512)
+
+#define ACSI_BUFFER_ORDER \
+ (ATARIHW_PRESENT(EXTD_DMA) ? \
+ ACSI_BUFFER_MINSIZE_ORDER : \
+ ACSI_BUFFER_SIZE_ORDER)
+
+#define ACSI_TIMEOUT (4*HZ)
+
+/* minimum delay between two commands */
+
+#define COMMAND_DELAY 500
+
+typedef enum {
+ NONE, HARDDISK, CDROM
+} ACSI_TYPE;
+
+struct acsi_info_struct {
+ ACSI_TYPE type; /* type of device */
+ unsigned target; /* target number */
+ unsigned lun; /* LUN in target controller */
+ unsigned removable : 1; /* Flag for removable media */
+ unsigned read_only : 1; /* Flag for read only devices */
+ unsigned old_atari_disk : 1; /* Is an old Atari disk */
+ unsigned changed : 1; /* Medium has been changed */
+ unsigned long size; /* #blocks */
+ int access_count;
+} acsi_info[MAX_DEV];
+
+/*
+ * SENSE KEYS
+ */
+
+#define NO_SENSE 0x00
+#define RECOVERED_ERROR 0x01
+#define NOT_READY 0x02
+#define MEDIUM_ERROR 0x03
+#define HARDWARE_ERROR 0x04
+#define ILLEGAL_REQUEST 0x05
+#define UNIT_ATTENTION 0x06
+#define DATA_PROTECT 0x07
+#define BLANK_CHECK 0x08
+#define COPY_ABORTED 0x0a
+#define ABORTED_COMMAND 0x0b
+#define VOLUME_OVERFLOW 0x0d
+#define MISCOMPARE 0x0e
+
+
+/*
+ * DEVICE TYPES
+ */
+
+#define TYPE_DISK 0x00
+#define TYPE_TAPE 0x01
+#define TYPE_WORM 0x04
+#define TYPE_ROM 0x05
+#define TYPE_MOD 0x07
+#define TYPE_NO_LUN 0x7f
+
+/* The data returned by MODE SENSE differ between the old Atari
+ * hard disks and SCSI disks connected to ACSI. In the following, both
+ * formats are defined and some macros to operate on them potably.
+ */
+
+typedef struct {
+ unsigned long dummy[2];
+ unsigned long sector_size;
+ unsigned char format_code;
+#define ATARI_SENSE_FORMAT_FIX 1
+#define ATARI_SENSE_FORMAT_CHNG 2
+ unsigned char cylinders_h;
+ unsigned char cylinders_l;
+ unsigned char heads;
+ unsigned char reduced_h;
+ unsigned char reduced_l;
+ unsigned char precomp_h;
+ unsigned char precomp_l;
+ unsigned char landing_zone;
+ unsigned char steprate;
+ unsigned char type;
+#define ATARI_SENSE_TYPE_FIXCHNG_MASK 4
+#define ATARI_SENSE_TYPE_SOFTHARD_MASK 8
+#define ATARI_SENSE_TYPE_FIX 4
+#define ATARI_SENSE_TYPE_CHNG 0
+#define ATARI_SENSE_TYPE_SOFT 0
+#define ATARI_SENSE_TYPE_HARD 8
+ unsigned char sectors;
+} ATARI_SENSE_DATA;
+
+#define ATARI_CAPACITY(sd) \
+ (((int)((sd).cylinders_h<<8)|(sd).cylinders_l) * \
+ (sd).heads * (sd).sectors)
+
+
+typedef struct {
+ unsigned char dummy1;
+ unsigned char medium_type;
+ unsigned char dummy2;
+ unsigned char descriptor_size;
+ unsigned long block_count;
+ unsigned long sector_size;
+ /* Page 0 data */
+ unsigned char page_code;
+ unsigned char page_size;
+ unsigned char page_flags;
+ unsigned char qualifier;
+} SCSI_SENSE_DATA;
+
+#define SCSI_CAPACITY(sd) ((sd).block_count & 0xffffff)
+
+
+typedef union {
+ ATARI_SENSE_DATA atari;
+ SCSI_SENSE_DATA scsi;
+} SENSE_DATA;
+
+#define SENSE_TYPE_UNKNOWN 0
+#define SENSE_TYPE_ATARI 1
+#define SENSE_TYPE_SCSI 2
+
+#define SENSE_TYPE(sd) \
+ (((sd).atari.dummy[0] == 8 && \
+ ((sd).atari.format_code == 1 || \
+ (sd).atari.format_code == 2)) ? SENSE_TYPE_ATARI : \
+ ((sd).scsi.dummy1 >= 11) ? SENSE_TYPE_SCSI : \
+ SENSE_TYPE_UNKNOWN)
+
+#define CAPACITY(sd) \
+ (SENSE_TYPE(sd) == SENSE_TYPE_ATARI ? \
+ ATARI_CAPACITY((sd).atari) : \
+ SCSI_CAPACITY((sd).scsi))
+
+#define SECTOR_SIZE(sd) \
+ (SENSE_TYPE(sd) == SENSE_TYPE_ATARI ? \
+ (sd).atari.sector_size : \
+ (sd).scsi.sector_size & 0xffffff)
+
+/* Default size if capacity cannot be determined (1 GByte) */
+#define DEFAULT_SIZE 0x1fffff
+
+#define CARTRCH_STAT(aip,buf) \
+ (aip->old_atari_disk ? \
+ (((buf)[0] & 0x7f) == 0x28) : \
+ ((((buf)[0] & 0x70) == 0x70) ? \
+ (((buf)[2] & 0x0f) == 0x06) : \
+ (((buf)[0] & 0x0f) == 0x06))) \
+
+/* These two are also exported to other drivers that work on the ACSI bus and
+ * need an ST-RAM buffer. */
+char *acsi_buffer;
+unsigned long phys_acsi_buffer;
+
+static int NDevices;
+
+static int CurrentNReq;
+static int CurrentNSect;
+static char *CurrentBuffer;
+
+static DEFINE_SPINLOCK(acsi_lock);
+
+
+#define SET_TIMER() mod_timer(&acsi_timer, jiffies + ACSI_TIMEOUT)
+#define CLEAR_TIMER() del_timer(&acsi_timer)
+
+static unsigned long STramMask;
+#define STRAM_ADDR(a) (((a) & STramMask) == 0)
+
+
+
+/* ACSI commands */
+
+static char tur_cmd[6] = { 0x00, 0, 0, 0, 0, 0 };
+static char modesense_cmd[6] = { 0x1a, 0, 0, 0, 24, 0 };
+static char modeselect_cmd[6] = { 0x15, 0, 0, 0, 12, 0 };
+static char inquiry_cmd[6] = { 0x12, 0, 0, 0,255, 0 };
+static char reqsense_cmd[6] = { 0x03, 0, 0, 0, 4, 0 };
+static char read_cmd[6] = { 0x08, 0, 0, 0, 0, 0 };
+static char write_cmd[6] = { 0x0a, 0, 0, 0, 0, 0 };
+static char pa_med_rem_cmd[6] = { 0x1e, 0, 0, 0, 0, 0 };
+
+#define CMDSET_TARG_LUN(cmd,targ,lun) \
+ do { \
+ cmd[0] = (cmd[0] & ~0xe0) | (targ)<<5; \
+ cmd[1] = (cmd[1] & ~0xe0) | (lun)<<5; \
+ } while(0)
+
+#define CMDSET_BLOCK(cmd,blk) \
+ do { \
+ unsigned long __blk = (blk); \
+ cmd[3] = __blk; __blk >>= 8; \
+ cmd[2] = __blk; __blk >>= 8; \
+ cmd[1] = (cmd[1] & 0xe0) | (__blk & 0x1f); \
+ } while(0)
+
+#define CMDSET_LEN(cmd,len) \
+ do { \
+ cmd[4] = (len); \
+ } while(0)
+
+/* ACSI errors (from REQUEST SENSE); There are two tables, one for the
+ * old Atari disks and one for SCSI on ACSI disks.
+ */
+
+struct acsi_error {
+ unsigned char code;
+ const char *text;
+} atari_acsi_errors[] = {
+ { 0x00, "No error (??)" },
+ { 0x01, "No index pulses" },
+ { 0x02, "Seek not complete" },
+ { 0x03, "Write fault" },
+ { 0x04, "Drive not ready" },
+ { 0x06, "No Track 00 signal" },
+ { 0x10, "ECC error in ID field" },
+ { 0x11, "Uncorrectable data error" },
+ { 0x12, "ID field address mark not found" },
+ { 0x13, "Data field address mark not found" },
+ { 0x14, "Record not found" },
+ { 0x15, "Seek error" },
+ { 0x18, "Data check in no retry mode" },
+ { 0x19, "ECC error during verify" },
+ { 0x1a, "Access to bad block" },
+ { 0x1c, "Unformatted or bad format" },
+ { 0x20, "Invalid command" },
+ { 0x21, "Invalid block address" },
+ { 0x23, "Volume overflow" },
+ { 0x24, "Invalid argument" },
+ { 0x25, "Invalid drive number" },
+ { 0x26, "Byte zero parity check" },
+ { 0x28, "Cartride changed" },
+ { 0x2c, "Error count overflow" },
+ { 0x30, "Controller selftest failed" }
+},
+
+ scsi_acsi_errors[] = {
+ { 0x00, "No error (??)" },
+ { 0x01, "Recovered error" },
+ { 0x02, "Drive not ready" },
+ { 0x03, "Uncorrectable medium error" },
+ { 0x04, "Hardware error" },
+ { 0x05, "Illegal request" },
+ { 0x06, "Unit attention (Reset or cartridge changed)" },
+ { 0x07, "Data protection" },
+ { 0x08, "Blank check" },
+ { 0x0b, "Aborted Command" },
+ { 0x0d, "Volume overflow" }
+};
+
+
+
+/***************************** Prototypes *****************************/
+
+static int acsicmd_dma( const char *cmd, char *buffer, int blocks, int
+ rwflag, int enable);
+static int acsi_reqsense( char *buffer, int targ, int lun);
+static void acsi_print_error(const unsigned char *errblk, struct acsi_info_struct *aip);
+static irqreturn_t acsi_interrupt (int irq, void *data);
+static void unexpected_acsi_interrupt( void );
+static void bad_rw_intr( void );
+static void read_intr( void );
+static void write_intr( void);
+static void acsi_times_out( unsigned long dummy );
+static void copy_to_acsibuffer( void );
+static void copy_from_acsibuffer( void );
+static void do_end_requests( void );
+static void do_acsi_request( request_queue_t * );
+static void redo_acsi_request( void );
+static int acsi_ioctl( struct inode *inode, struct file *file, unsigned int
+ cmd, unsigned long arg );
+static int acsi_open( struct inode * inode, struct file * filp );
+static int acsi_release( struct inode * inode, struct file * file );
+static void acsi_prevent_removal(struct acsi_info_struct *aip, int flag );
+static int acsi_change_blk_size( int target, int lun);
+static int acsi_mode_sense( int target, int lun, SENSE_DATA *sd );
+static int acsi_revalidate (struct gendisk *disk);
+
+/************************* End of Prototypes **************************/
+
+
+DEFINE_TIMER(acsi_timer, acsi_times_out, 0, 0);
+
+
+#ifdef CONFIG_ATARI_SLM
+
+extern int attach_slm( int target, int lun );
+extern int slm_init( void );
+
+#endif
+
+
+
+/***********************************************************************
+ *
+ * ACSI primitives
+ *
+ **********************************************************************/
+
+
+/*
+ * The following two functions wait for _IRQ to become Low or High,
+ * resp., with a timeout. The 'timeout' parameter is in jiffies
+ * (10ms).
+ * If the functions are called with timer interrupts on (int level <
+ * 6), the timeout is based on the 'jiffies' variable to provide exact
+ * timeouts for device probing etc.
+ * If interrupts are disabled, the number of tries is based on the
+ * 'loops_per_jiffy' variable. A rough estimation is sufficient here...
+ */
+
+#define INT_LEVEL \
+ ({ unsigned __sr; \
+ __asm__ __volatile__ ( "movew %/sr,%0" : "=dm" (__sr) ); \
+ (__sr >> 8) & 7; \
+ })
+
+int acsi_wait_for_IRQ( unsigned timeout )
+
+{
+ if (INT_LEVEL < 6) {
+ unsigned long maxjif = jiffies + timeout;
+ while (time_before(jiffies, maxjif))
+ if (!(mfp.par_dt_reg & 0x20)) return( 1 );
+ }
+ else {
+ long tries = loops_per_jiffy / 8 * timeout;
+ while( --tries >= 0 )
+ if (!(mfp.par_dt_reg & 0x20)) return( 1 );
+ }
+ return( 0 ); /* timeout! */
+}
+
+
+int acsi_wait_for_noIRQ( unsigned timeout )
+
+{
+ if (INT_LEVEL < 6) {
+ unsigned long maxjif = jiffies + timeout;
+ while (time_before(jiffies, maxjif))
+ if (mfp.par_dt_reg & 0x20) return( 1 );
+ }
+ else {
+ long tries = loops_per_jiffy * timeout / 8;
+ while( tries-- >= 0 )
+ if (mfp.par_dt_reg & 0x20) return( 1 );
+ }
+ return( 0 ); /* timeout! */
+}
+
+static struct timeval start_time;
+
+void
+acsi_delay_start(void)
+{
+ do_gettimeofday(&start_time);
+}
+
+/* wait from acsi_delay_start to now usec (<1E6) usec */
+
+void
+acsi_delay_end(long usec)
+{
+ struct timeval end_time;
+ long deltau,deltas;
+ do_gettimeofday(&end_time);
+ deltau=end_time.tv_usec - start_time.tv_usec;
+ deltas=end_time.tv_sec - start_time.tv_sec;
+ if (deltas > 1 || deltas < 0)
+ return;
+ if (deltas > 0)
+ deltau += 1000*1000;
+ if (deltau >= usec)
+ return;
+ udelay(usec-deltau);
+}
+
+/* acsicmd_dma() sends an ACSI command and sets up the DMA to transfer
+ * 'blocks' blocks of 512 bytes from/to 'buffer'.
+ * Because the _IRQ signal is used for handshaking the command bytes,
+ * the ACSI interrupt has to be disabled in this function. If the end
+ * of the operation should be signalled by a real interrupt, it has to be
+ * reenabled afterwards.
+ */
+
+static int acsicmd_dma( const char *cmd, char *buffer, int blocks, int rwflag, int enable)
+
+{ unsigned long flags, paddr;
+ int i;
+
+#ifdef NO_WRITE
+ if (rwflag || *cmd == 0x0a) {
+ printk( "ACSI: Write commands disabled!\n" );
+ return( 0 );
+ }
+#endif
+
+ rwflag = rwflag ? 0x100 : 0;
+ paddr = virt_to_phys( buffer );
+
+ acsi_delay_end(COMMAND_DELAY);
+ DISABLE_IRQ();
+
+ local_irq_save(flags);
+ /* Low on A1 */
+ dma_wd.dma_mode_status = 0x88 | rwflag;
+ MFPDELAY();
+
+ /* set DMA address */
+ dma_wd.dma_lo = (unsigned char)paddr;
+ paddr >>= 8;
+ MFPDELAY();
+ dma_wd.dma_md = (unsigned char)paddr;
+ paddr >>= 8;
+ MFPDELAY();
+ if (ATARIHW_PRESENT(EXTD_DMA))
+ st_dma_ext_dmahi = (unsigned short)paddr;
+ else
+ dma_wd.dma_hi = (unsigned char)paddr;
+ MFPDELAY();
+ local_irq_restore(flags);
+
+ /* send the command bytes except the last */
+ for( i = 0; i < 5; ++i ) {
+ DMA_LONG_WRITE( *cmd++, 0x8a | rwflag );
+ udelay(20);
+ if (!acsi_wait_for_IRQ( HZ/2 )) return( 0 ); /* timeout */
+ }
+
+ /* Clear FIFO and switch DMA to correct direction */
+ dma_wd.dma_mode_status = 0x92 | (rwflag ^ 0x100);
+ MFPDELAY();
+ dma_wd.dma_mode_status = 0x92 | rwflag;
+ MFPDELAY();
+
+ /* How many sectors for DMA */
+ dma_wd.fdc_acces_seccount = blocks;
+ MFPDELAY();
+
+ /* send last command byte */
+ dma_wd.dma_mode_status = 0x8a | rwflag;
+ MFPDELAY();
+ DMA_LONG_WRITE( *cmd++, 0x0a | rwflag );
+ if (enable)
+ ENABLE_IRQ();
+ udelay(80);
+
+ return( 1 );
+}
+
+
+/*
+ * acsicmd_nodma() sends an ACSI command that requires no DMA.
+ */
+
+int acsicmd_nodma( const char *cmd, int enable)
+
+{ int i;
+
+ acsi_delay_end(COMMAND_DELAY);
+ DISABLE_IRQ();
+
+ /* send first command byte */
+ dma_wd.dma_mode_status = 0x88;
+ MFPDELAY();
+ DMA_LONG_WRITE( *cmd++, 0x8a );
+ udelay(20);
+ if (!acsi_wait_for_IRQ( HZ/2 )) return( 0 ); /* timeout */
+
+ /* send the intermediate command bytes */
+ for( i = 0; i < 4; ++i ) {
+ DMA_LONG_WRITE( *cmd++, 0x8a );
+ udelay(20);
+ if (!acsi_wait_for_IRQ( HZ/2 )) return( 0 ); /* timeout */
+ }
+
+ /* send last command byte */
+ DMA_LONG_WRITE( *cmd++, 0x0a );
+ if (enable)
+ ENABLE_IRQ();
+ udelay(80);
+
+ return( 1 );
+ /* Note that the ACSI interrupt is still disabled after this
+ * function. If you want to get the IRQ delivered, enable it manually!
+ */
+}
+
+
+static int acsi_reqsense( char *buffer, int targ, int lun)
+
+{
+ CMDSET_TARG_LUN( reqsense_cmd, targ, lun);
+ if (!acsicmd_dma( reqsense_cmd, buffer, 1, 0, 0 )) return( 0 );
+ if (!acsi_wait_for_IRQ( 10 )) return( 0 );
+ acsi_getstatus();
+ if (!acsicmd_nodma( reqsense_cmd, 0 )) return( 0 );
+ if (!acsi_wait_for_IRQ( 10 )) return( 0 );
+ acsi_getstatus();
+ if (!acsicmd_nodma( reqsense_cmd, 0 )) return( 0 );
+ if (!acsi_wait_for_IRQ( 10 )) return( 0 );
+ acsi_getstatus();
+ if (!acsicmd_nodma( reqsense_cmd, 0 )) return( 0 );
+ if (!acsi_wait_for_IRQ( 10 )) return( 0 );
+ acsi_getstatus();
+ dma_cache_maintenance( virt_to_phys(buffer), 16, 0 );
+
+ return( 1 );
+}
+
+
+/*
+ * ACSI status phase: get the status byte from the bus
+ *
+ * I've seen several times that a 0xff status is read, propably due to
+ * a timing error. In this case, the procedure is repeated after the
+ * next _IRQ edge.
+ */
+
+int acsi_getstatus( void )
+
+{ int status;
+
+ DISABLE_IRQ();
+ for(;;) {
+ if (!acsi_wait_for_IRQ( 100 )) {
+ acsi_delay_start();
+ return( -1 );
+ }
+ dma_wd.dma_mode_status = 0x8a;
+ MFPDELAY();
+ status = dma_wd.fdc_acces_seccount;
+ if (status != 0xff) break;
+#ifdef DEBUG
+ printk("ACSI: skipping 0xff status byte\n" );
+#endif
+ udelay(40);
+ acsi_wait_for_noIRQ( 20 );
+ }
+ dma_wd.dma_mode_status = 0x80;
+ udelay(40);
+ acsi_wait_for_noIRQ( 20 );
+
+ acsi_delay_start();
+ return( status & 0x1f ); /* mask of the device# */
+}
+
+
+#if (defined(CONFIG_ATARI_SLM) || defined(CONFIG_ATARI_SLM_MODULE))
+
+/* Receive data in an extended status phase. Needed by SLM printer. */
+
+int acsi_extstatus( char *buffer, int cnt )
+
+{ int status;
+
+ DISABLE_IRQ();
+ udelay(80);
+ while( cnt-- > 0 ) {
+ if (!acsi_wait_for_IRQ( 40 )) return( 0 );
+ dma_wd.dma_mode_status = 0x8a;
+ MFPDELAY();
+ status = dma_wd.fdc_acces_seccount;
+ MFPDELAY();
+ *buffer++ = status & 0xff;
+ udelay(40);
+ }
+ return( 1 );
+}
+
+
+/* Finish an extended status phase */
+
+void acsi_end_extstatus( void )
+
+{
+ dma_wd.dma_mode_status = 0x80;
+ udelay(40);
+ acsi_wait_for_noIRQ( 20 );
+ acsi_delay_start();
+}
+
+
+/* Send data in an extended command phase */
+
+int acsi_extcmd( unsigned char *buffer, int cnt )
+
+{
+ while( cnt-- > 0 ) {
+ DMA_LONG_WRITE( *buffer++, 0x8a );
+ udelay(20);
+ if (!acsi_wait_for_IRQ( HZ/2 )) return( 0 ); /* timeout */
+ }
+ return( 1 );
+}
+
+#endif
+
+
+static void acsi_print_error(const unsigned char *errblk, struct acsi_info_struct *aip)
+
+{ int atari_err, i, errcode;
+ struct acsi_error *arr;
+
+ atari_err = aip->old_atari_disk;
+ if (atari_err)
+ errcode = errblk[0] & 0x7f;
+ else
+ if ((errblk[0] & 0x70) == 0x70)
+ errcode = errblk[2] & 0x0f;
+ else
+ errcode = errblk[0] & 0x0f;
+
+ printk( KERN_ERR "ACSI error 0x%02x", errcode );
+
+ if (errblk[0] & 0x80)
+ printk( " for sector %d",
+ ((errblk[1] & 0x1f) << 16) |
+ (errblk[2] << 8) | errblk[0] );
+
+ arr = atari_err ? atari_acsi_errors : scsi_acsi_errors;
+ i = atari_err ? sizeof(atari_acsi_errors)/sizeof(*atari_acsi_errors) :
+ sizeof(scsi_acsi_errors)/sizeof(*scsi_acsi_errors);
+
+ for( --i; i >= 0; --i )
+ if (arr[i].code == errcode) break;
+ if (i >= 0)
+ printk( ": %s\n", arr[i].text );
+}
+
+/*******************************************************************
+ *
+ * ACSI interrupt routine
+ * Test, if this is a ACSI interrupt and call the irq handler
+ * Otherwise ignore this interrupt.
+ *
+ *******************************************************************/
+
+static irqreturn_t acsi_interrupt(int irq, void *data )
+
+{ void (*acsi_irq_handler)(void) = do_acsi;
+
+ do_acsi = NULL;
+ CLEAR_TIMER();
+
+ if (!acsi_irq_handler)
+ acsi_irq_handler = unexpected_acsi_interrupt;
+ acsi_irq_handler();
+ return IRQ_HANDLED;
+}
+
+
+/******************************************************************
+ *
+ * The Interrupt handlers
+ *
+ *******************************************************************/
+
+
+static void unexpected_acsi_interrupt( void )
+
+{
+ printk( KERN_WARNING "Unexpected ACSI interrupt\n" );
+}
+
+
+/* This function is called in case of errors. Because we cannot reset
+ * the ACSI bus or a single device, there is no other choice than
+ * retrying several times :-(
+ */
+
+static void bad_rw_intr( void )
+
+{
+ if (!CURRENT)
+ return;
+
+ if (++CURRENT->errors >= MAX_ERRORS)
+ end_request(CURRENT, 0);
+ /* Otherwise just retry */
+}
+
+
+static void read_intr( void )
+
+{ int status;
+
+ status = acsi_getstatus();
+ if (status != 0) {
+ struct gendisk *disk = CURRENT->rq_disk;
+ struct acsi_info_struct *aip = disk->private_data;
+ printk(KERN_ERR "%s: ", disk->disk_name);
+ if (!acsi_reqsense(acsi_buffer, aip->target, aip->lun))
+ printk( "ACSI error and REQUEST SENSE failed (status=0x%02x)\n", status );
+ else {
+ acsi_print_error(acsi_buffer, aip);
+ if (CARTRCH_STAT(aip, acsi_buffer))
+ aip->changed = 1;
+ }
+ ENABLE_IRQ();
+ bad_rw_intr();
+ redo_acsi_request();
+ return;
+ }
+
+ dma_cache_maintenance( virt_to_phys(CurrentBuffer), CurrentNSect*512, 0 );
+ if (CurrentBuffer == acsi_buffer)
+ copy_from_acsibuffer();
+
+ do_end_requests();
+ redo_acsi_request();
+}
+
+
+static void write_intr(void)
+
+{ int status;
+
+ status = acsi_getstatus();
+ if (status != 0) {
+ struct gendisk *disk = CURRENT->rq_disk;
+ struct acsi_info_struct *aip = disk->private_data;
+ printk( KERN_ERR "%s: ", disk->disk_name);
+ if (!acsi_reqsense( acsi_buffer, aip->target, aip->lun))
+ printk( "ACSI error and REQUEST SENSE failed (status=0x%02x)\n", status );
+ else {
+ acsi_print_error(acsi_buffer, aip);
+ if (CARTRCH_STAT(aip, acsi_buffer))
+ aip->changed = 1;
+ }
+ bad_rw_intr();
+ redo_acsi_request();
+ return;
+ }
+
+ do_end_requests();
+ redo_acsi_request();
+}
+
+
+static void acsi_times_out( unsigned long dummy )
+
+{
+ DISABLE_IRQ();
+ if (!do_acsi) return;
+
+ do_acsi = NULL;
+ printk( KERN_ERR "ACSI timeout\n" );
+ if (!CURRENT)
+ return;
+ if (++CURRENT->errors >= MAX_ERRORS) {
+#ifdef DEBUG
+ printk( KERN_ERR "ACSI: too many errors.\n" );
+#endif
+ end_request(CURRENT, 0);
+ }
+
+ redo_acsi_request();
+}
+
+
+
+/***********************************************************************
+ *
+ * Scatter-gather utility functions
+ *
+ ***********************************************************************/
+
+
+static void copy_to_acsibuffer( void )
+
+{ int i;
+ char *src, *dst;
+ struct buffer_head *bh;
+
+ src = CURRENT->buffer;
+ dst = acsi_buffer;
+ bh = CURRENT->bh;
+
+ if (!bh)
+ memcpy( dst, src, CurrentNSect*512 );
+ else
+ for( i = 0; i < CurrentNReq; ++i ) {
+ memcpy( dst, src, bh->b_size );
+ dst += bh->b_size;
+ if ((bh = bh->b_reqnext))
+ src = bh->b_data;
+ }
+}
+
+
+static void copy_from_acsibuffer( void )
+
+{ int i;
+ char *src, *dst;
+ struct buffer_head *bh;
+
+ dst = CURRENT->buffer;
+ src = acsi_buffer;
+ bh = CURRENT->bh;
+
+ if (!bh)
+ memcpy( dst, src, CurrentNSect*512 );
+ else
+ for( i = 0; i < CurrentNReq; ++i ) {
+ memcpy( dst, src, bh->b_size );
+ src += bh->b_size;
+ if ((bh = bh->b_reqnext))
+ dst = bh->b_data;
+ }
+}
+
+
+static void do_end_requests( void )
+
+{ int i, n;
+
+ if (!CURRENT->bh) {
+ CURRENT->nr_sectors -= CurrentNSect;
+ CURRENT->current_nr_sectors -= CurrentNSect;
+ CURRENT->sector += CurrentNSect;
+ if (CURRENT->nr_sectors == 0)
+ end_request(CURRENT, 1);
+ }
+ else {
+ for( i = 0; i < CurrentNReq; ++i ) {
+ n = CURRENT->bh->b_size >> 9;
+ CURRENT->nr_sectors -= n;
+ CURRENT->current_nr_sectors -= n;
+ CURRENT->sector += n;
+ end_request(CURRENT, 1);
+ }
+ }
+}
+
+
+
+
+/***********************************************************************
+ *
+ * do_acsi_request and friends
+ *
+ ***********************************************************************/
+
+static void do_acsi_request( request_queue_t * q )
+
+{
+ stdma_lock( acsi_interrupt, NULL );
+ redo_acsi_request();
+}
+
+
+static void redo_acsi_request( void )
+{
+ unsigned block, target, lun, nsect;
+ char *buffer;
+ unsigned long pbuffer;
+ struct buffer_head *bh;
+ struct gendisk *disk;
+ struct acsi_info_struct *aip;
+
+ repeat:
+ CLEAR_TIMER();
+
+ if (do_acsi)
+ return;
+
+ if (!CURRENT) {
+ do_acsi = NULL;
+ ENABLE_IRQ();
+ stdma_release();
+ return;
+ }
+
+ disk = CURRENT->rq_disk;
+ aip = disk->private_data;
+ if (CURRENT->bh) {
+ if (!CURRENT->bh && !buffer_locked(CURRENT->bh))
+ panic("ACSI: block not locked");
+ }
+
+ block = CURRENT->sector;
+ if (block+CURRENT->nr_sectors >= get_capacity(disk)) {
+#ifdef DEBUG
+ printk( "%s: attempted access for blocks %d...%ld past end of device at block %ld.\n",
+ disk->disk_name,
+ block, block + CURRENT->nr_sectors - 1,
+ get_capacity(disk));
+#endif
+ end_request(CURRENT, 0);
+ goto repeat;
+ }
+ if (aip->changed) {
+ printk( KERN_NOTICE "%s: request denied because cartridge has "
+ "been changed.\n", disk->disk_name);
+ end_request(CURRENT, 0);
+ goto repeat;
+ }
+
+ target = aip->target;
+ lun = aip->lun;
+
+ /* Find out how many sectors should be transferred from/to
+ * consecutive buffers and thus can be done with a single command.
+ */
+ buffer = CURRENT->buffer;
+ pbuffer = virt_to_phys(buffer);
+ nsect = CURRENT->current_nr_sectors;
+ CurrentNReq = 1;
+
+ if ((bh = CURRENT->bh) && bh != CURRENT->bhtail) {
+ if (!STRAM_ADDR(pbuffer)) {
+ /* If transfer is done via the ACSI buffer anyway, we can
+ * assemble as much bh's as fit in the buffer.
+ */
+ while( (bh = bh->b_reqnext) ) {
+ if (nsect + (bh->b_size>>9) > ACSI_BUFFER_SECTORS) break;
+ nsect += bh->b_size >> 9;
+ ++CurrentNReq;
+ if (bh == CURRENT->bhtail) break;
+ }
+ buffer = acsi_buffer;
+ pbuffer = phys_acsi_buffer;
+ }
+ else {
+ unsigned long pendadr, pnewadr;
+ pendadr = pbuffer + nsect*512;
+ while( (bh = bh->b_reqnext) ) {
+ pnewadr = virt_to_phys(bh->b_data);
+ if (!STRAM_ADDR(pnewadr) || pendadr != pnewadr) break;
+ nsect += bh->b_size >> 9;
+ pendadr = pnewadr + bh->b_size;
+ ++CurrentNReq;
+ if (bh == CURRENT->bhtail) break;
+ }
+ }
+ }
+ else {
+ if (!STRAM_ADDR(pbuffer)) {
+ buffer = acsi_buffer;
+ pbuffer = phys_acsi_buffer;
+ if (nsect > ACSI_BUFFER_SECTORS)
+ nsect = ACSI_BUFFER_SECTORS;
+ }
+ }
+ CurrentBuffer = buffer;
+ CurrentNSect = nsect;
+
+ if (rq_data_dir(CURRENT) == WRITE) {
+ CMDSET_TARG_LUN( write_cmd, target, lun );
+ CMDSET_BLOCK( write_cmd, block );
+ CMDSET_LEN( write_cmd, nsect );
+ if (buffer == acsi_buffer)
+ copy_to_acsibuffer();
+ dma_cache_maintenance( pbuffer, nsect*512, 1 );
+ do_acsi = write_intr;
+ if (!acsicmd_dma( write_cmd, buffer, nsect, 1, 1)) {
+ do_acsi = NULL;
+ printk( KERN_ERR "ACSI (write): Timeout in command block\n" );
+ bad_rw_intr();
+ goto repeat;
+ }
+ SET_TIMER();
+ return;
+ }
+ if (rq_data_dir(CURRENT) == READ) {
+ CMDSET_TARG_LUN( read_cmd, target, lun );
+ CMDSET_BLOCK( read_cmd, block );
+ CMDSET_LEN( read_cmd, nsect );
+ do_acsi = read_intr;
+ if (!acsicmd_dma( read_cmd, buffer, nsect, 0, 1)) {
+ do_acsi = NULL;
+ printk( KERN_ERR "ACSI (read): Timeout in command block\n" );
+ bad_rw_intr();
+ goto repeat;
+ }
+ SET_TIMER();
+ return;
+ }
+ panic("unknown ACSI command");
+}
+
+
+
+/***********************************************************************
+ *
+ * Misc functions: ioctl, open, release, check_change, ...
+ *
+ ***********************************************************************/
+
+static int acsi_getgeo(struct block_device *bdev, struct hd_geometry *geo)
+{
+ struct acsi_info_struct *aip = bdev->bd_disk->private_data;
+
+ /*
+ * Just fake some geometry here, it's nonsense anyway
+ * To make it easy, use Adaptec's usual 64/32 mapping
+ */
+ geo->heads = 64;
+ geo->sectors = 32;
+ geo->cylinders = aip->size >> 11;
+ return 0;
+}
+
+static int acsi_ioctl( struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg )
+{
+ struct gendisk *disk = inode->i_bdev->bd_disk;
+ struct acsi_info_struct *aip = disk->private_data;
+ switch (cmd) {
+ case SCSI_IOCTL_GET_IDLUN:
+ /* SCSI compatible GET_IDLUN call to get target's ID and LUN number */
+ put_user( aip->target | (aip->lun << 8),
+ &((Scsi_Idlun *) arg)->dev_id );
+ put_user( 0, &((Scsi_Idlun *) arg)->host_unique_id );
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+
+/*
+ * Open a device, check for read-only and lock the medium if it is
+ * removable.
+ *
+ * Changes by Martin Rogge, 9th Aug 1995:
+ * Check whether check_disk_change (and therefore revalidate_acsidisk)
+ * was successful. They fail when there is no medium in the drive.
+ *
+ * The problem of media being changed during an operation can be
+ * ignored because of the prevent_removal code.
+ *
+ * Added check for the validity of the device number.
+ *
+ */
+
+static int acsi_open( struct inode * inode, struct file * filp )
+{
+ struct gendisk *disk = inode->i_bdev->bd_disk;
+ struct acsi_info_struct *aip = disk->private_data;
+
+ if (aip->access_count == 0 && aip->removable) {
+#if 0
+ aip->changed = 1; /* safety first */
+#endif
+ check_disk_change( inode->i_bdev );
+ if (aip->changed) /* revalidate was not successful (no medium) */
+ return -ENXIO;
+ acsi_prevent_removal(aip, 1);
+ }
+ aip->access_count++;
+
+ if (filp && filp->f_mode) {
+ check_disk_change( inode->i_bdev );
+ if (filp->f_mode & 2) {
+ if (aip->read_only) {
+ acsi_release( inode, filp );
+ return -EROFS;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Releasing a block device means we sync() it, so that it can safely
+ * be forgotten about...
+ */
+
+static int acsi_release( struct inode * inode, struct file * file )
+{
+ struct gendisk *disk = inode->i_bdev->bd_disk;
+ struct acsi_info_struct *aip = disk->private_data;
+ if (--aip->access_count == 0 && aip->removable)
+ acsi_prevent_removal(aip, 0);
+ return( 0 );
+}
+
+/*
+ * Prevent or allow a media change for removable devices.
+ */
+
+static void acsi_prevent_removal(struct acsi_info_struct *aip, int flag)
+{
+ stdma_lock( NULL, NULL );
+
+ CMDSET_TARG_LUN(pa_med_rem_cmd, aip->target, aip->lun);
+ CMDSET_LEN( pa_med_rem_cmd, flag );
+
+ if (acsicmd_nodma(pa_med_rem_cmd, 0) && acsi_wait_for_IRQ(3*HZ))
+ acsi_getstatus();
+ /* Do not report errors -- some devices may not know this command. */
+
+ ENABLE_IRQ();
+ stdma_release();
+}
+
+static int acsi_media_change(struct gendisk *disk)
+{
+ struct acsi_info_struct *aip = disk->private_data;
+
+ if (!aip->removable)
+ return 0;
+
+ if (aip->changed)
+ /* We can be sure that the medium has been changed -- REQUEST
+ * SENSE has reported this earlier.
+ */
+ return 1;
+
+ /* If the flag isn't set, make a test by reading block 0.
+ * If errors happen, it seems to be better to say "changed"...
+ */
+ stdma_lock( NULL, NULL );
+ CMDSET_TARG_LUN(read_cmd, aip->target, aip->lun);
+ CMDSET_BLOCK( read_cmd, 0 );
+ CMDSET_LEN( read_cmd, 1 );
+ if (acsicmd_dma(read_cmd, acsi_buffer, 1, 0, 0) &&
+ acsi_wait_for_IRQ(3*HZ)) {
+ if (acsi_getstatus()) {
+ if (acsi_reqsense(acsi_buffer, aip->target, aip->lun)) {
+ if (CARTRCH_STAT(aip, acsi_buffer))
+ aip->changed = 1;
+ }
+ else {
+ printk( KERN_ERR "%s: REQUEST SENSE failed in test for "
+ "medium change; assuming a change\n", disk->disk_name );
+ aip->changed = 1;
+ }
+ }
+ }
+ else {
+ printk( KERN_ERR "%s: Test for medium changed timed out; "
+ "assuming a change\n", disk->disk_name);
+ aip->changed = 1;
+ }
+ ENABLE_IRQ();
+ stdma_release();
+
+ /* Now, after reading a block, the changed status is surely valid. */
+ return aip->changed;
+}
+
+
+static int acsi_change_blk_size( int target, int lun)
+
+{ int i;
+
+ for (i=0; i<12; i++)
+ acsi_buffer[i] = 0;
+
+ acsi_buffer[3] = 8;
+ acsi_buffer[10] = 2;
+ CMDSET_TARG_LUN( modeselect_cmd, target, lun);
+
+ if (!acsicmd_dma( modeselect_cmd, acsi_buffer, 1,1,0) ||
+ !acsi_wait_for_IRQ( 3*HZ ) ||
+ acsi_getstatus() != 0 ) {
+ return(0);
+ }
+ return(1);
+}
+
+
+static int acsi_mode_sense( int target, int lun, SENSE_DATA *sd )
+
+{
+ int page;
+
+ CMDSET_TARG_LUN( modesense_cmd, target, lun );
+ for (page=0; page<4; page++) {
+ modesense_cmd[2] = page;
+ if (!acsicmd_dma( modesense_cmd, acsi_buffer, 1, 0, 0 ) ||
+ !acsi_wait_for_IRQ( 3*HZ ) ||
+ acsi_getstatus())
+ continue;
+
+ /* read twice to jump over the second 16-byte border! */
+ udelay(300);
+ if (acsi_wait_for_noIRQ( 20 ) &&
+ acsicmd_nodma( modesense_cmd, 0 ) &&
+ acsi_wait_for_IRQ( 3*HZ ) &&
+ acsi_getstatus() == 0)
+ break;
+ }
+ if (page == 4) {
+ return(0);
+ }
+
+ dma_cache_maintenance( phys_acsi_buffer, sizeof(SENSE_DATA), 0 );
+ *sd = *(SENSE_DATA *)acsi_buffer;
+
+ /* Validity check, depending on type of data */
+
+ switch( SENSE_TYPE(*sd) ) {
+
+ case SENSE_TYPE_ATARI:
+ if (CAPACITY(*sd) == 0)
+ goto invalid_sense;
+ break;
+
+ case SENSE_TYPE_SCSI:
+ if (sd->scsi.descriptor_size != 8)
+ goto invalid_sense;
+ break;
+
+ case SENSE_TYPE_UNKNOWN:
+
+ printk( KERN_ERR "ACSI target %d, lun %d: Cannot interpret "
+ "sense data\n", target, lun );
+
+ invalid_sense:
+
+#ifdef DEBUG
+ { int i;
+ printk( "Mode sense data for ACSI target %d, lun %d seem not valid:",
+ target, lun );
+ for( i = 0; i < sizeof(SENSE_DATA); ++i )
+ printk( "%02x ", (unsigned char)acsi_buffer[i] );
+ printk( "\n" );
+ }
+#endif
+ return( 0 );
+ }
+
+ return( 1 );
+}
+
+
+
+/*******************************************************************
+ *
+ * Initialization
+ *
+ ********************************************************************/
+
+
+extern struct block_device_operations acsi_fops;
+
+static struct gendisk *acsi_gendisk[MAX_DEV];
+
+#define MAX_SCSI_DEVICE_CODE 10
+
+static const char *const scsi_device_types[MAX_SCSI_DEVICE_CODE] =
+{
+ "Direct-Access ",
+ "Sequential-Access",
+ "Printer ",
+ "Processor ",
+ "WORM ",
+ "CD-ROM ",
+ "Scanner ",
+ "Optical Device ",
+ "Medium Changer ",
+ "Communications "
+};
+
+static void print_inquiry(unsigned char *data)
+{
+ int i;
+
+ printk(KERN_INFO " Vendor: ");
+ for (i = 8; i < 16; i++)
+ {
+ if (data[i] >= 0x20 && i < data[4] + 5)
+ printk("%c", data[i]);
+ else
+ printk(" ");
+ }
+
+ printk(" Model: ");
+ for (i = 16; i < 32; i++)
+ {
+ if (data[i] >= 0x20 && i < data[4] + 5)
+ printk("%c", data[i]);
+ else
+ printk(" ");
+ }
+
+ printk(" Rev: ");
+ for (i = 32; i < 36; i++)
+ {
+ if (data[i] >= 0x20 && i < data[4] + 5)
+ printk("%c", data[i]);
+ else
+ printk(" ");
+ }
+
+ printk("\n");
+
+ i = data[0] & 0x1f;
+
+ printk(KERN_INFO " Type: %s ", (i < MAX_SCSI_DEVICE_CODE
+ ? scsi_device_types[i]
+ : "Unknown "));
+ printk(" ANSI SCSI revision: %02x", data[2] & 0x07);
+ if ((data[2] & 0x07) == 1 && (data[3] & 0x0f) == 1)
+ printk(" CCS\n");
+ else
+ printk("\n");
+}
+
+
+/*
+ * Changes by Martin Rogge, 9th Aug 1995:
+ * acsi_devinit has been taken out of acsi_geninit, because it needs
+ * to be called from revalidate_acsidisk. The result of request sense
+ * is now checked for DRIVE NOT READY.
+ *
+ * The structure *aip is only valid when acsi_devinit returns
+ * DEV_SUPPORTED.
+ *
+ */
+
+#define DEV_NONE 0
+#define DEV_UNKNOWN 1
+#define DEV_SUPPORTED 2
+#define DEV_SLM 3
+
+static int acsi_devinit(struct acsi_info_struct *aip)
+{
+ int status, got_inquiry;
+ SENSE_DATA sense;
+ unsigned char reqsense, extsense;
+
+ /*****************************************************************/
+ /* Do a TEST UNIT READY command to test the presence of a device */
+ /*****************************************************************/
+
+ CMDSET_TARG_LUN(tur_cmd, aip->target, aip->lun);
+ if (!acsicmd_nodma(tur_cmd, 0)) {
+ /* timed out -> no device here */
+#ifdef DEBUG_DETECT
+ printk("target %d lun %d: timeout\n", aip->target, aip->lun);
+#endif
+ return DEV_NONE;
+ }
+
+ /*************************/
+ /* Read the ACSI status. */
+ /*************************/
+
+ status = acsi_getstatus();
+ if (status) {
+ if (status == 0x12) {
+ /* The SLM printer should be the only device that
+ * responds with the error code in the status byte. In
+ * correct status bytes, bit 4 is never set.
+ */
+ printk( KERN_INFO "Detected SLM printer at id %d lun %d\n",
+ aip->target, aip->lun);
+ return DEV_SLM;
+ }
+ /* ignore CHECK CONDITION, since some devices send a
+ UNIT ATTENTION */
+ if ((status & 0x1e) != 0x2) {
+#ifdef DEBUG_DETECT
+ printk("target %d lun %d: status %d\n",
+ aip->target, aip->lun, status);
+#endif
+ return DEV_UNKNOWN;
+ }
+ }
+
+ /*******************************/
+ /* Do a REQUEST SENSE command. */
+ /*******************************/
+
+ if (!acsi_reqsense(acsi_buffer, aip->target, aip->lun)) {
+ printk( KERN_WARNING "acsi_reqsense failed\n");
+ acsi_buffer[0] = 0;
+ acsi_buffer[2] = UNIT_ATTENTION;
+ }
+ reqsense = acsi_buffer[0];
+ extsense = acsi_buffer[2] & 0xf;
+ if (status) {
+ if ((reqsense & 0x70) == 0x70) { /* extended sense */
+ if (extsense != UNIT_ATTENTION &&
+ extsense != NOT_READY) {
+#ifdef DEBUG_DETECT
+ printk("target %d lun %d: extended sense %d\n",
+ aip->target, aip->lun, extsense);
+#endif
+ return DEV_UNKNOWN;
+ }
+ }
+ else {
+ if (reqsense & 0x7f) {
+#ifdef DEBUG_DETECT
+ printk("target %d lun %d: sense %d\n",
+ aip->target, aip->lun, reqsense);
+#endif
+ return DEV_UNKNOWN;
+ }
+ }
+ }
+ else
+ if (reqsense == 0x4) { /* SH204 Bug workaround */
+#ifdef DEBUG_DETECT
+ printk("target %d lun %d status=0 sense=4\n",
+ aip->target, aip->lun);
+#endif
+ return DEV_UNKNOWN;
+ }
+
+ /***********************************************************/
+ /* Do an INQUIRY command to get more infos on this device. */
+ /***********************************************************/
+
+ /* Assume default values */
+ aip->removable = 1;
+ aip->read_only = 0;
+ aip->old_atari_disk = 0;
+ aip->changed = (extsense == NOT_READY); /* medium inserted? */
+ aip->size = DEFAULT_SIZE;
+ got_inquiry = 0;
+ /* Fake inquiry result for old atari disks */
+ memcpy(acsi_buffer, "\000\000\001\000 Adaptec 40xx"
+ " ", 40);
+ CMDSET_TARG_LUN(inquiry_cmd, aip->target, aip->lun);
+ if (acsicmd_dma(inquiry_cmd, acsi_buffer, 1, 0, 0) &&
+ acsi_getstatus() == 0) {
+ acsicmd_nodma(inquiry_cmd, 0);
+ acsi_getstatus();
+ dma_cache_maintenance( phys_acsi_buffer, 256, 0 );
+ got_inquiry = 1;
+ aip->removable = !!(acsi_buffer[1] & 0x80);
+ }
+ if (aip->type == NONE) /* only at boot time */
+ print_inquiry(acsi_buffer);
+ switch(acsi_buffer[0]) {
+ case TYPE_DISK:
+ aip->type = HARDDISK;
+ break;
+ case TYPE_ROM:
+ aip->type = CDROM;
+ aip->read_only = 1;
+ break;
+ default:
+ return DEV_UNKNOWN;
+ }
+ /****************************/
+ /* Do a MODE SENSE command. */
+ /****************************/
+
+ if (!acsi_mode_sense(aip->target, aip->lun, &sense)) {
+ printk( KERN_WARNING "No mode sense data.\n" );
+ return DEV_UNKNOWN;
+ }
+ if ((SECTOR_SIZE(sense) != 512) &&
+ ((aip->type != CDROM) ||
+ !acsi_change_blk_size(aip->target, aip->lun) ||
+ !acsi_mode_sense(aip->target, aip->lun, &sense) ||
+ (SECTOR_SIZE(sense) != 512))) {
+ printk( KERN_WARNING "Sector size != 512 not supported.\n" );
+ return DEV_UNKNOWN;
+ }
+ /* There are disks out there that claim to have 0 sectors... */
+ if (CAPACITY(sense))
+ aip->size = CAPACITY(sense); /* else keep DEFAULT_SIZE */
+ if (!got_inquiry && SENSE_TYPE(sense) == SENSE_TYPE_ATARI) {
+ /* If INQUIRY failed and the sense data suggest an old
+ * Atari disk (SH20x, Megafile), the disk is not removable
+ */
+ aip->removable = 0;
+ aip->old_atari_disk = 1;
+ }
+
+ /******************/
+ /* We've done it. */
+ /******************/
+
+ return DEV_SUPPORTED;
+}
+
+EXPORT_SYMBOL(acsi_delay_start);
+EXPORT_SYMBOL(acsi_delay_end);
+EXPORT_SYMBOL(acsi_wait_for_IRQ);
+EXPORT_SYMBOL(acsi_wait_for_noIRQ);
+EXPORT_SYMBOL(acsicmd_nodma);
+EXPORT_SYMBOL(acsi_getstatus);
+EXPORT_SYMBOL(acsi_buffer);
+EXPORT_SYMBOL(phys_acsi_buffer);
+
+#ifdef CONFIG_ATARI_SLM_MODULE
+void acsi_attach_SLMs( int (*attach_func)( int, int ) );
+
+EXPORT_SYMBOL(acsi_extstatus);
+EXPORT_SYMBOL(acsi_end_extstatus);
+EXPORT_SYMBOL(acsi_extcmd);
+EXPORT_SYMBOL(acsi_attach_SLMs);
+
+/* to remember IDs of SLM devices, SLM module is loaded later
+ * (index is target#, contents is lun#, -1 means "no SLM") */
+int SLM_devices[8];
+#endif
+
+static struct block_device_operations acsi_fops = {
+ .owner = THIS_MODULE,
+ .open = acsi_open,
+ .release = acsi_release,
+ .ioctl = acsi_ioctl,
+ .getgeo = acsi_getgeo,
+ .media_changed = acsi_media_change,
+ .revalidate_disk= acsi_revalidate,
+};
+
+#ifdef CONFIG_ATARI_SLM_MODULE
+/* call attach_slm() for each device that is a printer; needed for init of SLM
+ * driver as a module, since it's not yet present if acsi.c is inited and thus
+ * the bus gets scanned. */
+void acsi_attach_SLMs( int (*attach_func)( int, int ) )
+{
+ int i, n = 0;
+
+ for( i = 0; i < 8; ++i )
+ if (SLM_devices[i] >= 0)
+ n += (*attach_func)( i, SLM_devices[i] );
+ printk( KERN_INFO "Found %d SLM printer(s) total.\n", n );
+}
+#endif /* CONFIG_ATARI_SLM_MODULE */
+
+
+int acsi_init( void )
+{
+ int err = 0;
+ int i, target, lun;
+ struct acsi_info_struct *aip;
+#ifdef CONFIG_ATARI_SLM
+ int n_slm = 0;
+#endif
+ if (!MACH_IS_ATARI || !ATARIHW_PRESENT(ACSI))
+ return 0;
+ if (register_blkdev(ACSI_MAJOR, "ad")) {
+ err = -EBUSY;
+ goto out1;
+ }
+ if (!(acsi_buffer =
+ (char *)atari_stram_alloc(ACSI_BUFFER_SIZE, "acsi"))) {
+ err = -ENOMEM;
+ printk( KERN_ERR "Unable to get ACSI ST-Ram buffer.\n" );
+ goto out2;
+ }
+ phys_acsi_buffer = virt_to_phys( acsi_buffer );
+ STramMask = ATARIHW_PRESENT(EXTD_DMA) ? 0x00000000 : 0xff000000;
+
+ acsi_queue = blk_init_queue(do_acsi_request, &acsi_lock);
+ if (!acsi_queue) {
+ err = -ENOMEM;
+ goto out2a;
+ }
+#ifdef CONFIG_ATARI_SLM
+ err = slm_init();
+#endif
+ if (err)
+ goto out3;
+
+ printk( KERN_INFO "Probing ACSI devices:\n" );
+ NDevices = 0;
+#ifdef CONFIG_ATARI_SLM_MODULE
+ for( i = 0; i < 8; ++i )
+ SLM_devices[i] = -1;
+#endif
+ stdma_lock(NULL, NULL);
+
+ for (target = 0; target < 8 && NDevices < MAX_DEV; ++target) {
+ lun = 0;
+ do {
+ aip = &acsi_info[NDevices];
+ aip->type = NONE;
+ aip->target = target;
+ aip->lun = lun;
+ i = acsi_devinit(aip);
+ switch (i) {
+ case DEV_SUPPORTED:
+ printk( KERN_INFO "Detected ");
+ switch (aip->type) {
+ case HARDDISK:
+ printk("disk");
+ break;
+ case CDROM:
+ printk("cdrom");
+ break;
+ default:
+ }
+ printk(" ad%c at id %d lun %d ",
+ 'a' + NDevices, target, lun);
+ if (aip->removable)
+ printk("(removable) ");
+ if (aip->read_only)
+ printk("(read-only) ");
+ if (aip->size == DEFAULT_SIZE)
+ printk(" unkown size, using default ");
+ printk("%ld MByte\n",
+ (aip->size*512+1024*1024/2)/(1024*1024));
+ NDevices++;
+ break;
+ case DEV_SLM:
+#ifdef CONFIG_ATARI_SLM
+ n_slm += attach_slm( target, lun );
+ break;
+#endif
+#ifdef CONFIG_ATARI_SLM_MODULE
+ SLM_devices[target] = lun;
+ break;
+#endif
+ /* neither of the above: fall through to unknown device */
+ case DEV_UNKNOWN:
+ printk( KERN_INFO "Detected unsupported device at "
+ "id %d lun %d\n", target, lun);
+ break;
+ }
+ }
+#ifdef CONFIG_ACSI_MULTI_LUN
+ while (i != DEV_NONE && ++lun < MAX_LUN);
+#else
+ while (0);
+#endif
+ }
+
+ /* reenable interrupt */
+ ENABLE_IRQ();
+ stdma_release();
+
+#ifndef CONFIG_ATARI_SLM
+ printk( KERN_INFO "Found %d ACSI device(s) total.\n", NDevices );
+#else
+ printk( KERN_INFO "Found %d ACSI device(s) and %d SLM printer(s) total.\n",
+ NDevices, n_slm );
+#endif
+ err = -ENOMEM;
+ for( i = 0; i < NDevices; ++i ) {
+ acsi_gendisk[i] = alloc_disk(16);
+ if (!acsi_gendisk[i])
+ goto out4;
+ }
+
+ for( i = 0; i < NDevices; ++i ) {
+ struct gendisk *disk = acsi_gendisk[i];
+ sprintf(disk->disk_name, "ad%c", 'a'+i);
+ aip = &acsi_info[NDevices];
+ disk->major = ACSI_MAJOR;
+ disk->first_minor = i << 4;
+ if (acsi_info[i].type != HARDDISK)
+ disk->minors = 1;
+ disk->fops = &acsi_fops;
+ disk->private_data = &acsi_info[i];
+ set_capacity(disk, acsi_info[i].size);
+ disk->queue = acsi_queue;
+ add_disk(disk);
+ }
+ return 0;
+out4:
+ while (i--)
+ put_disk(acsi_gendisk[i]);
+out3:
+ blk_cleanup_queue(acsi_queue);
+out2a:
+ atari_stram_free( acsi_buffer );
+out2:
+ unregister_blkdev( ACSI_MAJOR, "ad" );
+out1:
+ return err;
+}
+
+
+#ifdef MODULE
+
+MODULE_LICENSE("GPL");
+
+int init_module(void)
+{
+ int err;
+
+ if ((err = acsi_init()))
+ return( err );
+ printk( KERN_INFO "ACSI driver loaded as module.\n");
+ return( 0 );
+}
+
+void cleanup_module(void)
+{
+ int i;
+ del_timer( &acsi_timer );
+ blk_cleanup_queue(acsi_queue);
+ atari_stram_free( acsi_buffer );
+
+ if (unregister_blkdev( ACSI_MAJOR, "ad" ) != 0)
+ printk( KERN_ERR "acsi: cleanup_module failed\n");
+
+ for (i = 0; i < NDevices; i++) {
+ del_gendisk(acsi_gendisk[i]);
+ put_disk(acsi_gendisk[i]);
+ }
+}
+#endif
+
+/*
+ * This routine is called to flush all partitions and partition tables
+ * for a changed scsi disk, and then re-read the new partition table.
+ * If we are revalidating a disk because of a media change, then we
+ * enter with usage == 0. If we are using an ioctl, we automatically have
+ * usage == 1 (we need an open channel to use an ioctl :-), so this
+ * is our limit.
+ *
+ * Changes by Martin Rogge, 9th Aug 1995:
+ * got cd-roms to work by calling acsi_devinit. There are only two problems:
+ * First, if there is no medium inserted, the status will remain "changed".
+ * That is no problem at all, but our design of three-valued logic (medium
+ * changed, medium not changed, no medium inserted).
+ * Secondly the check could fail completely and the drive could deliver
+ * nonsensical data, which could mess up the acsi_info[] structure. In
+ * that case we try to make the entry safe.
+ *
+ */
+
+static int acsi_revalidate(struct gendisk *disk)
+{
+ struct acsi_info_struct *aip = disk->private_data;
+ stdma_lock( NULL, NULL );
+ if (acsi_devinit(aip) != DEV_SUPPORTED) {
+ printk( KERN_ERR "ACSI: revalidate failed for target %d lun %d\n",
+ aip->target, aip->lun);
+ aip->size = 0;
+ aip->read_only = 1;
+ aip->removable = 1;
+ aip->changed = 1; /* next acsi_open will try again... */
+ }
+
+ ENABLE_IRQ();
+ stdma_release();
+ set_capacity(disk, aip->size);
+ return 0;
+}
diff --git a/trunk/drivers/block/amiflop.c b/trunk/drivers/block/amiflop.c
index 6ce8b897e262..27a139025ced 100644
--- a/trunk/drivers/block/amiflop.c
+++ b/trunk/drivers/block/amiflop.c
@@ -1363,7 +1363,7 @@ static void redo_fd_request(void)
#ifdef DEBUG
printk("fd: sector %ld + %d requested for %s\n",
CURRENT->sector,cnt,
- (rq_data_dir(CURRENT) == READ) ? "read" : "write");
+ (CURRENT->cmd==READ)?"read":"write");
#endif
block = CURRENT->sector + cnt;
if ((int)block > floppy->blocks) {
diff --git a/trunk/drivers/block/cciss.c b/trunk/drivers/block/cciss.c
index 0fcad430474e..5acc6c44aead 100644
--- a/trunk/drivers/block/cciss.c
+++ b/trunk/drivers/block/cciss.c
@@ -87,7 +87,6 @@ static const struct pci_device_id cciss_pci_device_id[] = {
{PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_CISSD, 0x103C, 0x3214},
{PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_CISSD, 0x103C, 0x3215},
{PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_CISSC, 0x103C, 0x3237},
- {PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_CISSC, 0x103C, 0x323D},
{PCI_VENDOR_ID_HP, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID,
PCI_CLASS_STORAGE_RAID << 8, 0xffff << 8, 0},
{0,}
@@ -120,7 +119,6 @@ static struct board_type products[] = {
{0x3214103C, "Smart Array E200i", &SA5_access, 120},
{0x3215103C, "Smart Array E200i", &SA5_access, 120},
{0x3237103C, "Smart Array E500", &SA5_access, 512},
- {0x323D103C, "Smart Array P700m", &SA5_access, 512},
{0xFFFF103C, "Unknown Smart Array", &SA5_access, 120},
};
diff --git a/trunk/drivers/block/loop.c b/trunk/drivers/block/loop.c
index 4503290da407..0ed5470d2533 100644
--- a/trunk/drivers/block/loop.c
+++ b/trunk/drivers/block/loop.c
@@ -74,7 +74,6 @@
#include
#include
#include
-#include
#include
@@ -402,73 +401,50 @@ struct lo_read_data {
};
static int
-lo_splice_actor(struct pipe_inode_info *pipe, struct pipe_buffer *buf,
- struct splice_desc *sd)
+lo_read_actor(read_descriptor_t *desc, struct page *page,
+ unsigned long offset, unsigned long size)
{
- struct lo_read_data *p = sd->u.data;
+ unsigned long count = desc->count;
+ struct lo_read_data *p = desc->arg.data;
struct loop_device *lo = p->lo;
- struct page *page = buf->page;
sector_t IV;
- size_t size;
- int ret;
- ret = buf->ops->confirm(pipe, buf);
- if (unlikely(ret))
- return ret;
+ IV = ((sector_t) page->index << (PAGE_CACHE_SHIFT - 9))+(offset >> 9);
- IV = ((sector_t) page->index << (PAGE_CACHE_SHIFT - 9)) +
- (buf->offset >> 9);
- size = sd->len;
- if (size > p->bsize)
- size = p->bsize;
+ if (size > count)
+ size = count;
- if (lo_do_transfer(lo, READ, page, buf->offset, p->page, p->offset, size, IV)) {
+ if (lo_do_transfer(lo, READ, page, offset, p->page, p->offset, size, IV)) {
+ size = 0;
printk(KERN_ERR "loop: transfer error block %ld\n",
page->index);
- size = -EINVAL;
+ desc->error = -EINVAL;
}
flush_dcache_page(p->page);
- if (size > 0)
- p->offset += size;
-
+ desc->count = count - size;
+ desc->written += size;
+ p->offset += size;
return size;
}
-static int
-lo_direct_splice_actor(struct pipe_inode_info *pipe, struct splice_desc *sd)
-{
- return __splice_from_pipe(pipe, sd, lo_splice_actor);
-}
-
static int
do_lo_receive(struct loop_device *lo,
struct bio_vec *bvec, int bsize, loff_t pos)
{
struct lo_read_data cookie;
- struct splice_desc sd;
struct file *file;
- long retval;
+ int retval;
cookie.lo = lo;
cookie.page = bvec->bv_page;
cookie.offset = bvec->bv_offset;
cookie.bsize = bsize;
-
- sd.len = 0;
- sd.total_len = bvec->bv_len;
- sd.flags = 0;
- sd.pos = pos;
- sd.u.data = &cookie;
-
file = lo->lo_backing_file;
- retval = splice_direct_to_actor(file, &sd, lo_direct_splice_actor);
-
- if (retval < 0)
- return retval;
-
- return 0;
+ retval = file->f_op->sendfile(file, &pos, bvec->bv_len,
+ lo_read_actor, &cookie);
+ return (retval < 0)? retval: 0;
}
static int
@@ -703,8 +679,8 @@ static int loop_change_fd(struct loop_device *lo, struct file *lo_file,
if (!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode))
goto out_putf;
- /* new backing store needs to support loop (eg splice_read) */
- if (!inode->i_fop->splice_read)
+ /* new backing store needs to support loop (eg sendfile) */
+ if (!inode->i_fop->sendfile)
goto out_putf;
/* size of the new backing store needs to be the same */
@@ -784,7 +760,7 @@ static int loop_set_fd(struct loop_device *lo, struct file *lo_file,
* If we can't read - sorry. If we only can't write - well,
* it's going to be read-only.
*/
- if (!file->f_op->splice_read)
+ if (!file->f_op->sendfile)
goto out_putf;
if (aops->prepare_write && aops->commit_write)
lo_flags |= LO_FLAGS_USE_AOPS;
diff --git a/trunk/drivers/block/nbd.c b/trunk/drivers/block/nbd.c
index c575fb1d585f..069ae39a9cd9 100644
--- a/trunk/drivers/block/nbd.c
+++ b/trunk/drivers/block/nbd.c
@@ -416,7 +416,7 @@ static void nbd_clear_que(struct nbd_device *lo)
/*
* We always wait for result of write, for now. It would be nice to make it optional
* in future
- * if ((rq_data_dir(req) == WRITE) && (lo->flags & NBD_WRITE_NOCHK))
+ * if ((req->cmd == WRITE) && (lo->flags & NBD_WRITE_NOCHK))
* { printk( "Warning: Ignoring result!\n"); nbd_end_request( req ); }
*/
diff --git a/trunk/drivers/cdrom/Kconfig b/trunk/drivers/cdrom/Kconfig
new file mode 100644
index 000000000000..4b12e9031fb3
--- /dev/null
+++ b/trunk/drivers/cdrom/Kconfig
@@ -0,0 +1,213 @@
+#
+# CDROM driver configuration
+#
+
+menu "Old CD-ROM drivers (not SCSI, not IDE)"
+ depends on ISA && BLOCK
+
+config CD_NO_IDESCSI
+ bool "Support non-SCSI/IDE/ATAPI CDROM drives"
+ ---help---
+ If you have a CD-ROM drive that is neither SCSI nor IDE/ATAPI, say Y
+ here, otherwise N. Read the CD-ROM-HOWTO, available from
+ .
+
+ Note that the answer to this question doesn't directly affect the
+ kernel: saying N will just cause the configurator to skip all
+ the questions about these CD-ROM drives. If you are unsure what you
+ have, say Y and find out whether you have one of the following
+ drives.
+
+ For each of these drivers, a
+ exists. Especially in cases where you do not know exactly which kind
+ of drive you have you should read there. Most of these drivers use a
+ file drivers/cdrom/{driver_name}.h where you can define your
+ interface parameters and switch some internal goodies.
+
+ To compile these CD-ROM drivers as a module, choose M instead of Y.
+
+ If you want to use any of these CD-ROM drivers, you also have to
+ answer Y or M to "ISO 9660 CD-ROM file system support" below (this
+ answer will get "defaulted" for you if you enable any of the Linux
+ CD-ROM drivers).
+
+config AZTCD
+ tristate "Aztech/Orchid/Okano/Wearnes/TXC/CyDROM CDROM support"
+ depends on CD_NO_IDESCSI
+ ---help---
+ This is your driver if you have an Aztech CDA268-01A, Orchid
+ CD-3110, Okano or Wearnes CDD110, Conrad TXC, or CyCD-ROM CR520 or
+ CR540 CD-ROM drive. This driver -- just like all these CD-ROM
+ drivers -- is NOT for CD-ROM drives with IDE/ATAPI interfaces, such
+ as Aztech CDA269-031SE. Please read the file
+ .
+
+ If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM
+ file system support" below, because that's the file system used on
+ CD-ROMs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called aztcd.
+
+config GSCD
+ tristate "Goldstar R420 CDROM support"
+ depends on CD_NO_IDESCSI
+ ---help---
+ If this is your CD-ROM drive, say Y here. As described in the file
+ , you might have to change a setting
+ in the file before compiling the
+ kernel. Please read the file .
+
+ If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM
+ file system support" below, because that's the file system used on
+ CD-ROMs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called gscd.
+
+config SBPCD
+ tristate "Matsushita/Panasonic/Creative, Longshine, TEAC CDROM support"
+ depends on CD_NO_IDESCSI && BROKEN_ON_SMP
+ ---help---
+ This driver supports most of the drives which use the Panasonic or
+ Sound Blaster interface. Please read the file
+ .
+
+ The Matsushita CR-521, CR-522, CR-523, CR-562, CR-563 drives
+ (sometimes labeled "Creative"), the Creative Labs CD200, the
+ Longshine LCS-7260, the "IBM External ISA CD-ROM" (in fact a CR-56x
+ model), the TEAC CD-55A fall under this category. Some other
+ "electrically compatible" drives (Vertos, Genoa, some Funai models)
+ are currently not supported; for the Sanyo H94A drive currently a
+ separate driver (asked later) is responsible. Most drives have a
+ uniquely shaped faceplate, with a caddyless motorized drawer, but
+ without external brand markings. The older CR-52x drives have a
+ caddy and manual loading/eject, but still no external markings. The
+ driver is able to do an extended auto-probing for interface
+ addresses and drive types; this can help to find facts in cases you
+ are not sure, but can consume some time during the boot process if
+ none of the supported drives gets found. Once your drive got found,
+ you should enter the reported parameters into
+ and set "DISTRIBUTION 0" there.
+
+ This driver can support up to four CD-ROM controller cards, and each
+ card can support up to four CD-ROM drives; if you say Y here, you
+ will be asked how many controller cards you have. If compiled as a
+ module, only one controller card (but with up to four drives) is
+ usable.
+
+ If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM
+ file system support" below, because that's the file system used on
+ CD-ROMs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sbpcd.
+
+config MCDX
+ tristate "Mitsumi CDROM support"
+ depends on CD_NO_IDESCSI
+ ---help---
+ Use this driver if you want to be able to use your Mitsumi LU-005,
+ FX-001 or FX-001D CD-ROM drive.
+
+ Please read the file .
+
+ If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM
+ file system support" below, because that's the file system used on
+ CD-ROMs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mcdx.
+
+config OPTCD
+ tristate "Optics Storage DOLPHIN 8000AT CDROM support"
+ depends on CD_NO_IDESCSI
+ ---help---
+ This is the driver for the 'DOLPHIN' drive with a 34-pin Sony
+ compatible interface. It also works with the Lasermate CR328A. If
+ you have one of those, say Y. This driver does not work for the
+ Optics Storage 8001 drive; use the IDE-ATAPI CD-ROM driver for that
+ one. Please read the file .
+
+ If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM
+ file system support" below, because that's the file system used on
+ CD-ROMs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called optcd.
+
+config CM206
+ tristate "Philips/LMS CM206 CDROM support"
+ depends on CD_NO_IDESCSI && BROKEN_ON_SMP
+ ---help---
+ If you have a Philips/LMS CD-ROM drive cm206 in combination with a
+ cm260 host adapter card, say Y here. Please also read the file
+ .
+
+ If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM
+ file system support" below, because that's the file system used on
+ CD-ROMs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cm206.
+
+config SJCD
+ tristate "Sanyo CDR-H94A CDROM support"
+ depends on CD_NO_IDESCSI
+ help
+ If this is your CD-ROM drive, say Y here and read the file
+ . You should then also say Y or M to
+ "ISO 9660 CD-ROM file system support" below, because that's the
+ file system used on CD-ROMs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sjcd.
+
+config ISP16_CDI
+ tristate "ISP16/MAD16/Mozart soft configurable cdrom interface support"
+ depends on CD_NO_IDESCSI
+ ---help---
+ These are sound cards with built-in cdrom interfaces using the OPTi
+ 82C928 or 82C929 chips. Say Y here to have them detected and
+ possibly configured at boot time. In addition, You'll have to say Y
+ to a driver for the particular cdrom drive you have attached to the
+ card. Read for details.
+
+ To compile this driver as a module, choose M here: the
+ module will be called isp16.
+
+config CDU31A
+ tristate "Sony CDU31A/CDU33A CDROM support"
+ depends on CD_NO_IDESCSI && BROKEN_ON_SMP
+ ---help---
+ These CD-ROM drives have a spring-pop-out caddyless drawer, and a
+ rectangular green LED centered beneath it. NOTE: these CD-ROM
+ drives will not be auto detected by the kernel at boot time; you
+ have to provide the interface address as an option to the kernel at
+ boot time as described in or fill
+ in your parameters into . Try "man
+ bootparam" or see the documentation of your boot loader (lilo or
+ loadlin) about how to pass options to the kernel.
+
+ If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM
+ file system support" below, because that's the file system used on
+ CD-ROMs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cdu31a.
+
+config CDU535
+ tristate "Sony CDU535 CDROM support"
+ depends on CD_NO_IDESCSI
+ ---help---
+ This is the driver for the older Sony CDU-535 and CDU-531 CD-ROM
+ drives. Please read the file .
+
+ If you say Y here, you should also say Y or M to "ISO 9660 CD-ROM
+ file system support" below, because that's the file system used on
+ CD-ROMs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sonycd535.
+
+endmenu
diff --git a/trunk/drivers/cdrom/Makefile b/trunk/drivers/cdrom/Makefile
index 774c180a4e11..d1d1e5a4be73 100644
--- a/trunk/drivers/cdrom/Makefile
+++ b/trunk/drivers/cdrom/Makefile
@@ -10,4 +10,14 @@ obj-$(CONFIG_BLK_DEV_SR) += cdrom.o
obj-$(CONFIG_PARIDE_PCD) += cdrom.o
obj-$(CONFIG_CDROM_PKTCDVD) += cdrom.o
+obj-$(CONFIG_AZTCD) += aztcd.o
+obj-$(CONFIG_CDU31A) += cdu31a.o cdrom.o
+obj-$(CONFIG_CM206) += cm206.o cdrom.o
+obj-$(CONFIG_GSCD) += gscd.o
+obj-$(CONFIG_ISP16_CDI) += isp16.o
+obj-$(CONFIG_MCDX) += mcdx.o cdrom.o
+obj-$(CONFIG_OPTCD) += optcd.o
+obj-$(CONFIG_SBPCD) += sbpcd.o cdrom.o
+obj-$(CONFIG_SJCD) += sjcd.o
+obj-$(CONFIG_CDU535) += sonycd535.o
obj-$(CONFIG_VIOCD) += viocd.o cdrom.o
diff --git a/trunk/drivers/cdrom/aztcd.c b/trunk/drivers/cdrom/aztcd.c
new file mode 100644
index 000000000000..1f9fb7a96703
--- /dev/null
+++ b/trunk/drivers/cdrom/aztcd.c
@@ -0,0 +1,2492 @@
+#define AZT_VERSION "2.60"
+
+/* $Id: aztcd.c,v 2.60 1997/11/29 09:51:19 root Exp root $
+ linux/drivers/block/aztcd.c - Aztech CD268 CDROM driver
+
+ Copyright (C) 1994-98 Werner Zimmermann(Werner.Zimmermann@fht-esslingen.de)
+
+ based on Mitsumi CDROM driver by Martin Hariss and preworks by
+ Eberhard Moenkeberg; contains contributions by Joe Nardone and Robby
+ Schirmer.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ HISTORY
+ V0.0 Adaption to Aztech CD268-01A Version 1.3
+ Version is PRE_ALPHA, unresolved points:
+ 1. I use busy wait instead of timer wait in STEN_LOW,DTEN_LOW
+ thus driver causes CPU overhead and is very slow
+ 2. could not find a way to stop the drive, when it is
+ in data read mode, therefore I had to set
+ msf.end.min/sec/frame to 0:0:1 (in azt_poll); so only one
+ frame can be read in sequence, this is also the reason for
+ 3. getting 'timeout in state 4' messages, but nevertheless
+ it works
+ W.Zimmermann, Oct. 31, 1994
+ V0.1 Version is ALPHA, problems #2 and #3 resolved.
+ W.Zimmermann, Nov. 3, 1994
+ V0.2 Modification to some comments, debugging aids for partial test
+ with Borland C under DOS eliminated. Timer interrupt wait
+ STEN_LOW_WAIT additionally to busy wait for STEN_LOW implemented;
+ use it only for the 'slow' commands (ACMD_GET_Q_CHANNEL, ACMD_
+ SEEK_TO_LEAD_IN), all other commands are so 'fast', that busy
+ waiting seems better to me than interrupt rescheduling.
+ Besides that, when used in the wrong place, STEN_LOW_WAIT causes
+ kernel panic.
+ In function aztPlay command ACMD_PLAY_AUDIO added, should make
+ audio functions work. The Aztech drive needs different commands
+ to read data tracks and play audio tracks.
+ W.Zimmermann, Nov. 8, 1994
+ V0.3 Recognition of missing drive during boot up improved (speeded up).
+ W.Zimmermann, Nov. 13, 1994
+ V0.35 Rewrote the control mechanism in azt_poll (formerly mcd_poll)
+ including removal of all 'goto' commands. :-);
+ J. Nardone, Nov. 14, 1994
+ V0.4 Renamed variables and constants to 'azt' instead of 'mcd'; had
+ to make some "compatibility" defines in azt.h; please note,
+ that the source file was renamed to azt.c, the include file to
+ azt.h
+ Speeded up drive recognition during init (will be a little bit
+ slower than before if no drive is installed!); suggested by
+ Robby Schirmer.
+ read_count declared volatile and set to AZT_BUF_SIZ to make
+ drive faster (now 300kB/sec, was 60kB/sec before, measured
+ by 'time dd if=/dev/cdrom of=/dev/null bs=2048 count=4096';
+ different AZT_BUF_SIZes were test, above 16 no further im-
+ provement seems to be possible; suggested by E.Moenkeberg.
+ W.Zimmermann, Nov. 18, 1994
+ V0.42 Included getAztStatus command in GetQChannelInfo() to allow
+ reading Q-channel info on audio disks, if drive is stopped,
+ and some other bug fixes in the audio stuff, suggested by
+ Robby Schirmer.
+ Added more ioctls (reading data in mode 1 and mode 2).
+ Completely removed the old azt_poll() routine.
+ Detection of ORCHID CDS-3110 in aztcd_init implemented.
+ Additional debugging aids (see the readme file).
+ W.Zimmermann, Dec. 9, 1994
+ V0.50 Autodetection of drives implemented.
+ W.Zimmermann, Dec. 12, 1994
+ V0.52 Prepared for including in the standard kernel, renamed most
+ variables to contain 'azt', included autoconf.h
+ W.Zimmermann, Dec. 16, 1994
+ V0.6 Version for being included in the standard Linux kernel.
+ Renamed source and header file to aztcd.c and aztcd.h
+ W.Zimmermann, Dec. 24, 1994
+ V0.7 Changed VERIFY_READ to VERIFY_WRITE in aztcd_ioctl, case
+ CDROMREADMODE1 and CDROMREADMODE2; bug fix in the ioctl,
+ which causes kernel crashes when playing audio, changed
+ include-files (config.h instead of autoconf.h, removed
+ delay.h)
+ W.Zimmermann, Jan. 8, 1995
+ V0.72 Some more modifications for adaption to the standard kernel.
+ W.Zimmermann, Jan. 16, 1995
+ V0.80 aztcd is now part of the standard kernel since version 1.1.83.
+ Modified the SET_TIMER and CLEAR_TIMER macros to comply with
+ the new timer scheme.
+ W.Zimmermann, Jan. 21, 1995
+ V0.90 Included CDROMVOLCTRL, but with my Aztech drive I can only turn
+ the channels on and off. If it works better with your drive,
+ please mail me. Also implemented ACMD_CLOSE for CDROMSTART.
+ W.Zimmermann, Jan. 24, 1995
+ V1.00 Implemented close and lock tray commands. Patches supplied by
+ Frank Racis
+ Added support for loadable MODULEs, so aztcd can now also be
+ loaded by insmod and removed by rmmod during run time
+ Werner Zimmermann, Mar. 24, 95
+ V1.10 Implemented soundcard configuration for Orchid CDS-3110 drives
+ connected to Soundwave32 cards. Release for LST 2.1.
+ (still experimental)
+ Werner Zimmermann, May 8, 95
+ V1.20 Implemented limited support for DOSEMU0.60's cdrom.c. Now it works, but
+ sometimes DOSEMU may hang for 30 seconds or so. A fully functional ver-
+ sion needs an update of Dosemu0.60's cdrom.c, which will come with the
+ next revision of Dosemu.
+ Also Soundwave32 support now works.
+ Werner Zimmermann, May 22, 95
+ V1.30 Auto-eject feature. Inspired by Franc Racis (racis@psu.edu)
+ Werner Zimmermann, July 4, 95
+ V1.40 Started multisession support. Implementation copied from mcdx.c
+ by Heiko Schlittermann. Not tested yet.
+ Werner Zimmermann, July 15, 95
+ V1.50 Implementation of ioctl CDROMRESET, continued multisession, began
+ XA, but still untested. Heavy modifications to drive status de-
+ tection.
+ Werner Zimmermann, July 25, 95
+ V1.60 XA support now should work. Speeded up drive recognition in cases,
+ where no drive is installed.
+ Werner Zimmermann, August 8, 1995
+ V1.70 Multisession support now is completed, but there is still not
+ enough testing done. If you can test it, please contact me. For
+ details please read Documentation/cdrom/aztcd
+ Werner Zimmermann, August 19, 1995
+ V1.80 Modification to suit the new kernel boot procedure introduced
+ with kernel 1.3.33. Will definitely not work with older kernels.
+ Programming done by Linus himself.
+ Werner Zimmermann, October 11, 1995
+ V1.90 Support for Conrad TXC drives, thank's to Jochen Kunz and Olaf Kaluza.
+ Werner Zimmermann, October 21, 1995
+ V2.00 Changed #include "blk.h" to as the directory
+ structure was changed. README.aztcd is now /usr/src/docu-
+ mentation/cdrom/aztcd
+ Werner Zimmermann, November 10, 95
+ V2.10 Started to modify azt_poll to prevent reading beyond end of
+ tracks.
+ Werner Zimmermann, December 3, 95
+ V2.20 Changed some comments
+ Werner Zimmermann, April 1, 96
+ V2.30 Implemented support for CyCDROM CR520, CR940, Code for CR520
+ delivered by H.Berger with preworks by E.Moenkeberg.
+ Werner Zimmermann, April 29, 96
+ V2.40 Reorganized the placement of functions in the source code file
+ to reflect the layered approach; did not actually change code
+ Werner Zimmermann, May 1, 96
+ V2.50 Heiko Eissfeldt suggested to remove some VERIFY_READs in
+ aztcd_ioctl; check_aztcd_media_change modified
+ Werner Zimmermann, May 16, 96
+ V2.60 Implemented Auto-Probing; made changes for kernel's 2.1.xx blocksize
+ Adaption to linux kernel > 2.1.0
+ Werner Zimmermann, Nov 29, 97
+
+ 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
+*/
+
+#include
+#include "aztcd.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+
+#include
+
+/*###########################################################################
+ Defines
+ ###########################################################################
+*/
+
+#define MAJOR_NR AZTECH_CDROM_MAJOR
+#define QUEUE (azt_queue)
+#define CURRENT elv_next_request(azt_queue)
+#define SET_TIMER(func, jifs) delay_timer.expires = jiffies + (jifs); \
+ delay_timer.function = (void *) (func); \
+ add_timer(&delay_timer);
+
+#define CLEAR_TIMER del_timer(&delay_timer);
+
+#define RETURNM(message,value) {printk("aztcd: Warning: %s failed\n",message);\
+ return value;}
+#define RETURN(message) {printk("aztcd: Warning: %s failed\n",message);\
+ return;}
+
+/* Macros to switch the IDE-interface to the slave device and back to the master*/
+#define SWITCH_IDE_SLAVE outb_p(0xa0,azt_port+6); \
+ outb_p(0x10,azt_port+6); \
+ outb_p(0x00,azt_port+7); \
+ outb_p(0x10,azt_port+6);
+#define SWITCH_IDE_MASTER outb_p(0xa0,azt_port+6);
+
+
+#if 0
+#define AZT_TEST
+#define AZT_TEST1 /* */
+#define AZT_TEST2 /* do_aztcd_request */
+#define AZT_TEST3 /* AZT_S_state */
+#define AZT_TEST4 /* QUICK_LOOP-counter */
+#define AZT_TEST5 /* port(1) state */
+#define AZT_DEBUG
+#define AZT_DEBUG_MULTISESSION
+#endif
+
+static struct request_queue *azt_queue;
+
+static int current_valid(void)
+{
+ return CURRENT &&
+ CURRENT->cmd == READ &&
+ CURRENT->sector != -1;
+}
+
+#define AFL_STATUSorDATA (AFL_STATUS | AFL_DATA)
+#define AZT_BUF_SIZ 16
+
+#define READ_TIMEOUT 3000
+
+#define azt_port aztcd /*needed for the modutils */
+
+/*##########################################################################
+ Type Definitions
+ ##########################################################################
+*/
+enum azt_state_e { AZT_S_IDLE, /* 0 */
+ AZT_S_START, /* 1 */
+ AZT_S_MODE, /* 2 */
+ AZT_S_READ, /* 3 */
+ AZT_S_DATA, /* 4 */
+ AZT_S_STOP, /* 5 */
+ AZT_S_STOPPING /* 6 */
+};
+enum azt_read_modes { AZT_MODE_0, /*read mode for audio disks, not supported by Aztech firmware */
+ AZT_MODE_1, /*read mode for normal CD-ROMs */
+ AZT_MODE_2 /*read mode for XA CD-ROMs */
+};
+
+/*##########################################################################
+ Global Variables
+ ##########################################################################
+*/
+static int aztPresent = 0;
+
+static volatile int azt_transfer_is_active = 0;
+
+static char azt_buf[CD_FRAMESIZE_RAW * AZT_BUF_SIZ]; /*buffer for block size conversion */
+#if AZT_PRIVATE_IOCTLS
+static char buf[CD_FRAMESIZE_RAW]; /*separate buffer for the ioctls */
+#endif
+
+static volatile int azt_buf_bn[AZT_BUF_SIZ], azt_next_bn;
+static volatile int azt_buf_in, azt_buf_out = -1;
+static volatile int azt_error = 0;
+static int azt_open_count = 0;
+static volatile enum azt_state_e azt_state = AZT_S_IDLE;
+#ifdef AZT_TEST3
+static volatile enum azt_state_e azt_state_old = AZT_S_STOP;
+static volatile int azt_st_old = 0;
+#endif
+static volatile enum azt_read_modes azt_read_mode = AZT_MODE_1;
+
+static int azt_mode = -1;
+static volatile int azt_read_count = 1;
+
+static int azt_port = AZT_BASE_ADDR;
+
+module_param(azt_port, int, 0);
+
+static int azt_port_auto[16] = AZT_BASE_AUTO;
+
+static char azt_cont = 0;
+static char azt_init_end = 0;
+static char azt_auto_eject = AZT_AUTO_EJECT;
+
+static int AztTimeout, AztTries;
+static DECLARE_WAIT_QUEUE_HEAD(azt_waitq);
+static DEFINE_TIMER(delay_timer, NULL, 0, 0);
+
+static struct azt_DiskInfo DiskInfo;
+static struct azt_Toc Toc[MAX_TRACKS];
+static struct azt_Play_msf azt_Play;
+
+static int aztAudioStatus = CDROM_AUDIO_NO_STATUS;
+static char aztDiskChanged = 1;
+static char aztTocUpToDate = 0;
+
+static unsigned char aztIndatum;
+static unsigned long aztTimeOutCount;
+static int aztCmd = 0;
+
+static DEFINE_SPINLOCK(aztSpin);
+
+/*###########################################################################
+ Function Prototypes
+ ###########################################################################
+*/
+/* CDROM Drive Low Level I/O Functions */
+static void aztStatTimer(void);
+
+/* CDROM Drive Command Functions */
+static int aztGetDiskInfo(void);
+#if AZT_MULTISESSION
+static int aztGetMultiDiskInfo(void);
+#endif
+static int aztGetToc(int multi);
+
+/* Kernel Interface Functions */
+static int check_aztcd_media_change(struct gendisk *disk);
+static int aztcd_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
+ unsigned long arg);
+static int aztcd_open(struct inode *ip, struct file *fp);
+static int aztcd_release(struct inode *inode, struct file *file);
+
+static struct block_device_operations azt_fops = {
+ .owner = THIS_MODULE,
+ .open = aztcd_open,
+ .release = aztcd_release,
+ .ioctl = aztcd_ioctl,
+ .media_changed = check_aztcd_media_change,
+};
+
+/* Aztcd State Machine: Controls Drive Operating State */
+static void azt_poll(void);
+
+/* Miscellaneous support functions */
+static void azt_hsg2msf(long hsg, struct msf *msf);
+static long azt_msf2hsg(struct msf *mp);
+static void azt_bin2bcd(unsigned char *p);
+static int azt_bcd2bin(unsigned char bcd);
+
+/*##########################################################################
+ CDROM Drive Low Level I/O Functions
+ ##########################################################################
+*/
+/* Macros for the drive hardware interface handshake, these macros use
+ busy waiting */
+/* Wait for OP_OK = drive answers with AFL_OP_OK after receiving a command*/
+# define OP_OK op_ok()
+static void op_ok(void)
+{
+ aztTimeOutCount = 0;
+ do {
+ aztIndatum = inb(DATA_PORT);
+ aztTimeOutCount++;
+ if (aztTimeOutCount >= AZT_TIMEOUT) {
+ printk("aztcd: Error Wait OP_OK\n");
+ break;
+ }
+ } while (aztIndatum != AFL_OP_OK);
+}
+
+/* Wait for PA_OK = drive answers with AFL_PA_OK after receiving parameters*/
+#if 0
+# define PA_OK pa_ok()
+static void pa_ok(void)
+{
+ aztTimeOutCount = 0;
+ do {
+ aztIndatum = inb(DATA_PORT);
+ aztTimeOutCount++;
+ if (aztTimeOutCount >= AZT_TIMEOUT) {
+ printk("aztcd: Error Wait PA_OK\n");
+ break;
+ }
+ } while (aztIndatum != AFL_PA_OK);
+}
+#endif
+
+/* Wait for STEN=Low = handshake signal 'AFL_.._OK available or command executed*/
+# define STEN_LOW sten_low()
+static void sten_low(void)
+{
+ aztTimeOutCount = 0;
+ do {
+ aztIndatum = inb(STATUS_PORT);
+ aztTimeOutCount++;
+ if (aztTimeOutCount >= AZT_TIMEOUT) {
+ if (azt_init_end)
+ printk
+ ("aztcd: Error Wait STEN_LOW commands:%x\n",
+ aztCmd);
+ break;
+ }
+ } while (aztIndatum & AFL_STATUS);
+}
+
+/* Wait for DTEN=Low = handshake signal 'Data available'*/
+# define DTEN_LOW dten_low()
+static void dten_low(void)
+{
+ aztTimeOutCount = 0;
+ do {
+ aztIndatum = inb(STATUS_PORT);
+ aztTimeOutCount++;
+ if (aztTimeOutCount >= AZT_TIMEOUT) {
+ printk("aztcd: Error Wait DTEN_OK\n");
+ break;
+ }
+ } while (aztIndatum & AFL_DATA);
+}
+
+/*
+ * Macro for timer wait on STEN=Low, should only be used for 'slow' commands;
+ * may cause kernel panic when used in the wrong place
+*/
+#define STEN_LOW_WAIT statusAzt()
+static void statusAzt(void)
+{
+ AztTimeout = AZT_STATUS_DELAY;
+ SET_TIMER(aztStatTimer, HZ / 100);
+ sleep_on(&azt_waitq);
+ if (AztTimeout <= 0)
+ printk("aztcd: Error Wait STEN_LOW_WAIT command:%x\n",
+ aztCmd);
+ return;
+}
+
+static void aztStatTimer(void)
+{
+ if (!(inb(STATUS_PORT) & AFL_STATUS)) {
+ wake_up(&azt_waitq);
+ return;
+ }
+ AztTimeout--;
+ if (AztTimeout <= 0) {
+ wake_up(&azt_waitq);
+ printk("aztcd: Error aztStatTimer: Timeout\n");
+ return;
+ }
+ SET_TIMER(aztStatTimer, HZ / 100);
+}
+
+/*##########################################################################
+ CDROM Drive Command Functions
+ ##########################################################################
+*/
+/*
+ * Send a single command, return -1 on error, else 0
+*/
+static int aztSendCmd(int cmd)
+{
+ unsigned char data;
+ int retry;
+
+#ifdef AZT_DEBUG
+ printk("aztcd: Executing command %x\n", cmd);
+#endif
+
+ if ((azt_port == 0x1f0) || (azt_port == 0x170))
+ SWITCH_IDE_SLAVE; /*switch IDE interface to slave configuration */
+
+ aztCmd = cmd;
+ outb(POLLED, MODE_PORT);
+ do {
+ if (inb(STATUS_PORT) & AFL_STATUS)
+ break;
+ inb(DATA_PORT); /* if status left from last command, read and */
+ } while (1); /* discard it */
+ do {
+ if (inb(STATUS_PORT) & AFL_DATA)
+ break;
+ inb(DATA_PORT); /* if data left from last command, read and */
+ } while (1); /* discard it */
+ for (retry = 0; retry < AZT_RETRY_ATTEMPTS; retry++) {
+ outb((unsigned char) cmd, CMD_PORT);
+ STEN_LOW;
+ data = inb(DATA_PORT);
+ if (data == AFL_OP_OK) {
+ return 0;
+ } /*OP_OK? */
+ if (data == AFL_OP_ERR) {
+ STEN_LOW;
+ data = inb(DATA_PORT);
+ printk
+ ("### Error 1 aztcd: aztSendCmd %x Error Code %x\n",
+ cmd, data);
+ }
+ }
+ if (retry >= AZT_RETRY_ATTEMPTS) {
+ printk("### Error 2 aztcd: aztSendCmd %x \n", cmd);
+ azt_error = 0xA5;
+ }
+ RETURNM("aztSendCmd", -1);
+}
+
+/*
+ * Send a play or read command to the drive, return -1 on error, else 0
+*/
+static int sendAztCmd(int cmd, struct azt_Play_msf *params)
+{
+ unsigned char data;
+ int retry;
+
+#ifdef AZT_DEBUG
+ printk("aztcd: play start=%02x:%02x:%02x end=%02x:%02x:%02x\n",
+ params->start.min, params->start.sec, params->start.frame,
+ params->end.min, params->end.sec, params->end.frame);
+#endif
+ for (retry = 0; retry < AZT_RETRY_ATTEMPTS; retry++) {
+ aztSendCmd(cmd);
+ outb(params->start.min, CMD_PORT);
+ outb(params->start.sec, CMD_PORT);
+ outb(params->start.frame, CMD_PORT);
+ outb(params->end.min, CMD_PORT);
+ outb(params->end.sec, CMD_PORT);
+ outb(params->end.frame, CMD_PORT);
+ STEN_LOW;
+ data = inb(DATA_PORT);
+ if (data == AFL_PA_OK) {
+ return 0;
+ } /*PA_OK ? */
+ if (data == AFL_PA_ERR) {
+ STEN_LOW;
+ data = inb(DATA_PORT);
+ printk
+ ("### Error 1 aztcd: sendAztCmd %x Error Code %x\n",
+ cmd, data);
+ }
+ }
+ if (retry >= AZT_RETRY_ATTEMPTS) {
+ printk("### Error 2 aztcd: sendAztCmd %x\n ", cmd);
+ azt_error = 0xA5;
+ }
+ RETURNM("sendAztCmd", -1);
+}
+
+/*
+ * Send a seek command to the drive, return -1 on error, else 0
+*/
+static int aztSeek(struct azt_Play_msf *params)
+{
+ unsigned char data;
+ int retry;
+
+#ifdef AZT_DEBUG
+ printk("aztcd: aztSeek %02x:%02x:%02x\n",
+ params->start.min, params->start.sec, params->start.frame);
+#endif
+ for (retry = 0; retry < AZT_RETRY_ATTEMPTS; retry++) {
+ aztSendCmd(ACMD_SEEK);
+ outb(params->start.min, CMD_PORT);
+ outb(params->start.sec, CMD_PORT);
+ outb(params->start.frame, CMD_PORT);
+ STEN_LOW;
+ data = inb(DATA_PORT);
+ if (data == AFL_PA_OK) {
+ return 0;
+ } /*PA_OK ? */
+ if (data == AFL_PA_ERR) {
+ STEN_LOW;
+ data = inb(DATA_PORT);
+ printk("### Error 1 aztcd: aztSeek\n");
+ }
+ }
+ if (retry >= AZT_RETRY_ATTEMPTS) {
+ printk("### Error 2 aztcd: aztSeek\n ");
+ azt_error = 0xA5;
+ }
+ RETURNM("aztSeek", -1);
+}
+
+/* Send a Set Disk Type command
+ does not seem to work with Aztech drives, behavior is completely indepen-
+ dent on which mode is set ???
+*/
+static int aztSetDiskType(int type)
+{
+ unsigned char data;
+ int retry;
+
+#ifdef AZT_DEBUG
+ printk("aztcd: set disk type command: type= %i\n", type);
+#endif
+ for (retry = 0; retry < AZT_RETRY_ATTEMPTS; retry++) {
+ aztSendCmd(ACMD_SET_DISK_TYPE);
+ outb(type, CMD_PORT);
+ STEN_LOW;
+ data = inb(DATA_PORT);
+ if (data == AFL_PA_OK) { /*PA_OK ? */
+ azt_read_mode = type;
+ return 0;
+ }
+ if (data == AFL_PA_ERR) {
+ STEN_LOW;
+ data = inb(DATA_PORT);
+ printk
+ ("### Error 1 aztcd: aztSetDiskType %x Error Code %x\n",
+ type, data);
+ }
+ }
+ if (retry >= AZT_RETRY_ATTEMPTS) {
+ printk("### Error 2 aztcd: aztSetDiskType %x\n ", type);
+ azt_error = 0xA5;
+ }
+ RETURNM("aztSetDiskType", -1);
+}
+
+
+/* used in azt_poll to poll the status, expects another program to issue a
+ * ACMD_GET_STATUS directly before
+ */
+static int aztStatus(void)
+{
+ int st;
+/* int i;
+
+ i = inb(STATUS_PORT) & AFL_STATUS; is STEN=0? ???
+ if (!i)
+*/ STEN_LOW;
+ if (aztTimeOutCount < AZT_TIMEOUT) {
+ st = inb(DATA_PORT) & 0xFF;
+ return st;
+ } else
+ RETURNM("aztStatus", -1);
+}
+
+/*
+ * Get the drive status
+ */
+static int getAztStatus(void)
+{
+ int st;
+
+ if (aztSendCmd(ACMD_GET_STATUS))
+ RETURNM("getAztStatus 1", -1);
+ STEN_LOW;
+ st = inb(DATA_PORT) & 0xFF;
+#ifdef AZT_DEBUG
+ printk("aztcd: Status = %x\n", st);
+#endif
+ if ((st == 0xFF) || (st & AST_CMD_CHECK)) {
+ printk
+ ("aztcd: AST_CMD_CHECK error or no status available\n");
+ return -1;
+ }
+
+ if (((st & AST_MODE_BITS) != AST_BUSY)
+ && (aztAudioStatus == CDROM_AUDIO_PLAY))
+ /* XXX might be an error? look at q-channel? */
+ aztAudioStatus = CDROM_AUDIO_COMPLETED;
+
+ if ((st & AST_DSK_CHG) || (st & AST_NOT_READY)) {
+ aztDiskChanged = 1;
+ aztTocUpToDate = 0;
+ aztAudioStatus = CDROM_AUDIO_NO_STATUS;
+ }
+ return st;
+}
+
+
+/*
+ * Send a 'Play' command and get the status. Use only from the top half.
+ */
+static int aztPlay(struct azt_Play_msf *arg)
+{
+ if (sendAztCmd(ACMD_PLAY_AUDIO, arg) < 0)
+ RETURNM("aztPlay", -1);
+ return 0;
+}
+
+/*
+ * Subroutines to automatically close the door (tray) and
+ * lock it closed when the cd is mounted. Leave the tray
+ * locking as an option
+ */
+static void aztCloseDoor(void)
+{
+ aztSendCmd(ACMD_CLOSE);
+ STEN_LOW;
+ return;
+}
+
+static void aztLockDoor(void)
+{
+#if AZT_ALLOW_TRAY_LOCK
+ aztSendCmd(ACMD_LOCK);
+ STEN_LOW;
+#endif
+ return;
+}
+
+static void aztUnlockDoor(void)
+{
+#if AZT_ALLOW_TRAY_LOCK
+ aztSendCmd(ACMD_UNLOCK);
+ STEN_LOW;
+#endif
+ return;
+}
+
+/*
+ * Read a value from the drive. Should return quickly, so a busy wait
+ * is used to avoid excessive rescheduling. The read command itself must
+ * be issued with aztSendCmd() directly before
+ */
+static int aztGetValue(unsigned char *result)
+{
+ int s;
+
+ STEN_LOW;
+ if (aztTimeOutCount >= AZT_TIMEOUT) {
+ printk("aztcd: aztGetValue timeout\n");
+ return -1;
+ }
+ s = inb(DATA_PORT) & 0xFF;
+ *result = (unsigned char) s;
+ return 0;
+}
+
+/*
+ * Read the current Q-channel info. Also used for reading the
+ * table of contents.
+ */
+static int aztGetQChannelInfo(struct azt_Toc *qp)
+{
+ unsigned char notUsed;
+ int st;
+
+#ifdef AZT_DEBUG
+ printk("aztcd: starting aztGetQChannelInfo Time:%li\n", jiffies);
+#endif
+ if ((st = getAztStatus()) == -1)
+ RETURNM("aztGetQChannelInfo 1", -1);
+ if (aztSendCmd(ACMD_GET_Q_CHANNEL))
+ RETURNM("aztGetQChannelInfo 2", -1);
+ /*STEN_LOW_WAIT; ??? Dosemu0.60's cdrom.c does not like STEN_LOW_WAIT here */
+ if (aztGetValue(¬Used))
+ RETURNM("aztGetQChannelInfo 3", -1); /*??? Nullbyte einlesen */
+ if ((st & AST_MODE_BITS) == AST_INITIAL) {
+ qp->ctrl_addr = 0; /* when audio stop ACMD_GET_Q_CHANNEL returns */
+ qp->track = 0; /* only one byte with Aztech drives */
+ qp->pointIndex = 0;
+ qp->trackTime.min = 0;
+ qp->trackTime.sec = 0;
+ qp->trackTime.frame = 0;
+ qp->diskTime.min = 0;
+ qp->diskTime.sec = 0;
+ qp->diskTime.frame = 0;
+ return 0;
+ } else {
+ if (aztGetValue(&qp->ctrl_addr) < 0)
+ RETURNM("aztGetQChannelInfo 4", -1);
+ if (aztGetValue(&qp->track) < 0)
+ RETURNM("aztGetQChannelInfo 4", -1);
+ if (aztGetValue(&qp->pointIndex) < 0)
+ RETURNM("aztGetQChannelInfo 4", -1);
+ if (aztGetValue(&qp->trackTime.min) < 0)
+ RETURNM("aztGetQChannelInfo 4", -1);
+ if (aztGetValue(&qp->trackTime.sec) < 0)
+ RETURNM("aztGetQChannelInfo 4", -1);
+ if (aztGetValue(&qp->trackTime.frame) < 0)
+ RETURNM("aztGetQChannelInfo 4", -1);
+ if (aztGetValue(¬Used) < 0)
+ RETURNM("aztGetQChannelInfo 4", -1);
+ if (aztGetValue(&qp->diskTime.min) < 0)
+ RETURNM("aztGetQChannelInfo 4", -1);
+ if (aztGetValue(&qp->diskTime.sec) < 0)
+ RETURNM("aztGetQChannelInfo 4", -1);
+ if (aztGetValue(&qp->diskTime.frame) < 0)
+ RETURNM("aztGetQChannelInfo 4", -1);
+ }
+#ifdef AZT_DEBUG
+ printk("aztcd: exiting aztGetQChannelInfo Time:%li\n", jiffies);
+#endif
+ return 0;
+}
+
+/*
+ * Read the table of contents (TOC) and TOC header if necessary
+ */
+static int aztUpdateToc(void)
+{
+ int st;
+
+#ifdef AZT_DEBUG
+ printk("aztcd: starting aztUpdateToc Time:%li\n", jiffies);
+#endif
+ if (aztTocUpToDate)
+ return 0;
+
+ if (aztGetDiskInfo() < 0)
+ return -EIO;
+
+ if (aztGetToc(0) < 0)
+ return -EIO;
+
+ /*audio disk detection
+ with my Aztech drive there is no audio status bit, so I use the copy
+ protection bit of the first track. If this track is copy protected
+ (copy bit = 0), I assume, it's an audio disk. Strange, but works ??? */
+ if (!(Toc[DiskInfo.first].ctrl_addr & 0x40))
+ DiskInfo.audio = 1;
+ else
+ DiskInfo.audio = 0;
+
+ /* XA detection */
+ if (!DiskInfo.audio) {
+ azt_Play.start.min = 0; /*XA detection only seems to work */
+ azt_Play.start.sec = 2; /*when we play a track */
+ azt_Play.start.frame = 0;
+ azt_Play.end.min = 0;
+ azt_Play.end.sec = 0;
+ azt_Play.end.frame = 1;
+ if (sendAztCmd(ACMD_PLAY_READ, &azt_Play))
+ return -1;
+ DTEN_LOW;
+ for (st = 0; st < CD_FRAMESIZE; st++)
+ inb(DATA_PORT);
+ }
+ DiskInfo.xa = getAztStatus() & AST_MODE;
+ if (DiskInfo.xa) {
+ printk
+ ("aztcd: XA support experimental - mail results to Werner.Zimmermann@fht-esslingen.de\n");
+ }
+
+ /*multisession detection
+ support for multisession CDs is done automatically with Aztech drives,
+ we don't have to take care about TOC redirection; if we want the isofs
+ to take care about redirection, we have to set AZT_MULTISESSION to 1 */
+ DiskInfo.multi = 0;
+#if AZT_MULTISESSION
+ if (DiskInfo.xa) {
+ aztGetMultiDiskInfo(); /*here Disk.Info.multi is set */
+ }
+#endif
+ if (DiskInfo.multi) {
+ DiskInfo.lastSession.min = Toc[DiskInfo.next].diskTime.min;
+ DiskInfo.lastSession.sec = Toc[DiskInfo.next].diskTime.sec;
+ DiskInfo.lastSession.frame =
+ Toc[DiskInfo.next].diskTime.frame;
+ printk("aztcd: Multisession support experimental\n");
+ } else {
+ DiskInfo.lastSession.min =
+ Toc[DiskInfo.first].diskTime.min;
+ DiskInfo.lastSession.sec =
+ Toc[DiskInfo.first].diskTime.sec;
+ DiskInfo.lastSession.frame =
+ Toc[DiskInfo.first].diskTime.frame;
+ }
+
+ aztTocUpToDate = 1;
+#ifdef AZT_DEBUG
+ printk("aztcd: exiting aztUpdateToc Time:%li\n", jiffies);
+#endif
+ return 0;
+}
+
+
+/* Read the table of contents header, i.e. no. of tracks and start of first
+ * track
+ */
+static int aztGetDiskInfo(void)
+{
+ int limit;
+ unsigned char test;
+ struct azt_Toc qInfo;
+
+#ifdef AZT_DEBUG
+ printk("aztcd: starting aztGetDiskInfo Time:%li\n", jiffies);
+#endif
+ if (aztSendCmd(ACMD_SEEK_TO_LEADIN))
+ RETURNM("aztGetDiskInfo 1", -1);
+ STEN_LOW_WAIT;
+ test = 0;
+ for (limit = 300; limit > 0; limit--) {
+ if (aztGetQChannelInfo(&qInfo) < 0)
+ RETURNM("aztGetDiskInfo 2", -1);
+ if (qInfo.pointIndex == 0xA0) { /*Number of FirstTrack */
+ DiskInfo.first = qInfo.diskTime.min;
+ DiskInfo.first = azt_bcd2bin(DiskInfo.first);
+ test = test | 0x01;
+ }
+ if (qInfo.pointIndex == 0xA1) { /*Number of LastTrack */
+ DiskInfo.last = qInfo.diskTime.min;
+ DiskInfo.last = azt_bcd2bin(DiskInfo.last);
+ test = test | 0x02;
+ }
+ if (qInfo.pointIndex == 0xA2) { /*DiskLength */
+ DiskInfo.diskLength.min = qInfo.diskTime.min;
+ DiskInfo.diskLength.sec = qInfo.diskTime.sec;
+ DiskInfo.diskLength.frame = qInfo.diskTime.frame;
+ test = test | 0x04;
+ }
+ if ((qInfo.pointIndex == DiskInfo.first) && (test & 0x01)) { /*StartTime of First Track */
+ DiskInfo.firstTrack.min = qInfo.diskTime.min;
+ DiskInfo.firstTrack.sec = qInfo.diskTime.sec;
+ DiskInfo.firstTrack.frame = qInfo.diskTime.frame;
+ test = test | 0x08;
+ }
+ if (test == 0x0F)
+ break;
+ }
+#ifdef AZT_DEBUG
+ printk("aztcd: exiting aztGetDiskInfo Time:%li\n", jiffies);
+ printk
+ ("Disk Info: first %d last %d length %02X:%02X.%02X dez first %02X:%02X.%02X dez\n",
+ DiskInfo.first, DiskInfo.last, DiskInfo.diskLength.min,
+ DiskInfo.diskLength.sec, DiskInfo.diskLength.frame,
+ DiskInfo.firstTrack.min, DiskInfo.firstTrack.sec,
+ DiskInfo.firstTrack.frame);
+#endif
+ if (test != 0x0F)
+ return -1;
+ return 0;
+}
+
+#if AZT_MULTISESSION
+/*
+ * Get Multisession Disk Info
+ */
+static int aztGetMultiDiskInfo(void)
+{
+ int limit, k = 5;
+ unsigned char test;
+ struct azt_Toc qInfo;
+
+#ifdef AZT_DEBUG
+ printk("aztcd: starting aztGetMultiDiskInfo\n");
+#endif
+
+ do {
+ azt_Play.start.min = Toc[DiskInfo.last + 1].diskTime.min;
+ azt_Play.start.sec = Toc[DiskInfo.last + 1].diskTime.sec;
+ azt_Play.start.frame =
+ Toc[DiskInfo.last + 1].diskTime.frame;
+ test = 0;
+
+ for (limit = 30; limit > 0; limit--) { /*Seek for LeadIn of next session */
+ if (aztSeek(&azt_Play))
+ RETURNM("aztGetMultiDiskInfo 1", -1);
+ if (aztGetQChannelInfo(&qInfo) < 0)
+ RETURNM("aztGetMultiDiskInfo 2", -1);
+ if ((qInfo.track == 0) && (qInfo.pointIndex))
+ break; /*LeadIn found */
+ if ((azt_Play.start.sec += 10) > 59) {
+ azt_Play.start.sec = 0;
+ azt_Play.start.min++;
+ }
+ }
+ if (!limit)
+ break; /*Check, if a leadin track was found, if not we're
+ at the end of the disk */
+#ifdef AZT_DEBUG_MULTISESSION
+ printk("leadin found track %d pointIndex %x limit %d\n",
+ qInfo.track, qInfo.pointIndex, limit);
+#endif
+ for (limit = 300; limit > 0; limit--) {
+ if (++azt_Play.start.frame > 74) {
+ azt_Play.start.frame = 0;
+ if (azt_Play.start.sec > 59) {
+ azt_Play.start.sec = 0;
+ azt_Play.start.min++;
+ }
+ }
+ if (aztSeek(&azt_Play))
+ RETURNM("aztGetMultiDiskInfo 3", -1);
+ if (aztGetQChannelInfo(&qInfo) < 0)
+ RETURNM("aztGetMultiDiskInfo 4", -1);
+ if (qInfo.pointIndex == 0xA0) { /*Number of NextTrack */
+ DiskInfo.next = qInfo.diskTime.min;
+ DiskInfo.next = azt_bcd2bin(DiskInfo.next);
+ test = test | 0x01;
+ }
+ if (qInfo.pointIndex == 0xA1) { /*Number of LastTrack */
+ DiskInfo.last = qInfo.diskTime.min;
+ DiskInfo.last = azt_bcd2bin(DiskInfo.last);
+ test = test | 0x02;
+ }
+ if (qInfo.pointIndex == 0xA2) { /*DiskLength */
+ DiskInfo.diskLength.min =
+ qInfo.diskTime.min;
+ DiskInfo.diskLength.sec =
+ qInfo.diskTime.sec;
+ DiskInfo.diskLength.frame =
+ qInfo.diskTime.frame;
+ test = test | 0x04;
+ }
+ if ((qInfo.pointIndex == DiskInfo.next) && (test & 0x01)) { /*StartTime of Next Track */
+ DiskInfo.nextSession.min =
+ qInfo.diskTime.min;
+ DiskInfo.nextSession.sec =
+ qInfo.diskTime.sec;
+ DiskInfo.nextSession.frame =
+ qInfo.diskTime.frame;
+ test = test | 0x08;
+ }
+ if (test == 0x0F)
+ break;
+ }
+#ifdef AZT_DEBUG_MULTISESSION
+ printk
+ ("MultiDisk Info: first %d next %d last %d length %02x:%02x.%02x dez first %02x:%02x.%02x dez next %02x:%02x.%02x dez\n",
+ DiskInfo.first, DiskInfo.next, DiskInfo.last,
+ DiskInfo.diskLength.min, DiskInfo.diskLength.sec,
+ DiskInfo.diskLength.frame, DiskInfo.firstTrack.min,
+ DiskInfo.firstTrack.sec, DiskInfo.firstTrack.frame,
+ DiskInfo.nextSession.min, DiskInfo.nextSession.sec,
+ DiskInfo.nextSession.frame);
+#endif
+ if (test != 0x0F)
+ break;
+ else
+ DiskInfo.multi = 1; /*found TOC of more than one session */
+ aztGetToc(1);
+ } while (--k);
+
+#ifdef AZT_DEBUG
+ printk("aztcd: exiting aztGetMultiDiskInfo Time:%li\n", jiffies);
+#endif
+ return 0;
+}
+#endif
+
+/*
+ * Read the table of contents (TOC)
+ */
+static int aztGetToc(int multi)
+{
+ int i, px;
+ int limit;
+ struct azt_Toc qInfo;
+
+#ifdef AZT_DEBUG
+ printk("aztcd: starting aztGetToc Time:%li\n", jiffies);
+#endif
+ if (!multi) {
+ for (i = 0; i < MAX_TRACKS; i++)
+ Toc[i].pointIndex = 0;
+ i = DiskInfo.last + 3;
+ } else {
+ for (i = DiskInfo.next; i < MAX_TRACKS; i++)
+ Toc[i].pointIndex = 0;
+ i = DiskInfo.last + 4 - DiskInfo.next;
+ }
+
+/*Is there a good reason to stop motor before TOC read?
+ if (aztSendCmd(ACMD_STOP)) RETURNM("aztGetToc 1",-1);
+ STEN_LOW_WAIT;
+*/
+
+ if (!multi) {
+ azt_mode = 0x05;
+ if (aztSendCmd(ACMD_SEEK_TO_LEADIN))
+ RETURNM("aztGetToc 2", -1);
+ STEN_LOW_WAIT;
+ }
+ for (limit = 300; limit > 0; limit--) {
+ if (multi) {
+ if (++azt_Play.start.sec > 59) {
+ azt_Play.start.sec = 0;
+ azt_Play.start.min++;
+ }
+ if (aztSeek(&azt_Play))
+ RETURNM("aztGetToc 3", -1);
+ }
+ if (aztGetQChannelInfo(&qInfo) < 0)
+ break;
+
+ px = azt_bcd2bin(qInfo.pointIndex);
+
+ if (px > 0 && px < MAX_TRACKS && qInfo.track == 0)
+ if (Toc[px].pointIndex == 0) {
+ Toc[px] = qInfo;
+ i--;
+ }
+
+ if (i <= 0)
+ break;
+ }
+
+ Toc[DiskInfo.last + 1].diskTime = DiskInfo.diskLength;
+ Toc[DiskInfo.last].trackTime = DiskInfo.diskLength;
+
+#ifdef AZT_DEBUG_MULTISESSION
+ printk("aztcd: exiting aztGetToc\n");
+ for (i = 1; i <= DiskInfo.last + 1; i++)
+ printk
+ ("i = %2d ctl-adr = %02X track %2d px %02X %02X:%02X.%02X dez %02X:%02X.%02X dez\n",
+ i, Toc[i].ctrl_addr, Toc[i].track, Toc[i].pointIndex,
+ Toc[i].trackTime.min, Toc[i].trackTime.sec,
+ Toc[i].trackTime.frame, Toc[i].diskTime.min,
+ Toc[i].diskTime.sec, Toc[i].diskTime.frame);
+ for (i = 100; i < 103; i++)
+ printk
+ ("i = %2d ctl-adr = %02X track %2d px %02X %02X:%02X.%02X dez %02X:%02X.%02X dez\n",
+ i, Toc[i].ctrl_addr, Toc[i].track, Toc[i].pointIndex,
+ Toc[i].trackTime.min, Toc[i].trackTime.sec,
+ Toc[i].trackTime.frame, Toc[i].diskTime.min,
+ Toc[i].diskTime.sec, Toc[i].diskTime.frame);
+#endif
+
+ return limit > 0 ? 0 : -1;
+}
+
+
+/*##########################################################################
+ Kernel Interface Functions
+ ##########################################################################
+*/
+
+#ifndef MODULE
+static int __init aztcd_setup(char *str)
+{
+ int ints[4];
+
+ (void) get_options(str, ARRAY_SIZE(ints), ints);
+
+ if (ints[0] > 0)
+ azt_port = ints[1];
+ if (ints[1] > 1)
+ azt_cont = ints[2];
+ return 1;
+}
+
+__setup("aztcd=", aztcd_setup);
+
+#endif /* !MODULE */
+
+/*
+ * Checking if the media has been changed
+*/
+static int check_aztcd_media_change(struct gendisk *disk)
+{
+ if (aztDiskChanged) { /* disk changed */
+ aztDiskChanged = 0;
+ return 1;
+ } else
+ return 0; /* no change */
+}
+
+/*
+ * Kernel IO-controls
+*/
+static int aztcd_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
+ unsigned long arg)
+{
+ int i;
+ struct azt_Toc qInfo;
+ struct cdrom_ti ti;
+ struct cdrom_tochdr tocHdr;
+ struct cdrom_msf msf;
+ struct cdrom_tocentry entry;
+ struct azt_Toc *tocPtr;
+ struct cdrom_subchnl subchnl;
+ struct cdrom_volctrl volctrl;
+ void __user *argp = (void __user *)arg;
+
+#ifdef AZT_DEBUG
+ printk("aztcd: starting aztcd_ioctl - Command:%x Time: %li\n",
+ cmd, jiffies);
+ printk("aztcd Status %x\n", getAztStatus());
+#endif
+ if (!ip)
+ RETURNM("aztcd_ioctl 1", -EINVAL);
+ if (getAztStatus() < 0)
+ RETURNM("aztcd_ioctl 2", -EIO);
+ if ((!aztTocUpToDate) || (aztDiskChanged)) {
+ if ((i = aztUpdateToc()) < 0)
+ RETURNM("aztcd_ioctl 3", i); /* error reading TOC */
+ }
+
+ switch (cmd) {
+ case CDROMSTART: /* Spin up the drive. Don't know, what to do,
+ at least close the tray */
+#if AZT_PRIVATE_IOCTLS
+ if (aztSendCmd(ACMD_CLOSE))
+ RETURNM("aztcd_ioctl 4", -1);
+ STEN_LOW_WAIT;
+#endif
+ break;
+ case CDROMSTOP: /* Spin down the drive */
+ if (aztSendCmd(ACMD_STOP))
+ RETURNM("aztcd_ioctl 5", -1);
+ STEN_LOW_WAIT;
+ /* should we do anything if it fails? */
+ aztAudioStatus = CDROM_AUDIO_NO_STATUS;
+ break;
+ case CDROMPAUSE: /* Pause the drive */
+ if (aztAudioStatus != CDROM_AUDIO_PLAY)
+ return -EINVAL;
+
+ if (aztGetQChannelInfo(&qInfo) < 0) { /* didn't get q channel info */
+ aztAudioStatus = CDROM_AUDIO_NO_STATUS;
+ RETURNM("aztcd_ioctl 7", 0);
+ }
+ azt_Play.start = qInfo.diskTime; /* remember restart point */
+
+ if (aztSendCmd(ACMD_PAUSE))
+ RETURNM("aztcd_ioctl 8", -1);
+ STEN_LOW_WAIT;
+ aztAudioStatus = CDROM_AUDIO_PAUSED;
+ break;
+ case CDROMRESUME: /* Play it again, Sam */
+ if (aztAudioStatus != CDROM_AUDIO_PAUSED)
+ return -EINVAL;
+ /* restart the drive at the saved position. */
+ i = aztPlay(&azt_Play);
+ if (i < 0) {
+ aztAudioStatus = CDROM_AUDIO_ERROR;
+ return -EIO;
+ }
+ aztAudioStatus = CDROM_AUDIO_PLAY;
+ break;
+ case CDROMMULTISESSION: /*multisession support -- experimental */
+ {
+ struct cdrom_multisession ms;
+#ifdef AZT_DEBUG
+ printk("aztcd ioctl MULTISESSION\n");
+#endif
+ if (copy_from_user(&ms, argp,
+ sizeof(struct cdrom_multisession)))
+ return -EFAULT;
+ if (ms.addr_format == CDROM_MSF) {
+ ms.addr.msf.minute =
+ azt_bcd2bin(DiskInfo.lastSession.min);
+ ms.addr.msf.second =
+ azt_bcd2bin(DiskInfo.lastSession.sec);
+ ms.addr.msf.frame =
+ azt_bcd2bin(DiskInfo.lastSession.
+ frame);
+ } else if (ms.addr_format == CDROM_LBA)
+ ms.addr.lba =
+ azt_msf2hsg(&DiskInfo.lastSession);
+ else
+ return -EINVAL;
+ ms.xa_flag = DiskInfo.xa;
+ if (copy_to_user(argp, &ms,
+ sizeof(struct cdrom_multisession)))
+ return -EFAULT;
+#ifdef AZT_DEBUG
+ if (ms.addr_format == CDROM_MSF)
+ printk
+ ("aztcd multisession xa:%d, msf:%02x:%02x.%02x [%02x:%02x.%02x])\n",
+ ms.xa_flag, ms.addr.msf.minute,
+ ms.addr.msf.second, ms.addr.msf.frame,
+ DiskInfo.lastSession.min,
+ DiskInfo.lastSession.sec,
+ DiskInfo.lastSession.frame);
+ else
+ printk
+ ("aztcd multisession %d, lba:0x%08x [%02x:%02x.%02x])\n",
+ ms.xa_flag, ms.addr.lba,
+ DiskInfo.lastSession.min,
+ DiskInfo.lastSession.sec,
+ DiskInfo.lastSession.frame);
+#endif
+ return 0;
+ }
+ case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */
+ if (copy_from_user(&ti, argp, sizeof ti))
+ return -EFAULT;
+ if (ti.cdti_trk0 < DiskInfo.first
+ || ti.cdti_trk0 > DiskInfo.last
+ || ti.cdti_trk1 < ti.cdti_trk0) {
+ return -EINVAL;
+ }
+ if (ti.cdti_trk1 > DiskInfo.last)
+ ti.cdti_trk1 = DiskInfo.last;
+ azt_Play.start = Toc[ti.cdti_trk0].diskTime;
+ azt_Play.end = Toc[ti.cdti_trk1 + 1].diskTime;
+#ifdef AZT_DEBUG
+ printk("aztcd play: %02x:%02x.%02x to %02x:%02x.%02x\n",
+ azt_Play.start.min, azt_Play.start.sec,
+ azt_Play.start.frame, azt_Play.end.min,
+ azt_Play.end.sec, azt_Play.end.frame);
+#endif
+ i = aztPlay(&azt_Play);
+ if (i < 0) {
+ aztAudioStatus = CDROM_AUDIO_ERROR;
+ return -EIO;
+ }
+ aztAudioStatus = CDROM_AUDIO_PLAY;
+ break;
+ case CDROMPLAYMSF: /* Play starting at the given MSF address. */
+/* if (aztAudioStatus == CDROM_AUDIO_PLAY)
+ { if (aztSendCmd(ACMD_STOP)) RETURNM("aztcd_ioctl 9",-1);
+ STEN_LOW;
+ aztAudioStatus = CDROM_AUDIO_NO_STATUS;
+ }
+*/
+ if (copy_from_user(&msf, argp, sizeof msf))
+ return -EFAULT;
+ /* convert to bcd */
+ azt_bin2bcd(&msf.cdmsf_min0);
+ azt_bin2bcd(&msf.cdmsf_sec0);
+ azt_bin2bcd(&msf.cdmsf_frame0);
+ azt_bin2bcd(&msf.cdmsf_min1);
+ azt_bin2bcd(&msf.cdmsf_sec1);
+ azt_bin2bcd(&msf.cdmsf_frame1);
+ azt_Play.start.min = msf.cdmsf_min0;
+ azt_Play.start.sec = msf.cdmsf_sec0;
+ azt_Play.start.frame = msf.cdmsf_frame0;
+ azt_Play.end.min = msf.cdmsf_min1;
+ azt_Play.end.sec = msf.cdmsf_sec1;
+ azt_Play.end.frame = msf.cdmsf_frame1;
+#ifdef AZT_DEBUG
+ printk("aztcd play: %02x:%02x.%02x to %02x:%02x.%02x\n",
+ azt_Play.start.min, azt_Play.start.sec,
+ azt_Play.start.frame, azt_Play.end.min,
+ azt_Play.end.sec, azt_Play.end.frame);
+#endif
+ i = aztPlay(&azt_Play);
+ if (i < 0) {
+ aztAudioStatus = CDROM_AUDIO_ERROR;
+ return -EIO;
+ }
+ aztAudioStatus = CDROM_AUDIO_PLAY;
+ break;
+
+ case CDROMREADTOCHDR: /* Read the table of contents header */
+ tocHdr.cdth_trk0 = DiskInfo.first;
+ tocHdr.cdth_trk1 = DiskInfo.last;
+ if (copy_to_user(argp, &tocHdr, sizeof tocHdr))
+ return -EFAULT;
+ break;
+ case CDROMREADTOCENTRY: /* Read an entry in the table of contents */
+ if (copy_from_user(&entry, argp, sizeof entry))
+ return -EFAULT;
+ if ((!aztTocUpToDate) || aztDiskChanged)
+ aztUpdateToc();
+ if (entry.cdte_track == CDROM_LEADOUT)
+ tocPtr = &Toc[DiskInfo.last + 1];
+ else if (entry.cdte_track > DiskInfo.last
+ || entry.cdte_track < DiskInfo.first) {
+ return -EINVAL;
+ } else
+ tocPtr = &Toc[entry.cdte_track];
+ entry.cdte_adr = tocPtr->ctrl_addr;
+ entry.cdte_ctrl = tocPtr->ctrl_addr >> 4;
+ if (entry.cdte_format == CDROM_LBA)
+ entry.cdte_addr.lba =
+ azt_msf2hsg(&tocPtr->diskTime);
+ else if (entry.cdte_format == CDROM_MSF) {
+ entry.cdte_addr.msf.minute =
+ azt_bcd2bin(tocPtr->diskTime.min);
+ entry.cdte_addr.msf.second =
+ azt_bcd2bin(tocPtr->diskTime.sec);
+ entry.cdte_addr.msf.frame =
+ azt_bcd2bin(tocPtr->diskTime.frame);
+ } else {
+ return -EINVAL;
+ }
+ if (copy_to_user(argp, &entry, sizeof entry))
+ return -EFAULT;
+ break;
+ case CDROMSUBCHNL: /* Get subchannel info */
+ if (copy_from_user
+ (&subchnl, argp, sizeof(struct cdrom_subchnl)))
+ return -EFAULT;
+ if (aztGetQChannelInfo(&qInfo) < 0) {
+#ifdef AZT_DEBUG
+ printk
+ ("aztcd: exiting aztcd_ioctl - Error 3 - Command:%x\n",
+ cmd);
+#endif
+ return -EIO;
+ }
+ subchnl.cdsc_audiostatus = aztAudioStatus;
+ subchnl.cdsc_adr = qInfo.ctrl_addr;
+ subchnl.cdsc_ctrl = qInfo.ctrl_addr >> 4;
+ subchnl.cdsc_trk = azt_bcd2bin(qInfo.track);
+ subchnl.cdsc_ind = azt_bcd2bin(qInfo.pointIndex);
+ if (subchnl.cdsc_format == CDROM_LBA) {
+ subchnl.cdsc_absaddr.lba =
+ azt_msf2hsg(&qInfo.diskTime);
+ subchnl.cdsc_reladdr.lba =
+ azt_msf2hsg(&qInfo.trackTime);
+ } else { /*default */
+ subchnl.cdsc_format = CDROM_MSF;
+ subchnl.cdsc_absaddr.msf.minute =
+ azt_bcd2bin(qInfo.diskTime.min);
+ subchnl.cdsc_absaddr.msf.second =
+ azt_bcd2bin(qInfo.diskTime.sec);
+ subchnl.cdsc_absaddr.msf.frame =
+ azt_bcd2bin(qInfo.diskTime.frame);
+ subchnl.cdsc_reladdr.msf.minute =
+ azt_bcd2bin(qInfo.trackTime.min);
+ subchnl.cdsc_reladdr.msf.second =
+ azt_bcd2bin(qInfo.trackTime.sec);
+ subchnl.cdsc_reladdr.msf.frame =
+ azt_bcd2bin(qInfo.trackTime.frame);
+ }
+ if (copy_to_user(argp, &subchnl, sizeof(struct cdrom_subchnl)))
+ return -EFAULT;
+ break;
+ case CDROMVOLCTRL: /* Volume control
+ * With my Aztech CD268-01A volume control does not work, I can only
+ turn the channels on (any value !=0) or off (value==0). Maybe it
+ works better with your drive */
+ if (copy_from_user(&volctrl, argp, sizeof(volctrl)))
+ return -EFAULT;
+ azt_Play.start.min = 0x21;
+ azt_Play.start.sec = 0x84;
+ azt_Play.start.frame = volctrl.channel0;
+ azt_Play.end.min = volctrl.channel1;
+ azt_Play.end.sec = volctrl.channel2;
+ azt_Play.end.frame = volctrl.channel3;
+ sendAztCmd(ACMD_SET_VOLUME, &azt_Play);
+ STEN_LOW_WAIT;
+ break;
+ case CDROMEJECT:
+ aztUnlockDoor(); /* Assume user knows what they're doing */
+ /* all drives can at least stop! */
+ if (aztAudioStatus == CDROM_AUDIO_PLAY) {
+ if (aztSendCmd(ACMD_STOP))
+ RETURNM("azt_ioctl 10", -1);
+ STEN_LOW_WAIT;
+ }
+ if (aztSendCmd(ACMD_EJECT))
+ RETURNM("azt_ioctl 11", -1);
+ STEN_LOW_WAIT;
+ aztAudioStatus = CDROM_AUDIO_NO_STATUS;
+ break;
+ case CDROMEJECT_SW:
+ azt_auto_eject = (char) arg;
+ break;
+ case CDROMRESET:
+ outb(ACMD_SOFT_RESET, CMD_PORT); /*send reset */
+ STEN_LOW;
+ if (inb(DATA_PORT) != AFL_OP_OK) { /*OP_OK? */
+ printk
+ ("aztcd: AZTECH CD-ROM drive does not respond\n");
+ }
+ break;
+/*Take care, the following code is not compatible with other CD-ROM drivers,
+ use it at your own risk with cdplay.c. Set AZT_PRIVATE_IOCTLS to 0 in aztcd.h,
+ if you do not want to use it!
+*/
+#if AZT_PRIVATE_IOCTLS
+ case CDROMREADCOOKED: /*read data in mode 1 (2048 Bytes) */
+ case CDROMREADRAW: /*read data in mode 2 (2336 Bytes) */
+ {
+ if (copy_from_user(&msf, argp, sizeof msf))
+ return -EFAULT;
+ /* convert to bcd */
+ azt_bin2bcd(&msf.cdmsf_min0);
+ azt_bin2bcd(&msf.cdmsf_sec0);
+ azt_bin2bcd(&msf.cdmsf_frame0);
+ msf.cdmsf_min1 = 0;
+ msf.cdmsf_sec1 = 0;
+ msf.cdmsf_frame1 = 1; /*read only one frame */
+ azt_Play.start.min = msf.cdmsf_min0;
+ azt_Play.start.sec = msf.cdmsf_sec0;
+ azt_Play.start.frame = msf.cdmsf_frame0;
+ azt_Play.end.min = msf.cdmsf_min1;
+ azt_Play.end.sec = msf.cdmsf_sec1;
+ azt_Play.end.frame = msf.cdmsf_frame1;
+ if (cmd == CDROMREADRAW) {
+ if (DiskInfo.xa) {
+ return -1; /*XA Disks can't be read raw */
+ } else {
+ if (sendAztCmd(ACMD_PLAY_READ_RAW, &azt_Play))
+ return -1;
+ DTEN_LOW;
+ insb(DATA_PORT, buf, CD_FRAMESIZE_RAW);
+ if (copy_to_user(argp, &buf, CD_FRAMESIZE_RAW))
+ return -EFAULT;
+ }
+ } else
+ /*CDROMREADCOOKED*/ {
+ if (sendAztCmd(ACMD_PLAY_READ, &azt_Play))
+ return -1;
+ DTEN_LOW;
+ insb(DATA_PORT, buf, CD_FRAMESIZE);
+ if (copy_to_user(argp, &buf, CD_FRAMESIZE))
+ return -EFAULT;
+ }
+ }
+ break;
+ case CDROMSEEK: /*seek msf address */
+ if (copy_from_user(&msf, argp, sizeof msf))
+ return -EFAULT;
+ /* convert to bcd */
+ azt_bin2bcd(&msf.cdmsf_min0);
+ azt_bin2bcd(&msf.cdmsf_sec0);
+ azt_bin2bcd(&msf.cdmsf_frame0);
+ azt_Play.start.min = msf.cdmsf_min0;
+ azt_Play.start.sec = msf.cdmsf_sec0;
+ azt_Play.start.frame = msf.cdmsf_frame0;
+ if (aztSeek(&azt_Play))
+ return -1;
+ break;
+#endif /*end of incompatible code */
+ case CDROMREADMODE1: /*set read data in mode 1 */
+ return aztSetDiskType(AZT_MODE_1);
+ case CDROMREADMODE2: /*set read data in mode 2 */
+ return aztSetDiskType(AZT_MODE_2);
+ default:
+ return -EINVAL;
+ }
+#ifdef AZT_DEBUG
+ printk("aztcd: exiting aztcd_ioctl Command:%x Time:%li\n", cmd,
+ jiffies);
+#endif
+ return 0;
+}
+
+/*
+ * Take care of the different block sizes between cdrom and Linux.
+ * When Linux gets variable block sizes this will probably go away.
+ */
+static void azt_transfer(void)
+{
+#ifdef AZT_TEST
+ printk("aztcd: executing azt_transfer Time:%li\n", jiffies);
+#endif
+ if (!current_valid())
+ return;
+
+ while (CURRENT->nr_sectors) {
+ int bn = CURRENT->sector / 4;
+ int i;
+ for (i = 0; i < AZT_BUF_SIZ && azt_buf_bn[i] != bn; ++i);
+ if (i < AZT_BUF_SIZ) {
+ int offs = (i * 4 + (CURRENT->sector & 3)) * 512;
+ int nr_sectors = 4 - (CURRENT->sector & 3);
+ if (azt_buf_out != i) {
+ azt_buf_out = i;
+ if (azt_buf_bn[i] != bn) {
+ azt_buf_out = -1;
+ continue;
+ }
+ }
+ if (nr_sectors > CURRENT->nr_sectors)
+ nr_sectors = CURRENT->nr_sectors;
+ memcpy(CURRENT->buffer, azt_buf + offs,
+ nr_sectors * 512);
+ CURRENT->nr_sectors -= nr_sectors;
+ CURRENT->sector += nr_sectors;
+ CURRENT->buffer += nr_sectors * 512;
+ } else {
+ azt_buf_out = -1;
+ break;
+ }
+ }
+}
+
+static void do_aztcd_request(request_queue_t * q)
+{
+#ifdef AZT_TEST
+ printk(" do_aztcd_request(%ld+%ld) Time:%li\n", CURRENT->sector,
+ CURRENT->nr_sectors, jiffies);
+#endif
+ if (DiskInfo.audio) {
+ printk("aztcd: Error, tried to mount an Audio CD\n");
+ end_request(CURRENT, 0);
+ return;
+ }
+ azt_transfer_is_active = 1;
+ while (current_valid()) {
+ azt_transfer();
+ if (CURRENT->nr_sectors == 0) {
+ end_request(CURRENT, 1);
+ } else {
+ azt_buf_out = -1; /* Want to read a block not in buffer */
+ if (azt_state == AZT_S_IDLE) {
+ if ((!aztTocUpToDate) || aztDiskChanged) {
+ if (aztUpdateToc() < 0) {
+ while (current_valid())
+ end_request(CURRENT, 0);
+ break;
+ }
+ }
+ azt_state = AZT_S_START;
+ AztTries = 5;
+ SET_TIMER(azt_poll, HZ / 100);
+ }
+ break;
+ }
+ }
+ azt_transfer_is_active = 0;
+#ifdef AZT_TEST2
+ printk
+ ("azt_next_bn:%x azt_buf_in:%x azt_buf_out:%x azt_buf_bn:%x\n",
+ azt_next_bn, azt_buf_in, azt_buf_out, azt_buf_bn[azt_buf_in]);
+ printk(" do_aztcd_request ends Time:%li\n", jiffies);
+#endif
+}
+
+
+static void azt_invalidate_buffers(void)
+{
+ int i;
+
+#ifdef AZT_DEBUG
+ printk("aztcd: executing azt_invalidate_buffers\n");
+#endif
+ for (i = 0; i < AZT_BUF_SIZ; ++i)
+ azt_buf_bn[i] = -1;
+ azt_buf_out = -1;
+}
+
+/*
+ * Open the device special file. Check that a disk is in.
+ */
+static int aztcd_open(struct inode *ip, struct file *fp)
+{
+ int st;
+
+#ifdef AZT_DEBUG
+ printk("aztcd: starting aztcd_open\n");
+#endif
+
+ if (aztPresent == 0)
+ return -ENXIO; /* no hardware */
+
+ if (!azt_open_count && azt_state == AZT_S_IDLE) {
+ azt_invalidate_buffers();
+
+ st = getAztStatus(); /* check drive status */
+ if (st == -1)
+ goto err_out; /* drive doesn't respond */
+
+ if (st & AST_DOOR_OPEN) { /* close door, then get the status again. */
+ printk("aztcd: Door Open?\n");
+ aztCloseDoor();
+ st = getAztStatus();
+ }
+
+ if ((st & AST_NOT_READY) || (st & AST_DSK_CHG)) { /*no disk in drive or changed */
+ printk
+ ("aztcd: Disk Changed or No Disk in Drive?\n");
+ aztTocUpToDate = 0;
+ }
+ if (aztUpdateToc())
+ goto err_out;
+
+ }
+ ++azt_open_count;
+ aztLockDoor();
+
+#ifdef AZT_DEBUG
+ printk("aztcd: exiting aztcd_open\n");
+#endif
+ return 0;
+
+ err_out:
+ return -EIO;
+}
+
+
+/*
+ * On close, we flush all azt blocks from the buffer cache.
+ */
+static int aztcd_release(struct inode *inode, struct file *file)
+{
+#ifdef AZT_DEBUG
+ printk("aztcd: executing aztcd_release\n");
+ printk("inode: %p, device: %s file: %p\n", inode,
+ inode->i_bdev->bd_disk->disk_name, file);
+#endif
+ if (!--azt_open_count) {
+ azt_invalidate_buffers();
+ aztUnlockDoor();
+ if (azt_auto_eject)
+ aztSendCmd(ACMD_EJECT);
+ CLEAR_TIMER;
+ }
+ return 0;
+}
+
+static struct gendisk *azt_disk;
+
+/*
+ * Test for presence of drive and initialize it. Called at boot time.
+ */
+
+static int __init aztcd_init(void)
+{
+ long int count, max_count;
+ unsigned char result[50];
+ int st;
+ void* status = NULL;
+ int i = 0;
+ int ret = 0;
+
+ if (azt_port == 0) {
+ printk(KERN_INFO "aztcd: no Aztech CD-ROM Initialization");
+ return -EIO;
+ }
+
+ printk(KERN_INFO "aztcd: AZTECH, ORCHID, OKANO, WEARNES, TXC, CyDROM "
+ "CD-ROM Driver\n");
+ printk(KERN_INFO "aztcd: (C) 1994-98 W.Zimmermann\n");
+ if (azt_port == -1) {
+ printk
+ ("aztcd: DriverVersion=%s For IDE/ATAPI-drives use ide-cd.c\n",
+ AZT_VERSION);
+ } else
+ printk
+ ("aztcd: DriverVersion=%s BaseAddress=0x%x For IDE/ATAPI-drives use ide-cd.c\n",
+ AZT_VERSION, azt_port);
+ printk(KERN_INFO "aztcd: If you have problems, read /usr/src/linux/"
+ "Documentation/cdrom/aztcd\n");
+
+
+#ifdef AZT_SW32 /*CDROM connected to Soundwave32 card */
+ if ((0xFF00 & inw(AZT_SW32_ID_REG)) != 0x4500) {
+ printk
+ ("aztcd: no Soundwave32 card detected at base:%x init:%x config:%x id:%x\n",
+ AZT_SW32_BASE_ADDR, AZT_SW32_INIT,
+ AZT_SW32_CONFIG_REG, AZT_SW32_ID_REG);
+ return -EIO;
+ } else {
+ printk(KERN_INFO
+ "aztcd: Soundwave32 card detected at %x Version %x\n",
+ AZT_SW32_BASE_ADDR, inw(AZT_SW32_ID_REG));
+ outw(AZT_SW32_INIT, AZT_SW32_CONFIG_REG);
+ for (count = 0; count < 10000; count++); /*delay a bit */
+ }
+#endif
+
+ /* check for presence of drive */
+
+ if (azt_port == -1) { /* autoprobing for proprietary interface */
+ for (i = 0; (azt_port_auto[i] != 0) && (i < 16); i++) {
+ azt_port = azt_port_auto[i];
+ printk(KERN_INFO "aztcd: Autoprobing BaseAddress=0x%x"
+ "\n", azt_port);
+ /*proprietary interfaces need 4 bytes */
+ if (!request_region(azt_port, 4, "aztcd")) {
+ continue;
+ }
+ outb(POLLED, MODE_PORT);
+ inb(CMD_PORT);
+ inb(CMD_PORT);
+ outb(ACMD_GET_VERSION, CMD_PORT); /*Try to get version info */
+
+ aztTimeOutCount = 0;
+ do {
+ aztIndatum = inb(STATUS_PORT);
+ aztTimeOutCount++;
+ if (aztTimeOutCount >= AZT_FAST_TIMEOUT)
+ break;
+ } while (aztIndatum & AFL_STATUS);
+ if (inb(DATA_PORT) == AFL_OP_OK) { /* OK drive found */
+ break;
+ }
+ else { /* Drive not found on this port - try next one */
+ release_region(azt_port, 4);
+ }
+ }
+ if ((i == 16) || (azt_port_auto[i] == 0)) {
+ printk(KERN_INFO "aztcd: no AZTECH CD-ROM drive found\n");
+ return -EIO;
+ }
+ } else { /* no autoprobing */
+ if ((azt_port == 0x1f0) || (azt_port == 0x170))
+ status = request_region(azt_port, 8, "aztcd"); /*IDE-interfaces need 8 bytes */
+ else
+ status = request_region(azt_port, 4, "aztcd"); /*proprietary interfaces need 4 bytes */
+ if (!status) {
+ printk(KERN_WARNING "aztcd: conflict, I/O port (%X) "
+ "already used\n", azt_port);
+ return -EIO;
+ }
+
+ if ((azt_port == 0x1f0) || (azt_port == 0x170))
+ SWITCH_IDE_SLAVE; /*switch IDE interface to slave configuration */
+
+ outb(POLLED, MODE_PORT);
+ inb(CMD_PORT);
+ inb(CMD_PORT);
+ outb(ACMD_GET_VERSION, CMD_PORT); /*Try to get version info */
+
+ aztTimeOutCount = 0;
+ do {
+ aztIndatum = inb(STATUS_PORT);
+ aztTimeOutCount++;
+ if (aztTimeOutCount >= AZT_FAST_TIMEOUT)
+ break;
+ } while (aztIndatum & AFL_STATUS);
+
+ if (inb(DATA_PORT) != AFL_OP_OK) { /*OP_OK? If not, reset and try again */
+#ifndef MODULE
+ if (azt_cont != 0x79) {
+ printk(KERN_WARNING "aztcd: no AZTECH CD-ROM "
+ "drive found-Try boot parameter aztcd="
+ ",0x79\n");
+ ret = -EIO;
+ goto err_out;
+ }
+#else
+ if (0) {
+ }
+#endif
+ else {
+ printk(KERN_INFO "aztcd: drive reset - "
+ "please wait\n");
+ for (count = 0; count < 50; count++) {
+ inb(STATUS_PORT); /*removing all data from earlier tries */
+ inb(DATA_PORT);
+ }
+ outb(POLLED, MODE_PORT);
+ inb(CMD_PORT);
+ inb(CMD_PORT);
+ getAztStatus(); /*trap errors */
+ outb(ACMD_SOFT_RESET, CMD_PORT); /*send reset */
+ STEN_LOW;
+ if (inb(DATA_PORT) != AFL_OP_OK) { /*OP_OK? */
+ printk(KERN_WARNING "aztcd: no AZTECH "
+ "CD-ROM drive found\n");
+ ret = -EIO;
+ goto err_out;
+ }
+
+ for (count = 0; count < AZT_TIMEOUT;
+ count++)
+ barrier(); /* Stop gcc 2.96 being smart */
+ /* use udelay(), damnit -- AV */
+
+ if ((st = getAztStatus()) == -1) {
+ printk(KERN_WARNING "aztcd: Drive Status"
+ " Error Status=%x\n", st);
+ ret = -EIO;
+ goto err_out;
+ }
+#ifdef AZT_DEBUG
+ printk(KERN_DEBUG "aztcd: Status = %x\n", st);
+#endif
+ outb(POLLED, MODE_PORT);
+ inb(CMD_PORT);
+ inb(CMD_PORT);
+ outb(ACMD_GET_VERSION, CMD_PORT); /*GetVersion */
+ STEN_LOW;
+ OP_OK;
+ }
+ }
+ }
+
+ azt_init_end = 1;
+ STEN_LOW;
+ result[0] = inb(DATA_PORT); /*reading in a null byte??? */
+ for (count = 1; count < 50; count++) { /*Reading version string */
+ aztTimeOutCount = 0; /*here we must implement STEN_LOW differently */
+ do {
+ aztIndatum = inb(STATUS_PORT); /*because we want to exit by timeout */
+ aztTimeOutCount++;
+ if (aztTimeOutCount >= AZT_FAST_TIMEOUT)
+ break;
+ } while (aztIndatum & AFL_STATUS);
+ if (aztTimeOutCount >= AZT_FAST_TIMEOUT)
+ break; /*all chars read? */
+ result[count] = inb(DATA_PORT);
+ }
+ if (count > 30)
+ max_count = 30; /*print max.30 chars of the version string */
+ else
+ max_count = count;
+ printk(KERN_INFO "aztcd: FirmwareVersion=");
+ for (count = 1; count < max_count; count++)
+ printk("%c", result[count]);
+ printk("<<>> ");
+
+ if ((result[1] == 'A') && (result[2] == 'Z') && (result[3] == 'T')) {
+ printk("AZTECH drive detected\n");
+ /*AZTECH*/}
+ else if ((result[2] == 'C') && (result[3] == 'D')
+ && (result[4] == 'D')) {
+ printk("ORCHID or WEARNES drive detected\n"); /*ORCHID or WEARNES */
+ } else if ((result[1] == 0x03) && (result[2] == '5')) {
+ printk("TXC or CyCDROM drive detected\n"); /*Conrad TXC, CyCDROM */
+ } else { /*OTHERS or none */
+ printk("\nunknown drive or firmware version detected\n");
+ printk
+ ("aztcd may not run stable, if you want to try anyhow,\n");
+ printk("boot with: aztcd=,0x79\n");
+ if ((azt_cont != 0x79)) {
+ printk("aztcd: FirmwareVersion=");
+ for (count = 1; count < 5; count++)
+ printk("%c", result[count]);
+ printk("<<>> ");
+ printk("Aborted\n");
+ ret = -EIO;
+ goto err_out;
+ }
+ }
+ azt_disk = alloc_disk(1);
+ if (!azt_disk)
+ goto err_out;
+
+ if (register_blkdev(MAJOR_NR, "aztcd")) {
+ ret = -EIO;
+ goto err_out2;
+ }
+
+ azt_queue = blk_init_queue(do_aztcd_request, &aztSpin);
+ if (!azt_queue) {
+ ret = -ENOMEM;
+ goto err_out3;
+ }
+
+ blk_queue_hardsect_size(azt_queue, 2048);
+ azt_disk->major = MAJOR_NR;
+ azt_disk->first_minor = 0;
+ azt_disk->fops = &azt_fops;
+ sprintf(azt_disk->disk_name, "aztcd");
+ azt_disk->queue = azt_queue;
+ add_disk(azt_disk);
+ azt_invalidate_buffers();
+ aztPresent = 1;
+ aztCloseDoor();
+ return 0;
+err_out3:
+ unregister_blkdev(MAJOR_NR, "aztcd");
+err_out2:
+ put_disk(azt_disk);
+err_out:
+ if ((azt_port == 0x1f0) || (azt_port == 0x170)) {
+ SWITCH_IDE_MASTER;
+ release_region(azt_port, 8); /*IDE-interface */
+ } else
+ release_region(azt_port, 4); /*proprietary interface */
+ return ret;
+
+}
+
+static void __exit aztcd_exit(void)
+{
+ del_gendisk(azt_disk);
+ put_disk(azt_disk);
+ if ((unregister_blkdev(MAJOR_NR, "aztcd") == -EINVAL)) {
+ printk("What's that: can't unregister aztcd\n");
+ return;
+ }
+ blk_cleanup_queue(azt_queue);
+ if ((azt_port == 0x1f0) || (azt_port == 0x170)) {
+ SWITCH_IDE_MASTER;
+ release_region(azt_port, 8); /*IDE-interface */
+ } else
+ release_region(azt_port, 4); /*proprietary interface */
+ printk(KERN_INFO "aztcd module released.\n");
+}
+
+module_init(aztcd_init);
+module_exit(aztcd_exit);
+
+/*##########################################################################
+ Aztcd State Machine: Controls Drive Operating State
+ ##########################################################################
+*/
+static void azt_poll(void)
+{
+ int st = 0;
+ int loop_ctl = 1;
+ int skip = 0;
+
+ if (azt_error) {
+ if (aztSendCmd(ACMD_GET_ERROR))
+ RETURN("azt_poll 1");
+ STEN_LOW;
+ azt_error = inb(DATA_PORT) & 0xFF;
+ printk("aztcd: I/O error 0x%02x\n", azt_error);
+ azt_invalidate_buffers();
+#ifdef WARN_IF_READ_FAILURE
+ if (AztTries == 5)
+ printk
+ ("aztcd: Read of Block %d Failed - Maybe Audio Disk?\n",
+ azt_next_bn);
+#endif
+ if (!AztTries--) {
+ printk
+ ("aztcd: Read of Block %d Failed, Maybe Audio Disk? Giving up\n",
+ azt_next_bn);
+ if (azt_transfer_is_active) {
+ AztTries = 0;
+ loop_ctl = 0;
+ }
+ if (current_valid())
+ end_request(CURRENT, 0);
+ AztTries = 5;
+ }
+ azt_error = 0;
+ azt_state = AZT_S_STOP;
+ }
+
+ while (loop_ctl) {
+ loop_ctl = 0; /* each case must flip this back to 1 if we want
+ to come back up here */
+ switch (azt_state) {
+
+ case AZT_S_IDLE:
+#ifdef AZT_TEST3
+ if (azt_state != azt_state_old) {
+ azt_state_old = azt_state;
+ printk("AZT_S_IDLE\n");
+ }
+#endif
+ return;
+
+ case AZT_S_START:
+#ifdef AZT_TEST3
+ if (azt_state != azt_state_old) {
+ azt_state_old = azt_state;
+ printk("AZT_S_START\n");
+ }
+#endif
+ if (aztSendCmd(ACMD_GET_STATUS))
+ RETURN("azt_poll 2"); /*result will be checked by aztStatus() */
+ azt_state =
+ azt_mode == 1 ? AZT_S_READ : AZT_S_MODE;
+ AztTimeout = 3000;
+ break;
+
+ case AZT_S_MODE:
+#ifdef AZT_TEST3
+ if (azt_state != azt_state_old) {
+ azt_state_old = azt_state;
+ printk("AZT_S_MODE\n");
+ }
+#endif
+ if (!skip) {
+ if ((st = aztStatus()) != -1) {
+ if ((st & AST_DSK_CHG)
+ || (st & AST_NOT_READY)) {
+ aztDiskChanged = 1;
+ aztTocUpToDate = 0;
+ azt_invalidate_buffers();
+ end_request(CURRENT, 0);
+ printk
+ ("aztcd: Disk Changed or Not Ready 1 - Unmount Disk!\n");
+ }
+ } else
+ break;
+ }
+ skip = 0;
+
+ if ((st & AST_DOOR_OPEN) || (st & AST_NOT_READY)) {
+ aztDiskChanged = 1;
+ aztTocUpToDate = 0;
+ printk
+ ("aztcd: Disk Changed or Not Ready 2 - Unmount Disk!\n");
+ end_request(CURRENT, 0);
+ printk((st & AST_DOOR_OPEN) ?
+ "aztcd: door open\n" :
+ "aztcd: disk removed\n");
+ if (azt_transfer_is_active) {
+ azt_state = AZT_S_START;
+ loop_ctl = 1; /* goto immediately */
+ break;
+ }
+ azt_state = AZT_S_IDLE;
+ while (current_valid())
+ end_request(CURRENT, 0);
+ return;
+ }
+
+/* if (aztSendCmd(ACMD_SET_MODE)) RETURN("azt_poll 3");
+ outb(0x01, DATA_PORT);
+ PA_OK;
+ STEN_LOW;
+*/
+ if (aztSendCmd(ACMD_GET_STATUS))
+ RETURN("azt_poll 4");
+ STEN_LOW;
+ azt_mode = 1;
+ azt_state = AZT_S_READ;
+ AztTimeout = 3000;
+
+ break;
+
+
+ case AZT_S_READ:
+#ifdef AZT_TEST3
+ if (azt_state != azt_state_old) {
+ azt_state_old = azt_state;
+ printk("AZT_S_READ\n");
+ }
+#endif
+ if (!skip) {
+ if ((st = aztStatus()) != -1) {
+ if ((st & AST_DSK_CHG)
+ || (st & AST_NOT_READY)) {
+ aztDiskChanged = 1;
+ aztTocUpToDate = 0;
+ azt_invalidate_buffers();
+ printk
+ ("aztcd: Disk Changed or Not Ready 3 - Unmount Disk!\n");
+ end_request(CURRENT, 0);
+ }
+ } else
+ break;
+ }
+
+ skip = 0;
+ if ((st & AST_DOOR_OPEN) || (st & AST_NOT_READY)) {
+ aztDiskChanged = 1;
+ aztTocUpToDate = 0;
+ printk((st & AST_DOOR_OPEN) ?
+ "aztcd: door open\n" :
+ "aztcd: disk removed\n");
+ if (azt_transfer_is_active) {
+ azt_state = AZT_S_START;
+ loop_ctl = 1;
+ break;
+ }
+ azt_state = AZT_S_IDLE;
+ while (current_valid())
+ end_request(CURRENT, 0);
+ return;
+ }
+
+ if (current_valid()) {
+ struct azt_Play_msf msf;
+ int i;
+ azt_next_bn = CURRENT->sector / 4;
+ azt_hsg2msf(azt_next_bn, &msf.start);
+ i = 0;
+ /* find out in which track we are */
+ while (azt_msf2hsg(&msf.start) >
+ azt_msf2hsg(&Toc[++i].trackTime)) {
+ };
+ if (azt_msf2hsg(&msf.start) <
+ azt_msf2hsg(&Toc[i].trackTime) -
+ AZT_BUF_SIZ) {
+ azt_read_count = AZT_BUF_SIZ; /*fast, because we read ahead */
+ /*azt_read_count=CURRENT->nr_sectors; slow, no read ahead */
+ } else /* don't read beyond end of track */
+#if AZT_MULTISESSION
+ {
+ azt_read_count =
+ (azt_msf2hsg(&Toc[i].trackTime)
+ / 4) * 4 -
+ azt_msf2hsg(&msf.start);
+ if (azt_read_count < 0)
+ azt_read_count = 0;
+ if (azt_read_count > AZT_BUF_SIZ)
+ azt_read_count =
+ AZT_BUF_SIZ;
+ printk
+ ("aztcd: warning - trying to read beyond end of track\n");
+/* printk("%i %i %li %li\n",i,azt_read_count,azt_msf2hsg(&msf.start),azt_msf2hsg(&Toc[i].trackTime));
+*/ }
+#else
+ {
+ azt_read_count = AZT_BUF_SIZ;
+ }
+#endif
+ msf.end.min = 0;
+ msf.end.sec = 0;
+ msf.end.frame = azt_read_count; /*Mitsumi here reads 0xffffff sectors */
+#ifdef AZT_TEST3
+ printk
+ ("---reading msf-address %x:%x:%x %x:%x:%x\n",
+ msf.start.min, msf.start.sec,
+ msf.start.frame, msf.end.min,
+ msf.end.sec, msf.end.frame);
+ printk
+ ("azt_next_bn:%x azt_buf_in:%x azt_buf_out:%x azt_buf_bn:%x\n",
+ azt_next_bn, azt_buf_in, azt_buf_out,
+ azt_buf_bn[azt_buf_in]);
+#endif
+ if (azt_read_mode == AZT_MODE_2) {
+ sendAztCmd(ACMD_PLAY_READ_RAW, &msf); /*XA disks in raw mode */
+ } else {
+ sendAztCmd(ACMD_PLAY_READ, &msf); /*others in cooked mode */
+ }
+ azt_state = AZT_S_DATA;
+ AztTimeout = READ_TIMEOUT;
+ } else {
+ azt_state = AZT_S_STOP;
+ loop_ctl = 1;
+ break;
+ }
+
+ break;
+
+
+ case AZT_S_DATA:
+#ifdef AZT_TEST3
+ if (azt_state != azt_state_old) {
+ azt_state_old = azt_state;
+ printk("AZT_S_DATA\n");
+ }
+#endif
+
+ st = inb(STATUS_PORT) & AFL_STATUSorDATA;
+
+ switch (st) {
+
+ case AFL_DATA:
+#ifdef AZT_TEST3
+ if (st != azt_st_old) {
+ azt_st_old = st;
+ printk("---AFL_DATA st:%x\n", st);
+ }
+#endif
+ if (!AztTries--) {
+ printk
+ ("aztcd: Read of Block %d Failed, Maybe Audio Disk ? Giving up\n",
+ azt_next_bn);
+ if (azt_transfer_is_active) {
+ AztTries = 0;
+ break;
+ }
+ if (current_valid())
+ end_request(CURRENT, 0);
+ AztTries = 5;
+ }
+ azt_state = AZT_S_START;
+ AztTimeout = READ_TIMEOUT;
+ loop_ctl = 1;
+ break;
+
+ case AFL_STATUSorDATA:
+#ifdef AZT_TEST3
+ if (st != azt_st_old) {
+ azt_st_old = st;
+ printk
+ ("---AFL_STATUSorDATA st:%x\n",
+ st);
+ }
+#endif
+ break;
+
+ default:
+#ifdef AZT_TEST3
+ if (st != azt_st_old) {
+ azt_st_old = st;
+ printk("---default: st:%x\n", st);
+ }
+#endif
+ AztTries = 5;
+ if (!current_valid() && azt_buf_in == azt_buf_out) {
+ azt_state = AZT_S_STOP;
+ loop_ctl = 1;
+ break;
+ }
+ if (azt_read_count <= 0)
+ printk
+ ("aztcd: warning - try to read 0 frames\n");
+ while (azt_read_count) { /*??? fast read ahead loop */
+ azt_buf_bn[azt_buf_in] = -1;
+ DTEN_LOW; /*??? unsolved problem, very
+ seldom we get timeouts
+ here, don't now the real
+ reason. With my drive this
+ sometimes also happens with
+ Aztech's original driver under
+ DOS. Is it a hardware bug?
+ I tried to recover from such
+ situations here. Zimmermann */
+ if (aztTimeOutCount >= AZT_TIMEOUT) {
+ printk
+ ("read_count:%d CURRENT->nr_sectors:%ld azt_buf_in:%d\n",
+ azt_read_count,
+ CURRENT->nr_sectors,
+ azt_buf_in);
+ printk
+ ("azt_transfer_is_active:%x\n",
+ azt_transfer_is_active);
+ azt_read_count = 0;
+ azt_state = AZT_S_STOP;
+ loop_ctl = 1;
+ end_request(CURRENT, 1); /*should we have here (1) or (0)? */
+ } else {
+ if (azt_read_mode ==
+ AZT_MODE_2) {
+ insb(DATA_PORT,
+ azt_buf +
+ CD_FRAMESIZE_RAW
+ * azt_buf_in,
+ CD_FRAMESIZE_RAW);
+ } else {
+ insb(DATA_PORT,
+ azt_buf +
+ CD_FRAMESIZE *
+ azt_buf_in,
+ CD_FRAMESIZE);
+ }
+ azt_read_count--;
+#ifdef AZT_TEST3
+ printk
+ ("AZT_S_DATA; ---I've read data- read_count: %d\n",
+ azt_read_count);
+ printk
+ ("azt_next_bn:%d azt_buf_in:%d azt_buf_out:%d azt_buf_bn:%d\n",
+ azt_next_bn,
+ azt_buf_in,
+ azt_buf_out,
+ azt_buf_bn
+ [azt_buf_in]);
+#endif
+ azt_buf_bn[azt_buf_in] =
+ azt_next_bn++;
+ if (azt_buf_out == -1)
+ azt_buf_out =
+ azt_buf_in;
+ azt_buf_in =
+ azt_buf_in + 1 ==
+ AZT_BUF_SIZ ? 0 :
+ azt_buf_in + 1;
+ }
+ }
+ if (!azt_transfer_is_active) {
+ while (current_valid()) {
+ azt_transfer();
+ if (CURRENT->nr_sectors ==
+ 0)
+ end_request(CURRENT, 1);
+ else
+ break;
+ }
+ }
+
+ if (current_valid()
+ && (CURRENT->sector / 4 < azt_next_bn
+ || CURRENT->sector / 4 >
+ azt_next_bn + AZT_BUF_SIZ)) {
+ azt_state = AZT_S_STOP;
+ loop_ctl = 1;
+ break;
+ }
+ AztTimeout = READ_TIMEOUT;
+ if (azt_read_count == 0) {
+ azt_state = AZT_S_STOP;
+ loop_ctl = 1;
+ break;
+ }
+ break;
+ }
+ break;
+
+
+ case AZT_S_STOP:
+#ifdef AZT_TEST3
+ if (azt_state != azt_state_old) {
+ azt_state_old = azt_state;
+ printk("AZT_S_STOP\n");
+ }
+#endif
+ if (azt_read_count != 0)
+ printk("aztcd: discard data=%x frames\n",
+ azt_read_count);
+ while (azt_read_count != 0) {
+ int i;
+ if (!(inb(STATUS_PORT) & AFL_DATA)) {
+ if (azt_read_mode == AZT_MODE_2)
+ for (i = 0;
+ i < CD_FRAMESIZE_RAW;
+ i++)
+ inb(DATA_PORT);
+ else
+ for (i = 0;
+ i < CD_FRAMESIZE; i++)
+ inb(DATA_PORT);
+ }
+ azt_read_count--;
+ }
+ if (aztSendCmd(ACMD_GET_STATUS))
+ RETURN("azt_poll 5");
+ azt_state = AZT_S_STOPPING;
+ AztTimeout = 1000;
+ break;
+
+ case AZT_S_STOPPING:
+#ifdef AZT_TEST3
+ if (azt_state != azt_state_old) {
+ azt_state_old = azt_state;
+ printk("AZT_S_STOPPING\n");
+ }
+#endif
+
+ if ((st = aztStatus()) == -1 && AztTimeout)
+ break;
+
+ if ((st != -1)
+ && ((st & AST_DSK_CHG)
+ || (st & AST_NOT_READY))) {
+ aztDiskChanged = 1;
+ aztTocUpToDate = 0;
+ azt_invalidate_buffers();
+ printk
+ ("aztcd: Disk Changed or Not Ready 4 - Unmount Disk!\n");
+ end_request(CURRENT, 0);
+ }
+
+#ifdef AZT_TEST3
+ printk("CURRENT_VALID %d azt_mode %d\n",
+ current_valid(), azt_mode);
+#endif
+
+ if (current_valid()) {
+ if (st != -1) {
+ if (azt_mode == 1) {
+ azt_state = AZT_S_READ;
+ loop_ctl = 1;
+ skip = 1;
+ break;
+ } else {
+ azt_state = AZT_S_MODE;
+ loop_ctl = 1;
+ skip = 1;
+ break;
+ }
+ } else {
+ azt_state = AZT_S_START;
+ AztTimeout = 1;
+ }
+ } else {
+ azt_state = AZT_S_IDLE;
+ return;
+ }
+ break;
+
+ default:
+ printk("aztcd: invalid state %d\n", azt_state);
+ return;
+ } /* case */
+ } /* while */
+
+
+ if (!AztTimeout--) {
+ printk("aztcd: timeout in state %d\n", azt_state);
+ azt_state = AZT_S_STOP;
+ if (aztSendCmd(ACMD_STOP))
+ RETURN("azt_poll 6");
+ STEN_LOW_WAIT;
+ };
+
+ SET_TIMER(azt_poll, HZ / 100);
+}
+
+
+/*###########################################################################
+ * Miscellaneous support functions
+ ###########################################################################
+*/
+static void azt_hsg2msf(long hsg, struct msf *msf)
+{
+ hsg += 150;
+ msf->min = hsg / 4500;
+ hsg %= 4500;
+ msf->sec = hsg / 75;
+ msf->frame = hsg % 75;
+#ifdef AZT_DEBUG
+ if (msf->min >= 70)
+ printk("aztcd: Error hsg2msf address Minutes\n");
+ if (msf->sec >= 60)
+ printk("aztcd: Error hsg2msf address Seconds\n");
+ if (msf->frame >= 75)
+ printk("aztcd: Error hsg2msf address Frames\n");
+#endif
+ azt_bin2bcd(&msf->min); /* convert to BCD */
+ azt_bin2bcd(&msf->sec);
+ azt_bin2bcd(&msf->frame);
+}
+
+static long azt_msf2hsg(struct msf *mp)
+{
+ return azt_bcd2bin(mp->frame) + azt_bcd2bin(mp->sec) * 75
+ + azt_bcd2bin(mp->min) * 4500 - CD_MSF_OFFSET;
+}
+
+static void azt_bin2bcd(unsigned char *p)
+{
+ int u, t;
+
+ u = *p % 10;
+ t = *p / 10;
+ *p = u | (t << 4);
+}
+
+static int azt_bcd2bin(unsigned char bcd)
+{
+ return (bcd >> 4) * 10 + (bcd & 0xF);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_BLOCKDEV_MAJOR(AZTECH_CDROM_MAJOR);
diff --git a/trunk/drivers/cdrom/aztcd.h b/trunk/drivers/cdrom/aztcd.h
new file mode 100644
index 000000000000..057501e31628
--- /dev/null
+++ b/trunk/drivers/cdrom/aztcd.h
@@ -0,0 +1,162 @@
+/* $Id: aztcd.h,v 2.60 1997/11/29 09:51:22 root Exp root $
+ *
+ * Definitions for a AztechCD268 CD-ROM interface
+ * Copyright (C) 1994-98 Werner Zimmermann
+ *
+ * based on Mitsumi CDROM driver by Martin Harriss
+ *
+ * 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.
+ *
+ * History: W.Zimmermann adaption to Aztech CD268-01A Version 1.3
+ * October 1994 Email: Werner.Zimmermann@fht-esslingen.de
+ */
+
+/* *** change this to set the I/O port address of your CD-ROM drive,
+ set to '-1', if you want autoprobing */
+#define AZT_BASE_ADDR -1
+
+/* list of autoprobing addresses (not more than 15), last value must be 0x000
+ Note: Autoprobing is only enabled, if AZT_BASE_ADDR is set to '-1' ! */
+#define AZT_BASE_AUTO { 0x320, 0x300, 0x310, 0x330, 0x000 }
+
+/* Uncomment this, if your CDROM is connected to a Soundwave32-soundcard
+ and configure AZT_BASE_ADDR and AZT_SW32_BASE_ADDR */
+/*#define AZT_SW32 1
+*/
+
+#ifdef AZT_SW32
+#define AZT_SW32_BASE_ADDR 0x220 /*I/O port base address of your soundcard*/
+#endif
+
+/* Set this to 1, if you want your tray to be locked, set to 0 to prevent tray
+ from locking */
+#define AZT_ALLOW_TRAY_LOCK 1
+
+/*Set this to 1 to allow auto-eject when unmounting a disk, set to 0, if you
+ don't want the auto-eject feature*/
+#define AZT_AUTO_EJECT 0
+
+/*Set this to 1, if you want to use incompatible ioctls for reading in raw and
+ cooked mode */
+#define AZT_PRIVATE_IOCTLS 1
+
+/*Set this to 1, if you want multisession support by the ISO fs. Even if you set
+ this value to '0' you can use multisession CDs. In that case the drive's firm-
+ ware will do the appropriate redirection automatically. The CD will then look
+ like a single session CD (but nevertheless all data may be read). Please read
+ chapter '5.1 Multisession support' in README.aztcd for details. Normally it's
+ uncritical to leave this setting untouched */
+#define AZT_MULTISESSION 1
+
+/*Uncomment this, if you are using a linux kernel version prior to 2.1.0 */
+/*#define AZT_KERNEL_PRIOR_2_1 */
+
+/*---------------------------------------------------------------------------*/
+/*-----nothing to be configured for normal applications below this line------*/
+
+
+/* Increase this if you get lots of timeouts; if you get kernel panic, replace
+ STEN_LOW_WAIT by STEN_LOW in the source code */
+#define AZT_STATUS_DELAY 400 /*for timer wait, STEN_LOW_WAIT*/
+#define AZT_TIMEOUT 8000000 /*for busy wait STEN_LOW, DTEN_LOW*/
+#define AZT_FAST_TIMEOUT 10000 /*for reading the version string*/
+
+/* number of times to retry a command before giving up */
+#define AZT_RETRY_ATTEMPTS 3
+
+/* port access macros */
+#define CMD_PORT azt_port
+#define DATA_PORT azt_port
+#define STATUS_PORT azt_port+1
+#define MODE_PORT azt_port+2
+#ifdef AZT_SW32
+ #define AZT_SW32_INIT (unsigned int) (0xFF00 & (AZT_BASE_ADDR*16))
+ #define AZT_SW32_CONFIG_REG AZT_SW32_BASE_ADDR+0x16 /*Soundwave32 Config. Register*/
+ #define AZT_SW32_ID_REG AZT_SW32_BASE_ADDR+0x04 /*Soundwave32 ID Version Register*/
+#endif
+
+/* status bits */
+#define AST_CMD_CHECK 0x80 /* 1 = command error */
+#define AST_DOOR_OPEN 0x40 /* 1 = door is open */
+#define AST_NOT_READY 0x20 /* 1 = no disk in the drive */
+#define AST_DSK_CHG 0x02 /* 1 = disk removed or changed */
+#define AST_MODE 0x01 /* 0=MODE1, 1=MODE2 */
+#define AST_MODE_BITS 0x1C /* Mode Bits */
+#define AST_INITIAL 0x0C /* initial, only valid ... */
+#define AST_BUSY 0x04 /* now playing, only valid
+ in combination with mode
+ bits */
+/* flag bits */
+#define AFL_DATA 0x02 /* data available if low */
+#define AFL_STATUS 0x04 /* status available if low */
+#define AFL_OP_OK 0x01 /* OP_OK command correct*/
+#define AFL_PA_OK 0x02 /* PA_OK parameter correct*/
+#define AFL_OP_ERR 0x05 /* error in command*/
+#define AFL_PA_ERR 0x06 /* error in parameters*/
+#define POLLED 0x04 /* polled mode */
+
+/* commands */
+#define ACMD_SOFT_RESET 0x10 /* reset drive */
+#define ACMD_PLAY_READ 0x20 /* read data track in cooked mode */
+#define ACMD_PLAY_READ_RAW 0x21 /* reading in raw mode*/
+#define ACMD_SEEK 0x30 /* seek msf address*/
+#define ACMD_SEEK_TO_LEADIN 0x31 /* seek to leadin track*/
+#define ACMD_GET_ERROR 0x40 /* get error code */
+#define ACMD_GET_STATUS 0x41 /* get status */
+#define ACMD_GET_Q_CHANNEL 0x50 /* read info from q channel */
+#define ACMD_EJECT 0x60 /* eject/open tray */
+#define ACMD_CLOSE 0x61 /* close tray */
+#define ACMD_LOCK 0x71 /* lock tray closed */
+#define ACMD_UNLOCK 0x72 /* unlock tray */
+#define ACMD_PAUSE 0x80 /* pause */
+#define ACMD_STOP 0x81 /* stop play */
+#define ACMD_PLAY_AUDIO 0x90 /* play audio track */
+#define ACMD_SET_VOLUME 0x93 /* set audio level */
+#define ACMD_GET_VERSION 0xA0 /* get firmware version */
+#define ACMD_SET_DISK_TYPE 0xA1 /* set disk data mode */
+
+#define MAX_TRACKS 104
+
+struct msf {
+ unsigned char min;
+ unsigned char sec;
+ unsigned char frame;
+};
+
+struct azt_Play_msf {
+ struct msf start;
+ struct msf end;
+};
+
+struct azt_DiskInfo {
+ unsigned char first;
+ unsigned char next;
+ unsigned char last;
+ struct msf diskLength;
+ struct msf firstTrack;
+ unsigned char multi;
+ struct msf nextSession;
+ struct msf lastSession;
+ unsigned char xa;
+ unsigned char audio;
+};
+
+struct azt_Toc {
+ unsigned char ctrl_addr;
+ unsigned char track;
+ unsigned char pointIndex;
+ struct msf trackTime;
+ struct msf diskTime;
+};
diff --git a/trunk/drivers/cdrom/cdrom.c b/trunk/drivers/cdrom/cdrom.c
index aa5468f487ba..3625a05bc3d3 100644
--- a/trunk/drivers/cdrom/cdrom.c
+++ b/trunk/drivers/cdrom/cdrom.c
@@ -302,7 +302,7 @@ module_param(lockdoor, bool, 0);
module_param(check_media_type, bool, 0);
module_param(mrw_format_restart, bool, 0);
-static DEFINE_MUTEX(cdrom_mutex);
+static DEFINE_SPINLOCK(cdrom_lock);
static const char *mrw_format_status[] = {
"not mrw",
@@ -438,10 +438,10 @@ int register_cdrom(struct cdrom_device_info *cdi)
cdo->generic_packet = cdrom_dummy_generic_packet;
cdinfo(CD_REG_UNREG, "drive \"/dev/%s\" registered\n", cdi->name);
- mutex_lock(&cdrom_mutex);
+ spin_lock(&cdrom_lock);
cdi->next = topCdromPtr;
topCdromPtr = cdi;
- mutex_unlock(&cdrom_mutex);
+ spin_unlock(&cdrom_lock);
return 0;
}
#undef ENSURE
@@ -452,7 +452,7 @@ int unregister_cdrom(struct cdrom_device_info *unreg)
cdinfo(CD_OPEN, "entering unregister_cdrom\n");
prev = NULL;
- mutex_lock(&cdrom_mutex);
+ spin_lock(&cdrom_lock);
cdi = topCdromPtr;
while (cdi && cdi != unreg) {
prev = cdi;
@@ -460,7 +460,7 @@ int unregister_cdrom(struct cdrom_device_info *unreg)
}
if (cdi == NULL) {
- mutex_unlock(&cdrom_mutex);
+ spin_unlock(&cdrom_lock);
return -2;
}
if (prev)
@@ -468,7 +468,7 @@ int unregister_cdrom(struct cdrom_device_info *unreg)
else
topCdromPtr = cdi->next;
- mutex_unlock(&cdrom_mutex);
+ spin_unlock(&cdrom_lock);
if (cdi->exit)
cdi->exit(cdi);
@@ -3289,137 +3289,103 @@ static struct cdrom_sysctl_settings {
int check; /* check media type */
} cdrom_sysctl_settings;
-enum cdrom_print_option {
- CTL_NAME,
- CTL_SPEED,
- CTL_SLOTS,
- CTL_CAPABILITY
-};
-
-static int cdrom_print_info(const char *header, int val, char *info,
- int *pos, enum cdrom_print_option option)
-{
- const int max_size = sizeof(cdrom_sysctl_settings.info);
- struct cdrom_device_info *cdi;
- int ret;
-
- ret = scnprintf(info + *pos, max_size - *pos, header);
- if (!ret)
- return 1;
-
- *pos += ret;
-
- for (cdi = topCdromPtr; cdi; cdi = cdi->next) {
- switch (option) {
- case CTL_NAME:
- ret = scnprintf(info + *pos, max_size - *pos,
- "\t%s", cdi->name);
- break;
- case CTL_SPEED:
- ret = scnprintf(info + *pos, max_size - *pos,
- "\t%d", cdi->speed);
- break;
- case CTL_SLOTS:
- ret = scnprintf(info + *pos, max_size - *pos,
- "\t%d", cdi->capacity);
- break;
- case CTL_CAPABILITY:
- ret = scnprintf(info + *pos, max_size - *pos,
- "\t%d", CDROM_CAN(val) != 0);
- break;
- default:
- printk(KERN_INFO "cdrom: invalid option%d\n", option);
- return 1;
- }
- if (!ret)
- return 1;
- *pos += ret;
- }
-
- return 0;
-}
-
static int cdrom_sysctl_info(ctl_table *ctl, int write, struct file * filp,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
- int pos;
+ int pos;
+ struct cdrom_device_info *cdi;
char *info = cdrom_sysctl_settings.info;
- const int max_size = sizeof(cdrom_sysctl_settings.info);
if (!*lenp || (*ppos && !write)) {
*lenp = 0;
return 0;
}
- mutex_lock(&cdrom_mutex);
-
pos = sprintf(info, "CD-ROM information, " VERSION "\n");
- if (cdrom_print_info("\ndrive name:\t", 0, info, &pos, CTL_NAME))
- goto done;
- if (cdrom_print_info("\ndrive speed:\t", 0, info, &pos, CTL_SPEED))
- goto done;
- if (cdrom_print_info("\ndrive # of slots:", 0, info, &pos, CTL_SLOTS))
- goto done;
- if (cdrom_print_info("\nCan close tray:\t",
- CDC_CLOSE_TRAY, info, &pos, CTL_CAPABILITY))
- goto done;
- if (cdrom_print_info("\nCan open tray:\t",
- CDC_OPEN_TRAY, info, &pos, CTL_CAPABILITY))
- goto done;
- if (cdrom_print_info("\nCan lock tray:\t",
- CDC_LOCK, info, &pos, CTL_CAPABILITY))
- goto done;
- if (cdrom_print_info("\nCan change speed:",
- CDC_SELECT_SPEED, info, &pos, CTL_CAPABILITY))
- goto done;
- if (cdrom_print_info("\nCan select disk:",
- CDC_SELECT_DISC, info, &pos, CTL_CAPABILITY))
- goto done;
- if (cdrom_print_info("\nCan read multisession:",
- CDC_MULTI_SESSION, info, &pos, CTL_CAPABILITY))
- goto done;
- if (cdrom_print_info("\nCan read MCN:\t",
- CDC_MCN, info, &pos, CTL_CAPABILITY))
- goto done;
- if (cdrom_print_info("\nReports media changed:",
- CDC_MEDIA_CHANGED, info, &pos, CTL_CAPABILITY))
- goto done;
- if (cdrom_print_info("\nCan play audio:\t",
- CDC_PLAY_AUDIO, info, &pos, CTL_CAPABILITY))
- goto done;
- if (cdrom_print_info("\nCan write CD-R:\t",
- CDC_CD_R, info, &pos, CTL_CAPABILITY))
- goto done;
- if (cdrom_print_info("\nCan write CD-RW:",
- CDC_CD_RW, info, &pos, CTL_CAPABILITY))
- goto done;
- if (cdrom_print_info("\nCan read DVD:\t",
- CDC_DVD, info, &pos, CTL_CAPABILITY))
- goto done;
- if (cdrom_print_info("\nCan write DVD-R:",
- CDC_DVD_R, info, &pos, CTL_CAPABILITY))
- goto done;
- if (cdrom_print_info("\nCan write DVD-RAM:",
- CDC_DVD_RAM, info, &pos, CTL_CAPABILITY))
- goto done;
- if (cdrom_print_info("\nCan read MRW:\t",
- CDC_MRW, info, &pos, CTL_CAPABILITY))
- goto done;
- if (cdrom_print_info("\nCan write MRW:\t",
- CDC_MRW_W, info, &pos, CTL_CAPABILITY))
- goto done;
- if (cdrom_print_info("\nCan write RAM:\t",
- CDC_RAM, info, &pos, CTL_CAPABILITY))
- goto done;
- if (!scnprintf(info + pos, max_size - pos, "\n\n"))
- goto done;
-doit:
- mutex_unlock(&cdrom_mutex);
- return proc_dostring(ctl, write, filp, buffer, lenp, ppos);
-done:
- printk(KERN_INFO "cdrom: info buffer too small\n");
- goto doit;
+ pos += sprintf(info+pos, "\ndrive name:\t");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%s", cdi->name);
+
+ pos += sprintf(info+pos, "\ndrive speed:\t");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", cdi->speed);
+
+ pos += sprintf(info+pos, "\ndrive # of slots:");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", cdi->capacity);
+
+ pos += sprintf(info+pos, "\nCan close tray:\t");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_CLOSE_TRAY) != 0);
+
+ pos += sprintf(info+pos, "\nCan open tray:\t");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_OPEN_TRAY) != 0);
+
+ pos += sprintf(info+pos, "\nCan lock tray:\t");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_LOCK) != 0);
+
+ pos += sprintf(info+pos, "\nCan change speed:");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_SELECT_SPEED) != 0);
+
+ pos += sprintf(info+pos, "\nCan select disk:");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_SELECT_DISC) != 0);
+
+ pos += sprintf(info+pos, "\nCan read multisession:");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_MULTI_SESSION) != 0);
+
+ pos += sprintf(info+pos, "\nCan read MCN:\t");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_MCN) != 0);
+
+ pos += sprintf(info+pos, "\nReports media changed:");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_MEDIA_CHANGED) != 0);
+
+ pos += sprintf(info+pos, "\nCan play audio:\t");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_PLAY_AUDIO) != 0);
+
+ pos += sprintf(info+pos, "\nCan write CD-R:\t");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_CD_R) != 0);
+
+ pos += sprintf(info+pos, "\nCan write CD-RW:");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_CD_RW) != 0);
+
+ pos += sprintf(info+pos, "\nCan read DVD:\t");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_DVD) != 0);
+
+ pos += sprintf(info+pos, "\nCan write DVD-R:");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_DVD_R) != 0);
+
+ pos += sprintf(info+pos, "\nCan write DVD-RAM:");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_DVD_RAM) != 0);
+
+ pos += sprintf(info+pos, "\nCan read MRW:\t");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_MRW) != 0);
+
+ pos += sprintf(info+pos, "\nCan write MRW:\t");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_MRW_W) != 0);
+
+ pos += sprintf(info+pos, "\nCan write RAM:\t");
+ for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
+ pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_RAM) != 0);
+
+ strcpy(info+pos,"\n\n");
+
+ return proc_dostring(ctl, write, filp, buffer, lenp, ppos);
}
/* Unfortunately, per device settings are not implemented through
diff --git a/trunk/drivers/cdrom/cdu31a.c b/trunk/drivers/cdrom/cdu31a.c
new file mode 100644
index 000000000000..2157c58755e0
--- /dev/null
+++ b/trunk/drivers/cdrom/cdu31a.c
@@ -0,0 +1,3251 @@
+/*
+* Sony CDU-31A CDROM interface device driver.
+*
+* Corey Minyard (minyard@wf-rch.cirr.com)
+*
+* Colossians 3:17
+*
+* See Documentation/cdrom/cdu31a for additional details about this driver.
+*
+* 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.
+*
+* WARNING - All autoprobes have been removed from the driver.
+* You MUST configure the CDU31A via a LILO config
+* at boot time or in lilo.conf. I have the
+* following in my lilo.conf:
+*
+* append="cdu31a=0x1f88,0,PAS"
+*
+* The first number is the I/O base address of the
+* card. The second is the interrupt (0 means none).
+ * The third should be "PAS" if on a Pro-Audio
+ * spectrum, or nothing if on something else.
+ *
+ * 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 lot of
+ * the following:
+ *
+ * retry_count = jiffies+ SONY_JIFFIES_TIMEOUT;
+ * while (time_before(jiffies, retry_count) && (!
+ * For finding abug in the return of the track numbers.
+ * TOC processing redone for proper multisession support.
+ *
+ *
+ * It probably a little late to be adding a history, but I guess I
+ * will start.
+ *
+ * 10/24/95 - Added support for disabling the eject button when the
+ * drive is open. Note that there is a small problem
+ * still here, if the eject button is pushed while the
+ * drive light is flashing, the drive will return a bad
+ * status and be reset. It recovers, though.
+ *
+ * 03/07/97 - Fixed a problem with timers.
+ *
+ *
+ * 18 Spetember 1997 -- Ported to Uniform CD-ROM driver by
+ * Heiko Eissfeldt with additional
+ * changes by Erik Andersen
+ *
+ * 24 January 1998 -- Removed the scd_disc_status() function, which was now
+ * just dead code left over from the port.
+ * Erik Andersen
+ *
+ * 16 July 1998 -- Drive donated to Erik Andersen by John Kodis
+ * . Work begun on fixing driver to
+ * work under 2.1.X. Added temporary extra printks
+ * which seem to slow it down enough to work.
+ *
+ * 9 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
+ *
+ * 22 October 2004 -- Make the driver work in 2.6.X
+ * Added workaround to fix hard lockups on eject
+ * Fixed door locking problem after mounting empty drive
+ * Set double-speed drives to double speed by default
+ * Removed all readahead things - not needed anymore
+ * Ondrej Zary
+*/
+
+#define DEBUG 1
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include "cdu31a.h"
+
+#define MAJOR_NR CDU31A_CDROM_MAJOR
+#include
+
+#define CDU31A_MAX_CONSECUTIVE_ATTENTIONS 10
+
+#define PFX "CDU31A: "
+
+/*
+** Edit the following data to change interrupts, DMA channels, etc.
+** Default is polled and no DMA. DMA is not recommended for double-speed
+** drives.
+*/
+static struct {
+ unsigned short base; /* I/O Base Address */
+ short int_num; /* Interrupt Number (-1 means scan for it,
+ 0 means don't use) */
+} cdu31a_addresses[] __initdata = {
+ {0}
+};
+
+static int handle_sony_cd_attention(void);
+static int read_subcode(void);
+static void sony_get_toc(void);
+static int scd_spinup(void);
+/*static int scd_open(struct inode *inode, struct file *filp);*/
+static int scd_open(struct cdrom_device_info *, int);
+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);
+
+/* Parameters for the read-ahead. */
+static unsigned int sony_next_block; /* Next 512 byte block offset */
+static unsigned int sony_blocks_left = 0; /* Number of 512 byte blocks left
+ in the current read command. */
+
+
+/* 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 cdu31a_port = 0;
+module_param(cdu31a_port, uint, 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 struct request_queue *cdu31a_queue;
+static DEFINE_SPINLOCK(cdu31a_lock); /* queue lock */
+
+static int sony_spun_up = 0; /* Has the drive been spun up? */
+
+static int sony_speed = 0; /* Last wanted speed */
+
+static int sony_xa_mode = 0; /* Is an XA disk in the drive
+ and the drive a CDU31A? */
+
+static int sony_raw_data_mode = 1; /* 1 if data tracks, 0 if audio.
+ For raw data reads. */
+
+static unsigned int sony_usage = 0; /* How many processes have the
+ drive open. */
+
+static int sony_pas_init = 0; /* Initialize the Pro-Audio
+ Spectrum card? */
+
+static struct s_sony_session_toc single_toc; /* Holds the
+ table of
+ contents. */
+
+static struct s_all_sessions_toc sony_toc; /* entries gathered from all
+ sessions */
+
+static int sony_toc_read = 0; /* Has the TOC been read for
+ the drive? */
+
+static struct s_sony_subcode last_sony_subcode; /* Points to the last
+ subcode address read */
+
+static DECLARE_MUTEX(sony_sem); /* Semaphore for drive hardware access */
+
+static int is_double_speed = 0; /* does the drive support double speed ? */
+
+static int is_auto_eject = 1; /* Door has been locked? 1=No/0=Yes */
+
+/*
+ * 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.
+ */
+static unsigned volatile char cur_pos_msf[3] = { 0, 0, 0 };
+static unsigned volatile char final_pos_msf[3] = { 0, 0, 0 };
+
+/* What IRQ is the drive using? 0 if none. */
+static int cdu31a_irq = 0;
+module_param(cdu31a_irq, int, 0);
+
+/* The interrupt handler will wake this queue up when it gets an
+ interrupts. */
+static DECLARE_WAIT_QUEUE_HEAD(cdu31a_irq_wait);
+static int irq_flag = 0;
+
+static int curr_control_reg = 0; /* Current value of the control register */
+
+/* A disk changed variable. When a disk change is detected, it will
+ all be set to TRUE. As the upper layers ask for disk_changed status
+ it will be cleared. */
+static char disk_changed;
+
+/* This was readahead_buffer once... Now it's used only for audio reads */
+static char audio_buffer[CD_FRAMESIZE_RAW];
+
+/* Used to time a short period to abort an operation after the
+ drive has been idle for a while. This keeps the light on
+ the drive from flashing for very long. */
+static struct timer_list cdu31a_abort_timer;
+
+/* Marks if the timeout has started an abort read. This is used
+ on entry to the drive to tell the code to read out the status
+ from the abort read. */
+static int abort_read_started = 0;
+
+/*
+ * Uniform cdrom interface function
+ * report back, if disc has changed from time of last request.
+ */
+static int scd_media_changed(struct cdrom_device_info *cdi, int disc_nr)
+{
+ int retval;
+
+ retval = disk_changed;
+ disk_changed = 0;
+
+ return retval;
+}
+
+/*
+ * Uniform cdrom interface function
+ * report back, if drive is ready
+ */
+static int scd_drive_status(struct cdrom_device_info *cdi, int slot_nr)
+{
+ if (CDSL_CURRENT != slot_nr)
+ /* we have no changer support */
+ return -EINVAL;
+ if (sony_spun_up)
+ return CDS_DISC_OK;
+ if (down_interruptible(&sony_sem))
+ return -ERESTARTSYS;
+ if (scd_spinup() == 0)
+ sony_spun_up = 1;
+ up(&sony_sem);
+ return sony_spun_up ? CDS_DISC_OK : CDS_DRIVE_NOT_READY;
+}
+
+static inline void enable_interrupts(void)
+{
+ 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);
+}
+
+static inline void disable_interrupts(void)
+{
+ 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);
+}
+
+/*
+ * 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)
+{
+ if (cdu31a_irq <= 0) {
+ yield();
+ } else { /* Interrupt driven */
+ DEFINE_WAIT(w);
+ int first = 1;
+
+ while (1) {
+ prepare_to_wait(&cdu31a_irq_wait, &w,
+ TASK_INTERRUPTIBLE);
+ if (first) {
+ enable_interrupts();
+ first = 0;
+ }
+
+ if (irq_flag != 0)
+ break;
+ if (!signal_pending(current)) {
+ schedule();
+ continue;
+ } else
+ disable_interrupts();
+ break;
+ }
+ finish_wait(&cdu31a_irq_wait, &w);
+ irq_flag = 0;
+ }
+}
+
+
+/*
+ * 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 int is_result_reg_not_empty(void)
+{
+ return (inb(sony_cd_fifost_reg) & SONY_RES_REG_NOT_EMP_BIT) != 0;
+}
+
+static inline void reset_drive(void)
+{
+ curr_control_reg = 0;
+ sony_toc_read = 0;
+ outb(SONY_DRIVE_RESET_BIT, sony_cd_control_reg);
+}
+
+/*
+ * Uniform cdrom interface function
+ * reset drive and return when it is ready
+ */
+static int scd_reset(struct cdrom_device_info *cdi)
+{
+ unsigned long retry_count;
+
+ if (down_interruptible(&sony_sem))
+ return -ERESTARTSYS;
+ reset_drive();
+
+ retry_count = jiffies + SONY_RESET_TIMEOUT;
+ while (time_before(jiffies, retry_count) && (!is_attention())) {
+ sony_sleep();
+ }
+
+ up(&sony_sem);
+ return 0;
+}
+
+static inline void clear_attention(void)
+{
+ outb(curr_control_reg | SONY_ATTN_CLR_BIT, sony_cd_control_reg);
+}
+
+static inline void clear_result_ready(void)
+{
+ outb(curr_control_reg | SONY_RES_RDY_CLR_BIT, sony_cd_control_reg);
+}
+
+static inline void clear_data_ready(void)
+{
+ outb(curr_control_reg | SONY_DATA_RDY_CLR_BIT,
+ sony_cd_control_reg);
+}
+
+static inline void clear_param_reg(void)
+{
+ outb(curr_control_reg | 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(curr_control_reg | SONY_RES_RDY_INT_EN_BIT,
+ sony_cd_control_reg);
+ outb(cmd, sony_cd_cmd_reg);
+}
+
+static irqreturn_t cdu31a_interrupt(int irq, void *dev_id)
+{
+ unsigned char val;
+
+ if (abort_read_started) {
+ /* We might be waiting for an abort to finish. Don't
+ disable interrupts yet, though, because we handle
+ this one here. */
+ /* Clear out the result registers. */
+ while (is_result_reg_not_empty()) {
+ val = read_result_register();
+ }
+ clear_data_ready();
+ clear_result_ready();
+
+ /* Clear out the data */
+ while (is_data_requested()) {
+ val = read_data_register();
+ }
+ abort_read_started = 0;
+
+ /* If something was waiting, wake it up now. */
+ if (waitqueue_active(&cdu31a_irq_wait)) {
+ disable_interrupts();
+ irq_flag = 1;
+ wake_up_interruptible(&cdu31a_irq_wait);
+ }
+ } else if (waitqueue_active(&cdu31a_irq_wait)) {
+ disable_interrupts();
+ irq_flag = 1;
+ wake_up_interruptible(&cdu31a_irq_wait);
+ } else {
+ disable_interrupts();
+ printk(KERN_NOTICE PFX
+ "Got an interrupt but nothing was waiting\n");
+ }
+ return IRQ_HANDLED;
+}
+
+/*
+ * give more verbose error messages
+ */
+static unsigned char *translate_error(unsigned char err_code)
+{
+ static unsigned char errbuf[80];
+
+ switch (err_code) {
+ case 0x10: return "illegal command ";
+ case 0x11: return "illegal parameter ";
+
+ case 0x20: return "not loaded ";
+ case 0x21: return "no disc ";
+ case 0x22: return "not spinning ";
+ case 0x23: return "spinning ";
+ case 0x25: return "spindle servo ";
+ case 0x26: return "focus servo ";
+ case 0x29: return "eject mechanism ";
+ case 0x2a: return "audio playing ";
+ case 0x2c: return "emergency eject ";
+
+ case 0x30: return "focus ";
+ case 0x31: return "frame sync ";
+ case 0x32: return "subcode address ";
+ case 0x33: return "block sync ";
+ case 0x34: return "header address ";
+
+ case 0x40: return "illegal track read ";
+ case 0x41: return "mode 0 read ";
+ case 0x42: return "illegal mode read ";
+ case 0x43: return "illegal block size read ";
+ case 0x44: return "mode read ";
+ case 0x45: return "form read ";
+ case 0x46: return "leadout read ";
+ case 0x47: return "buffer overrun ";
+
+ case 0x53: return "unrecoverable CIRC ";
+ case 0x57: return "unrecoverable LECC ";
+
+ case 0x60: return "no TOC ";
+ case 0x61: return "invalid subcode data ";
+ case 0x63: return "focus on TOC read ";
+ case 0x64: return "frame sync on TOC read ";
+ case 0x65: return "TOC data ";
+
+ case 0x70: return "hardware failure ";
+ case 0x91: return "leadin ";
+ case 0x92: return "leadout ";
+ case 0x93: return "data track ";
+ }
+ sprintf(errbuf, "unknown 0x%02x ", err_code);
+ return errbuf;
+}
+
+/*
+ * Set the drive parameters so the drive will auto-spin-up when a
+ * disk is inserted.
+ */
+static void set_drive_params(int want_doublespeed)
+{
+ unsigned char res_reg[12];
+ unsigned int res_size;
+ unsigned char params[3];
+
+
+ params[0] = SONY_SD_AUTO_SPIN_DOWN_TIME;
+ params[1] = 0x00; /* Never spin down the drive. */
+ do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
+ params, 2, res_reg, &res_size);
+ if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) {
+ printk(KERN_NOTICE PFX
+ "Unable to set spin-down time: 0x%2.2x\n", res_reg[1]);
+ }
+
+ params[0] = SONY_SD_MECH_CONTROL;
+ params[1] = SONY_AUTO_SPIN_UP_BIT; /* Set auto spin up */
+
+ if (is_auto_eject)
+ params[1] |= SONY_AUTO_EJECT_BIT;
+
+ if (is_double_speed && want_doublespeed) {
+ params[1] |= SONY_DOUBLE_SPEED_BIT; /* Set the drive to double speed if
+ possible */
+ }
+ do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
+ params, 2, res_reg, &res_size);
+ if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) {
+ printk(KERN_NOTICE PFX "Unable to set mechanical "
+ "parameters: 0x%2.2x\n", res_reg[1]);
+ }
+}
+
+/*
+ * Uniform cdrom interface function
+ * select reading speed for data access
+ */
+static int scd_select_speed(struct cdrom_device_info *cdi, int speed)
+{
+ if (speed == 0)
+ sony_speed = 1;
+ else
+ sony_speed = speed - 1;
+
+ if (down_interruptible(&sony_sem))
+ return -ERESTARTSYS;
+ set_drive_params(sony_speed);
+ up(&sony_sem);
+ return 0;
+}
+
+/*
+ * Uniform cdrom interface function
+ * lock or unlock eject button
+ */
+static int scd_lock_door(struct cdrom_device_info *cdi, int lock)
+{
+ if (lock == 0) {
+ is_auto_eject = 1;
+ } else {
+ is_auto_eject = 0;
+ }
+ if (down_interruptible(&sony_sem))
+ return -ERESTARTSYS;
+ set_drive_params(sony_speed);
+ up(&sony_sem);
+ return 0;
+}
+
+/*
+ * This code will reset the drive and attempt to restore sane parameters.
+ */
+static void restart_on_error(void)
+{
+ unsigned char res_reg[12];
+ unsigned int res_size;
+ unsigned long retry_count;
+
+
+ printk(KERN_NOTICE PFX "Resetting drive on error\n");
+ reset_drive();
+ retry_count = jiffies + SONY_RESET_TIMEOUT;
+ while (time_before(jiffies, retry_count) && (!is_attention())) {
+ sony_sleep();
+ }
+ set_drive_params(sony_speed);
+ do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg, &res_size);
+ if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) {
+ printk(KERN_NOTICE PFX "Unable to spin up drive: 0x%2.2x\n",
+ res_reg[1]);
+ }
+
+ msleep(2000);
+
+ sony_get_toc();
+}
+
+/*
+ * 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 long retry_count;
+
+
+ while (handle_sony_cd_attention());
+ /* Wait for the result data to be ready */
+ retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+ while (time_before(jiffies, retry_count)
+ && (is_busy() || (!(is_result_ready())))) {
+ sony_sleep();
+
+ while (handle_sony_cd_attention());
+ }
+ if (is_busy() || (!(is_result_ready()))) {
+ pr_debug(PFX "timeout out %d\n", __LINE__);
+ 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++;
+
+ /* Check for block error status result. */
+ if ((a & 0xf0) == 0x50) {
+ *result_size = 1;
+ return;
+ }
+
+ b = read_result_register();
+ *result_buffer = b;
+ result_buffer++;
+ *result_size = 2;
+
+ /*
+ * 0x20 means an error occurred. Byte 2 will have the error code.
+ * Otherwise, the command succeeded, 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()) {
+ pr_debug(PFX "timeout out %d\n",
+ __LINE__);
+ 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()) {
+ pr_debug(PFX "timeout out %d\n",
+ __LINE__);
+ 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--;
+ }
+ }
+}
+
+/*
+ * 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 long retry_count;
+ int num_retries = 0;
+
+retry_cd_operation:
+
+ while (handle_sony_cd_attention());
+
+ retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+ while (time_before(jiffies, retry_count) && (is_busy())) {
+ sony_sleep();
+
+ while (handle_sony_cd_attention());
+ }
+ if (is_busy()) {
+ pr_debug(PFX "timeout out %d\n", __LINE__);
+ 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] & 0xf0) == 0x20)
+ && (num_retries < MAX_CDU31A_RETRIES)) {
+ num_retries++;
+ msleep(100);
+ goto retry_cd_operation;
+ }
+}
+
+
+/*
+ * 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;
+ volatile int val;
+
+
+#if 0
+ pr_debug(PFX "Entering %s\n", __FUNCTION__);
+#endif
+ if (is_attention()) {
+ if (num_consecutive_attentions >
+ CDU31A_MAX_CONSECUTIVE_ATTENTIONS) {
+ printk(KERN_NOTICE PFX "Too many consecutive "
+ "attentions: %d\n", num_consecutive_attentions);
+ num_consecutive_attentions = 0;
+ pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__,
+ __LINE__);
+ 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:
+ disk_changed = 1;
+ sony_toc_read = 0;
+ sony_audio_status = CDROM_AUDIO_NO_STATUS;
+ sony_blocks_left = 0;
+ break;
+
+ case SONY_SPIN_DOWN_COMPLETE_ATTN:
+ /* Mark the disk as spun down. */
+ sony_spun_up = 0;
+ break;
+
+ case SONY_AUDIO_PLAY_DONE_ATTN:
+ sony_audio_status = CDROM_AUDIO_COMPLETED;
+ read_subcode();
+ break;
+
+ case SONY_EJECT_PUSHED_ATTN:
+ if (is_auto_eject) {
+ 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++;
+ pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
+ return 1;
+ } else if (abort_read_started) {
+ while (is_result_reg_not_empty()) {
+ val = read_result_register();
+ }
+ clear_data_ready();
+ clear_result_ready();
+ /* Clear out the data */
+ while (is_data_requested()) {
+ val = read_data_register();
+ }
+ abort_read_started = 0;
+ pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
+ return 1;
+ }
+
+ num_consecutive_attentions = 0;
+#if 0
+ pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
+#endif
+ 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 = msf[2];
+ log += msf[1] * 75;
+ log += 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;
+}
+
+/* Starts a read operation. Returns 0 on success and 1 on failure.
+ The read operation used here allows multiple sequential sectors
+ to be read and status returned for each sector. The driver will
+ read the output one at a time as the requests come and abort the
+ operation if the requested sector is not the next one from the
+ drive. */
+static int
+start_request(unsigned int sector, unsigned int nsect)
+{
+ unsigned char params[6];
+ unsigned long retry_count;
+
+
+ pr_debug(PFX "Entering %s\n", __FUNCTION__);
+ log_to_msf(sector, params);
+ size_to_buf(nsect, ¶ms[3]);
+
+ /*
+ * 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 (time_before(jiffies, retry_count) && (is_busy())) {
+ sony_sleep();
+
+ while (handle_sony_cd_attention());
+ }
+
+ if (is_busy()) {
+ printk(KERN_NOTICE PFX "Timeout while waiting "
+ "to issue command\n");
+ pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
+ return 1;
+ } else {
+ /* Issue the command */
+ clear_result_ready();
+ clear_param_reg();
+
+ write_params(params, 6);
+ write_cmd(SONY_READ_BLKERR_STAT_CMD);
+
+ sony_blocks_left = nsect * 4;
+ sony_next_block = sector * 4;
+ pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
+ return 0;
+ }
+ pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
+}
+
+/* Abort a pending read operation. Clear all the drive status variables. */
+static void abort_read(void)
+{
+ unsigned char result_reg[2];
+ int result_size;
+ volatile int val;
+
+
+ do_sony_cd_cmd(SONY_ABORT_CMD, NULL, 0, result_reg, &result_size);
+ if ((result_reg[0] & 0xf0) == 0x20) {
+ printk(KERN_ERR PFX "Aborting read, %s error\n",
+ translate_error(result_reg[1]));
+ }
+
+ while (is_result_reg_not_empty()) {
+ val = read_result_register();
+ }
+ clear_data_ready();
+ clear_result_ready();
+ /* Clear out the data */
+ while (is_data_requested()) {
+ val = read_data_register();
+ }
+
+ sony_blocks_left = 0;
+}
+
+/* Called when the timer times out. This will abort the
+ pending read operation. */
+static void handle_abort_timeout(unsigned long data)
+{
+ pr_debug(PFX "Entering %s\n", __FUNCTION__);
+ /* If it is in use, ignore it. */
+ if (down_trylock(&sony_sem) == 0) {
+ /* We can't use abort_read(), because it will sleep
+ or schedule in the timer interrupt. Just start
+ the operation, finish it on the next access to
+ the drive. */
+ clear_result_ready();
+ clear_param_reg();
+ write_cmd(SONY_ABORT_CMD);
+
+ sony_blocks_left = 0;
+ abort_read_started = 1;
+ up(&sony_sem);
+ }
+ pr_debug(PFX "Leaving %s\n", __FUNCTION__);
+}
+
+/* Actually get one sector of data from the drive. */
+static void
+input_data_sector(char *buffer)
+{
+ pr_debug(PFX "Entering %s\n", __FUNCTION__);
+
+ /* If an XA disk on a CDU31A, skip the first 12 bytes of data from
+ the disk. The real data is after that. We can use audio_buffer. */
+ if (sony_xa_mode)
+ insb(sony_cd_read_reg, audio_buffer, CD_XA_HEAD);
+
+ clear_data_ready();
+
+ insb(sony_cd_read_reg, buffer, 2048);
+
+ /* If an XA disk, we have to clear out the rest of the unused
+ error correction data. We can use audio_buffer for that. */
+ if (sony_xa_mode)
+ insb(sony_cd_read_reg, audio_buffer, CD_XA_TAIL);
+
+ pr_debug(PFX "Leaving %s\n", __FUNCTION__);
+}
+
+/* read data from the drive. Note the nsect must be <= 4. */
+static void
+read_data_block(char *buffer,
+ unsigned int block,
+ unsigned int nblocks,
+ unsigned char res_reg[], int *res_size)
+{
+ unsigned long retry_count;
+
+ pr_debug(PFX "Entering %s\n", __FUNCTION__);
+
+ res_reg[0] = 0;
+ res_reg[1] = 0;
+ *res_size = 0;
+
+ /* Wait for the drive to tell us we have something */
+ retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+ while (time_before(jiffies, retry_count) && !(is_data_ready())) {
+ while (handle_sony_cd_attention());
+
+ sony_sleep();
+ }
+ if (!(is_data_ready())) {
+ if (is_result_ready()) {
+ get_result(res_reg, res_size);
+ if ((res_reg[0] & 0xf0) != 0x20) {
+ printk(KERN_NOTICE PFX "Got result that should"
+ " have been error: %d\n", res_reg[0]);
+ res_reg[0] = 0x20;
+ res_reg[1] = SONY_BAD_DATA_ERR;
+ *res_size = 2;
+ }
+ abort_read();
+ } else {
+ pr_debug(PFX "timeout out %d\n", __LINE__);
+ res_reg[0] = 0x20;
+ res_reg[1] = SONY_TIMEOUT_OP_ERR;
+ *res_size = 2;
+ abort_read();
+ }
+ } else {
+ input_data_sector(buffer);
+ sony_blocks_left -= nblocks;
+ sony_next_block += nblocks;
+
+ /* Wait for the status from the drive. */
+ retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+ while (time_before(jiffies, retry_count)
+ && !(is_result_ready())) {
+ while (handle_sony_cd_attention());
+
+ sony_sleep();
+ }
+
+ if (!is_result_ready()) {
+ pr_debug(PFX "timeout out %d\n", __LINE__);
+ res_reg[0] = 0x20;
+ res_reg[1] = SONY_TIMEOUT_OP_ERR;
+ *res_size = 2;
+ abort_read();
+ } else {
+ get_result(res_reg, res_size);
+
+ /* If we got a buffer status, handle that. */
+ if ((res_reg[0] & 0xf0) == 0x50) {
+
+ if ((res_reg[0] ==
+ SONY_NO_CIRC_ERR_BLK_STAT)
+ || (res_reg[0] ==
+ SONY_NO_LECC_ERR_BLK_STAT)
+ || (res_reg[0] ==
+ SONY_RECOV_LECC_ERR_BLK_STAT)) {
+ /* nothing here */
+ } else {
+ printk(KERN_ERR PFX "Data block "
+ "error: 0x%x\n", res_reg[0]);
+ res_reg[0] = 0x20;
+ res_reg[1] = SONY_BAD_DATA_ERR;
+ *res_size = 2;
+ }
+
+ /* Final transfer is done for read command, get final result. */
+ if (sony_blocks_left == 0) {
+ get_result(res_reg, res_size);
+ }
+ } else if ((res_reg[0] & 0xf0) != 0x20) {
+ /* The drive gave me bad status, I don't know what to do.
+ Reset the driver and return an error. */
+ printk(KERN_ERR PFX "Invalid block "
+ "status: 0x%x\n", res_reg[0]);
+ restart_on_error();
+ res_reg[0] = 0x20;
+ res_reg[1] = SONY_BAD_DATA_ERR;
+ *res_size = 2;
+ }
+ }
+ }
+ pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
+}
+
+
+/*
+ * 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(request_queue_t * q)
+{
+ struct request *req;
+ int block, nblock, num_retries;
+ unsigned char res_reg[12];
+ unsigned int res_size;
+
+ pr_debug(PFX "Entering %s\n", __FUNCTION__);
+
+ spin_unlock_irq(q->queue_lock);
+ if (down_interruptible(&sony_sem)) {
+ spin_lock_irq(q->queue_lock);
+ return;
+ }
+
+ /* Get drive status before doing anything. */
+ while (handle_sony_cd_attention());
+
+ /* Make sure we have a valid TOC. */
+ sony_get_toc();
+
+
+ /* Make sure the timer is cancelled. */
+ del_timer(&cdu31a_abort_timer);
+
+ while (1) {
+ /*
+ * The beginning here is stolen from the hard disk driver. I hope
+ * it's right.
+ */
+ req = elv_next_request(q);
+ if (!req)
+ goto end_do_cdu31a_request;
+
+ if (!sony_spun_up)
+ scd_spinup();
+
+ block = req->sector;
+ nblock = req->nr_sectors;
+ pr_debug(PFX "request at block %d, length %d blocks\n",
+ block, nblock);
+ if (!sony_toc_read) {
+ printk(KERN_NOTICE PFX "TOC not read\n");
+ end_request(req, 0);
+ continue;
+ }
+
+ /* WTF??? */
+ 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 (((block + nblock) / 4) >= sony_toc.lead_out_start_lba) {
+ printk(KERN_NOTICE PFX "Request past end of media\n");
+ end_request(req, 0);
+ continue;
+ }
+
+ if (nblock > 4)
+ nblock = 4;
+ num_retries = 0;
+
+ try_read_again:
+ while (handle_sony_cd_attention());
+
+ if (!sony_toc_read) {
+ printk(KERN_NOTICE PFX "TOC not read\n");
+ end_request(req, 0);
+ continue;
+ }
+
+ /* If no data is left to be read from the drive, start the
+ next request. */
+ if (sony_blocks_left == 0) {
+ if (start_request(block / 4, nblock / 4)) {
+ end_request(req, 0);
+ continue;
+ }
+ }
+ /* If the requested block is not the next one waiting in
+ the driver, abort the current operation and start a
+ new one. */
+ else if (block != sony_next_block) {
+ pr_debug(PFX "Read for block %d, expected %d\n",
+ block, sony_next_block);
+ abort_read();
+ if (!sony_toc_read) {
+ printk(KERN_NOTICE PFX "TOC not read\n");
+ end_request(req, 0);
+ continue;
+ }
+ if (start_request(block / 4, nblock / 4)) {
+ printk(KERN_NOTICE PFX "start request failed\n");
+ end_request(req, 0);
+ continue;
+ }
+ }
+
+ read_data_block(req->buffer, block, nblock, res_reg, &res_size);
+
+ if (res_reg[0] != 0x20) {
+ if (!end_that_request_first(req, 1, nblock)) {
+ spin_lock_irq(q->queue_lock);
+ blkdev_dequeue_request(req);
+ end_that_request_last(req, 1);
+ spin_unlock_irq(q->queue_lock);
+ }
+ continue;
+ }
+
+ if (num_retries > MAX_CDU31A_RETRIES) {
+ end_request(req, 0);
+ continue;
+ }
+
+ num_retries++;
+ if (res_reg[1] == SONY_NOT_SPIN_ERR) {
+ do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg,
+ &res_size);
+ } else {
+ printk(KERN_NOTICE PFX "%s error for block %d, nblock %d\n",
+ translate_error(res_reg[1]), block, nblock);
+ }
+ goto try_read_again;
+ }
+ end_do_cdu31a_request:
+#if 0
+ /* After finished, cancel any pending operations. */
+ abort_read();
+#else
+ /* Start a timer to time out after a while to disable
+ the read. */
+ cdu31a_abort_timer.expires = jiffies + 2 * HZ; /* Wait 2 seconds */
+ add_timer(&cdu31a_abort_timer);
+#endif
+
+ up(&sony_sem);
+ spin_lock_irq(q->queue_lock);
+ pr_debug(PFX "Leaving %s at %d\n", __FUNCTION__, __LINE__);
+}
+
+
+/*
+ * Read the table of contents from the drive and set up TOC if
+ * successful.
+ */
+static void sony_get_toc(void)
+{
+ unsigned char res_reg[2];
+ unsigned int res_size;
+ unsigned char parms[1];
+ int session;
+ int num_spin_ups;
+ int totaltracks = 0;
+ int mint = 99;
+ int maxt = 0;
+
+ pr_debug(PFX "Entering %s\n", __FUNCTION__);
+
+ num_spin_ups = 0;
+ if (!sony_toc_read) {
+ respinup_on_gettoc:
+ /* Ignore the result, since it might error if spinning already. */
+ do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg,
+ &res_size);
+
+ 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, it's ok. */
+ if ((res_reg[1] == SONY_AUDIO_PLAYING_ERR)
+ || (res_reg[1] == 0)) {
+ goto gettoc_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_gettoc;
+ }
+
+ printk("cdu31a: Error reading TOC: %x %s\n",
+ res_reg[0], translate_error(res_reg[1]));
+ return;
+ }
+
+ gettoc_drive_spinning:
+
+ /* The idea here is we keep asking for sessions until the command
+ fails. Then we know what the last valid session on the disk is.
+ No need to check session 0, since session 0 is the same as session
+ 1; the command returns different information if you give it 0.
+ */
+#if DEBUG
+ memset(&sony_toc, 0x0e, sizeof(sony_toc));
+ memset(&single_toc, 0x0f, sizeof(single_toc));
+#endif
+ session = 1;
+ while (1) {
+/* This seems to slow things down enough to make it work. This
+ * appears to be a problem in do_sony_cd_cmd. This printk seems
+ * to address the symptoms... -Erik */
+ pr_debug(PFX "Trying session %d\n", session);
+ parms[0] = session;
+ do_sony_cd_cmd(SONY_READ_TOC_SPEC_CMD,
+ parms, 1, res_reg, &res_size);
+
+ pr_debug(PFX "%2.2x %2.2x\n", res_reg[0], res_reg[1]);
+
+ if ((res_size < 2)
+ || ((res_reg[0] & 0xf0) == 0x20)) {
+ /* An error reading the TOC, this must be past the last session. */
+ if (session == 1)
+ printk
+ ("Yikes! Couldn't read any sessions!");
+ break;
+ }
+ pr_debug(PFX "Reading session %d\n", session);
+
+ parms[0] = session;
+ do_sony_cd_cmd(SONY_REQ_TOC_DATA_SPEC_CMD,
+ parms,
+ 1,
+ (unsigned char *) &single_toc,
+ &res_size);
+ if ((res_size < 2)
+ || ((single_toc.exec_status[0] & 0xf0) ==
+ 0x20)) {
+ printk(KERN_ERR PFX "Error reading "
+ "session %d: %x %s\n",
+ session, single_toc.exec_status[0],
+ translate_error(single_toc.
+ exec_status[1]));
+ /* An error reading the TOC. Return without sony_toc_read
+ set. */
+ return;
+ }
+ pr_debug(PFX "add0 %01x, con0 %01x, poi0 %02x, "
+ "1st trk %d, dsktyp %x, dum0 %x\n",
+ single_toc.address0, single_toc.control0,
+ single_toc.point0,
+ bcd_to_int(single_toc.first_track_num),
+ single_toc.disk_type, single_toc.dummy0);
+ pr_debug(PFX "add1 %01x, con1 %01x, poi1 %02x, "
+ "lst trk %d, dummy1 %x, dum2 %x\n",
+ single_toc.address1, single_toc.control1,
+ single_toc.point1,
+ bcd_to_int(single_toc.last_track_num),
+ single_toc.dummy1, single_toc.dummy2);
+ pr_debug(PFX "add2 %01x, con2 %01x, poi2 %02x "
+ "leadout start min %d, sec %d, frame %d\n",
+ single_toc.address2, single_toc.control2,
+ single_toc.point2,
+ bcd_to_int(single_toc.lead_out_start_msf[0]),
+ bcd_to_int(single_toc.lead_out_start_msf[1]),
+ bcd_to_int(single_toc.lead_out_start_msf[2]));
+ if (res_size > 18 && single_toc.pointb0 > 0xaf)
+ pr_debug(PFX "addb0 %01x, conb0 %01x, poib0 %02x, nextsession min %d, sec %d, frame %d\n"
+ "#mode5_ptrs %02d, max_start_outer_leadout_msf min %d, sec %d, frame %d\n",
+ single_toc.addressb0,
+ single_toc.controlb0,
+ single_toc.pointb0,
+ bcd_to_int(single_toc.
+ next_poss_prog_area_msf
+ [0]),
+ bcd_to_int(single_toc.
+ next_poss_prog_area_msf
+ [1]),
+ bcd_to_int(single_toc.
+ next_poss_prog_area_msf
+ [2]),
+ single_toc.num_mode_5_pointers,
+ bcd_to_int(single_toc.
+ max_start_outer_leadout_msf
+ [0]),
+ bcd_to_int(single_toc.
+ max_start_outer_leadout_msf
+ [1]),
+ bcd_to_int(single_toc.
+ max_start_outer_leadout_msf
+ [2]));
+ if (res_size > 27 && single_toc.pointb1 > 0xaf)
+ pr_debug(PFX "addb1 %01x, conb1 %01x, poib1 %02x, %x %x %x %x #skipint_ptrs %d, #skiptrkassign %d %x\n",
+ single_toc.addressb1,
+ single_toc.controlb1,
+ single_toc.pointb1,
+ single_toc.dummyb0_1[0],
+ single_toc.dummyb0_1[1],
+ single_toc.dummyb0_1[2],
+ single_toc.dummyb0_1[3],
+ single_toc.num_skip_interval_pointers,
+ single_toc.num_skip_track_assignments,
+ single_toc.dummyb0_2);
+ if (res_size > 36 && single_toc.pointb2 > 0xaf)
+ pr_debug(PFX "addb2 %01x, conb2 %01x, poib2 %02x, %02x %02x %02x %02x %02x %02x %02x\n",
+ single_toc.addressb2,
+ single_toc.controlb2,
+ single_toc.pointb2,
+ single_toc.tracksb2[0],
+ single_toc.tracksb2[1],
+ single_toc.tracksb2[2],
+ single_toc.tracksb2[3],
+ single_toc.tracksb2[4],
+ single_toc.tracksb2[5],
+ single_toc.tracksb2[6]);
+ if (res_size > 45 && single_toc.pointb3 > 0xaf)
+ pr_debug(PFX "addb3 %01x, conb3 %01x, poib3 %02x, %02x %02x %02x %02x %02x %02x %02x\n",
+ single_toc.addressb3,
+ single_toc.controlb3,
+ single_toc.pointb3,
+ single_toc.tracksb3[0],
+ single_toc.tracksb3[1],
+ single_toc.tracksb3[2],
+ single_toc.tracksb3[3],
+ single_toc.tracksb3[4],
+ single_toc.tracksb3[5],
+ single_toc.tracksb3[6]);
+ if (res_size > 54 && single_toc.pointb4 > 0xaf)
+ pr_debug(PFX "addb4 %01x, conb4 %01x, poib4 %02x, %02x %02x %02x %02x %02x %02x %02x\n",
+ single_toc.addressb4,
+ single_toc.controlb4,
+ single_toc.pointb4,
+ single_toc.tracksb4[0],
+ single_toc.tracksb4[1],
+ single_toc.tracksb4[2],
+ single_toc.tracksb4[3],
+ single_toc.tracksb4[4],
+ single_toc.tracksb4[5],
+ single_toc.tracksb4[6]);
+ if (res_size > 63 && single_toc.pointc0 > 0xaf)
+ pr_debug(PFX "addc0 %01x, conc0 %01x, poic0 %02x, %02x %02x %02x %02x %02x %02x %02x\n",
+ single_toc.addressc0,
+ single_toc.controlc0,
+ single_toc.pointc0,
+ single_toc.dummyc0[0],
+ single_toc.dummyc0[1],
+ single_toc.dummyc0[2],
+ single_toc.dummyc0[3],
+ single_toc.dummyc0[4],
+ single_toc.dummyc0[5],
+ single_toc.dummyc0[6]);
+#undef DEBUG
+#define DEBUG 0
+
+ sony_toc.lead_out_start_msf[0] =
+ bcd_to_int(single_toc.lead_out_start_msf[0]);
+ sony_toc.lead_out_start_msf[1] =
+ bcd_to_int(single_toc.lead_out_start_msf[1]);
+ sony_toc.lead_out_start_msf[2] =
+ bcd_to_int(single_toc.lead_out_start_msf[2]);
+ sony_toc.lead_out_start_lba =
+ single_toc.lead_out_start_lba =
+ msf_to_log(sony_toc.lead_out_start_msf);
+
+ /* For points that do not exist, move the data over them
+ to the right location. */
+ if (single_toc.pointb0 != 0xb0) {
+ memmove(((char *) &single_toc) + 27,
+ ((char *) &single_toc) + 18,
+ res_size - 18);
+ res_size += 9;
+ } else if (res_size > 18) {
+ sony_toc.lead_out_start_msf[0] =
+ bcd_to_int(single_toc.
+ max_start_outer_leadout_msf
+ [0]);
+ sony_toc.lead_out_start_msf[1] =
+ bcd_to_int(single_toc.
+ max_start_outer_leadout_msf
+ [1]);
+ sony_toc.lead_out_start_msf[2] =
+ bcd_to_int(single_toc.
+ max_start_outer_leadout_msf
+ [2]);
+ sony_toc.lead_out_start_lba =
+ msf_to_log(sony_toc.
+ lead_out_start_msf);
+ }
+ if (single_toc.pointb1 != 0xb1) {
+ memmove(((char *) &single_toc) + 36,
+ ((char *) &single_toc) + 27,
+ res_size - 27);
+ res_size += 9;
+ }
+ if (single_toc.pointb2 != 0xb2) {
+ memmove(((char *) &single_toc) + 45,
+ ((char *) &single_toc) + 36,
+ res_size - 36);
+ res_size += 9;
+ }
+ if (single_toc.pointb3 != 0xb3) {
+ memmove(((char *) &single_toc) + 54,
+ ((char *) &single_toc) + 45,
+ res_size - 45);
+ res_size += 9;
+ }
+ if (single_toc.pointb4 != 0xb4) {
+ memmove(((char *) &single_toc) + 63,
+ ((char *) &single_toc) + 54,
+ res_size - 54);
+ res_size += 9;
+ }
+ if (single_toc.pointc0 != 0xc0) {
+ memmove(((char *) &single_toc) + 72,
+ ((char *) &single_toc) + 63,
+ res_size - 63);
+ res_size += 9;
+ }
+#if DEBUG
+ printk(PRINT_INFO PFX "start track lba %u, "
+ "leadout start lba %u\n",
+ single_toc.start_track_lba,
+ single_toc.lead_out_start_lba);
+ {
+ int i;
+ for (i = 0;
+ i <
+ 1 +
+ bcd_to_int(single_toc.last_track_num)
+ -
+ bcd_to_int(single_toc.
+ first_track_num); i++) {
+ printk(KERN_INFO PFX "trk %02d: add 0x%01x, con 0x%01x, track %02d, start min %02d, sec %02d, frame %02d\n",
+ i,
+ single_toc.tracks[i].address,
+ single_toc.tracks[i].control,
+ bcd_to_int(single_toc.
+ tracks[i].track),
+ bcd_to_int(single_toc.
+ tracks[i].
+ track_start_msf
+ [0]),
+ bcd_to_int(single_toc.
+ tracks[i].
+ track_start_msf
+ [1]),
+ bcd_to_int(single_toc.
+ tracks[i].
+ track_start_msf
+ [2]));
+ if (mint >
+ bcd_to_int(single_toc.
+ tracks[i].track))
+ mint =
+ bcd_to_int(single_toc.
+ tracks[i].
+ track);
+ if (maxt <
+ bcd_to_int(single_toc.
+ tracks[i].track))
+ maxt =
+ bcd_to_int(single_toc.
+ tracks[i].
+ track);
+ }
+ printk(KERN_INFO PFX "min track number %d, "
+ "max track number %d\n",
+ mint, maxt);
+ }
+#endif
+
+ /* prepare a special table of contents for a CD-I disc. They don't have one. */
+ if (single_toc.disk_type == 0x10 &&
+ single_toc.first_track_num == 2 &&
+ single_toc.last_track_num == 2 /* CD-I */ ) {
+ sony_toc.tracks[totaltracks].address = 1;
+ sony_toc.tracks[totaltracks].control = 4; /* force data tracks */
+ sony_toc.tracks[totaltracks].track = 1;
+ sony_toc.tracks[totaltracks].
+ track_start_msf[0] = 0;
+ sony_toc.tracks[totaltracks].
+ track_start_msf[1] = 2;
+ sony_toc.tracks[totaltracks].
+ track_start_msf[2] = 0;
+ mint = maxt = 1;
+ totaltracks++;
+ } else
+ /* gather track entries from this session */
+ {
+ int i;
+ for (i = 0;
+ i <
+ 1 +
+ bcd_to_int(single_toc.last_track_num)
+ -
+ bcd_to_int(single_toc.
+ first_track_num);
+ i++, totaltracks++) {
+ sony_toc.tracks[totaltracks].
+ address =
+ single_toc.tracks[i].address;
+ sony_toc.tracks[totaltracks].
+ control =
+ single_toc.tracks[i].control;
+ sony_toc.tracks[totaltracks].
+ track =
+ bcd_to_int(single_toc.
+ tracks[i].track);
+ sony_toc.tracks[totaltracks].
+ track_start_msf[0] =
+ bcd_to_int(single_toc.
+ tracks[i].
+ track_start_msf[0]);
+ sony_toc.tracks[totaltracks].
+ track_start_msf[1] =
+ bcd_to_int(single_toc.
+ tracks[i].
+ track_start_msf[1]);
+ sony_toc.tracks[totaltracks].
+ track_start_msf[2] =
+ bcd_to_int(single_toc.
+ tracks[i].
+ track_start_msf[2]);
+ if (i == 0)
+ single_toc.
+ start_track_lba =
+ msf_to_log(sony_toc.
+ tracks
+ [totaltracks].
+ track_start_msf);
+ if (mint >
+ sony_toc.tracks[totaltracks].
+ track)
+ mint =
+ sony_toc.
+ tracks[totaltracks].
+ track;
+ if (maxt <
+ sony_toc.tracks[totaltracks].
+ track)
+ maxt =
+ sony_toc.
+ tracks[totaltracks].
+ track;
+ }
+ }
+ sony_toc.first_track_num = mint;
+ sony_toc.last_track_num = maxt;
+ /* Disk type of last session wins. For example:
+ CD-Extra has disk type 0 for the first session, so
+ a dumb HiFi CD player thinks it is a plain audio CD.
+ We are interested in the disk type of the last session,
+ which is 0x20 (XA) for CD-Extra, so we can access the
+ data track ... */
+ sony_toc.disk_type = single_toc.disk_type;
+ sony_toc.sessions = session;
+
+ /* don't believe everything :-) */
+ if (session == 1)
+ single_toc.start_track_lba = 0;
+ sony_toc.start_track_lba =
+ single_toc.start_track_lba;
+
+ if (session > 1 && single_toc.pointb0 == 0xb0 &&
+ sony_toc.lead_out_start_lba ==
+ single_toc.lead_out_start_lba) {
+ break;
+ }
+
+ /* Let's not get carried away... */
+ if (session > 40) {
+ printk(KERN_NOTICE PFX "too many sessions: "
+ "%d\n", session);
+ break;
+ }
+ session++;
+ }
+ sony_toc.track_entries = totaltracks;
+ /* add one entry for the LAST track with track number CDROM_LEADOUT */
+ sony_toc.tracks[totaltracks].address = single_toc.address2;
+ sony_toc.tracks[totaltracks].control = single_toc.control2;
+ sony_toc.tracks[totaltracks].track = CDROM_LEADOUT;
+ sony_toc.tracks[totaltracks].track_start_msf[0] =
+ sony_toc.lead_out_start_msf[0];
+ sony_toc.tracks[totaltracks].track_start_msf[1] =
+ sony_toc.lead_out_start_msf[1];
+ sony_toc.tracks[totaltracks].track_start_msf[2] =
+ sony_toc.lead_out_start_msf[2];
+
+ sony_toc_read = 1;
+
+ pr_debug(PFX "Disk session %d, start track: %d, "
+ "stop track: %d\n",
+ session, single_toc.start_track_lba,
+ single_toc.lead_out_start_lba);
+ }
+ pr_debug(PFX "Leaving %s\n", __FUNCTION__);
+}
+
+
+/*
+ * Uniform cdrom interface function
+ * return multisession offset and sector information
+ */
+static int scd_get_last_session(struct cdrom_device_info *cdi,
+ struct cdrom_multisession *ms_info)
+{
+ if (ms_info == NULL)
+ return 1;
+
+ if (!sony_toc_read) {
+ if (down_interruptible(&sony_sem))
+ return -ERESTARTSYS;
+ sony_get_toc();
+ up(&sony_sem);
+ }
+
+ ms_info->addr_format = CDROM_LBA;
+ ms_info->addr.lba = sony_toc.start_track_lba;
+ ms_info->xa_flag = sony_toc.disk_type == SONY_XA_DISK_TYPE ||
+ sony_toc.disk_type == 0x10 /* CDI */ ;
+
+ return 0;
+}
+
+/*
+ * Search for a specific track in the table of contents.
+ */
+static int find_track(int track)
+{
+ int i;
+
+ for (i = 0; i <= sony_toc.track_entries; i++) {
+ if (sony_toc.tracks[i].track == track) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+
+/*
+ * Read the subcode and put it in 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] & 0xf0) == 0x20)) {
+ printk(KERN_ERR PFX "Sony CDROM error %s (read_subcode)\n",
+ translate_error(last_sony_subcode.exec_status[1]));
+ return -EIO;
+ }
+
+ last_sony_subcode.track_num =
+ bcd_to_int(last_sony_subcode.track_num);
+ last_sony_subcode.index_num =
+ bcd_to_int(last_sony_subcode.index_num);
+ last_sony_subcode.abs_msf[0] =
+ bcd_to_int(last_sony_subcode.abs_msf[0]);
+ last_sony_subcode.abs_msf[1] =
+ bcd_to_int(last_sony_subcode.abs_msf[1]);
+ last_sony_subcode.abs_msf[2] =
+ bcd_to_int(last_sony_subcode.abs_msf[2]);
+
+ last_sony_subcode.rel_msf[0] =
+ bcd_to_int(last_sony_subcode.rel_msf[0]);
+ last_sony_subcode.rel_msf[1] =
+ bcd_to_int(last_sony_subcode.rel_msf[1]);
+ last_sony_subcode.rel_msf[2] =
+ bcd_to_int(last_sony_subcode.rel_msf[2]);
+ return 0;
+}
+
+/*
+ * Uniform cdrom interface function
+ * return the media catalog number found on some older audio cds
+ */
+static int
+scd_get_mcn(struct cdrom_device_info *cdi, struct cdrom_mcn *mcn)
+{
+ unsigned char resbuffer[2 + 14];
+ unsigned char *mcnp = mcn->medium_catalog_number;
+ unsigned char *resp = resbuffer + 3;
+ unsigned int res_size;
+
+ memset(mcn->medium_catalog_number, 0, 14);
+ if (down_interruptible(&sony_sem))
+ return -ERESTARTSYS;
+ do_sony_cd_cmd(SONY_REQ_UPC_EAN_CMD,
+ NULL, 0, resbuffer, &res_size);
+ up(&sony_sem);
+ if ((res_size < 2) || ((resbuffer[0] & 0xf0) == 0x20));
+ else {
+ /* packed bcd to single ASCII digits */
+ *mcnp++ = (*resp >> 4) + '0';
+ *mcnp++ = (*resp++ & 0x0f) + '0';
+ *mcnp++ = (*resp >> 4) + '0';
+ *mcnp++ = (*resp++ & 0x0f) + '0';
+ *mcnp++ = (*resp >> 4) + '0';
+ *mcnp++ = (*resp++ & 0x0f) + '0';
+ *mcnp++ = (*resp >> 4) + '0';
+ *mcnp++ = (*resp++ & 0x0f) + '0';
+ *mcnp++ = (*resp >> 4) + '0';
+ *mcnp++ = (*resp++ & 0x0f) + '0';
+ *mcnp++ = (*resp >> 4) + '0';
+ *mcnp++ = (*resp++ & 0x0f) + '0';
+ *mcnp++ = (*resp >> 4) + '0';
+ }
+ *mcnp = '\0';
+ 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(struct cdrom_subchnl *schi)
+{
+ /* Get attention stuff */
+ while (handle_sony_cd_attention());
+
+ sony_get_toc();
+ if (!sony_toc_read) {
+ return -EIO;
+ }
+
+ switch (sony_audio_status) {
+ case CDROM_AUDIO_NO_STATUS:
+ case CDROM_AUDIO_PLAY:
+ if (read_subcode() < 0) {
+ return -EIO;
+ }
+ break;
+
+ case CDROM_AUDIO_PAUSED:
+ case CDROM_AUDIO_COMPLETED:
+ break;
+
+#if 0
+ case CDROM_AUDIO_NO_STATUS:
+ schi->cdsc_audiostatus = sony_audio_status;
+ return 0;
+ break;
+#endif
+ 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 = last_sony_subcode.track_num;
+ schi->cdsc_ind = last_sony_subcode.index_num;
+ if (schi->cdsc_format == CDROM_MSF) {
+ schi->cdsc_absaddr.msf.minute =
+ last_sony_subcode.abs_msf[0];
+ schi->cdsc_absaddr.msf.second =
+ last_sony_subcode.abs_msf[1];
+ schi->cdsc_absaddr.msf.frame =
+ last_sony_subcode.abs_msf[2];
+
+ schi->cdsc_reladdr.msf.minute =
+ last_sony_subcode.rel_msf[0];
+ schi->cdsc_reladdr.msf.second =
+ last_sony_subcode.rel_msf[1];
+ schi->cdsc_reladdr.msf.frame =
+ 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 0;
+}
+
+/* Get audio data from the drive. This is fairly complex because I
+ am looking for status and data at the same time, but if I get status
+ then I just look for data. I need to get the status immediately so
+ the switch from audio to data tracks will happen quickly. */
+static void
+read_audio_data(char *buffer, unsigned char res_reg[], int *res_size)
+{
+ unsigned long retry_count;
+ int result_read;
+
+
+ res_reg[0] = 0;
+ res_reg[1] = 0;
+ *res_size = 0;
+ result_read = 0;
+
+ /* Wait for the drive to tell us we have something */
+ retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+ continue_read_audio_wait:
+ while (time_before(jiffies, retry_count) && !(is_data_ready())
+ && !(is_result_ready() || result_read)) {
+ while (handle_sony_cd_attention());
+
+ sony_sleep();
+ }
+ if (!(is_data_ready())) {
+ if (is_result_ready() && !result_read) {
+ get_result(res_reg, res_size);
+
+ /* Read block status and continue waiting for data. */
+ if ((res_reg[0] & 0xf0) == 0x50) {
+ result_read = 1;
+ goto continue_read_audio_wait;
+ }
+ /* Invalid data from the drive. Shut down the operation. */
+ else if ((res_reg[0] & 0xf0) != 0x20) {
+ printk(KERN_WARNING PFX "Got result that "
+ "should have been error: %d\n",
+ res_reg[0]);
+ res_reg[0] = 0x20;
+ res_reg[1] = SONY_BAD_DATA_ERR;
+ *res_size = 2;
+ }
+ abort_read();
+ } else {
+ pr_debug(PFX "timeout out %d\n", __LINE__);
+ res_reg[0] = 0x20;
+ res_reg[1] = SONY_TIMEOUT_OP_ERR;
+ *res_size = 2;
+ abort_read();
+ }
+ } else {
+ clear_data_ready();
+
+ /* If data block, then get 2340 bytes offset by 12. */
+ if (sony_raw_data_mode) {
+ insb(sony_cd_read_reg, buffer + CD_XA_HEAD,
+ CD_FRAMESIZE_RAW1);
+ } else {
+ /* Audio gets the whole 2352 bytes. */
+ insb(sony_cd_read_reg, buffer, CD_FRAMESIZE_RAW);
+ }
+
+ /* If I haven't already gotten the result, get it now. */
+ if (!result_read) {
+ /* Wait for the drive to tell us we have something */
+ retry_count = jiffies + SONY_JIFFIES_TIMEOUT;
+ while (time_before(jiffies, retry_count)
+ && !(is_result_ready())) {
+ while (handle_sony_cd_attention());
+
+ sony_sleep();
+ }
+
+ if (!is_result_ready()) {
+ pr_debug(PFX "timeout out %d\n", __LINE__);
+ res_reg[0] = 0x20;
+ res_reg[1] = SONY_TIMEOUT_OP_ERR;
+ *res_size = 2;
+ abort_read();
+ return;
+ } else {
+ get_result(res_reg, res_size);
+ }
+ }
+
+ if ((res_reg[0] & 0xf0) == 0x50) {
+ if ((res_reg[0] == SONY_NO_CIRC_ERR_BLK_STAT)
+ || (res_reg[0] == SONY_NO_LECC_ERR_BLK_STAT)
+ || (res_reg[0] == SONY_RECOV_LECC_ERR_BLK_STAT)
+ || (res_reg[0] == SONY_NO_ERR_DETECTION_STAT)) {
+ /* Ok, nothing to do. */
+ } else {
+ printk(KERN_ERR PFX "Data block error: 0x%x\n",
+ res_reg[0]);
+ res_reg[0] = 0x20;
+ res_reg[1] = SONY_BAD_DATA_ERR;
+ *res_size = 2;
+ }
+ } else if ((res_reg[0] & 0xf0) != 0x20) {
+ /* The drive gave me bad status, I don't know what to do.
+ Reset the driver and return an error. */
+ printk(KERN_NOTICE PFX "Invalid block status: 0x%x\n",
+ res_reg[0]);
+ restart_on_error();
+ res_reg[0] = 0x20;
+ res_reg[1] = SONY_BAD_DATA_ERR;
+ *res_size = 2;
+ }
+ }
+}
+
+/* Perform a raw data read. This will automatically detect the
+ track type and read the proper data (audio or data). */
+static int read_audio(struct cdrom_read_audio *ra)
+{
+ int retval;
+ unsigned char params[2];
+ unsigned char res_reg[12];
+ unsigned int res_size;
+ unsigned int cframe;
+
+ if (down_interruptible(&sony_sem))
+ return -ERESTARTSYS;
+ if (!sony_spun_up)
+ scd_spinup();
+
+ /* Set the drive to do raw operations. */
+ params[0] = SONY_SD_DECODE_PARAM;
+ params[1] = 0x06 | sony_raw_data_mode;
+ do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
+ params, 2, res_reg, &res_size);
+ if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) {
+ printk(KERN_ERR PFX "Unable to set decode params: 0x%2.2x\n",
+ res_reg[1]);
+ retval = -EIO;
+ goto out_up;
+ }
+
+ /* From here down, we have to goto exit_read_audio instead of returning
+ because the drive parameters have to be set back to data before
+ return. */
+
+ retval = 0;
+ if (start_request(ra->addr.lba, ra->nframes)) {
+ retval = -EIO;
+ goto exit_read_audio;
+ }
+
+ /* For every requested frame. */
+ cframe = 0;
+ while (cframe < ra->nframes) {
+ read_audio_data(audio_buffer, res_reg, &res_size);
+ if ((res_reg[0] & 0xf0) == 0x20) {
+ if (res_reg[1] == SONY_BAD_DATA_ERR) {
+ printk(KERN_ERR PFX "Data error on audio "
+ "sector %d\n",
+ ra->addr.lba + cframe);
+ } else if (res_reg[1] == SONY_ILL_TRACK_R_ERR) {
+ /* Illegal track type, change track types and start over. */
+ sony_raw_data_mode =
+ (sony_raw_data_mode) ? 0 : 1;
+
+ /* Set the drive mode. */
+ params[0] = SONY_SD_DECODE_PARAM;
+ params[1] = 0x06 | sony_raw_data_mode;
+ do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
+ params,
+ 2, res_reg, &res_size);
+ if ((res_size < 2)
+ || ((res_reg[0] & 0xf0) == 0x20)) {
+ printk(KERN_ERR PFX "Unable to set "
+ "decode params: 0x%2.2x\n",
+ res_reg[1]);
+ retval = -EIO;
+ goto exit_read_audio;
+ }
+
+ /* Restart the request on the current frame. */
+ if (start_request
+ (ra->addr.lba + cframe,
+ ra->nframes - cframe)) {
+ retval = -EIO;
+ goto exit_read_audio;
+ }
+
+ /* Don't go back to the top because don't want to get into
+ and infinite loop. A lot of code gets duplicated, but
+ that's no big deal, I don't guess. */
+ read_audio_data(audio_buffer, res_reg,
+ &res_size);
+ if ((res_reg[0] & 0xf0) == 0x20) {
+ if (res_reg[1] ==
+ SONY_BAD_DATA_ERR) {
+ printk(KERN_ERR PFX "Data error"
+ " on audio sector %d\n",
+ ra->addr.lba +
+ cframe);
+ } else {
+ printk(KERN_ERR PFX "Error reading audio data on sector %d: %s\n",
+ ra->addr.lba + cframe,
+ translate_error
+ (res_reg[1]));
+ retval = -EIO;
+ goto exit_read_audio;
+ }
+ } else if (copy_to_user(ra->buf +
+ (CD_FRAMESIZE_RAW
+ * cframe),
+ audio_buffer,
+ CD_FRAMESIZE_RAW)) {
+ retval = -EFAULT;
+ goto exit_read_audio;
+ }
+ } else {
+ printk(KERN_ERR PFX "Error reading audio "
+ "data on sector %d: %s\n",
+ ra->addr.lba + cframe,
+ translate_error(res_reg[1]));
+ retval = -EIO;
+ goto exit_read_audio;
+ }
+ } else if (copy_to_user(ra->buf + (CD_FRAMESIZE_RAW * cframe),
+ (char *)audio_buffer,
+ CD_FRAMESIZE_RAW)) {
+ retval = -EFAULT;
+ goto exit_read_audio;
+ }
+
+ cframe++;
+ }
+
+ get_result(res_reg, &res_size);
+ if ((res_reg[0] & 0xf0) == 0x20) {
+ printk(KERN_ERR PFX "Error return from audio read: %s\n",
+ translate_error(res_reg[1]));
+ retval = -EIO;
+ goto exit_read_audio;
+ }
+
+ exit_read_audio:
+
+ /* Set the drive mode back to the proper one for the disk. */
+ params[0] = SONY_SD_DECODE_PARAM;
+ if (!sony_xa_mode) {
+ params[1] = 0x0f;
+ } else {
+ params[1] = 0x07;
+ }
+ do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
+ params, 2, res_reg, &res_size);
+ if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) {
+ printk(KERN_ERR PFX "Unable to reset decode params: 0x%2.2x\n",
+ res_reg[1]);
+ retval = -EIO;
+ }
+
+ out_up:
+ up(&sony_sem);
+
+ return retval;
+}
+
+static int
+do_sony_cd_cmd_chk(const char *name,
+ unsigned char cmd,
+ unsigned char *params,
+ unsigned int num_params,
+ unsigned char *result_buffer, unsigned int *result_size)
+{
+ do_sony_cd_cmd(cmd, params, num_params, result_buffer,
+ result_size);
+ if ((*result_size < 2) || ((result_buffer[0] & 0xf0) == 0x20)) {
+ printk(KERN_ERR PFX "Error %s (CDROM%s)\n",
+ translate_error(result_buffer[1]), name);
+ return -EIO;
+ }
+ return 0;
+}
+
+/*
+ * Uniform cdrom interface function
+ * open the tray
+ */
+static int scd_tray_move(struct cdrom_device_info *cdi, int position)
+{
+ int retval;
+
+ if (down_interruptible(&sony_sem))
+ return -ERESTARTSYS;
+ if (position == 1 /* open tray */ ) {
+ unsigned char res_reg[12];
+ unsigned int res_size;
+
+ 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;
+ retval = do_sony_cd_cmd_chk("EJECT", SONY_EJECT_CMD, NULL, 0,
+ res_reg, &res_size);
+ } else {
+ if (0 == scd_spinup())
+ sony_spun_up = 1;
+ retval = 0;
+ }
+ up(&sony_sem);
+ return retval;
+}
+
+/*
+ * The big ugly ioctl handler.
+ */
+static int scd_audio_ioctl(struct cdrom_device_info *cdi,
+ unsigned int cmd, void *arg)
+{
+ unsigned char res_reg[12];
+ unsigned int res_size;
+ unsigned char params[7];
+ int i, retval;
+
+ if (down_interruptible(&sony_sem))
+ return -ERESTARTSYS;
+ switch (cmd) {
+ case CDROMSTART: /* Spin up the drive */
+ retval = do_sony_cd_cmd_chk("START", SONY_SPIN_UP_CMD, NULL,
+ 0, res_reg, &res_size);
+ 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;
+ retval = do_sony_cd_cmd_chk("STOP", SONY_SPIN_DOWN_CMD, NULL,
+ 0, res_reg, &res_size);
+ break;
+
+ case CDROMPAUSE: /* Pause the drive */
+ if (do_sony_cd_cmd_chk
+ ("PAUSE", SONY_AUDIO_STOP_CMD, NULL, 0, res_reg,
+ &res_size)) {
+ retval = -EIO;
+ break;
+ }
+ /* Get the current position and save it for resuming */
+ if (read_subcode() < 0) {
+ retval = -EIO;
+ break;
+ }
+ 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;
+ retval = 0;
+ break;
+
+ case CDROMRESUME: /* Start the drive after being paused */
+ if (sony_audio_status != CDROM_AUDIO_PAUSED) {
+ retval = -EINVAL;
+ break;
+ }
+
+ do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg,
+ &res_size);
+
+ /* Start the drive at the saved position. */
+ params[1] = int_to_bcd(cur_pos_msf[0]);
+ params[2] = int_to_bcd(cur_pos_msf[1]);
+ params[3] = int_to_bcd(cur_pos_msf[2]);
+ params[4] = int_to_bcd(final_pos_msf[0]);
+ params[5] = int_to_bcd(final_pos_msf[1]);
+ params[6] = int_to_bcd(final_pos_msf[2]);
+ params[0] = 0x03;
+ if (do_sony_cd_cmd_chk
+ ("RESUME", SONY_AUDIO_PLAYBACK_CMD, params, 7, res_reg,
+ &res_size) < 0) {
+ retval = -EIO;
+ break;
+ }
+ sony_audio_status = CDROM_AUDIO_PLAY;
+ retval = 0;
+ break;
+
+ case CDROMPLAYMSF: /* Play starting at the given MSF address. */
+ do_sony_cd_cmd(SONY_SPIN_UP_CMD, NULL, 0, res_reg,
+ &res_size);
+
+ /* The parameters are given in int, must be converted */
+ for (i = 1; i < 7; i++) {
+ params[i] =
+ int_to_bcd(((unsigned char *) arg)[i - 1]);
+ }
+ params[0] = 0x03;
+ if (do_sony_cd_cmd_chk
+ ("PLAYMSF", SONY_AUDIO_PLAYBACK_CMD, params, 7,
+ res_reg, &res_size) < 0) {
+ retval = -EIO;
+ break;
+ }
+
+ /* Save the final position for pauses and resumes */
+ final_pos_msf[0] = bcd_to_int(params[4]);
+ final_pos_msf[1] = bcd_to_int(params[5]);
+ final_pos_msf[2] = bcd_to_int(params[6]);
+ sony_audio_status = CDROM_AUDIO_PLAY;
+ retval = 0;
+ break;
+
+ case CDROMREADTOCHDR: /* Read the table of contents header */
+ {
+ struct cdrom_tochdr *hdr;
+
+ sony_get_toc();
+ if (!sony_toc_read) {
+ retval = -EIO;
+ break;
+ }
+
+ hdr = (struct cdrom_tochdr *) arg;
+ hdr->cdth_trk0 = sony_toc.first_track_num;
+ hdr->cdth_trk1 = sony_toc.last_track_num;
+ }
+ retval = 0;
+ break;
+
+ case CDROMREADTOCENTRY: /* Read a given table of contents entry */
+ {
+ struct cdrom_tocentry *entry;
+ int track_idx;
+ unsigned char *msf_val = NULL;
+
+ sony_get_toc();
+ if (!sony_toc_read) {
+ retval = -EIO;
+ break;
+ }
+
+ entry = (struct cdrom_tocentry *) arg;
+
+ track_idx = find_track(entry->cdte_track);
+ if (track_idx < 0) {
+ retval = -EINVAL;
+ break;
+ }
+
+ entry->cdte_adr =
+ sony_toc.tracks[track_idx].address;
+ 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 (entry->cdte_format == CDROM_LBA) {
+ entry->cdte_addr.lba = msf_to_log(msf_val);
+ } else if (entry->cdte_format == CDROM_MSF) {
+ entry->cdte_addr.msf.minute = *msf_val;
+ entry->cdte_addr.msf.second =
+ *(msf_val + 1);
+ entry->cdte_addr.msf.frame =
+ *(msf_val + 2);
+ }
+ }
+ retval = 0;
+ break;
+
+ case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */
+ {
+ struct cdrom_ti *ti = (struct cdrom_ti *) arg;
+ int track_idx;
+
+ sony_get_toc();
+ if (!sony_toc_read) {
+ retval = -EIO;
+ break;
+ }
+
+ if ((ti->cdti_trk0 < sony_toc.first_track_num)
+ || (ti->cdti_trk0 > sony_toc.last_track_num)
+ || (ti->cdti_trk1 < ti->cdti_trk0)) {
+ retval = -EINVAL;
+ break;
+ }
+
+ track_idx = find_track(ti->cdti_trk0);
+ if (track_idx < 0) {
+ retval = -EINVAL;
+ break;
+ }
+ params[1] =
+ int_to_bcd(sony_toc.tracks[track_idx].
+ track_start_msf[0]);
+ params[2] =
+ int_to_bcd(sony_toc.tracks[track_idx].
+ track_start_msf[1]);
+ params[3] =
+ int_to_bcd(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 >= sony_toc.last_track_num) {
+ track_idx = find_track(CDROM_LEADOUT);
+ } else {
+ track_idx = find_track(ti->cdti_trk1 + 1);
+ }
+ if (track_idx < 0) {
+ retval = -EINVAL;
+ break;
+ }
+ params[4] =
+ int_to_bcd(sony_toc.tracks[track_idx].
+ track_start_msf[0]);
+ params[5] =
+ int_to_bcd(sony_toc.tracks[track_idx].
+ track_start_msf[1]);
+ params[6] =
+ int_to_bcd(sony_toc.tracks[track_idx].
+ track_start_msf[2]);
+ 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] & 0xf0) == 0x20)) {
+ printk(KERN_ERR PFX
+ "Params: %x %x %x %x %x %x %x\n",
+ params[0], params[1], params[2],
+ params[3], params[4], params[5],
+ params[6]);
+ printk(KERN_ERR PFX
+ "Error %s (CDROMPLAYTRKIND)\n",
+ translate_error(res_reg[1]));
+ retval = -EIO;
+ break;
+ }
+
+ /* Save the final position for pauses and resumes */
+ final_pos_msf[0] = bcd_to_int(params[4]);
+ final_pos_msf[1] = bcd_to_int(params[5]);
+ final_pos_msf[2] = bcd_to_int(params[6]);
+ sony_audio_status = CDROM_AUDIO_PLAY;
+ retval = 0;
+ break;
+ }
+
+ case CDROMVOLCTRL: /* Volume control. What volume does this change, anyway? */
+ {
+ struct cdrom_volctrl *volctrl =
+ (struct cdrom_volctrl *) arg;
+
+ params[0] = SONY_SD_AUDIO_VOLUME;
+ params[1] = volctrl->channel0;
+ params[2] = volctrl->channel1;
+ retval = do_sony_cd_cmd_chk("VOLCTRL",
+ SONY_SET_DRIVE_PARAM_CMD,
+ params, 3, res_reg,
+ &res_size);
+ break;
+ }
+ case CDROMSUBCHNL: /* Get subchannel info */
+ retval = sony_get_subchnl_info((struct cdrom_subchnl *) arg);
+ break;
+
+ default:
+ retval = -EINVAL;
+ break;
+ }
+ up(&sony_sem);
+ return retval;
+}
+
+static int scd_read_audio(struct cdrom_device_info *cdi,
+ unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int retval;
+
+ if (down_interruptible(&sony_sem))
+ return -ERESTARTSYS;
+ switch (cmd) {
+ case CDROMREADAUDIO: /* Read 2352 byte audio tracks and 2340 byte
+ raw data tracks. */
+ {
+ struct cdrom_read_audio ra;
+
+
+ sony_get_toc();
+ if (!sony_toc_read) {
+ retval = -EIO;
+ break;
+ }
+
+ if (copy_from_user(&ra, argp, sizeof(ra))) {
+ retval = -EFAULT;
+ break;
+ }
+
+ if (ra.nframes == 0) {
+ retval = 0;
+ break;
+ }
+
+ if (!access_ok(VERIFY_WRITE, ra.buf,
+ CD_FRAMESIZE_RAW * ra.nframes))
+ return -EFAULT;
+
+ if (ra.addr_format == CDROM_LBA) {
+ if ((ra.addr.lba >=
+ sony_toc.lead_out_start_lba)
+ || (ra.addr.lba + ra.nframes >=
+ sony_toc.lead_out_start_lba)) {
+ retval = -EINVAL;
+ break;
+ }
+ } else if (ra.addr_format == CDROM_MSF) {
+ if ((ra.addr.msf.minute >= 75)
+ || (ra.addr.msf.second >= 60)
+ || (ra.addr.msf.frame >= 75)) {
+ retval = -EINVAL;
+ break;
+ }
+
+ ra.addr.lba = ((ra.addr.msf.minute * 4500)
+ + (ra.addr.msf.second * 75)
+ + ra.addr.msf.frame);
+ if ((ra.addr.lba >=
+ sony_toc.lead_out_start_lba)
+ || (ra.addr.lba + ra.nframes >=
+ sony_toc.lead_out_start_lba)) {
+ retval = -EINVAL;
+ break;
+ }
+
+ /* I know, this can go negative on an unsigned. However,
+ the first thing done to the data is to add this value,
+ so this should compensate and allow direct msf access. */
+ ra.addr.lba -= LOG_START_OFFSET;
+ } else {
+ retval = -EINVAL;
+ break;
+ }
+
+ retval = read_audio(&ra);
+ break;
+ }
+ retval = 0;
+ break;
+
+ default:
+ retval = -EINVAL;
+ }
+ up(&sony_sem);
+ return retval;
+}
+
+static int scd_spinup(void)
+{
+ unsigned char res_reg[12];
+ unsigned int res_size;
+ int num_spin_ups;
+
+ 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(KERN_ERR PFX "%s error (scd_open, spin up)\n",
+ translate_error(res_reg[1]));
+ return 1;
+ }
+
+ 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, it's ok. */
+ if ((res_reg[1] == SONY_AUDIO_PLAYING_ERR)
+ || (res_reg[1] == 0)) {
+ return 0;
+ }
+
+ /* 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(KERN_ERR PFX "Error %s (scd_open, read toc)\n",
+ translate_error(res_reg[1]));
+ do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg,
+ &res_size);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * 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 cdrom_device_info *cdi, int purpose)
+{
+ unsigned char res_reg[12];
+ unsigned int res_size;
+ unsigned char params[2];
+
+ if (purpose == 1) {
+ /* Open for IOCTLs only - no media check */
+ sony_usage++;
+ return 0;
+ }
+
+ if (sony_usage == 0) {
+ if (scd_spinup() != 0)
+ 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;
+ }
+
+ /* For XA on the CDU31A only, we have to do special reads.
+ The CDU33A handles XA automagically. */
+ /* if ( (sony_toc.disk_type == SONY_XA_DISK_TYPE) */
+ if ((sony_toc.disk_type != 0x00)
+ && (!is_double_speed)) {
+ params[0] = SONY_SD_DECODE_PARAM;
+ params[1] = 0x07;
+ do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
+ params, 2, res_reg, &res_size);
+ if ((res_size < 2)
+ || ((res_reg[0] & 0xf0) == 0x20)) {
+ printk(KERN_WARNING PFX "Unable to set "
+ "XA params: 0x%2.2x\n", res_reg[1]);
+ }
+ sony_xa_mode = 1;
+ }
+ /* A non-XA disk. Set the parms back if necessary. */
+ else if (sony_xa_mode) {
+ params[0] = SONY_SD_DECODE_PARAM;
+ params[1] = 0x0f;
+ do_sony_cd_cmd(SONY_SET_DRIVE_PARAM_CMD,
+ params, 2, res_reg, &res_size);
+ if ((res_size < 2)
+ || ((res_reg[0] & 0xf0) == 0x20)) {
+ printk(KERN_WARNING PFX "Unable to reset "
+ "XA params: 0x%2.2x\n", res_reg[1]);
+ }
+ sony_xa_mode = 0;
+ }
+
+ sony_spun_up = 1;
+ }
+
+ 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 cdrom_device_info *cdi)
+{
+ if (sony_usage == 1) {
+ unsigned char res_reg[12];
+ unsigned int res_size;
+
+ do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg,
+ &res_size);
+
+ sony_spun_up = 0;
+ }
+ sony_usage--;
+}
+
+static struct cdrom_device_ops scd_dops = {
+ .open = scd_open,
+ .release = scd_release,
+ .drive_status = scd_drive_status,
+ .media_changed = scd_media_changed,
+ .tray_move = scd_tray_move,
+ .lock_door = scd_lock_door,
+ .select_speed = scd_select_speed,
+ .get_last_session = scd_get_last_session,
+ .get_mcn = scd_get_mcn,
+ .reset = scd_reset,
+ .audio_ioctl = scd_audio_ioctl,
+ .capability = CDC_OPEN_TRAY | CDC_CLOSE_TRAY | CDC_LOCK |
+ CDC_SELECT_SPEED | CDC_MULTI_SESSION |
+ CDC_MCN | CDC_MEDIA_CHANGED | CDC_PLAY_AUDIO |
+ CDC_RESET | CDC_DRIVE_STATUS,
+ .n_minors = 1,
+};
+
+static struct cdrom_device_info scd_info = {
+ .ops = &scd_dops,
+ .speed = 2,
+ .capacity = 1,
+ .name = "cdu31a"
+};
+
+static int scd_block_open(struct inode *inode, struct file *file)
+{
+ return cdrom_open(&scd_info, inode, file);
+}
+
+static int scd_block_release(struct inode *inode, struct file *file)
+{
+ return cdrom_release(&scd_info, file);
+}
+
+static int scd_block_ioctl(struct inode *inode, struct file *file,
+ unsigned cmd, unsigned long arg)
+{
+ int retval;
+
+ /* The eject and close commands should be handled by Uniform CD-ROM
+ * driver - but I always got hard lockup instead of eject
+ * until I put this here.
+ */
+ switch (cmd) {
+ case CDROMEJECT:
+ scd_lock_door(&scd_info, 0);
+ retval = scd_tray_move(&scd_info, 1);
+ break;
+ case CDROMCLOSETRAY:
+ retval = scd_tray_move(&scd_info, 0);
+ break;
+ case CDROMREADAUDIO:
+ retval = scd_read_audio(&scd_info, CDROMREADAUDIO, arg);
+ break;
+ default:
+ retval = cdrom_ioctl(file, &scd_info, inode, cmd, arg);
+ }
+ return retval;
+}
+
+static int scd_block_media_changed(struct gendisk *disk)
+{
+ return cdrom_media_changed(&scd_info);
+}
+
+static struct block_device_operations scd_bdops =
+{
+ .owner = THIS_MODULE,
+ .open = scd_block_open,
+ .release = scd_block_release,
+ .ioctl = scd_block_ioctl,
+ .media_changed = scd_block_media_changed,
+};
+
+static struct gendisk *scd_gendisk;
+
+/* The different types of disc loading mechanisms supported */
+static char *load_mech[] __initdata =
+ { "caddy", "tray", "pop-up", "unknown" };
+
+static int __init
+get_drive_configuration(unsigned short base_io,
+ unsigned char res_reg[], unsigned int *res_size)
+{
+ unsigned long retry_count;
+
+
+ if (!request_region(base_io, 4, "cdu31a"))
+ return 0;
+
+ /* Set the base address */
+ cdu31a_port = base_io;
+
+ /* Set up all the register locations */
+ sony_cd_cmd_reg = cdu31a_port + SONY_CMD_REG_OFFSET;
+ sony_cd_param_reg = cdu31a_port + SONY_PARAM_REG_OFFSET;
+ sony_cd_write_reg = cdu31a_port + SONY_WRITE_REG_OFFSET;
+ sony_cd_control_reg = cdu31a_port + SONY_CONTROL_REG_OFFSET;
+ sony_cd_status_reg = cdu31a_port + SONY_STATUS_REG_OFFSET;
+ sony_cd_result_reg = cdu31a_port + SONY_RESULT_REG_OFFSET;
+ sony_cd_read_reg = cdu31a_port + SONY_READ_REG_OFFSET;
+ sony_cd_fifost_reg = cdu31a_port + 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 it's reset).
+ * If you don't wait, the next operation will probably fail.
+ */
+ reset_drive();
+ retry_count = jiffies + SONY_RESET_TIMEOUT;
+ while (time_before(jiffies, retry_count)
+ && (!is_attention())) {
+ sony_sleep();
+ }
+
+#if 0
+ /* If attention is never seen probably not a CDU31a present */
+ if (!is_attention()) {
+ res_reg[0] = 0x20;
+ goto out_err;
+ }
+#endif
+
+ /*
+ * Get the drive configuration.
+ */
+ do_sony_cd_cmd(SONY_REQ_DRIVE_CONFIG_CMD,
+ NULL,
+ 0, (unsigned char *) res_reg, res_size);
+ if (*res_size <= 2 || (res_reg[0] & 0xf0) != 0)
+ goto out_err;
+ return 1;
+ }
+
+ /* Return an error */
+ res_reg[0] = 0x20;
+out_err:
+ release_region(cdu31a_port, 4);
+ cdu31a_port = 0;
+ return 0;
+}
+
+#ifndef MODULE
+/*
+ * Set up base I/O and interrupts, called from main.c.
+ */
+
+static int __init cdu31a_setup(char *strings)
+{
+ int ints[4];
+
+ (void) get_options(strings, ARRAY_SIZE(ints), ints);
+
+ if (ints[0] > 0) {
+ cdu31a_port = ints[1];
+ }
+ if (ints[0] > 1) {
+ cdu31a_irq = ints[2];
+ }
+ if ((strings != NULL) && (*strings != '\0')) {
+ if (strcmp(strings, "PAS") == 0) {
+ sony_pas_init = 1;
+ } else {
+ printk(KERN_NOTICE PFX "Unknown interface type: %s\n",
+ strings);
+ }
+ }
+
+ return 1;
+}
+
+__setup("cdu31a=", cdu31a_setup);
+
+#endif
+
+/*
+ * Initialize the driver.
+ */
+int __init cdu31a_init(void)
+{
+ struct s_sony_drive_config drive_config;
+ struct gendisk *disk;
+ int deficiency = 0;
+ unsigned int res_size;
+ char msg[255];
+ char buf[40];
+ int i;
+ int tmp_irq;
+
+ /*
+ * 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.
+ */
+ if (sony_pas_init) {
+ outb(0xbc, 0x9a01);
+ outb(0xe2, 0x9a01);
+ }
+
+ /* Setting the base I/O address to 0xffff will disable it. */
+ if (cdu31a_port == 0xffff)
+ goto errout3;
+
+ if (cdu31a_port != 0) {
+ /* Need IRQ 0 because we can't sleep here. */
+ tmp_irq = cdu31a_irq;
+ cdu31a_irq = 0;
+ if (!get_drive_configuration(cdu31a_port,
+ drive_config.exec_status,
+ &res_size))
+ goto errout3;
+ cdu31a_irq = tmp_irq;
+ } else {
+ cdu31a_irq = 0;
+ for (i = 0; cdu31a_addresses[i].base; i++) {
+ if (get_drive_configuration(cdu31a_addresses[i].base,
+ drive_config.exec_status,
+ &res_size)) {
+ cdu31a_irq = cdu31a_addresses[i].int_num;
+ break;
+ }
+ }
+ if (!cdu31a_port)
+ goto errout3;
+ }
+
+ if (register_blkdev(MAJOR_NR, "cdu31a"))
+ goto errout2;
+
+ disk = alloc_disk(1);
+ if (!disk)
+ goto errout1;
+ disk->major = MAJOR_NR;
+ disk->first_minor = 0;
+ sprintf(disk->disk_name, "cdu31a");
+ disk->fops = &scd_bdops;
+ disk->flags = GENHD_FL_CD;
+
+ if (SONY_HWC_DOUBLE_SPEED(drive_config))
+ is_double_speed = 1;
+
+ tmp_irq = cdu31a_irq; /* Need IRQ 0 because we can't sleep here. */
+ cdu31a_irq = 0;
+
+ sony_speed = is_double_speed; /* Set 2X drives to 2X by default */
+ set_drive_params(sony_speed);
+
+ cdu31a_irq = tmp_irq;
+
+ if (cdu31a_irq > 0) {
+ if (request_irq
+ (cdu31a_irq, cdu31a_interrupt, IRQF_DISABLED,
+ "cdu31a", NULL)) {
+ printk(KERN_WARNING PFX "Unable to grab IRQ%d for "
+ "the CDU31A driver\n", cdu31a_irq);
+ cdu31a_irq = 0;
+ }
+ }
+
+ sprintf(msg, "Sony I/F CDROM : %8.8s %16.16s %8.8s\n",
+ drive_config.vendor_id,
+ drive_config.product_id,
+ drive_config.product_rev_level);
+ sprintf(buf, " Capabilities: %s",
+ load_mech[SONY_HWC_GET_LOAD_MECH(drive_config)]);
+ strcat(msg, buf);
+ if (SONY_HWC_AUDIO_PLAYBACK(drive_config))
+ strcat(msg, ", audio");
+ else
+ deficiency |= CDC_PLAY_AUDIO;
+ if (SONY_HWC_EJECT(drive_config))
+ strcat(msg, ", eject");
+ else
+ deficiency |= CDC_OPEN_TRAY;
+ if (SONY_HWC_LED_SUPPORT(drive_config))
+ strcat(msg, ", LED");
+ if (SONY_HWC_ELECTRIC_VOLUME(drive_config))
+ strcat(msg, ", elec. Vol");
+ if (SONY_HWC_ELECTRIC_VOLUME_CTL(drive_config))
+ strcat(msg, ", sep. Vol");
+ if (is_double_speed)
+ strcat(msg, ", double speed");
+ else
+ deficiency |= CDC_SELECT_SPEED;
+ if (cdu31a_irq > 0) {
+ sprintf(buf, ", irq %d", cdu31a_irq);
+ strcat(msg, buf);
+ }
+ strcat(msg, "\n");
+ printk(KERN_INFO PFX "%s",msg);
+
+ cdu31a_queue = blk_init_queue(do_cdu31a_request, &cdu31a_lock);
+ if (!cdu31a_queue)
+ goto errout0;
+ blk_queue_hardsect_size(cdu31a_queue, 2048);
+
+ init_timer(&cdu31a_abort_timer);
+ cdu31a_abort_timer.function = handle_abort_timeout;
+
+ scd_info.mask = deficiency;
+ scd_gendisk = disk;
+ if (register_cdrom(&scd_info))
+ goto err;
+ disk->queue = cdu31a_queue;
+ add_disk(disk);
+
+ disk_changed = 1;
+ return 0;
+
+err:
+ blk_cleanup_queue(cdu31a_queue);
+errout0:
+ if (cdu31a_irq)
+ free_irq(cdu31a_irq, NULL);
+ printk(KERN_ERR PFX "Unable to register with Uniform cdrom driver\n");
+ put_disk(disk);
+errout1:
+ if (unregister_blkdev(MAJOR_NR, "cdu31a")) {
+ printk(KERN_WARNING PFX "Can't unregister block device\n");
+ }
+errout2:
+ release_region(cdu31a_port, 4);
+errout3:
+ return -EIO;
+}
+
+
+static void __exit cdu31a_exit(void)
+{
+ del_gendisk(scd_gendisk);
+ put_disk(scd_gendisk);
+ if (unregister_cdrom(&scd_info)) {
+ printk(KERN_WARNING PFX "Can't unregister from Uniform "
+ "cdrom driver\n");
+ return;
+ }
+ if ((unregister_blkdev(MAJOR_NR, "cdu31a") == -EINVAL)) {
+ printk(KERN_WARNING PFX "Can't unregister\n");
+ return;
+ }
+
+ blk_cleanup_queue(cdu31a_queue);
+
+ if (cdu31a_irq > 0)
+ free_irq(cdu31a_irq, NULL);
+
+ release_region(cdu31a_port, 4);
+ printk(KERN_INFO PFX "module released.\n");
+}
+
+#ifdef MODULE
+module_init(cdu31a_init);
+#endif
+module_exit(cdu31a_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_BLOCKDEV_MAJOR(CDU31A_CDROM_MAJOR);
diff --git a/trunk/drivers/cdrom/cdu31a.h b/trunk/drivers/cdrom/cdu31a.h
new file mode 100644
index 000000000000..61d4768c412e
--- /dev/null
+++ b/trunk/drivers/cdrom/cdu31a.h
@@ -0,0 +1,411 @@
+/*
+ * Definitions for a Sony interface CDROM drive.
+ *
+ * Corey Minyard (minyard@wf-rch.cirr.com)
+ *
+ * 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.
+ *
+ */
+
+/*
+ * General defines.
+ */
+#define SONY_XA_DISK_TYPE 0x20
+
+/*
+ * Offsets (from the base address) and bits for the various write registers
+ * of the drive.
+ */
+#define SONY_CMD_REG_OFFSET 0
+#define SONY_PARAM_REG_OFFSET 1
+#define SONY_WRITE_REG_OFFSET 2
+#define SONY_CONTROL_REG_OFFSET 3
+# define SONY_ATTN_CLR_BIT 0x01
+# define SONY_RES_RDY_CLR_BIT 0x02
+# define SONY_DATA_RDY_CLR_BIT 0x04
+# define SONY_ATTN_INT_EN_BIT 0x08
+# define SONY_RES_RDY_INT_EN_BIT 0x10
+# define SONY_DATA_RDY_INT_EN_BIT 0x20
+# define SONY_PARAM_CLR_BIT 0x40
+# define SONY_DRIVE_RESET_BIT 0x80
+
+/*
+ * Offsets (from the base address) and bits for the various read registers
+ * of the drive.
+ */
+#define SONY_STATUS_REG_OFFSET 0
+# define SONY_ATTN_BIT 0x01
+# define SONY_RES_RDY_BIT 0x02
+# define SONY_DATA_RDY_BIT 0x04
+# define SONY_ATTN_INT_ST_BIT 0x08
+# define SONY_RES_RDY_INT_ST_BIT 0x10
+# define SONY_DATA_RDY_INT_ST_BIT 0x20
+# define SONY_DATA_REQUEST_BIT 0x40
+# define SONY_BUSY_BIT 0x80
+#define SONY_RESULT_REG_OFFSET 1
+#define SONY_READ_REG_OFFSET 2
+#define SONY_FIFOST_REG_OFFSET 3
+# define SONY_PARAM_WRITE_RDY_BIT 0x01
+# define SONY_PARAM_REG_EMPTY_BIT 0x02
+# define SONY_RES_REG_NOT_EMP_BIT 0x04
+# define SONY_RES_REG_FULL_BIT 0x08
+
+#define LOG_START_OFFSET 150 /* Offset of first logical sector */
+
+#define SONY_DETECT_TIMEOUT (8*HZ/10) /* Maximum amount of time
+ that drive detection code
+ will wait for response
+ from drive (in 1/100th's
+ of seconds). */
+
+#define SONY_JIFFIES_TIMEOUT (10*HZ) /* Maximum number of times the
+ drive will wait/try for an
+ operation */
+#define SONY_RESET_TIMEOUT HZ /* Maximum number of times the
+ drive will wait/try a reset
+ operation */
+#define SONY_READY_RETRIES 20000 /* How many times to retry a
+ spin waiting for a register
+ to come ready */
+
+#define MAX_CDU31A_RETRIES 3 /* How many times to retry an
+ operation */
+
+/* Commands to request or set drive control parameters and disc information */
+#define SONY_REQ_DRIVE_CONFIG_CMD 0x00 /* Returns s_sony_drive_config */
+#define SONY_REQ_DRIVE_MODE_CMD 0x01
+#define SONY_REQ_DRIVE_PARAM_CMD 0x02
+#define SONY_REQ_MECH_STATUS_CMD 0x03
+#define SONY_REQ_AUDIO_STATUS_CMD 0x04
+#define SONY_SET_DRIVE_PARAM_CMD 0x10
+#define SONY_REQ_TOC_DATA_CMD 0x20 /* Returns s_sony_toc */
+#define SONY_REQ_SUBCODE_ADDRESS_CMD 0x21 /* Returns s_sony_subcode */
+#define SONY_REQ_UPC_EAN_CMD 0x22
+#define SONY_REQ_ISRC_CMD 0x23
+#define SONY_REQ_TOC_DATA_SPEC_CMD 0x24 /* Returns s_sony_session_toc */
+
+/* Commands to request information from the drive */
+#define SONY_READ_TOC_CMD 0x30 /* let the drive firmware grab the TOC */
+#define SONY_SEEK_CMD 0x31
+#define SONY_READ_CMD 0x32
+#define SONY_READ_BLKERR_STAT_CMD 0x34
+#define SONY_ABORT_CMD 0x35
+#define SONY_READ_TOC_SPEC_CMD 0x36
+
+/* Commands to control audio */
+#define SONY_AUDIO_PLAYBACK_CMD 0x40
+#define SONY_AUDIO_STOP_CMD 0x41
+#define SONY_AUDIO_SCAN_CMD 0x42
+
+/* Miscellaneous control commands */
+#define SONY_EJECT_CMD 0x50
+#define SONY_SPIN_UP_CMD 0x51
+#define SONY_SPIN_DOWN_CMD 0x52
+
+/* Diagnostic commands */
+#define SONY_WRITE_BUFFER_CMD 0x60
+#define SONY_READ_BUFFER_CMD 0x61
+#define SONY_DIAGNOSTICS_CMD 0x62
+
+
+/*
+ * The following are command parameters for the set drive parameter command
+ */
+#define SONY_SD_DECODE_PARAM 0x00
+#define SONY_SD_INTERFACE_PARAM 0x01
+#define SONY_SD_BUFFERING_PARAM 0x02
+#define SONY_SD_AUDIO_PARAM 0x03
+#define SONY_SD_AUDIO_VOLUME 0x04
+#define SONY_SD_MECH_CONTROL 0x05
+#define SONY_SD_AUTO_SPIN_DOWN_TIME 0x06
+
+/*
+ * The following are parameter bits for the mechanical control command
+ */
+#define SONY_AUTO_SPIN_UP_BIT 0x01
+#define SONY_AUTO_EJECT_BIT 0x02
+#define SONY_DOUBLE_SPEED_BIT 0x04
+
+/*
+ * The following extract information from the drive configuration about
+ * the drive itself.
+ */
+#define SONY_HWC_GET_LOAD_MECH(c) (c.hw_config[0] & 0x03)
+#define SONY_HWC_EJECT(c) (c.hw_config[0] & 0x04)
+#define SONY_HWC_LED_SUPPORT(c) (c.hw_config[0] & 0x08)
+#define SONY_HWC_DOUBLE_SPEED(c) (c.hw_config[0] & 0x10)
+#define SONY_HWC_GET_BUF_MEM_SIZE(c) ((c.hw_config[0] & 0xc0) >> 6)
+#define SONY_HWC_AUDIO_PLAYBACK(c) (c.hw_config[1] & 0x01)
+#define SONY_HWC_ELECTRIC_VOLUME(c) (c.hw_config[1] & 0x02)
+#define SONY_HWC_ELECTRIC_VOLUME_CTL(c) (c.hw_config[1] & 0x04)
+
+#define SONY_HWC_CADDY_LOAD_MECH 0x00
+#define SONY_HWC_TRAY_LOAD_MECH 0x01
+#define SONY_HWC_POPUP_LOAD_MECH 0x02
+#define SONY_HWC_UNKWN_LOAD_MECH 0x03
+
+#define SONY_HWC_8KB_BUFFER 0x00
+#define SONY_HWC_32KB_BUFFER 0x01
+#define SONY_HWC_64KB_BUFFER 0x02
+#define SONY_HWC_UNKWN_BUFFER 0x03
+
+/*
+ * This is the complete status returned from the drive configuration request
+ * command.
+ */
+struct s_sony_drive_config
+{
+ unsigned char exec_status[2];
+ char vendor_id[8];
+ char product_id[16];
+ char product_rev_level[8];
+ unsigned char hw_config[2];
+};
+
+/* The following is returned from the request subcode address command */
+struct s_sony_subcode
+{
+ unsigned char exec_status[2];
+ unsigned char address :4;
+ unsigned char control :4;
+ unsigned char track_num;
+ unsigned char index_num;
+ unsigned char rel_msf[3];
+ unsigned char reserved1;
+ unsigned char abs_msf[3];
+};
+
+#define MAX_TRACKS 100 /* The maximum tracks a disk may have. */
+/*
+ * The following is returned from the request TOC (Table Of Contents) command.
+ * (last_track_num-first_track_num+1) values are valid in tracks.
+ */
+struct s_sony_toc
+{
+ unsigned char exec_status[2];
+ unsigned char address0 :4;
+ unsigned char control0 :4;
+ unsigned char point0;
+ unsigned char first_track_num;
+ unsigned char disk_type;
+ unsigned char dummy0;
+ unsigned char address1 :4;
+ unsigned char control1 :4;
+ unsigned char point1;
+ unsigned char last_track_num;
+ unsigned char dummy1;
+ unsigned char dummy2;
+ unsigned char address2 :4;
+ unsigned char control2 :4;
+ unsigned char point2;
+ unsigned char lead_out_start_msf[3];
+ struct
+ {
+ unsigned char address :4;
+ unsigned char control :4;
+ unsigned char track;
+ unsigned char track_start_msf[3];
+ } tracks[MAX_TRACKS];
+
+ unsigned int lead_out_start_lba;
+};
+
+struct s_sony_session_toc
+{
+ unsigned char exec_status[2];
+ unsigned char session_number;
+ unsigned char address0 :4;
+ unsigned char control0 :4;
+ unsigned char point0;
+ unsigned char first_track_num;
+ unsigned char disk_type;
+ unsigned char dummy0;
+ unsigned char address1 :4;
+ unsigned char control1 :4;
+ unsigned char point1;
+ unsigned char last_track_num;
+ unsigned char dummy1;
+ unsigned char dummy2;
+ unsigned char address2 :4;
+ unsigned char control2 :4;
+ unsigned char point2;
+ unsigned char lead_out_start_msf[3];
+ unsigned char addressb0 :4;
+ unsigned char controlb0 :4;
+ unsigned char pointb0;
+ unsigned char next_poss_prog_area_msf[3];
+ unsigned char num_mode_5_pointers;
+ unsigned char max_start_outer_leadout_msf[3];
+ unsigned char addressb1 :4;
+ unsigned char controlb1 :4;
+ unsigned char pointb1;
+ unsigned char dummyb0_1[4];
+ unsigned char num_skip_interval_pointers;
+ unsigned char num_skip_track_assignments;
+ unsigned char dummyb0_2;
+ unsigned char addressb2 :4;
+ unsigned char controlb2 :4;
+ unsigned char pointb2;
+ unsigned char tracksb2[7];
+ unsigned char addressb3 :4;
+ unsigned char controlb3 :4;
+ unsigned char pointb3;
+ unsigned char tracksb3[7];
+ unsigned char addressb4 :4;
+ unsigned char controlb4 :4;
+ unsigned char pointb4;
+ unsigned char tracksb4[7];
+ unsigned char addressc0 :4;
+ unsigned char controlc0 :4;
+ unsigned char pointc0;
+ unsigned char dummyc0[7];
+ struct
+ {
+ unsigned char address :4;
+ unsigned char control :4;
+ unsigned char track;
+ unsigned char track_start_msf[3];
+ } tracks[MAX_TRACKS];
+
+ unsigned int start_track_lba;
+ unsigned int lead_out_start_lba;
+ unsigned int mint;
+ unsigned int maxt;
+};
+
+struct s_all_sessions_toc
+{
+ unsigned char sessions;
+ unsigned int track_entries;
+ unsigned char first_track_num;
+ unsigned char last_track_num;
+ unsigned char disk_type;
+ unsigned char lead_out_start_msf[3];
+ struct
+ {
+ unsigned char address :4;
+ unsigned char control :4;
+ unsigned char track;
+ unsigned char track_start_msf[3];
+ } tracks[MAX_TRACKS];
+
+ unsigned int start_track_lba;
+ unsigned int lead_out_start_lba;
+};
+
+
+/*
+ * The following are errors returned from the drive.
+ */
+
+/* Command error group */
+#define SONY_ILL_CMD_ERR 0x10
+#define SONY_ILL_PARAM_ERR 0x11
+
+/* Mechanism group */
+#define SONY_NOT_LOAD_ERR 0x20
+#define SONY_NO_DISK_ERR 0x21
+#define SONY_NOT_SPIN_ERR 0x22
+#define SONY_SPIN_ERR 0x23
+#define SONY_SPINDLE_SERVO_ERR 0x25
+#define SONY_FOCUS_SERVO_ERR 0x26
+#define SONY_EJECT_MECH_ERR 0x29
+#define SONY_AUDIO_PLAYING_ERR 0x2a
+#define SONY_EMERGENCY_EJECT_ERR 0x2c
+
+/* Seek error group */
+#define SONY_FOCUS_ERR 0x30
+#define SONY_FRAME_SYNC_ERR 0x31
+#define SONY_SUBCODE_ADDR_ERR 0x32
+#define SONY_BLOCK_SYNC_ERR 0x33
+#define SONY_HEADER_ADDR_ERR 0x34
+
+/* Read error group */
+#define SONY_ILL_TRACK_R_ERR 0x40
+#define SONY_MODE_0_R_ERR 0x41
+#define SONY_ILL_MODE_R_ERR 0x42
+#define SONY_ILL_BLOCK_SIZE_R_ERR 0x43
+#define SONY_MODE_R_ERR 0x44
+#define SONY_FORM_R_ERR 0x45
+#define SONY_LEAD_OUT_R_ERR 0x46
+#define SONY_BUFFER_OVERRUN_R_ERR 0x47
+
+/* Data error group */
+#define SONY_UNREC_CIRC_ERR 0x53
+#define SONY_UNREC_LECC_ERR 0x57
+
+/* Subcode error group */
+#define SONY_NO_TOC_ERR 0x60
+#define SONY_SUBCODE_DATA_NVAL_ERR 0x61
+#define SONY_FOCUS_ON_TOC_READ_ERR 0x63
+#define SONY_FRAME_SYNC_ON_TOC_READ_ERR 0x64
+#define SONY_TOC_DATA_ERR 0x65
+
+/* Hardware failure group */
+#define SONY_HW_FAILURE_ERR 0x70
+#define SONY_LEAD_IN_A_ERR 0x91
+#define SONY_LEAD_OUT_A_ERR 0x92
+#define SONY_DATA_TRACK_A_ERR 0x93
+
+/*
+ * The following are returned from the Read With Block Error Status command.
+ * They are not errors but information (Errors from the 0x5x group above may
+ * also be returned
+ */
+#define SONY_NO_CIRC_ERR_BLK_STAT 0x50
+#define SONY_NO_LECC_ERR_BLK_STAT 0x54
+#define SONY_RECOV_LECC_ERR_BLK_STAT 0x55
+#define SONY_NO_ERR_DETECTION_STAT 0x59
+
+/*
+ * The following is not an error returned by the drive, but by the code
+ * that talks to the drive. It is returned because of a timeout.
+ */
+#define SONY_TIMEOUT_OP_ERR 0x01
+#define SONY_SIGNAL_OP_ERR 0x02
+#define SONY_BAD_DATA_ERR 0x03
+
+
+/*
+ * The following are attention code for asynchronous events from the drive.
+ */
+
+/* Standard attention group */
+#define SONY_EMER_EJECT_ATTN 0x2c
+#define SONY_HW_FAILURE_ATTN 0x70
+#define SONY_MECH_LOADED_ATTN 0x80
+#define SONY_EJECT_PUSHED_ATTN 0x81
+
+/* Audio attention group */
+#define SONY_AUDIO_PLAY_DONE_ATTN 0x90
+#define SONY_LEAD_IN_ERR_ATTN 0x91
+#define SONY_LEAD_OUT_ERR_ATTN 0x92
+#define SONY_DATA_TRACK_ERR_ATTN 0x93
+#define SONY_AUDIO_PLAYBACK_ERR_ATTN 0x94
+
+/* Auto spin up group */
+#define SONY_SPIN_UP_COMPLETE_ATTN 0x24
+#define SONY_SPINDLE_SERVO_ERR_ATTN 0x25
+#define SONY_FOCUS_SERVO_ERR_ATTN 0x26
+#define SONY_TOC_READ_DONE_ATTN 0x62
+#define SONY_FOCUS_ON_TOC_READ_ERR_ATTN 0x63
+#define SONY_SYNC_ON_TOC_READ_ERR_ATTN 0x65
+
+/* Auto eject group */
+#define SONY_SPIN_DOWN_COMPLETE_ATTN 0x27
+#define SONY_EJECT_COMPLETE_ATTN 0x28
+#define SONY_EJECT_MECH_ERR_ATTN 0x29
diff --git a/trunk/drivers/cdrom/cm206.c b/trunk/drivers/cdrom/cm206.c
new file mode 100644
index 000000000000..230131163240
--- /dev/null
+++ b/trunk/drivers/cdrom/cm206.c
@@ -0,0 +1,1594 @@
+/* cm206.c. A linux-driver for the cm206 cdrom player with cm260 adapter card.
+ Copyright (c) 1995--1997 David A. van Leeuwen.
+ $Id: cm206.c,v 1.5 1997/12/26 11:02:51 david Exp $
+
+ 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.
+
+History:
+ Started 25 jan 1994. Waiting for documentation...
+ 22 feb 1995: 0.1a first reasonably safe polling driver.
+ Two major bugs, one in read_sector and one in
+ do_cm206_request, happened to cancel!
+ 25 feb 1995: 0.2a first reasonable interrupt driven version of above.
+ uart writes are still done in polling mode.
+ 25 feb 1995: 0.21a writes also in interrupt mode, still some
+ small bugs to be found... Larger buffer.
+ 2 mrt 1995: 0.22 Bug found (cd-> nowhere, interrupt was called in
+ initialization), read_ahead of 16. Timeouts implemented.
+ unclear if they do something...
+ 7 mrt 1995: 0.23 Start of background read-ahead.
+ 18 mrt 1995: 0.24 Working background read-ahead. (still problems)
+ 26 mrt 1995: 0.25 Multi-session ioctl added (kernel v1.2).
+ Statistics implemented, though separate stats206.h.
+ Accessible through ioctl 0x1000 (just a number).
+ Hard to choose between v1.2 development and 1.1.75.
+ Bottom-half doesn't work with 1.2...
+ 0.25a: fixed... typo. Still problems...
+ 1 apr 1995: 0.26 Module support added. Most bugs found. Use kernel 1.2.n.
+ 5 apr 1995: 0.27 Auto-probe for the adapter card base address.
+ Auto-probe for the adaptor card irq line.
+ 7 apr 1995: 0.28 Added lilo setup support for base address and irq.
+ Use major number 32 (not in this source), officially
+ assigned to this driver.
+ 9 apr 1995: 0.29 Added very limited audio support. Toc_header, stop, pause,
+ resume, eject. Play_track ignores track info, because we can't
+ read a table-of-contents entry. Toc_entry is implemented
+ as a `placebo' function: always returns start of disc.
+ 3 may 1995: 0.30 Audio support completed. The get_toc_entry function
+ is implemented as a binary search.
+ 15 may 1995: 0.31 More work on audio stuff. Workman is not easy to
+ satisfy; changed binary search into linear search.
+ Auto-probe for base address somewhat relaxed.
+ 1 jun 1995: 0.32 Removed probe_irq_on/off for module version.
+ 10 jun 1995: 0.33 Workman still behaves funny, but you should be
+ able to eject and substitute another disc.
+
+ An adaptation of 0.33 is included in linux-1.3.7 by Eberhard Moenkeberg
+
+ 18 jul 1995: 0.34 Patch by Heiko Eissfeldt included, mainly considering
+ verify_area's in the ioctls. Some bugs introduced by
+ EM considering the base port and irq fixed.
+
+ 18 dec 1995: 0.35 Add some code for error checking... no luck...
+
+ We jump to reach our goal: version 1.0 in the next stable linux kernel.
+
+ 19 mar 1996: 0.95 Different implementation of CDROM_GET_UPC, on
+ request of Thomas Quinot.
+ 25 mar 1996: 0.96 Interpretation of opening with O_WRONLY or O_RDWR:
+ open only for ioctl operation, e.g., for operation of
+ tray etc.
+ 4 apr 1996: 0.97 First implementation of layer between VFS and cdrom
+ driver, a generic interface. Much of the functionality
+ of cm206_open() and cm206_ioctl() is transferred to a
+ new file cdrom.c and its header ucdrom.h.
+
+ Upgrade to Linux kernel 1.3.78.
+
+ 11 apr 1996 0.98 Upgrade to Linux kernel 1.3.85
+ More code moved to cdrom.c
+
+ 0.99 Some more small changes to decrease number
+ of oopses at module load;
+
+ 27 jul 1996 0.100 Many hours of debugging, kernel change from 1.2.13
+ to 2.0.7 seems to have introduced some weird behavior
+ in (interruptible_)sleep_on(&cd->data): the process
+ seems to be woken without any explicit wake_up in my own
+ code. Patch to try 100x in case such untriggered wake_up's
+ occur.
+
+ 28 jul 1996 0.101 Rewriting of the code that receives the command echo,
+ using a fifo to store echoed bytes.
+
+ Branch from 0.99:
+
+ 0.99.1.0 Update to kernel release 2.0.10 dev_t -> kdev_t
+ (emoenke) various typos found by others. extra
+ module-load oops protection.
+
+ 0.99.1.1 Initialization constant cdrom_dops.speed
+ changed from float (2.0) to int (2); Cli()-sti() pair
+ around cm260_reset() in module initialization code.
+
+ 0.99.1.2 Changes literally as proposed by Scott Snyder
+ for the 2.1 kernel line, which
+ have to do mainly with the poor minor support i had. The
+ major new concept is to change a cdrom driver's
+ operations struct from the capabilities struct. This
+ reflects the fact that there is one major for a driver,
+ whilst there can be many minors whith completely
+ different capabilities.
+
+ 0.99.1.3 More changes for operations/info separation.
+
+ 0.99.1.4 Added speed selection (someone had to do this
+ first).
+
+ 23 jan 1997 0.99.1.5 MODULE_PARMS call added.
+
+ 23 jan 1997 0.100.1.2--0.100.1.5 following similar lines as
+ 0.99.1.1--0.99.1.5. I get too many complaints about the
+ drive making read errors. What't wrong with the 2.0+
+ kernel line? Why get i (and othe cm206 owners) weird
+ results? Why were things good in the good old 1.1--1.2
+ era? Why don't i throw away the drive?
+
+ 2 feb 1997 0.102 Added `volatile' to values in cm206_struct. Seems to
+ reduce many of the problems. Rewrote polling routines
+ to use fixed delays between polls.
+ 0.103 Changed printk behavior.
+ 0.104 Added a 0.100 -> 0.100.1.1 change
+
+11 feb 1997 0.105 Allow auto_probe during module load, disable
+ with module option "auto_probe=0". Moved some debugging
+ statements to lower priority. Implemented select_speed()
+ function.
+
+13 feb 1997 1.0 Final version for 2.0 kernel line.
+
+ All following changes will be for the 2.1 kernel line.
+
+15 feb 1997 1.1 Keep up with kernel 2.1.26, merge in changes from
+ cdrom.c 0.100.1.1--1.0. Add some more MODULE_PARMS.
+
+14 sep 1997 1.2 Upgrade to Linux 2.1.55. Added blksize_size[], patch
+ sent by James Bottomley .
+
+21 dec 1997 1.4 Upgrade to Linux 2.1.72.
+
+24 jan 1998 Removed the cm206_disc_status() function, as it was now dead
+ code. The Uniform CDROM driver now provides this functionality.
+
+9 Nov. 1999 Make kernel-parameter implementation work with 2.3.x
+ Removed init_module & cleanup_module in favor of
+ module_init & module_exit.
+ Torben Mathiasen
+ *
+ * Parts of the code are based upon lmscd.c written by Kai Petzke,
+ * sbpcd.c written by Eberhard Moenkeberg, and mcd.c by Martin
+ * Harriss, but any off-the-shelf dynamic programming algorithm won't
+ * be able to find them.
+ *
+ * The cm206 drive interface and the cm260 adapter card seem to be
+ * sufficiently different from their cm205/cm250 counterparts
+ * in order to write a complete new driver.
+ *
+ * I call all routines connected to the Linux kernel something
+ * with `cm206' in it, as this stuff is too series-dependent.
+ *
+ * Currently, my limited knowledge is based on:
+ * - The Linux Kernel Hacker's guide, v. 0.5, by Michael K. Johnson
+ * - Linux Kernel Programmierung, by Michael Beck and others
+ * - Philips/LMS cm206 and cm226 product specification
+ * - Philips/LMS cm260 product specification
+ *
+ * David van Leeuwen, david@tm.tno.nl. */
+#define REVISION "$Revision: 1.5 $"
+
+#include
+
+#include /* These include what we really need */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* #include */
+
+#include
+
+#define MAJOR_NR CM206_CDROM_MAJOR
+
+#include
+
+#undef DEBUG
+#define STATISTICS /* record times and frequencies of events */
+#define AUTO_PROBE_MODULE
+#define USE_INSW
+
+#include "cm206.h"
+
+/* This variable defines whether or not to probe for adapter base port
+ address and interrupt request. It can be overridden by the boot
+ parameter `auto'.
+*/
+static int auto_probe = 1; /* Yes, why not? */
+
+static int cm206_base = CM206_BASE;
+static int cm206_irq = CM206_IRQ;
+#ifdef MODULE
+static int cm206[2] = { 0, 0 }; /* for compatible `insmod' parameter passing */
+module_param_array(cm206, int, NULL, 0); /* base,irq or irq,base */
+#endif
+
+module_param(cm206_base, int, 0); /* base */
+module_param(cm206_irq, int, 0); /* irq */
+module_param(auto_probe, bool, 0); /* auto probe base and irq */
+MODULE_LICENSE("GPL");
+
+#define POLLOOP 100 /* milliseconds */
+#define READ_AHEAD 1 /* defines private buffer, waste! */
+#define BACK_AHEAD 1 /* defines adapter-read ahead */
+#define DATA_TIMEOUT (3*HZ) /* measured in jiffies (10 ms) */
+#define UART_TIMEOUT (5*HZ/100)
+#define DSB_TIMEOUT (7*HZ) /* time for the slowest command to finish */
+#define UR_SIZE 4 /* uart receive buffer fifo size */
+
+#define LINUX_BLOCK_SIZE 512 /* WHERE is this defined? */
+#define RAW_SECTOR_SIZE 2352 /* ok, is also defined in cdrom.h */
+#define ISO_SECTOR_SIZE 2048
+#define BLOCKS_ISO (ISO_SECTOR_SIZE/LINUX_BLOCK_SIZE) /* 4 */
+#define CD_SYNC_HEAD 16 /* CD_SYNC + CD_HEAD */
+
+#ifdef STATISTICS /* keep track of errors in counters */
+#define stats(i) { ++cd->stats[st_ ## i]; \
+ cd->last_stat[st_ ## i] = cd->stat_counter++; \
+ }
+#else
+#define stats(i) (void) 0;
+#endif
+
+#define Debug(a) {printk (KERN_DEBUG); printk a;}
+#ifdef DEBUG
+#define debug(a) Debug(a)
+#else
+#define debug(a) (void) 0;
+#endif
+
+typedef unsigned char uch; /* 8-bits */
+typedef unsigned short ush; /* 16-bits */
+
+struct toc_struct { /* private copy of Table of Contents */
+ uch track, fsm[3], q0;
+};
+
+struct cm206_struct {
+ volatile ush intr_ds; /* data status read on last interrupt */
+ volatile ush intr_ls; /* uart line status read on last interrupt */
+ volatile uch ur[UR_SIZE]; /* uart receive buffer fifo */
+ volatile uch ur_w, ur_r; /* write/read buffer index */
+ volatile uch dsb, cc; /* drive status byte and condition (error) code */
+ int command; /* command to be written to the uart */
+ int openfiles;
+ ush sector[READ_AHEAD * RAW_SECTOR_SIZE / 2]; /* buffered cd-sector */
+ int sector_first, sector_last; /* range of these sectors */
+ wait_queue_head_t uart; /* wait queues for interrupt */
+ wait_queue_head_t data;
+ struct timer_list timer; /* time-out */
+ char timed_out;
+ signed char max_sectors; /* number of sectors that fit in adapter mem */
+ char wait_back; /* we're waiting for a background-read */
+ char background; /* is a read going on in the background? */
+ int adapter_first; /* if so, that's the starting sector */
+ int adapter_last;
+ char fifo_overflowed;
+ uch disc_status[7]; /* result of get_disc_status command */
+#ifdef STATISTICS
+ int stats[NR_STATS];
+ int last_stat[NR_STATS]; /* `time' at which stat was stat */
+ int stat_counter;
+#endif
+ struct toc_struct toc[101]; /* The whole table of contents + lead-out */
+ uch q[10]; /* Last read q-channel info */
+ uch audio_status[5]; /* last read position on pause */
+ uch media_changed; /* record if media changed */
+};
+
+#define DISC_STATUS cd->disc_status[0]
+#define FIRST_TRACK cd->disc_status[1]
+#define LAST_TRACK cd->disc_status[2]
+#define PAUSED cd->audio_status[0] /* misuse this memory byte! */
+#define PLAY_TO cd->toc[0] /* toc[0] records end-time in play */
+
+static struct cm206_struct *cd; /* the main memory structure */
+static struct request_queue *cm206_queue;
+static DEFINE_SPINLOCK(cm206_lock);
+
+/* First, we define some polling functions. These are actually
+ only being used in the initialization. */
+
+static void send_command_polled(int command)
+{
+ int loop = POLLOOP;
+ while (!(inw(r_line_status) & ls_transmitter_buffer_empty)
+ && loop > 0) {
+ mdelay(1); /* one millisec delay */
+ --loop;
+ }
+ outw(command, r_uart_transmit);
+}
+
+static uch receive_echo_polled(void)
+{
+ int loop = POLLOOP;
+ while (!(inw(r_line_status) & ls_receive_buffer_full) && loop > 0) {
+ mdelay(1);
+ --loop;
+ }
+ return ((uch) inw(r_uart_receive));
+}
+
+static uch send_receive_polled(int command)
+{
+ send_command_polled(command);
+ return receive_echo_polled();
+}
+
+static inline void clear_ur(void)
+{
+ if (cd->ur_r != cd->ur_w) {
+ debug(("Deleting bytes from fifo:"));
+ for (; cd->ur_r != cd->ur_w;
+ cd->ur_r++, cd->ur_r %= UR_SIZE)
+ debug((" 0x%x", cd->ur[cd->ur_r]));
+ debug(("\n"));
+ }
+}
+
+static struct tasklet_struct cm206_tasklet;
+
+/* The interrupt handler. When the cm260 generates an interrupt, very
+ much care has to be taken in reading out the registers in the right
+ order; in case of a receive_buffer_full interrupt, first the
+ uart_receive must be read, and then the line status again to
+ de-assert the interrupt line. It took me a couple of hours to find
+ this out:-(
+
+ The function reset_cm206 appears to cause an interrupt, because
+ pulling up the INIT line clears both the uart-write-buffer /and/
+ the uart-write-buffer-empty mask. We call this a `lost interrupt,'
+ as there seems so reason for this to happen.
+*/
+
+static irqreturn_t cm206_interrupt(int sig, void *dev_id)
+{
+ volatile ush fool;
+ cd->intr_ds = inw(r_data_status); /* resets data_ready, data_error,
+ crc_error, sync_error, toc_ready
+ interrupts */
+ cd->intr_ls = inw(r_line_status); /* resets overrun bit */
+ debug(("Intr, 0x%x 0x%x, %d\n", cd->intr_ds, cd->intr_ls,
+ cd->background));
+ if (cd->intr_ls & ls_attention)
+ stats(attention);
+ /* receive buffer full? */
+ if (cd->intr_ls & ls_receive_buffer_full) {
+ cd->ur[cd->ur_w] = inb(r_uart_receive); /* get order right! */
+ cd->intr_ls = inw(r_line_status); /* resets rbf interrupt */
+ debug(("receiving #%d: 0x%x\n", cd->ur_w,
+ cd->ur[cd->ur_w]));
+ cd->ur_w++;
+ cd->ur_w %= UR_SIZE;
+ if (cd->ur_w == cd->ur_r)
+ debug(("cd->ur overflow!\n"));
+ if (waitqueue_active(&cd->uart) && cd->background < 2) {
+ del_timer(&cd->timer);
+ wake_up_interruptible(&cd->uart);
+ }
+ }
+ /* data ready in fifo? */
+ else if (cd->intr_ds & ds_data_ready) {
+ if (cd->background)
+ ++cd->adapter_last;
+ if (waitqueue_active(&cd->data)
+ && (cd->wait_back || !cd->background)) {
+ del_timer(&cd->timer);
+ wake_up_interruptible(&cd->data);
+ }
+ stats(data_ready);
+ }
+ /* ready to issue a write command? */
+ else if (cd->command && cd->intr_ls & ls_transmitter_buffer_empty) {
+ outw(dc_normal | (inw(r_data_status) & 0x7f),
+ r_data_control);
+ outw(cd->command, r_uart_transmit);
+ cd->command = 0;
+ if (!cd->background)
+ wake_up_interruptible(&cd->uart);
+ }
+ /* now treat errors (at least, identify them for debugging) */
+ else if (cd->intr_ds & ds_fifo_overflow) {
+ debug(("Fifo overflow at sectors 0x%x\n",
+ cd->sector_first));
+ fool = inw(r_fifo_output_buffer); /* de-assert the interrupt */
+ cd->fifo_overflowed = 1; /* signal one word less should be read */
+ stats(fifo_overflow);
+ } else if (cd->intr_ds & ds_data_error) {
+ debug(("Data error at sector 0x%x\n", cd->sector_first));
+ stats(data_error);
+ } else if (cd->intr_ds & ds_crc_error) {
+ debug(("CRC error at sector 0x%x\n", cd->sector_first));
+ stats(crc_error);
+ } else if (cd->intr_ds & ds_sync_error) {
+ debug(("Sync at sector 0x%x\n", cd->sector_first));
+ stats(sync_error);
+ } else if (cd->intr_ds & ds_toc_ready) {
+ /* do something appropriate */
+ }
+ /* couldn't see why this interrupt, maybe due to init */
+ else {
+ outw(dc_normal | READ_AHEAD, r_data_control);
+ stats(lost_intr);
+ }
+ if (cd->background
+ && (cd->adapter_last - cd->adapter_first == cd->max_sectors
+ || cd->fifo_overflowed))
+ tasklet_schedule(&cm206_tasklet); /* issue a stop read command */
+ stats(interrupt);
+ return IRQ_HANDLED;
+}
+
+/* we have put the address of the wait queue in who */
+static void cm206_timeout(unsigned long who)
+{
+ cd->timed_out = 1;
+ debug(("Timing out\n"));
+ wake_up_interruptible((wait_queue_head_t *) who);
+}
+
+/* This function returns 1 if a timeout occurred, 0 if an interrupt
+ happened */
+static int sleep_or_timeout(wait_queue_head_t * wait, int timeout)
+{
+ cd->timed_out = 0;
+ init_timer(&cd->timer);
+ cd->timer.data = (unsigned long) wait;
+ cd->timer.expires = jiffies + timeout;
+ add_timer(&cd->timer);
+ debug(("going to sleep\n"));
+ interruptible_sleep_on(wait);
+ del_timer(&cd->timer);
+ if (cd->timed_out) {
+ cd->timed_out = 0;
+ return 1;
+ } else
+ return 0;
+}
+
+static void send_command(int command)
+{
+ debug(("Sending 0x%x\n", command));
+ if (!(inw(r_line_status) & ls_transmitter_buffer_empty)) {
+ cd->command = command;
+ cli(); /* don't interrupt before sleep */
+ outw(dc_mask_sync_error | dc_no_stop_on_error |
+ (inw(r_data_status) & 0x7f), r_data_control);
+ /* interrupt routine sends command */
+ if (sleep_or_timeout(&cd->uart, UART_TIMEOUT)) {
+ debug(("Time out on write-buffer\n"));
+ stats(write_timeout);
+ outw(command, r_uart_transmit);
+ }
+ debug(("Write commmand delayed\n"));
+ } else
+ outw(command, r_uart_transmit);
+}
+
+static uch receive_byte(int timeout)
+{
+ uch ret;
+ cli();
+ debug(("cli\n"));
+ ret = cd->ur[cd->ur_r];
+ if (cd->ur_r != cd->ur_w) {
+ sti();
+ debug(("returning #%d: 0x%x\n", cd->ur_r,
+ cd->ur[cd->ur_r]));
+ cd->ur_r++;
+ cd->ur_r %= UR_SIZE;
+ return ret;
+ } else if (sleep_or_timeout(&cd->uart, timeout)) { /* does sti() */
+ debug(("Time out on receive-buffer\n"));
+#ifdef STATISTICS
+ if (timeout == UART_TIMEOUT)
+ stats(receive_timeout) /* no `;'! */
+ else
+ stats(dsb_timeout);
+#endif
+ return 0xda;
+ }
+ ret = cd->ur[cd->ur_r];
+ debug(("slept; returning #%d: 0x%x\n", cd->ur_r,
+ cd->ur[cd->ur_r]));
+ cd->ur_r++;
+ cd->ur_r %= UR_SIZE;
+ return ret;
+}
+
+static inline uch receive_echo(void)
+{
+ return receive_byte(UART_TIMEOUT);
+}
+
+static inline uch send_receive(int command)
+{
+ send_command(command);
+ return receive_echo();
+}
+
+static inline uch wait_dsb(void)
+{
+ return receive_byte(DSB_TIMEOUT);
+}
+
+static int type_0_command(int command, int expect_dsb)
+{
+ int e;
+ clear_ur();
+ if (command != (e = send_receive(command))) {
+ debug(("command 0x%x echoed as 0x%x\n", command, e));
+ stats(echo);
+ return -1;
+ }
+ if (expect_dsb) {
+ cd->dsb = wait_dsb(); /* wait for command to finish */
+ }
+ return 0;
+}
+
+static int type_1_command(int command, int bytes, uch * status)
+{ /* returns info */
+ int i;
+ if (type_0_command(command, 0))
+ return -1;
+ for (i = 0; i < bytes; i++)
+ status[i] = send_receive(c_gimme);
+ return 0;
+}
+
+/* This function resets the adapter card. We'd better not do this too
+ * often, because it tends to generate `lost interrupts.' */
+static void reset_cm260(void)
+{
+ outw(dc_normal | dc_initialize | READ_AHEAD, r_data_control);
+ udelay(10); /* 3.3 mu sec minimum */
+ outw(dc_normal | READ_AHEAD, r_data_control);
+}
+
+/* fsm: frame-sec-min from linear address; one of many */
+static void fsm(int lba, uch * fsm)
+{
+ fsm[0] = lba % 75;
+ lba /= 75;
+ lba += 2;
+ fsm[1] = lba % 60;
+ fsm[2] = lba / 60;
+}
+
+static inline int fsm2lba(uch * fsm)
+{
+ return fsm[0] + 75 * (fsm[1] - 2 + 60 * fsm[2]);
+}
+
+static inline int f_s_m2lba(uch f, uch s, uch m)
+{
+ return f + 75 * (s - 2 + 60 * m);
+}
+
+static int start_read(int start)
+{
+ uch read_sector[4] = { c_read_data, };
+ int i, e;
+
+ fsm(start, &read_sector[1]);
+ clear_ur();
+ for (i = 0; i < 4; i++)
+ if (read_sector[i] != (e = send_receive(read_sector[i]))) {
+ debug(("read_sector: %x echoes %x\n",
+ read_sector[i], e));
+ stats(echo);
+ if (e == 0xff) { /* this seems to happen often */
+ e = receive_echo();
+ debug(("Second try %x\n", e));
+ if (e != read_sector[i])
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int stop_read(void)
+{
+ int e;
+ type_0_command(c_stop, 0);
+ if ((e = receive_echo()) != 0xff) {
+ debug(("c_stop didn't send 0xff, but 0x%x\n", e));
+ stats(stop_0xff);
+ return -1;
+ }
+ return 0;
+}
+
+/* This function starts to read sectors in adapter memory, the
+ interrupt routine should stop the read. In fact, the bottom_half
+ routine takes care of this. Set a flag `background' in the cd
+ struct to indicate the process. */
+
+static int read_background(int start, int reading)
+{
+ if (cd->background)
+ return -1; /* can't do twice */
+ outw(dc_normal | BACK_AHEAD, r_data_control);
+ if (!reading && start_read(start))
+ return -2;
+ cd->adapter_first = cd->adapter_last = start;
+ cd->background = 1; /* flag a read is going on */
+ return 0;
+}
+
+#ifdef USE_INSW
+#define transport_data insw
+#else
+/* this routine implements insw(,,). There was a time i had the
+ impression that there would be any difference in error-behaviour. */
+void transport_data(int port, ush * dest, int count)
+{
+ int i;
+ ush *d;
+ for (i = 0, d = dest; i < count; i++, d++)
+ *d = inw(port);
+}
+#endif
+
+
+#define MAX_TRIES 100
+static int read_sector(int start)
+{
+ int tries = 0;
+ if (cd->background) {
+ cd->background = 0;
+ cd->adapter_last = -1; /* invalidate adapter memory */
+ stop_read();
+ }
+ cd->fifo_overflowed = 0;
+ reset_cm260(); /* empty fifo etc. */
+ if (start_read(start))
+ return -1;
+ do {
+ if (sleep_or_timeout(&cd->data, DATA_TIMEOUT)) {
+ debug(("Read timed out sector 0x%x\n", start));
+ stats(read_timeout);
+ stop_read();
+ return -3;
+ }
+ tries++;
+ } while (cd->intr_ds & ds_fifo_empty && tries < MAX_TRIES);
+ if (tries > 1)
+ debug(("Took me some tries\n"))
+ else
+ if (tries == MAX_TRIES)
+ debug(("MAX_TRIES tries for read sector\n"));
+ transport_data(r_fifo_output_buffer, cd->sector,
+ READ_AHEAD * RAW_SECTOR_SIZE / 2);
+ if (read_background(start + READ_AHEAD, 1))
+ stats(read_background);
+ cd->sector_first = start;
+ cd->sector_last = start + READ_AHEAD;
+ stats(read_restarted);
+ return 0;
+}
+
+/* The function of bottom-half is to send a stop command to the drive
+ This isn't easy because the routine is not `owned' by any process;
+ we can't go to sleep! The variable cd->background gives the status:
+ 0 no read pending
+ 1 a read is pending
+ 2 c_stop waits for write_buffer_empty
+ 3 c_stop waits for receive_buffer_full: echo
+ 4 c_stop waits for receive_buffer_full: 0xff
+*/
+
+static void cm206_tasklet_func(unsigned long ignore)
+{
+ debug(("bh: %d\n", cd->background));
+ switch (cd->background) {
+ case 1:
+ stats(bh);
+ if (!(cd->intr_ls & ls_transmitter_buffer_empty)) {
+ cd->command = c_stop;
+ outw(dc_mask_sync_error | dc_no_stop_on_error |
+ (inw(r_data_status) & 0x7f), r_data_control);
+ cd->background = 2;
+ break; /* we'd better not time-out here! */
+ } else
+ outw(c_stop, r_uart_transmit);
+ /* fall into case 2: */
+ case 2:
+ /* the write has been satisfied by interrupt routine */
+ cd->background = 3;
+ break;
+ case 3:
+ if (cd->ur_r != cd->ur_w) {
+ if (cd->ur[cd->ur_r] != c_stop) {
+ debug(("cm206_bh: c_stop echoed 0x%x\n",
+ cd->ur[cd->ur_r]));
+ stats(echo);
+ }
+ cd->ur_r++;
+ cd->ur_r %= UR_SIZE;
+ }
+ cd->background++;
+ break;
+ case 4:
+ if (cd->ur_r != cd->ur_w) {
+ if (cd->ur[cd->ur_r] != 0xff) {
+ debug(("cm206_bh: c_stop reacted with 0x%x\n", cd->ur[cd->ur_r]));
+ stats(stop_0xff);
+ }
+ cd->ur_r++;
+ cd->ur_r %= UR_SIZE;
+ }
+ cd->background = 0;
+ }
+}
+
+static DECLARE_TASKLET(cm206_tasklet, cm206_tasklet_func, 0);
+
+/* This command clears the dsb_possible_media_change flag, so we must
+ * retain it.
+ */
+static void get_drive_status(void)
+{
+ uch status[2];
+ type_1_command(c_drive_status, 2, status); /* this might be done faster */
+ cd->dsb = status[0];
+ cd->cc = status[1];
+ cd->media_changed |=
+ !!(cd->dsb & (dsb_possible_media_change |
+ dsb_drive_not_ready | dsb_tray_not_closed));
+}
+
+static void get_disc_status(void)
+{
+ if (type_1_command(c_disc_status, 7, cd->disc_status)) {
+ debug(("get_disc_status: error\n"));
+ }
+}
+
+/* The new open. The real opening strategy is defined in cdrom.c. */
+
+static int cm206_open(struct cdrom_device_info *cdi, int purpose)
+{
+ if (!cd->openfiles) { /* reset only first time */
+ cd->background = 0;
+ reset_cm260();
+ cd->adapter_last = -1; /* invalidate adapter memory */
+ cd->sector_last = -1;
+ }
+ ++cd->openfiles;
+ stats(open);
+ return 0;
+}
+
+static void cm206_release(struct cdrom_device_info *cdi)
+{
+ if (cd->openfiles == 1) {
+ if (cd->background) {
+ cd->background = 0;
+ stop_read();
+ }
+ cd->sector_last = -1; /* Make our internal buffer invalid */
+ FIRST_TRACK = 0; /* No valid disc status */
+ }
+ --cd->openfiles;
+}
+
+/* Empty buffer empties $sectors$ sectors of the adapter card buffer,
+ * and then reads a sector in kernel memory. */
+static void empty_buffer(int sectors)
+{
+ while (sectors >= 0) {
+ transport_data(r_fifo_output_buffer,
+ cd->sector + cd->fifo_overflowed,
+ RAW_SECTOR_SIZE / 2 - cd->fifo_overflowed);
+ --sectors;
+ ++cd->adapter_first; /* update the current adapter sector */
+ cd->fifo_overflowed = 0; /* reset overflow bit */
+ stats(sector_transferred);
+ }
+ cd->sector_first = cd->adapter_first - 1;
+ cd->sector_last = cd->adapter_first; /* update the buffer sector */
+}
+
+/* try_adapter. This function determines if the requested sector is
+ in adapter memory, or will appear there soon. Returns 0 upon
+ success */
+static int try_adapter(int sector)
+{
+ if (cd->adapter_first <= sector && sector < cd->adapter_last) {
+ /* sector is in adapter memory */
+ empty_buffer(sector - cd->adapter_first);
+ return 0;
+ } else if (cd->background == 1 && cd->adapter_first <= sector
+ && sector < cd->adapter_first + cd->max_sectors) {
+ /* a read is going on, we can wait for it */
+ cd->wait_back = 1;
+ while (sector >= cd->adapter_last) {
+ if (sleep_or_timeout(&cd->data, DATA_TIMEOUT)) {
+ debug(("Timed out during background wait: %d %d %d %d\n", sector, cd->adapter_last, cd->adapter_first, cd->background));
+ stats(back_read_timeout);
+ cd->wait_back = 0;
+ return -1;
+ }
+ }
+ cd->wait_back = 0;
+ empty_buffer(sector - cd->adapter_first);
+ return 0;
+ } else
+ return -2;
+}
+
+/* This is not a very smart implementation. We could optimize for
+ consecutive block numbers. I'm not convinced this would really
+ bring down the processor load. */
+static void do_cm206_request(request_queue_t * q)
+{
+ long int i, cd_sec_no;
+ int quarter, error;
+ uch *source, *dest;
+ struct request *req;
+
+ while (1) { /* repeat until all requests have been satisfied */
+ req = elv_next_request(q);
+ if (!req)
+ return;
+
+ if (req->cmd != READ) {
+ debug(("Non-read command %d on cdrom\n", req->cmd));
+ end_request(req, 0);
+ continue;
+ }
+ spin_unlock_irq(q->queue_lock);
+ error = 0;
+ for (i = 0; i < req->nr_sectors; i++) {
+ int e1, e2;
+ cd_sec_no = (req->sector + i) / BLOCKS_ISO; /* 4 times 512 bytes */
+ quarter = (req->sector + i) % BLOCKS_ISO;
+ dest = req->buffer + i * LINUX_BLOCK_SIZE;
+ /* is already in buffer memory? */
+ if (cd->sector_first <= cd_sec_no
+ && cd_sec_no < cd->sector_last) {
+ source =
+ ((uch *) cd->sector) + 16 +
+ quarter * LINUX_BLOCK_SIZE +
+ (cd_sec_no -
+ cd->sector_first) * RAW_SECTOR_SIZE;
+ memcpy(dest, source, LINUX_BLOCK_SIZE);
+ } else if (!(e1 = try_adapter(cd_sec_no)) ||
+ !(e2 = read_sector(cd_sec_no))) {
+ source =
+ ((uch *) cd->sector) + 16 +
+ quarter * LINUX_BLOCK_SIZE;
+ memcpy(dest, source, LINUX_BLOCK_SIZE);
+ } else {
+ error = 1;
+ debug(("cm206_request: %d %d\n", e1, e2));
+ }
+ }
+ spin_lock_irq(q->queue_lock);
+ end_request(req, !error);
+ }
+}
+
+/* Audio support. I've tried very hard, but the cm206 drive doesn't
+ seem to have a get_toc (table-of-contents) function, while i'm
+ pretty sure it must read the toc upon disc insertion. Therefore
+ this function has been implemented through a binary search
+ strategy. All track starts that happen to be found are stored in
+ cd->toc[], for future use.
+
+ I've spent a whole day on a bug that only shows under Workman---
+ I don't get it. Tried everything, nothing works. If workman asks
+ for track# 0xaa, it'll get the wrong time back. Any other program
+ receives the correct value. I'm stymied.
+*/
+
+/* seek seeks to address lba. It does wait to arrive there. */
+static void seek(int lba)
+{
+ int i;
+ uch seek_command[4] = { c_seek, };
+
+ fsm(lba, &seek_command[1]);
+ for (i = 0; i < 4; i++)
+ type_0_command(seek_command[i], 0);
+ cd->dsb = wait_dsb();
+}
+
+static uch bcdbin(unsigned char bcd)
+{ /* stolen from mcd.c! */
+ return (bcd >> 4) * 10 + (bcd & 0xf);
+}
+
+static inline uch normalize_track(uch track)
+{
+ if (track < 1)
+ return 1;
+ if (track > LAST_TRACK)
+ return LAST_TRACK + 1;
+ return track;
+}
+
+/* This function does a binary search for track start. It records all
+ * tracks seen in the process. Input $track$ must be between 1 and
+ * #-of-tracks+1. Note that the start of the disc must be in toc[1].fsm.
+ */
+static int get_toc_lba(uch track)
+{
+ int max = 74 * 60 * 75 - 150, min = fsm2lba(cd->toc[1].fsm);
+ int i, lba, l, old_lba = 0;
+ uch *q = cd->q;
+ uch ct; /* current track */
+ int binary = 0;
+ const int skip = 3 * 60 * 75; /* 3 minutes */
+
+ for (i = track; i > 0; i--)
+ if (cd->toc[i].track) {
+ min = fsm2lba(cd->toc[i].fsm);
+ break;
+ }
+ lba = min + skip;
+ do {
+ seek(lba);
+ type_1_command(c_read_current_q, 10, q);
+ ct = normalize_track(q[1]);
+ if (!cd->toc[ct].track) {
+ l = q[9] - bcdbin(q[5]) + 75 * (q[8] -
+ bcdbin(q[4]) - 2 +
+ 60 * (q[7] -
+ bcdbin(q
+ [3])));
+ cd->toc[ct].track = q[1]; /* lead out still 0xaa */
+ fsm(l, cd->toc[ct].fsm);
+ cd->toc[ct].q0 = q[0]; /* contains adr and ctrl info */
+ if (ct == track)
+ return l;
+ }
+ old_lba = lba;
+ if (binary) {
+ if (ct < track)
+ min = lba;
+ else
+ max = lba;
+ lba = (min + max) / 2;
+ } else {
+ if (ct < track)
+ lba += skip;
+ else {
+ binary = 1;
+ max = lba;
+ min = lba - skip;
+ lba = (min + max) / 2;
+ }
+ }
+ } while (lba != old_lba);
+ return lba;
+}
+
+static void update_toc_entry(uch track)
+{
+ track = normalize_track(track);
+ if (!cd->toc[track].track)
+ get_toc_lba(track);
+}
+
+/* return 0 upon success */
+static int read_toc_header(struct cdrom_tochdr *hp)
+{
+ if (!FIRST_TRACK)
+ get_disc_status();
+ if (hp) {
+ int i;
+ hp->cdth_trk0 = FIRST_TRACK;
+ hp->cdth_trk1 = LAST_TRACK;
+ /* fill in first track position */
+ for (i = 0; i < 3; i++)
+ cd->toc[1].fsm[i] = cd->disc_status[3 + i];
+ update_toc_entry(LAST_TRACK + 1); /* find most entries */
+ return 0;
+ }
+ return -1;
+}
+
+static void play_from_to_msf(struct cdrom_msf *msfp)
+{
+ uch play_command[] = { c_play,
+ msfp->cdmsf_frame0, msfp->cdmsf_sec0, msfp->cdmsf_min0,
+ msfp->cdmsf_frame1, msfp->cdmsf_sec1, msfp->cdmsf_min1, 2,
+ 2
+ };
+ int i;
+ for (i = 0; i < 9; i++)
+ type_0_command(play_command[i], 0);
+ for (i = 0; i < 3; i++)
+ PLAY_TO.fsm[i] = play_command[i + 4];
+ PLAY_TO.track = 0; /* say no track end */
+ cd->dsb = wait_dsb();
+}
+
+static void play_from_to_track(int from, int to)
+{
+ uch play_command[8] = { c_play, };
+ int i;
+
+ if (from == 0) { /* continue paused play */
+ for (i = 0; i < 3; i++) {
+ play_command[i + 1] = cd->audio_status[i + 2];
+ play_command[i + 4] = PLAY_TO.fsm[i];
+ }
+ } else {
+ update_toc_entry(from);
+ update_toc_entry(to + 1);
+ for (i = 0; i < 3; i++) {
+ play_command[i + 1] = cd->toc[from].fsm[i];
+ PLAY_TO.fsm[i] = play_command[i + 4] =
+ cd->toc[to + 1].fsm[i];
+ }
+ PLAY_TO.track = to;
+ }
+ for (i = 0; i < 7; i++)
+ type_0_command(play_command[i], 0);
+ for (i = 0; i < 2; i++)
+ type_0_command(0x2, 0); /* volume */
+ cd->dsb = wait_dsb();
+}
+
+static int get_current_q(struct cdrom_subchnl *qp)
+{
+ int i;
+ uch *q = cd->q;
+ if (type_1_command(c_read_current_q, 10, q))
+ return 0;
+/* q[0] = bcdbin(q[0]); Don't think so! */
+ for (i = 2; i < 6; i++)
+ q[i] = bcdbin(q[i]);
+ qp->cdsc_adr = q[0] & 0xf;
+ qp->cdsc_ctrl = q[0] >> 4; /* from mcd.c */
+ qp->cdsc_trk = q[1];
+ qp->cdsc_ind = q[2];
+ if (qp->cdsc_format == CDROM_MSF) {
+ qp->cdsc_reladdr.msf.minute = q[3];
+ qp->cdsc_reladdr.msf.second = q[4];
+ qp->cdsc_reladdr.msf.frame = q[5];
+ qp->cdsc_absaddr.msf.minute = q[7];
+ qp->cdsc_absaddr.msf.second = q[8];
+ qp->cdsc_absaddr.msf.frame = q[9];
+ } else {
+ qp->cdsc_reladdr.lba = f_s_m2lba(q[5], q[4], q[3]);
+ qp->cdsc_absaddr.lba = f_s_m2lba(q[9], q[8], q[7]);
+ }
+ get_drive_status();
+ if (cd->dsb & dsb_play_in_progress)
+ qp->cdsc_audiostatus = CDROM_AUDIO_PLAY;
+ else if (PAUSED)
+ qp->cdsc_audiostatus = CDROM_AUDIO_PAUSED;
+ else
+ qp->cdsc_audiostatus = CDROM_AUDIO_NO_STATUS;
+ return 0;
+}
+
+static void invalidate_toc(void)
+{
+ memset(cd->toc, 0, sizeof(cd->toc));
+ memset(cd->disc_status, 0, sizeof(cd->disc_status));
+}
+
+/* cdrom.c guarantees that cdte_format == CDROM_MSF */
+static void get_toc_entry(struct cdrom_tocentry *ep)
+{
+ uch track = normalize_track(ep->cdte_track);
+ update_toc_entry(track);
+ ep->cdte_addr.msf.frame = cd->toc[track].fsm[0];
+ ep->cdte_addr.msf.second = cd->toc[track].fsm[1];
+ ep->cdte_addr.msf.minute = cd->toc[track].fsm[2];
+ ep->cdte_adr = cd->toc[track].q0 & 0xf;
+ ep->cdte_ctrl = cd->toc[track].q0 >> 4;
+ ep->cdte_datamode = 0;
+}
+
+/* Audio ioctl. Ioctl commands connected to audio are in such an
+ * idiosyncratic i/o format, that we leave these untouched. Return 0
+ * upon success. Memory checking has been done by cdrom_ioctl(), the
+ * calling function, as well as LBA/MSF sanitization.
+*/
+static int cm206_audio_ioctl(struct cdrom_device_info *cdi, unsigned int cmd,
+ void *arg)
+{
+ switch (cmd) {
+ case CDROMREADTOCHDR:
+ return read_toc_header((struct cdrom_tochdr *) arg);
+ case CDROMREADTOCENTRY:
+ get_toc_entry((struct cdrom_tocentry *) arg);
+ return 0;
+ case CDROMPLAYMSF:
+ play_from_to_msf((struct cdrom_msf *) arg);
+ return 0;
+ case CDROMPLAYTRKIND: /* admittedly, not particularly beautiful */
+ play_from_to_track(((struct cdrom_ti *) arg)->cdti_trk0,
+ ((struct cdrom_ti *) arg)->cdti_trk1);
+ return 0;
+ case CDROMSTOP:
+ PAUSED = 0;
+ if (cd->dsb & dsb_play_in_progress)
+ return type_0_command(c_stop, 1);
+ else
+ return 0;
+ case CDROMPAUSE:
+ get_drive_status();
+ if (cd->dsb & dsb_play_in_progress) {
+ type_0_command(c_stop, 1);
+ type_1_command(c_audio_status, 5,
+ cd->audio_status);
+ PAUSED = 1; /* say we're paused */
+ }
+ return 0;
+ case CDROMRESUME:
+ if (PAUSED)
+ play_from_to_track(0, 0);
+ PAUSED = 0;
+ return 0;
+ case CDROMSTART:
+ case CDROMVOLCTRL:
+ return 0;
+ case CDROMSUBCHNL:
+ return get_current_q((struct cdrom_subchnl *) arg);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int cm206_media_changed(struct cdrom_device_info *cdi, int disc_nr)
+{
+ if (cd != NULL) {
+ int r;
+ get_drive_status(); /* ensure cd->media_changed OK */
+ r = cd->media_changed;
+ cd->media_changed = 0; /* clear bit */
+ return r;
+ } else
+ return -EIO;
+}
+
+/* The new generic cdrom support. Routines should be concise, most of
+ the logic should be in cdrom.c */
+
+
+/* controls tray movement */
+static int cm206_tray_move(struct cdrom_device_info *cdi, int position)
+{
+ if (position) { /* 1: eject */
+ type_0_command(c_open_tray, 1);
+ invalidate_toc();
+ } else
+ type_0_command(c_close_tray, 1); /* 0: close */
+ return 0;
+}
+
+/* gives current state of the drive */
+static int cm206_drive_status(struct cdrom_device_info *cdi, int slot_nr)
+{
+ get_drive_status();
+ if (cd->dsb & dsb_tray_not_closed)
+ return CDS_TRAY_OPEN;
+ if (!(cd->dsb & dsb_disc_present))
+ return CDS_NO_DISC;
+ if (cd->dsb & dsb_drive_not_ready)
+ return CDS_DRIVE_NOT_READY;
+ return CDS_DISC_OK;
+}
+
+/* locks or unlocks door lock==1: lock; return 0 upon success */
+static int cm206_lock_door(struct cdrom_device_info *cdi, int lock)
+{
+ uch command = (lock) ? c_lock_tray : c_unlock_tray;
+ type_0_command(command, 1); /* wait and get dsb */
+ /* the logic calculates the success, 0 means successful */
+ return lock ^ ((cd->dsb & dsb_tray_locked) != 0);
+}
+
+/* Although a session start should be in LBA format, we return it in
+ MSF format because it is slightly easier, and the new generic ioctl
+ will take care of the necessary conversion. */
+static int cm206_get_last_session(struct cdrom_device_info *cdi,
+ struct cdrom_multisession *mssp)
+{
+ if (!FIRST_TRACK)
+ get_disc_status();
+ if (mssp != NULL) {
+ if (DISC_STATUS & cds_multi_session) { /* multi-session */
+ mssp->addr.msf.frame = cd->disc_status[3];
+ mssp->addr.msf.second = cd->disc_status[4];
+ mssp->addr.msf.minute = cd->disc_status[5];
+ mssp->addr_format = CDROM_MSF;
+ mssp->xa_flag = 1;
+ } else {
+ mssp->xa_flag = 0;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+static int cm206_get_upc(struct cdrom_device_info *cdi, struct cdrom_mcn *mcn)
+{
+ uch upc[10];
+ char *ret = mcn->medium_catalog_number;
+ int i;
+
+ if (type_1_command(c_read_upc, 10, upc))
+ return -EIO;
+ for (i = 0; i < 13; i++) {
+ int w = i / 2 + 1, r = i % 2;
+ if (r)
+ ret[i] = 0x30 | (upc[w] & 0x0f);
+ else
+ ret[i] = 0x30 | ((upc[w] >> 4) & 0x0f);
+ }
+ ret[13] = '\0';
+ return 0;
+}
+
+static int cm206_reset(struct cdrom_device_info *cdi)
+{
+ stop_read();
+ reset_cm260();
+ outw(dc_normal | dc_break | READ_AHEAD, r_data_control);
+ mdelay(1); /* 750 musec minimum */
+ outw(dc_normal | READ_AHEAD, r_data_control);
+ cd->sector_last = -1; /* flag no data buffered */
+ cd->adapter_last = -1;
+ invalidate_toc();
+ return 0;
+}
+
+static int cm206_select_speed(struct cdrom_device_info *cdi, int speed)
+{
+ int r;
+ switch (speed) {
+ case 0:
+ r = type_0_command(c_auto_mode, 1);
+ break;
+ case 1:
+ r = type_0_command(c_force_1x, 1);
+ break;
+ case 2:
+ r = type_0_command(c_force_2x, 1);
+ break;
+ default:
+ return -1;
+ }
+ if (r < 0)
+ return r;
+ else
+ return 1;
+}
+
+static struct cdrom_device_ops cm206_dops = {
+ .open = cm206_open,
+ .release = cm206_release,
+ .drive_status = cm206_drive_status,
+ .media_changed = cm206_media_changed,
+ .tray_move = cm206_tray_move,
+ .lock_door = cm206_lock_door,
+ .select_speed = cm206_select_speed,
+ .get_last_session = cm206_get_last_session,
+ .get_mcn = cm206_get_upc,
+ .reset = cm206_reset,
+ .audio_ioctl = cm206_audio_ioctl,
+ .capability = CDC_CLOSE_TRAY | CDC_OPEN_TRAY | CDC_LOCK |
+ CDC_MULTI_SESSION | CDC_MEDIA_CHANGED |
+ CDC_MCN | CDC_PLAY_AUDIO | CDC_SELECT_SPEED |
+ CDC_DRIVE_STATUS,
+ .n_minors = 1,
+};
+
+
+static struct cdrom_device_info cm206_info = {
+ .ops = &cm206_dops,
+ .speed = 2,
+ .capacity = 1,
+ .name = "cm206",
+};
+
+static int cm206_block_open(struct inode *inode, struct file *file)
+{
+ return cdrom_open(&cm206_info, inode, file);
+}
+
+static int cm206_block_release(struct inode *inode, struct file *file)
+{
+ return cdrom_release(&cm206_info, file);
+}
+
+static int cm206_block_ioctl(struct inode *inode, struct file *file,
+ unsigned cmd, unsigned long arg)
+{
+ switch (cmd) {
+#ifdef STATISTICS
+ case CM206CTL_GET_STAT:
+ if (arg >= NR_STATS)
+ return -EINVAL;
+ return cd->stats[arg];
+ case CM206CTL_GET_LAST_STAT:
+ if (arg >= NR_STATS)
+ return -EINVAL;
+ return cd->last_stat[arg];
+#endif
+ default:
+ break;
+ }
+
+ return cdrom_ioctl(file, &cm206_info, inode, cmd, arg);
+}
+
+static int cm206_block_media_changed(struct gendisk *disk)
+{
+ return cdrom_media_changed(&cm206_info);
+}
+
+static struct block_device_operations cm206_bdops =
+{
+ .owner = THIS_MODULE,
+ .open = cm206_block_open,
+ .release = cm206_block_release,
+ .ioctl = cm206_block_ioctl,
+ .media_changed = cm206_block_media_changed,
+};
+
+static struct gendisk *cm206_gendisk;
+
+/* This function probes for the adapter card. It returns the base
+ address if it has found the adapter card. One can specify a base
+ port to probe specifically, or 0 which means span all possible
+ bases.
+
+ Linus says it is too dangerous to use writes for probing, so we
+ stick with pure reads for a while. Hope that 8 possible ranges,
+ request_region, 15 bits of one port and 6 of another make things
+ likely enough to accept the region on the first hit...
+ */
+static int __init probe_base_port(int base)
+{
+ int b = 0x300, e = 0x370; /* this is the range of start addresses */
+ volatile int fool, i;
+
+ if (base)
+ b = e = base;
+ for (base = b; base <= e; base += 0x10) {
+ if (!request_region(base, 0x10,"cm206"))
+ continue;
+ for (i = 0; i < 3; i++)
+ fool = inw(base + 2); /* empty possibly uart_receive_buffer */
+ if ((inw(base + 6) & 0xffef) != 0x0001 || /* line_status */
+ (inw(base) & 0xad00) != 0) { /* data status */
+ release_region(base,0x10);
+ continue;
+ }
+ return (base);
+ }
+ return 0;
+}
+
+#if !defined(MODULE) || defined(AUTO_PROBE_MODULE)
+/* Probe for irq# nr. If nr==0, probe for all possible irq's. */
+static int __init probe_irq(int nr)
+{
+ int irqs, irq;
+ outw(dc_normal | READ_AHEAD, r_data_control); /* disable irq-generation */
+ sti();
+ irqs = probe_irq_on();
+ reset_cm260(); /* causes interrupt */
+ udelay(100); /* wait for it */
+ irq = probe_irq_off(irqs);
+ outw(dc_normal | READ_AHEAD, r_data_control); /* services interrupt */
+ if (nr && irq != nr && irq > 0)
+ return 0; /* wrong interrupt happened */
+ else
+ return irq;
+}
+#endif
+
+int __init cm206_init(void)
+{
+ uch e = 0;
+ long int size = sizeof(struct cm206_struct);
+ struct gendisk *disk;
+
+ printk(KERN_INFO "cm206 cdrom driver " REVISION);
+ cm206_base = probe_base_port(auto_probe ? 0 : cm206_base);
+ if (!cm206_base) {
+ printk(" can't find adapter!\n");
+ return -EIO;
+ }
+ printk(" adapter at 0x%x", cm206_base);
+ cd = kmalloc(size, GFP_KERNEL);
+ if (!cd)
+ goto out_base;
+ /* Now we have found the adaptor card, try to reset it. As we have
+ * found out earlier, this process generates an interrupt as well,
+ * so we might just exploit that fact for irq probing! */
+#if !defined(MODULE) || defined(AUTO_PROBE_MODULE)
+ cm206_irq = probe_irq(auto_probe ? 0 : cm206_irq);
+ if (cm206_irq <= 0) {
+ printk("can't find IRQ!\n");
+ goto out_probe;
+ } else
+ printk(" IRQ %d found\n", cm206_irq);
+#else
+ cli();
+ reset_cm260();
+ /* Now, the problem here is that reset_cm260 can generate an
+ interrupt. It seems that this can cause a kernel oops some time
+ later. So we wait a while and `service' this interrupt. */
+ mdelay(1);
+ outw(dc_normal | READ_AHEAD, r_data_control);
+ sti();
+ printk(" using IRQ %d\n", cm206_irq);
+#endif
+ if (send_receive_polled(c_drive_configuration) !=
+ c_drive_configuration) {
+ printk(KERN_INFO " drive not there\n");
+ goto out_probe;
+ }
+ e = send_receive_polled(c_gimme);
+ printk(KERN_INFO "Firmware revision %d", e & dcf_revision_code);
+ if (e & dcf_transfer_rate)
+ printk(" double");
+ else
+ printk(" single");
+ printk(" speed drive");
+ if (e & dcf_motorized_tray)
+ printk(", motorized tray");
+ if (request_irq(cm206_irq, cm206_interrupt, 0, "cm206", NULL)) {
+ printk("\nUnable to reserve IRQ---aborted\n");
+ goto out_probe;
+ }
+ printk(".\n");
+
+ if (register_blkdev(MAJOR_NR, "cm206"))
+ goto out_blkdev;
+
+ disk = alloc_disk(1);
+ if (!disk)
+ goto out_disk;
+ disk->major = MAJOR_NR;
+ disk->first_minor = 0;
+ sprintf(disk->disk_name, "cm206cd");
+ disk->fops = &cm206_bdops;
+ disk->flags = GENHD_FL_CD;
+ cm206_gendisk = disk;
+ if (register_cdrom(&cm206_info) != 0) {
+ printk(KERN_INFO "Cannot register for cdrom %d!\n", MAJOR_NR);
+ goto out_cdrom;
+ }
+ cm206_queue = blk_init_queue(do_cm206_request, &cm206_lock);
+ if (!cm206_queue)
+ goto out_queue;
+
+ blk_queue_hardsect_size(cm206_queue, 2048);
+ disk->queue = cm206_queue;
+ add_disk(disk);
+
+ memset(cd, 0, sizeof(*cd)); /* give'm some reasonable value */
+ cd->sector_last = -1; /* flag no data buffered */
+ cd->adapter_last = -1;
+ init_timer(&cd->timer);
+ cd->timer.function = cm206_timeout;
+ cd->max_sectors = (inw(r_data_status) & ds_ram_size) ? 24 : 97;
+ printk(KERN_INFO "%d kB adapter memory available, "
+ " %ld bytes kernel memory used.\n", cd->max_sectors * 2,
+ size);
+ return 0;
+
+out_queue:
+ unregister_cdrom(&cm206_info);
+out_cdrom:
+ put_disk(disk);
+out_disk:
+ unregister_blkdev(MAJOR_NR, "cm206");
+out_blkdev:
+ free_irq(cm206_irq, NULL);
+out_probe:
+ kfree(cd);
+out_base:
+ release_region(cm206_base, 16);
+ return -EIO;
+}
+
+#ifdef MODULE
+
+
+static void __init parse_options(void)
+{
+ int i;
+ for (i = 0; i < 2; i++) {
+ if (0x300 <= cm206[i] && i <= 0x370
+ && cm206[i] % 0x10 == 0) {
+ cm206_base = cm206[i];
+ auto_probe = 0;
+ } else if (3 <= cm206[i] && cm206[i] <= 15) {
+ cm206_irq = cm206[i];
+ auto_probe = 0;
+ }
+ }
+}
+
+static int __init __cm206_init(void)
+{
+ parse_options();
+#if !defined(AUTO_PROBE_MODULE)
+ auto_probe = 0;
+#endif
+ return cm206_init();
+}
+
+static void __exit cm206_exit(void)
+{
+ del_gendisk(cm206_gendisk);
+ put_disk(cm206_gendisk);
+ if (unregister_cdrom(&cm206_info)) {
+ printk("Can't unregister cdrom cm206\n");
+ return;
+ }
+ if (unregister_blkdev(MAJOR_NR, "cm206")) {
+ printk("Can't unregister major cm206\n");
+ return;
+ }
+ blk_cleanup_queue(cm206_queue);
+ free_irq(cm206_irq, NULL);
+ kfree(cd);
+ release_region(cm206_base, 16);
+ printk(KERN_INFO "cm206 removed\n");
+}
+
+module_init(__cm206_init);
+module_exit(cm206_exit);
+
+#else /* !MODULE */
+
+/* This setup function accepts either `auto' or numbers in the range
+ * 3--11 (for irq) or 0x300--0x370 (for base port) or both. */
+
+static int __init cm206_setup(char *s)
+{
+ int i, p[4];
+
+ (void) get_options(s, ARRAY_SIZE(p), p);
+
+ if (!strcmp(s, "auto"))
+ auto_probe = 1;
+ for (i = 1; i <= p[0]; i++) {
+ if (0x300 <= p[i] && i <= 0x370 && p[i] % 0x10 == 0) {
+ cm206_base = p[i];
+ auto_probe = 0;
+ } else if (3 <= p[i] && p[i] <= 15) {
+ cm206_irq = p[i];
+ auto_probe = 0;
+ }
+ }
+ return 1;
+}
+
+__setup("cm206=", cm206_setup);
+
+#endif /* !MODULE */
+MODULE_ALIAS_BLOCKDEV_MAJOR(CM206_CDROM_MAJOR);
+
diff --git a/trunk/drivers/cdrom/cm206.h b/trunk/drivers/cdrom/cm206.h
new file mode 100644
index 000000000000..0ae51c1a0dac
--- /dev/null
+++ b/trunk/drivers/cdrom/cm206.h
@@ -0,0 +1,171 @@
+/* cm206.h Header file for cm206.c.
+ Copyright (c) 1995 David van Leeuwen
+*/
+
+#ifndef LINUX_CM206_H
+#define LINUX_CM206_H
+
+#include
+
+/* First, the cm260 stuff */
+/* The ports and irq used. Although CM206_BASE and CM206_IRQ are defined
+ below, the values are not used unless autoprobing is turned off and
+ no LILO boot options or module command line options are given. Change
+ these values to your own as last resort if autoprobing and options
+ don't work. */
+
+#define CM206_BASE 0x340
+#define CM206_IRQ 11
+
+#define r_data_status (cm206_base)
+#define r_uart_receive (cm206_base+0x2)
+#define r_fifo_output_buffer (cm206_base+0x4)
+#define r_line_status (cm206_base+0x6)
+#define r_data_control (cm206_base+0x8)
+#define r_uart_transmit (cm206_base+0xa)
+#define r_test_clock (cm206_base+0xc)
+#define r_test_control (cm206_base+0xe)
+
+/* the data_status flags */
+#define ds_ram_size 0x4000
+#define ds_toc_ready 0x2000
+#define ds_fifo_empty 0x1000
+#define ds_sync_error 0x800
+#define ds_crc_error 0x400
+#define ds_data_error 0x200
+#define ds_fifo_overflow 0x100
+#define ds_data_ready 0x80
+
+/* the line_status flags */
+#define ls_attention 0x10
+#define ls_parity_error 0x8
+#define ls_overrun 0x4
+#define ls_receive_buffer_full 0x2
+#define ls_transmitter_buffer_empty 0x1
+
+/* the data control register flags */
+#define dc_read_q_channel 0x4000
+#define dc_mask_sync_error 0x2000
+#define dc_toc_enable 0x1000
+#define dc_no_stop_on_error 0x800
+#define dc_break 0x400
+#define dc_initialize 0x200
+#define dc_mask_transmit_ready 0x100
+#define dc_flag_enable 0x80
+
+/* Define the default data control register flags here */
+#define dc_normal (dc_mask_sync_error | dc_no_stop_on_error | \
+ dc_mask_transmit_ready)
+
+/* now some constants related to the cm206 */
+/* another drive status byte, echoed by the cm206 on most commands */
+
+#define dsb_error_condition 0x1
+#define dsb_play_in_progress 0x4
+#define dsb_possible_media_change 0x8
+#define dsb_disc_present 0x10
+#define dsb_drive_not_ready 0x20
+#define dsb_tray_locked 0x40
+#define dsb_tray_not_closed 0x80
+
+#define dsb_not_useful (dsb_drive_not_ready | dsb_tray_not_closed)
+
+/* the cm206 command set */
+
+#define c_close_tray 0
+#define c_lock_tray 0x01
+#define c_unlock_tray 0x04
+#define c_open_tray 0x05
+#define c_seek 0x10
+#define c_read_data 0x20
+#define c_force_1x 0x21
+#define c_force_2x 0x22
+#define c_auto_mode 0x23
+#define c_play 0x30
+#define c_set_audio_mode 0x31
+#define c_read_current_q 0x41
+#define c_stream_q 0x42
+#define c_drive_status 0x50
+#define c_disc_status 0x51
+#define c_audio_status 0x52
+#define c_drive_configuration 0x53
+#define c_read_upc 0x60
+#define c_stop 0x70
+#define c_calc_checksum 0xe5
+
+#define c_gimme 0xf8
+
+/* finally, the (error) condition that the drive can be in *
+ * OK, this is not always an error, but let's prefix it with e_ */
+
+#define e_none 0
+#define e_illegal_command 0x01
+#define e_sync 0x02
+#define e_seek 0x03
+#define e_parity 0x04
+#define e_focus 0x05
+#define e_header_sync 0x06
+#define e_code_incompatibility 0x07
+#define e_reset_done 0x08
+#define e_bad_parameter 0x09
+#define e_radial 0x0a
+#define e_sub_code 0x0b
+#define e_no_data_track 0x0c
+#define e_scan 0x0d
+#define e_tray_open 0x0f
+#define e_no_disc 0x10
+#define e_tray stalled 0x11
+
+/* drive configuration masks */
+
+#define dcf_revision_code 0x7
+#define dcf_transfer_rate 0x60
+#define dcf_motorized_tray 0x80
+
+/* disc status byte */
+
+#define cds_multi_session 0x2
+#define cds_all_audio 0x8
+#define cds_xa_mode 0xf0
+
+/* finally some ioctls for the driver */
+
+#define CM206CTL_GET_STAT _IO( 0x20, 0 )
+#define CM206CTL_GET_LAST_STAT _IO( 0x20, 1 )
+
+#ifdef STATISTICS
+
+/* This is an ugly way to guarantee that the names of the statistics
+ * are the same in the code and in the diagnostics program. */
+
+#ifdef __KERNEL__
+#define x(a) st_ ## a
+#define y enum
+#else
+#define x(a) #a
+#define y char * stats_name[] =
+#endif
+
+y {x(interrupt), x(data_ready), x(fifo_overflow), x(data_error),
+ x(crc_error), x(sync_error), x(lost_intr), x(echo),
+ x(write_timeout), x(receive_timeout), x(read_timeout),
+ x(dsb_timeout), x(stop_0xff), x(back_read_timeout),
+ x(sector_transferred), x(read_restarted), x(read_background),
+ x(bh), x(open), x(ioctl_multisession), x(attention)
+#ifdef __KERNEL__
+ , x(last_entry)
+#endif
+ };
+
+#ifdef __KERNEL__
+#define NR_STATS st_last_entry
+#else
+#define NR_STATS (sizeof(stats_name)/sizeof(char*))
+#endif
+
+#undef y
+#undef x
+
+#endif /* STATISTICS */
+
+#endif /* LINUX_CM206_H */
diff --git a/trunk/drivers/cdrom/gscd.c b/trunk/drivers/cdrom/gscd.c
new file mode 100644
index 000000000000..b3ab6e9b8df1
--- /dev/null
+++ b/trunk/drivers/cdrom/gscd.c
@@ -0,0 +1,1029 @@
+#define GSCD_VERSION "0.4a Oliver Raupach "
+
+/*
+ linux/drivers/block/gscd.c - GoldStar R420 CDROM driver
+
+ Copyright (C) 1995 Oliver Raupach
+ based upon pre-works by Eberhard Moenkeberg
+
+
+ For all kind of other information about the GoldStar CDROM
+ and this Linux device driver I installed a WWW-URL:
+ http://linux.rz.fh-hannover.de/~raupach
+
+
+ If you are the editor of a Linux CD, you should
+ enable gscd.c within your boot floppy kernel and
+ send me one of your CDs for free.
+
+
+ --------------------------------------------------------------------
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ --------------------------------------------------------------------
+
+ 9 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
+
+*/
+
+/* These settings are for various debug-level. Leave they untouched ... */
+#define NO_GSCD_DEBUG
+#define NO_IOCTL_DEBUG
+#define NO_MODULE_DEBUG
+#define NO_FUTURE_WORK
+/*------------------------*/
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#define MAJOR_NR GOLDSTAR_CDROM_MAJOR
+#include
+#include "gscd.h"
+
+static int gscdPresent = 0;
+
+static unsigned char gscd_buf[2048]; /* buffer for block size conversion */
+static int gscd_bn = -1;
+static short gscd_port = GSCD_BASE_ADDR;
+module_param_named(gscd, gscd_port, short, 0);
+
+/* Kommt spaeter vielleicht noch mal dran ...
+ * static DECLARE_WAIT_QUEUE_HEAD(gscd_waitq);
+ */
+
+static void gscd_read_cmd(struct request *req);
+static void gscd_hsg2msf(long hsg, struct msf *msf);
+static void gscd_bin2bcd(unsigned char *p);
+
+/* Schnittstellen zum Kern/FS */
+
+static void __do_gscd_request(unsigned long dummy);
+static int gscd_ioctl(struct inode *, struct file *, unsigned int,
+ unsigned long);
+static int gscd_open(struct inode *, struct file *);
+static int gscd_release(struct inode *, struct file *);
+static int check_gscd_med_chg(struct gendisk *disk);
+
+/* GoldStar Funktionen */
+
+static void cmd_out(int, char *, char *, int);
+static void cmd_status(void);
+static void init_cd_drive(int);
+
+static int get_status(void);
+static void clear_Audio(void);
+static void cc_invalidate(void);
+
+/* some things for the next version */
+#ifdef FUTURE_WORK
+static void update_state(void);
+static long gscd_msf2hsg(struct msf *mp);
+static int gscd_bcd2bin(unsigned char bcd);
+#endif
+
+
+/* lo-level cmd-Funktionen */
+
+static void cmd_info_in(char *, int);
+static void cmd_end(void);
+static void cmd_read_b(char *, int, int);
+static void cmd_read_w(char *, int, int);
+static int cmd_unit_alive(void);
+static void cmd_write_cmd(char *);
+
+
+/* GoldStar Variablen */
+
+static int curr_drv_state;
+static int drv_states[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+static int drv_mode;
+static int disk_state;
+static int speed;
+static int ndrives;
+
+static unsigned char drv_num_read;
+static unsigned char f_dsk_valid;
+static unsigned char current_drive;
+static unsigned char f_drv_ok;
+
+
+static char f_AudioPlay;
+static char f_AudioPause;
+static int AudioStart_m;
+static int AudioStart_f;
+static int AudioEnd_m;
+static int AudioEnd_f;
+
+static DEFINE_TIMER(gscd_timer, NULL, 0, 0);
+static DEFINE_SPINLOCK(gscd_lock);
+static struct request_queue *gscd_queue;
+
+static struct block_device_operations gscd_fops = {
+ .owner = THIS_MODULE,
+ .open = gscd_open,
+ .release = gscd_release,
+ .ioctl = gscd_ioctl,
+ .media_changed = check_gscd_med_chg,
+};
+
+/*
+ * Checking if the media has been changed
+ * (not yet implemented)
+ */
+static int check_gscd_med_chg(struct gendisk *disk)
+{
+#ifdef GSCD_DEBUG
+ printk("gscd: check_med_change\n");
+#endif
+ return 0;
+}
+
+
+#ifndef MODULE
+/* Using new interface for kernel-parameters */
+
+static int __init gscd_setup(char *str)
+{
+ int ints[2];
+ (void) get_options(str, ARRAY_SIZE(ints), ints);
+
+ if (ints[0] > 0) {
+ gscd_port = ints[1];
+ }
+ return 1;
+}
+
+__setup("gscd=", gscd_setup);
+
+#endif
+
+static int gscd_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
+ unsigned long arg)
+{
+ unsigned char to_do[10];
+ unsigned char dummy;
+
+
+ switch (cmd) {
+ case CDROMSTART: /* Spin up the drive */
+ /* Don't think we can do this. Even if we could,
+ * I think the drive times out and stops after a while
+ * anyway. For now, ignore it.
+ */
+ return 0;
+
+ case CDROMRESUME: /* keine Ahnung was das ist */
+ return 0;
+
+
+ case CDROMEJECT:
+ cmd_status();
+ to_do[0] = CMD_TRAY_CTL;
+ cmd_out(TYPE_INFO, (char *) &to_do, (char *) &dummy, 0);
+
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+
+}
+
+
+/*
+ * Take care of the different block sizes between cdrom and Linux.
+ * When Linux gets variable block sizes this will probably go away.
+ */
+
+static void gscd_transfer(struct request *req)
+{
+ while (req->nr_sectors > 0 && gscd_bn == req->sector / 4) {
+ long offs = (req->sector & 3) * 512;
+ memcpy(req->buffer, gscd_buf + offs, 512);
+ req->nr_sectors--;
+ req->sector++;
+ req->buffer += 512;
+ }
+}
+
+
+/*
+ * I/O request routine called from Linux kernel.
+ */
+
+static void do_gscd_request(request_queue_t * q)
+{
+ __do_gscd_request(0);
+}
+
+static void __do_gscd_request(unsigned long dummy)
+{
+ struct request *req;
+ unsigned int block;
+ unsigned int nsect;
+
+repeat:
+ req = elv_next_request(gscd_queue);
+ if (!req)
+ return;
+
+ block = req->sector;
+ nsect = req->nr_sectors;
+
+ if (req->sector == -1)
+ goto out;
+
+ if (req->cmd != READ) {
+ printk("GSCD: bad cmd %u\n", rq_data_dir(req));
+ end_request(req, 0);
+ goto repeat;
+ }
+
+ gscd_transfer(req);
+
+ /* if we satisfied the request from the buffer, we're done. */
+
+ if (req->nr_sectors == 0) {
+ end_request(req, 1);
+ goto repeat;
+ }
+#ifdef GSCD_DEBUG
+ printk("GSCD: block %d, nsect %d\n", block, nsect);
+#endif
+ gscd_read_cmd(req);
+out:
+ return;
+}
+
+
+
+/*
+ * Check the result of the set-mode command. On success, send the
+ * read-data command.
+ */
+
+static void gscd_read_cmd(struct request *req)
+{
+ long block;
+ struct gscd_Play_msf gscdcmd;
+ char cmd[] = { CMD_READ, 0x80, 0, 0, 0, 0, 1 }; /* cmd mode M-S-F secth sectl */
+
+ cmd_status();
+ if (disk_state & (ST_NO_DISK | ST_DOOR_OPEN)) {
+ printk("GSCD: no disk or door open\n");
+ end_request(req, 0);
+ } else {
+ if (disk_state & ST_INVALID) {
+ printk("GSCD: disk invalid\n");
+ end_request(req, 0);
+ } else {
+ gscd_bn = -1; /* purge our buffer */
+ block = req->sector / 4;
+ gscd_hsg2msf(block, &gscdcmd.start); /* cvt to msf format */
+
+ cmd[2] = gscdcmd.start.min;
+ cmd[3] = gscdcmd.start.sec;
+ cmd[4] = gscdcmd.start.frame;
+
+#ifdef GSCD_DEBUG
+ printk("GSCD: read msf %d:%d:%d\n", cmd[2], cmd[3],
+ cmd[4]);
+#endif
+ cmd_out(TYPE_DATA, (char *) &cmd,
+ (char *) &gscd_buf[0], 1);
+
+ gscd_bn = req->sector / 4;
+ gscd_transfer(req);
+ end_request(req, 1);
+ }
+ }
+ SET_TIMER(__do_gscd_request, 1);
+}
+
+
+/*
+ * Open the device special file. Check that a disk is in.
+ */
+
+static int gscd_open(struct inode *ip, struct file *fp)
+{
+ int st;
+
+#ifdef GSCD_DEBUG
+ printk("GSCD: open\n");
+#endif
+
+ if (gscdPresent == 0)
+ return -ENXIO; /* no hardware */
+
+ get_status();
+ st = disk_state & (ST_NO_DISK | ST_DOOR_OPEN);
+ if (st) {
+ printk("GSCD: no disk or door open\n");
+ return -ENXIO;
+ }
+
+/* if (updateToc() < 0)
+ return -EIO;
+*/
+
+ return 0;
+}
+
+
+/*
+ * On close, we flush all gscd blocks from the buffer cache.
+ */
+
+static int gscd_release(struct inode *inode, struct file *file)
+{
+
+#ifdef GSCD_DEBUG
+ printk("GSCD: release\n");
+#endif
+
+ gscd_bn = -1;
+
+ return 0;
+}
+
+
+static int get_status(void)
+{
+ int status;
+
+ cmd_status();
+ status = disk_state & (ST_x08 | ST_x04 | ST_INVALID | ST_x01);
+
+ if (status == (ST_x08 | ST_x04 | ST_INVALID | ST_x01)) {
+ cc_invalidate();
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+
+static void cc_invalidate(void)
+{
+ drv_num_read = 0xFF;
+ f_dsk_valid = 0xFF;
+ current_drive = 0xFF;
+ f_drv_ok = 0xFF;
+
+ clear_Audio();
+
+}
+
+static void clear_Audio(void)
+{
+
+ f_AudioPlay = 0;
+ f_AudioPause = 0;
+ AudioStart_m = 0;
+ AudioStart_f = 0;
+ AudioEnd_m = 0;
+ AudioEnd_f = 0;
+
+}
+
+/*
+ * waiting ?
+ */
+
+static int wait_drv_ready(void)
+{
+ int found, read;
+
+ do {
+ found = inb(GSCDPORT(0));
+ found &= 0x0f;
+ read = inb(GSCDPORT(0));
+ read &= 0x0f;
+ } while (read != found);
+
+#ifdef GSCD_DEBUG
+ printk("Wait for: %d\n", read);
+#endif
+
+ return read;
+}
+
+static void cc_Ident(char *respons)
+{
+ char to_do[] = { CMD_IDENT, 0, 0 };
+
+ cmd_out(TYPE_INFO, (char *) &to_do, (char *) respons, (int) 0x1E);
+
+}
+
+static void cc_SetSpeed(void)
+{
+ char to_do[] = { CMD_SETSPEED, 0, 0 };
+ char dummy;
+
+ if (speed > 0) {
+ to_do[1] = speed & 0x0F;
+ cmd_out(TYPE_INFO, (char *) &to_do, (char *) &dummy, 0);
+ }
+}
+
+static void cc_Reset(void)
+{
+ char to_do[] = { CMD_RESET, 0 };
+ char dummy;
+
+ cmd_out(TYPE_INFO, (char *) &to_do, (char *) &dummy, 0);
+}
+
+static void cmd_status(void)
+{
+ char to_do[] = { CMD_STATUS, 0 };
+ char dummy;
+
+ cmd_out(TYPE_INFO, (char *) &to_do, (char *) &dummy, 0);
+
+#ifdef GSCD_DEBUG
+ printk("GSCD: Status: %d\n", disk_state);
+#endif
+
+}
+
+static void cmd_out(int cmd_type, char *cmd, char *respo_buf, int respo_count)
+{
+ int result;
+
+
+ result = wait_drv_ready();
+ if (result != drv_mode) {
+ unsigned long test_loops = 0xFFFF;
+ int i, dummy;
+
+ outb(curr_drv_state, GSCDPORT(0));
+
+ /* LOCLOOP_170 */
+ do {
+ result = wait_drv_ready();
+ test_loops--;
+ } while ((result != drv_mode) && (test_loops > 0));
+
+ if (result != drv_mode) {
+ disk_state = ST_x08 | ST_x04 | ST_INVALID;
+ return;
+ }
+
+ /* ...and waiting */
+ for (i = 1, dummy = 1; i < 0xFFFF; i++) {
+ dummy *= i;
+ }
+ }
+
+ /* LOC_172 */
+ /* check the unit */
+ /* and wake it up */
+ if (cmd_unit_alive() != 0x08) {
+ /* LOC_174 */
+ /* game over for this unit */
+ disk_state = ST_x08 | ST_x04 | ST_INVALID;
+ return;
+ }
+
+ /* LOC_176 */
+#ifdef GSCD_DEBUG
+ printk("LOC_176 ");
+#endif
+ if (drv_mode == 0x09) {
+ /* magic... */
+ printk("GSCD: magic ...\n");
+ outb(result, GSCDPORT(2));
+ }
+
+ /* write the command to the drive */
+ cmd_write_cmd(cmd);
+
+ /* LOC_178 */
+ for (;;) {
+ result = wait_drv_ready();
+ if (result != drv_mode) {
+ /* LOC_179 */
+ if (result == 0x04) { /* Mode 4 */
+ /* LOC_205 */
+#ifdef GSCD_DEBUG
+ printk("LOC_205 ");
+#endif
+ disk_state = inb(GSCDPORT(2));
+
+ do {
+ result = wait_drv_ready();
+ } while (result != drv_mode);
+ return;
+
+ } else {
+ if (result == 0x06) { /* Mode 6 */
+ /* LOC_181 */
+#ifdef GSCD_DEBUG
+ printk("LOC_181 ");
+#endif
+
+ if (cmd_type == TYPE_DATA) {
+ /* read data */
+ /* LOC_184 */
+ if (drv_mode == 9) {
+ /* read the data to the buffer (word) */
+
+ /* (*(cmd+1))?(CD_FRAMESIZE/2):(CD_FRAMESIZE_RAW/2) */
+ cmd_read_w
+ (respo_buf,
+ respo_count,
+ CD_FRAMESIZE /
+ 2);
+ return;
+ } else {
+ /* read the data to the buffer (byte) */
+
+ /* (*(cmd+1))?(CD_FRAMESIZE):(CD_FRAMESIZE_RAW) */
+ cmd_read_b
+ (respo_buf,
+ respo_count,
+ CD_FRAMESIZE);
+ return;
+ }
+ } else {
+ /* read the info to the buffer */
+ cmd_info_in(respo_buf,
+ respo_count);
+ return;
+ }
+
+ return;
+ }
+ }
+
+ } else {
+ disk_state = ST_x08 | ST_x04 | ST_INVALID;
+ return;
+ }
+ } /* for (;;) */
+
+
+#ifdef GSCD_DEBUG
+ printk("\n");
+#endif
+}
+
+
+static void cmd_write_cmd(char *pstr)
+{
+ int i, j;
+
+ /* LOC_177 */
+#ifdef GSCD_DEBUG
+ printk("LOC_177 ");
+#endif
+
+ /* calculate the number of parameter */
+ j = *pstr & 0x0F;
+
+ /* shift it out */
+ for (i = 0; i < j; i++) {
+ outb(*pstr, GSCDPORT(2));
+ pstr++;
+ }
+}
+
+
+static int cmd_unit_alive(void)
+{
+ int result;
+ unsigned long max_test_loops;
+
+
+ /* LOC_172 */
+#ifdef GSCD_DEBUG
+ printk("LOC_172 ");
+#endif
+
+ outb(curr_drv_state, GSCDPORT(0));
+ max_test_loops = 0xFFFF;
+
+ do {
+ result = wait_drv_ready();
+ max_test_loops--;
+ } while ((result != 0x08) && (max_test_loops > 0));
+
+ return result;
+}
+
+
+static void cmd_info_in(char *pb, int count)
+{
+ int result;
+ char read;
+
+
+ /* read info */
+ /* LOC_182 */
+#ifdef GSCD_DEBUG
+ printk("LOC_182 ");
+#endif
+
+ do {
+ read = inb(GSCDPORT(2));
+ if (count > 0) {
+ *pb = read;
+ pb++;
+ count--;
+ }
+
+ /* LOC_183 */
+ do {
+ result = wait_drv_ready();
+ } while (result == 0x0E);
+ } while (result == 6);
+
+ cmd_end();
+ return;
+}
+
+
+static void cmd_read_b(char *pb, int count, int size)
+{
+ int result;
+ int i;
+
+
+ /* LOC_188 */
+ /* LOC_189 */
+#ifdef GSCD_DEBUG
+ printk("LOC_189 ");
+#endif
+
+ do {
+ do {
+ result = wait_drv_ready();
+ } while (result != 6 || result == 0x0E);
+
+ if (result != 6) {
+ cmd_end();
+ return;
+ }
+#ifdef GSCD_DEBUG
+ printk("LOC_191 ");
+#endif
+
+ for (i = 0; i < size; i++) {
+ *pb = inb(GSCDPORT(2));
+ pb++;
+ }
+ count--;
+ } while (count > 0);
+
+ cmd_end();
+ return;
+}
+
+
+static void cmd_end(void)
+{
+ int result;
+
+
+ /* LOC_204 */
+#ifdef GSCD_DEBUG
+ printk("LOC_204 ");
+#endif
+
+ do {
+ result = wait_drv_ready();
+ if (result == drv_mode) {
+ return;
+ }
+ } while (result != 4);
+
+ /* LOC_205 */
+#ifdef GSCD_DEBUG
+ printk("LOC_205 ");
+#endif
+
+ disk_state = inb(GSCDPORT(2));
+
+ do {
+ result = wait_drv_ready();
+ } while (result != drv_mode);
+ return;
+
+}
+
+
+static void cmd_read_w(char *pb, int count, int size)
+{
+ int result;
+ int i;
+
+
+#ifdef GSCD_DEBUG
+ printk("LOC_185 ");
+#endif
+
+ do {
+ /* LOC_185 */
+ do {
+ result = wait_drv_ready();
+ } while (result != 6 || result == 0x0E);
+
+ if (result != 6) {
+ cmd_end();
+ return;
+ }
+
+ for (i = 0; i < size; i++) {
+ /* na, hier muss ich noch mal drueber nachdenken */
+ *pb = inw(GSCDPORT(2));
+ pb++;
+ }
+ count--;
+ } while (count > 0);
+
+ cmd_end();
+ return;
+}
+
+static int __init find_drives(void)
+{
+ int *pdrv;
+ int drvnum;
+ int subdrv;
+ int i;
+
+ speed = 0;
+ pdrv = (int *) &drv_states;
+ curr_drv_state = 0xFE;
+ subdrv = 0;
+ drvnum = 0;
+
+ for (i = 0; i < 8; i++) {
+ subdrv++;
+ cmd_status();
+ disk_state &= ST_x08 | ST_x04 | ST_INVALID | ST_x01;
+ if (disk_state != (ST_x08 | ST_x04 | ST_INVALID)) {
+ /* LOC_240 */
+ *pdrv = curr_drv_state;
+ init_cd_drive(drvnum);
+ pdrv++;
+ drvnum++;
+ } else {
+ if (subdrv < 2) {
+ continue;
+ } else {
+ subdrv = 0;
+ }
+ }
+
+/* curr_drv_state<<1; <-- das geht irgendwie nicht */
+/* muss heissen: curr_drv_state <<= 1; (ist ja Wert-Zuweisung) */
+ curr_drv_state *= 2;
+ curr_drv_state |= 1;
+#ifdef GSCD_DEBUG
+ printk("DriveState: %d\n", curr_drv_state);
+#endif
+ }
+
+ ndrives = drvnum;
+ return drvnum;
+}
+
+static void __init init_cd_drive(int num)
+{
+ char resp[50];
+ int i;
+
+ printk("GSCD: init unit %d\n", num);
+ cc_Ident((char *) &resp);
+
+ printk("GSCD: identification: ");
+ for (i = 0; i < 0x1E; i++) {
+ printk("%c", resp[i]);
+ }
+ printk("\n");
+
+ cc_SetSpeed();
+
+}
+
+#ifdef FUTURE_WORK
+/* return_done */
+static void update_state(void)
+{
+ unsigned int AX;
+
+
+ if ((disk_state & (ST_x08 | ST_x04 | ST_INVALID | ST_x01)) == 0) {
+ if (disk_state == (ST_x08 | ST_x04 | ST_INVALID)) {
+ AX = ST_INVALID;
+ }
+
+ if ((disk_state & (ST_x08 | ST_x04 | ST_INVALID | ST_x01))
+ == 0) {
+ invalidate();
+ f_drv_ok = 0;
+ }
+
+ AX |= 0x8000;
+ }
+
+ if (disk_state & ST_PLAYING) {
+ AX |= 0x200;
+ }
+
+ AX |= 0x100;
+ /* pkt_esbx = AX; */
+
+ disk_state = 0;
+
+}
+#endif
+
+static struct gendisk *gscd_disk;
+
+static void __exit gscd_exit(void)
+{
+ CLEAR_TIMER;
+
+ del_gendisk(gscd_disk);
+ put_disk(gscd_disk);
+ if ((unregister_blkdev(MAJOR_NR, "gscd") == -EINVAL)) {
+ printk("What's that: can't unregister GoldStar-module\n");
+ return;
+ }
+ blk_cleanup_queue(gscd_queue);
+ release_region(gscd_port, GSCD_IO_EXTENT);
+ printk(KERN_INFO "GoldStar-module released.\n");
+}
+
+/* This is the common initialisation for the GoldStar drive. */
+/* It is called at boot time AND for module init. */
+static int __init gscd_init(void)
+{
+ int i;
+ int result;
+ int ret=0;
+
+ printk(KERN_INFO "GSCD: version %s\n", GSCD_VERSION);
+ printk(KERN_INFO
+ "GSCD: Trying to detect a Goldstar R420 CD-ROM drive at 0x%X.\n",
+ gscd_port);
+
+ if (!request_region(gscd_port, GSCD_IO_EXTENT, "gscd")) {
+ printk(KERN_WARNING "GSCD: Init failed, I/O port (%X) already"
+ " in use.\n", gscd_port);
+ return -EIO;
+ }
+
+
+ /* check for card */
+ result = wait_drv_ready();
+ if (result == 0x09) {
+ printk(KERN_WARNING "GSCD: DMA kann ich noch nicht!\n");
+ ret = -EIO;
+ goto err_out1;
+ }
+
+ if (result == 0x0b) {
+ drv_mode = result;
+ i = find_drives();
+ if (i == 0) {
+ printk(KERN_WARNING "GSCD: GoldStar CD-ROM Drive is"
+ " not found.\n");
+ ret = -EIO;
+ goto err_out1;
+ }
+ }
+
+ if ((result != 0x0b) && (result != 0x09)) {
+ printk(KERN_WARNING "GSCD: GoldStar Interface Adapter does not "
+ "exist or H/W error\n");
+ ret = -EIO;
+ goto err_out1;
+ }
+
+ /* reset all drives */
+ i = 0;
+ while (drv_states[i] != 0) {
+ curr_drv_state = drv_states[i];
+ printk(KERN_INFO "GSCD: Reset unit %d ... ", i);
+ cc_Reset();
+ printk("done\n");
+ i++;
+ }
+
+ gscd_disk = alloc_disk(1);
+ if (!gscd_disk)
+ goto err_out1;
+ gscd_disk->major = MAJOR_NR;
+ gscd_disk->first_minor = 0;
+ gscd_disk->fops = &gscd_fops;
+ sprintf(gscd_disk->disk_name, "gscd");
+
+ if (register_blkdev(MAJOR_NR, "gscd")) {
+ ret = -EIO;
+ goto err_out2;
+ }
+
+ gscd_queue = blk_init_queue(do_gscd_request, &gscd_lock);
+ if (!gscd_queue) {
+ ret = -ENOMEM;
+ goto err_out3;
+ }
+
+ disk_state = 0;
+ gscdPresent = 1;
+
+ gscd_disk->queue = gscd_queue;
+ add_disk(gscd_disk);
+
+ printk(KERN_INFO "GSCD: GoldStar CD-ROM Drive found.\n");
+ return 0;
+
+err_out3:
+ unregister_blkdev(MAJOR_NR, "gscd");
+err_out2:
+ put_disk(gscd_disk);
+err_out1:
+ release_region(gscd_port, GSCD_IO_EXTENT);
+ return ret;
+}
+
+static void gscd_hsg2msf(long hsg, struct msf *msf)
+{
+ hsg += CD_MSF_OFFSET;
+ msf->min = hsg / (CD_FRAMES * CD_SECS);
+ hsg %= CD_FRAMES * CD_SECS;
+ msf->sec = hsg / CD_FRAMES;
+ msf->frame = hsg % CD_FRAMES;
+
+ gscd_bin2bcd(&msf->min); /* convert to BCD */
+ gscd_bin2bcd(&msf->sec);
+ gscd_bin2bcd(&msf->frame);
+}
+
+
+static void gscd_bin2bcd(unsigned char *p)
+{
+ int u, t;
+
+ u = *p % 10;
+ t = *p / 10;
+ *p = u | (t << 4);
+}
+
+
+#ifdef FUTURE_WORK
+static long gscd_msf2hsg(struct msf *mp)
+{
+ return gscd_bcd2bin(mp->frame)
+ + gscd_bcd2bin(mp->sec) * CD_FRAMES
+ + gscd_bcd2bin(mp->min) * CD_FRAMES * CD_SECS - CD_MSF_OFFSET;
+}
+
+static int gscd_bcd2bin(unsigned char bcd)
+{
+ return (bcd >> 4) * 10 + (bcd & 0xF);
+}
+#endif
+
+MODULE_AUTHOR("Oliver Raupach ");
+MODULE_LICENSE("GPL");
+module_init(gscd_init);
+module_exit(gscd_exit);
+MODULE_ALIAS_BLOCKDEV_MAJOR(GOLDSTAR_CDROM_MAJOR);
diff --git a/trunk/drivers/cdrom/gscd.h b/trunk/drivers/cdrom/gscd.h
new file mode 100644
index 000000000000..a41e64bfc061
--- /dev/null
+++ b/trunk/drivers/cdrom/gscd.h
@@ -0,0 +1,108 @@
+/*
+ * Definitions for a GoldStar R420 CD-ROM interface
+ *
+ * Copyright (C) 1995 Oliver Raupach
+ * Eberhard Moenkeberg
+ *
+ * Published under the GPL.
+ *
+ */
+
+
+/* The Interface Card default address is 0x340. This will work for most
+ applications. Address selection is accomplished by jumpers PN801-1 to
+ PN801-4 on the GoldStar Interface Card.
+ Appropriate settings are: 0x300, 0x310, 0x320, 0x330, 0x340, 0x350, 0x360
+ 0x370, 0x380, 0x390, 0x3A0, 0x3B0, 0x3C0, 0x3D0, 0x3E0, 0x3F0 */
+
+/* insert here the I/O port address and extent */
+#define GSCD_BASE_ADDR 0x340
+#define GSCD_IO_EXTENT 4
+
+
+/************** nothing to set up below here *********************/
+
+/* port access macro */
+#define GSCDPORT(x) (gscd_port + (x))
+
+/*
+ * commands
+ * the lower nibble holds the command length
+ */
+#define CMD_STATUS 0x01
+#define CMD_READSUBQ 0x02 /* 1: ?, 2: UPC, 5: ? */
+#define CMD_SEEK 0x05 /* read_mode M-S-F */
+#define CMD_READ 0x07 /* read_mode M-S-F nsec_h nsec_l */
+#define CMD_RESET 0x11
+#define CMD_SETMODE 0x15
+#define CMD_PLAY 0x17 /* M-S-F M-S-F */
+#define CMD_LOCK_CTL 0x22 /* 0: unlock, 1: lock */
+#define CMD_IDENT 0x31
+#define CMD_SETSPEED 0x32 /* 0: auto */ /* ??? */
+#define CMD_GETMODE 0x41
+#define CMD_PAUSE 0x51
+#define CMD_READTOC 0x61
+#define CMD_DISKINFO 0x71
+#define CMD_TRAY_CTL 0x81
+
+/*
+ * disk_state:
+ */
+#define ST_PLAYING 0x80
+#define ST_UNLOCKED 0x40
+#define ST_NO_DISK 0x20
+#define ST_DOOR_OPEN 0x10
+#define ST_x08 0x08
+#define ST_x04 0x04
+#define ST_INVALID 0x02
+#define ST_x01 0x01
+
+/*
+ * cmd_type:
+ */
+#define TYPE_INFO 0x01
+#define TYPE_DATA 0x02
+
+/*
+ * read_mode:
+ */
+#define MOD_POLLED 0x80
+#define MOD_x08 0x08
+#define MOD_RAW 0x04
+
+#define READ_DATA(port, buf, nr) insb(port, buf, nr)
+
+#define SET_TIMER(func, jifs) \
+ ((mod_timer(&gscd_timer, jiffies + jifs)), \
+ (gscd_timer.function = func))
+
+#define CLEAR_TIMER del_timer_sync(&gscd_timer)
+
+#define MAX_TRACKS 104
+
+struct msf {
+ unsigned char min;
+ unsigned char sec;
+ unsigned char frame;
+};
+
+struct gscd_Play_msf {
+ struct msf start;
+ struct msf end;
+};
+
+struct gscd_DiskInfo {
+ unsigned char first;
+ unsigned char last;
+ struct msf diskLength;
+ struct msf firstTrack;
+};
+
+struct gscd_Toc {
+ unsigned char ctrl_addr;
+ unsigned char track;
+ unsigned char pointIndex;
+ struct msf trackTime;
+ struct msf diskTime;
+};
+
diff --git a/trunk/drivers/cdrom/isp16.c b/trunk/drivers/cdrom/isp16.c
new file mode 100644
index 000000000000..db0fd9a240e3
--- /dev/null
+++ b/trunk/drivers/cdrom/isp16.c
@@ -0,0 +1,374 @@
+/* -- ISP16 cdrom detection and configuration
+ *
+ * Copyright (c) 1995,1996 Eric van der Maarel
+ *
+ * Version 0.6
+ *
+ * History:
+ * 0.5 First release.
+ * Was included in the sjcd and optcd cdrom drivers.
+ * 0.6 First "stand-alone" version.
+ * Removed sound configuration.
+ * Added "module" support.
+ *
+ * 9 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
+ *
+ * 19 June 2004 -- check_region() converted to request_region()
+ * and return statement cleanups.
+ * - Jesper Juhl
+ *
+ * Detect cdrom interface on ISP16 sound card.
+ * Configure cdrom interface.
+ *
+ * Algorithm for the card with OPTi 82C928 taken
+ * from the CDSETUP.SYS driver for MSDOS,
+ * by OPTi Computers, version 2.03.
+ * Algorithm for the card with OPTi 82C929 as communicated
+ * to me by Vadim Model and Leo Spiekman.
+ *
+ * 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.
+ *
+ */
+
+#define ISP16_VERSION_MAJOR 0
+#define ISP16_VERSION_MINOR 6
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include "isp16.h"
+
+static short isp16_detect(void);
+static short isp16_c928__detect(void);
+static short isp16_c929__detect(void);
+static short isp16_cdi_config(int base, u_char drive_type, int irq,
+ int dma);
+static short isp16_type; /* dependent on type of interface card */
+static u_char isp16_ctrl;
+static u_short isp16_enable_port;
+
+static int isp16_cdrom_base = ISP16_CDROM_IO_BASE;
+static int isp16_cdrom_irq = ISP16_CDROM_IRQ;
+static int isp16_cdrom_dma = ISP16_CDROM_DMA;
+static char *isp16_cdrom_type = ISP16_CDROM_TYPE;
+
+module_param(isp16_cdrom_base, int, 0);
+module_param(isp16_cdrom_irq, int, 0);
+module_param(isp16_cdrom_dma, int, 0);
+module_param(isp16_cdrom_type, charp, 0);
+
+#define ISP16_IN(p) (outb(isp16_ctrl,ISP16_CTRL_PORT), inb(p))
+#define ISP16_OUT(p,b) (outb(isp16_ctrl,ISP16_CTRL_PORT), outb(b,p))
+
+#ifndef MODULE
+
+static int
+__init isp16_setup(char *str)
+{
+ int ints[4];
+
+ (void) get_options(str, ARRAY_SIZE(ints), ints);
+ if (ints[0] > 0)
+ isp16_cdrom_base = ints[1];
+ if (ints[0] > 1)
+ isp16_cdrom_irq = ints[2];
+ if (ints[0] > 2)
+ isp16_cdrom_dma = ints[3];
+ if (str)
+ isp16_cdrom_type = str;
+
+ return 1;
+}
+
+__setup("isp16=", isp16_setup);
+
+#endif /* MODULE */
+
+/*
+ * ISP16 initialisation.
+ *
+ */
+static int __init isp16_init(void)
+{
+ u_char expected_drive;
+
+ printk(KERN_INFO
+ "ISP16: configuration cdrom interface, version %d.%d.\n",
+ ISP16_VERSION_MAJOR, ISP16_VERSION_MINOR);
+
+ if (!strcmp(isp16_cdrom_type, "noisp16")) {
+ printk("ISP16: no cdrom interface configured.\n");
+ return 0;
+ }
+
+ if (!request_region(ISP16_IO_BASE, ISP16_IO_SIZE, "isp16")) {
+ printk("ISP16: i/o ports already in use.\n");
+ goto out;
+ }
+
+ if ((isp16_type = isp16_detect()) < 0) {
+ printk("ISP16: no cdrom interface found.\n");
+ goto cleanup_out;
+ }
+
+ printk(KERN_INFO
+ "ISP16: cdrom interface (with OPTi 82C92%d chip) detected.\n",
+ (isp16_type == 2) ? 9 : 8);
+
+ if (!strcmp(isp16_cdrom_type, "Sanyo"))
+ expected_drive =
+ (isp16_type ? ISP16_SANYO1 : ISP16_SANYO0);
+ else if (!strcmp(isp16_cdrom_type, "Sony"))
+ expected_drive = ISP16_SONY;
+ else if (!strcmp(isp16_cdrom_type, "Panasonic"))
+ expected_drive =
+ (isp16_type ? ISP16_PANASONIC1 : ISP16_PANASONIC0);
+ else if (!strcmp(isp16_cdrom_type, "Mitsumi"))
+ expected_drive = ISP16_MITSUMI;
+ else {
+ printk("ISP16: %s not supported by cdrom interface.\n",
+ isp16_cdrom_type);
+ goto cleanup_out;
+ }
+
+ if (isp16_cdi_config(isp16_cdrom_base, expected_drive,
+ isp16_cdrom_irq, isp16_cdrom_dma) < 0) {
+ printk
+ ("ISP16: cdrom interface has not been properly configured.\n");
+ goto cleanup_out;
+ }
+ printk(KERN_INFO
+ "ISP16: cdrom interface set up with io base 0x%03X, irq %d, dma %d,"
+ " type %s.\n", isp16_cdrom_base, isp16_cdrom_irq,
+ isp16_cdrom_dma, isp16_cdrom_type);
+ return 0;
+
+cleanup_out:
+ release_region(ISP16_IO_BASE, ISP16_IO_SIZE);
+out:
+ return -EIO;
+}
+
+static short __init isp16_detect(void)
+{
+
+ if (isp16_c929__detect() >= 0)
+ return 2;
+ else
+ return (isp16_c928__detect());
+}
+
+static short __init isp16_c928__detect(void)
+{
+ u_char ctrl;
+ u_char enable_cdrom;
+ u_char io;
+ short i = -1;
+
+ isp16_ctrl = ISP16_C928__CTRL;
+ isp16_enable_port = ISP16_C928__ENABLE_PORT;
+
+ /* read' and write' are a special read and write, respectively */
+
+ /* read' ISP16_CTRL_PORT, clear last two bits and write' back the result */
+ ctrl = ISP16_IN(ISP16_CTRL_PORT) & 0xFC;
+ ISP16_OUT(ISP16_CTRL_PORT, ctrl);
+
+ /* read' 3,4 and 5-bit from the cdrom enable port */
+ enable_cdrom = ISP16_IN(ISP16_C928__ENABLE_PORT) & 0x38;
+
+ if (!(enable_cdrom & 0x20)) { /* 5-bit not set */
+ /* read' last 2 bits of ISP16_IO_SET_PORT */
+ io = ISP16_IN(ISP16_IO_SET_PORT) & 0x03;
+ if (((io & 0x01) << 1) == (io & 0x02)) { /* bits are the same */
+ if (io == 0) { /* ...the same and 0 */
+ i = 0;
+ enable_cdrom |= 0x20;
+ } else { /* ...the same and 1 *//* my card, first time 'round */
+ i = 1;
+ enable_cdrom |= 0x28;
+ }
+ ISP16_OUT(ISP16_C928__ENABLE_PORT, enable_cdrom);
+ } else { /* bits are not the same */
+ ISP16_OUT(ISP16_CTRL_PORT, ctrl);
+ return i; /* -> not detected: possibly incorrect conclusion */
+ }
+ } else if (enable_cdrom == 0x20)
+ i = 0;
+ else if (enable_cdrom == 0x28) /* my card, already initialised */
+ i = 1;
+
+ ISP16_OUT(ISP16_CTRL_PORT, ctrl);
+
+ return i;
+}
+
+static short __init isp16_c929__detect(void)
+{
+ u_char ctrl;
+ u_char tmp;
+
+ isp16_ctrl = ISP16_C929__CTRL;
+ isp16_enable_port = ISP16_C929__ENABLE_PORT;
+
+ /* read' and write' are a special read and write, respectively */
+
+ /* read' ISP16_CTRL_PORT and save */
+ ctrl = ISP16_IN(ISP16_CTRL_PORT);
+
+ /* write' zero to the ctrl port and get response */
+ ISP16_OUT(ISP16_CTRL_PORT, 0);
+ tmp = ISP16_IN(ISP16_CTRL_PORT);
+
+ if (tmp != 2) /* isp16 with 82C929 not detected */
+ return -1;
+
+ /* restore ctrl port value */
+ ISP16_OUT(ISP16_CTRL_PORT, ctrl);
+
+ return 2;
+}
+
+static short __init
+isp16_cdi_config(int base, u_char drive_type, int irq, int dma)
+{
+ u_char base_code;
+ u_char irq_code;
+ u_char dma_code;
+ u_char i;
+
+ if ((drive_type == ISP16_MITSUMI) && (dma != 0))
+ printk("ISP16: Mitsumi cdrom drive has no dma support.\n");
+
+ switch (base) {
+ case 0x340:
+ base_code = ISP16_BASE_340;
+ break;
+ case 0x330:
+ base_code = ISP16_BASE_330;
+ break;
+ case 0x360:
+ base_code = ISP16_BASE_360;
+ break;
+ case 0x320:
+ base_code = ISP16_BASE_320;
+ break;
+ default:
+ printk
+ ("ISP16: base address 0x%03X not supported by cdrom interface.\n",
+ base);
+ return -1;
+ }
+ switch (irq) {
+ case 0:
+ irq_code = ISP16_IRQ_X;
+ break; /* disable irq */
+ case 5:
+ irq_code = ISP16_IRQ_5;
+ printk("ISP16: irq 5 shouldn't be used by cdrom interface,"
+ " due to possible conflicts with the sound card.\n");
+ break;
+ case 7:
+ irq_code = ISP16_IRQ_7;
+ printk("ISP16: irq 7 shouldn't be used by cdrom interface,"
+ " due to possible conflicts with the sound card.\n");
+ break;
+ case 3:
+ irq_code = ISP16_IRQ_3;
+ break;
+ case 9:
+ irq_code = ISP16_IRQ_9;
+ break;
+ case 10:
+ irq_code = ISP16_IRQ_10;
+ break;
+ case 11:
+ irq_code = ISP16_IRQ_11;
+ break;
+ default:
+ printk("ISP16: irq %d not supported by cdrom interface.\n",
+ irq);
+ return -1;
+ }
+ switch (dma) {
+ case 0:
+ dma_code = ISP16_DMA_X;
+ break; /* disable dma */
+ case 1:
+ printk("ISP16: dma 1 cannot be used by cdrom interface,"
+ " due to conflict with the sound card.\n");
+ return -1;
+ break;
+ case 3:
+ dma_code = ISP16_DMA_3;
+ break;
+ case 5:
+ dma_code = ISP16_DMA_5;
+ break;
+ case 6:
+ dma_code = ISP16_DMA_6;
+ break;
+ case 7:
+ dma_code = ISP16_DMA_7;
+ break;
+ default:
+ printk("ISP16: dma %d not supported by cdrom interface.\n",
+ dma);
+ return -1;
+ }
+
+ if (drive_type != ISP16_SONY && drive_type != ISP16_PANASONIC0 &&
+ drive_type != ISP16_PANASONIC1 && drive_type != ISP16_SANYO0 &&
+ drive_type != ISP16_SANYO1 && drive_type != ISP16_MITSUMI &&
+ drive_type != ISP16_DRIVE_X) {
+ printk
+ ("ISP16: drive type (code 0x%02X) not supported by cdrom"
+ " interface.\n", drive_type);
+ return -1;
+ }
+
+ /* set type of interface */
+ i = ISP16_IN(ISP16_DRIVE_SET_PORT) & ISP16_DRIVE_SET_MASK; /* clear some bits */
+ ISP16_OUT(ISP16_DRIVE_SET_PORT, i | drive_type);
+
+ /* enable cdrom on interface with 82C929 chip */
+ if (isp16_type > 1)
+ ISP16_OUT(isp16_enable_port, ISP16_ENABLE_CDROM);
+
+ /* set base address, irq and dma */
+ i = ISP16_IN(ISP16_IO_SET_PORT) & ISP16_IO_SET_MASK; /* keep some bits */
+ ISP16_OUT(ISP16_IO_SET_PORT, i | base_code | irq_code | dma_code);
+
+ return 0;
+}
+
+static void __exit isp16_exit(void)
+{
+ release_region(ISP16_IO_BASE, ISP16_IO_SIZE);
+ printk(KERN_INFO "ISP16: module released.\n");
+}
+
+module_init(isp16_init);
+module_exit(isp16_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/trunk/drivers/cdrom/isp16.h b/trunk/drivers/cdrom/isp16.h
new file mode 100644
index 000000000000..5bd22c8f7a96
--- /dev/null
+++ b/trunk/drivers/cdrom/isp16.h
@@ -0,0 +1,72 @@
+/* -- isp16.h
+ *
+ * Header for detection and initialisation of cdrom interface (only) on
+ * ISP16 (MAD16, Mozart) sound card.
+ *
+ * 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.
+ *
+ */
+
+/* These are the default values */
+#define ISP16_CDROM_TYPE "Sanyo"
+#define ISP16_CDROM_IO_BASE 0x340
+#define ISP16_CDROM_IRQ 0
+#define ISP16_CDROM_DMA 0
+
+/* Some (Media)Magic */
+/* define types of drive the interface on an ISP16 card may be looking at */
+#define ISP16_DRIVE_X 0x00
+#define ISP16_SONY 0x02
+#define ISP16_PANASONIC0 0x02
+#define ISP16_SANYO0 0x02
+#define ISP16_MITSUMI 0x04
+#define ISP16_PANASONIC1 0x06
+#define ISP16_SANYO1 0x06
+#define ISP16_DRIVE_NOT_USED 0x08 /* not used */
+#define ISP16_DRIVE_SET_MASK 0xF1 /* don't change 0-bit or 4-7-bits*/
+/* ...for port */
+#define ISP16_DRIVE_SET_PORT 0xF8D
+/* set io parameters */
+#define ISP16_BASE_340 0x00
+#define ISP16_BASE_330 0x40
+#define ISP16_BASE_360 0x80
+#define ISP16_BASE_320 0xC0
+#define ISP16_IRQ_X 0x00
+#define ISP16_IRQ_5 0x04 /* shouldn't be used to avoid sound card conflicts */
+#define ISP16_IRQ_7 0x08 /* shouldn't be used to avoid sound card conflicts */
+#define ISP16_IRQ_3 0x0C
+#define ISP16_IRQ_9 0x10
+#define ISP16_IRQ_10 0x14
+#define ISP16_IRQ_11 0x18
+#define ISP16_DMA_X 0x03
+#define ISP16_DMA_3 0x00
+#define ISP16_DMA_5 0x00
+#define ISP16_DMA_6 0x01
+#define ISP16_DMA_7 0x02
+#define ISP16_IO_SET_MASK 0x20 /* don't change 5-bit */
+/* ...for port */
+#define ISP16_IO_SET_PORT 0xF8E
+/* enable the card */
+#define ISP16_C928__ENABLE_PORT 0xF90 /* ISP16 with OPTi 82C928 chip */
+#define ISP16_C929__ENABLE_PORT 0xF91 /* ISP16 with OPTi 82C929 chip */
+#define ISP16_ENABLE_CDROM 0x80 /* seven bit */
+
+/* the magic stuff */
+#define ISP16_CTRL_PORT 0xF8F
+#define ISP16_C928__CTRL 0xE2 /* ISP16 with OPTi 82C928 chip */
+#define ISP16_C929__CTRL 0xE3 /* ISP16 with OPTi 82C929 chip */
+
+#define ISP16_IO_BASE 0xF8D
+#define ISP16_IO_SIZE 5 /* ports used from 0xF8D up to 0xF91 */
diff --git a/trunk/drivers/cdrom/mcdx.c b/trunk/drivers/cdrom/mcdx.c
new file mode 100644
index 000000000000..4310cc84dfed
--- /dev/null
+++ b/trunk/drivers/cdrom/mcdx.c
@@ -0,0 +1,1943 @@
+/*
+ * The Mitsumi CDROM interface
+ * Copyright (C) 1995 1996 Heiko Schlittermann
+ * VERSION: 2.14(hs)
+ *
+ * ... anyway, I'm back again, thanks to Marcin, he adopted
+ * large portions of my code (at least the parts containing
+ * my main thoughts ...)
+ *
+ ****************** H E L P *********************************
+ * If you ever plan to update your CD ROM drive and perhaps
+ * want to sell or simply give away your Mitsumi FX-001[DS]
+ * -- Please --
+ * mail me (heiko@lotte.sax.de). When my last drive goes
+ * ballistic no more driver support will be available from me!
+ *************************************************************
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Thanks to
+ * The Linux Community at all and ...
+ * Martin Harriss (he wrote the first Mitsumi Driver)
+ * Eberhard Moenkeberg (he gave me much support and the initial kick)
+ * Bernd Huebner, Ruediger Helsch (Unifix-Software GmbH, they
+ * improved the original driver)
+ * Jon Tombs, Bjorn Ekwall (module support)
+ * Daniel v. Mosnenck (he sent me the Technical and Programming Reference)
+ * Gerd Knorr (he lent me his PhotoCD)
+ * Nils Faerber and Roger E. Wolff (extensively tested the LU portion)
+ * Andreas Kies (testing the mysterious hang-ups)
+ * Heiko Eissfeldt (VERIFY_READ/WRITE)
+ * Marcin Dalecki (improved performance, shortened code)
+ * ... somebody forgotten?
+ *
+ * 9 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
+ */
+
+
+#ifdef RCS
+static const char *mcdx_c_version
+ = "$Id: mcdx.c,v 1.21 1997/01/26 07:12:59 davem Exp $";
+#endif
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#define MAJOR_NR MITSUMI_X_CDROM_MAJOR
+#include
+
+#include "mcdx.h"
+
+#ifndef HZ
+#error HZ not defined
+#endif
+
+#define xwarn(fmt, args...) printk(KERN_WARNING MCDX " " fmt, ## args)
+
+#if !MCDX_QUIET
+#define xinfo(fmt, args...) printk(KERN_INFO MCDX " " fmt, ## args)
+#else
+#define xinfo(fmt, args...) { ; }
+#endif
+
+#if MCDX_DEBUG
+#define xtrace(lvl, fmt, args...) \
+ { if (lvl > 0) \
+ { printk(KERN_DEBUG MCDX ":: " fmt, ## args); } }
+#define xdebug(fmt, args...) printk(KERN_DEBUG MCDX ":: " fmt, ## args)
+#else
+#define xtrace(lvl, fmt, args...) { ; }
+#define xdebug(fmt, args...) { ; }
+#endif
+
+/* CONSTANTS *******************************************************/
+
+/* Following are the number of sectors we _request_ from the drive
+ every time an access outside the already requested range is done.
+ The _direct_ size is the number of sectors we're allowed to skip
+ directly (performing a read instead of requesting the new sector
+ needed */
+static const int REQUEST_SIZE = 800; /* should be less then 255 * 4 */
+static const int DIRECT_SIZE = 400; /* should be less then REQUEST_SIZE */
+
+enum drivemodes { TOC, DATA, RAW, COOKED };
+enum datamodes { MODE0, MODE1, MODE2 };
+enum resetmodes { SOFT, HARD };
+
+static const int SINGLE = 0x01; /* single speed drive (FX001S, LU) */
+static const int DOUBLE = 0x02; /* double speed drive (FX001D, ..? */
+static const int DOOR = 0x04; /* door locking capability */
+static const int MULTI = 0x08; /* multi session capability */
+
+static const unsigned char READ1X = 0xc0;
+static const unsigned char READ2X = 0xc1;
+
+
+/* DECLARATIONS ****************************************************/
+struct s_subqcode {
+ unsigned char control;
+ unsigned char tno;
+ unsigned char index;
+ struct cdrom_msf0 tt;
+ struct cdrom_msf0 dt;
+};
+
+struct s_diskinfo {
+ unsigned int n_first;
+ unsigned int n_last;
+ struct cdrom_msf0 msf_leadout;
+ struct cdrom_msf0 msf_first;
+};
+
+struct s_multi {
+ unsigned char multi;
+ struct cdrom_msf0 msf_last;
+};
+
+struct s_version {
+ unsigned char code;
+ unsigned char ver;
+};
+
+/* Per drive/controller stuff **************************************/
+
+struct s_drive_stuff {
+ /* waitqueues */
+ wait_queue_head_t busyq;
+ wait_queue_head_t lockq;
+ wait_queue_head_t sleepq;
+
+ /* flags */
+ volatile int introk; /* status of last irq operation */
+ volatile int busy; /* drive performs an operation */
+ volatile int lock; /* exclusive usage */
+
+ /* cd infos */
+ struct s_diskinfo di;
+ struct s_multi multi;
+ struct s_subqcode *toc; /* first entry of the toc array */
+ struct s_subqcode start;
+ struct s_subqcode stop;
+ int xa; /* 1 if xa disk */
+ int audio; /* 1 if audio disk */
+ int audiostatus;
+
+ /* `buffer' control */
+ volatile int valid; /* pending, ..., values are valid */
+ volatile int pending; /* next sector to be read */
+ volatile int low_border; /* first sector not to be skipped direct */
+ volatile int high_border; /* first sector `out of area' */
+#ifdef AK2
+ volatile int int_err;
+#endif /* AK2 */
+
+ /* adds and odds */
+ unsigned wreg_data; /* w data */
+ unsigned wreg_reset; /* w hardware reset */
+ unsigned wreg_hcon; /* w hardware conf */
+ unsigned wreg_chn; /* w channel */
+ unsigned rreg_data; /* r data */
+ unsigned rreg_status; /* r status */
+
+ int irq; /* irq used by this drive */
+ int present; /* drive present and its capabilities */
+ unsigned char readcmd; /* read cmd depends on single/double speed */
+ unsigned char playcmd; /* play should always be single speed */
+ unsigned int xxx; /* set if changed, reset while open */
+ unsigned int yyy; /* set if changed, reset by media_changed */
+ int users; /* keeps track of open/close */
+ int lastsector; /* last block accessible */
+ int status; /* last operation's error / status */
+ int readerrs; /* # of blocks read w/o error */
+ struct cdrom_device_info info;
+ struct gendisk *disk;
+};
+
+
+/* Prototypes ******************************************************/
+
+/* The following prototypes are already declared elsewhere. They are
+ repeated here to show what's going on. And to sense, if they're
+ changed elsewhere. */
+
+static int mcdx_init(void);
+
+static int mcdx_block_open(struct inode *inode, struct file *file)
+{
+ struct s_drive_stuff *p = inode->i_bdev->bd_disk->private_data;
+ return cdrom_open(&p->info, inode, file);
+}
+
+static int mcdx_block_release(struct inode *inode, struct file *file)
+{
+ struct s_drive_stuff *p = inode->i_bdev->bd_disk->private_data;
+ return cdrom_release(&p->info, file);
+}
+
+static int mcdx_block_ioctl(struct inode *inode, struct file *file,
+ unsigned cmd, unsigned long arg)
+{
+ struct s_drive_stuff *p = inode->i_bdev->bd_disk->private_data;
+ return cdrom_ioctl(file, &p->info, inode, cmd, arg);
+}
+
+static int mcdx_block_media_changed(struct gendisk *disk)
+{
+ struct s_drive_stuff *p = disk->private_data;
+ return cdrom_media_changed(&p->info);
+}
+
+static struct block_device_operations mcdx_bdops =
+{
+ .owner = THIS_MODULE,
+ .open = mcdx_block_open,
+ .release = mcdx_block_release,
+ .ioctl = mcdx_block_ioctl,
+ .media_changed = mcdx_block_media_changed,
+};
+
+
+/* Indirect exported functions. These functions are exported by their
+ addresses, such as mcdx_open and mcdx_close in the
+ structure mcdx_dops. */
+
+/* exported by file_ops */
+static int mcdx_open(struct cdrom_device_info *cdi, int purpose);
+static void mcdx_close(struct cdrom_device_info *cdi);
+static int mcdx_media_changed(struct cdrom_device_info *cdi, int disc_nr);
+static int mcdx_tray_move(struct cdrom_device_info *cdi, int position);
+static int mcdx_lockdoor(struct cdrom_device_info *cdi, int lock);
+static int mcdx_audio_ioctl(struct cdrom_device_info *cdi,
+ unsigned int cmd, void *arg);
+
+/* misc internal support functions */
+static void log2msf(unsigned int, struct cdrom_msf0 *);
+static unsigned int msf2log(const struct cdrom_msf0 *);
+static unsigned int uint2bcd(unsigned int);
+static unsigned int bcd2uint(unsigned char);
+static unsigned port(int *);
+static int irq(int *);
+static void mcdx_delay(struct s_drive_stuff *, long jifs);
+static int mcdx_transfer(struct s_drive_stuff *, char *buf, int sector,
+ int nr_sectors);
+static int mcdx_xfer(struct s_drive_stuff *, char *buf, int sector,
+ int nr_sectors);
+
+static int mcdx_config(struct s_drive_stuff *, int);
+static int mcdx_requestversion(struct s_drive_stuff *, struct s_version *,
+ int);
+static int mcdx_stop(struct s_drive_stuff *, int);
+static int mcdx_hold(struct s_drive_stuff *, int);
+static int mcdx_reset(struct s_drive_stuff *, enum resetmodes, int);
+static int mcdx_setdrivemode(struct s_drive_stuff *, enum drivemodes, int);
+static int mcdx_setdatamode(struct s_drive_stuff *, enum datamodes, int);
+static int mcdx_requestsubqcode(struct s_drive_stuff *,
+ struct s_subqcode *, int);
+static int mcdx_requestmultidiskinfo(struct s_drive_stuff *,
+ struct s_multi *, int);
+static int mcdx_requesttocdata(struct s_drive_stuff *, struct s_diskinfo *,
+ int);
+static int mcdx_getstatus(struct s_drive_stuff *, int);
+static int mcdx_getval(struct s_drive_stuff *, int to, int delay, char *);
+static int mcdx_talk(struct s_drive_stuff *,
+ const unsigned char *cmd, size_t,
+ void *buffer, size_t size, unsigned int timeout, int);
+static int mcdx_readtoc(struct s_drive_stuff *);
+static int mcdx_playtrk(struct s_drive_stuff *, const struct cdrom_ti *);
+static int mcdx_playmsf(struct s_drive_stuff *, const struct cdrom_msf *);
+static int mcdx_setattentuator(struct s_drive_stuff *,
+ struct cdrom_volctrl *, int);
+
+/* static variables ************************************************/
+
+static int mcdx_drive_map[][2] = MCDX_DRIVEMAP;
+static struct s_drive_stuff *mcdx_stuffp[MCDX_NDRIVES];
+static DEFINE_SPINLOCK(mcdx_lock);
+static struct request_queue *mcdx_queue;
+
+/* You can only set the first two pairs, from old MODULE_PARM code. */
+static int mcdx_set(const char *val, struct kernel_param *kp)
+{
+ get_options((char *)val, 4, (int *)mcdx_drive_map);
+ return 0;
+}
+module_param_call(mcdx, mcdx_set, NULL, NULL, 0);
+
+static struct cdrom_device_ops mcdx_dops = {
+ .open = mcdx_open,
+ .release = mcdx_close,
+ .media_changed = mcdx_media_changed,
+ .tray_move = mcdx_tray_move,
+ .lock_door = mcdx_lockdoor,
+ .audio_ioctl = mcdx_audio_ioctl,
+ .capability = CDC_OPEN_TRAY | CDC_LOCK | CDC_MEDIA_CHANGED |
+ CDC_PLAY_AUDIO | CDC_DRIVE_STATUS,
+};
+
+/* KERNEL INTERFACE FUNCTIONS **************************************/
+
+
+static int mcdx_audio_ioctl(struct cdrom_device_info *cdi,
+ unsigned int cmd, void *arg)
+{
+ struct s_drive_stuff *stuffp = cdi->handle;
+
+ if (!stuffp->present)
+ return -ENXIO;
+
+ if (stuffp->xxx) {
+ if (-1 == mcdx_requesttocdata(stuffp, &stuffp->di, 1)) {
+ stuffp->lastsector = -1;
+ } else {
+ stuffp->lastsector = (CD_FRAMESIZE / 512)
+ * msf2log(&stuffp->di.msf_leadout) - 1;
+ }
+
+ if (stuffp->toc) {
+ kfree(stuffp->toc);
+ stuffp->toc = NULL;
+ if (-1 == mcdx_readtoc(stuffp))
+ return -1;
+ }
+
+ stuffp->xxx = 0;
+ }
+
+ switch (cmd) {
+ case CDROMSTART:{
+ xtrace(IOCTL, "ioctl() START\n");
+ /* Spin up the drive. Don't think we can do this.
+ * For now, ignore it.
+ */
+ return 0;
+ }
+
+ case CDROMSTOP:{
+ xtrace(IOCTL, "ioctl() STOP\n");
+ stuffp->audiostatus = CDROM_AUDIO_INVALID;
+ if (-1 == mcdx_stop(stuffp, 1))
+ return -EIO;
+ return 0;
+ }
+
+ case CDROMPLAYTRKIND:{
+ struct cdrom_ti *ti = (struct cdrom_ti *) arg;
+
+ xtrace(IOCTL, "ioctl() PLAYTRKIND\n");
+ if ((ti->cdti_trk0 < stuffp->di.n_first)
+ || (ti->cdti_trk0 > stuffp->di.n_last)
+ || (ti->cdti_trk1 < stuffp->di.n_first))
+ return -EINVAL;
+ if (ti->cdti_trk1 > stuffp->di.n_last)
+ ti->cdti_trk1 = stuffp->di.n_last;
+ xtrace(PLAYTRK, "ioctl() track %d to %d\n",
+ ti->cdti_trk0, ti->cdti_trk1);
+ return mcdx_playtrk(stuffp, ti);
+ }
+
+ case CDROMPLAYMSF:{
+ struct cdrom_msf *msf = (struct cdrom_msf *) arg;
+
+ xtrace(IOCTL, "ioctl() PLAYMSF\n");
+
+ if ((stuffp->audiostatus == CDROM_AUDIO_PLAY)
+ && (-1 == mcdx_hold(stuffp, 1)))
+ return -EIO;
+
+ msf->cdmsf_min0 = uint2bcd(msf->cdmsf_min0);
+ msf->cdmsf_sec0 = uint2bcd(msf->cdmsf_sec0);
+ msf->cdmsf_frame0 = uint2bcd(msf->cdmsf_frame0);
+
+ msf->cdmsf_min1 = uint2bcd(msf->cdmsf_min1);
+ msf->cdmsf_sec1 = uint2bcd(msf->cdmsf_sec1);
+ msf->cdmsf_frame1 = uint2bcd(msf->cdmsf_frame1);
+
+ stuffp->stop.dt.minute = msf->cdmsf_min1;
+ stuffp->stop.dt.second = msf->cdmsf_sec1;
+ stuffp->stop.dt.frame = msf->cdmsf_frame1;
+
+ return mcdx_playmsf(stuffp, msf);
+ }
+
+ case CDROMRESUME:{
+ xtrace(IOCTL, "ioctl() RESUME\n");
+ return mcdx_playtrk(stuffp, NULL);
+ }
+
+ case CDROMREADTOCENTRY:{
+ struct cdrom_tocentry *entry =
+ (struct cdrom_tocentry *) arg;
+ struct s_subqcode *tp = NULL;
+ xtrace(IOCTL, "ioctl() READTOCENTRY\n");
+
+ if (-1 == mcdx_readtoc(stuffp))
+ return -1;
+ if (entry->cdte_track == CDROM_LEADOUT)
+ tp = &stuffp->toc[stuffp->di.n_last -
+ stuffp->di.n_first + 1];
+ else if (entry->cdte_track > stuffp->di.n_last
+ || entry->cdte_track < stuffp->di.n_first)
+ return -EINVAL;
+ else
+ tp = &stuffp->toc[entry->cdte_track -
+ stuffp->di.n_first];
+
+ if (NULL == tp)
+ return -EIO;
+ entry->cdte_adr = tp->control;
+ entry->cdte_ctrl = tp->control >> 4;
+ /* Always return stuff in MSF, and let the Uniform cdrom driver
+ worry about what the user actually wants */
+ entry->cdte_addr.msf.minute =
+ bcd2uint(tp->dt.minute);
+ entry->cdte_addr.msf.second =
+ bcd2uint(tp->dt.second);
+ entry->cdte_addr.msf.frame =
+ bcd2uint(tp->dt.frame);
+ return 0;
+ }
+
+ case CDROMSUBCHNL:{
+ struct cdrom_subchnl *sub =
+ (struct cdrom_subchnl *) arg;
+ struct s_subqcode q;
+
+ xtrace(IOCTL, "ioctl() SUBCHNL\n");
+
+ if (-1 == mcdx_requestsubqcode(stuffp, &q, 2))
+ return -EIO;
+
+ xtrace(SUBCHNL, "audiostatus: %x\n",
+ stuffp->audiostatus);
+ sub->cdsc_audiostatus = stuffp->audiostatus;
+ sub->cdsc_adr = q.control;
+ sub->cdsc_ctrl = q.control >> 4;
+ sub->cdsc_trk = bcd2uint(q.tno);
+ sub->cdsc_ind = bcd2uint(q.index);
+
+ xtrace(SUBCHNL, "trk %d, ind %d\n",
+ sub->cdsc_trk, sub->cdsc_ind);
+ /* Always return stuff in MSF, and let the Uniform cdrom driver
+ worry about what the user actually wants */
+ sub->cdsc_absaddr.msf.minute =
+ bcd2uint(q.dt.minute);
+ sub->cdsc_absaddr.msf.second =
+ bcd2uint(q.dt.second);
+ sub->cdsc_absaddr.msf.frame = bcd2uint(q.dt.frame);
+ sub->cdsc_reladdr.msf.minute =
+ bcd2uint(q.tt.minute);
+ sub->cdsc_reladdr.msf.second =
+ bcd2uint(q.tt.second);
+ sub->cdsc_reladdr.msf.frame = bcd2uint(q.tt.frame);
+ xtrace(SUBCHNL,
+ "msf: abs %02d:%02d:%02d, rel %02d:%02d:%02d\n",
+ sub->cdsc_absaddr.msf.minute,
+ sub->cdsc_absaddr.msf.second,
+ sub->cdsc_absaddr.msf.frame,
+ sub->cdsc_reladdr.msf.minute,
+ sub->cdsc_reladdr.msf.second,
+ sub->cdsc_reladdr.msf.frame);
+
+ return 0;
+ }
+
+ case CDROMREADTOCHDR:{
+ struct cdrom_tochdr *toc =
+ (struct cdrom_tochdr *) arg;
+
+ xtrace(IOCTL, "ioctl() READTOCHDR\n");
+ toc->cdth_trk0 = stuffp->di.n_first;
+ toc->cdth_trk1 = stuffp->di.n_last;
+ xtrace(TOCHDR,
+ "ioctl() track0 = %d, track1 = %d\n",
+ stuffp->di.n_first, stuffp->di.n_last);
+ return 0;
+ }
+
+ case CDROMPAUSE:{
+ xtrace(IOCTL, "ioctl() PAUSE\n");
+ if (stuffp->audiostatus != CDROM_AUDIO_PLAY)
+ return -EINVAL;
+ if (-1 == mcdx_stop(stuffp, 1))
+ return -EIO;
+ stuffp->audiostatus = CDROM_AUDIO_PAUSED;
+ if (-1 ==
+ mcdx_requestsubqcode(stuffp, &stuffp->start,
+ 1))
+ return -EIO;
+ return 0;
+ }
+
+ case CDROMMULTISESSION:{
+ struct cdrom_multisession *ms =
+ (struct cdrom_multisession *) arg;
+ xtrace(IOCTL, "ioctl() MULTISESSION\n");
+ /* Always return stuff in LBA, and let the Uniform cdrom driver
+ worry about what the user actually wants */
+ ms->addr.lba = msf2log(&stuffp->multi.msf_last);
+ ms->xa_flag = !!stuffp->multi.multi;
+ xtrace(MS,
+ "ioctl() (%d, 0x%08x [%02x:%02x.%02x])\n",
+ ms->xa_flag, ms->addr.lba,
+ stuffp->multi.msf_last.minute,
+ stuffp->multi.msf_last.second,
+ stuffp->multi.msf_last.frame);
+
+ return 0;
+ }
+
+ case CDROMEJECT:{
+ xtrace(IOCTL, "ioctl() EJECT\n");
+ if (stuffp->users > 1)
+ return -EBUSY;
+ return (mcdx_tray_move(cdi, 1));
+ }
+
+ case CDROMCLOSETRAY:{
+ xtrace(IOCTL, "ioctl() CDROMCLOSETRAY\n");
+ return (mcdx_tray_move(cdi, 0));
+ }
+
+ case CDROMVOLCTRL:{
+ struct cdrom_volctrl *volctrl =
+ (struct cdrom_volctrl *) arg;
+ xtrace(IOCTL, "ioctl() VOLCTRL\n");
+
+#if 0 /* not tested! */
+ /* adjust for the weirdness of workman (md) */
+ /* can't test it (hs) */
+ volctrl.channel2 = volctrl.channel1;
+ volctrl.channel1 = volctrl.channel3 = 0x00;
+#endif
+ return mcdx_setattentuator(stuffp, volctrl, 2);
+ }
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static void do_mcdx_request(request_queue_t * q)
+{
+ struct s_drive_stuff *stuffp;
+ struct request *req;
+
+ again:
+
+ req = elv_next_request(q);
+ if (!req)
+ return;
+
+ stuffp = req->rq_disk->private_data;
+
+ if (!stuffp->present) {
+ xwarn("do_request(): bad device: %s\n",req->rq_disk->disk_name);
+ xtrace(REQUEST, "end_request(0): bad device\n");
+ end_request(req, 0);
+ return;
+ }
+
+ if (stuffp->audio) {
+ xwarn("do_request() attempt to read from audio cd\n");
+ xtrace(REQUEST, "end_request(0): read from audio\n");
+ end_request(req, 0);
+ return;
+ }
+
+ xtrace(REQUEST, "do_request() (%lu + %lu)\n",
+ req->sector, req->nr_sectors);
+
+ if (req->cmd != READ) {
+ xwarn("do_request(): non-read command to cd!!\n");
+ xtrace(REQUEST, "end_request(0): write\n");
+ end_request(req, 0);
+ return;
+ }
+ else {
+ stuffp->status = 0;
+ while (req->nr_sectors) {
+ int i;
+
+ i = mcdx_transfer(stuffp,
+ req->buffer,
+ req->sector,
+ req->nr_sectors);
+
+ if (i == -1) {
+ end_request(req, 0);
+ goto again;
+ }
+ req->sector += i;
+ req->nr_sectors -= i;
+ req->buffer += (i * 512);
+ }
+ end_request(req, 1);
+ goto again;
+
+ xtrace(REQUEST, "end_request(1)\n");
+ end_request(req, 1);
+ }
+
+ goto again;
+}
+
+static int mcdx_open(struct cdrom_device_info *cdi, int purpose)
+{
+ struct s_drive_stuff *stuffp;
+ xtrace(OPENCLOSE, "open()\n");
+ stuffp = cdi->handle;
+ if (!stuffp->present)
+ return -ENXIO;
+
+ /* Make the modules looking used ... (thanx bjorn).
+ * But we shouldn't forget to decrement the module counter
+ * on error return */
+
+ /* this is only done to test if the drive talks with us */
+ if (-1 == mcdx_getstatus(stuffp, 1))
+ return -EIO;
+
+ if (stuffp->xxx) {
+
+ xtrace(OPENCLOSE, "open() media changed\n");
+ stuffp->audiostatus = CDROM_AUDIO_INVALID;
+ stuffp->readcmd = 0;
+ xtrace(OPENCLOSE, "open() Request multisession info\n");
+ if (-1 ==
+ mcdx_requestmultidiskinfo(stuffp, &stuffp->multi, 6))
+ xinfo("No multidiskinfo\n");
+ } else {
+ /* multisession ? */
+ if (!stuffp->multi.multi)
+ stuffp->multi.msf_last.second = 2;
+
+ xtrace(OPENCLOSE, "open() MS: %d, last @ %02x:%02x.%02x\n",
+ stuffp->multi.multi,
+ stuffp->multi.msf_last.minute,
+ stuffp->multi.msf_last.second,
+ stuffp->multi.msf_last.frame);
+
+ {;
+ } /* got multisession information */
+ /* request the disks table of contents (aka diskinfo) */
+ if (-1 == mcdx_requesttocdata(stuffp, &stuffp->di, 1)) {
+
+ stuffp->lastsector = -1;
+
+ } else {
+
+ stuffp->lastsector = (CD_FRAMESIZE / 512)
+ * msf2log(&stuffp->di.msf_leadout) - 1;
+
+ xtrace(OPENCLOSE,
+ "open() start %d (%02x:%02x.%02x) %d\n",
+ stuffp->di.n_first,
+ stuffp->di.msf_first.minute,
+ stuffp->di.msf_first.second,
+ stuffp->di.msf_first.frame,
+ msf2log(&stuffp->di.msf_first));
+ xtrace(OPENCLOSE,
+ "open() last %d (%02x:%02x.%02x) %d\n",
+ stuffp->di.n_last,
+ stuffp->di.msf_leadout.minute,
+ stuffp->di.msf_leadout.second,
+ stuffp->di.msf_leadout.frame,
+ msf2log(&stuffp->di.msf_leadout));
+ }
+
+ if (stuffp->toc) {
+ xtrace(MALLOC, "open() free old toc @ %p\n",
+ stuffp->toc);
+ kfree(stuffp->toc);
+
+ stuffp->toc = NULL;
+ }
+
+ xtrace(OPENCLOSE, "open() init irq generation\n");
+ if (-1 == mcdx_config(stuffp, 1))
+ return -EIO;
+#ifdef FALLBACK
+ /* Set the read speed */
+ xwarn("AAA %x AAA\n", stuffp->readcmd);
+ if (stuffp->readerrs)
+ stuffp->readcmd = READ1X;
+ else
+ stuffp->readcmd =
+ stuffp->present | SINGLE ? READ1X : READ2X;
+ xwarn("XXX %x XXX\n", stuffp->readcmd);
+#else
+ stuffp->readcmd =
+ stuffp->present | SINGLE ? READ1X : READ2X;
+#endif
+
+ /* try to get the first sector, iff any ... */
+ if (stuffp->lastsector >= 0) {
+ char buf[512];
+ int ans;
+ int tries;
+
+ stuffp->xa = 0;
+ stuffp->audio = 0;
+
+ for (tries = 6; tries; tries--) {
+
+ stuffp->introk = 1;
+
+ xtrace(OPENCLOSE, "open() try as %s\n",
+ stuffp->xa ? "XA" : "normal");
+ /* set data mode */
+ if (-1 == (ans = mcdx_setdatamode(stuffp,
+ stuffp->
+ xa ?
+ MODE2 :
+ MODE1,
+ 1))) {
+ /* return -EIO; */
+ stuffp->xa = 0;
+ break;
+ }
+
+ if ((stuffp->audio = e_audio(ans)))
+ break;
+
+ while (0 ==
+ (ans =
+ mcdx_transfer(stuffp, buf, 0, 1)));
+
+ if (ans == 1)
+ break;
+ stuffp->xa = !stuffp->xa;
+ }
+ }
+ /* xa disks will be read in raw mode, others not */
+ if (-1 == mcdx_setdrivemode(stuffp,
+ stuffp->xa ? RAW : COOKED,
+ 1))
+ return -EIO;
+ if (stuffp->audio) {
+ xinfo("open() audio disk found\n");
+ } else if (stuffp->lastsector >= 0) {
+ xinfo("open() %s%s disk found\n",
+ stuffp->xa ? "XA / " : "",
+ stuffp->multi.
+ multi ? "Multi Session" : "Single Session");
+ }
+ }
+ stuffp->xxx = 0;
+ stuffp->users++;
+ return 0;
+}
+
+static void mcdx_close(struct cdrom_device_info *cdi)
+{
+ struct s_drive_stuff *stuffp;
+
+ xtrace(OPENCLOSE, "close()\n");
+
+ stuffp = cdi->handle;
+
+ --stuffp->users;
+}
+
+static int mcdx_media_changed(struct cdrom_device_info *cdi, int disc_nr)
+/* Return: 1 if media changed since last call to this function
+ 0 otherwise */
+{
+ struct s_drive_stuff *stuffp;
+
+ xinfo("mcdx_media_changed called for device %s\n", cdi->name);
+
+ stuffp = cdi->handle;
+ mcdx_getstatus(stuffp, 1);
+
+ if (stuffp->yyy == 0)
+ return 0;
+
+ stuffp->yyy = 0;
+ return 1;
+}
+
+#ifndef MODULE
+static int __init mcdx_setup(char *str)
+{
+ int pi[4];
+ (void) get_options(str, ARRAY_SIZE(pi), pi);
+
+ if (pi[0] > 0)
+ mcdx_drive_map[0][0] = pi[1];
+ if (pi[0] > 1)
+ mcdx_drive_map[0][1] = pi[2];
+ return 1;
+}
+
+__setup("mcdx=", mcdx_setup);
+
+#endif
+
+/* DIRTY PART ******************************************************/
+
+static void mcdx_delay(struct s_drive_stuff *stuff, long jifs)
+/* This routine is used for sleeping.
+ * A jifs value <0 means NO sleeping,
+ * =0 means minimal sleeping (let the kernel
+ * run for other processes)
+ * >0 means at least sleep for that amount.
+ * May be we could use a simple count loop w/ jumps to itself, but
+ * I wanna make this independent of cpu speed. [1 jiffy is 1/HZ] sec */
+{
+ if (jifs < 0)
+ return;
+
+ xtrace(SLEEP, "*** delay: sleepq\n");
+ interruptible_sleep_on_timeout(&stuff->sleepq, jifs);
+ xtrace(SLEEP, "delay awoken\n");
+ if (signal_pending(current)) {
+ xtrace(SLEEP, "got signal\n");
+ }
+}
+
+static irqreturn_t mcdx_intr(int irq, void *dev_id)
+{
+ struct s_drive_stuff *stuffp = dev_id;
+ unsigned char b;
+
+#ifdef AK2
+ if (!stuffp->busy && stuffp->pending)
+ stuffp->int_err = 1;
+
+#endif /* AK2 */
+ /* get the interrupt status */
+ b = inb(stuffp->rreg_status);
+ stuffp->introk = ~b & MCDX_RBIT_DTEN;
+
+ /* NOTE: We only should get interrupts if the data we
+ * requested are ready to transfer.
+ * But the drive seems to generate ``asynchronous'' interrupts
+ * on several error conditions too. (Despite the err int enable
+ * setting during initialisation) */
+
+ /* if not ok, read the next byte as the drives status */
+ if (!stuffp->introk) {
+ xtrace(IRQ, "intr() irq %d hw status 0x%02x\n", irq, b);
+ if (~b & MCDX_RBIT_STEN) {
+ xinfo("intr() irq %d status 0x%02x\n",
+ irq, inb(stuffp->rreg_data));
+ } else {
+ xinfo("intr() irq %d ambiguous hw status\n", irq);
+ }
+ } else {
+ xtrace(IRQ, "irq() irq %d ok, status %02x\n", irq, b);
+ }
+
+ stuffp->busy = 0;
+ wake_up_interruptible(&stuffp->busyq);
+ return IRQ_HANDLED;
+}
+
+
+static int mcdx_talk(struct s_drive_stuff *stuffp,
+ const unsigned char *cmd, size_t cmdlen,
+ void *buffer, size_t size, unsigned int timeout, int tries)
+/* Send a command to the drive, wait for the result.
+ * returns -1 on timeout, drive status otherwise
+ * If buffer is not zero, the result (length size) is stored there.
+ * If buffer is zero the size should be the number of bytes to read
+ * from the drive. These bytes are discarded.
+ */
+{
+ int st;
+ char c;
+ int discard;
+
+ /* Somebody wants the data read? */
+ if ((discard = (buffer == NULL)))
+ buffer = &c;
+
+ while (stuffp->lock) {
+ xtrace(SLEEP, "*** talk: lockq\n");
+ interruptible_sleep_on(&stuffp->lockq);
+ xtrace(SLEEP, "talk: awoken\n");
+ }
+
+ stuffp->lock = 1;
+
+ /* An operation other then reading data destroys the
+ * data already requested and remembered in stuffp->request, ... */
+ stuffp->valid = 0;
+
+#if MCDX_DEBUG & TALK
+ {
+ unsigned char i;
+ xtrace(TALK,
+ "talk() %d / %d tries, res.size %d, command 0x%02x",
+ tries, timeout, size, (unsigned char) cmd[0]);
+ for (i = 1; i < cmdlen; i++)
+ xtrace(TALK, " 0x%02x", cmd[i]);
+ xtrace(TALK, "\n");
+ }
+#endif
+
+ /* give up if all tries are done (bad) or if the status
+ * st != -1 (good) */
+ for (st = -1; st == -1 && tries; tries--) {
+
+ char *bp = (char *) buffer;
+ size_t sz = size;
+
+ outsb(stuffp->wreg_data, cmd, cmdlen);
+ xtrace(TALK, "talk() command sent\n");
+
+ /* get the status byte */
+ if (-1 == mcdx_getval(stuffp, timeout, 0, bp)) {
+ xinfo("talk() %02x timed out (status), %d tr%s left\n",
+ cmd[0], tries - 1, tries == 2 ? "y" : "ies");
+ continue;
+ }
+ st = *bp;
+ sz--;
+ if (!discard)
+ bp++;
+
+ xtrace(TALK, "talk() got status 0x%02x\n", st);
+
+ /* command error? */
+ if (e_cmderr(st)) {
+ xwarn("command error cmd = %02x %s \n",
+ cmd[0], cmdlen > 1 ? "..." : "");
+ st = -1;
+ continue;
+ }
+
+ /* audio status? */
+ if (stuffp->audiostatus == CDROM_AUDIO_INVALID)
+ stuffp->audiostatus =
+ e_audiobusy(st) ? CDROM_AUDIO_PLAY :
+ CDROM_AUDIO_NO_STATUS;
+ else if (stuffp->audiostatus == CDROM_AUDIO_PLAY
+ && e_audiobusy(st) == 0)
+ stuffp->audiostatus = CDROM_AUDIO_COMPLETED;
+
+ /* media change? */
+ if (e_changed(st)) {
+ xinfo("talk() media changed\n");
+ stuffp->xxx = stuffp->yyy = 1;
+ }
+
+ /* now actually get the data */
+ while (sz--) {
+ if (-1 == mcdx_getval(stuffp, timeout, 0, bp)) {
+ xinfo("talk() %02x timed out (data), %d tr%s left\n",
+ cmd[0], tries - 1,
+ tries == 2 ? "y" : "ies");
+ st = -1;
+ break;
+ }
+ if (!discard)
+ bp++;
+ xtrace(TALK, "talk() got 0x%02x\n", *(bp - 1));
+ }
+ }
+
+#if !MCDX_QUIET
+ if (!tries && st == -1)
+ xinfo("talk() giving up\n");
+#endif
+
+ stuffp->lock = 0;
+ wake_up_interruptible(&stuffp->lockq);
+
+ xtrace(TALK, "talk() done with 0x%02x\n", st);
+ return st;
+}
+
+/* MODULE STUFF ***********************************************************/
+
+static int __init __mcdx_init(void)
+{
+ int i;
+ int drives = 0;
+
+ mcdx_init();
+ for (i = 0; i < MCDX_NDRIVES; i++) {
+ if (mcdx_stuffp[i]) {
+ xtrace(INIT, "init_module() drive %d stuff @ %p\n",
+ i, mcdx_stuffp[i]);
+ drives++;
+ }
+ }
+
+ if (!drives)
+ return -EIO;
+
+ return 0;
+}
+
+static void __exit mcdx_exit(void)
+{
+ int i;
+
+ xinfo("cleanup_module called\n");
+
+ for (i = 0; i < MCDX_NDRIVES; i++) {
+ struct s_drive_stuff *stuffp = mcdx_stuffp[i];
+ if (!stuffp)
+ continue;
+ del_gendisk(stuffp->disk);
+ if (unregister_cdrom(&stuffp->info)) {
+ printk(KERN_WARNING "Can't unregister cdrom mcdx\n");
+ continue;
+ }
+ put_disk(stuffp->disk);
+ release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+ free_irq(stuffp->irq, NULL);
+ if (stuffp->toc) {
+ xtrace(MALLOC, "cleanup_module() free toc @ %p\n",
+ stuffp->toc);
+ kfree(stuffp->toc);
+ }
+ xtrace(MALLOC, "cleanup_module() free stuffp @ %p\n",
+ stuffp);
+ mcdx_stuffp[i] = NULL;
+ kfree(stuffp);
+ }
+
+ if (unregister_blkdev(MAJOR_NR, "mcdx") != 0) {
+ xwarn("cleanup() unregister_blkdev() failed\n");
+ }
+#if !MCDX_QUIET
+ else
+ xinfo("cleanup() succeeded\n");
+#endif
+ blk_cleanup_queue(mcdx_queue);
+}
+
+#ifdef MODULE
+module_init(__mcdx_init);
+#endif
+module_exit(mcdx_exit);
+
+
+/* Support functions ************************************************/
+
+static int __init mcdx_init_drive(int drive)
+{
+ struct s_version version;
+ struct gendisk *disk;
+ struct s_drive_stuff *stuffp;
+ int size = sizeof(*stuffp);
+ char msg[80];
+
+ xtrace(INIT, "init() try drive %d\n", drive);
+
+ xtrace(INIT, "kmalloc space for stuffpt's\n");
+ xtrace(MALLOC, "init() malloc %d bytes\n", size);
+ if (!(stuffp = kzalloc(size, GFP_KERNEL))) {
+ xwarn("init() malloc failed\n");
+ return 1;
+ }
+
+ disk = alloc_disk(1);
+ if (!disk) {
+ xwarn("init() malloc failed\n");
+ kfree(stuffp);
+ return 1;
+ }
+
+ xtrace(INIT, "init() got %d bytes for drive stuff @ %p\n",
+ sizeof(*stuffp), stuffp);
+
+ /* set default values */
+ stuffp->present = 0; /* this should be 0 already */
+ stuffp->toc = NULL; /* this should be NULL already */
+
+ /* setup our irq and i/o addresses */
+ stuffp->irq = irq(mcdx_drive_map[drive]);
+ stuffp->wreg_data = stuffp->rreg_data = port(mcdx_drive_map[drive]);
+ stuffp->wreg_reset = stuffp->rreg_status = stuffp->wreg_data + 1;
+ stuffp->wreg_hcon = stuffp->wreg_reset + 1;
+ stuffp->wreg_chn = stuffp->wreg_hcon + 1;
+
+ init_waitqueue_head(&stuffp->busyq);
+ init_waitqueue_head(&stuffp->lockq);
+ init_waitqueue_head(&stuffp->sleepq);
+
+ /* check if i/o addresses are available */
+ if (!request_region(stuffp->wreg_data, MCDX_IO_SIZE, "mcdx")) {
+ xwarn("0x%03x,%d: Init failed. "
+ "I/O ports (0x%03x..0x%03x) already in use.\n",
+ stuffp->wreg_data, stuffp->irq,
+ stuffp->wreg_data,
+ stuffp->wreg_data + MCDX_IO_SIZE - 1);
+ xtrace(MALLOC, "init() free stuffp @ %p\n", stuffp);
+ kfree(stuffp);
+ put_disk(disk);
+ xtrace(INIT, "init() continue at next drive\n");
+ return 0; /* next drive */
+ }
+
+ xtrace(INIT, "init() i/o port is available at 0x%03x\n"
+ stuffp->wreg_data);
+ xtrace(INIT, "init() hardware reset\n");
+ mcdx_reset(stuffp, HARD, 1);
+
+ xtrace(INIT, "init() get version\n");
+ if (-1 == mcdx_requestversion(stuffp, &version, 4)) {
+ /* failed, next drive */
+ release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+ xwarn("%s=0x%03x,%d: Init failed. Can't get version.\n",
+ MCDX, stuffp->wreg_data, stuffp->irq);
+ xtrace(MALLOC, "init() free stuffp @ %p\n", stuffp);
+ kfree(stuffp);
+ put_disk(disk);
+ xtrace(INIT, "init() continue at next drive\n");
+ return 0;
+ }
+
+ switch (version.code) {
+ case 'D':
+ stuffp->readcmd = READ2X;
+ stuffp->present = DOUBLE | DOOR | MULTI;
+ break;
+ case 'F':
+ stuffp->readcmd = READ1X;
+ stuffp->present = SINGLE | DOOR | MULTI;
+ break;
+ case 'M':
+ stuffp->readcmd = READ1X;
+ stuffp->present = SINGLE;
+ break;
+ default:
+ stuffp->present = 0;
+ break;
+ }
+
+ stuffp->playcmd = READ1X;
+
+ if (!stuffp->present) {
+ release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+ xwarn("%s=0x%03x,%d: Init failed. No Mitsumi CD-ROM?.\n",
+ MCDX, stuffp->wreg_data, stuffp->irq);
+ kfree(stuffp);
+ put_disk(disk);
+ return 0; /* next drive */
+ }
+
+ xtrace(INIT, "init() register blkdev\n");
+ if (register_blkdev(MAJOR_NR, "mcdx")) {
+ release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+ kfree(stuffp);
+ put_disk(disk);
+ return 1;
+ }
+
+ mcdx_queue = blk_init_queue(do_mcdx_request, &mcdx_lock);
+ if (!mcdx_queue) {
+ unregister_blkdev(MAJOR_NR, "mcdx");
+ release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+ kfree(stuffp);
+ put_disk(disk);
+ return 1;
+ }
+
+ xtrace(INIT, "init() subscribe irq and i/o\n");
+ if (request_irq(stuffp->irq, mcdx_intr, IRQF_DISABLED, "mcdx", stuffp)) {
+ release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+ xwarn("%s=0x%03x,%d: Init failed. Can't get irq (%d).\n",
+ MCDX, stuffp->wreg_data, stuffp->irq, stuffp->irq);
+ stuffp->irq = 0;
+ blk_cleanup_queue(mcdx_queue);
+ kfree(stuffp);
+ put_disk(disk);
+ return 0;
+ }
+
+ xtrace(INIT, "init() get garbage\n");
+ {
+ int i;
+ mcdx_delay(stuffp, HZ / 2);
+ for (i = 100; i; i--)
+ (void) inb(stuffp->rreg_status);
+ }
+
+
+#ifdef WE_KNOW_WHY
+ /* irq 11 -> channel register */
+ outb(0x50, stuffp->wreg_chn);
+#endif
+
+ xtrace(INIT, "init() set non dma but irq mode\n");
+ mcdx_config(stuffp, 1);
+
+ stuffp->info.ops = &mcdx_dops;
+ stuffp->info.speed = 2;
+ stuffp->info.capacity = 1;
+ stuffp->info.handle = stuffp;
+ sprintf(stuffp->info.name, "mcdx%d", drive);
+ disk->major = MAJOR_NR;
+ disk->first_minor = drive;
+ strcpy(disk->disk_name, stuffp->info.name);
+ disk->fops = &mcdx_bdops;
+ disk->flags = GENHD_FL_CD;
+ stuffp->disk = disk;
+
+ sprintf(msg, " mcdx: Mitsumi CD-ROM installed at 0x%03x, irq %d."
+ " (Firmware version %c %x)\n",
+ stuffp->wreg_data, stuffp->irq, version.code, version.ver);
+ mcdx_stuffp[drive] = stuffp;
+ xtrace(INIT, "init() mcdx_stuffp[%d] = %p\n", drive, stuffp);
+ if (register_cdrom(&stuffp->info) != 0) {
+ printk("Cannot register Mitsumi CD-ROM!\n");
+ free_irq(stuffp->irq, NULL);
+ release_region(stuffp->wreg_data, MCDX_IO_SIZE);
+ kfree(stuffp);
+ put_disk(disk);
+ if (unregister_blkdev(MAJOR_NR, "mcdx") != 0)
+ xwarn("cleanup() unregister_blkdev() failed\n");
+ blk_cleanup_queue(mcdx_queue);
+ return 2;
+ }
+ disk->private_data = stuffp;
+ disk->queue = mcdx_queue;
+ add_disk(disk);
+ printk(msg);
+ return 0;
+}
+
+static int __init mcdx_init(void)
+{
+ int drive;
+ xwarn("Version 2.14(hs) \n");
+
+ xwarn("$Id: mcdx.c,v 1.21 1997/01/26 07:12:59 davem Exp $\n");
+
+ /* zero the pointer array */
+ for (drive = 0; drive < MCDX_NDRIVES; drive++)
+ mcdx_stuffp[drive] = NULL;
+
+ /* do the initialisation */
+ for (drive = 0; drive < MCDX_NDRIVES; drive++) {
+ switch (mcdx_init_drive(drive)) {
+ case 2:
+ return -EIO;
+ case 1:
+ break;
+ }
+ }
+ return 0;
+}
+
+static int mcdx_transfer(struct s_drive_stuff *stuffp,
+ char *p, int sector, int nr_sectors)
+/* This seems to do the actually transfer. But it does more. It
+ keeps track of errors occurred and will (if possible) fall back
+ to single speed on error.
+ Return: -1 on timeout or other error
+ else status byte (as in stuff->st) */
+{
+ int ans;
+
+ ans = mcdx_xfer(stuffp, p, sector, nr_sectors);
+ return ans;
+#ifdef FALLBACK
+ if (-1 == ans)
+ stuffp->readerrs++;
+ else
+ return ans;
+
+ if (stuffp->readerrs && stuffp->readcmd == READ1X) {
+ xwarn("XXX Already reading 1x -- no chance\n");
+ return -1;
+ }
+
+ xwarn("XXX Fallback to 1x\n");
+
+ stuffp->readcmd = READ1X;
+ return mcdx_transfer(stuffp, p, sector, nr_sectors);
+#endif
+
+}
+
+
+static int mcdx_xfer(struct s_drive_stuff *stuffp,
+ char *p, int sector, int nr_sectors)
+/* This does actually the transfer from the drive.
+ Return: -1 on timeout or other error
+ else status byte (as in stuff->st) */
+{
+ int border;
+ int done = 0;
+ long timeout;
+
+ if (stuffp->audio) {
+ xwarn("Attempt to read from audio CD.\n");
+ return -1;
+ }
+
+ if (!stuffp->readcmd) {
+ xinfo("Can't transfer from missing disk.\n");
+ return -1;
+ }
+
+ while (stuffp->lock) {
+ interruptible_sleep_on(&stuffp->lockq);
+ }
+
+ if (stuffp->valid && (sector >= stuffp->pending)
+ && (sector < stuffp->low_border)) {
+
+ /* All (or at least a part of the sectors requested) seems
+ * to be already requested, so we don't need to bother the
+ * drive with new requests ...
+ * Wait for the drive become idle, but first
+ * check for possible occurred errors --- the drive
+ * seems to report them asynchronously */
+
+
+ border = stuffp->high_border < (border =
+ sector + nr_sectors)
+ ? stuffp->high_border : border;
+
+ stuffp->lock = current->pid;
+
+ do {
+
+ while (stuffp->busy) {
+
+ timeout =
+ interruptible_sleep_on_timeout
+ (&stuffp->busyq, 5 * HZ);
+
+ if (!stuffp->introk) {
+ xtrace(XFER,
+ "error via interrupt\n");
+ } else if (!timeout) {
+ xtrace(XFER, "timeout\n");
+ } else if (signal_pending(current)) {
+ xtrace(XFER, "signal\n");
+ } else
+ continue;
+
+ stuffp->lock = 0;
+ stuffp->busy = 0;
+ stuffp->valid = 0;
+
+ wake_up_interruptible(&stuffp->lockq);
+ xtrace(XFER, "transfer() done (-1)\n");
+ return -1;
+ }
+
+ /* check if we need to set the busy flag (as we
+ * expect an interrupt */
+ stuffp->busy = (3 == (stuffp->pending & 3));
+
+ /* Test if it's the first sector of a block,
+ * there we have to skip some bytes as we read raw data */
+ if (stuffp->xa && (0 == (stuffp->pending & 3))) {
+ const int HEAD =
+ CD_FRAMESIZE_RAW - CD_XA_TAIL -
+ CD_FRAMESIZE;
+ insb(stuffp->rreg_data, p, HEAD);
+ }
+
+ /* now actually read the data */
+ insb(stuffp->rreg_data, p, 512);
+
+ /* test if it's the last sector of a block,
+ * if so, we have to handle XA special */
+ if ((3 == (stuffp->pending & 3)) && stuffp->xa) {
+ char dummy[CD_XA_TAIL];
+ insb(stuffp->rreg_data, &dummy[0], CD_XA_TAIL);
+ }
+
+ if (stuffp->pending == sector) {
+ p += 512;
+ done++;
+ sector++;
+ }
+ } while (++(stuffp->pending) < border);
+
+ stuffp->lock = 0;
+ wake_up_interruptible(&stuffp->lockq);
+
+ } else {
+
+ /* The requested sector(s) is/are out of the
+ * already requested range, so we have to bother the drive
+ * with a new request. */
+
+ static unsigned char cmd[] = {
+ 0,
+ 0, 0, 0,
+ 0, 0, 0
+ };
+
+ cmd[0] = stuffp->readcmd;
+
+ /* The numbers held in ->pending, ..., should be valid */
+ stuffp->valid = 1;
+ stuffp->pending = sector & ~3;
+
+ /* do some sanity checks */
+ if (stuffp->pending > stuffp->lastsector) {
+ xwarn
+ ("transfer() sector %d from nirvana requested.\n",
+ stuffp->pending);
+ stuffp->status = MCDX_ST_EOM;
+ stuffp->valid = 0;
+ xtrace(XFER, "transfer() done (-1)\n");
+ return -1;
+ }
+
+ if ((stuffp->low_border = stuffp->pending + DIRECT_SIZE)
+ > stuffp->lastsector + 1) {
+ xtrace(XFER, "cut low_border\n");
+ stuffp->low_border = stuffp->lastsector + 1;
+ }
+ if ((stuffp->high_border = stuffp->pending + REQUEST_SIZE)
+ > stuffp->lastsector + 1) {
+ xtrace(XFER, "cut high_border\n");
+ stuffp->high_border = stuffp->lastsector + 1;
+ }
+
+ { /* Convert the sector to be requested to MSF format */
+ struct cdrom_msf0 pending;
+ log2msf(stuffp->pending / 4, &pending);
+ cmd[1] = pending.minute;
+ cmd[2] = pending.second;
+ cmd[3] = pending.frame;
+ }
+
+ cmd[6] =
+ (unsigned
+ char) ((stuffp->high_border - stuffp->pending) / 4);
+ xtrace(XFER, "[%2d]\n", cmd[6]);
+
+ stuffp->busy = 1;
+ /* Now really issue the request command */
+ outsb(stuffp->wreg_data, cmd, sizeof cmd);
+
+ }
+#ifdef AK2
+ if (stuffp->int_err) {
+ stuffp->valid = 0;
+ stuffp->int_err = 0;
+ return -1;
+ }
+#endif /* AK2 */
+
+ stuffp->low_border = (stuffp->low_border +=
+ done) <
+ stuffp->high_border ? stuffp->low_border : stuffp->high_border;
+
+ return done;
+}
+
+
+/* Access to elements of the mcdx_drive_map members */
+
+static unsigned port(int *ip)
+{
+ return ip[0];
+}
+static int irq(int *ip)
+{
+ return ip[1];
+}
+
+/* Misc number converters */
+
+static unsigned int bcd2uint(unsigned char c)
+{
+ return (c >> 4) * 10 + (c & 0x0f);
+}
+
+static unsigned int uint2bcd(unsigned int ival)
+{
+ return ((ival / 10) << 4) | (ival % 10);
+}
+
+static void log2msf(unsigned int l, struct cdrom_msf0 *pmsf)
+{
+ l += CD_MSF_OFFSET;
+ pmsf->minute = uint2bcd(l / 4500), l %= 4500;
+ pmsf->second = uint2bcd(l / 75);
+ pmsf->frame = uint2bcd(l % 75);
+}
+
+static unsigned int msf2log(const struct cdrom_msf0 *pmsf)
+{
+ return bcd2uint(pmsf->frame)
+ + bcd2uint(pmsf->second) * 75
+ + bcd2uint(pmsf->minute) * 4500 - CD_MSF_OFFSET;
+}
+
+int mcdx_readtoc(struct s_drive_stuff *stuffp)
+/* Read the toc entries from the CD,
+ * Return: -1 on failure, else 0 */
+{
+
+ if (stuffp->toc) {
+ xtrace(READTOC, "ioctl() toc already read\n");
+ return 0;
+ }
+
+ xtrace(READTOC, "ioctl() readtoc for %d tracks\n",
+ stuffp->di.n_last - stuffp->di.n_first + 1);
+
+ if (-1 == mcdx_hold(stuffp, 1))
+ return -1;
+
+ xtrace(READTOC, "ioctl() tocmode\n");
+ if (-1 == mcdx_setdrivemode(stuffp, TOC, 1))
+ return -EIO;
+
+ /* all seems to be ok so far ... malloc */
+ {
+ int size;
+ size =
+ sizeof(struct s_subqcode) * (stuffp->di.n_last -
+ stuffp->di.n_first + 2);
+
+ xtrace(MALLOC, "ioctl() malloc %d bytes\n", size);
+ stuffp->toc = kmalloc(size, GFP_KERNEL);
+ if (!stuffp->toc) {
+ xwarn("Cannot malloc %d bytes for toc\n", size);
+ mcdx_setdrivemode(stuffp, DATA, 1);
+ return -EIO;
+ }
+ }
+
+ /* now read actually the index */
+ {
+ int trk;
+ int retries;
+
+ for (trk = 0;
+ trk < (stuffp->di.n_last - stuffp->di.n_first + 1);
+ trk++)
+ stuffp->toc[trk].index = 0;
+
+ for (retries = 300; retries; retries--) { /* why 300? */
+ struct s_subqcode q;
+ unsigned int idx;
+
+ if (-1 == mcdx_requestsubqcode(stuffp, &q, 1)) {
+ mcdx_setdrivemode(stuffp, DATA, 1);
+ return -EIO;
+ }
+
+ idx = bcd2uint(q.index);
+
+ if ((idx > 0)
+ && (idx <= stuffp->di.n_last)
+ && (q.tno == 0)
+ && (stuffp->toc[idx - stuffp->di.n_first].
+ index == 0)) {
+ stuffp->toc[idx - stuffp->di.n_first] = q;
+ xtrace(READTOC,
+ "ioctl() toc idx %d (trk %d)\n",
+ idx, trk);
+ trk--;
+ }
+ if (trk == 0)
+ break;
+ }
+ memset(&stuffp->
+ toc[stuffp->di.n_last - stuffp->di.n_first + 1], 0,
+ sizeof(stuffp->toc[0]));
+ stuffp->toc[stuffp->di.n_last - stuffp->di.n_first +
+ 1].dt = stuffp->di.msf_leadout;
+ }
+
+ /* unset toc mode */
+ xtrace(READTOC, "ioctl() undo toc mode\n");
+ if (-1 == mcdx_setdrivemode(stuffp, DATA, 2))
+ return -EIO;
+
+#if MCDX_DEBUG && READTOC
+ {
+ int trk;
+ for (trk = 0;
+ trk < (stuffp->di.n_last - stuffp->di.n_first + 2);
+ trk++)
+ xtrace(READTOC, "ioctl() %d readtoc %02x %02x %02x"
+ " %02x:%02x.%02x %02x:%02x.%02x\n",
+ trk + stuffp->di.n_first,
+ stuffp->toc[trk].control,
+ stuffp->toc[trk].tno,
+ stuffp->toc[trk].index,
+ stuffp->toc[trk].tt.minute,
+ stuffp->toc[trk].tt.second,
+ stuffp->toc[trk].tt.frame,
+ stuffp->toc[trk].dt.minute,
+ stuffp->toc[trk].dt.second,
+ stuffp->toc[trk].dt.frame);
+ }
+#endif
+
+ return 0;
+}
+
+static int
+mcdx_playmsf(struct s_drive_stuff *stuffp, const struct cdrom_msf *msf)
+{
+ unsigned char cmd[7] = {
+ 0, 0, 0, 0, 0, 0, 0
+ };
+
+ if (!stuffp->readcmd) {
+ xinfo("Can't play from missing disk.\n");
+ return -1;
+ }
+
+ cmd[0] = stuffp->playcmd;
+
+ cmd[1] = msf->cdmsf_min0;
+ cmd[2] = msf->cdmsf_sec0;
+ cmd[3] = msf->cdmsf_frame0;
+ cmd[4] = msf->cdmsf_min1;
+ cmd[5] = msf->cdmsf_sec1;
+ cmd[6] = msf->cdmsf_frame1;
+
+ xtrace(PLAYMSF, "ioctl(): play %x "
+ "%02x:%02x:%02x -- %02x:%02x:%02x\n",
+ cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6]);
+
+ outsb(stuffp->wreg_data, cmd, sizeof cmd);
+
+ if (-1 == mcdx_getval(stuffp, 3 * HZ, 0, NULL)) {
+ xwarn("playmsf() timeout\n");
+ return -1;
+ }
+
+ stuffp->audiostatus = CDROM_AUDIO_PLAY;
+ return 0;
+}
+
+static int
+mcdx_playtrk(struct s_drive_stuff *stuffp, const struct cdrom_ti *ti)
+{
+ struct s_subqcode *p;
+ struct cdrom_msf msf;
+
+ if (-1 == mcdx_readtoc(stuffp))
+ return -1;
+
+ if (ti)
+ p = &stuffp->toc[ti->cdti_trk0 - stuffp->di.n_first];
+ else
+ p = &stuffp->start;
+
+ msf.cdmsf_min0 = p->dt.minute;
+ msf.cdmsf_sec0 = p->dt.second;
+ msf.cdmsf_frame0 = p->dt.frame;
+
+ if (ti) {
+ p = &stuffp->toc[ti->cdti_trk1 - stuffp->di.n_first + 1];
+ stuffp->stop = *p;
+ } else
+ p = &stuffp->stop;
+
+ msf.cdmsf_min1 = p->dt.minute;
+ msf.cdmsf_sec1 = p->dt.second;
+ msf.cdmsf_frame1 = p->dt.frame;
+
+ return mcdx_playmsf(stuffp, &msf);
+}
+
+
+/* Drive functions ************************************************/
+
+static int mcdx_tray_move(struct cdrom_device_info *cdi, int position)
+{
+ struct s_drive_stuff *stuffp = cdi->handle;
+
+ if (!stuffp->present)
+ return -ENXIO;
+ if (!(stuffp->present & DOOR))
+ return -ENOSYS;
+
+ if (position) /* 1: eject */
+ return mcdx_talk(stuffp, "\xf6", 1, NULL, 1, 5 * HZ, 3);
+ else /* 0: close */
+ return mcdx_talk(stuffp, "\xf8", 1, NULL, 1, 5 * HZ, 3);
+ return 1;
+}
+
+static int mcdx_stop(struct s_drive_stuff *stuffp, int tries)
+{
+ return mcdx_talk(stuffp, "\xf0", 1, NULL, 1, 2 * HZ, tries);
+}
+
+static int mcdx_hold(struct s_drive_stuff *stuffp, int tries)
+{
+ return mcdx_talk(stuffp, "\x70", 1, NULL, 1, 2 * HZ, tries);
+}
+
+static int mcdx_requestsubqcode(struct s_drive_stuff *stuffp,
+ struct s_subqcode *sub, int tries)
+{
+ char buf[11];
+ int ans;
+
+ if (-1 == (ans = mcdx_talk(stuffp, "\x20", 1, buf, sizeof(buf),
+ 2 * HZ, tries)))
+ return -1;
+ sub->control = buf[1];
+ sub->tno = buf[2];
+ sub->index = buf[3];
+ sub->tt.minute = buf[4];
+ sub->tt.second = buf[5];
+ sub->tt.frame = buf[6];
+ sub->dt.minute = buf[8];
+ sub->dt.second = buf[9];
+ sub->dt.frame = buf[10];
+
+ return ans;
+}
+
+static int mcdx_requestmultidiskinfo(struct s_drive_stuff *stuffp,
+ struct s_multi *multi, int tries)
+{
+ char buf[5];
+ int ans;
+
+ if (stuffp->present & MULTI) {
+ ans =
+ mcdx_talk(stuffp, "\x11", 1, buf, sizeof(buf), 2 * HZ,
+ tries);
+ multi->multi = buf[1];
+ multi->msf_last.minute = buf[2];
+ multi->msf_last.second = buf[3];
+ multi->msf_last.frame = buf[4];
+ return ans;
+ } else {
+ multi->multi = 0;
+ return 0;
+ }
+}
+
+static int mcdx_requesttocdata(struct s_drive_stuff *stuffp, struct s_diskinfo *info,
+ int tries)
+{
+ char buf[9];
+ int ans;
+ ans =
+ mcdx_talk(stuffp, "\x10", 1, buf, sizeof(buf), 2 * HZ, tries);
+ if (ans == -1) {
+ info->n_first = 0;
+ info->n_last = 0;
+ } else {
+ info->n_first = bcd2uint(buf[1]);
+ info->n_last = bcd2uint(buf[2]);
+ info->msf_leadout.minute = buf[3];
+ info->msf_leadout.second = buf[4];
+ info->msf_leadout.frame = buf[5];
+ info->msf_first.minute = buf[6];
+ info->msf_first.second = buf[7];
+ info->msf_first.frame = buf[8];
+ }
+ return ans;
+}
+
+static int mcdx_setdrivemode(struct s_drive_stuff *stuffp, enum drivemodes mode,
+ int tries)
+{
+ char cmd[2];
+ int ans;
+
+ xtrace(HW, "setdrivemode() %d\n", mode);
+
+ if (-1 == (ans = mcdx_talk(stuffp, "\xc2", 1, cmd, sizeof(cmd), 5 * HZ, tries)))
+ return -1;
+
+ switch (mode) {
+ case TOC:
+ cmd[1] |= 0x04;
+ break;
+ case DATA:
+ cmd[1] &= ~0x04;
+ break;
+ case RAW:
+ cmd[1] |= 0x40;
+ break;
+ case COOKED:
+ cmd[1] &= ~0x40;
+ break;
+ default:
+ break;
+ }
+ cmd[0] = 0x50;
+ return mcdx_talk(stuffp, cmd, 2, NULL, 1, 5 * HZ, tries);
+}
+
+static int mcdx_setdatamode(struct s_drive_stuff *stuffp, enum datamodes mode,
+ int tries)
+{
+ unsigned char cmd[2] = { 0xa0 };
+ xtrace(HW, "setdatamode() %d\n", mode);
+ switch (mode) {
+ case MODE0:
+ cmd[1] = 0x00;
+ break;
+ case MODE1:
+ cmd[1] = 0x01;
+ break;
+ case MODE2:
+ cmd[1] = 0x02;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return mcdx_talk(stuffp, cmd, 2, NULL, 1, 5 * HZ, tries);
+}
+
+static int mcdx_config(struct s_drive_stuff *stuffp, int tries)
+{
+ char cmd[4];
+
+ xtrace(HW, "config()\n");
+
+ cmd[0] = 0x90;
+
+ cmd[1] = 0x10; /* irq enable */
+ cmd[2] = 0x05; /* pre, err irq enable */
+
+ if (-1 == mcdx_talk(stuffp, cmd, 3, NULL, 1, 1 * HZ, tries))
+ return -1;
+
+ cmd[1] = 0x02; /* dma select */
+ cmd[2] = 0x00; /* no dma */
+
+ return mcdx_talk(stuffp, cmd, 3, NULL, 1, 1 * HZ, tries);
+}
+
+static int mcdx_requestversion(struct s_drive_stuff *stuffp, struct s_version *ver,
+ int tries)
+{
+ char buf[3];
+ int ans;
+
+ if (-1 == (ans = mcdx_talk(stuffp, "\xdc",
+ 1, buf, sizeof(buf), 2 * HZ, tries)))
+ return ans;
+
+ ver->code = buf[1];
+ ver->ver = buf[2];
+
+ return ans;
+}
+
+static int mcdx_reset(struct s_drive_stuff *stuffp, enum resetmodes mode, int tries)
+{
+ if (mode == HARD) {
+ outb(0, stuffp->wreg_chn); /* no dma, no irq -> hardware */
+ outb(0, stuffp->wreg_reset); /* hw reset */
+ return 0;
+ } else
+ return mcdx_talk(stuffp, "\x60", 1, NULL, 1, 5 * HZ, tries);
+}
+
+static int mcdx_lockdoor(struct cdrom_device_info *cdi, int lock)
+{
+ struct s_drive_stuff *stuffp = cdi->handle;
+ char cmd[2] = { 0xfe };
+
+ if (!(stuffp->present & DOOR))
+ return -ENOSYS;
+ if (stuffp->present & DOOR) {
+ cmd[1] = lock ? 0x01 : 0x00;
+ return mcdx_talk(stuffp, cmd, sizeof(cmd), NULL, 1, 5 * HZ, 3);
+ } else
+ return 0;
+}
+
+static int mcdx_getstatus(struct s_drive_stuff *stuffp, int tries)
+{
+ return mcdx_talk(stuffp, "\x40", 1, NULL, 1, 5 * HZ, tries);
+}
+
+static int
+mcdx_getval(struct s_drive_stuff *stuffp, int to, int delay, char *buf)
+{
+ unsigned long timeout = to + jiffies;
+ char c;
+
+ if (!buf)
+ buf = &c;
+
+ while (inb(stuffp->rreg_status) & MCDX_RBIT_STEN) {
+ if (time_after(jiffies, timeout))
+ return -1;
+ mcdx_delay(stuffp, delay);
+ }
+
+ *buf = (unsigned char) inb(stuffp->rreg_data) & 0xff;
+
+ return 0;
+}
+
+static int mcdx_setattentuator(struct s_drive_stuff *stuffp,
+ struct cdrom_volctrl *vol, int tries)
+{
+ char cmd[5];
+ cmd[0] = 0xae;
+ cmd[1] = vol->channel0;
+ cmd[2] = 0;
+ cmd[3] = vol->channel1;
+ cmd[4] = 0;
+
+ return mcdx_talk(stuffp, cmd, sizeof(cmd), NULL, 5, 200, tries);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_BLOCKDEV_MAJOR(MITSUMI_X_CDROM_MAJOR);
diff --git a/trunk/drivers/cdrom/mcdx.h b/trunk/drivers/cdrom/mcdx.h
new file mode 100644
index 000000000000..83c364a74dc4
--- /dev/null
+++ b/trunk/drivers/cdrom/mcdx.h
@@ -0,0 +1,185 @@
+/*
+ * Definitions for the Mitsumi CDROM interface
+ * Copyright (C) 1995 1996 Heiko Schlittermann
+ * VERSION: @VERSION@
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Thanks to
+ * The Linux Community at all and ...
+ * Martin Harris (he wrote the first Mitsumi Driver)
+ * Eberhard Moenkeberg (he gave me much support and the initial kick)
+ * Bernd Huebner, Ruediger Helsch (Unifix-Software Gmbh, they
+ * improved the original driver)
+ * Jon Tombs, Bjorn Ekwall (module support)
+ * Daniel v. Mosnenck (he sent me the Technical and Programming Reference)
+ * Gerd Knorr (he lent me his PhotoCD)
+ * Nils Faerber and Roger E. Wolff (extensively tested the LU portion)
+ * Andreas Kies (testing the mysterious hang up's)
+ * ... somebody forgotten?
+ * Marcin Dalecki
+ *
+ */
+
+/*
+ * The following lines are for user configuration
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * {0|1} -- 1 if you want the driver detect your drive, may crash and
+ * needs a long time to seek. The higher the address the longer the
+ * seek.
+ *
+ * WARNING: AUTOPROBE doesn't work.
+ */
+#define MCDX_AUTOPROBE 0
+
+/*
+ * Drive specific settings according to the jumpers on the controller
+ * board(s).
+ * o MCDX_NDRIVES : number of used entries of the following table
+ * o MCDX_DRIVEMAP : table of {i/o base, irq} per controller
+ *
+ * NOTE: I didn't get a drive at irq 9(2) working. Not even alone.
+ */
+#if MCDX_AUTOPROBE == 0
+ #define MCDX_NDRIVES 1
+ #define MCDX_DRIVEMAP { \
+ {0x300, 11}, \
+ {0x304, 05}, \
+ {0x000, 00}, \
+ {0x000, 00}, \
+ {0x000, 00}, \
+ }
+#else
+ #error Autoprobing is not implemented yet.
+#endif
+
+#ifndef MCDX_QUIET
+#define MCDX_QUIET 1
+#endif
+
+#ifndef MCDX_DEBUG
+#define MCDX_DEBUG 0
+#endif
+
+/* *** make the following line uncommented, if you're sure,
+ * *** all configuration is done */
+/* #define I_WAS_HERE */
+
+/* The name of the device */
+#define MCDX "mcdx"
+
+/* Flags for DEBUGGING */
+#define INIT 0
+#define MALLOC 0
+#define IOCTL 0
+#define PLAYTRK 0
+#define SUBCHNL 0
+#define TOCHDR 0
+#define MS 0
+#define PLAYMSF 0
+#define READTOC 0
+#define OPENCLOSE 0
+#define HW 0
+#define TALK 0
+#define IRQ 0
+#define XFER 0
+#define REQUEST 0
+#define SLEEP 0
+
+/* The following addresses are taken from the Mitsumi Reference
+ * and describe the possible i/o range for the controller.
+ */
+#define MCDX_IO_BEGIN ((char*) 0x300) /* first base of i/o addr */
+#define MCDX_IO_END ((char*) 0x3fc) /* last base of i/o addr */
+
+/* Per controller 4 bytes i/o are needed. */
+#define MCDX_IO_SIZE 4
+
+/*
+ * Bits
+ */
+
+/* The status byte, returned from every command, set if
+ * the description is true */
+#define MCDX_RBIT_OPEN 0x80 /* door is open */
+#define MCDX_RBIT_DISKSET 0x40 /* disk set (recognised) */
+#define MCDX_RBIT_CHANGED 0x20 /* disk was changed */
+#define MCDX_RBIT_CHECK 0x10 /* disk rotates, servo is on */
+#define MCDX_RBIT_AUDIOTR 0x08 /* current track is audio */
+#define MCDX_RBIT_RDERR 0x04 /* read error, refer SENSE KEY */
+#define MCDX_RBIT_AUDIOBS 0x02 /* currently playing audio */
+#define MCDX_RBIT_CMDERR 0x01 /* command, param or format error */
+
+/* The I/O Register holding the h/w status of the drive,
+ * can be read at i/o base + 1 */
+#define MCDX_RBIT_DOOR 0x10 /* door is open */
+#define MCDX_RBIT_STEN 0x04 /* if 0, i/o base contains drive status */
+#define MCDX_RBIT_DTEN 0x02 /* if 0, i/o base contains data */
+
+/*
+ * The commands.
+ */
+
+#define OPCODE 1 /* offset of opcode */
+#define MCDX_CMD_REQUEST_TOC 1, 0x10
+#define MCDX_CMD_REQUEST_STATUS 1, 0x40
+#define MCDX_CMD_RESET 1, 0x60
+#define MCDX_CMD_REQUEST_DRIVE_MODE 1, 0xc2
+#define MCDX_CMD_SET_INTERLEAVE 2, 0xc8, 0
+#define MCDX_CMD_DATAMODE_SET 2, 0xa0, 0
+ #define MCDX_DATAMODE1 0x01
+ #define MCDX_DATAMODE2 0x02
+#define MCDX_CMD_LOCK_DOOR 2, 0xfe, 0
+
+#define READ_AHEAD 4 /* 8 Sectors (4K) */
+
+/* Useful macros */
+#define e_door(x) ((x) & MCDX_RBIT_OPEN)
+#define e_check(x) (~(x) & MCDX_RBIT_CHECK)
+#define e_notset(x) (~(x) & MCDX_RBIT_DISKSET)
+#define e_changed(x) ((x) & MCDX_RBIT_CHANGED)
+#define e_audio(x) ((x) & MCDX_RBIT_AUDIOTR)
+#define e_audiobusy(x) ((x) & MCDX_RBIT_AUDIOBS)
+#define e_cmderr(x) ((x) & MCDX_RBIT_CMDERR)
+#define e_readerr(x) ((x) & MCDX_RBIT_RDERR)
+
+/** no drive specific */
+#define MCDX_CDBLK 2048 /* 2048 cooked data each blk */
+
+#define MCDX_DATA_TIMEOUT (HZ/10) /* 0.1 second */
+
+/*
+ * Access to the msf array
+ */
+#define MSF_MIN 0 /* minute */
+#define MSF_SEC 1 /* second */
+#define MSF_FRM 2 /* frame */
+
+/*
+ * Errors
+ */
+#define MCDX_E 1 /* unspec error */
+#define MCDX_ST_EOM 0x0100 /* end of media */
+#define MCDX_ST_DRV 0x00ff /* mask to query the drive status */
+
+#ifndef I_WAS_HERE
+#ifndef MODULE
+#warning You have not edited mcdx.h
+#warning Perhaps irq and i/o settings are wrong.
+#endif
+#endif
+
+/* ex:set ts=4 sw=4: */
diff --git a/trunk/drivers/cdrom/optcd.c b/trunk/drivers/cdrom/optcd.c
new file mode 100644
index 000000000000..3541690a77d4
--- /dev/null
+++ b/trunk/drivers/cdrom/optcd.c
@@ -0,0 +1,2105 @@
+/* linux/drivers/cdrom/optcd.c - Optics Storage 8000 AT CDROM driver
+ $Id: optcd.c,v 1.11 1997/01/26 07:13:00 davem Exp $
+
+ Copyright (C) 1995 Leo Spiekman (spiekman@dutette.et.tudelft.nl)
+
+
+ Based on Aztech CD268 CDROM driver by Werner Zimmermann and preworks
+ by Eberhard Moenkeberg (emoenke@gwdg.de).
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+/* Revision history
+
+
+ 14-5-95 v0.0 Plays sound tracks. No reading of data CDs yet.
+ Detection of disk change doesn't work.
+ 21-5-95 v0.1 First ALPHA version. CD can be mounted. The
+ device major nr is borrowed from the Aztech
+ driver. Speed is around 240 kb/s, as measured
+ with "time dd if=/dev/cdrom of=/dev/null \
+ bs=2048 count=4096".
+ 24-6-95 v0.2 Reworked the #defines for the command codes
+ and the like, as well as the structure of
+ the hardware communication protocol, to
+ reflect the "official" documentation, kindly
+ supplied by C.K. Tan, Optics Storage Pte. Ltd.
+ Also tidied up the state machine somewhat.
+ 28-6-95 v0.3 Removed the ISP-16 interface code, as this
+ should go into its own driver. The driver now
+ has its own major nr.
+ Disk change detection now seems to work, too.
+ This version became part of the standard
+ kernel as of version 1.3.7
+ 24-9-95 v0.4 Re-inserted ISP-16 interface code which I
+ copied from sjcd.c, with a few changes.
+ Updated README.optcd. Submitted for
+ inclusion in 1.3.21
+ 29-9-95 v0.4a Fixed bug that prevented compilation as module
+ 25-10-95 v0.5 Started multisession code. Implementation
+ copied from Werner Zimmermann, who copied it
+ from Heiko Schlittermann's mcdx.
+ 17-1-96 v0.6 Multisession works; some cleanup too.
+ 18-4-96 v0.7 Increased some timing constants;
+ thanks to Luke McFarlane. Also tidied up some
+ printk behaviour. ISP16 initialization
+ is now handled by a separate driver.
+
+ 09-11-99 Make kernel-parameter implementation work with 2.3.x
+ Removed init_module & cleanup_module in favor of
+ module_init & module_exit.
+ Torben Mathiasen
+*/
+
+/* Includes */
+
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include "optcd.h"
+
+#include
+
+#define MAJOR_NR OPTICS_CDROM_MAJOR
+#define QUEUE (opt_queue)
+#define CURRENT elv_next_request(opt_queue)
+
+
+/* Debug support */
+
+
+/* Don't forget to add new debug flags here. */
+#if DEBUG_DRIVE_IF | DEBUG_VFS | DEBUG_CONV | DEBUG_TOC | \
+ DEBUG_BUFFERS | DEBUG_REQUEST | DEBUG_STATE | DEBUG_MULTIS
+#define DEBUG(x) debug x
+static void debug(int debug_this, const char* fmt, ...)
+{
+ char s[1024];
+ va_list args;
+
+ if (!debug_this)
+ return;
+
+ va_start(args, fmt);
+ vsnprintf(s, sizeof(s), fmt, args);
+ printk(KERN_DEBUG "optcd: %s\n", s);
+ va_end(args);
+}
+#else
+#define DEBUG(x)
+#endif
+
+
+/* Drive hardware/firmware characteristics
+ Identifiers in accordance with Optics Storage documentation */
+
+
+#define optcd_port optcd /* Needed for the modutils. */
+static short optcd_port = OPTCD_PORTBASE; /* I/O base of drive. */
+module_param(optcd_port, short, 0);
+/* Drive registers, read */
+#define DATA_PORT optcd_port /* Read data/status */
+#define STATUS_PORT optcd_port+1 /* Indicate data/status availability */
+
+/* Drive registers, write */
+#define COMIN_PORT optcd_port /* For passing command/parameter */
+#define RESET_PORT optcd_port+1 /* Write anything and wait 0.5 sec */
+#define HCON_PORT optcd_port+2 /* Host Xfer Configuration */
+
+
+/* Command completion/status read from DATA register */
+#define ST_DRVERR 0x80
+#define ST_DOOR_OPEN 0x40
+#define ST_MIXEDMODE_DISK 0x20
+#define ST_MODE_BITS 0x1c
+#define ST_M_STOP 0x00
+#define ST_M_READ 0x04
+#define ST_M_AUDIO 0x04
+#define ST_M_PAUSE 0x08
+#define ST_M_INITIAL 0x0c
+#define ST_M_ERROR 0x10
+#define ST_M_OTHERS 0x14
+#define ST_MODE2TRACK 0x02
+#define ST_DSK_CHG 0x01
+#define ST_L_LOCK 0x01
+#define ST_CMD_OK 0x00
+#define ST_OP_OK 0x01
+#define ST_PA_OK 0x02
+#define ST_OP_ERROR 0x05
+#define ST_PA_ERROR 0x06
+
+
+/* Error codes (appear as command completion code from DATA register) */
+/* Player related errors */
+#define ERR_ILLCMD 0x11 /* Illegal command to player module */
+#define ERR_ILLPARM 0x12 /* Illegal parameter to player module */
+#define ERR_SLEDGE 0x13
+#define ERR_FOCUS 0x14
+#define ERR_MOTOR 0x15
+#define ERR_RADIAL 0x16
+#define ERR_PLL 0x17 /* PLL lock error */
+#define ERR_SUB_TIM 0x18 /* Subcode timeout error */
+#define ERR_SUB_NF 0x19 /* Subcode not found error */
+#define ERR_TRAY 0x1a
+#define ERR_TOC 0x1b /* Table of Contents read error */
+#define ERR_JUMP 0x1c
+/* Data errors */
+#define ERR_MODE 0x21
+#define ERR_FORM 0x22
+#define ERR_HEADADDR 0x23 /* Header Address not found */
+#define ERR_CRC 0x24
+#define ERR_ECC 0x25 /* Uncorrectable ECC error */
+#define ERR_CRC_UNC 0x26 /* CRC error and uncorrectable error */
+#define ERR_ILLBSYNC 0x27 /* Illegal block sync error */
+#define ERR_VDST 0x28 /* VDST not found */
+/* Timeout errors */
+#define ERR_READ_TIM 0x31 /* Read timeout error */
+#define ERR_DEC_STP 0x32 /* Decoder stopped */
+#define ERR_DEC_TIM 0x33 /* Decoder interrupt timeout error */
+/* Function abort codes */
+#define ERR_KEY 0x41 /* Key -Detected abort */
+#define ERR_READ_FINISH 0x42 /* Read Finish */
+/* Second Byte diagnostic codes */
+#define ERR_NOBSYNC 0x01 /* No block sync */
+#define ERR_SHORTB 0x02 /* Short block */
+#define ERR_LONGB 0x03 /* Long block */
+#define ERR_SHORTDSP 0x04 /* Short DSP word */
+#define ERR_LONGDSP 0x05 /* Long DSP word */
+
+
+/* Status availability flags read from STATUS register */
+#define FL_EJECT 0x20
+#define FL_WAIT 0x10 /* active low */
+#define FL_EOP 0x08 /* active low */
+#define FL_STEN 0x04 /* Status available when low */
+#define FL_DTEN 0x02 /* Data available when low */
+#define FL_DRQ 0x01 /* active low */
+#define FL_RESET 0xde /* These bits are high after a reset */
+#define FL_STDT (FL_STEN|FL_DTEN)
+
+
+/* Transfer mode, written to HCON register */
+#define HCON_DTS 0x08
+#define HCON_SDRQB 0x04
+#define HCON_LOHI 0x02
+#define HCON_DMA16 0x01
+
+
+/* Drive command set, written to COMIN register */
+/* Quick response commands */
+#define COMDRVST 0x20 /* Drive Status Read */
+#define COMERRST 0x21 /* Error Status Read */
+#define COMIOCTLISTAT 0x22 /* Status Read; reset disk changed bit */
+#define COMINITSINGLE 0x28 /* Initialize Single Speed */
+#define COMINITDOUBLE 0x29 /* Initialize Double Speed */
+#define COMUNLOCK 0x30 /* Unlock */
+#define COMLOCK 0x31 /* Lock */
+#define COMLOCKST 0x32 /* Lock/Unlock Status */
+#define COMVERSION 0x40 /* Get Firmware Revision */
+#define COMVOIDREADMODE 0x50 /* Void Data Read Mode */
+/* Read commands */
+#define COMFETCH 0x60 /* Prefetch Data */
+#define COMREAD 0x61 /* Read */
+#define COMREADRAW 0x62 /* Read Raw Data */
+#define COMREADALL 0x63 /* Read All 2646 Bytes */
+/* Player control commands */
+#define COMLEADIN 0x70 /* Seek To Lead-in */
+#define COMSEEK 0x71 /* Seek */
+#define COMPAUSEON 0x80 /* Pause On */
+#define COMPAUSEOFF 0x81 /* Pause Off */
+#define COMSTOP 0x82 /* Stop */
+#define COMOPEN 0x90 /* Open Tray Door */
+#define COMCLOSE 0x91 /* Close Tray Door */
+#define COMPLAY 0xa0 /* Audio Play */
+#define COMPLAY_TNO 0xa2 /* Audio Play By Track Number */
+#define COMSUBQ 0xb0 /* Read Sub-q Code */
+#define COMLOCATION 0xb1 /* Read Head Position */
+/* Audio control commands */
+#define COMCHCTRL 0xc0 /* Audio Channel Control */
+/* Miscellaneous (test) commands */
+#define COMDRVTEST 0xd0 /* Write Test Bytes */
+#define COMTEST 0xd1 /* Diagnostic Test */
+
+/* Low level drive interface. Only here we do actual I/O
+ Waiting for status / data available */
+
+
+/* Busy wait until FLAG goes low. Return 0 on timeout. */
+static inline int flag_low(int flag, unsigned long timeout)
+{
+ int flag_high;
+ unsigned long count = 0;
+
+ while ((flag_high = (inb(STATUS_PORT) & flag)))
+ if (++count >= timeout)
+ break;
+
+ DEBUG((DEBUG_DRIVE_IF, "flag_low 0x%x count %ld%s",
+ flag, count, flag_high ? " timeout" : ""));
+ return !flag_high;
+}
+
+
+/* Timed waiting for status or data */
+static int sleep_timeout; /* max # of ticks to sleep */
+static DECLARE_WAIT_QUEUE_HEAD(waitq);
+static void sleep_timer(unsigned long data);
+static DEFINE_TIMER(delay_timer, sleep_timer, 0, 0);
+static DEFINE_SPINLOCK(optcd_lock);
+static struct request_queue *opt_queue;
+
+/* Timer routine: wake up when desired flag goes low,
+ or when timeout expires. */
+static void sleep_timer(unsigned long data)
+{
+ int flags = inb(STATUS_PORT) & FL_STDT;
+
+ if (flags == FL_STDT && --sleep_timeout > 0) {
+ mod_timer(&delay_timer, jiffies + HZ/100); /* multi-statement macro */
+ } else
+ wake_up(&waitq);
+}
+
+
+/* Sleep until FLAG goes low. Return 0 on timeout or wrong flag low. */
+static int sleep_flag_low(int flag, unsigned long timeout)
+{
+ int flag_high;
+
+ DEBUG((DEBUG_DRIVE_IF, "sleep_flag_low"));
+
+ sleep_timeout = timeout;
+ flag_high = inb(STATUS_PORT) & flag;
+ if (flag_high && sleep_timeout > 0) {
+ mod_timer(&delay_timer, jiffies + HZ/100);
+ sleep_on(&waitq);
+ flag_high = inb(STATUS_PORT) & flag;
+ }
+
+ DEBUG((DEBUG_DRIVE_IF, "flag 0x%x count %ld%s",
+ flag, timeout, flag_high ? " timeout" : ""));
+ return !flag_high;
+}
+
+/* Low level drive interface. Only here we do actual I/O
+ Sending commands and parameters */
+
+
+/* Errors in the command protocol */
+#define ERR_IF_CMD_TIMEOUT 0x100
+#define ERR_IF_ERR_TIMEOUT 0x101
+#define ERR_IF_RESP_TIMEOUT 0x102
+#define ERR_IF_DATA_TIMEOUT 0x103
+#define ERR_IF_NOSTAT 0x104
+
+
+/* Send command code. Return <0 indicates error */
+static int send_cmd(int cmd)
+{
+ unsigned char ack;
+
+ DEBUG((DEBUG_DRIVE_IF, "sending command 0x%02x\n", cmd));
+
+ outb(HCON_DTS, HCON_PORT); /* Enable Suspend Data Transfer */
+ outb(cmd, COMIN_PORT); /* Send command code */
+ if (!flag_low(FL_STEN, BUSY_TIMEOUT)) /* Wait for status */
+ return -ERR_IF_CMD_TIMEOUT;
+ ack = inb(DATA_PORT); /* read command acknowledge */
+ outb(HCON_SDRQB, HCON_PORT); /* Disable Suspend Data Transfer */
+ return ack==ST_OP_OK ? 0 : -ack;
+}
+
+
+/* Send command parameters. Return <0 indicates error */
+static int send_params(struct cdrom_msf *params)
+{
+ unsigned char ack;
+
+ DEBUG((DEBUG_DRIVE_IF, "sending parameters"
+ " %02x:%02x:%02x"
+ " %02x:%02x:%02x",
+ params->cdmsf_min0,
+ params->cdmsf_sec0,
+ params->cdmsf_frame0,
+ params->cdmsf_min1,
+ params->cdmsf_sec1,
+ params->cdmsf_frame1));
+
+ outb(params->cdmsf_min0, COMIN_PORT);
+ outb(params->cdmsf_sec0, COMIN_PORT);
+ outb(params->cdmsf_frame0, COMIN_PORT);
+ outb(params->cdmsf_min1, COMIN_PORT);
+ outb(params->cdmsf_sec1, COMIN_PORT);
+ outb(params->cdmsf_frame1, COMIN_PORT);
+ if (!flag_low(FL_STEN, BUSY_TIMEOUT)) /* Wait for status */
+ return -ERR_IF_CMD_TIMEOUT;
+ ack = inb(DATA_PORT); /* read command acknowledge */
+ return ack==ST_PA_OK ? 0 : -ack;
+}
+
+
+/* Send parameters for SEEK command. Return <0 indicates error */
+static int send_seek_params(struct cdrom_msf *params)
+{
+ unsigned char ack;
+
+ DEBUG((DEBUG_DRIVE_IF, "sending seek parameters"
+ " %02x:%02x:%02x",
+ params->cdmsf_min0,
+ params->cdmsf_sec0,
+ params->cdmsf_frame0));
+
+ outb(params->cdmsf_min0, COMIN_PORT);
+ outb(params->cdmsf_sec0, COMIN_PORT);
+ outb(params->cdmsf_frame0, COMIN_PORT);
+ if (!flag_low(FL_STEN, BUSY_TIMEOUT)) /* Wait for status */
+ return -ERR_IF_CMD_TIMEOUT;
+ ack = inb(DATA_PORT); /* read command acknowledge */
+ return ack==ST_PA_OK ? 0 : -ack;
+}
+
+
+/* Wait for command execution status. Choice between busy waiting
+ and sleeping. Return value <0 indicates timeout. */
+static inline int get_exec_status(int busy_waiting)
+{
+ unsigned char exec_status;
+
+ if (busy_waiting
+ ? !flag_low(FL_STEN, BUSY_TIMEOUT)
+ : !sleep_flag_low(FL_STEN, SLEEP_TIMEOUT))
+ return -ERR_IF_CMD_TIMEOUT;
+
+ exec_status = inb(DATA_PORT);
+ DEBUG((DEBUG_DRIVE_IF, "returned exec status 0x%02x", exec_status));
+ return exec_status;
+}
+
+
+/* Wait busy for extra byte of data that a command returns.
+ Return value <0 indicates timeout. */
+static inline int get_data(int short_timeout)
+{
+ unsigned char data;
+
+ if (!flag_low(FL_STEN, short_timeout ? FAST_TIMEOUT : BUSY_TIMEOUT))
+ return -ERR_IF_DATA_TIMEOUT;
+
+ data = inb(DATA_PORT);
+ DEBUG((DEBUG_DRIVE_IF, "returned data 0x%02x", data));
+ return data;
+}
+
+
+/* Returns 0 if failed */
+static int reset_drive(void)
+{
+ unsigned long count = 0;
+ int flags;
+
+ DEBUG((DEBUG_DRIVE_IF, "reset drive"));
+
+ outb(0, RESET_PORT);
+ while (++count < RESET_WAIT)
+ inb(DATA_PORT);
+
+ count = 0;
+ while ((flags = (inb(STATUS_PORT) & FL_RESET)) != FL_RESET)
+ if (++count >= BUSY_TIMEOUT)
+ break;
+
+ DEBUG((DEBUG_DRIVE_IF, "reset %s",
+ flags == FL_RESET ? "succeeded" : "failed"));
+
+ if (flags != FL_RESET)
+ return 0; /* Reset failed */
+ outb(HCON_SDRQB, HCON_PORT); /* Disable Suspend Data Transfer */
+ return 1; /* Reset succeeded */
+}
+
+
+/* Facilities for asynchronous operation */
+
+/* Read status/data availability flags FL_STEN and FL_DTEN */
+static inline int stdt_flags(void)
+{
+ return inb(STATUS_PORT) & FL_STDT;
+}
+
+
+/* Fetch status that has previously been waited for. <0 means not available */
+static inline int fetch_status(void)
+{
+ unsigned char status;
+
+ if (inb(STATUS_PORT) & FL_STEN)
+ return -ERR_IF_NOSTAT;
+
+ status = inb(DATA_PORT);
+ DEBUG((DEBUG_DRIVE_IF, "fetched exec status 0x%02x", status));
+ return status;
+}
+
+
+/* Fetch data that has previously been waited for. */
+static inline void fetch_data(char *buf, int n)
+{
+ insb(DATA_PORT, buf, n);
+ DEBUG((DEBUG_DRIVE_IF, "fetched 0x%x bytes", n));
+}
+
+
+/* Flush status and data fifos */
+static inline void flush_data(void)
+{
+ while ((inb(STATUS_PORT) & FL_STDT) != FL_STDT)
+ inb(DATA_PORT);
+ DEBUG((DEBUG_DRIVE_IF, "flushed fifos"));
+}
+
+/* Command protocol */
+
+
+/* Send a simple command and wait for response. Command codes < COMFETCH
+ are quick response commands */
+static inline int exec_cmd(int cmd)
+{
+ int ack = send_cmd(cmd);
+ if (ack < 0)
+ return ack;
+ return get_exec_status(cmd < COMFETCH);
+}
+
+
+/* Send a command with parameters. Don't wait for the response,
+ * which consists of data blocks read from the CD. */
+static inline int exec_read_cmd(int cmd, struct cdrom_msf *params)
+{
+ int ack = send_cmd(cmd);
+ if (ack < 0)
+ return ack;
+ return send_params(params);
+}
+
+
+/* Send a seek command with parameters and wait for response */
+static inline int exec_seek_cmd(int cmd, struct cdrom_msf *params)
+{
+ int ack = send_cmd(cmd);
+ if (ack < 0)
+ return ack;
+ ack = send_seek_params(params);
+ if (ack < 0)
+ return ack;
+ return 0;
+}
+
+
+/* Send a command with parameters and wait for response */
+static inline int exec_long_cmd(int cmd, struct cdrom_msf *params)
+{
+ int ack = exec_read_cmd(cmd, params);
+ if (ack < 0)
+ return ack;
+ return get_exec_status(0);
+}
+
+/* Address conversion routines */
+
+
+/* Binary to BCD (2 digits) */
+static inline void single_bin2bcd(u_char *p)
+{
+ DEBUG((DEBUG_CONV, "bin2bcd %02d", *p));
+ *p = (*p % 10) | ((*p / 10) << 4);
+}
+
+
+/* Convert entire msf struct */
+static void bin2bcd(struct cdrom_msf *msf)
+{
+ single_bin2bcd(&msf->cdmsf_min0);
+ single_bin2bcd(&msf->cdmsf_sec0);
+ single_bin2bcd(&msf->cdmsf_frame0);
+ single_bin2bcd(&msf->cdmsf_min1);
+ single_bin2bcd(&msf->cdmsf_sec1);
+ single_bin2bcd(&msf->cdmsf_frame1);
+}
+
+
+/* Linear block address to minute, second, frame form */
+#define CD_FPM (CD_SECS * CD_FRAMES) /* frames per minute */
+
+static void lba2msf(int lba, struct cdrom_msf *msf)
+{
+ DEBUG((DEBUG_CONV, "lba2msf %d", lba));
+ lba += CD_MSF_OFFSET;
+ msf->cdmsf_min0 = lba / CD_FPM; lba %= CD_FPM;
+ msf->cdmsf_sec0 = lba / CD_FRAMES;
+ msf->cdmsf_frame0 = lba % CD_FRAMES;
+ msf->cdmsf_min1 = 0;
+ msf->cdmsf_sec1 = 0;
+ msf->cdmsf_frame1 = 0;
+ bin2bcd(msf);
+}
+
+
+/* Two BCD digits to binary */
+static inline u_char bcd2bin(u_char bcd)
+{
+ DEBUG((DEBUG_CONV, "bcd2bin %x%02x", bcd));
+ return (bcd >> 4) * 10 + (bcd & 0x0f);
+}
+
+
+static void msf2lba(union cdrom_addr *addr)
+{
+ addr->lba = addr->msf.minute * CD_FPM
+ + addr->msf.second * CD_FRAMES
+ + addr->msf.frame - CD_MSF_OFFSET;
+}
+
+
+/* Minute, second, frame address BCD to binary or to linear address,
+ depending on MODE */
+static void msf_bcd2bin(union cdrom_addr *addr)
+{
+ addr->msf.minute = bcd2bin(addr->msf.minute);
+ addr->msf.second = bcd2bin(addr->msf.second);
+ addr->msf.frame = bcd2bin(addr->msf.frame);
+}
+
+/* High level drive commands */
+
+
+static int audio_status = CDROM_AUDIO_NO_STATUS;
+static char toc_uptodate = 0;
+static char disk_changed = 1;
+
+/* Get drive status, flagging completion of audio play and disk changes. */
+static int drive_status(void)
+{
+ int status;
+
+ status = exec_cmd(COMIOCTLISTAT);
+ DEBUG((DEBUG_DRIVE_IF, "IOCTLISTAT: %03x", status));
+ if (status < 0)
+ return status;
+ if (status == 0xff) /* No status available */
+ return -ERR_IF_NOSTAT;
+
+ if (((status & ST_MODE_BITS) != ST_M_AUDIO) &&
+ (audio_status == CDROM_AUDIO_PLAY)) {
+ audio_status = CDROM_AUDIO_COMPLETED;
+ }
+
+ if (status & ST_DSK_CHG) {
+ toc_uptodate = 0;
+ disk_changed = 1;
+ audio_status = CDROM_AUDIO_NO_STATUS;
+ }
+
+ return status;
+}
+
+
+/* Read the current Q-channel info. Also used for reading the
+ table of contents. qp->cdsc_format must be set on entry to
+ indicate the desired address format */
+static int get_q_channel(struct cdrom_subchnl *qp)
+{
+ int status, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10;
+
+ status = drive_status();
+ if (status < 0)
+ return status;
+ qp->cdsc_audiostatus = audio_status;
+
+ status = exec_cmd(COMSUBQ);
+ if (status < 0)
+ return status;
+
+ d1 = get_data(0);
+ if (d1 < 0)
+ return d1;
+ qp->cdsc_adr = d1;
+ qp->cdsc_ctrl = d1 >> 4;
+
+ d2 = get_data(0);
+ if (d2 < 0)
+ return d2;
+ qp->cdsc_trk = bcd2bin(d2);
+
+ d3 = get_data(0);
+ if (d3 < 0)
+ return d3;
+ qp->cdsc_ind = bcd2bin(d3);
+
+ d4 = get_data(0);
+ if (d4 < 0)
+ return d4;
+ qp->cdsc_reladdr.msf.minute = d4;
+
+ d5 = get_data(0);
+ if (d5 < 0)
+ return d5;
+ qp->cdsc_reladdr.msf.second = d5;
+
+ d6 = get_data(0);
+ if (d6 < 0)
+ return d6;
+ qp->cdsc_reladdr.msf.frame = d6;
+
+ d7 = get_data(0);
+ if (d7 < 0)
+ return d7;
+ /* byte not used */
+
+ d8 = get_data(0);
+ if (d8 < 0)
+ return d8;
+ qp->cdsc_absaddr.msf.minute = d8;
+
+ d9 = get_data(0);
+ if (d9 < 0)
+ return d9;
+ qp->cdsc_absaddr.msf.second = d9;
+
+ d10 = get_data(0);
+ if (d10 < 0)
+ return d10;
+ qp->cdsc_absaddr.msf.frame = d10;
+
+ DEBUG((DEBUG_TOC, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
+ d1, d2, d3, d4, d5, d6, d7, d8, d9, d10));
+
+ msf_bcd2bin(&qp->cdsc_absaddr);
+ msf_bcd2bin(&qp->cdsc_reladdr);
+ if (qp->cdsc_format == CDROM_LBA) {
+ msf2lba(&qp->cdsc_absaddr);
+ msf2lba(&qp->cdsc_reladdr);
+ }
+
+ return 0;
+}
+
+/* Table of contents handling */
+
+
+/* Errors in table of contents */
+#define ERR_TOC_MISSINGINFO 0x120
+#define ERR_TOC_MISSINGENTRY 0x121
+
+
+struct cdrom_disk_info {
+ unsigned char first;
+ unsigned char last;
+ struct cdrom_msf0 disk_length;
+ struct cdrom_msf0 first_track;
+ /* Multisession info: */
+ unsigned char next;
+ struct cdrom_msf0 next_session;
+ struct cdrom_msf0 last_session;
+ unsigned char multi;
+ unsigned char xa;
+ unsigned char audio;
+};
+static struct cdrom_disk_info disk_info;
+
+#define MAX_TRACKS 111
+static struct cdrom_subchnl toc[MAX_TRACKS];
+
+#define QINFO_FIRSTTRACK 100 /* bcd2bin(0xa0) */
+#define QINFO_LASTTRACK 101 /* bcd2bin(0xa1) */
+#define QINFO_DISKLENGTH 102 /* bcd2bin(0xa2) */
+#define QINFO_NEXTSESSION 110 /* bcd2bin(0xb0) */
+
+#define I_FIRSTTRACK 0x01
+#define I_LASTTRACK 0x02
+#define I_DISKLENGTH 0x04
+#define I_NEXTSESSION 0x08
+#define I_ALL (I_FIRSTTRACK | I_LASTTRACK | I_DISKLENGTH)
+
+
+#if DEBUG_TOC
+static void toc_debug_info(int i)
+{
+ printk(KERN_DEBUG "#%3d ctl %1x, adr %1x, track %2d index %3d"
+ " %2d:%02d.%02d %2d:%02d.%02d\n",
+ i, toc[i].cdsc_ctrl, toc[i].cdsc_adr,
+ toc[i].cdsc_trk, toc[i].cdsc_ind,
+ toc[i].cdsc_reladdr.msf.minute,
+ toc[i].cdsc_reladdr.msf.second,
+ toc[i].cdsc_reladdr.msf.frame,
+ toc[i].cdsc_absaddr.msf.minute,
+ toc[i].cdsc_absaddr.msf.second,
+ toc[i].cdsc_absaddr.msf.frame);
+}
+#endif
+
+
+static int read_toc(void)
+{
+ int status, limit, count;
+ unsigned char got_info = 0;
+ struct cdrom_subchnl q_info;
+#if DEBUG_TOC
+ int i;
+#endif
+
+ DEBUG((DEBUG_TOC, "starting read_toc"));
+
+ count = 0;
+ for (limit = 60; limit > 0; limit--) {
+ int index;
+
+ q_info.cdsc_format = CDROM_MSF;
+ status = get_q_channel(&q_info);
+ if (status < 0)
+ return status;
+
+ index = q_info.cdsc_ind;
+ if (index > 0 && index < MAX_TRACKS
+ && q_info.cdsc_trk == 0 && toc[index].cdsc_ind == 0) {
+ toc[index] = q_info;
+ DEBUG((DEBUG_TOC, "got %d", index));
+ if (index < 100)
+ count++;
+
+ switch (q_info.cdsc_ind) {
+ case QINFO_FIRSTTRACK:
+ got_info |= I_FIRSTTRACK;
+ break;
+ case QINFO_LASTTRACK:
+ got_info |= I_LASTTRACK;
+ break;
+ case QINFO_DISKLENGTH:
+ got_info |= I_DISKLENGTH;
+ break;
+ case QINFO_NEXTSESSION:
+ got_info |= I_NEXTSESSION;
+ break;
+ }
+ }
+
+ if ((got_info & I_ALL) == I_ALL
+ && toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute + count
+ >= toc[QINFO_LASTTRACK].cdsc_absaddr.msf.minute + 1)
+ break;
+ }
+
+ /* Construct disk_info from TOC */
+ if (disk_info.first == 0) {
+ disk_info.first = toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute;
+ disk_info.first_track.minute =
+ toc[disk_info.first].cdsc_absaddr.msf.minute;
+ disk_info.first_track.second =
+ toc[disk_info.first].cdsc_absaddr.msf.second;
+ disk_info.first_track.frame =
+ toc[disk_info.first].cdsc_absaddr.msf.frame;
+ }
+ disk_info.last = toc[QINFO_LASTTRACK].cdsc_absaddr.msf.minute;
+ disk_info.disk_length.minute =
+ toc[QINFO_DISKLENGTH].cdsc_absaddr.msf.minute;
+ disk_info.disk_length.second =
+ toc[QINFO_DISKLENGTH].cdsc_absaddr.msf.second-2;
+ disk_info.disk_length.frame =
+ toc[QINFO_DISKLENGTH].cdsc_absaddr.msf.frame;
+ disk_info.next_session.minute =
+ toc[QINFO_NEXTSESSION].cdsc_reladdr.msf.minute;
+ disk_info.next_session.second =
+ toc[QINFO_NEXTSESSION].cdsc_reladdr.msf.second;
+ disk_info.next_session.frame =
+ toc[QINFO_NEXTSESSION].cdsc_reladdr.msf.frame;
+ disk_info.next = toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute;
+ disk_info.last_session.minute =
+ toc[disk_info.next].cdsc_absaddr.msf.minute;
+ disk_info.last_session.second =
+ toc[disk_info.next].cdsc_absaddr.msf.second;
+ disk_info.last_session.frame =
+ toc[disk_info.next].cdsc_absaddr.msf.frame;
+ toc[disk_info.last + 1].cdsc_absaddr.msf.minute =
+ disk_info.disk_length.minute;
+ toc[disk_info.last + 1].cdsc_absaddr.msf.second =
+ disk_info.disk_length.second;
+ toc[disk_info.last + 1].cdsc_absaddr.msf.frame =
+ disk_info.disk_length.frame;
+#if DEBUG_TOC
+ for (i = 1; i <= disk_info.last + 1; i++)
+ toc_debug_info(i);
+ toc_debug_info(QINFO_FIRSTTRACK);
+ toc_debug_info(QINFO_LASTTRACK);
+ toc_debug_info(QINFO_DISKLENGTH);
+ toc_debug_info(QINFO_NEXTSESSION);
+#endif
+
+ DEBUG((DEBUG_TOC, "exiting read_toc, got_info %x, count %d",
+ got_info, count));
+ if ((got_info & I_ALL) != I_ALL
+ || toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute + count
+ < toc[QINFO_LASTTRACK].cdsc_absaddr.msf.minute + 1)
+ return -ERR_TOC_MISSINGINFO;
+ return 0;
+}
+
+
+#ifdef MULTISESSION
+static int get_multi_disk_info(void)
+{
+ int sessions, status;
+ struct cdrom_msf multi_index;
+
+
+ for (sessions = 2; sessions < 10 /* %%for now */; sessions++) {
+ int count;
+
+ for (count = 100; count < MAX_TRACKS; count++)
+ toc[count].cdsc_ind = 0;
+
+ multi_index.cdmsf_min0 = disk_info.next_session.minute;
+ multi_index.cdmsf_sec0 = disk_info.next_session.second;
+ multi_index.cdmsf_frame0 = disk_info.next_session.frame;
+ if (multi_index.cdmsf_sec0 >= 20)
+ multi_index.cdmsf_sec0 -= 20;
+ else {
+ multi_index.cdmsf_sec0 += 40;
+ multi_index.cdmsf_min0--;
+ }
+ DEBUG((DEBUG_MULTIS, "Try %d: %2d:%02d.%02d", sessions,
+ multi_index.cdmsf_min0,
+ multi_index.cdmsf_sec0,
+ multi_index.cdmsf_frame0));
+ bin2bcd(&multi_index);
+ multi_index.cdmsf_min1 = 0;
+ multi_index.cdmsf_sec1 = 0;
+ multi_index.cdmsf_frame1 = 1;
+
+ status = exec_read_cmd(COMREAD, &multi_index);
+ if (status < 0) {
+ DEBUG((DEBUG_TOC, "exec_read_cmd COMREAD: %02x",
+ -status));
+ break;
+ }
+ status = sleep_flag_low(FL_DTEN, MULTI_SEEK_TIMEOUT) ?
+ 0 : -ERR_TOC_MISSINGINFO;
+ flush_data();
+ if (status < 0) {
+ DEBUG((DEBUG_TOC, "sleep_flag_low: %02x", -status));
+ break;
+ }
+
+ status = read_toc();
+ if (status < 0) {
+ DEBUG((DEBUG_TOC, "read_toc: %02x", -status));
+ break;
+ }
+
+ disk_info.multi = 1;
+ }
+
+ exec_cmd(COMSTOP);
+
+ if (status < 0)
+ return -EIO;
+ return 0;
+}
+#endif /* MULTISESSION */
+
+
+static int update_toc(void)
+{
+ int status, count;
+
+ if (toc_uptodate)
+ return 0;
+
+ DEBUG((DEBUG_TOC, "starting update_toc"));
+
+ disk_info.first = 0;
+ for (count = 0; count < MAX_TRACKS; count++)
+ toc[count].cdsc_ind = 0;
+
+ status = exec_cmd(COMLEADIN);
+ if (status < 0)
+ return -EIO;
+
+ status = read_toc();
+ if (status < 0) {
+ DEBUG((DEBUG_TOC, "read_toc: %02x", -status));
+ return -EIO;
+ }
+
+ /* Audio disk detection. Look at first track. */
+ disk_info.audio =
+ (toc[disk_info.first].cdsc_ctrl & CDROM_DATA_TRACK) ? 0 : 1;
+
+ /* XA detection */
+ disk_info.xa = drive_status() & ST_MODE2TRACK;
+
+ /* Multisession detection: if we want this, define MULTISESSION */
+ disk_info.multi = 0;
+#ifdef MULTISESSION
+ if (disk_info.xa)
+ get_multi_disk_info(); /* Here disk_info.multi is set */
+#endif /* MULTISESSION */
+ if (disk_info.multi)
+ printk(KERN_WARNING "optcd: Multisession support experimental, "
+ "see Documentation/cdrom/optcd\n");
+
+ DEBUG((DEBUG_TOC, "exiting update_toc"));
+
+ toc_uptodate = 1;
+ return 0;
+}
+
+/* Request handling */
+
+static int current_valid(void)
+{
+ return CURRENT &&
+ CURRENT->cmd == READ &&
+ CURRENT->sector != -1;
+}
+
+/* Buffers for block size conversion. */
+#define NOBUF -1
+
+static char buf[CD_FRAMESIZE * N_BUFS];
+static volatile int buf_bn[N_BUFS], next_bn;
+static volatile int buf_in = 0, buf_out = NOBUF;
+
+static inline void opt_invalidate_buffers(void)
+{
+ int i;
+
+ DEBUG((DEBUG_BUFFERS, "executing opt_invalidate_buffers"));
+
+ for (i = 0; i < N_BUFS; i++)
+ buf_bn[i] = NOBUF;
+ buf_out = NOBUF;
+}
+
+
+/* Take care of the different block sizes between cdrom and Linux.
+ When Linux gets variable block sizes this will probably go away. */
+static void transfer(void)
+{
+#if DEBUG_BUFFERS | DEBUG_REQUEST
+ printk(KERN_DEBUG "optcd: executing transfer\n");
+#endif
+
+ if (!current_valid())
+ return;
+ while (CURRENT -> nr_sectors) {
+ int bn = CURRENT -> sector / 4;
+ int i, offs, nr_sectors;
+ for (i = 0; i < N_BUFS && buf_bn[i] != bn; ++i);
+
+ DEBUG((DEBUG_REQUEST, "found %d", i));
+
+ if (i >= N_BUFS) {
+ buf_out = NOBUF;
+ break;
+ }
+
+ offs = (i * 4 + (CURRENT -> sector & 3)) * 512;
+ nr_sectors = 4 - (CURRENT -> sector & 3);
+
+ if (buf_out != i) {
+ buf_out = i;
+ if (buf_bn[i] != bn) {
+ buf_out = NOBUF;
+ continue;
+ }
+ }
+
+ if (nr_sectors > CURRENT -> nr_sectors)
+ nr_sectors = CURRENT -> nr_sectors;
+ memcpy(CURRENT -> buffer, buf + offs, nr_sectors * 512);
+ CURRENT -> nr_sectors -= nr_sectors;
+ CURRENT -> sector += nr_sectors;
+ CURRENT -> buffer += nr_sectors * 512;
+ }
+}
+
+
+/* State machine for reading disk blocks */
+
+enum state_e {
+ S_IDLE, /* 0 */
+ S_START, /* 1 */
+ S_READ, /* 2 */
+ S_DATA, /* 3 */
+ S_STOP, /* 4 */
+ S_STOPPING /* 5 */
+};
+
+static volatile enum state_e state = S_IDLE;
+#if DEBUG_STATE
+static volatile enum state_e state_old = S_STOP;
+static volatile int flags_old = 0;
+static volatile long state_n = 0;
+#endif
+
+
+/* Used as mutex to keep do_optcd_request (and other processes calling
+ ioctl) out while some process is inside a VFS call.
+ Reverse is accomplished by checking if state = S_IDLE upon entry
+ of opt_ioctl and opt_media_change. */
+static int in_vfs = 0;
+
+
+static volatile int transfer_is_active = 0;
+static volatile int error = 0; /* %% do something with this?? */
+static int tries; /* ibid?? */
+static int timeout = 0;
+
+static void poll(unsigned long data);
+static struct timer_list req_timer = {.function = poll};
+
+
+static void poll(unsigned long data)
+{
+ static volatile int read_count = 1;
+ int flags;
+ int loop_again = 1;
+ int status = 0;
+ int skip = 0;
+
+ if (error) {
+ printk(KERN_ERR "optcd: I/O error 0x%02x\n", error);
+ opt_invalidate_buffers();
+ if (!tries--) {
+ printk(KERN_ERR "optcd: read block %d failed;"
+ " Giving up\n", next_bn);
+ if (transfer_is_active)
+ loop_again = 0;
+ if (current_valid())
+ end_request(CURRENT, 0);
+ tries = 5;
+ }
+ error = 0;
+ state = S_STOP;
+ }
+
+ while (loop_again)
+ {
+ loop_again = 0; /* each case must flip this back to 1 if we want
+ to come back up here */
+
+#if DEBUG_STATE
+ if (state == state_old)
+ state_n++;
+ else {
+ state_old = state;
+ if (++state_n > 1)
+ printk(KERN_DEBUG "optcd: %ld times "
+ "in previous state\n", state_n);
+ printk(KERN_DEBUG "optcd: state %d\n", state);
+ state_n = 0;
+ }
+#endif
+
+ switch (state) {
+ case S_IDLE:
+ return;
+ case S_START:
+ if (in_vfs)
+ break;
+ if (send_cmd(COMDRVST)) {
+ state = S_IDLE;
+ while (current_valid())
+ end_request(CURRENT, 0);
+ return;
+ }
+ state = S_READ;
+ timeout = READ_TIMEOUT;
+ break;
+ case S_READ: {
+ struct cdrom_msf msf;
+ if (!skip) {
+ status = fetch_status();
+ if (status < 0)
+ break;
+ if (status & ST_DSK_CHG) {
+ toc_uptodate = 0;
+ opt_invalidate_buffers();
+ }
+ }
+ skip = 0;
+ if ((status & ST_DOOR_OPEN) || (status & ST_DRVERR)) {
+ toc_uptodate = 0;
+ opt_invalidate_buffers();
+ printk(KERN_WARNING "optcd: %s\n",
+ (status & ST_DOOR_OPEN)
+ ? "door open"
+ : "disk removed");
+ state = S_IDLE;
+ while (current_valid())
+ end_request(CURRENT, 0);
+ return;
+ }
+ if (!current_valid()) {
+ state = S_STOP;
+ loop_again = 1;
+ break;
+ }
+ next_bn = CURRENT -> sector / 4;
+ lba2msf(next_bn, &msf);
+ read_count = N_BUFS;
+ msf.cdmsf_frame1 = read_count; /* Not BCD! */
+
+ DEBUG((DEBUG_REQUEST, "reading %x:%x.%x %x:%x.%x",
+ msf.cdmsf_min0,
+ msf.cdmsf_sec0,
+ msf.cdmsf_frame0,
+ msf.cdmsf_min1,
+ msf.cdmsf_sec1,
+ msf.cdmsf_frame1));
+ DEBUG((DEBUG_REQUEST, "next_bn:%d buf_in:%d"
+ " buf_out:%d buf_bn:%d",
+ next_bn,
+ buf_in,
+ buf_out,
+ buf_bn[buf_in]));
+
+ exec_read_cmd(COMREAD, &msf);
+ state = S_DATA;
+ timeout = READ_TIMEOUT;
+ break;
+ }
+ case S_DATA:
+ flags = stdt_flags() & (FL_STEN|FL_DTEN);
+
+#if DEBUG_STATE
+ if (flags != flags_old) {
+ flags_old = flags;
+ printk(KERN_DEBUG "optcd: flags:%x\n", flags);
+ }
+ if (flags == FL_STEN)
+ printk(KERN_DEBUG "timeout cnt: %d\n", timeout);
+#endif
+
+ switch (flags) {
+ case FL_DTEN: /* only STEN low */
+ if (!tries--) {
+ printk(KERN_ERR
+ "optcd: read block %d failed; "
+ "Giving up\n", next_bn);
+ if (transfer_is_active) {
+ tries = 0;
+ break;
+ }
+ if (current_valid())
+ end_request(CURRENT, 0);
+ tries = 5;
+ }
+ state = S_START;
+ timeout = READ_TIMEOUT;
+ loop_again = 1;
+ case (FL_STEN|FL_DTEN): /* both high */
+ break;
+ default: /* DTEN low */
+ tries = 5;
+ if (!current_valid() && buf_in == buf_out) {
+ state = S_STOP;
+ loop_again = 1;
+ break;
+ }
+ if (read_count<=0)
+ printk(KERN_WARNING
+ "optcd: warning - try to read"
+ " 0 frames\n");
+ while (read_count) {
+ buf_bn[buf_in] = NOBUF;
+ if (!flag_low(FL_DTEN, BUSY_TIMEOUT)) {
+ /* should be no waiting here!?? */
+ printk(KERN_ERR
+ "read_count:%d "
+ "CURRENT->nr_sectors:%ld "
+ "buf_in:%d\n",
+ read_count,
+ CURRENT->nr_sectors,
+ buf_in);
+ printk(KERN_ERR
+ "transfer active: %x\n",
+ transfer_is_active);
+ read_count = 0;
+ state = S_STOP;
+ loop_again = 1;
+ end_request(CURRENT, 0);
+ break;
+ }
+ fetch_data(buf+
+ CD_FRAMESIZE*buf_in,
+ CD_FRAMESIZE);
+ read_count--;
+
+ DEBUG((DEBUG_REQUEST,
+ "S_DATA; ---I've read data- "
+ "read_count: %d",
+ read_count));
+ DEBUG((DEBUG_REQUEST,
+ "next_bn:%d buf_in:%d "
+ "buf_out:%d buf_bn:%d",
+ next_bn,
+ buf_in,
+ buf_out,
+ buf_bn[buf_in]));
+
+ buf_bn[buf_in] = next_bn++;
+ if (buf_out == NOBUF)
+ buf_out = buf_in;
+ buf_in = buf_in + 1 ==
+ N_BUFS ? 0 : buf_in + 1;
+ }
+ if (!transfer_is_active) {
+ while (current_valid()) {
+ transfer();
+ if (CURRENT -> nr_sectors == 0)
+ end_request(CURRENT, 1);
+ else
+ break;
+ }
+ }
+
+ if (current_valid()
+ && (CURRENT -> sector / 4 < next_bn ||
+ CURRENT -> sector / 4 >
+ next_bn + N_BUFS)) {
+ state = S_STOP;
+ loop_again = 1;
+ break;
+ }
+ timeout = READ_TIMEOUT;
+ if (read_count == 0) {
+ state = S_STOP;
+ loop_again = 1;
+ break;
+ }
+ }
+ break;
+ case S_STOP:
+ if (read_count != 0)
+ printk(KERN_ERR
+ "optcd: discard data=%x frames\n",
+ read_count);
+ flush_data();
+ if (send_cmd(COMDRVST)) {
+ state = S_IDLE;
+ while (current_valid())
+ end_request(CURRENT, 0);
+ return;
+ }
+ state = S_STOPPING;
+ timeout = STOP_TIMEOUT;
+ break;
+ case S_STOPPING:
+ status = fetch_status();
+ if (status < 0 && timeout)
+ break;
+ if ((status >= 0) && (status & ST_DSK_CHG)) {
+ toc_uptodate = 0;
+ opt_invalidate_buffers();
+ }
+ if (current_valid()) {
+ if (status >= 0) {
+ state = S_READ;
+ loop_again = 1;
+ skip = 1;
+ break;
+ } else {
+ state = S_START;
+ timeout = 1;
+ }
+ } else {
+ state = S_IDLE;
+ return;
+ }
+ break;
+ default:
+ printk(KERN_ERR "optcd: invalid state %d\n", state);
+ return;
+ } /* case */
+ } /* while */
+
+ if (!timeout--) {
+ printk(KERN_ERR "optcd: timeout in state %d\n", state);
+ state = S_STOP;
+ if (exec_cmd(COMSTOP) < 0) {
+ state = S_IDLE;
+ while (current_valid())
+ end_request(CURRENT, 0);
+ return;
+ }
+ }
+
+ mod_timer(&req_timer, jiffies + HZ/100);
+}
+
+
+static void do_optcd_request(request_queue_t * q)
+{
+ DEBUG((DEBUG_REQUEST, "do_optcd_request(%ld+%ld)",
+ CURRENT -> sector, CURRENT -> nr_sectors));
+
+ if (disk_info.audio) {
+ printk(KERN_WARNING "optcd: tried to mount an Audio CD\n");
+ end_request(CURRENT, 0);
+ return;
+ }
+
+ transfer_is_active = 1;
+ while (current_valid()) {
+ transfer(); /* First try to transfer block from buffers */
+ if (CURRENT -> nr_sectors == 0) {
+ end_request(CURRENT, 1);
+ } else { /* Want to read a block not in buffer */
+ buf_out = NOBUF;
+ if (state == S_IDLE) {
+ /* %% Should this block the request queue?? */
+ if (update_toc() < 0) {
+ while (current_valid())
+ end_request(CURRENT, 0);
+ break;
+ }
+ /* Start state machine */
+ state = S_START;
+ timeout = READ_TIMEOUT;
+ tries = 5;
+ /* %% why not start right away?? */
+ mod_timer(&req_timer, jiffies + HZ/100);
+ }
+ break;
+ }
+ }
+ transfer_is_active = 0;
+
+ DEBUG((DEBUG_REQUEST, "next_bn:%d buf_in:%d buf_out:%d buf_bn:%d",
+ next_bn, buf_in, buf_out, buf_bn[buf_in]));
+ DEBUG((DEBUG_REQUEST, "do_optcd_request ends"));
+}
+
+/* IOCTLs */
+
+
+static char auto_eject = 0;
+
+static int cdrompause(void)
+{
+ int status;
+
+ if (audio_status != CDROM_AUDIO_PLAY)
+ return -EINVAL;
+
+ status = exec_cmd(COMPAUSEON);
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_cmd COMPAUSEON: %02x", -status));
+ return -EIO;
+ }
+ audio_status = CDROM_AUDIO_PAUSED;
+ return 0;
+}
+
+
+static int cdromresume(void)
+{
+ int status;
+
+ if (audio_status != CDROM_AUDIO_PAUSED)
+ return -EINVAL;
+
+ status = exec_cmd(COMPAUSEOFF);
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_cmd COMPAUSEOFF: %02x", -status));
+ audio_status = CDROM_AUDIO_ERROR;
+ return -EIO;
+ }
+ audio_status = CDROM_AUDIO_PLAY;
+ return 0;
+}
+
+
+static int cdromplaymsf(void __user *arg)
+{
+ int status;
+ struct cdrom_msf msf;
+
+ if (copy_from_user(&msf, arg, sizeof msf))
+ return -EFAULT;
+
+ bin2bcd(&msf);
+ status = exec_long_cmd(COMPLAY, &msf);
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_long_cmd COMPLAY: %02x", -status));
+ audio_status = CDROM_AUDIO_ERROR;
+ return -EIO;
+ }
+
+ audio_status = CDROM_AUDIO_PLAY;
+ return 0;
+}
+
+
+static int cdromplaytrkind(void __user *arg)
+{
+ int status;
+ struct cdrom_ti ti;
+ struct cdrom_msf msf;
+
+ if (copy_from_user(&ti, arg, sizeof ti))
+ return -EFAULT;
+
+ if (ti.cdti_trk0 < disk_info.first
+ || ti.cdti_trk0 > disk_info.last
+ || ti.cdti_trk1 < ti.cdti_trk0)
+ return -EINVAL;
+ if (ti.cdti_trk1 > disk_info.last)
+ ti.cdti_trk1 = disk_info.last;
+
+ msf.cdmsf_min0 = toc[ti.cdti_trk0].cdsc_absaddr.msf.minute;
+ msf.cdmsf_sec0 = toc[ti.cdti_trk0].cdsc_absaddr.msf.second;
+ msf.cdmsf_frame0 = toc[ti.cdti_trk0].cdsc_absaddr.msf.frame;
+ msf.cdmsf_min1 = toc[ti.cdti_trk1 + 1].cdsc_absaddr.msf.minute;
+ msf.cdmsf_sec1 = toc[ti.cdti_trk1 + 1].cdsc_absaddr.msf.second;
+ msf.cdmsf_frame1 = toc[ti.cdti_trk1 + 1].cdsc_absaddr.msf.frame;
+
+ DEBUG((DEBUG_VFS, "play %02d:%02d.%02d to %02d:%02d.%02d",
+ msf.cdmsf_min0,
+ msf.cdmsf_sec0,
+ msf.cdmsf_frame0,
+ msf.cdmsf_min1,
+ msf.cdmsf_sec1,
+ msf.cdmsf_frame1));
+
+ bin2bcd(&msf);
+ status = exec_long_cmd(COMPLAY, &msf);
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_long_cmd COMPLAY: %02x", -status));
+ audio_status = CDROM_AUDIO_ERROR;
+ return -EIO;
+ }
+
+ audio_status = CDROM_AUDIO_PLAY;
+ return 0;
+}
+
+
+static int cdromreadtochdr(void __user *arg)
+{
+ struct cdrom_tochdr tochdr;
+
+ tochdr.cdth_trk0 = disk_info.first;
+ tochdr.cdth_trk1 = disk_info.last;
+
+ return copy_to_user(arg, &tochdr, sizeof tochdr) ? -EFAULT : 0;
+}
+
+
+static int cdromreadtocentry(void __user *arg)
+{
+ struct cdrom_tocentry entry;
+ struct cdrom_subchnl *tocptr;
+
+ if (copy_from_user(&entry, arg, sizeof entry))
+ return -EFAULT;
+
+ if (entry.cdte_track == CDROM_LEADOUT)
+ tocptr = &toc[disk_info.last + 1];
+ else if (entry.cdte_track > disk_info.last
+ || entry.cdte_track < disk_info.first)
+ return -EINVAL;
+ else
+ tocptr = &toc[entry.cdte_track];
+
+ entry.cdte_adr = tocptr->cdsc_adr;
+ entry.cdte_ctrl = tocptr->cdsc_ctrl;
+ entry.cdte_addr.msf.minute = tocptr->cdsc_absaddr.msf.minute;
+ entry.cdte_addr.msf.second = tocptr->cdsc_absaddr.msf.second;
+ entry.cdte_addr.msf.frame = tocptr->cdsc_absaddr.msf.frame;
+ /* %% What should go into entry.cdte_datamode? */
+
+ if (entry.cdte_format == CDROM_LBA)
+ msf2lba(&entry.cdte_addr);
+ else if (entry.cdte_format != CDROM_MSF)
+ return -EINVAL;
+
+ return copy_to_user(arg, &entry, sizeof entry) ? -EFAULT : 0;
+}
+
+
+static int cdromvolctrl(void __user *arg)
+{
+ int status;
+ struct cdrom_volctrl volctrl;
+ struct cdrom_msf msf;
+
+ if (copy_from_user(&volctrl, arg, sizeof volctrl))
+ return -EFAULT;
+
+ msf.cdmsf_min0 = 0x10;
+ msf.cdmsf_sec0 = 0x32;
+ msf.cdmsf_frame0 = volctrl.channel0;
+ msf.cdmsf_min1 = volctrl.channel1;
+ msf.cdmsf_sec1 = volctrl.channel2;
+ msf.cdmsf_frame1 = volctrl.channel3;
+
+ status = exec_long_cmd(COMCHCTRL, &msf);
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_long_cmd COMCHCTRL: %02x", -status));
+ return -EIO;
+ }
+ return 0;
+}
+
+
+static int cdromsubchnl(void __user *arg)
+{
+ int status;
+ struct cdrom_subchnl subchnl;
+
+ if (copy_from_user(&subchnl, arg, sizeof subchnl))
+ return -EFAULT;
+
+ if (subchnl.cdsc_format != CDROM_LBA
+ && subchnl.cdsc_format != CDROM_MSF)
+ return -EINVAL;
+
+ status = get_q_channel(&subchnl);
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "get_q_channel: %02x", -status));
+ return -EIO;
+ }
+
+ if (copy_to_user(arg, &subchnl, sizeof subchnl))
+ return -EFAULT;
+ return 0;
+}
+
+
+static struct gendisk *optcd_disk;
+
+
+static int cdromread(void __user *arg, int blocksize, int cmd)
+{
+ int status;
+ struct cdrom_msf msf;
+
+ if (copy_from_user(&msf, arg, sizeof msf))
+ return -EFAULT;
+
+ bin2bcd(&msf);
+ msf.cdmsf_min1 = 0;
+ msf.cdmsf_sec1 = 0;
+ msf.cdmsf_frame1 = 1; /* read only one frame */
+ status = exec_read_cmd(cmd, &msf);
+
+ DEBUG((DEBUG_VFS, "read cmd status 0x%x", status));
+
+ if (!sleep_flag_low(FL_DTEN, SLEEP_TIMEOUT))
+ return -EIO;
+
+ fetch_data(optcd_disk->private_data, blocksize);
+
+ if (copy_to_user(arg, optcd_disk->private_data, blocksize))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+static int cdromseek(void __user *arg)
+{
+ int status;
+ struct cdrom_msf msf;
+
+ if (copy_from_user(&msf, arg, sizeof msf))
+ return -EFAULT;
+
+ bin2bcd(&msf);
+ status = exec_seek_cmd(COMSEEK, &msf);
+
+ DEBUG((DEBUG_VFS, "COMSEEK status 0x%x", status));
+
+ if (status < 0)
+ return -EIO;
+ return 0;
+}
+
+
+#ifdef MULTISESSION
+static int cdrommultisession(void __user *arg)
+{
+ struct cdrom_multisession ms;
+
+ if (copy_from_user(&ms, arg, sizeof ms))
+ return -EFAULT;
+
+ ms.addr.msf.minute = disk_info.last_session.minute;
+ ms.addr.msf.second = disk_info.last_session.second;
+ ms.addr.msf.frame = disk_info.last_session.frame;
+
+ if (ms.addr_format != CDROM_LBA
+ && ms.addr_format != CDROM_MSF)
+ return -EINVAL;
+ if (ms.addr_format == CDROM_LBA)
+ msf2lba(&ms.addr);
+
+ ms.xa_flag = disk_info.xa;
+
+ if (copy_to_user(arg, &ms, sizeof(struct cdrom_multisession)))
+ return -EFAULT;
+
+#if DEBUG_MULTIS
+ if (ms.addr_format == CDROM_MSF)
+ printk(KERN_DEBUG
+ "optcd: multisession xa:%d, msf:%02d:%02d.%02d\n",
+ ms.xa_flag,
+ ms.addr.msf.minute,
+ ms.addr.msf.second,
+ ms.addr.msf.frame);
+ else
+ printk(KERN_DEBUG
+ "optcd: multisession %d, lba:0x%08x [%02d:%02d.%02d])\n",
+ ms.xa_flag,
+ ms.addr.lba,
+ disk_info.last_session.minute,
+ disk_info.last_session.second,
+ disk_info.last_session.frame);
+#endif /* DEBUG_MULTIS */
+
+ return 0;
+}
+#endif /* MULTISESSION */
+
+
+static int cdromreset(void)
+{
+ if (state != S_IDLE) {
+ error = 1;
+ tries = 0;
+ }
+
+ toc_uptodate = 0;
+ disk_changed = 1;
+ opt_invalidate_buffers();
+ audio_status = CDROM_AUDIO_NO_STATUS;
+
+ if (!reset_drive())
+ return -EIO;
+ return 0;
+}
+
+/* VFS calls */
+
+
+static int opt_ioctl(struct inode *ip, struct file *fp,
+ unsigned int cmd, unsigned long arg)
+{
+ int status, err, retval = 0;
+ void __user *argp = (void __user *)arg;
+
+ DEBUG((DEBUG_VFS, "starting opt_ioctl"));
+
+ if (!ip)
+ return -EINVAL;
+
+ if (cmd == CDROMRESET)
+ return cdromreset();
+
+ /* is do_optcd_request or another ioctl busy? */
+ if (state != S_IDLE || in_vfs)
+ return -EBUSY;
+
+ in_vfs = 1;
+
+ status = drive_status();
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "drive_status: %02x", -status));
+ in_vfs = 0;
+ return -EIO;
+ }
+
+ if (status & ST_DOOR_OPEN)
+ switch (cmd) { /* Actions that can be taken with door open */
+ case CDROMCLOSETRAY:
+ /* We do this before trying to read the toc. */
+ err = exec_cmd(COMCLOSE);
+ if (err < 0) {
+ DEBUG((DEBUG_VFS,
+ "exec_cmd COMCLOSE: %02x", -err));
+ in_vfs = 0;
+ return -EIO;
+ }
+ break;
+ default: in_vfs = 0;
+ return -EBUSY;
+ }
+
+ err = update_toc();
+ if (err < 0) {
+ DEBUG((DEBUG_VFS, "update_toc: %02x", -err));
+ in_vfs = 0;
+ return -EIO;
+ }
+
+ DEBUG((DEBUG_VFS, "ioctl cmd 0x%x", cmd));
+
+ switch (cmd) {
+ case CDROMPAUSE: retval = cdrompause(); break;
+ case CDROMRESUME: retval = cdromresume(); break;
+ case CDROMPLAYMSF: retval = cdromplaymsf(argp); break;
+ case CDROMPLAYTRKIND: retval = cdromplaytrkind(argp); break;
+ case CDROMREADTOCHDR: retval = cdromreadtochdr(argp); break;
+ case CDROMREADTOCENTRY: retval = cdromreadtocentry(argp); break;
+
+ case CDROMSTOP: err = exec_cmd(COMSTOP);
+ if (err < 0) {
+ DEBUG((DEBUG_VFS,
+ "exec_cmd COMSTOP: %02x",
+ -err));
+ retval = -EIO;
+ } else
+ audio_status = CDROM_AUDIO_NO_STATUS;
+ break;
+ case CDROMSTART: break; /* This is a no-op */
+ case CDROMEJECT: err = exec_cmd(COMUNLOCK);
+ if (err < 0) {
+ DEBUG((DEBUG_VFS,
+ "exec_cmd COMUNLOCK: %02x",
+ -err));
+ retval = -EIO;
+ break;
+ }
+ err = exec_cmd(COMOPEN);
+ if (err < 0) {
+ DEBUG((DEBUG_VFS,
+ "exec_cmd COMOPEN: %02x",
+ -err));
+ retval = -EIO;
+ }
+ break;
+
+ case CDROMVOLCTRL: retval = cdromvolctrl(argp); break;
+ case CDROMSUBCHNL: retval = cdromsubchnl(argp); break;
+
+ /* The drive detects the mode and automatically delivers the
+ correct 2048 bytes, so we don't need these IOCTLs */
+ case CDROMREADMODE2: retval = -EINVAL; break;
+ case CDROMREADMODE1: retval = -EINVAL; break;
+
+ /* Drive doesn't support reading audio */
+ case CDROMREADAUDIO: retval = -EINVAL; break;
+
+ case CDROMEJECT_SW: auto_eject = (char) arg;
+ break;
+
+#ifdef MULTISESSION
+ case CDROMMULTISESSION: retval = cdrommultisession(argp); break;
+#endif
+
+ case CDROM_GET_MCN: retval = -EINVAL; break; /* not implemented */
+ case CDROMVOLREAD: retval = -EINVAL; break; /* not implemented */
+
+ case CDROMREADRAW:
+ /* this drive delivers 2340 bytes in raw mode */
+ retval = cdromread(argp, CD_FRAMESIZE_RAW1, COMREADRAW);
+ break;
+ case CDROMREADCOOKED:
+ retval = cdromread(argp, CD_FRAMESIZE, COMREAD);
+ break;
+ case CDROMREADALL:
+ retval = cdromread(argp, CD_FRAMESIZE_RAWER, COMREADALL);
+ break;
+
+ case CDROMSEEK: retval = cdromseek(argp); break;
+ case CDROMPLAYBLK: retval = -EINVAL; break; /* not implemented */
+ case CDROMCLOSETRAY: break; /* The action was taken earlier */
+ default: retval = -EINVAL;
+ }
+ in_vfs = 0;
+ return retval;
+}
+
+
+static int open_count = 0;
+
+/* Open device special file; check that a disk is in. */
+static int opt_open(struct inode *ip, struct file *fp)
+{
+ DEBUG((DEBUG_VFS, "starting opt_open"));
+
+ if (!open_count && state == S_IDLE) {
+ int status;
+ char *buf;
+
+ buf = kmalloc(CD_FRAMESIZE_RAWER, GFP_KERNEL);
+ if (!buf) {
+ printk(KERN_INFO "optcd: cannot allocate read buffer\n");
+ return -ENOMEM;
+ }
+ optcd_disk->private_data = buf; /* save read buffer */
+
+ toc_uptodate = 0;
+ opt_invalidate_buffers();
+
+ status = exec_cmd(COMCLOSE); /* close door */
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_cmd COMCLOSE: %02x", -status));
+ }
+
+ status = drive_status();
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "drive_status: %02x", -status));
+ goto err_out;
+ }
+ DEBUG((DEBUG_VFS, "status: %02x", status));
+ if ((status & ST_DOOR_OPEN) || (status & ST_DRVERR)) {
+ printk(KERN_INFO "optcd: no disk or door open\n");
+ goto err_out;
+ }
+ status = exec_cmd(COMLOCK); /* Lock door */
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_cmd COMLOCK: %02x", -status));
+ }
+ status = update_toc(); /* Read table of contents */
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "update_toc: %02x", -status));
+ status = exec_cmd(COMUNLOCK); /* Unlock door */
+ if (status < 0) {
+ DEBUG((DEBUG_VFS,
+ "exec_cmd COMUNLOCK: %02x", -status));
+ }
+ goto err_out;
+ }
+ open_count++;
+ }
+
+ DEBUG((DEBUG_VFS, "exiting opt_open"));
+
+ return 0;
+
+err_out:
+ return -EIO;
+}
+
+
+/* Release device special file; flush all blocks from the buffer cache */
+static int opt_release(struct inode *ip, struct file *fp)
+{
+ int status;
+
+ DEBUG((DEBUG_VFS, "executing opt_release"));
+ DEBUG((DEBUG_VFS, "inode: %p, device: %s, file: %p\n",
+ ip, ip->i_bdev->bd_disk->disk_name, fp));
+
+ if (!--open_count) {
+ toc_uptodate = 0;
+ opt_invalidate_buffers();
+ status = exec_cmd(COMUNLOCK); /* Unlock door */
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_cmd COMUNLOCK: %02x", -status));
+ }
+ if (auto_eject) {
+ status = exec_cmd(COMOPEN);
+ DEBUG((DEBUG_VFS, "exec_cmd COMOPEN: %02x", -status));
+ }
+ kfree(optcd_disk->private_data);
+ del_timer(&delay_timer);
+ del_timer(&req_timer);
+ }
+ return 0;
+}
+
+
+/* Check if disk has been changed */
+static int opt_media_change(struct gendisk *disk)
+{
+ DEBUG((DEBUG_VFS, "executing opt_media_change"));
+ DEBUG((DEBUG_VFS, "dev: %s; disk_changed = %d\n",
+ disk->disk_name, disk_changed));
+
+ if (disk_changed) {
+ disk_changed = 0;
+ return 1;
+ }
+ return 0;
+}
+
+/* Driver initialisation */
+
+
+/* Returns 1 if a drive is detected with a version string
+ starting with "DOLPHIN". Otherwise 0. */
+static int __init version_ok(void)
+{
+ char devname[100];
+ int count, i, ch, status;
+
+ status = exec_cmd(COMVERSION);
+ if (status < 0) {
+ DEBUG((DEBUG_VFS, "exec_cmd COMVERSION: %02x", -status));
+ return 0;
+ }
+ if ((count = get_data(1)) < 0) {
+ DEBUG((DEBUG_VFS, "get_data(1): %02x", -count));
+ return 0;
+ }
+ for (i = 0, ch = -1; count > 0; count--) {
+ if ((ch = get_data(1)) < 0) {
+ DEBUG((DEBUG_VFS, "get_data(1): %02x", -ch));
+ break;
+ }
+ if (i < 99)
+ devname[i++] = ch;
+ }
+ devname[i] = '\0';
+ if (ch < 0)
+ return 0;
+
+ printk(KERN_INFO "optcd: Device %s detected\n", devname);
+ return ((devname[0] == 'D')
+ && (devname[1] == 'O')
+ && (devname[2] == 'L')
+ && (devname[3] == 'P')
+ && (devname[4] == 'H')
+ && (devname[5] == 'I')
+ && (devname[6] == 'N'));
+}
+
+
+static struct block_device_operations opt_fops = {
+ .owner = THIS_MODULE,
+ .open = opt_open,
+ .release = opt_release,
+ .ioctl = opt_ioctl,
+ .media_changed = opt_media_change,
+};
+
+#ifndef MODULE
+/* Get kernel parameter when used as a kernel driver */
+static int optcd_setup(char *str)
+{
+ int ints[4];
+ (void)get_options(str, ARRAY_SIZE(ints), ints);
+
+ if (ints[0] > 0)
+ optcd_port = ints[1];
+
+ return 1;
+}
+
+__setup("optcd=", optcd_setup);
+
+#endif /* MODULE */
+
+/* Test for presence of drive and initialize it. Called at boot time
+ or during module initialisation. */
+static int __init optcd_init(void)
+{
+ int status;
+
+ if (optcd_port <= 0) {
+ printk(KERN_INFO
+ "optcd: no Optics Storage CDROM Initialization\n");
+ return -EIO;
+ }
+ optcd_disk = alloc_disk(1);
+ if (!optcd_disk) {
+ printk(KERN_ERR "optcd: can't allocate disk\n");
+ return -ENOMEM;
+ }
+ optcd_disk->major = MAJOR_NR;
+ optcd_disk->first_minor = 0;
+ optcd_disk->fops = &opt_fops;
+ sprintf(optcd_disk->disk_name, "optcd");
+
+ if (!request_region(optcd_port, 4, "optcd")) {
+ printk(KERN_ERR "optcd: conflict, I/O port 0x%x already used\n",
+ optcd_port);
+ put_disk(optcd_disk);
+ return -EIO;
+ }
+
+ if (!reset_drive()) {
+ printk(KERN_ERR "optcd: drive at 0x%x not ready\n", optcd_port);
+ release_region(optcd_port, 4);
+ put_disk(optcd_disk);
+ return -EIO;
+ }
+ if (!version_ok()) {
+ printk(KERN_ERR "optcd: unknown drive detected; aborting\n");
+ release_region(optcd_port, 4);
+ put_disk(optcd_disk);
+ return -EIO;
+ }
+ status = exec_cmd(COMINITDOUBLE);
+ if (status < 0) {
+ printk(KERN_ERR "optcd: cannot init double speed mode\n");
+ release_region(optcd_port, 4);
+ DEBUG((DEBUG_VFS, "exec_cmd COMINITDOUBLE: %02x", -status));
+ put_disk(optcd_disk);
+ return -EIO;
+ }
+ if (register_blkdev(MAJOR_NR, "optcd")) {
+ release_region(optcd_port, 4);
+ put_disk(optcd_disk);
+ return -EIO;
+ }
+
+
+ opt_queue = blk_init_queue(do_optcd_request, &optcd_lock);
+ if (!opt_queue) {
+ unregister_blkdev(MAJOR_NR, "optcd");
+ release_region(optcd_port, 4);
+ put_disk(optcd_disk);
+ return -ENOMEM;
+ }
+
+ blk_queue_hardsect_size(opt_queue, 2048);
+ optcd_disk->queue = opt_queue;
+ add_disk(optcd_disk);
+
+ printk(KERN_INFO "optcd: DOLPHIN 8000 AT CDROM at 0x%x\n", optcd_port);
+ return 0;
+}
+
+
+static void __exit optcd_exit(void)
+{
+ del_gendisk(optcd_disk);
+ put_disk(optcd_disk);
+ if (unregister_blkdev(MAJOR_NR, "optcd") == -EINVAL) {
+ printk(KERN_ERR "optcd: what's that: can't unregister\n");
+ return;
+ }
+ blk_cleanup_queue(opt_queue);
+ release_region(optcd_port, 4);
+ printk(KERN_INFO "optcd: module released.\n");
+}
+
+module_init(optcd_init);
+module_exit(optcd_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_BLOCKDEV_MAJOR(OPTICS_CDROM_MAJOR);
diff --git a/trunk/drivers/cdrom/optcd.h b/trunk/drivers/cdrom/optcd.h
new file mode 100644
index 000000000000..1911bb92ee28
--- /dev/null
+++ b/trunk/drivers/cdrom/optcd.h
@@ -0,0 +1,52 @@
+/* linux/include/linux/optcd.h - Optics Storage 8000 AT CDROM driver
+ $Id: optcd.h,v 1.2 1996/01/15 18:43:44 root Exp root $
+
+ Copyright (C) 1995 Leo Spiekman (spiekman@dutette.et.tudelft.nl)
+
+
+ Configuration file for linux/drivers/cdrom/optcd.c
+*/
+
+#ifndef _LINUX_OPTCD_H
+#define _LINUX_OPTCD_H
+
+
+/* I/O base of drive. Drive uses base to base+2.
+ This setting can be overridden with the kernel or insmod command
+ line option 'optcd='. Use address of 0 to disable driver. */
+#define OPTCD_PORTBASE 0x340
+
+
+/* enable / disable parts of driver by define / undef */
+#define MULTISESSION /* multisession support (ALPHA) */
+
+
+/* Change 0 to 1 to debug various parts of the driver */
+#define DEBUG_DRIVE_IF 0 /* Low level drive interface */
+#define DEBUG_CONV 0 /* Address conversions */
+#define DEBUG_BUFFERS 0 /* Buffering and block size conversion */
+#define DEBUG_REQUEST 0 /* Request mechanism */
+#define DEBUG_STATE 0 /* State machine */
+#define DEBUG_TOC 0 /* Q-channel and Table of Contents */
+#define DEBUG_MULTIS 0 /* Multisession code */
+#define DEBUG_VFS 0 /* VFS interface */
+
+
+/* Don't touch these unless you know what you're doing. */
+
+/* Various timeout loop repetition counts. */
+#define BUSY_TIMEOUT 10000000 /* for busy wait */
+#define FAST_TIMEOUT 100000 /* ibid. for probing */
+#define SLEEP_TIMEOUT 6000 /* for timer wait */
+#define MULTI_SEEK_TIMEOUT 1000 /* for timer wait */
+#define READ_TIMEOUT 6000 /* for poll wait */
+#define STOP_TIMEOUT 2000 /* for poll wait */
+#define RESET_WAIT 5000 /* busy wait at drive reset */
+
+/* # of buffers for block size conversion. 6 is optimal for my setup (P75),
+ giving 280 kb/s, with 0.4% CPU usage. Experiment to find your optimal
+ setting */
+#define N_BUFS 6
+
+
+#endif /* _LINUX_OPTCD_H */
diff --git a/trunk/drivers/cdrom/sbpcd.c b/trunk/drivers/cdrom/sbpcd.c
new file mode 100644
index 000000000000..a1283b1ef989
--- /dev/null
+++ b/trunk/drivers/cdrom/sbpcd.c
@@ -0,0 +1,5966 @@
+/*
+ * sbpcd.c CD-ROM device driver for the whole family of traditional,
+ * non-ATAPI IDE-style Matsushita/Panasonic CR-5xx drives.
+ * Works with SoundBlaster compatible cards and with "no-sound"
+ * interface cards like Lasermate, Panasonic CI-101P, Teac, ...
+ * Also for the Longshine LCS-7260 drive.
+ * Also for the IBM "External ISA CD-Rom" drive.
+ * Also for the CreativeLabs CD200 drive.
+ * Also for the TEAC CD-55A drive.
+ * Also for the ECS-AT "Vertos 100" drive.
+ * Not for Sanyo drives (but for the H94A, sjcd is there...).
+ * Not for any other Funai drives than the CD200 types (sometimes
+ * labelled E2550UA or MK4015 or 2800F).
+ */
+
+#define VERSION "v4.63 Andrew J. Kroll Wed Jul 26 04:24:10 EDT 2000"
+
+/* Copyright (C) 1993, 1994, 1995 Eberhard Moenkeberg
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * You should have received a copy of the GNU General Public License
+ * (for example /usr/src/linux/COPYING); if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * If you change this software, you should mail a .diff file with some
+ * description lines to emoenke@gwdg.de. I want to know about it.
+ *
+ * If you are the editor of a Linux CD, you should enable sbpcd.c within
+ * your boot floppy kernel and send me one of your CDs for free.
+ *
+ * If you would like to port the driver to an other operating system (f.e.
+ * FreeBSD or NetBSD) or use it as an information source, you shall not be
+ * restricted by the GPL under the following conditions:
+ * a) the source code of your work is freely available
+ * b) my part of the work gets mentioned at all places where your
+ * authorship gets mentioned
+ * c) I receive a copy of your code together with a full installation
+ * package of your operating system for free.
+ *
+ *
+ * VERSION HISTORY
+ *
+ * 0.1 initial release, April/May 93, after mcd.c (Martin Harriss)
+ *
+ * 0.2 thek "repeat:"-loop in do_sbpcd_request did not check for
+ * end-of-request_queue (resulting in kernel panic).
+ * Flow control seems stable, but throughput is not better.
+ *
+ * 0.3 interrupt locking totally eliminated (maybe "inb" and "outb"
+ * are still locking) - 0.2 made keyboard-type-ahead losses.
+ * check_sbpcd_media_change added (to use by isofs/inode.c)
+ * - but it detects almost nothing.
+ *
+ * 0.4 use MAJOR 25 definitely.
+ * Almost total re-design to support double-speed drives and
+ * "naked" (no sound) interface cards ("LaserMate" interface type).
+ * Flow control should be exact now.
+ * Don't occupy the SbPro IRQ line (not needed either); will
+ * live together with Hannu Savolainen's sndkit now.
+ * Speeded up data transfer to 150 kB/sec, with help from Kai
+ * Makisara, the "provider" of the "mt" tape utility.
+ * Give "SpinUp" command if necessary.
+ * First steps to support up to 4 drives (but currently only one).
+ * Implemented audio capabilities - workman should work, xcdplayer
+ * gives some problems.
+ * This version is still consuming too much CPU time, and
+ * sleeping still has to be worked on.
+ * During "long" implied seeks, it seems possible that a
+ * ReadStatus command gets ignored. That gives the message
+ * "ResponseStatus timed out" (happens about 6 times here during
+ * a "ls -alR" of the YGGDRASIL LGX-Beta CD). Such a case is
+ * handled without data error, but it should get done better.
+ *
+ * 0.5 Free CPU during waits (again with help from Kai Makisara).
+ * Made it work together with the LILO/kernel setup standard.
+ * Included auto-probing code, as suggested by YGGDRASIL.
+ * Formal redesign to add DDI debugging.
+ * There are still flaws in IOCTL (workman with double speed drive).
+ *
+ * 1.0 Added support for all drive IDs (0...3, no longer only 0)
+ * and up to 4 drives on one controller.
+ * Added "#define MANY_SESSION" for "old" multi session CDs.
+ *
+ * 1.1 Do SpinUp for new drives, too.
+ * Revised for clean compile under "old" kernels (0.99pl9).
+ *
+ * 1.2 Found the "workman with double-speed drive" bug: use the driver's
+ * audio_state, not what the drive is reporting with ReadSubQ.
+ *
+ * 1.3 Minor cleanups.
+ * Refinements regarding Workman.
+ *
+ * 1.4 Read XA disks (PhotoCDs) with "old" drives, too (but only the first
+ * session - no chance to fully access a "multi-session" CD).
+ * This currently still is too slow (50 kB/sec) - but possibly
+ * the old drives won't do it faster.
+ * Implemented "door (un)lock" for new drives (still does not work
+ * as wanted - no lock possible after an unlock).
+ * Added some debugging printout for the UPC/EAN code - but my drives
+ * return only zeroes. Is there no UPC/EAN code written?
+ *
+ * 1.5 Laborate with UPC/EAN code (not better yet).
+ * Adapt to kernel 1.1.8 change (have to explicitly include
+ * now).
+ *
+ * 1.6 Trying to read audio frames as data. Impossible with the current
+ * drive firmware levels, as it seems. Awaiting any hint. ;-)
+ * Changed "door unlock": repeat it until success.
+ * Changed CDROMSTOP routine (stop somewhat "softer" so that Workman
+ * won't get confused).
+ * Added a third interface type: Sequoia S-1000, as used with the SPEA
+ * Media FX sound card. This interface (usable for Sony and Mitsumi
+ * drives, too) needs a special configuration setup and behaves like a
+ * LaserMate type after that. Still experimental - I do not have such
+ * an interface.
+ * Use the "variable BLOCK_SIZE" feature (2048). But it does only work
+ * if you give the mount option "block=2048".
+ * The media_check routine is currently disabled; now that it gets
+ * called as it should I fear it must get synchronized for not to
+ * disturb the normal driver's activity.
+ *
+ * 2.0 Version number bumped - two reasons:
+ * - reading audio tracks as data works now with CR-562 and CR-563. We
+ * currently do it by an IOCTL (yet has to get standardized), one frame
+ * at a time; that is pretty slow. But it works.
+ * - we are maintaining now up to 4 interfaces (each up to 4 drives):
+ * did it the easy way - a different MAJOR (25, 26, ...) and a different
+ * copy of the driver (sbpcd.c, sbpcd2.c, sbpcd3.c, sbpcd4.c - only
+ * distinguished by the value of SBPCD_ISSUE and the driver's name),
+ * and a common sbpcd.h file.
+ * Bettered the "ReadCapacity error" problem with old CR-52x drives (the
+ * drives sometimes need a manual "eject/insert" before work): just
+ * reset the drive and do again. Needs lots of resets here and sometimes
+ * that does not cure, so this can't be the solution.
+ *
+ * 2.1 Found bug with multisession CDs (accessing frame 16).
+ * "read audio" works now with address type CDROM_MSF, too.
+ * Bigger audio frame buffer: allows reading max. 4 frames at time; this
+ * gives a significant speedup, but reading more than one frame at once
+ * gives missing chunks at each single frame boundary.
+ *
+ * 2.2 Kernel interface cleanups: timers, init, setup, media check.
+ *
+ * 2.3 Let "door lock" and "eject" live together.
+ * Implemented "close tray" (done automatically during open).
+ *
+ * 2.4 Use different names for device registering.
+ *
+ * 2.5 Added "#if EJECT" code (default: enabled) to automatically eject
+ * the tray during last call to "sbpcd_release".
+ * Added "#if JUKEBOX" code (default: disabled) to automatically eject
+ * the tray during call to "sbpcd_open" if no disk is in.
+ * Turn on the CD volume of "compatible" sound cards, too; just define
+ * SOUND_BASE (in sbpcd.h) accordingly (default: disabled).
+ *
+ * 2.6 Nothing new.
+ *
+ * 2.7 Added CDROMEJECT_SW ioctl to set the "EJECT" behavior on the fly:
+ * 0 disables, 1 enables auto-ejecting. Useful to keep the tray in
+ * during shutdown.
+ *
+ * 2.8 Added first support (still BETA, I need feedback or a drive) for
+ * the Longshine LCS-7260 drives. They appear as double-speed drives
+ * using the "old" command scheme, extended by tray control and door
+ * lock functions.
+ * Found (and fixed preliminary) a flaw with some multisession CDs: we
+ * have to re-direct not only the accesses to frame 16 (the isofs
+ * routines drive it up to max. 100), but also those to the continuation
+ * (repetition) frames (as far as they exist - currently set fix as
+ * 16..20).
+ * Changed default of the "JUKEBOX" define. If you use this default,
+ * your tray will eject if you try to mount without a disk in. Next
+ * mount command will insert the tray - so, just fill in a disk. ;-)
+ *
+ * 2.9 Fulfilled the Longshine LCS-7260 support; with great help and
+ * experiments by Serge Robyns.
+ * First attempts to support the TEAC CD-55A drives; but still not
+ * usable yet.
+ * Implemented the CDROMMULTISESSION ioctl; this is an attempt to handle
+ * multi session CDs more "transparent" (redirection handling has to be
+ * done within the isofs routines, and only for the special purpose of
+ * obtaining the "right" volume descriptor; accesses to the raw device
+ * should not get redirected).
+ *
+ * 3.0 Just a "normal" increment, with some provisions to do it better. ;-)
+ * Introduced "#define READ_AUDIO" to specify the maximum number of
+ * audio frames to grab with one request. This defines a buffer size
+ * within kernel space; a value of 0 will reserve no such space and
+ * disable the CDROMREADAUDIO ioctl. A value of 75 enables the reading
+ * of a whole second with one command, but will use a buffer of more
+ * than 172 kB.
+ * Started CD200 support. Drive detection should work, but nothing
+ * more.
+ *
+ * 3.1 Working to support the CD200 and the Teac CD-55A drives.
+ * AT-BUS style device numbering no longer used: use SCSI style now.
+ * So, the first "found" device has MINOR 0, regardless of the
+ * jumpered drive ID. This implies modifications to the /dev/sbpcd*
+ * entries for some people, but will help the DAU (german TLA, english:
+ * "newbie", maybe ;-) to install his "first" system from a CD.
+ *
+ * 3.2 Still testing with CD200 and CD-55A drives.
+ *
+ * 3.3 Working with CD200 support.
+ *
+ * 3.4 Auto-probing stops if an address of 0 is seen (to be entered with
+ * the kernel command line).
+ * Made the driver "loadable". If used as a module, "audio copy" is
+ * disabled, and the internal read ahead data buffer has a reduced size
+ * of 4 kB; so, throughput may be reduced a little bit with slow CPUs.
+ *
+ * 3.5 Provisions to handle weird photoCDs which have an interrupted
+ * "formatting" immediately after the last frames of some files: simply
+ * never "read ahead" with MultiSession CDs. By this, CPU usage may be
+ * increased with those CDs, and there may be a loss in speed.
+ * Re-structured the messaging system.
+ * The "loadable" version no longer has a limited READ_AUDIO buffer
+ * size.
+ * Removed "MANY_SESSION" handling for "old" multi session CDs.
+ * Added "private" IOCTLs CDROMRESET and CDROMVOLREAD.
+ * Started again to support the TEAC CD-55A drives, now that I found
+ * the money for "my own" drive. ;-)
+ * The TEAC CD-55A support is fairly working now.
+ * I have measured that the drive "delivers" at 600 kB/sec (even with
+ * bigger requests than the drive's 64 kB buffer can satisfy), but
+ * the "real" rate does not exceed 520 kB/sec at the moment.
+ * Caused by the various changes to build in TEAC support, the timed
+ * loops are de-optimized at the moment (less throughput with CR-52x
+ * drives, and the TEAC will give speed only with SBP_BUFFER_FRAMES 64).
+ *
+ * 3.6 Fixed TEAC data read problems with SbPro interfaces.
+ * Initial size of the READ_AUDIO buffer is 0. Can get set to any size
+ * during runtime.
+ *
+ * 3.7 Introduced MAX_DRIVES for some poor interface cards (seen with TEAC
+ * drives) which allow only one drive (ID 0); this avoids repetitive
+ * detection under IDs 1..3.
+ * Elongated cmd_out_T response waiting; necessary for photo CDs with
+ * a lot of sessions.
+ * Bettered the sbpcd_open() behavior with TEAC drives.
+ *
+ * 3.8 Elongated max_latency for CR-56x drives.
+ *
+ * 3.9 Finally fixed the long-known SoundScape/SPEA/Sequoia S-1000 interface
+ * configuration bug.
+ * Now Corey, Heiko, Ken, Leo, Vadim/Eric & Werner are invited to copy
+ * the config_spea() routine into their drivers. ;-)
+ *
+ * 4.0 No "big step" - normal version increment.
+ * Adapted the benefits from 1.3.33.
+ * Fiddled with CDROMREADAUDIO flaws.
+ * Avoid ReadCapacity command with CD200 drives (the MKE 1.01 version
+ * seems not to support it).
+ * Fulfilled "read audio" for CD200 drives, with help of Pete Heist
+ * (heistp@rpi.edu).
+ *
+ * 4.1 Use loglevel KERN_INFO with printk().
+ * Added support for "Vertos 100" drive ("ECS-AT") - it is very similar
+ * to the Longshine LCS-7260. Give feedback if you can - I never saw
+ * such a drive, and I have no specs.
+ *
+ * 4.2 Support for Teac 16-bit interface cards. Can't get auto-detected,
+ * so you have to jumper your card to 0x2C0. Still not 100% - come
+ * in contact if you can give qualified feedback.
+ * Use loglevel KERN_NOTICE with printk(). If you get annoyed by a
+ * flood of unwanted messages and the accompanied delay, try to read
+ * my documentation. Especially the Linux CDROM drivers have to do an
+ * important job for the newcomers, so the "distributed" version has
+ * to fit some special needs. Since generations, the flood of messages
+ * is user-configurable (even at runtime), but to get aware of this, one
+ * needs a special mental quality: the ability to read.
+ *
+ * 4.3 CD200F does not like to receive a command while the drive is
+ * reading the ToC; still trying to solve it.
+ * Removed some redundant verify_area calls (yes, Heiko Eissfeldt
+ * is visiting all the Linux CDROM drivers ;-).
+ *
+ * 4.4 Adapted one idea from tiensivu@pilot.msu.edu's "stripping-down"
+ * experiments: "KLOGD_PAUSE".
+ * Inhibited "play audio" attempts with data CDs. Provisions for a
+ * "data-safe" handling of "mixed" (data plus audio) Cds.
+ *
+ * 4.5 Meanwhile Gonzalo Tornaria (GTL) built a
+ * special end_request routine: we seem to have to take care for not
+ * to have two processes working at the request list. My understanding
+ * was and is that ll_rw_blk should not call do_sbpcd_request as long
+ * as there is still one call active (the first call will care for all
+ * outstanding I/Os, and if a second call happens, that is a bug in
+ * ll_rw_blk.c).
+ * "Check media change" without touching any drive.
+ *
+ * 4.6 Use a semaphore to synchronize multi-activity; elaborated by Rob
+ * Riggs . At the moment, we simply block "read"
+ * against "ioctl" and vice versa. This could be refined further, but
+ * I guess with almost no performance increase.
+ * Experiments to speed up the CD-55A; again with help of Rob Riggs
+ * (to be true, he gave both, idea & code. ;-)
+ *
+ * 4.61 Ported to Uniform CD-ROM driver by
+ * Heiko Eissfeldt with additional
+ * changes by Erik Andersen
+ *
+ * 4.62 Fix a bug where playing audio left the drive in an unusable state.
+ * Heiko Eissfeldt
+ *
+ * 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
+ *
+ * 4.63 Bug fixes for audio annoyances, new legacy CDROM maintainer.
+ * Annoying things fixed:
+ * TOC reread on automated disk changes
+ * TOC reread on manual cd changes
+ * Play IOCTL tries to play CD before it's actually ready... sometimes.
+ * CD_AUDIO_COMPLETED state so workman (and other playes) can repeat play.
+ * Andrew J. Kroll Wed Jul 26 04:24:10 EDT 2000
+ *
+ * 4.64 Fix module parameters - were being completely ignored.
+ * Can also specify max_drives=N as a setup int to get rid of
+ * "ghost" drives on crap hardware (aren't they all?) Paul Gortmaker
+ *
+ * TODO
+ * implement "read all subchannel data" (96 bytes per frame)
+ * remove alot of the virtual status bits and deal with hardware status
+ * move the change of cd for audio to a better place
+ * add debug levels to insmod parameters (trivial)
+ *
+ * special thanks to Kai Makisara (kai.makisara@vtt.fi) for his fine
+ * elaborated speed-up experiments (and the fabulous results!), for
+ * the "push" towards load-free wait loops, and for the extensive mail
+ * thread which brought additional hints and bug fixes.
+ *
+ */
+
+/*
+ * Trying to merge requests breaks this driver horribly (as in it goes
+ * boom and apparently has done so since 2.3.41). As it is a legacy
+ * driver for a horribly slow double speed CD on a hideous interface
+ * designed for polled operation, I won't lose any sleep in simply
+ * disallowing merging. Paul G. 02/2001
+ *
+ * Thu May 30 14:14:47 CEST 2002:
+ *
+ * I have presumably found the reson for the above - there was a bogous
+ * end_request substitute, which was manipulating the request queues
+ * incorrectly. If someone has access to the actual hardware, and it's
+ * still operations - well please free to test it.
+ *
+ * Marcin Dalecki
+ */
+
+/*
+ * Add bio/kdev_t changes for 2.5.x required to make it work again.
+ * Still room for improvement in the request handling here if anyone
+ * actually cares. Bring your own chainsaw. Paul G. 02/2002
+ */
+
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include "sbpcd.h"
+
+#define MAJOR_NR MATSUSHITA_CDROM_MAJOR
+#include
+
+/*==========================================================================*/
+#if SBPCD_DIS_IRQ
+# define SBPCD_CLI cli()
+# define SBPCD_STI sti()
+#else
+# define SBPCD_CLI
+# define SBPCD_STI
+#endif
+
+/*==========================================================================*/
+/*
+ * auto-probing address list
+ * inspired by Adam J. Richter from Yggdrasil
+ *
+ * still not good enough - can cause a hang.
+ * example: a NE 2000 ethernet card at 300 will cause a hang probing 310.
+ * if that happens, reboot and use the LILO (kernel) command line.
+ * The possibly conflicting ethernet card addresses get NOT probed
+ * by default - to minimize the hang possibilities.
+ *
+ * The SB Pro addresses get "mirrored" at 0x6xx and some more locations - to
+ * avoid a type error, the 0x2xx-addresses must get checked before 0x6xx.
+ *
+ * send mail to emoenke@gwdg.de if your interface card is not FULLY
+ * represented here.
+ */
+static int sbpcd[] =
+{
+ CDROM_PORT, SBPRO, /* probe with user's setup first */
+#if DISTRIBUTION
+ 0x230, 1, /* Soundblaster Pro and 16 (default) */
+#if 0
+ 0x300, 0, /* CI-101P (default), WDH-7001C (default),
+ Galaxy (default), Reveal (one default) */
+ 0x250, 1, /* OmniCD default, Soundblaster Pro and 16 */
+ 0x2C0, 3, /* Teac 16-bit cards */
+ 0x260, 1, /* OmniCD */
+ 0x320, 0, /* Lasermate, CI-101P, WDH-7001C, Galaxy, Reveal (other default),
+ Longshine LCS-6853 (default) */
+ 0x338, 0, /* Reveal Sound Wave 32 card model #SC600 */
+ 0x340, 0, /* Mozart sound card (default), Lasermate, CI-101P */
+ 0x360, 0, /* Lasermate, CI-101P */
+ 0x270, 1, /* Soundblaster 16 */
+ 0x670, 0, /* "sound card #9" */
+ 0x690, 0, /* "sound card #9" */
+ 0x338, 2, /* SPEA Media FX, Ensonic SoundScape (default) */
+ 0x328, 2, /* SPEA Media FX */
+ 0x348, 2, /* SPEA Media FX */
+ 0x634, 0, /* some newer sound cards */
+ 0x638, 0, /* some newer sound cards */
+ 0x230, 1, /* some newer sound cards */
+ /* due to incomplete address decoding of the SbPro card, these must be last */
+ 0x630, 0, /* "sound card #9" (default) */
+ 0x650, 0, /* "sound card #9" */
+#ifdef MODULE
+ /*
+ * some "hazardous" locations (no harm with the loadable version)
+ * (will stop the bus if a NE2000 ethernet card resides at offset -0x10)
+ */
+ 0x330, 0, /* Lasermate, CI-101P, WDH-7001C */
+ 0x350, 0, /* Lasermate, CI-101P */
+ 0x358, 2, /* SPEA Media FX */
+ 0x370, 0, /* Lasermate, CI-101P */
+ 0x290, 1, /* Soundblaster 16 */
+ 0x310, 0, /* Lasermate, CI-101P, WDH-7001C */
+#endif /* MODULE */
+#endif
+#endif /* DISTRIBUTION */
+};
+
+/*
+ * Protects access to global structures etc.
+ */
+static __cacheline_aligned DEFINE_SPINLOCK(sbpcd_lock);
+static struct request_queue *sbpcd_queue;
+
+/* You can only set the first pair, from old MODULE_PARM code. */
+static int sbpcd_set(const char *val, struct kernel_param *kp)
+{
+ get_options((char *)val, 2, (int *)sbpcd);
+ return 0;
+}
+module_param_call(sbpcd, sbpcd_set, NULL, NULL, 0);
+
+#define NUM_PROBE (sizeof(sbpcd) / sizeof(int))
+
+/*==========================================================================*/
+
+#define INLINE inline
+
+/*==========================================================================*/
+/*
+ * the forward references:
+ */
+static void sbp_sleep(u_int);
+static void mark_timeout_delay(u_long);
+static void mark_timeout_data(u_long);
+#if 0
+static void mark_timeout_audio(u_long);
+#endif
+static void sbp_read_cmd(struct request *req);
+static int sbp_data(struct request *req);
+static int cmd_out(void);
+static int DiskInfo(void);
+
+/*==========================================================================*/
+
+/*
+ * pattern for printk selection:
+ *
+ * (1<99) msgnum=0;
+ va_start(args, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ va_end(args);
+ printk(MSG_LEVEL "%s-%d [%02d]: %s", major_name, current_drive - D_S, msgnum, buf);
+#if KLOGD_PAUSE
+ sbp_sleep(KLOGD_PAUSE); /* else messages get lost */
+#endif /* KLOGD_PAUSE */
+ return;
+}
+/*==========================================================================*/
+/*
+ * DDI interface: runtime trace bit pattern maintenance
+ */
+static int sbpcd_dbg_ioctl(unsigned long arg, int level)
+{
+ switch(arg)
+ {
+ case 0: /* OFF */
+ sbpcd_debug = DBG_INF;
+ break;
+
+ default:
+ if (arg>=128) sbpcd_debug &= ~(1<<(arg-128));
+ else sbpcd_debug |= (1<>4));
+}
+/*==========================================================================*/
+static INLINE u_char byt2bcd(u_char i)
+{
+ return (((i/10)<<4)+i%10);
+}
+/*==========================================================================*/
+static INLINE u_char bcd2bin(u_char bcd)
+{
+ return ((bcd>>4)*10+(bcd&0x0F));
+}
+/*==========================================================================*/
+static INLINE int msf2blk(int msfx)
+{
+ MSF msf;
+ int i;
+
+ msf.n=msfx;
+ i=(msf.c[2] * CD_SECS + msf.c[1]) * CD_FRAMES + msf.c[0] - CD_MSF_OFFSET;
+ if (i<0) return (0);
+ return (i);
+}
+/*==========================================================================*/
+/*
+ * convert m-s-f_number (3 bytes only) to logical_block_address
+ */
+static INLINE int msf2lba(u_char *msf)
+{
+ int i;
+
+ i=(msf[0] * CD_SECS + msf[1]) * CD_FRAMES + msf[2] - CD_MSF_OFFSET;
+ if (i<0) return (0);
+ return (i);
+}
+/*==========================================================================*/
+/* evaluate cc_ReadError code */
+static int sta2err(int sta)
+{
+ if (famT_drive)
+ {
+ if (sta==0x00) return (0);
+ if (sta==0x01) return (-604); /* CRC error */
+ if (sta==0x02) return (-602); /* drive not ready */
+ if (sta==0x03) return (-607); /* unknown media */
+ if (sta==0x04) return (-612); /* general failure */
+ if (sta==0x05) return (0);
+ if (sta==0x06) return (-ERR_DISKCHANGE); /* disk change */
+ if (sta==0x0b) return (-612); /* general failure */
+ if (sta==0xff) return (-612); /* general failure */
+ return (0);
+ }
+ else
+ {
+ if (sta<=2) return (sta);
+ if (sta==0x05) return (-604); /* CRC error */
+ if (sta==0x06) return (-606); /* seek error */
+ if (sta==0x0d) return (-606); /* seek error */
+ if (sta==0x0e) return (-603); /* unknown command */
+ if (sta==0x14) return (-603); /* unknown command */
+ if (sta==0x0c) return (-611); /* read fault */
+ if (sta==0x0f) return (-611); /* read fault */
+ if (sta==0x10) return (-611); /* read fault */
+ if (sta>=0x16) return (-612); /* general failure */
+ if (sta==0x11) return (-ERR_DISKCHANGE); /* disk change (LCS: removed) */
+ if (famL_drive)
+ if (sta==0x12) return (-ERR_DISKCHANGE); /* disk change (inserted) */
+ return (-602); /* drive not ready */
+ }
+}
+/*==========================================================================*/
+static INLINE void clr_cmdbuf(void)
+{
+ int i;
+
+ for (i=0;i<10;i++) drvcmd[i]=0;
+ cmd_type=0;
+}
+/*==========================================================================*/
+static void flush_status(void)
+{
+ int i;
+
+ sbp_sleep(15*HZ/10);
+ for (i=maxtim_data;i!=0;i--) inb(CDi_status);
+}
+/*====================================================================*/
+/*
+ * CDi status loop for Teac CD-55A (Rob Riggs)
+ *
+ * This is needed because for some strange reason
+ * the CD-55A can take a real long time to give a
+ * status response. This seems to happen after we
+ * issue a READ command where a long seek is involved.
+ *
+ * I tried to ensure that we get max throughput with
+ * minimal busy waiting. We busy wait at first, then
+ * "switch gears" and start sleeping. We sleep for
+ * longer periods of time the longer we wait.
+ *
+ */
+static int CDi_stat_loop_T(void)
+{
+ int i, gear=1;
+ u_long timeout_1, timeout_2, timeout_3, timeout_4;
+
+ timeout_1 = jiffies + HZ / 50; /* sbp_sleep(0) for a short period */
+ timeout_2 = jiffies + HZ / 5; /* nap for no more than 200ms */
+ timeout_3 = jiffies + 5 * HZ; /* sleep for up to 5s */
+ timeout_4 = jiffies + 45 * HZ; /* long sleep for up to 45s. */
+ do
+ {
+ i = inb(CDi_status);
+ if (!(i&s_not_data_ready)) return (i);
+ if (!(i&s_not_result_ready)) return (i);
+ switch(gear)
+ {
+ case 4:
+ sbp_sleep(HZ);
+ if (time_after(jiffies, timeout_4)) gear++;
+ msg(DBG_TEA, "CDi_stat_loop_T: long sleep active.\n");
+ break;
+ case 3:
+ sbp_sleep(HZ/10);
+ if (time_after(jiffies, timeout_3)) gear++;
+ break;
+ case 2:
+ sbp_sleep(HZ/100);
+ if (time_after(jiffies, timeout_2)) gear++;
+ break;
+ case 1:
+ sbp_sleep(0);
+ if (time_after(jiffies, timeout_1)) gear++;
+ }
+ } while (gear < 5);
+ return -1;
+}
+/*==========================================================================*/
+static int CDi_stat_loop(void)
+{
+ int i,j;
+
+ for(timeout = jiffies + 10*HZ, i=maxtim_data; time_before(jiffies, timeout); )
+ {
+ for ( ;i!=0;i--)
+ {
+ j=inb(CDi_status);
+ if (!(j&s_not_data_ready)) return (j);
+ if (!(j&s_not_result_ready)) return (j);
+ if (fam0L_drive) if (j&s_attention) return (j);
+ }
+ sbp_sleep(1);
+ i = 1;
+ }
+ msg(DBG_LCS,"CDi_stat_loop failed in line %d\n", __LINE__);
+ return (-1);
+}
+/*==========================================================================*/
+#if 00000
+/*==========================================================================*/
+static int tst_DataReady(void)
+{
+ int i;
+
+ i=inb(CDi_status);
+ if (i&s_not_data_ready) return (0);
+ return (1);
+}
+/*==========================================================================*/
+static int tst_ResultReady(void)
+{
+ int i;
+
+ i=inb(CDi_status);
+ if (i&s_not_result_ready) return (0);
+ return (1);
+}
+/*==========================================================================*/
+static int tst_Attention(void)
+{
+ int i;
+
+ i=inb(CDi_status);
+ if (i&s_attention) return (1);
+ return (0);
+}
+/*==========================================================================*/
+#endif
+/*==========================================================================*/
+static int ResponseInfo(void)
+{
+ int i,j,st=0;
+ u_long timeout;
+
+ for (i=0,timeout=jiffies+HZ;i0) msg(DBG_INF,"ResponseInfo: got %d trailing bytes.\n",j);
+#endif /* 000 */
+ for (j=0;j0) return (-j);
+ else return (i);
+}
+/*==========================================================================*/
+static void EvaluateStatus(int st)
+{
+ current_drive->status_bits=0;
+ if (fam1_drive) current_drive->status_bits=st|p_success;
+ else if (fam0_drive)
+ {
+ if (st&p_caddin_old) current_drive->status_bits |= p_door_closed|p_caddy_in;
+ if (st&p_spinning) current_drive->status_bits |= p_spinning;
+ if (st&p_check) current_drive->status_bits |= p_check;
+ if (st&p_success_old) current_drive->status_bits |= p_success;
+ if (st&p_busy_old) current_drive->status_bits |= p_busy_new;
+ if (st&p_disk_ok) current_drive->status_bits |= p_disk_ok;
+ }
+ else if (famLV_drive)
+ {
+ current_drive->status_bits |= p_success;
+ if (st&p_caddin_old) current_drive->status_bits |= p_disk_ok|p_caddy_in;
+ if (st&p_spinning) current_drive->status_bits |= p_spinning;
+ if (st&p_check) current_drive->status_bits |= p_check;
+ if (st&p_busy_old) current_drive->status_bits |= p_busy_new;
+ if (st&p_lcs_door_closed) current_drive->status_bits |= p_door_closed;
+ if (st&p_lcs_door_locked) current_drive->status_bits |= p_door_locked;
+ }
+ else if (fam2_drive)
+ {
+ current_drive->status_bits |= p_success;
+ if (st&p2_check) current_drive->status_bits |= p1_check;
+ if (st&p2_door_closed) current_drive->status_bits |= p1_door_closed;
+ if (st&p2_disk_in) current_drive->status_bits |= p1_disk_in;
+ if (st&p2_busy1) current_drive->status_bits |= p1_busy;
+ if (st&p2_busy2) current_drive->status_bits |= p1_busy;
+ if (st&p2_spinning) current_drive->status_bits |= p1_spinning;
+ if (st&p2_door_locked) current_drive->status_bits |= p1_door_locked;
+ if (st&p2_disk_ok) current_drive->status_bits |= p1_disk_ok;
+ }
+ else if (famT_drive)
+ {
+ return; /* still needs to get coded */
+ current_drive->status_bits |= p_success;
+ if (st&p2_check) current_drive->status_bits |= p1_check;
+ if (st&p2_door_closed) current_drive->status_bits |= p1_door_closed;
+ if (st&p2_disk_in) current_drive->status_bits |= p1_disk_in;
+ if (st&p2_busy1) current_drive->status_bits |= p1_busy;
+ if (st&p2_busy2) current_drive->status_bits |= p1_busy;
+ if (st&p2_spinning) current_drive->status_bits |= p1_spinning;
+ if (st&p2_door_locked) current_drive->status_bits |= p1_door_locked;
+ if (st&p2_disk_ok) current_drive->status_bits |= p1_disk_ok;
+ }
+ return;
+}
+/*==========================================================================*/
+static int cmd_out_T(void);
+
+static int get_state_T(void)
+{
+ int i;
+
+ clr_cmdbuf();
+ current_drive->n_bytes=1;
+ drvcmd[0]=CMDT_STATUS;
+ i=cmd_out_T();
+ if (i>=0) i=infobuf[0];
+ else
+ {
+ msg(DBG_TEA,"get_state_T error %d\n", i);
+ return (i);
+ }
+ if (i>=0)
+ /* 2: closed, disk in */
+ current_drive->status_bits=p1_door_closed|p1_disk_in|p1_spinning|p1_disk_ok;
+ else if (current_drive->error_state==6)
+ {
+ /* 3: closed, disk in, changed ("06 xx xx") */
+ current_drive->status_bits=p1_door_closed|p1_disk_in;
+ current_drive->CD_changed=0xFF;
+ current_drive->diskstate_flags &= ~toc_bit;
+ }
+ else if ((current_drive->error_state!=2)||(current_drive->b3!=0x3A)||(current_drive->b4==0x00))
+ {
+ /* 1: closed, no disk ("xx yy zz"or "02 3A 00") */
+ current_drive->status_bits=p1_door_closed;
+ current_drive->open_count=0;
+ }
+ else if (current_drive->b4==0x01)
+ {
+ /* 0: open ("02 3A 01") */
+ current_drive->status_bits=0;
+ current_drive->open_count=0;
+ }
+ else
+ {
+ /* 1: closed, no disk ("02 3A xx") */
+ current_drive->status_bits=p1_door_closed;
+ current_drive->open_count=0;
+ }
+ return (current_drive->status_bits);
+}
+/*==========================================================================*/
+static int ResponseStatus(void)
+{
+ int i,j;
+ u_long timeout;
+
+ msg(DBG_STA,"doing ResponseStatus...\n");
+ if (famT_drive) return (get_state_T());
+ if (flags_cmd_out & f_respo3) timeout = jiffies;
+ else if (flags_cmd_out & f_respo2) timeout = jiffies + 16*HZ;
+ else timeout = jiffies + 4*HZ;
+ j=maxtim_8;
+ do
+ {
+ for ( ;j!=0;j--)
+ {
+ i=inb(CDi_status);
+ if (!(i&s_not_result_ready)) break;
+ }
+ if ((j!=0)||time_after(jiffies, timeout)) break;
+ sbp_sleep(1);
+ j = 1;
+ }
+ while (1);
+ if (j==0)
+ {
+ if ((flags_cmd_out & f_respo3) == 0)
+ msg(DBG_STA,"ResponseStatus: timeout.\n");
+ current_drive->status_bits=0;
+ return (-401);
+ }
+ i=inb(CDi_info);
+ msg(DBG_STA,"ResponseStatus: response %02X.\n", i);
+ EvaluateStatus(i);
+ msg(DBG_STA,"status_bits=%02X, i=%02X\n",current_drive->status_bits,i);
+ return (current_drive->status_bits);
+}
+/*==========================================================================*/
+static void cc_ReadStatus(void)
+{
+ int i;
+
+ msg(DBG_STA,"giving cc_ReadStatus command\n");
+ if (famT_drive) return;
+ SBPCD_CLI;
+ if (fam0LV_drive) OUT(CDo_command,CMD0_STATUS);
+ else if (fam1_drive) OUT(CDo_command,CMD1_STATUS);
+ else if (fam2_drive) OUT(CDo_command,CMD2_STATUS);
+ if (!fam0LV_drive) for (i=0;i<6;i++) OUT(CDo_command,0);
+ SBPCD_STI;
+}
+/*==========================================================================*/
+static int cc_ReadError(void)
+{
+ int i;
+
+ clr_cmdbuf();
+ msg(DBG_ERR,"giving cc_ReadError command.\n");
+ if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_READ_ERR;
+ response_count=8;
+ flags_cmd_out=f_putcmd|f_ResponseStatus;
+ }
+ else if (fam0LV_drive)
+ {
+ drvcmd[0]=CMD0_READ_ERR;
+ response_count=6;
+ if (famLV_drive)
+ flags_cmd_out=f_putcmd;
+ else
+ flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus;
+ }
+ else if (fam2_drive)
+ {
+ drvcmd[0]=CMD2_READ_ERR;
+ response_count=6;
+ flags_cmd_out=f_putcmd;
+ }
+ else if (famT_drive)
+ {
+ response_count=5;
+ drvcmd[0]=CMDT_READ_ERR;
+ }
+ i=cmd_out();
+ current_drive->error_byte=0;
+ msg(DBG_ERR,"cc_ReadError: cmd_out(CMDx_READ_ERR) returns %d (%02X)\n",i,i);
+ if (i<0) return (i);
+ if (fam0V_drive) i=1;
+ else i=2;
+ current_drive->error_byte=infobuf[i];
+ msg(DBG_ERR,"cc_ReadError: infobuf[%d] is %d (%02X)\n",i,current_drive->error_byte,current_drive->error_byte);
+ i=sta2err(infobuf[i]);
+ if (i==-ERR_DISKCHANGE)
+ {
+ current_drive->CD_changed=0xFF;
+ current_drive->diskstate_flags &= ~toc_bit;
+ }
+ return (i);
+}
+/*==========================================================================*/
+static int cc_DriveReset(void);
+
+static int cmd_out_T(void)
+{
+#undef CMDT_TRIES
+#define CMDT_TRIES 1000
+#define TEST_FALSE_FF 1
+
+ int i, j, l=0, m, ntries;
+ unsigned long flags;
+
+ current_drive->error_state=0;
+ current_drive->b3=0;
+ current_drive->b4=0;
+ current_drive->f_drv_error=0;
+ for (i=0;i<10;i++) sprintf(&msgbuf[i*3]," %02X",drvcmd[i]);
+ msgbuf[i*3]=0;
+ msg(DBG_CMD,"cmd_out_T:%s\n",msgbuf);
+
+ OUT(CDo_sel_i_d,0);
+ OUT(CDo_enable,current_drive->drv_sel);
+ i=inb(CDi_status);
+ do_16bit=0;
+ if ((f_16bit)&&(!(i&0x80)))
+ {
+ do_16bit=1;
+ msg(DBG_TEA,"cmd_out_T: do_16bit set.\n");
+ }
+ if (!(i&s_not_result_ready))
+ do
+ {
+ j=inb(CDi_info);
+ i=inb(CDi_status);
+ sbp_sleep(0);
+ msg(DBG_TEA,"cmd_out_T: spurious !s_not_result_ready. (%02X)\n", j);
+ }
+ while (!(i&s_not_result_ready));
+ save_flags(flags); cli();
+ for (i=0;i<10;i++) OUT(CDo_command,drvcmd[i]);
+ restore_flags(flags);
+ for (ntries=CMDT_TRIES;ntries>0;ntries--)
+ {
+ if (drvcmd[0]==CMDT_READ_VER) sbp_sleep(HZ); /* fixme */
+#if 01
+ OUT(CDo_sel_i_d,1);
+#endif /* 01 */
+ if (teac==2)
+ {
+ if ((i=CDi_stat_loop_T()) == -1) break;
+ }
+ else
+ {
+#if 0
+ OUT(CDo_sel_i_d,1);
+#endif /* 0 */
+ i=inb(CDi_status);
+ }
+ if (!(i&s_not_data_ready)) /* f.e. CMDT_DISKINFO */
+ {
+ OUT(CDo_sel_i_d,1);
+ if (drvcmd[0]==CMDT_READ) return (0); /* handled elsewhere */
+ if (drvcmd[0]==CMDT_DISKINFO)
+ {
+ l=0;
+ do
+ {
+ if (do_16bit)
+ {
+ i=inw(CDi_data);
+ infobuf[l++]=i&0x0ff;
+ infobuf[l++]=i>>8;
+#if TEST_FALSE_FF
+ if ((l==2)&&(infobuf[0]==0x0ff))
+ {
+ infobuf[0]=infobuf[1];
+ l=1;
+ msg(DBG_TEA,"cmd_out_T: do_16bit: false first byte!\n");
+ }
+#endif /* TEST_FALSE_FF */
+ }
+ else infobuf[l++]=inb(CDi_data);
+ i=inb(CDi_status);
+ }
+ while (!(i&s_not_data_ready));
+ for (j=0;j1) msg(DBG_TEA,"cmd_out_T READ_ERR recursion (%02X): %d !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n", drvcmd[0], recursion);
+ clr_cmdbuf();
+ drvcmd[0]=CMDT_READ_ERR;
+ j=cmd_out_T(); /* !!! recursive here !!! */
+ --recursion;
+ sbp_sleep(1);
+ }
+ while (j<0);
+ current_drive->error_state=infobuf[2];
+ current_drive->b3=infobuf[3];
+ current_drive->b4=infobuf[4];
+ if (current_drive->f_drv_error)
+ {
+ current_drive->f_drv_error=0;
+ cc_DriveReset();
+ current_drive->error_state=2;
+ }
+ return (-current_drive->error_state-400);
+ }
+ if (drvcmd[0]==CMDT_READ) return (0); /* handled elsewhere */
+ if ((teac==0)||(ntries<(CMDT_TRIES-5))) sbp_sleep(HZ/10);
+ else sbp_sleep(HZ/100);
+ if (ntries>(CMDT_TRIES-50)) continue;
+ msg(DBG_TEA,"cmd_out_T: next CMDT_TRIES (%02X): %d.\n", drvcmd[0], ntries-1);
+ }
+ current_drive->f_drv_error=1;
+ cc_DriveReset();
+ current_drive->error_state=2;
+ return (-99);
+}
+/*==========================================================================*/
+static int cmd_out(void)
+{
+ int i=0;
+
+ if (famT_drive) return(cmd_out_T());
+
+ if (flags_cmd_out&f_putcmd)
+ {
+ unsigned long flags;
+ for (i=0;i<7;i++)
+ sprintf(&msgbuf[i*3], " %02X", drvcmd[i]);
+ msgbuf[i*3]=0;
+ msg(DBG_CMD,"cmd_out:%s\n", msgbuf);
+ save_flags(flags); cli();
+ for (i=0;i<7;i++) OUT(CDo_command,drvcmd[i]);
+ restore_flags(flags);
+ }
+ if (response_count!=0)
+ {
+ if (cmd_type!=0)
+ {
+ if (sbpro_type==1) OUT(CDo_sel_i_d,1);
+ msg(DBG_INF,"misleaded to try ResponseData.\n");
+ if (sbpro_type==1) OUT(CDo_sel_i_d,0);
+ return (-22);
+ }
+ else i=ResponseInfo();
+ if (i<0) return (i);
+ }
+ if (current_drive->in_SpinUp) msg(DBG_SPI,"in_SpinUp: to CDi_stat_loop.\n");
+ if (flags_cmd_out&f_lopsta)
+ {
+ i=CDi_stat_loop();
+ if ((i<0)||!(i&s_attention)) return (-8);
+ }
+ if (!(flags_cmd_out&f_getsta)) goto LOC_229;
+
+ LOC_228:
+ if (current_drive->in_SpinUp) msg(DBG_SPI,"in_SpinUp: to cc_ReadStatus.\n");
+ cc_ReadStatus();
+
+ LOC_229:
+ if (flags_cmd_out&f_ResponseStatus)
+ {
+ if (current_drive->in_SpinUp) msg(DBG_SPI,"in_SpinUp: to ResponseStatus.\n");
+ i=ResponseStatus();
+ /* builds status_bits, returns orig. status or p_busy_new */
+ if (i<0) return (i);
+ if (flags_cmd_out&(f_bit1|f_wait_if_busy))
+ {
+ if (!st_check)
+ {
+ if ((flags_cmd_out&f_bit1)&&(i&p_success)) goto LOC_232;
+ if ((!(flags_cmd_out&f_wait_if_busy))||(!st_busy)) goto LOC_228;
+ }
+ }
+ }
+ LOC_232:
+ if (!(flags_cmd_out&f_obey_p_check)) return (0);
+ if (!st_check) return (0);
+ if (current_drive->in_SpinUp) msg(DBG_SPI,"in_SpinUp: to cc_ReadError.\n");
+ i=cc_ReadError();
+ if (current_drive->in_SpinUp) msg(DBG_SPI,"in_SpinUp: to cmd_out OK.\n");
+ msg(DBG_000,"cmd_out: cc_ReadError=%d\n", i);
+ return (i);
+}
+/*==========================================================================*/
+static int cc_Seek(u_int pos, char f_blk_msf)
+{
+ int i;
+
+ clr_cmdbuf();
+ if (f_blk_msf>1) return (-3);
+ if (fam0V_drive)
+ {
+ drvcmd[0]=CMD0_SEEK;
+ if (f_blk_msf==1) pos=msf2blk(pos);
+ drvcmd[2]=(pos>>16)&0x00FF;
+ drvcmd[3]=(pos>>8)&0x00FF;
+ drvcmd[4]=pos&0x00FF;
+ if (fam0_drive)
+ flags_cmd_out = f_putcmd | f_respo2 | f_lopsta | f_getsta |
+ f_ResponseStatus | f_obey_p_check | f_bit1;
+ else
+ flags_cmd_out = f_putcmd;
+ }
+ else if (fam1L_drive)
+ {
+ drvcmd[0]=CMD1_SEEK; /* same as CMD1_ and CMDL_ */
+ if (f_blk_msf==0) pos=blk2msf(pos);
+ drvcmd[1]=(pos>>16)&0x00FF;
+ drvcmd[2]=(pos>>8)&0x00FF;
+ drvcmd[3]=pos&0x00FF;
+ if (famL_drive)
+ flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1;
+ else
+ flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (fam2_drive)
+ {
+ drvcmd[0]=CMD2_SEEK;
+ if (f_blk_msf==0) pos=blk2msf(pos);
+ drvcmd[2]=(pos>>24)&0x00FF;
+ drvcmd[3]=(pos>>16)&0x00FF;
+ drvcmd[4]=(pos>>8)&0x00FF;
+ drvcmd[5]=pos&0x00FF;
+ flags_cmd_out=f_putcmd|f_ResponseStatus;
+ }
+ else if (famT_drive)
+ {
+ drvcmd[0]=CMDT_SEEK;
+ if (f_blk_msf==1) pos=msf2blk(pos);
+ drvcmd[2]=(pos>>24)&0x00FF;
+ drvcmd[3]=(pos>>16)&0x00FF;
+ drvcmd[4]=(pos>>8)&0x00FF;
+ drvcmd[5]=pos&0x00FF;
+ current_drive->n_bytes=1;
+ }
+ response_count=0;
+ i=cmd_out();
+ return (i);
+}
+/*==========================================================================*/
+static int cc_SpinUp(void)
+{
+ int i;
+
+ msg(DBG_SPI,"SpinUp.\n");
+ current_drive->in_SpinUp = 1;
+ clr_cmdbuf();
+ if (fam0LV_drive)
+ {
+ drvcmd[0]=CMD0_SPINUP;
+ if (fam0L_drive)
+ flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|
+ f_ResponseStatus|f_obey_p_check|f_bit1;
+ else
+ flags_cmd_out=f_putcmd;
+ }
+ else if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_SPINUP;
+ flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (fam2_drive)
+ {
+ drvcmd[0]=CMD2_TRAY_CTL;
+ drvcmd[4]=0x01; /* "spinup" */
+ flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (famT_drive)
+ {
+ drvcmd[0]=CMDT_TRAY_CTL;
+ drvcmd[4]=0x03; /* "insert", it hopefully spins the drive up */
+ }
+ response_count=0;
+ i=cmd_out();
+ current_drive->in_SpinUp = 0;
+ return (i);
+}
+/*==========================================================================*/
+static int cc_SpinDown(void)
+{
+ int i;
+
+ if (fam0_drive) return (0);
+ clr_cmdbuf();
+ response_count=0;
+ if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_SPINDOWN;
+ flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (fam2_drive)
+ {
+ drvcmd[0]=CMD2_TRAY_CTL;
+ drvcmd[4]=0x02; /* "eject" */
+ flags_cmd_out=f_putcmd|f_ResponseStatus;
+ }
+ else if (famL_drive)
+ {
+ drvcmd[0]=CMDL_SPINDOWN;
+ drvcmd[1]=1;
+ flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1;
+ }
+ else if (famV_drive)
+ {
+ drvcmd[0]=CMDV_SPINDOWN;
+ flags_cmd_out=f_putcmd;
+ }
+ else if (famT_drive)
+ {
+ drvcmd[0]=CMDT_TRAY_CTL;
+ drvcmd[4]=0x02; /* "eject" */
+ }
+ i=cmd_out();
+ return (i);
+}
+/*==========================================================================*/
+static int cc_get_mode_T(void)
+{
+ int i;
+
+ clr_cmdbuf();
+ response_count=10;
+ drvcmd[0]=CMDT_GETMODE;
+ drvcmd[4]=response_count;
+ i=cmd_out_T();
+ return (i);
+}
+/*==========================================================================*/
+static int cc_set_mode_T(void)
+{
+ int i;
+
+ clr_cmdbuf();
+ response_count=1;
+ drvcmd[0]=CMDT_SETMODE;
+ drvcmd[1]=current_drive->speed_byte;
+ drvcmd[2]=current_drive->frmsiz>>8;
+ drvcmd[3]=current_drive->frmsiz&0x0FF;
+ drvcmd[4]=current_drive->f_XA; /* 1: XA */
+ drvcmd[5]=current_drive->type_byte; /* 0, 1, 3 */
+ drvcmd[6]=current_drive->mode_xb_6;
+ drvcmd[7]=current_drive->mode_yb_7|current_drive->volume_control;
+ drvcmd[8]=current_drive->mode_xb_8;
+ drvcmd[9]=current_drive->delay;
+ i=cmd_out_T();
+ return (i);
+}
+/*==========================================================================*/
+static int cc_prep_mode_T(void)
+{
+ int i, j;
+
+ i=cc_get_mode_T();
+ if (i<0) return (i);
+ for (i=0;i<10;i++)
+ sprintf(&msgbuf[i*3], " %02X", infobuf[i]);
+ msgbuf[i*3]=0;
+ msg(DBG_TEA,"CMDT_GETMODE:%s\n", msgbuf);
+ current_drive->speed_byte=0x02; /* 0x02: auto quad, 0x82: quad, 0x81: double, 0x80: single */
+ current_drive->frmsiz=make16(infobuf[2],infobuf[3]);
+ current_drive->f_XA=infobuf[4];
+ if (current_drive->f_XA==0) current_drive->type_byte=0;
+ else current_drive->type_byte=1;
+ current_drive->mode_xb_6=infobuf[6];
+ current_drive->mode_yb_7=1;
+ current_drive->mode_xb_8=infobuf[8];
+ current_drive->delay=0; /* 0, 1, 2, 3 */
+ j=cc_set_mode_T();
+ i=cc_get_mode_T();
+ for (i=0;i<10;i++)
+ sprintf(&msgbuf[i*3], " %02X", infobuf[i]);
+ msgbuf[i*3]=0;
+ msg(DBG_TEA,"CMDT_GETMODE:%s\n", msgbuf);
+ return (j);
+}
+/*==========================================================================*/
+static int cc_SetSpeed(u_char speed, u_char x1, u_char x2)
+{
+ int i;
+
+ if (fam0LV_drive) return (0);
+ clr_cmdbuf();
+ response_count=0;
+ if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_SETMODE;
+ drvcmd[1]=0x03;
+ drvcmd[2]=speed;
+ drvcmd[3]=x1;
+ drvcmd[4]=x2;
+ flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (fam2_drive)
+ {
+ drvcmd[0]=CMD2_SETSPEED;
+ if (speed&speed_auto)
+ {
+ drvcmd[2]=0xFF;
+ drvcmd[3]=0xFF;
+ }
+ else
+ {
+ drvcmd[2]=0;
+ drvcmd[3]=150;
+ }
+ flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (famT_drive)
+ {
+ return (0);
+ }
+ i=cmd_out();
+ return (i);
+}
+/*==========================================================================*/
+static int cc_SetVolume(void)
+{
+ int i;
+ u_char channel0,channel1,volume0,volume1;
+ u_char control0,value0,control1,value1;
+
+ current_drive->diskstate_flags &= ~volume_bit;
+ clr_cmdbuf();
+ channel0=current_drive->vol_chan0;
+ volume0=current_drive->vol_ctrl0;
+ channel1=control1=current_drive->vol_chan1;
+ volume1=value1=current_drive->vol_ctrl1;
+ control0=value0=0;
+
+ if (famV_drive) return (0);
+
+ if (((current_drive->drv_options&audio_mono)!=0)&&(current_drive->drv_type>=drv_211))
+ {
+ if ((volume0!=0)&&(volume1==0))
+ {
+ volume1=volume0;
+ channel1=channel0;
+ }
+ else if ((volume0==0)&&(volume1!=0))
+ {
+ volume0=volume1;
+ channel0=channel1;
+ }
+ }
+ if (channel0>1)
+ {
+ channel0=0;
+ volume0=0;
+ }
+ if (channel1>1)
+ {
+ channel1=1;
+ volume1=0;
+ }
+
+ if (fam1_drive)
+ {
+ control0=channel0+1;
+ control1=channel1+1;
+ value0=(volume0>volume1)?volume0:volume1;
+ value1=value0;
+ if (volume0==0) control0=0;
+ if (volume1==0) control1=0;
+ drvcmd[0]=CMD1_SETMODE;
+ drvcmd[1]=0x05;
+ drvcmd[3]=control0;
+ drvcmd[4]=value0;
+ drvcmd[5]=control1;
+ drvcmd[6]=value1;
+ flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (fam2_drive)
+ {
+ control0=channel0+1;
+ control1=channel1+1;
+ value0=(volume0>volume1)?volume0:volume1;
+ value1=value0;
+ if (volume0==0) control0=0;
+ if (volume1==0) control1=0;
+ drvcmd[0]=CMD2_SETMODE;
+ drvcmd[1]=0x0E;
+ drvcmd[3]=control0;
+ drvcmd[4]=value0;
+ drvcmd[5]=control1;
+ drvcmd[6]=value1;
+ flags_cmd_out=f_putcmd|f_ResponseStatus;
+ }
+ else if (famL_drive)
+ {
+ if ((volume0==0)||(channel0!=0)) control0 |= 0x80;
+ if ((volume1==0)||(channel1!=1)) control0 |= 0x40;
+ if (volume0|volume1) value0=0x80;
+ drvcmd[0]=CMDL_SETMODE;
+ drvcmd[1]=0x03;
+ drvcmd[4]=control0;
+ drvcmd[5]=value0;
+ flags_cmd_out=f_putcmd|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1;
+ }
+ else if (fam0_drive) /* different firmware levels */
+ {
+ if (current_drive->drv_type>=drv_300)
+ {
+ control0=volume0&0xFC;
+ value0=volume1&0xFC;
+ if ((volume0!=0)&&(volume0<4)) control0 |= 0x04;
+ if ((volume1!=0)&&(volume1<4)) value0 |= 0x04;
+ if (channel0!=0) control0 |= 0x01;
+ if (channel1==1) value0 |= 0x01;
+ }
+ else
+ {
+ value0=(volume0>volume1)?volume0:volume1;
+ if (current_drive->drv_typedrv_typedrv_type>=drv_201)
+ {
+ if (volume0==0) control0 |= 0x80;
+ if (volume1==0) control0 |= 0x40;
+ }
+ if (current_drive->drv_type>=drv_211)
+ {
+ if (channel0!=0) control0 |= 0x20;
+ if (channel1!=1) control0 |= 0x10;
+ }
+ }
+ drvcmd[0]=CMD0_SETMODE;
+ drvcmd[1]=0x83;
+ drvcmd[4]=control0;
+ drvcmd[5]=value0;
+ flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (famT_drive)
+ {
+ current_drive->volume_control=0;
+ if (!volume0) current_drive->volume_control|=0x10;
+ if (!volume1) current_drive->volume_control|=0x20;
+ i=cc_prep_mode_T();
+ if (i<0) return (i);
+ }
+ if (!famT_drive)
+ {
+ response_count=0;
+ i=cmd_out();
+ if (i<0) return (i);
+ }
+ current_drive->diskstate_flags |= volume_bit;
+ return (0);
+}
+/*==========================================================================*/
+static int GetStatus(void)
+{
+ int i;
+
+ if (famT_drive) return (0);
+ flags_cmd_out=f_getsta|f_ResponseStatus|f_obey_p_check;
+ response_count=0;
+ cmd_type=0;
+ i=cmd_out();
+ return (i);
+}
+/*==========================================================================*/
+static int cc_DriveReset(void)
+{
+ int i;
+
+ msg(DBG_RES,"cc_DriveReset called.\n");
+ clr_cmdbuf();
+ response_count=0;
+ if (fam0LV_drive) OUT(CDo_reset,0x00);
+ else if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_RESET;
+ flags_cmd_out=f_putcmd;
+ i=cmd_out();
+ }
+ else if (fam2_drive)
+ {
+ drvcmd[0]=CMD2_RESET;
+ flags_cmd_out=f_putcmd;
+ i=cmd_out();
+ OUT(CDo_reset,0x00);
+ }
+ else if (famT_drive)
+ {
+ OUT(CDo_sel_i_d,0);
+ OUT(CDo_enable,current_drive->drv_sel);
+ OUT(CDo_command,CMDT_RESET);
+ for (i=1;i<10;i++) OUT(CDo_command,0);
+ }
+ if (fam0LV_drive) sbp_sleep(5*HZ); /* wait 5 seconds */
+ else sbp_sleep(1*HZ); /* wait a second */
+#if 1
+ if (famT_drive)
+ {
+ msg(DBG_TEA, "================CMDT_RESET given=================.\n");
+ sbp_sleep(3*HZ);
+ }
+#endif /* 1 */
+ flush_status();
+ i=GetStatus();
+ if (i<0) return i;
+ if (!famT_drive)
+ if (current_drive->error_byte!=aud_12) return -501;
+ return (0);
+}
+
+/*==========================================================================*/
+static int SetSpeed(void)
+{
+ int i, speed;
+
+ if (!(current_drive->drv_options&(speed_auto|speed_300|speed_150))) return (0);
+ speed=speed_auto;
+ if (!(current_drive->drv_options&speed_auto))
+ {
+ speed |= speed_300;
+ if (!(current_drive->drv_options&speed_300)) speed=0;
+ }
+ i=cc_SetSpeed(speed,0,0);
+ return (i);
+}
+
+static void switch_drive(struct sbpcd_drive *);
+
+static int sbpcd_select_speed(struct cdrom_device_info *cdi, int speed)
+{
+ struct sbpcd_drive *p = cdi->handle;
+ if (p != current_drive)
+ switch_drive(p);
+
+ return cc_SetSpeed(speed == 2 ? speed_300 : speed_150, 0, 0);
+}
+
+/*==========================================================================*/
+static int DriveReset(void)
+{
+ int i;
+
+ i=cc_DriveReset();
+ if (i<0) return (-22);
+ do
+ {
+ i=GetStatus();
+ if ((i<0)&&(i!=-ERR_DISKCHANGE)) {
+ return (-2); /* from sta2err */
+ }
+ if (!st_caddy_in) break;
+ sbp_sleep(1);
+ }
+ while (!st_diskok);
+#if 000
+ current_drive->CD_changed=1;
+#endif
+ if ((st_door_closed) && (st_caddy_in))
+ {
+ i=DiskInfo();
+ if (i<0) return (-23);
+ }
+ return (0);
+}
+
+static int sbpcd_reset(struct cdrom_device_info *cdi)
+{
+ struct sbpcd_drive *p = cdi->handle;
+ if (p != current_drive)
+ switch_drive(p);
+ return DriveReset();
+}
+
+/*==========================================================================*/
+static int cc_PlayAudio(int pos_audio_start,int pos_audio_end)
+{
+ int i, j, n;
+
+ if (current_drive->audio_state==audio_playing) return (-EINVAL);
+ clr_cmdbuf();
+ response_count=0;
+ if (famLV_drive)
+ {
+ drvcmd[0]=CMDL_PLAY;
+ i=msf2blk(pos_audio_start);
+ n=msf2blk(pos_audio_end)+1-i;
+ drvcmd[1]=(i>>16)&0x00FF;
+ drvcmd[2]=(i>>8)&0x00FF;
+ drvcmd[3]=i&0x00FF;
+ drvcmd[4]=(n>>16)&0x00FF;
+ drvcmd[5]=(n>>8)&0x00FF;
+ drvcmd[6]=n&0x00FF;
+ if (famL_drive)
+ flags_cmd_out = f_putcmd | f_respo2 | f_lopsta | f_getsta |
+ f_ResponseStatus | f_obey_p_check | f_wait_if_busy;
+ else
+ flags_cmd_out = f_putcmd;
+ }
+ else
+ {
+ j=1;
+ if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_PLAY_MSF;
+ flags_cmd_out = f_putcmd | f_respo2 | f_ResponseStatus |
+ f_obey_p_check | f_wait_if_busy;
+ }
+ else if (fam2_drive)
+ {
+ drvcmd[0]=CMD2_PLAY_MSF;
+ flags_cmd_out = f_putcmd | f_ResponseStatus | f_obey_p_check;
+ }
+ else if (famT_drive)
+ {
+ drvcmd[0]=CMDT_PLAY_MSF;
+ j=3;
+ response_count=1;
+ }
+ else if (fam0_drive)
+ {
+ drvcmd[0]=CMD0_PLAY_MSF;
+ flags_cmd_out = f_putcmd | f_respo2 | f_lopsta | f_getsta |
+ f_ResponseStatus | f_obey_p_check | f_wait_if_busy;
+ }
+ drvcmd[j]=(pos_audio_start>>16)&0x00FF;
+ drvcmd[j+1]=(pos_audio_start>>8)&0x00FF;
+ drvcmd[j+2]=pos_audio_start&0x00FF;
+ drvcmd[j+3]=(pos_audio_end>>16)&0x00FF;
+ drvcmd[j+4]=(pos_audio_end>>8)&0x00FF;
+ drvcmd[j+5]=pos_audio_end&0x00FF;
+ }
+ i=cmd_out();
+ return (i);
+}
+/*==========================================================================*/
+static int cc_Pause_Resume(int pau_res)
+{
+ int i;
+
+ clr_cmdbuf();
+ response_count=0;
+ if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_PAU_RES;
+ if (pau_res!=1) drvcmd[1]=0x80;
+ flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (fam2_drive)
+ {
+ drvcmd[0]=CMD2_PAU_RES;
+ if (pau_res!=1) drvcmd[2]=0x01;
+ flags_cmd_out=f_putcmd|f_ResponseStatus;
+ }
+ else if (fam0LV_drive)
+ {
+ drvcmd[0]=CMD0_PAU_RES;
+ if (pau_res!=1) drvcmd[1]=0x80;
+ if (famL_drive)
+ flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|
+ f_obey_p_check|f_bit1;
+ else if (famV_drive)
+ flags_cmd_out=f_putcmd;
+ else
+ flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|
+ f_obey_p_check;
+ }
+ else if (famT_drive)
+ {
+ if (pau_res==3) return (cc_PlayAudio(current_drive->pos_audio_start,current_drive->pos_audio_end));
+ else if (pau_res==1) drvcmd[0]=CMDT_PAUSE;
+ else return (-56);
+ }
+ i=cmd_out();
+ return (i);
+}
+/*==========================================================================*/
+static int cc_LockDoor(char lock)
+{
+ int i;
+
+ if (fam0_drive) return (0);
+ msg(DBG_LCK,"cc_LockDoor: %d (drive %d)\n", lock, current_drive - D_S);
+ msg(DBG_LCS,"p_door_locked bit %d before\n", st_door_locked);
+ clr_cmdbuf();
+ response_count=0;
+ if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_LOCK_CTL;
+ if (lock==1) drvcmd[1]=0x01;
+ flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (fam2_drive)
+ {
+ drvcmd[0]=CMD2_LOCK_CTL;
+ if (lock==1) drvcmd[4]=0x01;
+ flags_cmd_out=f_putcmd|f_ResponseStatus;
+ }
+ else if (famLV_drive)
+ {
+ drvcmd[0]=CMDL_LOCK_CTL;
+ if (lock==1) drvcmd[1]=0x01;
+ if (famL_drive)
+ flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1;
+ else
+ flags_cmd_out=f_putcmd;
+ }
+ else if (famT_drive)
+ {
+ drvcmd[0]=CMDT_LOCK_CTL;
+ if (lock==1) drvcmd[4]=0x01;
+ }
+ i=cmd_out();
+ msg(DBG_LCS,"p_door_locked bit %d after\n", st_door_locked);
+ return (i);
+}
+/*==========================================================================*/
+/*==========================================================================*/
+static int UnLockDoor(void)
+{
+ int i,j;
+
+ j=20;
+ do
+ {
+ i=cc_LockDoor(0);
+ --j;
+ sbp_sleep(1);
+ }
+ while ((i<0)&&(j));
+ if (i<0)
+ {
+ cc_DriveReset();
+ return -84;
+ }
+ return (0);
+}
+/*==========================================================================*/
+static int LockDoor(void)
+{
+ int i,j;
+
+ j=20;
+ do
+ {
+ i=cc_LockDoor(1);
+ --j;
+ sbp_sleep(1);
+ }
+ while ((i<0)&&(j));
+ if (j==0)
+ {
+ cc_DriveReset();
+ j=20;
+ do
+ {
+ i=cc_LockDoor(1);
+ --j;
+ sbp_sleep(1);
+ }
+ while ((i<0)&&(j));
+ }
+ return (i);
+}
+
+static int sbpcd_lock_door(struct cdrom_device_info *cdi, int lock)
+{
+ return lock ? LockDoor() : UnLockDoor();
+}
+
+/*==========================================================================*/
+static int cc_CloseTray(void)
+{
+ int i;
+
+ if (fam0_drive) return (0);
+ msg(DBG_LCK,"cc_CloseTray (drive %d)\n", current_drive - D_S);
+ msg(DBG_LCS,"p_door_closed bit %d before\n", st_door_closed);
+
+ clr_cmdbuf();
+ response_count=0;
+ if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_TRAY_CTL;
+ flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (fam2_drive)
+ {
+ drvcmd[0]=CMD2_TRAY_CTL;
+ drvcmd[1]=0x01;
+ drvcmd[4]=0x03; /* "insert" */
+ flags_cmd_out=f_putcmd|f_ResponseStatus;
+ }
+ else if (famLV_drive)
+ {
+ drvcmd[0]=CMDL_TRAY_CTL;
+ if (famLV_drive)
+ flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|
+ f_ResponseStatus|f_obey_p_check|f_bit1;
+ else
+ flags_cmd_out=f_putcmd;
+ }
+ else if (famT_drive)
+ {
+ drvcmd[0]=CMDT_TRAY_CTL;
+ drvcmd[4]=0x03; /* "insert" */
+ }
+ i=cmd_out();
+ msg(DBG_LCS,"p_door_closed bit %d after\n", st_door_closed);
+
+ i=cc_ReadError();
+ flags_cmd_out |= f_respo2;
+ cc_ReadStatus(); /* command: give 1-byte status */
+ i=ResponseStatus();
+ if (famT_drive&&(i<0))
+ {
+ cc_DriveReset();
+ i=ResponseStatus();
+#if 0
+ sbp_sleep(HZ);
+#endif /* 0 */
+ i=ResponseStatus();
+ }
+ if (i<0)
+ {
+ msg(DBG_INF,"sbpcd cc_CloseTray: ResponseStatus timed out (%d).\n",i);
+ }
+ if (!(famT_drive))
+ {
+ if (!st_spinning)
+ {
+ cc_SpinUp();
+ if (st_check) i=cc_ReadError();
+ flags_cmd_out |= f_respo2;
+ cc_ReadStatus();
+ i=ResponseStatus();
+ } else {
+ }
+ }
+ i=DiskInfo();
+ return (i);
+}
+
+static int sbpcd_tray_move(struct cdrom_device_info *cdi, int position)
+{
+ int retval=0;
+ switch_drive(cdi->handle);
+ /* DUH! --AJK */
+ if(current_drive->CD_changed != 0xFF) {
+ current_drive->CD_changed=0xFF;
+ current_drive->diskstate_flags &= ~cd_size_bit;
+ }
+ if (position == 1) {
+ cc_SpinDown();
+ } else {
+ retval=cc_CloseTray();
+ }
+ return retval;
+}
+
+/*==========================================================================*/
+static int cc_ReadSubQ(void)
+{
+ int i,j;
+
+ current_drive->diskstate_flags &= ~subq_bit;
+ for (j=255;j>0;j--)
+ {
+ clr_cmdbuf();
+ if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_READSUBQ;
+ flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+ response_count=11;
+ }
+ else if (fam2_drive)
+ {
+ drvcmd[0]=CMD2_READSUBQ;
+ drvcmd[1]=0x02;
+ drvcmd[3]=0x01;
+ flags_cmd_out=f_putcmd;
+ response_count=10;
+ }
+ else if (fam0LV_drive)
+ {
+ drvcmd[0]=CMD0_READSUBQ;
+ drvcmd[1]=0x02;
+ if (famLV_drive)
+ flags_cmd_out=f_putcmd;
+ else
+ flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
+ response_count=13;
+ }
+ else if (famT_drive)
+ {
+ response_count=12;
+ drvcmd[0]=CMDT_READSUBQ;
+ drvcmd[1]=0x02;
+ drvcmd[2]=0x40;
+ drvcmd[3]=0x01;
+ drvcmd[8]=response_count;
+ }
+ i=cmd_out();
+ if (i<0) return (i);
+ for (i=0;iSubQ_ctl_adr=current_drive->SubQ_trk=current_drive->SubQ_pnt_idx=current_drive->SubQ_whatisthis=0;
+ current_drive->SubQ_run_tot=current_drive->SubQ_run_trk=0;
+ return (0);
+ }
+ }
+ if (famT_drive) current_drive->SubQ_ctl_adr=infobuf[1];
+ else current_drive->SubQ_ctl_adr=swap_nibbles(infobuf[1]);
+ current_drive->SubQ_trk=byt2bcd(infobuf[2]);
+ current_drive->SubQ_pnt_idx=byt2bcd(infobuf[3]);
+ if (fam0LV_drive) i=5;
+ else if (fam12_drive) i=4;
+ else if (famT_drive) i=8;
+ current_drive->SubQ_run_tot=make32(make16(0,infobuf[i]),make16(infobuf[i+1],infobuf[i+2])); /* msf-bin */
+ i=7;
+ if (fam0LV_drive) i=9;
+ else if (fam12_drive) i=7;
+ else if (famT_drive) i=4;
+ current_drive->SubQ_run_trk=make32(make16(0,infobuf[i]),make16(infobuf[i+1],infobuf[i+2])); /* msf-bin */
+ current_drive->SubQ_whatisthis=infobuf[i+3];
+ current_drive->diskstate_flags |= subq_bit;
+ return (0);
+}
+/*==========================================================================*/
+static int cc_ModeSense(void)
+{
+ int i;
+
+ if (fam2_drive) return (0);
+ if (famV_drive) return (0);
+ current_drive->diskstate_flags &= ~frame_size_bit;
+ clr_cmdbuf();
+ if (fam1_drive)
+ {
+ response_count=5;
+ drvcmd[0]=CMD1_GETMODE;
+ flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (fam0L_drive)
+ {
+ response_count=2;
+ drvcmd[0]=CMD0_GETMODE;
+ if (famL_drive) flags_cmd_out=f_putcmd;
+ else flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (famT_drive)
+ {
+ response_count=10;
+ drvcmd[0]=CMDT_GETMODE;
+ drvcmd[4]=response_count;
+ }
+ i=cmd_out();
+ if (i<0) return (i);
+ i=0;
+ current_drive->sense_byte=0;
+ if (fam1_drive) current_drive->sense_byte=infobuf[i++];
+ else if (famT_drive)
+ {
+ if (infobuf[4]==0x01) current_drive->xa_byte=0x20;
+ else current_drive->xa_byte=0;
+ i=2;
+ }
+ current_drive->frame_size=make16(infobuf[i],infobuf[i+1]);
+ for (i=0;idiskstate_flags |= frame_size_bit;
+ return (0);
+}
+/*==========================================================================*/
+/*==========================================================================*/
+static int cc_ModeSelect(int framesize)
+{
+ int i;
+
+ if (fam2_drive) return (0);
+ if (famV_drive) return (0);
+ current_drive->diskstate_flags &= ~frame_size_bit;
+ clr_cmdbuf();
+ current_drive->frame_size=framesize;
+ if (framesize==CD_FRAMESIZE_RAW) current_drive->sense_byte=0x82;
+ else current_drive->sense_byte=0x00;
+
+ msg(DBG_XA1,"cc_ModeSelect: %02X %04X\n",
+ current_drive->sense_byte, current_drive->frame_size);
+
+ if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_SETMODE;
+ drvcmd[1]=0x00;
+ drvcmd[2]=current_drive->sense_byte;
+ drvcmd[3]=(current_drive->frame_size>>8)&0xFF;
+ drvcmd[4]=current_drive->frame_size&0xFF;
+ flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (fam0L_drive)
+ {
+ drvcmd[0]=CMD0_SETMODE;
+ drvcmd[1]=0x00;
+ drvcmd[2]=(current_drive->frame_size>>8)&0xFF;
+ drvcmd[3]=current_drive->frame_size&0xFF;
+ drvcmd[4]=0x00;
+ if(famL_drive)
+ flags_cmd_out=f_putcmd|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check;
+ else
+ flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (famT_drive)
+ {
+ return (-1);
+ }
+ response_count=0;
+ i=cmd_out();
+ if (i<0) return (i);
+ current_drive->diskstate_flags |= frame_size_bit;
+ return (0);
+}
+/*==========================================================================*/
+static int cc_GetVolume(void)
+{
+ int i;
+ u_char switches;
+ u_char chan0=0;
+ u_char vol0=0;
+ u_char chan1=1;
+ u_char vol1=0;
+
+ if (famV_drive) return (0);
+ current_drive->diskstate_flags &= ~volume_bit;
+ clr_cmdbuf();
+ if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_GETMODE;
+ drvcmd[1]=0x05;
+ response_count=5;
+ flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (fam2_drive)
+ {
+ drvcmd[0]=CMD2_GETMODE;
+ drvcmd[1]=0x0E;
+ response_count=5;
+ flags_cmd_out=f_putcmd;
+ }
+ else if (fam0L_drive)
+ {
+ drvcmd[0]=CMD0_GETMODE;
+ drvcmd[1]=0x03;
+ response_count=2;
+ if(famL_drive)
+ flags_cmd_out=f_putcmd;
+ else
+ flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (famT_drive)
+ {
+ i=cc_get_mode_T();
+ if (i<0) return (i);
+ }
+ if (!famT_drive)
+ {
+ i=cmd_out();
+ if (i<0) return (i);
+ }
+ if (fam1_drive)
+ {
+ chan0=infobuf[1]&0x0F;
+ vol0=infobuf[2];
+ chan1=infobuf[3]&0x0F;
+ vol1=infobuf[4];
+ if (chan0==0)
+ {
+ chan0=1;
+ vol0=0;
+ }
+ if (chan1==0)
+ {
+ chan1=2;
+ vol1=0;
+ }
+ chan0 >>= 1;
+ chan1 >>= 1;
+ }
+ else if (fam2_drive)
+ {
+ chan0=infobuf[1];
+ vol0=infobuf[2];
+ chan1=infobuf[3];
+ vol1=infobuf[4];
+ }
+ else if (famL_drive)
+ {
+ chan0=0;
+ chan1=1;
+ vol0=vol1=infobuf[1];
+ switches=infobuf[0];
+ if ((switches&0x80)!=0) chan0=1;
+ if ((switches&0x40)!=0) chan1=0;
+ }
+ else if (fam0_drive) /* different firmware levels */
+ {
+ chan0=0;
+ chan1=1;
+ vol0=vol1=infobuf[1];
+ if (current_drive->drv_type>=drv_201)
+ {
+ if (current_drive->drv_typedrv_type>=drv_211)
+ {
+ if ((switches&0x20)!=0) chan0=1;
+ if ((switches&0x10)!=0) chan1=0;
+ }
+ }
+ else
+ {
+ vol0=infobuf[0];
+ if ((vol0&0x01)!=0) chan0=1;
+ if ((vol1&0x01)==0) chan1=0;
+ vol0 &= 0xFC;
+ vol1 &= 0xFC;
+ if (vol0!=0) vol0 += 3;
+ if (vol1!=0) vol1 += 3;
+ }
+ }
+ }
+ else if (famT_drive)
+ {
+ current_drive->volume_control=infobuf[7];
+ chan0=0;
+ chan1=1;
+ if (current_drive->volume_control&0x10) vol0=0;
+ else vol0=0xff;
+ if (current_drive->volume_control&0x20) vol1=0;
+ else vol1=0xff;
+ }
+ current_drive->vol_chan0=chan0;
+ current_drive->vol_ctrl0=vol0;
+ current_drive->vol_chan1=chan1;
+ current_drive->vol_ctrl1=vol1;
+#if 000
+ current_drive->vol_chan2=2;
+ current_drive->vol_ctrl2=0xFF;
+ current_drive->vol_chan3=3;
+ current_drive->vol_ctrl3=0xFF;
+#endif /* 000 */
+ current_drive->diskstate_flags |= volume_bit;
+ return (0);
+}
+/*==========================================================================*/
+static int cc_ReadCapacity(void)
+{
+ int i, j;
+
+ if (fam2_drive) return (0); /* some firmware lacks this command */
+ if (famLV_drive) return (0); /* some firmware lacks this command */
+ if (famT_drive) return (0); /* done with cc_ReadTocDescr() */
+ current_drive->diskstate_flags &= ~cd_size_bit;
+ for (j=3;j>0;j--)
+ {
+ clr_cmdbuf();
+ if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_CAPACITY;
+ response_count=5;
+ flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+ }
+#if 00
+ else if (fam2_drive)
+ {
+ drvcmd[0]=CMD2_CAPACITY;
+ response_count=8;
+ flags_cmd_out=f_putcmd;
+ }
+#endif
+ else if (fam0_drive)
+ {
+ drvcmd[0]=CMD0_CAPACITY;
+ response_count=5;
+ flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
+ }
+ i=cmd_out();
+ if (i>=0) break;
+ msg(DBG_000,"cc_ReadCapacity: cmd_out: err %d\n", i);
+ cc_ReadError();
+ }
+ if (j==0) return (i);
+ if (fam1_drive) current_drive->CDsize_frm=msf2blk(make32(make16(0,infobuf[0]),make16(infobuf[1],infobuf[2])))+CD_MSF_OFFSET;
+ else if (fam0_drive) current_drive->CDsize_frm=make32(make16(0,infobuf[0]),make16(infobuf[1],infobuf[2]));
+#if 00
+ else if (fam2_drive) current_drive->CDsize_frm=make32(make16(infobuf[0],infobuf[1]),make16(infobuf[2],infobuf[3]));
+#endif
+ current_drive->diskstate_flags |= cd_size_bit;
+ msg(DBG_000,"cc_ReadCapacity: %d frames.\n", current_drive->CDsize_frm);
+ return (0);
+}
+/*==========================================================================*/
+static int cc_ReadTocDescr(void)
+{
+ int i;
+
+ current_drive->diskstate_flags &= ~toc_bit;
+ clr_cmdbuf();
+ if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_DISKINFO;
+ response_count=6;
+ flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (fam0LV_drive)
+ {
+ drvcmd[0]=CMD0_DISKINFO;
+ response_count=6;
+ if(famLV_drive)
+ flags_cmd_out=f_putcmd;
+ else
+ flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (fam2_drive)
+ {
+ /* possibly longer timeout periods necessary */
+ current_drive->f_multisession=0;
+ drvcmd[0]=CMD2_DISKINFO;
+ drvcmd[1]=0x02;
+ drvcmd[2]=0xAB;
+ drvcmd[3]=0xFF; /* session */
+ response_count=8;
+ flags_cmd_out=f_putcmd;
+ }
+ else if (famT_drive)
+ {
+ current_drive->f_multisession=0;
+ response_count=12;
+ drvcmd[0]=CMDT_DISKINFO;
+ drvcmd[1]=0x02;
+ drvcmd[6]=CDROM_LEADOUT;
+ drvcmd[8]=response_count;
+ drvcmd[9]=0x00;
+ }
+ i=cmd_out();
+ if (i<0) return (i);
+ if ((famT_drive)&&(ixa_byte=infobuf[0];
+ if (fam2_drive)
+ {
+ current_drive->first_session=infobuf[1];
+ current_drive->last_session=infobuf[2];
+ current_drive->n_first_track=infobuf[3];
+ current_drive->n_last_track=infobuf[4];
+ if (current_drive->first_session!=current_drive->last_session)
+ {
+ current_drive->f_multisession=1;
+ current_drive->lba_multi=msf2blk(make32(make16(0,infobuf[5]),make16(infobuf[6],infobuf[7])));
+ }
+#if 0
+ if (current_drive->first_session!=current_drive->last_session)
+ {
+ if (current_drive->last_session<=20)
+ zwanzig=current_drive->last_session+1;
+ else zwanzig=20;
+ for (count=current_drive->first_session;countmsf_multi_n[count]=make32(make16(0,infobuf[5]),make16(infobuf[6],infobuf[7]));
+ }
+ current_drive->diskstate_flags |= multisession_bit;
+ }
+#endif
+ drvcmd[0]=CMD2_DISKINFO;
+ drvcmd[1]=0x02;
+ drvcmd[2]=0xAA;
+ drvcmd[3]=0xFF;
+ response_count=5;
+ flags_cmd_out=f_putcmd;
+ i=cmd_out();
+ if (i<0) return (i);
+ current_drive->size_msf=make32(make16(0,infobuf[2]),make16(infobuf[3],infobuf[4]));
+ current_drive->size_blk=msf2blk(current_drive->size_msf);
+ current_drive->CDsize_frm=current_drive->size_blk+1;
+ }
+ else if (famT_drive)
+ {
+ current_drive->size_msf=make32(make16(infobuf[8],infobuf[9]),make16(infobuf[10],infobuf[11]));
+ current_drive->size_blk=msf2blk(current_drive->size_msf);
+ current_drive->CDsize_frm=current_drive->size_blk+1;
+ current_drive->n_first_track=infobuf[2];
+ current_drive->n_last_track=infobuf[3];
+ }
+ else
+ {
+ current_drive->n_first_track=infobuf[1];
+ current_drive->n_last_track=infobuf[2];
+ current_drive->size_msf=make32(make16(0,infobuf[3]),make16(infobuf[4],infobuf[5]));
+ current_drive->size_blk=msf2blk(current_drive->size_msf);
+ if (famLV_drive) current_drive->CDsize_frm=current_drive->size_blk+1;
+ }
+ current_drive->diskstate_flags |= toc_bit;
+ msg(DBG_TOC,"TocDesc: xa %02X firstt %02X lastt %02X size %08X firstses %02X lastsess %02X\n",
+ current_drive->xa_byte,
+ current_drive->n_first_track,
+ current_drive->n_last_track,
+ current_drive->size_msf,
+ current_drive->first_session,
+ current_drive->last_session);
+ return (0);
+}
+/*==========================================================================*/
+static int cc_ReadTocEntry(int num)
+{
+ int i;
+
+ clr_cmdbuf();
+ if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_READTOC;
+ drvcmd[2]=num;
+ response_count=8;
+ flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (fam2_drive)
+ {
+ /* possibly longer timeout periods necessary */
+ drvcmd[0]=CMD2_DISKINFO;
+ drvcmd[1]=0x02;
+ drvcmd[2]=num;
+ response_count=5;
+ flags_cmd_out=f_putcmd;
+ }
+ else if (fam0LV_drive)
+ {
+ drvcmd[0]=CMD0_READTOC;
+ drvcmd[1]=0x02;
+ drvcmd[2]=num;
+ response_count=8;
+ if (famLV_drive)
+ flags_cmd_out=f_putcmd;
+ else
+ flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (famT_drive)
+ {
+ response_count=12;
+ drvcmd[0]=CMDT_DISKINFO;
+ drvcmd[1]=0x02;
+ drvcmd[6]=num;
+ drvcmd[8]=response_count;
+ drvcmd[9]=0x00;
+ }
+ i=cmd_out();
+ if (i<0) return (i);
+ if ((famT_drive)&&(iTocEnt_nixbyte=infobuf[0];
+ i=1;
+ }
+ else if (fam2_drive) i=0;
+ else if (famT_drive) i=5;
+ current_drive->TocEnt_ctl_adr=swap_nibbles(infobuf[i++]);
+ if ((fam1_drive)||(fam0L_drive))
+ {
+ current_drive->TocEnt_number=infobuf[i++];
+ current_drive->TocEnt_format=infobuf[i];
+ }
+ else
+ {
+ current_drive->TocEnt_number=num;
+ current_drive->TocEnt_format=0;
+ }
+ if (fam1_drive) i=4;
+ else if (fam0LV_drive) i=5;
+ else if (fam2_drive) i=2;
+ else if (famT_drive) i=9;
+ current_drive->TocEnt_address=make32(make16(0,infobuf[i]),
+ make16(infobuf[i+1],infobuf[i+2]));
+ for (i=0;iTocEnt_nixbyte, current_drive->TocEnt_ctl_adr,
+ current_drive->TocEnt_number, current_drive->TocEnt_format,
+ current_drive->TocEnt_address);
+ return (0);
+}
+/*==========================================================================*/
+static int cc_ReadPacket(void)
+{
+ int i;
+
+ clr_cmdbuf();
+ drvcmd[0]=CMD0_PACKET;
+ drvcmd[1]=response_count;
+ if(famL_drive) flags_cmd_out=f_putcmd;
+ else if (fam01_drive)
+ flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check;
+ else if (fam2_drive) return (-1); /* not implemented yet */
+ else if (famT_drive)
+ {
+ return (-1);
+ }
+ i=cmd_out();
+ return (i);
+}
+/*==========================================================================*/
+static int convert_UPC(u_char *p)
+{
+ int i;
+
+ p++;
+ if (fam0L_drive) p[13]=0;
+ for (i=0;i<7;i++)
+ {
+ if (fam1_drive) current_drive->UPC_buf[i]=swap_nibbles(*p++);
+ else if (fam0L_drive)
+ {
+ current_drive->UPC_buf[i]=((*p++)<<4)&0xFF;
+ current_drive->UPC_buf[i] |= *p++;
+ }
+ else if (famT_drive)
+ {
+ return (-1);
+ }
+ else /* CD200 */
+ {
+ return (-1);
+ }
+ }
+ current_drive->UPC_buf[6] &= 0xF0;
+ return (0);
+}
+/*==========================================================================*/
+static int cc_ReadUPC(void)
+{
+ int i;
+#if TEST_UPC
+ int block, checksum;
+#endif /* TEST_UPC */
+
+ if (fam2_drive) return (0); /* not implemented yet */
+ if (famT_drive) return (0); /* not implemented yet */
+ if (famV_drive) return (0); /* not implemented yet */
+#if 1
+ if (fam0_drive) return (0); /* but it should work */
+#endif
+
+ current_drive->diskstate_flags &= ~upc_bit;
+#if TEST_UPC
+ for (block=CD_MSF_OFFSET+1;block>16)&0xFF;
+ drvcmd[2]=(block>>8)&0xFF;
+ drvcmd[3]=block&0xFF;
+#endif /* TEST_UPC */
+ response_count=8;
+ flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (fam0L_drive)
+ {
+ drvcmd[0]=CMD0_READ_UPC;
+#if TEST_UPC
+ drvcmd[2]=(block>>16)&0xFF;
+ drvcmd[3]=(block>>8)&0xFF;
+ drvcmd[4]=block&0xFF;
+#endif /* TEST_UPC */
+ response_count=0;
+ flags_cmd_out=f_putcmd|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1;
+ }
+ else if (fam2_drive)
+ {
+ return (-1);
+ }
+ else if (famT_drive)
+ {
+ return (-1);
+ }
+ i=cmd_out();
+ if (i<0)
+ {
+ msg(DBG_000,"cc_ReadUPC cmd_out: err %d\n", i);
+ return (i);
+ }
+ if (fam0L_drive)
+ {
+ response_count=16;
+ if (famL_drive) flags_cmd_out=f_putcmd;
+ i=cc_ReadPacket();
+ if (i<0)
+ {
+ msg(DBG_000,"cc_ReadUPC ReadPacket: err %d\n", i);
+ return (i);
+ }
+ }
+#if TEST_UPC
+ checksum=0;
+#endif /* TEST_UPC */
+ for (i=0;i<(fam1_drive?8:16);i++)
+ {
+#if TEST_UPC
+ checksum |= infobuf[i];
+#endif /* TEST_UPC */
+ sprintf(&msgbuf[i*3], " %02X", infobuf[i]);
+ }
+ msgbuf[i*3]=0;
+ msg(DBG_UPC,"UPC info:%s\n", msgbuf);
+#if TEST_UPC
+ if ((checksum&0x7F)!=0) break;
+ }
+#endif /* TEST_UPC */
+ current_drive->UPC_ctl_adr=0;
+ if (fam1_drive) i=0;
+ else i=2;
+ if ((infobuf[i]&0x80)!=0)
+ {
+ convert_UPC(&infobuf[i]);
+ current_drive->UPC_ctl_adr = (current_drive->TocEnt_ctl_adr & 0xF0) | 0x02;
+ }
+ for (i=0;i<7;i++)
+ sprintf(&msgbuf[i*3], " %02X", current_drive->UPC_buf[i]);
+ sprintf(&msgbuf[i*3], " (%02X)", current_drive->UPC_ctl_adr);
+ msgbuf[i*3+5]=0;
+ msg(DBG_UPC,"UPC code:%s\n", msgbuf);
+ current_drive->diskstate_flags |= upc_bit;
+ return (0);
+}
+
+static int sbpcd_get_mcn(struct cdrom_device_info *cdi, struct cdrom_mcn *mcn)
+{
+ int i;
+ unsigned char *mcnp = mcn->medium_catalog_number;
+ unsigned char *resp;
+
+ current_drive->diskstate_flags &= ~upc_bit;
+ clr_cmdbuf();
+ if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_READ_UPC;
+ response_count=8;
+ flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+ }
+ else if (fam0L_drive)
+ {
+ drvcmd[0]=CMD0_READ_UPC;
+ response_count=0;
+ flags_cmd_out=f_putcmd|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1;
+ }
+ else if (fam2_drive)
+ {
+ return (-1);
+ }
+ else if (famT_drive)
+ {
+ return (-1);
+ }
+ i=cmd_out();
+ if (i<0)
+ {
+ msg(DBG_000,"cc_ReadUPC cmd_out: err %d\n", i);
+ return (i);
+ }
+ if (fam0L_drive)
+ {
+ response_count=16;
+ if (famL_drive) flags_cmd_out=f_putcmd;
+ i=cc_ReadPacket();
+ if (i<0)
+ {
+ msg(DBG_000,"cc_ReadUPC ReadPacket: err %d\n", i);
+ return (i);
+ }
+ }
+ current_drive->UPC_ctl_adr=0;
+ if (fam1_drive) i=0;
+ else i=2;
+
+ resp = infobuf + i;
+ if (*resp++ == 0x80) {
+ /* packed bcd to single ASCII digits */
+ *mcnp++ = (*resp >> 4) + '0';
+ *mcnp++ = (*resp++ & 0x0f) + '0';
+ *mcnp++ = (*resp >> 4) + '0';
+ *mcnp++ = (*resp++ & 0x0f) + '0';
+ *mcnp++ = (*resp >> 4) + '0';
+ *mcnp++ = (*resp++ & 0x0f) + '0';
+ *mcnp++ = (*resp >> 4) + '0';
+ *mcnp++ = (*resp++ & 0x0f) + '0';
+ *mcnp++ = (*resp >> 4) + '0';
+ *mcnp++ = (*resp++ & 0x0f) + '0';
+ *mcnp++ = (*resp >> 4) + '0';
+ *mcnp++ = (*resp++ & 0x0f) + '0';
+ *mcnp++ = (*resp >> 4) + '0';
+ }
+ *mcnp = '\0';
+
+ current_drive->diskstate_flags |= upc_bit;
+ return (0);
+}
+
+/*==========================================================================*/
+static int cc_CheckMultiSession(void)
+{
+ int i;
+
+ if (fam2_drive) return (0);
+ current_drive->f_multisession=0;
+ current_drive->lba_multi=0;
+ if (fam0_drive) return (0);
+ clr_cmdbuf();
+ if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_MULTISESS;
+ response_count=6;
+ flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check;
+ i=cmd_out();
+ if (i<0) return (i);
+ if ((infobuf[0]&0x80)!=0)
+ {
+ current_drive->f_multisession=1;
+ current_drive->lba_multi=msf2blk(make32(make16(0,infobuf[1]),
+ make16(infobuf[2],infobuf[3])));
+ }
+ }
+ else if (famLV_drive)
+ {
+ drvcmd[0]=CMDL_MULTISESS;
+ drvcmd[1]=3;
+ drvcmd[2]=1;
+ response_count=8;
+ flags_cmd_out=f_putcmd;
+ i=cmd_out();
+ if (i<0) return (i);
+ current_drive->lba_multi=msf2blk(make32(make16(0,infobuf[5]),
+ make16(infobuf[6],infobuf[7])));
+ }
+ else if (famT_drive)
+ {
+ response_count=12;
+ drvcmd[0]=CMDT_DISKINFO;
+ drvcmd[1]=0x02;
+ drvcmd[6]=0;
+ drvcmd[8]=response_count;
+ drvcmd[9]=0x40;
+ i=cmd_out();
+ if (i<0) return (i);
+ if (ifirst_session=infobuf[2];
+ current_drive->last_session=infobuf[3];
+ current_drive->track_of_last_session=infobuf[6];
+ if (current_drive->first_session!=current_drive->last_session)
+ {
+ current_drive->f_multisession=1;
+ current_drive->lba_multi=msf2blk(make32(make16(0,infobuf[9]),make16(infobuf[10],infobuf[11])));
+ }
+ }
+ for (i=0;ilba_multi);
+ if (current_drive->lba_multi>200)
+ {
+ current_drive->f_multisession=1;
+ msg(DBG_MUL,"MultiSession base: %06X\n", current_drive->lba_multi);
+ }
+ return (0);
+}
+/*==========================================================================*/
+#ifdef FUTURE
+static int cc_SubChanInfo(int frame, int count, u_char *buffer)
+ /* "frame" is a RED BOOK (msf-bin) address */
+{
+ int i;
+
+ if (fam0LV_drive) return (-ENOSYS); /* drive firmware lacks it */
+ if (famT_drive)
+ {
+ return (-1);
+ }
+#if 0
+ if (current_drive->audio_state!=audio_playing) return (-ENODATA);
+#endif
+ clr_cmdbuf();
+ drvcmd[0]=CMD1_SUBCHANINF;
+ drvcmd[1]=(frame>>16)&0xFF;
+ drvcmd[2]=(frame>>8)&0xFF;
+ drvcmd[3]=frame&0xFF;
+ drvcmd[5]=(count>>8)&0xFF;
+ drvcmd[6]=count&0xFF;
+ flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check;
+ cmd_type=READ_SC;
+ current_drive->frame_size=CD_FRAMESIZE_SUB;
+ i=cmd_out(); /* which buffer to use? */
+ return (i);
+}
+#endif /* FUTURE */
+/*==========================================================================*/
+static void __init check_datarate(void)
+{
+ int i=0;
+
+ msg(DBG_IOX,"check_datarate entered.\n");
+ datarate=0;
+#if TEST_STI
+ for (i=0;i<=1000;i++) printk(".");
+#endif
+ /* set a timer to make (timed_out_delay!=0) after 1.1 seconds */
+#if 1
+ del_timer(&delay_timer);
+#endif
+ delay_timer.expires=jiffies+11*HZ/10;
+ timed_out_delay=0;
+ add_timer(&delay_timer);
+#if 0
+ msg(DBG_TIM,"delay timer started (11*HZ/10).\n");
+#endif
+ do
+ {
+ i=inb(CDi_status);
+ datarate++;
+#if 1
+ if (datarate>0x6FFFFFFF) break;
+#endif
+ }
+ while (!timed_out_delay);
+ del_timer(&delay_timer);
+#if 0
+ msg(DBG_TIM,"datarate: %04X\n", datarate);
+#endif
+ if (datarate<65536) datarate=65536;
+ maxtim16=datarate*16;
+ maxtim04=datarate*4;
+ maxtim02=datarate*2;
+ maxtim_8=datarate/32;
+#if LONG_TIMING
+ maxtim_data=datarate/100;
+#else
+ maxtim_data=datarate/300;
+#endif /* LONG_TIMING */
+#if 0
+ msg(DBG_TIM,"maxtim_8 %d, maxtim_data %d.\n", maxtim_8, maxtim_data);
+#endif
+}
+/*==========================================================================*/
+#if 0
+static int c2_ReadError(int fam)
+{
+ int i;
+
+ clr_cmdbuf();
+ response_count=9;
+ clr_respo_buf(9);
+ if (fam==1)
+ {
+ drvcmd[0]=CMD0_READ_ERR; /* same as CMD1_ and CMDL_ */
+ i=do_cmd(f_putcmd|f_lopsta|f_getsta|f_ResponseStatus);
+ }
+ else if (fam==2)
+ {
+ drvcmd[0]=CMD2_READ_ERR;
+ i=do_cmd(f_putcmd);
+ }
+ else return (-1);
+ return (i);
+}
+#endif
+/*==========================================================================*/
+static void __init ask_mail(void)
+{
+ int i;
+
+ msg(DBG_INF, "please mail the following lines to emoenke@gwdg.de\n");
+ msg(DBG_INF, "(don't mail if you are not using the actual kernel):\n");
+ msg(DBG_INF, "%s\n", VERSION);
+ msg(DBG_INF, "address %03X, type %s, drive %s (ID %d)\n",
+ CDo_command, type, current_drive->drive_model, current_drive->drv_id);
+ for (i=0;i<12;i++)
+ sprintf(&msgbuf[i*3], " %02X", infobuf[i]);
+ msgbuf[i*3]=0;
+ msg(DBG_INF,"infobuf =%s\n", msgbuf);
+ for (i=0;i<12;i++)
+ sprintf(&msgbuf[i*3], " %c ", infobuf[i]);
+ msgbuf[i*3]=0;
+ msg(DBG_INF,"infobuf =%s\n", msgbuf);
+}
+/*==========================================================================*/
+static int __init check_version(void)
+{
+ int i, j, l;
+ int teac_possible=0;
+
+ msg(DBG_INI,"check_version: id=%d, d=%d.\n", current_drive->drv_id, current_drive - D_S);
+ current_drive->drv_type=0;
+
+ /* check for CR-52x, CR-56x, LCS-7260 and ECS-AT */
+ /* clear any pending error state */
+ clr_cmdbuf();
+ drvcmd[0]=CMD0_READ_ERR; /* same as CMD1_ and CMDL_ */
+ response_count=9;
+ flags_cmd_out=f_putcmd;
+ i=cmd_out();
+ if (i<0) msg(DBG_INI,"CMD0_READ_ERR returns %d (ok anyway).\n",i);
+ /* read drive version */
+ clr_cmdbuf();
+ for (i=0;i<12;i++) infobuf[i]=0;
+ drvcmd[0]=CMD0_READ_VER; /* same as CMD1_ and CMDL_ */
+ response_count=12; /* fam1: only 11 */
+ flags_cmd_out=f_putcmd;
+ i=cmd_out();
+ if (i<-1) msg(DBG_INI,"CMD0_READ_VER returns %d\n",i);
+ if (i==-11) teac_possible++;
+ j=0;
+ for (i=0;i<12;i++) j+=infobuf[i];
+ if (j)
+ {
+ for (i=0;i<12;i++)
+ sprintf(&msgbuf[i*3], " %02X", infobuf[i]);
+ msgbuf[i*3]=0;
+ msg(DBG_ECS,"infobuf =%s\n", msgbuf);
+ for (i=0;i<12;i++)
+ sprintf(&msgbuf[i*3], " %c ", infobuf[i]);
+ msgbuf[i*3]=0;
+ msg(DBG_ECS,"infobuf =%s\n", msgbuf);
+ }
+ for (i=0;i<4;i++) if (infobuf[i]!=family1[i]) break;
+ if (i==4)
+ {
+ current_drive->drive_model[0]='C';
+ current_drive->drive_model[1]='R';
+ current_drive->drive_model[2]='-';
+ current_drive->drive_model[3]='5';
+ current_drive->drive_model[4]=infobuf[i++];
+ current_drive->drive_model[5]=infobuf[i++];
+ current_drive->drive_model[6]=0;
+ current_drive->drv_type=drv_fam1;
+ }
+ if (!current_drive->drv_type)
+ {
+ for (i=0;i<8;i++) if (infobuf[i]!=family0[i]) break;
+ if (i==8)
+ {
+ current_drive->drive_model[0]='C';
+ current_drive->drive_model[1]='R';
+ current_drive->drive_model[2]='-';
+ current_drive->drive_model[3]='5';
+ current_drive->drive_model[4]='2';
+ current_drive->drive_model[5]='x';
+ current_drive->drive_model[6]=0;
+ current_drive->drv_type=drv_fam0;
+ }
+ }
+ if (!current_drive->drv_type)
+ {
+ for (i=0;i<8;i++) if (infobuf[i]!=familyL[i]) break;
+ if (i==8)
+ {
+ for (j=0;j<8;j++)
+ current_drive->drive_model[j]=infobuf[j];
+ current_drive->drive_model[8]=0;
+ current_drive->drv_type=drv_famL;
+ }
+ }
+ if (!current_drive->drv_type)
+ {
+ for (i=0;i<6;i++) if (infobuf[i]!=familyV[i]) break;
+ if (i==6)
+ {
+ for (j=0;j<6;j++)
+ current_drive->drive_model[j]=infobuf[j];
+ current_drive->drive_model[6]=0;
+ current_drive->drv_type=drv_famV;
+ i+=2; /* 2 blanks before version */
+ }
+ }
+ if (!current_drive->drv_type)
+ {
+ /* check for CD200 */
+ clr_cmdbuf();
+ drvcmd[0]=CMD2_READ_ERR;
+ response_count=9;
+ flags_cmd_out=f_putcmd;
+ i=cmd_out();
+ if (i<0) msg(DBG_INI,"CMD2_READERR returns %d (ok anyway).\n",i);
+ if (i<0) msg(DBG_000,"CMD2_READERR returns %d (ok anyway).\n",i);
+ /* read drive version */
+ clr_cmdbuf();
+ for (i=0;i<12;i++) infobuf[i]=0;
+ if (sbpro_type==1) OUT(CDo_sel_i_d,0);
+#if 0
+ OUT(CDo_reset,0);
+ sbp_sleep(6*HZ);
+ OUT(CDo_enable,current_drive->drv_sel);
+#endif
+ drvcmd[0]=CMD2_READ_VER;
+ response_count=12;
+ flags_cmd_out=f_putcmd;
+ i=cmd_out();
+ if (i<0) msg(DBG_INI,"CMD2_READ_VER returns %d\n",i);
+ if (i==-7) teac_possible++;
+ j=0;
+ for (i=0;i<12;i++) j+=infobuf[i];
+ if (j)
+ {
+ for (i=0;i<12;i++)
+ sprintf(&msgbuf[i*3], " %02X", infobuf[i]);
+ msgbuf[i*3]=0;
+ msg(DBG_IDX,"infobuf =%s\n", msgbuf);
+ for (i=0;i<12;i++)
+ sprintf(&msgbuf[i*3], " %c ", infobuf[i]);
+ msgbuf[i*3]=0;
+ msg(DBG_IDX,"infobuf =%s\n", msgbuf);
+ }
+ if (i>=0)
+ {
+ for (i=0;i<5;i++) if (infobuf[i]!=family2[i]) break;
+ if (i==5)
+ {
+ current_drive->drive_model[0]='C';
+ current_drive->drive_model[1]='D';
+ current_drive->drive_model[2]='2';
+ current_drive->drive_model[3]='0';
+ current_drive->drive_model[4]='0';
+ current_drive->drive_model[5]=infobuf[i++];
+ current_drive->drive_model[6]=infobuf[i++];
+ current_drive->drive_model[7]=0;
+ current_drive->drv_type=drv_fam2;
+ }
+ }
+ }
+ if (!current_drive->drv_type)
+ {
+ /* check for TEAC CD-55A */
+ msg(DBG_TEA,"teac_possible: %d\n",teac_possible);
+ for (j=1;j<=((current_drive->drv_id==0)?3:1);j++)
+ {
+ for (l=1;l<=((current_drive->drv_id==0)?10:1);l++)
+ {
+ msg(DBG_TEA,"TEAC reset #%d-%d.\n", j, l);
+ if (sbpro_type==1) OUT(CDo_reset,0);
+ else
+ {
+ OUT(CDo_enable,current_drive->drv_sel);
+ OUT(CDo_sel_i_d,0);
+ OUT(CDo_command,CMDT_RESET);
+ for (i=0;i<9;i++) OUT(CDo_command,0);
+ }
+ sbp_sleep(5*HZ/10);
+ OUT(CDo_enable,current_drive->drv_sel);
+ OUT(CDo_sel_i_d,0);
+ i=inb(CDi_status);
+ msg(DBG_TEA,"TEAC CDi_status: %02X.\n",i);
+#if 0
+ if (i&s_not_result_ready) continue; /* drive not present or ready */
+#endif
+ i=inb(CDi_info);
+ msg(DBG_TEA,"TEAC CDi_info: %02X.\n",i);
+ if (i==0x55) break; /* drive found */
+ }
+ if (i==0x55) break; /* drive found */
+ }
+ if (i==0x55) /* drive found */
+ {
+ msg(DBG_TEA,"TEAC drive found.\n");
+ clr_cmdbuf();
+ flags_cmd_out=f_putcmd;
+ response_count=12;
+ drvcmd[0]=CMDT_READ_VER;
+ drvcmd[4]=response_count;
+ for (i=0;i<12;i++) infobuf[i]=0;
+ i=cmd_out_T();
+ if (i!=0) msg(DBG_TEA,"cmd_out_T(CMDT_READ_VER) returns %d.\n",i);
+ for (i=1;i<6;i++) if (infobuf[i]!=familyT[i-1]) break;
+ if (i==6)
+ {
+ current_drive->drive_model[0]='C';
+ current_drive->drive_model[1]='D';
+ current_drive->drive_model[2]='-';
+ current_drive->drive_model[3]='5';
+ current_drive->drive_model[4]='5';
+ current_drive->drive_model[5]=0;
+ current_drive->drv_type=drv_famT;
+ }
+ }
+ }
+ if (!current_drive->drv_type)
+ {
+ msg(DBG_TEA,"no drive found at address %03X under ID %d.\n",CDo_command,current_drive->drv_id);
+ return (-522);
+ }
+ for (j=0;j<4;j++) current_drive->firmware_version[j]=infobuf[i+j];
+ if (famL_drive)
+ {
+ u_char lcs_firm_e1[]="A E1";
+ u_char lcs_firm_f4[]="A4F4";
+
+ for (j=0;j<4;j++)
+ if (current_drive->firmware_version[j]!=lcs_firm_e1[j]) break;
+ if (j==4) current_drive->drv_type=drv_e1;
+
+ for (j=0;j<4;j++)
+ if (current_drive->firmware_version[j]!=lcs_firm_f4[j]) break;
+ if (j==4) current_drive->drv_type=drv_f4;
+
+ if (current_drive->drv_type==drv_famL) ask_mail();
+ }
+ else if (famT_drive)
+ {
+ j=infobuf[4]; /* one-byte version??? - here: 0x15 */
+ if (j=='5')
+ {
+ current_drive->firmware_version[0]=infobuf[7];
+ current_drive->firmware_version[1]=infobuf[8];
+ current_drive->firmware_version[2]=infobuf[10];
+ current_drive->firmware_version[3]=infobuf[11];
+ }
+ else
+ {
+ if (j!=0x15) ask_mail();
+ current_drive->firmware_version[0]='0';
+ current_drive->firmware_version[1]='.';
+ current_drive->firmware_version[2]='0'+(j>>4);
+ current_drive->firmware_version[3]='0'+(j&0x0f);
+ }
+ }
+ else /* CR-52x, CR-56x, CD200, ECS-AT */
+ {
+ j = (current_drive->firmware_version[0] & 0x0F) * 100 +
+ (current_drive->firmware_version[2] & 0x0F) *10 +
+ (current_drive->firmware_version[3] & 0x0F);
+ if (fam0_drive)
+ {
+ if (j<200) current_drive->drv_type=drv_199;
+ else if (j<201) current_drive->drv_type=drv_200;
+ else if (j<210) current_drive->drv_type=drv_201;
+ else if (j<211) current_drive->drv_type=drv_210;
+ else if (j<300) current_drive->drv_type=drv_211;
+ else if (j>=300) current_drive->drv_type=drv_300;
+ }
+ else if (fam1_drive)
+ {
+ if (j<100) current_drive->drv_type=drv_099;
+ else
+ {
+ current_drive->drv_type=drv_100;
+ if ((j!=500)&&(j!=102)) ask_mail();
+ }
+ }
+ else if (fam2_drive)
+ {
+ if (current_drive->drive_model[5]=='F')
+ {
+ if ((j!=1)&&(j!=35)&&(j!=200)&&(j!=210))
+ ask_mail(); /* unknown version at time */
+ }
+ else
+ {
+ msg(DBG_INF,"this CD200 drive is not fully supported yet - only audio will work.\n");
+ if ((j!=101)&&(j!=35))
+ ask_mail(); /* unknown version at time */
+ }
+ }
+ else if (famV_drive)
+ {
+ if ((j==100)||(j==150)) current_drive->drv_type=drv_at;
+ ask_mail(); /* hopefully we get some feedback by this */
+ }
+ }
+ msg(DBG_LCS,"drive type %02X\n",current_drive->drv_type);
+ msg(DBG_INI,"check_version done.\n");
+ return (0);
+}
+/*==========================================================================*/
+static void switch_drive(struct sbpcd_drive *p)
+{
+ current_drive = p;
+ OUT(CDo_enable,current_drive->drv_sel);
+ msg(DBG_DID,"drive %d (ID=%d) activated.\n",
+ current_drive - D_S, current_drive->drv_id);
+ return;
+}
+/*==========================================================================*/
+#ifdef PATH_CHECK
+/*
+ * probe for the presence of an interface card
+ */
+static int __init check_card(int port)
+{
+#undef N_RESPO
+#define N_RESPO 20
+ int i, j, k;
+ u_char response[N_RESPO];
+ u_char save_port0;
+ u_char save_port3;
+
+ msg(DBG_INI,"check_card entered.\n");
+ save_port0=inb(port+0);
+ save_port3=inb(port+3);
+
+ for (j=0;j0;i--) OUT(port+0,0);
+ for (k=0;k0;i--)
+ {
+ if (inb(port+1)&s_not_result_ready) continue;
+ response[k]=inb(port+0);
+ break;
+ }
+ }
+ for (i=0;i0;i--) OUT(port+0,0);
+ for (k=0;k0;i--)
+ {
+ if (inb(port+1)&s_not_result_ready) continue;
+ response[k]=inb(port+0);
+ break;
+ }
+ }
+ for (i=0;i0;i--) OUT(port+0,0);
+ for (k=0;k0;i--)
+ {
+ if (inb(port+1)&s_not_result_ready) continue;
+ response[k]=inb(port+0);
+ break;
+ }
+ }
+ for (i=0;i0;i--) OUT(port+0,0);
+ for (k=0;k0;i--)
+ {
+ if (inb(port+1)&s_not_result_ready) continue;
+ response[k]=inb(port+0);
+ break;
+ }
+ }
+ for (i=0;idrv_id=j;
+ if (sbpro_type==1) p->drv_sel=(j&0x01)<<1|(j&0x02)>>1;
+ else p->drv_sel=j;
+ switch_drive(p);
+ msg(DBG_INI,"check_drives: drive %d (ID=%d) activated.\n",ndrives,j);
+ msg(DBG_000,"check_drives: drive %d (ID=%d) activated.\n",ndrives,j);
+ i=check_version();
+ if (i<0) msg(DBG_INI,"check_version returns %d.\n",i);
+ else
+ {
+ current_drive->drv_options=drv_pattern[j];
+ if (fam0L_drive) current_drive->drv_options&=~(speed_auto|speed_300|speed_150);
+ msg(DBG_INF, "Drive %d (ID=%d): %.9s (%.4s) at 0x%03X (type %d)\n",
+ current_drive - D_S,
+ current_drive->drv_id,
+ current_drive->drive_model,
+ current_drive->firmware_version,
+ CDo_command,
+ sbpro_type);
+ ndrives++;
+ }
+ }
+ for (j=ndrives;jpos_audio_end)-1;
+ if (i<0) return (-1);
+ i=cc_Seek(i,0);
+ return (i);
+}
+#endif /* FUTURE */
+/*==========================================================================*/
+static int ReadToC(void)
+{
+ int i, j;
+ current_drive->diskstate_flags &= ~toc_bit;
+ current_drive->ored_ctl_adr=0;
+ /* special handling of CD-I HE */
+ if ((current_drive->n_first_track == 2 && current_drive->n_last_track == 2) ||
+ current_drive->xa_byte == 0x10)
+ {
+ current_drive->TocBuffer[1].nixbyte=0;
+ current_drive->TocBuffer[1].ctl_adr=0x40;
+ current_drive->TocBuffer[1].number=1;
+ current_drive->TocBuffer[1].format=0;
+ current_drive->TocBuffer[1].address=blk2msf(0);
+ current_drive->ored_ctl_adr |= 0x40;
+ current_drive->n_first_track = 1;
+ current_drive->n_last_track = 1;
+ current_drive->xa_byte = 0x10;
+ j = 2;
+ } else
+ for (j=current_drive->n_first_track;j<=current_drive->n_last_track;j++)
+ {
+ i=cc_ReadTocEntry(j);
+ if (i<0)
+ {
+ msg(DBG_INF,"cc_ReadTocEntry(%d) returns %d.\n",j,i);
+ return (i);
+ }
+ current_drive->TocBuffer[j].nixbyte=current_drive->TocEnt_nixbyte;
+ current_drive->TocBuffer[j].ctl_adr=current_drive->TocEnt_ctl_adr;
+ current_drive->TocBuffer[j].number=current_drive->TocEnt_number;
+ current_drive->TocBuffer[j].format=current_drive->TocEnt_format;
+ current_drive->TocBuffer[j].address=current_drive->TocEnt_address;
+ current_drive->ored_ctl_adr |= current_drive->TocEnt_ctl_adr;
+ }
+ /* fake entry for LeadOut Track */
+ current_drive->TocBuffer[j].nixbyte=0;
+ current_drive->TocBuffer[j].ctl_adr=0;
+ current_drive->TocBuffer[j].number=CDROM_LEADOUT;
+ current_drive->TocBuffer[j].format=0;
+ current_drive->TocBuffer[j].address=current_drive->size_msf;
+
+ current_drive->diskstate_flags |= toc_bit;
+ return (0);
+}
+/*==========================================================================*/
+static int DiskInfo(void)
+{
+ int i, j;
+
+ current_drive->mode=READ_M1;
+
+#undef LOOP_COUNT
+#define LOOP_COUNT 10 /* needed for some "old" drives */
+
+ msg(DBG_000,"DiskInfo entered.\n");
+ for (j=1;j=0) break;
+ msg(DBG_INF,"DiskInfo: ReadCapacity #%d returns %d\n", j, i);
+#if 0
+ i=cc_DriveReset();
+#endif
+ if (!fam0_drive && j == 2) break;
+ }
+ if (j==LOOP_COUNT) return (-33); /* give up */
+
+ i=cc_ReadTocDescr();
+ if (i<0)
+ {
+ msg(DBG_INF,"DiskInfo: ReadTocDescr returns %d\n", i);
+ return (i);
+ }
+ i=ReadToC();
+ if (i<0)
+ {
+ msg(DBG_INF,"DiskInfo: ReadToC returns %d\n", i);
+ return (i);
+ }
+ i=cc_CheckMultiSession();
+ if (i<0)
+ {
+ msg(DBG_INF,"DiskInfo: cc_CheckMultiSession returns %d\n", i);
+ return (i);
+ }
+ if (current_drive->f_multisession) current_drive->sbp_bufsiz=1; /* possibly a weird PhotoCD */
+ else current_drive->sbp_bufsiz=buffers;
+ i=cc_ReadTocEntry(current_drive->n_first_track);
+ if (i<0)
+ {
+ msg(DBG_INF,"DiskInfo: cc_ReadTocEntry(1) returns %d\n", i);
+ return (i);
+ }
+ i=cc_ReadUPC();
+ if (i<0) msg(DBG_INF,"DiskInfo: cc_ReadUPC returns %d\n", i);
+ if ((fam0L_drive) && (current_drive->xa_byte==0x20 || current_drive->xa_byte == 0x10))
+ {
+ /* XA disk with old drive */
+ cc_ModeSelect(CD_FRAMESIZE_RAW1);
+ cc_ModeSense();
+ }
+ if (famT_drive) cc_prep_mode_T();
+ msg(DBG_000,"DiskInfo done.\n");
+ return (0);
+}
+
+static int sbpcd_drive_status(struct cdrom_device_info *cdi, int slot_nr)
+{
+ struct sbpcd_drive *p = cdi->handle;
+ int st;
+
+ if (CDSL_CURRENT != slot_nr) {
+ /* we have no changer support */
+ return -EINVAL;
+ }
+
+ cc_ReadStatus();
+ st=ResponseStatus();
+ if (st<0)
+ {
+ msg(DBG_INF,"sbpcd_drive_status: timeout.\n");
+ return (0);
+ }
+ msg(DBG_000,"Drive Status: door_locked =%d.\n", st_door_locked);
+ msg(DBG_000,"Drive Status: door_closed =%d.\n", st_door_closed);
+ msg(DBG_000,"Drive Status: caddy_in =%d.\n", st_caddy_in);
+ msg(DBG_000,"Drive Status: disk_ok =%d.\n", st_diskok);
+ msg(DBG_000,"Drive Status: spinning =%d.\n", st_spinning);
+ msg(DBG_000,"Drive Status: busy =%d.\n", st_busy);
+
+#if 0
+ if (!(p->status_bits & p_door_closed)) return CDS_TRAY_OPEN;
+ if (p->status_bits & p_disk_ok) return CDS_DISC_OK;
+ if (p->status_bits & p_disk_in) return CDS_DRIVE_NOT_READY;
+
+ return CDS_NO_DISC;
+#else
+ if (p->status_bits & p_spinning) return CDS_DISC_OK;
+/* return CDS_TRAY_OPEN; */
+ return CDS_NO_DISC;
+
+#endif
+
+}
+
+
+/*==========================================================================*/
+#ifdef FUTURE
+/*
+ * called always if driver gets entered
+ * returns 0 or ERROR2 or ERROR15
+ */
+static int prepare(u_char func, u_char subfunc)
+{
+ int i;
+
+ if (fam0L_drive)
+ {
+ i=inb(CDi_status);
+ if (i&s_attention) GetStatus();
+ }
+ else if (fam1_drive) GetStatus();
+ else if (fam2_drive) GetStatus();
+ else if (famT_drive) GetStatus();
+ if (current_drive->CD_changed==0xFF)
+ {
+ current_drive->diskstate_flags=0;
+ current_drive->audio_state=0;
+ if (!st_diskok)
+ {
+ i=check_allowed1(func,subfunc);
+ if (i<0) return (-2);
+ }
+ else
+ {
+ i=check_allowed3(func,subfunc);
+ if (i<0)
+ {
+ current_drive->CD_changed=1;
+ return (-15);
+ }
+ }
+ }
+ else
+ {
+ if (!st_diskok)
+ {
+ current_drive->diskstate_flags=0;
+ current_drive->audio_state=0;
+ i=check_allowed1(func,subfunc);
+ if (i<0) return (-2);
+ }
+ else
+ {
+ if (st_busy)
+ {
+ if (current_drive->audio_state!=audio_pausing)
+ {
+ i=check_allowed2(func,subfunc);
+ if (i<0) return (-2);
+ }
+ }
+ else
+ {
+ if (current_drive->audio_state==audio_playing) seek_pos_audio_end();
+ current_drive->audio_state=0;
+ }
+ if (!frame_size_valid)
+ {
+ i=DiskInfo();
+ if (i<0)
+ {
+ current_drive->diskstate_flags=0;
+ current_drive->audio_state=0;
+ i=check_allowed1(func,subfunc);
+ if (i<0) return (-2);
+ }
+ }
+ }
+ }
+ return (0);
+}
+#endif /* FUTURE */
+/*==========================================================================*/
+/*==========================================================================*/
+/*
+ * Check the results of the "get status" command.
+ */
+static int sbp_status(void)
+{
+ int st;
+
+ st=ResponseStatus();
+ if (st<0)
+ {
+ msg(DBG_INF,"sbp_status: timeout.\n");
+ return (0);
+ }
+
+ if (!st_spinning) msg(DBG_SPI,"motor got off - ignoring.\n");
+
+ if (st_check)
+ {
+ msg(DBG_INF,"st_check detected - retrying.\n");
+ return (0);
+ }
+ if (!st_door_closed)
+ {
+ msg(DBG_INF,"door is open - retrying.\n");
+ return (0);
+ }
+ if (!st_caddy_in)
+ {
+ msg(DBG_INF,"disk removed - retrying.\n");
+ return (0);
+ }
+ if (!st_diskok)
+ {
+ msg(DBG_INF,"!st_diskok detected - retrying.\n");
+ return (0);
+ }
+ if (st_busy)
+ {
+ msg(DBG_INF,"st_busy detected - retrying.\n");
+ return (0);
+ }
+ return (1);
+}
+/*==========================================================================*/
+
+static int sbpcd_get_last_session(struct cdrom_device_info *cdi, struct cdrom_multisession *ms_infp)
+{
+ struct sbpcd_drive *p = cdi->handle;
+ ms_infp->addr_format = CDROM_LBA;
+ ms_infp->addr.lba = p->lba_multi;
+ if (p->f_multisession)
+ ms_infp->xa_flag=1; /* valid redirection address */
+ else
+ ms_infp->xa_flag=0; /* invalid redirection address */
+
+ return 0;
+}
+
+static int sbpcd_audio_ioctl(struct cdrom_device_info *cdi, u_int cmd,
+ void * arg)
+{
+ struct sbpcd_drive *p = cdi->handle;
+ int i, st, j;
+
+ msg(DBG_IO2,"ioctl(%s, 0x%08lX, 0x%08p)\n", cdi->name, cmd, arg);
+ if (p->drv_id==-1) {
+ msg(DBG_INF, "ioctl: bad device: %s\n", cdi->name);
+ return (-ENXIO); /* no such drive */
+ }
+ down(&ioctl_read_sem);
+ if (p != current_drive)
+ switch_drive(p);
+
+ msg(DBG_IO2,"ioctl: device %s, request %04X\n",cdi->name,cmd);
+ switch (cmd) /* Sun-compatible */
+ {
+
+ case CDROMPAUSE: /* Pause the drive */
+ msg(DBG_IOC,"ioctl: CDROMPAUSE entered.\n");
+ /* pause the drive unit when it is currently in PLAY mode, */
+ /* or reset the starting and ending locations when in PAUSED mode. */
+ /* If applicable, at the next stopping point it reaches */
+ /* the drive will discontinue playing. */
+ switch (current_drive->audio_state)
+ {
+ case audio_playing:
+ if (famL_drive) i=cc_ReadSubQ();
+ else i=cc_Pause_Resume(1);
+ if (i<0) RETURN_UP(-EIO);
+ if (famL_drive) i=cc_Pause_Resume(1);
+ else i=cc_ReadSubQ();
+ if (i<0) RETURN_UP(-EIO);
+ current_drive->pos_audio_start=current_drive->SubQ_run_tot;
+ current_drive->audio_state=audio_pausing;
+ RETURN_UP(0);
+ case audio_pausing:
+ i=cc_Seek(current_drive->pos_audio_start,1);
+ if (i<0) RETURN_UP(-EIO);
+ RETURN_UP(0);
+ default:
+ RETURN_UP(-EINVAL);
+ }
+
+ case CDROMRESUME: /* resume paused audio play */
+ msg(DBG_IOC,"ioctl: CDROMRESUME entered.\n");
+ /* resume playing audio tracks when a previous PLAY AUDIO call has */
+ /* been paused with a PAUSE command. */
+ /* It will resume playing from the location saved in SubQ_run_tot. */
+ if (current_drive->audio_state!=audio_pausing) RETURN_UP(-EINVAL);
+ if (famL_drive)
+ i=cc_PlayAudio(current_drive->pos_audio_start,
+ current_drive->pos_audio_end);
+ else i=cc_Pause_Resume(3);
+ if (i<0) RETURN_UP(-EIO);
+ current_drive->audio_state=audio_playing;
+ RETURN_UP(0);
+
+ case CDROMPLAYMSF:
+ msg(DBG_IOC,"ioctl: CDROMPLAYMSF entered.\n");
+#ifdef SAFE_MIXED
+ if (current_drive->has_data>1) RETURN_UP(-EBUSY);
+#endif /* SAFE_MIXED */
+ if (current_drive->audio_state==audio_playing)
+ {
+ i=cc_Pause_Resume(1);
+ if (i<0) RETURN_UP(-EIO);
+ i=cc_ReadSubQ();
+ if (i<0) RETURN_UP(-EIO);
+ current_drive->pos_audio_start=current_drive->SubQ_run_tot;
+ i=cc_Seek(current_drive->pos_audio_start,1);
+ }
+ memcpy(&msf, (void *) arg, sizeof(struct cdrom_msf));
+ /* values come as msf-bin */
+ current_drive->pos_audio_start = (msf.cdmsf_min0<<16) |
+ (msf.cdmsf_sec0<<8) |
+ msf.cdmsf_frame0;
+ current_drive->pos_audio_end = (msf.cdmsf_min1<<16) |
+ (msf.cdmsf_sec1<<8) |
+ msf.cdmsf_frame1;
+ msg(DBG_IOX,"ioctl: CDROMPLAYMSF %08X %08X\n",
+ current_drive->pos_audio_start,current_drive->pos_audio_end);
+ i=cc_PlayAudio(current_drive->pos_audio_start,current_drive->pos_audio_end);
+ if (i<0)
+ {
+ msg(DBG_INF,"ioctl: cc_PlayAudio returns %d\n",i);
+ DriveReset();
+ current_drive->audio_state=0;
+ RETURN_UP(-EIO);
+ }
+ current_drive->audio_state=audio_playing;
+ RETURN_UP(0);
+
+ case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */
+ msg(DBG_IOC,"ioctl: CDROMPLAYTRKIND entered.\n");
+#ifdef SAFE_MIXED
+ if (current_drive->has_data>1) RETURN_UP(-EBUSY);
+#endif /* SAFE_MIXED */
+ if (current_drive->audio_state==audio_playing)
+ {
+ msg(DBG_IOX,"CDROMPLAYTRKIND: already audio_playing.\n");
+#if 1
+ RETURN_UP(0); /* just let us play on */
+#else
+ RETURN_UP(-EINVAL); /* play on, but say "error" */
+#endif
+ }
+ memcpy(&ti,(void *) arg,sizeof(struct cdrom_ti));
+ msg(DBG_IOX,"ioctl: trk0: %d, ind0: %d, trk1:%d, ind1:%d\n",
+ ti.cdti_trk0,ti.cdti_ind0,ti.cdti_trk1,ti.cdti_ind1);
+ if (ti.cdti_trk0n_first_track) RETURN_UP(-EINVAL);
+ if (ti.cdti_trk0>current_drive->n_last_track) RETURN_UP(-EINVAL);
+ if (ti.cdti_trk1current_drive->n_last_track) ti.cdti_trk1=current_drive->n_last_track;
+ current_drive->pos_audio_start=current_drive->TocBuffer[ti.cdti_trk0].address;
+ current_drive->pos_audio_end=current_drive->TocBuffer[ti.cdti_trk1+1].address;
+ i=cc_PlayAudio(current_drive->pos_audio_start,current_drive->pos_audio_end);
+ if (i<0)
+ {
+ msg(DBG_INF,"ioctl: cc_PlayAudio returns %d\n",i);
+ DriveReset();
+ current_drive->audio_state=0;
+ RETURN_UP(-EIO);
+ }
+ current_drive->audio_state=audio_playing;
+ RETURN_UP(0);
+
+ case CDROMREADTOCHDR: /* Read the table of contents header */
+ msg(DBG_IOC,"ioctl: CDROMREADTOCHDR entered.\n");
+ tochdr.cdth_trk0=current_drive->n_first_track;
+ tochdr.cdth_trk1=current_drive->n_last_track;
+ memcpy((void *) arg, &tochdr, sizeof(struct cdrom_tochdr));
+ RETURN_UP(0);
+
+ case CDROMREADTOCENTRY: /* Read an entry in the table of contents */
+ msg(DBG_IOC,"ioctl: CDROMREADTOCENTRY entered.\n");
+ memcpy(&tocentry, (void *) arg, sizeof(struct cdrom_tocentry));
+ i=tocentry.cdte_track;
+ if (i==CDROM_LEADOUT) i=current_drive->n_last_track+1;
+ else if (in_first_track||i>current_drive->n_last_track)
+ RETURN_UP(-EINVAL);
+ tocentry.cdte_adr=current_drive->TocBuffer[i].ctl_adr&0x0F;
+ tocentry.cdte_ctrl=(current_drive->TocBuffer[i].ctl_adr>>4)&0x0F;
+ tocentry.cdte_datamode=current_drive->TocBuffer[i].format;
+ if (tocentry.cdte_format==CDROM_MSF) /* MSF-bin required */
+ {
+ tocentry.cdte_addr.msf.minute=(current_drive->TocBuffer[i].address>>16)&0x00FF;
+ tocentry.cdte_addr.msf.second=(current_drive->TocBuffer[i].address>>8)&0x00FF;
+ tocentry.cdte_addr.msf.frame=current_drive->TocBuffer[i].address&0x00FF;
+ }
+ else if (tocentry.cdte_format==CDROM_LBA) /* blk required */
+ tocentry.cdte_addr.lba=msf2blk(current_drive->TocBuffer[i].address);
+ else RETURN_UP(-EINVAL);
+ memcpy((void *) arg, &tocentry, sizeof(struct cdrom_tocentry));
+ RETURN_UP(0);
+
+ case CDROMSTOP: /* Spin down the drive */
+ msg(DBG_IOC,"ioctl: CDROMSTOP entered.\n");
+#ifdef SAFE_MIXED
+ if (current_drive->has_data>1) RETURN_UP(-EBUSY);
+#endif /* SAFE_MIXED */
+ i=cc_Pause_Resume(1);
+ current_drive->audio_state=0;
+#if 0
+ cc_DriveReset();
+#endif
+ RETURN_UP(i);
+
+ case CDROMSTART: /* Spin up the drive */
+ msg(DBG_IOC,"ioctl: CDROMSTART entered.\n");
+ cc_SpinUp();
+ current_drive->audio_state=0;
+ RETURN_UP(0);
+
+ case CDROMVOLCTRL: /* Volume control */
+ msg(DBG_IOC,"ioctl: CDROMVOLCTRL entered.\n");
+ memcpy(&volctrl,(char *) arg,sizeof(volctrl));
+ current_drive->vol_chan0=0;
+ current_drive->vol_ctrl0=volctrl.channel0;
+ current_drive->vol_chan1=1;
+ current_drive->vol_ctrl1=volctrl.channel1;
+ i=cc_SetVolume();
+ RETURN_UP(0);
+
+ case CDROMVOLREAD: /* read Volume settings from drive */
+ msg(DBG_IOC,"ioctl: CDROMVOLREAD entered.\n");
+ st=cc_GetVolume();
+ if (st<0) RETURN_UP(st);
+ volctrl.channel0=current_drive->vol_ctrl0;
+ volctrl.channel1=current_drive->vol_ctrl1;
+ volctrl.channel2=0;
+ volctrl.channel2=0;
+ memcpy((void *)arg,&volctrl,sizeof(volctrl));
+ RETURN_UP(0);
+
+ case CDROMSUBCHNL: /* Get subchannel info */
+ msg(DBG_IOS,"ioctl: CDROMSUBCHNL entered.\n");
+ /* Bogus, I can do better than this! --AJK
+ if ((st_spinning)||(!subq_valid)) {
+ i=cc_ReadSubQ();
+ if (i<0) RETURN_UP(-EIO);
+ }
+ */
+ i=cc_ReadSubQ();
+ if (i<0) {
+ j=cc_ReadError(); /* clear out error status from drive */
+ current_drive->audio_state=CDROM_AUDIO_NO_STATUS;
+ /* get and set the disk state here,
+ probably not the right place, but who cares!
+ It makes it work properly! --AJK */
+ if (current_drive->CD_changed==0xFF) {
+ msg(DBG_000,"Disk changed detect\n");
+ current_drive->diskstate_flags &= ~cd_size_bit;
+ }
+ RETURN_UP(-EIO);
+ }
+ if (current_drive->CD_changed==0xFF) {
+ /* reread the TOC because the disk has changed! --AJK */
+ msg(DBG_000,"Disk changed STILL detected, rereading TOC!\n");
+ i=DiskInfo();
+ if(i==0) {
+ current_drive->CD_changed=0x00; /* cd has changed, procede, */
+ RETURN_UP(-EIO); /* and get TOC, etc on next try! --AJK */
+ } else {
+ RETURN_UP(-EIO); /* we weren't ready yet! --AJK */
+ }
+ }
+ memcpy(&SC, (void *) arg, sizeof(struct cdrom_subchnl));
+ /*
+ This virtual crap is very bogus!
+ It doesn't detect when the cd is done playing audio!
+ Lets do this right with proper hardware register reading!
+ */
+ cc_ReadStatus();
+ i=ResponseStatus();
+ msg(DBG_000,"Drive Status: door_locked =%d.\n", st_door_locked);
+ msg(DBG_000,"Drive Status: door_closed =%d.\n", st_door_closed);
+ msg(DBG_000,"Drive Status: caddy_in =%d.\n", st_caddy_in);
+ msg(DBG_000,"Drive Status: disk_ok =%d.\n", st_diskok);
+ msg(DBG_000,"Drive Status: spinning =%d.\n", st_spinning);
+ msg(DBG_000,"Drive Status: busy =%d.\n", st_busy);
+ /* st_busy indicates if it's _ACTUALLY_ playing audio */
+ switch (current_drive->audio_state)
+ {
+ case audio_playing:
+ if(st_busy==0) {
+ /* CD has stopped playing audio --AJK */
+ current_drive->audio_state=audio_completed;
+ SC.cdsc_audiostatus=CDROM_AUDIO_COMPLETED;
+ } else {
+ SC.cdsc_audiostatus=CDROM_AUDIO_PLAY;
+ }
+ break;
+ case audio_pausing:
+ SC.cdsc_audiostatus=CDROM_AUDIO_PAUSED;
+ break;
+ case audio_completed:
+ SC.cdsc_audiostatus=CDROM_AUDIO_COMPLETED;
+ break;
+ default:
+ SC.cdsc_audiostatus=CDROM_AUDIO_NO_STATUS;
+ break;
+ }
+ SC.cdsc_adr=current_drive->SubQ_ctl_adr;
+ SC.cdsc_ctrl=current_drive->SubQ_ctl_adr>>4;
+ SC.cdsc_trk=bcd2bin(current_drive->SubQ_trk);
+ SC.cdsc_ind=bcd2bin(current_drive->SubQ_pnt_idx);
+ if (SC.cdsc_format==CDROM_LBA)
+ {
+ SC.cdsc_absaddr.lba=msf2blk(current_drive->SubQ_run_tot);
+ SC.cdsc_reladdr.lba=msf2blk(current_drive->SubQ_run_trk);
+ }
+ else /* not only if (SC.cdsc_format==CDROM_MSF) */
+ {
+ SC.cdsc_absaddr.msf.minute=(current_drive->SubQ_run_tot>>16)&0x00FF;
+ SC.cdsc_absaddr.msf.second=(current_drive->SubQ_run_tot>>8)&0x00FF;
+ SC.cdsc_absaddr.msf.frame=current_drive->SubQ_run_tot&0x00FF;
+ SC.cdsc_reladdr.msf.minute=(current_drive->SubQ_run_trk>>16)&0x00FF;
+ SC.cdsc_reladdr.msf.second=(current_drive->SubQ_run_trk>>8)&0x00FF;
+ SC.cdsc_reladdr.msf.frame=current_drive->SubQ_run_trk&0x00FF;
+ }
+ memcpy((void *) arg, &SC, sizeof(struct cdrom_subchnl));
+ msg(DBG_IOS,"CDROMSUBCHNL: %1X %02X %08X %08X %02X %02X %06X %06X\n",
+ SC.cdsc_format,SC.cdsc_audiostatus,
+ SC.cdsc_adr,SC.cdsc_ctrl,
+ SC.cdsc_trk,SC.cdsc_ind,
+ SC.cdsc_absaddr,SC.cdsc_reladdr);
+ RETURN_UP(0);
+
+ default:
+ msg(DBG_IOC,"ioctl: unknown function request %04X\n", cmd);
+ RETURN_UP(-EINVAL);
+ } /* end switch(cmd) */
+}
+/*==========================================================================*/
+/*
+ * Take care of the different block sizes between cdrom and Linux.
+ */
+static void sbp_transfer(struct request *req)
+{
+ long offs;
+
+ while ( (req->nr_sectors > 0) &&
+ (req->sector/4 >= current_drive->sbp_first_frame) &&
+ (req->sector/4 <= current_drive->sbp_last_frame) )
+ {
+ offs = (req->sector - current_drive->sbp_first_frame * 4) * 512;
+ memcpy(req->buffer, current_drive->sbp_buf + offs, 512);
+ req->nr_sectors--;
+ req->sector++;
+ req->buffer += 512;
+ }
+}
+/*==========================================================================*/
+/*
+ * special end_request for sbpcd to solve CURRENT==NULL bug. (GTL)
+ * GTL = Gonzalo Tornaria
+ *
+ * This is a kludge so we don't need to modify end_request.
+ * We put the req we take out after INIT_REQUEST in the requests list,
+ * so that end_request will discard it.
+ *
+ * The bug could be present in other block devices, perhaps we
+ * should modify INIT_REQUEST and end_request instead, and
+ * change every block device..
+ *
+ * Could be a race here?? Could e.g. a timer interrupt schedule() us?
+ * If so, we should copy end_request here, and do it right.. (or
+ * modify end_request and the block devices).
+ *
+ * In any case, the race here would be much small than it was, and
+ * I couldn't reproduce..
+ *
+ * The race could be: suppose CURRENT==NULL. We put our req in the list,
+ * and we are scheduled. Other process takes over, and gets into
+ * do_sbpcd_request. It sees CURRENT!=NULL (it is == to our req), so
+ * proceeds. It ends, so CURRENT is now NULL.. Now we awake somewhere in
+ * end_request, but now CURRENT==NULL... oops!
+ *
+ */
+#undef DEBUG_GTL
+
+/*==========================================================================*/
+/*
+ * I/O request routine, called from Linux kernel.
+ */
+static void do_sbpcd_request(request_queue_t * q)
+{
+ u_int block;
+ u_int nsect;
+ int status_tries, data_tries;
+ struct request *req;
+ struct sbpcd_drive *p;
+#ifdef DEBUG_GTL
+ static int xx_nr=0;
+ int xnr;
+#endif
+
+ request_loop:
+#ifdef DEBUG_GTL
+ xnr=++xx_nr;
+
+ req = elv_next_request(q);
+
+ if (!req)
+ {
+ printk( "do_sbpcd_request[%di](NULL), Pid:%d, Time:%li\n",
+ xnr, current->pid, jiffies);
+ printk( "do_sbpcd_request[%do](NULL) end 0 (null), Time:%li\n",
+ xnr, jiffies);
+ return;
+ }
+
+ printk(" do_sbpcd_request[%di](%p:%ld+%ld), Pid:%d, Time:%li\n",
+ xnr, req, req->sector, req->nr_sectors, current->pid, jiffies);
+#endif
+
+ req = elv_next_request(q); /* take out our request so no other */
+ if (!req)
+ return;
+
+ if (req -> sector == -1)
+ end_request(req, 0);
+ spin_unlock_irq(q->queue_lock);
+
+ down(&ioctl_read_sem);
+ if (rq_data_dir(elv_next_request(q)) != READ)
+ {
+ msg(DBG_INF, "bad cmd %d\n", req->cmd[0]);
+ goto err_done;
+ }
+ p = req->rq_disk->private_data;
+#if OLD_BUSY
+ while (busy_audio) sbp_sleep(HZ); /* wait a bit */
+ busy_data=1;
+#endif /* OLD_BUSY */
+
+ if (p->audio_state==audio_playing) goto err_done;
+ if (p != current_drive)
+ switch_drive(p);
+
+ block = req->sector; /* always numbered as 512-byte-pieces */
+ nsect = req->nr_sectors; /* always counted as 512-byte-pieces */
+
+ msg(DBG_BSZ,"read sector %d (%d sectors)\n", block, nsect);
+#if 0
+ msg(DBG_MUL,"read LBA %d\n", block/4);
+#endif
+
+ sbp_transfer(req);
+ /* if we satisfied the request from the buffer, we're done. */
+ if (req->nr_sectors == 0)
+ {
+#ifdef DEBUG_GTL
+ printk(" do_sbpcd_request[%do](%p:%ld+%ld) end 2, Time:%li\n",
+ xnr, req, req->sector, req->nr_sectors, jiffies);
+#endif
+ up(&ioctl_read_sem);
+ spin_lock_irq(q->queue_lock);
+ end_request(req, 1);
+ goto request_loop;
+ }
+
+#ifdef FUTURE
+ i=prepare(0,0); /* at moment not really a hassle check, but ... */
+ if (i!=0)
+ msg(DBG_INF,"\"prepare\" tells error %d -- ignored\n", i);
+#endif /* FUTURE */
+
+ if (!st_spinning) cc_SpinUp();
+
+ for (data_tries=n_retries; data_tries > 0; data_tries--)
+ {
+ for (status_tries=3; status_tries > 0; status_tries--)
+ {
+ flags_cmd_out |= f_respo3;
+ cc_ReadStatus();
+ if (sbp_status() != 0) break;
+ if (st_check) cc_ReadError();
+ sbp_sleep(1); /* wait a bit, try again */
+ }
+ if (status_tries == 0)
+ {
+ msg(DBG_INF,"sbp_status: failed after 3 tries in line %d\n", __LINE__);
+ break;
+ }
+
+ sbp_read_cmd(req);
+ sbp_sleep(0);
+ if (sbp_data(req) != 0)
+ {
+#ifdef SAFE_MIXED
+ current_drive->has_data=2; /* is really a data disk */
+#endif /* SAFE_MIXED */
+#ifdef DEBUG_GTL
+ printk(" do_sbpcd_request[%do](%p:%ld+%ld) end 3, Time:%li\n",
+ xnr, req, req->sector, req->nr_sectors, jiffies);
+#endif
+ up(&ioctl_read_sem);
+ spin_lock_irq(q->queue_lock);
+ end_request(req, 1);
+ goto request_loop;
+ }
+ }
+
+ err_done:
+#if OLD_BUSY
+ busy_data=0;
+#endif /* OLD_BUSY */
+#ifdef DEBUG_GTL
+ printk(" do_sbpcd_request[%do](%p:%ld+%ld) end 4 (error), Time:%li\n",
+ xnr, req, req->sector, req->nr_sectors, jiffies);
+#endif
+ up(&ioctl_read_sem);
+ sbp_sleep(0); /* wait a bit, try again */
+ spin_lock_irq(q->queue_lock);
+ end_request(req, 0);
+ goto request_loop;
+}
+/*==========================================================================*/
+/*
+ * build and send the READ command.
+ */
+static void sbp_read_cmd(struct request *req)
+{
+#undef OLD
+
+ int i;
+ int block;
+
+ current_drive->sbp_first_frame=current_drive->sbp_last_frame=-1; /* purge buffer */
+ current_drive->sbp_current = 0;
+ block=req->sector/4;
+ if (block+current_drive->sbp_bufsiz <= current_drive->CDsize_frm)
+ current_drive->sbp_read_frames = current_drive->sbp_bufsiz;
+ else
+ {
+ current_drive->sbp_read_frames=current_drive->CDsize_frm-block;
+ /* avoid reading past end of data */
+ if (current_drive->sbp_read_frames < 1)
+ {
+ msg(DBG_INF,"requested frame %d, CD size %d ???\n",
+ block, current_drive->CDsize_frm);
+ current_drive->sbp_read_frames=1;
+ }
+ }
+
+ flags_cmd_out = f_putcmd | f_respo2 | f_ResponseStatus | f_obey_p_check;
+ clr_cmdbuf();
+ if (famV_drive)
+ {
+ drvcmd[0]=CMDV_READ;
+ lba2msf(block,&drvcmd[1]); /* msf-bcd format required */
+ bin2bcdx(&drvcmd[1]);
+ bin2bcdx(&drvcmd[2]);
+ bin2bcdx(&drvcmd[3]);
+ drvcmd[4]=current_drive->sbp_read_frames>>8;
+ drvcmd[5]=current_drive->sbp_read_frames&0xff;
+ drvcmd[6]=0x02; /* flag "msf-bcd" */
+ }
+ else if (fam0L_drive)
+ {
+ flags_cmd_out |= f_lopsta | f_getsta | f_bit1;
+ if (current_drive->xa_byte==0x20)
+ {
+ cmd_type=READ_M2;
+ drvcmd[0]=CMD0_READ_XA; /* "read XA frames", old drives */
+ drvcmd[1]=(block>>16)&0x0ff;
+ drvcmd[2]=(block>>8)&0x0ff;
+ drvcmd[3]=block&0x0ff;
+ drvcmd[4]=(current_drive->sbp_read_frames>>8)&0x0ff;
+ drvcmd[5]=current_drive->sbp_read_frames&0x0ff;
+ }
+ else
+ {
+ drvcmd[0]=CMD0_READ; /* "read frames", old drives */
+ if (current_drive->drv_type>=drv_201)
+ {
+ lba2msf(block,&drvcmd[1]); /* msf-bcd format required */
+ bin2bcdx(&drvcmd[1]);
+ bin2bcdx(&drvcmd[2]);
+ bin2bcdx(&drvcmd[3]);
+ }
+ else
+ {
+ drvcmd[1]=(block>>16)&0x0ff;
+ drvcmd[2]=(block>>8)&0x0ff;
+ drvcmd[3]=block&0x0ff;
+ }
+ drvcmd[4]=(current_drive->sbp_read_frames>>8)&0x0ff;
+ drvcmd[5]=current_drive->sbp_read_frames&0x0ff;
+ drvcmd[6]=(current_drive->drv_typesbp_read_frames>>8)&0x0ff;
+ drvcmd[6]=current_drive->sbp_read_frames&0x0ff;
+ }
+ else if (fam2_drive)
+ {
+ drvcmd[0]=CMD2_READ;
+ lba2msf(block,&drvcmd[1]); /* msf-bin format required */
+ drvcmd[4]=(current_drive->sbp_read_frames>>8)&0x0ff;
+ drvcmd[5]=current_drive->sbp_read_frames&0x0ff;
+ drvcmd[6]=0x02;
+ }
+ else if (famT_drive)
+ {
+ drvcmd[0]=CMDT_READ;
+ drvcmd[2]=(block>>24)&0x0ff;
+ drvcmd[3]=(block>>16)&0x0ff;
+ drvcmd[4]=(block>>8)&0x0ff;
+ drvcmd[5]=block&0x0ff;
+ drvcmd[7]=(current_drive->sbp_read_frames>>8)&0x0ff;
+ drvcmd[8]=current_drive->sbp_read_frames&0x0ff;
+ }
+ flags_cmd_out=f_putcmd;
+ response_count=0;
+ i=cmd_out();
+ if (i<0) msg(DBG_INF,"error giving READ command: %0d\n", i);
+ return;
+}
+/*==========================================================================*/
+/*
+ * Check the completion of the read-data command. On success, read
+ * the current_drive->sbp_bufsiz * 2048 bytes of data from the disk into buffer.
+ */
+static int sbp_data(struct request *req)
+{
+ int i=0, j=0, l, frame;
+ u_int try=0;
+ u_long timeout;
+ u_char *p;
+ u_int data_tries = 0;
+ u_int data_waits = 0;
+ u_int data_retrying = 0;
+ int error_flag;
+ int xa_count;
+ int max_latency;
+ int success;
+ int wait;
+ int duration;
+
+ error_flag=0;
+ success=0;
+#if LONG_TIMING
+ max_latency=9*HZ;
+#else
+ if (current_drive->f_multisession) max_latency=15*HZ;
+ else max_latency=5*HZ;
+#endif
+ duration=jiffies;
+ for (frame=0;framesbp_read_frames&&!error_flag; frame++)
+ {
+ SBPCD_CLI;
+
+ del_timer(&data_timer);
+ data_timer.expires=jiffies+max_latency;
+ timed_out_data=0;
+ add_timer(&data_timer);
+ while (!timed_out_data)
+ {
+ if (current_drive->f_multisession) try=maxtim_data*4;
+ else try=maxtim_data;
+ msg(DBG_000,"sbp_data: CDi_status loop: try=%d.\n",try);
+ for ( ; try!=0;try--)
+ {
+ j=inb(CDi_status);
+ if (!(j&s_not_data_ready)) break;
+ if (!(j&s_not_result_ready)) break;
+ if (fam0LV_drive) if (j&s_attention) break;
+ }
+ if (!(j&s_not_data_ready)) goto data_ready;
+ if (try==0)
+ {
+ if (data_retrying == 0) data_waits++;
+ data_retrying = 1;
+ msg(DBG_000,"sbp_data: CDi_status loop: sleeping.\n");
+ sbp_sleep(1);
+ try = 1;
+ }
+ }
+ msg(DBG_INF,"sbp_data: CDi_status loop expired.\n");
+ data_ready:
+ del_timer(&data_timer);
+
+ if (timed_out_data)
+ {
+ msg(DBG_INF,"sbp_data: CDi_status timeout (timed_out_data) (%02X).\n", j);
+ error_flag++;
+ }
+ if (try==0)
+ {
+ msg(DBG_INF,"sbp_data: CDi_status timeout (try=0) (%02X).\n", j);
+ error_flag++;
+ }
+ if (!(j&s_not_result_ready))
+ {
+ msg(DBG_INF, "sbp_data: RESULT_READY where DATA_READY awaited (%02X).\n", j);
+ response_count=20;
+ j=ResponseInfo();
+ j=inb(CDi_status);
+ }
+ if (j&s_not_data_ready)
+ {
+ if ((current_drive->ored_ctl_adr&0x40)==0)
+ msg(DBG_INF, "CD contains no data tracks.\n");
+ else msg(DBG_INF, "sbp_data: DATA_READY timeout (%02X).\n", j);
+ error_flag++;
+ }
+ SBPCD_STI;
+ if (error_flag) break;
+
+ msg(DBG_000, "sbp_data: beginning to read.\n");
+ p = current_drive->sbp_buf + frame * CD_FRAMESIZE;
+ if (sbpro_type==1) OUT(CDo_sel_i_d,1);
+ if (cmd_type==READ_M2) {
+ if (do_16bit) insw(CDi_data, xa_head_buf, CD_XA_HEAD>>1);
+ else insb(CDi_data, xa_head_buf, CD_XA_HEAD);
+ }
+ if (do_16bit) insw(CDi_data, p, CD_FRAMESIZE>>1);
+ else insb(CDi_data, p, CD_FRAMESIZE);
+ if (cmd_type==READ_M2) {
+ if (do_16bit) insw(CDi_data, xa_tail_buf, CD_XA_TAIL>>1);
+ else insb(CDi_data, xa_tail_buf, CD_XA_TAIL);
+ }
+ current_drive->sbp_current++;
+ if (sbpro_type==1) OUT(CDo_sel_i_d,0);
+ if (cmd_type==READ_M2)
+ {
+ for (xa_count=0;xa_count= 1000)
+ {
+ msg(DBG_INF,"sbp_data() statistics: %d waits in %d frames.\n", data_waits, data_tries);
+ data_waits = data_tries = 0;
+ }
+ }
+ duration=jiffies-duration;
+ msg(DBG_TEA,"time to read %d frames: %d jiffies .\n",frame,duration);
+ if (famT_drive)
+ {
+ wait=8;
+ do
+ {
+ if (teac==2)
+ {
+ if ((i=CDi_stat_loop_T()) == -1) break;
+ }
+ else
+ {
+ sbp_sleep(1);
+ OUT(CDo_sel_i_d,0);
+ i=inb(CDi_status);
+ }
+ if (!(i&s_not_data_ready))
+ {
+ OUT(CDo_sel_i_d,1);
+ j=0;
+ do
+ {
+ if (do_16bit) i=inw(CDi_data);
+ else i=inb(CDi_data);
+ j++;
+ i=inb(CDi_status);
+ }
+ while (!(i&s_not_data_ready));
+ msg(DBG_TEA, "==========too much data (%d bytes/words)==============.\n", j);
+ }
+ if (!(i&s_not_result_ready))
+ {
+ OUT(CDo_sel_i_d,0);
+ l=0;
+ do
+ {
+ infobuf[l++]=inb(CDi_info);
+ i=inb(CDi_status);
+ }
+ while (!(i&s_not_result_ready));
+ if (infobuf[0]==0x00) success=1;
+#if 1
+ for (j=0;j1) msg(DBG_TEA,"cmd_out_T READ_ERR recursion (sbp_data): %d !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n",recursion);
+ else msg(DBG_TEA,"sbp_data: CMDT_READ_ERR necessary.\n");
+ clr_cmdbuf();
+ drvcmd[0]=CMDT_READ_ERR;
+ j=cmd_out_T(); /* !!! recursive here !!! */
+ --recursion;
+ sbp_sleep(1);
+ }
+ while (j<0);
+ current_drive->error_state=infobuf[2];
+ current_drive->b3=infobuf[3];
+ current_drive->b4=infobuf[4];
+ }
+ break;
+ }
+ else
+ {
+#if 0
+ msg(DBG_TEA, "============= waiting for result=================.\n");
+ sbp_sleep(1);
+#endif
+ }
+ }
+ while (wait--);
+ }
+
+ if (error_flag) /* must have been spurious D_RDY or (ATTN&&!D_RDY) */
+ {
+ msg(DBG_TEA, "================error flag: %d=================.\n", error_flag);
+ msg(DBG_INF,"sbp_data: read aborted by drive.\n");
+#if 1
+ i=cc_DriveReset(); /* ugly fix to prevent a hang */
+#else
+ i=cc_ReadError();
+#endif
+ return (0);
+ }
+
+ if (fam0LV_drive)
+ {
+ SBPCD_CLI;
+ i=maxtim_data;
+ for (timeout=jiffies+HZ; time_before(jiffies, timeout); timeout--)
+ {
+ for ( ;i!=0;i--)
+ {
+ j=inb(CDi_status);
+ if (!(j&s_not_data_ready)) break;
+ if (!(j&s_not_result_ready)) break;
+ if (j&s_attention) break;
+ }
+ if (i != 0 || time_after_eq(jiffies, timeout)) break;
+ sbp_sleep(0);
+ i = 1;
+ }
+ if (i==0) msg(DBG_INF,"status timeout after READ.\n");
+ if (!(j&s_attention))
+ {
+ msg(DBG_INF,"sbp_data: timeout waiting DRV_ATTN - retrying.\n");
+ i=cc_DriveReset(); /* ugly fix to prevent a hang */
+ SBPCD_STI;
+ return (0);
+ }
+ SBPCD_STI;
+ }
+
+#if 0
+ if (!success)
+#endif
+ do
+ {
+ if (fam0LV_drive) cc_ReadStatus();
+#if 1
+ if (famT_drive) msg(DBG_TEA, "================before ResponseStatus=================.\n", i);
+#endif
+ i=ResponseStatus(); /* builds status_bits, returns orig. status (old) or faked p_success (new) */
+#if 1
+ if (famT_drive) msg(DBG_TEA, "================ResponseStatus: %d=================.\n", i);
+#endif
+ if (i<0)
+ {
+ msg(DBG_INF,"bad cc_ReadStatus after read: %02X\n", current_drive->status_bits);
+ return (0);
+ }
+ }
+ while ((fam0LV_drive)&&(!st_check)&&(!(i&p_success)));
+ if (st_check)
+ {
+ i=cc_ReadError();
+ msg(DBG_INF,"cc_ReadError was necessary after read: %d\n",i);
+ return (0);
+ }
+ if (fatal_err)
+ {
+ fatal_err=0;
+ current_drive->sbp_first_frame=current_drive->sbp_last_frame=-1; /* purge buffer */
+ current_drive->sbp_current = 0;
+ msg(DBG_INF,"sbp_data: fatal_err - retrying.\n");
+ return (0);
+ }
+
+ current_drive->sbp_first_frame = req -> sector / 4;
+ current_drive->sbp_last_frame = current_drive->sbp_first_frame + current_drive->sbp_read_frames - 1;
+ sbp_transfer(req);
+ return (1);
+}
+/*==========================================================================*/
+
+static int sbpcd_block_open(struct inode *inode, struct file *file)
+{
+ struct sbpcd_drive *p = inode->i_bdev->bd_disk->private_data;
+ return cdrom_open(p->sbpcd_infop, inode, file);
+}
+
+static int sbpcd_block_release(struct inode *inode, struct file *file)
+{
+ struct sbpcd_drive *p = inode->i_bdev->bd_disk->private_data;
+ return cdrom_release(p->sbpcd_infop, file);
+}
+
+static int sbpcd_block_ioctl(struct inode *inode, struct file *file,
+ unsigned cmd, unsigned long arg)
+{
+ struct sbpcd_drive *p = inode->i_bdev->bd_disk->private_data;
+ struct cdrom_device_info *cdi = p->sbpcd_infop;
+ int ret, i;
+
+ ret = cdrom_ioctl(file, p->sbpcd_infop, inode, cmd, arg);
+ if (ret != -ENOSYS)
+ return ret;
+
+ msg(DBG_IO2,"ioctl(%s, 0x%08lX, 0x%08lX)\n", cdi->name, cmd, arg);
+ if (p->drv_id==-1) {
+ msg(DBG_INF, "ioctl: bad device: %s\n", cdi->name);
+ return (-ENXIO); /* no such drive */
+ }
+ down(&ioctl_read_sem);
+ if (p != current_drive)
+ switch_drive(p);
+
+ msg(DBG_IO2,"ioctl: device %s, request %04X\n",cdi->name,cmd);
+ switch (cmd) /* Sun-compatible */
+ {
+ case DDIOCSDBG: /* DDI Debug */
+ if (!capable(CAP_SYS_ADMIN)) RETURN_UP(-EPERM);
+ i=sbpcd_dbg_ioctl(arg,1);
+ RETURN_UP(i);
+ case CDROMRESET: /* hard reset the drive */
+ msg(DBG_IOC,"ioctl: CDROMRESET entered.\n");
+ i=DriveReset();
+ current_drive->audio_state=0;
+ RETURN_UP(i);
+
+ case CDROMREADMODE1:
+ msg(DBG_IOC,"ioctl: CDROMREADMODE1 requested.\n");
+#ifdef SAFE_MIXED
+ if (current_drive->has_data>1) RETURN_UP(-EBUSY);
+#endif /* SAFE_MIXED */
+ cc_ModeSelect(CD_FRAMESIZE);
+ cc_ModeSense();
+ current_drive->mode=READ_M1;
+ RETURN_UP(0);
+
+ case CDROMREADMODE2: /* not usable at the moment */
+ msg(DBG_IOC,"ioctl: CDROMREADMODE2 requested.\n");
+#ifdef SAFE_MIXED
+ if (current_drive->has_data>1) RETURN_UP(-EBUSY);
+#endif /* SAFE_MIXED */
+ cc_ModeSelect(CD_FRAMESIZE_RAW1);
+ cc_ModeSense();
+ current_drive->mode=READ_M2;
+ RETURN_UP(0);
+
+ case CDROMAUDIOBUFSIZ: /* configure the audio buffer size */
+ msg(DBG_IOC,"ioctl: CDROMAUDIOBUFSIZ entered.\n");
+ if (current_drive->sbp_audsiz>0)
+ vfree(current_drive->aud_buf);
+ current_drive->aud_buf=NULL;
+ current_drive->sbp_audsiz=arg;
+
+ if (current_drive->sbp_audsiz>16)
+ {
+ current_drive->sbp_audsiz = 0;
+ RETURN_UP(current_drive->sbp_audsiz);
+ }
+
+ if (current_drive->sbp_audsiz>0)
+ {
+ current_drive->aud_buf=(u_char *) vmalloc(current_drive->sbp_audsiz*CD_FRAMESIZE_RAW);
+ if (current_drive->aud_buf==NULL)
+ {
+ msg(DBG_INF,"audio buffer (%d frames) not available.\n",current_drive->sbp_audsiz);
+ current_drive->sbp_audsiz=0;
+ }
+ else msg(DBG_INF,"audio buffer size: %d frames.\n",current_drive->sbp_audsiz);
+ }
+ RETURN_UP(current_drive->sbp_audsiz);
+
+ case CDROMREADAUDIO:
+ { /* start of CDROMREADAUDIO */
+ int i=0, j=0, frame, block=0;
+ u_int try=0;
+ u_long timeout;
+ u_char *p;
+ u_int data_tries = 0;
+ u_int data_waits = 0;
+ u_int data_retrying = 0;
+ int status_tries;
+ int error_flag;
+
+ msg(DBG_IOC,"ioctl: CDROMREADAUDIO entered.\n");
+ if (fam0_drive) RETURN_UP(-EINVAL);
+ if (famL_drive) RETURN_UP(-EINVAL);
+ if (famV_drive) RETURN_UP(-EINVAL);
+ if (famT_drive) RETURN_UP(-EINVAL);
+#ifdef SAFE_MIXED
+ if (current_drive->has_data>1) RETURN_UP(-EBUSY);
+#endif /* SAFE_MIXED */
+ if (current_drive->aud_buf==NULL) RETURN_UP(-EINVAL);
+ if (copy_from_user(&read_audio, (void __user *)arg,
+ sizeof(struct cdrom_read_audio)))
+ RETURN_UP(-EFAULT);
+ if (read_audio.nframes < 0 || read_audio.nframes>current_drive->sbp_audsiz) RETURN_UP(-EINVAL);
+ if (!access_ok(VERIFY_WRITE, read_audio.buf,
+ read_audio.nframes*CD_FRAMESIZE_RAW))
+ RETURN_UP(-EFAULT);
+
+ if (read_audio.addr_format==CDROM_MSF) /* MSF-bin specification of where to start */
+ block=msf2lba(&read_audio.addr.msf.minute);
+ else if (read_audio.addr_format==CDROM_LBA) /* lba specification of where to start */
+ block=read_audio.addr.lba;
+ else RETURN_UP(-EINVAL);
+#if 000
+ i=cc_SetSpeed(speed_150,0,0);
+ if (i) msg(DBG_AUD,"read_audio: SetSpeed error %d\n", i);
+#endif
+ msg(DBG_AUD,"read_audio: lba: %d, msf: %06X\n",
+ block, blk2msf(block));
+ msg(DBG_AUD,"read_audio: before cc_ReadStatus.\n");
+#if OLD_BUSY
+ while (busy_data) sbp_sleep(HZ/10); /* wait a bit */
+ busy_audio=1;
+#endif /* OLD_BUSY */
+ error_flag=0;
+ for (data_tries=5; data_tries>0; data_tries--)
+ {
+ msg(DBG_AUD,"data_tries=%d ...\n", data_tries);
+ current_drive->mode=READ_AU;
+ cc_ModeSelect(CD_FRAMESIZE_RAW);
+ cc_ModeSense();
+ for (status_tries=3; status_tries > 0; status_tries--)
+ {
+ flags_cmd_out |= f_respo3;
+ cc_ReadStatus();
+ if (sbp_status() != 0) break;
+ if (st_check) cc_ReadError();
+ sbp_sleep(1); /* wait a bit, try again */
+ }
+ if (status_tries == 0)
+ {
+ msg(DBG_AUD,"read_audio: sbp_status: failed after 3 tries in line %d.\n", __LINE__);
+ continue;
+ }
+ msg(DBG_AUD,"read_audio: sbp_status: ok.\n");
+
+ flags_cmd_out = f_putcmd | f_respo2 | f_ResponseStatus | f_obey_p_check;
+ if (fam0L_drive)
+ {
+ flags_cmd_out |= f_lopsta | f_getsta | f_bit1;
+ cmd_type=READ_M2;
+ drvcmd[0]=CMD0_READ_XA; /* "read XA frames", old drives */
+ drvcmd[1]=(block>>16)&0x000000ff;
+ drvcmd[2]=(block>>8)&0x000000ff;
+ drvcmd[3]=block&0x000000ff;
+ drvcmd[4]=0;
+ drvcmd[5]=read_audio.nframes; /* # of frames */
+ drvcmd[6]=0;
+ }
+ else if (fam1_drive)
+ {
+ drvcmd[0]=CMD1_READ; /* "read frames", new drives */
+ lba2msf(block,&drvcmd[1]); /* msf-bin format required */
+ drvcmd[4]=0;
+ drvcmd[5]=0;
+ drvcmd[6]=read_audio.nframes; /* # of frames */
+ }
+ else if (fam2_drive)
+ {
+ drvcmd[0]=CMD2_READ_XA2;
+ lba2msf(block,&drvcmd[1]); /* msf-bin format required */
+ drvcmd[4]=0;
+ drvcmd[5]=read_audio.nframes; /* # of frames */
+ drvcmd[6]=0x11; /* raw mode */
+ }
+ else if (famT_drive) /* CD-55A: not tested yet */
+ {
+ }
+ msg(DBG_AUD,"read_audio: before giving \"read\" command.\n");
+ flags_cmd_out=f_putcmd;
+ response_count=0;
+ i=cmd_out();
+ if (i<0) msg(DBG_INF,"error giving READ AUDIO command: %0d\n", i);
+ sbp_sleep(0);
+ msg(DBG_AUD,"read_audio: after giving \"read\" command.\n");
+ for (frame=1;frame<2 && !error_flag; frame++)
+ {
+ try=maxtim_data;
+ for (timeout=jiffies+9*HZ; ; )
+ {
+ for ( ; try!=0;try--)
+ {
+ j=inb(CDi_status);
+ if (!(j&s_not_data_ready)) break;
+ if (!(j&s_not_result_ready)) break;
+ if (fam0L_drive) if (j&s_attention) break;
+ }
+ if (try != 0 || time_after_eq(jiffies, timeout)) break;
+ if (data_retrying == 0) data_waits++;
+ data_retrying = 1;
+ sbp_sleep(1);
+ try = 1;
+ }
+ if (try==0)
+ {
+ msg(DBG_INF,"read_audio: sbp_data: CDi_status timeout.\n");
+ error_flag++;
+ break;
+ }
+ msg(DBG_AUD,"read_audio: sbp_data: CDi_status ok.\n");
+ if (j&s_not_data_ready)
+ {
+ msg(DBG_INF, "read_audio: sbp_data: DATA_READY timeout.\n");
+ error_flag++;
+ break;
+ }
+ msg(DBG_AUD,"read_audio: before reading data.\n");
+ error_flag=0;
+ p = current_drive->aud_buf;
+ if (sbpro_type==1) OUT(CDo_sel_i_d,1);
+ if (do_16bit)
+ {
+ u_short *p2 = (u_short *) p;
+
+ for (; (u_char *) p2 < current_drive->aud_buf + read_audio.nframes*CD_FRAMESIZE_RAW;)
+ {
+ if ((inb_p(CDi_status)&s_not_data_ready)) continue;
+
+ /* get one sample */
+ *p2++ = inw_p(CDi_data);
+ *p2++ = inw_p(CDi_data);
+ }
+ } else {
+ for (; p < current_drive->aud_buf + read_audio.nframes*CD_FRAMESIZE_RAW;)
+ {
+ if ((inb_p(CDi_status)&s_not_data_ready)) continue;
+
+ /* get one sample */
+ *p++ = inb_p(CDi_data);
+ *p++ = inb_p(CDi_data);
+ *p++ = inb_p(CDi_data);
+ *p++ = inb_p(CDi_data);
+ }
+ }
+ if (sbpro_type==1) OUT(CDo_sel_i_d,0);
+ data_retrying = 0;
+ }
+ msg(DBG_AUD,"read_audio: after reading data.\n");
+ if (error_flag) /* must have been spurious D_RDY or (ATTN&&!D_RDY) */
+ {
+ msg(DBG_AUD,"read_audio: read aborted by drive\n");
+#if 0000
+ i=cc_DriveReset(); /* ugly fix to prevent a hang */
+#else
+ i=cc_ReadError();
+#endif
+ continue;
+ }
+ if (fam0L_drive)
+ {
+ i=maxtim_data;
+ for (timeout=jiffies+9*HZ; time_before(jiffies, timeout); timeout--)
+ {
+ for ( ;i!=0;i--)
+ {
+ j=inb(CDi_status);
+ if (!(j&s_not_data_ready)) break;
+ if (!(j&s_not_result_ready)) break;
+ if (j&s_attention) break;
+ }
+ if (i != 0 || time_after_eq(jiffies, timeout)) break;
+ sbp_sleep(0);
+ i = 1;
+ }
+ if (i==0) msg(DBG_AUD,"read_audio: STATUS TIMEOUT AFTER READ");
+ if (!(j&s_attention))
+ {
+ msg(DBG_AUD,"read_audio: sbp_data: timeout waiting DRV_ATTN - retrying\n");
+ i=cc_DriveReset(); /* ugly fix to prevent a hang */
+ continue;
+ }
+ }
+ do
+ {
+ if (fam0L_drive) cc_ReadStatus();
+ i=ResponseStatus(); /* builds status_bits, returns orig. status (old) or faked p_success (new) */
+ if (i<0) { msg(DBG_AUD,
+ "read_audio: cc_ReadStatus error after read: %02X\n",
+ current_drive->status_bits);
+ continue; /* FIXME */
+ }
+ }
+ while ((fam0L_drive)&&(!st_check)&&(!(i&p_success)));
+ if (st_check)
+ {
+ i=cc_ReadError();
+ msg(DBG_AUD,"read_audio: cc_ReadError was necessary after read: %02X\n",i);
+ continue;
+ }
+ if (copy_to_user(read_audio.buf,
+ current_drive->aud_buf,
+ read_audio.nframes * CD_FRAMESIZE_RAW))
+ RETURN_UP(-EFAULT);
+ msg(DBG_AUD,"read_audio: copy_to_user done.\n");
+ break;
+ }
+ cc_ModeSelect(CD_FRAMESIZE);
+ cc_ModeSense();
+ current_drive->mode=READ_M1;
+#if OLD_BUSY
+ busy_audio=0;
+#endif /* OLD_BUSY */
+ if (data_tries == 0)
+ {
+ msg(DBG_AUD,"read_audio: failed after 5 tries in line %d.\n", __LINE__);
+ RETURN_UP(-EIO);
+ }
+ msg(DBG_AUD,"read_audio: successful return.\n");
+ RETURN_UP(0);
+ } /* end of CDROMREADAUDIO */
+
+ default:
+ msg(DBG_IOC,"ioctl: unknown function request %04X\n", cmd);
+ RETURN_UP(-EINVAL);
+ } /* end switch(cmd) */
+}
+
+static int sbpcd_block_media_changed(struct gendisk *disk)
+{
+ struct sbpcd_drive *p = disk->private_data;
+ return cdrom_media_changed(p->sbpcd_infop);
+}
+
+static struct block_device_operations sbpcd_bdops =
+{
+ .owner = THIS_MODULE,
+ .open = sbpcd_block_open,
+ .release = sbpcd_block_release,
+ .ioctl = sbpcd_block_ioctl,
+ .media_changed = sbpcd_block_media_changed,
+};
+/*==========================================================================*/
+/*
+ * Open the device special file. Check that a disk is in. Read TOC.
+ */
+static int sbpcd_open(struct cdrom_device_info *cdi, int purpose)
+{
+ struct sbpcd_drive *p = cdi->handle;
+
+ down(&ioctl_read_sem);
+ switch_drive(p);
+
+ /*
+ * try to keep an "open" counter here and lock the door if 0->1.
+ */
+ msg(DBG_LCK,"open_count: %d -> %d\n",
+ current_drive->open_count,current_drive->open_count+1);
+ if (++current_drive->open_count<=1)
+ {
+ int i;
+ i=LockDoor();
+ current_drive->open_count=1;
+ if (famT_drive) msg(DBG_TEA,"sbpcd_open: before i=DiskInfo();.\n");
+ i=DiskInfo();
+ if (famT_drive) msg(DBG_TEA,"sbpcd_open: after i=DiskInfo();.\n");
+ if ((current_drive->ored_ctl_adr&0x40)==0)
+ {
+ msg(DBG_INF,"CD contains no data tracks.\n");
+#ifdef SAFE_MIXED
+ current_drive->has_data=0;
+#endif /* SAFE_MIXED */
+ }
+#ifdef SAFE_MIXED
+ else if (current_drive->has_data<1) current_drive->has_data=1;
+#endif /* SAFE_MIXED */
+ }
+ if (!st_spinning) cc_SpinUp();
+ RETURN_UP(0);
+}
+/*==========================================================================*/
+/*
+ * On close, we flush all sbp blocks from the buffer cache.
+ */
+static void sbpcd_release(struct cdrom_device_info * cdi)
+{
+ struct sbpcd_drive *p = cdi->handle;
+
+ if (p->drv_id==-1) {
+ msg(DBG_INF, "release: bad device: %s\n", cdi->name);
+ return;
+ }
+ down(&ioctl_read_sem);
+ switch_drive(p);
+ /*
+ * try to keep an "open" counter here and unlock the door if 1->0.
+ */
+ msg(DBG_LCK,"open_count: %d -> %d\n",
+ p->open_count,p->open_count-1);
+ if (p->open_count>-2) /* CDROMEJECT may have been done */
+ {
+ if (--p->open_count<=0)
+ {
+ p->sbp_first_frame=p->sbp_last_frame=-1;
+ if (p->audio_state!=audio_playing)
+ if (p->f_eject) cc_SpinDown();
+ p->diskstate_flags &= ~cd_size_bit;
+ p->open_count=0;
+#ifdef SAFE_MIXED
+ p->has_data=0;
+#endif /* SAFE_MIXED */
+ }
+ }
+ up(&ioctl_read_sem);
+ return ;
+}
+/*==========================================================================*/
+/*
+ *
+ */
+static int sbpcd_media_changed( struct cdrom_device_info *cdi, int disc_nr);
+static struct cdrom_device_ops sbpcd_dops = {
+ .open = sbpcd_open,
+ .release = sbpcd_release,
+ .drive_status = sbpcd_drive_status,
+ .media_changed = sbpcd_media_changed,
+ .tray_move = sbpcd_tray_move,
+ .lock_door = sbpcd_lock_door,
+ .select_speed = sbpcd_select_speed,
+ .get_last_session = sbpcd_get_last_session,
+ .get_mcn = sbpcd_get_mcn,
+ .reset = sbpcd_reset,
+ .audio_ioctl = sbpcd_audio_ioctl,
+ .capability = CDC_CLOSE_TRAY | CDC_OPEN_TRAY | CDC_LOCK |
+ CDC_MULTI_SESSION | CDC_MEDIA_CHANGED |
+ CDC_MCN | CDC_PLAY_AUDIO,
+ .n_minors = 1,
+};
+
+/*==========================================================================*/
+/*
+ * accept "kernel command line" parameters
+ * (suggested by Peter MacDonald with SLS 1.03)
+ *
+ * This is only implemented for the first controller. Should be enough to
+ * allow installing with a "strange" distribution kernel.
+ *
+ * use: tell LILO:
+ * sbpcd=0x230,SoundBlaster
+ * or
+ * sbpcd=0x300,LaserMate
+ * or
+ * sbpcd=0x338,SoundScape
+ * or
+ * sbpcd=0x2C0,Teac16bit
+ *
+ * (upper/lower case sensitive here - but all-lowercase is ok!!!).
+ *
+ * the address value has to be the CDROM PORT ADDRESS -
+ * not the soundcard base address.
+ * For the SPEA/SoundScape setup, DO NOT specify the "configuration port"
+ * address, but the address which is really used for the CDROM (usually 8
+ * bytes above).
+ *
+ */
+
+int sbpcd_setup(char *s)
+{
+#ifndef MODULE
+ int p[4];
+ (void)get_options(s, ARRAY_SIZE(p), p);
+ setup_done++;
+ msg(DBG_INI,"sbpcd_setup called with %04X,%s\n",p[1], s);
+ sbpro_type=0; /* default: "LaserMate" */
+ if (p[0]>1) sbpro_type=p[2];
+ else if (!strcmp(s,str_sb)) sbpro_type=1;
+ else if (!strcmp(s,str_sb_l)) sbpro_type=1;
+ else if (!strcmp(s,str_sp)) sbpro_type=2;
+ else if (!strcmp(s,str_sp_l)) sbpro_type=2;
+ else if (!strcmp(s,str_ss)) sbpro_type=2;
+ else if (!strcmp(s,str_ss_l)) sbpro_type=2;
+ else if (!strcmp(s,str_t16)) sbpro_type=3;
+ else if (!strcmp(s,str_t16_l)) sbpro_type=3;
+ if (p[0]>0) sbpcd_ioaddr=p[1];
+ if (p[0]>2) max_drives=p[3];
+#else
+ sbpcd_ioaddr = sbpcd[0];
+ sbpro_type = sbpcd[1];
+#endif
+
+ CDo_command=sbpcd_ioaddr;
+ CDi_info=sbpcd_ioaddr;
+ CDi_status=sbpcd_ioaddr+1;
+ CDo_sel_i_d=sbpcd_ioaddr+1;
+ CDo_reset=sbpcd_ioaddr+2;
+ CDo_enable=sbpcd_ioaddr+3;
+ f_16bit=0;
+ if ((sbpro_type==1)||(sbpro_type==3))
+ {
+ CDi_data=sbpcd_ioaddr;
+ if (sbpro_type==3)
+ {
+ f_16bit=1;
+ sbpro_type=1;
+ }
+ }
+ else CDi_data=sbpcd_ioaddr+2;
+
+ return 1;
+}
+
+__setup("sbpcd=", sbpcd_setup);
+
+
+/*==========================================================================*/
+/*
+ * Sequoia S-1000 CD-ROM Interface Configuration
+ * as used within SPEA Media FX, Ensonic SoundScape and some Reveal cards
+ * The soundcard has to get jumpered for the interface type "Panasonic"
+ * (not Sony or Mitsumi) and to get soft-configured for
+ * -> configuration port address
+ * -> CDROM port offset (num_ports): has to be 8 here. Possibly this
+ * offset value determines the interface type (none, Panasonic,
+ * Mitsumi, Sony).
+ * The interface uses a configuration port (0x320, 0x330, 0x340, 0x350)
+ * some bytes below the real CDROM address.
+ *
+ * For the Panasonic style (LaserMate) interface and the configuration
+ * port 0x330, we have to use an offset of 8; so, the real CDROM port
+ * address is 0x338.
+ */
+static int __init config_spea(void)
+{
+ /*
+ * base address offset between configuration port and CDROM port,
+ * this probably defines the interface type
+ * 2 (type=??): 0x00
+ * 8 (type=LaserMate):0x10
+ * 16 (type=??):0x20
+ * 32 (type=??):0x30
+ */
+ int n_ports=0x10;
+
+ int irq_number=0; /* off:0x00, 2/9:0x01, 7:0x03, 12:0x05, 15:0x07 */
+ int dma_channel=0; /* off: 0x00, 0:0x08, 1:0x18, 3:0x38, 5:0x58, 6:0x68 */
+ int dack_polarity=0; /* L:0x00, H:0x80 */
+ int drq_polarity=0x40; /* L:0x00, H:0x40 */
+ int i;
+
+#define SPEA_REG_1 sbpcd_ioaddr-0x08+4
+#define SPEA_REG_2 sbpcd_ioaddr-0x08+5
+
+ OUT(SPEA_REG_1,0xFF);
+ i=inb(SPEA_REG_1);
+ if (i!=0x0F)
+ {
+ msg(DBG_SEQ,"no SPEA interface at %04X present.\n", sbpcd_ioaddr);
+ return (-1); /* no interface found */
+ }
+ OUT(SPEA_REG_1,0x04);
+ OUT(SPEA_REG_2,0xC0);
+
+ OUT(SPEA_REG_1,0x05);
+ OUT(SPEA_REG_2,0x10|drq_polarity|dack_polarity);
+
+#if 1
+#define SPEA_PATTERN 0x80
+#else
+#define SPEA_PATTERN 0x00
+#endif
+ OUT(SPEA_REG_1,0x06);
+ OUT(SPEA_REG_2,dma_channel|irq_number|SPEA_PATTERN);
+ OUT(SPEA_REG_2,dma_channel|irq_number|SPEA_PATTERN);
+
+ OUT(SPEA_REG_1,0x09);
+ i=(inb(SPEA_REG_2)&0xCF)|n_ports;
+ OUT(SPEA_REG_2,i);
+
+ sbpro_type = 0; /* acts like a LaserMate interface now */
+ msg(DBG_SEQ,"found SoundScape interface at %04X.\n", sbpcd_ioaddr);
+ return (0);
+}
+
+/*==========================================================================*/
+/*
+ * Test for presence of drive and initialize it.
+ * Called once at boot or load time.
+ */
+
+/* FIXME: cleanups after failed allocations are too ugly for words */
+#ifdef MODULE
+int __init __sbpcd_init(void)
+#else
+int __init sbpcd_init(void)
+#endif
+{
+ int i=0, j=0;
+ int addr[2]={1, CDROM_PORT};
+ int port_index;
+
+ sti();
+
+ msg(DBG_INF,"sbpcd.c %s\n", VERSION);
+#ifndef MODULE
+#if DISTRIBUTION
+ if (!setup_done)
+ {
+ msg(DBG_INF,"Looking for Matsushita/Panasonic, CreativeLabs, Longshine, TEAC CD-ROM drives\n");
+ msg(DBG_INF,"= = = = = = = = = = W A R N I N G = = = = = = = = = =\n");
+ msg(DBG_INF,"Auto-Probing can cause a hang (f.e. touching an NE2000 card).\n");
+ msg(DBG_INF,"If that happens, you have to reboot and use the\n");
+ msg(DBG_INF,"LILO (kernel) command line feature like:\n");
+ msg(DBG_INF," LILO boot: ... sbpcd=0x230,SoundBlaster\n");
+ msg(DBG_INF,"or like:\n");
+ msg(DBG_INF," LILO boot: ... sbpcd=0x300,LaserMate\n");
+ msg(DBG_INF,"or like:\n");
+ msg(DBG_INF," LILO boot: ... sbpcd=0x338,SoundScape\n");
+ msg(DBG_INF,"with your REAL address.\n");
+ msg(DBG_INF,"= = = = = = = = = = END of WARNING = = = = = == = = =\n");
+ }
+#endif /* DISTRIBUTION */
+ sbpcd[0]=sbpcd_ioaddr; /* possibly changed by kernel command line */
+ sbpcd[1]=sbpro_type; /* possibly changed by kernel command line */
+#endif /* MODULE */
+
+ for (port_index=0;port_index=0) break; /* drive found */
+ } /* end of cycling through the set of possible I/O port addresses */
+
+ if (ndrives==0)
+ {
+ msg(DBG_INF, "No drive found.\n");
+#ifdef MODULE
+ return -EIO;
+#else
+ goto init_done;
+#endif /* MODULE */
+ }
+
+ if (port_index>0)
+ {
+ msg(DBG_INF, "You should read Documentation/cdrom/sbpcd\n");
+ msg(DBG_INF, "and then configure sbpcd.h for your hardware.\n");
+ }
+ check_datarate();
+ msg(DBG_INI,"check_datarate done.\n");
+
+ for (j=0;jdrv_id==-1)
+ continue;
+ switch_drive(p);
+#if 1
+ if (!famL_drive) cc_DriveReset();
+#endif
+ if (!st_spinning) cc_SpinUp();
+ p->sbp_first_frame = -1; /* First frame in buffer */
+ p->sbp_last_frame = -1; /* Last frame in buffer */
+ p->sbp_read_frames = 0; /* Number of frames being read to buffer */
+ p->sbp_current = 0; /* Frame being currently read */
+ p->CD_changed=1;
+ p->frame_size=CD_FRAMESIZE;
+ p->f_eject=0;
+#if EJECT
+ if (!fam0_drive) p->f_eject=1;
+#endif /* EJECT */
+ cc_ReadStatus();
+ i=ResponseStatus(); /* returns orig. status or p_busy_new */
+ if (famT_drive) i=ResponseStatus(); /* returns orig. status or p_busy_new */
+ if (i<0)
+ {
+ if (i!=-402)
+ msg(DBG_INF,"init: ResponseStatus returns %d.\n",i);
+ }
+ else
+ {
+ if (st_check)
+ {
+ i=cc_ReadError();
+ msg(DBG_INI,"init: cc_ReadError returns %d\n",i);
+ }
+ }
+ msg(DBG_INI,"init: first GetStatus: %d\n",i);
+ msg(DBG_LCS,"init: first GetStatus: error_byte=%d\n",
+ p->error_byte);
+ if (p->error_byte==aud_12)
+ {
+ timeout=jiffies+2*HZ;
+ do
+ {
+ i=GetStatus();
+ msg(DBG_INI,"init: second GetStatus: %02X\n",i);
+ msg(DBG_LCS,
+ "init: second GetStatus: error_byte=%d\n",
+ p->error_byte);
+ if (i<0) break;
+ if (!st_caddy_in) break;
+ }
+ while ((!st_diskok)||time_after(jiffies, timeout));
+ }
+ i=SetSpeed();
+ if (i>=0) p->CD_changed=1;
+ }
+
+ if (!request_region(CDo_command,4,major_name))
+ {
+ printk(KERN_WARNING "sbpcd: Unable to request region 0x%x\n", CDo_command);
+ return -EIO;
+ }
+
+ /*
+ * Turn on the CD audio channels.
+ * The addresses are obtained from SOUND_BASE (see sbpcd.h).
+ */
+#if SOUND_BASE
+ OUT(MIXER_addr,MIXER_CD_Volume); /* select SB Pro mixer register */
+ OUT(MIXER_data,0xCC); /* one nibble per channel, max. value: 0xFF */
+#endif /* SOUND_BASE */
+
+ if (register_blkdev(MAJOR_NR, major_name)) {
+#ifdef MODULE
+ return -EIO;
+#else
+ goto init_done;
+#endif /* MODULE */
+ }
+
+ /*
+ * init error handling is broken beyond belief in this driver...
+ */
+ sbpcd_queue = blk_init_queue(do_sbpcd_request, &sbpcd_lock);
+ if (!sbpcd_queue) {
+ release_region(CDo_command,4);
+ unregister_blkdev(MAJOR_NR, major_name);
+ return -ENOMEM;
+ }
+
+ for (j=0;jdrv_id==-1) continue;
+ switch_drive(p);
+#ifdef SAFE_MIXED
+ p->has_data=0;
+#endif /* SAFE_MIXED */
+ /*
+ * allocate memory for the frame buffers
+ */
+ p->aud_buf=NULL;
+ p->sbp_audsiz=0;
+ p->sbp_bufsiz=buffers;
+ if (p->drv_type&drv_fam1)
+ if (READ_AUDIO>0)
+ p->sbp_audsiz = READ_AUDIO;
+ p->sbp_buf=(u_char *) vmalloc(buffers*CD_FRAMESIZE);
+ if (!p->sbp_buf) {
+ msg(DBG_INF,"data buffer (%d frames) not available.\n",
+ buffers);
+ if ((unregister_blkdev(MAJOR_NR, major_name) == -EINVAL))
+ {
+ printk("Can't unregister %s\n", major_name);
+ }
+ release_region(CDo_command,4);
+ blk_cleanup_queue(sbpcd_queue);
+ return -EIO;
+ }
+#ifdef MODULE
+ msg(DBG_INF,"data buffer size: %d frames.\n",buffers);
+#endif /* MODULE */
+ if (p->sbp_audsiz>0)
+ {
+ p->aud_buf=(u_char *) vmalloc(p->sbp_audsiz*CD_FRAMESIZE_RAW);
+ if (p->aud_buf==NULL) msg(DBG_INF,"audio buffer (%d frames) not available.\n",p->sbp_audsiz);
+ else msg(DBG_INF,"audio buffer size: %d frames.\n",p->sbp_audsiz);
+ }
+ sbpcd_infop = vmalloc(sizeof (struct cdrom_device_info));
+ if (sbpcd_infop == NULL)
+ {
+ release_region(CDo_command,4);
+ blk_cleanup_queue(sbpcd_queue);
+ return -ENOMEM;
+ }
+ memset(sbpcd_infop, 0, sizeof(struct cdrom_device_info));
+ sbpcd_infop->ops = &sbpcd_dops;
+ sbpcd_infop->speed = 2;
+ sbpcd_infop->capacity = 1;
+ sprintf(sbpcd_infop->name, "sbpcd%d", j);
+ sbpcd_infop->handle = p;
+ p->sbpcd_infop = sbpcd_infop;
+ disk = alloc_disk(1);
+ disk->major = MAJOR_NR;
+ disk->first_minor = j;
+ disk->fops = &sbpcd_bdops;
+ strcpy(disk->disk_name, sbpcd_infop->name);
+ disk->flags = GENHD_FL_CD;
+ p->disk = disk;
+ if (register_cdrom(sbpcd_infop))
+ {
+ printk(" sbpcd: Unable to register with Uniform CD-ROm driver\n");
+ }
+ disk->private_data = p;
+ disk->queue = sbpcd_queue;
+ add_disk(disk);
+ }
+ blk_queue_hardsect_size(sbpcd_queue, CD_FRAMESIZE);
+
+#ifndef MODULE
+ init_done:
+#endif
+ return 0;
+}
+/*==========================================================================*/
+#ifdef MODULE
+static void sbpcd_exit(void)
+{
+ int j;
+
+ if ((unregister_blkdev(MAJOR_NR, major_name) == -EINVAL))
+ {
+ msg(DBG_INF, "What's that: can't unregister %s.\n", major_name);
+ return;
+ }
+ release_region(CDo_command,4);
+ blk_cleanup_queue(sbpcd_queue);
+ for (j=0;j0)
+ vfree(D_S[j].aud_buf);
+ if ((unregister_cdrom(D_S[j].sbpcd_infop) == -EINVAL))
+ {
+ msg(DBG_INF, "What's that: can't unregister info %s.\n", major_name);
+ return;
+ }
+ vfree(D_S[j].sbpcd_infop);
+ }
+ msg(DBG_INF, "%s module released.\n", major_name);
+}
+
+
+module_init(__sbpcd_init) /*HACK!*/;
+module_exit(sbpcd_exit);
+
+
+#endif /* MODULE */
+static int sbpcd_media_changed(struct cdrom_device_info *cdi, int disc_nr)
+{
+ struct sbpcd_drive *p = cdi->handle;
+ msg(DBG_CHK,"media_check (%s) called\n", cdi->name);
+
+ if (p->CD_changed==0xFF)
+ {
+ p->CD_changed=0;
+ msg(DBG_CHK,"medium changed (drive %s)\n", cdi->name);
+ current_drive->diskstate_flags &= ~toc_bit;
+ /* we *don't* need invalidate here, it's done by caller */
+ current_drive->diskstate_flags &= ~cd_size_bit;
+#ifdef SAFE_MIXED
+ current_drive->has_data=0;
+#endif /* SAFE_MIXED */
+
+ return (1);
+ }
+ else
+ return (0);
+}
+
+MODULE_LICENSE("GPL");
+/* FIXME: Old modules.conf claims MATSUSHITA_CDROM2_MAJOR and CDROM3, but
+ AFAICT this doesn't support those majors, so why? --RR 30 Jul 2003 */
+MODULE_ALIAS_BLOCKDEV_MAJOR(MATSUSHITA_CDROM_MAJOR);
+
+/*==========================================================================*/
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only. This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 8
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -8
+ * c-argdecl-indent: 8
+ * c-label-offset: -8
+ * c-continued-statement-offset: 8
+ * c-continued-brace-offset: 0
+ * End:
+ */
+
diff --git a/trunk/drivers/cdrom/sbpcd.h b/trunk/drivers/cdrom/sbpcd.h
new file mode 100644
index 000000000000..2f2225f13c6f
--- /dev/null
+++ b/trunk/drivers/cdrom/sbpcd.h
@@ -0,0 +1,839 @@
+/*
+ * sbpcd.h Specify interface address and interface type here.
+ */
+
+/*
+ * Attention! This file contains user-serviceable parts!
+ * I recommend to make use of it...
+ * If you feel helpless, look into Documentation/cdrom/sbpcd
+ * (good idea anyway, at least before mailing me).
+ *
+ * The definitions for the first controller can get overridden by
+ * the kernel command line ("lilo boot option").
+ * Examples:
+ * sbpcd=0x300,LaserMate
+ * or
+ * sbpcd=0x230,SoundBlaster
+ * or
+ * sbpcd=0x338,SoundScape
+ * or
+ * sbpcd=0x2C0,Teac16bit
+ *
+ * If sbpcd gets used as a module, you can load it with
+ * insmod sbpcd.o sbpcd=0x300,0
+ * or
+ * insmod sbpcd.o sbpcd=0x230,1
+ * or
+ * insmod sbpcd.o sbpcd=0x338,2
+ * or
+ * insmod sbpcd.o sbpcd=0x2C0,3
+ * respective to override the configured address and type.
+ */
+
+/*
+ * define your CDROM port base address as CDROM_PORT
+ * and specify the type of your interface card as SBPRO.
+ *
+ * address:
+ * ========
+ * SBPRO type addresses typically are 0x0230 (=0x220+0x10), 0x0250, ...
+ * LASERMATE type (CI-101P, WDH-7001C) addresses typically are 0x0300, ...
+ * SOUNDSCAPE addresses are from the LASERMATE type and range. You have to
+ * specify the REAL address here, not the configuration port address. Look
+ * at the CDROM driver's invoking line within your DOS CONFIG.SYS, or let
+ * sbpcd auto-probe, if you are not firm with the address.
+ * There are some soundcards on the market with 0x0630, 0x0650, ...; their
+ * type is not obvious (both types are possible).
+ *
+ * example: if your SBPRO audio address is 0x220, specify 0x230 and SBPRO 1.
+ * if your soundcard has its CDROM port above 0x300, specify
+ * that address and try SBPRO 0 first.
+ * if your SoundScape configuration port is at 0x330, specify
+ * 0x338 and SBPRO 2.
+ *
+ * interface type:
+ * ===============
+ * set SBPRO to 1 for "true" SoundBlaster card
+ * set SBPRO to 0 for "compatible" soundcards and
+ * for "poor" (no sound) interface cards.
+ * set SBPRO to 2 for Ensonic SoundScape or SPEA Media FX cards
+ * set SBPRO to 3 for Teac 16bit interface cards
+ *
+ * Almost all "compatible" sound boards need to set SBPRO to 0.
+ * If SBPRO is set wrong, the drives will get found - but any
+ * data access will give errors (audio access will work).
+ * The "OmniCD" no-sound interface card from CreativeLabs and most Teac
+ * interface cards need SBPRO 1.
+ *
+ * sound base:
+ * ===========
+ * The SOUND_BASE definition tells if we should try to turn the CD sound
+ * channels on. It will only be of use regarding soundcards with a SbPro
+ * compatible mixer.
+ *
+ * Example: #define SOUND_BASE 0x220 enables the sound card's CD channels
+ * #define SOUND_BASE 0 leaves the soundcard untouched
+ */
+#define CDROM_PORT 0x340 /* <-----------<< port address */
+#define SBPRO 0 /* <-----------<< interface type */
+#define MAX_DRIVES 4 /* set to 1 if the card does not use "drive select" */
+#define SOUND_BASE 0x220 /* <-----------<< sound address of this card or 0 */
+
+/*
+ * some more or less user dependent definitions - service them!
+ */
+
+/* Set this to 0 once you have configured your interface definitions right. */
+#define DISTRIBUTION 1
+
+/*
+ * Time to wait after giving a message.
+ * This gets important if you enable non-standard DBG_xxx flags.
+ * You will see what happens if you omit the pause or make it
+ * too short. Be warned!
+ */
+#define KLOGD_PAUSE 1
+
+/* tray control: eject tray if no disk is in */
+#if DISTRIBUTION
+#define JUKEBOX 0
+#else
+#define JUKEBOX 1
+#endif /* DISTRIBUTION */
+
+/* tray control: eject tray after last use */
+#if DISTRIBUTION
+#define EJECT 0
+#else
+#define EJECT 1
+#endif /* DISTRIBUTION */
+
+/* max. number of audio frames to read with one */
+/* request (allocates n* 2352 bytes kernel memory!) */
+/* may be freely adjusted, f.e. 75 (= 1 sec.), at */
+/* runtime by use of the CDROMAUDIOBUFSIZ ioctl. */
+#define READ_AUDIO 0
+
+/* Optimizations for the Teac CD-55A drive read performance.
+ * SBP_TEAC_SPEED can be changed here, or one can set the
+ * variable "teac" when loading as a module.
+ * Valid settings are:
+ * 0 - very slow - the recommended "DISTRIBUTION 1" setup.
+ * 1 - 2x performance with little overhead. No busy waiting.
+ * 2 - 4x performance with 5ms overhead per read. Busy wait.
+ *
+ * Setting SBP_TEAC_SPEED or the variable 'teac' to anything
+ * other than 0 may cause problems. If you run into them, first
+ * change SBP_TEAC_SPEED back to 0 and see if your drive responds
+ * normally. If yes, you are "allowed" to report your case - to help
+ * me with the driver, not to solve your hassle. Don´t mail if you
+ * simply are stuck into your own "tuning" experiments, you know?
+ */
+#define SBP_TEAC_SPEED 1
+
+/*==========================================================================*/
+/*==========================================================================*/
+/*
+ * nothing to change below here if you are not fully aware what you're doing
+ */
+#ifndef _LINUX_SBPCD_H
+
+#define _LINUX_SBPCD_H
+/*==========================================================================*/
+/*==========================================================================*/
+/*
+ * driver's own read_ahead, data mode
+ */
+#define SBP_BUFFER_FRAMES 8
+
+#define LONG_TIMING 0 /* test against timeouts with "gold" CDs on CR-521 */
+#undef FUTURE
+#undef SAFE_MIXED
+
+#define TEST_UPC 0
+#define SPEA_TEST 0
+#define TEST_STI 0
+#define OLD_BUSY 0
+#undef PATH_CHECK
+#ifndef SOUND_BASE
+#define SOUND_BASE 0
+#endif
+#if DISTRIBUTION
+#undef SBP_TEAC_SPEED
+#define SBP_TEAC_SPEED 0
+#endif
+/*==========================================================================*/
+/*
+ * DDI interface definitions
+ * "invented" by Fred N. van Kempen..
+ */
+#define DDIOCSDBG 0x9000
+
+/*==========================================================================*/
+/*
+ * "private" IOCTL functions
+ */
+#define CDROMAUDIOBUFSIZ 0x5382 /* set the audio buffer size */
+
+/*==========================================================================*/
+/*
+ * Debug output levels
+ */
+#define DBG_INF 1 /* necessary information */
+#define DBG_BSZ 2 /* BLOCK_SIZE trace */
+#define DBG_REA 3 /* READ status trace */
+#define DBG_CHK 4 /* MEDIA CHECK trace */
+#define DBG_TIM 5 /* datarate timer test */
+#define DBG_INI 6 /* initialization trace */
+#define DBG_TOC 7 /* tell TocEntry values */
+#define DBG_IOC 8 /* ioctl trace */
+#define DBG_STA 9 /* ResponseStatus() trace */
+#define DBG_ERR 10 /* cc_ReadError() trace */
+#define DBG_CMD 11 /* cmd_out() trace */
+#define DBG_WRN 12 /* give explanation before auto-probing */
+#define DBG_MUL 13 /* multi session code test */
+#define DBG_IDX 14 /* test code for drive_id !=0 */
+#define DBG_IOX 15 /* some special information */
+#define DBG_DID 16 /* drive ID test */
+#define DBG_RES 17 /* drive reset info */
+#define DBG_SPI 18 /* SpinUp test */
+#define DBG_IOS 19 /* ioctl trace: subchannel functions */
+#define DBG_IO2 20 /* ioctl trace: general */
+#define DBG_UPC 21 /* show UPC information */
+#define DBG_XA1 22 /* XA mode debugging */
+#define DBG_LCK 23 /* door (un)lock info */
+#define DBG_SQ1 24 /* dump SubQ frame */
+#define DBG_AUD 25 /* READ AUDIO debugging */
+#define DBG_SEQ 26 /* Sequoia interface configuration trace */
+#define DBG_LCS 27 /* Longshine LCS-7260 debugging trace */
+#define DBG_CD2 28 /* MKE/Funai CD200 debugging trace */
+#define DBG_TEA 29 /* TEAC CD-55A debugging trace */
+#define DBG_ECS 30 /* ECS-AT (Vertos 100) debugging trace */
+#define DBG_000 31 /* unnecessary information */
+
+/*==========================================================================*/
+/*==========================================================================*/
+
+/*
+ * bits of flags_cmd_out:
+ */
+#define f_respo3 0x100
+#define f_putcmd 0x80
+#define f_respo2 0x40
+#define f_lopsta 0x20
+#define f_getsta 0x10
+#define f_ResponseStatus 0x08
+#define f_obey_p_check 0x04
+#define f_bit1 0x02
+#define f_wait_if_busy 0x01
+
+/*
+ * diskstate_flags:
+ */
+#define x80_bit 0x80
+#define upc_bit 0x40
+#define volume_bit 0x20
+#define toc_bit 0x10
+#define multisession_bit 0x08
+#define cd_size_bit 0x04
+#define subq_bit 0x02
+#define frame_size_bit 0x01
+
+/*
+ * disk states (bits of diskstate_flags):
+ */
+#define upc_valid (current_drive->diskstate_flags&upc_bit)
+#define volume_valid (current_drive->diskstate_flags&volume_bit)
+#define toc_valid (current_drive->diskstate_flags&toc_bit)
+#define cd_size_valid (current_drive->diskstate_flags&cd_size_bit)
+#define subq_valid (current_drive->diskstate_flags&subq_bit)
+#define frame_size_valid (current_drive->diskstate_flags&frame_size_bit)
+
+/*
+ * the status_bits variable
+ */
+#define p_success 0x100
+#define p_door_closed 0x80
+#define p_caddy_in 0x40
+#define p_spinning 0x20
+#define p_check 0x10
+#define p_busy_new 0x08
+#define p_door_locked 0x04
+#define p_disk_ok 0x01
+
+/*
+ * LCS-7260 special status result bits:
+ */
+#define p_lcs_door_locked 0x02
+#define p_lcs_door_closed 0x01 /* probably disk_in */
+
+/*
+ * CR-52x special status result bits:
+ */
+#define p_caddin_old 0x40
+#define p_success_old 0x08
+#define p_busy_old 0x04
+#define p_bit_1 0x02 /* hopefully unused now */
+
+/*
+ * "generation specific" defs of the status result bits:
+ */
+#define p0_door_closed 0x80
+#define p0_caddy_in 0x40
+#define p0_spinning 0x20
+#define p0_check 0x10
+#define p0_success 0x08 /* unused */
+#define p0_busy 0x04
+#define p0_bit_1 0x02 /* unused */
+#define p0_disk_ok 0x01
+
+#define pL_disk_in 0x40
+#define pL_spinning 0x20
+#define pL_check 0x10
+#define pL_success 0x08 /* unused ?? */
+#define pL_busy 0x04
+#define pL_door_locked 0x02
+#define pL_door_closed 0x01
+
+#define pV_door_closed 0x40
+#define pV_spinning 0x20
+#define pV_check 0x10
+#define pV_success 0x08
+#define pV_busy 0x04
+#define pV_door_locked 0x02
+#define pV_disk_ok 0x01
+
+#define p1_door_closed 0x80
+#define p1_disk_in 0x40
+#define p1_spinning 0x20
+#define p1_check 0x10
+#define p1_busy 0x08
+#define p1_door_locked 0x04
+#define p1_bit_1 0x02 /* unused */
+#define p1_disk_ok 0x01
+
+#define p2_disk_ok 0x80
+#define p2_door_locked 0x40
+#define p2_spinning 0x20
+#define p2_busy2 0x10
+#define p2_busy1 0x08
+#define p2_door_closed 0x04
+#define p2_disk_in 0x02
+#define p2_check 0x01
+
+/*
+ * used drive states:
+ */
+#define st_door_closed (current_drive->status_bits&p_door_closed)
+#define st_caddy_in (current_drive->status_bits&p_caddy_in)
+#define st_spinning (current_drive->status_bits&p_spinning)
+#define st_check (current_drive->status_bits&p_check)
+#define st_busy (current_drive->status_bits&p_busy_new)
+#define st_door_locked (current_drive->status_bits&p_door_locked)
+#define st_diskok (current_drive->status_bits&p_disk_ok)
+
+/*
+ * bits of the CDi_status register:
+ */
+#define s_not_result_ready 0x04 /* 0: "result ready" */
+#define s_not_data_ready 0x02 /* 0: "data ready" */
+#define s_attention 0x01 /* 1: "attention required" */
+/*
+ * usable as:
+ */
+#define DRV_ATTN ((inb(CDi_status)&s_attention)!=0)
+#define DATA_READY ((inb(CDi_status)&s_not_data_ready)==0)
+#define RESULT_READY ((inb(CDi_status)&s_not_result_ready)==0)
+
+/*
+ * drive families and types (firmware versions):
+ */
+#define drv_fam0 0x0100 /* CR-52x family */
+#define drv_199 (drv_fam0+0x01) /* <200 */
+#define drv_200 (drv_fam0+0x02) /* <201 */
+#define drv_201 (drv_fam0+0x03) /* <210 */
+#define drv_210 (drv_fam0+0x04) /* <211 */
+#define drv_211 (drv_fam0+0x05) /* <300 */
+#define drv_300 (drv_fam0+0x06) /* >=300 */
+
+#define drv_fam1 0x0200 /* CR-56x family */
+#define drv_099 (drv_fam1+0x01) /* <100 */
+#define drv_100 (drv_fam1+0x02) /* >=100, only 1.02 and 5.00 known */
+
+#define drv_fam2 0x0400 /* CD200 family */
+
+#define drv_famT 0x0800 /* TEAC CD-55A */
+
+#define drv_famL 0x1000 /* Longshine family */
+#define drv_260 (drv_famL+0x01) /* LCS-7260 */
+#define drv_e1 (drv_famL+0x01) /* LCS-7260, firmware "A E1" */
+#define drv_f4 (drv_famL+0x02) /* LCS-7260, firmware "A4F4" */
+
+#define drv_famV 0x2000 /* ECS-AT (vertos-100) family */
+#define drv_at (drv_famV+0x01) /* ECS-AT, firmware "1.00" */
+
+#define fam0_drive (current_drive->drv_type&drv_fam0)
+#define famL_drive (current_drive->drv_type&drv_famL)
+#define famV_drive (current_drive->drv_type&drv_famV)
+#define fam1_drive (current_drive->drv_type&drv_fam1)
+#define fam2_drive (current_drive->drv_type&drv_fam2)
+#define famT_drive (current_drive->drv_type&drv_famT)
+#define fam0L_drive (current_drive->drv_type&(drv_fam0|drv_famL))
+#define fam0V_drive (current_drive->drv_type&(drv_fam0|drv_famV))
+#define famLV_drive (current_drive->drv_type&(drv_famL|drv_famV))
+#define fam0LV_drive (current_drive->drv_type&(drv_fam0|drv_famL|drv_famV))
+#define fam1L_drive (current_drive->drv_type&(drv_fam1|drv_famL))
+#define fam1V_drive (current_drive->drv_type&(drv_fam1|drv_famV))
+#define fam1LV_drive (current_drive->drv_type&(drv_fam1|drv_famL|drv_famV))
+#define fam01_drive (current_drive->drv_type&(drv_fam0|drv_fam1))
+#define fam12_drive (current_drive->drv_type&(drv_fam1|drv_fam2))
+#define fam2T_drive (current_drive->drv_type&(drv_fam2|drv_famT))
+
+/*
+ * audio states:
+ */
+#define audio_completed 3 /* Forgot this one! --AJK */
+#define audio_playing 2
+#define audio_pausing 1
+
+/*
+ * drv_pattern, drv_options:
+ */
+#define speed_auto 0x80
+#define speed_300 0x40
+#define speed_150 0x20
+#define audio_mono 0x04
+
+/*
+ * values of cmd_type (0 else):
+ */
+#define READ_M1 0x01 /* "data mode 1": 2048 bytes per frame */
+#define READ_M2 0x02 /* "data mode 2": 12+2048+280 bytes per frame */
+#define READ_SC 0x04 /* "subchannel info": 96 bytes per frame */
+#define READ_AU 0x08 /* "audio frame": 2352 bytes per frame */
+
+/*
+ * sense_byte:
+ *
+ * values: 00
+ * 01
+ * 81
+ * 82 "raw audio" mode
+ * xx from infobuf[0] after 85 00 00 00 00 00 00
+ */
+
+/* audio status (bin) */
+#define aud_00 0x00 /* Audio status byte not supported or not valid */
+#define audx11 0x0b /* Audio play operation in progress */
+#define audx12 0x0c /* Audio play operation paused */
+#define audx13 0x0d /* Audio play operation successfully completed */
+#define audx14 0x0e /* Audio play operation stopped due to error */
+#define audx15 0x0f /* No current audio status to return */
+/* audio status (bcd) */
+#define aud_11 0x11 /* Audio play operation in progress */
+#define aud_12 0x12 /* Audio play operation paused */
+#define aud_13 0x13 /* Audio play operation successfully completed */
+#define aud_14 0x14 /* Audio play operation stopped due to error */
+#define aud_15 0x15 /* No current audio status to return */
+
+/*
+ * highest allowed drive number (MINOR+1)
+ */
+#define NR_SBPCD 4
+
+/*
+ * we try to never disable interrupts - seems to work
+ */
+#define SBPCD_DIS_IRQ 0
+
+/*
+ * "write byte to port"
+ */
+#define OUT(x,y) outb(y,x)
+
+/*==========================================================================*/
+
+#define MIXER_addr SOUND_BASE+4 /* sound card's address register */
+#define MIXER_data SOUND_BASE+5 /* sound card's data register */
+#define MIXER_CD_Volume 0x28 /* internal SB Pro register address */
+
+/*==========================================================================*/
+
+#define MAX_TRACKS 99
+
+#define ERR_DISKCHANGE 615
+
+/*==========================================================================*/
+/*
+ * To make conversions easier (machine dependent!)
+ */
+typedef union _msf
+{
+ u_int n;
+ u_char c[4];
+} MSF;
+
+typedef union _blk
+{
+ u_int n;
+ u_char c[4];
+} BLK;
+
+/*==========================================================================*/
+
+/*============================================================================
+==============================================================================
+
+COMMAND SET of "old" drives like CR-521, CR-522
+ (the CR-562 family is different):
+
+No. Command Code
+--------------------------------------------
+
+Drive Commands:
+ 1 Seek 01
+ 2 Read Data 02
+ 3 Read XA-Data 03
+ 4 Read Header 04
+ 5 Spin Up 05
+ 6 Spin Down 06
+ 7 Diagnostic 07
+ 8 Read UPC 08
+ 9 Read ISRC 09
+10 Play Audio 0A
+11 Play Audio MSF 0B
+12 Play Audio Track/Index 0C
+
+Status Commands:
+13 Read Status 81
+14 Read Error 82
+15 Read Drive Version 83
+16 Mode Select 84
+17 Mode Sense 85
+18 Set XA Parameter 86
+19 Read XA Parameter 87
+20 Read Capacity 88
+21 Read SUB_Q 89
+22 Read Disc Code 8A
+23 Read Disc Information 8B
+24 Read TOC 8C
+25 Pause/Resume 8D
+26 Read Packet 8E
+27 Read Path Check 00
+
+
+all numbers (lba, msf-bin, msf-bcd, counts) to transfer high byte first
+
+mnemo 7-byte command #bytes response (r0...rn)
+________ ____________________ ____
+
+Read Status:
+status: 81. (1) one-byte command, gives the main
+ status byte
+Read Error:
+check1: 82 00 00 00 00 00 00. (6) r1: audio status
+
+Read Packet:
+check2: 8e xx 00 00 00 00 00. (xx) gets xx bytes response, relating
+ to commands 01 04 05 07 08 09
+
+Play Audio:
+play: 0a ll-bb-aa nn-nn-nn. (0) play audio, ll-bb-aa: starting block (lba),
+ nn-nn-nn: #blocks
+Play Audio MSF:
+ 0b mm-ss-ff mm-ss-ff (0) play audio from/to
+
+Play Audio Track/Index:
+ 0c ...
+
+Pause/Resume:
+pause: 8d pr 00 00 00 00 00. (0) pause (pr=00)
+ resume (pr=80) audio playing
+
+Mode Select:
+ 84 00 nn-nn ??.?? 00 (0) nn-nn: 2048 or 2340
+ possibly defines transfer size
+
+set_vol: 84 83 00 00 sw le 00. (0) sw(itch): lrxxxxxx (off=1)
+ le(vel): min=0, max=FF, else half
+ (firmware 2.11)
+
+Mode Sense:
+get_vol: 85 03 00 00 00 00 00. (2) tell current audio volume setting
+
+Read Disc Information:
+tocdesc: 8b 00 00 00 00 00 00. (6) read the toc descriptor ("msf-bin"-format)
+
+Read TOC:
+tocent: 8c fl nn 00 00 00 00. (8) read toc entry #nn
+ (fl=0:"lba"-, =2:"msf-bin"-format)
+
+Read Capacity:
+capacit: 88 00 00 00 00 00 00. (5) "read CD-ROM capacity"
+
+
+Read Path Check:
+ping: 00 00 00 00 00 00 00. (2) r0=AA, r1=55
+ ("ping" if the drive is connected)
+
+Read Drive Version:
+ident: 83 00 00 00 00 00 00. (12) gives "MATSHITAn.nn"
+ (n.nn = 2.01, 2.11., 3.00, ...)
+
+Seek:
+seek: 01 00 ll-bb-aa 00 00. (0)
+seek: 01 02 mm-ss-ff 00 00. (0)
+
+Read Data:
+read: 02 xx-xx-xx nn-nn fl. (?) read nn-nn blocks of 2048 bytes,
+ starting at block xx-xx-xx
+ fl=0: "lba"-, =2:"msf-bcd"-coded xx-xx-xx
+
+Read XA-Data:
+read: 03 xx-xx-xx nn-nn fl. (?) read nn-nn blocks of 2340 bytes,
+ starting at block xx-xx-xx
+ fl=0: "lba"-, =2:"msf-bcd"-coded xx-xx-xx
+
+Read SUB_Q:
+ 89 fl 00 00 00 00 00. (13) r0: audio status, r4-r7: lba/msf,
+ fl=0: "lba", fl=2: "msf"
+
+Read Disc Code:
+ 8a 00 00 00 00 00 00. (14) possibly extended "check condition"-info
+
+Read Header:
+ 04 00 ll-bb-aa 00 00. (0) 4 bytes response with "check2"
+ 04 02 mm-ss-ff 00 00. (0) 4 bytes response with "check2"
+
+Spin Up:
+ 05 00 ll-bb-aa 00 00. (0) possibly implies a "seek"
+
+Spin Down:
+ 06 ...
+
+Diagnostic:
+ 07 00 ll-bb-aa 00 00. (2) 2 bytes response with "check2"
+ 07 02 mm-ss-ff 00 00. (2) 2 bytes response with "check2"
+
+Read UPC:
+ 08 00 ll-bb-aa 00 00. (16)
+ 08 02 mm-ss-ff 00 00. (16)
+
+Read ISRC:
+ 09 00 ll-bb-aa 00 00. (15) 15 bytes response with "check2"
+ 09 02 mm-ss-ff 00 00. (15) 15 bytes response with "check2"
+
+Set XA Parameter:
+ 86 ...
+
+Read XA Parameter:
+ 87 ...
+
+==============================================================================
+============================================================================*/
+
+/*
+ * commands
+ *
+ * CR-52x: CMD0_
+ * CR-56x: CMD1_
+ * CD200: CMD2_
+ * LCS-7260: CMDL_
+ * TEAC CD-55A: CMDT_
+ * ECS-AT: CMDV_
+ */
+#define CMD1_RESET 0x0a
+#define CMD2_RESET 0x01
+#define CMDT_RESET 0xc0
+
+#define CMD1_LOCK_CTL 0x0c
+#define CMD2_LOCK_CTL 0x1e
+#define CMDT_LOCK_CTL CMD2_LOCK_CTL
+#define CMDL_LOCK_CTL 0x0e
+#define CMDV_LOCK_CTL CMDL_LOCK_CTL
+
+#define CMD1_TRAY_CTL 0x07
+#define CMD2_TRAY_CTL 0x1b
+#define CMDT_TRAY_CTL CMD2_TRAY_CTL
+#define CMDL_TRAY_CTL 0x0d
+#define CMDV_TRAY_CTL CMDL_TRAY_CTL
+
+#define CMD1_MULTISESS 0x8d
+#define CMDL_MULTISESS 0x8c
+#define CMDV_MULTISESS CMDL_MULTISESS
+
+#define CMD1_SUBCHANINF 0x11
+#define CMD2_SUBCHANINF 0x??
+
+#define CMD1_ABORT 0x08
+#define CMD2_ABORT 0x08
+#define CMDT_ABORT 0x08
+
+#define CMD2_x02 0x02
+
+#define CMD2_SETSPEED 0xda
+
+#define CMD0_PATH_CHECK 0x00
+#define CMD1_PATH_CHECK 0x???
+#define CMD2_PATH_CHECK 0x???
+#define CMDT_PATH_CHECK 0x???
+#define CMDL_PATH_CHECK CMD0_PATH_CHECK
+#define CMDV_PATH_CHECK CMD0_PATH_CHECK
+
+#define CMD0_SEEK 0x01
+#define CMD1_SEEK CMD0_SEEK
+#define CMD2_SEEK 0x2b
+#define CMDT_SEEK CMD2_SEEK
+#define CMDL_SEEK CMD0_SEEK
+#define CMDV_SEEK CMD0_SEEK
+
+#define CMD0_READ 0x02
+#define CMD1_READ 0x10
+#define CMD2_READ 0x28
+#define CMDT_READ CMD2_READ
+#define CMDL_READ CMD0_READ
+#define CMDV_READ CMD0_READ
+
+#define CMD0_READ_XA 0x03
+#define CMD2_READ_XA 0xd4
+#define CMD2_READ_XA2 0xd5
+#define CMDL_READ_XA CMD0_READ_XA /* really ?? */
+#define CMDV_READ_XA CMD0_READ_XA
+
+#define CMD0_READ_HEAD 0x04
+
+#define CMD0_SPINUP 0x05
+#define CMD1_SPINUP 0x02
+#define CMD2_SPINUP CMD2_TRAY_CTL
+#define CMDL_SPINUP CMD0_SPINUP
+#define CMDV_SPINUP CMD0_SPINUP
+
+#define CMD0_SPINDOWN 0x06 /* really??? */
+#define CMD1_SPINDOWN 0x06
+#define CMD2_SPINDOWN CMD2_TRAY_CTL
+#define CMDL_SPINDOWN 0x0d
+#define CMDV_SPINDOWN CMD0_SPINDOWN
+
+#define CMD0_DIAG 0x07
+
+#define CMD0_READ_UPC 0x08
+#define CMD1_READ_UPC 0x88
+#define CMD2_READ_UPC 0x???
+#define CMDL_READ_UPC CMD0_READ_UPC
+#define CMDV_READ_UPC 0x8f
+
+#define CMD0_READ_ISRC 0x09
+
+#define CMD0_PLAY 0x0a
+#define CMD1_PLAY 0x???
+#define CMD2_PLAY 0x???
+#define CMDL_PLAY CMD0_PLAY
+#define CMDV_PLAY CMD0_PLAY
+
+#define CMD0_PLAY_MSF 0x0b
+#define CMD1_PLAY_MSF 0x0e
+#define CMD2_PLAY_MSF 0x47
+#define CMDT_PLAY_MSF CMD2_PLAY_MSF
+#define CMDL_PLAY_MSF 0x???
+
+#define CMD0_PLAY_TI 0x0c
+#define CMD1_PLAY_TI 0x0f
+
+#define CMD0_STATUS 0x81
+#define CMD1_STATUS 0x05
+#define CMD2_STATUS 0x00
+#define CMDT_STATUS CMD2_STATUS
+#define CMDL_STATUS CMD0_STATUS
+#define CMDV_STATUS CMD0_STATUS
+#define CMD2_SEEK_LEADIN 0x00
+
+#define CMD0_READ_ERR 0x82
+#define CMD1_READ_ERR CMD0_READ_ERR
+#define CMD2_READ_ERR 0x03
+#define CMDT_READ_ERR CMD2_READ_ERR /* get audio status */
+#define CMDL_READ_ERR CMD0_READ_ERR
+#define CMDV_READ_ERR CMD0_READ_ERR
+
+#define CMD0_READ_VER 0x83
+#define CMD1_READ_VER CMD0_READ_VER
+#define CMD2_READ_VER 0x12
+#define CMDT_READ_VER CMD2_READ_VER /* really ?? */
+#define CMDL_READ_VER CMD0_READ_VER
+#define CMDV_READ_VER CMD0_READ_VER
+
+#define CMD0_SETMODE 0x84
+#define CMD1_SETMODE 0x09
+#define CMD2_SETMODE 0x55
+#define CMDT_SETMODE CMD2_SETMODE
+#define CMDL_SETMODE CMD0_SETMODE
+
+#define CMD0_GETMODE 0x85
+#define CMD1_GETMODE 0x84
+#define CMD2_GETMODE 0x5a
+#define CMDT_GETMODE CMD2_GETMODE
+#define CMDL_GETMODE CMD0_GETMODE
+
+#define CMD0_SET_XA 0x86
+
+#define CMD0_GET_XA 0x87
+
+#define CMD0_CAPACITY 0x88
+#define CMD1_CAPACITY 0x85
+#define CMD2_CAPACITY 0x25
+#define CMDL_CAPACITY CMD0_CAPACITY /* missing in some firmware versions */
+
+#define CMD0_READSUBQ 0x89
+#define CMD1_READSUBQ 0x87
+#define CMD2_READSUBQ 0x42
+#define CMDT_READSUBQ CMD2_READSUBQ
+#define CMDL_READSUBQ CMD0_READSUBQ
+#define CMDV_READSUBQ CMD0_READSUBQ
+
+#define CMD0_DISKCODE 0x8a
+
+#define CMD0_DISKINFO 0x8b
+#define CMD1_DISKINFO CMD0_DISKINFO
+#define CMD2_DISKINFO 0x43
+#define CMDT_DISKINFO CMD2_DISKINFO
+#define CMDL_DISKINFO CMD0_DISKINFO
+#define CMDV_DISKINFO CMD0_DISKINFO
+
+#define CMD0_READTOC 0x8c
+#define CMD1_READTOC CMD0_READTOC
+#define CMD2_READTOC 0x???
+#define CMDL_READTOC CMD0_READTOC
+#define CMDV_READTOC CMD0_READTOC
+
+#define CMD0_PAU_RES 0x8d
+#define CMD1_PAU_RES 0x0d
+#define CMD2_PAU_RES 0x4b
+#define CMDT_PAUSE CMD2_PAU_RES
+#define CMDL_PAU_RES CMD0_PAU_RES
+#define CMDV_PAUSE CMD0_PAU_RES
+
+#define CMD0_PACKET 0x8e
+#define CMD1_PACKET CMD0_PACKET
+#define CMD2_PACKET 0x???
+#define CMDL_PACKET CMD0_PACKET
+#define CMDV_PACKET 0x???
+
+/*==========================================================================*/
+/*==========================================================================*/
+#endif /* _LINUX_SBPCD_H */
+/*==========================================================================*/
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only. This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 8
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -8
+ * c-argdecl-indent: 8
+ * c-label-offset: -8
+ * c-continued-statement-offset: 8
+ * c-continued-brace-offset: 0
+ * End:
+ */
diff --git a/trunk/drivers/cdrom/sjcd.c b/trunk/drivers/cdrom/sjcd.c
new file mode 100644
index 000000000000..5409fca5bbfc
--- /dev/null
+++ b/trunk/drivers/cdrom/sjcd.c
@@ -0,0 +1,1815 @@
+/* -- sjcd.c
+ *
+ * Sanyo CD-ROM device driver implementation, Version 1.6
+ * Copyright (C) 1995 Vadim V. Model
+ *
+ * model@cecmow.enet.dec.com
+ * vadim@rbrf.ru
+ * vadim@ipsun.ras.ru
+ *
+ *
+ * This driver is based on pre-works by Eberhard Moenkeberg (emoenke@gwdg.de);
+ * it was developed under use of mcd.c from Martin Harriss, with help of
+ * Eric van der Maarel (H.T.M.v.d.Maarel@marin.nl).
+ *
+ * It is planned to include these routines into sbpcd.c later - to make
+ * a "mixed use" on one cable possible for all kinds of drives which use
+ * the SoundBlaster/Panasonic style CDROM interface. But today, the
+ * ability to install directly from CDROM is more important than flexibility.
+ *
+ * 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.
+ *
+ * History:
+ * 1.1 First public release with kernel version 1.3.7.
+ * Written by Vadim Model.
+ * 1.2 Added detection and configuration of cdrom interface
+ * on ISP16 soundcard.
+ * Allow for command line options: sjcd=,,
+ * 1.3 Some minor changes to README.sjcd.
+ * 1.4 MSS Sound support!! Listen to a CD through the speakers.
+ * 1.5 Module support and bugfixes.
+ * Tray locking.
+ * 1.6 Removed ISP16 code from this driver.
+ * Allow only to set io base address on command line: sjcd=
+ * Changes to Documentation/cdrom/sjcd
+ * Added cleanup after any error in the initialisation.
+ * 1.7 Added code to set the sector size tables to prevent the bug present in
+ * the previous version of this driver. Coded added by Anthony Barbachan
+ * from bugfix tip originally suggested by Alan Cox.
+ *
+ * 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
+ */
+
+#define SJCD_VERSION_MAJOR 1
+#define SJCD_VERSION_MINOR 7
+
+#include
+#include
+#include
+#include
+#include