From patchwork Thu Dec 13 14:43:22 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andreas Brauchli X-Patchwork-Id: 10728795 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 2E88591E for ; Thu, 13 Dec 2018 14:44:26 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 1CB332B2E1 for ; Thu, 13 Dec 2018 14:44:26 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 0D8DE2C237; Thu, 13 Dec 2018 14:44:26 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.9 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id EC5242C237 for ; Thu, 13 Dec 2018 14:44:24 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728928AbeLMOoN (ORCPT ); Thu, 13 Dec 2018 09:44:13 -0500 Received: from mail-ed1-f66.google.com ([209.85.208.66]:42330 "EHLO mail-ed1-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727618AbeLMOoN (ORCPT ); Thu, 13 Dec 2018 09:44:13 -0500 Received: by mail-ed1-f66.google.com with SMTP id j6so2210129edp.9 for ; Thu, 13 Dec 2018 06:44:12 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=elementarea-net.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id; bh=9ENHyVN8bquqxl3FHVwLNcHPO91MhKyV7Sw3VUDBU9A=; b=u60LdoOpwKdGAgTkUYOywyEoEyNS+taSEyCX75doI0phW8ecbfmfwVpx4L8qF6dqEb HoluZi4HiNP+0ZkpV2bf0fyXamLRdFYZZOdcnfva9rnPLnXLULlZDrS/PhKpJJ0JfWu/ qkMTfjA0VL18yuQb/ErHLDyIehMf4F8mK7LBW8O0nmdx9fwNY05nE346ecJGSOCpF6Pf Qd7MiU6eYVfw1A6kQohJguQ16QGhJQs6y3AK7Lnc6r09VMWrQ8OLBsHsSJUIF40/3zR9 eVtk9Faa5I2ggF4y+pQ12HTpb9DgWbOSeEGWqh0ScqQwyIxG+DezXFVUhNJyyQdLh6lg MZxw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=9ENHyVN8bquqxl3FHVwLNcHPO91MhKyV7Sw3VUDBU9A=; b=QrkHQ7qAhToeUJdnZFQaZ4T2TvWWTmqM+jkkKlI9k86lGfeqVjS+4zkd/hziUM9dV7 tm6/Bp2/zjpe2a5bVmPkeUatqACtKwRyM8Xe8KcvYu/BwFiDeVVXGo3h6fv49RToWqBE oSlAtXsnf1k4cccXKtTawXxgYTUNoLGauDFSVeYmTXDtBEHuXgilDhJuPhJqbsVuQdO5 Imc1oYkgIDP5tSLre5pJCv37FXtTc2nNTamWI0oUC9vcKNOfBp2Zq2RVlVYKI4tzjEU8 /lAA3rGBrtxWhizMQyZUQBOhvgBBqkKaBdcazhcqVmPGZAHcm1f7GsL/cK/a3BuQJk9j M2hg== X-Gm-Message-State: AA+aEWbU2OOh2bXNf9klbNmerYruu6foXfEpeeyd+BVQIfDIe9lFM7LV e9zQeC8I0nP/9RnZJxi1rat9eg== X-Google-Smtp-Source: AFSGD/XZHyDeKxfrfgZNGX6JP4C9rjH65PJWphyxk9awPTZ23wRkdsvB0SOnazaMWp6Tunbk1AVTCQ== X-Received: by 2002:a50:a4b6:: with SMTP id w51mr22221370edb.48.1544712251315; Thu, 13 Dec 2018 06:44:11 -0800 (PST) Received: from ch-sta-l-li0012.sensirion.lokal ([209.206.26.1]) by smtp.gmail.com with ESMTPSA id q16sm656290eds.60.2018.12.13.06.44.10 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 13 Dec 2018 06:44:10 -0800 (PST) From: Andreas Brauchli X-Google-Original-From: Andreas Brauchli To: Jonathan Cameron , Hartmut Knaack , Lars-Peter Clausen , Peter Meerwald-Stadler Cc: linux-kernel@vger.kernel.org, linux-iio@vger.kernel.org, Andreas Brauchli Subject: [PATCH v3 1/3] iio: Add modifiers for ethanol and H2 gases Date: Thu, 13 Dec 2018 15:43:22 +0100 Message-Id: <20181213144323.24820-2-andreas.brauchli@sensirion.com> X-Mailer: git-send-email 2.17.1 Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Add ethanol and H2 gas modifiers: * IIO_MOD_ETHANOL * IIO_MOD_H2 Signed-off-by: Andreas Brauchli Acked-by: Matt Ranostay --- Documentation/ABI/testing/sysfs-bus-iio | 4 ++++ include/uapi/linux/iio/types.h | 2 ++ tools/iio/iio_event_monitor.c | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 8127a08e366d..6d64521bf565 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -1554,6 +1554,10 @@ What: /sys/bus/iio/devices/iio:deviceX/in_concentration_raw What: /sys/bus/iio/devices/iio:deviceX/in_concentrationX_raw What: /sys/bus/iio/devices/iio:deviceX/in_concentration_co2_raw What: /sys/bus/iio/devices/iio:deviceX/in_concentrationX_co2_raw +What: /sys/bus/iio/devices/iio:deviceX/in_concentration_ethanol_raw +What: /sys/bus/iio/devices/iio:deviceX/in_concentrationX_ethanol_raw +What: /sys/bus/iio/devices/iio:deviceX/in_concentration_h2_raw +What: /sys/bus/iio/devices/iio:deviceX/in_concentrationX_h2_raw What: /sys/bus/iio/devices/iio:deviceX/in_concentration_voc_raw What: /sys/bus/iio/devices/iio:deviceX/in_concentrationX_voc_raw KernelVersion: 4.3 diff --git a/include/uapi/linux/iio/types.h b/include/uapi/linux/iio/types.h index 92baabc103ac..04a6649fa442 100644 --- a/include/uapi/linux/iio/types.h +++ b/include/uapi/linux/iio/types.h @@ -87,6 +87,8 @@ enum iio_modifier { IIO_MOD_VOC, IIO_MOD_LIGHT_UV, IIO_MOD_LIGHT_DUV, + IIO_MOD_ETHANOL, + IIO_MOD_H2, }; enum iio_event_type { diff --git a/tools/iio/iio_event_monitor.c b/tools/iio/iio_event_monitor.c index ac2de6b7e89f..f29c885f4b28 100644 --- a/tools/iio/iio_event_monitor.c +++ b/tools/iio/iio_event_monitor.c @@ -114,6 +114,8 @@ static const char * const iio_modifier_names[] = { [IIO_MOD_I] = "i", [IIO_MOD_Q] = "q", [IIO_MOD_CO2] = "co2", + [IIO_MOD_ETHANOL] = "ethanol", + [IIO_MOD_H2] = "h2", [IIO_MOD_VOC] = "voc", }; @@ -199,6 +201,8 @@ static bool event_is_known(struct iio_event_data *event) case IIO_MOD_I: case IIO_MOD_Q: case IIO_MOD_CO2: + case IIO_MOD_ETHANOL: + case IIO_MOD_H2: case IIO_MOD_VOC: break; default: From patchwork Thu Dec 13 14:43:23 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andreas Brauchli X-Patchwork-Id: 10728793 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 1F7E291E for ; Thu, 13 Dec 2018 14:44:19 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 0C3002B2E1 for ; Thu, 13 Dec 2018 14:44:19 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 001AC2C237; Thu, 13 Dec 2018 14:44:18 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.9 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id A48772B2E1 for ; Thu, 13 Dec 2018 14:44:17 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727791AbeLMOoQ (ORCPT ); Thu, 13 Dec 2018 09:44:16 -0500 Received: from mail-ed1-f65.google.com ([209.85.208.65]:34109 "EHLO mail-ed1-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728985AbeLMOoQ (ORCPT ); Thu, 13 Dec 2018 09:44:16 -0500 Received: by mail-ed1-f65.google.com with SMTP id b3so2252924ede.1 for ; Thu, 13 Dec 2018 06:44:14 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=elementarea-net.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id; bh=eRFmoBUAYnKx6Ib0N4HHeZ8G2BNcGuJoE8hxRu8bGfo=; b=zkl2lfN4zzUX2w1WWnJnjxIMxBwOLJbwvtigGzaamULivDSbA2FHO8rLV5LlTstACX GLr0TAkY9jKz7ZV48jBl1SEIbRAnj6tuoNN0lns+/e3J/Oxp50sqvPaI/9nEhhF/5CMo DqeGz00QOlaqYRFIpyrNyiPxlvlkPCx+6pdTBP150dsri34kWoxhqf7zsoOmd4H/t5N3 yvt7GHD2z9Titv3O0g8WDO0T59WuL5cEmjV4rVRt/dqT0BKpS+YQpFnoVsahUD2xVCN2 psw0Ji47M8YxX9a+U2TmVawu7TiRVMVc8zZYqOV45fKGa+a5wrjpdqJ26b/dYOeI6S/b xvTA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=eRFmoBUAYnKx6Ib0N4HHeZ8G2BNcGuJoE8hxRu8bGfo=; b=H9G5HVz/euXcYcKXFAM8tSMz4zoPQ6aHeNoKTux2iisolBNRyNlqJKPjeYiHAcpM1W qf5KHuPoRxyTRj6kFiY9uGnuBE1NhqiRuboJoRJo6039Xu81P8EaY7tlJntbq4/Vx+mr eMGnGSNr7dh78BGwzQwBqJySfbzVei9Fd5IPEsViu6VX4u8Je6y9LM3yyWOYrdihGG/5 PxkPg6id24d2MWes5ThoENZKJ1jxb70/0dI8F6hT+lu1UQkP2C5kyZIZF1oaTwatK1bk Syti65smv7e8hUyfukbJUAoVsTNIBG5PXqN9Vn/QLp+1YmHQw+mwhIYGReHcdDqaZQJZ M0bA== X-Gm-Message-State: AA+aEWaFHpmkZrVUkObTUli+ThES9V3OKSn2aNy2kFJ0FwWV5M2zREuk J1N/n9Pjynil2XPD46Ncx7p58A== X-Google-Smtp-Source: AFSGD/XF2UgWqaS1IBaKmC6RpUhbISB11DmT3kvDwOEBfInQiZTyeFn27E1QWsdampTtE1ksrGY+IQ== X-Received: by 2002:aa7:c352:: with SMTP id j18mr22655633edr.295.1544712253346; Thu, 13 Dec 2018 06:44:13 -0800 (PST) Received: from ch-sta-l-li0012.sensirion.lokal ([209.206.26.1]) by smtp.gmail.com with ESMTPSA id q16sm656290eds.60.2018.12.13.06.44.12 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 13 Dec 2018 06:44:12 -0800 (PST) From: Andreas Brauchli X-Google-Original-From: Andreas Brauchli To: Jonathan Cameron , Hartmut Knaack , Lars-Peter Clausen , Peter Meerwald-Stadler Cc: linux-kernel@vger.kernel.org, linux-iio@vger.kernel.org, Andreas Brauchli Subject: [PATCH v3 2/3] iio: chemical: sgp30: Support Sensirion SGP30/SGPC3 sensors Date: Thu, 13 Dec 2018 15:43:23 +0100 Message-Id: <20181213144323.24820-3-andreas.brauchli@sensirion.com> X-Mailer: git-send-email 2.17.1 Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Support Sensirion SGP30 and SGPC3 multi-pixel I2C gas sensors Supported Features: * Indoor Air Quality (IAQ) concentrations for - tVOC (in_concentration_voc_input) - CO2eq (in_concentration_co2_input) - SGP30 only IAQ concentrations are periodically read out by a background thread to allow the sensor to maintain its internal baseline. * Gas concentration signals - Ethanol (in_concentration_ethanol_raw) - H2 (in_concentration_h2_raw) - SGP30 only https://www.sensirion.com/file/datasheet_sgp30 https://www.sensirion.com/file/datasheet_sgpc3 Signed-off-by: Andreas Brauchli Reviewed-by: Andreas Brauchli --- drivers/iio/chemical/Kconfig | 13 + drivers/iio/chemical/Makefile | 1 + drivers/iio/chemical/sgp30.c | 603 ++++++++++++++++++++++++++++++++++ 3 files changed, 617 insertions(+) create mode 100644 drivers/iio/chemical/sgp30.c diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig index b8e005be4f87..b0c912be94a9 100644 --- a/drivers/iio/chemical/Kconfig +++ b/drivers/iio/chemical/Kconfig @@ -61,6 +61,19 @@ config IAQCORE iAQ-Core Continuous/Pulsed VOC (Volatile Organic Compounds) sensors +config SENSIRION_SGP30 + tristate "Sensirion SGPxx gas sensors" + depends on I2C + select CRC8 + help + Say Y here to build I2C interface support for the following + Sensirion SGP gas sensors: + * SGP30 gas sensor + * SGPC3 low power gas sensor + + To compile this driver as module, choose M here: the + module will be called sgp30. + config VZ89X tristate "SGX Sensortech MiCS VZ89X VOC sensor" depends on I2C diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile index 2f4c4ba4d781..61e83944db1a 100644 --- a/drivers/iio/chemical/Makefile +++ b/drivers/iio/chemical/Makefile @@ -9,4 +9,5 @@ obj-$(CONFIG_BME680_I2C) += bme680_i2c.o obj-$(CONFIG_BME680_SPI) += bme680_spi.o obj-$(CONFIG_CCS811) += ccs811.o obj-$(CONFIG_IAQCORE) += ams-iaq-core.o +obj-$(CONFIG_SENSIRION_SGP30) += sgp30.o obj-$(CONFIG_VZ89X) += vz89x.o diff --git a/drivers/iio/chemical/sgp30.c b/drivers/iio/chemical/sgp30.c new file mode 100644 index 000000000000..90eb4cbb18cf --- /dev/null +++ b/drivers/iio/chemical/sgp30.c @@ -0,0 +1,603 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * sgp30.c - Support for Sensirion SGP Gas Sensors + * + * Copyright (C) 2018 Andreas Brauchli + * + * I2C slave address: 0x58 + * + * Datasheets: + * https://www.sensirion.com/file/datasheet_sgp30 + * https://www.sensirion.com/file/datasheet_sgpc3 + * + * TODO: + * - baseline support + * - humidity compensation + * - power mode switching (SGPC3) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SGP_WORD_LEN 2 +#define SGP_CRC8_POLYNOMIAL 0x31 +#define SGP_CRC8_INIT 0xff +#define SGP_CRC8_LEN 1 +#define SGP_CMD(cmd_word) cpu_to_be16(cmd_word) +#define SGP_CMD_DURATION_US 12000 +#define SGP_MEASUREMENT_DURATION_US 50000 +#define SGP_CMD_LEN SGP_WORD_LEN +#define SGP_CMD_MAX_BUF_SIZE (SGP_CMD_LEN + 2 * SGP_WORD_LEN) +#define SGP_MEASUREMENT_LEN 2 +#define SGP30_MEASURE_INTERVAL_HZ 1 +#define SGPC3_MEASURE_INTERVAL_HZ 2 +#define SGP_VERS_PRODUCT(data) ((((data)->feature_set) & 0xf000) >> 12) +#define SGP_VERS_RESERVED(data) ((((data)->feature_set) & 0x0800) >> 11) +#define SGP_VERS_GEN(data) ((((data)->feature_set) & 0x0600) >> 9) +#define SGP_VERS_ENG_BIT(data) ((((data)->feature_set) & 0x0100) >> 8) +#define SGP_VERS_MAJOR(data) ((((data)->feature_set) & 0x00e0) >> 5) +#define SGP_VERS_MINOR(data) (((data)->feature_set) & 0x001f) + +DECLARE_CRC8_TABLE(sgp_crc8_table); + +enum sgp_product_id { + SGP30 = 0, + SGPC3, +}; + +enum sgp30_channel_idx { + SGP30_IAQ_TVOC_IDX = 0, + SGP30_IAQ_CO2EQ_IDX, + SGP30_SIG_ETOH_IDX, + SGP30_SIG_H2_IDX, +}; + +enum sgpc3_channel_idx { + SGPC3_IAQ_TVOC_IDX = 10, + SGPC3_SIG_ETOH_IDX, +}; + +enum sgp_cmd { + SGP_CMD_IAQ_INIT = SGP_CMD(0x2003), + SGP_CMD_IAQ_MEASURE = SGP_CMD(0x2008), + SGP_CMD_GET_FEATURE_SET = SGP_CMD(0x202f), + SGP_CMD_GET_SERIAL_ID = SGP_CMD(0x3682), + + SGP30_CMD_MEASURE_SIGNAL = SGP_CMD(0x2050), + + SGPC3_CMD_MEASURE_RAW = SGP_CMD(0x2046), +}; + +struct sgp_version { + u8 major; + u8 minor; +}; + +struct sgp_crc_word { + __be16 value; + u8 crc8; +} __attribute__((__packed__)); + +union sgp_reading { + u8 start; + struct sgp_crc_word raw_words[4]; +}; + +enum _iaq_buffer_state { + IAQ_BUFFER_EMPTY = 0, + IAQ_BUFFER_DEFAULT_VALS, + IAQ_BUFFER_VALID, +}; + +struct sgp_data { + struct i2c_client *client; + struct task_struct *iaq_thread; + struct mutex data_lock; + unsigned long iaq_init_start_jiffies; + unsigned long iaq_defval_skip_jiffies; + u16 product_id; + u16 feature_set; + unsigned long measure_interval_jiffies; + enum sgp_cmd iaq_init_cmd; + enum sgp_cmd measure_iaq_cmd; + enum sgp_cmd measure_gas_signals_cmd; + union sgp_reading buffer; + union sgp_reading iaq_buffer; + enum _iaq_buffer_state iaq_buffer_state; +}; + +struct sgp_device { + const struct iio_chan_spec *channels; + int num_channels; +}; + +static const struct sgp_version supported_versions_sgp30[] = { + { + .major = 1, + .minor = 0, + }, +}; + +static const struct sgp_version supported_versions_sgpc3[] = { + { + .major = 0, + .minor = 4, + }, +}; + +static const struct iio_chan_spec sgp30_channels[] = { + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_VOC, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = SGP30_IAQ_TVOC_IDX, + }, + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_CO2, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = SGP30_IAQ_CO2EQ_IDX, + }, + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_ETHANOL, + .modified = 1, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW), + .address = SGP30_SIG_ETOH_IDX, + .scan_type = { + .endianness = IIO_BE, + }, + }, + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_H2, + .modified = 1, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW), + .address = SGP30_SIG_H2_IDX, + .scan_type = { + .endianness = IIO_BE, + }, + }, +}; + +static const struct iio_chan_spec sgpc3_channels[] = { + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_VOC, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = SGPC3_IAQ_TVOC_IDX, + }, + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_ETHANOL, + .modified = 1, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW), + .address = SGPC3_SIG_ETOH_IDX, + .scan_type = { + .endianness = IIO_BE, + }, + }, +}; + +static const struct sgp_device sgp_devices[] = { + [SGP30] = { + .channels = sgp30_channels, + .num_channels = ARRAY_SIZE(sgp30_channels), + }, + [SGPC3] = { + .channels = sgpc3_channels, + .num_channels = ARRAY_SIZE(sgpc3_channels), + }, +}; + +/** + * sgp_verify_buffer() - verify the checksums of the data buffer words + * + * @data: SGP data + * @buf: Raw data buffer + * @word_count: Num data words stored in the buffer, excluding CRC bytes + * + * Return: 0 on success, negative error otherwise. + */ +static int sgp_verify_buffer(const struct sgp_data *data, + union sgp_reading *buf, size_t word_count) +{ + size_t size = word_count * (SGP_WORD_LEN + SGP_CRC8_LEN); + int i; + u8 crc; + u8 *data_buf = &buf->start; + + for (i = 0; i < size; i += SGP_WORD_LEN + SGP_CRC8_LEN) { + crc = crc8(sgp_crc8_table, &data_buf[i], SGP_WORD_LEN, + SGP_CRC8_INIT); + if (crc != data_buf[i + SGP_WORD_LEN]) { + dev_err(&data->client->dev, "CRC error\n"); + return -EIO; + } + } + + return 0; +} + +/** + * sgp_read_cmd() - reads data from sensor after issuing a command + * The caller must hold data->data_lock for the duration of the call. + * @data: SGP data + * @cmd: SGP Command to issue + * @buf: Raw data buffer to use + * @word_count: Num words to read, excluding CRC bytes + * + * Return: 0 on success, negative error otherwise. + */ +static int sgp_read_cmd(struct sgp_data *data, enum sgp_cmd cmd, + union sgp_reading *buf, size_t word_count, + unsigned long duration_us) +{ + int ret; + struct i2c_client *client = data->client; + size_t size = word_count * (SGP_WORD_LEN + SGP_CRC8_LEN); + u8 *data_buf; + + ret = i2c_master_send(client, (const char *)&cmd, SGP_CMD_LEN); + if (ret != SGP_CMD_LEN) + return -EIO; + usleep_range(duration_us, duration_us + 1000); + + if (word_count == 0) + return 0; + + data_buf = &buf->start; + ret = i2c_master_recv(client, data_buf, size); + if (ret < 0) + return ret; + if (ret != size) + return -EIO; + + return sgp_verify_buffer(data, buf, word_count); +} + +/** + * sgp_measure_iaq() - measure and retrieve IAQ values from sensor + * The caller must hold data->data_lock for the duration of the call. + * @data: SGP data + * + * Return: 0 on success, -EBUSY on default values, negative error + * otherwise. + */ + +static int sgp_measure_iaq(struct sgp_data *data) +{ + int ret; + /* data contains default values */ + bool default_vals = !time_after(jiffies, data->iaq_init_start_jiffies + + data->iaq_defval_skip_jiffies); + + ret = sgp_read_cmd(data, data->measure_iaq_cmd, &data->iaq_buffer, + SGP_MEASUREMENT_LEN, SGP_MEASUREMENT_DURATION_US); + if (ret < 0) + return ret; + + data->iaq_buffer_state = IAQ_BUFFER_DEFAULT_VALS; + + if (default_vals) + return -EBUSY; + + data->iaq_buffer_state = IAQ_BUFFER_VALID; + + return 0; +} + +static void sgp_iaq_thread_sleep_until(const struct sgp_data *data, + unsigned long sleep_jiffies) +{ + const long IAQ_POLL = 50000; + + while (!time_after(jiffies, sleep_jiffies)) { + usleep_range(IAQ_POLL, IAQ_POLL + 10000); + if (kthread_should_stop() || data->iaq_init_start_jiffies == 0) + return; + } +} + +static int sgp_iaq_threadfn(void *p) +{ + struct sgp_data *data = (struct sgp_data *)p; + unsigned long next_update_jiffies; + int ret; + + while (!kthread_should_stop()) { + mutex_lock(&data->data_lock); + if (data->iaq_init_start_jiffies == 0) { + ret = sgp_read_cmd(data, data->iaq_init_cmd, NULL, 0, + SGP_CMD_DURATION_US); + if (ret < 0) + goto unlock_sleep_continue; + data->iaq_init_start_jiffies = jiffies; + } + + ret = sgp_measure_iaq(data); + if (ret && ret != -EBUSY) { + dev_warn(&data->client->dev, + "IAQ measurement error [%d]\n", ret); + } +unlock_sleep_continue: + next_update_jiffies = jiffies + data->measure_interval_jiffies; + mutex_unlock(&data->data_lock); + sgp_iaq_thread_sleep_until(data, next_update_jiffies); + } + + return 0; +} + +static int sgp_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct sgp_data *data = iio_priv(indio_dev); + struct sgp_crc_word *words; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + mutex_lock(&data->data_lock); + if (data->iaq_buffer_state != IAQ_BUFFER_VALID) { + mutex_unlock(&data->data_lock); + return -EBUSY; + } + words = data->iaq_buffer.raw_words; + switch (chan->address) { + case SGP30_IAQ_TVOC_IDX: + case SGPC3_IAQ_TVOC_IDX: + *val = 0; + *val2 = be16_to_cpu(words[1].value); + ret = IIO_VAL_INT_PLUS_NANO; + break; + case SGP30_IAQ_CO2EQ_IDX: + *val = 0; + *val2 = be16_to_cpu(words[0].value); + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + break; + } + mutex_unlock(&data->data_lock); + break; + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->data_lock); + if (chan->address == SGPC3_SIG_ETOH_IDX) { + if (data->iaq_buffer_state == IAQ_BUFFER_EMPTY) + ret = -EBUSY; + else + ret = 0; + words = data->iaq_buffer.raw_words; + } else { + ret = sgp_read_cmd(data, data->measure_gas_signals_cmd, + &data->buffer, SGP_MEASUREMENT_LEN, + SGP_MEASUREMENT_DURATION_US); + words = data->buffer.raw_words; + } + if (ret) { + mutex_unlock(&data->data_lock); + return ret; + } + + switch (chan->address) { + case SGP30_SIG_ETOH_IDX: + *val = be16_to_cpu(words[1].value); + ret = IIO_VAL_INT; + break; + case SGPC3_SIG_ETOH_IDX: + case SGP30_SIG_H2_IDX: + *val = be16_to_cpu(words[0].value); + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + mutex_unlock(&data->data_lock); + break; + default: + return -EINVAL; + } + + return ret; +} + +static int sgp_check_compat(struct sgp_data *data, + unsigned int product_id) +{ + const struct sgp_version *supported_versions; + u16 ix, num_fs; + u16 product, generation, major, minor; + + /* driver does not match product */ + generation = SGP_VERS_GEN(data); + if (generation != 0) { + dev_err(&data->client->dev, + "incompatible product generation %d != 0", generation); + return -ENODEV; + } + + product = SGP_VERS_PRODUCT(data); + if (product != product_id) { + dev_err(&data->client->dev, + "sensor reports a different product: 0x%04hx\n", + product); + return -ENODEV; + } + + if (SGP_VERS_RESERVED(data)) + dev_warn(&data->client->dev, "reserved bit is set\n"); + + /* engineering samples are not supported: no interface guarantees */ + if (SGP_VERS_ENG_BIT(data)) + return -ENODEV; + + switch (product) { + case SGP30: + supported_versions = supported_versions_sgp30; + num_fs = ARRAY_SIZE(supported_versions_sgp30); + break; + case SGPC3: + supported_versions = supported_versions_sgpc3; + num_fs = ARRAY_SIZE(supported_versions_sgpc3); + break; + default: + return -ENODEV; + } + + major = SGP_VERS_MAJOR(data); + minor = SGP_VERS_MINOR(data); + for (ix = 0; ix < num_fs; ix++) { + if (major == supported_versions[ix].major && + minor >= supported_versions[ix].minor) + return 0; + } + dev_err(&data->client->dev, "unsupported sgp version: %d.%d\n", + major, minor); + + return -ENODEV; +} + +static void sgp_init(struct sgp_data *data) +{ + data->iaq_init_cmd = SGP_CMD_IAQ_INIT; + data->iaq_init_start_jiffies = 0; + data->iaq_buffer_state = IAQ_BUFFER_EMPTY; + switch (SGP_VERS_PRODUCT(data)) { + case SGP30: + data->measure_interval_jiffies = SGP30_MEASURE_INTERVAL_HZ * HZ; + data->measure_iaq_cmd = SGP_CMD_IAQ_MEASURE; + data->measure_gas_signals_cmd = SGP30_CMD_MEASURE_SIGNAL; + data->product_id = SGP30; + data->iaq_defval_skip_jiffies = 15 * HZ; + break; + case SGPC3: + data->measure_interval_jiffies = SGPC3_MEASURE_INTERVAL_HZ * HZ; + data->measure_iaq_cmd = SGPC3_CMD_MEASURE_RAW; + data->measure_gas_signals_cmd = SGPC3_CMD_MEASURE_RAW; + data->product_id = SGPC3; + data->iaq_defval_skip_jiffies = + 43 * data->measure_interval_jiffies; + break; + }; +} + +static const struct iio_info sgp_info = { + .read_raw = sgp_read_raw, +}; + +static const struct of_device_id sgp_dt_ids[] = { + { .compatible = "sensirion,sgp30", .data = (void *)SGP30 }, + { .compatible = "sensirion,sgpc3", .data = (void *)SGPC3 }, + { } +}; + +static int sgp_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct sgp_data *data; + const struct of_device_id *of_id; + unsigned long product_id; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + of_id = of_match_device(sgp_dt_ids, &client->dev); + if (of_id) + product_id = (unsigned long)of_id->data; + else + product_id = id->driver_data; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + crc8_populate_msb(sgp_crc8_table, SGP_CRC8_POLYNOMIAL); + mutex_init(&data->data_lock); + + /* get feature set version and write it to client data */ + ret = sgp_read_cmd(data, SGP_CMD_GET_FEATURE_SET, &data->buffer, 1, + SGP_CMD_DURATION_US); + if (ret < 0) + return ret; + + data->feature_set = be16_to_cpu(data->buffer.raw_words[0].value); + + ret = sgp_check_compat(data, product_id); + if (ret) + return ret; + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &sgp_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = sgp_devices[product_id].channels; + indio_dev->num_channels = sgp_devices[product_id].num_channels; + + sgp_init(data); + + ret = devm_iio_device_register(&client->dev, indio_dev); + if (ret) { + dev_err(&client->dev, "failed to register iio device\n"); + return ret; + } + + data->iaq_thread = kthread_run(sgp_iaq_threadfn, data, + "%s-iaq", data->client->name); + + return 0; +} + +static int sgp_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct sgp_data *data = iio_priv(indio_dev); + + if (data->iaq_thread) + kthread_stop(data->iaq_thread); + + return 0; +} + +static const struct i2c_device_id sgp_id[] = { + { "sgp30", SGP30 }, + { "sgpc3", SGPC3 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, sgp_id); +MODULE_DEVICE_TABLE(of, sgp_dt_ids); + +static struct i2c_driver sgp_driver = { + .driver = { + .name = "sgp30", + .of_match_table = of_match_ptr(sgp_dt_ids), + }, + .probe = sgp_probe, + .remove = sgp_remove, + .id_table = sgp_id, +}; +module_i2c_driver(sgp_driver); + +MODULE_AUTHOR("Andreas Brauchli "); +MODULE_AUTHOR("Pascal Sachs "); +MODULE_DESCRIPTION("Sensirion SGP gas sensors"); +MODULE_LICENSE("GPL v2"); From patchwork Thu Dec 13 14:44:18 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andreas Brauchli X-Patchwork-Id: 10728797 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id AED3E112E for ; Thu, 13 Dec 2018 14:44:31 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 9CFFF2C237 for ; Thu, 13 Dec 2018 14:44:31 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 90DAD2C2DF; Thu, 13 Dec 2018 14:44:31 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.9 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 42BDB2C237 for ; Thu, 13 Dec 2018 14:44:31 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728033AbeLMOoZ (ORCPT ); Thu, 13 Dec 2018 09:44:25 -0500 Received: from mail-ed1-f68.google.com ([209.85.208.68]:42348 "EHLO mail-ed1-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729031AbeLMOoZ (ORCPT ); Thu, 13 Dec 2018 09:44:25 -0500 Received: by mail-ed1-f68.google.com with SMTP id j6so2210629edp.9 for ; Thu, 13 Dec 2018 06:44:24 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=elementarea-net.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id; bh=sk7VSPsVfgFjKWt+SwLUYCsEgXBvRweH1J/0uUlmL+g=; b=i7jNemsKibDCPkzcm1wYCpQOuvYbN6U7EaeERh0gqlZQOJZlotvNV4ey7Bd9lQ3u1m QyxSvLcnkKCuVtaDy75JjC67h1uoEU4qtHsjozNOwM08LpzCaEGNw3hapF8H5B/u36WB KnkK3c1N7g2VSTf4lDEmh0Tukco4Qjc9VSbOyl65MZDL1w73iqSSM/sgvQirpBCzx4gL sIjy1lLtjsGUg9CeSejLXaIVo0lULeJ0yWa6i6U0dW/roqgk+T4VYn5z8a9P7kaqKbpP J3khK/1l/NfrE+8xtKaIW36swNLHznCwdK4eAx1kLe7uqgnG3y7vevBT+1Ph+A0VBROk n+rg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=sk7VSPsVfgFjKWt+SwLUYCsEgXBvRweH1J/0uUlmL+g=; b=lpAVg/SEKBeDEfiqHUSZR9JKMxTY9LdaV7GYyIumi/g4DGlTeHMqd+IV7HYxAWSor9 qI+8uet5rc2VYGkeJ52TS3F8jjGKq2ZyPorKjNt/ftRcHVLh8Qrih5KUxkcmeci7ocFO yFyYelKdlNgz4qPZf7Fh5nBB95wDhsRTx1qKcw/98eyAzUa/+SbbpfSc80sQ76COpLlK mlArdTEbTehLV8hXPAGE9dBwI+Ua19iBNzRhzUQuauet9ukBlIDCXB9C1KVqtihsoQ2a 2zC0+jMErFXS6grtQI55G+SKojFwSeFgQ+ST4HcMwz/ezM8414dkqJFYWIpbDknBISfz wClQ== X-Gm-Message-State: AA+aEWZF532dLtxo5g6gIg7AMJAUQf0vZQf9y7rg0SZafeI2agYy/Mov d+9DFockGs4s/CQz1E4oVwTitg== X-Google-Smtp-Source: AFSGD/UBorbcbCXzDdCylHLZJJQEYPVz0+FXfIFnK0a+34sYScFFvoz+a8t6Jbd92fV0GwipvDqEIQ== X-Received: by 2002:a50:d797:: with SMTP id w23mr23531264edi.19.1544712263833; Thu, 13 Dec 2018 06:44:23 -0800 (PST) Received: from ch-sta-l-li0012.sensirion.lokal ([209.206.26.1]) by smtp.gmail.com with ESMTPSA id p36sm674842edc.78.2018.12.13.06.44.22 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 13 Dec 2018 06:44:23 -0800 (PST) From: Andreas Brauchli X-Google-Original-From: Andreas Brauchli To: Jonathan Cameron , Hartmut Knaack , Lars-Peter Clausen , Peter Meerwald-Stadler , Rob Herring , Mark Rutland Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Andreas Brauchli Subject: [PATCH v3 3/3] iio: chemical: sgp30: add device tree support Date: Thu, 13 Dec 2018 15:44:18 +0100 Message-Id: <20181213144418.24965-1-andreas.brauchli@sensirion.com> X-Mailer: git-send-email 2.17.1 Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Add device tree support for Sensirion SGP30/SGPC3 gas sensors. Signed-off-by: Andreas Brauchli Reviewed-by: Rob Herring --- .../bindings/iio/chemical/sensirion,sgp30.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/chemical/sensirion,sgp30.txt diff --git a/Documentation/devicetree/bindings/iio/chemical/sensirion,sgp30.txt b/Documentation/devicetree/bindings/iio/chemical/sensirion,sgp30.txt new file mode 100644 index 000000000000..5844ed58173c --- /dev/null +++ b/Documentation/devicetree/bindings/iio/chemical/sensirion,sgp30.txt @@ -0,0 +1,15 @@ +* Sensirion SGP30/SGPC3 multi-pixel Gas Sensor + +Required properties: + + - compatible: must be one of + "sensirion,sgp30" + "sensirion,sgpc3" + - reg: the I2C address of the sensor + +Example: + +gas@58 { + compatible = "sensirion,sgp30"; + reg = <0x58>; +};