From ea40f3af24da508d4de2ed8fb23cb9af45e88360 Mon Sep 17 00:00:00 2001 From: Keith Short Date: Wed, 19 Jul 2023 12:02:34 -0600 Subject: [PATCH] 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 --- drivers/gpio/Kconfig.nct38xx | 17 ++- drivers/gpio/gpio_nct38xx.c | 54 +++++-- drivers/gpio/gpio_nct38xx.h | 104 ------------- drivers/gpio/gpio_nct38xx_alert.c | 1 + drivers/gpio/gpio_nct38xx_port.c | 143 +++++++++--------- drivers/mfd/CMakeLists.txt | 1 + drivers/mfd/Kconfig | 1 + drivers/mfd/Kconfig.nct38xx | 10 ++ drivers/mfd/mfd_nct38xx.c | 88 +++++++++++ .../gpio/nuvoton,nct38xx-gpio-alert.yaml | 4 +- dts/bindings/gpio/nuvoton,nct38xx-gpio.yaml | 98 +++++++----- dts/bindings/mfd/nuvoton,nct38xx.yaml | 14 ++ include/zephyr/drivers/mfd/nct38xx.h | 91 +++++++++++ tests/drivers/build_all/gpio/app.overlay | 97 ++++++------ 14 files changed, 442 insertions(+), 281 deletions(-) create mode 100644 drivers/mfd/Kconfig.nct38xx create mode 100644 drivers/mfd/mfd_nct38xx.c create mode 100644 dts/bindings/mfd/nuvoton,nct38xx.yaml create mode 100644 include/zephyr/drivers/mfd/nct38xx.h diff --git a/drivers/gpio/Kconfig.nct38xx b/drivers/gpio/Kconfig.nct38xx index 3c4f396063b..25e91e3cf09 100644 --- a/drivers/gpio/Kconfig.nct38xx +++ b/drivers/gpio/Kconfig.nct38xx @@ -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 diff --git a/drivers/gpio/gpio_nct38xx.c b/drivers/gpio/gpio_nct38xx.c index 82c8663fe15..eb5412fce5c 100644 --- a/drivers/gpio/gpio_nct38xx.c +++ b/drivers/gpio/gpio_nct38xx.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -17,6 +18,25 @@ #include 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); diff --git a/drivers/gpio/gpio_nct38xx.h b/drivers/gpio/gpio_nct38xx.h index 78b7466f6bc..38032664566 100644 --- a/drivers/gpio/gpio_nct38xx.h +++ b/drivers/gpio/gpio_nct38xx.h @@ -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 * diff --git a/drivers/gpio/gpio_nct38xx_alert.c b/drivers/gpio/gpio_nct38xx_alert.c index 64ece947bd5..7b5a14b722d 100644 --- a/drivers/gpio/gpio_nct38xx_alert.c +++ b/drivers/gpio/gpio_nct38xx_alert.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include diff --git a/drivers/gpio/gpio_nct38xx_port.c b/drivers/gpio/gpio_nct38xx_port.c index 07d3838d842..d21d66734d6 100644 --- a/drivers/gpio/gpio_nct38xx_port.c +++ b/drivers/gpio/gpio_nct38xx_port.c @@ -8,8 +8,8 @@ #include "gpio_nct38xx.h" #include - #include +#include #include 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, ®); + ret = nct38xx_reg_read_byte(config->mfd, NCT38XX_REG_MUX_CONTROL, ®); 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), ®); 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), ®); 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), ®); 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), ®); 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, ®); + ret = nct38xx_reg_read_byte(config->mfd, NCT38XX_REG_MUX_CONTROL, ®); 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), ®); 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), ®); 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), ®); 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), ®); 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), ®); 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), ®); 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), ®); + ret = nct38xx_reg_read_byte(config->mfd, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), + ®); 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), ®); 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), ®); 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), ®); 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)), \ diff --git a/drivers/mfd/CMakeLists.txt b/drivers/mfd/CMakeLists.txt index 0d27ef2c034..e8e59dd1692 100644 --- a/drivers/mfd/CMakeLists.txt +++ b/drivers/mfd/CMakeLists.txt @@ -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) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 114b78ad587..be84e348c04 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -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" diff --git a/drivers/mfd/Kconfig.nct38xx b/drivers/mfd/Kconfig.nct38xx new file mode 100644 index 00000000000..aa852d974bb --- /dev/null +++ b/drivers/mfd/Kconfig.nct38xx @@ -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. diff --git a/drivers/mfd/mfd_nct38xx.c b/drivers/mfd/mfd_nct38xx.c new file mode 100644 index 00000000000..ac5885fd46c --- /dev/null +++ b/drivers/mfd/mfd_nct38xx.c @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2023 Google, LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nuvoton_nct38xx + +#include + +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) diff --git a/dts/bindings/gpio/nuvoton,nct38xx-gpio-alert.yaml b/dts/bindings/gpio/nuvoton,nct38xx-gpio-alert.yaml index c6043983b28..4cae0a79eb2 100644 --- a/dts/bindings/gpio/nuvoton,nct38xx-gpio-alert.yaml +++ b/dts/bindings/gpio/nuvoton,nct38xx-gpio-alert.yaml @@ -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. diff --git a/dts/bindings/gpio/nuvoton,nct38xx-gpio.yaml b/dts/bindings/gpio/nuvoton,nct38xx-gpio.yaml index 5f704e768da..5b9afbbc892 100644 --- a/dts/bindings/gpio/nuvoton,nct38xx-gpio.yaml +++ b/dts/bindings/gpio/nuvoton,nct38xx-gpio.yaml @@ -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] diff --git a/dts/bindings/mfd/nuvoton,nct38xx.yaml b/dts/bindings/mfd/nuvoton,nct38xx.yaml new file mode 100644 index 00000000000..ea1e250b4d7 --- /dev/null +++ b/dts/bindings/mfd/nuvoton,nct38xx.yaml @@ -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 diff --git a/include/zephyr/drivers/mfd/nct38xx.h b/include/zephyr/drivers/mfd/nct38xx.h new file mode 100644 index 00000000000..2ea4513c281 --- /dev/null +++ b/include/zephyr/drivers/mfd/nct38xx.h @@ -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 +#include + +#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_ */ diff --git a/tests/drivers/build_all/gpio/app.overlay b/tests/drivers/build_all/gpio/app.overlay index 2fd3bdc2597..10eea2e04e0 100644 --- a/tests/drivers/build_all/gpio/app.overlay +++ b/tests/drivers/build_all/gpio/app.overlay @@ -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>; + }; }; };