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