/* 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