| /* |
| * Copyright 2024 NXP |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/drivers/gpio/gpio_utils.h> |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/sys/byteorder.h> |
| |
| #include <zephyr/drivers/mfd/adp5585.h> |
| |
| #define DT_DRV_COMPAT adi_adp5585_gpio |
| |
| LOG_MODULE_REGISTER(adp5585_gpio, CONFIG_GPIO_LOG_LEVEL); |
| |
| #define ADP5585_BANK(offs) (offs >> 3) |
| #define ADP5585_BIT(offs) (offs & GENMASK(2, 0)) |
| |
| enum adp5585_gpio_pin_direction { |
| adp5585_pin_input = 0U, |
| adp5585_pin_output, |
| }; |
| |
| enum adp5585_gpio_pin_drive_mode { |
| adp5585_pin_drive_pp = 0U, |
| adp5585_pin_drive_od, |
| }; |
| |
| enum adp5585_gpio_pull_config { |
| adp5585_pull_up_300k = 0U, |
| adp5585_pull_dn_300k, |
| adp5585_pull_up_100k, /* not used */ |
| adp5585_pull_disable, |
| }; |
| |
| enum adp5585_gpio_int_en { |
| adp5585_int_disable = 0U, |
| adp5585_int_enable, |
| }; |
| |
| enum adp5585_gpio_int_level { |
| adp5585_int_active_low = 0U, |
| adp5585_int_active_high, |
| }; |
| |
| /** Configuration data */ |
| struct adp5585_gpio_config { |
| /* gpio_driver_config needs to be first */ |
| struct gpio_driver_config common; |
| const struct device *mfd_dev; |
| const struct gpio_dt_spec gpio_int; |
| }; |
| |
| /** Runtime driver data */ |
| struct adp5585_gpio_data { |
| /* gpio_driver_data needs to be first */ |
| struct gpio_driver_data common; |
| uint16_t output; |
| |
| sys_slist_t callbacks; |
| }; |
| |
| static int gpio_adp5585_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) |
| { |
| const struct adp5585_gpio_config *cfg = dev->config; |
| struct adp5585_gpio_data *data = dev->data; |
| const struct mfd_adp5585_config *parent_cfg = |
| (struct mfd_adp5585_config *)(cfg->mfd_dev->config); |
| struct mfd_adp5585_data *parent_data = (struct mfd_adp5585_data *)(cfg->mfd_dev->data); |
| |
| int ret = 0; |
| uint8_t reg_value; |
| |
| /* ADP5585 has non-contiguous gpio pin layouts, account for this */ |
| if ((pin & cfg->common.port_pin_mask) == 0) { |
| LOG_ERR("pin %d is invalid for this device", pin); |
| return -ENOTSUP; |
| } |
| |
| uint8_t bank = ADP5585_BANK(pin); |
| uint8_t bank_pin = ADP5585_BIT(pin); |
| |
| /* Can't do I2C bus operations from an ISR */ |
| if (k_is_in_isr()) { |
| return -EWOULDBLOCK; |
| } |
| |
| /* Simultaneous PU & PD mode not supported */ |
| 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(&parent_data->lock, K_FOREVER); |
| |
| if ((flags & GPIO_SINGLE_ENDED) != 0) { |
| reg_value = adp5585_pin_drive_od << bank_pin; |
| } else { |
| reg_value = adp5585_pin_drive_pp << bank_pin; |
| } |
| ret = i2c_reg_update_byte_dt(&parent_cfg->i2c_bus, ADP5585_GPO_OUT_MODE_A + bank, |
| BIT(bank_pin), reg_value); |
| if (ret != 0) { |
| goto out; |
| } |
| |
| uint8_t regaddr = ADP5585_RPULL_CONFIG_A + (bank << 1); |
| uint8_t shift = bank_pin << 1; |
| |
| if (bank_pin > 3U) { |
| regaddr += 1U; |
| shift = (bank_pin - 3U) << 1; |
| } |
| if ((flags & GPIO_PULL_UP) != 0) { |
| reg_value = adp5585_pull_up_300k << shift; |
| } else if ((flags & GPIO_PULL_DOWN) != 0) { |
| reg_value = adp5585_pull_dn_300k << shift; |
| } else { |
| reg_value = adp5585_pull_disable << shift; |
| } |
| |
| ret = i2c_reg_update_byte_dt(&parent_cfg->i2c_bus, regaddr, |
| 0b11U << shift, reg_value); |
| if (ret != 0) { |
| goto out; |
| } |
| |
| /* Ensure either Output or Input is specified */ |
| if ((flags & GPIO_OUTPUT) != 0) { |
| |
| /* Set Low or High if specified */ |
| if ((flags & GPIO_OUTPUT_INIT_LOW) != 0) { |
| data->output &= ~BIT(pin); |
| } else if ((flags & GPIO_OUTPUT_INIT_HIGH) != 0) { |
| data->output |= BIT(pin); |
| } |
| if (bank == 0) { |
| /* reg_value for ADP5585_GPO_OUT_MODE */ |
| reg_value = (uint8_t)data->output; |
| } else { |
| /* reg_value for ADP5585_GPO_OUT_MODE */ |
| reg_value = (uint8_t)(data->output >> 8); |
| } |
| ret = i2c_reg_write_byte_dt(&parent_cfg->i2c_bus, |
| ADP5585_GPO_OUT_MODE_A + bank, |
| reg_value); |
| if (ret != 0) { |
| goto out; |
| } |
| /* reg_value for ADP5585_GPIO_DIRECTION */ |
| reg_value = adp5585_pin_output << bank_pin; |
| } else if ((flags & GPIO_INPUT) != 0) { |
| /* reg_value for ADP5585_GPIO_DIRECTION */ |
| reg_value = adp5585_pin_output << bank_pin; |
| } |
| |
| ret = i2c_reg_update_byte_dt(&parent_cfg->i2c_bus, |
| ADP5585_GPIO_DIRECTION_A + bank, |
| BIT(bank_pin), reg_value); |
| |
| out: |
| k_sem_give(&parent_data->lock); |
| if (ret != 0) { |
| LOG_ERR("pin configure error: %d", ret); |
| } |
| return ret; |
| } |
| |
| static int gpio_adp5585_port_read(const struct device *dev, gpio_port_value_t *value) |
| { |
| const struct adp5585_gpio_config *cfg = dev->config; |
| /* struct adp5585_gpio_data *data = dev->data; */ |
| const struct mfd_adp5585_config *parent_cfg = |
| (struct mfd_adp5585_config *)(cfg->mfd_dev->config); |
| struct mfd_adp5585_data *parent_data = (struct mfd_adp5585_data *)(cfg->mfd_dev->data); |
| |
| uint16_t input_data = 0; |
| int ret = 0; |
| |
| /* Can't do I2C bus operations from an ISR */ |
| if (k_is_in_isr()) { |
| return -EWOULDBLOCK; |
| } |
| |
| k_sem_take(&parent_data->lock, K_FOREVER); |
| |
| /** Read Input Register */ |
| |
| uint8_t gpi_status_reg; |
| uint8_t gpi_status_buf[2]; |
| |
| ret = i2c_write_read_dt(&parent_cfg->i2c_bus, &gpi_status_reg, 1U, |
| gpi_status_buf, 2U); |
| if (ret) { |
| goto out; |
| } |
| input_data = sys_le16_to_cpu(*((uint16_t *)gpi_status_buf)); |
| *value = input_data; |
| |
| out: |
| k_sem_give(&parent_data->lock); |
| LOG_DBG("read %x got %d", input_data, ret); |
| return ret; |
| } |
| |
| static int gpio_adp5585_port_write(const struct device *dev, gpio_port_pins_t mask, |
| gpio_port_value_t value, gpio_port_value_t toggle) |
| { |
| const struct adp5585_gpio_config *cfg = dev->config; |
| struct adp5585_gpio_data *data = dev->data; |
| const struct mfd_adp5585_config *parent_cfg = |
| (struct mfd_adp5585_config *)(cfg->mfd_dev->config); |
| struct mfd_adp5585_data *parent_data = (struct mfd_adp5585_data *)(cfg->mfd_dev->data); |
| |
| uint16_t orig_out; |
| uint16_t out; |
| uint8_t reg_value; |
| int ret; |
| |
| /* Can't do I2C bus operations from an ISR */ |
| if (k_is_in_isr()) { |
| return -EWOULDBLOCK; |
| } |
| |
| k_sem_take(&parent_data->lock, K_FOREVER); |
| |
| orig_out = data->output; |
| out = ((orig_out & ~mask) | (value & mask)) ^ toggle; |
| |
| reg_value = (uint8_t)out; |
| uint8_t gpo_data_out_buf[] = { ADP5585_GPO_DATA_OUT_A, |
| (uint8_t)out, (uint8_t)(out >> 8) }; |
| |
| ret = i2c_write_dt(&parent_cfg->i2c_bus, gpo_data_out_buf, sizeof(gpo_data_out_buf)); |
| if (ret) { |
| goto out; |
| } |
| |
| data->output = out; |
| |
| out: |
| k_sem_give(&parent_data->lock); |
| LOG_DBG("write %x msk %08x val %08x => %x: %d", orig_out, mask, value, out, ret); |
| return ret; |
| } |
| |
| static int gpio_adp5585_port_set_masked(const struct device *dev, gpio_port_pins_t mask, |
| gpio_port_value_t value) |
| { |
| return gpio_adp5585_port_write(dev, mask, value, 0); |
| } |
| |
| static int gpio_adp5585_port_set_bits(const struct device *dev, gpio_port_pins_t pins) |
| { |
| return gpio_adp5585_port_write(dev, pins, pins, 0); |
| } |
| |
| static int gpio_adp5585_port_clear_bits(const struct device *dev, gpio_port_pins_t pins) |
| { |
| return gpio_adp5585_port_write(dev, pins, 0, 0); |
| } |
| |
| static int gpio_adp5585_port_toggle_bits(const struct device *dev, gpio_port_pins_t pins) |
| { |
| return gpio_adp5585_port_write(dev, 0, 0, pins); |
| } |
| |
| static int gpio_adp5585_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin, |
| enum gpio_int_mode mode, enum gpio_int_trig trig) |
| { |
| const struct adp5585_gpio_config *cfg = dev->config; |
| /* struct adp5585_gpio_data *data = dev->data; */ |
| const struct mfd_adp5585_config *parent_cfg = |
| (struct mfd_adp5585_config *)(cfg->mfd_dev->config); |
| struct mfd_adp5585_data *parent_data = (struct mfd_adp5585_data *)(cfg->mfd_dev->data); |
| int ret = 0; |
| |
| if (parent_cfg->nint_gpio.port == NULL) { |
| return -ENOTSUP; |
| } |
| |
| /* ADP5585 has non-contiguous gpio pin layouts, account for this */ |
| if ((pin & cfg->common.port_pin_mask) == 0) { |
| LOG_ERR("pin %d is invalid for this device", pin); |
| return -ENOTSUP; |
| } |
| |
| /* This device supports only level-triggered interrupts. */ |
| /* This device does NOT support either-level interrupt. */ |
| if (mode == GPIO_INT_MODE_EDGE || trig == GPIO_INT_TRIG_BOTH) { |
| return -ENOTSUP; |
| } |
| if (k_is_in_isr()) { |
| return -EWOULDBLOCK; |
| } |
| |
| uint8_t bank = ADP5585_BANK(pin); |
| uint8_t bank_pin = ADP5585_BIT(pin); |
| |
| k_sem_take(&parent_data->lock, K_FOREVER); |
| |
| if (mode == GPIO_INT_MODE_DISABLED) { |
| ret = i2c_reg_update_byte_dt(&parent_cfg->i2c_bus, |
| ADP5585_GPI_INTERRUPT_EN_A + bank, BIT(bank_pin), |
| (adp5585_int_disable << bank_pin)); |
| } else if ((trig & GPIO_INT_TRIG_BOTH) != 0) { |
| if (trig == GPIO_INT_TRIG_LOW) { |
| ret = i2c_reg_update_byte_dt( |
| &parent_cfg->i2c_bus, ADP5585_GPI_INT_LEVEL_A + bank, |
| BIT(bank_pin), (adp5585_int_active_low << bank_pin)); |
| } else { |
| ret = i2c_reg_update_byte_dt( |
| &parent_cfg->i2c_bus, ADP5585_GPI_INT_LEVEL_A + bank, |
| BIT(bank_pin), (adp5585_int_active_high << bank_pin)); |
| } |
| |
| /* make sure GPI_n_EVENT_EN is disabled, otherwise it will generate FIFO event */ |
| ret = i2c_reg_update_byte_dt(&parent_cfg->i2c_bus, |
| ADP5585_GPI_EVENT_EN_A + bank, BIT(bank_pin), 0U); |
| ret = i2c_reg_update_byte_dt(&parent_cfg->i2c_bus, |
| ADP5585_GPI_INTERRUPT_EN_A + bank, |
| BIT(bank_pin), (adp5585_int_enable << bank_pin)); |
| } |
| |
| k_sem_give(&parent_data->lock); |
| return ret; |
| } |
| |
| static int gpio_adp5585_manage_callback(const struct device *dev, struct gpio_callback *callback, |
| bool set) |
| { |
| struct adp5585_gpio_data *data = dev->data; |
| |
| return gpio_manage_callback(&data->callbacks, callback, set); |
| } |
| |
| void gpio_adp5585_irq_handler(const struct device *dev) |
| { |
| const struct adp5585_gpio_config *cfg = dev->config; |
| struct adp5585_gpio_data *data = dev->data; |
| const struct mfd_adp5585_config *parent_cfg = |
| (struct mfd_adp5585_config *)(cfg->mfd_dev->config); |
| struct mfd_adp5585_data *parent_data = (struct mfd_adp5585_data *)(cfg->mfd_dev->data); |
| |
| uint16_t reg_int_status; |
| int ret = 0; |
| |
| k_sem_take(&parent_data->lock, K_FOREVER); |
| |
| /* Read Input Register */ |
| ret = i2c_burst_read_dt(&parent_cfg->i2c_bus, ADP5585_GPI_INT_STAT_A, |
| (uint8_t *)®_int_status, 2U); |
| if (ret != 0) { |
| LOG_WRN("%s failed to read interrupt status %d", dev->name, ret); |
| goto out; |
| } |
| |
| out: |
| k_sem_give(&parent_data->lock); |
| |
| if (ret == 0 && reg_int_status != 0) { |
| gpio_fire_callbacks(&data->callbacks, dev, reg_int_status); |
| } |
| } |
| |
| /** |
| * @brief Initialization function of ADP5585_GPIO |
| * |
| * 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_adp5585_init(const struct device *dev) |
| { |
| const struct adp5585_gpio_config *cfg = dev->config; |
| struct adp5585_gpio_data *data = dev->data; |
| const struct mfd_adp5585_config *parent_cfg = |
| (struct mfd_adp5585_config *)(cfg->mfd_dev->config); |
| struct mfd_adp5585_data *parent_data = (struct mfd_adp5585_data *)(cfg->mfd_dev->data); |
| int ret = 0; |
| |
| if (!device_is_ready(cfg->mfd_dev)) { |
| LOG_ERR("%s: parent dev not ready", dev->name); |
| ret = -ENODEV; |
| goto out; |
| } |
| |
| if (!device_is_ready(parent_cfg->i2c_bus.bus)) { |
| LOG_ERR("I2C bus device not found"); |
| ret = -EIO; |
| goto out; |
| } |
| |
| k_sem_take(&parent_data->lock, K_FOREVER); |
| |
| /** Read output register */ |
| uint8_t gpo_data_out_buf[] = { ADP5585_GPO_DATA_OUT_A, |
| 0x00, 0x00 }; |
| |
| ret = i2c_write_read_dt(&parent_cfg->i2c_bus, gpo_data_out_buf, 1U, |
| gpo_data_out_buf + 1, 2U); |
| if (ret) { |
| goto out; |
| } |
| data->output = sys_le16_to_cpu(*((uint16_t *)(gpo_data_out_buf + 1))); |
| |
| /** Set RPULL to high-z by default */ |
| uint8_t rpull_config_buf[] = { ADP5585_RPULL_CONFIG_A, |
| 0xffU, 0x03U, 0xffU, 0x03U }; |
| |
| ret = i2c_write_dt(&parent_cfg->i2c_bus, rpull_config_buf, sizeof(rpull_config_buf)); |
| if (ret) { |
| goto out; |
| } |
| |
| parent_data->child.gpio_dev = dev; |
| |
| /** Enable GPI interrupt */ |
| if ((ret == 0) && gpio_is_ready_dt(&parent_cfg->nint_gpio)) { |
| ret = i2c_reg_update_byte_dt(&parent_cfg->i2c_bus, ADP5585_INT_EN, (1U << 1), |
| (1U << 1)); |
| } |
| |
| out: |
| k_sem_give(&parent_data->lock); |
| if (ret) { |
| LOG_ERR("%s init failed: %d", dev->name, ret); |
| } else { |
| LOG_INF("%s init ok", dev->name); |
| } |
| return ret; |
| } |
| |
| static const struct gpio_driver_api api_table = { |
| .pin_configure = gpio_adp5585_config, |
| .port_get_raw = gpio_adp5585_port_read, |
| .port_set_masked_raw = gpio_adp5585_port_set_masked, |
| .port_set_bits_raw = gpio_adp5585_port_set_bits, |
| .port_clear_bits_raw = gpio_adp5585_port_clear_bits, |
| .port_toggle_bits = gpio_adp5585_port_toggle_bits, |
| .pin_interrupt_configure = gpio_adp5585_pin_interrupt_configure, |
| .manage_callback = gpio_adp5585_manage_callback, |
| }; |
| |
| #define GPIO_ADP5585_INIT(inst) \ |
| static const struct adp5585_gpio_config adp5585_gpio_cfg_##inst = { \ |
| .common = { \ |
| .port_pin_mask = GPIO_DT_INST_PORT_PIN_MASK_NGPIOS_EXC( \ |
| inst, DT_INST_PROP(inst, ngpios)) \ |
| }, \ |
| .mfd_dev = DEVICE_DT_GET(DT_INST_PARENT(inst)), \ |
| }; \ |
| static struct adp5585_gpio_data adp5585_gpio_drvdata_##inst; \ |
| DEVICE_DT_INST_DEFINE(inst, gpio_adp5585_init, NULL, \ |
| &adp5585_gpio_drvdata_##inst, \ |
| &adp5585_gpio_cfg_##inst, POST_KERNEL, \ |
| CONFIG_GPIO_ADP5585_INIT_PRIORITY, &api_table); |
| |
| DT_INST_FOREACH_STATUS_OKAY(GPIO_ADP5585_INIT) |