diff options
-rw-r--r-- | drivers/iio/humidity/Kconfig | 10 | ||||
-rw-r--r-- | drivers/iio/humidity/Makefile | 1 | ||||
-rw-r--r-- | drivers/iio/humidity/am2315.c | 228 |
3 files changed, 239 insertions, 0 deletions
diff --git a/drivers/iio/humidity/Kconfig b/drivers/iio/humidity/Kconfig index 866dda133336..738a86d9e4a9 100644 --- a/drivers/iio/humidity/Kconfig +++ b/drivers/iio/humidity/Kconfig | |||
@@ -3,6 +3,16 @@ | |||
3 | # | 3 | # |
4 | menu "Humidity sensors" | 4 | menu "Humidity sensors" |
5 | 5 | ||
6 | config AM2315 | ||
7 | tristate "Aosong AM2315 relative humidity and temperature sensor" | ||
8 | depends on I2C | ||
9 | help | ||
10 | If you say yes here you get support for the Aosong AM2315 | ||
11 | relative humidity and ambient temperature sensor. | ||
12 | |||
13 | This driver can also be built as a module. If so, the module will | ||
14 | be called am2315. | ||
15 | |||
6 | config DHT11 | 16 | config DHT11 |
7 | tristate "DHT11 (and compatible sensors) driver" | 17 | tristate "DHT11 (and compatible sensors) driver" |
8 | depends on GPIOLIB || COMPILE_TEST | 18 | depends on GPIOLIB || COMPILE_TEST |
diff --git a/drivers/iio/humidity/Makefile b/drivers/iio/humidity/Makefile index c9f089a9a6b8..4a73442fcd9c 100644 --- a/drivers/iio/humidity/Makefile +++ b/drivers/iio/humidity/Makefile | |||
@@ -2,6 +2,7 @@ | |||
2 | # Makefile for IIO humidity sensor drivers | 2 | # Makefile for IIO humidity sensor drivers |
3 | # | 3 | # |
4 | 4 | ||
5 | obj-$(CONFIG_AM2315) += am2315.o | ||
5 | obj-$(CONFIG_DHT11) += dht11.o | 6 | obj-$(CONFIG_DHT11) += dht11.o |
6 | obj-$(CONFIG_HDC100X) += hdc100x.o | 7 | obj-$(CONFIG_HDC100X) += hdc100x.o |
7 | obj-$(CONFIG_HTU21) += htu21.o | 8 | obj-$(CONFIG_HTU21) += htu21.o |
diff --git a/drivers/iio/humidity/am2315.c b/drivers/iio/humidity/am2315.c new file mode 100644 index 000000000000..d392fc8a9d16 --- /dev/null +++ b/drivers/iio/humidity/am2315.c | |||
@@ -0,0 +1,228 @@ | |||
1 | /** | ||
2 | * Aosong AM2315 relative humidity and temperature | ||
3 | * | ||
4 | * Copyright (c) 2016, Intel Corporation. | ||
5 | * | ||
6 | * This file is subject to the terms and conditions of version 2 of | ||
7 | * the GNU General Public License. See the file COPYING in the main | ||
8 | * directory of this archive for more details. | ||
9 | * | ||
10 | * 7-bit I2C address: 0x5C. | ||
11 | */ | ||
12 | |||
13 | #include <linux/acpi.h> | ||
14 | #include <linux/delay.h> | ||
15 | #include <linux/i2c.h> | ||
16 | #include <linux/kernel.h> | ||
17 | #include <linux/module.h> | ||
18 | #include <linux/iio/iio.h> | ||
19 | #include <linux/iio/sysfs.h> | ||
20 | |||
21 | #define AM2315_REG_HUM_MSB 0x00 | ||
22 | #define AM2315_REG_HUM_LSB 0x01 | ||
23 | #define AM2315_REG_TEMP_MSB 0x02 | ||
24 | #define AM2315_REG_TEMP_LSB 0x03 | ||
25 | |||
26 | #define AM2315_FUNCTION_READ 0x03 | ||
27 | #define AM2315_HUM_OFFSET 2 | ||
28 | #define AM2315_TEMP_OFFSET 4 | ||
29 | |||
30 | #define AM2315_DRIVER_NAME "am2315" | ||
31 | |||
32 | struct am2315_data { | ||
33 | struct i2c_client *client; | ||
34 | struct mutex lock; | ||
35 | }; | ||
36 | |||
37 | struct am2315_sensor_data { | ||
38 | s16 hum_data; | ||
39 | s16 temp_data; | ||
40 | }; | ||
41 | |||
42 | static const struct iio_chan_spec am2315_channels[] = { | ||
43 | { | ||
44 | .type = IIO_HUMIDITYRELATIVE, | ||
45 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | | ||
46 | BIT(IIO_CHAN_INFO_SCALE) | ||
47 | }, | ||
48 | { | ||
49 | .type = IIO_TEMP, | ||
50 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | | ||
51 | BIT(IIO_CHAN_INFO_SCALE) | ||
52 | }, | ||
53 | }; | ||
54 | |||
55 | /* CRC calculation algorithm, as specified in the datasheet (page 13). */ | ||
56 | static u16 am2315_crc(u8 *data, u8 nr_bytes) | ||
57 | { | ||
58 | int i; | ||
59 | u16 crc = 0xffff; | ||
60 | |||
61 | while (nr_bytes--) { | ||
62 | crc ^= *data++; | ||
63 | for (i = 0; i < 8; i++) { | ||
64 | if (crc & 0x01) { | ||
65 | crc >>= 1; | ||
66 | crc ^= 0xA001; | ||
67 | } else { | ||
68 | crc >>= 1; | ||
69 | } | ||
70 | } | ||
71 | } | ||
72 | |||
73 | return crc; | ||
74 | } | ||
75 | |||
76 | /* Simple function that sends a few bytes to the device to wake it up. */ | ||
77 | static void am2315_ping(struct i2c_client *client) | ||
78 | { | ||
79 | i2c_smbus_read_byte_data(client, AM2315_REG_HUM_MSB); | ||
80 | } | ||
81 | |||
82 | static int am2315_read_data(struct am2315_data *data, | ||
83 | struct am2315_sensor_data *sensor_data) | ||
84 | { | ||
85 | int ret; | ||
86 | /* tx_buf format: <function code> <start addr> <nr of regs to read> */ | ||
87 | u8 tx_buf[3] = { AM2315_FUNCTION_READ, AM2315_REG_HUM_MSB, 4 }; | ||
88 | /* | ||
89 | * rx_buf format: | ||
90 | * <function code> <number of registers read> | ||
91 | * <humidity MSB> <humidity LSB> <temp MSB> <temp LSB> | ||
92 | * <CRC LSB> <CRC MSB> | ||
93 | */ | ||
94 | u8 rx_buf[8]; | ||
95 | u16 crc; | ||
96 | |||
97 | /* First wake up the device. */ | ||
98 | am2315_ping(data->client); | ||
99 | |||
100 | mutex_lock(&data->lock); | ||
101 | ret = i2c_master_send(data->client, tx_buf, sizeof(tx_buf)); | ||
102 | if (ret < 0) { | ||
103 | dev_err(&data->client->dev, "failed to send read request\n"); | ||
104 | goto exit_unlock; | ||
105 | } | ||
106 | /* Wait 2-3 ms, then read back the data sent by the device. */ | ||
107 | usleep_range(2000, 3000); | ||
108 | /* Do a bulk data read, then pick out what we need. */ | ||
109 | ret = i2c_master_recv(data->client, rx_buf, sizeof(rx_buf)); | ||
110 | if (ret < 0) { | ||
111 | dev_err(&data->client->dev, "failed to read sensor data\n"); | ||
112 | goto exit_unlock; | ||
113 | } | ||
114 | mutex_unlock(&data->lock); | ||
115 | /* | ||
116 | * Do a CRC check on the data and compare it to the value | ||
117 | * calculated by the device. | ||
118 | */ | ||
119 | crc = am2315_crc(rx_buf, sizeof(rx_buf) - 2); | ||
120 | if ((crc & 0xff) != rx_buf[6] || (crc >> 8) != rx_buf[7]) { | ||
121 | dev_err(&data->client->dev, "failed to verify sensor data\n"); | ||
122 | return -EIO; | ||
123 | } | ||
124 | |||
125 | sensor_data->hum_data = (rx_buf[AM2315_HUM_OFFSET] << 8) | | ||
126 | rx_buf[AM2315_HUM_OFFSET + 1]; | ||
127 | sensor_data->temp_data = (rx_buf[AM2315_TEMP_OFFSET] << 8) | | ||
128 | rx_buf[AM2315_TEMP_OFFSET + 1]; | ||
129 | |||
130 | return ret; | ||
131 | |||
132 | exit_unlock: | ||
133 | mutex_unlock(&data->lock); | ||
134 | return ret; | ||
135 | } | ||
136 | |||
137 | static int am2315_read_raw(struct iio_dev *indio_dev, | ||
138 | struct iio_chan_spec const *chan, | ||
139 | int *val, int *val2, long mask) | ||
140 | { | ||
141 | int ret; | ||
142 | struct am2315_sensor_data sensor_data; | ||
143 | struct am2315_data *data = iio_priv(indio_dev); | ||
144 | |||
145 | switch (mask) { | ||
146 | case IIO_CHAN_INFO_RAW: | ||
147 | ret = am2315_read_data(data, &sensor_data); | ||
148 | if (ret < 0) | ||
149 | return ret; | ||
150 | *val = (chan->type == IIO_HUMIDITYRELATIVE) ? | ||
151 | sensor_data.hum_data : sensor_data.temp_data; | ||
152 | return IIO_VAL_INT; | ||
153 | case IIO_CHAN_INFO_SCALE: | ||
154 | *val = 100; | ||
155 | return IIO_VAL_INT; | ||
156 | } | ||
157 | |||
158 | return -EINVAL; | ||
159 | } | ||
160 | |||
161 | static const struct iio_info am2315_info = { | ||
162 | .driver_module = THIS_MODULE, | ||
163 | .read_raw = am2315_read_raw, | ||
164 | }; | ||
165 | |||
166 | static int am2315_probe(struct i2c_client *client, | ||
167 | const struct i2c_device_id *id) | ||
168 | { | ||
169 | struct iio_dev *indio_dev; | ||
170 | struct am2315_data *data; | ||
171 | |||
172 | indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); | ||
173 | if (!indio_dev) { | ||
174 | dev_err(&client->dev, "iio allocation failed!\n"); | ||
175 | return -ENOMEM; | ||
176 | } | ||
177 | |||
178 | data = iio_priv(indio_dev); | ||
179 | data->client = client; | ||
180 | i2c_set_clientdata(client, indio_dev); | ||
181 | mutex_init(&data->lock); | ||
182 | |||
183 | indio_dev->dev.parent = &client->dev; | ||
184 | indio_dev->info = &am2315_info; | ||
185 | indio_dev->name = AM2315_DRIVER_NAME; | ||
186 | indio_dev->modes = INDIO_DIRECT_MODE; | ||
187 | indio_dev->channels = am2315_channels; | ||
188 | indio_dev->num_channels = ARRAY_SIZE(am2315_channels); | ||
189 | |||
190 | return iio_device_register(indio_dev); | ||
191 | } | ||
192 | |||
193 | static int am2315_remove(struct i2c_client *client) | ||
194 | { | ||
195 | struct iio_dev *indio_dev = i2c_get_clientdata(client); | ||
196 | |||
197 | iio_device_unregister(indio_dev); | ||
198 | |||
199 | return 0; | ||
200 | } | ||
201 | |||
202 | static const struct i2c_device_id am2315_i2c_id[] = { | ||
203 | {"am2315", 0}, | ||
204 | {} | ||
205 | }; | ||
206 | |||
207 | static const struct acpi_device_id am2315_acpi_id[] = { | ||
208 | {"AOS2315", 0}, | ||
209 | {} | ||
210 | }; | ||
211 | |||
212 | MODULE_DEVICE_TABLE(acpi, am2315_acpi_id); | ||
213 | |||
214 | static struct i2c_driver am2315_driver = { | ||
215 | .driver = { | ||
216 | .name = "am2315", | ||
217 | .acpi_match_table = ACPI_PTR(am2315_acpi_id), | ||
218 | }, | ||
219 | .probe = am2315_probe, | ||
220 | .remove = am2315_remove, | ||
221 | .id_table = am2315_i2c_id, | ||
222 | }; | ||
223 | |||
224 | module_i2c_driver(am2315_driver); | ||
225 | |||
226 | MODULE_AUTHOR("Tiberiu Breana <tiberiu.a.breana@intel.com>"); | ||
227 | MODULE_DESCRIPTION("Aosong AM2315 relative humidity and temperature"); | ||
228 | MODULE_LICENSE("GPL v2"); | ||