1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * si1133.c - Support for Silabs SI1133 combined ambient |
4 | * light and UV index sensors |
5 | * |
6 | * Copyright 2018 Maxime Roussin-Belanger <maxime.roussinbelanger@gmail.com> |
7 | */ |
8 | |
9 | #include <linux/delay.h> |
10 | #include <linux/i2c.h> |
11 | #include <linux/interrupt.h> |
12 | #include <linux/module.h> |
13 | #include <linux/regmap.h> |
14 | |
15 | #include <linux/iio/iio.h> |
16 | #include <linux/iio/sysfs.h> |
17 | |
18 | #include <linux/util_macros.h> |
19 | |
20 | #include <asm/unaligned.h> |
21 | |
22 | #define SI1133_REG_PART_ID 0x00 |
23 | #define SI1133_REG_REV_ID 0x01 |
24 | #define SI1133_REG_MFR_ID 0x02 |
25 | #define SI1133_REG_INFO0 0x03 |
26 | #define SI1133_REG_INFO1 0x04 |
27 | |
28 | #define SI1133_PART_ID 0x33 |
29 | |
30 | #define SI1133_REG_HOSTIN0 0x0A |
31 | #define SI1133_REG_COMMAND 0x0B |
32 | #define SI1133_REG_IRQ_ENABLE 0x0F |
33 | #define SI1133_REG_RESPONSE1 0x10 |
34 | #define SI1133_REG_RESPONSE0 0x11 |
35 | #define SI1133_REG_IRQ_STATUS 0x12 |
36 | #define SI1133_REG_MEAS_RATE 0x1A |
37 | |
38 | #define SI1133_IRQ_CHANNEL_ENABLE 0xF |
39 | |
40 | #define SI1133_CMD_RESET_CTR 0x00 |
41 | #define SI1133_CMD_RESET_SW 0x01 |
42 | #define SI1133_CMD_FORCE 0x11 |
43 | #define SI1133_CMD_START_AUTONOMOUS 0x13 |
44 | #define SI1133_CMD_PARAM_SET 0x80 |
45 | #define SI1133_CMD_PARAM_QUERY 0x40 |
46 | #define SI1133_CMD_PARAM_MASK 0x3F |
47 | |
48 | #define SI1133_CMD_ERR_MASK BIT(4) |
49 | #define SI1133_CMD_SEQ_MASK 0xF |
50 | #define SI1133_MAX_CMD_CTR 0xF |
51 | |
52 | #define SI1133_PARAM_REG_CHAN_LIST 0x01 |
53 | #define SI1133_PARAM_REG_ADCCONFIG(x) ((x) * 4) + 2 |
54 | #define SI1133_PARAM_REG_ADCSENS(x) ((x) * 4) + 3 |
55 | #define SI1133_PARAM_REG_ADCPOST(x) ((x) * 4) + 4 |
56 | |
57 | #define SI1133_ADCMUX_MASK 0x1F |
58 | |
59 | #define SI1133_ADCCONFIG_DECIM_RATE(x) (x) << 5 |
60 | |
61 | #define SI1133_ADCSENS_SCALE_MASK 0x70 |
62 | #define SI1133_ADCSENS_SCALE_SHIFT 4 |
63 | #define SI1133_ADCSENS_HSIG_MASK BIT(7) |
64 | #define SI1133_ADCSENS_HSIG_SHIFT 7 |
65 | #define SI1133_ADCSENS_HW_GAIN_MASK 0xF |
66 | #define SI1133_ADCSENS_NB_MEAS(x) fls(x) << SI1133_ADCSENS_SCALE_SHIFT |
67 | |
68 | #define SI1133_ADCPOST_24BIT_EN BIT(6) |
69 | #define SI1133_ADCPOST_POSTSHIFT_BITQTY(x) (x & GENMASK(2, 0)) << 3 |
70 | |
71 | #define SI1133_PARAM_ADCMUX_SMALL_IR 0x0 |
72 | #define SI1133_PARAM_ADCMUX_MED_IR 0x1 |
73 | #define SI1133_PARAM_ADCMUX_LARGE_IR 0x2 |
74 | #define SI1133_PARAM_ADCMUX_WHITE 0xB |
75 | #define SI1133_PARAM_ADCMUX_LARGE_WHITE 0xD |
76 | #define SI1133_PARAM_ADCMUX_UV 0x18 |
77 | #define SI1133_PARAM_ADCMUX_UV_DEEP 0x19 |
78 | |
79 | #define SI1133_ERR_INVALID_CMD 0x0 |
80 | #define SI1133_ERR_INVALID_LOCATION_CMD 0x1 |
81 | #define SI1133_ERR_SATURATION_ADC_OR_OVERFLOW_ACCUMULATION 0x2 |
82 | #define SI1133_ERR_OUTPUT_BUFFER_OVERFLOW 0x3 |
83 | |
84 | #define SI1133_COMPLETION_TIMEOUT_MS 500 |
85 | |
86 | #define SI1133_CMD_MINSLEEP_US_LOW 5000 |
87 | #define SI1133_CMD_MINSLEEP_US_HIGH 7500 |
88 | #define SI1133_CMD_TIMEOUT_MS 25 |
89 | #define SI1133_CMD_LUX_TIMEOUT_MS 5000 |
90 | #define SI1133_CMD_TIMEOUT_US SI1133_CMD_TIMEOUT_MS * 1000 |
91 | |
92 | #define SI1133_REG_HOSTOUT(x) (x) + 0x13 |
93 | |
94 | #define SI1133_MEASUREMENT_FREQUENCY 1250 |
95 | |
96 | #define SI1133_X_ORDER_MASK 0x0070 |
97 | #define SI1133_Y_ORDER_MASK 0x0007 |
98 | #define si1133_get_x_order(m) ((m) & SI1133_X_ORDER_MASK) >> 4 |
99 | #define si1133_get_y_order(m) ((m) & SI1133_Y_ORDER_MASK) |
100 | |
101 | #define SI1133_LUX_ADC_MASK 0xE |
102 | #define SI1133_ADC_THRESHOLD 16000 |
103 | #define SI1133_INPUT_FRACTION_HIGH 7 |
104 | #define SI1133_INPUT_FRACTION_LOW 15 |
105 | #define SI1133_LUX_OUTPUT_FRACTION 12 |
106 | #define SI1133_LUX_BUFFER_SIZE 9 |
107 | #define SI1133_MEASURE_BUFFER_SIZE 3 |
108 | |
109 | static const int si1133_scale_available[] = { |
110 | 1, 2, 4, 8, 16, 32, 64, 128}; |
111 | |
112 | static IIO_CONST_ATTR(scale_available, "1 2 4 8 16 32 64 128" ); |
113 | |
114 | static IIO_CONST_ATTR_INT_TIME_AVAIL("0.0244 0.0488 0.0975 0.195 0.390 0.780 " |
115 | "1.560 3.120 6.24 12.48 25.0 50.0" ); |
116 | |
117 | /* A.K.A. HW_GAIN in datasheet */ |
118 | enum si1133_int_time { |
119 | _24_4_us = 0, |
120 | _48_8_us = 1, |
121 | _97_5_us = 2, |
122 | _195_0_us = 3, |
123 | _390_0_us = 4, |
124 | _780_0_us = 5, |
125 | _1_560_0_us = 6, |
126 | _3_120_0_us = 7, |
127 | _6_240_0_us = 8, |
128 | _12_480_0_us = 9, |
129 | _25_ms = 10, |
130 | _50_ms = 11, |
131 | }; |
132 | |
133 | /* Integration time in milliseconds, nanoseconds */ |
134 | static const int si1133_int_time_table[][2] = { |
135 | [_24_4_us] = {0, 24400}, |
136 | [_48_8_us] = {0, 48800}, |
137 | [_97_5_us] = {0, 97500}, |
138 | [_195_0_us] = {0, 195000}, |
139 | [_390_0_us] = {0, 390000}, |
140 | [_780_0_us] = {0, 780000}, |
141 | [_1_560_0_us] = {1, 560000}, |
142 | [_3_120_0_us] = {3, 120000}, |
143 | [_6_240_0_us] = {6, 240000}, |
144 | [_12_480_0_us] = {12, 480000}, |
145 | [_25_ms] = {25, 000000}, |
146 | [_50_ms] = {50, 000000}, |
147 | }; |
148 | |
149 | static const struct regmap_range si1133_reg_ranges[] = { |
150 | regmap_reg_range(0x00, 0x02), |
151 | regmap_reg_range(0x0A, 0x0B), |
152 | regmap_reg_range(0x0F, 0x0F), |
153 | regmap_reg_range(0x10, 0x12), |
154 | regmap_reg_range(0x13, 0x2C), |
155 | }; |
156 | |
157 | static const struct regmap_range si1133_reg_ro_ranges[] = { |
158 | regmap_reg_range(0x00, 0x02), |
159 | regmap_reg_range(0x10, 0x2C), |
160 | }; |
161 | |
162 | static const struct regmap_range si1133_precious_ranges[] = { |
163 | regmap_reg_range(0x12, 0x12), |
164 | }; |
165 | |
166 | static const struct regmap_access_table si1133_write_ranges_table = { |
167 | .yes_ranges = si1133_reg_ranges, |
168 | .n_yes_ranges = ARRAY_SIZE(si1133_reg_ranges), |
169 | .no_ranges = si1133_reg_ro_ranges, |
170 | .n_no_ranges = ARRAY_SIZE(si1133_reg_ro_ranges), |
171 | }; |
172 | |
173 | static const struct regmap_access_table si1133_read_ranges_table = { |
174 | .yes_ranges = si1133_reg_ranges, |
175 | .n_yes_ranges = ARRAY_SIZE(si1133_reg_ranges), |
176 | }; |
177 | |
178 | static const struct regmap_access_table si1133_precious_table = { |
179 | .yes_ranges = si1133_precious_ranges, |
180 | .n_yes_ranges = ARRAY_SIZE(si1133_precious_ranges), |
181 | }; |
182 | |
183 | static const struct regmap_config si1133_regmap_config = { |
184 | .reg_bits = 8, |
185 | .val_bits = 8, |
186 | |
187 | .max_register = 0x2C, |
188 | |
189 | .wr_table = &si1133_write_ranges_table, |
190 | .rd_table = &si1133_read_ranges_table, |
191 | |
192 | .precious_table = &si1133_precious_table, |
193 | }; |
194 | |
195 | struct si1133_data { |
196 | struct regmap *regmap; |
197 | struct i2c_client *client; |
198 | |
199 | /* Lock protecting one command at a time can be processed */ |
200 | struct mutex mutex; |
201 | |
202 | int rsp_seq; |
203 | u8 scan_mask; |
204 | u8 adc_sens[6]; |
205 | u8 adc_config[6]; |
206 | |
207 | struct completion completion; |
208 | }; |
209 | |
210 | struct si1133_coeff { |
211 | s16 info; |
212 | u16 mag; |
213 | }; |
214 | |
215 | struct si1133_lux_coeff { |
216 | struct si1133_coeff coeff_high[4]; |
217 | struct si1133_coeff coeff_low[9]; |
218 | }; |
219 | |
220 | static const struct si1133_lux_coeff lux_coeff = { |
221 | { |
222 | { 0, 209}, |
223 | { 1665, 93}, |
224 | { 2064, 65}, |
225 | {-2671, 234} |
226 | }, |
227 | { |
228 | { 0, 0}, |
229 | { 1921, 29053}, |
230 | {-1022, 36363}, |
231 | { 2320, 20789}, |
232 | { -367, 57909}, |
233 | {-1774, 38240}, |
234 | { -608, 46775}, |
235 | {-1503, 51831}, |
236 | {-1886, 58928} |
237 | } |
238 | }; |
239 | |
240 | static int si1133_calculate_polynomial_inner(s32 input, u8 fraction, u16 mag, |
241 | s8 shift) |
242 | { |
243 | return ((input << fraction) / mag) << shift; |
244 | } |
245 | |
246 | static int si1133_calculate_output(s32 x, s32 y, u8 x_order, u8 y_order, |
247 | u8 input_fraction, s8 sign, |
248 | const struct si1133_coeff *coeffs) |
249 | { |
250 | s8 shift; |
251 | int x1 = 1; |
252 | int x2 = 1; |
253 | int y1 = 1; |
254 | int y2 = 1; |
255 | |
256 | shift = ((u16)coeffs->info & 0xFF00) >> 8; |
257 | shift ^= 0xFF; |
258 | shift += 1; |
259 | shift = -shift; |
260 | |
261 | if (x_order > 0) { |
262 | x1 = si1133_calculate_polynomial_inner(input: x, fraction: input_fraction, |
263 | mag: coeffs->mag, shift); |
264 | if (x_order > 1) |
265 | x2 = x1; |
266 | } |
267 | |
268 | if (y_order > 0) { |
269 | y1 = si1133_calculate_polynomial_inner(input: y, fraction: input_fraction, |
270 | mag: coeffs->mag, shift); |
271 | if (y_order > 1) |
272 | y2 = y1; |
273 | } |
274 | |
275 | return sign * x1 * x2 * y1 * y2; |
276 | } |
277 | |
278 | /* |
279 | * The algorithm is from: |
280 | * https://siliconlabs.github.io/Gecko_SDK_Doc/efm32zg/html/si1133_8c_source.html#l00716 |
281 | */ |
282 | static int si1133_calc_polynomial(s32 x, s32 y, u8 input_fraction, u8 num_coeff, |
283 | const struct si1133_coeff *coeffs) |
284 | { |
285 | u8 x_order, y_order; |
286 | u8 counter; |
287 | s8 sign; |
288 | int output = 0; |
289 | |
290 | for (counter = 0; counter < num_coeff; counter++) { |
291 | if (coeffs->info < 0) |
292 | sign = -1; |
293 | else |
294 | sign = 1; |
295 | |
296 | x_order = si1133_get_x_order(coeffs->info); |
297 | y_order = si1133_get_y_order(coeffs->info); |
298 | |
299 | if ((x_order == 0) && (y_order == 0)) |
300 | output += |
301 | sign * coeffs->mag << SI1133_LUX_OUTPUT_FRACTION; |
302 | else |
303 | output += si1133_calculate_output(x, y, x_order, |
304 | y_order, |
305 | input_fraction, sign, |
306 | coeffs); |
307 | coeffs++; |
308 | } |
309 | |
310 | return abs(output); |
311 | } |
312 | |
313 | static int si1133_cmd_reset_sw(struct si1133_data *data) |
314 | { |
315 | struct device *dev = &data->client->dev; |
316 | unsigned int resp; |
317 | unsigned long timeout; |
318 | int err; |
319 | |
320 | err = regmap_write(map: data->regmap, SI1133_REG_COMMAND, |
321 | SI1133_CMD_RESET_SW); |
322 | if (err) |
323 | return err; |
324 | |
325 | timeout = jiffies + msecs_to_jiffies(SI1133_CMD_TIMEOUT_MS); |
326 | while (true) { |
327 | err = regmap_read(map: data->regmap, SI1133_REG_RESPONSE0, val: &resp); |
328 | if (err == -ENXIO) { |
329 | usleep_range(SI1133_CMD_MINSLEEP_US_LOW, |
330 | SI1133_CMD_MINSLEEP_US_HIGH); |
331 | continue; |
332 | } |
333 | |
334 | if ((resp & SI1133_MAX_CMD_CTR) == SI1133_MAX_CMD_CTR) |
335 | break; |
336 | |
337 | if (time_after(jiffies, timeout)) { |
338 | dev_warn(dev, "Timeout on reset ctr resp: %d\n" , resp); |
339 | return -ETIMEDOUT; |
340 | } |
341 | } |
342 | |
343 | if (!err) |
344 | data->rsp_seq = SI1133_MAX_CMD_CTR; |
345 | |
346 | return err; |
347 | } |
348 | |
349 | static int si1133_parse_response_err(struct device *dev, u32 resp, u8 cmd) |
350 | { |
351 | resp &= 0xF; |
352 | |
353 | switch (resp) { |
354 | case SI1133_ERR_OUTPUT_BUFFER_OVERFLOW: |
355 | dev_warn(dev, "Output buffer overflow: 0x%02x\n" , cmd); |
356 | return -EOVERFLOW; |
357 | case SI1133_ERR_SATURATION_ADC_OR_OVERFLOW_ACCUMULATION: |
358 | dev_warn(dev, "Saturation of the ADC or overflow of accumulation: 0x%02x\n" , |
359 | cmd); |
360 | return -EOVERFLOW; |
361 | case SI1133_ERR_INVALID_LOCATION_CMD: |
362 | dev_warn(dev, |
363 | "Parameter access to an invalid location: 0x%02x\n" , |
364 | cmd); |
365 | return -EINVAL; |
366 | case SI1133_ERR_INVALID_CMD: |
367 | dev_warn(dev, "Invalid command 0x%02x\n" , cmd); |
368 | return -EINVAL; |
369 | default: |
370 | dev_warn(dev, "Unknown error 0x%02x\n" , cmd); |
371 | return -EINVAL; |
372 | } |
373 | } |
374 | |
375 | static int si1133_cmd_reset_counter(struct si1133_data *data) |
376 | { |
377 | int err = regmap_write(map: data->regmap, SI1133_REG_COMMAND, |
378 | SI1133_CMD_RESET_CTR); |
379 | if (err) |
380 | return err; |
381 | |
382 | data->rsp_seq = 0; |
383 | |
384 | return 0; |
385 | } |
386 | |
387 | static int si1133_command(struct si1133_data *data, u8 cmd) |
388 | { |
389 | struct device *dev = &data->client->dev; |
390 | u32 resp; |
391 | int err; |
392 | int expected_seq; |
393 | |
394 | mutex_lock(&data->mutex); |
395 | |
396 | expected_seq = (data->rsp_seq + 1) & SI1133_MAX_CMD_CTR; |
397 | |
398 | if (cmd == SI1133_CMD_FORCE) |
399 | reinit_completion(x: &data->completion); |
400 | |
401 | err = regmap_write(map: data->regmap, SI1133_REG_COMMAND, val: cmd); |
402 | if (err) { |
403 | dev_warn(dev, "Failed to write command 0x%02x, ret=%d\n" , cmd, |
404 | err); |
405 | goto out; |
406 | } |
407 | |
408 | if (cmd == SI1133_CMD_FORCE) { |
409 | /* wait for irq */ |
410 | if (!wait_for_completion_timeout(x: &data->completion, |
411 | timeout: msecs_to_jiffies(SI1133_COMPLETION_TIMEOUT_MS))) { |
412 | err = -ETIMEDOUT; |
413 | goto out; |
414 | } |
415 | err = regmap_read(map: data->regmap, SI1133_REG_RESPONSE0, val: &resp); |
416 | if (err) |
417 | goto out; |
418 | } else { |
419 | err = regmap_read_poll_timeout(data->regmap, |
420 | SI1133_REG_RESPONSE0, resp, |
421 | (resp & SI1133_CMD_SEQ_MASK) == |
422 | expected_seq || |
423 | (resp & SI1133_CMD_ERR_MASK), |
424 | SI1133_CMD_MINSLEEP_US_LOW, |
425 | SI1133_CMD_TIMEOUT_MS * 1000); |
426 | if (err) { |
427 | dev_warn(dev, |
428 | "Failed to read command 0x%02x, ret=%d\n" , |
429 | cmd, err); |
430 | goto out; |
431 | } |
432 | } |
433 | |
434 | if (resp & SI1133_CMD_ERR_MASK) { |
435 | err = si1133_parse_response_err(dev, resp, cmd); |
436 | si1133_cmd_reset_counter(data); |
437 | } else { |
438 | data->rsp_seq = expected_seq; |
439 | } |
440 | |
441 | out: |
442 | mutex_unlock(lock: &data->mutex); |
443 | |
444 | return err; |
445 | } |
446 | |
447 | static int si1133_param_set(struct si1133_data *data, u8 param, u32 value) |
448 | { |
449 | int err = regmap_write(map: data->regmap, SI1133_REG_HOSTIN0, val: value); |
450 | |
451 | if (err) |
452 | return err; |
453 | |
454 | return si1133_command(data, SI1133_CMD_PARAM_SET | |
455 | (param & SI1133_CMD_PARAM_MASK)); |
456 | } |
457 | |
458 | static int si1133_param_query(struct si1133_data *data, u8 param, u32 *result) |
459 | { |
460 | int err = si1133_command(data, SI1133_CMD_PARAM_QUERY | |
461 | (param & SI1133_CMD_PARAM_MASK)); |
462 | if (err) |
463 | return err; |
464 | |
465 | return regmap_read(map: data->regmap, SI1133_REG_RESPONSE1, val: result); |
466 | } |
467 | |
468 | #define SI1133_CHANNEL(_ch, _type) \ |
469 | .type = _type, \ |
470 | .channel = _ch, \ |
471 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ |
472 | .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | \ |
473 | BIT(IIO_CHAN_INFO_SCALE) | \ |
474 | BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ |
475 | |
476 | static const struct iio_chan_spec si1133_channels[] = { |
477 | { |
478 | .type = IIO_LIGHT, |
479 | .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), |
480 | .channel = 0, |
481 | }, |
482 | { |
483 | SI1133_CHANNEL(SI1133_PARAM_ADCMUX_WHITE, IIO_INTENSITY) |
484 | .channel2 = IIO_MOD_LIGHT_BOTH, |
485 | }, |
486 | { |
487 | SI1133_CHANNEL(SI1133_PARAM_ADCMUX_LARGE_WHITE, IIO_INTENSITY) |
488 | .channel2 = IIO_MOD_LIGHT_BOTH, |
489 | .extend_name = "large" , |
490 | }, |
491 | { |
492 | SI1133_CHANNEL(SI1133_PARAM_ADCMUX_SMALL_IR, IIO_INTENSITY) |
493 | .extend_name = "small" , |
494 | .modified = 1, |
495 | .channel2 = IIO_MOD_LIGHT_IR, |
496 | }, |
497 | { |
498 | SI1133_CHANNEL(SI1133_PARAM_ADCMUX_MED_IR, IIO_INTENSITY) |
499 | .modified = 1, |
500 | .channel2 = IIO_MOD_LIGHT_IR, |
501 | }, |
502 | { |
503 | SI1133_CHANNEL(SI1133_PARAM_ADCMUX_LARGE_IR, IIO_INTENSITY) |
504 | .extend_name = "large" , |
505 | .modified = 1, |
506 | .channel2 = IIO_MOD_LIGHT_IR, |
507 | }, |
508 | { |
509 | SI1133_CHANNEL(SI1133_PARAM_ADCMUX_UV, IIO_UVINDEX) |
510 | }, |
511 | { |
512 | SI1133_CHANNEL(SI1133_PARAM_ADCMUX_UV_DEEP, IIO_UVINDEX) |
513 | .modified = 1, |
514 | .channel2 = IIO_MOD_LIGHT_DUV, |
515 | } |
516 | }; |
517 | |
518 | static int si1133_get_int_time_index(int milliseconds, int nanoseconds) |
519 | { |
520 | int i; |
521 | |
522 | for (i = 0; i < ARRAY_SIZE(si1133_int_time_table); i++) { |
523 | if (milliseconds == si1133_int_time_table[i][0] && |
524 | nanoseconds == si1133_int_time_table[i][1]) |
525 | return i; |
526 | } |
527 | return -EINVAL; |
528 | } |
529 | |
530 | static int si1133_set_integration_time(struct si1133_data *data, u8 adc, |
531 | int milliseconds, int nanoseconds) |
532 | { |
533 | int index; |
534 | |
535 | index = si1133_get_int_time_index(milliseconds, nanoseconds); |
536 | if (index < 0) |
537 | return index; |
538 | |
539 | data->adc_sens[adc] &= 0xF0; |
540 | data->adc_sens[adc] |= index; |
541 | |
542 | return si1133_param_set(data, SI1133_PARAM_REG_ADCSENS(0), |
543 | value: data->adc_sens[adc]); |
544 | } |
545 | |
546 | static int si1133_set_chlist(struct si1133_data *data, u8 scan_mask) |
547 | { |
548 | /* channel list already set, no need to reprogram */ |
549 | if (data->scan_mask == scan_mask) |
550 | return 0; |
551 | |
552 | data->scan_mask = scan_mask; |
553 | |
554 | return si1133_param_set(data, SI1133_PARAM_REG_CHAN_LIST, value: scan_mask); |
555 | } |
556 | |
557 | static int si1133_chan_set_adcconfig(struct si1133_data *data, u8 adc, |
558 | u8 adc_config) |
559 | { |
560 | int err; |
561 | |
562 | err = si1133_param_set(data, SI1133_PARAM_REG_ADCCONFIG(adc), |
563 | value: adc_config); |
564 | if (err) |
565 | return err; |
566 | |
567 | data->adc_config[adc] = adc_config; |
568 | |
569 | return 0; |
570 | } |
571 | |
572 | static int si1133_update_adcconfig(struct si1133_data *data, uint8_t adc, |
573 | u8 mask, u8 shift, u8 value) |
574 | { |
575 | u32 adc_config; |
576 | int err; |
577 | |
578 | err = si1133_param_query(data, SI1133_PARAM_REG_ADCCONFIG(adc), |
579 | result: &adc_config); |
580 | if (err) |
581 | return err; |
582 | |
583 | adc_config &= ~mask; |
584 | adc_config |= (value << shift); |
585 | |
586 | return si1133_chan_set_adcconfig(data, adc, adc_config); |
587 | } |
588 | |
589 | static int si1133_set_adcmux(struct si1133_data *data, u8 adc, u8 mux) |
590 | { |
591 | if ((mux & data->adc_config[adc]) == mux) |
592 | return 0; /* mux already set to correct value */ |
593 | |
594 | return si1133_update_adcconfig(data, adc, SI1133_ADCMUX_MASK, shift: 0, value: mux); |
595 | } |
596 | |
597 | static int si1133_force_measurement(struct si1133_data *data) |
598 | { |
599 | return si1133_command(data, SI1133_CMD_FORCE); |
600 | } |
601 | |
602 | static int si1133_bulk_read(struct si1133_data *data, u8 start_reg, u8 length, |
603 | u8 *buffer) |
604 | { |
605 | int err; |
606 | |
607 | err = si1133_force_measurement(data); |
608 | if (err) |
609 | return err; |
610 | |
611 | return regmap_bulk_read(map: data->regmap, reg: start_reg, val: buffer, val_count: length); |
612 | } |
613 | |
614 | static int si1133_measure(struct si1133_data *data, |
615 | struct iio_chan_spec const *chan, |
616 | int *val) |
617 | { |
618 | int err; |
619 | |
620 | u8 buffer[SI1133_MEASURE_BUFFER_SIZE]; |
621 | |
622 | err = si1133_set_adcmux(data, adc: 0, mux: chan->channel); |
623 | if (err) |
624 | return err; |
625 | |
626 | /* Deactivate lux measurements if they were active */ |
627 | err = si1133_set_chlist(data, BIT(0)); |
628 | if (err) |
629 | return err; |
630 | |
631 | err = si1133_bulk_read(data, SI1133_REG_HOSTOUT(0), length: sizeof(buffer), |
632 | buffer); |
633 | if (err) |
634 | return err; |
635 | |
636 | *val = sign_extend32(value: get_unaligned_be24(p: &buffer[0]), index: 23); |
637 | |
638 | return err; |
639 | } |
640 | |
641 | static irqreturn_t si1133_threaded_irq_handler(int irq, void *private) |
642 | { |
643 | struct iio_dev *iio_dev = private; |
644 | struct si1133_data *data = iio_priv(indio_dev: iio_dev); |
645 | u32 irq_status; |
646 | int err; |
647 | |
648 | err = regmap_read(map: data->regmap, SI1133_REG_IRQ_STATUS, val: &irq_status); |
649 | if (err) { |
650 | dev_err_ratelimited(&iio_dev->dev, "Error reading IRQ\n" ); |
651 | goto out; |
652 | } |
653 | |
654 | if (irq_status != data->scan_mask) |
655 | return IRQ_NONE; |
656 | |
657 | out: |
658 | complete(&data->completion); |
659 | |
660 | return IRQ_HANDLED; |
661 | } |
662 | |
663 | static int si1133_scale_to_swgain(int scale_integer, int scale_fractional) |
664 | { |
665 | scale_integer = find_closest(scale_integer, si1133_scale_available, |
666 | ARRAY_SIZE(si1133_scale_available)); |
667 | if (scale_integer < 0 || |
668 | scale_integer > ARRAY_SIZE(si1133_scale_available) || |
669 | scale_fractional != 0) |
670 | return -EINVAL; |
671 | |
672 | return scale_integer; |
673 | } |
674 | |
675 | static int si1133_chan_set_adcsens(struct si1133_data *data, u8 adc, |
676 | u8 adc_sens) |
677 | { |
678 | int err; |
679 | |
680 | err = si1133_param_set(data, SI1133_PARAM_REG_ADCSENS(adc), value: adc_sens); |
681 | if (err) |
682 | return err; |
683 | |
684 | data->adc_sens[adc] = adc_sens; |
685 | |
686 | return 0; |
687 | } |
688 | |
689 | static int si1133_update_adcsens(struct si1133_data *data, u8 mask, |
690 | u8 shift, u8 value) |
691 | { |
692 | int err; |
693 | u32 adc_sens; |
694 | |
695 | err = si1133_param_query(data, SI1133_PARAM_REG_ADCSENS(0), |
696 | result: &adc_sens); |
697 | if (err) |
698 | return err; |
699 | |
700 | adc_sens &= ~mask; |
701 | adc_sens |= (value << shift); |
702 | |
703 | return si1133_chan_set_adcsens(data, adc: 0, adc_sens); |
704 | } |
705 | |
706 | static int si1133_get_lux(struct si1133_data *data, int *val) |
707 | { |
708 | int err; |
709 | int lux; |
710 | s32 high_vis; |
711 | s32 low_vis; |
712 | s32 ir; |
713 | u8 buffer[SI1133_LUX_BUFFER_SIZE]; |
714 | |
715 | /* Activate lux channels */ |
716 | err = si1133_set_chlist(data, SI1133_LUX_ADC_MASK); |
717 | if (err) |
718 | return err; |
719 | |
720 | err = si1133_bulk_read(data, SI1133_REG_HOSTOUT(0), |
721 | SI1133_LUX_BUFFER_SIZE, buffer); |
722 | if (err) |
723 | return err; |
724 | |
725 | high_vis = sign_extend32(value: get_unaligned_be24(p: &buffer[0]), index: 23); |
726 | |
727 | low_vis = sign_extend32(value: get_unaligned_be24(p: &buffer[3]), index: 23); |
728 | |
729 | ir = sign_extend32(value: get_unaligned_be24(p: &buffer[6]), index: 23); |
730 | |
731 | if (high_vis > SI1133_ADC_THRESHOLD || ir > SI1133_ADC_THRESHOLD) |
732 | lux = si1133_calc_polynomial(x: high_vis, y: ir, |
733 | SI1133_INPUT_FRACTION_HIGH, |
734 | ARRAY_SIZE(lux_coeff.coeff_high), |
735 | coeffs: &lux_coeff.coeff_high[0]); |
736 | else |
737 | lux = si1133_calc_polynomial(x: low_vis, y: ir, |
738 | SI1133_INPUT_FRACTION_LOW, |
739 | ARRAY_SIZE(lux_coeff.coeff_low), |
740 | coeffs: &lux_coeff.coeff_low[0]); |
741 | |
742 | *val = lux >> SI1133_LUX_OUTPUT_FRACTION; |
743 | |
744 | return err; |
745 | } |
746 | |
747 | static int si1133_read_raw(struct iio_dev *iio_dev, |
748 | struct iio_chan_spec const *chan, |
749 | int *val, int *val2, long mask) |
750 | { |
751 | struct si1133_data *data = iio_priv(indio_dev: iio_dev); |
752 | u8 adc_sens = data->adc_sens[0]; |
753 | int err; |
754 | |
755 | switch (mask) { |
756 | case IIO_CHAN_INFO_PROCESSED: |
757 | switch (chan->type) { |
758 | case IIO_LIGHT: |
759 | err = si1133_get_lux(data, val); |
760 | if (err) |
761 | return err; |
762 | |
763 | return IIO_VAL_INT; |
764 | default: |
765 | return -EINVAL; |
766 | } |
767 | case IIO_CHAN_INFO_RAW: |
768 | switch (chan->type) { |
769 | case IIO_INTENSITY: |
770 | case IIO_UVINDEX: |
771 | err = si1133_measure(data, chan, val); |
772 | if (err) |
773 | return err; |
774 | |
775 | return IIO_VAL_INT; |
776 | default: |
777 | return -EINVAL; |
778 | } |
779 | case IIO_CHAN_INFO_INT_TIME: |
780 | switch (chan->type) { |
781 | case IIO_INTENSITY: |
782 | case IIO_UVINDEX: |
783 | adc_sens &= SI1133_ADCSENS_HW_GAIN_MASK; |
784 | |
785 | *val = si1133_int_time_table[adc_sens][0]; |
786 | *val2 = si1133_int_time_table[adc_sens][1]; |
787 | return IIO_VAL_INT_PLUS_MICRO; |
788 | default: |
789 | return -EINVAL; |
790 | } |
791 | case IIO_CHAN_INFO_SCALE: |
792 | switch (chan->type) { |
793 | case IIO_INTENSITY: |
794 | case IIO_UVINDEX: |
795 | adc_sens &= SI1133_ADCSENS_SCALE_MASK; |
796 | adc_sens >>= SI1133_ADCSENS_SCALE_SHIFT; |
797 | |
798 | *val = BIT(adc_sens); |
799 | |
800 | return IIO_VAL_INT; |
801 | default: |
802 | return -EINVAL; |
803 | } |
804 | case IIO_CHAN_INFO_HARDWAREGAIN: |
805 | switch (chan->type) { |
806 | case IIO_INTENSITY: |
807 | case IIO_UVINDEX: |
808 | adc_sens >>= SI1133_ADCSENS_HSIG_SHIFT; |
809 | |
810 | *val = adc_sens; |
811 | |
812 | return IIO_VAL_INT; |
813 | default: |
814 | return -EINVAL; |
815 | } |
816 | default: |
817 | return -EINVAL; |
818 | } |
819 | } |
820 | |
821 | static int si1133_write_raw(struct iio_dev *iio_dev, |
822 | struct iio_chan_spec const *chan, |
823 | int val, int val2, long mask) |
824 | { |
825 | struct si1133_data *data = iio_priv(indio_dev: iio_dev); |
826 | |
827 | switch (mask) { |
828 | case IIO_CHAN_INFO_SCALE: |
829 | switch (chan->type) { |
830 | case IIO_INTENSITY: |
831 | case IIO_UVINDEX: |
832 | val = si1133_scale_to_swgain(scale_integer: val, scale_fractional: val2); |
833 | if (val < 0) |
834 | return val; |
835 | |
836 | return si1133_update_adcsens(data, |
837 | SI1133_ADCSENS_SCALE_MASK, |
838 | SI1133_ADCSENS_SCALE_SHIFT, |
839 | value: val); |
840 | default: |
841 | return -EINVAL; |
842 | } |
843 | case IIO_CHAN_INFO_INT_TIME: |
844 | return si1133_set_integration_time(data, adc: 0, milliseconds: val, nanoseconds: val2); |
845 | case IIO_CHAN_INFO_HARDWAREGAIN: |
846 | switch (chan->type) { |
847 | case IIO_INTENSITY: |
848 | case IIO_UVINDEX: |
849 | if (val != 0 && val != 1) |
850 | return -EINVAL; |
851 | |
852 | return si1133_update_adcsens(data, |
853 | SI1133_ADCSENS_HSIG_MASK, |
854 | SI1133_ADCSENS_HSIG_SHIFT, |
855 | value: val); |
856 | default: |
857 | return -EINVAL; |
858 | } |
859 | default: |
860 | return -EINVAL; |
861 | } |
862 | } |
863 | |
864 | static struct attribute *si1133_attributes[] = { |
865 | &iio_const_attr_integration_time_available.dev_attr.attr, |
866 | &iio_const_attr_scale_available.dev_attr.attr, |
867 | NULL, |
868 | }; |
869 | |
870 | static const struct attribute_group si1133_attribute_group = { |
871 | .attrs = si1133_attributes, |
872 | }; |
873 | |
874 | static const struct iio_info si1133_info = { |
875 | .read_raw = si1133_read_raw, |
876 | .write_raw = si1133_write_raw, |
877 | .attrs = &si1133_attribute_group, |
878 | }; |
879 | |
880 | /* |
881 | * si1133_init_lux_channels - Configure 3 different channels(adc) (1,2 and 3) |
882 | * The channel configuration for the lux measurement was taken from : |
883 | * https://siliconlabs.github.io/Gecko_SDK_Doc/efm32zg/html/si1133_8c_source.html#l00578 |
884 | * |
885 | * Reserved the channel 0 for the other raw measurements |
886 | */ |
887 | static int si1133_init_lux_channels(struct si1133_data *data) |
888 | { |
889 | int err; |
890 | |
891 | err = si1133_chan_set_adcconfig(data, adc: 1, |
892 | SI1133_ADCCONFIG_DECIM_RATE(1) | |
893 | SI1133_PARAM_ADCMUX_LARGE_WHITE); |
894 | if (err) |
895 | return err; |
896 | |
897 | err = si1133_param_set(data, SI1133_PARAM_REG_ADCPOST(1), |
898 | SI1133_ADCPOST_24BIT_EN | |
899 | SI1133_ADCPOST_POSTSHIFT_BITQTY(0)); |
900 | if (err) |
901 | return err; |
902 | err = si1133_chan_set_adcsens(data, adc: 1, SI1133_ADCSENS_HSIG_MASK | |
903 | SI1133_ADCSENS_NB_MEAS(64) | _48_8_us); |
904 | if (err) |
905 | return err; |
906 | |
907 | err = si1133_chan_set_adcconfig(data, adc: 2, |
908 | SI1133_ADCCONFIG_DECIM_RATE(1) | |
909 | SI1133_PARAM_ADCMUX_LARGE_WHITE); |
910 | if (err) |
911 | return err; |
912 | |
913 | err = si1133_param_set(data, SI1133_PARAM_REG_ADCPOST(2), |
914 | SI1133_ADCPOST_24BIT_EN | |
915 | SI1133_ADCPOST_POSTSHIFT_BITQTY(2)); |
916 | if (err) |
917 | return err; |
918 | |
919 | err = si1133_chan_set_adcsens(data, adc: 2, SI1133_ADCSENS_HSIG_MASK | |
920 | SI1133_ADCSENS_NB_MEAS(1) | _3_120_0_us); |
921 | if (err) |
922 | return err; |
923 | |
924 | err = si1133_chan_set_adcconfig(data, adc: 3, |
925 | SI1133_ADCCONFIG_DECIM_RATE(1) | |
926 | SI1133_PARAM_ADCMUX_MED_IR); |
927 | if (err) |
928 | return err; |
929 | |
930 | err = si1133_param_set(data, SI1133_PARAM_REG_ADCPOST(3), |
931 | SI1133_ADCPOST_24BIT_EN | |
932 | SI1133_ADCPOST_POSTSHIFT_BITQTY(2)); |
933 | if (err) |
934 | return err; |
935 | |
936 | return si1133_chan_set_adcsens(data, adc: 3, SI1133_ADCSENS_HSIG_MASK | |
937 | SI1133_ADCSENS_NB_MEAS(64) | _48_8_us); |
938 | } |
939 | |
940 | static int si1133_initialize(struct si1133_data *data) |
941 | { |
942 | int err; |
943 | |
944 | err = si1133_cmd_reset_sw(data); |
945 | if (err) |
946 | return err; |
947 | |
948 | /* Turn off autonomous mode */ |
949 | err = si1133_param_set(data, SI1133_REG_MEAS_RATE, value: 0); |
950 | if (err) |
951 | return err; |
952 | |
953 | err = si1133_init_lux_channels(data); |
954 | if (err) |
955 | return err; |
956 | |
957 | return regmap_write(map: data->regmap, SI1133_REG_IRQ_ENABLE, |
958 | SI1133_IRQ_CHANNEL_ENABLE); |
959 | } |
960 | |
961 | static int si1133_validate_ids(struct iio_dev *iio_dev) |
962 | { |
963 | struct si1133_data *data = iio_priv(indio_dev: iio_dev); |
964 | |
965 | unsigned int part_id, rev_id, mfr_id; |
966 | int err; |
967 | |
968 | err = regmap_read(map: data->regmap, SI1133_REG_PART_ID, val: &part_id); |
969 | if (err) |
970 | return err; |
971 | |
972 | err = regmap_read(map: data->regmap, SI1133_REG_REV_ID, val: &rev_id); |
973 | if (err) |
974 | return err; |
975 | |
976 | err = regmap_read(map: data->regmap, SI1133_REG_MFR_ID, val: &mfr_id); |
977 | if (err) |
978 | return err; |
979 | |
980 | dev_info(&iio_dev->dev, |
981 | "Device ID part 0x%02x rev 0x%02x mfr 0x%02x\n" , |
982 | part_id, rev_id, mfr_id); |
983 | if (part_id != SI1133_PART_ID) { |
984 | dev_err(&iio_dev->dev, |
985 | "Part ID mismatch got 0x%02x, expected 0x%02x\n" , |
986 | part_id, SI1133_PART_ID); |
987 | return -ENODEV; |
988 | } |
989 | |
990 | return 0; |
991 | } |
992 | |
993 | static int si1133_probe(struct i2c_client *client) |
994 | { |
995 | const struct i2c_device_id *id = i2c_client_get_device_id(client); |
996 | struct si1133_data *data; |
997 | struct iio_dev *iio_dev; |
998 | int err; |
999 | |
1000 | iio_dev = devm_iio_device_alloc(parent: &client->dev, sizeof_priv: sizeof(*data)); |
1001 | if (!iio_dev) |
1002 | return -ENOMEM; |
1003 | |
1004 | data = iio_priv(indio_dev: iio_dev); |
1005 | |
1006 | init_completion(x: &data->completion); |
1007 | |
1008 | data->regmap = devm_regmap_init_i2c(client, &si1133_regmap_config); |
1009 | if (IS_ERR(ptr: data->regmap)) { |
1010 | err = PTR_ERR(ptr: data->regmap); |
1011 | dev_err(&client->dev, "Failed to initialise regmap: %d\n" , err); |
1012 | return err; |
1013 | } |
1014 | |
1015 | i2c_set_clientdata(client, data: iio_dev); |
1016 | data->client = client; |
1017 | |
1018 | iio_dev->name = id->name; |
1019 | iio_dev->channels = si1133_channels; |
1020 | iio_dev->num_channels = ARRAY_SIZE(si1133_channels); |
1021 | iio_dev->info = &si1133_info; |
1022 | iio_dev->modes = INDIO_DIRECT_MODE; |
1023 | |
1024 | mutex_init(&data->mutex); |
1025 | |
1026 | err = si1133_validate_ids(iio_dev); |
1027 | if (err) |
1028 | return err; |
1029 | |
1030 | err = si1133_initialize(data); |
1031 | if (err) { |
1032 | dev_err(&client->dev, |
1033 | "Error when initializing chip: %d\n" , err); |
1034 | return err; |
1035 | } |
1036 | |
1037 | if (!client->irq) { |
1038 | dev_err(&client->dev, |
1039 | "Required interrupt not provided, cannot proceed\n" ); |
1040 | return -EINVAL; |
1041 | } |
1042 | |
1043 | err = devm_request_threaded_irq(dev: &client->dev, irq: client->irq, |
1044 | NULL, |
1045 | thread_fn: si1133_threaded_irq_handler, |
1046 | IRQF_ONESHOT | IRQF_SHARED, |
1047 | devname: client->name, dev_id: iio_dev); |
1048 | if (err) { |
1049 | dev_warn(&client->dev, "Request irq %d failed: %i\n" , |
1050 | client->irq, err); |
1051 | return err; |
1052 | } |
1053 | |
1054 | return devm_iio_device_register(&client->dev, iio_dev); |
1055 | } |
1056 | |
1057 | static const struct i2c_device_id si1133_ids[] = { |
1058 | { "si1133" , 0 }, |
1059 | { } |
1060 | }; |
1061 | MODULE_DEVICE_TABLE(i2c, si1133_ids); |
1062 | |
1063 | static struct i2c_driver si1133_driver = { |
1064 | .driver = { |
1065 | .name = "si1133" , |
1066 | }, |
1067 | .probe = si1133_probe, |
1068 | .id_table = si1133_ids, |
1069 | }; |
1070 | |
1071 | module_i2c_driver(si1133_driver); |
1072 | |
1073 | MODULE_AUTHOR("Maxime Roussin-Belanger <maxime.roussinbelanger@gmail.com>" ); |
1074 | MODULE_DESCRIPTION("Silabs SI1133, UV index sensor and ambient light sensor driver" ); |
1075 | MODULE_LICENSE("GPL" ); |
1076 | |