// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2025 Akhilesh Patil * * Driver for adp810 pressure and temperature sensor * Datasheet: * https://aosong.com/userfiles/files/media/Datasheet%20ADP810-Digital.pdf */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * 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 "); MODULE_DESCRIPTION("Driver for Aosong ADP810 sensor"); MODULE_LICENSE("GPL");