From 87e54931670aabd6a6f69f773073d21cba72202c Mon Sep 17 00:00:00 2001 From: Savinay Dharmappa Date: Mon, 12 Nov 2018 21:06:24 +0530 Subject: [PATCH] drivers: flash: add a generic spi nor flash driver This driver is inspired from the w25qxxdv SPI NOR flash driver which was already implementing the CFI (Common Flash Interface) for its purpose. To handle other NOR flash a flash id table (as Linux do) which contains the geometry for a few SPI NOR flash based on their JEDEC ID has been introduced. We currently support the following flash: - W25Q80 - W25Q16 - W25Q32 - S25FL216K - MX25UM512 The read and write functions are able to handle more then one page at a time and return the number of bytes read or write. Also because every NOR flash expect to disable the write protection before writing or erasing, the write enable command is now part of the write and erase functions. Signed-off-by: Sebastien Bourdelin Signed-off-by: Savinay Dharmappa --- drivers/flash/CMakeLists.txt | 1 + drivers/flash/Kconfig | 2 + drivers/flash/Kconfig.nor | 76 +++++++ drivers/flash/spi_nor.c | 421 +++++++++++++++++++++++++++++++++++ drivers/flash/spi_nor.h | 40 ++++ 5 files changed, 540 insertions(+) create mode 100644 drivers/flash/Kconfig.nor create mode 100644 drivers/flash/spi_nor.c create mode 100644 drivers/flash/spi_nor.h diff --git a/drivers/flash/CMakeLists.txt b/drivers/flash/CMakeLists.txt index 0716831d227..5bfb8e76f15 100644 --- a/drivers/flash/CMakeLists.txt +++ b/drivers/flash/CMakeLists.txt @@ -1,5 +1,6 @@ zephyr_library() +zephyr_library_sources_ifdef(CONFIG_SPI_NOR spi_nor.c) zephyr_library_sources_ifdef(CONFIG_SPI_FLASH_W25QXXDV spi_flash_w25qxxdv.c) zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_QMSI soc_flash_qmsi.c) zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_NRF soc_flash_nrf.c) diff --git a/drivers/flash/Kconfig b/drivers/flash/Kconfig index 7d786182a71..eb4e66c73d9 100644 --- a/drivers/flash/Kconfig +++ b/drivers/flash/Kconfig @@ -87,6 +87,8 @@ config SOC_FLASH_NIOS2_QSPI_DEV_NAME source "drivers/flash/Kconfig.gecko" +source "drivers/flash/Kconfig.nor" + source "drivers/flash/Kconfig.qmsi" source "drivers/flash/Kconfig.stm32" diff --git a/drivers/flash/Kconfig.nor b/drivers/flash/Kconfig.nor new file mode 100644 index 00000000000..f7f68a99aff --- /dev/null +++ b/drivers/flash/Kconfig.nor @@ -0,0 +1,76 @@ +# +# Copyright (c) 2018 Savoir-Faire Linux. +# +# SPDX-License-Identifier: Apache-2.0 +# + + +menuconfig SPI_NOR + bool + prompt "SPI NOR Flash" + select FLASH_HAS_DRIVER_ENABLED + depends on SPI && FLASH + +if SPI_NOR + +config SPI_NOR_INIT_PRIORITY + int + default 80 + help + Device driver initialization priority. + Device is connected to SPI bus, it has to + be initialized after SPI driver. + +config SPI_NOR_JEDEC_ID + hex "Unique Id to identify SPI flash" + default 0 + help + This is a unique id to identify SPI flash + +config SPI_NOR_PAGE_SIZE + int "Page size of SPI flash" + default 0 + help + This option specifies page size of SPI flash + +config SPI_NOR_SECTOR_SIZE + int "Sector size of SPI flash" + default 0 + help + This option specifies sector size of SPI flash + +config SPI_NOR_SECTORS + int "Number of sectors in SPI flash" + default 0 + help + This option specifies number of sector present + in SPI flash + +config SPI_NOR_BLOCK_SIZE + int "Block size of SPI flash" + default 0 + help + This option specifies block size of SPI flash + +choice SPI_NOR_BLOCK_ERASE_SIZE +prompt "Select Block size for erasing" + +config SPI_NOR_BLOCK_ERASE_32K + bool "Select 32K block erase" + help + This Enables 32K Block erase + +config SPI_NOR_BLOCK_ERASE_64K + bool "Select 64K block erase" + help + This Enables 64K Block erase + +endchoice + +config SPI_NOR_BLOCK_ERASE_SIZE + int + default 2 if SPI_NOR_BLOCK_ERASE_32K + default 4 if SPI_NOR_BLOCK_ERASE_64K + help + This option specifies block size of SPI flash +endif # SPI_NOR diff --git a/drivers/flash/spi_nor.c b/drivers/flash/spi_nor.c new file mode 100644 index 00000000000..fa6f12436f9 --- /dev/null +++ b/drivers/flash/spi_nor.c @@ -0,0 +1,421 @@ +/* + * 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 +#include +#include +#include +#include +#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 MASK_256 0xFF +#define MASK_4K 0xFFF +#define MASK_32K 0x7FFF +#define MASK_64K 0xFFFF + +#define BLOCK_ERASE_32K BIT(1) +#define BLOCK_ERASE_64K BIT(2) + +#define SPI_NOR_MAX_ADDR_WIDTH 4 + +#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; +#if defined(CONFIG_SPI_NOR_GPIO_SPI_CS) + struct spi_cs_control cs_ctrl; +#endif /* CONFIG_SPI_NOR_GPIO_SPI_CS */ + 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, 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; + const u8_t *const p_dest = dest; + int ret; + int to_read; + + /* should be between 0 and flash size */ + if ((addr < 0) || (addr + size) > (params->sector_size + * params->n_sectors)) { + return -EINVAL; + } + + /* start address should be aligned on a page size */ + if ((addr & (params->page_size - 1)) != 0) { + return -EINVAL; + } + + SYNC_LOCK(); + + spi_nor_wait_until_ready(dev); + + while (size) { + to_read = size; + if (size > params->page_size) { + to_read = params->page_size; + } + + ret = spi_nor_cmd_addr_read(dev, SPI_NOR_CMD_READ, addr, + dest, to_read); + if (ret != 0) { + goto err; + } + size -= to_read; + addr += to_read; + dest = (u8_t *)dest + to_read; + } + +err: + SYNC_UNLOCK(); + + return ((u8_t *)dest - p_dest); +} + +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; + const u8_t *const p_src = src; + int ret; + size_t to_write; + + /* should be between 0 and flash size */ + if ((addr < 0) || ((size + addr) > (params->sector_size * + params->n_sectors))) { + return -EINVAL; + } + + /* start address should be aligned on a page size */ + if ((addr & (params->page_size - 1)) != 0) { + return -EINVAL; + } + + SYNC_LOCK(); + + while (size) { + /* write enable */ + spi_nor_cmd_write(dev, SPI_NOR_CMD_WREN); + + to_write = size; + if (size >= params->page_size) { + to_write = params->page_size; + } + + ret = spi_nor_cmd_addr_write(dev, SPI_NOR_CMD_PP, addr, + (void *)src, to_write); + if (ret != 0) { + goto err; + } + size -= to_write; + addr += to_write; + src = (u8_t *)src + to_write; + + spi_nor_wait_until_ready(dev); + } + +err: + SYNC_UNLOCK(); + + return ((u8_t *)src - p_src); +} + +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 ((params->flag & BLOCK_ERASE_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 ((params->flag & BLOCK_ERASE_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(CONFIG_SPI_NOR_SPI_NAME); + if (!data->spi) { + return -EINVAL; + } + + data->spi_cfg.frequency = CONFIG_SPI_NOR_SPI_FREQ_0; + data->spi_cfg.operation = SPI_WORD_SET(8); + data->spi_cfg.slave = CONFIG_SPI_NOR_SPI_SLAVE; + +#if defined(CONFIG_SPI_NOR_GPIO_SPI_CS) + data->cs_ctrl.gpio_dev = + device_get_binding(CONFIG_SPI_NOR_GPIO_SPI_CS_DRV_NAME); + if (!data->cs_ctrl.gpio_dev) { + return -ENODEV; + } + + data->cs_ctrl.gpio_pin = CONFIG_SPI_NOR_GPIO_SPI_CS_PIN; + data->cs_ctrl.delay = CONFIG_SPI_NOR_GPIO_SPI_CS_WAIT_DELAY; + + data->spi_cfg.cs = &data->cs_ctrl; +#endif /* CONFIG_SPI_NOR_GPIO_SPI_CS */ + + /* 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); +} + +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 = (flash_api_pages_layout) + flash_page_layout_not_implemented, +#endif + .write_block_size = 1, +}; + +static const struct spi_nor_config flash_id = { + JEDEC_ID(CONFIG_SPI_NOR_JEDEC_ID), + CONFIG_SPI_NOR_PAGE_SIZE, CONFIG_SPI_NOR_SECTOR_SIZE, + CONFIG_SPI_NOR_SECTORS, CONFIG_SPI_NOR_BLOCK_SIZE, + CONFIG_SPI_NOR_BLOCK_ERASE_SIZE, +}; + +static struct spi_nor_data spi_nor_memory_data; + +DEVICE_AND_API_INIT(spi_flash_memory, CONFIG_SPI_NOR_DRV_NAME, + &spi_nor_init, &spi_nor_memory_data, + &flash_id, POST_KERNEL, CONFIG_SPI_NOR_INIT_PRIORITY, + &spi_nor_api); diff --git a/drivers/flash/spi_nor.h b/drivers/flash/spi_nor.h new file mode 100644 index 00000000000..434b0095990 --- /dev/null +++ b/drivers/flash/spi_nor.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018 Savoir-Faire Linux. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __SPI_NOR_H__ +#define __SPI_NOR_H__ + +#include + +#define SPI_NOR_MAX_ID_LEN 3 + +struct spi_nor_config { + u8_t id[SPI_NOR_MAX_ID_LEN]; + u32_t page_size; + u32_t sector_size; + u32_t n_sectors; + u32_t block_size; + u32_t flag; +}; + +/* Status register bits */ +#define SPI_NOR_WIP_BIT BIT(0) /* Write in progress */ +#define SPI_NOR_WEL_BIT BIT(1) /* Write enable latch */ + +/* Flash opcodes */ +#define SPI_NOR_CMD_WRSR 0x01 /* Write status register */ +#define SPI_NOR_CMD_RDSR 0x05 /* Read status register */ +#define SPI_NOR_CMD_READ 0x03 /* Read data */ +#define SPI_NOR_CMD_WREN 0x06 /* Write enable */ +#define SPI_NOR_CMD_WRDI 0x04 /* Write disable */ +#define SPI_NOR_CMD_PP 0x02 /* Page program */ +#define SPI_NOR_CMD_SE 0x20 /* Sector erase */ +#define SPI_NOR_CMD_BE_32K 0x52 /* Block erase 32KB */ +#define SPI_NOR_CMD_BE 0xD8 /* Block erase */ +#define SPI_NOR_CMD_CE 0xC7 /* Chip erase */ +#define SPI_NOR_CMD_RDID 0x9F /* Read JEDEC ID */ + +#endif /*__SPI_NOR_H__*/