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