root/drivers/iio/chemical/scd4x.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Sensirion SCD4X carbon dioxide sensor i2c driver
 *
 * Copyright (C) 2021 Protonic Holland
 * Author: Roan van Dijk <roan@protonic.nl>
 *
 * I2C slave address: 0x62
 *
 * Datasheets:
 * https://www.sensirion.com/file/datasheet_scd4x
 */

#include <linux/unaligned.h>
#include <linux/crc8.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/trigger.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/types.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/types.h>

#define SCD4X_CRC8_POLYNOMIAL 0x31
#define SCD4X_TIMEOUT_ERR 1000
#define SCD4X_READ_BUF_SIZE 9
#define SCD4X_COMMAND_BUF_SIZE 2
#define SCD4X_WRITE_BUF_SIZE 5
#define SCD4X_FRC_MIN_PPM 0
#define SCD4X_FRC_MAX_PPM 2000
#define SCD4X_PRESSURE_COMP_MIN_MBAR 700
#define SCD4X_PRESSURE_COMP_MAX_MBAR 1200
#define SCD4X_READY_MASK 0x01

/*Commands SCD4X*/
enum scd4x_cmd {
        CMD_START_MEAS          = 0x21b1,
        CMD_READ_MEAS           = 0xec05,
        CMD_STOP_MEAS           = 0x3f86,
        CMD_SET_TEMP_OFFSET     = 0x241d,
        CMD_GET_TEMP_OFFSET     = 0x2318,
        CMD_SET_AMB_PRESSURE    = 0xe000,
        CMD_GET_AMB_PRESSURE    = 0xe000,
        CMD_FRC                 = 0x362f,
        CMD_SET_ASC             = 0x2416,
        CMD_GET_ASC             = 0x2313,
        CMD_GET_DATA_READY      = 0xe4b8,
};

enum scd4x_channel_idx {
        SCD4X_CO2,
        SCD4X_TEMP,
        SCD4X_HR,
        /* kernel timestamp, at the end of buffer */
        SCD4X_TS,
};

struct scd4x_state {
        struct i2c_client *client;
        /* maintain access to device, to prevent concurrent reads/writes */
        struct mutex lock;
        struct regulator *vdd;
};

DECLARE_CRC8_TABLE(scd4x_crc8_table);

static int scd4x_i2c_xfer(struct scd4x_state *state, char *txbuf, int txsize,
                                char *rxbuf, int rxsize)
{
        struct i2c_client *client = state->client;
        int ret;

        ret = i2c_master_send(client, txbuf, txsize);

        if (ret < 0)
                return ret;
        if (ret != txsize)
                return -EIO;

        if (rxsize == 0)
                return 0;

        ret = i2c_master_recv(client, rxbuf, rxsize);
        if (ret < 0)
                return ret;
        if (ret != rxsize)
                return -EIO;

        return 0;
}

static int scd4x_send_command(struct scd4x_state *state, enum scd4x_cmd cmd)
{
        char buf[SCD4X_COMMAND_BUF_SIZE];
        int ret;

        /*
         * Measurement needs to be stopped before sending commands.
         * Except stop and start command.
         */
        if ((cmd != CMD_STOP_MEAS) && (cmd != CMD_START_MEAS)) {

                ret = scd4x_send_command(state, CMD_STOP_MEAS);
                if (ret)
                        return ret;

                /* execution time for stopping measurement */
                msleep_interruptible(500);
        }

        put_unaligned_be16(cmd, buf);
        ret = scd4x_i2c_xfer(state, buf, 2, buf, 0);
        if (ret)
                return ret;

        if ((cmd != CMD_STOP_MEAS) && (cmd != CMD_START_MEAS)) {
                ret = scd4x_send_command(state, CMD_START_MEAS);
                if (ret)
                        return ret;
        }

        return 0;
}

static int scd4x_read(struct scd4x_state *state, enum scd4x_cmd cmd,
                        void *response, int response_sz)
{
        struct i2c_client *client = state->client;
        char buf[SCD4X_READ_BUF_SIZE];
        char *rsp = response;
        int i, ret;
        char crc;

        /*
         * Measurement needs to be stopped before sending commands.
         * Except for reading measurement and data ready command.
         */
        if ((cmd != CMD_GET_DATA_READY) && (cmd != CMD_READ_MEAS) &&
            (cmd != CMD_GET_AMB_PRESSURE)) {
                ret = scd4x_send_command(state, CMD_STOP_MEAS);
                if (ret)
                        return ret;

                /* execution time for stopping measurement */
                msleep_interruptible(500);
        }

        /* CRC byte for every 2 bytes of data */
        response_sz += response_sz / 2;

        put_unaligned_be16(cmd, buf);
        ret = scd4x_i2c_xfer(state, buf, 2, buf, response_sz);
        if (ret)
                return ret;

        for (i = 0; i < response_sz; i += 3) {
                crc = crc8(scd4x_crc8_table, buf + i, 2, CRC8_INIT_VALUE);
                if (crc != buf[i + 2]) {
                        dev_err(&client->dev, "CRC error\n");
                        return -EIO;
                }

                *rsp++ = buf[i];
                *rsp++ = buf[i + 1];
        }

        /* start measurement */
        if ((cmd != CMD_GET_DATA_READY) && (cmd != CMD_READ_MEAS) &&
            (cmd != CMD_GET_AMB_PRESSURE)) {
                ret = scd4x_send_command(state, CMD_START_MEAS);
                if (ret)
                        return ret;
        }

        return 0;
}

static int scd4x_write(struct scd4x_state *state, enum scd4x_cmd cmd, uint16_t arg)
{
        char buf[SCD4X_WRITE_BUF_SIZE];
        int ret;
        char crc;

        put_unaligned_be16(cmd, buf);
        put_unaligned_be16(arg, buf + 2);

        crc = crc8(scd4x_crc8_table, buf + 2, 2, CRC8_INIT_VALUE);
        buf[4] = crc;

        /* measurement needs to be stopped before sending commands */
        if (cmd != CMD_SET_AMB_PRESSURE) {
                ret = scd4x_send_command(state, CMD_STOP_MEAS);
                if (ret)
                        return ret;
        }

        /* execution time */
        msleep_interruptible(500);

        ret = scd4x_i2c_xfer(state, buf, SCD4X_WRITE_BUF_SIZE, buf, 0);
        if (ret)
                return ret;

        /* start measurement, except for forced calibration command */
        if ((cmd != CMD_FRC) && (cmd != CMD_SET_AMB_PRESSURE)) {
                ret = scd4x_send_command(state, CMD_START_MEAS);
                if (ret)
                        return ret;
        }

        return 0;
}

static int scd4x_write_and_fetch(struct scd4x_state *state, enum scd4x_cmd cmd,
                                uint16_t arg, void *response, int response_sz)
{
        struct i2c_client *client = state->client;
        char buf[SCD4X_READ_BUF_SIZE];
        char *rsp = response;
        int i, ret;
        char crc;

        ret = scd4x_write(state, CMD_FRC, arg);
        if (ret)
                goto err;

        /* execution time */
        msleep_interruptible(400);

        /* CRC byte for every 2 bytes of data */
        response_sz += response_sz / 2;

        ret = i2c_master_recv(client, buf, response_sz);
        if (ret < 0)
                goto err;
        if (ret != response_sz) {
                ret = -EIO;
                goto err;
        }

        for (i = 0; i < response_sz; i += 3) {
                crc = crc8(scd4x_crc8_table, buf + i, 2, CRC8_INIT_VALUE);
                if (crc != buf[i + 2]) {
                        dev_err(&client->dev, "CRC error\n");
                        ret = -EIO;
                        goto err;
                }

                *rsp++ = buf[i];
                *rsp++ = buf[i + 1];
        }

        return scd4x_send_command(state, CMD_START_MEAS);

err:
        /*
         * on error try to start the measurement,
         * puts sensor back into continuous measurement
         */
        scd4x_send_command(state, CMD_START_MEAS);

        return ret;
}

static int scd4x_read_meas(struct scd4x_state *state, uint16_t *meas)
{
        int i, ret;
        __be16 buf[3];

        ret = scd4x_read(state, CMD_READ_MEAS, buf, sizeof(buf));
        if (ret)
                return ret;

        for (i = 0; i < ARRAY_SIZE(buf); i++)
                meas[i] = be16_to_cpu(buf[i]);

        return 0;
}

static int scd4x_wait_meas_poll(struct scd4x_state *state)
{
        struct i2c_client *client = state->client;
        int tries = 6;
        int ret;

        do {
                __be16 bval;
                uint16_t val;

                ret = scd4x_read(state, CMD_GET_DATA_READY, &bval, sizeof(bval));
                if (ret)
                        return -EIO;
                val = be16_to_cpu(bval);

                /* new measurement available */
                if (val & 0x7FF)
                        return 0;

                msleep_interruptible(1000);
        } while (--tries);

        /* try to start sensor on timeout */
        ret = scd4x_send_command(state, CMD_START_MEAS);
        if (ret)
                dev_err(&client->dev, "failed to start measurement: %d\n", ret);

        return -ETIMEDOUT;
}

static int scd4x_read_poll(struct scd4x_state *state, uint16_t *buf)
{
        int ret;

        ret = scd4x_wait_meas_poll(state);
        if (ret)
                return ret;

        return scd4x_read_meas(state, buf);
}

static int scd4x_read_channel(struct scd4x_state *state, int chan)
{
        int ret;
        uint16_t buf[3];

        ret = scd4x_read_poll(state, buf);
        if (ret)
                return ret;

        return buf[chan];
}

static int scd4x_read_raw(struct iio_dev *indio_dev,
                        struct iio_chan_spec const *chan, int *val,
                        int *val2, long mask)
{
        struct scd4x_state *state = iio_priv(indio_dev);
        int ret;
        __be16 tmp;

        switch (mask) {
        case IIO_CHAN_INFO_RAW:
                if (chan->output) {
                        mutex_lock(&state->lock);
                        ret = scd4x_read(state, CMD_GET_AMB_PRESSURE, &tmp, sizeof(tmp));
                        mutex_unlock(&state->lock);

                        if (ret)
                                return ret;

                        *val = be16_to_cpu(tmp);
                        return IIO_VAL_INT;
                }

                if (!iio_device_claim_direct(indio_dev))
                        return -EBUSY;

                mutex_lock(&state->lock);
                ret = scd4x_read_channel(state, chan->address);
                mutex_unlock(&state->lock);

                iio_device_release_direct(indio_dev);
                if (ret < 0)
                        return ret;

                *val = ret;
                return IIO_VAL_INT;
        case IIO_CHAN_INFO_SCALE:
                if (chan->type == IIO_CONCENTRATION) {
                        *val = 0;
                        *val2 = 100;
                        return IIO_VAL_INT_PLUS_MICRO;
                } else if (chan->type == IIO_TEMP) {
                        *val = 175000;
                        *val2 = 65536;
                        return IIO_VAL_FRACTIONAL;
                } else if (chan->type == IIO_HUMIDITYRELATIVE) {
                        *val = 100000;
                        *val2 = 65536;
                        return IIO_VAL_FRACTIONAL;
                }
                return -EINVAL;
        case IIO_CHAN_INFO_OFFSET:
                *val = -16852;
                *val2 = 114286;
                return IIO_VAL_INT_PLUS_MICRO;
        case IIO_CHAN_INFO_CALIBBIAS:
                mutex_lock(&state->lock);
                ret = scd4x_read(state, CMD_GET_TEMP_OFFSET, &tmp, sizeof(tmp));
                mutex_unlock(&state->lock);
                if (ret)
                        return ret;

                *val = be16_to_cpu(tmp);

                return IIO_VAL_INT;
        default:
                return -EINVAL;
        }
}

static const int scd4x_pressure_calibbias_available[] = {
        SCD4X_PRESSURE_COMP_MIN_MBAR, 1, SCD4X_PRESSURE_COMP_MAX_MBAR,
};

static int scd4x_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
                            const int **vals, int *type, int *length, long mask)
{
        switch (mask) {
        case IIO_CHAN_INFO_RAW:
                *vals = scd4x_pressure_calibbias_available;
                *type = IIO_VAL_INT;

                return IIO_AVAIL_RANGE;
        }

        return -EINVAL;
}


static int scd4x_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
                                int val, int val2, long mask)
{
        struct scd4x_state *state = iio_priv(indio_dev);
        int ret = 0;

        switch (mask) {
        case IIO_CHAN_INFO_CALIBBIAS:
                mutex_lock(&state->lock);
                ret = scd4x_write(state, CMD_SET_TEMP_OFFSET, val);
                mutex_unlock(&state->lock);

                return ret;
        case IIO_CHAN_INFO_RAW:
                switch (chan->type) {
                case IIO_PRESSURE:
                        if (val < SCD4X_PRESSURE_COMP_MIN_MBAR ||
                            val > SCD4X_PRESSURE_COMP_MAX_MBAR)
                                return -EINVAL;

                        mutex_lock(&state->lock);
                        ret = scd4x_write(state, CMD_SET_AMB_PRESSURE, val);
                        mutex_unlock(&state->lock);

                        return ret;
                default:
                        return -EINVAL;
                }
        default:
                return -EINVAL;
        }
}

static ssize_t calibration_auto_enable_show(struct device *dev,
                        struct device_attribute *attr, char *buf)
{
        struct iio_dev *indio_dev = dev_to_iio_dev(dev);
        struct scd4x_state *state = iio_priv(indio_dev);
        int ret;
        __be16 bval;
        u16 val;

        mutex_lock(&state->lock);
        ret = scd4x_read(state, CMD_GET_ASC, &bval, sizeof(bval));
        mutex_unlock(&state->lock);
        if (ret) {
                dev_err(dev, "failed to read automatic calibration");
                return ret;
        }

        val = (be16_to_cpu(bval) & SCD4X_READY_MASK) ? 1 : 0;

        return sysfs_emit(buf, "%d\n", val);
}

static ssize_t calibration_auto_enable_store(struct device *dev,
                                        struct device_attribute *attr,
                                        const char *buf, size_t len)
{
        struct iio_dev *indio_dev = dev_to_iio_dev(dev);
        struct scd4x_state *state = iio_priv(indio_dev);
        bool val;
        int ret;
        uint16_t value;

        ret = kstrtobool(buf, &val);
        if (ret)
                return ret;

        value = val;

        mutex_lock(&state->lock);
        ret = scd4x_write(state, CMD_SET_ASC, value);
        mutex_unlock(&state->lock);
        if (ret)
                dev_err(dev, "failed to set automatic calibration");

        return ret ?: len;
}

static ssize_t calibration_forced_value_store(struct device *dev,
                                        struct device_attribute *attr,
                                        const char *buf, size_t len)
{
        struct iio_dev *indio_dev = dev_to_iio_dev(dev);
        struct scd4x_state *state = iio_priv(indio_dev);
        uint16_t val, arg;
        int ret;

        ret = kstrtou16(buf, 0, &arg);
        if (ret)
                return ret;

        if (arg < SCD4X_FRC_MIN_PPM || arg > SCD4X_FRC_MAX_PPM)
                return -EINVAL;

        mutex_lock(&state->lock);
        ret = scd4x_write_and_fetch(state, CMD_FRC, arg, &val, sizeof(val));
        mutex_unlock(&state->lock);

        if (ret)
                return ret;

        if (val == 0xff) {
                dev_err(dev, "forced calibration has failed");
                return -EINVAL;
        }

        return len;
}

static IIO_DEVICE_ATTR_RW(calibration_auto_enable, 0);
static IIO_DEVICE_ATTR_WO(calibration_forced_value, 0);

static IIO_CONST_ATTR(calibration_forced_value_available,
               __stringify([SCD4X_FRC_MIN_PPM 1 SCD4X_FRC_MAX_PPM]));

static struct attribute *scd4x_attrs[] = {
        &iio_dev_attr_calibration_auto_enable.dev_attr.attr,
        &iio_dev_attr_calibration_forced_value.dev_attr.attr,
        &iio_const_attr_calibration_forced_value_available.dev_attr.attr,
        NULL
};

static const struct attribute_group scd4x_attr_group = {
        .attrs = scd4x_attrs,
};

static const struct iio_info scd4x_info = {
        .attrs = &scd4x_attr_group,
        .read_raw = scd4x_read_raw,
        .write_raw = scd4x_write_raw,
        .read_avail = scd4x_read_avail,
};

static const struct iio_chan_spec scd4x_channels[] = {
        {
                /*
                 * this channel is special in a sense we are pretending that
                 * sensor is able to change measurement chamber pressure but in
                 * fact we're just setting pressure compensation value
                 */
                .type = IIO_PRESSURE,
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
                .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW),
                .output = 1,
                .scan_index = -1,
        },
        {
                .type = IIO_CONCENTRATION,
                .channel2 = IIO_MOD_CO2,
                .modified = 1,
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
                                        BIT(IIO_CHAN_INFO_SCALE),
                .address = SCD4X_CO2,
                .scan_index = SCD4X_CO2,
                .scan_type = {
                        .sign = 'u',
                        .realbits = 16,
                        .storagebits = 16,
                        .endianness = IIO_CPU,
                },
        },
        {
                .type = IIO_TEMP,
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
                                        BIT(IIO_CHAN_INFO_SCALE) |
                                        BIT(IIO_CHAN_INFO_OFFSET) |
                                        BIT(IIO_CHAN_INFO_CALIBBIAS),
                .address = SCD4X_TEMP,
                .scan_index = SCD4X_TEMP,
                .scan_type = {
                        .sign = 'u',
                        .realbits = 16,
                        .storagebits = 16,
                        .endianness = IIO_CPU,
                },
        },
        {
                .type = IIO_HUMIDITYRELATIVE,
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
                                        BIT(IIO_CHAN_INFO_SCALE),
                .address = SCD4X_HR,
                .scan_index = SCD4X_HR,
                .scan_type = {
                        .sign = 'u',
                        .realbits = 16,
                        .storagebits = 16,
                        .endianness = IIO_CPU,
                },
        },
        IIO_CHAN_SOFT_TIMESTAMP(SCD4X_TS),
};

static int scd4x_suspend(struct device *dev)
{
        struct iio_dev *indio_dev = dev_get_drvdata(dev);
        struct scd4x_state *state  = iio_priv(indio_dev);
        int ret;

        ret = scd4x_send_command(state, CMD_STOP_MEAS);
        if (ret)
                return ret;

        return regulator_disable(state->vdd);
}

static int scd4x_resume(struct device *dev)
{
        struct iio_dev *indio_dev = dev_get_drvdata(dev);
        struct scd4x_state *state = iio_priv(indio_dev);
        int ret;

        ret = regulator_enable(state->vdd);
        if (ret)
                return ret;

        return scd4x_send_command(state, CMD_START_MEAS);
}

static DEFINE_SIMPLE_DEV_PM_OPS(scd4x_pm_ops, scd4x_suspend, scd4x_resume);

static void scd4x_stop_meas(void *state)
{
        scd4x_send_command(state, CMD_STOP_MEAS);
}

static void scd4x_disable_regulator(void *data)
{
        struct scd4x_state *state = data;

        regulator_disable(state->vdd);
}

static irqreturn_t scd4x_trigger_handler(int irq, void *p)
{
        struct iio_poll_func *pf = p;
        struct iio_dev *indio_dev = pf->indio_dev;
        struct scd4x_state *state = iio_priv(indio_dev);
        struct {
                uint16_t data[3];
                aligned_s64 ts;
        } scan = { };
        int ret;

        mutex_lock(&state->lock);
        ret = scd4x_read_poll(state, scan.data);
        mutex_unlock(&state->lock);
        if (ret)
                goto out;

        iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
                                    iio_get_time_ns(indio_dev));
out:
        iio_trigger_notify_done(indio_dev->trig);
        return IRQ_HANDLED;
}

static int scd4x_probe(struct i2c_client *client)
{
        static const unsigned long scd4x_scan_masks[] = { 0x07, 0x00 };
        struct device *dev = &client->dev;
        struct iio_dev *indio_dev;
        struct scd4x_state *state;
        int ret;

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

        state = iio_priv(indio_dev);
        mutex_init(&state->lock);
        state->client = client;
        crc8_populate_msb(scd4x_crc8_table, SCD4X_CRC8_POLYNOMIAL);

        indio_dev->info = &scd4x_info;
        indio_dev->name = client->name;
        indio_dev->channels = scd4x_channels;
        indio_dev->num_channels = ARRAY_SIZE(scd4x_channels);
        indio_dev->modes = INDIO_DIRECT_MODE;
        indio_dev->available_scan_masks = scd4x_scan_masks;

        state->vdd = devm_regulator_get(dev, "vdd");
        if (IS_ERR(state->vdd))
                return dev_err_probe(dev, PTR_ERR(state->vdd), "failed to get regulator\n");

        ret = regulator_enable(state->vdd);
        if (ret)
                return ret;

        ret = devm_add_action_or_reset(dev, scd4x_disable_regulator, state);
        if (ret)
                return ret;

        ret = scd4x_send_command(state, CMD_STOP_MEAS);
        if (ret) {
                dev_err(dev, "failed to stop measurement: %d\n", ret);
                return ret;
        }

        /* execution time */
        msleep_interruptible(500);

        ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, scd4x_trigger_handler, NULL);
        if (ret)
                return ret;

        ret = scd4x_send_command(state, CMD_START_MEAS);
        if (ret) {
                dev_err(dev, "failed to start measurement: %d\n", ret);
                return ret;
        }

        ret = devm_add_action_or_reset(dev, scd4x_stop_meas, state);
        if (ret)
                return ret;

        return devm_iio_device_register(dev, indio_dev);
}

static const struct of_device_id scd4x_dt_ids[] = {
        { .compatible = "sensirion,scd40" },
        { .compatible = "sensirion,scd41" },
        { }
};
MODULE_DEVICE_TABLE(of, scd4x_dt_ids);

static struct i2c_driver scd4x_i2c_driver = {
        .driver = {
                .name = KBUILD_MODNAME,
                .of_match_table = scd4x_dt_ids,
                .pm = pm_sleep_ptr(&scd4x_pm_ops),
        },
        .probe = scd4x_probe,
};
module_i2c_driver(scd4x_i2c_driver);

MODULE_AUTHOR("Roan van Dijk <roan@protonic.nl>");
MODULE_DESCRIPTION("Sensirion SCD4X carbon dioxide sensor core driver");
MODULE_LICENSE("GPL v2");