1 /*
2 * Copyright (c) 2024 deveritec GmbH
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT vishay_vcnl36825t
8
9 #include "vcnl36825t.h"
10
11 #include <zephyr/device.h>
12 #include <zephyr/kernel.h>
13
14 #include <zephyr/drivers/gpio.h>
15 #include <zephyr/drivers/i2c.h>
16 #include <zephyr/drivers/sensor.h>
17
18 #include <zephyr/sys/byteorder.h>
19 #include <zephyr/sys/check.h>
20
21 #include <zephyr/logging/log.h>
22 LOG_MODULE_DECLARE(VCNL36825T, CONFIG_SENSOR_LOG_LEVEL);
23
vcnl36825t_trigger_attr_set(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,const struct sensor_value * val)24 int vcnl36825t_trigger_attr_set(const struct device *dev, enum sensor_channel chan,
25 enum sensor_attribute attr, const struct sensor_value *val)
26 {
27 CHECKIF(dev == NULL) {
28 return -EINVAL;
29 }
30
31 CHECKIF(val == NULL) {
32 return -EINVAL;
33 }
34
35 const struct vcnl36825t_config *config = dev->config;
36 int rc;
37 uint16_t reg_addr;
38
39 switch (attr) {
40 case SENSOR_ATTR_UPPER_THRESH:
41 reg_addr = VCNL36825T_REG_PS_THDH;
42 break;
43 case SENSOR_ATTR_LOWER_THRESH:
44 reg_addr = VCNL36825T_REG_PS_THDL;
45 break;
46 default:
47 LOG_ERR("unknown attribute %d", (int)attr);
48 return -ENOTSUP;
49 }
50
51 rc = vcnl36825t_write(&config->i2c, reg_addr, (uint16_t)val->val1);
52 if (rc < 0) {
53 LOG_ERR("error writing attribute %d", attr);
54 return rc;
55 }
56
57 return 0;
58 }
59
60 /**
61 * @brief callback called if a GPIO-interrupt is registered
62 *
63 * @param port device struct for the GPIO device
64 * @param cb @ref struct gpio_callback owning this handler
65 * @param pins mask of pins that triggered the callback handler
66 */
vcnl36825t_gpio_callback(const struct device * port,struct gpio_callback * cb,uint32_t pins)67 static void vcnl36825t_gpio_callback(const struct device *port, struct gpio_callback *cb,
68 uint32_t pins)
69 {
70 ARG_UNUSED(port);
71 ARG_UNUSED(pins);
72
73 CHECKIF(!cb) {
74 LOG_ERR("cb: NULL");
75 return;
76 }
77 struct vcnl36825t_data *data = CONTAINER_OF(cb, struct vcnl36825t_data, int_gpio_handler);
78 int rc;
79
80 rc = gpio_pin_interrupt_configure_dt(data->int_gpio, GPIO_INT_DISABLE);
81 if (rc != 0) {
82 LOG_ERR("error deactivating SoC interrupt %d", rc);
83 }
84
85 #if CONFIG_VCNL36825T_TRIGGER_OWN_THREAD
86 k_sem_give(&data->int_gpio_sem);
87 #elif CONFIG_VCNL36825T_TRIGGER_GLOBAL_THREAD
88 k_work_submit(&data->int_work);
89 #endif
90 }
91
vcnl36825t_thread_cb(const struct device * dev)92 static void vcnl36825t_thread_cb(const struct device *dev)
93 {
94 const struct vcnl36825t_config *config = dev->config;
95 struct vcnl36825t_data *data = dev->data;
96 int rc;
97 uint16_t reg_value;
98
99 if (data->int_handler != NULL) {
100 data->int_handler(dev, data->int_trigger);
101 }
102
103 rc = gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_EDGE_FALLING);
104 if (rc < 0) {
105 LOG_ERR("error activating SoC interrupt %d", rc);
106 return;
107 }
108
109 rc = vcnl36825t_read(&config->i2c, VCNL36825T_REG_INT_FLAG, ®_value);
110 if (rc < 0) {
111 LOG_ERR("error reading interrupt flag register %d", rc);
112 return;
113 }
114
115 if (FIELD_GET(VCNL36825T_PS_IF_AWAY_MSK, reg_value) == 1) {
116 LOG_INF("\"away\" trigger (PS below THDL)");
117 } else if (FIELD_GET(VCNL36825T_PS_IF_CLOSE_MSK, reg_value) == 1) {
118 LOG_INF("\"close\" trigger (PS above THDH)");
119 } else if (FIELD_GET(VCNL36825T_PS_SPFLAG_MSK, reg_value) == 1) {
120 LOG_INF("enter protection mode trigger");
121 } else if (FIELD_GET(VCNL36825T_PS_ACFLAG_MSK, reg_value) == 1) {
122 LOG_INF("finished auto calibration trigger");
123 }
124 }
125
126 #if CONFIG_VCNL36825T_TRIGGER_OWN_THREAD
vcnl36825t_thread_main(void * p1,void * p2,void * p3)127 static void vcnl36825t_thread_main(void *p1, void *p2, void *p3)
128 {
129 ARG_UNUSED(p2);
130 ARG_UNUSED(p3);
131
132 struct vcnl36825t_data *data = p1;
133
134 while (1) {
135 k_sem_take(&data->int_gpio_sem, K_FOREVER);
136 vcnl36825t_thread_cb(data->dev);
137 }
138 }
139 #endif
140
141 #if CONFIG_VCNL36825T_TRIGGER_GLOBAL_THREAD
vcnl36825t_work_cb(struct k_work * work)142 static void vcnl36825t_work_cb(struct k_work *work)
143 {
144 struct vcnl36825t_data *data = CONTAINER_OF(work, struct vcnl36825t_data, int_work);
145
146 vcnl36825t_thread_cb(data->dev);
147 }
148 #endif
149
vcnl36825t_trigger_set(const struct device * dev,const struct sensor_trigger * trig,sensor_trigger_handler_t handler)150 int vcnl36825t_trigger_set(const struct device *dev, const struct sensor_trigger *trig,
151 sensor_trigger_handler_t handler)
152 {
153 CHECKIF(dev == NULL) {
154 LOG_ERR("dev: NULL");
155 return -EINVAL;
156 }
157
158 CHECKIF(trig == NULL) {
159 LOG_ERR("trig: NULL");
160 return -EINVAL;
161 }
162
163 const struct vcnl36825t_config *config = dev->config;
164 struct vcnl36825t_data *data = dev->data;
165
166 int rc;
167 uint16_t regdata;
168
169 if (trig->chan != SENSOR_CHAN_PROX) {
170 LOG_ERR("invalid channel %d", (int)trig->chan);
171 return -ENOTSUP;
172 }
173
174 if (trig->type != SENSOR_TRIG_THRESHOLD) {
175 LOG_ERR("invalid trigger type %d", (int)trig->type);
176 return -ENOTSUP;
177 }
178
179 rc = gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_DISABLE);
180 if (rc < 0) {
181 LOG_ERR("error configuring SoC interrupt %d", rc);
182 return rc;
183 }
184
185 data->int_trigger = trig;
186 data->int_handler = handler;
187
188 if (handler == NULL) {
189 regdata = VCNL36825T_PS_INT_DISABLE;
190 } else {
191 switch (config->int_mode) {
192 case VCNL36825T_INT_MODE_NORMAL:
193 regdata = VCNL36825T_PS_INT_MODE_NORMAL;
194 break;
195 case VCNL36825T_INT_MODE_FIRST_HIGH:
196 regdata = VCNL36825T_PS_INT_MODE_FIRST_HIGH;
197 break;
198 case VCNL36825T_INT_MODE_LOGIC_HIGH_LOW:
199 regdata = VCNL36825T_PS_INT_MODE_LOGIC_HIGH_LOW;
200 break;
201 default:
202 LOG_ERR("unknown interrupt mode");
203 return -ENOTSUP;
204 }
205 }
206
207 rc = vcnl36825t_update(&config->i2c, VCNL36825T_REG_PS_CONF2, VCNL36825T_PS_INT_MSK,
208 regdata);
209 if (rc < 0) {
210 LOG_ERR("error updating interrupt %d", rc);
211 return rc;
212 }
213
214 if (handler) {
215 rc = gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_EDGE_FALLING);
216 if (rc < 0) {
217 LOG_ERR("error configuring SoC interrupt %d", rc);
218 return rc;
219 }
220
221 /* read interrupt register one time to clear any pending interrupt */
222 rc = vcnl36825t_read(&config->i2c, VCNL36825T_REG_INT_FLAG, ®data);
223 if (rc < 0) {
224 LOG_ERR("error clearing interrupt flag register %d", rc);
225 return rc;
226 }
227 }
228
229 return 0;
230 }
231
vcnl36825t_trigger_init(const struct device * dev)232 int vcnl36825t_trigger_init(const struct device *dev)
233 {
234 CHECKIF(dev == NULL) {
235 LOG_ERR("dev: NULL");
236 return -EINVAL;
237 }
238
239 const struct vcnl36825t_config *config = dev->config;
240 struct vcnl36825t_data *data = dev->data;
241
242 int rc;
243 uint8_t reg_value;
244
245 if (!gpio_is_ready_dt(&config->int_gpio)) {
246 LOG_ERR("interrupt GPIO not ready");
247 return -ENODEV;
248 }
249
250 data->dev = dev;
251 data->int_gpio = &config->int_gpio;
252
253 rc = gpio_pin_configure_dt(data->int_gpio, GPIO_INPUT);
254 if (rc < 0) {
255 LOG_ERR("error setting interrupt gpio configuration %d", rc);
256 return rc;
257 }
258
259 /* PS_CONF2 */
260 reg_value = VCNL36825T_PS_INT_DISABLE;
261
262 if (config->int_smart_persistence) {
263 reg_value |= VCNL36825T_PS_SMART_PERS_ENABLED;
264 }
265
266 switch (config->int_proximity_count) {
267 case 1:
268 reg_value |= VCNL36825T_PS_PERS_1;
269 break;
270 case 2:
271 reg_value |= VCNL36825T_PS_PERS_2;
272 break;
273 case 3:
274 reg_value |= VCNL36825T_PS_PERS_3;
275 break;
276 case 4:
277 __fallthrough;
278 default:
279 reg_value |= VCNL36825T_PS_PERS_4;
280 }
281
282 rc = vcnl36825t_update(
283 &config->i2c, VCNL36825T_REG_PS_CONF2,
284 (VCNL36825T_PS_SMART_PERS_MSK | VCNL36825T_PS_INT_MSK | VCNL36825T_PS_PERS_MSK),
285 reg_value);
286 if (rc < 0) {
287 LOG_ERR("could not write interrupt configuration %d", rc);
288 return rc;
289 }
290
291 #if CONFIG_VCNL36825T_TRIGGER_OWN_THREAD
292 k_sem_init(&data->int_gpio_sem, 0, K_SEM_MAX_LIMIT);
293
294 k_thread_create(&data->int_thread, data->int_thread_stack,
295 CONFIG_VCNL36825T_THREAD_STACK_SIZE, vcnl36825t_thread_main, data, NULL,
296 NULL, K_PRIO_COOP(CONFIG_VCNL36825T_THREAD_PRIORITY), 0, K_NO_WAIT);
297 #elif CONFIG_VCNL36825T_TRIGGER_GLOBAL_THREAD
298 k_work_init(&data->int_work, vcnl36825t_work_cb);
299 #else
300 #error "invalid interrupt threading configuration"
301 #endif
302
303 gpio_init_callback(&data->int_gpio_handler, vcnl36825t_gpio_callback,
304 BIT(config->int_gpio.pin));
305
306 rc = gpio_add_callback(config->int_gpio.port, &data->int_gpio_handler);
307 if (rc < 0) {
308 LOG_ERR("could not set gpio callback %d", rc);
309 return rc;
310 }
311
312 rc = gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_DISABLE);
313 if (rc < 0) {
314 LOG_ERR("could not set SoC interrupt configuration %d", rc);
315 return rc;
316 }
317
318 return 0;
319 }
320