#define GSCD_VERSION "0.4a Oliver Raupach <raupach@nwfs1.rz.fh-hannover.de>"
/*
linux/drivers/block/gscd.c - GoldStar R420 CDROM driver
Copyright (C) 1995 Oliver Raupach <raupach@nwfs1.rz.fh-hannover.de>
based upon pre-works by Eberhard Moenkeberg <emoenke@gwdg.de>
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 <tmm@image.dk>
*/
/* 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 <linux/module.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/kernel.h>
#include <linux/cdrom.h>
#include <linux/ioport.h>
#include <linux/major.h>
#include <linux/string.h>
#include <linux/init.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#define MAJOR_NR GOLDSTAR_CDROM_MAJOR
#include <linux/blkdev.h>
#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 <raupach@nwfs1.rz.fh-hannover.de>");
MODULE_LICENSE("GPL");
module_init(gscd_init);
module_exit(gscd_exit);
MODULE_ALIAS_BLOCKDEV_MAJOR(GOLDSTAR_CDROM_MAJOR);