drivers: flash: spi nor: Add MultInstance support

Modify the SPI Nor driver to be able to have multiple instances at
the same time.

This patch is heavily inspired by the at45 driver.
It was tested on the nRF5340 DK by using the external spi memory two times.
Macros were improved by de-nordic

Signed-off-by: Mehdi Zemzem <mehdi.zemzem2@gmail.com>
This commit is contained in:
Mehdi Zemzem 2024-03-19 09:55:28 +01:00 committed by Fabio Baltieri
parent ca36feeca8
commit adedf14c42
2 changed files with 346 additions and 243 deletions

View file

@ -48,36 +48,43 @@ LOG_MODULE_REGISTER(spi_nor, CONFIG_FLASH_LOG_LEVEL);
#define SPI_NOR_MAX_ADDR_WIDTH 4 #define SPI_NOR_MAX_ADDR_WIDTH 4
#if DT_INST_NODE_HAS_PROP(0, t_enter_dpd)
#define T_DP_MS DIV_ROUND_UP(DT_INST_PROP(0, t_enter_dpd), NSEC_PER_MSEC)
#else /* T_ENTER_DPD */
#define T_DP_MS 0
#endif /* T_ENTER_DPD */
#if DT_INST_NODE_HAS_PROP(0, t_exit_dpd)
#define T_RES1_MS DIV_ROUND_UP(DT_INST_PROP(0, t_exit_dpd), NSEC_PER_MSEC)
#endif /* T_EXIT_DPD */
#if DT_INST_NODE_HAS_PROP(0, dpd_wakeup_sequence)
#define T_DPDD_MS DIV_ROUND_UP(DT_INST_PROP_BY_IDX(0, dpd_wakeup_sequence, 0), NSEC_PER_MSEC)
#define T_CRDP_MS DIV_ROUND_UP(DT_INST_PROP_BY_IDX(0, dpd_wakeup_sequence, 1), NSEC_PER_MSEC)
#define T_RDP_MS DIV_ROUND_UP(DT_INST_PROP_BY_IDX(0, dpd_wakeup_sequence, 2), NSEC_PER_MSEC)
#else /* DPD_WAKEUP_SEQUENCE */
#define T_DPDD_MS 0
#endif /* DPD_WAKEUP_SEQUENCE */
#define _INST_HAS_WP_OR(inst) DT_INST_NODE_HAS_PROP(inst, wp_gpios) || #define ANY_INST_HAS_TRUE_(idx, bool_prop) \
#define ANY_INST_HAS_WP_GPIOS DT_INST_FOREACH_STATUS_OKAY(_INST_HAS_WP_OR) 0 COND_CODE_1(DT_INST_PROP(idx, bool_prop), (1,), ())
#define _INST_HAS_HOLD_OR(inst) DT_INST_NODE_HAS_PROP(inst, hold_gpios) || #define ANY_INST_HAS_TRUE(bool_prop) \
#define ANY_INST_HAS_HOLD_GPIOS DT_INST_FOREACH_STATUS_OKAY(_INST_HAS_HOLD_OR) 0 COND_CODE_1(IS_EMPTY(DT_INST_FOREACH_STATUS_OKAY_VARGS(ANY_INST_HAS_TRUE_, bool_prop)), \
(0), (1))
#define ANY_INST_HAS_PROP_(idx, prop_name) \
COND_CODE_1(DT_INST_NODE_HAS_PROP(idx, prop_name), (1,), ())
#define ANY_INST_HAS_PROP(prop_name) \
COND_CODE_1(IS_EMPTY(DT_INST_FOREACH_STATUS_OKAY_VARGS(ANY_INST_HAS_PROP_, prop_name)), \
(0), (1))
#define ANY_INST_HAS_MXICY_MX25R_POWER_MODE ANY_INST_HAS_PROP(mxicy_mx25r_power_mode)
#define ANY_INST_HAS_DPD ANY_INST_HAS_TRUE(has_dpd)
#define ANY_INST_HAS_T_EXIT_DPD ANY_INST_HAS_PROP(t_exit_dpd)
#define ANY_INST_HAS_DPD_WAKEUP_SEQUENCE ANY_INST_HAS_PROP(dpd_wakeup_sequence)
#define ANY_INST_HAS_RESET_GPIOS ANY_INST_HAS_PROP(reset_gpios)
#define ANY_INST_HAS_WP_GPIOS ANY_INST_HAS_PROP(wp_gpios)
#define ANY_INST_HAS_HOLD_GPIOS ANY_INST_HAS_PROP(hold_gpios)
#define DEV_CFG(_dev_) ((const struct spi_nor_config * const) (_dev_)->config) #define DEV_CFG(_dev_) ((const struct spi_nor_config * const) (_dev_)->config)
/* MXICY Related defines*/
/* MXICY Low-power/high perf mode is second bit in configuration register 2 */
#define LH_SWITCH_BIT 9
#define JEDEC_MACRONIX_ID 0xc2
#define JEDEC_MX25R_TYPE_ID 0x28
/* Build-time data associated with the device. */ /* Build-time data associated with the device. */
struct spi_nor_config { struct spi_nor_config {
/* Devicetree SPI configuration */ /* Devicetree SPI configuration */
struct spi_dt_spec spi; struct spi_dt_spec spi;
#if DT_INST_NODE_HAS_PROP(0, reset_gpios) #if ANY_INST_HAS_RESET_GPIOS
const struct gpio_dt_spec reset; const struct gpio_dt_spec reset;
#endif #endif
@ -119,12 +126,40 @@ struct spi_nor_config {
#if ANY_INST_HAS_WP_GPIOS #if ANY_INST_HAS_WP_GPIOS
/* The write-protect GPIO (wp-gpios) */ /* The write-protect GPIO (wp-gpios) */
const struct gpio_dt_spec *wp; const struct gpio_dt_spec wp;
#endif #endif
#if ANY_INST_HAS_HOLD_GPIOS #if ANY_INST_HAS_HOLD_GPIOS
/* The hold GPIO (hold-gpios) */ /* The hold GPIO (hold-gpios) */
const struct gpio_dt_spec *hold; const struct gpio_dt_spec hold;
#endif #endif
#if ANY_INST_HAS_DPD
uint16_t t_enter_dpd; /* in microseconds */
uint16_t t_dpdd_ms; /* in microseconds */
#if ANY_INST_HAS_T_EXIT_DPD
uint16_t t_exit_dpd; /* in microseconds */
#endif
#endif
#if ANY_INST_HAS_DPD_WAKEUP_SEQUENCE
uint16_t t_crdp_ms; /* in microseconds */
uint16_t t_rdp_ms; /* in microseconds */
#endif
#if ANY_INST_HAS_MXICY_MX25R_POWER_MODE
bool mxicy_mx25r_power_mode;
#endif
/* exist flags for dts opt-ins */
bool dpd_exist:1;
bool dpd_wakeup_sequence_exist:1;
bool mxicy_mx25r_power_mode_exist:1;
bool enter_4byte_addr_exist:1;
bool reset_gpios_exist:1;
bool requires_ulbpr_exist:1;
bool wp_gpios_exist:1;
bool hold_gpios_exist:1;
}; };
/** /**
@ -133,7 +168,7 @@ struct spi_nor_config {
*/ */
struct spi_nor_data { struct spi_nor_data {
struct k_sem sem; struct k_sem sem;
#if DT_INST_NODE_HAS_PROP(0, has_dpd) #if ANY_INST_HAS_DPD
/* Low 32-bits of uptime counter at which device last entered /* Low 32-bits of uptime counter at which device last entered
* deep power-down. * deep power-down.
*/ */
@ -249,10 +284,16 @@ static const struct flash_parameters flash_nor_parameters = {
/* Capture the time at which the device entered deep power-down. */ /* Capture the time at which the device entered deep power-down. */
static inline void record_entered_dpd(const struct device *const dev) static inline void record_entered_dpd(const struct device *const dev)
{ {
#if DT_INST_NODE_HAS_PROP(0, has_dpd) #if ANY_INST_HAS_DPD
const struct spi_nor_config *const driver_config = dev->config;
if (driver_config->dpd_exist) {
struct spi_nor_data *const driver_data = dev->data; struct spi_nor_data *const driver_data = dev->data;
driver_data->ts_enter_dpd = k_uptime_get_32(); driver_data->ts_enter_dpd = k_uptime_get_32();
}
#else
ARG_UNUSED(dev);
#endif #endif
} }
@ -261,7 +302,10 @@ static inline void record_entered_dpd(const struct device *const dev)
*/ */
static inline void delay_until_exit_dpd_ok(const struct device *const dev) static inline void delay_until_exit_dpd_ok(const struct device *const dev)
{ {
#if DT_INST_NODE_HAS_PROP(0, has_dpd) #if ANY_INST_HAS_DPD
const struct spi_nor_config *const driver_config = dev->config;
if (driver_config->dpd_exist) {
struct spi_nor_data *const driver_data = dev->data; struct spi_nor_data *const driver_data = dev->data;
int32_t since = (int32_t)(k_uptime_get_32() - driver_data->ts_enter_dpd); int32_t since = (int32_t)(k_uptime_get_32() - driver_data->ts_enter_dpd);
@ -273,10 +317,10 @@ static inline void delay_until_exit_dpd_ok(const struct device *const dev)
*/ */
if (since >= 0) { if (since >= 0) {
/* Subtract time required for DPD to be reached */ /* Subtract time required for DPD to be reached */
since -= T_DP_MS; since -= driver_config->t_enter_dpd;
/* Subtract time required in DPD before exit */ /* Subtract time required in DPD before exit */
since -= T_DPDD_MS; since -= driver_config->t_dpdd_ms;
/* If the adjusted time is negative we have to wait /* If the adjusted time is negative we have to wait
* until it reaches zero before we can proceed. * until it reaches zero before we can proceed.
@ -285,7 +329,10 @@ static inline void delay_until_exit_dpd_ok(const struct device *const dev)
k_sleep(K_MSEC((uint32_t)-since)); k_sleep(K_MSEC((uint32_t)-since));
} }
} }
#endif /* DT_INST_NODE_HAS_PROP(0, has_dpd) */ }
#else
ARG_UNUSED(dev);
#endif /* ANY_INST_HAS_DPD */
} }
/* Indicates that an access command includes bytes for the address. /* Indicates that an access command includes bytes for the address.
@ -455,8 +502,10 @@ static int read_sfdp(const struct device *const dev,
static int enter_dpd(const struct device *const dev) static int enter_dpd(const struct device *const dev)
{ {
int ret = 0; int ret = 0;
const struct spi_nor_config *cfg = dev->config;
if (IS_ENABLED(DT_INST_PROP(0, has_dpd))) { ARG_UNUSED(cfg);
if (cfg->dpd_exist) {
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_DPD); ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_DPD);
if (ret == 0) { if (ret == 0) {
record_entered_dpd(dev); record_entered_dpd(dev);
@ -468,11 +517,13 @@ static int enter_dpd(const struct device *const dev)
static int exit_dpd(const struct device *const dev) static int exit_dpd(const struct device *const dev)
{ {
int ret = 0; int ret = 0;
const struct spi_nor_config *cfg = dev->config;
if (IS_ENABLED(DT_INST_PROP(0, has_dpd))) { if (cfg->dpd_exist) {
delay_until_exit_dpd_ok(dev); delay_until_exit_dpd_ok(dev);
#if DT_INST_NODE_HAS_PROP(0, dpd_wakeup_sequence) #if ANY_INST_HAS_DPD_WAKEUP_SEQUENCE
if (cfg->dpd_wakeup_sequence_exist) {
/* Assert CSn and wait for tCRDP. /* Assert CSn and wait for tCRDP.
* *
* Unfortunately the SPI API doesn't allow us to * Unfortunately the SPI API doesn't allow us to
@ -484,13 +535,16 @@ static int exit_dpd(const struct device *const dev)
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_RDID); ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_RDID);
/* Deassert CSn and wait for tRDP */ /* Deassert CSn and wait for tRDP */
k_sleep(K_MSEC(T_RDP_MS)); k_sleep(K_MSEC(cfg->t_rdp_ms));
#else /* DPD_WAKEUP_SEQUENCE */ } else {
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_RDPD); ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_RDPD);
#if ANY_INST_HAS_T_EXIT_DPD
if (ret == 0) { if (ret == 0) {
#if DT_INST_NODE_HAS_PROP(0, t_exit_dpd) if (cfg->dpd_exist) {
k_sleep(K_MSEC(T_RES1_MS)); k_sleep(K_MSEC(cfg->t_exit_dpd));
}
}
#endif /* T_EXIT_DPD */ #endif /* T_EXIT_DPD */
} }
#endif /* DPD_WAKEUP_SEQUENCE */ #endif /* DPD_WAKEUP_SEQUENCE */
@ -581,7 +635,7 @@ static int spi_nor_wrsr(const struct device *dev,
return ret; return ret;
} }
#if DT_INST_NODE_HAS_PROP(0, mxicy_mx25r_power_mode) #if ANY_INST_HAS_MXICY_MX25R_POWER_MODE
/** /**
* @brief Read the configuration register. * @brief Read the configuration register.
@ -595,13 +649,16 @@ static int spi_nor_wrsr(const struct device *dev,
*/ */
static int mxicy_rdcr(const struct device *dev) static int mxicy_rdcr(const struct device *dev)
{ {
uint16_t cr; const struct spi_nor_config *cfg = dev->config;
enum { CMD_RDCR = 0x15 }; uint16_t cr = -ENOSYS;
if (cfg->mxicy_mx25r_power_mode_exist) {
int ret = spi_nor_cmd_read(dev, CMD_RDCR, &cr, sizeof(cr)); int ret = spi_nor_cmd_read(dev, CMD_RDCR, &cr, sizeof(cr));
if (ret < 0) { if (ret < 0) {
return ret; return ret;
} }
}
return cr; return cr;
} }
@ -620,11 +677,15 @@ static int mxicy_rdcr(const struct device *dev)
static int mxicy_wrcr(const struct device *dev, static int mxicy_wrcr(const struct device *dev,
uint16_t cr) uint16_t cr)
{ {
const struct spi_nor_config *cfg = dev->config;
int ret = -ENOSYS;
/* The configuration register bytes on the Macronix MX25R devices are /* The configuration register bytes on the Macronix MX25R devices are
* written using the Write Status Register command where the configuration * written using the Write Status Register command where the configuration
* register bytes are written as two extra bytes after the status register. * register bytes are written as two extra bytes after the status register.
* First read out the current status register to preserve the value. * First read out the current status register to preserve the value.
*/ */
if (cfg->mxicy_mx25r_power_mode_exist) {
int sr = spi_nor_rdsr(dev); int sr = spi_nor_rdsr(dev);
if (sr < 0) { if (sr < 0) {
@ -632,7 +693,7 @@ static int mxicy_wrcr(const struct device *dev,
return sr; return sr;
} }
int ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_WREN); ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_WREN);
if (ret == 0) { if (ret == 0) {
uint8_t data[] = { uint8_t data[] = {
@ -641,31 +702,33 @@ static int mxicy_wrcr(const struct device *dev,
cr >> 8 /* Configuration register 2 */ cr >> 8 /* Configuration register 2 */
}; };
ret = spi_nor_access(dev, SPI_NOR_CMD_WRSR, NOR_ACCESS_WRITE, 0, data, ret = spi_nor_access(dev, SPI_NOR_CMD_WRSR, NOR_ACCESS_WRITE, 0,
sizeof(data)); data, sizeof(data));
spi_nor_wait_until_ready(dev, WAIT_READY_REGISTER); spi_nor_wait_until_ready(dev, WAIT_READY_REGISTER);
} }
}
return ret; return ret;
} }
static int mxicy_configure(const struct device *dev, const uint8_t *jedec_id) static int mxicy_configure(const struct device *dev, const uint8_t *jedec_id)
{ {
const struct spi_nor_config *cfg = dev->config;
int ret = -ENOSYS;
if (cfg->mxicy_mx25r_power_mode_exist) {
/* Low-power/high perf mode is second bit in configuration register 2 */ /* Low-power/high perf mode is second bit in configuration register 2 */
enum { LH_SWITCH_BIT = 9 }; int current_cr, new_cr;
const uint8_t JEDEC_MACRONIX_ID = 0xc2;
const uint8_t JEDEC_MX25R_TYPE_ID = 0x28;
int current_cr, new_cr, ret;
/* lh_switch enum index: /* lh_switch enum index:
* 0: Ultra low power * 0: Ultra low power
* 1: High performance mode * 1: High performance mode
*/ */
const bool use_high_perf = DT_INST_ENUM_IDX(0, mxicy_mx25r_power_mode); const bool use_high_perf = cfg->mxicy_mx25r_power_mode;
/* Only supported on Macronix MX25R Ultra Low Power series. */ /* Only supported on Macronix MX25R Ultra Low Power series. */
if (jedec_id[0] != JEDEC_MACRONIX_ID || jedec_id[1] != JEDEC_MX25R_TYPE_ID) { if (jedec_id[0] != JEDEC_MACRONIX_ID || jedec_id[1] != JEDEC_MX25R_TYPE_ID) {
LOG_WRN("L/H switch not supported for device id: %02x %02x %02x", jedec_id[0], LOG_WRN("L/H switch not supported for device id: %02x %02x %02x",
jedec_id[1], jedec_id[2]); jedec_id[0], jedec_id[1], jedec_id[2]);
/* Do not return an error here because the flash still functions */ /* Do not return an error here because the flash still functions */
return 0; return 0;
} }
@ -695,11 +758,12 @@ static int mxicy_configure(const struct device *dev, const uint8_t *jedec_id)
} }
release_device(dev); release_device(dev);
}
return ret; return ret;
} }
#endif /* DT_INST_NODE_HAS_PROP(0, mxicy_mx25r_power_mode) */ #endif /* ANY_INST_HAS_MXICY_MX25R_POWER_MODE */
static int spi_nor_read(const struct device *dev, off_t addr, void *dest, static int spi_nor_read(const struct device *dev, off_t addr, void *dest,
size_t size) size_t size)
@ -892,25 +956,27 @@ static int spi_nor_write_protection_set(const struct device *dev,
bool write_protect) bool write_protect)
{ {
int ret; int ret;
const struct spi_nor_config *cfg = dev->config;
#if ANY_INST_HAS_WP_GPIOS #if ANY_INST_HAS_WP_GPIOS
if (DEV_CFG(dev)->wp && write_protect == false) { if (DEV_CFG(dev)->wp_gpios_exist && write_protect == false) {
gpio_pin_set_dt(DEV_CFG(dev)->wp, 0); gpio_pin_set_dt(&(DEV_CFG(dev)->wp), 0);
} }
#endif #endif
ARG_UNUSED(cfg);
ret = spi_nor_cmd_write(dev, (write_protect) ? ret = spi_nor_cmd_write(dev, (write_protect) ?
SPI_NOR_CMD_WRDI : SPI_NOR_CMD_WREN); SPI_NOR_CMD_WRDI : SPI_NOR_CMD_WREN);
if (IS_ENABLED(DT_INST_PROP(0, requires_ulbpr)) if (cfg->requires_ulbpr_exist
&& (ret == 0) && (ret == 0)
&& !write_protect) { && !write_protect) {
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_ULBPR); ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_ULBPR);
} }
#if ANY_INST_HAS_WP_GPIOS #if ANY_INST_HAS_WP_GPIOS
if (DEV_CFG(dev)->wp && write_protect == true) { if (DEV_CFG(dev)->wp_gpios_exist && write_protect == true) {
gpio_pin_set_dt(DEV_CFG(dev)->wp, 1); gpio_pin_set_dt(&(DEV_CFG(dev)->wp), 1);
} }
#endif #endif
@ -970,8 +1036,10 @@ static int spi_nor_read_jedec_id(const struct device *dev,
static int spi_nor_set_address_mode(const struct device *dev, static int spi_nor_set_address_mode(const struct device *dev,
uint8_t enter_4byte_addr) uint8_t enter_4byte_addr)
{ {
int ret = 0; const struct spi_nor_config *cfg = dev->config;
int ret = -ENOSYS;
if (cfg->enter_4byte_addr_exist) {
/* Do nothing if not provided (either no bits or all bits /* Do nothing if not provided (either no bits or all bits
* set). * set).
*/ */
@ -1006,6 +1074,7 @@ static int spi_nor_set_address_mode(const struct device *dev,
} }
release_device(dev); release_device(dev);
}
return ret; return ret;
} }
@ -1226,7 +1295,9 @@ static int spi_nor_configure(const struct device *dev)
return -ENODEV; return -ENODEV;
} }
#if DT_INST_NODE_HAS_PROP(0, reset_gpios) #if ANY_INST_HAS_RESET_GPIOS
if (cfg->reset_gpios_exist) {
if (!gpio_is_ready_dt(&cfg->reset)) { if (!gpio_is_ready_dt(&cfg->reset)) {
LOG_ERR("Reset pin not ready"); LOG_ERR("Reset pin not ready");
return -ENODEV; return -ENODEV;
@ -1239,6 +1310,7 @@ static int spi_nor_configure(const struct device *dev)
if (rc) { if (rc) {
return rc; return rc;
} }
}
#endif #endif
/* After a soft-reset the flash might be in DPD or busy writing/erasing. /* After a soft-reset the flash might be in DPD or busy writing/erasing.
@ -1339,10 +1411,12 @@ static int spi_nor_configure(const struct device *dev)
#endif /* CONFIG_FLASH_PAGE_LAYOUT */ #endif /* CONFIG_FLASH_PAGE_LAYOUT */
#endif /* CONFIG_SPI_NOR_SFDP_MINIMAL */ #endif /* CONFIG_SPI_NOR_SFDP_MINIMAL */
#if DT_INST_NODE_HAS_PROP(0, mxicy_mx25r_power_mode) #if ANY_INST_HAS_MXICY_MX25R_POWER_MODE
if (cfg->mxicy_mx25r_power_mode_exist) {
/* Do not fail init if setting configuration register fails */ /* Do not fail init if setting configuration register fails */
(void) mxicy_configure(dev, jedec_id); (void)mxicy_configure(dev, jedec_id);
#endif /* DT_INST_NODE_HAS_PROP(0, mxicy_mx25r_power_mode) */ }
#endif /* ANY_INST_HAS_MXICY_MX25R_POWER_MODE */
if (IS_ENABLED(CONFIG_SPI_NOR_IDLE_IN_DPD) if (IS_ENABLED(CONFIG_SPI_NOR_IDLE_IN_DPD)
&& (enter_dpd(dev) != 0)) { && (enter_dpd(dev) != 0)) {
@ -1415,24 +1489,24 @@ static int spi_nor_init(const struct device *dev)
} }
#if ANY_INST_HAS_WP_GPIOS #if ANY_INST_HAS_WP_GPIOS
if (DEV_CFG(dev)->wp) { if (DEV_CFG(dev)->wp_gpios_exist) {
if (!device_is_ready(DEV_CFG(dev)->wp->port)) { if (!device_is_ready(DEV_CFG(dev)->wp.port)) {
LOG_ERR("Write-protect pin not ready"); LOG_ERR("Write-protect pin not ready");
return -ENODEV; return -ENODEV;
} }
if (gpio_pin_configure_dt(DEV_CFG(dev)->wp, GPIO_OUTPUT_ACTIVE)) { if (gpio_pin_configure_dt(&(DEV_CFG(dev)->wp), GPIO_OUTPUT_ACTIVE)) {
LOG_ERR("Write-protect pin failed to set active"); LOG_ERR("Write-protect pin failed to set active");
return -ENODEV; return -ENODEV;
} }
} }
#endif /* ANY_INST_HAS_WP_GPIOS */ #endif /* ANY_INST_HAS_WP_GPIOS */
#if ANY_INST_HAS_HOLD_GPIOS #if ANY_INST_HAS_HOLD_GPIOS
if (DEV_CFG(dev)->hold) { if (DEV_CFG(dev)->hold_gpios_exist) {
if (!device_is_ready(DEV_CFG(dev)->hold->port)) { if (!device_is_ready(DEV_CFG(dev)->hold.port)) {
LOG_ERR("Hold pin not ready"); LOG_ERR("Hold pin not ready");
return -ENODEV; return -ENODEV;
} }
if (gpio_pin_configure_dt(DEV_CFG(dev)->hold, GPIO_OUTPUT_INACTIVE)) { if (gpio_pin_configure_dt(&(DEV_CFG(dev)->hold), GPIO_OUTPUT_INACTIVE)) {
LOG_ERR("Hold pin failed to set inactive"); LOG_ERR("Hold pin failed to set inactive");
return -ENODEV; return -ENODEV;
} }
@ -1489,117 +1563,144 @@ static const struct flash_driver_api spi_nor_api = {
#endif #endif
}; };
#ifndef CONFIG_SPI_NOR_SFDP_RUNTIME #define PAGE_LAYOUT_GEN(idx) \
/* We need to know the size and ID of the configuration data we're BUILD_ASSERT(DT_INST_NODE_HAS_PROP(idx, size), \
* using so we can disable the device we see at runtime if it isn't "jedec,spi-nor size required for non-runtime SFDP page layout"); \
* compatible with what we're taking from devicetree or minimal. enum { \
*/ INST_##idx##_BYTES = (DT_INST_PROP(idx, size) / 8) \
BUILD_ASSERT(DT_INST_NODE_HAS_PROP(0, jedec_id), }; \
"jedec,spi-nor jedec-id required for non-runtime SFDP"); BUILD_ASSERT(SPI_NOR_IS_SECTOR_ALIGNED(CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE), \
"SPI_NOR_FLASH_LAYOUT_PAGE_SIZE must be multiple of 4096"); \
#if defined(CONFIG_FLASH_PAGE_LAYOUT) enum { \
LAYOUT_PAGES_##idx##_COUNT = \
/* For devicetree or minimal page layout we need to know the size of (INST_##idx##_BYTES / CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE) \
* the device. We can't extract it from the raw BFP data, so require }; \
* it to be present in devicetree. BUILD_ASSERT((CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE * LAYOUT_PAGES_##idx##_COUNT) == \
*/ INST_##idx##_BYTES, \
BUILD_ASSERT(DT_INST_NODE_HAS_PROP(0, size),
"jedec,spi-nor size required for non-runtime SFDP page layout");
/* instance 0 size in bytes */
#define INST_0_BYTES (DT_INST_PROP(0, size) / 8)
BUILD_ASSERT(SPI_NOR_IS_SECTOR_ALIGNED(CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE),
"SPI_NOR_FLASH_LAYOUT_PAGE_SIZE must be multiple of 4096");
/* instance 0 page count */
#define LAYOUT_PAGES_COUNT (INST_0_BYTES / CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE)
BUILD_ASSERT((CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE * LAYOUT_PAGES_COUNT)
== INST_0_BYTES,
"SPI_NOR_FLASH_LAYOUT_PAGE_SIZE incompatible with flash size"); "SPI_NOR_FLASH_LAYOUT_PAGE_SIZE incompatible with flash size");
#endif /* CONFIG_FLASH_PAGE_LAYOUT */ #define SFDP_BFP_ATTR_GEN(idx) \
BUILD_ASSERT(DT_INST_NODE_HAS_PROP(idx, sfdp_bfp), \
"jedec,spi-nor sfdp-bfp required for devicetree SFDP"); \
static const __aligned(4) uint8_t bfp_##idx##_data[] = DT_INST_PROP(idx, sfdp_bfp);
#ifdef CONFIG_SPI_NOR_SFDP_DEVICETREE #define INST_ATTR_GEN(idx) \
BUILD_ASSERT(DT_INST_NODE_HAS_PROP(0, sfdp_bfp), BUILD_ASSERT(DT_INST_NODE_HAS_PROP(idx, jedec_id), \
"jedec,spi-nor sfdp-bfp required for devicetree SFDP"); "jedec,spi-nor jedec-id required for non-runtime SFDP"); \
IF_ENABLED(CONFIG_FLASH_PAGE_LAYOUT, (PAGE_LAYOUT_GEN(idx))) \
IF_ENABLED(CONFIG_SPI_NOR_SFDP_DEVICETREE, (SFDP_BFP_ATTR_GEN(idx)))
static const __aligned(4) uint8_t bfp_data_0[] = DT_INST_PROP(0, sfdp_bfp); #define ATTRIBUTES_DEFINE(idx) COND_CODE_1(CONFIG_SPI_NOR_SFDP_RUNTIME, EMPTY(), \
#endif /* CONFIG_SPI_NOR_SFDP_DEVICETREE */ (INST_ATTR_GEN(idx)))
#endif /* CONFIG_SPI_NOR_SFDP_RUNTIME */ #define DEFINE_PAGE_LAYOUT(idx) \
IF_ENABLED(CONFIG_FLASH_PAGE_LAYOUT, \
(.layout = { \
.pages_count = LAYOUT_PAGES_##idx##_COUNT, \
.pages_size = CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE, \
},))
#if DT_INST_NODE_HAS_PROP(0, has_lock) #define INST_HAS_LOCK(idx) DT_INST_NODE_HAS_PROP(idx, has_lock)
/* Currently we only know of devices where the BP bits are present in
* the first byte of the status register. Complain if that changes.
*/
BUILD_ASSERT(DT_INST_PROP(0, has_lock) == (DT_INST_PROP(0, has_lock) & 0xFF),
"Need support for lock clear beyond SR1");
#endif
#define INST_HAS_WP_GPIO(idx) DT_INST_NODE_HAS_PROP(idx, wp_gpios) #define INST_HAS_WP_GPIO(idx) DT_INST_NODE_HAS_PROP(idx, wp_gpios)
#define INST_WP_GPIO_SPEC(idx) \
IF_ENABLED(INST_HAS_WP_GPIO(idx), (static const struct gpio_dt_spec wp_##idx = \
GPIO_DT_SPEC_INST_GET(idx, wp_gpios);))
#define INST_HAS_HOLD_GPIO(idx) DT_INST_NODE_HAS_PROP(idx, hold_gpios) #define INST_HAS_HOLD_GPIO(idx) DT_INST_NODE_HAS_PROP(idx, hold_gpios)
#define INST_HOLD_GPIO_SPEC(idx) \ #define LOCK_DEFINE(idx) \
IF_ENABLED(INST_HAS_HOLD_GPIO(idx), (static const struct gpio_dt_spec hold_##idx = \ IF_ENABLED(INST_HAS_LOCK(idx), (BUILD_ASSERT(DT_INST_PROP(idx, has_lock) == \
GPIO_DT_SPEC_INST_GET(idx, hold_gpios);)) (DT_INST_PROP(idx, has_lock) & 0xFF), \
"Need support for lock clear beyond SR1");))
INST_WP_GPIO_SPEC(0) #define INST_HAS_ENTER_4BYTE_ADDR(idx) DT_INST_NODE_HAS_PROP(idx, enter_4byte_addr)
INST_HOLD_GPIO_SPEC(0)
static const struct spi_nor_config spi_nor_config_0 = { #define CONFIGURE_4BYTE_ADDR(idx) \
.spi = SPI_DT_SPEC_INST_GET(0, SPI_WORD_SET(8), IF_ENABLED(INST_HAS_ENTER_4BYTE_ADDR(idx), \
CONFIG_SPI_NOR_CS_WAIT_DELAY), (.enter_4byte_addr = DT_INST_PROP(idx, enter_4byte_addr),))
#if DT_INST_NODE_HAS_PROP(0, reset_gpios)
.reset = GPIO_DT_SPEC_INST_GET(0, reset_gpios), #define INIT_T_ENTER_DPD(idx) \
COND_CODE_1(DT_INST_NODE_HAS_PROP(idx, t_enter_dpd), \
(.t_enter_dpd = \
DIV_ROUND_UP(DT_INST_PROP(idx, t_enter_dpd), NSEC_PER_MSEC)),\
(.t_enter_dpd = 0))
#if ANY_INST_HAS_T_EXIT_DPD
#define INIT_T_EXIT_DPD(idx) \
COND_CODE_1( \
DT_INST_NODE_HAS_PROP(idx, t_exit_dpd), \
(.t_exit_dpd = DIV_ROUND_UP(DT_INST_PROP(idx, t_exit_dpd), NSEC_PER_MSEC)),\
(.t_exit_dpd = 0))
#endif #endif
#if !defined(CONFIG_SPI_NOR_SFDP_RUNTIME) #define INIT_WP_GPIOS(idx) \
COND_CODE_1(DT_INST_NODE_HAS_PROP(idx, wp_gpios), \
(.wp = GPIO_DT_SPEC_INST_GET(idx, wp_gpios)), \
(.wp = {0}))
#if defined(CONFIG_FLASH_PAGE_LAYOUT) #define INIT_HOLD_GPIOS(idx) \
.layout = { COND_CODE_1(DT_INST_NODE_HAS_PROP(idx, hold_gpios), \
.pages_count = LAYOUT_PAGES_COUNT, (.hold = GPIO_DT_SPEC_INST_GET(idx, hold_gpios)), \
.pages_size = CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE, (.hold = {0},))
},
#undef LAYOUT_PAGES_COUNT
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
.flash_size = DT_INST_PROP(0, size) / 8, #define INIT_WAKEUP_SEQ_PARAMS(idx) \
.jedec_id = DT_INST_PROP(0, jedec_id), COND_CODE_1(DT_INST_NODE_HAS_PROP(idx, dpd_wakeup_sequence), \
(.t_dpdd_ms = DIV_ROUND_UP( \
DT_INST_PROP_BY_IDX(idx, dpd_wakeup_sequence, 0), NSEC_PER_MSEC),\
.t_crdp_ms = DIV_ROUND_UP( \
DT_INST_PROP_BY_IDX(idx, dpd_wakeup_sequence, 1), NSEC_PER_MSEC),\
.t_rdp_ms = DIV_ROUND_UP( \
DT_INST_PROP_BY_IDX(idx, dpd_wakeup_sequence, 2), NSEC_PER_MSEC)),\
(.t_dpdd_ms = 0, .t_crdp_ms = 0, .t_rdp_ms = 0))
#if DT_INST_NODE_HAS_PROP(0, has_lock) #define INIT_MXICY_MX25R_POWER_MODE(idx) \
.has_lock = DT_INST_PROP(0, has_lock), COND_CODE_1(DT_INST_NODE_HAS_PROP(idx, mxicy_mx25r_power_mode), \
#endif (.mxicy_mx25r_power_mode = DT_INST_ENUM_IDX(idx, mxicy_mx25r_power_mode)),\
#if defined(CONFIG_SPI_NOR_SFDP_MINIMAL) \ (.mxicy_mx25r_power_mode = 0))
&& DT_INST_NODE_HAS_PROP(0, enter_4byte_addr)
.enter_4byte_addr = DT_INST_PROP(0, enter_4byte_addr),
#endif
#ifdef CONFIG_SPI_NOR_SFDP_DEVICETREE
.bfp_len = sizeof(bfp_data_0) / 4,
.bfp = (const struct jesd216_bfp *)bfp_data_0,
#endif /* CONFIG_SPI_NOR_SFDP_DEVICETREE */
#endif /* CONFIG_SPI_NOR_SFDP_RUNTIME */ #define INIT_RESET_GPIOS(idx) \
COND_CODE_1(DT_INST_NODE_HAS_PROP(idx, reset_gpios), \
(.reset = GPIO_DT_SPEC_INST_GET(idx, reset_gpios)), \
(.reset = {0}))
#if DT_INST_NODE_HAS_PROP(0, wp_gpios) #define INST_CONFIG_STRUCT_GEN(idx) \
.wp = &wp_0, DEFINE_PAGE_LAYOUT(idx) \
#endif .flash_size = DT_INST_PROP(idx, size) / 8, \
.jedec_id = DT_INST_PROP(idx, jedec_id), \
.dpd_exist = DT_INST_PROP(idx, has_dpd), \
.dpd_wakeup_sequence_exist = DT_INST_NODE_HAS_PROP(idx, dpd_wakeup_sequence), \
.mxicy_mx25r_power_mode_exist = DT_INST_NODE_HAS_PROP(idx, mxicy_mx25r_power_mode), \
.enter_4byte_addr_exist = DT_INST_NODE_HAS_PROP(idx, enter_4byte_addr), \
.reset_gpios_exist = DT_INST_NODE_HAS_PROP(idx, reset_gpios), \
.requires_ulbpr_exist = DT_INST_PROP(idx, requires_ulbpr), \
.wp_gpios_exist = DT_INST_NODE_HAS_PROP(idx, wp_gpios), \
.hold_gpios_exist = DT_INST_NODE_HAS_PROP(idx, hold_gpios), \
IF_ENABLED(INST_HAS_LOCK(idx), (.has_lock = DT_INST_PROP(idx, has_lock),)) \
IF_ENABLED(CONFIG_SPI_NOR_SFDP_MINIMAL, (CONFIGURE_4BYTE_ADDR(idx))) \
IF_ENABLED(CONFIG_SPI_NOR_SFDP_DEVICETREE, \
(.bfp_len = sizeof(bfp_##idx##_data) / 4, \
.bfp = (const struct jesd216_bfp *)bfp_##idx##_data,)) \
IF_ENABLED(ANY_INST_HAS_DPD, (INIT_T_ENTER_DPD(idx),)) \
IF_ENABLED(UTIL_AND(ANY_INST_HAS_DPD, ANY_INST_HAS_T_EXIT_DPD), (INIT_T_EXIT_DPD(idx),))\
IF_ENABLED(ANY_INST_HAS_DPD_WAKEUP_SEQUENCE, (INIT_WAKEUP_SEQ_PARAMS(idx),)) \
IF_ENABLED(ANY_INST_HAS_MXICY_MX25R_POWER_MODE, (INIT_MXICY_MX25R_POWER_MODE(idx),)) \
IF_ENABLED(ANY_INST_HAS_RESET_GPIOS, (INIT_RESET_GPIOS(idx),)) \
IF_ENABLED(ANY_INST_HAS_WP_GPIOS, (INIT_WP_GPIOS(idx),))
#if DT_INST_NODE_HAS_PROP(0, hold_gpios) #define GENERATE_CONFIG_STRUCT(idx) \
.hold = &hold_0, static const struct spi_nor_config spi_nor_##idx##_config = { \
#endif .spi = SPI_DT_SPEC_INST_GET(idx, SPI_WORD_SET(8), CONFIG_SPI_NOR_CS_WAIT_DELAY),\
}; COND_CODE_1(CONFIG_SPI_NOR_SFDP_RUNTIME, EMPTY(), (INST_CONFIG_STRUCT_GEN(idx)))};
static struct spi_nor_data spi_nor_data_0; #define ASSIGN_PM(idx) \
PM_DEVICE_DT_INST_DEFINE(idx, spi_nor_pm_control);
PM_DEVICE_DT_INST_DEFINE(0, spi_nor_pm_control); #define SPI_NOR_INST(idx) \
DEVICE_DT_INST_DEFINE(0, &spi_nor_init, PM_DEVICE_DT_INST_GET(0), ASSIGN_PM(idx) \
&spi_nor_data_0, &spi_nor_config_0, ATTRIBUTES_DEFINE(idx) \
POST_KERNEL, CONFIG_SPI_NOR_INIT_PRIORITY, LOCK_DEFINE(idx) \
&spi_nor_api); GENERATE_CONFIG_STRUCT(idx) \
static struct spi_nor_data spi_nor_##idx##_data; \
DEVICE_DT_INST_DEFINE(idx, &spi_nor_init, PM_DEVICE_DT_INST_GET(idx), \
&spi_nor_##idx##_data, &spi_nor_##idx##_config, \
POST_KERNEL, CONFIG_SPI_NOR_INIT_PRIORITY, &spi_nor_api);
DT_INST_FOREACH_STATUS_OKAY(SPI_NOR_INST)

View file

@ -113,4 +113,6 @@
#define SPI_NOR_IS_32K_ALIGNED(_ofs) SPI_NOR_IS_ALIGNED(_ofs, 15) #define SPI_NOR_IS_32K_ALIGNED(_ofs) SPI_NOR_IS_ALIGNED(_ofs, 15)
#define SPI_NOR_IS_64K_ALIGNED(_ofs) SPI_NOR_IS_ALIGNED(_ofs, 16) #define SPI_NOR_IS_64K_ALIGNED(_ofs) SPI_NOR_IS_ALIGNED(_ofs, 16)
#define CMD_RDCR 0x15 /* Read the configuration register. */
#endif /*__SPI_NOR_H__*/ #endif /*__SPI_NOR_H__*/