/**    
    @file     mtd-nand.c
    @date     2010/05/27
    @author   오재경 freefrug@falinux.com  FALinux.Co.,Ltd.
    @brief    mtd를 통해 nand 플래시를 제어한다
              2011/03/25 오재경 ezboot 1.x 를 지원을 추가
              2011/04/13 오재경 배드블럭에 대한 에러를 수정
              2011/06/13 오재경 배드블럭에 대한 에러를 수정
    @todo     
    		MTD_NANDECC_OFF 옵션도 있으니 구현하자
    @bug     
    @remark  
    @warning 
*/
//
//  저작권    에프에이리눅스(주)
//            외부공개 금지
//
//----------------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/poll.h>
#include <sys/mman.h> 

#include <mtd-abi.h>
#include <mtd-nand.h>
#include <util.h>

char desc_mtd_nand[] = "falinux mtd-nand ver 0.3.4";

//------------------------------------------------------------------------------
/** @brief   mtd-nand 생성함수
    @param   fname  mtd 노드파일이름
    @return  mtdnand_t 구조체 포인터
*///----------------------------------------------------------------------------
mtdnand_t *mtdnand_create( char *fname )
{       
	mtdnand_t *mtd;
	
	mtd = malloc( sizeof(mtdnand_t) );
	memset( (void *)mtd, 0, sizeof(mtdnand_t) );

	sprintf( mtd->node_name, "%s", fname );
	mtd->fd = open( mtd->node_name, O_RDWR );	// O_RDONLY
	
	if ( 0 > mtd->fd )
	{
		perror( "mtd open error" );
		free( mtd );
		return NULL;
	}

	// 난드플래시의 정보를 얻는다.
	if ( 0 == ioctl( mtd->fd, MEMGETINFO, &mtd->info) )
	{
		printf( " mtd=%s \n", mtd->node_name );
		printf( "  * total size  %dMB\n", mtd->info.size/(1024*1024) );
		printf( "  * erase size  %d\n"  , mtd->info.erasesize        );
		printf( "  * write size  %d\n"  , mtd->info.writesize        );
		printf( "  * oob   size  %d\n"  , mtd->info.oobsize          );
		printf( "  * ecc type    %d\n"  , mtd->info.ecctype          );
		printf( "\n" );
	}
	else
	{
		perror( "unable to get mtd-info" );
		free( mtd );
		return NULL;
	}

	return mtd;
}
//------------------------------------------------------------------------------
/** @brief   mtd-nand 해제함수
    @param   mtd   mtdnand_t 구조체 포인터
*///----------------------------------------------------------------------------
void  mtdnand_free( mtdnand_t *mtd )
{
	if ( mtd )
	{
		close( mtd->fd );
		free( mtd );
	}
}
//------------------------------------------------------------------------------
/** @brief   mtd-nand 의 엑세스 위치를 이동한다.
    @param   mtd     mtdnand_t 구조체 포인터
    @param   offset  이동크기
    @param   origin  기준위치
    @return  파일포인터의 위치, 에러일경우 음수 
*///----------------------------------------------------------------------------
int  mtdnand_seek( mtdnand_t *mtd, int offset, int origin )
{
	int pos;
	
	pos = lseek( mtd->fd, offset, origin );
	//printf( "seek=0x%08x (%d)\n", rtn, rtn );	
	
	if ( 0 > pos )
	{
		perror( "mtd seek error" );	
		return -1;
	}
	else
	{
		mtd->pos = pos;
		
		//switch( origin )
		//{
		//case SEEK_SET : mtd->pos  = offset; break;
		//case SEEK_CUR : mtd->pos += offset; break;
		//case SEEK_END : mtd->pos  = mtd->info.size + offset; break;
		//}
	}
	
	return pos;
}

//------------------------------------------------------------------------------
/** @brief   mtd-nand 의 oob 영역을 읽는다.
    @param   mtd    mtdnand_t 구조체 포인터
    @param   buf    데이타를 담을 버퍼
    @param   count  버퍼의 크기
    @return  성공0
*///----------------------------------------------------------------------------
int  mtdnand_read_oob( mtdnand_t *mtd, unsigned char *buf, int count )
{
	struct mtd_oob_buf oob_buf;
	int rtn;

	oob_buf.start  = mtd->pos;
	oob_buf.length = (count > mtd->info.oobsize) ? mtd->info.oobsize : count;
	oob_buf.ptr    = buf;

	rtn = ioctl( mtd->fd, MEMREADOOB, &oob_buf );
	if ( 0 > rtn )
	{
		perror( "mtd read-oob error" );
	}

	return rtn;
}
//------------------------------------------------------------------------------
/** @brief   mtd-nand 의 oob 영역을 읽는다.
    @param   mtd     mtdnand_t 구조체 포인터
    @return  -1 : badblock   0:정상블럭
*///----------------------------------------------------------------------------
int  mtdnand_is_bad( mtdnand_t *mtd )
{
	unsigned char buf[256], cmp;

	cmp = 0x00;
	if ( 0 == mtdnand_read_oob( mtd, buf, 6 ) )
	{
		if ( 512 == mtd->info.writesize )
		{
			cmp = buf[5];
		}
		else
		{
			cmp = buf[0];
		}
	}

	if ( cmp != 0xff )
	{
		printf( "%s bad block 0x%08x\n", mtd->node_name, mtd->pos );
		return -1;
	}
	return 0;
}
//------------------------------------------------------------------------------
/** @brief   mtd-nand 의 한블럭을 지운다.
    @param   mtd     mtdnand_t 구조체 포인터
    @return  성공0
*///----------------------------------------------------------------------------
int  mtdnand_erase( mtdnand_t *mtd )
{
	int rtn;
	struct erase_info_user erase_info;
	
	erase_info.start  = (mtd->pos/mtd->info.erasesize)*mtd->info.erasesize;
	erase_info.length = mtd->info.erasesize;
	
	rtn = ioctl( mtd->fd, MEMERASE, &erase_info);
	if ( 0 != rtn )
	{
		perror( "mtd erase error" );
	}	
	return rtn;
}
//------------------------------------------------------------------------------
/** @brief   mtd-nand 에서 데이타를 읽는다.
    @param   mtd    mtdnand_t 구조체 포인터
    @param   buf    데이타를 담을 버퍼
    @param   count  버퍼의 크기
    @return  읽은 데이타의 크기
    @remark  배드블럭을 만나면 
*///----------------------------------------------------------------------------
int  mtdnand_read( mtdnand_t *mtd, unsigned char *buf, int count )
{
	int rtn;
	
	rtn = read( mtd->fd, buf, count );
	if ( 0 > rtn )
	{
		perror( "mtd read error" );	
	}
	else
	{
		mtd->pos += rtn;	
	}
	
	return rtn;
}
//------------------------------------------------------------------------------
/** @brief   mtd-nand 에서 데이타를 읽는다.
    @param   mtd    mtdnand_t 구조체 포인터
    @param   buf    데이타를 담을 버퍼
    @param   count  버퍼의 크기
    @return  읽은 데이타의 크기
    @remark  배드블럭을 만나면 다음블럭으로 자동으로 점프한다.
*///----------------------------------------------------------------------------
int  mtdnand_read_skip_bad( mtdnand_t *mtd, unsigned char *buf, int count )
{
	int rtn, esize, bad_cnt;
	int rdcnt, remain;
	
	esize   = mtd->info.erasesize;
	bad_cnt = 0;
	remain  = count;

	// 페이지 단위로 정렬한다.
	mtd->pos = ALIGN_SIZE( mtd->pos, mtd->info.writesize );
	mtdnand_seek( mtd, mtd->pos, SEEK_SET );
	
	while( 0 < remain )
	{
		// 블럭의 시작인가?
		if ( 0 == (mtd->pos % esize) )
		{
			while( mtdnand_is_bad( mtd ) )
			{
				printf( "\n bad block  offset-page=%d\n", mtd->pos/mtd->info.writesize  );	
				mtd->pos += esize;
				mtdnand_seek( mtd, mtd->pos, SEEK_SET );
				bad_cnt ++;
				if ( MAX_BADBLOCK_COUNT <= bad_cnt ) 
				{
					printf( "mtd read error : many bad block over %d\n", MAX_BADBLOCK_COUNT );	
					return RDERR_MANY_BADBLOCK	;
				}
			}
		}	

		// 읽을 갯수를 결정한다.
		rdcnt = (remain > esize) ? esize : remain;
	
		// 데이타 읽기
		rtn = read( mtd->fd, buf, rdcnt );
		if ( 0 > rtn )
		{
			perror( "mtd read error" );	
			return rtn;
		}
		else
		{
			mtd->pos += rdcnt;	
		}
		
		remain -= rdcnt;
		buf    += rdcnt;
	}
	
	return count;
}


//------------------------------------------------------------------------------
/** @brief   mtd-nand 에 데이타를 쓴다.
    @param   mtd    mtdnand_t 구조체 포인터
    @param   buf    데이타 버퍼
    @param   count  버퍼의 크기
    @return  쓴 데이타의 크기
    @remark  데이타의 크기는 writesize 크기의 배수만 쓰여진다.
             erase 는 자동으로 이루어지며 erasesize 배수의 위치일 경우 지운다.
                          배드블럭이 있으면 다음블럭으로 넘긴다.
*///----------------------------------------------------------------------------
int  mtdnand_write( mtdnand_t *mtd, unsigned char *buf, int count )
{
	int  wrcnt, esize, wsize, wr_total, remain;
	char wbuf[1024*256], *getp;
	
	getp     = buf;
	wr_total = 0;
	remain   = count;
	esize    = mtd->info.erasesize;
	wsize    = mtd->info.writesize;

	// 쓰기의 위치는 항상 writesize의 배수이어야 한다.
	if ( 0 != ( mtd->pos % wsize ) )
	{
		mtdnand_seek( mtd, (mtd->pos/wsize)*wsize, SEEK_SET );
	}
	
	while( 0 < remain )
	{	
		// 블럭을 지워야 하는가?
		if ( 0 == (mtd->pos % esize) )
		{
			if ( mtdnand_is_bad( mtd ) )
			{
				mtd->pos += esize;
				mtdnand_seek( mtd, mtd->pos, SEEK_SET );
				continue;
			}

			// 지워서 에러가  있다면 배드블럭이다(?)
			// mtdnand_erase( mtd );
			if ( 0 != mtdnand_erase( mtd ) )
			{
				mtd->pos += esize;
				mtdnand_seek( mtd, mtd->pos, SEEK_SET );
				continue;
			}
		}
		
		// writesize 만큼 쓴다.
		wrcnt = (remain >= wsize) ? wsize : remain;
		
		// writesize 보다 작다면 나머지를 0xff 로 채운다.
		memcpy( wbuf, getp, wrcnt );
		if ( wrcnt < wsize )
		{
			memset( wbuf+wrcnt, 0xff, wsize-wrcnt );	
		}
		
		// 쓰기는 항상 writesize 이어야 한다.
		wrcnt = write( mtd->fd, wbuf, wsize );	
		if ( wrcnt < 0 )
		{
			perror( "mtd write error" );
			return -1;
		}

		mtd->pos += wrcnt;
		
		getp     += wrcnt;
		wr_total += wrcnt;
		remain   -= wrcnt;
	}
	
	return wr_total;
}


//------------------------------------------------------------------------------
/** @brief   mtd-nand 를 모두 지운다.
    @param   mtd     mtdnand_t 구조체 포인터
*///----------------------------------------------------------------------------
void  mtdnand_erase_all( mtdnand_t *mtd )
{
	int  remain, esize;
	char str[256];

	void progress_msg( char *msg )
	{
		printf( "%s", msg ); fflush( stdout );
	}
	
	sprintf( str, " erase all mtd=%s size=%dMB\n", mtd->node_name, mtd->info.size/(1024*1024) );
	progress_msg( str );
	
	// mtd 어프셋을 설정한다.
	mtdnand_seek( mtd, 0, SEEK_SET );
	
	remain = mtd->info.size;
	esize  = mtd->info.erasesize;
	
	while( 0 < remain )
	{
		mtdnand_erase( mtd );
		mtdnand_seek( mtd, esize, SEEK_CUR );
		
		remain -= esize;
		
		if ( 0 == (remain % (1024*1024)) )
		{
			progress_msg( "." );
		}
	}
	
	progress_msg( ".end\n" );
}