From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wr1-f42.google.com (mail-wr1-f42.google.com [209.85.221.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5A09847669E for ; Tue, 28 Jul 2026 21:49:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.42 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785275394; cv=none; b=QhpsLnRy1wjTKb6IrypY8laRBPq94Arshr3r2ZddJGIyJPTVpTf+7Kd2wpzGiYQ1W+9Mt29uc38wHXuz3huengmLRThOZLcT63pohhfb02BWlSMH7/2uWwQqMqQOUYmBjITg0voR3HhfBaC7e/JL/I9Jbu8GQV5YLlnRll9TkLM= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785275394; c=relaxed/simple; bh=RJyRw+BdckFG78WTTuhkPf724Yk19YrzaCpwYe040oM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=glWUG7WGKp+d28sOgQYJxPryK+KmfA4eTzQSp8fhBIBZQ9GY6r61mtnQMCs8V0PM/y+WgfkbEzO9cr/zZnb9Q4vRThVf9VMJrC9WiQbpIO+ajfVNkjXD5Xi/TTIILWNRjH1mue7hNQdxPq8f7OZU/moyVhrGGuqQMRm2FExRGLQ= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=YBZ8Rj1t; arc=none smtp.client-ip=209.85.221.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="YBZ8Rj1t" Received: by mail-wr1-f42.google.com with SMTP id ffacd0b85a97d-47f6609c657so179685f8f.2 for ; Tue, 28 Jul 2026 14:49:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785275390; x=1785880190; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=2RZny5erhJ28j6r6y6Dda21yz2JJE3yiIZtw3mjYPqU=; b=YBZ8Rj1txei4DEZcHSDxpxpGbFyCUD/h1NCB1RAEhktBEMpGMdc8pdCZMf+6v8C+wp arnsR093HXUZw8Ks3ah+egG5YhaBrDO58RSmFlCjY8C1Vaj2ELD5fVy7kCgvyxNi3093 taI6kguQ3vX6bvmgOg06nuJEjPFB+RjB1ZVwdO3uJh/M+/N1dh+3iMr/dAn3V/iSqtL+ qnjYRjIiJwCb2DoEO3BIbEsgd9px7a7B/vk5XKVxJccqGYgJhs20+ixo9AgpggyyWUqy ayaHTIGM1PgtUy/fM8UNETNIoAgPT8ztNT/WiqjvGcPb7AFBcJQEgSmSuZoH4KIYvqOP RAOQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785275390; x=1785880190; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=2RZny5erhJ28j6r6y6Dda21yz2JJE3yiIZtw3mjYPqU=; b=odZ6ZV8yFnN8+I4aQuiAuhVUk9P2pZ+g+J0b9RKh1NJWrrT+oCTDwt7qZZjiAN053b CEaTVC+N0lLuX23+SppbXTdbJ6LydmW0S3RbEw5LwmHVt5CsuP+NwSOZQ5s2hAr3zQOI RsoS7b1KI65vhRCGQTvi8UNjCcyCPkXUTzi/aTQK2sQt+vrDCXDqa4cCoDsxU35a1Pmq groIxpVbCo4HFoBBOyjRtw8b9qvMzF2H8l6ENkjTDsz+EkMRSY0m41WShgXk2ix2HBCi Dds7jwVn/AF9l28mQEM2jPv44KkBIrXxn/IYBn/FwH36nL/gtfP21bHtQKq+0g0T8pFr FkdQ== X-Forwarded-Encrypted: i=1; AHgh+RrqKCOi67JtG77AGXxc1Ys6Q+x/RCgTZ3+J13bDq6hFo3O7awtSkVdX8UVna9Ryb/wLfvWobjQ/5bq+@vger.kernel.org X-Gm-Message-State: AOJu0YwOZyjg7ZnOh/5aMT/4yPlZw4BSUpvZUXKq3pvo2ogNDJpmDGP8 DPfOp0yxrt5r/1O8yHlDFX4tD2RLGcObtkttWNQ7Sxed05v5UzSIwFoH X-Gm-Gg: AR+sD10COReGJvteZCRpxwpQuAAfpF8E0ccbEMF6fFni9NUMlAetj4bTUBH/RY8GovZ gFw41lj2cIROciH2tk1xNak3Zf6fxRzl3eBfktqgNGa8WX4Em6YboAkgYkQTTf4+qzWBiWwqn4R dF1lOV2jztInuH7tuDQyHZqt7jj5DokYgcT4jTp1MfWFAxt3v2zP4Orj+J5Int4CtfEdTjXKKbW b/mG60gZulDmODxz7bsEl+zgRUnZW/tu22je3RvD3qDm1y2SykuvIH7120y2ds66z+7L6FGmkFF DWNgYIzuabegn5+Lp/6Cpnd5qi3sffdl48OLU05zAIMKh5XRlXVzfZGbDg6pSwuPBqxUKoXDU/7 G4fCym8VFhJJADVjks0i9cqOEEiTi6DwV8x2ajcHFvuMs0FTDoa36UqHtJiQ= X-Received: by 2002:a05:6000:18a3:b0:47f:9287:6356 with SMTP id ffacd0b85a97d-47fb1ea77bamr5003228f8f.32.1785275389341; Tue, 28 Jul 2026 14:49:49 -0700 (PDT) Received: from sefo-laptop ([2a02:8071:50c5:5c0::361b]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-47fb6b0ef25sm2353042f8f.24.2026.07.28.14.49.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 28 Jul 2026 14:49:48 -0700 (PDT) From: Wadim Mueller To: jonathan.cameron@oss.qualcomm.com, krzk+dt@kernel.org, robh@kernel.org, conor+dt@kernel.org, dlechner@baylibre.com, nuno.sa@analog.com, andy@kernel.org, maxwell@maxwelld.cc Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, marcelo.schmitt1@gmail.com, 455.rodrigo.alencar@gmail.com Subject: [PATCH v6 4/4] iio: flow: add Sensirion SLF3S liquid flow sensor driver Date: Tue, 28 Jul 2026 23:49:43 +0200 Message-ID: <20260728214943.29820-5-wafgo01@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260728214943.29820-1-wafgo01@gmail.com> References: <20260728214943.29820-1-wafgo01@gmail.com> Precedence: bulk X-Mailing-List: devicetree@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Add a driver for the Sensirion SLF3S family of digital liquid-flow sensors on I2C. Currently supported variants are SLF3S-0600F, SLF3S-1300F and SLF3S-4000B; they share the same register map and differ only in flow-scale factor and calibrated measurement range. The variant (and therefore the scale) is auto-detected from the product-information register at probe time; a sensor reporting an unknown sub-type falls back to the variant named in the device tree / I2C table, so a drop-in replacement part keeps working on a kernel that does not know its sub-type yet. Each measurement frame returns a 16-bit signed flow value, a 16-bit signed temperature reading and a status word, each protected by a CRC-8 byte. The driver exposes the flow rate as IIO_VOLUMEFLOW and the temperature as IIO_TEMP via the standard IIO read_raw / read_scale interface. The volume-flow scale is reported in m^3/s. As the per-LSB scale is on the order of 1e-12 m^3/s, it is emitted as a 64-bit fixed-point value with femto (1e-15) resolution (IIO_VAL_DECIMAL64_FEMTO) so the small SI value keeps full precision. This relies on the IIO_VAL_DECIMAL64_FEMTO format type added earlier in this series. The active calibration medium can be switched at runtime between the factory-calibrated water and isopropyl-alcohol modes via the in_volumeflow_medium sysfs attribute; the sensor starts in water mode after probe. The sensor has no low-power state of its own, so system suspend stops the measurement and disables the vdd supply; resume powers the sensor back up, waits out the power-up time and restarts the measurement with the previously active medium, following the scd30/scd4x precedent. This driver also creates the drivers/iio/flow/ subdirectory and the corresponding Kconfig/Makefile glue. Signed-off-by: Wadim Mueller --- Documentation/ABI/testing/sysfs-bus-iio-flow | 21 + MAINTAINERS | 1 + drivers/iio/Kconfig | 1 + drivers/iio/Makefile | 1 + drivers/iio/flow/Kconfig | 22 + drivers/iio/flow/Makefile | 7 + drivers/iio/flow/slf3s.c | 543 +++++++++++++++++++ 7 files changed, 596 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-flow create mode 100644 drivers/iio/flow/Kconfig create mode 100644 drivers/iio/flow/Makefile create mode 100644 drivers/iio/flow/slf3s.c diff --git a/Documentation/ABI/testing/sysfs-bus-iio-flow b/Documentation/ABI/testing/sysfs-bus-iio-flow new file mode 100644 index 000000000000..fece2ecfa994 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-flow @@ -0,0 +1,21 @@ +What: /sys/bus/iio/devices/iio:deviceX/in_volumeflow_medium +What: /sys/bus/iio/devices/iio:deviceX/in_volumeflowY_medium +KernelVersion: 7.3 +Contact: linux-iio@vger.kernel.org +Description: + The calibration medium the flow sensor uses to convert its + raw reading into a volumetric flow rate. Liquid flow sensors + are factory-calibrated per medium, so the selected medium has + to match the fluid actually flowing through the sensor for the + reported flow rate to be correct. + + Reading returns the currently active medium; writing one of the + strings listed in in_volumeflow_medium_available selects it. + +What: /sys/bus/iio/devices/iio:deviceX/in_volumeflow_medium_available +What: /sys/bus/iio/devices/iio:deviceX/in_volumeflowY_medium_available +KernelVersion: 7.3 +Contact: linux-iio@vger.kernel.org +Description: + Space separated list of the calibration media supported by the + device, e.g. "water ipa". diff --git a/MAINTAINERS b/MAINTAINERS index 497492825f70..03de8197f0a9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24589,6 +24589,7 @@ R: Maxwell Doose L: linux-iio@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/iio/flow/sensirion,slf3s.yaml +F: drivers/iio/flow/slf3s.c SENSIRION SPS30 AIR POLLUTION SENSOR DRIVER M: Tomasz Duszynski diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 661127aed2f9..652557a5b851 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -92,6 +92,7 @@ source "drivers/iio/common/Kconfig" source "drivers/iio/dac/Kconfig" source "drivers/iio/dummy/Kconfig" source "drivers/iio/filter/Kconfig" +source "drivers/iio/flow/Kconfig" source "drivers/iio/frequency/Kconfig" source "drivers/iio/gyro/Kconfig" source "drivers/iio/health/Kconfig" diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index cb80ef837e84..f03a4100c800 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -29,6 +29,7 @@ obj-y += dac/ obj-y += dummy/ obj-y += gyro/ obj-y += filter/ +obj-y += flow/ obj-y += frequency/ obj-y += health/ obj-y += humidity/ diff --git a/drivers/iio/flow/Kconfig b/drivers/iio/flow/Kconfig new file mode 100644 index 000000000000..e0e1a8e3654a --- /dev/null +++ b/drivers/iio/flow/Kconfig @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Liquid / gas flow sensor drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Flow sensors" + +config SENSIRION_SLF3S + tristate "Sensirion SLF3S liquid flow sensor" + depends on I2C + select CRC8 + help + Say yes here to build support for the Sensirion SLF3S family of + digital liquid-flow sensors (SLF3S-0600F, SLF3S-1300F and + SLF3S-4000B). The driver reports the volumetric flow rate and the + embedded temperature reading via the standard IIO interface. + + To compile this driver as a module, choose M here: the module + will be called slf3s. + +endmenu diff --git a/drivers/iio/flow/Makefile b/drivers/iio/flow/Makefile new file mode 100644 index 000000000000..3cf4ab95c69c --- /dev/null +++ b/drivers/iio/flow/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for industrial I/O flow sensor drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_SENSIRION_SLF3S) += slf3s.o diff --git a/drivers/iio/flow/slf3s.c b/drivers/iio/flow/slf3s.c new file mode 100644 index 000000000000..518ad41afc6c --- /dev/null +++ b/drivers/iio/flow/slf3s.c @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sensirion SLF3S liquid flow sensor driver. + * + * Supports the SLF3S-0600F, SLF3S-1300F and SLF3S-4000B liquid-flow + * sensors over I2C. Each measurement frame returns a 16-bit signed + * flow value, a 16-bit signed temperature value and a status word, + * each protected by a CRC-8 byte. + * + * The active calibration medium (water or isopropyl alcohol) is + * runtime-switchable via the in_volumeflow_medium sysfs attribute and + * defaults to water. + * + * Datasheet: https://sensirion.com/products/catalog/SLF3S-0600F/ + * + * Copyright (C) 2026 CMBlu Energy GmbH + * Author: Wadim Mueller + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define SLF3S_CRC8_POLY 0x31 +#define SLF3S_CRC8_INIT 0xff + +#define SLF3S_PRODUCT_ID_LEN 18 +#define SLF3S_PRODUCT_FAMILY_BYTE 1 +#define SLF3S_PRODUCT_SUBTYPE_BYTE 3 +#define SLF3S_PRODUCT_FAMILY_ID 0x03 + +/* Datasheet section 2.2: tPU = 25 ms max from power-on to first cmd. */ +#define SLF3S_POWER_UP_DELAY_US (25 * USEC_PER_MSEC) +/* Datasheet section 2.2: tw = 60 ms typical until first valid sample. */ +#define SLF3S_MEAS_START_DELAY_US (60 * USEC_PER_MSEC) + +static const u8 slf3s_cmd_prep_pid[] = { 0x36, 0x7c }; +static const u8 slf3s_cmd_read_pid[] = { 0xe1, 0x02 }; +static const u8 slf3s_cmd_start_water[] = { 0x36, 0x08 }; +static const u8 slf3s_cmd_start_ipa[] = { 0x36, 0x15 }; +static const u8 slf3s_cmd_stop_meas[] = { 0x3f, 0xf9 }; + +enum slf3s_medium { + SLF3S_MEDIUM_WATER, + SLF3S_MEDIUM_IPA, +}; + +static const char * const slf3s_medium_modes[] = { + [SLF3S_MEDIUM_WATER] = "water", + [SLF3S_MEDIUM_IPA] = "ipa", +}; + +enum slf3s_variant_id { + SLF3S_0600F, + SLF3S_1300F, + SLF3S_4000B, +}; + +/** + * struct slf3s_variant - per-variant calibration constants + * @sub_type: product-info sub-type byte returned by the sensor + * @name: name reported via @iio_dev.name + * @scale: flow scale in l/s per LSB + */ +struct slf3s_variant { + u8 sub_type; + const char *name; + struct s32_fract scale; +}; + +static const struct slf3s_variant slf3s_variants[] = { + [SLF3S_0600F] = { + .sub_type = 0x03, + .name = "slf3s-0600f", + .scale = { .numerator = 1, .denominator = 600 * MICRO }, + }, + [SLF3S_1300F] = { + .sub_type = 0x02, + .name = "slf3s-1300f", + .scale = { .numerator = 1, .denominator = 30 * MICRO }, + }, + [SLF3S_4000B] = { + .sub_type = 0x05, + .name = "slf3s-4000b", + .scale = { .numerator = 1, .denominator = 1920 * MILLI }, + }, +}; + +/** + * struct slf3s_data - per-device state + * @client: I2C client this instance is bound to + * @vdd: supply regulator, disabled while suspended + * @variant: pointer into @slf3s_variants for the detected device + * @medium: currently active calibration medium + * @lock: serialises the multi-step command/response exchanges + * @crc_table: pre-computed CRC-8 lookup table for SLF3S_CRC8_POLY + */ +struct slf3s_data { + struct i2c_client *client; + struct regulator *vdd; + const struct slf3s_variant *variant; + enum slf3s_medium medium; + struct mutex lock; + u8 crc_table[CRC8_TABLE_SIZE]; +}; + +static int slf3s_send_cmd(struct i2c_client *client, const u8 *cmd) +{ + int ret; + + ret = i2c_master_send(client, cmd, 2); + if (ret < 0) + return ret; + if (ret != 2) + return -EIO; + + return 0; +} + +/* Start continuous measurement and wait until the first sample is valid. */ +static int slf3s_start_meas(struct slf3s_data *sf, enum slf3s_medium medium) +{ + const u8 *cmd = (medium == SLF3S_MEDIUM_IPA) ? slf3s_cmd_start_ipa + : slf3s_cmd_start_water; + int ret; + + ret = slf3s_send_cmd(sf->client, cmd); + if (ret) + return ret; + + fsleep(SLF3S_MEAS_START_DELAY_US); + + return 0; +} + +static bool slf3s_crc_valid(const struct slf3s_data *sf, const u8 *block) +{ + return crc8(sf->crc_table, block, 2, SLF3S_CRC8_INIT) == block[2]; +} + +/* + * Read the product-info block and pick the matching variant. The + * sub-type byte returned by the sensor is the source of truth; a + * DT-supplied compatible only seeds an initial guess and is overridden + * on mismatch (with an informational message so misconfigured device + * trees are easy to spot). + * + * Bus / CRC failures are real errors and fail probe. An unknown + * sub-type byte falls back to the variant named in the device tree / + * I2C table, so a drop-in replacement part that lists one of the known + * compatibles keeps working on an older kernel that does not know its + * sub-type yet. Without any match data probe fails since no + * meaningful scale can be published. + */ +static int slf3s_detect_variant(struct slf3s_data *sf) +{ + struct i2c_client *client = sf->client; + u8 buf[SLF3S_PRODUCT_ID_LEN]; + int ret; + + ret = slf3s_send_cmd(client, slf3s_cmd_prep_pid); + if (ret) + return ret; + + ret = slf3s_send_cmd(client, slf3s_cmd_read_pid); + if (ret) + return ret; + + ret = i2c_master_recv(client, buf, sizeof(buf)); + if (ret < 0) + return ret; + if (ret != sizeof(buf)) + return -EIO; + + for (unsigned int i = 0; i < SLF3S_PRODUCT_ID_LEN; i += 3) { + if (!slf3s_crc_valid(sf, &buf[i])) + return -EIO; + } + + if (buf[SLF3S_PRODUCT_FAMILY_BYTE] != SLF3S_PRODUCT_FAMILY_ID) + dev_info(&client->dev, + "unexpected family byte 0x%02x (expected 0x%02x)\n", + buf[SLF3S_PRODUCT_FAMILY_BYTE], + SLF3S_PRODUCT_FAMILY_ID); + + for (unsigned int i = 0; i < ARRAY_SIZE(slf3s_variants); i++) { + if (buf[SLF3S_PRODUCT_SUBTYPE_BYTE] != + slf3s_variants[i].sub_type) + continue; + + if (sf->variant && sf->variant != &slf3s_variants[i]) + dev_info(&client->dev, + "DT compatible says %s but sensor reports %s; using the latter\n", + sf->variant->name, + slf3s_variants[i].name); + + sf->variant = &slf3s_variants[i]; + + return 0; + } + + if (sf->variant) { + dev_warn(&client->dev, + "unknown SLF3S sub-type 0x%02x, assuming %s\n", + buf[SLF3S_PRODUCT_SUBTYPE_BYTE], sf->variant->name); + return 0; + } + + dev_err(&client->dev, "unknown SLF3S sub-type 0x%02x\n", + buf[SLF3S_PRODUCT_SUBTYPE_BYTE]); + + return -ENODEV; +} + +static int slf3s_read_sample(struct slf3s_data *sf, int *flow, int *temp) +{ + /* + * A measurement frame is flow, temperature and a signaling-flags + * word, each followed by a CRC byte. Only flow and temperature are + * used, so the read is stopped after their two words (6 bytes). + */ + u8 buf[6]; + int ret; + + ret = i2c_master_recv(sf->client, buf, sizeof(buf)); + if (ret < 0) + return ret; + if (ret != sizeof(buf)) + return -EIO; + + for (unsigned int i = 0; i < sizeof(buf); i += 3) { + if (!slf3s_crc_valid(sf, &buf[i])) + return -EIO; + } + + *flow = sign_extend32(get_unaligned_be16(&buf[0]), 15); + *temp = sign_extend32(get_unaligned_be16(&buf[3]), 15); + + return 0; +} + +static int slf3s_get_medium(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct slf3s_data *sf = iio_priv(indio_dev); + + return sf->medium; +} + +static int slf3s_set_medium(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int mode) +{ + struct slf3s_data *sf = iio_priv(indio_dev); + int ret; + + guard(mutex)(&sf->lock); + + ret = slf3s_send_cmd(sf->client, slf3s_cmd_stop_meas); + if (ret) + return ret; + + ret = slf3s_start_meas(sf, mode); + if (ret) { + /* + * Try to restart with the previous medium so the sensor is + * not left idle, which would fail all subsequent reads. + */ + if (slf3s_start_meas(sf, sf->medium)) + dev_warn(&sf->client->dev, + "failed to restart measurement, reads will fail until a medium is set\n"); + return ret; + } + + sf->medium = mode; + + return 0; +} + +static const struct iio_enum slf3s_medium_enum = { + .items = slf3s_medium_modes, + .num_items = ARRAY_SIZE(slf3s_medium_modes), + .get = slf3s_get_medium, + .set = slf3s_set_medium, +}; + +static const struct iio_chan_spec_ext_info slf3s_ext_info[] = { + IIO_ENUM("medium", IIO_SHARED_BY_TYPE, &slf3s_medium_enum), + IIO_ENUM_AVAILABLE("medium", IIO_SHARED_BY_TYPE, &slf3s_medium_enum), + { } +}; + +static const struct iio_chan_spec slf3s_channels[] = { + { + .type = IIO_VOLUMEFLOW, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .ext_info = slf3s_ext_info, + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int slf3s_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct slf3s_data *sf = iio_priv(indio_dev); + int flow, temp, ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + scoped_guard(mutex, &sf->lock) + ret = slf3s_read_sample(sf, &flow, &temp); + if (ret) + return ret; + + *val = (chan->type == IIO_VOLUMEFLOW) ? flow : temp; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_VOLUMEFLOW) { + /* + * The variant scale is the flow per LSB in l/s, but + * IIO reports volume flow in m^3/s (1 l = 1e-3 m^3). + * These values are tiny (~1.67e-12 m^3/s for the + * SLF3S-0600F), so emit a 64-bit fixed-point value with + * femto (1e-15) resolution to preserve precision. + * Converting l/s to m^3/s (/ MILLI) and scaling to femto + * (* FEMTO) leaves a net * (FEMTO / MILLI) factor. + */ + const struct slf3s_variant *v = sf->variant; + s64 num = (s64)v->scale.numerator * (FEMTO / MILLI); + s64 scale = DIV_S64_ROUND_CLOSEST(num, + v->scale.denominator); + + iio_val_s64_decompose(scale, val, val2); + + return IIO_VAL_DECIMAL64_FEMTO; + } + /* Temperature LSB = 1/200 degC; IIO_TEMP wants milli-degC. */ + *val = MILLIDEGREE_PER_DEGREE / 200; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_info slf3s_info = { + .read_raw = slf3s_read_raw, +}; + +static void slf3s_stop_meas(void *data) +{ + struct slf3s_data *sf = data; + + slf3s_send_cmd(sf->client, slf3s_cmd_stop_meas); +} + +static void slf3s_disable_vdd(void *data) +{ + struct slf3s_data *sf = data; + + regulator_disable(sf->vdd); +} + +static int slf3s_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct slf3s_data *sf; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*sf)); + if (!indio_dev) + return -ENOMEM; + + sf = iio_priv(indio_dev); + sf->client = client; + i2c_set_clientdata(client, indio_dev); + sf->variant = i2c_get_match_data(client); + sf->medium = SLF3S_MEDIUM_WATER; + crc8_populate_msb(sf->crc_table, SLF3S_CRC8_POLY); + + ret = devm_mutex_init(dev, &sf->lock); + if (ret) + return ret; + + sf->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(sf->vdd)) + return dev_err_probe(dev, PTR_ERR(sf->vdd), + "failed to get vdd supply\n"); + + ret = regulator_enable(sf->vdd); + if (ret) + return dev_err_probe(dev, ret, "failed to enable vdd supply\n"); + + ret = devm_add_action_or_reset(dev, slf3s_disable_vdd, sf); + if (ret) + return ret; + + fsleep(SLF3S_POWER_UP_DELAY_US); + + /* + * The sensor may still be in continuous measurement mode from a + * previous boot (warm reboot / kexec); in that case it would NACK + * the product-id command below. Stop it first and ignore the error + * if it was already idle. + */ + slf3s_send_cmd(client, slf3s_cmd_stop_meas); + + ret = slf3s_detect_variant(sf); + if (ret) + return dev_err_probe(dev, ret, "product info read failed\n"); + + ret = slf3s_start_meas(sf, sf->medium); + if (ret) + return dev_err_probe(dev, ret, + "failed to start measurement\n"); + + ret = devm_add_action_or_reset(dev, slf3s_stop_meas, sf); + if (ret) + return ret; + + indio_dev->name = sf->variant->name; + indio_dev->channels = slf3s_channels; + indio_dev->num_channels = ARRAY_SIZE(slf3s_channels); + indio_dev->info = &slf3s_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + return devm_iio_device_register(dev, indio_dev); +} + +/* + * The sensor has no low-power state of its own, so stop the measurement + * and cut the supply while suspended. Resume powers it back up, waits + * out the power-up time and restarts with the medium that was active + * before. + */ +static int slf3s_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct slf3s_data *sf = iio_priv(indio_dev); + int ret; + + guard(mutex)(&sf->lock); + + ret = slf3s_send_cmd(sf->client, slf3s_cmd_stop_meas); + if (ret) + return ret; + + return regulator_disable(sf->vdd); +} + +static int slf3s_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct slf3s_data *sf = iio_priv(indio_dev); + int ret; + + guard(mutex)(&sf->lock); + + ret = regulator_enable(sf->vdd); + if (ret) + return ret; + + fsleep(SLF3S_POWER_UP_DELAY_US); + + return slf3s_start_meas(sf, sf->medium); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(slf3s_pm_ops, slf3s_suspend, slf3s_resume); + +static const struct i2c_device_id slf3s_id[] = { + { + .name = "slf3s-0600f", + .driver_data = (kernel_ulong_t)&slf3s_variants[SLF3S_0600F], + }, + { + .name = "slf3s-1300f", + .driver_data = (kernel_ulong_t)&slf3s_variants[SLF3S_1300F], + }, + { + .name = "slf3s-4000b", + .driver_data = (kernel_ulong_t)&slf3s_variants[SLF3S_4000B], + }, + { } +}; +MODULE_DEVICE_TABLE(i2c, slf3s_id); + +static const struct of_device_id slf3s_of_match[] = { + { + .compatible = "sensirion,slf3s-0600f", + .data = &slf3s_variants[SLF3S_0600F], + }, + { + .compatible = "sensirion,slf3s-1300f", + .data = &slf3s_variants[SLF3S_1300F], + }, + { + .compatible = "sensirion,slf3s-4000b", + .data = &slf3s_variants[SLF3S_4000B], + }, + { } +}; +MODULE_DEVICE_TABLE(of, slf3s_of_match); + +static struct i2c_driver slf3s_driver = { + .driver = { + .name = "slf3s", + .of_match_table = slf3s_of_match, + .pm = pm_sleep_ptr(&slf3s_pm_ops), + }, + .probe = slf3s_probe, + .id_table = slf3s_id, +}; +module_i2c_driver(slf3s_driver); + +MODULE_AUTHOR("Wadim Mueller "); +MODULE_DESCRIPTION("Sensirion SLF3S liquid flow sensor driver"); +MODULE_LICENSE("GPL"); -- 2.52.0