sensor: rm3100: Add streaming mode

Compatible trigger: DRDY.

Tested with Sensor Shell commands.

Signed-off-by: Luis Ubieda <luisf@croxel.com>
This commit is contained in:
Luis Ubieda 2025-04-29 00:01:03 -04:00 committed by Benjamin Cabé
commit a13be2f320
8 changed files with 336 additions and 2 deletions

View file

@ -7,3 +7,6 @@ zephyr_library_sources(
rm3100.c rm3100.c
rm3100_decoder.c rm3100_decoder.c
) )
zephyr_library_sources_ifdef(CONFIG_RM3100_STREAM
rm3100_stream.c
)

View file

@ -11,3 +11,10 @@ config RM3100
select SENSOR_ASYNC_API select SENSOR_ASYNC_API
help help
Enable driver for PNI RM3100 high-accuracy 3-axis magnetometer. Enable driver for PNI RM3100 high-accuracy 3-axis magnetometer.
config RM3100_STREAM
bool "RM3100 Streaming Mode"
depends on $(dt_compat_any_has_prop,$(DT_COMPAT_PNI_RM3100),int-gpios)
help
Enable streaming mode for the RM3100 sensor.
This mode allows for continuous data output at a specified rate.

View file

@ -20,6 +20,7 @@
#include "rm3100_reg.h" #include "rm3100_reg.h"
#include "rm3100_bus.h" #include "rm3100_bus.h"
#include "rm3100_decoder.h" #include "rm3100_decoder.h"
#include "rm3100_stream.h"
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(RM3100, CONFIG_SENSOR_LOG_LEVEL); LOG_MODULE_REGISTER(RM3100, CONFIG_SENSOR_LOG_LEVEL);
@ -118,6 +119,8 @@ static void rm3100_submit(const struct device *dev, struct rtio_iodev_sqe *iodev
if (!cfg->is_streaming) { if (!cfg->is_streaming) {
rm3100_submit_one_shot(dev, iodev_sqe); rm3100_submit_one_shot(dev, iodev_sqe);
} else if (IS_ENABLED(CONFIG_RM3100_STREAM)) {
rm3100_stream_submit(dev, iodev_sqe);
} else { } else {
LOG_ERR("Streaming not supported"); LOG_ERR("Streaming not supported");
rtio_iodev_sqe_err(iodev_sqe, -ENOTSUP); rtio_iodev_sqe_err(iodev_sqe, -ENOTSUP);
@ -149,6 +152,14 @@ static int rm3100_init(const struct device *dev)
} }
LOG_DBG("RM3100 chip ID confirmed: 0x%02x", val); LOG_DBG("RM3100 chip ID confirmed: 0x%02x", val);
if (IS_ENABLED(CONFIG_RM3100_STREAM)) {
err = rm3100_stream_init(dev);
if (err < 0) {
LOG_ERR("Failed to set up stream config: %d", err);
return err;
}
}
uint16_t cycle_count[] = { uint16_t cycle_count[] = {
sys_be16_to_cpu(RM3100_CYCLE_COUNT_DEFAULT), sys_be16_to_cpu(RM3100_CYCLE_COUNT_DEFAULT),
sys_be16_to_cpu(RM3100_CYCLE_COUNT_DEFAULT), sys_be16_to_cpu(RM3100_CYCLE_COUNT_DEFAULT),
@ -196,7 +207,9 @@ static int rm3100_init(const struct device *dev)
RTIO_DEFINE(rm3100_rtio_ctx_##inst, 8, 8); \ RTIO_DEFINE(rm3100_rtio_ctx_##inst, 8, 8); \
I2C_DT_IODEV_DEFINE(rm3100_bus_##inst, DT_DRV_INST(inst)); \ I2C_DT_IODEV_DEFINE(rm3100_bus_##inst, DT_DRV_INST(inst)); \
\ \
static const struct rm3100_config rm3100_cfg_##inst; \ static const struct rm3100_config rm3100_cfg_##inst = { \
.int_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, int_gpios, {0}), \
}; \
\ \
static struct rm3100_data rm3100_data_##inst = { \ static struct rm3100_data rm3100_data_##inst = { \
.rtio = { \ .rtio = { \

View file

@ -9,6 +9,7 @@
#define ZEPHYR_DRIVERS_SENSOR_PNI_RM3100_H_ #define ZEPHYR_DRIVERS_SENSOR_PNI_RM3100_H_
#include <zephyr/drivers/sensor.h> #include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/rtio/rtio.h> #include <zephyr/rtio/rtio.h>
#include "rm3100_reg.h" #include "rm3100_reg.h"
@ -21,6 +22,10 @@ struct rm3100_encoded_data {
uint64_t timestamp; uint64_t timestamp;
uint8_t channels : 3; uint8_t channels : 3;
uint16_t cycle_count; uint16_t cycle_count;
uint8_t status;
struct {
bool drdy : 1;
} events;
} header; } header;
union { union {
uint8_t payload[RM3100_TOTAL_BYTES]; uint8_t payload[RM3100_TOTAL_BYTES];
@ -33,7 +38,21 @@ struct rm3100_encoded_data {
}; };
struct rm3100_config { struct rm3100_config {
uint32_t unused; /* Will be expanded with stremaing-mode to hold int-gpios */ struct gpio_dt_spec int_gpio;
};
struct rm3100_stream {
struct gpio_callback cb;
const struct device *dev;
struct rtio_iodev_sqe *iodev_sqe;
struct {
struct {
bool drdy : 1;
} enabled;
struct {
enum sensor_stream_data_opt drdy;
} opt;
} settings;
}; };
struct rm3100_data { struct rm3100_data {
@ -45,6 +64,9 @@ struct rm3100_data {
struct { struct {
uint8_t odr; uint8_t odr;
} settings; } settings;
#if defined(CONFIG_RM3100_STREAM)
struct rm3100_stream stream;
#endif /* CONFIG_RM3100_STREAM */
}; };
#endif /* ZEPHYR_DRIVERS_SENSOR_PNI_RM3100_H_ */ #endif /* ZEPHYR_DRIVERS_SENSOR_PNI_RM3100_H_ */

View file

@ -8,6 +8,9 @@
#ifndef ZEPHYR_DRIVERS_SENSOR_PNI_RM3100_REG_H_ #ifndef ZEPHYR_DRIVERS_SENSOR_PNI_RM3100_REG_H_
#define ZEPHYR_DRIVERS_SENSOR_PNI_RM3100_REG_H_ #define ZEPHYR_DRIVERS_SENSOR_PNI_RM3100_REG_H_
/* Address value has a read bit */
#define REG_READ_BIT BIT(7)
/* RM3100 register addresses */ /* RM3100 register addresses */
#define RM3100_REG_CMM 0x01 /* Continuous measurement mode */ #define RM3100_REG_CMM 0x01 /* Continuous measurement mode */
#define RM3100_REG_CCX_MSB 0x04 /* Cycle Count X LSB */ #define RM3100_REG_CCX_MSB 0x04 /* Cycle Count X LSB */

View file

@ -0,0 +1,261 @@
/*
* Copyright (c) 2025 Croxel, Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/sensor_clock.h>
#include <zephyr/rtio/rtio.h>
#include <zephyr/sys/check.h>
#include "rm3100_stream.h"
#include "rm3100_decoder.h"
#include "rm3100.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(RM3100_STREAM, CONFIG_SENSOR_LOG_LEVEL);
static void rm3100_complete_result(struct rtio *ctx, const struct rtio_sqe *sqe, void *arg)
{
const struct device *dev = (const struct device *)arg;
struct rm3100_data *data = dev->data;
struct rtio_iodev_sqe *iodev_sqe = data->stream.iodev_sqe;
struct rtio_cqe *cqe;
int err = 0;
struct rm3100_encoded_data *edata = sqe->userdata;
edata->header.events.drdy = ((edata->header.status != 0) &&
data->stream.settings.enabled.drdy);
edata->header.channels = 0;
if (!edata->header.events.drdy) {
LOG_ERR("Status register does not have DRDY bit set: 0x%02x",
edata->header.status);
} else if (data->stream.settings.opt.drdy == SENSOR_STREAM_DATA_INCLUDE) {
edata->header.channels |= rm3100_encode_channel(SENSOR_CHAN_MAGN_XYZ);
}
do {
cqe = rtio_cqe_consume(ctx);
if (cqe != NULL) {
err = cqe->result;
rtio_cqe_release(ctx, cqe);
}
} while (cqe != NULL);
if (err) {
rtio_iodev_sqe_err(iodev_sqe, err);
} else {
rtio_iodev_sqe_ok(iodev_sqe, 0);
}
LOG_DBG("Streaming read-out complete");
}
static void rm3100_stream_get_data(const struct device *dev)
{
struct rm3100_data *data = dev->data;
uint64_t cycles;
int err;
CHECKIF(!data->stream.iodev_sqe) {
LOG_WRN("No RTIO submission with an INT GPIO event");
return;
}
struct rtio_iodev_sqe *iodev_sqe = data->stream.iodev_sqe;
uint8_t *buf;
uint32_t buf_len;
uint32_t min_buf_len = sizeof(struct rm3100_encoded_data);
struct rm3100_encoded_data *edata;
err = rtio_sqe_rx_buf(iodev_sqe, min_buf_len, min_buf_len, &buf, &buf_len);
if (err) {
LOG_ERR("Failed to get a read buffer of size %u bytes", min_buf_len);
data->stream.iodev_sqe = NULL;
rtio_iodev_sqe_err(iodev_sqe, err);
return;
}
edata = (struct rm3100_encoded_data *)buf;
err = sensor_clock_get_cycles(&cycles);
CHECKIF(err) {
LOG_ERR("Failed to get timestamp: %d", err);
data->stream.iodev_sqe = NULL;
rtio_iodev_sqe_err(iodev_sqe, err);
return;
}
edata->header.timestamp = sensor_clock_cycles_to_ns(cycles);
struct rtio_sqe *status_wr_sqe = rtio_sqe_acquire(data->rtio.ctx);
struct rtio_sqe *status_rd_sqe = rtio_sqe_acquire(data->rtio.ctx);
struct rtio_sqe *write_sqe = rtio_sqe_acquire(data->rtio.ctx);
struct rtio_sqe *read_sqe = rtio_sqe_acquire(data->rtio.ctx);
struct rtio_sqe *complete_sqe = rtio_sqe_acquire(data->rtio.ctx);
if (!write_sqe || !read_sqe || !complete_sqe) {
LOG_ERR("Failed to acquire RTIO SQEs");
rtio_sqe_drop_all(data->rtio.ctx);
data->stream.iodev_sqe = NULL;
rtio_iodev_sqe_err(iodev_sqe, -ENOMEM);
return;
}
uint8_t val;
val = RM3100_REG_STATUS;
rtio_sqe_prep_tiny_write(status_wr_sqe,
data->rtio.iodev,
RTIO_PRIO_HIGH,
&val,
1,
NULL);
status_wr_sqe->flags |= RTIO_SQE_TRANSACTION;
rtio_sqe_prep_read(status_rd_sqe,
data->rtio.iodev,
RTIO_PRIO_HIGH,
&edata->header.status,
sizeof(edata->header.status),
NULL);
status_rd_sqe->iodev_flags |= RTIO_IODEV_I2C_STOP | RTIO_IODEV_I2C_RESTART;
status_rd_sqe->flags |= RTIO_SQE_CHAINED;
val = RM3100_REG_MX;
rtio_sqe_prep_tiny_write(write_sqe,
data->rtio.iodev,
RTIO_PRIO_HIGH,
&val,
1,
NULL);
write_sqe->flags |= RTIO_SQE_TRANSACTION;
rtio_sqe_prep_read(read_sqe,
data->rtio.iodev,
RTIO_PRIO_HIGH,
edata->payload,
sizeof(edata->payload),
NULL);
read_sqe->iodev_flags |= RTIO_IODEV_I2C_STOP | RTIO_IODEV_I2C_RESTART;
read_sqe->flags |= RTIO_SQE_CHAINED;
rtio_sqe_prep_callback_no_cqe(complete_sqe,
rm3100_complete_result,
(void *)dev,
buf);
rtio_submit(data->rtio.ctx, 0);
}
static void rm3100_gpio_callback(const struct device *gpio_dev,
struct gpio_callback *cb,
uint32_t pins)
{
struct rm3100_stream *stream = CONTAINER_OF(cb,
struct rm3100_stream,
cb);
const struct device *dev = stream->dev;
const struct rm3100_config *cfg = dev->config;
int err;
/* Disable interrupts */
err = gpio_pin_interrupt_configure_dt(&cfg->int_gpio,
GPIO_INT_MODE_DISABLED);
if (err) {
LOG_ERR("Failed to disable interrupt");
return;
}
rm3100_stream_get_data(dev);
}
static inline bool settings_changed(const struct rm3100_stream *a,
const struct rm3100_stream *b)
{
return (a->settings.enabled.drdy != b->settings.enabled.drdy) ||
(a->settings.opt.drdy != b->settings.opt.drdy);
}
void rm3100_stream_submit(const struct device *dev,
struct rtio_iodev_sqe *iodev_sqe)
{
const struct sensor_read_config *read_config = iodev_sqe->sqe.iodev->data;
struct rm3100_data *data = dev->data;
const struct rm3100_config *cfg = dev->config;
int err;
if ((read_config->count != 1) ||
(read_config->triggers[0].trigger != SENSOR_TRIG_DATA_READY)) {
LOG_ERR("Only SENSOR_TRIG_DATA_READY is supported");
rtio_iodev_sqe_err(iodev_sqe, -ENOTSUP);
return;
}
/* Store context for next submission (handled within callbacks) */
data->stream.iodev_sqe = iodev_sqe;
data->stream.settings.enabled.drdy = true;
data->stream.settings.opt.drdy = read_config->triggers[0].opt;
err = gpio_pin_interrupt_configure_dt(&cfg->int_gpio,
GPIO_INT_LEVEL_ACTIVE);
if (err) {
LOG_ERR("Failed to enable interrupts");
data->stream.iodev_sqe = NULL;
rtio_iodev_sqe_err(iodev_sqe, err);
return;
}
}
int rm3100_stream_init(const struct device *dev)
{
const struct rm3100_config *cfg = dev->config;
struct rm3100_data *data = dev->data;
int err;
/** Needed to get back the device handle from the callback context */
data->stream.dev = dev;
if (!cfg->int_gpio.port) {
LOG_ERR("Interrupt GPIO not supplied");
return -ENODEV;
}
if (!gpio_is_ready_dt(&cfg->int_gpio)) {
LOG_ERR("Interrupt GPIO not ready");
return -ENODEV;
}
err = gpio_pin_configure_dt(&cfg->int_gpio, GPIO_INPUT);
if (err) {
LOG_ERR("Failed to configure interrupt GPIO");
return -EIO;
}
gpio_init_callback(&data->stream.cb,
rm3100_gpio_callback,
BIT(cfg->int_gpio.pin));
err = gpio_add_callback(cfg->int_gpio.port, &data->stream.cb);
if (err) {
LOG_ERR("Failed to add interrupt callback");
return -EIO;
}
err = gpio_pin_interrupt_configure_dt(&cfg->int_gpio,
GPIO_INT_MODE_DISABLED);
if (err) {
LOG_ERR("Failed to configure interrupt as disabled");
return -EIO;
}
return 0;
}

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2025 Croxel Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_SENSOR_RM3100_STREAM_H_
#define ZEPHYR_DRIVERS_SENSOR_RM3100_STREAM_H_
#include "rm3100.h"
int rm3100_stream_init(const struct device *dev);
void rm3100_stream_submit(const struct device *dev,
struct rtio_iodev_sqe *iodev_sqe);
#endif /* ZEPHYR_DRIVERS_SENSOR_RM3100_STREAM_H_ */

View file

@ -26,6 +26,13 @@ compatible: "pni,rm3100"
include: [sensor-device.yaml, i2c-device.yaml] include: [sensor-device.yaml, i2c-device.yaml]
properties: properties:
int-gpios:
type: phandle-array
description: |
The INT signal default configuration is active-high. The
property value should ensure the flags properly describe the
signal that is presented to the driver.
odr: odr:
type: int type: int
default: 0x96 default: 0x96