diff options
Diffstat (limited to 'drivers/iio/humidity')
-rw-r--r-- | drivers/iio/humidity/Kconfig | 35 | ||||
-rw-r--r-- | drivers/iio/humidity/Makefile | 7 | ||||
-rw-r--r-- | drivers/iio/humidity/dht11.c | 306 | ||||
-rw-r--r-- | drivers/iio/humidity/si7005.c | 189 | ||||
-rw-r--r-- | drivers/iio/humidity/si7020.c | 161 |
5 files changed, 698 insertions, 0 deletions
diff --git a/drivers/iio/humidity/Kconfig b/drivers/iio/humidity/Kconfig new file mode 100644 index 000000000..4813b793b --- /dev/null +++ b/drivers/iio/humidity/Kconfig @@ -0,0 +1,35 @@ +# +# 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. + +config SI7020 + tristate "Si7013/20/21 Relative Humidity and Temperature Sensors" + depends on I2C + help + Say yes here to build support for the Silicon Labs Si7013/20/21 + Relative Humidity and Temperature Sensors. + + To compile this driver as a module, choose M here: the module + will be called si7020. + +endmenu diff --git a/drivers/iio/humidity/Makefile b/drivers/iio/humidity/Makefile new file mode 100644 index 000000000..86e2d26e9 --- /dev/null +++ b/drivers/iio/humidity/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for IIO humidity sensor drivers +# + +obj-$(CONFIG_DHT11) += dht11.o +obj-$(CONFIG_SI7005) += si7005.o +obj-$(CONFIG_SI7020) += si7020.o diff --git a/drivers/iio/humidity/dht11.c b/drivers/iio/humidity/dht11.c new file mode 100644 index 000000000..7d79a1ac5 --- /dev/null +++ b/drivers/iio/humidity/dht11.c @@ -0,0 +1,306 @@ +/* + * 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/mutex.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 2 +#define DHT11_BITS_PER_READ 40 +/* + * Note that when reading the sensor actually 84 edges are detected, but + * since the last edge is not significant, we only store 83: + */ +#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; + struct mutex lock; + + 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 = 1; 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; +} + +/* + * 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 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; + + mutex_lock(&dht11->lock); + 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 = request_irq(dht11->irq, dht11_handle_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + iio_dev->name, iio_dev); + if (ret) + goto err; + + ret = wait_for_completion_killable_timeout(&dht11->completion, + HZ); + + free_irq(dht11->irq, iio_dev); + + 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; + mutex_unlock(&dht11->lock); + return ret; +} + +static const struct iio_info dht11_iio_info = { + .driver_module = THIS_MODULE, + .read_raw = dht11_read_raw, +}; + +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; + } + + dht11->timestamp = iio_get_time_ns() - DHT11_DATA_VALID_TIME - 1; + dht11->num_edges = -1; + + platform_set_drvdata(pdev, iio); + + init_completion(&dht11->completion); + mutex_init(&dht11->lock); + 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, + .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 000000000..bdd586e6d --- /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"); diff --git a/drivers/iio/humidity/si7020.c b/drivers/iio/humidity/si7020.c new file mode 100644 index 000000000..fa3b809af --- /dev/null +++ b/drivers/iio/humidity/si7020.c @@ -0,0 +1,161 @@ +/* + * si7020.c - Silicon Labs Si7013/20/21 Relative Humidity and Temp Sensors + * Copyright (c) 2013,2014 Uplogix, Inc. + * David Barksdale <dbarksdale@uplogix.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * 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. + */ + +/* + * The Silicon Labs Si7013/20/21 Relative Humidity and Temperature Sensors + * are i2c devices which have an identical programming interface for + * measuring relative humidity and temperature. The Si7013 has an additional + * temperature input which this driver does not support. + * + * Data Sheets: + * Si7013: http://www.silabs.com/Support%20Documents/TechnicalDocs/Si7013.pdf + * Si7020: http://www.silabs.com/Support%20Documents/TechnicalDocs/Si7020.pdf + * Si7021: http://www.silabs.com/Support%20Documents/TechnicalDocs/Si7021.pdf + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/sysfs.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +/* Measure Relative Humidity, Hold Master Mode */ +#define SI7020CMD_RH_HOLD 0xE5 +/* Measure Temperature, Hold Master Mode */ +#define SI7020CMD_TEMP_HOLD 0xE3 +/* Software Reset */ +#define SI7020CMD_RESET 0xFE + +static int si7020_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct i2c_client **client = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_smbus_read_word_data(*client, + chan->type == IIO_TEMP ? + SI7020CMD_TEMP_HOLD : + SI7020CMD_RH_HOLD); + if (ret < 0) + return ret; + *val = ret >> 2; + if (chan->type == IIO_HUMIDITYRELATIVE) + *val &= GENMASK(11, 0); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_TEMP) + *val = 175720; /* = 175.72 * 1000 */ + else + *val = 125 * 1000; + *val2 = 65536 >> 2; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_OFFSET: + /* + * Since iio_convert_raw_to_processed_unlocked assumes offset + * is an integer we have to round these values and lose + * accuracy. + * Relative humidity will be 0.0032959% too high and + * temperature will be 0.00277344 degrees too high. + * This is no big deal because it's within the accuracy of the + * sensor. + */ + if (chan->type == IIO_TEMP) + *val = -4368; /* = -46.85 * (65536 >> 2) / 175.72 */ + else + *val = -786; /* = -6 * (65536 >> 2) / 125 */ + return IIO_VAL_INT; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_chan_spec si7020_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 si7020_info = { + .read_raw = si7020_read_raw, + .driver_module = THIS_MODULE, +}; + +static int si7020_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct i2c_client **data; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE | + I2C_FUNC_SMBUS_READ_WORD_DATA)) + return -ENODEV; + + /* Reset device, loads default settings. */ + ret = i2c_smbus_write_byte(client, SI7020CMD_RESET); + if (ret < 0) + return ret; + /* Wait the maximum power-up time after software reset. */ + msleep(15); + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + *data = client; + + indio_dev->dev.parent = &client->dev; + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &si7020_info; + indio_dev->channels = si7020_channels; + indio_dev->num_channels = ARRAY_SIZE(si7020_channels); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id si7020_id[] = { + { "si7020", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, si7020_id); + +static struct i2c_driver si7020_driver = { + .driver.name = "si7020", + .probe = si7020_probe, + .id_table = si7020_id, +}; + +module_i2c_driver(si7020_driver); +MODULE_DESCRIPTION("Silicon Labs Si7013/20/21 Relative Humidity and Temperature Sensors"); +MODULE_AUTHOR("David Barksdale <dbarksdale@uplogix.com>"); +MODULE_LICENSE("GPL"); |