/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : w25qxx.c
  * @brief          : W25QXX flash memory driver source file
  ******************************************************************************
  */
/* USER CODE END Header */

#include "w25qxx.h"

/* Private variables */
static w25qxx_spi_interface_t g_spi_interface;
static w25qxx_device_info_t g_device_info;

/**
 * @brief Wait for W25QXX to be ready
 */
static void w25qxx_wait_ready(void) {
    uint8_t status;
    do {
        status = w25qxx_read_status_reg1();
    } while (status & W25QXX_STATUS_BUSY_BIT);
}

/**
 * @brief Send write enable command
 * @return true if successful, false otherwise
 */
static bool w25qxx_write_enable(void) {
    uint8_t cmd = W25QXX_CMD_WRITE_ENABLE;
    
    g_spi_interface.cs_set(true);
    bool result = g_spi_interface.spi_send(&cmd, 1);
    g_spi_interface.cs_set(false);
    
    return result;
}

/**
 * @brief Initialize W25QXX flash memory
 * @param interface SPI communication interface
 * @return true if initialization is successful, false otherwise
 */
bool w25qxx_init(const w25qxx_spi_interface_t *interface) {
    if (interface == NULL) {
        return false;
    }
    
    /* Save SPI interface */
    g_spi_interface = *interface;
    
    /* Initialize device info */
    g_device_info.manufacturer_id = 0;
    g_device_info.device_id = 0;
    g_device_info.capacity = 0;
    g_device_info.page_size = 256; /* Default page size for W25QXX */
    g_device_info.sector_size = 4096; /* Default sector size */
    g_device_info.block_size = 65536; /* Default block size */
    
    /* Get device ID */
    uint8_t cmd = W25QXX_CMD_RDID;
    uint8_t id_data[3] = {0};
    
    g_spi_interface.cs_set(true);
    if (!g_spi_interface.spi_send(&cmd, 1)) {
        g_spi_interface.cs_set(false);
        return false;
    }
    
    if (!g_spi_interface.spi_receive(id_data, 3)) {
        g_spi_interface.cs_set(false);
        return false;
    }
    g_spi_interface.cs_set(false);
    
    /* Parse device ID */
    g_device_info.manufacturer_id = id_data[0];
    g_device_info.device_id = (id_data[1] << 8) | id_data[2];
    
    /* Set capacity based on device ID */
    switch (g_device_info.device_id) {
        case 0x4013: /* W25Q80 */
            g_device_info.capacity = 1 * 1024 * 1024;
            break;
        case 0x4014: /* W25Q16 */
        case 0x0015: /* W25Q16 in some versions or alternative ID */
            g_device_info.capacity = 2 * 1024 * 1024;
            break;
        case 0x4015: /* W25Q32 */
            g_device_info.capacity = 4 * 1024 * 1024;
            break;
        case 0x4016: /* W25Q64 */
            g_device_info.capacity = 8 * 1024 * 1024;
            break;
        case 0x4017: /* W25Q128 */
            g_device_info.capacity = 16 * 1024 * 1024;
            break;
        case 0x4018: /* W25Q256 */
            g_device_info.capacity = 32 * 1024 * 1024;
            break;
        default:
            return false;
    }
    
    return true;
}

/**
 * @brief Get W25QXX device information
 * @param info Pointer to device information structure to fill
 * @return true if successful, false otherwise
 */
bool w25qxx_get_device_info(w25qxx_device_info_t *info) {
    if (info == NULL) {
        return false;
    }
    
    *info = g_device_info;
    return true;
}

/**
 * @brief Read data from W25QXX flash memory
 * @param address Starting address to read from
 * @param data Pointer to data buffer to store read data
 * @param size Size of data to read
 * @return true if read is successful, false otherwise
 */
bool w25qxx_read(uint32_t address, uint8_t *data, uint32_t size) {
    if (data == NULL || size == 0) {
        return false;
    }
    
    /* Check address range */
    if (address + size > g_device_info.capacity) {
        return false;
    }
    
    uint8_t cmd[4];
    cmd[0] = W25QXX_CMD_READ;
    cmd[1] = (uint8_t)((address >> 16) & 0xFF);
    cmd[2] = (uint8_t)((address >> 8) & 0xFF);
    cmd[3] = (uint8_t)(address & 0xFF);
    
    g_spi_interface.cs_set(true);
    if (!g_spi_interface.spi_send(cmd, 4)) {
        g_spi_interface.cs_set(false);
        return false;
    }
    
    bool result = g_spi_interface.spi_receive(data, size);
    g_spi_interface.cs_set(false);
    
    return result;
}

/**
 * @brief Write a page to W25QXX flash memory
 * @param address Starting address to write to
 * @param data Pointer to data buffer to write
 * @param size Size of data to write
 * @return true if write is successful, false otherwise
 */
static bool w25qxx_write_page(uint32_t address, const uint8_t *data, uint32_t size) {
    if (data == NULL || size == 0) {
        return false;
    }
    
    /* Check page boundary */
    uint32_t page_start = address & 0xFFFFFF00;
    uint32_t page_end = page_start + 255;
    if (address + size > page_end + 1) {
        return false;
    }
    
    /* Send write enable command */
    if (!w25qxx_write_enable()) {
        return false;
    }
    
    /* Send page program command */
    uint8_t cmd[4];
    cmd[0] = W25QXX_CMD_PAGE_PROGRAM;
    cmd[1] = (uint8_t)((address >> 16) & 0xFF);
    cmd[2] = (uint8_t)((address >> 8) & 0xFF);
    cmd[3] = (uint8_t)(address & 0xFF);
    
    g_spi_interface.cs_set(true);
    if (!g_spi_interface.spi_send(cmd, 4)) {
        g_spi_interface.cs_set(false);
        return false;
    }
    
    if (!g_spi_interface.spi_send(data, size)) {
        g_spi_interface.cs_set(false);
        return false;
    }
    
    g_spi_interface.cs_set(false);
    
    /* Wait for write to complete */
    w25qxx_wait_ready();
    
    return true;
}

/**
 * @brief Write data to W25QXX flash memory
 * @param address Starting address to write to
 * @param data Pointer to data buffer to write
 * @param size Size of data to write
 * @return true if write is successful, false otherwise
 */
bool w25qxx_write(uint32_t address, const uint8_t *data, uint32_t size) {
    if (data == NULL || size == 0) {
        return false;
    }
    
    /* Check address range */
    if (address + size > g_device_info.capacity) {
        return false;
    }
    
    uint32_t current_address = address;
    uint32_t remaining_size = size;
    uint32_t page_offset = address % 256;
    uint32_t write_size;
    
    /* Write first partial page if needed */
    if (page_offset > 0) {
        write_size = (256 - page_offset) < remaining_size ? (256 - page_offset) : remaining_size;
        if (!w25qxx_write_page(current_address, data, write_size)) {
            return false;
        }
        current_address += write_size;
        data += write_size;
        remaining_size -= write_size;
    }
    
    /* Write full pages */
    while (remaining_size >= 256) {
        if (!w25qxx_write_page(current_address, data, 256)) {
            return false;
        }
        current_address += 256;
        data += 256;
        remaining_size -= 256;
    }
    
    /* Write last partial page if needed */
    if (remaining_size > 0) {
        if (!w25qxx_write_page(current_address, data, remaining_size)) {
            return false;
        }
    }
    
    return true;
}

/**
 * @brief Erase 4KB block
 * @param address Address within the block to erase
 * @return true if erase is successful, false otherwise
 */
bool w25qxx_erase_block_4kb(uint32_t address) {
    /* Check address range */
    if (address >= g_device_info.capacity) {
        return false;
    }
    
    /* Send write enable command */
    if (!w25qxx_write_enable()) {
        return false;
    }
    
    /* Send block erase command */
    uint8_t cmd[4];
    cmd[0] = W25QXX_CMD_BLOCK_ERASE_4KB;
    cmd[1] = (uint8_t)((address >> 16) & 0xFF);
    cmd[2] = (uint8_t)((address >> 8) & 0xFF);
    cmd[3] = (uint8_t)(address & 0xFF);
    
    g_spi_interface.cs_set(true);
    if (!g_spi_interface.spi_send(cmd, 4)) {
        g_spi_interface.cs_set(false);
        return false;
    }
    g_spi_interface.cs_set(false);
    
    /* Wait for erase to complete */
    w25qxx_wait_ready();
    
    return true;
}

/**
 * @brief Erase 32KB block
 * @param address Address within the block to erase
 * @return true if erase is successful, false otherwise
 */
bool w25qxx_erase_block_32kb(uint32_t address) {
    /* Check address range */
    if (address >= g_device_info.capacity) {
        return false;
    }
    
    /* Send write enable command */
    if (!w25qxx_write_enable()) {
        return false;
    }
    
    /* Send block erase command */
    uint8_t cmd[4];
    cmd[0] = W25QXX_CMD_BLOCK_ERASE_32KB;
    cmd[1] = (uint8_t)((address >> 16) & 0xFF);
    cmd[2] = (uint8_t)((address >> 8) & 0xFF);
    cmd[3] = (uint8_t)(address & 0xFF);
    
    g_spi_interface.cs_set(true);
    if (!g_spi_interface.spi_send(cmd, 4)) {
        g_spi_interface.cs_set(false);
        return false;
    }
    g_spi_interface.cs_set(false);
    
    /* Wait for erase to complete */
    w25qxx_wait_ready();
    
    return true;
}

/**
 * @brief Erase 64KB block
 * @param address Address within the block to erase
 * @return true if erase is successful, false otherwise
 */
bool w25qxx_erase_block_64kb(uint32_t address) {
    /* Check address range */
    if (address >= g_device_info.capacity) {
        return false;
    }
    
    /* Send write enable command */
    if (!w25qxx_write_enable()) {
        return false;
    }
    
    /* Send block erase command */
    uint8_t cmd[4];
    cmd[0] = W25QXX_CMD_BLOCK_ERASE_64KB;
    cmd[1] = (uint8_t)((address >> 16) & 0xFF);
    cmd[2] = (uint8_t)((address >> 8) & 0xFF);
    cmd[3] = (uint8_t)(address & 0xFF);
    
    g_spi_interface.cs_set(true);
    if (!g_spi_interface.spi_send(cmd, 4)) {
        g_spi_interface.cs_set(false);
        return false;
    }
    g_spi_interface.cs_set(false);
    
    /* Wait for erase to complete */
    w25qxx_wait_ready();
    
    return true;
}

/**
 * @brief Erase entire chip
 * @return true if erase is successful, false otherwise
 */
bool w25qxx_erase_chip(void) {
    /* Send write enable command */
    if (!w25qxx_write_enable()) {
        return false;
    }
    
    /* Send chip erase command */
    uint8_t cmd = W25QXX_CMD_CHIP_ERASE;
    
    g_spi_interface.cs_set(true);
    if (!g_spi_interface.spi_send(&cmd, 1)) {
        g_spi_interface.cs_set(false);
        return false;
    }
    g_spi_interface.cs_set(false);
    
    /* Wait for erase to complete */
    w25qxx_wait_ready();
    
    return true;
}

/**
 * @brief Read status register 1
 * @return Status register 1 value
 */
uint8_t w25qxx_read_status_reg1(void) {
    uint8_t cmd = W25QXX_CMD_READ_STATUS_REG1;
    uint8_t status;
    
    g_spi_interface.cs_set(true);
    g_spi_interface.spi_send(&cmd, 1);
    g_spi_interface.spi_receive(&status, 1);
    g_spi_interface.cs_set(false);
    
    return status;
}

/**
 * @brief Read status register 2
 * @return Status register 2 value
 */
uint8_t w25qxx_read_status_reg2(void) {
    uint8_t cmd = W25QXX_CMD_READ_STATUS_REG2;
    uint8_t status;
    
    g_spi_interface.cs_set(true);
    g_spi_interface.spi_send(&cmd, 1);
    g_spi_interface.spi_receive(&status, 1);
    g_spi_interface.cs_set(false);
    
    return status;
}

/**
 * @brief Write status register
 * @param reg1 Status register 1 value
 * @param reg2 Status register 2 value
 * @return true if write is successful, false otherwise
 */
bool w25qxx_write_status_reg(uint8_t reg1, uint8_t reg2) {
    /* Send write enable command */
    if (!w25qxx_write_enable()) {
        return false;
    }
    
    /* Send write status register command */
    uint8_t cmd[3];
    cmd[0] = W25QXX_CMD_WRITE_STATUS_REG;
    cmd[1] = reg1;
    cmd[2] = reg2;
    
    g_spi_interface.cs_set(true);
    bool result = g_spi_interface.spi_send(cmd, 3);
    g_spi_interface.cs_set(false);
    
    /* Wait for write to complete */
    w25qxx_wait_ready();
    
    return result;
}

/**
 * @brief Put W25QXX into power down mode
 * @return true if successful, false otherwise
 */
bool w25qxx_power_down(void) {
    uint8_t cmd = W25QXX_CMD_POWER_DOWN;
    
    g_spi_interface.cs_set(true);
    bool result = g_spi_interface.spi_send(&cmd, 1);
    g_spi_interface.cs_set(false);
    
    /* Wait for power down to complete */
    g_spi_interface.delay_ms(1);
    
    return result;
}

/**
 * @brief Release W25QXX from power down mode
 * @return true if successful, false otherwise
 */
bool w25qxx_release_power_down(void) {
    uint8_t cmd = W25QXX_CMD_RELEASE_POWER_DOWN;
    
    g_spi_interface.cs_set(true);
    bool result = g_spi_interface.spi_send(&cmd, 1);
    g_spi_interface.cs_set(false);
    
    /* Wait for release to complete */
    g_spi_interface.delay_ms(1);
    
    return result;
}