blob: fc96af414d46621d030ae7e41c14c7eb30f4a40a [file] [log] [blame]
/*
* Copyright (c) 2025 Cirrus Logic, Inc.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Driver for AT25XV021A SPI flash devices, a variant of Atmel's AT25 family
*/
#define DT_DRV_COMPAT atmel_at25xv021a
#include <zephyr/drivers/flash.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/kernel.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(spi_flash_at25xv021a, CONFIG_FLASH_LOG_LEVEL);
/* AT25XV021A opcodes */
#define DEV_READ (0x0b)
#define DEV_PAGE_ERASE (0x81)
#define DEV_CHIP_ERASE (0x60)
#define DEV_WRITE (0x02)
#define DEV_WRITE_ENABLE (0x06)
#define DEV_PROTECT (0x36)
#define DEV_UNPROTECT (0x39)
#define DEV_READ_SR (0x05)
#define DEV_WRITE_SR (0x01)
#define DEV_READ_DEVICE_INFO (0x9f)
#define DEV_DEEP_SLEEP (0xb9)
#define DEV_ULTRA_DEEP_SLEEP (0x79)
#define DEV_RESUME (0xab)
/* AT25XV021A driver instruction set */
#define DEV_DUMMY_BYTE (0x00)
#define DEV_HW_LOCK (0xf8)
#define DEV_HW_UNLOCK (0x00)
#define DEV_GLOBAL_PROTECT (0x7f)
#define DEV_GLOBAL_UNPROTECT (0x00)
/* AT25XV021A status register masks */
#define DEV_SR_BUSY (1U << 0)
#define DEV_SR_WEL (1U << 1)
#define DEV_SR_SWP (3U << 2)
#define DEV_SR_WPP (1U << 4)
#define DEV_SR_EPE (1U << 5)
#define DEV_SR_SPRL (1U << 7)
#define ANY_DEV_WRITEABLE !DT_ALL_INST_HAS_BOOL_STATUS_OKAY(read_only)
#define ANY_DEV_HAS_WP_GPIO DT_ANY_INST_HAS_PROP_STATUS_OKAY(wp_gpios)
struct flash_at25xv021a_config {
struct spi_dt_spec spi;
#if ANY_DEV_HAS_WP_GPIO
struct gpio_dt_spec wp_gpio;
#endif /* ANY_DEV_HAS_WP_GPIO */
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
struct flash_pages_layout pages_layout;
#endif /* defined(CONFIG_FLASH_PAGE_LAYOUT) */
struct flash_parameters parameters;
uint8_t jedec_id[3];
size_t size;
k_timeout_t timeout;
bool read_only;
bool ultra_deep_sleep;
#if ANY_DEV_WRITEABLE
size_t page_size;
k_timeout_t timeout_erase;
#endif /* ANY_DEV_WRITEABLE */
};
struct flash_at25xv021a_data {
struct k_mutex lock;
};
static int flash_at25xv021a_read_status(const struct device *dev, uint8_t *status)
{
int err;
uint8_t sr[2];
uint8_t cmd[2] = {DEV_READ_SR, DEV_DUMMY_BYTE};
const struct flash_at25xv021a_config *config = dev->config;
const struct spi_buf tx_buf = {.buf = cmd, .len = ARRAY_SIZE(cmd)};
const struct spi_buf_set tx = {.buffers = &tx_buf, .count = 1};
const struct spi_buf rx_buf = {.buf = sr, .len = ARRAY_SIZE(sr)};
const struct spi_buf_set rx = {.buffers = &rx_buf, .count = 1};
err = spi_transceive_dt(&config->spi, &tx, &rx);
if (err < 0) {
LOG_ERR("unable to read status register from %s", dev->name);
return err;
}
*status = sr[1];
return err;
}
static int flash_at25xv021a_wait_for_idle(const struct device *dev, k_timeout_t timeout)
{
int err;
uint8_t status;
k_timepoint_t end = sys_timepoint_calc(timeout);
while (!sys_timepoint_expired(end)) {
err = flash_at25xv021a_read_status(dev, &status);
if (err != 0) {
return err;
}
if ((status & DEV_SR_BUSY) == 0) {
return 0;
}
k_msleep(1);
}
LOG_ERR("timed out waiting for %s to idle", dev->name);
return -EBUSY;
}
static int flash_at25xv021a_spi_transceive(const struct device *dev, const struct spi_dt_spec *spi,
const struct spi_buf_set *tx,
const struct spi_buf_set *rx)
{
int err;
const struct flash_at25xv021a_config *config = dev->config;
err = flash_at25xv021a_wait_for_idle(dev, config->timeout);
if (err != 0) {
return err;
}
err = spi_transceive_dt(spi, tx, rx);
if (err < 0) {
LOG_ERR("unable to read from %s", dev->name);
}
return err;
}
static int flash_at25xv021a_verify_device(const struct device *dev)
{
int err;
uint8_t info[3];
uint8_t cmd[1] = {DEV_READ_DEVICE_INFO};
const struct flash_at25xv021a_config *config = dev->config;
const struct spi_buf tx_buf = {.buf = cmd, .len = ARRAY_SIZE(cmd)};
const struct spi_buf_set tx = {.buffers = &tx_buf, .count = 1};
const struct spi_buf rx_bufs[2] = {
{.buf = NULL, .len = ARRAY_SIZE(cmd)},
{.buf = info, .len = ARRAY_SIZE(info)},
};
const struct spi_buf_set rx = {.buffers = rx_bufs, .count = ARRAY_SIZE(rx_bufs)};
err = flash_at25xv021a_spi_transceive(dev, &config->spi, &tx, &rx);
if (err != 0) {
return err;
}
if ((info[0] != config->jedec_id[0]) || (info[1] != config->jedec_id[1]) ||
(info[2] != config->jedec_id[2])) {
return -ENODEV;
}
return err;
}
static int flash_at25xv021a_read_internal(const struct device *dev, off_t offset, void *buf,
size_t len)
{
const struct flash_at25xv021a_config *config = dev->config;
uint8_t cmd[5] = {
DEV_READ,
FIELD_GET(GENMASK(23, 16), offset),
FIELD_GET(GENMASK(15, 8), offset),
FIELD_GET(GENMASK(7, 0), offset),
DEV_DUMMY_BYTE,
};
const struct spi_buf tx_buf = {.buf = cmd, .len = ARRAY_SIZE(cmd)};
const struct spi_buf_set tx = {
.buffers = &tx_buf,
.count = 1,
};
const struct spi_buf rx_bufs[2] = {
{.buf = NULL, .len = ARRAY_SIZE(cmd)},
{.buf = buf, .len = len},
};
const struct spi_buf_set rx = {.buffers = rx_bufs, .count = ARRAY_SIZE(rx_bufs)};
return flash_at25xv021a_spi_transceive(dev, &config->spi, &tx, &rx);
}
static int flash_at25xv021a_read(const struct device *dev, off_t offset, void *buf, size_t len)
{
int err;
struct flash_at25xv021a_data *data = dev->data;
const struct flash_at25xv021a_config *config = dev->config;
if (len == 0) {
LOG_DBG("attempted to read 0 bytes from %s", dev->name);
return 0;
}
if (len > config->size) {
LOG_ERR("attempted to read more than device %s size: %u", dev->name, config->size);
return -EINVAL;
}
k_mutex_lock(&data->lock, K_FOREVER);
err = flash_at25xv021a_read_internal(dev, offset, buf, len);
k_mutex_unlock(&data->lock);
return err;
}
#if ANY_DEV_WRITEABLE
static int flash_at25xv021a_check_status(const struct device *dev, uint8_t mask, uint8_t *status)
{
int err;
uint8_t temp_status;
const struct flash_at25xv021a_config *config = dev->config;
err = flash_at25xv021a_wait_for_idle(dev, config->timeout);
if (err != 0) {
return err;
}
err = flash_at25xv021a_read_status(dev, &temp_status);
if (err != 0) {
return err;
}
*status = (temp_status & mask);
return err;
}
static int flash_at25xv021a_spi_write(const struct device *dev, const struct spi_dt_spec *spi,
const struct spi_buf_set *tx)
{
int err;
const struct flash_at25xv021a_config *config = dev->config;
err = flash_at25xv021a_wait_for_idle(dev, config->timeout);
if (err != 0) {
return err;
}
err = spi_write_dt(spi, tx);
if (err < 0) {
LOG_ERR("unable to write to %s", dev->name);
}
return err;
}
static int flash_at25xv021a_write_enable(const struct device *dev)
{
int err;
uint8_t status;
uint8_t cmd[1] = {DEV_WRITE_ENABLE};
const struct flash_at25xv021a_config *config = dev->config;
const struct spi_buf tx_buf = {.buf = &cmd, .len = ARRAY_SIZE(cmd)};
const struct spi_buf_set tx = {.buffers = &tx_buf, .count = 1};
err = flash_at25xv021a_spi_write(dev, &config->spi, &tx);
if (err != 0) {
return err;
}
err = flash_at25xv021a_check_status(dev, DEV_SR_WEL, &status);
if (err != 0) {
return err;
}
if (status != DEV_SR_WEL) {
LOG_ERR("unable to enable writes on %s", dev->name);
return -EIO;
}
return err;
}
static int flash_at25xv021a_hardware_lock(const struct device *dev)
{
int err;
uint8_t status;
uint8_t cmd[2] = {DEV_WRITE_SR, DEV_HW_LOCK};
const struct flash_at25xv021a_config *config = dev->config;
const struct spi_buf tx_buf = {.buf = &cmd, .len = ARRAY_SIZE(cmd)};
const struct spi_buf_set tx = {.buffers = &tx_buf, .count = 1};
err = flash_at25xv021a_write_enable(dev);
if (err != 0) {
return err;
}
err = flash_at25xv021a_spi_write(dev, &config->spi, &tx);
if (err != 0) {
return err;
}
/* Ensure device is idle before configuring WP pin. */
err = flash_at25xv021a_wait_for_idle(dev, config->timeout);
if (err != 0) {
return err;
}
#if ANY_DEV_HAS_WP_GPIO
err = gpio_pin_configure_dt(&config->wp_gpio, GPIO_OUTPUT_ACTIVE);
if (err < 0) {
LOG_ERR("unable to set WP GPIO");
return err;
}
#endif /* ANY_DEV_HAS_WP_GPIOS */
err = flash_at25xv021a_check_status(dev, DEV_SR_SPRL, &status);
if (err != 0) {
return err;
}
if (status != DEV_SR_SPRL) {
LOG_ERR("unable to lock hardware");
return -EIO;
}
return err;
}
static int flash_at25xv021a_hardware_unlock(const struct device *dev)
{
int err;
uint8_t status;
uint8_t cmd[2] = {DEV_WRITE_SR, DEV_HW_UNLOCK};
const struct flash_at25xv021a_config *config = dev->config;
const struct spi_buf tx_buf = {.buf = &cmd, .len = ARRAY_SIZE(cmd)};
const struct spi_buf_set tx = {.buffers = &tx_buf, .count = 1};
/* Ensure device is idle before configuring WP pin. */
err = flash_at25xv021a_wait_for_idle(dev, config->timeout);
if (err != 0) {
return err;
}
#if ANY_DEV_HAS_WP_GPIO
err = gpio_pin_configure_dt(&config->wp_gpio, GPIO_OUTPUT_INACTIVE);
if (err < 0) {
LOG_ERR("unable to set WP GPIO");
return err;
}
#endif /* ANY_DEV_HAS_WP_GPIO */
err = flash_at25xv021a_write_enable(dev);
if (err != 0) {
return err;
}
err = flash_at25xv021a_spi_write(dev, &config->spi, &tx);
if (err != 0) {
return err;
}
err = flash_at25xv021a_check_status(dev, DEV_SR_SPRL, &status);
if (err != 0) {
return err;
}
if (status == DEV_SR_SPRL) {
LOG_ERR("unable to unlock hardware");
return -EIO;
}
return err;
}
static int flash_at25xv021a_global_protection(const struct device *dev, uint8_t protection_cmd)
{
int err;
uint8_t expected_status, status;
const struct flash_at25xv021a_config *config = dev->config;
uint8_t cmd[2] = {DEV_WRITE_SR, protection_cmd};
const struct spi_buf tx_buf = {.buf = &cmd, .len = ARRAY_SIZE(cmd)};
const struct spi_buf_set tx = {.buffers = &tx_buf, .count = 1};
err = flash_at25xv021a_hardware_unlock(dev);
if (err != 0) {
return err;
}
err = flash_at25xv021a_write_enable(dev);
if (err != 0) {
return err;
}
err = flash_at25xv021a_spi_write(dev, &config->spi, &tx);
if (err != 0) {
return err;
}
err = flash_at25xv021a_hardware_lock(dev);
if (err != 0) {
return err;
}
err = flash_at25xv021a_check_status(dev, DEV_SR_SWP, &status);
if (err != 0) {
return err;
}
expected_status = (protection_cmd == DEV_GLOBAL_PROTECT) ? DEV_SR_SWP : 0;
if (status != expected_status) {
LOG_ERR("unable to update global protection");
return -EIO;
}
return err;
}
static int flash_at25xv021a_software_protection(const struct device *dev, off_t page,
uint8_t protection_cmd)
{
int err;
uint8_t status;
uint8_t unexpected_status;
const struct flash_at25xv021a_config *config = dev->config;
uint8_t cmd[4] = {
protection_cmd,
FIELD_GET(GENMASK(23, 16), page),
FIELD_GET(GENMASK(15, 8), page),
FIELD_GET(GENMASK(7, 0), page),
};
const struct spi_buf tx_buf = {.buf = &cmd, .len = ARRAY_SIZE(cmd)};
const struct spi_buf_set tx = {.buffers = &tx_buf, .count = 1};
err = flash_at25xv021a_hardware_unlock(dev);
if (err != 0) {
return err;
}
err = flash_at25xv021a_write_enable(dev);
if (err != 0) {
return err;
}
err = flash_at25xv021a_spi_write(dev, &config->spi, &tx);
if (err != 0) {
return err;
}
err = flash_at25xv021a_hardware_lock(dev);
if (err != 0) {
return err;
}
err = flash_at25xv021a_check_status(dev, DEV_SR_SWP, &status);
if (err != 0) {
return err;
}
unexpected_status = (protection_cmd == DEV_PROTECT) ? 0 : DEV_SR_SWP;
if (status == unexpected_status) {
LOG_ERR("failed to update software protection for %s", dev->name);
return -EIO;
}
return err;
}
static int flash_at25xv021a_hardware_init(const struct device *dev)
{
int err;
uint8_t status;
err = flash_at25xv021a_hardware_unlock(dev);
if (err != 0) {
return err;
}
err = flash_at25xv021a_global_protection(dev, DEV_GLOBAL_PROTECT);
if (err != 0) {
return err;
}
err = flash_at25xv021a_hardware_lock(dev);
if (err != 0) {
return err;
}
err = flash_at25xv021a_check_status(dev, (DEV_SR_SPRL | DEV_SR_SWP), &status);
if (err != 0) {
return err;
}
if (status != (DEV_SR_SPRL | DEV_SR_SWP)) {
LOG_ERR("unable to initialize hardware");
return -EIO;
}
return err;
}
static int flash_at25xv021a_write_internal(const struct device *dev, off_t offset, const void *buf,
size_t len)
{
int err;
uint8_t status;
const struct flash_at25xv021a_config *config = dev->config;
uint8_t cmd[4] = {
DEV_WRITE,
FIELD_GET(GENMASK(23, 16), offset),
FIELD_GET(GENMASK(15, 8), offset),
FIELD_GET(GENMASK(7, 0), offset),
};
const struct spi_buf tx_bufs[2] = {
{.buf = cmd, .len = ARRAY_SIZE(cmd)},
{.buf = (void *)buf, .len = len},
};
const struct spi_buf_set tx = {.buffers = tx_bufs, .count = ARRAY_SIZE(tx_bufs)};
err = flash_at25xv021a_write_enable(dev);
if (err != 0) {
return err;
}
err = flash_at25xv021a_spi_write(dev, &config->spi, &tx);
if (err != 0) {
return err;
}
err = flash_at25xv021a_check_status(dev, DEV_SR_EPE, &status);
if (err != 0) {
return err;
}
if (status != 0) {
LOG_ERR("failed to program %s", dev->name);
return -EIO;
}
return err;
}
static int flash_at25xv021a_process_write(const struct device *dev, off_t offset, const void *buf,
size_t len)
{
int err;
const struct flash_at25xv021a_config *config = dev->config;
off_t page_start = ROUND_DOWN(offset, config->page_size);
err = flash_at25xv021a_software_protection(dev, page_start, DEV_UNPROTECT);
if (err != 0) {
return err;
}
err = flash_at25xv021a_write_internal(dev, offset, buf, len);
if (err != 0) {
return err;
}
return flash_at25xv021a_software_protection(dev, page_start, DEV_PROTECT);
}
static int flash_at25xv021a_chip_erase(const struct device *dev)
{
int err;
uint8_t status;
uint8_t cmd[1] = {DEV_CHIP_ERASE};
const struct flash_at25xv021a_config *config = dev->config;
const struct spi_buf tx_buf = {.buf = &cmd, .len = ARRAY_SIZE(cmd)};
const struct spi_buf_set tx = {.buffers = &tx_buf, .count = 1};
err = flash_at25xv021a_global_protection(dev, DEV_GLOBAL_UNPROTECT);
if (err != 0) {
return err;
}
err = flash_at25xv021a_write_enable(dev);
if (err != 0) {
return err;
}
err = flash_at25xv021a_spi_write(dev, &config->spi, &tx);
if (err != 0) {
return err;
}
/* Need to wait extra time for chip erase. */
err = flash_at25xv021a_wait_for_idle(dev, config->timeout_erase);
if (err != 0) {
return err;
}
err = flash_at25xv021a_global_protection(dev, DEV_GLOBAL_PROTECT);
if (err != 0) {
return err;
}
err = flash_at25xv021a_check_status(dev, DEV_SR_EPE, &status);
if (err != 0) {
return err;
}
if (status != 0) {
LOG_ERR("failed to erase %s", dev->name);
return -EIO;
}
return err;
}
static int flash_at25xv021a_erase_internal(const struct device *dev, uint32_t addr)
{
int err;
uint8_t status;
const struct flash_at25xv021a_config *config = dev->config;
uint8_t cmd[4] = {
DEV_PAGE_ERASE,
FIELD_GET(GENMASK(9, 8), addr),
FIELD_GET(GENMASK(7, 0), addr),
DEV_DUMMY_BYTE,
};
const struct spi_buf tx_buf = {.buf = &cmd, .len = ARRAY_SIZE(cmd)};
const struct spi_buf_set tx = {.buffers = &tx_buf, .count = 1};
err = flash_at25xv021a_write_enable(dev);
if (err != 0) {
return err;
}
err = flash_at25xv021a_spi_write(dev, &config->spi, &tx);
if (err != 0) {
return err;
}
/* Page erase operations can take up to 20 milliseconds. */
err = flash_at25xv021a_wait_for_idle(dev, config->timeout_erase);
if (err != 0) {
return err;
}
err = flash_at25xv021a_check_status(dev, DEV_SR_EPE, &status);
if (err != 0) {
return err;
}
if (status != 0) {
LOG_ERR("unable to erase from %s", dev->name);
return -EIO;
}
return err;
}
static int flash_at25xv021a_process_erase(const struct device *dev, off_t *offset)
{
int err;
const struct flash_at25xv021a_config *config = dev->config;
uint32_t page_addr = *offset / config->page_size;
err = flash_at25xv021a_software_protection(dev, *offset, DEV_UNPROTECT);
if (err != 0) {
return err;
}
err = flash_at25xv021a_erase_internal(dev, page_addr);
if (err != 0) {
return err;
}
err = flash_at25xv021a_software_protection(dev, *offset, DEV_PROTECT);
if (err != 0) {
return err;
}
*offset += config->page_size;
return err;
}
static int flash_at25xv021a_write(const struct device *dev, off_t offset, const void *buf,
size_t len)
{
int err;
struct flash_at25xv021a_data *data = dev->data;
const struct flash_at25xv021a_config *config = dev->config;
if (config->read_only) {
LOG_ERR("attempted to write to read-only device %s", dev->name);
return -EINVAL;
}
if (len == 0) {
LOG_DBG("attempted to write 0 bytes to %s", dev->name);
return 0;
}
if (len > config->page_size) {
LOG_ERR("attempted to write more than page size in one write operation");
return -EINVAL;
}
k_mutex_lock(&data->lock, K_FOREVER);
err = flash_at25xv021a_process_write(dev, offset, buf, len);
if (err != 0) {
LOG_ERR("unable to complete write operation for %s", dev->name);
}
k_mutex_unlock(&data->lock);
return err;
}
static int flash_at25xv021a_erase(const struct device *dev, off_t offset, size_t size)
{
int err = 0;
struct flash_at25xv021a_data *data = dev->data;
const struct flash_at25xv021a_config *config = dev->config;
if (config->read_only) {
LOG_ERR("attempted to erase from read-only device %s", dev->name);
return -EINVAL;
}
if (size == 0) {
LOG_DBG("attempted to erase 0 bytes from %s", dev->name);
return 0;
}
if (offset % config->page_size != 0 || size % config->page_size != 0) {
LOG_ERR("offset and/or size is not aligned to page size in %s erase", dev->name);
return -EINVAL;
}
if (offset + size > config->size) {
LOG_ERR("attempted to erase beyond %s size boundary: %u", dev->name, config->size);
return -EINVAL;
}
k_mutex_lock(&data->lock, K_FOREVER);
if (offset == 0 && size == config->size) {
err = flash_at25xv021a_chip_erase(dev);
k_mutex_unlock(&data->lock);
return err;
}
while (size != 0) {
err = flash_at25xv021a_process_erase(dev, &offset);
if (err != 0) {
LOG_ERR("unable to complete erase operation for %s", dev->name);
break;
}
size -= config->page_size;
}
k_mutex_unlock(&data->lock);
return err;
}
#else
static int flash_at25xv021a_write(const struct device *dev, off_t offset, const void *buf,
size_t len)
{
LOG_ERR("attempted to write to read-only device %s", dev->name);
return -EINVAL;
}
static int flash_at25xv021a_erase(const struct device *dev, off_t offset, size_t size)
{
LOG_ERR("attempted to erase from read-only device %s", dev->name);
return -EINVAL;
}
#endif /* ANY_DEV_WRITEABLE */
static int flash_at25xv021a_get_size(const struct device *dev, uint64_t *size)
{
const struct flash_at25xv021a_config *config = dev->config;
*size = (uint64_t)config->size;
return 0;
}
static const struct flash_parameters *flash_at25xv021a_get_parameters(const struct device *dev)
{
const struct flash_at25xv021a_config *config = dev->config;
return &config->parameters;
}
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
static void flash_at25xv021a_pages_layout(const struct device *dev,
const struct flash_pages_layout **layout,
size_t *layout_size)
{
const struct flash_at25xv021a_config *config = dev->config;
*layout = &config->pages_layout;
*layout_size = 1;
}
#endif /* defined(CONFIG_FLASH_PAGE_LAYOUT) */
#ifdef CONFIG_PM_DEVICE
static int flash_at25xv021a_resume(const struct device *dev)
{
int err;
uint8_t cmd[1] = {DEV_RESUME};
const struct flash_at25xv021a_config *config = dev->config;
const struct spi_buf tx_bufs = {.buf = cmd, .len = ARRAY_SIZE(cmd)};
const struct spi_buf_set tx = {.buffers = &tx_bufs, .count = 1};
/* If in ultra deep sleep mode, we can send any command. Don't check return status. */
(void)spi_write_dt(&config->spi, &tx);
/* Device takes a minimum of 70 microseconds to exit ultra deep sleep mode. */
k_msleep(1);
err = flash_at25xv021a_verify_device(dev);
if (err != 0) {
LOG_ERR("failed to resume %s", dev->name);
}
return err;
}
static int flash_at25xv021a_suspend(const struct device *dev)
{
int err;
const struct flash_at25xv021a_config *config = dev->config;
uint8_t cmd[1] = {config->ultra_deep_sleep ? DEV_ULTRA_DEEP_SLEEP : DEV_DEEP_SLEEP};
const struct spi_buf tx_bufs = {.buf = cmd, .len = ARRAY_SIZE(cmd)};
const struct spi_buf_set tx = {.buffers = &tx_bufs, .count = 1};
/* Longer timeout in case suspend is called during a chip erase operation. */
err = flash_at25xv021a_wait_for_idle(dev, config->timeout_erase);
if (err != 0) {
return err;
}
return flash_at25xv021a_spi_write(dev, &config->spi, &tx);
}
static int flash_at25xv021a_pm_action(const struct device *dev, enum pm_device_action action)
{
switch (action) {
case PM_DEVICE_ACTION_RESUME:
return flash_at25xv021a_resume(dev);
case PM_DEVICE_ACTION_SUSPEND:
return flash_at25xv021a_suspend(dev);
case PM_DEVICE_ACTION_TURN_OFF:
__fallthrough;
case PM_DEVICE_ACTION_TURN_ON:
__fallthrough;
default:
break;
}
return -ENOTSUP;
}
#endif /* CONFIG_PM_DEVICE */
static int flash_at25xv021a_init(const struct device *dev)
{
int err;
struct flash_at25xv021a_data *data = dev->data;
const struct flash_at25xv021a_config *config = dev->config;
err = k_mutex_init(&data->lock);
if (err != 0) {
LOG_ERR("unable to initialize mutex");
return err;
}
if (!device_is_ready(config->spi.bus)) {
LOG_ERR("spi bus is not ready");
return -ENODEV;
}
#ifdef CONFIG_PM_DEVICE
/* Resume if device was previously suspended. */
err = flash_at25xv021a_resume(dev);
if (err != 0) {
return err;
}
#endif /* CONFIG_PM_DEVICE */
err = flash_at25xv021a_verify_device(dev);
if (err != 0) {
LOG_ERR("unable to verify device information");
return err;
}
#if ANY_DEV_WRITEABLE && ANY_DEV_HAS_WP_GPIO
if (!device_is_ready(config->wp_gpio.port)) {
LOG_ERR("device controlling WP GPIO is not ready");
return -ENODEV;
}
if (!gpio_is_ready_dt(&config->wp_gpio)) {
LOG_ERR("WP GPIO is not ready");
return -ENODEV;
}
#endif /* ANY_DEV_WRITEABLE && ANY_DEV_HAS_WP_GPIO */
#if ANY_DEV_WRITEABLE
return flash_at25xv021a_hardware_init(dev);
#else
return 0;
#endif /* ANY_DEV_WRITEABLE */
}
static DEVICE_API(flash, spi_flash_at25xv021a_api) = {
.read = flash_at25xv021a_read,
.write = flash_at25xv021a_write,
.erase = flash_at25xv021a_erase,
.get_size = flash_at25xv021a_get_size,
.get_parameters = flash_at25xv021a_get_parameters,
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
.page_layout = flash_at25xv021a_pages_layout,
#endif
};
#define ASSERT_SIZE(sz) BUILD_ASSERT(sz > 0, "Size must be positive")
#define ASSERT_PAGE_SIZE(pg) \
BUILD_ASSERT(IS_POWER_OF_TWO(pg), "Page size must be positive and a power of 2")
#define ASSERT_TIMEOUTS(timeout, timeout_erase) \
BUILD_ASSERT((timeout > 0) && (timeout_erase > 0), "Timeouts must be positive")
#define SPI_OP (SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8))
#define PAGE_SIZE_IF_WRITEABLE(inst) \
COND_CODE_0(DT_INST_NODE_PROP_OR(inst, read_only, false), \
(.page_size = DT_INST_PROP(inst, page_size),), \
(EMPTY))
#define TIMEOUT_ERASE_IF_WRITEABLE(inst) \
COND_CODE_0(DT_INST_NODE_PROP_OR(inst, read_only, false), \
(.timeout_erase = K_MSEC(DT_INST_PROP(inst, timeout_erase)),), \
(EMPTY))
#define WP_GPIO_IF_PROVIDED(inst) \
IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, wp_gpios), \
(.wp_gpio = GPIO_DT_SPEC_INST_GET(inst, wp_gpios),))
#define PAGES_LAYOUT_IF_ENABLED(inst) \
IF_ENABLED(CONFIG_FLASH_PAGE_LAYOUT, (.pages_layout = { \
.pages_count = DT_INST_PROP(inst, size) / DT_INST_PROP(inst, page_size),\
.pages_size = DT_INST_PROP(inst, page_size), \
},))
#define SPI_FLASH_AT25XV021A_DEFINE(inst) \
\
ASSERT_SIZE(DT_INST_PROP(inst, size)); \
ASSERT_PAGE_SIZE(DT_INST_PROP(inst, page_size)); \
ASSERT_TIMEOUTS(DT_INST_PROP(inst, timeout), DT_INST_PROP(inst, timeout_erase)); \
\
static const struct flash_at25xv021a_config flash_at25xv021a_config_##inst = { \
.spi = SPI_DT_SPEC_INST_GET(inst, SPI_OP), \
.jedec_id = DT_INST_PROP(inst, jedec_id), \
.size = DT_INST_PROP(inst, size), \
.timeout = K_MSEC(DT_INST_PROP(inst, timeout)), \
.read_only = DT_INST_PROP(inst, read_only), \
.ultra_deep_sleep = DT_INST_PROP(inst, ultra_deep_sleep), \
.parameters = \
{ \
.write_block_size = DT_INST_PROP(inst, page_size), \
.erase_value = 0xff, \
}, \
WP_GPIO_IF_PROVIDED(inst) PAGES_LAYOUT_IF_ENABLED(inst) \
PAGE_SIZE_IF_WRITEABLE(inst) TIMEOUT_ERASE_IF_WRITEABLE(inst)}; \
\
static struct flash_at25xv021a_data flash_at25xv021a_data_##inst; \
\
PM_DEVICE_DT_INST_DEFINE(inst, flash_at25xv021a_pm_action); \
\
DEVICE_DT_INST_DEFINE(inst, flash_at25xv021a_init, PM_DEVICE_DT_INST_GET(inst), \
&flash_at25xv021a_data_##inst, &flash_at25xv021a_config_##inst, \
POST_KERNEL, CONFIG_FLASH_INIT_PRIORITY, &spi_flash_at25xv021a_api);
DT_INST_FOREACH_STATUS_OKAY(SPI_FLASH_AT25XV021A_DEFINE)