sensor: rm3100: Basic functionality

This patch introduces rm3100 magnetometer sensor, with basic
support (only read-decode).

This driver has bus support for I2C.

Signed-off-by: Luis Ubieda <luisf@croxel.com>
This commit is contained in:
Luis Ubieda 2025-04-23 21:52:05 -04:00 committed by Benjamin Cabé
commit c1f3e2c712
13 changed files with 640 additions and 0 deletions

View file

@ -22,6 +22,7 @@ add_subdirectory(nordic)
add_subdirectory(nuvoton)
add_subdirectory(nxp)
add_subdirectory(pixart)
add_subdirectory(pni)
add_subdirectory(realtek)
add_subdirectory(renesas)
add_subdirectory(rohm)

View file

@ -108,6 +108,7 @@ source "drivers/sensor/nordic/Kconfig"
source "drivers/sensor/nuvoton/Kconfig"
source "drivers/sensor/nxp/Kconfig"
source "drivers/sensor/pixart/Kconfig"
source "drivers/sensor/pni/Kconfig"
source "drivers/sensor/realtek/Kconfig"
source "drivers/sensor/renesas/Kconfig"
source "drivers/sensor/rohm/Kconfig"

View file

@ -0,0 +1,7 @@
# Copyright (c) 2025 Croxel, Inc.
# Copyright (c) 2025 CogniPilot Foundation
# SPDX-License-Identifier: Apache-2.0
# zephyr-keep-sorted-start
add_subdirectory_ifdef(CONFIG_RM3100 rm3100)
# zephyr-keep-sorted-stop

View file

@ -0,0 +1,7 @@
# Copyright (c) 2025 Croxel, Inc.
# Copyright (c) 2025 CogniPilot Foundation
# SPDX-License-Identifier: Apache-2.0
# zephyr-keep-sorted-start
source "drivers/sensor/pni/rm3100/Kconfig"
# zephyr-keep-sorted-stop

View file

@ -0,0 +1,9 @@
# Copyright (c) 2025 Croxel, Inc.
# Copyright (c) 2025 CogniPilot Foundation
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(
rm3100.c
rm3100_decoder.c
)

View file

@ -0,0 +1,13 @@
# Copyright (c) 2025 Croxel, Inc.
# Copyright (c) 2025 CogniPilot Foundation
# SPDX-License-Identifier: Apache-2.0
config RM3100
bool "RM3100 3-Axis Magnetometer"
default y
depends on DT_HAS_PNI_RM3100_ENABLED
select I2C
select I2C_RTIO
select SENSOR_ASYNC_API
help
Enable driver for PNI RM3100 high-accuracy 3-axis magnetometer.

View file

@ -0,0 +1,181 @@
/*
* Copyright (c) 2025 Croxel, Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT pni_rm3100
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/rtio/rtio.h>
#include <zephyr/rtio/work.h>
#include <zephyr/sys/check.h>
#include "rm3100.h"
#include "rm3100_reg.h"
#include "rm3100_bus.h"
#include "rm3100_decoder.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(RM3100, CONFIG_SENSOR_LOG_LEVEL);
static void rm3100_complete_result(struct rtio *ctx, const struct rtio_sqe *sqe, void *arg)
{
struct rtio_iodev_sqe *iodev_sqe = (struct rtio_iodev_sqe *)sqe->userdata;
struct rtio_cqe *cqe;
int err = 0;
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("One-shot fetch completed");
}
static void rm3100_submit_one_shot(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe)
{
const struct sensor_read_config *cfg = iodev_sqe->sqe.iodev->data;
const struct sensor_chan_spec *const channels = cfg->channels;
const size_t num_channels = cfg->count;
uint32_t min_buf_len = sizeof(struct rm3100_encoded_data);
int err;
uint8_t *buf;
uint32_t buf_len;
struct rm3100_encoded_data *edata;
struct rm3100_data *data = dev->data;
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);
rtio_iodev_sqe_err(iodev_sqe, err);
return;
}
edata = (struct rm3100_encoded_data *)buf;
err = rm3100_encode(dev, channels, num_channels, buf);
if (err != 0) {
LOG_ERR("Failed to encode sensor data");
rtio_iodev_sqe_err(iodev_sqe, err);
return;
}
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_iodev_sqe_err(iodev_sqe, -ENOMEM);
return;
}
uint8_t 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,
iodev_sqe);
rtio_submit(data->rtio.ctx, 0);
}
static void rm3100_submit(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe)
{
const struct sensor_read_config *cfg = iodev_sqe->sqe.iodev->data;
if (!cfg->is_streaming) {
rm3100_submit_one_shot(dev, iodev_sqe);
} else {
LOG_ERR("Streaming not supported");
rtio_iodev_sqe_err(iodev_sqe, -ENOTSUP);
}
}
/* This will be implemented later */
static DEVICE_API(sensor, rm3100_driver_api) = {
/* API functions will be added here */
.submit = rm3100_submit,
.get_decoder = rm3100_get_decoder,
};
static int rm3100_init(const struct device *dev)
{
uint8_t val;
int err;
/* Check device ID to make sure we can talk to the sensor */
err = rm3100_bus_read(dev, RM3100_REG_REVID, &val, 1);
if (err < 0) {
LOG_ERR("Failed to read chip ID");
return err;
} else if (val != RM3100_REVID_VALUE) {
LOG_ERR("Invalid chip ID: 0x%02x, expected 0x%02x",
val, RM3100_REVID_VALUE);
return -ENODEV;
}
LOG_DBG("RM3100 chip ID confirmed: 0x%02x", val);
/** Enable Continuous measurement on all axis */
val = RM3100_CMM_ALL_AXIS;
err = rm3100_bus_write(dev, RM3100_REG_CMM, &val, 1);
if (err < 0) {
LOG_ERR("Failed to set sensor in Continuous Measurement Mode: %d", err);
return err;
}
return 0;
}
#define RM3100_DEFINE(inst) \
\
RTIO_DEFINE(rm3100_rtio_ctx_##inst, 8, 8); \
I2C_DT_IODEV_DEFINE(rm3100_bus_##inst, DT_DRV_INST(inst)); \
\
static const struct rm3100_config rm3100_cfg_##inst; \
\
static struct rm3100_data rm3100_data_##inst = { \
.rtio = { \
.iodev = &rm3100_bus_##inst, \
.ctx = &rm3100_rtio_ctx_##inst, \
}, \
}; \
\
SENSOR_DEVICE_DT_INST_DEFINE(inst, rm3100_init, NULL, \
&rm3100_data_##inst, \
&rm3100_cfg_##inst, \
POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, \
&rm3100_driver_api);
DT_INST_FOREACH_STATUS_OKAY(RM3100_DEFINE)

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2025 Croxel, Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_SENSOR_PNI_RM3100_H_
#define ZEPHYR_DRIVERS_SENSOR_PNI_RM3100_H_
#include <zephyr/drivers/sensor.h>
#include <zephyr/rtio/rtio.h>
#include "rm3100_reg.h"
/* RM3100 produces 3 bytes (24-bit) of data per axis */
#define RM3100_BYTES_PER_AXIS 3
#define RM3100_TOTAL_BYTES (RM3100_BYTES_PER_AXIS * 3)
struct rm3100_encoded_data {
struct {
uint64_t timestamp;
uint8_t channels : 3;
} header;
union {
uint8_t payload[RM3100_TOTAL_BYTES];
struct {
uint32_t x : 24;
uint32_t y : 24;
uint32_t z : 24;
} __attribute__((__packed__)) magn;
};
};
struct rm3100_config {
uint32_t unused; /* Will be expanded with stremaing-mode to hold int-gpios */
};
struct rm3100_data {
/* RTIO context */
struct {
struct rtio_iodev *iodev;
struct rtio *ctx;
} rtio;
};
#endif /* ZEPHYR_DRIVERS_SENSOR_PNI_RM3100_H_ */

View file

@ -0,0 +1,93 @@
/*
* Copyright (c) 2025 Croxel Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_SENSOR_RM3100_BUS_H_
#define ZEPHYR_DRIVERS_SENSOR_RM3100_BUS_H_
#include <stdint.h>
#include <zephyr/rtio/rtio.h>
#include "rm3100.h"
#include "rm3100_reg.h"
static inline int rm3100_bus_read(const struct device *dev,
uint8_t reg,
uint8_t *buf,
uint16_t len)
{
struct rm3100_data *data = dev->data;
struct rtio *ctx = data->rtio.ctx;
struct rtio_iodev *iodev = data->rtio.iodev;
struct rtio_sqe *write_sqe = rtio_sqe_acquire(ctx);
struct rtio_sqe *read_sqe = rtio_sqe_acquire(ctx);
struct rtio_cqe *cqe;
int err;
if (!write_sqe || !read_sqe) {
return -ENOMEM;
}
rtio_sqe_prep_write(write_sqe, iodev, RTIO_PRIO_HIGH, &reg, 1, NULL);
write_sqe->flags |= RTIO_SQE_TRANSACTION;
rtio_sqe_prep_read(read_sqe, iodev, RTIO_PRIO_HIGH, buf, len, NULL);
read_sqe->iodev_flags |= RTIO_IODEV_I2C_STOP | RTIO_IODEV_I2C_RESTART;
err = rtio_submit(ctx, 2);
if (err) {
return err;
}
do {
cqe = rtio_cqe_consume(ctx);
if (cqe != NULL) {
err = cqe->result;
rtio_cqe_release(ctx, cqe);
}
} while (cqe != NULL);
return err;
}
static inline int rm3100_bus_write(const struct device *dev,
uint8_t reg,
const uint8_t *buf,
uint16_t len)
{
struct rm3100_data *data = dev->data;
struct rtio *ctx = data->rtio.ctx;
struct rtio_iodev *iodev = data->rtio.iodev;
struct rtio_sqe *write_reg_sqe = rtio_sqe_acquire(ctx);
struct rtio_sqe *write_buf_sqe = rtio_sqe_acquire(ctx);
struct rtio_cqe *cqe;
int err;
if (!write_reg_sqe || !write_buf_sqe) {
return -ENOMEM;
}
rtio_sqe_prep_write(write_reg_sqe, iodev, RTIO_PRIO_HIGH, &reg, 1, NULL);
write_reg_sqe->flags |= RTIO_SQE_TRANSACTION;
rtio_sqe_prep_write(write_buf_sqe, iodev, RTIO_PRIO_HIGH, buf, len, NULL);
write_buf_sqe->iodev_flags |= RTIO_IODEV_I2C_STOP;
err = rtio_submit(ctx, 2);
if (err) {
return err;
}
do {
cqe = rtio_cqe_consume(ctx);
if (cqe != NULL) {
err = cqe->result;
rtio_cqe_release(ctx, cqe);
}
} while (cqe != NULL);
return err;
}
#endif /* ZEPHYR_DRIVERS_SENSOR_RM3100_BUS_H_ */

View file

@ -0,0 +1,220 @@
/*
* 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/sys/util.h>
#include <zephyr/sys/byteorder.h>
#include "rm3100.h"
uint8_t rm3100_encode_channel(enum sensor_channel chan)
{
switch (chan) {
case SENSOR_CHAN_MAGN_X:
return BIT(0);
case SENSOR_CHAN_MAGN_Y:
return BIT(1);
case SENSOR_CHAN_MAGN_Z:
return BIT(2);
case SENSOR_CHAN_ALL:
case SENSOR_CHAN_MAGN_XYZ:
return BIT(0) | BIT(1) | BIT(2);
default:
return 0;
}
}
int rm3100_encode(const struct device *dev,
const struct sensor_chan_spec *const channels,
size_t num_channels,
uint8_t *buf)
{
struct rm3100_encoded_data *edata = (struct rm3100_encoded_data *)buf;
uint64_t cycles;
int err;
edata->header.channels = 0;
for (size_t i = 0; i < num_channels; i++) {
edata->header.channels |= rm3100_encode_channel(channels[i].chan_type);
}
err = sensor_clock_get_cycles(&cycles);
if (err != 0) {
return err;
}
edata->header.timestamp = sensor_clock_cycles_to_ns(cycles);
return 0;
}
static int rm3100_decoder_get_size_info(struct sensor_chan_spec chan_spec,
size_t *base_size,
size_t *frame_size)
{
switch (chan_spec.chan_type) {
case SENSOR_CHAN_MAGN_X:
case SENSOR_CHAN_MAGN_Y:
case SENSOR_CHAN_MAGN_Z:
*base_size = sizeof(struct sensor_q31_data);
*frame_size = sizeof(struct sensor_q31_sample_data);
return 0;
case SENSOR_CHAN_MAGN_XYZ:
*base_size = sizeof(struct sensor_three_axis_data);
*frame_size = sizeof(struct sensor_three_axis_data);
return 0;
default:
return -ENOTSUP;
}
return 0;
}
static int rm3100_decoder_get_frame_count(const uint8_t *buffer,
struct sensor_chan_spec chan_spec,
uint16_t *frame_count)
{
struct rm3100_encoded_data *edata = (struct rm3100_encoded_data *)buffer;
if (chan_spec.chan_idx != 0) {
return -ENOTSUP;
}
uint8_t channel_request = rm3100_encode_channel(chan_spec.chan_type);
if (((edata->header.channels & channel_request) != channel_request)) {
return -ENODATA;
}
switch (chan_spec.chan_type) {
case SENSOR_CHAN_MAGN_X:
case SENSOR_CHAN_MAGN_Y:
case SENSOR_CHAN_MAGN_Z:
case SENSOR_CHAN_MAGN_XYZ:
*frame_count = 1;
return 0;
default:
return -ENOTSUP;
}
return -1;
}
static int rm3100_convert_raw_to_q31(uint32_t raw_reading, q31_t *out, int8_t *shift)
{
int64_t value;
raw_reading = sys_be24_to_cpu(raw_reading);
value = sign_extend(raw_reading, 23);
/** Convert to Gauss, assuming 1 LSB = 75 uT, given default Cycle-Counting (200).
* We can represent the largest sample (2^23 LSB) in Gauss with 11 bits.
*/
*shift = 11;
int64_t micro_tesla_scaled = ((int64_t)value << (31 - *shift)) / 75;
int64_t gauss_scaled = (int64_t)micro_tesla_scaled / 100;
*out = gauss_scaled;
return 0;
}
static int rm3100_decoder_decode(const uint8_t *buffer,
struct sensor_chan_spec chan_spec,
uint32_t *fit,
uint16_t max_count,
void *data_out)
{
struct rm3100_encoded_data *edata = (struct rm3100_encoded_data *)buffer;
uint8_t channel_request;
if (*fit != 0) {
return 0;
}
if (max_count == 0 || chan_spec.chan_idx != 0) {
return -EINVAL;
}
switch (chan_spec.chan_type) {
case SENSOR_CHAN_MAGN_X:
case SENSOR_CHAN_MAGN_Y:
case SENSOR_CHAN_MAGN_Z: {
channel_request = rm3100_encode_channel(chan_spec.chan_type);
if ((edata->header.channels & channel_request) != channel_request) {
return -ENODATA;
}
struct sensor_q31_data *out = (struct sensor_q31_data *)data_out;
out->header.base_timestamp_ns = edata->header.timestamp;
out->header.reading_count = 1;
uint32_t raw_reading;
if (chan_spec.chan_type == SENSOR_CHAN_MAGN_X) {
raw_reading = edata->magn.x;
} else if (chan_spec.chan_type == SENSOR_CHAN_MAGN_Y) {
raw_reading = edata->magn.y;
} else {
raw_reading = edata->magn.z;
}
rm3100_convert_raw_to_q31(raw_reading, &out->readings->value, &out->shift);
*fit = 1;
return 1;
}
case SENSOR_CHAN_MAGN_XYZ: {
channel_request = rm3100_encode_channel(chan_spec.chan_type);
if ((edata->header.channels & channel_request) != channel_request) {
return -ENODATA;
}
struct sensor_three_axis_data *out = (struct sensor_three_axis_data *)data_out;
out->header.base_timestamp_ns = edata->header.timestamp;
out->header.reading_count = 1;
rm3100_convert_raw_to_q31(edata->magn.x, &out->readings[0].x, &out->shift);
rm3100_convert_raw_to_q31(edata->magn.y, &out->readings[0].y, &out->shift);
rm3100_convert_raw_to_q31(edata->magn.z, &out->readings[0].z, &out->shift);
*fit = 1;
return 1;
}
default:
return -EINVAL;
}
return -1;
}
static bool rm3100_decoder_has_trigger(const uint8_t *buffer,
enum sensor_trigger_type trigger)
{
return false;
}
SENSOR_DECODER_API_DT_DEFINE() = {
.get_frame_count = rm3100_decoder_get_frame_count,
.get_size_info = rm3100_decoder_get_size_info,
.decode = rm3100_decoder_decode,
.has_trigger = rm3100_decoder_has_trigger,
};
int rm3100_get_decoder(const struct device *dev,
const struct sensor_decoder_api **decoder)
{
ARG_UNUSED(dev);
*decoder = &SENSOR_DECODER_NAME();
return 0;
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2025 Croxel Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_SENSOR_RM3100_DECODER_H_
#define ZEPHYR_DRIVERS_SENSOR_RM3100_DECODER_H_
#include <stdint.h>
#include <zephyr/drivers/sensor.h>
#include "rm3100.h"
int rm3100_encode(const struct device *dev,
const struct sensor_chan_spec *const channels,
size_t num_channels,
uint8_t *buf);
uint8_t rm3100_encode_channel(enum sensor_channel chan);
int rm3100_get_decoder(const struct device *dev,
const struct sensor_decoder_api **decoder);
#endif /* ZEPHYR_DRIVERS_SENSOR_RM3100_DECODER_H_ */

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2025 Croxel, Inc.
* Copyright (c) 2025 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_SENSOR_PNI_RM3100_REG_H_
#define ZEPHYR_DRIVERS_SENSOR_PNI_RM3100_REG_H_
/* RM3100 register addresses */
#define RM3100_REG_CMM 0x01 /* Continuous measurement mode */
#define RM3100_REG_MX 0x24 /* Measurement results X (3 bytes) */
#define RM3100_REG_MY 0x27 /* Measurement results Y (3 bytes) */
#define RM3100_REG_MZ 0x2A /* Measurement results Z (3 bytes) */
#define RM3100_REG_STATUS 0x34 /* Status of DRDY */
#define RM3100_REG_REVID 0x36 /* Hardware revision ID */
/* Default values */
#define RM3100_REVID_VALUE 0x22 /* Expected REVID register value */
#define RM3100_CMM_ALL_AXIS 0x71
#endif /* ZEPHYR_DRIVERS_SENSOR_PNI_RM3100_REG_H_ */

View file

@ -0,0 +1,13 @@
# Copyright (c) 2025 Croxel, Inc.
# Copyright (c) 2025 CogniPilot Foundation
# SPDX-License-Identifier: Apache-2.0
description: |
PNI RM3100 3-axis Magnetometer.
The RM3100 can use either I2C or SPI as a communication bus.
This driver currently supports I2C only.
compatible: "pni,rm3100"
include: [sensor-device.yaml, i2c-device.yaml]