378 lines
8.3 KiB
C
378 lines
8.3 KiB
C
/*
|
|
* 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"
|
|
|
|
static inline int spi_flash_wb_id(struct device *dev)
|
|
{
|
|
struct spi_flash_data *const driver_data = dev->driver_data;
|
|
uint8_t buf[W25QXXDV_LEN_CMD_AND_ID];
|
|
uint32_t temp_data;
|
|
|
|
buf[0] = W25QXXDV_CMD_RDID;
|
|
|
|
if (spi_transceive(driver_data->spi, buf, W25QXXDV_LEN_CMD_AND_ID,
|
|
buf, W25QXXDV_LEN_CMD_AND_ID) != 0) {
|
|
return -EIO;
|
|
}
|
|
|
|
temp_data = ((uint32_t) buf[1]) << 16;
|
|
temp_data |= ((uint32_t) buf[2]) << 8;
|
|
temp_data |= (uint32_t) buf[3];
|
|
|
|
if (temp_data != W25QXXDV_RDID_VALUE) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spi_flash_wb_config(struct device *dev)
|
|
{
|
|
struct spi_flash_data *const driver_data = dev->driver_data;
|
|
struct spi_config config;
|
|
|
|
config.max_sys_freq = CONFIG_SPI_FLASH_W25QXXDV_SPI_FREQ_0;
|
|
|
|
config.config = SPI_WORD(8);
|
|
|
|
if (spi_slave_select(driver_data->spi,
|
|
CONFIG_SPI_FLASH_W25QXXDV_SPI_SLAVE) !=
|
|
0) {
|
|
return -EIO;
|
|
}
|
|
|
|
if (spi_configure(driver_data->spi, &config) != 0) {
|
|
return -EIO;
|
|
}
|
|
|
|
return spi_flash_wb_id(dev);
|
|
}
|
|
|
|
static int spi_flash_wb_reg_read(struct device *dev, uint8_t *data)
|
|
{
|
|
struct spi_flash_data *const driver_data = dev->driver_data;
|
|
uint8_t buf[2];
|
|
|
|
if (spi_transceive(driver_data->spi, data, 2, buf, 2) != 0) {
|
|
return -EIO;
|
|
}
|
|
|
|
memcpy(data, buf, 2);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void wait_for_flash_idle(struct device *dev)
|
|
{
|
|
uint8_t buf[2];
|
|
|
|
buf[0] = W25QXXDV_CMD_RDSR;
|
|
spi_flash_wb_reg_read(dev, buf);
|
|
|
|
while (buf[1] & W25QXXDV_WIP_BIT) {
|
|
buf[0] = W25QXXDV_CMD_RDSR;
|
|
spi_flash_wb_reg_read(dev, buf);
|
|
}
|
|
}
|
|
|
|
static int spi_flash_wb_reg_write(struct device *dev, uint8_t *data)
|
|
{
|
|
struct spi_flash_data *const driver_data = dev->driver_data;
|
|
uint8_t buf;
|
|
|
|
wait_for_flash_idle(dev);
|
|
|
|
if (spi_transceive(driver_data->spi, data, 1,
|
|
&buf /*dummy */, 1) != 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;
|
|
uint8_t *buf = driver_data->buf;
|
|
|
|
if (len > CONFIG_SPI_FLASH_W25QXXDV_MAX_DATA_LEN || offset < 0) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
k_sem_take(&driver_data->sem, K_FOREVER);
|
|
|
|
if (spi_flash_wb_config(dev) != 0) {
|
|
k_sem_give(&driver_data->sem);
|
|
return -EIO;
|
|
}
|
|
|
|
wait_for_flash_idle(dev);
|
|
|
|
buf[0] = W25QXXDV_CMD_READ;
|
|
buf[1] = (uint8_t) (offset >> 16);
|
|
buf[2] = (uint8_t) (offset >> 8);
|
|
buf[3] = (uint8_t) offset;
|
|
|
|
memset(buf + W25QXXDV_LEN_CMD_ADDRESS, 0, len);
|
|
|
|
if (spi_transceive(driver_data->spi, buf, len + W25QXXDV_LEN_CMD_ADDRESS,
|
|
buf, len + W25QXXDV_LEN_CMD_ADDRESS) != 0) {
|
|
k_sem_give(&driver_data->sem);
|
|
return -EIO;
|
|
}
|
|
|
|
memcpy(data, buf + W25QXXDV_LEN_CMD_ADDRESS, len);
|
|
|
|
k_sem_give(&driver_data->sem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spi_flash_wb_write(struct device *dev, off_t offset,
|
|
const void *data, size_t len)
|
|
{
|
|
struct spi_flash_data *const driver_data = dev->driver_data;
|
|
uint8_t *buf = driver_data->buf;
|
|
|
|
if (len > CONFIG_SPI_FLASH_W25QXXDV_MAX_DATA_LEN || offset < 0) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
k_sem_take(&driver_data->sem, K_FOREVER);
|
|
|
|
if (spi_flash_wb_config(dev) != 0) {
|
|
k_sem_give(&driver_data->sem);
|
|
return -EIO;
|
|
}
|
|
|
|
wait_for_flash_idle(dev);
|
|
|
|
buf[0] = W25QXXDV_CMD_RDSR;
|
|
spi_flash_wb_reg_read(dev, buf);
|
|
|
|
if (!(buf[1] & W25QXXDV_WEL_BIT)) {
|
|
k_sem_give(&driver_data->sem);
|
|
return -EIO;
|
|
}
|
|
|
|
wait_for_flash_idle(dev);
|
|
|
|
buf[0] = W25QXXDV_CMD_PP;
|
|
buf[1] = (uint8_t) (offset >> 16);
|
|
buf[2] = (uint8_t) (offset >> 8);
|
|
buf[3] = (uint8_t) offset;
|
|
|
|
memcpy(buf + W25QXXDV_LEN_CMD_ADDRESS, data, len);
|
|
|
|
/* Assume write protection has been disabled. Note that w25qxxdv
|
|
* flash automatically turns on write protection at the completion
|
|
* of each write or erase transaction.
|
|
*/
|
|
if (spi_write(driver_data->spi, buf, len + W25QXXDV_LEN_CMD_ADDRESS) != 0) {
|
|
k_sem_give(&driver_data->sem);
|
|
return -EIO;
|
|
}
|
|
|
|
k_sem_give(&driver_data->sem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spi_flash_wb_write_protection_set(struct device *dev, bool enable)
|
|
{
|
|
struct spi_flash_data *const driver_data = dev->driver_data;
|
|
uint8_t buf = 0;
|
|
|
|
k_sem_take(&driver_data->sem, K_FOREVER);
|
|
|
|
if (spi_flash_wb_config(dev) != 0) {
|
|
k_sem_give(&driver_data->sem);
|
|
return -EIO;
|
|
}
|
|
|
|
wait_for_flash_idle(dev);
|
|
|
|
if (enable) {
|
|
buf = W25QXXDV_CMD_WRDI;
|
|
} else {
|
|
buf = W25QXXDV_CMD_WREN;
|
|
}
|
|
|
|
if (spi_flash_wb_reg_write(dev, &buf) != 0) {
|
|
k_sem_give(&driver_data->sem);
|
|
return -EIO;
|
|
}
|
|
|
|
k_sem_give(&driver_data->sem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
uint8_t buf[W25QXXDV_LEN_CMD_ADDRESS];
|
|
uint8_t erase_opcode;
|
|
uint32_t len;
|
|
|
|
if (offset < 0) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
wait_for_flash_idle(dev);
|
|
|
|
/* write enable */
|
|
buf[0] = W25QXXDV_CMD_WREN;
|
|
spi_flash_wb_reg_write(dev, buf);
|
|
|
|
wait_for_flash_idle(dev);
|
|
|
|
switch (size) {
|
|
case W25QXXDV_SECTOR_SIZE:
|
|
erase_opcode = W25QXXDV_CMD_SE;
|
|
len = W25QXXDV_LEN_CMD_ADDRESS;
|
|
break;
|
|
case W25QXXDV_BLOCK32K_SIZE:
|
|
erase_opcode = W25QXXDV_CMD_BE32K;
|
|
len = W25QXXDV_LEN_CMD_ADDRESS;
|
|
break;
|
|
case W25QXXDV_BLOCK_SIZE:
|
|
erase_opcode = W25QXXDV_CMD_BE;
|
|
len = W25QXXDV_LEN_CMD_ADDRESS;
|
|
break;
|
|
case CONFIG_SPI_FLASH_W25QXXDV_FLASH_SIZE:
|
|
erase_opcode = W25QXXDV_CMD_CE;
|
|
len = 1;
|
|
break;
|
|
default:
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
buf[0] = erase_opcode;
|
|
buf[1] = (uint8_t) (offset >> 16);
|
|
buf[2] = (uint8_t) (offset >> 8);
|
|
buf[3] = (uint8_t) offset;
|
|
|
|
/* 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_write(driver_data->spi, buf, len);
|
|
}
|
|
|
|
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;
|
|
uint8_t *buf = driver_data->buf;
|
|
int ret = 0;
|
|
uint32_t new_offset = offset;
|
|
uint32_t size_remaining = size;
|
|
|
|
if ((offset < 0) || ((offset & W25QXXDV_SECTOR_MASK) != 0) ||
|
|
((size + offset) > CONFIG_SPI_FLASH_W25QXXDV_FLASH_SIZE) ||
|
|
((size & W25QXXDV_SECTOR_MASK) != 0)) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
k_sem_take(&driver_data->sem, K_FOREVER);
|
|
|
|
if (spi_flash_wb_config(dev) != 0) {
|
|
k_sem_give(&driver_data->sem);
|
|
return -EIO;
|
|
}
|
|
|
|
buf[0] = W25QXXDV_CMD_RDSR;
|
|
spi_flash_wb_reg_read(dev, buf);
|
|
|
|
if (!(buf[1] & W25QXXDV_WEL_BIT)) {
|
|
k_sem_give(&driver_data->sem);
|
|
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;
|
|
}
|
|
}
|
|
|
|
k_sem_give(&driver_data->sem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
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,
|
|
};
|
|
|
|
static int spi_flash_init(struct device *dev)
|
|
{
|
|
struct device *spi_dev;
|
|
struct spi_flash_data *data = dev->driver_data;
|
|
int ret;
|
|
|
|
spi_dev = device_get_binding(CONFIG_SPI_FLASH_W25QXXDV_SPI_NAME);
|
|
if (!spi_dev) {
|
|
return -EIO;
|
|
}
|
|
|
|
data->spi = spi_dev;
|
|
|
|
k_sem_init(&data->sem, 0, UINT_MAX);
|
|
k_sem_give(&data->sem);
|
|
|
|
ret = spi_flash_wb_config(dev);
|
|
if (!ret) {
|
|
dev->driver_api = &spi_flash_api;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct spi_flash_data spi_flash_memory_data;
|
|
|
|
DEVICE_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);
|