/* * 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 #include #include #include 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)