Implements driver for the Vishay VEML6040 rgbw light sensor. Included functionality: setting the integration time and reading the raw values for the four channels Not yet implemented: setting the measurements to 'Manual Force Mode' (Auto measurements off, and adding a measurement trigger) Datasheet: https://www.vishay.com/docs/84276/veml6040.pdf signed-off-by: Arthur Becker <arthur.becker@xxxxxxxxxx> --- V1 -> V2: Addressed review comments. DT-bindings in separate patch drivers/iio/light/Kconfig | 11 ++ drivers/iio/light/Makefile | 1 + drivers/iio/light/veml6040.c | 307 +++++++++++++++++++++++++++++++++++ 3 files changed, 319 insertions(+) create mode 100644 drivers/iio/light/veml6040.c diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index fd5a9879a582..7ff517b728ec 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -654,6 +654,17 @@ config VEML6030 To compile this driver as a module, choose M here: the module will be called veml6030. +config VEML6040 + tristate "VEML6040 RGBW light sensor" + select REGMAP_I2C + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML6040 + RGBW light sensor. + + To compile this driver as a module, choose M here: the + module will be called veml6040. + config VEML6070 tristate "VEML6070 UV A light sensor" depends on I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index 2e5fdb33e0e9..ae957c88aa0c 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_US5182D) += us5182d.o obj-$(CONFIG_VCNL4000) += vcnl4000.o obj-$(CONFIG_VCNL4035) += vcnl4035.o obj-$(CONFIG_VEML6030) += veml6030.o +obj-$(CONFIG_VEML6040) += veml6040.o obj-$(CONFIG_VEML6070) += veml6070.o obj-$(CONFIG_VEML6075) += veml6075.o obj-$(CONFIG_VL6180) += vl6180.o diff --git a/drivers/iio/light/veml6040.c b/drivers/iio/light/veml6040.c new file mode 100644 index 000000000000..9ce807d5484a --- /dev/null +++ b/drivers/iio/light/veml6040.c @@ -0,0 +1,307 @@ +#include <linux/bitfield.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/module.h> +#include <linux/regmap.h> + +/* VEML6040 Configuration Registers + * + * SD: Shutdown + * AF: Auto / Force Mode (Auto Measurements On:0, Off:1) + * TR: Trigger Measurement (when AF Bit is set) + * IT: Integration Time + */ +#define VEML6040_CONF_REG_RW 0x000 +#define VEML6040_CONF_SD_MSK BIT(0) +#define VEML6040_CONF_AF_MSK BIT(1) +#define VEML6040_CONF_TR_MSK BIT(2) +#define VEML6040_CONF_IT_MSK GENMASK(6, 4) +#define VEML6040_CONF_IT_40 0 +#define VEML6040_CONF_IT_80 1 +#define VEML6040_CONF_IT_160 2 +#define VEML6040_CONF_IT_320 3 +#define VEML6040_CONF_IT_640 4 +#define VEML6040_CONF_IT_1280 5 + +/* VEML6040 Read Only Registers */ +#define VEML6040_REG_R_RO 0x08 +#define VEML6040_REG_G_RO 0x09 +#define VEML6040_REG_B_RO 0x0A +#define VEML6040_REG_W_RO 0x0B + +static const int veml6040_int_time_avail[] = { 40, 80, 160, 320, 640, 1280 }; + +enum veml6040_chan { + CH_RED, + CH_GREEN, + CH_BLUE, + CH_WHITE, +}; + +struct veml6040_data { + struct i2c_client *client; + struct regmap *regmap; +}; + +static const struct regmap_config veml6040_regmap_config = { + .name = "veml6040_regmap", + .reg_bits = 8, + .val_bits = 16, + .max_register = VEML6040_REG_W_RO, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static int veml6040_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret, reg, it_index; + struct veml6040_data *data = iio_priv(indio_dev); + struct regmap *regmap = data->regmap; + struct device *dev = &data->client->dev; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->channel) { + case CH_RED: + ret = regmap_read(regmap, VEML6040_REG_R_RO, ®); + break; + case CH_GREEN: + ret = regmap_read(regmap, VEML6040_REG_G_RO, ®); + break; + case CH_BLUE: + ret = regmap_read(regmap, VEML6040_REG_B_RO, ®); + break; + case CH_WHITE: + ret = regmap_read(regmap, VEML6040_REG_W_RO, ®); + break; + default: + return -EINVAL; + } + if (ret) { + dev_err(dev, "iio-veml6040 - Can't read data %d\n", + ret); + return ret; + } + *val = reg; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_INT_TIME: + ret = regmap_read(regmap, VEML6040_CONF_REG_RW, ®); + if (ret) { + dev_err(dev, "iio-veml6040 - Can't read data %d\n", + ret); + return ret; + } + it_index = FIELD_GET(VEML6040_CONF_IT_MSK, reg); + if (it_index >= ARRAY_SIZE(veml6040_int_time_avail)) { + dev_err(dev, + "iio-veml6040 - Invalid Integration Time Set"); + return -EINVAL; + } + *val = veml6040_int_time_avail[it_index]; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int veml6040_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct veml6040_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + for (int i = 0; i < ARRAY_SIZE(veml6040_int_time_avail); i++) { + if (veml6040_int_time_avail[i] == val) { + return regmap_update_bits( + data->regmap, VEML6040_CONF_REG_RW, + VEML6040_CONF_IT_MSK, + FIELD_PREP(VEML6040_CONF_IT_MSK, i)); + } + } + return -EINVAL; + default: + return -EINVAL; + } + return 0; +} + +static int veml6040_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *length = ARRAY_SIZE(veml6040_int_time_avail); + *vals = veml6040_int_time_avail; + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + + default: + return -EINVAL; + } +} + +static const struct iio_info veml6040_info = { + .read_raw = veml6040_read_raw, + .write_raw = veml6040_write_raw, + .read_avail = veml6040_read_avail, +}; + +static const struct iio_chan_spec veml6040_channels[] = { + { + .type = IIO_INTENSITY, + .channel = CH_RED, + .channel2 = IIO_MOD_LIGHT_RED, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_INTENSITY, + .channel = CH_GREEN, + .channel2 = IIO_MOD_LIGHT_GREEN, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_INTENSITY, + .channel = CH_BLUE, + .channel2 = IIO_MOD_LIGHT_BLUE, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_INTENSITY, + .channel = CH_WHITE, + .channel2 = IIO_MOD_LIGHT_CLEAR, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_INT_TIME), + } +}; + +static int veml6040_shutdown(struct veml6040_data *data) +{ + return regmap_update_bits(data->regmap, VEML6040_CONF_REG_RW, + VEML6040_CONF_SD_MSK, VEML6040_CONF_SD_MSK); +} + +static void veml6040_shutdown_action(void *data) +{ + veml6040_shutdown(data); +} + +static int veml6040_probe(struct i2c_client *client) +{ + struct veml6040_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + return dev_err_probe(&client->dev, -EOPNOTSUPP, + "I2C adapter doesn't support plain I2C\n"); + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) { + return dev_err_probe(&client->dev, -ENOMEM, + "IIO device allocation failed\n"); + } + + regmap = devm_regmap_init_i2c(client, &veml6040_regmap_config); + if (IS_ERR(regmap)) { + return dev_err_probe(&client->dev, PTR_ERR(regmap), + "Regmap setup failed\n"); + } + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + + indio_dev->name = "veml6040"; + indio_dev->info = &veml6040_info; + indio_dev->channels = veml6040_channels; + indio_dev->num_channels = ARRAY_SIZE(veml6040_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = regmap_update_bits( + data->regmap, VEML6040_CONF_REG_RW, VEML6040_CONF_IT_MSK, + FIELD_PREP(VEML6040_CONF_IT_MSK, VEML6040_CONF_IT_40)); + + if (ret) { + return dev_err_probe(&client->dev, ret, + "Could not set Integration Time: %d\n", + ret); + } + + ret = regmap_update_bits(data->regmap, VEML6040_CONF_REG_RW, + VEML6040_CONF_AF_MSK, + FIELD_PREP(VEML6040_CONF_AF_MSK, 0)); + if (ret) { + return dev_err_probe(&client->dev, ret, + "Could not set Automode: %d\n", ret); + } + + ret = regmap_update_bits(data->regmap, VEML6040_CONF_REG_RW, + VEML6040_CONF_SD_MSK, + FIELD_PREP(VEML6040_CONF_SD_MSK, 0)); + if (ret) { + return dev_err_probe(&client->dev, ret, + "Could not set Enable: %d\n", ret); + } + + ret = devm_add_action_or_reset(&client->dev, veml6040_shutdown_action, + data); + if (ret) { + return dev_err_probe(&client->dev, ret, + "Could not add shutdown action: %d\n", + ret); + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id veml6040_id_table[] = { { "veml6040", 0 }, + {} }; +MODULE_DEVICE_TABLE(i2c, veml6040_id_table); + +static const struct of_device_id veml6040_of_match[] = { + { .compatible = "vishay,veml6040" }, + {} +}; +MODULE_DEVICE_TABLE(of, veml6040_of_match); + +static struct i2c_driver veml6040_driver = { + .probe = veml6040_probe, + .id_table = veml6040_id_table, + .driver = { + .name = "veml6040", + .of_match_table = veml6040_of_match, + }, +}; + +module_i2c_driver(veml6040_driver); + +MODULE_DESCRIPTION("veml6040 RGBW light sensor driver"); +MODULE_AUTHOR("Arthur Becker <arthur.becker@xxxxxxxxxx>"); +MODULE_LICENSE("GPL"); -- 2.34.1