/* * Copyright (c) 2024 ITE Corporation. All Rights Reserved. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT ite_it8801_gpio #include #include #include #include #include #include #include LOG_MODULE_REGISTER(gpio_ite_it8801, CONFIG_GPIO_LOG_LEVEL); struct gpio_it8801_config { /* gpio_driver_config needs to be first */ struct gpio_driver_config common; /* IT8801 controller dev */ const struct device *mfd; /* I2C device for the MFD parent */ const struct i2c_dt_spec i2c_dev; /* GPIO input pin status register */ uint8_t reg_ipsr; /* GPIO set output value register */ uint8_t reg_sovr; /* GPIO control register */ uint8_t reg_gpcr; /* GPIO interrupt status register */ uint8_t reg_gpisr; /* GPIO interrupt enable register */ uint8_t reg_gpier; uint8_t pin_mask; }; struct gpio_it8801_data { struct gpio_driver_data common; struct it8801_mfd_callback it8801_gpio_callback; sys_slist_t callbacks; }; static int ioex_check_is_not_valid(const struct device *dev, gpio_pin_t pin) { const struct gpio_it8801_config *config = dev->config; if (BIT(pin) & ~(config->pin_mask)) { LOG_ERR("GPIO port%d-%d is not support", config->reg_ipsr, pin); return -ENOTSUP; } return 0; } static int gpio_it8801_configure(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) { const struct gpio_it8801_config *config = dev->config; int ret; uint8_t reg_gpcr = config->reg_gpcr + pin; uint8_t mask = BIT(pin); uint8_t new_value, control; /* Don't support "open source" mode */ if (((flags & GPIO_SINGLE_ENDED) != 0) && ((flags & GPIO_LINE_OPEN_DRAIN) == 0)) { return -ENOTSUP; } if (ioex_check_is_not_valid(dev, pin)) { return -ENOTSUP; } ret = i2c_reg_read_byte_dt(&config->i2c_dev, reg_gpcr, &control); if (ret) { LOG_ERR("Failed to read control value (ret %d)", ret); return ret; } if (flags == GPIO_DISCONNECTED) { control &= ~(IT8801_GPIODIR | IT8801_GPIOPDE | IT8801_GPIOPUE); goto write_and_return; } /* If output, set level before changing type to an output. */ if (flags & GPIO_OUTPUT) { if (flags & GPIO_OUTPUT_INIT_HIGH) { new_value = mask; } else if (flags & GPIO_OUTPUT_INIT_LOW) { new_value = 0; } else { new_value = 0; } ret = i2c_reg_update_byte_dt(&config->i2c_dev, config->reg_sovr, mask, new_value); if (ret) { LOG_ERR("Failed to set output value (ret %d)", ret); return ret; } /* Set output */ control |= IT8801_GPIODIR; /* Select open drain 0:push-pull 1:open-drain */ if (flags & GPIO_OPEN_DRAIN) { control |= IT8801_GPIOIOT_OD; } else { control &= ~IT8801_GPIOIOT_OD; } } else { /* Set input */ control &= ~IT8801_GPIODIR; } /* Handle pullup / pulldown */ if (flags & GPIO_PULL_UP) { control = (control | IT8801_GPIOPUE) & ~IT8801_GPIOPDE; } else if (flags & GPIO_PULL_DOWN) { control = (control | IT8801_GPIOPDE) & ~IT8801_GPIOPUE; } else { /* No pull up/down */ control &= ~(IT8801_GPIOPUE | IT8801_GPIOPDE); } write_and_return: /* Set GPIO control */ ret = i2c_reg_write_byte_dt(&config->i2c_dev, reg_gpcr, control); if (ret) { LOG_ERR("Failed to set control value (ret %d)", ret); return ret; } return 0; } #ifdef CONFIG_GPIO_GET_CONFIG static int gpio_it8801_get_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t *out_flags) { const struct gpio_it8801_config *config = dev->config; gpio_flags_t flags = 0; int ret; uint8_t reg_gpcr = config->reg_gpcr + pin; uint8_t mask = BIT(pin); uint8_t control, value; if (ioex_check_is_not_valid(dev, pin)) { return -ENOTSUP; } ret = i2c_reg_read_byte_dt(&config->i2c_dev, reg_gpcr, &control); if (ret) { LOG_ERR("Failed to read control value (ret %d)", ret); return ret; } /* Get GPIO direction */ if (control & IT8801_GPIODIR) { flags |= GPIO_OUTPUT; /* Get GPIO type, 0:push-pull 1:open-drain */ if (control & IT8801_GPIOIOT_OD) { flags |= GPIO_OPEN_DRAIN; } ret = i2c_reg_read_byte_dt(&config->i2c_dev, config->reg_ipsr, &value); if (ret) { LOG_ERR("Failed to read pin status (ret %d)", ret); return ret; } /* Get GPIO output level */ if (value & mask) { flags |= GPIO_OUTPUT_HIGH; } else { flags |= GPIO_OUTPUT_LOW; } } else { flags |= GPIO_INPUT; } /* pullup / pulldown */ if (control & IT8801_GPIOPUE) { flags |= GPIO_PULL_UP; } else if (control & IT8801_GPIOPDE) { flags |= GPIO_PULL_DOWN; } *out_flags = flags; return 0; } #endif static int gpio_it8801_port_get_raw(const struct device *dev, gpio_port_value_t *value) { const struct gpio_it8801_config *config = dev->config; int ret; uint8_t val; /* Get raw bits of GPIO mirror register */ ret = i2c_reg_read_byte_dt(&config->i2c_dev, config->reg_ipsr, &val); if (ret) { LOG_ERR("Failed to get port mask (ret %d)", ret); return ret; } *value = val; return 0; } static int gpio_it8801_port_set_masked_raw(const struct device *dev, gpio_port_pins_t mask, gpio_port_value_t value) { const struct gpio_it8801_config *config = dev->config; int ret; ret = i2c_reg_update_byte_dt(&config->i2c_dev, config->reg_sovr, mask, value); if (ret) { LOG_ERR("Failed to set port mask (ret %d)", ret); return ret; } return 0; } static int gpio_it8801_port_set_bits_raw(const struct device *dev, gpio_port_pins_t pins) { const struct gpio_it8801_config *config = dev->config; int ret; /* Set raw bits of GPIO data register */ ret = i2c_reg_update_byte_dt(&config->i2c_dev, config->reg_sovr, pins, pins); if (ret) { LOG_ERR("Failed to set bits raw (ret %d)", ret); return ret; } return 0; } static int gpio_it8801_port_clear_bits_raw(const struct device *dev, gpio_port_pins_t pins) { const struct gpio_it8801_config *config = dev->config; int ret; /* Clear raw bits of GPIO data register */ ret = i2c_reg_update_byte_dt(&config->i2c_dev, config->reg_sovr, pins, 0); if (ret) { LOG_ERR("Failed to clear bits raw (ret %d)", ret); return ret; } return 0; } static int gpio_it8801_port_toggle_bits(const struct device *dev, gpio_port_pins_t pins) { const struct gpio_it8801_config *config = dev->config; int ret; uint8_t val, new_val; ret = i2c_reg_read_byte_dt(&config->i2c_dev, config->reg_sovr, &val); if (ret) { return ret; } /* Toggle raw bits of GPIO data register */ new_val = val ^ pins; if (new_val != val) { ret = i2c_reg_write_byte_dt(&config->i2c_dev, config->reg_sovr, new_val); if (ret) { LOG_ERR("Failed to write toggle value (ret %d)", ret); return ret; } } return 0; } static int gpio_it8801_manage_callback(const struct device *dev, struct gpio_callback *callback, bool set) { struct gpio_it8801_data *data = dev->data; int ret; ret = gpio_manage_callback(&data->callbacks, callback, set); return ret; } static void it8801_gpio_alert_handler(const struct device *dev) { const struct gpio_it8801_config *config = dev->config; struct gpio_it8801_data *data = dev->data; int ret; uint8_t isr_val, ier_val; ret = i2c_reg_read_byte_dt(&config->i2c_dev, config->reg_gpisr, &isr_val); if (ret) { LOG_ERR("Failed to read GPIO interrupt status (ret %d)", ret); return; } ret = i2c_reg_read_byte_dt(&config->i2c_dev, config->reg_gpier, &ier_val); if (ret) { LOG_ERR("Failed to read GPIO interrupt pin set (ret %d)", ret); return; } if (isr_val & ier_val) { /* Clear pending interrupt */ ret = i2c_reg_write_byte_dt(&config->i2c_dev, config->reg_gpisr, isr_val); if (ret) { LOG_ERR("Failed to clear GPIO interrupt (ret %d)", ret); return; } gpio_fire_callbacks(&data->callbacks, dev, isr_val); } } static int gpio_it8801_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin, enum gpio_int_mode mode, enum gpio_int_trig trig) { const struct gpio_it8801_config *config = dev->config; struct gpio_it8801_data *data = dev->data; int ret; uint8_t reg_gpcr = config->reg_gpcr + pin; uint8_t control; uint8_t mask = BIT(pin); if (ioex_check_is_not_valid(dev, pin)) { return -ENOTSUP; } /* Disable irq before configuring it */ ret = i2c_reg_update_byte_dt(&config->i2c_dev, config->reg_gpier, mask, 0); if (ret) { LOG_ERR("Failed to disable irq (ret %d)", ret); return ret; } if (mode == GPIO_INT_MODE_DISABLED) { return ret; } /* Set input pin */ ret = i2c_reg_update_byte_dt(&config->i2c_dev, reg_gpcr, IT8801_GPIODIR, 0); if (ret) { LOG_ERR("Failed to set input pin (ret %d)", ret); return ret; } /* Clear trigger type */ ret = i2c_reg_update_byte_dt(&config->i2c_dev, reg_gpcr, GENMASK(4, 3), 0); if (ret) { LOG_ERR("Failed to clear trigger type (ret %d)", ret); return ret; } ret = i2c_reg_read_byte_dt(&config->i2c_dev, reg_gpcr, &control); if (ret) { LOG_ERR("Failed to read gpio control (ret %d)", ret); return ret; } if (mode == GPIO_INT_MODE_EDGE) { /* Set edge trigger */ if ((trig & GPIO_INT_TRIG_BOTH) == GPIO_INT_TRIG_BOTH) { control |= IT8801_GPIOIOT_INT_FALL | IT8801_GPIOIOT_INT_RISE; } else if (trig & GPIO_INT_TRIG_LOW) { control |= IT8801_GPIOIOT_INT_FALL; } else if (trig & GPIO_INT_TRIG_HIGH) { control |= IT8801_GPIOIOT_INT_RISE; } else { LOG_ERR("Invalid interrupt trigger type %d", trig); return -EINVAL; } } else if (mode == GPIO_INT_MODE_LEVEL) { /* Set level trigger */ if (trig & GPIO_INT_TRIG_LOW) { control &= ~IT8801_GPIOPOL; } else { control |= IT8801_GPIOPOL; } } /* Set control value */ ret = i2c_reg_write_byte_dt(&config->i2c_dev, reg_gpcr, control); if (ret) { LOG_ERR("Failed to write trigger state (ret %d)", ret); return ret; } /* Clear pending interrupt */ ret = i2c_reg_update_byte_dt(&config->i2c_dev, config->reg_gpisr, mask, mask); if (ret) { LOG_ERR("Failed to clear pending interrupt (ret %d)", ret); return ret; } /* Enable GPIO interrupt */ ret = i2c_reg_update_byte_dt(&config->i2c_dev, config->reg_gpier, mask, mask); if (ret) { LOG_ERR("Failed to enable interrupt (ret %d)", ret); return ret; } /* Gather GPIO interrupt enable */ ret = i2c_reg_write_byte_dt(&config->i2c_dev, IT8801_REG_GIECR, IT8801_REG_MASK_GGPIOIE); /* Register the interrupt of IT8801 MFD callback function */ data->it8801_gpio_callback.cb = it8801_gpio_alert_handler; data->it8801_gpio_callback.dev = dev; mfd_it8801_register_interrupt_callback(config->mfd, &data->it8801_gpio_callback); return ret; } static DEVICE_API(gpio, gpio_it8801_driver_api) = { .pin_configure = gpio_it8801_configure, #ifdef CONFIG_GPIO_GET_CONFIG .pin_get_config = gpio_it8801_get_config, #endif .port_get_raw = gpio_it8801_port_get_raw, .port_set_masked_raw = gpio_it8801_port_set_masked_raw, .port_set_bits_raw = gpio_it8801_port_set_bits_raw, .port_clear_bits_raw = gpio_it8801_port_clear_bits_raw, .port_toggle_bits = gpio_it8801_port_toggle_bits, .pin_interrupt_configure = gpio_it8801_pin_interrupt_configure, .manage_callback = gpio_it8801_manage_callback, }; static int gpio_it8801_init(const struct device *dev) { const struct gpio_it8801_config *config = dev->config; /* Verify multi-function parent is ready */ if (!device_is_ready(config->mfd)) { LOG_ERR("(gpio)%s is not ready", config->mfd->name); return -ENODEV; } return 0; } #define GPIO_IT8801_DEVICE_INST(inst) \ static struct gpio_it8801_data gpio_it8801_data_##inst; \ static const struct gpio_it8801_config gpio_it8801_cfg_##inst = { \ .common = {.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(inst)}, \ .mfd = DEVICE_DT_GET(DT_INST_PARENT(inst)), \ .i2c_dev = I2C_DT_SPEC_GET(DT_INST_PARENT(inst)), \ .reg_ipsr = DT_INST_REG_ADDR_BY_IDX(inst, 0), \ .reg_sovr = DT_INST_REG_ADDR_BY_IDX(inst, 1), \ .reg_gpcr = DT_INST_REG_ADDR_BY_IDX(inst, 2), \ .reg_gpisr = DT_INST_REG_ADDR_BY_IDX(inst, 3), \ .reg_gpier = DT_INST_REG_ADDR_BY_IDX(inst, 4), \ .pin_mask = DT_INST_PROP(inst, pin_mask), \ }; \ DEVICE_DT_INST_DEFINE(inst, gpio_it8801_init, NULL, &gpio_it8801_data_##inst, \ &gpio_it8801_cfg_##inst, POST_KERNEL, CONFIG_MFD_INIT_PRIORITY, \ &gpio_it8801_driver_api); DT_INST_FOREACH_STATUS_OKAY(GPIO_IT8801_DEVICE_INST)