|  | /* | 
|  | * Copyright (c) 2018 Peter Bigot Consulting, LLC | 
|  | * Copyright (c) 2018 Aapo Vienamo | 
|  | * Copyright (c) 2019 Nordic Semiconductor ASA | 
|  | * Copyright (c) 2019 Vestas Wind Systems A/S | 
|  | * Copyright (c) 2020 ZedBlox Ltd. | 
|  | * Copyright (c) 2021 Laird Connectivity | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <kernel.h> | 
|  | #include <device.h> | 
|  | #include <init.h> | 
|  | #include <drivers/gpio.h> | 
|  | #include <drivers/i2c.h> | 
|  | #include <sys/byteorder.h> | 
|  | #include <sys/util.h> | 
|  |  | 
|  | #include <logging/log.h> | 
|  | LOG_MODULE_REGISTER(pca953x, CONFIG_GPIO_LOG_LEVEL); | 
|  |  | 
|  | #include "gpio_utils.h" | 
|  |  | 
|  | /* PCA953X Register addresses */ | 
|  | #define PCA953X_INPUT_PORT		0x00 | 
|  | #define PCA953X_OUTPUT_PORT		0x01 | 
|  | #define PCA953X_CONFIGURATION		0x03 | 
|  |  | 
|  | /* Number of pins supported by the device */ | 
|  | #define NUM_PINS 8 | 
|  |  | 
|  | /* Max to select all pins supported on the device. */ | 
|  | #define ALL_PINS ((uint8_t)BIT_MASK(NUM_PINS)) | 
|  |  | 
|  | /** Cache of the output configuration and data of the pins. */ | 
|  | struct pca953x_pin_state { | 
|  | uint8_t dir; | 
|  | uint8_t input; | 
|  | uint8_t output; | 
|  | }; | 
|  |  | 
|  | struct pca953x_irq_state { | 
|  | uint8_t rising; | 
|  | uint8_t falling; | 
|  | }; | 
|  |  | 
|  | /** Runtime driver data */ | 
|  | struct pca953x_drv_data { | 
|  | /* gpio_driver_data needs to be first */ | 
|  | struct gpio_driver_data common; | 
|  | struct pca953x_pin_state pin_state; | 
|  | struct k_sem lock; | 
|  | struct gpio_callback gpio_cb; | 
|  | struct k_work work; | 
|  | struct pca953x_irq_state irq_state; | 
|  | const struct device *dev; | 
|  | /* user ISR cb */ | 
|  | sys_slist_t cb; | 
|  | }; | 
|  |  | 
|  | /** Configuration data */ | 
|  | struct pca953x_config { | 
|  | /* gpio_driver_config needs to be first */ | 
|  | struct gpio_driver_config common; | 
|  | const struct device *i2c_dev; | 
|  | const struct gpio_dt_spec gpio_int; | 
|  | bool interrupt_enabled; | 
|  | uint8_t i2c_addr; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * @brief Gets the state of input pins of the PCA953X I/O Port and | 
|  | * stores in driver data struct. | 
|  | * | 
|  | * @param dev Pointer to the device structure for the driver instance. | 
|  | * | 
|  | * @retval 0 If successful. | 
|  | * @retval Negative value for error code. | 
|  | */ | 
|  | static int update_input(const struct device *dev) | 
|  | { | 
|  | const struct pca953x_config *cfg = dev->config; | 
|  | struct pca953x_drv_data *drv_data = dev->data; | 
|  | uint8_t input_states; | 
|  | int rc = 0; | 
|  |  | 
|  | rc = i2c_reg_read_byte(cfg->i2c_dev, cfg->i2c_addr, | 
|  | PCA953X_INPUT_PORT, &input_states); | 
|  |  | 
|  | if (rc == 0) { | 
|  | drv_data->pin_state.input = input_states; | 
|  | } | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Handles interrupt triggered by the interrupt pin of PCA953X I/O Port. | 
|  | * | 
|  | * If nint_gpios is configured in device tree then this will be triggered each | 
|  | * time a gpio configured as an input changes state. The gpio input states are | 
|  | * read in this function which clears the interrupt. | 
|  | * | 
|  | * @param dev Pointer to the device structure for the driver instance. | 
|  | */ | 
|  | static void gpio_pca953x_handle_interrupt(const struct device *dev) | 
|  | { | 
|  | struct pca953x_drv_data *drv_data = dev->data; | 
|  | struct pca953x_irq_state *irq_state = &drv_data->irq_state; | 
|  | int rc; | 
|  | uint8_t previous_state; | 
|  | uint8_t current_state; | 
|  | uint8_t transitioned_pins; | 
|  | uint8_t interrupt_status = 0; | 
|  |  | 
|  | k_sem_take(&drv_data->lock, K_FOREVER); | 
|  |  | 
|  | /* Any interrupts enabled? */ | 
|  | if (!irq_state->rising && !irq_state->falling) { | 
|  | rc = -EINVAL; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | /* Store previous input state then read new value */ | 
|  | previous_state = drv_data->pin_state.input; | 
|  | rc = update_input(dev); | 
|  | if (rc != 0) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | /* Find out which input pins have changed state */ | 
|  | current_state = drv_data->pin_state.input; | 
|  | transitioned_pins = previous_state ^ current_state; | 
|  |  | 
|  | /* Mask gpio transactions with rising/falling edge interrupt config */ | 
|  | interrupt_status = (irq_state->rising & transitioned_pins & | 
|  | current_state); | 
|  | interrupt_status |= (irq_state->falling & transitioned_pins & | 
|  | previous_state); | 
|  |  | 
|  | out: | 
|  | k_sem_give(&drv_data->lock); | 
|  |  | 
|  | if ((rc == 0) && (interrupt_status)) { | 
|  | gpio_fire_callbacks(&drv_data->cb, dev, interrupt_status); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Work handler for PCA953X interrupt | 
|  | * | 
|  | * @param work Work struct that contains pointer to interrupt handler function | 
|  | */ | 
|  | static void gpio_pca953x_work_handler(struct k_work *work) | 
|  | { | 
|  | struct pca953x_drv_data *drv_data = | 
|  | CONTAINER_OF(work, struct pca953x_drv_data, work); | 
|  |  | 
|  | gpio_pca953x_handle_interrupt(drv_data->dev); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief ISR for intterupt pin of PCA953X | 
|  | * | 
|  | * @param dev Pointer to the device structure for the driver instance. | 
|  | * @param gpio_cb Pointer to callback function struct | 
|  | * @param pins Bitmask of pins that triggered interrupt | 
|  | */ | 
|  | static void gpio_pca953x_init_cb(const struct device *dev, | 
|  | struct gpio_callback *gpio_cb, uint32_t pins) | 
|  | { | 
|  | struct pca953x_drv_data *drv_data = | 
|  | CONTAINER_OF(gpio_cb, struct pca953x_drv_data, gpio_cb); | 
|  |  | 
|  | ARG_UNUSED(pins); | 
|  |  | 
|  | k_work_submit(&drv_data->work); | 
|  | } | 
|  |  | 
|  | static int gpio_pca953x_config(const struct device *dev, gpio_pin_t pin, | 
|  | gpio_flags_t flags) | 
|  | { | 
|  | const struct pca953x_config *cfg = dev->config; | 
|  | struct pca953x_drv_data *drv_data = dev->data; | 
|  | struct pca953x_pin_state *pins = &drv_data->pin_state; | 
|  | int rc = 0; | 
|  | bool data_first = false; | 
|  |  | 
|  | /* Can't do I2C bus operations from an ISR */ | 
|  | if (k_is_in_isr()) { | 
|  | return -EWOULDBLOCK; | 
|  | } | 
|  |  | 
|  | /* Zephyr currently defines drive strength support based on | 
|  | * the behavior and capabilities of the Nordic GPIO | 
|  | * peripheral: strength defaults to low but can be set high, | 
|  | * and is controlled independently for output levels. | 
|  | * | 
|  | * The PCA953X supports only high strength, and does not | 
|  | * support different strengths for different levels. | 
|  | * | 
|  | * Until something more general is available reject any | 
|  | * attempt to set a non-default drive strength. | 
|  | */ | 
|  | if ((flags & (GPIO_DS_ALT_LOW | GPIO_DS_ALT_HIGH)) != 0) { | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | /* Single Ended lines (Open drain and open source) not supported */ | 
|  | if ((flags & GPIO_SINGLE_ENDED) != 0) { | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | /* The PCA953X has no internal pull up support */ | 
|  | if (((flags & GPIO_PULL_UP) != 0) || ((flags & GPIO_PULL_DOWN) != 0)) { | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | /* Simultaneous input & output mode not supported */ | 
|  | if (((flags & GPIO_INPUT) != 0) && ((flags & GPIO_OUTPUT) != 0)) { | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | k_sem_take(&drv_data->lock, K_FOREVER); | 
|  |  | 
|  | /* Ensure either Output or Input is specified */ | 
|  | if ((flags & GPIO_OUTPUT) != 0) { | 
|  | pins->dir &= ~BIT(pin); | 
|  | if ((flags & GPIO_OUTPUT_INIT_LOW) != 0) { | 
|  | pins->output &= ~BIT(pin); | 
|  | data_first = true; | 
|  | } else if ((flags & GPIO_OUTPUT_INIT_HIGH) != 0) { | 
|  | pins->output |= BIT(pin); | 
|  | data_first = true; | 
|  | } | 
|  | } else if ((flags & GPIO_INPUT) != 0) { | 
|  | pins->dir |= BIT(pin); | 
|  | } else { | 
|  | rc = -ENOTSUP; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | /* Set output values */ | 
|  | if (data_first) { | 
|  | rc = i2c_reg_write_byte(cfg->i2c_dev, cfg->i2c_addr, | 
|  | PCA953X_OUTPUT_PORT, pins->output); | 
|  | } | 
|  |  | 
|  | if (rc == 0) { | 
|  | /* Set pin directions */ | 
|  | rc = i2c_reg_write_byte(cfg->i2c_dev, cfg->i2c_addr, | 
|  | PCA953X_CONFIGURATION, pins->dir); | 
|  | } | 
|  |  | 
|  | if (rc == 0) { | 
|  | /* Refresh input status */ | 
|  | rc = update_input(dev); | 
|  | } | 
|  |  | 
|  | out: | 
|  | k_sem_give(&drv_data->lock); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static int gpio_pca953x_port_read(const struct device *dev, | 
|  | gpio_port_value_t *value) | 
|  | { | 
|  | const struct pca953x_config *cfg = dev->config; | 
|  | struct pca953x_drv_data *drv_data = dev->data; | 
|  | uint8_t input_pin_data; | 
|  | int rc = 0; | 
|  |  | 
|  | /* Can't do I2C bus operations from an ISR */ | 
|  | if (k_is_in_isr()) { | 
|  | return -EWOULDBLOCK; | 
|  | } | 
|  |  | 
|  | k_sem_take(&drv_data->lock, K_FOREVER); | 
|  |  | 
|  | /* Read Input Register */ | 
|  | rc = i2c_reg_read_byte(cfg->i2c_dev, cfg->i2c_addr, | 
|  | PCA953X_INPUT_PORT, &input_pin_data); | 
|  |  | 
|  | LOG_DBG("read %x got %d", input_pin_data, rc); | 
|  |  | 
|  | if (rc == 0) { | 
|  | drv_data->pin_state.input = input_pin_data; | 
|  | *value = (gpio_port_value_t)(drv_data->pin_state.input); | 
|  | } | 
|  |  | 
|  | k_sem_give(&drv_data->lock); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static int gpio_pca953x_port_write(const struct device *dev, | 
|  | gpio_port_pins_t mask, | 
|  | gpio_port_value_t value, | 
|  | gpio_port_value_t toggle) | 
|  | { | 
|  | const struct pca953x_config *cfg = dev->config; | 
|  | struct pca953x_drv_data *drv_data = dev->data; | 
|  | uint8_t *outp = &drv_data->pin_state.output; | 
|  | int rc; | 
|  | uint8_t orig_out; | 
|  | uint8_t out; | 
|  |  | 
|  | /* Can't do I2C bus operations from an ISR */ | 
|  | if (k_is_in_isr()) { | 
|  | return -EWOULDBLOCK; | 
|  | } | 
|  |  | 
|  | k_sem_take(&drv_data->lock, K_FOREVER); | 
|  |  | 
|  | orig_out = *outp; | 
|  | out = ((orig_out & ~mask) | (value & mask)) ^ toggle; | 
|  |  | 
|  | rc = i2c_reg_write_byte(cfg->i2c_dev, cfg->i2c_addr, | 
|  | PCA953X_OUTPUT_PORT, out); | 
|  |  | 
|  | if (rc == 0) { | 
|  | *outp = out; | 
|  | } | 
|  |  | 
|  | k_sem_give(&drv_data->lock); | 
|  |  | 
|  | LOG_DBG("write %x msk %08x val %08x => %x: %d", orig_out, mask, | 
|  | value, out, rc); | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static int gpio_pca953x_port_set_masked(const struct device *dev, | 
|  | gpio_port_pins_t mask, | 
|  | gpio_port_value_t value) | 
|  | { | 
|  | return gpio_pca953x_port_write(dev, mask, value, 0); | 
|  | } | 
|  |  | 
|  | static int gpio_pca953x_port_set_bits(const struct device *dev, | 
|  | gpio_port_pins_t pins) | 
|  | { | 
|  | return gpio_pca953x_port_write(dev, pins, pins, 0); | 
|  | } | 
|  |  | 
|  | static int gpio_pca953x_port_clear_bits(const struct device *dev, | 
|  | gpio_port_pins_t pins) | 
|  | { | 
|  | return gpio_pca953x_port_write(dev, pins, 0, 0); | 
|  | } | 
|  |  | 
|  | static int gpio_pca953x_port_toggle_bits(const struct device *dev, | 
|  | gpio_port_pins_t pins) | 
|  | { | 
|  | return gpio_pca953x_port_write(dev, 0, 0, pins); | 
|  | } | 
|  |  | 
|  | static int gpio_pca953x_pin_interrupt_configure(const struct device *dev, | 
|  | gpio_pin_t pin, | 
|  | enum gpio_int_mode mode, | 
|  | enum gpio_int_trig trig) | 
|  | { | 
|  | const struct pca953x_config *cfg = dev->config; | 
|  | struct pca953x_drv_data *drv_data = dev->data; | 
|  | struct pca953x_irq_state *irq = &drv_data->irq_state; | 
|  |  | 
|  | if (!cfg->interrupt_enabled) { | 
|  | return -ENOTSUP; | 
|  | } | 
|  | /* Device does not support level-triggered interrupts. */ | 
|  | if (mode == GPIO_INT_MODE_LEVEL) { | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | k_sem_take(&drv_data->lock, K_FOREVER); | 
|  |  | 
|  | if (mode == GPIO_INT_MODE_DISABLED) { | 
|  | irq->falling &= ~BIT(pin); | 
|  | irq->rising &= ~BIT(pin); | 
|  | } else { /* GPIO_INT_MODE_EDGE */ | 
|  | if (trig == GPIO_INT_TRIG_BOTH) { | 
|  | irq->falling |= BIT(pin); | 
|  | irq->rising |= BIT(pin); | 
|  | } else if (trig == GPIO_INT_TRIG_LOW) { | 
|  | irq->falling |= BIT(pin); | 
|  | irq->rising &= ~BIT(pin); | 
|  | } else if (trig == GPIO_INT_TRIG_HIGH) { | 
|  | irq->falling &= ~BIT(pin); | 
|  | irq->rising |= BIT(pin); | 
|  | } | 
|  | } | 
|  |  | 
|  | k_sem_give(&drv_data->lock); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int gpio_pca953x_manage_callback(const struct device *dev, | 
|  | struct gpio_callback *callback, | 
|  | bool set) | 
|  | { | 
|  | struct pca953x_drv_data *data = dev->data; | 
|  |  | 
|  | return gpio_manage_callback(&data->cb, callback, set); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Initialization function of PCA953X | 
|  | * | 
|  | * This sets initial input/ output configuration and output states. | 
|  | * The interrupt is configured if this is enabled. | 
|  | * | 
|  | * @param dev Device struct | 
|  | * @return 0 if successful, failed otherwise. | 
|  | */ | 
|  | static int gpio_pca953x_init(const struct device *dev) | 
|  | { | 
|  | const struct pca953x_config *cfg = dev->config; | 
|  | struct pca953x_drv_data *drv_data = dev->data; | 
|  | int rc = 0; | 
|  |  | 
|  | if (!device_is_ready(cfg->i2c_dev)) { | 
|  | LOG_ERR("I2C device not found"); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | /* Do an initial read, this clears the interrupt pin and sets | 
|  | * up the initial value of the pin state input data. | 
|  | */ | 
|  | rc = update_input(dev); | 
|  | if (rc) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (cfg->interrupt_enabled) { | 
|  | if (!device_is_ready(cfg->gpio_int.port)) { | 
|  | LOG_ERR("Cannot get pointer to gpio interrupt device"); | 
|  | rc = -EINVAL; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | drv_data->dev = dev; | 
|  |  | 
|  | k_work_init(&drv_data->work, gpio_pca953x_work_handler); | 
|  |  | 
|  | rc = gpio_pin_configure_dt(&cfg->gpio_int, GPIO_INPUT); | 
|  | if (rc) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | rc = gpio_pin_interrupt_configure_dt(&cfg->gpio_int, | 
|  | GPIO_INT_EDGE_TO_ACTIVE); | 
|  | if (rc) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | gpio_init_callback(&drv_data->gpio_cb, | 
|  | gpio_pca953x_init_cb, | 
|  | BIT(cfg->gpio_int.pin)); | 
|  |  | 
|  | rc = gpio_add_callback(cfg->gpio_int.port, | 
|  | &drv_data->gpio_cb); | 
|  | } | 
|  | out: | 
|  | if (rc) { | 
|  | LOG_ERR("%s init failed: %d", dev->name, rc); | 
|  | } else { | 
|  | LOG_INF("%s init ok", dev->name); | 
|  | } | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static const struct gpio_driver_api api_table = { | 
|  | .pin_configure = gpio_pca953x_config, | 
|  | .port_get_raw = gpio_pca953x_port_read, | 
|  | .port_set_masked_raw = gpio_pca953x_port_set_masked, | 
|  | .port_set_bits_raw = gpio_pca953x_port_set_bits, | 
|  | .port_clear_bits_raw = gpio_pca953x_port_clear_bits, | 
|  | .port_toggle_bits = gpio_pca953x_port_toggle_bits, | 
|  | .pin_interrupt_configure = gpio_pca953x_pin_interrupt_configure, | 
|  | .manage_callback = gpio_pca953x_manage_callback, | 
|  | }; | 
|  |  | 
|  | #define GPIO_PCA953X_INIT(n)							\ | 
|  | static const struct pca953x_config pca953x_cfg_##n = {			\ | 
|  | .i2c_dev = DEVICE_DT_GET(DT_INST_BUS(n)),			\ | 
|  | .common = {							\ | 
|  | .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(n),	\ | 
|  | },								\ | 
|  | .interrupt_enabled = DT_INST_NODE_HAS_PROP(n, nint_gpios),	\ | 
|  | .gpio_int = GPIO_DT_SPEC_INST_GET_OR(n, nint_gpios, {0}),	\ | 
|  | .i2c_addr = DT_INST_REG_ADDR(n),				\ | 
|  | };									\ | 
|  | \ | 
|  | static struct pca953x_drv_data pca953x_drvdata_##n = {			\ | 
|  | .lock = Z_SEM_INITIALIZER(pca953x_drvdata_##n.lock, 1, 1),	\ | 
|  | .pin_state.dir = ALL_PINS,					\ | 
|  | .pin_state.output = ALL_PINS,					\ | 
|  | };									\ | 
|  | DEVICE_DT_INST_DEFINE(n,						\ | 
|  | gpio_pca953x_init,						\ | 
|  | NULL,								\ | 
|  | &pca953x_drvdata_##n,						\ | 
|  | &pca953x_cfg_##n,						\ | 
|  | POST_KERNEL,							\ | 
|  | CONFIG_GPIO_PCA953X_INIT_PRIORITY,				\ | 
|  | &api_table); | 
|  |  | 
|  | #define DT_DRV_COMPAT ti_tca9538 | 
|  | DT_INST_FOREACH_STATUS_OKAY(GPIO_PCA953X_INIT) |