Add support for GD-Rom device [LWN.net] (original) (raw)

| From: | | "Adrian McMenamin" lkmladrian@gmail.com | | ----------------- | | ------------------------------------------------------------------------------------------------------------------------- | | To: | | linux-kernel@vger.kernel.org, linux-sh@vger.kernel.org, "Paul Mundt" lethal@linux-sh.org, axboe@kernel.dk | | Subject: | | [PATCH - SH/Dreamcast] Add support for GD-Rom device | | Date: | | Thu, 20 Dec 2007 23:59:52 +0000 | | Message-ID: | | 8b67d60712201559h5dbb2a17q8f16223b26b88006@mail.gmail.com | | Archive‑link: | | Article |

This patch adds support for the CD Rom device (called a "GD Rom") on the SEGA Dreamcast.This device has a command block similar to a standard ATA-3 device, though implements Sega's proprietary packet interface - the so-called "Sega Packet Interface".

There have been previous GD Rom drivers for Linux knocking about (though never anywhere near the mainline), though this is a new driver and most importantly uses DMA and not PIO for reads. It also directly supports the SEGA proprietary GD-Rom format.

This is the second posting and I think I've taken on board all or almost all the suggestions/change requests made last time. But grateful for all comments.

This is targetted at Paul Mundt's 2.6.25 queue, but should also compile against 2.6.24-rc5

I am using it play mp3s off CD even as I write this and it seems to work pretty well :)

Signed-off by: Adrian McMenamin adrian@mcmen.demon.co.uk

diff -ruN linux-2.6-orig/drivers/block/Kconfig linux-2.6/drivers/block/Kconfig --- linux-2.6-orig/drivers/block/Kconfig 2007-12-15 22:23:47.000000000 +0000 +++ linux-2.6/drivers/block/Kconfig 2007-12-20 23:36:15.000000000 +0000 @@ -105,6 +105,18 @@ "MicroSolutions backpack protocol", "DataStor Commuter protocol" etc.).

+config GDROM + tristate "SEGA Dreamcast GD-ROM drive" + depends on SH_DREAMCAST + help + A standard SEGA Dreamcast comes with a modified CD ROM drive called a + "GD-ROM" by SEGA to signify it is capable of reading special disks + with up to 1 GB of data. This drive will also read standard CD ROM + disks. Select this option to access any disks in your GD ROM drive.

@@ -425,10 +437,4 @@ block device driver. It communicates with a back-end driver in another domain which drives the actual block device.

-config VIRTIO_BLK - tristate "Virtio block driver (EXPERIMENTAL)" - depends on EXPERIMENTAL && VIRTIO - ---help--- - This is the virtual block driver for lguest. Say Y or M.

endif # BLK_DEV diff -ruN linux-2.6-orig/drivers/cdrom/gdrom.c linux-2.6/drivers/cdrom/gdrom.c --- linux-2.6-orig/drivers/cdrom/gdrom.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6/drivers/cdrom/gdrom.c 2007-12-20 23:36:15.000000000 +0000 @@ -0,0 +1,810 @@ +/* GD ROM driver for the SEGA Dreamcast

+#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 <asm/io.h> +#include <asm/dma.h> +#include <asm/delay.h> +#include <asm/mach/dma.h> +#include <asm/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 + +static struct platform_device *pd; +static int gdrom_major; +static wait_queue_head_t command_queue; +static wait_queue_head_t 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 void gdrom_wait_clrbusy(void) +{ + / long timeouts - typical for a CD Rom / + unsigned long timeout = jiffies + HZ * 60; + while ((ctrl_inb(GDROM_ALTSTATUS_REG) & 0x80) && (time_before(jiffies, timeout))) + cpu_relax(); +} + +static void gdrom_wait_busy_sleeps(void) +{ + unsigned long timeout; + / Wait to get busy first / + timeout = jiffies + HZ * 60; + while (((ctrl_inb(GDROM_ALTSTATUS_REG) & 0x80) == 0) && (time_before(jiffies, timeout))) + cpu_relax(); + / Now wait for busy to clear */ + gdrom_wait_clrbusy(); +} + +static void gdrom_identifydevice(void *buf) +{ + int c; + short data = buf; + gdrom_wait_clrbusy(); + ctrl_outb(GDROM_COM_IDDEV, GDROM_STATUSCOMMAND_REG); + gdrom_wait_busy_sleeps(); + / 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; + /* 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 / + gdrom_wait_clrbusy(); + ctrl_outb(GDROM_COM_PACKET, GDROM_STATUSCOMMAND_REG); + while ((ctrl_inb(GDROM_ALTSTATUS_REG) & 0x88) != 8) + ; / wait for DRQ to be set to 1 / + outsw(PHYSADDR(GDROM_DATA_REG), cmd, 6); +} + +/ gdrom_command_executediagnostic:

(time_before(jiffies, timeout))) + cpu_relax(); + ctrl_outb(GDROM_COM_PACKET, GDROM_STATUSCOMMAND_REG); + timeout = jiffies + HZ / 2; + while (((ctrl_inb(GDROM_ALTSTATUS_REG) & 0x88) != 8) && (time_before(jiffies, timeout))) + cpu_relax(); /* wait for DRQ to be set to 1 / + gd.pending = 1; + gd.transfer = 1; + outsw(PHYSADDR(GDROM_DATA_REG), &read_command->cmd, 6); + timeout = jiffies + HZ / 2; + while ((ctrl_inb(GDROM_DMA_STATUS_REG)) && (time_before(jiffies, timeout))) + cpu_relax(); + ctrl_outb(1, GDROM_DMA_STATUS_REG); + / 5 second error margin here seems more reasonable / + wait_event_interruptible_timeout(request_queue, gd.transfer == 0, HZ * 5); + err = ctrl_inb(GDROM_DMA_STATUS_REG); + err = gd.transfer; + gd.transfer = 0; + gd.pending = 0; + kfree(read_command); + / now seek to take the request spinlock