/* * Copyright (c) 2017 Google LLC. * Copyright (c) 2019 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT atmel_sam0_gpio #include #include #include #include #include #include "gpio_utils.h" #ifndef PORT_PMUX_PMUXE_A_Val #define PORT_PMUX_PMUXE_A_Val (0) #endif struct gpio_sam0_config { /* gpio_driver_config needs to be first */ struct gpio_driver_config common; PortGroup *regs; #ifdef CONFIG_SAM0_EIC uint8_t id; #endif }; struct gpio_sam0_data { /* gpio_driver_data needs to be first */ struct gpio_driver_data common; const struct device *dev; gpio_port_pins_t debounce; #ifdef CONFIG_SAM0_EIC sys_slist_t callbacks; #endif }; #define DEV_CFG(dev) \ ((const struct gpio_sam0_config *const)(dev)->config) #define DEV_DATA(dev) \ ((struct gpio_sam0_data *const)(dev)->data) #ifdef CONFIG_SAM0_EIC static void gpio_sam0_isr(uint32_t pins, void *arg) { struct gpio_sam0_data *const data = (struct gpio_sam0_data *)arg; gpio_fire_callbacks(&data->callbacks, data->dev, pins); } #endif static int gpio_sam0_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) { const struct gpio_sam0_config *config = DEV_CFG(dev); PortGroup *regs = config->regs; PORT_PINCFG_Type pincfg = { .reg = 0, }; if ((flags & GPIO_SINGLE_ENDED) != 0) { return -ENOTSUP; } /* Supports disconnected, input, output, or bidirectional */ if ((flags & GPIO_INPUT) != 0) { pincfg.bit.INEN = 1; } if ((flags & GPIO_OUTPUT) != 0) { /* Output is incompatible with pull */ if ((flags & (GPIO_PULL_UP | GPIO_PULL_DOWN)) != 0) { return -ENOTSUP; } /* Bidirectional is supported */ if ((flags & GPIO_OUTPUT_INIT_LOW) != 0) { regs->OUTCLR.reg = BIT(pin); } else if ((flags & GPIO_OUTPUT_INIT_HIGH) != 0) { regs->OUTSET.reg = BIT(pin); } regs->DIRSET.reg = BIT(pin); } else { /* Not output, may be input */ regs->DIRCLR.reg = BIT(pin); /* Pull configuration is supported if not output */ if ((flags & (GPIO_PULL_UP | GPIO_PULL_DOWN)) != 0) { pincfg.bit.PULLEN = 1; if ((flags & GPIO_PULL_UP) != 0) { regs->OUTSET.reg = BIT(pin); } else { regs->OUTCLR.reg = BIT(pin); } } } /* Preserve debounce flag for interrupt configuration. */ WRITE_BIT(DEV_DATA(dev)->debounce, pin, ((flags & GPIO_INT_DEBOUNCE) != 0) && (pincfg.bit.INEN != 0)); /* Write the now-built pin configuration */ regs->PINCFG[pin] = pincfg; return 0; } static int gpio_sam0_port_get_raw(const struct device *dev, gpio_port_value_t *value) { const struct gpio_sam0_config *config = DEV_CFG(dev); *value = config->regs->IN.reg; return 0; } static int gpio_sam0_port_set_masked_raw(const struct device *dev, gpio_port_pins_t mask, gpio_port_value_t value) { const struct gpio_sam0_config *config = DEV_CFG(dev); uint32_t out = config->regs->OUT.reg; config->regs->OUT.reg = (out & ~mask) | (value & mask); return 0; } static int gpio_sam0_port_set_bits_raw(const struct device *dev, gpio_port_pins_t pins) { const struct gpio_sam0_config *config = DEV_CFG(dev); config->regs->OUTSET.reg = pins; return 0; } static int gpio_sam0_port_clear_bits_raw(const struct device *dev, gpio_port_pins_t pins) { const struct gpio_sam0_config *config = DEV_CFG(dev); config->regs->OUTCLR.reg = pins; return 0; } static int gpio_sam0_port_toggle_bits(const struct device *dev, gpio_port_pins_t pins) { const struct gpio_sam0_config *config = DEV_CFG(dev); config->regs->OUTTGL.reg = pins; return 0; } #ifdef CONFIG_SAM0_EIC static int gpio_sam0_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin, enum gpio_int_mode mode, enum gpio_int_trig trig) { const struct gpio_sam0_config *config = DEV_CFG(dev); struct gpio_sam0_data *const data = DEV_DATA(dev); PortGroup *regs = config->regs; PORT_PINCFG_Type pincfg = { .reg = regs->PINCFG[pin].reg, }; enum sam0_eic_trigger trigger; int rc = 0; data->dev = dev; switch (mode) { case GPIO_INT_MODE_DISABLED: pincfg.bit.PMUXEN = 0; rc = sam0_eic_disable_interrupt(config->id, pin); if (rc == -EBUSY) { /* Ignore diagnostic disabling disabled */ rc = 0; } if (rc == 0) { rc = sam0_eic_release(config->id, pin); } break; case GPIO_INT_MODE_LEVEL: case GPIO_INT_MODE_EDGE: /* Enabling interrupts on a pin requires disconnecting * the pin from the I/O pin controller (PORT) module * and connecting it to the External Interrupt * Controller (EIC). This would prevent using the pin * as an output, so interrupts are only supported if * the pin is configured as input-only. */ if ((pincfg.bit.INEN == 0) || ((regs->DIR.reg & BIT(pin)) != 0)) { rc = -ENOTSUP; break; } /* Transfer control to EIC */ pincfg.bit.PMUXEN = 1; if ((pin & 1U) != 0) { regs->PMUX[pin / 2U].bit.PMUXO = PORT_PMUX_PMUXE_A_Val; } else { regs->PMUX[pin / 2U].bit.PMUXE = PORT_PMUX_PMUXE_A_Val; } switch (trig) { case GPIO_INT_TRIG_LOW: trigger = (mode == GPIO_INT_MODE_LEVEL) ? SAM0_EIC_LOW : SAM0_EIC_FALLING; break; case GPIO_INT_TRIG_HIGH: trigger = (mode == GPIO_INT_MODE_LEVEL) ? SAM0_EIC_HIGH : SAM0_EIC_RISING; break; case GPIO_INT_TRIG_BOTH: trigger = SAM0_EIC_BOTH; break; default: rc = -EINVAL; break; } if (rc == 0) { rc = sam0_eic_acquire(config->id, pin, trigger, (DEV_DATA(dev)->debounce & BIT(pin)) != 0, gpio_sam0_isr, data); } if (rc == 0) { rc = sam0_eic_enable_interrupt(config->id, pin); } break; default: rc = -EINVAL; break; } if (rc == 0) { /* Update the pin configuration */ regs->PINCFG[pin] = pincfg; } return rc; } static int gpio_sam0_manage_callback(const struct device *dev, struct gpio_callback *callback, bool set) { struct gpio_sam0_data *const data = DEV_DATA(dev); return gpio_manage_callback(&data->callbacks, callback, set); } static uint32_t gpio_sam0_get_pending_int(const struct device *dev) { const struct gpio_sam0_config *config = DEV_CFG(dev); return sam0_eic_interrupt_pending(config->id); } #endif static const struct gpio_driver_api gpio_sam0_api = { .pin_configure = gpio_sam0_config, .port_get_raw = gpio_sam0_port_get_raw, .port_set_masked_raw = gpio_sam0_port_set_masked_raw, .port_set_bits_raw = gpio_sam0_port_set_bits_raw, .port_clear_bits_raw = gpio_sam0_port_clear_bits_raw, .port_toggle_bits = gpio_sam0_port_toggle_bits, #ifdef CONFIG_SAM0_EIC .pin_interrupt_configure = gpio_sam0_pin_interrupt_configure, .manage_callback = gpio_sam0_manage_callback, .get_pending_int = gpio_sam0_get_pending_int, #endif }; static int gpio_sam0_init(const struct device *dev) { return 0; } /* Port A */ #if DT_NODE_HAS_STATUS(DT_NODELABEL(porta), okay) static const struct gpio_sam0_config gpio_sam0_config_0 = { .common = { .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(0), }, .regs = (PortGroup *)DT_REG_ADDR(DT_NODELABEL(porta)), #ifdef CONFIG_SAM0_EIC .id = 0, #endif }; static struct gpio_sam0_data gpio_sam0_data_0; DEVICE_DT_DEFINE(DT_NODELABEL(porta), gpio_sam0_init, NULL, &gpio_sam0_data_0, &gpio_sam0_config_0, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &gpio_sam0_api); #endif /* Port B */ #if DT_NODE_HAS_STATUS(DT_NODELABEL(portb), okay) static const struct gpio_sam0_config gpio_sam0_config_1 = { .common = { .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(1), }, .regs = (PortGroup *)DT_REG_ADDR(DT_NODELABEL(portb)), #ifdef CONFIG_SAM0_EIC .id = 1, #endif }; static struct gpio_sam0_data gpio_sam0_data_1; DEVICE_DT_DEFINE(DT_NODELABEL(portb), gpio_sam0_init, NULL, &gpio_sam0_data_1, &gpio_sam0_config_1, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &gpio_sam0_api); #endif /* Port C */ #if DT_NODE_HAS_STATUS(DT_NODELABEL(portc), okay) static const struct gpio_sam0_config gpio_sam0_config_2 = { .common = { .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(2), }, .regs = (PortGroup *)DT_REG_ADDR(DT_NODELABEL(portc)), #ifdef CONFIG_SAM0_EIC .id = 2, #endif }; static struct gpio_sam0_data gpio_sam0_data_2; DEVICE_DT_DEFINE(DT_NODELABEL(portc), gpio_sam0_init, NULL, &gpio_sam0_data_2, &gpio_sam0_config_2, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &gpio_sam0_api); #endif /* Port D */ #if DT_NODE_HAS_STATUS(DT_NODELABEL(portd), okay) static const struct gpio_sam0_config gpio_sam0_config_3 = { .common = { .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(3), }, .regs = (PortGroup *)DT_REG_ADDR(DT_NODELABEL(portd)), #ifdef CONFIG_SAM0_EIC .id = 3, #endif }; static struct gpio_sam0_data gpio_sam0_data_3; DEVICE_DT_DEFINE(DT_NODELABEL(portd), gpio_sam0_init, NULL, &gpio_sam0_data_3, &gpio_sam0_config_3, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &gpio_sam0_api); #endif