[PATCH v2 3/5] iio: chemical: sgp40: Implement get_serial_number-command
From: Jaakko Koivisto
Date: Mon Nov 02 2026 - 04:19:42 EST
Add functionality for retrieving SGP40 sensor serial number
and present it to the userspace using serialnumber ABI.
Signed-off-by: Jaakko Koivisto <jmatko@xxxxxx>
---
Currently only the bno055 driver uses the serialnumber ABI, and presents
the SN in hex. Documentation gives hex output as example format, not hard
requirement, but should we use the same format here anyway?
drivers/iio/chemical/sgp40.c | 76 ++++++++++++++++++++++++++++++++++++
1 file changed, 76 insertions(+)
diff --git a/drivers/iio/chemical/sgp40.c b/drivers/iio/chemical/sgp40.c
index 080d51f9f53c..e0a300720fce 100644
--- a/drivers/iio/chemical/sgp40.c
+++ b/drivers/iio/chemical/sgp40.c
@@ -35,6 +35,7 @@
#include <linux/mutex.h>
#include <linux/i2c.h>
#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
/*
* floating point calculation of voc is done as integer
@@ -70,6 +71,18 @@ struct sgp40_tg_result {
u8 res_crc;
} __packed;
+/*
+ * Datasheet table 16. Serial number is given as 48-bit value 0xAAAABBBBCCCC.
+ */
+struct sgp40_serial_number_result {
+ __be16 A;
+ u8 A_crc;
+ __be16 B;
+ u8 B_crc;
+ __be16 C;
+ u8 C_crc;
+} __packed;
+
static const struct iio_chan_spec sgp40_channels[] = {
{
.type = IIO_CONCENTRATION,
@@ -161,6 +174,41 @@ static int sgp40_calc_voc(struct sgp40_data *data, u16 resistance_raw, int *voc)
return 0;
}
+static int sgp40_get_serial_number(struct sgp40_data *data, u64 *serial_number)
+{
+ int ret;
+ struct i2c_client *client = data->client;
+ struct sgp40_tg_measure get_sn = { .command = {0x36, 0x82} };
+ struct sgp40_serial_number_result res;
+
+ guard(mutex)(&data->lock);
+
+ ret = i2c_master_send(client, (char *)&get_sn, sizeof(get_sn.command));
+ if (ret < 0)
+ return ret;
+ if (ret != sizeof(get_sn.command))
+ return dev_err_probe(data->dev, -EIO,
+ "i2c_master_send ret: %d, expected %zu\n", ret, sizeof(get_sn.command));
+
+ fsleep(1000); /* Max duration for get_serial_number command. Datasheet table 8. */
+
+ ret = i2c_master_recv(client, (char *)&res, sizeof(res));
+ if (ret < 0)
+ return ret;
+ if (ret != sizeof(res))
+ return dev_err_probe(data->dev, -EIO,
+ "i2c_master_recv ret: %d, expected: %zu\n", ret, sizeof(res));
+
+ if (crc8(sgp40_crc8_table, (u8 *)&res.A, 2, SGP40_CRC8_INIT) != res.A_crc ||
+ crc8(sgp40_crc8_table, (u8 *)&res.B, 2, SGP40_CRC8_INIT) != res.B_crc ||
+ crc8(sgp40_crc8_table, (u8 *)&res.C, 2, SGP40_CRC8_INIT) != res.C_crc)
+ return dev_err_probe(data->dev, -EIO, "CRC error in get_serial_number\n");
+
+ *serial_number = ((u64)be16_to_cpu(res.A) << 32) | ((u64)be16_to_cpu(res.B) << 16) | (u64)be16_to_cpu(res.C);
+
+ return 0;
+}
+
static int sgp40_measure_resistance_raw(struct sgp40_data *data, u16 *resistance_raw)
{
int ret;
@@ -306,9 +354,37 @@ static int sgp40_write_raw(struct iio_dev *indio_dev,
return -EINVAL;
}
+static ssize_t serialnumber_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u64 serial_number;
+ struct sgp40_data *data = iio_priv(dev_to_iio_dev(dev));
+
+ ret = sgp40_get_serial_number(data, &serial_number);
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit_at(buf, 0, "%llu\n", serial_number);
+}
+
+static IIO_DEVICE_ATTR_RO(serialnumber, 0);
+
+static struct attribute *sgp40_attributes[] = {
+ &iio_dev_attr_serialnumber.dev_attr.attr,
+ NULL
+};
+
+static struct attribute_group sgp40_attribute_group = {
+ .attrs = sgp40_attributes,
+};
+
+
static const struct iio_info sgp40_info = {
.read_raw = sgp40_read_raw,
.write_raw = sgp40_write_raw,
+ .attrs = &sgp40_attribute_group,
};
static int sgp40_probe(struct i2c_client *client)
--
2.56.0