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 ®, 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