#include "mik32_hal_pcc.h"
#include "mik32_hal_gpio.h"
#include "mik32_hal_spifi_w25.h"

/*
 * Программа демонстрирует переход из FLASH в RAM и обратно
 * FLASH (QPI+XIP) -> RAM (write_flash) RAM -> FLASH (QPI+XIP)
 * Сначала переводим микросхему W25Q64 в режим работы с периферией,
 * затем переводим ее в режим работы с командами и проверяем
 * успешность перехода чтением JDEC ID (0x60), пробуем
 * стереть последний сектор флеши и затем в первую страницу последнего
 * сектора записываем тестовые данные, затем снова возвращаем режим QPI+XIP
 * и возвращаемся к первоначальной работе из флеш.
 * Все переходы сопровождаются миганием светодиодов.
 * Результат записи во флеш можно увидеть в отладчике по адресу 0х801FF000
 * после возвращения в режим работы с памятью
 */

void SystemClock_Config();
void GPIO_Init();
void flash_write_page_qpi(void);
int SPIFI_WaitIntrqTimeout(uint32_t timeout);
uint32_t  get_flash_usage_end(void);
bool is_sector_free(uint32_t sector_address);
uint8_t read_sreg_1(void);
bool w25q_is_busy(uint32_t timeout);
void write_enable(void);
void w25q_sector_erase(uint32_t address);
void page_program(unsigned int ByteAddress, uint8_t data[], int byte_count);
bool write_to_last_sector(uint8_t *data);


#define TIMEOUT            100000

#define FLASH_TOTAL_SIZE   (2 * 1024 * 1024)
#define FLASH_BASE_ADDRESS 0x80000000
#define SECTOR_SIZE 4096
#define PAGE_SIZE 256

volatile uint8_t jedec_id_qpi[3] = {0};

extern uint32_t __DATA_IMAGE_END__;
extern uint32_t __TEXT_END__;


int main()
{
    SystemClock_Config();
    GPIO_Init();

    flash_write_page_qpi();

    while (1)
    {
    	// если мы попали сюда, значит мы успешно поработали с FLASH
    	// из RAM и снова вернулись к работе программы из FLASH
    	// и в отладчике теперь можно просмотреть результат записи
        HAL_GPIO_TogglePin(GPIO_0, GPIO_PIN_9 | GPIO_PIN_10);
        HAL_DelayMs(700);
    }
}

void SystemClock_Config(void)
{
    PCC_InitTypeDef PCC_OscInit = {0};

    PCC_OscInit.OscillatorEnable = PCC_OSCILLATORTYPE_ALL;
    PCC_OscInit.FreqMon.OscillatorSystem = PCC_OSCILLATORTYPE_OSC32M;
    PCC_OscInit.FreqMon.ForceOscSys = PCC_FORCE_OSC_SYS_UNFIXED;
    PCC_OscInit.FreqMon.Force32KClk = PCC_FREQ_MONITOR_SOURCE_OSC32K;
    PCC_OscInit.AHBDivider = 0;
    PCC_OscInit.APBMDivider = 0;
    PCC_OscInit.APBPDivider = 0;
    PCC_OscInit.HSI32MCalibrationValue = 128;
    PCC_OscInit.LSI32KCalibrationValue = 8;
    PCC_OscInit.RTCClockSelection = PCC_RTC_CLOCK_SOURCE_AUTO;
    PCC_OscInit.RTCClockCPUSelection = PCC_CPU_RTC_CLOCK_SOURCE_OSC32K;
    HAL_PCC_Config(&PCC_OscInit);
}

void GPIO_Init()
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    __HAL_PCC_GPIO_0_CLK_ENABLE();

    GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10;
    GPIO_InitStruct.Mode = HAL_GPIO_MODE_GPIO_OUTPUT;
    GPIO_InitStruct.Pull = HAL_GPIO_PULL_NONE;
    
    HAL_GPIO_Init(GPIO_0, &GPIO_InitStruct);
}


void __attribute__((used, section(".ram_text"))) flash_write_page_qpi(void)
{
  // мы сейчас с флеш точно работаем XIP + QPI, но перешли в ОЗУ и теперь нужно уйти из XIP,
  // т.е работали в режиме памяти, для этого надо перейти в режим периферии
  // сначала делаем сброс текущей команды работы в режиме памяти

	SPIFI_CONFIG->STAT = SPIFI_CONFIG_STAT_RESET_M;

	//выводим QPI из "без команд" в режим "с командами"
    const uint32_t cmd_chip_qpi_w_cmd =
		SPIFI_DIRECTION_INPUT |
		SPIFI_CONFIG_CMD_INTLEN(1) |
		SPIFI_CONFIG_CMD_FIELDFORM(SPIFI_FIELDFORM_ALL_PARALLEL) |
		SPIFI_CONFIG_CMD_FRAMEFORM(SPIFI_FRAMEFORM_3ADDR) |  /* нет кода операции, 3 адреса  */
		SPIFI_CONFIG_CMD_OPCODE(0xEB);

	SPIFI_CONFIG->ADDR  = 0;
	SPIFI_CONFIG->IDATA = 0;
	SPIFI_CONFIG->CMD   = cmd_chip_qpi_w_cmd;
	if(SPIFI_WaitIntrqTimeout(TIMEOUT) != 0){
            SPIFI_CONFIG->STAT |= SPIFI_CONFIG_STAT_INTRQ_M; //  команда выполнена, выставлен IRQ, сбросим IRQ
	     }
	else {
		asm("nop"); // таймаут, что-то пошло не так
       }

	// контрольная проверка принимает ли флеш команды или нет, для этого пробуем прочитать JDEC ID
    const uint32_t cmd_jedec_qpi =
	    SPIFI_DIRECTION_INPUT |
	    SPIFI_CONFIG_CMD_INTLEN(0) |
	    SPIFI_CONFIG_CMD_FIELDFORM(SPIFI_FIELDFORM_ALL_PARALLEL) |
	    SPIFI_CONFIG_CMD_FRAMEFORM(SPIFI_FRAMEFORM_OPCODE) |
	    SPIFI_CONFIG_CMD_OPCODE(0x9F);

    SPIFI_CONFIG->ADDR  = 0;
    SPIFI_CONFIG->IDATA = 0;
    SPIFI_CONFIG->CMD   = cmd_jedec_qpi | SPIFI_CONFIG_CMD_DATALEN(3);
    if(SPIFI_WaitIntrqTimeout(TIMEOUT) != 0){
		 SPIFI_CONFIG->STAT |= SPIFI_CONFIG_STAT_INTRQ_M; // сбросим IRQ
		}
	else {
		asm("nop"); // таймаут, что-то пошло не так
		  }

	  for (int i = 0; i < 3; i++)
	     jedec_id_qpi[i] =  SPIFI_CONFIG->DATA8;

	  // если успешно прочитали JDEC ID, то поморгаем вразнобой
	  if(jedec_id_qpi[1] == 0x60){
		  for(int i=0; i<16; i++)
		      {
		  	     GPIO_0->OUTPUT ^= 1<<9;
		  	     for(int i=0; i<500000; i++)
		  	        {
		  	   	       asm("nop");
		  	        }
		  	     GPIO_0->OUTPUT ^= 1<<10;
		  	     for(int i=0; i<500000; i++)
		  	        {
		  	     	   asm("nop");
		  	        }
		  	    }
	       }
	  for(int i=0; i<500000; i++){
	  	    	   asm("nop");
	  	         }

	//***** пробуем стереть и записать первую страницу последнего сектора ******

	uint8_t my_data[PAGE_SIZE];
	// формируем данные для записи
	for(int i=0; i<PAGE_SIZE; i++)
		my_data[i] = i;

	if (write_to_last_sector(my_data)) {
	     // стирание и запись прошли успешно
	   }

	//*************************************************************************

    //вернем назад QPI из режима "с командами" в режим "без команд"
    const uint32_t cmd_chip_qpi_wo_cmd =
		SPIFI_DIRECTION_INPUT |
		SPIFI_CONFIG_CMD_INTLEN(1) |
		SPIFI_CONFIG_CMD_FIELDFORM(SPIFI_FIELDFORM_ALL_PARALLEL) |
		SPIFI_CONFIG_CMD_FRAMEFORM(SPIFI_FRAMEFORM_OPCODE_3ADDR) |  /* есть код операции, 3 адреса  */
		SPIFI_CONFIG_CMD_OPCODE(0xEB);

	SPIFI_CONFIG->ADDR  = 0;
	SPIFI_CONFIG->IDATA = 0x20;
	SPIFI_CONFIG->CMD  = cmd_chip_qpi_wo_cmd;
	if(SPIFI_WaitIntrqTimeout(TIMEOUT) != 0){
			  SPIFI_CONFIG->STAT |= SPIFI_CONFIG_STAT_INTRQ_M; //  команда выполнена, выставлен IRQ, сбросим IRQ
		 }
	else {
		asm("nop"); // таймаут, что-то пошло не так
		 }

     // а теперь вернем SPIFI в режим работы с памятью
	 // настройки кэш не трогаем, они не изменились

     const uint32_t mcmd_chip_qpi_wo_cmd =
		SPIFI_DIRECTION_INPUT |
		SPIFI_CONFIG_CMD_INTLEN(1) |
		SPIFI_CONFIG_CMD_FIELDFORM(SPIFI_FIELDFORM_ALL_PARALLEL) |
		SPIFI_CONFIG_CMD_FRAMEFORM(SPIFI_FRAMEFORM_3ADDR) |  /* нет кода операции, 3 адреса  */
		SPIFI_CONFIG_CMD_OPCODE(0xEB);

    SPIFI_CONFIG->ADDR  = 0;
    SPIFI_CONFIG->IDATA = 0x20;
    SPIFI_CONFIG->MCMD  = mcmd_chip_qpi_wo_cmd;
    if(SPIFI_WaitIntrqTimeout(TIMEOUT) != 0){
	   SPIFI_CONFIG->STAT |= SPIFI_CONFIG_STAT_INTRQ_M; //  команда выполнена, выставлен IRQ, сбросим IRQ
    }
    else {
	     asm("nop"); // таймаут, что-то пошло не так
    }

    // поморгали перед выходом из ОЗУ, если настройки правильные,
    // то после этого мы вернемся к работе программы из флеш
    GPIO_0->OUTPUT ^= 1<<9 | 1<<10;
    for(int i=0; i<16; i++)
     {
	    GPIO_0->OUTPUT ^= 1<<9 | 1<<10;
	    for(int i=0; i<500000; i++){
		   asm("nop");
		 }
	 }
}

//*****************************************************************************
// Получить конец используемой флеш-памяти
__attribute__((used, section(".ram_text")))
uint32_t  get_flash_usage_end(void) {
    // Используем максимальный адрес между концом кода и концом данных
    uint32_t code_end = (uint32_t)&__TEXT_END__;
    uint32_t data_end = (uint32_t)&__DATA_IMAGE_END__;
    return (code_end > data_end) ? code_end : data_end;
}

//*****************************************************************************
// Проверить, свободен ли сектор
__attribute__((used, section(".ram_text")))
bool is_sector_free(uint32_t sector_address) {
    // Сектор свободен, если он находится за пределами используемой памяти
    uint32_t usage_end = get_flash_usage_end();
    return (sector_address >= usage_end);
}

//*****************************************************************************
__attribute__((used, section(".ram_text")))
uint8_t read_sreg_1(void)
{
    static uint8_t read_sreg = 0;

    const uint32_t cmd_read_sreg1 =
		SPIFI_DIRECTION_INPUT |
		SPIFI_CONFIG_CMD_INTLEN(0) |
		SPIFI_CONFIG_CMD_FIELDFORM(SPIFI_FIELDFORM_ALL_PARALLEL) |
		SPIFI_CONFIG_CMD_FRAMEFORM(SPIFI_FRAMEFORM_OPCODE) |
		SPIFI_CONFIG_CMD_OPCODE(0x05);

	SPIFI_CONFIG->CMD   = cmd_read_sreg1 | SPIFI_CONFIG_CMD_DATALEN(1);
	if(SPIFI_WaitIntrqTimeout(TIMEOUT) != 0){
		 SPIFI_CONFIG->STAT |= SPIFI_CONFIG_STAT_INTRQ_M; // сбросим IRQ
		}
	else {
		asm("nop"); // таймаут, что-то пошло не так
		return 0;
	 }

    read_sreg = SPIFI_CONFIG->DATA8;
    return read_sreg;

}

//*****************************************************************************
__attribute__((used, section(".ram_text")))
bool w25q_is_busy(uint32_t timeout){

 while (timeout-- != 0)
	{
		if ((read_sreg_1() & 1) == 0)
		{
			asm("nop");
			return 0;
		}
	}
	return 1;
}

//*****************************************************************************
__attribute__((used, section(".ram_text")))
void write_enable(void)
{
 	const uint32_t cmd_write_enable =
	    SPIFI_DIRECTION_INPUT |
	    SPIFI_CONFIG_CMD_INTLEN(0) |
	    SPIFI_CONFIG_CMD_FIELDFORM(SPIFI_FIELDFORM_ALL_PARALLEL) |
	    SPIFI_CONFIG_CMD_FRAMEFORM(SPIFI_FRAMEFORM_OPCODE) |
	    SPIFI_CONFIG_CMD_OPCODE(0x06);

    SPIFI_CONFIG->STAT |= SPIFI_CONFIG_STAT_INTRQ_M;
    SPIFI_CONFIG->CMD = cmd_write_enable;
    if(SPIFI_WaitIntrqTimeout(TIMEOUT) == 0)
    {
         return;
    }
}

//*****************************************************************************
__attribute__((used, section(".ram_text")))
void w25q_sector_erase(uint32_t address){

	write_enable();

	const uint32_t cmd_sector_erase_4k =
	    SPIFI_DIRECTION_INPUT |
	    SPIFI_CONFIG_CMD_INTLEN(0) |
	    SPIFI_CONFIG_CMD_FIELDFORM(SPIFI_FIELDFORM_ALL_PARALLEL) |
	    SPIFI_CONFIG_CMD_FRAMEFORM(SPIFI_FRAMEFORM_OPCODE_3ADDR) |
	    SPIFI_CONFIG_CMD_OPCODE(0x20);

	 SPIFI_CONFIG->STAT |= SPIFI_CONFIG_STAT_INTRQ_M;
	 SPIFI_CONFIG->ADDR = address;
	 SPIFI_CONFIG->CMD = cmd_sector_erase_4k;
	 if(SPIFI_WaitIntrqTimeout(TIMEOUT) == 0)
	   {
	       return;
	   }
}

//*****************************************************************************
__attribute__((used, section(".ram_text")))
void page_program(unsigned int ByteAddress, uint8_t data[], int byte_count)  {

    if(byte_count > 256)
    {
       return;
    }

 	SPIFI_CONFIG->STAT |= SPIFI_CONFIG_STAT_INTRQ_M;
    SPIFI_CONFIG->ADDR = ByteAddress;
    SPIFI_CONFIG->IDATA = 0x00;

    const uint32_t cmd_page_program =
        SPIFI_DIRECTION_OUTPUT |
        SPIFI_CONFIG_CMD_INTLEN(0) |
        SPIFI_CONFIG_CMD_FIELDFORM(SPIFI_FIELDFORM_ALL_PARALLEL) |
        SPIFI_CONFIG_CMD_FRAMEFORM(SPIFI_FRAMEFORM_OPCODE_3ADDR) |
        SPIFI_CONFIG_CMD_OPCODE(0x02);
    SPIFI_CONFIG->CMD  = cmd_page_program | SPIFI_CONFIG_CMD_DATALEN(byte_count);

    for(int i = 0; i < byte_count; i++)
    {
        SPIFI_CONFIG->DATA8 = data[i];
    }

    SPIFI_CONFIG->STAT |= SPIFI_CONFIG_STAT_INTRQ_M;
}

//*****************************************************************************
__attribute__((used, section(".ram_text")))
bool write_to_last_sector(uint8_t *data){
	// Определяем адрес последнего сектора
	uint32_t last_sector_address = FLASH_BASE_ADDRESS + FLASH_TOTAL_SIZE - SECTOR_SIZE;

	// Проверяем, свободен ли сектор
	if (!is_sector_free(last_sector_address)) {
		// Сектор занят - нельзя использовать
		return 0;
	}

	// Стираем сектор (это необходимо перед записью)
	w25q_sector_erase(last_sector_address);
	if(w25q_is_busy(TIMEOUT) == 1){
		return 0;
	  }

	write_enable();
	page_program(last_sector_address, data, PAGE_SIZE);
	if(w25q_is_busy(TIMEOUT) == 1){
			return 0;
		}

 return 1;
}

//*****************************************************************************
int __attribute__((used, section(".ram_text")))	SPIFI_WaitIntrqTimeout(uint32_t timeout)
	{
	    while (timeout-- > 0)
	    {
	        if((SPIFI_CONFIG->STAT & SPIFI_CONFIG_STAT_INTRQ_M) != 0)
	        {
	            return 1;
	        }
	    }
	    return 0;
	}

//*****************************************************************************
