/**
 * @file
 * @brief Provide Generic GPMC NAND implementation for OMAP platforms
 *
 * FileName: arch/arm/mach-omap/gpmc_nand.c
 *
 * GPMC has a NAND controller inbuilt. This provides a generic implementation
 * for board files to register a nand device. drivers/nand/nand_base.c takes
 * care of identifing the type of device, size etc.
 *
 * A typical device registration is as follows:
 *
 * @code
 * static struct device_d my_nand_device = {
 *	.name = "gpmc_nand",
 *	.id = some identifier you need to show.. e.g. "gpmc_nand0"
 *	.map_base = GPMC base address
 *	.size = GPMC address map size.
 *	.platform_data = platform data - required - explained below
 * };
 * platform data required:
 * static struct gpmc_nand_platform_data nand_plat = {
 *	.cs = give the chip select of the device
 *	.device_width = what is the width of the device 8 or 16?
 *	.max_timeout = delay desired for operation
 *	.wait_mon_pin = do you use wait monitoring? if so wait pin
 *	.plat_options = platform options.
 *		NAND_HWECC_ENABLE/DISABLE - hw ecc enable/disable
 *		NAND_WAITPOL_LOW/HIGH - wait pin polarity
 *	.oob = if you would like to replace oob with a custom OOB.
 *	.nand_setup  = if you would like a special setup function to be called
 *	.priv = any params you'd like to save(e.g. like nand_setup to use)
 *};
 * then in your code, you'd device_register(&my_nand_device);
 * @endcode
 *
 * Note:
 * @li Enable CONFIG_NAND_OMAP_GPMC_HWECC in menuconfig to get H/w ECC support
 * @li You may choose to register two "devices" for the same CS to get BOTH
 * hwecc and swecc devices.
 * @li You can choose to have your own OOB definition for compliance with ROM
 * code organization - only if you dont want to use NAND's default oob layout.
 * see GPMC_NAND_ECC_LP_x8_LAYOUT etc..
 *
 * @see gpmc_nand_platform_data
 * @warning Remember to initialize GPMC before initializing the nand dev.
 */
/*
 * (C) Copyright 2008
 * Texas Instruments, <www.ti.com>
 * Nishanth Menon <x0nishan@ti.com>
 *
 * Based on:
 * drivers/mtd/nand/omap2.c from linux kernel
 *
 * Copyright (c) 2004 Texas Instruments, Jian Zhang <jzhang@ti.com>
 * Copyright (c) 2004 Micron Technology Inc.
 * Copyright (c) 2004 David Brownell
 *
 * 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.
 */

#include <common.h>
#include <errno.h>
#include <init.h>
#include <driver.h>
#include <malloc.h>
#include <clock.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/nand_ecc.h>
#include <asm/io.h>
#include <mach/silicon.h>
#include <mach/gpmc.h>
#include <mach/gpmc_nand.h>

/* Enable me to get tons of debug messages -for use without jtag */
#if 0
#define gpmcnand_dbg(FORMAT, ARGS...) fprintf(stdout,\
		"gpmc_nand:%s:%d:Entry:"FORMAT"\n",\
		__func__, __LINE__, ARGS)
#else
#define gpmcnand_dbg(FORMAT, ARGS...)
#endif
#define gpmcnand_err(ARGS...) fprintf(stderr, "omapnand: " ARGS);

/** internal structure maintained for nand information */
struct gpmc_nand_info {
	struct nand_hw_control controller;
	struct device_d *pdev;
	struct gpmc_nand_platform_data *pdata;
	struct nand_chip nand;
	struct mtd_info minfo;
	int gpmc_cs;
	void *gpmc_command;
	void *gpmc_address;
	void *gpmc_data;
	unsigned long gpmc_base;
	unsigned char wait_mon_mask;
	uint64_t timeout;
	unsigned inuse:1;
	unsigned wait_pol:1;
#ifdef CONFIG_NAND_OMAP_GPMC_HWECC
	unsigned char ecc_parity_pairs;
	unsigned int ecc_config;
#endif
};

/**
 * @brief calls the platform specific dev_ready functionds
 *
 * @param mtd - mtd info structure
 *
 * @return
 */
static int omap_dev_ready(struct mtd_info *mtd)
{
	struct nand_chip *nand = (struct nand_chip *)(mtd->priv);
	struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv);
	uint64_t start = get_time_ns();
	unsigned long comp;

	gpmcnand_dbg("mtd=%x", (unsigned int)mtd);
	/* What do we mean by assert and de-assert? */
	comp = (oinfo->wait_pol == NAND_WAITPOL_HIGH) ?
	    oinfo->wait_mon_mask : 0x0;
	while (1) {
		/* Breakout condition */
		if (is_timeout(start, oinfo->timeout)) {
			gpmcnand_err("timedout\n");
			return -ETIMEDOUT;
		}
		/* if the wait is released, we are good to go */
		if (comp ==
		    (readl(oinfo->gpmc_base + GPMC_STATUS) &&
		     oinfo->wait_mon_mask))
			break;
	}
	return 0;
}

/**
 * @brief This function will enable or disable the Write Protect feature on
 * NAND device. GPMC has a single WP bit for all CS devices..
 *
 * @param oinfo  omap nand info
 * @param mode 0-disable else enable
 *
 * @return none
 */
static void gpmc_nand_wp(struct gpmc_nand_info *oinfo, int mode)
{
	unsigned long config = readl(oinfo->gpmc_base + GPMC_CFG);

	gpmcnand_dbg("mode=%x", mode);
	if (mode)
		config &= ~(NAND_WP_BIT);	/* WP is ON */
	else
		config |= (NAND_WP_BIT);	/* WP is OFF */

	writel(config, oinfo->gpmc_base + GPMC_CFG);
}

/**
 * @brief respond to hw event change request
 *
 * MTD layer uses NAND_CTRL_CLE etc to control selection of the latch
 * we hoodwink by changing the R and W registers according to the state
 * we are requested.
 *
 * @param mtd - mtd info structure
 * @param cmd command mtd layer is requesting
 *
 * @return none
 */
static void omap_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl)
{
	struct nand_chip *nand = (struct nand_chip *)(mtd->priv);
	struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv);
	gpmcnand_dbg("mtd=%x nand=%x cmd=%x ctrl = %x", (unsigned int)mtd, nand,
		  cmd, ctrl);
	switch (ctrl) {
	case NAND_CTRL_CHANGE | NAND_CTRL_CLE:
		nand->IO_ADDR_W = oinfo->gpmc_command;
		nand->IO_ADDR_R = oinfo->gpmc_data;
		break;

	case NAND_CTRL_CHANGE | NAND_CTRL_ALE:
		nand->IO_ADDR_W = oinfo->gpmc_address;
		nand->IO_ADDR_R = oinfo->gpmc_data;
		break;

	case NAND_CTRL_CHANGE | NAND_NCE:
		nand->IO_ADDR_W = oinfo->gpmc_data;
		nand->IO_ADDR_R = oinfo->gpmc_data;
		break;
	}

	if (cmd != NAND_CMD_NONE)
		writeb(cmd, nand->IO_ADDR_W);
	return;
}

#ifdef CONFIG_NAND_OMAP_GPMC_HWECC

/**
 * @brief This function will generate true ECC value, which can be used
 * when correcting data read from NAND flash memory core
 *
 * @param ecc_buf buffer to store ecc code
 *
 * @return re-formatted ECC value
 */
static unsigned int gen_true_ecc(u8 *ecc_buf)
{
	gpmcnand_dbg("ecc_buf=%x 1, 2 3 = %x %x %x", (unsigned int)ecc_buf,
		  ecc_buf[0], ecc_buf[1], ecc_buf[2]);
	return ecc_buf[0] | (ecc_buf[1] << 16) | ((ecc_buf[2] & 0xF0) << 20) |
	    ((ecc_buf[2] & 0x0F) << 8);
}

/**
 * @brief Compares the ecc read from nand spare area with ECC
 * registers values and corrects one bit error if it has occured
 * Further details can be had from OMAP TRM and the following selected links:
 * http://en.wikipedia.org/wiki/Hamming_code
 * http://www.cs.utexas.edu/users/plaxton/c/337/05f/slides/ErrorCorrection-4.pdf
 *
 * @param mtd - mtd info structure
 * @param dat  page data
 * @param read_ecc ecc readback
 * @param calc_ecc calculated ecc (from reg)
 *
 * @return 0 if data is OK or corrected, else returns -1
 */
static int omap_correct_data(struct mtd_info *mtd, uint8_t *dat,
			     uint8_t *read_ecc, uint8_t *calc_ecc)
{
	unsigned int orig_ecc, new_ecc, res, hm;
	unsigned short parity_bits, byte;
	unsigned char bit;
	struct nand_chip *nand = (struct nand_chip *)(mtd->priv);
	struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv);

	gpmcnand_dbg("mtd=%x dat=%x read_ecc=%x calc_ecc=%x", (unsigned int)mtd,
		  (unsigned int)dat, (unsigned int)read_ecc,
		  (unsigned int)calc_ecc);

	/* Regenerate the orginal ECC */
	orig_ecc = gen_true_ecc(read_ecc);
	new_ecc = gen_true_ecc(calc_ecc);
	/* Get the XOR of real ecc */
	res = orig_ecc ^ new_ecc;
	if (res) {
		/* Get the hamming width */
		hm = hweight32(res);
		/* Single bit errors can be corrected! */
		if (hm == oinfo->ecc_parity_pairs) {
			/* Correctable data! */
			parity_bits = res >> 16;
			bit = (parity_bits & 0x7);
			byte = (parity_bits >> 3) & 0x1FF;
			/* Flip the bit to correct */
			dat[byte] ^= (0x1 << bit);

		} else if (hm == 1) {
			gpmcnand_err("Ecc is wrong\n");
			/* ECC itself is corrupted */
			return 2;
		} else {
			gpmcnand_err("bad compare! failed\n");
			/* detected 2 bit error */
			return -1;
		}
	}
	return 0;
}

/**
 * @brief Using noninverted ECC can be considered ugly since writing a blank
 * page ie. padding will clear the ECC bytes. This is no problem as long
 * nobody is trying to write data on the seemingly unused page. Reading
 * an erased page will produce an ECC mismatch between generated and read
 * ECC bytes that has to be dealt with separately.
 *
 * @param mtd - mtd info structure
 * @param dat data being written
 * @param ecc_code ecc code returned back to nand layer
 *
 * @return 0
 */
static int omap_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat,
			      uint8_t *ecc_code)
{
	struct nand_chip *nand = (struct nand_chip *)(mtd->priv);
	struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv);
	unsigned int val;
	gpmcnand_dbg("mtd=%x dat=%x ecc_code=%x", (unsigned int)mtd,
		  (unsigned int)dat, (unsigned int)ecc_code);
	debug("ecc 0 1 2 = %x %x %x", ecc_code[0], ecc_code[1], ecc_code[2]);

	/* Since we smartly tell mtd driver to use eccsize of 512, only
	 * ECC Reg1 will be used.. we just read that */
	val = readl(oinfo->gpmc_base + GPMC_ECC1_RESULT);
	ecc_code[0] = val & 0xFF;
	ecc_code[1] = (val >> 16) & 0xFF;
	ecc_code[2] = ((val >> 8) & 0x0f) | ((val >> 20) & 0xf0);

	/* Stop reading anymore ECC vals and clear old results
	 * enable will be called if more reads are required */
	writel(0x000, oinfo->gpmc_base + GPMC_ECC_CONFIG);
	return 0;
}

/*
 * omap_enable_ecc - This function enables the hardware ecc functionality
 * @param mtd - mtd info structure
 * @param mode - Read/Write mode
 */
static void omap_enable_hwecc(struct mtd_info *mtd, int mode)
{
	struct nand_chip *nand = (struct nand_chip *)(mtd->priv);
	struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv);
	gpmcnand_dbg("mtd=%x mode=%x", (unsigned int)mtd, mode);
	switch (mode) {
	case NAND_ECC_READ:
	case NAND_ECC_WRITE:
		/* Clear the ecc result registers
		 * select ecc reg as 1
		 */
		writel(0x101, oinfo->gpmc_base + GPMC_ECC_CONTROL);
		/* Size 0 = 0xFF, Size1 is 0xFF - both are 512 bytes
		 * tell all regs to generate size0 sized regs
		 * we just have a single ECC engine for all CS
		 */
		writel(0x3FCFF000, oinfo->gpmc_base +
				GPMC_ECC_SIZE_CONFIG);
		writel(oinfo->ecc_config, oinfo->gpmc_base +
				GPMC_ECC_CONFIG);
		break;
	default:
		gpmcnand_err("Error: Unrecognized Mode[%d]!\n", mode);
		break;
	}
}
#endif				/* CONFIG_NAND_OMAP_GPMC_HWECC */

/**
 * @brief nand device probe.
 *
 * @param pdev -matching device
 *
 * @return -failure reason or give 0
 */
static int gpmc_nand_probe(struct device_d *pdev)
{
	struct gpmc_nand_info *oinfo;
	struct gpmc_nand_platform_data *pdata;
	struct nand_chip *nand;
	struct mtd_info *minfo;
	unsigned long cs_base;
	int err;

	gpmcnand_dbg("pdev=%x", (unsigned int)pdev);
	pdata = (struct gpmc_nand_platform_data *)pdev->platform_data;
	if (pdata == NULL) {
		gpmcnand_err("platform data missing\n");
		return -ENODEV;
	}

	oinfo = calloc(1, sizeof(struct gpmc_nand_info));
	if (!oinfo) {
		gpmcnand_err("oinfo alloc failed!\n");
		return -ENOMEM;
	}

	/* fill up my data structures */
	oinfo->pdev = pdev;
	oinfo->pdata = pdata;
	pdev->platform_data = (void *)oinfo;

	nand = &oinfo->nand;
	nand->priv = (void *)oinfo;

	minfo = &oinfo->minfo;
	minfo->priv = (void *)nand;

	if (pdata->cs >= GPMC_NUM_CS) {
		gpmcnand_err("Invalid CS!\n");
		err = -EINVAL;
		goto out_release_mem;
	}
	/* Setup register specific data */
	oinfo->gpmc_cs = pdata->cs;
	oinfo->gpmc_base = pdev->map_base;
	cs_base = oinfo->gpmc_base + GPMC_CONFIG1_0 +
		(pdata->cs * GPMC_CONFIG_CS_SIZE);
	oinfo->gpmc_command = (void *)(cs_base + GPMC_CS_NAND_COMMAND);
	oinfo->gpmc_address = (void *)(cs_base + GPMC_CS_NAND_ADDRESS);
	oinfo->gpmc_data = (void *)(cs_base + GPMC_CS_NAND_DATA);
	oinfo->timeout = pdata->max_timeout;
	debug("GPMC Details:\n"
		"GPMC BASE=%x\n"
		"CMD=%x\n"
		"ADDRESS=%x\n"
		"DATA=%x\n"
		"CS_BASE=%x\n",
		oinfo->gpmc_base, oinfo->gpmc_command,
		oinfo->gpmc_address, oinfo->gpmc_data, cs_base);

	/* If we are 16 bit dev, our gpmc config tells us that */
	if ((readl(cs_base) & 0x3000) == 0x1000) {
		debug("16 bit dev\n");
		nand->options |= NAND_BUSWIDTH_16;
	}

	/* Same data register for in and out */
	nand->IO_ADDR_W = nand->IO_ADDR_R = (void *)oinfo->gpmc_data;
	/*
	 * If RDY/BSY line is connected to OMAP then use the omap ready
	 * function and the generic nand_wait function which reads the
	 * status register after monitoring the RDY/BSY line. Otherwise
	 * use a standard chip delay which is slightly more than tR
	 * (AC Timing) of the NAND device and read the status register
	 * until you get a failure or success
	 */
	if (pdata->wait_mon_pin > 4) {
		gpmcnand_err("Invalid wait monitoring pin\n");
		err = -EINVAL;
		goto out_release_mem;
	}
	if (pdata->wait_mon_pin) {
		/* Set up the wait monitoring mask
		 * This is GPMC_STATUS reg relevant */
		oinfo->wait_mon_mask = (0x1 << (pdata->wait_mon_pin - 1)) << 8;
		oinfo->wait_pol = (pdata->plat_options & NAND_WAITPOL_MASK);
		nand->dev_ready = omap_dev_ready;
		nand->chip_delay = 0;
	} else {
		/* use the default nand_wait function */
		nand->chip_delay = 50;
	}

	/* Use default cmdfunc */
	/* nand cmd control */
	nand->cmd_ctrl = omap_hwcontrol;

	/* Dont do a bbt scan at the start */
	nand->options |= NAND_SKIP_BBTSCAN;

	/* State my controller */
	nand->controller = &oinfo->controller;

	/* if a different placement scheme is requested */
	if (pdata->oob)
		nand->ecc.layout = pdata->oob;

#ifdef CONFIG_NAND_OMAP_GPMC_HWECC
	if (pdata->plat_options & NAND_HWECC_ENABLE) {
		/* Program how many columns we expect+
		 * enable the cs we want and enable the engine
		 */
		oinfo->ecc_config = (pdata->cs << 1) |
		    ((nand->options & NAND_BUSWIDTH_16) ?
		     (0x1 << 7) : 0x0) | 0x1;
		nand->ecc.hwctl = omap_enable_hwecc;
		nand->ecc.calculate = omap_calculate_ecc;
		nand->ecc.correct = omap_correct_data;
		nand->ecc.mode = NAND_ECC_HW;
		nand->ecc.size = 512;
		nand->ecc.bytes = 3;
		nand->ecc.steps = nand->ecc.layout->eccbytes / nand->ecc.bytes;
		oinfo->ecc_parity_pairs = 12;
	} else
#endif
		nand->ecc.mode = NAND_ECC_SOFT;

	/* All information is ready.. now lets call setup, if present */
	if (pdata->nand_setup) {
		err = pdata->nand_setup(pdata);
		if (err) {
			gpmcnand_err("pdataform setup failed\n");
			goto out_release_mem;
		}
	}
	/* Remove write protection */
	gpmc_nand_wp(oinfo, 0);

	/* we do not know what state of device we have is, so
	 * Send a reset to the device
	 * 8 bit write will work on 16 and 8 bit devices
	 */
	writeb(NAND_CMD_RESET, oinfo->gpmc_command);
	mdelay(1);

	/* In normal mode, we scan to get just the device
	 * presence and then to get the device geometry
	 */
	if (nand_scan(minfo, 1)) {
		gpmcnand_err("device scan failed\n");
		err = -ENXIO;
		goto out_release_mem;
	}

	/* We are all set to register with the system now! */
	err = add_mtd_device(minfo);
	if (err) {
		gpmcnand_err("device registration failed\n");
		goto out_release_mem;
	}
	return 0;

out_release_mem:
	if (oinfo)
		free(oinfo);

	gpmcnand_err("Failed!!\n");
	return err;
}

/** GMPC nand driver -> device registered by platforms */
static struct driver_d gpmc_nand_driver = {
	.name = "gpmc_nand",
	.probe = gpmc_nand_probe,
};

static int gpmc_nand_init(void)
{
	return register_driver(&gpmc_nand_driver);
}

device_initcall(gpmc_nand_init);
