/****************************************************************************
 * nand_test_dev.c
 *
 * Copyright (C) 2016 Allwinner.
 *
 *
 * This file is licensed under the terms of the GNU General Public
 * License version 2.  This program is licensed "as is" without any
 * warranty of any kind, whether express or implied.
 ****************************************************************************/
#define _NAND_TEST_DEV_C_

#include "nand_test_dev.h"
#include "nand_blk.h"
#include "nand_dev.h"
#include "nand_type.h"

struct nand_kobject *s_nand_kobj2;

#define NFTL_SCHEDULE_TIMEOUT (HZ >> 2)
#define NFTL_FLUSH_DATA_TIME 1

/****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
int nftl_test_thread(void *arg)
{
	struct _nftl_blk *nftl_blk = arg;
	unsigned long time;

	nftl_blk->time_flush = NFTL_FLUSH_DATA_TIME * HZ;
	nftl_blk->time_flush = HZ;

	while (!kthread_should_stop()) {

		mutex_lock(nftl_blk->blk_lock);

		if (nftl_get_zone_write_cache_nums(nftl_blk->nftl_zone) > 32) {
			time = jiffies;
			nftl_blk->flush_write_cache(nftl_blk, 8);
		}

		else {
			time = jiffies;
			if (time_after(time,
				       nftl_blk->time + nftl_blk->time_flush + HZ)) {
				nftl_blk->flush_write_cache(nftl_blk, 2);
			}
		}

		if (garbage_collect(nftl_blk->nftl_zone) != 0)
			nand_dbg_err("nftl_thread garbage_collect error!\n");

		if (do_prio_gc(nftl_blk->nftl_zone) != 0)
			nand_dbg_err("nftl_thread do_prio_gc error!\n");

		mutex_unlock(nftl_blk->blk_lock);
		set_current_state(TASK_INTERRUPTIBLE);
		schedule_timeout(NFTL_SCHEDULE_TIMEOUT);
	}

	nftl_blk->nftl_thread = (void *)NULL;
	return 0;
}

/****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
#ifdef __LINUX_NAND_SUPPORT_INT__

spinlock_t nand_int_test_lock;

static irqreturn_t nand_test_interrupt(int irq, void *channel)
{
	unsigned int no;
	unsigned long iflags;
	__u32 nand_index;

	spin_lock_irqsave(&nand_int_test_lock, iflags);

	no = *((unsigned int *)channel);

	do_nand_interrupt(no);

	spin_unlock_irqrestore(&nand_int_test_lock, iflags);

	return IRQ_HANDLED;
}

#endif

/****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
int nand_interrupt_init(void)
{
#ifdef __LINUX_NAND_SUPPORT_INT__

	static unsigned int channel0;

	spin_lock_init(&nand_int_test_lock);

	if ((nand_wait_rb_mode() != 0) || (nand_wait_dma_mode() != 0)) {
		if (request_irq(SUNXI_IRQ_NAND, nand_test_interrupt, IRQF_DISABLED,
				dev_name, &channel0)) {
			nand_dbg_err("nand interrupte ch0 irqno: %d register error\n",
				     SUNXI_IRQ_NAND);
			return -EAGAIN;
		}
	}
#endif
	return 0;
}

/****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
int build_all_nftl(struct _nand_info *nand_info)
{
	struct _nand_phy_partition *phy_partition;

	phy_partition = get_head_phy_partition_from_nand_info(nand_info);

	nftl_blk_head.nftl_blk_next = NULL;
	while (phy_partition != NULL) {
		if (add_nand_for_test(phy_partition) != 0) {
			nand_dbg_err("build_all_nftl fail!\n");
			return 1;
		}
		phy_partition = get_next_phy_partition(phy_partition);
	}

	return 0;
}

/****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
int add_nand_for_test(struct _nand_phy_partition *phy_partition)
{
	int i;
	__u32 cur_offset = 0;
	struct _nftl_blk *nftl_blk;
	struct _nand_dev *nand_dev;
	struct _nand_disk *disk;
	struct _nand_disk *head_disk;
	uint16 PartitionNO;

	PartitionNO = get_partitionNO(phy_partition);

	nftl_blk = kmalloc(sizeof(struct _nftl_blk), GFP_KERNEL);
	if (!nftl_blk)
		return 1;

	nftl_blk->nand = build_nand_partition(phy_partition);

	if (nftl_initialize(nftl_blk, PartitionNO)) {
		nand_dbg_err("nftl_initialize failed\n");
		return 1;
	}

	nftl_blk->blk_lock = kmalloc(sizeof(struct mutex), GFP_KERNEL);
	if (!nftl_blk->blk_lock)
		return 1;

	mutex_init(nftl_blk->blk_lock);

	nftl_blk->nftl_thread =
	    kthread_run(nftl_test_thread, nftl_blk, "%sd", "nftl");
	if (IS_ERR(nftl_blk->nftl_thread)) {
		nand_dbg_err("nftl_thread kthread_run fail!\n");
		return 1;
	}

	add_nftl_blk_list(&nftl_blk_head, nftl_blk);

	s_nand_kobj2 = kzalloc(sizeof(struct nand_kobject), GFP_KERNEL);
	if (!s_nand_kobj2)
		return 1;

	s_nand_kobj2->nftl_blk = nftl_blk;
	if (kobject_init_and_add(&s_nand_kobj2->kobj, &ktype, NULL, "nand_driver%d",
				 PartitionNO) != 0) {
		nand_dbg_err("init nand sysfs fail!\n");
		return 1;
	}

	return 0;
}

/****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
int nand_driver_test_init(void)
{
	int ret = 0;
	struct _nand_info *p_nand_info2;

	ret = nand_interrupt_init();
	if (ret != 0) {
		nand_dbg_err("nand_driver_test_init fail1!\n");
		return ret;
	}

	p_nand_info2 = nand_hw_init();
	if (p_nand_info2 == NULL) {
		nand_dbg_err("nand_driver_test_init fail2!\n");
		NandHwExit();
		return 1;
	}

	/*ret = nand_info_init(p_nand_info2, 0, 8, NULL);*/
	ret = nand_info_init(p_nand_info2, 0);
	if (ret != 0) {
		nand_dbg_err("nand_driver_test_init fail3!\n");
		return ret;
	}

	ret = build_all_nftl(p_nand_info2);
	if (ret != 0) {
		nand_dbg_err("nand_driver_test_init fail4!\n");
		nand_ftl_exit();
		return ret;
	}
	return 0;
}

/****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
int nand_driver_test_exit(void)
{
	nftl_flush_write_cache();
	nand_ftl_exit();
	NandHwExit();
	return 0;
}

/****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
int nftl_test_read(uint32 start_sector, uint32 len, unsigned char *buf)
{
	int ret = 0;
	struct _nftl_blk *nftl_blk;

	nftl_blk = nftl_blk_head.nftl_blk_next;

	if (len == 0)
		return 0;

	while (start_sector >= nftl_blk->nftl_logic_size) {
		start_sector -= nftl_blk->nftl_logic_size;
		nftl_blk = nftl_blk->nftl_blk_next;
		if (nftl_blk == NULL) {
			nand_dbg_err("[NE]parameter error %d,%d !\n",
				     start_sector, len);
			return 1;
		}
	}

	mutex_lock(nftl_blk->blk_lock);
	ret = nftl_blk->read_data(nftl_blk, start_sector, len, buf);
	mutex_unlock(nftl_blk->blk_lock);

	return ret;
}

/****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
uint32 nftl_test_write(uint32 start_sector, uint32 len, unsigned char *buf)
{
	int ret = 0;
	struct _nftl_blk *nftl_blk;

	nftl_blk = nftl_blk_head.nftl_blk_next;

	if (len == 0)
		return 0;

	while (start_sector >= nftl_blk->nftl_logic_size) {
		start_sector -= nftl_blk->nftl_logic_size;
		nftl_blk = nftl_blk->nftl_blk_next;
		if (nftl_blk == NULL) {
			nand_dbg_err("[NE]parameter error %d,%d !\n",
				     start_sector, len);
			return 1;
		}
	}

	mutex_lock(nftl_blk->blk_lock);
	ret = nftl_blk->write_data(nftl_blk, start_sector, len, buf);
	mutex_unlock(nftl_blk->blk_lock);

	return ret;
}

/****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
uint32 nftl_test_flush_write_cache(void)
{
	struct _nftl_blk *nftl_blk;

	nftl_blk = nftl_blk_head.nftl_blk_next;

	while (nftl_blk != NULL) {
		mutex_lock(nftl_blk->blk_lock);
		nftl_blk->flush_write_cache(nftl_blk, 0xffff);
		mutex_unlock(nftl_blk->blk_lock);
		nftl_blk = nftl_blk->nftl_blk_next;
	}
	return 0;
}

/****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
void obj_test_release(struct kobject *kobject)
{
	nand_dbg_err("release");
}

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
static ssize_t nand_test_show(struct kobject *kobject, struct attribute *attr,
			      char *buf)
{
	ssize_t count = 0;
	struct nand_kobject *nand_kobj;

	nand_kobj = (struct nand_kobject *)kobject;
	print_nftl_zone(nand_kobj->nftl_blk->nftl_zone);

	return count;
}

/****************************************************************************
*Name         :
*Description  :receive testcase num from echo command
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
static ssize_t nand_test_store(struct kobject *kobject, struct attribute *attr,
			       const char *buf, size_t count)
{
	int ret;
	int argnum = 0;
	char cmd[32] = {0};
	unsigned int param0 = 0;
	unsigned int param1 = 0;
	unsigned int param2 = 0;

	struct nand_kobject *nand_kobj;

	nand_kobj = (struct nand_kobject *)kobject;

	argnum = sscanf(buf, "%31s %u %u %u ", cmd, &param0, &param1, &param2);
	nand_dbg_err("argnum=%i, cmd=%s, param0=%u, param1=%u, param2=%u\n",
		     argnum, cmd, param0, param1, param2);

	if (-1 == argnum) {
		nand_dbg_err("cmd format err!");
		goto NAND_TEST_STORE_EXIT;
	}

	if (strcmp(cmd, "help") == 0) {
		nand_dbg_err("nand debug cmd:\n");
		nand_dbg_err("  help\n");
	} else if (strcmp(cmd, "flush") == 0) {
		nand_dbg_err("nand debug cmd:\n");
		nand_dbg_err("  flush\n");
		mutex_lock(nand_kobj->nftl_blk->blk_lock);
		ret =
		    nand_kobj->nftl_blk->flush_write_cache(nand_kobj->nftl_blk,
							   param0);
		mutex_unlock(nand_kobj->nftl_blk->blk_lock);
		goto NAND_TEST_STORE_EXIT;
	} else if (strcmp(cmd, "gcall") == 0) {
		nand_dbg_err("nand debug cmd:\n");
		nand_dbg_err("  gcall\n");
		mutex_lock(nand_kobj->nftl_blk->blk_lock);
		/* ret = gc_all(nand_kobj->nftl_blk->nftl_zone); */
		mutex_unlock(nand_kobj->nftl_blk->blk_lock);
		goto NAND_TEST_STORE_EXIT;
	} else if (strcmp(cmd, "gcone") == 0) {
		nand_dbg_err("nand debug cmd:\n");
		nand_dbg_err("  gcone\n");
		mutex_lock(nand_kobj->nftl_blk->blk_lock);
		/* ret = gc_one(nand_kobj->nftl_blk->nftl_zone); */
		mutex_unlock(nand_kobj->nftl_blk->blk_lock);
		goto NAND_TEST_STORE_EXIT;
	} else if (strcmp(cmd, "test") == 0) {
		nand_dbg_err("nand debug cmd:\n");
		nand_dbg_err("  test\n");
		mutex_lock(nand_kobj->nftl_blk->blk_lock);
		ret =
		    nftl_set_zone_test((void *)nand_kobj->nftl_blk->nftl_zone,
				       param0);
		mutex_unlock(nand_kobj->nftl_blk->blk_lock);
		goto NAND_TEST_STORE_EXIT;
	} else if (strcmp(cmd, "showall") == 0) {
		nand_dbg_err("nand debug cmd:\n");
		nand_dbg_err("  show all\n");
		print_free_list(nand_kobj->nftl_blk->nftl_zone);
		print_block_invalid_list(nand_kobj->nftl_blk->nftl_zone);
		print_nftl_zone(nand_kobj->nftl_blk->nftl_zone);
		goto NAND_TEST_STORE_EXIT;
	} else {
		nand_dbg_err("err, nand debug undefined cmd: %s\n", cmd);
	}

NAND_TEST_STORE_EXIT:
	return count;
}

struct attribute const prompt_attr = {
    .name = "nand_debug",
    .mode = S_IRWXUGO};

static struct attribute *def_attrs[] = {
    &prompt_attr,
    NULL};

const struct sysfs_ops obj_test_sysops = {
    .show = nand_test_show,
    .store = nand_test_store};

struct kobj_type ktype = {
    .release = obj_test_release,
    .sysfs_ops = &obj_test_sysops,
    .default_attrs = def_attrs};
