root/drivers/hwmon/chipcap2.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * cc2.c - Support for the Amphenol ChipCap 2 relative humidity, temperature sensor
 *
 * Part numbers supported:
 * CC2D23, CC2D23S, CC2D25, CC2D25S, CC2D33, CC2D33S, CC2D35, CC2D35S
 *
 * Author: Javier Carrasco <javier.carrasco.cruz@gmail.com>
 *
 * Datasheet and application notes:
 * https://www.amphenol-sensors.com/en/telaire/humidity/527-humidity-sensors/3095-chipcap-2
 */

#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/hwmon.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/regulator/consumer.h>

#define CC2_START_CM                    0xA0
#define CC2_START_NOM                   0x80
#define CC2_R_ALARM_H_ON                0x18
#define CC2_R_ALARM_H_OFF               0x19
#define CC2_R_ALARM_L_ON                0x1A
#define CC2_R_ALARM_L_OFF               0x1B
#define CC2_RW_OFFSET                   0x40
#define CC2_W_ALARM_H_ON                (CC2_R_ALARM_H_ON + CC2_RW_OFFSET)
#define CC2_W_ALARM_H_OFF               (CC2_R_ALARM_H_OFF + CC2_RW_OFFSET)
#define CC2_W_ALARM_L_ON                (CC2_R_ALARM_L_ON + CC2_RW_OFFSET)
#define CC2_W_ALARM_L_OFF               (CC2_R_ALARM_L_OFF + CC2_RW_OFFSET)

#define CC2_STATUS_FIELD                GENMASK(7, 6)
#define CC2_STATUS_VALID_DATA           0x00
#define CC2_STATUS_STALE_DATA           0x01
#define CC2_STATUS_CMD_MODE             0x02

#define CC2_RESPONSE_FIELD              GENMASK(1, 0)
#define CC2_RESPONSE_BUSY               0x00
#define CC2_RESPONSE_ACK                0x01
#define CC2_RESPONSE_NACK               0x02

#define CC2_ERR_CORR_EEPROM             BIT(2)
#define CC2_ERR_UNCORR_EEPROM           BIT(3)
#define CC2_ERR_RAM_PARITY              BIT(4)
#define CC2_ERR_CONFIG_LOAD             BIT(5)

#define CC2_EEPROM_SIZE                 10
#define CC2_EEPROM_DATA_LEN             3
#define CC2_MEASUREMENT_DATA_LEN        4

#define CC2_RH_DATA_FIELD               GENMASK(13, 0)

/* ensure clean off -> on transitions */
#define CC2_POWER_CYCLE_MS              80

#define CC2_STARTUP_TO_DATA_MS          55
#define CC2_RESP_START_CM_US            100
#define CC2_RESP_EEPROM_R_US            100
#define CC2_RESP_EEPROM_W_MS            12
#define CC2_STARTUP_TIME_US             1250

#define CC2_RH_MAX                      (100 * 1000U)

#define CC2_CM_RETRIES                  5

struct cc2_rh_alarm_info {
        bool low_alarm;
        bool high_alarm;
        bool low_alarm_visible;
        bool high_alarm_visible;
};

struct cc2_data {
        struct cc2_rh_alarm_info rh_alarm;
        struct completion complete;
        struct device *hwmon;
        struct i2c_client *client;
        struct regulator *regulator;
        const char *name;
        int irq_ready;
        int irq_low;
        int irq_high;
        bool process_irqs;
};

enum cc2_chan_addr {
        CC2_CHAN_TEMP = 0,
        CC2_CHAN_HUMIDITY,
};

/* %RH as a per cent mille from a register value */
static long cc2_rh_convert(u16 data)
{
        unsigned long tmp = (data & CC2_RH_DATA_FIELD) * CC2_RH_MAX;

        return tmp / ((1 << 14) - 1);
}

/* convert %RH to a register value */
static u16 cc2_rh_to_reg(long data)
{
        return data * ((1 << 14) - 1) / CC2_RH_MAX;
}

/* temperature in milli degrees celsius from a register value */
static long cc2_temp_convert(u16 data)
{
        unsigned long tmp = ((data >> 2) * 165 * 1000U) / ((1 << 14) - 1);

        return tmp - 40 * 1000U;
}

static int cc2_enable(struct cc2_data *data)
{
        int ret;

        /* exclusive regulator, check in case a disable failed */
        if (regulator_is_enabled(data->regulator))
                return 0;

        /* clear any pending completion */
        try_wait_for_completion(&data->complete);

        ret = regulator_enable(data->regulator);
        if (ret < 0)
                return ret;

        usleep_range(CC2_STARTUP_TIME_US, CC2_STARTUP_TIME_US + 125);

        data->process_irqs = true;

        return 0;
}

static void cc2_disable(struct cc2_data *data)
{
        int err;

        /* ignore alarms triggered by voltage toggling when powering up */
        data->process_irqs = false;

        /* exclusive regulator, check in case an enable failed */
        if (regulator_is_enabled(data->regulator)) {
                err = regulator_disable(data->regulator);
                if (err)
                        dev_dbg(&data->client->dev, "Failed to disable device");
        }
}

static int cc2_cmd_response_diagnostic(struct device *dev, u8 status)
{
        int resp;

        if (FIELD_GET(CC2_STATUS_FIELD, status) != CC2_STATUS_CMD_MODE) {
                dev_dbg(dev, "Command sent out of command window\n");
                return -ETIMEDOUT;
        }

        resp = FIELD_GET(CC2_RESPONSE_FIELD, status);
        switch (resp) {
        case CC2_RESPONSE_ACK:
                return 0;
        case CC2_RESPONSE_BUSY:
                return -EBUSY;
        case CC2_RESPONSE_NACK:
                if (resp & CC2_ERR_CORR_EEPROM)
                        dev_dbg(dev, "Command failed: corrected EEPROM\n");
                if (resp & CC2_ERR_UNCORR_EEPROM)
                        dev_dbg(dev, "Command failed: uncorrected EEPROM\n");
                if (resp & CC2_ERR_RAM_PARITY)
                        dev_dbg(dev, "Command failed: RAM parity\n");
                if (resp & CC2_ERR_RAM_PARITY)
                        dev_dbg(dev, "Command failed: configuration error\n");
                return -ENODATA;
        default:
                dev_dbg(dev, "Unknown command reply\n");
                return -EINVAL;
        }
}

static int cc2_read_command_status(struct i2c_client *client)
{
        u8 status;
        int ret;

        ret = i2c_master_recv(client, &status, 1);
        if (ret != 1) {
                ret = ret < 0 ? ret : -EIO;
                return ret;
        }

        return cc2_cmd_response_diagnostic(&client->dev, status);
}

/*
 * The command mode is only accessible after sending the START_CM command in the
 * first 10 ms after power-up. Only in case the command window is missed,
 * CC2_CM_RETRIES retries are attempted before giving up and returning an error.
 */
static int cc2_command_mode_start(struct cc2_data *data)
{
        unsigned long timeout;
        int i, ret;

        for (i = 0; i < CC2_CM_RETRIES; i++) {
                ret = cc2_enable(data);
                if (ret < 0)
                        return ret;

                ret = i2c_smbus_write_word_data(data->client, CC2_START_CM, 0);
                if (ret < 0)
                        return ret;

                if (data->irq_ready > 0) {
                        timeout = usecs_to_jiffies(2 * CC2_RESP_START_CM_US);
                        ret = wait_for_completion_timeout(&data->complete,
                                                          timeout);
                        if (!ret)
                                return -ETIMEDOUT;
                } else {
                        usleep_range(CC2_RESP_START_CM_US,
                                     2 * CC2_RESP_START_CM_US);
                }
                ret = cc2_read_command_status(data->client);
                if (ret != -ETIMEDOUT || i == CC2_CM_RETRIES)
                        break;

                /* command window missed, prepare for a retry */
                cc2_disable(data);
                msleep(CC2_POWER_CYCLE_MS);
        }

        return ret;
}

/* Sending a Start_NOM command finishes the command mode immediately with no
 * reply and the device enters normal operation mode
 */
static int cc2_command_mode_finish(struct cc2_data *data)
{
        int ret;

        ret = i2c_smbus_write_word_data(data->client, CC2_START_NOM, 0);
        if (ret < 0)
                return ret;

        return 0;
}

static int cc2_write_reg(struct cc2_data *data, u8 reg, u16 val)
{
        unsigned long timeout;
        int ret;

        ret = cc2_command_mode_start(data);
        if (ret < 0)
                goto disable;

        cpu_to_be16s(&val);
        ret = i2c_smbus_write_word_data(data->client, reg, val);
        if (ret < 0)
                goto disable;

        if (data->irq_ready > 0) {
                timeout = msecs_to_jiffies(2 * CC2_RESP_EEPROM_W_MS);
                ret = wait_for_completion_timeout(&data->complete, timeout);
                if (!ret) {
                        ret = -ETIMEDOUT;
                        goto disable;
                }
        } else {
                msleep(CC2_RESP_EEPROM_W_MS);
        }

        ret = cc2_read_command_status(data->client);

disable:
        cc2_disable(data);

        return ret;
}

static int cc2_read_reg(struct cc2_data *data, u8 reg, u16 *val)
{
        u8 buf[CC2_EEPROM_DATA_LEN];
        unsigned long timeout;
        int ret;

        ret = cc2_command_mode_start(data);
        if (ret < 0)
                return ret;

        ret = i2c_smbus_write_word_data(data->client, reg, 0);
        if (ret < 0)
                return ret;

        if (data->irq_ready > 0) {
                timeout = usecs_to_jiffies(2 * CC2_RESP_EEPROM_R_US);
                ret = wait_for_completion_timeout(&data->complete, timeout);
                if (!ret)
                        return -ETIMEDOUT;

        } else {
                usleep_range(CC2_RESP_EEPROM_R_US, CC2_RESP_EEPROM_R_US + 10);
        }
        ret = i2c_master_recv(data->client, buf, CC2_EEPROM_DATA_LEN);
        if (ret != CC2_EEPROM_DATA_LEN)
                return ret < 0 ? ret : -EIO;

        *val = be16_to_cpup((__be16 *)&buf[1]);

        return cc2_read_command_status(data->client);
}

static int cc2_get_reg_val(struct cc2_data *data, u8 reg, long *val)
{
        u16 reg_val;
        int ret;

        ret = cc2_read_reg(data, reg, &reg_val);
        if (!ret)
                *val = cc2_rh_convert(reg_val);

        cc2_disable(data);

        return ret;
}

static int cc2_data_fetch(struct i2c_client *client,
                          enum hwmon_sensor_types type, long *val)
{
        u8 data[CC2_MEASUREMENT_DATA_LEN];
        u8 status;
        int ret;

        ret = i2c_master_recv(client, data, CC2_MEASUREMENT_DATA_LEN);
        if (ret != CC2_MEASUREMENT_DATA_LEN) {
                ret = ret < 0 ? ret : -EIO;
                return ret;
        }
        status = FIELD_GET(CC2_STATUS_FIELD, data[0]);
        if (status == CC2_STATUS_STALE_DATA)
                return -EBUSY;

        if (status != CC2_STATUS_VALID_DATA)
                return -EIO;

        switch (type) {
        case hwmon_humidity:
                *val = cc2_rh_convert(be16_to_cpup((__be16 *)&data[0]));
                break;
        case hwmon_temp:
                *val = cc2_temp_convert(be16_to_cpup((__be16 *)&data[2]));
                break;
        default:
                return -EINVAL;
        }

        return 0;
}

static int cc2_read_measurement(struct cc2_data *data,
                                enum hwmon_sensor_types type, long *val)
{
        unsigned long timeout;
        int ret;

        if (data->irq_ready > 0) {
                timeout = msecs_to_jiffies(CC2_STARTUP_TO_DATA_MS * 2);
                ret = wait_for_completion_timeout(&data->complete, timeout);
                if (!ret)
                        return -ETIMEDOUT;

        } else {
                msleep(CC2_STARTUP_TO_DATA_MS);
        }

        ret = cc2_data_fetch(data->client, type, val);

        return ret;
}

/*
 * A measurement requires enabling the device, waiting for the automatic
 * measurement to finish, reading the measurement data and disabling the device
 * again.
 */
static int cc2_measurement(struct cc2_data *data, enum hwmon_sensor_types type,
                           long *val)
{
        int ret;

        ret = cc2_enable(data);
        if (ret)
                return ret;

        ret = cc2_read_measurement(data, type, val);

        cc2_disable(data);

        return ret;
}

/*
 * In order to check alarm status, the corresponding ALARM_OFF (hysteresis)
 * register must be read and a new measurement must be carried out to trigger
 * the alarm signals. Given that the device carries out a measurement after
 * exiting the command mode, there is no need to force two power-up sequences.
 * Instead, a NOM command is sent and the device is disabled after the
 * measurement is read.
 */
static int cc2_read_hyst_and_measure(struct cc2_data *data, u8 reg,
                                     long *hyst, long *measurement)
{
        u16 reg_val;
        int ret;

        ret = cc2_read_reg(data, reg, &reg_val);
        if (ret)
                goto disable;

        *hyst = cc2_rh_convert(reg_val);

        ret = cc2_command_mode_finish(data);
        if (ret)
                goto disable;

        ret = cc2_read_measurement(data, hwmon_humidity, measurement);

disable:
        cc2_disable(data);

        return ret;
}

static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type,
                              u32 attr, int channel)
{
        const struct cc2_data *cc2 = data;

        switch (type) {
        case hwmon_humidity:
                switch (attr) {
                case hwmon_humidity_input:
                        return 0444;
                case hwmon_humidity_min_alarm:
                        return cc2->rh_alarm.low_alarm_visible ? 0444 : 0;
                case hwmon_humidity_max_alarm:
                        return cc2->rh_alarm.high_alarm_visible ? 0444 : 0;
                case hwmon_humidity_min:
                case hwmon_humidity_min_hyst:
                        return cc2->rh_alarm.low_alarm_visible ? 0644 : 0;
                case hwmon_humidity_max:
                case hwmon_humidity_max_hyst:
                        return cc2->rh_alarm.high_alarm_visible ? 0644 : 0;
                default:
                        return 0;
                }
        case hwmon_temp:
                switch (attr) {
                case hwmon_temp_input:
                        return 0444;
                default:
                        return 0;
                }
        default:
                break;
        }

        return 0;
}

static irqreturn_t cc2_ready_interrupt(int irq, void *data)
{
        struct cc2_data *cc2 = data;

        if (cc2->process_irqs)
                complete(&cc2->complete);

        return IRQ_HANDLED;
}

static irqreturn_t cc2_low_interrupt(int irq, void *data)
{
        struct cc2_data *cc2 = data;

        if (cc2->process_irqs) {
                hwmon_notify_event(cc2->hwmon, hwmon_humidity,
                                   hwmon_humidity_min_alarm, CC2_CHAN_HUMIDITY);
                cc2->rh_alarm.low_alarm = true;
        }

        return IRQ_HANDLED;
}

static irqreturn_t cc2_high_interrupt(int irq, void *data)
{
        struct cc2_data *cc2 = data;

        if (cc2->process_irqs) {
                hwmon_notify_event(cc2->hwmon, hwmon_humidity,
                                   hwmon_humidity_max_alarm, CC2_CHAN_HUMIDITY);
                cc2->rh_alarm.high_alarm = true;
        }

        return IRQ_HANDLED;
}

static int cc2_humidity_min_alarm_status(struct cc2_data *data, long *val)
{
        long measurement, min_hyst;
        int ret;

        ret = cc2_read_hyst_and_measure(data, CC2_R_ALARM_L_OFF, &min_hyst,
                                        &measurement);
        if (ret < 0)
                return ret;

        if (data->rh_alarm.low_alarm) {
                *val = (measurement < min_hyst) ? 1 : 0;
                data->rh_alarm.low_alarm = *val;
        } else {
                *val = 0;
        }

        return 0;
}

static int cc2_humidity_max_alarm_status(struct cc2_data *data, long *val)
{
        long measurement, max_hyst;
        int ret;

        ret = cc2_read_hyst_and_measure(data, CC2_R_ALARM_H_OFF, &max_hyst,
                                        &measurement);
        if (ret < 0)
                return ret;

        if (data->rh_alarm.high_alarm) {
                *val = (measurement > max_hyst) ? 1 : 0;
                data->rh_alarm.high_alarm = *val;
        } else {
                *val = 0;
        }

        return 0;
}

static int cc2_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
                    int channel, long *val)
{
        struct cc2_data *data = dev_get_drvdata(dev);

        switch (type) {
        case hwmon_temp:
                return cc2_measurement(data, type, val);
        case hwmon_humidity:
                switch (attr) {
                case hwmon_humidity_input:
                        return cc2_measurement(data, type, val);
                case hwmon_humidity_min:
                        return cc2_get_reg_val(data, CC2_R_ALARM_L_ON, val);
                case hwmon_humidity_min_hyst:
                        return cc2_get_reg_val(data, CC2_R_ALARM_L_OFF, val);
                case hwmon_humidity_max:
                        return cc2_get_reg_val(data, CC2_R_ALARM_H_ON, val);
                case hwmon_humidity_max_hyst:
                        return cc2_get_reg_val(data, CC2_R_ALARM_H_OFF, val);
                case hwmon_humidity_min_alarm:
                        return cc2_humidity_min_alarm_status(data, val);
                case hwmon_humidity_max_alarm:
                        return cc2_humidity_max_alarm_status(data, val);
                default:
                        return -EOPNOTSUPP;
                }
        default:
                return -EOPNOTSUPP;
        }
}

static int cc2_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
                     int channel, long val)
{
        struct cc2_data *data = dev_get_drvdata(dev);
        u16 arg;
        u8 cmd;

        if (type != hwmon_humidity)
                return -EOPNOTSUPP;

        if (val < 0 || val > CC2_RH_MAX)
                return -EINVAL;

        switch (attr) {
        case hwmon_humidity_min:
                cmd = CC2_W_ALARM_L_ON;
                arg = cc2_rh_to_reg(val);
                return cc2_write_reg(data, cmd, arg);
        case hwmon_humidity_min_hyst:
                cmd = CC2_W_ALARM_L_OFF;
                arg = cc2_rh_to_reg(val);
                return cc2_write_reg(data, cmd, arg);
        case hwmon_humidity_max:
                cmd = CC2_W_ALARM_H_ON;
                arg = cc2_rh_to_reg(val);
                return cc2_write_reg(data, cmd, arg);
        case hwmon_humidity_max_hyst:
                cmd = CC2_W_ALARM_H_OFF;
                arg = cc2_rh_to_reg(val);
                return cc2_write_reg(data, cmd, arg);
        default:
                return -EOPNOTSUPP;
        }
}

static int cc2_request_ready_irq(struct cc2_data *data, struct device *dev)
{
        int ret = 0;

        data->irq_ready = fwnode_irq_get_byname(dev_fwnode(dev), "ready");
        if (data->irq_ready > 0) {
                init_completion(&data->complete);
                ret = devm_request_threaded_irq(dev, data->irq_ready, NULL,
                                                cc2_ready_interrupt,
                                                IRQF_ONESHOT |
                                                IRQF_TRIGGER_RISING,
                                                dev_name(dev), data);
        }

        return ret;
}

static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev)
{
        int ret = 0;

        data->irq_low = fwnode_irq_get_byname(dev_fwnode(dev), "low");
        if (data->irq_low > 0) {
                ret = devm_request_threaded_irq(dev, data->irq_low, NULL,
                                                cc2_low_interrupt,
                                                IRQF_ONESHOT |
                                                IRQF_TRIGGER_RISING,
                                                dev_name(dev), data);
                if (ret)
                        return ret;

                data->rh_alarm.low_alarm_visible = true;
        }

        data->irq_high = fwnode_irq_get_byname(dev_fwnode(dev), "high");
        if (data->irq_high > 0) {
                ret = devm_request_threaded_irq(dev, data->irq_high, NULL,
                                                cc2_high_interrupt,
                                                IRQF_ONESHOT |
                                                IRQF_TRIGGER_RISING,
                                                dev_name(dev), data);
                if (ret)
                        return ret;

                data->rh_alarm.high_alarm_visible = true;
        }

        return ret;
}

static const struct hwmon_channel_info *cc2_info[] = {
        HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
        HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_MIN | HWMON_H_MAX |
                           HWMON_H_MIN_HYST | HWMON_H_MAX_HYST |
                           HWMON_H_MIN_ALARM | HWMON_H_MAX_ALARM),
        NULL
};

static const struct hwmon_ops cc2_hwmon_ops = {
        .is_visible = cc2_is_visible,
        .read = cc2_read,
        .write = cc2_write,
};

static const struct hwmon_chip_info cc2_chip_info = {
        .ops = &cc2_hwmon_ops,
        .info = cc2_info,
};

static int cc2_probe(struct i2c_client *client)
{
        struct cc2_data *data;
        struct device *dev = &client->dev;
        int ret;

        if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
                return -EOPNOTSUPP;

        data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
        if (!data)
                return -ENOMEM;

        i2c_set_clientdata(client, data);

        data->client = client;

        data->regulator = devm_regulator_get_exclusive(dev, "vdd");
        if (IS_ERR(data->regulator))
                return dev_err_probe(dev, PTR_ERR(data->regulator),
                                     "Failed to get regulator\n");

        ret = cc2_request_ready_irq(data, dev);
        if (ret)
                return dev_err_probe(dev, ret, "Failed to request ready irq\n");

        ret = cc2_request_alarm_irqs(data, dev);
        if (ret)
                return dev_err_probe(dev, ret, "Failed to request alarm irqs\n");

        data->hwmon = devm_hwmon_device_register_with_info(dev, client->name,
                                                           data, &cc2_chip_info,
                                                           NULL);
        if (IS_ERR(data->hwmon))
                return dev_err_probe(dev, PTR_ERR(data->hwmon),
                                     "Failed to register hwmon device\n");

        return 0;
}

static void cc2_remove(struct i2c_client *client)
{
        struct cc2_data *data = i2c_get_clientdata(client);

        cc2_disable(data);
}

static const struct i2c_device_id cc2_id[] = {
        { "cc2d23" },
        { "cc2d23s" },
        { "cc2d25" },
        { "cc2d25s" },
        { "cc2d33" },
        { "cc2d33s" },
        { "cc2d35" },
        { "cc2d35s" },
        { }
};
MODULE_DEVICE_TABLE(i2c, cc2_id);

static const struct of_device_id cc2_of_match[] = {
        { .compatible = "amphenol,cc2d23" },
        { .compatible = "amphenol,cc2d23s" },
        { .compatible = "amphenol,cc2d25" },
        { .compatible = "amphenol,cc2d25s" },
        { .compatible = "amphenol,cc2d33" },
        { .compatible = "amphenol,cc2d33s" },
        { .compatible = "amphenol,cc2d35" },
        { .compatible = "amphenol,cc2d35s" },
        { },
};
MODULE_DEVICE_TABLE(of, cc2_of_match);

static struct i2c_driver cc2_driver = {
        .driver = {
                .name   = "cc2d23",
                .of_match_table = cc2_of_match,
        },
        .probe          = cc2_probe,
        .remove         = cc2_remove,
        .id_table = cc2_id,
};
module_i2c_driver(cc2_driver);

MODULE_AUTHOR("Javier Carrasco <javier.carrasco.cruz@gamil.com>");
MODULE_DESCRIPTION("Amphenol ChipCap 2 humidity and temperature sensor driver");
MODULE_LICENSE("GPL");