1 /*
2  * Copyright (c) 2020 Richard Osterloh
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT vishay_vcnl4040
8 
9 #include "vcnl4040.h"
10 #include <zephyr/pm/device.h>
11 #include <zephyr/sys/__assert.h>
12 #include <zephyr/sys/byteorder.h>
13 #include <zephyr/sys/util.h>
14 #include <zephyr/logging/log.h>
15 #include <stdlib.h>
16 
17 LOG_MODULE_REGISTER(vcnl4040, CONFIG_SENSOR_LOG_LEVEL);
18 
vcnl4040_read(const struct device * dev,uint8_t reg,uint16_t * out)19 int vcnl4040_read(const struct device *dev, uint8_t reg, uint16_t *out)
20 {
21 	const struct vcnl4040_config *config = dev->config;
22 	uint8_t buff[2] = { 0 };
23 	int ret = 0;
24 
25 	ret = i2c_write_read_dt(&config->i2c,
26 			     &reg, sizeof(reg), buff, sizeof(buff));
27 
28 	if (ret == 0) {
29 		*out = sys_get_le16(buff);
30 	}
31 
32 	return ret;
33 }
34 
vcnl4040_write(const struct device * dev,uint8_t reg,uint16_t value)35 int vcnl4040_write(const struct device *dev, uint8_t reg, uint16_t value)
36 {
37 	const struct vcnl4040_config *config = dev->config;
38 	uint8_t buf[3];
39 	int ret;
40 
41 	buf[0] = reg;
42 	sys_put_le16(value, &buf[1]);
43 
44 	ret = i2c_write_dt(&config->i2c, buf, sizeof(buf));
45 	if (ret < 0) {
46 		LOG_ERR("write[%02X]: %u", reg, ret);
47 		return ret;
48 	}
49 
50 	return 0;
51 }
52 
vcnl4040_sample_fetch(const struct device * dev,enum sensor_channel chan)53 static int vcnl4040_sample_fetch(const struct device *dev,
54 				 enum sensor_channel chan)
55 {
56 	struct vcnl4040_data *data = dev->data;
57 	int ret = 0;
58 
59 #ifdef CONFIG_VCNL4040_ENABLE_ALS
60 	__ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL ||
61 			chan == SENSOR_CHAN_PROX ||
62 			chan == SENSOR_CHAN_LIGHT);
63 #else
64 	__ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_PROX);
65 #endif
66 	k_mutex_lock(&data->mutex, K_FOREVER);
67 
68 	if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_PROX) {
69 		ret = vcnl4040_read(dev, VCNL4040_REG_PS_DATA,
70 				    &data->proximity);
71 		if (ret < 0) {
72 			LOG_ERR("Could not fetch proximity");
73 			goto exit;
74 		}
75 	}
76 #ifdef CONFIG_VCNL4040_ENABLE_ALS
77 	if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_LIGHT) {
78 		ret = vcnl4040_read(dev, VCNL4040_REG_ALS_DATA,
79 				    &data->light);
80 		if (ret < 0) {
81 			LOG_ERR("Could not fetch ambient light");
82 		}
83 	}
84 #endif
85 exit:
86 	k_mutex_unlock(&data->mutex);
87 
88 	return ret;
89 }
90 
vcnl4040_channel_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)91 static int vcnl4040_channel_get(const struct device *dev,
92 				enum sensor_channel chan,
93 				struct sensor_value *val)
94 {
95 	struct vcnl4040_data *data = dev->data;
96 	int ret = 0;
97 
98 	k_mutex_lock(&data->mutex, K_FOREVER);
99 
100 	switch (chan) {
101 	case SENSOR_CHAN_PROX:
102 		val->val1 = data->proximity;
103 		val->val2 = 0;
104 		break;
105 
106 #ifdef CONFIG_VCNL4040_ENABLE_ALS
107 	case SENSOR_CHAN_LIGHT:
108 		val->val1 = data->light * data->sensitivity;
109 		val->val2 = 0;
110 		break;
111 #endif
112 
113 	default:
114 		ret = -ENOTSUP;
115 	}
116 
117 	k_mutex_unlock(&data->mutex);
118 
119 	return ret;
120 }
121 
vcnl4040_reg_setup(const struct device * dev)122 static int vcnl4040_reg_setup(const struct device *dev)
123 {
124 	const struct vcnl4040_config *config = dev->config;
125 	uint16_t value[VCNL4040_RW_REG_COUNT] = { 0 };
126 	uint8_t reg;
127 	int ret = 0;
128 
129 #ifdef CONFIG_VCNL4040_ENABLE_ALS
130 	struct vcnl4040_data *data = dev->data;
131 
132 	/* Set ALS integration time */
133 	value[VCNL4040_REG_ALS_CONF] = config->als_it << VCNL4040_ALS_IT_POS;
134 	/* Clear ALS shutdown */
135 	value[VCNL4040_REG_ALS_CONF] &= ~VCNL4040_ALS_SD_MASK;
136 
137 	/*
138 	 * scale the lux depending on the value of the integration time
139 	 * see page 8 of the VCNL4040 application note:
140 	 * https://www.vishay.com/docs/84307/designingvcnl4040.pdf
141 	 */
142 	switch (config->als_it) {
143 	case VCNL4040_AMBIENT_INTEGRATION_TIME_80MS:
144 		data->sensitivity = 0.12;
145 		break;
146 	case VCNL4040_AMBIENT_INTEGRATION_TIME_160MS:
147 		data->sensitivity = 0.06;
148 		break;
149 	case VCNL4040_AMBIENT_INTEGRATION_TIME_320MS:
150 		data->sensitivity = 0.03;
151 		break;
152 	case VCNL4040_AMBIENT_INTEGRATION_TIME_640MS:
153 		data->sensitivity = 0.015;
154 		break;
155 	default:
156 		data->sensitivity = 1.0;
157 		LOG_WRN("Cannot set ALS sensitivity from ALS_IT=%d",
158 			config->als_it);
159 		break;
160 	}
161 #else
162 	value[VCNL4040_REG_ALS_CONF] = VCNL4040_ALS_SD_MASK; /* default */
163 #endif
164 
165 	/* Set PS_HD */
166 	value[VCNL4040_REG_PS_CONF] = VCNL4040_PS_HD_MASK;
167 	/* Set duty cycle */
168 	value[VCNL4040_REG_PS_CONF] |= config->led_dc << VCNL4040_PS_DUTY_POS;
169 	/* Set integration time */
170 	value[VCNL4040_REG_PS_CONF] |= config->proxy_it << VCNL4040_PS_IT_POS;
171 	/* Clear proximity shutdown */
172 	value[VCNL4040_REG_PS_CONF] &= ~VCNL4040_PS_SD_MASK;
173 
174 	/* Set LED current */
175 	value[VCNL4040_REG_PS_MS] = config->led_i << VCNL4040_LED_I_POS;
176 
177 	for (reg = 0; reg < ARRAY_SIZE(value); reg++) {
178 		ret |= vcnl4040_write(dev, reg, value[reg]);
179 	}
180 
181 	return ret;
182 }
183 
184 #ifdef CONFIG_PM_DEVICE
vcnl4040_pm_action(const struct device * dev,enum pm_device_action action)185 static int vcnl4040_pm_action(const struct device *dev,
186 			      enum pm_device_action action)
187 {
188 	int ret = 0;
189 	uint16_t ps_conf;
190 
191 	ret = vcnl4040_read(dev, VCNL4040_REG_PS_CONF, &ps_conf);
192 	if (ret < 0) {
193 		return ret;
194 	}
195 #ifdef CONFIG_VCNL4040_ENABLE_ALS
196 	uint16_t als_conf;
197 
198 	ret = vcnl4040_read(dev, VCNL4040_REG_ALS_CONF, &als_conf);
199 	if (ret < 0) {
200 		return ret;
201 	}
202 #endif
203 	switch (action) {
204 	case PM_DEVICE_ACTION_RESUME:
205 		/* Clear proximity shutdown */
206 		ps_conf &= ~VCNL4040_PS_SD_MASK;
207 
208 		ret = vcnl4040_write(dev, VCNL4040_REG_PS_CONF,
209 					ps_conf);
210 		if (ret < 0) {
211 			return ret;
212 		}
213 #ifdef CONFIG_VCNL4040_ENABLE_ALS
214 		/* Clear als shutdown */
215 		als_conf &= ~VCNL4040_ALS_SD_MASK;
216 
217 		ret = vcnl4040_write(dev, VCNL4040_REG_ALS_CONF,
218 					als_conf);
219 		if (ret < 0) {
220 			return ret;
221 		}
222 #endif
223 		break;
224 	case PM_DEVICE_ACTION_SUSPEND:
225 		/* Set proximity shutdown bit 0 */
226 		ps_conf |= VCNL4040_PS_SD_MASK;
227 
228 		ret = vcnl4040_write(dev, VCNL4040_REG_PS_CONF,
229 					ps_conf);
230 		if (ret < 0) {
231 			return ret;
232 		}
233 #ifdef CONFIG_VCNL4040_ENABLE_ALS
234 		/* Clear als shutdown bit 0 */
235 		als_conf |= VCNL4040_ALS_SD_MASK;
236 
237 		ret = vcnl4040_write(dev, VCNL4040_REG_ALS_CONF,
238 					als_conf);
239 		if (ret < 0) {
240 			return ret;
241 		}
242 #endif
243 		break;
244 	default:
245 		return -ENOTSUP;
246 	}
247 
248 	return ret;
249 }
250 #endif
251 
vcnl4040_init(const struct device * dev)252 static int vcnl4040_init(const struct device *dev)
253 {
254 	const struct vcnl4040_config *config = dev->config;
255 	struct vcnl4040_data *data = dev->data;
256 	uint16_t id;
257 
258 	/* Get the I2C device */
259 	if (!device_is_ready(config->i2c.bus)) {
260 		LOG_ERR("Bus device is not ready");
261 		return -ENODEV;
262 	}
263 
264 	/* Check device id */
265 	if (vcnl4040_read(dev, VCNL4040_REG_DEVICE_ID, &id)) {
266 		LOG_ERR("Could not read device id");
267 		return -EIO;
268 	}
269 
270 	if (id != VCNL4040_DEFAULT_ID) {
271 		LOG_ERR("Incorrect device id (%d)", id);
272 		return -EIO;
273 	}
274 
275 	if (vcnl4040_reg_setup(dev)) {
276 		LOG_ERR("register setup");
277 		return -EIO;
278 	}
279 
280 	k_mutex_init(&data->mutex);
281 
282 #if CONFIG_VCNL4040_TRIGGER
283 	if (vcnl4040_trigger_init(dev)) {
284 		LOG_ERR("Could not initialise interrupts");
285 		return -EIO;
286 	}
287 #endif
288 
289 	LOG_DBG("Init complete");
290 
291 	return 0;
292 }
293 
294 static DEVICE_API(sensor, vcnl4040_driver_api) = {
295 	.sample_fetch = vcnl4040_sample_fetch,
296 	.channel_get = vcnl4040_channel_get,
297 #ifdef CONFIG_VCNL4040_TRIGGER
298 	.attr_set = vcnl4040_attr_set,
299 	.trigger_set = vcnl4040_trigger_set,
300 #endif
301 };
302 
303 #define VCNL4040_DEFINE(inst)									\
304 	static struct vcnl4040_data vcnl4040_data_##inst;					\
305 												\
306 	static const struct vcnl4040_config vcnl4040_config_##inst = {				\
307 		.i2c = I2C_DT_SPEC_INST_GET(inst),						\
308 		IF_ENABLED(CONFIG_VCNL4040_TRIGGER,						\
309 			   (.int_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, int_gpios, { 0 }),))	\
310 		.led_i = DT_INST_ENUM_IDX(inst, led_current),					\
311 		.led_dc = DT_INST_ENUM_IDX(inst, led_duty_cycle),				\
312 		.als_it = DT_INST_ENUM_IDX(inst, als_it),					\
313 		.proxy_it = DT_INST_ENUM_IDX(inst, proximity_it),				\
314 		.proxy_type = DT_INST_ENUM_IDX(inst, proximity_trigger),			\
315 	};											\
316 												\
317 	PM_DEVICE_DT_INST_DEFINE(inst, vcnl4040_pm_action);					\
318 												\
319 	SENSOR_DEVICE_DT_INST_DEFINE(inst, vcnl4040_init, PM_DEVICE_DT_INST_GET(inst),		\
320 			      &vcnl4040_data_##inst, &vcnl4040_config_##inst, POST_KERNEL,	\
321 			      CONFIG_SENSOR_INIT_PRIORITY, &vcnl4040_driver_api);		\
322 
323 DT_INST_FOREACH_STATUS_OKAY(VCNL4040_DEFINE)
324