Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

iio: humidity: Add TI HDC20x0 support

Add driver support for HDC2010/2080 series devices and sysfs
documentation for their heater element.

HDC2010 is an integrated high-accuracy humidity and temperature sensor
with very low power consumption. The device includes a resistive heating
element. The temperature range is -40C to 125C with 0.2C
accuracy. Humidity measurement is 0 to 100% with 2% RH accuracy.

Signed-off-by: Eugene Zaikonnikov <ez@norphonic.com>
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>

authored by

Eugene Zaikonnikov and committed by
Jonathan Cameron
0115a63c fe184be8

+373
+9
Documentation/ABI/testing/sysfs-bus-iio-humidity-hdc2010
··· 1 + What: /sys/bus/iio/devices/iio:deviceX/out_current_heater_raw 2 + What: /sys/bus/iio/devices/iio:deviceX/out_current_heater_raw_available 3 + KernelVersion: 5.3.8 4 + Contact: linux-iio@vger.kernel.org 5 + Description: 6 + Controls the heater device within the humidity sensor to get 7 + rid of excess condensation. 8 + 9 + Valid control values are 0 = OFF, and 1 = ON.
+10
drivers/iio/humidity/Kconfig
··· 38 38 To compile this driver as a module, choose M here: the module 39 39 will be called hdc100x. 40 40 41 + config HDC2010 42 + tristate "TI HDC2010 relative humidity and temperature sensor" 43 + depends on I2C 44 + help 45 + Say yes here to build support for the Texas Instruments 46 + HDC2010 and HDC2080 relative humidity and temperature sensors. 47 + 48 + To compile this driver as a module, choose M here: the module 49 + will be called hdc2010. 50 + 41 51 config HID_SENSOR_HUMIDITY 42 52 tristate "HID Environmental humidity sensor" 43 53 depends on HID_SENSOR_HUB
+1
drivers/iio/humidity/Makefile
··· 6 6 obj-$(CONFIG_AM2315) += am2315.o 7 7 obj-$(CONFIG_DHT11) += dht11.o 8 8 obj-$(CONFIG_HDC100X) += hdc100x.o 9 + obj-$(CONFIG_HDC2010) += hdc2010.o 9 10 obj-$(CONFIG_HID_SENSOR_HUMIDITY) += hid-sensor-humidity.o 10 11 11 12 hts221-y := hts221_core.o \
+353
drivers/iio/humidity/hdc2010.c
··· 1 + // SPDX-License-Identifier: GPL-2.0+ 2 + /* 3 + * hdc2010.c - Support for the TI HDC2010 and HDC2080 4 + * temperature + relative humidity sensors 5 + * 6 + * Copyright (C) 2020 Norphonic AS 7 + * Author: Eugene Zaikonnikov <ez@norphonic.com> 8 + * 9 + * Datasheet: https://www.ti.com/product/HDC2010/datasheet 10 + * Datasheet: https://www.ti.com/product/HDC2080/datasheet 11 + */ 12 + 13 + #include <linux/module.h> 14 + #include <linux/init.h> 15 + #include <linux/i2c.h> 16 + #include <linux/bitops.h> 17 + 18 + #include <linux/iio/iio.h> 19 + #include <linux/iio/sysfs.h> 20 + 21 + #define HDC2010_REG_TEMP_LOW 0x00 22 + #define HDC2010_REG_TEMP_HIGH 0x01 23 + #define HDC2010_REG_HUMIDITY_LOW 0x02 24 + #define HDC2010_REG_HUMIDITY_HIGH 0x03 25 + #define HDC2010_REG_INTERRUPT_DRDY 0x04 26 + #define HDC2010_REG_TEMP_MAX 0x05 27 + #define HDC2010_REG_HUMIDITY_MAX 0x06 28 + #define HDC2010_REG_INTERRUPT_EN 0x07 29 + #define HDC2010_REG_TEMP_OFFSET_ADJ 0x08 30 + #define HDC2010_REG_HUMIDITY_OFFSET_ADJ 0x09 31 + #define HDC2010_REG_TEMP_THR_L 0x0a 32 + #define HDC2010_REG_TEMP_THR_H 0x0b 33 + #define HDC2010_REG_RH_THR_L 0x0c 34 + #define HDC2010_REG_RH_THR_H 0x0d 35 + #define HDC2010_REG_RESET_DRDY_INT_CONF 0x0e 36 + #define HDC2010_REG_MEASUREMENT_CONF 0x0f 37 + 38 + #define HDC2010_MEAS_CONF GENMASK(2, 1) 39 + #define HDC2010_MEAS_TRIG BIT(0) 40 + #define HDC2010_HEATER_EN BIT(3) 41 + #define HDC2010_AMM GENMASK(6, 4) 42 + 43 + struct hdc2010_data { 44 + struct i2c_client *client; 45 + struct mutex lock; 46 + u8 measurement_config; 47 + u8 interrupt_config; 48 + u8 drdy_config; 49 + }; 50 + 51 + enum hdc2010_addr_groups { 52 + HDC2010_GROUP_TEMP = 0, 53 + HDC2010_GROUP_HUMIDITY, 54 + }; 55 + 56 + struct hdc2010_reg_record { 57 + unsigned long primary; 58 + unsigned long peak; 59 + }; 60 + 61 + static const struct hdc2010_reg_record hdc2010_reg_translation[] = { 62 + [HDC2010_GROUP_TEMP] = { 63 + .primary = HDC2010_REG_TEMP_LOW, 64 + .peak = HDC2010_REG_TEMP_MAX, 65 + }, 66 + [HDC2010_GROUP_HUMIDITY] = { 67 + .primary = HDC2010_REG_HUMIDITY_LOW, 68 + .peak = HDC2010_REG_HUMIDITY_MAX, 69 + }, 70 + }; 71 + 72 + static IIO_CONST_ATTR(out_current_heater_raw_available, "0 1"); 73 + 74 + static struct attribute *hdc2010_attributes[] = { 75 + &iio_const_attr_out_current_heater_raw_available.dev_attr.attr, 76 + NULL 77 + }; 78 + 79 + static const struct attribute_group hdc2010_attribute_group = { 80 + .attrs = hdc2010_attributes, 81 + }; 82 + 83 + static const struct iio_chan_spec hdc2010_channels[] = { 84 + { 85 + .type = IIO_TEMP, 86 + .address = HDC2010_GROUP_TEMP, 87 + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | 88 + BIT(IIO_CHAN_INFO_PEAK) | 89 + BIT(IIO_CHAN_INFO_OFFSET) | 90 + BIT(IIO_CHAN_INFO_SCALE), 91 + }, 92 + { 93 + .type = IIO_HUMIDITYRELATIVE, 94 + .address = HDC2010_GROUP_HUMIDITY, 95 + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | 96 + BIT(IIO_CHAN_INFO_PEAK) | 97 + BIT(IIO_CHAN_INFO_SCALE), 98 + }, 99 + { 100 + .type = IIO_CURRENT, 101 + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 102 + .extend_name = "heater", 103 + .output = 1, 104 + }, 105 + }; 106 + 107 + static int hdc2010_update_drdy_config(struct hdc2010_data *data, 108 + char mask, char val) 109 + { 110 + u8 tmp = (~mask & data->drdy_config) | val; 111 + int ret; 112 + 113 + ret = i2c_smbus_write_byte_data(data->client, 114 + HDC2010_REG_RESET_DRDY_INT_CONF, tmp); 115 + if (ret) 116 + return ret; 117 + 118 + data->drdy_config = tmp; 119 + 120 + return 0; 121 + } 122 + 123 + static int hdc2010_get_prim_measurement_word(struct hdc2010_data *data, 124 + struct iio_chan_spec const *chan) 125 + { 126 + struct i2c_client *client = data->client; 127 + s32 ret; 128 + 129 + ret = i2c_smbus_read_word_data(client, 130 + hdc2010_reg_translation[chan->address].primary); 131 + 132 + if (ret < 0) 133 + dev_err(&client->dev, "Could not read sensor measurement word\n"); 134 + 135 + return ret; 136 + } 137 + 138 + static int hdc2010_get_peak_measurement_byte(struct hdc2010_data *data, 139 + struct iio_chan_spec const *chan) 140 + { 141 + struct i2c_client *client = data->client; 142 + s32 ret; 143 + 144 + ret = i2c_smbus_read_byte_data(client, 145 + hdc2010_reg_translation[chan->address].peak); 146 + 147 + if (ret < 0) 148 + dev_err(&client->dev, "Could not read sensor measurement byte\n"); 149 + 150 + return ret; 151 + } 152 + 153 + static int hdc2010_get_heater_status(struct hdc2010_data *data) 154 + { 155 + return !!(data->drdy_config & HDC2010_HEATER_EN); 156 + } 157 + 158 + static int hdc2010_read_raw(struct iio_dev *indio_dev, 159 + struct iio_chan_spec const *chan, int *val, 160 + int *val2, long mask) 161 + { 162 + struct hdc2010_data *data = iio_priv(indio_dev); 163 + 164 + switch (mask) { 165 + case IIO_CHAN_INFO_RAW: { 166 + int ret; 167 + 168 + if (chan->type == IIO_CURRENT) { 169 + *val = hdc2010_get_heater_status(data); 170 + return IIO_VAL_INT; 171 + } 172 + ret = iio_device_claim_direct_mode(indio_dev); 173 + if (ret) 174 + return ret; 175 + mutex_lock(&data->lock); 176 + ret = hdc2010_get_prim_measurement_word(data, chan); 177 + mutex_unlock(&data->lock); 178 + iio_device_release_direct_mode(indio_dev); 179 + if (ret < 0) 180 + return ret; 181 + *val = ret; 182 + return IIO_VAL_INT; 183 + } 184 + case IIO_CHAN_INFO_PEAK: { 185 + int ret; 186 + 187 + ret = iio_device_claim_direct_mode(indio_dev); 188 + if (ret) 189 + return ret; 190 + mutex_lock(&data->lock); 191 + ret = hdc2010_get_peak_measurement_byte(data, chan); 192 + mutex_unlock(&data->lock); 193 + iio_device_release_direct_mode(indio_dev); 194 + if (ret < 0) 195 + return ret; 196 + /* Scaling up the value so we can use same offset as RAW */ 197 + *val = ret * 256; 198 + return IIO_VAL_INT; 199 + } 200 + case IIO_CHAN_INFO_SCALE: 201 + *val2 = 65536; 202 + if (chan->type == IIO_TEMP) 203 + *val = 165000; 204 + else 205 + *val = 100000; 206 + return IIO_VAL_FRACTIONAL; 207 + case IIO_CHAN_INFO_OFFSET: 208 + *val = -15887; 209 + *val2 = 515151; 210 + return IIO_VAL_INT_PLUS_MICRO; 211 + default: 212 + return -EINVAL; 213 + } 214 + } 215 + 216 + static int hdc2010_write_raw(struct iio_dev *indio_dev, 217 + struct iio_chan_spec const *chan, 218 + int val, int val2, long mask) 219 + { 220 + struct hdc2010_data *data = iio_priv(indio_dev); 221 + int new, ret; 222 + 223 + switch (mask) { 224 + case IIO_CHAN_INFO_RAW: 225 + if (chan->type != IIO_CURRENT || val2 != 0) 226 + return -EINVAL; 227 + 228 + switch (val) { 229 + case 1: 230 + new = HDC2010_HEATER_EN; 231 + break; 232 + case 0: 233 + new = 0; 234 + break; 235 + default: 236 + return -EINVAL; 237 + } 238 + 239 + mutex_lock(&data->lock); 240 + ret = hdc2010_update_drdy_config(data, HDC2010_HEATER_EN, new); 241 + mutex_unlock(&data->lock); 242 + return ret; 243 + default: 244 + return -EINVAL; 245 + } 246 + } 247 + 248 + static const struct iio_info hdc2010_info = { 249 + .read_raw = hdc2010_read_raw, 250 + .write_raw = hdc2010_write_raw, 251 + .attrs = &hdc2010_attribute_group, 252 + }; 253 + 254 + static int hdc2010_probe(struct i2c_client *client, 255 + const struct i2c_device_id *id) 256 + { 257 + struct iio_dev *indio_dev; 258 + struct hdc2010_data *data; 259 + u8 tmp; 260 + int ret; 261 + 262 + if (!i2c_check_functionality(client->adapter, 263 + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C)) 264 + return -EOPNOTSUPP; 265 + 266 + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); 267 + if (!indio_dev) 268 + return -ENOMEM; 269 + 270 + data = iio_priv(indio_dev); 271 + i2c_set_clientdata(client, indio_dev); 272 + data->client = client; 273 + mutex_init(&data->lock); 274 + 275 + indio_dev->dev.parent = &client->dev; 276 + /* 277 + * As DEVICE ID register does not differentiate between 278 + * HDC2010 and HDC2080, we have the name hardcoded 279 + */ 280 + indio_dev->name = "hdc2010"; 281 + indio_dev->modes = INDIO_DIRECT_MODE; 282 + indio_dev->info = &hdc2010_info; 283 + 284 + indio_dev->channels = hdc2010_channels; 285 + indio_dev->num_channels = ARRAY_SIZE(hdc2010_channels); 286 + 287 + /* Enable Automatic Measurement Mode at 5Hz */ 288 + ret = hdc2010_update_drdy_config(data, HDC2010_AMM, HDC2010_AMM); 289 + if (ret) 290 + return ret; 291 + 292 + /* 293 + * We enable both temp and humidity measurement. 294 + * However the measurement won't start even in AMM until triggered. 295 + */ 296 + tmp = (data->measurement_config & ~HDC2010_MEAS_CONF) | 297 + HDC2010_MEAS_TRIG; 298 + 299 + ret = i2c_smbus_write_byte_data(client, HDC2010_REG_MEASUREMENT_CONF, tmp); 300 + if (ret) { 301 + dev_warn(&client->dev, "Unable to set up measurement\n"); 302 + if (hdc2010_update_drdy_config(data, HDC2010_AMM, 0)) 303 + dev_warn(&client->dev, "Unable to restore default AMM\n"); 304 + return ret; 305 + }; 306 + 307 + data->measurement_config = tmp; 308 + 309 + return iio_device_register(indio_dev); 310 + } 311 + 312 + static int hdc2010_remove(struct i2c_client *client) 313 + { 314 + struct iio_dev *indio_dev = i2c_get_clientdata(client); 315 + struct hdc2010_data *data = iio_priv(indio_dev); 316 + 317 + iio_device_unregister(indio_dev); 318 + 319 + /* Disable Automatic Measurement Mode */ 320 + if (hdc2010_update_drdy_config(data, HDC2010_AMM, 0)) 321 + dev_warn(&client->dev, "Unable to restore default AMM\n"); 322 + 323 + return 0; 324 + } 325 + 326 + static const struct i2c_device_id hdc2010_id[] = { 327 + { "hdc2010" }, 328 + { "hdc2080" }, 329 + { } 330 + }; 331 + MODULE_DEVICE_TABLE(i2c, hdc2010_id); 332 + 333 + static const struct of_device_id hdc2010_dt_ids[] = { 334 + { .compatible = "ti,hdc2010" }, 335 + { .compatible = "ti,hdc2080" }, 336 + { } 337 + }; 338 + MODULE_DEVICE_TABLE(of, hdc2010_dt_ids); 339 + 340 + static struct i2c_driver hdc2010_driver = { 341 + .driver = { 342 + .name = "hdc2010", 343 + .of_match_table = hdc2010_dt_ids, 344 + }, 345 + .probe = hdc2010_probe, 346 + .remove = hdc2010_remove, 347 + .id_table = hdc2010_id, 348 + }; 349 + module_i2c_driver(hdc2010_driver); 350 + 351 + MODULE_AUTHOR("Eugene Zaikonnikov <ez@norphonic.com>"); 352 + MODULE_DESCRIPTION("TI HDC2010 humidity and temperature sensor driver"); 353 + MODULE_LICENSE("GPL");