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:
parent
ca36feeca8
commit
adedf14c42
|
@ -48,36 +48,43 @@ LOG_MODULE_REGISTER(spi_nor, CONFIG_FLASH_LOG_LEVEL);
|
|||
|
||||
#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_WP_GPIOS DT_INST_FOREACH_STATUS_OKAY(_INST_HAS_WP_OR) 0
|
||||
#define ANY_INST_HAS_TRUE_(idx, bool_prop) \
|
||||
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_HOLD_GPIOS DT_INST_FOREACH_STATUS_OKAY(_INST_HAS_HOLD_OR) 0
|
||||
#define ANY_INST_HAS_TRUE(bool_prop) \
|
||||
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)
|
||||
|
||||
/* 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. */
|
||||
struct spi_nor_config {
|
||||
/* Devicetree SPI configuration */
|
||||
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;
|
||||
#endif
|
||||
|
||||
|
@ -119,12 +126,40 @@ struct spi_nor_config {
|
|||
|
||||
#if ANY_INST_HAS_WP_GPIOS
|
||||
/* The write-protect GPIO (wp-gpios) */
|
||||
const struct gpio_dt_spec *wp;
|
||||
const struct gpio_dt_spec wp;
|
||||
#endif
|
||||
|
||||
#if ANY_INST_HAS_HOLD_GPIOS
|
||||
/* The hold GPIO (hold-gpios) */
|
||||
const struct gpio_dt_spec *hold;
|
||||
const struct gpio_dt_spec hold;
|
||||
#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 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
|
||||
* 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. */
|
||||
static inline void record_entered_dpd(const struct device *const dev)
|
||||
{
|
||||
#if DT_INST_NODE_HAS_PROP(0, has_dpd)
|
||||
struct spi_nor_data *const driver_data = dev->data;
|
||||
#if ANY_INST_HAS_DPD
|
||||
const struct spi_nor_config *const driver_config = dev->config;
|
||||
|
||||
driver_data->ts_enter_dpd = k_uptime_get_32();
|
||||
if (driver_config->dpd_exist) {
|
||||
struct spi_nor_data *const driver_data = dev->data;
|
||||
|
||||
driver_data->ts_enter_dpd = k_uptime_get_32();
|
||||
}
|
||||
#else
|
||||
ARG_UNUSED(dev);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -261,31 +302,37 @@ static inline void record_entered_dpd(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)
|
||||
struct spi_nor_data *const driver_data = dev->data;
|
||||
int32_t since = (int32_t)(k_uptime_get_32() - driver_data->ts_enter_dpd);
|
||||
#if ANY_INST_HAS_DPD
|
||||
const struct spi_nor_config *const driver_config = dev->config;
|
||||
|
||||
/* If the time is negative the 32-bit counter has wrapped,
|
||||
* which is certainly long enough no further delay is
|
||||
* required. Otherwise we have to check whether it's been
|
||||
* long enough taking into account necessary delays for
|
||||
* entering and exiting DPD.
|
||||
*/
|
||||
if (since >= 0) {
|
||||
/* Subtract time required for DPD to be reached */
|
||||
since -= T_DP_MS;
|
||||
if (driver_config->dpd_exist) {
|
||||
struct spi_nor_data *const driver_data = dev->data;
|
||||
int32_t since = (int32_t)(k_uptime_get_32() - driver_data->ts_enter_dpd);
|
||||
|
||||
/* Subtract time required in DPD before exit */
|
||||
since -= T_DPDD_MS;
|
||||
|
||||
/* If the adjusted time is negative we have to wait
|
||||
* until it reaches zero before we can proceed.
|
||||
/* If the time is negative the 32-bit counter has wrapped,
|
||||
* which is certainly long enough no further delay is
|
||||
* required. Otherwise we have to check whether it's been
|
||||
* long enough taking into account necessary delays for
|
||||
* entering and exiting DPD.
|
||||
*/
|
||||
if (since < 0) {
|
||||
k_sleep(K_MSEC((uint32_t)-since));
|
||||
if (since >= 0) {
|
||||
/* Subtract time required for DPD to be reached */
|
||||
since -= driver_config->t_enter_dpd;
|
||||
|
||||
/* Subtract time required in DPD before exit */
|
||||
since -= driver_config->t_dpdd_ms;
|
||||
|
||||
/* If the adjusted time is negative we have to wait
|
||||
* until it reaches zero before we can proceed.
|
||||
*/
|
||||
if (since < 0) {
|
||||
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.
|
||||
|
@ -455,8 +502,10 @@ static int read_sfdp(const struct device *const dev,
|
|||
static int enter_dpd(const struct device *const dev)
|
||||
{
|
||||
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);
|
||||
if (ret == 0) {
|
||||
record_entered_dpd(dev);
|
||||
|
@ -468,29 +517,34 @@ static int enter_dpd(const struct device *const dev)
|
|||
static int exit_dpd(const struct device *const dev)
|
||||
{
|
||||
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);
|
||||
|
||||
#if DT_INST_NODE_HAS_PROP(0, dpd_wakeup_sequence)
|
||||
/* Assert CSn and wait for tCRDP.
|
||||
*
|
||||
* Unfortunately the SPI API doesn't allow us to
|
||||
* control CSn so fake it by writing a known-supported
|
||||
* single-byte command, hoping that'll hold the assert
|
||||
* long enough. This is highly likely, since the
|
||||
* duration is usually less than two SPI clock cycles.
|
||||
*/
|
||||
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_RDID);
|
||||
#if ANY_INST_HAS_DPD_WAKEUP_SEQUENCE
|
||||
if (cfg->dpd_wakeup_sequence_exist) {
|
||||
/* Assert CSn and wait for tCRDP.
|
||||
*
|
||||
* Unfortunately the SPI API doesn't allow us to
|
||||
* control CSn so fake it by writing a known-supported
|
||||
* single-byte command, hoping that'll hold the assert
|
||||
* long enough. This is highly likely, since the
|
||||
* duration is usually less than two SPI clock cycles.
|
||||
*/
|
||||
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_RDID);
|
||||
|
||||
/* Deassert CSn and wait for tRDP */
|
||||
k_sleep(K_MSEC(T_RDP_MS));
|
||||
#else /* DPD_WAKEUP_SEQUENCE */
|
||||
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_RDPD);
|
||||
/* Deassert CSn and wait for tRDP */
|
||||
k_sleep(K_MSEC(cfg->t_rdp_ms));
|
||||
} else {
|
||||
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_RDPD);
|
||||
|
||||
if (ret == 0) {
|
||||
#if DT_INST_NODE_HAS_PROP(0, t_exit_dpd)
|
||||
k_sleep(K_MSEC(T_RES1_MS));
|
||||
#if ANY_INST_HAS_T_EXIT_DPD
|
||||
if (ret == 0) {
|
||||
if (cfg->dpd_exist) {
|
||||
k_sleep(K_MSEC(cfg->t_exit_dpd));
|
||||
}
|
||||
}
|
||||
#endif /* T_EXIT_DPD */
|
||||
}
|
||||
#endif /* DPD_WAKEUP_SEQUENCE */
|
||||
|
@ -581,7 +635,7 @@ static int spi_nor_wrsr(const struct device *dev,
|
|||
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.
|
||||
|
@ -595,12 +649,15 @@ static int spi_nor_wrsr(const struct device *dev,
|
|||
*/
|
||||
static int mxicy_rdcr(const struct device *dev)
|
||||
{
|
||||
uint16_t cr;
|
||||
enum { CMD_RDCR = 0x15 };
|
||||
int ret = spi_nor_cmd_read(dev, CMD_RDCR, &cr, sizeof(cr));
|
||||
const struct spi_nor_config *cfg = dev->config;
|
||||
uint16_t cr = -ENOSYS;
|
||||
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
if (cfg->mxicy_mx25r_power_mode_exist) {
|
||||
int ret = spi_nor_cmd_read(dev, CMD_RDCR, &cr, sizeof(cr));
|
||||
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return cr;
|
||||
|
@ -620,30 +677,35 @@ static int mxicy_rdcr(const struct device *dev)
|
|||
static int mxicy_wrcr(const struct device *dev,
|
||||
uint16_t cr)
|
||||
{
|
||||
const struct spi_nor_config *cfg = dev->config;
|
||||
int ret = -ENOSYS;
|
||||
/* The configuration register bytes on the Macronix MX25R devices are
|
||||
* written using the Write Status Register command where the configuration
|
||||
* register bytes are written as two extra bytes after the status register.
|
||||
* First read out the current status register to preserve the value.
|
||||
*/
|
||||
int sr = spi_nor_rdsr(dev);
|
||||
|
||||
if (sr < 0) {
|
||||
LOG_ERR("Read status register failed: %d", sr);
|
||||
return sr;
|
||||
}
|
||||
if (cfg->mxicy_mx25r_power_mode_exist) {
|
||||
int sr = spi_nor_rdsr(dev);
|
||||
|
||||
int ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_WREN);
|
||||
if (sr < 0) {
|
||||
LOG_ERR("Read status register failed: %d", sr);
|
||||
return sr;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
uint8_t data[] = {
|
||||
sr,
|
||||
cr & 0xFF, /* Configuration register 1 */
|
||||
cr >> 8 /* Configuration register 2 */
|
||||
};
|
||||
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_WREN);
|
||||
|
||||
ret = spi_nor_access(dev, SPI_NOR_CMD_WRSR, NOR_ACCESS_WRITE, 0, data,
|
||||
sizeof(data));
|
||||
spi_nor_wait_until_ready(dev, WAIT_READY_REGISTER);
|
||||
if (ret == 0) {
|
||||
uint8_t data[] = {
|
||||
sr,
|
||||
cr & 0xFF, /* Configuration register 1 */
|
||||
cr >> 8 /* Configuration register 2 */
|
||||
};
|
||||
|
||||
ret = spi_nor_access(dev, SPI_NOR_CMD_WRSR, NOR_ACCESS_WRITE, 0,
|
||||
data, sizeof(data));
|
||||
spi_nor_wait_until_ready(dev, WAIT_READY_REGISTER);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
@ -651,55 +713,57 @@ static int mxicy_wrcr(const struct device *dev,
|
|||
|
||||
static int mxicy_configure(const struct device *dev, const uint8_t *jedec_id)
|
||||
{
|
||||
/* Low-power/high perf mode is second bit in configuration register 2 */
|
||||
enum { LH_SWITCH_BIT = 9 };
|
||||
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:
|
||||
* 0: Ultra low power
|
||||
* 1: High performance mode
|
||||
*/
|
||||
const bool use_high_perf = DT_INST_ENUM_IDX(0, mxicy_mx25r_power_mode);
|
||||
const struct spi_nor_config *cfg = dev->config;
|
||||
int ret = -ENOSYS;
|
||||
|
||||
/* Only supported on Macronix MX25R Ultra Low Power series. */
|
||||
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],
|
||||
jedec_id[1], jedec_id[2]);
|
||||
/* Do not return an error here because the flash still functions */
|
||||
return 0;
|
||||
}
|
||||
if (cfg->mxicy_mx25r_power_mode_exist) {
|
||||
/* Low-power/high perf mode is second bit in configuration register 2 */
|
||||
int current_cr, new_cr;
|
||||
/* lh_switch enum index:
|
||||
* 0: Ultra low power
|
||||
* 1: High performance mode
|
||||
*/
|
||||
const bool use_high_perf = cfg->mxicy_mx25r_power_mode;
|
||||
|
||||
acquire_device(dev);
|
||||
/* Only supported on Macronix MX25R Ultra Low Power series. */
|
||||
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], jedec_id[1], jedec_id[2]);
|
||||
/* Do not return an error here because the flash still functions */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Read current configuration register */
|
||||
acquire_device(dev);
|
||||
|
||||
/* Read current configuration register */
|
||||
|
||||
ret = mxicy_rdcr(dev);
|
||||
if (ret < 0) {
|
||||
release_device(dev);
|
||||
return ret;
|
||||
}
|
||||
current_cr = ret;
|
||||
|
||||
LOG_DBG("Use high performance mode? %d", use_high_perf);
|
||||
new_cr = current_cr;
|
||||
WRITE_BIT(new_cr, LH_SWITCH_BIT, use_high_perf);
|
||||
if (new_cr != current_cr) {
|
||||
ret = mxicy_wrcr(dev, new_cr);
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Enable high performace mode failed: %d", ret);
|
||||
}
|
||||
|
||||
ret = mxicy_rdcr(dev);
|
||||
if (ret < 0) {
|
||||
release_device(dev);
|
||||
return ret;
|
||||
}
|
||||
current_cr = ret;
|
||||
|
||||
LOG_DBG("Use high performance mode? %d", use_high_perf);
|
||||
new_cr = current_cr;
|
||||
WRITE_BIT(new_cr, LH_SWITCH_BIT, use_high_perf);
|
||||
if (new_cr != current_cr) {
|
||||
ret = mxicy_wrcr(dev, new_cr);
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Enable high performace mode failed: %d", ret);
|
||||
}
|
||||
|
||||
release_device(dev);
|
||||
|
||||
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,
|
||||
size_t size)
|
||||
|
@ -892,25 +956,27 @@ static int spi_nor_write_protection_set(const struct device *dev,
|
|||
bool write_protect)
|
||||
{
|
||||
int ret;
|
||||
const struct spi_nor_config *cfg = dev->config;
|
||||
|
||||
#if ANY_INST_HAS_WP_GPIOS
|
||||
if (DEV_CFG(dev)->wp && write_protect == false) {
|
||||
gpio_pin_set_dt(DEV_CFG(dev)->wp, 0);
|
||||
if (DEV_CFG(dev)->wp_gpios_exist && write_protect == false) {
|
||||
gpio_pin_set_dt(&(DEV_CFG(dev)->wp), 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
ARG_UNUSED(cfg);
|
||||
ret = spi_nor_cmd_write(dev, (write_protect) ?
|
||||
SPI_NOR_CMD_WRDI : SPI_NOR_CMD_WREN);
|
||||
|
||||
if (IS_ENABLED(DT_INST_PROP(0, requires_ulbpr))
|
||||
if (cfg->requires_ulbpr_exist
|
||||
&& (ret == 0)
|
||||
&& !write_protect) {
|
||||
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_ULBPR);
|
||||
}
|
||||
|
||||
#if ANY_INST_HAS_WP_GPIOS
|
||||
if (DEV_CFG(dev)->wp && write_protect == true) {
|
||||
gpio_pin_set_dt(DEV_CFG(dev)->wp, 1);
|
||||
if (DEV_CFG(dev)->wp_gpios_exist && write_protect == true) {
|
||||
gpio_pin_set_dt(&(DEV_CFG(dev)->wp), 1);
|
||||
}
|
||||
#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,
|
||||
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
|
||||
* set).
|
||||
*/
|
||||
|
@ -1006,6 +1074,7 @@ static int spi_nor_set_address_mode(const struct device *dev,
|
|||
}
|
||||
|
||||
release_device(dev);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -1226,18 +1295,21 @@ static int spi_nor_configure(const struct device *dev)
|
|||
return -ENODEV;
|
||||
}
|
||||
|
||||
#if DT_INST_NODE_HAS_PROP(0, reset_gpios)
|
||||
if (!gpio_is_ready_dt(&cfg->reset)) {
|
||||
LOG_ERR("Reset pin not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
if (gpio_pin_configure_dt(&cfg->reset, GPIO_OUTPUT_ACTIVE)) {
|
||||
LOG_ERR("Couldn't configure reset pin");
|
||||
return -ENODEV;
|
||||
}
|
||||
rc = gpio_pin_set_dt(&cfg->reset, 0);
|
||||
if (rc) {
|
||||
return rc;
|
||||
#if ANY_INST_HAS_RESET_GPIOS
|
||||
|
||||
if (cfg->reset_gpios_exist) {
|
||||
if (!gpio_is_ready_dt(&cfg->reset)) {
|
||||
LOG_ERR("Reset pin not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
if (gpio_pin_configure_dt(&cfg->reset, GPIO_OUTPUT_ACTIVE)) {
|
||||
LOG_ERR("Couldn't configure reset pin");
|
||||
return -ENODEV;
|
||||
}
|
||||
rc = gpio_pin_set_dt(&cfg->reset, 0);
|
||||
if (rc) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -1339,10 +1411,12 @@ static int spi_nor_configure(const struct device *dev)
|
|||
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
|
||||
#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 */
|
||||
(void) mxicy_configure(dev, jedec_id);
|
||||
#endif /* DT_INST_NODE_HAS_PROP(0, mxicy_mx25r_power_mode) */
|
||||
(void)mxicy_configure(dev, jedec_id);
|
||||
}
|
||||
#endif /* ANY_INST_HAS_MXICY_MX25R_POWER_MODE */
|
||||
|
||||
if (IS_ENABLED(CONFIG_SPI_NOR_IDLE_IN_DPD)
|
||||
&& (enter_dpd(dev) != 0)) {
|
||||
|
@ -1415,24 +1489,24 @@ static int spi_nor_init(const struct device *dev)
|
|||
}
|
||||
|
||||
#if ANY_INST_HAS_WP_GPIOS
|
||||
if (DEV_CFG(dev)->wp) {
|
||||
if (!device_is_ready(DEV_CFG(dev)->wp->port)) {
|
||||
if (DEV_CFG(dev)->wp_gpios_exist) {
|
||||
if (!device_is_ready(DEV_CFG(dev)->wp.port)) {
|
||||
LOG_ERR("Write-protect pin not ready");
|
||||
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");
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
#endif /* ANY_INST_HAS_WP_GPIOS */
|
||||
#if ANY_INST_HAS_HOLD_GPIOS
|
||||
if (DEV_CFG(dev)->hold) {
|
||||
if (!device_is_ready(DEV_CFG(dev)->hold->port)) {
|
||||
if (DEV_CFG(dev)->hold_gpios_exist) {
|
||||
if (!device_is_ready(DEV_CFG(dev)->hold.port)) {
|
||||
LOG_ERR("Hold pin not ready");
|
||||
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");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
@ -1489,117 +1563,144 @@ static const struct flash_driver_api spi_nor_api = {
|
|||
#endif
|
||||
};
|
||||
|
||||
#ifndef CONFIG_SPI_NOR_SFDP_RUNTIME
|
||||
/* We need to know the size and ID of the configuration data we're
|
||||
* using so we can disable the device we see at runtime if it isn't
|
||||
* compatible with what we're taking from devicetree or minimal.
|
||||
*/
|
||||
BUILD_ASSERT(DT_INST_NODE_HAS_PROP(0, jedec_id),
|
||||
"jedec,spi-nor jedec-id required for non-runtime SFDP");
|
||||
|
||||
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
||||
|
||||
/* For devicetree or minimal page layout we need to know the size of
|
||||
* the device. We can't extract it from the raw BFP data, so require
|
||||
* it to be present in devicetree.
|
||||
*/
|
||||
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,
|
||||
#define PAGE_LAYOUT_GEN(idx) \
|
||||
BUILD_ASSERT(DT_INST_NODE_HAS_PROP(idx, size), \
|
||||
"jedec,spi-nor size required for non-runtime SFDP page layout"); \
|
||||
enum { \
|
||||
INST_##idx##_BYTES = (DT_INST_PROP(idx, 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"); \
|
||||
enum { \
|
||||
LAYOUT_PAGES_##idx##_COUNT = \
|
||||
(INST_##idx##_BYTES / CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE) \
|
||||
}; \
|
||||
BUILD_ASSERT((CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE * LAYOUT_PAGES_##idx##_COUNT) == \
|
||||
INST_##idx##_BYTES, \
|
||||
"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
|
||||
BUILD_ASSERT(DT_INST_NODE_HAS_PROP(0, sfdp_bfp),
|
||||
"jedec,spi-nor sfdp-bfp required for devicetree SFDP");
|
||||
#define INST_ATTR_GEN(idx) \
|
||||
BUILD_ASSERT(DT_INST_NODE_HAS_PROP(idx, jedec_id), \
|
||||
"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);
|
||||
#endif /* CONFIG_SPI_NOR_SFDP_DEVICETREE */
|
||||
#define ATTRIBUTES_DEFINE(idx) COND_CODE_1(CONFIG_SPI_NOR_SFDP_RUNTIME, EMPTY(), \
|
||||
(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)
|
||||
/* 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_LOCK(idx) DT_INST_NODE_HAS_PROP(idx, has_lock)
|
||||
|
||||
#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_HOLD_GPIO_SPEC(idx) \
|
||||
IF_ENABLED(INST_HAS_HOLD_GPIO(idx), (static const struct gpio_dt_spec hold_##idx = \
|
||||
GPIO_DT_SPEC_INST_GET(idx, hold_gpios);))
|
||||
#define LOCK_DEFINE(idx) \
|
||||
IF_ENABLED(INST_HAS_LOCK(idx), (BUILD_ASSERT(DT_INST_PROP(idx, has_lock) == \
|
||||
(DT_INST_PROP(idx, has_lock) & 0xFF), \
|
||||
"Need support for lock clear beyond SR1");))
|
||||
|
||||
INST_WP_GPIO_SPEC(0)
|
||||
INST_HOLD_GPIO_SPEC(0)
|
||||
#define INST_HAS_ENTER_4BYTE_ADDR(idx) DT_INST_NODE_HAS_PROP(idx, enter_4byte_addr)
|
||||
|
||||
static const struct spi_nor_config spi_nor_config_0 = {
|
||||
.spi = SPI_DT_SPEC_INST_GET(0, SPI_WORD_SET(8),
|
||||
CONFIG_SPI_NOR_CS_WAIT_DELAY),
|
||||
#if DT_INST_NODE_HAS_PROP(0, reset_gpios)
|
||||
.reset = GPIO_DT_SPEC_INST_GET(0, reset_gpios),
|
||||
#define CONFIGURE_4BYTE_ADDR(idx) \
|
||||
IF_ENABLED(INST_HAS_ENTER_4BYTE_ADDR(idx), \
|
||||
(.enter_4byte_addr = DT_INST_PROP(idx, enter_4byte_addr),))
|
||||
|
||||
#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
|
||||
|
||||
#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)
|
||||
.layout = {
|
||||
.pages_count = LAYOUT_PAGES_COUNT,
|
||||
.pages_size = CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE,
|
||||
},
|
||||
#undef LAYOUT_PAGES_COUNT
|
||||
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
|
||||
#define INIT_HOLD_GPIOS(idx) \
|
||||
COND_CODE_1(DT_INST_NODE_HAS_PROP(idx, hold_gpios), \
|
||||
(.hold = GPIO_DT_SPEC_INST_GET(idx, hold_gpios)), \
|
||||
(.hold = {0},))
|
||||
|
||||
.flash_size = DT_INST_PROP(0, size) / 8,
|
||||
.jedec_id = DT_INST_PROP(0, jedec_id),
|
||||
#define INIT_WAKEUP_SEQ_PARAMS(idx) \
|
||||
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)
|
||||
.has_lock = DT_INST_PROP(0, has_lock),
|
||||
#endif
|
||||
#if defined(CONFIG_SPI_NOR_SFDP_MINIMAL) \
|
||||
&& 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 */
|
||||
#define INIT_MXICY_MX25R_POWER_MODE(idx) \
|
||||
COND_CODE_1(DT_INST_NODE_HAS_PROP(idx, mxicy_mx25r_power_mode), \
|
||||
(.mxicy_mx25r_power_mode = DT_INST_ENUM_IDX(idx, mxicy_mx25r_power_mode)),\
|
||||
(.mxicy_mx25r_power_mode = 0))
|
||||
|
||||
#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)
|
||||
.wp = &wp_0,
|
||||
#endif
|
||||
#define INST_CONFIG_STRUCT_GEN(idx) \
|
||||
DEFINE_PAGE_LAYOUT(idx) \
|
||||
.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)
|
||||
.hold = &hold_0,
|
||||
#endif
|
||||
};
|
||||
#define GENERATE_CONFIG_STRUCT(idx) \
|
||||
static const struct spi_nor_config spi_nor_##idx##_config = { \
|
||||
.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);
|
||||
DEVICE_DT_INST_DEFINE(0, &spi_nor_init, PM_DEVICE_DT_INST_GET(0),
|
||||
&spi_nor_data_0, &spi_nor_config_0,
|
||||
POST_KERNEL, CONFIG_SPI_NOR_INIT_PRIORITY,
|
||||
&spi_nor_api);
|
||||
#define SPI_NOR_INST(idx) \
|
||||
ASSIGN_PM(idx) \
|
||||
ATTRIBUTES_DEFINE(idx) \
|
||||
LOCK_DEFINE(idx) \
|
||||
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)
|
||||
|
|
|
@ -113,4 +113,6 @@
|
|||
#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 CMD_RDCR 0x15 /* Read the configuration register. */
|
||||
|
||||
#endif /*__SPI_NOR_H__*/
|
||||
|
|
Loading…
Reference in a new issue