text: /*
   * 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 <drivers/flash.h>
  #include <drivers/spi.h>
  #include <init.h>
  #include <string.h>
  #include <logging/log.h>
  
  #include "spi_nor.h"
  #include "flash_priv.h"
  
  LOG_MODULE_REGISTER(spi_nor, CONFIG_FLASH_LOG_LEVEL);
  
  #define SPI_NOR_MAX_ADDR_WIDTH 4
  
  /**
   * 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_INST_0_JEDEC_SPI_NOR_CS_GPIOS_CONTROLLER
  	struct spi_cs_control cs_ctrl;
  #endif /* DT_INST_0_JEDEC_SPI_NOR_CS_GPIOS_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, &reg, 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->size)) {
  		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->size)) {
  		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->size)) {
  		return -ENODEV;
  	}
  
  	SYNC_LOCK();
  
  	while (size) {
  		/* write enable */
  		spi_nor_cmd_write(dev, SPI_NOR_CMD_WREN);
  
  		if (size == params->size) {
  			/* chip erase */
  			spi_nor_cmd_write(dev, SPI_NOR_CMD_CE);
  			size -= params->size;
  		} else if ((size >= SPI_NOR_BLOCK_SIZE)
  			   && SPI_NOR_IS_BLOCK_ALIGNED(addr)) {
  			/* 64 KiB block erase */
  			spi_nor_cmd_addr_write(dev, SPI_NOR_CMD_BE, addr,
  			NULL, 0);
  			addr += SPI_NOR_BLOCK_SIZE;
  			size -= SPI_NOR_BLOCK_SIZE;
  		} else if ((size >= SPI_NOR_BLOCK32_SIZE)
  			   && SPI_NOR_IS_BLOCK32_ALIGNED(addr)) {
  			/* 32 KiB block erase */
  			spi_nor_cmd_addr_write(dev, SPI_NOR_CMD_BE_32K, addr,
  					       NULL, 0);
  			addr += SPI_NOR_BLOCK32_SIZE;
  			size -= SPI_NOR_BLOCK32_SIZE;
  		} else if ((size >= SPI_NOR_SECTOR_SIZE)
  			   && SPI_NOR_IS_SECTOR_ALIGNED(addr)) {
  			/* sector erase */
  			spi_nor_cmd_addr_write(dev, SPI_NOR_CMD_SE, addr,
  					       NULL, 0);
  			addr += SPI_NOR_SECTOR_SIZE;
  			size -= SPI_NOR_SECTOR_SIZE;
  		} else {
  			/* minimal erase size is at least a sector size */
  			SYNC_UNLOCK();
  			LOG_DBG("unsupported at 0x%lx size %zu", (long)addr,
  				size);
  			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_INST_0_JEDEC_SPI_NOR_BUS_NAME);
  	if (!data->spi) {
  		return -EINVAL;
  	}
  
  	data->spi_cfg.frequency = DT_INST_0_JEDEC_SPI_NOR_SPI_MAX_FREQUENCY;
  	data->spi_cfg.operation = SPI_WORD_SET(8);
  	data->spi_cfg.slave = DT_INST_0_JEDEC_SPI_NOR_BASE_ADDRESS;
  
  #ifdef DT_INST_0_JEDEC_SPI_NOR_CS_GPIOS_CONTROLLER
  	data->cs_ctrl.gpio_dev =
  		device_get_binding(DT_INST_0_JEDEC_SPI_NOR_CS_GPIOS_CONTROLLER);
  	if (!data->cs_ctrl.gpio_dev) {
  		return -ENODEV;
  	}
  
  	data->cs_ctrl.gpio_pin = DT_INST_0_JEDEC_SPI_NOR_CS_GPIOS_PIN;
  	data->cs_ctrl.delay = CONFIG_SPI_NOR_CS_WAIT_DELAY;
  
  	data->spi_cfg.cs = &data->cs_ctrl;
  #endif /* DT_INST_0_JEDEC_SPI_NOR_CS_GPIOS_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)
  
  /* instance 0 size in bytes */
  #define INST_0_BYTES (DT_INST_0_JEDEC_SPI_NOR_SIZE / 8)
  
  /* instance 0 page count */
  #define LAYOUT_PAGES_COUNT (INST_0_BYTES / CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE)
  
  BUILD_ASSERT_MSG((CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE * LAYOUT_PAGES_COUNT)
  		 == INST_0_BYTES,
  		 "SPI_NOR_FLASH_LAYOUT_PAGE_SIZE incompatible with flash size");
  
  static const struct flash_pages_layout dev_layout = {
  	.pages_count = LAYOUT_PAGES_COUNT,
  	.pages_size = CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE,
  };
  #undef LAYOUT_PAGES_COUNT
  
  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 = 1,
  };
  
  static const struct spi_nor_config flash_id = {
  	.id = DT_INST_0_JEDEC_SPI_NOR_JEDEC_ID,
  #ifdef DT_INST_0_JEDEC_SPI_NOR_HAS_BE32K
  	.has_be32k = true,
  #endif /* DT_INST_0_JEDEC_SPI_NOR_HAS_BE32K */
  	.size = DT_INST_0_JEDEC_SPI_NOR_SIZE / 8,
  };
  
  static struct spi_nor_data spi_nor_memory_data;
  
  DEVICE_AND_API_INIT(spi_flash_memory, DT_INST_0_JEDEC_SPI_NOR_LABEL,
  		    &spi_nor_init, &spi_nor_memory_data,
  		    &flash_id, POST_KERNEL, CONFIG_SPI_NOR_INIT_PRIORITY,
  		    &spi_nor_api);
  