/*
 *  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 <linux/module.h>
#include <linux/mtd/nand.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#include <linux/clk.h>
#include <linux/reset.h>
#include <mach/utils.h>

/* nand commands */
#define NAND_CMD_ALE		BIT(18)
#define NAND_CMD_CLE		BIT(19)
#define NAND_CMD_CS		0
#define NAND_CMD_RESET		0xff
#define NAND_CMD		(NAND_CMD_CS | NAND_CMD_CLE)
#define NAND_ADDR		(NAND_CMD_CS | NAND_CMD_ALE)
#define NAND_DATA		(NAND_CMD_CS)

static void oxnas_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl)
{
	struct nand_chip *this = mtd->priv;
	unsigned long nandaddr = (unsigned long) this->IO_ADDR_W;

	if (ctrl & NAND_CTRL_CHANGE) {
		nandaddr &= ~(NAND_CMD | NAND_ADDR);
		if (ctrl & NAND_CLE)
			nandaddr |= NAND_CMD;
		else if (ctrl & NAND_ALE)
			nandaddr |= NAND_ADDR;
		this->IO_ADDR_W = (void __iomem *) nandaddr;
	}

	if (cmd != NAND_CMD_NONE)
		writeb(cmd, (void __iomem *) nandaddr);
}

static int oxnas_nand_probe(struct platform_device *pdev)
{
	/* enable clock and release static block reset */
	struct clk *clk = of_clk_get(pdev->dev.of_node, 0);

	if (IS_ERR(clk))
		return PTR_ERR(clk);

	clk_prepare_enable(clk);
	device_reset(&pdev->dev);

	return 0;
}

/* allow users to override the partition in DT using the cmdline */
static const char * part_probes[] = { "cmdlinepart", "ofpart", NULL };

static struct platform_nand_data oxnas_nand_data = {
	.chip = {
		.nr_chips		= 1,
		.chip_delay		= 30,
		.part_probe_types	= part_probes,
	},
	.ctrl = {
		.probe		= oxnas_nand_probe,
		.cmd_ctrl	= oxnas_cmd_ctrl,
	}
};

/*
 * Try to find the node inside the DT. If it is available attach out
 * platform_nand_data
 */
static int __init oxnas_register_nand(void)
{
	struct device_node *node;
	struct platform_device *pdev;

	node = of_find_compatible_node(NULL, NULL, "plxtech,nand-nas782x");
	if (!node)
		return -ENOENT;
	pdev = of_find_device_by_node(node);
	if (!pdev)
		return -EINVAL;
	pdev->dev.platform_data = &oxnas_nand_data;
	of_node_put(node);
	return 0;
}

subsys_initcall(oxnas_register_nand);

static const struct of_device_id oxnas_nand_ids[] = {
	{ .compatible = "plxtech,nand-nas782x"},
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, oxnas_nand_ids);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ma Haijun");
MODULE_DESCRIPTION("NAND glue for Oxnas platforms");
MODULE_ALIAS("platform:oxnas_nand");
