| /* |
| * Copyright (c) 2016 Intel Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <errno.h> |
| |
| #include <flash.h> |
| #include <spi.h> |
| #include <init.h> |
| #include <string.h> |
| #include "spi_flash_w25qxxdv_defs.h" |
| #include "spi_flash_w25qxxdv.h" |
| #include "flash_priv.h" |
| |
| #if defined(CONFIG_MULTITHREADING) |
| #define SYNC_INIT() k_sem_init( \ |
| &((struct spi_flash_data *)dev->driver_data)->sem, 1, UINT_MAX) |
| #define SYNC_LOCK() k_sem_take(&driver_data->sem, K_FOREVER) |
| #define SYNC_UNLOCK() k_sem_give(&driver_data->sem) |
| #else |
| #define SYNC_INIT() |
| #define SYNC_LOCK() |
| #define SYNC_UNLOCK() |
| #endif |
| |
| static int spi_flash_wb_access(struct spi_flash_data *ctx, |
| u8_t cmd, bool addressed, off_t offset, |
| void *data, size_t length, bool write) |
| { |
| u8_t access[4]; |
| struct spi_buf buf[2] = { |
| { |
| .buf = access |
| }, |
| { |
| .buf = data, |
| .len = length |
| } |
| }; |
| struct spi_buf_set tx = { |
| .buffers = buf, |
| }; |
| |
| access[0] = cmd; |
| |
| if (addressed) { |
| access[1] = (u8_t) (offset >> 16); |
| access[2] = (u8_t) (offset >> 8); |
| access[3] = (u8_t) offset; |
| |
| buf[0].len = 4; |
| } else { |
| buf[0].len = 1; |
| } |
| |
| tx.count = length ? 2 : 1; |
| |
| if (!write) { |
| const struct spi_buf_set rx = { |
| .buffers = buf, |
| .count = 2 |
| }; |
| |
| return spi_transceive(ctx->spi, &ctx->spi_cfg, &tx, &rx); |
| } |
| |
| return spi_write(ctx->spi, &ctx->spi_cfg, &tx); |
| } |
| |
| static inline int spi_flash_wb_id(struct device *dev) |
| { |
| struct spi_flash_data *const driver_data = dev->driver_data; |
| u32_t temp_data; |
| u8_t buf[3]; |
| |
| if (spi_flash_wb_access(driver_data, W25QXXDV_CMD_RDID, |
| false, 0, buf, 3, false) != 0) { |
| return -EIO; |
| } |
| |
| temp_data = ((u32_t) buf[0]) << 16; |
| temp_data |= ((u32_t) buf[1]) << 8; |
| temp_data |= (u32_t) buf[2]; |
| |
| if (temp_data != CONFIG_SPI_FLASH_W25QXXDV_DEVICE_ID) { |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| static u8_t spi_flash_wb_reg_read(struct device *dev, u8_t reg) |
| { |
| struct spi_flash_data *const driver_data = dev->driver_data; |
| |
| if (spi_flash_wb_access(driver_data, reg, |
| false, 0, ®, 1, false)) { |
| return 0; |
| } |
| |
| return reg; |
| } |
| |
| static inline void wait_for_flash_idle(struct device *dev) |
| { |
| u8_t reg; |
| |
| do { |
| reg = spi_flash_wb_reg_read(dev, W25QXXDV_CMD_RDSR); |
| } while (reg & W25QXXDV_WIP_BIT); |
| } |
| |
| static int spi_flash_wb_reg_write(struct device *dev, u8_t reg) |
| { |
| struct spi_flash_data *const driver_data = dev->driver_data; |
| |
| if (spi_flash_wb_access(driver_data, reg, false, 0, |
| NULL, 0, true) != 0) { |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int spi_flash_wb_read(struct device *dev, off_t offset, void *data, |
| size_t len) |
| { |
| struct spi_flash_data *const driver_data = dev->driver_data; |
| int ret; |
| |
| if (offset < 0) { |
| return -ENODEV; |
| } |
| |
| SYNC_LOCK(); |
| |
| wait_for_flash_idle(dev); |
| |
| ret = spi_flash_wb_access(driver_data, W25QXXDV_CMD_READ, |
| true, offset, data, len, false); |
| |
| SYNC_UNLOCK(); |
| |
| return ret; |
| } |
| |
| static int spi_flash_wb_write_protection_set_with_lock(struct device *dev, |
| bool enable, bool lock) |
| { |
| struct spi_flash_data *const driver_data = dev->driver_data; |
| u8_t reg = 0U; |
| int ret; |
| |
| if (lock) { |
| SYNC_LOCK(); |
| } |
| |
| wait_for_flash_idle(dev); |
| |
| if (enable) { |
| reg = W25QXXDV_CMD_WRDI; |
| } else { |
| reg = W25QXXDV_CMD_WREN; |
| } |
| |
| ret = spi_flash_wb_reg_write(dev, reg); |
| |
| if (lock) { |
| SYNC_UNLOCK(); |
| } |
| |
| return ret; |
| } |
| |
| static int spi_flash_wb_write_protection_set(struct device *dev, bool enable) |
| { |
| return spi_flash_wb_write_protection_set_with_lock(dev, enable, true); |
| } |
| |
| static int spi_flash_wb_program_page(struct device *dev, off_t offset, |
| const void *data, size_t len) |
| { |
| u8_t reg; |
| struct spi_flash_data *const driver_data = dev->driver_data; |
| |
| __ASSERT(len <= CONFIG_SPI_FLASH_W25QXXDV_PAGE_PROGRAM_SIZE, |
| "Maximum length is %d for page programming (actual:%d)", |
| CONFIG_SPI_FLASH_W25QXXDV_PAGE_PROGRAM_SIZE, len); |
| |
| wait_for_flash_idle(dev); |
| |
| reg = spi_flash_wb_reg_read(dev, W25QXXDV_CMD_RDSR); |
| if (!(reg & W25QXXDV_WEL_BIT)) { |
| return -EIO; |
| } |
| |
| wait_for_flash_idle(dev); |
| |
| /* Assume write protection has been disabled. Note that w25qxxdv |
| * flash automatically turns on write protection at the completion |
| * of each write or erase transaction. |
| */ |
| return spi_flash_wb_access(driver_data, W25QXXDV_CMD_PP, |
| true, offset, (void *)data, len, true); |
| |
| } |
| |
| static int spi_flash_wb_write(struct device *dev, off_t offset, |
| const void *data, size_t len) |
| { |
| int ret; |
| off_t page_offset; |
| /* Cast `data` to prevent `void*` arithmetic */ |
| const u8_t *data_ptr = data; |
| struct spi_flash_data *const driver_data = dev->driver_data; |
| |
| if (offset < 0) { |
| return -ENOTSUP; |
| } |
| |
| SYNC_LOCK(); |
| |
| /* Calculate the offset in the first page we write */ |
| page_offset = offset % CONFIG_SPI_FLASH_W25QXXDV_PAGE_PROGRAM_SIZE; |
| |
| /* |
| * Write all data that does not fit into a single programmable page. |
| * By doing this logic, we can safely disable lock protection in |
| * between pages as in case the user did not disable protection then |
| * it will fail on the first write. |
| */ |
| while ((page_offset + len) > |
| CONFIG_SPI_FLASH_W25QXXDV_PAGE_PROGRAM_SIZE) { |
| size_t len_to_write_in_page = |
| CONFIG_SPI_FLASH_W25QXXDV_PAGE_PROGRAM_SIZE - |
| page_offset; |
| |
| ret = spi_flash_wb_program_page(dev, offset, |
| data_ptr, len_to_write_in_page); |
| if (ret) { |
| goto end; |
| } |
| |
| ret = spi_flash_wb_write_protection_set_with_lock(dev, |
| false, false); |
| if (ret) { |
| goto end; |
| } |
| |
| len -= len_to_write_in_page; |
| offset += len_to_write_in_page; |
| data_ptr += len_to_write_in_page; |
| |
| /* |
| * For the subsequent pages we always start at the beginning |
| * of a page |
| */ |
| page_offset = 0; |
| } |
| |
| ret = spi_flash_wb_program_page(dev, offset, data_ptr, len); |
| |
| end: |
| SYNC_UNLOCK(); |
| |
| return ret; |
| } |
| |
| static inline int spi_flash_wb_erase_internal(struct device *dev, |
| off_t offset, size_t size) |
| { |
| struct spi_flash_data *const driver_data = dev->driver_data; |
| bool need_offset = true; |
| u8_t erase_opcode; |
| |
| if (offset < 0) { |
| return -ENOTSUP; |
| } |
| |
| wait_for_flash_idle(dev); |
| |
| /* write enable */ |
| spi_flash_wb_reg_write(dev, W25QXXDV_CMD_WREN); |
| |
| wait_for_flash_idle(dev); |
| |
| switch (size) { |
| case W25QXXDV_SECTOR_SIZE: |
| erase_opcode = W25QXXDV_CMD_SE; |
| break; |
| case W25QXXDV_BLOCK32K_SIZE: |
| erase_opcode = W25QXXDV_CMD_BE32K; |
| break; |
| case W25QXXDV_BLOCK_SIZE: |
| erase_opcode = W25QXXDV_CMD_BE; |
| break; |
| case CONFIG_SPI_FLASH_W25QXXDV_FLASH_SIZE: |
| erase_opcode = W25QXXDV_CMD_CE; |
| need_offset = false; |
| break; |
| default: |
| return -EIO; |
| |
| } |
| |
| /* Assume write protection has been disabled. Note that w25qxxdv |
| * flash automatically turns on write protection at the completion |
| * of each write or erase transaction. |
| */ |
| return spi_flash_wb_access(driver_data, erase_opcode, |
| need_offset, offset, NULL, 0, true); |
| } |
| |
| static int spi_flash_wb_erase(struct device *dev, off_t offset, size_t size) |
| { |
| struct spi_flash_data *const driver_data = dev->driver_data; |
| int ret = 0; |
| u32_t new_offset = offset; |
| u32_t size_remaining = size; |
| u8_t reg; |
| |
| if ((offset < 0) || ((offset & W25QXXDV_SECTOR_MASK) != 0) || |
| ((size + offset) > CONFIG_SPI_FLASH_W25QXXDV_FLASH_SIZE) || |
| ((size & W25QXXDV_SECTOR_MASK) != 0)) { |
| return -ENODEV; |
| } |
| |
| SYNC_LOCK(); |
| |
| reg = spi_flash_wb_reg_read(dev, W25QXXDV_CMD_RDSR); |
| |
| if (!(reg & W25QXXDV_WEL_BIT)) { |
| SYNC_UNLOCK(); |
| return -EIO; |
| } |
| |
| while ((size_remaining >= W25QXXDV_SECTOR_SIZE) && (ret == 0)) { |
| if (size_remaining == CONFIG_SPI_FLASH_W25QXXDV_FLASH_SIZE) { |
| ret = spi_flash_wb_erase_internal(dev, offset, size); |
| break; |
| } |
| |
| if (size_remaining >= W25QXXDV_BLOCK_SIZE) { |
| ret = spi_flash_wb_erase_internal(dev, new_offset, |
| W25QXXDV_BLOCK_SIZE); |
| new_offset += W25QXXDV_BLOCK_SIZE; |
| size_remaining -= W25QXXDV_BLOCK_SIZE; |
| continue; |
| } |
| |
| if (size_remaining >= W25QXXDV_BLOCK32K_SIZE) { |
| ret = spi_flash_wb_erase_internal(dev, new_offset, |
| W25QXXDV_BLOCK32K_SIZE); |
| new_offset += W25QXXDV_BLOCK32K_SIZE; |
| size_remaining -= W25QXXDV_BLOCK32K_SIZE; |
| continue; |
| } |
| |
| if (size_remaining >= W25QXXDV_SECTOR_SIZE) { |
| ret = spi_flash_wb_erase_internal(dev, new_offset, |
| W25QXXDV_SECTOR_SIZE); |
| new_offset += W25QXXDV_SECTOR_SIZE; |
| size_remaining -= W25QXXDV_SECTOR_SIZE; |
| continue; |
| } |
| } |
| |
| SYNC_UNLOCK(); |
| |
| return ret; |
| } |
| |
| #if defined(CONFIG_FLASH_PAGE_LAYOUT) |
| static struct flash_pages_layout dev_layout; |
| |
| static void flash_wb_pages_layout(struct device *dev, |
| const struct flash_pages_layout **layout, |
| size_t *layout_size) |
| { |
| *layout = &dev_layout; |
| *layout_size = 1; |
| } |
| #endif /* CONFIG_FLASH_PAGE_LAYOUT */ |
| |
| static const struct flash_driver_api spi_flash_api = { |
| .read = spi_flash_wb_read, |
| .write = spi_flash_wb_write, |
| .erase = spi_flash_wb_erase, |
| .write_protection = spi_flash_wb_write_protection_set, |
| #if defined(CONFIG_FLASH_PAGE_LAYOUT) |
| .page_layout = flash_wb_pages_layout, |
| #endif |
| .write_block_size = 1, |
| }; |
| |
| static int spi_flash_wb_configure(struct device *dev) |
| { |
| struct spi_flash_data *data = dev->driver_data; |
| |
| data->spi = device_get_binding(DT_WINBOND_W25Q16_0_BUS_NAME); |
| if (!data->spi) { |
| return -EINVAL; |
| } |
| |
| data->spi_cfg.frequency = DT_WINBOND_W25Q16_0_SPI_MAX_FREQUENCY; |
| data->spi_cfg.operation = SPI_WORD_SET(8); |
| data->spi_cfg.slave = DT_WINBOND_W25Q16_0_BASE_ADDRESS; |
| |
| #if defined(CONFIG_SPI_FLASH_W25QXXDV_GPIO_SPI_CS) |
| data->cs_ctrl.gpio_dev = device_get_binding( |
| DT_WINBOND_W25Q16_0_CS_GPIO_CONTROLLER); |
| if (!data->cs_ctrl.gpio_dev) { |
| return -ENODEV; |
| } |
| |
| data->cs_ctrl.gpio_pin = DT_WINBOND_W25Q16_0_CS_GPIO_PIN; |
| data->cs_ctrl.delay = CONFIG_SPI_FLASH_W25QXXDV_GPIO_CS_WAIT_DELAY; |
| |
| data->spi_cfg.cs = &data->cs_ctrl; |
| #endif /* CONFIG_SPI_FLASH_W25QXXDV_GPIO_SPI_CS */ |
| |
| return spi_flash_wb_id(dev); |
| } |
| |
| static int spi_flash_init(struct device *dev) |
| { |
| int ret; |
| |
| SYNC_INIT(); |
| |
| ret = spi_flash_wb_configure(dev); |
| |
| #if defined(CONFIG_FLASH_PAGE_LAYOUT) |
| /* |
| * Note: we use the sector size rather than the page size as some |
| * modules that consumes the flash page layout assume the page |
| * size is the minimal size they can erase. |
| */ |
| dev_layout.pages_count = (CONFIG_SPI_FLASH_W25QXXDV_FLASH_SIZE / W25QXXDV_SECTOR_SIZE); |
| dev_layout.pages_size = W25QXXDV_SECTOR_SIZE; |
| #endif |
| |
| return ret; |
| } |
| |
| static struct spi_flash_data spi_flash_memory_data; |
| |
| DEVICE_AND_API_INIT(spi_flash_memory, CONFIG_SPI_FLASH_W25QXXDV_DRV_NAME, |
| spi_flash_init, &spi_flash_memory_data, NULL, POST_KERNEL, |
| CONFIG_SPI_FLASH_W25QXXDV_INIT_PRIORITY, &spi_flash_api); |