* Re: [PATCH RFC v5 1/6] dt-bindings: iio: add Open Sensor Fusion device
From: sashiko-bot @ 2026-06-16 7:31 UTC (permalink / raw)
To: Jinseob Kim; +Cc: robh, conor+dt, devicetree
In-Reply-To: <20260616072242.3942-2-kimjinseob88@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The binding uses a generic software protocol as the compatible string instead of describing specific hardware.
--
commit d9858298f5a88a8d0d1665f79878488487e06393
Author: Jinseob Kim <kimjinseob88@gmail.com>
dt-bindings: iio: add Open Sensor Fusion device
Adds the generic Open Sensor Fusion device binding for a serdev-attached IIO
sensor aggregation hub and documents the vendor prefix.
> The opensensorfusion,osf compatible describes the generic Open Sensor
> Fusion host interface. OSF GREEN is not the Linux compatible identity.
> Likewise, OSF0 is the current wire magic and a wire-format detail, not
> the Linux driver identity.
[Severity: Medium]
Does this approach align with Device Tree design principles?
Device Tree is typically intended to describe specific hardware components
(such as vendor,chip-model), rather than software interfaces or generic wire
protocols.
> diff --git a/Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml b/Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml
> new file mode 100644
> index 0000000000000..012a07fd69fe4
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml
[ ... ]
> +properties:
> + compatible:
> + const: opensensorfusion,osf
> +
> + vcc-supply:
> + description:
> + Regulator supplying power to the Open Sensor Fusion device.
> +
> +required:
> + - compatible
> + - vcc-supply
[Severity: Medium]
Is it appropriate to constrain the compatible string to a generic software
protocol identifier here instead of a hardware-specific string?
Because this generic compatible string also requires specific hardware
constraints like vcc-supply, will future hardware variants that speak the
same OSF protocol but have different electrical needs (such as needing a
vddio-supply or reset GPIOs) be able to distinguish themselves without
clashing with these generic constraints?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260616072242.3942-1-kimjinseob88@gmail.com?part=1
^ permalink raw reply
* [PATCH RFC v5 6/6] iio: osf: register IIO devices from capabilities
From: Jinseob Kim @ 2026-06-16 7:22 UTC (permalink / raw)
To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: David Lechner, Nuno Sá, Andy Shevchenko, Jonathan Corbet,
Shuah Khan, linux-iio, devicetree, linux-doc, linux-kernel,
Jinseob Kim
In-Reply-To: <20260616072242.3942-1-kimjinseob88@gmail.com>
Register IIO devices for supported Open Sensor Fusion capability entries
and push received samples into IIO buffers when enabled.
Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
---
drivers/iio/opensensorfusion/Kconfig | 11 +-
drivers/iio/opensensorfusion/Makefile | 3 +-
drivers/iio/opensensorfusion/osf_core.c | 253 ++++++++++++++++++++--
drivers/iio/opensensorfusion/osf_core.h | 52 +++++
drivers/iio/opensensorfusion/osf_iio.c | 275 ++++++++++++++++++++++++
drivers/iio/opensensorfusion/osf_iio.h | 22 ++
6 files changed, 586 insertions(+), 30 deletions(-)
create mode 100644 drivers/iio/opensensorfusion/osf_iio.c
create mode 100644 drivers/iio/opensensorfusion/osf_iio.h
diff --git a/drivers/iio/opensensorfusion/Kconfig b/drivers/iio/opensensorfusion/Kconfig
index d393eb3aa..8b9376d28 100644
--- a/drivers/iio/opensensorfusion/Kconfig
+++ b/drivers/iio/opensensorfusion/Kconfig
@@ -5,11 +5,10 @@ config OPEN_SENSOR_FUSION
depends on IIO
depends on SERIAL_DEV_BUS
select CRC32
+ select IIO_BUFFER
+ select IIO_KFIFO_BUF
help
- Build the Open Sensor Fusion UART receive path.
+ Build the Open Sensor Fusion UART IIO driver.
- The driver receives OSF protocol frames over a serdev UART.
- Frames are decoded and validated before being passed to the
- driver core.
- This patch only adds the transport path.
- IIO device registration is added separately.
+ The driver receives OSF protocol frames over a serdev UART and
+ registers IIO devices for supported capability entries.
diff --git a/drivers/iio/opensensorfusion/Makefile b/drivers/iio/opensensorfusion/Makefile
index 940c82edd..b4e03b80c 100644
--- a/drivers/iio/opensensorfusion/Makefile
+++ b/drivers/iio/opensensorfusion/Makefile
@@ -2,4 +2,5 @@
obj-$(CONFIG_OPEN_SENSOR_FUSION) += open-sensor-fusion.o
-open-sensor-fusion-y := osf_core.o osf_protocol.o osf_serdev.o osf_stream.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
index 137fb7166..61ef55646 100644
--- a/drivers/iio/opensensorfusion/osf_core.c
+++ b/drivers/iio/opensensorfusion/osf_core.c
@@ -5,7 +5,7 @@
#include <linux/types.h>
#include "osf_core.h"
-#include "osf_protocol.h"
+#include "osf_iio.h"
#define OSF_RESERVED_MSG_FIRST 0x7f00
#define OSF_RESERVED_MSG_LAST 0x7fff
@@ -13,31 +13,198 @@
void osf_core_init(struct osf_device *osf, struct device *dev)
{
- memset(osf, 0, sizeof(*osf));
+ mutex_init(&osf->latest_lock);
osf->dev = dev;
}
void osf_core_unregister_iio(struct osf_device *osf)
{
+ unsigned int i;
+
+ for (i = 0; i < osf->iio_dev_count; i++)
+ osf_iio_unregister_sensor(osf->iio_devs[i].indio_dev);
+
+ osf->iio_dev_count = 0;
+}
+
+static struct iio_dev *osf_core_find_iio_dev(struct osf_device *osf,
+ u16 sensor_type, u16 sensor_index)
+{
+ const struct osf_iio_binding *binding;
+ unsigned int i;
+
+ for (i = 0; i < osf->iio_dev_count; i++) {
+ binding = &osf->iio_devs[i];
+ if (binding->sensor_type == sensor_type &&
+ binding->sensor_index == sensor_index)
+ return binding->indio_dev;
+ }
+
+ return NULL;
+}
+
+static struct osf_latest_sample *
+osf_core_find_latest_sample(struct osf_device *osf, u16 sensor_type,
+ u16 sensor_index)
+{
+ struct osf_latest_sample *latest;
+ unsigned int i;
+
+ for (i = 0; i < osf->latest_sample_count; i++) {
+ latest = &osf->latest_samples[i];
+ if (latest->sensor_type == sensor_type &&
+ latest->sensor_index == sensor_index)
+ return latest;
+ }
+
+ if (osf->latest_sample_count >= OSF_MAX_CAPABILITIES)
+ return NULL;
+
+ return &osf->latest_samples[osf->latest_sample_count++];
+}
+
+static bool osf_core_capability_is_duplicate(const struct osf_capability_cache *cache,
+ unsigned int index)
+{
+ const struct osf_capability_entry *entry = &cache->entries[index];
+ unsigned int i;
+
+ for (i = 0; i < index; i++) {
+ if (!osf_iio_sensor_supported(cache->entries[i].sensor_type,
+ cache->entries[i].channel_count))
+ continue;
+
+ if (cache->entries[i].sensor_type == entry->sensor_type &&
+ cache->entries[i].sensor_index == entry->sensor_index)
+ return true;
+ }
+
+ return false;
}
-static int osf_core_validate_sensor_sample(const struct osf_frame *frame)
+static int osf_core_register_capabilities(struct osf_device *osf,
+ const struct osf_capability_cache *cache)
{
+ struct iio_dev *indio_dev;
+ unsigned int i;
+ int ret;
+
+ if (osf->capability_cache.valid)
+ return 0;
+
+ for (i = 0; i < cache->capability_count; i++) {
+ if (!osf_iio_sensor_supported(cache->entries[i].sensor_type,
+ cache->entries[i].channel_count))
+ continue;
+
+ if (osf_core_capability_is_duplicate(cache, i))
+ return -EEXIST;
+ }
+
+ for (i = 0; i < cache->capability_count; i++) {
+ if (!osf_iio_sensor_supported(cache->entries[i].sensor_type,
+ cache->entries[i].channel_count))
+ continue;
+
+ ret = osf_iio_register_sensor(osf->dev, &cache->entries[i],
+ osf, &indio_dev);
+ if (ret)
+ goto err_unregister;
+
+ osf->iio_devs[osf->iio_dev_count].sensor_type =
+ cache->entries[i].sensor_type;
+ osf->iio_devs[osf->iio_dev_count].sensor_index =
+ cache->entries[i].sensor_index;
+ osf->iio_devs[osf->iio_dev_count].indio_dev = indio_dev;
+ osf->iio_dev_count++;
+ }
+
+ return 0;
+
+err_unregister:
+ osf_core_unregister_iio(osf);
+
+ return ret;
+}
+
+static int osf_core_handle_sensor_sample(struct osf_device *osf,
+ const struct osf_frame *frame)
+{
+ struct osf_latest_sample *latest;
struct osf_sensor_sample sample;
+ struct iio_dev *indio_dev;
+ s32 values[OSF_MAX_SAMPLE_CHANNELS] = { };
+ 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;
+ }
- return osf_protocol_decode_sensor_sample(frame, &sample);
+ mutex_lock(&osf->latest_lock);
+ latest = osf_core_find_latest_sample(osf, sample.sensor_type,
+ sample.sensor_index);
+ if (!latest) {
+ mutex_unlock(&osf->latest_lock);
+ return -E2BIG;
+ }
+
+ memcpy(latest->values, values, sizeof(values));
+ 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;
+ osf->last_sequence = frame->sequence;
+ mutex_unlock(&osf->latest_lock);
+
+ indio_dev = osf_core_find_iio_dev(osf, sample.sensor_type,
+ sample.sensor_index);
+ if (!indio_dev)
+ return 0;
+
+ return osf_iio_push_sample(indio_dev, values, sample.channel_count);
}
-static int osf_core_validate_device_status(const struct osf_frame *frame)
+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;
+ int ret;
- return osf_protocol_decode_device_status(frame, &status);
+ ret = osf_protocol_decode_device_status(frame, &status);
+ if (ret)
+ return ret;
+
+ 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;
+ osf->status_cache = cache;
+ osf->last_sequence = frame->sequence;
+
+ return 0;
}
-static int osf_core_validate_capability_report(const struct osf_frame *frame)
+static int osf_core_handle_capability_report(struct osf_device *osf,
+ const struct osf_frame *frame)
{
- struct osf_capability_entry entry;
+ struct osf_capability_cache cache = { };
struct osf_capability_report report;
unsigned int i;
int ret;
@@ -46,12 +213,32 @@ static int osf_core_validate_capability_report(const struct osf_frame *frame)
if (ret)
return ret;
+ if (report.capability_count > OSF_MAX_CAPABILITIES)
+ return -E2BIG;
+
+ if (osf->capability_cache.valid) {
+ osf->last_sequence = frame->sequence;
+ return 0;
+ }
+
for (i = 0; i < report.capability_count; i++) {
- ret = osf_protocol_decode_capability_entry(&report, i, &entry);
+ 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;
+
+ ret = osf_core_register_capabilities(osf, &cache);
+ if (ret)
+ return ret;
+
+ osf->capability_cache = cache;
+ osf->last_sequence = frame->sequence;
+
return 0;
}
@@ -73,27 +260,47 @@ int osf_core_receive_frame(struct osf_device *osf, const u8 *buf, size_t len)
switch (frame.message_type) {
case OSF_MSG_SENSOR_SAMPLE:
- ret = osf_core_validate_sensor_sample(&frame);
- break;
+ return osf_core_handle_sensor_sample(osf, &frame);
case OSF_MSG_DEVICE_STATUS:
- ret = osf_core_validate_device_status(&frame);
- break;
+ return osf_core_handle_device_status(osf, &frame);
case OSF_MSG_CAPABILITY_REPORT:
- ret = osf_core_validate_capability_report(&frame);
- break;
+ return osf_core_handle_capability_report(osf, &frame);
default:
if (frame.message_type >= OSF_RESERVED_MSG_FIRST &&
frame.message_type <= OSF_RESERVED_MSG_LAST)
- ret = 0;
- else if (frame.message_type >= OSF_VENDOR_PRIVATE_FIRST)
- ret = 0;
- else
- ret = -EOPNOTSUPP;
- break;
+ 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 int i;
+ int ret = -ENODATA;
+
+ if (!osf || !value)
+ return -EINVAL;
+
+ mutex_lock(&osf->latest_lock);
+ for (i = 0; i < osf->latest_sample_count; i++) {
+ latest = &osf->latest_samples[i];
+ if (latest->sensor_type != sensor_type ||
+ latest->sensor_index != sensor_index)
+ continue;
+
+ if (!latest->valid || channel >= latest->channel_count)
+ break;
- if (!ret)
- osf->last_sequence = frame.sequence;
+ *value = latest->values[channel];
+ ret = 0;
+ break;
+ }
+ mutex_unlock(&osf->latest_lock);
return ret;
}
diff --git a/drivers/iio/opensensorfusion/osf_core.h b/drivers/iio/opensensorfusion/osf_core.h
index 3680c8c9b..04dd2a367 100644
--- a/drivers/iio/opensensorfusion/osf_core.h
+++ b/drivers/iio/opensensorfusion/osf_core.h
@@ -2,17 +2,69 @@
#ifndef _OSF_CORE_H
#define _OSF_CORE_H
+#include <linux/mutex.h>
#include <linux/types.h>
+#include "osf_protocol.h"
+
+#define OSF_MAX_SAMPLE_CHANNELS 3
+#define OSF_MAX_CAPABILITIES 16
+
struct device;
+struct iio_dev;
+
+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_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_iio_binding {
+ u16 sensor_type;
+ u16 sensor_index;
+ struct iio_dev *indio_dev;
+};
struct osf_device {
struct device *dev;
+ /* Protects latest_samples and latest_sample_count. */
+ struct mutex latest_lock;
+ struct osf_latest_sample latest_samples[OSF_MAX_CAPABILITIES];
+ unsigned int latest_sample_count;
+ struct osf_capability_cache capability_cache;
+ struct osf_status_cache status_cache;
+ struct osf_iio_binding iio_devs[OSF_MAX_CAPABILITIES];
+ unsigned int iio_dev_count;
u64 last_sequence;
};
void osf_core_init(struct osf_device *osf, struct device *dev);
void osf_core_unregister_iio(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);
#endif
diff --git a/drivers/iio/opensensorfusion/osf_iio.c b/drivers/iio/opensensorfusion/osf_iio.c
new file mode 100644
index 000000000..862a797f4
--- /dev/null
+++ b/drivers/iio/opensensorfusion/osf_iio.c
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/array_size.h>
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/kfifo_buf.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include "osf_core.h"
+#include "osf_iio.h"
+
+struct osf_iio_sensor_spec {
+ u16 sensor_type;
+ u16 channel_count;
+ const char *name;
+ const struct iio_chan_spec *channels;
+ unsigned int num_channels;
+ const unsigned long *available_scan_masks;
+};
+
+struct osf_iio_state {
+ const struct osf_iio_sensor_spec *spec;
+ struct iio_buffer *buffer;
+ u32 scale_nano;
+ u16 sensor_index;
+ struct osf_device *osf;
+};
+
+#define OSF_SCAN_TYPE_S32 \
+ { \
+ .sign = 's', \
+ .realbits = 32, \
+ .storagebits = 32, \
+ .endianness = IIO_CPU, \
+ }
+
+#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 unsigned long osf_3axis_available_scan_masks[] = {
+ GENMASK(2, 0),
+ 0
+};
+
+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),
+ .available_scan_masks = osf_3axis_available_scan_masks,
+ },
+ {
+ .sensor_type = OSF_SENSOR_GYROSCOPE,
+ .channel_count = 3,
+ .name = "osf-gyro",
+ .channels = osf_gyro_channels,
+ .num_channels = ARRAY_SIZE(osf_gyro_channels),
+ .available_scan_masks = osf_3axis_available_scan_masks,
+ },
+ {
+ .sensor_type = OSF_SENSOR_MAGNETOMETER,
+ .channel_count = 3,
+ .name = "osf-magn",
+ .channels = osf_mag_channels,
+ .num_channels = ARRAY_SIZE(osf_mag_channels),
+ .available_scan_masks = osf_3axis_available_scan_masks,
+ },
+ {
+ .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:
+ 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 / NANO;
+ *val2 = state->scale_nano % 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_register_sensor(struct device *dev,
+ const struct osf_capability_entry *entry,
+ struct osf_device *osf, struct iio_dev **indio_dev)
+{
+ const struct osf_iio_sensor_spec *spec;
+ struct osf_iio_state *state;
+ struct iio_dev *iio_dev;
+ int ret;
+
+ 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 = 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 = osf;
+
+ 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;
+ iio_dev->available_scan_masks = spec->available_scan_masks;
+
+ state->buffer = iio_kfifo_allocate();
+ if (!state->buffer) {
+ ret = -ENOMEM;
+ goto err_free_iio;
+ }
+
+ ret = iio_device_attach_buffer(iio_dev, state->buffer);
+ if (ret)
+ goto err_free_buffer;
+
+ ret = iio_device_register(iio_dev);
+ if (ret)
+ goto err_free_buffer;
+
+ *indio_dev = iio_dev;
+
+ return 0;
+
+err_free_buffer:
+ iio_kfifo_free(state->buffer);
+err_free_iio:
+ iio_device_free(iio_dev);
+
+ return ret;
+}
+
+void osf_iio_unregister_sensor(struct iio_dev *indio_dev)
+{
+ struct osf_iio_state *state = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ iio_kfifo_free(state->buffer);
+ iio_device_free(indio_dev);
+}
+
+int osf_iio_push_sample(struct iio_dev *indio_dev, const s32 *values,
+ unsigned int channel_count)
+{
+ struct osf_iio_state *state = iio_priv(indio_dev);
+ s64 timestamp;
+
+ if (channel_count != state->spec->channel_count)
+ return -EPROTO;
+
+ /* This is only a fast path; IIO rechecks buffer state while pushing. */
+ if (!iio_buffer_enabled(indio_dev))
+ return 0;
+
+ timestamp = iio_get_time_ns(indio_dev);
+
+ return iio_push_to_buffers_with_ts_unaligned(indio_dev, values,
+ channel_count * sizeof(*values),
+ timestamp);
+}
diff --git a/drivers/iio/opensensorfusion/osf_iio.h b/drivers/iio/opensensorfusion/osf_iio.h
new file mode 100644
index 000000000..d90c58fc4
--- /dev/null
+++ b/drivers/iio/opensensorfusion/osf_iio.h
@@ -0,0 +1,22 @@
+/* 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_device;
+
+int osf_iio_register_sensor(struct device *dev,
+ const struct osf_capability_entry *entry,
+ struct osf_device *osf, struct iio_dev **indio_dev);
+void osf_iio_unregister_sensor(struct iio_dev *indio_dev);
+int osf_iio_push_sample(struct iio_dev *indio_dev, const s32 *values,
+ unsigned int channel_count);
+bool osf_iio_sensor_supported(u16 sensor_type, u16 channel_count);
+const char *osf_iio_sensor_name(u16 sensor_type);
+
+#endif
--
2.43.0
^ permalink raw reply related
* [PATCH RFC v5 5/6] iio: osf: add UART transport
From: Jinseob Kim @ 2026-06-16 7:22 UTC (permalink / raw)
To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: David Lechner, Nuno Sá, Andy Shevchenko, Jonathan Corbet,
Shuah Khan, linux-iio, devicetree, linux-doc, linux-kernel,
Jinseob Kim
In-Reply-To: <20260616072242.3942-1-kimjinseob88@gmail.com>
Add the serdev UART transport and the initial OSF core receive path.
Enable the required vcc regulator with devm_regulator_get_enable()
before opening the UART, keeping power handling limited to the simple
probe-time requirement for this RFC.
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 | 5 +
drivers/iio/opensensorfusion/osf_core.c | 99 ++++++++++++++++++
drivers/iio/opensensorfusion/osf_core.h | 18 ++++
drivers/iio/opensensorfusion/osf_serdev.c | 117 ++++++++++++++++++++++
7 files changed, 256 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_serdev.c
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..d393eb3aa
--- /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
+ help
+ Build the Open Sensor Fusion UART receive path.
+
+ The driver receives OSF protocol frames over a serdev UART.
+ Frames are decoded and validated before being passed to the
+ driver core.
+ This patch only adds the transport path.
+ IIO device registration is added separately.
diff --git a/drivers/iio/opensensorfusion/Makefile b/drivers/iio/opensensorfusion/Makefile
new file mode 100644
index 000000000..940c82edd
--- /dev/null
+++ b/drivers/iio/opensensorfusion/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_OPEN_SENSOR_FUSION) += open-sensor-fusion.o
+
+open-sensor-fusion-y := osf_core.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..137fb7166
--- /dev/null
+++ b/drivers/iio/opensensorfusion/osf_core.c
@@ -0,0 +1,99 @@
+// 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"
+
+#define OSF_RESERVED_MSG_FIRST 0x7f00
+#define OSF_RESERVED_MSG_LAST 0x7fff
+#define OSF_VENDOR_PRIVATE_FIRST 0x8000
+
+void osf_core_init(struct osf_device *osf, struct device *dev)
+{
+ memset(osf, 0, sizeof(*osf));
+ osf->dev = dev;
+}
+
+void osf_core_unregister_iio(struct osf_device *osf)
+{
+}
+
+static int osf_core_validate_sensor_sample(const struct osf_frame *frame)
+{
+ struct osf_sensor_sample sample;
+
+ return osf_protocol_decode_sensor_sample(frame, &sample);
+}
+
+static int osf_core_validate_device_status(const struct osf_frame *frame)
+{
+ struct osf_device_status status;
+
+ return osf_protocol_decode_device_status(frame, &status);
+}
+
+static int osf_core_validate_capability_report(const struct osf_frame *frame)
+{
+ struct osf_capability_entry entry;
+ struct osf_capability_report report;
+ unsigned int i;
+ int ret;
+
+ ret = osf_protocol_decode_capability_report(frame, &report);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < report.capability_count; i++) {
+ ret = osf_protocol_decode_capability_entry(&report, i, &entry);
+ if (ret)
+ return ret;
+ }
+
+ 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:
+ ret = osf_core_validate_sensor_sample(&frame);
+ break;
+ case OSF_MSG_DEVICE_STATUS:
+ ret = osf_core_validate_device_status(&frame);
+ break;
+ case OSF_MSG_CAPABILITY_REPORT:
+ ret = osf_core_validate_capability_report(&frame);
+ break;
+ default:
+ if (frame.message_type >= OSF_RESERVED_MSG_FIRST &&
+ frame.message_type <= OSF_RESERVED_MSG_LAST)
+ ret = 0;
+ else if (frame.message_type >= OSF_VENDOR_PRIVATE_FIRST)
+ ret = 0;
+ else
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ if (!ret)
+ osf->last_sequence = frame.sequence;
+
+ return ret;
+}
diff --git a/drivers/iio/opensensorfusion/osf_core.h b/drivers/iio/opensensorfusion/osf_core.h
new file mode 100644
index 000000000..3680c8c9b
--- /dev/null
+++ b/drivers/iio/opensensorfusion/osf_core.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _OSF_CORE_H
+#define _OSF_CORE_H
+
+#include <linux/types.h>
+
+struct device;
+
+struct osf_device {
+ struct device *dev;
+ u64 last_sequence;
+};
+
+void osf_core_init(struct osf_device *osf, struct device *dev);
+void osf_core_unregister_iio(struct osf_device *osf);
+int osf_core_receive_frame(struct osf_device *osf, const u8 *buf, size_t len);
+
+#endif
diff --git a/drivers/iio/opensensorfusion/osf_serdev.c b/drivers/iio/opensensorfusion/osf_serdev.c
new file mode 100644
index 000000000..624cb01fe
--- /dev/null
+++ b/drivers/iio/opensensorfusion/osf_serdev.c
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+#include <linux/serdev.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include "osf_core.h"
+#include "osf_stream.h"
+
+#define OSF_SERDEV_BAUD 115200
+
+struct osf_serdev {
+ struct serdev_device *serdev;
+ struct osf_device osf;
+ struct osf_stream stream;
+};
+
+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;
+
+ 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(&osf_uart->osf, &serdev->dev);
+ osf_stream_init(&osf_uart->stream, &osf_uart->osf);
+
+ serdev_device_set_drvdata(serdev, osf_uart);
+ serdev_device_set_client_ops(serdev, &osf_serdev_ops);
+
+ ret = devm_regulator_get_enable(&serdev->dev, "vcc");
+ if (ret)
+ return dev_err_probe(&serdev->dev, ret,
+ "failed to enable vcc regulator\n");
+
+ 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);
+
+ return 0;
+}
+
+static void osf_serdev_remove(struct serdev_device *serdev)
+{
+ struct osf_serdev *osf_uart = serdev_device_get_drvdata(serdev);
+
+ serdev_device_close(serdev);
+ osf_stream_reset(&osf_uart->stream);
+ osf_core_unregister_iio(&osf_uart->osf);
+}
+
+static const struct of_device_id osf_serdev_of_match[] = {
+ { .compatible = "opensensorfusion,osf" },
+ { }
+};
+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,
+ },
+};
+
+module_serdev_device_driver(osf_serdev_driver);
+
+MODULE_DESCRIPTION("Open Sensor Fusion IIO driver");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH RFC v5 4/6] iio: osf: add stream parser
From: Jinseob Kim @ 2026-06-16 7:22 UTC (permalink / raw)
To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: David Lechner, Nuno Sá, Andy Shevchenko, Jonathan Corbet,
Shuah Khan, linux-iio, devicetree, linux-doc, linux-kernel,
Jinseob Kim
In-Reply-To: <20260616072242.3942-1-kimjinseob88@gmail.com>
Add a byte-stream parser that resynchronizes on OSF frame magic, validates
complete frames, and forwards decoded frames to the OSF core.
Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
---
drivers/iio/opensensorfusion/osf_stream.c | 187 ++++++++++++++++++++++
drivers/iio/opensensorfusion/osf_stream.h | 31 ++++
2 files changed, 218 insertions(+)
create mode 100644 drivers/iio/opensensorfusion/osf_stream.c
create mode 100644 drivers/iio/opensensorfusion/osf_stream.h
diff --git a/drivers/iio/opensensorfusion/osf_stream.c b/drivers/iio/opensensorfusion/osf_stream.c
new file mode 100644
index 000000000..957f73716
--- /dev/null
+++ b/drivers/iio/opensensorfusion/osf_stream.c
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/unaligned.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 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 void osf_stream_drop_invalid_head(struct osf_stream *stream)
+{
+ osf_stream_discard(stream, 1);
+}
+
+static bool osf_stream_magic_match(const u8 *buf, size_t len)
+{
+ return !memcmp(buf, osf_stream_magic, len);
+}
+
+static size_t osf_stream_discard_to_magic(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)
+{
+ size_t discarded;
+ size_t frame_len;
+ u32 payload_len;
+ int first_err = 0;
+ int ret;
+
+ while (stream->len) {
+ discarded = osf_stream_discard_to_magic(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)
+ break;
+
+ if (get_unaligned_le16(stream->buf + 6) !=
+ OSF_FRAME_HEADER_LEN) {
+ stream->stats.dropped_bytes++;
+ osf_stream_drop_invalid_head(stream);
+ if (!first_err)
+ first_err = -EPROTO;
+ continue;
+ }
+
+ payload_len = get_unaligned_le32(stream->buf + 10);
+ if (payload_len > OSF_STREAM_MAX_PAYLOAD_LEN) {
+ stream->stats.dropped_bytes++;
+ osf_stream_drop_invalid_head(stream);
+ if (!first_err)
+ first_err = -EMSGSIZE;
+ continue;
+ }
+
+ frame_len = OSF_FRAME_HEADER_LEN + payload_len + OSF_FRAME_CRC_LEN;
+ if (stream->len < frame_len)
+ break;
+
+ ret = osf_core_receive_frame(stream->osf, stream->buf, frame_len);
+ if (ret) {
+ if (ret == -EBADMSG) {
+ stream->stats.bad_crc_frames++;
+ stream->stats.dropped_bytes++;
+ osf_stream_drop_invalid_head(stream);
+ } else {
+ 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 related
* [PATCH RFC v5 3/6] iio: osf: add protocol decoding
From: Jinseob Kim @ 2026-06-16 7:22 UTC (permalink / raw)
To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: David Lechner, Nuno Sá, Andy Shevchenko, Jonathan Corbet,
Shuah Khan, linux-iio, devicetree, linux-doc, linux-kernel,
Jinseob Kim
In-Reply-To: <20260616072242.3942-1-kimjinseob88@gmail.com>
Add helpers for validating and decoding Open Sensor Fusion frames and the
message payloads used by the initial receive path.
Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
---
drivers/iio/opensensorfusion/osf_protocol.c | 249 ++++++++++++++++++++
drivers/iio/opensensorfusion/osf_protocol.h | 97 ++++++++
2 files changed, 346 insertions(+)
create mode 100644 drivers/iio/opensensorfusion/osf_protocol.c
create mode 100644 drivers/iio/opensensorfusion/osf_protocol.h
diff --git a/drivers/iio/opensensorfusion/osf_protocol.c b/drivers/iio/opensensorfusion/osf_protocol.c
new file mode 100644
index 000000000..5bee545f3
--- /dev/null
+++ b/drivers/iio/opensensorfusion/osf_protocol.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bits.h>
+#include <linux/crc32.h>
+#include <linux/errno.h>
+#include <linux/limits.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#include "osf_protocol.h"
+
+#define OSF_CRC32_INIT GENMASK(31, 0)
+#define OSF_CRC32_XOROUT GENMASK(31, 0)
+#define OSF_FRAME_MAGIC 0x3046534f /* "OSF0" little-endian */
+
+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 (get_unaligned_le32(buf) != OSF_FRAME_MAGIC)
+ return -EPROTO;
+
+ major = buf[4];
+ if (major != OSF_PROTOCOL_MAJOR)
+ return -EPROTO;
+
+ if (get_unaligned_le16(buf + 6) != OSF_FRAME_HEADER_LEN)
+ return -EPROTO;
+
+ payload_len = get_unaligned_le32(buf + 10);
+ if (payload_len > len - OSF_FRAME_MIN_LEN)
+ return -EMSGSIZE;
+
+ if (get_unaligned_le32(buf + 34))
+ return -EPROTO;
+
+ 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 = get_unaligned_le32(buf + OSF_FRAME_HEADER_LEN + payload_len);
+
+ if (actual_crc != expected_crc)
+ return -EBADMSG;
+
+ frame->protocol_minor = buf[5];
+ frame->message_type = get_unaligned_le16(buf + 8);
+ frame->payload_len = payload_len;
+ frame->sequence = get_unaligned_le64(buf + 14);
+ frame->timestamp_us = get_unaligned_le64(buf + 22);
+ frame->flags = get_unaligned_le32(buf + 30);
+ 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;
+ u16 sensor_type;
+ size_t 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;
+ sensor_type = get_unaligned_le16(payload);
+ channel_count = get_unaligned_le16(payload + 4);
+ sample_format = get_unaligned_le16(payload + 6);
+
+ if (!osf_sensor_type_valid(sensor_type))
+ return -EPROTO;
+
+ if (!channel_count)
+ return -EPROTO;
+
+ if (sample_format != OSF_SAMPLE_FORMAT_S32)
+ return -EPROTO;
+
+ if (get_unaligned_le32(payload + 12))
+ return -EPROTO;
+
+ if (channel_count > (SIZE_MAX - OSF_SENSOR_SAMPLE_BASE_LEN) / sizeof(s32))
+ return -EOVERFLOW;
+
+ expected_len = OSF_SENSOR_SAMPLE_BASE_LEN + channel_count * sizeof(s32);
+ if (frame->payload_len != expected_len)
+ return -EMSGSIZE;
+
+ sample->sensor_type = sensor_type;
+ sample->sensor_index = get_unaligned_le16(payload + 2);
+ sample->channel_count = channel_count;
+ sample->sample_format = sample_format;
+ sample->scale_nano = get_unaligned_le32(payload + 8);
+ 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;
+
+ /* Samples are little-endian two's-complement signed values. */
+ *value = (s32)get_unaligned_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;
+ if (get_unaligned_le32(payload + 16))
+ return -EPROTO;
+
+ status->uptime_s = get_unaligned_le32(payload);
+ status->status_flags = get_unaligned_le32(payload + 4);
+ status->error_flags = get_unaligned_le32(payload + 8);
+ status->dropped_frames = get_unaligned_le32(payload + 12);
+
+ return 0;
+}
+
+int osf_protocol_decode_capability_report(const struct osf_frame *frame,
+ struct osf_capability_report *report)
+{
+ u16 capability_count;
+ size_t 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 = get_unaligned_le16(payload);
+
+ if (get_unaligned_le16(payload + 2))
+ return -EPROTO;
+
+ if (capability_count > (SIZE_MAX - OSF_CAP_REPORT_BASE_LEN) /
+ OSF_CAP_SENSOR_ENTRY_LEN)
+ return -EOVERFLOW;
+
+ 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->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)
+{
+ u16 sample_format;
+ u16 sensor_type;
+ u32 flags;
+ 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;
+ sensor_type = get_unaligned_le16(payload);
+ sample_format = get_unaligned_le16(payload + 6);
+ flags = get_unaligned_le32(payload + 12);
+
+ if (!osf_sensor_type_valid(sensor_type))
+ return -EPROTO;
+
+ if (sample_format != OSF_SAMPLE_FORMAT_S32)
+ return -EPROTO;
+
+ if (flags & ~OSF_CAPABILITY_FLAGS_MASK)
+ return -EPROTO;
+
+ if (get_unaligned_le32(payload + 16))
+ return -EPROTO;
+
+ entry->sensor_type = sensor_type;
+ entry->sensor_index = get_unaligned_le16(payload + 2);
+ entry->channel_count = get_unaligned_le16(payload + 4);
+ entry->sample_format = sample_format;
+ entry->scale_nano = get_unaligned_le32(payload + 8);
+ entry->flags = flags;
+
+ return 0;
+}
diff --git a/drivers/iio/opensensorfusion/osf_protocol.h b/drivers/iio/opensensorfusion/osf_protocol.h
new file mode 100644
index 000000000..c62c2c254
--- /dev/null
+++ b/drivers/iio/opensensorfusion/osf_protocol.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _OSF_PROTOCOL_H
+#define _OSF_PROTOCOL_H
+
+#include <linux/bits.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 GENMASK(1, 0)
+
+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;
+ /* payload points into the caller-owned frame buffer. */
+ const u8 *payload;
+ u32 crc;
+};
+
+struct osf_sensor_sample {
+ u16 sensor_type;
+ u16 sensor_index;
+ u16 channel_count;
+ u16 sample_format;
+ u32 scale_nano;
+ const u8 *samples;
+};
+
+struct osf_device_status {
+ u32 uptime_s;
+ u32 status_flags;
+ u32 error_flags;
+ u32 dropped_frames;
+};
+
+struct osf_capability_report {
+ u16 capability_count;
+ const u8 *entries;
+};
+
+struct osf_capability_entry {
+ u16 sensor_type;
+ u16 sensor_index;
+ u16 channel_count;
+ u16 sample_format;
+ u32 scale_nano;
+ u32 flags;
+};
+
+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
--
2.43.0
^ permalink raw reply related
* [PATCH RFC v5 2/6] Documentation: iio: add Open Sensor Fusion driver overview
From: Jinseob Kim @ 2026-06-16 7:22 UTC (permalink / raw)
To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: David Lechner, Nuno Sá, Andy Shevchenko, Jonathan Corbet,
Shuah Khan, linux-iio, devicetree, linux-doc, linux-kernel,
Jinseob Kim
In-Reply-To: <20260616072242.3942-1-kimjinseob88@gmail.com>
Document the Linux IIO mapping for Open Sensor Fusion devices, including
capability-driven IIO device registration and the initially supported
receive path.
Call out that OSF0 is a wire magic value, while protocol_major and
protocol_minor carry protocol compatibility inside frames. The Linux
compatible remains the generic Open Sensor Fusion host interface.
Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
---
Documentation/iio/index.rst | 1 +
Documentation/iio/open-sensor-fusion.rst | 71 ++++++++++++++++++++++++
2 files changed, 72 insertions(+)
create mode 100644 Documentation/iio/open-sensor-fusion.rst
diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst
index ba3e609c6..2713ec5e0 100644
--- a/Documentation/iio/index.rst
+++ b/Documentation/iio/index.rst
@@ -38,4 +38,5 @@ Industrial I/O Kernel Drivers
adxl345
bno055
ep93xx_adc
+ open-sensor-fusion
opt4060
diff --git a/Documentation/iio/open-sensor-fusion.rst b/Documentation/iio/open-sensor-fusion.rst
new file mode 100644
index 000000000..cf3bbd761
--- /dev/null
+++ b/Documentation/iio/open-sensor-fusion.rst
@@ -0,0 +1,71 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+Open Sensor Fusion
+==================
+
+Open Sensor Fusion is a sensor aggregation hub interface. The Linux IIO driver
+receives OSF protocol frames from an attached device, discovers supported sensor
+streams through capability reports, and registers matching IIO devices for the
+sensor classes supported by the driver.
+
+This document is a driver-facing overview for the Linux IIO mapping. The full
+wire protocol, firmware behavior, and hardware model details belong in the Open
+Sensor Fusion project documentation.
+
+Device Model
+------------
+
+An OSF device sends binary frames from the device to the host. The host driver
+uses ``CAPABILITY_REPORT`` messages to discover which sensor streams are
+available. Device Tree describes the attached OSF sensor aggregation hub; it does
+not enumerate the individual sensors discovered at runtime.
+
+The currently supported Linux subset exposes:
+
+* accelerometer samples as ``IIO_ACCEL`` X/Y/Z channels,
+* gyroscope samples as ``IIO_ANGL_VEL`` X/Y/Z channels,
+* magnetometer samples as ``IIO_MAGN`` X/Y/Z channels, and
+* temperature samples as ``IIO_TEMP``.
+
+Protocol Scope
+---------------
+
+The driver supports OSF protocol major version 0 for the initial IIO receive
+path. The current wire magic is ``OSF0``; that string is a wire-format detail and
+is not the Linux driver identity. Device Tree keeps the generic
+``opensensorfusion,osf`` compatible rather than naming a product such as OSF
+GREEN or a wire magic value.
+
+Protocol versioning is carried by the ``protocol_major`` and ``protocol_minor``
+fields at fixed offsets in the OSF frame header. The driver currently
+supports ``protocol_major`` 0. ``protocol_minor`` changes within major version
+0 are intended to remain backward-compatible within the fixed header layout.
+Incompatible wire-format changes require a new ``protocol_major``. A future
+device that cannot expose compatible version discovery through that fixed
+header layout would need a different Device Tree compatible.
+
+The initial Linux driver handles device-to-host frames for:
+
+* ``SENSOR_SAMPLE`` buffered and direct-mode sample data,
+* ``CAPABILITY_REPORT`` based IIO device registration, and
+* ``DEVICE_STATUS`` cache updates.
+
+Vendor-private message types are ignored. Command transport, calibration
+control ABI, fusion output ABI, and runtime capability removal are outside the
+initial Linux IIO receive path.
+
+Timestamps
+----------
+
+OSF frames include a device-side ``timestamp_us`` field. Buffered IIO samples use
+an IIO timestamp captured on the host when samples are pushed to IIO buffers.
+The initial driver does not correlate the device timestamp with the host IIO
+clock.
+
+Compatibility Notes
+-------------------
+
+The project protocol documentation should define the compatibility rules for
+reserved fields, optional flags, and trailing extension data. Until those rules
+are finalized, the Linux decoder keeps conservative bounds checks around the
+currently supported message layouts.
--
2.43.0
^ permalink raw reply related
* [PATCH RFC v5 1/6] dt-bindings: iio: add Open Sensor Fusion device
From: Jinseob Kim @ 2026-06-16 7:22 UTC (permalink / raw)
To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: David Lechner, Nuno Sá, Andy Shevchenko, Jonathan Corbet,
Shuah Khan, linux-iio, devicetree, linux-doc, linux-kernel,
Jinseob Kim
In-Reply-To: <20260616072242.3942-1-kimjinseob88@gmail.com>
Add the generic Open Sensor Fusion device binding for a serdev-attached
IIO sensor aggregation hub, and document the opensensorfusion vendor
prefix.
The opensensorfusion,osf compatible describes the generic Open Sensor
Fusion host interface. OSF GREEN is not the Linux compatible identity.
Likewise, OSF0 is the current wire magic and a wire-format detail, not
the Linux driver identity.
The fixed OSF frame header carries protocol_major and protocol_minor at
fixed offsets. This driver currently supports protocol_major 0.
protocol_minor changes are intended to remain backward-compatible within
that fixed header layout. Incompatible wire-format changes require a new
protocol_major. If a future device cannot expose compatible version
discovery through the fixed header layout, it will need a different
compatible.
Require vcc-supply so the driver can enable device power before starting
communication.
Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
---
.../bindings/iio/opensensorfusion,osf.yaml | 59 +++++++++++++++++++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
MAINTAINERS | 13 ++++
3 files changed, 74 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml
diff --git a/Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml b/Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml
new file mode 100644
index 000000000..012a07fd6
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml
@@ -0,0 +1,59 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/opensensorfusion,osf.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Open Sensor Fusion Sensor Aggregation Hub
+
+maintainers:
+ - Jinseob Kim <kimjinseob88@gmail.com>
+
+description: |
+ Open Sensor Fusion is a sensor aggregation hub. The hub exposes an OSF
+ protocol data stream over its host interface and may report capabilities and
+ samples for multiple sensor classes. The Linux driver discovers the actual
+ sensor channels from OSF capability reports instead of describing those
+ sensors in Device Tree.
+
+ Open Sensor Fusion is not a generic industry standard. Public project
+ documentation is available at:
+
+ https://github.com/opensensorfusion
+
+ The compatible describes the generic Open Sensor Fusion host interface. It
+ is not an OSF GREEN board identity, and it does not encode the OSF0 wire
+ magic. OSF0, protocol_major, and protocol_minor are wire-protocol details
+ exchanged in OSF frames.
+
+allOf:
+ - $ref: /schemas/serial/serial-peripheral-props.yaml#
+
+properties:
+ compatible:
+ const: opensensorfusion,osf
+
+ vcc-supply:
+ description:
+ Regulator supplying power to the Open Sensor Fusion device.
+
+required:
+ - compatible
+ - vcc-supply
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ vcc_sensor: regulator-0 {
+ compatible = "regulator-fixed";
+ regulator-name = "sensor-vcc";
+ };
+
+ serial {
+ sensor {
+ compatible = "opensensorfusion,osf";
+ vcc-supply = <&vcc_sensor>;
+ };
+ };
+...
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 28784d66a..88172d4a4 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1237,6 +1237,8 @@ patternProperties:
description: OpenPandora GmbH
"^openrisc,.*":
description: OpenRISC.io
+ "^opensensorfusion,.*":
+ description: Open Sensor Fusion
"^openwrt,.*":
description: OpenWrt
"^option,.*":
diff --git a/MAINTAINERS b/MAINTAINERS
index c2c6d7927..2ddefc42d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20011,6 +20011,19 @@ F: Documentation/devicetree/
F: arch/*/boot/dts/
F: include/dt-bindings/
+OPEN SENSOR FUSION IIO DRIVER
+M: Jinseob Kim <kimjinseob88@gmail.com>
+S: Maintained
+F: Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml
+F: Documentation/iio/open-sensor-fusion.rst
+F: drivers/iio/opensensorfusion/Kconfig
+F: drivers/iio/opensensorfusion/Makefile
+F: drivers/iio/opensensorfusion/osf_core.*
+F: drivers/iio/opensensorfusion/osf_iio.*
+F: drivers/iio/opensensorfusion/osf_protocol.*
+F: drivers/iio/opensensorfusion/osf_serdev.c
+F: drivers/iio/opensensorfusion/osf_stream.*
+
OPENCOMPUTE PTP CLOCK DRIVER
M: Vadim Fedorenko <vadim.fedorenko@linux.dev>
L: netdev@vger.kernel.org
--
2.43.0
^ permalink raw reply related
* [PATCH RFC v5 0/6] iio: add Open Sensor Fusion IIO driver
From: Jinseob Kim @ 2026-06-16 7:22 UTC (permalink / raw)
To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: David Lechner, Nuno Sá, Andy Shevchenko, Jonathan Corbet,
Shuah Khan, linux-iio, devicetree, linux-doc, linux-kernel,
Jinseob Kim
Open Sensor Fusion is a sensor aggregation hub interface. The Linux IIO
driver receives OSF protocol frames from a serdev-attached device,
discovers supported sensor streams from capability reports, and exposes
the supported raw sensor data through IIO devices.
The initial driver supports protocol major version 0 and the receive path
for accelerometer, gyroscope, magnetometer, and temperature samples. The
current wire magic is OSF0, but OSF0 is a wire-format detail and not the
Linux driver identity. Protocol compatibility is carried by the
protocol_major and protocol_minor fields in the fixed OSF frame header.
This is still RFC because the driver-facing OSF protocol subset, the
compatible binding, and future protocol compatibility rules are being
reviewed.
Runtime testing was done with an OSF GREEN prototype connected to a
Raspberry Pi over UART. The driver registered osf-accel, osf-gyro,
osf-magn, and osf-temp IIO devices. Direct raw reads and software kfifo
buffer reads were tested.
Changes since v4:
- Regenerated the series as a full standalone replacement series from a
clean upstream base.
- Removed previous-version add/delete churn from the generated series.
- Clarified OSF0, protocol_major, and protocol_minor compatibility
handling.
- Added required vcc-supply support to the binding.
- Added probe-time regulator enablement with devm_regulator_get_enable().
- Added the opensensorfusion vendor prefix.
- Fixed checkpatch cleanup issues in commit messages and driver style.
Jinseob Kim (6):
dt-bindings: iio: add Open Sensor Fusion device
Documentation: iio: add Open Sensor Fusion driver overview
iio: osf: add protocol decoding
iio: osf: add stream parser
iio: osf: add UART transport
iio: osf: register IIO devices from capabilities
.../bindings/iio/opensensorfusion,osf.yaml | 59 ++++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
Documentation/iio/index.rst | 1 +
Documentation/iio/open-sensor-fusion.rst | 71 ++++
MAINTAINERS | 13 +
drivers/iio/Kconfig | 1 +
drivers/iio/Makefile | 1 +
drivers/iio/opensensorfusion/Kconfig | 14 +
drivers/iio/opensensorfusion/Makefile | 6 +
drivers/iio/opensensorfusion/osf_core.c | 306 ++++++++++++++++++
drivers/iio/opensensorfusion/osf_core.h | 70 ++++
drivers/iio/opensensorfusion/osf_iio.c | 275 ++++++++++++++++
drivers/iio/opensensorfusion/osf_iio.h | 22 ++
drivers/iio/opensensorfusion/osf_protocol.c | 249 ++++++++++++++
drivers/iio/opensensorfusion/osf_protocol.h | 97 ++++++
drivers/iio/opensensorfusion/osf_serdev.c | 117 +++++++
drivers/iio/opensensorfusion/osf_stream.c | 187 +++++++++++
drivers/iio/opensensorfusion/osf_stream.h | 31 ++
18 files changed, 1522 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml
create mode 100644 Documentation/iio/open-sensor-fusion.rst
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_stream.c
create mode 100644 drivers/iio/opensensorfusion/osf_stream.h
--
2.43.0
^ permalink raw reply
* Re: [PATCH v3 2/3] perf: marvell: Add CN20K LLC-TAD PMU support
From: sashiko-bot @ 2026-06-16 7:22 UTC (permalink / raw)
To: Geetha sowjanya; +Cc: conor+dt, devicetree, linux-perf-users, robh
In-Reply-To: <20260616071149.12523-3-gakula@marvell.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The patch series reverses the registration order of the PMU and CPU hotplug, creating a race window where the PMU is exposed to userspace before hotplug migration is wired up.
--
commit 71b7ba2b271b1080739c44e37ec7465ecb45ee0e
Author: Geetha sowjanya <gakula@marvell.com>
perf: marvell: Add CN20K LLC-TAD PMU support
Add support for the LLC Tag-and-Data (TAD) PMU present in
Marvell CN20K SoCs.
> diff --git a/drivers/perf/marvell_cn10k_tad_pmu.c b/drivers/perf/marvell_cn10k_tad_pmu.c
> index 69a6648fa6648..cd81bf8ff5690 100644
> --- a/drivers/perf/marvell_cn10k_tad_pmu.c
> +++ b/drivers/perf/marvell_cn10k_tad_pmu.c
[ ... ]
> @@ -478,7 +521,7 @@ static int tad_pmu_probe(struct platform_device *pdev)
> .read = tad_pmu_event_counter_read,
> };
>
> - if (version == TAD_PMU_V1) {
> + if (version == TAD_PMU_V1 || version == TAD_PMU_V3) {
> tad_pmu->pmu.attr_groups = tad_pmu_attr_groups;
> tad_pmu->ops = &tad_pmu_ops;
> } else {
[Severity: Medium]
Looking at the remainder of tad_pmu_probe(), the registration order is:
ret = perf_pmu_register(&tad_pmu->pmu, "tad", -1);
if (ret) {
dev_err(&pdev->dev, "Error %d registering perf PMU\n", ret);
return ret;
}
ret = cpuhp_state_add_instance_nocalls(tad_pmu_cpuhp_state,
&tad_pmu->node);
Does this sequence create a race window where the PMU is exposed to
userspace before hotplug migration is wired up?
If userspace immediately creates events assigned to tad_pmu->cpu, and that
CPU goes offline before the hotplug state instance is added, the hotplug
callback wouldn't be invoked to migrate the context. This could leave the
PMU tied to an offline CPU, preventing its events from being scheduled.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260616071149.12523-1-gakula@marvell.com?part=2
^ permalink raw reply
* [PATCH v4] arm64: dts: imx94: Add Root Port node and PERST property
From: hongxing.zhu @ 2026-06-16 7:23 UTC (permalink / raw)
To: sherry.sun, robh, krzk+dt, conor+dt, frank.li, s.hauer, festevam
Cc: kernel, devicetree, imx, linux-arm-kernel, linux-kernel,
Richard Zhu
From: Richard Zhu <hongxing.zhu@nxp.com>
Since describing the PCIe PERST# property under Host Bridge node is now
deprecated, it is recommended to add it to the Root Port node, so
creating the Root Port node and add the reset-gpios property in Root
Port.
Move the regulator to Root Port nodes as well, because that the PCI
pwrctrl framework had been integrated into pci-imx6 driver.
Signed-off-by: Richard Zhu <hongxing.zhu@nxp.com>
Reviewed-by: Sherry Sun <sherry.sun@nxp.com>
---
arch/arm64/boot/dts/freescale/imx94.dtsi | 11 +++++++++++
arch/arm64/boot/dts/freescale/imx943-evk.dts | 14 ++++++++++----
arch/arm64/boot/dts/freescale/imx943.dtsi | 11 +++++++++++
3 files changed, 32 insertions(+), 4 deletions(-)
---
Changes in v4:
Add the description of regualtor changes into commit message too.
Changes in v3:
- Move the regulator to Root Port node as well, since [2] had been
settled.
- Collect Reviewed-by tag issued by Sherry.
Changes in v2:
- Delete reset-gpio properties in PCIe bridge node.
- Correct the "reset-gpio" property to "reset-gpios".
Since the patch-set [1] issued by Sherry had been landed. Add according
changes on i.MX943 board too.
[1] https://lkml.org/lkml/2026/6/1/1461
[2] https://lore.kernel.org/imx/20260520084904.2424253-1-sherry.sun@oss.nxp.com/
diff --git a/arch/arm64/boot/dts/freescale/imx94.dtsi b/arch/arm64/boot/dts/freescale/imx94.dtsi
index 1f9035e6cf159..dfbb73603cb24 100644
--- a/arch/arm64/boot/dts/freescale/imx94.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx94.dtsi
@@ -1411,6 +1411,17 @@ pcie0: pcie@4c300000 {
power-domains = <&scmi_devpd IMX94_PD_HSIO_TOP>;
fsl,max-link-speed = <3>;
status = "disabled";
+
+ pcie0_port0: pcie@0 {
+ compatible = "pciclass,0604";
+ device_type = "pci";
+ reg = <0x0 0x0 0x0 0x0 0x0>;
+ bus-range = <0x01 0xff>;
+
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges;
+ };
};
pcie0_ep: pcie-ep@4c300000 {
diff --git a/arch/arm64/boot/dts/freescale/imx943-evk.dts b/arch/arm64/boot/dts/freescale/imx943-evk.dts
index 7cfd424689507..674410e541cba 100644
--- a/arch/arm64/boot/dts/freescale/imx943-evk.dts
+++ b/arch/arm64/boot/dts/freescale/imx943-evk.dts
@@ -1034,12 +1034,15 @@ &pcie0 {
<&pcie_ref_clk>;
clock-names = "pcie", "pcie_bus", "pcie_phy", "pcie_aux",
"ref", "extref";
- reset-gpio = <&pcal6416_i2c3_u46 3 GPIO_ACTIVE_LOW>;
- vpcie3v3aux-supply = <®_m2_wlan>;
supports-clkreq;
status = "okay";
};
+&pcie0_port0 {
+ reset-gpios = <&pcal6416_i2c3_u46 3 GPIO_ACTIVE_LOW>;
+ vpcie3v3aux-supply = <®_m2_wlan>;
+};
+
&pcie0_ep {
pinctrl-0 = <&pinctrl_pcie0>;
pinctrl-names = "default";
@@ -1058,12 +1061,15 @@ &pcie1 {
<&pcie_ref_clk>;
clock-names = "pcie", "pcie_bus", "pcie_phy", "pcie_aux",
"ref", "extref";
- reset-gpio = <&pcal6416_i2c3_u46 1 GPIO_ACTIVE_LOW>;
- vpcie3v3aux-supply = <®_slot_pwr>;
supports-clkreq;
status = "okay";
};
+&pcie1_port0 {
+ reset-gpios = <&pcal6416_i2c3_u46 1 GPIO_ACTIVE_LOW>;
+ vpcie3v3aux-supply = <®_slot_pwr>;
+};
+
&pcie1_ep {
pinctrl-0 = <&pinctrl_pcie1>;
pinctrl-names = "default";
diff --git a/arch/arm64/boot/dts/freescale/imx943.dtsi b/arch/arm64/boot/dts/freescale/imx943.dtsi
index cf5b3dbb47ff7..01152fd0efa5e 100644
--- a/arch/arm64/boot/dts/freescale/imx943.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx943.dtsi
@@ -255,6 +255,17 @@ pcie1: pcie@4c380000 {
power-domains = <&scmi_devpd IMX94_PD_HSIO_TOP>;
fsl,max-link-speed = <3>;
status = "disabled";
+
+ pcie1_port0: pcie@0 {
+ compatible = "pciclass,0604";
+ device_type = "pci";
+ reg = <0x0 0x0 0x0 0x0 0x0>;
+ bus-range = <0x01 0xff>;
+
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges;
+ };
};
pcie1_ep: pcie-ep@4c380000 {
--
2.34.1
^ permalink raw reply related
* Re: [PATCH 3/4] mfd: mt6397-core: add mt6323 AUXADC support
From: Andy Shevchenko @ 2026-06-16 7:18 UTC (permalink / raw)
To: Roman Vivchar
Cc: Lee Jones, Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Matthias Brugger, AngeloGioacchino Del Regno, linux-iio,
devicetree, linux-kernel, linux-arm-kernel, linux-mediatek,
Ben Grisdale
In-Reply-To: <ycj3k-vV_tQv6u9OfCTP9G9kkeeE-_dbzg_oeHPXn2pb1oZe_Sa1BH6GUGuSd_gD6uPSJiTX7tz6MTlFcqGdWX-f3GMfyW2sLgAOCw6YeNE=@protonmail.com>
On Mon, Jun 15, 2026 at 11:10:20PM +0000, Roman Vivchar wrote:
> On Thursday, June 11th, 2026 at 7:37 PM, Lee Jones <lee@kernel.org> wrote:
> > On Tue, 02 Jun 2026, Roman Vivchar via B4 Relay wrote:
...
> > > + .of_compatible = "mediatek,mt6323-auxadc",
> >
> > [Severity: High]
> > Does adding this compatible string expose an unhandled edge case in the
> > auxadc driver?
> >
> > If this device is instantiated directly from a Device Tree without an MFD
> > parent (e.g., at the DT root), dev->parent could be the platform bus
> > (which has a NULL parent) or NULL itself.
> >
> > Looking at mt6323_auxadc_probe() in drivers/iio/adc/mt6323-auxadc.c:
> >
> > regmap = dev_get_regmap(dev->parent->parent, NULL);
> >
> > Could blindly dereferencing dev->parent->parent here, or passing a NULL
> > device to dev_get_regmap() (which calls devres_find()), result in a
> > kernel oops if probed as a root node?
>
> This is intended behavior, ADC always should be probed as a child of
> the mt6323 MFD because it doesn't have own MMIO space. Otherwise the
> devicetree is malformed. Recent mt6359 ADC driver has same thing with
> dev->parent->parent regmap.
I think Sashiko is overprotective here, but if changing, the whole lot of MFD
drivers should be changed (like Rafael recently has done for driver_override
issue in ACPI related drivers).
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH 3/3] backlight: lp8864: Convert from LED to backlight class driver
From: Sverdlin, Alexander @ 2026-06-16 7:17 UTC (permalink / raw)
To: linux-leds@vger.kernel.org, afd@ti.com
Cc: jingoohan1@gmail.com, linux-fbdev@vger.kernel.org,
pavel@kernel.org, lee@kernel.org, dri-devel@lists.freedesktop.org,
robh@kernel.org, linux-kernel@vger.kernel.org, krzk+dt@kernel.org,
danielt@kernel.org, conor+dt@kernel.org, deller@gmx.de,
devicetree@vger.kernel.org
In-Reply-To: <0b39450b-559b-43d4-a1e9-bb6684691cb5@ti.com>
Hi Andrew!
On Mon, 2026-06-15 at 14:51 -0500, Andrew Davis wrote:
> > Move the TI LP8864/LP8866 driver from drivers/leds/ to
> > drivers/video/backlight/
>
> Why move it? You can register a backlight device from any directory.
I'm personally fine with the driver residing in drivers/leds, it's
just that currently there are no combined drivers there, the combined
drivers providing both interfaces only live in video/backlight.
But if it's OK from the maintainers' perspective, it will be even
more consistent regarding Kconfig symbol.
> > and convert it to register a backlight class
> > device as its primary interface.
> >
>
> What do you mean by "primary"? You should be able to register with
> both frameworks and have the driver interop between as needed.
Well, I only meant the user's (or my own) perspective, sorry for confusion.
> > The motivation is a use case on a hot-pluggable segment of an I2C bus.
> > The generic led-backlight driver (drivers/video/backlight/led_bl.c) is a
> > platform driver and as such inherently non-hotpluggable.
>
> That isn't strictly true, there is platform_device_{del,unregister}(), so
> whatever your mechanism for removing the I2C device would be, the same
> could be done to the led_bl device before then removing the I2C device.
led_bl is not really designed to act on dynamically instantiated devices,
it's very much device-tree affine (of_count_phandle_with_args(), etc...)
> We don't want to have to move every LED driver that could possibly
> be used as a backlight to the backlight framework, the led_bl.c
> handles adapting LED->backlight as needed. So what you really need
> here is to de-couple led_bl.c from DT so it can better handle dynamic
> add/remove. Then this LED driver simply could register a "led-backlight"
> platform driver to handle the backlight interface, and remove the
> backlight device when it itself (the LED device) is removed.
The mechanism we have regarding hot plugging currently is just I2C bridge,
which de-registers and registers the bridged bus. So no additional drivers
are required, as long as I2C devices are self-contained and not glued with
platform devices.
So bottom line is, I'd prefer to just add the backlight interface to the
existing driver, no matter where it would live in the future.
--
Alexander Sverdlin
Siemens AG
www.siemens.com
^ permalink raw reply
* Re: [PATCH v2 2/4] iio: adc: mt6323-auxadc: add mt6323 PMIC AUXADC driver
From: Andy Shevchenko @ 2026-06-16 7:15 UTC (permalink / raw)
To: Roman Vivchar
Cc: Jonathan Cameron, Roman Vivchar via B4 Relay, David Lechner,
Nuno Sá, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
Lee Jones, linux-iio, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, Ben Grisdale
In-Reply-To: <E5H72l1Cg-JQGpaYAFl5TIyITmghqMsTaHQTR8VkuFtlzmfCGTpVlceNp8c2BDW7UoheO0Fx6NLJHmu56WHRNWdPNLsaoIKMCftj_bnyvEI=@protonmail.com>
On Mon, Jun 15, 2026 at 10:52:57PM +0000, Roman Vivchar wrote:
> On Sunday, June 14th, 2026 at 8:22 PM, Jonathan Cameron <jic23@kernel.org> wrote:
> > On Tue, 09 Jun 2026 16:31:59 +0300
> > Roman Vivchar via B4 Relay <devnull+rva333.protonmail.com@kernel.org> wrote:
...
> > > +/*
> > > + * The MediaTek MT6323 (as well as a lot of other PMICs) has the following hierarchy:
> > > + * PMIC AUXADC <- PMIC MFD <- SoC PWRAP (wrapper for PWRAP FSM)
> > > + *
> > > + * Therefore, PWRAP regmap should be obtained using dev->parent->parent.
> > > + */
> > > +struct mt6323_auxadc {
> > > + struct regmap *regmap;
> > > + struct mutex lock;
> > Locks should always have a comment on what data they are protecting.
> > I think this one is about protecting the state of a device during a channel read
> > by serializing those reads.
>
> Nuno said kerneldoc looks unnecessary on v1 [1]. How the comment should
> look?
The comment near to the member:
/* Why this lock? */
struct mutex lock;
...
> After these changes, should I keep or drop Andy's Reviewed-by?
Keep it, the above mentioned changes does not affect on that.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* [PATCH v3 3/3] dt-bindings: perf: marvell: add CN20K TAD PMU support
From: Geetha sowjanya @ 2026-06-16 7:11 UTC (permalink / raw)
To: linux-perf-users, linux-kernel, linux-arm-kernel, devicetree
Cc: mark.rutland, will, krzk+dt, gakula
In-Reply-To: <20260616071149.12523-1-gakula@marvell.com>
Marvell CN20K SoCs integrate a Performance Monitoring Unit (PMU)
associated with the LLC Tag-and-Data (TAD) blocks. The PMU provides
hardware counters to monitor cache traffic and performance events
via a dedicated MMIO region.
The CN20K LLC-TAD PMU is largely similar to CN10K, but differs in the
layout of PFC/PRF register offsets relative to each TAD base. These
offsets are derived from the compatible string in the driver and are
not described through Devicetree properties.
Because of this, using "marvell,cn10k-tad-pmu" as a fallback for CN20K
would result in incorrect register programming. Therefore, add a
separate compatible string:
"marvell,cn20k-tad-pmu"
Update the binding to document CN20K alongside CN10K.
Signed-off-by: Geetha sowjanya <gakula@marvell.com>
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
.../bindings/perf/marvell-cn10k-tad.yaml | 25 +++++++++++++------
1 file changed, 17 insertions(+), 8 deletions(-)
diff --git a/Documentation/devicetree/bindings/perf/marvell-cn10k-tad.yaml b/Documentation/devicetree/bindings/perf/marvell-cn10k-tad.yaml
index 362142252667..d11121a1e2c9 100644
--- a/Documentation/devicetree/bindings/perf/marvell-cn10k-tad.yaml
+++ b/Documentation/devicetree/bindings/perf/marvell-cn10k-tad.yaml
@@ -4,23 +4,32 @@
$id: http://devicetree.org/schemas/perf/marvell-cn10k-tad.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
-title: Marvell CN10K LLC-TAD performance monitor
+title: Marvell CN10K / CN20K LLC-TAD performance monitor
maintainers:
- Bhaskara Budiredla <bbudiredla@marvell.com>
+ - Geetha sowjanya <gakula@marvell.com>
description: |
- The Tag-and-Data units (TADs) maintain coherence and contain CN10K
- shared on-chip last level cache (LLC). The tad pmu measures the
- performance of last-level cache. Each tad pmu supports up to eight
- counters.
+ The Tag-and-Data units (TADs) maintain coherence and contain the
+ shared on-chip last level cache (LLC) on Marvell CN10K and CN20K SoCs.
+ The TAD PMU measures last-level cache performance. Each TAD PMU
+ supports up to eight counters.
- The DT setup comprises of number of tad blocks, the sizes of pmu
- regions, tad blocks and overall base address of the HW.
+ The DT setup describes the number of TAD blocks, the sizes of PMU
+ regions and TAD pages, and the overall MMIO base of the hardware.
+
+ marvell,cn20k-tad-pmu is not a compatible fallback for
+ marvell,cn10k-tad-pmu (and vice versa): the driver selects different
+ PFC/PRF MMIO offsets from the compatible string, and those offsets are
+ not described by separate DT properties today.
properties:
compatible:
- const: marvell,cn10k-tad-pmu
+ items:
+ - enum:
+ - marvell,cn10k-tad-pmu
+ - marvell,cn20k-tad-pmu
reg:
maxItems: 1
--
2.25.1
^ permalink raw reply related
* [PATCH v3 2/3] perf: marvell: Add CN20K LLC-TAD PMU support
From: Geetha sowjanya @ 2026-06-16 7:11 UTC (permalink / raw)
To: linux-perf-users, linux-kernel, linux-arm-kernel, devicetree
Cc: mark.rutland, will, krzk+dt, gakula
In-Reply-To: <20260616071149.12523-1-gakula@marvell.com>
Add support for the LLC Tag-and-Data (TAD) PMU present in
Marvell CN20K SoCs.
The CN20K TAD PMU is based on the CN10K design but differs in the
layout of PFC/PRF register offsets relative to each TAD base, and
introduces additional events. These offsets are selected by the driver
based on the compatible string and are not described via DT properties.
Because of this, "marvell,cn10k-tad-pmu" cannot be used as a fallback
for CN20K, as it would result in incorrect register programming.
Add support for "marvell,cn20k-tad-pmu" by:
- Introducing a TAD_PMU_V3 profile with CN20K-specific register bases
- Extending the event map for new CN20K events
- Matching the PMU via OF and ACPI (MRVL000F)
Signed-off-by: Geetha sowjanya <gakula@marvell.com>
---
Changelog (since v2)
--------------------
- Validate the eventId using an appropriate mask to ensure
it is restricted to 8 bits.
Changelog (since v1)
--------------------
- Hide V3-only events on CN10K via sysfs is_visible and reject them in
event_init.
- Use CN20K-specific MPAM PRF bits (MATCH_MPAMNS, partid << 10) for V3;
software partid is limited to nine bits so this does not collide with
the fixed bit at 25.
- Reset hwc->prev_count when starting counters so reads match cleared HW.
drivers/perf/marvell_cn10k_tad_pmu.c | 54 ++++++++++++++++++++++++++--
1 file changed, 52 insertions(+), 2 deletions(-)
diff --git a/drivers/perf/marvell_cn10k_tad_pmu.c b/drivers/perf/marvell_cn10k_tad_pmu.c
index 69a6648fa664..cd81bf8ff569 100644
--- a/drivers/perf/marvell_cn10k_tad_pmu.c
+++ b/drivers/perf/marvell_cn10k_tad_pmu.c
@@ -17,11 +17,14 @@
#define TAD_PRF_OFFSET 0x900
#define TAD_PFC_OFFSET 0x800
+#define TAD_PRF_NS_OFFSET 0x30900
+#define TAD_PFC_NS_OFFSET 0x30800
#define TAD_PFC(base, counter) ((base) | ((u64)(counter) << 3))
#define TAD_PRF(base, counter) ((base) | ((u64)(counter) << 3))
#define TAD_PRF_CNTSEL_MASK 0xFF
#define TAD_PRF_MATCH_PARTID BIT(8)
#define TAD_PRF_PARTID_NS BIT(10)
+#define TAD_PRF_MATCH_MPAMNS BIT(25)
/*
* config1: bits 0..8 MPAM partition id (including 0); bit 9 requests
* filtering for MPAM-capable events. All-zero config1 means no filter.
@@ -39,6 +42,7 @@ struct tad_region {
enum mrvl_tad_pmu_version {
TAD_PMU_V1 = 1,
TAD_PMU_V2,
+ TAD_PMU_V3,
};
struct tad_pmu_data {
@@ -86,8 +90,15 @@ static void tad_pmu_start_counter(struct tad_pmu *pmu,
if (use_mpam && event_idx > 0x19 && event_idx < 0x21) {
partid_filter = TAD_PRF_MATCH_PARTID | TAD_PRF_PARTID_NS |
((u64)partid << 11);
+
+ if (pdata->id == TAD_PMU_V3)
+ partid_filter = TAD_PRF_MATCH_PARTID | TAD_PRF_MATCH_MPAMNS |
+ ((u64)partid << 10);
}
+ /* CN10K support events 0:24*/
+ if (pdata->id == TAD_PMU_V1 && event_idx >= 0x25)
+ return;
for (i = 0; i < pmu->region_cnt; i++) {
reg_val = event_idx & 0xFF;
@@ -160,6 +171,7 @@ static void tad_pmu_event_counter_start(struct perf_event *event, int flags)
struct hw_perf_event *hwc = &event->hw;
hwc->state = 0;
+ local64_set(&hwc->prev_count, 0);
tad_pmu->ops->start_counter(tad_pmu, event);
}
@@ -220,6 +232,8 @@ static int tad_pmu_event_init(struct perf_event *event)
if (cfg1)
return -EINVAL;
} else {
+ if (pdata->id == TAD_PMU_V1 && event_idx >= 0x25)
+ return -EINVAL;
if ((cfg1 & GENMASK(8, 0)) && !(cfg1 & TAD_PARTID_FILTER_EN))
return -EINVAL;
if (cfg1 & TAD_PARTID_FILTER_EN) {
@@ -246,6 +260,22 @@ static ssize_t tad_pmu_event_show(struct device *dev,
return sysfs_emit(page, "event=0x%02llx\n", pmu_attr->id);
}
+static umode_t tad_pmu_event_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int unused)
+{
+ struct pmu *pmu = dev_get_drvdata(kobj_to_dev(kobj));
+ struct tad_pmu *t = to_tad_pmu(pmu);
+ struct device_attribute *da = container_of(attr, struct device_attribute,
+ attr);
+ struct perf_pmu_events_attr *e = container_of(da, struct perf_pmu_events_attr,
+ attr);
+ u64 id = e->id;
+
+ if (t->pdata->id != TAD_PMU_V3 && id >= 0x25)
+ return 0;
+ return attr->mode;
+}
+
#define TAD_PMU_EVENT_ATTR(name, config) \
PMU_EVENT_ATTR_ID(name, tad_pmu_event_show, config)
@@ -287,12 +317,25 @@ static struct attribute *tad_pmu_event_attrs[] = {
TAD_PMU_EVENT_ATTR(tad_dat_rd_byp, 0x22),
TAD_PMU_EVENT_ATTR(tad_ifb_occ, 0x23),
TAD_PMU_EVENT_ATTR(tad_req_occ, 0x24),
+ TAD_PMU_EVENT_ATTR(tad_req_msh_out_dtg_evict, 0x25),
+ TAD_PMU_EVENT_ATTR(tad_req_msh_out_ltg_evict, 0x26),
+ TAD_PMU_EVENT_ATTR(tad_rsp_msh_out_mpam, 0x28),
+ TAD_PMU_EVENT_ATTR(tad_replays, 0x29),
+ TAD_PMU_EVENT_ATTR(tad_req_byp0, 0x2a),
+ TAD_PMU_EVENT_ATTR(tad_req_byp1, 0x2b),
+ TAD_PMU_EVENT_ATTR(tad_txreq_byp, 0x2c),
+ TAD_PMU_EVENT_ATTR(tad_time_in_dslp, 0x2d),
+ TAD_PMU_EVENT_ATTR(tad_time_elapsed, 0x2e),
+ TAD_PMU_EVENT_ATTR(tad_req_msh_out_dss_rd_128mrg, 0x2f),
+ TAD_PMU_EVENT_ATTR(tad_req_msh_out_dss_wr_128mrg, 0x30),
+ TAD_PMU_EVENT_ATTR(tad_tot_cycle, 0xff),
NULL
};
static const struct attribute_group tad_pmu_events_attr_group = {
.name = "events",
.attrs = tad_pmu_event_attrs,
+ .is_visible = tad_pmu_event_attr_is_visible,
};
static struct attribute *ody_tad_pmu_event_attrs[] = {
@@ -478,7 +521,7 @@ static int tad_pmu_probe(struct platform_device *pdev)
.read = tad_pmu_event_counter_read,
};
- if (version == TAD_PMU_V1) {
+ if (version == TAD_PMU_V1 || version == TAD_PMU_V3) {
tad_pmu->pmu.attr_groups = tad_pmu_attr_groups;
tad_pmu->ops = &tad_pmu_ops;
} else {
@@ -521,6 +564,11 @@ static const struct tad_pmu_data tad_pmu_data = {
.tad_pfc_offset = TAD_PFC_OFFSET,
};
+static const struct tad_pmu_data tad_pmu_cn20k_data = {
+ .id = TAD_PMU_V3,
+ .tad_prf_offset = TAD_PRF_NS_OFFSET,
+ .tad_pfc_offset = TAD_PFC_NS_OFFSET,
+};
#endif
#ifdef CONFIG_ACPI
@@ -534,6 +582,7 @@ static const struct tad_pmu_data tad_pmu_v2_data = {
#ifdef CONFIG_OF
static const struct of_device_id tad_pmu_of_match[] = {
{ .compatible = "marvell,cn10k-tad-pmu", .data = &tad_pmu_data },
+ { .compatible = "marvell,cn20k-tad-pmu", .data = &tad_pmu_cn20k_data },
{},
};
#endif
@@ -542,6 +591,7 @@ static const struct of_device_id tad_pmu_of_match[] = {
static const struct acpi_device_id tad_pmu_acpi_match[] = {
{"MRVL000B", (kernel_ulong_t)&tad_pmu_data},
{"MRVL000D", (kernel_ulong_t)&tad_pmu_v2_data},
+ {"MRVL000F", (kernel_ulong_t)&tad_pmu_cn20k_data},
{},
};
MODULE_DEVICE_TABLE(acpi, tad_pmu_acpi_match);
@@ -603,6 +653,6 @@ static void __exit tad_pmu_exit(void)
module_init(tad_pmu_init);
module_exit(tad_pmu_exit);
-MODULE_DESCRIPTION("Marvell CN10K LLC-TAD perf driver");
+MODULE_DESCRIPTION("Marvell CN10K/CN20K LLC-TAD perf driver");
MODULE_AUTHOR("Bhaskara Budiredla <bbudiredla@marvell.com>");
MODULE_LICENSE("GPL v2");
--
2.25.1
^ permalink raw reply related
* [PATCH v3 1/3] perf: marvell: Add MPAM partid filtering to CN10K TAD PMU
From: Geetha sowjanya @ 2026-06-16 7:11 UTC (permalink / raw)
To: linux-perf-users, linux-kernel, linux-arm-kernel, devicetree
Cc: mark.rutland, will, krzk+dt, gakula
In-Reply-To: <20260616071149.12523-1-gakula@marvell.com>
From: Tanmay Jagdale <tanmay@marvell.com>
The TAD PMU exposes counters that can be filtered by MPAM partition id
for a subset of allocation and hit events.
Add a 9-bit partid format attribute (config1) and route counter programming
through variant-specific ops so CN10K keeps MPAM-capable programming while
Odyssey keeps the reduced event set without advertising partid in sysfs.
Probe no longer mutates the platform_device MMIO resource (walk a local
map_start), rejects tad-cnt / page sizes of zero, validates the memory
window against tad-cnt, and registers the perf PMU before hotplug with
correct unwind.
Example:
perf stat -e tad/tad_alloc_any,partid=0x12,partid_en=1/ -- <program>
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
---
Changelog (since v2)
--------------------
- Validate the eventId using an appropriate mask to ensure
it is restricted to 8 bits
Changelog (since v1)
--------------------
- Fix config1 filter enable to use bit 9 consistently with the PMU format
string (partid_en) and reject reserved bits with GENMASK(9, 0).
- Register perf_pmu_register before cpuhp_state_add_instance_nocalls and
unregister on hotplug failure.
drivers/perf/marvell_cn10k_tad_pmu.c | 216 ++++++++++++++++++++-------
1 file changed, 164 insertions(+), 52 deletions(-)
diff --git a/drivers/perf/marvell_cn10k_tad_pmu.c b/drivers/perf/marvell_cn10k_tad_pmu.c
index 51ccb0befa05..69a6648fa664 100644
--- a/drivers/perf/marvell_cn10k_tad_pmu.c
+++ b/drivers/perf/marvell_cn10k_tad_pmu.c
@@ -7,6 +7,7 @@
#define pr_fmt(fmt) "tad_pmu: " fmt
#include <linux/io.h>
+#include <linux/bits.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/cpuhotplug.h>
@@ -14,12 +15,20 @@
#include <linux/platform_device.h>
#include <linux/acpi.h>
-#define TAD_PFC_OFFSET 0x800
-#define TAD_PFC(counter) (TAD_PFC_OFFSET | (counter << 3))
#define TAD_PRF_OFFSET 0x900
-#define TAD_PRF(counter) (TAD_PRF_OFFSET | (counter << 3))
+#define TAD_PFC_OFFSET 0x800
+#define TAD_PFC(base, counter) ((base) | ((u64)(counter) << 3))
+#define TAD_PRF(base, counter) ((base) | ((u64)(counter) << 3))
#define TAD_PRF_CNTSEL_MASK 0xFF
+#define TAD_PRF_MATCH_PARTID BIT(8)
+#define TAD_PRF_PARTID_NS BIT(10)
+/*
+ * config1: bits 0..8 MPAM partition id (including 0); bit 9 requests
+ * filtering for MPAM-capable events. All-zero config1 means no filter.
+ */
+#define TAD_PARTID_FILTER_EN BIT(9)
#define TAD_MAX_COUNTERS 8
+#define TAD_EVENT_SEL_MASK GENMASK(7, 0)
#define to_tad_pmu(p) (container_of(p, struct tad_pmu, pmu))
@@ -27,30 +36,92 @@ struct tad_region {
void __iomem *base;
};
+enum mrvl_tad_pmu_version {
+ TAD_PMU_V1 = 1,
+ TAD_PMU_V2,
+};
+
+struct tad_pmu_data {
+ int id;
+ u64 tad_prf_offset;
+ u64 tad_pfc_offset;
+};
+
struct tad_pmu {
struct pmu pmu;
struct tad_region *regions;
u32 region_cnt;
unsigned int cpu;
+ const struct tad_pmu_ops *ops;
+ const struct tad_pmu_data *pdata;
struct hlist_node node;
struct perf_event *events[TAD_MAX_COUNTERS];
DECLARE_BITMAP(counters_map, TAD_MAX_COUNTERS);
};
-enum mrvl_tad_pmu_version {
- TAD_PMU_V1 = 1,
- TAD_PMU_V2,
-};
-
-struct tad_pmu_data {
- int id;
+struct tad_pmu_ops {
+ void (*start_counter)(struct tad_pmu *pmu, struct perf_event *event);
};
static int tad_pmu_cpuhp_state;
+static void tad_pmu_start_counter(struct tad_pmu *pmu,
+ struct perf_event *event)
+{
+ const struct tad_pmu_data *pdata = pmu->pdata;
+ struct hw_perf_event *hwc = &event->hw;
+ u32 event_idx = (u32)(event->attr.config & TAD_EVENT_SEL_MASK);
+ u32 counter_idx = hwc->idx;
+ u64 partid_filter = 0;
+ u64 reg_val;
+ u64 cfg1 = event->attr.config1;
+ bool use_mpam = cfg1 & TAD_PARTID_FILTER_EN;
+ u32 partid = (u32)(cfg1 & GENMASK(8, 0));
+ int i;
+
+ for (i = 0; i < pmu->region_cnt; i++)
+ writeq_relaxed(0, pmu->regions[i].base +
+ TAD_PFC(pdata->tad_pfc_offset, counter_idx));
+
+ if (use_mpam && event_idx > 0x19 && event_idx < 0x21) {
+ partid_filter = TAD_PRF_MATCH_PARTID | TAD_PRF_PARTID_NS |
+ ((u64)partid << 11);
+ }
+
+
+ for (i = 0; i < pmu->region_cnt; i++) {
+ reg_val = event_idx & 0xFF;
+ reg_val |= partid_filter;
+ writeq_relaxed(reg_val, pmu->regions[i].base +
+ TAD_PRF(pdata->tad_prf_offset, counter_idx));
+ }
+}
+
+static void tad_pmu_v2_start_counter(struct tad_pmu *pmu,
+ struct perf_event *event)
+{
+ const struct tad_pmu_data *pdata = pmu->pdata;
+ struct hw_perf_event *hwc = &event->hw;
+ u32 event_idx = (u32)(event->attr.config & TAD_EVENT_SEL_MASK);
+ u32 counter_idx = hwc->idx;
+ u64 reg_val;
+ int i;
+
+ for (i = 0; i < pmu->region_cnt; i++)
+ writeq_relaxed(0, pmu->regions[i].base +
+ TAD_PFC(pdata->tad_pfc_offset, counter_idx));
+
+ for (i = 0; i < pmu->region_cnt; i++) {
+ reg_val = event_idx & 0xFF;
+ writeq_relaxed(reg_val, pmu->regions[i].base +
+ TAD_PRF(pdata->tad_prf_offset, counter_idx));
+ }
+}
+
static void tad_pmu_event_counter_read(struct perf_event *event)
{
struct tad_pmu *tad_pmu = to_tad_pmu(event->pmu);
+ const struct tad_pmu_data *pdata = tad_pmu->pdata;
struct hw_perf_event *hwc = &event->hw;
u32 counter_idx = hwc->idx;
u64 prev, new;
@@ -60,7 +131,7 @@ static void tad_pmu_event_counter_read(struct perf_event *event)
prev = local64_read(&hwc->prev_count);
for (i = 0, new = 0; i < tad_pmu->region_cnt; i++)
new += readq(tad_pmu->regions[i].base +
- TAD_PFC(counter_idx));
+ TAD_PFC(pdata->tad_pfc_offset, counter_idx));
} while (local64_cmpxchg(&hwc->prev_count, prev, new) != prev);
local64_add(new - prev, &event->count);
@@ -69,16 +140,14 @@ static void tad_pmu_event_counter_read(struct perf_event *event)
static void tad_pmu_event_counter_stop(struct perf_event *event, int flags)
{
struct tad_pmu *tad_pmu = to_tad_pmu(event->pmu);
+ const struct tad_pmu_data *pdata = tad_pmu->pdata;
struct hw_perf_event *hwc = &event->hw;
u32 counter_idx = hwc->idx;
int i;
- /* TAD()_PFC() stop counting on the write
- * which sets TAD()_PRF()[CNTSEL] == 0
- */
for (i = 0; i < tad_pmu->region_cnt; i++) {
writeq_relaxed(0, tad_pmu->regions[i].base +
- TAD_PRF(counter_idx));
+ TAD_PRF(pdata->tad_prf_offset, counter_idx));
}
tad_pmu_event_counter_read(event);
@@ -89,26 +158,10 @@ static void tad_pmu_event_counter_start(struct perf_event *event, int flags)
{
struct tad_pmu *tad_pmu = to_tad_pmu(event->pmu);
struct hw_perf_event *hwc = &event->hw;
- u32 event_idx = event->attr.config;
- u32 counter_idx = hwc->idx;
- u64 reg_val;
- int i;
hwc->state = 0;
- /* Typically TAD_PFC() are zeroed to start counting */
- for (i = 0; i < tad_pmu->region_cnt; i++)
- writeq_relaxed(0, tad_pmu->regions[i].base +
- TAD_PFC(counter_idx));
-
- /* TAD()_PFC() start counting on the write
- * which sets TAD()_PRF()[CNTSEL] != 0
- */
- for (i = 0; i < tad_pmu->region_cnt; i++) {
- reg_val = event_idx & 0xFF;
- writeq_relaxed(reg_val, tad_pmu->regions[i].base +
- TAD_PRF(counter_idx));
- }
+ tad_pmu->ops->start_counter(tad_pmu, event);
}
static void tad_pmu_event_counter_del(struct perf_event *event, int flags)
@@ -128,7 +181,6 @@ static int tad_pmu_event_counter_add(struct perf_event *event, int flags)
struct hw_perf_event *hwc = &event->hw;
int idx;
- /* Get a free counter for this event */
idx = find_first_zero_bit(tad_pmu->counters_map, TAD_MAX_COUNTERS);
if (idx == TAD_MAX_COUNTERS)
return -EAGAIN;
@@ -148,6 +200,9 @@ static int tad_pmu_event_counter_add(struct perf_event *event, int flags)
static int tad_pmu_event_init(struct perf_event *event)
{
struct tad_pmu *tad_pmu = to_tad_pmu(event->pmu);
+ const struct tad_pmu_data *pdata = tad_pmu->pdata;
+ u32 event_idx = (u32)(event->attr.config & TAD_EVENT_SEL_MASK);
+ u64 cfg1 = event->attr.config1;
if (event->attr.type != event->pmu->type)
return -ENOENT;
@@ -158,6 +213,23 @@ static int tad_pmu_event_init(struct perf_event *event)
if (event->state != PERF_EVENT_STATE_OFF)
return -EINVAL;
+ if (event->attr.config & ~TAD_EVENT_SEL_MASK)
+ return -EINVAL;
+
+ if (pdata->id == TAD_PMU_V2) {
+ if (cfg1)
+ return -EINVAL;
+ } else {
+ if ((cfg1 & GENMASK(8, 0)) && !(cfg1 & TAD_PARTID_FILTER_EN))
+ return -EINVAL;
+ if (cfg1 & TAD_PARTID_FILTER_EN) {
+ if (event_idx <= 0x19 || event_idx >= 0x21)
+ return -EINVAL;
+ }
+ if (cfg1 & ~GENMASK(9, 0))
+ return -EINVAL;
+ }
+
event->cpu = tad_pmu->cpu;
event->hw.idx = -1;
event->hw.config_base = event->attr.config;
@@ -232,7 +304,7 @@ static struct attribute *ody_tad_pmu_event_attrs[] = {
TAD_PMU_EVENT_ATTR(tad_hit_ltg, 0x1e),
TAD_PMU_EVENT_ATTR(tad_hit_any, 0x1f),
TAD_PMU_EVENT_ATTR(tad_tag_rd, 0x20),
- TAD_PMU_EVENT_ATTR(tad_tot_cycle, 0xFF),
+ TAD_PMU_EVENT_ATTR(tad_tot_cycle, 0xff),
NULL
};
@@ -242,9 +314,13 @@ static const struct attribute_group ody_tad_pmu_events_attr_group = {
};
PMU_FORMAT_ATTR(event, "config:0-7");
+PMU_FORMAT_ATTR(partid, "config1:0-8");
+PMU_FORMAT_ATTR(partid_en, "config1:9-9");
static struct attribute *tad_pmu_format_attrs[] = {
&format_attr_event.attr,
+ &format_attr_partid.attr,
+ &format_attr_partid_en.attr,
NULL
};
@@ -253,6 +329,16 @@ static struct attribute_group tad_pmu_format_attr_group = {
.attrs = tad_pmu_format_attrs,
};
+static struct attribute *ody_tad_pmu_format_attrs[] = {
+ &format_attr_event.attr,
+ NULL
+};
+
+static struct attribute_group ody_tad_pmu_format_attr_group = {
+ .name = "format",
+ .attrs = ody_tad_pmu_format_attrs,
+};
+
static ssize_t tad_pmu_cpumask_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -281,16 +367,25 @@ static const struct attribute_group *tad_pmu_attr_groups[] = {
static const struct attribute_group *ody_tad_pmu_attr_groups[] = {
&ody_tad_pmu_events_attr_group,
- &tad_pmu_format_attr_group,
+ &ody_tad_pmu_format_attr_group,
&tad_pmu_cpumask_attr_group,
NULL
};
+static const struct tad_pmu_ops tad_pmu_ops = {
+ .start_counter = tad_pmu_start_counter,
+};
+
+static const struct tad_pmu_ops tad_pmu_v2_ops = {
+ .start_counter = tad_pmu_v2_start_counter,
+};
+
static int tad_pmu_probe(struct platform_device *pdev)
{
const struct tad_pmu_data *dev_data;
struct device *dev = &pdev->dev;
struct tad_region *regions;
+ resource_size_t map_start;
struct tad_pmu *tad_pmu;
struct resource *res;
u32 tad_pmu_page_size;
@@ -298,7 +393,6 @@ static int tad_pmu_probe(struct platform_device *pdev)
u32 tad_cnt;
int version;
int i, ret;
- char *name;
tad_pmu = devm_kzalloc(&pdev->dev, sizeof(*tad_pmu), GFP_KERNEL);
if (!tad_pmu)
@@ -312,6 +406,7 @@ static int tad_pmu_probe(struct platform_device *pdev)
return -ENODEV;
}
version = dev_data->id;
+ tad_pmu->pdata = dev_data;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
@@ -338,22 +433,31 @@ static int tad_pmu_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "Can't find tad-cnt property\n");
return ret;
}
+ if (!tad_cnt || !tad_page_size || !tad_pmu_page_size) {
+ dev_err(&pdev->dev, "Invalid tad-cnt or page size\n");
+ return -EINVAL;
+ }
regions = devm_kcalloc(&pdev->dev, tad_cnt,
sizeof(*regions), GFP_KERNEL);
if (!regions)
return -ENOMEM;
- /* ioremap the distributed TAD pmu regions */
- for (i = 0; i < tad_cnt && res->start < res->end; i++) {
- regions[i].base = devm_ioremap(&pdev->dev,
- res->start,
+ map_start = res->start;
+ for (i = 0; i < tad_cnt; i++) {
+ if (map_start > res->end ||
+ tad_pmu_page_size > (resource_size_t)(res->end - map_start + 1)) {
+ dev_err(&pdev->dev, "TAD PMU mem window too small for tad-cnt=%u\n",
+ tad_cnt);
+ return -EINVAL;
+ }
+ regions[i].base = devm_ioremap(&pdev->dev, map_start,
tad_pmu_page_size);
if (!regions[i].base) {
dev_err(&pdev->dev, "TAD%d ioremap fail\n", i);
return -ENOMEM;
}
- res->start += tad_page_size;
+ map_start += tad_page_size;
}
tad_pmu->regions = regions;
@@ -374,28 +478,31 @@ static int tad_pmu_probe(struct platform_device *pdev)
.read = tad_pmu_event_counter_read,
};
- if (version == TAD_PMU_V1)
+ if (version == TAD_PMU_V1) {
tad_pmu->pmu.attr_groups = tad_pmu_attr_groups;
- else
+ tad_pmu->ops = &tad_pmu_ops;
+ } else {
tad_pmu->pmu.attr_groups = ody_tad_pmu_attr_groups;
+ tad_pmu->ops = &tad_pmu_v2_ops;
+ }
tad_pmu->cpu = raw_smp_processor_id();
- /* Register pmu instance for cpu hotplug */
+ ret = perf_pmu_register(&tad_pmu->pmu, "tad", -1);
+ if (ret) {
+ dev_err(&pdev->dev, "Error %d registering perf PMU\n", ret);
+ return ret;
+ }
+
ret = cpuhp_state_add_instance_nocalls(tad_pmu_cpuhp_state,
&tad_pmu->node);
if (ret) {
dev_err(&pdev->dev, "Error %d registering hotplug\n", ret);
+ perf_pmu_unregister(&tad_pmu->pmu);
return ret;
}
- name = "tad";
- ret = perf_pmu_register(&tad_pmu->pmu, name, -1);
- if (ret)
- cpuhp_state_remove_instance_nocalls(tad_pmu_cpuhp_state,
- &tad_pmu->node);
-
- return ret;
+ return 0;
}
static void tad_pmu_remove(struct platform_device *pdev)
@@ -410,12 +517,17 @@ static void tad_pmu_remove(struct platform_device *pdev)
#if defined(CONFIG_OF) || defined(CONFIG_ACPI)
static const struct tad_pmu_data tad_pmu_data = {
.id = TAD_PMU_V1,
+ .tad_prf_offset = TAD_PRF_OFFSET,
+ .tad_pfc_offset = TAD_PFC_OFFSET,
};
+
#endif
#ifdef CONFIG_ACPI
static const struct tad_pmu_data tad_pmu_v2_data = {
.id = TAD_PMU_V2,
+ .tad_prf_offset = TAD_PRF_OFFSET,
+ .tad_pfc_offset = TAD_PFC_OFFSET,
};
#endif
@@ -491,6 +603,6 @@ static void __exit tad_pmu_exit(void)
module_init(tad_pmu_init);
module_exit(tad_pmu_exit);
-MODULE_DESCRIPTION("Marvell CN10K LLC-TAD Perf driver");
+MODULE_DESCRIPTION("Marvell CN10K LLC-TAD perf driver");
MODULE_AUTHOR("Bhaskara Budiredla <bbudiredla@marvell.com>");
MODULE_LICENSE("GPL v2");
--
2.25.1
^ permalink raw reply related
* [PATCH v3 0/3] perf: marvell: LLC-TAD PMU MPAM filtering support
From: Geetha sowjanya @ 2026-06-16 7:11 UTC (permalink / raw)
To: linux-perf-users, linux-kernel, linux-arm-kernel, devicetree
Cc: mark.rutland, will, krzk+dt, gakula
This series extends the Marvell LLC-TAD performance driver used on CN10K
and CN20K systems.
Patch 1 adds optional MPAM partition-id filtering for the subset of TAD
events that support it, exposes partid / partid_en in the PMU format string,
and keeps the reduced Odyssey event surface without advertising partid where
it does not apply. It also fixes probe resource handling (no in-place
mutation of platform_get_resource() bounds, validate MMIO window vs
tad-cnt), orders perf registration vs hotplug with unwind, and aligns the
filter-enable bit in config1 with the sysfs format (bit 9).
Patch 2 introduces CN20K LLC-TAD support: non-standard PFC/PRF offsets,
additional programmable events with visibility checks so CN10K does not
advertise V3-only events, CN20K-specific MPAM encoding for the V3 profile,
local64_set(prev_count) on counter start, and device discovery via OF and
ACPI.
Patch 3 extends the DeviceTree binding for marvell,cn20k-tad-pmu.
Changes since v2
----------------
- Validate the eventId using an appropriate mask to ensure it is restricted to 8 bits.
Changes since v1
----------------
- config1: use bit 9 for MPAM filter enable consistently with partid_en in
the PMU format; allow only bits 0..9 in event_init on CN10K/CN20K paths.
- Reject reserved bits in attr.config and use the same 8-bit event index in
start_counter as in event_init so MPAM validation cannot be bypassed.
- Hide V3-only sysfs events on V1.
- Reset prev_count when starting counters after clearing hardware.
- DT binding: explain non-fallback compatibles for CN10K vs CN20K.
Tanmay Jagdale (1):
perf: marvell: Add MPAM partid filtering to CN10K TAD PMU
Geetha sowjanya (2):
perf: marvell: Add CN20K LLC-TAD PMU support
dt-bindings: perf: marvell: Extend CN10K TAD PMU binding for CN20K
Signed-off-by: Geetha sowjanya <gakula@marvell.com>
--
2.25.1
^ permalink raw reply
* Re: [PATCH v3 2/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel
From: Devarsh Thakkar @ 2026-06-16 7:08 UTC (permalink / raw)
To: LiangCheng Wang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Wig Cheng
Cc: dri-devel, devicetree, linux-kernel, Tomi Valkeinen
In-Reply-To: <20260529-bar-v3-2-5c2ac1c751ee@gmail.com>
Hi LiangCheng,
Thanks for the patch.
On 29/05/26 16:01, LiangCheng Wang wrote:
> Introduce a DRM driver for the Mayqueen Pixpaper 4.26
> monochrome e-ink display panel, which is controlled via SPI.
> The driver supports an 800x480 display with XRGB8888
> framebuffer input.
>
1) Could you please share the datasheet for the display controller used
inside this pixpaper version ?
The PIXPAPER 4.26 command set matches the Solomon SSD1683 almost exactly
as documented in the SSD1683 datasheet [0]. It can also be seen that the
macro values match exactly as well with the posted driver [1].
The only material difference is the ctrl2 update byte 0xF4 vs the
SSD1683 documented sequences (0xFF for BW full refresh, 0xF7 for
3-color). 0xF4 omits the final Disable Analog + Disable OSC bits that
SSD1683 normally expects.
2) Does 0xFF or 0xF7 mode work as well for your display or is it
strictly 0xF4 which seems to mean that analog and osc bits are disabled
?
3) Also could you confirm which display controller IC does the PIXPAPER
4.26 use ? If it is SSD1683 (or any other ssd16xx variant), it would be
appropriate to add this panel as a new display panel entry in
panel-ssd16xx.c rather than a separate driver to avoid code duplication.
You can refer how "Good Display GDEY042T81" was added for e.g. it should
be something like :
[PIXPAPER426M] = {
.data_entry_mode = SSD16XX_DATA_ENTRY_XINC_YINC,
.driver_output_ctrl_byte3 = 0x02, /* SM=1 interlaced scan */
.default_refresh_mode = SSD16XX_REFRESH_FULL, /* single
mode: 0xF4 */
.default_border_waveform_init = SSD16XX_BORDER_LUT1,
.default_border_waveform_update = SSD16XX_BORDER_LUT1,
.default_clear_on_init = -1,
.default_clear_on_disable = -1,
.red_supported = false,
.mode = &pixpaper426m_mode,
},
and a new compatible entry :
static const struct of_device_id ssd16xx_of_match[] = {
{ .compatible = "gooddisplay,gdey042t81", .data = (void
*)GDEY042T81 },
{ .compatible = "mayqueen,pixpaper-426m", .data = (void
*)PIXPAPER426M },
{ }
};
[0] : https://www.crystalfontz.com/controllers/SolomonSystech/SSD1683
[1] : https://lore.kernel.org/all/20260430183311.2978142-4-devarsht@ti.com/
Kindly let me know if any queries.
Regards
Devarsh
> Also, add Kconfig and Makefile entries for the driver and
> update MAINTAINERS for the Pixpaper DRM drivers and binding.
>
> Signed-off-by: LiangCheng Wang <zaq14760@gmail.com>
> ---
> MAINTAINERS | 3 +-
> drivers/gpu/drm/tiny/Kconfig | 16 +
> drivers/gpu/drm/tiny/Makefile | 1 +
> drivers/gpu/drm/tiny/pixpaper-426m.c | 817 +++++++++++++++++++++++++++++++++++
> 4 files changed, 836 insertions(+), 1 deletion(-)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 882214b0e7db53bb8cc8e75b5d2269ee0591ea20..eebd73ee1f531d3785ec963da03fbab265c2d188 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -8234,11 +8234,12 @@ T: git https://gitlab.freedesktop.org/drm/misc/kernel.git
> F: Documentation/devicetree/bindings/display/repaper.txt
> F: drivers/gpu/drm/tiny/repaper.c
>
> -DRM DRIVER FOR PIXPAPER E-INK PANEL
> +DRM DRIVER FOR PIXPAPER E-INK PANELS
> M: LiangCheng Wang <zaq14760@gmail.com>
> L: dri-devel@lists.freedesktop.org
> S: Maintained
> F: Documentation/devicetree/bindings/display/mayqueen,pixpaper.yaml
> +F: drivers/gpu/drm/tiny/pixpaper-426m.c
> F: drivers/gpu/drm/tiny/pixpaper.c
>
> DRM DRIVER FOR QEMU'S CIRRUS DEVICE
> diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig
> index f0e72d4b6a4709564e63c758e857bdb4a320dbe7..028c4314106ac31dfa717f6433c28e58b34c21e8 100644
> --- a/drivers/gpu/drm/tiny/Kconfig
> +++ b/drivers/gpu/drm/tiny/Kconfig
> @@ -98,6 +98,22 @@ config DRM_PIXPAPER
>
> If M is selected, the module will be built as pixpaper.ko.
>
> +config DRM_PIXPAPER_426M
> + tristate "DRM support for PIXPAPER 4.26 monochrome display panel"
> + depends on DRM && SPI
> + depends on MMU
> + select DRM_CLIENT_SELECTION
> + select DRM_GEM_SHMEM_HELPER
> + select DRM_KMS_HELPER
> + help
> + DRM driver for the Mayqueen Pixpaper 4.26 monochrome e-ink
> + display panel.
> +
> + This driver supports SPI-connected 800x480 monochrome panels
> + with an XRGB8888 framebuffer input format.
> +
> + If M is selected, the module will be built as pixpaper-426m.ko.
> +
> config TINYDRM_HX8357D
> tristate "DRM support for HX8357D display panels"
> depends on DRM && SPI
> diff --git a/drivers/gpu/drm/tiny/Makefile b/drivers/gpu/drm/tiny/Makefile
> index 48d30bf6152f979404ac1004174587823a30109e..037b751a1a851cc2f86f701ff71008bcb9c59f29 100644
> --- a/drivers/gpu/drm/tiny/Makefile
> +++ b/drivers/gpu/drm/tiny/Makefile
> @@ -7,6 +7,7 @@ obj-$(CONFIG_DRM_CIRRUS_QEMU) += cirrus-qemu.o
> obj-$(CONFIG_DRM_GM12U320) += gm12u320.o
> obj-$(CONFIG_DRM_PANEL_MIPI_DBI) += panel-mipi-dbi.o
> obj-$(CONFIG_DRM_PIXPAPER) += pixpaper.o
> +obj-$(CONFIG_DRM_PIXPAPER_426M) += pixpaper-426m.o
> obj-$(CONFIG_TINYDRM_HX8357D) += hx8357d.o
> obj-$(CONFIG_TINYDRM_ILI9163) += ili9163.o
> obj-$(CONFIG_TINYDRM_ILI9225) += ili9225.o
> diff --git a/drivers/gpu/drm/tiny/pixpaper-426m.c b/drivers/gpu/drm/tiny/pixpaper-426m.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..99464d564f315543037a3621ff85f98f1bd8f34c
> --- /dev/null
> +++ b/drivers/gpu/drm/tiny/pixpaper-426m.c
> @@ -0,0 +1,817 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * DRM driver for PIXPAPER 4.26 monochrome e-ink panel
> + *
> + * Author: LiangCheng Wang <zaq14760@gmail.com>,
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/module.h>
> +#include <linux/spi/spi.h>
> +#include <linux/string.h>
> +
> +#include <drm/clients/drm_client_setup.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_fbdev_shmem.h>
> +#include <drm/drm_framebuffer.h>
> +#include <drm/drm_gem_atomic_helper.h>
> +#include <drm/drm_gem_shmem_helper.h>
> +#include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_probe_helper.h>
> +
> +MODULE_IMPORT_NS("DMA_BUF");
> +
> +/* Panel visible resolution */
> +#define PIXPAPER_WIDTH 800
> +#define PIXPAPER_HEIGHT 480
> +
> +/*
> + * The panel datasheet specifies an active area of 92.8 mm x 55.68 mm.
> + * Round to whole millimeters for drm_display_info.
> + */
> +#define PIXPAPER_WIDTH_MM 93
> +#define PIXPAPER_HEIGHT_MM 56
> +
> +/*
> + * According to the panel datasheet, no RGB-style timing parameters
> + * (porches, sync widths, or a dot clock) are provided. Define a minimal
> + * fixed mode only to satisfy the DRM mode API for this SPI-driven
> + * e-paper panel.
> + */
> +#define PIXPAPER_HSYNC_LEN 1
> +#define PIXPAPER_HFRONT_PORCH 1
> +#define PIXPAPER_HBACK_PORCH 1
> +#define PIXPAPER_VSYNC_LEN 1
> +#define PIXPAPER_VFRONT_PORCH 1
> +#define PIXPAPER_VBACK_PORCH 1
> +#define PIXPAPER_MODE_REFRESH_HZ 1
> +#define PIXPAPER_MODE_CLOCK_KHZ \
> + (((PIXPAPER_WIDTH + PIXPAPER_HFRONT_PORCH + PIXPAPER_HSYNC_LEN + \
> + PIXPAPER_HBACK_PORCH) * \
> + (PIXPAPER_HEIGHT + PIXPAPER_VFRONT_PORCH + PIXPAPER_VSYNC_LEN + \
> + PIXPAPER_VBACK_PORCH) * \
> + PIXPAPER_MODE_REFRESH_HZ) / 1000)
> +
> +#define PIXPAPER_SPI_BITS_PER_WORD 8
> +#define PIXPAPER_SPI_SPEED_DEFAULT 1000000
> +
> +#define PIXPAPER_TX_BUF_SIZE 8
> +
> +#define PIXPAPER_PIXEL_THRESHOLD 128
> +
> +#define PIXPAPER_BUSY_TIMEOUT_MS 10000
> +#define PIXPAPER_BUSY_POLL_INITIAL_US_MIN 1000
> +#define PIXPAPER_BUSY_POLL_INITIAL_US_MAX 1500
> +#define PIXPAPER_BUSY_POLL_US_MIN 100
> +#define PIXPAPER_BUSY_POLL_US_MAX 200
> +
> +#define PIXPAPER_RAM_START_ADDR 0x00
> +
> +#define PIXPAPER_LUMA_R_WEIGHT 299
> +#define PIXPAPER_LUMA_G_WEIGHT 587
> +#define PIXPAPER_LUMA_B_WEIGHT 114
> +#define PIXPAPER_LUMA_DIVISOR 1000
> +#define PIXPAPER_LUMA_ROUNDING_BIAS 500
> +
> +#define PIXPAPER_CMD_DRIVER_OUTPUT_CTRL 0x01
> +#define PIXPAPER_CMD_BOOSTER_SOFT_START_CTRL 0x0C
> +#define PIXPAPER_CMD_TEMP_SENSOR_CONTROL 0x18
> +#define PIXPAPER_CMD_MASTER_ACTIVATION 0x20
> +#define PIXPAPER_CMD_DISPLAY_UPDATE_CTRL2 0x22
> +#define PIXPAPER_CMD_WRITE_RAM_BW 0x24
> +#define PIXPAPER_CMD_BORDER_WAVEFORM_CONTROL 0x3C
> +#define PIXPAPER_CMD_SET_RAM_X_START_END 0x44
> +#define PIXPAPER_CMD_SET_RAM_Y_START_END 0x45
> +#define PIXPAPER_CMD_SET_RAM_X_ADDR_COUNTER 0x4E
> +#define PIXPAPER_CMD_SET_RAM_Y_ADDR_COUNTER 0x4F
> +
> +#define PIXPAPER_DRIVER_OUTPUT_SM BIT(1)
> +
> +#define PIXPAPER_BORDER_WAVEFORM_GS_TRANSITION (0x0 << 6)
> +#define PIXPAPER_BORDER_WAVEFORM_LUT1_SEL 0x1
> +
> +#define PIXPAPER_UPDATE_CTRL2_ENABLE_CLK BIT(7)
> +#define PIXPAPER_UPDATE_CTRL2_ENABLE_ANALOG BIT(6)
> +#define PIXPAPER_UPDATE_CTRL2_LOAD_TEMP BIT(5)
> +#define PIXPAPER_UPDATE_CTRL2_LOAD_LUT BIT(4)
> +#define PIXPAPER_UPDATE_CTRL2_PATTERN_DISPLAY BIT(2)
> +
> +#define PIXPAPER_TEMP_SENSOR_INTERNAL 0x80
> +#define PIXPAPER_SOFTSTART_A 0xAE
> +#define PIXPAPER_SOFTSTART_B 0xC7
> +#define PIXPAPER_SOFTSTART_C 0xC3
> +#define PIXPAPER_SOFTSTART_D 0xC0
> +#define PIXPAPER_SOFTSTART_E 0x80
> +#define PIXPAPER_DRIVER_OUTPUT_GD_SM_TB PIXPAPER_DRIVER_OUTPUT_SM
> +#define PIXPAPER_BORDER_LUT1 \
> + (PIXPAPER_BORDER_WAVEFORM_GS_TRANSITION | \
> + PIXPAPER_BORDER_WAVEFORM_LUT1_SEL)
> +#define PIXPAPER_UPDATE_INITIAL \
> + (PIXPAPER_UPDATE_CTRL2_ENABLE_CLK | \
> + PIXPAPER_UPDATE_CTRL2_ENABLE_ANALOG | \
> + PIXPAPER_UPDATE_CTRL2_LOAD_TEMP | \
> + PIXPAPER_UPDATE_CTRL2_LOAD_LUT | \
> + PIXPAPER_UPDATE_CTRL2_PATTERN_DISPLAY)
> +struct pixpaper_error_ctx {
> + int errno_code;
> +};
> +
> +struct pixpaper_init_seq {
> + u8 cmd;
> + const u8 *data;
> + u8 len;
> +};
> +
> +struct pixpaper_panel {
> + struct drm_device drm;
> + struct drm_plane plane;
> + struct drm_crtc crtc;
> + struct drm_encoder encoder;
> + struct drm_connector connector;
> +
> + struct spi_device *spi;
> + struct gpio_desc *reset;
> + struct gpio_desc *busy;
> + struct gpio_desc *dc;
> +
> + u8 *tx_buf;
> +};
> +
> +static const uint32_t pixpaper_formats[] = {
> + DRM_FORMAT_XRGB8888,
> +};
> +
> +static const u8 pixpaper_init_temp_sensor[] = {
> + PIXPAPER_TEMP_SENSOR_INTERNAL,
> +};
> +
> +static const u8 pixpaper_init_softstart[] = {
> + PIXPAPER_SOFTSTART_A,
> + PIXPAPER_SOFTSTART_B,
> + PIXPAPER_SOFTSTART_C,
> + PIXPAPER_SOFTSTART_D,
> + PIXPAPER_SOFTSTART_E,
> +};
> +
> +static const u8 pixpaper_init_driver_output[] = {
> + (PIXPAPER_HEIGHT - 1) & 0xff,
> + (PIXPAPER_HEIGHT - 1) >> 8,
> + PIXPAPER_DRIVER_OUTPUT_GD_SM_TB,
> +};
> +
> +static const u8 pixpaper_init_border[] = {
> + PIXPAPER_BORDER_LUT1,
> +};
> +
> +static const u8 pixpaper_init_ram_x_window[] = {
> + PIXPAPER_RAM_START_ADDR,
> + PIXPAPER_RAM_START_ADDR,
> + (PIXPAPER_WIDTH - 1) & 0xff,
> + (PIXPAPER_WIDTH - 1) >> 8,
> +};
> +
> +static const u8 pixpaper_init_ram_y_window[] = {
> + PIXPAPER_RAM_START_ADDR,
> + PIXPAPER_RAM_START_ADDR,
> + (PIXPAPER_HEIGHT - 1) & 0xff,
> + (PIXPAPER_HEIGHT - 1) >> 8,
> +};
> +
> +static const u8 pixpaper_init_ram_x_counter[] = {
> + PIXPAPER_RAM_START_ADDR,
> + PIXPAPER_RAM_START_ADDR,
> +};
> +
> +static const u8 pixpaper_init_ram_y_counter[] = {
> + PIXPAPER_RAM_START_ADDR,
> + PIXPAPER_RAM_START_ADDR,
> +};
> +
> +static const struct pixpaper_init_seq pixpaper_init_seqs[] = {
> + {
> + .cmd = PIXPAPER_CMD_TEMP_SENSOR_CONTROL,
> + .data = pixpaper_init_temp_sensor,
> + .len = ARRAY_SIZE(pixpaper_init_temp_sensor),
> + },
> + {
> + .cmd = PIXPAPER_CMD_BOOSTER_SOFT_START_CTRL,
> + .data = pixpaper_init_softstart,
> + .len = ARRAY_SIZE(pixpaper_init_softstart),
> + },
> + {
> + .cmd = PIXPAPER_CMD_DRIVER_OUTPUT_CTRL,
> + .data = pixpaper_init_driver_output,
> + .len = ARRAY_SIZE(pixpaper_init_driver_output),
> + },
> + {
> + .cmd = PIXPAPER_CMD_BORDER_WAVEFORM_CONTROL,
> + .data = pixpaper_init_border,
> + .len = ARRAY_SIZE(pixpaper_init_border),
> + },
> + {
> + .cmd = PIXPAPER_CMD_SET_RAM_X_START_END,
> + .data = pixpaper_init_ram_x_window,
> + .len = ARRAY_SIZE(pixpaper_init_ram_x_window),
> + },
> + {
> + .cmd = PIXPAPER_CMD_SET_RAM_Y_START_END,
> + .data = pixpaper_init_ram_y_window,
> + .len = ARRAY_SIZE(pixpaper_init_ram_y_window),
> + },
> + {
> + .cmd = PIXPAPER_CMD_SET_RAM_X_ADDR_COUNTER,
> + .data = pixpaper_init_ram_x_counter,
> + .len = ARRAY_SIZE(pixpaper_init_ram_x_counter),
> + },
> + {
> + .cmd = PIXPAPER_CMD_SET_RAM_Y_ADDR_COUNTER,
> + .data = pixpaper_init_ram_y_counter,
> + .len = ARRAY_SIZE(pixpaper_init_ram_y_counter),
> + },
> +};
> +
> +static inline struct pixpaper_panel *to_pixpaper_panel(struct drm_device *drm)
> +{
> + return container_of(drm, struct pixpaper_panel, drm);
> +}
> +
> +static void pixpaper_wait_for_panel(struct pixpaper_panel *panel)
> +{
> + unsigned int timeout_ms = PIXPAPER_BUSY_TIMEOUT_MS;
> + unsigned long timeout_jiffies = jiffies + msecs_to_jiffies(timeout_ms);
> +
> + usleep_range(PIXPAPER_BUSY_POLL_INITIAL_US_MIN,
> + PIXPAPER_BUSY_POLL_INITIAL_US_MAX);
> + while (gpiod_get_value_cansleep(panel->busy) != 0) {
> + if (time_after(jiffies, timeout_jiffies)) {
> + /*
> + * Treat a busy timeout as warning-only. Some panels may
> + * keep BUSY asserted longer than expected during
> + * initialization or refresh.
> + */
> + drm_warn(&panel->drm, "Busy wait timed out\n");
> + return;
> + }
> + usleep_range(PIXPAPER_BUSY_POLL_US_MIN,
> + PIXPAPER_BUSY_POLL_US_MAX);
> + }
> +}
> +
> +static void pixpaper_spi_write(struct pixpaper_panel *panel, int dc,
> + const void *buf, size_t len,
> + struct pixpaper_error_ctx *err)
> +{
> + int ret;
> +
> + if (err->errno_code || !len)
> + return;
> +
> + gpiod_set_value_cansleep(panel->dc, dc);
> + usleep_range(1, 5);
> +
> + ret = spi_write(panel->spi, buf, len);
> + if (ret < 0)
> + err->errno_code = ret;
> +}
> +
> +static void pixpaper_send_cmd(struct pixpaper_panel *panel, u8 cmd,
> + struct pixpaper_error_ctx *err)
> +{
> + panel->tx_buf[0] = cmd;
> + pixpaper_spi_write(panel, 0, panel->tx_buf, sizeof(cmd), err);
> +}
> +
> +static void pixpaper_send_data(struct pixpaper_panel *panel, u8 data,
> + struct pixpaper_error_ctx *err)
> +{
> + panel->tx_buf[0] = data;
> + pixpaper_spi_write(panel, 1, panel->tx_buf, sizeof(data), err);
> +}
> +
> +static void pixpaper_reset_ram_counters(struct pixpaper_panel *panel,
> + struct pixpaper_error_ctx *err)
> +{
> + if (err->errno_code)
> + return;
> +
> + pixpaper_send_cmd(panel, PIXPAPER_CMD_SET_RAM_X_ADDR_COUNTER, err);
> + pixpaper_send_data(panel, PIXPAPER_RAM_START_ADDR, err);
> + pixpaper_send_data(panel, PIXPAPER_RAM_START_ADDR, err);
> +
> + pixpaper_send_cmd(panel, PIXPAPER_CMD_SET_RAM_Y_ADDR_COUNTER, err);
> + pixpaper_send_data(panel, PIXPAPER_RAM_START_ADDR, err);
> + pixpaper_send_data(panel, PIXPAPER_RAM_START_ADDR, err);
> +}
> +
> +static void pixpaper_write_ram(struct pixpaper_panel *panel, u8 cmd,
> + const u8 *buf, u32 len,
> + struct pixpaper_error_ctx *err)
> +{
> + if (err->errno_code || !buf || !len)
> + return;
> +
> + pixpaper_reset_ram_counters(panel, err);
> +
> + pixpaper_send_cmd(panel, cmd, err);
> + pixpaper_spi_write(panel, 1, buf, len, err);
> +}
> +
> +static void pixpaper_send_init_seq(struct pixpaper_panel *panel,
> + const struct pixpaper_init_seq *seq,
> + struct pixpaper_error_ctx *err)
> +{
> + if (err->errno_code || !seq->data || !seq->len)
> + return;
> +
> + if (seq->len > PIXPAPER_TX_BUF_SIZE) {
> + err->errno_code = -EINVAL;
> + return;
> + }
> +
> + pixpaper_send_cmd(panel, seq->cmd, err);
> + memcpy(panel->tx_buf, seq->data, seq->len);
> + pixpaper_spi_write(panel, 1, panel->tx_buf, seq->len, err);
> +}
> +
> +static void pixpaper_trigger_update(struct pixpaper_panel *panel,
> + struct pixpaper_error_ctx *err)
> +{
> + if (err->errno_code)
> + return;
> +
> + pixpaper_send_cmd(panel, PIXPAPER_CMD_DISPLAY_UPDATE_CTRL2, err);
> + pixpaper_send_data(panel, PIXPAPER_UPDATE_INITIAL, err);
> + pixpaper_send_cmd(panel, PIXPAPER_CMD_MASTER_ACTIVATION, err);
> + pixpaper_wait_for_panel(panel);
> +}
> +
> +static void pixpaper_xrgb8888_to_bw(const void *src, void *dst, u32 height,
> + u32 width, u32 src_pitch, u32 dst_pitch)
> +{
> + const uint8_t *src_base = src;
> + uint8_t *dst_pixels = dst;
> +
> + if (dst == NULL || src == NULL)
> + return;
> +
> + for (u32 y = 0; y < height; y++) {
> + uint8_t *dst_row = dst_pixels + y * dst_pitch;
> + const __le32 *src_pixels =
> + (const __le32 *)(src_base + y * src_pitch);
> +
> + for (u32 x = 0; x < width; x++) {
> + /*
> + * The panel RAM X direction is reversed relative to DRM
> + * coordinates. Read pixels from right to left so the
> + * displayed image matches the expected orientation on
> + * the panel.
> + */
> + u32 src_x = width - 1 - x;
> + uint8_t r, g, b;
> + u8 bit;
> + u32 bit_pos = x % 8;
> + u32 byte_pos = x / 8;
> + uint32_t gray_val;
> + uint32_t pixel;
> +
> + pixel = le32_to_cpu(src_pixels[src_x]);
> + r = (pixel >> 16) & 0xFF;
> + g = (pixel >> 8) & 0xFF;
> + b = pixel & 0xFF;
> +
> + gray_val = (r * PIXPAPER_LUMA_R_WEIGHT +
> + g * PIXPAPER_LUMA_G_WEIGHT +
> + b * PIXPAPER_LUMA_B_WEIGHT +
> + PIXPAPER_LUMA_ROUNDING_BIAS) /
> + PIXPAPER_LUMA_DIVISOR;
> + bit = gray_val >= PIXPAPER_PIXEL_THRESHOLD;
> +
> + if (bit)
> + dst_row[byte_pos] |= BIT(7 - bit_pos);
> + else
> + dst_row[byte_pos] &= ~BIT(7 - bit_pos);
> + }
> + }
> +}
> +
> +static void *pixpaper_prepare_buffer(const void *vaddr,
> + const struct drm_framebuffer *fb,
> + u32 *dst_pitch,
> + struct pixpaper_error_ctx *err)
> +{
> + void *dst;
> +
> + if (err->errno_code)
> + return NULL;
> +
> + *dst_pitch = DIV_ROUND_UP(fb->width, 8);
> + dst = kzalloc(*dst_pitch * fb->height, GFP_KERNEL);
> + if (!dst) {
> + err->errno_code = -ENOMEM;
> + return NULL;
> + }
> +
> + pixpaper_xrgb8888_to_bw(vaddr, dst, fb->height, fb->width,
> + fb->pitches[0], *dst_pitch);
> +
> + return dst;
> +}
> +
> +static void pixpaper_write_image(struct pixpaper_panel *panel,
> + const u8 *buf, u32 len,
> + struct pixpaper_error_ctx *err)
> +{
> + if (err->errno_code)
> + return;
> +
> + pixpaper_write_ram(panel, PIXPAPER_CMD_WRITE_RAM_BW, buf, len, err);
> +}
> +
> +static int pixpaper_panel_hw_init(struct pixpaper_panel *panel)
> +{
> + struct pixpaper_error_ctx err = { .errno_code = 0 };
> + u8 i;
> +
> + gpiod_set_value_cansleep(panel->reset, 0);
> + msleep(50);
> + gpiod_set_value_cansleep(panel->reset, 1);
> + msleep(50);
> +
> + pixpaper_wait_for_panel(panel);
> +
> + for (i = 0; i < ARRAY_SIZE(pixpaper_init_seqs); i++) {
> + pixpaper_send_init_seq(panel, &pixpaper_init_seqs[i], &err);
> + if (err.errno_code)
> + goto init_fail;
> + }
> +
> + return 0;
> +
> +init_fail:
> + drm_err(&panel->drm, "Hardware initialization failed (err=%d)\n",
> + err.errno_code);
> + return err.errno_code;
> +}
> +
> +static int pixpaper_plane_helper_atomic_check(struct drm_plane *plane,
> + struct drm_atomic_state *state)
> +{
> + struct drm_plane_state *new_plane_state =
> + drm_atomic_get_new_plane_state(state, plane);
> + struct drm_crtc *new_crtc = new_plane_state->crtc;
> + struct drm_crtc_state *new_crtc_state = NULL;
> + int ret;
> +
> + if (new_crtc)
> + new_crtc_state = drm_atomic_get_new_crtc_state(state, new_crtc);
> +
> + ret = drm_atomic_helper_check_plane_state(new_plane_state,
> + new_crtc_state, DRM_PLANE_NO_SCALING,
> + DRM_PLANE_NO_SCALING, false, false);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +static int pixpaper_crtc_helper_atomic_check(struct drm_crtc *crtc,
> + struct drm_atomic_state *state)
> +{
> + struct drm_crtc_state *crtc_state =
> + drm_atomic_get_new_crtc_state(state, crtc);
> +
> + if (!crtc_state->enable)
> + return 0;
> +
> + return drm_atomic_helper_check_crtc_primary_plane(crtc_state);
> +}
> +
> +static void pixpaper_crtc_atomic_enable(struct drm_crtc *crtc,
> + struct drm_atomic_state *state)
> +{
> + struct pixpaper_panel *panel = to_pixpaper_panel(crtc->dev);
> + struct drm_device *drm = &panel->drm;
> + int idx;
> +
> + if (!drm_dev_enter(drm, &idx))
> + return;
> +
> + drm_dev_exit(idx);
> +}
> +
> +static void pixpaper_crtc_atomic_disable(struct drm_crtc *crtc,
> + struct drm_atomic_state *state)
> +{
> + struct pixpaper_panel *panel = to_pixpaper_panel(crtc->dev);
> + struct drm_device *drm = &panel->drm;
> + int idx;
> +
> + if (!drm_dev_enter(drm, &idx))
> + return;
> +
> + drm_dev_exit(idx);
> +}
> +
> +static void pixpaper_plane_atomic_update(struct drm_plane *plane,
> + struct drm_atomic_state *state)
> +{
> + struct drm_plane_state *plane_state =
> + drm_atomic_get_new_plane_state(state, plane);
> + struct drm_shadow_plane_state *shadow_plane_state =
> + to_drm_shadow_plane_state(plane_state);
> + struct pixpaper_panel *panel = to_pixpaper_panel(plane->dev);
> +
> + if (!plane_state->crtc || !plane_state->fb || !plane_state->visible)
> + return;
> +
> + {
> + struct drm_device *drm = &panel->drm;
> + struct drm_framebuffer *fb = plane_state->fb;
> + struct iosys_map map = shadow_plane_state->data[0];
> + const void *vaddr = map.vaddr;
> + int idx;
> + struct pixpaper_error_ctx err = { .errno_code = 0 };
> + uint32_t dst_pitch;
> + void *dst = NULL;
> + u32 dst_len;
> +
> + if (!drm_dev_enter(drm, &idx))
> + return;
> +
> + if (fb->format->format != DRM_FORMAT_XRGB8888) {
> + err.errno_code = -EINVAL;
> + drm_err_once(drm, "Unsupported framebuffer format: 0x%08x\n",
> + fb->format->format);
> + goto update_cleanup;
> + }
> +
> + dst = pixpaper_prepare_buffer(vaddr, fb, &dst_pitch, &err);
> + if (err.errno_code) {
> + drm_err_once(drm, "Failed to allocate temporary buffer\n");
> + goto update_cleanup;
> + }
> +
> + dst_len = dst_pitch * fb->height;
> + pixpaper_write_image(panel, dst, dst_len, &err);
> + if (err.errno_code)
> + goto update_cleanup;
> +
> + pixpaper_trigger_update(panel, &err);
> + if (err.errno_code)
> + goto update_cleanup;
> +update_cleanup:
> + if (err.errno_code && err.errno_code != -ETIMEDOUT)
> + drm_err_once(drm, "Frame update failed: %d\n",
> + err.errno_code);
> +
> + kfree(dst);
> + drm_dev_exit(idx);
> + }
> +}
> +
> +static const struct drm_display_mode pixpaper_mode = {
> + .clock = PIXPAPER_MODE_CLOCK_KHZ,
> + .hdisplay = PIXPAPER_WIDTH,
> + .hsync_start = PIXPAPER_WIDTH + PIXPAPER_HFRONT_PORCH,
> + .hsync_end = PIXPAPER_WIDTH + PIXPAPER_HFRONT_PORCH + PIXPAPER_HSYNC_LEN,
> + .htotal = PIXPAPER_WIDTH + PIXPAPER_HFRONT_PORCH + PIXPAPER_HSYNC_LEN +
> + PIXPAPER_HBACK_PORCH,
> + .vdisplay = PIXPAPER_HEIGHT,
> + .vsync_start = PIXPAPER_HEIGHT + PIXPAPER_VFRONT_PORCH,
> + .vsync_end = PIXPAPER_HEIGHT + PIXPAPER_VFRONT_PORCH + PIXPAPER_VSYNC_LEN,
> + .vtotal = PIXPAPER_HEIGHT + PIXPAPER_VFRONT_PORCH + PIXPAPER_VSYNC_LEN +
> + PIXPAPER_VBACK_PORCH,
> + .width_mm = PIXPAPER_WIDTH_MM,
> + .height_mm = PIXPAPER_HEIGHT_MM,
> + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
> +};
> +
> +static int pixpaper_connector_get_modes(struct drm_connector *connector)
> +{
> + return drm_connector_helper_get_modes_fixed(connector, &pixpaper_mode);
> +}
> +
> +static enum drm_mode_status
> +pixpaper_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
> +{
> + if (mode->hdisplay == PIXPAPER_WIDTH &&
> + mode->vdisplay == PIXPAPER_HEIGHT)
> + return MODE_OK;
> +
> + return MODE_BAD;
> +}
> +
> +static const struct drm_plane_funcs pixpaper_plane_funcs = {
> + .update_plane = drm_atomic_helper_update_plane,
> + .disable_plane = drm_atomic_helper_disable_plane,
> + .destroy = drm_plane_cleanup,
> + DRM_GEM_SHADOW_PLANE_FUNCS,
> +};
> +
> +static const struct drm_plane_helper_funcs pixpaper_plane_helper_funcs = {
> + DRM_GEM_SHADOW_PLANE_HELPER_FUNCS,
> + .atomic_check = pixpaper_plane_helper_atomic_check,
> + .atomic_update = pixpaper_plane_atomic_update,
> +};
> +
> +static const struct drm_crtc_funcs pixpaper_crtc_funcs = {
> + .set_config = drm_atomic_helper_set_config,
> + .page_flip = drm_atomic_helper_page_flip,
> + .reset = drm_atomic_helper_crtc_reset,
> + .destroy = drm_crtc_cleanup,
> + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
> +};
> +
> +static const struct drm_crtc_helper_funcs pixpaper_crtc_helper_funcs = {
> + .mode_valid = pixpaper_mode_valid,
> + .atomic_check = pixpaper_crtc_helper_atomic_check,
> + .atomic_enable = pixpaper_crtc_atomic_enable,
> + .atomic_disable = pixpaper_crtc_atomic_disable,
> +};
> +
> +static const struct drm_encoder_funcs pixpaper_encoder_funcs = {
> + .destroy = drm_encoder_cleanup,
> +};
> +
> +static const struct drm_connector_funcs pixpaper_connector_funcs = {
> + .reset = drm_atomic_helper_connector_reset,
> + .fill_modes = drm_helper_probe_single_connector_modes,
> + .destroy = drm_connector_cleanup,
> + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static const struct drm_connector_helper_funcs pixpaper_connector_helper_funcs = {
> + .get_modes = pixpaper_connector_get_modes,
> +};
> +
> +DEFINE_DRM_GEM_FOPS(pixpaper_fops);
> +
> +static struct drm_driver pixpaper_drm_driver = {
> + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
> + .fops = &pixpaper_fops,
> + .name = "pixpaper-426m",
> + .desc = "DRM driver for PIXPAPER 4.26 monochrome e-ink panel",
> + .major = 1,
> + .minor = 0,
> + DRM_GEM_SHMEM_DRIVER_OPS,
> + DRM_FBDEV_SHMEM_DRIVER_OPS,
> +};
> +
> +static const struct drm_mode_config_funcs pixpaper_mode_config_funcs = {
> + .fb_create = drm_gem_fb_create_with_dirty,
> + .atomic_check = drm_atomic_helper_check,
> + .atomic_commit = drm_atomic_helper_commit,
> +};
> +
> +static int pixpaper_probe(struct spi_device *spi)
> +{
> + struct device *dev = &spi->dev;
> + struct pixpaper_panel *panel;
> + struct drm_device *drm;
> + int ret;
> +
> + panel = devm_drm_dev_alloc(dev, &pixpaper_drm_driver,
> + struct pixpaper_panel, drm);
> + if (IS_ERR(panel))
> + return PTR_ERR(panel);
> +
> + drm = &panel->drm;
> + panel->spi = spi;
> + spi_set_drvdata(spi, panel);
> +
> + panel->tx_buf = devm_kzalloc(dev, PIXPAPER_TX_BUF_SIZE, GFP_KERNEL);
> + if (!panel->tx_buf)
> + return -ENOMEM;
> +
> + ret = drmm_mode_config_init(drm);
> + if (ret)
> + return ret;
> +
> + spi->mode = SPI_MODE_0;
> + spi->bits_per_word = PIXPAPER_SPI_BITS_PER_WORD;
> +
> + if (!spi->max_speed_hz) {
> + drm_warn(drm,
> + "spi-max-frequency not specified in DT, using default %u Hz\n",
> + PIXPAPER_SPI_SPEED_DEFAULT);
> + spi->max_speed_hz = PIXPAPER_SPI_SPEED_DEFAULT;
> + }
> +
> + ret = spi_setup(spi);
> + if (ret < 0) {
> + drm_err(drm, "SPI setup failed: %d\n", ret);
> + return ret;
> + }
> +
> + if (!dev->dma_mask)
> + dev->dma_mask = &dev->coherent_dma_mask;
> + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
> + if (ret) {
> + drm_err(drm, "Failed to set DMA mask: %d\n", ret);
> + return ret;
> + }
> +
> + panel->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
> + if (IS_ERR(panel->reset))
> + return PTR_ERR(panel->reset);
> +
> + panel->busy = devm_gpiod_get(dev, "busy", GPIOD_IN);
> + if (IS_ERR(panel->busy))
> + return PTR_ERR(panel->busy);
> +
> + panel->dc = devm_gpiod_get(dev, "dc", GPIOD_OUT_HIGH);
> + if (IS_ERR(panel->dc))
> + return PTR_ERR(panel->dc);
> +
> + ret = pixpaper_panel_hw_init(panel);
> + if (ret) {
> + drm_err(drm, "Panel hardware initialization failed: %d\n", ret);
> + return ret;
> + }
> +
> + drm->mode_config.funcs = &pixpaper_mode_config_funcs;
> + drm->mode_config.min_width = PIXPAPER_WIDTH;
> + drm->mode_config.max_width = PIXPAPER_WIDTH;
> + drm->mode_config.min_height = PIXPAPER_HEIGHT;
> + drm->mode_config.max_height = PIXPAPER_HEIGHT;
> +
> + ret = drm_universal_plane_init(drm, &panel->plane, 1, &pixpaper_plane_funcs,
> + pixpaper_formats, ARRAY_SIZE(pixpaper_formats), NULL,
> + DRM_PLANE_TYPE_PRIMARY, NULL);
> + if (ret)
> + return ret;
> + drm_plane_helper_add(&panel->plane, &pixpaper_plane_helper_funcs);
> +
> + ret = drm_crtc_init_with_planes(drm, &panel->crtc, &panel->plane, NULL,
> + &pixpaper_crtc_funcs, NULL);
> + if (ret)
> + return ret;
> + drm_crtc_helper_add(&panel->crtc, &pixpaper_crtc_helper_funcs);
> +
> + ret = drm_encoder_init(drm, &panel->encoder, &pixpaper_encoder_funcs,
> + DRM_MODE_ENCODER_NONE, NULL);
> + if (ret)
> + return ret;
> +
> + ret = drm_connector_init(drm, &panel->connector,
> + &pixpaper_connector_funcs,
> + DRM_MODE_CONNECTOR_SPI);
> + if (ret)
> + return ret;
> +
> + drm_connector_helper_add(&panel->connector,
> + &pixpaper_connector_helper_funcs);
> + drm_connector_attach_encoder(&panel->connector, &panel->encoder);
> + panel->encoder.possible_crtcs = drm_crtc_mask(&panel->crtc);
> +
> + drm_mode_config_reset(drm);
> +
> + ret = drm_dev_register(drm, 0);
> + if (ret)
> + return ret;
> +
> + drm_client_setup(drm, NULL);
> +
> + return 0;
> +}
> +
> +static void pixpaper_remove(struct spi_device *spi)
> +{
> + struct pixpaper_panel *panel = spi_get_drvdata(spi);
> +
> + if (!panel)
> + return;
> +
> + drm_dev_unplug(&panel->drm);
> + drm_atomic_helper_shutdown(&panel->drm);
> +}
> +
> +static const struct spi_device_id pixpaper_ids[] = { { "pixpaper-426m", 0 }, {} };
> +MODULE_DEVICE_TABLE(spi, pixpaper_ids);
> +
> +static const struct of_device_id pixpaper_dt_ids[] = {
> + { .compatible = "mayqueen,pixpaper-426m" },
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, pixpaper_dt_ids);
> +
> +static struct spi_driver pixpaper_spi_driver = {
> + .driver = {
> + .name = "pixpaper-426m",
> + .of_match_table = pixpaper_dt_ids,
> + },
> + .id_table = pixpaper_ids,
> + .probe = pixpaper_probe,
> + .remove = pixpaper_remove,
> +};
> +
> +module_spi_driver(pixpaper_spi_driver);
> +
> +MODULE_AUTHOR("LiangCheng Wang");
> +MODULE_DESCRIPTION("DRM SPI driver for PIXPAPER 4.26 monochrome e-ink panel");
> +MODULE_LICENSE("GPL");
>
^ permalink raw reply
* Re: [PATCH V12 7/9] iio: imu: inv_icm42607: Add Accelerometer for icm42607
From: Andy Shevchenko @ 2026-06-16 7:07 UTC (permalink / raw)
To: Chris Morgan
Cc: Chris Morgan, linux-iio, andy, nuno.sa, dlechner, jic23,
jean-baptiste.maneyrol, linux-rockchip, devicetree, heiko,
conor+dt, krzk+dt, robh
In-Reply-To: <PH0PR19MB9973380BF26E3EF16BD2D35ED0A5E62@PH0PR19MB997338.namprd19.prod.outlook.com>
On Mon, Jun 15, 2026 at 11:40:31AM -0500, Chris Morgan wrote:
> On Mon, Jun 15, 2026 at 06:07:15PM +0300, Andy Shevchenko wrote:
> > On Mon, Jun 15, 2026 at 09:51:40AM -0500, Chris Morgan wrote:
> > > On Mon, Jun 15, 2026 at 02:21:05PM +0300, Andy Shevchenko wrote:
> > > > On Thu, Jun 11, 2026 at 03:26:04PM -0500, Chris Morgan wrote:
...
> > > > > + for (i = 5; i < ARRAY_SIZE(inv_icm42607_accel_odr); ++i) {
> > > >
> > > > Why pre-increment? Same for all other cases.
> > >
> > > The register starts at 5 and all values below 5 are invalid. Starting
> > > this increment at 5 ensures we don't expose invalid values to
> > > userspace.
> >
> > It doesn't explain pre-increment. Post-increment should work as is.
>
> The array this references starts at 5, because those correspond to the
> values written to the odr register. That said, I do see a bug because
> the odr register is from highest to smallest and this array is
> backwards in the accel and gyro code. I'll fix that.
Good we even managed to find a bug, but what I initially pointed out is simply
as ++i versus i++. When I see ++i, it rises a flag: "this code is special", but
looking closer, I see nothing special about it.
> > > > > + if (i == odr)
> > > > > + break;
> > > > > + }
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH 6/7] hwmon: adm1275: Support ROHM BD12790
From: sashiko-bot @ 2026-06-16 6:59 UTC (permalink / raw)
To: Matti Vaittinen; +Cc: conor+dt, devicetree, linux-hwmon, robh
In-Reply-To: <8ca875d21f2d9a4d53a87b47a5e6efab48266178.1781591132.git.mazziesaccount@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] The driver compares the device-returned PMBUS_MFR_MODEL string using strncasecmp without verifying the returned length, leading to uninitialized stack memory reads.
- [High] Multiplication of power and current coefficients by `shunt` overflows a 32-bit unsigned integer for larger shunt resistors.
--
commit 8dff4bf5659b589eabe7fc876665de9339faae1f
Author: Matti Vaittinen <mazziesaccount@gmail.com>
hwmon: adm1275: Support ROHM BD12790
Add support for ROHM BD12790 hot-swap controller which is largely
similar to Analog Devices adm1272.
> diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c
> --- a/drivers/hwmon/pmbus/adm1275.c
> +++ b/drivers/hwmon/pmbus/adm1275.c
[ ... ]
> @@ -581,7 +606,8 @@ static int adm1275_probe(struct i2c_client *client)
> if (mid->driver_data == adm1272 || mid->driver_data == adm1273 ||
> mid->driver_data == adm1278 || mid->driver_data == adm1281 ||
> mid->driver_data == adm1293 || mid->driver_data == adm1294 ||
> - mid->driver_data == bd12780 || mid->driver_data == sq24905c)
> + mid->driver_data == bd12780 || mid->driver_data == bd12790 ||
> + mid->driver_data == sq24905c)
> config_read_fn = i2c_smbus_read_word_data;
> else
[Severity: High]
This is a pre-existing issue, but I noticed that just before this block in
adm1275_probe(), there might be an out-of-bounds stack read:
ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer);
if (ret < 0) { ... }
for (mid = adm1275_id; mid->name[0]; mid++) {
if (!strncasecmp(mid->name, block_buffer, strlen(mid->name)))
break;
}
Does this code read uninitialized stack memory if the device returns a
string that matches a valid model name prefix, but is shorter than the valid
model name length?
Since i2c_smbus_read_block_data() reads up to 32 bytes into the uninitialized
stack array block_buffer without appending a null terminator, strncasecmp()
could read past the valid bytes returned in ret.
For example, if the device returns a shorter string like "adm12", checking
it against "adm1275" up to the length of "adm1275" will continue reading
into uninitialized stack bounds.
[ ... ]
> @@ -685,12 +722,23 @@ static int adm1275_probe(struct i2c_client *client)
> break;
> case adm1272:
> case adm1273:
> + case bd12790:
> + {
[ ... ]
[Severity: High]
This isn't a bug introduced by this patch, but looking further down in
adm1275_probe(), could the coefficient calculations for current and power
overflow a 32-bit unsigned integer?
info->m[PSC_CURRENT_OUT] =
coefficients[cindex].m * shunt / 1000;
info->m[PSC_POWER] =
coefficients[pindex].m * shunt / 1000;
If a larger shunt resistor value is provided via device tree
(e.g., 200,000 micro-ohms to measure lower currents), multiplying this by
larger coefficients like 30631 (used for ADM1293) evaluates to 6,126,200,000.
Since shunt is a u32 and the coefficient promotes to int, this operation uses
32-bit unsigned arithmetic.
Does this wrap-around the maximum 32-bit value of 4,294,967,295 and result
in drastically incorrect telemetry scaling factors?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1781591132.git.mazziesaccount@gmail.com?part=6
^ permalink raw reply
* Re: [PATCH 7/7] hwmon: adm1275: Support module auto-loading
From: sashiko-bot @ 2026-06-16 6:59 UTC (permalink / raw)
To: Matti Vaittinen; +Cc: robh, linux-hwmon, conor+dt, devicetree
In-Reply-To: <634e76680ed93e58ebeb35db080138b791cb6c27.1781591132.git.mazziesaccount@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Low] The commit message incorrectly references `spi_device_id` instead of `i2c_device_id`.
Pre-existing issues:
- [High] The newly added `adm1275_of_match` table is incomplete, leaving module auto-loading broken for the majority of supported devices.
--
commit ed700404448bb29c6f451edcac6e84d5a0f95644
Author: Matti Vaittinen <mazziesaccount@gmail.com>
hwmon: adm1275: Support module auto-loading
This commit adds an of_device_id table to the adm1275 driver to enable
automatic module loading when devices are parsed from the device tree
at boot.
> hwmon: adm1275: Support module auto-loading
>
> Populating the spi_device_id -table is not enough to make the
[Severity: Low]
This isn't a bug, but there's a typo in the commit message. The driver
is an I2C driver and uses an i2c_device_id table, not an spi_device_id
table.
[ ... ]
> diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c
> index 9e21dd4083e94..c27bb0e493547 100644
> --- a/drivers/hwmon/pmbus/adm1275.c
> +++ b/drivers/hwmon/pmbus/adm1275.c
> @@ -927,9 +927,17 @@ static int adm1275_probe(struct i2c_client *client)
> return pmbus_do_probe(client, info);
> }
>
> +static const struct of_device_id adm1275_of_match[] = {
> + { .compatible = "rohm,bd12780", },
> + { .compatible = "rohm,bd12790", },
[Severity: High]
This is a pre-existing issue, but the newly added adm1275_of_match
table appears to be incomplete.
Since udev relies on the of_device_id table to export the correct
MODALIAS for auto-loading, wouldn't omitting the other devices
supported by this driver (such as adm1075, adm1275, adm1276) leave
auto-loading broken for those devices on DT systems?
Should we include compatible strings for the rest of the supported
hardware here to ensure they also benefit from module auto-loading?
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, adm1275_of_match);
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1781591132.git.mazziesaccount@gmail.com?part=7
^ permalink raw reply
* Re: [PATCH 1/7] dt-bindings: adm1275: ROHM BD12780 hot-swap controller
From: sashiko-bot @ 2026-06-16 6:55 UTC (permalink / raw)
To: Matti Vaittinen; +Cc: robh, linux-hwmon, conor+dt, devicetree
In-Reply-To: <d63c4df5e9df845bc4f94b4abdcd068a23929974.1781591132.git.mazziesaccount@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Invalid use of the `items` object for the single-string compatible enum, allowing arrays of multiple arbitrary compatible strings.
--
commit 7132123aae357451c411a0cee036262cc9f62a45
Author: Matti Vaittinen <mazziesaccount@gmail.com>
dt-bindings: adm1275: ROHM BD12780 hot-swap controller
This commit adds support for the ROHM BD12780 and BD12780A hot-swap
controllers to the adm1275 device tree bindings. It introduces new
compatible strings, requiring BD12780A to use BD12780 as a fallback.
> diff --git a/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml b/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml
> index d6a7517f2a50c..bc67510ef3ab1 100644
> --- a/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml
> +++ b/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml
[ ... ]
> @@ -25,19 +25,35 @@ description: |
> properties:
> compatible:
> - enum:
> - - adi,adm1075
> - - adi,adm1272
> - - adi,adm1273
> - - adi,adm1275
> - - adi,adm1276
> - - adi,adm1278
> - - adi,adm1281
> - - adi,adm1293
> - - adi,adm1294
> - - silergy,mc09c
> + oneOf:
> + - items:
> + enum:
> + - adi,adm1075
> + - adi,adm1272
> + - adi,adm1273
[Severity: Low]
Does wrapping this enum inside an items list inadvertently allow multiple
exclusive strings instead of restricting it to exactly one?
In dt-schema, applying an items object directly to a string-array property
like compatible defines the schema for all elements of the array, but does
not impose bounds on the array length. This might allow a device tree to
define a compatible property with multiple strings from this list, such as:
compatible = "adi,adm1075", "adi,adm1272";
To enforce exactly one string from the list, should the items wrapper be
removed so that the enum resides directly under oneOf?
> + - adi,adm1275
> + - adi,adm1276
> + - adi,adm1278
> + - adi,adm1281
> + - adi,adm1293
> + - adi,adm1294
> + - rohm,bd12780
> + - silergy,mc09c
> +
> +# Require BD12780 as a fall-back for BD12780A.
> + - items:
> + - const: rohm,bd12780a
> + - const: rohm,bd12780
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1781591132.git.mazziesaccount@gmail.com?part=1
^ permalink raw reply
* Re: [PATCH 5/7] doc: adm1275: Add ROHM BD12790
From: sashiko-bot @ 2026-06-16 6:54 UTC (permalink / raw)
To: Matti Vaittinen; +Cc: robh, conor+dt, devicetree, linux-hwmon
In-Reply-To: <7bfa22c245b81c8027736019aff717cc1f5039c8.1781591132.git.mazziesaccount@gmail.com>
> From: Matti Vaittinen <mazziesaccount@gmail.com>
>
> Add the ROHM BD12790 to the list of the ICs supported by the adm1275
> driver.
>
> Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1781591132.git.mazziesaccount@gmail.com?part=5
^ permalink raw reply
* Re: [PATCH 3/7] hwmon: adm1275: Support ROHM BD12780
From: sashiko-bot @ 2026-06-16 6:54 UTC (permalink / raw)
To: Matti Vaittinen; +Cc: conor+dt, robh, linux-hwmon, devicetree
In-Reply-To: <c92f1356fbf967dee3130f2eb0da08eb84800d47.1781591132.git.mazziesaccount@gmail.com>
> From: Matti Vaittinen <mazziesaccount@gmail.com>
>
> ROHM BD12780 and BD12780A are hot-swap controllers. They are largely
> similar to Analog Devices ADM1278. Besides the ID registers and some
> added functionality, the BD12780 and BD12780A mark PMON_CONFIG bits
> [15:14] as reserved. Hence TSFILT setting must be omitted on these ICs.
>
> The BD12780 has 3 pins usable for configuring the I2C address. The
> BD12780A lists the ADDR3-pin as "not connect".
>
> Support ROHM BD12780 and BD12780A controllers.
>
> Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1781591132.git.mazziesaccount@gmail.com?part=3
^ permalink raw reply
* Re: [PATCH 4/7] dt-bindings: adm1275: ROHM BD12790 hot-swap controller
From: sashiko-bot @ 2026-06-16 6:53 UTC (permalink / raw)
To: Matti Vaittinen; +Cc: robh, devicetree, conor+dt, linux-hwmon
In-Reply-To: <ae80a037fa3b03ef5cfb446bf3e9c44efd4f04ec.1781591132.git.mazziesaccount@gmail.com>
> From: Matti Vaittinen <mazziesaccount@gmail.com>
>
> Support ROHM BD12790 hot-swap controller which is largely compatible
> with the Analog Devices adm1272.
>
> Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1781591132.git.mazziesaccount@gmail.com?part=4
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox