| /* |
| * Copyright (c) 2023 Andreas Kilian |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT vishay_veml7700 |
| |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/drivers/sensor.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/pm/device.h> |
| #include <zephyr/sys/byteorder.h> |
| |
| #include "zephyr/drivers/sensor/veml7700.h" |
| |
| 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 <tt>enum veml7700_als_it</tt> 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 <tt>enum veml7700_als_gain</tt> and <tt>enum veml7700_als_it</tt> |
| * 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; |
| uint32_t int_flags; |
| }; |
| |
| /** |
| * @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 uint32_t veml7700_counts_to_lux(const struct veml7700_data *data, |
| uint16_t counts) |
| { |
| return counts * veml7700_resolution[data->gain][data->it]; |
| } |
| |
| static uint16_t veml7700_lux_to_counts(const struct veml7700_data *data, |
| uint32_t lux) |
| { |
| return lux / veml7700_resolution[data->gain][data->it]; |
| } |
| |
| 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 uint16_t veml7700_build_als_conf_param(const struct veml7700_data *data) |
| { |
| 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 param; |
| } |
| |
| 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; |
| |
| param = veml7700_build_als_conf_param(data); |
| 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; |
| data->als_lux = veml7700_counts_to_lux(data, counts); |
| LOG_DBG("Read ALS measurement: counts=%d, lux=%d", data->als_counts, data->als_lux); |
| |
| 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; |
| |
| if (attr == SENSOR_ATTR_LOWER_THRESH) { |
| data->thresh_low = veml7700_lux_to_counts(data, val->val1); |
| return veml7700_write_thresh_low(dev); |
| } else if (attr == SENSOR_ATTR_UPPER_THRESH) { |
| data->thresh_high = veml7700_lux_to_counts(data, val->val1); |
| 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 (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; |
| } |
| } |
| |
| 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_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->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) |