blob: 5c744b499263eb4d3220fa00e7c8bf5ca5de246b [file] [log] [blame]
/*
* Copyright (c) 2025 Renesas Electronics Corporation
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <soc.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/cache.h>
#include "spi_nor.h"
#include "r_spi_flash_api.h"
#if defined(CONFIG_FLASH_RENESAS_RZ_QSPI_SPIBSC)
#include "r_spibsc.h"
#else
#include "r_xspi_qspi.h"
#endif
LOG_MODULE_REGISTER(renesas_rz_qspi, CONFIG_FLASH_LOG_LEVEL);
#if defined(CONFIG_FLASH_RENESAS_RZ_QSPI_XSPI)
#define QSPI_DEFAULT_SR (0x40)
#define QSPI_UPDATE_CR (0xC0) /* Configuration register (DC0=1, DC1=1 (Dummy cycle = 10)) */
#define QSPI_DATA_CR_UPDATE (QSPI_UPDATE_CR << 8 | QSPI_DEFAULT_SR)
#endif /* CONFIG_FLASH_RENESAS_RZ_QSPI_XSPI */
#define FLASH_RZ_BASE_ADDRESS (CONFIG_FLASH_BASE_ADDRESS - CONFIG_FLASH_RENESAS_RZ_MIRROR_OFFSET)
/* QSPI COMMANDS */
#define QSPI_CMD_RDSFDP (0x5A) /* Read SFDP */
/* XIP (Execute In Place) mode */
#define QSPI_CMD_XIP_ENTER (0xA5) /* XIP Enter command */
#define QSPI_CMD_XIP_EXIT (0xFF) /* XIP Exit command */
#define QSPI_CMD_QUAD_PAGE_PROGRAM (0x33)
/* One byte data transfer */
#define DATA_LENGTH_DEFAULT_BYTE (0U)
#define ONE_BYTE (1U)
#define TWO_BYTE (2U)
#define THREE_BYTE (3U)
#define FOUR_BYTE (4U)
/* Default erase value */
#define QSPI_ERASE_VALUE (0xFF)
/* Maximum memory buffer size of write operation in memory-map mode */
#if defined(CONFIG_FLASH_RENESAS_RZ_QSPI_SPIBSC)
#define QSPI_MAX_BUFFER_SIZE 256U
#else
#define QSPI_MAX_BUFFER_SIZE 64U
#endif
struct flash_renesas_rz_data {
spi_flash_ctrl_t *fsp_ctrl;
spi_flash_cfg_t *fsp_cfg;
struct k_sem sem;
};
struct flash_renesas_rz_config {
const struct pinctrl_dev_config *pin_cfg;
const spi_flash_api_t *fsp_api;
uint32_t erase_block_size;
uint32_t flash_size;
struct flash_parameters flash_param;
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
struct flash_pages_layout layout;
#endif
};
static const spi_flash_erase_command_t g_erase_command_list[4] = {
{.command = SPI_NOR_CMD_SE, .size = SPI_NOR_SECTOR_SIZE},
{.command = SPI_NOR_CMD_BE_32K, .size = SPI_NOR_BLOCK_32K_SIZE},
{.command = SPI_NOR_CMD_BE, .size = SPI_NOR_BLOCK_SIZE},
{.command = SPI_NOR_CMD_CE, .size = SPI_FLASH_ERASE_SIZE_CHIP_ERASE},
};
static void acquire_device(const struct device *dev)
{
struct flash_renesas_rz_data *dev_data = dev->data;
k_sem_take(&dev_data->sem, K_FOREVER);
}
static void release_device(const struct device *dev)
{
struct flash_renesas_rz_data *dev_data = dev->data;
k_sem_give(&dev_data->sem);
}
static int qspi_wait_until_ready(const struct device *dev)
{
const struct flash_renesas_rz_config *config = dev->config;
struct flash_renesas_rz_data *data = dev->data;
spi_flash_status_t status = {.write_in_progress = true};
uint32_t timeout = 0xFFFFFF;
fsp_err_t err;
while ((status.write_in_progress) && (timeout > 0)) {
err = config->fsp_api->statusGet(data->fsp_ctrl, &status);
if (err != FSP_SUCCESS) {
LOG_ERR("Status get failed");
return -EIO;
}
timeout--;
}
return 0;
}
#if CONFIG_FLASH_PAGE_LAYOUT
void flash_renesas_rz_page_layout(const struct device *dev,
const struct flash_pages_layout **layout, size_t *layout_size)
{
const struct flash_renesas_rz_config *config = dev->config;
*layout = &config->layout;
*layout_size = 1;
}
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
#if defined(CONFIG_FLASH_JESD216_API)
static int qspi_flash_rz_read_jedec_id(const struct device *dev, uint8_t *id)
{
const struct flash_renesas_rz_config *config = dev->config;
struct flash_renesas_rz_data *data = dev->data;
int ret = 0;
if (id == NULL) {
return -EINVAL;
}
spi_flash_direct_transfer_t trans = {
.command = SPI_NOR_CMD_RDID,
.address = 0,
.data = 0,
.command_length = 1U,
.address_length = 0U,
.data_length = THREE_BYTE,
.dummy_cycles = 0U,
};
acquire_device(dev);
ret = config->fsp_api->directTransfer(data->fsp_ctrl, &trans,
SPI_FLASH_DIRECT_TRANSFER_DIR_READ);
if (FSP_SUCCESS != (fsp_err_t)ret) {
LOG_ERR("Failed to read device id");
release_device(dev);
return -EIO;
}
/* Get flash device ID */
memcpy(id, &trans.data, sizeof(trans.data));
release_device(dev);
return ret;
}
static int qspi_flash_renesas_rz_sfdp_read(const struct device *dev, off_t addr, void *data,
size_t len)
{
const struct flash_renesas_rz_config *config = dev->config;
struct flash_renesas_rz_data *dev_data = dev->data;
int ret = 0;
size_t size;
spi_flash_direct_transfer_t trans = {
.command = QSPI_CMD_RDSFDP,
.address = addr,
.data = 0,
.command_length = 1U,
.address_length = 3U,
.data_length = FOUR_BYTE,
.dummy_cycles = SPI_NOR_DUMMY_RD,
};
acquire_device(dev);
while (len > 0) {
size = MIN(len, trans.data_length);
trans.address = addr;
trans.data_length = size;
ret = config->fsp_api->directTransfer(dev_data->fsp_ctrl, &trans,
SPI_FLASH_DIRECT_TRANSFER_DIR_READ);
if (FSP_SUCCESS != (fsp_err_t)ret) {
LOG_ERR("Failed to read SFDP id");
release_device(dev);
return -EIO;
}
memcpy(data, &trans.data, size);
len -= size;
addr += size;
data = (uint8_t *)data + size;
}
release_device(dev);
return ret;
}
#endif
static bool qspi_flash_rz_valid(uint32_t area_size, off_t offset, size_t len)
{
if ((offset < 0) || (offset >= area_size) || ((area_size - offset) < len)) {
return false;
}
return true;
}
static int qspi_flash_rz_erase(const struct device *dev, off_t offset, size_t len)
{
const struct flash_renesas_rz_config *config = dev->config;
struct flash_renesas_rz_data *data = dev->data;
int err = 0;
struct flash_pages_info page_info_start, page_info_end;
uint32_t erase_size;
int rc;
if (!len) {
return 0;
}
if (!qspi_flash_rz_valid(config->flash_size, offset, len)) {
LOG_ERR("The offset 0x%lx is invalid", (long)offset);
return -EINVAL;
}
if (0 != (len % config->erase_block_size)) {
LOG_ERR("The size %zu is not align with block size (%u)", len,
config->erase_block_size);
return -EINVAL;
}
rc = flash_get_page_info_by_offs(dev, offset, &page_info_start);
if ((rc != 0) || (offset != page_info_start.start_offset)) {
LOG_ERR("The offset 0x%lx is not aligned with the starting sector", (long)offset);
return -EINVAL;
}
rc = flash_get_page_info_by_offs(dev, (offset + len), &page_info_end);
if ((rc != 0) || ((offset + len) != page_info_end.start_offset)) {
LOG_ERR("The size %zu is not aligned with the ending sector", len);
return -EINVAL;
}
acquire_device(dev);
while (len > 0) {
if (len < SPI_NOR_BLOCK_32K_SIZE) {
erase_size = SPI_NOR_SECTOR_SIZE;
} else if (len < SPI_NOR_BLOCK_SIZE) {
erase_size = SPI_NOR_BLOCK_32K_SIZE;
} else {
erase_size = SPI_NOR_BLOCK_SIZE;
}
uint8_t *dest = (uint8_t *)FLASH_RZ_BASE_ADDRESS;
dest += (size_t)offset;
err = config->fsp_api->erase(data->fsp_ctrl, dest, erase_size);
if (FSP_SUCCESS != (fsp_err_t)err) {
LOG_ERR("Erase failed");
err = -EIO;
break;
}
err = qspi_wait_until_ready(dev);
if (err) {
LOG_ERR("Failed to get status for QSPI operation");
err = -EIO;
break;
}
offset += erase_size;
len -= erase_size;
#if defined(CONFIG_FLASH_RENESAS_RZ_QSPI_SPIBSC)
spibsc_instance_ctrl_t *p_ctrl = (spibsc_instance_ctrl_t *)data->fsp_ctrl;
/* Invalidating SPIBSC cache */
p_ctrl->p_reg->DRCR_b.RCF = 1;
sys_cache_data_invd_range((void *)dest, erase_size);
#endif
}
release_device(dev);
return err;
}
static int qspi_flash_rz_read(const struct device *dev, off_t offset, void *data, size_t len)
{
const struct flash_renesas_rz_config *config = dev->config;
if (!len) {
return 0;
}
if (!data) {
return -EINVAL;
}
if (!qspi_flash_rz_valid(config->flash_size, offset, len)) {
return -EINVAL;
}
acquire_device(dev);
uint8_t *dest = (uint8_t *)FLASH_RZ_BASE_ADDRESS;
dest += (size_t)offset;
memcpy(data, dest, len);
release_device(dev);
return 0;
}
static int qspi_flash_rz_write(const struct device *dev, off_t offset, const void *data, size_t len)
{
const struct flash_renesas_rz_config *config = dev->config;
struct flash_renesas_rz_data *dev_data = dev->data;
int err = 0;
uint32_t remaining_bytes = len;
uint32_t size = (uint32_t)len;
if (!len) {
return 0;
}
if (!data) {
return -EINVAL;
}
if (!qspi_flash_rz_valid(config->flash_size, offset, len)) {
return -EINVAL;
}
acquire_device(dev);
while (remaining_bytes > 0) {
size = MIN(remaining_bytes, QSPI_MAX_BUFFER_SIZE);
uint8_t *dest = (uint8_t *)FLASH_RZ_BASE_ADDRESS;
dest += (size_t)offset;
err = config->fsp_api->write(dev_data->fsp_ctrl, (const uint8_t *)data, dest, size);
if (FSP_SUCCESS != (fsp_err_t)err) {
LOG_ERR("Flash write failed");
err = -EIO;
break;
}
err = qspi_wait_until_ready(dev);
if (err) {
LOG_ERR("Failed to get status for QSPI operation");
err = -EIO;
break;
}
remaining_bytes -= size;
offset += size;
data = (const uint8_t *)data + size;
#if defined(CONFIG_FLASH_RENESAS_RZ_QSPI_SPIBSC)
spibsc_instance_ctrl_t *p_ctrl = (spibsc_instance_ctrl_t *)dev_data->fsp_ctrl;
/* Invalidating SPIBSC cache */
p_ctrl->p_reg->DRCR_b.RCF = 1;
sys_cache_data_invd_range((void *)dest, size);
#endif
}
release_device(dev);
return err;
}
static int qspi_flash_rz_get_size(const struct device *dev, uint64_t *size)
{
const struct flash_renesas_rz_config *config = dev->config;
*size = (uint64_t)config->flash_size;
return 0;
}
static const struct flash_parameters *qspi_flash_rz_get_parameters(const struct device *dev)
{
const struct flash_renesas_rz_config *config = dev->config;
return &config->flash_param;
}
#if CONFIG_FLASH_RENESAS_RZ_QSPI_XSPI
static int spi_flash_direct_write(const struct device *dev, uint8_t command, uint32_t tx_data,
uint8_t data_length)
{
const struct flash_renesas_rz_config *config = dev->config;
struct flash_renesas_rz_data *data = dev->data;
int ret;
spi_flash_direct_transfer_t trans = {
.command = command,
.address = 0U,
.data = tx_data,
.command_length = 1U,
.address_length = 0U,
.data_length = data_length,
.dummy_cycles = 0U,
};
ret = config->fsp_api->directTransfer(data->fsp_ctrl, &trans,
SPI_FLASH_DIRECT_TRANSFER_DIR_WRITE);
if (FSP_SUCCESS != (fsp_err_t)ret) {
LOG_ERR("Failed to write command");
return -EIO;
};
return ret;
}
#endif
static int flash_renesas_rz_init(const struct device *dev)
{
const struct flash_renesas_rz_config *config = dev->config;
struct flash_renesas_rz_data *data = dev->data;
int ret = 0;
#if CONFIG_FLASH_RENESAS_RZ_QSPI_XSPI
ret = pinctrl_apply_state(config->pin_cfg, PINCTRL_STATE_DEFAULT);
if (ret) {
LOG_ERR("Failed to configure pins for QSPI with code: %d", ret);
return -EIO;
}
#endif
k_sem_init(&data->sem, 1, 1);
ret = config->fsp_api->open(data->fsp_ctrl, data->fsp_cfg);
if (FSP_SUCCESS != (fsp_err_t)ret) {
LOG_ERR("Open failed");
return -EIO;
}
#if CONFIG_FLASH_RENESAS_RZ_QSPI_XSPI
/* Write Enable Command */
ret = spi_flash_direct_write(dev, data->fsp_cfg->write_enable_command, 0U,
DATA_LENGTH_DEFAULT_BYTE);
if (ret) {
return ret;
}
/* Write Status Command */
ret = spi_flash_direct_write(dev, SPI_NOR_CMD_WRSR, QSPI_DATA_CR_UPDATE, TWO_BYTE);
if (ret) {
return ret;
}
#endif
return ret;
}
static DEVICE_API(flash, flash_renesas_rz_qspi_driver_api) = {
.erase = qspi_flash_rz_erase,
.write = qspi_flash_rz_write,
.read = qspi_flash_rz_read,
.get_parameters = qspi_flash_rz_get_parameters,
.get_size = qspi_flash_rz_get_size,
#ifdef CONFIG_FLASH_PAGE_LAYOUT
.page_layout = flash_renesas_rz_page_layout,
#endif
#if defined(CONFIG_FLASH_JESD216_API)
.sfdp_read = qspi_flash_renesas_rz_sfdp_read,
.read_jedec_id = qspi_flash_rz_read_jedec_id,
#endif
};
#define DT_DRV_COMPAT renesas_rz_qspi_xspi
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
#define FLASH_RENESAS_RZ_QSPI_XSPI_DEFINE(n) \
PINCTRL_DT_DEFINE(DT_INST_PARENT(n)); \
static xspi_qspi_timing_setting_t g_qspi##n##_timing_settings = { \
.command_to_command_interval = XSPI_QSPI_COMMAND_INTERVAL_CLOCKS_2, \
.cs_pullup_lag = XSPI_QSPI_CS_PULLUP_CLOCKS_1, \
.cs_pulldown_lead = XSPI_QSPI_CS_PULLDOWN_CLOCKS_1}; \
static xspi_qspi_address_space_t g_qspi##n##_address_space_settings = { \
.unit0_cs0_end_address = XSPI_QSPI_CFG_UNIT_0_CS_0_END_ADDRESS, \
.unit0_cs1_start_address = XSPI_QSPI_CFG_UNIT_0_CS_1_START_ADDRESS, \
.unit0_cs1_end_address = XSPI_QSPI_CFG_UNIT_0_CS_1_END_ADDRESS, \
.unit1_cs0_end_address = XSPI_QSPI_CFG_UNIT_1_CS_0_END_ADDRESS, \
.unit1_cs1_start_address = XSPI_QSPI_CFG_UNIT_1_CS_1_START_ADDRESS, \
.unit1_cs1_end_address = XSPI_QSPI_CFG_UNIT_1_CS_1_END_ADDRESS, \
}; \
static const xspi_qspi_extended_cfg_t g_qspi##n##_extended_cfg = { \
.unit = n, \
.chip_select = XSPI_QSPI_CHIP_SELECT_##n, \
.memory_size = XSPI_QSPI_MEMORY_SIZE_64MB, \
.p_timing_settings = &g_qspi##n##_timing_settings, \
.prefetch_en = \
(xspi_qspi_prefetch_function_t)XSPI_QSPI_CFG_UNIT_##n##_PREFETCH_FUNCTION, \
.p_address_space = &g_qspi##n##_address_space_settings, \
}; \
static spi_flash_cfg_t g_qspi##n##_cfg = { \
.spi_protocol = SPI_FLASH_PROTOCOL_1S_1S_1S, \
.read_mode = SPI_FLASH_READ_MODE_FAST_READ, \
.address_bytes = SPI_FLASH_ADDRESS_BYTES_3, \
.dummy_clocks = SPI_FLASH_DUMMY_CLOCKS_10, \
.read_command = SPI_NOR_CMD_READ_FAST, \
.page_program_command = SPI_NOR_CMD_PP, \
.page_program_address_lines = SPI_FLASH_DATA_LINES_4, \
.page_size_bytes = SPI_NOR_PAGE_SIZE, \
.write_enable_command = SPI_NOR_CMD_WREN, \
.status_command = SPI_NOR_CMD_RDSR, \
.write_status_bit = 0, \
.xip_enter_command = QSPI_CMD_XIP_ENTER, \
.xip_exit_command = QSPI_CMD_XIP_EXIT, \
.p_erase_command_list = &g_erase_command_list[0], \
.erase_command_list_length = ARRAY_SIZE(g_erase_command_list), \
.p_extend = &g_qspi##n##_extended_cfg, \
}; \
static xspi_qspi_instance_ctrl_t g_qspi##n##_ctrl; \
static struct flash_renesas_rz_data flash_renesas_rz_data_##n = { \
.fsp_ctrl = &g_qspi##n##_ctrl, \
.fsp_cfg = &g_qspi##n##_cfg, \
}; \
static const struct flash_renesas_rz_config flash_renesas_rz_config_##n = { \
.pin_cfg = PINCTRL_DT_DEV_CONFIG_GET(DT_INST_PARENT(n)), \
.fsp_api = &g_spi_flash_on_xspi_qspi, \
.flash_size = DT_INST_REG_SIZE(n), \
.erase_block_size = DT_INST_PROP_OR(n, erase_block_size, 4096), \
.flash_param = \
{ \
.write_block_size = DT_INST_PROP(n, write_block_size), \
.erase_value = QSPI_ERASE_VALUE, \
}, \
IF_ENABLED(CONFIG_FLASH_PAGE_LAYOUT, \
(.layout = { \
.pages_count = \
DT_INST_REG_SIZE(n) / DT_INST_PROP_OR(n, erase_block_size, 4096), \
.pages_size = DT_INST_PROP_OR(n, erase_block_size, 4096), \
},))}; \
DEVICE_DT_INST_DEFINE(n, flash_renesas_rz_init, NULL, &flash_renesas_rz_data_##n, \
&flash_renesas_rz_config_##n, POST_KERNEL, \
CONFIG_FLASH_INIT_PRIORITY, &flash_renesas_rz_qspi_driver_api);
DT_INST_FOREACH_STATUS_OKAY(FLASH_RENESAS_RZ_QSPI_XSPI_DEFINE)
#endif
#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT renesas_rz_qspi_spibsc
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
#define FLASH_RENESAS_RZ_QSPI_SPIBSC_DEFINE(n) \
static const spibsc_extended_cfg_t g_qspi##n##_extended_cfg = { \
.delay = \
{ \
.slch = 0, \
.clsh = 0, \
.shsl = 6, \
}, \
.io_fix_mask = (0u << 2) | (1u << 3), \
.io_fix_value = (1u << 2) | (1u << 3), \
}; \
static spi_flash_cfg_t g_qspi##n##_cfg = { \
.spi_protocol = SPI_FLASH_PROTOCOL_EXTENDED_SPI, \
.read_mode = SPI_FLASH_READ_MODE_FAST_READ_QUAD_IO, \
.address_bytes = SPI_FLASH_ADDRESS_BYTES_3, \
.dummy_clocks = SPI_FLASH_DUMMY_CLOCKS_DEFAULT, \
.read_command = SPI_NOR_CMD_4READ, \
.page_program_command = QSPI_CMD_QUAD_PAGE_PROGRAM, \
.page_program_address_lines = SPI_FLASH_DATA_LINES_4, \
.page_size_bytes = SPI_NOR_PAGE_SIZE, \
.write_enable_command = SPI_NOR_CMD_WREN, \
.status_command = SPI_NOR_CMD_RDSR, \
.write_status_bit = 0, \
.xip_enter_command = QSPI_CMD_XIP_ENTER, \
.xip_exit_command = QSPI_CMD_XIP_EXIT, \
.p_erase_command_list = &g_erase_command_list[0], \
.erase_command_list_length = ARRAY_SIZE(g_erase_command_list), \
.p_extend = &g_qspi##n##_extended_cfg, \
}; \
static spibsc_instance_ctrl_t g_qspi##n##_ctrl; \
static struct flash_renesas_rz_data flash_renesas_rz_data_##n = { \
.fsp_ctrl = &g_qspi##n##_ctrl, \
.fsp_cfg = &g_qspi##n##_cfg, \
}; \
static const struct flash_renesas_rz_config flash_renesas_rz_config_##n = { \
.pin_cfg = NULL, \
.fsp_api = &g_spi_flash_on_spibsc, \
.flash_size = DT_INST_REG_SIZE(n), \
.erase_block_size = DT_INST_PROP_OR(n, erase_block_size, 4096), \
.flash_param = \
{ \
.write_block_size = DT_INST_PROP(n, write_block_size), \
.erase_value = QSPI_ERASE_VALUE, \
}, \
IF_ENABLED(CONFIG_FLASH_PAGE_LAYOUT, \
(.layout = { \
.pages_count = \
DT_INST_REG_SIZE(n) / DT_INST_PROP_OR(n, erase_block_size, 4096), \
.pages_size = DT_INST_PROP_OR(n, erase_block_size, 4096), \
},))}; \
DEVICE_DT_INST_DEFINE(n, flash_renesas_rz_init, NULL, &flash_renesas_rz_data_##n, \
&flash_renesas_rz_config_##n, POST_KERNEL, \
CONFIG_FLASH_INIT_PRIORITY, &flash_renesas_rz_qspi_driver_api);
DT_INST_FOREACH_STATUS_OKAY(FLASH_RENESAS_RZ_QSPI_SPIBSC_DEFINE)
#endif