mfd: Add NCT38xx multi-function driver

The Nuvoton NCT38xx is a multi-function device providing a TCPC
controller and a I/O expander (GPIO driver).  Add a multi-function
driver to manage exclusive access to the device.

Tested with "twister -T tests/drivers/build_all/gpio".

Signed-off-by: Keith Short <keithshort@google.com>
This commit is contained in:
Keith Short 2023-07-19 12:02:34 -06:00 committed by Fabio Baltieri
parent 617c7cb337
commit ea40f3af24
14 changed files with 442 additions and 281 deletions

View file

@ -7,7 +7,8 @@ config GPIO_NCT38XX
bool "NCT38XX I2C-based GPIO chip"
default y
depends on DT_HAS_NUVOTON_NCT38XX_GPIO_PORT_ENABLED
depends on I2C
select I2C
select MFD
help
Enable driver for NCT38XX I2C-based GPIO chip.
@ -15,17 +16,17 @@ if GPIO_NCT38XX
config GPIO_NCT38XX_INIT_PRIORITY
int "NCT38XX GPIO init priority"
default 51
default 62
help
Device driver initialization priority. The priority should be lower
than I2C device.
NCT38xx GPIO driver initialization priority. The priority must be lower
than MFD_INIT_PRIORITY.
config GPIO_NCT38XX_PORT_INIT_PRIORITY
int "NCT38XX GPIO port init priority"
default 52
default 64
help
Device driver initialization priority. The priority should be lower
than I2C & GPIO_NCT38XX_INIT_PRIORITY device.
NCT38xx GPIO port device driver initialization priority. The priority
must be lower than GPIO_NCT38XX_INIT_PRIORITY device.
config GPIO_NCT38XX_ALERT
bool "NCT38XX GPIO interrupt"
@ -36,7 +37,7 @@ config GPIO_NCT38XX_ALERT
config GPIO_NCT38XX_ALERT_INIT_PRIORITY
int "NCT38XX GPIO alert handler init priority"
default 52
default 64
depends on GPIO_NCT38XX_ALERT
help
NCT38XX alert handler initialization priority. This initialization

View file

@ -9,6 +9,7 @@
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/gpio/gpio_nct38xx.h>
#include <zephyr/drivers/mfd/nct38xx.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/util_macro.h>
@ -17,6 +18,25 @@
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(gpio_ntc38xx, CONFIG_GPIO_LOG_LEVEL);
/* Driver config */
struct gpio_nct38xx_config {
/* Multi-function device, parent to the NCT38xx GPIO controller */
const struct device *mfd;
/* GPIO ports */
const struct device **sub_gpio_dev;
uint8_t sub_gpio_port_num;
/* Alert handler */
const struct device *alert_dev;
};
/* Driver data */
struct gpio_nct38xx_data {
/* NCT38XX device */
const struct device *dev;
/* lock NCT38xx register access */
struct k_sem *lock;
};
void nct38xx_gpio_alert_handler(const struct device *dev)
{
const struct gpio_nct38xx_config *const config = dev->config;
@ -29,11 +49,16 @@ void nct38xx_gpio_alert_handler(const struct device *dev)
static int nct38xx_init_interrupt(const struct device *dev)
{
uint16_t alert, alert_mask = 0;
int ret = 0;
struct gpio_nct38xx_data *data = dev->data;
k_sem_take(data->lock, K_FOREVER);
/* Disable all interrupt */
if (nct38xx_reg_burst_write(dev, NCT38XX_REG_ALERT_MASK, (uint8_t *)&alert_mask,
sizeof(alert_mask))) {
return -EIO;
ret = -EIO;
goto unlock;
}
/* Enable vendor-defined alert for GPIO. */
@ -41,34 +66,42 @@ static int nct38xx_init_interrupt(const struct device *dev)
/* Clear alert */
if (nct38xx_reg_burst_read(dev, NCT38XX_REG_ALERT, (uint8_t *)&alert, sizeof(alert))) {
return -EIO;
ret = -EIO;
goto unlock;
}
alert &= alert_mask;
if (alert) {
if (nct38xx_reg_burst_write(dev, NCT38XX_REG_ALERT, (uint8_t *)&alert,
sizeof(alert))) {
return -EIO;
ret = -EIO;
goto unlock;
}
}
if (nct38xx_reg_burst_write(dev, NCT38XX_REG_ALERT_MASK, (uint8_t *)&alert_mask,
sizeof(alert_mask))) {
return -EIO;
ret = -EIO;
goto unlock;
}
return 0;
unlock:
k_sem_give(data->lock);
return ret;
}
static int nct38xx_gpio_init(const struct device *dev)
{
const struct gpio_nct38xx_config *const config = dev->config;
struct gpio_nct38xx_data *data = dev->data;
/* Check I2C is ready */
if (!device_is_ready(config->i2c_dev.bus)) {
LOG_ERR("%s device not ready", config->i2c_dev.bus->name);
/* Verify multi-function parent is ready */
if (!device_is_ready(config->mfd)) {
LOG_ERR("%s device not ready", config->mfd->name);
return -ENODEV;
}
data->lock = mfd_nct38xx_get_lock_reference(config->mfd);
if (IS_ENABLED(CONFIG_GPIO_NCT38XX_ALERT)) {
nct38xx_init_interrupt(dev);
}
@ -81,7 +114,7 @@ static int nct38xx_gpio_init(const struct device *dev)
DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP(inst, DEVICE_DT_GET, (,)) \
}; \
static const struct gpio_nct38xx_config gpio_nct38xx_cfg_##inst = { \
.i2c_dev = I2C_DT_SPEC_INST_GET(inst), \
.mfd = DEVICE_DT_GET(DT_INST_PARENT(inst)), \
.sub_gpio_dev = sub_gpio_dev_##inst, \
.sub_gpio_port_num = ARRAY_SIZE(sub_gpio_dev_##inst), \
}; \
@ -93,3 +126,6 @@ static int nct38xx_gpio_init(const struct device *dev)
CONFIG_GPIO_NCT38XX_INIT_PRIORITY, NULL);
DT_INST_FOREACH_STATUS_OKAY(GPIO_NCT38XX_DEVICE_INSTANCE)
/* The nct38xx MFD parent must be initialized before this driver */
BUILD_ASSERT(CONFIG_GPIO_NCT38XX_INIT_PRIORITY > CONFIG_MFD_INIT_PRIORITY);

View file

@ -31,110 +31,6 @@
#define NCT38XX_REG_ALERT_VENDOR_DEFINDED_ALERT 15
#define NCT38XX_REG_ALERT_MASK_VENDOR_DEFINDED_ALERT 15
/* Driver config */
struct gpio_nct38xx_config {
/* I2C device */
const struct i2c_dt_spec i2c_dev;
/* GPIO ports */
const struct device **sub_gpio_dev;
uint8_t sub_gpio_port_num;
/* Alert handler */
const struct device *alert_dev;
};
/* Driver data */
struct gpio_nct38xx_data {
/* NCT38XX device */
const struct device *dev;
};
/**
* @brief Read a NCT38XX register
*
* @param dev NCT38XX device
* @param reg_addr Register address
* @param val A pointer to a buffer for the data to return
*
* @return 0 if successful, otherwise failed.
*/
static inline int nct38xx_reg_read_byte(const struct device *dev, uint8_t reg_addr, uint8_t *val)
{
const struct gpio_nct38xx_config *const config =
(const struct gpio_nct38xx_config *)dev->config;
return i2c_reg_read_byte_dt(&config->i2c_dev, reg_addr, val);
}
/**
* @brief Read a sequence of NCT38XX registers
*
* @param dev NCT38XX device
* @param start_addr The register start address
* @param buf A pointer to a buffer for the data to return
* @param num_bytes Number of data to read
*
* @return 0 if successful, otherwise failed.
*/
static inline int nct38xx_reg_burst_read(const struct device *dev, uint8_t start_addr, uint8_t *buf,
uint32_t num_bytes)
{
const struct gpio_nct38xx_config *const config =
(const struct gpio_nct38xx_config *)dev->config;
return i2c_burst_read_dt(&config->i2c_dev, start_addr, buf, num_bytes);
}
/**
* @brief Write a NCT38XX register
*
* @param dev NCT38XX device
* @param reg_addr Register address
* @param val Data to write
*
* @return 0 if successful, otherwise failed.
*/
static inline int nct38xx_reg_write_byte(const struct device *dev, uint8_t reg_addr, uint8_t val)
{
const struct gpio_nct38xx_config *const config =
(const struct gpio_nct38xx_config *)dev->config;
return i2c_reg_write_byte_dt(&config->i2c_dev, reg_addr, val);
}
/**
* @brief Write a sequence of NCT38XX registers
*
* @param dev NCT38XX device
* @param start_addr The register start address
* @param buf A pointer to a buffer for the data to write
* @param num_bytes Number of data to write
*
* @return 0 if successful, otherwise failed.
*/
static inline int nct38xx_reg_burst_write(const struct device *dev, uint8_t start_addr,
uint8_t *buf, uint32_t num_bytes)
{
const struct gpio_nct38xx_config *const config =
(const struct gpio_nct38xx_config *)dev->config;
return i2c_burst_write_dt(&config->i2c_dev, start_addr, buf, num_bytes);
}
/**
* @brief Compare data & write a NCT38XX register
*
* @param dev NCT38XX device
* @param reg_addr Register address
* @param reg_val Old register data
* @param new_val New register data
*
* @return 0 if successful, otherwise failed.
*/
static inline int nct38xx_reg_update(const struct device *dev, uint8_t reg_addr, uint8_t reg_val,
uint8_t new_val)
{
if (reg_val == new_val)
return 0;
return nct38xx_reg_write_byte(dev, reg_addr, new_val);
}
/**
* @brief Dispatch GPIO port ISR
*

View file

@ -9,6 +9,7 @@
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/gpio/gpio_nct38xx.h>
#include <zephyr/drivers/mfd/nct38xx.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/util_macro.h>

View file

@ -8,8 +8,8 @@
#include "gpio_nct38xx.h"
#include <zephyr/drivers/gpio/gpio_utils.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/mfd/nct38xx.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(gpio_ntc38xx, CONFIG_GPIO_LOG_LEVEL);
@ -18,7 +18,7 @@ struct gpio_nct38xx_port_config {
/* gpio_driver_config needs to be first */
struct gpio_driver_config common;
/* NCT38XX controller dev */
const struct device *nct38xx_dev;
const struct device *mfd;
/* GPIO port index */
uint8_t gpio_port;
/* GPIO port 0 pinmux mask */
@ -31,8 +31,8 @@ struct gpio_nct38xx_port_data {
struct gpio_driver_data common;
/* GPIO callback list */
sys_slist_t cb_list_gpio;
/* lock GPIO register access */
struct k_sem lock;
/* lock NCT38xx register access */
struct k_sem *lock;
};
/* GPIO api functions */
@ -59,11 +59,11 @@ static int gpio_nct38xx_pin_config(const struct device *dev, gpio_pin_t pin, gpi
return -ENOTSUP;
}
k_sem_take(&data->lock, K_FOREVER);
k_sem_take(data->lock, K_FOREVER);
/* Pin multiplexing */
if (config->gpio_port == 0) {
ret = nct38xx_reg_read_byte(config->nct38xx_dev, NCT38XX_REG_MUX_CONTROL, &reg);
ret = nct38xx_reg_read_byte(config->mfd, NCT38XX_REG_MUX_CONTROL, &reg);
if (ret < 0) {
goto done;
}
@ -72,8 +72,7 @@ static int gpio_nct38xx_pin_config(const struct device *dev, gpio_pin_t pin, gpi
/* NCT3807 bit3 must be set to 0 */
new_reg &= config->pinmux_mask;
ret = nct38xx_reg_update(config->nct38xx_dev, NCT38XX_REG_MUX_CONTROL, reg,
new_reg);
ret = nct38xx_reg_update(config->mfd, NCT38XX_REG_MUX_CONTROL, reg, new_reg);
if (ret < 0) {
goto done;
}
@ -81,20 +80,20 @@ static int gpio_nct38xx_pin_config(const struct device *dev, gpio_pin_t pin, gpi
/* Configure pin as input. */
if (flags & GPIO_INPUT) {
ret = nct38xx_reg_read_byte(config->nct38xx_dev,
ret = nct38xx_reg_read_byte(config->mfd,
NCT38XX_REG_GPIO_DIR(config->gpio_port), &reg);
if (ret < 0) {
goto done;
}
new_reg = reg & ~mask;
ret = nct38xx_reg_update(config->nct38xx_dev,
ret = nct38xx_reg_update(config->mfd,
NCT38XX_REG_GPIO_DIR(config->gpio_port), reg, new_reg);
goto done;
}
/* Select open drain 0:push-pull 1:open-drain */
ret = nct38xx_reg_read_byte(config->nct38xx_dev, NCT38XX_REG_GPIO_OD_SEL(config->gpio_port),
ret = nct38xx_reg_read_byte(config->mfd, NCT38XX_REG_GPIO_OD_SEL(config->gpio_port),
&reg);
if (ret < 0) {
goto done;
@ -104,14 +103,14 @@ static int gpio_nct38xx_pin_config(const struct device *dev, gpio_pin_t pin, gpi
} else {
new_reg = reg & ~mask;
}
ret = nct38xx_reg_update(config->nct38xx_dev, NCT38XX_REG_GPIO_OD_SEL(config->gpio_port),
ret = nct38xx_reg_update(config->mfd, NCT38XX_REG_GPIO_OD_SEL(config->gpio_port),
reg, new_reg);
if (ret < 0) {
goto done;
}
/* Set level 0:low 1:high */
ret = nct38xx_reg_read_byte(config->nct38xx_dev,
ret = nct38xx_reg_read_byte(config->mfd,
NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port),
&reg);
if (ret < 0) {
@ -122,7 +121,7 @@ static int gpio_nct38xx_pin_config(const struct device *dev, gpio_pin_t pin, gpi
} else if (flags & GPIO_OUTPUT_INIT_LOW) {
new_reg = reg & ~mask;
}
ret = nct38xx_reg_update(config->nct38xx_dev, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port),
ret = nct38xx_reg_update(config->mfd, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port),
reg, new_reg);
if (ret < 0) {
goto done;
@ -130,18 +129,18 @@ static int gpio_nct38xx_pin_config(const struct device *dev, gpio_pin_t pin, gpi
/* Configure pin as output, if requested 0:input 1:output */
if (flags & GPIO_OUTPUT) {
ret = nct38xx_reg_read_byte(config->nct38xx_dev,
ret = nct38xx_reg_read_byte(config->mfd,
NCT38XX_REG_GPIO_DIR(config->gpio_port), &reg);
if (ret < 0) {
goto done;
}
new_reg = reg | mask;
ret = nct38xx_reg_update(config->nct38xx_dev,
ret = nct38xx_reg_update(config->mfd,
NCT38XX_REG_GPIO_DIR(config->gpio_port), reg, new_reg);
}
done:
k_sem_give(&data->lock);
k_sem_give(data->lock);
return ret;
}
@ -154,7 +153,7 @@ int gpio_nct38xx_pin_get_config(const struct device *dev, gpio_pin_t pin, gpio_f
uint8_t reg;
int ret;
k_sem_take(&data->lock, K_FOREVER);
k_sem_take(data->lock, K_FOREVER);
if (config->gpio_port == 0) {
if (mask & (~config->common.port_pin_mask)) {
@ -162,7 +161,7 @@ int gpio_nct38xx_pin_get_config(const struct device *dev, gpio_pin_t pin, gpio_f
goto done;
}
ret = nct38xx_reg_read_byte(config->nct38xx_dev, NCT38XX_REG_MUX_CONTROL, &reg);
ret = nct38xx_reg_read_byte(config->mfd, NCT38XX_REG_MUX_CONTROL, &reg);
if (ret < 0) {
goto done;
}
@ -173,7 +172,7 @@ int gpio_nct38xx_pin_get_config(const struct device *dev, gpio_pin_t pin, gpio_f
}
}
ret = nct38xx_reg_read_byte(config->nct38xx_dev, NCT38XX_REG_GPIO_DIR(config->gpio_port),
ret = nct38xx_reg_read_byte(config->mfd, NCT38XX_REG_GPIO_DIR(config->gpio_port),
&reg);
if (ret < 0) {
goto done;
@ -184,7 +183,7 @@ int gpio_nct38xx_pin_get_config(const struct device *dev, gpio_pin_t pin, gpio_f
*flags = GPIO_OUTPUT;
/* 0 - push-pull, 1 - open-drain */
ret = nct38xx_reg_read_byte(config->nct38xx_dev,
ret = nct38xx_reg_read_byte(config->mfd,
NCT38XX_REG_GPIO_OD_SEL(config->gpio_port), &reg);
if (ret < 0) {
goto done;
@ -195,7 +194,7 @@ int gpio_nct38xx_pin_get_config(const struct device *dev, gpio_pin_t pin, gpio_f
}
/* Output value */
ret = nct38xx_reg_read_byte(config->nct38xx_dev,
ret = nct38xx_reg_read_byte(config->mfd,
NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), &reg);
if (ret < 0) {
goto done;
@ -212,7 +211,7 @@ int gpio_nct38xx_pin_get_config(const struct device *dev, gpio_pin_t pin, gpio_f
}
done:
k_sem_give(&data->lock);
k_sem_give(data->lock);
return ret;
}
#endif /* CONFIG_GPIO_GET_CONFIG */
@ -223,12 +222,12 @@ static int gpio_nct38xx_port_get_raw(const struct device *dev, gpio_port_value_t
const struct gpio_nct38xx_port_config *const config = dev->config;
struct gpio_nct38xx_port_data *const data = dev->data;
k_sem_take(&data->lock, K_FOREVER);
k_sem_take(data->lock, K_FOREVER);
ret = nct38xx_reg_read_byte(config->nct38xx_dev,
ret = nct38xx_reg_read_byte(config->mfd,
NCT38XX_REG_GPIO_DATA_IN(config->gpio_port), (uint8_t *)value);
k_sem_give(&data->lock);
k_sem_give(data->lock);
return ret;
}
@ -240,19 +239,19 @@ static int gpio_nct38xx_port_set_masked_raw(const struct device *dev, gpio_port_
uint8_t reg, new_reg;
int ret;
k_sem_take(&data->lock, K_FOREVER);
k_sem_take(data->lock, K_FOREVER);
ret = nct38xx_reg_read_byte(config->nct38xx_dev,
ret = nct38xx_reg_read_byte(config->mfd,
NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), &reg);
if (ret < 0) {
goto done;
}
new_reg = ((reg & ~mask) | (value & mask));
ret = nct38xx_reg_update(config->nct38xx_dev, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port),
ret = nct38xx_reg_update(config->mfd, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port),
reg, new_reg);
done:
k_sem_give(&data->lock);
k_sem_give(data->lock);
return ret;
}
@ -264,19 +263,19 @@ static int gpio_nct38xx_port_set_bits_raw(const struct device *dev, gpio_port_pi
uint8_t reg, new_reg;
int ret;
k_sem_take(&data->lock, K_FOREVER);
k_sem_take(data->lock, K_FOREVER);
ret = nct38xx_reg_read_byte(config->nct38xx_dev,
ret = nct38xx_reg_read_byte(config->mfd,
NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), &reg);
if (ret < 0) {
goto done;
}
new_reg = reg | mask;
ret = nct38xx_reg_update(config->nct38xx_dev, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port),
ret = nct38xx_reg_update(config->mfd, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port),
reg, new_reg);
done:
k_sem_give(&data->lock);
k_sem_give(data->lock);
return ret;
}
@ -288,19 +287,19 @@ static int gpio_nct38xx_port_clear_bits_raw(const struct device *dev, gpio_port_
uint8_t reg, new_reg;
int ret;
k_sem_take(&data->lock, K_FOREVER);
k_sem_take(data->lock, K_FOREVER);
ret = nct38xx_reg_read_byte(config->nct38xx_dev,
ret = nct38xx_reg_read_byte(config->mfd,
NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), &reg);
if (ret < 0) {
goto done;
}
new_reg = reg & ~mask;
ret = nct38xx_reg_update(config->nct38xx_dev, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port),
ret = nct38xx_reg_update(config->mfd, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port),
reg, new_reg);
done:
k_sem_give(&data->lock);
k_sem_give(data->lock);
return ret;
}
@ -312,19 +311,19 @@ static int gpio_nct38xx_port_toggle_bits(const struct device *dev, gpio_port_pin
uint8_t reg, new_reg;
int ret;
k_sem_take(&data->lock, K_FOREVER);
k_sem_take(data->lock, K_FOREVER);
ret = nct38xx_reg_read_byte(config->nct38xx_dev,
NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), &reg);
ret = nct38xx_reg_read_byte(config->mfd, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port),
&reg);
if (ret < 0) {
goto done;
}
new_reg = reg ^ mask;
ret = nct38xx_reg_update(config->nct38xx_dev, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port),
ret = nct38xx_reg_update(config->mfd, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port),
reg, new_reg);
done:
k_sem_give(&data->lock);
k_sem_give(data->lock);
return ret;
}
@ -338,16 +337,16 @@ static int gpio_nct38xx_pin_interrupt_configure(const struct device *dev, gpio_p
int ret;
uint32_t mask = BIT(pin);
k_sem_take(&data->lock, K_FOREVER);
k_sem_take(data->lock, K_FOREVER);
/* Disable irq before configuring them */
ret = nct38xx_reg_read_byte(config->nct38xx_dev,
ret = nct38xx_reg_read_byte(config->mfd,
NCT38XX_REG_GPIO_ALERT_MASK(config->gpio_port), &reg);
if (ret < 0) {
goto done;
}
new_reg = reg & ~mask;
ret = nct38xx_reg_update(config->nct38xx_dev,
ret = nct38xx_reg_update(config->mfd,
NCT38XX_REG_GPIO_ALERT_MASK(config->gpio_port), reg, new_reg);
if (ret < 0) {
goto done;
@ -359,12 +358,12 @@ static int gpio_nct38xx_pin_interrupt_configure(const struct device *dev, gpio_p
}
/* set edge register */
ret = nct38xx_reg_read_byte(config->nct38xx_dev,
ret = nct38xx_reg_read_byte(config->mfd,
NCT38XX_REG_GPIO_ALERT_RISE(config->gpio_port), &rise);
if (ret < 0) {
goto done;
}
ret = nct38xx_reg_read_byte(config->nct38xx_dev,
ret = nct38xx_reg_read_byte(config->mfd,
NCT38XX_REG_GPIO_ALERT_FALL(config->gpio_port), &fall);
if (ret < 0) {
goto done;
@ -390,13 +389,13 @@ static int gpio_nct38xx_pin_interrupt_configure(const struct device *dev, gpio_p
new_fall = fall & ~mask;
}
ret = nct38xx_reg_update(config->nct38xx_dev,
ret = nct38xx_reg_update(config->mfd,
NCT38XX_REG_GPIO_ALERT_RISE(config->gpio_port), rise, new_rise);
if (ret < 0) {
goto done;
}
ret = nct38xx_reg_update(config->nct38xx_dev,
ret = nct38xx_reg_update(config->mfd,
NCT38XX_REG_GPIO_ALERT_FALL(config->gpio_port), fall, new_fall);
if (ret < 0) {
goto done;
@ -404,7 +403,7 @@ static int gpio_nct38xx_pin_interrupt_configure(const struct device *dev, gpio_p
if (mode == GPIO_INT_MODE_LEVEL) {
/* set active high/low */
ret = nct38xx_reg_read_byte(config->nct38xx_dev,
ret = nct38xx_reg_read_byte(config->mfd,
NCT38XX_REG_GPIO_ALERT_LEVEL(config->gpio_port), &reg);
if (ret < 0) {
goto done;
@ -419,7 +418,7 @@ static int gpio_nct38xx_pin_interrupt_configure(const struct device *dev, gpio_p
ret = -EINVAL;
goto done;
}
ret = nct38xx_reg_update(config->nct38xx_dev,
ret = nct38xx_reg_update(config->mfd,
NCT38XX_REG_GPIO_ALERT_LEVEL(config->gpio_port), reg,
new_reg);
@ -429,24 +428,24 @@ static int gpio_nct38xx_pin_interrupt_configure(const struct device *dev, gpio_p
}
/* Clear pending bit */
ret = nct38xx_reg_write_byte(config->nct38xx_dev,
ret = nct38xx_reg_write_byte(config->mfd,
NCT38XX_REG_GPIO_ALERT_STAT(config->gpio_port), mask);
if (ret < 0) {
goto done;
}
/* Enable it after configuration is completed */
ret = nct38xx_reg_read_byte(config->nct38xx_dev,
ret = nct38xx_reg_read_byte(config->mfd,
NCT38XX_REG_GPIO_ALERT_MASK(config->gpio_port), &reg);
if (ret < 0) {
goto done;
}
new_reg = reg | mask;
ret = nct38xx_reg_update(config->nct38xx_dev,
ret = nct38xx_reg_update(config->mfd,
NCT38XX_REG_GPIO_ALERT_MASK(config->gpio_port), reg, new_reg);
done:
k_sem_give(&data->lock);
k_sem_give(data->lock);
return ret;
}
@ -468,12 +467,12 @@ static int gpio_nct38xx_port_get_direction(const struct device *dev, gpio_port_p
uint8_t dir_reg;
int ret;
k_sem_take(&data->lock, K_FOREVER);
k_sem_take(data->lock, K_FOREVER);
if (config->gpio_port == 0) {
uint8_t enabled_gpios;
/* Remove the disabled GPIOs from the mask */
ret = nct38xx_reg_read_byte(config->nct38xx_dev, NCT38XX_REG_MUX_CONTROL,
ret = nct38xx_reg_read_byte(config->mfd, NCT38XX_REG_MUX_CONTROL,
&enabled_gpios);
mask &= (enabled_gpios & config->common.port_pin_mask);
@ -483,7 +482,7 @@ static int gpio_nct38xx_port_get_direction(const struct device *dev, gpio_port_p
}
/* Read direction register, 0 - input, 1 - output */
ret = nct38xx_reg_read_byte(config->nct38xx_dev, NCT38XX_REG_GPIO_DIR(config->gpio_port),
ret = nct38xx_reg_read_byte(config->mfd, NCT38XX_REG_GPIO_DIR(config->gpio_port),
&dir_reg);
if (ret < 0) {
goto done;
@ -498,7 +497,7 @@ static int gpio_nct38xx_port_get_direction(const struct device *dev, gpio_port_p
}
done:
k_sem_give(&data->lock);
k_sem_give(data->lock);
return ret;
}
#endif /* CONFIG_GPIO_GET_DIRECTION */
@ -511,32 +510,32 @@ int gpio_nct38xx_dispatch_port_isr(const struct device *dev)
int ret;
do {
k_sem_take(&data->lock, K_FOREVER);
ret = nct38xx_reg_read_byte(config->nct38xx_dev,
k_sem_take(data->lock, K_FOREVER);
ret = nct38xx_reg_read_byte(config->mfd,
NCT38XX_REG_GPIO_ALERT_STAT(config->gpio_port),
&alert_pins);
if (ret < 0) {
k_sem_give(&data->lock);
k_sem_give(data->lock);
return ret;
}
ret = nct38xx_reg_read_byte(config->nct38xx_dev,
ret = nct38xx_reg_read_byte(config->mfd,
NCT38XX_REG_GPIO_ALERT_MASK(config->gpio_port), &mask);
if (ret < 0) {
k_sem_give(&data->lock);
k_sem_give(data->lock);
return ret;
}
alert_pins &= mask;
if (alert_pins) {
ret = nct38xx_reg_write_byte(config->nct38xx_dev,
ret = nct38xx_reg_write_byte(config->mfd,
NCT38XX_REG_GPIO_ALERT_STAT(config->gpio_port),
alert_pins);
if (ret < 0) {
k_sem_give(&data->lock);
k_sem_give(data->lock);
return ret;
}
}
k_sem_give(&data->lock);
k_sem_give(data->lock);
gpio_fire_callbacks(&data->cb_list_gpio, dev, alert_pins);
@ -572,12 +571,12 @@ static int gpio_nct38xx_port_init(const struct device *dev)
const struct gpio_nct38xx_port_config *const config = dev->config;
struct gpio_nct38xx_port_data *const data = dev->data;
if (!device_is_ready(config->nct38xx_dev)) {
LOG_ERR("%s is not ready", config->nct38xx_dev->name);
if (!device_is_ready(config->mfd)) {
LOG_ERR("%s is not ready", config->mfd->name);
return -ENODEV;
}
k_sem_init(&data->lock, 1, 1);
data->lock = mfd_nct38xx_get_lock_reference(config->mfd);
return 0;
}
@ -589,7 +588,7 @@ BUILD_ASSERT(CONFIG_GPIO_NCT38XX_PORT_INIT_PRIORITY > CONFIG_GPIO_NCT38XX_INIT_P
static const struct gpio_nct38xx_port_config gpio_nct38xx_port_cfg_##inst = { \
.common = {.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(inst) & \
DT_INST_PROP(inst, pin_mask)}, \
.nct38xx_dev = DEVICE_DT_GET(DT_INST_PARENT(inst)), \
.mfd = DEVICE_DT_GET(DT_INST_GPARENT(inst)), \
.gpio_port = DT_INST_REG_ADDR(inst), \
.pinmux_mask = COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, pinmux_mask), \
(DT_INST_PROP(inst, pinmux_mask)), (0)), \

View file

@ -3,6 +3,7 @@
zephyr_library()
zephyr_library_sources_ifdef(CONFIG_MFD_NCT38XX mfd_nct38xx.c)
zephyr_library_sources_ifdef(CONFIG_MFD_NPM1300 mfd_npm1300.c)
zephyr_library_sources_ifdef(CONFIG_MFD_NPM6001 mfd_npm6001.c)
zephyr_library_sources_ifdef(CONFIG_MFD_AXP192 mfd_axp192.c)

View file

@ -19,6 +19,7 @@ config MFD_INIT_PRIORITY
Multi-function devices initialization priority.
source "drivers/mfd/Kconfig.axp192"
source "drivers/mfd/Kconfig.nct38xx"
source "drivers/mfd/Kconfig.npm1300"
source "drivers/mfd/Kconfig.npm6001"

View file

@ -0,0 +1,10 @@
# Copyright (c) 2023 Google, LLC
# SPDX -License-Identifier: Apache-2.0
config MFD_NCT38XX
bool "Nuvton NCT38xx multi-function device driver"
default y
depends on DT_HAS_NUVOTON_NCT38XX_ENABLED
select I2C
help
Enable the Nuvoton NCT38xx TCPC multi-function device driver.

88
drivers/mfd/mfd_nct38xx.c Normal file
View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2023 Google, LLC
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nuvoton_nct38xx
#include <zephyr/drivers/i2c.h>
struct mfd_nct38xx_config {
const struct i2c_dt_spec i2c_dev;
};
struct mfd_nct38xx_data {
/* lock NC38xx register access */
struct k_sem lock;
};
static int mfd_nct38xx_init(const struct device *dev)
{
const struct mfd_nct38xx_config *config = dev->config;
struct mfd_nct38xx_data *data = dev->data;
if (!device_is_ready(config->i2c_dev.bus)) {
return -ENODEV;
}
k_sem_init(&data->lock, 1, 1);
return 0;
}
struct k_sem *mfd_nct38xx_get_lock_reference(const struct device *dev)
{
struct mfd_nct38xx_data *data = dev->data;
return &data->lock;
}
int nct38xx_reg_read_byte(const struct device *dev, uint8_t reg_addr, uint8_t *val)
{
const struct mfd_nct38xx_config *const config = dev->config;
return i2c_reg_read_byte_dt(&config->i2c_dev, reg_addr, val);
}
int nct38xx_reg_burst_read(const struct device *dev, uint8_t start_addr, uint8_t *buf,
uint32_t num_bytes)
{
const struct mfd_nct38xx_config *const config = dev->config;
return i2c_burst_read_dt(&config->i2c_dev, start_addr, buf, num_bytes);
}
int nct38xx_reg_write_byte(const struct device *dev, uint8_t reg_addr, uint8_t val)
{
const struct mfd_nct38xx_config *const config = dev->config;
return i2c_reg_write_byte_dt(&config->i2c_dev, reg_addr, val);
}
int nct38xx_reg_burst_write(const struct device *dev, uint8_t start_addr, uint8_t *buf,
uint32_t num_bytes)
{
const struct mfd_nct38xx_config *const config = dev->config;
return i2c_burst_write_dt(&config->i2c_dev, start_addr, buf, num_bytes);
}
int nct38xx_reg_update(const struct device *dev, uint8_t reg_addr, uint8_t reg_val, uint8_t new_val)
{
if (reg_val == new_val) {
return 0;
}
return nct38xx_reg_write_byte(dev, reg_addr, new_val);
}
#define MFD_NCT38XX_DEFINE(inst) \
static struct mfd_nct38xx_data nct38xx_data_##inst; \
static const struct mfd_nct38xx_config nct38xx_cfg_##inst = { \
.i2c_dev = I2C_DT_SPEC_INST_GET(inst), \
}; \
\
DEVICE_DT_INST_DEFINE(inst, mfd_nct38xx_init, NULL, &nct38xx_data_##inst, \
&nct38xx_cfg_##inst, POST_KERNEL, CONFIG_MFD_INIT_PRIORITY, NULL);
DT_INST_FOREACH_STATUS_OKAY(MFD_NCT38XX_DEFINE)

View file

@ -2,7 +2,7 @@
# SPDX-License-Identifier: Apache-2.0
description: |
Nuvoton NCT38XX series I2C-based GPIO expander alert handler
Nuvoton NCT38XX series I2C-based GPIO expander alert handler.
Example:
nct3807_alert_1 {
@ -25,4 +25,4 @@ properties:
type: phandles
required: true
description: |
NCT38XX devices which alert are handled by this alert handler.
List of NCT38XX multi-function devices managed by this alert handler.

View file

@ -4,69 +4,83 @@
description: |
Nuvoton NCT38XX series I2C-based GPIO expander
This must be a child of the NCT38xx multi-function device.
Example:
&i2c0_0 {
nct3807@70 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "nuvoton,nct38xx-gpio";
compatible = "nuvoton,nct38xx";
reg = <0x70>;
gpio@0 {
compatible = "nuvoton,nct38xx-gpio-port";
reg = <0x0>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <8>;
pin_mask = <0xff>;
pinmux_mask = <0xf7>;
};
nct3807-gpio {
#address-cells = <1>;
#size-cells = <0>;
compatible = "nuvoton,nct38xx-gpio";
gpio@1 {
compatible = "nuvoton,nct38xx-gpio-port";
reg = <0x1>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <8>;
pin_mask = <0xff>;
gpio@0 {
compatible = "nuvoton,nct38xx-gpio-port";
reg = <0x0>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <8>;
pin_mask = <0xff>;
pinmux_mask = <0xf7>;
};
gpio@1 {
compatible = "nuvoton,nct38xx-gpio-port";
reg = <0x1>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <8>;
pin_mask = <0xff>;
};
};
};
nct3808_0_P1@71 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "nuvoton,nct38xx-gpio";
compatible = "nuvoton,nct38xx";
reg = <0x71>;
gpio@0 {
compatible = "nuvoton,nct38xx-gpio-port";
reg = <0x0>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <8>;
pin_mask = <0xdc>;
pinmux_mask = <0xff>;
nct3808-0-p1-gpio {
#address-cells = <1>;
#size-cells = <0>;
compatible = "nuvoton,nct38xx-gpio";
gpio@0 {
compatible = "nuvoton,nct38xx-gpio-port";
reg = <0x0>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <8>;
pin_mask = <0xdc>;
pinmux_mask = <0xff>;
};
};
};
nct3808_0_P2@75 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "nuvoton,nct38xx-gpio";
compatible = "nuvoton,nct38xx";
reg = <0x75>;
gpio@0 {
compatible = "nuvoton,nct38xx-gpio-port";
reg = <0x0>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <8>;
pin_mask = <0xdc>;
pinmux_mask = <0xff>;
nct3808-0-P2-gpio {
#address-cells = <1>;
#size-cells = <0>;
compatible = "nuvoton,nct38xx-gpio";
gpio@0 {
compatible = "nuvoton,nct38xx-gpio-port";
reg = <0x0>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <8>;
pin_mask = <0xdc>;
pinmux_mask = <0xff>;
};
};
};
};
compatible: "nuvoton,nct38xx-gpio"
include: [i2c-device.yaml]
include: [base.yaml]

View file

@ -0,0 +1,14 @@
# Copyright (c) 2023, Google, LLC
# SPDX-License-Identifier: Apache-2.0
description: |
Nuvoton NCT38xx multi-function device
The NCT38xx provides a TCPC and an I/O Expander capabilities.
The TCPC and I/O expander drivers are added to the devicetree
as children of the nuvoton,nct38xx device.
compatible: "nuvoton,nct38xx"
include: i2c-device.yaml

View file

@ -0,0 +1,91 @@
/*
* Copyright (c) 2023 Google, LLC
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DRIVERS_MFD_NCT38XX_H_
#define ZEPHYR_INCLUDE_DRIVERS_MFD_NCT38XX_H_
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Get the semaphore reference for a NCT38xx instance. Callers
* should pass the return value to k_sem_take/k_sem_give
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return Address of the semaphore
*/
struct k_sem *mfd_nct38xx_get_lock_reference(const struct device *dev);
/**
* @brief Read a NCT38XX register
*
* @param dev NCT38XX multi-function device
* @param reg_addr Register address
* @param val A pointer to a buffer for the data to return
*
* @return 0 if successful, otherwise failed.
*/
int nct38xx_reg_read_byte(const struct device *dev, uint8_t reg_addr, uint8_t *val);
/**
* @brief Read a sequence of NCT38XX registers
*
* @param dev NCT38XX multi-function device
* @param start_addr The register start address
* @param buf A pointer to a buffer for the data to return
* @param num_bytes Number of data to read
*
* @return 0 if successful, otherwise failed.
*/
int nct38xx_reg_burst_read(const struct device *dev, uint8_t start_addr, uint8_t *buf,
uint32_t num_bytes);
/**
* @brief Write a NCT38XX register
*
* @param dev NCT38XX device
* @param reg_addr Register address
* @param val Data to write
*
* @return 0 if successful, otherwise failed.
*/
int nct38xx_reg_write_byte(const struct device *dev, uint8_t reg_addr, uint8_t val);
/**
* @brief Write a sequence of NCT38XX registers
*
* @param dev NCT38XX device
* @param start_addr The register start address
* @param buf A pointer to a buffer for the data to write
* @param num_bytes Number of data to write
*
* @return 0 if successful, otherwise failed.
*/
int nct38xx_reg_burst_write(const struct device *dev, uint8_t start_addr, uint8_t *buf,
uint32_t num_bytes);
/**
* @brief Compare data & write a NCT38XX register
*
* @param dev NCT38XX device
* @param reg_addr Register address
* @param reg_val Old register data
* @param new_val New register data
*
* @return 0 if successful, otherwise failed.
*/
int nct38xx_reg_update(const struct device *dev, uint8_t reg_addr, uint8_t reg_val,
uint8_t new_val);
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_DRIVERS_MFD_NCT38XX_H_ */

View file

@ -103,63 +103,72 @@
gpio-controller;
};
test_i2c_nct3807: nct3807@72 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "nuvoton,nct38xx-gpio";
mfd-nct38xx@72 {
compatible = "nuvoton,nct38xx";
reg = <0x72>;
test_i2c_nct3807: nct3807 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "nuvoton,nct38xx-gpio";
gpio@0 {
compatible = "nuvoton,nct38xx-gpio-port";
reg = <0x0>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <8>;
pin_mask = <0xff>;
pinmux_mask = <0xf7>;
};
gpio@0 {
compatible = "nuvoton,nct38xx-gpio-port";
reg = <0x0>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <8>;
pin_mask = <0xff>;
pinmux_mask = <0xf7>;
};
gpio@1 {
compatible = "nuvoton,nct38xx-gpio-port";
reg = <0x1>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <8>;
pin_mask = <0xff>;
gpio@1 {
compatible = "nuvoton,nct38xx-gpio-port";
reg = <0x1>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <8>;
pin_mask = <0xff>;
};
};
};
test_i2c_nct3808_p1: nct3808_0_P1@71 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "nuvoton,nct38xx-gpio";
test_i2c_nct3808_p1: mfd-nct38xx@71 {
compatible = "nuvoton,nct38xx";
reg = <0x71>;
nct3808_0_P1 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "nuvoton,nct38xx-gpio";
gpio@0 {
compatible = "nuvoton,nct38xx-gpio-port";
reg = <0x0>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <8>;
pin_mask = <0xdc>;
pinmux_mask = <0xff>;
gpio@0 {
compatible = "nuvoton,nct38xx-gpio-port";
reg = <0x0>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <8>;
pin_mask = <0xdc>;
pinmux_mask = <0xff>;
};
};
};
test_i2c_nct3808_p2: nct3808_0_P2@75 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "nuvoton,nct38xx-gpio";
test_i2c_nct3808_p2: mfd-nct38xx@75 {
compatible = "nuvoton,nct38xx";
reg = <0x75>;
nct3808_0_P2 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "nuvoton,nct38xx-gpio";
gpio@0 {
compatible = "nuvoton,nct38xx-gpio-port";
reg = <0x0>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <8>;
pin_mask = <0xdc>;
pinmux_mask = <0xff>;
gpio@0 {
compatible = "nuvoton,nct38xx-gpio-port";
reg = <0x0>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <8>;
pin_mask = <0xdc>;
pinmux_mask = <0xff>;
};
};
};