/*******************************************************************************
 * @file    w25q32.c
 * @brief   W25Q32RV External SPI Flash Driver (Pure Bit-bang - bypass nrfx)
 ******************************************************************************/

#include "w25q32.h"
#include "../../debug_print.h"
#include "../../spi2_bus.h"
#include "nrf_gpio.h"
#include "nrf_delay.h"

/*==============================================================================
 * COMMANDS
 *============================================================================*/

#define CMD_JEDEC_ID      0x9F
#define CMD_READ          0x03
#define CMD_WREN          0x06
#define CMD_PP            0x02
#define CMD_SECTOR_ERASE  0x20
#define CMD_CHIP_ERASE    0xC7
#define CMD_RDSR          0x05

#define SR_BUSY           0x01

/*==============================================================================
 * BIT-BANG SPI (bypass nrfx_spim completely)
 *============================================================================*/

static inline void cs_low(void)  { nrf_gpio_pin_clear(W25Q_CS_PIN); }
static inline void cs_high(void) { nrf_gpio_pin_set(W25Q_CS_PIN);   }

/*==============================================================================
 * POWER CONTROL - P0.01 + P0.11 both HIGH = W25Q32 power ON
 *============================================================================*/

void w25q32_power_on(void)
{
    nrf_gpio_cfg_output(W25Q_PWR_PIN1);
    nrf_gpio_cfg_output(W25Q_PWR_PIN2);
    nrf_gpio_pin_set(W25Q_PWR_PIN1);   /* P0.01 HIGH */
    nrf_gpio_pin_set(W25Q_PWR_PIN2);   /* P0.11 HIGH */
    nrf_delay_ms(10);  /* Wait for power stabilization */
    DBG_PRINTF("[W25Q] Power ON (P0.%d=H, P0.%d=H)\r\n", W25Q_PWR_PIN1, W25Q_PWR_PIN2);
}

void w25q32_power_off(void)
{
    nrf_gpio_pin_clear(W25Q_PWR_PIN1);
    nrf_gpio_pin_clear(W25Q_PWR_PIN2);
    DBG_PRINTF("[W25Q] Power OFF\r\n");
}

static void bb_spi_init(void)
{
    /* Step 1: Power ON W25Q32 */
    w25q32_power_on();

    /* Step 2: Release nrfx_spim ownership of pins */
    if (spi2_bus_is_initialized()) {
        DBG_PRINTF("[W25Q] Releasing SPI2 bus...\r\n");
        spi2_bus_uninit();
        nrf_delay_ms(1);
    }

    /* Step 3: ADA2200 CS HIGH (deselect) */
    nrf_gpio_cfg_output(13);  /* ADA2200 CS = P0.13 */
    nrf_gpio_pin_set(13);     /* HIGH = deselected */
    DBG_PRINTF("[W25Q] ADA2200 CS(P0.13) = HIGH\r\n");

    /* Step 4: Force pins back to GPIO mode */
    nrf_gpio_cfg_default(W25Q_SCK_PIN);
    nrf_gpio_cfg_default(W25Q_MOSI_PIN);
    nrf_gpio_cfg_default(W25Q_MISO_PIN);
    nrf_gpio_cfg_default(W25Q_CS_PIN);

    /* Step 5: Configure bit-bang SPI with HIGH drive strength */
    nrf_gpio_cfg(W25Q_SCK_PIN,
                 NRF_GPIO_PIN_DIR_OUTPUT,
                 NRF_GPIO_PIN_INPUT_DISCONNECT,
                 NRF_GPIO_PIN_NOPULL,
                 NRF_GPIO_PIN_H0H1,
                 NRF_GPIO_PIN_NOSENSE);

    nrf_gpio_cfg(W25Q_MOSI_PIN,
                 NRF_GPIO_PIN_DIR_OUTPUT,
                 NRF_GPIO_PIN_INPUT_DISCONNECT,
                 NRF_GPIO_PIN_NOPULL,
                 NRF_GPIO_PIN_H0H1,
                 NRF_GPIO_PIN_NOSENSE);

    nrf_gpio_cfg_input(W25Q_MISO_PIN, NRF_GPIO_PIN_PULLUP);

    nrf_gpio_cfg(W25Q_CS_PIN,
                 NRF_GPIO_PIN_DIR_OUTPUT,
                 NRF_GPIO_PIN_INPUT_DISCONNECT,
                 NRF_GPIO_PIN_NOPULL,
                 NRF_GPIO_PIN_H0H1,
                 NRF_GPIO_PIN_NOSENSE);

    nrf_gpio_pin_clear(W25Q_SCK_PIN);  /* Clock idle low (Mode 0) */
    nrf_gpio_pin_set(W25Q_CS_PIN);     /* CS high (deselected) */

    DBG_PRINTF("[W25Q] GPIO: SCK=%d MOSI=%d MISO=%d(dedicated) CS=%d\r\n",
               W25Q_SCK_PIN, W25Q_MOSI_PIN, W25Q_MISO_PIN, W25Q_CS_PIN);

    /* Step 6: Check MISO state */
    int miso_state = nrf_gpio_pin_read(W25Q_MISO_PIN);
    DBG_PRINTF("[W25Q] MISO(P0.%d) = %s\r\n", W25Q_MISO_PIN, miso_state ? "HIGH" : "LOW");
}

static uint8_t bb_spi_transfer_byte(uint8_t tx)
{
    uint8_t rx = 0;
    for (int i = 7; i >= 0; i--) {
        /* Set MOSI */
        if (tx & (1 << i)) {
            nrf_gpio_pin_set(W25Q_MOSI_PIN);
        } else {
            nrf_gpio_pin_clear(W25Q_MOSI_PIN);
        }

        /* Clock high - sample MISO */
        nrf_gpio_pin_set(W25Q_SCK_PIN);
        __NOP(); __NOP(); __NOP(); __NOP();

        if (nrf_gpio_pin_read(W25Q_MISO_PIN)) {
            rx |= (1 << i);
        }

        /* Clock low */
        nrf_gpio_pin_clear(W25Q_SCK_PIN);
        __NOP(); __NOP(); __NOP(); __NOP();
    }
    return rx;
}

/*==============================================================================
 * PRIVATE HELPERS
 *============================================================================*/

static uint8_t read_status(void)
{
    uint8_t status;

    cs_low();
    bb_spi_transfer_byte(CMD_RDSR);
    status = bb_spi_transfer_byte(0xFF);
    cs_high();

    return status;
}

static void wait_ready(void)
{
    while (read_status() & SR_BUSY) {
        nrf_delay_ms(1);
    }
}

/*==============================================================================
 * PUBLIC FUNCTIONS
 *============================================================================*/

bool w25q32_init(void)
{
    DBG_PRINTF("[W25Q] init (bit-bang)\r\n");

    bb_spi_init();
    nrf_delay_ms(5);

    return w25q32_check_jedec();
}

bool w25q32_check_jedec(void)
{
    uint8_t mfr, type, cap;

    cs_low();
    bb_spi_transfer_byte(CMD_JEDEC_ID);
    mfr  = bb_spi_transfer_byte(0xFF);
    type = bb_spi_transfer_byte(0xFF);
    cap  = bb_spi_transfer_byte(0xFF);
    cs_high();

    DBG_PRINTF("[W25Q] JEDEC = %02X %02X %02X\r\n", mfr, type, cap);

    /* Winbond = 0xEF, W25Q32 = 0x40 0x16 */
    return (mfr == 0xEF);
}

void w25q32_read(uint32_t addr, uint8_t *buf, uint32_t len)
{
    cs_low();
    bb_spi_transfer_byte(CMD_READ);
    bb_spi_transfer_byte((uint8_t)(addr >> 16));
    bb_spi_transfer_byte((uint8_t)(addr >> 8));
    bb_spi_transfer_byte((uint8_t)(addr));

    for (uint32_t i = 0; i < len; i++) {
        buf[i] = bb_spi_transfer_byte(0xFF);
    }
    cs_high();
}

void w25q32_write(uint32_t addr, const uint8_t *buf, uint32_t len)
{
    while (len) {
        uint32_t page = 256 - (addr & 0xFF);
        if (page > len) page = len;

        /* Write Enable */
        cs_low();
        bb_spi_transfer_byte(CMD_WREN);
        cs_high();

        /* Page Program */
        cs_low();
        bb_spi_transfer_byte(CMD_PP);
        bb_spi_transfer_byte((uint8_t)(addr >> 16));
        bb_spi_transfer_byte((uint8_t)(addr >> 8));
        bb_spi_transfer_byte((uint8_t)(addr));

        for (uint32_t i = 0; i < page; i++) {
            bb_spi_transfer_byte(buf[i]);
        }
        cs_high();

        wait_ready();

        addr += page;
        buf  += page;
        len  -= page;
    }
}

void w25q32_sector_erase(uint32_t addr)
{
    addr &= ~0xFFF;  /* Align to 4KB sector */

    /* Write Enable */
    cs_low();
    bb_spi_transfer_byte(CMD_WREN);
    cs_high();

    /* Sector Erase */
    cs_low();
    bb_spi_transfer_byte(CMD_SECTOR_ERASE);
    bb_spi_transfer_byte((uint8_t)(addr >> 16));
    bb_spi_transfer_byte((uint8_t)(addr >> 8));
    bb_spi_transfer_byte((uint8_t)(addr));
    cs_high();

    wait_ready();
}

void w25q32_chip_erase(void)
{
    DBG_PRINTF("[W25Q] Chip erase...\r\n");

    /* Write Enable */
    cs_low();
    bb_spi_transfer_byte(CMD_WREN);
    cs_high();

    /* Chip Erase */
    cs_low();
    bb_spi_transfer_byte(CMD_CHIP_ERASE);
    cs_high();

    wait_ready();

    DBG_PRINTF("[W25Q] Erase done\r\n");
}
