/*
 * 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;
		}

		if (pin_cfg.out_state == 0) {
			flags |= GPIO_OUTPUT_INIT_LOW;
		} else {
			flags |= GPIO_OUTPUT_INIT_HIGH;
		}
	} 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;
	}

	*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);
