1 /* ST Microelectronics ISM330DHCX 6-axis IMU sensor driver
2  *
3  * Copyright (c) 2020 STMicroelectronics
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  *
7  * Datasheet:
8  * https://www.st.com/resource/en/datasheet/ism330dhcx.pdf
9  */
10 
11 #define DT_DRV_COMPAT st_ism330dhcx
12 
13 #include <zephyr/kernel.h>
14 #include <zephyr/drivers/sensor.h>
15 #include <zephyr/drivers/gpio.h>
16 #include <zephyr/logging/log.h>
17 
18 #include "ism330dhcx.h"
19 
20 LOG_MODULE_DECLARE(ISM330DHCX, CONFIG_SENSOR_LOG_LEVEL);
21 
22 #if defined(CONFIG_ISM330DHCX_ENABLE_TEMP)
23 /**
24  * ism330dhcx_enable_t_int - TEMP enable selected int pin to generate interrupt
25  */
ism330dhcx_enable_t_int(const struct device * dev,int enable)26 static int ism330dhcx_enable_t_int(const struct device *dev, int enable)
27 {
28 	const struct ism330dhcx_config *cfg = dev->config;
29 	struct ism330dhcx_data *ism330dhcx = dev->data;
30 	ism330dhcx_pin_int2_route_t int2_route;
31 
32 	if (enable) {
33 		int16_t buf;
34 
35 		/* dummy read: re-trigger interrupt */
36 		ism330dhcx_temperature_raw_get(ism330dhcx->ctx, &buf);
37 	}
38 
39 	/* set interrupt (TEMP DRDY interrupt is only on INT2) */
40 	if (cfg->int_pin == 1) {
41 		return -EIO;
42 	}
43 
44 	ism330dhcx_read_reg(ism330dhcx->ctx, ISM330DHCX_INT2_CTRL,
45 			    (uint8_t *)&int2_route.int2_ctrl, 1);
46 	int2_route.int2_ctrl.int2_drdy_temp = enable;
47 	return ism330dhcx_write_reg(ism330dhcx->ctx, ISM330DHCX_INT2_CTRL,
48 				    (uint8_t *)&int2_route.int2_ctrl, 1);
49 }
50 #endif
51 
52 /**
53  * ism330dhcx_enable_xl_int - XL enable selected int pin to generate interrupt
54  */
ism330dhcx_enable_xl_int(const struct device * dev,int enable)55 static int ism330dhcx_enable_xl_int(const struct device *dev, int enable)
56 {
57 	const struct ism330dhcx_config *cfg = dev->config;
58 	struct ism330dhcx_data *ism330dhcx = dev->data;
59 
60 	if (enable) {
61 		int16_t buf[3];
62 
63 		/* dummy read: re-trigger interrupt */
64 		ism330dhcx_acceleration_raw_get(ism330dhcx->ctx, buf);
65 	}
66 
67 	/* set interrupt */
68 	if (cfg->int_pin == 1) {
69 		ism330dhcx_pin_int1_route_t int1_route;
70 
71 		ism330dhcx_read_reg(ism330dhcx->ctx, ISM330DHCX_INT1_CTRL,
72 				    (uint8_t *)&int1_route.int1_ctrl, 1);
73 
74 		int1_route.int1_ctrl.int1_drdy_xl = enable;
75 		return ism330dhcx_write_reg(ism330dhcx->ctx, ISM330DHCX_INT1_CTRL,
76 					    (uint8_t *)&int1_route.int1_ctrl, 1);
77 	} else {
78 		ism330dhcx_pin_int2_route_t int2_route;
79 
80 		ism330dhcx_read_reg(ism330dhcx->ctx, ISM330DHCX_INT2_CTRL,
81 				    (uint8_t *)&int2_route.int2_ctrl, 1);
82 		int2_route.int2_ctrl.int2_drdy_xl = enable;
83 		return ism330dhcx_write_reg(ism330dhcx->ctx, ISM330DHCX_INT2_CTRL,
84 					    (uint8_t *)&int2_route.int2_ctrl, 1);
85 	}
86 }
87 
88 /**
89  * ism330dhcx_enable_g_int - Gyro enable selected int pin to generate interrupt
90  */
ism330dhcx_enable_g_int(const struct device * dev,int enable)91 static int ism330dhcx_enable_g_int(const struct device *dev, int enable)
92 {
93 	const struct ism330dhcx_config *cfg = dev->config;
94 	struct ism330dhcx_data *ism330dhcx = dev->data;
95 
96 	if (enable) {
97 		int16_t buf[3];
98 
99 		/* dummy read: re-trigger interrupt */
100 		ism330dhcx_angular_rate_raw_get(ism330dhcx->ctx, buf);
101 	}
102 
103 	/* set interrupt */
104 	if (cfg->int_pin == 1) {
105 		ism330dhcx_pin_int1_route_t int1_route;
106 
107 		ism330dhcx_read_reg(ism330dhcx->ctx, ISM330DHCX_INT1_CTRL,
108 				 (uint8_t *)&int1_route.int1_ctrl, 1);
109 		int1_route.int1_ctrl.int1_drdy_g = enable;
110 		return ism330dhcx_write_reg(ism330dhcx->ctx, ISM330DHCX_INT1_CTRL,
111 					    (uint8_t *)&int1_route.int1_ctrl, 1);
112 	} else {
113 		ism330dhcx_pin_int2_route_t int2_route;
114 
115 		ism330dhcx_read_reg(ism330dhcx->ctx, ISM330DHCX_INT2_CTRL,
116 				 (uint8_t *)&int2_route.int2_ctrl, 1);
117 		int2_route.int2_ctrl.int2_drdy_g = enable;
118 		return ism330dhcx_write_reg(ism330dhcx->ctx, ISM330DHCX_INT2_CTRL,
119 					    (uint8_t *)&int2_route.int2_ctrl, 1);
120 	}
121 }
122 
123 /**
124  * ism330dhcx_trigger_set - link external trigger to event data ready
125  */
ism330dhcx_trigger_set(const struct device * dev,const struct sensor_trigger * trig,sensor_trigger_handler_t handler)126 int ism330dhcx_trigger_set(const struct device *dev,
127 			   const struct sensor_trigger *trig,
128 			   sensor_trigger_handler_t handler)
129 {
130 	struct ism330dhcx_data *ism330dhcx = dev->data;
131 	const struct ism330dhcx_config *cfg = dev->config;
132 
133 	if (!cfg->drdy_gpio.port) {
134 		return -ENOTSUP;
135 	}
136 
137 	if (trig->chan == SENSOR_CHAN_ACCEL_XYZ) {
138 		ism330dhcx->handler_drdy_acc = handler;
139 		ism330dhcx->trig_drdy_acc = trig;
140 		if (handler) {
141 			return ism330dhcx_enable_xl_int(dev, ISM330DHCX_EN_BIT);
142 		} else {
143 			return ism330dhcx_enable_xl_int(dev, ISM330DHCX_DIS_BIT);
144 		}
145 	} else if (trig->chan == SENSOR_CHAN_GYRO_XYZ) {
146 		ism330dhcx->handler_drdy_gyr = handler;
147 		ism330dhcx->trig_drdy_gyr = trig;
148 		if (handler) {
149 			return ism330dhcx_enable_g_int(dev, ISM330DHCX_EN_BIT);
150 		} else {
151 			return ism330dhcx_enable_g_int(dev, ISM330DHCX_DIS_BIT);
152 		}
153 	}
154 #if defined(CONFIG_ISM330DHCX_ENABLE_TEMP)
155 	else if (trig->chan == SENSOR_CHAN_DIE_TEMP) {
156 		ism330dhcx->handler_drdy_temp = handler;
157 		ism330dhcx->trig_drdy_temp = trig;
158 		if (handler) {
159 			return ism330dhcx_enable_t_int(dev, ISM330DHCX_EN_BIT);
160 		} else {
161 			return ism330dhcx_enable_t_int(dev, ISM330DHCX_DIS_BIT);
162 		}
163 	}
164 #endif
165 
166 	return -ENOTSUP;
167 }
168 
169 /**
170  * ism330dhcx_handle_interrupt - handle the drdy event
171  * read data and call handler if registered any
172  */
ism330dhcx_handle_interrupt(const struct device * dev)173 static void ism330dhcx_handle_interrupt(const struct device *dev)
174 {
175 	struct ism330dhcx_data *ism330dhcx = dev->data;
176 	const struct ism330dhcx_config *cfg = dev->config;
177 	ism330dhcx_status_reg_t status;
178 
179 	while (1) {
180 		if (ism330dhcx_status_reg_get(ism330dhcx->ctx, &status) < 0) {
181 			LOG_DBG("failed reading status reg");
182 			return;
183 		}
184 
185 		if ((status.xlda == 0) && (status.gda == 0)
186 #if defined(CONFIG_ISM330DHCX_ENABLE_TEMP)
187 					&& (status.tda == 0)
188 #endif
189 					) {
190 			break;
191 		}
192 
193 		if ((status.xlda) && (ism330dhcx->handler_drdy_acc != NULL)) {
194 			ism330dhcx->handler_drdy_acc(dev, ism330dhcx->trig_drdy_acc);
195 		}
196 
197 		if ((status.gda) && (ism330dhcx->handler_drdy_gyr != NULL)) {
198 			ism330dhcx->handler_drdy_gyr(dev, ism330dhcx->trig_drdy_gyr);
199 		}
200 
201 #if defined(CONFIG_ISM330DHCX_ENABLE_TEMP)
202 		if ((status.tda) && (ism330dhcx->handler_drdy_temp != NULL)) {
203 			ism330dhcx->handler_drdy_temp(dev, ism330dhcx->trig_drdy_temp);
204 		}
205 #endif
206 	}
207 
208 	gpio_pin_interrupt_configure_dt(&cfg->drdy_gpio, GPIO_INT_EDGE_TO_ACTIVE);
209 }
210 
ism330dhcx_gpio_callback(const struct device * dev,struct gpio_callback * cb,uint32_t pins)211 static void ism330dhcx_gpio_callback(const struct device *dev,
212 				     struct gpio_callback *cb, uint32_t pins)
213 {
214 	struct ism330dhcx_data *ism330dhcx =
215 		CONTAINER_OF(cb, struct ism330dhcx_data, gpio_cb);
216 	const struct ism330dhcx_config *cfg = ism330dhcx->dev->config;
217 
218 	ARG_UNUSED(pins);
219 
220 	gpio_pin_interrupt_configure_dt(&cfg->drdy_gpio, GPIO_INT_DISABLE);
221 
222 #if defined(CONFIG_ISM330DHCX_TRIGGER_OWN_THREAD)
223 	k_sem_give(&ism330dhcx->gpio_sem);
224 #elif defined(CONFIG_ISM330DHCX_TRIGGER_GLOBAL_THREAD)
225 	k_work_submit(&ism330dhcx->work);
226 #endif /* CONFIG_ISM330DHCX_TRIGGER_OWN_THREAD */
227 }
228 
229 #ifdef CONFIG_ISM330DHCX_TRIGGER_OWN_THREAD
ism330dhcx_thread(void * p1,void * p2,void * p3)230 static void ism330dhcx_thread(void *p1, void *p2, void *p3)
231 {
232 	ARG_UNUSED(p2);
233 	ARG_UNUSED(p3);
234 
235 	struct ism330dhcx_data *ism330dhcx = p1;
236 
237 	while (1) {
238 		k_sem_take(&ism330dhcx->gpio_sem, K_FOREVER);
239 		ism330dhcx_handle_interrupt(ism330dhcx->dev);
240 	}
241 }
242 #endif /* CONFIG_ISM330DHCX_TRIGGER_OWN_THREAD */
243 
244 #ifdef CONFIG_ISM330DHCX_TRIGGER_GLOBAL_THREAD
ism330dhcx_work_cb(struct k_work * work)245 static void ism330dhcx_work_cb(struct k_work *work)
246 {
247 	struct ism330dhcx_data *ism330dhcx =
248 		CONTAINER_OF(work, struct ism330dhcx_data, work);
249 
250 	ism330dhcx_handle_interrupt(ism330dhcx->dev);
251 }
252 #endif /* CONFIG_ISM330DHCX_TRIGGER_GLOBAL_THREAD */
253 
ism330dhcx_init_interrupt(const struct device * dev)254 int ism330dhcx_init_interrupt(const struct device *dev)
255 {
256 	struct ism330dhcx_data *ism330dhcx = dev->data;
257 	const struct ism330dhcx_config *cfg = dev->config;
258 	int ret;
259 
260 	if (!gpio_is_ready_dt(&cfg->drdy_gpio)) {
261 		LOG_ERR("GPIO device not ready");
262 		return -ENODEV;
263 	}
264 
265 #if defined(CONFIG_ISM330DHCX_TRIGGER_OWN_THREAD)
266 	k_sem_init(&ism330dhcx->gpio_sem, 0, K_SEM_MAX_LIMIT);
267 
268 	k_thread_create(&ism330dhcx->thread, ism330dhcx->thread_stack,
269 			CONFIG_ISM330DHCX_THREAD_STACK_SIZE,
270 			ism330dhcx_thread,
271 			ism330dhcx, NULL, NULL,
272 			K_PRIO_COOP(CONFIG_ISM330DHCX_THREAD_PRIORITY),
273 			0, K_NO_WAIT);
274 #elif defined(CONFIG_ISM330DHCX_TRIGGER_GLOBAL_THREAD)
275 	ism330dhcx->work.handler = ism330dhcx_work_cb;
276 #endif /* CONFIG_ISM330DHCX_TRIGGER_OWN_THREAD */
277 
278 	ret = gpio_pin_configure_dt(&cfg->drdy_gpio, GPIO_INPUT);
279 	if (ret < 0) {
280 		LOG_ERR("Could not configure gpio");
281 		return ret;
282 	}
283 
284 	gpio_init_callback(&ism330dhcx->gpio_cb, ism330dhcx_gpio_callback, BIT(cfg->drdy_gpio.pin));
285 
286 	if (gpio_add_callback(cfg->drdy_gpio.port, &ism330dhcx->gpio_cb) < 0) {
287 		LOG_ERR("Could not set gpio callback");
288 		return -EIO;
289 	}
290 
291 	/* enable interrupt on int1/int2 in pulse mode */
292 	if (ism330dhcx_data_ready_mode_set(ism330dhcx->ctx,
293 					   ISM330DHCX_DRDY_PULSED) < 0) {
294 		LOG_ERR("Could not set pulse mode");
295 		return -EIO;
296 	}
297 
298 	return gpio_pin_interrupt_configure_dt(&cfg->drdy_gpio, GPIO_INT_EDGE_TO_ACTIVE);
299 }
300