root/drivers/iio/adc/nct7201.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * Driver for Nuvoton nct7201 and nct7202 power monitor chips.
 *
 * Copyright (c) 2024-2025 Nuvoton Technology corporation.
 */

#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/time.h>
#include <linux/types.h>
#include <linux/unaligned.h>

#include <linux/iio/events.h>
#include <linux/iio/iio.h>

#define NCT7201_REG_INTERRUPT_STATUS                    0x0C
#define NCT7201_REG_VOLT_LOW_BYTE                       0x0F
#define NCT7201_REG_CONFIGURATION                       0x10
#define  NCT7201_BIT_CONFIGURATION_START                BIT(0)
#define  NCT7201_BIT_CONFIGURATION_ALERT_MSK            BIT(1)
#define  NCT7201_BIT_CONFIGURATION_CONV_RATE            BIT(2)
#define  NCT7201_BIT_CONFIGURATION_RESET                BIT(7)

#define NCT7201_REG_ADVANCED_CONFIGURATION              0x11
#define  NCT7201_BIT_ADVANCED_CONF_MOD_ALERT            BIT(0)
#define  NCT7201_BIT_ADVANCED_CONF_MOD_STS              BIT(1)
#define  NCT7201_BIT_ADVANCED_CONF_FAULT_QUEUE          BIT(2)
#define  NCT7201_BIT_ADVANCED_CONF_EN_DEEP_SHUTDOWN     BIT(4)
#define  NCT7201_BIT_ADVANCED_CONF_EN_SMB_TIMEOUT       BIT(5)
#define  NCT7201_BIT_ADVANCED_CONF_MOD_RSTIN            BIT(7)

#define NCT7201_REG_CHANNEL_INPUT_MODE                  0x12
#define NCT7201_REG_CHANNEL_ENABLE                      0x13
#define NCT7201_REG_INTERRUPT_MASK_1                    0x15
#define NCT7201_REG_INTERRUPT_MASK_2                    0x16
#define NCT7201_REG_BUSY_STATUS                 0x1E
#define  NCT7201_BIT_BUSY                               BIT(0)
#define  NCT7201_BIT_PWR_UP                             BIT(1)
#define NCT7201_REG_ONE_SHOT                            0x1F
#define NCT7201_REG_SMUS_ADDRESS                        0xFC
#define NCT7201_REG_VIN_MASK                            GENMASK(15, 3)

#define NCT7201_REG_VIN(i)                              (0x00 + i)
#define NCT7201_REG_VIN_HIGH_LIMIT(i)                   (0x20 + (i) * 2)
#define NCT7201_REG_VIN_LOW_LIMIT(i)                    (0x21 + (i) * 2)
#define NCT7201_MAX_CHANNEL                             12

static const struct regmap_range nct7201_read_reg_range[] = {
        regmap_reg_range(NCT7201_REG_INTERRUPT_STATUS, NCT7201_REG_BUSY_STATUS),
        regmap_reg_range(NCT7201_REG_SMUS_ADDRESS, NCT7201_REG_SMUS_ADDRESS),
};

static const struct regmap_access_table nct7201_readable_regs_tbl = {
        .yes_ranges = nct7201_read_reg_range,
        .n_yes_ranges = ARRAY_SIZE(nct7201_read_reg_range),
};

static const struct regmap_range nct7201_write_reg_range[] = {
        regmap_reg_range(NCT7201_REG_CONFIGURATION, NCT7201_REG_INTERRUPT_MASK_2),
        regmap_reg_range(NCT7201_REG_ONE_SHOT, NCT7201_REG_ONE_SHOT),
};

static const struct regmap_access_table nct7201_writeable_regs_tbl = {
        .yes_ranges = nct7201_write_reg_range,
        .n_yes_ranges = ARRAY_SIZE(nct7201_write_reg_range),
};

static const struct regmap_range nct7201_read_vin_reg_range[] = {
        regmap_reg_range(NCT7201_REG_VIN(0), NCT7201_REG_VIN(NCT7201_MAX_CHANNEL - 1)),
        regmap_reg_range(NCT7201_REG_VIN_HIGH_LIMIT(0),
                         NCT7201_REG_VIN_LOW_LIMIT(NCT7201_MAX_CHANNEL - 1)),
};

static const struct regmap_access_table nct7201_readable_vin_regs_tbl = {
        .yes_ranges = nct7201_read_vin_reg_range,
        .n_yes_ranges = ARRAY_SIZE(nct7201_read_vin_reg_range),
};

static const struct regmap_range nct7201_write_vin_reg_range[] = {
        regmap_reg_range(NCT7201_REG_VIN_HIGH_LIMIT(0),
                         NCT7201_REG_VIN_LOW_LIMIT(NCT7201_MAX_CHANNEL - 1)),
};

static const struct regmap_access_table nct7201_writeable_vin_regs_tbl = {
        .yes_ranges = nct7201_write_vin_reg_range,
        .n_yes_ranges = ARRAY_SIZE(nct7201_write_vin_reg_range),
};

static const struct regmap_config nct7201_regmap8_config = {
        .name = "vin-data-read-byte",
        .reg_bits = 8,
        .val_bits = 8,
        .use_single_read = true,
        .use_single_write = true,
        .max_register = 0xff,
        .rd_table = &nct7201_readable_regs_tbl,
        .wr_table = &nct7201_writeable_regs_tbl,
};

static const struct regmap_config nct7201_regmap16_config = {
        .name = "vin-data-read-word",
        .reg_bits = 8,
        .val_bits = 16,
        .max_register = 0xff,
        .rd_table = &nct7201_readable_vin_regs_tbl,
        .wr_table = &nct7201_writeable_vin_regs_tbl,
};

struct nct7201_chip_info {
        struct regmap *regmap;
        struct regmap *regmap16;
        int num_vin_channels;
        __le16 vin_mask;
};

struct nct7201_adc_model_data {
        const char *model_name;
        const struct iio_chan_spec *channels;
        unsigned int num_channels;
        int num_vin_channels;
};

static const struct iio_event_spec nct7201_events[] = {
        {
                .type = IIO_EV_TYPE_THRESH,
                .dir = IIO_EV_DIR_RISING,
                .mask_separate = BIT(IIO_EV_INFO_VALUE) |
                                 BIT(IIO_EV_INFO_ENABLE),
        }, {
                .type = IIO_EV_TYPE_THRESH,
                .dir = IIO_EV_DIR_FALLING,
                .mask_separate = BIT(IIO_EV_INFO_VALUE) |
                                 BIT(IIO_EV_INFO_ENABLE),
        },
};

#define NCT7201_VOLTAGE_CHANNEL(num)                                    \
        {                                                               \
                .type = IIO_VOLTAGE,                                    \
                .indexed = 1,                                           \
                .channel = num + 1,                                     \
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),           \
                .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),   \
                .address = num,                                         \
                .event_spec = nct7201_events,                           \
                .num_event_specs = ARRAY_SIZE(nct7201_events),          \
        }

static const struct iio_chan_spec nct7201_channels[] = {
        NCT7201_VOLTAGE_CHANNEL(0),
        NCT7201_VOLTAGE_CHANNEL(1),
        NCT7201_VOLTAGE_CHANNEL(2),
        NCT7201_VOLTAGE_CHANNEL(3),
        NCT7201_VOLTAGE_CHANNEL(4),
        NCT7201_VOLTAGE_CHANNEL(5),
        NCT7201_VOLTAGE_CHANNEL(6),
        NCT7201_VOLTAGE_CHANNEL(7),
};

static const struct iio_chan_spec nct7202_channels[] = {
        NCT7201_VOLTAGE_CHANNEL(0),
        NCT7201_VOLTAGE_CHANNEL(1),
        NCT7201_VOLTAGE_CHANNEL(2),
        NCT7201_VOLTAGE_CHANNEL(3),
        NCT7201_VOLTAGE_CHANNEL(4),
        NCT7201_VOLTAGE_CHANNEL(5),
        NCT7201_VOLTAGE_CHANNEL(6),
        NCT7201_VOLTAGE_CHANNEL(7),
        NCT7201_VOLTAGE_CHANNEL(8),
        NCT7201_VOLTAGE_CHANNEL(9),
        NCT7201_VOLTAGE_CHANNEL(10),
        NCT7201_VOLTAGE_CHANNEL(11),
};

static int nct7201_read_raw(struct iio_dev *indio_dev,
                            struct iio_chan_spec const *chan,
                            int *val, int *val2, long mask)
{
        struct nct7201_chip_info *chip = iio_priv(indio_dev);
        unsigned int value;
        int err;

        if (chan->type != IIO_VOLTAGE)
                return -EOPNOTSUPP;

        switch (mask) {
        case IIO_CHAN_INFO_RAW:
                err = regmap_read(chip->regmap16, NCT7201_REG_VIN(chan->address), &value);
                if (err)
                        return err;
                *val = FIELD_GET(NCT7201_REG_VIN_MASK, value);
                return IIO_VAL_INT;
        case IIO_CHAN_INFO_SCALE:
                /* From the datasheet, we have to multiply by 0.0004995 */
                *val = 0;
                *val2 = 499500;
                return IIO_VAL_INT_PLUS_NANO;
        default:
                return -EINVAL;
        }
}

static int nct7201_read_event_value(struct iio_dev *indio_dev,
                                    const struct iio_chan_spec *chan,
                                    enum iio_event_type type,
                                    enum iio_event_direction dir,
                                    enum iio_event_info info,
                                    int *val, int *val2)
{
        struct nct7201_chip_info *chip = iio_priv(indio_dev);
        unsigned int value;
        int err;

        if (chan->type != IIO_VOLTAGE)
                return -EOPNOTSUPP;

        if (info != IIO_EV_INFO_VALUE)
                return -EINVAL;

        if (dir == IIO_EV_DIR_FALLING)
                err = regmap_read(chip->regmap16, NCT7201_REG_VIN_LOW_LIMIT(chan->address),
                                  &value);
        else
                err = regmap_read(chip->regmap16, NCT7201_REG_VIN_HIGH_LIMIT(chan->address),
                                  &value);
        if (err)
                return err;

        *val = FIELD_GET(NCT7201_REG_VIN_MASK, value);

        return IIO_VAL_INT;
}

static int nct7201_write_event_value(struct iio_dev *indio_dev,
                                     const struct iio_chan_spec *chan,
                                     enum iio_event_type type,
                                     enum iio_event_direction dir,
                                     enum iio_event_info info,
                                     int val, int val2)
{
        struct nct7201_chip_info *chip = iio_priv(indio_dev);
        int err;

        if (chan->type != IIO_VOLTAGE)
                return -EOPNOTSUPP;

        if (info != IIO_EV_INFO_VALUE)
                return -EOPNOTSUPP;

        if (dir == IIO_EV_DIR_FALLING)
                err = regmap_write(chip->regmap16, NCT7201_REG_VIN_LOW_LIMIT(chan->address),
                                   FIELD_PREP(NCT7201_REG_VIN_MASK, val));
        else
                err = regmap_write(chip->regmap16, NCT7201_REG_VIN_HIGH_LIMIT(chan->address),
                                   FIELD_PREP(NCT7201_REG_VIN_MASK, val));

        return err;
}

static int nct7201_read_event_config(struct iio_dev *indio_dev,
                                     const struct iio_chan_spec *chan,
                                     enum iio_event_type type,
                                     enum iio_event_direction dir)
{
        struct nct7201_chip_info *chip = iio_priv(indio_dev);

        if (chan->type != IIO_VOLTAGE)
                return -EOPNOTSUPP;

        return !!(le16_to_cpu(chip->vin_mask) & BIT(chan->address));
}

static int nct7201_write_event_config(struct iio_dev *indio_dev,
                                      const struct iio_chan_spec *chan,
                                      enum iio_event_type type,
                                      enum iio_event_direction dir,
                                      bool state)
{
        struct nct7201_chip_info *chip = iio_priv(indio_dev);
        __le16 mask = cpu_to_le16(BIT(chan->address));
        int err;

        if (chan->type != IIO_VOLTAGE)
                return -EOPNOTSUPP;

        if (state)
                chip->vin_mask |= mask;
        else
                chip->vin_mask &= ~mask;

        if (chip->num_vin_channels <= 8)
                err = regmap_write(chip->regmap, NCT7201_REG_CHANNEL_ENABLE,
                                   le16_to_cpu(chip->vin_mask));
        else
                err = regmap_bulk_write(chip->regmap, NCT7201_REG_CHANNEL_ENABLE,
                                        &chip->vin_mask, sizeof(chip->vin_mask));

        return err;
}

static const struct iio_info nct7201_info = {
        .read_raw = nct7201_read_raw,
        .read_event_config = nct7201_read_event_config,
        .write_event_config = nct7201_write_event_config,
        .read_event_value = nct7201_read_event_value,
        .write_event_value = nct7201_write_event_value,
};

static const struct iio_info nct7201_info_no_irq = {
        .read_raw = nct7201_read_raw,
};

static const struct nct7201_adc_model_data nct7201_model_data = {
        .model_name = "nct7201",
        .channels = nct7201_channels,
        .num_channels = ARRAY_SIZE(nct7201_channels),
        .num_vin_channels = 8,
};

static const struct nct7201_adc_model_data nct7202_model_data = {
        .model_name = "nct7202",
        .channels = nct7202_channels,
        .num_channels = ARRAY_SIZE(nct7202_channels),
        .num_vin_channels = 12,
};

static int nct7201_init_chip(struct nct7201_chip_info *chip)
{
        struct device *dev = regmap_get_device(chip->regmap);
        __le16 data = cpu_to_le16(GENMASK(chip->num_vin_channels - 1, 0));
        unsigned int value;
        int err;

        err = regmap_write(chip->regmap, NCT7201_REG_CONFIGURATION,
                           NCT7201_BIT_CONFIGURATION_RESET);
        if (err)
                return dev_err_probe(dev, err, "Failed to reset chip\n");

        /*
         * After about 25 msecs, the device should be ready and then the power-up
         * bit will be set to 1.
         */
        fsleep(25 * USEC_PER_MSEC);

        err = regmap_read(chip->regmap, NCT7201_REG_BUSY_STATUS, &value);
        if (err)
                return dev_err_probe(dev, err, "Failed to read busy status\n");
        if (!(value & NCT7201_BIT_PWR_UP))
                return dev_err_probe(dev, -EIO, "Failed to power up after reset\n");

        /* Enable Channels */
        if (chip->num_vin_channels <= 8)
                err = regmap_write(chip->regmap, NCT7201_REG_CHANNEL_ENABLE,
                                   le16_to_cpu(data));
        else
                err = regmap_bulk_write(chip->regmap, NCT7201_REG_CHANNEL_ENABLE,
                                        &data, sizeof(data));
        if (err)
                return dev_err_probe(dev, err, "Failed to enable channels\n");

        err = regmap_bulk_read(chip->regmap, NCT7201_REG_CHANNEL_ENABLE,
                               &chip->vin_mask, sizeof(chip->vin_mask));
        if (err)
                return dev_err_probe(dev, err,
                                     "Failed to read channel enable register\n");

        /* Start monitoring if needed */
        err = regmap_set_bits(chip->regmap, NCT7201_REG_CONFIGURATION,
                              NCT7201_BIT_CONFIGURATION_START);
        if (err)
                return dev_err_probe(dev, err, "Failed to start monitoring\n");

        return 0;
}

static irqreturn_t nct7201_irq_handler(int irq, void *private)
{
        struct iio_dev *indio_dev = private;
        struct nct7201_chip_info *chip = iio_priv(indio_dev);
        __le16 data;
        int err;

        err = regmap_bulk_read(chip->regmap, NCT7201_REG_INTERRUPT_STATUS,
                               &data, sizeof(data));
        if (err)
                return IRQ_NONE;

        if (data)
                iio_push_event(indio_dev,
                               IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE,
                                                    0,
                                                    IIO_EV_TYPE_THRESH,
                                                    IIO_EV_DIR_EITHER),
                               iio_get_time_ns(indio_dev));

        return IRQ_HANDLED;
}

static int nct7201_probe(struct i2c_client *client)
{
        const struct nct7201_adc_model_data *model_data;
        struct device *dev = &client->dev;
        struct nct7201_chip_info *chip;
        struct iio_dev *indio_dev;
        int ret;

        model_data = i2c_get_match_data(client);
        if (!model_data)
                return -ENODEV;

        indio_dev = devm_iio_device_alloc(dev, sizeof(*chip));
        if (!indio_dev)
                return -ENOMEM;
        chip = iio_priv(indio_dev);

        chip->regmap = devm_regmap_init_i2c(client, &nct7201_regmap8_config);
        if (IS_ERR(chip->regmap))
                return dev_err_probe(dev, PTR_ERR(chip->regmap),
                                     "Failed to init regmap\n");

        chip->regmap16 = devm_regmap_init_i2c(client, &nct7201_regmap16_config);
        if (IS_ERR(chip->regmap16))
                return dev_err_probe(dev, PTR_ERR(chip->regmap16),
                                     "Failed to init regmap16\n");

        chip->num_vin_channels = model_data->num_vin_channels;

        ret = nct7201_init_chip(chip);
        if (ret)
                return ret;

        indio_dev->name = model_data->model_name;
        indio_dev->channels = model_data->channels;
        indio_dev->num_channels = model_data->num_channels;
        if (client->irq) {
                /* Enable alert function */
                ret = regmap_clear_bits(chip->regmap, NCT7201_REG_CONFIGURATION,
                                      NCT7201_BIT_CONFIGURATION_ALERT_MSK);
                if (ret)
                        return dev_err_probe(dev, ret,
                                             "Failed to enable alert function\n");

                ret = devm_request_threaded_irq(dev, client->irq,
                                                NULL, nct7201_irq_handler,
                                                IRQF_TRIGGER_LOW | IRQF_ONESHOT,
                                                client->name, indio_dev);
                if (ret)
                        return dev_err_probe(dev, ret,
                                             "Failed to assign interrupt.\n");

                indio_dev->info = &nct7201_info;
        } else {
                indio_dev->info = &nct7201_info_no_irq;
        }
        indio_dev->modes = INDIO_DIRECT_MODE;

        return devm_iio_device_register(dev, indio_dev);
}

static const struct i2c_device_id nct7201_id[] = {
        { .name = "nct7201", .driver_data = (kernel_ulong_t)&nct7201_model_data },
        { .name = "nct7202", .driver_data = (kernel_ulong_t)&nct7202_model_data },
        { }
};
MODULE_DEVICE_TABLE(i2c, nct7201_id);

static const struct of_device_id nct7201_of_match[] = {
        {
                .compatible = "nuvoton,nct7201",
                .data = &nct7201_model_data,
        },
        {
                .compatible = "nuvoton,nct7202",
                .data = &nct7202_model_data,
        },
        { }
};
MODULE_DEVICE_TABLE(of, nct7201_of_match);

static struct i2c_driver nct7201_driver = {
        .driver = {
                .name   = "nct7201",
                .of_match_table = nct7201_of_match,
        },
        .probe = nct7201_probe,
        .id_table = nct7201_id,
};
module_i2c_driver(nct7201_driver);

MODULE_AUTHOR("Eason Yang <j2anfernee@gmail.com>");
MODULE_DESCRIPTION("Nuvoton NCT7201 voltage monitor driver");
MODULE_LICENSE("GPL");