zephyr/drivers/flash/flash_npcx_fiu_nor.c
Madhurima Paruchuri 9fab38dd04 drivers: flash: npcx: Update erase function to allow 0x1000 byte erase size
Modify the NPCX driver erase method to allow 0x1000 byte size erases
along with 0x10000 byte size erases based on input parameters

Signed-off-by: Madhurima Paruchuri <mparuchuri@google.com>
2023-08-03 10:29:14 +02:00

628 lines
16 KiB
C

/*
* Copyright (c) 2023 Nuvoton Technology Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nuvoton_npcx_fiu_nor
#include <zephyr/kernel.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/drivers/flash/npcx_flash_api_ex.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/dt-bindings/flash_controller/npcx_fiu_qspi.h>
#include <soc.h>
#ifdef CONFIG_USERSPACE
#include <zephyr/syscall.h>
#include <zephyr/syscall_handler.h>
#endif
#include "flash_npcx_fiu_qspi.h"
#include "spi_nor.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(flash_npcx_fiu_nor, CONFIG_FLASH_LOG_LEVEL);
#define BLOCK_64K_SIZE KB(64)
#define BLOCK_4K_SIZE KB(4)
/* Device config */
struct flash_npcx_nor_config {
/* QSPI bus device for mutex control and bus configuration */
const struct device *qspi_bus;
/* Mapped address for flash read via direct access */
uintptr_t mapped_addr;
/* Size of nor device in bytes, from size property */
uint32_t flash_size;
/* Maximum chip erase time-out in ms */
uint32_t max_timeout;
/* SPI Nor device configuration on QSPI bus */
struct npcx_qspi_cfg qspi_cfg;
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
struct flash_pages_layout layout;
#endif
};
/* Device data */
struct flash_npcx_nor_data {
/* Specific control operation for Quad-SPI Nor Flash */
uint32_t operation;
};
static const struct flash_parameters flash_npcx_parameters = {
.write_block_size = 1,
.erase_value = 0xff,
};
#define DT_INST_QUAD_EN_PROP_OR(inst) \
COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, quad_enable_requirements), \
(_CONCAT(JESD216_DW15_QER_VAL_, \
DT_INST_STRING_TOKEN(inst, quad_enable_requirements))), \
((JESD216_DW15_QER_VAL_NONE)))
static inline bool is_within_region(off_t addr, size_t size, off_t region_start,
size_t region_size)
{
return (addr >= region_start &&
(addr < (region_start + region_size)) &&
((addr + size) <= (region_start + region_size)));
}
static int flash_npcx_uma_transceive(const struct device *dev, struct npcx_uma_cfg *cfg,
uint32_t flags)
{
const struct flash_npcx_nor_config *config = dev->config;
struct flash_npcx_nor_data *data = dev->data;
int ret;
/* Lock SPI bus and configure it if needed */
qspi_npcx_fiu_mutex_lock_configure(config->qspi_bus, &config->qspi_cfg,
data->operation);
/* Execute UMA transaction */
ret = qspi_npcx_fiu_uma_transceive(config->qspi_bus, cfg, flags);
/* Unlock SPI bus */
qspi_npcx_fiu_mutex_unlock(config->qspi_bus);
return ret;
}
/* NPCX UMA functions for SPI NOR flash */
static int flash_npcx_uma_cmd_only(const struct device *dev, uint8_t opcode)
{
struct npcx_uma_cfg cfg = { .opcode = opcode};
return flash_npcx_uma_transceive(dev, &cfg, 0); /* opcode only */
}
static int flash_npcx_uma_cmd_by_addr(const struct device *dev, uint8_t opcode,
uint32_t addr)
{
struct npcx_uma_cfg cfg = { .opcode = opcode};
cfg.addr.u32 = sys_cpu_to_be32(addr);
return flash_npcx_uma_transceive(dev, &cfg, NPCX_UMA_ACCESS_ADDR);
}
static int flash_npcx_uma_read(const struct device *dev, uint8_t opcode,
uint8_t *dst, const size_t size)
{
struct npcx_uma_cfg cfg = { .opcode = opcode,
.rx_buf = dst,
.rx_count = size};
return flash_npcx_uma_transceive(dev, &cfg, NPCX_UMA_ACCESS_READ);
}
static int flash_npcx_uma_write(const struct device *dev, uint8_t opcode,
uint8_t *src, const size_t size)
{
struct npcx_uma_cfg cfg = { .opcode = opcode,
.tx_buf = src,
.tx_count = size};
return flash_npcx_uma_transceive(dev, &cfg, NPCX_UMA_ACCESS_WRITE);
}
static int flash_npcx_uma_write_by_addr(const struct device *dev, uint8_t opcode,
uint8_t *src, const size_t size, uint32_t addr)
{
struct npcx_uma_cfg cfg = { .opcode = opcode,
.tx_buf = src,
.tx_count = size};
cfg.addr.u32 = sys_cpu_to_be32(addr);
return flash_npcx_uma_transceive(dev, &cfg, NPCX_UMA_ACCESS_WRITE |
NPCX_UMA_ACCESS_ADDR);
}
/* Local SPI NOR flash functions */
static int flash_npcx_nor_wait_until_ready(const struct device *dev)
{
int ret;
uint8_t reg;
const struct flash_npcx_nor_config *config = dev->config;
int64_t st = k_uptime_get();
do {
ret = flash_npcx_uma_read(dev, SPI_NOR_CMD_RDSR, &reg, sizeof(reg));
if (ret != 0) {
return ret;
} else if ((reg & SPI_NOR_WIP_BIT) == 0) {
return 0;
}
} while ((k_uptime_get() - st) < config->max_timeout);
return -EBUSY;
}
static int flash_npcx_nor_read_status_regs(const struct device *dev, uint8_t *sts_reg)
{
int ret = flash_npcx_uma_read(dev, SPI_NOR_CMD_RDSR, sts_reg, 1);
if (ret != 0) {
return ret;
}
return flash_npcx_uma_read(dev, SPI_NOR_CMD_RDSR2, sts_reg + 1, 1);
}
static int flash_npcx_nor_write_status_regs(const struct device *dev, uint8_t *sts_reg)
{
int ret;
ret = flash_npcx_uma_cmd_only(dev, SPI_NOR_CMD_WREN);
if (ret != 0) {
return ret;
}
ret = flash_npcx_uma_write(dev, SPI_NOR_CMD_WRSR, sts_reg, 2);
if (ret != 0) {
return ret;
}
return flash_npcx_nor_wait_until_ready(dev);
}
/* Flash API functions */
#if defined(CONFIG_FLASH_JESD216_API)
static int flash_npcx_nor_read_jedec_id(const struct device *dev, uint8_t *id)
{
if (id == NULL) {
return -EINVAL;
}
return flash_npcx_uma_read(dev, SPI_NOR_CMD_RDID, id, SPI_NOR_MAX_ID_LEN);
}
static int flash_npcx_nor_read_sfdp(const struct device *dev, off_t addr,
void *data, size_t size)
{
uint8_t sfdp_addr[4];
struct npcx_uma_cfg cfg = { .opcode = JESD216_CMD_READ_SFDP,
.tx_buf = sfdp_addr,
.tx_count = 4,
.rx_buf = data,
.rx_count = size};
if (data == NULL) {
return -EINVAL;
}
/* CMD_READ_SFDP needs a 24-bit address followed by a dummy byte */
sfdp_addr[0] = (addr >> 16) & 0xff;
sfdp_addr[1] = (addr >> 8) & 0xff;
sfdp_addr[2] = addr & 0xff;
return flash_npcx_uma_transceive(dev, &cfg, NPCX_UMA_ACCESS_WRITE |
NPCX_UMA_ACCESS_READ);
}
#endif /* CONFIG_FLASH_JESD216_API */
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
static void flash_npcx_nor_pages_layout(const struct device *dev,
const struct flash_pages_layout **layout,
size_t *layout_size)
{
const struct flash_npcx_nor_config *config = dev->config;
*layout = &config->layout;
*layout_size = 1;
}
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
static int flash_npcx_nor_read(const struct device *dev, off_t addr,
void *data, size_t size)
{
const struct flash_npcx_nor_config *config = dev->config;
struct flash_npcx_nor_data *dev_data = dev->data;
/* Out of the region of nor flash device? */
if (!is_within_region(addr, size, 0, config->flash_size)) {
return -EINVAL;
}
/* Lock/Unlock SPI bus also for DRA mode */
qspi_npcx_fiu_mutex_lock_configure(config->qspi_bus, &config->qspi_cfg,
dev_data->operation);
/* Trigger Direct Read Access (DRA) via reading memory mapped-address */
memcpy(data, (void *)(config->mapped_addr + addr), size);
qspi_npcx_fiu_mutex_unlock(config->qspi_bus);
return 0;
}
static int flash_npcx_nor_erase(const struct device *dev, off_t addr, size_t size)
{
const struct flash_npcx_nor_config *config = dev->config;
int ret = 0;
bool offset_4k_aligned = SPI_NOR_IS_SECTOR_ALIGNED(addr);
bool offset_64k_aligned = SPI_NOR_IS_64K_ALIGNED(addr);
/* Out of the region of nor flash device? */
if (!is_within_region(addr, size, 0, config->flash_size)) {
LOG_ERR("Addr %ld, size %d are out of range", addr, size);
return -EINVAL;
}
/* address must be sector-aligned */
if (!offset_4k_aligned) {
LOG_ERR("Addr %ld is not sector-aligned", addr);
return -EINVAL;
}
/* size must be a multiple of sectors */
if ((size % BLOCK_4K_SIZE) != 0) {
LOG_ERR("Size %d is not a multiple of sectors", size);
return -EINVAL;
}
/* Select erase opcode by size */
if (size == config->flash_size) {
flash_npcx_uma_cmd_only(dev, SPI_NOR_CMD_WREN);
/* Send chip erase command */
flash_npcx_uma_cmd_only(dev, SPI_NOR_CMD_CE);
return flash_npcx_nor_wait_until_ready(dev);
}
while (size > 0) {
flash_npcx_uma_cmd_only(dev, SPI_NOR_CMD_WREN);
/* Send page/block erase command with addr */
if ((size >= BLOCK_64K_SIZE) && offset_64k_aligned) {
flash_npcx_uma_cmd_by_addr(dev, SPI_NOR_CMD_BE, addr);
addr += BLOCK_64K_SIZE;
size -= BLOCK_64K_SIZE;
} else if ((size >= BLOCK_4K_SIZE) && offset_4k_aligned) {
flash_npcx_uma_cmd_by_addr(dev, SPI_NOR_CMD_SE, addr);
addr += BLOCK_4K_SIZE;
size -= BLOCK_4K_SIZE;
} else {
return -EINVAL;
}
ret = flash_npcx_nor_wait_until_ready(dev);
if (ret != 0) {
break;
}
}
return ret;
}
static int flash_npcx_nor_write(const struct device *dev, off_t addr,
const void *data, size_t size)
{
const struct flash_npcx_nor_config *config = dev->config;
uint8_t *tx_buf = (uint8_t *)data;
int ret = 0;
size_t sz_write;
/* Out of the region of nor flash device? */
if (!is_within_region(addr, size, 0, config->flash_size)) {
return -EINVAL;
}
/* Don't write more than a page. */
if (size > SPI_NOR_PAGE_SIZE) {
sz_write = SPI_NOR_PAGE_SIZE;
} else {
sz_write = size;
}
/*
* Correct the size of first write to not go through page boundary and
* make the address of next write to align to page boundary.
*/
if (((addr + sz_write - 1U) / SPI_NOR_PAGE_SIZE) != (addr / SPI_NOR_PAGE_SIZE)) {
sz_write -= (addr + sz_write) & (SPI_NOR_PAGE_SIZE - 1);
}
while (size > 0) {
/* Start to write */
flash_npcx_uma_cmd_only(dev, SPI_NOR_CMD_WREN);
ret = flash_npcx_uma_write_by_addr(dev, SPI_NOR_CMD_PP, tx_buf,
sz_write, addr);
if (ret != 0) {
break;
}
/* Wait for writing completed */
ret = flash_npcx_nor_wait_until_ready(dev);
if (ret != 0) {
break;
}
size -= sz_write;
tx_buf += sz_write;
addr += sz_write;
if (size > SPI_NOR_PAGE_SIZE) {
sz_write = SPI_NOR_PAGE_SIZE;
} else {
sz_write = size;
}
}
return ret;
}
static const struct flash_parameters *
flash_npcx_nor_get_parameters(const struct device *dev)
{
ARG_UNUSED(dev);
return &flash_npcx_parameters;
};
#ifdef CONFIG_FLASH_EX_OP_ENABLED
static int flash_npcx_nor_ex_exec_uma(const struct device *dev,
const struct npcx_ex_ops_uma_in *op_in,
const struct npcx_ex_ops_uma_out *op_out)
{
int flag = 0;
struct npcx_uma_cfg cfg;
if (op_in == NULL) {
return -EINVAL;
}
/* Organize a UMA transaction */
cfg.opcode = op_in->opcode;
if (op_in->tx_count != 0) {
cfg.tx_buf = op_in->tx_buf;
cfg.tx_count = op_in->tx_count;
flag |= NPCX_UMA_ACCESS_WRITE;
}
if (op_in->addr_count != 0) {
cfg.addr.u32 = sys_cpu_to_be32(op_in->addr);
flag |= NPCX_UMA_ACCESS_ADDR;
}
if (op_out != NULL && op_in->rx_count != 0) {
cfg.rx_buf = op_out->rx_buf;
cfg.rx_count = op_in->rx_count;
flag |= NPCX_UMA_ACCESS_READ;
}
return flash_npcx_uma_transceive(dev, &cfg, flag);
}
static int flash_npcx_nor_ex_set_spi_spec(const struct device *dev,
const struct npcx_ex_ops_qspi_oper_in *op_in)
{
struct flash_npcx_nor_data *data = dev->data;
/* Cannot disable write protection of internal flash */
if ((data->operation & NPCX_EX_OP_INT_FLASH_WP) != 0) {
if ((op_in->mask & NPCX_EX_OP_INT_FLASH_WP) != 0 && !op_in->enable) {
return -EINVAL;
}
}
if (op_in->enable) {
data->operation |= op_in->mask;
} else {
data->operation &= ~op_in->mask;
}
return 0;
}
static int flash_npcx_nor_ex_get_spi_spec(const struct device *dev,
struct npcx_ex_ops_qspi_oper_out *op_out)
{
struct flash_npcx_nor_data *data = dev->data;
op_out->oper = data->operation;
return 0;
}
static int flash_npcx_nor_ex_op(const struct device *dev, uint16_t code,
const uintptr_t in, void *out)
{
#ifdef CONFIG_USERSPACE
bool syscall_trap = z_syscall_trap();
#endif
int ret;
switch (code) {
case FLASH_NPCX_EX_OP_EXEC_UMA:
{
struct npcx_ex_ops_uma_in *op_in = (struct npcx_ex_ops_uma_in *)in;
struct npcx_ex_ops_uma_out *op_out = (struct npcx_ex_ops_uma_out *)out;
#ifdef CONFIG_USERSPACE
struct npcx_ex_ops_uma_in in_copy;
struct npcx_ex_ops_uma_out out_copy;
if (syscall_trap) {
Z_OOPS(z_user_from_copy(&in_copy, op_in, sizeof(in_copy)));
op_in = &in_copy;
op_out = &out_copy;
}
#endif
ret = flash_npcx_nor_ex_exec_uma(dev, op_in, op_out);
#ifdef CONFIG_USERSPACE
if (ret == 0 && syscall_trap) {
Z_OOPS(z_user_to_copy(out, op_out, sizeof(out_copy)));
}
#endif
break;
}
case FLASH_NPCX_EX_OP_SET_QSPI_OPER:
{
struct npcx_ex_ops_qspi_oper_in *op_in = (struct npcx_ex_ops_qspi_oper_in *)in;
#ifdef CONFIG_USERSPACE
struct npcx_ex_ops_qspi_oper_in in_copy;
if (syscall_trap) {
Z_OOPS(z_user_from_copy(&in_copy, op_in, sizeof(in_copy)));
op_in = &in_copy;
}
#endif
ret = flash_npcx_nor_ex_set_spi_spec(dev, op_in);
break;
}
case FLASH_NPCX_EX_OP_GET_QSPI_OPER:
{
struct npcx_ex_ops_qspi_oper_out *op_out =
(struct npcx_ex_ops_qspi_oper_out *)out;
#ifdef CONFIG_USERSPACE
struct npcx_ex_ops_qspi_oper_out out_copy;
if (syscall_trap) {
op_out = &out_copy;
}
#endif
ret = flash_npcx_nor_ex_get_spi_spec(dev, op_out);
#ifdef CONFIG_USERSPACE
if (ret == 0 && syscall_trap) {
Z_OOPS(z_user_to_copy(out, op_out, sizeof(out_copy)));
}
#endif
break;
}
default:
ret = -ENOTSUP;
break;
}
return ret;
}
#endif
static const struct flash_driver_api flash_npcx_nor_driver_api = {
.read = flash_npcx_nor_read,
.write = flash_npcx_nor_write,
.erase = flash_npcx_nor_erase,
.get_parameters = flash_npcx_nor_get_parameters,
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
.page_layout = flash_npcx_nor_pages_layout,
#endif
#if defined(CONFIG_FLASH_JESD216_API)
.sfdp_read = flash_npcx_nor_read_sfdp,
.read_jedec_id = flash_npcx_nor_read_jedec_id,
#endif
#ifdef CONFIG_FLASH_EX_OP_ENABLED
.ex_op = flash_npcx_nor_ex_op,
#endif
};
static int flash_npcx_nor_init(const struct device *dev)
{
const struct flash_npcx_nor_config *config = dev->config;
int ret;
if (!IS_ENABLED(CONFIG_FLASH_NPCX_FIU_NOR_INIT)) {
return 0;
}
/* Enable quad access of spi NOR flash */
if (config->qspi_cfg.qer_type != JESD216_DW15_QER_NONE) {
uint8_t qe_idx, qe_bit, sts_reg[2];
/* Read status registers first */
ret = flash_npcx_nor_read_status_regs(dev, sts_reg);
if (ret != 0) {
LOG_ERR("Enable quad access: read reg failed %d!", ret);
return ret;
}
switch (config->qspi_cfg.qer_type) {
case JESD216_DW15_QER_S1B6:
qe_idx = 1;
qe_bit = 6;
break;
case JESD216_DW15_QER_S2B1v1:
__fallthrough;
case JESD216_DW15_QER_S2B1v4:
__fallthrough;
case JESD216_DW15_QER_S2B1v5:
qe_idx = 2;
qe_bit = 1;
break;
default:
return -ENOTSUP;
}
/* Set QE bit in status register */
sts_reg[qe_idx - 1] |= BIT(qe_bit);
ret = flash_npcx_nor_write_status_regs(dev, sts_reg);
if (ret != 0) {
LOG_ERR("Enable quad access: write reg failed %d!", ret);
return ret;
}
}
/* Enable 4-byte address of spi NOR flash */
if (config->qspi_cfg.enter_4ba != 0) {
bool wr_en = (config->qspi_cfg.enter_4ba & 0x02) != 0;
if (wr_en) {
ret = flash_npcx_uma_cmd_only(dev, SPI_NOR_CMD_WREN);
if (ret != 0) {
LOG_ERR("Enable 4byte addr: WREN failed %d!", ret);
return ret;
}
}
ret = flash_npcx_uma_cmd_only(dev, SPI_NOR_CMD_4BA);
if (ret != 0) {
LOG_ERR("Enable 4byte addr: 4BA failed %d!", ret);
return ret;
}
}
return 0;
}
#define NPCX_FLASH_NOR_INIT(n) \
BUILD_ASSERT(DT_INST_QUAD_EN_PROP_OR(n) == JESD216_DW15_QER_NONE || \
DT_INST_STRING_TOKEN(n, rd_mode) == NPCX_RD_MODE_FAST_DUAL, \
"Fast Dual IO read must be selected in Quad mode"); \
PINCTRL_DT_INST_DEFINE(n); \
static const struct flash_npcx_nor_config flash_npcx_nor_config_##n = { \
.qspi_bus = DEVICE_DT_GET(DT_PARENT(DT_DRV_INST(n))), \
.mapped_addr = DT_INST_PROP(n, mapped_addr), \
.flash_size = DT_INST_PROP(n, size) / 8, \
.max_timeout = DT_INST_PROP(n, max_timeout), \
.qspi_cfg = { \
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
.flags = DT_INST_PROP(n, qspi_flags), \
.enter_4ba = DT_INST_PROP_OR(n, enter_4byte_addr, 0), \
.qer_type = DT_INST_QUAD_EN_PROP_OR(n), \
.rd_mode = DT_INST_STRING_TOKEN(n, rd_mode), \
}, \
IF_ENABLED(CONFIG_FLASH_PAGE_LAYOUT, ( \
.layout = { \
.pages_count = DT_INST_PROP(n, size) / \
(8 * SPI_NOR_PAGE_SIZE), \
.pages_size = SPI_NOR_PAGE_SIZE, \
},)) \
}; \
static struct flash_npcx_nor_data flash_npcx_nor_data_##n; \
DEVICE_DT_INST_DEFINE(n, flash_npcx_nor_init, NULL, \
&flash_npcx_nor_data_##n, &flash_npcx_nor_config_##n, \
POST_KERNEL, CONFIG_FLASH_INIT_PRIORITY, \
&flash_npcx_nor_driver_api);
DT_INST_FOREACH_STATUS_OKAY(NPCX_FLASH_NOR_INIT)