|  | /* | 
|  | * Copyright (c) 2016 Open-RnD Sp. z o.o. | 
|  | * Copyright (C) 2025 Savoir-faire Linux, Inc. | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT st_stm32_gpio | 
|  |  | 
|  | #include <errno.h> | 
|  |  | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/device.h> | 
|  | #include <soc.h> | 
|  | #include <stm32_ll_bus.h> | 
|  | #include <stm32_ll_exti.h> | 
|  | #include <stm32_ll_gpio.h> | 
|  | #include <stm32_ll_pwr.h> | 
|  | #include <zephyr/drivers/gpio.h> | 
|  | #include <zephyr/drivers/clock_control/stm32_clock_control.h> | 
|  | #include <zephyr/sys/util.h> | 
|  | #include <zephyr/drivers/interrupt_controller/gpio_intc_stm32.h> | 
|  | #include <zephyr/pm/device.h> | 
|  | #include <zephyr/pm/device_runtime.h> | 
|  | #include <zephyr/drivers/misc/stm32_wkup_pins/stm32_wkup_pins.h> | 
|  | #include <zephyr/dt-bindings/gpio/stm32-gpio.h> | 
|  |  | 
|  | #include "stm32_hsem.h" | 
|  | #include "gpio_stm32.h" | 
|  | #include <zephyr/drivers/gpio/gpio_utils.h> | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  |  | 
|  | LOG_MODULE_REGISTER(stm32, CONFIG_GPIO_LOG_LEVEL); | 
|  |  | 
|  | /** | 
|  | * @brief Common GPIO driver for STM32 MCUs. | 
|  | */ | 
|  |  | 
|  | /** | 
|  | * @brief EXTI interrupt callback | 
|  | */ | 
|  | static void gpio_stm32_isr(gpio_port_pins_t pin, void *arg) | 
|  | { | 
|  | struct gpio_stm32_data *data = arg; | 
|  |  | 
|  | gpio_fire_callbacks(&data->cb, data->dev, pin); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Common gpio flags to custom flags | 
|  | */ | 
|  | static int gpio_stm32_flags_to_conf(gpio_flags_t flags, uint32_t *pincfg) | 
|  | { | 
|  |  | 
|  | if ((flags & GPIO_OUTPUT) != 0) { | 
|  | /* Output only or Output/Input */ | 
|  |  | 
|  | *pincfg = STM32_PINCFG_MODE_OUTPUT; | 
|  |  | 
|  | if ((flags & GPIO_SINGLE_ENDED) != 0) { | 
|  | if (flags & GPIO_LINE_OPEN_DRAIN) { | 
|  | *pincfg |= STM32_PINCFG_OPEN_DRAIN; | 
|  | } else  { | 
|  | /* Output can't be open source */ | 
|  | return -ENOTSUP; | 
|  | } | 
|  | } else { | 
|  | *pincfg |= STM32_PINCFG_PUSH_PULL; | 
|  | } | 
|  |  | 
|  | if ((flags & GPIO_PULL_UP) != 0) { | 
|  | *pincfg |= STM32_PINCFG_PULL_UP; | 
|  | } else if ((flags & GPIO_PULL_DOWN) != 0) { | 
|  | *pincfg |= STM32_PINCFG_PULL_DOWN; | 
|  | } | 
|  |  | 
|  | } else if  ((flags & GPIO_INPUT) != 0) { | 
|  | /* Input */ | 
|  |  | 
|  | *pincfg = STM32_PINCFG_MODE_INPUT; | 
|  |  | 
|  | if ((flags & GPIO_PULL_UP) != 0) { | 
|  | *pincfg |= STM32_PINCFG_PULL_UP; | 
|  | } else if ((flags & GPIO_PULL_DOWN) != 0) { | 
|  | *pincfg |= STM32_PINCFG_PULL_DOWN; | 
|  | } else { | 
|  | *pincfg |= STM32_PINCFG_FLOATING; | 
|  | } | 
|  | } else { | 
|  | /* Deactivated: Analog */ | 
|  | *pincfg = STM32_PINCFG_MODE_ANALOG; | 
|  | } | 
|  |  | 
|  | #if !defined(CONFIG_SOC_SERIES_STM32F1X) | 
|  | switch (flags & (STM32_GPIO_SPEED_MASK << STM32_GPIO_SPEED_SHIFT)) { | 
|  | case STM32_GPIO_VERY_HIGH_SPEED: | 
|  | *pincfg |= STM32_OSPEEDR_VERY_HIGH_SPEED; | 
|  | break; | 
|  | case STM32_GPIO_HIGH_SPEED: | 
|  | *pincfg |= STM32_OSPEEDR_HIGH_SPEED; | 
|  | break; | 
|  | case STM32_GPIO_MEDIUM_SPEED: | 
|  | *pincfg |= STM32_OSPEEDR_MEDIUM_SPEED; | 
|  | break; | 
|  | default: | 
|  | *pincfg |= STM32_OSPEEDR_LOW_SPEED; | 
|  | break; | 
|  | } | 
|  | #endif /* !CONFIG_SOC_SERIES_STM32F1X */ | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #if defined(CONFIG_GPIO_GET_CONFIG) && !defined(CONFIG_SOC_SERIES_STM32F1X) | 
|  | /** | 
|  | * @brief Custom stm32 flags to zephyr | 
|  | */ | 
|  | static int gpio_stm32_pincfg_to_flags(struct gpio_stm32_pin pin_cfg, | 
|  | gpio_flags_t *out_flags) | 
|  | { | 
|  | gpio_flags_t flags = 0; | 
|  |  | 
|  | if (pin_cfg.mode == LL_GPIO_MODE_OUTPUT) { | 
|  | flags |= GPIO_OUTPUT; | 
|  | if (pin_cfg.type == LL_GPIO_OUTPUT_OPENDRAIN) { | 
|  | flags |= GPIO_OPEN_DRAIN; | 
|  | } | 
|  | } else if (pin_cfg.mode == LL_GPIO_MODE_INPUT) { | 
|  | flags |= GPIO_INPUT; | 
|  | #ifdef CONFIG_SOC_SERIES_STM32F1X | 
|  | } else if (pin_cfg.mode == LL_GPIO_MODE_FLOATING) { | 
|  | flags |= GPIO_INPUT; | 
|  | #endif | 
|  | } else { | 
|  | flags |= GPIO_DISCONNECTED; | 
|  | } | 
|  |  | 
|  | if (pin_cfg.pupd == LL_GPIO_PULL_UP) { | 
|  | flags |= GPIO_PULL_UP; | 
|  | } else if (pin_cfg.pupd == LL_GPIO_PULL_DOWN) { | 
|  | flags |= GPIO_PULL_DOWN; | 
|  | } | 
|  |  | 
|  | if (pin_cfg.out_state != 0) { | 
|  | flags |= GPIO_OUTPUT_HIGH; | 
|  | } else { | 
|  | flags |= GPIO_OUTPUT_LOW; | 
|  | } | 
|  |  | 
|  | *out_flags = flags; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  | #endif /* CONFIG_GPIO_GET_CONFIG */ | 
|  |  | 
|  | /** | 
|  | * @brief Translate pin to pinval that the LL library needs | 
|  | */ | 
|  | static inline uint32_t stm32_pinval_get(gpio_pin_t pin) | 
|  | { | 
|  | uint32_t pinval; | 
|  |  | 
|  | #ifdef CONFIG_SOC_SERIES_STM32F1X | 
|  | pinval = (1 << pin) << GPIO_PIN_MASK_POS; | 
|  | if (pin < 8) { | 
|  | pinval |= 1 << pin; | 
|  | } else { | 
|  | pinval |= (1 << (pin % 8)) | 0x04000000; | 
|  | } | 
|  | #else | 
|  | pinval = 1 << pin; | 
|  | #endif | 
|  | return pinval; | 
|  | } | 
|  |  | 
|  | static inline void ll_gpio_set_pin_pull(GPIO_TypeDef *GPIOx, uint32_t Pin, uint32_t Pull) | 
|  | { | 
|  | #if defined(CONFIG_SOC_SERIES_STM32WB0X) | 
|  | /* On STM32WB0, the PWRC PU/PD control registers should be used instead | 
|  | * of the GPIO controller registers, so we cannot use LL_GPIO_SetPinPull. | 
|  | */ | 
|  | const uint32_t gpio = (GPIOx == GPIOA) ? LL_PWR_GPIO_A : LL_PWR_GPIO_B; | 
|  |  | 
|  | if (Pull == LL_GPIO_PULL_UP) { | 
|  | LL_PWR_EnableGPIOPullUp(gpio, Pin); | 
|  | LL_PWR_DisableGPIOPullDown(gpio, Pin); | 
|  | } else if (Pull == LL_GPIO_PULL_DOWN) { | 
|  | LL_PWR_EnableGPIOPullDown(gpio, Pin); | 
|  | LL_PWR_DisableGPIOPullUp(gpio, Pin); | 
|  | } else if (Pull == LL_GPIO_PULL_NO) { | 
|  | LL_PWR_DisableGPIOPullUp(gpio, Pin); | 
|  | LL_PWR_DisableGPIOPullDown(gpio, Pin); | 
|  | } | 
|  | #else | 
|  | LL_GPIO_SetPinPull(GPIOx, Pin, Pull); | 
|  | #endif /* CONFIG_SOC_SERIES_STM32WB0X */ | 
|  | } | 
|  |  | 
|  | __maybe_unused static inline uint32_t ll_gpio_get_pin_pull(GPIO_TypeDef *GPIOx, uint32_t Pin) | 
|  | { | 
|  | #if defined(CONFIG_SOC_SERIES_STM32WB0X) | 
|  | /* On STM32WB0, the PWRC PU/PD control registers should be used instead | 
|  | * of the GPIO controller registers, so we cannot use LL_GPIO_GetPinPull. | 
|  | */ | 
|  | const uint32_t gpio = (GPIOx == GPIOA) ? LL_PWR_GPIO_A : LL_PWR_GPIO_B; | 
|  |  | 
|  | if (LL_PWR_IsEnabledGPIOPullDown(gpio, Pin)) { | 
|  | return LL_GPIO_PULL_DOWN; | 
|  | } else if (LL_PWR_IsEnabledGPIOPullUp(gpio, Pin)) { | 
|  | return LL_GPIO_PULL_UP; | 
|  | } else { | 
|  | return LL_GPIO_PULL_NO; | 
|  | } | 
|  | #else | 
|  | return LL_GPIO_GetPinPull(GPIOx, Pin); | 
|  | #endif /* CONFIG_SOC_SERIES_STM32WB0X */ | 
|  | } | 
|  |  | 
|  | static inline void gpio_stm32_disable_pin_irqs(uint32_t port, gpio_pin_t pin) | 
|  | { | 
|  | #if defined(CONFIG_EXTI_STM32) | 
|  | if (port != stm32_exti_get_line_src_port(pin)) { | 
|  | /* EXTI line not owned by this port - do nothing */ | 
|  | return; | 
|  | } | 
|  | #endif | 
|  | stm32_gpio_irq_line_t irq_line = stm32_gpio_intc_get_pin_irq_line(port, pin); | 
|  |  | 
|  | stm32_gpio_intc_disable_line(irq_line); | 
|  | stm32_gpio_intc_remove_irq_callback(irq_line); | 
|  | stm32_gpio_intc_select_line_trigger(irq_line, STM32_GPIO_IRQ_TRIG_NONE); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Configure the hardware. | 
|  | */ | 
|  | static void gpio_stm32_configure_raw(const struct device *dev, gpio_pin_t pin, | 
|  | uint32_t conf, uint32_t func) | 
|  | { | 
|  | const struct gpio_stm32_config *cfg = dev->config; | 
|  | GPIO_TypeDef *gpio = (GPIO_TypeDef *)cfg->base; | 
|  |  | 
|  | uint32_t pin_ll = stm32_pinval_get(pin); | 
|  |  | 
|  | #ifdef CONFIG_SOC_SERIES_STM32F1X | 
|  | ARG_UNUSED(func); | 
|  |  | 
|  | uint32_t temp = conf & | 
|  | (STM32_MODE_INOUT_MASK << STM32_MODE_INOUT_SHIFT); | 
|  |  | 
|  | if (temp == STM32_MODE_INPUT) { | 
|  | temp = conf & (STM32_CNF_IN_MASK << STM32_CNF_IN_SHIFT); | 
|  |  | 
|  | if (temp == STM32_CNF_IN_ANALOG) { | 
|  | LL_GPIO_SetPinMode(gpio, pin_ll, LL_GPIO_MODE_ANALOG); | 
|  | } else if (temp == STM32_CNF_IN_FLOAT) { | 
|  | LL_GPIO_SetPinMode(gpio, pin_ll, LL_GPIO_MODE_FLOATING); | 
|  | } else { | 
|  | temp = conf & (STM32_PUPD_MASK << STM32_PUPD_SHIFT); | 
|  |  | 
|  | if (temp == STM32_PUPD_PULL_UP) { | 
|  | LL_GPIO_SetPinPull(gpio, pin_ll, | 
|  | LL_GPIO_PULL_UP); | 
|  | } else { | 
|  | LL_GPIO_SetPinPull(gpio, pin_ll, | 
|  | LL_GPIO_PULL_DOWN); | 
|  | } | 
|  |  | 
|  | LL_GPIO_SetPinMode(gpio, pin_ll, LL_GPIO_MODE_INPUT); | 
|  | } | 
|  |  | 
|  | } else { | 
|  | temp = conf & (STM32_CNF_OUT_1_MASK << STM32_CNF_OUT_1_SHIFT); | 
|  |  | 
|  | if (temp == STM32_CNF_GP_OUTPUT) { | 
|  | LL_GPIO_SetPinMode(gpio, pin_ll, LL_GPIO_MODE_OUTPUT); | 
|  | } else { | 
|  | LL_GPIO_SetPinMode(gpio, pin_ll, | 
|  | LL_GPIO_MODE_ALTERNATE); | 
|  | } | 
|  |  | 
|  | temp = conf & (STM32_CNF_OUT_0_MASK << STM32_CNF_OUT_0_SHIFT); | 
|  |  | 
|  | if (temp == STM32_CNF_PUSH_PULL) { | 
|  | LL_GPIO_SetPinOutputType(gpio, pin_ll, | 
|  | LL_GPIO_OUTPUT_PUSHPULL); | 
|  | } else { | 
|  | LL_GPIO_SetPinOutputType(gpio, pin_ll, | 
|  | LL_GPIO_OUTPUT_OPENDRAIN); | 
|  | } | 
|  |  | 
|  | temp = conf & | 
|  | (STM32_MODE_OSPEED_MASK << STM32_MODE_OSPEED_SHIFT); | 
|  |  | 
|  | if (temp == STM32_MODE_OUTPUT_MAX_2) { | 
|  | LL_GPIO_SetPinSpeed(gpio, pin_ll, | 
|  | LL_GPIO_SPEED_FREQ_LOW); | 
|  | } else if (temp == STM32_MODE_OUTPUT_MAX_10) { | 
|  | LL_GPIO_SetPinSpeed(gpio, pin_ll, | 
|  | LL_GPIO_SPEED_FREQ_MEDIUM); | 
|  | } else { | 
|  | LL_GPIO_SetPinSpeed(gpio, pin_ll, | 
|  | LL_GPIO_SPEED_FREQ_HIGH); | 
|  | } | 
|  | } | 
|  | #else | 
|  | uint32_t mode, otype, ospeed, pupd; | 
|  |  | 
|  | mode = conf & (STM32_MODER_MASK << STM32_MODER_SHIFT); | 
|  | otype = conf & (STM32_OTYPER_MASK << STM32_OTYPER_SHIFT); | 
|  | ospeed = conf & (STM32_OSPEEDR_MASK << STM32_OSPEEDR_SHIFT); | 
|  | pupd = conf & (STM32_PUPDR_MASK << STM32_PUPDR_SHIFT); | 
|  |  | 
|  | z_stm32_hsem_lock(CFG_HW_GPIO_SEMID, HSEM_LOCK_DEFAULT_RETRY); | 
|  |  | 
|  | #if defined(CONFIG_SOC_SERIES_STM32L4X) && defined(GPIO_ASCR_ASC0) | 
|  | /* | 
|  | * For STM32L47xx/48xx, register ASCR should be configured to connect | 
|  | * analog switch of gpio lines to the ADC. | 
|  | */ | 
|  | if (mode == STM32_MODER_ANALOG_MODE) { | 
|  | LL_GPIO_EnablePinAnalogControl(gpio, pin_ll); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | LL_GPIO_SetPinOutputType(gpio, pin_ll, otype >> STM32_OTYPER_SHIFT); | 
|  |  | 
|  | LL_GPIO_SetPinSpeed(gpio, pin_ll, ospeed >> STM32_OSPEEDR_SHIFT); | 
|  |  | 
|  | ll_gpio_set_pin_pull(gpio, pin_ll, pupd >> STM32_PUPDR_SHIFT); | 
|  |  | 
|  | if (mode == STM32_MODER_ALT_MODE) { | 
|  | if (pin < 8) { | 
|  | LL_GPIO_SetAFPin_0_7(gpio, pin_ll, func); | 
|  | } else { | 
|  | LL_GPIO_SetAFPin_8_15(gpio, pin_ll, func); | 
|  | } | 
|  | } | 
|  |  | 
|  | LL_GPIO_SetPinMode(gpio, pin_ll, mode >> STM32_MODER_SHIFT); | 
|  |  | 
|  | z_stm32_hsem_unlock(CFG_HW_GPIO_SEMID); | 
|  | #endif  /* CONFIG_SOC_SERIES_STM32F1X */ | 
|  |  | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GPIO port clock handling | 
|  | */ | 
|  | static int gpio_stm32_clock_request(const struct device *dev, bool on) | 
|  | { | 
|  | const struct gpio_stm32_config *cfg = dev->config; | 
|  | int ret; | 
|  |  | 
|  | __ASSERT_NO_MSG(dev != NULL); | 
|  |  | 
|  | /* enable clock for subsystem */ | 
|  | const struct device *const clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); | 
|  |  | 
|  | if (on) { | 
|  | ret = clock_control_on(clk, | 
|  | (clock_control_subsys_t)&cfg->pclken); | 
|  | } else { | 
|  | ret = clock_control_off(clk, | 
|  | (clock_control_subsys_t)&cfg->pclken); | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int gpio_stm32_port_get_raw(const struct device *dev, uint32_t *value) | 
|  | { | 
|  | const struct gpio_stm32_config *cfg = dev->config; | 
|  | GPIO_TypeDef *gpio = (GPIO_TypeDef *)cfg->base; | 
|  |  | 
|  | *value = LL_GPIO_ReadInputPort(gpio); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int gpio_stm32_port_set_masked_raw(const struct device *dev, | 
|  | gpio_port_pins_t mask, | 
|  | gpio_port_value_t value) | 
|  | { | 
|  | const struct gpio_stm32_config *cfg = dev->config; | 
|  | GPIO_TypeDef *gpio = (GPIO_TypeDef *)cfg->base; | 
|  | uint32_t port_value; | 
|  |  | 
|  | z_stm32_hsem_lock(CFG_HW_GPIO_SEMID, HSEM_LOCK_DEFAULT_RETRY); | 
|  |  | 
|  | port_value = LL_GPIO_ReadOutputPort(gpio); | 
|  | LL_GPIO_WriteOutputPort(gpio, (port_value & ~mask) | (mask & value)); | 
|  |  | 
|  | z_stm32_hsem_unlock(CFG_HW_GPIO_SEMID); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int gpio_stm32_port_set_bits_raw(const struct device *dev, | 
|  | gpio_port_pins_t pins) | 
|  | { | 
|  | const struct gpio_stm32_config *cfg = dev->config; | 
|  | GPIO_TypeDef *gpio = (GPIO_TypeDef *)cfg->base; | 
|  |  | 
|  | /* | 
|  | * On F1 series, using LL API requires a costly pin mask translation. | 
|  | * Skip it and use CMSIS API directly. Valid also on other series. | 
|  | */ | 
|  | WRITE_REG(gpio->BSRR, pins); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int gpio_stm32_port_clear_bits_raw(const struct device *dev, | 
|  | gpio_port_pins_t pins) | 
|  | { | 
|  | const struct gpio_stm32_config *cfg = dev->config; | 
|  | GPIO_TypeDef *gpio = (GPIO_TypeDef *)cfg->base; | 
|  |  | 
|  | #ifdef CONFIG_SOC_SERIES_STM32F1X | 
|  | /* | 
|  | * On F1 series, using LL API requires a costly pin mask translation. | 
|  | * Skip it and use CMSIS API directly. | 
|  | */ | 
|  | WRITE_REG(gpio->BRR, pins); | 
|  | #else | 
|  | /* On other series, LL abstraction is needed  */ | 
|  | LL_GPIO_ResetOutputPin(gpio, pins); | 
|  | #endif | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int gpio_stm32_port_toggle_bits(const struct device *dev, | 
|  | gpio_port_pins_t pins) | 
|  | { | 
|  | const struct gpio_stm32_config *cfg = dev->config; | 
|  | GPIO_TypeDef *gpio = (GPIO_TypeDef *)cfg->base; | 
|  |  | 
|  | /* | 
|  | * On F1 series, using LL API requires a costly pin mask translation. | 
|  | * Skip it and use CMSIS API directly. Valid also on other series. | 
|  | */ | 
|  | z_stm32_hsem_lock(CFG_HW_GPIO_SEMID, HSEM_LOCK_DEFAULT_RETRY); | 
|  | WRITE_REG(gpio->ODR, READ_REG(gpio->ODR) ^ pins); | 
|  | z_stm32_hsem_unlock(CFG_HW_GPIO_SEMID); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_SOC_SERIES_STM32F1X | 
|  | #define IS_GPIO_OUT GPIO_OUT | 
|  | #else | 
|  | #define IS_GPIO_OUT STM32_GPIO | 
|  | #endif | 
|  |  | 
|  | int gpio_stm32_configure(const struct device *dev, gpio_pin_t pin, uint32_t conf, uint32_t func) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | ret = pm_device_runtime_get(dev); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | gpio_stm32_configure_raw(dev, pin, conf, func); | 
|  |  | 
|  | if (func == IS_GPIO_OUT) { | 
|  | uint32_t gpio_out = conf & (STM32_ODR_MASK << STM32_ODR_SHIFT); | 
|  |  | 
|  | if (gpio_out == STM32_ODR_1) { | 
|  | gpio_stm32_port_set_bits_raw(dev, BIT(pin)); | 
|  | } else if (gpio_out == STM32_ODR_0) { | 
|  | gpio_stm32_port_clear_bits_raw(dev, BIT(pin)); | 
|  | } | 
|  | } | 
|  |  | 
|  | return pm_device_runtime_put(dev); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Configure pin or port | 
|  | */ | 
|  | static int gpio_stm32_config(const struct device *dev, | 
|  | gpio_pin_t pin, gpio_flags_t flags) | 
|  | { | 
|  | int err; | 
|  | uint32_t pincfg; | 
|  | struct gpio_stm32_data *data = dev->data; | 
|  |  | 
|  | /* figure out if we can map the requested GPIO | 
|  | * configuration | 
|  | */ | 
|  | err = gpio_stm32_flags_to_conf(flags, &pincfg); | 
|  | if (err != 0) { | 
|  | return err; | 
|  | } | 
|  |  | 
|  | /* Enable device clock before configuration (requires bank writes) */ | 
|  | if ((((flags & GPIO_OUTPUT) != 0) || ((flags & GPIO_INPUT) != 0)) && | 
|  | !(data->pin_has_clock_enabled & BIT(pin))) { | 
|  | err = pm_device_runtime_get(dev); | 
|  | if (err < 0) { | 
|  | return err; | 
|  | } | 
|  | data->pin_has_clock_enabled |= BIT(pin); | 
|  | } | 
|  |  | 
|  | if ((flags & GPIO_OUTPUT) != 0) { | 
|  | if ((flags & GPIO_OUTPUT_INIT_HIGH) != 0) { | 
|  | gpio_stm32_port_set_bits_raw(dev, BIT(pin)); | 
|  | } else if ((flags & GPIO_OUTPUT_INIT_LOW) != 0) { | 
|  | gpio_stm32_port_clear_bits_raw(dev, BIT(pin)); | 
|  | } | 
|  | } | 
|  |  | 
|  | gpio_stm32_configure_raw(dev, pin, pincfg, 0); | 
|  |  | 
|  | #ifdef CONFIG_STM32_WKUP_PINS | 
|  | if (flags & STM32_GPIO_WKUP) { | 
|  | #ifdef CONFIG_POWEROFF | 
|  | struct gpio_dt_spec gpio_dt_cfg = { | 
|  | .port = dev, | 
|  | .pin = pin, | 
|  | .dt_flags = (gpio_dt_flags_t)flags, | 
|  | }; | 
|  |  | 
|  | if (stm32_pwr_wkup_pin_cfg_gpio((const struct gpio_dt_spec *)&gpio_dt_cfg)) { | 
|  | LOG_ERR("Could not configure GPIO %s pin %d as a wake-up source", | 
|  | gpio_dt_cfg.port->name, gpio_dt_cfg.pin); | 
|  | } | 
|  | #else | 
|  | LOG_DBG("STM32_GPIO_WKUP flag has no effect when CONFIG_POWEROFF=n"); | 
|  | #endif /* CONFIG_POWEROFF */ | 
|  | } | 
|  | #endif /* CONFIG_STM32_WKUP_PINS */ | 
|  |  | 
|  | /* Decrement GPIO usage count only if pin is now disconnected after being connected */ | 
|  | if (((flags & GPIO_OUTPUT) == 0) && ((flags & GPIO_INPUT) == 0) && | 
|  | (data->pin_has_clock_enabled & BIT(pin))) { | 
|  | err = pm_device_runtime_put(dev); | 
|  | if (err < 0) { | 
|  | return err; | 
|  | } | 
|  | data->pin_has_clock_enabled &= ~BIT(pin); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #if defined(CONFIG_GPIO_GET_CONFIG) && !defined(CONFIG_SOC_SERIES_STM32F1X) | 
|  | /** | 
|  | * @brief Get configuration of pin | 
|  | */ | 
|  | static int gpio_stm32_get_config(const struct device *dev, | 
|  | gpio_pin_t pin, gpio_flags_t *flags) | 
|  | { | 
|  | const struct gpio_stm32_config *cfg = dev->config; | 
|  | GPIO_TypeDef *gpio = (GPIO_TypeDef *)cfg->base; | 
|  | struct gpio_stm32_pin pin_config; | 
|  | uint32_t pin_ll; | 
|  | int err; | 
|  |  | 
|  | err = pm_device_runtime_get(dev); | 
|  | if (err < 0) { | 
|  | return err; | 
|  | } | 
|  |  | 
|  | pin_ll = stm32_pinval_get(pin); | 
|  | pin_config.type = LL_GPIO_GetPinOutputType(gpio, pin_ll); | 
|  | pin_config.pupd = ll_gpio_get_pin_pull(gpio, pin_ll); | 
|  | pin_config.mode = LL_GPIO_GetPinMode(gpio, pin_ll); | 
|  | pin_config.out_state = LL_GPIO_IsOutputPinSet(gpio, pin_ll); | 
|  |  | 
|  | gpio_stm32_pincfg_to_flags(pin_config, flags); | 
|  |  | 
|  | return pm_device_runtime_put(dev); | 
|  | } | 
|  | #endif /* CONFIG_GPIO_GET_CONFIG */ | 
|  |  | 
|  | static int gpio_stm32_pin_interrupt_configure(const struct device *dev, | 
|  | gpio_pin_t pin, | 
|  | enum gpio_int_mode mode, | 
|  | enum gpio_int_trig trig) | 
|  | { | 
|  | const struct gpio_stm32_config *cfg = dev->config; | 
|  | struct gpio_stm32_data *data = dev->data; | 
|  | const stm32_gpio_irq_line_t irq_line = stm32_gpio_intc_get_pin_irq_line(cfg->port, pin); | 
|  | uint32_t irq_trigger = 0; | 
|  | int err = 0; | 
|  |  | 
|  | #ifdef CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT | 
|  | if (mode == GPIO_INT_MODE_DISABLE_ONLY) { | 
|  | stm32_gpio_intc_disable_line(irq_line); | 
|  | goto exit; | 
|  | } else if (mode == GPIO_INT_MODE_ENABLE_ONLY) { | 
|  | stm32_gpio_intc_enable_line(irq_line); | 
|  | goto exit; | 
|  | } | 
|  | #endif /* CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT */ | 
|  |  | 
|  | if (mode == GPIO_INT_MODE_DISABLED) { | 
|  | gpio_stm32_disable_pin_irqs(cfg->port, pin); | 
|  | goto exit; | 
|  | } | 
|  |  | 
|  | if (mode == GPIO_INT_MODE_LEVEL) { | 
|  | /* Level-sensitive interrupts are only supported on STM32WB0. */ | 
|  | if (!IS_ENABLED(CONFIG_SOC_SERIES_STM32WB0X)) { | 
|  | err = -ENOTSUP; | 
|  | goto exit; | 
|  | } else { | 
|  | switch (trig) { | 
|  | case GPIO_INT_TRIG_LOW: | 
|  | irq_trigger = STM32_GPIO_IRQ_TRIG_LOW_LEVEL; | 
|  | break; | 
|  | case GPIO_INT_TRIG_HIGH: | 
|  | irq_trigger = STM32_GPIO_IRQ_TRIG_HIGH_LEVEL; | 
|  | break; | 
|  | default: | 
|  | err = -EINVAL; | 
|  | goto exit; | 
|  | } | 
|  | } | 
|  | } else { | 
|  | switch (trig) { | 
|  | case GPIO_INT_TRIG_LOW: | 
|  | irq_trigger = STM32_GPIO_IRQ_TRIG_FALLING; | 
|  | break; | 
|  | case GPIO_INT_TRIG_HIGH: | 
|  | irq_trigger = STM32_GPIO_IRQ_TRIG_RISING; | 
|  | break; | 
|  | case GPIO_INT_TRIG_BOTH: | 
|  | irq_trigger = STM32_GPIO_IRQ_TRIG_BOTH; | 
|  | break; | 
|  | default: | 
|  | err = -EINVAL; | 
|  | goto exit; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (stm32_gpio_intc_set_irq_callback(irq_line, gpio_stm32_isr, data) != 0) { | 
|  | err = -EBUSY; | 
|  | goto exit; | 
|  | } | 
|  |  | 
|  | #if defined(CONFIG_EXTI_STM32) | 
|  | stm32_exti_set_line_src_port(pin, cfg->port); | 
|  | #endif | 
|  |  | 
|  | stm32_gpio_intc_select_line_trigger(irq_line, irq_trigger); | 
|  |  | 
|  | stm32_gpio_intc_enable_line(irq_line); | 
|  |  | 
|  | exit: | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int gpio_stm32_manage_callback(const struct device *dev, | 
|  | struct gpio_callback *callback, | 
|  | bool set) | 
|  | { | 
|  | struct gpio_stm32_data *data = dev->data; | 
|  |  | 
|  | return gpio_manage_callback(&data->cb, callback, set); | 
|  | } | 
|  |  | 
|  | static DEVICE_API(gpio, gpio_stm32_driver) = { | 
|  | .pin_configure = gpio_stm32_config, | 
|  | #if defined(CONFIG_GPIO_GET_CONFIG) && !defined(CONFIG_SOC_SERIES_STM32F1X) | 
|  | .pin_get_config = gpio_stm32_get_config, | 
|  | #endif /* CONFIG_GPIO_GET_CONFIG */ | 
|  | .port_get_raw = gpio_stm32_port_get_raw, | 
|  | .port_set_masked_raw = gpio_stm32_port_set_masked_raw, | 
|  | .port_set_bits_raw = gpio_stm32_port_set_bits_raw, | 
|  | .port_clear_bits_raw = gpio_stm32_port_clear_bits_raw, | 
|  | .port_toggle_bits = gpio_stm32_port_toggle_bits, | 
|  | .pin_interrupt_configure = gpio_stm32_pin_interrupt_configure, | 
|  | .manage_callback = gpio_stm32_manage_callback, | 
|  | }; | 
|  |  | 
|  | static int gpio_stm32_pm_action(const struct device *dev, | 
|  | enum pm_device_action action) | 
|  | { | 
|  | switch (action) { | 
|  | case PM_DEVICE_ACTION_RESUME: | 
|  | return gpio_stm32_clock_request(dev, true); | 
|  | case PM_DEVICE_ACTION_SUSPEND: | 
|  | return gpio_stm32_clock_request(dev, false); | 
|  | case PM_DEVICE_ACTION_TURN_OFF: | 
|  | case PM_DEVICE_ACTION_TURN_ON: | 
|  | break; | 
|  | default: | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * @brief Initialize GPIO port | 
|  | * | 
|  | * Perform basic initialization of a GPIO port. The code will | 
|  | * enable the clock for corresponding peripheral. | 
|  | * | 
|  | * @param dev GPIO device struct | 
|  | * | 
|  | * @return 0 | 
|  | */ | 
|  | __maybe_unused static int gpio_stm32_init(const struct device *dev) | 
|  | { | 
|  | struct gpio_stm32_data *data = dev->data; | 
|  |  | 
|  | data->dev = dev; | 
|  |  | 
|  | if (!device_is_ready(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE))) { | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | #if (defined(PWR_CR2_IOSV) || defined(PWR_SVMCR_IO2SV)) && \ | 
|  | DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(gpiog)) | 
|  | z_stm32_hsem_lock(CFG_HW_RCC_SEMID, HSEM_LOCK_DEFAULT_RETRY); | 
|  | /* Port G[15:2] requires external power supply */ | 
|  | /* Cf: L4/L5/U3 RM, Chapter "Independent I/O supply rail" */ | 
|  | #ifdef CONFIG_SOC_SERIES_STM32U3X | 
|  | LL_PWR_EnableVDDIO2(); | 
|  | #else | 
|  | LL_PWR_EnableVddIO2(); | 
|  | #endif | 
|  | z_stm32_hsem_unlock(CFG_HW_RCC_SEMID); | 
|  | #endif | 
|  |  | 
|  | return pm_device_driver_init(dev, gpio_stm32_pm_action); | 
|  | } | 
|  |  | 
|  | #define GPIO_DEVICE_INIT(__node, __suffix, __base_addr, __port, __cenr, __bus) \ | 
|  | static const struct gpio_stm32_config gpio_stm32_cfg_## __suffix = {   \ | 
|  | .common = {						       \ | 
|  | .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_NGPIOS(16U), \ | 
|  | },							       \ | 
|  | .base = (uint32_t *)__base_addr,				       \ | 
|  | .port = __port,						       \ | 
|  | COND_CODE_1(DT_NODE_HAS_PROP(__node, clocks),		       \ | 
|  | (.pclken = { .bus = __bus, .enr = __cenr },),       \ | 
|  | (/* Nothing if clocks not present */))	       \ | 
|  | };								       \ | 
|  | static struct gpio_stm32_data gpio_stm32_data_## __suffix;	       \ | 
|  | PM_DEVICE_DT_DEFINE(__node, gpio_stm32_pm_action);		       \ | 
|  | DEVICE_DT_DEFINE(__node,					       \ | 
|  | COND_CODE_1(DT_NODE_HAS_PROP(__node, clocks),      \ | 
|  | (gpio_stm32_init),		       \ | 
|  | (NULL)),			       \ | 
|  | PM_DEVICE_DT_GET(__node),			       \ | 
|  | &gpio_stm32_data_## __suffix,		       \ | 
|  | &gpio_stm32_cfg_## __suffix,		       \ | 
|  | PRE_KERNEL_1,				       \ | 
|  | CONFIG_GPIO_INIT_PRIORITY,			       \ | 
|  | &gpio_stm32_driver) | 
|  |  | 
|  | #define GPIO_DEVICE_INIT_STM32(__suffix, __SUFFIX)			\ | 
|  | GPIO_DEVICE_INIT(DT_NODELABEL(gpio##__suffix),	\ | 
|  | __suffix,					\ | 
|  | DT_REG_ADDR(DT_NODELABEL(gpio##__suffix)),	\ | 
|  | STM32_PORT##__SUFFIX,				\ | 
|  | DT_CLOCKS_CELL(DT_NODELABEL(gpio##__suffix), bits),\ | 
|  | DT_CLOCKS_CELL(DT_NODELABEL(gpio##__suffix), bus)) | 
|  |  | 
|  | #define GPIO_DEVICE_INIT_STM32_IF_OKAY(__suffix, __SUFFIX) \ | 
|  | COND_CODE_1(DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(gpio##__suffix)), \ | 
|  | (GPIO_DEVICE_INIT_STM32(__suffix, __SUFFIX)), \ | 
|  | ()) | 
|  |  | 
|  | GPIO_DEVICE_INIT_STM32_IF_OKAY(a, A); | 
|  | GPIO_DEVICE_INIT_STM32_IF_OKAY(b, B); | 
|  | GPIO_DEVICE_INIT_STM32_IF_OKAY(c, C); | 
|  | GPIO_DEVICE_INIT_STM32_IF_OKAY(d, D); | 
|  | GPIO_DEVICE_INIT_STM32_IF_OKAY(e, E); | 
|  | GPIO_DEVICE_INIT_STM32_IF_OKAY(f, F); | 
|  | GPIO_DEVICE_INIT_STM32_IF_OKAY(g, G); | 
|  | GPIO_DEVICE_INIT_STM32_IF_OKAY(h, H); | 
|  | GPIO_DEVICE_INIT_STM32_IF_OKAY(i, I); | 
|  | GPIO_DEVICE_INIT_STM32_IF_OKAY(j, J); | 
|  | GPIO_DEVICE_INIT_STM32_IF_OKAY(k, K); | 
|  |  | 
|  | GPIO_DEVICE_INIT_STM32_IF_OKAY(m, M); | 
|  | GPIO_DEVICE_INIT_STM32_IF_OKAY(n, N); | 
|  | GPIO_DEVICE_INIT_STM32_IF_OKAY(o, O); | 
|  | GPIO_DEVICE_INIT_STM32_IF_OKAY(p, P); | 
|  | GPIO_DEVICE_INIT_STM32_IF_OKAY(q, Q); |