| /* GD ROM driver for the SEGA Dreamcast |
| * copyright Adrian McMenamin, 2007 |
| * With thanks to Marcus Comstedt and Nathan Keynes |
| * for work in reversing PIO and DMA |
| * |
| * 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., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| */ |
| |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/fs.h> |
| #include <linux/kernel.h> |
| #include <linux/list.h> |
| #include <linux/slab.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/cdrom.h> |
| #include <linux/genhd.h> |
| #include <linux/bio.h> |
| #include <linux/blkdev.h> |
| #include <linux/interrupt.h> |
| #include <linux/device.h> |
| #include <linux/wait.h> |
| #include <linux/workqueue.h> |
| #include <linux/platform_device.h> |
| #include <scsi/scsi.h> |
| #include <asm/io.h> |
| #include <asm/dma.h> |
| #include <asm/delay.h> |
| #include <mach/dma.h> |
| #include <mach/sysasic.h> |
| |
| #define GDROM_DEV_NAME "gdrom" |
| #define GD_SESSION_OFFSET 150 |
| |
| /* GD Rom commands */ |
| #define GDROM_COM_SOFTRESET 0x08 |
| #define GDROM_COM_EXECDIAG 0x90 |
| #define GDROM_COM_PACKET 0xA0 |
| #define GDROM_COM_IDDEV 0xA1 |
| |
| /* GD Rom registers */ |
| #define GDROM_BASE_REG 0xA05F7000 |
| #define GDROM_ALTSTATUS_REG (GDROM_BASE_REG + 0x18) |
| #define GDROM_DATA_REG (GDROM_BASE_REG + 0x80) |
| #define GDROM_ERROR_REG (GDROM_BASE_REG + 0x84) |
| #define GDROM_INTSEC_REG (GDROM_BASE_REG + 0x88) |
| #define GDROM_SECNUM_REG (GDROM_BASE_REG + 0x8C) |
| #define GDROM_BCL_REG (GDROM_BASE_REG + 0x90) |
| #define GDROM_BCH_REG (GDROM_BASE_REG + 0x94) |
| #define GDROM_DSEL_REG (GDROM_BASE_REG + 0x98) |
| #define GDROM_STATUSCOMMAND_REG (GDROM_BASE_REG + 0x9C) |
| #define GDROM_RESET_REG (GDROM_BASE_REG + 0x4E4) |
| |
| #define GDROM_DMA_STARTADDR_REG (GDROM_BASE_REG + 0x404) |
| #define GDROM_DMA_LENGTH_REG (GDROM_BASE_REG + 0x408) |
| #define GDROM_DMA_DIRECTION_REG (GDROM_BASE_REG + 0x40C) |
| #define GDROM_DMA_ENABLE_REG (GDROM_BASE_REG + 0x414) |
| #define GDROM_DMA_STATUS_REG (GDROM_BASE_REG + 0x418) |
| #define GDROM_DMA_WAIT_REG (GDROM_BASE_REG + 0x4A0) |
| #define GDROM_DMA_ACCESS_CTRL_REG (GDROM_BASE_REG + 0x4B8) |
| |
| #define GDROM_HARD_SECTOR 2048 |
| #define BLOCK_LAYER_SECTOR 512 |
| #define GD_TO_BLK 4 |
| |
| #define GDROM_DEFAULT_TIMEOUT (HZ * 7) |
| |
| static const struct { |
| int sense_key; |
| const char * const text; |
| } sense_texts[] = { |
| {NO_SENSE, "OK"}, |
| {RECOVERED_ERROR, "Recovered from error"}, |
| {NOT_READY, "Device not ready"}, |
| {MEDIUM_ERROR, "Disk not ready"}, |
| {HARDWARE_ERROR, "Hardware error"}, |
| {ILLEGAL_REQUEST, "Command has failed"}, |
| {UNIT_ATTENTION, "Device needs attention - disk may have been changed"}, |
| {DATA_PROTECT, "Data protection error"}, |
| {ABORTED_COMMAND, "Command aborted"}, |
| }; |
| |
| static struct platform_device *pd; |
| static int gdrom_major; |
| static DECLARE_WAIT_QUEUE_HEAD(command_queue); |
| static DECLARE_WAIT_QUEUE_HEAD(request_queue); |
| |
| static DEFINE_SPINLOCK(gdrom_lock); |
| static void gdrom_readdisk_dma(struct work_struct *work); |
| static DECLARE_WORK(work, gdrom_readdisk_dma); |
| static LIST_HEAD(gdrom_deferred); |
| |
| struct gdromtoc { |
| unsigned int entry[99]; |
| unsigned int first, last; |
| unsigned int leadout; |
| }; |
| |
| static struct gdrom_unit { |
| struct gendisk *disk; |
| struct cdrom_device_info *cd_info; |
| int status; |
| int pending; |
| int transfer; |
| char disk_type; |
| struct gdromtoc *toc; |
| struct request_queue *gdrom_rq; |
| } gd; |
| |
| struct gdrom_id { |
| char mid; |
| char modid; |
| char verid; |
| char padA[13]; |
| char mname[16]; |
| char modname[16]; |
| char firmver[16]; |
| char padB[16]; |
| }; |
| |
| static int gdrom_getsense(short *bufstring); |
| static int gdrom_packetcommand(struct cdrom_device_info *cd_info, |
| struct packet_command *command); |
| static int gdrom_hardreset(struct cdrom_device_info *cd_info); |
| |
| static bool gdrom_is_busy(void) |
| { |
| return (ctrl_inb(GDROM_ALTSTATUS_REG) & 0x80) != 0; |
| } |
| |
| static bool gdrom_data_request(void) |
| { |
| return (ctrl_inb(GDROM_ALTSTATUS_REG) & 0x88) == 8; |
| } |
| |
| static bool gdrom_wait_clrbusy(void) |
| { |
| unsigned long timeout = jiffies + GDROM_DEFAULT_TIMEOUT; |
| while ((ctrl_inb(GDROM_ALTSTATUS_REG) & 0x80) && |
| (time_before(jiffies, timeout))) |
| cpu_relax(); |
| return time_before(jiffies, timeout + 1); |
| } |
| |
| static bool gdrom_wait_busy_sleeps(void) |
| { |
| unsigned long timeout; |
| /* Wait to get busy first */ |
| timeout = jiffies + GDROM_DEFAULT_TIMEOUT; |
| while (!gdrom_is_busy() && time_before(jiffies, timeout)) |
| cpu_relax(); |
| /* Now wait for busy to clear */ |
| return gdrom_wait_clrbusy(); |
| } |
| |
| static void gdrom_identifydevice(void *buf) |
| { |
| int c; |
| short *data = buf; |
| /* If the device won't clear it has probably |
| * been hit by a serious failure - but we'll |
| * try to return a sense key even so */ |
| if (!gdrom_wait_clrbusy()) { |
| gdrom_getsense(NULL); |
| return; |
| } |
| ctrl_outb(GDROM_COM_IDDEV, GDROM_STATUSCOMMAND_REG); |
| if (!gdrom_wait_busy_sleeps()) { |
| gdrom_getsense(NULL); |
| return; |
| } |
| /* now read in the data */ |
| for (c = 0; c < 40; c++) |
| data[c] = ctrl_inw(GDROM_DATA_REG); |
| } |
| |
| static void gdrom_spicommand(void *spi_string, int buflen) |
| { |
| short *cmd = spi_string; |
| unsigned long timeout; |
| |
| /* ensure IRQ_WAIT is set */ |
| ctrl_outb(0x08, GDROM_ALTSTATUS_REG); |
| /* specify how many bytes we expect back */ |
| ctrl_outb(buflen & 0xFF, GDROM_BCL_REG); |
| ctrl_outb((buflen >> 8) & 0xFF, GDROM_BCH_REG); |
| /* other parameters */ |
| ctrl_outb(0, GDROM_INTSEC_REG); |
| ctrl_outb(0, GDROM_SECNUM_REG); |
| ctrl_outb(0, GDROM_ERROR_REG); |
| /* Wait until we can go */ |
| if (!gdrom_wait_clrbusy()) { |
| gdrom_getsense(NULL); |
| return; |
| } |
| timeout = jiffies + GDROM_DEFAULT_TIMEOUT; |
| ctrl_outb(GDROM_COM_PACKET, GDROM_STATUSCOMMAND_REG); |
| while (!gdrom_data_request() && time_before(jiffies, timeout)) |
| cpu_relax(); |
| if (!time_before(jiffies, timeout + 1)) { |
| gdrom_getsense(NULL); |
| return; |
| } |
| outsw(PHYSADDR(GDROM_DATA_REG), cmd, 6); |
| } |
| |
| |
| /* gdrom_command_executediagnostic: |
| * Used to probe for presence of working GDROM |
| * Restarts GDROM device and then applies standard ATA 3 |
| * Execute Diagnostic Command: a return of '1' indicates device 0 |
| * present and device 1 absent |
| */ |
| static char gdrom_execute_diagnostic(void) |
| { |
| gdrom_hardreset(gd.cd_info); |
| if (!gdrom_wait_clrbusy()) |
| return 0; |
| ctrl_outb(GDROM_COM_EXECDIAG, GDROM_STATUSCOMMAND_REG); |
| if (!gdrom_wait_busy_sleeps()) |
| return 0; |
| return ctrl_inb(GDROM_ERROR_REG); |
| } |
| |
| /* |
| * Prepare disk command |
| * byte 0 = 0x70 |
| * byte 1 = 0x1f |
| */ |
| static int gdrom_preparedisk_cmd(void) |
| { |
| struct packet_command *spin_command; |
| spin_command = kzalloc(sizeof(struct packet_command), GFP_KERNEL); |
| if (!spin_command) |
| return -ENOMEM; |
| spin_command->cmd[0] = 0x70; |
| spin_command->cmd[2] = 0x1f; |
| spin_command->buflen = 0; |
| gd.pending = 1; |
| gdrom_packetcommand(gd.cd_info, spin_command); |
| /* 60 second timeout */ |
| wait_event_interruptible_timeout(command_queue, gd.pending == 0, |
| GDROM_DEFAULT_TIMEOUT); |
| gd.pending = 0; |
| kfree(spin_command); |
| if (gd.status & 0x01) { |
| /* log an error */ |
| gdrom_getsense(NULL); |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| /* |
| * Read TOC command |
| * byte 0 = 0x14 |
| * byte 1 = session |
| * byte 3 = sizeof TOC >> 8 ie upper byte |
| * byte 4 = sizeof TOC & 0xff ie lower byte |
| */ |
| static int gdrom_readtoc_cmd(struct gdromtoc *toc, int session) |
| { |
| int tocsize; |
| struct packet_command *toc_command; |
| int err = 0; |
| |
| toc_command = kzalloc(sizeof(struct packet_command), GFP_KERNEL); |
| if (!toc_command) |
| return -ENOMEM; |
| tocsize = sizeof(struct gdromtoc); |
| toc_command->cmd[0] = 0x14; |
| toc_command->cmd[1] = session; |
| toc_command->cmd[3] = tocsize >> 8; |
| toc_command->cmd[4] = tocsize & 0xff; |
| toc_command->buflen = tocsize; |
| if (gd.pending) { |
| err = -EBUSY; |
| goto cleanup_readtoc_final; |
| } |
| gd.pending = 1; |
| gdrom_packetcommand(gd.cd_info, toc_command); |
| wait_event_interruptible_timeout(command_queue, gd.pending == 0, |
| GDROM_DEFAULT_TIMEOUT); |
| if (gd.pending) { |
| err = -EINVAL; |
| goto cleanup_readtoc; |
| } |
| insw(PHYSADDR(GDROM_DATA_REG), toc, tocsize/2); |
| if (gd.status & 0x01) |
| err = -EINVAL; |
| |
| cleanup_readtoc: |
| gd.pending = 0; |
| cleanup_readtoc_final: |
| kfree(toc_command); |
| return err; |
| } |
| |
| /* TOC helpers */ |
| static int get_entry_lba(int track) |
| { |
| return (cpu_to_be32(track & 0xffffff00) - GD_SESSION_OFFSET); |
| } |
| |
| static int get_entry_q_ctrl(int track) |
| { |
| return (track & 0x000000f0) >> 4; |
| } |
| |
| static int get_entry_track(int track) |
| { |
| return (track & 0x0000ff00) >> 8; |
| } |
| |
| static int gdrom_get_last_session(struct cdrom_device_info *cd_info, |
| struct cdrom_multisession *ms_info) |
| { |
| int fentry, lentry, track, data, tocuse, err; |
| if (!gd.toc) |
| return -ENOMEM; |
| tocuse = 1; |
| /* Check if GD-ROM */ |
| err = gdrom_readtoc_cmd(gd.toc, 1); |
| /* Not a GD-ROM so check if standard CD-ROM */ |
| if (err) { |
| tocuse = 0; |
| err = gdrom_readtoc_cmd(gd.toc, 0); |
| if (err) { |
| printk(KERN_INFO "GDROM: Could not get CD " |
| "table of contents\n"); |
| return -ENXIO; |
| } |
| } |
| |
| fentry = get_entry_track(gd.toc->first); |
| lentry = get_entry_track(gd.toc->last); |
| /* Find the first data track */ |
| track = get_entry_track(gd.toc->last); |
| do { |
| data = gd.toc->entry[track - 1]; |
| if (get_entry_q_ctrl(data)) |
| break; /* ie a real data track */ |
| track--; |
| } while (track >= fentry); |
| |
| if ((track > 100) || (track < get_entry_track(gd.toc->first))) { |
| printk(KERN_INFO "GDROM: No data on the last " |
| "session of the CD\n"); |
| gdrom_getsense(NULL); |
| return -ENXIO; |
| } |
| |
| ms_info->addr_format = CDROM_LBA; |
| ms_info->addr.lba = get_entry_lba(data); |
| ms_info->xa_flag = 1; |
| return 0; |
| } |
| |
| static int gdrom_open(struct cdrom_device_info *cd_info, int purpose) |
| { |
| /* spin up the disk */ |
| return gdrom_preparedisk_cmd(); |
| } |
| |
| /* this function is required even if empty */ |
| static void gdrom_release(struct cdrom_device_info *cd_info) |
| { |
| } |
| |
| static int gdrom_drivestatus(struct cdrom_device_info *cd_info, int ignore) |
| { |
| /* read the sense key */ |
| char sense = ctrl_inb(GDROM_ERROR_REG); |
| sense &= 0xF0; |
| if (sense == 0) |
| return CDS_DISC_OK; |
| if (sense == 0x20) |
| return CDS_DRIVE_NOT_READY; |
| /* default */ |
| return CDS_NO_INFO; |
| } |
| |
| static int gdrom_mediachanged(struct cdrom_device_info *cd_info, int ignore) |
| { |
| /* check the sense key */ |
| return (ctrl_inb(GDROM_ERROR_REG) & 0xF0) == 0x60; |
| } |
| |
| /* reset the G1 bus */ |
| static int gdrom_hardreset(struct cdrom_device_info *cd_info) |
| { |
| int count; |
| ctrl_outl(0x1fffff, GDROM_RESET_REG); |
| for (count = 0xa0000000; count < 0xa0200000; count += 4) |
| ctrl_inl(count); |
| return 0; |
| } |
| |
| /* keep the function looking like the universal |
| * CD Rom specification - returning int */ |
| static int gdrom_packetcommand(struct cdrom_device_info *cd_info, |
| struct packet_command *command) |
| { |
| gdrom_spicommand(&command->cmd, command->buflen); |
| return 0; |
| } |
| |
| /* Get Sense SPI command |
| * From Marcus Comstedt |
| * cmd = 0x13 |
| * cmd + 4 = length of returned buffer |
| * Returns 5 16 bit words |
| */ |
| static int gdrom_getsense(short *bufstring) |
| { |
| struct packet_command *sense_command; |
| short sense[5]; |
| int sense_key; |
| int err = -EIO; |
| |
| sense_command = kzalloc(sizeof(struct packet_command), GFP_KERNEL); |
| if (!sense_command) |
| return -ENOMEM; |
| sense_command->cmd[0] = 0x13; |
| sense_command->cmd[4] = 10; |
| sense_command->buflen = 10; |
| /* even if something is pending try to get |
| * the sense key if possible */ |
| if (gd.pending && !gdrom_wait_clrbusy()) { |
| err = -EBUSY; |
| goto cleanup_sense_final; |
| } |
| gd.pending = 1; |
| gdrom_packetcommand(gd.cd_info, sense_command); |
| wait_event_interruptible_timeout(command_queue, gd.pending == 0, |
| GDROM_DEFAULT_TIMEOUT); |
| if (gd.pending) |
| goto cleanup_sense; |
| insw(PHYSADDR(GDROM_DATA_REG), &sense, sense_command->buflen/2); |
| if (sense[1] & 40) { |
| printk(KERN_INFO "GDROM: Drive not ready - command aborted\n"); |
| goto cleanup_sense; |
| } |
| sense_key = sense[1] & 0x0F; |
| if (sense_key < ARRAY_SIZE(sense_texts)) |
| printk(KERN_INFO "GDROM: %s\n", sense_texts[sense_key].text); |
| else |
| printk(KERN_ERR "GDROM: Unknown sense key: %d\n", sense_key); |
| if (bufstring) /* return addional sense data */ |
| memcpy(bufstring, &sense[4], 2); |
| if (sense_key < 2) |
| err = 0; |
| |
| cleanup_sense: |
| gd.pending = 0; |
| cleanup_sense_final: |
| kfree(sense_command); |
| return err; |
| } |
| |
| static int gdrom_audio_ioctl(struct cdrom_device_info *cdi, unsigned int cmd, |
| void *arg) |
| { |
| return -EINVAL; |
| } |
| |
| static struct cdrom_device_ops gdrom_ops = { |
| .open = gdrom_open, |
| .release = gdrom_release, |
| .drive_status = gdrom_drivestatus, |
| .media_changed = gdrom_mediachanged, |
| .get_last_session = gdrom_get_last_session, |
| .reset = gdrom_hardreset, |
| .audio_ioctl = gdrom_audio_ioctl, |
| .capability = CDC_MULTI_SESSION | CDC_MEDIA_CHANGED | |
| CDC_RESET | CDC_DRIVE_STATUS | CDC_CD_R, |
| .n_minors = 1, |
| }; |
| |
| static int gdrom_bdops_open(struct inode *inode, struct file *file) |
| { |
| return cdrom_open(gd.cd_info, inode, file); |
| } |
| |
| static int gdrom_bdops_release(struct inode *inode, struct file *file) |
| { |
| return cdrom_release(gd.cd_info, file); |
| } |
| |
| static int gdrom_bdops_mediachanged(struct gendisk *disk) |
| { |
| return cdrom_media_changed(gd.cd_info); |
| } |
| |
| static int gdrom_bdops_ioctl(struct inode *inode, struct file *file, |
| unsigned cmd, unsigned long arg) |
| { |
| return cdrom_ioctl(file, gd.cd_info, inode, cmd, arg); |
| } |
| |
| static struct block_device_operations gdrom_bdops = { |
| .owner = THIS_MODULE, |
| .open = gdrom_bdops_open, |
| .release = gdrom_bdops_release, |
| .media_changed = gdrom_bdops_mediachanged, |
| .ioctl = gdrom_bdops_ioctl, |
| }; |
| |
| static irqreturn_t gdrom_command_interrupt(int irq, void *dev_id) |
| { |
| gd.status = ctrl_inb(GDROM_STATUSCOMMAND_REG); |
| if (gd.pending != 1) |
| return IRQ_HANDLED; |
| gd.pending = 0; |
| wake_up_interruptible(&command_queue); |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t gdrom_dma_interrupt(int irq, void *dev_id) |
| { |
| gd.status = ctrl_inb(GDROM_STATUSCOMMAND_REG); |
| if (gd.transfer != 1) |
| return IRQ_HANDLED; |
| gd.transfer = 0; |
| wake_up_interruptible(&request_queue); |
| return IRQ_HANDLED; |
| } |
| |
| static int __devinit gdrom_set_interrupt_handlers(void) |
| { |
| int err; |
| |
| err = request_irq(HW_EVENT_GDROM_CMD, gdrom_command_interrupt, |
| IRQF_DISABLED, "gdrom_command", &gd); |
| if (err) |
| return err; |
| err = request_irq(HW_EVENT_GDROM_DMA, gdrom_dma_interrupt, |
| IRQF_DISABLED, "gdrom_dma", &gd); |
| if (err) |
| free_irq(HW_EVENT_GDROM_CMD, &gd); |
| return err; |
| } |
| |
| /* Implement DMA read using SPI command |
| * 0 -> 0x30 |
| * 1 -> mode |
| * 2 -> block >> 16 |
| * 3 -> block >> 8 |
| * 4 -> block |
| * 8 -> sectors >> 16 |
| * 9 -> sectors >> 8 |
| * 10 -> sectors |
| */ |
| static void gdrom_readdisk_dma(struct work_struct *work) |
| { |
| int err, block, block_cnt; |
| struct packet_command *read_command; |
| struct list_head *elem, *next; |
| struct request *req; |
| unsigned long timeout; |
| |
| if (list_empty(&gdrom_deferred)) |
| return; |
| read_command = kzalloc(sizeof(struct packet_command), GFP_KERNEL); |
| if (!read_command) |
| return; /* get more memory later? */ |
| read_command->cmd[0] = 0x30; |
| read_command->cmd[1] = 0x20; |
| spin_lock(&gdrom_lock); |
| list_for_each_safe(elem, next, &gdrom_deferred) { |
| req = list_entry(elem, struct request, queuelist); |
| spin_unlock(&gdrom_lock); |
| block = req->sector/GD_TO_BLK + GD_SESSION_OFFSET; |
| block_cnt = req->nr_sectors/GD_TO_BLK; |
| ctrl_outl(PHYSADDR(req->buffer), GDROM_DMA_STARTADDR_REG); |
| ctrl_outl(block_cnt * GDROM_HARD_SECTOR, GDROM_DMA_LENGTH_REG); |
| ctrl_outl(1, GDROM_DMA_DIRECTION_REG); |
| ctrl_outl(1, GDROM_DMA_ENABLE_REG); |
| read_command->cmd[2] = (block >> 16) & 0xFF; |
| read_command->cmd[3] = (block >> 8) & 0xFF; |
| read_command->cmd[4] = block & 0xFF; |
| read_command->cmd[8] = (block_cnt >> 16) & 0xFF; |
| read_command->cmd[9] = (block_cnt >> 8) & 0xFF; |
| read_command->cmd[10] = block_cnt & 0xFF; |
| /* set for DMA */ |
| ctrl_outb(1, GDROM_ERROR_REG); |
| /* other registers */ |
| ctrl_outb(0, GDROM_SECNUM_REG); |
| ctrl_outb(0, GDROM_BCL_REG); |
| ctrl_outb(0, GDROM_BCH_REG); |
| ctrl_outb(0, GDROM_DSEL_REG); |
| ctrl_outb(0, GDROM_INTSEC_REG); |
| /* Wait for registers to reset after any previous activity */ |
| timeout = jiffies + HZ / 2; |
| while (gdrom_is_busy() && time_before(jiffies, timeout)) |
| cpu_relax(); |
| ctrl_outb(GDROM_COM_PACKET, GDROM_STATUSCOMMAND_REG); |
| timeout = jiffies + HZ / 2; |
| /* Wait for packet command to finish */ |
| while (gdrom_is_busy() && time_before(jiffies, timeout)) |
| cpu_relax(); |
| gd.pending = 1; |
| gd.transfer = 1; |
| outsw(PHYSADDR(GDROM_DATA_REG), &read_command->cmd, 6); |
| timeout = jiffies + HZ / 2; |
| /* Wait for any pending DMA to finish */ |
| while (ctrl_inb(GDROM_DMA_STATUS_REG) && |
| time_before(jiffies, timeout)) |
| cpu_relax(); |
| /* start transfer */ |
| ctrl_outb(1, GDROM_DMA_STATUS_REG); |
| wait_event_interruptible_timeout(request_queue, |
| gd.transfer == 0, GDROM_DEFAULT_TIMEOUT); |
| err = gd.transfer; |
| gd.transfer = 0; |
| gd.pending = 0; |
| /* now seek to take the request spinlock |
| * before handling ending the request */ |
| spin_lock(&gdrom_lock); |
| list_del_init(&req->queuelist); |
| end_dequeued_request(req, 1 - err); |
| } |
| spin_unlock(&gdrom_lock); |
| kfree(read_command); |
| } |
| |
| static void gdrom_request_handler_dma(struct request *req) |
| { |
| /* dequeue, add to list of deferred work |
| * and then schedule workqueue */ |
| blkdev_dequeue_request(req); |
| list_add_tail(&req->queuelist, &gdrom_deferred); |
| schedule_work(&work); |
| } |
| |
| static void gdrom_request(struct request_queue *rq) |
| { |
| struct request *req; |
| |
| while ((req = elv_next_request(rq)) != NULL) { |
| if (!blk_fs_request(req)) { |
| printk(KERN_DEBUG "GDROM: Non-fs request ignored\n"); |
| end_request(req, 0); |
| } |
| if (rq_data_dir(req) != READ) { |
| printk(KERN_NOTICE "GDROM: Read only device -"); |
| printk(" write request ignored\n"); |
| end_request(req, 0); |
| } |
| if (req->nr_sectors) |
| gdrom_request_handler_dma(req); |
| else |
| end_request(req, 0); |
| } |
| } |
| |
| /* Print string identifying GD ROM device */ |
| static int __devinit gdrom_outputversion(void) |
| { |
| struct gdrom_id *id; |
| char *model_name, *manuf_name, *firmw_ver; |
| int err = -ENOMEM; |
| |
| /* query device ID */ |
| id = kzalloc(sizeof(struct gdrom_id), GFP_KERNEL); |
| if (!id) |
| return err; |
| gdrom_identifydevice(id); |
| model_name = kstrndup(id->modname, 16, GFP_KERNEL); |
| if (!model_name) |
| goto free_id; |
| manuf_name = kstrndup(id->mname, 16, GFP_KERNEL); |
| if (!manuf_name) |
| goto free_model_name; |
| firmw_ver = kstrndup(id->firmver, 16, GFP_KERNEL); |
| if (!firmw_ver) |
| goto free_manuf_name; |
| printk(KERN_INFO "GDROM: %s from %s with firmware %s\n", |
| model_name, manuf_name, firmw_ver); |
| err = 0; |
| kfree(firmw_ver); |
| free_manuf_name: |
| kfree(manuf_name); |
| free_model_name: |
| kfree(model_name); |
| free_id: |
| kfree(id); |
| return err; |
| } |
| |
| /* set the default mode for DMA transfer */ |
| static int __devinit gdrom_init_dma_mode(void) |
| { |
| ctrl_outb(0x13, GDROM_ERROR_REG); |
| ctrl_outb(0x22, GDROM_INTSEC_REG); |
| if (!gdrom_wait_clrbusy()) |
| return -EBUSY; |
| ctrl_outb(0xEF, GDROM_STATUSCOMMAND_REG); |
| if (!gdrom_wait_busy_sleeps()) |
| return -EBUSY; |
| /* Memory protection setting for GDROM DMA |
| * Bits 31 - 16 security: 0x8843 |
| * Bits 15 and 7 reserved (0) |
| * Bits 14 - 8 start of transfer range in 1 MB blocks OR'ed with 0x80 |
| * Bits 6 - 0 end of transfer range in 1 MB blocks OR'ed with 0x80 |
| * (0x40 | 0x80) = start range at 0x0C000000 |
| * (0x7F | 0x80) = end range at 0x0FFFFFFF */ |
| ctrl_outl(0x8843407F, GDROM_DMA_ACCESS_CTRL_REG); |
| ctrl_outl(9, GDROM_DMA_WAIT_REG); /* DMA word setting */ |
| return 0; |
| } |
| |
| static void __devinit probe_gdrom_setupcd(void) |
| { |
| gd.cd_info->ops = &gdrom_ops; |
| gd.cd_info->capacity = 1; |
| strcpy(gd.cd_info->name, GDROM_DEV_NAME); |
| gd.cd_info->mask = CDC_CLOSE_TRAY|CDC_OPEN_TRAY|CDC_LOCK| |
| CDC_SELECT_DISC; |
| } |
| |
| static void __devinit probe_gdrom_setupdisk(void) |
| { |
| gd.disk->major = gdrom_major; |
| gd.disk->first_minor = 1; |
| gd.disk->minors = 1; |
| strcpy(gd.disk->disk_name, GDROM_DEV_NAME); |
| } |
| |
| static int __devinit probe_gdrom_setupqueue(void) |
| { |
| blk_queue_hardsect_size(gd.gdrom_rq, GDROM_HARD_SECTOR); |
| /* using DMA so memory will need to be contiguous */ |
| blk_queue_max_hw_segments(gd.gdrom_rq, 1); |
| /* set a large max size to get most from DMA */ |
| blk_queue_max_segment_size(gd.gdrom_rq, 0x40000); |
| gd.disk->queue = gd.gdrom_rq; |
| return gdrom_init_dma_mode(); |
| } |
| |
| /* |
| * register this as a block device and as compliant with the |
| * universal CD Rom driver interface |
| */ |
| static int __devinit probe_gdrom(struct platform_device *devptr) |
| { |
| int err; |
| /* Start the device */ |
| if (gdrom_execute_diagnostic() != 1) { |
| printk(KERN_WARNING "GDROM: ATA Probe for GDROM failed.\n"); |
| return -ENODEV; |
| } |
| /* Print out firmware ID */ |
| if (gdrom_outputversion()) |
| return -ENOMEM; |
| /* Register GDROM */ |
| gdrom_major = register_blkdev(0, GDROM_DEV_NAME); |
| if (gdrom_major <= 0) |
| return gdrom_major; |
| printk(KERN_INFO "GDROM: Registered with major number %d\n", |
| gdrom_major); |
| /* Specify basic properties of drive */ |
| gd.cd_info = kzalloc(sizeof(struct cdrom_device_info), GFP_KERNEL); |
| if (!gd.cd_info) { |
| err = -ENOMEM; |
| goto probe_fail_no_mem; |
| } |
| probe_gdrom_setupcd(); |
| gd.disk = alloc_disk(1); |
| if (!gd.disk) { |
| err = -ENODEV; |
| goto probe_fail_no_disk; |
| } |
| probe_gdrom_setupdisk(); |
| if (register_cdrom(gd.cd_info)) { |
| err = -ENODEV; |
| goto probe_fail_cdrom_register; |
| } |
| gd.disk->fops = &gdrom_bdops; |
| /* latch on to the interrupt */ |
| err = gdrom_set_interrupt_handlers(); |
| if (err) |
| goto probe_fail_cmdirq_register; |
| gd.gdrom_rq = blk_init_queue(gdrom_request, &gdrom_lock); |
| if (!gd.gdrom_rq) |
| goto probe_fail_requestq; |
| |
| err = probe_gdrom_setupqueue(); |
| if (err) |
| goto probe_fail_toc; |
| |
| gd.toc = kzalloc(sizeof(struct gdromtoc), GFP_KERNEL); |
| if (!gd.toc) |
| goto probe_fail_toc; |
| add_disk(gd.disk); |
| return 0; |
| |
| probe_fail_toc: |
| blk_cleanup_queue(gd.gdrom_rq); |
| probe_fail_requestq: |
| free_irq(HW_EVENT_GDROM_DMA, &gd); |
| free_irq(HW_EVENT_GDROM_CMD, &gd); |
| probe_fail_cmdirq_register: |
| probe_fail_cdrom_register: |
| del_gendisk(gd.disk); |
| probe_fail_no_disk: |
| kfree(gd.cd_info); |
| unregister_blkdev(gdrom_major, GDROM_DEV_NAME); |
| gdrom_major = 0; |
| probe_fail_no_mem: |
| printk(KERN_WARNING "GDROM: Probe failed - error is 0x%X\n", err); |
| return err; |
| } |
| |
| static int __devexit remove_gdrom(struct platform_device *devptr) |
| { |
| flush_scheduled_work(); |
| blk_cleanup_queue(gd.gdrom_rq); |
| free_irq(HW_EVENT_GDROM_CMD, &gd); |
| free_irq(HW_EVENT_GDROM_DMA, &gd); |
| del_gendisk(gd.disk); |
| if (gdrom_major) |
| unregister_blkdev(gdrom_major, GDROM_DEV_NAME); |
| unregister_cdrom(gd.cd_info); |
| |
| return 0; |
| } |
| |
| static struct platform_driver gdrom_driver = { |
| .probe = probe_gdrom, |
| .remove = __devexit_p(remove_gdrom), |
| .driver = { |
| .name = GDROM_DEV_NAME, |
| }, |
| }; |
| |
| static int __init init_gdrom(void) |
| { |
| int rc; |
| gd.toc = NULL; |
| rc = platform_driver_register(&gdrom_driver); |
| if (rc) |
| return rc; |
| pd = platform_device_register_simple(GDROM_DEV_NAME, -1, NULL, 0); |
| if (IS_ERR(pd)) { |
| platform_driver_unregister(&gdrom_driver); |
| return PTR_ERR(pd); |
| } |
| return 0; |
| } |
| |
| static void __exit exit_gdrom(void) |
| { |
| platform_device_unregister(pd); |
| platform_driver_unregister(&gdrom_driver); |
| kfree(gd.toc); |
| } |
| |
| module_init(init_gdrom); |
| module_exit(exit_gdrom); |
| MODULE_AUTHOR("Adrian McMenamin <adrian@mcmen.demon.co.uk>"); |
| MODULE_DESCRIPTION("SEGA Dreamcast GD-ROM Driver"); |
| MODULE_LICENSE("GPL"); |