/*
 * Generic Nand Test interface
 *
 * Copyright (C) 2016-2017 zhangzhanming <zhangzhanming@allwinnertech.com>
 *
 * 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.
 */

#define _NAND_TEST_C_

#include "nand_blk.h"
#include "nand_dev.h"

extern int debug_data;

extern uint32 gc_all(void *zone);
extern uint32 gc_one(void *zone);
extern uint32 prio_gc_one(void *zone, uint16 block, uint32 flag);
extern void print_nftl_zone(void *zone);
extern void print_free_list(void *zone);
extern void print_block_invalid_list(void *zone);
extern uint32 nftl_set_zone_test(void *_zone, uint32 num);
extern int nand_dbg_phy_read(unsigned short nDieNum, unsigned short nBlkNum,
			     unsigned short nPage);
extern int nand_dbg_zone_phy_read(void *zone, uint16 block, uint16 page);
extern int nand_dbg_zone_erase(void *zone, uint16 block, uint16 erase_num);
extern int nand_dbg_zone_phy_write(void *zone, uint16 block, uint16 page);
extern int nand_dbg_phy_write(unsigned short nDieNum, unsigned short nBlkNum,
			      unsigned short nPage);
extern int nand_dbg_phy_erase(unsigned short nDieNum, unsigned short nBlkNum);
extern int _dev_nand_read2(char *name, __u32 start_sector, __u32 len,
			   unsigned char *buf);
extern void nand_phy_test(void);
extern int nand_check_table(void *zone);

extern int _dev_nand_read(struct _nand_dev *nand_dev, __u32 start_sector,
			  __u32 len, unsigned char *buf);
extern int _dev_nand_write(struct _nand_dev *nand_dev, __u32 start_sector,
			   __u32 len, unsigned char *buf);
extern int _dev_flush_write_cache(struct _nand_dev *nand_dev, __u32 num);
extern struct _nand_dev *_get_nand_dev_by_name(char *name);

struct task_struct *p_udisk_test_thread;
int udisk_test_thread_running;
struct _nand_dev *udisk_test_nand_dev;

unsigned char *udisk_test_buf;	/*128K*/
unsigned char *udisk_test_mapping;	/* (500*1024) * 8 byte*/

#define  MAX_PTR_PER_TEST   (500*1024)

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
static unsigned long rand = 100;
static inline unsigned int my_random(void)
{
	/* See "Numerical Recipes in C", second edition, p. 284 */
	rand = rand * 1664525L + 1013904223L;
	return rand;
}

unsigned int udisk_test_get_rand(unsigned int min, unsigned int max)
{
	unsigned int max_num, rand_dat;

	max_num = max - min;

	rand_dat = my_random();

	rand_dat = rand_dat % max_num;

	return min + rand_dat;
}

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
unsigned int udisk_test_fill_buf(unsigned int counter, unsigned int addr,
				 unsigned int len, unsigned char *buf)
{
	int i;
	unsigned int *p_dat1;
	unsigned int *p_dat2;

	memset(buf, 0xa3, len * 512);

	for (i = 0; i < len; i++) {
		p_dat1 = (unsigned int *)(buf + i * 512);
		p_dat2 = (unsigned int *)(buf + i * 512 + 4);

		*p_dat1 = addr + i;
		*p_dat2 = counter;
	}
	return 0;
}

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
unsigned int udisk_test_check_buf(unsigned int counter, unsigned int addr,
				  unsigned int len, unsigned char *buf)
{
	int i;
	unsigned int *p_dat1;
	unsigned int *p_dat2;

	for (i = 0; i < len; i++) {
		p_dat1 = (unsigned int *)(buf + i * 512);
		p_dat2 = (unsigned int *)(buf + i * 512 + 4);

		if ((*p_dat1 != (addr + i)) || (*p_dat2 != counter))
			return 1;

	}

	return 0;
}

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
unsigned int udisk_test_updata_mapping(unsigned int ptr, unsigned int addr,
				       unsigned int len, unsigned char *mapping)
{
	unsigned int *p_addr;
	unsigned int *p_len;

	p_addr = (unsigned int *)(mapping + ptr * 8);
	p_len = (unsigned int *)(mapping + ptr * 8 + 4);
	*p_addr = addr;
	*p_len = len;

	return 0;
}

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
unsigned int udisk_test_write_mapping(unsigned char *mapping)
{
	unsigned int addr, len;

	addr = 0x16000;
	len = (MAX_PTR_PER_TEST * 8);
	len >>= 9;

	_dev_nand_write(udisk_test_nand_dev, addr, len, mapping);

	return 0;
}

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
unsigned int udisk_test_read_mapping(unsigned char *mapping)
{
	unsigned int addr, len;

	addr = 0x16000;
	len = (MAX_PTR_PER_TEST * 8);
	len >>= 9;

	_dev_nand_read(udisk_test_nand_dev, addr, len, mapping);

	return 0;
}

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
unsigned int udisk_test_check_mapping(unsigned char *mapping,
				      unsigned char *buf)
{
	unsigned int magic_dat;
	unsigned int i, counter, flag;

	unsigned int *p_addr;
	unsigned int *p_len;

	udisk_test_read_mapping(mapping);

	magic_dat = *(unsigned int *)mapping;

	if ((magic_dat != 0xf8f8f8f8) && (magic_dat != 0xffffffff)) {
		printk("udisk test can not get mapping 0x%x!\n", magic_dat);
		return 0xfffffff0;
	}

	if (magic_dat == 0xffffffff) {
		printk("udisk test first !\n");
		return 0xffffffff;
	}
	p_addr = (unsigned int *)(mapping + 8);
	p_len = (unsigned int *)(mapping + 8 + 4);

	_dev_nand_read(udisk_test_nand_dev, *p_addr, *p_len, udisk_test_buf);
	p_addr = (unsigned int *)(udisk_test_buf + 4);
	counter = *p_addr;

	flag = 0;
	for (i = 1; i < MAX_PTR_PER_TEST; i++) {
		p_addr = (unsigned int *)(mapping + i * 8);
		p_len = (unsigned int *)(mapping + i * 8 + 4);

		if (*p_addr == 0xffffffff) {
			printk("udisk test check num:%d !\n", i);
			break;
		}

		_dev_nand_read(udisk_test_nand_dev, *p_addr, *p_len,
			       udisk_test_buf);

		flag =
		    udisk_test_check_buf(counter, *p_addr, *p_len,
					 udisk_test_buf);
		if (flag != 0) {
			printk("udisk test check_mapping  error %d!\n", i);
			return 0xfffffff0;
		}
	}

	return counter;
}

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
static int udisk_test_thread(void *arg)
{
	unsigned int min_addr, max_addr, min_len, max_len;
	unsigned int addr, len, counter, ptr;

	long time_out = 1 * HZ;
	udisk_test_nand_dev = _get_nand_dev_by_name("UDISK");

	min_len = 1;
	max_len = 256;		/*128k*/

	min_addr = 0x32000;	/*start from 100M*/
	max_addr = udisk_test_nand_dev->size - 1 - max_len;

	if (min_addr >= max_addr) {
		udisk_test_thread_running = 3;
		printk("udisk test not start 1!\n");
	}

	ptr = 1;
	counter = udisk_test_check_mapping(udisk_test_mapping, udisk_test_buf);
	if (counter == 0xfffffff0) {
		udisk_test_thread_running = 3;
		ptr = MAX_PTR_PER_TEST;
		printk("udisk test error!!!!\n");
	}

	counter += 1;
	memset(udisk_test_mapping, 0xff, MAX_PTR_PER_TEST * 8);
	memset(udisk_test_mapping, 0xf8, 8);

	while (!kthread_should_stop()) {
		if (udisk_test_thread_running != 1) {
			if (ptr < MAX_PTR_PER_TEST)
				udisk_test_write_mapping(udisk_test_mapping);

			udisk_test_thread_running = 3;
			goto udisk_test_sleep;
		}

		addr = udisk_test_get_rand(min_addr, max_addr);
		len = udisk_test_get_rand(min_len, max_len);

		udisk_test_updata_mapping(ptr, addr, len, udisk_test_mapping);

		udisk_test_fill_buf(counter, addr, len, udisk_test_buf);

		printk
		    ("udisk test thread running addr:0x%x len:%d total size:0x%x conter:0x%x,ptr:%d !\n",
		     addr, len, udisk_test_nand_dev->size, counter, ptr);
		_dev_nand_write(udisk_test_nand_dev, addr, len, udisk_test_buf);

		ptr++;
		if (ptr >= MAX_PTR_PER_TEST) {
			printk("max write time in one test!\n");
			udisk_test_write_mapping(udisk_test_mapping);
		}

udisk_test_sleep:
		set_current_state(TASK_INTERRUPTIBLE);
		schedule_timeout(time_out);
	}
	return 0;
}

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
void udisk_test_start(struct _nftl_blk *nftl_blk)
{
	if (p_udisk_test_thread == NULL) {
		udisk_test_thread_running = 1;

		udisk_test_buf = kmalloc(0x20000, GFP_KERNEL);	/*128K*/
		udisk_test_mapping = kmalloc(MAX_PTR_PER_TEST * 8, GFP_KERNEL);

		p_udisk_test_thread =
		    kthread_run(udisk_test_thread, nftl_blk, "%sd", "nftl");
		printk("udisk test thread start!\n");
	} else {
		printk("udisk test thread already start!\n");
	}
	return;
}

/*****************************************************************************
*Name         :
*Description  :
*Parameter    :
*Return       :
*Note         :
*****************************************************************************/
void udisk_test_stop(void)
{
	if (p_udisk_test_thread == NULL) {
		printk("udisk test thread already stop!\n");
		return;
	}

	udisk_test_thread_running = 2;

	while (1) {
		schedule_timeout(HZ);
		if (udisk_test_thread_running == 3) {
			kthread_stop(p_udisk_test_thread);

			kfree(udisk_test_mapping);
			kfree(udisk_test_buf);
			_dev_flush_write_cache(udisk_test_nand_dev, 0xffff);
			udisk_test_thread_running = 0;
			p_udisk_test_thread = NULL;
			printk("udisk test thread stop!\n");
			break;
		}
	}
	return;
}