diff options
Diffstat (limited to 'drivers/iio/pressure/adp810.c')
| -rw-r--r-- | drivers/iio/pressure/adp810.c | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/drivers/iio/pressure/adp810.c b/drivers/iio/pressure/adp810.c new file mode 100644 index 000000000000..5282612d1309 --- /dev/null +++ b/drivers/iio/pressure/adp810.c @@ -0,0 +1,225 @@ +// 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/mod_devicetable.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[] = { + { "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"); |
