|
@@ -0,0 +1,832 @@
|
|
|
|
+/* vmu-flash.c
|
|
|
|
+ * Driver for SEGA Dreamcast Visual Memory Unit
|
|
|
|
+ *
|
|
|
|
+ * Copyright (c) Adrian McMenamin 2002 - 2009
|
|
|
|
+ * Copyright (c) Paul Mundt 2001
|
|
|
|
+ *
|
|
|
|
+ * Licensed under version 2 of the
|
|
|
|
+ * GNU General Public Licence
|
|
|
|
+ */
|
|
|
|
+#include <linux/init.h>
|
|
|
|
+#include <linux/sched.h>
|
|
|
|
+#include <linux/delay.h>
|
|
|
|
+#include <linux/maple.h>
|
|
|
|
+#include <linux/mtd/mtd.h>
|
|
|
|
+#include <linux/mtd/map.h>
|
|
|
|
+
|
|
|
|
+struct vmu_cache {
|
|
|
|
+ unsigned char *buffer; /* Cache */
|
|
|
|
+ unsigned int block; /* Which block was cached */
|
|
|
|
+ unsigned long jiffies_atc; /* When was it cached? */
|
|
|
|
+ int valid;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct mdev_part {
|
|
|
|
+ struct maple_device *mdev;
|
|
|
|
+ int partition;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct vmupart {
|
|
|
|
+ u16 user_blocks;
|
|
|
|
+ u16 root_block;
|
|
|
|
+ u16 numblocks;
|
|
|
|
+ char *name;
|
|
|
|
+ struct vmu_cache *pcache;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct memcard {
|
|
|
|
+ u16 tempA;
|
|
|
|
+ u16 tempB;
|
|
|
|
+ u32 partitions;
|
|
|
|
+ u32 blocklen;
|
|
|
|
+ u32 writecnt;
|
|
|
|
+ u32 readcnt;
|
|
|
|
+ u32 removeable;
|
|
|
|
+ int partition;
|
|
|
|
+ int read;
|
|
|
|
+ unsigned char *blockread;
|
|
|
|
+ struct vmupart *parts;
|
|
|
|
+ struct mtd_info *mtd;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct vmu_block {
|
|
|
|
+ unsigned int num; /* block number */
|
|
|
|
+ unsigned int ofs; /* block offset */
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static struct vmu_block *ofs_to_block(unsigned long src_ofs,
|
|
|
|
+ struct mtd_info *mtd, int partition)
|
|
|
|
+{
|
|
|
|
+ struct vmu_block *vblock;
|
|
|
|
+ struct maple_device *mdev;
|
|
|
|
+ struct memcard *card;
|
|
|
|
+ struct mdev_part *mpart;
|
|
|
|
+ int num;
|
|
|
|
+
|
|
|
|
+ mpart = mtd->priv;
|
|
|
|
+ mdev = mpart->mdev;
|
|
|
|
+ card = maple_get_drvdata(mdev);
|
|
|
|
+
|
|
|
|
+ if (src_ofs >= card->parts[partition].numblocks * card->blocklen)
|
|
|
|
+ goto failed;
|
|
|
|
+
|
|
|
|
+ num = src_ofs / card->blocklen;
|
|
|
|
+ if (num > card->parts[partition].numblocks)
|
|
|
|
+ goto failed;
|
|
|
|
+
|
|
|
|
+ vblock = kmalloc(sizeof(struct vmu_block), GFP_KERNEL);
|
|
|
|
+ if (!vblock)
|
|
|
|
+ goto failed;
|
|
|
|
+
|
|
|
|
+ vblock->num = num;
|
|
|
|
+ vblock->ofs = src_ofs % card->blocklen;
|
|
|
|
+ return vblock;
|
|
|
|
+
|
|
|
|
+failed:
|
|
|
|
+ return NULL;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* Maple bus callback function for reads */
|
|
|
|
+static void vmu_blockread(struct mapleq *mq)
|
|
|
|
+{
|
|
|
|
+ struct maple_device *mdev;
|
|
|
|
+ struct memcard *card;
|
|
|
|
+
|
|
|
|
+ mdev = mq->dev;
|
|
|
|
+ card = maple_get_drvdata(mdev);
|
|
|
|
+ /* copy the read in data */
|
|
|
|
+
|
|
|
|
+ if (unlikely(!card->blockread))
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ memcpy(card->blockread, mq->recvbuf->buf + 12,
|
|
|
|
+ card->blocklen/card->readcnt);
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* Interface with maple bus to read blocks
|
|
|
|
+ * caching the results so that other parts
|
|
|
|
+ * of the driver can access block reads */
|
|
|
|
+static int maple_vmu_read_block(unsigned int num, unsigned char *buf,
|
|
|
|
+ struct mtd_info *mtd)
|
|
|
|
+{
|
|
|
|
+ struct memcard *card;
|
|
|
|
+ struct mdev_part *mpart;
|
|
|
|
+ struct maple_device *mdev;
|
|
|
|
+ int partition, error = 0, x, wait;
|
|
|
|
+ unsigned char *blockread = NULL;
|
|
|
|
+ struct vmu_cache *pcache;
|
|
|
|
+ __be32 sendbuf;
|
|
|
|
+
|
|
|
|
+ mpart = mtd->priv;
|
|
|
|
+ mdev = mpart->mdev;
|
|
|
|
+ partition = mpart->partition;
|
|
|
|
+ card = maple_get_drvdata(mdev);
|
|
|
|
+ pcache = card->parts[partition].pcache;
|
|
|
|
+ pcache->valid = 0;
|
|
|
|
+
|
|
|
|
+ /* prepare the cache for this block */
|
|
|
|
+ if (!pcache->buffer) {
|
|
|
|
+ pcache->buffer = kmalloc(card->blocklen, GFP_KERNEL);
|
|
|
|
+ if (!pcache->buffer) {
|
|
|
|
+ dev_err(&mdev->dev, "VMU at (%d, %d) - read fails due"
|
|
|
|
+ " to lack of memory\n", mdev->port,
|
|
|
|
+ mdev->unit);
|
|
|
|
+ error = -ENOMEM;
|
|
|
|
+ goto outB;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Reads may be phased - again the hardware spec
|
|
|
|
+ * supports this - though may not be any devices in
|
|
|
|
+ * the wild that implement it, but we will here
|
|
|
|
+ */
|
|
|
|
+ for (x = 0; x < card->readcnt; x++) {
|
|
|
|
+ sendbuf = cpu_to_be32(partition << 24 | x << 16 | num);
|
|
|
|
+
|
|
|
|
+ if (atomic_read(&mdev->busy) == 1) {
|
|
|
|
+ wait_event_interruptible_timeout(mdev->maple_wait,
|
|
|
|
+ atomic_read(&mdev->busy) == 0, HZ);
|
|
|
|
+ if (atomic_read(&mdev->busy) == 1) {
|
|
|
|
+ dev_notice(&mdev->dev, "VMU at (%d, %d)"
|
|
|
|
+ " is busy\n", mdev->port, mdev->unit);
|
|
|
|
+ error = -EAGAIN;
|
|
|
|
+ goto outB;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ atomic_set(&mdev->busy, 1);
|
|
|
|
+ blockread = kmalloc(card->blocklen/card->readcnt, GFP_KERNEL);
|
|
|
|
+ if (!blockread) {
|
|
|
|
+ error = -ENOMEM;
|
|
|
|
+ atomic_set(&mdev->busy, 0);
|
|
|
|
+ goto outB;
|
|
|
|
+ }
|
|
|
|
+ card->blockread = blockread;
|
|
|
|
+
|
|
|
|
+ maple_getcond_callback(mdev, vmu_blockread, 0,
|
|
|
|
+ MAPLE_FUNC_MEMCARD);
|
|
|
|
+ error = maple_add_packet(mdev, MAPLE_FUNC_MEMCARD,
|
|
|
|
+ MAPLE_COMMAND_BREAD, 2, &sendbuf);
|
|
|
|
+ /* Very long timeouts seem to be needed when box is stressed */
|
|
|
|
+ wait = wait_event_interruptible_timeout(mdev->maple_wait,
|
|
|
|
+ (atomic_read(&mdev->busy) == 0 ||
|
|
|
|
+ atomic_read(&mdev->busy) == 2), HZ * 3);
|
|
|
|
+ /*
|
|
|
|
+ * MTD layer does not handle hotplugging well
|
|
|
|
+ * so have to return errors when VMU is unplugged
|
|
|
|
+ * in the middle of a read (busy == 2)
|
|
|
|
+ */
|
|
|
|
+ if (error || atomic_read(&mdev->busy) == 2) {
|
|
|
|
+ if (atomic_read(&mdev->busy) == 2)
|
|
|
|
+ error = -ENXIO;
|
|
|
|
+ atomic_set(&mdev->busy, 0);
|
|
|
|
+ card->blockread = NULL;
|
|
|
|
+ goto outA;
|
|
|
|
+ }
|
|
|
|
+ if (wait == 0 || wait == -ERESTARTSYS) {
|
|
|
|
+ card->blockread = NULL;
|
|
|
|
+ atomic_set(&mdev->busy, 0);
|
|
|
|
+ error = -EIO;
|
|
|
|
+ list_del_init(&(mdev->mq->list));
|
|
|
|
+ kfree(mdev->mq->sendbuf);
|
|
|
|
+ mdev->mq->sendbuf = NULL;
|
|
|
|
+ if (wait == -ERESTARTSYS) {
|
|
|
|
+ dev_warn(&mdev->dev, "VMU read on (%d, %d)"
|
|
|
|
+ " interrupted on block 0x%X\n",
|
|
|
|
+ mdev->port, mdev->unit, num);
|
|
|
|
+ } else
|
|
|
|
+ dev_notice(&mdev->dev, "VMU read on (%d, %d)"
|
|
|
|
+ " timed out on block 0x%X\n",
|
|
|
|
+ mdev->port, mdev->unit, num);
|
|
|
|
+ goto outA;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ memcpy(buf + (card->blocklen/card->readcnt) * x, blockread,
|
|
|
|
+ card->blocklen/card->readcnt);
|
|
|
|
+
|
|
|
|
+ memcpy(pcache->buffer + (card->blocklen/card->readcnt) * x,
|
|
|
|
+ card->blockread, card->blocklen/card->readcnt);
|
|
|
|
+ card->blockread = NULL;
|
|
|
|
+ pcache->block = num;
|
|
|
|
+ pcache->jiffies_atc = jiffies;
|
|
|
|
+ pcache->valid = 1;
|
|
|
|
+ kfree(blockread);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return error;
|
|
|
|
+
|
|
|
|
+outA:
|
|
|
|
+ kfree(blockread);
|
|
|
|
+outB:
|
|
|
|
+ return error;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* communicate with maple bus for phased writing */
|
|
|
|
+static int maple_vmu_write_block(unsigned int num, const unsigned char *buf,
|
|
|
|
+ struct mtd_info *mtd)
|
|
|
|
+{
|
|
|
|
+ struct memcard *card;
|
|
|
|
+ struct mdev_part *mpart;
|
|
|
|
+ struct maple_device *mdev;
|
|
|
|
+ int partition, error, locking, x, phaselen, wait;
|
|
|
|
+ __be32 *sendbuf;
|
|
|
|
+
|
|
|
|
+ mpart = mtd->priv;
|
|
|
|
+ mdev = mpart->mdev;
|
|
|
|
+ partition = mpart->partition;
|
|
|
|
+ card = maple_get_drvdata(mdev);
|
|
|
|
+
|
|
|
|
+ phaselen = card->blocklen/card->writecnt;
|
|
|
|
+
|
|
|
|
+ sendbuf = kmalloc(phaselen + 4, GFP_KERNEL);
|
|
|
|
+ if (!sendbuf) {
|
|
|
|
+ error = -ENOMEM;
|
|
|
|
+ goto fail_nosendbuf;
|
|
|
|
+ }
|
|
|
|
+ for (x = 0; x < card->writecnt; x++) {
|
|
|
|
+ sendbuf[0] = cpu_to_be32(partition << 24 | x << 16 | num);
|
|
|
|
+ memcpy(&sendbuf[1], buf + phaselen * x, phaselen);
|
|
|
|
+ /* wait until the device is not busy doing something else
|
|
|
|
+ * or 1 second - which ever is longer */
|
|
|
|
+ if (atomic_read(&mdev->busy) == 1) {
|
|
|
|
+ wait_event_interruptible_timeout(mdev->maple_wait,
|
|
|
|
+ atomic_read(&mdev->busy) == 0, HZ);
|
|
|
|
+ if (atomic_read(&mdev->busy) == 1) {
|
|
|
|
+ error = -EBUSY;
|
|
|
|
+ dev_notice(&mdev->dev, "VMU write at (%d, %d)"
|
|
|
|
+ "failed - device is busy\n",
|
|
|
|
+ mdev->port, mdev->unit);
|
|
|
|
+ goto fail_nolock;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ atomic_set(&mdev->busy, 1);
|
|
|
|
+
|
|
|
|
+ locking = maple_add_packet(mdev, MAPLE_FUNC_MEMCARD,
|
|
|
|
+ MAPLE_COMMAND_BWRITE, phaselen / 4 + 2, sendbuf);
|
|
|
|
+ wait = wait_event_interruptible_timeout(mdev->maple_wait,
|
|
|
|
+ atomic_read(&mdev->busy) == 0, HZ/10);
|
|
|
|
+ if (locking) {
|
|
|
|
+ error = -EIO;
|
|
|
|
+ atomic_set(&mdev->busy, 0);
|
|
|
|
+ goto fail_nolock;
|
|
|
|
+ }
|
|
|
|
+ if (atomic_read(&mdev->busy) == 2) {
|
|
|
|
+ atomic_set(&mdev->busy, 0);
|
|
|
|
+ } else if (wait == 0 || wait == -ERESTARTSYS) {
|
|
|
|
+ error = -EIO;
|
|
|
|
+ dev_warn(&mdev->dev, "Write at (%d, %d) of block"
|
|
|
|
+ " 0x%X at phase %d failed: could not"
|
|
|
|
+ " communicate with VMU", mdev->port,
|
|
|
|
+ mdev->unit, num, x);
|
|
|
|
+ atomic_set(&mdev->busy, 0);
|
|
|
|
+ kfree(mdev->mq->sendbuf);
|
|
|
|
+ mdev->mq->sendbuf = NULL;
|
|
|
|
+ list_del_init(&(mdev->mq->list));
|
|
|
|
+ goto fail_nolock;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ kfree(sendbuf);
|
|
|
|
+
|
|
|
|
+ return card->blocklen;
|
|
|
|
+
|
|
|
|
+fail_nolock:
|
|
|
|
+ kfree(sendbuf);
|
|
|
|
+fail_nosendbuf:
|
|
|
|
+ dev_err(&mdev->dev, "VMU (%d, %d): write failed\n", mdev->port,
|
|
|
|
+ mdev->unit);
|
|
|
|
+ return error;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* mtd function to simulate reading byte by byte */
|
|
|
|
+static unsigned char vmu_flash_read_char(unsigned long ofs, int *retval,
|
|
|
|
+ struct mtd_info *mtd)
|
|
|
|
+{
|
|
|
|
+ struct vmu_block *vblock;
|
|
|
|
+ struct memcard *card;
|
|
|
|
+ struct mdev_part *mpart;
|
|
|
|
+ struct maple_device *mdev;
|
|
|
|
+ unsigned char *buf, ret;
|
|
|
|
+ int partition, error;
|
|
|
|
+
|
|
|
|
+ mpart = mtd->priv;
|
|
|
|
+ mdev = mpart->mdev;
|
|
|
|
+ partition = mpart->partition;
|
|
|
|
+ card = maple_get_drvdata(mdev);
|
|
|
|
+ *retval = 0;
|
|
|
|
+
|
|
|
|
+ buf = kmalloc(card->blocklen, GFP_KERNEL);
|
|
|
|
+ if (!buf) {
|
|
|
|
+ *retval = 1;
|
|
|
|
+ ret = -ENOMEM;
|
|
|
|
+ goto finish;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ vblock = ofs_to_block(ofs, mtd, partition);
|
|
|
|
+ if (!vblock) {
|
|
|
|
+ *retval = 3;
|
|
|
|
+ ret = -ENOMEM;
|
|
|
|
+ goto out_buf;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ error = maple_vmu_read_block(vblock->num, buf, mtd);
|
|
|
|
+ if (error) {
|
|
|
|
+ ret = error;
|
|
|
|
+ *retval = 2;
|
|
|
|
+ goto out_vblock;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ret = buf[vblock->ofs];
|
|
|
|
+
|
|
|
|
+out_vblock:
|
|
|
|
+ kfree(vblock);
|
|
|
|
+out_buf:
|
|
|
|
+ kfree(buf);
|
|
|
|
+finish:
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* mtd higher order function to read flash */
|
|
|
|
+static int vmu_flash_read(struct mtd_info *mtd, loff_t from, size_t len,
|
|
|
|
+ size_t *retlen, u_char *buf)
|
|
|
|
+{
|
|
|
|
+ struct maple_device *mdev;
|
|
|
|
+ struct memcard *card;
|
|
|
|
+ struct mdev_part *mpart;
|
|
|
|
+ struct vmu_cache *pcache;
|
|
|
|
+ struct vmu_block *vblock;
|
|
|
|
+ int index = 0, retval, partition, leftover, numblocks;
|
|
|
|
+ unsigned char cx;
|
|
|
|
+
|
|
|
|
+ if (len < 1)
|
|
|
|
+ return -EIO;
|
|
|
|
+
|
|
|
|
+ mpart = mtd->priv;
|
|
|
|
+ mdev = mpart->mdev;
|
|
|
|
+ partition = mpart->partition;
|
|
|
|
+ card = maple_get_drvdata(mdev);
|
|
|
|
+
|
|
|
|
+ numblocks = card->parts[partition].numblocks;
|
|
|
|
+ if (from + len > numblocks * card->blocklen)
|
|
|
|
+ len = numblocks * card->blocklen - from;
|
|
|
|
+ if (len == 0)
|
|
|
|
+ return -EIO;
|
|
|
|
+ /* Have we cached this bit already? */
|
|
|
|
+ pcache = card->parts[partition].pcache;
|
|
|
|
+ do {
|
|
|
|
+ vblock = ofs_to_block(from + index, mtd, partition);
|
|
|
|
+ if (!vblock)
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+ /* Have we cached this and is the cache valid and timely? */
|
|
|
|
+ if (pcache->valid &&
|
|
|
|
+ time_before(jiffies, pcache->jiffies_atc + HZ) &&
|
|
|
|
+ (pcache->block == vblock->num)) {
|
|
|
|
+ /* we have cached it, so do necessary copying */
|
|
|
|
+ leftover = card->blocklen - vblock->ofs;
|
|
|
|
+ if (vblock->ofs + len - index < card->blocklen) {
|
|
|
|
+ /* only a bit of this block to copy */
|
|
|
|
+ memcpy(buf + index,
|
|
|
|
+ pcache->buffer + vblock->ofs,
|
|
|
|
+ len - index);
|
|
|
|
+ index = len;
|
|
|
|
+ } else {
|
|
|
|
+ /* otherwise copy remainder of whole block */
|
|
|
|
+ memcpy(buf + index, pcache->buffer +
|
|
|
|
+ vblock->ofs, leftover);
|
|
|
|
+ index += leftover;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ /*
|
|
|
|
+ * Not cached so read one byte -
|
|
|
|
+ * but cache the rest of the block
|
|
|
|
+ */
|
|
|
|
+ cx = vmu_flash_read_char(from + index, &retval, mtd);
|
|
|
|
+ if (retval) {
|
|
|
|
+ *retlen = index;
|
|
|
|
+ kfree(vblock);
|
|
|
|
+ return cx;
|
|
|
|
+ }
|
|
|
|
+ memset(buf + index, cx, 1);
|
|
|
|
+ index++;
|
|
|
|
+ }
|
|
|
|
+ kfree(vblock);
|
|
|
|
+ } while (len > index);
|
|
|
|
+ *retlen = index;
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int vmu_flash_write(struct mtd_info *mtd, loff_t to, size_t len,
|
|
|
|
+ size_t *retlen, const u_char *buf)
|
|
|
|
+{
|
|
|
|
+ struct maple_device *mdev;
|
|
|
|
+ struct memcard *card;
|
|
|
|
+ struct mdev_part *mpart;
|
|
|
|
+ int index = 0, partition, error = 0, numblocks;
|
|
|
|
+ struct vmu_cache *pcache;
|
|
|
|
+ struct vmu_block *vblock;
|
|
|
|
+ unsigned char *buffer;
|
|
|
|
+
|
|
|
|
+ mpart = mtd->priv;
|
|
|
|
+ mdev = mpart->mdev;
|
|
|
|
+ partition = mpart->partition;
|
|
|
|
+ card = maple_get_drvdata(mdev);
|
|
|
|
+
|
|
|
|
+ /* simple sanity checks */
|
|
|
|
+ if (len < 1) {
|
|
|
|
+ error = -EIO;
|
|
|
|
+ goto failed;
|
|
|
|
+ }
|
|
|
|
+ numblocks = card->parts[partition].numblocks;
|
|
|
|
+ if (to + len > numblocks * card->blocklen)
|
|
|
|
+ len = numblocks * card->blocklen - to;
|
|
|
|
+ if (len == 0) {
|
|
|
|
+ error = -EIO;
|
|
|
|
+ goto failed;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ vblock = ofs_to_block(to, mtd, partition);
|
|
|
|
+ if (!vblock) {
|
|
|
|
+ error = -ENOMEM;
|
|
|
|
+ goto failed;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ buffer = kmalloc(card->blocklen, GFP_KERNEL);
|
|
|
|
+ if (!buffer) {
|
|
|
|
+ error = -ENOMEM;
|
|
|
|
+ goto fail_buffer;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ do {
|
|
|
|
+ /* Read in the block we are to write to */
|
|
|
|
+ error = maple_vmu_read_block(vblock->num, buffer, mtd);
|
|
|
|
+ if (error)
|
|
|
|
+ goto fail_io;
|
|
|
|
+
|
|
|
|
+ do {
|
|
|
|
+ buffer[vblock->ofs] = buf[index];
|
|
|
|
+ vblock->ofs++;
|
|
|
|
+ index++;
|
|
|
|
+ if (index >= len)
|
|
|
|
+ break;
|
|
|
|
+ } while (vblock->ofs < card->blocklen);
|
|
|
|
+
|
|
|
|
+ /* write out new buffer */
|
|
|
|
+ error = maple_vmu_write_block(vblock->num, buffer, mtd);
|
|
|
|
+ /* invalidate the cache */
|
|
|
|
+ pcache = card->parts[partition].pcache;
|
|
|
|
+ pcache->valid = 0;
|
|
|
|
+
|
|
|
|
+ if (error != card->blocklen)
|
|
|
|
+ goto fail_io;
|
|
|
|
+
|
|
|
|
+ vblock->num++;
|
|
|
|
+ vblock->ofs = 0;
|
|
|
|
+ } while (len > index);
|
|
|
|
+
|
|
|
|
+ kfree(buffer);
|
|
|
|
+ *retlen = index;
|
|
|
|
+ kfree(vblock);
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+fail_io:
|
|
|
|
+ kfree(buffer);
|
|
|
|
+fail_buffer:
|
|
|
|
+ kfree(vblock);
|
|
|
|
+failed:
|
|
|
|
+ dev_err(&mdev->dev, "VMU write failing with error %d\n", error);
|
|
|
|
+ return error;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void vmu_flash_sync(struct mtd_info *mtd)
|
|
|
|
+{
|
|
|
|
+ /* Do nothing here */
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* Maple bus callback function to recursively query hardware details */
|
|
|
|
+static void vmu_queryblocks(struct mapleq *mq)
|
|
|
|
+{
|
|
|
|
+ struct maple_device *mdev;
|
|
|
|
+ unsigned short *res;
|
|
|
|
+ struct memcard *card;
|
|
|
|
+ __be32 partnum;
|
|
|
|
+ struct vmu_cache *pcache;
|
|
|
|
+ struct mdev_part *mpart;
|
|
|
|
+ struct mtd_info *mtd_cur;
|
|
|
|
+ struct vmupart *part_cur;
|
|
|
|
+ int error;
|
|
|
|
+
|
|
|
|
+ mdev = mq->dev;
|
|
|
|
+ card = maple_get_drvdata(mdev);
|
|
|
|
+ res = (unsigned short *) (mq->recvbuf->buf);
|
|
|
|
+ card->tempA = res[12];
|
|
|
|
+ card->tempB = res[6];
|
|
|
|
+
|
|
|
|
+ dev_info(&mdev->dev, "VMU device at partition %d has %d user "
|
|
|
|
+ "blocks with a root block at %d\n", card->partition,
|
|
|
|
+ card->tempA, card->tempB);
|
|
|
|
+
|
|
|
|
+ part_cur = &card->parts[card->partition];
|
|
|
|
+ part_cur->user_blocks = card->tempA;
|
|
|
|
+ part_cur->root_block = card->tempB;
|
|
|
|
+ part_cur->numblocks = card->tempB + 1;
|
|
|
|
+ part_cur->name = kmalloc(12, GFP_KERNEL);
|
|
|
|
+ if (!part_cur->name)
|
|
|
|
+ goto fail_name;
|
|
|
|
+
|
|
|
|
+ sprintf(part_cur->name, "vmu%d.%d.%d",
|
|
|
|
+ mdev->port, mdev->unit, card->partition);
|
|
|
|
+ mtd_cur = &card->mtd[card->partition];
|
|
|
|
+ mtd_cur->name = part_cur->name;
|
|
|
|
+ mtd_cur->type = 8;
|
|
|
|
+ mtd_cur->flags = MTD_WRITEABLE|MTD_NO_ERASE;
|
|
|
|
+ mtd_cur->size = part_cur->numblocks * card->blocklen;
|
|
|
|
+ mtd_cur->erasesize = card->blocklen;
|
|
|
|
+ mtd_cur->write = vmu_flash_write;
|
|
|
|
+ mtd_cur->read = vmu_flash_read;
|
|
|
|
+ mtd_cur->sync = vmu_flash_sync;
|
|
|
|
+ mtd_cur->writesize = card->blocklen;
|
|
|
|
+
|
|
|
|
+ mpart = kmalloc(sizeof(struct mdev_part), GFP_KERNEL);
|
|
|
|
+ if (!mpart)
|
|
|
|
+ goto fail_mpart;
|
|
|
|
+
|
|
|
|
+ mpart->mdev = mdev;
|
|
|
|
+ mpart->partition = card->partition;
|
|
|
|
+ mtd_cur->priv = mpart;
|
|
|
|
+ mtd_cur->owner = THIS_MODULE;
|
|
|
|
+
|
|
|
|
+ pcache = kzalloc(sizeof(struct vmu_cache), GFP_KERNEL);
|
|
|
|
+ if (!pcache)
|
|
|
|
+ goto fail_cache_create;
|
|
|
|
+ part_cur->pcache = pcache;
|
|
|
|
+
|
|
|
|
+ error = add_mtd_device(mtd_cur);
|
|
|
|
+ if (error)
|
|
|
|
+ goto fail_mtd_register;
|
|
|
|
+
|
|
|
|
+ maple_getcond_callback(mdev, NULL, 0,
|
|
|
|
+ MAPLE_FUNC_MEMCARD);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Set up a recursive call to the (probably theoretical)
|
|
|
|
+ * second or more partition
|
|
|
|
+ */
|
|
|
|
+ if (++card->partition < card->partitions) {
|
|
|
|
+ partnum = cpu_to_be32(card->partition << 24);
|
|
|
|
+ maple_getcond_callback(mdev, vmu_queryblocks, 0,
|
|
|
|
+ MAPLE_FUNC_MEMCARD);
|
|
|
|
+ maple_add_packet(mdev, MAPLE_FUNC_MEMCARD,
|
|
|
|
+ MAPLE_COMMAND_GETMINFO, 2, &partnum);
|
|
|
|
+ }
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+fail_mtd_register:
|
|
|
|
+ dev_err(&mdev->dev, "Could not register maple device at (%d, %d)"
|
|
|
|
+ "error is 0x%X\n", mdev->port, mdev->unit, error);
|
|
|
|
+ for (error = 0; error <= card->partition; error++) {
|
|
|
|
+ kfree(((card->parts)[error]).pcache);
|
|
|
|
+ ((card->parts)[error]).pcache = NULL;
|
|
|
|
+ }
|
|
|
|
+fail_cache_create:
|
|
|
|
+fail_mpart:
|
|
|
|
+ for (error = 0; error <= card->partition; error++) {
|
|
|
|
+ kfree(((card->mtd)[error]).priv);
|
|
|
|
+ ((card->mtd)[error]).priv = NULL;
|
|
|
|
+ }
|
|
|
|
+ maple_getcond_callback(mdev, NULL, 0,
|
|
|
|
+ MAPLE_FUNC_MEMCARD);
|
|
|
|
+ kfree(part_cur->name);
|
|
|
|
+fail_name:
|
|
|
|
+ return;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* Handles very basic info about the flash, queries for details */
|
|
|
|
+static int __devinit vmu_connect(struct maple_device *mdev)
|
|
|
|
+{
|
|
|
|
+ unsigned long test_flash_data, basic_flash_data;
|
|
|
|
+ int c, error;
|
|
|
|
+ struct memcard *card;
|
|
|
|
+ u32 partnum = 0;
|
|
|
|
+
|
|
|
|
+ test_flash_data = be32_to_cpu(mdev->devinfo.function);
|
|
|
|
+ /* Need to count how many bits are set - to find out which
|
|
|
|
+ * function_data element has details of the memory card:
|
|
|
|
+ * using Brian Kernighan's/Peter Wegner's method */
|
|
|
|
+ for (c = 0; test_flash_data; c++)
|
|
|
|
+ test_flash_data &= test_flash_data - 1;
|
|
|
|
+
|
|
|
|
+ basic_flash_data = be32_to_cpu(mdev->devinfo.function_data[c - 1]);
|
|
|
|
+
|
|
|
|
+ card = kmalloc(sizeof(struct memcard), GFP_KERNEL);
|
|
|
|
+ if (!card) {
|
|
|
|
+ error = ENOMEM;
|
|
|
|
+ goto fail_nomem;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ card->partitions = (basic_flash_data >> 24 & 0xFF) + 1;
|
|
|
|
+ card->blocklen = ((basic_flash_data >> 16 & 0xFF) + 1) << 5;
|
|
|
|
+ card->writecnt = basic_flash_data >> 12 & 0xF;
|
|
|
|
+ card->readcnt = basic_flash_data >> 8 & 0xF;
|
|
|
|
+ card->removeable = basic_flash_data >> 7 & 1;
|
|
|
|
+
|
|
|
|
+ card->partition = 0;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Not sure there are actually any multi-partition devices in the
|
|
|
|
+ * real world, but the hardware supports them, so, so will we
|
|
|
|
+ */
|
|
|
|
+ card->parts = kmalloc(sizeof(struct vmupart) * card->partitions,
|
|
|
|
+ GFP_KERNEL);
|
|
|
|
+ if (!card->parts) {
|
|
|
|
+ error = -ENOMEM;
|
|
|
|
+ goto fail_partitions;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ card->mtd = kmalloc(sizeof(struct mtd_info) * card->partitions,
|
|
|
|
+ GFP_KERNEL);
|
|
|
|
+ if (!card->mtd) {
|
|
|
|
+ error = -ENOMEM;
|
|
|
|
+ goto fail_mtd_info;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ maple_set_drvdata(mdev, card);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * We want to trap meminfo not get cond
|
|
|
|
+ * so set interval to zero, but rely on maple bus
|
|
|
|
+ * driver to pass back the results of the meminfo
|
|
|
|
+ */
|
|
|
|
+ maple_getcond_callback(mdev, vmu_queryblocks, 0,
|
|
|
|
+ MAPLE_FUNC_MEMCARD);
|
|
|
|
+
|
|
|
|
+ /* Make sure we are clear to go */
|
|
|
|
+ if (atomic_read(&mdev->busy) == 1) {
|
|
|
|
+ wait_event_interruptible_timeout(mdev->maple_wait,
|
|
|
|
+ atomic_read(&mdev->busy) == 0, HZ);
|
|
|
|
+ if (atomic_read(&mdev->busy) == 1) {
|
|
|
|
+ dev_notice(&mdev->dev, "VMU at (%d, %d) is busy\n",
|
|
|
|
+ mdev->port, mdev->unit);
|
|
|
|
+ error = -EAGAIN;
|
|
|
|
+ goto fail_device_busy;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ atomic_set(&mdev->busy, 1);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Set up the minfo call: vmu_queryblocks will handle
|
|
|
|
+ * the information passed back
|
|
|
|
+ */
|
|
|
|
+ error = maple_add_packet(mdev, MAPLE_FUNC_MEMCARD,
|
|
|
|
+ MAPLE_COMMAND_GETMINFO, 2, &partnum);
|
|
|
|
+ if (error) {
|
|
|
|
+ dev_err(&mdev->dev, "Could not lock VMU at (%d, %d)"
|
|
|
|
+ " error is 0x%X\n", mdev->port, mdev->unit, error);
|
|
|
|
+ goto fail_mtd_info;
|
|
|
|
+ }
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+fail_device_busy:
|
|
|
|
+ kfree(card->mtd);
|
|
|
|
+fail_mtd_info:
|
|
|
|
+ kfree(card->parts);
|
|
|
|
+fail_partitions:
|
|
|
|
+ kfree(card);
|
|
|
|
+fail_nomem:
|
|
|
|
+ return error;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void __devexit vmu_disconnect(struct maple_device *mdev)
|
|
|
|
+{
|
|
|
|
+ struct memcard *card;
|
|
|
|
+ struct mdev_part *mpart;
|
|
|
|
+ int x;
|
|
|
|
+
|
|
|
|
+ mdev->callback = NULL;
|
|
|
|
+ card = maple_get_drvdata(mdev);
|
|
|
|
+ for (x = 0; x < card->partitions; x++) {
|
|
|
|
+ mpart = ((card->mtd)[x]).priv;
|
|
|
|
+ mpart->mdev = NULL;
|
|
|
|
+ del_mtd_device(&((card->mtd)[x]));
|
|
|
|
+ kfree(((card->parts)[x]).name);
|
|
|
|
+ }
|
|
|
|
+ kfree(card->parts);
|
|
|
|
+ kfree(card->mtd);
|
|
|
|
+ kfree(card);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* Callback to handle eccentricities of both mtd subsystem
|
|
|
|
+ * and general flakyness of Dreamcast VMUs
|
|
|
|
+ */
|
|
|
|
+static int vmu_can_unload(struct maple_device *mdev)
|
|
|
|
+{
|
|
|
|
+ struct memcard *card;
|
|
|
|
+ int x;
|
|
|
|
+ struct mtd_info *mtd;
|
|
|
|
+
|
|
|
|
+ card = maple_get_drvdata(mdev);
|
|
|
|
+ for (x = 0; x < card->partitions; x++) {
|
|
|
|
+ mtd = &((card->mtd)[x]);
|
|
|
|
+ if (mtd->usecount > 0)
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+ return 1;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#define ERRSTR "VMU at (%d, %d) file error -"
|
|
|
|
+
|
|
|
|
+static void vmu_file_error(struct maple_device *mdev, void *recvbuf)
|
|
|
|
+{
|
|
|
|
+ enum maple_file_errors error = ((int *)recvbuf)[1];
|
|
|
|
+
|
|
|
|
+ switch (error) {
|
|
|
|
+
|
|
|
|
+ case MAPLE_FILEERR_INVALID_PARTITION:
|
|
|
|
+ dev_notice(&mdev->dev, ERRSTR " invalid partition number\n",
|
|
|
|
+ mdev->port, mdev->unit);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case MAPLE_FILEERR_PHASE_ERROR:
|
|
|
|
+ dev_notice(&mdev->dev, ERRSTR " phase error\n",
|
|
|
|
+ mdev->port, mdev->unit);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case MAPLE_FILEERR_INVALID_BLOCK:
|
|
|
|
+ dev_notice(&mdev->dev, ERRSTR " invalid block number\n",
|
|
|
|
+ mdev->port, mdev->unit);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case MAPLE_FILEERR_WRITE_ERROR:
|
|
|
|
+ dev_notice(&mdev->dev, ERRSTR " write error\n",
|
|
|
|
+ mdev->port, mdev->unit);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case MAPLE_FILEERR_INVALID_WRITE_LENGTH:
|
|
|
|
+ dev_notice(&mdev->dev, ERRSTR " invalid write length\n",
|
|
|
|
+ mdev->port, mdev->unit);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case MAPLE_FILEERR_BAD_CRC:
|
|
|
|
+ dev_notice(&mdev->dev, ERRSTR " bad CRC\n",
|
|
|
|
+ mdev->port, mdev->unit);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ dev_notice(&mdev->dev, ERRSTR " 0x%X\n",
|
|
|
|
+ mdev->port, mdev->unit, error);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+static int __devinit probe_maple_vmu(struct device *dev)
|
|
|
|
+{
|
|
|
|
+ int error;
|
|
|
|
+ struct maple_device *mdev = to_maple_dev(dev);
|
|
|
|
+ struct maple_driver *mdrv = to_maple_driver(dev->driver);
|
|
|
|
+
|
|
|
|
+ mdev->can_unload = vmu_can_unload;
|
|
|
|
+ mdev->fileerr_handler = vmu_file_error;
|
|
|
|
+ mdev->driver = mdrv;
|
|
|
|
+
|
|
|
|
+ error = vmu_connect(mdev);
|
|
|
|
+ if (error)
|
|
|
|
+ return error;
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int __devexit remove_maple_vmu(struct device *dev)
|
|
|
|
+{
|
|
|
|
+ struct maple_device *mdev = to_maple_dev(dev);
|
|
|
|
+
|
|
|
|
+ vmu_disconnect(mdev);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static struct maple_driver vmu_flash_driver = {
|
|
|
|
+ .function = MAPLE_FUNC_MEMCARD,
|
|
|
|
+ .drv = {
|
|
|
|
+ .name = "Dreamcast_visual_memory",
|
|
|
|
+ .probe = probe_maple_vmu,
|
|
|
|
+ .remove = __devexit_p(remove_maple_vmu),
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int __init vmu_flash_map_init(void)
|
|
|
|
+{
|
|
|
|
+ return maple_driver_register(&vmu_flash_driver);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void __exit vmu_flash_map_exit(void)
|
|
|
|
+{
|
|
|
|
+ maple_driver_unregister(&vmu_flash_driver);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+module_init(vmu_flash_map_init);
|
|
|
|
+module_exit(vmu_flash_map_exit);
|
|
|
|
+
|
|
|
|
+MODULE_LICENSE("GPL");
|
|
|
|
+MODULE_AUTHOR("Adrian McMenamin");
|
|
|
|
+MODULE_DESCRIPTION("Flash mapping for Sega Dreamcast visual memory");
|