/* * Copyright (c) 2023 Andreas Kilian * Copyright (c) 2024 Jeff Welder (Ellenby Technologies, Inc.) * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT vishay_veml7700 #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(VEML7700, CONFIG_SENSOR_LOG_LEVEL); /* * Bit mask to zero out all bits except 14 and 15. * Those two are used for the high and low threshold * interrupt flags in the ALS_INT register. */ #define VEML7700_ALS_INT_MASK (~BIT_MASK(14)) /* * 16-bit command register addresses */ #define VEML7700_CMDCODE_ALS_CONF 0x00 #define VEML7700_CMDCODE_ALS_WH 0x01 #define VEML7700_CMDCODE_ALS_WL 0x02 #define VEML7700_CMDCODE_PSM 0x03 #define VEML7700_CMDCODE_ALS 0x04 #define VEML7700_CMDCODE_WHITE 0x05 #define VEML7700_CMDCODE_ALS_INT 0x06 /* * Devicetree psm-mode property value for "PSM disabled" */ #define VEML7700_PSM_DISABLED 0x00 /* * ALS integration time setting values. * * The enumerators of enum veml7700_als_it provide * indices into this array to get the related value for the * ALS_IT configuration bits. */ static const uint8_t veml7700_it_values[VEML7700_ALS_IT_ELEM_COUNT] = { 0x0C, /* 25 - 0b1100 */ 0x08, /* 50 - 0b1000 */ 0x00, /* 100 - 0b0000 */ 0x01, /* 200 - 0b0001 */ 0x02, /* 400 - 0b0010 */ 0x03, /* 800 - 0b0011 */ }; /* * Resolution matrix for values to convert between data provided * by the sensor ("counts") and lux. * * These values depend on the current gain and integration time settings. * The enumerators of enum veml7700_als_gain and enum veml7700_als_it * are used for indices into this matrix. */ static const float veml7700_resolution[VEML7700_ALS_GAIN_ELEM_COUNT][VEML7700_ALS_IT_ELEM_COUNT] = { /* 25ms 50ms 100ms 200ms 400ms 800ms Integration Time */ {0.2304f, 0.1152f, 0.0576f, 0.0288f, 0.0144f, 0.0072f}, /* Gain 1 */ {0.1152f, 0.0576f, 0.0288f, 0.0144f, 0.0072f, 0.0036f}, /* Gain 2 */ {1.8432f, 0.9216f, 0.4608f, 0.2304f, 0.1152f, 0.0576f}, /* Gain 1/8 */ {0.9216f, 0.4608f, 0.2304f, 0.1152f, 0.0576f, 0.0288f}, /* Gain 1/4 */ }; struct veml7700_config { struct i2c_dt_spec bus; uint8_t psm; }; struct veml7700_data { uint8_t shut_down; enum veml7700_als_gain gain; enum veml7700_als_it it; enum veml7700_int_mode int_mode; uint16_t thresh_high; uint16_t thresh_low; uint16_t als_counts; uint32_t als_lux; uint16_t white_counts; uint32_t int_flags; }; static bool is_veml7700_gain_in_range(uint32_t gain_selection) { return (gain_selection < VEML7700_ALS_GAIN_ELEM_COUNT); } static bool is_veml7700_it_in_range(uint32_t it_selection) { return (it_selection < VEML7700_ALS_IT_ELEM_COUNT); } /** * @brief Waits for a specific amount of time which depends * on the current integration time setting. * * According to datasheet for a measurement to complete one has * to wait for at least the integration time. But tests showed * that a much longer wait time is needed. Simply adding 50 or * 100ms to the integration time is not enough so we doubled * the integration time to get our wait time. * * This function is only called if the sensor is used in "single shot" * measuring mode. In this mode the sensor measures on demand an * measurements take time depending on the configures integration time. * In continuous mode, activated by one of the power saving modes, * you can always use the last sample value and no waiting is required. * * For more information see the "Designing the VEML7700 Into an Application" * application notes about the power saving modes. */ static void veml7700_sleep_by_integration_time(const struct veml7700_data *data) { switch (data->it) { case VEML7700_ALS_IT_25: k_msleep(50); break; case VEML7700_ALS_IT_50: k_msleep(100); break; case VEML7700_ALS_IT_100: k_msleep(200); break; case VEML7700_ALS_IT_200: k_msleep(400); break; case VEML7700_ALS_IT_400: k_msleep(800); break; case VEML7700_ALS_IT_800: k_msleep(1600); break; } } static int veml7700_counts_to_lux(const struct veml7700_data *data, uint16_t counts, uint32_t *lux) { if (!is_veml7700_gain_in_range(data->gain) || !is_veml7700_it_in_range(data->it)) { return -EINVAL; } *lux = counts * veml7700_resolution[data->gain][data->it]; return 0; } static int veml7700_lux_to_counts(const struct veml7700_data *data, uint32_t lux, uint16_t *counts) { if (!is_veml7700_gain_in_range(data->gain) || !is_veml7700_it_in_range(data->it)) { return -EINVAL; } *counts = lux / veml7700_resolution[data->gain][data->it]; return 0; } static int veml7700_check_gain(const struct sensor_value *val) { return val->val1 >= VEML7700_ALS_GAIN_1 && val->val1 <= VEML7700_ALS_GAIN_1_4; } static int veml7700_check_it(const struct sensor_value *val) { return val->val1 >= VEML7700_ALS_IT_25 && val->val1 <= VEML7700_ALS_IT_800; } static int veml7700_check_int_mode(const struct sensor_value *val) { return (val->val1 >= VEML7700_ALS_PERS_1 && val->val1 <= VEML7700_ALS_PERS_8) || val->val1 == VEML7700_INT_DISABLED; } static int veml7700_build_als_conf_param(const struct veml7700_data *data, uint16_t *return_value) { if (!is_veml7700_gain_in_range(data->gain) || !is_veml7700_it_in_range(data->it)) { return -EINVAL; } uint16_t param = 0; /* Bits 15:13 -> reserved */ /* Bits 12:11 -> gain selection (ALS_GAIN) */ param |= data->gain << 11; /* Bit 10 -> reserved */ /* Bits 9:6 -> integration time (ALS_IT) */ param |= veml7700_it_values[data->it] << 6; /* Bits 5:4 -> interrupt persistent protection (ALS_PERS) */ if (data->int_mode != VEML7700_INT_DISABLED) { param |= data->int_mode << 4; /* Bit 1 -> interrupt enable (ALS_INT_EN) */ param |= BIT(1); } /* Bits 3:2 -> reserved */ /* Bit 0 -> shut down setting (ALS_SD) */ if (data->shut_down) { param |= BIT(0); } *return_value = param; return 0; } static uint16_t veml7700_build_psm_param(const struct veml7700_config *conf) { /* We can directly use the devicetree configuration value. */ return conf->psm; } static int veml7700_write(const struct device *dev, uint8_t cmd, uint16_t data) { const struct veml7700_config *conf = dev->config; uint8_t send_buf[3]; send_buf[0] = cmd; /* byte 0: command code */ sys_put_le16(data, &send_buf[1]); /* bytes 1,2: command arguments */ return i2c_write_dt(&conf->bus, send_buf, ARRAY_SIZE(send_buf)); } static int veml7700_read(const struct device *dev, uint8_t cmd, uint16_t *data) { const struct veml7700_config *conf = dev->config; uint8_t recv_buf[2]; int ret = i2c_write_read_dt(&conf->bus, &cmd, sizeof(cmd), &recv_buf, ARRAY_SIZE(recv_buf)); if (ret < 0) { return ret; } *data = sys_get_le16(recv_buf); return 0; } static int veml7700_write_als_conf(const struct device *dev) { const struct veml7700_data *data = dev->data; uint16_t param; int ret = 0; ret = veml7700_build_als_conf_param(data, ¶m); if (ret < 0) { return ret; } LOG_DBG("Writing ALS configuration: 0x%04x", param); return veml7700_write(dev, VEML7700_CMDCODE_ALS_CONF, param); } static int veml7700_write_psm(const struct device *dev) { const struct veml7700_config *conf = dev->config; uint16_t psm_param; psm_param = veml7700_build_psm_param(conf); LOG_DBG("Writing PSM configuration: 0x%04x", psm_param); return veml7700_write(dev, VEML7700_CMDCODE_PSM, psm_param); } static int veml7700_write_thresh_low(const struct device *dev) { const struct veml7700_data *data = dev->data; LOG_DBG("Writing low threshold counts: %d", data->thresh_low); return veml7700_write(dev, VEML7700_CMDCODE_ALS_WL, data->thresh_low); } static int veml7700_write_thresh_high(const struct device *dev) { const struct veml7700_data *data = dev->data; LOG_DBG("Writing high threshold counts: %d", data->thresh_high); return veml7700_write(dev, VEML7700_CMDCODE_ALS_WH, data->thresh_high); } static int veml7700_set_shutdown_flag(const struct device *dev, uint8_t new_val) { struct veml7700_data *data = dev->data; uint8_t prev_sd; int ret; prev_sd = data->shut_down; data->shut_down = new_val; ret = veml7700_write_als_conf(dev); if (ret < 0) { data->shut_down = prev_sd; } return ret; } static int veml7700_fetch_als(const struct device *dev) { struct veml7700_data *data = dev->data; uint16_t counts; int ret; ret = veml7700_read(dev, VEML7700_CMDCODE_ALS, &counts); if (ret < 0) { return ret; } data->als_counts = counts; ret = veml7700_counts_to_lux(data, counts, &data->als_lux); if (ret < 0) { return ret; } LOG_DBG("Read ALS measurement: counts=%d, lux=%d", data->als_counts, data->als_lux); return 0; } static int veml7700_fetch_white(const struct device *dev) { struct veml7700_data *data = dev->data; uint16_t counts; int ret; ret = veml7700_read(dev, VEML7700_CMDCODE_WHITE, &counts); if (ret < 0) { return ret; } data->white_counts = counts; LOG_DBG("Read White Light measurement: counts=%d", data->white_counts); return 0; } static int veml7700_fetch_int_flags(const struct device *dev) { struct veml7700_data *data = dev->data; uint16_t int_flags = 0; int ret; ret = veml7700_read(dev, VEML7700_CMDCODE_ALS_INT, &int_flags); if (ret < 0) { return ret; } data->int_flags = int_flags & VEML7700_ALS_INT_MASK; LOG_DBG("Read int state: 0x%02x", data->int_flags); return 0; } static int veml7700_attr_set(const struct device *dev, enum sensor_channel chan, enum sensor_attribute attr, const struct sensor_value *val) { if (chan != SENSOR_CHAN_LIGHT) { return -ENOTSUP; } struct veml7700_data *data = dev->data; int ret = 0; if (attr == SENSOR_ATTR_LOWER_THRESH) { ret = veml7700_lux_to_counts(data, val->val1, &data->thresh_low); if (ret < 0) { return ret; } return veml7700_write_thresh_low(dev); } else if (attr == SENSOR_ATTR_UPPER_THRESH) { ret = veml7700_lux_to_counts(data, val->val1, &data->thresh_high); if (ret < 0) { return ret; } return veml7700_write_thresh_high(dev); } else if ((enum sensor_attribute_veml7700)attr == SENSOR_ATTR_VEML7700_GAIN) { if (veml7700_check_gain(val)) { data->gain = (enum veml7700_als_gain)val->val1; return veml7700_write_als_conf(dev); } else { return -EINVAL; } } else if ((enum sensor_attribute_veml7700)attr == SENSOR_ATTR_VEML7700_ITIME) { if (veml7700_check_it(val)) { data->it = (enum veml7700_als_it)val->val1; return veml7700_write_als_conf(dev); } else { return -EINVAL; } } else if ((enum sensor_attribute_veml7700)attr == SENSOR_ATTR_VEML7700_INT_MODE) { if (veml7700_check_int_mode(val)) { data->int_mode = (enum veml7700_int_mode)val->val1; return veml7700_write_als_conf(dev); } else { return -EINVAL; } } else { return -ENOTSUP; } } static int veml7700_attr_get(const struct device *dev, enum sensor_channel chan, enum sensor_attribute attr, struct sensor_value *val) { if (chan != SENSOR_CHAN_LIGHT) { return -ENOTSUP; } struct veml7700_data *data = dev->data; if (attr == SENSOR_ATTR_LOWER_THRESH) { val->val1 = data->thresh_low; } else if (attr == SENSOR_ATTR_UPPER_THRESH) { val->val1 = data->thresh_high; } else if ((enum sensor_attribute_veml7700)attr == SENSOR_ATTR_VEML7700_GAIN) { val->val1 = data->gain; } else if ((enum sensor_attribute_veml7700)attr == SENSOR_ATTR_VEML7700_ITIME) { val->val1 = data->it; } else if ((enum sensor_attribute_veml7700)attr == SENSOR_ATTR_VEML7700_INT_MODE) { val->val1 = data->int_mode; } else { return -ENOTSUP; } val->val2 = 0; return 0; } static int veml7700_perform_single_measurement(const struct device *dev) { struct veml7700_data *data = dev->data; int ret; /* Start sensor */ ret = veml7700_set_shutdown_flag(dev, 0); if (ret < 0) { return ret; } /* Wait for sensor to finish it's startup sequence */ k_msleep(5); /* Wait for measurement to complete */ veml7700_sleep_by_integration_time(data); /* Shut down sensor */ return veml7700_set_shutdown_flag(dev, 1); } static int veml7700_sample_fetch(const struct device *dev, enum sensor_channel chan) { const struct veml7700_config *conf = dev->config; struct veml7700_data *data; int ret; /* Start sensor for new measurement if power saving mode is disabled */ if ((chan == SENSOR_CHAN_LIGHT || chan == SENSOR_CHAN_ALL) && conf->psm == VEML7700_PSM_DISABLED) { ret = veml7700_perform_single_measurement(dev); if (ret < 0) { return ret; } } if (chan == SENSOR_CHAN_LIGHT) { return veml7700_fetch_als(dev); } else if ((enum sensor_channel_veml7700)chan == SENSOR_CHAN_VEML7700_INTERRUPT) { data = dev->data; if (data->int_mode != VEML7700_INT_DISABLED) { return veml7700_fetch_int_flags(dev); } else { return -ENOTSUP; } } else if ((enum sensor_channel_veml7700)chan == SENSOR_CHAN_VEML7700_WHITE_RAW_COUNTS) { return veml7700_fetch_white(dev); } else if (chan == SENSOR_CHAN_ALL) { data = dev->data; if (data->int_mode != VEML7700_INT_DISABLED) { ret = veml7700_fetch_int_flags(dev); if (ret < 0) { return ret; } } ret = veml7700_fetch_white(dev); if (ret < 0) { return ret; } return veml7700_fetch_als(dev); } else { return -ENOTSUP; } } static int veml7700_channel_get(const struct device *dev, enum sensor_channel chan, struct sensor_value *val) { struct veml7700_data *data = dev->data; if (chan == SENSOR_CHAN_LIGHT) { val->val1 = data->als_lux; } else if ((enum sensor_channel_veml7700)chan == SENSOR_CHAN_VEML7700_RAW_COUNTS) { val->val1 = data->als_counts; } else if ((enum sensor_channel_veml7700)chan == SENSOR_CHAN_VEML7700_WHITE_RAW_COUNTS) { val->val1 = data->white_counts; } else if ((enum sensor_channel_veml7700)chan == SENSOR_CHAN_VEML7700_INTERRUPT) { val->val1 = data->int_flags; } else { return -ENOTSUP; } val->val2 = 0; return 0; } #ifdef CONFIG_PM_DEVICE static int veml7700_pm_action(const struct device *dev, enum pm_device_action action) { const struct veml7700_config *conf = dev->config; if (conf->psm != VEML7700_PSM_DISABLED) { switch (action) { case PM_DEVICE_ACTION_SUSPEND: return veml7700_set_shutdown_flag(dev, 1); case PM_DEVICE_ACTION_RESUME: return veml7700_set_shutdown_flag(dev, 0); default: return -ENOTSUP; } } return 0; } #endif /* CONFIG_PM_DEVICE */ static int veml7700_init(const struct device *dev) { const struct veml7700_config *conf = dev->config; struct veml7700_data *data = dev->data; int ret; if (!i2c_is_ready_dt(&conf->bus)) { LOG_ERR("Device not ready"); return -ENODEV; } /* Initialize power saving mode */ ret = veml7700_write_psm(dev); if (ret < 0) { return ret; } /* Set initial data values */ data->thresh_low = 0; data->thresh_high = 0xFFFF; data->gain = VEML7700_ALS_GAIN_1_4; data->it = VEML7700_ALS_IT_100; data->int_mode = VEML7700_INT_DISABLED; data->als_counts = 0; data->als_lux = 0; data->white_counts = 0; data->shut_down = (conf->psm != VEML7700_PSM_DISABLED) ? 0 : 1; /* Initialize sensor configuration */ ret = veml7700_write_thresh_low(dev); if (ret < 0) { return ret; } ret = veml7700_write_thresh_high(dev); if (ret < 0) { return ret; } ret = veml7700_write_als_conf(dev); if (ret < 0) { return ret; } return 0; } static const struct sensor_driver_api veml7700_api = {.sample_fetch = veml7700_sample_fetch, .channel_get = veml7700_channel_get, .attr_set = veml7700_attr_set, .attr_get = veml7700_attr_get}; #define VEML7700_INIT(n) \ static struct veml7700_data veml7700_data_##n; \ \ static const struct veml7700_config veml7700_config_##n = { \ .bus = I2C_DT_SPEC_INST_GET(n), .psm = DT_INST_PROP(n, psm_mode)}; \ \ PM_DEVICE_DT_INST_DEFINE(n, veml7700_pm_action); \ \ SENSOR_DEVICE_DT_INST_DEFINE(n, veml7700_init, PM_DEVICE_DT_INST_GET(n), \ &veml7700_data_##n, &veml7700_config_##n, POST_KERNEL, \ CONFIG_SENSOR_INIT_PRIORITY, &veml7700_api); DT_INST_FOREACH_STATUS_OKAY(VEML7700_INIT)