]> git.kernelconcepts.de Git - karo-tx-uboot.git/blob - drivers/mtd/mtdconcat.c
Merge branch 'master' of git://git.denx.de/u-boot-nand-flash
[karo-tx-uboot.git] / drivers / mtd / mtdconcat.c
1 /*
2  * MTD device concatenation layer
3  *
4  * (C) 2002 Robert Kaiser <rkaiser@sysgo.de>
5  *
6  * NAND support by Christian Gan <cgan@iders.ca>
7  *
8  * This code is GPL
9  */
10
11 #include <linux/mtd/mtd.h>
12 #include <linux/compat.h>
13 #include <linux/mtd/concat.h>
14 #include <ubi_uboot.h>
15
16 /*
17  * Our storage structure:
18  * Subdev points to an array of pointers to struct mtd_info objects
19  * which is allocated along with this structure
20  *
21  */
22 struct mtd_concat {
23         struct mtd_info mtd;
24         int num_subdev;
25         struct mtd_info **subdev;
26 };
27
28 /*
29  * how to calculate the size required for the above structure,
30  * including the pointer array subdev points to:
31  */
32 #define SIZEOF_STRUCT_MTD_CONCAT(num_subdev)    \
33         ((sizeof(struct mtd_concat) + (num_subdev) * sizeof(struct mtd_info *)))
34
35 /*
36  * Given a pointer to the MTD object in the mtd_concat structure,
37  * we can retrieve the pointer to that structure with this macro.
38  */
39 #define CONCAT(x)  ((struct mtd_concat *)(x))
40
41 /*
42  * MTD methods which look up the relevant subdevice, translate the
43  * effective address and pass through to the subdevice.
44  */
45
46 static int
47 concat_read(struct mtd_info *mtd, loff_t from, size_t len,
48             size_t * retlen, u_char * buf)
49 {
50         struct mtd_concat *concat = CONCAT(mtd);
51         int ret = 0, err;
52         int i;
53
54         *retlen = 0;
55
56         for (i = 0; i < concat->num_subdev; i++) {
57                 struct mtd_info *subdev = concat->subdev[i];
58                 size_t size, retsize;
59
60                 if (from >= subdev->size) {
61                         /* Not destined for this subdev */
62                         size = 0;
63                         from -= subdev->size;
64                         continue;
65                 }
66                 if (from + len > subdev->size)
67                         /* First part goes into this subdev */
68                         size = subdev->size - from;
69                 else
70                         /* Entire transaction goes into this subdev */
71                         size = len;
72
73                 err = mtd_read(subdev, from, size, &retsize, buf);
74
75                 /* Save information about bitflips! */
76                 if (unlikely(err)) {
77                         if (mtd_is_eccerr(err)) {
78                                 mtd->ecc_stats.failed++;
79                                 ret = err;
80                         } else if (mtd_is_bitflip(err)) {
81                                 mtd->ecc_stats.corrected++;
82                                 /* Do not overwrite -EBADMSG !! */
83                                 if (!ret)
84                                         ret = err;
85                         } else
86                                 return err;
87                 }
88
89                 *retlen += retsize;
90                 len -= size;
91                 if (len == 0)
92                         return ret;
93
94                 buf += size;
95                 from = 0;
96         }
97         return -EINVAL;
98 }
99
100 static int
101 concat_write(struct mtd_info *mtd, loff_t to, size_t len,
102              size_t * retlen, const u_char * buf)
103 {
104         struct mtd_concat *concat = CONCAT(mtd);
105         int err = -EINVAL;
106         int i;
107
108         *retlen = 0;
109
110         for (i = 0; i < concat->num_subdev; i++) {
111                 struct mtd_info *subdev = concat->subdev[i];
112                 size_t size, retsize;
113
114                 if (to >= subdev->size) {
115                         size = 0;
116                         to -= subdev->size;
117                         continue;
118                 }
119                 if (to + len > subdev->size)
120                         size = subdev->size - to;
121                 else
122                         size = len;
123
124                 err = mtd_write(subdev, to, size, &retsize, buf);
125                 if (err)
126                         break;
127
128                 *retlen += retsize;
129                 len -= size;
130                 if (len == 0)
131                         break;
132
133                 err = -EINVAL;
134                 buf += size;
135                 to = 0;
136         }
137         return err;
138 }
139
140 static int
141 concat_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
142 {
143         struct mtd_concat *concat = CONCAT(mtd);
144         struct mtd_oob_ops devops = *ops;
145         int i, err, ret = 0;
146
147         ops->retlen = ops->oobretlen = 0;
148
149         for (i = 0; i < concat->num_subdev; i++) {
150                 struct mtd_info *subdev = concat->subdev[i];
151
152                 if (from >= subdev->size) {
153                         from -= subdev->size;
154                         continue;
155                 }
156
157                 /* partial read ? */
158                 if (from + devops.len > subdev->size)
159                         devops.len = subdev->size - from;
160
161                 err = mtd_read_oob(subdev, from, &devops);
162                 ops->retlen += devops.retlen;
163                 ops->oobretlen += devops.oobretlen;
164
165                 /* Save information about bitflips! */
166                 if (unlikely(err)) {
167                         if (mtd_is_eccerr(err)) {
168                                 mtd->ecc_stats.failed++;
169                                 ret = err;
170                         } else if (mtd_is_bitflip(err)) {
171                                 mtd->ecc_stats.corrected++;
172                                 /* Do not overwrite -EBADMSG !! */
173                                 if (!ret)
174                                         ret = err;
175                         } else
176                                 return err;
177                 }
178
179                 if (devops.datbuf) {
180                         devops.len = ops->len - ops->retlen;
181                         if (!devops.len)
182                                 return ret;
183                         devops.datbuf += devops.retlen;
184                 }
185                 if (devops.oobbuf) {
186                         devops.ooblen = ops->ooblen - ops->oobretlen;
187                         if (!devops.ooblen)
188                                 return ret;
189                         devops.oobbuf += ops->oobretlen;
190                 }
191
192                 from = 0;
193         }
194         return -EINVAL;
195 }
196
197 static int
198 concat_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops)
199 {
200         struct mtd_concat *concat = CONCAT(mtd);
201         struct mtd_oob_ops devops = *ops;
202         int i, err;
203
204         if (!(mtd->flags & MTD_WRITEABLE))
205                 return -EROFS;
206
207         ops->retlen = 0;
208
209         for (i = 0; i < concat->num_subdev; i++) {
210                 struct mtd_info *subdev = concat->subdev[i];
211
212                 if (to >= subdev->size) {
213                         to -= subdev->size;
214                         continue;
215                 }
216
217                 /* partial write ? */
218                 if (to + devops.len > subdev->size)
219                         devops.len = subdev->size - to;
220
221                 err = mtd_write_oob(subdev, to, &devops);
222                 ops->retlen += devops.retlen;
223                 if (err)
224                         return err;
225
226                 if (devops.datbuf) {
227                         devops.len = ops->len - ops->retlen;
228                         if (!devops.len)
229                                 return 0;
230                         devops.datbuf += devops.retlen;
231                 }
232                 if (devops.oobbuf) {
233                         devops.ooblen = ops->ooblen - ops->oobretlen;
234                         if (!devops.ooblen)
235                                 return 0;
236                         devops.oobbuf += devops.oobretlen;
237                 }
238                 to = 0;
239         }
240         return -EINVAL;
241 }
242
243 static void concat_erase_callback(struct erase_info *instr)
244 {
245         /* Nothing to do here in U-Boot */
246 }
247
248 static int concat_dev_erase(struct mtd_info *mtd, struct erase_info *erase)
249 {
250         int err;
251         wait_queue_head_t waitq;
252         DECLARE_WAITQUEUE(wait, current);
253
254         /*
255          * This code was stol^H^H^H^Hinspired by mtdchar.c
256          */
257         init_waitqueue_head(&waitq);
258
259         erase->mtd = mtd;
260         erase->callback = concat_erase_callback;
261         erase->priv = (unsigned long) &waitq;
262
263         /*
264          * FIXME: Allow INTERRUPTIBLE. Which means
265          * not having the wait_queue head on the stack.
266          */
267         err = mtd_erase(mtd, erase);
268         if (!err) {
269                 set_current_state(TASK_UNINTERRUPTIBLE);
270                 add_wait_queue(&waitq, &wait);
271                 if (erase->state != MTD_ERASE_DONE
272                     && erase->state != MTD_ERASE_FAILED)
273                         schedule();
274                 remove_wait_queue(&waitq, &wait);
275                 set_current_state(TASK_RUNNING);
276
277                 err = (erase->state == MTD_ERASE_FAILED) ? -EIO : 0;
278         }
279         return err;
280 }
281
282 static int concat_erase(struct mtd_info *mtd, struct erase_info *instr)
283 {
284         struct mtd_concat *concat = CONCAT(mtd);
285         struct mtd_info *subdev;
286         int i, err;
287         uint64_t length, offset = 0;
288         struct erase_info *erase;
289
290         /*
291          * Check for proper erase block alignment of the to-be-erased area.
292          * It is easier to do this based on the super device's erase
293          * region info rather than looking at each particular sub-device
294          * in turn.
295          */
296         if (!concat->mtd.numeraseregions) {
297                 /* the easy case: device has uniform erase block size */
298                 if (instr->addr & (concat->mtd.erasesize - 1))
299                         return -EINVAL;
300                 if (instr->len & (concat->mtd.erasesize - 1))
301                         return -EINVAL;
302         } else {
303                 /* device has variable erase size */
304                 struct mtd_erase_region_info *erase_regions =
305                     concat->mtd.eraseregions;
306
307                 /*
308                  * Find the erase region where the to-be-erased area begins:
309                  */
310                 for (i = 0; i < concat->mtd.numeraseregions &&
311                      instr->addr >= erase_regions[i].offset; i++) ;
312                 --i;
313
314                 /*
315                  * Now erase_regions[i] is the region in which the
316                  * to-be-erased area begins. Verify that the starting
317                  * offset is aligned to this region's erase size:
318                  */
319                 if (instr->addr & (erase_regions[i].erasesize - 1))
320                         return -EINVAL;
321
322                 /*
323                  * now find the erase region where the to-be-erased area ends:
324                  */
325                 for (; i < concat->mtd.numeraseregions &&
326                      (instr->addr + instr->len) >= erase_regions[i].offset;
327                      ++i) ;
328                 --i;
329                 /*
330                  * check if the ending offset is aligned to this region's erase size
331                  */
332                 if ((instr->addr + instr->len) & (erase_regions[i].erasesize -
333                                                   1))
334                         return -EINVAL;
335         }
336
337         /* make a local copy of instr to avoid modifying the caller's struct */
338         erase = kmalloc(sizeof (struct erase_info), GFP_KERNEL);
339
340         if (!erase)
341                 return -ENOMEM;
342
343         *erase = *instr;
344         length = instr->len;
345
346         /*
347          * find the subdevice where the to-be-erased area begins, adjust
348          * starting offset to be relative to the subdevice start
349          */
350         for (i = 0; i < concat->num_subdev; i++) {
351                 subdev = concat->subdev[i];
352                 if (subdev->size <= erase->addr) {
353                         erase->addr -= subdev->size;
354                         offset += subdev->size;
355                 } else {
356                         break;
357                 }
358         }
359
360         /* must never happen since size limit has been verified above */
361         BUG_ON(i >= concat->num_subdev);
362
363         /* now do the erase: */
364         err = 0;
365         for (; length > 0; i++) {
366                 /* loop for all subdevices affected by this request */
367                 subdev = concat->subdev[i];     /* get current subdevice */
368
369                 /* limit length to subdevice's size: */
370                 if (erase->addr + length > subdev->size)
371                         erase->len = subdev->size - erase->addr;
372                 else
373                         erase->len = length;
374
375                 length -= erase->len;
376                 if ((err = concat_dev_erase(subdev, erase))) {
377                         /* sanity check: should never happen since
378                          * block alignment has been checked above */
379                         BUG_ON(err == -EINVAL);
380                         if (erase->fail_addr != MTD_FAIL_ADDR_UNKNOWN)
381                                 instr->fail_addr = erase->fail_addr + offset;
382                         break;
383                 }
384                 /*
385                  * erase->addr specifies the offset of the area to be
386                  * erased *within the current subdevice*. It can be
387                  * non-zero only the first time through this loop, i.e.
388                  * for the first subdevice where blocks need to be erased.
389                  * All the following erases must begin at the start of the
390                  * current subdevice, i.e. at offset zero.
391                  */
392                 erase->addr = 0;
393                 offset += subdev->size;
394         }
395         instr->state = erase->state;
396         kfree(erase);
397         if (err)
398                 return err;
399
400         if (instr->callback)
401                 instr->callback(instr);
402         return 0;
403 }
404
405 static int concat_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
406 {
407         struct mtd_concat *concat = CONCAT(mtd);
408         int i, err = -EINVAL;
409
410         for (i = 0; i < concat->num_subdev; i++) {
411                 struct mtd_info *subdev = concat->subdev[i];
412                 uint64_t size;
413
414                 if (ofs >= subdev->size) {
415                         size = 0;
416                         ofs -= subdev->size;
417                         continue;
418                 }
419                 if (ofs + len > subdev->size)
420                         size = subdev->size - ofs;
421                 else
422                         size = len;
423
424                 err = mtd_lock(subdev, ofs, size);
425
426                 if (err)
427                         break;
428
429                 len -= size;
430                 if (len == 0)
431                         break;
432
433                 err = -EINVAL;
434                 ofs = 0;
435         }
436
437         return err;
438 }
439
440 static int concat_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
441 {
442         struct mtd_concat *concat = CONCAT(mtd);
443         int i, err = 0;
444
445         for (i = 0; i < concat->num_subdev; i++) {
446                 struct mtd_info *subdev = concat->subdev[i];
447                 uint64_t size;
448
449                 if (ofs >= subdev->size) {
450                         size = 0;
451                         ofs -= subdev->size;
452                         continue;
453                 }
454                 if (ofs + len > subdev->size)
455                         size = subdev->size - ofs;
456                 else
457                         size = len;
458
459                 err = mtd_unlock(subdev, ofs, size);
460
461                 if (err)
462                         break;
463
464                 len -= size;
465                 if (len == 0)
466                         break;
467
468                 err = -EINVAL;
469                 ofs = 0;
470         }
471
472         return err;
473 }
474
475 static void concat_sync(struct mtd_info *mtd)
476 {
477         struct mtd_concat *concat = CONCAT(mtd);
478         int i;
479
480         for (i = 0; i < concat->num_subdev; i++) {
481                 struct mtd_info *subdev = concat->subdev[i];
482                 mtd_sync(subdev);
483         }
484 }
485
486 static int concat_block_isbad(struct mtd_info *mtd, loff_t ofs)
487 {
488         struct mtd_concat *concat = CONCAT(mtd);
489         int i, res = 0;
490
491         if (!mtd_can_have_bb(concat->subdev[0]))
492                 return res;
493
494         for (i = 0; i < concat->num_subdev; i++) {
495                 struct mtd_info *subdev = concat->subdev[i];
496
497                 if (ofs >= subdev->size) {
498                         ofs -= subdev->size;
499                         continue;
500                 }
501
502                 res = mtd_block_isbad(subdev, ofs);
503                 break;
504         }
505
506         return res;
507 }
508
509 static int concat_block_markbad(struct mtd_info *mtd, loff_t ofs)
510 {
511         struct mtd_concat *concat = CONCAT(mtd);
512         int i, err = -EINVAL;
513
514         if (!mtd_can_have_bb(concat->subdev[0]))
515                 return 0;
516
517         for (i = 0; i < concat->num_subdev; i++) {
518                 struct mtd_info *subdev = concat->subdev[i];
519
520                 if (ofs >= subdev->size) {
521                         ofs -= subdev->size;
522                         continue;
523                 }
524
525                 err = mtd_block_markbad(subdev, ofs);
526                 if (!err)
527                         mtd->ecc_stats.badblocks++;
528                 break;
529         }
530
531         return err;
532 }
533
534 /*
535  * This function constructs a virtual MTD device by concatenating
536  * num_devs MTD devices. A pointer to the new device object is
537  * stored to *new_dev upon success. This function does _not_
538  * register any devices: this is the caller's responsibility.
539  */
540 struct mtd_info *mtd_concat_create(struct mtd_info *subdev[],   /* subdevices to concatenate */
541                                    int num_devs,        /* number of subdevices      */
542                                    const char *name)
543 {                               /* name for the new device   */
544         int i;
545         size_t size;
546         struct mtd_concat *concat;
547         uint32_t max_erasesize, curr_erasesize;
548         int num_erase_region;
549
550         debug("Concatenating MTD devices:\n");
551         for (i = 0; i < num_devs; i++)
552                 debug("(%d): \"%s\"\n", i, subdev[i]->name);
553         debug("into device \"%s\"\n", name);
554
555         /* allocate the device structure */
556         size = SIZEOF_STRUCT_MTD_CONCAT(num_devs);
557         concat = kzalloc(size, GFP_KERNEL);
558         if (!concat) {
559                 printk
560                     ("memory allocation error while creating concatenated device \"%s\"\n",
561                      name);
562                 return NULL;
563         }
564         concat->subdev = (struct mtd_info **) (concat + 1);
565
566         /*
567          * Set up the new "super" device's MTD object structure, check for
568          * incompatibilites between the subdevices.
569          */
570         concat->mtd.type = subdev[0]->type;
571         concat->mtd.flags = subdev[0]->flags;
572         concat->mtd.size = subdev[0]->size;
573         concat->mtd.erasesize = subdev[0]->erasesize;
574         concat->mtd.writesize = subdev[0]->writesize;
575         concat->mtd.subpage_sft = subdev[0]->subpage_sft;
576         concat->mtd.oobsize = subdev[0]->oobsize;
577         concat->mtd.oobavail = subdev[0]->oobavail;
578         if (subdev[0]->_read_oob)
579                 concat->mtd._read_oob = concat_read_oob;
580         if (subdev[0]->_write_oob)
581                 concat->mtd._write_oob = concat_write_oob;
582         if (subdev[0]->_block_isbad)
583                 concat->mtd._block_isbad = concat_block_isbad;
584         if (subdev[0]->_block_markbad)
585                 concat->mtd._block_markbad = concat_block_markbad;
586
587         concat->mtd.ecc_stats.badblocks = subdev[0]->ecc_stats.badblocks;
588
589         concat->subdev[0] = subdev[0];
590
591         for (i = 1; i < num_devs; i++) {
592                 if (concat->mtd.type != subdev[i]->type) {
593                         kfree(concat);
594                         printk("Incompatible device type on \"%s\"\n",
595                                subdev[i]->name);
596                         return NULL;
597                 }
598                 if (concat->mtd.flags != subdev[i]->flags) {
599                         /*
600                          * Expect all flags except MTD_WRITEABLE to be
601                          * equal on all subdevices.
602                          */
603                         if ((concat->mtd.flags ^ subdev[i]->
604                              flags) & ~MTD_WRITEABLE) {
605                                 kfree(concat);
606                                 printk("Incompatible device flags on \"%s\"\n",
607                                        subdev[i]->name);
608                                 return NULL;
609                         } else
610                                 /* if writeable attribute differs,
611                                    make super device writeable */
612                                 concat->mtd.flags |=
613                                     subdev[i]->flags & MTD_WRITEABLE;
614                 }
615
616                 concat->mtd.size += subdev[i]->size;
617                 concat->mtd.ecc_stats.badblocks +=
618                         subdev[i]->ecc_stats.badblocks;
619                 if (concat->mtd.writesize   !=  subdev[i]->writesize ||
620                     concat->mtd.subpage_sft != subdev[i]->subpage_sft ||
621                     concat->mtd.oobsize    !=  subdev[i]->oobsize ||
622                     !concat->mtd._read_oob  != !subdev[i]->_read_oob ||
623                     !concat->mtd._write_oob != !subdev[i]->_write_oob) {
624                         kfree(concat);
625                         printk("Incompatible OOB or ECC data on \"%s\"\n",
626                                subdev[i]->name);
627                         return NULL;
628                 }
629                 concat->subdev[i] = subdev[i];
630
631         }
632
633         concat->mtd.ecclayout = subdev[0]->ecclayout;
634
635         concat->num_subdev = num_devs;
636         concat->mtd.name = name;
637
638         concat->mtd._erase = concat_erase;
639         concat->mtd._read = concat_read;
640         concat->mtd._write = concat_write;
641         concat->mtd._sync = concat_sync;
642         concat->mtd._lock = concat_lock;
643         concat->mtd._unlock = concat_unlock;
644
645         /*
646          * Combine the erase block size info of the subdevices:
647          *
648          * first, walk the map of the new device and see how
649          * many changes in erase size we have
650          */
651         max_erasesize = curr_erasesize = subdev[0]->erasesize;
652         num_erase_region = 1;
653         for (i = 0; i < num_devs; i++) {
654                 if (subdev[i]->numeraseregions == 0) {
655                         /* current subdevice has uniform erase size */
656                         if (subdev[i]->erasesize != curr_erasesize) {
657                                 /* if it differs from the last subdevice's erase size, count it */
658                                 ++num_erase_region;
659                                 curr_erasesize = subdev[i]->erasesize;
660                                 if (curr_erasesize > max_erasesize)
661                                         max_erasesize = curr_erasesize;
662                         }
663                 } else {
664                         /* current subdevice has variable erase size */
665                         int j;
666                         for (j = 0; j < subdev[i]->numeraseregions; j++) {
667
668                                 /* walk the list of erase regions, count any changes */
669                                 if (subdev[i]->eraseregions[j].erasesize !=
670                                     curr_erasesize) {
671                                         ++num_erase_region;
672                                         curr_erasesize =
673                                             subdev[i]->eraseregions[j].
674                                             erasesize;
675                                         if (curr_erasesize > max_erasesize)
676                                                 max_erasesize = curr_erasesize;
677                                 }
678                         }
679                 }
680         }
681
682         if (num_erase_region == 1) {
683                 /*
684                  * All subdevices have the same uniform erase size.
685                  * This is easy:
686                  */
687                 concat->mtd.erasesize = curr_erasesize;
688                 concat->mtd.numeraseregions = 0;
689         } else {
690                 uint64_t tmp64;
691
692                 /*
693                  * erase block size varies across the subdevices: allocate
694                  * space to store the data describing the variable erase regions
695                  */
696                 struct mtd_erase_region_info *erase_region_p;
697                 uint64_t begin, position;
698
699                 concat->mtd.erasesize = max_erasesize;
700                 concat->mtd.numeraseregions = num_erase_region;
701                 concat->mtd.eraseregions = erase_region_p =
702                     kmalloc(num_erase_region *
703                             sizeof (struct mtd_erase_region_info), GFP_KERNEL);
704                 if (!erase_region_p) {
705                         kfree(concat);
706                         printk
707                             ("memory allocation error while creating erase region list"
708                              " for device \"%s\"\n", name);
709                         return NULL;
710                 }
711
712                 /*
713                  * walk the map of the new device once more and fill in
714                  * in erase region info:
715                  */
716                 curr_erasesize = subdev[0]->erasesize;
717                 begin = position = 0;
718                 for (i = 0; i < num_devs; i++) {
719                         if (subdev[i]->numeraseregions == 0) {
720                                 /* current subdevice has uniform erase size */
721                                 if (subdev[i]->erasesize != curr_erasesize) {
722                                         /*
723                                          *  fill in an mtd_erase_region_info structure for the area
724                                          *  we have walked so far:
725                                          */
726                                         erase_region_p->offset = begin;
727                                         erase_region_p->erasesize =
728                                             curr_erasesize;
729                                         tmp64 = position - begin;
730                                         do_div(tmp64, curr_erasesize);
731                                         erase_region_p->numblocks = tmp64;
732                                         begin = position;
733
734                                         curr_erasesize = subdev[i]->erasesize;
735                                         ++erase_region_p;
736                                 }
737                                 position += subdev[i]->size;
738                         } else {
739                                 /* current subdevice has variable erase size */
740                                 int j;
741                                 for (j = 0; j < subdev[i]->numeraseregions; j++) {
742                                         /* walk the list of erase regions, count any changes */
743                                         if (subdev[i]->eraseregions[j].
744                                             erasesize != curr_erasesize) {
745                                                 erase_region_p->offset = begin;
746                                                 erase_region_p->erasesize =
747                                                     curr_erasesize;
748                                                 tmp64 = position - begin;
749                                                 do_div(tmp64, curr_erasesize);
750                                                 erase_region_p->numblocks = tmp64;
751                                                 begin = position;
752
753                                                 curr_erasesize =
754                                                     subdev[i]->eraseregions[j].
755                                                     erasesize;
756                                                 ++erase_region_p;
757                                         }
758                                         position +=
759                                             subdev[i]->eraseregions[j].
760                                             numblocks * (uint64_t)curr_erasesize;
761                                 }
762                         }
763                 }
764                 /* Now write the final entry */
765                 erase_region_p->offset = begin;
766                 erase_region_p->erasesize = curr_erasesize;
767                 tmp64 = position - begin;
768                 do_div(tmp64, curr_erasesize);
769                 erase_region_p->numblocks = tmp64;
770         }
771
772         return &concat->mtd;
773 }