// 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 (kref_read(&mtd->refcnt))
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");