Skip to content

Commit 98a344f

Browse files
pabigotcarlescufi
authored andcommitted
drivers: spi_nor: support deep-power-down mode
Add internal API to enter and exit deep power-down mode. Add Kconfig option to return to DPD whenever device is not active. When device power management becomes more mature it should be possible to implement it, which would allow use of DPD without having to enter and exit DPD between consecutive transactions. Signed-off-by: Peter A. Bigot <[email protected]>
1 parent c2e9441 commit 98a344f

File tree

4 files changed

+173
-2
lines changed

4 files changed

+173
-2
lines changed

drivers/flash/Kconfig.nor

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,16 @@ config SPI_NOR_FLASH_LAYOUT_PAGE_SIZE
3131
size (65536). Other options include the 32K-byte erase size
3232
(32768), and the sector size (4096).
3333

34+
config SPI_NOR_IDLE_IN_DPD
35+
bool "Use Deep Power-Down mode when flash is not being accessed."
36+
help
37+
Where supported deep power-down mode can reduce current draw
38+
to as little as 0.1% of standby current. However it takes
39+
some milliseconds to enter and exit from this mode.
40+
41+
Select this option for applications where device power
42+
management is not enabled, the flash remains inactive for
43+
long periods, and when used the impact of waiting for mode
44+
enter and exit delays is acceptable.
45+
3446
endif # SPI_NOR

drivers/flash/spi_nor.c

Lines changed: 152 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,44 @@
1818

1919
LOG_MODULE_REGISTER(spi_nor, CONFIG_FLASH_LOG_LEVEL);
2020

21+
/* Device Power Management Notes
22+
*
23+
* These flash devices have several modes during operation:
24+
* * When CSn is asserted (during a SPI operation) the device is
25+
* active.
26+
* * When CSn is deasserted the device enters a standby mode.
27+
* * Some devices support a Deep Power-Down mode which reduces current
28+
* to as little as 0.1% of standby.
29+
*
30+
* The power reduction from DPD is sufficent to warrant allowing its
31+
* use even in cases where Zephyr's device power management is not
32+
* available. This is selected through the SPI_NOR_IDLE_IN_DPD
33+
* Kconfig option.
34+
*
35+
* When mapped to the Zephyr Device Power Management states:
36+
* * DEVICE_PM_ACTIVE_STATE covers both active and standby modes;
37+
* * DEVICE_PM_LOW_POWER_STATE, DEVICE_PM_SUSPEND_STATE, and
38+
* DEVICE_PM_OFF_STATE all correspond to deep-power-down mode.
39+
*/
40+
2141
#define SPI_NOR_MAX_ADDR_WIDTH 4
2242

43+
#ifndef NSEC_PER_MSEC
44+
#define NSEC_PER_MSEC (NSEC_PER_USEC * USEC_PER_MSEC)
45+
#endif
46+
47+
#ifdef DT_INST_0_JEDEC_SPI_NOR_T_ENTER_DPD
48+
#define T_DP_MS ceiling_fraction(DT_INST_0_JEDEC_SPI_NOR_T_ENTER_DPD, NSEC_PER_MSEC)
49+
#endif /* T_ENTER_DPD */
50+
#ifdef DT_INST_0_JEDEC_SPI_NOR_T_EXIT_DPD
51+
#define T_RES1_MS ceiling_fraction(DT_INST_0_JEDEC_SPI_NOR_T_EXIT_DPD, NSEC_PER_MSEC)
52+
#endif /* T_EXIT_DPD */
53+
#ifdef DT_INST_0_JEDEC_SPI_NOR_DPD_WAKEUP_SEQUENCE
54+
#define T_DPDD_MS ceiling_fraction(DT_INST_0_JEDEC_SPI_NOR_DPD_WAKEUP_SEQUENCE_0, NSEC_PER_MSEC)
55+
#define T_CRDP_MS ceiling_fraction(DT_INST_0_JEDEC_SPI_NOR_DPD_WAKEUP_SEQUENCE_1, NSEC_PER_MSEC)
56+
#define T_RDP_MS ceiling_fraction(DT_INST_0_JEDEC_SPI_NOR_DPD_WAKEUP_SEQUENCE_2, NSEC_PER_MSEC)
57+
#endif /* DPD_WAKEUP_SEQUENCE */
58+
2359
/**
2460
* struct spi_nor_data - Structure for defining the SPI NOR access
2561
* @spi: The SPI device
@@ -33,9 +69,59 @@ struct spi_nor_data {
3369
#ifdef DT_INST_0_JEDEC_SPI_NOR_CS_GPIOS_CONTROLLER
3470
struct spi_cs_control cs_ctrl;
3571
#endif /* DT_INST_0_JEDEC_SPI_NOR_CS_GPIOS_CONTROLLER */
72+
#ifdef DT_INST_0_JEDEC_SPI_NOR_HAS_DPD
73+
/* Low 32-bits of uptime counter at which device last entered
74+
* deep power-down.
75+
*/
76+
u32_t ts_enter_dpd;
77+
#endif
3678
struct k_sem sem;
3779
};
3880

81+
/* Capture the time at which the device entered deep power-down. */
82+
static inline void record_entered_dpd(const struct device *const dev)
83+
{
84+
#ifdef DT_INST_0_JEDEC_SPI_NOR_HAS_DPD
85+
struct spi_nor_data *const driver_data = dev->driver_data;
86+
87+
driver_data->ts_enter_dpd = k_uptime_get_32();
88+
#endif
89+
}
90+
91+
/* Check the current time against the time DPD was entered and delay
92+
* until it's ok to initiate the DPD exit process.
93+
*/
94+
static inline void delay_until_exit_dpd_ok(const struct device *const dev)
95+
{
96+
#ifdef DT_INST_0_JEDEC_SPI_NOR_HAS_DPD
97+
struct spi_nor_data *const driver_data = dev->driver_data;
98+
s32_t since = (s32_t)(k_uptime_get_32() - driver_data->ts_enter_dpd);
99+
100+
/* If the time is negative the 32-bit counter has wrapped,
101+
* which is certainly long enough no further delay is
102+
* required. Otherwise we have to check whether it's been
103+
* long enough.
104+
*/
105+
if (since >= 0) {
106+
#ifdef DT_INST_0_JEDEC_SPI_NOR_T_ENTER_DPD
107+
/* Subtract time required for DPD to be reached */
108+
since -= T_DP_MS;
109+
#endif /* T_ENTER_DPD */
110+
#ifdef DT_INST_0_JEDEC_SPI_NOR_DPD_WAKEUP_SEQUENCE
111+
/* Subtract time required in DPD before exit */
112+
since -= T_DPDD_MS;
113+
#endif /* DT_INST_0_JEDEC_SPI_NOR_DPD_WAKEUP_SEQUENCE */
114+
115+
/* If the adjusted time is negative we have to wait
116+
* until it reaches zero before we can proceed.
117+
*/
118+
if (since < 0) {
119+
k_sleep(K_MSEC((u32_t)-since));
120+
}
121+
}
122+
#endif /* DT_INST_0_JEDEC_SPI_NOR_HAS_DPD */
123+
}
124+
39125
/*
40126
* @brief Send an SPI command
41127
*
@@ -99,9 +185,56 @@ static int spi_nor_access(const struct device *const dev,
99185
#define spi_nor_cmd_addr_write(dev, opcode, addr, src, length) \
100186
spi_nor_access(dev, opcode, true, addr, (void *)src, length, true)
101187

188+
static int enter_dpd(const struct device *const dev)
189+
{
190+
int ret = 0;
191+
192+
if (IS_ENABLED(DT_INST_0_JEDEC_SPI_NOR_HAS_DPD)) {
193+
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_DPD);
194+
if (ret == 0) {
195+
record_entered_dpd(dev);
196+
}
197+
}
198+
return ret;
199+
}
200+
201+
static int exit_dpd(const struct device *const dev)
202+
{
203+
int ret = 0;
204+
205+
if (IS_ENABLED(DT_INST_0_JEDEC_SPI_NOR_HAS_DPD)) {
206+
delay_until_exit_dpd_ok(dev);
207+
208+
#ifdef DT_INST_0_JEDEC_SPI_NOR_DPD_WAKEUP_SEQUENCE
209+
/* Assert CSn and wait for tCRDP.
210+
*
211+
* Unfortunately the SPI API doesn't allow us to
212+
* control CSn so fake it by writing a known-supported
213+
* single-byte command, hoping that'll hold the assert
214+
* long enough. This is highly likely, since the
215+
* duration is usually less than two SPI clock cycles.
216+
*/
217+
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_RDID);
218+
219+
/* Deassert CSn and wait for tRDP */
220+
k_sleep(K_MSEC(T_RDP_MS));
221+
#else /* DPD_WAKEUP_SEQUENCE */
222+
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_RDPD);
223+
224+
if (ret == 0) {
225+
#ifdef DT_INST_0_JEDEC_SPI_NOR_T_EXIT_DPD
226+
k_sleep(K_MSEC(T_RES1_MS));
227+
#endif /* T_EXIT_DPD */
228+
}
229+
#endif /* DPD_WAKEUP_SEQUENCE */
230+
}
231+
return ret;
232+
}
233+
102234
/* Everything necessary to acquire owning access to the device.
103235
*
104-
* For now this means taking the lock.
236+
* This means taking the lock and, if necessary, waking the device
237+
* from deep power-down mode.
105238
*/
106239
static void acquire_device(struct device *dev)
107240
{
@@ -110,14 +243,23 @@ static void acquire_device(struct device *dev)
110243

111244
k_sem_take(&driver_data->sem, K_FOREVER);
112245
}
246+
247+
if (IS_ENABLED(CONFIG_SPI_NOR_IDLE_IN_DPD)) {
248+
exit_dpd(dev);
249+
}
113250
}
114251

115252
/* Everything necessary to release access to the device.
116253
*
117-
* For now, this means releasing the lock.
254+
* This means (optionally) putting the device into deep power-down
255+
* mode, and releasing the lock.
118256
*/
119257
static void release_device(struct device *dev)
120258
{
259+
if (IS_ENABLED(CONFIG_SPI_NOR_IDLE_IN_DPD)) {
260+
enter_dpd(dev);
261+
}
262+
121263
if (IS_ENABLED(CONFIG_MULTITHREADING)) {
122264
struct spi_nor_data *const driver_data = dev->driver_data;
123265

@@ -348,11 +490,19 @@ static int spi_nor_configure(struct device *dev)
348490
data->spi_cfg.cs = &data->cs_ctrl;
349491
#endif /* DT_INST_0_JEDEC_SPI_NOR_CS_GPIOS_CONTROLLER */
350492

493+
/* Might be in DPD if system restarted without power cycle. */
494+
exit_dpd(dev);
495+
351496
/* now the spi bus is configured, we can verify the flash id */
352497
if (spi_nor_read_id(dev, params) != 0) {
353498
return -ENODEV;
354499
}
355500

501+
if (IS_ENABLED(CONFIG_SPI_NOR_IDLE_IN_DPD)
502+
&& (enter_dpd(dev) != 0)) {
503+
return -ENODEV;
504+
}
505+
356506
return 0;
357507
}
358508

drivers/flash/spi_nor.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ struct spi_nor_config {
3939
#define SPI_NOR_CMD_CE 0xC7 /* Chip erase */
4040
#define SPI_NOR_CMD_RDID 0x9F /* Read JEDEC ID */
4141
#define SPI_NOR_CMD_ULBPR 0x98 /* Global Block Protection Unlock */
42+
#define SPI_NOR_CMD_DPD 0xB9 /* Deep Power Down */
43+
#define SPI_NOR_CMD_RDPD 0xAB /* Release from Deep Power Down */
4244

4345
/* Page, sector, and block size are standard, not configurable. */
4446
#define SPI_NOR_PAGE_SIZE 0x0100U

samples/drivers/spi_flash/sample.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,10 @@ tests:
1616
- "Data read 55 aa"
1717
- "Data read matches with data written. Good!!"
1818
depends_on: spi
19+
sample.driver.spi_flash_dpd:
20+
tags: spi flash
21+
filter: dt_compat_enabled("jedec,spi-nor")
22+
build_only: true
23+
extra_configs:
24+
- CONFIG_SPI_NOR_IDLE_IN_DPD=y
25+
depends_on: spi

0 commit comments

Comments
 (0)