1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * Vishay VEML6040 RGBW light sensor driver
4 *
5 * Copyright (C) 2024 Sentec AG
6 * Author: Arthur Becker <arthur.becker@sentec.com>
7 *
8 */
9
10 #include <linux/bitfield.h>
11 #include <linux/err.h>
12 #include <linux/i2c.h>
13 #include <linux/iio/iio.h>
14 #include <linux/iio/sysfs.h>
15 #include <linux/module.h>
16 #include <linux/regmap.h>
17
18 /* VEML6040 Configuration Registers
19 *
20 * SD: Shutdown
21 * AF: Auto / Force Mode (Auto Measurements On:0, Off:1)
22 * TR: Trigger Measurement (when AF Bit is set)
23 * IT: Integration Time
24 */
25 #define VEML6040_CONF_REG 0x000
26 #define VEML6040_CONF_SD_MSK BIT(0)
27 #define VEML6040_CONF_AF_MSK BIT(1)
28 #define VEML6040_CONF_TR_MSK BIT(2)
29 #define VEML6040_CONF_IT_MSK GENMASK(6, 4)
30 #define VEML6040_CONF_IT_40_MS 0
31 #define VEML6040_CONF_IT_80_MS 1
32 #define VEML6040_CONF_IT_160_MS 2
33 #define VEML6040_CONF_IT_320_MS 3
34 #define VEML6040_CONF_IT_640_MS 4
35 #define VEML6040_CONF_IT_1280_MS 5
36
37 /* VEML6040 Read Only Registers */
38 #define VEML6040_REG_R 0x08
39 #define VEML6040_REG_G 0x09
40 #define VEML6040_REG_B 0x0A
41 #define VEML6040_REG_W 0x0B
42
43 static const int veml6040_it_ms[] = { 40, 80, 160, 320, 640, 1280 };
44
45 enum veml6040_chan {
46 CH_RED,
47 CH_GREEN,
48 CH_BLUE,
49 CH_WHITE,
50 };
51
52 struct veml6040_data {
53 struct i2c_client *client;
54 struct regmap *regmap;
55 };
56
57 static const struct regmap_config veml6040_regmap_config = {
58 .name = "veml6040_regmap",
59 .reg_bits = 8,
60 .val_bits = 16,
61 .max_register = VEML6040_REG_W,
62 .val_format_endian = REGMAP_ENDIAN_LITTLE,
63 };
64
veml6040_read_raw(struct iio_dev * indio_dev,struct iio_chan_spec const * chan,int * val,int * val2,long mask)65 static int veml6040_read_raw(struct iio_dev *indio_dev,
66 struct iio_chan_spec const *chan, int *val,
67 int *val2, long mask)
68 {
69 int ret, reg, it_index;
70 struct veml6040_data *data = iio_priv(indio_dev);
71 struct regmap *regmap = data->regmap;
72 struct device *dev = &data->client->dev;
73
74 switch (mask) {
75 case IIO_CHAN_INFO_RAW:
76 ret = regmap_read(regmap, chan->address, ®);
77 if (ret) {
78 dev_err(dev, "Data read failed: %d\n", ret);
79 return ret;
80 }
81 *val = reg;
82 return IIO_VAL_INT;
83
84 case IIO_CHAN_INFO_INT_TIME:
85 ret = regmap_read(regmap, VEML6040_CONF_REG, ®);
86 if (ret) {
87 dev_err(dev, "Data read failed: %d\n", ret);
88 return ret;
89 }
90 it_index = FIELD_GET(VEML6040_CONF_IT_MSK, reg);
91 if (it_index >= ARRAY_SIZE(veml6040_it_ms)) {
92 dev_err(dev, "Invalid Integration Time Set");
93 return -EINVAL;
94 }
95 *val = veml6040_it_ms[it_index];
96 return IIO_VAL_INT;
97
98 default:
99 return -EINVAL;
100 }
101 }
102
veml6040_write_raw(struct iio_dev * indio_dev,struct iio_chan_spec const * chan,int val,int val2,long mask)103 static int veml6040_write_raw(struct iio_dev *indio_dev,
104 struct iio_chan_spec const *chan, int val,
105 int val2, long mask)
106 {
107 struct veml6040_data *data = iio_priv(indio_dev);
108
109 switch (mask) {
110 case IIO_CHAN_INFO_INT_TIME:
111 for (int i = 0; i < ARRAY_SIZE(veml6040_it_ms); i++) {
112 if (veml6040_it_ms[i] != val)
113 continue;
114
115 return regmap_update_bits(data->regmap,
116 VEML6040_CONF_REG,
117 VEML6040_CONF_IT_MSK,
118 FIELD_PREP(VEML6040_CONF_IT_MSK, i));
119 }
120 return -EINVAL;
121 default:
122 return -EINVAL;
123 }
124 }
125
veml6040_read_avail(struct iio_dev * indio_dev,struct iio_chan_spec const * chan,const int ** vals,int * type,int * length,long mask)126 static int veml6040_read_avail(struct iio_dev *indio_dev,
127 struct iio_chan_spec const *chan,
128 const int **vals, int *type, int *length,
129 long mask)
130 {
131 switch (mask) {
132 case IIO_CHAN_INFO_INT_TIME:
133 *length = ARRAY_SIZE(veml6040_it_ms);
134 *vals = veml6040_it_ms;
135 *type = IIO_VAL_INT;
136 return IIO_AVAIL_LIST;
137
138 default:
139 return -EINVAL;
140 }
141 }
142
143 static const struct iio_info veml6040_info = {
144 .read_raw = veml6040_read_raw,
145 .write_raw = veml6040_write_raw,
146 .read_avail = veml6040_read_avail,
147 };
148
149 static const struct iio_chan_spec veml6040_channels[] = {
150 {
151 .type = IIO_INTENSITY,
152 .address = VEML6040_REG_R,
153 .channel = CH_RED,
154 .channel2 = IIO_MOD_LIGHT_RED,
155 .modified = 1,
156 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
157 .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME),
158 .info_mask_shared_by_type_available =
159 BIT(IIO_CHAN_INFO_INT_TIME),
160 },
161 {
162 .type = IIO_INTENSITY,
163 .address = VEML6040_REG_G,
164 .channel = CH_GREEN,
165 .channel2 = IIO_MOD_LIGHT_GREEN,
166 .modified = 1,
167 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
168 .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME),
169 .info_mask_shared_by_type_available =
170 BIT(IIO_CHAN_INFO_INT_TIME),
171 },
172 {
173 .type = IIO_INTENSITY,
174 .address = VEML6040_REG_B,
175 .channel = CH_BLUE,
176 .channel2 = IIO_MOD_LIGHT_BLUE,
177 .modified = 1,
178 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
179 .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME),
180 .info_mask_shared_by_type_available =
181 BIT(IIO_CHAN_INFO_INT_TIME),
182 },
183 {
184 .type = IIO_INTENSITY,
185 .address = VEML6040_REG_W,
186 .channel = CH_WHITE,
187 .channel2 = IIO_MOD_LIGHT_CLEAR,
188 .modified = 1,
189 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
190 .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME),
191 .info_mask_shared_by_type_available =
192 BIT(IIO_CHAN_INFO_INT_TIME),
193 }
194 };
195
veml6040_shutdown_action(void * data)196 static void veml6040_shutdown_action(void *data)
197 {
198 struct veml6040_data *veml6040_data = data;
199
200 regmap_update_bits(veml6040_data->regmap, VEML6040_CONF_REG,
201 VEML6040_CONF_SD_MSK, VEML6040_CONF_SD_MSK);
202 }
203
veml6040_probe(struct i2c_client * client)204 static int veml6040_probe(struct i2c_client *client)
205 {
206 struct device *dev = &client->dev;
207 struct veml6040_data *data;
208 struct iio_dev *indio_dev;
209 struct regmap *regmap;
210 const int init_config =
211 FIELD_PREP(VEML6040_CONF_IT_MSK, VEML6040_CONF_IT_40_MS) |
212 FIELD_PREP(VEML6040_CONF_AF_MSK, 0) |
213 FIELD_PREP(VEML6040_CONF_SD_MSK, 0);
214 int ret;
215
216 if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
217 return dev_err_probe(dev, -EOPNOTSUPP,
218 "I2C adapter doesn't support plain I2C\n");
219
220 indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
221 if (!indio_dev)
222 return dev_err_probe(dev, -ENOMEM,
223 "IIO device allocation failed\n");
224
225 regmap = devm_regmap_init_i2c(client, &veml6040_regmap_config);
226 if (IS_ERR(regmap))
227 return dev_err_probe(dev, PTR_ERR(regmap),
228 "Regmap setup failed\n");
229
230 data = iio_priv(indio_dev);
231 i2c_set_clientdata(client, indio_dev);
232 data->client = client;
233 data->regmap = regmap;
234
235 indio_dev->name = "veml6040";
236 indio_dev->info = &veml6040_info;
237 indio_dev->channels = veml6040_channels;
238 indio_dev->num_channels = ARRAY_SIZE(veml6040_channels);
239 indio_dev->modes = INDIO_DIRECT_MODE;
240
241 ret = devm_regulator_get_enable(dev, "vdd");
242 if (ret)
243 return ret;
244
245 ret = regmap_write(regmap, VEML6040_CONF_REG, init_config);
246 if (ret)
247 return dev_err_probe(dev, ret,
248 "Could not set initial config\n");
249
250 ret = devm_add_action_or_reset(dev, veml6040_shutdown_action, data);
251 if (ret)
252 return ret;
253
254 return devm_iio_device_register(dev, indio_dev);
255 }
256
257 static const struct i2c_device_id veml6040_id_table[] = {
258 {"veml6040"},
259 {}
260 };
261 MODULE_DEVICE_TABLE(i2c, veml6040_id_table);
262
263 static const struct of_device_id veml6040_of_match[] = {
264 {.compatible = "vishay,veml6040"},
265 {}
266 };
267 MODULE_DEVICE_TABLE(of, veml6040_of_match);
268
269 static struct i2c_driver veml6040_driver = {
270 .probe = veml6040_probe,
271 .id_table = veml6040_id_table,
272 .driver = {
273 .name = "veml6040",
274 .of_match_table = veml6040_of_match,
275 },
276 };
277 module_i2c_driver(veml6040_driver);
278
279 MODULE_DESCRIPTION("veml6040 RGBW light sensor driver");
280 MODULE_AUTHOR("Arthur Becker <arthur.becker@sentec.com>");
281 MODULE_LICENSE("GPL");
282