mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-09-02 17:33:00 +00:00
Add spi flash erase boundary check. The erase boundary must be 4K-aligned. So, the flash chip will round down an non-4K aligned address to the nearest 4K boundary automatically. As a result of this, the erase area will start from a different address (if the API caller specifies an non-4K alighed address), even though the size of the erased area is the same as what is specified by the API caller. Let's add a boundary check to make sure the starting address (from the API caller) is 4K aligned. Correct an error in the highest address check. Because of the error, the erase api call will fail if the flash's highest address byte is involved. Jira: ZEP-1277 ZEP-1278 Change-Id: I8b6be57cc8f636f94e5fe67d5a492841a8555005 Signed-off-by: Baohong Liu <baohong.liu@intel.com>
388 lines
8.8 KiB
C
388 lines
8.8 KiB
C
/*
|
|
* Copyright (c) 2016 Intel Corporation.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#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);
|