/* * Copyright 2022 Google LLC * * SPDX-License-Identifier: Apache-2.0 */ /** * @file File that collects common data and configs for RS1718S chip. The file * doesn't provide any API itself. The feature-specific API should be provided * in separated files e.g. GPIO API. * * This file is placed in drivers/gpio directory, because GPIO is only one * supported feature at the moment. It can be move to tcpc dir in the future. */ #define DT_DRV_COMPAT richtek_rt1718s #include "gpio_rt1718s.h" #include #include #include #include #include LOG_MODULE_REGISTER(gpio_rt1718s, CONFIG_GPIO_LOG_LEVEL); static void rt1718s_alert_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { ARG_UNUSED(pins); struct rt1718s_data *data = CONTAINER_OF(cb, struct rt1718s_data, gpio_cb); k_work_submit(&data->alert_worker); } static void rt1718s_alert_worker(struct k_work *work) { struct rt1718s_data *const data = CONTAINER_OF(work, struct rt1718s_data, alert_worker); const struct device *const dev = data->dev; const struct rt1718s_config *const config = dev->config; uint16_t alert, mask; do { /* Read alert and mask */ k_sem_take(&data->lock_tcpci, K_FOREVER); if (rt1718s_reg_burst_read(dev, RT1718S_REG_ALERT, (uint8_t *)&alert, sizeof(alert)) || rt1718s_reg_burst_read(dev, RT1718S_REG_ALERT_MASK, (uint8_t *)&mask, sizeof(mask))) { k_sem_give(&data->lock_tcpci); LOG_ERR("i2c access failed"); continue; } /* Content of the alert and alert mask registers are * defined by the TCPCI specification - "A masked * register will still indicate in the ALERT register, * but shall not set the Alert# pin low" */ alert &= mask; if (alert) { /* Clear all alert bits that causes the interrupt */ if (rt1718s_reg_burst_write(dev, RT1718S_REG_ALERT, (uint8_t *)&alert, sizeof(alert))) { k_sem_give(&data->lock_tcpci); LOG_ERR("i2c access failed"); continue; } } k_sem_give(&data->lock_tcpci); /* There are a few sources of the vendor * defined alert for the RT1718S, but handle * only GPIO at the moment. */ if (alert & RT1718S_REG_ALERT_VENDOR_DEFINED_ALERT) { rt1718s_gpio_alert_handler(dev); } /* While the interrupt signal is still active, we have more work to do. */ } while (gpio_pin_get_dt(&config->irq_gpio)); } static int rt1718s_init(const struct device *dev) { const struct rt1718s_config *const config = dev->config; struct rt1718s_data *data = dev->data; int ret; /* Check I2C is ready */ if (!device_is_ready(config->i2c_dev.bus)) { LOG_ERR("%s device not ready", config->i2c_dev.bus->name); return -ENODEV; } k_sem_init(&data->lock_tcpci, 1, 1); if (IS_ENABLED(CONFIG_GPIO_RT1718S_INTERRUPT)) { if (!gpio_is_ready_dt(&config->irq_gpio)) { LOG_ERR("%s device not ready", config->irq_gpio.port->name); return -ENODEV; } /* Set the interrupt pin for handling the alert */ k_work_init(&data->alert_worker, rt1718s_alert_worker); gpio_pin_configure_dt(&config->irq_gpio, GPIO_INPUT); gpio_init_callback(&data->gpio_cb, rt1718s_alert_callback, BIT(config->irq_gpio.pin)); ret = gpio_add_callback(config->irq_gpio.port, &data->gpio_cb); if (ret < 0) { return ret; } gpio_pin_interrupt_configure_dt(&config->irq_gpio, GPIO_INT_EDGE_TO_ACTIVE); } return 0; } #define CHECK_PORT_DEVICE(node_id) \ COND_CODE_1(DT_HAS_COMPAT_STATUS_OKAY(richtek_rt1718s_gpio_port), DEVICE_DT_GET(node_id), \ ()) #define IRQ_GPIO(inst) \ COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, irq_gpios), \ (.irq_gpio = GPIO_DT_SPEC_INST_GET(inst, irq_gpios)), ()) #define GET_PORT_DEVICE(inst) DT_INST_FOREACH_CHILD_STATUS_OKAY(inst, CHECK_PORT_DEVICE) #define GPIO_RT1718S_DEVICE_INSTANCE(inst) \ static const struct rt1718s_config rt1718s_cfg_##inst = { \ .i2c_dev = I2C_DT_SPEC_INST_GET(inst), \ .gpio_port_dev = GET_PORT_DEVICE(inst), \ IRQ_GPIO(inst) \ }; \ static struct rt1718s_data rt1718s_data_##inst = { \ .dev = DEVICE_DT_INST_GET(inst), \ }; \ DEVICE_DT_INST_DEFINE(inst, rt1718s_init, NULL, &rt1718s_data_##inst, &rt1718s_cfg_##inst, \ POST_KERNEL, CONFIG_RT1718S_INIT_PRIORITY, NULL); DT_INST_FOREACH_STATUS_OKAY(GPIO_RT1718S_DEVICE_INSTANCE)