From: Arthur Becker <arthur.becker@xxxxxxxxxx> Subject: [PATCH] iio: light: driver for Vishay VEML6040 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 'Active Force' (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> --- drivers/iio/light/Kconfig | 11 ++ drivers/iio/light/Makefile | 1 + drivers/iio/light/veml6040.c | 338 +++++++++++++++++++++++++++++++++++ 3 files changed, 350 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..7effb712820f --- /dev/null +++ b/drivers/iio/light/veml6040.c @@ -0,0 +1,338 @@ +#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> + +#define VEML6040_DRV_NAME "veml6040" + +#define VEML6040_SLAVE_ADDR 0x10 + +/* VEML6040 Registers */ +#define VEML6040_REG_CONF_RW 0x00 +#define VEML6040_REG_R_RO 0x08 +#define VEML6040_REG_G_RO 0x09 +#define VEML6040_REG_B_RO 0x0A +#define VEML6040_REG_W_RO 0x0B + +/* Bit masks for specific functionality */ +#define VEML6040_MASK_IT GENMASK(6, 4) /* bit mask integration time */ +#define VEML6040_MASK_SD BIT(0) /* Shutdown */ +#define VEML6040_MASK_AF \ + BIT(1) /* Active Force (Auto Measurements 0:on, 1:off) */ +#define VEML6040_MASK_TRIG BIT(2) /* Trigger Measurement (when AF Bit is set) */ + +/* VEML6040 Command Codes for Integration Time in Milliseconds */ +#define VEML6040_IT_40 0x00 +#define VEML6040_IT_80 0x10 +#define VEML6040_IT_160 0x20 +#define VEML6040_IT_320 0x30 +#define VEML6040_IT_640 0x40 +#define VEML6040_IT_1280 0x50 + +static const int veml6040_int_time_avail[] = { 40, 80, 160, 320, 640, 1280 }; + +static const int veml6040_int_time_codes[] = { + VEML6040_IT_40, VEML6040_IT_80, VEML6040_IT_160, + VEML6040_IT_320, VEML6040_IT_640, VEML6040_IT_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_enable(struct veml6040_data *data) +{ + return regmap_update_bits(data->regmap, VEML6040_REG_CONF_RW, + VEML6040_MASK_SD, 0x00); +} + +static int veml6040_shutdown(struct veml6040_data *data) +{ + return regmap_update_bits(data->regmap, VEML6040_REG_CONF_RW, + VEML6040_MASK_SD, 0xFF); +} + +static int veml6040_set_automode(struct veml6040_data *data) +{ + return regmap_update_bits(data->regmap, VEML6040_REG_CONF_RW, + VEML6040_MASK_AF, 0x00); +} + +static int veml6040_set_forcemode(struct veml6040_data *data) +{ + return regmap_update_bits(data->regmap, VEML6040_REG_CONF_RW, + VEML6040_MASK_AF, 0xFF); +} + +static int veml6040_reset_measurement_trig(struct veml6040_data *data) +{ + return regmap_update_bits(data->regmap, VEML6040_REG_CONF_RW, + VEML6040_MASK_TRIG, 0x00); +} + +static int veml6040_trig_measurement(struct veml6040_data *data) +{ + return regmap_update_bits(data->regmap, VEML6040_REG_CONF_RW, + VEML6040_MASK_TRIG, 0xFF); +} + +static int veml6040_set_it(struct veml6040_data *data, int it_code) +{ + return regmap_update_bits(data->regmap, VEML6040_REG_CONF_RW, + VEML6040_MASK_IT, it_code); +} + +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; + } + break; + + case IIO_CHAN_INFO_INT_TIME: + ret = regmap_read(regmap, VEML6040_REG_CONF_RW, ®); + it_index = (reg & VEML6040_MASK_IT) >> 4; + reg = veml6040_int_time_avail[it_index]; + break; + + default: + return -EINVAL; + } + + if (ret < 0) { + dev_err(dev, "iio-veml6040 - Can't read data %d\n", ret); + return ret; + } + *val = reg; + return IIO_VAL_INT; +} + +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 veml6040_set_it( + data, veml6040_int_time_codes[i]); + } + } + 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) +{ + printk("iio-veml6040 - read_avail\n"); + 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 = true, + .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 = true, + .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 = true, + .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 = true, + .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_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct veml6040_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, + "iio-veml6040 - i2c adapter doesn't support plain i2c\n"); + return -EOPNOTSUPP; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) { + dev_err(&client->dev, "iio-veml6040 - Error! Out of memory\n"); + return -ENOMEM; + } + + regmap = devm_regmap_init_i2c(client, &veml6040_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, + "iio-veml6040 - Error! Can't setup regmap\n"); + return PTR_ERR(regmap); + } + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + + indio_dev->name = VEML6040_DRV_NAME; + indio_dev->info = &veml6040_info; + indio_dev->channels = veml6040_channels; + indio_dev->num_channels = ARRAY_SIZE(veml6040_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Initialize config register */ + ret = veml6040_set_it(data, VEML6040_IT_40); + if (ret < 0) { + dev_err(&client->dev, + "iio-veml6040 - Could not set Integration Time: %d\n", + ret); + return ret; + } + + ret = veml6040_set_automode(data); + if (ret < 0) { + dev_err(&client->dev, + "iio-veml6040 - Could not set Automode: %d\n", ret); + return ret; + } + + ret = veml6040_enable(data); + if (ret < 0) { + dev_err(&client->dev, + "iio-veml6040 - Could not set Enable: %d\n", ret); + return ret; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id veml6040_id_table[] = { + { VEML6040_DRV_NAME, 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 void veml6040_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct veml6040_data *data; + + /* Set device to power down mode */ + indio_dev = i2c_get_clientdata(client); + data = iio_priv(indio_dev); + veml6040_shutdown(data); +} + +static struct i2c_driver veml6040_driver = { + .probe = veml6040_probe, + .remove = veml6040_remove, + .id_table = veml6040_id_table, + .driver = { + .name = VEML6040_DRV_NAME, + .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