linux/drivers/iio/humidity/hdc2010.c
<<
>>
Prefs
   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
  43struct 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
  51enum hdc2010_addr_groups {
  52        HDC2010_GROUP_TEMP = 0,
  53        HDC2010_GROUP_HUMIDITY,
  54};
  55
  56struct hdc2010_reg_record {
  57        unsigned long primary;
  58        unsigned long peak;
  59};
  60
  61static 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
  72static IIO_CONST_ATTR(out_current_heater_raw_available, "0 1");
  73
  74static struct attribute *hdc2010_attributes[] = {
  75        &iio_const_attr_out_current_heater_raw_available.dev_attr.attr,
  76        NULL
  77};
  78
  79static const struct attribute_group hdc2010_attribute_group = {
  80        .attrs = hdc2010_attributes,
  81};
  82
  83static 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
 107static 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
 123static 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
 138static 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
 153static int hdc2010_get_heater_status(struct hdc2010_data *data)
 154{
 155        return !!(data->drdy_config & HDC2010_HEATER_EN);
 156}
 157
 158static 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
 216static 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
 248static 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
 254static 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
 312static 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
 326static const struct i2c_device_id hdc2010_id[] = {
 327        { "hdc2010" },
 328        { "hdc2080" },
 329        { }
 330};
 331MODULE_DEVICE_TABLE(i2c, hdc2010_id);
 332
 333static const struct of_device_id hdc2010_dt_ids[] = {
 334        { .compatible = "ti,hdc2010" },
 335        { .compatible = "ti,hdc2080" },
 336        { }
 337};
 338MODULE_DEVICE_TABLE(of, hdc2010_dt_ids);
 339
 340static 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};
 349module_i2c_driver(hdc2010_driver);
 350
 351MODULE_AUTHOR("Eugene Zaikonnikov <ez@norphonic.com>");
 352MODULE_DESCRIPTION("TI HDC2010 humidity and temperature sensor driver");
 353MODULE_LICENSE("GPL");
 354