diff options
Diffstat (limited to 'drivers/cdrom/gscd.c')
-rw-r--r-- | drivers/cdrom/gscd.c | 1031 |
1 files changed, 1031 insertions, 0 deletions
diff --git a/drivers/cdrom/gscd.c b/drivers/cdrom/gscd.c new file mode 100644 index 0000000..7eac10e --- /dev/null +++ b/drivers/cdrom/gscd.c @@ -0,0 +1,1031 @@ +#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 struct timer_list gscd_timer = TIMER_INITIALIZER(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 %lu\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"); + sprintf(gscd_disk->devfs_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); |