You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
460 lines
12 KiB
460 lines
12 KiB
/* |
|
* Copyright (c) 2024 ITE Corporation. All Rights Reserved. |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT ite_it8801_gpio |
|
|
|
#include <zephyr/device.h> |
|
#include <zephyr/drivers/gpio.h> |
|
#include <zephyr/drivers/gpio/gpio_utils.h> |
|
#include <zephyr/drivers/i2c.h> |
|
#include <zephyr/drivers/mfd/mfd_ite_it8801.h> |
|
#include <zephyr/spinlock.h> |
|
|
|
#include <zephyr/logging/log.h> |
|
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 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) { |
|
ret = i2c_reg_update_byte_dt(&config->i2c_dev, config->reg_sovr, mask, |
|
mask); |
|
} else if (flags & GPIO_OUTPUT_INIT_LOW) { |
|
ret = i2c_reg_update_byte_dt(&config->i2c_dev, config->reg_sovr, mask, 0); |
|
} |
|
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)
|
|
|