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, &reg_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, &regdata);
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