/******************************************************************************* * @file w25q32.c * @brief W25Q32RV External SPI Flash Driver (HW SPI via spi2_bus) ******************************************************************************/ #include "w25q32.h" #include "../../debug_print.h" #include "../../spi2_bus.h" #include "nrf_gpio.h" #include "nrf_delay.h" #include /*============================================================================== * 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 /*============================================================================== * CS CONTROL (manual GPIO - P0.24) *============================================================================*/ 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); } /*============================================================================== * SPI BUS HELPERS *============================================================================*/ static void ensure_spi_ready(void) { if (!spi2_bus_is_initialized()) { spi2_bus_init(); } } /*============================================================================== * 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"); } /*============================================================================== * PRIVATE HELPERS *============================================================================*/ static uint8_t read_status(void) { uint8_t tx[2] = { CMD_RDSR, 0xFF }; uint8_t rx[2] = { 0 }; ret_code_t err; cs_low(); err = spi2_bus_transfer(tx, 2, rx, 2); cs_high(); if (err != NRF_SUCCESS) { DBG_PRINTF("[W25Q] read_status ERR=%d\r\n", err); } return rx[1]; } static void wait_ready(void) { while (read_status() & SR_BUSY) { nrf_delay_ms(1); } } static void write_enable(void) { uint8_t cmd = CMD_WREN; cs_low(); spi2_bus_transfer(&cmd, 1, NULL, 0); cs_high(); } /*============================================================================== * PUBLIC FUNCTIONS *============================================================================*/ bool w25q32_init(void) { DBG_PRINTF("[W25Q] init (HW SPI)\r\n"); w25q32_power_on(); /* CS pin setup */ nrf_gpio_cfg_output(W25Q_CS_PIN); nrf_gpio_pin_set(W25Q_CS_PIN); ensure_spi_ready(); nrf_delay_ms(5); return w25q32_check_jedec(); } bool w25q32_check_jedec(void) { uint8_t tx[4] = { CMD_JEDEC_ID, 0xFF, 0xFF, 0xFF }; uint8_t rx[4] = { 0 }; ensure_spi_ready(); cs_low(); spi2_bus_transfer(tx, 4, rx, 4); cs_high(); /* rx[0] = dummy (during CMD), rx[1]=mfr, rx[2]=type, rx[3]=cap */ DBG_PRINTF("[W25Q] JEDEC = %02X %02X %02X\r\n", rx[1], rx[2], rx[3]); /* Winbond = 0xEF, W25Q32 = 0x40 0x16 */ return (rx[1] == 0xEF); } void w25q32_read(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd[4]; DBG_PRINTF("[W25Q] read addr=0x%06X len=%u\r\n", addr, len); ensure_spi_ready(); cmd[0] = CMD_READ; cmd[1] = (uint8_t)(addr >> 16); cmd[2] = (uint8_t)(addr >> 8); cmd[3] = (uint8_t)(addr); cs_low(); /* Send command + address */ spi2_bus_transfer(cmd, 4, NULL, 0); /* Read data - send dummy bytes, receive into buf */ while (len > 0) { uint8_t chunk = (len > 255) ? 255 : (uint8_t)len; uint8_t dummy[255]; memset(dummy, 0xFF, chunk); spi2_bus_transfer(dummy, chunk, buf, chunk); buf += chunk; len -= chunk; } cs_high(); } void w25q32_write(uint32_t addr, const uint8_t *buf, uint32_t len) { uint8_t cmd[255]; DBG_PRINTF("[W25Q] write addr=0x%06X len=%u\r\n", addr, len); ensure_spi_ready(); while (len) { uint32_t page = 256 - (addr & 0xFF); if (page > len) page = len; if (page > 251) page = 251; /* 4 (cmd+addr) + 251 = 255 max */ write_enable(); /* Build command: PP + 3-byte address + data */ cmd[0] = CMD_PP; cmd[1] = (uint8_t)(addr >> 16); cmd[2] = (uint8_t)(addr >> 8); cmd[3] = (uint8_t)(addr); memcpy(&cmd[4], buf, page); cs_low(); spi2_bus_transfer(cmd, (uint8_t)(4 + page), NULL, 0); cs_high(); wait_ready(); DBG_PRINTF("[W25Q] page done %u bytes\r\n", page); addr += page; buf += page; len -= page; } DBG_PRINTF("[W25Q] write OK\r\n"); } void w25q32_sector_erase(uint32_t addr) { uint8_t cmd[4]; ensure_spi_ready(); addr &= ~0xFFF; /* Align to 4KB sector */ write_enable(); cmd[0] = CMD_SECTOR_ERASE; cmd[1] = (uint8_t)(addr >> 16); cmd[2] = (uint8_t)(addr >> 8); cmd[3] = (uint8_t)(addr); cs_low(); spi2_bus_transfer(cmd, 4, NULL, 0); cs_high(); wait_ready(); } void w25q32_chip_erase(void) { uint8_t cmd; ensure_spi_ready(); DBG_PRINTF("[W25Q] Chip erase...\r\n"); write_enable(); cmd = CMD_CHIP_ERASE; cs_low(); spi2_bus_transfer(&cmd, 1, NULL, 0); cs_high(); wait_ready(); DBG_PRINTF("[W25Q] Erase done\r\n"); }