From 17abc9ec68b73ddeb262a507a62421016b9c54d5 Mon Sep 17 00:00:00 2001 From: Tomasz Duszynski Date: Fri, 14 Dec 2018 19:28:01 +0100 Subject: iio: add IIO_MASSCONCENTRATION channel type Measuring particulate matter in ug / m3 (micro-grams per cubic meter) is de facto standard. Existing air quality sensors usually follow this convention and are capable of returning measurements using this unit. IIO currently does not offer suitable channel type for this type of measurements hence this patch adds this. In addition, extra modifiers are introduced used for distinguishing between fine pm1, pm2p5 and coarse pm4, pm10 particle measurements, i.e IIO_MOD_PM1, IIO_MOD_PM25 and IIO_MOD_PM4, IIO_MOD_PM10. pmX consists of particles with aerodynamic diameter less or equal to X micrometers. Signed-off-by: Tomasz Duszynski Acked-by: Matt Ranostay Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 8127a08e366d..67fd88bf7910 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -1684,4 +1684,19 @@ KernelVersion: 4.18 Contact: linux-iio@vger.kernel.org Description: Raw (unscaled) phase difference reading from channel Y - that can be processed to radians. \ No newline at end of file + that can be processed to radians. + +What: /sys/bus/iio/devices/iio:deviceX/in_massconcentration_pm1_input +What: /sys/bus/iio/devices/iio:deviceX/in_massconcentrationY_pm1_input +What: /sys/bus/iio/devices/iio:deviceX/in_massconcentration_pm2p5_input +What: /sys/bus/iio/devices/iio:deviceX/in_massconcentrationY_pm2p5_input +What: /sys/bus/iio/devices/iio:deviceX/in_massconcentration_pm4_input +What: /sys/bus/iio/devices/iio:deviceX/in_massconcentrationY_pm4_input +What: /sys/bus/iio/devices/iio:deviceX/in_massconcentration_pm10_input +What: /sys/bus/iio/devices/iio:deviceX/in_massconcentrationY_pm10_input +KernelVersion: 4.22 +Contact: linux-iio@vger.kernel.org +Description: + Mass concentration reading of particulate matter in ug / m3. + pmX consists of particles with aerodynamic diameter less or + equal to X micrometers. -- cgit v1.2.3 From b170f7d48443d1ea3e4ffbf409025b5e5b1146fe Mon Sep 17 00:00:00 2001 From: Andreas Brauchli Date: Thu, 13 Dec 2018 15:43:22 +0100 Subject: iio: Add modifiers for ethanol and H2 gases Add ethanol and H2 gas modifiers: * IIO_MOD_ETHANOL * IIO_MOD_H2 Signed-off-by: Andreas Brauchli Acked-by: Matt Ranostay Signed-off-by: Jonathan Cameron --- 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(+) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 67fd88bf7910..864f8efd12e5 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 c59adac24b1c..fdd81affca4b 100644 --- a/include/uapi/linux/iio/types.h +++ b/include/uapi/linux/iio/types.h @@ -92,6 +92,8 @@ enum iio_modifier { IIO_MOD_PM2P5, IIO_MOD_PM4, IIO_MOD_PM10, + 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 f6b8003fbe3c..7bf9bde28bcc 100644 --- a/tools/iio/iio_event_monitor.c +++ b/tools/iio/iio_event_monitor.c @@ -115,6 +115,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", [IIO_MOD_PM1] = "pm1", [IIO_MOD_PM2P5] = "pm2p5", @@ -205,6 +207,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: case IIO_MOD_PM1: case IIO_MOD_PM2P5: -- cgit v1.2.3 From c546d49656143855093c7b7fde60866e6e23a69d Mon Sep 17 00:00:00 2001 From: Tomasz Duszynski Date: Tue, 18 Dec 2018 21:28:09 +0100 Subject: iio: chemical: sps30: add support for self cleaning Self cleaning is especially useful in cases where sensor undergoes frequent power on/off cycles. In such scenarios it is recommended to turn self cleaning at least once per week in order to maintain reliable measurements. Self cleaning is activated by writing 1 to a dedicated attribute. Internal fan accelerates to its maximum speed and keeps spinning for about 10 seconds blowing out accumulated dust. Signed-off-by: Tomasz Duszynski Tested-by: Andreas Brauchli Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio-sps30 | 8 ++++++ drivers/iio/chemical/sps30.c | 35 ++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-sps30 (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-iio-sps30 b/Documentation/ABI/testing/sysfs-bus-iio-sps30 new file mode 100644 index 000000000000..e7ce2c57635e --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-sps30 @@ -0,0 +1,8 @@ +What: /sys/bus/iio/devices/iio:deviceX/start_cleaning +Date: December 2018 +KernelVersion: 4.22 +Contact: linux-iio@vger.kernel.org +Description: + Writing 1 starts sensor self cleaning. Internal fan accelerates + to its maximum speed and keeps spinning for about 10 seconds in + order to blow out accumulated dust. diff --git a/drivers/iio/chemical/sps30.c b/drivers/iio/chemical/sps30.c index fa3cd409b90b..f3b4390c8f5c 100644 --- a/drivers/iio/chemical/sps30.c +++ b/drivers/iio/chemical/sps30.c @@ -7,7 +7,6 @@ * I2C slave address: 0x69 * * TODO: - * - support for turning on fan cleaning * - support for reading/setting auto cleaning interval */ @@ -37,6 +36,7 @@ #define SPS30_READ_DATA_READY_FLAG 0x0202 #define SPS30_READ_DATA 0x0300 #define SPS30_READ_SERIAL 0xd033 +#define SPS30_START_FAN_CLEANING 0x5607 enum { PM1, @@ -104,6 +104,7 @@ static int sps30_do_cmd(struct sps30_state *state, u16 cmd, u8 *data, int size) break; case SPS30_STOP_MEAS: case SPS30_RESET: + case SPS30_START_FAN_CLEANING: ret = sps30_write_then_read(state, buf, 2, NULL, 0); break; case SPS30_READ_DATA_READY_FLAG: @@ -275,7 +276,39 @@ static int sps30_read_raw(struct iio_dev *indio_dev, return -EINVAL; } +static ssize_t start_cleaning_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sps30_state *state = iio_priv(indio_dev); + int val, ret; + + if (kstrtoint(buf, 0, &val) || val != 1) + return -EINVAL; + + mutex_lock(&state->lock); + ret = sps30_do_cmd(state, SPS30_START_FAN_CLEANING, NULL, 0); + mutex_unlock(&state->lock); + if (ret) + return ret; + + return len; +} + +static IIO_DEVICE_ATTR_WO(start_cleaning, 0); + +static struct attribute *sps30_attrs[] = { + &iio_dev_attr_start_cleaning.dev_attr.attr, + NULL +}; + +static const struct attribute_group sps30_attr_group = { + .attrs = sps30_attrs, +}; + static const struct iio_info sps30_info = { + .attrs = &sps30_attr_group, .read_raw = sps30_read_raw, }; -- cgit v1.2.3 From 62129a0849d27cc94ced832bcf9dcde283dcbe08 Mon Sep 17 00:00:00 2001 From: Tomasz Duszynski Date: Tue, 15 Jan 2019 20:00:06 +0100 Subject: iio: chemical: sps30: allow changing self cleaning period Sensor can periodically trigger self cleaning. Period can be changed by writing a new value to a dedicated attribute. Upon attribute read current period gets returned. Signed-off-by: Tomasz Duszynski Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio-sps30 | 20 ++++ drivers/iio/chemical/sps30.c | 143 ++++++++++++++++++++++---- 2 files changed, 145 insertions(+), 18 deletions(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-iio-sps30 b/Documentation/ABI/testing/sysfs-bus-iio-sps30 index e7ce2c57635e..143df8e89d08 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio-sps30 +++ b/Documentation/ABI/testing/sysfs-bus-iio-sps30 @@ -6,3 +6,23 @@ Description: Writing 1 starts sensor self cleaning. Internal fan accelerates to its maximum speed and keeps spinning for about 10 seconds in order to blow out accumulated dust. + +What: /sys/bus/iio/devices/iio:deviceX/cleaning_period +Date: January 2019 +KernelVersion: 5.1 +Contact: linux-iio@vger.kernel.org +Description: + Sensor is capable of triggering self cleaning periodically. + Period can be changed by writing a new value here. Upon reading + the current one is returned. Units are seconds. + + Writing 0 disables periodical self cleaning entirely. + +What: /sys/bus/iio/devices/iio:deviceX/cleaning_period_available +Date: January 2019 +KernelVersion: 5.1 +Contact: linux-iio@vger.kernel.org +Description: + The range of available values in seconds represented as the + minimum value, the step and the maximum value, all enclosed in + square brackets. diff --git a/drivers/iio/chemical/sps30.c b/drivers/iio/chemical/sps30.c index f3b4390c8f5c..376fac41ecb5 100644 --- a/drivers/iio/chemical/sps30.c +++ b/drivers/iio/chemical/sps30.c @@ -5,9 +5,6 @@ * Copyright (c) Tomasz Duszynski * * I2C slave address: 0x69 - * - * TODO: - * - support for reading/setting auto cleaning interval */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -21,6 +18,7 @@ #include #include #include +#include #include #define SPS30_CRC8_POLYNOMIAL 0x31 @@ -28,6 +26,9 @@ #define SPS30_MAX_READ_SIZE 48 /* sensor measures reliably up to 3000 ug / m3 */ #define SPS30_MAX_PM 3000 +/* minimum and maximum self cleaning periods in seconds */ +#define SPS30_AUTO_CLEANING_PERIOD_MIN 0 +#define SPS30_AUTO_CLEANING_PERIOD_MAX 604800 /* SPS30 commands */ #define SPS30_START_MEAS 0x0010 @@ -37,6 +38,9 @@ #define SPS30_READ_DATA 0x0300 #define SPS30_READ_SERIAL 0xd033 #define SPS30_START_FAN_CLEANING 0x5607 +#define SPS30_AUTO_CLEANING_PERIOD 0x8004 +/* not a sensor command per se, used only to distinguish write from read */ +#define SPS30_READ_AUTO_CLEANING_PERIOD 0x8005 enum { PM1, @@ -45,6 +49,11 @@ enum { PM10, }; +enum { + RESET, + MEASURING, +}; + struct sps30_state { struct i2c_client *client; /* @@ -52,6 +61,7 @@ struct sps30_state { * Must be held whenever sequence of commands is to be executed. */ struct mutex lock; + int state; }; DECLARE_CRC8_TABLE(sps30_crc8_table); @@ -107,6 +117,9 @@ static int sps30_do_cmd(struct sps30_state *state, u16 cmd, u8 *data, int size) case SPS30_START_FAN_CLEANING: ret = sps30_write_then_read(state, buf, 2, NULL, 0); break; + case SPS30_READ_AUTO_CLEANING_PERIOD: + buf[0] = SPS30_AUTO_CLEANING_PERIOD >> 8; + buf[1] = (u8)SPS30_AUTO_CLEANING_PERIOD; case SPS30_READ_DATA_READY_FLAG: case SPS30_READ_DATA: case SPS30_READ_SERIAL: @@ -114,6 +127,15 @@ static int sps30_do_cmd(struct sps30_state *state, u16 cmd, u8 *data, int size) size += size / 2; ret = sps30_write_then_read(state, buf, 2, buf, size); break; + case SPS30_AUTO_CLEANING_PERIOD: + buf[2] = data[0]; + buf[3] = data[1]; + buf[4] = crc8(sps30_crc8_table, &buf[2], 2, CRC8_INIT_VALUE); + buf[5] = data[2]; + buf[6] = data[3]; + buf[7] = crc8(sps30_crc8_table, &buf[5], 2, CRC8_INIT_VALUE); + ret = sps30_write_then_read(state, buf, 8, NULL, 0); + break; } if (ret) @@ -170,6 +192,14 @@ static int sps30_do_meas(struct sps30_state *state, s32 *data, int size) int i, ret, tries = 5; u8 tmp[16]; + if (state->state == RESET) { + ret = sps30_do_cmd(state, SPS30_START_MEAS, NULL, 0); + if (ret) + return ret; + + state->state = MEASURING; + } + while (tries--) { ret = sps30_do_cmd(state, SPS30_READ_DATA_READY_FLAG, tmp, 2); if (ret) @@ -276,6 +306,24 @@ static int sps30_read_raw(struct iio_dev *indio_dev, return -EINVAL; } +static int sps30_do_cmd_reset(struct sps30_state *state) +{ + int ret; + + ret = sps30_do_cmd(state, SPS30_RESET, NULL, 0); + msleep(300); + /* + * Power-on-reset causes sensor to produce some glitch on i2c bus and + * some controllers end up in error state. Recover simply by placing + * some data on the bus, for example STOP_MEAS command, which + * is NOP in this case. + */ + sps30_do_cmd(state, SPS30_STOP_MEAS, NULL, 0); + state->state = RESET; + + return ret; +} + static ssize_t start_cleaning_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) @@ -296,10 +344,82 @@ static ssize_t start_cleaning_store(struct device *dev, return len; } +static ssize_t cleaning_period_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sps30_state *state = iio_priv(indio_dev); + u8 tmp[4]; + int ret; + + mutex_lock(&state->lock); + ret = sps30_do_cmd(state, SPS30_READ_AUTO_CLEANING_PERIOD, tmp, 4); + mutex_unlock(&state->lock); + if (ret) + return ret; + + return sprintf(buf, "%d\n", get_unaligned_be32(tmp)); +} + +static ssize_t cleaning_period_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sps30_state *state = iio_priv(indio_dev); + int val, ret; + u8 tmp[4]; + + if (kstrtoint(buf, 0, &val)) + return -EINVAL; + + if ((val < SPS30_AUTO_CLEANING_PERIOD_MIN) || + (val > SPS30_AUTO_CLEANING_PERIOD_MAX)) + return -EINVAL; + + put_unaligned_be32(val, tmp); + + mutex_lock(&state->lock); + ret = sps30_do_cmd(state, SPS30_AUTO_CLEANING_PERIOD, tmp, 0); + if (ret) { + mutex_unlock(&state->lock); + return ret; + } + + msleep(20); + + /* + * sensor requires reset in order to return up to date self cleaning + * period + */ + ret = sps30_do_cmd_reset(state); + if (ret) + dev_warn(dev, + "period changed but reads will return the old value\n"); + + mutex_unlock(&state->lock); + + return len; +} + +static ssize_t cleaning_period_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "[%d %d %d]\n", + SPS30_AUTO_CLEANING_PERIOD_MIN, 1, + SPS30_AUTO_CLEANING_PERIOD_MAX); +} + static IIO_DEVICE_ATTR_WO(start_cleaning, 0); +static IIO_DEVICE_ATTR_RW(cleaning_period, 0); +static IIO_DEVICE_ATTR_RO(cleaning_period_available, 0); static struct attribute *sps30_attrs[] = { &iio_dev_attr_start_cleaning.dev_attr.attr, + &iio_dev_attr_cleaning_period.dev_attr.attr, + &iio_dev_attr_cleaning_period_available.dev_attr.attr, NULL }; @@ -362,6 +482,7 @@ static int sps30_probe(struct i2c_client *client) state = iio_priv(indio_dev); i2c_set_clientdata(client, indio_dev); state->client = client; + state->state = RESET; indio_dev->dev.parent = &client->dev; indio_dev->info = &sps30_info; indio_dev->name = client->name; @@ -373,19 +494,11 @@ static int sps30_probe(struct i2c_client *client) mutex_init(&state->lock); crc8_populate_msb(sps30_crc8_table, SPS30_CRC8_POLYNOMIAL); - ret = sps30_do_cmd(state, SPS30_RESET, NULL, 0); + ret = sps30_do_cmd_reset(state); if (ret) { dev_err(&client->dev, "failed to reset device\n"); return ret; } - msleep(300); - /* - * Power-on-reset causes sensor to produce some glitch on i2c bus and - * some controllers end up in error state. Recover simply by placing - * some data on the bus, for example STOP_MEAS command, which - * is NOP in this case. - */ - sps30_do_cmd(state, SPS30_STOP_MEAS, NULL, 0); ret = sps30_do_cmd(state, SPS30_READ_SERIAL, buf, sizeof(buf)); if (ret) { @@ -395,12 +508,6 @@ static int sps30_probe(struct i2c_client *client) /* returned serial number is already NUL terminated */ dev_info(&client->dev, "serial number: %s\n", buf); - ret = sps30_do_cmd(state, SPS30_START_MEAS, NULL, 0); - if (ret) { - dev_err(&client->dev, "failed to start measurement\n"); - return ret; - } - ret = devm_add_action_or_reset(&client->dev, sps30_stop_meas, state); if (ret) return ret; -- cgit v1.2.3