Initial Release of HDC2010 Driver

* The HDC2010 isn't SW compatible with the HDC100x series so a new
driver needed to be written.
* This driver is a minimal starting point, providing single shot reads
and heater control.

Change-Id: I2de168a6dee68829b143fac32ae6b2f4134cfd45
diff --git a/coral-enviro-drivers/Makefile b/coral-enviro-drivers/Makefile
index cc0dc5d..08736fe 100644
--- a/coral-enviro-drivers/Makefile
+++ b/coral-enviro-drivers/Makefile
@@ -2,6 +2,7 @@
 # Makefile for the industrial I/O core.
 #
 
+obj-m += humidity/
 obj-m += light/
 obj-m += adc/
 obj-m += pressure/
diff --git a/coral-enviro-drivers/debian/changelog b/coral-enviro-drivers/debian/changelog
index 2a0d6af..9c10717 100644
--- a/coral-enviro-drivers/debian/changelog
+++ b/coral-enviro-drivers/debian/changelog
@@ -1,3 +1,9 @@
+coral-enviro-drivers-dkms (1.1) stable; urgency=medium
+
+  * Add support for HDC20x0.
+
+ -- Coral <coral-support@google.com>  Wed, 03 Apr 2019 17:12:41 -0700
+
 coral-enviro-drivers-dkms (1.0) stable; urgency=medium
 
   * Proper debian package based on dh_dkms.
diff --git a/coral-enviro-drivers/debian/coral-enviro-drivers-dkms.dkms b/coral-enviro-drivers/debian/coral-enviro-drivers-dkms.dkms
index 3b1a903..32c0bca 100644
--- a/coral-enviro-drivers/debian/coral-enviro-drivers-dkms.dkms
+++ b/coral-enviro-drivers/debian/coral-enviro-drivers-dkms.dkms
@@ -10,5 +10,8 @@
 BUILT_MODULE_NAME[2]="ti-ads1015"
 BUILT_MODULE_LOCATION[2]="./adc"
 DEST_MODULE_LOCATION[2]="/kernel/drivers/iio/adc"
+BUILT_MODULE_NAME[3]="hdc20x0"
+BUILT_MODULE_LOCATION[3]="./humidity"
+DEST_MODULE_LOCATION[3]="/kernel/drivers/iio/humidity"
 
 AUTOINSTALL="yes"
diff --git a/coral-enviro-drivers/debian/rules b/coral-enviro-drivers/debian/rules
index 800baa1..d045a9c 100755
--- a/coral-enviro-drivers/debian/rules
+++ b/coral-enviro-drivers/debian/rules
@@ -10,7 +10,7 @@
 	dh $@ --with dkms
 
 override_dh_install:
-	dh_install adc/ light/ pressure/ Makefile usr/src/coral-enviro-drivers-$(VERSION)/
+	dh_install adc/ humidity/ light/ pressure/ Makefile usr/src/coral-enviro-drivers-$(VERSION)/
 
 override_dh_dkms:
 	dh_dkms -V $(VERSION)
diff --git a/coral-enviro-drivers/humidity/Makefile b/coral-enviro-drivers/humidity/Makefile
new file mode 100644
index 0000000..56a5681
--- /dev/null
+++ b/coral-enviro-drivers/humidity/Makefile
@@ -0,0 +1,9 @@
+obj-m := hdc20x0.o
+KVERSION := $(shell uname -r)
+
+all:
+	$(MAKE) -C /lib/modules/$(KVERSION)/build M=$(PWD) modules
+
+clean:
+	$(MAKE) -C /lib/modules/$(KVERSION)/build M=$(PWD) clean
+
diff --git a/coral-enviro-drivers/humidity/hdc20x0.c b/coral-enviro-drivers/humidity/hdc20x0.c
new file mode 100644
index 0000000..42fb05c
--- /dev/null
+++ b/coral-enviro-drivers/humidity/hdc20x0.c
@@ -0,0 +1,276 @@
+/*
+ * hdc20x0.c - Support for the TI HDC20x0 temperature + humidity sensors
+ *
+ * Copyright (C) 2019 Google LLC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define HDC20X0_REG_TEMP_LOW 0x00
+#define HDC20X0_REG_TEMP_HIGH 0x01
+#define HDC20X0_REG_HUMID_LOW 0x02
+#define HDC20X0_REG_HUMID_HIGH 0x03
+#define HDC20X0_REG_INTERRUPT_DRDY 0x04
+#define HDC20X0_REG_TEMP_MAX 0x05
+#define HDC20X0_REG_HUMID_MAX 0x06
+#define HDC20X0_REG_INTERRUPT_CONFIG 0x07
+#define HDC20X0_REG_TEMP_OFFSET_ADJUST 0x08
+#define HDC20X0_REG_HUM_OFFSET_ADJUST 0x09
+#define HDC20X0_REG_TEMP_THR_L 0x0A
+#define HDC20X0_REG_TEMP_THR_H 0x0B
+#define HDC20X0_REG_HUMID_THR_L 0x0C
+#define HDC20X0_REG_HUMID_THR_H 0x0D
+#define HDC20X0_REG_CONFIG 0x0E
+#define HDC20X0_REG_MEASUREMENT_CONFIG 0x0F
+#define HDC20X0_REG_MID_L 0xFC
+#define HDC20X0_REG_MID_H 0xFD
+#define HDC20X0_REG_DEVICE_ID_L 0xFE
+#define HDC20X0_REG_DEVICE_ID_H 0xFF
+
+#define HDC20X0_REG_CONFIG_HEATER_EN 0x8
+#define HDC20X0_REG_MEASUREMENT_CONFIG_TRIGGER 0x1
+
+#define HDC20X0_DRV_NAME "hdc20x0"
+
+struct hdc20x0_data {
+	struct regmap *regmap;
+	/* Mutex should be used on read/write operations to prevent bus
+	 * contention */
+	struct mutex lock;
+	u16 config;
+};
+
+static const struct regmap_config hdc20x0_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = HDC20X0_REG_DEVICE_ID_H,
+};
+
+static IIO_CONST_ATTR(out_current_heater_raw_available, "0 1");
+
+static struct attribute *hdc20x0_attributes[] = {
+	&iio_const_attr_out_current_heater_raw_available.dev_attr.attr, NULL
+};
+
+static struct attribute_group hdc20x0_attribute_group = {
+	.attrs = hdc20x0_attributes,
+};
+
+static const struct iio_chan_spec hdc20x0_channels[] = {
+	{
+		.type = IIO_TEMP,
+		.address = HDC20X0_REG_TEMP_LOW,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.info_mask_shared_by_type =
+			BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET),
+	},
+	{
+		.type = IIO_HUMIDITYRELATIVE,
+		.address = HDC20X0_REG_HUMID_LOW,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+	},
+	{
+		.type = IIO_CURRENT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.extend_name = "heater",
+		.output = 1,
+	},
+};
+
+static int hdc20x0_update_config(struct hdc20x0_data *data, int mask, int val)
+{
+	int tmp = (~mask & data->config) | val;
+	int ret;
+
+	ret = regmap_update_bits(data->regmap, HDC20X0_REG_CONFIG, mask, val);
+	if (!ret)
+		data->config = tmp;
+
+	return ret;
+}
+
+static int hdc20x0_get_measurement(
+	struct hdc20x0_data *data, struct iio_chan_spec const *chan)
+{
+	struct device *dev = regmap_get_device(data->regmap);
+	int ret;
+	int val, reg;
+
+	/* Trigger measurement by setting bit in measurement config */
+	ret = regmap_update_bits(data->regmap, HDC20X0_REG_MEASUREMENT_CONFIG,
+		HDC20X0_REG_MEASUREMENT_CONFIG_TRIGGER, 1);
+
+	/* Read low byte of measurement */
+	ret = regmap_read(data->regmap, chan->address, &reg);
+	if (ret < 0) {
+		dev_err(dev, "cannot read low byte");
+		return ret;
+	}
+	val = reg;
+
+	/* High byte is address + 1 */
+	ret = regmap_read(data->regmap, chan->address + 1, &reg);
+	if (ret < 0) {
+		dev_err(dev, "cannot read high byte");
+		return ret;
+	}
+	val |= reg << 8;
+
+	return val;
+}
+
+static int hdc20x0_get_heater_status(struct hdc20x0_data *data)
+{
+	return !!(data->config & HDC20X0_REG_CONFIG_HEATER_EN);
+}
+
+static int hdc20x0_read_raw(struct iio_dev *indio_dev,
+	struct iio_chan_spec const *chan, int *val, int *val2, long mask)
+{
+	struct hdc20x0_data *data = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		mutex_lock(&data->lock);
+		if (chan->type == IIO_CURRENT) {
+			*val = hdc20x0_get_heater_status(data);
+			ret = IIO_VAL_INT;
+		} else {
+			ret = hdc20x0_get_measurement(data, chan);
+			if (ret >= 0) {
+				*val = ret;
+				ret = IIO_VAL_INT;
+			}
+		}
+		mutex_unlock(&data->lock);
+		return ret;
+	case IIO_CHAN_INFO_SCALE:
+		if (chan->type == IIO_TEMP) {
+			*val = 165;
+			*val2 = 65536;
+			return IIO_VAL_FRACTIONAL;
+		} else if (chan->type == IIO_HUMIDITYRELATIVE) {
+			*val = 100;
+			*val2 = 65536;
+			return IIO_VAL_FRACTIONAL;
+		} else {
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_OFFSET:
+		if (chan->type == IIO_TEMP) {
+			/* IIO offset happens before scaling so values are -40 *
+			 * scaling */
+			*val = -15887;
+			*val2 = 515151;
+			return IIO_VAL_INT_PLUS_MICRO;
+		} else {
+			return -EINVAL;
+		}
+	default:
+		return -EINVAL;
+	}
+}
+
+static int hdc20x0_write_raw(struct iio_dev *indio_dev,
+	struct iio_chan_spec const *chan, int val, int val2, long mask)
+{
+	struct hdc20x0_data *data = iio_priv(indio_dev);
+	int ret = -EINVAL;
+
+	if (mask == IIO_CHAN_INFO_RAW) {
+		if (chan->type != IIO_CURRENT || val2 != 0)
+			return -EINVAL;
+
+		mutex_lock(&data->lock);
+		ret = hdc20x0_update_config(data, HDC20X0_REG_CONFIG_HEATER_EN,
+			val ? HDC20X0_REG_CONFIG_HEATER_EN : 0);
+		mutex_unlock(&data->lock);
+		return ret;
+	} else {
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info hdc20x0_info = {
+	.read_raw = hdc20x0_read_raw,
+	.write_raw = hdc20x0_write_raw,
+	.attrs = &hdc20x0_attribute_group,
+	.driver_module = THIS_MODULE,
+};
+
+static int hdc20x0_probe(
+	struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct iio_dev *indio_dev;
+	struct hdc20x0_data *data;
+
+	if (!i2c_check_functionality(client->adapter,
+		    I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE))
+		return -ENODEV;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->regmap = devm_regmap_init_i2c(client, &hdc20x0_regmap_config);
+	mutex_init(&data->lock);
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->name = HDC20X0_DRV_NAME;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->info = &hdc20x0_info;
+
+	indio_dev->channels = hdc20x0_channels;
+	indio_dev->num_channels = ARRAY_SIZE(hdc20x0_channels);
+
+	return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static int hdc20x0_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+	struct hdc20x0_data *data = iio_priv(indio_dev);
+
+	iio_device_unregister(indio_dev);
+}
+
+static const struct i2c_device_id hdc20x0_id[] = { { HDC20X0_DRV_NAME, 0 },
+	{} };
+MODULE_DEVICE_TABLE(i2c, hdc20x0_id);
+
+static struct i2c_driver hdc20x0_driver = {
+	.driver = {
+		.name	= HDC20X0_DRV_NAME,
+	},
+	.probe = hdc20x0_probe,
+	.remove	= hdc20x0_remove,
+	.id_table = hdc20x0_id,
+};
+
+module_i2c_driver(hdc20x0_driver);
+
+MODULE_AUTHOR("Michael Brooks <mrbrooks@google.com>");
+MODULE_DESCRIPTION("TI HDC20x0 Humidity and Temperature Sensor Driver");
+MODULE_LICENSE("GPL");