forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
817 lines
19 KiB
817 lines
19 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* vmu-flash.c |
|
* Driver for SEGA Dreamcast Visual Memory Unit |
|
* |
|
* Copyright (c) Adrian McMenamin 2002 - 2009 |
|
* Copyright (c) Paul Mundt 2001 |
|
*/ |
|
#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 removable; |
|
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->removable = 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_array(card->partitions, sizeof(struct vmupart), |
|
GFP_KERNEL); |
|
if (!card->parts) { |
|
error = -ENOMEM; |
|
goto fail_partitions; |
|
} |
|
|
|
card->mtd = kmalloc_array(card->partitions, sizeof(struct mtd_info), |
|
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) |
|
{ |
|
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; |
|
|
|
return vmu_connect(mdev); |
|
} |
|
|
|
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");
|
|
|