| /* |
| * Copyright (c) 2021 Nuvoton Technology Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT nuvoton_nct38xx_gpio_port |
| |
| #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); |
| |
| /* Driver config */ |
| struct gpio_nct38xx_port_config { |
| /* gpio_driver_config needs to be first */ |
| struct gpio_driver_config common; |
| /* NCT38XX controller dev */ |
| const struct device *mfd; |
| /* GPIO port index */ |
| uint8_t gpio_port; |
| /* GPIO port 0 pinmux mask */ |
| uint8_t pinmux_mask; |
| }; |
| |
| /* Driver data */ |
| struct gpio_nct38xx_port_data { |
| /* gpio_driver_data needs to be first */ |
| struct gpio_driver_data common; |
| /* GPIO callback list */ |
| sys_slist_t cb_list_gpio; |
| /* lock NCT38xx register access */ |
| struct k_sem *lock; |
| /* I2C device for the MFD parent */ |
| const struct i2c_dt_spec *i2c_dev; |
| }; |
| |
| /* GPIO api functions */ |
| static int gpio_nct38xx_pin_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) |
| { |
| const struct gpio_nct38xx_port_config *const config = dev->config; |
| struct gpio_nct38xx_port_data *const data = dev->data; |
| uint32_t mask; |
| uint8_t new_reg; |
| int ret; |
| |
| /* Don't support simultaneous in/out mode */ |
| if (((flags & GPIO_INPUT) != 0) && ((flags & GPIO_OUTPUT) != 0)) { |
| return -ENOTSUP; |
| } |
| |
| /* Don't support "open source" mode */ |
| if (((flags & GPIO_SINGLE_ENDED) != 0) && ((flags & GPIO_LINE_OPEN_DRAIN) == 0)) { |
| return -ENOTSUP; |
| } |
| |
| /* Don't support pull-up/pull-down */ |
| if (((flags & GPIO_PULL_UP) != 0) || ((flags & GPIO_PULL_DOWN) != 0)) { |
| return -ENOTSUP; |
| } |
| |
| k_sem_take(data->lock, K_FOREVER); |
| |
| /* Pin multiplexing */ |
| if (config->gpio_port == 0) { |
| /* Set the mux control bit, but ensure the reserved fields |
| * are cleared. Note that pinmux_mask contains the set |
| * of non-reserved bits. |
| */ |
| new_reg = BIT(pin) & config->pinmux_mask; |
| mask = BIT(pin) | ~config->pinmux_mask; |
| |
| ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_MUX_CONTROL, mask, new_reg); |
| if (ret < 0) { |
| goto done; |
| } |
| } |
| |
| /* Configure pin as input. */ |
| if (flags & GPIO_INPUT) { |
| /* Clear the direction bit to set as an input */ |
| new_reg = 0; |
| mask = BIT(pin); |
| ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DIR(config->gpio_port), |
| mask, new_reg); |
| |
| goto done; |
| } |
| |
| /* Select open drain 0:push-pull 1:open-drain */ |
| mask = BIT(pin); |
| if (flags & GPIO_OPEN_DRAIN) { |
| new_reg = mask; |
| } else { |
| new_reg = 0; |
| } |
| ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_OD_SEL(config->gpio_port), |
| mask, new_reg); |
| if (ret < 0) { |
| goto done; |
| } |
| |
| /* Set level 0:low 1:high */ |
| if (flags & GPIO_OUTPUT_INIT_HIGH) { |
| new_reg = mask; |
| } else if (flags & GPIO_OUTPUT_INIT_LOW) { |
| new_reg = 0; |
| } |
| ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), |
| mask, new_reg); |
| if (ret < 0) { |
| goto done; |
| } |
| |
| /* Configure pin as output, if requested 0:input 1:output */ |
| if (flags & GPIO_OUTPUT) { |
| new_reg = BIT(pin); |
| ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DIR(config->gpio_port), |
| mask, new_reg); |
| } |
| |
| done: |
| k_sem_give(data->lock); |
| return ret; |
| } |
| |
| #ifdef CONFIG_GPIO_GET_CONFIG |
| int gpio_nct38xx_pin_get_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t *flags) |
| { |
| const struct gpio_nct38xx_port_config *const config = dev->config; |
| struct gpio_nct38xx_port_data *const data = dev->data; |
| uint32_t mask = BIT(pin); |
| uint8_t reg; |
| int ret; |
| |
| k_sem_take(data->lock, K_FOREVER); |
| |
| if (config->gpio_port == 0) { |
| if (mask & (~config->common.port_pin_mask)) { |
| ret = -ENOTSUP; |
| goto done; |
| } |
| |
| ret = i2c_reg_read_byte_dt(data->i2c_dev, NCT38XX_REG_MUX_CONTROL, ®); |
| if (ret < 0) { |
| goto done; |
| } |
| |
| if ((mask & config->pinmux_mask) && (mask & (~reg))) { |
| *flags = GPIO_DISCONNECTED; |
| goto done; |
| } |
| } |
| |
| ret = i2c_reg_read_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DIR(config->gpio_port), ®); |
| if (ret < 0) { |
| goto done; |
| } |
| |
| if (reg & mask) { |
| /* Output */ |
| *flags = GPIO_OUTPUT; |
| |
| /* 0 - push-pull, 1 - open-drain */ |
| ret = i2c_reg_read_byte_dt(data->i2c_dev, |
| NCT38XX_REG_GPIO_OD_SEL(config->gpio_port), ®); |
| if (ret < 0) { |
| goto done; |
| } |
| |
| if (mask & reg) { |
| *flags |= GPIO_OPEN_DRAIN; |
| } |
| |
| /* Output value */ |
| ret = i2c_reg_read_byte_dt(data->i2c_dev, |
| NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), ®); |
| if (ret < 0) { |
| goto done; |
| } |
| |
| if (mask & reg) { |
| *flags |= GPIO_OUTPUT_HIGH; |
| } else { |
| *flags |= GPIO_OUTPUT_LOW; |
| } |
| } else { |
| /* Input */ |
| *flags = GPIO_INPUT; |
| } |
| |
| done: |
| k_sem_give(data->lock); |
| return ret; |
| } |
| #endif /* CONFIG_GPIO_GET_CONFIG */ |
| |
| static int gpio_nct38xx_port_get_raw(const struct device *dev, gpio_port_value_t *value) |
| { |
| int ret; |
| 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); |
| |
| ret = i2c_reg_read_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DATA_IN(config->gpio_port), |
| (uint8_t *)value); |
| |
| k_sem_give(data->lock); |
| return ret; |
| } |
| |
| static int gpio_nct38xx_port_set_masked_raw(const struct device *dev, gpio_port_pins_t mask, |
| gpio_port_value_t value) |
| { |
| const struct gpio_nct38xx_port_config *const config = dev->config; |
| struct gpio_nct38xx_port_data *const data = dev->data; |
| int ret; |
| |
| k_sem_take(data->lock, K_FOREVER); |
| |
| ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), |
| mask, value); |
| |
| k_sem_give(data->lock); |
| |
| return ret; |
| } |
| |
| static int gpio_nct38xx_port_set_bits_raw(const struct device *dev, gpio_port_pins_t mask) |
| { |
| const struct gpio_nct38xx_port_config *const config = dev->config; |
| struct gpio_nct38xx_port_data *const data = dev->data; |
| int ret; |
| |
| k_sem_take(data->lock, K_FOREVER); |
| |
| ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), |
| mask, mask); |
| |
| k_sem_give(data->lock); |
| |
| return ret; |
| } |
| |
| static int gpio_nct38xx_port_clear_bits_raw(const struct device *dev, gpio_port_pins_t mask) |
| { |
| const struct gpio_nct38xx_port_config *const config = dev->config; |
| struct gpio_nct38xx_port_data *const data = dev->data; |
| int ret; |
| |
| k_sem_take(data->lock, K_FOREVER); |
| |
| ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), |
| mask, 0); |
| |
| k_sem_give(data->lock); |
| |
| return ret; |
| } |
| |
| static int gpio_nct38xx_port_toggle_bits(const struct device *dev, gpio_port_pins_t mask) |
| { |
| const struct gpio_nct38xx_port_config *const config = dev->config; |
| struct gpio_nct38xx_port_data *const data = dev->data; |
| uint8_t reg, new_reg; |
| int ret; |
| |
| k_sem_take(data->lock, K_FOREVER); |
| |
| ret = i2c_reg_read_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), |
| ®); |
| if (ret < 0) { |
| goto done; |
| } |
| new_reg = reg ^ mask; |
| if (new_reg != reg) { |
| ret = i2c_reg_write_byte_dt(data->i2c_dev, |
| NCT38XX_REG_GPIO_DATA_OUT(config->gpio_port), new_reg); |
| } |
| |
| done: |
| k_sem_give(data->lock); |
| |
| return ret; |
| } |
| |
| static int gpio_nct38xx_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin, |
| enum gpio_int_mode mode, enum gpio_int_trig trig) |
| { |
| const struct gpio_nct38xx_port_config *const config = dev->config; |
| struct gpio_nct38xx_port_data *const data = dev->data; |
| uint8_t new_reg, new_rise, new_fall; |
| int ret; |
| uint32_t mask = BIT(pin); |
| |
| k_sem_take(data->lock, K_FOREVER); |
| |
| /* Disable irq before configuring them */ |
| new_reg = 0; |
| ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_ALERT_MASK(config->gpio_port), |
| mask, new_reg); |
| |
| /* Configure and enable interrupt? */ |
| if (mode == GPIO_INT_MODE_DISABLED) { |
| goto done; |
| } |
| |
| /* set edge register */ |
| if (mode == GPIO_INT_MODE_EDGE) { |
| if (trig == GPIO_INT_TRIG_LOW) { |
| new_rise = 0; |
| new_fall = mask; |
| } else if (trig == GPIO_INT_TRIG_HIGH) { |
| new_rise = mask; |
| new_fall = 0; |
| } else if (trig == GPIO_INT_TRIG_BOTH) { |
| new_rise = mask; |
| new_fall = mask; |
| } else { |
| LOG_ERR("Invalid interrupt trigger type %d", trig); |
| return -EINVAL; |
| } |
| } else { |
| /* level mode */ |
| new_rise = 0; |
| new_fall = 0; |
| } |
| |
| ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_ALERT_RISE(config->gpio_port), |
| mask, new_rise); |
| if (ret < 0) { |
| goto done; |
| } |
| |
| ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_ALERT_FALL(config->gpio_port), |
| mask, new_fall); |
| if (ret < 0) { |
| goto done; |
| } |
| |
| if (mode == GPIO_INT_MODE_LEVEL) { |
| /* set active high/low */ |
| if (trig == GPIO_INT_TRIG_LOW) { |
| new_reg = 0; |
| } else if (trig == GPIO_INT_TRIG_HIGH) { |
| new_reg = mask; |
| } else { |
| LOG_ERR("Invalid interrupt trigger type %d", trig); |
| ret = -EINVAL; |
| goto done; |
| } |
| ret = i2c_reg_update_byte_dt(data->i2c_dev, |
| NCT38XX_REG_GPIO_ALERT_LEVEL(config->gpio_port), mask, |
| new_reg); |
| |
| if (ret < 0) { |
| goto done; |
| } |
| } |
| |
| /* Clear pending bit */ |
| ret = i2c_reg_write_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_ALERT_STAT(config->gpio_port), |
| mask); |
| if (ret < 0) { |
| goto done; |
| } |
| |
| /* Enable it after configuration is completed */ |
| new_reg = mask; |
| ret = i2c_reg_update_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_ALERT_MASK(config->gpio_port), |
| mask, new_reg); |
| |
| done: |
| k_sem_give(data->lock); |
| |
| return ret; |
| } |
| |
| static int gpio_nct38xx_manage_callback(const struct device *dev, struct gpio_callback *callback, |
| bool set) |
| { |
| struct gpio_nct38xx_port_data *const data = dev->data; |
| |
| return gpio_manage_callback(&data->cb_list_gpio, callback, set); |
| } |
| |
| #ifdef CONFIG_GPIO_GET_DIRECTION |
| static int gpio_nct38xx_port_get_direction(const struct device *dev, gpio_port_pins_t mask, |
| gpio_port_pins_t *inputs, gpio_port_pins_t *outputs) |
| { |
| const struct gpio_nct38xx_port_config *const config = dev->config; |
| struct gpio_nct38xx_port_data *const data = dev->data; |
| uint8_t dir_reg; |
| int ret; |
| |
| k_sem_take(data->lock, K_FOREVER); |
| |
| if (config->gpio_port == 0) { |
| uint8_t enabled_gpios; |
| /* Remove the disabled GPIOs from the mask */ |
| ret = i2c_reg_read_byte_dt(data->i2c_dev, NCT38XX_REG_MUX_CONTROL, &enabled_gpios); |
| mask &= (enabled_gpios & config->common.port_pin_mask); |
| |
| if (ret < 0) { |
| goto done; |
| } |
| } |
| |
| /* Read direction register, 0 - input, 1 - output */ |
| ret = i2c_reg_read_byte_dt(data->i2c_dev, NCT38XX_REG_GPIO_DIR(config->gpio_port), |
| &dir_reg); |
| if (ret < 0) { |
| goto done; |
| } |
| |
| if (inputs) { |
| *inputs = mask & (~dir_reg); |
| } |
| |
| if (outputs) { |
| *outputs = mask & dir_reg; |
| } |
| |
| done: |
| k_sem_give(data->lock); |
| return ret; |
| } |
| #endif /* CONFIG_GPIO_GET_DIRECTION */ |
| |
| int gpio_nct38xx_dispatch_port_isr(const struct device *dev) |
| { |
| const struct gpio_nct38xx_port_config *const config = dev->config; |
| struct gpio_nct38xx_port_data *const data = dev->data; |
| uint8_t alert_pins, mask; |
| int ret; |
| |
| do { |
| k_sem_take(data->lock, K_FOREVER); |
| ret = i2c_reg_read_byte_dt( |
| data->i2c_dev, NCT38XX_REG_GPIO_ALERT_STAT(config->gpio_port), &alert_pins); |
| if (ret < 0) { |
| k_sem_give(data->lock); |
| return ret; |
| } |
| |
| ret = i2c_reg_read_byte_dt(data->i2c_dev, |
| NCT38XX_REG_GPIO_ALERT_MASK(config->gpio_port), &mask); |
| if (ret < 0) { |
| k_sem_give(data->lock); |
| return ret; |
| } |
| alert_pins &= mask; |
| if (alert_pins) { |
| ret = i2c_reg_write_byte_dt(data->i2c_dev, |
| NCT38XX_REG_GPIO_ALERT_STAT(config->gpio_port), |
| alert_pins); |
| if (ret < 0) { |
| k_sem_give(data->lock); |
| return ret; |
| } |
| } |
| k_sem_give(data->lock); |
| |
| gpio_fire_callbacks(&data->cb_list_gpio, dev, alert_pins); |
| |
| /* |
| * Vendor defined alert is generated if at least one STATn bit |
| * changes from 0 to 1. We should guarantee the STATn bit is |
| * clear to 0 before leaving isr. |
| */ |
| } while (alert_pins); |
| |
| return 0; |
| } |
| |
| static const struct gpio_driver_api gpio_nct38xx_driver = { |
| .pin_configure = gpio_nct38xx_pin_config, |
| #ifdef CONFIG_GPIO_GET_CONFIG |
| .pin_get_config = gpio_nct38xx_pin_get_config, |
| #endif /* CONFIG_GPIO_GET_CONFIG */ |
| .port_get_raw = gpio_nct38xx_port_get_raw, |
| .port_set_masked_raw = gpio_nct38xx_port_set_masked_raw, |
| .port_set_bits_raw = gpio_nct38xx_port_set_bits_raw, |
| .port_clear_bits_raw = gpio_nct38xx_port_clear_bits_raw, |
| .port_toggle_bits = gpio_nct38xx_port_toggle_bits, |
| .pin_interrupt_configure = gpio_nct38xx_pin_interrupt_configure, |
| .manage_callback = gpio_nct38xx_manage_callback, |
| #ifdef CONFIG_GPIO_GET_DIRECTION |
| .port_get_direction = gpio_nct38xx_port_get_direction, |
| #endif /* CONFIG_GPIO_GET_DIRECTION */ |
| }; |
| |
| 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->mfd)) { |
| LOG_ERR("%s is not ready", config->mfd->name); |
| return -ENODEV; |
| } |
| |
| data->lock = mfd_nct38xx_get_lock_reference(config->mfd); |
| data->i2c_dev = mfd_nct38xx_get_i2c_dt_spec(config->mfd); |
| |
| return 0; |
| } |
| |
| /* NCT38XX GPIO port driver must be initialized after NCT38XX GPIO driver */ |
| BUILD_ASSERT(CONFIG_GPIO_NCT38XX_PORT_INIT_PRIORITY > CONFIG_GPIO_NCT38XX_INIT_PRIORITY); |
| |
| #define GPIO_NCT38XX_PORT_DEVICE_INSTANCE(inst) \ |
| 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)}, \ |
| .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)), \ |
| }; \ |
| BUILD_ASSERT( \ |
| !(DT_INST_REG_ADDR(inst) == 0 && !(DT_INST_NODE_HAS_PROP(inst, pinmux_mask))), \ |
| "Port 0 should assign pinmux_mask property."); \ |
| static struct gpio_nct38xx_port_data gpio_nct38xx_port_data_##inst; \ |
| DEVICE_DT_INST_DEFINE(inst, gpio_nct38xx_port_init, NULL, &gpio_nct38xx_port_data_##inst, \ |
| &gpio_nct38xx_port_cfg_##inst, POST_KERNEL, \ |
| CONFIG_GPIO_NCT38XX_PORT_INIT_PRIORITY, &gpio_nct38xx_driver); |
| |
| DT_INST_FOREACH_STATUS_OKAY(GPIO_NCT38XX_PORT_DEVICE_INSTANCE) |