Skip to content

Commit a721697

Browse files
committed
sensor: icm45686: Add basic sensor functionality
- Add support for Fetch/Get API. - Add support for Read/Decode API. - Add config settings through device-tree. - Add bus support for SPI (although easily extensible to others as based on RTIO). Fetch/Get API tested with accel_polling sample. Read/Decode API tested with sensor_shell sample. Signed-off-by: Luis Ubieda <[email protected]>
1 parent 1f1bec8 commit a721697

File tree

12 files changed

+1363
-0
lines changed

12 files changed

+1363
-0
lines changed

drivers/sensor/tdk/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
add_subdirectory_ifdef(CONFIG_ICM42605 icm42605)
66
add_subdirectory_ifdef(CONFIG_ICM42688 icm42688)
77
add_subdirectory_ifdef(CONFIG_ICM42X70 icm42x70)
8+
add_subdirectory_ifdef(CONFIG_ICM45686 icm45686)
89
add_subdirectory_ifdef(CONFIG_ICP101XX icp101xx)
910
add_subdirectory_ifdef(CONFIG_MPU6050 mpu6050)
1011
add_subdirectory_ifdef(CONFIG_MPU9250 mpu9250)

drivers/sensor/tdk/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
source "drivers/sensor/tdk/icm42605/Kconfig"
66
source "drivers/sensor/tdk/icm42688/Kconfig"
77
source "drivers/sensor/tdk/icm42x70/Kconfig"
8+
source "drivers/sensor/tdk/icm45686/Kconfig"
89
source "drivers/sensor/tdk/icp101xx/Kconfig"
910
source "drivers/sensor/tdk/mpu6050/Kconfig"
1011
source "drivers/sensor/tdk/mpu9250/Kconfig"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copyright (c) 2025 Croxel Inc.
2+
# Copyright (c) 2025 CogniPilot Foundation
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
zephyr_library()
6+
zephyr_library_include_directories(.)
7+
zephyr_library_sources(
8+
icm45686.c
9+
)
10+
zephyr_library_sources_ifdef(CONFIG_SENSOR_ASYNC_API
11+
icm45686_decoder.c
12+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright (c) 2025 Croxel Inc.
2+
# Copyright (c) 2025 CogniPilot Foundation
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
config ICM45686
6+
bool "ICM45686 High-precision 6-Axis Motion Tracking Device"
7+
default y
8+
depends on DT_HAS_INVENSENSE_ICM45686_ENABLED
9+
select SPI
10+
select SPI_RTIO
11+
help
12+
Enable driver for ICM45686 High-precision 6-axis motion
13+
tracking device.
Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
/*
2+
* Copyright (c) 2022 Intel Corporation
3+
* Copyright (c) 2025 Croxel Inc.
4+
* Copyright (c) 2025 CogniPilot Foundation
5+
*
6+
* SPDX-License-Identifier: Apache-2.0
7+
*/
8+
9+
#define DT_DRV_COMPAT invensense_icm45686
10+
11+
#include <zephyr/drivers/sensor.h>
12+
#include <zephyr/drivers/spi.h>
13+
#include <zephyr/drivers/gpio.h>
14+
#include <zephyr/rtio/rtio.h>
15+
16+
#if defined(CONFIG_SENSOR_ASYNC_API)
17+
#include <zephyr/rtio/work.h>
18+
#endif /* CONFIG_SENSOR_ASYNC_API */
19+
20+
#include "icm45686.h"
21+
#include "icm45686_reg.h"
22+
#include "icm45686_bus.h"
23+
#include "icm45686_decoder.h"
24+
25+
#include <zephyr/logging/log.h>
26+
LOG_MODULE_REGISTER(ICM45686, CONFIG_SENSOR_LOG_LEVEL);
27+
28+
static inline int reg_write(const struct device *dev, uint8_t reg, uint8_t val)
29+
{
30+
return icm45686_bus_write(dev, reg, &val, 1);
31+
}
32+
33+
static inline int reg_read(const struct device *dev, uint8_t reg, uint8_t *val)
34+
{
35+
return icm45686_bus_read(dev, reg, val, 1);
36+
}
37+
38+
static inline int icm45686_fetch_data(const struct device *dev,
39+
struct icm45686_encoded_data *edata)
40+
{
41+
int err;
42+
43+
err = icm45686_bus_read(dev,
44+
REG_ACCEL_DATA_X1_UI,
45+
edata->payload.buf,
46+
sizeof(edata->payload.buf));
47+
48+
LOG_HEXDUMP_DBG(edata->payload.buf,
49+
sizeof(edata->payload.buf),
50+
"ICM45686 data");
51+
52+
return err;
53+
}
54+
55+
static int icm45686_sample_fetch(const struct device *dev,
56+
enum sensor_channel chan)
57+
{
58+
if (chan != SENSOR_CHAN_ALL) {
59+
return -ENOTSUP;
60+
}
61+
62+
struct icm45686_data *data = dev->data;
63+
64+
return icm45686_fetch_data(dev, &data->edata);
65+
}
66+
67+
static int icm45686_channel_get(const struct device *dev,
68+
enum sensor_channel chan,
69+
struct sensor_value *val)
70+
{
71+
struct icm45686_data *data = dev->data;
72+
73+
switch (chan) {
74+
case SENSOR_CHAN_ACCEL_X:
75+
icm45686_accel_ms(&data->edata, data->edata.payload.accel.x,
76+
&val->val1, &val->val2);
77+
break;
78+
case SENSOR_CHAN_ACCEL_Y:
79+
icm45686_accel_ms(&data->edata, data->edata.payload.accel.y,
80+
&val->val1, &val->val2);
81+
break;
82+
case SENSOR_CHAN_ACCEL_Z:
83+
icm45686_accel_ms(&data->edata, data->edata.payload.accel.z,
84+
&val->val1, &val->val2);
85+
break;
86+
case SENSOR_CHAN_GYRO_X:
87+
icm45686_gyro_rads(&data->edata, data->edata.payload.gyro.x,
88+
&val->val1, &val->val2);
89+
break;
90+
case SENSOR_CHAN_GYRO_Y:
91+
icm45686_gyro_rads(&data->edata, data->edata.payload.gyro.y,
92+
&val->val1, &val->val2);
93+
break;
94+
case SENSOR_CHAN_GYRO_Z:
95+
icm45686_gyro_rads(&data->edata, data->edata.payload.gyro.z,
96+
&val->val1, &val->val2);
97+
break;
98+
case SENSOR_CHAN_DIE_TEMP:
99+
icm45686_temp_c(data->edata.payload.temp, &val->val1, &val->val2);
100+
break;
101+
case SENSOR_CHAN_ACCEL_XYZ:
102+
icm45686_accel_ms(&data->edata, data->edata.payload.accel.x,
103+
&val[0].val1, &val[0].val2);
104+
icm45686_accel_ms(&data->edata, data->edata.payload.accel.y,
105+
&val[1].val1, &val[1].val2);
106+
icm45686_accel_ms(&data->edata, data->edata.payload.accel.z,
107+
&val[2].val1, &val[2].val2);
108+
break;
109+
case SENSOR_CHAN_GYRO_XYZ:
110+
icm45686_gyro_rads(&data->edata, data->edata.payload.gyro.x,
111+
&val->val1, &val->val2);
112+
icm45686_gyro_rads(&data->edata, data->edata.payload.gyro.y,
113+
&val[1].val1, &val[1].val2);
114+
icm45686_gyro_rads(&data->edata, data->edata.payload.gyro.z,
115+
&val[2].val1, &val[2].val2);
116+
break;
117+
default:
118+
return -ENOTSUP;
119+
}
120+
121+
return 0;
122+
}
123+
124+
#if defined(CONFIG_SENSOR_ASYNC_API)
125+
126+
static inline void icm45686_submit_one_shot(const struct device *dev,
127+
struct rtio_iodev_sqe *iodev_sqe)
128+
{
129+
const struct sensor_read_config *cfg = iodev_sqe->sqe.iodev->data;
130+
const struct sensor_chan_spec *const channels = cfg->channels;
131+
const size_t num_channels = cfg->count;
132+
uint32_t min_buf_len = sizeof(struct icm45686_encoded_data);
133+
int err;
134+
uint8_t *buf;
135+
uint32_t buf_len;
136+
struct icm45686_encoded_data *edata;
137+
138+
err = rtio_sqe_rx_buf(iodev_sqe, min_buf_len, min_buf_len, &buf, &buf_len);
139+
if (err != 0) {
140+
LOG_ERR("Failed to get a read buffer of size %u bytes", min_buf_len);
141+
rtio_iodev_sqe_err(iodev_sqe, err);
142+
return;
143+
}
144+
145+
edata = (struct icm45686_encoded_data *)buf;
146+
147+
err = icm45686_encode(dev, channels, num_channels, buf);
148+
if (err != 0) {
149+
LOG_ERR("Failed to encode sensor data");
150+
rtio_iodev_sqe_err(iodev_sqe, err);
151+
return;
152+
}
153+
154+
err = icm45686_fetch_data(dev, edata);
155+
if (err != 0) {
156+
LOG_ERR("Failed to fetch samples");
157+
rtio_iodev_sqe_err(iodev_sqe, err);
158+
return;
159+
}
160+
161+
rtio_iodev_sqe_ok(iodev_sqe, 0);
162+
163+
LOG_DBG("One-shot fetch completed");
164+
}
165+
166+
static void icm45686_submit_sync(struct rtio_iodev_sqe *iodev_sqe)
167+
{
168+
const struct sensor_read_config *cfg = iodev_sqe->sqe.iodev->data;
169+
const struct device *dev = cfg->sensor;
170+
171+
if (!cfg->is_streaming) {
172+
icm45686_submit_one_shot(dev, iodev_sqe);
173+
} else {
174+
LOG_ERR("Streaming not supported");
175+
rtio_iodev_sqe_err(iodev_sqe, -ENOTSUP);
176+
}
177+
}
178+
179+
static void icm45686_submit(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe)
180+
{
181+
struct rtio_work_req *req = rtio_work_req_alloc();
182+
183+
if (req == NULL) {
184+
LOG_ERR("Failed to allocate RTIO work request");
185+
rtio_iodev_sqe_err(iodev_sqe, -ENOMEM);
186+
return;
187+
}
188+
189+
rtio_work_req_submit(req, iodev_sqe, icm45686_submit_sync);
190+
}
191+
192+
#endif /* CONFIG_SENSOR_ASYNC_API */
193+
194+
static DEVICE_API(sensor, icm45686_driver_api) = {
195+
.sample_fetch = icm45686_sample_fetch,
196+
.channel_get = icm45686_channel_get,
197+
#if defined(CONFIG_SENSOR_ASYNC_API)
198+
.get_decoder = icm45686_get_decoder,
199+
.submit = icm45686_submit,
200+
#endif /* CONFIG_SENSOR_ASYNC_API */
201+
};
202+
203+
static int icm45686_init(const struct device *dev)
204+
{
205+
struct icm45686_data *data = dev->data;
206+
const struct icm45686_config *cfg = dev->config;
207+
uint8_t read_val = 0;
208+
uint8_t val;
209+
int err;
210+
211+
if (!spi_is_ready_iodev(data->rtio.iodev)) {
212+
LOG_ERR("Bus is not ready");
213+
return -ENODEV;
214+
}
215+
216+
/* Soft-reset sensor to restore config to defaults */
217+
218+
err = reg_write(dev, REG_MISC2, REG_MISC2_SOFT_RST(1));
219+
if (err) {
220+
LOG_ERR("Failed to write soft-reset: %d", err);
221+
return err;
222+
}
223+
/* Wait for soft-reset to take effect */
224+
k_sleep(K_MSEC(1));
225+
226+
/* A complete soft-reset clears the bit */
227+
err = reg_read(dev, REG_MISC2, &read_val);
228+
if (err) {
229+
LOG_ERR("Failed to read soft-reset: %d", err);
230+
return err;
231+
}
232+
if ((read_val & REG_MISC2_SOFT_RST(1)) != 0) {
233+
LOG_ERR("Soft-reset command failed");
234+
return -EIO;
235+
}
236+
237+
/* Set Slew-rate to 10-ns typical, to allow proper SPI readouts */
238+
239+
err = reg_write(dev, REG_DRIVE_CONFIG0, REG_DRIVE_CONFIG0_SPI_SLEW(2));
240+
if (err) {
241+
LOG_ERR("Failed to write slew-rate: %d", err);
242+
return err;
243+
}
244+
/* Wait for register to take effect */
245+
k_sleep(K_USEC(2));
246+
247+
/* Confirm ID Value matches */
248+
err = reg_read(dev, REG_WHO_AM_I, &read_val);
249+
if (err) {
250+
LOG_ERR("Failed to read WHO_AM_I: %d", err);
251+
return err;
252+
}
253+
if (read_val != WHO_AM_I_ICM45686) {
254+
LOG_ERR("Unexpected WHO_AM_I value - expected: 0x%02x, actual: 0x%02x",
255+
WHO_AM_I_ICM45686, read_val);
256+
return -EIO;
257+
}
258+
259+
/* Sensor Configuration */
260+
261+
val = REG_PWR_MGMT0_ACCEL_MODE(cfg->settings.accel.pwr_mode) |
262+
REG_PWR_MGMT0_GYRO_MODE(cfg->settings.gyro.pwr_mode);
263+
err = reg_write(dev, REG_PWR_MGMT0, val);
264+
if (err) {
265+
LOG_ERR("Failed to write Power settings: %d", err);
266+
return err;
267+
}
268+
269+
val = REG_ACCEL_CONFIG0_ODR(cfg->settings.accel.odr) |
270+
REG_ACCEL_CONFIG0_FS(cfg->settings.accel.fs);
271+
err = reg_write(dev, REG_ACCEL_CONFIG0, val);
272+
if (err) {
273+
LOG_ERR("Failed to write Accel settings: %d", err);
274+
return err;
275+
}
276+
277+
val = REG_GYRO_CONFIG0_ODR(cfg->settings.gyro.odr) |
278+
REG_GYRO_CONFIG0_FS(cfg->settings.gyro.fs);
279+
err = reg_write(dev, REG_GYRO_CONFIG0, val);
280+
if (err) {
281+
LOG_ERR("Failed to write Gyro settings: %d", err);
282+
return err;
283+
}
284+
285+
LOG_DBG("Init OK");
286+
287+
return 0;
288+
}
289+
290+
#define ICM45686_VALID_ACCEL_ODR(pwr_mode, odr) \
291+
((pwr_mode == ICM45686_DT_ACCEL_LP && odr >= ICM45686_DT_ACCEL_ODR_400) || \
292+
(pwr_mode == ICM45686_DT_ACCEL_LN && odr <= ICM45686_DT_ACCEL_ODR_12_5) || \
293+
(pwr_mode == ICM45686_DT_ACCEL_OFF))
294+
295+
#define ICM45686_VALID_GYRO_ODR(pwr_mode, odr) \
296+
((pwr_mode == ICM45686_DT_GYRO_LP && odr >= ICM45686_DT_GYRO_ODR_400) || \
297+
(pwr_mode == ICM45686_DT_GYRO_LN && odr <= ICM45686_DT_GYRO_ODR_12_5) || \
298+
(pwr_mode == ICM45686_DT_GYRO_OFF))
299+
300+
#define ICM45686_INIT(inst) \
301+
\
302+
RTIO_DEFINE(icm45686_rtio_ctx_##inst, 8, 8); \
303+
SPI_DT_IODEV_DEFINE(icm45686_bus_##inst, \
304+
DT_DRV_INST(inst), \
305+
SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_TRANSFER_MSB, \
306+
0U); \
307+
\
308+
static const struct icm45686_config icm45686_cfg_##inst = { \
309+
.settings = { \
310+
.accel = { \
311+
.pwr_mode = DT_INST_PROP(inst, accel_pwr_mode), \
312+
.fs = DT_INST_PROP(inst, accel_fs), \
313+
.odr = DT_INST_PROP(inst, accel_odr), \
314+
}, \
315+
.gyro = { \
316+
.pwr_mode = DT_INST_PROP(inst, gyro_pwr_mode), \
317+
.fs = DT_INST_PROP(inst, gyro_fs), \
318+
.odr = DT_INST_PROP(inst, gyro_odr), \
319+
}, \
320+
}, \
321+
}; \
322+
static struct icm45686_data icm45686_data_##inst = { \
323+
.edata.header = { \
324+
.is_fifo = false, \
325+
.accel_fs = DT_INST_PROP(inst, accel_fs), \
326+
.gyro_fs = DT_INST_PROP(inst, gyro_fs), \
327+
}, \
328+
.rtio = { \
329+
.iodev = &icm45686_bus_##inst, \
330+
.ctx = &icm45686_rtio_ctx_##inst, \
331+
}, \
332+
}; \
333+
\
334+
/* Build-time settings verification: Inform the user of invalid settings at build time */ \
335+
BUILD_ASSERT(ICM45686_VALID_ACCEL_ODR(DT_INST_PROP(inst, accel_pwr_mode), \
336+
DT_INST_PROP(inst, accel_odr)), \
337+
"Invalid accel ODR setting. Please check supported ODRs for LP and LN"); \
338+
BUILD_ASSERT(ICM45686_VALID_GYRO_ODR(DT_INST_PROP(inst, gyro_pwr_mode), \
339+
DT_INST_PROP(inst, gyro_odr)), \
340+
"Invalid gyro ODR setting. Please check supported ODRs for LP and LN"); \
341+
\
342+
SENSOR_DEVICE_DT_INST_DEFINE(inst, icm45686_init, \
343+
NULL, \
344+
&icm45686_data_##inst, \
345+
&icm45686_cfg_##inst, \
346+
POST_KERNEL, \
347+
CONFIG_SENSOR_INIT_PRIORITY, \
348+
&icm45686_driver_api);
349+
350+
DT_INST_FOREACH_STATUS_OKAY(ICM45686_INIT)

0 commit comments

Comments
 (0)