1
2
3
4
5
6
7
8
9
10
11#define PRERELEASE
12
13#include <linux/kernel.h>
14#include <linux/module.h>
15#include <asm/errno.h>
16#include <asm/io.h>
17#include <asm/uaccess.h>
18#include <linux/miscdevice.h>
19#include <linux/delay.h>
20#include <linux/slab.h>
21#include <linux/init.h>
22#include <linux/hdreg.h>
23
24#include <linux/kmod.h>
25#include <linux/mtd/mtd.h>
26#include <linux/mtd/nand.h>
27#include <linux/mtd/nftl.h>
28#include <linux/mtd/blktrans.h>
29
30
31
32
33
34#define MAX_LOOPS 10000
35
36
37static void nftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
38{
39 struct NFTLrecord *nftl;
40 unsigned long temp;
41
42 if (mtd->type != MTD_NANDFLASH)
43 return;
44
45 if (memcmp(mtd->name, "DiskOnChip", 10))
46 return;
47
48 if (!mtd->block_isbad) {
49 printk(KERN_ERR
50"NFTL no longer supports the old DiskOnChip drivers loaded via docprobe.\n"
51"Please use the new diskonchip driver under the NAND subsystem.\n");
52 return;
53 }
54
55 DEBUG(MTD_DEBUG_LEVEL1, "NFTL: add_mtd for %s\n", mtd->name);
56
57 nftl = kzalloc(sizeof(struct NFTLrecord), GFP_KERNEL);
58
59 if (!nftl) {
60 printk(KERN_WARNING "NFTL: out of memory for data structures\n");
61 return;
62 }
63
64 nftl->mbd.mtd = mtd;
65 nftl->mbd.devnum = -1;
66
67 nftl->mbd.tr = tr;
68
69 if (NFTL_mount(nftl) < 0) {
70 printk(KERN_WARNING "NFTL: could not mount device\n");
71 kfree(nftl);
72 return;
73 }
74
75
76
77
78 nftl->cylinders = 1024;
79 nftl->heads = 16;
80
81 temp = nftl->cylinders * nftl->heads;
82 nftl->sectors = nftl->mbd.size / temp;
83 if (nftl->mbd.size % temp) {
84 nftl->sectors++;
85 temp = nftl->cylinders * nftl->sectors;
86 nftl->heads = nftl->mbd.size / temp;
87
88 if (nftl->mbd.size % temp) {
89 nftl->heads++;
90 temp = nftl->heads * nftl->sectors;
91 nftl->cylinders = nftl->mbd.size / temp;
92 }
93 }
94
95 if (nftl->mbd.size != nftl->heads * nftl->cylinders * nftl->sectors) {
96
97
98
99
100 printk(KERN_WARNING "NFTL: cannot calculate a geometry to "
101 "match size of 0x%lx.\n", nftl->mbd.size);
102 printk(KERN_WARNING "NFTL: using C:%d H:%d S:%d "
103 "(== 0x%lx sects)\n",
104 nftl->cylinders, nftl->heads , nftl->sectors,
105 (long)nftl->cylinders * (long)nftl->heads *
106 (long)nftl->sectors );
107 }
108
109 if (add_mtd_blktrans_dev(&nftl->mbd)) {
110 kfree(nftl->ReplUnitTable);
111 kfree(nftl->EUNtable);
112 kfree(nftl);
113 return;
114 }
115#ifdef PSYCHO_DEBUG
116 printk(KERN_INFO "NFTL: Found new nftl%c\n", nftl->mbd.devnum + 'a');
117#endif
118}
119
120static void nftl_remove_dev(struct mtd_blktrans_dev *dev)
121{
122 struct NFTLrecord *nftl = (void *)dev;
123
124 DEBUG(MTD_DEBUG_LEVEL1, "NFTL: remove_dev (i=%d)\n", dev->devnum);
125
126 del_mtd_blktrans_dev(dev);
127 kfree(nftl->ReplUnitTable);
128 kfree(nftl->EUNtable);
129 kfree(nftl);
130}
131
132
133
134
135int nftl_read_oob(struct mtd_info *mtd, loff_t offs, size_t len,
136 size_t *retlen, uint8_t *buf)
137{
138 struct mtd_oob_ops ops;
139 int res;
140
141 ops.mode = MTD_OOB_PLACE;
142 ops.ooboffs = offs & (mtd->writesize - 1);
143 ops.ooblen = len;
144 ops.oobbuf = buf;
145 ops.datbuf = NULL;
146
147 res = mtd->read_oob(mtd, offs & ~(mtd->writesize - 1), &ops);
148 *retlen = ops.oobretlen;
149 return res;
150}
151
152
153
154
155int nftl_write_oob(struct mtd_info *mtd, loff_t offs, size_t len,
156 size_t *retlen, uint8_t *buf)
157{
158 struct mtd_oob_ops ops;
159 int res;
160
161 ops.mode = MTD_OOB_PLACE;
162 ops.ooboffs = offs & (mtd->writesize - 1);
163 ops.ooblen = len;
164 ops.oobbuf = buf;
165 ops.datbuf = NULL;
166
167 res = mtd->write_oob(mtd, offs & ~(mtd->writesize - 1), &ops);
168 *retlen = ops.oobretlen;
169 return res;
170}
171
172#ifdef CONFIG_NFTL_RW
173
174
175
176
177static int nftl_write(struct mtd_info *mtd, loff_t offs, size_t len,
178 size_t *retlen, uint8_t *buf, uint8_t *oob)
179{
180 struct mtd_oob_ops ops;
181 int res;
182
183 ops.mode = MTD_OOB_PLACE;
184 ops.ooboffs = offs;
185 ops.ooblen = mtd->oobsize;
186 ops.oobbuf = oob;
187 ops.datbuf = buf;
188 ops.len = len;
189
190 res = mtd->write_oob(mtd, offs & ~(mtd->writesize - 1), &ops);
191 *retlen = ops.retlen;
192 return res;
193}
194
195
196
197
198
199static u16 NFTL_findfreeblock(struct NFTLrecord *nftl, int desperate )
200{
201
202
203
204
205 u16 pot = nftl->LastFreeEUN;
206 int silly = nftl->nb_blocks;
207
208
209 if (!desperate && nftl->numfreeEUNs < 2) {
210 DEBUG(MTD_DEBUG_LEVEL1, "NFTL_findfreeblock: there are too few free EUNs\n");
211 return 0xffff;
212 }
213
214
215 do {
216 if (nftl->ReplUnitTable[pot] == BLOCK_FREE) {
217 nftl->LastFreeEUN = pot;
218 nftl->numfreeEUNs--;
219 return pot;
220 }
221
222
223
224
225
226 if (++pot > nftl->lastEUN)
227 pot = le16_to_cpu(nftl->MediaHdr.FirstPhysicalEUN);
228
229 if (!silly--) {
230 printk("Argh! No free blocks found! LastFreeEUN = %d, "
231 "FirstEUN = %d\n", nftl->LastFreeEUN,
232 le16_to_cpu(nftl->MediaHdr.FirstPhysicalEUN));
233 return 0xffff;
234 }
235 } while (pot != nftl->LastFreeEUN);
236
237 return 0xffff;
238}
239
240static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned pendingblock )
241{
242 struct mtd_info *mtd = nftl->mbd.mtd;
243 u16 BlockMap[MAX_SECTORS_PER_UNIT];
244 unsigned char BlockLastState[MAX_SECTORS_PER_UNIT];
245 unsigned char BlockFreeFound[MAX_SECTORS_PER_UNIT];
246 unsigned int thisEUN;
247 int block;
248 int silly;
249 unsigned int targetEUN;
250 struct nftl_oob oob;
251 int inplace = 1;
252 size_t retlen;
253
254 memset(BlockMap, 0xff, sizeof(BlockMap));
255 memset(BlockFreeFound, 0, sizeof(BlockFreeFound));
256
257 thisEUN = nftl->EUNtable[thisVUC];
258
259 if (thisEUN == BLOCK_NIL) {
260 printk(KERN_WARNING "Trying to fold non-existent "
261 "Virtual Unit Chain %d!\n", thisVUC);
262 return BLOCK_NIL;
263 }
264
265
266
267
268 silly = MAX_LOOPS;
269 targetEUN = BLOCK_NIL;
270 while (thisEUN <= nftl->lastEUN ) {
271 unsigned int status, foldmark;
272
273 targetEUN = thisEUN;
274 for (block = 0; block < nftl->EraseSize / 512; block ++) {
275 nftl_read_oob(mtd, (thisEUN * nftl->EraseSize) +
276 (block * 512), 16 , &retlen,
277 (char *)&oob);
278 if (block == 2) {
279 foldmark = oob.u.c.FoldMark | oob.u.c.FoldMark1;
280 if (foldmark == FOLD_MARK_IN_PROGRESS) {
281 DEBUG(MTD_DEBUG_LEVEL1,
282 "Write Inhibited on EUN %d\n", thisEUN);
283 inplace = 0;
284 } else {
285
286
287
288 inplace = 1;
289 }
290 }
291 status = oob.b.Status | oob.b.Status1;
292 BlockLastState[block] = status;
293
294 switch(status) {
295 case SECTOR_FREE:
296 BlockFreeFound[block] = 1;
297 break;
298
299 case SECTOR_USED:
300 if (!BlockFreeFound[block])
301 BlockMap[block] = thisEUN;
302 else
303 printk(KERN_WARNING
304 "SECTOR_USED found after SECTOR_FREE "
305 "in Virtual Unit Chain %d for block %d\n",
306 thisVUC, block);
307 break;
308 case SECTOR_DELETED:
309 if (!BlockFreeFound[block])
310 BlockMap[block] = BLOCK_NIL;
311 else
312 printk(KERN_WARNING
313 "SECTOR_DELETED found after SECTOR_FREE "
314 "in Virtual Unit Chain %d for block %d\n",
315 thisVUC, block);
316 break;
317
318 case SECTOR_IGNORE:
319 break;
320 default:
321 printk("Unknown status for block %d in EUN %d: %x\n",
322 block, thisEUN, status);
323 }
324 }
325
326 if (!silly--) {
327 printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%x\n",
328 thisVUC);
329 return BLOCK_NIL;
330 }
331
332 thisEUN = nftl->ReplUnitTable[thisEUN];
333 }
334
335 if (inplace) {
336
337
338
339
340
341
342
343 for (block = 0; block < nftl->EraseSize / 512 ; block++) {
344 if (BlockLastState[block] != SECTOR_FREE &&
345 BlockMap[block] != BLOCK_NIL &&
346 BlockMap[block] != targetEUN) {
347 DEBUG(MTD_DEBUG_LEVEL1, "Setting inplace to 0. VUC %d, "
348 "block %d was %x lastEUN, "
349 "and is in EUN %d (%s) %d\n",
350 thisVUC, block, BlockLastState[block],
351 BlockMap[block],
352 BlockMap[block]== targetEUN ? "==" : "!=",
353 targetEUN);
354 inplace = 0;
355 break;
356 }
357 }
358
359 if (pendingblock >= (thisVUC * (nftl->EraseSize / 512)) &&
360 pendingblock < ((thisVUC + 1)* (nftl->EraseSize / 512)) &&
361 BlockLastState[pendingblock - (thisVUC * (nftl->EraseSize / 512))] !=
362 SECTOR_FREE) {
363 DEBUG(MTD_DEBUG_LEVEL1, "Pending write not free in EUN %d. "
364 "Folding out of place.\n", targetEUN);
365 inplace = 0;
366 }
367 }
368
369 if (!inplace) {
370 DEBUG(MTD_DEBUG_LEVEL1, "Cannot fold Virtual Unit Chain %d in place. "
371 "Trying out-of-place\n", thisVUC);
372
373 targetEUN = NFTL_findfreeblock(nftl, 1);
374 if (targetEUN == BLOCK_NIL) {
375
376
377
378
379
380
381 printk(KERN_WARNING
382 "NFTL_findfreeblock(desperate) returns 0xffff.\n");
383 return BLOCK_NIL;
384 }
385 } else {
386
387
388
389
390 oob.u.c.FoldMark = oob.u.c.FoldMark1 = cpu_to_le16(FOLD_MARK_IN_PROGRESS);
391 oob.u.c.unused = 0xffffffff;
392 nftl_write_oob(mtd, (nftl->EraseSize * targetEUN) + 2 * 512 + 8,
393 8, &retlen, (char *)&oob.u);
394 }
395
396
397
398
399
400 DEBUG(MTD_DEBUG_LEVEL1,"Folding chain %d into unit %d\n", thisVUC, targetEUN);
401 for (block = 0; block < nftl->EraseSize / 512 ; block++) {
402 unsigned char movebuf[512];
403 int ret;
404
405
406 if (BlockMap[block] == targetEUN ||
407 (pendingblock == (thisVUC * (nftl->EraseSize / 512) + block))) {
408 continue;
409 }
410
411
412
413 if (BlockMap[block] == BLOCK_NIL)
414 continue;
415
416 ret = mtd->read(mtd, (nftl->EraseSize * BlockMap[block]) + (block * 512),
417 512, &retlen, movebuf);
418 if (ret < 0 && ret != -EUCLEAN) {
419 ret = mtd->read(mtd, (nftl->EraseSize * BlockMap[block])
420 + (block * 512), 512, &retlen,
421 movebuf);
422 if (ret != -EIO)
423 printk("Error went away on retry.\n");
424 }
425 memset(&oob, 0xff, sizeof(struct nftl_oob));
426 oob.b.Status = oob.b.Status1 = SECTOR_USED;
427
428 nftl_write(nftl->mbd.mtd, (nftl->EraseSize * targetEUN) +
429 (block * 512), 512, &retlen, movebuf, (char *)&oob);
430 }
431
432
433 oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC);
434 oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum = 0xffff;
435
436 nftl_write_oob(mtd, (nftl->EraseSize * targetEUN) + 8,
437 8, &retlen, (char *)&oob.u);
438
439
440
441
442
443
444
445
446 thisEUN = nftl->EUNtable[thisVUC];
447 DEBUG(MTD_DEBUG_LEVEL1,"Want to erase\n");
448
449
450
451 while (thisEUN <= nftl->lastEUN && thisEUN != targetEUN) {
452 unsigned int EUNtmp;
453
454 EUNtmp = nftl->ReplUnitTable[thisEUN];
455
456 if (NFTL_formatblock(nftl, thisEUN) < 0) {
457
458
459 nftl->ReplUnitTable[thisEUN] = BLOCK_RESERVED;
460 } else {
461
462 nftl->ReplUnitTable[thisEUN] = BLOCK_FREE;
463 nftl->numfreeEUNs++;
464 }
465 thisEUN = EUNtmp;
466 }
467
468
469 nftl->ReplUnitTable[targetEUN] = BLOCK_NIL;
470 nftl->EUNtable[thisVUC] = targetEUN;
471
472 return targetEUN;
473}
474
475static u16 NFTL_makefreeblock( struct NFTLrecord *nftl , unsigned pendingblock)
476{
477
478
479
480
481
482
483
484 u16 LongestChain = 0;
485 u16 ChainLength = 0, thislen;
486 u16 chain, EUN;
487
488 for (chain = 0; chain < le32_to_cpu(nftl->MediaHdr.FormattedSize) / nftl->EraseSize; chain++) {
489 EUN = nftl->EUNtable[chain];
490 thislen = 0;
491
492 while (EUN <= nftl->lastEUN) {
493 thislen++;
494
495 EUN = nftl->ReplUnitTable[EUN] & 0x7fff;
496 if (thislen > 0xff00) {
497 printk("Endless loop in Virtual Chain %d: Unit %x\n",
498 chain, EUN);
499 }
500 if (thislen > 0xff10) {
501
502
503 thislen = 0;
504 break;
505 }
506 }
507
508 if (thislen > ChainLength) {
509
510 ChainLength = thislen;
511 LongestChain = chain;
512 }
513 }
514
515 if (ChainLength < 2) {
516 printk(KERN_WARNING "No Virtual Unit Chains available for folding. "
517 "Failing request\n");
518 return 0xffff;
519 }
520
521 return NFTL_foldchain (nftl, LongestChain, pendingblock);
522}
523
524
525
526
527static inline u16 NFTL_findwriteunit(struct NFTLrecord *nftl, unsigned block)
528{
529 u16 lastEUN;
530 u16 thisVUC = block / (nftl->EraseSize / 512);
531 struct mtd_info *mtd = nftl->mbd.mtd;
532 unsigned int writeEUN;
533 unsigned long blockofs = (block * 512) & (nftl->EraseSize -1);
534 size_t retlen;
535 int silly, silly2 = 3;
536 struct nftl_oob oob;
537
538 do {
539
540
541
542
543
544
545
546 lastEUN = BLOCK_NIL;
547 writeEUN = nftl->EUNtable[thisVUC];
548 silly = MAX_LOOPS;
549 while (writeEUN <= nftl->lastEUN) {
550 struct nftl_bci bci;
551 size_t retlen;
552 unsigned int status;
553
554 lastEUN = writeEUN;
555
556 nftl_read_oob(mtd,
557 (writeEUN * nftl->EraseSize) + blockofs,
558 8, &retlen, (char *)&bci);
559
560 DEBUG(MTD_DEBUG_LEVEL2, "Status of block %d in EUN %d is %x\n",
561 block , writeEUN, le16_to_cpu(bci.Status));
562
563 status = bci.Status | bci.Status1;
564 switch(status) {
565 case SECTOR_FREE:
566 return writeEUN;
567
568 case SECTOR_DELETED:
569 case SECTOR_USED:
570 case SECTOR_IGNORE:
571 break;
572 default:
573
574 break;
575 }
576
577 if (!silly--) {
578 printk(KERN_WARNING
579 "Infinite loop in Virtual Unit Chain 0x%x\n",
580 thisVUC);
581 return 0xffff;
582 }
583
584
585 writeEUN = nftl->ReplUnitTable[writeEUN];
586 }
587
588
589
590
591
592 writeEUN = NFTL_findfreeblock(nftl, 0);
593
594 if (writeEUN == BLOCK_NIL) {
595
596
597
598
599
600
601
602
603
604 writeEUN = NFTL_makefreeblock(nftl, 0xffff);
605
606 if (writeEUN == BLOCK_NIL) {
607
608
609
610
611
612
613 DEBUG(MTD_DEBUG_LEVEL1, "Using desperate==1 to find free EUN to accommodate write to VUC %d\n", thisVUC);
614 writeEUN = NFTL_findfreeblock(nftl, 1);
615 }
616 if (writeEUN == BLOCK_NIL) {
617
618
619
620
621
622
623 printk(KERN_WARNING "Cannot make free space.\n");
624 return BLOCK_NIL;
625 }
626
627 lastEUN = BLOCK_NIL;
628 continue;
629 }
630
631
632
633 if (lastEUN != BLOCK_NIL) {
634 thisVUC |= 0x8000;
635 } else {
636
637 nftl->EUNtable[thisVUC] = writeEUN;
638 }
639
640
641
642 nftl->ReplUnitTable[writeEUN] = BLOCK_NIL;
643
644
645 nftl_read_oob(mtd, writeEUN * nftl->EraseSize + 8, 8,
646 &retlen, (char *)&oob.u);
647
648 oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC);
649
650 nftl_write_oob(mtd, writeEUN * nftl->EraseSize + 8, 8,
651 &retlen, (char *)&oob.u);
652
653
654
655
656 if (lastEUN != BLOCK_NIL) {
657
658 nftl->ReplUnitTable[lastEUN] = writeEUN;
659
660 nftl_read_oob(mtd, (lastEUN * nftl->EraseSize) + 8,
661 8, &retlen, (char *)&oob.u);
662
663 oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum
664 = cpu_to_le16(writeEUN);
665
666 nftl_write_oob(mtd, (lastEUN * nftl->EraseSize) + 8,
667 8, &retlen, (char *)&oob.u);
668 }
669
670 return writeEUN;
671
672 } while (silly2--);
673
674 printk(KERN_WARNING "Error folding to make room for Virtual Unit Chain 0x%x\n",
675 thisVUC);
676 return 0xffff;
677}
678
679static int nftl_writeblock(struct mtd_blktrans_dev *mbd, unsigned long block,
680 char *buffer)
681{
682 struct NFTLrecord *nftl = (void *)mbd;
683 u16 writeEUN;
684 unsigned long blockofs = (block * 512) & (nftl->EraseSize - 1);
685 size_t retlen;
686 struct nftl_oob oob;
687
688 writeEUN = NFTL_findwriteunit(nftl, block);
689
690 if (writeEUN == BLOCK_NIL) {
691 printk(KERN_WARNING
692 "NFTL_writeblock(): Cannot find block to write to\n");
693
694 return 1;
695 }
696
697 memset(&oob, 0xff, sizeof(struct nftl_oob));
698 oob.b.Status = oob.b.Status1 = SECTOR_USED;
699
700 nftl_write(nftl->mbd.mtd, (writeEUN * nftl->EraseSize) + blockofs,
701 512, &retlen, (char *)buffer, (char *)&oob);
702 return 0;
703}
704#endif
705
706static int nftl_readblock(struct mtd_blktrans_dev *mbd, unsigned long block,
707 char *buffer)
708{
709 struct NFTLrecord *nftl = (void *)mbd;
710 struct mtd_info *mtd = nftl->mbd.mtd;
711 u16 lastgoodEUN;
712 u16 thisEUN = nftl->EUNtable[block / (nftl->EraseSize / 512)];
713 unsigned long blockofs = (block * 512) & (nftl->EraseSize - 1);
714 unsigned int status;
715 int silly = MAX_LOOPS;
716 size_t retlen;
717 struct nftl_bci bci;
718
719 lastgoodEUN = BLOCK_NIL;
720
721 if (thisEUN != BLOCK_NIL) {
722 while (thisEUN < nftl->nb_blocks) {
723 if (nftl_read_oob(mtd, (thisEUN * nftl->EraseSize) +
724 blockofs, 8, &retlen,
725 (char *)&bci) < 0)
726 status = SECTOR_IGNORE;
727 else
728 status = bci.Status | bci.Status1;
729
730 switch (status) {
731 case SECTOR_FREE:
732
733 goto the_end;
734 case SECTOR_DELETED:
735 lastgoodEUN = BLOCK_NIL;
736 break;
737 case SECTOR_USED:
738 lastgoodEUN = thisEUN;
739 break;
740 case SECTOR_IGNORE:
741 break;
742 default:
743 printk("Unknown status for block %ld in EUN %d: %x\n",
744 block, thisEUN, status);
745 break;
746 }
747
748 if (!silly--) {
749 printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%lx\n",
750 block / (nftl->EraseSize / 512));
751 return 1;
752 }
753 thisEUN = nftl->ReplUnitTable[thisEUN];
754 }
755 }
756
757 the_end:
758 if (lastgoodEUN == BLOCK_NIL) {
759
760 memset(buffer, 0, 512);
761 } else {
762 loff_t ptr = (lastgoodEUN * nftl->EraseSize) + blockofs;
763 size_t retlen;
764 int res = mtd->read(mtd, ptr, 512, &retlen, buffer);
765
766 if (res < 0 && res != -EUCLEAN)
767 return -EIO;
768 }
769 return 0;
770}
771
772static int nftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
773{
774 struct NFTLrecord *nftl = (void *)dev;
775
776 geo->heads = nftl->heads;
777 geo->sectors = nftl->sectors;
778 geo->cylinders = nftl->cylinders;
779
780 return 0;
781}
782
783
784
785
786
787
788
789
790static struct mtd_blktrans_ops nftl_tr = {
791 .name = "nftl",
792 .major = NFTL_MAJOR,
793 .part_bits = NFTL_PARTN_BITS,
794 .blksize = 512,
795 .getgeo = nftl_getgeo,
796 .readsect = nftl_readblock,
797#ifdef CONFIG_NFTL_RW
798 .writesect = nftl_writeblock,
799#endif
800 .add_mtd = nftl_add_mtd,
801 .remove_dev = nftl_remove_dev,
802 .owner = THIS_MODULE,
803};
804
805static int __init init_nftl(void)
806{
807 return register_mtd_blktrans(&nftl_tr);
808}
809
810static void __exit cleanup_nftl(void)
811{
812 deregister_mtd_blktrans(&nftl_tr);
813}
814
815module_init(init_nftl);
816module_exit(cleanup_nftl);
817
818MODULE_LICENSE("GPL");
819MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>, Fabrice Bellard <fabrice.bellard@netgem.com> et al.");
820MODULE_DESCRIPTION("Support code for NAND Flash Translation Layer, used on M-Systems DiskOnChip 2000 and Millennium");
821