xref: /rk3399_rockchip-uboot/drivers/mtd/mtdconcat.c (revision dfe64e2c89731a3f9950d7acd8681b68df2bae03)
10a572655SStefan Roese /*
20a572655SStefan Roese  * MTD device concatenation layer
30a572655SStefan Roese  *
40a572655SStefan Roese  * (C) 2002 Robert Kaiser <rkaiser@sysgo.de>
50a572655SStefan Roese  *
60a572655SStefan Roese  * NAND support by Christian Gan <cgan@iders.ca>
70a572655SStefan Roese  *
80a572655SStefan Roese  * This code is GPL
90a572655SStefan Roese  */
100a572655SStefan Roese 
110a572655SStefan Roese #include <linux/mtd/mtd.h>
127b15e2bbSMike Frysinger #include <linux/compat.h>
130a572655SStefan Roese #include <linux/mtd/concat.h>
140a572655SStefan Roese #include <ubi_uboot.h>
150a572655SStefan Roese 
160a572655SStefan Roese /*
170a572655SStefan Roese  * Our storage structure:
180a572655SStefan Roese  * Subdev points to an array of pointers to struct mtd_info objects
190a572655SStefan Roese  * which is allocated along with this structure
200a572655SStefan Roese  *
210a572655SStefan Roese  */
220a572655SStefan Roese struct mtd_concat {
230a572655SStefan Roese 	struct mtd_info mtd;
240a572655SStefan Roese 	int num_subdev;
250a572655SStefan Roese 	struct mtd_info **subdev;
260a572655SStefan Roese };
270a572655SStefan Roese 
280a572655SStefan Roese /*
290a572655SStefan Roese  * how to calculate the size required for the above structure,
300a572655SStefan Roese  * including the pointer array subdev points to:
310a572655SStefan Roese  */
320a572655SStefan Roese #define SIZEOF_STRUCT_MTD_CONCAT(num_subdev)	\
330a572655SStefan Roese 	((sizeof(struct mtd_concat) + (num_subdev) * sizeof(struct mtd_info *)))
340a572655SStefan Roese 
350a572655SStefan Roese /*
360a572655SStefan Roese  * Given a pointer to the MTD object in the mtd_concat structure,
370a572655SStefan Roese  * we can retrieve the pointer to that structure with this macro.
380a572655SStefan Roese  */
390a572655SStefan Roese #define CONCAT(x)  ((struct mtd_concat *)(x))
400a572655SStefan Roese 
410a572655SStefan Roese /*
420a572655SStefan Roese  * MTD methods which look up the relevant subdevice, translate the
430a572655SStefan Roese  * effective address and pass through to the subdevice.
440a572655SStefan Roese  */
450a572655SStefan Roese 
460a572655SStefan Roese static int
470a572655SStefan Roese concat_read(struct mtd_info *mtd, loff_t from, size_t len,
480a572655SStefan Roese 	    size_t * retlen, u_char * buf)
490a572655SStefan Roese {
500a572655SStefan Roese 	struct mtd_concat *concat = CONCAT(mtd);
510a572655SStefan Roese 	int ret = 0, err;
520a572655SStefan Roese 	int i;
530a572655SStefan Roese 
540a572655SStefan Roese 	*retlen = 0;
550a572655SStefan Roese 
560a572655SStefan Roese 	for (i = 0; i < concat->num_subdev; i++) {
570a572655SStefan Roese 		struct mtd_info *subdev = concat->subdev[i];
580a572655SStefan Roese 		size_t size, retsize;
590a572655SStefan Roese 
600a572655SStefan Roese 		if (from >= subdev->size) {
610a572655SStefan Roese 			/* Not destined for this subdev */
620a572655SStefan Roese 			size = 0;
630a572655SStefan Roese 			from -= subdev->size;
640a572655SStefan Roese 			continue;
650a572655SStefan Roese 		}
660a572655SStefan Roese 		if (from + len > subdev->size)
670a572655SStefan Roese 			/* First part goes into this subdev */
680a572655SStefan Roese 			size = subdev->size - from;
690a572655SStefan Roese 		else
700a572655SStefan Roese 			/* Entire transaction goes into this subdev */
710a572655SStefan Roese 			size = len;
720a572655SStefan Roese 
73*dfe64e2cSSergey Lapin 		err = mtd_read(subdev, from, size, &retsize, buf);
740a572655SStefan Roese 
750a572655SStefan Roese 		/* Save information about bitflips! */
760a572655SStefan Roese 		if (unlikely(err)) {
77*dfe64e2cSSergey Lapin 			if (mtd_is_eccerr(err)) {
780a572655SStefan Roese 				mtd->ecc_stats.failed++;
790a572655SStefan Roese 				ret = err;
80*dfe64e2cSSergey Lapin 			} else if (mtd_is_bitflip(err)) {
810a572655SStefan Roese 				mtd->ecc_stats.corrected++;
820a572655SStefan Roese 				/* Do not overwrite -EBADMSG !! */
830a572655SStefan Roese 				if (!ret)
840a572655SStefan Roese 					ret = err;
850a572655SStefan Roese 			} else
860a572655SStefan Roese 				return err;
870a572655SStefan Roese 		}
880a572655SStefan Roese 
890a572655SStefan Roese 		*retlen += retsize;
900a572655SStefan Roese 		len -= size;
910a572655SStefan Roese 		if (len == 0)
920a572655SStefan Roese 			return ret;
930a572655SStefan Roese 
940a572655SStefan Roese 		buf += size;
950a572655SStefan Roese 		from = 0;
960a572655SStefan Roese 	}
970a572655SStefan Roese 	return -EINVAL;
980a572655SStefan Roese }
990a572655SStefan Roese 
1000a572655SStefan Roese static int
1010a572655SStefan Roese concat_write(struct mtd_info *mtd, loff_t to, size_t len,
1020a572655SStefan Roese 	     size_t * retlen, const u_char * buf)
1030a572655SStefan Roese {
1040a572655SStefan Roese 	struct mtd_concat *concat = CONCAT(mtd);
1050a572655SStefan Roese 	int err = -EINVAL;
1060a572655SStefan Roese 	int i;
1070a572655SStefan Roese 
1080a572655SStefan Roese 	*retlen = 0;
1090a572655SStefan Roese 
1100a572655SStefan Roese 	for (i = 0; i < concat->num_subdev; i++) {
1110a572655SStefan Roese 		struct mtd_info *subdev = concat->subdev[i];
1120a572655SStefan Roese 		size_t size, retsize;
1130a572655SStefan Roese 
1140a572655SStefan Roese 		if (to >= subdev->size) {
1150a572655SStefan Roese 			size = 0;
1160a572655SStefan Roese 			to -= subdev->size;
1170a572655SStefan Roese 			continue;
1180a572655SStefan Roese 		}
1190a572655SStefan Roese 		if (to + len > subdev->size)
1200a572655SStefan Roese 			size = subdev->size - to;
1210a572655SStefan Roese 		else
1220a572655SStefan Roese 			size = len;
1230a572655SStefan Roese 
124*dfe64e2cSSergey Lapin 		err = mtd_write(subdev, to, size, &retsize, buf);
1250a572655SStefan Roese 		if (err)
1260a572655SStefan Roese 			break;
1270a572655SStefan Roese 
1280a572655SStefan Roese 		*retlen += retsize;
1290a572655SStefan Roese 		len -= size;
1300a572655SStefan Roese 		if (len == 0)
1310a572655SStefan Roese 			break;
1320a572655SStefan Roese 
1330a572655SStefan Roese 		err = -EINVAL;
1340a572655SStefan Roese 		buf += size;
1350a572655SStefan Roese 		to = 0;
1360a572655SStefan Roese 	}
1370a572655SStefan Roese 	return err;
1380a572655SStefan Roese }
1390a572655SStefan Roese 
1400a572655SStefan Roese static int
1410a572655SStefan Roese concat_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
1420a572655SStefan Roese {
1430a572655SStefan Roese 	struct mtd_concat *concat = CONCAT(mtd);
1440a572655SStefan Roese 	struct mtd_oob_ops devops = *ops;
1450a572655SStefan Roese 	int i, err, ret = 0;
1460a572655SStefan Roese 
1470a572655SStefan Roese 	ops->retlen = ops->oobretlen = 0;
1480a572655SStefan Roese 
1490a572655SStefan Roese 	for (i = 0; i < concat->num_subdev; i++) {
1500a572655SStefan Roese 		struct mtd_info *subdev = concat->subdev[i];
1510a572655SStefan Roese 
1520a572655SStefan Roese 		if (from >= subdev->size) {
1530a572655SStefan Roese 			from -= subdev->size;
1540a572655SStefan Roese 			continue;
1550a572655SStefan Roese 		}
1560a572655SStefan Roese 
1570a572655SStefan Roese 		/* partial read ? */
1580a572655SStefan Roese 		if (from + devops.len > subdev->size)
1590a572655SStefan Roese 			devops.len = subdev->size - from;
1600a572655SStefan Roese 
161*dfe64e2cSSergey Lapin 		err = mtd_read_oob(subdev, from, &devops);
1620a572655SStefan Roese 		ops->retlen += devops.retlen;
1630a572655SStefan Roese 		ops->oobretlen += devops.oobretlen;
1640a572655SStefan Roese 
1650a572655SStefan Roese 		/* Save information about bitflips! */
1660a572655SStefan Roese 		if (unlikely(err)) {
167*dfe64e2cSSergey Lapin 			if (mtd_is_eccerr(err)) {
1680a572655SStefan Roese 				mtd->ecc_stats.failed++;
1690a572655SStefan Roese 				ret = err;
170*dfe64e2cSSergey Lapin 			} else if (mtd_is_bitflip(err)) {
1710a572655SStefan Roese 				mtd->ecc_stats.corrected++;
1720a572655SStefan Roese 				/* Do not overwrite -EBADMSG !! */
1730a572655SStefan Roese 				if (!ret)
1740a572655SStefan Roese 					ret = err;
1750a572655SStefan Roese 			} else
1760a572655SStefan Roese 				return err;
1770a572655SStefan Roese 		}
1780a572655SStefan Roese 
1790a572655SStefan Roese 		if (devops.datbuf) {
1800a572655SStefan Roese 			devops.len = ops->len - ops->retlen;
1810a572655SStefan Roese 			if (!devops.len)
1820a572655SStefan Roese 				return ret;
1830a572655SStefan Roese 			devops.datbuf += devops.retlen;
1840a572655SStefan Roese 		}
1850a572655SStefan Roese 		if (devops.oobbuf) {
1860a572655SStefan Roese 			devops.ooblen = ops->ooblen - ops->oobretlen;
1870a572655SStefan Roese 			if (!devops.ooblen)
1880a572655SStefan Roese 				return ret;
1890a572655SStefan Roese 			devops.oobbuf += ops->oobretlen;
1900a572655SStefan Roese 		}
1910a572655SStefan Roese 
1920a572655SStefan Roese 		from = 0;
1930a572655SStefan Roese 	}
1940a572655SStefan Roese 	return -EINVAL;
1950a572655SStefan Roese }
1960a572655SStefan Roese 
1970a572655SStefan Roese static int
1980a572655SStefan Roese concat_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops)
1990a572655SStefan Roese {
2000a572655SStefan Roese 	struct mtd_concat *concat = CONCAT(mtd);
2010a572655SStefan Roese 	struct mtd_oob_ops devops = *ops;
2020a572655SStefan Roese 	int i, err;
2030a572655SStefan Roese 
2040a572655SStefan Roese 	if (!(mtd->flags & MTD_WRITEABLE))
2050a572655SStefan Roese 		return -EROFS;
2060a572655SStefan Roese 
2070a572655SStefan Roese 	ops->retlen = 0;
2080a572655SStefan Roese 
2090a572655SStefan Roese 	for (i = 0; i < concat->num_subdev; i++) {
2100a572655SStefan Roese 		struct mtd_info *subdev = concat->subdev[i];
2110a572655SStefan Roese 
2120a572655SStefan Roese 		if (to >= subdev->size) {
2130a572655SStefan Roese 			to -= subdev->size;
2140a572655SStefan Roese 			continue;
2150a572655SStefan Roese 		}
2160a572655SStefan Roese 
2170a572655SStefan Roese 		/* partial write ? */
2180a572655SStefan Roese 		if (to + devops.len > subdev->size)
2190a572655SStefan Roese 			devops.len = subdev->size - to;
2200a572655SStefan Roese 
221*dfe64e2cSSergey Lapin 		err = mtd_write_oob(subdev, to, &devops);
2220a572655SStefan Roese 		ops->retlen += devops.retlen;
2230a572655SStefan Roese 		if (err)
2240a572655SStefan Roese 			return err;
2250a572655SStefan Roese 
2260a572655SStefan Roese 		if (devops.datbuf) {
2270a572655SStefan Roese 			devops.len = ops->len - ops->retlen;
2280a572655SStefan Roese 			if (!devops.len)
2290a572655SStefan Roese 				return 0;
2300a572655SStefan Roese 			devops.datbuf += devops.retlen;
2310a572655SStefan Roese 		}
2320a572655SStefan Roese 		if (devops.oobbuf) {
2330a572655SStefan Roese 			devops.ooblen = ops->ooblen - ops->oobretlen;
2340a572655SStefan Roese 			if (!devops.ooblen)
2350a572655SStefan Roese 				return 0;
2360a572655SStefan Roese 			devops.oobbuf += devops.oobretlen;
2370a572655SStefan Roese 		}
2380a572655SStefan Roese 		to = 0;
2390a572655SStefan Roese 	}
2400a572655SStefan Roese 	return -EINVAL;
2410a572655SStefan Roese }
2420a572655SStefan Roese 
2430a572655SStefan Roese static void concat_erase_callback(struct erase_info *instr)
2440a572655SStefan Roese {
2450a572655SStefan Roese 	/* Nothing to do here in U-Boot */
2460a572655SStefan Roese }
2470a572655SStefan Roese 
2480a572655SStefan Roese static int concat_dev_erase(struct mtd_info *mtd, struct erase_info *erase)
2490a572655SStefan Roese {
2500a572655SStefan Roese 	int err;
2510a572655SStefan Roese 	wait_queue_head_t waitq;
2520a572655SStefan Roese 	DECLARE_WAITQUEUE(wait, current);
2530a572655SStefan Roese 
2540a572655SStefan Roese 	/*
2550a572655SStefan Roese 	 * This code was stol^H^H^H^Hinspired by mtdchar.c
2560a572655SStefan Roese 	 */
2570a572655SStefan Roese 	init_waitqueue_head(&waitq);
2580a572655SStefan Roese 
2590a572655SStefan Roese 	erase->mtd = mtd;
2600a572655SStefan Roese 	erase->callback = concat_erase_callback;
2610a572655SStefan Roese 	erase->priv = (unsigned long) &waitq;
2620a572655SStefan Roese 
2630a572655SStefan Roese 	/*
2640a572655SStefan Roese 	 * FIXME: Allow INTERRUPTIBLE. Which means
2650a572655SStefan Roese 	 * not having the wait_queue head on the stack.
2660a572655SStefan Roese 	 */
267*dfe64e2cSSergey Lapin 	err = mtd_erase(mtd, erase);
2680a572655SStefan Roese 	if (!err) {
2690a572655SStefan Roese 		set_current_state(TASK_UNINTERRUPTIBLE);
2700a572655SStefan Roese 		add_wait_queue(&waitq, &wait);
2710a572655SStefan Roese 		if (erase->state != MTD_ERASE_DONE
2720a572655SStefan Roese 		    && erase->state != MTD_ERASE_FAILED)
2730a572655SStefan Roese 			schedule();
2740a572655SStefan Roese 		remove_wait_queue(&waitq, &wait);
2750a572655SStefan Roese 		set_current_state(TASK_RUNNING);
2760a572655SStefan Roese 
2770a572655SStefan Roese 		err = (erase->state == MTD_ERASE_FAILED) ? -EIO : 0;
2780a572655SStefan Roese 	}
2790a572655SStefan Roese 	return err;
2800a572655SStefan Roese }
2810a572655SStefan Roese 
2820a572655SStefan Roese static int concat_erase(struct mtd_info *mtd, struct erase_info *instr)
2830a572655SStefan Roese {
2840a572655SStefan Roese 	struct mtd_concat *concat = CONCAT(mtd);
2850a572655SStefan Roese 	struct mtd_info *subdev;
2860a572655SStefan Roese 	int i, err;
2870a572655SStefan Roese 	uint64_t length, offset = 0;
2880a572655SStefan Roese 	struct erase_info *erase;
2890a572655SStefan Roese 
2900a572655SStefan Roese 	/*
2910a572655SStefan Roese 	 * Check for proper erase block alignment of the to-be-erased area.
2920a572655SStefan Roese 	 * It is easier to do this based on the super device's erase
2930a572655SStefan Roese 	 * region info rather than looking at each particular sub-device
2940a572655SStefan Roese 	 * in turn.
2950a572655SStefan Roese 	 */
2960a572655SStefan Roese 	if (!concat->mtd.numeraseregions) {
2970a572655SStefan Roese 		/* the easy case: device has uniform erase block size */
2980a572655SStefan Roese 		if (instr->addr & (concat->mtd.erasesize - 1))
2990a572655SStefan Roese 			return -EINVAL;
3000a572655SStefan Roese 		if (instr->len & (concat->mtd.erasesize - 1))
3010a572655SStefan Roese 			return -EINVAL;
3020a572655SStefan Roese 	} else {
3030a572655SStefan Roese 		/* device has variable erase size */
3040a572655SStefan Roese 		struct mtd_erase_region_info *erase_regions =
3050a572655SStefan Roese 		    concat->mtd.eraseregions;
3060a572655SStefan Roese 
3070a572655SStefan Roese 		/*
3080a572655SStefan Roese 		 * Find the erase region where the to-be-erased area begins:
3090a572655SStefan Roese 		 */
3100a572655SStefan Roese 		for (i = 0; i < concat->mtd.numeraseregions &&
3110a572655SStefan Roese 		     instr->addr >= erase_regions[i].offset; i++) ;
3120a572655SStefan Roese 		--i;
3130a572655SStefan Roese 
3140a572655SStefan Roese 		/*
3150a572655SStefan Roese 		 * Now erase_regions[i] is the region in which the
3160a572655SStefan Roese 		 * to-be-erased area begins. Verify that the starting
3170a572655SStefan Roese 		 * offset is aligned to this region's erase size:
3180a572655SStefan Roese 		 */
3190a572655SStefan Roese 		if (instr->addr & (erase_regions[i].erasesize - 1))
3200a572655SStefan Roese 			return -EINVAL;
3210a572655SStefan Roese 
3220a572655SStefan Roese 		/*
3230a572655SStefan Roese 		 * now find the erase region where the to-be-erased area ends:
3240a572655SStefan Roese 		 */
3250a572655SStefan Roese 		for (; i < concat->mtd.numeraseregions &&
3260a572655SStefan Roese 		     (instr->addr + instr->len) >= erase_regions[i].offset;
3270a572655SStefan Roese 		     ++i) ;
3280a572655SStefan Roese 		--i;
3290a572655SStefan Roese 		/*
3300a572655SStefan Roese 		 * check if the ending offset is aligned to this region's erase size
3310a572655SStefan Roese 		 */
3320a572655SStefan Roese 		if ((instr->addr + instr->len) & (erase_regions[i].erasesize -
3330a572655SStefan Roese 						  1))
3340a572655SStefan Roese 			return -EINVAL;
3350a572655SStefan Roese 	}
3360a572655SStefan Roese 
3370a572655SStefan Roese 	/* make a local copy of instr to avoid modifying the caller's struct */
3380a572655SStefan Roese 	erase = kmalloc(sizeof (struct erase_info), GFP_KERNEL);
3390a572655SStefan Roese 
3400a572655SStefan Roese 	if (!erase)
3410a572655SStefan Roese 		return -ENOMEM;
3420a572655SStefan Roese 
3430a572655SStefan Roese 	*erase = *instr;
3440a572655SStefan Roese 	length = instr->len;
3450a572655SStefan Roese 
3460a572655SStefan Roese 	/*
3470a572655SStefan Roese 	 * find the subdevice where the to-be-erased area begins, adjust
3480a572655SStefan Roese 	 * starting offset to be relative to the subdevice start
3490a572655SStefan Roese 	 */
3500a572655SStefan Roese 	for (i = 0; i < concat->num_subdev; i++) {
3510a572655SStefan Roese 		subdev = concat->subdev[i];
3520a572655SStefan Roese 		if (subdev->size <= erase->addr) {
3530a572655SStefan Roese 			erase->addr -= subdev->size;
3540a572655SStefan Roese 			offset += subdev->size;
3550a572655SStefan Roese 		} else {
3560a572655SStefan Roese 			break;
3570a572655SStefan Roese 		}
3580a572655SStefan Roese 	}
3590a572655SStefan Roese 
3600a572655SStefan Roese 	/* must never happen since size limit has been verified above */
3610a572655SStefan Roese 	BUG_ON(i >= concat->num_subdev);
3620a572655SStefan Roese 
3630a572655SStefan Roese 	/* now do the erase: */
3640a572655SStefan Roese 	err = 0;
3650a572655SStefan Roese 	for (; length > 0; i++) {
3660a572655SStefan Roese 		/* loop for all subdevices affected by this request */
3670a572655SStefan Roese 		subdev = concat->subdev[i];	/* get current subdevice */
3680a572655SStefan Roese 
3690a572655SStefan Roese 		/* limit length to subdevice's size: */
3700a572655SStefan Roese 		if (erase->addr + length > subdev->size)
3710a572655SStefan Roese 			erase->len = subdev->size - erase->addr;
3720a572655SStefan Roese 		else
3730a572655SStefan Roese 			erase->len = length;
3740a572655SStefan Roese 
3750a572655SStefan Roese 		length -= erase->len;
3760a572655SStefan Roese 		if ((err = concat_dev_erase(subdev, erase))) {
3770a572655SStefan Roese 			/* sanity check: should never happen since
3780a572655SStefan Roese 			 * block alignment has been checked above */
3790a572655SStefan Roese 			BUG_ON(err == -EINVAL);
3800a572655SStefan Roese 			if (erase->fail_addr != MTD_FAIL_ADDR_UNKNOWN)
3810a572655SStefan Roese 				instr->fail_addr = erase->fail_addr + offset;
3820a572655SStefan Roese 			break;
3830a572655SStefan Roese 		}
3840a572655SStefan Roese 		/*
3850a572655SStefan Roese 		 * erase->addr specifies the offset of the area to be
3860a572655SStefan Roese 		 * erased *within the current subdevice*. It can be
3870a572655SStefan Roese 		 * non-zero only the first time through this loop, i.e.
3880a572655SStefan Roese 		 * for the first subdevice where blocks need to be erased.
3890a572655SStefan Roese 		 * All the following erases must begin at the start of the
3900a572655SStefan Roese 		 * current subdevice, i.e. at offset zero.
3910a572655SStefan Roese 		 */
3920a572655SStefan Roese 		erase->addr = 0;
3930a572655SStefan Roese 		offset += subdev->size;
3940a572655SStefan Roese 	}
3950a572655SStefan Roese 	instr->state = erase->state;
3960a572655SStefan Roese 	kfree(erase);
3970a572655SStefan Roese 	if (err)
3980a572655SStefan Roese 		return err;
3990a572655SStefan Roese 
4000a572655SStefan Roese 	if (instr->callback)
4010a572655SStefan Roese 		instr->callback(instr);
4020a572655SStefan Roese 	return 0;
4030a572655SStefan Roese }
4040a572655SStefan Roese 
4050a572655SStefan Roese static int concat_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
4060a572655SStefan Roese {
4070a572655SStefan Roese 	struct mtd_concat *concat = CONCAT(mtd);
4080a572655SStefan Roese 	int i, err = -EINVAL;
4090a572655SStefan Roese 
4100a572655SStefan Roese 	for (i = 0; i < concat->num_subdev; i++) {
4110a572655SStefan Roese 		struct mtd_info *subdev = concat->subdev[i];
4120a572655SStefan Roese 		uint64_t size;
4130a572655SStefan Roese 
4140a572655SStefan Roese 		if (ofs >= subdev->size) {
4150a572655SStefan Roese 			size = 0;
4160a572655SStefan Roese 			ofs -= subdev->size;
4170a572655SStefan Roese 			continue;
4180a572655SStefan Roese 		}
4190a572655SStefan Roese 		if (ofs + len > subdev->size)
4200a572655SStefan Roese 			size = subdev->size - ofs;
4210a572655SStefan Roese 		else
4220a572655SStefan Roese 			size = len;
4230a572655SStefan Roese 
424*dfe64e2cSSergey Lapin 		err = mtd_lock(subdev, ofs, size);
4250a572655SStefan Roese 
4260a572655SStefan Roese 		if (err)
4270a572655SStefan Roese 			break;
4280a572655SStefan Roese 
4290a572655SStefan Roese 		len -= size;
4300a572655SStefan Roese 		if (len == 0)
4310a572655SStefan Roese 			break;
4320a572655SStefan Roese 
4330a572655SStefan Roese 		err = -EINVAL;
4340a572655SStefan Roese 		ofs = 0;
4350a572655SStefan Roese 	}
4360a572655SStefan Roese 
4370a572655SStefan Roese 	return err;
4380a572655SStefan Roese }
4390a572655SStefan Roese 
4400a572655SStefan Roese static int concat_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
4410a572655SStefan Roese {
4420a572655SStefan Roese 	struct mtd_concat *concat = CONCAT(mtd);
4430a572655SStefan Roese 	int i, err = 0;
4440a572655SStefan Roese 
4450a572655SStefan Roese 	for (i = 0; i < concat->num_subdev; i++) {
4460a572655SStefan Roese 		struct mtd_info *subdev = concat->subdev[i];
4470a572655SStefan Roese 		uint64_t size;
4480a572655SStefan Roese 
4490a572655SStefan Roese 		if (ofs >= subdev->size) {
4500a572655SStefan Roese 			size = 0;
4510a572655SStefan Roese 			ofs -= subdev->size;
4520a572655SStefan Roese 			continue;
4530a572655SStefan Roese 		}
4540a572655SStefan Roese 		if (ofs + len > subdev->size)
4550a572655SStefan Roese 			size = subdev->size - ofs;
4560a572655SStefan Roese 		else
4570a572655SStefan Roese 			size = len;
4580a572655SStefan Roese 
459*dfe64e2cSSergey Lapin 		err = mtd_unlock(subdev, ofs, size);
4600a572655SStefan Roese 
4610a572655SStefan Roese 		if (err)
4620a572655SStefan Roese 			break;
4630a572655SStefan Roese 
4640a572655SStefan Roese 		len -= size;
4650a572655SStefan Roese 		if (len == 0)
4660a572655SStefan Roese 			break;
4670a572655SStefan Roese 
4680a572655SStefan Roese 		err = -EINVAL;
4690a572655SStefan Roese 		ofs = 0;
4700a572655SStefan Roese 	}
4710a572655SStefan Roese 
4720a572655SStefan Roese 	return err;
4730a572655SStefan Roese }
4740a572655SStefan Roese 
4750a572655SStefan Roese static void concat_sync(struct mtd_info *mtd)
4760a572655SStefan Roese {
4770a572655SStefan Roese 	struct mtd_concat *concat = CONCAT(mtd);
4780a572655SStefan Roese 	int i;
4790a572655SStefan Roese 
4800a572655SStefan Roese 	for (i = 0; i < concat->num_subdev; i++) {
4810a572655SStefan Roese 		struct mtd_info *subdev = concat->subdev[i];
482*dfe64e2cSSergey Lapin 		mtd_sync(subdev);
4830a572655SStefan Roese 	}
4840a572655SStefan Roese }
4850a572655SStefan Roese 
4860a572655SStefan Roese static int concat_block_isbad(struct mtd_info *mtd, loff_t ofs)
4870a572655SStefan Roese {
4880a572655SStefan Roese 	struct mtd_concat *concat = CONCAT(mtd);
4890a572655SStefan Roese 	int i, res = 0;
4900a572655SStefan Roese 
491*dfe64e2cSSergey Lapin 	if (!mtd_can_have_bb(concat->subdev[0]))
4920a572655SStefan Roese 		return res;
4930a572655SStefan Roese 
4940a572655SStefan Roese 	for (i = 0; i < concat->num_subdev; i++) {
4950a572655SStefan Roese 		struct mtd_info *subdev = concat->subdev[i];
4960a572655SStefan Roese 
4970a572655SStefan Roese 		if (ofs >= subdev->size) {
4980a572655SStefan Roese 			ofs -= subdev->size;
4990a572655SStefan Roese 			continue;
5000a572655SStefan Roese 		}
5010a572655SStefan Roese 
502*dfe64e2cSSergey Lapin 		res = mtd_block_isbad(subdev, ofs);
5030a572655SStefan Roese 		break;
5040a572655SStefan Roese 	}
5050a572655SStefan Roese 
5060a572655SStefan Roese 	return res;
5070a572655SStefan Roese }
5080a572655SStefan Roese 
5090a572655SStefan Roese static int concat_block_markbad(struct mtd_info *mtd, loff_t ofs)
5100a572655SStefan Roese {
5110a572655SStefan Roese 	struct mtd_concat *concat = CONCAT(mtd);
5120a572655SStefan Roese 	int i, err = -EINVAL;
5130a572655SStefan Roese 
514*dfe64e2cSSergey Lapin 	if (!mtd_can_have_bb(concat->subdev[0]))
5150a572655SStefan Roese 		return 0;
5160a572655SStefan Roese 
5170a572655SStefan Roese 	for (i = 0; i < concat->num_subdev; i++) {
5180a572655SStefan Roese 		struct mtd_info *subdev = concat->subdev[i];
5190a572655SStefan Roese 
5200a572655SStefan Roese 		if (ofs >= subdev->size) {
5210a572655SStefan Roese 			ofs -= subdev->size;
5220a572655SStefan Roese 			continue;
5230a572655SStefan Roese 		}
5240a572655SStefan Roese 
525*dfe64e2cSSergey Lapin 		err = mtd_block_markbad(subdev, ofs);
5260a572655SStefan Roese 		if (!err)
5270a572655SStefan Roese 			mtd->ecc_stats.badblocks++;
5280a572655SStefan Roese 		break;
5290a572655SStefan Roese 	}
5300a572655SStefan Roese 
5310a572655SStefan Roese 	return err;
5320a572655SStefan Roese }
5330a572655SStefan Roese 
5340a572655SStefan Roese /*
5350a572655SStefan Roese  * This function constructs a virtual MTD device by concatenating
5360a572655SStefan Roese  * num_devs MTD devices. A pointer to the new device object is
5370a572655SStefan Roese  * stored to *new_dev upon success. This function does _not_
5380a572655SStefan Roese  * register any devices: this is the caller's responsibility.
5390a572655SStefan Roese  */
5400a572655SStefan Roese struct mtd_info *mtd_concat_create(struct mtd_info *subdev[],	/* subdevices to concatenate */
5410a572655SStefan Roese 				   int num_devs,	/* number of subdevices      */
5420a572655SStefan Roese 				   const char *name)
5430a572655SStefan Roese {				/* name for the new device   */
5440a572655SStefan Roese 	int i;
5450a572655SStefan Roese 	size_t size;
5460a572655SStefan Roese 	struct mtd_concat *concat;
5470a572655SStefan Roese 	uint32_t max_erasesize, curr_erasesize;
5480a572655SStefan Roese 	int num_erase_region;
5490a572655SStefan Roese 
5500a572655SStefan Roese 	debug("Concatenating MTD devices:\n");
5510a572655SStefan Roese 	for (i = 0; i < num_devs; i++)
5520a572655SStefan Roese 		debug("(%d): \"%s\"\n", i, subdev[i]->name);
5530a572655SStefan Roese 	debug("into device \"%s\"\n", name);
5540a572655SStefan Roese 
5550a572655SStefan Roese 	/* allocate the device structure */
5560a572655SStefan Roese 	size = SIZEOF_STRUCT_MTD_CONCAT(num_devs);
5570a572655SStefan Roese 	concat = kzalloc(size, GFP_KERNEL);
5580a572655SStefan Roese 	if (!concat) {
5590a572655SStefan Roese 		printk
5600a572655SStefan Roese 		    ("memory allocation error while creating concatenated device \"%s\"\n",
5610a572655SStefan Roese 		     name);
5620a572655SStefan Roese 		return NULL;
5630a572655SStefan Roese 	}
5640a572655SStefan Roese 	concat->subdev = (struct mtd_info **) (concat + 1);
5650a572655SStefan Roese 
5660a572655SStefan Roese 	/*
5670a572655SStefan Roese 	 * Set up the new "super" device's MTD object structure, check for
5680a572655SStefan Roese 	 * incompatibilites between the subdevices.
5690a572655SStefan Roese 	 */
5700a572655SStefan Roese 	concat->mtd.type = subdev[0]->type;
5710a572655SStefan Roese 	concat->mtd.flags = subdev[0]->flags;
5720a572655SStefan Roese 	concat->mtd.size = subdev[0]->size;
5730a572655SStefan Roese 	concat->mtd.erasesize = subdev[0]->erasesize;
5740a572655SStefan Roese 	concat->mtd.writesize = subdev[0]->writesize;
5750a572655SStefan Roese 	concat->mtd.subpage_sft = subdev[0]->subpage_sft;
5760a572655SStefan Roese 	concat->mtd.oobsize = subdev[0]->oobsize;
5770a572655SStefan Roese 	concat->mtd.oobavail = subdev[0]->oobavail;
578*dfe64e2cSSergey Lapin 	if (subdev[0]->_read_oob)
579*dfe64e2cSSergey Lapin 		concat->mtd._read_oob = concat_read_oob;
580*dfe64e2cSSergey Lapin 	if (subdev[0]->_write_oob)
581*dfe64e2cSSergey Lapin 		concat->mtd._write_oob = concat_write_oob;
582*dfe64e2cSSergey Lapin 	if (subdev[0]->_block_isbad)
583*dfe64e2cSSergey Lapin 		concat->mtd._block_isbad = concat_block_isbad;
584*dfe64e2cSSergey Lapin 	if (subdev[0]->_block_markbad)
585*dfe64e2cSSergey Lapin 		concat->mtd._block_markbad = concat_block_markbad;
5860a572655SStefan Roese 
5870a572655SStefan Roese 	concat->mtd.ecc_stats.badblocks = subdev[0]->ecc_stats.badblocks;
5880a572655SStefan Roese 
5890a572655SStefan Roese 	concat->subdev[0] = subdev[0];
5900a572655SStefan Roese 
5910a572655SStefan Roese 	for (i = 1; i < num_devs; i++) {
5920a572655SStefan Roese 		if (concat->mtd.type != subdev[i]->type) {
5930a572655SStefan Roese 			kfree(concat);
5940a572655SStefan Roese 			printk("Incompatible device type on \"%s\"\n",
5950a572655SStefan Roese 			       subdev[i]->name);
5960a572655SStefan Roese 			return NULL;
5970a572655SStefan Roese 		}
5980a572655SStefan Roese 		if (concat->mtd.flags != subdev[i]->flags) {
5990a572655SStefan Roese 			/*
6000a572655SStefan Roese 			 * Expect all flags except MTD_WRITEABLE to be
6010a572655SStefan Roese 			 * equal on all subdevices.
6020a572655SStefan Roese 			 */
6030a572655SStefan Roese 			if ((concat->mtd.flags ^ subdev[i]->
6040a572655SStefan Roese 			     flags) & ~MTD_WRITEABLE) {
6050a572655SStefan Roese 				kfree(concat);
6060a572655SStefan Roese 				printk("Incompatible device flags on \"%s\"\n",
6070a572655SStefan Roese 				       subdev[i]->name);
6080a572655SStefan Roese 				return NULL;
6090a572655SStefan Roese 			} else
6100a572655SStefan Roese 				/* if writeable attribute differs,
6110a572655SStefan Roese 				   make super device writeable */
6120a572655SStefan Roese 				concat->mtd.flags |=
6130a572655SStefan Roese 				    subdev[i]->flags & MTD_WRITEABLE;
6140a572655SStefan Roese 		}
6150a572655SStefan Roese 
6160a572655SStefan Roese 		concat->mtd.size += subdev[i]->size;
6170a572655SStefan Roese 		concat->mtd.ecc_stats.badblocks +=
6180a572655SStefan Roese 			subdev[i]->ecc_stats.badblocks;
6190a572655SStefan Roese 		if (concat->mtd.writesize   !=  subdev[i]->writesize ||
6200a572655SStefan Roese 		    concat->mtd.subpage_sft != subdev[i]->subpage_sft ||
6210a572655SStefan Roese 		    concat->mtd.oobsize    !=  subdev[i]->oobsize ||
622*dfe64e2cSSergey Lapin 		    !concat->mtd._read_oob  != !subdev[i]->_read_oob ||
623*dfe64e2cSSergey Lapin 		    !concat->mtd._write_oob != !subdev[i]->_write_oob) {
6240a572655SStefan Roese 			kfree(concat);
6250a572655SStefan Roese 			printk("Incompatible OOB or ECC data on \"%s\"\n",
6260a572655SStefan Roese 			       subdev[i]->name);
6270a572655SStefan Roese 			return NULL;
6280a572655SStefan Roese 		}
6290a572655SStefan Roese 		concat->subdev[i] = subdev[i];
6300a572655SStefan Roese 
6310a572655SStefan Roese 	}
6320a572655SStefan Roese 
6330a572655SStefan Roese 	concat->mtd.ecclayout = subdev[0]->ecclayout;
6340a572655SStefan Roese 
6350a572655SStefan Roese 	concat->num_subdev = num_devs;
6360a572655SStefan Roese 	concat->mtd.name = name;
6370a572655SStefan Roese 
638*dfe64e2cSSergey Lapin 	concat->mtd._erase = concat_erase;
639*dfe64e2cSSergey Lapin 	concat->mtd._read = concat_read;
640*dfe64e2cSSergey Lapin 	concat->mtd._write = concat_write;
641*dfe64e2cSSergey Lapin 	concat->mtd._sync = concat_sync;
642*dfe64e2cSSergey Lapin 	concat->mtd._lock = concat_lock;
643*dfe64e2cSSergey Lapin 	concat->mtd._unlock = concat_unlock;
6440a572655SStefan Roese 
6450a572655SStefan Roese 	/*
6460a572655SStefan Roese 	 * Combine the erase block size info of the subdevices:
6470a572655SStefan Roese 	 *
6480a572655SStefan Roese 	 * first, walk the map of the new device and see how
6490a572655SStefan Roese 	 * many changes in erase size we have
6500a572655SStefan Roese 	 */
6510a572655SStefan Roese 	max_erasesize = curr_erasesize = subdev[0]->erasesize;
6520a572655SStefan Roese 	num_erase_region = 1;
6530a572655SStefan Roese 	for (i = 0; i < num_devs; i++) {
6540a572655SStefan Roese 		if (subdev[i]->numeraseregions == 0) {
6550a572655SStefan Roese 			/* current subdevice has uniform erase size */
6560a572655SStefan Roese 			if (subdev[i]->erasesize != curr_erasesize) {
6570a572655SStefan Roese 				/* if it differs from the last subdevice's erase size, count it */
6580a572655SStefan Roese 				++num_erase_region;
6590a572655SStefan Roese 				curr_erasesize = subdev[i]->erasesize;
6600a572655SStefan Roese 				if (curr_erasesize > max_erasesize)
6610a572655SStefan Roese 					max_erasesize = curr_erasesize;
6620a572655SStefan Roese 			}
6630a572655SStefan Roese 		} else {
6640a572655SStefan Roese 			/* current subdevice has variable erase size */
6650a572655SStefan Roese 			int j;
6660a572655SStefan Roese 			for (j = 0; j < subdev[i]->numeraseregions; j++) {
6670a572655SStefan Roese 
6680a572655SStefan Roese 				/* walk the list of erase regions, count any changes */
6690a572655SStefan Roese 				if (subdev[i]->eraseregions[j].erasesize !=
6700a572655SStefan Roese 				    curr_erasesize) {
6710a572655SStefan Roese 					++num_erase_region;
6720a572655SStefan Roese 					curr_erasesize =
6730a572655SStefan Roese 					    subdev[i]->eraseregions[j].
6740a572655SStefan Roese 					    erasesize;
6750a572655SStefan Roese 					if (curr_erasesize > max_erasesize)
6760a572655SStefan Roese 						max_erasesize = curr_erasesize;
6770a572655SStefan Roese 				}
6780a572655SStefan Roese 			}
6790a572655SStefan Roese 		}
6800a572655SStefan Roese 	}
6810a572655SStefan Roese 
6820a572655SStefan Roese 	if (num_erase_region == 1) {
6830a572655SStefan Roese 		/*
6840a572655SStefan Roese 		 * All subdevices have the same uniform erase size.
6850a572655SStefan Roese 		 * This is easy:
6860a572655SStefan Roese 		 */
6870a572655SStefan Roese 		concat->mtd.erasesize = curr_erasesize;
6880a572655SStefan Roese 		concat->mtd.numeraseregions = 0;
6890a572655SStefan Roese 	} else {
6900a572655SStefan Roese 		uint64_t tmp64;
6910a572655SStefan Roese 
6920a572655SStefan Roese 		/*
6930a572655SStefan Roese 		 * erase block size varies across the subdevices: allocate
6940a572655SStefan Roese 		 * space to store the data describing the variable erase regions
6950a572655SStefan Roese 		 */
6960a572655SStefan Roese 		struct mtd_erase_region_info *erase_region_p;
6970a572655SStefan Roese 		uint64_t begin, position;
6980a572655SStefan Roese 
6990a572655SStefan Roese 		concat->mtd.erasesize = max_erasesize;
7000a572655SStefan Roese 		concat->mtd.numeraseregions = num_erase_region;
7010a572655SStefan Roese 		concat->mtd.eraseregions = erase_region_p =
7020a572655SStefan Roese 		    kmalloc(num_erase_region *
7030a572655SStefan Roese 			    sizeof (struct mtd_erase_region_info), GFP_KERNEL);
7040a572655SStefan Roese 		if (!erase_region_p) {
7050a572655SStefan Roese 			kfree(concat);
7060a572655SStefan Roese 			printk
7070a572655SStefan Roese 			    ("memory allocation error while creating erase region list"
7080a572655SStefan Roese 			     " for device \"%s\"\n", name);
7090a572655SStefan Roese 			return NULL;
7100a572655SStefan Roese 		}
7110a572655SStefan Roese 
7120a572655SStefan Roese 		/*
7130a572655SStefan Roese 		 * walk the map of the new device once more and fill in
7140a572655SStefan Roese 		 * in erase region info:
7150a572655SStefan Roese 		 */
7160a572655SStefan Roese 		curr_erasesize = subdev[0]->erasesize;
7170a572655SStefan Roese 		begin = position = 0;
7180a572655SStefan Roese 		for (i = 0; i < num_devs; i++) {
7190a572655SStefan Roese 			if (subdev[i]->numeraseregions == 0) {
7200a572655SStefan Roese 				/* current subdevice has uniform erase size */
7210a572655SStefan Roese 				if (subdev[i]->erasesize != curr_erasesize) {
7220a572655SStefan Roese 					/*
7230a572655SStefan Roese 					 *  fill in an mtd_erase_region_info structure for the area
7240a572655SStefan Roese 					 *  we have walked so far:
7250a572655SStefan Roese 					 */
7260a572655SStefan Roese 					erase_region_p->offset = begin;
7270a572655SStefan Roese 					erase_region_p->erasesize =
7280a572655SStefan Roese 					    curr_erasesize;
7290a572655SStefan Roese 					tmp64 = position - begin;
7300a572655SStefan Roese 					do_div(tmp64, curr_erasesize);
7310a572655SStefan Roese 					erase_region_p->numblocks = tmp64;
7320a572655SStefan Roese 					begin = position;
7330a572655SStefan Roese 
7340a572655SStefan Roese 					curr_erasesize = subdev[i]->erasesize;
7350a572655SStefan Roese 					++erase_region_p;
7360a572655SStefan Roese 				}
7370a572655SStefan Roese 				position += subdev[i]->size;
7380a572655SStefan Roese 			} else {
7390a572655SStefan Roese 				/* current subdevice has variable erase size */
7400a572655SStefan Roese 				int j;
7410a572655SStefan Roese 				for (j = 0; j < subdev[i]->numeraseregions; j++) {
7420a572655SStefan Roese 					/* walk the list of erase regions, count any changes */
7430a572655SStefan Roese 					if (subdev[i]->eraseregions[j].
7440a572655SStefan Roese 					    erasesize != curr_erasesize) {
7450a572655SStefan Roese 						erase_region_p->offset = begin;
7460a572655SStefan Roese 						erase_region_p->erasesize =
7470a572655SStefan Roese 						    curr_erasesize;
7480a572655SStefan Roese 						tmp64 = position - begin;
7490a572655SStefan Roese 						do_div(tmp64, curr_erasesize);
7500a572655SStefan Roese 						erase_region_p->numblocks = tmp64;
7510a572655SStefan Roese 						begin = position;
7520a572655SStefan Roese 
7530a572655SStefan Roese 						curr_erasesize =
7540a572655SStefan Roese 						    subdev[i]->eraseregions[j].
7550a572655SStefan Roese 						    erasesize;
7560a572655SStefan Roese 						++erase_region_p;
7570a572655SStefan Roese 					}
7580a572655SStefan Roese 					position +=
7590a572655SStefan Roese 					    subdev[i]->eraseregions[j].
7600a572655SStefan Roese 					    numblocks * (uint64_t)curr_erasesize;
7610a572655SStefan Roese 				}
7620a572655SStefan Roese 			}
7630a572655SStefan Roese 		}
7640a572655SStefan Roese 		/* Now write the final entry */
7650a572655SStefan Roese 		erase_region_p->offset = begin;
7660a572655SStefan Roese 		erase_region_p->erasesize = curr_erasesize;
7670a572655SStefan Roese 		tmp64 = position - begin;
7680a572655SStefan Roese 		do_div(tmp64, curr_erasesize);
7690a572655SStefan Roese 		erase_region_p->numblocks = tmp64;
7700a572655SStefan Roese 	}
7710a572655SStefan Roese 
7720a572655SStefan Roese 	return &concat->mtd;
7730a572655SStefan Roese }
774