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
29DECLARE_CRC8_TABLE(ags02ma_crc8_table);
30
31struct ags02ma_data {
32 struct i2c_client *client;
33};
34
35struct ags02ma_reading {
36 __be32 data;
37 u8 crc;
38} __packed;
39
40static 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, buf: &reg, count: 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(msecs: delay);
56
57 ret = i2c_master_recv(client, buf: (u8 *)&read_buffer, count: 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(table: ags02ma_crc8_table, pdata: (u8 *)&read_buffer.data,
65 nbytes: 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
75static 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(indio_dev: iio_device);
81
82 switch (mask) {
83 case IIO_CHAN_INFO_RAW:
84 ret = ags02ma_register_read(client: 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
100static const struct iio_info ags02ma_info = {
101 .read_raw = ags02ma_read_raw,
102};
103
104static 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
111static 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(parent: &client->dev, sizeof_priv: sizeof(*data));
119 if (!indio_dev)
120 return -ENOMEM;
121
122 crc8_populate_msb(table: ags02ma_crc8_table, AGS02MA_CRC8_POLYNOMIAL);
123
124 ret = ags02ma_register_read(client, AGS02MA_VERSION_REG,
125 AGS02MA_VERSION_PROCESSING_DELAY, val: &version);
126 if (ret < 0)
127 return dev_err_probe(dev: &client->dev, err: ret,
128 fmt: "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
141static const struct i2c_device_id ags02ma_id_table[] = {
142 { "ags02ma" },
143 { /* Sentinel */ }
144};
145MODULE_DEVICE_TABLE(i2c, ags02ma_id_table);
146
147static const struct of_device_id ags02ma_of_table[] = {
148 { .compatible = "aosong,ags02ma" },
149 { /* Sentinel */ }
150};
151MODULE_DEVICE_TABLE(of, ags02ma_of_table);
152
153static 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};
161module_i2c_driver(ags02ma_driver);
162
163MODULE_AUTHOR("Anshul Dalal <anshulusr@gmail.com>");
164MODULE_DESCRIPTION("Aosong AGS02MA TVOC Driver");
165MODULE_LICENSE("GPL");
166

source code of linux/drivers/iio/chemical/ags02ma.c