/*
 * imx_nand_bbm.c - create a flash bad block table for i.MX NAND
 *
 * Copyright (c) 2013 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <common.h>
#include <command.h>
#include <fs.h>
#include <errno.h>
#include <getopt.h>
#include <fcntl.h>
#include <malloc.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/err.h>

/*
 * The i.MX NAND controller has the problem that it handles the
 * data in chunks of 512 bytes. It doesn't treat 2k NAND chips as
 * 2048 byte data + 64 OOB, but instead:
 *
 * 512b data + 16b OOB +
 * 512b data + 16b OOB +
 * 512b data + 16b OOB +
 * 512b data + 16b OOB
 *
 * This means that the factory provided bad block marker ends up
 * in the page data at offset 2000 instead of in the OOB data.
 *
 * To preserve the factory bad block information we take the following
 * strategy:
 *
 * - If the NAND driver detects that no flash BBT is present on 2k NAND
 *   chips it will not create one because it would do so based on the wrong
 *   BBM position
 * - This command is used to create a flash BBT then.
 *
 * From this point on we can forget about the BBMs and rely completely
 * on the flash BBT.
 *
 */
static int checkbad(struct mtd_info *mtd, loff_t ofs, void *__buf)
{
	int ret, retlen;
	uint8_t *buf = __buf;

	ret = mtd->read(mtd, ofs, mtd->writesize, &retlen, buf);
	if (ret < 0)
		return ret;

	if (buf[2000] != 0xff)
		return 1;

	return 0;
}

static void *create_bbt(struct mtd_info *mtd)
{
	struct nand_chip *chip = mtd->priv;
	int len, i, numblocks, ret;
	loff_t from = 0;
	void *buf;
	uint8_t *bbt;

	if ((chip->bbt_td && chip->bbt_td->pages[0] != -1) ||
			(chip->bbt_md && chip->bbt_md->pages[0] != -1)) {
		printf("Flash bbt already present\n");
		return ERR_PTR(-EEXIST);
	}

	len = mtd->size >> (chip->bbt_erase_shift + 2);

	/* Allocate memory (2bit per block) and clear the memory bad block table */
	bbt = kzalloc(len, GFP_KERNEL);
	if (!bbt)
		return ERR_PTR(-ENOMEM);

	buf = malloc(mtd->writesize);
	if (!buf) {
		ret = -ENOMEM;
		goto out2;
	}

	numblocks = mtd->size >> (chip->bbt_erase_shift - 1);

	for (i = 0; i < numblocks;) {
		ret = checkbad(mtd, from, buf);
		if (ret < 0)
			goto out1;

		if (ret) {
			bbt[i >> 3] |= 0x03 << (i & 0x6);
			printf("Bad eraseblock %d at 0x%08x\n", i >> 1,
					(unsigned int)from);
		}

		i += 2;
		from += (1 << chip->bbt_erase_shift);
	}

	return bbt;

out1:
	free(buf);
out2:
	free(bbt);

	return ERR_PTR(ret);
}

static int attach_bbt(struct mtd_info *mtd, void *bbt)
{
	struct nand_chip *chip = mtd->priv;

	chip->bbt_td->options |= NAND_BBT_WRITE | NAND_BBT_CREATE;
	chip->bbt_md->options |= NAND_BBT_WRITE | NAND_BBT_CREATE;
	free(chip->bbt);
	chip->bbt = bbt;

	return nand_update_bbt(mtd, 0);
}

static int do_imx_nand_bbm(int argc, char *argv[])
{
	int opt, ret;
	struct cdev *cdev;
	struct mtd_info *mtd;
	int yes = 0;
	void *bbt;

	while ((opt = getopt(argc, argv, "y")) > 0) {
		switch (opt) {
		case 'y':
			yes = 1;
			break;
		default:
			return COMMAND_ERROR_USAGE;
		}
	}

	cdev = cdev_open("nand0", O_RDWR);
	if (!cdev)
		return -ENOENT;

	mtd = cdev->mtd;
	if (!mtd)
		return -EINVAL;

	if (strcmp(mtd->name, "imx_nand")) {
		printf("This is not an i.MX nand but a %s\n", mtd->name);
		ret = -EINVAL;
		goto out;
	}

	switch (mtd->writesize) {
	case 512:
		printf("writesize is 512. This command is not needed\n");
		ret = 1;
		goto out;
	case 2048:
		break;
	default:
		printf("not implemented for writesize %d\n", mtd->writesize);
		ret = 1;
		goto out;
	}

	bbt = create_bbt(mtd);
	if (IS_ERR(bbt)) {
		ret = 1;
		goto out;
	}

	if (!yes) {
		int c;

		printf("create flash bbt (y/n)?");
		c = getc();
		if (c == 'y')
			yes = 1;
		printf("\n");
	}

	if (!yes) {
		free(bbt);
		ret = 1;

		goto out;
	}

	ret = attach_bbt(mtd, bbt);
	if (!ret)
		printf("bbt successfully added\n");
	else
		free(bbt);

out:
	cdev_close(cdev);

	return ret;
}

BAREBOX_CMD_START(imx_nand_bbm)
	.cmd		= do_imx_nand_bbm,
	BAREBOX_CMD_DESC("create BBT for i.MX NAND")
	BAREBOX_CMD_GROUP(CMD_GRP_HWMANIP)
BAREBOX_CMD_END
