zephyr/drivers/gpio/gpio_rt1718s.c
Dawid Niedzwiecki 2d93f03c25 driver: gpio: rt1718s: Add RT1718S GPIO driver
RT1718S is an i2c-based TCPC chip that supports 3 additional GPIOs.
The pins can be used for USB-C operations e.g. handling FRS, but they
can also work as usual GPIOs.

Add a driver for the RT1718S GPIO and a handler for an alert signal from
the chip. The handler reads the alert register once asserted and calls
the GPIO interrupt handler if needed(Vendor-defined alert).

gpio_rt1718s.c file and "richtek,rt1718s" node collect common properties
and data for all RS1718S functionalities. The file can be extended for
TCPC driver. rt1718s.h file also defines inline functions with i2c
operations common for all drivers. The common header and source files
can be moved to tcpc directories once the tcpc driver is added since it
is the main functionality.

Signed-off-by: Dawid Niedzwiecki <dn@semihalf.com>
2022-11-28 10:48:53 +01:00

145 lines
5 KiB
C

/*
* 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 <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/util_macro.h>
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 (!device_is_ready(config->irq_gpio.port)) {
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)