root/drivers/iio/pressure/adp810.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2025 Akhilesh Patil <akhilesh@ee.iitb.ac.in>
 *
 * Driver for adp810 pressure and temperature sensor
 * Datasheet:
 *   https://aosong.com/userfiles/files/media/Datasheet%20ADP810-Digital.pdf
 */

#include <linux/array_size.h>
#include <linux/cleanup.h>
#include <linux/crc8.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/errno.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/types.h>
#include <linux/unaligned.h>

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

/*
 * Refer section 5.4 checksum calculation from datasheet.
 * This sensor uses CRC polynomial x^8 + x^5 + x^4 + 1 (0x31)
 */
#define ADP810_CRC8_POLYNOMIAL          0x31

DECLARE_CRC8_TABLE(crc_table);

/*
 * Buffer declaration which holds 9 bytes of measurement data read
 * from the sensor. Use __packed to avoid any paddings, as data sent
 * from the sensor is strictly contiguous 9 bytes.
 */
struct adp810_read_buf {
        __be16 dp;
        u8 dp_crc;
        __be16 tmp;
        u8 tmp_crc;
        __be16 sf;
        u8 sf_crc;
} __packed;

struct adp810_data {
        struct i2c_client *client;
        /* Use lock to synchronize access to device during read sequence */
        struct mutex lock;
};

static int adp810_measure(struct adp810_data *data, struct adp810_read_buf *buf)
{
        struct i2c_client *client = data->client;
        struct device *dev = &client->dev;
        int ret;
        u8 trig_cmd[2] = {0x37, 0x2d};

        /* Send trigger command to the sensor for measurement */
        ret = i2c_master_send(client, trig_cmd, sizeof(trig_cmd));
        if (ret < 0) {
                dev_err(dev, "Error sending trigger command\n");
                return ret;
        }
        if (ret != sizeof(trig_cmd))
                return -EIO;

        /*
         * Wait for the sensor to acquire data. As per datasheet section 5.3.1,
         * at least 10ms delay before reading from the sensor is recommended.
         * Here, we wait for 20ms to have some safe margin on the top
         * of recommendation and to compensate for any possible variations.
         */
        msleep(20);

        /* Read sensor values */
        ret = i2c_master_recv(client, (char *)buf, sizeof(*buf));
        if (ret < 0) {
                dev_err(dev, "Error reading from sensor\n");
                return ret;
        }
        if (ret != sizeof(*buf))
                return -EIO;

        /* CRC checks */
        crc8_populate_msb(crc_table, ADP810_CRC8_POLYNOMIAL);
        if (buf->dp_crc != crc8(crc_table, (u8 *)&buf->dp, 0x2, CRC8_INIT_VALUE)) {
                dev_err(dev, "CRC error for pressure\n");
                return -EIO;
        }

        if (buf->tmp_crc != crc8(crc_table, (u8 *)&buf->tmp, 0x2, CRC8_INIT_VALUE)) {
                dev_err(dev, "CRC error for temperature\n");
                return -EIO;
        }

        if (buf->sf_crc != crc8(crc_table, (u8 *)&buf->sf, 0x2, CRC8_INIT_VALUE)) {
                dev_err(dev, "CRC error for scale\n");
                return -EIO;
        }

        return 0;
}

static int adp810_read_raw(struct iio_dev *indio_dev,
                           struct iio_chan_spec const *chan,
                           int *val, int *val2, long mask)
{
        struct adp810_data *data = iio_priv(indio_dev);
        struct device *dev = &data->client->dev;
        struct adp810_read_buf buf = { };
        int ret;

        scoped_guard(mutex, &data->lock) {
                ret = adp810_measure(data, &buf);
                if (ret) {
                        dev_err(dev, "Failed to read from device\n");
                        return ret;
                }
        }

        switch (mask) {
        case IIO_CHAN_INFO_RAW:
                switch (chan->type) {
                case IIO_PRESSURE:
                        *val = get_unaligned_be16(&buf.dp);
                        return IIO_VAL_INT;
                case IIO_TEMP:
                        *val = get_unaligned_be16(&buf.tmp);
                        return IIO_VAL_INT;
                default:
                        return -EINVAL;
                }
        case IIO_CHAN_INFO_SCALE:
                switch (chan->type) {
                case IIO_PRESSURE:
                        *val = get_unaligned_be16(&buf.sf);
                        return IIO_VAL_INT;
                case IIO_TEMP:
                        *val = 200;
                        return IIO_VAL_INT;
                default:
                        return -EINVAL;
                }
        default:
                return -EINVAL;
        }
}

static const struct iio_info adp810_info = {
        .read_raw = adp810_read_raw,
};

static const struct iio_chan_spec adp810_channels[] = {
        {
                .type = IIO_PRESSURE,
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
                .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
        },
        {
                .type = IIO_TEMP,
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
                .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
        },
};

static int adp810_probe(struct i2c_client *client)
{
        struct device *dev = &client->dev;
        struct iio_dev *indio_dev;
        struct adp810_data *data;
        int ret;

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

        data = iio_priv(indio_dev);
        data->client = client;

        ret = devm_mutex_init(dev, &data->lock);
        if (ret)
                return ret;

        indio_dev->name = "adp810";
        indio_dev->channels = adp810_channels;
        indio_dev->num_channels = ARRAY_SIZE(adp810_channels);
        indio_dev->info = &adp810_info;
        indio_dev->modes = INDIO_DIRECT_MODE;

        ret = devm_iio_device_register(dev, indio_dev);
        if (ret)
                return dev_err_probe(dev, ret, "Failed to register IIO device\n");

        return 0;
}

static const struct i2c_device_id adp810_id_table[] = {
        { .name = "adp810" },
        { }
};
MODULE_DEVICE_TABLE(i2c, adp810_id_table);

static const struct of_device_id adp810_of_table[] = {
        { .compatible = "aosong,adp810" },
        { }
};
MODULE_DEVICE_TABLE(of, adp810_of_table);

static struct i2c_driver adp810_driver = {
        .driver = {
                .name = "adp810",
                .of_match_table = adp810_of_table,
        },
        .probe  = adp810_probe,
        .id_table = adp810_id_table,
};
module_i2c_driver(adp810_driver);

MODULE_AUTHOR("Akhilesh Patil <akhilesh@ee.iitb.ac.in>");
MODULE_DESCRIPTION("Driver for Aosong ADP810 sensor");
MODULE_LICENSE("GPL");