/***********************************************************************
 * Copyright (c)  2017 - 2022, Unicmicro Co.,Ltd .
 * All rights reserved.
 * Filename    : qspi.c
 * Description : qspi source file
 * Author(s)   : zhilan
 * version     : V1.0
 * Modify date : 2022-08-04
 ***********************************************************************/
#include "qspi.h"

void (*qspi_func)(void) =  NULL ;

/************************************************************************
 * function   : QSPI_IRQHandler
 * description: qspi interrupt handling
 * input : none
 * return: none
 ************************************************************************/
void QSPI_IRQHandler(void)
{
	if(REG_QSPI_INTUS & QSPI_DONE_STATUS)			 //qspi complete interrupt
	{
		REG_QSPI_INTUS = QSPI_DONE_CLEAR;
		if(qspi_func != NULL)
		{
			qspi_func();	
	    }			
	}	
	else
	{
		REG_QSPI_INTUS = QSPI_OPERR_CLEAR;	        //clear qspi error interrupt
		if(qspi_func != NULL)
		{
			qspi_func();		
		}	
	}
}

/************************************************************************
 * function    : qspi_init
 * description : qspi initial
 * input 	   : uint8_t qspi_line : qspi communication mode
 *				 uint8_t work_mode:  set CPHOL and CPHA
 *				 uint16_t qspi_baud: qspi communication rate  The minimum setting is 2 and multiple writing of 2 is required.
 * return	   : none
 ************************************************************************/
void qspi_init(uint8_t qspi_line,uint8_t work_mode,uint16_t qspi_baud)         
{
	REG_SCU_PERICLKEN |= (1<<23);                							// QSPI时钟使能 
    REG_SCU_PERIRESET |= (1<<23);                							// QSPI释放复位 

	REG_SCU_IOCTRLPROTECT = 0xA5A55A5A; 						            //清除IO写保护
    REG_SCU_PDSEL = (REG_SCU_PDSEL&(~((uint32_t)0xF<<4)))|(5<<4);			//PD1复用为qspi_miso  QSPI D1 MISO
    REG_SCU_PASEL = (REG_SCU_PASEL&(~((uint32_t)0xF<<12)))|(5<<12);		    //PA3复用为qspi_mosi  QSPI D0 MOSI
    REG_SCU_PASEL = (REG_SCU_PASEL&(~((uint32_t)0xF<<0)))|(7<<0);			//PA0复用为qspi_sck	  QSPI CLK	
    REG_SCU_PDSEL = (REG_SCU_PDSEL&(~((uint32_t)0xF<<12)))|(7<<12);		    //PD3复用为qspi_csn   QSPI CS
    REG_SCU_PBSEL = (REG_SCU_PBSEL&(~((uint32_t)0xF<<28)))|(6<<28);		    //PB7复用为qspi_hold  QSPI D3 HOLD
    REG_SCU_PDSEL = (REG_SCU_PDSEL&(~((uint32_t)0xF<<0)))|(7<<0);			//PD0复用为qspi_wp    QSPI D2 WP
	REG_SCU_IOCTRLPROTECT = 0xFFFFFFFF; 						            //开启IO写保护
	
	REG_QSPI_CTRL  = 0;									
	REG_QSPI_CTRL |= (23<<5);                          						//设置时间标尺为 （Fsys/2）-1
	REG_QSPI_CTRL |= qspi_line;                        						//设置qspi为单线、2线or4线传输
	REG_QSPI_CTRL |= work_mode;                        						//设置时钟极性和时钟相位
	REG_QSPI_CTRL |= QSPI_MSB;                         						//设置MSB传输在前  
	REG_QSPI_BAUD  = qspi_baud;                        						//设置分频因子 Fqspi=Fsys/qspi_baud
							
							
#ifdef CPU_HOLD						
	REG_QSPI_CTRL |= (1<<11);                          						 //SPI_PGT_SET寄存器设置的时间内，CPU停止运行  
#endif						
						
	REG_QSPI_MEMO_ACC |= QSPI_EN;                      						 //使能qspi 	 								
}

/************************************************************************
 * function    : qspi_irq_init
 * description : qspi irq initial
 * input 	   : uint8_t state:  	enable/disable	 
 *				 void (*pfunc)():	callback handler function		
 * return	   : none
 ************************************************************************/
void qspi_irq_init( uint8_t state, void (*pfunc)())
{
	if(QSPI_IRQ_ENABLE == state)
	{
		qspi_func = pfunc;
		REG_QSPI_INTEN |= QSPI_DONEEN;		   								//complete interrupt enable
		
		NVIC_ClearPendingIRQ(QSPI_IRQn);
		NVIC_EnableIRQ(QSPI_IRQn);
	}
	else
	{
		qspi_func = NULL;
		REG_QSPI_INTEN &= ~QSPI_DONEEN;										//complete interrupt disable
	}
}

/************************************************************************
 * function    : qspi_line_set
 * description : set line of qspi
 * input 	   : uint8_t  line	Qspi communication mode in 1-wire, 2-wire, 4-wire
 * return	   : none
 ************************************************************************/
void qspi_line_set(uint8_t line)
{
	REG_QSPI_CTRL &= ~(0x3<<3);
	if(QSPI_LINE_4 == line)
	{
		REG_QSPI_CTRL |= QSPI_LINE_4;
	}
	else if(QSPI_LINE_2 == line)
	{
		REG_QSPI_CTRL |= QSPI_LINE_2;
	}
	else
	{
		REG_QSPI_CTRL |= QSPI_LINE_1;
	}
}

/************************************************************************
 * function    : qspi_config
 * description : config the frame of qspi
 * input 	   : uint32_t cfg cfg parameters 
 * return	   : none
 ************************************************************************/
void qspi_config(uint32_t cfg)
{
	REG_QSPI_MEMO_ACC = cfg;
	REG_QSPI_MEMO_ACC |= (1<<0);											//enable qspi
}

/************************************************************************
 * function    : qspi_set_cmd
 * description : set the command 
 * input 	   : uint8_t type :	read/write
 *				 uint8_t cmd:	command 
 * return	   : none
 ************************************************************************/
void qspi_set_cmd(uint8_t type,uint8_t cmd)
{
	if(QSPI_CMD_WR == type)
	{
		REG_QSPI_CMD = cmd<<8;
	}
	else
	{
		REG_QSPI_CMD = cmd;
	}
}

/************************************************************************
 * function    : qspi_set_program_time
 * description : set the waiting time of programing
 * input 	   : uint16_t tim: time to wait,rely on HALF_US
 * return	   : none
 ************************************************************************/
void qspi_set_program_time(uint16_t tim)
{
	REG_QSPI_PGT_SET = tim;
}

/************************************************************************
 * function    : qspi_set_para_read
 * description : set the parameter while reading
 * input 	   : uint8_t para1 : read command parameter 1
 * 		 	   : uint16_t para2 :read command parameter 2
 * return	   : none
 ************************************************************************/
void qspi_set_para_read(uint8_t para1, uint16_t para2)
{
	REG_QSPI_PARA_R = (para2<<8)|para1;
}

/************************************************************************
 * function    : qspi_set_para_write
 * description : set the parameter while writing
 * input 	   : uint8_t para1 : write command parameter 1
 * 		 	   : uint16_t para2 :write command parameter 2
 * return	   : none
 ************************************************************************/
void qspi_set_para_write(uint8_t para1, uint16_t para2)
{
	REG_QSPI_PARA_W = (para2<<8)|para1;
}

/************************************************************************
 * function    : qspi_read_byte
 * description : read byte from EXT_FLASH_START_ADDR+offset
 * input 	   : uint32_t offset: offset of address to read
 * return	   : none
 ************************************************************************/
__inline uint8_t qspi_read_byte(uint32_t offset)
{
	return (*(volatile uint8_t*)(EXT_FLASH_START_ADDR+offset));
}

/************************************************************************
 * function    : qspi_read_word
 * description : read word from EXT_FLASH_START_ADDR+offset
 * input 	   : uint32_t offset ： offset of address to read
 * return	   : none
 ************************************************************************/
__inline uint16_t qspi_read_word(uint32_t offset)
{
	return (*(volatile uint16_t*)(EXT_FLASH_START_ADDR+offset));
}


/************************************************************************
 * function    : qspi_read_dword
 * description : read 1 double-word from EXT_FLASH_START_ADDR+offset
 * input 	   : uint32_t offset: offset of address to read
 * return	   : none
 ************************************************************************/
__inline uint32_t qspi_read_dword(uint32_t offset)
{
	return (*(volatile uint32_t*)(EXT_FLASH_START_ADDR+offset));
}

/************************************************************************
 * function    : qspi_write_byte
 * description : write byte from EXT_FLASH_START_ADDR+offset
 * input 	   : uint32_t offset: offset of address to write
				 uint8_t val:	value to write 
 * return	   : none
 ************************************************************************/
__inline void qspi_write_byte(uint32_t offset, uint8_t val)
{
	(*(volatile uint8_t*)(EXT_FLASH_START_ADDR+offset)) = val;
}

/************************************************************************
 * function    : qspi_write_word
 * description : write word from EXT_FLASH_START_ADDR+offset
 * input 	   : uint32_t offset: offset of address to write
				 uint16_t val:	value to write 
 * return	   : none
 ************************************************************************/
__inline void qspi_write_word(uint32_t offset, uint16_t val)
{
	(*(volatile uint16_t*)(EXT_FLASH_START_ADDR+offset)) = val;
}

/************************************************************************
 * function    : qspi_write_dword
 * description : write double-word from EXT_FLASH_START_ADDR+offset
 * input 	   : uint32_t offset: offset of address to write
				 uint32_t val:	value to write 
 * return	   : none
 ************************************************************************/
__inline void qspi_write_dword(uint32_t offset, uint32_t val)
{
	(*(volatile uint32_t*)(EXT_FLASH_START_ADDR+offset)) = val;
}
	
/************************************************************************
 * function    : flash_get_status
 * description : get the status of flash(idle/busy)
 * input	   : none
 * return	   : flash status
 ************************************************************************/
uint8_t flash_get_status(void)
{
	uint8_t flash_status;

	qspi_line_set(QSPI_LINE_1);
	qspi_config(READ_NO_ADDR);
	qspi_set_cmd( QSPI_CMD_RD, READ_FLASH_STATUS);				
	qspi_set_program_time(0);
	flash_status = qspi_read_byte(0);	
	
	return  flash_status;	
}

/************************************************************************
 * function    : flash_wait_till_idle
 * description : wait untill flash is idle
 * input	   : none
 * return	   : none
 ************************************************************************/
void flash_wait_till_idle(void)
{
	while((flash_get_status()&(1<<0))==0x1);    		
}

/************************************************************************
 * function    : flash_write_enable
 * description : enable the write function of flash
 * input	   : none
 * return	   : none
 ************************************************************************/
void flash_write_enable(void)
{
	qspi_line_set(QSPI_LINE_1);
	qspi_config(WRITE_NO_ADDR | WRITE_NO_DATA);
	qspi_set_cmd( QSPI_CMD_WR, WRITE_ENABLE);				
	qspi_set_program_time(0);
	qspi_write_byte( 0, 0x01);
	flash_wait_till_idle();
}

/************************************************************************
 * function    : flash_write_disable
 * description : disable the write function of flash
 * input	   : none
 * return	   : none
 ************************************************************************/
void flash_write_disable(void)
{
	qspi_line_set(QSPI_LINE_1);
	qspi_config(WRITE_NO_ADDR | WRITE_NO_DATA);
	qspi_set_cmd( QSPI_CMD_WR, WRITE_DISABLE);				
	qspi_set_program_time(0);
	qspi_write_byte( 0, 0x01);
}


/************************************************************************
 * function    : flash_write_page
 * description : program page of flash using 1-line
 * input	   : uint8_t* buffer:	buffer which store the data
 *				 uint32_t write_addr: the starting address of page
 *				 uint16_tlen: length of the buffer to write
 * return	   : none
 ************************************************************************/
void flash_write_page(uint8_t* buffer,uint32_t write_addr,uint16_t len)
{
	for(int i=0;i<len;i++)
	{
		flash_write_enable();
		qspi_config(ADDR_WIDTH_3);	
		qspi_set_cmd( QSPI_CMD_WR, PAGE_PROGRAM);				
		qspi_set_program_time(0xfa0);	
		qspi_write_byte( write_addr+i, buffer[i]);								
		flash_wait_till_idle();
	}
	flash_write_disable();
}

/************************************************************************
 * function    : flash_read_bytes
 * description : rad data from flash using 1-line
 * input	   : uint8_t* buffer:	buffer which store the data
 *				 uint32_t read_addr: the starting address of data
 *				 uint16_t len: length of the buffer to read			 
 * return	   : none
 ************************************************************************/
void flash_read_bytes(uint8_t* buffer,uint32_t read_addr,uint16_t len)
{
	qspi_line_set(QSPI_LINE_1);
	qspi_config(ADDR_WIDTH_3);	
	qspi_set_cmd( QSPI_CMD_RD, READ_DATA_BYTES);	

	for(int i=0;i<len;i++)
	{
		buffer[i] = qspi_read_byte(read_addr+i);
	}
}

/************************************************************************
 * function    : flash_2read_bytes
 * description : rad data from flash using 2-line
 * input	   : uint8_t* buffer:	buffer which store the data
 *				 uint32_t read_addr: the starting address of data
 *				 uint16_t len: length of the buffer to read
 * return	   : none
 ************************************************************************/
void flash_2read_bytes(uint8_t* buffer,uint32_t read_addr,uint16_t len)
{
	qspi_line_set(QSPI_LINE_2);
	qspi_config(ADDR_WIDTH_3 | ADDR_LINE_SIZE_R | Para_No1R_1 | Para_Ord1R_AFTER);
	qspi_set_cmd( QSPI_CMD_RD, DUAL_IO_FAST_READ);
	
	for(int i=0;i<len;i++)
	{
		buffer[i] = qspi_read_byte(read_addr+i);
	}
}

/************************************************************************
 * function    : flash_quad_enable
 * description : enable the quad mode 
 * input	   : none
 * return	   : none
 ************************************************************************/
void flash_quad_enable(void)
{
	qspi_line_set(QSPI_LINE_1);
	qspi_config(WRITE_NO_ADDR);		
	qspi_set_program_time(0xfa0);
	qspi_set_cmd(QSPI_CMD_WR, WRITE_STATUS_REG);	
	qspi_write_byte(0, 0x02);
}

/************************************************************************
 * function    : flash_quad_disable
 * description : disable the quad mode 
 * input	   : none
 * return	   : none
 ************************************************************************/
void flash_quad_disable(void)
{
	qspi_line_set(QSPI_LINE_1);
	qspi_config(WRITE_NO_ADDR);
	qspi_set_cmd( QSPI_CMD_WR, WRITE_STATUS_REG);				
	qspi_set_program_time(0xfa0);
	qspi_write_byte( 0, 0x0);
}

/************************************************************************
 * function    : flash_4write_page
 * description : program page of flash using 4-line
 * input	   : uint8_t* buffer:	buffer which store the data
 *				 uint32_t write_addr: the starting address of page
 *				 uint16_t len: length of the buffer to write
 * return	   : none
 ************************************************************************/
void flash_4write_page(uint8_t* buffer,uint32_t write_addr,uint16_t len)
{
	flash_write_enable(); 
	flash_quad_enable();
	flash_wait_till_idle();
	for(int i=0;i<len;i++)
	{
		flash_write_enable();   
		qspi_line_set(QSPI_LINE_4);
		qspi_config(ADDR_WIDTH_3);
		qspi_set_cmd( QSPI_CMD_WR, QUAD_PAGE_PROGRAM);				
		qspi_set_program_time(0xfa0);
		qspi_write_byte( write_addr+i, buffer[i]);

	}
	flash_write_enable();
	flash_wait_till_idle();
	flash_write_disable();
}


/************************************************************************
 * function    : flash_4read_bytes
 * description : rad data from flash using 4-line
 * input	   : uint8_t* buffer:	buffer which store the data
 *				 uint32_t read_addr: the starting address of data
 *				 uint16_t len: length of the buffer to read
 * return	   : none
 ************************************************************************/
void flash_4read_bytes(uint8_t* buffer,uint32_t read_addr,uint16_t len)
{
	flash_write_enable(); 
	flash_quad_enable();
	flash_wait_till_idle();
	qspi_line_set(QSPI_LINE_4);
	qspi_config(ADDR_WIDTH_3 | ADDR_LINE_SIZE_R | Para_No1R_1 | 
				Para_Ord1R_AFTER | Para_No2R_2 | Para_Ord2R_AFTER );
	qspi_set_para_read(0xff, 0);
	qspi_set_cmd(QSPI_CMD_RD, QUAD_IO_FAST_READ);

	for(int i=0;i<len;i++)
	{
		buffer[i] = qspi_read_byte(read_addr+i);
	}
	flash_write_enable(); 
	flash_quad_disable();
	flash_wait_till_idle();
}
/************************************************************************
 * function    : flash_enter_quad_fast_mode
 * description : set flash into 4-line fast read mode
 * input	   : none
 * return	   : none
 ************************************************************************/
void flash_enter_quad_fast_mode(void)
{
	flash_write_enable(); 
	flash_quad_enable();
	flash_wait_till_idle();
	
	qspi_line_set(QSPI_LINE_4);
	qspi_config(	ADDR_WIDTH_3 | ADDR_LINE_SIZE_R | Para_No1R_1 | 
					Para_Ord1R_AFTER | Para_No2R_2 | Para_Ord2R_AFTER);
	qspi_set_para_read( 0xff, 0);	
	qspi_set_cmd( QSPI_CMD_RD, QUAD_IO_FAST_READ);
	
}

/************************************************************************
 * function    : flash_read_device_id
 * description : read flash's deive number	
 * input	   : none
 * return	   : manufacture id
 ************************************************************************/
uint16_t flash_read_device_id(void)
{
	uint16_t manufacture_id = 0;
	
	qspi_config(ADDR_WIDTH_3);
	qspi_line_set(QSPI_LINE_1);
	qspi_set_cmd( QSPI_CMD_RD, READ_DEVICE_ID);	
	manufacture_id = qspi_read_word(0);		
	
	return manufacture_id;
}

/************************************************************************
 * function    : flash_read_id
 * description : read flash's identifier	
 * input	   : none
 * return	   : flash id
 ************************************************************************/
uint32_t flash_read_id(void)
{
	uint32_t flash_id = 0;
	
	qspi_line_set(QSPI_LINE_1);
	qspi_set_cmd( QSPI_CMD_RD, READ_DATA_BYTES);
	flash_wait_till_idle();
	qspi_set_cmd( QSPI_CMD_RD, FLASH_ID);		
	flash_id = qspi_read_dword(0);	
	flash_id = flash_id>>8;
	
	return flash_id;
}

/************************************************************************
 * function    : erase_flash_sector
 * description : erase flash sector
 * input	   : starting address of the sector 
 * return	   : none
 ************************************************************************/
void erase_flash_sector(uint32_t erase_add)
{
	flash_write_enable();
	qspi_config(WRITE_NO_DATA | ADDR_WIDTH_3);
	qspi_line_set(QSPI_LINE_1);
	qspi_set_cmd( QSPI_CMD_WR, SECTOR_ERASE);				
	qspi_set_program_time(0xfa0);	
	qspi_write_byte( erase_add, 0x01);						
	flash_wait_till_idle();
}


