818 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/* vmu-flash.c3 * Driver for SEGA Dreamcast Visual Memory Unit4 *5 * Copyright (c) Adrian McMenamin 2002 - 20096 * Copyright (c) Paul Mundt 20017 */8#include <linux/init.h>9#include <linux/slab.h>10#include <linux/sched.h>11#include <linux/delay.h>12#include <linux/maple.h>13#include <linux/mtd/mtd.h>14#include <linux/mtd/map.h>15 16struct vmu_cache {17 unsigned char *buffer; /* Cache */18 unsigned int block; /* Which block was cached */19 unsigned long jiffies_atc; /* When was it cached? */20 int valid;21};22 23struct mdev_part {24 struct maple_device *mdev;25 int partition;26};27 28struct vmupart {29 u16 user_blocks;30 u16 root_block;31 u16 numblocks;32 char *name;33 struct vmu_cache *pcache;34};35 36struct memcard {37 u16 tempA;38 u16 tempB;39 u32 partitions;40 u32 blocklen;41 u32 writecnt;42 u32 readcnt;43 u32 removable;44 int partition;45 int read;46 unsigned char *blockread;47 struct vmupart *parts;48 struct mtd_info *mtd;49};50 51struct vmu_block {52 unsigned int num; /* block number */53 unsigned int ofs; /* block offset */54};55 56static struct vmu_block *ofs_to_block(unsigned long src_ofs,57 struct mtd_info *mtd, int partition)58{59 struct vmu_block *vblock;60 struct maple_device *mdev;61 struct memcard *card;62 struct mdev_part *mpart;63 int num;64 65 mpart = mtd->priv;66 mdev = mpart->mdev;67 card = maple_get_drvdata(mdev);68 69 if (src_ofs >= card->parts[partition].numblocks * card->blocklen)70 goto failed;71 72 num = src_ofs / card->blocklen;73 if (num > card->parts[partition].numblocks)74 goto failed;75 76 vblock = kmalloc(sizeof(struct vmu_block), GFP_KERNEL);77 if (!vblock)78 goto failed;79 80 vblock->num = num;81 vblock->ofs = src_ofs % card->blocklen;82 return vblock;83 84failed:85 return NULL;86}87 88/* Maple bus callback function for reads */89static void vmu_blockread(struct mapleq *mq)90{91 struct maple_device *mdev;92 struct memcard *card;93 94 mdev = mq->dev;95 card = maple_get_drvdata(mdev);96 /* copy the read in data */97 98 if (unlikely(!card->blockread))99 return;100 101 memcpy(card->blockread, mq->recvbuf->buf + 12,102 card->blocklen/card->readcnt);103 104}105 106/* Interface with maple bus to read blocks107 * caching the results so that other parts108 * of the driver can access block reads */109static int maple_vmu_read_block(unsigned int num, unsigned char *buf,110 struct mtd_info *mtd)111{112 struct memcard *card;113 struct mdev_part *mpart;114 struct maple_device *mdev;115 int partition, error = 0, x, wait;116 unsigned char *blockread = NULL;117 struct vmu_cache *pcache;118 __be32 sendbuf;119 120 mpart = mtd->priv;121 mdev = mpart->mdev;122 partition = mpart->partition;123 card = maple_get_drvdata(mdev);124 pcache = card->parts[partition].pcache;125 pcache->valid = 0;126 127 /* prepare the cache for this block */128 if (!pcache->buffer) {129 pcache->buffer = kmalloc(card->blocklen, GFP_KERNEL);130 if (!pcache->buffer) {131 dev_err(&mdev->dev, "VMU at (%d, %d) - read fails due"132 " to lack of memory\n", mdev->port,133 mdev->unit);134 error = -ENOMEM;135 goto outB;136 }137 }138 139 /*140 * Reads may be phased - again the hardware spec141 * supports this - though may not be any devices in142 * the wild that implement it, but we will here143 */144 for (x = 0; x < card->readcnt; x++) {145 sendbuf = cpu_to_be32(partition << 24 | x << 16 | num);146 147 if (atomic_read(&mdev->busy) == 1) {148 wait_event_interruptible_timeout(mdev->maple_wait,149 atomic_read(&mdev->busy) == 0, HZ);150 if (atomic_read(&mdev->busy) == 1) {151 dev_notice(&mdev->dev, "VMU at (%d, %d)"152 " is busy\n", mdev->port, mdev->unit);153 error = -EAGAIN;154 goto outB;155 }156 }157 158 atomic_set(&mdev->busy, 1);159 blockread = kmalloc(card->blocklen/card->readcnt, GFP_KERNEL);160 if (!blockread) {161 error = -ENOMEM;162 atomic_set(&mdev->busy, 0);163 goto outB;164 }165 card->blockread = blockread;166 167 maple_getcond_callback(mdev, vmu_blockread, 0,168 MAPLE_FUNC_MEMCARD);169 error = maple_add_packet(mdev, MAPLE_FUNC_MEMCARD,170 MAPLE_COMMAND_BREAD, 2, &sendbuf);171 /* Very long timeouts seem to be needed when box is stressed */172 wait = wait_event_interruptible_timeout(mdev->maple_wait,173 (atomic_read(&mdev->busy) == 0 ||174 atomic_read(&mdev->busy) == 2), HZ * 3);175 /*176 * MTD layer does not handle hotplugging well177 * so have to return errors when VMU is unplugged178 * in the middle of a read (busy == 2)179 */180 if (error || atomic_read(&mdev->busy) == 2) {181 if (atomic_read(&mdev->busy) == 2)182 error = -ENXIO;183 atomic_set(&mdev->busy, 0);184 card->blockread = NULL;185 goto outA;186 }187 if (wait == 0 || wait == -ERESTARTSYS) {188 card->blockread = NULL;189 atomic_set(&mdev->busy, 0);190 error = -EIO;191 list_del_init(&(mdev->mq->list));192 kfree(mdev->mq->sendbuf);193 mdev->mq->sendbuf = NULL;194 if (wait == -ERESTARTSYS) {195 dev_warn(&mdev->dev, "VMU read on (%d, %d)"196 " interrupted on block 0x%X\n",197 mdev->port, mdev->unit, num);198 } else199 dev_notice(&mdev->dev, "VMU read on (%d, %d)"200 " timed out on block 0x%X\n",201 mdev->port, mdev->unit, num);202 goto outA;203 }204 205 memcpy(buf + (card->blocklen/card->readcnt) * x, blockread,206 card->blocklen/card->readcnt);207 208 memcpy(pcache->buffer + (card->blocklen/card->readcnt) * x,209 card->blockread, card->blocklen/card->readcnt);210 card->blockread = NULL;211 pcache->block = num;212 pcache->jiffies_atc = jiffies;213 pcache->valid = 1;214 kfree(blockread);215 }216 217 return error;218 219outA:220 kfree(blockread);221outB:222 return error;223}224 225/* communicate with maple bus for phased writing */226static int maple_vmu_write_block(unsigned int num, const unsigned char *buf,227 struct mtd_info *mtd)228{229 struct memcard *card;230 struct mdev_part *mpart;231 struct maple_device *mdev;232 int partition, error, locking, x, phaselen, wait;233 __be32 *sendbuf;234 235 mpart = mtd->priv;236 mdev = mpart->mdev;237 partition = mpart->partition;238 card = maple_get_drvdata(mdev);239 240 phaselen = card->blocklen/card->writecnt;241 242 sendbuf = kmalloc(phaselen + 4, GFP_KERNEL);243 if (!sendbuf) {244 error = -ENOMEM;245 goto fail_nosendbuf;246 }247 for (x = 0; x < card->writecnt; x++) {248 sendbuf[0] = cpu_to_be32(partition << 24 | x << 16 | num);249 memcpy(&sendbuf[1], buf + phaselen * x, phaselen);250 /* wait until the device is not busy doing something else251 * or 1 second - which ever is longer */252 if (atomic_read(&mdev->busy) == 1) {253 wait_event_interruptible_timeout(mdev->maple_wait,254 atomic_read(&mdev->busy) == 0, HZ);255 if (atomic_read(&mdev->busy) == 1) {256 error = -EBUSY;257 dev_notice(&mdev->dev, "VMU write at (%d, %d)"258 "failed - device is busy\n",259 mdev->port, mdev->unit);260 goto fail_nolock;261 }262 }263 atomic_set(&mdev->busy, 1);264 265 locking = maple_add_packet(mdev, MAPLE_FUNC_MEMCARD,266 MAPLE_COMMAND_BWRITE, phaselen / 4 + 2, sendbuf);267 wait = wait_event_interruptible_timeout(mdev->maple_wait,268 atomic_read(&mdev->busy) == 0, HZ/10);269 if (locking) {270 error = -EIO;271 atomic_set(&mdev->busy, 0);272 goto fail_nolock;273 }274 if (atomic_read(&mdev->busy) == 2) {275 atomic_set(&mdev->busy, 0);276 } else if (wait == 0 || wait == -ERESTARTSYS) {277 error = -EIO;278 dev_warn(&mdev->dev, "Write at (%d, %d) of block"279 " 0x%X at phase %d failed: could not"280 " communicate with VMU", mdev->port,281 mdev->unit, num, x);282 atomic_set(&mdev->busy, 0);283 kfree(mdev->mq->sendbuf);284 mdev->mq->sendbuf = NULL;285 list_del_init(&(mdev->mq->list));286 goto fail_nolock;287 }288 }289 kfree(sendbuf);290 291 return card->blocklen;292 293fail_nolock:294 kfree(sendbuf);295fail_nosendbuf:296 dev_err(&mdev->dev, "VMU (%d, %d): write failed\n", mdev->port,297 mdev->unit);298 return error;299}300 301/* mtd function to simulate reading byte by byte */302static unsigned char vmu_flash_read_char(unsigned long ofs, int *retval,303 struct mtd_info *mtd)304{305 struct vmu_block *vblock;306 struct memcard *card;307 struct mdev_part *mpart;308 struct maple_device *mdev;309 unsigned char *buf, ret;310 int partition, error;311 312 mpart = mtd->priv;313 mdev = mpart->mdev;314 partition = mpart->partition;315 card = maple_get_drvdata(mdev);316 *retval = 0;317 318 buf = kmalloc(card->blocklen, GFP_KERNEL);319 if (!buf) {320 *retval = 1;321 ret = -ENOMEM;322 goto finish;323 }324 325 vblock = ofs_to_block(ofs, mtd, partition);326 if (!vblock) {327 *retval = 3;328 ret = -ENOMEM;329 goto out_buf;330 }331 332 error = maple_vmu_read_block(vblock->num, buf, mtd);333 if (error) {334 ret = error;335 *retval = 2;336 goto out_vblock;337 }338 339 ret = buf[vblock->ofs];340 341out_vblock:342 kfree(vblock);343out_buf:344 kfree(buf);345finish:346 return ret;347}348 349/* mtd higher order function to read flash */350static int vmu_flash_read(struct mtd_info *mtd, loff_t from, size_t len,351 size_t *retlen, u_char *buf)352{353 struct maple_device *mdev;354 struct memcard *card;355 struct mdev_part *mpart;356 struct vmu_cache *pcache;357 struct vmu_block *vblock;358 int index = 0, retval, partition, leftover, numblocks;359 unsigned char cx;360 361 mpart = mtd->priv;362 mdev = mpart->mdev;363 partition = mpart->partition;364 card = maple_get_drvdata(mdev);365 366 numblocks = card->parts[partition].numblocks;367 if (from + len > numblocks * card->blocklen)368 len = numblocks * card->blocklen - from;369 if (len == 0)370 return -EIO;371 /* Have we cached this bit already? */372 pcache = card->parts[partition].pcache;373 do {374 vblock = ofs_to_block(from + index, mtd, partition);375 if (!vblock)376 return -ENOMEM;377 /* Have we cached this and is the cache valid and timely? */378 if (pcache->valid &&379 time_before(jiffies, pcache->jiffies_atc + HZ) &&380 (pcache->block == vblock->num)) {381 /* we have cached it, so do necessary copying */382 leftover = card->blocklen - vblock->ofs;383 if (vblock->ofs + len - index < card->blocklen) {384 /* only a bit of this block to copy */385 memcpy(buf + index,386 pcache->buffer + vblock->ofs,387 len - index);388 index = len;389 } else {390 /* otherwise copy remainder of whole block */391 memcpy(buf + index, pcache->buffer +392 vblock->ofs, leftover);393 index += leftover;394 }395 } else {396 /*397 * Not cached so read one byte -398 * but cache the rest of the block399 */400 cx = vmu_flash_read_char(from + index, &retval, mtd);401 if (retval) {402 *retlen = index;403 kfree(vblock);404 return cx;405 }406 memset(buf + index, cx, 1);407 index++;408 }409 kfree(vblock);410 } while (len > index);411 *retlen = index;412 413 return 0;414}415 416static int vmu_flash_write(struct mtd_info *mtd, loff_t to, size_t len,417 size_t *retlen, const u_char *buf)418{419 struct maple_device *mdev;420 struct memcard *card;421 struct mdev_part *mpart;422 int index = 0, partition, error = 0, numblocks;423 struct vmu_cache *pcache;424 struct vmu_block *vblock;425 unsigned char *buffer;426 427 mpart = mtd->priv;428 mdev = mpart->mdev;429 partition = mpart->partition;430 card = maple_get_drvdata(mdev);431 432 numblocks = card->parts[partition].numblocks;433 if (to + len > numblocks * card->blocklen)434 len = numblocks * card->blocklen - to;435 if (len == 0) {436 error = -EIO;437 goto failed;438 }439 440 vblock = ofs_to_block(to, mtd, partition);441 if (!vblock) {442 error = -ENOMEM;443 goto failed;444 }445 446 buffer = kmalloc(card->blocklen, GFP_KERNEL);447 if (!buffer) {448 error = -ENOMEM;449 goto fail_buffer;450 }451 452 do {453 /* Read in the block we are to write to */454 error = maple_vmu_read_block(vblock->num, buffer, mtd);455 if (error)456 goto fail_io;457 458 do {459 buffer[vblock->ofs] = buf[index];460 vblock->ofs++;461 index++;462 if (index >= len)463 break;464 } while (vblock->ofs < card->blocklen);465 466 /* write out new buffer */467 error = maple_vmu_write_block(vblock->num, buffer, mtd);468 /* invalidate the cache */469 pcache = card->parts[partition].pcache;470 pcache->valid = 0;471 472 if (error != card->blocklen)473 goto fail_io;474 475 vblock->num++;476 vblock->ofs = 0;477 } while (len > index);478 479 kfree(buffer);480 *retlen = index;481 kfree(vblock);482 return 0;483 484fail_io:485 kfree(buffer);486fail_buffer:487 kfree(vblock);488failed:489 dev_err(&mdev->dev, "VMU write failing with error %d\n", error);490 return error;491}492 493static void vmu_flash_sync(struct mtd_info *mtd)494{495 /* Do nothing here */496}497 498/* Maple bus callback function to recursively query hardware details */499static void vmu_queryblocks(struct mapleq *mq)500{501 struct maple_device *mdev;502 unsigned short *res;503 struct memcard *card;504 __be32 partnum;505 struct vmu_cache *pcache;506 struct mdev_part *mpart;507 struct mtd_info *mtd_cur;508 struct vmupart *part_cur;509 int error;510 511 mdev = mq->dev;512 card = maple_get_drvdata(mdev);513 res = (unsigned short *) (mq->recvbuf->buf);514 card->tempA = res[12];515 card->tempB = res[6];516 517 dev_info(&mdev->dev, "VMU device at partition %d has %d user "518 "blocks with a root block at %d\n", card->partition,519 card->tempA, card->tempB);520 521 part_cur = &card->parts[card->partition];522 part_cur->user_blocks = card->tempA;523 part_cur->root_block = card->tempB;524 part_cur->numblocks = card->tempB + 1;525 part_cur->name = kmalloc(12, GFP_KERNEL);526 if (!part_cur->name)527 goto fail_name;528 529 sprintf(part_cur->name, "vmu%d.%d.%d",530 mdev->port, mdev->unit, card->partition);531 mtd_cur = &card->mtd[card->partition];532 mtd_cur->name = part_cur->name;533 mtd_cur->type = 8;534 mtd_cur->flags = MTD_WRITEABLE|MTD_NO_ERASE;535 mtd_cur->size = part_cur->numblocks * card->blocklen;536 mtd_cur->erasesize = card->blocklen;537 mtd_cur->_write = vmu_flash_write;538 mtd_cur->_read = vmu_flash_read;539 mtd_cur->_sync = vmu_flash_sync;540 mtd_cur->writesize = card->blocklen;541 542 mpart = kmalloc(sizeof(struct mdev_part), GFP_KERNEL);543 if (!mpart)544 goto fail_mpart;545 546 mpart->mdev = mdev;547 mpart->partition = card->partition;548 mtd_cur->priv = mpart;549 mtd_cur->owner = THIS_MODULE;550 551 pcache = kzalloc(sizeof(struct vmu_cache), GFP_KERNEL);552 if (!pcache)553 goto fail_cache_create;554 part_cur->pcache = pcache;555 556 error = mtd_device_register(mtd_cur, NULL, 0);557 if (error)558 goto fail_mtd_register;559 560 maple_getcond_callback(mdev, NULL, 0,561 MAPLE_FUNC_MEMCARD);562 563 /*564 * Set up a recursive call to the (probably theoretical)565 * second or more partition566 */567 if (++card->partition < card->partitions) {568 partnum = cpu_to_be32(card->partition << 24);569 maple_getcond_callback(mdev, vmu_queryblocks, 0,570 MAPLE_FUNC_MEMCARD);571 maple_add_packet(mdev, MAPLE_FUNC_MEMCARD,572 MAPLE_COMMAND_GETMINFO, 2, &partnum);573 }574 return;575 576fail_mtd_register:577 dev_err(&mdev->dev, "Could not register maple device at (%d, %d)"578 "error is 0x%X\n", mdev->port, mdev->unit, error);579 for (error = 0; error <= card->partition; error++) {580 kfree(((card->parts)[error]).pcache);581 ((card->parts)[error]).pcache = NULL;582 }583fail_cache_create:584fail_mpart:585 for (error = 0; error <= card->partition; error++) {586 kfree(((card->mtd)[error]).priv);587 ((card->mtd)[error]).priv = NULL;588 }589 maple_getcond_callback(mdev, NULL, 0,590 MAPLE_FUNC_MEMCARD);591 kfree(part_cur->name);592fail_name:593 return;594}595 596/* Handles very basic info about the flash, queries for details */597static int vmu_connect(struct maple_device *mdev)598{599 unsigned long test_flash_data, basic_flash_data;600 int c, error;601 struct memcard *card;602 u32 partnum = 0;603 604 test_flash_data = be32_to_cpu(mdev->devinfo.function);605 /* Need to count how many bits are set - to find out which606 * function_data element has details of the memory card607 */608 c = hweight_long(test_flash_data);609 610 basic_flash_data = be32_to_cpu(mdev->devinfo.function_data[c - 1]);611 612 card = kmalloc(sizeof(struct memcard), GFP_KERNEL);613 if (!card) {614 error = -ENOMEM;615 goto fail_nomem;616 }617 618 card->partitions = (basic_flash_data >> 24 & 0xFF) + 1;619 card->blocklen = ((basic_flash_data >> 16 & 0xFF) + 1) << 5;620 card->writecnt = basic_flash_data >> 12 & 0xF;621 card->readcnt = basic_flash_data >> 8 & 0xF;622 card->removable = basic_flash_data >> 7 & 1;623 624 card->partition = 0;625 626 /*627 * Not sure there are actually any multi-partition devices in the628 * real world, but the hardware supports them, so, so will we629 */630 card->parts = kmalloc_array(card->partitions, sizeof(struct vmupart),631 GFP_KERNEL);632 if (!card->parts) {633 error = -ENOMEM;634 goto fail_partitions;635 }636 637 card->mtd = kmalloc_array(card->partitions, sizeof(struct mtd_info),638 GFP_KERNEL);639 if (!card->mtd) {640 error = -ENOMEM;641 goto fail_mtd_info;642 }643 644 maple_set_drvdata(mdev, card);645 646 /*647 * We want to trap meminfo not get cond648 * so set interval to zero, but rely on maple bus649 * driver to pass back the results of the meminfo650 */651 maple_getcond_callback(mdev, vmu_queryblocks, 0,652 MAPLE_FUNC_MEMCARD);653 654 /* Make sure we are clear to go */655 if (atomic_read(&mdev->busy) == 1) {656 wait_event_interruptible_timeout(mdev->maple_wait,657 atomic_read(&mdev->busy) == 0, HZ);658 if (atomic_read(&mdev->busy) == 1) {659 dev_notice(&mdev->dev, "VMU at (%d, %d) is busy\n",660 mdev->port, mdev->unit);661 error = -EAGAIN;662 goto fail_device_busy;663 }664 }665 666 atomic_set(&mdev->busy, 1);667 668 /*669 * Set up the minfo call: vmu_queryblocks will handle670 * the information passed back671 */672 error = maple_add_packet(mdev, MAPLE_FUNC_MEMCARD,673 MAPLE_COMMAND_GETMINFO, 2, &partnum);674 if (error) {675 dev_err(&mdev->dev, "Could not lock VMU at (%d, %d)"676 " error is 0x%X\n", mdev->port, mdev->unit, error);677 goto fail_mtd_info;678 }679 return 0;680 681fail_device_busy:682 kfree(card->mtd);683fail_mtd_info:684 kfree(card->parts);685fail_partitions:686 kfree(card);687fail_nomem:688 return error;689}690 691static void vmu_disconnect(struct maple_device *mdev)692{693 struct memcard *card;694 struct mdev_part *mpart;695 int x;696 697 mdev->callback = NULL;698 card = maple_get_drvdata(mdev);699 for (x = 0; x < card->partitions; x++) {700 mpart = ((card->mtd)[x]).priv;701 mpart->mdev = NULL;702 mtd_device_unregister(&((card->mtd)[x]));703 kfree(((card->parts)[x]).name);704 }705 kfree(card->parts);706 kfree(card->mtd);707 kfree(card);708}709 710/* Callback to handle eccentricities of both mtd subsystem711 * and general flakyness of Dreamcast VMUs712 */713static int vmu_can_unload(struct maple_device *mdev)714{715 struct memcard *card;716 int x;717 struct mtd_info *mtd;718 719 card = maple_get_drvdata(mdev);720 for (x = 0; x < card->partitions; x++) {721 mtd = &((card->mtd)[x]);722 if (kref_read(&mtd->refcnt))723 return 0;724 }725 return 1;726}727 728#define ERRSTR "VMU at (%d, %d) file error -"729 730static void vmu_file_error(struct maple_device *mdev, void *recvbuf)731{732 enum maple_file_errors error = ((int *)recvbuf)[1];733 734 switch (error) {735 736 case MAPLE_FILEERR_INVALID_PARTITION:737 dev_notice(&mdev->dev, ERRSTR " invalid partition number\n",738 mdev->port, mdev->unit);739 break;740 741 case MAPLE_FILEERR_PHASE_ERROR:742 dev_notice(&mdev->dev, ERRSTR " phase error\n",743 mdev->port, mdev->unit);744 break;745 746 case MAPLE_FILEERR_INVALID_BLOCK:747 dev_notice(&mdev->dev, ERRSTR " invalid block number\n",748 mdev->port, mdev->unit);749 break;750 751 case MAPLE_FILEERR_WRITE_ERROR:752 dev_notice(&mdev->dev, ERRSTR " write error\n",753 mdev->port, mdev->unit);754 break;755 756 case MAPLE_FILEERR_INVALID_WRITE_LENGTH:757 dev_notice(&mdev->dev, ERRSTR " invalid write length\n",758 mdev->port, mdev->unit);759 break;760 761 case MAPLE_FILEERR_BAD_CRC:762 dev_notice(&mdev->dev, ERRSTR " bad CRC\n",763 mdev->port, mdev->unit);764 break;765 766 default:767 dev_notice(&mdev->dev, ERRSTR " 0x%X\n",768 mdev->port, mdev->unit, error);769 }770}771 772 773static int probe_maple_vmu(struct device *dev)774{775 struct maple_device *mdev = to_maple_dev(dev);776 struct maple_driver *mdrv = to_maple_driver(dev->driver);777 778 mdev->can_unload = vmu_can_unload;779 mdev->fileerr_handler = vmu_file_error;780 mdev->driver = mdrv;781 782 return vmu_connect(mdev);783}784 785static int remove_maple_vmu(struct device *dev)786{787 struct maple_device *mdev = to_maple_dev(dev);788 789 vmu_disconnect(mdev);790 return 0;791}792 793static struct maple_driver vmu_flash_driver = {794 .function = MAPLE_FUNC_MEMCARD,795 .drv = {796 .name = "Dreamcast_visual_memory",797 .probe = probe_maple_vmu,798 .remove = remove_maple_vmu,799 },800};801 802static int __init vmu_flash_map_init(void)803{804 return maple_driver_register(&vmu_flash_driver);805}806 807static void __exit vmu_flash_map_exit(void)808{809 maple_driver_unregister(&vmu_flash_driver);810}811 812module_init(vmu_flash_map_init);813module_exit(vmu_flash_map_exit);814 815MODULE_LICENSE("GPL");816MODULE_AUTHOR("Adrian McMenamin");817MODULE_DESCRIPTION("Flash mapping for Sega Dreamcast visual memory");818