/*******************************************************************************
 * @file    dr_w25q32.c
 * @brief   W25Q32 SPI Flash Driver for nRF52840
 *          Software SPI (bit-bang) implementation
 * @date    2026-03-12
 *
 * @details Bit-bang SPI driver for W25Q32 NOR Flash.
 *          SPI Mode 0 (CPOL=0, CPHA=0):
 *            - SCLK idle LOW
 *            - Data sampled on rising edge
 *            - Data shifted out on falling edge
 *
 *          W25Q32 SPI timing:
 *            CS  ──┐                                    ┌──
 *                  └────────────────────────────────────┘
 *            CLK    ┌──┐  ┌──┐  ┌──┐       ┌──┐  ┌──┐
 *              ─────┘  └──┘  └──┘  └── ... ┘  └──┘  └────
 *            DI  <CMD7><CMD6><CMD5>...<D1 ><D0 >
 *            DO  <Z   ><Z   ><Z   >...<D1 ><D0 >
 ******************************************************************************/

#include "dr_w25q32.h"
#include "nrf_gpio.h"
#include "nrf_delay.h"
#include "debug_print.h"
#include <string.h>

/*==============================================================================
 * PRIVATE DEFINES
 *============================================================================*/

#define DR_PIN_NUM(pin)     ((pin) & 0x1F)
#define DR_PIN_PORT(pin)    (((pin) >> 5) & 0x01)

/* Pin masks for direct register access */
#define FLASH_CS_MASK       (1UL << DR_PIN_NUM(DR_FLASH_PIN_CS))
#define FLASH_SCLK_MASK     (1UL << DR_PIN_NUM(DR_FLASH_PIN_SCLK))
#define FLASH_MISO_MASK     (1UL << DR_PIN_NUM(DR_FLASH_PIN_MISO))
#define FLASH_MOSI_MASK     (1UL << DR_PIN_NUM(DR_FLASH_PIN_MOSI))

/* Port registers - all pins on P0 */
#define FLASH_PORT          NRF_P0  // NRF_P0: 포트 레지스터에 접근하는 베이스 포인터(포트 전체 제어 X)

/* Timeout for busy wait (ms) */
#define BUSY_TIMEOUT_PAGE_PROGRAM   10
#define BUSY_TIMEOUT_SECTOR_ERASE   500
#define BUSY_TIMEOUT_BLOCK_ERASE    2000
#define BUSY_TIMEOUT_CHIP_ERASE     60000

/* W25Q32 JEDEC ID expected values */
#define W25Q32_MANUFACTURER_ID  0xEF
#define W25Q32_MEMORY_TYPE      0x40
#define W25Q32_CAPACITY         0x16

/*==============================================================================
 * PRIVATE VARIABLES
 *============================================================================*/

static bool m_initialized = false;

/*==============================================================================
 * LOW-LEVEL SPI BIT-BANG
 *
 * W25Q32 uses SPI Mode 0:
 *   - CPOL=0: Clock idle LOW
 *   - CPHA=0: Data sampled on rising edge, shifted on falling edge
 *============================================================================*/

static inline void flash_cs_low(void)
{
    FLASH_PORT->OUTCLR = FLASH_CS_MASK;
}

static inline void flash_cs_high(void)
{
    FLASH_PORT->OUTSET = FLASH_CS_MASK;
}

static inline void flash_sclk_low(void)
{
    FLASH_PORT->OUTCLR = FLASH_SCLK_MASK;
}

static inline void flash_sclk_high(void)
{
    FLASH_PORT->OUTSET = FLASH_SCLK_MASK;
}

static inline void flash_mosi_high(void)
{
    FLASH_PORT->OUTSET = FLASH_MOSI_MASK;
}

static inline void flash_mosi_low(void)
{
    FLASH_PORT->OUTCLR = FLASH_MOSI_MASK;
}

static inline uint32_t flash_read_miso(void)
{
    return (FLASH_PORT->IN & FLASH_MISO_MASK) ? 1 : 0;
}

/**
 * @brief Send one byte via SPI (MSB first)
 */
static void spi_send_byte(uint8_t byte)
{
    for (int i = 7; i >= 0; i--)
    {
        /* Set MOSI */
        if (byte & (1 << i))
            flash_mosi_high();
        else
            flash_mosi_low();

        /* Rising edge - data sampled by slave */
        flash_sclk_high();
        __NOP();
        __NOP();

        /* Falling edge */
        flash_sclk_low();
        __NOP();
    }
}

/**
 * @brief Receive one byte via SPI (MSB first)
 * @note Sends 0xFF (MOSI high) while reading
 */
static uint8_t spi_recv_byte(void)
{
    uint8_t byte = 0;

    flash_mosi_high();  /* Keep MOSI high during read */

    for (int i = 7; i >= 0; i--)
    {
        /* Rising edge - sample MISO */
        flash_sclk_high();
        __NOP();
        __NOP();

        if (flash_read_miso())
            byte |= (1 << i);

        /* Falling edge */
        flash_sclk_low();
        __NOP();
    }

    return byte;
}

/**
 * @brief Initialize GPIO pins
 */
static void flash_gpio_init(void)
{
    /* CS: Output, HIGH (deselected) */
    nrf_gpio_cfg_output(DR_FLASH_PIN_CS);
    nrf_gpio_pin_set(DR_FLASH_PIN_CS);

    /* SCLK: Output, LOW (idle for Mode 0) */
    nrf_gpio_cfg_output(DR_FLASH_PIN_SCLK);
    nrf_gpio_pin_clear(DR_FLASH_PIN_SCLK);

    /* MOSI: Output, HIGH */
    nrf_gpio_cfg_output(DR_FLASH_PIN_MOSI);
    nrf_gpio_pin_set(DR_FLASH_PIN_MOSI);

    /* MISO: Input, no pull */
    nrf_gpio_cfg_input(DR_FLASH_PIN_MISO, NRF_GPIO_PIN_NOPULL);
}

/**
 * @brief Send Write Enable command (0x06)
 */
static void flash_write_enable(void)
{
    flash_cs_low();
    spi_send_byte(DR_FLASH_CMD_WRITE_ENABLE);
    flash_cs_high();
    __NOP(); __NOP(); __NOP(); __NOP();
}

/*==============================================================================
 * PUBLIC FUNCTIONS - INITIALIZATION
 *============================================================================*/

dr_flash_err_t dr_w25q32_init(void)
{
    DBG_PRINTF("[FLASH] Init\n");
    flash_gpio_init();
    m_initialized = true;
    DBG_PRINTF("[FLASH] Init OK\n");
    return DR_FLASH_OK;
}

void dr_w25q32_uninit(void)
{
    if (!m_initialized) return;

    DBG_PRINTF("[FLASH] Uninit\n");

    /* CS high (deselect) */
    nrf_gpio_pin_set(DR_FLASH_PIN_CS);

    /* Release pins to default */
    nrf_gpio_cfg_default(DR_FLASH_PIN_CS);

    /* Don't release shared pins (SCLK, MISO, MOSI) -
       they may be in use by ADC121S051 */

    m_initialized = false;
}

bool dr_w25q32_is_initialized(void)
{
    return m_initialized;
}

/*==============================================================================
 * PUBLIC FUNCTIONS - IDENTIFICATION
 *============================================================================*/

dr_flash_err_t dr_w25q32_read_jedec_id(dr_flash_jedec_t *jedec) //JEDEC ID 읽기 (0xEF, 0x40, 0x16)
{
    if (!m_initialized) return DR_FLASH_ERR_NOT_INIT;
    if (!jedec) return DR_FLASH_ERR_INVALID_PARAM;

    flash_cs_low();
    spi_send_byte(DR_FLASH_CMD_READ_JEDEC_ID);
    jedec->manufacturer_id = spi_recv_byte();
    jedec->memory_type     = spi_recv_byte();
    jedec->capacity        = spi_recv_byte();
    flash_cs_high();

    DBG_PRINTF("[FLASH] JEDEC ID: 0x%02X 0x%02X 0x%02X\n",
               jedec->manufacturer_id, jedec->memory_type, jedec->capacity);

    return DR_FLASH_OK;
}

dr_flash_err_t dr_w25q32_read_uid(uint8_t *uid) // 8바이트 고유 ID 읽기
{
    if (!m_initialized) return DR_FLASH_ERR_NOT_INIT;
    if (!uid) return DR_FLASH_ERR_INVALID_PARAM;

    flash_cs_low();
    spi_send_byte(DR_FLASH_CMD_READ_UID);

    /* 4 dummy bytes */
    spi_send_byte(0x00);
    spi_send_byte(0x00);
    spi_send_byte(0x00);
    spi_send_byte(0x00);

    /* 8-byte unique ID */
    for (int i = 0; i < DR_FLASH_UID_LENGTH; i++)
    {
        uid[i] = spi_recv_byte();
    }
    flash_cs_high();

    DBG_PRINTF("[FLASH] UID: %02X%02X%02X%02X%02X%02X%02X%02X\n",
               uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], uid[6], uid[7]);

    return DR_FLASH_OK;
}

/*==============================================================================
 * PUBLIC FUNCTIONS - STATUS
 *============================================================================*/

dr_flash_err_t dr_w25q32_read_status(uint8_t *status)
{
    if (!m_initialized) return DR_FLASH_ERR_NOT_INIT;
    if (!status) return DR_FLASH_ERR_INVALID_PARAM;

    flash_cs_low();
    spi_send_byte(DR_FLASH_CMD_READ_STATUS1);
    *status = spi_recv_byte();
    flash_cs_high();

    return DR_FLASH_OK;
}

bool dr_w25q32_is_busy(void)
{
    uint8_t status = 0;
    dr_w25q32_read_status(&status);
    return (status & DR_FLASH_SR1_BUSY) ? true : false;
}

dr_flash_err_t dr_w25q32_wait_busy(uint32_t timeout_ms)
{
    if (!m_initialized) return DR_FLASH_ERR_NOT_INIT;

    while (timeout_ms > 0)
    {
        if (!dr_w25q32_is_busy())
            return DR_FLASH_OK;

        nrf_delay_ms(1);
        timeout_ms--;
    }

    return DR_FLASH_ERR_TIMEOUT;
}

/*==============================================================================
 * PUBLIC FUNCTIONS - READ
 *============================================================================*/

dr_flash_err_t dr_w25q32_read(uint32_t addr, uint8_t *buf, uint32_t len)    	// 데이터 읽기 (주소, 길이)
{
    if (!m_initialized) return DR_FLASH_ERR_NOT_INIT;
    if (!buf || len == 0) return DR_FLASH_ERR_INVALID_PARAM;
    if (addr + len > DR_FLASH_TOTAL_SIZE) return DR_FLASH_ERR_INVALID_PARAM;

    DBG_PRINTF("[FLASH] Read addr=0x%06X len=%d\n", addr, len);

    flash_cs_low();

    /* Command + 24-bit address */
    spi_send_byte(DR_FLASH_CMD_READ_DATA);
    spi_send_byte((addr >> 16) & 0xFF);
    spi_send_byte((addr >>  8) & 0xFF);
    spi_send_byte((addr      ) & 0xFF);

    /* Read data */
    for (uint32_t i = 0; i < len; i++)
    {
        buf[i] = spi_recv_byte();
    }

    flash_cs_high();

    return DR_FLASH_OK;
}

/*==============================================================================
 * PUBLIC FUNCTIONS - WRITE
 *============================================================================*/

dr_flash_err_t dr_w25q32_write(uint32_t addr, const uint8_t *data, uint32_t len)    // 페이지 쓰기 (최대 256B)
{
    if (!m_initialized) return DR_FLASH_ERR_NOT_INIT;
    if (!data || len == 0 || len > DR_FLASH_PAGE_SIZE)
        return DR_FLASH_ERR_INVALID_PARAM;
    if (addr + len > DR_FLASH_TOTAL_SIZE)
        return DR_FLASH_ERR_INVALID_PARAM;

    /* Check page boundary crossing */
    uint32_t page_offset = addr % DR_FLASH_PAGE_SIZE;
    if (page_offset + len > DR_FLASH_PAGE_SIZE)
        return DR_FLASH_ERR_INVALID_PARAM;

    DBG_PRINTF("[FLASH] Write addr=0x%06X len=%d\n", addr, len);

    /* Write Enable */
    flash_write_enable();

    /* Page Program */
    flash_cs_low();
    spi_send_byte(DR_FLASH_CMD_PAGE_PROGRAM);
    spi_send_byte((addr >> 16) & 0xFF);
    spi_send_byte((addr >>  8) & 0xFF);
    spi_send_byte((addr      ) & 0xFF);

    for (uint32_t i = 0; i < len; i++)
    {
        spi_send_byte(data[i]);
    }

    flash_cs_high();

    /* Wait for programming to complete */
    dr_flash_err_t err = dr_w25q32_wait_busy(BUSY_TIMEOUT_PAGE_PROGRAM);
    if (err != DR_FLASH_OK)
        DBG_PRINTF("[FLASH] Write TIMEOUT!\n");
    return err;
}

/*==============================================================================
 * PUBLIC FUNCTIONS - ERASE
 *============================================================================*/

dr_flash_err_t dr_w25q32_erase_sector(uint32_t addr)    // 4KB 섹터 삭제
{
    if (!m_initialized) return DR_FLASH_ERR_NOT_INIT;
    if (addr >= DR_FLASH_TOTAL_SIZE) return DR_FLASH_ERR_INVALID_PARAM;

    DBG_PRINTF("[FLASH] Erase sector addr=0x%06X\n", addr);

    flash_write_enable();

    flash_cs_low();
    spi_send_byte(DR_FLASH_CMD_SECTOR_ERASE);
    spi_send_byte((addr >> 16) & 0xFF);
    spi_send_byte((addr >>  8) & 0xFF);
    spi_send_byte((addr      ) & 0xFF);
    flash_cs_high();

    dr_flash_err_t err = dr_w25q32_wait_busy(BUSY_TIMEOUT_SECTOR_ERASE);
    if (err != DR_FLASH_OK)
        DBG_PRINTF("[FLASH] Erase sector TIMEOUT!\n");
    return err;
}

dr_flash_err_t dr_w25q32_erase_block_32k(uint32_t addr)     // 블록 삭제
{
    if (!m_initialized) return DR_FLASH_ERR_NOT_INIT;
    if (addr >= DR_FLASH_TOTAL_SIZE) return DR_FLASH_ERR_INVALID_PARAM;

    DBG_PRINTF("[FLASH] Erase block 32K addr=0x%06X\n", addr);

    flash_write_enable();

    flash_cs_low();
    spi_send_byte(DR_FLASH_CMD_BLOCK_ERASE_32K);
    spi_send_byte((addr >> 16) & 0xFF);
    spi_send_byte((addr >>  8) & 0xFF);
    spi_send_byte((addr      ) & 0xFF);
    flash_cs_high();

    dr_flash_err_t err = dr_w25q32_wait_busy(BUSY_TIMEOUT_BLOCK_ERASE);
    if (err != DR_FLASH_OK)
        DBG_PRINTF("[FLASH] Erase block 32K TIMEOUT!\n");
    return err;
}

dr_flash_err_t dr_w25q32_erase_block_64k(uint32_t addr)     // 블록 삭제
{
    if (!m_initialized) return DR_FLASH_ERR_NOT_INIT;
    if (addr >= DR_FLASH_TOTAL_SIZE) return DR_FLASH_ERR_INVALID_PARAM;

    DBG_PRINTF("[FLASH] Erase block 64K addr=0x%06X\n", addr);

    flash_write_enable();

    flash_cs_low();
    spi_send_byte(DR_FLASH_CMD_BLOCK_ERASE_64K);
    spi_send_byte((addr >> 16) & 0xFF);
    spi_send_byte((addr >>  8) & 0xFF);
    spi_send_byte((addr      ) & 0xFF);
    flash_cs_high();

    dr_flash_err_t err = dr_w25q32_wait_busy(BUSY_TIMEOUT_BLOCK_ERASE);
    if (err != DR_FLASH_OK)
        DBG_PRINTF("[FLASH] Erase block 64K TIMEOUT!\n");
    return err;
}

dr_flash_err_t dr_w25q32_chip_erase(void)   // 전체 삭제
{
    if (!m_initialized) return DR_FLASH_ERR_NOT_INIT;

    DBG_PRINTF("[FLASH] Chip erase...\n");

    flash_write_enable();

    flash_cs_low();
    spi_send_byte(DR_FLASH_CMD_CHIP_ERASE);
    flash_cs_high();

    dr_flash_err_t err = dr_w25q32_wait_busy(BUSY_TIMEOUT_CHIP_ERASE);
    if (err == DR_FLASH_OK)
        DBG_PRINTF("[FLASH] Chip erase OK\n");
    else
        DBG_PRINTF("[FLASH] Chip erase TIMEOUT!\n");
    return err;
}

/*==============================================================================
 * PUBLIC FUNCTIONS - POWER MANAGEMENT
 *============================================================================*/

dr_flash_err_t dr_w25q32_deep_powerdown(void)   // 저전력 모드 (~1uA)
{
    if (!m_initialized) return DR_FLASH_ERR_NOT_INIT;

    DBG_PRINTF("[FLASH] Deep power-down\n");

    flash_cs_low();
    spi_send_byte(DR_FLASH_CMD_POWER_DOWN);
    flash_cs_high();

    /* tDP: CS high to deep power-down = 3us max */
    nrf_delay_us(5);

    return DR_FLASH_OK;
}

dr_flash_err_t dr_w25q32_wakeup(void)   // 저전력 모드 해제(깨우기)
{
    if (!m_initialized) return DR_FLASH_ERR_NOT_INIT;

    DBG_PRINTF("[FLASH] Wakeup\n");

    flash_cs_low();
    spi_send_byte(DR_FLASH_CMD_RELEASE_PD);
    flash_cs_high();

    /* tRES1: CS high to standby = 3us max */
    nrf_delay_us(5);

    return DR_FLASH_OK;
}

/*==============================================================================
 * PUBLIC FUNCTIONS - DEBUG / TEST
 *============================================================================*/

bool dr_w25q32_test(void)   // JEDEC ID로 통신 테스트
{
    dr_flash_jedec_t jedec;

    if (dr_w25q32_read_jedec_id(&jedec) != DR_FLASH_OK)
    {
        DBG_PRINTF("[FLASH] Test FAIL - read error\n");
        return false;
    }

    bool pass = (jedec.manufacturer_id == W25Q32_MANUFACTURER_ID &&
                 jedec.memory_type     == W25Q32_MEMORY_TYPE &&
                 jedec.capacity        == W25Q32_CAPACITY);

    DBG_PRINTF("[FLASH] Test %s\n", pass ? "PASS" : "FAIL");
    return pass;
}
