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, buf: ®, 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 | |
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(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 | |
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(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 | |
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 | |