diff options
Diffstat (limited to 'drivers/iio/humidity')
| -rw-r--r-- | drivers/iio/humidity/Kconfig | 25 | ||||
| -rw-r--r-- | drivers/iio/humidity/Makefile | 6 | ||||
| -rw-r--r-- | drivers/iio/humidity/dht11.c | 294 | ||||
| -rw-r--r-- | drivers/iio/humidity/si7005.c | 189 | 
4 files changed, 514 insertions, 0 deletions
diff --git a/drivers/iio/humidity/Kconfig b/drivers/iio/humidity/Kconfig new file mode 100644 index 00000000000..e116bd8dd0e --- /dev/null +++ b/drivers/iio/humidity/Kconfig @@ -0,0 +1,25 @@ +# +# humidity sensor drivers +# +menu "Humidity sensors" + +config DHT11 +	tristate "DHT11 (and compatible sensors) driver" +	depends on GPIOLIB +	help +	  This driver supports reading data via a single interrupt +	  generating GPIO line. Currently tested are DHT11 and DHT22. +	  Other sensors should work as well as long as they speak the +	  same protocol. + +config SI7005 +	tristate "SI7005 relative humidity and temperature sensor" +	depends on I2C +	help +	  Say yes here to build support for the Silabs Si7005 relative +	  humidity and temperature sensor. + +	  To compile this driver as a module, choose M here: the module +	  will be called si7005. + +endmenu diff --git a/drivers/iio/humidity/Makefile b/drivers/iio/humidity/Makefile new file mode 100644 index 00000000000..e3f3d942e64 --- /dev/null +++ b/drivers/iio/humidity/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for IIO humidity sensor drivers +# + +obj-$(CONFIG_DHT11) += dht11.o +obj-$(CONFIG_SI7005) += si7005.o diff --git a/drivers/iio/humidity/dht11.c b/drivers/iio/humidity/dht11.c new file mode 100644 index 00000000000..d8771f546bf --- /dev/null +++ b/drivers/iio/humidity/dht11.c @@ -0,0 +1,294 @@ +/* + * DHT11/DHT22 bit banging GPIO driver + * + * Copyright (c) Harald Geyer <harald@ccbib.org> + * + * 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/err.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/printk.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/sysfs.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/wait.h> +#include <linux/bitops.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> + +#include <linux/iio/iio.h> + +#define DRIVER_NAME	"dht11" + +#define DHT11_DATA_VALID_TIME	2000000000  /* 2s in ns */ + +#define DHT11_EDGES_PREAMBLE 4 +#define DHT11_BITS_PER_READ 40 +#define DHT11_EDGES_PER_READ (2*DHT11_BITS_PER_READ + DHT11_EDGES_PREAMBLE + 1) + +/* Data transmission timing (nano seconds) */ +#define DHT11_START_TRANSMISSION	18  /* ms */ +#define DHT11_SENSOR_RESPONSE	80000 +#define DHT11_START_BIT		50000 +#define DHT11_DATA_BIT_LOW	27000 +#define DHT11_DATA_BIT_HIGH	70000 + +struct dht11 { +	struct device			*dev; + +	int				gpio; +	int				irq; + +	struct completion		completion; + +	s64				timestamp; +	int				temperature; +	int				humidity; + +	/* num_edges: -1 means "no transmission in progress" */ +	int				num_edges; +	struct {s64 ts; int value; }	edges[DHT11_EDGES_PER_READ]; +}; + +static unsigned char dht11_decode_byte(int *timing, int threshold) +{ +	unsigned char ret = 0; +	int i; + +	for (i = 0; i < 8; ++i) { +		ret <<= 1; +		if (timing[i] >= threshold) +			++ret; +	} + +	return ret; +} + +static int dht11_decode(struct dht11 *dht11, int offset) +{ +	int i, t, timing[DHT11_BITS_PER_READ], threshold, +		timeres = DHT11_SENSOR_RESPONSE; +	unsigned char temp_int, temp_dec, hum_int, hum_dec, checksum; + +	/* Calculate timestamp resolution */ +	for (i = 0; i < dht11->num_edges; ++i) { +		t = dht11->edges[i].ts - dht11->edges[i-1].ts; +		if (t > 0 && t < timeres) +			timeres = t; +	} +	if (2*timeres > DHT11_DATA_BIT_HIGH) { +		pr_err("dht11: timeresolution %d too bad for decoding\n", +			timeres); +		return -EIO; +	} +	threshold = DHT11_DATA_BIT_HIGH / timeres; +	if (DHT11_DATA_BIT_LOW/timeres + 1 >= threshold) +		pr_err("dht11: WARNING: decoding ambiguous\n"); + +	/* scale down with timeres and check validity */ +	for (i = 0; i < DHT11_BITS_PER_READ; ++i) { +		t = dht11->edges[offset + 2*i + 2].ts - +			dht11->edges[offset + 2*i + 1].ts; +		if (!dht11->edges[offset + 2*i + 1].value) +			return -EIO;  /* lost synchronisation */ +		timing[i] = t / timeres; +	} + +	hum_int = dht11_decode_byte(timing, threshold); +	hum_dec = dht11_decode_byte(&timing[8], threshold); +	temp_int = dht11_decode_byte(&timing[16], threshold); +	temp_dec = dht11_decode_byte(&timing[24], threshold); +	checksum = dht11_decode_byte(&timing[32], threshold); + +	if (((hum_int + hum_dec + temp_int + temp_dec) & 0xff) != checksum) +		return -EIO; + +	dht11->timestamp = iio_get_time_ns(); +	if (hum_int < 20) {  /* DHT22 */ +		dht11->temperature = (((temp_int & 0x7f) << 8) + temp_dec) * +					((temp_int & 0x80) ? -100 : 100); +		dht11->humidity = ((hum_int << 8) + hum_dec) * 100; +	} else if (temp_dec == 0 && hum_dec == 0) {  /* DHT11 */ +		dht11->temperature = temp_int * 1000; +		dht11->humidity = hum_int * 1000; +	} else { +		dev_err(dht11->dev, +			"Don't know how to decode data: %d %d %d %d\n", +			hum_int, hum_dec, temp_int, temp_dec); +		return -EIO; +	} + +	return 0; +} + +static int dht11_read_raw(struct iio_dev *iio_dev, +			const struct iio_chan_spec *chan, +			int *val, int *val2, long m) +{ +	struct dht11 *dht11 = iio_priv(iio_dev); +	int ret; + +	if (dht11->timestamp + DHT11_DATA_VALID_TIME < iio_get_time_ns()) { +		reinit_completion(&dht11->completion); + +		dht11->num_edges = 0; +		ret = gpio_direction_output(dht11->gpio, 0); +		if (ret) +			goto err; +		msleep(DHT11_START_TRANSMISSION); +		ret = gpio_direction_input(dht11->gpio); +		if (ret) +			goto err; + +		ret = wait_for_completion_killable_timeout(&dht11->completion, +								 HZ); +		if (ret == 0 && dht11->num_edges < DHT11_EDGES_PER_READ - 1) { +			dev_err(&iio_dev->dev, +					"Only %d signal edges detected\n", +					dht11->num_edges); +			ret = -ETIMEDOUT; +		} +		if (ret < 0) +			goto err; + +		ret = dht11_decode(dht11, +				dht11->num_edges == DHT11_EDGES_PER_READ ? +					DHT11_EDGES_PREAMBLE : +					DHT11_EDGES_PREAMBLE - 2); +		if (ret) +			goto err; +	} + +	ret = IIO_VAL_INT; +	if (chan->type == IIO_TEMP) +		*val = dht11->temperature; +	else if (chan->type == IIO_HUMIDITYRELATIVE) +		*val = dht11->humidity; +	else +		ret = -EINVAL; +err: +	dht11->num_edges = -1; +	return ret; +} + +static const struct iio_info dht11_iio_info = { +	.driver_module		= THIS_MODULE, +	.read_raw		= dht11_read_raw, +}; + +/* + * IRQ handler called on GPIO edges +*/ +static irqreturn_t dht11_handle_irq(int irq, void *data) +{ +	struct iio_dev *iio = data; +	struct dht11 *dht11 = iio_priv(iio); + +	/* TODO: Consider making the handler safe for IRQ sharing */ +	if (dht11->num_edges < DHT11_EDGES_PER_READ && dht11->num_edges >= 0) { +		dht11->edges[dht11->num_edges].ts = iio_get_time_ns(); +		dht11->edges[dht11->num_edges++].value = +						gpio_get_value(dht11->gpio); + +		if (dht11->num_edges >= DHT11_EDGES_PER_READ) +			complete(&dht11->completion); +	} + +	return IRQ_HANDLED; +} + +static const struct iio_chan_spec dht11_chan_spec[] = { +	{ .type = IIO_TEMP, +		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), }, +	{ .type = IIO_HUMIDITYRELATIVE, +		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), } +}; + +static const struct of_device_id dht11_dt_ids[] = { +	{ .compatible = "dht11", }, +	{ } +}; +MODULE_DEVICE_TABLE(of, dht11_dt_ids); + +static int dht11_probe(struct platform_device *pdev) +{ +	struct device *dev = &pdev->dev; +	struct device_node *node = dev->of_node; +	struct dht11 *dht11; +	struct iio_dev *iio; +	int ret; + +	iio = devm_iio_device_alloc(dev, sizeof(*dht11)); +	if (!iio) { +		dev_err(dev, "Failed to allocate IIO device\n"); +		return -ENOMEM; +	} + +	dht11 = iio_priv(iio); +	dht11->dev = dev; + +	dht11->gpio = ret = of_get_gpio(node, 0); +	if (ret < 0) +		return ret; +	ret = devm_gpio_request_one(dev, dht11->gpio, GPIOF_IN, pdev->name); +	if (ret) +		return ret; + +	dht11->irq = gpio_to_irq(dht11->gpio); +	if (dht11->irq < 0) { +		dev_err(dev, "GPIO %d has no interrupt\n", dht11->gpio); +		return -EINVAL; +	} +	ret = devm_request_irq(dev, dht11->irq, dht11_handle_irq, +				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, +				pdev->name, iio); +	if (ret) +		return ret; + +	dht11->timestamp = iio_get_time_ns() - DHT11_DATA_VALID_TIME - 1; +	dht11->num_edges = -1; + +	platform_set_drvdata(pdev, iio); + +	init_completion(&dht11->completion); +	iio->name = pdev->name; +	iio->dev.parent = &pdev->dev; +	iio->info = &dht11_iio_info; +	iio->modes = INDIO_DIRECT_MODE; +	iio->channels = dht11_chan_spec; +	iio->num_channels = ARRAY_SIZE(dht11_chan_spec); + +	return devm_iio_device_register(dev, iio); +} + +static struct platform_driver dht11_driver = { +	.driver = { +		.name	= DRIVER_NAME, +		.owner	= THIS_MODULE, +		.of_match_table = dht11_dt_ids, +	}, +	.probe  = dht11_probe, +}; + +module_platform_driver(dht11_driver); + +MODULE_AUTHOR("Harald Geyer <harald@ccbib.org>"); +MODULE_DESCRIPTION("DHT11 humidity/temperature sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/humidity/si7005.c b/drivers/iio/humidity/si7005.c new file mode 100644 index 00000000000..bdd586e6d95 --- /dev/null +++ b/drivers/iio/humidity/si7005.c @@ -0,0 +1,189 @@ +/* + * si7005.c - Support for Silabs Si7005 humidity and temperature sensor + * + * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net> + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License.  See the file COPYING in the main + * directory of this archive for more details. + * + * (7-bit I2C slave address 0x40) + * + * TODO: heater, fast mode, processed mode (temp. / linearity compensation) + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/pm.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define SI7005_STATUS 0x00 +#define SI7005_DATA 0x01 /* 16-bit, MSB */ +#define SI7005_CONFIG 0x03 +#define SI7005_ID 0x11 + +#define SI7005_STATUS_NRDY BIT(0) +#define SI7005_CONFIG_TEMP BIT(4) +#define SI7005_CONFIG_START BIT(0) + +#define SI7005_ID_7005 0x50 +#define SI7005_ID_7015 0xf0 + +struct si7005_data { +	struct i2c_client *client; +	struct mutex lock; +	u8 config; +}; + +static int si7005_read_measurement(struct si7005_data *data, bool temp) +{ +	int tries = 50; +	int ret; + +	mutex_lock(&data->lock); + +	ret = i2c_smbus_write_byte_data(data->client, SI7005_CONFIG, +		data->config | SI7005_CONFIG_START | +		(temp ? SI7005_CONFIG_TEMP : 0)); +	if (ret < 0) +		goto done; + +	while (tries-- > 0) { +		msleep(20); +		ret = i2c_smbus_read_byte_data(data->client, SI7005_STATUS); +		if (ret < 0) +			goto done; +		if (!(ret & SI7005_STATUS_NRDY)) +			break; +	} +	if (tries < 0) { +		ret = -EIO; +		goto done; +	} + +	ret = i2c_smbus_read_word_swapped(data->client, SI7005_DATA); + +done: +	mutex_unlock(&data->lock); + +	return ret; +} + +static int si7005_read_raw(struct iio_dev *indio_dev, +			    struct iio_chan_spec const *chan, int *val, +			    int *val2, long mask) +{ +	struct si7005_data *data = iio_priv(indio_dev); +	int ret; + +	switch (mask) { +	case IIO_CHAN_INFO_RAW: +		ret = si7005_read_measurement(data, chan->type == IIO_TEMP); +		if (ret < 0) +			return ret; +		*val = ret; +		return IIO_VAL_INT; +	case IIO_CHAN_INFO_SCALE: +		if (chan->type == IIO_TEMP) { +			*val = 7; +			*val2 = 812500; +		} else { +			*val = 3; +			*val2 = 906250; +		} +		return IIO_VAL_INT_PLUS_MICRO; +	case IIO_CHAN_INFO_OFFSET: +		if (chan->type == IIO_TEMP) +			*val = -50 * 32 * 4; +		else +			*val = -24 * 16 * 16; +		return IIO_VAL_INT; +	default: +		break; +	} + +	return -EINVAL; +} + +static const struct iio_chan_spec si7005_channels[] = { +	{ +		.type = IIO_HUMIDITYRELATIVE, +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | +			BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), +	}, +	{ +		.type = IIO_TEMP, +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | +			BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), +	} +}; + +static const struct iio_info si7005_info = { +	.read_raw = si7005_read_raw, +	.driver_module = THIS_MODULE, +}; + +static int si7005_probe(struct i2c_client *client, +			 const struct i2c_device_id *id) +{ +	struct iio_dev *indio_dev; +	struct si7005_data *data; +	int ret; + +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) +		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->client = client; +	mutex_init(&data->lock); + +	indio_dev->dev.parent = &client->dev; +	indio_dev->name = dev_name(&client->dev); +	indio_dev->modes = INDIO_DIRECT_MODE; +	indio_dev->info = &si7005_info; + +	indio_dev->channels = si7005_channels; +	indio_dev->num_channels = ARRAY_SIZE(si7005_channels); + +	ret = i2c_smbus_read_byte_data(client, SI7005_ID); +	if (ret < 0) +		return ret; +	if (ret != SI7005_ID_7005 && ret != SI7005_ID_7015) +		return -ENODEV; + +	ret = i2c_smbus_read_byte_data(client, SI7005_CONFIG); +	if (ret < 0) +		return ret; +	data->config = ret; + +	return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id si7005_id[] = { +	{ "si7005", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, si7005_id); + +static struct i2c_driver si7005_driver = { +	.driver = { +		.name	= "si7005", +		.owner	= THIS_MODULE, +	}, +	.probe = si7005_probe, +	.id_table = si7005_id, +}; +module_i2c_driver(si7005_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("Silabs Si7005 humidity and temperature sensor driver"); +MODULE_LICENSE("GPL");  | 
