|  | /* | 
|  | * Copyright (c) 2015 Intel Corporation. | 
|  | * Copyright (c) 2020 Norbit ODM AS | 
|  | * Copyright 2022 NXP | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT nxp_pca95xx | 
|  |  | 
|  | /** | 
|  | * @file Driver for PCA95XX and PCAL95XX I2C-based GPIO driver. | 
|  | */ | 
|  |  | 
|  | #include <errno.h> | 
|  |  | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/device.h> | 
|  | #include <zephyr/init.h> | 
|  | #include <zephyr/sys/byteorder.h> | 
|  | #include <zephyr/sys/util.h> | 
|  | #include <zephyr/drivers/gpio.h> | 
|  | #include <zephyr/drivers/i2c.h> | 
|  |  | 
|  | #include <zephyr/drivers/gpio/gpio_utils.h> | 
|  |  | 
|  | #define LOG_LEVEL CONFIG_GPIO_LOG_LEVEL | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_REGISTER(gpio_pca95xx); | 
|  |  | 
|  | #if CONFIG_LITTLE_ENDIAN | 
|  | #define LOW_BYTE_LE16_IDX	0 | 
|  | #define HIGH_BYTE_LE16_IDX	1 | 
|  | #else | 
|  | #define LOW_BYTE_LE16_IDX	1 | 
|  | #define HIGH_BYTE_LE16_IDX	0 | 
|  | #endif | 
|  |  | 
|  | /* 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_CONF_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) | 
|  | #define PCA_HAS_INTERRUPT_MASK_REG	BIT(2) | 
|  |  | 
|  | /** Configuration data */ | 
|  | struct gpio_pca95xx_config { | 
|  | /* gpio_driver_config needs to be first */ | 
|  | struct gpio_driver_config common; | 
|  | struct i2c_dt_spec bus; | 
|  | uint8_t capabilities; | 
|  | #ifdef CONFIG_GPIO_PCA95XX_INTERRUPT | 
|  | struct gpio_dt_spec int_gpio; | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | /** Runtime driver data */ | 
|  | struct gpio_pca95xx_drv_data { | 
|  | /* gpio_driver_data needs to be first */ | 
|  | struct gpio_driver_data common; | 
|  |  | 
|  | struct { | 
|  | uint16_t input; | 
|  | uint16_t output; | 
|  | uint16_t dir; | 
|  | uint16_t pud_en; | 
|  | uint16_t pud_sel; | 
|  | uint16_t int_mask; | 
|  | uint16_t input_latch; | 
|  | } 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; | 
|  | uint16_t port_data, value; | 
|  | int ret; | 
|  |  | 
|  | ret = i2c_burst_read_dt(&config->bus, reg, (uint8_t *)&port_data, | 
|  | sizeof(port_data)); | 
|  | if (ret != 0) { | 
|  | LOG_ERR("PCA95XX[0x%X]: error reading register 0x%X (%d)", | 
|  | config->bus.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", | 
|  | config->bus.addr, reg, (*buf & 0xFF), (reg + 1), (*buf >> 8)); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | static int write_port_reg(const struct device *dev, uint8_t reg, uint8_t pin, | 
|  | uint16_t *cache, uint16_t value) | 
|  | { | 
|  | const struct gpio_pca95xx_config * const config = dev->config; | 
|  | uint8_t buf[2]; | 
|  | int ret; | 
|  |  | 
|  | if (pin < 8) { | 
|  | buf[1] = value; | 
|  | } else { | 
|  | buf[1] = value >> 8; | 
|  | reg++; | 
|  | } | 
|  | buf[0] = reg; | 
|  |  | 
|  | LOG_DBG("PCA95XX[0x%X]: Write: REG[0x%X] = 0x%X", config->bus.addr, | 
|  | reg, buf[1]); | 
|  |  | 
|  | ret = i2c_write_dt(&config->bus, buf, sizeof(buf)); | 
|  | if (ret == 0) { | 
|  | *cache = value; | 
|  | } else { | 
|  | LOG_ERR("PCA95XX[0x%X]: error writing to register 0x%X " | 
|  | "(%d)", config->bus.addr, reg, ret); | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @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; | 
|  | uint8_t buf[3]; | 
|  | int ret; | 
|  |  | 
|  | LOG_DBG("PCA95XX[0x%X]: Write: REG[0x%X] = 0x%X, REG[0x%X] = " | 
|  | "0x%X", config->bus.addr, reg, (value & 0xFF), | 
|  | (reg + 1), (value >> 8)); | 
|  |  | 
|  | buf[0] = reg; | 
|  | sys_put_le16(value, &buf[1]); | 
|  |  | 
|  | ret = i2c_write_dt(&config->bus, buf, sizeof(buf)); | 
|  | if (ret == 0) { | 
|  | *cache = value; | 
|  | } else { | 
|  | LOG_ERR("PCA95XX[0x%X]: error writing to register 0x%X " | 
|  | "(%d)", config->bus.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_reg(const struct device *dev, uint8_t pin, | 
|  | uint16_t value) | 
|  | { | 
|  | struct gpio_pca95xx_drv_data * const drv_data = | 
|  | (struct gpio_pca95xx_drv_data * const)dev->data; | 
|  |  | 
|  | return write_port_reg(dev, REG_OUTPUT_PORT0, pin, | 
|  | &drv_data->reg_cache.output, value); | 
|  | } | 
|  |  | 
|  | 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_reg(const struct device *dev, uint8_t pin, | 
|  | uint16_t value) | 
|  | { | 
|  | struct gpio_pca95xx_drv_data * const drv_data = | 
|  | (struct gpio_pca95xx_drv_data * const)dev->data; | 
|  |  | 
|  | return write_port_reg(dev, REG_CONF_PORT0, pin, | 
|  | &drv_data->reg_cache.dir, 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_reg(const struct device *dev, uint8_t pin, | 
|  | uint16_t value) | 
|  | { | 
|  | struct gpio_pca95xx_drv_data * const drv_data = | 
|  | (struct gpio_pca95xx_drv_data * const)dev->data; | 
|  |  | 
|  | return write_port_reg(dev, REG_PUD_SEL_PORT0, pin, | 
|  | &drv_data->reg_cache.pud_sel, value); | 
|  | } | 
|  |  | 
|  | static inline int update_pul_en_reg(const struct device *dev, uint8_t pin, | 
|  | uint8_t value) | 
|  | { | 
|  | struct gpio_pca95xx_drv_data * const drv_data = | 
|  | (struct gpio_pca95xx_drv_data * const)dev->data; | 
|  |  | 
|  | return write_port_reg(dev, REG_PUD_EN_PORT0, pin, | 
|  | &drv_data->reg_cache.pud_en, value); | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_GPIO_PCA95XX_INTERRUPT | 
|  | static inline int update_int_mask_reg(const struct device *dev, uint8_t pin, | 
|  | uint16_t value) | 
|  | { | 
|  | struct gpio_pca95xx_drv_data * const drv_data = | 
|  | (struct gpio_pca95xx_drv_data * const)dev->data; | 
|  |  | 
|  | /* If the interrupt mask is present, so is the input latch */ | 
|  | write_port_reg(dev, REG_INPUT_LATCH_PORT0, pin, &drv_data->reg_cache.input_latch, ~value); | 
|  |  | 
|  | return write_port_reg(dev, REG_INT_MASK_PORT0, pin, | 
|  | &drv_data->reg_cache.int_mask, value); | 
|  | } | 
|  | #endif /* CONFIG_GPIO_PCA95XX_INTERRUPT */ | 
|  |  | 
|  | /** | 
|  | * @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); | 
|  | } | 
|  | ret = update_output_reg(dev, pin, reg_out); | 
|  | if (ret != 0) { | 
|  | return ret; | 
|  | } | 
|  | reg_dir &= ~BIT(pin); | 
|  | } else { | 
|  | reg_dir |= BIT(pin); | 
|  | } | 
|  |  | 
|  | ret = update_direction_reg(dev, pin, reg_dir); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Initialize pins to default state | 
|  | * | 
|  | * @param dev Device struct of the PCA95XX | 
|  | * | 
|  | * @return 0 if successful, failed otherwise | 
|  | */ | 
|  | static int init_pins(const struct device *dev) | 
|  | { | 
|  | return update_direction_regs(dev, 0xFFFF); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @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_reg(dev, pin, 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_reg(dev, pin, 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; | 
|  | #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)", | 
|  | config->bus.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)", config->bus.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 get_triggered_it(struct gpio_pca95xx_drv_data *drv_data, | 
|  | uint16_t *trig_edge, uint16_t *trig_level) | 
|  | { | 
|  | uint16_t input_cache, changed_pins, input_new; | 
|  | int ret; | 
|  |  | 
|  | input_cache = drv_data->reg_cache.input; | 
|  | ret = update_input_regs(drv_data->instance, &input_new); | 
|  | if (ret == 0) { | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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); | 
|  | const struct gpio_pca95xx_config * const config = drv_data->instance->config; | 
|  | uint16_t trig_edge = 0, trig_level = 0; | 
|  | uint32_t triggered_int = 0; | 
|  |  | 
|  | k_sem_take(&drv_data->lock, K_FOREVER); | 
|  |  | 
|  | /* Note: PCA Interrupt status is cleared by reading inputs */ | 
|  | if (config->capabilities & PCA_HAS_INTERRUPT_MASK_REG) { | 
|  | /* gpio latched read values, to be compared to cached ones */ | 
|  | get_triggered_it(drv_data, &trig_edge, &trig_level); | 
|  | } | 
|  | /* gpio unlatched read values, in case signal has flipped again */ | 
|  | get_triggered_it(drv_data, &trig_edge, &trig_level); | 
|  |  | 
|  | 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); | 
|  | } | 
|  |  | 
|  | 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; | 
|  | 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; | 
|  | 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->bus.addr); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | k_sem_take(&drv_data->lock, K_FOREVER); | 
|  |  | 
|  | /* Check if GPIO port has an interrupt mask register */ | 
|  | if (config->capabilities & PCA_HAS_INTERRUPT_MASK_REG) { | 
|  | uint16_t reg_out; | 
|  |  | 
|  | reg_out = drv_data->reg_cache.int_mask; | 
|  | WRITE_BIT(reg_out, pin, (mode == GPIO_INT_MODE_DISABLED)); | 
|  |  | 
|  | ret = update_int_mask_reg(dev, pin, reg_out); | 
|  | if (ret != 0) { | 
|  | LOG_ERR("PCA95XX[0x%X]: failed to update int mask (%d)", | 
|  | config->bus.addr, ret); | 
|  | goto err; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* 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) { | 
|  | ret = gpio_pin_interrupt_configure_dt( | 
|  | &config->int_gpio, 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->bus.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); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | 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 /* CONFIG_GPIO_PCA95XX_INTERRUPT */ | 
|  |  | 
|  | static DEVICE_API(gpio, 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, | 
|  | #ifdef CONFIG_GPIO_PCA95XX_INTERRUPT | 
|  | .pin_interrupt_configure = gpio_pca95xx_pin_interrupt_configure, | 
|  | .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; | 
|  | int ret; | 
|  |  | 
|  | if (!device_is_ready(config->bus.bus)) { | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | k_sem_init(&drv_data->lock, 1, 1); | 
|  |  | 
|  | ret = init_pins(dev); | 
|  | if (ret != 0) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_GPIO_PCA95XX_INTERRUPT | 
|  | /* Check if GPIO port supports interrupts */ | 
|  | if ((config->capabilities & PCA_HAS_INTERRUPT) != 0) { | 
|  | /* 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 */ | 
|  | if (!gpio_is_ready_dt(&config->int_gpio)) { | 
|  | LOG_ERR("PCA95XX[0x%X]: interrupt GPIO not ready", | 
|  | config->bus.addr); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | ret = gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT); | 
|  | if (ret != 0) { | 
|  | LOG_ERR("PCA95XX[0x%X]: failed to configure interrupt" | 
|  | " pin %d (%d)", config->bus.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)); | 
|  | ret = gpio_add_callback(config->int_gpio.port, &drv_data->gpio_callback); | 
|  | if (ret != 0) { | 
|  | LOG_ERR("PCA95XX[0x%X]: failed to add interrupt callback for" | 
|  | " pin %d (%d)", config->bus.addr, | 
|  | config->int_gpio.pin, ret); | 
|  | return ret; | 
|  | } | 
|  | } | 
|  | #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),	\ | 
|  | },								\ | 
|  | .bus = I2C_DT_SPEC_INST_GET(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) |			\ | 
|  | (DT_INST_PROP(inst, has_interrupt_mask_reg) ?		\ | 
|  | PCA_HAS_INTERRUPT_MASK_REG : 0) |		\ | 
|  | ))							\ | 
|  | 0,							\ | 
|  | IF_ENABLED(CONFIG_GPIO_PCA95XX_INTERRUPT,			\ | 
|  | (.int_gpio = GPIO_DT_SPEC_INST_GET_OR(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,					\ | 
|  | .reg_cache.int_mask = 0xFFFF,					\ | 
|  | .reg_cache.input_latch = 0x0,				\ | 
|  | 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) |