mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH RFC 0/3] iio: add Open Sensor Fusion UART driver
@ 2026-05-20  7:28 Jinseob Kim
  2026-05-20  7:28 ` [PATCH RFC 1/3] dt-bindings: iio: imu: add Open Sensor Fusion UART binding Jinseob Kim
                   ` (4 more replies)
  0 siblings, 5 replies; 12+ messages in thread
From: Jinseob Kim @ 2026-05-20  7:28 UTC (permalink / raw)
  To: Jonathan Cameron, linux-iio
  Cc: David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	devicetree, linux-kernel

Open Sensor Fusion is a UART-attached sensor aggregation device. The current
prototype sends OSF protocol v0 frames over a host UART link. This RFC adds the
first Linux IIO receive path for that UART stream.

The first transport is serdev UART. USB, real sensor reads, fusion output, and
production timestamp correlation are intentionally left out of this series.

The driver code is kept in one patch for the first RFC to avoid intermediate
non-buildable states. The internal files are still split by protocol, stream,
core, transport, and IIO code.

Current validation coverage:

- STM32F405 test firmware OSF protocol v0 UART stream at 115200 8N1.
- Raspberry Pi 4 serdev probe and receive path to osf_core_receive_frame().
- CAPABILITY_REPORT-driven IIO device registration.
- IIO read_raw for accel, gyro, magn, and temp samples.
- IIO software kfifo buffer userspace read.
- scan decode helper for buffered samples.
- dt_binding_check pass for the binding.
- checkpatch.pl --strict with ERROR 0, WARNING 0, CHECK 0.
- W=1 source compile/link phase clean in the staging kernel tree.
- modpost unresolved symbol warnings came from the staging tree missing
  Module.symvers.

Known limits:

- The sample source is synthetic stream data, not real ICM-42688-P or MMC5983MA
  sensor reads.
- The IIO timestamp is Linux host receive time. Device time correlation is still
  open.
- Runtime capability removal is not implemented.
- The staging layout uses drivers/iio/opensensorfusion/. The final directory is
  open for review.

Review feedback wanted on the IIO device layout, timestamp policy, binding
shape, and driver directory.

Jinseob Kim (3):
  dt-bindings: iio: imu: add Open Sensor Fusion UART binding
  iio: osf: add Open Sensor Fusion UART IIO driver
  MAINTAINERS: add Open Sensor Fusion IIO driver entry

 .../iio/imu/opensensorfusion,osf-uart.yaml    |  33 ++
 MAINTAINERS                                   |   7 +
 drivers/iio/Kconfig                           |   1 +
 drivers/iio/Makefile                          |   1 +
 drivers/iio/opensensorfusion/Kconfig          |  15 +
 drivers/iio/opensensorfusion/Makefile         |   6 +
 drivers/iio/opensensorfusion/osf_core.c       | 334 +++++++++++++++++
 drivers/iio/opensensorfusion/osf_core.h       |  81 ++++
 drivers/iio/opensensorfusion/osf_iio.c        | 268 +++++++++++++
 drivers/iio/opensensorfusion/osf_iio.h        |  21 ++
 drivers/iio/opensensorfusion/osf_protocol.c   | 234 ++++++++++++
 drivers/iio/opensensorfusion/osf_protocol.h   | 100 +++++
 drivers/iio/opensensorfusion/osf_serdev.c     | 354 ++++++++++++++++++
 drivers/iio/opensensorfusion/osf_serdev.h     |   8 +
 drivers/iio/opensensorfusion/osf_stream.c     | 212 +++++++++++
 drivers/iio/opensensorfusion/osf_stream.h     |  31 ++
 16 files changed, 1706 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/imu/opensensorfusion,osf-uart.yaml
 create mode 100644 drivers/iio/opensensorfusion/Kconfig
 create mode 100644 drivers/iio/opensensorfusion/Makefile
 create mode 100644 drivers/iio/opensensorfusion/osf_core.c
 create mode 100644 drivers/iio/opensensorfusion/osf_core.h
 create mode 100644 drivers/iio/opensensorfusion/osf_iio.c
 create mode 100644 drivers/iio/opensensorfusion/osf_iio.h
 create mode 100644 drivers/iio/opensensorfusion/osf_protocol.c
 create mode 100644 drivers/iio/opensensorfusion/osf_protocol.h
 create mode 100644 drivers/iio/opensensorfusion/osf_serdev.c
 create mode 100644 drivers/iio/opensensorfusion/osf_serdev.h
 create mode 100644 drivers/iio/opensensorfusion/osf_stream.c
 create mode 100644 drivers/iio/opensensorfusion/osf_stream.h

-- 
2.43.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH RFC 1/3] dt-bindings: iio: imu: add Open Sensor Fusion UART binding
  2026-05-20  7:28 [PATCH RFC 0/3] iio: add Open Sensor Fusion UART driver Jinseob Kim
@ 2026-05-20  7:28 ` Jinseob Kim
  2026-05-20 16:33   ` Conor Dooley
  2026-05-20  7:28 ` [PATCH RFC 2/3] iio: osf: add Open Sensor Fusion UART IIO driver Jinseob Kim
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 12+ messages in thread
From: Jinseob Kim @ 2026-05-20  7:28 UTC (permalink / raw)
  To: Jonathan Cameron, linux-iio
  Cc: David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	devicetree, linux-kernel

Add a binding for a UART-attached Open Sensor Fusion device.

The device is exposed as a serdev child node using the
opensensorfusion,osf-uart compatible string. Keep Raspberry Pi overlay and
test notes outside the binding.

The binding has been checked with dt_binding_check.

Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
---
 .../iio/imu/opensensorfusion,osf-uart.yaml    | 33 +++++++++++++++++++
 1 file changed, 33 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/imu/opensensorfusion,osf-uart.yaml

diff --git a/Documentation/devicetree/bindings/iio/imu/opensensorfusion,osf-uart.yaml b/Documentation/devicetree/bindings/iio/imu/opensensorfusion,osf-uart.yaml
new file mode 100644
index 000000000..6f329c326
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/imu/opensensorfusion,osf-uart.yaml
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/imu/opensensorfusion,osf-uart.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Open Sensor Fusion UART Sensor Aggregation Device
+
+maintainers:
+  - Jinseob Kim <kimjinseob88@gmail.com>
+
+description: |
+  Open Sensor Fusion is a UART-attached sensor aggregation device. The device
+  exposes an OSF protocol v0 data stream over its host UART interface and may
+  report capabilities and samples for multiple sensor classes.
+
+properties:
+  compatible:
+    const: opensensorfusion,osf-uart
+
+required:
+  - compatible
+
+additionalProperties: false
+
+examples:
+  - |
+    serial {
+        sensor {
+            compatible = "opensensorfusion,osf-uart";
+        };
+    };
+...
-- 
2.43.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH RFC 2/3] iio: osf: add Open Sensor Fusion UART IIO driver
  2026-05-20  7:28 [PATCH RFC 0/3] iio: add Open Sensor Fusion UART driver Jinseob Kim
  2026-05-20  7:28 ` [PATCH RFC 1/3] dt-bindings: iio: imu: add Open Sensor Fusion UART binding Jinseob Kim
@ 2026-05-20  7:28 ` Jinseob Kim
  2026-05-22 16:24   ` Jonathan Cameron
  2026-05-20  7:28 ` [PATCH RFC 3/3] MAINTAINERS: add Open Sensor Fusion IIO driver entry Jinseob Kim
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 12+ messages in thread
From: Jinseob Kim @ 2026-05-20  7:28 UTC (permalink / raw)
  To: Jonathan Cameron, linux-iio
  Cc: David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	devicetree, linux-kernel

Add the initial Open Sensor Fusion UART IIO driver.

This includes the protocol v0 parser, OSF0 stream assembler, core
capability/sample cache, serdev UART transport, IIO read_raw path, and
software kfifo buffer support.

The first RFC keeps the driver code in one patch to avoid intermediate
non-buildable states. The internal files still separate protocol, stream,
core, transport, and IIO code.

The tested path is STM32F405 test firmware OSF UART stream to Raspberry Pi
serdev, then IIO read_raw and buffered userspace reads.

Not included here: real sensor reads, USB transport, fusion/AHRS/Kalman,
runtime capability removal, or production timestamp correlation.

Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
---
 drivers/iio/Kconfig                         |   1 +
 drivers/iio/Makefile                        |   1 +
 drivers/iio/opensensorfusion/Kconfig        |  15 +
 drivers/iio/opensensorfusion/Makefile       |   6 +
 drivers/iio/opensensorfusion/osf_core.c     | 334 ++++++++++++++++++
 drivers/iio/opensensorfusion/osf_core.h     |  81 +++++
 drivers/iio/opensensorfusion/osf_iio.c      | 268 +++++++++++++++
 drivers/iio/opensensorfusion/osf_iio.h      |  21 ++
 drivers/iio/opensensorfusion/osf_protocol.c | 234 +++++++++++++
 drivers/iio/opensensorfusion/osf_protocol.h | 100 ++++++
 drivers/iio/opensensorfusion/osf_serdev.c   | 354 ++++++++++++++++++++
 drivers/iio/opensensorfusion/osf_serdev.h   |   8 +
 drivers/iio/opensensorfusion/osf_stream.c   | 212 ++++++++++++
 drivers/iio/opensensorfusion/osf_stream.h   |  31 ++
 14 files changed, 1666 insertions(+)
 create mode 100644 drivers/iio/opensensorfusion/Kconfig
 create mode 100644 drivers/iio/opensensorfusion/Makefile
 create mode 100644 drivers/iio/opensensorfusion/osf_core.c
 create mode 100644 drivers/iio/opensensorfusion/osf_core.h
 create mode 100644 drivers/iio/opensensorfusion/osf_iio.c
 create mode 100644 drivers/iio/opensensorfusion/osf_iio.h
 create mode 100644 drivers/iio/opensensorfusion/osf_protocol.c
 create mode 100644 drivers/iio/opensensorfusion/osf_protocol.h
 create mode 100644 drivers/iio/opensensorfusion/osf_serdev.c
 create mode 100644 drivers/iio/opensensorfusion/osf_serdev.h
 create mode 100644 drivers/iio/opensensorfusion/osf_stream.c
 create mode 100644 drivers/iio/opensensorfusion/osf_stream.h

diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
index 661127aed..939f6c546 100644
--- a/drivers/iio/Kconfig
+++ b/drivers/iio/Kconfig
@@ -101,6 +101,7 @@ source "drivers/iio/light/Kconfig"
 source "drivers/iio/magnetometer/Kconfig"
 source "drivers/iio/multiplexer/Kconfig"
 source "drivers/iio/orientation/Kconfig"
+source "drivers/iio/opensensorfusion/Kconfig"
 source "drivers/iio/test/Kconfig"
 if IIO_TRIGGER
    source "drivers/iio/trigger/Kconfig"
diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
index cb80ef837..d864fe17b 100644
--- a/drivers/iio/Makefile
+++ b/drivers/iio/Makefile
@@ -37,6 +37,7 @@ obj-y += light/
 obj-y += magnetometer/
 obj-y += multiplexer/
 obj-y += orientation/
+obj-y += opensensorfusion/
 obj-y += position/
 obj-y += potentiometer/
 obj-y += potentiostat/
diff --git a/drivers/iio/opensensorfusion/Kconfig b/drivers/iio/opensensorfusion/Kconfig
new file mode 100644
index 000000000..1db1dbe56
--- /dev/null
+++ b/drivers/iio/opensensorfusion/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config OPEN_SENSOR_FUSION
+	tristate "Open Sensor Fusion UART IIO driver"
+	depends on IIO
+	depends on SERIAL_DEV_BUS
+	select CRC32
+	select IIO_BUFFER
+	select IIO_KFIFO_BUF
+	help
+	  Build the Open Sensor Fusion UART IIO driver.
+
+	  This includes the serdev UART receive path for OSF0 protocol
+	  bring-up. It does not include real sensor reads, USB transport,
+	  or fusion/AHRS/Kalman output.
diff --git a/drivers/iio/opensensorfusion/Makefile b/drivers/iio/opensensorfusion/Makefile
new file mode 100644
index 000000000..b4e03b80c
--- /dev/null
+++ b/drivers/iio/opensensorfusion/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_OPEN_SENSOR_FUSION) += open-sensor-fusion.o
+
+open-sensor-fusion-y := osf_core.o osf_iio.o osf_protocol.o osf_serdev.o \
+			 osf_stream.o
diff --git a/drivers/iio/opensensorfusion/osf_core.c b/drivers/iio/opensensorfusion/osf_core.c
new file mode 100644
index 000000000..fd2eccefc
--- /dev/null
+++ b/drivers/iio/opensensorfusion/osf_core.c
@@ -0,0 +1,334 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/ktime.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#include "osf_core.h"
+#include "osf_serdev.h"
+
+#define OSF_RESERVED_MSG_FIRST		0x7f00
+#define OSF_RESERVED_MSG_LAST		0x7fff
+#define OSF_VENDOR_PRIVATE_FIRST	0x8000
+
+static struct osf_latest_sample *
+osf_core_find_latest_sample(struct osf_device *osf, u16 sensor_type,
+			    u16 sensor_index, bool allocate)
+{
+	struct osf_latest_sample *free_slot = NULL;
+	struct osf_latest_sample *latest;
+	unsigned int i;
+
+	for (i = 0; i < OSF_MAX_LATEST_SAMPLES; i++) {
+		latest = &osf->latest_samples[i];
+		if (!latest->valid) {
+			if (!free_slot)
+				free_slot = latest;
+			continue;
+		}
+
+		if (latest->sensor_type == sensor_type &&
+		    latest->sensor_index == sensor_index)
+			return latest;
+	}
+
+	return allocate ? free_slot : NULL;
+}
+
+static void osf_core_store_latest_sample(struct osf_latest_sample *latest,
+					 const struct osf_sensor_sample *sample,
+					 const s32 *values,
+					 const struct osf_frame *frame)
+{
+	unsigned int i;
+
+	for (i = 0; i < sample->channel_count; i++)
+		latest->values[i] = values[i];
+
+	for (; i < OSF_MAX_SAMPLE_CHANNELS; i++)
+		latest->values[i] = 0;
+
+	latest->sensor_type = sample->sensor_type;
+	latest->sensor_index = sample->sensor_index;
+	latest->channel_count = sample->channel_count;
+	latest->sample_format = sample->sample_format;
+	latest->scale_nano = sample->scale_nano;
+	latest->sequence = frame->sequence;
+	latest->timestamp_us = frame->timestamp_us;
+	latest->valid = true;
+}
+
+static int osf_core_handle_sensor_sample(struct osf_device *osf,
+					 const struct osf_frame *frame)
+{
+	osf_sample_callback_t callback;
+	void *callback_context;
+	struct osf_latest_sample *latest;
+	struct osf_sample_event event = { };
+	struct osf_sensor_sample sample;
+	s32 values[OSF_MAX_SAMPLE_CHANNELS] = { };
+	unsigned long flags;
+	unsigned int i;
+	int ret;
+
+	ret = osf_protocol_decode_sensor_sample(frame, &sample);
+	if (ret)
+		return ret;
+
+	if (sample.channel_count > OSF_MAX_SAMPLE_CHANNELS)
+		return -E2BIG;
+
+	for (i = 0; i < sample.channel_count; i++) {
+		ret = osf_protocol_sensor_sample_value(&sample, i, &values[i]);
+		if (ret)
+			return ret;
+	}
+
+	event.sensor_type = sample.sensor_type;
+	event.sensor_index = sample.sensor_index;
+	event.channel_count = sample.channel_count;
+	event.sample_format = sample.sample_format;
+	event.scale_nano = sample.scale_nano;
+	event.sequence = frame->sequence;
+	event.timestamp_us = frame->timestamp_us;
+	event.host_timestamp_ns = ktime_get_ns();
+	for (i = 0; i < sample.channel_count; i++)
+		event.values[i] = values[i];
+
+	spin_lock_irqsave(&osf->lock, flags);
+	latest = osf_core_find_latest_sample(osf, sample.sensor_type,
+					     sample.sensor_index, true);
+	if (!latest) {
+		spin_unlock_irqrestore(&osf->lock, flags);
+		return -ENOSPC;
+	}
+
+	osf_core_store_latest_sample(latest, &sample, values, frame);
+	osf_core_store_latest_sample(&osf->latest_sample, &sample, values,
+				     frame);
+	osf->last_sequence = frame->sequence;
+	callback = osf->sample_callback;
+	callback_context = osf->sample_callback_context;
+	spin_unlock_irqrestore(&osf->lock, flags);
+
+	if (callback)
+		callback(callback_context, &event);
+
+	return 0;
+}
+
+static int osf_core_handle_device_status(struct osf_device *osf,
+					 const struct osf_frame *frame)
+{
+	struct osf_status_cache cache = { };
+	struct osf_device_status status;
+	unsigned long flags;
+	int ret;
+
+	ret = osf_protocol_decode_device_status(frame, &status);
+	if (ret)
+		return ret;
+
+	if (status.reserved)
+		return -EPROTO;
+
+	cache.uptime_s = status.uptime_s;
+	cache.status_flags = status.status_flags;
+	cache.error_flags = status.error_flags;
+	cache.dropped_frames = status.dropped_frames;
+	cache.sequence = frame->sequence;
+	cache.valid = true;
+	spin_lock_irqsave(&osf->lock, flags);
+	osf->status_cache = cache;
+	osf->last_sequence = frame->sequence;
+	spin_unlock_irqrestore(&osf->lock, flags);
+
+	return 0;
+}
+
+static int osf_core_handle_capability_report(struct osf_device *osf,
+					     const struct osf_frame *frame)
+{
+	struct osf_capability_cache cache = { };
+	struct osf_capability_report report;
+	unsigned long flags;
+	unsigned int i;
+	int ret;
+
+	ret = osf_protocol_decode_capability_report(frame, &report);
+	if (ret)
+		return ret;
+
+	if (report.capability_count > OSF_MAX_CAPABILITIES)
+		return -E2BIG;
+
+	for (i = 0; i < report.capability_count; i++) {
+		ret = osf_protocol_decode_capability_entry(&report, i,
+							   &cache.entries[i]);
+		if (ret)
+			return ret;
+	}
+
+	cache.capability_count = report.capability_count;
+	cache.sequence = frame->sequence;
+	cache.valid = true;
+	spin_lock_irqsave(&osf->lock, flags);
+	osf->capability_cache = cache;
+	osf->last_sequence = frame->sequence;
+	spin_unlock_irqrestore(&osf->lock, flags);
+
+	return 0;
+}
+
+int osf_core_receive_frame(struct osf_device *osf, const u8 *buf, size_t len)
+{
+	struct osf_frame frame;
+	size_t frame_len;
+	int ret;
+
+	if (!osf || !buf)
+		return -EINVAL;
+
+	ret = osf_protocol_decode_frame(buf, len, &frame, &frame_len);
+	if (ret)
+		return ret;
+
+	if (frame_len != len)
+		return -EMSGSIZE;
+
+	switch (frame.message_type) {
+	case OSF_MSG_SENSOR_SAMPLE:
+		return osf_core_handle_sensor_sample(osf, &frame);
+	case OSF_MSG_DEVICE_STATUS:
+		return osf_core_handle_device_status(osf, &frame);
+	case OSF_MSG_CAPABILITY_REPORT:
+		return osf_core_handle_capability_report(osf, &frame);
+	default:
+		if (frame.message_type >= OSF_RESERVED_MSG_FIRST &&
+		    frame.message_type <= OSF_RESERVED_MSG_LAST)
+			return 0;
+		if (frame.message_type >= OSF_VENDOR_PRIVATE_FIRST)
+			return 0;
+		return -EOPNOTSUPP;
+	}
+}
+
+int osf_core_read_latest_sample(struct osf_device *osf, u16 sensor_type,
+				u16 sensor_index, unsigned int channel,
+				s32 *value)
+{
+	const struct osf_latest_sample *latest;
+	unsigned long flags;
+	int ret = 0;
+
+	if (!osf || !value)
+		return -EINVAL;
+
+	spin_lock_irqsave(&osf->lock, flags);
+	latest = osf_core_find_latest_sample(osf, sensor_type,
+					     sensor_index, false);
+	if (!latest)
+		latest = &osf->latest_sample;
+
+	if (!latest->valid) {
+		ret = -ENODATA;
+		goto out_unlock;
+	}
+
+	if (latest->sensor_type != sensor_type ||
+	    latest->sensor_index != sensor_index) {
+		ret = -ENODATA;
+		goto out_unlock;
+	}
+
+	if (channel >= latest->channel_count) {
+		ret = -ENODATA;
+		goto out_unlock;
+	}
+
+	*value = latest->values[channel];
+
+out_unlock:
+	spin_unlock_irqrestore(&osf->lock, flags);
+
+	return ret;
+}
+
+void osf_core_init_device(struct osf_device *osf)
+{
+	if (!osf)
+		return;
+
+	spin_lock_init(&osf->lock);
+	osf->sample_callback = NULL;
+	osf->sample_callback_context = NULL;
+}
+
+bool osf_core_copy_capability_cache(struct osf_device *osf,
+				    struct osf_capability_cache *cache)
+{
+	unsigned long flags;
+	bool valid;
+
+	if (!osf || !cache)
+		return false;
+
+	spin_lock_irqsave(&osf->lock, flags);
+	valid = osf->capability_cache.valid;
+	if (valid)
+		*cache = osf->capability_cache;
+	spin_unlock_irqrestore(&osf->lock, flags);
+
+	return valid;
+}
+
+bool osf_core_capability_sequence(struct osf_device *osf, u64 *sequence)
+{
+	unsigned long flags;
+	bool valid;
+
+	if (!osf || !sequence)
+		return false;
+
+	spin_lock_irqsave(&osf->lock, flags);
+	valid = osf->capability_cache.valid;
+	if (valid)
+		*sequence = osf->capability_cache.sequence;
+	spin_unlock_irqrestore(&osf->lock, flags);
+
+	return valid;
+}
+
+void osf_core_set_sample_callback(struct osf_device *osf,
+				  osf_sample_callback_t callback,
+				  void *context)
+{
+	unsigned long flags;
+
+	if (!osf)
+		return;
+
+	spin_lock_irqsave(&osf->lock, flags);
+	osf->sample_callback = callback;
+	osf->sample_callback_context = context;
+	spin_unlock_irqrestore(&osf->lock, flags);
+}
+
+static int __init osf_core_init(void)
+{
+	return osf_serdev_register_driver();
+}
+
+static void __exit osf_core_exit(void)
+{
+	osf_serdev_unregister_driver();
+}
+
+module_init(osf_core_init);
+module_exit(osf_core_exit);
+
+MODULE_DESCRIPTION("Open Sensor Fusion IIO skeleton");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/opensensorfusion/osf_core.h b/drivers/iio/opensensorfusion/osf_core.h
new file mode 100644
index 000000000..882683721
--- /dev/null
+++ b/drivers/iio/opensensorfusion/osf_core.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _OSF_CORE_H
+#define _OSF_CORE_H
+
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#include "osf_protocol.h"
+
+#define OSF_MAX_SAMPLE_CHANNELS	3
+#define OSF_MAX_CAPABILITIES	16
+#define OSF_MAX_LATEST_SAMPLES	OSF_MAX_CAPABILITIES
+
+struct osf_latest_sample {
+	u16 sensor_type;
+	u16 sensor_index;
+	u16 channel_count;
+	u16 sample_format;
+	u32 scale_nano;
+	s32 values[OSF_MAX_SAMPLE_CHANNELS];
+	u64 sequence;
+	u64 timestamp_us;
+	bool valid;
+};
+
+struct osf_sample_event {
+	u16 sensor_type;
+	u16 sensor_index;
+	u16 channel_count;
+	u16 sample_format;
+	u32 scale_nano;
+	s32 values[OSF_MAX_SAMPLE_CHANNELS];
+	u64 sequence;
+	u64 timestamp_us;
+	s64 host_timestamp_ns;
+};
+
+typedef void (*osf_sample_callback_t)(void *context,
+				      const struct osf_sample_event *event);
+
+struct osf_capability_cache {
+	u16 capability_count;
+	struct osf_capability_entry entries[OSF_MAX_CAPABILITIES];
+	u64 sequence;
+	bool valid;
+};
+
+struct osf_status_cache {
+	u32 uptime_s;
+	u32 status_flags;
+	u32 error_flags;
+	u32 dropped_frames;
+	u64 sequence;
+	bool valid;
+};
+
+struct osf_device {
+	/* Protects latest sample, capability, status, and callback state. */
+	spinlock_t lock;
+	struct osf_latest_sample latest_sample;
+	struct osf_latest_sample latest_samples[OSF_MAX_LATEST_SAMPLES];
+	struct osf_capability_cache capability_cache;
+	struct osf_status_cache status_cache;
+	u64 last_sequence;
+	osf_sample_callback_t sample_callback;
+	void *sample_callback_context;
+};
+
+void osf_core_init_device(struct osf_device *osf);
+int osf_core_receive_frame(struct osf_device *osf, const u8 *buf, size_t len);
+int osf_core_read_latest_sample(struct osf_device *osf, u16 sensor_type,
+				u16 sensor_index, unsigned int channel,
+				s32 *value);
+bool osf_core_copy_capability_cache(struct osf_device *osf,
+				    struct osf_capability_cache *cache);
+bool osf_core_capability_sequence(struct osf_device *osf, u64 *sequence);
+void osf_core_set_sample_callback(struct osf_device *osf,
+				  osf_sample_callback_t callback,
+				  void *context);
+
+#endif
diff --git a/drivers/iio/opensensorfusion/osf_iio.c b/drivers/iio/opensensorfusion/osf_iio.c
new file mode 100644
index 000000000..3d2674e43
--- /dev/null
+++ b/drivers/iio/opensensorfusion/osf_iio.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/kfifo_buf.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+
+#include "osf_core.h"
+#include "osf_iio.h"
+
+#define OSF_SCALE_NANO		1000000000U
+#define OSF_IIO_SCAN_BYTES						\
+	(ALIGN(OSF_MAX_SAMPLE_CHANNELS * sizeof(s32), sizeof(s64)) +	\
+	 sizeof(s64))
+
+struct osf_iio_sensor_spec {
+	u16 sensor_type;
+	u16 channel_count;
+	const char *name;
+	const struct iio_chan_spec *channels;
+	unsigned int num_channels;
+};
+
+struct osf_iio_state {
+	const struct osf_iio_sensor_spec *spec;
+	u32 scale_nano;
+	u16 sensor_index;
+	struct osf_device *osf;
+};
+
+#define OSF_SCAN_TYPE_S32						\
+	{								\
+		.sign = 's',						\
+		.realbits = 32,					\
+		.storagebits = 32,					\
+		.endianness = IIO_LE,					\
+	}
+
+#define OSF_MOD_CHAN(_type, _mod, _idx)				\
+	{								\
+		.type = (_type),					\
+		.modified = 1,					\
+		.channel2 = (_mod),					\
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
+		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),	\
+		.scan_index = (_idx),					\
+		.scan_type = OSF_SCAN_TYPE_S32,			\
+	}
+
+#define OSF_CHAN(_type, _idx)					\
+	{								\
+		.type = (_type),					\
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
+		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),	\
+		.scan_index = (_idx),					\
+		.scan_type = OSF_SCAN_TYPE_S32,			\
+	}
+
+static const struct iio_chan_spec osf_accel_channels[] = {
+	OSF_MOD_CHAN(IIO_ACCEL, IIO_MOD_X, 0),
+	OSF_MOD_CHAN(IIO_ACCEL, IIO_MOD_Y, 1),
+	OSF_MOD_CHAN(IIO_ACCEL, IIO_MOD_Z, 2),
+	IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static const struct iio_chan_spec osf_gyro_channels[] = {
+	OSF_MOD_CHAN(IIO_ANGL_VEL, IIO_MOD_X, 0),
+	OSF_MOD_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, 1),
+	OSF_MOD_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, 2),
+	IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static const struct iio_chan_spec osf_mag_channels[] = {
+	OSF_MOD_CHAN(IIO_MAGN, IIO_MOD_X, 0),
+	OSF_MOD_CHAN(IIO_MAGN, IIO_MOD_Y, 1),
+	OSF_MOD_CHAN(IIO_MAGN, IIO_MOD_Z, 2),
+	IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static const struct iio_chan_spec osf_temp_channels[] = {
+	OSF_CHAN(IIO_TEMP, 0),
+	IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct osf_iio_sensor_spec osf_iio_sensor_specs[] = {
+	{
+		.sensor_type = OSF_SENSOR_ACCELEROMETER,
+		.channel_count = 3,
+		.name = "osf-accel",
+		.channels = osf_accel_channels,
+		.num_channels = ARRAY_SIZE(osf_accel_channels),
+	},
+	{
+		.sensor_type = OSF_SENSOR_GYROSCOPE,
+		.channel_count = 3,
+		.name = "osf-gyro",
+		.channels = osf_gyro_channels,
+		.num_channels = ARRAY_SIZE(osf_gyro_channels),
+	},
+	{
+		.sensor_type = OSF_SENSOR_MAGNETOMETER,
+		.channel_count = 3,
+		.name = "osf-magn",
+		.channels = osf_mag_channels,
+		.num_channels = ARRAY_SIZE(osf_mag_channels),
+	},
+	{
+		.sensor_type = OSF_SENSOR_TEMPERATURE,
+		.channel_count = 1,
+		.name = "osf-temp",
+		.channels = osf_temp_channels,
+		.num_channels = ARRAY_SIZE(osf_temp_channels),
+	},
+};
+
+static const struct osf_iio_sensor_spec *
+osf_iio_find_sensor_spec(u16 sensor_type, u16 channel_count)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(osf_iio_sensor_specs); i++) {
+		if (osf_iio_sensor_specs[i].sensor_type == sensor_type &&
+		    osf_iio_sensor_specs[i].channel_count == channel_count)
+			return &osf_iio_sensor_specs[i];
+	}
+
+	return NULL;
+}
+
+bool osf_iio_sensor_supported(u16 sensor_type, u16 channel_count)
+{
+	return !!osf_iio_find_sensor_spec(sensor_type, channel_count);
+}
+
+const char *osf_iio_sensor_name(u16 sensor_type)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(osf_iio_sensor_specs); i++) {
+		if (osf_iio_sensor_specs[i].sensor_type == sensor_type)
+			return osf_iio_sensor_specs[i].name;
+	}
+
+	return NULL;
+}
+
+static int osf_iio_read_raw(struct iio_dev *indio_dev,
+			    const struct iio_chan_spec *chan, int *val,
+			    int *val2, long mask)
+{
+	struct osf_iio_state *state = iio_priv(indio_dev);
+	s32 raw;
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		if (!state->osf)
+			return -ENODATA;
+
+		if (chan->scan_index < 0)
+			return -EINVAL;
+
+		ret = osf_core_read_latest_sample(state->osf,
+						  state->spec->sensor_type,
+						  state->sensor_index,
+						  chan->scan_index, &raw);
+		if (ret)
+			return ret;
+
+		*val = raw;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		*val = state->scale_nano / OSF_SCALE_NANO;
+		*val2 = state->scale_nano % OSF_SCALE_NANO;
+		return IIO_VAL_INT_PLUS_NANO;
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info osf_iio_info = {
+	.read_raw = osf_iio_read_raw,
+};
+
+int osf_iio_push_sample(struct iio_dev *indio_dev,
+			const struct osf_sample_event *event)
+{
+	struct osf_iio_state *state;
+	u8 scan[OSF_IIO_SCAN_BYTES] __aligned(8) = { };
+	s32 *scan_values = (s32 *)scan;
+	unsigned int i;
+
+	if (!indio_dev || !event)
+		return -EINVAL;
+
+	if (!iio_buffer_enabled(indio_dev))
+		return 0;
+
+	state = iio_priv(indio_dev);
+	if (event->sensor_type != state->spec->sensor_type ||
+	    event->sensor_index != state->sensor_index)
+		return -EINVAL;
+
+	if (event->sample_format != OSF_SAMPLE_FORMAT_S32 ||
+	    event->channel_count != state->spec->channel_count ||
+	    event->channel_count > OSF_MAX_SAMPLE_CHANNELS)
+		return -EINVAL;
+
+	for (i = 0; i < event->channel_count; i++)
+		scan_values[i] = event->values[i];
+
+	return iio_push_to_buffers_with_timestamp(indio_dev, scan,
+						  event->host_timestamp_ns);
+}
+
+int osf_iio_register_sensor(struct device *dev,
+			    const struct osf_capability_entry *entry,
+			    void *driver_data, struct iio_dev **indio_dev)
+{
+	const struct osf_iio_sensor_spec *spec;
+	struct osf_iio_state *state;
+	struct iio_dev *iio_dev;
+	int ret;
+
+	if (!dev || !entry)
+		return -EINVAL;
+
+	spec = osf_iio_find_sensor_spec(entry->sensor_type,
+					entry->channel_count);
+	if (!spec)
+		return -EOPNOTSUPP;
+
+	if (entry->sample_format != OSF_SAMPLE_FORMAT_S32)
+		return -EOPNOTSUPP;
+
+	iio_dev = devm_iio_device_alloc(dev, sizeof(*state));
+	if (!iio_dev)
+		return -ENOMEM;
+
+	state = iio_priv(iio_dev);
+	state->spec = spec;
+	state->scale_nano = entry->scale_nano;
+	state->sensor_index = entry->sensor_index;
+	state->osf = driver_data;
+
+	iio_dev->name = spec->name;
+	iio_dev->info = &osf_iio_info;
+	iio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;
+	iio_dev->channels = spec->channels;
+	iio_dev->num_channels = spec->num_channels;
+
+	ret = devm_iio_kfifo_buffer_setup(dev, iio_dev, NULL);
+	if (ret)
+		return ret;
+
+	ret = devm_iio_device_register(dev, iio_dev);
+	if (ret)
+		return ret;
+
+	if (indio_dev)
+		*indio_dev = iio_dev;
+
+	return 0;
+}
diff --git a/drivers/iio/opensensorfusion/osf_iio.h b/drivers/iio/opensensorfusion/osf_iio.h
new file mode 100644
index 000000000..1e8d7a39b
--- /dev/null
+++ b/drivers/iio/opensensorfusion/osf_iio.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _OSF_IIO_H
+#define _OSF_IIO_H
+
+#include <linux/types.h>
+
+#include "osf_protocol.h"
+
+struct device;
+struct iio_dev;
+struct osf_sample_event;
+
+int osf_iio_register_sensor(struct device *dev,
+			    const struct osf_capability_entry *entry,
+			    void *driver_data, struct iio_dev **indio_dev);
+int osf_iio_push_sample(struct iio_dev *indio_dev,
+			const struct osf_sample_event *event);
+bool osf_iio_sensor_supported(u16 sensor_type, u16 channel_count);
+const char *osf_iio_sensor_name(u16 sensor_type);
+
+#endif
diff --git a/drivers/iio/opensensorfusion/osf_protocol.c b/drivers/iio/opensensorfusion/osf_protocol.c
new file mode 100644
index 000000000..8235d3af5
--- /dev/null
+++ b/drivers/iio/opensensorfusion/osf_protocol.c
@@ -0,0 +1,234 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/crc32.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+
+#include "osf_protocol.h"
+
+#define OSF_CRC32_INIT		0xffffffffU
+#define OSF_CRC32_XOROUT	0xffffffffU
+
+static u16 osf_get_le16(const u8 *buf)
+{
+	return buf[0] | buf[1] << 8;
+}
+
+static u32 osf_get_le32(const u8 *buf)
+{
+	return (u32)buf[0] | (u32)buf[1] << 8 |
+	       (u32)buf[2] << 16 | (u32)buf[3] << 24;
+}
+
+static u64 osf_get_le64(const u8 *buf)
+{
+	return (u64)osf_get_le32(buf) | (u64)osf_get_le32(buf + 4) << 32;
+}
+
+static bool osf_sensor_type_valid(u16 sensor_type)
+{
+	return sensor_type >= OSF_SENSOR_ACCELEROMETER &&
+	       sensor_type <= OSF_SENSOR_PROXIMITY;
+}
+
+static u32 osf_crc32_ieee(const u8 *buf, size_t len)
+{
+	return crc32_le(OSF_CRC32_INIT, buf, len) ^ OSF_CRC32_XOROUT;
+}
+
+int osf_protocol_decode_frame(const u8 *buf, size_t len,
+			      struct osf_frame *frame, size_t *frame_len)
+{
+	u32 expected_crc;
+	u32 actual_crc;
+	u32 payload_len;
+	size_t total_len;
+	u8 major;
+
+	if (!buf || !frame || !frame_len)
+		return -EINVAL;
+
+	if (len < OSF_FRAME_MIN_LEN)
+		return -EMSGSIZE;
+
+	if (buf[0] != 'O' || buf[1] != 'S' || buf[2] != 'F' || buf[3] != '0')
+		return -EPROTO;
+
+	major = buf[4];
+	if (major != OSF_PROTOCOL_MAJOR)
+		return -EPROTO;
+
+	if (osf_get_le16(buf + 6) != OSF_FRAME_HEADER_LEN)
+		return -EPROTO;
+
+	payload_len = osf_get_le32(buf + 10);
+	if (payload_len > len - OSF_FRAME_MIN_LEN)
+		return -EMSGSIZE;
+
+	total_len = OSF_FRAME_HEADER_LEN + payload_len + OSF_FRAME_CRC_LEN;
+	expected_crc = osf_crc32_ieee(buf, OSF_FRAME_HEADER_LEN + payload_len);
+	actual_crc = osf_get_le32(buf + OSF_FRAME_HEADER_LEN + payload_len);
+
+	if (actual_crc != expected_crc)
+		return -EBADMSG;
+
+	frame->protocol_minor = buf[5];
+	frame->message_type = osf_get_le16(buf + 8);
+	frame->payload_len = payload_len;
+	frame->sequence = osf_get_le64(buf + 14);
+	frame->timestamp_us = osf_get_le64(buf + 22);
+	frame->flags = osf_get_le32(buf + 30);
+	frame->reserved = osf_get_le32(buf + 34);
+	frame->payload = buf + OSF_FRAME_HEADER_LEN;
+	frame->crc = actual_crc;
+	*frame_len = total_len;
+
+	return 0;
+}
+
+int osf_protocol_decode_sensor_sample(const struct osf_frame *frame,
+				      struct osf_sensor_sample *sample)
+{
+	u16 channel_count;
+	u16 sample_format;
+	u32 expected_len;
+	const u8 *payload;
+
+	if (!frame || !sample || !frame->payload)
+		return -EINVAL;
+
+	if (frame->message_type != OSF_MSG_SENSOR_SAMPLE)
+		return -EPROTO;
+
+	if (frame->payload_len < OSF_SENSOR_SAMPLE_BASE_LEN)
+		return -EMSGSIZE;
+
+	payload = frame->payload;
+	channel_count = osf_get_le16(payload + 4);
+	sample_format = osf_get_le16(payload + 6);
+
+	if (sample_format != OSF_SAMPLE_FORMAT_S32)
+		return -EPROTO;
+
+	expected_len = OSF_SENSOR_SAMPLE_BASE_LEN + channel_count * sizeof(s32);
+	if (frame->payload_len != expected_len)
+		return -EMSGSIZE;
+
+	sample->sensor_type = osf_get_le16(payload);
+	sample->sensor_index = osf_get_le16(payload + 2);
+	sample->channel_count = channel_count;
+	sample->sample_format = sample_format;
+	sample->scale_nano = osf_get_le32(payload + 8);
+	sample->reserved = osf_get_le32(payload + 12);
+	sample->samples = payload + OSF_SENSOR_SAMPLE_BASE_LEN;
+
+	return 0;
+}
+
+int osf_protocol_sensor_sample_value(const struct osf_sensor_sample *sample,
+				     unsigned int index, s32 *value)
+{
+	if (!sample || !sample->samples || !value)
+		return -EINVAL;
+
+	if (index >= sample->channel_count)
+		return -ERANGE;
+
+	*value = (s32)osf_get_le32(sample->samples + index * sizeof(s32));
+
+	return 0;
+}
+
+int osf_protocol_decode_device_status(const struct osf_frame *frame,
+				      struct osf_device_status *status)
+{
+	const u8 *payload;
+
+	if (!frame || !status || !frame->payload)
+		return -EINVAL;
+
+	if (frame->message_type != OSF_MSG_DEVICE_STATUS)
+		return -EPROTO;
+
+	if (frame->payload_len != OSF_DEVICE_STATUS_LEN)
+		return -EMSGSIZE;
+
+	payload = frame->payload;
+	status->uptime_s = osf_get_le32(payload);
+	status->status_flags = osf_get_le32(payload + 4);
+	status->error_flags = osf_get_le32(payload + 8);
+	status->dropped_frames = osf_get_le32(payload + 12);
+	status->reserved = osf_get_le32(payload + 16);
+
+	return 0;
+}
+
+int osf_protocol_decode_capability_report(const struct osf_frame *frame,
+					  struct osf_capability_report *report)
+{
+	u16 capability_count;
+	u32 expected_len;
+	const u8 *payload;
+
+	if (!frame || !report || !frame->payload)
+		return -EINVAL;
+
+	if (frame->message_type != OSF_MSG_CAPABILITY_REPORT)
+		return -EPROTO;
+
+	if (frame->payload_len < OSF_CAP_REPORT_BASE_LEN)
+		return -EMSGSIZE;
+
+	payload = frame->payload;
+	capability_count = osf_get_le16(payload);
+	expected_len = OSF_CAP_REPORT_BASE_LEN +
+		       capability_count * OSF_CAP_SENSOR_ENTRY_LEN;
+
+	if (frame->payload_len != expected_len)
+		return -EMSGSIZE;
+
+	report->capability_count = capability_count;
+	report->reserved = osf_get_le16(payload + 2);
+	if (report->reserved)
+		return -EPROTO;
+
+	report->entries = payload + OSF_CAP_REPORT_BASE_LEN;
+
+	return 0;
+}
+
+int osf_protocol_decode_capability_entry(const struct osf_capability_report *report,
+					 unsigned int index,
+					 struct osf_capability_entry *entry)
+{
+	const u8 *payload;
+
+	if (!report || !report->entries || !entry)
+		return -EINVAL;
+
+	if (index >= report->capability_count)
+		return -ERANGE;
+
+	payload = report->entries + index * OSF_CAP_SENSOR_ENTRY_LEN;
+	entry->sensor_type = osf_get_le16(payload);
+	entry->sensor_index = osf_get_le16(payload + 2);
+	entry->channel_count = osf_get_le16(payload + 4);
+	entry->sample_format = osf_get_le16(payload + 6);
+	entry->scale_nano = osf_get_le32(payload + 8);
+	entry->flags = osf_get_le32(payload + 12);
+	entry->reserved = osf_get_le32(payload + 16);
+
+	if (!osf_sensor_type_valid(entry->sensor_type))
+		return -EPROTO;
+
+	if (entry->sample_format != OSF_SAMPLE_FORMAT_S32)
+		return -EPROTO;
+
+	if (entry->flags & ~OSF_CAPABILITY_FLAGS_MASK)
+		return -EPROTO;
+
+	if (entry->reserved)
+		return -EPROTO;
+
+	return 0;
+}
diff --git a/drivers/iio/opensensorfusion/osf_protocol.h b/drivers/iio/opensensorfusion/osf_protocol.h
new file mode 100644
index 000000000..fd6e9581f
--- /dev/null
+++ b/drivers/iio/opensensorfusion/osf_protocol.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _OSF_PROTOCOL_H
+#define _OSF_PROTOCOL_H
+
+#include <linux/types.h>
+
+#define OSF_PROTOCOL_MAJOR		0
+#define OSF_PROTOCOL_MINOR		0
+#define OSF_FRAME_HEADER_LEN		38
+#define OSF_FRAME_CRC_LEN		4
+#define OSF_FRAME_MIN_LEN		(OSF_FRAME_HEADER_LEN + OSF_FRAME_CRC_LEN)
+
+#define OSF_SENSOR_SAMPLE_BASE_LEN	16
+#define OSF_DEVICE_STATUS_LEN		20
+#define OSF_CAP_REPORT_BASE_LEN		4
+#define OSF_CAP_SENSOR_ENTRY_LEN		20
+#define OSF_CAPABILITY_FLAGS_MASK	0x00000003U
+
+enum osf_message_type {
+	OSF_MSG_SENSOR_SAMPLE		= 0x0001,
+	OSF_MSG_DEVICE_STATUS		= 0x0002,
+	OSF_MSG_CAPABILITY_REPORT	= 0x0003,
+};
+
+enum osf_sensor_type {
+	OSF_SENSOR_ACCELEROMETER		= 0x0001,
+	OSF_SENSOR_GYROSCOPE		= 0x0002,
+	OSF_SENSOR_MAGNETOMETER		= 0x0003,
+	OSF_SENSOR_BAROMETER		= 0x0004,
+	OSF_SENSOR_TEMPERATURE		= 0x0005,
+	OSF_SENSOR_HUMIDITY		= 0x0006,
+	OSF_SENSOR_AMBIENT_LIGHT		= 0x0007,
+	OSF_SENSOR_PROXIMITY		= 0x0008,
+};
+
+enum osf_sample_format {
+	OSF_SAMPLE_FORMAT_S32		= 0x0001,
+};
+
+struct osf_frame {
+	u8 protocol_minor;
+	u16 message_type;
+	u32 payload_len;
+	u64 sequence;
+	u64 timestamp_us;
+	u32 flags;
+	u32 reserved;
+	const u8 *payload;
+	u32 crc;
+};
+
+struct osf_sensor_sample {
+	u16 sensor_type;
+	u16 sensor_index;
+	u16 channel_count;
+	u16 sample_format;
+	u32 scale_nano;
+	u32 reserved;
+	const u8 *samples;
+};
+
+struct osf_device_status {
+	u32 uptime_s;
+	u32 status_flags;
+	u32 error_flags;
+	u32 dropped_frames;
+	u32 reserved;
+};
+
+struct osf_capability_report {
+	u16 capability_count;
+	u16 reserved;
+	const u8 *entries;
+};
+
+struct osf_capability_entry {
+	u16 sensor_type;
+	u16 sensor_index;
+	u16 channel_count;
+	u16 sample_format;
+	u32 scale_nano;
+	u32 flags;
+	u32 reserved;
+};
+
+int osf_protocol_decode_frame(const u8 *buf, size_t len,
+			      struct osf_frame *frame, size_t *frame_len);
+int osf_protocol_decode_sensor_sample(const struct osf_frame *frame,
+				      struct osf_sensor_sample *sample);
+int osf_protocol_decode_device_status(const struct osf_frame *frame,
+				      struct osf_device_status *status);
+int osf_protocol_decode_capability_report(const struct osf_frame *frame,
+					  struct osf_capability_report *report);
+int osf_protocol_decode_capability_entry(const struct osf_capability_report *report,
+					 unsigned int index,
+					 struct osf_capability_entry *entry);
+int osf_protocol_sensor_sample_value(const struct osf_sensor_sample *sample,
+				     unsigned int index, s32 *value);
+
+#endif
diff --git a/drivers/iio/opensensorfusion/osf_serdev.c b/drivers/iio/opensensorfusion/osf_serdev.c
new file mode 100644
index 000000000..1827760c2
--- /dev/null
+++ b/drivers/iio/opensensorfusion/osf_serdev.c
@@ -0,0 +1,354 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/atomic.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/serdev.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include "osf_core.h"
+#include "osf_iio.h"
+#include "osf_serdev.h"
+#include "osf_stream.h"
+
+#define OSF_SERDEV_BAUD		115200
+#define OSF_SERDEV_BUFFER_QUEUE_LEN	32
+
+struct osf_serdev_iio {
+	u16 sensor_type;
+	u16 sensor_index;
+	struct iio_dev *indio_dev;
+};
+
+struct osf_serdev {
+	struct serdev_device *serdev;
+	struct osf_device osf;
+	struct osf_stream stream;
+	struct work_struct iio_register_work;
+	struct work_struct buffer_push_work;
+	/* Serializes IIO device table access and registration work. */
+	struct mutex iio_lock;
+	/* Protects sample events queued from the receive path. */
+	spinlock_t sample_lock;
+	struct osf_serdev_iio iio[OSF_MAX_CAPABILITIES];
+	unsigned int iio_count;
+	struct osf_sample_event sample_queue[OSF_SERDEV_BUFFER_QUEUE_LEN];
+	unsigned int sample_queue_head;
+	unsigned int sample_queue_tail;
+	unsigned int sample_queue_count;
+	u64 sample_queue_drops;
+	atomic64_t capability_sequence_scheduled;
+};
+
+static struct iio_dev *osf_serdev_find_iio(struct osf_serdev *osf_uart,
+					   const struct osf_sample_event *event)
+{
+	unsigned int i;
+
+	for (i = 0; i < osf_uart->iio_count; i++) {
+		if (osf_uart->iio[i].sensor_type == event->sensor_type &&
+		    osf_uart->iio[i].sensor_index == event->sensor_index)
+			return osf_uart->iio[i].indio_dev;
+	}
+
+	return NULL;
+}
+
+static bool osf_serdev_iio_registered(struct osf_serdev *osf_uart,
+				      const struct osf_capability_entry *entry)
+{
+	unsigned int i;
+
+	for (i = 0; i < osf_uart->iio_count; i++) {
+		if (osf_uart->iio[i].sensor_type == entry->sensor_type &&
+		    osf_uart->iio[i].sensor_index == entry->sensor_index)
+			return true;
+	}
+
+	return false;
+}
+
+static int osf_serdev_register_iio(struct osf_serdev *osf_uart,
+				   const struct osf_capability_entry *entry)
+{
+	struct device *dev = &osf_uart->serdev->dev;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	if (osf_serdev_iio_registered(osf_uart, entry))
+		return 0;
+
+	if (!osf_iio_sensor_supported(entry->sensor_type, entry->channel_count) ||
+	    entry->sample_format != OSF_SAMPLE_FORMAT_S32) {
+		dev_dbg(dev,
+			"ignoring unsupported capability sensor=%u index=%u channels=%u format=%u\n",
+			entry->sensor_type, entry->sensor_index,
+			entry->channel_count, entry->sample_format);
+		return 0;
+	}
+
+	if (osf_uart->iio_count >= ARRAY_SIZE(osf_uart->iio)) {
+		dev_warn(dev, "IIO registration table full, ignoring sensor=%u index=%u\n",
+			 entry->sensor_type, entry->sensor_index);
+		return 0;
+	}
+
+	ret = osf_iio_register_sensor(dev, entry, &osf_uart->osf, &indio_dev);
+	if (ret)
+		return ret;
+
+	osf_uart->iio[osf_uart->iio_count].sensor_type = entry->sensor_type;
+	osf_uart->iio[osf_uart->iio_count].sensor_index = entry->sensor_index;
+	osf_uart->iio[osf_uart->iio_count].indio_dev = indio_dev;
+	osf_uart->iio_count++;
+
+	return 1;
+}
+
+static void osf_serdev_iio_register_work(struct work_struct *work)
+{
+	struct osf_serdev *osf_uart =
+		container_of(work, struct osf_serdev, iio_register_work);
+	struct device *dev = &osf_uart->serdev->dev;
+	struct osf_capability_cache cache;
+	unsigned int registered = 0;
+	unsigned int i;
+	int ret;
+
+	if (!osf_core_copy_capability_cache(&osf_uart->osf, &cache))
+		return;
+
+	mutex_lock(&osf_uart->iio_lock);
+	for (i = 0; i < cache.capability_count; i++) {
+		ret = osf_serdev_register_iio(osf_uart, &cache.entries[i]);
+		if (ret) {
+			if (ret > 0) {
+				registered++;
+				continue;
+			}
+
+			dev_err(dev,
+				"failed to register IIO sensor=%u index=%u: %d\n",
+				cache.entries[i].sensor_type,
+				cache.entries[i].sensor_index, ret);
+		}
+	}
+	mutex_unlock(&osf_uart->iio_lock);
+
+	if (registered)
+		dev_info(dev,
+			 "registered %u Open Sensor Fusion IIO devices from capability report seq=%llu\n",
+			 registered, (unsigned long long)cache.sequence);
+}
+
+static bool osf_serdev_dequeue_sample(struct osf_serdev *osf_uart,
+				      struct osf_sample_event *event)
+{
+	unsigned long flags;
+	bool valid = false;
+
+	spin_lock_irqsave(&osf_uart->sample_lock, flags);
+	if (osf_uart->sample_queue_count) {
+		*event = osf_uart->sample_queue[osf_uart->sample_queue_tail];
+		osf_uart->sample_queue_tail =
+			(osf_uart->sample_queue_tail + 1) %
+			ARRAY_SIZE(osf_uart->sample_queue);
+		osf_uart->sample_queue_count--;
+		valid = true;
+	}
+	spin_unlock_irqrestore(&osf_uart->sample_lock, flags);
+
+	return valid;
+}
+
+static void osf_serdev_buffer_push_work(struct work_struct *work)
+{
+	struct osf_serdev *osf_uart =
+		container_of(work, struct osf_serdev, buffer_push_work);
+	struct device *dev = &osf_uart->serdev->dev;
+	struct osf_sample_event event;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	while (osf_serdev_dequeue_sample(osf_uart, &event)) {
+		mutex_lock(&osf_uart->iio_lock);
+		indio_dev = osf_serdev_find_iio(osf_uart, &event);
+		if (indio_dev) {
+			ret = osf_iio_push_sample(indio_dev, &event);
+			if (ret)
+				dev_dbg_ratelimited(dev,
+						    "failed to push IIO buffer sample sensor=%u index=%u ret=%d\n",
+						    event.sensor_type,
+						    event.sensor_index, ret);
+		}
+		mutex_unlock(&osf_uart->iio_lock);
+	}
+}
+
+static void osf_serdev_sample_ready(void *context,
+				    const struct osf_sample_event *event)
+{
+	struct osf_serdev *osf_uart = context;
+	unsigned long flags;
+	u64 drops = 0;
+	bool queued = false;
+
+	if (!osf_uart || !event)
+		return;
+
+	spin_lock_irqsave(&osf_uart->sample_lock, flags);
+	if (osf_uart->sample_queue_count < ARRAY_SIZE(osf_uart->sample_queue)) {
+		osf_uart->sample_queue[osf_uart->sample_queue_head] = *event;
+		osf_uart->sample_queue_head =
+			(osf_uart->sample_queue_head + 1) %
+			ARRAY_SIZE(osf_uart->sample_queue);
+		osf_uart->sample_queue_count++;
+		queued = true;
+	} else {
+		osf_uart->sample_queue_drops++;
+		drops = osf_uart->sample_queue_drops;
+	}
+	spin_unlock_irqrestore(&osf_uart->sample_lock, flags);
+
+	if (queued) {
+		schedule_work(&osf_uart->buffer_push_work);
+		return;
+	}
+
+	dev_dbg_ratelimited(&osf_uart->serdev->dev,
+			    "dropping IIO buffer sample sensor=%u index=%u drops=%llu\n",
+			    event->sensor_type, event->sensor_index,
+			    (unsigned long long)drops);
+}
+
+static void osf_serdev_schedule_iio_registration(struct osf_serdev *osf_uart)
+{
+	s64 scheduled_seq;
+	u64 sequence;
+
+	if (!osf_core_capability_sequence(&osf_uart->osf, &sequence))
+		return;
+
+	scheduled_seq = atomic64_read(&osf_uart->capability_sequence_scheduled);
+	if (scheduled_seq == (s64)sequence)
+		return;
+
+	atomic64_set(&osf_uart->capability_sequence_scheduled, (s64)sequence);
+	schedule_work(&osf_uart->iio_register_work);
+}
+
+static size_t osf_serdev_receive_buf(struct serdev_device *serdev,
+				     const u8 *buf, size_t count)
+{
+	struct osf_serdev *osf_uart = serdev_device_get_drvdata(serdev);
+	const struct osf_stream_stats *stats;
+	u64 valid_before;
+	int ret;
+
+	valid_before = osf_uart->stream.stats.valid_frames;
+	ret = osf_stream_receive_bytes(&osf_uart->stream, buf, count);
+	stats = &osf_uart->stream.stats;
+	osf_serdev_schedule_iio_registration(osf_uart);
+
+	if (ret || stats->valid_frames != valid_before)
+		dev_dbg_ratelimited(&serdev->dev,
+				    "rx count=%zu valid=%llu bad_magic=%llu bad_crc=%llu partial=%llu dropped=%llu ret=%d\n",
+				    count,
+				    (unsigned long long)stats->valid_frames,
+				    (unsigned long long)stats->bad_magic_resyncs,
+				    (unsigned long long)stats->bad_crc_frames,
+				    (unsigned long long)stats->partial_frames,
+				    (unsigned long long)stats->dropped_bytes,
+				    ret);
+
+	return count;
+}
+
+static const struct serdev_device_ops osf_serdev_ops = {
+	.receive_buf = osf_serdev_receive_buf,
+};
+
+static int osf_serdev_probe(struct serdev_device *serdev)
+{
+	struct osf_serdev *osf_uart;
+	unsigned int baudrate;
+	int ret;
+
+	osf_uart = devm_kzalloc(&serdev->dev, sizeof(*osf_uart), GFP_KERNEL);
+	if (!osf_uart)
+		return -ENOMEM;
+
+	osf_uart->serdev = serdev;
+	osf_core_init_device(&osf_uart->osf);
+	osf_stream_init(&osf_uart->stream, &osf_uart->osf);
+	INIT_WORK(&osf_uart->iio_register_work, osf_serdev_iio_register_work);
+	INIT_WORK(&osf_uart->buffer_push_work, osf_serdev_buffer_push_work);
+	mutex_init(&osf_uart->iio_lock);
+	spin_lock_init(&osf_uart->sample_lock);
+	atomic64_set(&osf_uart->capability_sequence_scheduled, -1);
+	osf_core_set_sample_callback(&osf_uart->osf, osf_serdev_sample_ready,
+				     osf_uart);
+
+	serdev_device_set_drvdata(serdev, osf_uart);
+	serdev_device_set_client_ops(serdev, &osf_serdev_ops);
+
+	ret = serdev_device_open(serdev);
+	if (ret)
+		return ret;
+
+	baudrate = serdev_device_set_baudrate(serdev, OSF_SERDEV_BAUD);
+	if (baudrate != OSF_SERDEV_BAUD)
+		dev_warn(&serdev->dev, "requested %u baud, controller set %u\n",
+			 OSF_SERDEV_BAUD, baudrate);
+
+	serdev_device_set_flow_control(serdev, false);
+
+	dev_info(&serdev->dev, "Open Sensor Fusion UART opened at %u baud\n",
+		 OSF_SERDEV_BAUD);
+
+	return 0;
+}
+
+static void osf_serdev_remove(struct serdev_device *serdev)
+{
+	struct osf_serdev *osf_uart = serdev_device_get_drvdata(serdev);
+
+	osf_core_set_sample_callback(&osf_uart->osf, NULL, NULL);
+	serdev_device_close(serdev);
+	cancel_work_sync(&osf_uart->iio_register_work);
+	cancel_work_sync(&osf_uart->buffer_push_work);
+	osf_stream_reset(&osf_uart->stream);
+}
+
+static const struct of_device_id osf_serdev_of_match[] = {
+	{ .compatible = "opensensorfusion,osf-uart" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, osf_serdev_of_match);
+
+static struct serdev_device_driver osf_serdev_driver = {
+	.probe = osf_serdev_probe,
+	.remove = osf_serdev_remove,
+	.driver = {
+		.name = "open-sensor-fusion-uart",
+		.of_match_table = osf_serdev_of_match,
+	},
+};
+
+int osf_serdev_register_driver(void)
+{
+	return serdev_device_driver_register(&osf_serdev_driver);
+}
+
+void osf_serdev_unregister_driver(void)
+{
+	serdev_device_driver_unregister(&osf_serdev_driver);
+}
diff --git a/drivers/iio/opensensorfusion/osf_serdev.h b/drivers/iio/opensensorfusion/osf_serdev.h
new file mode 100644
index 000000000..278b088df
--- /dev/null
+++ b/drivers/iio/opensensorfusion/osf_serdev.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _OSF_SERDEV_H
+#define _OSF_SERDEV_H
+
+int osf_serdev_register_driver(void);
+void osf_serdev_unregister_driver(void);
+
+#endif
diff --git a/drivers/iio/opensensorfusion/osf_stream.c b/drivers/iio/opensensorfusion/osf_stream.c
new file mode 100644
index 000000000..e9addf70e
--- /dev/null
+++ b/drivers/iio/opensensorfusion/osf_stream.c
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include "osf_core.h"
+#include "osf_protocol.h"
+#include "osf_stream.h"
+
+#define OSF_STREAM_MAGIC_LEN	4
+#define OSF_STREAM_MAX_PAYLOAD_LEN				\
+	(OSF_STREAM_MAX_FRAME_LEN - OSF_FRAME_HEADER_LEN - OSF_FRAME_CRC_LEN)
+
+static const u8 osf_stream_magic[OSF_STREAM_MAGIC_LEN] = {
+	'O', 'S', 'F', '0',
+};
+
+static u16 osf_stream_get_le16(const u8 *buf)
+{
+	return buf[0] | buf[1] << 8;
+}
+
+static u32 osf_stream_get_le32(const u8 *buf)
+{
+	return (u32)buf[0] | (u32)buf[1] << 8 |
+	       (u32)buf[2] << 16 | (u32)buf[3] << 24;
+}
+
+static void osf_stream_discard(struct osf_stream *stream, size_t count)
+{
+	if (count >= stream->len) {
+		stream->len = 0;
+		return;
+	}
+
+	memmove(stream->buf, stream->buf + count, stream->len - count);
+	stream->len -= count;
+}
+
+static bool osf_stream_magic_match(const u8 *buf, size_t len)
+{
+	return !memcmp(buf, osf_stream_magic, len);
+}
+
+static size_t osf_stream_resync(struct osf_stream *stream)
+{
+	size_t old_len = stream->len;
+	size_t match_len;
+	size_t i;
+
+	for (i = 0; i < stream->len; i++) {
+		match_len = stream->len - i;
+		if (match_len > OSF_STREAM_MAGIC_LEN)
+			match_len = OSF_STREAM_MAGIC_LEN;
+
+		if (osf_stream_magic_match(stream->buf + i, match_len)) {
+			if (i)
+				osf_stream_discard(stream, i);
+			return i;
+		}
+	}
+
+	stream->len = 0;
+	return old_len;
+}
+
+static int osf_stream_process(struct osf_stream *stream)
+{
+	struct osf_frame frame;
+	size_t decoded_len;
+	size_t discarded;
+	size_t frame_len;
+	u32 payload_len;
+	int first_err = 0;
+	int ret;
+
+	while (stream->len) {
+		discarded = osf_stream_resync(stream);
+		if (discarded) {
+			stream->stats.bad_magic_resyncs++;
+			stream->stats.dropped_bytes += discarded;
+			if (!first_err)
+				first_err = -EPROTO;
+		}
+
+		if (!stream->len)
+			break;
+
+		if (stream->len < OSF_FRAME_HEADER_LEN) {
+			stream->stats.partial_frames++;
+			break;
+		}
+
+		if (osf_stream_get_le16(stream->buf + 6) !=
+		    OSF_FRAME_HEADER_LEN) {
+			stream->stats.dropped_bytes++;
+			osf_stream_discard(stream, 1);
+			if (!first_err)
+				first_err = -EPROTO;
+			continue;
+		}
+
+		payload_len = osf_stream_get_le32(stream->buf + 10);
+		if (payload_len > OSF_STREAM_MAX_PAYLOAD_LEN) {
+			stream->stats.dropped_bytes++;
+			osf_stream_discard(stream, 1);
+			if (!first_err)
+				first_err = -EMSGSIZE;
+			continue;
+		}
+
+		frame_len = OSF_FRAME_HEADER_LEN + payload_len + OSF_FRAME_CRC_LEN;
+		if (stream->len < frame_len) {
+			stream->stats.partial_frames++;
+			break;
+		}
+
+		ret = osf_protocol_decode_frame(stream->buf, frame_len, &frame,
+						&decoded_len);
+		if (ret) {
+			if (ret == -EBADMSG)
+				stream->stats.bad_crc_frames++;
+			stream->stats.dropped_bytes++;
+			osf_stream_discard(stream, 1);
+			if (!first_err)
+				first_err = ret;
+			continue;
+		}
+
+		if (decoded_len != frame_len) {
+			stream->stats.dropped_bytes++;
+			osf_stream_discard(stream, 1);
+			if (!first_err)
+				first_err = -EMSGSIZE;
+			continue;
+		}
+
+		ret = osf_core_receive_frame(stream->osf, stream->buf, frame_len);
+		if (ret) {
+			osf_stream_discard(stream, frame_len);
+			if (!first_err)
+				first_err = ret;
+			continue;
+		}
+
+		stream->stats.valid_frames++;
+		osf_stream_discard(stream, frame_len);
+	}
+
+	return first_err;
+}
+
+void osf_stream_init(struct osf_stream *stream, struct osf_device *osf)
+{
+	if (!stream)
+		return;
+
+	stream->osf = osf;
+	stream->len = 0;
+	memset(&stream->stats, 0, sizeof(stream->stats));
+}
+
+void osf_stream_reset(struct osf_stream *stream)
+{
+	if (stream) {
+		stream->len = 0;
+		memset(&stream->stats, 0, sizeof(stream->stats));
+	}
+}
+
+int osf_stream_receive_bytes(struct osf_stream *stream, const u8 *buf,
+			     size_t len)
+{
+	size_t copy_len;
+	size_t space;
+	int first_err = 0;
+	int ret;
+
+	if (!stream || !stream->osf || (!buf && len))
+		return -EINVAL;
+
+	if (!len) {
+		ret = osf_stream_process(stream);
+		if (ret && !first_err)
+			first_err = ret;
+		return first_err;
+	}
+
+	while (len) {
+		space = OSF_STREAM_MAX_FRAME_LEN - stream->len;
+		if (!space) {
+			stream->stats.dropped_bytes++;
+			osf_stream_discard(stream, 1);
+			if (!first_err)
+				first_err = -EMSGSIZE;
+			continue;
+		}
+
+		copy_len = len < space ? len : space;
+		memcpy(stream->buf + stream->len, buf, copy_len);
+		stream->len += copy_len;
+		buf += copy_len;
+		len -= copy_len;
+
+		ret = osf_stream_process(stream);
+		if (ret && !first_err)
+			first_err = ret;
+	}
+
+	return first_err;
+}
diff --git a/drivers/iio/opensensorfusion/osf_stream.h b/drivers/iio/opensensorfusion/osf_stream.h
new file mode 100644
index 000000000..f7f9477fe
--- /dev/null
+++ b/drivers/iio/opensensorfusion/osf_stream.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _OSF_STREAM_H
+#define _OSF_STREAM_H
+
+#include <linux/types.h>
+
+#define OSF_STREAM_MAX_FRAME_LEN	4096
+
+struct osf_device;
+
+struct osf_stream_stats {
+	u64 valid_frames;
+	u64 bad_magic_resyncs;
+	u64 bad_crc_frames;
+	u64 partial_frames;
+	u64 dropped_bytes;
+};
+
+struct osf_stream {
+	struct osf_device *osf;
+	u8 buf[OSF_STREAM_MAX_FRAME_LEN];
+	size_t len;
+	struct osf_stream_stats stats;
+};
+
+void osf_stream_init(struct osf_stream *stream, struct osf_device *osf);
+void osf_stream_reset(struct osf_stream *stream);
+int osf_stream_receive_bytes(struct osf_stream *stream, const u8 *buf,
+			     size_t len);
+
+#endif
-- 
2.43.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH RFC 3/3] MAINTAINERS: add Open Sensor Fusion IIO driver entry
  2026-05-20  7:28 [PATCH RFC 0/3] iio: add Open Sensor Fusion UART driver Jinseob Kim
  2026-05-20  7:28 ` [PATCH RFC 1/3] dt-bindings: iio: imu: add Open Sensor Fusion UART binding Jinseob Kim
  2026-05-20  7:28 ` [PATCH RFC 2/3] iio: osf: add Open Sensor Fusion UART IIO driver Jinseob Kim
@ 2026-05-20  7:28 ` Jinseob Kim
  2026-05-20  7:59   ` Joshua Crofts
  2026-05-20 11:55 ` [PATCH RFC 0/3] iio: add Open Sensor Fusion UART driver Andy Shevchenko
  2026-05-22 15:22 ` Jonathan Cameron
  4 siblings, 1 reply; 12+ messages in thread
From: Jinseob Kim @ 2026-05-20  7:28 UTC (permalink / raw)
  To: Jonathan Cameron, linux-iio
  Cc: David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	devicetree, linux-kernel

Add a maintainer entry for the Open Sensor Fusion IIO driver and binding.

The driver path reflects the current RFC layout. It may need to be adjusted
after review if the driver moves under a different IIO directory.

Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
---
 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index c2c6d7927..001ba4ea6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20011,6 +20011,13 @@ F:	Documentation/devicetree/
 F:	arch/*/boot/dts/
 F:	include/dt-bindings/
 
+OPEN SENSOR FUSION IIO DRIVER
+M:	Jinseob Kim <kimjinseob88@gmail.com>
+L:	linux-iio@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/iio/imu/opensensorfusion,osf-uart.yaml
+F:	drivers/iio/opensensorfusion/
+
 OPENCOMPUTE PTP CLOCK DRIVER
 M:	Vadim Fedorenko <vadim.fedorenko@linux.dev>
 L:	netdev@vger.kernel.org
-- 
2.43.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH RFC 3/3] MAINTAINERS: add Open Sensor Fusion IIO driver entry
  2026-05-20  7:28 ` [PATCH RFC 3/3] MAINTAINERS: add Open Sensor Fusion IIO driver entry Jinseob Kim
@ 2026-05-20  7:59   ` Joshua Crofts
       [not found]     ` <CALMSewJinjbnHT_sOgWmHVeThv3su_E6fioyAjEWKzf7uROc2Q@mail.gmail.com>
  0 siblings, 1 reply; 12+ messages in thread
From: Joshua Crofts @ 2026-05-20  7:59 UTC (permalink / raw)
  To: Jinseob Kim
  Cc: Jonathan Cameron, linux-iio, David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	devicetree, linux-kernel

On Wed, 20 May 2026 at 09:35, Jinseob Kim <kimjinseob88@gmail.com> wrote:
>
> Add a maintainer entry for the Open Sensor Fusion IIO driver and binding.
>
> The driver path reflects the current RFC layout. It may need to be adjusted
> after review if the driver moves under a different IIO directory.
>
> Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
> ---
>  MAINTAINERS | 7 +++++++
>  1 file changed, 7 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index c2c6d7927..001ba4ea6 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -20011,6 +20011,13 @@ F:     Documentation/devicetree/
>  F:     arch/*/boot/dts/
>  F:     include/dt-bindings/
>
> +OPEN SENSOR FUSION IIO DRIVER
> +M:     Jinseob Kim <kimjinseob88@gmail.com>
> +L:     linux-iio@vger.kernel.org
> +S:     Maintained
> +F:     Documentation/devicetree/bindings/iio/imu/opensensorfusion,osf-uart.yaml

All of the above should be added in your dt-bindings patch. If you're
adding a new file,
it's expected for you to also add a new entry in MAINTAINERS in the same patch,
otherwise it creates confusion and an unnecessary additional patch that the
maintainer could overlook (highly unlikely, but could happen).

> +F:     drivers/iio/opensensorfusion/

And this line would be added in the actual driver patch.

-- 
Kind regards

CJD

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH RFC 3/3] MAINTAINERS: add Open Sensor Fusion IIO driver entry
       [not found]     ` <CALMSewJinjbnHT_sOgWmHVeThv3su_E6fioyAjEWKzf7uROc2Q@mail.gmail.com>
@ 2026-05-20  8:14       ` j k
  2026-05-20  8:22         ` Joshua Crofts
  0 siblings, 1 reply; 12+ messages in thread
From: j k @ 2026-05-20  8:14 UTC (permalink / raw)
  To: Joshua Crofts
  Cc: Jonathan Cameron, linux-iio, David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	devicetree, linux-kernel

Sorry, resending in plain text.

Thanks, that makes sense.

I will fold the binding-related MAINTAINERS entry into the dt-bindings patch

and add the driver path entry in the driver patch in the next revision.

Thanks again

Jinseob


2026년 5월 20일 (수) 오후 5:07, j k <kimjinseob88@gmail.com>님이 작성:
>
> Thanks, that makes sense.
>
> I will fold the binding-related MAINTAINERS entry into the dt-bindings patch
> and add the driver path entry in the driver patch in the next revision.
>
> Thanks again
>
> Jinseob
>
> 2026년 5월 20일 (수) 오후 4:59, Joshua Crofts <joshua.crofts1@gmail.com>님이 작성:
>>
>> On Wed, 20 May 2026 at 09:35, Jinseob Kim <kimjinseob88@gmail.com> wrote:
>> >
>> > Add a maintainer entry for the Open Sensor Fusion IIO driver and binding.
>> >
>> > The driver path reflects the current RFC layout. It may need to be adjusted
>> > after review if the driver moves under a different IIO directory.
>> >
>> > Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
>> > ---
>> >  MAINTAINERS | 7 +++++++
>> >  1 file changed, 7 insertions(+)
>> >
>> > diff --git a/MAINTAINERS b/MAINTAINERS
>> > index c2c6d7927..001ba4ea6 100644
>> > --- a/MAINTAINERS
>> > +++ b/MAINTAINERS
>> > @@ -20011,6 +20011,13 @@ F:     Documentation/devicetree/
>> >  F:     arch/*/boot/dts/
>> >  F:     include/dt-bindings/
>> >
>> > +OPEN SENSOR FUSION IIO DRIVER
>> > +M:     Jinseob Kim <kimjinseob88@gmail.com>
>> > +L:     linux-iio@vger.kernel.org
>> > +S:     Maintained
>> > +F:     Documentation/devicetree/bindings/iio/imu/opensensorfusion,osf-uart.yaml
>>
>> All of the above should be added in your dt-bindings patch. If you're
>> adding a new file,
>> it's expected for you to also add a new entry in MAINTAINERS in the same patch,
>> otherwise it creates confusion and an unnecessary additional patch that the
>> maintainer could overlook (highly unlikely, but could happen).
>>
>> > +F:     drivers/iio/opensensorfusion/
>>
>> And this line would be added in the actual driver patch.
>>
>> --
>> Kind regards
>>
>> CJD

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH RFC 3/3] MAINTAINERS: add Open Sensor Fusion IIO driver entry
  2026-05-20  8:14       ` j k
@ 2026-05-20  8:22         ` Joshua Crofts
  2026-05-20 11:14           ` Jonathan Cameron
  0 siblings, 1 reply; 12+ messages in thread
From: Joshua Crofts @ 2026-05-20  8:22 UTC (permalink / raw)
  To: j k
  Cc: Jonathan Cameron, linux-iio, David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	devicetree, linux-kernel

On Wed, 20 May 2026 at 10:14, j k <kimjinseob88@gmail.com> wrote:
>
> Sorry, resending in plain text.
>
> Thanks, that makes sense.
>
> I will fold the binding-related MAINTAINERS entry into the dt-bindings patch
>
> and add the driver path entry in the driver patch in the next revision.
>
> Thanks again
>
> Jinseob

No worries, also please don't top post, just inline your comments like my
previous reply. Check the mailing list on lore.kernel.org to see how to
reply to messages etc.

-- 
Kind regards

CJD

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH RFC 3/3] MAINTAINERS: add Open Sensor Fusion IIO driver entry
  2026-05-20  8:22         ` Joshua Crofts
@ 2026-05-20 11:14           ` Jonathan Cameron
  0 siblings, 0 replies; 12+ messages in thread
From: Jonathan Cameron @ 2026-05-20 11:14 UTC (permalink / raw)
  To: Joshua Crofts
  Cc: j k, linux-iio, David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	devicetree, linux-kernel

On Wed, 20 May 2026 10:22:05 +0200
Joshua Crofts <joshua.crofts1@gmail.com> wrote:

> On Wed, 20 May 2026 at 10:14, j k <kimjinseob88@gmail.com> wrote:
> >
> > Sorry, resending in plain text.
> >
> > Thanks, that makes sense.
> >
> > I will fold the binding-related MAINTAINERS entry into the dt-bindings patch
> >
> > and add the driver path entry in the driver patch in the next revision.
> >
> > Thanks again
> >
> > Jinseob  
> 
> No worries, also please don't top post, just inline your comments like my
> previous reply. Check the mailing list on lore.kernel.org to see how to
> reply to messages etc.
> 
Plus for future reference - if you agree with feedback don't reply at all.

Thanks etc all goes in the change log of v2.

Saves a few seconds for a bunch of reviewers who have far too many emails
to read :)

Jonathan



^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH RFC 0/3] iio: add Open Sensor Fusion UART driver
  2026-05-20  7:28 [PATCH RFC 0/3] iio: add Open Sensor Fusion UART driver Jinseob Kim
                   ` (2 preceding siblings ...)
  2026-05-20  7:28 ` [PATCH RFC 3/3] MAINTAINERS: add Open Sensor Fusion IIO driver entry Jinseob Kim
@ 2026-05-20 11:55 ` Andy Shevchenko
  2026-05-22 15:22 ` Jonathan Cameron
  4 siblings, 0 replies; 12+ messages in thread
From: Andy Shevchenko @ 2026-05-20 11:55 UTC (permalink / raw)
  To: Jinseob Kim
  Cc: Jonathan Cameron, linux-iio, David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	devicetree, linux-kernel

On Wed, May 20, 2026 at 1:47 PM Jinseob Kim <kimjinseob88@gmail.com> wrote:
>
> Open Sensor Fusion is a UART-attached sensor aggregation device. The current
> prototype sends OSF protocol v0 frames over a host UART link. This RFC adds the
> first Linux IIO receive path for that UART stream.
>
> The first transport is serdev UART. USB, real sensor reads, fusion output, and
> production timestamp correlation are intentionally left out of this series.
>
> The driver code is kept in one patch for the first RFC to avoid intermediate
> non-buildable states. The internal files are still split by protocol, stream,
> core, transport, and IIO code.
>
> Current validation coverage:
>
> - STM32F405 test firmware OSF protocol v0 UART stream at 115200 8N1.
> - Raspberry Pi 4 serdev probe and receive path to osf_core_receive_frame().
> - CAPABILITY_REPORT-driven IIO device registration.
> - IIO read_raw for accel, gyro, magn, and temp samples.
> - IIO software kfifo buffer userspace read.
> - scan decode helper for buffered samples.
> - dt_binding_check pass for the binding.
> - checkpatch.pl --strict with ERROR 0, WARNING 0, CHECK 0.
> - W=1 source compile/link phase clean in the staging kernel tree.
> - modpost unresolved symbol warnings came from the staging tree missing
>   Module.symvers.
>
> Known limits:
>
> - The sample source is synthetic stream data, not real ICM-42688-P or MMC5983MA
>   sensor reads.
> - The IIO timestamp is Linux host receive time. Device time correlation is still
>   open.
> - Runtime capability removal is not implemented.
> - The staging layout uses drivers/iio/opensensorfusion/. The final directory is
>   open for review.
>
> Review feedback wanted on the IIO device layout, timestamp policy, binding
> shape, and driver directory.


>  16 files changed, 1706 insertions(+)

No.
Split it to at least 5 patches (as far as I can briefly see it's
achievable): base + feature1 + feature2 + …

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH RFC 1/3] dt-bindings: iio: imu: add Open Sensor Fusion UART binding
  2026-05-20  7:28 ` [PATCH RFC 1/3] dt-bindings: iio: imu: add Open Sensor Fusion UART binding Jinseob Kim
@ 2026-05-20 16:33   ` Conor Dooley
  0 siblings, 0 replies; 12+ messages in thread
From: Conor Dooley @ 2026-05-20 16:33 UTC (permalink / raw)
  To: Jinseob Kim
  Cc: Jonathan Cameron, linux-iio, David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	devicetree, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 3295 bytes --]

On Wed, May 20, 2026 at 04:28:41PM +0900, Jinseob Kim wrote:
> Add a binding for a UART-attached Open Sensor Fusion device.
> 
> The device is exposed as a serdev child node using the
> opensensorfusion,osf-uart compatible string.

> Keep Raspberry Pi overlay and
> test notes outside the binding.

This should not be in the commit message, it's not relevant info.

> The binding has been checked with dt_binding_check.

This is a requirement, there's no reason to state it in the commit
message.

> 
> Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
> ---
>  .../iio/imu/opensensorfusion,osf-uart.yaml    | 33 +++++++++++++++++++
>  1 file changed, 33 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/iio/imu/opensensorfusion,osf-uart.yaml
> 
> diff --git a/Documentation/devicetree/bindings/iio/imu/opensensorfusion,osf-uart.yaml b/Documentation/devicetree/bindings/iio/imu/opensensorfusion,osf-uart.yaml
> new file mode 100644
> index 000000000..6f329c326
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/imu/opensensorfusion,osf-uart.yaml

I find it hard to believe that this is an "imu". It sounds more like
this is a bus or remoteproc than anything else.

> @@ -0,0 +1,33 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/imu/opensensorfusion,osf-uart.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Open Sensor Fusion UART Sensor Aggregation Device
> +
> +maintainers:
> +  - Jinseob Kim <kimjinseob88@gmail.com>
> +
> +description: |
> +  Open Sensor Fusion is a UART-attached sensor aggregation device. The device

What actually is this device?
Googling "open sensor fusion" gives me
https://github.com/memsindustrygroup/Open-Source-Sensor-Fusion
and
https://www.nxp.com/company/about-nxp/smarter-world-blog/BL-OPEN-SOURCE-SENSOR-FUSION
but neither of these explain to me what sort of device we are looking at
here. Seems as if it is not /a/ device at all, but could be one of many
devices, provided that they implement the other side of the protocol?

> +  exposes an OSF protocol v0 data stream over its host UART interface and may

What does "v0" mean here? Is the data format not complete yet?
Are versions of the protocol likely to be backwards compatible?
Will the device identify what version of the protocol it implements?

> +  report capabilities and samples for multiple sensor classes.

Yeah, then definitely this is not an "imu".

> +
> +properties:
> +  compatible:
> +    const: opensensorfusion,osf-uart

If this is some sort of remote processor slaved to the os, it seems possible
that the may be required to do turn its power or reset it somehow. Having
nothing but a compatible seems incomplete to me but I have no idea what
the complete set of properties would actually be.

Probably a good bit of discussion is needed here, but for now:
pw-bot: changes-requested

Thanks,
Conor.

> +
> +required:
> +  - compatible
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    serial {
> +        sensor {
> +            compatible = "opensensorfusion,osf-uart";
> +        };
> +    };
> +...
> -- 
> 2.43.0
> 
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH RFC 0/3] iio: add Open Sensor Fusion UART driver
  2026-05-20  7:28 [PATCH RFC 0/3] iio: add Open Sensor Fusion UART driver Jinseob Kim
                   ` (3 preceding siblings ...)
  2026-05-20 11:55 ` [PATCH RFC 0/3] iio: add Open Sensor Fusion UART driver Andy Shevchenko
@ 2026-05-22 15:22 ` Jonathan Cameron
  4 siblings, 0 replies; 12+ messages in thread
From: Jonathan Cameron @ 2026-05-22 15:22 UTC (permalink / raw)
  To: Jinseob Kim
  Cc: linux-iio, David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	devicetree, linux-kernel

On Wed, 20 May 2026 16:28:40 +0900
Jinseob Kim <kimjinseob88@gmail.com> wrote:

> Open Sensor Fusion is a UART-attached sensor aggregation device. The current
> prototype sends OSF protocol v0 frames over a host UART link. This RFC adds the
> first Linux IIO receive path for that UART stream.
> 
> The first transport is serdev UART. USB, real sensor reads, fusion output, and
> production timestamp correlation are intentionally left out of this series.
> 
> The driver code is kept in one patch for the first RFC to avoid intermediate
> non-buildable states. The internal files are still split by protocol, stream,
> core, transport, and IIO code.

Even for an RFC you need to break it up.  If you do it right there won't
be non-buildable states.  Take a look at how any complex IIO driver is added.

Key thing here seems to be that we need some references.

Superficially sounds like an open source sensor hub implementation but
we need a datasheet / reference manual to be able to review this properly.

I'll have a first look but it's likely to be somewhat superficial.

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH RFC 2/3] iio: osf: add Open Sensor Fusion UART IIO driver
  2026-05-20  7:28 ` [PATCH RFC 2/3] iio: osf: add Open Sensor Fusion UART IIO driver Jinseob Kim
@ 2026-05-22 16:24   ` Jonathan Cameron
  0 siblings, 0 replies; 12+ messages in thread
From: Jonathan Cameron @ 2026-05-22 16:24 UTC (permalink / raw)
  To: Jinseob Kim
  Cc: linux-iio, David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	devicetree, linux-kernel

On Wed, 20 May 2026 16:28:42 +0900
Jinseob Kim <kimjinseob88@gmail.com> wrote:

> Add the initial Open Sensor Fusion UART IIO driver.
> 
> This includes the protocol v0 parser, OSF0 stream assembler, core
> capability/sample cache, serdev UART transport, IIO read_raw path, and
> software kfifo buffer support.
> 
> The first RFC keeps the driver code in one patch to avoid intermediate
> non-buildable states. The internal files still separate protocol, stream,
> core, transport, and IIO code.
> 
> The tested path is STM32F405 test firmware OSF UART stream to Raspberry Pi
> serdev, then IIO read_raw and buffered userspace reads.
> 
> Not included here: real sensor reads, USB transport, fusion/AHRS/Kalman,
> runtime capability removal, or production timestamp correlation.
> 
There are several things in here I don't follow.  Why do we not
push the data directly to the iio devices when it shows up?
There seems to be a queue in between that I don't understand the purpose
of - that just seems to mean async handling where synchronous should
be fine.

Anyhow, as I mentioned this review is somewhat superficial given the driver
needs a lot of work to get it into a more reviewable state.
In particular break it up into more sensible sized chunks, building up
functionality from device discovery to actually registering IIO devices and then
adding additional features on that base.

> Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>

> diff --git a/drivers/iio/opensensorfusion/osf_core.c b/drivers/iio/opensensorfusion/osf_core.c
> new file mode 100644
> index 000000000..fd2eccefc
> --- /dev/null
> +++ b/drivers/iio/opensensorfusion/osf_core.c
> @@ -0,0 +1,334 @@

> +
> +static int osf_core_handle_sensor_sample(struct osf_device *osf,
> +					 const struct osf_frame *frame)
> +{
> +	osf_sample_callback_t callback;
> +	void *callback_context;
> +	struct osf_latest_sample *latest;
> +	struct osf_sample_event event = { };
> +	struct osf_sensor_sample sample;
> +	s32 values[OSF_MAX_SAMPLE_CHANNELS] = { };
> +	unsigned long flags;
> +	unsigned int i;
> +	int ret;
> +
> +	ret = osf_protocol_decode_sensor_sample(frame, &sample);
> +	if (ret)
> +		return ret;
> +
> +	if (sample.channel_count > OSF_MAX_SAMPLE_CHANNELS)
> +		return -E2BIG;
> +
> +	for (i = 0; i < sample.channel_count; i++) {
> +		ret = osf_protocol_sensor_sample_value(&sample, i, &values[i]);
This converts to CPU endian. That's fine but then your channels aren't little endian.
> +		if (ret)
> +			return ret;
> +	}
> +
> +	event.sensor_type = sample.sensor_type;
> +	event.sensor_index = sample.sensor_index;
> +	event.channel_count = sample.channel_count;
> +	event.sample_format = sample.sample_format;
> +	event.scale_nano = sample.scale_nano;
> +	event.sequence = frame->sequence;
> +	event.timestamp_us = frame->timestamp_us;
> +	event.host_timestamp_ns = ktime_get_ns();

For IIO stuff we have (for weird historical reasons) rather complex clock
handling for timestamps. Please follow that.

> +	for (i = 0; i < sample.channel_count; i++)
> +		event.values[i] = values[i];
> +
> +	spin_lock_irqsave(&osf->lock, flags);
> +	latest = osf_core_find_latest_sample(osf, sample.sensor_type,
> +					     sample.sensor_index, true);
> +	if (!latest) {
> +		spin_unlock_irqrestore(&osf->lock, flags);
> +		return -ENOSPC;
> +	}
> +
> +	osf_core_store_latest_sample(latest, &sample, values, frame);
> +	osf_core_store_latest_sample(&osf->latest_sample, &sample, values,
> +				     frame);
> +	osf->last_sequence = frame->sequence;
> +	callback = osf->sample_callback;
> +	callback_context = osf->sample_callback_context;
> +	spin_unlock_irqrestore(&osf->lock, flags);
> +
> +	if (callback)
> +		callback(callback_context, &event);
> +
> +	return 0;
> +}

> +
> +int osf_core_read_latest_sample(struct osf_device *osf, u16 sensor_type,
> +				u16 sensor_index, unsigned int channel,
> +				s32 *value)
> +{
> +	const struct osf_latest_sample *latest;
> +	unsigned long flags;
> +	int ret = 0;
> +
> +	if (!osf || !value)
> +		return -EINVAL;
> +
> +	spin_lock_irqsave(&osf->lock, flags);

Use guard() and see if you can relax to a mutex. I haven't really worked
out the flow in here yet so maybe you can't.

> +	latest = osf_core_find_latest_sample(osf, sensor_type,
> +					     sensor_index, false);
> +	if (!latest)
> +		latest = &osf->latest_sample;
> +
> +	if (!latest->valid) {
> +		ret = -ENODATA;
> +		goto out_unlock;
> +	}
> +
> +	if (latest->sensor_type != sensor_type ||
> +	    latest->sensor_index != sensor_index) {
> +		ret = -ENODATA;
> +		goto out_unlock;
> +	}
> +
> +	if (channel >= latest->channel_count) {
> +		ret = -ENODATA;
> +		goto out_unlock;
> +	}
> +
> +	*value = latest->values[channel];
> +
> +out_unlock:
> +	spin_unlock_irqrestore(&osf->lock, flags);
> +
> +	return ret;
> +}

> +
> +bool osf_core_capability_sequence(struct osf_device *osf, u64 *sequence)
> +{
> +	unsigned long flags;
> +	bool valid;
> +
> +	if (!osf || !sequence)
> +		return false;
> +
> +	spin_lock_irqsave(&osf->lock, flags);
> +	valid = osf->capability_cache.valid;
> +	if (valid)
> +		*sequence = osf->capability_cache.sequence;
use guard() above
and 	if (valid)
		return false;

	*sequence = ..
	return true;

That way the 'error' path is out of line and can be quickly ignored by readers.
> +	spin_unlock_irqrestore(&osf->lock, flags);
> +
> +	return valid;
> +}
> +
> +void osf_core_set_sample_callback(struct osf_device *osf,
> +				  osf_sample_callback_t callback,
> +				  void *context)
> +{
> +	unsigned long flags;
> +
> +	if (!osf)
> +		return;
> +
> +	spin_lock_irqsave(&osf->lock, flags);
Use guard() for these probably.
> +	osf->sample_callback = callback;
> +	osf->sample_callback_context = context;
> +	spin_unlock_irqrestore(&osf->lock, flags);
> +}
> +
> +static int __init osf_core_init(void)
> +{
> +	return osf_serdev_register_driver();
> +}
> +
> +static void __exit osf_core_exit(void)
> +{
> +	osf_serdev_unregister_driver();
> +}
This smells like code break out not making sense. I'd
expect to see the driver_structure right next to these.

> +
> +module_init(osf_core_init);
> +module_exit(osf_core_exit);

> diff --git a/drivers/iio/opensensorfusion/osf_iio.c b/drivers/iio/opensensorfusion/osf_iio.c
> new file mode 100644
> index 000000000..3d2674e43
> --- /dev/null
> +++ b/drivers/iio/opensensorfusion/osf_iio.c
> @@ -0,0 +1,268 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <linux/bitops.h>
> +#include <linux/device.h>
> +#include <linux/errno.h>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/kfifo_buf.h>
> +#include <linux/kernel.h>
> +#include <linux/types.h>
> +
> +#include "osf_core.h"
> +#include "osf_iio.h"
> +
> +#define OSF_SCALE_NANO		1000000000U
NANO is defined in the kernel headers.

> +#define OSF_IIO_SCAN_BYTES						\
> +	(ALIGN(OSF_MAX_SAMPLE_CHANNELS * sizeof(s32), sizeof(s64)) +	\
> +	 sizeof(s64))
> +
> +struct osf_iio_sensor_spec {
> +	u16 sensor_type;
> +	u16 channel_count;
> +	const char *name;
> +	const struct iio_chan_spec *channels;
> +	unsigned int num_channels;
> +};
> +
> +struct osf_iio_state {
> +	const struct osf_iio_sensor_spec *spec;
> +	u32 scale_nano;
> +	u16 sensor_index;
> +	struct osf_device *osf;
> +};


> +int osf_iio_push_sample(struct iio_dev *indio_dev,
> +			const struct osf_sample_event *event)
> +{
> +	struct osf_iio_state *state;
> +	u8 scan[OSF_IIO_SCAN_BYTES] __aligned(8) = { };
we have macros for this. 
IIO_DECLARE_BUFFER_WITH_TS() which will give you a suitable sized
s32 array so you don't need the cast that follows.

Mind you the buffer is described as little endian so maybe
should be __le32


> +	s32 *scan_values = (s32 *)scan;
> +	unsigned int i;
> +
> +	if (!indio_dev || !event)
> +		return -EINVAL;
> +
> +	if (!iio_buffer_enabled(indio_dev))

I haven't checked, but this feels insufficient. Probably want to force the
device to stay in buffered mode.  iio_device_try_claim_buffer_mode()
(rarely used, but this may be one place that needs it).


> +		return 0;
> +
> +	state = iio_priv(indio_dev);
> +	if (event->sensor_type != state->spec->sensor_type ||
> +	    event->sensor_index != state->sensor_index)
> +		return -EINVAL;
> +
> +	if (event->sample_format != OSF_SAMPLE_FORMAT_S32 ||
> +	    event->channel_count != state->spec->channel_count ||
> +	    event->channel_count > OSF_MAX_SAMPLE_CHANNELS)
> +		return -EINVAL;
> +
> +	for (i = 0; i < event->channel_count; i++)
> +		scan_values[i] = event->values[i];
> +
> +	return iio_push_to_buffers_with_timestamp(indio_dev, scan,
> +						  event->host_timestamp_ns);

For new code iio_push_to_buffers_with_ts()   It has a bounds check that
this version doesn't have.

> +}
> +
> +int osf_iio_register_sensor(struct device *dev,
> +			    const struct osf_capability_entry *entry,
> +			    void *driver_data, struct iio_dev **indio_dev)
> +{
> +	const struct osf_iio_sensor_spec *spec;
> +	struct osf_iio_state *state;
> +	struct iio_dev *iio_dev;
> +	int ret;
> +
> +	if (!dev || !entry)
> +		return -EINVAL;
> +
> +	spec = osf_iio_find_sensor_spec(entry->sensor_type,
> +					entry->channel_count);
> +	if (!spec)
> +		return -EOPNOTSUPP;
> +
> +	if (entry->sample_format != OSF_SAMPLE_FORMAT_S32)
> +		return -EOPNOTSUPP;
> +
> +	iio_dev = devm_iio_device_alloc(dev, sizeof(*state));
> +	if (!iio_dev)
> +		return -ENOMEM;
> +
> +	state = iio_priv(iio_dev);
> +	state->spec = spec;
> +	state->scale_nano = entry->scale_nano;
> +	state->sensor_index = entry->sensor_index;
> +	state->osf = driver_data;
> +
> +	iio_dev->name = spec->name;

What does this end up as?  Should be something close to a part number
though in multifunction parts can include something about the type.

> +	iio_dev->info = &osf_iio_info;
> +	iio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;

Look at devm_iio_kfifo_buffer_setup() and what it does to this.

> +	iio_dev->channels = spec->channels;
> +	iio_dev->num_channels = spec->num_channels;
> +
> +	ret = devm_iio_kfifo_buffer_setup(dev, iio_dev, NULL);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_iio_device_register(dev, iio_dev);
> +	if (ret)
> +		return ret;
> +
> +	if (indio_dev)
> +		*indio_dev = iio_dev;

why would you call this function without?
For kernel code we don't defined against nonsensical parameters.
Catch those in review rather than complicating the code.

> +
> +	return 0;
> +}

> +#endif
> diff --git a/drivers/iio/opensensorfusion/osf_protocol.c b/drivers/iio/opensensorfusion/osf_protocol.c
> new file mode 100644
> index 000000000..8235d3af5
> --- /dev/null
> +++ b/drivers/iio/opensensorfusion/osf_protocol.c
> @@ -0,0 +1,234 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <linux/crc32.h>
> +#include <linux/errno.h>
> +#include <linux/types.h>
> +
> +#include "osf_protocol.h"
> +
> +#define OSF_CRC32_INIT		0xffffffffU
> +#define OSF_CRC32_XOROUT	0xffffffffU
GENMASK(31, 0)

No one likes counting fs.
> +
> +static u16 osf_get_le16(const u8 *buf)
> +{
> +	return buf[0] | buf[1] << 8;
> +}
> +
> +static u32 osf_get_le32(const u8 *buf)
> +{
> +	return (u32)buf[0] | (u32)buf[1] << 8 |
> +	       (u32)buf[2] << 16 | (u32)buf[3] << 24;
> +}
Another copy?  See comments on the one in osf_stream.c

> +
> +static u64 osf_get_le64(const u8 *buf)
> +{
> +	return (u64)osf_get_le32(buf) | (u64)osf_get_le32(buf + 4) << 32;
Looks like
get_unaligned_le64()?


> +}
>

> +int osf_protocol_decode_capability_entry(const struct osf_capability_report *report,
> +					 unsigned int index,
> +					 struct osf_capability_entry *entry)
> +{
> +	const u8 *payload;
> +
> +	if (!report || !report->entries || !entry)
> +		return -EINVAL;
> +
> +	if (index >= report->capability_count)
> +		return -ERANGE;
> +
> +	payload = report->entries + index * OSF_CAP_SENSOR_ENTRY_LEN;
> +	entry->sensor_type = osf_get_le16(payload);
> +	entry->sensor_index = osf_get_le16(payload + 2);
> +	entry->channel_count = osf_get_le16(payload + 4);
> +	entry->sample_format = osf_get_le16(payload + 6);
> +	entry->scale_nano = osf_get_le32(payload + 8);
> +	entry->flags = osf_get_le32(payload + 12);
> +	entry->reserved = osf_get_le32(payload + 16);

Easier to read as
	*entry = (struct osf_capability_entry) {
		.sensor_type = get_unaligned_le16(payload),
		.sensor_index = get_unaligned_le16(payload + 2),
	....

	};

Probably can apply similar in other places.

> +
> +	if (!osf_sensor_type_valid(entry->sensor_type))
> +		return -EPROTO;
> +
> +	if (entry->sample_format != OSF_SAMPLE_FORMAT_S32)
> +		return -EPROTO;
> +
> +	if (entry->flags & ~OSF_CAPABILITY_FLAGS_MASK)
> +		return -EPROTO;
> +
> +	if (entry->reserved)
> +		return -EPROTO;
> +
> +	return 0;
> +}


> diff --git a/drivers/iio/opensensorfusion/osf_serdev.c b/drivers/iio/opensensorfusion/osf_serdev.c
> new file mode 100644
> index 000000000..1827760c2
> --- /dev/null
> +++ b/drivers/iio/opensensorfusion/osf_serdev.c
> @@ -0,0 +1,354 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <linux/atomic.h>
> +#include <linux/device.h>
> +#include <linux/errno.h>
> +#include <linux/kernel.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>

Not seeing this used. Maybe I missed something of specific.
I wouldn't expect there to be anything in a driver like this.


> +#include <linux/serdev.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/types.h>
> +#include <linux/workqueue.h>


> +
> +static int osf_serdev_register_iio(struct osf_serdev *osf_uart,
> +				   const struct osf_capability_entry *entry)
> +{
> +	struct device *dev = &osf_uart->serdev->dev;
> +	struct iio_dev *indio_dev;
> +	int ret;
> +
> +	if (osf_serdev_iio_registered(osf_uart, entry))
> +		return 0;
> +
> +	if (!osf_iio_sensor_supported(entry->sensor_type, entry->channel_count) ||
> +	    entry->sample_format != OSF_SAMPLE_FORMAT_S32) {
> +		dev_dbg(dev,
> +			"ignoring unsupported capability sensor=%u index=%u channels=%u format=%u\n",
> +			entry->sensor_type, entry->sensor_index,
> +			entry->channel_count, entry->sample_format);
> +		return 0;
> +	}
> +
> +	if (osf_uart->iio_count >= ARRAY_SIZE(osf_uart->iio)) {
> +		dev_warn(dev, "IIO registration table full, ignoring sensor=%u index=%u\n",
> +			 entry->sensor_type, entry->sensor_index);
> +		return 0;
> +	}
> +
> +	ret = osf_iio_register_sensor(dev, entry, &osf_uart->osf, &indio_dev);
> +	if (ret)
> +		return ret;
> +
> +	osf_uart->iio[osf_uart->iio_count].sensor_type = entry->sensor_type;
> +	osf_uart->iio[osf_uart->iio_count].sensor_index = entry->sensor_index;
> +	osf_uart->iio[osf_uart->iio_count].indio_dev = indio_dev;

	osf_uart->iio[osf_uart->iio_count++] = (struct osf_serdev_iio) {
		.sensor_type = entry->sensor_type,
		.sensor_index = entry->sensor_index,
		.indio_dev = indio_dev,
	};

> +	osf_uart->iio_count++;
> +
> +	return 1;
> +}
> +
> +static void osf_serdev_iio_register_work(struct work_struct *work)
> +{
> +	struct osf_serdev *osf_uart =
> +		container_of(work, struct osf_serdev, iio_register_work);
> +	struct device *dev = &osf_uart->serdev->dev;
> +	struct osf_capability_cache cache;
> +	unsigned int registered = 0;
> +	unsigned int i;
> +	int ret;
> +
> +	if (!osf_core_copy_capability_cache(&osf_uart->osf, &cache))
> +		return;
> +
> +	mutex_lock(&osf_uart->iio_lock);
I'd use guard(mutex)(); here

> +	for (i = 0; i < cache.capability_count; i++) {
> +		ret = osf_serdev_register_iio(osf_uart, &cache.entries[i]);
> +		if (ret) {
> +			if (ret > 0) {
> +				registered++;
> +				continue;
> +			}
> +
			Then

			if (ret < 0) {
				dev_err();
				return ret;
			}

			registered++;

> +			dev_err(dev,
> +				"failed to register IIO sensor=%u index=%u: %d\n",
> +				cache.entries[i].sensor_type,
> +				cache.entries[i].sensor_index, ret);
> +		}
> +	}
> +	mutex_unlock(&osf_uart->iio_lock);
> +
> +	if (registered)
With above, this only doesn't happen if the count was 0.  Maybe check that separately
so you can then do this unconditionally.

> +		dev_info(dev,
> +			 "registered %u Open Sensor Fusion IIO devices from capability report seq=%llu\n",
> +			 registered, (unsigned long long)cache.sequence);
> +}

> +
> +static int osf_serdev_probe(struct serdev_device *serdev)
> +{
> +	struct osf_serdev *osf_uart;
> +	unsigned int baudrate;
> +	int ret;
> +
> +	osf_uart = devm_kzalloc(&serdev->dev, sizeof(*osf_uart), GFP_KERNEL);
> +	if (!osf_uart)
> +		return -ENOMEM;
> +
> +	osf_uart->serdev = serdev;
> +	osf_core_init_device(&osf_uart->osf);
> +	osf_stream_init(&osf_uart->stream, &osf_uart->osf);
> +	INIT_WORK(&osf_uart->iio_register_work, osf_serdev_iio_register_work);
> +	INIT_WORK(&osf_uart->buffer_push_work, osf_serdev_buffer_push_work);
> +	mutex_init(&osf_uart->iio_lock);
> +	spin_lock_init(&osf_uart->sample_lock);
> +	atomic64_set(&osf_uart->capability_sequence_scheduled, -1);
> +	osf_core_set_sample_callback(&osf_uart->osf, osf_serdev_sample_ready,
> +				     osf_uart);
> +
> +	serdev_device_set_drvdata(serdev, osf_uart);
> +	serdev_device_set_client_ops(serdev, &osf_serdev_ops);
> +
> +	ret = serdev_device_open(serdev);
> +	if (ret)
> +		return ret;
> +
> +	baudrate = serdev_device_set_baudrate(serdev, OSF_SERDEV_BAUD);
> +	if (baudrate != OSF_SERDEV_BAUD)
> +		dev_warn(&serdev->dev, "requested %u baud, controller set %u\n",
> +			 OSF_SERDEV_BAUD, baudrate);
> +
> +	serdev_device_set_flow_control(serdev, false);
> +
> +	dev_info(&serdev->dev, "Open Sensor Fusion UART opened at %u baud\n",
> +		 OSF_SERDEV_BAUD);

Generally we try to avoid prints like this.  Is it useful to know the baud rate?
It's easy to tell if the rest worked.

> +
> +	return 0;
> +}
> +
> +static void osf_serdev_remove(struct serdev_device *serdev)
> +{
> +	struct osf_serdev *osf_uart = serdev_device_get_drvdata(serdev);
> +
> +	osf_core_set_sample_callback(&osf_uart->osf, NULL, NULL);
> +	serdev_device_close(serdev);
> +	cancel_work_sync(&osf_uart->iio_register_work);
> +	cancel_work_sync(&osf_uart->buffer_push_work);
> +	osf_stream_reset(&osf_uart->stream);
> +}

> +
> +int osf_serdev_register_driver(void)
> +{
> +	return serdev_device_driver_register(&osf_serdev_driver);
> +}
> +
> +void osf_serdev_unregister_driver(void)
> +{
> +	serdev_device_driver_unregister(&osf_serdev_driver);
> +}
As above, feels like the file break up is making this more complex.
These should be next to module_init and allow us of the macros
that generate that code for us.


> +#endif
> diff --git a/drivers/iio/opensensorfusion/osf_stream.c b/drivers/iio/opensensorfusion/osf_stream.c
> new file mode 100644
> index 000000000..e9addf70e
> --- /dev/null
> +++ b/drivers/iio/opensensorfusion/osf_stream.c
> @@ -0,0 +1,212 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <linux/errno.h>
> +#include <linux/string.h>
> +#include <linux/types.h>
> +
> +#include "osf_core.h"
> +#include "osf_protocol.h"
> +#include "osf_stream.h"
> +
> +#define OSF_STREAM_MAGIC_LEN	4
> +#define OSF_STREAM_MAX_PAYLOAD_LEN				\
> +	(OSF_STREAM_MAX_FRAME_LEN - OSF_FRAME_HEADER_LEN - OSF_FRAME_CRC_LEN)
> +
> +static const u8 osf_stream_magic[OSF_STREAM_MAGIC_LEN] = {
> +	'O', 'S', 'F', '0',
> +};
> +
> +static u16 osf_stream_get_le16(const u8 *buf)
> +{
> +	return buf[0] | buf[1] << 8;
We have standard functions in the kernel for this. Don't reinvent the wheel.

get_unaligned_le16()

> +}
> +
> +static u32 osf_stream_get_le32(const u8 *buf)

Likewise.

> +{
> +	return (u32)buf[0] | (u32)buf[1] << 8 |
> +	       (u32)buf[2] << 16 | (u32)buf[3] << 24;
> +}

> +
> +static bool osf_stream_magic_match(const u8 *buf, size_t len)
> +{
> +	return !memcmp(buf, osf_stream_magic, len);

Just do this inline.  If it was stand alone the length match
would belong in here not at the caller.


> +}
> +
> +static size_t osf_stream_resync(struct osf_stream *stream)
> +{
> +	size_t old_len = stream->len;
> +	size_t match_len;
> +	size_t i;
> +
> +	for (i = 0; i < stream->len; i++) {
In modern kernel code can do something like:
	for (size_t i = 0; i < stream->len; i++)

> +		match_len = stream->len - i;
> +		if (match_len > OSF_STREAM_MAGIC_LEN)
> +			match_len = OSF_STREAM_MAGIC_LEN;

		match_len = min(match_len, OSF_STREAM_MAGIC_LEN);

> +
> +		if (osf_stream_magic_match(stream->buf + i, match_len)) {
> +			if (i)
> +				osf_stream_discard(stream, i);
I'd special case 0 as that's your 'good' path.
			if (i == 0)
				return 0; 

			osf_stream_discard(stream, i);
			return i;

> +			return i;
> +		}
> +	}
> +
> +	stream->len = 0;
Whilst this is a short cut, I'd still call osf_stream_discard() as then it
is obvious that this is also discarding. Failing that add a comment 
/* Discard the lot */


> +	return old_len;
Why is it useful to return old_len if failed to resync?
That's non obvious enough I think this needs documentation on what
the return value is.

I'd be tempted to return a bool for whether it succeeded and
pass a pointer to put how much was discarded in.  That
will make the condition below simpler.

> +}
> +
> +static int osf_stream_process(struct osf_stream *stream)
> +{
> +	struct osf_frame frame;
> +	size_t decoded_len;
> +	size_t discarded;
> +	size_t frame_len;
> +	u32 payload_len;
> +	int first_err = 0;
> +	int ret;
> +
> +	while (stream->len) {
> +		discarded = osf_stream_resync(stream);
> +		if (discarded) {
> +			stream->stats.bad_magic_resyncs++;
> +			stream->stats.dropped_bytes += discarded;
> +			if (!first_err)
> +				first_err = -EPROTO;
So if we resynced but discarded a few 
> +		}
> +
> +		if (!stream->len)

This took a bit of figuring out. It's the resync failed path. I'd rather
see an explicit return bool for that. The discarded variable can be
parameter as mentioned above.


> +			break;
> +
> +		if (stream->len < OSF_FRAME_HEADER_LEN) {
> +			stream->stats.partial_frames++;
> +			break;
> +		}
> +
> +		if (osf_stream_get_le16(stream->buf + 6) !=

		if (get_unaligned_le16(stream->buf + 6) != OSF_FRAME_HEADER_LEN) {

Slightly long lines are fine in IIO as long as they aren't too much past 80
and improve readability.


> +		    OSF_FRAME_HEADER_LEN) {
> +			stream->stats.dropped_bytes++;
> +			osf_stream_discard(stream, 1);

Needs a comment I think. Not obvious to me at all why you'd drop 1 byte.
Is the idea it forces a resync?  Might as well drop the magic string
in that case.  I'd add a helper for this so you can do something like..

osf_force_resync(stream); in all these cases or something easier
to follow than a 1 byte drop.


> +			if (!first_err)
> +				first_err = -EPROTO;
> +			continue;
> +		}
> +
> +		payload_len = osf_stream_get_le32(stream->buf + 10);

get_unaligned_le32()

> +		if (payload_len > OSF_STREAM_MAX_PAYLOAD_LEN) {
> +			stream->stats.dropped_bytes++;
> +			osf_stream_discard(stream, 1);
> +			if (!first_err)
> +				first_err = -EMSGSIZE;
> +			continue;
> +		}
> +
> +		frame_len = OSF_FRAME_HEADER_LEN + payload_len + OSF_FRAME_CRC_LEN;
> +		if (stream->len < frame_len) {
> +			stream->stats.partial_frames++;
> +			break;
> +		}
> +
> +		ret = osf_protocol_decode_frame(stream->buf, frame_len, &frame,
> +						&decoded_len);
> +		if (ret) {
> +			if (ret == -EBADMSG)
> +				stream->stats.bad_crc_frames++;
> +			stream->stats.dropped_bytes++;
> +			osf_stream_discard(stream, 1);
> +			if (!first_err)
> +				first_err = ret;
> +			continue;
> +		}
> +
> +		if (decoded_len != frame_len) {
> +			stream->stats.dropped_bytes++;
> +			osf_stream_discard(stream, 1);
> +			if (!first_err)
> +				first_err = -EMSGSIZE;
> +			continue;
> +		}
> +
> +		ret = osf_core_receive_frame(stream->osf, stream->buf, frame_len);
> +		if (ret) {
> +			osf_stream_discard(stream, frame_len);
> +			if (!first_err)
> +				first_err = ret;
> +			continue;
> +		}
> +
> +		stream->stats.valid_frames++;
> +		osf_stream_discard(stream, frame_len);
> +	}
> +
> +	return first_err;
> +}
> +
> +void osf_stream_init(struct osf_stream *stream, struct osf_device *osf)
> +{
> +	if (!stream)
> +		return;
> +
> +	stream->osf = osf;
> +	stream->len = 0;
> +	memset(&stream->stats, 0, sizeof(stream->stats));
> +}
> +
> +void osf_stream_reset(struct osf_stream *stream)
> +{
> +	if (stream) {
> +		stream->len = 0;
> +		memset(&stream->stats, 0, sizeof(stream->stats));
> +	}
> +}
> +
> +int osf_stream_receive_bytes(struct osf_stream *stream, const u8 *buf,
> +			     size_t len)
> +{
> +	size_t copy_len;
> +	size_t space;
> +	int first_err = 0;
> +	int ret;
> +
> +	if (!stream || !stream->osf || (!buf && len))
> +		return -EINVAL;
> +
> +	if (!len) {
> +		ret = osf_stream_process(stream);
> +		if (ret && !first_err)
> +			first_err = ret;
> +		return first_err;
> +	}
> +
> +	while (len) {
> +		space = OSF_STREAM_MAX_FRAME_LEN - stream->len;
> +		if (!space) {
> +			stream->stats.dropped_bytes++;
> +			osf_stream_discard(stream, 1);
> +			if (!first_err)
> +				first_err = -EMSGSIZE;
> +			continue;
> +		}
> +
> +		copy_len = len < space ? len : space;
> +		memcpy(stream->buf + stream->len, buf, copy_len);
> +		stream->len += copy_len;
> +		buf += copy_len;
> +		len -= copy_len;
> +
> +		ret = osf_stream_process(stream);
> +		if (ret && !first_err)
> +			first_err = ret;
> +	}
> +
> +	return first_err;
> +}


^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2026-05-22 16:25 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-05-20  7:28 [PATCH RFC 0/3] iio: add Open Sensor Fusion UART driver Jinseob Kim
2026-05-20  7:28 ` [PATCH RFC 1/3] dt-bindings: iio: imu: add Open Sensor Fusion UART binding Jinseob Kim
2026-05-20 16:33   ` Conor Dooley
2026-05-20  7:28 ` [PATCH RFC 2/3] iio: osf: add Open Sensor Fusion UART IIO driver Jinseob Kim
2026-05-22 16:24   ` Jonathan Cameron
2026-05-20  7:28 ` [PATCH RFC 3/3] MAINTAINERS: add Open Sensor Fusion IIO driver entry Jinseob Kim
2026-05-20  7:59   ` Joshua Crofts
     [not found]     ` <CALMSewJinjbnHT_sOgWmHVeThv3su_E6fioyAjEWKzf7uROc2Q@mail.gmail.com>
2026-05-20  8:14       ` j k
2026-05-20  8:22         ` Joshua Crofts
2026-05-20 11:14           ` Jonathan Cameron
2026-05-20 11:55 ` [PATCH RFC 0/3] iio: add Open Sensor Fusion UART driver Andy Shevchenko
2026-05-22 15:22 ` Jonathan Cameron

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

Powered by JetHome