From 1ebd52e72a1795538d81a737448eb5e3fe8772c8 Mon Sep 17 00:00:00 2001 From: Aksel Skauge Mellbye Date: Wed, 27 Aug 2025 16:48:14 +0200 Subject: [PATCH 1/2] dts: arm: silabs: Add EM4 wakeup pins and power state Add EM4 wakeup capable pin mapping to GPIO port node. Pins capable of EM4 wakeup have dedicated interrupt flags. Add EM4 as a soft-off power state that is disabled by default. Marking it as disabled allows users to enter it with `pm_state_force()`, while preventing the power management subsystem from selecting the state automatically. Signed-off-by: Aksel Skauge Mellbye --- dts/arm/silabs/xg21/xg21.dtsi | 8 ++++++++ dts/arm/silabs/xg22/xg22.dtsi | 20 +++++++++++++++++++- dts/arm/silabs/xg23/xg23.dtsi | 20 +++++++++++++++++++- dts/arm/silabs/xg24/xg24.dtsi | 20 +++++++++++++++++++- dts/arm/silabs/xg27/xg27.dtsi | 20 +++++++++++++++++++- dts/arm/silabs/xg28/xg28.dtsi | 20 +++++++++++++++++++- dts/arm/silabs/xg29/xg29.dtsi | 20 +++++++++++++++++++- dts/bindings/gpio/silabs,gpio-port.yaml | 12 ++++++++++++ 8 files changed, 134 insertions(+), 6 deletions(-) diff --git a/dts/arm/silabs/xg21/xg21.dtsi b/dts/arm/silabs/xg21/xg21.dtsi index 8d5c44bd9c9bc..3f0a56120f906 100644 --- a/dts/arm/silabs/xg21/xg21.dtsi +++ b/dts/arm/silabs/xg21/xg21.dtsi @@ -351,6 +351,8 @@ reg = <0x5003c000 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <0>; + silabs,wakeup-pins = <5>; status = "disabled"; }; @@ -359,6 +361,8 @@ reg = <0x5003c030 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <3>; + silabs,wakeup-pins = <1>; status = "disabled"; }; @@ -367,6 +371,8 @@ reg = <0x5003c060 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <6>, <7>; + silabs,wakeup-pins = <0>, <5>; status = "disabled"; }; @@ -375,6 +381,8 @@ reg = <0x5003c090 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <9>; + silabs,wakeup-pins = <2>; status = "disabled"; }; }; diff --git a/dts/arm/silabs/xg22/xg22.dtsi b/dts/arm/silabs/xg22/xg22.dtsi index 1aaa8db3869c1..c01e590abedc8 100644 --- a/dts/arm/silabs/xg22/xg22.dtsi +++ b/dts/arm/silabs/xg22/xg22.dtsi @@ -129,7 +129,7 @@ * The minimum residency and exit latency is * managed by sl_power_manager on S2 devices. */ - cpu-power-states = <&pstate_em1 &pstate_em2>; + cpu-power-states = <&pstate_em1 &pstate_em2 &pstate_em4>; #address-cells = <1>; #size-cells = <1>; @@ -158,6 +158,16 @@ compatible = "zephyr,power-state"; power-state-name = "suspend-to-idle"; }; + + /* + * EM4 is a shutdown state where all clocks are off. The device + * is reset on wakeup from EM4. + */ + pstate_em4: em4 { + compatible = "zephyr,power-state"; + power-state-name = "soft-off"; + status = "disabled"; + }; }; }; @@ -403,6 +413,8 @@ reg = <0x5003C000 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <0>; + silabs,wakeup-pins = <5>; status = "disabled"; }; @@ -411,6 +423,8 @@ reg = <0x5003C030 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <3>, <4>; + silabs,wakeup-pins = <1>, <3>; status = "disabled"; }; @@ -419,6 +433,8 @@ reg = <0x5003C060 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <6>, <7>, <8>; + silabs,wakeup-pins = <0>, <5>, <7>; status = "disabled"; }; @@ -427,6 +443,8 @@ reg = <0x5003C090 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <9>; + silabs,wakeup-pins = <2>; status = "disabled"; }; }; diff --git a/dts/arm/silabs/xg23/xg23.dtsi b/dts/arm/silabs/xg23/xg23.dtsi index 4feca87c7090f..c88517697ede2 100644 --- a/dts/arm/silabs/xg23/xg23.dtsi +++ b/dts/arm/silabs/xg23/xg23.dtsi @@ -149,7 +149,7 @@ device_type = "cpu"; compatible = "arm,cortex-m33"; reg = <0>; - cpu-power-states = <&pstate_em1 &pstate_em2>; + cpu-power-states = <&pstate_em1 &pstate_em2 &pstate_em4>; /* * The minimum residency and exit latency is * managed by sl_power_manager on S2 devices. @@ -182,6 +182,16 @@ compatible = "zephyr,power-state"; power-state-name = "suspend-to-idle"; }; + + /* + * EM4 is a shutdown state where all clocks are off. The device + * is reset on wakeup from EM4. + */ + pstate_em4: em4 { + compatible = "zephyr,power-state"; + power-state-name = "soft-off"; + status = "disabled"; + }; }; }; @@ -461,6 +471,8 @@ reg = <0x5003c030 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <0>; + silabs,wakeup-pins = <5>; status = "disabled"; }; @@ -469,6 +481,8 @@ reg = <0x5003c060 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <3>, <4>; + silabs,wakeup-pins = <1>, <3>; status = "disabled"; }; @@ -477,6 +491,8 @@ reg = <0x5003c090 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <6>, <7>, <8>; + silabs,wakeup-pins = <0>, <5>, <7>; status = "disabled"; }; @@ -485,6 +501,8 @@ reg = <0x5003c0C0 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <9>, <10>; + silabs,wakeup-pins = <2>, <5>; status = "disabled"; }; }; diff --git a/dts/arm/silabs/xg24/xg24.dtsi b/dts/arm/silabs/xg24/xg24.dtsi index 1cdab33bed43b..ffe4516b3fcd9 100644 --- a/dts/arm/silabs/xg24/xg24.dtsi +++ b/dts/arm/silabs/xg24/xg24.dtsi @@ -139,7 +139,7 @@ device_type = "cpu"; compatible = "arm,cortex-m33"; reg = <0>; - cpu-power-states = <&pstate_em1 &pstate_em2>; + cpu-power-states = <&pstate_em1 &pstate_em2 &pstate_em4>; /* * The minimum residency and exit latency is * managed by sl_power_manager on S2 devices. @@ -172,6 +172,16 @@ compatible = "zephyr,power-state"; power-state-name = "suspend-to-idle"; }; + + /* + * EM4 is a shutdown state where all clocks are off. The device + * is reset on wakeup from EM4. + */ + pstate_em4: em4 { + compatible = "zephyr,power-state"; + power-state-name = "soft-off"; + status = "disabled"; + }; }; }; @@ -431,6 +441,8 @@ reg = <0x5003c030 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <0>; + silabs,wakeup-pins = <5>; status = "disabled"; }; @@ -439,6 +451,8 @@ reg = <0x5003c060 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <3>, <4>; + silabs,wakeup-pins = <1>, <3>; status = "disabled"; }; @@ -447,6 +461,8 @@ reg = <0x5003c090 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <6>, <7>, <8>; + silabs,wakeup-pins = <0>, <5>, <7>; status = "disabled"; }; @@ -455,6 +471,8 @@ reg = <0x5003c0C0 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <9>, <10>; + silabs,wakeup-pins = <2>, <5>; status = "disabled"; }; }; diff --git a/dts/arm/silabs/xg27/xg27.dtsi b/dts/arm/silabs/xg27/xg27.dtsi index 023551e2db70d..c957f6a5eba96 100644 --- a/dts/arm/silabs/xg27/xg27.dtsi +++ b/dts/arm/silabs/xg27/xg27.dtsi @@ -138,7 +138,7 @@ * The minimum residency and exit latency is * managed by sl_power_manager on S2 devices. */ - cpu-power-states = <&pstate_em1 &pstate_em2>; + cpu-power-states = <&pstate_em1 &pstate_em2 &pstate_em4>; #address-cells = <1>; #size-cells = <1>; @@ -167,6 +167,16 @@ compatible = "zephyr,power-state"; power-state-name = "suspend-to-idle"; }; + + /* + * EM4 is a shutdown state where all clocks are off. The device + * is reset on wakeup from EM4. + */ + pstate_em4: em4 { + compatible = "zephyr,power-state"; + power-state-name = "soft-off"; + status = "disabled"; + }; }; }; @@ -412,6 +422,8 @@ reg = <0x5003C030 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <0>; + silabs,wakeup-pins = <5>; status = "disabled"; }; @@ -420,6 +432,8 @@ reg = <0x5003C060 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <3>, <4>; + silabs,wakeup-pins = <1>, <3>; status = "disabled"; }; @@ -428,6 +442,8 @@ reg = <0x5003C090 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <6>, <7>, <8>; + silabs,wakeup-pins = <0>, <5>, <7>; status = "disabled"; }; @@ -436,6 +452,8 @@ reg = <0x5003C0C0 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <9>; + silabs,wakeup-pins = <2>; status = "disabled"; }; }; diff --git a/dts/arm/silabs/xg28/xg28.dtsi b/dts/arm/silabs/xg28/xg28.dtsi index ed5bfa457ccbd..9e0e1033c732e 100644 --- a/dts/arm/silabs/xg28/xg28.dtsi +++ b/dts/arm/silabs/xg28/xg28.dtsi @@ -170,7 +170,7 @@ device_type = "cpu"; compatible = "arm,cortex-m33"; reg = <0>; - cpu-power-states = <&pstate_em1 &pstate_em2>; + cpu-power-states = <&pstate_em1 &pstate_em2 &pstate_em4>; /* * The minimum residency and exit latency is * managed by sl_power_manager on S2 devices. @@ -196,6 +196,16 @@ compatible = "zephyr,power-state"; power-state-name = "suspend-to-idle"; }; + + /* + * EM4 is a shutdown state where all clocks are off. The device + * is reset on wakeup from EM4. + */ + pstate_em4: em4 { + compatible = "zephyr,power-state"; + power-state-name = "soft-off"; + status = "disabled"; + }; }; }; @@ -475,6 +485,8 @@ reg = <0x5003c030 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <0>; + silabs,wakeup-pins = <5>; status = "disabled"; }; @@ -483,6 +495,8 @@ reg = <0x5003c060 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <3>, <4>; + silabs,wakeup-pins = <1>, <3>; status = "disabled"; }; @@ -491,6 +505,8 @@ reg = <0x5003c090 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <6>, <7>, <8>; + silabs,wakeup-pins = <0>, <5>, <7>; status = "disabled"; }; @@ -499,6 +515,8 @@ reg = <0x5003c0c0 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <9>, <10>; + silabs,wakeup-pins = <2>, <5>; status = "disabled"; }; }; diff --git a/dts/arm/silabs/xg29/xg29.dtsi b/dts/arm/silabs/xg29/xg29.dtsi index db155d3dac475..161b430cc50b9 100644 --- a/dts/arm/silabs/xg29/xg29.dtsi +++ b/dts/arm/silabs/xg29/xg29.dtsi @@ -146,7 +146,7 @@ device_type = "cpu"; compatible = "arm,cortex-m33"; reg = <0>; - cpu-power-states = <&pstate_em1 &pstate_em2>; + cpu-power-states = <&pstate_em1 &pstate_em2 &pstate_em4>; /* * The minimum residency and exit latency is * managed by sl_power_manager on S2 devices. @@ -179,6 +179,16 @@ compatible = "zephyr,power-state"; power-state-name = "suspend-to-idle"; }; + + /* + * EM4 is a shutdown state where all clocks are off. The device + * is reset on wakeup from EM4. + */ + pstate_em4: em4 { + compatible = "zephyr,power-state"; + power-state-name = "soft-off"; + status = "disabled"; + }; }; }; @@ -293,6 +303,8 @@ reg = <0x5003C030 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <0>; + silabs,wakeup-pins = <5>; status = "disabled"; }; @@ -301,6 +313,8 @@ reg = <0x5003C060 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <3>, <4>; + silabs,wakeup-pins = <1>, <3>; status = "disabled"; }; @@ -309,6 +323,8 @@ reg = <0x5003C090 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <6>, <7>, <8>; + silabs,wakeup-pins = <0>, <5>, <7>; status = "disabled"; }; @@ -317,6 +333,8 @@ reg = <0x5003C0C0 0x30>; gpio-controller; #gpio-cells = <2>; + silabs,wakeup-ints = <9>; + silabs,wakeup-pins = <2>; status = "disabled"; }; }; diff --git a/dts/bindings/gpio/silabs,gpio-port.yaml b/dts/bindings/gpio/silabs,gpio-port.yaml index 25aa379171bab..9462ef85b32ef 100644 --- a/dts/bindings/gpio/silabs,gpio-port.yaml +++ b/dts/bindings/gpio/silabs,gpio-port.yaml @@ -14,6 +14,18 @@ properties: "#gpio-cells": const: 2 + silabs,wakeup-ints: + type: array + description: | + List of EM4 wakeup interrupt numbers for this port. The corresponding entry + in `silabs,wakeup-pins` indicates the pin associated with the interrupt number. + + silabs,wakeup-pins: + type: array + description: | + List of EM4 wakeup capable pins for this port. The corresponding entry in + `silabs,wakeup-ints` indicates the interrupt number associated with the pin. + gpio-cells: - pin - flags From 761b73ffbcef9ce6871c405c622e678bd0fc57ee Mon Sep 17 00:00:00 2001 From: Aksel Skauge Mellbye Date: Wed, 27 Aug 2025 17:24:22 +0200 Subject: [PATCH 2/2] drivers: gpio: silabs: Support EM4 wakeup interrupts EM4 wakeup interrupts are dedicated interrupts tied to specific pins that enable wakeup from EM4 through reset. Add support for using these interrupts instead of the regular interrupts when the GPIO_INT_TRIG_WAKE flag is set. Since it's not possible to tell what pin is associated with what EM4WU interrupt at runtime, the driver must store a mapping table sourced from device tree. Signed-off-by: Aksel Skauge Mellbye --- drivers/gpio/gpio_silabs.c | 94 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio_silabs.c b/drivers/gpio/gpio_silabs.c index b2935c9555185..a3b1f2fd7c636 100644 --- a/drivers/gpio/gpio_silabs.c +++ b/drivers/gpio/gpio_silabs.c @@ -22,8 +22,11 @@ LOG_MODULE_REGISTER(gpio_silabs, CONFIG_GPIO_LOG_LEVEL); #define GET_SILABS_GPIO_INDEX(node_id) \ (DT_REG_ADDR(node_id) - DT_REG_ADDR(DT_NODELABEL(gpioa))) / SILABS_GPIO_PORT_ADDR_SPACE_SIZE -#define NUMBER_OF_PORTS (SIZEOF_FIELD(GPIO_TypeDef, P) / SIZEOF_FIELD(GPIO_TypeDef, P[0])) -#define NUM_IRQ_LINES 16 +#define NUMBER_OF_PORTS (SIZEOF_FIELD(GPIO_TypeDef, P) / SIZEOF_FIELD(GPIO_TypeDef, P[0])) +#define NUM_IRQ_LINES 16 +#define MAX_EM4_IRQ_PER_PORT 3 +#define EM4WU_TO_INT(wu) ((wu) + NUM_IRQ_LINES) +#define INT_TO_EM4WU(int_no) ((int_no) - NUM_IRQ_LINES) struct gpio_silabs_common_config { /* IRQ configuration function */ @@ -39,6 +42,11 @@ struct gpio_silabs_common_data { const struct device *ports[NUMBER_OF_PORTS]; }; +struct gpio_silabs_em4wu_mapping { + uint8_t wu_no; + uint8_t pin; +}; + struct gpio_silabs_port_config { /* gpio_driver_config must be first */ struct gpio_driver_config common; @@ -46,6 +54,10 @@ struct gpio_silabs_port_config { sl_gpio_port_t gpio_index; /* pointer to common device */ const struct device *common_dev; + /* Number of valid EM4 wakeup interrupt mappings */ + int em4wu_pin_count; + /* EM4 wakeup interrupt mapping for GPIO pins */ + struct gpio_silabs_em4wu_mapping em4wu_pins[MAX_EM4_IRQ_PER_PORT]; }; struct gpio_silabs_port_data { @@ -253,6 +265,38 @@ static int interrupt_to_pin(int int_no) return ROUND_DOWN(int_no, 4) + FIELD_GET(0xF << (offset * 4), reg); } +static int gpio_silabs_pin_interrupt_configure_em4wu(sl_gpio_t *gpio, enum gpio_int_mode mode, + enum gpio_int_trig trig) +{ + int32_t em4wu_no = sl_hal_gpio_get_em4_interrupt_number(gpio); + int32_t int_no = EM4WU_TO_INT(em4wu_no); + + if (em4wu_no == SL_GPIO_INTERRUPT_UNAVAILABLE) { + LOG_ERR("Pin %u is not EM4 wakeup capable", gpio->pin); + return -EINVAL; + } + + if (mode != GPIO_INT_MODE_DISABLED) { + if (trig == GPIO_INT_TRIG_BOTH) { + LOG_ERR("EM4 wakeup interrupt on pin %u can only trigger on one edge", + gpio->pin); + return -ENOTSUP; + } + + sl_hal_gpio_configure_wakeup_em4_external_interrupt(gpio, em4wu_no, + trig == GPIO_INT_TRIG_HIGH); + } + + if (mode == GPIO_INT_MODE_DISABLED) { + sl_hal_gpio_disable_interrupts(BIT(int_no)); + sl_hal_gpio_disable_pin_em4_wakeup(BIT(int_no)); + } else { + sl_hal_gpio_enable_interrupts(BIT(int_no)); + } + + return 0; +} + static int gpio_silabs_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin, enum gpio_int_mode mode, enum gpio_int_trig trig) { @@ -264,12 +308,20 @@ static int gpio_silabs_pin_interrupt_configure(const struct device *dev, gpio_pi sl_gpio_interrupt_flag_t flag = SL_GPIO_INTERRUPT_RISING_FALLING_EDGE; uint32_t enabled_interrupts; int32_t int_no = SL_GPIO_INTERRUPT_UNAVAILABLE; + bool em4_wakeup; + + em4_wakeup = ((trig & GPIO_INT_WAKEUP) == GPIO_INT_WAKEUP); + trig &= ~GPIO_INT_WAKEUP; if (mode == GPIO_INT_MODE_LEVEL) { LOG_ERR("Level interrupt not supported on pin %u", pin); return -ENOTSUP; } + if (em4_wakeup) { + return gpio_silabs_pin_interrupt_configure_em4wu(&gpio, mode, trig); + } + enabled_interrupts = sl_hal_gpio_get_enabled_interrupts(); for (int i = 0; i < NUM_IRQ_LINES; ++i) { @@ -315,6 +367,25 @@ static int gpio_silabs_port_manage_callback(const struct device *dev, struct gpi return gpio_manage_callback(&data->callbacks, cb, set); } +static void gpio_silabs_em4wu_interrupt_to_port_pin(struct gpio_silabs_common_data *data, + int int_no, int *port, int *pin) +{ + ARRAY_FOR_EACH(data->ports, p) { + const struct device *dev = data->ports[p]; + const struct gpio_silabs_port_config *config = dev->config; + + for (int i = 0; i < config->em4wu_pin_count; i++) { + const struct gpio_silabs_em4wu_mapping *em4wu = &config->em4wu_pins[i]; + + if (em4wu->wu_no == INT_TO_EM4WU(int_no)) { + *port = config->gpio_index; + *pin = em4wu->pin; + return; + } + } + } +} + static void gpio_silabs_common_isr(const struct device *dev) { struct gpio_silabs_common_data *data = dev->data; @@ -323,8 +394,15 @@ static void gpio_silabs_common_isr(const struct device *dev) while (pending) { int int_no = find_lsb_set(pending) - 1; - int port = interrupt_to_port(int_no); - int pin = interrupt_to_pin(int_no); + int port = -1; + int pin = -1; + + if (int_no >= NUM_IRQ_LINES) { + gpio_silabs_em4wu_interrupt_to_port_pin(data, int_no, &port, &pin); + } else { + port = interrupt_to_port(int_no); + pin = interrupt_to_pin(int_no); + } port_pin_masks[port] |= BIT(pin); sl_hal_gpio_clear_interrupts(BIT(int_no)); @@ -385,11 +463,19 @@ static int gpio_silabs_common_init(const struct device *dev) return 0; } +#define EM4_WAKEUP_PIN(node, prop, idx) \ + { \ + .wu_no = DT_PROP_BY_IDX(node, prop, idx), \ + .pin = DT_PROP_BY_IDX(node, silabs_wakeup_pins, idx), \ + }, + #define GPIO_PORT_INIT(n) \ static const struct gpio_silabs_port_config gpio_silabs_port_config_##n = { \ .common.port_pin_mask = (gpio_port_pins_t)(-1), \ .gpio_index = GET_SILABS_GPIO_INDEX(n), \ .common_dev = DEVICE_DT_GET(DT_PARENT(n)), \ + .em4wu_pin_count = DT_PROP_LEN(n, silabs_wakeup_ints), \ + .em4wu_pins = {DT_FOREACH_PROP_ELEM(n, silabs_wakeup_ints, EM4_WAKEUP_PIN)}, \ }; \ static struct gpio_silabs_port_data gpio_silabs_port_data_##n; \ DEVICE_DT_DEFINE(n, gpio_silabs_port_init, NULL, &gpio_silabs_port_data_##n, \