Browse Source
Add I2C-based GPIO device driver. Supports 16-port GPIO divided into 3 groups. Signed-off-by: Tim Lin <tim2.lin@ite.corp-partner.google.com>pull/82508/head
6 changed files with 499 additions and 0 deletions
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
# Copyright (c) 2024 ITE Corporation. All Rights Reserved. |
||||
# SPDX-License-Identifier: Apache-2.0 |
||||
|
||||
config GPIO_ITE_IT8801 |
||||
bool "ITE IT8801 GPIO device driver" |
||||
default y |
||||
depends on DT_HAS_ITE_IT8801_GPIO_ENABLED |
||||
select I2C |
||||
select MFD |
||||
help |
||||
Enable driver for ITE IT8801 I2C-based GPIO. |
@ -0,0 +1,462 @@
@@ -0,0 +1,462 @@
|
||||
/*
|
||||
* 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 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 const struct gpio_driver_api 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) |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
# Copyright (c) 2024 ITE Corporation. All Rights Reserved. |
||||
# SPDX-License-Identifier: Apache-2.0 |
||||
|
||||
description: ITE IT8801 I2C-based GPIO device driver |
||||
|
||||
compatible: "ite,it8801-gpio" |
||||
|
||||
include: [base.yaml, gpio-controller.yaml] |
||||
|
||||
properties: |
||||
reg: |
||||
required: true |
||||
|
||||
pin-mask: |
||||
type: int |
||||
required: true |
||||
description: | |
||||
Not every GPIO pins are usable for IT8801. This property |
||||
indicates the usable GPIO pin mask. |
||||
|
||||
gpio-cells: |
||||
- pin |
||||
- flags |
Loading…
Reference in new issue