1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Aosong AM2315 relative humidity and temperature |
4 | * |
5 | * Copyright (c) 2016, Intel Corporation. |
6 | * |
7 | * 7-bit I2C address: 0x5C. |
8 | */ |
9 | |
10 | #include <linux/delay.h> |
11 | #include <linux/i2c.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/module.h> |
14 | #include <linux/iio/buffer.h> |
15 | #include <linux/iio/iio.h> |
16 | #include <linux/iio/sysfs.h> |
17 | #include <linux/iio/trigger_consumer.h> |
18 | #include <linux/iio/triggered_buffer.h> |
19 | |
20 | #define AM2315_REG_HUM_MSB 0x00 |
21 | #define AM2315_REG_HUM_LSB 0x01 |
22 | #define AM2315_REG_TEMP_MSB 0x02 |
23 | #define AM2315_REG_TEMP_LSB 0x03 |
24 | |
25 | #define AM2315_FUNCTION_READ 0x03 |
26 | #define AM2315_HUM_OFFSET 2 |
27 | #define AM2315_TEMP_OFFSET 4 |
28 | #define AM2315_ALL_CHANNEL_MASK GENMASK(1, 0) |
29 | |
30 | #define AM2315_DRIVER_NAME "am2315" |
31 | |
32 | struct am2315_data { |
33 | struct i2c_client *client; |
34 | struct mutex lock; |
35 | /* Ensure timestamp is naturally aligned */ |
36 | struct { |
37 | s16 chans[2]; |
38 | s64 timestamp __aligned(8); |
39 | } scan; |
40 | }; |
41 | |
42 | struct am2315_sensor_data { |
43 | s16 hum_data; |
44 | s16 temp_data; |
45 | }; |
46 | |
47 | static const struct iio_chan_spec am2315_channels[] = { |
48 | { |
49 | .type = IIO_HUMIDITYRELATIVE, |
50 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
51 | BIT(IIO_CHAN_INFO_SCALE), |
52 | .scan_index = 0, |
53 | .scan_type = { |
54 | .sign = 's', |
55 | .realbits = 16, |
56 | .storagebits = 16, |
57 | .endianness = IIO_CPU, |
58 | }, |
59 | }, |
60 | { |
61 | .type = IIO_TEMP, |
62 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
63 | BIT(IIO_CHAN_INFO_SCALE), |
64 | .scan_index = 1, |
65 | .scan_type = { |
66 | .sign = 's', |
67 | .realbits = 16, |
68 | .storagebits = 16, |
69 | .endianness = IIO_CPU, |
70 | }, |
71 | }, |
72 | IIO_CHAN_SOFT_TIMESTAMP(2), |
73 | }; |
74 | |
75 | /* CRC calculation algorithm, as specified in the datasheet (page 13). */ |
76 | static u16 am2315_crc(u8 *data, u8 nr_bytes) |
77 | { |
78 | int i; |
79 | u16 crc = 0xffff; |
80 | |
81 | while (nr_bytes--) { |
82 | crc ^= *data++; |
83 | for (i = 0; i < 8; i++) { |
84 | if (crc & 0x01) { |
85 | crc >>= 1; |
86 | crc ^= 0xA001; |
87 | } else { |
88 | crc >>= 1; |
89 | } |
90 | } |
91 | } |
92 | |
93 | return crc; |
94 | } |
95 | |
96 | /* Simple function that sends a few bytes to the device to wake it up. */ |
97 | static void am2315_ping(struct i2c_client *client) |
98 | { |
99 | i2c_smbus_read_byte_data(client, AM2315_REG_HUM_MSB); |
100 | } |
101 | |
102 | static int am2315_read_data(struct am2315_data *data, |
103 | struct am2315_sensor_data *sensor_data) |
104 | { |
105 | int ret; |
106 | /* tx_buf format: <function code> <start addr> <nr of regs to read> */ |
107 | u8 tx_buf[3] = { AM2315_FUNCTION_READ, AM2315_REG_HUM_MSB, 4 }; |
108 | /* |
109 | * rx_buf format: |
110 | * <function code> <number of registers read> |
111 | * <humidity MSB> <humidity LSB> <temp MSB> <temp LSB> |
112 | * <CRC LSB> <CRC MSB> |
113 | */ |
114 | u8 rx_buf[8]; |
115 | u16 crc; |
116 | |
117 | /* First wake up the device. */ |
118 | am2315_ping(client: data->client); |
119 | |
120 | mutex_lock(&data->lock); |
121 | ret = i2c_master_send(client: data->client, buf: tx_buf, count: sizeof(tx_buf)); |
122 | if (ret < 0) { |
123 | dev_err(&data->client->dev, "failed to send read request\n" ); |
124 | goto exit_unlock; |
125 | } |
126 | /* Wait 2-3 ms, then read back the data sent by the device. */ |
127 | usleep_range(min: 2000, max: 3000); |
128 | /* Do a bulk data read, then pick out what we need. */ |
129 | ret = i2c_master_recv(client: data->client, buf: rx_buf, count: sizeof(rx_buf)); |
130 | if (ret < 0) { |
131 | dev_err(&data->client->dev, "failed to read sensor data\n" ); |
132 | goto exit_unlock; |
133 | } |
134 | mutex_unlock(lock: &data->lock); |
135 | /* |
136 | * Do a CRC check on the data and compare it to the value |
137 | * calculated by the device. |
138 | */ |
139 | crc = am2315_crc(data: rx_buf, nr_bytes: sizeof(rx_buf) - 2); |
140 | if ((crc & 0xff) != rx_buf[6] || (crc >> 8) != rx_buf[7]) { |
141 | dev_err(&data->client->dev, "failed to verify sensor data\n" ); |
142 | return -EIO; |
143 | } |
144 | |
145 | sensor_data->hum_data = (rx_buf[AM2315_HUM_OFFSET] << 8) | |
146 | rx_buf[AM2315_HUM_OFFSET + 1]; |
147 | sensor_data->temp_data = (rx_buf[AM2315_TEMP_OFFSET] << 8) | |
148 | rx_buf[AM2315_TEMP_OFFSET + 1]; |
149 | |
150 | return ret; |
151 | |
152 | exit_unlock: |
153 | mutex_unlock(lock: &data->lock); |
154 | return ret; |
155 | } |
156 | |
157 | static irqreturn_t am2315_trigger_handler(int irq, void *p) |
158 | { |
159 | int i; |
160 | int ret; |
161 | int bit; |
162 | struct iio_poll_func *pf = p; |
163 | struct iio_dev *indio_dev = pf->indio_dev; |
164 | struct am2315_data *data = iio_priv(indio_dev); |
165 | struct am2315_sensor_data sensor_data; |
166 | |
167 | ret = am2315_read_data(data, sensor_data: &sensor_data); |
168 | if (ret < 0) |
169 | goto err; |
170 | |
171 | mutex_lock(&data->lock); |
172 | if (*(indio_dev->active_scan_mask) == AM2315_ALL_CHANNEL_MASK) { |
173 | data->scan.chans[0] = sensor_data.hum_data; |
174 | data->scan.chans[1] = sensor_data.temp_data; |
175 | } else { |
176 | i = 0; |
177 | for_each_set_bit(bit, indio_dev->active_scan_mask, |
178 | indio_dev->masklength) { |
179 | data->scan.chans[i] = (bit ? sensor_data.temp_data : |
180 | sensor_data.hum_data); |
181 | i++; |
182 | } |
183 | } |
184 | mutex_unlock(lock: &data->lock); |
185 | |
186 | iio_push_to_buffers_with_timestamp(indio_dev, data: &data->scan, |
187 | timestamp: pf->timestamp); |
188 | err: |
189 | iio_trigger_notify_done(trig: indio_dev->trig); |
190 | return IRQ_HANDLED; |
191 | } |
192 | |
193 | static int am2315_read_raw(struct iio_dev *indio_dev, |
194 | struct iio_chan_spec const *chan, |
195 | int *val, int *val2, long mask) |
196 | { |
197 | int ret; |
198 | struct am2315_sensor_data sensor_data; |
199 | struct am2315_data *data = iio_priv(indio_dev); |
200 | |
201 | switch (mask) { |
202 | case IIO_CHAN_INFO_RAW: |
203 | ret = am2315_read_data(data, sensor_data: &sensor_data); |
204 | if (ret < 0) |
205 | return ret; |
206 | *val = (chan->type == IIO_HUMIDITYRELATIVE) ? |
207 | sensor_data.hum_data : sensor_data.temp_data; |
208 | return IIO_VAL_INT; |
209 | case IIO_CHAN_INFO_SCALE: |
210 | *val = 100; |
211 | return IIO_VAL_INT; |
212 | } |
213 | |
214 | return -EINVAL; |
215 | } |
216 | |
217 | static const struct iio_info am2315_info = { |
218 | .read_raw = am2315_read_raw, |
219 | }; |
220 | |
221 | static int am2315_probe(struct i2c_client *client) |
222 | { |
223 | int ret; |
224 | struct iio_dev *indio_dev; |
225 | struct am2315_data *data; |
226 | |
227 | indio_dev = devm_iio_device_alloc(parent: &client->dev, sizeof_priv: sizeof(*data)); |
228 | if (!indio_dev) { |
229 | dev_err(&client->dev, "iio allocation failed!\n" ); |
230 | return -ENOMEM; |
231 | } |
232 | |
233 | data = iio_priv(indio_dev); |
234 | data->client = client; |
235 | i2c_set_clientdata(client, data: indio_dev); |
236 | mutex_init(&data->lock); |
237 | |
238 | indio_dev->info = &am2315_info; |
239 | indio_dev->name = AM2315_DRIVER_NAME; |
240 | indio_dev->modes = INDIO_DIRECT_MODE; |
241 | indio_dev->channels = am2315_channels; |
242 | indio_dev->num_channels = ARRAY_SIZE(am2315_channels); |
243 | |
244 | ret = devm_iio_triggered_buffer_setup(&client->dev, |
245 | indio_dev, iio_pollfunc_store_time, |
246 | am2315_trigger_handler, NULL); |
247 | if (ret < 0) { |
248 | dev_err(&client->dev, "iio triggered buffer setup failed\n" ); |
249 | return ret; |
250 | } |
251 | |
252 | return devm_iio_device_register(&client->dev, indio_dev); |
253 | } |
254 | |
255 | static const struct i2c_device_id am2315_i2c_id[] = { |
256 | {"am2315" , 0}, |
257 | {} |
258 | }; |
259 | MODULE_DEVICE_TABLE(i2c, am2315_i2c_id); |
260 | |
261 | static struct i2c_driver am2315_driver = { |
262 | .driver = { |
263 | .name = "am2315" , |
264 | }, |
265 | .probe = am2315_probe, |
266 | .id_table = am2315_i2c_id, |
267 | }; |
268 | |
269 | module_i2c_driver(am2315_driver); |
270 | |
271 | MODULE_AUTHOR("Tiberiu Breana <tiberiu.a.breana@intel.com>" ); |
272 | MODULE_DESCRIPTION("Aosong AM2315 relative humidity and temperature" ); |
273 | MODULE_LICENSE("GPL v2" ); |
274 | |