1 /* ST Microelectronics IIS328DQ 3-axis accelerometer driver
2  *
3  * Copyright (c) 2020 STMicroelectronics
4  * Copyright (c) 2024 SILA Embedded Solutions GmbH
5  *
6  * SPDX-License-Identifier: Apache-2.0
7  *
8  * Datasheet:
9  * https://www.st.com/resource/en/datasheet/iis328dq.pdf
10  */
11 
12 #define DT_DRV_COMPAT st_iis328dq
13 
14 #include <string.h>
15 #include <zephyr/init.h>
16 #include <zephyr/sys/__assert.h>
17 #include <zephyr/sys/byteorder.h>
18 #include <zephyr/logging/log.h>
19 #include <zephyr/drivers/sensor.h>
20 
21 #if DT_ANY_INST_ON_BUS_STATUS_OKAY(spi)
22 #include <zephyr/drivers/spi.h>
23 #elif DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c)
24 #include <zephyr/drivers/i2c.h>
25 #endif
26 
27 #include "iis328dq.h"
28 
29 LOG_MODULE_REGISTER(IIS328DQ, CONFIG_SENSOR_LOG_LEVEL);
30 
31 /**
32  * iis328dq_set_odr - set new Full Scale (in ±g)
33  */
iis328dq_set_range(const struct device * dev,uint8_t fs)34 static int iis328dq_set_range(const struct device *dev, uint8_t fs)
35 {
36 	int err;
37 	struct iis328dq_data *iis328dq = dev->data;
38 	const struct iis328dq_config *cfg = dev->config;
39 	stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
40 	iis328dq_fs_t fs_reg;
41 	uint8_t gain;
42 
43 	if (fs <= 2) {
44 		fs_reg = IIS328DQ_2g;
45 		gain = 1;
46 	} else if (fs <= 4) {
47 		fs_reg = IIS328DQ_4g;
48 		gain = 2;
49 	} else if (fs <= 8) {
50 		fs_reg = IIS328DQ_8g;
51 		gain = 4;
52 	} else {
53 		LOG_ERR("FS too high");
54 		return -ENOTSUP;
55 	}
56 	err = iis328dq_full_scale_set(ctx, fs_reg);
57 
58 	if (!err) {
59 		iis328dq->gain = gain;
60 	}
61 
62 	return err;
63 }
64 
65 /**
66  * iis328dq_set_odr - set new Output Data Rate/sampling frequency (in Hz)
67  */
iis328dq_set_odr(const struct device * dev,uint16_t odr)68 static int iis328dq_set_odr(const struct device *dev, uint16_t odr)
69 {
70 	const struct iis328dq_config *cfg = dev->config;
71 	stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
72 
73 	iis328dq_dr_t odr_reg;
74 
75 	if (odr == 0U) {
76 		odr_reg = IIS328DQ_ODR_OFF;
77 	} else if (odr <= 1) {
78 		odr_reg = IIS328DQ_ODR_1Hz;
79 	} else if (odr <= 2) {
80 		odr_reg = IIS328DQ_ODR_2Hz;
81 	} else if (odr <= 5) {
82 		odr_reg = IIS328DQ_ODR_5Hz;
83 	} else if (odr <= 10) {
84 		odr_reg = IIS328DQ_ODR_10Hz;
85 	} else if (odr <= 50) {
86 		odr_reg = IIS328DQ_ODR_50Hz;
87 	} else if (odr <= 100) {
88 		odr_reg = IIS328DQ_ODR_100Hz;
89 	} else if (odr <= 400) {
90 		odr_reg = IIS328DQ_ODR_400Hz;
91 	} else if (odr <= 1000) {
92 		odr_reg = IIS328DQ_ODR_1kHz;
93 	} else {
94 		LOG_ERR("ODR too high");
95 		return -ENOTSUP;
96 	}
97 
98 	if (iis328dq_data_rate_set(ctx, odr_reg) != 0) {
99 		LOG_ERR("Failed to set ODR");
100 		return -EIO;
101 	}
102 
103 	return 0;
104 }
105 
iis328dq_convert(struct sensor_value * val,int raw_val,uint8_t gain)106 static inline void iis328dq_convert(struct sensor_value *val, int raw_val, uint8_t gain)
107 {
108 	int64_t dval;
109 
110 	/* Gain is in mg/LSB */
111 	/* Convert to μm/s^2 */
112 	dval = ((int64_t)raw_val * gain * SENSOR_G) / 1000LL;
113 	val->val1 = dval / 1000000LL;
114 	val->val2 = dval % 1000000LL;
115 }
116 
iis328dq_channel_get_acc(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)117 static inline void iis328dq_channel_get_acc(const struct device *dev, enum sensor_channel chan,
118 					    struct sensor_value *val)
119 {
120 	int i;
121 	uint8_t ofs_start, ofs_stop;
122 	struct iis328dq_data *iis328dq = dev->data;
123 	struct sensor_value *pval = val;
124 
125 	switch (chan) {
126 	case SENSOR_CHAN_ACCEL_X:
127 		ofs_start = ofs_stop = 0U;
128 		break;
129 	case SENSOR_CHAN_ACCEL_Y:
130 		ofs_start = ofs_stop = 1U;
131 		break;
132 	case SENSOR_CHAN_ACCEL_Z:
133 		ofs_start = ofs_stop = 2U;
134 		break;
135 	default:
136 		ofs_start = 0U;
137 		ofs_stop = 2U;
138 		break;
139 	}
140 
141 	for (i = ofs_start; i <= ofs_stop; i++) {
142 		iis328dq_convert(pval++, iis328dq->acc[i], iis328dq->gain);
143 	}
144 }
145 
iis328dq_channel_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)146 static int iis328dq_channel_get(const struct device *dev, enum sensor_channel chan,
147 				struct sensor_value *val)
148 {
149 	switch (chan) {
150 	case SENSOR_CHAN_ACCEL_X:
151 	case SENSOR_CHAN_ACCEL_Y:
152 	case SENSOR_CHAN_ACCEL_Z:
153 	case SENSOR_CHAN_ACCEL_XYZ:
154 		iis328dq_channel_get_acc(dev, chan, val);
155 		return 0;
156 	default:
157 		LOG_DBG("Channel not supported");
158 		break;
159 	}
160 
161 	return -ENOTSUP;
162 }
163 
164 #ifdef CONFIG_IIS328DQ_THRESHOLD
iis328dq_set_threshold(const struct device * dev,bool is_lower,const struct sensor_value * val)165 static int iis328dq_set_threshold(const struct device *dev, bool is_lower,
166 				  const struct sensor_value *val)
167 {
168 	int err;
169 	const struct iis328dq_config *cfg = dev->config;
170 	struct iis328dq_data *iis328dq = dev->data;
171 	stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
172 
173 	if (val->val1 < 0 || val->val2 < 0) {
174 		/* thresholds are absolute */
175 		return -EINVAL;
176 	}
177 	int64_t micro_ms2 = (val->val1 * INT64_C(1000000)) + val->val2;
178 	/* factor guessed from similar-looking LIS2DH12 datasheet */
179 	uint8_t mg_per_digit = iis328dq->gain * 16;
180 
181 	int16_t val_raw = (micro_ms2 * 1000LL) / SENSOR_G / mg_per_digit;
182 
183 	if (is_lower) {
184 		/* internal INT1 handles lower threshold */
185 		err = iis328dq_int1_treshold_set(ctx, val_raw);
186 		if (err) {
187 			LOG_ERR("Could not set INT1_THS to 0x%02X, error %d", val_raw, err);
188 			return err;
189 		}
190 	} else {
191 		/* internal INT2 handles lower threshold */
192 		err = iis328dq_int2_treshold_set(ctx, val_raw);
193 		if (err) {
194 			LOG_ERR("Could not set INT2_THS to 0x%02X, error %d", val_raw, err);
195 			return err;
196 		}
197 	}
198 	return 0;
199 }
200 
iis328dq_set_duration(const struct device * dev,uint16_t dur)201 static int iis328dq_set_duration(const struct device *dev, uint16_t dur)
202 {
203 	int err;
204 	const struct iis328dq_config *cfg = dev->config;
205 	stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
206 
207 	if (dur > 0x7F) {
208 		LOG_WRN("Duration value %u too large", dur);
209 		return -EINVAL;
210 	}
211 
212 	err = iis328dq_int1_dur_set(ctx, dur);
213 	if (err) {
214 		LOG_ERR("Could not set INT1_DUR to 0x%02X, error %d", dur, err);
215 		return err;
216 	}
217 	err = iis328dq_int2_dur_set(ctx, dur);
218 	if (err) {
219 		LOG_ERR("Could not set INT2_DUR to 0x%02X, error %d", dur, err);
220 		return err;
221 	}
222 	return 0;
223 }
224 #endif /* CONFIG_IIS328DQ_THRESHOLD */
225 #define IIS328DQ_ATTR_DURATION SENSOR_ATTR_PRIV_START
226 
iis328dq_dev_config(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,const struct sensor_value * val)227 static int iis328dq_dev_config(const struct device *dev, enum sensor_channel chan,
228 			       enum sensor_attribute attr, const struct sensor_value *val)
229 {
230 	switch (attr) {
231 	case SENSOR_ATTR_FULL_SCALE:
232 		return iis328dq_set_range(dev, sensor_ms2_to_g(val));
233 	case SENSOR_ATTR_SAMPLING_FREQUENCY:
234 		return iis328dq_set_odr(dev, val->val1);
235 #ifdef CONFIG_IIS328DQ_THRESHOLD
236 	case SENSOR_ATTR_LOWER_THRESH:
237 	case SENSOR_ATTR_UPPER_THRESH:
238 		if (chan != SENSOR_CHAN_ACCEL_XYZ) {
239 			LOG_ERR("Threshold cannot be set per-channel");
240 			return -ENOTSUP;
241 		}
242 		return iis328dq_set_threshold(dev, attr == SENSOR_ATTR_LOWER_THRESH, val);
243 	case IIS328DQ_ATTR_DURATION:
244 		if (chan != SENSOR_CHAN_ACCEL_XYZ) {
245 			LOG_ERR("Duration cannot be set per-channel");
246 			return -ENOTSUP;
247 		}
248 		return iis328dq_set_duration(dev, val->val1);
249 #endif /* CONFIG_IIS328DQ_THRESHOLD */
250 	default:
251 		LOG_DBG("Acc attribute not supported");
252 		break;
253 	}
254 
255 	return -ENOTSUP;
256 }
257 
iis328dq_attr_set(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,const struct sensor_value * val)258 static int iis328dq_attr_set(const struct device *dev, enum sensor_channel chan,
259 			     enum sensor_attribute attr, const struct sensor_value *val)
260 {
261 	switch (chan) {
262 	case SENSOR_CHAN_ACCEL_X:
263 	case SENSOR_CHAN_ACCEL_Y:
264 	case SENSOR_CHAN_ACCEL_Z:
265 	case SENSOR_CHAN_ACCEL_XYZ:
266 	case SENSOR_CHAN_ALL:
267 		return iis328dq_dev_config(dev, chan, attr, val);
268 	default:
269 		LOG_DBG("Attr not supported on %d channel", chan);
270 		break;
271 	}
272 
273 	return -ENOTSUP;
274 }
275 
iis328dq_sample_fetch(const struct device * dev,enum sensor_channel chan)276 static int iis328dq_sample_fetch(const struct device *dev, enum sensor_channel chan)
277 {
278 	struct iis328dq_data *iis328dq = dev->data;
279 	const struct iis328dq_config *cfg = dev->config;
280 	stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
281 	int16_t buf[3];
282 
283 	/* fetch raw data sample */
284 	if (iis328dq_acceleration_raw_get(ctx, buf) < 0) {
285 		LOG_DBG("Failed to fetch raw data sample");
286 		return -EIO;
287 	}
288 
289 	iis328dq->acc[0] = buf[0] >> 4;
290 	iis328dq->acc[1] = buf[1] >> 4;
291 	iis328dq->acc[2] = buf[2] >> 4;
292 
293 	return 0;
294 }
295 
296 static DEVICE_API(sensor, iis328dq_driver_api) = {
297 	.attr_set = iis328dq_attr_set,
298 #if CONFIG_IIS328DQ_TRIGGER
299 	.trigger_set = iis328dq_trigger_set,
300 #endif /* CONFIG_IIS328DQ_TRIGGER */
301 	.sample_fetch = iis328dq_sample_fetch,
302 	.channel_get = iis328dq_channel_get,
303 };
304 
iis328dq_init(const struct device * dev)305 static int iis328dq_init(const struct device *dev)
306 {
307 	struct iis328dq_data *iis328dq = dev->data;
308 	const struct iis328dq_config *cfg = dev->config;
309 	stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
310 	uint8_t reg_value;
311 
312 	iis328dq->dev = dev;
313 
314 	/* check chip ID */
315 	if (iis328dq_device_id_get(ctx, &reg_value) < 0) {
316 		return -EIO;
317 	}
318 
319 	if (reg_value != IIS328DQ_ID) {
320 		LOG_ERR("Invalid chip ID");
321 		return -EINVAL;
322 	}
323 
324 	/* reset device */
325 	if (iis328dq_boot_set(ctx, PROPERTY_ENABLE) < 0) {
326 		return -EIO;
327 	}
328 
329 	k_sleep(K_MSEC(100));
330 
331 	if (iis328dq_boot_get(ctx, &reg_value) < 0) {
332 		return -EIO;
333 	}
334 	if (reg_value != PROPERTY_DISABLE) {
335 		LOG_ERR("BOOT did not deassert");
336 		return -EIO;
337 	}
338 
339 	if (iis328dq_block_data_update_set(ctx, PROPERTY_ENABLE) < 0) {
340 		return -EIO;
341 	}
342 
343 	/* set default odr to 12.5Hz acc */
344 	if (iis328dq_set_odr(dev, 12) < 0) {
345 		LOG_ERR("odr init error (12.5 Hz)");
346 		return -EIO;
347 	}
348 
349 	if (iis328dq_set_range(dev, cfg->range) < 0) {
350 		LOG_ERR("range init error %d", cfg->range);
351 		return -EIO;
352 	}
353 
354 #ifdef CONFIG_IIS328DQ_TRIGGER
355 	if (iis328dq_init_interrupt(dev) < 0) {
356 		LOG_ERR("Failed to initialize interrupts");
357 		return -EIO;
358 	}
359 #endif /* CONFIG_IIS328DQ_TRIGGER */
360 
361 	return 0;
362 }
363 
364 /*
365  * Device creation macro, shared by IIS328DQ_DEFINE_SPI() and
366  * IIS328DQ_DEFINE_I2C().
367  */
368 
369 #define IIS328DQ_DEVICE_INIT(inst)                                                                 \
370 	SENSOR_DEVICE_DT_INST_DEFINE(inst, iis328dq_init, NULL, &iis328dq_data_##inst,             \
371 				     &iis328dq_config_##inst, POST_KERNEL,                         \
372 				     CONFIG_SENSOR_INIT_PRIORITY, &iis328dq_driver_api);
373 
374 #ifdef CONFIG_IIS328DQ_TRIGGER
375 #ifdef CONFIG_IIS328DQ_THRESHOLD
376 #define IIS328DQ_CFG_IRQ_THRESHOLD(inst)                                                           \
377 	.threshold_pad = DT_INST_PROP_OR(inst, threshold_int_pad, -1),
378 #else
379 #define IIS328DQ_CFG_IRQ_THRESHOLD(inst)
380 #endif /* CONFIG_IIS328DQ_THRESHOLD */
381 
382 #define IIS328DQ_CFG_IRQ(inst)                                                                     \
383 	.gpio_int1 = GPIO_DT_SPEC_INST_GET_OR(inst, int1_gpios, {0}),                              \
384 	.gpio_int2 = GPIO_DT_SPEC_INST_GET_OR(inst, int2_gpios, {0}),                              \
385 	.drdy_pad = DT_INST_PROP_OR(inst, drdy_int_pad, -1), IIS328DQ_CFG_IRQ_THRESHOLD(inst)
386 #else
387 #define IIS328DQ_CFG_IRQ(inst)
388 #endif /* CONFIG_IIS328DQ_TRIGGER */
389 
390 #define IIS328DQ_CONFIG_COMMON(inst)                                                               \
391 	.range = DT_INST_PROP(inst, range),                                                        \
392 	IF_ENABLED(UTIL_OR(DT_INST_NODE_HAS_PROP(inst, int1_gpios),                                \
393 			   DT_INST_NODE_HAS_PROP(inst, int2_gpios)),                               \
394 		   (IIS328DQ_CFG_IRQ(inst)))
395 
396 /*
397  * Instantiation macros used when a device is on a SPI bus.
398  */
399 
400 #define IIS328DQ_SPI_OPERATION                                                                     \
401 	(SPI_WORD_SET(8) | SPI_OP_MODE_MASTER | SPI_MODE_CPOL | SPI_MODE_CPHA)
402 
403 #define IIS328DQ_CONFIG_SPI(inst)                                                                  \
404 	{                                                                                          \
405 		STMEMSC_CTX_SPI_INCR(&iis328dq_config_##inst.stmemsc_cfg),                         \
406 			.stmemsc_cfg =                                                             \
407 				{                                                                  \
408 					.spi = SPI_DT_SPEC_INST_GET(inst, IIS328DQ_SPI_OPERATION,  \
409 								    0),                            \
410 				},                                                                 \
411 			IIS328DQ_CONFIG_COMMON(inst)                                               \
412 	}
413 
414 /*
415  * Instantiation macros used when a device is on an I2C bus.
416  */
417 
418 #define IIS328DQ_CONFIG_I2C(inst)                                                                  \
419 	{                                                                                          \
420 		STMEMSC_CTX_I2C_INCR(&iis328dq_config_##inst.stmemsc_cfg),                         \
421 			.stmemsc_cfg =                                                             \
422 				{                                                                  \
423 					.i2c = I2C_DT_SPEC_INST_GET(inst),                         \
424 				},                                                                 \
425 			IIS328DQ_CONFIG_COMMON(inst)                                               \
426 	}
427 
428 /*
429  * Main instantiation macro. Use of COND_CODE_1() selects the right
430  * bus-specific macro at preprocessor time.
431  */
432 
433 #define IIS328DQ_DEFINE(inst)                                                                      \
434 	static struct iis328dq_data iis328dq_data_##inst;                                          \
435 	static const struct iis328dq_config iis328dq_config_##inst =                               \
436 		COND_CODE_1(DT_INST_ON_BUS(inst, spi), (IIS328DQ_CONFIG_SPI(inst)),                \
437 			    (IIS328DQ_CONFIG_I2C(inst)));                                          \
438 	IIS328DQ_DEVICE_INIT(inst)                                                                 \
439 	IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, drdy_int_pad),                                      \
440 		   (BUILD_ASSERT(                                                                  \
441 			    DT_INST_NODE_HAS_PROP(                                                 \
442 				    inst, CONCAT(int, DT_INST_PROP(inst, drdy_int_pad), _gpios)),  \
443 			    "No GPIO pin defined for IIS328DQ DRDY interrupt");))                  \
444 	IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, threshold_int_pad),                                 \
445 		   (BUILD_ASSERT(DT_INST_NODE_HAS_PROP(                                            \
446 					 inst, CONCAT(int, DT_INST_PROP(inst, threshold_int_pad),  \
447 						      _gpios)),                                    \
448 				 "No GPIO pin defined for IIS328DQ threshold interrupt");))        \
449 	IF_ENABLED(                                                                                \
450 		UTIL_AND(DT_INST_NODE_HAS_PROP(inst, drdy_int_pad),                                \
451 			 DT_INST_NODE_HAS_PROP(inst, threshold_int_pad)),                          \
452 		(BUILD_ASSERT(                                                                     \
453 			 DT_INST_PROP(inst, drdy_int_pad) !=                                       \
454 				 DT_INST_PROP(inst, threshold_int_pad),                            \
455 			 "IIS328DQ DRDY interrupt and threshold interrupt cannot share a pin");))
456 
457 DT_INST_FOREACH_STATUS_OKAY(IIS328DQ_DEFINE)
458