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(struct vcnl4040_data * data)89 static void vcnl4040_thread_main(struct vcnl4040_data *data)
90 {
91 	while (true) {
92 		k_sem_take(&data->trig_sem, K_FOREVER);
93 		vcnl4040_handle_int(data->dev);
94 	}
95 }
96 #endif
97 
98 #ifdef CONFIG_VCNL4040_TRIGGER_GLOBAL_THREAD
vcnl4040_work_handler(struct k_work * work)99 static void vcnl4040_work_handler(struct k_work *work)
100 {
101 	struct vcnl4040_data *data =
102 		CONTAINER_OF(work, struct vcnl4040_data, work);
103 
104 	vcnl4040_handle_int(data->dev);
105 }
106 #endif
107 
vcnl4040_attr_set(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,const struct sensor_value * val)108 int vcnl4040_attr_set(const struct device *dev,
109 		      enum sensor_channel chan,
110 		      enum sensor_attribute attr,
111 		      const struct sensor_value *val)
112 {
113 	struct vcnl4040_data *data = dev->data;
114 	const struct vcnl4040_config *config = dev->config;
115 	int ret = 0;
116 
117 	if (config->int_gpio.port == NULL) {
118 		return -ENOTSUP;
119 	}
120 
121 	k_mutex_lock(&data->mutex, K_FOREVER);
122 
123 	if (chan == SENSOR_CHAN_PROX) {
124 		if (attr == SENSOR_ATTR_UPPER_THRESH) {
125 			if (vcnl4040_write(dev, VCNL4040_REG_PS_THDH,
126 					   (uint16_t)val->val1)) {
127 				ret = -EIO;
128 				goto exit;
129 			}
130 
131 			ret = 0;
132 			goto exit;
133 		}
134 		if (attr == SENSOR_ATTR_LOWER_THRESH) {
135 			if (vcnl4040_write(dev, VCNL4040_REG_PS_THDL,
136 					   (uint16_t)val->val1)) {
137 				ret = -EIO;
138 				goto exit;
139 			}
140 
141 			ret = 0;
142 			goto exit;
143 		}
144 	}
145 #ifdef CONFIG_VCNL4040_ENABLE_ALS
146 	if (chan == SENSOR_CHAN_LIGHT) {
147 		if (attr == SENSOR_ATTR_UPPER_THRESH) {
148 			if (vcnl4040_write(dev, VCNL4040_REG_ALS_THDH,
149 					   (uint16_t)val->val1)) {
150 				ret = -EIO;
151 				goto exit;
152 			}
153 
154 			ret = 0;
155 			goto exit;
156 		}
157 		if (attr == SENSOR_ATTR_LOWER_THRESH) {
158 			if (vcnl4040_write(dev, VCNL4040_REG_ALS_THDL,
159 					   (uint16_t)val->val1)) {
160 				ret = -EIO;
161 				goto exit;
162 			}
163 
164 			ret = 0;
165 			goto exit;
166 		}
167 	}
168 #endif
169 	ret = -ENOTSUP;
170 exit:
171 	k_mutex_unlock(&data->mutex);
172 
173 	return ret;
174 }
175 
vcnl4040_trigger_set(const struct device * dev,const struct sensor_trigger * trig,sensor_trigger_handler_t handler)176 int vcnl4040_trigger_set(const struct device *dev,
177 			 const struct sensor_trigger *trig,
178 			 sensor_trigger_handler_t handler)
179 {
180 	const struct vcnl4040_config *config = dev->config;
181 	struct vcnl4040_data *data = dev->data;
182 	uint16_t conf;
183 	int ret = 0;
184 
185 	if (config->int_gpio.port == NULL) {
186 		return -ENOTSUP;
187 	}
188 
189 	k_mutex_lock(&data->mutex, K_FOREVER);
190 
191 	switch (trig->type) {
192 	case SENSOR_TRIG_THRESHOLD:
193 		if (trig->chan == SENSOR_CHAN_PROX) {
194 			if (vcnl4040_read(dev, VCNL4040_REG_PS_CONF, &conf)) {
195 				ret = -EIO;
196 				goto exit;
197 			}
198 
199 			/* Set interrupt bits 1:0 of high register */
200 			conf |= config->proxy_type << 8;
201 
202 			if (vcnl4040_write(dev, VCNL4040_REG_PS_CONF, conf)) {
203 				ret = -EIO;
204 				goto exit;
205 			}
206 
207 			data->proxy_handler = handler;
208 			data->proxy_trigger = trig;
209 		} else if (trig->chan == SENSOR_CHAN_LIGHT) {
210 #ifdef CONFIG_VCNL4040_ENABLE_ALS
211 			if (vcnl4040_read(dev, VCNL4040_REG_ALS_CONF, &conf)) {
212 				ret = -EIO;
213 				goto exit;
214 			}
215 
216 			/* ALS interrupt enable */
217 			conf |= VCNL4040_ALS_INT_EN_MASK;
218 
219 			if (vcnl4040_write(dev, VCNL4040_REG_ALS_CONF, conf)) {
220 				ret = -EIO;
221 				goto exit;
222 			}
223 
224 			data->als_handler = handler;
225 			data->als_trigger = trig;
226 #else
227 			ret = -ENOTSUP;
228 			goto exit;
229 #endif
230 		} else {
231 			ret = -ENOTSUP;
232 			goto exit;
233 		}
234 		break;
235 	default:
236 		LOG_ERR("Unsupported sensor trigger");
237 		ret = -ENOTSUP;
238 		goto exit;
239 	}
240 exit:
241 	k_mutex_unlock(&data->mutex);
242 
243 	return ret;
244 }
245 
vcnl4040_trigger_init(const struct device * dev)246 int vcnl4040_trigger_init(const struct device *dev)
247 {
248 	const struct vcnl4040_config *config = dev->config;
249 	struct vcnl4040_data *data = dev->data;
250 
251 	data->dev = dev;
252 
253 	if (config->int_gpio.port == NULL) {
254 		LOG_DBG("instance '%s' doesn't support trigger mode", dev->name);
255 		return 0;
256 	}
257 
258 	/* Get the GPIO device */
259 	if (!gpio_is_ready_dt(&config->int_gpio)) {
260 		LOG_ERR("%s: device %s is not ready", dev->name,
261 				config->int_gpio.port->name);
262 		return -ENODEV;
263 	}
264 
265 #if defined(CONFIG_VCNL4040_TRIGGER_OWN_THREAD)
266 	k_sem_init(&data->trig_sem, 0, K_SEM_MAX_LIMIT);
267 	k_thread_create(&data->thread, data->thread_stack,
268 			CONFIG_VCNL4040_THREAD_STACK_SIZE,
269 			(k_thread_entry_t)vcnl4040_thread_main,
270 			data, NULL, NULL,
271 			K_PRIO_COOP(CONFIG_VCNL4040_THREAD_PRIORITY),
272 			0, K_NO_WAIT);
273 	k_thread_name_set(&data->thread, "VCNL4040 trigger");
274 #elif defined(CONFIG_VCNL4040_TRIGGER_GLOBAL_THREAD)
275 	data->work.handler = vcnl4040_work_handler;
276 #endif
277 
278 	gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT);
279 
280 	gpio_init_callback(&data->gpio_cb, vcnl4040_gpio_callback,
281 			   BIT(config->int_gpio.pin));
282 
283 	if (gpio_add_callback(config->int_gpio.port, &data->gpio_cb) < 0) {
284 		LOG_ERR("Failed to set gpio callback");
285 		return -EIO;
286 	}
287 
288 	gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_EDGE_TO_ACTIVE);
289 
290 	if (gpio_pin_get_dt(&config->int_gpio) > 0) {
291 		vcnl4040_handle_cb(data);
292 	}
293 
294 	return 0;
295 }
296