/* * SPDX-License-Identifier: Apache-2.0 * * GPIO driver for the CC2650 SOC from Texas Instruments. */ #include #include #include #include #include #include #include "gpio_utils.h" struct gpio_cc2650_data { u32_t pin_callback_enables; sys_slist_t callbacks; }; /* Pre-declarations */ static int gpio_cc2650_init(struct device *dev); static int gpio_cc2650_config(struct device *port, int access_op, u32_t pin, int flags); static int gpio_cc2650_write(struct device *port, int access_op, u32_t pin, u32_t value); static int gpio_cc2650_read(struct device *port, int access_op, u32_t pin, u32_t *value); static int gpio_cc2650_manage_callback(struct device *port, struct gpio_callback *callback, bool set); static int gpio_cc2650_enable_callback(struct device *port, int access_op, u32_t pin); static int gpio_cc2650_disable_callback(struct device *port, int access_op, u32_t pin); static u32_t gpio_cc2650_get_pending_int(struct device *dev); /* GPIO registers */ static const u32_t doutset31_0 = REG_ADDR(DT_TI_CC2650_GPIO_40022000_BASE_ADDRESS, CC2650_GPIO_DOUTSET31_0); static const u32_t doutclr31_0 = REG_ADDR(DT_TI_CC2650_GPIO_40022000_BASE_ADDRESS, CC2650_GPIO_DOUTCLR31_0); static const u32_t din31_0 = REG_ADDR(DT_TI_CC2650_GPIO_40022000_BASE_ADDRESS, CC2650_GPIO_DIN31_0); static const u32_t doe31_0 = REG_ADDR(DT_TI_CC2650_GPIO_40022000_BASE_ADDRESS, CC2650_GPIO_DOE31_0); static const u32_t evflags31_0 = REG_ADDR(DT_TI_CC2650_GPIO_40022000_BASE_ADDRESS, CC2650_GPIO_EVFLAGS31_0); static struct gpio_cc2650_data gpio_cc2650_data = { .pin_callback_enables = 0 }; static const struct gpio_driver_api gpio_cc2650_funcs = { .config = gpio_cc2650_config, .write = gpio_cc2650_write, .read = gpio_cc2650_read, .manage_callback = gpio_cc2650_manage_callback, .enable_callback = gpio_cc2650_enable_callback, .disable_callback = gpio_cc2650_disable_callback, .get_pending_int = gpio_cc2650_get_pending_int }; DEVICE_AND_API_INIT(gpio_cc2650_0, CONFIG_GPIO_CC2650_NAME, gpio_cc2650_init, &gpio_cc2650_data, NULL, PRE_KERNEL_1, CONFIG_GPIO_CC2650_INIT_PRIO, &gpio_cc2650_funcs); static void disconnect(const int pin, u32_t *gpiodoe31_0, u32_t *iocfg) { *gpiodoe31_0 &= ~BIT(pin); *iocfg &= ~(CC2650_IOC_IOCFGX_PULL_CTL_MASK | CC2650_IOC_IOCFGX_IE_MASK); *iocfg |= CC2650_IOC_INPUT_DISABLED | CC2650_IOC_NO_PULL; } /* Configure a single pin. * If any asked option is not implementable, rollback entirely to * previous configuration. * * Note: For pin drive strength, the CC2650 devices only support * symmetric sink/source capabilities. * Thus, you may ONLY determine the common drive strength with * GPIO *low output state* flags. Flags for *high output state* * will be ignored. */ static int gpio_cc2650_config_pin(int pin, int flags) { const u32_t iocfg = REG_ADDR(DT_TI_CC2650_PINMUX_40081000_BASE_ADDRESS, CC2650_IOC_IOCFG0 + 0x4 * pin); u32_t iocfg_config = sys_read32(iocfg); u32_t gpio_doe31_0_config = sys_read32(doe31_0); /* Reset all configurable fields to 0 */ iocfg_config &= ~(CC2650_IOC_IOCFGX_IOSTR_MASK | CC2650_IOC_IOCFGX_PULL_CTL_MASK | CC2650_IOC_IOCFGX_EDGE_DET_MASK | CC2650_IOC_IOCFGX_EDGE_IRQ_EN_MASK | CC2650_IOC_IOCFGX_IOMODE_MASK | CC2650_IOC_IOCFGX_IE_MASK | CC2650_IOC_IOCFGX_HYST_EN_MASK); if (flags & GPIO_DIR_OUT) { gpio_doe31_0_config |= BIT(pin); iocfg_config |= CC2650_IOC_INPUT_DISABLED; } else { gpio_doe31_0_config &= ~BIT(pin); iocfg_config |= CC2650_IOC_INPUT_ENABLED; } if (flags & GPIO_INT) { if (!(flags & GPIO_INT_EDGE) && !(flags & GPIO_INT_DOUBLE_EDGE)) { /* Can't do level-based interrupt */ /* Don't commit changes */ return -ENOTSUP; } iocfg_config |= BIT(CC2650_IOC_IOCFGX_EDGE_IRQ_EN_POS); if (flags & GPIO_INT_EDGE) { if (flags & GPIO_INT_ACTIVE_HIGH) { iocfg_config |= CC2650_IOC_POS_EDGE_DET; } else { iocfg_config |= CC2650_IOC_NEG_EDGE_DET; } } else if (flags & GPIO_INT_DOUBLE_EDGE) { iocfg_config |= CC2650_IOC_NEG_AND_POS_EDGE_DET; } if (flags & GPIO_INT_DEBOUNCE) { iocfg_config |= CC2650_IOC_HYSTERESIS_ENABLED; } else { iocfg_config |= CC2650_IOC_HYSTERESIS_DISABLED; } } if (flags & GPIO_POL_INV) { iocfg_config |= CC2650_IOC_INVERTED_IO; } else { iocfg_config |= CC2650_IOC_NORMAL_IO; } if (flags & GPIO_PUD_PULL_UP) { iocfg_config |= CC2650_IOC_PULL_UP; } else if (flags & GPIO_PUD_PULL_DOWN) { iocfg_config |= CC2650_IOC_PULL_DOWN; } else { iocfg_config |= CC2650_IOC_NO_PULL; } /* Remember, we only look at GPIO_DS_*_LOW ! */ if (flags & GPIO_DS_DISCONNECT_LOW) { disconnect(pin, &gpio_doe31_0_config, &iocfg_config); } if (flags & GPIO_DS_ALT_LOW) { iocfg_config |= CC2650_IOC_MAX_DRIVE_STRENGTH; } else { iocfg_config |= CC2650_IOC_MIN_DRIVE_STRENGTH; } /* Commit changes */ sys_write32(iocfg_config, iocfg); sys_write32(gpio_doe31_0_config, doe31_0); return 0; } static inline void gpio_cc2650_write_pin(int pin, u32_t value) { value ? sys_write32(BIT(pin), doutset31_0) : sys_write32(BIT(pin), doutclr31_0); } static inline void gpio_cc2650_read_pin(int pin, u32_t *value) { *value = sys_read32(din31_0) & BIT(pin); } static void gpio_cc2650_isr(void *arg) { struct device *dev = (struct device *)arg; struct gpio_cc2650_data *data = dev->driver_data; const u32_t events = sys_read32(evflags31_0); const u32_t call_mask = events & data->pin_callback_enables; /* Clear GPIO trigger events */ u32_t evflags = sys_read32(evflags31_0); sys_write32(evflags | call_mask, evflags31_0); _gpio_fire_callbacks(&data->callbacks, dev, call_mask); } static int gpio_cc2650_init(struct device *dev) { ARG_UNUSED(dev); /* ISR setup */ IRQ_CONNECT(DT_TI_CC2650_GPIO_40022000_IRQ_0, DT_TI_CC2650_GPIO_40022000_IRQ_0_PRIORITY, gpio_cc2650_isr, DEVICE_GET(gpio_cc2650_0), 0); irq_enable(DT_TI_CC2650_GPIO_40022000_IRQ_0); return 0; } static int gpio_cc2650_config(struct device *port, int access_op, u32_t pin, int flags) { ARG_UNUSED(port); if (access_op == GPIO_ACCESS_BY_PIN) { return gpio_cc2650_config_pin(pin, flags); } const u32_t nb_pins = 32U; for (u8_t i = 0; i < nb_pins; ++i) { if (pin & 0x1 && gpio_cc2650_config_pin(i, flags) == -ENOTSUP) { /* The flags being treated the same for * every pin, if we get here then it's * necessarily the first pin on which we act. * * We expect gpio_cc2650_config_pin() to * NOT commit its changes if any problem * arises, thus we do nothing special here * to implement rollback to previous * configuration. */ return -ENOTSUP; } pin >>= 1; } return 0; } static int gpio_cc2650_write(struct device *port, int access_op, u32_t pin, u32_t value) { ARG_UNUSED(port); if (access_op == GPIO_ACCESS_BY_PIN) { gpio_cc2650_write_pin(pin, value); } else { const u32_t nb_pins = 32U; for (u32_t i = 0; i < nb_pins; ++i) { if (pin & 0x1) { gpio_cc2650_write_pin(i, value); } pin >>= 1; } } return 0; } static int gpio_cc2650_read(struct device *port, int access_op, u32_t pin, u32_t *value) { ARG_UNUSED(port); if (access_op == GPIO_ACCESS_BY_PIN) { gpio_cc2650_read_pin(pin, value); *value >>= pin; } else { const u32_t nb_pins = 32U; for (u32_t i = 0; i < nb_pins; ++i) { if (pin & 0x1) { gpio_cc2650_read_pin(i, value); } pin >>= 1; } } return 0; } static int gpio_cc2650_manage_callback(struct device *port, struct gpio_callback *callback, bool set) { struct gpio_cc2650_data *data = port->driver_data; return _gpio_manage_callback(&data->callbacks, callback, set); } static int gpio_cc2650_enable_callback(struct device *port, int access_op, u32_t pin) { struct gpio_cc2650_data *data = port->driver_data; if (access_op == GPIO_ACCESS_BY_PIN) { data->pin_callback_enables |= BIT(pin); } else { data->pin_callback_enables |= pin; } return 0; } static int gpio_cc2650_disable_callback(struct device *port, int access_op, u32_t pin) { struct gpio_cc2650_data *data = port->driver_data; if (access_op == GPIO_ACCESS_BY_PIN) { data->pin_callback_enables &= ~BIT(pin); } else { data->pin_callback_enables &= ~pin; } return 0; } static u32_t gpio_cc2650_get_pending_int(struct device *dev) { ARG_UNUSED(dev); return sys_read32(evflags31_0); }