/* * Copyright (c) 2015 Intel Corporation. * Copyright (c) 2020 Norbit ODM AS * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT nxp_pca95xx /** * @file Driver for PCA95XX I2C-based GPIO driver. */ #include #include #include #include #include #include #include #include #include "gpio_utils.h" #define LOG_LEVEL CONFIG_GPIO_LOG_LEVEL #include LOG_MODULE_REGISTER(gpio_pca95xx); /* Register definitions */ #define REG_INPUT_PORT0 0x00 #define REG_INPUT_PORT1 0x01 #define REG_OUTPUT_PORT0 0x02 #define REG_OUTPUT_PORT1 0x03 #define REG_POL_INV_PORT0 0x04 #define REG_POL_INV_PORT1 0x05 #define REG_CONF_PORT0 0x06 #define REG_CONG_PORT1 0x07 #define REG_OUT_DRV_STRENGTH_PORT0_L 0x40 #define REG_OUT_DRV_STRENGTH_PORT0_H 0x41 #define REG_OUT_DRV_STRENGTH_PORT1_L 0x42 #define REG_OUT_DRV_STRENGTH_PORT1_H 0x43 #define REG_INPUT_LATCH_PORT0 0x44 #define REG_INPUT_LATCH_PORT1 0x45 #define REG_PUD_EN_PORT0 0x46 #define REG_PUD_EN_PORT1 0x47 #define REG_PUD_SEL_PORT0 0x48 #define REG_PUD_SEL_PORT1 0x49 #define REG_INT_MASK_PORT0 0x4A #define REG_INT_MASK_PORT1 0x4B #define REG_INT_STATUS_PORT0 0x4C #define REG_INT_STATUS_PORT1 0x4D #define REG_OUTPUT_PORT_CONF 0x4F /* Driver flags */ #define PCA_HAS_PUD BIT(0) #define PCA_HAS_INTERRUPT BIT(1) /** Configuration data */ struct gpio_pca95xx_config { /* gpio_driver_config needs to be first */ struct gpio_driver_config common; /** The master I2C device's name */ const char * const i2c_master_dev_name; /** The slave address of the chip */ uint16_t i2c_slave_addr; uint8_t capabilities; #ifdef CONFIG_GPIO_PCA95XX_INTERRUPT /* Interrupt pin definition */ const char *int_gpio_port; gpio_pin_t int_gpio_pin; gpio_flags_t int_gpio_flags; #endif }; /** Runtime driver data */ struct gpio_pca95xx_drv_data { /* gpio_driver_data needs to be first */ struct gpio_driver_data common; /** Master I2C device */ const struct device *i2c_master; struct { uint16_t input; uint16_t output; uint16_t dir; uint16_t pud_en; uint16_t pud_sel; } reg_cache; struct k_sem lock; #ifdef CONFIG_GPIO_PCA95XX_INTERRUPT /* Self-reference to the driver instance */ const struct device *instance; /* port ISR callback routine address */ sys_slist_t callbacks; /* interrupt triggering pin masks */ struct { uint16_t edge_rising; uint16_t edge_falling; uint16_t level_high; uint16_t level_low; } interrupts; struct gpio_callback gpio_callback; struct k_work interrupt_worker; bool interrupt_active; #endif }; /** * @brief Read both port 0 and port 1 registers of certain register function. * * Given the register in reg, read the pair of port 0 and port 1. * * @param dev Device struct of the PCA95XX. * @param reg Register to read (the PORT0 of the pair of registers). * @param cache Pointer to the cache to be updated after successful read. * @param buf Buffer to read data into. * * @return 0 if successful, failed otherwise. */ static int read_port_regs(const struct device *dev, uint8_t reg, uint16_t *cache, uint16_t *buf) { const struct gpio_pca95xx_config * const config = dev->config; struct gpio_pca95xx_drv_data * const drv_data = (struct gpio_pca95xx_drv_data * const)dev->data; const struct device *i2c_master = drv_data->i2c_master; uint16_t i2c_addr = config->i2c_slave_addr; uint16_t port_data, value; int ret; ret = i2c_burst_read(i2c_master, i2c_addr, reg, (uint8_t *)&port_data, sizeof(port_data)); if (ret != 0) { LOG_ERR("PCA95XX[0x%X]: error reading register 0x%X (%d)", i2c_addr, reg, ret); return ret; } value = sys_le16_to_cpu(port_data); *cache = value; *buf = value; LOG_DBG("PCA95XX[0x%X]: Read: REG[0x%X] = 0x%X, REG[0x%X] = 0x%X", i2c_addr, reg, (*buf & 0xFF), (reg + 1), (*buf >> 8)); return 0; } /** * @brief Write both port 0 and port 1 registers of certain register function. * * Given the register in reg, write the pair of port 0 and port 1. * * @param dev Device struct of the PCA95XX. * @param reg Register to write into (the PORT0 of the pair of registers). * @param cache Pointer to the cache to be updated after successful write. * @param value New value to set. * * @return 0 if successful, failed otherwise. */ static int write_port_regs(const struct device *dev, uint8_t reg, uint16_t *cache, uint16_t value) { const struct gpio_pca95xx_config * const config = dev->config; struct gpio_pca95xx_drv_data * const drv_data = (struct gpio_pca95xx_drv_data * const)dev->data; const struct device *i2c_master = drv_data->i2c_master; uint16_t i2c_addr = config->i2c_slave_addr; uint16_t port_data; int ret; LOG_DBG("PCA95XX[0x%X]: Write: REG[0x%X] = 0x%X, REG[0x%X] = " "0x%X", i2c_addr, reg, (value & 0xFF), (reg + 1), (value >> 8)); port_data = sys_cpu_to_le16(value); ret = i2c_burst_write(i2c_master, i2c_addr, reg, (uint8_t *)&port_data, sizeof(port_data)); if (ret == 0) { *cache = value; } else { LOG_ERR("PCA95XX[0x%X]: error writing to register 0x%X " "(%d)", i2c_addr, reg, ret); } return ret; } static inline int update_input_regs(const struct device *dev, uint16_t *buf) { struct gpio_pca95xx_drv_data * const drv_data = (struct gpio_pca95xx_drv_data * const)dev->data; return read_port_regs(dev, REG_INPUT_PORT0, &drv_data->reg_cache.input, buf); } static inline int update_output_regs(const struct device *dev, uint16_t value) { struct gpio_pca95xx_drv_data * const drv_data = (struct gpio_pca95xx_drv_data * const)dev->data; return write_port_regs(dev, REG_OUTPUT_PORT0, &drv_data->reg_cache.output, value); } static inline int update_direction_regs(const struct device *dev, uint16_t value) { struct gpio_pca95xx_drv_data * const drv_data = (struct gpio_pca95xx_drv_data * const)dev->data; return write_port_regs(dev, REG_CONF_PORT0, &drv_data->reg_cache.dir, value); } static inline int update_pul_sel_regs(const struct device *dev, uint16_t value) { struct gpio_pca95xx_drv_data * const drv_data = (struct gpio_pca95xx_drv_data * const)dev->data; return write_port_regs(dev, REG_PUD_SEL_PORT0, &drv_data->reg_cache.pud_sel, value); } static inline int update_pul_en_regs(const struct device *dev, uint16_t value) { struct gpio_pca95xx_drv_data * const drv_data = (struct gpio_pca95xx_drv_data * const)dev->data; return write_port_regs(dev, REG_PUD_EN_PORT0, &drv_data->reg_cache.pud_en, value); } /** * @brief Setup the pin direction (input or output) * * @param dev Device struct of the PCA95XX * @param pin The pin number * @param flags Flags of pin or port * * @return 0 if successful, failed otherwise */ static int setup_pin_dir(const struct device *dev, uint32_t pin, int flags) { struct gpio_pca95xx_drv_data * const drv_data = (struct gpio_pca95xx_drv_data * const)dev->data; uint16_t reg_dir = drv_data->reg_cache.dir; uint16_t reg_out = drv_data->reg_cache.output; int ret; /* For each pin, 0 == output, 1 == input */ if ((flags & GPIO_OUTPUT) != 0U) { if ((flags & GPIO_OUTPUT_INIT_HIGH) != 0U) { reg_out |= BIT(pin); } else if ((flags & GPIO_OUTPUT_INIT_LOW) != 0U) { reg_out &= ~BIT(pin); } reg_dir &= ~BIT(pin); } else { reg_dir |= BIT(pin); } ret = update_output_regs(dev, reg_out); if (ret != 0) { return ret; } ret = update_direction_regs(dev, reg_dir); return ret; } /** * @brief Setup the pin pull up/pull down status * * @param dev Device struct of the PCA95XX * @param pin The pin number * @param flags Flags of pin or port * * @return 0 if successful, failed otherwise */ static int setup_pin_pullupdown(const struct device *dev, uint32_t pin, int flags) { const struct gpio_pca95xx_config * const config = dev->config; struct gpio_pca95xx_drv_data * const drv_data = (struct gpio_pca95xx_drv_data * const)dev->data; uint16_t reg_pud; int ret; if ((config->capabilities & PCA_HAS_PUD) == 0) { /* Chip does not support pull up/pull down */ if ((flags & (GPIO_PULL_UP | GPIO_PULL_DOWN)) != 0U) { return -ENOTSUP; } /* If both GPIO_PULL_UP and GPIO_PULL_DOWN are not set, * we should disable them in hardware. But need to skip * if the chip does not support pull up/pull down. */ return 0; } /* If disabling pull up/down, there is no need to set the selection * register. Just go straight to disabling. */ if ((flags & (GPIO_PULL_UP | GPIO_PULL_DOWN)) != 0U) { /* Setup pin pull up or pull down */ reg_pud = drv_data->reg_cache.pud_sel; /* pull down == 0, pull up == 1 */ WRITE_BIT(reg_pud, pin, (flags & GPIO_PULL_UP) != 0U); ret = update_pul_sel_regs(dev, reg_pud); if (ret) { return ret; } } /* enable/disable pull up/down */ reg_pud = drv_data->reg_cache.pud_en; WRITE_BIT(reg_pud, pin, (flags & (GPIO_PULL_UP | GPIO_PULL_DOWN)) != 0U); ret = update_pul_en_regs(dev, reg_pud); return ret; } /** * @brief Configure pin or port * * @param dev Device struct of the PCA95XX * @param pin The pin number * @param flags Flags of pin or port * * @return 0 if successful, failed otherwise */ static int gpio_pca95xx_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) { int ret; struct gpio_pca95xx_drv_data * const drv_data = (struct gpio_pca95xx_drv_data * const)dev->data; #if (CONFIG_GPIO_LOG_LEVEL >= LOG_LEVEL_DEBUG) const struct gpio_pca95xx_config * const config = dev->config; uint16_t i2c_addr = config->i2c_slave_addr; #endif /* Does not support disconnected pin */ if ((flags & (GPIO_INPUT | GPIO_OUTPUT)) == GPIO_DISCONNECTED) { return -ENOTSUP; } /* Open-drain support is per port, not per pin. * So can't really support the API as-is. */ if ((flags & GPIO_SINGLE_ENDED) != 0U) { return -ENOTSUP; } /* Can't do I2C bus operations from an ISR */ if (k_is_in_isr()) { return -EWOULDBLOCK; } k_sem_take(&drv_data->lock, K_FOREVER); ret = setup_pin_dir(dev, pin, flags); if (ret) { LOG_ERR("PCA95XX[0x%X]: error setting pin direction (%d)", i2c_addr, ret); goto done; } ret = setup_pin_pullupdown(dev, pin, flags); if (ret) { LOG_ERR("PCA95XX[0x%X]: error setting pin pull up/down " "(%d)", i2c_addr, ret); goto done; } done: k_sem_give(&drv_data->lock); return ret; } static int gpio_pca95xx_port_get_raw(const struct device *dev, uint32_t *value) { struct gpio_pca95xx_drv_data * const drv_data = (struct gpio_pca95xx_drv_data * const)dev->data; uint16_t buf; int ret; /* Can't do I2C bus operations from an ISR */ if (k_is_in_isr()) { return -EWOULDBLOCK; } k_sem_take(&drv_data->lock, K_FOREVER); ret = update_input_regs(dev, &buf); if (ret != 0) { goto done; } *value = buf; done: k_sem_give(&drv_data->lock); return ret; } static int gpio_pca95xx_port_set_masked_raw(const struct device *dev, uint32_t mask, uint32_t value) { struct gpio_pca95xx_drv_data * const drv_data = (struct gpio_pca95xx_drv_data * const)dev->data; uint16_t reg_out; int ret; /* Can't do I2C bus operations from an ISR */ if (k_is_in_isr()) { return -EWOULDBLOCK; } k_sem_take(&drv_data->lock, K_FOREVER); reg_out = drv_data->reg_cache.output; reg_out = (reg_out & ~mask) | (mask & value); ret = update_output_regs(dev, reg_out); k_sem_give(&drv_data->lock); return ret; } static int gpio_pca95xx_port_set_bits_raw(const struct device *dev, uint32_t mask) { return gpio_pca95xx_port_set_masked_raw(dev, mask, mask); } static int gpio_pca95xx_port_clear_bits_raw(const struct device *dev, uint32_t mask) { return gpio_pca95xx_port_set_masked_raw(dev, mask, 0); } static int gpio_pca95xx_port_toggle_bits(const struct device *dev, uint32_t mask) { struct gpio_pca95xx_drv_data * const drv_data = (struct gpio_pca95xx_drv_data * const)dev->data; uint16_t reg_out; int ret; /* Can't do I2C bus operations from an ISR */ if (k_is_in_isr()) { return -EWOULDBLOCK; } k_sem_take(&drv_data->lock, K_FOREVER); reg_out = drv_data->reg_cache.output; reg_out ^= mask; ret = update_output_regs(dev, reg_out); k_sem_give(&drv_data->lock); return ret; } #ifdef CONFIG_GPIO_PCA95XX_INTERRUPT static void gpio_pca95xx_interrupt_worker(struct k_work *work) { struct gpio_pca95xx_drv_data * const drv_data = CONTAINER_OF( work, struct gpio_pca95xx_drv_data, interrupt_worker); uint16_t input_new, input_cache, changed_pins, trig_edge; uint16_t trig_level = 0; uint32_t triggered_int = 0; int ret; k_sem_take(&drv_data->lock, K_FOREVER); input_cache = drv_data->reg_cache.input; ret = update_input_regs(drv_data->instance, &input_new); if (ret == 0) { /* Note: PCA Interrupt status is cleared by reading inputs */ changed_pins = (input_cache ^ input_new); trig_edge = (changed_pins & input_new & drv_data->interrupts.edge_rising); trig_edge |= (changed_pins & input_cache & drv_data->interrupts.edge_falling); trig_level = (input_new & drv_data->interrupts.level_high); trig_level |= (~input_new & drv_data->interrupts.level_low); triggered_int = (trig_edge | trig_level); } k_sem_give(&drv_data->lock); if (triggered_int != 0) { gpio_fire_callbacks(&drv_data->callbacks, drv_data->instance, triggered_int); } /* Emulate level triggering */ if (trig_level != 0) { /* Reschedule worker */ k_work_submit(&drv_data->interrupt_worker); } } static void gpio_pca95xx_interrupt_callback(const struct device *dev, struct gpio_callback *cb, gpio_port_pins_t pins) { struct gpio_pca95xx_drv_data * const drv_data = CONTAINER_OF(cb, struct gpio_pca95xx_drv_data, gpio_callback); ARG_UNUSED(pins); /* Cannot read PCA95xx registers from ISR context, queue worker */ k_work_submit(&drv_data->interrupt_worker); } #endif /* CONFIG_GPIO_PCA95XX_INTERRUPT */ static int gpio_pca95xx_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin, enum gpio_int_mode mode, enum gpio_int_trig trig) { int ret = 0; if (!IS_ENABLED(CONFIG_GPIO_PCA95XX_INTERRUPT) && (mode != GPIO_INT_MODE_DISABLED)) { return -ENOTSUP; } #ifdef CONFIG_GPIO_PCA95XX_INTERRUPT const struct gpio_pca95xx_config * const config = dev->config; struct gpio_pca95xx_drv_data * const drv_data = (struct gpio_pca95xx_drv_data * const)dev->data; const struct device *int_gpio_dev; uint16_t reg; bool enabled, edge, level, active; /* Check if GPIO port supports interrupts */ if ((config->capabilities & PCA_HAS_INTERRUPT) == 0U) { return -ENOTSUP; } /* Check for an invalid pin number */ if (BIT(pin) > config->common.port_pin_mask) { return -EINVAL; } /* Check configured pin direction */ if ((mode != GPIO_INT_MODE_DISABLED) && (BIT(pin) & drv_data->reg_cache.dir) != BIT(pin)) { LOG_ERR("PCA95XX[0x%X]: output pin cannot trigger interrupt", config->i2c_slave_addr); return -ENOTSUP; } k_sem_take(&drv_data->lock, K_FOREVER); /* Update interrupt masks */ enabled = ((mode & GPIO_INT_MODE_DISABLED) == 0U); edge = (mode == GPIO_INT_MODE_EDGE); level = (mode == GPIO_INT_MODE_LEVEL); WRITE_BIT(drv_data->interrupts.edge_rising, pin, (enabled && edge && ((trig & GPIO_INT_TRIG_HIGH) == GPIO_INT_TRIG_HIGH))); WRITE_BIT(drv_data->interrupts.edge_falling, pin, (enabled && edge && ((trig & GPIO_INT_TRIG_LOW) == GPIO_INT_TRIG_LOW))); WRITE_BIT(drv_data->interrupts.level_high, pin, (enabled && level && ((trig & GPIO_INT_TRIG_HIGH) == GPIO_INT_TRIG_HIGH))); WRITE_BIT(drv_data->interrupts.level_low, pin, (enabled && level && ((trig & GPIO_INT_TRIG_LOW) == GPIO_INT_TRIG_LOW))); active = ((drv_data->interrupts.edge_rising || drv_data->interrupts.edge_falling || drv_data->interrupts.level_high || drv_data->interrupts.level_low) > 0); /* Enable / disable interrupt as needed */ if (active != drv_data->interrupt_active) { int_gpio_dev = device_get_binding(config->int_gpio_port); ret = gpio_pin_interrupt_configure(int_gpio_dev, config->int_gpio_pin, (active ? GPIO_INT_EDGE_TO_ACTIVE : GPIO_INT_MODE_DISABLED)); if (ret != 0) { LOG_ERR("PCA95XX[0x%X]: failed to configure interrupt " "on pin %d (%d)", config->i2c_slave_addr, config->int_gpio_pin, ret); goto err; } drv_data->interrupt_active = active; if (active) { /* Read current status to reset any * active signal on INT line */ update_input_regs(dev, ®); } } err: k_sem_give(&drv_data->lock); #endif /* CONFIG_GPIO_PCA95XX_INTERRUPT */ return ret; } #ifdef CONFIG_GPIO_PCA95XX_INTERRUPT static int gpio_pca95xx_manage_callback(const struct device *dev, struct gpio_callback *callback, bool set) { const struct gpio_pca95xx_config * const config = dev->config; struct gpio_pca95xx_drv_data * const drv_data = (struct gpio_pca95xx_drv_data * const)dev->data; if ((config->capabilities & PCA_HAS_INTERRUPT) == 0U) { return -ENOTSUP; } k_sem_take(&drv_data->lock, K_FOREVER); gpio_manage_callback(&drv_data->callbacks, callback, set); k_sem_give(&drv_data->lock); return 0; } #endif static const struct gpio_driver_api gpio_pca95xx_drv_api_funcs = { .pin_configure = gpio_pca95xx_config, .port_get_raw = gpio_pca95xx_port_get_raw, .port_set_masked_raw = gpio_pca95xx_port_set_masked_raw, .port_set_bits_raw = gpio_pca95xx_port_set_bits_raw, .port_clear_bits_raw = gpio_pca95xx_port_clear_bits_raw, .port_toggle_bits = gpio_pca95xx_port_toggle_bits, .pin_interrupt_configure = gpio_pca95xx_pin_interrupt_configure, #ifdef CONFIG_GPIO_PCA95XX_INTERRUPT .manage_callback = gpio_pca95xx_manage_callback, #endif }; /** * @brief Initialization function of PCA95XX * * @param dev Device struct * @return 0 if successful, failed otherwise. */ static int gpio_pca95xx_init(const struct device *dev) { const struct gpio_pca95xx_config * const config = dev->config; struct gpio_pca95xx_drv_data * const drv_data = (struct gpio_pca95xx_drv_data * const)dev->data; const struct device *i2c_master; /* Find out the device struct of the I2C master */ i2c_master = device_get_binding((char *)config->i2c_master_dev_name); if (!i2c_master) { return -EINVAL; } drv_data->i2c_master = i2c_master; k_sem_init(&drv_data->lock, 1, 1); #ifdef CONFIG_GPIO_PCA95XX_INTERRUPT /* Check if GPIO port supports interrupts */ if ((config->capabilities & PCA_HAS_INTERRUPT) != 0) { const struct device *int_gpio_dev; int ret; /* Store self-reference for interrupt handling */ drv_data->instance = dev; /* Prepare interrupt worker */ k_work_init(&drv_data->interrupt_worker, gpio_pca95xx_interrupt_worker); /* Configure GPIO interrupt pin */ int_gpio_dev = device_get_binding(config->int_gpio_port); if (int_gpio_dev == NULL) { LOG_ERR("PCA95XX[0x%X]: error getting interrupt GPIO" " device (%s)", config->i2c_slave_addr, config->int_gpio_port); return -ENODEV; } ret = gpio_pin_configure(int_gpio_dev, config->int_gpio_pin, (config->int_gpio_flags | GPIO_INPUT)); if (ret != 0) { LOG_ERR("PCA95XX[0x%X]: failed to configure interrupt" " pin %d (%d)", config->i2c_slave_addr, config->int_gpio_pin, ret); return ret; } /* Prepare GPIO callback for interrupt pin */ gpio_init_callback(&drv_data->gpio_callback, gpio_pca95xx_interrupt_callback, BIT(config->int_gpio_pin)); gpio_add_callback(int_gpio_dev, &drv_data->gpio_callback); } #endif return 0; } #define GPIO_PCA95XX_DEVICE_INSTANCE(inst) \ static const struct gpio_pca95xx_config gpio_pca95xx_##inst##_cfg = { \ .common = { \ .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(inst), \ }, \ .i2c_master_dev_name = DT_INST_BUS_LABEL(inst), \ .i2c_slave_addr = DT_INST_REG_ADDR(inst), \ .capabilities = \ (DT_INST_PROP(inst, has_pud) ? \ PCA_HAS_PUD : 0) | \ IF_ENABLED(CONFIG_GPIO_PCA95XX_INTERRUPT, ( \ (DT_INST_NODE_HAS_PROP(inst, interrupt_gpios) ? \ PCA_HAS_INTERRUPT : 0) | \ )) \ 0, \ IF_ENABLED(CONFIG_GPIO_PCA95XX_INTERRUPT, ( \ IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, interrupt_gpios), ( \ .int_gpio_port = DT_INST_GPIO_LABEL(inst, interrupt_gpios), \ .int_gpio_pin = DT_INST_GPIO_PIN(inst, interrupt_gpios), \ .int_gpio_flags = DT_INST_GPIO_FLAGS(inst, interrupt_gpios), \ )))) \ }; \ \ static struct gpio_pca95xx_drv_data gpio_pca95xx_##inst##_drvdata = { \ .reg_cache.input = 0x0, \ .reg_cache.output = 0xFFFF, \ .reg_cache.dir = 0xFFFF, \ .reg_cache.pud_en = 0x0, \ .reg_cache.pud_sel = 0xFFFF, \ IF_ENABLED(CONFIG_GPIO_PCA95XX_INTERRUPT, ( \ .interrupt_active = false, \ )) \ }; \ \ DEVICE_DT_INST_DEFINE(inst, \ gpio_pca95xx_init, \ NULL, \ &gpio_pca95xx_##inst##_drvdata, \ &gpio_pca95xx_##inst##_cfg, \ POST_KERNEL, CONFIG_GPIO_PCA95XX_INIT_PRIORITY, \ &gpio_pca95xx_drv_api_funcs); DT_INST_FOREACH_STATUS_OKAY(GPIO_PCA95XX_DEVICE_INSTANCE)