/* * Copyright (c) 2020 Richard Osterloh * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT vishay_vcnl4040 #include "vcnl4040.h" #include #include #include #include #include #include LOG_MODULE_REGISTER(vcnl4040, CONFIG_SENSOR_LOG_LEVEL); int vcnl4040_read(const struct device *dev, uint8_t reg, uint16_t *out) { const struct vcnl4040_config *config = dev->config; uint8_t buff[2] = { 0 }; int ret = 0; ret = i2c_write_read_dt(&config->i2c, ®, sizeof(reg), buff, sizeof(buff)); if (ret == 0) { *out = sys_get_le16(buff); } return ret; } int vcnl4040_write(const struct device *dev, uint8_t reg, uint16_t value) { const struct vcnl4040_config *config = dev->config; uint8_t buf[3]; int ret; buf[0] = reg; sys_put_le16(value, &buf[1]); ret = i2c_write_dt(&config->i2c, buf, sizeof(buf)); if (ret < 0) { LOG_ERR("write[%02X]: %u", reg, ret); return ret; } return 0; } static int vcnl4040_sample_fetch(const struct device *dev, enum sensor_channel chan) { struct vcnl4040_data *data = dev->data; int ret = 0; #ifdef CONFIG_VCNL4040_ENABLE_ALS __ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_PROX || chan == SENSOR_CHAN_LIGHT); #else __ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_PROX); #endif k_mutex_lock(&data->mutex, K_FOREVER); if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_PROX) { ret = vcnl4040_read(dev, VCNL4040_REG_PS_DATA, &data->proximity); if (ret < 0) { LOG_ERR("Could not fetch proximity"); goto exit; } } #ifdef CONFIG_VCNL4040_ENABLE_ALS if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_LIGHT) { ret = vcnl4040_read(dev, VCNL4040_REG_ALS_DATA, &data->light); if (ret < 0) { LOG_ERR("Could not fetch ambient light"); } } #endif exit: k_mutex_unlock(&data->mutex); return ret; } static int vcnl4040_channel_get(const struct device *dev, enum sensor_channel chan, struct sensor_value *val) { struct vcnl4040_data *data = dev->data; int ret = 0; k_mutex_lock(&data->mutex, K_FOREVER); switch (chan) { case SENSOR_CHAN_PROX: val->val1 = data->proximity; val->val2 = 0; break; #ifdef CONFIG_VCNL4040_ENABLE_ALS case SENSOR_CHAN_LIGHT: val->val1 = data->light * data->sensitivity; val->val2 = 0; break; #endif default: ret = -ENOTSUP; } k_mutex_unlock(&data->mutex); return ret; } static int vcnl4040_reg_setup(const struct device *dev) { const struct vcnl4040_config *config = dev->config; uint16_t value[VCNL4040_RW_REG_COUNT] = { 0 }; uint8_t reg; int ret = 0; #ifdef CONFIG_VCNL4040_ENABLE_ALS struct vcnl4040_data *data = dev->data; /* Set ALS integration time */ value[VCNL4040_REG_ALS_CONF] = config->als_it << VCNL4040_ALS_IT_POS; /* Clear ALS shutdown */ value[VCNL4040_REG_ALS_CONF] &= ~VCNL4040_ALS_SD_MASK; /* * scale the lux depending on the value of the integration time * see page 8 of the VCNL4040 application note: * https://www.vishay.com/docs/84307/designingvcnl4040.pdf */ switch (config->als_it) { case VCNL4040_AMBIENT_INTEGRATION_TIME_80MS: data->sensitivity = 0.12; break; case VCNL4040_AMBIENT_INTEGRATION_TIME_160MS: data->sensitivity = 0.06; break; case VCNL4040_AMBIENT_INTEGRATION_TIME_320MS: data->sensitivity = 0.03; break; case VCNL4040_AMBIENT_INTEGRATION_TIME_640MS: data->sensitivity = 0.015; break; default: data->sensitivity = 1.0; LOG_WRN("Cannot set ALS sensitivity from ALS_IT=%d", config->als_it); break; } #else value[VCNL4040_REG_ALS_CONF] = VCNL4040_ALS_SD_MASK; /* default */ #endif /* Set PS_HD */ value[VCNL4040_REG_PS_CONF] = VCNL4040_PS_HD_MASK; /* Set duty cycle */ value[VCNL4040_REG_PS_CONF] |= config->led_dc << VCNL4040_PS_DUTY_POS; /* Set integration time */ value[VCNL4040_REG_PS_CONF] |= config->proxy_it << VCNL4040_PS_IT_POS; /* Clear proximity shutdown */ value[VCNL4040_REG_PS_CONF] &= ~VCNL4040_PS_SD_MASK; /* Set LED current */ value[VCNL4040_REG_PS_MS] = config->led_i << VCNL4040_LED_I_POS; for (reg = 0; reg < ARRAY_SIZE(value); reg++) { ret |= vcnl4040_write(dev, reg, value[reg]); } return ret; } #ifdef CONFIG_PM_DEVICE static int vcnl4040_pm_action(const struct device *dev, enum pm_device_action action) { int ret = 0; uint16_t ps_conf; ret = vcnl4040_read(dev, VCNL4040_REG_PS_CONF, &ps_conf); if (ret < 0) { return ret; } #ifdef CONFIG_VCNL4040_ENABLE_ALS uint16_t als_conf; ret = vcnl4040_read(dev, VCNL4040_REG_ALS_CONF, &als_conf); if (ret < 0) { return ret; } #endif switch (action) { case PM_DEVICE_ACTION_RESUME: /* Clear proximity shutdown */ ps_conf &= ~VCNL4040_PS_SD_MASK; ret = vcnl4040_write(dev, VCNL4040_REG_PS_CONF, ps_conf); if (ret < 0) { return ret; } #ifdef CONFIG_VCNL4040_ENABLE_ALS /* Clear als shutdown */ als_conf &= ~VCNL4040_ALS_SD_MASK; ret = vcnl4040_write(dev, VCNL4040_REG_ALS_CONF, als_conf); if (ret < 0) { return ret; } #endif break; case PM_DEVICE_ACTION_SUSPEND: /* Set proximity shutdown bit 0 */ ps_conf |= VCNL4040_PS_SD_MASK; ret = vcnl4040_write(dev, VCNL4040_REG_PS_CONF, ps_conf); if (ret < 0) { return ret; } #ifdef CONFIG_VCNL4040_ENABLE_ALS /* Clear als shutdown bit 0 */ als_conf |= VCNL4040_ALS_SD_MASK; ret = vcnl4040_write(dev, VCNL4040_REG_ALS_CONF, als_conf); if (ret < 0) { return ret; } #endif break; default: return -ENOTSUP; } return ret; } #endif static int vcnl4040_init(const struct device *dev) { const struct vcnl4040_config *config = dev->config; struct vcnl4040_data *data = dev->data; uint16_t id; /* Get the I2C device */ if (!device_is_ready(config->i2c.bus)) { LOG_ERR("Bus device is not ready"); return -ENODEV; } /* Check device id */ if (vcnl4040_read(dev, VCNL4040_REG_DEVICE_ID, &id)) { LOG_ERR("Could not read device id"); return -EIO; } if (id != VCNL4040_DEFAULT_ID) { LOG_ERR("Incorrect device id (%d)", id); return -EIO; } if (vcnl4040_reg_setup(dev)) { LOG_ERR("register setup"); return -EIO; } k_mutex_init(&data->mutex); #if CONFIG_VCNL4040_TRIGGER if (vcnl4040_trigger_init(dev)) { LOG_ERR("Could not initialise interrupts"); return -EIO; } #endif LOG_DBG("Init complete"); return 0; } static DEVICE_API(sensor, vcnl4040_driver_api) = { .sample_fetch = vcnl4040_sample_fetch, .channel_get = vcnl4040_channel_get, #ifdef CONFIG_VCNL4040_TRIGGER .attr_set = vcnl4040_attr_set, .trigger_set = vcnl4040_trigger_set, #endif }; #define VCNL4040_DEFINE(inst) \ static struct vcnl4040_data vcnl4040_data_##inst; \ \ static const struct vcnl4040_config vcnl4040_config_##inst = { \ .i2c = I2C_DT_SPEC_INST_GET(inst), \ IF_ENABLED(CONFIG_VCNL4040_TRIGGER, \ (.int_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, int_gpios, { 0 }),)) \ .led_i = DT_INST_ENUM_IDX(inst, led_current), \ .led_dc = DT_INST_ENUM_IDX(inst, led_duty_cycle), \ .als_it = DT_INST_ENUM_IDX(inst, als_it), \ .proxy_it = DT_INST_ENUM_IDX(inst, proximity_it), \ .proxy_type = DT_INST_ENUM_IDX(inst, proximity_trigger), \ }; \ \ PM_DEVICE_DT_INST_DEFINE(inst, vcnl4040_pm_action); \ \ SENSOR_DEVICE_DT_INST_DEFINE(inst, vcnl4040_init, PM_DEVICE_DT_INST_GET(inst), \ &vcnl4040_data_##inst, &vcnl4040_config_##inst, POST_KERNEL, \ CONFIG_SENSOR_INIT_PRIORITY, &vcnl4040_driver_api); \ DT_INST_FOREACH_STATUS_OKAY(VCNL4040_DEFINE)