Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
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

/*
* 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)