| /* |
| * Copyright (c) 2018 Savoir-Faire Linux. |
| * |
| * This driver is heavily inspired from the spi_flash_w25qxxdv.c SPI NOR driver. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <errno.h> |
| #include <flash.h> |
| #include <spi.h> |
| #include <init.h> |
| #include <string.h> |
| #include "spi_nor.h" |
| #include "flash_priv.h" |
| |
| #define SZ_256 0x100 |
| #define SZ_512 0x200 |
| #define SZ_1024 0x400 |
| #define SZ_4K 0x1000 |
| #define SZ_32K 0x8000 |
| #define SZ_64K 0x10000 |
| |
| #define SPI_NOR_PAGE_SIZE 256 |
| |
| #define MASK_256 0xFF |
| #define MASK_4K 0xFFF |
| #define MASK_32K 0x7FFF |
| #define MASK_64K 0xFFFF |
| |
| #define SPI_NOR_MAX_ADDR_WIDTH 4 |
| #define SECTORS_COUNT ((DT_JEDEC_SPI_NOR_0_SIZE / 8) \ |
| / CONFIG_SPI_NOR_SECTOR_SIZE) |
| |
| #define JEDEC_ID(x) \ |
| { \ |
| ((x) >> 16) & 0xFF, \ |
| ((x) >> 8) & 0xFF, \ |
| (x) & 0xFF, \ |
| } |
| |
| /** |
| * struct spi_nor_data - Structure for defining the SPI NOR access |
| * @spi: The SPI device |
| * @spi_cfg: The SPI configuration |
| * @cs_ctrl: The GPIO pin used to emulate the SPI CS if required |
| * @sem: The semaphore to access to the flash |
| */ |
| struct spi_nor_data { |
| struct device *spi; |
| struct spi_config spi_cfg; |
| #ifdef DT_JEDEC_SPI_NOR_0_CS_GPIO_CONTROLLER |
| struct spi_cs_control cs_ctrl; |
| #endif /* DT_JEDEC_SPI_NOR_0_CS_GPIO_CONTROLLER */ |
| struct k_sem sem; |
| }; |
| |
| #if defined(CONFIG_MULTITHREADING) |
| #define SYNC_INIT() k_sem_init( \ |
| &((struct spi_nor_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 |
| |
| /* |
| * @brief Send an SPI command |
| * |
| * @param dev Device struct |
| * @param opcode The command to send |
| * @param is_addressed A flag to define if the command is addressed |
| * @param addr The address to send |
| * @param data The buffer to store or read the value |
| * @param length The size of the buffer |
| * @param is_write A flag to define if it's a read or a write command |
| * @return 0 on success, negative errno code otherwise |
| */ |
| static int spi_nor_access(const struct device *const dev, |
| u8_t opcode, bool is_addressed, off_t addr, |
| void *data, size_t length, bool is_write) |
| { |
| struct spi_nor_data *const driver_data = dev->driver_data; |
| |
| u8_t buf[4] = { |
| opcode, |
| (addr & 0xFF0000) >> 16, |
| (addr & 0xFF00) >> 8, |
| (addr & 0xFF), |
| }; |
| |
| struct spi_buf spi_buf[2] = { |
| { |
| .buf = buf, |
| .len = (is_addressed) ? 4 : 1, |
| }, |
| { |
| .buf = data, |
| .len = length |
| } |
| }; |
| const struct spi_buf_set tx_set = { |
| .buffers = spi_buf, |
| .count = (length) ? 2 : 1 |
| }; |
| |
| const struct spi_buf_set rx_set = { |
| .buffers = spi_buf, |
| .count = 2 |
| }; |
| |
| if (is_write) { |
| return spi_write(driver_data->spi, |
| &driver_data->spi_cfg, &tx_set); |
| } |
| |
| return spi_transceive(driver_data->spi, |
| &driver_data->spi_cfg, &tx_set, &rx_set); |
| } |
| |
| #define spi_nor_cmd_read(dev, opcode, dest, length) \ |
| spi_nor_access(dev, opcode, false, 0, dest, length, false) |
| #define spi_nor_cmd_addr_read(dev, opcode, addr, dest, length) \ |
| spi_nor_access(dev, opcode, true, addr, dest, length, false) |
| #define spi_nor_cmd_write(dev, opcode) \ |
| spi_nor_access(dev, opcode, false, 0, NULL, 0, true) |
| #define spi_nor_cmd_addr_write(dev, opcode, addr, src, length) \ |
| spi_nor_access(dev, opcode, true, addr, (void *)src, length, true) |
| |
| /** |
| * @brief Retrieve the Flash JEDEC ID and compare it with the one expected |
| * |
| * @param dev The device structure |
| * @param flash_id The flash info structure which contains the expected JEDEC ID |
| * @return 0 on success, negative errno code otherwise |
| */ |
| static inline int spi_nor_read_id(struct device *dev, |
| const struct spi_nor_config *const flash_id) |
| { |
| u8_t buf[SPI_NOR_MAX_ID_LEN]; |
| |
| if (spi_nor_cmd_read(dev, SPI_NOR_CMD_RDID, buf, |
| SPI_NOR_MAX_ID_LEN) != 0) { |
| return -EIO; |
| } |
| |
| if (memcmp(flash_id->id, buf, SPI_NOR_MAX_ID_LEN) != 0) { |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Wait until the flash is ready |
| * |
| * @param dev The device structure |
| * @return 0 on success, negative errno code otherwise |
| */ |
| static int spi_nor_wait_until_ready(struct device *dev) |
| { |
| int ret; |
| u8_t reg; |
| |
| do { |
| ret = spi_nor_cmd_read(dev, SPI_NOR_CMD_RDSR, ®, 1); |
| } while (!ret && (reg & SPI_NOR_WIP_BIT)); |
| |
| return ret; |
| } |
| |
| static int spi_nor_read(struct device *dev, off_t addr, void *dest, |
| size_t size) |
| { |
| struct spi_nor_data *const driver_data = dev->driver_data; |
| const struct spi_nor_config *params = dev->config->config_info; |
| int ret; |
| |
| /* should be between 0 and flash size */ |
| if ((addr < 0) || (addr + size) > (params->sector_size |
| * params->n_sectors)) { |
| return -EINVAL; |
| } |
| |
| SYNC_LOCK(); |
| |
| spi_nor_wait_until_ready(dev); |
| |
| ret = spi_nor_cmd_addr_read(dev, SPI_NOR_CMD_READ, addr, dest, size); |
| |
| SYNC_UNLOCK(); |
| return ret; |
| } |
| |
| static int spi_nor_write(struct device *dev, off_t addr, const void *src, |
| size_t size) |
| { |
| struct spi_nor_data *const driver_data = dev->driver_data; |
| const struct spi_nor_config *params = dev->config->config_info; |
| int ret; |
| |
| /* should be between 0 and flash size */ |
| if ((addr < 0) || ((size + addr) > (params->sector_size * |
| params->n_sectors))) { |
| return -EINVAL; |
| } |
| |
| SYNC_LOCK(); |
| |
| while (size > 0) { |
| size_t to_write = size; |
| |
| /* Don't write more than a page. */ |
| if (to_write >= SPI_NOR_PAGE_SIZE) { |
| to_write = SPI_NOR_PAGE_SIZE; |
| } |
| |
| /* Don't write across a page boundary */ |
| if (((addr + to_write - 1U) / SPI_NOR_PAGE_SIZE) |
| != (addr / SPI_NOR_PAGE_SIZE)) { |
| to_write = SPI_NOR_PAGE_SIZE - (addr % SPI_NOR_PAGE_SIZE); |
| } |
| |
| spi_nor_cmd_write(dev, SPI_NOR_CMD_WREN); |
| ret = spi_nor_cmd_addr_write(dev, SPI_NOR_CMD_PP, addr, |
| src, to_write); |
| if (ret != 0) { |
| SYNC_UNLOCK(); |
| return ret; |
| } |
| |
| size -= to_write; |
| src = (const u8_t *)src + to_write; |
| addr += to_write; |
| |
| spi_nor_wait_until_ready(dev); |
| } |
| |
| SYNC_UNLOCK(); |
| return 0; |
| } |
| |
| static int spi_nor_erase(struct device *dev, off_t addr, size_t size) |
| { |
| struct spi_nor_data *const driver_data = dev->driver_data; |
| const struct spi_nor_config *params = dev->config->config_info; |
| |
| /* should be between 0 and flash size */ |
| if ((addr < 0) || ((size + addr) > |
| (params->sector_size * params->n_sectors))) { |
| return -ENODEV; |
| } |
| |
| SYNC_LOCK(); |
| |
| while (size) { |
| /* write enable */ |
| spi_nor_cmd_write(dev, SPI_NOR_CMD_WREN); |
| |
| if (size == (params->sector_size * params->n_sectors)) { |
| /* chip erase */ |
| spi_nor_cmd_write(dev, SPI_NOR_CMD_CE); |
| size -= (params->sector_size * params->n_sectors); |
| } else if ((DT_JEDEC_SPI_NOR_0_ERASE_BLOCK_SIZE == SZ_64K) |
| && (size >= SZ_64K) |
| && ((addr & MASK_64K) == 0)) { |
| /* 64 KiB block erase */ |
| spi_nor_cmd_addr_write(dev, SPI_NOR_CMD_BE, addr, |
| NULL, 0); |
| addr += SZ_64K; |
| size -= SZ_64K; |
| } else if ((DT_JEDEC_SPI_NOR_0_ERASE_BLOCK_SIZE == SZ_32K) |
| && (size >= SZ_32K) |
| && ((addr & MASK_32K) == 0)) { |
| /* 32 KiB block erase */ |
| spi_nor_cmd_addr_write(dev, SPI_NOR_CMD_BE_32K, addr, |
| NULL, 0); |
| addr += SZ_32K; |
| size -= SZ_32K; |
| } else if ((size >= params->sector_size) && |
| ((addr & (params->sector_size - 1)) == 0)) { |
| /* sector erase */ |
| spi_nor_cmd_addr_write(dev, SPI_NOR_CMD_SE, addr, |
| NULL, 0); |
| addr += params->sector_size; |
| size -= params->sector_size; |
| } else { |
| /* minimal erase size is at least a sector size */ |
| SYNC_UNLOCK(); |
| return -EINVAL; |
| } |
| |
| spi_nor_wait_until_ready(dev); |
| } |
| |
| SYNC_UNLOCK(); |
| |
| return 0; |
| } |
| |
| static int spi_nor_write_protection_set(struct device *dev, bool write_protect) |
| { |
| struct spi_nor_data *const driver_data = dev->driver_data; |
| int ret; |
| |
| SYNC_LOCK(); |
| |
| spi_nor_wait_until_ready(dev); |
| |
| ret = spi_nor_cmd_write(dev, (write_protect) ? |
| SPI_NOR_CMD_WRDI : SPI_NOR_CMD_WREN); |
| |
| SYNC_UNLOCK(); |
| |
| return ret; |
| } |
| |
| /** |
| * @brief Configure the flash |
| * |
| * @param dev The flash device structure |
| * @param info The flash info structure |
| * @return 0 on success, negative errno code otherwise |
| */ |
| static int spi_nor_configure(struct device *dev) |
| { |
| struct spi_nor_data *data = dev->driver_data; |
| const struct spi_nor_config *params = dev->config->config_info; |
| |
| data->spi = device_get_binding(DT_JEDEC_SPI_NOR_0_BUS_NAME); |
| if (!data->spi) { |
| return -EINVAL; |
| } |
| |
| data->spi_cfg.frequency = DT_JEDEC_SPI_NOR_0_SPI_MAX_FREQUENCY; |
| data->spi_cfg.operation = SPI_WORD_SET(8); |
| data->spi_cfg.slave = DT_JEDEC_SPI_NOR_0_BASE_ADDRESS; |
| |
| #ifdef DT_JEDEC_SPI_NOR_0_CS_GPIO_CONTROLLER |
| data->cs_ctrl.gpio_dev = |
| device_get_binding(DT_JEDEC_SPI_NOR_0_CS_GPIO_CONTROLLER); |
| if (!data->cs_ctrl.gpio_dev) { |
| return -ENODEV; |
| } |
| |
| data->cs_ctrl.gpio_pin = DT_JEDEC_SPI_NOR_0_CS_GPIO_PIN; |
| data->cs_ctrl.delay = CONFIG_SPI_NOR_CS_WAIT_DELAY; |
| |
| data->spi_cfg.cs = &data->cs_ctrl; |
| #endif /* DT_JEDEC_SPI_NOR_0_CS_GPIO_CONTROLLER */ |
| |
| /* now the spi bus is configured, we can verify the flash id */ |
| if (spi_nor_read_id(dev, params) != 0) { |
| return -ENODEV; |
| } |
| |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Initialize and configure the flash |
| * |
| * @param name The flash name |
| * @return 0 on success, negative errno code otherwise |
| */ |
| static int spi_nor_init(struct device *dev) |
| { |
| SYNC_INIT(); |
| |
| return spi_nor_configure(dev); |
| } |
| |
| #if defined(CONFIG_FLASH_PAGE_LAYOUT) |
| static const struct flash_pages_layout dev_layout = { |
| .pages_count = DT_JEDEC_SPI_NOR_0_SIZE / 8 / DT_JEDEC_SPI_NOR_0_ERASE_BLOCK_SIZE, |
| .pages_size = DT_JEDEC_SPI_NOR_0_ERASE_BLOCK_SIZE, |
| }; |
| |
| static void spi_nor_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_nor_api = { |
| .read = spi_nor_read, |
| .write = spi_nor_write, |
| .erase = spi_nor_erase, |
| .write_protection = spi_nor_write_protection_set, |
| #if defined(CONFIG_FLASH_PAGE_LAYOUT) |
| .page_layout = spi_nor_pages_layout, |
| #endif |
| .write_block_size = DT_JEDEC_SPI_NOR_0_WRITE_BLOCK_SIZE, |
| }; |
| |
| static const struct spi_nor_config flash_id = { |
| .id = { |
| DT_JEDEC_SPI_NOR_0_JEDEC_ID_0, |
| DT_JEDEC_SPI_NOR_0_JEDEC_ID_1, |
| DT_JEDEC_SPI_NOR_0_JEDEC_ID_2, |
| }, |
| .page_size = CONFIG_SPI_NOR_PAGE_SIZE, |
| .sector_size = CONFIG_SPI_NOR_SECTOR_SIZE, |
| .n_sectors = SECTORS_COUNT, |
| }; |
| |
| static struct spi_nor_data spi_nor_memory_data; |
| |
| DEVICE_AND_API_INIT(spi_flash_memory, DT_JEDEC_SPI_NOR_0_LABEL, |
| &spi_nor_init, &spi_nor_memory_data, |
| &flash_id, POST_KERNEL, CONFIG_SPI_NOR_INIT_PRIORITY, |
| &spi_nor_api); |