| /* 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/slab.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; | |
| 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); | |
| 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 = mtd_device_register(mtd_cur, NULL, 0); | |
| 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 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 | |
| */ | |
| c = hweight_long(test_flash_data); | |
| 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 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; | |
| mtd_device_unregister(&((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 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 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 = 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"); |