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.
468 lines
15 KiB
468 lines
15 KiB
/* |
|
* Copyright (c) 2025 ITE Corporation. All Rights Reserved. |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
* |
|
*/ |
|
|
|
#define DT_DRV_COMPAT ite_it51xxx_gpio |
|
|
|
#include <chip_chipregs.h> |
|
#include <soc.h> |
|
#include <soc_dt.h> |
|
#include <zephyr/device.h> |
|
#include <zephyr/drivers/gpio.h> |
|
#include <zephyr/drivers/gpio/gpio_utils.h> |
|
#include <zephyr/drivers/interrupt_controller/wuc_ite_it51xxx.h> |
|
#include <zephyr/dt-bindings/gpio/ite-it8xxx2-gpio.h> |
|
#include <zephyr/dt-bindings/interrupt-controller/ite-intc.h> |
|
#include <zephyr/kernel.h> |
|
#include <zephyr/logging/log.h> |
|
|
|
LOG_MODULE_REGISTER(gpio_ite_it51xxx, LOG_LEVEL_ERR); |
|
|
|
#define IT515XX_GPIO_MAX_PINS 8 |
|
#define IT515XX_GPxyVS BIT(3) |
|
|
|
struct it51xxx_gpio_wuc_map_cfg { |
|
/* WUC control device structure */ |
|
const struct device *wucs; |
|
/* WUC pin mask */ |
|
uint8_t mask; |
|
}; |
|
|
|
/* |
|
* Structure gpio_ite_cfg is about the setting of GPIO |
|
* this config will be used at initial time |
|
*/ |
|
struct gpio_ite_cfg { |
|
/* gpio_driver_config needs to be first */ |
|
struct gpio_driver_config common; |
|
/* gpio wake-up input source configuration list */ |
|
const struct it51xxx_gpio_wuc_map_cfg *wuc_map_list; |
|
/* GPIO port data register (bit mapping to pin) */ |
|
uintptr_t reg_gpdr; |
|
/* GPIO port data mirror register (bit mapping to pin) */ |
|
uintptr_t reg_gpdmr; |
|
/* GPIO port output type register (bit mapping to pin) */ |
|
uintptr_t reg_gpotr; |
|
/* GPIO port 1.8V select register (bit mapping to pin) */ |
|
uintptr_t reg_gpxycr1; |
|
/* GPIO port control register (byte mapping to pin) */ |
|
uintptr_t reg_gpcr; |
|
/* GPIO/KBS function selection register (bit mapping to pin) */ |
|
uintptr_t reg_ksfselr; |
|
/* GPIO's irq */ |
|
uint8_t gpio_irq[8]; |
|
/* Support input voltage selection */ |
|
uint8_t has_volt_sel[8]; |
|
/* Number of pins per group of GPIO */ |
|
uint8_t num_pins; |
|
}; |
|
|
|
/* Structure gpio_ite_data is about callback function */ |
|
struct gpio_ite_data { |
|
struct gpio_driver_data common; |
|
struct k_spinlock lock; |
|
sys_slist_t callbacks; |
|
uint8_t volt_default_set; |
|
uint8_t level_isr_high; |
|
uint8_t level_isr_low; |
|
}; |
|
|
|
/** |
|
* Driver functions |
|
*/ |
|
static int gpio_ite_configure(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) |
|
{ |
|
const struct gpio_ite_cfg *config = dev->config; |
|
struct gpio_ite_data *data = dev->data; |
|
uint8_t mask = BIT(pin); |
|
int rc = 0; |
|
|
|
/* Don't support "open source" mode */ |
|
if (((flags & GPIO_SINGLE_ENDED) != 0) && ((flags & GPIO_LINE_OPEN_DRAIN) == 0)) { |
|
return -ENOTSUP; |
|
} |
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
|
|
if (flags == GPIO_DISCONNECTED) { |
|
sys_write8(GPCR_PORT_PIN_MODE_TRISTATE, config->reg_gpcr + pin); |
|
/* |
|
* Since not all GPIOs can be to configured as tri-state, |
|
* prompt error if pin doesn't support the flag. |
|
*/ |
|
if (sys_read8(config->reg_gpcr + pin) != GPCR_PORT_PIN_MODE_TRISTATE) { |
|
/* Go back to default setting (input) */ |
|
sys_write8(GPCR_PORT_PIN_MODE_INPUT, config->reg_gpcr + pin); |
|
LOG_ERR("Cannot config the node-gpio@%x, pin=%d as tri-state", |
|
(uint32_t)config->reg_gpdr, pin); |
|
rc = -ENOTSUP; |
|
goto unlock_and_return; |
|
} |
|
/* |
|
* The following configuration isn't necessary because the pin |
|
* was configured as disconnected. |
|
*/ |
|
rc = 0; |
|
goto unlock_and_return; |
|
} |
|
|
|
/* |
|
* Select open drain first, so that we don't glitch the signal |
|
* when changing the line to an output. |
|
*/ |
|
if ((uint8_t *)config->reg_gpotr != NULL) { |
|
if (flags & GPIO_OPEN_DRAIN) { |
|
sys_write8(sys_read8(config->reg_gpotr) | mask, config->reg_gpotr); |
|
} else { |
|
sys_write8(sys_read8(config->reg_gpotr) & ~mask, config->reg_gpotr); |
|
} |
|
} |
|
|
|
/* 1.8V or 3.3V */ |
|
if (config->has_volt_sel[pin]) { |
|
gpio_flags_t volt = flags & IT8XXX2_GPIO_VOLTAGE_MASK; |
|
mm_reg_t reg_gpxycr1_pin = config->reg_gpxycr1 + (pin * 2); |
|
|
|
if (volt == IT8XXX2_GPIO_VOLTAGE_1P8) { |
|
__ASSERT(!(flags & GPIO_PULL_UP), |
|
"Don't enable internal pullup if 1.8V voltage is used"); |
|
sys_write8(sys_read8(reg_gpxycr1_pin) | IT515XX_GPxyVS, reg_gpxycr1_pin); |
|
data->volt_default_set &= ~mask; |
|
} else if (volt == IT8XXX2_GPIO_VOLTAGE_3P3) { |
|
sys_write8(sys_read8(reg_gpxycr1_pin) & ~IT515XX_GPxyVS, reg_gpxycr1_pin); |
|
/* |
|
* A variable is needed to store the difference between |
|
* 3.3V and default so that the flag can be distinguished |
|
* between the two in gpio_ite_get_config. |
|
*/ |
|
data->volt_default_set &= ~mask; |
|
} else if (volt == IT8XXX2_GPIO_VOLTAGE_DEFAULT) { |
|
sys_write8(sys_read8(reg_gpxycr1_pin) & ~IT515XX_GPxyVS, reg_gpxycr1_pin); |
|
data->volt_default_set |= mask; |
|
} else { |
|
rc = -EINVAL; |
|
goto unlock_and_return; |
|
} |
|
} |
|
|
|
/* GPIOK, L N group have to set this register */ |
|
if ((uint8_t *)config->reg_ksfselr) { |
|
sys_write8(sys_read8(config->reg_ksfselr) | mask, config->reg_ksfselr); |
|
} |
|
|
|
/* If output, set level before changing type to an output. */ |
|
if (flags & GPIO_OUTPUT) { |
|
if (flags & GPIO_OUTPUT_INIT_HIGH) { |
|
sys_write8(sys_read8(config->reg_gpdr) | mask, config->reg_gpdr); |
|
} else if (flags & GPIO_OUTPUT_INIT_LOW) { |
|
sys_write8(sys_read8(config->reg_gpdr) & ~mask, config->reg_gpdr); |
|
} |
|
} |
|
|
|
uint8_t reg_gpcr = sys_read8(config->reg_gpcr + pin); |
|
/* Set input or output. */ |
|
/* Handle regular GPIO controller */ |
|
if (flags & GPIO_OUTPUT) { |
|
reg_gpcr = (reg_gpcr | GPCR_PORT_PIN_MODE_OUTPUT) & ~GPCR_PORT_PIN_MODE_INPUT; |
|
} else { |
|
reg_gpcr = (reg_gpcr | GPCR_PORT_PIN_MODE_INPUT) & ~GPCR_PORT_PIN_MODE_OUTPUT; |
|
} |
|
|
|
/* Handle pullup / pulldown */ |
|
/* Handle regular GPIO controller */ |
|
if (flags & GPIO_PULL_UP) { |
|
reg_gpcr = (reg_gpcr | GPCR_PORT_PIN_MODE_PULLUP) & ~GPCR_PORT_PIN_MODE_PULLDOWN; |
|
} else if (flags & GPIO_PULL_DOWN) { |
|
reg_gpcr = (reg_gpcr | GPCR_PORT_PIN_MODE_PULLDOWN) & ~GPCR_PORT_PIN_MODE_PULLUP; |
|
} else { |
|
/* No pull up/down */ |
|
reg_gpcr &= ~(GPCR_PORT_PIN_MODE_PULLDOWN | GPCR_PORT_PIN_MODE_PULLUP); |
|
} |
|
sys_write8(reg_gpcr, config->reg_gpcr + pin); |
|
|
|
unlock_and_return: |
|
k_spin_unlock(&data->lock, key); |
|
return rc; |
|
} |
|
|
|
#ifdef CONFIG_GPIO_GET_CONFIG |
|
static int gpio_ite_get_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t *out_flags) |
|
{ |
|
const struct gpio_ite_cfg *config = dev->config; |
|
struct gpio_ite_data *data = dev->data; |
|
mm_reg_t reg_gpxycr1_pin = config->reg_gpxycr1 + (pin * 2); |
|
uint8_t mask = BIT(pin); |
|
gpio_flags_t flags = 0; |
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
/* push-pull or open-drain */ |
|
if (sys_read8(config->reg_gpotr) & mask) { |
|
flags |= GPIO_OPEN_DRAIN; |
|
} |
|
|
|
/* 1.8V or 3.3V */ |
|
if (config->has_volt_sel[pin]) { |
|
if (data->volt_default_set & mask) { |
|
flags |= IT8XXX2_GPIO_VOLTAGE_DEFAULT; |
|
} else { |
|
if (sys_read8(reg_gpxycr1_pin) & IT515XX_GPxyVS) { |
|
flags |= IT8XXX2_GPIO_VOLTAGE_1P8; |
|
} else { |
|
flags |= IT8XXX2_GPIO_VOLTAGE_3P3; |
|
} |
|
} |
|
} |
|
|
|
/* set input or output. */ |
|
if (sys_read8(config->reg_gpcr + pin) & GPCR_PORT_PIN_MODE_OUTPUT) { |
|
flags |= GPIO_OUTPUT; |
|
|
|
/* set level */ |
|
if (sys_read8(config->reg_gpdr) & mask) { |
|
flags |= GPIO_OUTPUT_HIGH; |
|
} else { |
|
flags |= GPIO_OUTPUT_LOW; |
|
} |
|
} |
|
|
|
if (sys_read8(config->reg_gpcr + pin) & GPCR_PORT_PIN_MODE_INPUT) { |
|
flags |= GPIO_INPUT; |
|
|
|
/* pullup / pulldown */ |
|
if (sys_read8(config->reg_gpcr + pin) & GPCR_PORT_PIN_MODE_PULLUP) { |
|
flags |= GPIO_PULL_UP; |
|
} |
|
|
|
if (sys_read8(config->reg_gpcr + pin) & GPCR_PORT_PIN_MODE_PULLDOWN) { |
|
flags |= GPIO_PULL_DOWN; |
|
} |
|
} |
|
|
|
*out_flags = flags; |
|
k_spin_unlock(&data->lock, key); |
|
|
|
return 0; |
|
} |
|
#endif |
|
|
|
static int gpio_ite_port_get_raw(const struct device *dev, gpio_port_value_t *value) |
|
{ |
|
const struct gpio_ite_cfg *config = dev->config; |
|
|
|
/* Get raw bits of GPIO mirror register */ |
|
*value = sys_read8(config->reg_gpdmr); |
|
|
|
return 0; |
|
} |
|
|
|
static int gpio_ite_port_set_masked_raw(const struct device *dev, gpio_port_pins_t mask, |
|
gpio_port_value_t value) |
|
{ |
|
const struct gpio_ite_cfg *config = dev->config; |
|
struct gpio_ite_data *data = dev->data; |
|
uint8_t masked_value = value & mask; |
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
uint8_t out = sys_read8(config->reg_gpdr); |
|
|
|
sys_write8(((out & ~mask) | masked_value), config->reg_gpdr); |
|
k_spin_unlock(&data->lock, key); |
|
|
|
return 0; |
|
} |
|
|
|
static int gpio_ite_port_set_bits_raw(const struct device *dev, gpio_port_pins_t pins) |
|
{ |
|
const struct gpio_ite_cfg *config = dev->config; |
|
struct gpio_ite_data *data = dev->data; |
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
/* Set raw bits of GPIO data register */ |
|
sys_write8(sys_read8(config->reg_gpdr) | pins, config->reg_gpdr); |
|
k_spin_unlock(&data->lock, key); |
|
|
|
return 0; |
|
} |
|
|
|
static int gpio_ite_port_clear_bits_raw(const struct device *dev, gpio_port_pins_t pins) |
|
{ |
|
const struct gpio_ite_cfg *config = dev->config; |
|
struct gpio_ite_data *data = dev->data; |
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
/* Clear raw bits of GPIO data register */ |
|
sys_write8(sys_read8(config->reg_gpdr) & ~pins, config->reg_gpdr); |
|
k_spin_unlock(&data->lock, key); |
|
|
|
return 0; |
|
} |
|
|
|
static int gpio_ite_port_toggle_bits(const struct device *dev, gpio_port_pins_t pins) |
|
{ |
|
const struct gpio_ite_cfg *config = dev->config; |
|
struct gpio_ite_data *data = dev->data; |
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
/* Toggle raw bits of GPIO data register */ |
|
sys_write8(sys_read8(config->reg_gpdr) ^ pins, config->reg_gpdr); |
|
k_spin_unlock(&data->lock, key); |
|
|
|
return 0; |
|
} |
|
|
|
static int gpio_ite_manage_callback(const struct device *dev, struct gpio_callback *callback, |
|
bool set) |
|
{ |
|
struct gpio_ite_data *data = dev->data; |
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
int rc = gpio_manage_callback(&data->callbacks, callback, set); |
|
|
|
k_spin_unlock(&data->lock, key); |
|
|
|
return rc; |
|
} |
|
|
|
static void gpio_ite_isr(const void *arg) |
|
{ |
|
const struct device *dev = arg; |
|
const struct gpio_ite_cfg *config = dev->config; |
|
struct gpio_ite_data *data = dev->data; |
|
uint8_t irq = ite_intc_get_irq_num(); |
|
uint8_t num_pins = config->num_pins; |
|
uint8_t pin; |
|
|
|
for (pin = 0; pin < num_pins; pin++) { |
|
if (irq == config->gpio_irq[pin]) { |
|
/* Should be safe even without spinlock. */ |
|
/* Clear the WUC status register. */ |
|
it51xxx_wuc_clear_status(config->wuc_map_list[pin].wucs, |
|
config->wuc_map_list[pin].mask); |
|
/* The callbacks are user code, and therefore should |
|
* not hold the lock. |
|
*/ |
|
gpio_fire_callbacks(&data->callbacks, dev, BIT(pin)); |
|
|
|
break; |
|
} |
|
} |
|
} |
|
|
|
static int gpio_ite_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin, |
|
enum gpio_int_mode mode, enum gpio_int_trig trig) |
|
{ |
|
const struct gpio_ite_cfg *config = dev->config; |
|
uint32_t flags; |
|
uint8_t gpio_irq = config->gpio_irq[pin]; |
|
struct gpio_ite_data *data = dev->data; |
|
|
|
if (!gpio_irq) { |
|
LOG_ERR("Unsupport interrupt pin"); |
|
return -ENOTSUP; |
|
} |
|
|
|
#ifdef CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT |
|
if (mode == GPIO_INT_MODE_DISABLED || mode == GPIO_INT_MODE_DISABLE_ONLY) { |
|
#else |
|
if (mode == GPIO_INT_MODE_DISABLED) { |
|
#endif /* CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT */ |
|
/* Disable GPIO interrupt */ |
|
irq_disable(gpio_irq); |
|
return 0; |
|
#ifdef CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT |
|
} else if (mode == GPIO_INT_MODE_ENABLE_ONLY) { |
|
/* Only enable GPIO interrupt */ |
|
irq_enable(gpio_irq); |
|
return 0; |
|
#endif /* CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT */ |
|
} |
|
|
|
/* Disable irq before configuring it */ |
|
irq_disable(gpio_irq); |
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
|
|
if (mode == GPIO_INT_MODE_LEVEL) { |
|
flags = WUC_TYPE_LEVEL_TRIG; |
|
if (trig & GPIO_INT_TRIG_HIGH) { |
|
flags |= WUC_TYPE_LEVEL_HIGH; |
|
} else { |
|
flags |= WUC_TYPE_LEVEL_LOW; |
|
} |
|
} else { |
|
switch (trig) { |
|
case GPIO_INT_TRIG_LOW: |
|
flags = WUC_TYPE_EDGE_FALLING; |
|
break; |
|
case GPIO_INT_TRIG_HIGH: |
|
flags = WUC_TYPE_EDGE_RISING; |
|
break; |
|
case GPIO_INT_TRIG_BOTH: |
|
flags = WUC_TYPE_EDGE_BOTH; |
|
break; |
|
default: |
|
k_spin_unlock(&data->lock, key); |
|
return -EINVAL; |
|
} |
|
} |
|
/* Select wakeup interrupt edge triggered type of GPIO pins */ |
|
it51xxx_wuc_set_polarity(config->wuc_map_list[pin].wucs, config->wuc_map_list[pin].mask, |
|
flags); |
|
/* |
|
* Always write 1 to clear the WUC status register after |
|
* modifying edge mode selection register (WUBEMR and WUEMR). |
|
*/ |
|
it51xxx_wuc_clear_status(config->wuc_map_list[pin].wucs, config->wuc_map_list[pin].mask); |
|
/* Enable wakeup interrupt of GPIO pins */ |
|
it51xxx_wuc_enable(config->wuc_map_list[pin].wucs, config->wuc_map_list[pin].mask); |
|
|
|
k_spin_unlock(&data->lock, key); |
|
|
|
/* Enable GPIO interrupt */ |
|
irq_connect_dynamic(gpio_irq, 0, gpio_ite_isr, dev, 0); |
|
irq_enable(gpio_irq); |
|
|
|
return 0; |
|
} |
|
|
|
static DEVICE_API(gpio, gpio_ite_driver_api) = { |
|
.pin_configure = gpio_ite_configure, |
|
#ifdef CONFIG_GPIO_GET_CONFIG |
|
.pin_get_config = gpio_ite_get_config, |
|
#endif |
|
.port_get_raw = gpio_ite_port_get_raw, |
|
.port_set_masked_raw = gpio_ite_port_set_masked_raw, |
|
.port_set_bits_raw = gpio_ite_port_set_bits_raw, |
|
.port_clear_bits_raw = gpio_ite_port_clear_bits_raw, |
|
.port_toggle_bits = gpio_ite_port_toggle_bits, |
|
.pin_interrupt_configure = gpio_ite_pin_interrupt_configure, |
|
.manage_callback = gpio_ite_manage_callback, |
|
}; |
|
|
|
#define GPIO_ITE_DEV_CFG_DATA(inst) \ |
|
BUILD_ASSERT(DT_INST_PROP(inst, ngpios) <= IT515XX_GPIO_MAX_PINS, \ |
|
"The maximum number of pins per port is 8."); \ |
|
static const struct it51xxx_gpio_wuc_map_cfg \ |
|
it51xxx_gpio_wuc_##inst[IT8XXX2_DT_INST_WUCCTRL_LEN(inst)] = \ |
|
IT8XXX2_DT_WUC_ITEMS_LIST(inst); \ |
|
static struct gpio_ite_data gpio_ite_data_##inst; \ |
|
static const struct gpio_ite_cfg gpio_ite_cfg_##inst = { \ |
|
.common = {.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(inst)}, \ |
|
.wuc_map_list = it51xxx_gpio_wuc_##inst, \ |
|
.reg_gpdr = DT_INST_REG_ADDR_BY_IDX(inst, 0), \ |
|
.reg_gpdmr = DT_INST_REG_ADDR_BY_IDX(inst, 1), \ |
|
.reg_gpotr = DT_INST_REG_ADDR_BY_IDX(inst, 2), \ |
|
.reg_gpxycr1 = DT_INST_REG_ADDR_BY_IDX(inst, 3), \ |
|
.reg_gpcr = DT_INST_REG_ADDR_BY_IDX(inst, 4), \ |
|
.reg_ksfselr = DT_INST_REG_ADDR_BY_IDX(inst, 5), \ |
|
.gpio_irq = IT8XXX2_DT_GPIO_IRQ_LIST(inst), \ |
|
.has_volt_sel = DT_INST_PROP_OR(inst, has_volt_sel, {0}), \ |
|
.num_pins = DT_INST_PROP(inst, ngpios), \ |
|
}; \ |
|
DEVICE_DT_INST_DEFINE(inst, NULL, NULL, &gpio_ite_data_##inst, &gpio_ite_cfg_##inst, \ |
|
PRE_KERNEL_1, CONFIG_GPIO_INIT_PRIORITY, &gpio_ite_driver_api); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(GPIO_ITE_DEV_CFG_DATA)
|
|
|