Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

iio: chemical: add support for Aosong AGS02MA

A simple driver for the TVOC (Total Volatile Organic Compounds)
sensor from Aosong: AGS02MA

Steps in reading the VOC sensor value over i2c:
1. Read 5 bytes from the register `AGS02MA_TVOC_READ_REG` [0x00]
2. The first 4 bytes are taken as the big endian sensor data with final
byte being the CRC
3. The CRC is verified and the value is returned over an
`IIO_CHAN_INFO_RAW` channel as percents

Tested on Raspberry Pi Zero 2W

Datasheet: https://asairsensors.com/wp-content/uploads/2021/09/AGS02MA.pdf
Signed-off-by: Anshul Dalal <anshulusr@gmail.com>
Link: https://lore.kernel.org/r/20231215162312.143568-3-anshulusr@gmail.com
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>

authored by

Anshul Dalal and committed by
Jonathan Cameron
d58013f3 c9c6f564

+184
+7
MAINTAINERS
··· 3071 3071 W: http://www.akm.com/ 3072 3072 F: drivers/iio/magnetometer/ak8974.c 3073 3073 3074 + AOSONG AGS02MA TVOC SENSOR DRIVER 3075 + M: Anshul Dalal <anshulusr@gmail.com> 3076 + L: linux-iio@vger.kernel.org 3077 + S: Maintained 3078 + F: Documentation/devicetree/bindings/iio/chemical/aosong,ags02ma.yaml 3079 + F: drivers/iio/chemical/ags02ma.c 3080 + 3074 3081 ASC7621 HARDWARE MONITOR DRIVER 3075 3082 M: George Joseph <george.joseph@fairview5.com> 3076 3083 L: linux-hwmon@vger.kernel.org
+11
drivers/iio/chemical/Kconfig
··· 5 5 6 6 menu "Chemical Sensors" 7 7 8 + config AOSONG_AGS02MA 9 + tristate "Aosong AGS02MA TVOC sensor driver" 10 + depends on I2C 11 + select CRC8 12 + help 13 + Say Y here to build support for Aosong AGS02MA TVOC (Total Volatile 14 + Organic Compounds) sensor. 15 + 16 + To compile this driver as module, choose M here: the module will be 17 + called ags02ma. 18 + 8 19 config ATLAS_PH_SENSOR 9 20 tristate "Atlas Scientific OEM SM sensors" 10 21 depends on I2C
+1
drivers/iio/chemical/Makefile
··· 4 4 # 5 5 6 6 # When adding new entries keep the list in alphabetical order 7 + obj-$(CONFIG_AOSONG_AGS02MA) += ags02ma.o 7 8 obj-$(CONFIG_ATLAS_PH_SENSOR) += atlas-sensor.o 8 9 obj-$(CONFIG_ATLAS_EZO_SENSOR) += atlas-ezo-sensor.o 9 10 obj-$(CONFIG_BME680) += bme680_core.o
+165
drivers/iio/chemical/ags02ma.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + /* 3 + * Copyright (C) 2023 Anshul Dalal <anshulusr@gmail.com> 4 + * 5 + * Driver for Aosong AGS02MA 6 + * 7 + * Datasheet: 8 + * https://asairsensors.com/wp-content/uploads/2021/09/AGS02MA.pdf 9 + * Product Page: 10 + * http://www.aosong.com/m/en/products-33.html 11 + */ 12 + 13 + #include <linux/crc8.h> 14 + #include <linux/delay.h> 15 + #include <linux/i2c.h> 16 + #include <linux/module.h> 17 + 18 + #include <linux/iio/iio.h> 19 + 20 + #define AGS02MA_TVOC_READ_REG 0x00 21 + #define AGS02MA_VERSION_REG 0x11 22 + 23 + #define AGS02MA_VERSION_PROCESSING_DELAY 30 24 + #define AGS02MA_TVOC_READ_PROCESSING_DELAY 1500 25 + 26 + #define AGS02MA_CRC8_INIT 0xff 27 + #define AGS02MA_CRC8_POLYNOMIAL 0x31 28 + 29 + DECLARE_CRC8_TABLE(ags02ma_crc8_table); 30 + 31 + struct ags02ma_data { 32 + struct i2c_client *client; 33 + }; 34 + 35 + struct ags02ma_reading { 36 + __be32 data; 37 + u8 crc; 38 + } __packed; 39 + 40 + static int ags02ma_register_read(struct i2c_client *client, u8 reg, u16 delay, 41 + u32 *val) 42 + { 43 + int ret; 44 + u8 crc; 45 + struct ags02ma_reading read_buffer; 46 + 47 + ret = i2c_master_send(client, &reg, sizeof(reg)); 48 + if (ret < 0) { 49 + dev_err(&client->dev, 50 + "Failed to send data to register 0x%x: %d", reg, ret); 51 + return ret; 52 + } 53 + 54 + /* Processing Delay, Check Table 7.7 in the datasheet */ 55 + msleep_interruptible(delay); 56 + 57 + ret = i2c_master_recv(client, (u8 *)&read_buffer, sizeof(read_buffer)); 58 + if (ret < 0) { 59 + dev_err(&client->dev, 60 + "Failed to receive from register 0x%x: %d", reg, ret); 61 + return ret; 62 + } 63 + 64 + crc = crc8(ags02ma_crc8_table, (u8 *)&read_buffer.data, 65 + sizeof(read_buffer.data), AGS02MA_CRC8_INIT); 66 + if (crc != read_buffer.crc) { 67 + dev_err(&client->dev, "CRC error\n"); 68 + return -EIO; 69 + } 70 + 71 + *val = be32_to_cpu(read_buffer.data); 72 + return 0; 73 + } 74 + 75 + static int ags02ma_read_raw(struct iio_dev *iio_device, 76 + struct iio_chan_spec const *chan, int *val, 77 + int *val2, long mask) 78 + { 79 + int ret; 80 + struct ags02ma_data *data = iio_priv(iio_device); 81 + 82 + switch (mask) { 83 + case IIO_CHAN_INFO_RAW: 84 + ret = ags02ma_register_read(data->client, AGS02MA_TVOC_READ_REG, 85 + AGS02MA_TVOC_READ_PROCESSING_DELAY, 86 + val); 87 + if (ret < 0) 88 + return ret; 89 + return IIO_VAL_INT; 90 + case IIO_CHAN_INFO_SCALE: 91 + /* The sensor reads data as ppb */ 92 + *val = 0; 93 + *val2 = 100; 94 + return IIO_VAL_INT_PLUS_NANO; 95 + default: 96 + return -EINVAL; 97 + } 98 + } 99 + 100 + static const struct iio_info ags02ma_info = { 101 + .read_raw = ags02ma_read_raw, 102 + }; 103 + 104 + static const struct iio_chan_spec ags02ma_channel = { 105 + .type = IIO_CONCENTRATION, 106 + .channel2 = IIO_MOD_VOC, 107 + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | 108 + BIT(IIO_CHAN_INFO_SCALE), 109 + }; 110 + 111 + static int ags02ma_probe(struct i2c_client *client) 112 + { 113 + int ret; 114 + struct ags02ma_data *data; 115 + struct iio_dev *indio_dev; 116 + u32 version; 117 + 118 + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); 119 + if (!indio_dev) 120 + return -ENOMEM; 121 + 122 + crc8_populate_msb(ags02ma_crc8_table, AGS02MA_CRC8_POLYNOMIAL); 123 + 124 + ret = ags02ma_register_read(client, AGS02MA_VERSION_REG, 125 + AGS02MA_VERSION_PROCESSING_DELAY, &version); 126 + if (ret < 0) 127 + return dev_err_probe(&client->dev, ret, 128 + "Failed to read device version\n"); 129 + dev_dbg(&client->dev, "Aosong AGS02MA, Version: 0x%x", version); 130 + 131 + data = iio_priv(indio_dev); 132 + data->client = client; 133 + indio_dev->info = &ags02ma_info; 134 + indio_dev->channels = &ags02ma_channel; 135 + indio_dev->num_channels = 1; 136 + indio_dev->name = "ags02ma"; 137 + 138 + return devm_iio_device_register(&client->dev, indio_dev); 139 + } 140 + 141 + static const struct i2c_device_id ags02ma_id_table[] = { 142 + { "ags02ma" }, 143 + { /* Sentinel */ } 144 + }; 145 + MODULE_DEVICE_TABLE(i2c, ags02ma_id_table); 146 + 147 + static const struct of_device_id ags02ma_of_table[] = { 148 + { .compatible = "aosong,ags02ma" }, 149 + { /* Sentinel */ } 150 + }; 151 + MODULE_DEVICE_TABLE(of, ags02ma_of_table); 152 + 153 + static struct i2c_driver ags02ma_driver = { 154 + .driver = { 155 + .name = "ags02ma", 156 + .of_match_table = ags02ma_of_table, 157 + }, 158 + .id_table = ags02ma_id_table, 159 + .probe = ags02ma_probe, 160 + }; 161 + module_i2c_driver(ags02ma_driver); 162 + 163 + MODULE_AUTHOR("Anshul Dalal <anshulusr@gmail.com>"); 164 + MODULE_DESCRIPTION("Aosong AGS02MA TVOC Driver"); 165 + MODULE_LICENSE("GPL");