/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <common.h>
#include <init.h>
#include <io.h>
#include <linux/mtd/nand.h>
#include <mach/imx-nand.h>
#include <mach/generic.h>
#include <mach/imx-regs.h>

static void __bare_init noinline imx_nandboot_wait_op_done(void *regs)
{
	u32 r;

	while (1) {
		r = readw(regs + NFC_V1_V2_CONFIG2);
		if (r & NFC_V1_V2_CONFIG2_INT)
			break;
	};

	r &= ~NFC_V1_V2_CONFIG2_INT;

	writew(r, regs + NFC_V1_V2_CONFIG2);
}

/*
 * This function issues the specified command to the NAND device and
 * waits for completion.
 *
 * @param       cmd     command for NAND Flash
 */
static void __bare_init imx_nandboot_send_cmd(void *regs, u16 cmd)
{
	writew(cmd, regs + NFC_V1_V2_FLASH_CMD);
	writew(NFC_CMD, regs + NFC_V1_V2_CONFIG2);

	imx_nandboot_wait_op_done(regs);
}

/*
 * This function sends an address (or partial address) to the
 * NAND device.  The address is used to select the source/destination for
 * a NAND command.
 *
 * @param       addr    address to be written to NFC.
 * @param       islast  True if this is the last address cycle for command
 */
static void __bare_init noinline imx_nandboot_send_addr(void *regs, u16 addr)
{
	writew(addr, regs + NFC_V1_V2_FLASH_ADDR);
	writew(NFC_ADDR, regs + NFC_V1_V2_CONFIG2);

	/* Wait for operation to complete */
	imx_nandboot_wait_op_done(regs);
}

static void __bare_init imx_nandboot_nfc_addr(void *regs, u32 offs, int pagesize_2k)
{
	imx_nandboot_send_addr(regs, offs & 0xff);

	if (pagesize_2k) {
		imx_nandboot_send_addr(regs, offs & 0xff);
		imx_nandboot_send_addr(regs, (offs >> 11) & 0xff);
		imx_nandboot_send_addr(regs, (offs >> 19) & 0xff);
		imx_nandboot_send_addr(regs, (offs >> 27) & 0xff);
		imx_nandboot_send_cmd(regs, NAND_CMD_READSTART);
	} else {
		imx_nandboot_send_addr(regs, (offs >> 9) & 0xff);
		imx_nandboot_send_addr(regs, (offs >> 17) & 0xff);
		imx_nandboot_send_addr(regs, (offs >> 25) & 0xff);
	}
}

static void __bare_init imx_nandboot_send_page(void *regs,
		unsigned int ops, int pagesize_2k)
{
	int bufs, i;

	if (nfc_is_v1() && pagesize_2k)
		bufs = 4;
	else
		bufs = 1;

	for (i = 0; i < bufs; i++) {
		/* NANDFC buffer 0 is used for page read/write */
		writew(i, regs + NFC_V1_V2_BUF_ADDR);

		writew(ops, regs + NFC_V1_V2_CONFIG2);

		/* Wait for operation to complete */
		imx_nandboot_wait_op_done(regs);
	}
}

static void __bare_init __memcpy32(void *trg, const void *src, int size)
{
	int i;
	unsigned int *t = trg;
	unsigned const int *s = src;

	for (i = 0; i < (size >> 2); i++)
		*t++ = *s++;
}

static int __maybe_unused is_pagesize_2k(void)
{
#ifdef CONFIG_ARCH_IMX21
	if (readl(IMX_SYSTEM_CTL_BASE + 0x14) & (1 << 5))
		return 1;
	else
		return 0;
#endif
#ifdef CONFIG_ARCH_IMX27
	if (readl(IMX_SYSTEM_CTL_BASE + 0x14) & (1 << 5))
		return 1;
	else
		return 0;
#endif
#ifdef CONFIG_ARCH_IMX31
	if (readl(IMX_CCM_BASE + CCM_RCSR) & RCSR_NFMS)
		return 1;
	else
		return 0;
#endif
#if defined(CONFIG_ARCH_IMX35) || defined(CONFIG_ARCH_IMX25)
	if (readl(IMX_CCM_BASE + CCM_RCSR) & (1 << 8))
		return 1;
	else
		return 0;
#endif
}

void __bare_init imx_nand_load_image(void *dest, int size)
{
	u32 tmp, page, block, blocksize, pagesize;
	int pagesize_2k = 1;
	void *regs, *base, *spare0;

#if defined(CONFIG_NAND_IMX_BOOT_512)
	pagesize_2k = 0;
#elif defined(CONFIG_NAND_IMX_BOOT_2K)
	pagesize_2k = 1;
#else
	pagesize_2k = is_pagesize_2k();
#endif

	if (pagesize_2k) {
		pagesize = 2048;
		blocksize = 128 * 1024;
	} else {
		pagesize = 512;
		blocksize = 16 * 1024;
	}

	base = (void __iomem *)IMX_NFC_BASE;
	if (nfc_is_v21()) {
		regs = base + 0x1e00;
		spare0 = base + 0x1000;
	} else if (nfc_is_v1()) {
		regs = base + 0xe00;
		spare0 = base + 0x800;
	}

	imx_nandboot_send_cmd(regs, NAND_CMD_RESET);

	/* preset operation */
	/* Unlock the internal RAM Buffer */
	writew(0x2, regs + NFC_V1_V2_CONFIG);

	/* Unlock Block Command for given address range */
	writew(0x4, regs + NFC_V1_V2_WRPROT);

	tmp = readw(regs + NFC_V1_V2_CONFIG1);
	tmp |= NFC_V1_V2_CONFIG1_ECC_EN;
	if (nfc_is_v21())
		/* currently no support for 218 byte OOB with stronger ECC */
		tmp |= NFC_V2_CONFIG1_ECC_MODE_4;
	tmp &= ~(NFC_V1_V2_CONFIG1_SP_EN | NFC_V1_V2_CONFIG1_INT_MSK);
	writew(tmp, regs + NFC_V1_V2_CONFIG1);

	if (nfc_is_v21()) {
		if (pagesize_2k)
			writew(NFC_V2_SPAS_SPARESIZE(64), regs + NFC_V2_SPAS);
		else
			writew(NFC_V2_SPAS_SPARESIZE(16), regs + NFC_V2_SPAS);
	}

	block = page = 0;

	while (1) {
		page = 0;
		while (page * pagesize < blocksize) {
			debug("page: %d block: %d dest: %p src "
					"0x%08x\n",
					page, block, dest,
					block * blocksize +
					page * pagesize);

			imx_nandboot_send_cmd(regs, NAND_CMD_READ0);
			imx_nandboot_nfc_addr(regs, block * blocksize +
					page * pagesize, pagesize_2k);
			imx_nandboot_send_page(regs, NFC_OUTPUT, pagesize_2k);
			page++;

			if (pagesize_2k) {
				if ((readw(spare0) & 0xff) != 0xff)
					continue;
			} else {
				if ((readw(spare0 + 4) & 0xff00) != 0xff00)
					continue;
			}

			__memcpy32(dest, base, pagesize);
			dest += pagesize;
			size -= pagesize;

			if (size <= 0)
				return;
		}
		block++;
	}
}

#define CONFIG_NAND_IMX_BOOT_DEBUG
#ifdef CONFIG_NAND_IMX_BOOT_DEBUG
#include <command.h>

static int do_nand_boot_test(struct command *cmdtp, int argc, char *argv[])
{
	void *dest;
	int size;

	if (argc < 3)
		return COMMAND_ERROR_USAGE;

	dest = (void *)strtoul_suffix(argv[1], NULL, 0);
	size = strtoul_suffix(argv[2], NULL, 0);

	imx_nand_load_image(dest, size);

	return 0;
}

static const __maybe_unused char cmd_nand_boot_test_help[] =
"Usage: nand_boot_test <dest> <size>\n"
"This command loads the booloader from the NAND memory like the reset\n"
"routine does. Its intended for development tests only";

BAREBOX_CMD_START(nand_boot_test)
	.cmd		= do_nand_boot_test,
	.usage		= "load bootloader from NAND",
	BAREBOX_CMD_HELP(cmd_nand_boot_test_help)
BAREBOX_CMD_END
#endif
