/* * Copyright (c) 2018 Justin Watson * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT atmel_sam_gpio #include #include #include #include #include #include #include #include #include "gpio_utils.h" typedef void (*config_func_t)(const struct device *dev); struct gpio_sam_config { /* gpio_driver_config needs to be first */ struct gpio_driver_config common; Pio *regs; config_func_t config_func; uint32_t periph_id; }; struct gpio_sam_runtime { /* gpio_driver_data needs to be first */ struct gpio_driver_data common; sys_slist_t cb; }; #define GPIO_SAM_ALL_PINS 0xFFFFFFFF static int gpio_sam_port_configure(const struct device *dev, uint32_t mask, gpio_flags_t flags) { const struct gpio_sam_config * const cfg = dev->config; Pio * const pio = cfg->regs; if ((flags & GPIO_SINGLE_ENDED) != 0) { if ((flags & GPIO_LINE_OPEN_DRAIN) != 0) { /* Enable open-drain drive mode */ pio->PIO_MDER = mask; } else { /* Open-drain is the only supported single-ended mode */ return -ENOTSUP; } } else { /* Disable open-drain drive mode */ pio->PIO_MDDR = mask; } if (!(flags & (GPIO_OUTPUT | GPIO_INPUT))) { /* Neither input nor output mode is selected */ /* Disable the interrupt. */ pio->PIO_IDR = mask; /* Disable pull-up. */ pio->PIO_PUDR = mask; #if defined(CONFIG_SOC_SERIES_SAM4S) || \ defined(CONFIG_SOC_SERIES_SAM4E) || \ defined(CONFIG_SOC_SERIES_SAME70) || \ defined(CONFIG_SOC_SERIES_SAMV71) /* Disable pull-down. */ pio->PIO_PPDDR = mask; #endif /* Let the PIO control the pin (instead of a peripheral). */ pio->PIO_PER = mask; /* Disable output. */ pio->PIO_ODR = mask; return 0; } /* Setup the pin direction. */ if (flags & GPIO_OUTPUT) { if (flags & GPIO_OUTPUT_INIT_HIGH) { /* Set the pin. */ pio->PIO_SODR = mask; } if (flags & GPIO_OUTPUT_INIT_LOW) { /* Clear the pin. */ pio->PIO_CODR = mask; } /* Enable the output */ pio->PIO_OER = mask; /* Enable direct control of output level via PIO_ODSR */ pio->PIO_OWER = mask; } else { /* Disable the output */ pio->PIO_ODR = mask; } /* Note: Input is always enabled. */ /* Setup selected Pull resistor. * * A pull cannot be enabled if the opposite pull is enabled. * Clear both pulls, then enable the one we need. */ pio->PIO_PUDR = mask; #if defined(CONFIG_SOC_SERIES_SAM4S) || \ defined(CONFIG_SOC_SERIES_SAM4E) || \ defined(CONFIG_SOC_SERIES_SAME70) || \ defined(CONFIG_SOC_SERIES_SAMV71) pio->PIO_PPDDR = mask; #endif if (flags & GPIO_PULL_UP) { /* Enable pull-up. */ pio->PIO_PUER = mask; #if defined(CONFIG_SOC_SERIES_SAM4S) || \ defined(CONFIG_SOC_SERIES_SAM4E) || \ defined(CONFIG_SOC_SERIES_SAME70) || \ defined(CONFIG_SOC_SERIES_SAMV71) /* Setup Pull-down resistor. */ } else if (flags & GPIO_PULL_DOWN) { /* Enable pull-down. */ pio->PIO_PPDER = mask; #endif } #if defined(CONFIG_SOC_SERIES_SAM3X) /* Setup debounce. */ if (flags & SAM_GPIO_DEBOUNCE) { pio->PIO_DIFSR = mask; } else { pio->PIO_SCIFSR = mask; } #elif defined(CONFIG_SOC_SERIES_SAM4S) || \ defined(CONFIG_SOC_SERIES_SAM4E) || \ defined(CONFIG_SOC_SERIES_SAME70) || \ defined(CONFIG_SOC_SERIES_SAMV71) /* Setup debounce. */ if (flags & SAM_GPIO_DEBOUNCE) { pio->PIO_IFSCER = mask; } else { pio->PIO_IFSCDR = mask; } #endif /* Enable the PIO to control the pin (instead of a peripheral). */ pio->PIO_PER = mask; return 0; } static int gpio_sam_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) { return gpio_sam_port_configure(dev, BIT(pin), flags); } static int gpio_sam_port_get_raw(const struct device *dev, uint32_t *value) { const struct gpio_sam_config * const cfg = dev->config; Pio * const pio = cfg->regs; *value = pio->PIO_PDSR; return 0; } static int gpio_sam_port_set_masked_raw(const struct device *dev, uint32_t mask, uint32_t value) { const struct gpio_sam_config * const cfg = dev->config; Pio * const pio = cfg->regs; pio->PIO_ODSR = (pio->PIO_ODSR & ~mask) | (mask & value); return 0; } static int gpio_sam_port_set_bits_raw(const struct device *dev, uint32_t mask) { const struct gpio_sam_config * const cfg = dev->config; Pio * const pio = cfg->regs; /* Set pins. */ pio->PIO_SODR = mask; return 0; } static int gpio_sam_port_clear_bits_raw(const struct device *dev, uint32_t mask) { const struct gpio_sam_config * const cfg = dev->config; Pio * const pio = cfg->regs; /* Clear pins. */ pio->PIO_CODR = mask; return 0; } static int gpio_sam_port_toggle_bits(const struct device *dev, uint32_t mask) { const struct gpio_sam_config * const cfg = dev->config; Pio * const pio = cfg->regs; /* Toggle pins. */ pio->PIO_ODSR ^= mask; return 0; } static int gpio_sam_port_interrupt_configure(const struct device *dev, uint32_t mask, enum gpio_int_mode mode, enum gpio_int_trig trig) { const struct gpio_sam_config * const cfg = dev->config; Pio * const pio = cfg->regs; /* Disable the interrupt. */ pio->PIO_IDR = mask; /* Disable additional interrupt modes. */ pio->PIO_AIMDR = mask; if (trig != GPIO_INT_TRIG_BOTH) { /* Enable additional interrupt modes to support single * edge/level detection. */ pio->PIO_AIMER = mask; if (mode == GPIO_INT_MODE_EDGE) { pio->PIO_ESR = mask; } else { pio->PIO_LSR = mask; } uint32_t rising_edge; if (trig == GPIO_INT_TRIG_HIGH) { rising_edge = mask; } else { rising_edge = ~mask; } /* Set to high-level or rising edge. */ pio->PIO_REHLSR = rising_edge & mask; /* Set to low-level or falling edge. */ pio->PIO_FELLSR = ~rising_edge & mask; } if (mode != GPIO_INT_MODE_DISABLED) { /* Clear any pending interrupts */ (void)pio->PIO_ISR; /* Enable the interrupt. */ pio->PIO_IER = mask; } return 0; } static int gpio_sam_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin, enum gpio_int_mode mode, enum gpio_int_trig trig) { return gpio_sam_port_interrupt_configure(dev, BIT(pin), mode, trig); } static void gpio_sam_isr(const struct device *dev) { const struct gpio_sam_config * const cfg = dev->config; Pio * const pio = cfg->regs; struct gpio_sam_runtime *context = dev->data; uint32_t int_stat; int_stat = pio->PIO_ISR; gpio_fire_callbacks(&context->cb, dev, int_stat); } static int gpio_sam_manage_callback(const struct device *port, struct gpio_callback *callback, bool set) { struct gpio_sam_runtime *context = port->data; return gpio_manage_callback(&context->cb, callback, set); } static const struct gpio_driver_api gpio_sam_api = { .pin_configure = gpio_sam_config, .port_get_raw = gpio_sam_port_get_raw, .port_set_masked_raw = gpio_sam_port_set_masked_raw, .port_set_bits_raw = gpio_sam_port_set_bits_raw, .port_clear_bits_raw = gpio_sam_port_clear_bits_raw, .port_toggle_bits = gpio_sam_port_toggle_bits, .pin_interrupt_configure = gpio_sam_pin_interrupt_configure, .manage_callback = gpio_sam_manage_callback, }; int gpio_sam_init(const struct device *dev) { const struct gpio_sam_config * const cfg = dev->config; /* The peripheral clock must be enabled for the interrupts to work. */ soc_pmc_peripheral_enable(cfg->periph_id); cfg->config_func(dev); return 0; } #define GPIO_SAM_INIT(n) \ static void port_##n##_sam_config_func(const struct device *dev); \ \ static const struct gpio_sam_config port_##n##_sam_config = { \ .common = { \ .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(n),\ }, \ .regs = (Pio *)DT_INST_REG_ADDR(n), \ .periph_id = DT_INST_PROP(n, peripheral_id), \ .config_func = port_##n##_sam_config_func, \ }; \ \ static struct gpio_sam_runtime port_##n##_sam_runtime; \ \ DEVICE_DT_INST_DEFINE(n, gpio_sam_init, NULL, \ &port_##n##_sam_runtime, \ &port_##n##_sam_config, PRE_KERNEL_1, \ CONFIG_GPIO_INIT_PRIORITY, \ &gpio_sam_api); \ \ static void port_##n##_sam_config_func(const struct device *dev) \ { \ IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), \ gpio_sam_isr, \ DEVICE_DT_INST_GET(n), 0); \ irq_enable(DT_INST_IRQN(n)); \ } DT_INST_FOREACH_STATUS_OKAY(GPIO_SAM_INIT)