1 /*
2  * Copyright (c) 2020 Richard Osterloh
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include "vcnl4040.h"
8 #include <zephyr/logging/log.h>
9 
10 LOG_MODULE_DECLARE(vcnl4040, CONFIG_SENSOR_LOG_LEVEL);
11 
vcnl4040_handle_cb(struct vcnl4040_data * data)12 static void vcnl4040_handle_cb(struct vcnl4040_data *data)
13 {
14 	const struct vcnl4040_config *config = data->dev->config;
15 
16 	gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_DISABLE);
17 
18 #if defined(CONFIG_VCNL4040_TRIGGER_OWN_THREAD)
19 	k_sem_give(&data->trig_sem);
20 #elif defined(CONFIG_VCNL4040_TRIGGER_GLOBAL_THREAD)
21 	k_work_submit(&data->work);
22 #endif
23 }
24 
vcnl4040_gpio_callback(const struct device * dev,struct gpio_callback * cb,uint32_t pin_mask)25 static void vcnl4040_gpio_callback(const struct device *dev,
26 				   struct gpio_callback *cb,
27 				   uint32_t pin_mask)
28 {
29 	struct vcnl4040_data *data =
30 		CONTAINER_OF(cb, struct vcnl4040_data, gpio_cb);
31 	const struct vcnl4040_config *config = data->dev->config;
32 
33 	if ((pin_mask & BIT(config->int_gpio.pin)) == 0U) {
34 		return;
35 	}
36 
37 	vcnl4040_handle_cb(data);
38 }
39 
vcnl4040_handle_proxy_int(const struct device * dev)40 static int vcnl4040_handle_proxy_int(const struct device *dev)
41 {
42 	struct vcnl4040_data *data = dev->data;
43 
44 	if (data->proxy_handler != NULL) {
45 		data->proxy_handler(dev, data->proxy_trigger);
46 	}
47 
48 	return 0;
49 }
50 
vcnl4040_handle_als_int(const struct device * dev)51 static int vcnl4040_handle_als_int(const struct device *dev)
52 {
53 	struct vcnl4040_data *data = dev->data;
54 
55 	if (data->als_handler != NULL) {
56 		data->als_handler(dev, data->als_trigger);
57 	}
58 
59 	return 0;
60 }
61 
vcnl4040_handle_int(const struct device * dev)62 static void vcnl4040_handle_int(const struct device *dev)
63 {
64 	const struct vcnl4040_config *config = dev->config;
65 	struct vcnl4040_data *data = dev->data;
66 	uint16_t int_source;
67 
68 	if (vcnl4040_read(dev, VCNL4040_REG_INT_FLAG, &int_source)) {
69 		LOG_ERR("Could not read interrupt source");
70 		int_source = 0U;
71 	}
72 
73 	data->int_type = int_source >> 8;
74 
75 	if (data->int_type == VCNL4040_PROXIMITY_AWAY ||
76 	    data->int_type == VCNL4040_PROXIMITY_CLOSE) {
77 		vcnl4040_handle_proxy_int(dev);
78 	} else if (data->int_type == VCNL4040_AMBIENT_HIGH ||
79 		   data->int_type == VCNL4040_AMBIENT_LOW) {
80 		vcnl4040_handle_als_int(dev);
81 	} else {
82 		LOG_ERR("Unknown interrupt source %d", data->int_type);
83 	}
84 
85 	gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_EDGE_TO_ACTIVE);
86 }
87 
88 #ifdef CONFIG_VCNL4040_TRIGGER_OWN_THREAD
vcnl4040_thread_main(void * p1,void * p2,void * p3)89 static void vcnl4040_thread_main(void *p1, void *p2, void *p3)
90 {
91 	ARG_UNUSED(p2);
92 	ARG_UNUSED(p3);
93 
94 	struct vcnl4040_data *data = p1;
95 
96 	while (true) {
97 		k_sem_take(&data->trig_sem, K_FOREVER);
98 		vcnl4040_handle_int(data->dev);
99 	}
100 }
101 #endif
102 
103 #ifdef CONFIG_VCNL4040_TRIGGER_GLOBAL_THREAD
vcnl4040_work_handler(struct k_work * work)104 static void vcnl4040_work_handler(struct k_work *work)
105 {
106 	struct vcnl4040_data *data =
107 		CONTAINER_OF(work, struct vcnl4040_data, work);
108 
109 	vcnl4040_handle_int(data->dev);
110 }
111 #endif
112 
vcnl4040_attr_set(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,const struct sensor_value * val)113 int vcnl4040_attr_set(const struct device *dev,
114 		      enum sensor_channel chan,
115 		      enum sensor_attribute attr,
116 		      const struct sensor_value *val)
117 {
118 	struct vcnl4040_data *data = dev->data;
119 	const struct vcnl4040_config *config = dev->config;
120 	int ret = 0;
121 
122 	if (config->int_gpio.port == NULL) {
123 		return -ENOTSUP;
124 	}
125 
126 	k_mutex_lock(&data->mutex, K_FOREVER);
127 
128 	if (chan == SENSOR_CHAN_PROX) {
129 		if (attr == SENSOR_ATTR_UPPER_THRESH) {
130 			if (vcnl4040_write(dev, VCNL4040_REG_PS_THDH,
131 					   (uint16_t)val->val1)) {
132 				ret = -EIO;
133 				goto exit;
134 			}
135 
136 			ret = 0;
137 			goto exit;
138 		}
139 		if (attr == SENSOR_ATTR_LOWER_THRESH) {
140 			if (vcnl4040_write(dev, VCNL4040_REG_PS_THDL,
141 					   (uint16_t)val->val1)) {
142 				ret = -EIO;
143 				goto exit;
144 			}
145 
146 			ret = 0;
147 			goto exit;
148 		}
149 	}
150 #ifdef CONFIG_VCNL4040_ENABLE_ALS
151 	if (chan == SENSOR_CHAN_LIGHT) {
152 		if (attr == SENSOR_ATTR_UPPER_THRESH) {
153 			if (vcnl4040_write(dev, VCNL4040_REG_ALS_THDH,
154 					   (uint16_t)val->val1)) {
155 				ret = -EIO;
156 				goto exit;
157 			}
158 
159 			ret = 0;
160 			goto exit;
161 		}
162 		if (attr == SENSOR_ATTR_LOWER_THRESH) {
163 			if (vcnl4040_write(dev, VCNL4040_REG_ALS_THDL,
164 					   (uint16_t)val->val1)) {
165 				ret = -EIO;
166 				goto exit;
167 			}
168 
169 			ret = 0;
170 			goto exit;
171 		}
172 	}
173 #endif
174 	ret = -ENOTSUP;
175 exit:
176 	k_mutex_unlock(&data->mutex);
177 
178 	return ret;
179 }
180 
vcnl4040_trigger_set(const struct device * dev,const struct sensor_trigger * trig,sensor_trigger_handler_t handler)181 int vcnl4040_trigger_set(const struct device *dev,
182 			 const struct sensor_trigger *trig,
183 			 sensor_trigger_handler_t handler)
184 {
185 	const struct vcnl4040_config *config = dev->config;
186 	struct vcnl4040_data *data = dev->data;
187 	uint16_t conf;
188 	int ret = 0;
189 
190 	if (config->int_gpio.port == NULL) {
191 		return -ENOTSUP;
192 	}
193 
194 	k_mutex_lock(&data->mutex, K_FOREVER);
195 
196 	switch (trig->type) {
197 	case SENSOR_TRIG_THRESHOLD:
198 		if (trig->chan == SENSOR_CHAN_PROX) {
199 			if (vcnl4040_read(dev, VCNL4040_REG_PS_CONF, &conf)) {
200 				ret = -EIO;
201 				goto exit;
202 			}
203 
204 			/* Set interrupt bits 1:0 of high register */
205 			conf |= config->proxy_type << 8;
206 
207 			if (vcnl4040_write(dev, VCNL4040_REG_PS_CONF, conf)) {
208 				ret = -EIO;
209 				goto exit;
210 			}
211 
212 			data->proxy_handler = handler;
213 			data->proxy_trigger = trig;
214 		} else if (trig->chan == SENSOR_CHAN_LIGHT) {
215 #ifdef CONFIG_VCNL4040_ENABLE_ALS
216 			if (vcnl4040_read(dev, VCNL4040_REG_ALS_CONF, &conf)) {
217 				ret = -EIO;
218 				goto exit;
219 			}
220 
221 			/* ALS interrupt enable */
222 			conf |= VCNL4040_ALS_INT_EN_MASK;
223 
224 			if (vcnl4040_write(dev, VCNL4040_REG_ALS_CONF, conf)) {
225 				ret = -EIO;
226 				goto exit;
227 			}
228 
229 			data->als_handler = handler;
230 			data->als_trigger = trig;
231 #else
232 			ret = -ENOTSUP;
233 			goto exit;
234 #endif
235 		} else {
236 			ret = -ENOTSUP;
237 			goto exit;
238 		}
239 		break;
240 	default:
241 		LOG_ERR("Unsupported sensor trigger");
242 		ret = -ENOTSUP;
243 		goto exit;
244 	}
245 exit:
246 	k_mutex_unlock(&data->mutex);
247 
248 	return ret;
249 }
250 
vcnl4040_trigger_init(const struct device * dev)251 int vcnl4040_trigger_init(const struct device *dev)
252 {
253 	const struct vcnl4040_config *config = dev->config;
254 	struct vcnl4040_data *data = dev->data;
255 
256 	data->dev = dev;
257 
258 	if (config->int_gpio.port == NULL) {
259 		LOG_DBG("instance '%s' doesn't support trigger mode", dev->name);
260 		return 0;
261 	}
262 
263 	/* Get the GPIO device */
264 	if (!gpio_is_ready_dt(&config->int_gpio)) {
265 		LOG_ERR("%s: device %s is not ready", dev->name,
266 				config->int_gpio.port->name);
267 		return -ENODEV;
268 	}
269 
270 #if defined(CONFIG_VCNL4040_TRIGGER_OWN_THREAD)
271 	k_sem_init(&data->trig_sem, 0, K_SEM_MAX_LIMIT);
272 	k_thread_create(&data->thread, data->thread_stack,
273 			CONFIG_VCNL4040_THREAD_STACK_SIZE,
274 			vcnl4040_thread_main,
275 			data, NULL, NULL,
276 			K_PRIO_COOP(CONFIG_VCNL4040_THREAD_PRIORITY),
277 			0, K_NO_WAIT);
278 	k_thread_name_set(&data->thread, "VCNL4040 trigger");
279 #elif defined(CONFIG_VCNL4040_TRIGGER_GLOBAL_THREAD)
280 	data->work.handler = vcnl4040_work_handler;
281 #endif
282 
283 	gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT);
284 
285 	gpio_init_callback(&data->gpio_cb, vcnl4040_gpio_callback,
286 			   BIT(config->int_gpio.pin));
287 
288 	if (gpio_add_callback(config->int_gpio.port, &data->gpio_cb) < 0) {
289 		LOG_ERR("Failed to set gpio callback");
290 		return -EIO;
291 	}
292 
293 	gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_EDGE_TO_ACTIVE);
294 
295 	if (gpio_pin_get_dt(&config->int_gpio) > 0) {
296 		vcnl4040_handle_cb(data);
297 	}
298 
299 	return 0;
300 }
301