// SPDX-License-Identifier: GPL-2.0 /* * Nuvoton NCT6694 GPIO controller driver based on USB interface. * * Copyright (C) 2025 Nuvoton Technology Corp. */ #include #include #include #include #include #include #include /* * USB command module type for NCT6694 GPIO controller. * This defines the module type used for communication with the NCT6694 * GPIO controller over the USB interface. */ #define NCT6694_GPIO_MOD 0xFF #define NCT6694_GPIO_VER 0x90 #define NCT6694_GPIO_VALID 0x110 #define NCT6694_GPI_DATA 0x120 #define NCT6694_GPO_DIR 0x170 #define NCT6694_GPO_TYPE 0x180 #define NCT6694_GPO_DATA 0x190 #define NCT6694_GPI_STS 0x130 #define NCT6694_GPI_CLR 0x140 #define NCT6694_GPI_FALLING 0x150 #define NCT6694_GPI_RISING 0x160 #define NCT6694_NR_GPIO 8 struct nct6694_gpio_data { struct nct6694 *nct6694; struct gpio_chip gpio; struct mutex lock; /* Protect irq operation */ struct mutex irq_lock; unsigned char reg_val; unsigned char irq_trig_falling; unsigned char irq_trig_rising; /* Current gpio group */ unsigned char group; int irq; }; static int nct6694_get_direction(struct gpio_chip *gpio, unsigned int offset) { struct nct6694_gpio_data *data = gpiochip_get_data(gpio); const struct nct6694_cmd_header cmd_hd = { .mod = NCT6694_GPIO_MOD, .offset = cpu_to_le16(NCT6694_GPO_DIR + data->group), .len = cpu_to_le16(sizeof(data->reg_val)) }; int ret; guard(mutex)(&data->lock); ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); if (ret < 0) return ret; return !(BIT(offset) & data->reg_val); } static int nct6694_direction_input(struct gpio_chip *gpio, unsigned int offset) { struct nct6694_gpio_data *data = gpiochip_get_data(gpio); const struct nct6694_cmd_header cmd_hd = { .mod = NCT6694_GPIO_MOD, .offset = cpu_to_le16(NCT6694_GPO_DIR + data->group), .len = cpu_to_le16(sizeof(data->reg_val)) }; int ret; guard(mutex)(&data->lock); ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); if (ret < 0) return ret; data->reg_val &= ~BIT(offset); return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val); } static int nct6694_direction_output(struct gpio_chip *gpio, unsigned int offset, int val) { struct nct6694_gpio_data *data = gpiochip_get_data(gpio); struct nct6694_cmd_header cmd_hd = { .mod = NCT6694_GPIO_MOD, .offset = cpu_to_le16(NCT6694_GPO_DIR + data->group), .len = cpu_to_le16(sizeof(data->reg_val)) }; int ret; guard(mutex)(&data->lock); /* Set direction to output */ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); if (ret < 0) return ret; data->reg_val |= BIT(offset); ret = nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val); if (ret < 0) return ret; /* Then set output level */ cmd_hd.offset = cpu_to_le16(NCT6694_GPO_DATA + data->group); ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); if (ret < 0) return ret; if (val) data->reg_val |= BIT(offset); else data->reg_val &= ~BIT(offset); return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val); } static int nct6694_get_value(struct gpio_chip *gpio, unsigned int offset) { struct nct6694_gpio_data *data = gpiochip_get_data(gpio); struct nct6694_cmd_header cmd_hd = { .mod = NCT6694_GPIO_MOD, .offset = cpu_to_le16(NCT6694_GPO_DIR + data->group), .len = cpu_to_le16(sizeof(data->reg_val)) }; int ret; guard(mutex)(&data->lock); ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); if (ret < 0) return ret; if (BIT(offset) & data->reg_val) { cmd_hd.offset = cpu_to_le16(NCT6694_GPO_DATA + data->group); ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); if (ret < 0) return ret; return !!(BIT(offset) & data->reg_val); } cmd_hd.offset = cpu_to_le16(NCT6694_GPI_DATA + data->group); ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); if (ret < 0) return ret; return !!(BIT(offset) & data->reg_val); } static int nct6694_set_value(struct gpio_chip *gpio, unsigned int offset, int val) { struct nct6694_gpio_data *data = gpiochip_get_data(gpio); const struct nct6694_cmd_header cmd_hd = { .mod = NCT6694_GPIO_MOD, .offset = cpu_to_le16(NCT6694_GPO_DATA + data->group), .len = cpu_to_le16(sizeof(data->reg_val)) }; int ret; guard(mutex)(&data->lock); ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); if (ret < 0) return ret; if (val) data->reg_val |= BIT(offset); else data->reg_val &= ~BIT(offset); return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val); } static int nct6694_set_config(struct gpio_chip *gpio, unsigned int offset, unsigned long config) { struct nct6694_gpio_data *data = gpiochip_get_data(gpio); const struct nct6694_cmd_header cmd_hd = { .mod = NCT6694_GPIO_MOD, .offset = cpu_to_le16(NCT6694_GPO_TYPE + data->group), .len = cpu_to_le16(sizeof(data->reg_val)) }; int ret; guard(mutex)(&data->lock); ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); if (ret < 0) return ret; switch (pinconf_to_config_param(config)) { case PIN_CONFIG_DRIVE_OPEN_DRAIN: data->reg_val |= BIT(offset); break; case PIN_CONFIG_DRIVE_PUSH_PULL: data->reg_val &= ~BIT(offset); break; default: return -ENOTSUPP; } return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val); } static int nct6694_init_valid_mask(struct gpio_chip *gpio, unsigned long *valid_mask, unsigned int ngpios) { struct nct6694_gpio_data *data = gpiochip_get_data(gpio); const struct nct6694_cmd_header cmd_hd = { .mod = NCT6694_GPIO_MOD, .offset = cpu_to_le16(NCT6694_GPIO_VALID + data->group), .len = cpu_to_le16(sizeof(data->reg_val)) }; int ret; guard(mutex)(&data->lock); ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); if (ret < 0) return ret; *valid_mask = data->reg_val; return ret; } static irqreturn_t nct6694_irq_handler(int irq, void *priv) { struct nct6694_gpio_data *data = priv; struct nct6694_cmd_header cmd_hd = { .mod = NCT6694_GPIO_MOD, .offset = cpu_to_le16(NCT6694_GPI_STS + data->group), .len = cpu_to_le16(sizeof(data->reg_val)) }; unsigned char status; int ret; guard(mutex)(&data->lock); ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); if (ret) return IRQ_NONE; status = data->reg_val; while (status) { int bit = __ffs(status); data->reg_val = BIT(bit); handle_nested_irq(irq_find_mapping(data->gpio.irq.domain, bit)); status &= ~BIT(bit); cmd_hd.offset = cpu_to_le16(NCT6694_GPI_CLR + data->group); nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val); } return IRQ_HANDLED; } static int nct6694_get_irq_trig(struct nct6694_gpio_data *data) { struct nct6694_cmd_header cmd_hd = { .mod = NCT6694_GPIO_MOD, .offset = cpu_to_le16(NCT6694_GPI_FALLING + data->group), .len = cpu_to_le16(sizeof(data->reg_val)) }; int ret; guard(mutex)(&data->lock); ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->irq_trig_falling); if (ret) return ret; cmd_hd.offset = cpu_to_le16(NCT6694_GPI_RISING + data->group); return nct6694_read_msg(data->nct6694, &cmd_hd, &data->irq_trig_rising); } static void nct6694_irq_mask(struct irq_data *d) { struct gpio_chip *gpio = irq_data_get_irq_chip_data(d); irq_hw_number_t hwirq = irqd_to_hwirq(d); gpiochip_disable_irq(gpio, hwirq); } static void nct6694_irq_unmask(struct irq_data *d) { struct gpio_chip *gpio = irq_data_get_irq_chip_data(d); irq_hw_number_t hwirq = irqd_to_hwirq(d); gpiochip_enable_irq(gpio, hwirq); } static int nct6694_irq_set_type(struct irq_data *d, unsigned int type) { struct gpio_chip *gpio = irq_data_get_irq_chip_data(d); struct nct6694_gpio_data *data = gpiochip_get_data(gpio); irq_hw_number_t hwirq = irqd_to_hwirq(d); guard(mutex)(&data->lock); switch (type) { case IRQ_TYPE_EDGE_RISING: data->irq_trig_rising |= BIT(hwirq); break; case IRQ_TYPE_EDGE_FALLING: data->irq_trig_falling |= BIT(hwirq); break; case IRQ_TYPE_EDGE_BOTH: data->irq_trig_rising |= BIT(hwirq); data->irq_trig_falling |= BIT(hwirq); break; default: return -ENOTSUPP; } return 0; } static void nct6694_irq_bus_lock(struct irq_data *d) { struct gpio_chip *gpio = irq_data_get_irq_chip_data(d); struct nct6694_gpio_data *data = gpiochip_get_data(gpio); mutex_lock(&data->irq_lock); } static void nct6694_irq_bus_sync_unlock(struct irq_data *d) { struct gpio_chip *gpio = irq_data_get_irq_chip_data(d); struct nct6694_gpio_data *data = gpiochip_get_data(gpio); struct nct6694_cmd_header cmd_hd = { .mod = NCT6694_GPIO_MOD, .offset = cpu_to_le16(NCT6694_GPI_FALLING + data->group), .len = cpu_to_le16(sizeof(data->reg_val)) }; scoped_guard(mutex, &data->lock) { nct6694_write_msg(data->nct6694, &cmd_hd, &data->irq_trig_falling); cmd_hd.offset = cpu_to_le16(NCT6694_GPI_RISING + data->group); nct6694_write_msg(data->nct6694, &cmd_hd, &data->irq_trig_rising); } mutex_unlock(&data->irq_lock); } static const struct irq_chip nct6694_irq_chip = { .name = "gpio-nct6694", .irq_mask = nct6694_irq_mask, .irq_unmask = nct6694_irq_unmask, .irq_set_type = nct6694_irq_set_type, .irq_bus_lock = nct6694_irq_bus_lock, .irq_bus_sync_unlock = nct6694_irq_bus_sync_unlock, .flags = IRQCHIP_IMMUTABLE, GPIOCHIP_IRQ_RESOURCE_HELPERS, }; static void nct6694_irq_dispose_mapping(void *d) { struct nct6694_gpio_data *data = d; irq_dispose_mapping(data->irq); } static void nct6694_gpio_ida_free(void *d) { struct nct6694_gpio_data *data = d; struct nct6694 *nct6694 = data->nct6694; ida_free(&nct6694->gpio_ida, data->group); } static int nct6694_gpio_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct nct6694 *nct6694 = dev_get_drvdata(dev->parent); struct nct6694_gpio_data *data; struct gpio_irq_chip *girq; int ret, i; char **names; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; data->nct6694 = nct6694; ret = ida_alloc(&nct6694->gpio_ida, GFP_KERNEL); if (ret < 0) return ret; data->group = ret; ret = devm_add_action_or_reset(dev, nct6694_gpio_ida_free, data); if (ret) return ret; names = devm_kcalloc(dev, NCT6694_NR_GPIO, sizeof(char *), GFP_KERNEL); if (!names) return -ENOMEM; for (i = 0; i < NCT6694_NR_GPIO; i++) { names[i] = devm_kasprintf(dev, GFP_KERNEL, "GPIO%X%d", data->group, i); if (!names[i]) return -ENOMEM; } data->irq = irq_create_mapping(nct6694->domain, NCT6694_IRQ_GPIO0 + data->group); if (!data->irq) return -EINVAL; ret = devm_add_action_or_reset(dev, nct6694_irq_dispose_mapping, data); if (ret) return ret; data->gpio.names = (const char * const*)names; data->gpio.label = pdev->name; data->gpio.direction_input = nct6694_direction_input; data->gpio.get = nct6694_get_value; data->gpio.direction_output = nct6694_direction_output; data->gpio.set = nct6694_set_value; data->gpio.get_direction = nct6694_get_direction; data->gpio.set_config = nct6694_set_config; data->gpio.init_valid_mask = nct6694_init_valid_mask; data->gpio.base = -1; data->gpio.can_sleep = false; data->gpio.owner = THIS_MODULE; data->gpio.ngpio = NCT6694_NR_GPIO; platform_set_drvdata(pdev, data); ret = devm_mutex_init(dev, &data->lock); if (ret) return ret; ret = devm_mutex_init(dev, &data->irq_lock); if (ret) return ret; ret = nct6694_get_irq_trig(data); if (ret) { dev_err_probe(dev, ret, "Failed to get irq trigger type\n"); return ret; } girq = &data->gpio.irq; gpio_irq_chip_set_chip(girq, &nct6694_irq_chip); girq->parent_handler = NULL; girq->num_parents = 0; girq->parents = NULL; girq->default_type = IRQ_TYPE_NONE; girq->handler = handle_level_irq; girq->threaded = true; ret = devm_request_threaded_irq(dev, data->irq, NULL, nct6694_irq_handler, IRQF_ONESHOT | IRQF_SHARED, "gpio-nct6694", data); if (ret) { dev_err_probe(dev, ret, "Failed to request irq\n"); return ret; } return devm_gpiochip_add_data(dev, &data->gpio, data); } static struct platform_driver nct6694_gpio_driver = { .driver = { .name = "nct6694-gpio", }, .probe = nct6694_gpio_probe, }; module_platform_driver(nct6694_gpio_driver); MODULE_DESCRIPTION("USB-GPIO controller driver for NCT6694"); MODULE_AUTHOR("Ming Yu "); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:nct6694-gpio");