/* * Copyright (c) 2016 Open-RnD Sp. z o.o. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT st_stm32_gpio #include #include #include #include #include #include #include #include #include #include #include "stm32_hsem.h" #include "gpio_stm32.h" #include "gpio_utils.h" /** * @brief Common GPIO driver for STM32 MCUs. */ /** * @brief EXTI interrupt callback */ static void gpio_stm32_isr(int line, void *arg) { struct device *dev = arg; struct gpio_stm32_data *data = dev->driver_data; gpio_fire_callbacks(&data->cb, dev, BIT(line)); } /** * @brief Common gpio flags to custom flags */ static int gpio_stm32_flags_to_conf(int flags, int *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 { /* Desactivated: Analog */ *pincfg = STM32_PINCFG_MODE_ANALOG; } return 0; } /** * @brief Translate pin to pinval that the LL library needs */ static inline uint32_t stm32_pinval_get(int 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; } /** * @brief Configure the hardware. */ int gpio_stm32_configure(uint32_t *base_addr, int pin, int conf, int altf) { GPIO_TypeDef *gpio = (GPIO_TypeDef *)base_addr; int pin_ll = stm32_pinval_get(pin); #ifdef CONFIG_SOC_SERIES_STM32F1X ARG_UNUSED(altf); 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 { LL_GPIO_SetPinMode(gpio, pin_ll, LL_GPIO_MODE_INPUT); 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); } } } 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 unsigned int 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); LL_GPIO_SetPinMode(gpio, pin_ll, mode >> STM32_MODER_SHIFT); if (STM32_MODER_ALT_MODE == mode) { if (pin < 8) { LL_GPIO_SetAFPin_0_7(gpio, pin_ll, altf); } else { LL_GPIO_SetAFPin_8_15(gpio, pin_ll, altf); } } #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_SetPinPull(gpio, pin_ll, pupd >> STM32_PUPDR_SHIFT); z_stm32_hsem_unlock(CFG_HW_GPIO_SEMID); #endif /* CONFIG_SOC_SERIES_STM32F1X */ return 0; } static inline uint32_t gpio_stm32_pin_to_exti_line(int pin) { #if defined(CONFIG_SOC_SERIES_STM32L0X) || \ defined(CONFIG_SOC_SERIES_STM32F0X) return ((pin % 4 * 4) << 16) | (pin / 4); #elif defined(CONFIG_SOC_SERIES_STM32MP1X) return (((pin * 8) % 32) << 16) | (pin / 4); #elif defined(CONFIG_SOC_SERIES_STM32G0X) || \ defined(CONFIG_SOC_SERIES_STM32L5X) return ((pin & 0x3) << (16 + 3)) | (pin >> 2); #else return (0xF << ((pin % 4 * 4) + 16)) | (pin / 4); #endif } static void gpio_stm32_set_exti_source(int port, int pin) { uint32_t line = gpio_stm32_pin_to_exti_line(pin); #if defined(CONFIG_SOC_SERIES_STM32L0X) && defined(LL_SYSCFG_EXTI_PORTH) /* * Ports F and G are not present on some STM32L0 parts, so * for these parts port H external interrupt should be enabled * by writing value 0x5 instead of 0x7. */ if (port == STM32_PORTH) { port = LL_SYSCFG_EXTI_PORTH; } #endif z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY); #ifdef CONFIG_SOC_SERIES_STM32F1X LL_GPIO_AF_SetEXTISource(port, line); #elif CONFIG_SOC_SERIES_STM32MP1X LL_EXTI_SetEXTISource(port, line); #elif defined(CONFIG_SOC_SERIES_STM32G0X) || \ defined(CONFIG_SOC_SERIES_STM32L5X) LL_EXTI_SetEXTISource(port, line); #else LL_SYSCFG_SetEXTISource(port, line); #endif z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID); } static int gpio_stm32_get_exti_source(int pin) { uint32_t line = gpio_stm32_pin_to_exti_line(pin); int port; #ifdef CONFIG_SOC_SERIES_STM32F1X port = LL_GPIO_AF_GetEXTISource(line); #elif CONFIG_SOC_SERIES_STM32MP1X port = LL_EXTI_GetEXTISource(line); #elif defined(CONFIG_SOC_SERIES_STM32G0X) || \ defined(CONFIG_SOC_SERIES_STM32L5X) port = LL_EXTI_GetEXTISource(line); #else port = LL_SYSCFG_GetEXTISource(line); #endif #if defined(CONFIG_SOC_SERIES_STM32L0X) && defined(LL_SYSCFG_EXTI_PORTH) /* * Ports F and G are not present on some STM32L0 parts, so * for these parts port H external interrupt is enabled * by writing value 0x5 instead of 0x7. */ if (port == LL_SYSCFG_EXTI_PORTH) { port = STM32_PORTH; } #endif return port; } /** * @brief Enable EXTI of the specific line */ static int gpio_stm32_enable_int(int port, int pin) { #if defined(CONFIG_SOC_SERIES_STM32F2X) || \ defined(CONFIG_SOC_SERIES_STM32F3X) || \ defined(CONFIG_SOC_SERIES_STM32F4X) || \ defined(CONFIG_SOC_SERIES_STM32F7X) || \ defined(CONFIG_SOC_SERIES_STM32H7X) || \ defined(CONFIG_SOC_SERIES_STM32L1X) || \ defined(CONFIG_SOC_SERIES_STM32L4X) || \ defined(CONFIG_SOC_SERIES_STM32G4X) struct device *clk = device_get_binding(STM32_CLOCK_CONTROL_NAME); struct stm32_pclken pclken = { #ifdef CONFIG_SOC_SERIES_STM32H7X .bus = STM32_CLOCK_BUS_APB4, .enr = LL_APB4_GRP1_PERIPH_SYSCFG #else .bus = STM32_CLOCK_BUS_APB2, .enr = LL_APB2_GRP1_PERIPH_SYSCFG #endif /* CONFIG_SOC_SERIES_STM32H7X */ }; /* Enable SYSCFG clock */ clock_control_on(clk, (clock_control_subsys_t *) &pclken); #endif gpio_stm32_set_exti_source(port, pin); return 0; } static int gpio_stm32_port_get_raw(struct device *dev, uint32_t *value) { const struct gpio_stm32_config *cfg = dev->config_info; GPIO_TypeDef *gpio = (GPIO_TypeDef *)cfg->base; *value = LL_GPIO_ReadInputPort(gpio); return 0; } static int gpio_stm32_port_set_masked_raw(struct device *dev, gpio_port_pins_t mask, gpio_port_value_t value) { const struct gpio_stm32_config *cfg = dev->config_info; 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(struct device *dev, gpio_port_pins_t pins) { const struct gpio_stm32_config *cfg = dev->config_info; 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(struct device *dev, gpio_port_pins_t pins) { const struct gpio_stm32_config *cfg = dev->config_info; 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(struct device *dev, gpio_port_pins_t pins) { const struct gpio_stm32_config *cfg = dev->config_info; 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; } /** * @brief Configure pin or port */ static int gpio_stm32_config(struct device *dev, gpio_pin_t pin, gpio_flags_t flags) { const struct gpio_stm32_config *cfg = dev->config_info; int err = 0; int pincfg; /* figure out if we can map the requested GPIO * configuration */ err = gpio_stm32_flags_to_conf(flags, &pincfg); if (err != 0) { goto exit; } 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(cfg->base, pin, pincfg, 0); exit: return err; } static int gpio_stm32_pin_interrupt_configure(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_info; int edge = 0; int err = 0; if (mode == GPIO_INT_MODE_DISABLED) { if (gpio_stm32_get_exti_source(pin) == cfg->port) { stm32_exti_disable(pin); stm32_exti_unset_callback(pin); stm32_exti_trigger(pin, STM32_EXTI_TRIG_NONE); } /* else: No irq source configured for pin. Nothing to disable */ goto exit; } /* Level trigger interrupts not supported */ if (mode == GPIO_INT_MODE_LEVEL) { err = -ENOTSUP; goto exit; } if (stm32_exti_set_callback(pin, gpio_stm32_isr, dev) != 0) { err = -EBUSY; goto exit; } gpio_stm32_enable_int(cfg->port, pin); switch (trig) { case GPIO_INT_TRIG_LOW: edge = STM32_EXTI_TRIG_FALLING; break; case GPIO_INT_TRIG_HIGH: edge = STM32_EXTI_TRIG_RISING; break; case GPIO_INT_TRIG_BOTH: edge = STM32_EXTI_TRIG_BOTH; break; } stm32_exti_trigger(pin, edge); stm32_exti_enable(pin); exit: return err; } static int gpio_stm32_manage_callback(struct device *dev, struct gpio_callback *callback, bool set) { struct gpio_stm32_data *data = dev->driver_data; return gpio_manage_callback(&data->cb, callback, set); } static const struct gpio_driver_api gpio_stm32_driver = { .pin_configure = gpio_stm32_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, }; /** * @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 */ static int gpio_stm32_init(struct device *device) { const struct gpio_stm32_config *cfg = device->config_info; /* enable clock for subsystem */ struct device *clk = device_get_binding(STM32_CLOCK_CONTROL_NAME); if (clock_control_on(clk, (clock_control_subsys_t *)&cfg->pclken) != 0) { return -EIO; } #ifdef PWR_CR2_IOSV if (cfg->port == STM32_PORTG) { /* Port G[15:2] requires external power supply */ /* Cf: L4XX RM, ยง5.1 Power supplies */ z_stm32_hsem_lock(CFG_HW_RCC_SEMID, HSEM_LOCK_DEFAULT_RETRY); if (LL_APB1_GRP1_IsEnabledClock(LL_APB1_GRP1_PERIPH_PWR)) { LL_PWR_EnableVddIO2(); } else { LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR); LL_PWR_EnableVddIO2(); LL_APB1_GRP1_DisableClock(LL_APB1_GRP1_PERIPH_PWR); } z_stm32_hsem_unlock(CFG_HW_RCC_SEMID); } #endif /* PWR_CR2_IOSV */ return 0; } #define GPIO_DEVICE_INIT(__name, __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, \ .pclken = { .bus = __bus, .enr = __cenr } \ }; \ static struct gpio_stm32_data gpio_stm32_data_## __suffix; \ DEVICE_AND_API_INIT(gpio_stm32_## __suffix, \ __name, \ gpio_stm32_init, \ &gpio_stm32_data_## __suffix, \ &gpio_stm32_cfg_## __suffix, \ POST_KERNEL, \ CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ &gpio_stm32_driver) #define GPIO_DEVICE_INIT_STM32(__suffix, __SUFFIX) \ GPIO_DEVICE_INIT(DT_LABEL(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)) #if DT_NODE_HAS_STATUS(DT_NODELABEL(gpioa), okay) GPIO_DEVICE_INIT_STM32(a, A); #endif /* DT_NODE_HAS_STATUS(DT_NODELABEL(gpioa), okay) */ #if DT_NODE_HAS_STATUS(DT_NODELABEL(gpiob), okay) GPIO_DEVICE_INIT_STM32(b, B); #endif /* DT_NODE_HAS_STATUS(DT_NODELABEL(gpiob), okay) */ #if DT_NODE_HAS_STATUS(DT_NODELABEL(gpioc), okay) GPIO_DEVICE_INIT_STM32(c, C); #endif /* DT_NODE_HAS_STATUS(DT_NODELABEL(gpioc), okay) */ #if DT_NODE_HAS_STATUS(DT_NODELABEL(gpiod), okay) GPIO_DEVICE_INIT_STM32(d, D); #endif /* DT_NODE_HAS_STATUS(DT_NODELABEL(gpiod), okay) */ #if DT_NODE_HAS_STATUS(DT_NODELABEL(gpioe), okay) GPIO_DEVICE_INIT_STM32(e, E); #endif /* DT_NODE_HAS_STATUS(DT_NODELABEL(gpioe), okay) */ #if DT_NODE_HAS_STATUS(DT_NODELABEL(gpiof), okay) GPIO_DEVICE_INIT_STM32(f, F); #endif /* DT_NODE_HAS_STATUS(DT_NODELABEL(gpiof), okay) */ #if DT_NODE_HAS_STATUS(DT_NODELABEL(gpiog), okay) GPIO_DEVICE_INIT_STM32(g, G); #endif /* DT_NODE_HAS_STATUS(DT_NODELABEL(gpiog), okay) */ #if DT_NODE_HAS_STATUS(DT_NODELABEL(gpioh), okay) GPIO_DEVICE_INIT_STM32(h, H); #endif /* DT_NODE_HAS_STATUS(DT_NODELABEL(gpioh), okay) */ #if DT_NODE_HAS_STATUS(DT_NODELABEL(gpioi), okay) GPIO_DEVICE_INIT_STM32(i, I); #endif /* DT_NODE_HAS_STATUS(DT_NODELABEL(gpioi), okay) */ #if DT_NODE_HAS_STATUS(DT_NODELABEL(gpioj), okay) GPIO_DEVICE_INIT_STM32(j, J); #endif /* DT_NODE_HAS_STATUS(DT_NODELABEL(gpioj), okay) */ #if DT_NODE_HAS_STATUS(DT_NODELABEL(gpiok), okay) GPIO_DEVICE_INIT_STM32(k, K); #endif /* DT_NODE_HAS_STATUS(DT_NODELABEL(gpiok), okay) */ #if defined(CONFIG_SOC_SERIES_STM32F1X) static int gpio_stm32_afio_init(struct device *device) { UNUSED(device); LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_AFIO); #if defined(CONFIG_GPIO_STM32_SWJ_NONJTRST) /* released PB4 */ __HAL_AFIO_REMAP_SWJ_NONJTRST(); #elif defined(CONFIG_GPIO_STM32_SWJ_NOJTAG) /* released PB4 PB3 PA15 */ __HAL_AFIO_REMAP_SWJ_NOJTAG(); #elif defined(CONFIG_GPIO_STM32_SWJ_DISABLE) /* released PB4 PB3 PA13 PA14 PA15 */ __HAL_AFIO_REMAP_SWJ_DISABLE(); #endif LL_APB2_GRP1_DisableClock(LL_APB2_GRP1_PERIPH_AFIO); return 0; } DEVICE_INIT(gpio_stm32_afio, "", gpio_stm32_afio_init, NULL, NULL, PRE_KERNEL_2, 0); #endif /* CONFIG_SOC_SERIES_STM32F1X */