drivers: spi_nrfx_*: Add support for optional WAKE line
Add option to use (by defining the `wake-gpios` devicetree properties) an additional signal line between SPI master and SPI slave that allows the latter to stay in low-power state and wake up only when a transfer is to occur. Signed-off-by: Andrzej Głąbek <andrzej.glabek@nordicsemi.no>
This commit is contained in:
parent
f132f55e32
commit
7974ff2665
|
@ -16,8 +16,10 @@ zephyr_library_sources_ifdef(CONFIG_SPI_SAM spi_sam.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_SPI_SAM0 spi_sam0.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_SIFIVE spi_sifive.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_RV32M1_LPSPI spi_rv32m1_lpspi.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_NRFX_SPI spi_nrfx_spi.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_NRFX_SPIM spi_nrfx_spim.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_NRFX_SPI spi_nrfx_spi.c
|
||||
spi_nrfx_common.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_NRFX_SPIM spi_nrfx_spim.c
|
||||
spi_nrfx_common.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_NRFX_SPIS spi_nrfx_spis.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_LITESPI spi_litespi.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_OC_SIMPLE spi_oc_simple.c)
|
||||
|
|
|
@ -66,4 +66,13 @@ config SPI_NRFX_RAM_BUFFER_SIZE
|
|||
supplying buffers located in flash to the driver, otherwise such
|
||||
transfers will fail.
|
||||
|
||||
config SPI_NRFX_WAKE_TIMEOUT_US
|
||||
int "Maximum time to wait for SPI slave to wake up"
|
||||
default 200
|
||||
help
|
||||
Maximum amount of time (in microseconds) that SPI master should wait
|
||||
for SPI slave to wake up after the WAKE line is asserted. Used only
|
||||
by instances that have the WAKE line configured (see the wake-gpios
|
||||
devicetree property).
|
||||
|
||||
endif # SPI_NRFX
|
||||
|
|
75
drivers/spi/spi_nrfx_common.c
Normal file
75
drivers/spi/spi_nrfx_common.c
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "spi_nrfx_common.h"
|
||||
#include <zephyr/kernel.h>
|
||||
#include <nrfx_gpiote.h>
|
||||
|
||||
int spi_nrfx_wake_init(uint32_t wake_pin)
|
||||
{
|
||||
nrfx_gpiote_input_config_t input_config = {
|
||||
.pull = NRF_GPIO_PIN_PULLDOWN,
|
||||
};
|
||||
uint8_t ch;
|
||||
nrfx_gpiote_trigger_config_t trigger_config = {
|
||||
.trigger = NRFX_GPIOTE_TRIGGER_HITOLO,
|
||||
.p_in_channel = &ch,
|
||||
};
|
||||
nrfx_err_t res;
|
||||
|
||||
res = nrfx_gpiote_channel_alloc(&ch);
|
||||
if (res != NRFX_SUCCESS) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
res = nrfx_gpiote_input_configure(wake_pin,
|
||||
&input_config,
|
||||
&trigger_config,
|
||||
NULL);
|
||||
if (res != NRFX_SUCCESS) {
|
||||
nrfx_gpiote_channel_free(ch);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spi_nrfx_wake_request(uint32_t wake_pin)
|
||||
{
|
||||
nrf_gpiote_event_t trigger_event = nrfx_gpiote_in_event_get(wake_pin);
|
||||
uint32_t start_cycles;
|
||||
uint32_t max_wait_cycles =
|
||||
DIV_ROUND_UP(CONFIG_SPI_NRFX_WAKE_TIMEOUT_US *
|
||||
CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC,
|
||||
1000000);
|
||||
int err = 0;
|
||||
|
||||
/* Enable the trigger (a high-to-low transition) without its interrupt.
|
||||
* The expected time to wait is quite short so it is not worth paying
|
||||
* the overhead of context switching to handle the interrupt.
|
||||
*/
|
||||
nrfx_gpiote_trigger_enable(wake_pin, false);
|
||||
/* Enable pull-up on the WAKE line. After the slave device sees the
|
||||
* WAKE line going high, it will force the line to go low. This will
|
||||
* be caught by the enabled trigger and the loop below waits for that.
|
||||
*/
|
||||
nrf_gpio_cfg_input(wake_pin, NRF_GPIO_PIN_PULLUP);
|
||||
|
||||
start_cycles = k_cycle_get_32();
|
||||
while (!nrf_gpiote_event_check(NRF_GPIOTE, trigger_event)) {
|
||||
uint32_t elapsed_cycles = k_cycle_get_32() - start_cycles;
|
||||
|
||||
if (elapsed_cycles >= max_wait_cycles) {
|
||||
err = -ETIMEDOUT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nrfx_gpiote_trigger_disable(wake_pin);
|
||||
nrf_gpio_cfg_input(wake_pin, NRF_GPIO_PIN_PULLDOWN);
|
||||
|
||||
return err;
|
||||
}
|
17
drivers/spi/spi_nrfx_common.h
Normal file
17
drivers/spi/spi_nrfx_common.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_DRIVERS_SPI_NRFX_COMMON_H_
|
||||
#define ZEPHYR_DRIVERS_SPI_NRFX_COMMON_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define WAKE_PIN_NOT_USED UINT32_MAX
|
||||
|
||||
int spi_nrfx_wake_init(uint32_t wake_pin);
|
||||
int spi_nrfx_wake_request(uint32_t wake_pin);
|
||||
|
||||
#endif /* ZEPHYR_DRIVERS_SPI_NRFX_COMMON_H_ */
|
|
@ -15,6 +15,7 @@
|
|||
LOG_MODULE_REGISTER(spi_nrfx_spi, CONFIG_SPI_LOG_LEVEL);
|
||||
|
||||
#include "spi_context.h"
|
||||
#include "spi_nrfx_common.h"
|
||||
|
||||
struct spi_nrfx_data {
|
||||
struct spi_context ctx;
|
||||
|
@ -29,6 +30,7 @@ struct spi_nrfx_config {
|
|||
nrfx_spi_config_t def_config;
|
||||
void (*irq_connect)(void);
|
||||
const struct pinctrl_dev_config *pcfg;
|
||||
uint32_t wake_pin;
|
||||
};
|
||||
|
||||
static void event_handler(const nrfx_spi_evt_t *p_event, void *p_context);
|
||||
|
@ -231,6 +233,18 @@ static int transceive(const struct device *dev,
|
|||
if (error == 0) {
|
||||
dev_data->busy = true;
|
||||
|
||||
if (dev_config->wake_pin != WAKE_PIN_NOT_USED) {
|
||||
error = spi_nrfx_wake_request(dev_config->wake_pin);
|
||||
if (error == -ETIMEDOUT) {
|
||||
LOG_WRN("Waiting for WAKE acknowledgment timed out");
|
||||
/* If timeout occurs, try to perform the transfer
|
||||
* anyway, just in case the slave device was unable
|
||||
* to signal that it was already awaken and prepared
|
||||
* for the transfer.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
spi_context_buffers_setup(&dev_data->ctx, tx_bufs, rx_bufs, 1);
|
||||
spi_context_cs_control(&dev_data->ctx, true);
|
||||
|
||||
|
@ -363,6 +377,18 @@ static int spi_nrfx_init(const struct device *dev)
|
|||
return err;
|
||||
}
|
||||
|
||||
if (dev_config->wake_pin != WAKE_PIN_NOT_USED) {
|
||||
err = spi_nrfx_wake_init(dev_config->wake_pin);
|
||||
if (err == -ENODEV) {
|
||||
LOG_ERR("Failed to allocate GPIOTE channel for WAKE");
|
||||
return err;
|
||||
}
|
||||
if (err == -EIO) {
|
||||
LOG_ERR("Failed to configure WAKE pin");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
dev_config->irq_connect();
|
||||
|
||||
err = spi_context_cs_configure_all(&dev_data->ctx);
|
||||
|
@ -413,7 +439,12 @@ static int spi_nrfx_init(const struct device *dev)
|
|||
}, \
|
||||
.irq_connect = irq_connect##idx, \
|
||||
.pcfg = PINCTRL_DT_DEV_CONFIG_GET(SPI(idx)), \
|
||||
.wake_pin = NRF_DT_GPIOS_TO_PSEL_OR(SPI(idx), wake_gpios, \
|
||||
WAKE_PIN_NOT_USED), \
|
||||
}; \
|
||||
BUILD_ASSERT(!DT_NODE_HAS_PROP(SPI(idx), wake_gpios) || \
|
||||
!(DT_GPIO_FLAGS(SPI(idx), wake_gpios) & GPIO_ACTIVE_LOW), \
|
||||
"WAKE line must be configured as active high"); \
|
||||
PM_DEVICE_DT_DEFINE(SPI(idx), spi_nrfx_pm_action); \
|
||||
DEVICE_DT_DEFINE(SPI(idx), \
|
||||
spi_nrfx_init, \
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
LOG_MODULE_REGISTER(spi_nrfx_spim, CONFIG_SPI_LOG_LEVEL);
|
||||
|
||||
#include "spi_context.h"
|
||||
#include "spi_nrfx_common.h"
|
||||
|
||||
#if defined(CONFIG_SOC_NRF52832) && !defined(CONFIG_SOC_NRF52832_ALLOW_SPIM_DESPITE_PAN_58)
|
||||
#error This driver is not available by default for nRF52832 because of Product Anomaly 58 \
|
||||
|
@ -59,6 +60,7 @@ struct spi_nrfx_config {
|
|||
#ifdef CONFIG_SOC_NRF52832_ALLOW_SPIM_DESPITE_PAN_58
|
||||
bool anomaly_58_workaround;
|
||||
#endif
|
||||
uint32_t wake_pin;
|
||||
};
|
||||
|
||||
static void event_handler(const nrfx_spim_evt_t *p_event, void *p_context);
|
||||
|
@ -393,6 +395,18 @@ static int transceive(const struct device *dev,
|
|||
if (error == 0) {
|
||||
dev_data->busy = true;
|
||||
|
||||
if (dev_config->wake_pin != WAKE_PIN_NOT_USED) {
|
||||
error = spi_nrfx_wake_request(dev_config->wake_pin);
|
||||
if (error == -ETIMEDOUT) {
|
||||
LOG_WRN("Waiting for WAKE acknowledgment timed out");
|
||||
/* If timeout occurs, try to perform the transfer
|
||||
* anyway, just in case the slave device was unable
|
||||
* to signal that it was already awaken and prepared
|
||||
* for the transfer.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
spi_context_buffers_setup(&dev_data->ctx, tx_bufs, rx_bufs, 1);
|
||||
spi_context_cs_control(&dev_data->ctx, true);
|
||||
|
||||
|
@ -529,6 +543,18 @@ static int spi_nrfx_init(const struct device *dev)
|
|||
return err;
|
||||
}
|
||||
|
||||
if (dev_config->wake_pin != WAKE_PIN_NOT_USED) {
|
||||
err = spi_nrfx_wake_init(dev_config->wake_pin);
|
||||
if (err == -ENODEV) {
|
||||
LOG_ERR("Failed to allocate GPIOTE channel for WAKE");
|
||||
return err;
|
||||
}
|
||||
if (err == -EIO) {
|
||||
LOG_ERR("Failed to configure WAKE pin");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
dev_config->irq_connect();
|
||||
|
||||
err = spi_context_cs_configure_all(&dev_data->ctx);
|
||||
|
@ -603,7 +629,12 @@ static int spi_nrfx_init(const struct device *dev)
|
|||
(.anomaly_58_workaround = \
|
||||
SPIM_PROP(idx, anomaly_58_workaround),), \
|
||||
()) \
|
||||
.wake_pin = NRF_DT_GPIOS_TO_PSEL_OR(SPIM(idx), wake_gpios, \
|
||||
WAKE_PIN_NOT_USED), \
|
||||
}; \
|
||||
BUILD_ASSERT(!DT_NODE_HAS_PROP(SPIM(idx), wake_gpios) || \
|
||||
!(DT_GPIO_FLAGS(SPIM(idx), wake_gpios) & GPIO_ACTIVE_LOW),\
|
||||
"WAKE line must be configured as active high"); \
|
||||
PM_DEVICE_DT_DEFINE(SPIM(idx), spim_nrfx_pm_action); \
|
||||
DEVICE_DT_DEFINE(SPIM(idx), \
|
||||
spi_nrfx_init, \
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <zephyr/drivers/spi.h>
|
||||
#include <zephyr/drivers/pinctrl.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <soc.h>
|
||||
#include <nrfx_spis.h>
|
||||
|
||||
|
@ -18,6 +19,8 @@ LOG_MODULE_REGISTER(spi_nrfx_spis, CONFIG_SPI_LOG_LEVEL);
|
|||
struct spi_nrfx_data {
|
||||
struct spi_context ctx;
|
||||
const struct device *dev;
|
||||
struct k_sem wake_sem;
|
||||
struct gpio_callback wake_cb_data;
|
||||
};
|
||||
|
||||
struct spi_nrfx_config {
|
||||
|
@ -26,6 +29,7 @@ struct spi_nrfx_config {
|
|||
void (*irq_connect)(void);
|
||||
uint16_t max_buf_len;
|
||||
const struct pinctrl_dev_config *pcfg;
|
||||
struct gpio_dt_spec wake_gpio;
|
||||
};
|
||||
|
||||
static inline nrf_spis_mode_t get_nrf_spis_mode(uint16_t operation)
|
||||
|
@ -130,6 +134,32 @@ static int prepare_for_transfer(const struct device *dev,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void wake_callback(const struct device *dev, struct gpio_callback *cb,
|
||||
uint32_t pins)
|
||||
{
|
||||
struct spi_nrfx_data *dev_data =
|
||||
CONTAINER_OF(cb, struct spi_nrfx_data, wake_cb_data);
|
||||
const struct spi_nrfx_config *dev_config = dev_data->dev->config;
|
||||
|
||||
(void)gpio_pin_interrupt_configure_dt(&dev_config->wake_gpio,
|
||||
GPIO_INT_DISABLE);
|
||||
k_sem_give(&dev_data->wake_sem);
|
||||
}
|
||||
|
||||
static void wait_for_wake(struct spi_nrfx_data *dev_data,
|
||||
const struct spi_nrfx_config *dev_config)
|
||||
{
|
||||
/* If the WAKE line is low, wait until it goes high - this is a signal
|
||||
* from the master that it wants to perform a transfer.
|
||||
*/
|
||||
if (gpio_pin_get_raw(dev_config->wake_gpio.port,
|
||||
dev_config->wake_gpio.pin) == 0) {
|
||||
(void)gpio_pin_interrupt_configure_dt(&dev_config->wake_gpio,
|
||||
GPIO_INT_LEVEL_HIGH);
|
||||
(void)k_sem_take(&dev_data->wake_sem, K_FOREVER);
|
||||
}
|
||||
}
|
||||
|
||||
static int transceive(const struct device *dev,
|
||||
const struct spi_config *spi_cfg,
|
||||
const struct spi_buf_set *tx_bufs,
|
||||
|
@ -139,6 +169,7 @@ static int transceive(const struct device *dev,
|
|||
void *userdata)
|
||||
{
|
||||
struct spi_nrfx_data *dev_data = dev->data;
|
||||
const struct spi_nrfx_config *dev_config = dev->config;
|
||||
int error;
|
||||
|
||||
spi_context_lock(&dev_data->ctx, asynchronous, cb, userdata, spi_cfg);
|
||||
|
@ -155,14 +186,41 @@ static int transceive(const struct device *dev,
|
|||
LOG_ERR("Only buffers located in RAM are supported");
|
||||
error = -ENOTSUP;
|
||||
} else {
|
||||
if (dev_config->wake_gpio.port) {
|
||||
wait_for_wake(dev_data, dev_config);
|
||||
|
||||
nrf_spis_enable(dev_config->spis.p_reg);
|
||||
}
|
||||
|
||||
error = prepare_for_transfer(dev,
|
||||
tx_bufs ? tx_bufs->buffers[0].buf : NULL,
|
||||
tx_bufs ? tx_bufs->buffers[0].len : 0,
|
||||
rx_bufs ? rx_bufs->buffers[0].buf : NULL,
|
||||
rx_bufs ? rx_bufs->buffers[0].len : 0);
|
||||
if (error == 0) {
|
||||
if (dev_config->wake_gpio.port) {
|
||||
/* Set the WAKE line low (tie it to ground)
|
||||
* to signal readiness to handle the transfer.
|
||||
*/
|
||||
gpio_pin_set_raw(dev_config->wake_gpio.port,
|
||||
dev_config->wake_gpio.pin,
|
||||
0);
|
||||
/* Set the WAKE line back high (i.e. disconnect
|
||||
* output for its pin since it's configured in
|
||||
* open drain mode) so that it can be controlled
|
||||
* by the other side again.
|
||||
*/
|
||||
gpio_pin_set_raw(dev_config->wake_gpio.port,
|
||||
dev_config->wake_gpio.pin,
|
||||
1);
|
||||
}
|
||||
|
||||
error = spi_context_wait_for_completion(&dev_data->ctx);
|
||||
}
|
||||
|
||||
if (dev_config->wake_gpio.port) {
|
||||
nrf_spis_disable(dev_config->spis.p_reg);
|
||||
}
|
||||
}
|
||||
|
||||
spi_context_release(&dev_data->ctx, error);
|
||||
|
@ -245,6 +303,42 @@ static int spi_nrfx_init(const struct device *dev)
|
|||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (dev_config->wake_gpio.port) {
|
||||
if (!device_is_ready(dev_config->wake_gpio.port)) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* In open drain mode, the output is disconnected when set to
|
||||
* the high state, so the following will effectively configure
|
||||
* the pin as an input only.
|
||||
*/
|
||||
err = gpio_pin_configure_dt(&dev_config->wake_gpio,
|
||||
GPIO_INPUT |
|
||||
GPIO_OUTPUT_HIGH |
|
||||
GPIO_OPEN_DRAIN);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
gpio_init_callback(&dev_data->wake_cb_data, wake_callback,
|
||||
BIT(dev_config->wake_gpio.pin));
|
||||
err = gpio_add_callback(dev_config->wake_gpio.port,
|
||||
&dev_data->wake_cb_data);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
/* When the WAKE line is used, the SPIS peripheral is enabled
|
||||
* only after the master signals that it wants to perform a
|
||||
* transfer and it is disabled right after the transfer is done.
|
||||
* Waiting for the WAKE line to go high, what can be done using
|
||||
* the GPIO PORT event, instead of just waiting for the transfer
|
||||
* with the SPIS peripheral enabled, significantly reduces idle
|
||||
* power consumption.
|
||||
*/
|
||||
nrf_spis_disable(dev_config->spis.p_reg);
|
||||
}
|
||||
|
||||
spi_context_unlock_unconditionally(&dev_data->ctx);
|
||||
|
||||
return 0;
|
||||
|
@ -270,6 +364,8 @@ static int spi_nrfx_init(const struct device *dev)
|
|||
SPI_CONTEXT_INIT_LOCK(spi_##idx##_data, ctx), \
|
||||
SPI_CONTEXT_INIT_SYNC(spi_##idx##_data, ctx), \
|
||||
.dev = DEVICE_DT_GET(SPIS(idx)), \
|
||||
.wake_sem = Z_SEM_INITIALIZER( \
|
||||
spi_##idx##_data.wake_sem, 0, 1), \
|
||||
}; \
|
||||
PINCTRL_DT_DEFINE(SPIS(idx)); \
|
||||
static const struct spi_nrfx_config spi_##idx##z_config = { \
|
||||
|
@ -288,7 +384,11 @@ static int spi_nrfx_init(const struct device *dev)
|
|||
.irq_connect = irq_connect##idx, \
|
||||
.pcfg = PINCTRL_DT_DEV_CONFIG_GET(SPIS(idx)), \
|
||||
.max_buf_len = BIT_MASK(SPIS_PROP(idx, easydma_maxcnt_bits)), \
|
||||
.wake_gpio = GPIO_DT_SPEC_GET_OR(SPIS(idx), wake_gpios, {0}), \
|
||||
}; \
|
||||
BUILD_ASSERT(!DT_NODE_HAS_PROP(SPIS(idx), wake_gpios) || \
|
||||
!(DT_GPIO_FLAGS(SPIS(idx), wake_gpios) & GPIO_ACTIVE_LOW),\
|
||||
"WAKE line must be configured as active high"); \
|
||||
DEVICE_DT_DEFINE(SPIS(idx), \
|
||||
spi_nrfx_init, \
|
||||
NULL, \
|
||||
|
|
|
@ -36,3 +36,27 @@ properties:
|
|||
description: |
|
||||
Maximum number of bits available in the EasyDMA MAXCNT register. This
|
||||
property must be set at SoC level DTS files.
|
||||
|
||||
wake-gpios:
|
||||
type: phandle-array
|
||||
description: |
|
||||
Optional bi-directional line that allows SPI master to indicate to SPI
|
||||
slave (by setting the line high) that a transfer is to occur, so that
|
||||
the latter can prepare (and indicate its readiness) for handling that
|
||||
transfer when it is actually needed, and stay in any desired low-power
|
||||
state otherwise.
|
||||
The protocol is as follows:
|
||||
- initially, SPI slave configures its WAKE line pin as an input and SPI
|
||||
master keeps the line in the low state
|
||||
- when a transfer is to be performed, SPI master configures its WAKE
|
||||
line pin as an input with pull-up; this changes the line state to
|
||||
high but allows SPI slave to override that state
|
||||
- when SPI slave detects the high state of the WAKE line, it prepares
|
||||
for the transfer and when everything is ready, it drives the WAKE
|
||||
line low by configuring its pin as an output
|
||||
- the generated high-to-low transition on the WAKE line is a signal
|
||||
to SPI master that it can proceed with the transfer
|
||||
- SPI slave releases the line by configuring its pin back to be an input
|
||||
and SPI master again keeps the line in the low state
|
||||
Please note that the line must be configured and properly handled on
|
||||
both sides for the mechanism to work correctly.
|
||||
|
|
Loading…
Reference in a new issue