/* * Copyright (c) 2023 Nuvoton Technology Corporation. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT nuvoton_npcx_fiu_nor #include #include #include #include #include #include #ifdef CONFIG_USERSPACE #include #include #endif #include "flash_npcx_fiu_qspi.h" #include "spi_nor.h" #include 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, ®, 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; /* 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 (!SPI_NOR_IS_SECTOR_ALIGNED(addr)) { 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) && SPI_NOR_IS_64K_ALIGNED(addr)) { flash_npcx_uma_cmd_by_addr(dev, SPI_NOR_CMD_BE, addr); addr += BLOCK_64K_SIZE; size -= BLOCK_64K_SIZE; } else { flash_npcx_uma_cmd_by_addr(dev, SPI_NOR_CMD_SE, addr); addr += BLOCK_4K_SIZE; size -= BLOCK_4K_SIZE; } 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) { K_OOPS(k_usermode_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) { K_OOPS(k_usermode_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) { K_OOPS(k_usermode_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) { K_OOPS(k_usermode_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)