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, ®, 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"); 166