From patchwork Mon Apr 25 08:42:26 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pascal Sachs X-Patchwork-Id: 8924401 Return-Path: X-Original-To: patchwork-linux-hwmon@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id ACBF1BF29F for ; Mon, 25 Apr 2016 08:43:20 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 9394120166 for ; Mon, 25 Apr 2016 08:43:18 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 4D9AA20114 for ; Mon, 25 Apr 2016 08:43:16 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753955AbcDYInP (ORCPT ); Mon, 25 Apr 2016 04:43:15 -0400 Received: from mail-wm0-f65.google.com ([74.125.82.65]:33588 "EHLO mail-wm0-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753894AbcDYInN (ORCPT ); Mon, 25 Apr 2016 04:43:13 -0400 Received: by mail-wm0-f65.google.com with SMTP id r12so20222767wme.0; Mon, 25 Apr 2016 01:43:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to; bh=tpgGru0s4iJH7aPSX+402sStngnzSMX/A7/fdt7p7UI=; b=w5nfgmQcE8qTtNIoMH7uwvmHNIOm26HJ0d5MuCYMo8RznKq6EPA9inh9/bgWZGF/f7 mivsP0xNPC1+XSxAmc+/h7XTVlasNOP2XlVSAqzVwHnu79JdginPbtzRKU2mZXFsEYBC qceMghkFUBDHoIcdBqnF0jX8ptCjbJ8lE5DxI5RjtVY0gG4yACn56Mj6eKaljk9wSSpY wFHMl/CrWvtMJaqVzmR3gsZ8tthoxOyzXWJnVYORovDKL4iXoLcHj9SAxVMTA3fgqIJC ckQuQcvnflmlI5KSsHmhUG5TSwbFHBggD+Vm8rp84qLJn5JzH+AbNHAq79ZkbBp7+luz jmXQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to; bh=tpgGru0s4iJH7aPSX+402sStngnzSMX/A7/fdt7p7UI=; b=Pe2IqTP6542JBsFWYFkxB6uRJPbya8iSsQJfYpgVXhfeJ//vJ/aj+eaw/ljmRnpJZo BUB+ODTE7yRM2l4TcXgmhxnkkIJzklpsAVpgW3SSdzGVAmoLPB7fTpIBR0Ou60Z2W5C7 hf0ToktBvkl77Ykk7GDvY4IbHdFjU6LgsT6/L0aSo0IzNGNJkQ8Hp5ADhqj/hmflpaKH JJ7KvWLrRgZyabRcXHITLY1Rplez5FlaeWIDLVB38KjLYp78bb9BEJMxLgno1W+6T9ge GHRP5+m68+WN5mWNHgxmQIeVo2MXMVBBw5FoGgnY/P+UdVqwCXbJH9yec1F/E6Yv1hyp H+kg== X-Gm-Message-State: AOPr4FUPbL86nRdRr7CUibHNpRU/GhjteR3vng3b7hZ6yE2hW43Id6lfQBcyX76KTQE5Yw== X-Received: by 10.194.168.194 with SMTP id zy2mr2205879wjb.143.1461573791448; Mon, 25 Apr 2016 01:43:11 -0700 (PDT) Received: from ltli07.sensirion.lokal (mail.sensirion.com. [212.243.229.186]) by smtp.googlemail.com with ESMTPSA id a1sm22367018wje.43.2016.04.25.01.43.09 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 25 Apr 2016 01:43:10 -0700 (PDT) From: Pascal Sachs To: jdelvare@suse.com, linux@roeck-us.net Cc: corbet@lwn.net, linux-hwmon@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, David Frey , Pascal Sachs Subject: [PATCH 1/1 v2] hwmon: add support for Sensirion SHT3x sensors Date: Mon, 25 Apr 2016 10:42:26 +0200 Message-Id: <1461573746-13767-1-git-send-email-pascalsachs@gmail.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: 157916ADD54A1D4ABFF27DDF0233870C956E1A93@S-EXCH01.sensirion.lokal Sender: linux-hwmon-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-hwmon@vger.kernel.org X-Spam-Status: No, score=-7.8 required=5.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, T_DKIM_INVALID, UNPARSEABLE_RELAY autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: David Frey This driver implements support for the Sensirion SHT3x-DIS chip, a humidity and temperature sensor. Temperature is measured in degrees celsius, relative humidity is expressed as a percentage. In the sysfs interface, all values are scaled by 1000, i.e. the value for 31.5 degrees celsius is 31500. Signed-off-by: Pascal Sachs --- Patch was generated against kernel v4.6-rc5 Documentation/hwmon/sht3x | 66 ++++ drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/sht3x.c | 675 ++++++++++++++++++++++++++++++++++++ drivers/hwmon/twl4030-madc-hwmon.c | 1 + include/linux/platform_data/sht3x.h | 25 ++ 6 files changed, 778 insertions(+) create mode 100644 Documentation/hwmon/sht3x create mode 100644 drivers/hwmon/sht3x.c create mode 100644 include/linux/platform_data/sht3x.h diff --git a/Documentation/hwmon/sht3x b/Documentation/hwmon/sht3x new file mode 100644 index 0000000..c04748d --- /dev/null +++ b/Documentation/hwmon/sht3x @@ -0,0 +1,66 @@ +Kernel driver sht3x +=================== + +Supported chips: + * Sensirion SHT3x-DIS + Prefix: 'sht3x' + Addresses scanned: none + Datasheet: http://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/Humidity/Sensirion_Humidity_Datasheet_SHT3x_DIS.pdf + +Author: + David Frey + Pascal Sachs + +Description +----------- + +This driver implements support for the Sensirion SHT3x-DIS chip, a humidity +and temperature sensor. Temperature is measured in degrees celsius, relative +humidity is expressed as a percentage. In the sysfs interface, all values are +scaled by 1000, i.e. the value for 31.5 degrees celsius is 31500. + +The device communicates with the I2C protocol. Sensors can have the I2C +addresses 0x44 or 0x45, depending on the wiring. See +Documentation/i2c/instantiating-devices for methods to instantiate the device. + +There are two options configurable by means of sht3x_platform_data: +1. blocking (pull the I2C clock line down while performing the measurement) or + non-blocking mode. Blocking mode will guarantee the fastest result but + the I2C bus will be busy during that time. By default, non-blocking mode + is used. Make sure clock-stretching works properly on your device if you + want to use blocking mode. +2. high or low accuracy. High accuracy is used by default and using it is + strongly recommended. + +The sht3x sensor supports a single shot mode as well as 5 periodic measure +modes, which can be controlled with the frequency sysfs interface. +The allowed frequencies are as follows: + * 0 single shot mode + * 500 0.5 Hz periodic measurement + * 1000 1 Hz periodic measurement + * 2000 2 Hz periodic measurement + * 4000 4 Hz periodic measurement + * 10000 10 Hz periodic measurement + +While in periodic measure mode, read out of humidity and temperature values are +not supported. Nevertheless it is possible to read out the values with maximal +the same frequency as the periodic measure mode. + +sysfs-Interface +--------------- + +temp1_input: temperature input +humidity1_input: humidity input +temp1_max: temperature max value +temp1_max_hyst: temperature hysteresis value for max limit +humidity1_max: humidity max value +humidity1_max_hyst: humidity hysteresis value for max limit +temp1_min: temperature min value +temp1_min_hyst: temperature hysteresis value for min limit +humidity1_min: humidity min value +humidity1_min_hyst: humidity hysteresis value for min limit +frequency: Measurement frequency, 0 for single shot, frequency in + mHz for periodic measurement +soft_reset: Soft reset the internal state of the sensor, clear the + status register, clear the alert and switch back to + single shot mode diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 5c2d13a..76f4c53 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1254,6 +1254,16 @@ config SENSORS_SHT21 This driver can also be built as a module. If so, the module will be called sht21. +config SENSORS_SHT3x + tristate "Sensiron humidity and temperature sensors. SHT3x and compat." + depends on I2C && CRC8 + help + If you say yes here you get support for the Sensiron SHT30 and SHT31 + humidity and temperature sensors. + + This driver can also be built as a module. If so, the module + will be called sht3x. + config SENSORS_SHTC1 tristate "Sensiron humidity and temperature sensors. SHTC1 and compat." depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 58cc3ac..999ff2d 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -137,6 +137,7 @@ obj-$(CONFIG_SENSORS_SCH5627) += sch5627.o obj-$(CONFIG_SENSORS_SCH5636) += sch5636.o obj-$(CONFIG_SENSORS_SHT15) += sht15.o obj-$(CONFIG_SENSORS_SHT21) += sht21.o +obj-$(CONFIG_SENSORS_SHT3x) += sht3x.o obj-$(CONFIG_SENSORS_SHTC1) += shtc1.o obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o obj-$(CONFIG_SENSORS_SMM665) += smm665.o diff --git a/drivers/hwmon/sht3x.c b/drivers/hwmon/sht3x.c new file mode 100644 index 0000000..97f72b8 --- /dev/null +++ b/drivers/hwmon/sht3x.c @@ -0,0 +1,675 @@ +/* Sensirion SHT3x-DIS humidity and temperature sensor driver. + * The SHT3x comes in many different versions, this driver is for the + * I2C version only. + * + * Copyright (C) 2016 Sensirion AG, Switzerland + * Author: David Frey + * Author: Pascal Sachs + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* commands (high precision mode) */ +static const unsigned char sht3x_cmd_measure_blocking_hpm[] = { 0x2c, 0x06 }; +static const unsigned char sht3x_cmd_measure_nonblocking_hpm[] = { 0x24, 0x00 }; + +/* commands (low power mode) */ +static const unsigned char sht3x_cmd_measure_blocking_lpm[] = { 0x2c, 0x10 }; +static const unsigned char sht3x_cmd_measure_nonblocking_lpm[] = { 0x24, 0x16 }; + +/* commands for periodic mode */ +static const unsigned char sht3x_cmd_measure_periodic_mode[] = { 0xe0, 0x00 }; +static const unsigned char sht3x_cmd_break[] = { 0x30, 0x93 }; + +/* other commands */ +static const unsigned char sht3x_cmd_clear_status_reg[] = { 0x30, 0x41 }; +static const unsigned char sht3x_cmd_soft_reset[] = { 0x30, 0xa2 }; + +/* delays for non-blocking i2c commands, both in us */ +#define SHT3X_NONBLOCKING_WAIT_TIME_HPM 15000 +#define SHT3X_NONBLOCKING_WAIT_TIME_LPM 4000 + +#define SHT3X_WORD_LEN 2 +#define SHT3X_CMD_LENGTH 2 +#define SHT3X_CRC8_LEN 1 +#define SHT3X_RESPONSE_LENGTH 6 +#define SHT3X_CRC8_LEN 1 +#define SHT3X_CRC8_POLYNOMIAL 0x31 +#define SHT3X_CRC8_INIT 0xFF +#define SHT3X_ID_SHT 0 +#define SHT3X_ID_STS 1 + +DECLARE_CRC8_TABLE(sht3x_crc8_table); + +/* periodic measure commands (high precision mode) */ +static const char periodic_measure_commands_hpm[][SHT3X_CMD_LENGTH] = { + /* 0.5 measurements per second */ + {0x20, 0x32}, + /* 1 measurements per second */ + {0x21, 0x30}, + /* 2 measurements per second */ + {0x22, 0x36}, + /* 4 measurements per second */ + {0x23, 0x34}, + /* 10 measurements per second */ + {0x27, 0x37}, +}; + +/* periodic measure commands (low power mode) */ +static const char periodic_measure_commands_lpm[][SHT3X_CMD_LENGTH] = { + /* 0.5 measurements per second */ + {0x20, 0x2f}, + /* 1 measurements per second */ + {0x21, 0x2d}, + /* 2 measurements per second */ + {0x22, 0x2b}, + /* 4 measurements per second */ + {0x23, 0x29}, + /* 10 measurements per second */ + {0x27, 0x2a}, +}; + +struct sht3x_alert_commands { + const char read_command[SHT3X_CMD_LENGTH]; + const char write_command[SHT3X_CMD_LENGTH]; +}; + +const struct sht3x_alert_commands alert_commands[] = { + /* temp1_max, humidity1_max */ + { {0xe1, 0x1f}, {0x61, 0x1d} }, + /* temp_1_max_hyst, humidity1_max_hyst */ + { {0xe1, 0x14}, {0x61, 0x16} }, + /* temp1_min, humidity1_min */ + { {0xe1, 0x02}, {0x61, 0x00} }, + /* temp_1_min_hyst, humidity1_min_hyst */ + { {0xe1, 0x09}, {0x61, 0x0B} }, +}; + + +static const u16 mode_to_frequency[] = { + 0, + 500, + 1000, + 2000, + 4000, + 10000, +}; + +struct sht3x_data { + struct i2c_client *client; + struct mutex update_lock; + + u8 mode; + const unsigned char *command; + u32 wait_time; /* in us*/ + + struct sht3x_platform_data setup; + + int temperature; /* 1000 * temperature in dgr C */ + u32 humidity; /* 1000 * relative humidity in %RH */ +}; + + +static int find_index(const u16 *list, size_t size, u16 value) +{ + size_t index = 0; + + while (index < size && list[index] != value) + index++; + + return index == size ? -1 : index; +} + +static int sht3x_read_from_command(struct i2c_client *client, + struct sht3x_data *data, + const char *command, + char *buf, int length, u32 wait_time) +{ + int ret; + + mutex_lock(&data->update_lock); + + ret = i2c_master_send(client, command, SHT3X_CMD_LENGTH); + + if (ret != SHT3X_CMD_LENGTH) { + ret = ret < 0 ? ret : -EIO; + goto out; + } + + if (wait_time) + usleep_range(wait_time, wait_time + 1000); + + ret = i2c_master_recv(client, buf, length); + if (ret != length) { + ret = ret < 0 ? ret : -EIO; + goto out; + } + + ret = 0; +out: + mutex_unlock(&data->update_lock); + return ret; + +} + +static int sht3x_extract_temperature(u16 raw) +{ + /* + * From datasheet: + * T = -45 + 175 * ST / 2^16 + * Adapted for integer fixed point (3 digit) arithmetic. + */ + return ((21875 * (int)raw) >> 13) - 45000; +} + +static u32 sht3x_extract_humidity(u16 raw) +{ + /* + * From datasheet: + * RH = 100 * SRH / 2^16 + * Adapted for integer fixed point (3 digit) arithmetic. + */ + return (12500 * (int)raw) >> 13; +} + +/* sysfs attributes */ +static struct sht3x_data *sht3x_update_client(struct device *dev) +{ + struct sht3x_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned char buf[SHT3X_RESPONSE_LENGTH]; + u16 val; + int ret; + + ret = sht3x_read_from_command(client, data, data->command, + buf, sizeof(buf), data->wait_time); + if (ret) + return ERR_PTR(ret); + + val = be16_to_cpup((__be16 *) buf); + data->temperature = sht3x_extract_temperature(val); + val = be16_to_cpup((__be16 *) (buf + 3)); + data->humidity = sht3x_extract_humidity(val); + + return data; +} + +static ssize_t temp1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sht3x_data *data = sht3x_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->temperature); +} + +static ssize_t humidity1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sht3x_data *data = sht3x_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->humidity); +} + +static int alert_read_raw(struct device *dev, + struct device_attribute *attr, + char *buffer, int length) +{ + int ret; + u8 index; + const struct sht3x_alert_commands *commands; + struct sht3x_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + index = to_sensor_dev_attr(attr)->index; + commands = &alert_commands[index]; + + ret = sht3x_read_from_command(client, data, commands->read_command, + buffer, length, 0); + + return ret; +} + +static ssize_t temp1_alert_read(struct device *dev, + struct device_attribute *attr, + int *temperature) +{ + int ret; + u16 raw; + char buffer[SHT3X_RESPONSE_LENGTH]; + + ret = alert_read_raw(dev, attr, buffer, SHT3X_RESPONSE_LENGTH); + if (ret) + return ret; + + raw = be16_to_cpup((__be16 *) buffer); + + /* + * lower 9 bits are temperature MSB + */ + *temperature = sht3x_extract_temperature((raw & 0x01ff) << 7); + + return 0; +} + + +static ssize_t temp1_alert_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + int temperature; + + ret = temp1_alert_read(dev, attr, &temperature); + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%d\n", temperature); +} + + +static ssize_t humidity1_alert_read(struct device *dev, + struct device_attribute *attr, + u32 *humidity) +{ + int ret; + u16 raw; + char buffer[SHT3X_RESPONSE_LENGTH]; + + ret = alert_read_raw(dev, attr, buffer, SHT3X_RESPONSE_LENGTH); + if (ret) + return ret; + + raw = be16_to_cpup((__be16 *) buffer); + + /* + * top 7 bits are the humidity MSB, + */ + *humidity = sht3x_extract_humidity(raw & 0xfe00); + + return 0; +} + +static ssize_t humidity1_alert_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u32 humidity; + + ret = humidity1_alert_read(dev, attr, &humidity); + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%d\n", humidity); +} + +static size_t alert_store(struct device *dev, + struct device_attribute *attr, + size_t count, + int temperature, + u32 humidity) +{ + char buffer[SHT3X_CMD_LENGTH + SHT3X_WORD_LEN + SHT3X_CRC8_LEN]; + char *position = buffer; + int ret; + u16 raw; + u8 index; + struct sht3x_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + const struct sht3x_alert_commands *commands; + + index = to_sensor_dev_attr(attr)->index; + commands = &alert_commands[index]; + + memcpy(position, commands->write_command, SHT3X_CMD_LENGTH); + position += SHT3X_CMD_LENGTH; + /* + * ST = (T + 45) / 175 * 2^16 + * SRH = RH / 100 * 2^16 + * adapted for fixed point arithmetic and packed the same as + * in alert_show() + */ + raw = ((u32)(temperature + 45000) * 24543) >> (16 + 7); + raw |= (((u32)humidity * 42950) >> 16) & 0xfe00; + + *(__be16 *)(position) = cpu_to_be16(raw); + position += SHT3X_WORD_LEN; + *position = crc8(sht3x_crc8_table, + (position - SHT3X_WORD_LEN), + SHT3X_WORD_LEN, + SHT3X_CRC8_INIT); + + + ret = i2c_master_send(client, buffer, sizeof(buffer)); + if (ret != sizeof(buffer)) + return ret < 0 ? ret : -EIO; + + return count; + +} + +static ssize_t temp1_alert_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int temperature; + u32 humidity; + int ret; + + ret = kstrtoint(buf, 0, &temperature); + + if (ret) + return -EINVAL; + + temperature = clamp_val(temperature, -45000, 130000); + + /* + * use old humidity alert value since we can only update + * temperature and humidity alerts together + */ + ret = humidity1_alert_read(dev, attr, &humidity); + if (ret) + return ret; + + ret = alert_store(dev, attr, count, temperature, humidity); + + return ret; +} + +static ssize_t humidity1_alert_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int temperature; + u32 humidity; + int ret; + + ret = kstrtou32(buf, 0, &humidity); + + if (ret) + return -EINVAL; + + humidity = clamp_val(humidity, 0, 100000); + + /* + * use old temperature alert value since we can only update + * temperature and humidity alerts together + */ + ret = temp1_alert_read(dev, attr, &temperature); + if (ret) + return ret; + + ret = alert_store(dev, attr, count, temperature, humidity); + + return ret; +} + +static void sht3x_select_command(struct sht3x_data *data) +{ + /* + * In blocking mode (clock stretching mode) the I2C bus + * is blocked for other traffic, thus the call to i2c_master_recv() + * will wait until the data is ready. For non blocking mode, we + * have to wait ourselves. + */ + if (data->mode > 0) { + data->command = sht3x_cmd_measure_periodic_mode; + data->wait_time = 0; + } else if (data->setup.blocking_io) { + data->command = data->setup.high_precision ? + sht3x_cmd_measure_blocking_hpm : + sht3x_cmd_measure_blocking_lpm; + data->wait_time = 0; + } else { + if (data->setup.high_precision) { + data->command = sht3x_cmd_measure_nonblocking_hpm; + data->wait_time = SHT3X_NONBLOCKING_WAIT_TIME_HPM; + } else { + data->command = sht3x_cmd_measure_nonblocking_lpm; + data->wait_time = SHT3X_NONBLOCKING_WAIT_TIME_LPM; + } + } +} + +static ssize_t frequency_show(struct device *dev, + struct device_attribute *attr, + char *buf) { + struct sht3x_data *data = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", mode_to_frequency[data->mode]); +} + +static ssize_t frequency_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) { + u16 frequency; + int mode; + int ret; + const char *command; + struct sht3x_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + ret = kstrtou16(buf, 0, &frequency); + + if (ret) + return -EINVAL; + + mode = find_index(mode_to_frequency, sizeof(mode_to_frequency), + frequency); + + /* invalid frequency */ + if (mode < 0) + return -EINVAL; + + /* mode did not change */ + if (mode == data->mode) + return count; + + /* abort periodic measure */ + ret = i2c_master_send(client, sht3x_cmd_break, SHT3X_CMD_LENGTH); + if (ret != SHT3X_CMD_LENGTH) + return ret < 0 ? ret : -EIO; + data->mode = 0; + + if (mode > 0) { + if (data->setup.high_precision) + command = periodic_measure_commands_hpm[mode - 1]; + else + command = periodic_measure_commands_lpm[mode - 1]; + + /* select mode */ + ret = i2c_master_send(client, command, SHT3X_CMD_LENGTH); + if (ret != SHT3X_CMD_LENGTH) + return ret < 0 ? ret : -EIO; + } + + /* select mode and command */ + data->mode = mode; + sht3x_select_command(data); + + return count; +} + +static ssize_t soft_reset(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) { + int ret; + struct sht3x_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + /* break if in periodic measure mode */ + if (data->mode > 0) { + ret = i2c_master_send(client, sht3x_cmd_break, + SHT3X_CMD_LENGTH); + if (ret != SHT3X_CMD_LENGTH) + return ret < 0 ? ret : -EIO; + data->mode = 0; + } + + /* clear status register */ + ret = i2c_master_send(client, sht3x_cmd_clear_status_reg, + SHT3X_CMD_LENGTH); + if (ret != SHT3X_CMD_LENGTH) + return ret < 0 ? ret : -EIO; + + /* soft reset */ + ret = i2c_master_send(client, sht3x_cmd_soft_reset, SHT3X_CMD_LENGTH); + if (ret != SHT3X_CMD_LENGTH) + return ret < 0 ? ret : -EIO; + + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, temp1_input_show, NULL, 0); +static SENSOR_DEVICE_ATTR(humidity1_input, S_IRUGO, humidity1_input_show, + NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, temp1_alert_show, + temp1_alert_store, 0); +static SENSOR_DEVICE_ATTR(humidity1_max, S_IRUGO | S_IWUSR, + humidity1_alert_show, humidity1_alert_store, 0); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IRUGO | S_IWUSR, temp1_alert_show, + temp1_alert_store, 1); +static SENSOR_DEVICE_ATTR(humidity1_max_hyst, S_IRUGO | S_IWUSR, + humidity1_alert_show, humidity1_alert_store, 1); +static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, temp1_alert_show, + temp1_alert_store, 2); +static SENSOR_DEVICE_ATTR(humidity1_min, S_IRUGO | S_IWUSR, + humidity1_alert_show, humidity1_alert_store, 2); +static SENSOR_DEVICE_ATTR(temp1_min_hyst, S_IRUGO | S_IWUSR, + temp1_alert_show, temp1_alert_store, 3); +static SENSOR_DEVICE_ATTR(humidity1_min_hyst, S_IRUGO | S_IWUSR, + humidity1_alert_show, humidity1_alert_store, 3); +static SENSOR_DEVICE_ATTR(frequency, S_IRUGO | S_IWUSR, frequency_show, + frequency_store, 0); +static SENSOR_DEVICE_ATTR(soft_reset, S_IWUSR, NULL, soft_reset, 0); + +static struct attribute *sht3x_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_humidity1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_humidity1_max.dev_attr.attr, + &sensor_dev_attr_humidity1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_min_hyst.dev_attr.attr, + &sensor_dev_attr_humidity1_min.dev_attr.attr, + &sensor_dev_attr_humidity1_min_hyst.dev_attr.attr, + &sensor_dev_attr_frequency.dev_attr.attr, + &sensor_dev_attr_soft_reset.dev_attr.attr, + NULL +}; + +static struct attribute *sts3x_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(sht3x); +ATTRIBUTE_GROUPS(sts3x); + +static int sht3x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct sht3x_data *data; + struct device *hwmon_dev; + struct i2c_adapter *adap = client->adapter; + struct device *dev = &client->dev; + const struct attribute_group **attribute_groups; + + /* + * we require full i2c support since the sht3x uses multi-byte read and + * writes as well as multi-byte commands which are not supported by + * the smbus protocol + */ + if (!i2c_check_functionality(adap, I2C_FUNC_I2C)) + return -ENODEV; + + ret = i2c_master_send(client, sht3x_cmd_clear_status_reg, + SHT3X_CMD_LENGTH); + if (ret != SHT3X_CMD_LENGTH) + return ret < 0 ? ret : -ENODEV; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->setup.blocking_io = false; + data->setup.high_precision = true; + data->mode = 0; + data->client = client; + crc8_populate_msb(sht3x_crc8_table, SHT3X_CRC8_POLYNOMIAL); + + if (client->dev.platform_data) + data->setup = *(struct sht3x_platform_data *)dev->platform_data; + sht3x_select_command(data); + mutex_init(&data->update_lock); + + if (id->driver_data == SHT3X_ID_STS) + attribute_groups = sts3x_groups; + else + attribute_groups = sht3x_groups; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, + client->name, + data, attribute_groups); + + if (IS_ERR(hwmon_dev)) + dev_dbg(dev, "unable to register hwmon device\n"); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +/* device ID table */ +static const struct i2c_device_id sht3x_id[] = { + {"sht3x", SHT3X_ID_SHT}, + {"sts3x", SHT3X_ID_STS}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, sht3x_id); + +static struct i2c_driver sht3x_i2c_driver = { + .driver.name = "sht3x", + .probe = sht3x_probe, + .id_table = sht3x_id, +}; + +module_i2c_driver(sht3x_i2c_driver); + +MODULE_AUTHOR("David Frey "); +MODULE_AUTHOR("Pascal Sachs "); +MODULE_DESCRIPTION("Sensirion SHT3x humidity and temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/twl4030-madc-hwmon.c b/drivers/hwmon/twl4030-madc-hwmon.c index b5caf7f..8201173 100644 --- a/drivers/hwmon/twl4030-madc-hwmon.c +++ b/drivers/hwmon/twl4030-madc-hwmon.c @@ -25,6 +25,7 @@ */ #include #include +//FIXME this is used for clamp_val #include #include #include diff --git a/include/linux/platform_data/sht3x.h b/include/linux/platform_data/sht3x.h new file mode 100644 index 0000000..46af9a0 --- /dev/null +++ b/include/linux/platform_data/sht3x.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2015 Sensirion AG, Switzerland + * Author: David Frey + * Author: Pascal Sachs + * + * 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. + * + */ + +#ifndef __SHT3X_H_ +#define __SHT3X_H_ + +struct sht3x_platform_data { + bool blocking_io; + bool high_precision; +}; +#endif /* __SHT3X_H_ */