|  | /* | 
|  | * Copyright 2022 Google LLC | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT richtek_rt1718s_gpio_port | 
|  |  | 
|  | /** | 
|  | * @file Driver for RS1718S TCPC chip GPIOs. | 
|  | */ | 
|  |  | 
|  | #include "gpio_rt1718s.h" | 
|  |  | 
|  | #include <zephyr/drivers/gpio.h> | 
|  | #include <zephyr/drivers/gpio/gpio_utils.h> | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_DECLARE(gpio_rt1718s_port, CONFIG_GPIO_LOG_LEVEL); | 
|  |  | 
|  | /* Driver config */ | 
|  | struct gpio_rt1718s_port_config { | 
|  | /* gpio_driver_config needs to be first */ | 
|  | struct gpio_driver_config common; | 
|  | /* RT1718S chip device */ | 
|  | const struct device *rt1718s_dev; | 
|  | }; | 
|  |  | 
|  | /* Driver data */ | 
|  | struct gpio_rt1718s_port_data { | 
|  | /* gpio_driver_data needs to be first */ | 
|  | struct gpio_driver_data common; | 
|  | /* GPIO callback list */ | 
|  | sys_slist_t cb_list_gpio; | 
|  | /* lock GPIO registers access */ | 
|  | struct k_sem lock; | 
|  | }; | 
|  |  | 
|  | /* GPIO api functions */ | 
|  | static int gpio_rt1718s_pin_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) | 
|  | { | 
|  | const struct gpio_rt1718s_port_config *const config = dev->config; | 
|  | struct gpio_rt1718s_port_data *const data = dev->data; | 
|  | uint8_t new_reg = 0; | 
|  | int ret = 0; | 
|  |  | 
|  | /* Don't support simultaneous in/out mode */ | 
|  | if ((flags & GPIO_INPUT) && (flags & GPIO_OUTPUT)) { | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | /* Don't support "open source" mode */ | 
|  | if ((flags & GPIO_SINGLE_ENDED) && !(flags & GPIO_LINE_OPEN_DRAIN)) { | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | /* RT1718S has 3 GPIOs so check range */ | 
|  | if (pin >= RT1718S_GPIO_NUM) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Configure pin as input. */ | 
|  | if (flags & GPIO_INPUT) { | 
|  | /* Do not set RT1718S_REG_GPIO_CTRL_OE bit for input */ | 
|  | /* Set pull-high/low input */ | 
|  | if (flags & GPIO_PULL_UP) { | 
|  | new_reg |= RT1718S_REG_GPIO_CTRL_PU; | 
|  | } | 
|  | if (flags & GPIO_PULL_DOWN) { | 
|  | new_reg |= RT1718S_REG_GPIO_CTRL_PD; | 
|  | } | 
|  | } else if (flags & GPIO_OUTPUT) { | 
|  | /* Set GPIO as output */ | 
|  | new_reg |= RT1718S_REG_GPIO_CTRL_OE; | 
|  |  | 
|  | /* Set push-pull or open-drain */ | 
|  | if (!(flags & GPIO_SINGLE_ENDED)) { | 
|  | new_reg |= RT1718S_REG_GPIO_CTRL_OD_N; | 
|  | } | 
|  |  | 
|  | /* Set init state */ | 
|  | if (flags & GPIO_OUTPUT_INIT_HIGH) { | 
|  | new_reg |= RT1718S_REG_GPIO_CTRL_O; | 
|  | } | 
|  | } | 
|  |  | 
|  | k_sem_take(&data->lock, K_FOREVER); | 
|  | ret = rt1718s_reg_write_byte(config->rt1718s_dev, RT1718S_REG_GPIO_CTRL(pin), new_reg); | 
|  | k_sem_give(&data->lock); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int gpio_rt1718s_port_get_raw(const struct device *dev, gpio_port_value_t *value) | 
|  | { | 
|  | const struct gpio_rt1718s_port_config *const config = dev->config; | 
|  | uint8_t reg; | 
|  | int ret; | 
|  |  | 
|  | ret = rt1718s_reg_read_byte(config->rt1718s_dev, RT1718S_REG_RT_ST8, ®); | 
|  | *value = reg & (RT1718S_REG_RT_ST8_GPIO1_I | RT1718S_REG_RT_ST8_GPIO2_I | | 
|  | RT1718S_REG_RT_ST8_GPIO3_I); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int gpio_rt1718s_port_set_masked_raw(const struct device *dev, gpio_port_pins_t mask, | 
|  | gpio_port_value_t value) | 
|  | { | 
|  | const struct gpio_rt1718s_port_config *const config = dev->config; | 
|  | struct gpio_rt1718s_port_data *const data = dev->data; | 
|  | uint8_t new_reg, reg; | 
|  | int ret = 0; | 
|  |  | 
|  | k_sem_take(&data->lock, K_FOREVER); | 
|  |  | 
|  | for (int pin = 0; pin < RT1718S_GPIO_NUM; pin++) { | 
|  | if (mask & BIT(pin)) { | 
|  | ret = rt1718s_reg_read_byte(config->rt1718s_dev, RT1718S_REG_GPIO_CTRL(pin), | 
|  | ®); | 
|  | if (ret < 0) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (value & BIT(pin)) { | 
|  | new_reg = reg | RT1718S_REG_GPIO_CTRL_O; | 
|  | } else { | 
|  | new_reg = reg & ~RT1718S_REG_GPIO_CTRL_O; | 
|  | } | 
|  | ret = rt1718s_reg_update(config->rt1718s_dev, RT1718S_REG_GPIO_CTRL(pin), | 
|  | reg, new_reg); | 
|  | } | 
|  | } | 
|  |  | 
|  | k_sem_give(&data->lock); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int gpio_rt1718s_port_set_bits_raw(const struct device *dev, gpio_port_pins_t mask) | 
|  | { | 
|  | const struct gpio_rt1718s_port_config *const config = dev->config; | 
|  | struct gpio_rt1718s_port_data *const data = dev->data; | 
|  | uint8_t new_reg, reg; | 
|  | int ret = 0; | 
|  |  | 
|  | k_sem_take(&data->lock, K_FOREVER); | 
|  |  | 
|  | for (int pin = 0; pin < RT1718S_GPIO_NUM; pin++) { | 
|  | if (mask & BIT(pin)) { | 
|  | ret = rt1718s_reg_read_byte(config->rt1718s_dev, RT1718S_REG_GPIO_CTRL(pin), | 
|  | ®); | 
|  | if (ret < 0) { | 
|  | break; | 
|  | } | 
|  | new_reg = reg | RT1718S_REG_GPIO_CTRL_O; | 
|  | ret = rt1718s_reg_update(config->rt1718s_dev, RT1718S_REG_GPIO_CTRL(pin), | 
|  | reg, new_reg); | 
|  | } | 
|  | } | 
|  |  | 
|  | k_sem_give(&data->lock); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int gpio_rt1718s_port_clear_bits_raw(const struct device *dev, gpio_port_pins_t mask) | 
|  | { | 
|  | const struct gpio_rt1718s_port_config *const config = dev->config; | 
|  | struct gpio_rt1718s_port_data *const data = dev->data; | 
|  | uint8_t new_reg, reg; | 
|  | int ret = 0; | 
|  |  | 
|  | k_sem_take(&data->lock, K_FOREVER); | 
|  |  | 
|  | for (int pin = 0; pin < RT1718S_GPIO_NUM; pin++) { | 
|  | if (mask & BIT(pin)) { | 
|  | ret = rt1718s_reg_read_byte(config->rt1718s_dev, RT1718S_REG_GPIO_CTRL(pin), | 
|  | ®); | 
|  | if (ret < 0) { | 
|  | break; | 
|  | } | 
|  | new_reg = reg & ~RT1718S_REG_GPIO_CTRL_O; | 
|  | ret = rt1718s_reg_update(config->rt1718s_dev, RT1718S_REG_GPIO_CTRL(pin), | 
|  | reg, new_reg); | 
|  | } | 
|  | } | 
|  |  | 
|  | k_sem_give(&data->lock); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int gpio_rt1718s_port_toggle_bits(const struct device *dev, gpio_port_pins_t mask) | 
|  | { | 
|  | const struct gpio_rt1718s_port_config *const config = dev->config; | 
|  | struct gpio_rt1718s_port_data *const data = dev->data; | 
|  | uint8_t new_reg, reg; | 
|  | int ret = 0; | 
|  |  | 
|  | k_sem_take(&data->lock, K_FOREVER); | 
|  |  | 
|  | for (int pin = 0; pin < RT1718S_GPIO_NUM; pin++) { | 
|  | if (mask & BIT(pin)) { | 
|  | ret = rt1718s_reg_read_byte(config->rt1718s_dev, RT1718S_REG_GPIO_CTRL(pin), | 
|  | ®); | 
|  | if (ret < 0) { | 
|  | break; | 
|  | } | 
|  | new_reg = reg ^ RT1718S_REG_GPIO_CTRL_O; | 
|  | ret = rt1718s_reg_update(config->rt1718s_dev, RT1718S_REG_GPIO_CTRL(pin), | 
|  | reg, new_reg); | 
|  | } | 
|  | } | 
|  |  | 
|  | k_sem_give(&data->lock); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int gpio_rt1718s_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin, | 
|  | enum gpio_int_mode mode, enum gpio_int_trig trig) | 
|  | { | 
|  | const struct gpio_rt1718s_port_config *const config = dev->config; | 
|  | struct gpio_rt1718s_port_data *const data = dev->data; | 
|  | struct rt1718s_data *const data_rt1718s = config->rt1718s_dev->data; | 
|  | uint8_t reg_int8, reg_mask8, new_reg_mask8 = 0; | 
|  | uint8_t mask_rise = BIT(pin), mask_fall = BIT(4 + pin); | 
|  | uint16_t alert_mask; | 
|  | int ret; | 
|  |  | 
|  | /* Check passed arguments */ | 
|  | if (mode == GPIO_INT_MODE_LEVEL || pin >= RT1718S_GPIO_NUM) { | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | k_sem_take(&data->lock, K_FOREVER); | 
|  | k_sem_take(&data_rt1718s->lock_tcpci, K_FOREVER); | 
|  |  | 
|  | ret = rt1718s_reg_read_byte(config->rt1718s_dev, RT1718S_REG_RT_MASK8, ®_mask8); | 
|  | if (ret < 0) { | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | /* Disable GPIO interrupt */ | 
|  | if (mode == GPIO_INT_MODE_DISABLED) { | 
|  | new_reg_mask8 = reg_mask8 & ~(mask_rise | mask_fall); | 
|  | } else if (mode == GPIO_INT_MODE_EDGE) { | 
|  | switch (trig) { | 
|  | case GPIO_INT_TRIG_BOTH: | 
|  | new_reg_mask8 = reg_mask8 | mask_rise | mask_fall; | 
|  | break; | 
|  | case GPIO_INT_TRIG_HIGH: | 
|  | new_reg_mask8 = (reg_mask8 | mask_rise) & ~mask_fall; | 
|  | break; | 
|  | case GPIO_INT_TRIG_LOW: | 
|  | new_reg_mask8 = (reg_mask8 | mask_fall) & ~mask_rise; | 
|  | break; | 
|  | } | 
|  |  | 
|  | ret = rt1718s_reg_burst_read(config->rt1718s_dev, RT1718S_REG_ALERT_MASK, | 
|  | (uint8_t *)&alert_mask, sizeof(alert_mask)); | 
|  | if (ret) { | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | /* Enable Vendor Defined Alert for GPIO interrupts */ | 
|  | if (!(alert_mask & RT1718S_REG_ALERT_MASK_VENDOR_DEFINED_ALERT)) { | 
|  | alert_mask |= RT1718S_REG_ALERT_MASK_VENDOR_DEFINED_ALERT; | 
|  | ret = rt1718s_reg_burst_write(config->rt1718s_dev, RT1718S_REG_ALERT_MASK, | 
|  | (uint8_t *)&alert_mask, sizeof(alert_mask)); | 
|  |  | 
|  | if (ret) { | 
|  | goto done; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Clear pending interrupts, which were trigger before enabling the pin | 
|  | * interrupt by user. | 
|  | */ | 
|  | reg_int8 = mask_rise | mask_fall; | 
|  | rt1718s_reg_write_byte(config->rt1718s_dev, RT1718S_REG_RT_INT8, reg_int8); | 
|  | } | 
|  |  | 
|  | /* MASK8 handles 3 GPIOs interrupts, both edges */ | 
|  | ret = rt1718s_reg_update(config->rt1718s_dev, RT1718S_REG_RT_MASK8, reg_mask8, | 
|  | new_reg_mask8); | 
|  |  | 
|  | done: | 
|  | k_sem_give(&data_rt1718s->lock_tcpci); | 
|  | k_sem_give(&data->lock); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int gpio_rt1718s_manage_callback(const struct device *dev, struct gpio_callback *callback, | 
|  | bool set) | 
|  | { | 
|  | struct gpio_rt1718s_port_data *const data = dev->data; | 
|  |  | 
|  | return gpio_manage_callback(&data->cb_list_gpio, callback, set); | 
|  | } | 
|  |  | 
|  | void rt1718s_gpio_alert_handler(const struct device *dev) | 
|  | { | 
|  | const struct rt1718s_config *const config = dev->config; | 
|  | struct gpio_rt1718s_port_data *const data_port = config->gpio_port_dev->data; | 
|  | uint8_t reg_int8, reg_mask8; | 
|  |  | 
|  | k_sem_take(&data_port->lock, K_FOREVER); | 
|  |  | 
|  | /* Get mask and state of GPIO interrupts */ | 
|  | if (rt1718s_reg_read_byte(dev, RT1718S_REG_RT_INT8, ®_int8) || | 
|  | rt1718s_reg_read_byte(dev, RT1718S_REG_RT_MASK8, ®_mask8)) { | 
|  | k_sem_give(&data_port->lock); | 
|  | LOG_ERR("i2c access failed"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | reg_int8 &= reg_mask8; | 
|  | /* Clear the interrupts */ | 
|  | if (reg_int8) { | 
|  | if (rt1718s_reg_write_byte(dev, RT1718S_REG_RT_INT8, reg_int8)) { | 
|  | k_sem_give(&data_port->lock); | 
|  | LOG_ERR("i2c access failed"); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | k_sem_give(&data_port->lock); | 
|  |  | 
|  | if (reg_int8 & RT1718S_GPIO_INT_MASK) | 
|  | /* Call the GPIO callbacks for rising *or* falling edge */ | 
|  | gpio_fire_callbacks(&data_port->cb_list_gpio, config->gpio_port_dev, | 
|  | (reg_int8 & 0x7) | ((reg_int8 >> 4) & 0x7)); | 
|  | } | 
|  |  | 
|  | static const struct gpio_driver_api gpio_rt1718s_driver = { | 
|  | .pin_configure = gpio_rt1718s_pin_config, | 
|  | .port_get_raw = gpio_rt1718s_port_get_raw, | 
|  | .port_set_masked_raw = gpio_rt1718s_port_set_masked_raw, | 
|  | .port_set_bits_raw = gpio_rt1718s_port_set_bits_raw, | 
|  | .port_clear_bits_raw = gpio_rt1718s_port_clear_bits_raw, | 
|  | .port_toggle_bits = gpio_rt1718s_port_toggle_bits, | 
|  | .pin_interrupt_configure = gpio_rt1718s_pin_interrupt_configure, | 
|  | .manage_callback = gpio_rt1718s_manage_callback, | 
|  | }; | 
|  |  | 
|  | static int gpio_rt1718s_port_init(const struct device *dev) | 
|  | { | 
|  | const struct gpio_rt1718s_port_config *const config = dev->config; | 
|  | struct gpio_rt1718s_port_data *const data = dev->data; | 
|  |  | 
|  | if (!device_is_ready(config->rt1718s_dev)) { | 
|  | LOG_ERR("%s is not ready", config->rt1718s_dev->name); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | k_sem_init(&data->lock, 1, 1); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* RT1718S GPIO port driver must be initialized after RT1718S chip driver */ | 
|  | BUILD_ASSERT(CONFIG_GPIO_RT1718S_PORT_INIT_PRIORITY > CONFIG_RT1718S_INIT_PRIORITY); | 
|  |  | 
|  | #define GPIO_RT1718S_PORT_DEVICE_INSTANCE(inst)                                                    \ | 
|  | static const struct gpio_rt1718s_port_config gpio_rt1718s_port_cfg_##inst = {              \ | 
|  | .common = {.port_pin_mask = 0x7},                                                  \ | 
|  | .rt1718s_dev = DEVICE_DT_GET(DT_INST_PARENT(inst)),                                \ | 
|  | };                                                                                         \ | 
|  | static struct gpio_rt1718s_port_data gpio_rt1718s_port_data_##inst;                        \ | 
|  | DEVICE_DT_INST_DEFINE(inst, gpio_rt1718s_port_init, NULL, &gpio_rt1718s_port_data_##inst,  \ | 
|  | &gpio_rt1718s_port_cfg_##inst, POST_KERNEL,                          \ | 
|  | CONFIG_GPIO_RT1718S_PORT_INIT_PRIORITY, &gpio_rt1718s_driver); | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(GPIO_RT1718S_PORT_DEVICE_INSTANCE) |