root/drivers/gpio/gpio-nct6694.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Nuvoton NCT6694 GPIO controller driver based on USB interface.
 *
 * Copyright (C) 2025 Nuvoton Technology Corp.
 */

#include <linux/bits.h>
#include <linux/gpio/driver.h>
#include <linux/idr.h>
#include <linux/interrupt.h>
#include <linux/mfd/nct6694.h>
#include <linux/module.h>
#include <linux/platform_device.h>

/*
 * 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 <tmyu0@nuvoton.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:nct6694-gpio");