Skip to content

Commit 35f49ba

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 cba9160 commit 35f49ba

File tree

12 files changed

+1352
-0
lines changed

12 files changed

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

0 commit comments

Comments
 (0)