18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright © 2012 NetCommWireless
48c2ecf20Sopenharmony_ci * Iwo Mergler <Iwo.Mergler@netcommwireless.com.au>
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Test for multi-bit error recovery on a NAND page This mostly tests the
78c2ecf20Sopenharmony_ci * ECC controller / driver.
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * There are two test modes:
108c2ecf20Sopenharmony_ci *
118c2ecf20Sopenharmony_ci *	0 - artificially inserting bit errors until the ECC fails
128c2ecf20Sopenharmony_ci *	    This is the default method and fairly quick. It should
138c2ecf20Sopenharmony_ci *	    be independent of the quality of the FLASH.
148c2ecf20Sopenharmony_ci *
158c2ecf20Sopenharmony_ci *	1 - re-writing the same pattern repeatedly until the ECC fails.
168c2ecf20Sopenharmony_ci *	    This method relies on the physics of NAND FLASH to eventually
178c2ecf20Sopenharmony_ci *	    generate '0' bits if '1' has been written sufficient times.
188c2ecf20Sopenharmony_ci *	    Depending on the NAND, the first bit errors will appear after
198c2ecf20Sopenharmony_ci *	    1000 or more writes and then will usually snowball, reaching the
208c2ecf20Sopenharmony_ci *	    limits of the ECC quickly.
218c2ecf20Sopenharmony_ci *
228c2ecf20Sopenharmony_ci *	    The test stops after 10000 cycles, should your FLASH be
238c2ecf20Sopenharmony_ci *	    exceptionally good and not generate bit errors before that. Try
248c2ecf20Sopenharmony_ci *	    a different page in that case.
258c2ecf20Sopenharmony_ci *
268c2ecf20Sopenharmony_ci * Please note that neither of these tests will significantly 'use up' any
278c2ecf20Sopenharmony_ci * FLASH endurance. Only a maximum of two erase operations will be performed.
288c2ecf20Sopenharmony_ci */
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci#define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci#include <linux/init.h>
338c2ecf20Sopenharmony_ci#include <linux/module.h>
348c2ecf20Sopenharmony_ci#include <linux/moduleparam.h>
358c2ecf20Sopenharmony_ci#include <linux/mtd/mtd.h>
368c2ecf20Sopenharmony_ci#include <linux/err.h>
378c2ecf20Sopenharmony_ci#include <linux/mtd/rawnand.h>
388c2ecf20Sopenharmony_ci#include <linux/slab.h>
398c2ecf20Sopenharmony_ci#include "mtd_test.h"
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_cistatic int dev;
428c2ecf20Sopenharmony_cimodule_param(dev, int, S_IRUGO);
438c2ecf20Sopenharmony_ciMODULE_PARM_DESC(dev, "MTD device number to use");
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_cistatic unsigned page_offset;
468c2ecf20Sopenharmony_cimodule_param(page_offset, uint, S_IRUGO);
478c2ecf20Sopenharmony_ciMODULE_PARM_DESC(page_offset, "Page number relative to dev start");
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_cistatic unsigned seed;
508c2ecf20Sopenharmony_cimodule_param(seed, uint, S_IRUGO);
518c2ecf20Sopenharmony_ciMODULE_PARM_DESC(seed, "Random seed");
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_cistatic int mode;
548c2ecf20Sopenharmony_cimodule_param(mode, int, S_IRUGO);
558c2ecf20Sopenharmony_ciMODULE_PARM_DESC(mode, "0=incremental errors, 1=overwrite test");
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_cistatic unsigned max_overwrite = 10000;
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_cistatic loff_t   offset;     /* Offset of the page we're using. */
608c2ecf20Sopenharmony_cistatic unsigned eraseblock; /* Eraseblock number for our page. */
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci/* We assume that the ECC can correct up to a certain number
638c2ecf20Sopenharmony_ci * of biterrors per subpage. */
648c2ecf20Sopenharmony_cistatic unsigned subsize;  /* Size of subpages */
658c2ecf20Sopenharmony_cistatic unsigned subcount; /* Number of subpages per page */
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_cistatic struct mtd_info *mtd;   /* MTD device */
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic uint8_t *wbuffer; /* One page write / compare buffer */
708c2ecf20Sopenharmony_cistatic uint8_t *rbuffer; /* One page read buffer */
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci/* 'random' bytes from known offsets */
738c2ecf20Sopenharmony_cistatic uint8_t hash(unsigned offset)
748c2ecf20Sopenharmony_ci{
758c2ecf20Sopenharmony_ci	unsigned v = offset;
768c2ecf20Sopenharmony_ci	unsigned char c;
778c2ecf20Sopenharmony_ci	v ^= 0x7f7edfd3;
788c2ecf20Sopenharmony_ci	v = v ^ (v >> 3);
798c2ecf20Sopenharmony_ci	v = v ^ (v >> 5);
808c2ecf20Sopenharmony_ci	v = v ^ (v >> 13);
818c2ecf20Sopenharmony_ci	c = v & 0xFF;
828c2ecf20Sopenharmony_ci	/* Reverse bits of result. */
838c2ecf20Sopenharmony_ci	c = (c & 0x0F) << 4 | (c & 0xF0) >> 4;
848c2ecf20Sopenharmony_ci	c = (c & 0x33) << 2 | (c & 0xCC) >> 2;
858c2ecf20Sopenharmony_ci	c = (c & 0x55) << 1 | (c & 0xAA) >> 1;
868c2ecf20Sopenharmony_ci	return c;
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci/* Writes wbuffer to page */
908c2ecf20Sopenharmony_cistatic int write_page(int log)
918c2ecf20Sopenharmony_ci{
928c2ecf20Sopenharmony_ci	if (log)
938c2ecf20Sopenharmony_ci		pr_info("write_page\n");
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	return mtdtest_write(mtd, offset, mtd->writesize, wbuffer);
968c2ecf20Sopenharmony_ci}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci/* Re-writes the data area while leaving the OOB alone. */
998c2ecf20Sopenharmony_cistatic int rewrite_page(int log)
1008c2ecf20Sopenharmony_ci{
1018c2ecf20Sopenharmony_ci	int err = 0;
1028c2ecf20Sopenharmony_ci	struct mtd_oob_ops ops;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	if (log)
1058c2ecf20Sopenharmony_ci		pr_info("rewrite page\n");
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	ops.mode      = MTD_OPS_RAW; /* No ECC */
1088c2ecf20Sopenharmony_ci	ops.len       = mtd->writesize;
1098c2ecf20Sopenharmony_ci	ops.retlen    = 0;
1108c2ecf20Sopenharmony_ci	ops.ooblen    = 0;
1118c2ecf20Sopenharmony_ci	ops.oobretlen = 0;
1128c2ecf20Sopenharmony_ci	ops.ooboffs   = 0;
1138c2ecf20Sopenharmony_ci	ops.datbuf    = wbuffer;
1148c2ecf20Sopenharmony_ci	ops.oobbuf    = NULL;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	err = mtd_write_oob(mtd, offset, &ops);
1178c2ecf20Sopenharmony_ci	if (err || ops.retlen != mtd->writesize) {
1188c2ecf20Sopenharmony_ci		pr_err("error: write_oob failed (%d)\n", err);
1198c2ecf20Sopenharmony_ci		if (!err)
1208c2ecf20Sopenharmony_ci			err = -EIO;
1218c2ecf20Sopenharmony_ci	}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	return err;
1248c2ecf20Sopenharmony_ci}
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci/* Reads page into rbuffer. Returns number of corrected bit errors (>=0)
1278c2ecf20Sopenharmony_ci * or error (<0) */
1288c2ecf20Sopenharmony_cistatic int read_page(int log)
1298c2ecf20Sopenharmony_ci{
1308c2ecf20Sopenharmony_ci	int err = 0;
1318c2ecf20Sopenharmony_ci	size_t read;
1328c2ecf20Sopenharmony_ci	struct mtd_ecc_stats oldstats;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	if (log)
1358c2ecf20Sopenharmony_ci		pr_info("read_page\n");
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	/* Saving last mtd stats */
1388c2ecf20Sopenharmony_ci	memcpy(&oldstats, &mtd->ecc_stats, sizeof(oldstats));
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	err = mtd_read(mtd, offset, mtd->writesize, &read, rbuffer);
1418c2ecf20Sopenharmony_ci	if (!err || err == -EUCLEAN)
1428c2ecf20Sopenharmony_ci		err = mtd->ecc_stats.corrected - oldstats.corrected;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	if (err < 0 || read != mtd->writesize) {
1458c2ecf20Sopenharmony_ci		pr_err("error: read failed at %#llx\n", (long long)offset);
1468c2ecf20Sopenharmony_ci		if (err >= 0)
1478c2ecf20Sopenharmony_ci			err = -EIO;
1488c2ecf20Sopenharmony_ci	}
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	return err;
1518c2ecf20Sopenharmony_ci}
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci/* Verifies rbuffer against random sequence */
1548c2ecf20Sopenharmony_cistatic int verify_page(int log)
1558c2ecf20Sopenharmony_ci{
1568c2ecf20Sopenharmony_ci	unsigned i, errs = 0;
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	if (log)
1598c2ecf20Sopenharmony_ci		pr_info("verify_page\n");
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	for (i = 0; i < mtd->writesize; i++) {
1628c2ecf20Sopenharmony_ci		if (rbuffer[i] != hash(i+seed)) {
1638c2ecf20Sopenharmony_ci			pr_err("Error: page offset %u, expected %02x, got %02x\n",
1648c2ecf20Sopenharmony_ci				i, hash(i+seed), rbuffer[i]);
1658c2ecf20Sopenharmony_ci			errs++;
1668c2ecf20Sopenharmony_ci		}
1678c2ecf20Sopenharmony_ci	}
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	if (errs)
1708c2ecf20Sopenharmony_ci		return -EIO;
1718c2ecf20Sopenharmony_ci	else
1728c2ecf20Sopenharmony_ci		return 0;
1738c2ecf20Sopenharmony_ci}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci#define CBIT(v, n) ((v) & (1 << (n)))
1768c2ecf20Sopenharmony_ci#define BCLR(v, n) ((v) = (v) & ~(1 << (n)))
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci/* Finds the first '1' bit in wbuffer starting at offset 'byte'
1798c2ecf20Sopenharmony_ci * and sets it to '0'. */
1808c2ecf20Sopenharmony_cistatic int insert_biterror(unsigned byte)
1818c2ecf20Sopenharmony_ci{
1828c2ecf20Sopenharmony_ci	int bit;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	while (byte < mtd->writesize) {
1858c2ecf20Sopenharmony_ci		for (bit = 7; bit >= 0; bit--) {
1868c2ecf20Sopenharmony_ci			if (CBIT(wbuffer[byte], bit)) {
1878c2ecf20Sopenharmony_ci				BCLR(wbuffer[byte], bit);
1888c2ecf20Sopenharmony_ci				pr_info("Inserted biterror @ %u/%u\n", byte, bit);
1898c2ecf20Sopenharmony_ci				return 0;
1908c2ecf20Sopenharmony_ci			}
1918c2ecf20Sopenharmony_ci		}
1928c2ecf20Sopenharmony_ci		byte++;
1938c2ecf20Sopenharmony_ci	}
1948c2ecf20Sopenharmony_ci	pr_err("biterror: Failed to find a '1' bit\n");
1958c2ecf20Sopenharmony_ci	return -EIO;
1968c2ecf20Sopenharmony_ci}
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci/* Writes 'random' data to page and then introduces deliberate bit
1998c2ecf20Sopenharmony_ci * errors into the page, while verifying each step. */
2008c2ecf20Sopenharmony_cistatic int incremental_errors_test(void)
2018c2ecf20Sopenharmony_ci{
2028c2ecf20Sopenharmony_ci	int err = 0;
2038c2ecf20Sopenharmony_ci	unsigned i;
2048c2ecf20Sopenharmony_ci	unsigned errs_per_subpage = 0;
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	pr_info("incremental biterrors test\n");
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	for (i = 0; i < mtd->writesize; i++)
2098c2ecf20Sopenharmony_ci		wbuffer[i] = hash(i+seed);
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	err = write_page(1);
2128c2ecf20Sopenharmony_ci	if (err)
2138c2ecf20Sopenharmony_ci		goto exit;
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci	while (1) {
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci		err = rewrite_page(1);
2188c2ecf20Sopenharmony_ci		if (err)
2198c2ecf20Sopenharmony_ci			goto exit;
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci		err = read_page(1);
2228c2ecf20Sopenharmony_ci		if (err > 0)
2238c2ecf20Sopenharmony_ci			pr_info("Read reported %d corrected bit errors\n", err);
2248c2ecf20Sopenharmony_ci		if (err < 0) {
2258c2ecf20Sopenharmony_ci			pr_err("After %d biterrors per subpage, read reported error %d\n",
2268c2ecf20Sopenharmony_ci				errs_per_subpage, err);
2278c2ecf20Sopenharmony_ci			err = 0;
2288c2ecf20Sopenharmony_ci			goto exit;
2298c2ecf20Sopenharmony_ci		}
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci		err = verify_page(1);
2328c2ecf20Sopenharmony_ci		if (err) {
2338c2ecf20Sopenharmony_ci			pr_err("ECC failure, read data is incorrect despite read success\n");
2348c2ecf20Sopenharmony_ci			goto exit;
2358c2ecf20Sopenharmony_ci		}
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci		pr_info("Successfully corrected %d bit errors per subpage\n",
2388c2ecf20Sopenharmony_ci			errs_per_subpage);
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci		for (i = 0; i < subcount; i++) {
2418c2ecf20Sopenharmony_ci			err = insert_biterror(i * subsize);
2428c2ecf20Sopenharmony_ci			if (err < 0)
2438c2ecf20Sopenharmony_ci				goto exit;
2448c2ecf20Sopenharmony_ci		}
2458c2ecf20Sopenharmony_ci		errs_per_subpage++;
2468c2ecf20Sopenharmony_ci	}
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ciexit:
2498c2ecf20Sopenharmony_ci	return err;
2508c2ecf20Sopenharmony_ci}
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci/* Writes 'random' data to page and then re-writes that same data repeatedly.
2548c2ecf20Sopenharmony_ci   This eventually develops bit errors (bits written as '1' will slowly become
2558c2ecf20Sopenharmony_ci   '0'), which are corrected as far as the ECC is capable of. */
2568c2ecf20Sopenharmony_cistatic int overwrite_test(void)
2578c2ecf20Sopenharmony_ci{
2588c2ecf20Sopenharmony_ci	int err = 0;
2598c2ecf20Sopenharmony_ci	unsigned i;
2608c2ecf20Sopenharmony_ci	unsigned max_corrected = 0;
2618c2ecf20Sopenharmony_ci	unsigned opno = 0;
2628c2ecf20Sopenharmony_ci	/* We don't expect more than this many correctable bit errors per
2638c2ecf20Sopenharmony_ci	 * page. */
2648c2ecf20Sopenharmony_ci	#define MAXBITS 512
2658c2ecf20Sopenharmony_ci	static unsigned bitstats[MAXBITS]; /* bit error histogram. */
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci	memset(bitstats, 0, sizeof(bitstats));
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	pr_info("overwrite biterrors test\n");
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci	for (i = 0; i < mtd->writesize; i++)
2728c2ecf20Sopenharmony_ci		wbuffer[i] = hash(i+seed);
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_ci	err = write_page(1);
2758c2ecf20Sopenharmony_ci	if (err)
2768c2ecf20Sopenharmony_ci		goto exit;
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_ci	while (opno < max_overwrite) {
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci		err = write_page(0);
2818c2ecf20Sopenharmony_ci		if (err)
2828c2ecf20Sopenharmony_ci			break;
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ci		err = read_page(0);
2858c2ecf20Sopenharmony_ci		if (err >= 0) {
2868c2ecf20Sopenharmony_ci			if (err >= MAXBITS) {
2878c2ecf20Sopenharmony_ci				pr_info("Implausible number of bit errors corrected\n");
2888c2ecf20Sopenharmony_ci				err = -EIO;
2898c2ecf20Sopenharmony_ci				break;
2908c2ecf20Sopenharmony_ci			}
2918c2ecf20Sopenharmony_ci			bitstats[err]++;
2928c2ecf20Sopenharmony_ci			if (err > max_corrected) {
2938c2ecf20Sopenharmony_ci				max_corrected = err;
2948c2ecf20Sopenharmony_ci				pr_info("Read reported %d corrected bit errors\n",
2958c2ecf20Sopenharmony_ci					err);
2968c2ecf20Sopenharmony_ci			}
2978c2ecf20Sopenharmony_ci		} else { /* err < 0 */
2988c2ecf20Sopenharmony_ci			pr_info("Read reported error %d\n", err);
2998c2ecf20Sopenharmony_ci			err = 0;
3008c2ecf20Sopenharmony_ci			break;
3018c2ecf20Sopenharmony_ci		}
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_ci		err = verify_page(0);
3048c2ecf20Sopenharmony_ci		if (err) {
3058c2ecf20Sopenharmony_ci			bitstats[max_corrected] = opno;
3068c2ecf20Sopenharmony_ci			pr_info("ECC failure, read data is incorrect despite read success\n");
3078c2ecf20Sopenharmony_ci			break;
3088c2ecf20Sopenharmony_ci		}
3098c2ecf20Sopenharmony_ci
3108c2ecf20Sopenharmony_ci		err = mtdtest_relax();
3118c2ecf20Sopenharmony_ci		if (err)
3128c2ecf20Sopenharmony_ci			break;
3138c2ecf20Sopenharmony_ci
3148c2ecf20Sopenharmony_ci		opno++;
3158c2ecf20Sopenharmony_ci	}
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci	/* At this point bitstats[0] contains the number of ops with no bit
3188c2ecf20Sopenharmony_ci	 * errors, bitstats[1] the number of ops with 1 bit error, etc. */
3198c2ecf20Sopenharmony_ci	pr_info("Bit error histogram (%d operations total):\n", opno);
3208c2ecf20Sopenharmony_ci	for (i = 0; i < max_corrected; i++)
3218c2ecf20Sopenharmony_ci		pr_info("Page reads with %3d corrected bit errors: %d\n",
3228c2ecf20Sopenharmony_ci			i, bitstats[i]);
3238c2ecf20Sopenharmony_ci
3248c2ecf20Sopenharmony_ciexit:
3258c2ecf20Sopenharmony_ci	return err;
3268c2ecf20Sopenharmony_ci}
3278c2ecf20Sopenharmony_ci
3288c2ecf20Sopenharmony_cistatic int __init mtd_nandbiterrs_init(void)
3298c2ecf20Sopenharmony_ci{
3308c2ecf20Sopenharmony_ci	int err = 0;
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci	printk("\n");
3338c2ecf20Sopenharmony_ci	printk(KERN_INFO "==================================================\n");
3348c2ecf20Sopenharmony_ci	pr_info("MTD device: %d\n", dev);
3358c2ecf20Sopenharmony_ci
3368c2ecf20Sopenharmony_ci	mtd = get_mtd_device(NULL, dev);
3378c2ecf20Sopenharmony_ci	if (IS_ERR(mtd)) {
3388c2ecf20Sopenharmony_ci		err = PTR_ERR(mtd);
3398c2ecf20Sopenharmony_ci		pr_err("error: cannot get MTD device\n");
3408c2ecf20Sopenharmony_ci		goto exit_mtddev;
3418c2ecf20Sopenharmony_ci	}
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci	if (!mtd_type_is_nand(mtd)) {
3448c2ecf20Sopenharmony_ci		pr_info("this test requires NAND flash\n");
3458c2ecf20Sopenharmony_ci		err = -ENODEV;
3468c2ecf20Sopenharmony_ci		goto exit_nand;
3478c2ecf20Sopenharmony_ci	}
3488c2ecf20Sopenharmony_ci
3498c2ecf20Sopenharmony_ci	pr_info("MTD device size %llu, eraseblock=%u, page=%u, oob=%u\n",
3508c2ecf20Sopenharmony_ci		(unsigned long long)mtd->size, mtd->erasesize,
3518c2ecf20Sopenharmony_ci		mtd->writesize, mtd->oobsize);
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_ci	subsize  = mtd->writesize >> mtd->subpage_sft;
3548c2ecf20Sopenharmony_ci	subcount = mtd->writesize / subsize;
3558c2ecf20Sopenharmony_ci
3568c2ecf20Sopenharmony_ci	pr_info("Device uses %d subpages of %d bytes\n", subcount, subsize);
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_ci	offset     = (loff_t)page_offset * mtd->writesize;
3598c2ecf20Sopenharmony_ci	eraseblock = mtd_div_by_eb(offset, mtd);
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	pr_info("Using page=%u, offset=%llu, eraseblock=%u\n",
3628c2ecf20Sopenharmony_ci		page_offset, offset, eraseblock);
3638c2ecf20Sopenharmony_ci
3648c2ecf20Sopenharmony_ci	wbuffer = kmalloc(mtd->writesize, GFP_KERNEL);
3658c2ecf20Sopenharmony_ci	if (!wbuffer) {
3668c2ecf20Sopenharmony_ci		err = -ENOMEM;
3678c2ecf20Sopenharmony_ci		goto exit_wbuffer;
3688c2ecf20Sopenharmony_ci	}
3698c2ecf20Sopenharmony_ci
3708c2ecf20Sopenharmony_ci	rbuffer = kmalloc(mtd->writesize, GFP_KERNEL);
3718c2ecf20Sopenharmony_ci	if (!rbuffer) {
3728c2ecf20Sopenharmony_ci		err = -ENOMEM;
3738c2ecf20Sopenharmony_ci		goto exit_rbuffer;
3748c2ecf20Sopenharmony_ci	}
3758c2ecf20Sopenharmony_ci
3768c2ecf20Sopenharmony_ci	err = mtdtest_erase_eraseblock(mtd, eraseblock);
3778c2ecf20Sopenharmony_ci	if (err)
3788c2ecf20Sopenharmony_ci		goto exit_error;
3798c2ecf20Sopenharmony_ci
3808c2ecf20Sopenharmony_ci	if (mode == 0)
3818c2ecf20Sopenharmony_ci		err = incremental_errors_test();
3828c2ecf20Sopenharmony_ci	else
3838c2ecf20Sopenharmony_ci		err = overwrite_test();
3848c2ecf20Sopenharmony_ci
3858c2ecf20Sopenharmony_ci	if (err)
3868c2ecf20Sopenharmony_ci		goto exit_error;
3878c2ecf20Sopenharmony_ci
3888c2ecf20Sopenharmony_ci	/* We leave the block un-erased in case of test failure. */
3898c2ecf20Sopenharmony_ci	err = mtdtest_erase_eraseblock(mtd, eraseblock);
3908c2ecf20Sopenharmony_ci	if (err)
3918c2ecf20Sopenharmony_ci		goto exit_error;
3928c2ecf20Sopenharmony_ci
3938c2ecf20Sopenharmony_ci	err = -EIO;
3948c2ecf20Sopenharmony_ci	pr_info("finished successfully.\n");
3958c2ecf20Sopenharmony_ci	printk(KERN_INFO "==================================================\n");
3968c2ecf20Sopenharmony_ci
3978c2ecf20Sopenharmony_ciexit_error:
3988c2ecf20Sopenharmony_ci	kfree(rbuffer);
3998c2ecf20Sopenharmony_ciexit_rbuffer:
4008c2ecf20Sopenharmony_ci	kfree(wbuffer);
4018c2ecf20Sopenharmony_ciexit_wbuffer:
4028c2ecf20Sopenharmony_ci	/* Nothing */
4038c2ecf20Sopenharmony_ciexit_nand:
4048c2ecf20Sopenharmony_ci	put_mtd_device(mtd);
4058c2ecf20Sopenharmony_ciexit_mtddev:
4068c2ecf20Sopenharmony_ci	return err;
4078c2ecf20Sopenharmony_ci}
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_cistatic void __exit mtd_nandbiterrs_exit(void)
4108c2ecf20Sopenharmony_ci{
4118c2ecf20Sopenharmony_ci	return;
4128c2ecf20Sopenharmony_ci}
4138c2ecf20Sopenharmony_ci
4148c2ecf20Sopenharmony_cimodule_init(mtd_nandbiterrs_init);
4158c2ecf20Sopenharmony_cimodule_exit(mtd_nandbiterrs_exit);
4168c2ecf20Sopenharmony_ci
4178c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("NAND bit error recovery test");
4188c2ecf20Sopenharmony_ciMODULE_AUTHOR("Iwo Mergler");
4198c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
420