blob: 96a938c89211ec539a0ed94e2ab8db50bd34bc79 [file] [log] [blame]
/*
* Copyright 2025 The ChromiumOS Authors.
*
* SPDX-License-Identifier: Apache-2.0
*
* This driver is inspired from the flash_andes_qspi.c flash driver.
*
*/
#define DT_DRV_COMPAT andestech_qspi_nor_xip
#include "soc_v5.h"
#include <errno.h>
#include <string.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/cache.h>
#include <zephyr/drivers/flash/andes_flash_xip_api_ex.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/init.h>
#include <zephyr/logging/log.h>
#include "flash_andes_qspi.h"
#include "spi_nor.h"
LOG_MODULE_REGISTER(flash_andes_xip, CONFIG_FLASH_LOG_LEVEL);
/* Indicates that an access command includes bytes for the address.
* If not provided the opcode is not followed by address bytes.
*/
#define ANDES_ACCESS_ADDRESSED BIT(0)
/* Indicates that an access command is performing a write.
* If not provided access is a read.
*/
#define ANDES_ACCESS_WRITE BIT(1)
/* Set max write size to page size. */
#define ANDES_PAGE_SIZE 256
#define MMISC_CTL_BRPE_EN BIT(3)
#define MEMCTRL_CHG BIT(8)
struct atcspi200_regs {
/* 0x00 */
volatile uint32_t ID;
/* 0x04 */
volatile uint8_t reserver_04_0F[12];
/* 0x10 */
volatile uint32_t TFMAT;
/* 0x14 */
volatile uint32_t DIOCTRL;
/* 0x18 */
volatile uint32_t WRCNT;
/* 0x1C */
volatile uint32_t RDCNT;
/* 0x20 */
volatile uint32_t TCTRL;
/* 0x24 */
volatile uint32_t CMD;
/* 0x28 */
volatile uint32_t ADDR;
/* 0x2C */
volatile uint32_t DATA;
/* 0x30 */
volatile uint32_t CTRL;
/* 0x34 */
volatile uint32_t STATUS;
/* 0x38 */
volatile uint32_t INTEN;
/* 0x3C */
volatile uint32_t INTST;
/* 0x40 */
volatile uint32_t IFTIM;
/* 0x44 */
volatile uint8_t reserver_44_4F[12];
/* 0x50 */
volatile uint32_t MEMCTRL;
/* 0x54 */
volatile uint8_t reserver_54_5F[12];
/* 0x60 */
volatile uint32_t SLVSR;
/* 0x64 */
volatile uint32_t SLVCOUNT;
/* 0x68 */
volatile uint8_t reserver_68_7B[20];
/* 0x7C */
volatile uint32_t CONF;
};
struct flash_andes_qspi_xip_config {
struct flash_parameters parameters;
struct atcspi200_regs *regs;
uint32_t mapped_base;
uint32_t flash_size;
bool is_xip;
#ifdef CONFIG_FLASH_PAGE_LAYOUT
struct flash_pages_layout layout;
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
};
struct flash_andes_qspi_xip_data {
#ifdef CONFIG_FLASH_EX_OP_ENABLED
/* Lock of the status registers. */
bool status_lock;
#endif
};
#define flash_andes_qspi_xip_cmd_read(dev, opcode, dest, length) \
flash_andes_qspi_xip_access(dev, opcode, 0, 0, dest, length)
#define flash_andes_qspi_xip_cmd_write_data(dev, opcode, src, length) \
flash_andes_qspi_xip_access(dev, opcode, ANDES_ACCESS_WRITE, 0, src, length)
#define flash_andes_qspi_xip_cmd_write(dev, opcode) \
flash_andes_qspi_xip_access(dev, opcode, ANDES_ACCESS_WRITE, 0, NULL, 0)
#define flash_andes_qspi_xip_cmd_addr_read(dev, opcode, addr, dest, length) \
flash_andes_qspi_xip_access(dev, opcode, ANDES_ACCESS_ADDRESSED, addr, dest, length)
#define flash_andes_qspi_xip_cmd_addr_write(dev, opcode, addr, src, length) \
flash_andes_qspi_xip_access(dev, opcode, ANDES_ACCESS_WRITE | ANDES_ACCESS_ADDRESSED, \
addr, (void *)src, length)
#define STAT_TXFULL BIT(23)
#define STAT_RXEMPTY BIT(14)
#define STAT_SPIACTIVE BIT(0)
static __ramfunc void handle_data_transfer(bool is_write, uint8_t *data, size_t length,
struct atcspi200_regs *regs)
{
size_t processed = 0;
/* Data Merge is disabled. */
if (is_write) {
while (length > processed) {
if (!(regs->STATUS & STAT_TXFULL)) {
regs->DATA = data[processed];
processed++;
}
}
} else {
while (length > processed) {
if (!(regs->STATUS & STAT_RXEMPTY)) {
data[processed] = regs->DATA;
processed++;
}
}
}
}
/*
* @brief Send an SPI command
*
* @param dev Device struct
* @param opcode The command to send
* @param access flags that determine how the command is constructed.
* @param addr The address to send
* @param data The buffer to store or read transferred data
* @param length The size of the buffer
* @return 0 on success
*/
static __ramfunc int flash_andes_qspi_xip_access(const struct device *const dev, uint8_t opcode,
uint8_t access, off_t addr, void *data,
size_t length)
{
const struct flash_andes_qspi_xip_config *config = dev->config;
struct atcspi200_regs *regs = config->regs;
bool is_write = (access & ANDES_ACCESS_WRITE) != 0;
uint32_t tctrl;
uint32_t tfmat;
if ((length > 0) && (data == NULL)) {
return -EINVAL;
}
/* Wait till previous SPI transfer is finished. */
while (regs->STATUS & STAT_SPIACTIVE) {
}
/* Command phase enable */
tctrl = TCTRL_CMD_EN_MSK;
if (access & ANDES_ACCESS_ADDRESSED) {
/* Enable and set ADDR len */
regs->ADDR = addr;
/* Address phase enable */
tctrl |= TCTRL_ADDR_EN_MSK;
}
if (length == 0) {
tctrl |= TRNS_MODE_NONE_DATA;
} else if (is_write) {
tctrl |= TRNS_MODE_WRITE_ONLY;
#ifdef CONFIG_FLASH_ANDES_QSPI_XIP_COUNT_REGS
regs->WRCNT = length - 1;
#else /* CONFIG_FLASH_ANDES_QSPI_XIP_COUNT_REGS */
tctrl |= (length - 1) << TCTRL_WR_TCNT_OFFSET;
#endif /* CONFIG_FLASH_ANDES_QSPI_XIP_COUNT_REGS */
} else {
tctrl |= TRNS_MODE_READ_ONLY;
#ifdef CONFIG_FLASH_ANDES_QSPI_XIP_COUNT_REGS
regs->RDCNT = length - 1;
#else /* CONFIG_FLASH_ANDES_QSPI_XIP_COUNT_REGS */
tctrl |= (length - 1) << TCTRL_RD_TCNT_OFFSET;
#endif /* CONFIG_FLASH_ANDES_QSPI_XIP_COUNT_REGS */
}
if (opcode == SPI_NOR_CMD_PP_1_1_4) {
tctrl |= DUAL_IO_MODE;
}
tfmat = regs->TFMAT;
/* Clear data and address length fields + disable data merge. */
tfmat &= ~TFMAT_DATA_LEN_MSK & ~TFMAT_ADDR_LEN_MSK & ~TFMAT_DATA_MERGE_MSK;
/* Set data len to 7+1 and address to 3 bytes. */
tfmat |= (7 << TFMAT_DATA_LEN_OFFSET) | (0x2 << TFMAT_ADDR_LEN_OFFSET);
regs->TFMAT = tfmat;
regs->TCTRL = tctrl;
/* write CMD register to send command*/
regs->CMD = opcode;
if (length > 0) {
handle_data_transfer(is_write, (uint8_t *)data, length, regs);
}
/* Wait till SPI transfer is finished. */
while (regs->STATUS & STAT_SPIACTIVE) {
}
return 0;
}
/**
* @brief Wait until the flash is ready
*
* @param dev The device structure
* @return 0 on success, negative errno code otherwise
*/
static __ramfunc int flash_andes_qspi_xip_wait_until_ready(const struct device *dev)
{
int ret;
uint8_t reg;
do {
ret = flash_andes_qspi_xip_cmd_read(dev, FLASH_ANDES_CMD_RDSR, &reg, 1);
} while (!ret && (reg & FLASH_ANDES_WIP_BIT));
return ret;
}
/**
* @brief Set write protection
*
* @param dev The device structure
* @return 0 on success, negative errno code otherwise
*/
static __ramfunc int write_protection_set(const struct device *dev, bool write_protect)
{
return flash_andes_qspi_xip_cmd_write(dev, (write_protect) ? FLASH_ANDES_CMD_WRDI
: FLASH_ANDES_CMD_WREN);
}
static unsigned int prepare_for_ramfunc(void)
{
unsigned int key;
key = irq_lock();
csr_clear(NDS_MMISC_CTL, MMISC_CTL_BRPE_EN);
return key;
}
static void cleanup_after_ramfunc(unsigned int key)
{
csr_set(NDS_MMISC_CTL, MMISC_CTL_BRPE_EN);
irq_unlock(key);
}
static __ramfunc void prepare_for_flashing(const struct device *dev)
{
const struct flash_andes_qspi_xip_config *config = dev->config;
struct atcspi200_regs *regs = config->regs;
uint32_t memctrl;
/* Make sure a previous SPI transfer is finished. */
while (regs->STATUS & STAT_SPIACTIVE) {
}
/* Exit memory-mapped interface before SPI transfer. */
memctrl = regs->MEMCTRL;
regs->MEMCTRL = memctrl;
while (regs->MEMCTRL & MEMCTRL_CHG) {
}
}
static __ramfunc void cleanup_after_flashing(const struct device *dev, off_t addr, size_t size)
{
#ifdef CONFIG_CACHE_MANAGEMENT
if (size > 0) {
const struct flash_andes_qspi_xip_config *config = dev->config;
/* Invalidate modyfied flash memory. */
cache_data_invd_range((void *)(addr + config->mapped_base), size);
cache_instr_invd_range((void *)(addr + config->mapped_base), size);
}
#endif /* CONFIG_CACHE_MANAGEMENT */
}
static int flash_andes_qspi_xip_read(const struct device *dev, off_t addr, void *dest, size_t size)
{
const struct flash_andes_qspi_xip_config *config = dev->config;
if (size == 0) {
return 0;
}
/* Check read boundaries */
if ((addr < 0 || addr >= config->flash_size) || ((config->flash_size - addr) < size)) {
return -EINVAL;
}
/* Use memory-mapped mechanism for reading. */
memcpy(dest, (void *)(config->mapped_base + addr), size);
return 0;
}
static __ramfunc int do_write(const struct device *dev, off_t addr, const void *src, size_t size)
{
int ret = 0;
size_t to_write;
off_t addr_curr = addr;
size_t size_remainig = size;
prepare_for_flashing(dev);
while (size_remainig > 0) {
ret = write_protection_set(dev, false);
if (ret != 0) {
break;
}
/* Get the adequate size to send*/
to_write = MIN(ANDES_PAGE_SIZE - (addr_curr % ANDES_PAGE_SIZE), size_remainig);
ret = flash_andes_qspi_xip_cmd_addr_write(dev, SPI_NOR_CMD_PP_1_1_4, addr_curr, src,
to_write);
flash_andes_qspi_xip_wait_until_ready(dev);
if (ret != 0) {
break;
}
src = (const uint8_t *)src + to_write;
size_remainig -= to_write;
addr_curr += to_write;
}
/* Make sure write protection is enabled at the end of write. */
int ret2 = write_protection_set(dev, true);
if (!ret) {
ret = ret2;
}
cleanup_after_flashing(dev, addr, size);
return ret;
}
static int flash_andes_qspi_xip_write(const struct device *dev, off_t addr, const void *src,
size_t size)
{
const struct flash_andes_qspi_xip_config *config = dev->config;
int ret = 0;
unsigned int key;
if (size == 0) {
return 0;
}
/* Check write boundaries */
if ((addr < 0 || addr >= config->flash_size) || ((config->flash_size - addr) < size)) {
return -EINVAL;
}
/*
* Synchronous mechanisms like semaphores are not needed,
* because interrupts are always locked and there are no reschedule points.
*/
key = prepare_for_ramfunc();
ret = do_write(dev, addr, src, size);
cleanup_after_ramfunc(key);
return ret;
}
static __ramfunc int do_erase(const struct device *dev, off_t addr, size_t size)
{
int ret = 0;
off_t addr_curr = addr;
size_t size_remainig = size;
prepare_for_flashing(dev);
while (size_remainig > 0) {
ret = write_protection_set(dev, false);
if (ret != 0) {
break;
}
/* Use the smallest erase unit not to hold CPU for long. */
ret = flash_andes_qspi_xip_cmd_addr_write(dev, SPI_NOR_CMD_SE, addr_curr, NULL, 0);
flash_andes_qspi_xip_wait_until_ready(dev);
if (ret != 0) {
break;
}
addr_curr += SPI_NOR_SECTOR_SIZE;
size_remainig -= SPI_NOR_SECTOR_SIZE;
}
/* Make sure write protection is enabled at the end of erase. */
int ret2 = write_protection_set(dev, true);
if (!ret) {
ret = ret2;
}
cleanup_after_flashing(dev, addr, size);
return ret;
}
static int flash_andes_qspi_xip_erase(const struct device *dev, off_t addr, size_t size)
{
const struct flash_andes_qspi_xip_config *config = dev->config;
int ret = 0;
unsigned int key;
if (size == 0) {
return 0;
}
/* Check erase boundaries */
if ((addr < 0 || addr >= config->flash_size) || ((config->flash_size - addr) < size)) {
return -EINVAL;
}
/* address must be sector-aligned */
if (!SPI_NOR_IS_SECTOR_ALIGNED(addr)) {
return -EINVAL;
}
/* size must be a multiple of sectors */
if ((size % SPI_NOR_SECTOR_SIZE) != 0) {
return -EINVAL;
}
/*
* Synchronous mechanisms like semaphores are not needed,
* because interrupts are always locked and there are no reschedule points.
*/
key = prepare_for_ramfunc();
do_erase(dev, addr, size);
cleanup_after_ramfunc(key);
return ret;
}
static int flash_andes_qspi_xip_init(const struct device *dev)
{
const struct flash_andes_qspi_xip_config *config = dev->config;
if (!config->is_xip) {
return -EINVAL;
}
return 0;
}
static const struct flash_parameters *flash_andes_qspi_xip_get_parameters(const struct device *dev)
{
const struct flash_andes_qspi_xip_config *config = dev->config;
return &config->parameters;
}
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
static void flash_andes_qspi_xip_pages_layout(const struct device *dev,
const struct flash_pages_layout **layout,
size_t *layout_size)
{
const struct flash_andes_qspi_xip_config *config = dev->config;
*layout = &config->layout;
*layout_size = 1;
}
#endif
#ifdef CONFIG_FLASH_EX_OP_ENABLED
static __ramfunc int flash_andes_qspi_xip_get_status(const struct device *dev,
struct andes_xip_ex_ops_get_out *op_out)
{
int ret = 0;
prepare_for_flashing(dev);
ret = flash_andes_qspi_xip_cmd_read(dev, SPI_NOR_CMD_RDSR, &op_out->regs[0], 1);
if (ret) {
goto cleanup;
}
ret = flash_andes_qspi_xip_cmd_read(dev, SPI_NOR_CMD_RDSR2, &op_out->regs[1], 1);
if (ret) {
goto cleanup;
}
ret = flash_andes_qspi_xip_cmd_read(dev, SPI_NOR_CMD_RDSR3, &op_out->regs[2], 1);
cleanup:
cleanup_after_flashing(dev, 0, 0);
return ret;
}
static __ramfunc int write_status_register(const struct device *dev, uint8_t sr, uint8_t mask,
uint8_t op_read, uint8_t op_write)
{
uint8_t sr_curr;
uint8_t sr_new;
int ret, ret2 = 0;
if (!mask) {
return 0;
}
ret = flash_andes_qspi_xip_cmd_read(dev, op_read, &sr_curr, 1);
if (ret) {
return ret;
}
sr_new = (sr_curr & ~mask) | sr;
if (sr_new != sr_curr) {
ret = write_protection_set(dev, false);
if (ret != 0) {
return ret;
}
ret = flash_andes_qspi_xip_cmd_write_data(dev, op_write, &sr_new, 1);
ret2 = flash_andes_qspi_xip_wait_until_ready(dev);
}
if (!ret) {
ret = ret2;
}
return ret;
}
static __ramfunc int flash_andes_qspi_xip_set_status(const struct device *dev,
struct andes_xip_ex_ops_set_in *op_out)
{
int ret;
struct flash_andes_qspi_xip_data *data = dev->data;
if (data->status_lock) {
return -EPERM;
}
prepare_for_flashing(dev);
ret = write_status_register(dev, op_out->regs[0], op_out->masks[0], SPI_NOR_CMD_RDSR,
SPI_NOR_CMD_WRSR);
if (ret) {
goto cleanup;
}
ret = write_status_register(dev, op_out->regs[1], op_out->masks[1], SPI_NOR_CMD_RDSR2,
SPI_NOR_CMD_WRSR2);
if (ret) {
goto cleanup;
}
ret = write_status_register(dev, op_out->regs[2], op_out->masks[2], SPI_NOR_CMD_RDSR3,
SPI_NOR_CMD_WRSR3);
cleanup:
cleanup_after_flashing(dev, 0, 0);
return ret;
}
static __ramfunc int flash_andes_qspi_xip_lock(const struct device *dev,
struct andes_xip_ex_ops_lock_in *op_in)
{
struct flash_andes_qspi_xip_data *data = dev->data;
data->status_lock = op_in->enable;
return 0;
}
static __ramfunc int flash_andes_qspi_xip_lock_state(const struct device *dev,
struct andes_xip_ex_ops_lock_state_out *op_out)
{
struct flash_andes_qspi_xip_data *data = dev->data;
op_out->state = data->status_lock;
return 0;
}
static __ramfunc int
flash_andes_qspi_xip_set_memrdcmd(const struct device *dev,
struct andes_xip_ex_ops_mem_read_cmd_in *op_in)
{
const struct flash_andes_qspi_xip_config *config = dev->config;
struct atcspi200_regs *regs = config->regs;
prepare_for_flashing(dev);
regs->MEMCTRL = (uint32_t)op_in->cmd;
while (regs->MEMCTRL & MEMCTRL_CHG) {
}
cleanup_after_flashing(dev, 0, 0);
return 0;
}
static int flash_andes_qspi_xip_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 key, ret;
switch (code) {
case FLASH_ANDES_XIP_EX_OP_GET_STATUS_REGS: {
struct andes_xip_ex_ops_get_out *op_out = (struct andes_xip_ex_ops_get_out *)out;
#ifdef CONFIG_USERSPACE
struct andes_xip_ex_ops_get_out copy_out;
if (syscall_trap) {
op_out = &copy_out;
}
#endif
key = prepare_for_ramfunc();
ret = flash_andes_qspi_xip_get_status(dev, op_out);
cleanup_after_ramfunc(key);
#ifdef CONFIG_USERSPACE
if (ret == 0 && syscall_trap) {
K_OOPS(k_usermode_to_copy(out, op_out, sizeof(copy_out)));
}
#endif
break;
}
case FLASH_ANDES_XIP_EX_OP_SET_STATUS_REGS: {
struct andes_xip_ex_ops_set_in *op_in = (struct andes_xip_ex_ops_set_in *)in;
#ifdef CONFIG_USERSPACE
struct andes_xip_ex_ops_set_in copy_in;
if (syscall_trap) {
K_OOPS(k_usermode_from_copy(&copy_in, op_in, sizeof(copy_in)));
op_in = &copy_in;
}
#endif
key = prepare_for_ramfunc();
ret = flash_andes_qspi_xip_set_status(dev, op_in);
cleanup_after_ramfunc(key);
break;
}
case FLASH_ANDES_XIP_EX_OP_LOCK: {
struct andes_xip_ex_ops_lock_in *op_in = (struct andes_xip_ex_ops_lock_in *)in;
#ifdef CONFIG_USERSPACE
struct andes_xip_ex_ops_lock_in copy_in;
if (syscall_trap) {
K_OOPS(k_usermode_from_copy(&copy_in, op_in, sizeof(copy_in)));
op_in = &copy_in;
}
#endif
ret = flash_andes_qspi_xip_lock(dev, op_in);
break;
}
case FLASH_ANDES_XIP_EX_OP_LOCK_STATE: {
struct andes_xip_ex_ops_lock_state_out *op_out =
(struct andes_xip_ex_ops_lock_state_out *)out;
#ifdef CONFIG_USERSPACE
struct andes_xip_ex_ops_lock_state_out copy_out;
if (syscall_trap) {
op_out = &copy_out;
}
#endif
key = prepare_for_ramfunc();
ret = flash_andes_qspi_xip_lock_state(dev, op_out);
cleanup_after_ramfunc(key);
#ifdef CONFIG_USERSPACE
if (ret == 0 && syscall_trap) {
K_OOPS(k_usermode_to_copy(out, op_out, sizeof(copy_out)));
}
#endif
break;
}
case FLASH_ANDES_XIP_EX_OP_MEM_READ_CMD: {
struct andes_xip_ex_ops_mem_read_cmd_in *op_in =
(struct andes_xip_ex_ops_mem_read_cmd_in *)in;
#ifdef CONFIG_USERSPACE
struct andes_xip_ex_ops_mem_read_cmd_in copy_in;
if (syscall_trap) {
K_OOPS(k_usermode_from_copy(&copy_in, op_in, sizeof(copy_in)));
op_in = &copy_in;
}
#endif
key = prepare_for_ramfunc();
ret = flash_andes_qspi_xip_set_memrdcmd(dev, op_in);
cleanup_after_ramfunc(key);
break;
}
default:
ret = -ENOTSUP;
break;
}
return ret;
}
#endif
static DEVICE_API(flash, flash_andes_qspi_xip_api) = {
.read = flash_andes_qspi_xip_read,
.write = flash_andes_qspi_xip_write,
.erase = flash_andes_qspi_xip_erase,
.get_parameters = flash_andes_qspi_xip_get_parameters,
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
.page_layout = flash_andes_qspi_xip_pages_layout,
#endif
#ifdef CONFIG_FLASH_EX_OP_ENABLED
.ex_op = flash_andes_qspi_xip_ex_op,
#endif
};
#define IS_XIP(node_id) DT_SAME_NODE(node_id, DT_CHOSEN(zephyr_flash))
#define LAYOUT_PAGES_PROP(n) \
IF_ENABLED(CONFIG_FLASH_PAGE_LAYOUT, \
(.layout = { \
.pages_count = (DT_INST_PROP(n, size) / \
CONFIG_FLASH_ANDES_QSPI_LAYOUT_PAGE_SIZE), \
.pages_size = \
CONFIG_FLASH_ANDES_QSPI_LAYOUT_PAGE_SIZE, \
}, \
))
#define FLASH_ANDES_QSPI_XIP_INIT(n) \
static const struct flash_andes_qspi_xip_config flash_andes_qspi_xip_config_##n = { \
.regs = (struct atcspi200_regs *)DT_REG_ADDR(DT_INST_BUS(n)), \
.mapped_base = DT_REG_ADDR_BY_IDX(DT_INST_BUS(n), 1), \
.parameters = {.write_block_size = 1, .erase_value = 0xff}, \
.is_xip = IS_XIP(DT_DRV_INST(n)), \
.flash_size = DT_INST_PROP(n, size), \
LAYOUT_PAGES_PROP(n)}; \
\
static struct flash_andes_qspi_xip_data flash_andes_qspi_xip_data_##n; \
\
DEVICE_DT_INST_DEFINE(n, &flash_andes_qspi_xip_init, NULL, &flash_andes_qspi_xip_data_##n, \
&flash_andes_qspi_xip_config_##n, POST_KERNEL, \
CONFIG_FLASH_ANDES_QSPI_INIT_PRIORITY, &flash_andes_qspi_xip_api);
DT_INST_FOREACH_STATUS_OKAY(FLASH_ANDES_QSPI_XIP_INIT)