| /* |
| * Copyright (c) 2016 Open-RnD Sp. z o.o. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <errno.h> |
| |
| #include <kernel.h> |
| #include <device.h> |
| #include <soc.h> |
| #include <gpio.h> |
| #include <clock_control/stm32_clock_control.h> |
| #include <pinmux/stm32/pinmux_stm32.h> |
| #include <pinmux.h> |
| #include <misc/util.h> |
| #include <interrupt_controller/exti_stm32.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; |
| |
| if ((BIT(line) & data->cb_pins) != 0) { |
| gpio_fire_callbacks(&data->cb, dev, BIT(line)); |
| } |
| } |
| |
| /** |
| * @brief Common gpio flags to custom flags |
| */ |
| const int gpio_stm32_flags_to_conf(int flags, int *pincfg) |
| { |
| int direction = flags & GPIO_DIR_MASK; |
| int pud = flags & GPIO_PUD_MASK; |
| |
| if (pincfg == NULL) { |
| return -EINVAL; |
| } |
| |
| if (direction == GPIO_DIR_OUT) { |
| *pincfg = STM32_PINCFG_MODE_OUTPUT; |
| } else { |
| /* pull-{up,down} maybe? */ |
| *pincfg = STM32_PINCFG_MODE_INPUT; |
| if (pud == GPIO_PUD_PULL_UP) { |
| *pincfg |= STM32_PINCFG_PULL_UP; |
| } else if (pud == GPIO_PUD_PULL_DOWN) { |
| *pincfg |= STM32_PINCFG_PULL_DOWN; |
| } else { |
| /* floating */ |
| *pincfg |= STM32_PINCFG_FLOATING; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Translate pin to pinval that the LL library needs |
| */ |
| static inline u32_t stm32_pinval_get(int pin) |
| { |
| u32_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(u32_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); |
| |
| u32_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); |
| |
| 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); |
| |
| #endif /* CONFIG_SOC_SERIES_STM32F1X */ |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Enable EXTI of the specific line |
| */ |
| const 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_STM32L1X) || \ |
| defined(CONFIG_SOC_SERIES_STM32L4X) |
| struct device *clk = device_get_binding(STM32_CLOCK_CONTROL_NAME); |
| struct stm32_pclken pclken = { |
| .bus = STM32_CLOCK_BUS_APB2, |
| .enr = LL_APB2_GRP1_PERIPH_SYSCFG |
| }; |
| /* Enable SYSCFG clock */ |
| clock_control_on(clk, (clock_control_subsys_t *) &pclken); |
| #endif |
| |
| uint32_t line; |
| |
| if (pin > 15) { |
| return -EINVAL; |
| } |
| |
| #if defined(CONFIG_SOC_SERIES_STM32L0X) || \ |
| defined(CONFIG_SOC_SERIES_STM32F0X) |
| line = ((pin % 4 * 4) << 16) | (pin / 4); |
| #elif defined(CONFIG_SOC_SERIES_STM32MP1X) |
| line = (((pin * 8) % 32) << 16) | (pin / 4); |
| #else |
| line = (0xF << ((pin % 4 * 4) + 16)) | (pin / 4); |
| #endif |
| |
| #ifdef CONFIG_SOC_SERIES_STM32L0X |
| /* |
| * 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 |
| |
| #ifdef CONFIG_SOC_SERIES_STM32F1X |
| LL_GPIO_AF_SetEXTISource(port, line); |
| #elif CONFIG_SOC_SERIES_STM32MP1X |
| LL_EXTI_SetEXTISource(port, line); |
| #else |
| LL_SYSCFG_SetEXTISource(port, line); |
| #endif |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Configure pin or port |
| */ |
| static int gpio_stm32_config(struct device *dev, int access_op, |
| u32_t pin, int flags) |
| { |
| const struct gpio_stm32_config *cfg = dev->config->config_info; |
| int pincfg; |
| int map_res; |
| |
| if (access_op != GPIO_ACCESS_BY_PIN) { |
| return -ENOTSUP; |
| } |
| |
| if ((flags & GPIO_POL_MASK) == GPIO_POL_INV) { |
| /* hardware cannot invert signal */ |
| return -ENOTSUP; |
| } |
| |
| /* figure out if we can map the requested GPIO |
| * configuration |
| */ |
| map_res = gpio_stm32_flags_to_conf(flags, &pincfg); |
| if (map_res != 0) { |
| return map_res; |
| } |
| |
| if (gpio_stm32_configure(cfg->base, pin, pincfg, 0) != 0) { |
| return -EIO; |
| } |
| |
| if ((flags & GPIO_INT) != 0) { |
| |
| if (stm32_exti_set_callback(pin, cfg->port, |
| gpio_stm32_isr, dev) != 0) { |
| return -EBUSY; |
| } |
| |
| gpio_stm32_enable_int(cfg->port, pin); |
| |
| if ((flags & GPIO_INT_EDGE) != 0) { |
| int edge = 0; |
| |
| if ((flags & GPIO_INT_DOUBLE_EDGE) != 0) { |
| edge = STM32_EXTI_TRIG_RISING | |
| STM32_EXTI_TRIG_FALLING; |
| } else if ((flags & GPIO_INT_ACTIVE_HIGH) != 0) { |
| edge = STM32_EXTI_TRIG_RISING; |
| } else { |
| edge = STM32_EXTI_TRIG_FALLING; |
| } |
| |
| stm32_exti_trigger(pin, edge); |
| } else { |
| /* Level trigger interrupts not supported */ |
| return -ENOTSUP; |
| } |
| |
| if (stm32_exti_enable(pin) != 0) { |
| return -ENOSYS; |
| } |
| |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Set the pin or port output |
| */ |
| static int gpio_stm32_write(struct device *dev, int access_op, |
| u32_t pin, u32_t value) |
| { |
| const struct gpio_stm32_config *cfg = dev->config->config_info; |
| GPIO_TypeDef *gpio = (GPIO_TypeDef *)cfg->base; |
| |
| if (access_op != GPIO_ACCESS_BY_PIN) { |
| return -ENOTSUP; |
| } |
| |
| pin = stm32_pinval_get(pin); |
| if (value != 0U) { |
| LL_GPIO_SetOutputPin(gpio, pin); |
| } else { |
| LL_GPIO_ResetOutputPin(gpio, pin); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Read the pin or port status |
| */ |
| static int gpio_stm32_read(struct device *dev, int access_op, |
| u32_t pin, u32_t *value) |
| { |
| const struct gpio_stm32_config *cfg = dev->config->config_info; |
| GPIO_TypeDef *gpio = (GPIO_TypeDef *)cfg->base; |
| |
| if (access_op != GPIO_ACCESS_BY_PIN) { |
| return -ENOTSUP; |
| } |
| |
| *value = (LL_GPIO_ReadInputPort(gpio) >> pin) & 0x1; |
| |
| return 0; |
| } |
| |
| 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 int gpio_stm32_enable_callback(struct device *dev, |
| int access_op, u32_t pin) |
| { |
| struct gpio_stm32_data *data = dev->driver_data; |
| |
| if (access_op != GPIO_ACCESS_BY_PIN) { |
| return -ENOTSUP; |
| } |
| |
| data->cb_pins |= BIT(pin); |
| |
| return 0; |
| } |
| |
| static int gpio_stm32_disable_callback(struct device *dev, |
| int access_op, u32_t pin) |
| { |
| struct gpio_stm32_data *data = dev->driver_data; |
| |
| if (access_op != GPIO_ACCESS_BY_PIN) { |
| return -ENOTSUP; |
| } |
| |
| data->cb_pins &= ~BIT(pin); |
| |
| return 0; |
| } |
| |
| static const struct gpio_driver_api gpio_stm32_driver = { |
| .config = gpio_stm32_config, |
| .write = gpio_stm32_write, |
| .read = gpio_stm32_read, |
| .manage_callback = gpio_stm32_manage_callback, |
| .enable_callback = gpio_stm32_enable_callback, |
| .disable_callback = gpio_stm32_disable_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->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 */ |
| 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); |
| } |
| } |
| #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 = { \ |
| .base = (u32_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_GPIO_STM32_GPIO##__SUFFIX##_LABEL, \ |
| __suffix, \ |
| DT_GPIO_STM32_GPIO##__SUFFIX##_BASE_ADDRESS, \ |
| STM32_PORT##__SUFFIX, \ |
| DT_GPIO_STM32_GPIO##__SUFFIX##_CLOCK_BITS, \ |
| DT_GPIO_STM32_GPIO##__SUFFIX##_CLOCK_BUS) |
| |
| #ifdef CONFIG_GPIO_STM32_PORTA |
| GPIO_DEVICE_INIT_STM32(a, A); |
| #endif /* CONFIG_GPIO_STM32_PORTA */ |
| |
| #ifdef CONFIG_GPIO_STM32_PORTB |
| GPIO_DEVICE_INIT_STM32(b, B); |
| #endif /* CONFIG_GPIO_STM32_PORTB */ |
| |
| #ifdef CONFIG_GPIO_STM32_PORTC |
| GPIO_DEVICE_INIT_STM32(c, C); |
| #endif /* CONFIG_GPIO_STM32_PORTC */ |
| |
| #ifdef CONFIG_GPIO_STM32_PORTD |
| GPIO_DEVICE_INIT_STM32(d, D); |
| #endif /* CONFIG_GPIO_STM32_PORTD */ |
| |
| #ifdef CONFIG_GPIO_STM32_PORTE |
| GPIO_DEVICE_INIT_STM32(e, E); |
| #endif /* CONFIG_GPIO_STM32_PORTE */ |
| |
| #ifdef CONFIG_GPIO_STM32_PORTF |
| GPIO_DEVICE_INIT_STM32(f, F); |
| #endif /* CONFIG_GPIO_STM32_PORTF */ |
| |
| #ifdef CONFIG_GPIO_STM32_PORTG |
| GPIO_DEVICE_INIT_STM32(g, G); |
| #endif /* CONFIG_GPIO_STM32_PORTG */ |
| |
| #ifdef CONFIG_GPIO_STM32_PORTH |
| GPIO_DEVICE_INIT_STM32(h, H); |
| #endif /* CONFIG_GPIO_STM32_PORTH */ |
| |
| #ifdef CONFIG_GPIO_STM32_PORTI |
| GPIO_DEVICE_INIT_STM32(i, I); |
| #endif /* CONFIG_GPIO_STM32_PORTI */ |
| |
| #ifdef CONFIG_GPIO_STM32_PORTJ |
| GPIO_DEVICE_INIT_STM32(j, J); |
| #endif /* CONFIG_GPIO_STM32_PORTJ */ |
| |
| #ifdef CONFIG_GPIO_STM32_PORTK |
| GPIO_DEVICE_INIT_STM32(k, K); |
| #endif /* CONFIG_GPIO_STM32_PORTK */ |