diff options
| author | Jinke Han <jinkehan@didiglobal.com> | 2026-09-08 15:37:42 +0800 |
|---|---|---|
| committer | Ingo Molnar <mingo@kernel.org> | 2026-09-17 09:48:42 +0200 |
| commit | a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch) | |
| tree | 73fef824f9a846cf935869d0dbc5070ef6e2f741 /drivers/iio/light | |
| download | linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip | |
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function,
a crash can be triggered.
The eBPF tool probes offset 257 of the __hrtimer_run_queues()
function:
<__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1)
<__hrtimer_run_queues+254>: mov %r14,%rdi
<__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12>
<__hrtimer_run_queues+263>: mov %eax,%r12d
<__hrtimer_run_queues+266>: xchg %ax,%ax
<__hrtimer_run_queues+268>: mov %r13,%rdi
Which triggers this crash:
BUG: unable to handle page fault for address: 00000000000f41c9
#PF: supervisor write access in kernel mode
#PF: error_code(0x0002) - not-present page
PGD 0 P4D 0
Oops: 0002 [#1] SMP NOPTI
CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P
RIP: 0010:__hrtimer_run_queues+0x106/0x230
Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is
at the 6th byte of the above CS CALL instruction. Since the CS CALL
instruction occupies 6 bytes, the exception occurred in the middle of that
call instruction.
The root cause is that when using eBPF tools to probe in the middle of a
function, a kprobe with INT3 is used as the underlying implementation.
During single-step emulation of the original CALL instruction,
int3_emulate_call() assumes that the probed CALL instruction is 5 bytes
long. However, the actual CS-prefixed CALL instruction occupies 6 bytes,
so it constructs an incorrect exception return address. When the CPU
returns from the kprobe handler, the next instruction to be executed is at
the address of the last byte of that CS CALL instruction. Coincidentally,
starting from that address, the CPU fetches and decodes a completely
different instruction, which ultimately triggers a kernel crash.
Fix the issue by using the actual instruction length obtained from
the instruction decoder when constructing the exception return
address, rather than relying on the hardcoded CALL_INSN_SIZE macro.
[ mingo: Refined the changelog ]
Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step")
Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Signed-off-by: Jinke Han <jinkehan@didiglobal.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Acked-by: Yafang Shao <laoar.shao@gmail.com>
Acked-by: Borislav Petkov <bp@alien8.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'drivers/iio/light')
73 files changed, 50728 insertions, 0 deletions
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig new file mode 100644 index 000000000..f23bbce12 --- /dev/null +++ b/drivers/iio/light/Kconfig @@ -0,0 +1,809 @@ +# SPDX-License-Identifier: GPL-2.0-only + +# +# Light sensors +# +# When adding new entries keep the list in alphabetical order + +menu "Light sensors" + +config ACPI_ALS + tristate "ACPI Ambient Light Sensor" + depends on ACPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IIO_KFIFO_BUF + help + Say Y here if you want to build a driver for the ACPI0008 + Ambient Light Sensor. + + To compile this driver as a module, choose M here: the module will + be called acpi-als. + +config ADJD_S311 + tristate "ADJD-S311-CR999 digital color sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + depends on I2C + help + If you say yes here you get support for the Avago ADJD-S311-CR999 + digital color light sensor. + + This driver can also be built as a module. If so, the module + will be called adjd_s311. + +config ADUX1020 + tristate "ADUX1020 photometric sensor" + select REGMAP_I2C + depends on I2C + help + Say Y here if you want to build a driver for the Analog Devices + ADUX1020 photometric sensor. + + To compile this driver as a module, choose M here: the + module will be called adux1020. + +config AL3000A + tristate "AL3000a ambient light sensor" + select REGMAP_I2C + depends on I2C + help + Say Y here if you want to build a driver for the Dyna Image AL3000a + ambient light sensor. + + To compile this driver as a module, choose M here: the + module will be called al3000a. + +config AL3010 + tristate "AL3010 ambient light sensor" + select REGMAP_I2C + depends on I2C + help + Say Y here if you want to build a driver for the Dyna Image AL3010 + ambient light sensor. + + To compile this driver as a module, choose M here: the + module will be called al3010. + +config AL3320A + tristate "AL3320A ambient light sensor" + select REGMAP_I2C + depends on I2C + help + Say Y here if you want to build a driver for the Dyna Image AL3320A + ambient light sensor. + + To compile this driver as a module, choose M here: the + module will be called al3320a. + +config APDS9160 + tristate "APDS9160 combined als and proximity sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here if you want to build support for a Broadcom APDS9160 + combined ambient light and proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called apds9160. + +config APDS9300 + tristate "APDS9300 ambient light sensor" + depends on I2C + help + Say Y here if you want to build a driver for the Avago APDS9300 + ambient light sensor. + + To compile this driver as a module, choose M here: the + module will be called apds9300. + +config APDS9306 + tristate "Avago APDS9306 Ambient Light Sensor" + depends on I2C + select REGMAP_I2C + select IIO_GTS_HELPER + help + If you say Y or M here, you get support for Avago APDS9306 + Ambient Light Sensor. + + If built as a dynamically linked module, it will be called + apds9306. + +config APDS9960 + tristate "Avago APDS9960 gesture/RGB/ALS/proximity sensor" + select REGMAP_I2C + select IIO_BUFFER + select IIO_KFIFO_BUF + depends on I2C + help + Say Y here to build I2C interface support for the Avago + APDS9960 gesture/RGB/ALS/proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called apds9960 + +config APDS9999 + tristate "Broadcom APDS9999 ALS, RGB and proximity sensor" + depends on I2C + help + Say Y here if you want to build support for the Broadcom APDS9999 + ALS, RGB and proximity sensor with I2C interface. + + This driver provides ambient light sensing (ALS/Lux), raw + intensity data for red, green, blue and IR channels, plus + proximity detection support. + + To compile this driver as a module, choose M here: the + module will be called apds9999. + +config AS73211 + tristate "AMS AS73211 XYZ color sensor and AMS AS7331 UV sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for the AMS AS73211 + JENCOLOR(R) Digital XYZ and the AMS AS7331 UVA, UVB and UVC + ultraviolet sensors. + + For triggered measurements, you will need an additional trigger driver + like IIO_HRTIMER_TRIGGER or IIO_SYSFS_TRIGGER. + + This driver can also be built as a module. If so, the module + will be called as73211. + +config BH1745 + tristate "ROHM BH1745 colour sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IIO_GTS_HELPER + help + Say Y here to build support for the ROHM bh1745 colour sensor. + + To compile this driver as a module, choose M here: the module will + be called bh1745. + +config BH1750 + tristate "ROHM BH1750 ambient light sensor" + depends on I2C + help + Say Y here to build support for the ROHM BH1710, BH1715, BH1721, + BH1750, BH1751 ambient light sensors. + + To compile this driver as a module, choose M here: the module will + be called bh1750. + +config BH1780 + tristate "ROHM BH1780 ambient light sensor" + depends on I2C + help + Say Y here to build support for the ROHM BH1780GLI ambient + light sensor. + + To compile this driver as a module, choose M here: the module will + be called bh1780. + +config CM32181 + depends on I2C + tristate "CM32181 driver" + help + Say Y here if you use cm32181. + This option enables ambient light sensor using + Capella cm32181 device driver. + + To compile this driver as a module, choose M here: + the module will be called cm32181. + +config CM3232 + depends on I2C + tristate "CM3232 ambient light sensor" + help + Say Y here if you use cm3232. + This option enables ambient light sensor using + Capella Microsystems cm3232 device driver. + + To compile this driver as a module, choose M here: + the module will be called cm3232. + +config CM3323 + depends on I2C + tristate "Capella CM3323 color light sensor" + help + Say Y here if you want to build a driver for Capella CM3323 + color sensor. + + To compile this driver as a module, choose M here: the module will + be called cm3323. + +config CM3605 + tristate "Capella CM3605 ambient light and proximity sensor" + help + Say Y here if you want to build a driver for Capella CM3605 + ambient light and short range proximity sensor. + + To compile this driver as a module, choose M here: the module will + be called cm3605. + +config CM36651 + depends on I2C + tristate "CM36651 driver" + help + Say Y here if you use cm36651. + This option enables proximity & RGB sensor using + Capella cm36651 device driver. + + To compile this driver as a module, choose M here: + the module will be called cm36651. + +config IIO_CROS_EC_LIGHT_PROX + tristate "ChromeOS EC Light and Proximity Sensors" + depends on IIO_CROS_EC_SENSORS_CORE + help + Say Y here if you use the light and proximity sensors + presented by the ChromeOS EC Sensor hub. + + To compile this driver as a module, choose M here: + the module will be called cros_ec_light_prox. + +config GP2AP002 + tristate "Sharp GP2AP002 Proximity/ALS sensor" + depends on I2C + select REGMAP + help + Say Y here if you have a Sharp GP2AP002 proximity/ALS combo-chip + hooked to an I2C bus. + + To compile this driver as a module, choose M here: the + module will be called gp2ap002. + +config GP2AP020A00F + tristate "Sharp GP2AP020A00F Proximity/ALS sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IRQ_WORK + help + Say Y here if you have a Sharp GP2AP020A00F proximity/ALS combo-chip + hooked to an I2C bus. + + To compile this driver as a module, choose M here: the + module will be called gp2ap020a00f. + +config IQS621_ALS + tristate "Azoteq IQS621/622 ambient light sensors" + depends on MFD_IQS62X || COMPILE_TEST + help + Say Y here if you want to build support for the Azoteq IQS621 + and IQS622 ambient light sensors. + + To compile this driver as a module, choose M here: the module + will be called iqs621-als. + +config SENSORS_ISL29018 + tristate "Intersil 29018 light and proximity sensor" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for ambient light sensing and + proximity infrared sensing from Intersil ISL29018. + This driver will provide the measurements of ambient light intensity + in lux, proximity infrared sensing and normal infrared sensing. + Data from sensor is accessible via sysfs. + +config SENSORS_ISL29028 + tristate "Intersil ISL29028 Concurrent Light and Proximity Sensor" + depends on I2C + select REGMAP_I2C + help + Provides driver for the Intersil's ISL29028 device. + This driver supports the sysfs interface to get the ALS, IR intensity, + Proximity value via iio. The ISL29028 provides the concurrent sensing + of ambient light and proximity. + +config ISL29125 + tristate "Intersil ISL29125 digital color light sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build a driver for the Intersil ISL29125 + RGB light sensor for I2C. + + To compile this driver as a module, choose M here: the module will be + called isl29125. + +config ISL76682 + tristate "Intersil ISL76682 Light Sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here if you want to build a driver for the Intersil ISL76682 + Ambient Light Sensor and IR Intensity sensor. This driver provides + the readouts via standard IIO sysfs and device interface. Both ALS + illuminance and IR illuminance are provided raw with separate scale + setting which can be configured via sysfs, the default scale is 1000 + lux, other options are 4000/16000/64000 lux. + + To compile this driver as a module, choose M here: the module will be + called isl76682. + +config HID_SENSOR_ALS + depends on HID_SENSOR_HUB + select IIO_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID ALS" + help + Say yes here to build support for the HID SENSOR + Ambient light sensor. + + To compile this driver as a module, choose M here: the + module will be called hid-sensor-als. + +config HID_SENSOR_PROX + depends on HID_SENSOR_HUB + select IIO_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID PROX" + help + Say yes here to build support for the HID SENSOR + Proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called hid-sensor-prox. + +config JSA1212 + tristate "JSA1212 ALS and proximity sensor driver" + depends on I2C + select REGMAP_I2C + help + Say Y here if you want to build a IIO driver for JSA1212 + proximity & ALS sensor device. + + To compile this driver as a module, choose M here: + the module will be called jsa1212. + +config ROHM_BU27034 + tristate "ROHM BU27034 ambient light sensor" + depends on I2C + select REGMAP_I2C + select IIO_GTS_HELPER + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Enable support for the ROHM BU27034 ambient light sensor. ROHM BU27034 + is an ambient light sensor with 3 channels and 3 photo diodes capable + of detecting a very wide range of illuminance. + Typical application is adjusting LCD and backlight power of TVs and + mobile phones. + +config RPR0521 + tristate "ROHM RPR0521 ALS and proximity sensor driver" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build support for ROHM's RPR0521 + ambient light and proximity sensor device. + + To compile this driver as a module, choose M here: + the module will be called rpr0521. + +config SENSORS_LM3533 + tristate "LM3533 ambient light sensor" + depends on MFD_LM3533 + help + If you say yes here you get support for the ambient light sensor + interface on National Semiconductor / TI LM3533 Lighting Power + chips. + + The sensor interface can be used to control the LEDs and backlights + of the chip through defining five light zones and three sets of + corresponding output-current values. + + The driver provides raw and mean adc readings along with the current + light zone through sysfs. A threshold event can be generated on zone + changes. The ALS-control output values can be set per zone for the + three current output channels. + +config LTR390 + tristate "LTR-390UV-01 ambient light and UV sensor" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the Lite-On LTR-390UV-01 + ambient light and UV sensor. + + This driver can also be built as a module. If so, the module + will be called ltr390. + +config LTR501 + tristate "LTR-501ALS-01 light sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for the Lite-On LTR-501ALS-01 + ambient light and proximity sensor. This driver also supports LTR-559 + ALS/PS or LTR-301 ALS sensors. + + This driver can also be built as a module. If so, the module + will be called ltr501. + +config LTRF216A + tristate "Liteon LTRF216A Light Sensor" + depends on I2C + select REGMAP_I2C + help + If you say Y or M here, you get support for Liteon LTRF216A + Ambient Light Sensor. + + If built as a dynamically linked module, it will be called + ltrf216a. + +config LV0104CS + tristate "LV0104CS Ambient Light Sensor" + depends on I2C + help + Say Y here if you want to build support for the On Semiconductor + LV0104CS ambient light sensor. + + To compile this driver as a module, choose M here: + the module will be called lv0104cs. + +config MAX44000 + tristate "MAX44000 Ambient and Infrared Proximity Sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build support for Maxim Integrated's + MAX44000 ambient and infrared proximity sensor device. + + To compile this driver as a module, choose M here: + the module will be called max44000. + +config MAX44009 + tristate "MAX44009 Ambient Light Sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here if you want to build support for Maxim Integrated's + MAX44009 ambient light sensor device. + + To compile this driver as a module, choose M here: + the module will be called max44009. + +config NOA1305 + tristate "ON Semiconductor NOA1305 ambient light sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here if you want to build support for the ON Semiconductor + NOA1305 ambient light sensor. + + To compile this driver as a module, choose M here: + The module will be called noa1305. + +config OPT3001 + tristate "Texas Instruments OPT3001 Light Sensor" + depends on I2C + help + If you say Y or M here, you get support for Texas Instruments + OPT3001 Ambient Light Sensor, OPT3002 Light-to-Digital Sensor. + + If built as a dynamically linked module, it will be called + opt3001. + +config OPT4001 + tristate "Texas Instruments OPT4001 Light Sensor" + depends on I2C + select REGMAP_I2C + help + If you say Y or M here, you get support for Texas Instruments + OPT4001 Ambient Light Sensor. + + If built as a dynamically linked module, it will be called + opt4001. + +config OPT4060 + tristate "Texas Instruments OPT4060 RGBW Color Sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say Y or M here, you get support for Texas Instruments + OPT4060 RGBW Color Sensor. + + If built as a dynamically linked module, it will be called + opt4060. + +config PA12203001 + tristate "TXC PA12203001 light and proximity sensor" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the TXC PA12203001 + ambient light and proximity sensor. + + This driver can also be built as a module. If so, the module + will be called pa12203001. + +config SI1133 + tristate "SI1133 UV Index Sensor and Ambient Light Sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here if you want to build a driver for the Silicon Labs SI1133 + UV Index Sensor and Ambient Light Sensor chip. + + To compile this driver as a module, choose M here: the module will be + called si1133. + +config SI1145 + tristate "SI1132 and SI1141/2/3/5/6/7 combined ALS, UV index and proximity sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build a driver for the Silicon Labs SI1132 or + SI1141/2/3/5/6/7 combined ambient light, UV index and proximity sensor + chips. + + To compile this driver as a module, choose M here: the module will be + called si1145. + +config STK3310 + tristate "STK3310 ALS and proximity sensor" + depends on I2C + select REGMAP_I2C + help + Say yes here to get support for the Sensortek STK3310 ambient light + and proximity sensor. The STK3311 model is also supported by this + driver. + + Choosing M will build the driver as a module. If so, the module + will be called stk3310. + +config ST_UVIS25 + tristate "STMicroelectronics UVIS25 sensor driver" + depends on (I2C || SPI) + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select ST_UVIS25_I2C if (I2C) + select ST_UVIS25_SPI if (SPI_MASTER) + help + Say yes here to build support for STMicroelectronics UVIS25 + uv sensor + + To compile this driver as a module, choose M here: the module + will be called st_uvis25. + +config ST_UVIS25_I2C + tristate + depends on ST_UVIS25 + select REGMAP_I2C + +config ST_UVIS25_SPI + tristate + depends on ST_UVIS25 + select REGMAP_SPI + +config TCS3414 + tristate "TAOS TCS3414 digital color sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for the TAOS TCS3414 + family of digital color sensors. + + This driver can also be built as a module. If so, the module + will be called tcs3414. + +config TCS3472 + tristate "TAOS TCS3472 color light-to-digital converter" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for the TAOS TCS3472 + family of color light-to-digital converters with IR filter. + + This driver can also be built as a module. If so, the module + will be called tcs3472. + +config SENSORS_TSL2563 + tristate "TAOS TSL2560, TSL2561, TSL2562 and TSL2563 ambient light sensors" + depends on I2C + help + If you say yes here you get support for the Taos TSL2560, + TSL2561, TSL2562 and TSL2563 ambient light sensors. + + This driver can also be built as a module. If so, the module + will be called tsl2563. + +config TSL2583 + tristate "TAOS TSL2580, TSL2581 and TSL2583 light-to-digital converters" + depends on I2C + help + Provides support for the TAOS tsl2580, tsl2581 and tsl2583 devices. + Access ALS data via iio, sysfs. + +config TSL2591 + tristate "TAOS TSL2591 ambient light sensor" + depends on I2C + help + Select Y here for support of the AMS/TAOS TSL2591 ambient light sensor, + featuring channels for combined visible + IR intensity and lux illuminance. + Access data via iio and sysfs. Supports iio_events. + + To compile this driver as a module, select M: the + module will be called tsl2591. + +config TSL2772 + tristate "TAOS TSL/TMD2x71 and TSL/TMD2x72 Family of light and proximity sensors" + depends on I2C + help + Support for: tsl2571, tsl2671, tmd2671, tsl2771, tmd2771, tsl2572, tsl2672, + tmd2672, tsl2772, tmd2772 devices. + Provides iio_events and direct access via sysfs. + +config TSL4531 + tristate "TAOS TSL4531 ambient light sensors" + depends on I2C + help + Say Y here if you want to build a driver for the TAOS TSL4531 family + of ambient light sensors with direct lux output. + + To compile this driver as a module, choose M here: the + module will be called tsl4531. + +config US5182D + tristate "UPISEMI light and proximity sensor" + depends on I2C + help + If you say yes here you get support for the UPISEMI US5182D + ambient light and proximity sensor. + + This driver can also be built as a module. If so, the module + will be called us5182d. + +config VCNL4000 + tristate "VCNL4000/4010/4020/4200 combined ALS and proximity sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VCNL4000, + VCNL4010, VCNL4020, VCNL4200 combined ambient light and proximity + sensor. + + To compile this driver as a module, choose M here: the + module will be called vcnl4000. + +config VCNL4035 + tristate "VCNL4035 combined ALS and proximity sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP_I2C + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VCNL4035, + combined ambient light (ALS) and proximity sensor. Currently only ALS + function is available. + + To compile this driver as a module, choose M here: the + module will be called vcnl4035. + +config VEML3235 + tristate "VEML3235 ambient light sensor" + select REGMAP_I2C + select IIO_GTS_HELPER + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML3235 + ambient light sensor. + + To compile this driver as a module, choose M here: the + module will be called veml3235. + +config VEML3328 + tristate "VEML3328 RGBCIR light sensor" + select REGMAP_I2C + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML3328 + RGB IR light sensor. + + To compile this driver as a module, choose M here: the + module will be called veml3328 + +config VEML6030 + tristate "VEML6030 and VEML6035 ambient light sensors" + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IIO_GTS_HELPER + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML6030 + and VEML6035 ambient light sensors (ALS). + + 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 VEML6046X00 + tristate "VEML6046X00 RGBIR color sensor" + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML6046X00 + high accuracy RGBIR color sensor. + + To compile this driver as a module, choose M here: the + module will be called veml6046x00. + +config VEML6070 + tristate "VEML6070 UV A light sensor" + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML6070 UV A + light sensor. + + To compile this driver as a module, choose M here: the + module will be called veml6070. + +config VEML6075 + tristate "VEML6075 UVA and UVB light sensor" + select REGMAP_I2C + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML6075 UVA + and UVB light sensor. + + To compile this driver as a module, choose M here: the + module will be called veml6075. + +config VL6180 + tristate "VL6180 ALS, range and proximity sensor" + depends on I2C + help + Say Y here if you want to build a driver for the STMicroelectronics + VL6180 combined ambient light, range and proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called vl6180. + +config ZOPT2201 + tristate "ZOPT2201 ALS and UV B sensor" + depends on I2C + help + Say Y here if you want to build a driver for the IDT + ZOPT2201 ambient light and UV B sensor. + + To compile this driver as a module, choose M here: the + module will be called zopt2201. + +endmenu diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile new file mode 100644 index 000000000..64e354c49 --- /dev/null +++ b/drivers/iio/light/Makefile @@ -0,0 +1,76 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for IIO Light sensors +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_ACPI_ALS) += acpi-als.o +obj-$(CONFIG_ADJD_S311) += adjd_s311.o +obj-$(CONFIG_ADUX1020) += adux1020.o +obj-$(CONFIG_AL3000A) += al3000a.o +obj-$(CONFIG_AL3010) += al3010.o +obj-$(CONFIG_AL3320A) += al3320a.o +obj-$(CONFIG_APDS9160) += apds9160.o +obj-$(CONFIG_APDS9300) += apds9300.o +obj-$(CONFIG_APDS9306) += apds9306.o +obj-$(CONFIG_APDS9960) += apds9960.o +obj-$(CONFIG_APDS9999) += apds9999.o +obj-$(CONFIG_AS73211) += as73211.o +obj-$(CONFIG_BH1745) += bh1745.o +obj-$(CONFIG_BH1750) += bh1750.o +obj-$(CONFIG_BH1780) += bh1780.o +obj-$(CONFIG_CM32181) += cm32181.o +obj-$(CONFIG_CM3232) += cm3232.o +obj-$(CONFIG_CM3323) += cm3323.o +obj-$(CONFIG_CM3605) += cm3605.o +obj-$(CONFIG_CM36651) += cm36651.o +obj-$(CONFIG_IIO_CROS_EC_LIGHT_PROX) += cros_ec_light_prox.o +obj-$(CONFIG_GP2AP002) += gp2ap002.o +obj-$(CONFIG_GP2AP020A00F) += gp2ap020a00f.o +obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o +obj-$(CONFIG_HID_SENSOR_PROX) += hid-sensor-prox.o +obj-$(CONFIG_IQS621_ALS) += iqs621-als.o +obj-$(CONFIG_SENSORS_ISL29018) += isl29018.o +obj-$(CONFIG_SENSORS_ISL29028) += isl29028.o +obj-$(CONFIG_ISL29125) += isl29125.o +obj-$(CONFIG_ISL76682) += isl76682.o +obj-$(CONFIG_JSA1212) += jsa1212.o +obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o +obj-$(CONFIG_LTR390) += ltr390.o +obj-$(CONFIG_LTR501) += ltr501.o +obj-$(CONFIG_LTRF216A) += ltrf216a.o +obj-$(CONFIG_LV0104CS) += lv0104cs.o +obj-$(CONFIG_MAX44000) += max44000.o +obj-$(CONFIG_MAX44009) += max44009.o +obj-$(CONFIG_NOA1305) += noa1305.o +obj-$(CONFIG_OPT3001) += opt3001.o +obj-$(CONFIG_OPT4001) += opt4001.o +obj-$(CONFIG_OPT4060) += opt4060.o +obj-$(CONFIG_PA12203001) += pa12203001.o +obj-$(CONFIG_ROHM_BU27034) += rohm-bu27034.o +obj-$(CONFIG_RPR0521) += rpr0521.o +obj-$(CONFIG_SI1133) += si1133.o +obj-$(CONFIG_SI1145) += si1145.o +obj-$(CONFIG_STK3310) += stk3310.o +obj-$(CONFIG_ST_UVIS25) += st_uvis25_core.o +obj-$(CONFIG_ST_UVIS25_I2C) += st_uvis25_i2c.o +obj-$(CONFIG_ST_UVIS25_SPI) += st_uvis25_spi.o +obj-$(CONFIG_TCS3414) += tcs3414.o +obj-$(CONFIG_TCS3472) += tcs3472.o +obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o +obj-$(CONFIG_TSL2583) += tsl2583.o +obj-$(CONFIG_TSL2591) += tsl2591.o +obj-$(CONFIG_TSL2772) += tsl2772.o +obj-$(CONFIG_TSL4531) += tsl4531.o +obj-$(CONFIG_US5182D) += us5182d.o +obj-$(CONFIG_VCNL4000) += vcnl4000.o +obj-$(CONFIG_VCNL4035) += vcnl4035.o +obj-$(CONFIG_VEML3235) += veml3235.o +obj-$(CONFIG_VEML3328) += veml3328.o +obj-$(CONFIG_VEML6030) += veml6030.o +obj-$(CONFIG_VEML6040) += veml6040.o +obj-$(CONFIG_VEML6046X00) += veml6046x00.o +obj-$(CONFIG_VEML6070) += veml6070.o +obj-$(CONFIG_VEML6075) += veml6075.o +obj-$(CONFIG_VL6180) += vl6180.o +obj-$(CONFIG_ZOPT2201) += zopt2201.o diff --git a/drivers/iio/light/acpi-als.c b/drivers/iio/light/acpi-als.c new file mode 100644 index 000000000..1983a7f17 --- /dev/null +++ b/drivers/iio/light/acpi-als.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ACPI Ambient Light Sensor Driver + * + * Based on ALS driver: + * Copyright (C) 2009 Zhang Rui <rui.zhang@intel.com> + * + * Rework for IIO subsystem: + * Copyright (C) 2012-2013 Martin Liska <marxin.liska@gmail.com> + * + * Final cleanup and debugging: + * Copyright (C) 2013-2014 Marek Vasut <marex@denx.de> + * Copyright (C) 2015 Gabriele Mazzotta <gabriele.mzt@gmail.com> + */ + +#include <linux/module.h> +#include <linux/acpi.h> +#include <linux/err.h> +#include <linux/irq.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> + +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> + +#define ACPI_ALS_CLASS "als" +#define ACPI_ALS_DEVICE_NAME "acpi-als" +#define ACPI_ALS_NOTIFY_ILLUMINANCE 0x80 + +/* + * So far, there's only one channel in here, but the specification for + * ACPI0008 says there can be more to what the block can report. Like + * chromaticity and such. We are ready for incoming additions! + */ +static const struct iio_chan_spec acpi_als_channels[] = { + { + .type = IIO_LIGHT, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + }, + /* _RAW is here for backward ABI compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_PROCESSED), + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +struct acpi_als { + struct acpi_device *device; + struct mutex lock; + struct iio_trigger *trig; +}; + +/* + * All types of properties the ACPI0008 block can report. The ALI, ALC, ALT + * and ALP can all be handled by acpi_als_read_value() below, while the ALR is + * special. + * + * The _ALR property returns tables that can be used to fine-tune the values + * reported by the other props based on the particular hardware type and it's + * location (it contains tables for "rainy", "bright inhouse lighting" etc.). + * + * So far, we support only ALI (illuminance). + */ +#define ACPI_ALS_ILLUMINANCE "_ALI" +#define ACPI_ALS_CHROMATICITY "_ALC" +#define ACPI_ALS_COLOR_TEMP "_ALT" +#define ACPI_ALS_POLLING "_ALP" +#define ACPI_ALS_TABLES "_ALR" + +static int acpi_als_read_value(struct acpi_als *als, char *prop, s32 *val) +{ + unsigned long long temp_val; + acpi_status status; + + status = acpi_evaluate_integer(als->device->handle, prop, NULL, + &temp_val); + + if (ACPI_FAILURE(status)) { + acpi_evaluation_failure_warn(als->device->handle, prop, status); + return -EIO; + } + + *val = temp_val; + + return 0; +} + +static void acpi_als_notify(acpi_handle handle, u32 event, void *data) +{ + struct iio_dev *indio_dev = data; + struct acpi_als *als = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev) && iio_trigger_using_own(indio_dev)) { + switch (event) { + case ACPI_ALS_NOTIFY_ILLUMINANCE: + iio_trigger_poll_nested(als->trig); + break; + default: + /* Unhandled event */ + dev_dbg(&als->device->dev, + "Unhandled ACPI ALS event (%08x)!\n", + event); + } + } +} + +static int acpi_als_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct acpi_als *als = iio_priv(indio_dev); + s32 temp_val; + int ret; + + if ((mask != IIO_CHAN_INFO_PROCESSED) && (mask != IIO_CHAN_INFO_RAW)) + return -EINVAL; + + /* we support only illumination (_ALI) so far. */ + if (chan->type != IIO_LIGHT) + return -EINVAL; + + ret = acpi_als_read_value(als, ACPI_ALS_ILLUMINANCE, &temp_val); + if (ret < 0) + return ret; + + *val = temp_val; + + return IIO_VAL_INT; +} + +static const struct iio_info acpi_als_info = { + .read_raw = acpi_als_read_raw, +}; + +static irqreturn_t acpi_als_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct acpi_als *als = iio_priv(indio_dev); + struct { + s32 light; + aligned_s64 ts; + } scan = { }; + s32 val; + int ret; + + mutex_lock(&als->lock); + + ret = acpi_als_read_value(als, ACPI_ALS_ILLUMINANCE, &val); + if (ret < 0) + goto out; + scan.light = val; + + /* + * When coming from own trigger via polls, set polling function + * timestamp here. Given ACPI notifier is already in a thread and call + * function directly, there is no need to set the timestamp in the + * notify function. + * + * If the timestamp was actually 0, the timestamp is set one more time. + */ + if (!pf->timestamp) + pf->timestamp = iio_get_time_ns(indio_dev); + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp); +out: + mutex_unlock(&als->lock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int acpi_als_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct acpi_device *device; + struct iio_dev *indio_dev; + struct acpi_als *als; + int ret; + + device = ACPI_COMPANION(dev); + if (!device) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*als)); + if (!indio_dev) + return -ENOMEM; + + als = iio_priv(indio_dev); + + als->device = device; + mutex_init(&als->lock); + + indio_dev->name = ACPI_ALS_DEVICE_NAME; + indio_dev->info = &acpi_als_info; + indio_dev->channels = acpi_als_channels; + indio_dev->num_channels = ARRAY_SIZE(acpi_als_channels); + + als->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, + iio_device_id(indio_dev)); + if (!als->trig) + return -ENOMEM; + + ret = devm_iio_trigger_register(dev, als->trig); + if (ret) + return ret; + /* + * Set hardware trigger by default to let events flow when + * BIOS support notification. + */ + indio_dev->trig = iio_trigger_get(als->trig); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + acpi_als_trigger_handler, + NULL); + if (ret) + return ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ret; + + return acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY, + acpi_als_notify, indio_dev); +} + +static void acpi_als_remove(struct platform_device *pdev) +{ + acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), + ACPI_DEVICE_NOTIFY, acpi_als_notify); +} + +static const struct acpi_device_id acpi_als_device_ids[] = { + {"ACPI0008", 0}, + { } +}; + +MODULE_DEVICE_TABLE(acpi, acpi_als_device_ids); + +static struct platform_driver acpi_als_driver = { + .probe = acpi_als_probe, + .remove = acpi_als_remove, + .driver = { + .name = "acpi_als", + .acpi_match_table = acpi_als_device_ids, + }, +}; +module_platform_driver(acpi_als_driver); + +MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>"); +MODULE_AUTHOR("Martin Liska <marxin.liska@gmail.com>"); +MODULE_AUTHOR("Marek Vasut <marex@denx.de>"); +MODULE_DESCRIPTION("ACPI Ambient Light Sensor Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/adjd_s311.c b/drivers/iio/light/adjd_s311.c new file mode 100644 index 000000000..088b9431d --- /dev/null +++ b/drivers/iio/light/adjd_s311.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * adjd_s311.c - Support for ADJD-S311-CR999 digital color sensor + * + * Copyright (C) 2012 Peter Meerwald <pmeerw@pmeerw.net> + * + * driver for ADJD-S311-CR999 digital color sensor (10-bit channels for + * red, green, blue, clear); 7-bit I2C slave address 0x74 + * + * limitations: no calibration, no offset mode, no sleep mode + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/bitmap.h> +#include <linux/err.h> +#include <linux/irq.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> + +#define ADJD_S311_DRV_NAME "adjd_s311" + +#define ADJD_S311_CTRL 0x00 +#define ADJD_S311_CONFIG 0x01 +#define ADJD_S311_CAP_RED 0x06 +#define ADJD_S311_CAP_GREEN 0x07 +#define ADJD_S311_CAP_BLUE 0x08 +#define ADJD_S311_CAP_CLEAR 0x09 +#define ADJD_S311_INT_RED 0x0a +#define ADJD_S311_INT_GREEN 0x0c +#define ADJD_S311_INT_BLUE 0x0e +#define ADJD_S311_INT_CLEAR 0x10 +#define ADJD_S311_DATA_RED 0x40 +#define ADJD_S311_DATA_GREEN 0x42 +#define ADJD_S311_DATA_BLUE 0x44 +#define ADJD_S311_DATA_CLEAR 0x46 +#define ADJD_S311_OFFSET_RED 0x48 +#define ADJD_S311_OFFSET_GREEN 0x49 +#define ADJD_S311_OFFSET_BLUE 0x4a +#define ADJD_S311_OFFSET_CLEAR 0x4b + +#define ADJD_S311_CTRL_GOFS 0x02 +#define ADJD_S311_CTRL_GSSR 0x01 +#define ADJD_S311_CAP_MASK 0x0f +#define ADJD_S311_INT_MASK 0x0fff +#define ADJD_S311_DATA_MASK 0x03ff + +struct adjd_s311_data { + struct i2c_client *client; +}; + +enum adjd_s311_channel_idx { + IDX_RED, IDX_GREEN, IDX_BLUE, IDX_CLEAR +}; + +#define ADJD_S311_DATA_REG(chan) (ADJD_S311_DATA_RED + (chan) * 2) +#define ADJD_S311_INT_REG(chan) (ADJD_S311_INT_RED + (chan) * 2) +#define ADJD_S311_CAP_REG(chan) (ADJD_S311_CAP_RED + (chan)) + +static int adjd_s311_req_data(struct iio_dev *indio_dev) +{ + struct adjd_s311_data *data = iio_priv(indio_dev); + int tries = 10; + + int ret = i2c_smbus_write_byte_data(data->client, ADJD_S311_CTRL, + ADJD_S311_CTRL_GSSR); + if (ret < 0) + return ret; + + while (tries--) { + ret = i2c_smbus_read_byte_data(data->client, ADJD_S311_CTRL); + if (ret < 0) + return ret; + if (!(ret & ADJD_S311_CTRL_GSSR)) + break; + msleep(20); + } + + if (tries < 0) { + dev_err(&data->client->dev, + "adjd_s311_req_data() failed, data not ready\n"); + return -EIO; + } + + return 0; +} + +static int adjd_s311_read_data(struct iio_dev *indio_dev, u8 reg, int *val) +{ + struct adjd_s311_data *data = iio_priv(indio_dev); + + int ret = adjd_s311_req_data(indio_dev); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(data->client, reg); + if (ret < 0) + return ret; + + *val = ret & ADJD_S311_DATA_MASK; + + return 0; +} + +static irqreturn_t adjd_s311_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adjd_s311_data *data = iio_priv(indio_dev); + s64 time_ns = iio_get_time_ns(indio_dev); + int i, j = 0; + struct { + s16 chans[4]; + aligned_s64 ts; + } scan = { }; + + int ret = adjd_s311_req_data(indio_dev); + if (ret < 0) + goto done; + + iio_for_each_active_channel(indio_dev, i) { + ret = i2c_smbus_read_word_data(data->client, + ADJD_S311_DATA_REG(i)); + if (ret < 0) + goto done; + + scan.chans[j++] = ret & ADJD_S311_DATA_MASK; + } + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), time_ns); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +#define ADJD_S311_CHANNEL(_color, _scan_idx) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .address = (IDX_##_color), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .channel2 = (IIO_MOD_LIGHT_##_color), \ + .scan_index = (_scan_idx), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 10, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +static const struct iio_chan_spec adjd_s311_channels[] = { + ADJD_S311_CHANNEL(RED, 0), + ADJD_S311_CHANNEL(GREEN, 1), + ADJD_S311_CHANNEL(BLUE, 2), + ADJD_S311_CHANNEL(CLEAR, 3), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static int adjd_s311_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct adjd_s311_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = adjd_s311_read_data(indio_dev, + ADJD_S311_DATA_REG(chan->address), val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_HARDWAREGAIN: + ret = i2c_smbus_read_byte_data(data->client, + ADJD_S311_CAP_REG(chan->address)); + if (ret < 0) + return ret; + *val = ret & ADJD_S311_CAP_MASK; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + ret = i2c_smbus_read_word_data(data->client, + ADJD_S311_INT_REG(chan->address)); + if (ret < 0) + return ret; + *val = 0; + /* + * not documented, based on measurement: + * 4095 LSBs correspond to roughly 4 ms + */ + *val2 = ret & ADJD_S311_INT_MASK; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int adjd_s311_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct adjd_s311_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + if (val < 0 || val > ADJD_S311_CAP_MASK) + return -EINVAL; + + return i2c_smbus_write_byte_data(data->client, + ADJD_S311_CAP_REG(chan->address), val); + case IIO_CHAN_INFO_INT_TIME: + if (val != 0 || val2 < 0 || val2 > ADJD_S311_INT_MASK) + return -EINVAL; + + return i2c_smbus_write_word_data(data->client, + ADJD_S311_INT_REG(chan->address), val2); + } + return -EINVAL; +} + +static const struct iio_info adjd_s311_info = { + .read_raw = adjd_s311_read_raw, + .write_raw = adjd_s311_write_raw, +}; + +static int adjd_s311_probe(struct i2c_client *client) +{ + struct adjd_s311_data *data; + struct iio_dev *indio_dev; + int err; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (indio_dev == NULL) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + + indio_dev->info = &adjd_s311_info; + indio_dev->name = ADJD_S311_DRV_NAME; + indio_dev->channels = adjd_s311_channels; + indio_dev->num_channels = ARRAY_SIZE(adjd_s311_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + err = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL, + adjd_s311_trigger_handler, NULL); + if (err < 0) + return err; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id adjd_s311_id[] = { + { .name = "adjd_s311" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adjd_s311_id); + +static struct i2c_driver adjd_s311_driver = { + .driver = { + .name = ADJD_S311_DRV_NAME, + }, + .probe = adjd_s311_probe, + .id_table = adjd_s311_id, +}; +module_i2c_driver(adjd_s311_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("ADJD-S311 color sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/adux1020.c b/drivers/iio/light/adux1020.c new file mode 100644 index 000000000..0ea282ecc --- /dev/null +++ b/drivers/iio/light/adux1020.c @@ -0,0 +1,842 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * adux1020.c - Support for Analog Devices ADUX1020 photometric sensor + * + * Copyright (C) 2019 Linaro Ltd. + * Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> + * + * TODO: Triggered buffer support + */ + +#include <linux/bitfield.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regmap.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> + +#define ADUX1020_DRV_NAME "adux1020" + +/* System registers */ +#define ADUX1020_REG_CHIP_ID 0x08 +#define ADUX1020_REG_SLAVE_ADDRESS 0x09 + +#define ADUX1020_REG_SW_RESET 0x0f +#define ADUX1020_REG_INT_ENABLE 0x1c +#define ADUX1020_REG_INT_POLARITY 0x1d +#define ADUX1020_REG_PROX_TH_ON1 0x2a +#define ADUX1020_REG_PROX_TH_OFF1 0x2b +#define ADUX1020_REG_PROX_TYPE 0x2f +#define ADUX1020_REG_TEST_MODES_3 0x32 +#define ADUX1020_REG_FORCE_MODE 0x33 +#define ADUX1020_REG_FREQUENCY 0x40 +#define ADUX1020_REG_LED_CURRENT 0x41 +#define ADUX1020_REG_OP_MODE 0x45 +#define ADUX1020_REG_INT_MASK 0x48 +#define ADUX1020_REG_INT_STATUS 0x49 +#define ADUX1020_REG_DATA_BUFFER 0x60 + +/* Chip ID bits */ +#define ADUX1020_CHIP_ID_MASK GENMASK(11, 0) +#define ADUX1020_CHIP_ID 0x03fc + +#define ADUX1020_SW_RESET BIT(1) +#define ADUX1020_FIFO_FLUSH BIT(15) +#define ADUX1020_OP_MODE_MASK GENMASK(3, 0) +#define ADUX1020_DATA_OUT_MODE_MASK GENMASK(7, 4) +#define ADUX1020_DATA_OUT_PROX_I FIELD_PREP(ADUX1020_DATA_OUT_MODE_MASK, 1) + +#define ADUX1020_MODE_INT_MASK GENMASK(7, 0) +#define ADUX1020_INT_ENABLE 0x2094 +#define ADUX1020_INT_DISABLE 0x2090 +#define ADUX1020_PROX_INT_ENABLE 0x00f0 +#define ADUX1020_PROX_ON1_INT BIT(0) +#define ADUX1020_PROX_OFF1_INT BIT(1) +#define ADUX1020_FIFO_INT_ENABLE 0x7f +#define ADUX1020_MODE_INT_DISABLE 0xff +#define ADUX1020_MODE_INT_STATUS_MASK GENMASK(7, 0) +#define ADUX1020_FIFO_STATUS_MASK GENMASK(15, 8) +#define ADUX1020_INT_CLEAR 0xff +#define ADUX1020_PROX_TYPE BIT(15) + +#define ADUX1020_INT_PROX_ON1 BIT(0) +#define ADUX1020_INT_PROX_OFF1 BIT(1) + +#define ADUX1020_FORCE_CLOCK_ON 0x0f4f +#define ADUX1020_FORCE_CLOCK_RESET 0x0040 +#define ADUX1020_ACTIVE_4_STATE 0x0008 + +#define ADUX1020_PROX_FREQ_MASK GENMASK(7, 4) +#define ADUX1020_PROX_FREQ(x) FIELD_PREP(ADUX1020_PROX_FREQ_MASK, x) + +#define ADUX1020_LED_CURRENT_MASK GENMASK(3, 0) +#define ADUX1020_LED_PIREF_EN BIT(12) + +/* Operating modes */ +enum adux1020_op_modes { + ADUX1020_MODE_STANDBY, + ADUX1020_MODE_PROX_I, + ADUX1020_MODE_PROX_XY, + ADUX1020_MODE_GEST, + ADUX1020_MODE_SAMPLE, + ADUX1020_MODE_FORCE = 0x0e, + ADUX1020_MODE_IDLE = 0x0f, +}; + +struct adux1020_data { + struct i2c_client *client; + struct iio_dev *indio_dev; + struct mutex lock; + struct regmap *regmap; +}; + +struct adux1020_mode_data { + u8 bytes; + u8 buf_len; + u16 int_en; +}; + +static const struct adux1020_mode_data adux1020_modes[] = { + [ADUX1020_MODE_PROX_I] = { + .bytes = 2, + .buf_len = 1, + .int_en = ADUX1020_PROX_INT_ENABLE, + }, +}; + +static const struct regmap_config adux1020_regmap_config = { + .name = "adux1020_regmap", + .reg_bits = 8, + .val_bits = 16, + .max_register = 0x6F, +}; + +static const struct reg_sequence adux1020_def_conf[] = { + { 0x000c, 0x000f }, + { 0x0010, 0x1010 }, + { 0x0011, 0x004c }, + { 0x0012, 0x5f0c }, + { 0x0013, 0xada5 }, + { 0x0014, 0x0080 }, + { 0x0015, 0x0000 }, + { 0x0016, 0x0600 }, + { 0x0017, 0x0000 }, + { 0x0018, 0x2693 }, + { 0x0019, 0x0004 }, + { 0x001a, 0x4280 }, + { 0x001b, 0x0060 }, + { 0x001c, 0x2094 }, + { 0x001d, 0x0020 }, + { 0x001e, 0x0001 }, + { 0x001f, 0x0100 }, + { 0x0020, 0x0320 }, + { 0x0021, 0x0A13 }, + { 0x0022, 0x0320 }, + { 0x0023, 0x0113 }, + { 0x0024, 0x0000 }, + { 0x0025, 0x2412 }, + { 0x0026, 0x2412 }, + { 0x0027, 0x0022 }, + { 0x0028, 0x0000 }, + { 0x0029, 0x0300 }, + { 0x002a, 0x0700 }, + { 0x002b, 0x0600 }, + { 0x002c, 0x6000 }, + { 0x002d, 0x4000 }, + { 0x002e, 0x0000 }, + { 0x002f, 0x0000 }, + { 0x0030, 0x0000 }, + { 0x0031, 0x0000 }, + { 0x0032, 0x0040 }, + { 0x0033, 0x0008 }, + { 0x0034, 0xE400 }, + { 0x0038, 0x8080 }, + { 0x0039, 0x8080 }, + { 0x003a, 0x2000 }, + { 0x003b, 0x1f00 }, + { 0x003c, 0x2000 }, + { 0x003d, 0x2000 }, + { 0x003e, 0x0000 }, + { 0x0040, 0x8069 }, + { 0x0041, 0x1f2f }, + { 0x0042, 0x4000 }, + { 0x0043, 0x0000 }, + { 0x0044, 0x0008 }, + { 0x0046, 0x0000 }, + { 0x0048, 0x00ef }, + { 0x0049, 0x0000 }, + { 0x0045, 0x0000 }, +}; + +static const int adux1020_rates[][2] = { + { 0, 100000 }, + { 0, 200000 }, + { 0, 500000 }, + { 1, 0 }, + { 2, 0 }, + { 5, 0 }, + { 10, 0 }, + { 20, 0 }, + { 50, 0 }, + { 100, 0 }, + { 190, 0 }, + { 450, 0 }, + { 820, 0 }, + { 1400, 0 }, +}; + +static const int adux1020_led_currents[][2] = { + { 0, 25000 }, + { 0, 40000 }, + { 0, 55000 }, + { 0, 70000 }, + { 0, 85000 }, + { 0, 100000 }, + { 0, 115000 }, + { 0, 130000 }, + { 0, 145000 }, + { 0, 160000 }, + { 0, 175000 }, + { 0, 190000 }, + { 0, 205000 }, + { 0, 220000 }, + { 0, 235000 }, + { 0, 250000 }, +}; + +static int adux1020_flush_fifo(struct adux1020_data *data) +{ + int ret; + + /* Force Idle mode */ + ret = regmap_write(data->regmap, ADUX1020_REG_FORCE_MODE, + ADUX1020_ACTIVE_4_STATE); + if (ret < 0) + return ret; + + ret = regmap_update_bits(data->regmap, ADUX1020_REG_OP_MODE, + ADUX1020_OP_MODE_MASK, ADUX1020_MODE_FORCE); + if (ret < 0) + return ret; + + ret = regmap_update_bits(data->regmap, ADUX1020_REG_OP_MODE, + ADUX1020_OP_MODE_MASK, ADUX1020_MODE_IDLE); + if (ret < 0) + return ret; + + /* Flush FIFO */ + ret = regmap_write(data->regmap, ADUX1020_REG_TEST_MODES_3, + ADUX1020_FORCE_CLOCK_ON); + if (ret < 0) + return ret; + + ret = regmap_write(data->regmap, ADUX1020_REG_INT_STATUS, + ADUX1020_FIFO_FLUSH); + if (ret < 0) + return ret; + + return regmap_write(data->regmap, ADUX1020_REG_TEST_MODES_3, + ADUX1020_FORCE_CLOCK_RESET); +} + +static int adux1020_read_fifo(struct adux1020_data *data, u16 *buf, u8 buf_len) +{ + unsigned int regval; + int i, ret; + + /* Enable 32MHz clock */ + ret = regmap_write(data->regmap, ADUX1020_REG_TEST_MODES_3, + ADUX1020_FORCE_CLOCK_ON); + if (ret < 0) + return ret; + + for (i = 0; i < buf_len; i++) { + ret = regmap_read(data->regmap, ADUX1020_REG_DATA_BUFFER, + ®val); + if (ret < 0) + return ret; + + buf[i] = regval; + } + + /* Set 32MHz clock to be controlled by internal state machine */ + return regmap_write(data->regmap, ADUX1020_REG_TEST_MODES_3, + ADUX1020_FORCE_CLOCK_RESET); +} + +static int adux1020_set_mode(struct adux1020_data *data, + enum adux1020_op_modes mode) +{ + int ret; + + /* Switch to standby mode before changing the mode */ + ret = regmap_write(data->regmap, ADUX1020_REG_OP_MODE, + ADUX1020_MODE_STANDBY); + if (ret < 0) + return ret; + + /* Set data out and switch to the desired mode */ + switch (mode) { + case ADUX1020_MODE_PROX_I: + ret = regmap_update_bits(data->regmap, ADUX1020_REG_OP_MODE, + ADUX1020_DATA_OUT_MODE_MASK, + ADUX1020_DATA_OUT_PROX_I); + if (ret < 0) + return ret; + + ret = regmap_update_bits(data->regmap, ADUX1020_REG_OP_MODE, + ADUX1020_OP_MODE_MASK, + ADUX1020_MODE_PROX_I); + if (ret < 0) + return ret; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int adux1020_measure(struct adux1020_data *data, + enum adux1020_op_modes mode, + u16 *val) +{ + unsigned int status; + int ret, tries = 50; + + /* Disable INT pin as polling is going to be used */ + ret = regmap_write(data->regmap, ADUX1020_REG_INT_ENABLE, + ADUX1020_INT_DISABLE); + if (ret < 0) + return ret; + + /* Enable mode interrupt */ + ret = regmap_update_bits(data->regmap, ADUX1020_REG_INT_MASK, + ADUX1020_MODE_INT_MASK, + adux1020_modes[mode].int_en); + if (ret < 0) + return ret; + + while (tries--) { + ret = regmap_read(data->regmap, ADUX1020_REG_INT_STATUS, + &status); + if (ret < 0) + return ret; + + status &= ADUX1020_FIFO_STATUS_MASK; + if (status >= adux1020_modes[mode].bytes) + break; + msleep(20); + } + + if (tries < 0) + return -EIO; + + ret = adux1020_read_fifo(data, val, adux1020_modes[mode].buf_len); + if (ret < 0) + return ret; + + /* Clear mode interrupt */ + ret = regmap_write(data->regmap, ADUX1020_REG_INT_STATUS, + (~adux1020_modes[mode].int_en)); + if (ret < 0) + return ret; + + /* Disable mode interrupts */ + return regmap_update_bits(data->regmap, ADUX1020_REG_INT_MASK, + ADUX1020_MODE_INT_MASK, + ADUX1020_MODE_INT_DISABLE); +} + +static int adux1020_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct adux1020_data *data = iio_priv(indio_dev); + u16 buf[3]; + int ret = -EINVAL; + unsigned int regval; + + mutex_lock(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_PROXIMITY: + ret = adux1020_set_mode(data, ADUX1020_MODE_PROX_I); + if (ret < 0) + goto fail; + + ret = adux1020_measure(data, ADUX1020_MODE_PROX_I, buf); + if (ret < 0) + goto fail; + + *val = buf[0]; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_CURRENT: + ret = regmap_read(data->regmap, + ADUX1020_REG_LED_CURRENT, ®val); + if (ret < 0) + goto fail; + + regval = regval & ADUX1020_LED_CURRENT_MASK; + + *val = adux1020_led_currents[regval][0]; + *val2 = adux1020_led_currents[regval][1]; + + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + break; + } + break; + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_PROXIMITY: + ret = regmap_read(data->regmap, ADUX1020_REG_FREQUENCY, + ®val); + if (ret < 0) + goto fail; + + regval = FIELD_GET(ADUX1020_PROX_FREQ_MASK, regval); + + *val = adux1020_rates[regval][0]; + *val2 = adux1020_rates[regval][1]; + + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + break; + } + break; + default: + break; + } + +fail: + mutex_unlock(&data->lock); + + return ret; +}; + +static inline int adux1020_find_index(const int array[][2], int count, int val, + int val2) +{ + int i; + + for (i = 0; i < count; i++) + if (val == array[i][0] && val2 == array[i][1]) + return i; + + return -EINVAL; +} + +static int adux1020_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct adux1020_data *data = iio_priv(indio_dev); + int i, ret = -EINVAL; + + mutex_lock(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (chan->type == IIO_PROXIMITY) { + i = adux1020_find_index(adux1020_rates, + ARRAY_SIZE(adux1020_rates), + val, val2); + if (i < 0) { + ret = i; + goto fail; + } + + ret = regmap_update_bits(data->regmap, + ADUX1020_REG_FREQUENCY, + ADUX1020_PROX_FREQ_MASK, + ADUX1020_PROX_FREQ(i)); + } + break; + case IIO_CHAN_INFO_PROCESSED: + if (chan->type == IIO_CURRENT) { + i = adux1020_find_index(adux1020_led_currents, + ARRAY_SIZE(adux1020_led_currents), + val, val2); + if (i < 0) { + ret = i; + goto fail; + } + + ret = regmap_update_bits(data->regmap, + ADUX1020_REG_LED_CURRENT, + ADUX1020_LED_CURRENT_MASK, i); + } + break; + default: + break; + } + +fail: + mutex_unlock(&data->lock); + + return ret; +} + +static int adux1020_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct adux1020_data *data = iio_priv(indio_dev); + int ret, mask; + + mutex_lock(&data->lock); + + ret = regmap_write(data->regmap, ADUX1020_REG_INT_ENABLE, + ADUX1020_INT_ENABLE); + if (ret < 0) + goto fail; + + ret = regmap_write(data->regmap, ADUX1020_REG_INT_POLARITY, 0); + if (ret < 0) + goto fail; + + switch (chan->type) { + case IIO_PROXIMITY: + if (dir == IIO_EV_DIR_RISING) + mask = ADUX1020_PROX_ON1_INT; + else + mask = ADUX1020_PROX_OFF1_INT; + + if (state) + ret = regmap_clear_bits(data->regmap, + ADUX1020_REG_INT_MASK, mask); + else + ret = regmap_set_bits(data->regmap, + ADUX1020_REG_INT_MASK, mask); + if (ret < 0) + goto fail; + + /* + * Trigger proximity interrupt when the intensity is above + * or below threshold + */ + ret = regmap_set_bits(data->regmap, ADUX1020_REG_PROX_TYPE, + ADUX1020_PROX_TYPE); + if (ret < 0) + goto fail; + + /* Set proximity mode */ + ret = adux1020_set_mode(data, ADUX1020_MODE_PROX_I); + break; + default: + ret = -EINVAL; + break; + } + +fail: + mutex_unlock(&data->lock); + + return ret; +} + +static int adux1020_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct adux1020_data *data = iio_priv(indio_dev); + int ret, mask; + unsigned int regval; + + switch (chan->type) { + case IIO_PROXIMITY: + if (dir == IIO_EV_DIR_RISING) + mask = ADUX1020_PROX_ON1_INT; + else + mask = ADUX1020_PROX_OFF1_INT; + break; + default: + return -EINVAL; + } + + ret = regmap_read(data->regmap, ADUX1020_REG_INT_MASK, ®val); + if (ret < 0) + return ret; + + return !(regval & mask); +} + +static int adux1020_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct adux1020_data *data = iio_priv(indio_dev); + u8 reg; + int ret; + unsigned int regval; + + switch (chan->type) { + case IIO_PROXIMITY: + if (dir == IIO_EV_DIR_RISING) + reg = ADUX1020_REG_PROX_TH_ON1; + else + reg = ADUX1020_REG_PROX_TH_OFF1; + break; + default: + return -EINVAL; + } + + ret = regmap_read(data->regmap, reg, ®val); + if (ret < 0) + return ret; + + *val = regval; + + return IIO_VAL_INT; +} + +static int adux1020_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct adux1020_data *data = iio_priv(indio_dev); + u8 reg; + + switch (chan->type) { + case IIO_PROXIMITY: + if (dir == IIO_EV_DIR_RISING) + reg = ADUX1020_REG_PROX_TH_ON1; + else + reg = ADUX1020_REG_PROX_TH_OFF1; + break; + default: + return -EINVAL; + } + + /* Full scale threshold value is 0-65535 */ + if (val < 0 || val > 65535) + return -EINVAL; + + return regmap_write(data->regmap, reg, val); +} + +static const struct iio_event_spec adux1020_proximity_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec adux1020_channels[] = { + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .event_spec = adux1020_proximity_event, + .num_event_specs = ARRAY_SIZE(adux1020_proximity_event), + }, + { + .type = IIO_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .extend_name = "led", + .output = 1, + }, +}; + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL( + "0.1 0.2 0.5 1 2 5 10 20 50 100 190 450 820 1400"); + +static struct attribute *adux1020_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group adux1020_attribute_group = { + .attrs = adux1020_attributes, +}; + +static const struct iio_info adux1020_info = { + .attrs = &adux1020_attribute_group, + .read_raw = adux1020_read_raw, + .write_raw = adux1020_write_raw, + .read_event_config = adux1020_read_event_config, + .write_event_config = adux1020_write_event_config, + .read_event_value = adux1020_read_thresh, + .write_event_value = adux1020_write_thresh, +}; + +static irqreturn_t adux1020_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct adux1020_data *data = iio_priv(indio_dev); + int ret, status; + + ret = regmap_read(data->regmap, ADUX1020_REG_INT_STATUS, &status); + if (ret < 0) + return IRQ_HANDLED; + + status &= ADUX1020_MODE_INT_STATUS_MASK; + + if (status & ADUX1020_INT_PROX_ON1) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + } + + if (status & ADUX1020_INT_PROX_OFF1) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + } + + regmap_update_bits(data->regmap, ADUX1020_REG_INT_STATUS, + ADUX1020_MODE_INT_MASK, ADUX1020_INT_CLEAR); + + return IRQ_HANDLED; +} + +static int adux1020_chip_init(struct adux1020_data *data) +{ + struct i2c_client *client = data->client; + int ret; + unsigned int val; + + ret = regmap_read(data->regmap, ADUX1020_REG_CHIP_ID, &val); + if (ret < 0) + return ret; + + if ((val & ADUX1020_CHIP_ID_MASK) != ADUX1020_CHIP_ID) { + dev_err(&client->dev, "invalid chip id 0x%04x\n", val); + return -ENODEV; + } + + dev_dbg(&client->dev, "Detected ADUX1020 with chip id: 0x%04x\n", val); + + ret = regmap_set_bits(data->regmap, ADUX1020_REG_SW_RESET, + ADUX1020_SW_RESET); + if (ret < 0) + return ret; + + /* Load default configuration */ + ret = regmap_multi_reg_write(data->regmap, adux1020_def_conf, + ARRAY_SIZE(adux1020_def_conf)); + if (ret < 0) + return ret; + + ret = adux1020_flush_fifo(data); + if (ret < 0) + return ret; + + /* Use LED_IREF for proximity mode */ + ret = regmap_clear_bits(data->regmap, ADUX1020_REG_LED_CURRENT, + ADUX1020_LED_PIREF_EN); + if (ret < 0) + return ret; + + /* Mask all interrupts */ + return regmap_update_bits(data->regmap, ADUX1020_REG_INT_MASK, + ADUX1020_MODE_INT_MASK, ADUX1020_MODE_INT_DISABLE); +} + +static int adux1020_probe(struct i2c_client *client) +{ + struct adux1020_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->info = &adux1020_info; + indio_dev->name = ADUX1020_DRV_NAME; + indio_dev->channels = adux1020_channels; + indio_dev->num_channels = ARRAY_SIZE(adux1020_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + data = iio_priv(indio_dev); + + data->regmap = devm_regmap_init_i2c(client, &adux1020_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "regmap initialization failed.\n"); + return PTR_ERR(data->regmap); + } + + data->client = client; + data->indio_dev = indio_dev; + mutex_init(&data->lock); + + ret = adux1020_chip_init(data); + if (ret) + return ret; + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, adux1020_interrupt_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + ADUX1020_DRV_NAME, indio_dev); + if (ret) + return ret; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id adux1020_id[] = { + { .name = "adux1020" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adux1020_id); + +static const struct of_device_id adux1020_of_match[] = { + { .compatible = "adi,adux1020" }, + { } +}; +MODULE_DEVICE_TABLE(of, adux1020_of_match); + +static struct i2c_driver adux1020_driver = { + .driver = { + .name = ADUX1020_DRV_NAME, + .of_match_table = adux1020_of_match, + }, + .probe = adux1020_probe, + .id_table = adux1020_id, +}; +module_i2c_driver(adux1020_driver); + +MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>"); +MODULE_DESCRIPTION("ADUX1020 photometric sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/al3000a.c b/drivers/iio/light/al3000a.c new file mode 100644 index 000000000..957ec4233 --- /dev/null +++ b/drivers/iio/light/al3000a.c @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/types.h> + +#include <linux/iio/iio.h> + +#define AL3000A_REG_SYSTEM 0x00 +#define AL3000A_REG_DATA 0x05 + +#define AL3000A_CONFIG_ENABLE 0x00 +#define AL3000A_CONFIG_DISABLE 0x0b +#define AL3000A_CONFIG_RESET 0x0f +#define AL3000A_GAIN_MASK GENMASK(5, 0) + +/* + * These are pre-calculated lux values based on possible output of sensor + * (range 0x00 - 0x3F) + */ +static const u32 lux_table[] = { + 1, 1, 1, 2, 2, 2, 3, 4, /* 0 - 7 */ + 4, 5, 6, 7, 9, 11, 13, 16, /* 8 - 15 */ + 19, 22, 27, 32, 39, 46, 56, 67, /* 16 - 23 */ + 80, 96, 116, 139, 167, 200, 240, 289, /* 24 - 31 */ + 347, 416, 499, 600, 720, 864, 1037, 1245, /* 32 - 39 */ + 1495, 1795, 2155, 2587, 3105, 3728, 4475, 5373, /* 40 - 47 */ + 6450, 7743, 9296, 11160, 13397, 16084, 19309, 23180, /* 48 - 55 */ + 27828, 33408, 40107, 48148, 57803, 69393, 83306, 100000 /* 56 - 63 */ +}; + +static const struct regmap_config al3000a_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AL3000A_REG_DATA, +}; + +struct al3000a_data { + struct regmap *regmap; + struct regulator *vdd_supply; +}; + +static const struct iio_chan_spec al3000a_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, +}; + +static int al3000a_set_pwr_on(struct al3000a_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regulator_enable(data->vdd_supply); + if (ret) { + dev_err(dev, "failed to enable vdd power supply\n"); + return ret; + } + + return regmap_write(data->regmap, AL3000A_REG_SYSTEM, AL3000A_CONFIG_ENABLE); +} + +static void al3000a_set_pwr_off(void *_data) +{ + struct al3000a_data *data = _data; + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regmap_write(data->regmap, AL3000A_REG_SYSTEM, AL3000A_CONFIG_DISABLE); + if (ret) + dev_err(dev, "failed to write system register\n"); + + ret = regulator_disable(data->vdd_supply); + if (ret) + dev_err(dev, "failed to disable vdd power supply\n"); +} + +static int al3000a_init(struct al3000a_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = al3000a_set_pwr_on(data); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, al3000a_set_pwr_off, data); + if (ret) + return ret; + + ret = regmap_write(data->regmap, AL3000A_REG_SYSTEM, AL3000A_CONFIG_RESET); + if (ret) + return ret; + + return regmap_write(data->regmap, AL3000A_REG_SYSTEM, AL3000A_CONFIG_ENABLE); +} + +static int al3000a_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct al3000a_data *data = iio_priv(indio_dev); + int ret, gain; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = regmap_read(data->regmap, AL3000A_REG_DATA, &gain); + if (ret) + return ret; + + *val = lux_table[gain & AL3000A_GAIN_MASK]; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_info al3000a_info = { + .read_raw = al3000a_read_raw, +}; + +static int al3000a_probe(struct i2c_client *client) +{ + struct al3000a_data *data; + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + + data->regmap = devm_regmap_init_i2c(client, &al3000a_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "cannot allocate regmap\n"); + + data->vdd_supply = devm_regulator_get(dev, "vdd"); + if (IS_ERR(data->vdd_supply)) + return dev_err_probe(dev, PTR_ERR(data->vdd_supply), + "failed to get vdd regulator\n"); + + indio_dev->info = &al3000a_info; + indio_dev->name = "al3000a"; + indio_dev->channels = al3000a_channels; + indio_dev->num_channels = ARRAY_SIZE(al3000a_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = al3000a_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to init ALS\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static int al3000a_suspend(struct device *dev) +{ + struct al3000a_data *data = iio_priv(dev_get_drvdata(dev)); + + al3000a_set_pwr_off(data); + return 0; +} + +static int al3000a_resume(struct device *dev) +{ + struct al3000a_data *data = iio_priv(dev_get_drvdata(dev)); + + return al3000a_set_pwr_on(data); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(al3000a_pm_ops, al3000a_suspend, al3000a_resume); + +static const struct i2c_device_id al3000a_id[] = { + { .name = "al3000a" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, al3000a_id); + +static const struct of_device_id al3000a_of_match[] = { + { .compatible = "dynaimage,al3000a" }, + { } +}; +MODULE_DEVICE_TABLE(of, al3000a_of_match); + +static struct i2c_driver al3000a_driver = { + .driver = { + .name = "al3000a", + .of_match_table = al3000a_of_match, + .pm = pm_sleep_ptr(&al3000a_pm_ops), + }, + .probe = al3000a_probe, + .id_table = al3000a_id, +}; +module_i2c_driver(al3000a_driver); + +MODULE_AUTHOR("Svyatolsav Ryhel <clamor95@gmail.com>"); +MODULE_DESCRIPTION("al3000a Ambient Light Sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/al3010.c b/drivers/iio/light/al3010.c new file mode 100644 index 000000000..49f6d8336 --- /dev/null +++ b/drivers/iio/light/al3010.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AL3010 - Dyna Image Ambient Light Sensor + * + * Copyright (c) 2014, Intel Corporation. + * Copyright (c) 2016, Dyna-Image Corp. + * Copyright (c) 2020, David Heidelberg, MichaÅ‚ MirosÅ‚aw, Dmitry Osipenko + * + * IIO driver for AL3010 (7-bit I2C slave address 0x1C). + * + * TODO: interrupt support, thresholds + * When the driver will get support for interrupt handling, then interrupt + * will need to be disabled before turning sensor OFF in order to avoid + * potential races with the interrupt handling. + */ + +#include <linux/bitfield.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define AL3010_REG_SYSTEM 0x00 +#define AL3010_REG_DATA_LOW 0x0c +#define AL3010_REG_CONFIG 0x10 + +#define AL3010_CONFIG_DISABLE 0x00 +#define AL3010_CONFIG_ENABLE 0x01 + +#define AL3010_GAIN_MASK GENMASK(6,4) + +#define AL3010_SCALE_AVAILABLE "1.1872 0.2968 0.0742 0.018" + +enum al3xxxx_range { + AL3XXX_RANGE_1, /* 77806 lx */ + AL3XXX_RANGE_2, /* 19542 lx */ + AL3XXX_RANGE_3, /* 4863 lx */ + AL3XXX_RANGE_4 /* 1216 lx */ +}; + +static const int al3010_scales[][2] = { + { 1, 187200 }, { 0, 296800 }, { 0, 74200 }, { 0, 18600 }, +}; + +static const struct regmap_config al3010_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AL3010_REG_CONFIG, +}; + +struct al3010_data { + struct regmap *regmap; +}; + +static const struct iio_chan_spec al3010_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + } +}; + +static IIO_CONST_ATTR(in_illuminance_scale_available, AL3010_SCALE_AVAILABLE); + +static struct attribute *al3010_attributes[] = { + &iio_const_attr_in_illuminance_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group al3010_attribute_group = { + .attrs = al3010_attributes, +}; + +static int al3010_set_pwr_on(struct al3010_data *data) +{ + return regmap_write(data->regmap, AL3010_REG_SYSTEM, AL3010_CONFIG_ENABLE); +} + +static void al3010_set_pwr_off(void *_data) +{ + struct al3010_data *data = _data; + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regmap_write(data->regmap, AL3010_REG_SYSTEM, AL3010_CONFIG_DISABLE); + if (ret) + dev_err(dev, "failed to write system register\n"); +} + +static int al3010_init(struct al3010_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = al3010_set_pwr_on(data); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, al3010_set_pwr_off, data); + if (ret) + return ret; + return regmap_write(data->regmap, AL3010_REG_CONFIG, + FIELD_PREP(AL3010_GAIN_MASK, AL3XXX_RANGE_3)); +} + +static int al3010_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct al3010_data *data = iio_priv(indio_dev); + int ret, gain; + __le16 raw; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * ALS ADC value is stored in two adjacent registers: + * - low byte of output is stored at AL3010_REG_DATA_LOW + * - high byte of output is stored at AL3010_REG_DATA_LOW + 1 + */ + ret = regmap_bulk_read(data->regmap, AL3010_REG_DATA_LOW, + &raw, sizeof(raw)); + if (ret) + return ret; + + *val = le16_to_cpu(raw); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(data->regmap, AL3010_REG_CONFIG, &gain); + if (ret) + return ret; + + gain = FIELD_GET(AL3010_GAIN_MASK, gain); + *val = al3010_scales[gain][0]; + *val2 = al3010_scales[gain][1]; + + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int al3010_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct al3010_data *data = iio_priv(indio_dev); + unsigned int i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ARRAY_SIZE(al3010_scales); i++) { + if (val != al3010_scales[i][0] || + val2 != al3010_scales[i][1]) + continue; + + return regmap_write(data->regmap, AL3010_REG_CONFIG, + FIELD_PREP(AL3010_GAIN_MASK, i)); + } + break; + } + return -EINVAL; +} + +static const struct iio_info al3010_info = { + .read_raw = al3010_read_raw, + .write_raw = al3010_write_raw, + .attrs = &al3010_attribute_group, +}; + +static int al3010_probe(struct i2c_client *client) +{ + struct al3010_data *data; + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->regmap = devm_regmap_init_i2c(client, &al3010_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "cannot allocate regmap\n"); + + indio_dev->info = &al3010_info; + indio_dev->name = "al3010"; + indio_dev->channels = al3010_channels; + indio_dev->num_channels = ARRAY_SIZE(al3010_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = al3010_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to init ALS\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static int al3010_suspend(struct device *dev) +{ + struct al3010_data *data = iio_priv(dev_get_drvdata(dev)); + + al3010_set_pwr_off(data); + return 0; +} + +static int al3010_resume(struct device *dev) +{ + struct al3010_data *data = iio_priv(dev_get_drvdata(dev)); + + return al3010_set_pwr_on(data); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(al3010_pm_ops, al3010_suspend, al3010_resume); + +static const struct i2c_device_id al3010_id[] = { + { .name = "al3010" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, al3010_id); + +static const struct of_device_id al3010_of_match[] = { + { .compatible = "dynaimage,al3010", }, + { } +}; +MODULE_DEVICE_TABLE(of, al3010_of_match); + +static struct i2c_driver al3010_driver = { + .driver = { + .name = "al3010", + .of_match_table = al3010_of_match, + .pm = pm_sleep_ptr(&al3010_pm_ops), + }, + .probe = al3010_probe, + .id_table = al3010_id, +}; +module_i2c_driver(al3010_driver); + +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>"); +MODULE_AUTHOR("David Heidelberg <david@ixit.cz>"); +MODULE_DESCRIPTION("AL3010 Ambient Light Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/al3320a.c b/drivers/iio/light/al3320a.c new file mode 100644 index 000000000..8bb7f7e87 --- /dev/null +++ b/drivers/iio/light/al3320a.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AL3320A - Dyna Image Ambient Light Sensor + * + * Copyright (c) 2014, Intel Corporation. + * + * IIO driver for AL3320A (7-bit I2C slave address 0x1C). + * + * TODO: interrupt support, thresholds + * When the driver will get support for interrupt handling, then interrupt + * will need to be disabled before turning sensor OFF in order to avoid + * potential races with the interrupt handling. + */ + +#include <linux/bitfield.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define AL3320A_REG_CONFIG 0x00 +#define AL3320A_REG_STATUS 0x01 +#define AL3320A_REG_INT 0x02 +#define AL3320A_REG_WAIT 0x06 +#define AL3320A_REG_CONFIG_RANGE 0x07 +#define AL3320A_REG_PERSIST 0x08 +#define AL3320A_REG_MEAN_TIME 0x09 +#define AL3320A_REG_ADUMMY 0x0A +#define AL3320A_REG_DATA_LOW 0x22 + +#define AL3320A_REG_LOW_THRESH_LOW 0x30 +#define AL3320A_REG_LOW_THRESH_HIGH 0x31 +#define AL3320A_REG_HIGH_THRESH_LOW 0x32 +#define AL3320A_REG_HIGH_THRESH_HIGH 0x33 + +#define AL3320A_CONFIG_DISABLE 0x00 +#define AL3320A_CONFIG_ENABLE 0x01 + +#define AL3320A_GAIN_MASK GENMASK(2, 1) + +/* chip params default values */ +#define AL3320A_DEFAULT_MEAN_TIME 4 +#define AL3320A_DEFAULT_WAIT_TIME 0 /* no waiting */ + +#define AL3320A_SCALE_AVAILABLE "0.512 0.128 0.032 0.01" + +enum al3320a_range { + AL3320A_RANGE_1, /* 33.28 Klx */ + AL3320A_RANGE_2, /* 8.32 Klx */ + AL3320A_RANGE_3, /* 2.08 Klx */ + AL3320A_RANGE_4 /* 0.65 Klx */ +}; + +static const int al3320a_scales[][2] = { + {0, 512000}, {0, 128000}, {0, 32000}, {0, 10000} +}; + +static const struct regmap_config al3320a_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AL3320A_REG_HIGH_THRESH_HIGH, +}; + +struct al3320a_data { + struct regmap *regmap; +}; + +static const struct iio_chan_spec al3320a_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + } +}; + +static IIO_CONST_ATTR(in_illuminance_scale_available, AL3320A_SCALE_AVAILABLE); + +static struct attribute *al3320a_attributes[] = { + &iio_const_attr_in_illuminance_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group al3320a_attribute_group = { + .attrs = al3320a_attributes, +}; + +static int al3320a_set_pwr_on(struct al3320a_data *data) +{ + return regmap_write(data->regmap, AL3320A_REG_CONFIG, AL3320A_CONFIG_ENABLE); +} + +static void al3320a_set_pwr_off(void *_data) +{ + struct al3320a_data *data = _data; + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regmap_write(data->regmap, AL3320A_REG_CONFIG, AL3320A_CONFIG_DISABLE); + if (ret) + dev_err(dev, "failed to write system register\n"); +} + +static int al3320a_init(struct al3320a_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = al3320a_set_pwr_on(data); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, al3320a_set_pwr_off, data); + if (ret) + return ret; + + ret = regmap_write(data->regmap, AL3320A_REG_CONFIG_RANGE, + FIELD_PREP(AL3320A_GAIN_MASK, AL3320A_RANGE_3)); + if (ret) + return ret; + + ret = regmap_write(data->regmap, AL3320A_REG_MEAN_TIME, + AL3320A_DEFAULT_MEAN_TIME); + if (ret) + return ret; + + return regmap_write(data->regmap, AL3320A_REG_WAIT, + AL3320A_DEFAULT_WAIT_TIME); +} + +static int al3320a_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct al3320a_data *data = iio_priv(indio_dev); + int ret, gain; + __le16 raw; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * ALS ADC value is stored in two adjacent registers: + * - low byte of output is stored at AL3320A_REG_DATA_LOW + * - high byte of output is stored at AL3320A_REG_DATA_LOW + 1 + */ + ret = regmap_bulk_read(data->regmap, AL3320A_REG_DATA_LOW, + &raw, sizeof(raw)); + if (ret) + return ret; + + *val = le16_to_cpu(raw); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(data->regmap, AL3320A_REG_CONFIG_RANGE, &gain); + if (ret) + return ret; + + gain = FIELD_GET(AL3320A_GAIN_MASK, gain); + *val = al3320a_scales[gain][0]; + *val2 = al3320a_scales[gain][1]; + + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int al3320a_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct al3320a_data *data = iio_priv(indio_dev); + unsigned int i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ARRAY_SIZE(al3320a_scales); i++) { + if (val != al3320a_scales[i][0] || + val2 != al3320a_scales[i][1]) + continue; + + return regmap_write(data->regmap, AL3320A_REG_CONFIG_RANGE, + FIELD_PREP(AL3320A_GAIN_MASK, i)); + } + break; + } + return -EINVAL; +} + +static const struct iio_info al3320a_info = { + .read_raw = al3320a_read_raw, + .write_raw = al3320a_write_raw, + .attrs = &al3320a_attribute_group, +}; + +static int al3320a_probe(struct i2c_client *client) +{ + struct al3320a_data *data; + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + + data->regmap = devm_regmap_init_i2c(client, &al3320a_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "cannot allocate regmap\n"); + + indio_dev->info = &al3320a_info; + indio_dev->name = "al3320a"; + indio_dev->channels = al3320a_channels; + indio_dev->num_channels = ARRAY_SIZE(al3320a_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = al3320a_init(data); + if (ret < 0) { + dev_err(dev, "al3320a chip init failed\n"); + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static int al3320a_suspend(struct device *dev) +{ + struct al3320a_data *data = iio_priv(dev_get_drvdata(dev)); + + al3320a_set_pwr_off(data); + return 0; +} + +static int al3320a_resume(struct device *dev) +{ + struct al3320a_data *data = iio_priv(dev_get_drvdata(dev)); + + return al3320a_set_pwr_on(data); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(al3320a_pm_ops, al3320a_suspend, + al3320a_resume); + +static const struct i2c_device_id al3320a_id[] = { + { .name = "al3320a" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, al3320a_id); + +static const struct of_device_id al3320a_of_match[] = { + { .compatible = "dynaimage,al3320a", }, + { } +}; +MODULE_DEVICE_TABLE(of, al3320a_of_match); + +static const struct acpi_device_id al3320a_acpi_match[] = { + {"CALS0001"}, + { } +}; +MODULE_DEVICE_TABLE(acpi, al3320a_acpi_match); + +static struct i2c_driver al3320a_driver = { + .driver = { + .name = "al3320a", + .of_match_table = al3320a_of_match, + .pm = pm_sleep_ptr(&al3320a_pm_ops), + .acpi_match_table = al3320a_acpi_match, + }, + .probe = al3320a_probe, + .id_table = al3320a_id, +}; + +module_i2c_driver(al3320a_driver); + +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>"); +MODULE_DESCRIPTION("AL3320A Ambient Light Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/apds9160.c b/drivers/iio/light/apds9160.c new file mode 100644 index 000000000..d7e0924b8 --- /dev/null +++ b/drivers/iio/light/apds9160.c @@ -0,0 +1,1589 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * APDS9160 sensor driver. + * Chip is combined proximity and ambient light sensor. + * Author: 2024 Mikael Gonella-Bolduc <m.gonella.bolduc@gmail.com> + */ + +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <linux/iio/iio.h> +#include <linux/iio/events.h> +#include <linux/iio/sysfs.h> + +#include <linux/unaligned.h> + +/* Main control register */ +#define APDS9160_REG_CTRL 0x00 +#define APDS9160_CTRL_SWRESET BIT(4) /* 1: Activate reset */ +#define APDS9160_CTRL_MODE_RGB BIT(2) /* 0: ALS & IR, 1: RGB & IR */ +#define APDS9160_CTRL_EN_ALS BIT(1) /* 1: ALS active */ +#define APDS9160_CTLR_EN_PS BIT(0) /* 1: PS active */ + +/* Status register */ +#define APDS9160_SR_LS_INT BIT(4) +#define APDS9160_SR_LS_NEW_DATA BIT(3) +#define APDS9160_SR_PS_INT BIT(1) +#define APDS9160_SR_PS_NEW_DATA BIT(0) + +/* Interrupt configuration registers */ +#define APDS9160_REG_INT_CFG 0x19 +#define APDS9160_REG_INT_PST 0x1A +#define APDS9160_INT_CFG_EN_LS BIT(2) /* LS int enable */ +#define APDS9160_INT_CFG_EN_PS BIT(0) /* PS int enable */ + +/* Proximity registers */ +#define APDS9160_REG_PS_LED 0x01 +#define APDS9160_REG_PS_PULSES 0x02 +#define APDS9160_REG_PS_MEAS_RATE 0x03 +#define APDS9160_REG_PS_THRES_HI_LSB 0x1B +#define APDS9160_REG_PS_THRES_HI_MSB 0x1C +#define APDS9160_REG_PS_THRES_LO_LSB 0x1D +#define APDS9160_REG_PS_THRES_LO_MSB 0x1E +#define APDS9160_REG_PS_DATA_LSB 0x08 +#define APDS9160_REG_PS_DATA_MSB 0x09 +#define APDS9160_REG_PS_CAN_LEVEL_DIG_LSB 0x1F +#define APDS9160_REG_PS_CAN_LEVEL_DIG_MSB 0x20 +#define APDS9160_REG_PS_CAN_LEVEL_ANA_DUR 0x21 +#define APDS9160_REG_PS_CAN_LEVEL_ANA_CURRENT 0x22 + +/* Light sensor registers */ +#define APDS9160_REG_LS_MEAS_RATE 0x04 +#define APDS9160_REG_LS_GAIN 0x05 +#define APDS9160_REG_LS_DATA_CLEAR_LSB 0x0A +#define APDS9160_REG_LS_DATA_CLEAR 0x0B +#define APDS9160_REG_LS_DATA_CLEAR_MSB 0x0C +#define APDS9160_REG_LS_DATA_ALS_LSB 0x0D +#define APDS9160_REG_LS_DATA_ALS 0x0E +#define APDS9160_REG_LS_DATA_ALS_MSB 0x0F +#define APDS9160_REG_LS_THRES_UP_LSB 0x24 +#define APDS9160_REG_LS_THRES_UP 0x25 +#define APDS9160_REG_LS_THRES_UP_MSB 0x26 +#define APDS9160_REG_LS_THRES_LO_LSB 0x27 +#define APDS9160_REG_LS_THRES_LO 0x28 +#define APDS9160_REG_LS_THRES_LO_MSB 0x29 +#define APDS9160_REG_LS_THRES_VAR 0x2A + +/* Part identification number register */ +#define APDS9160_REG_ID 0x06 + +/* Status register */ +#define APDS9160_REG_SR 0x07 +#define APDS9160_SR_DATA_ALS BIT(3) +#define APDS9160_SR_DATA_PS BIT(0) + +/* Supported ID:s */ +#define APDS9160_PART_ID_0 0x03 + +#define APDS9160_PS_THRES_MAX 0x7FF +#define APDS9160_LS_THRES_MAX 0xFFFFF +#define APDS9160_CMD_LS_RESOLUTION_25MS 0x04 +#define APDS9160_CMD_LS_RESOLUTION_50MS 0x03 +#define APDS9160_CMD_LS_RESOLUTION_100MS 0x02 +#define APDS9160_CMD_LS_RESOLUTION_200MS 0x01 +#define APDS9160_PS_DATA_MASK 0x7FF + +#define APDS9160_DEFAULT_LS_GAIN 3 +#define APDS9160_DEFAULT_LS_RATE 100 +#define APDS9160_DEFAULT_PS_RATE 100 +#define APDS9160_DEFAULT_PS_CANCELLATION_LEVEL 0 +#define APDS9160_DEFAULT_PS_ANALOG_CANCELLATION 0 +#define APDS9160_DEFAULT_PS_GAIN 1 +#define APDS9160_DEFAULT_PS_CURRENT 100 +#define APDS9160_DEFAULT_PS_RESOLUTION_11BITS 0x03 + +static const struct reg_default apds9160_reg_defaults[] = { + { APDS9160_REG_CTRL, 0x00 }, /* Sensors disabled by default */ + { APDS9160_REG_PS_LED, 0x33 }, /* 60 kHz frequency, 100 mA */ + { APDS9160_REG_PS_PULSES, 0x08 }, /* 8 pulses */ + { APDS9160_REG_PS_MEAS_RATE, 0x05 }, /* 100ms */ + { APDS9160_REG_LS_MEAS_RATE, 0x22 }, /* 100ms */ + { APDS9160_REG_LS_GAIN, 0x01 }, /* 3x */ + { APDS9160_REG_INT_CFG, 0x10 }, /* Interrupts disabled */ + { APDS9160_REG_INT_PST, 0x00 }, + { APDS9160_REG_PS_THRES_HI_LSB, 0xFF }, + { APDS9160_REG_PS_THRES_HI_MSB, 0x07 }, + { APDS9160_REG_PS_THRES_LO_LSB, 0x00 }, + { APDS9160_REG_PS_THRES_LO_MSB, 0x00 }, + { APDS9160_REG_PS_CAN_LEVEL_DIG_LSB, 0x00 }, + { APDS9160_REG_PS_CAN_LEVEL_DIG_MSB, 0x00 }, + { APDS9160_REG_PS_CAN_LEVEL_ANA_DUR, 0x00 }, + { APDS9160_REG_PS_CAN_LEVEL_ANA_CURRENT, 0x00 }, + { APDS9160_REG_LS_THRES_UP_LSB, 0xFF }, + { APDS9160_REG_LS_THRES_UP, 0xFF }, + { APDS9160_REG_LS_THRES_UP_MSB, 0x0F }, + { APDS9160_REG_LS_THRES_LO_LSB, 0x00 }, + { APDS9160_REG_LS_THRES_LO, 0x00 }, + { APDS9160_REG_LS_THRES_LO_MSB, 0x00 }, + { APDS9160_REG_LS_THRES_VAR, 0x00 }, +}; + +static const struct regmap_range apds9160_readable_ranges[] = { + regmap_reg_range(APDS9160_REG_CTRL, APDS9160_REG_LS_THRES_VAR), +}; + +static const struct regmap_access_table apds9160_readable_table = { + .yes_ranges = apds9160_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9160_readable_ranges), +}; + +static const struct regmap_range apds9160_writeable_ranges[] = { + regmap_reg_range(APDS9160_REG_CTRL, APDS9160_REG_LS_GAIN), + regmap_reg_range(APDS9160_REG_INT_CFG, APDS9160_REG_LS_THRES_VAR), +}; + +static const struct regmap_access_table apds9160_writeable_table = { + .yes_ranges = apds9160_writeable_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9160_writeable_ranges), +}; + +static const struct regmap_range apds9160_volatile_ranges[] = { + regmap_reg_range(APDS9160_REG_SR, APDS9160_REG_LS_DATA_ALS_MSB), +}; + +static const struct regmap_access_table apds9160_volatile_table = { + .yes_ranges = apds9160_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9160_volatile_ranges), +}; + +static const struct regmap_config apds9160_regmap_config = { + .name = "apds9160_regmap", + .reg_bits = 8, + .val_bits = 8, + .use_single_read = true, + .use_single_write = true, + + .rd_table = &apds9160_readable_table, + .wr_table = &apds9160_writeable_table, + .volatile_table = &apds9160_volatile_table, + + .reg_defaults = apds9160_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(apds9160_reg_defaults), + .max_register = 37, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct iio_event_spec apds9160_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec apds9160_channels[] = { + { + /* Proximity sensor channel */ + .type = IIO_PROXIMITY, + .address = APDS9160_REG_PS_DATA_LSB, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .event_spec = apds9160_event_spec, + .num_event_specs = ARRAY_SIZE(apds9160_event_spec), + }, + { + /* Proximity sensor led current */ + .type = IIO_CURRENT, + .output = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), + }, + { + /* Illuminance */ + .type = IIO_LIGHT, + .address = APDS9160_REG_LS_DATA_ALS_LSB, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .event_spec = apds9160_event_spec, + .num_event_specs = ARRAY_SIZE(apds9160_event_spec), + }, + { + /* Clear channel */ + .type = IIO_INTENSITY, + .address = APDS9160_REG_LS_DATA_CLEAR_LSB, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .channel2 = IIO_MOD_LIGHT_CLEAR, + .modified = 1, + }, +}; + +static const struct iio_chan_spec apds9160_channels_without_events[] = { + { + /* Proximity sensor channel */ + .type = IIO_PROXIMITY, + .address = APDS9160_REG_PS_DATA_LSB, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + /* Proximity sensor led current */ + .type = IIO_CURRENT, + .output = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), + }, + { + /* Illuminance */ + .type = IIO_LIGHT, + .address = APDS9160_REG_LS_DATA_ALS_LSB, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + /* Clear channel */ + .type = IIO_INTENSITY, + .address = APDS9160_REG_LS_DATA_CLEAR_LSB, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .channel2 = IIO_MOD_LIGHT_CLEAR, + .modified = 1, + }, +}; + +static const int apds9160_als_rate_avail[] = { + 25, 50, 100, 200 +}; + +static const int apds9160_als_rate_map[][2] = { + { 25, 0x00 }, + { 50, 0x01 }, + { 100, 0x02 }, + { 200, 0x03 }, +}; + +static const int apds9160_als_gain_map[][2] = { + { 1, 0x00 }, + { 3, 0x01 }, + { 6, 0x02 }, + { 18, 0x03 }, + { 54, 0x04 }, +}; + +static const int apds9160_ps_gain_avail[] = { + 1, 2, 4, 8 +}; + +static const int apds9160_ps_gain_map[][2] = { + { 1, 0x00 }, + { 2, 0x01 }, + { 4, 0x02 }, + { 8, 0x03 }, +}; + +static const int apds9160_ps_rate_avail[] = { + 25, 50, 100, 200, 400 +}; + +static const int apds9160_ps_rate_map[][2] = { + { 25, 0x03 }, + { 50, 0x04 }, + { 100, 0x05 }, + { 200, 0x06 }, + { 400, 0x07 }, +}; + +static const int apds9160_ps_led_current_avail[] = { + 10, 25, 50, 100, 150, 175, 200 +}; + +static const int apds9160_ps_led_current_map[][2] = { + { 10, 0x00 }, + { 25, 0x01 }, + { 50, 0x02 }, + { 100, 0x03 }, + { 150, 0x04 }, + { 175, 0x05 }, + { 200, 0x06 }, +}; + +/** + * struct apds9160_scale - apds9160 scale mapping definition + * + * @itime: Integration time in ms + * @gain: Gain multiplier + * @scale1: lux/count resolution + * @scale2: micro lux/count + */ +struct apds9160_scale { + int itime; + int gain; + int scale1; + int scale2; +}; + +/* Scale mapping extracted from datasheet */ +static const struct apds9160_scale apds9160_als_scale_map[] = { + { + .itime = 25, + .gain = 1, + .scale1 = 3, + .scale2 = 272000, + }, + { + .itime = 25, + .gain = 3, + .scale1 = 1, + .scale2 = 77000, + }, + { + .itime = 25, + .gain = 6, + .scale1 = 0, + .scale2 = 525000, + }, + { + .itime = 25, + .gain = 18, + .scale1 = 0, + .scale2 = 169000, + }, + { + .itime = 25, + .gain = 54, + .scale1 = 0, + .scale2 = 49000, + }, + { + .itime = 50, + .gain = 1, + .scale1 = 1, + .scale2 = 639000, + }, + { + .itime = 50, + .gain = 3, + .scale1 = 0, + .scale2 = 538000, + }, + { + .itime = 50, + .gain = 6, + .scale1 = 0, + .scale2 = 263000, + }, + { + .itime = 50, + .gain = 18, + .scale1 = 0, + .scale2 = 84000, + }, + { + .itime = 50, + .gain = 54, + .scale1 = 0, + .scale2 = 25000, + }, + { + .itime = 100, + .gain = 1, + .scale1 = 0, + .scale2 = 819000, + }, + { + .itime = 100, + .gain = 3, + .scale1 = 0, + .scale2 = 269000, + }, + { + .itime = 100, + .gain = 6, + .scale1 = 0, + .scale2 = 131000, + }, + { + .itime = 100, + .gain = 18, + .scale1 = 0, + .scale2 = 42000, + }, + { + .itime = 100, + .gain = 54, + .scale1 = 0, + .scale2 = 12000, + }, + { + .itime = 200, + .gain = 1, + .scale1 = 0, + .scale2 = 409000, + }, + { + .itime = 200, + .gain = 3, + .scale1 = 0, + .scale2 = 135000, + }, + { + .itime = 200, + .gain = 6, + .scale1 = 0, + .scale2 = 66000, + }, + { + .itime = 200, + .gain = 18, + .scale1 = 0, + .scale2 = 21000, + }, + { + .itime = 200, + .gain = 54, + .scale1 = 0, + .scale2 = 6000, + }, +}; + +static const int apds9160_25ms_avail[][2] = { + { 3, 272000 }, + { 1, 77000 }, + { 0, 525000 }, + { 0, 169000 }, + { 0, 49000 }, +}; + +static const int apds9160_50ms_avail[][2] = { + { 1, 639000 }, + { 0, 538000 }, + { 0, 263000 }, + { 0, 84000 }, + { 0, 25000 }, +}; + +static const int apds9160_100ms_avail[][2] = { + { 0, 819000 }, + { 0, 269000 }, + { 0, 131000 }, + { 0, 42000 }, + { 0, 12000 }, +}; + +static const int apds9160_200ms_avail[][2] = { + { 0, 409000 }, + { 0, 135000 }, + { 0, 66000 }, + { 0, 21000 }, + { 0, 6000 }, +}; + +static const struct reg_field apds9160_reg_field_ls_en = + REG_FIELD(APDS9160_REG_CTRL, 1, 1); + +static const struct reg_field apds9160_reg_field_ps_en = + REG_FIELD(APDS9160_REG_CTRL, 0, 0); + +static const struct reg_field apds9160_reg_field_int_ps = + REG_FIELD(APDS9160_REG_INT_CFG, 0, 0); + +static const struct reg_field apds9160_reg_field_int_als = + REG_FIELD(APDS9160_REG_INT_CFG, 2, 2); + +static const struct reg_field apds9160_reg_field_ps_overflow = + REG_FIELD(APDS9160_REG_PS_DATA_MSB, 3, 3); + +static const struct reg_field apds9160_reg_field_als_rate = + REG_FIELD(APDS9160_REG_LS_MEAS_RATE, 0, 2); + +static const struct reg_field apds9160_reg_field_als_gain = + REG_FIELD(APDS9160_REG_LS_GAIN, 0, 2); + +static const struct reg_field apds9160_reg_field_ps_rate = + REG_FIELD(APDS9160_REG_PS_MEAS_RATE, 0, 2); + +static const struct reg_field apds9160_reg_field_als_res = + REG_FIELD(APDS9160_REG_LS_MEAS_RATE, 4, 6); + +static const struct reg_field apds9160_reg_field_ps_current = + REG_FIELD(APDS9160_REG_PS_LED, 0, 2); + +static const struct reg_field apds9160_reg_field_ps_gain = + REG_FIELD(APDS9160_REG_PS_MEAS_RATE, 6, 7); + +static const struct reg_field apds9160_reg_field_ps_resolution = + REG_FIELD(APDS9160_REG_PS_MEAS_RATE, 3, 4); + +struct apds9160_chip { + struct i2c_client *client; + struct regmap *regmap; + + struct regmap_field *reg_enable_ps; + struct regmap_field *reg_enable_als; + struct regmap_field *reg_int_ps; + struct regmap_field *reg_int_als; + struct regmap_field *reg_ps_overflow; + struct regmap_field *reg_als_rate; + struct regmap_field *reg_als_resolution; + struct regmap_field *reg_ps_rate; + struct regmap_field *reg_als_gain; + struct regmap_field *reg_ps_current; + struct regmap_field *reg_ps_gain; + struct regmap_field *reg_ps_resolution; + + struct mutex lock; /* protects state and config data */ + + /* State data */ + int als_int; + int ps_int; + + /* Configuration values */ + int als_itime; + int als_hwgain; + int als_scale1; + int als_scale2; + int ps_rate; + int ps_cancellation_level; + int ps_current; + int ps_gain; +}; + +static int apds9160_set_ps_rate(struct apds9160_chip *data, int val) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(apds9160_ps_rate_map); idx++) { + int ret; + + if (apds9160_ps_rate_map[idx][0] != val) + continue; + + ret = regmap_field_write(data->reg_ps_rate, + apds9160_ps_rate_map[idx][1]); + if (ret) + return ret; + data->ps_rate = val; + + return ret; + } + + return -EINVAL; +} + +static int apds9160_set_ps_gain(struct apds9160_chip *data, int val) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(apds9160_ps_gain_map); idx++) { + int ret; + + if (apds9160_ps_gain_map[idx][0] != val) + continue; + + ret = regmap_field_write(data->reg_ps_gain, + apds9160_ps_gain_map[idx][1]); + if (ret) + return ret; + data->ps_gain = val; + + return ret; + } + + return -EINVAL; +} + +/* + * The PS intelligent cancellation level register allows + * for an on-chip subtraction of the ADC count caused by + * unwanted reflected light from PS ADC output. + */ +static int apds9160_set_ps_cancellation_level(struct apds9160_chip *data, + int val) +{ + int ret; + __le16 buf; + + if (val < 0 || val > 0xFFFF) + return -EINVAL; + + buf = cpu_to_le16(val); + ret = regmap_bulk_write(data->regmap, APDS9160_REG_PS_CAN_LEVEL_DIG_LSB, + &buf, 2); + if (ret) + return ret; + + data->ps_cancellation_level = val; + + return ret; +} + +/* + * This parameter determines the cancellation pulse duration + * in each of the PWM pulse. The cancellation is applied during the + * integration phase of the PS measurement. + * Duration is programmed in half clock cycles + * A duration value of 0 or 1 will not generate any cancellation pulse + */ +static int apds9160_set_ps_analog_cancellation(struct apds9160_chip *data, + int val) +{ + if (val < 0 || val > 63) + return -EINVAL; + + return regmap_write(data->regmap, APDS9160_REG_PS_CAN_LEVEL_ANA_DUR, + val); +} + +/* + * This parameter works in conjunction with the cancellation pulse duration + * The value determines the current used for crosstalk cancellation + * Coarse value is in steps of 60 nA + * Fine value is in steps of 2.4 nA + */ +static int apds9160_set_ps_cancellation_current(struct apds9160_chip *data, + int coarse_val, + int fine_val) +{ + int val; + + if (coarse_val < 0 || coarse_val > 4) + return -EINVAL; + + if (fine_val < 0 || fine_val > 15) + return -EINVAL; + + /* Coarse value at B4:B5 and fine value at B0:B3 */ + val = (coarse_val << 4) | fine_val; + + return regmap_write(data->regmap, APDS9160_REG_PS_CAN_LEVEL_ANA_CURRENT, + val); +} + +static int apds9160_ps_init_analog_cancellation(struct device *dev, + struct apds9160_chip *data) +{ + int ret, duration, picoamp, idx, coarse, fine; + + ret = device_property_read_u32(dev, + "ps-cancellation-duration", &duration); + if (ret || duration == 0) { + /* Don't fail since this is not required */ + return 0; + } + + ret = device_property_read_u32(dev, + "ps-cancellation-current-picoamp", &picoamp); + if (ret) + return ret; + + if (picoamp < 60000 || picoamp > 276000 || picoamp % 2400 != 0) + return dev_err_probe(dev, -EINVAL, + "Invalid cancellation current\n"); + + /* Compute required coarse and fine value from requested current */ + fine = 0; + coarse = 0; + for (idx = 60000; idx < picoamp; idx += 2400) { + if (fine == 15) { + fine = 0; + coarse++; + idx += 21600; + } else { + fine++; + } + } + + if (picoamp != idx) + dev_warn(dev, + "Invalid cancellation current %i, rounding to %i\n", + picoamp, idx); + + ret = apds9160_set_ps_analog_cancellation(data, duration); + if (ret) + return ret; + + return apds9160_set_ps_cancellation_current(data, coarse, fine); +} + +static int apds9160_set_ps_current(struct apds9160_chip *data, int val) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(apds9160_ps_led_current_map); idx++) { + int ret; + + if (apds9160_ps_led_current_map[idx][0] != val) + continue; + + ret = regmap_field_write( + data->reg_ps_current, + apds9160_ps_led_current_map[idx][1]); + if (ret) + return ret; + data->ps_current = val; + + return ret; + } + + return -EINVAL; +} + +static int apds9160_set_als_gain(struct apds9160_chip *data, int gain) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(apds9160_als_gain_map); idx++) { + int ret; + + if (gain != apds9160_als_gain_map[idx][0]) + continue; + + ret = regmap_field_write(data->reg_als_gain, + apds9160_als_gain_map[idx][1]); + if (ret) + return ret; + data->als_hwgain = gain; + + return ret; + } + + return -EINVAL; +} + +static int apds9160_set_als_scale(struct apds9160_chip *data, int val, int val2) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(apds9160_als_scale_map); idx++) { + if (apds9160_als_scale_map[idx].itime == data->als_itime && + apds9160_als_scale_map[idx].scale1 == val && + apds9160_als_scale_map[idx].scale2 == val2) { + int ret = apds9160_set_als_gain(data, + apds9160_als_scale_map[idx].gain); + if (ret) + return ret; + data->als_scale1 = val; + data->als_scale2 = val2; + + return ret; + } + } + + return -EINVAL; +} + +static int apds9160_set_als_resolution(struct apds9160_chip *data, int val) +{ + switch (val) { + case 25: + return regmap_field_write(data->reg_als_resolution, + APDS9160_CMD_LS_RESOLUTION_25MS); + case 50: + return regmap_field_write(data->reg_als_resolution, + APDS9160_CMD_LS_RESOLUTION_50MS); + case 200: + return regmap_field_write(data->reg_als_resolution, + APDS9160_CMD_LS_RESOLUTION_200MS); + default: + return regmap_field_write(data->reg_als_resolution, + APDS9160_CMD_LS_RESOLUTION_100MS); + } +} + +static int apds9160_set_als_rate(struct apds9160_chip *data, int val) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(apds9160_als_rate_map); idx++) { + if (apds9160_als_rate_map[idx][0] != val) + continue; + + return regmap_field_write(data->reg_als_rate, + apds9160_als_rate_map[idx][1]); + } + + return -EINVAL; +} + +/* + * Setting the integration time ajusts resolution, rate, scale and gain + */ +static int apds9160_set_als_int_time(struct apds9160_chip *data, int val) +{ + int ret; + int idx; + + ret = apds9160_set_als_rate(data, val); + if (ret) + return ret; + + /* Match resolution register with rate */ + ret = apds9160_set_als_resolution(data, val); + if (ret) + return ret; + + data->als_itime = val; + + /* Set the scale minimum gain */ + for (idx = 0; idx < ARRAY_SIZE(apds9160_als_scale_map); idx++) { + if (data->als_itime != apds9160_als_scale_map[idx].itime) + continue; + + return apds9160_set_als_scale(data, + apds9160_als_scale_map[idx].scale1, + apds9160_als_scale_map[idx].scale2); + } + + return -EINVAL; +} + +static int apds9160_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct apds9160_chip *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_LIGHT: + *length = ARRAY_SIZE(apds9160_als_rate_avail); + *vals = (const int *)apds9160_als_rate_avail; + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + case IIO_PROXIMITY: + *length = ARRAY_SIZE(apds9160_ps_rate_avail); + *vals = (const int *)apds9160_ps_rate_avail; + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PROXIMITY: + *length = ARRAY_SIZE(apds9160_ps_gain_avail); + *vals = (const int *)apds9160_ps_gain_avail; + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + case IIO_LIGHT: + /* The available scales changes depending on itime */ + switch (data->als_itime) { + case 25: + *length = ARRAY_SIZE(apds9160_25ms_avail) * 2; + *vals = (const int *)apds9160_25ms_avail; + *type = IIO_VAL_INT_PLUS_MICRO; + + return IIO_AVAIL_LIST; + case 50: + *length = ARRAY_SIZE(apds9160_50ms_avail) * 2; + *vals = (const int *)apds9160_50ms_avail; + *type = IIO_VAL_INT_PLUS_MICRO; + + return IIO_AVAIL_LIST; + case 100: + *length = ARRAY_SIZE(apds9160_100ms_avail) * 2; + *vals = (const int *)apds9160_100ms_avail; + *type = IIO_VAL_INT_PLUS_MICRO; + + return IIO_AVAIL_LIST; + case 200: + *length = ARRAY_SIZE(apds9160_200ms_avail) * 2; + *vals = (const int *)apds9160_200ms_avail; + *type = IIO_VAL_INT_PLUS_MICRO; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_CURRENT: + *length = ARRAY_SIZE(apds9160_ps_led_current_avail); + *vals = (const int *)apds9160_ps_led_current_avail; + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static int apds9160_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + return IIO_VAL_INT; + case IIO_CHAN_INFO_HARDWAREGAIN: + return IIO_VAL_INT; + case IIO_CHAN_INFO_RAW: + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int apds9160_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct apds9160_chip *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_PROXIMITY: { + __le16 buf; + + ret = regmap_bulk_read(data->regmap, chan->address, + &buf, 2); + if (ret) + return ret; + *val = le16_to_cpu(buf); + /* Remove overflow bits from result */ + *val = FIELD_GET(APDS9160_PS_DATA_MASK, *val); + + return IIO_VAL_INT; + } + case IIO_LIGHT: + case IIO_INTENSITY: { + u8 buf[3]; + + ret = regmap_bulk_read(data->regmap, chan->address, + &buf, 3); + if (ret) + return ret; + *val = get_unaligned_le24(buf); + + return IIO_VAL_INT; + } + case IIO_CURRENT: + *val = data->ps_current; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_HARDWAREGAIN: + switch (chan->type) { + case IIO_LIGHT: + *val = data->als_hwgain; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_PROXIMITY: + *val = data->ps_rate; + + return IIO_VAL_INT; + case IIO_LIGHT: + *val = data->als_itime; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_PROXIMITY: + *val = data->ps_cancellation_level; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PROXIMITY: + *val = data->ps_gain; + + return IIO_VAL_INT; + case IIO_LIGHT: + *val = data->als_scale1; + *val2 = data->als_scale2; + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +}; + +static int apds9160_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct apds9160_chip *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val2 != 0) + return -EINVAL; + switch (chan->type) { + case IIO_PROXIMITY: + return apds9160_set_ps_rate(data, val); + case IIO_LIGHT: + return apds9160_set_als_int_time(data, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PROXIMITY: + return apds9160_set_ps_gain(data, val); + case IIO_LIGHT: + return apds9160_set_als_scale(data, val, val2); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + if (val2 != 0) + return -EINVAL; + switch (chan->type) { + case IIO_PROXIMITY: + return apds9160_set_ps_cancellation_level(data, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + if (val2 != 0) + return -EINVAL; + switch (chan->type) { + case IIO_CURRENT: + return apds9160_set_ps_current(data, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static inline int apds9160_get_thres_reg(const struct iio_chan_spec *chan, + enum iio_event_direction dir, u8 *reg) +{ + switch (dir) { + case IIO_EV_DIR_RISING: + switch (chan->type) { + case IIO_PROXIMITY: + *reg = APDS9160_REG_PS_THRES_HI_LSB; + break; + case IIO_LIGHT: + *reg = APDS9160_REG_LS_THRES_UP_LSB; + break; + default: + return -EINVAL; + } break; + case IIO_EV_DIR_FALLING: + switch (chan->type) { + case IIO_PROXIMITY: + *reg = APDS9160_REG_PS_THRES_LO_LSB; + break; + case IIO_LIGHT: + *reg = APDS9160_REG_LS_THRES_LO_LSB; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int apds9160_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + u8 reg; + int ret; + struct apds9160_chip *data = iio_priv(indio_dev); + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + ret = apds9160_get_thres_reg(chan, dir, ®); + if (ret < 0) + return ret; + + switch (chan->type) { + case IIO_PROXIMITY: { + __le16 buf; + + ret = regmap_bulk_read(data->regmap, reg, &buf, 2); + if (ret < 0) + return ret; + *val = le16_to_cpu(buf); + return IIO_VAL_INT; + } + case IIO_LIGHT: { + u8 buf[3]; + + ret = regmap_bulk_read(data->regmap, reg, &buf, 3); + if (ret < 0) + return ret; + *val = get_unaligned_le24(buf); + return IIO_VAL_INT; + } + default: + return -EINVAL; + } +} + +static int apds9160_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + u8 reg; + int ret = 0; + struct apds9160_chip *data = iio_priv(indio_dev); + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + ret = apds9160_get_thres_reg(chan, dir, ®); + if (ret < 0) + return ret; + + switch (chan->type) { + case IIO_PROXIMITY: { + __le16 buf; + + if (val < 0 || val > APDS9160_PS_THRES_MAX) + return -EINVAL; + + buf = cpu_to_le16(val); + return regmap_bulk_write(data->regmap, reg, &buf, 2); + } + case IIO_LIGHT: { + u8 buf[3]; + + if (val < 0 || val > APDS9160_LS_THRES_MAX) + return -EINVAL; + + put_unaligned_le24(val, buf); + return regmap_bulk_write(data->regmap, reg, &buf, 3); + } + default: + return -EINVAL; + } +} + +static int apds9160_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct apds9160_chip *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_PROXIMITY: + return data->ps_int; + case IIO_LIGHT: + return data->als_int; + default: + return -EINVAL; + } +} + +static int apds9160_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct apds9160_chip *data = iio_priv(indio_dev); + int ret; + + switch (chan->type) { + case IIO_PROXIMITY: + ret = regmap_field_write(data->reg_int_ps, state); + if (ret) + return ret; + data->ps_int = state; + + return 0; + case IIO_LIGHT: + ret = regmap_field_write(data->reg_int_als, state); + if (ret) + return ret; + data->als_int = state; + + return 0; + default: + return -EINVAL; + } +} + +static irqreturn_t apds9160_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct apds9160_chip *data = iio_priv(indio_dev); + int ret, status; + + /* Reading status register clears the interrupt flag */ + ret = regmap_read(data->regmap, APDS9160_REG_SR, &status); + if (ret < 0) { + dev_err_ratelimited(&data->client->dev, + "irq status reg read failed\n"); + return IRQ_HANDLED; + } + + if ((status & APDS9160_SR_LS_INT) && + (status & APDS9160_SR_LS_NEW_DATA) && data->als_int) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + } + + if ((status & APDS9160_SR_PS_INT) && + (status & APDS9160_SR_PS_NEW_DATA) && data->ps_int) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + } + + return IRQ_HANDLED; +} + +static int apds9160_detect(struct apds9160_chip *chip) +{ + struct i2c_client *client = chip->client; + int ret; + u32 val; + + ret = regmap_read(chip->regmap, APDS9160_REG_ID, &val); + if (ret < 0) { + dev_err(&client->dev, "ID read failed\n"); + return ret; + } + + if (val != APDS9160_PART_ID_0) + dev_info(&client->dev, "Unknown part id %u\n", val); + + return 0; +} + +static void apds9160_disable(void *chip) +{ + struct apds9160_chip *data = chip; + int ret; + + ret = regmap_field_write(data->reg_enable_als, 0); + if (ret) + return; + + regmap_field_write(data->reg_enable_ps, 0); +} + +static int apds9160_chip_init(struct apds9160_chip *chip) +{ + int ret; + + /* Write default values to interrupt register */ + ret = regmap_field_write(chip->reg_int_ps, 0); + chip->ps_int = 0; + if (ret) + return ret; + + ret = regmap_field_write(chip->reg_int_als, 0); + chip->als_int = 0; + if (ret) + return ret; + + /* Write default values to control register */ + ret = regmap_field_write(chip->reg_enable_als, 1); + if (ret) + return ret; + + ret = regmap_field_write(chip->reg_enable_ps, 1); + if (ret) + return ret; + + /* Write other default values */ + ret = regmap_field_write(chip->reg_ps_resolution, + APDS9160_DEFAULT_PS_RESOLUTION_11BITS); + if (ret) + return ret; + + /* Write default values to configuration registers */ + ret = apds9160_set_ps_current(chip, APDS9160_DEFAULT_PS_CURRENT); + if (ret) + return ret; + + ret = apds9160_set_ps_rate(chip, APDS9160_DEFAULT_PS_RATE); + if (ret) + return ret; + + ret = apds9160_set_als_int_time(chip, APDS9160_DEFAULT_LS_RATE); + if (ret) + return ret; + + ret = apds9160_set_als_scale(chip, + apds9160_100ms_avail[0][0], + apds9160_100ms_avail[0][1]); + if (ret) + return ret; + + ret = apds9160_set_ps_gain(chip, APDS9160_DEFAULT_PS_GAIN); + if (ret) + return ret; + + ret = apds9160_set_ps_analog_cancellation( + chip, APDS9160_DEFAULT_PS_ANALOG_CANCELLATION); + if (ret) + return ret; + + ret = apds9160_set_ps_cancellation_level( + chip, APDS9160_DEFAULT_PS_CANCELLATION_LEVEL); + if (ret) + return ret; + + return devm_add_action_or_reset(&chip->client->dev, apds9160_disable, + chip); +} + +static int apds9160_regfield_init(struct apds9160_chip *data) +{ + struct device *dev = &data->client->dev; + struct regmap *regmap = data->regmap; + struct regmap_field *tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9160_reg_field_int_als); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_int_als = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9160_reg_field_int_ps); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_int_ps = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9160_reg_field_ls_en); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_enable_als = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9160_reg_field_ps_en); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_enable_ps = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, + apds9160_reg_field_ps_overflow); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_ps_overflow = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9160_reg_field_als_rate); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_als_rate = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9160_reg_field_als_res); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_als_resolution = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9160_reg_field_ps_rate); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_ps_rate = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9160_reg_field_als_gain); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_als_gain = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, + apds9160_reg_field_ps_current); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_ps_current = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9160_reg_field_ps_gain); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_ps_gain = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, + apds9160_reg_field_ps_resolution); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_ps_resolution = tmp; + + return 0; +} + +static const struct iio_info apds9160_info = { + .read_avail = apds9160_read_avail, + .read_raw = apds9160_read_raw, + .write_raw = apds9160_write_raw, + .write_raw_get_fmt = apds9160_write_raw_get_fmt, + .read_event_value = apds9160_read_event, + .write_event_value = apds9160_write_event, + .read_event_config = apds9160_read_event_config, + .write_event_config = apds9160_write_event_config, +}; + +static const struct iio_info apds9160_info_no_events = { + .read_avail = apds9160_read_avail, + .read_raw = apds9160_read_raw, + .write_raw = apds9160_write_raw, + .write_raw_get_fmt = apds9160_write_raw_get_fmt, +}; + +static int apds9160_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct apds9160_chip *chip; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable vdd supply\n"); + + indio_dev->name = "apds9160"; + indio_dev->modes = INDIO_DIRECT_MODE; + + chip = iio_priv(indio_dev); + chip->client = client; + chip->regmap = devm_regmap_init_i2c(client, &apds9160_regmap_config); + if (IS_ERR(chip->regmap)) + return dev_err_probe(dev, PTR_ERR(chip->regmap), + "regmap initialization failed.\n"); + + chip->client = client; + mutex_init(&chip->lock); + + ret = apds9160_detect(chip); + if (ret < 0) + return dev_err_probe(dev, ret, "apds9160 not found\n"); + + ret = apds9160_regfield_init(chip); + if (ret) + return ret; + + ret = apds9160_chip_init(chip); + if (ret) + return ret; + + ret = apds9160_ps_init_analog_cancellation(dev, chip); + if (ret) + return ret; + + if (client->irq > 0) { + indio_dev->info = &apds9160_info; + indio_dev->channels = apds9160_channels; + indio_dev->num_channels = ARRAY_SIZE(apds9160_channels); + ret = devm_request_threaded_irq(dev, client->irq, NULL, + apds9160_irq_handler, + IRQF_ONESHOT, "apds9160_event", + indio_dev); + if (ret) + return ret; + } else { + indio_dev->info = &apds9160_info_no_events; + indio_dev->channels = apds9160_channels_without_events; + indio_dev->num_channels = + ARRAY_SIZE(apds9160_channels_without_events); + } + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, + "failed iio device registration\n"); + + return ret; +} + +static const struct of_device_id apds9160_of_match[] = { + { .compatible = "brcm,apds9160" }, + { } +}; +MODULE_DEVICE_TABLE(of, apds9160_of_match); + +static const struct i2c_device_id apds9160_id[] = { + { .name = "apds9160" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, apds9160_id); + +static struct i2c_driver apds9160_driver = { + .driver = { + .name = "apds9160", + .of_match_table = apds9160_of_match, + }, + .probe = apds9160_probe, + .id_table = apds9160_id, +}; +module_i2c_driver(apds9160_driver); + +MODULE_DESCRIPTION("APDS9160 combined ALS and proximity sensor"); +MODULE_AUTHOR("Mikael Gonella-Bolduc <m.gonella.bolduc@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/apds9300.c b/drivers/iio/light/apds9300.c new file mode 100644 index 000000000..f0abbc4a8 --- /dev/null +++ b/drivers/iio/light/apds9300.c @@ -0,0 +1,514 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * apds9300.c - IIO driver for Avago APDS9300 ambient light sensor + * + * Copyright 2013 Oleksandr Kravchenko <o.v.kravchenko@globallogic.com> + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/interrupt.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> + +#define APDS9300_DRV_NAME "apds9300" + +/* Command register bits */ +#define APDS9300_CMD BIT(7) /* Select command register. Must write as 1 */ +#define APDS9300_WORD BIT(5) /* I2C write/read: if 1 word, if 0 byte */ +#define APDS9300_CLEAR BIT(6) /* Interrupt clear. Clears pending interrupt */ + +/* Register set */ +#define APDS9300_CONTROL 0x00 /* Control of basic functions */ +#define APDS9300_THRESHLOWLOW 0x02 /* Low byte of low interrupt threshold */ +#define APDS9300_THRESHHIGHLOW 0x04 /* Low byte of high interrupt threshold */ +#define APDS9300_INTERRUPT 0x06 /* Interrupt control */ +#define APDS9300_DATA0LOW 0x0c /* Low byte of ADC channel 0 */ +#define APDS9300_DATA1LOW 0x0e /* Low byte of ADC channel 1 */ + +/* Power on/off value for APDS9300_CONTROL register */ +#define APDS9300_POWER_ON 0x03 +#define APDS9300_POWER_OFF 0x00 + +/* Interrupts */ +#define APDS9300_INTR_ENABLE 0x10 +/* Interrupt Persist Function: Any value outside of threshold range */ +#define APDS9300_THRESH_INTR 0x01 + +#define APDS9300_THRESH_MAX 0xffff /* Max threshold value */ + +struct apds9300_data { + struct i2c_client *client; + struct mutex mutex; + bool power_state; + int thresh_low; + int thresh_hi; + bool intr_en; +}; + +/* Lux calculation */ + +/* Calculated values 1000 * (CH1/CH0)^1.4 for CH1/CH0 from 0 to 0.52 */ +static const u16 apds9300_lux_ratio[] = { + 0, 2, 4, 7, 11, 15, 19, 24, 29, 34, 40, 45, 51, 57, 64, 70, 77, 84, 91, + 98, 105, 112, 120, 128, 136, 144, 152, 160, 168, 177, 185, 194, 203, + 212, 221, 230, 239, 249, 258, 268, 277, 287, 297, 307, 317, 327, 337, + 347, 358, 368, 379, 390, 400, +}; + +static unsigned long apds9300_calculate_lux(u16 ch0, u16 ch1) +{ + unsigned long lux, tmp; + + /* avoid division by zero */ + if (ch0 == 0) + return 0; + + tmp = DIV_ROUND_UP(ch1 * 100, ch0); + if (tmp <= 52) { + lux = 3150 * ch0 - (unsigned long)DIV_ROUND_UP_ULL(ch0 + * apds9300_lux_ratio[tmp] * 5930ull, 1000); + } else if (tmp <= 65) { + lux = 2290 * ch0 - 2910 * ch1; + } else if (tmp <= 80) { + lux = 1570 * ch0 - 1800 * ch1; + } else if (tmp <= 130) { + lux = 338 * ch0 - 260 * ch1; + } else { + lux = 0; + } + + return lux / 100000; +} + +static int apds9300_get_adc_val(struct apds9300_data *data, int adc_number) +{ + int ret; + u8 flags = APDS9300_CMD | APDS9300_WORD; + + if (!data->power_state) + return -EBUSY; + + /* Select ADC0 or ADC1 data register */ + flags |= adc_number ? APDS9300_DATA1LOW : APDS9300_DATA0LOW; + + ret = i2c_smbus_read_word_data(data->client, flags); + if (ret < 0) + dev_err(&data->client->dev, + "failed to read ADC%d value\n", adc_number); + + return ret; +} + +static int apds9300_set_thresh_low(struct apds9300_data *data, int value) +{ + int ret; + + if (!data->power_state) + return -EBUSY; + + if (value > APDS9300_THRESH_MAX) + return -EINVAL; + + ret = i2c_smbus_write_word_data(data->client, APDS9300_THRESHLOWLOW + | APDS9300_CMD | APDS9300_WORD, value); + if (ret) { + dev_err(&data->client->dev, "failed to set thresh_low\n"); + return ret; + } + data->thresh_low = value; + + return 0; +} + +static int apds9300_set_thresh_hi(struct apds9300_data *data, int value) +{ + int ret; + + if (!data->power_state) + return -EBUSY; + + if (value > APDS9300_THRESH_MAX) + return -EINVAL; + + ret = i2c_smbus_write_word_data(data->client, APDS9300_THRESHHIGHLOW + | APDS9300_CMD | APDS9300_WORD, value); + if (ret) { + dev_err(&data->client->dev, "failed to set thresh_hi\n"); + return ret; + } + data->thresh_hi = value; + + return 0; +} + +static int apds9300_set_intr_state(struct apds9300_data *data, bool state) +{ + int ret; + u8 cmd; + + if (!data->power_state) + return -EBUSY; + + cmd = state ? APDS9300_INTR_ENABLE | APDS9300_THRESH_INTR : 0x00; + ret = i2c_smbus_write_byte_data(data->client, + APDS9300_INTERRUPT | APDS9300_CMD, cmd); + if (ret) { + dev_err(&data->client->dev, + "failed to set interrupt state %d\n", state); + return ret; + } + data->intr_en = state; + + return 0; +} + +static int apds9300_set_power_state(struct apds9300_data *data, bool state) +{ + int ret; + u8 cmd; + + cmd = state ? APDS9300_POWER_ON : APDS9300_POWER_OFF; + ret = i2c_smbus_write_byte_data(data->client, + APDS9300_CONTROL | APDS9300_CMD, cmd); + if (ret) { + dev_err(&data->client->dev, + "failed to set power state %d\n", state); + return ret; + } + data->power_state = state; + + return 0; +} + +static void apds9300_clear_intr(struct apds9300_data *data) +{ + int ret; + + ret = i2c_smbus_write_byte(data->client, APDS9300_CLEAR | APDS9300_CMD); + if (ret < 0) + dev_err(&data->client->dev, "failed to clear interrupt\n"); +} + +static int apds9300_chip_init(struct apds9300_data *data) +{ + int ret; + + /* Need to set power off to ensure that the chip is off */ + ret = apds9300_set_power_state(data, 0); + if (ret < 0) + goto err; + /* + * Probe the chip. To do so we try to power up the device and then to + * read back the 0x03 code + */ + ret = apds9300_set_power_state(data, 1); + if (ret < 0) + goto err; + ret = i2c_smbus_read_byte_data(data->client, + APDS9300_CONTROL | APDS9300_CMD); + if (ret != APDS9300_POWER_ON) { + ret = -ENODEV; + goto err; + } + /* + * Disable interrupt to ensure thai it is doesn't enable + * i.e. after device soft reset + */ + ret = apds9300_set_intr_state(data, false); + if (ret < 0) + goto err; + + return 0; + +err: + dev_err(&data->client->dev, "failed to init the chip\n"); + return ret; +} + +static int apds9300_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + int ch0, ch1, ret = -EINVAL; + struct apds9300_data *data = iio_priv(indio_dev); + + mutex_lock(&data->mutex); + switch (chan->type) { + case IIO_LIGHT: + ch0 = apds9300_get_adc_val(data, 0); + if (ch0 < 0) { + ret = ch0; + break; + } + ch1 = apds9300_get_adc_val(data, 1); + if (ch1 < 0) { + ret = ch1; + break; + } + *val = apds9300_calculate_lux(ch0, ch1); + ret = IIO_VAL_INT; + break; + case IIO_INTENSITY: + ret = apds9300_get_adc_val(data, chan->channel); + if (ret < 0) + break; + *val = ret; + ret = IIO_VAL_INT; + break; + default: + break; + } + mutex_unlock(&data->mutex); + + return ret; +} + +static int apds9300_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, + int *val, int *val2) +{ + struct apds9300_data *data = iio_priv(indio_dev); + + switch (dir) { + case IIO_EV_DIR_RISING: + *val = data->thresh_hi; + break; + case IIO_EV_DIR_FALLING: + *val = data->thresh_low; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static int apds9300_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int val, + int val2) +{ + struct apds9300_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + if (dir == IIO_EV_DIR_RISING) + ret = apds9300_set_thresh_hi(data, val); + else + ret = apds9300_set_thresh_low(data, val); + mutex_unlock(&data->mutex); + + return ret; +} + +static int apds9300_read_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct apds9300_data *data = iio_priv(indio_dev); + + return data->intr_en; +} + +static int apds9300_write_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct apds9300_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = apds9300_set_intr_state(data, state); + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct iio_info apds9300_info_no_irq = { + .read_raw = apds9300_read_raw, +}; + +static const struct iio_info apds9300_info = { + .read_raw = apds9300_read_raw, + .read_event_value = apds9300_read_thresh, + .write_event_value = apds9300_write_thresh, + .read_event_config = apds9300_read_interrupt_config, + .write_event_config = apds9300_write_interrupt_config, +}; + +static const struct iio_event_spec apds9300_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec apds9300_channels[] = { + { + .type = IIO_LIGHT, + .channel = 0, + .indexed = true, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .type = IIO_INTENSITY, + .channel = 0, + .channel2 = IIO_MOD_LIGHT_BOTH, + .indexed = true, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = apds9300_event_spec, + .num_event_specs = ARRAY_SIZE(apds9300_event_spec), + }, { + .type = IIO_INTENSITY, + .channel = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .indexed = true, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, +}; + +static irqreturn_t apds9300_interrupt_handler(int irq, void *private) +{ + struct iio_dev *dev_info = private; + struct apds9300_data *data = iio_priv(dev_info); + + iio_push_event(dev_info, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(dev_info)); + + apds9300_clear_intr(data); + + return IRQ_HANDLED; +} + +static int apds9300_probe(struct i2c_client *client) +{ + struct apds9300_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + ret = apds9300_chip_init(data); + if (ret < 0) + goto err; + + mutex_init(&data->mutex); + + indio_dev->channels = apds9300_channels; + indio_dev->num_channels = ARRAY_SIZE(apds9300_channels); + indio_dev->name = APDS9300_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (client->irq) + indio_dev->info = &apds9300_info; + else + indio_dev->info = &apds9300_info_no_irq; + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, apds9300_interrupt_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "apds9300_event", indio_dev); + if (ret) + goto err; + } + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto err; + + return 0; + +err: + /* Ensure that power off in case of error */ + apds9300_set_power_state(data, 0); + return ret; +} + +static void apds9300_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct apds9300_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + /* Ensure that power off and interrupts are disabled */ + apds9300_set_intr_state(data, false); + apds9300_set_power_state(data, false); +} + +static int apds9300_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct apds9300_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = apds9300_set_power_state(data, false); + mutex_unlock(&data->mutex); + + return ret; +} + +static int apds9300_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct apds9300_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = apds9300_set_power_state(data, true); + mutex_unlock(&data->mutex); + + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(apds9300_pm_ops, apds9300_suspend, + apds9300_resume); + +static const struct i2c_device_id apds9300_id[] = { + { .name = APDS9300_DRV_NAME }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, apds9300_id); + +static struct i2c_driver apds9300_driver = { + .driver = { + .name = APDS9300_DRV_NAME, + .pm = pm_sleep_ptr(&apds9300_pm_ops), + }, + .probe = apds9300_probe, + .remove = apds9300_remove, + .id_table = apds9300_id, +}; + +module_i2c_driver(apds9300_driver); + +MODULE_AUTHOR("Kravchenko Oleksandr <o.v.kravchenko@globallogic.com>"); +MODULE_AUTHOR("GlobalLogic inc."); +MODULE_DESCRIPTION("APDS9300 ambient light photo sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/apds9306.c b/drivers/iio/light/apds9306.c new file mode 100644 index 000000000..f681b02e4 --- /dev/null +++ b/drivers/iio/light/apds9306.c @@ -0,0 +1,1354 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * APDS-9306/APDS-9306-065 Ambient Light Sensor + * I2C Address: 0x52 + * Datasheet: https://docs.broadcom.com/doc/AV02-4755EN + * + * Copyright (C) 2024 Subhajit Ghosh <subhajit.ghosh@tweaklogic.com> + */ + +#include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <linux/iio/iio.h> +#include <linux/iio/iio-gts-helper.h> +#include <linux/iio/events.h> +#include <linux/iio/sysfs.h> + +#include <linux/unaligned.h> + +#define APDS9306_MAIN_CTRL_REG 0x00 +#define APDS9306_ALS_MEAS_RATE_REG 0x04 +#define APDS9306_ALS_GAIN_REG 0x05 +#define APDS9306_PART_ID_REG 0x06 +#define APDS9306_MAIN_STATUS_REG 0x07 +#define APDS9306_CLEAR_DATA_0_REG 0x0A +#define APDS9306_CLEAR_DATA_1_REG 0x0B +#define APDS9306_CLEAR_DATA_2_REG 0x0C +#define APDS9306_ALS_DATA_0_REG 0x0D +#define APDS9306_ALS_DATA_1_REG 0x0E +#define APDS9306_ALS_DATA_2_REG 0x0F +#define APDS9306_INT_CFG_REG 0x19 +#define APDS9306_INT_PERSISTENCE_REG 0x1A +#define APDS9306_ALS_THRES_UP_0_REG 0x21 +#define APDS9306_ALS_THRES_UP_1_REG 0x22 +#define APDS9306_ALS_THRES_UP_2_REG 0x23 +#define APDS9306_ALS_THRES_LOW_0_REG 0x24 +#define APDS9306_ALS_THRES_LOW_1_REG 0x25 +#define APDS9306_ALS_THRES_LOW_2_REG 0x26 +#define APDS9306_ALS_THRES_VAR_REG 0x27 + +#define APDS9306_ALS_INT_STAT_MASK BIT(4) +#define APDS9306_ALS_DATA_STAT_MASK BIT(3) + +#define APDS9306_ALS_THRES_VAL_MAX (BIT(20) - 1) +#define APDS9306_ALS_THRES_VAR_NUM_VALS 8 +#define APDS9306_ALS_PERSIST_NUM_VALS 16 +#define APDS9306_ALS_READ_DATA_DELAY_US (20 * USEC_PER_MSEC) +#define APDS9306_NUM_REPEAT_RATES 7 +#define APDS9306_INT_SRC_CLEAR 0 +#define APDS9306_INT_SRC_ALS 1 +#define APDS9306_SAMP_FREQ_10HZ 0 + +/** + * struct part_id_gts_multiplier - Part no. and corresponding gts multiplier + * + * GTS (Gain Time Scale) are helper functions for Light sensors which along + * with hardware gains also has gains associated with Integration times. + * + * There are two variants of the device with slightly different characteristics, + * they have same ADC count for different Lux levels as mentioned in the + * datasheet. This multiplier array is used to store the derived Lux per count + * value for the two variants to be used by the GTS helper functions. + * + * @part_id: Part ID of the device + * @max_scale_int: Multiplier for iio_init_iio_gts() + * @max_scale_nano: Multiplier for iio_init_iio_gts() + */ +struct part_id_gts_multiplier { + int part_id; + int max_scale_int; + int max_scale_nano; +}; + +/* + * As per the datasheet, at HW Gain = 3x, Integration time 100mS (32x), + * typical 2000 ADC counts are observed for 49.8 uW per sq cm (340.134 lux) + * for apds9306 and 43 uW per sq cm (293.69 lux) for apds9306-065. + * Assuming lux per count is linear across all integration time ranges. + * + * Lux = (raw + offset) * scale; offset can be any value by userspace. + * HG = Hardware Gain; ITG = Gain by changing integration time. + * Scale table by IIO GTS Helpers = (1 / HG) * (1 / ITG) * Multiplier. + * + * The Lux values provided in the datasheet are at ITG=32x and HG=3x, + * at typical 2000 count for both variants of the device. + * + * Lux per ADC count at 3x and 32x for apds9306 = 340.134 / 2000 + * Lux per ADC count at 3x and 32x for apds9306-065 = 293.69 / 2000 + * + * The Multiplier for the scale table provided to userspace: + * IIO GTS scale Multiplier for apds9306 = (340.134 / 2000) * 32 * 3 = 16.326432 + * and for apds9306-065 = (293.69 / 2000) * 32 * 3 = 14.09712 + */ +static const struct part_id_gts_multiplier apds9306_gts_mul[] = { + { + .part_id = 0xB1, + .max_scale_int = 16, + .max_scale_nano = 326432000, + }, { + .part_id = 0xB3, + .max_scale_int = 14, + .max_scale_nano = 97120000, + }, +}; + +static const int apds9306_repeat_rate_freq[APDS9306_NUM_REPEAT_RATES][2] = { + { 40, 0 }, + { 20, 0 }, + { 10, 0 }, + { 5, 0 }, + { 2, 0 }, + { 1, 0 }, + { 0, 500000 }, +}; + +static const int apds9306_repeat_rate_period[APDS9306_NUM_REPEAT_RATES] = { + 25000, 50000, 100000, 200000, 500000, 1000000, 2000000, +}; + +/** + * struct apds9306_regfields - apds9306 regmap fields definitions + * + * @sw_reset: Software reset regfield + * @en: Enable regfield + * @intg_time: Resolution regfield + * @repeat_rate: Measurement Rate regfield + * @gain: Hardware gain regfield + * @int_src: Interrupt channel regfield + * @int_thresh_var_en: Interrupt variance threshold regfield + * @int_en: Interrupt enable regfield + * @int_persist_val: Interrupt persistence regfield + * @int_thresh_var_val: Interrupt threshold variance value regfield + */ +struct apds9306_regfields { + struct regmap_field *sw_reset; + struct regmap_field *en; + struct regmap_field *intg_time; + struct regmap_field *repeat_rate; + struct regmap_field *gain; + struct regmap_field *int_src; + struct regmap_field *int_thresh_var_en; + struct regmap_field *int_en; + struct regmap_field *int_persist_val; + struct regmap_field *int_thresh_var_val; +}; + +/** + * struct apds9306_data - apds9306 private data and registers definitions + * + * @dev: Pointer to the device structure + * @gts: IIO Gain Time Scale structure + * @mutex: Lock for protecting adc reads, device settings changes where + * some calculations are required before or after setting or + * getting the raw settings values from regmap writes or reads + * respectively. + * @regmap: Regmap structure pointer + * @rf: Regmap register fields structure + * @read_data_available: Flag set by IRQ handler for ADC data available + */ +struct apds9306_data { + struct device *dev; + struct iio_gts gts; + + struct mutex mutex; + + struct regmap *regmap; + struct apds9306_regfields rf; + + int read_data_available; +}; + +/* + * Available scales with gain 1x - 18x, timings 3.125, 25, 50, 100, 200, 400 ms + * Time impacts to gain: 1x, 8x, 16x, 32x, 64x, 128x + */ +#define APDS9306_GSEL_1X 0x00 +#define APDS9306_GSEL_3X 0x01 +#define APDS9306_GSEL_6X 0x02 +#define APDS9306_GSEL_9X 0x03 +#define APDS9306_GSEL_18X 0x04 + +static const struct iio_gain_sel_pair apds9306_gains[] = { + GAIN_SCALE_GAIN(1, APDS9306_GSEL_1X), + GAIN_SCALE_GAIN(3, APDS9306_GSEL_3X), + GAIN_SCALE_GAIN(6, APDS9306_GSEL_6X), + GAIN_SCALE_GAIN(9, APDS9306_GSEL_9X), + GAIN_SCALE_GAIN(18, APDS9306_GSEL_18X), +}; + +#define APDS9306_MEAS_MODE_400MS 0x00 +#define APDS9306_MEAS_MODE_200MS 0x01 +#define APDS9306_MEAS_MODE_100MS 0x02 +#define APDS9306_MEAS_MODE_50MS 0x03 +#define APDS9306_MEAS_MODE_25MS 0x04 +#define APDS9306_MEAS_MODE_3125US 0x05 + +static const struct iio_itime_sel_mul apds9306_itimes[] = { + GAIN_SCALE_ITIME_US(400000, APDS9306_MEAS_MODE_400MS, BIT(7)), + GAIN_SCALE_ITIME_US(200000, APDS9306_MEAS_MODE_200MS, BIT(6)), + GAIN_SCALE_ITIME_US(100000, APDS9306_MEAS_MODE_100MS, BIT(5)), + GAIN_SCALE_ITIME_US(50000, APDS9306_MEAS_MODE_50MS, BIT(4)), + GAIN_SCALE_ITIME_US(25000, APDS9306_MEAS_MODE_25MS, BIT(3)), + GAIN_SCALE_ITIME_US(3125, APDS9306_MEAS_MODE_3125US, BIT(0)), +}; + +static const struct iio_event_spec apds9306_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD), + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH_ADAPTIVE, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec apds9306_channels_with_events[] = { + { + .type = IIO_LIGHT, + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), + .event_spec = apds9306_event_spec, + .num_event_specs = ARRAY_SIZE(apds9306_event_spec), + }, { + .type = IIO_INTENSITY, + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .channel2 = IIO_MOD_LIGHT_CLEAR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .modified = 1, + .event_spec = apds9306_event_spec, + .num_event_specs = ARRAY_SIZE(apds9306_event_spec), + }, +}; + +static const struct iio_chan_spec apds9306_channels_without_events[] = { + { + .type = IIO_LIGHT, + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_INTENSITY, + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .modified = 1, + .channel2 = IIO_MOD_LIGHT_CLEAR, + }, +}; + +/* INT_PERSISTENCE available */ +static IIO_CONST_ATTR(thresh_either_period_available, "[0 1 15]"); + +/* ALS_THRESH_VAR available */ +static IIO_CONST_ATTR(thresh_adaptive_either_values_available, "[0 1 7]"); + +static struct attribute *apds9306_event_attributes[] = { + &iio_const_attr_thresh_either_period_available.dev_attr.attr, + &iio_const_attr_thresh_adaptive_either_values_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group apds9306_event_attr_group = { + .attrs = apds9306_event_attributes, +}; + +static const struct regmap_range apds9306_readable_ranges[] = { + regmap_reg_range(APDS9306_MAIN_CTRL_REG, APDS9306_ALS_THRES_VAR_REG) +}; + +static const struct regmap_range apds9306_writable_ranges[] = { + regmap_reg_range(APDS9306_MAIN_CTRL_REG, APDS9306_ALS_GAIN_REG), + regmap_reg_range(APDS9306_INT_CFG_REG, APDS9306_ALS_THRES_VAR_REG) +}; + +static const struct regmap_range apds9306_volatile_ranges[] = { + regmap_reg_range(APDS9306_MAIN_STATUS_REG, APDS9306_MAIN_STATUS_REG), + regmap_reg_range(APDS9306_CLEAR_DATA_0_REG, APDS9306_ALS_DATA_2_REG) +}; + +static const struct regmap_range apds9306_precious_ranges[] = { + regmap_reg_range(APDS9306_MAIN_STATUS_REG, APDS9306_MAIN_STATUS_REG) +}; + +static const struct regmap_access_table apds9306_readable_table = { + .yes_ranges = apds9306_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9306_readable_ranges) +}; + +static const struct regmap_access_table apds9306_writable_table = { + .yes_ranges = apds9306_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9306_writable_ranges) +}; + +static const struct regmap_access_table apds9306_volatile_table = { + .yes_ranges = apds9306_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9306_volatile_ranges) +}; + +static const struct regmap_access_table apds9306_precious_table = { + .yes_ranges = apds9306_precious_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9306_precious_ranges) +}; + +static const struct regmap_config apds9306_regmap = { + .name = "apds9306_regmap", + .reg_bits = 8, + .val_bits = 8, + .rd_table = &apds9306_readable_table, + .wr_table = &apds9306_writable_table, + .volatile_table = &apds9306_volatile_table, + .precious_table = &apds9306_precious_table, + .max_register = APDS9306_ALS_THRES_VAR_REG, + .cache_type = REGCACHE_MAPLE, +}; + +static const struct reg_field apds9306_rf_sw_reset = + REG_FIELD(APDS9306_MAIN_CTRL_REG, 4, 4); + +static const struct reg_field apds9306_rf_en = + REG_FIELD(APDS9306_MAIN_CTRL_REG, 1, 1); + +static const struct reg_field apds9306_rf_intg_time = + REG_FIELD(APDS9306_ALS_MEAS_RATE_REG, 4, 6); + +static const struct reg_field apds9306_rf_repeat_rate = + REG_FIELD(APDS9306_ALS_MEAS_RATE_REG, 0, 2); + +static const struct reg_field apds9306_rf_gain = + REG_FIELD(APDS9306_ALS_GAIN_REG, 0, 2); + +static const struct reg_field apds9306_rf_int_src = + REG_FIELD(APDS9306_INT_CFG_REG, 4, 5); + +static const struct reg_field apds9306_rf_int_thresh_var_en = + REG_FIELD(APDS9306_INT_CFG_REG, 3, 3); + +static const struct reg_field apds9306_rf_int_en = + REG_FIELD(APDS9306_INT_CFG_REG, 2, 2); + +static const struct reg_field apds9306_rf_int_persist_val = + REG_FIELD(APDS9306_INT_PERSISTENCE_REG, 4, 7); + +static const struct reg_field apds9306_rf_int_thresh_var_val = + REG_FIELD(APDS9306_ALS_THRES_VAR_REG, 0, 2); + +static int apds9306_regfield_init(struct apds9306_data *data) +{ + struct device *dev = data->dev; + struct regmap *regmap = data->regmap; + struct regmap_field *tmp; + struct apds9306_regfields *rf = &data->rf; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_sw_reset); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->sw_reset = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_en); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->en = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_intg_time); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->intg_time = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_repeat_rate); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->repeat_rate = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_gain); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->gain = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_src); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->int_src = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_thresh_var_en); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->int_thresh_var_en = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_en); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->int_en = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_persist_val); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->int_persist_val = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_thresh_var_val); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->int_thresh_var_val = tmp; + + return 0; +} + +static int apds9306_power_state(struct apds9306_data *data, bool state) +{ + struct apds9306_regfields *rf = &data->rf; + int ret; + + /* Reset not included as it causes ugly I2C bus error */ + if (state) { + ret = regmap_field_write(rf->en, 1); + if (ret) + return ret; + /* 5ms wake up time */ + fsleep(5000); + return 0; + } + + return regmap_field_write(rf->en, 0); +} + +static int apds9306_read_data(struct apds9306_data *data, int *val, int reg) +{ + struct device *dev = data->dev; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct apds9306_regfields *rf = &data->rf; + u64 ev_code; + int ret, delay, intg_time, intg_time_idx, repeat_rate_idx, int_src; + int status = 0; + u8 buff[3]; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(data->dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return PM_RUNTIME_ACQUIRE_ERR(&pm); + + ret = regmap_field_read(rf->intg_time, &intg_time_idx); + if (ret) + return ret; + + ret = regmap_field_read(rf->repeat_rate, &repeat_rate_idx); + if (ret) + return ret; + + ret = regmap_field_read(rf->int_src, &int_src); + if (ret) + return ret; + + intg_time = iio_gts_find_int_time_by_sel(&data->gts, intg_time_idx); + if (intg_time < 0) + return intg_time; + + /* Whichever is greater - integration time period or sampling period. */ + delay = max(intg_time, apds9306_repeat_rate_period[repeat_rate_idx]); + + /* + * Clear stale data flag that might have been set by the interrupt + * handler if it got data available flag set in the status reg. + */ + data->read_data_available = 0; + + /* + * If this function runs parallel with the interrupt handler, either + * this reads and clears the status registers or the interrupt handler + * does. The interrupt handler sets a flag for read data available + * in our private structure which we read here. + */ + ret = regmap_read_poll_timeout(data->regmap, APDS9306_MAIN_STATUS_REG, + status, data->read_data_available || + (status & (APDS9306_ALS_DATA_STAT_MASK | + APDS9306_ALS_INT_STAT_MASK)), + APDS9306_ALS_READ_DATA_DELAY_US, delay * 2); + if (ret) + return ret; + + /* If we reach here before the interrupt handler we push an event */ + if ((status & APDS9306_ALS_INT_STAT_MASK)) { + if (int_src == APDS9306_INT_SRC_ALS) + ev_code = IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER); + else + ev_code = IIO_MOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_MOD_LIGHT_CLEAR, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER); + + iio_push_event(indio_dev, ev_code, iio_get_time_ns(indio_dev)); + } + + ret = regmap_bulk_read(data->regmap, reg, buff, sizeof(buff)); + if (ret) { + dev_err_ratelimited(dev, "read data failed\n"); + return ret; + } + + *val = get_unaligned_le24(&buff); + + return 0; +} + +static int apds9306_intg_time_get(struct apds9306_data *data, int *val2) +{ + struct apds9306_regfields *rf = &data->rf; + int ret, intg_time_idx; + + ret = regmap_field_read(rf->intg_time, &intg_time_idx); + if (ret) + return ret; + + ret = iio_gts_find_int_time_by_sel(&data->gts, intg_time_idx); + if (ret < 0) + return ret; + + *val2 = ret; + + return 0; +} + +static int apds9306_intg_time_set(struct apds9306_data *data, int val2) +{ + struct device *dev = data->dev; + struct apds9306_regfields *rf = &data->rf; + int ret, intg_old, gain_old, gain_new, gain_new_closest, intg_time_idx; + int gain_idx; + bool ok; + + if (!iio_gts_valid_time(&data->gts, val2)) { + dev_err_ratelimited(dev, "Unsupported integration time %u\n", val2); + return -EINVAL; + } + + ret = regmap_field_read(rf->intg_time, &intg_time_idx); + if (ret) + return ret; + + ret = regmap_field_read(rf->gain, &gain_idx); + if (ret) + return ret; + + intg_old = iio_gts_find_int_time_by_sel(&data->gts, intg_time_idx); + if (intg_old < 0) + return intg_old; + + if (intg_old == val2) + return 0; + + gain_old = iio_gts_find_gain_by_sel(&data->gts, gain_idx); + if (gain_old < 0) + return gain_old; + + iio_gts_find_new_gain_by_old_gain_time(&data->gts, gain_old, intg_old, + val2, &gain_new); + + if (gain_new < 0) { + dev_err_ratelimited(dev, "Unsupported gain with time\n"); + return gain_new; + } + + gain_new_closest = iio_find_closest_gain_low(&data->gts, gain_new, &ok); + if (gain_new_closest < 0) { + gain_new_closest = iio_gts_get_min_gain(&data->gts); + if (gain_new_closest < 0) + return gain_new_closest; + } + if (!ok) + dev_dbg(dev, "Unable to find optimum gain, setting minimum"); + + ret = iio_gts_find_sel_by_int_time(&data->gts, val2); + if (ret < 0) + return ret; + + ret = regmap_field_write(rf->intg_time, ret); + if (ret) + return ret; + + ret = iio_gts_find_sel_by_gain(&data->gts, gain_new_closest); + if (ret < 0) + return ret; + + return regmap_field_write(rf->gain, ret); +} + +static int apds9306_sampling_freq_get(struct apds9306_data *data, int *val, + int *val2) +{ + struct apds9306_regfields *rf = &data->rf; + int ret, repeat_rate_idx; + + ret = regmap_field_read(rf->repeat_rate, &repeat_rate_idx); + if (ret) + return ret; + + if (repeat_rate_idx >= ARRAY_SIZE(apds9306_repeat_rate_freq)) + return -EINVAL; + + *val = apds9306_repeat_rate_freq[repeat_rate_idx][0]; + *val2 = apds9306_repeat_rate_freq[repeat_rate_idx][1]; + + return 0; +} + +static int apds9306_sampling_freq_set(struct apds9306_data *data, int val, + int val2) +{ + struct apds9306_regfields *rf = &data->rf; + int i; + + for (i = 0; i < ARRAY_SIZE(apds9306_repeat_rate_freq); i++) { + if (apds9306_repeat_rate_freq[i][0] == val && + apds9306_repeat_rate_freq[i][1] == val2) + return regmap_field_write(rf->repeat_rate, i); + } + + return -EINVAL; +} + +static int apds9306_scale_get(struct apds9306_data *data, int *val, int *val2) +{ + struct apds9306_regfields *rf = &data->rf; + int gain, intg, ret, intg_time_idx, gain_idx; + + ret = regmap_field_read(rf->gain, &gain_idx); + if (ret) + return ret; + + ret = regmap_field_read(rf->intg_time, &intg_time_idx); + if (ret) + return ret; + + gain = iio_gts_find_gain_by_sel(&data->gts, gain_idx); + if (gain < 0) + return gain; + + intg = iio_gts_find_int_time_by_sel(&data->gts, intg_time_idx); + if (intg < 0) + return intg; + + return iio_gts_get_scale(&data->gts, gain, intg, val, val2); +} + +static int apds9306_scale_set(struct apds9306_data *data, int val, int val2) +{ + struct apds9306_regfields *rf = &data->rf; + int i, ret, time_sel, gain_sel, intg_time_idx; + + ret = regmap_field_read(rf->intg_time, &intg_time_idx); + if (ret) + return ret; + + ret = iio_gts_find_gain_sel_for_scale_using_time(&data->gts, + intg_time_idx, val, val2, &gain_sel); + if (ret) { + for (i = 0; i < data->gts.num_itime; i++) { + time_sel = data->gts.itime_table[i].sel; + + if (time_sel == intg_time_idx) + continue; + + ret = iio_gts_find_gain_sel_for_scale_using_time(&data->gts, + time_sel, val, val2, &gain_sel); + if (!ret) + break; + } + if (ret) + return -EINVAL; + + ret = regmap_field_write(rf->intg_time, time_sel); + if (ret) + return ret; + } + + return regmap_field_write(rf->gain, gain_sel); +} + +static int apds9306_event_period_get(struct apds9306_data *data, int *val) +{ + struct apds9306_regfields *rf = &data->rf; + int period, ret; + + ret = regmap_field_read(rf->int_persist_val, &period); + if (ret) + return ret; + + if (!in_range(period, 0, APDS9306_ALS_PERSIST_NUM_VALS)) + return -EINVAL; + + *val = period; + + return ret; +} + +static int apds9306_event_period_set(struct apds9306_data *data, int val) +{ + struct apds9306_regfields *rf = &data->rf; + + if (!in_range(val, 0, APDS9306_ALS_PERSIST_NUM_VALS)) + return -EINVAL; + + return regmap_field_write(rf->int_persist_val, val); +} + +static int apds9306_get_thresh_reg(int dir) +{ + if (dir == IIO_EV_DIR_RISING) + return APDS9306_ALS_THRES_UP_0_REG; + else if (dir == IIO_EV_DIR_FALLING) + return APDS9306_ALS_THRES_LOW_0_REG; + else + return -EINVAL; +} + +static int apds9306_event_thresh_get(struct apds9306_data *data, int dir, + int *val) +{ + int reg, ret; + u8 buff[3]; + + reg = apds9306_get_thresh_reg(dir); + if (reg < 0) + return reg; + + ret = regmap_bulk_read(data->regmap, reg, buff, sizeof(buff)); + if (ret) + return ret; + + *val = get_unaligned_le24(&buff); + + return 0; +} + +static int apds9306_event_thresh_set(struct apds9306_data *data, int dir, + int val) +{ + int reg; + u8 buff[3]; + + reg = apds9306_get_thresh_reg(dir); + if (reg < 0) + return reg; + + if (!in_range(val, 0, APDS9306_ALS_THRES_VAL_MAX)) + return -EINVAL; + + put_unaligned_le24(val, buff); + + return regmap_bulk_write(data->regmap, reg, buff, sizeof(buff)); +} + +static int apds9306_event_thresh_adaptive_get(struct apds9306_data *data, int *val) +{ + struct apds9306_regfields *rf = &data->rf; + int thr_adpt, ret; + + ret = regmap_field_read(rf->int_thresh_var_val, &thr_adpt); + if (ret) + return ret; + + if (!in_range(thr_adpt, 0, APDS9306_ALS_THRES_VAR_NUM_VALS)) + return -EINVAL; + + *val = thr_adpt; + + return ret; +} + +static int apds9306_event_thresh_adaptive_set(struct apds9306_data *data, int val) +{ + struct apds9306_regfields *rf = &data->rf; + + if (!in_range(val, 0, APDS9306_ALS_THRES_VAR_NUM_VALS)) + return -EINVAL; + + return regmap_field_write(rf->int_thresh_var_val, val); +} + +static int apds9306_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct apds9306_data *data = iio_priv(indio_dev); + int ret, reg; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->channel2 == IIO_MOD_LIGHT_CLEAR) + reg = APDS9306_CLEAR_DATA_0_REG; + else + reg = APDS9306_ALS_DATA_0_REG; + /* + * Changing device parameters during adc operation, resets + * the ADC which has to avoided. + */ + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = apds9306_read_data(data, val, reg); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + ret = apds9306_intg_time_get(data, val2); + if (ret) + return ret; + *val = 0; + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = apds9306_sampling_freq_get(data, val, val2); + if (ret) + return ret; + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + ret = apds9306_scale_get(data, val, val2); + if (ret) + return ret; + + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +}; + +static int apds9306_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct apds9306_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return iio_gts_avail_times(&data->gts, vals, type, length); + case IIO_CHAN_INFO_SCALE: + return iio_gts_all_avail_scales(&data->gts, vals, type, length); + case IIO_CHAN_INFO_SAMP_FREQ: + *length = ARRAY_SIZE(apds9306_repeat_rate_freq) * 2; + *vals = (const int *)apds9306_repeat_rate_freq; + *type = IIO_VAL_INT_PLUS_MICRO; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int apds9306_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_INT_TIME: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int apds9306_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct apds9306_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->mutex); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val) + return -EINVAL; + return apds9306_intg_time_set(data, val2); + case IIO_CHAN_INFO_SCALE: + return apds9306_scale_set(data, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return apds9306_sampling_freq_set(data, val, val2); + default: + return -EINVAL; + } +} + +static irqreturn_t apds9306_irq_handler(int irq, void *priv) +{ + struct iio_dev *indio_dev = priv; + struct apds9306_data *data = iio_priv(indio_dev); + struct apds9306_regfields *rf = &data->rf; + u64 ev_code; + int ret, status, int_src; + + /* + * The interrupt line is released and the interrupt flag is + * cleared as a result of reading the status register. All the + * status flags are cleared as a result of this read. + */ + ret = regmap_read(data->regmap, APDS9306_MAIN_STATUS_REG, &status); + if (ret < 0) { + dev_err_ratelimited(data->dev, "status reg read failed\n"); + return IRQ_HANDLED; + } + + ret = regmap_field_read(rf->int_src, &int_src); + if (ret) + return ret; + + if ((status & APDS9306_ALS_INT_STAT_MASK)) { + if (int_src == APDS9306_INT_SRC_ALS) + ev_code = IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER); + else + ev_code = IIO_MOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_MOD_LIGHT_CLEAR, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER); + + iio_push_event(indio_dev, ev_code, iio_get_time_ns(indio_dev)); + } + + /* + * If a one-shot read through sysfs is underway at the same time + * as this interrupt handler is executing and a read data available + * flag was set, this flag is set to inform read_poll_timeout() + * to exit. + */ + if ((status & APDS9306_ALS_DATA_STAT_MASK)) + data->read_data_available = 1; + + return IRQ_HANDLED; +} + +static int apds9306_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct apds9306_data *data = iio_priv(indio_dev); + int ret; + + switch (type) { + case IIO_EV_TYPE_THRESH: + if (dir == IIO_EV_DIR_EITHER && info == IIO_EV_INFO_PERIOD) + ret = apds9306_event_period_get(data, val); + else + ret = apds9306_event_thresh_get(data, dir, val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_EV_TYPE_THRESH_ADAPTIVE: + ret = apds9306_event_thresh_adaptive_get(data, val); + if (ret) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int apds9306_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct apds9306_data *data = iio_priv(indio_dev); + + switch (type) { + case IIO_EV_TYPE_THRESH: + if (dir == IIO_EV_DIR_EITHER && info == IIO_EV_INFO_PERIOD) + return apds9306_event_period_set(data, val); + + return apds9306_event_thresh_set(data, dir, val); + case IIO_EV_TYPE_THRESH_ADAPTIVE: + return apds9306_event_thresh_adaptive_set(data, val); + default: + return -EINVAL; + } +} + +static int apds9306_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct apds9306_data *data = iio_priv(indio_dev); + struct apds9306_regfields *rf = &data->rf; + int int_en, int_src, ret; + + switch (type) { + case IIO_EV_TYPE_THRESH: { + guard(mutex)(&data->mutex); + + ret = regmap_field_read(rf->int_src, &int_src); + if (ret) + return ret; + + ret = regmap_field_read(rf->int_en, &int_en); + if (ret) + return ret; + + if (chan->type == IIO_LIGHT) + return int_en && (int_src == APDS9306_INT_SRC_ALS); + + if (chan->type == IIO_INTENSITY) + return int_en && (int_src == APDS9306_INT_SRC_CLEAR); + + return -EINVAL; + } + case IIO_EV_TYPE_THRESH_ADAPTIVE: + ret = regmap_field_read(rf->int_thresh_var_en, &int_en); + if (ret) + return ret; + + return int_en; + default: + return -EINVAL; + } +} + +static int apds9306_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct apds9306_data *data = iio_priv(indio_dev); + struct apds9306_regfields *rf = &data->rf; + int ret, enabled; + + switch (type) { + case IIO_EV_TYPE_THRESH: { + guard(mutex)(&data->mutex); + + ret = regmap_field_read(rf->int_en, &enabled); + if (ret) + return ret; + + /* + * If interrupt is enabled, the channel is set before enabling + * the interrupt. In case of disable, no need to switch + * channels. In case of different channel is selected while + * interrupt in on, just change the channel. + */ + if (state) { + if (chan->type == IIO_LIGHT) + ret = regmap_field_write(rf->int_src, 1); + else if (chan->type == IIO_INTENSITY) + ret = regmap_field_write(rf->int_src, 0); + else + return -EINVAL; + + if (ret) + return ret; + + if (enabled) + return 0; + + ret = regmap_field_write(rf->int_en, 1); + if (ret) + return ret; + + return pm_runtime_resume_and_get(data->dev); + } else { + if (!enabled) + return 0; + + ret = regmap_field_write(rf->int_en, 0); + if (ret) + return ret; + + pm_runtime_put_autosuspend(data->dev); + + return 0; + } + } + case IIO_EV_TYPE_THRESH_ADAPTIVE: + return regmap_field_write(rf->int_thresh_var_en, state); + default: + return -EINVAL; + } +} + +static const struct iio_info apds9306_info_no_events = { + .read_avail = apds9306_read_avail, + .read_raw = apds9306_read_raw, + .write_raw = apds9306_write_raw, + .write_raw_get_fmt = apds9306_write_raw_get_fmt, +}; + +static const struct iio_info apds9306_info = { + .read_avail = apds9306_read_avail, + .read_raw = apds9306_read_raw, + .write_raw = apds9306_write_raw, + .write_raw_get_fmt = apds9306_write_raw_get_fmt, + .read_event_value = apds9306_read_event, + .write_event_value = apds9306_write_event, + .read_event_config = apds9306_read_event_config, + .write_event_config = apds9306_write_event_config, + .event_attrs = &apds9306_event_attr_group, +}; + +static int apds9306_init_iio_gts(struct apds9306_data *data) +{ + int i, ret, part_id; + + ret = regmap_read(data->regmap, APDS9306_PART_ID_REG, &part_id); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(apds9306_gts_mul); i++) + if (part_id == apds9306_gts_mul[i].part_id) + break; + + if (i == ARRAY_SIZE(apds9306_gts_mul)) + return -ENOENT; + + return devm_iio_init_iio_gts(data->dev, + apds9306_gts_mul[i].max_scale_int, + apds9306_gts_mul[i].max_scale_nano, + apds9306_gains, ARRAY_SIZE(apds9306_gains), + apds9306_itimes, ARRAY_SIZE(apds9306_itimes), + &data->gts); +} + +static void apds9306_powerdown(void *ptr) +{ + struct apds9306_data *data = ptr; + struct apds9306_regfields *rf = &data->rf; + int ret; + + ret = regmap_field_write(rf->int_thresh_var_en, 0); + if (ret) + return; + + ret = regmap_field_write(rf->int_en, 0); + if (ret) + return; + + apds9306_power_state(data, false); +} + +static int apds9306_device_init(struct apds9306_data *data) +{ + struct apds9306_regfields *rf = &data->rf; + int ret; + + ret = apds9306_init_iio_gts(data); + if (ret) + return ret; + + ret = regmap_field_write(rf->intg_time, APDS9306_MEAS_MODE_100MS); + if (ret) + return ret; + + ret = regmap_field_write(rf->repeat_rate, APDS9306_SAMP_FREQ_10HZ); + if (ret) + return ret; + + ret = regmap_field_write(rf->gain, APDS9306_GSEL_3X); + if (ret) + return ret; + + ret = regmap_field_write(rf->int_src, APDS9306_INT_SRC_ALS); + if (ret) + return ret; + + ret = regmap_field_write(rf->int_en, 0); + if (ret) + return ret; + + return regmap_field_write(rf->int_thresh_var_en, 0); +} + +static int apds9306_pm_init(struct apds9306_data *data) +{ + struct device *dev = data->dev; + int ret; + + ret = apds9306_power_state(data, true); + if (ret) + return ret; + + ret = pm_runtime_set_active(dev); + if (ret) + return ret; + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(dev, 5000); + pm_runtime_use_autosuspend(dev); + pm_runtime_get(dev); + + return 0; +} + +static int apds9306_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct apds9306_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + + mutex_init(&data->mutex); + + data->regmap = devm_regmap_init_i2c(client, &apds9306_regmap); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "regmap initialization failed\n"); + + data->dev = dev; + i2c_set_clientdata(client, indio_dev); + + ret = apds9306_regfield_init(data); + if (ret) + return dev_err_probe(dev, ret, "regfield initialization failed\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulator\n"); + + indio_dev->name = "apds9306"; + indio_dev->modes = INDIO_DIRECT_MODE; + if (client->irq) { + indio_dev->info = &apds9306_info; + indio_dev->channels = apds9306_channels_with_events; + indio_dev->num_channels = ARRAY_SIZE(apds9306_channels_with_events); + ret = devm_request_threaded_irq(dev, client->irq, NULL, + apds9306_irq_handler, IRQF_ONESHOT, + "apds9306_event", indio_dev); + if (ret) + return ret; + } else { + indio_dev->info = &apds9306_info_no_events; + indio_dev->channels = apds9306_channels_without_events; + indio_dev->num_channels = + ARRAY_SIZE(apds9306_channels_without_events); + } + + ret = apds9306_pm_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed pm init\n"); + + ret = apds9306_device_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to init device\n"); + + ret = devm_add_action_or_reset(dev, apds9306_powerdown, data); + if (ret) + return ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "failed iio device registration\n"); + + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static int apds9306_runtime_suspend(struct device *dev) +{ + struct apds9306_data *data = iio_priv(dev_get_drvdata(dev)); + + return apds9306_power_state(data, false); +} + +static int apds9306_runtime_resume(struct device *dev) +{ + struct apds9306_data *data = iio_priv(dev_get_drvdata(dev)); + + return apds9306_power_state(data, true); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(apds9306_pm_ops, + apds9306_runtime_suspend, + apds9306_runtime_resume, + NULL); + +static const struct of_device_id apds9306_of_match[] = { + { .compatible = "avago,apds9306" }, + { } +}; +MODULE_DEVICE_TABLE(of, apds9306_of_match); + +static struct i2c_driver apds9306_driver = { + .driver = { + .name = "apds9306", + .pm = pm_ptr(&apds9306_pm_ops), + .of_match_table = apds9306_of_match, + }, + .probe = apds9306_probe, +}; +module_i2c_driver(apds9306_driver); + +MODULE_AUTHOR("Subhajit Ghosh <subhajit.ghosh@tweaklogic.com>"); +MODULE_DESCRIPTION("APDS9306 Ambient Light Sensor driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_GTS_HELPER"); diff --git a/drivers/iio/light/apds9960.c b/drivers/iio/light/apds9960.c new file mode 100644 index 000000000..afe520de6 --- /dev/null +++ b/drivers/iio/light/apds9960.c @@ -0,0 +1,1187 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * apds9960.c - Support for Avago APDS9960 gesture/RGB/ALS/proximity sensor + * + * Copyright (C) 2015, 2018 + * Author: Matt Ranostay <matt.ranostay@konsulko.com> + * + * TODO: gesture + proximity calib offsets + */ + +#include <linux/acpi.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/irq.h> +#include <linux/i2c.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/events.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/iio/sysfs.h> + +#define APDS9960_DRV_NAME "apds9960" + +#define APDS9960_REG_RAM_START 0x00 +#define APDS9960_REG_RAM_END 0x7f + +#define APDS9960_REG_ENABLE 0x80 +#define APDS9960_REG_ATIME 0x81 +#define APDS9960_REG_WTIME 0x83 + +#define APDS9960_REG_AILTL 0x84 +#define APDS9960_REG_AILTH 0x85 +#define APDS9960_REG_AIHTL 0x86 +#define APDS9960_REG_AIHTH 0x87 + +#define APDS9960_REG_PILT 0x89 +#define APDS9960_REG_PIHT 0x8b +#define APDS9960_REG_PERS 0x8c + +#define APDS9960_REG_CONFIG_1 0x8d +#define APDS9960_REG_PPULSE 0x8e + +#define APDS9960_REG_CONTROL 0x8f +#define APDS9960_REG_CONTROL_AGAIN_MASK 0x03 +#define APDS9960_REG_CONTROL_PGAIN_MASK 0x0c +#define APDS9960_REG_CONTROL_AGAIN_MASK_SHIFT 0 +#define APDS9960_REG_CONTROL_PGAIN_MASK_SHIFT 2 + +#define APDS9960_REG_CONFIG_2 0x90 +#define APDS9960_REG_ID 0x92 + +#define APDS9960_REG_STATUS 0x93 +#define APDS9960_REG_STATUS_PS_INT BIT(5) +#define APDS9960_REG_STATUS_ALS_INT BIT(4) +#define APDS9960_REG_STATUS_GINT BIT(2) + +#define APDS9960_REG_PDATA 0x9c +#define APDS9960_REG_POFFSET_UR 0x9d +#define APDS9960_REG_POFFSET_DL 0x9e +#define APDS9960_REG_CONFIG_3 0x9f + +#define APDS9960_REG_GPENTH 0xa0 +#define APDS9960_REG_GEXTH 0xa1 + +#define APDS9960_REG_GCONF_1 0xa2 +#define APDS9960_REG_GCONF_1_GFIFO_THRES_MASK 0xc0 +#define APDS9960_REG_GCONF_1_GFIFO_THRES_MASK_SHIFT 6 + +#define APDS9960_REG_GCONF_2 0xa3 +#define APDS9960_REG_GCONF_2_GGAIN_MASK 0x60 +#define APDS9960_REG_GCONF_2_GGAIN_MASK_SHIFT 5 + +#define APDS9960_REG_GOFFSET_U 0xa4 +#define APDS9960_REG_GOFFSET_D 0xa5 +#define APDS9960_REG_GPULSE 0xa6 +#define APDS9960_REG_GOFFSET_L 0xa7 +#define APDS9960_REG_GOFFSET_R 0xa9 +#define APDS9960_REG_GCONF_3 0xaa + +#define APDS9960_REG_GCONF_4 0xab +#define APDS9960_REG_GFLVL 0xae +#define APDS9960_REG_GSTATUS 0xaf + +#define APDS9960_REG_IFORCE 0xe4 +#define APDS9960_REG_PICLEAR 0xe5 +#define APDS9960_REG_CICLEAR 0xe6 +#define APDS9960_REG_AICLEAR 0xe7 + +#define APDS9960_DEFAULT_PERS 0x33 +#define APDS9960_DEFAULT_GPENTH 0x50 +#define APDS9960_DEFAULT_GEXTH 0x40 + +#define APDS9960_MAX_PXS_THRES_VAL 255 +#define APDS9960_MAX_ALS_THRES_VAL 0xffff +#define APDS9960_MAX_INT_TIME_IN_US 1000000 + +enum apds9960_als_channel_idx { + IDX_ALS_CLEAR, IDX_ALS_RED, IDX_ALS_GREEN, IDX_ALS_BLUE, +}; + +#define APDS9960_REG_ALS_BASE 0x94 +#define APDS9960_REG_ALS_CHANNEL(_colour) \ + (APDS9960_REG_ALS_BASE + (IDX_ALS_##_colour * 2)) + +enum apds9960_gesture_channel_idx { + IDX_DIR_UP, IDX_DIR_DOWN, IDX_DIR_LEFT, IDX_DIR_RIGHT, +}; + +#define APDS9960_REG_GFIFO_BASE 0xfc +#define APDS9960_REG_GFIFO_DIR(_dir) \ + (APDS9960_REG_GFIFO_BASE + IDX_DIR_##_dir) + +struct apds9960_data { + struct i2c_client *client; + struct iio_dev *indio_dev; + struct mutex lock; + + /* regmap fields */ + struct regmap *regmap; + struct regmap_field *reg_int_als; + struct regmap_field *reg_int_ges; + struct regmap_field *reg_int_pxs; + + struct regmap_field *reg_enable_als; + struct regmap_field *reg_enable_ges; + struct regmap_field *reg_enable_pxs; + + /* state */ + bool als_int; + bool pxs_int; + int gesture_mode_running; + + /* gain values */ + int als_gain; + int pxs_gain; + + /* integration time value in us */ + int als_adc_int_us; + + /* gesture buffer */ + u8 buffer[4]; /* 4 8-bit channels */ + + /* calibration value buffer */ + int calibbias[5]; +}; + +enum { + APDS9960_CHAN_PROXIMITY, + APDS9960_CHAN_GESTURE_UP, + APDS9960_CHAN_GESTURE_DOWN, + APDS9960_CHAN_GESTURE_LEFT, + APDS9960_CHAN_GESTURE_RIGHT, +}; + +static const unsigned int apds9960_offset_regs[][2] = { + [APDS9960_CHAN_PROXIMITY] = {APDS9960_REG_POFFSET_UR, APDS9960_REG_POFFSET_DL}, + [APDS9960_CHAN_GESTURE_UP] = {APDS9960_REG_GOFFSET_U, 0}, + [APDS9960_CHAN_GESTURE_DOWN] = {APDS9960_REG_GOFFSET_D, 0}, + [APDS9960_CHAN_GESTURE_LEFT] = {APDS9960_REG_GOFFSET_L, 0}, + [APDS9960_CHAN_GESTURE_RIGHT] = {APDS9960_REG_GOFFSET_R, 0}, +}; + +static const struct reg_default apds9960_reg_defaults[] = { + /* Default ALS integration time = 2.48ms */ + { APDS9960_REG_ATIME, 0xff }, +}; + +static const struct regmap_range apds9960_volatile_ranges[] = { + regmap_reg_range(APDS9960_REG_STATUS, + APDS9960_REG_PDATA), + regmap_reg_range(APDS9960_REG_GFLVL, + APDS9960_REG_GSTATUS), + regmap_reg_range(APDS9960_REG_GFIFO_DIR(UP), + APDS9960_REG_GFIFO_DIR(RIGHT)), + regmap_reg_range(APDS9960_REG_IFORCE, + APDS9960_REG_AICLEAR), +}; + +static const struct regmap_access_table apds9960_volatile_table = { + .yes_ranges = apds9960_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9960_volatile_ranges), +}; + +static const struct regmap_range apds9960_precious_ranges[] = { + regmap_reg_range(APDS9960_REG_RAM_START, APDS9960_REG_RAM_END), +}; + +static const struct regmap_access_table apds9960_precious_table = { + .yes_ranges = apds9960_precious_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9960_precious_ranges), +}; + +static const struct regmap_range apds9960_readable_ranges[] = { + regmap_reg_range(APDS9960_REG_ENABLE, + APDS9960_REG_GSTATUS), + regmap_reg_range(APDS9960_REG_GFIFO_DIR(UP), + APDS9960_REG_GFIFO_DIR(RIGHT)), +}; + +static const struct regmap_access_table apds9960_readable_table = { + .yes_ranges = apds9960_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9960_readable_ranges), +}; + +static const struct regmap_range apds9960_writeable_ranges[] = { + regmap_reg_range(APDS9960_REG_ENABLE, APDS9960_REG_CONFIG_2), + regmap_reg_range(APDS9960_REG_POFFSET_UR, APDS9960_REG_GCONF_4), + regmap_reg_range(APDS9960_REG_IFORCE, APDS9960_REG_AICLEAR), +}; + +static const struct regmap_access_table apds9960_writeable_table = { + .yes_ranges = apds9960_writeable_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9960_writeable_ranges), +}; + +static const struct regmap_config apds9960_regmap_config = { + .name = "apds9960_regmap", + .reg_bits = 8, + .val_bits = 8, + .use_single_read = true, + .use_single_write = true, + + .volatile_table = &apds9960_volatile_table, + .precious_table = &apds9960_precious_table, + .rd_table = &apds9960_readable_table, + .wr_table = &apds9960_writeable_table, + + .reg_defaults = apds9960_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(apds9960_reg_defaults), + .max_register = APDS9960_REG_GFIFO_DIR(RIGHT), + .cache_type = REGCACHE_MAPLE, +}; + +static const struct iio_event_spec apds9960_pxs_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_event_spec apds9960_als_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +#define APDS9960_GESTURE_CHANNEL(_dir, _si) { \ + .type = IIO_PROXIMITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .channel = _si + 1, \ + .scan_index = _si, \ + .indexed = 1, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 8, \ + .storagebits = 8, \ + }, \ +} + +#define APDS9960_INTENSITY_CHANNEL(_colour) { \ + .type = IIO_INTENSITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .channel2 = IIO_MOD_LIGHT_##_colour, \ + .address = APDS9960_REG_ALS_CHANNEL(_colour), \ + .modified = 1, \ + .scan_index = -1, \ +} + +static const unsigned long apds9960_scan_masks[] = {0xf, 0}; + +static const struct iio_chan_spec apds9960_channels[] = { + { + .type = IIO_PROXIMITY, + .address = APDS9960_REG_PDATA, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .channel = 0, + .indexed = 0, + .scan_index = -1, + + .event_spec = apds9960_pxs_event_spec, + .num_event_specs = ARRAY_SIZE(apds9960_pxs_event_spec), + }, + /* Gesture Sensor */ + APDS9960_GESTURE_CHANNEL(UP, 0), + APDS9960_GESTURE_CHANNEL(DOWN, 1), + APDS9960_GESTURE_CHANNEL(LEFT, 2), + APDS9960_GESTURE_CHANNEL(RIGHT, 3), + /* ALS */ + { + .type = IIO_INTENSITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + .channel2 = IIO_MOD_LIGHT_CLEAR, + .address = APDS9960_REG_ALS_CHANNEL(CLEAR), + .modified = 1, + .scan_index = -1, + + .event_spec = apds9960_als_event_spec, + .num_event_specs = ARRAY_SIZE(apds9960_als_event_spec), + }, + /* RGB Sensor */ + APDS9960_INTENSITY_CHANNEL(RED), + APDS9960_INTENSITY_CHANNEL(GREEN), + APDS9960_INTENSITY_CHANNEL(BLUE), +}; + +static int apds9960_set_calibbias(struct apds9960_data *data, + struct iio_chan_spec const *chan, int calibbias) +{ + int ret, i; + + if (calibbias < S8_MIN || calibbias > S8_MAX) + return -EINVAL; + + guard(mutex)(&data->lock); + for (i = 0; i < 2; i++) { + if (apds9960_offset_regs[chan->channel][i] == 0) + break; + + ret = regmap_write(data->regmap, apds9960_offset_regs[chan->channel][i], calibbias); + if (ret < 0) + return ret; + } + data->calibbias[chan->channel] = calibbias; + + return 0; +} + +/* integration time in us */ +static const int apds9960_int_time[][2] = { + { 28000, 246}, + {100000, 219}, + {200000, 182}, + {700000, 0} +}; + +/* gain mapping */ +static const int apds9960_pxs_gain_map[] = {1, 2, 4, 8}; +static const int apds9960_als_gain_map[] = {1, 4, 16, 64}; + +static IIO_CONST_ATTR(proximity_scale_available, "1 2 4 8"); +static IIO_CONST_ATTR(intensity_scale_available, "1 4 16 64"); +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.028 0.1 0.2 0.7"); + +static struct attribute *apds9960_attributes[] = { + &iio_const_attr_proximity_scale_available.dev_attr.attr, + &iio_const_attr_intensity_scale_available.dev_attr.attr, + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group apds9960_attribute_group = { + .attrs = apds9960_attributes, +}; + +static const struct reg_field apds9960_reg_field_int_als = + REG_FIELD(APDS9960_REG_ENABLE, 4, 4); + +static const struct reg_field apds9960_reg_field_int_ges = + REG_FIELD(APDS9960_REG_GCONF_4, 1, 1); + +static const struct reg_field apds9960_reg_field_int_pxs = + REG_FIELD(APDS9960_REG_ENABLE, 5, 5); + +static const struct reg_field apds9960_reg_field_enable_als = + REG_FIELD(APDS9960_REG_ENABLE, 1, 1); + +static const struct reg_field apds9960_reg_field_enable_ges = + REG_FIELD(APDS9960_REG_ENABLE, 6, 6); + +static const struct reg_field apds9960_reg_field_enable_pxs = + REG_FIELD(APDS9960_REG_ENABLE, 2, 2); + +static int apds9960_set_it_time(struct apds9960_data *data, int val2) +{ + int ret = -EINVAL; + int idx; + + for (idx = 0; idx < ARRAY_SIZE(apds9960_int_time); idx++) { + if (apds9960_int_time[idx][0] == val2) { + mutex_lock(&data->lock); + ret = regmap_write(data->regmap, APDS9960_REG_ATIME, + apds9960_int_time[idx][1]); + if (!ret) + data->als_adc_int_us = val2; + mutex_unlock(&data->lock); + break; + } + } + + return ret; +} + +static int apds9960_set_pxs_gain(struct apds9960_data *data, int val) +{ + int ret = -EINVAL; + int idx; + + for (idx = 0; idx < ARRAY_SIZE(apds9960_pxs_gain_map); idx++) { + if (apds9960_pxs_gain_map[idx] == val) { + /* pxs + gesture gains are mirrored */ + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, + APDS9960_REG_CONTROL, + APDS9960_REG_CONTROL_PGAIN_MASK, + idx << APDS9960_REG_CONTROL_PGAIN_MASK_SHIFT); + if (ret) { + mutex_unlock(&data->lock); + break; + } + + ret = regmap_update_bits(data->regmap, + APDS9960_REG_GCONF_2, + APDS9960_REG_GCONF_2_GGAIN_MASK, + idx << APDS9960_REG_GCONF_2_GGAIN_MASK_SHIFT); + if (!ret) + data->pxs_gain = idx; + mutex_unlock(&data->lock); + break; + } + } + + return ret; +} + +static int apds9960_set_als_gain(struct apds9960_data *data, int val) +{ + int ret = -EINVAL; + int idx; + + for (idx = 0; idx < ARRAY_SIZE(apds9960_als_gain_map); idx++) { + if (apds9960_als_gain_map[idx] == val) { + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, + APDS9960_REG_CONTROL, + APDS9960_REG_CONTROL_AGAIN_MASK, idx); + if (!ret) + data->als_gain = idx; + mutex_unlock(&data->lock); + break; + } + } + + return ret; +} + +#ifdef CONFIG_PM +static int apds9960_set_power_state(struct apds9960_data *data, bool on) +{ + struct device *dev = &data->client->dev; + int ret = 0; + + mutex_lock(&data->lock); + + if (on) { + int suspended; + + suspended = pm_runtime_suspended(dev); + ret = pm_runtime_get_sync(dev); + + /* Allow one integration cycle before allowing a reading */ + if (suspended) + usleep_range(data->als_adc_int_us, + APDS9960_MAX_INT_TIME_IN_US); + } else { + ret = pm_runtime_put_autosuspend(dev); + } + + mutex_unlock(&data->lock); + + return ret; +} +#else +static int apds9960_set_power_state(struct apds9960_data *data, bool on) +{ + return 0; +} +#endif + +static int apds9960_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct apds9960_data *data = iio_priv(indio_dev); + __le16 buf; + int ret = -EINVAL; + + if (data->gesture_mode_running) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + apds9960_set_power_state(data, true); + switch (chan->type) { + case IIO_PROXIMITY: + ret = regmap_read(data->regmap, chan->address, val); + if (!ret) + ret = IIO_VAL_INT; + break; + case IIO_INTENSITY: + ret = regmap_bulk_read(data->regmap, chan->address, + &buf, 2); + if (!ret) { + ret = IIO_VAL_INT; + *val = le16_to_cpu(buf); + } + break; + default: + ret = -EINVAL; + } + apds9960_set_power_state(data, false); + break; + case IIO_CHAN_INFO_INT_TIME: + /* RGB + ALS sensors only have integration time */ + mutex_lock(&data->lock); + switch (chan->type) { + case IIO_INTENSITY: + *val = 0; + *val2 = data->als_adc_int_us; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + } + mutex_unlock(&data->lock); + break; + case IIO_CHAN_INFO_SCALE: + mutex_lock(&data->lock); + switch (chan->type) { + case IIO_PROXIMITY: + *val = apds9960_pxs_gain_map[data->pxs_gain]; + ret = IIO_VAL_INT; + break; + case IIO_INTENSITY: + *val = apds9960_als_gain_map[data->als_gain]; + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + } + mutex_unlock(&data->lock); + break; + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&data->lock); + *val = data->calibbias[chan->channel]; + ret = IIO_VAL_INT; + mutex_unlock(&data->lock); + break; + } + + return ret; +}; + +static int apds9960_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct apds9960_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + /* RGB + ALS sensors only have int time */ + switch (chan->type) { + case IIO_INTENSITY: + if (val != 0) + return -EINVAL; + return apds9960_set_it_time(data, val2); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + if (val2 != 0) + return -EINVAL; + switch (chan->type) { + case IIO_PROXIMITY: + return apds9960_set_pxs_gain(data, val); + case IIO_INTENSITY: + return apds9960_set_als_gain(data, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + if (val2 != 0) + return -EINVAL; + return apds9960_set_calibbias(data, chan, val); + default: + return -EINVAL; + } + + return 0; +} + +static inline int apds9960_get_thres_reg(const struct iio_chan_spec *chan, + enum iio_event_direction dir, + u8 *reg) +{ + switch (dir) { + case IIO_EV_DIR_RISING: + switch (chan->type) { + case IIO_PROXIMITY: + *reg = APDS9960_REG_PIHT; + break; + case IIO_INTENSITY: + *reg = APDS9960_REG_AIHTL; + break; + default: + return -EINVAL; + } + break; + case IIO_EV_DIR_FALLING: + switch (chan->type) { + case IIO_PROXIMITY: + *reg = APDS9960_REG_PILT; + break; + case IIO_INTENSITY: + *reg = APDS9960_REG_AILTL; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int apds9960_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + u8 reg; + __le16 buf; + int ret = 0; + struct apds9960_data *data = iio_priv(indio_dev); + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + ret = apds9960_get_thres_reg(chan, dir, ®); + if (ret < 0) + return ret; + + if (chan->type == IIO_PROXIMITY) { + ret = regmap_read(data->regmap, reg, val); + if (ret < 0) + return ret; + } else if (chan->type == IIO_INTENSITY) { + ret = regmap_bulk_read(data->regmap, reg, &buf, 2); + if (ret < 0) + return ret; + *val = le16_to_cpu(buf); + } else + return -EINVAL; + + *val2 = 0; + + return IIO_VAL_INT; +} + +static int apds9960_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + u8 reg; + __le16 buf; + int ret = 0; + struct apds9960_data *data = iio_priv(indio_dev); + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + ret = apds9960_get_thres_reg(chan, dir, ®); + if (ret < 0) + return ret; + + if (chan->type == IIO_PROXIMITY) { + if (val < 0 || val > APDS9960_MAX_PXS_THRES_VAL) + return -EINVAL; + ret = regmap_write(data->regmap, reg, val); + if (ret < 0) + return ret; + } else if (chan->type == IIO_INTENSITY) { + if (val < 0 || val > APDS9960_MAX_ALS_THRES_VAL) + return -EINVAL; + buf = cpu_to_le16(val); + ret = regmap_bulk_write(data->regmap, reg, &buf, 2); + if (ret < 0) + return ret; + } else + return -EINVAL; + + return 0; +} + +static int apds9960_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct apds9960_data *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_PROXIMITY: + return data->pxs_int; + case IIO_INTENSITY: + return data->als_int; + default: + return -EINVAL; + } +} + +static int apds9960_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct apds9960_data *data = iio_priv(indio_dev); + int ret; + + switch (chan->type) { + case IIO_PROXIMITY: + if (data->pxs_int == state) + return -EINVAL; + + ret = regmap_field_write(data->reg_int_pxs, state); + if (ret) + return ret; + data->pxs_int = state; + apds9960_set_power_state(data, state); + break; + case IIO_INTENSITY: + if (data->als_int == state) + return -EINVAL; + + ret = regmap_field_write(data->reg_int_als, state); + if (ret) + return ret; + data->als_int = state; + apds9960_set_power_state(data, state); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct iio_info apds9960_info = { + .attrs = &apds9960_attribute_group, + .read_raw = apds9960_read_raw, + .write_raw = apds9960_write_raw, + .read_event_value = apds9960_read_event, + .write_event_value = apds9960_write_event, + .read_event_config = apds9960_read_event_config, + .write_event_config = apds9960_write_event_config, + +}; + +static inline int apds9660_fifo_is_empty(struct apds9960_data *data) +{ + int cnt; + int ret; + + ret = regmap_read(data->regmap, APDS9960_REG_GFLVL, &cnt); + if (ret) + return ret; + + return cnt; +} + +static void apds9960_read_gesture_fifo(struct apds9960_data *data) +{ + int ret, cnt = 0; + + mutex_lock(&data->lock); + data->gesture_mode_running = 1; + + while (cnt || (cnt = apds9660_fifo_is_empty(data) > 0)) { + ret = regmap_bulk_read(data->regmap, APDS9960_REG_GFIFO_BASE, + &data->buffer, 4); + + if (ret) + goto err_read; + + iio_push_to_buffers(data->indio_dev, data->buffer); + cnt--; + } + +err_read: + data->gesture_mode_running = 0; + mutex_unlock(&data->lock); +} + +static irqreturn_t apds9960_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct apds9960_data *data = iio_priv(indio_dev); + int ret, status; + + ret = regmap_read(data->regmap, APDS9960_REG_STATUS, &status); + if (ret < 0) { + dev_err(&data->client->dev, "irq status reg read failed\n"); + return IRQ_HANDLED; + } + + if ((status & APDS9960_REG_STATUS_ALS_INT) && data->als_int) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + regmap_write(data->regmap, APDS9960_REG_CICLEAR, 1); + } + + if ((status & APDS9960_REG_STATUS_PS_INT) && data->pxs_int) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + regmap_write(data->regmap, APDS9960_REG_PICLEAR, 1); + } + + if (status & APDS9960_REG_STATUS_GINT) + apds9960_read_gesture_fifo(data); + + return IRQ_HANDLED; +} + +static int apds9960_set_powermode(struct apds9960_data *data, bool state) +{ + return regmap_update_bits(data->regmap, APDS9960_REG_ENABLE, 1, state); +} + +static int apds9960_buffer_postenable(struct iio_dev *indio_dev) +{ + struct apds9960_data *data = iio_priv(indio_dev); + int ret; + + ret = regmap_field_write(data->reg_int_ges, 1); + if (ret) + return ret; + + ret = regmap_field_write(data->reg_enable_ges, 1); + if (ret) + return ret; + + pm_runtime_get_sync(&data->client->dev); + + return 0; +} + +static int apds9960_buffer_predisable(struct iio_dev *indio_dev) +{ + struct apds9960_data *data = iio_priv(indio_dev); + int ret; + + ret = regmap_field_write(data->reg_enable_ges, 0); + if (ret) + return ret; + + ret = regmap_field_write(data->reg_int_ges, 0); + if (ret) + return ret; + + pm_runtime_put_autosuspend(&data->client->dev); + + return 0; +} + +static const struct iio_buffer_setup_ops apds9960_buffer_setup_ops = { + .postenable = apds9960_buffer_postenable, + .predisable = apds9960_buffer_predisable, +}; + +static int apds9960_regfield_init(struct apds9960_data *data) +{ + struct device *dev = &data->client->dev; + struct regmap *regmap = data->regmap; + + data->reg_int_als = devm_regmap_field_alloc(dev, regmap, + apds9960_reg_field_int_als); + if (IS_ERR(data->reg_int_als)) { + dev_err(dev, "INT ALS reg field init failed\n"); + return PTR_ERR(data->reg_int_als); + } + + data->reg_int_ges = devm_regmap_field_alloc(dev, regmap, + apds9960_reg_field_int_ges); + if (IS_ERR(data->reg_int_ges)) { + dev_err(dev, "INT gesture reg field init failed\n"); + return PTR_ERR(data->reg_int_ges); + } + + data->reg_int_pxs = devm_regmap_field_alloc(dev, regmap, + apds9960_reg_field_int_pxs); + if (IS_ERR(data->reg_int_pxs)) { + dev_err(dev, "INT pxs reg field init failed\n"); + return PTR_ERR(data->reg_int_pxs); + } + + data->reg_enable_als = devm_regmap_field_alloc(dev, regmap, + apds9960_reg_field_enable_als); + if (IS_ERR(data->reg_enable_als)) { + dev_err(dev, "Enable ALS reg field init failed\n"); + return PTR_ERR(data->reg_enable_als); + } + + data->reg_enable_ges = devm_regmap_field_alloc(dev, regmap, + apds9960_reg_field_enable_ges); + if (IS_ERR(data->reg_enable_ges)) { + dev_err(dev, "Enable gesture reg field init failed\n"); + return PTR_ERR(data->reg_enable_ges); + } + + data->reg_enable_pxs = devm_regmap_field_alloc(dev, regmap, + apds9960_reg_field_enable_pxs); + if (IS_ERR(data->reg_enable_pxs)) { + dev_err(dev, "Enable PXS reg field init failed\n"); + return PTR_ERR(data->reg_enable_pxs); + } + + return 0; +} + +static int apds9960_chip_init(struct apds9960_data *data) +{ + int ret; + + /* Default IT for ALS of 28 ms */ + ret = apds9960_set_it_time(data, 28000); + if (ret) + return ret; + + /* Ensure gesture interrupt is OFF */ + ret = regmap_field_write(data->reg_int_ges, 0); + if (ret) + return ret; + + /* Disable gesture sensor, since polling is useless from user-space */ + ret = regmap_field_write(data->reg_enable_ges, 0); + if (ret) + return ret; + + /* Ensure proximity interrupt is OFF */ + ret = regmap_field_write(data->reg_int_pxs, 0); + if (ret) + return ret; + + /* Enable proximity sensor for polling */ + ret = regmap_field_write(data->reg_enable_pxs, 1); + if (ret) + return ret; + + /* Ensure ALS interrupt is OFF */ + ret = regmap_field_write(data->reg_int_als, 0); + if (ret) + return ret; + + /* Enable ALS sensor for polling */ + ret = regmap_field_write(data->reg_enable_als, 1); + if (ret) + return ret; + /* + * When enabled trigger an interrupt after 3 readings + * outside threshold for ALS + PXS + */ + ret = regmap_write(data->regmap, APDS9960_REG_PERS, + APDS9960_DEFAULT_PERS); + if (ret) + return ret; + + /* + * Wait for 4 event outside gesture threshold to prevent interrupt + * flooding. + */ + ret = regmap_update_bits(data->regmap, APDS9960_REG_GCONF_1, + APDS9960_REG_GCONF_1_GFIFO_THRES_MASK, + BIT(0) << APDS9960_REG_GCONF_1_GFIFO_THRES_MASK_SHIFT); + if (ret) + return ret; + + /* Default ENTER and EXIT thresholds for the GESTURE engine. */ + ret = regmap_write(data->regmap, APDS9960_REG_GPENTH, + APDS9960_DEFAULT_GPENTH); + if (ret) + return ret; + + ret = regmap_write(data->regmap, APDS9960_REG_GEXTH, + APDS9960_DEFAULT_GEXTH); + if (ret) + return ret; + + return apds9960_set_powermode(data, 1); +} + +static int apds9960_probe(struct i2c_client *client) +{ + struct apds9960_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->info = &apds9960_info; + indio_dev->name = APDS9960_DRV_NAME; + indio_dev->channels = apds9960_channels; + indio_dev->num_channels = ARRAY_SIZE(apds9960_channels); + indio_dev->available_scan_masks = apds9960_scan_masks; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_kfifo_buffer_setup(&client->dev, indio_dev, + &apds9960_buffer_setup_ops); + if (ret) + return ret; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + + data->regmap = devm_regmap_init_i2c(client, &apds9960_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "regmap initialization failed.\n"); + return PTR_ERR(data->regmap); + } + + data->client = client; + data->indio_dev = indio_dev; + mutex_init(&data->lock); + + ret = pm_runtime_set_active(&client->dev); + if (ret) + goto error_power_down; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, 5000); + pm_runtime_use_autosuspend(&client->dev); + + apds9960_set_power_state(data, true); + + ret = apds9960_regfield_init(data); + if (ret) + goto error_power_down; + + ret = apds9960_chip_init(data); + if (ret) + goto error_power_down; + + if (client->irq <= 0) { + dev_err(&client->dev, "no valid irq defined\n"); + ret = -EINVAL; + goto error_power_down; + } + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, apds9960_interrupt_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "apds9960_event", + indio_dev); + if (ret) + goto error_power_down; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_power_down; + + apds9960_set_power_state(data, false); + + return 0; + +error_power_down: + apds9960_set_power_state(data, false); + + return ret; +} + +static void apds9960_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct apds9960_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + apds9960_set_powermode(data, 0); +} + +#ifdef CONFIG_PM +static int apds9960_runtime_suspend(struct device *dev) +{ + struct apds9960_data *data = + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return apds9960_set_powermode(data, 0); +} + +static int apds9960_runtime_resume(struct device *dev) +{ + struct apds9960_data *data = + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return apds9960_set_powermode(data, 1); +} +#endif + +static const struct dev_pm_ops apds9960_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(apds9960_runtime_suspend, + apds9960_runtime_resume, NULL) +}; + +static const struct i2c_device_id apds9960_id[] = { + { .name = "apds9960" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, apds9960_id); + +static const struct acpi_device_id apds9960_acpi_match[] = { + { "MSHW0184" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, apds9960_acpi_match); + +static const struct of_device_id apds9960_of_match[] = { + { .compatible = "avago,apds9960" }, + { } +}; +MODULE_DEVICE_TABLE(of, apds9960_of_match); + +static struct i2c_driver apds9960_driver = { + .driver = { + .name = APDS9960_DRV_NAME, + .of_match_table = apds9960_of_match, + .pm = &apds9960_pm_ops, + .acpi_match_table = apds9960_acpi_match, + }, + .probe = apds9960_probe, + .remove = apds9960_remove, + .id_table = apds9960_id, +}; +module_i2c_driver(apds9960_driver); + +MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>"); +MODULE_DESCRIPTION("APDS9960 Gesture/RGB/ALS/Proximity sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/apds9999.c b/drivers/iio/light/apds9999.c new file mode 100644 index 000000000..43fa9992c --- /dev/null +++ b/drivers/iio/light/apds9999.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * IIO driver for Broadcom APDS9999 Lux Light Sensor + * + * Copyright (C) 2026 + * Author: Jose A. Perez de Azpillaga <azpijr@gmail.com> + * + * TODO: proximity sensor + */ + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/unaligned.h> +#include <linux/units.h> + +#define APDS9999_REG_MAIN_CTRL 0x00 +#define APDS9999_MAIN_CTRL_LS_EN BIT(1) +#define APDS9999_REG_LS_MEAS_RATE 0x04 +#define APDS9999_LS_RES_MASK GENMASK(6, 4) +#define APDS9999_LS_RATE_MASK GENMASK(2, 0) +#define APDS9999_REG_LS_GAIN 0x05 +#define APDS9999_REG_PART_ID 0x06 +#define APDS9999_REG_MAIN_STATUS 0x07 +#define APDS9999_MAIN_STATUS_LS_DATA BIT(3) +#define APDS9999_REG_LS_DATA_IR_0 0x0A +#define APDS9999_REG_LS_DATA_GREEN_0 0x0D +#define APDS9999_REG_LS_DATA_BLUE_0 0x10 +#define APDS9999_REG_LS_DATA_RED_0 0x13 + +#define APDS9999_PART_ID 0xC2 + +#define APDS9999_GAIN_1X 0 +#define APDS9999_GAIN_3X 1 +#define APDS9999_GAIN_6X 2 +#define APDS9999_GAIN_9X 3 +#define APDS9999_GAIN_18X 4 + +static const int apds9999_gains[] = { + [APDS9999_GAIN_1X] = 1, + [APDS9999_GAIN_3X] = 3, + [APDS9999_GAIN_6X] = 6, + [APDS9999_GAIN_9X] = 9, + [APDS9999_GAIN_18X] = 18, +}; + +#define APDS9999_RES_20BIT 0 +#define APDS9999_RES_19BIT 1 +#define APDS9999_RES_18BIT 2 +#define APDS9999_RES_17BIT 3 +#define APDS9999_RES_16BIT 4 +#define APDS9999_RES_13BIT 5 + +static const int apds9999_itimes_us[] = { + [APDS9999_RES_20BIT] = 400 * USEC_PER_MSEC, + [APDS9999_RES_19BIT] = 200 * USEC_PER_MSEC, + [APDS9999_RES_18BIT] = 100 * USEC_PER_MSEC, + [APDS9999_RES_17BIT] = 50 * USEC_PER_MSEC, + [APDS9999_RES_16BIT] = 25 * USEC_PER_MSEC, + [APDS9999_RES_13BIT] = 3125, +}; + +#define APDS9999_RATE_25_MS 0 +#define APDS9999_RATE_50_MS 1 +#define APDS9999_RATE_100_MS 2 +#define APDS9999_RATE_200_MS 3 +#define APDS9999_RATE_500_MS 4 +#define APDS9999_RATE_1000_MS 5 +#define APDS9999_RATE_2000_MS 6 + +struct apds9999_data { + struct i2c_client *client; + /* lock: serializes access to device registers and cached values */ + struct mutex lock; + int als_gain_idx; + int als_res; + int als_rate; +}; + +static void apds9999_standby(void *client) +{ + i2c_smbus_write_byte_data(client, APDS9999_REG_MAIN_CTRL, 0); +} + +/* + * Apply power-on defaults: 18-bit / 100 ms resolution and rate, + * 3x gain. These match the datasheet reset values. + */ +static int apds9999_init(struct apds9999_data *data) +{ + struct device *dev = &data->client->dev; + struct i2c_client *client = data->client; + u8 regval; + int ret; + + ret = devm_add_action_or_reset(dev, apds9999_standby, client); + if (ret) + return ret; + + guard(mutex)(&data->lock); + + regval = FIELD_PREP(APDS9999_LS_RES_MASK, APDS9999_RES_18BIT) | + FIELD_PREP(APDS9999_LS_RATE_MASK, APDS9999_RATE_100_MS); + ret = i2c_smbus_write_byte_data(client, APDS9999_REG_LS_MEAS_RATE, + regval); + if (ret) + return ret; + data->als_res = APDS9999_RES_18BIT; + data->als_rate = APDS9999_RATE_100_MS; + + ret = i2c_smbus_write_byte_data(client, APDS9999_REG_LS_GAIN, + APDS9999_GAIN_3X); + if (ret) + return ret; + data->als_gain_idx = APDS9999_GAIN_3X; + + return i2c_smbus_write_byte_data(client, APDS9999_REG_MAIN_CTRL, + APDS9999_MAIN_CTRL_LS_EN); +} + +static int apds9999_read_channel(struct apds9999_data *data, u8 reg, + u32 *counts) +{ + struct i2c_client *client = data->client; + u8 buf[3]; + int ret, tries; + + guard(mutex)(&data->lock); + + /* + * Poll MAIN_STATUS for new data. Timeout: ~2 integration periods + * plus margin. Each try sleeps 20 ms. + */ + tries = max(2, (apds9999_itimes_us[data->als_res] * 2) / 20000); + + while (tries--) { + ret = i2c_smbus_read_byte_data(client, + APDS9999_REG_MAIN_STATUS); + if (ret < 0) + return ret; + if (ret & APDS9999_MAIN_STATUS_LS_DATA) + break; + fsleep(20000); + } + + if (tries < 0) + return -ETIMEDOUT; + + ret = i2c_smbus_read_i2c_block_data(client, reg, sizeof(buf), buf); + if (ret < 0) + return ret; + if (ret != sizeof(buf)) + return -EIO; + + *counts = get_unaligned_le24(buf) & GENMASK(19, 0); + return 0; +} + +static int apds9999_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct apds9999_data *data = iio_priv(indio_dev); + int gain, itime_us; + u64 scale_nano; + u32 counts; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = apds9999_read_channel(data, chan->address, &counts); + if (ret) + return ret; + *val = (int)counts; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: { + u32 remainder; + + /* + * Scale (lux per count) = 54 / (gain * integration_time_ms) + * + * The constant 54 is derived from the datasheet table: + * at gain = 3x, itime = 100 ms -> 0.180 lux/count + * -> C = 0.180 * 3 * 100 = 54 + * + * Expressed as IIO_VAL_INT_PLUS_NANO. + */ + gain = apds9999_gains[data->als_gain_idx]; + itime_us = apds9999_itimes_us[data->als_res]; + + /* scale_nano = 54 * 1e12 / (gain * itime_us) nano-lux/count */ + scale_nano = div_u64(54ULL * NSEC_PER_SEC * USEC_PER_MSEC, (u32)(gain * itime_us)); + *val = (int)div_u64_rem(scale_nano, NSEC_PER_SEC, &remainder); + *val2 = (int)remainder; + return IIO_VAL_INT_PLUS_NANO; + } + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = apds9999_itimes_us[data->als_res]; + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + } +} + +static const struct iio_info apds9999_info = { + .read_raw = apds9999_read_raw, +}; + +/* + * The green channel uses optical coating to approximate the human eye + * spectral response. IIO_INTENSITY channels provide raw ADC data for + * red, green, blue, and IR so userspace can compute weighted lux. + */ +static const struct iio_chan_spec apds9999_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .address = APDS9999_REG_LS_DATA_GREEN_0, + }, + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_RED, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .address = APDS9999_REG_LS_DATA_RED_0, + }, + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_GREEN, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .address = APDS9999_REG_LS_DATA_GREEN_0, + }, + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BLUE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .address = APDS9999_REG_LS_DATA_BLUE_0, + }, + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .address = APDS9999_REG_LS_DATA_IR_0, + }, +}; + +static int apds9999_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct apds9999_data *data; + struct iio_dev *indio_dev; + int ret, part_id; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + part_id = i2c_smbus_read_byte_data(client, APDS9999_REG_PART_ID); + if (part_id < 0) + return dev_err_probe(dev, part_id, "failed to read PART_ID\n"); + if (part_id != APDS9999_PART_ID) + dev_info(dev, "unexpected PART_ID 0x%02x (expected 0x%02x)\n", + part_id, APDS9999_PART_ID); + + ret = apds9999_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize device\n"); + + indio_dev->name = "apds9999"; + indio_dev->info = &apds9999_info; + indio_dev->channels = apds9999_channels; + indio_dev->num_channels = ARRAY_SIZE(apds9999_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "failed to register IIO device\n"); + + return 0; +} + +static const struct i2c_device_id apds9999_id[] = { + { .name = "apds9999" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, apds9999_id); + +static const struct of_device_id apds9999_of_match[] = { + { .compatible = "brcm,apds9999" }, + { } +}; +MODULE_DEVICE_TABLE(of, apds9999_of_match); + +static struct i2c_driver apds9999_driver = { + .driver = { + .name = "apds9999", + .of_match_table = apds9999_of_match, + }, + .probe = apds9999_probe, + .id_table = apds9999_id, +}; +module_i2c_driver(apds9999_driver); + +MODULE_AUTHOR("Jose A. Perez de Azpillaga <azpijr@gmail.com>"); +MODULE_DESCRIPTION("APDS-9999 Lux Light Sensor IIO Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/as73211.c b/drivers/iio/light/as73211.c new file mode 100644 index 000000000..67f33bbe1 --- /dev/null +++ b/drivers/iio/light/as73211.c @@ -0,0 +1,897 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Support for AMS AS73211 JENCOLOR(R) Digital XYZ Sensor and AMS AS7331 + * UVA, UVB and UVC (DUV) Ultraviolet Sensor + * + * Author: Christian Eggers <ceggers@arri.de> + * + * Copyright (c) 2020 ARRI Lighting + * + * Color light sensor with 16-bit channels for x, y, z and temperature); + * 7-bit I2C slave address 0x74 .. 0x77. + * + * Datasheets: + * AS73211: https://ams.com/documents/20143/36005/AS73211_DS000556_3-01.pdf + * AS7331: https://ams.com/documents/20143/9106314/AS7331_DS001047_4-00.pdf + */ + +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/units.h> + +#define AS73211_DRV_NAME "as73211" + +/* AS73211 configuration registers */ +#define AS73211_REG_OSR 0x0 +#define AS73211_REG_AGEN 0x2 +#define AS73211_REG_CREG1 0x6 +#define AS73211_REG_CREG2 0x7 +#define AS73211_REG_CREG3 0x8 + +/* AS73211 output register bank */ +#define AS73211_OUT_OSR_STATUS 0 +#define AS73211_OUT_TEMP 1 +#define AS73211_OUT_MRES1 2 +#define AS73211_OUT_MRES2 3 +#define AS73211_OUT_MRES3 4 + +#define AS73211_OSR_SS BIT(7) +#define AS73211_OSR_PD BIT(6) +#define AS73211_OSR_SW_RES BIT(3) +#define AS73211_OSR_DOS_MASK GENMASK(2, 0) +#define AS73211_OSR_DOS_CONFIG FIELD_PREP(AS73211_OSR_DOS_MASK, 0x2) +#define AS73211_OSR_DOS_MEASURE FIELD_PREP(AS73211_OSR_DOS_MASK, 0x3) + +#define AS73211_AGEN_DEVID_MASK GENMASK(7, 4) +#define AS73211_AGEN_DEVID(x) FIELD_PREP(AS73211_AGEN_DEVID_MASK, (x)) +#define AS73211_AGEN_MUT_MASK GENMASK(3, 0) +#define AS73211_AGEN_MUT(x) FIELD_PREP(AS73211_AGEN_MUT_MASK, (x)) + +#define AS73211_CREG1_GAIN_MASK GENMASK(7, 4) +#define AS73211_CREG1_GAIN_1 11 +#define AS73211_CREG1_TIME_MASK GENMASK(3, 0) + +#define AS73211_CREG3_CCLK_MASK GENMASK(1, 0) + +#define AS73211_OSR_STATUS_OUTCONVOF BIT(15) +#define AS73211_OSR_STATUS_MRESOF BIT(14) +#define AS73211_OSR_STATUS_ADCOF BIT(13) +#define AS73211_OSR_STATUS_LDATA BIT(12) +#define AS73211_OSR_STATUS_NDATA BIT(11) +#define AS73211_OSR_STATUS_NOTREADY BIT(10) + +#define AS73211_SAMPLE_FREQ_BASE 1024000 + +#define AS73211_SAMPLE_TIME_NUM 15 +#define AS73211_SAMPLE_TIME_MAX_MS BIT(AS73211_SAMPLE_TIME_NUM - 1) + +/* Available sample frequencies are 1.024MHz multiplied by powers of two. */ +static const int as73211_samp_freq_avail[] = { + AS73211_SAMPLE_FREQ_BASE * 1, + AS73211_SAMPLE_FREQ_BASE * 2, + AS73211_SAMPLE_FREQ_BASE * 4, + AS73211_SAMPLE_FREQ_BASE * 8, +}; + +static const int as73211_hardwaregain_avail[] = { + 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, +}; + +struct as73211_data; + +/** + * struct as73211_spec_dev_data - device-specific data + * @intensity_scale: Function to retrieve intensity scale values. + * @channels: Device channels. + * @num_channels: Number of channels of the device. + */ +struct as73211_spec_dev_data { + int (*intensity_scale)(struct as73211_data *data, int chan, int *val, int *val2); + struct iio_chan_spec const *channels; + int num_channels; +}; + +/** + * struct as73211_data - Instance data for one AS73211 + * @client: I2C client. + * @osr: Cached Operational State Register. + * @creg1: Cached Configuration Register 1. + * @creg2: Cached Configuration Register 2. + * @creg3: Cached Configuration Register 3. + * @mutex: Keeps cached registers in sync with the device. + * @completion: Completion to wait for interrupt. + * @int_time_avail: Available integration times (depend on sampling frequency). + * @spec_dev: device-specific configuration. + */ +struct as73211_data { + struct i2c_client *client; + u8 osr; + u8 creg1; + u8 creg2; + u8 creg3; + struct mutex mutex; + struct completion completion; + int int_time_avail[AS73211_SAMPLE_TIME_NUM * 2]; + const struct as73211_spec_dev_data *spec_dev; +}; + +#define AS73211_COLOR_CHANNEL(_color, _si, _addr) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .channel2 = IIO_MOD_##_color, \ + .address = _addr, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +#define AS73211_OFFSET_TEMP_INT (-66) +#define AS73211_OFFSET_TEMP_MICRO 900000 +#define AS73211_SCALE_TEMP_INT 0 +#define AS73211_SCALE_TEMP_MICRO 50000 + +#define AS73211_SCALE_X 277071108 /* nW/m^2 */ +#define AS73211_SCALE_Y 298384270 /* nW/m^2 */ +#define AS73211_SCALE_Z 160241927 /* nW/m^2 */ + +#define AS7331_SCALE_UVA 340000 /* nW/cm^2 */ +#define AS7331_SCALE_UVB 378000 /* nW/cm^2 */ +#define AS7331_SCALE_UVC 166000 /* nW/cm^2 */ + +/* Channel order MUST match devices result register order */ +#define AS73211_SCAN_INDEX_TEMP 0 +#define AS73211_SCAN_INDEX_X 1 +#define AS73211_SCAN_INDEX_Y 2 +#define AS73211_SCAN_INDEX_Z 3 +#define AS73211_SCAN_INDEX_TS 4 + +#define AS73211_SCAN_MASK_COLOR ( \ + BIT(AS73211_SCAN_INDEX_X) | \ + BIT(AS73211_SCAN_INDEX_Y) | \ + BIT(AS73211_SCAN_INDEX_Z)) + +#define AS73211_SCAN_MASK_ALL ( \ + BIT(AS73211_SCAN_INDEX_TEMP) | \ + AS73211_SCAN_MASK_COLOR) + +static const unsigned long as73211_scan_masks[] = { + AS73211_SCAN_MASK_COLOR, + AS73211_SCAN_MASK_ALL, + 0 +}; + +static const struct iio_chan_spec as73211_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .address = AS73211_OUT_TEMP, + .scan_index = AS73211_SCAN_INDEX_TEMP, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + } + }, + AS73211_COLOR_CHANNEL(X, AS73211_SCAN_INDEX_X, AS73211_OUT_MRES1), + AS73211_COLOR_CHANNEL(Y, AS73211_SCAN_INDEX_Y, AS73211_OUT_MRES2), + AS73211_COLOR_CHANNEL(Z, AS73211_SCAN_INDEX_Z, AS73211_OUT_MRES3), + IIO_CHAN_SOFT_TIMESTAMP(AS73211_SCAN_INDEX_TS), +}; + +static const struct iio_chan_spec as7331_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .address = AS73211_OUT_TEMP, + .scan_index = AS73211_SCAN_INDEX_TEMP, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + } + }, + AS73211_COLOR_CHANNEL(LIGHT_UVA, AS73211_SCAN_INDEX_X, AS73211_OUT_MRES1), + AS73211_COLOR_CHANNEL(LIGHT_UVB, AS73211_SCAN_INDEX_Y, AS73211_OUT_MRES2), + AS73211_COLOR_CHANNEL(LIGHT_DUV, AS73211_SCAN_INDEX_Z, AS73211_OUT_MRES3), + IIO_CHAN_SOFT_TIMESTAMP(AS73211_SCAN_INDEX_TS), +}; + +static unsigned int as73211_integration_time_1024cyc(struct as73211_data *data) +{ + /* + * Return integration time in units of 1024 clock cycles. Integration time + * in CREG1 is in powers of 2 (x 1024 cycles). + */ + return BIT(FIELD_GET(AS73211_CREG1_TIME_MASK, data->creg1)); +} + +static unsigned int as73211_integration_time_us(struct as73211_data *data, + unsigned int integration_time_1024cyc) +{ + /* + * f_samp is configured in CREG3 in powers of 2 (x 1.024 MHz) + * t_cycl is configured in CREG1 in powers of 2 (x 1024 cycles) + * t_int_us = 1 / (f_samp) * t_cycl * US_PER_SEC + * = 1 / (2^CREG3_CCLK * 1,024,000) * 2^CREG1_CYCLES * 1,024 * US_PER_SEC + * = 2^(-CREG3_CCLK) * 2^CREG1_CYCLES * 1,000 + * In order to get rid of negative exponents, we extend the "fraction" + * by 2^3 (CREG3_CCLK,max = 3) + * t_int_us = 2^(3-CREG3_CCLK) * 2^CREG1_CYCLES * 125 + */ + return BIT(3 - FIELD_GET(AS73211_CREG3_CCLK_MASK, data->creg3)) * + integration_time_1024cyc * 125; +} + +static void as73211_integration_time_calc_avail(struct as73211_data *data) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(data->int_time_avail) / 2; i++) { + unsigned int time_us = as73211_integration_time_us(data, BIT(i)); + + data->int_time_avail[i * 2 + 0] = time_us / USEC_PER_SEC; + data->int_time_avail[i * 2 + 1] = time_us % USEC_PER_SEC; + } +} + +static unsigned int as73211_gain(struct as73211_data *data) +{ + /* gain can be calculated from CREG1 as 2^(11 - CREG1_GAIN) */ + return BIT(AS73211_CREG1_GAIN_1 - FIELD_GET(AS73211_CREG1_GAIN_MASK, data->creg1)); +} + +/* must be called with as73211_data::mutex held. */ +static int as73211_req_data(struct as73211_data *data) +{ + unsigned int time_us = as73211_integration_time_us(data, + as73211_integration_time_1024cyc(data)); + struct device *dev = &data->client->dev; + union i2c_smbus_data smbus_data; + u16 osr_status; + int ret; + + if (data->client->irq) + reinit_completion(&data->completion); + + /* + * During measurement, there should be no traffic on the i2c bus as the + * electrical noise would disturb the measurement process. + */ + i2c_lock_bus(data->client->adapter, I2C_LOCK_SEGMENT); + + data->osr &= ~AS73211_OSR_DOS_MASK; + data->osr |= AS73211_OSR_DOS_MEASURE | AS73211_OSR_SS; + + smbus_data.byte = data->osr; + ret = __i2c_smbus_xfer(data->client->adapter, data->client->addr, + data->client->flags, I2C_SMBUS_WRITE, + AS73211_REG_OSR, I2C_SMBUS_BYTE_DATA, &smbus_data); + if (ret < 0) { + i2c_unlock_bus(data->client->adapter, I2C_LOCK_SEGMENT); + return ret; + } + + /* + * Reset AS73211_OSR_SS (is self clearing) in order to avoid unintentional + * triggering of further measurements later. + */ + data->osr &= ~AS73211_OSR_SS; + + /* + * Add 33% extra margin for the timeout. fclk,min = fclk,typ - 27%. + */ + time_us += time_us / 3; + if (data->client->irq) { + ret = wait_for_completion_timeout(&data->completion, usecs_to_jiffies(time_us)); + if (!ret) { + dev_err(dev, "timeout waiting for READY IRQ\n"); + i2c_unlock_bus(data->client->adapter, I2C_LOCK_SEGMENT); + return -ETIMEDOUT; + } + } else { + /* Wait integration time */ + usleep_range(time_us, 2 * time_us); + } + + i2c_unlock_bus(data->client->adapter, I2C_LOCK_SEGMENT); + + ret = i2c_smbus_read_word_data(data->client, AS73211_OUT_OSR_STATUS); + if (ret < 0) + return ret; + + osr_status = ret; + if (osr_status != (AS73211_OSR_DOS_MEASURE | AS73211_OSR_STATUS_NDATA)) { + if (osr_status & AS73211_OSR_SS) { + dev_err(dev, "%s() Measurement has not stopped\n", __func__); + return -ETIME; + } + if (osr_status & AS73211_OSR_STATUS_NOTREADY) { + dev_err(dev, "%s() Data is not ready\n", __func__); + return -ENODATA; + } + if (!(osr_status & AS73211_OSR_STATUS_NDATA)) { + dev_err(dev, "%s() No new data available\n", __func__); + return -ENODATA; + } + if (osr_status & AS73211_OSR_STATUS_LDATA) { + dev_err(dev, "%s() Result buffer overrun\n", __func__); + return -ENOBUFS; + } + if (osr_status & AS73211_OSR_STATUS_ADCOF) { + dev_err(dev, "%s() ADC overflow\n", __func__); + return -EOVERFLOW; + } + if (osr_status & AS73211_OSR_STATUS_MRESOF) { + dev_err(dev, "%s() Measurement result overflow\n", __func__); + return -EOVERFLOW; + } + if (osr_status & AS73211_OSR_STATUS_OUTCONVOF) { + dev_err(dev, "%s() Timer overflow\n", __func__); + return -EOVERFLOW; + } + dev_err(dev, "%s() Unexpected status value\n", __func__); + return -EIO; + } + + return 0; +} + +static int as73211_intensity_scale(struct as73211_data *data, int chan, + int *val, int *val2) +{ + switch (chan) { + case IIO_MOD_X: + *val = AS73211_SCALE_X; + break; + case IIO_MOD_Y: + *val = AS73211_SCALE_Y; + break; + case IIO_MOD_Z: + *val = AS73211_SCALE_Z; + break; + default: + return -EINVAL; + } + *val2 = as73211_integration_time_1024cyc(data) * as73211_gain(data); + + return IIO_VAL_FRACTIONAL; +} + +static int as7331_intensity_scale(struct as73211_data *data, int chan, + int *val, int *val2) +{ + switch (chan) { + case IIO_MOD_LIGHT_UVA: + *val = AS7331_SCALE_UVA; + break; + case IIO_MOD_LIGHT_UVB: + *val = AS7331_SCALE_UVB; + break; + case IIO_MOD_LIGHT_DUV: + *val = AS7331_SCALE_UVC; + break; + default: + return -EINVAL; + } + *val2 = as73211_integration_time_1024cyc(data) * as73211_gain(data); + + return IIO_VAL_FRACTIONAL; +} + +static int as73211_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct as73211_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = as73211_req_data(data); + if (ret < 0) { + iio_device_release_direct(indio_dev); + return ret; + } + + ret = i2c_smbus_read_word_data(data->client, chan->address); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_OFFSET: + *val = AS73211_OFFSET_TEMP_INT; + *val2 = AS73211_OFFSET_TEMP_MICRO; + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + *val = AS73211_SCALE_TEMP_INT; + *val2 = AS73211_SCALE_TEMP_MICRO; + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_INTENSITY: + return data->spec_dev->intensity_scale(data, chan->channel2, + val, val2); + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SAMP_FREQ: + /* f_samp is configured in CREG3 in powers of 2 (x 1.024 MHz) */ + *val = BIT(FIELD_GET(AS73211_CREG3_CCLK_MASK, data->creg3)) * + AS73211_SAMPLE_FREQ_BASE; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_HARDWAREGAIN: + *val = as73211_gain(data); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_INT_TIME: { + unsigned int time_us; + + mutex_lock(&data->mutex); + time_us = as73211_integration_time_us(data, as73211_integration_time_1024cyc(data)); + mutex_unlock(&data->mutex); + *val = time_us / USEC_PER_SEC; + *val2 = time_us % USEC_PER_SEC; + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + }} +} + +static int as73211_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, long mask) +{ + struct as73211_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *length = ARRAY_SIZE(as73211_samp_freq_avail); + *vals = as73211_samp_freq_avail; + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_HARDWAREGAIN: + *length = ARRAY_SIZE(as73211_hardwaregain_avail); + *vals = as73211_hardwaregain_avail; + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_INT_TIME: + *length = ARRAY_SIZE(data->int_time_avail); + *vals = data->int_time_avail; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + + default: + return -EINVAL; + } +} + +static int _as73211_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan __always_unused, + int val, int val2, long mask) +{ + struct as73211_data *data = iio_priv(indio_dev); + int ret; + + /* Need to switch to config mode ... */ + if ((data->osr & AS73211_OSR_DOS_MASK) != AS73211_OSR_DOS_CONFIG) { + data->osr &= ~AS73211_OSR_DOS_MASK; + data->osr |= AS73211_OSR_DOS_CONFIG; + + ret = i2c_smbus_write_byte_data(data->client, AS73211_REG_OSR, data->osr); + if (ret < 0) + return ret; + } + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: { + int reg_bits, freq_kHz = val / HZ_PER_KHZ; /* 1024, 2048, ... */ + + /* val must be 1024 * 2^x */ + if (val < 0 || (freq_kHz * HZ_PER_KHZ) != val || + !is_power_of_2(freq_kHz) || val2) + return -EINVAL; + + /* f_samp is configured in CREG3 in powers of 2 (x 1.024 MHz (=2^10)) */ + reg_bits = ilog2(freq_kHz) - 10; + if (!FIELD_FIT(AS73211_CREG3_CCLK_MASK, reg_bits)) + return -EINVAL; + + data->creg3 &= ~AS73211_CREG3_CCLK_MASK; + data->creg3 |= FIELD_PREP(AS73211_CREG3_CCLK_MASK, reg_bits); + as73211_integration_time_calc_avail(data); + + ret = i2c_smbus_write_byte_data(data->client, AS73211_REG_CREG3, data->creg3); + if (ret < 0) + return ret; + + return 0; + } + case IIO_CHAN_INFO_HARDWAREGAIN: { + unsigned int reg_bits; + + if (val < 0 || !is_power_of_2(val) || val2) + return -EINVAL; + + /* gain can be calculated from CREG1 as 2^(11 - CREG1_GAIN) */ + reg_bits = AS73211_CREG1_GAIN_1 - ilog2(val); + if (!FIELD_FIT(AS73211_CREG1_GAIN_MASK, reg_bits)) + return -EINVAL; + + data->creg1 &= ~AS73211_CREG1_GAIN_MASK; + data->creg1 |= FIELD_PREP(AS73211_CREG1_GAIN_MASK, reg_bits); + + ret = i2c_smbus_write_byte_data(data->client, AS73211_REG_CREG1, data->creg1); + if (ret < 0) + return ret; + + return 0; + } + case IIO_CHAN_INFO_INT_TIME: { + int val_us = val * USEC_PER_SEC + val2; + int time_ms; + int reg_bits; + + /* f_samp is configured in CREG3 in powers of 2 (x 1.024 MHz) */ + int f_samp_1_024mhz = BIT(FIELD_GET(AS73211_CREG3_CCLK_MASK, data->creg3)); + + /* + * time_ms = time_us * US_PER_MS * f_samp_1_024mhz / MHZ_PER_HZ + * = time_us * f_samp_1_024mhz / 1000 + */ + time_ms = (val_us * f_samp_1_024mhz) / 1000; /* 1 ms, 2 ms, ... (power of two) */ + if (time_ms < 0 || !is_power_of_2(time_ms) || time_ms > AS73211_SAMPLE_TIME_MAX_MS) + return -EINVAL; + + reg_bits = ilog2(time_ms); + if (!FIELD_FIT(AS73211_CREG1_TIME_MASK, reg_bits)) + return -EINVAL; /* not possible due to previous tests */ + + data->creg1 &= ~AS73211_CREG1_TIME_MASK; + data->creg1 |= FIELD_PREP(AS73211_CREG1_TIME_MASK, reg_bits); + + ret = i2c_smbus_write_byte_data(data->client, AS73211_REG_CREG1, data->creg1); + if (ret < 0) + return ret; + + return 0; + + default: + return -EINVAL; + }} +} + +static int as73211_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct as73211_data *data = iio_priv(indio_dev); + int ret; + + guard(mutex)(&data->mutex); + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = _as73211_write_raw(indio_dev, chan, val, val2, mask); + iio_device_release_direct(indio_dev); + + return ret; +} + +static irqreturn_t as73211_ready_handler(int irq __always_unused, void *priv) +{ + struct as73211_data *data = iio_priv(priv); + + complete(&data->completion); + + return IRQ_HANDLED; +} + +static irqreturn_t as73211_trigger_handler(int irq __always_unused, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct as73211_data *data = iio_priv(indio_dev); + struct { + __le16 chan[4]; + aligned_s64 ts; + } scan = { }; + int data_result, ret; + + mutex_lock(&data->mutex); + + data_result = as73211_req_data(data); + if (data_result < 0 && data_result != -EOVERFLOW) + goto done; /* don't push any data for errors other than EOVERFLOW */ + + if (*indio_dev->active_scan_mask == AS73211_SCAN_MASK_ALL) { + /* Optimization for reading all (color + temperature) channels */ + u8 addr = as73211_channels[0].address; + struct i2c_msg msgs[] = { + { + .addr = data->client->addr, + .flags = 0, + .len = 1, + .buf = &addr, + }, + { + .addr = data->client->addr, + .flags = I2C_M_RD, + .len = sizeof(scan.chan), + .buf = (u8 *)&scan.chan, + }, + }; + + ret = i2c_transfer(data->client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + goto done; + } else { + /* Optimization for reading only color channels */ + + /* AS73211 starts reading at address 2 */ + ret = i2c_master_recv(data->client, + (char *)&scan.chan[0], 3 * sizeof(scan.chan[0])); + if (ret < 0) + goto done; + } + + if (data_result) { + /* + * Saturate all channels (in case of overflows). Temperature channel + * is not affected by overflows. + */ + if (*indio_dev->active_scan_mask == AS73211_SCAN_MASK_ALL) { + scan.chan[1] = cpu_to_le16(U16_MAX); + scan.chan[2] = cpu_to_le16(U16_MAX); + scan.chan[3] = cpu_to_le16(U16_MAX); + } else { + scan.chan[0] = cpu_to_le16(U16_MAX); + scan.chan[1] = cpu_to_le16(U16_MAX); + scan.chan[2] = cpu_to_le16(U16_MAX); + } + } + + iio_push_to_buffers_with_timestamp(indio_dev, &scan, iio_get_time_ns(indio_dev)); + +done: + mutex_unlock(&data->mutex); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_info as73211_info = { + .read_raw = as73211_read_raw, + .read_avail = as73211_read_avail, + .write_raw = as73211_write_raw, +}; + +static int as73211_power(struct iio_dev *indio_dev, bool state) +{ + struct as73211_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + + if (state) + data->osr &= ~AS73211_OSR_PD; + else + data->osr |= AS73211_OSR_PD; + + ret = i2c_smbus_write_byte_data(data->client, AS73211_REG_OSR, data->osr); + + mutex_unlock(&data->mutex); + + if (ret < 0) + return ret; + + return 0; +} + +static void as73211_power_disable(void *data) +{ + struct iio_dev *indio_dev = data; + + as73211_power(indio_dev, false); +} + +static int as73211_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct as73211_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + data->spec_dev = i2c_get_match_data(client); + if (!data->spec_dev) + return -EINVAL; + + mutex_init(&data->mutex); + init_completion(&data->completion); + + indio_dev->info = &as73211_info; + indio_dev->name = AS73211_DRV_NAME; + indio_dev->channels = data->spec_dev->channels; + indio_dev->num_channels = data->spec_dev->num_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->available_scan_masks = as73211_scan_masks; + + ret = i2c_smbus_read_byte_data(data->client, AS73211_REG_OSR); + if (ret < 0) + return ret; + data->osr = ret; + + /* reset device */ + data->osr |= AS73211_OSR_SW_RES; + ret = i2c_smbus_write_byte_data(data->client, AS73211_REG_OSR, data->osr); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, AS73211_REG_OSR); + if (ret < 0) + return ret; + data->osr = ret; + + /* + * Reading AGEN is only possible after reset (AGEN is not available if + * device is in measurement mode). + */ + ret = i2c_smbus_read_byte_data(data->client, AS73211_REG_AGEN); + if (ret < 0) + return ret; + + /* At the time of writing this driver, only DEVID 2 and MUT 1 are known. */ + if ((ret & AS73211_AGEN_DEVID_MASK) != AS73211_AGEN_DEVID(2) || + (ret & AS73211_AGEN_MUT_MASK) != AS73211_AGEN_MUT(1)) + return -ENODEV; + + ret = i2c_smbus_read_byte_data(data->client, AS73211_REG_CREG1); + if (ret < 0) + return ret; + data->creg1 = ret; + + ret = i2c_smbus_read_byte_data(data->client, AS73211_REG_CREG2); + if (ret < 0) + return ret; + data->creg2 = ret; + + ret = i2c_smbus_read_byte_data(data->client, AS73211_REG_CREG3); + if (ret < 0) + return ret; + data->creg3 = ret; + as73211_integration_time_calc_avail(data); + + ret = as73211_power(indio_dev, true); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(dev, as73211_power_disable, indio_dev); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, as73211_trigger_handler, NULL); + if (ret) + return ret; + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, + as73211_ready_handler, + IRQF_ONESHOT, + client->name, indio_dev); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static int as73211_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + + return as73211_power(indio_dev, false); +} + +static int as73211_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + + return as73211_power(indio_dev, true); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(as73211_pm_ops, as73211_suspend, + as73211_resume); + +static const struct as73211_spec_dev_data as73211_spec = { + .intensity_scale = as73211_intensity_scale, + .channels = as73211_channels, + .num_channels = ARRAY_SIZE(as73211_channels), +}; + +static const struct as73211_spec_dev_data as7331_spec = { + .intensity_scale = as7331_intensity_scale, + .channels = as7331_channels, + .num_channels = ARRAY_SIZE(as7331_channels), +}; + +static const struct of_device_id as73211_of_match[] = { + { .compatible = "ams,as73211", &as73211_spec }, + { .compatible = "ams,as7331", &as7331_spec }, + { } +}; +MODULE_DEVICE_TABLE(of, as73211_of_match); + +static const struct i2c_device_id as73211_id[] = { + { .name = "as73211", .driver_data = (kernel_ulong_t)&as73211_spec }, + { .name = "as7331", .driver_data = (kernel_ulong_t)&as7331_spec }, + { } +}; +MODULE_DEVICE_TABLE(i2c, as73211_id); + +static struct i2c_driver as73211_driver = { + .driver = { + .name = AS73211_DRV_NAME, + .of_match_table = as73211_of_match, + .pm = pm_sleep_ptr(&as73211_pm_ops), + }, + .probe = as73211_probe, + .id_table = as73211_id, +}; +module_i2c_driver(as73211_driver); + +MODULE_AUTHOR("Christian Eggers <ceggers@arri.de>"); +MODULE_DESCRIPTION("AS73211 XYZ True Color Sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/bh1745.c b/drivers/iio/light/bh1745.c new file mode 100644 index 000000000..2b8ff3607 --- /dev/null +++ b/drivers/iio/light/bh1745.c @@ -0,0 +1,900 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ROHM BH1745 digital colour sensor driver + * + * Copyright (C) Mudit Sharma <muditsharma.info@gmail.com> + * + * 7-bit I2C slave addresses: + * 0x38 (ADDR pin low) + * 0x39 (ADDR pin high) + */ + +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/util_macros.h> +#include <linux/iio/events.h> +#include <linux/regmap.h> +#include <linux/bits.h> +#include <linux/bitfield.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/iio-gts-helper.h> + +/* BH1745 configuration registers */ + +/* System control */ +#define BH1745_SYS_CTRL 0x40 +#define BH1745_SYS_CTRL_SW_RESET BIT(7) +#define BH1745_SYS_CTRL_INTR_RESET BIT(6) +#define BH1745_SYS_CTRL_PART_ID_MASK GENMASK(5, 0) +#define BH1745_PART_ID 0x0B + +/* Mode control 1 */ +#define BH1745_MODE_CTRL1 0x41 +#define BH1745_CTRL1_MEASUREMENT_TIME_MASK GENMASK(2, 0) + +/* Mode control 2 */ +#define BH1745_MODE_CTRL2 0x42 +#define BH1745_CTRL2_RGBC_EN BIT(4) +#define BH1745_CTRL2_ADC_GAIN_MASK GENMASK(1, 0) + +/* Interrupt */ +#define BH1745_INTR 0x60 +#define BH1745_INTR_STATUS BIT(7) +#define BH1745_INTR_SOURCE_MASK GENMASK(3, 2) +#define BH1745_INTR_ENABLE BIT(0) + +#define BH1745_PERSISTENCE 0x61 + +/* Threshold high */ +#define BH1745_TH_LSB 0x62 +#define BH1745_TH_MSB 0x63 + +/* Threshold low */ +#define BH1745_TL_LSB 0x64 +#define BH1745_TL_MSB 0x65 + +/* BH1745 data output regs */ +#define BH1745_RED_LSB 0x50 +#define BH1745_RED_MSB 0x51 +#define BH1745_GREEN_LSB 0x52 +#define BH1745_GREEN_MSB 0x53 +#define BH1745_BLUE_LSB 0x54 +#define BH1745_BLUE_MSB 0x55 +#define BH1745_CLEAR_LSB 0x56 +#define BH1745_CLEAR_MSB 0x57 + +#define BH1745_MANU_ID_REG 0x92 + +/* From 16x max HW gain and 32x max integration time */ +#define BH1745_MAX_GAIN 512 + +enum bh1745_int_source { + BH1745_INTR_SOURCE_RED, + BH1745_INTR_SOURCE_GREEN, + BH1745_INTR_SOURCE_BLUE, + BH1745_INTR_SOURCE_CLEAR, +}; + +enum bh1745_gain { + BH1745_ADC_GAIN_1X, + BH1745_ADC_GAIN_2X, + BH1745_ADC_GAIN_16X, +}; + +enum bh1745_measurement_time { + BH1745_MEASUREMENT_TIME_160MS, + BH1745_MEASUREMENT_TIME_320MS, + BH1745_MEASUREMENT_TIME_640MS, + BH1745_MEASUREMENT_TIME_1280MS, + BH1745_MEASUREMENT_TIME_2560MS, + BH1745_MEASUREMENT_TIME_5120MS, +}; + +enum bh1745_presistence_value { + BH1745_PRESISTENCE_UPDATE_TOGGLE, + BH1745_PRESISTENCE_UPDATE_EACH_MEASUREMENT, + BH1745_PRESISTENCE_UPDATE_FOUR_MEASUREMENT, + BH1745_PRESISTENCE_UPDATE_EIGHT_MEASUREMENT, +}; + +static const struct iio_gain_sel_pair bh1745_gain[] = { + GAIN_SCALE_GAIN(1, BH1745_ADC_GAIN_1X), + GAIN_SCALE_GAIN(2, BH1745_ADC_GAIN_2X), + GAIN_SCALE_GAIN(16, BH1745_ADC_GAIN_16X), +}; + +static const struct iio_itime_sel_mul bh1745_itimes[] = { + GAIN_SCALE_ITIME_US(5120000, BH1745_MEASUREMENT_TIME_5120MS, 32), + GAIN_SCALE_ITIME_US(2560000, BH1745_MEASUREMENT_TIME_2560MS, 16), + GAIN_SCALE_ITIME_US(1280000, BH1745_MEASUREMENT_TIME_1280MS, 8), + GAIN_SCALE_ITIME_US(640000, BH1745_MEASUREMENT_TIME_640MS, 4), + GAIN_SCALE_ITIME_US(320000, BH1745_MEASUREMENT_TIME_320MS, 2), + GAIN_SCALE_ITIME_US(160000, BH1745_MEASUREMENT_TIME_160MS, 1), +}; + +struct bh1745_data { + /* + * Lock to prevent device setting update or read before + * related calculations are completed + */ + struct mutex lock; + struct regmap *regmap; + struct device *dev; + struct iio_trigger *trig; + struct iio_gts gts; +}; + +static const struct regmap_range bh1745_volatile_ranges[] = { + regmap_reg_range(BH1745_MODE_CTRL2, BH1745_MODE_CTRL2), /* VALID */ + regmap_reg_range(BH1745_RED_LSB, BH1745_CLEAR_MSB), /* Data */ + regmap_reg_range(BH1745_INTR, BH1745_INTR), /* Interrupt */ +}; + +static const struct regmap_access_table bh1745_volatile_regs = { + .yes_ranges = bh1745_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(bh1745_volatile_ranges), +}; + +static const struct regmap_range bh1745_readable_ranges[] = { + regmap_reg_range(BH1745_SYS_CTRL, BH1745_MODE_CTRL2), + regmap_reg_range(BH1745_RED_LSB, BH1745_CLEAR_MSB), + regmap_reg_range(BH1745_INTR, BH1745_INTR), + regmap_reg_range(BH1745_PERSISTENCE, BH1745_TL_MSB), + regmap_reg_range(BH1745_MANU_ID_REG, BH1745_MANU_ID_REG), +}; + +static const struct regmap_access_table bh1745_readable_regs = { + .yes_ranges = bh1745_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(bh1745_readable_ranges), +}; + +static const struct regmap_range bh1745_writable_ranges[] = { + regmap_reg_range(BH1745_SYS_CTRL, BH1745_MODE_CTRL2), + regmap_reg_range(BH1745_INTR, BH1745_INTR), + regmap_reg_range(BH1745_PERSISTENCE, BH1745_TL_MSB), +}; + +static const struct regmap_access_table bh1745_writable_regs = { + .yes_ranges = bh1745_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(bh1745_writable_ranges), +}; + +static const struct regmap_config bh1745_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = BH1745_MANU_ID_REG, + .cache_type = REGCACHE_RBTREE, + .volatile_table = &bh1745_volatile_regs, + .wr_table = &bh1745_writable_regs, + .rd_table = &bh1745_readable_regs, +}; + +static const struct iio_event_spec bh1745_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_shared_by_type = BIT(IIO_EV_INFO_PERIOD), + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +#define BH1745_CHANNEL(_colour, _si, _addr) \ + { \ + .type = IIO_INTENSITY, .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .event_spec = bh1745_event_spec, \ + .num_event_specs = ARRAY_SIZE(bh1745_event_spec), \ + .channel2 = IIO_MOD_LIGHT_##_colour, .address = _addr, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + } + +static const struct iio_chan_spec bh1745_channels[] = { + BH1745_CHANNEL(RED, 0, BH1745_RED_LSB), + BH1745_CHANNEL(GREEN, 1, BH1745_GREEN_LSB), + BH1745_CHANNEL(BLUE, 2, BH1745_BLUE_LSB), + BH1745_CHANNEL(CLEAR, 3, BH1745_CLEAR_LSB), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static int bh1745_reset(struct bh1745_data *data) +{ + return regmap_set_bits(data->regmap, BH1745_SYS_CTRL, + BH1745_SYS_CTRL_SW_RESET | + BH1745_SYS_CTRL_INTR_RESET); +} + +static int bh1745_power_on(struct bh1745_data *data) +{ + return regmap_set_bits(data->regmap, BH1745_MODE_CTRL2, + BH1745_CTRL2_RGBC_EN); +} + +static void bh1745_power_off(void *data_ptr) +{ + struct bh1745_data *data = data_ptr; + struct device *dev = data->dev; + int ret; + + ret = regmap_clear_bits(data->regmap, BH1745_MODE_CTRL2, + BH1745_CTRL2_RGBC_EN); + if (ret) + dev_err(dev, "Failed to turn off device\n"); +} + +static int bh1745_get_scale(struct bh1745_data *data, int *val, int *val2) +{ + int ret; + int value; + int gain_sel, int_time_sel; + int gain; + const struct iio_itime_sel_mul *int_time; + + ret = regmap_read(data->regmap, BH1745_MODE_CTRL2, &value); + if (ret) + return ret; + + gain_sel = FIELD_GET(BH1745_CTRL2_ADC_GAIN_MASK, value); + gain = iio_gts_find_gain_by_sel(&data->gts, gain_sel); + + ret = regmap_read(data->regmap, BH1745_MODE_CTRL1, &value); + if (ret) + return ret; + + int_time_sel = FIELD_GET(BH1745_CTRL1_MEASUREMENT_TIME_MASK, value); + int_time = iio_gts_find_itime_by_sel(&data->gts, int_time_sel); + + return iio_gts_get_scale(&data->gts, gain, int_time->time_us, val, + val2); +} + +static int bh1745_set_scale(struct bh1745_data *data, int val) +{ + struct device *dev = data->dev; + int ret; + int value; + int hw_gain_sel, current_int_time_sel, new_int_time_sel; + + ret = regmap_read(data->regmap, BH1745_MODE_CTRL1, &value); + if (ret) + return ret; + + current_int_time_sel = FIELD_GET(BH1745_CTRL1_MEASUREMENT_TIME_MASK, + value); + ret = iio_gts_find_gain_sel_for_scale_using_time(&data->gts, + current_int_time_sel, + val, 0, &hw_gain_sel); + if (ret) { + for (int i = 0; i < data->gts.num_itime; i++) { + new_int_time_sel = data->gts.itime_table[i].sel; + + if (new_int_time_sel == current_int_time_sel) + continue; + + ret = iio_gts_find_gain_sel_for_scale_using_time(&data->gts, + new_int_time_sel, + val, 0, + &hw_gain_sel); + if (!ret) + break; + } + + if (ret) { + dev_dbg(dev, "Unsupported scale value requested: %d\n", + val); + return -EINVAL; + } + + ret = regmap_write_bits(data->regmap, BH1745_MODE_CTRL1, + BH1745_CTRL1_MEASUREMENT_TIME_MASK, + new_int_time_sel); + if (ret) + return ret; + } + + return regmap_write_bits(data->regmap, BH1745_MODE_CTRL2, + BH1745_CTRL2_ADC_GAIN_MASK, hw_gain_sel); +} + +static int bh1745_get_int_time(struct bh1745_data *data, int *val) +{ + int ret; + int value; + int int_time, int_time_sel; + + ret = regmap_read(data->regmap, BH1745_MODE_CTRL1, &value); + if (ret) + return ret; + + int_time_sel = FIELD_GET(BH1745_CTRL1_MEASUREMENT_TIME_MASK, value); + int_time = iio_gts_find_int_time_by_sel(&data->gts, int_time_sel); + if (int_time < 0) + return int_time; + + *val = int_time; + + return 0; +} + +static int bh1745_set_int_time(struct bh1745_data *data, int val, int val2) +{ + struct device *dev = data->dev; + int ret; + int value; + int current_int_time, current_hwgain_sel, current_hwgain; + int new_hwgain, new_hwgain_sel, new_int_time_sel; + int req_int_time = (1000000 * val) + val2; + + if (!iio_gts_valid_time(&data->gts, req_int_time)) { + dev_dbg(dev, "Unsupported integration time requested: %d\n", + req_int_time); + return -EINVAL; + } + + ret = bh1745_get_int_time(data, ¤t_int_time); + if (ret) + return ret; + + if (current_int_time == req_int_time) + return 0; + + ret = regmap_read(data->regmap, BH1745_MODE_CTRL2, &value); + if (ret) + return ret; + + current_hwgain_sel = FIELD_GET(BH1745_CTRL2_ADC_GAIN_MASK, value); + current_hwgain = iio_gts_find_gain_by_sel(&data->gts, + current_hwgain_sel); + ret = iio_gts_find_new_gain_by_old_gain_time(&data->gts, current_hwgain, + current_int_time, + req_int_time, + &new_hwgain); + if (new_hwgain < 0) { + dev_dbg(dev, "No corresponding gain for requested integration time\n"); + return ret; + } + + if (ret) { + bool in_range; + + new_hwgain = iio_find_closest_gain_low(&data->gts, new_hwgain, + &in_range); + if (new_hwgain < 0) { + new_hwgain = iio_gts_get_min_gain(&data->gts); + if (new_hwgain < 0) + return ret; + } + + if (!in_range) + dev_dbg(dev, "Optimal gain out of range\n"); + + dev_dbg(dev, "Scale changed, new hw_gain %d\n", new_hwgain); + } + + new_hwgain_sel = iio_gts_find_sel_by_gain(&data->gts, new_hwgain); + if (new_hwgain_sel < 0) + return new_hwgain_sel; + + ret = regmap_write_bits(data->regmap, BH1745_MODE_CTRL2, + BH1745_CTRL2_ADC_GAIN_MASK, + new_hwgain_sel); + if (ret) + return ret; + + new_int_time_sel = iio_gts_find_sel_by_int_time(&data->gts, + req_int_time); + if (new_int_time_sel < 0) + return new_int_time_sel; + + return regmap_write_bits(data->regmap, BH1745_MODE_CTRL1, + BH1745_CTRL1_MEASUREMENT_TIME_MASK, + new_int_time_sel); +} + +static int bh1745_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bh1745_data *data = iio_priv(indio_dev); + int ret; + int value; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_bulk_read(data->regmap, chan->address, &value, 2); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + *val = value; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: { + guard(mutex)(&data->lock); + ret = bh1745_get_scale(data, val, val2); + if (ret) + return ret; + + return IIO_VAL_INT; + } + + case IIO_CHAN_INFO_INT_TIME: { + guard(mutex)(&data->lock); + *val = 0; + ret = bh1745_get_int_time(data, val2); + if (ret) + return 0; + + return IIO_VAL_INT_PLUS_MICRO; + } + + default: + return -EINVAL; + } +} + +static int bh1745_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bh1745_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return bh1745_set_scale(data, val); + + case IIO_CHAN_INFO_INT_TIME: + return bh1745_set_int_time(data, val, val2); + + default: + return -EINVAL; + } +} + +static int bh1745_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT; + + case IIO_CHAN_INFO_INT_TIME: + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + } +} + +static int bh1745_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct bh1745_data *data = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = regmap_bulk_read(data->regmap, BH1745_TH_LSB, + val, 2); + if (ret) + return ret; + + return IIO_VAL_INT; + + case IIO_EV_DIR_FALLING: + ret = regmap_bulk_read(data->regmap, BH1745_TL_LSB, + val, 2); + if (ret) + return ret; + + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + case IIO_EV_INFO_PERIOD: + ret = regmap_read(data->regmap, BH1745_PERSISTENCE, val); + if (ret) + return ret; + + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int bh1745_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct bh1745_data *data = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + if (val < 0x0 || val > 0xFFFF) + return -EINVAL; + + switch (dir) { + case IIO_EV_DIR_RISING: + ret = regmap_bulk_write(data->regmap, BH1745_TH_LSB, + &val, 2); + if (ret) + return ret; + + return IIO_VAL_INT; + + case IIO_EV_DIR_FALLING: + ret = regmap_bulk_write(data->regmap, BH1745_TL_LSB, + &val, 2); + if (ret) + return ret; + + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + case IIO_EV_INFO_PERIOD: + if (val < BH1745_PRESISTENCE_UPDATE_TOGGLE || + val > BH1745_PRESISTENCE_UPDATE_EIGHT_MEASUREMENT) + return -EINVAL; + ret = regmap_write(data->regmap, BH1745_PERSISTENCE, val); + if (ret) + return ret; + + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int bh1745_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct bh1745_data *data = iio_priv(indio_dev); + int ret; + int value; + int int_src; + + ret = regmap_read(data->regmap, BH1745_INTR, &value); + if (ret) + return ret; + + if (!FIELD_GET(BH1745_INTR_ENABLE, value)) + return 0; + + int_src = FIELD_GET(BH1745_INTR_SOURCE_MASK, value); + + switch (chan->channel2) { + case IIO_MOD_LIGHT_RED: + if (int_src == BH1745_INTR_SOURCE_RED) + return 1; + return 0; + + case IIO_MOD_LIGHT_GREEN: + if (int_src == BH1745_INTR_SOURCE_GREEN) + return 1; + return 0; + + case IIO_MOD_LIGHT_BLUE: + if (int_src == BH1745_INTR_SOURCE_BLUE) + return 1; + return 0; + + case IIO_MOD_LIGHT_CLEAR: + if (int_src == BH1745_INTR_SOURCE_CLEAR) + return 1; + return 0; + + default: + return -EINVAL; + } +} + +static int bh1745_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct bh1745_data *data = iio_priv(indio_dev); + int value; + + if (!state) + return regmap_clear_bits(data->regmap, + BH1745_INTR, BH1745_INTR_ENABLE); + + /* Latch is always enabled when enabling interrupt */ + value = BH1745_INTR_ENABLE; + + switch (chan->channel2) { + case IIO_MOD_LIGHT_RED: + return regmap_write(data->regmap, BH1745_INTR, + value | FIELD_PREP(BH1745_INTR_SOURCE_MASK, + BH1745_INTR_SOURCE_RED)); + + case IIO_MOD_LIGHT_GREEN: + return regmap_write(data->regmap, BH1745_INTR, + value | FIELD_PREP(BH1745_INTR_SOURCE_MASK, + BH1745_INTR_SOURCE_GREEN)); + + case IIO_MOD_LIGHT_BLUE: + return regmap_write(data->regmap, BH1745_INTR, + value | FIELD_PREP(BH1745_INTR_SOURCE_MASK, + BH1745_INTR_SOURCE_BLUE)); + + case IIO_MOD_LIGHT_CLEAR: + return regmap_write(data->regmap, BH1745_INTR, + value | FIELD_PREP(BH1745_INTR_SOURCE_MASK, + BH1745_INTR_SOURCE_CLEAR)); + + default: + return -EINVAL; + } +} + +static int bh1745_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, const int **vals, + int *type, int *length, long mask) +{ + struct bh1745_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return iio_gts_avail_times(&data->gts, vals, type, length); + + case IIO_CHAN_INFO_SCALE: + return iio_gts_all_avail_scales(&data->gts, vals, type, length); + + default: + return -EINVAL; + } +} + +static const struct iio_info bh1745_info = { + .read_raw = bh1745_read_raw, + .write_raw = bh1745_write_raw, + .write_raw_get_fmt = bh1745_write_raw_get_fmt, + .read_event_value = bh1745_read_thresh, + .write_event_value = bh1745_write_thresh, + .read_event_config = bh1745_read_event_config, + .write_event_config = bh1745_write_event_config, + .read_avail = bh1745_read_avail, +}; + +static irqreturn_t bh1745_interrupt_handler(int interrupt, void *p) +{ + struct iio_dev *indio_dev = p; + struct bh1745_data *data = iio_priv(indio_dev); + int ret; + int value; + int int_src; + + ret = regmap_read(data->regmap, BH1745_INTR, &value); + if (ret) + return IRQ_NONE; + + int_src = FIELD_GET(BH1745_INTR_SOURCE_MASK, value); + + if (value & BH1745_INTR_STATUS) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, int_src, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static irqreturn_t bh1745_trigger_handler(int interrupt, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bh1745_data *data = iio_priv(indio_dev); + struct { + u16 chans[4]; + aligned_s64 timestamp; + } scan = { }; + u16 value; + int ret; + int i; + int j = 0; + + iio_for_each_active_channel(indio_dev, i) { + ret = regmap_bulk_read(data->regmap, BH1745_RED_LSB + 2 * i, + &value, 2); + if (ret) + goto err; + + scan.chans[j++] = value; + } + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int bh1745_setup_triggered_buffer(struct iio_dev *indio_dev, + struct device *parent, + int irq) +{ + struct bh1745_data *data = iio_priv(indio_dev); + struct device *dev = data->dev; + int ret; + + ret = devm_iio_triggered_buffer_setup(parent, indio_dev, NULL, + bh1745_trigger_handler, NULL); + if (ret) + return dev_err_probe(dev, ret, + "Triggered buffer setup failed\n"); + + if (irq) { + ret = devm_request_threaded_irq(dev, irq, NULL, + bh1745_interrupt_handler, + IRQF_ONESHOT, + "bh1745_interrupt", indio_dev); + if (ret) + return ret; + } + + return 0; +} + +static int bh1745_init(struct bh1745_data *data) +{ + int ret; + struct device *dev = data->dev; + + mutex_init(&data->lock); + + ret = devm_iio_init_iio_gts(dev, BH1745_MAX_GAIN, 0, bh1745_gain, + ARRAY_SIZE(bh1745_gain), bh1745_itimes, + ARRAY_SIZE(bh1745_itimes), &data->gts); + if (ret) + return ret; + + ret = bh1745_reset(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to reset sensor\n"); + + ret = bh1745_power_on(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to turn on sensor\n"); + + ret = devm_add_action_or_reset(dev, bh1745_power_off, data); + if (ret) + return ret; + + return 0; +} + +static int bh1745_probe(struct i2c_client *client) +{ + int ret; + int value; + int part_id; + struct bh1745_data *data; + struct iio_dev *indio_dev; + struct device *dev = &client->dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->info = &bh1745_info; + indio_dev->name = "bh1745"; + indio_dev->channels = bh1745_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = ARRAY_SIZE(bh1745_channels); + data = iio_priv(indio_dev); + data->dev = &client->dev; + data->regmap = devm_regmap_init_i2c(client, &bh1745_regmap); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "Failed to initialize Regmap\n"); + + ret = regmap_read(data->regmap, BH1745_SYS_CTRL, &value); + if (ret) + return ret; + + part_id = FIELD_GET(BH1745_SYS_CTRL_PART_ID_MASK, value); + if (part_id != BH1745_PART_ID) + dev_warn(dev, "Unknown part ID 0x%x\n", part_id); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get and enable regulator\n"); + + ret = bh1745_init(data); + if (ret) + return ret; + + ret = bh1745_setup_triggered_buffer(indio_dev, indio_dev->dev.parent, + client->irq); + if (ret) + return ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to register device\n"); + + return 0; +} + +static const struct i2c_device_id bh1745_idtable[] = { + { .name = "bh1745" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bh1745_idtable); + +static const struct of_device_id bh1745_of_match[] = { + { .compatible = "rohm,bh1745" }, + { } +}; +MODULE_DEVICE_TABLE(of, bh1745_of_match); + +static struct i2c_driver bh1745_driver = { + .driver = { + .name = "bh1745", + .of_match_table = bh1745_of_match, + }, + .probe = bh1745_probe, + .id_table = bh1745_idtable, +}; +module_i2c_driver(bh1745_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mudit Sharma <muditsharma.info@gmail.com>"); +MODULE_DESCRIPTION("BH1745 colour sensor driver"); +MODULE_IMPORT_NS("IIO_GTS_HELPER"); diff --git a/drivers/iio/light/bh1750.c b/drivers/iio/light/bh1750.c new file mode 100644 index 000000000..a51ac98c8 --- /dev/null +++ b/drivers/iio/light/bh1750.c @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ROHM BH1710/BH1715/BH1721/BH1750/BH1751 ambient light sensor driver + * + * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com> + * + * Data sheets: + * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1710fvc-e.pdf + * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1715fvc-e.pdf + * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1721fvc-e.pdf + * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1750fvi-e.pdf + * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1751fvi-e.pdf + * + * 7-bit I2C slave addresses: + * 0x23 (ADDR pin low) + * 0x5C (ADDR pin high) + * + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/module.h> +#include <linux/gpio/consumer.h> + +#define BH1750_POWER_DOWN 0x00 +#define BH1750_ONE_TIME_H_RES_MODE 0x20 /* auto-mode for BH1721 */ +#define BH1750_CHANGE_INT_TIME_H_BIT 0x40 +#define BH1750_CHANGE_INT_TIME_L_BIT 0x60 + +/* Define the reset delay time in microseconds */ +#define BH1750_RESET_DELAY_US 10000 /* 10ms */ + +enum { + BH1710, + BH1721, + BH1750, +}; + +struct bh1750_chip_info; +struct bh1750_data { + struct i2c_client *client; + struct mutex lock; + const struct bh1750_chip_info *chip_info; + u16 mtreg; + struct gpio_desc *reset_gpio; +}; + +struct bh1750_chip_info { + u16 mtreg_min; + u16 mtreg_max; + u16 mtreg_default; + int mtreg_to_usec; + int mtreg_to_scale; + + /* + * For BH1710/BH1721 all possible integration time values won't fit + * into one page so displaying is limited to every second one. + * Note, that user can still write proper values which were not + * listed. + */ + int inc; + + u16 int_time_low_mask; + u16 int_time_high_mask; +}; + +static const struct bh1750_chip_info bh1750_chip_info_tbl[] = { + [BH1710] = { 140, 1022, 300, 400, 250000000, 2, 0x001F, 0x03E0 }, + [BH1721] = { 140, 1020, 300, 400, 250000000, 2, 0x0010, 0x03E0 }, + [BH1750] = { 31, 254, 69, 1740, 57500000, 1, 0x001F, 0x00E0 }, +}; + +static int bh1750_change_int_time(struct bh1750_data *data, int usec) +{ + int ret; + u16 val; + u8 regval; + const struct bh1750_chip_info *chip_info = data->chip_info; + + if ((usec % chip_info->mtreg_to_usec) != 0) + return -EINVAL; + + val = usec / chip_info->mtreg_to_usec; + if (val < chip_info->mtreg_min || val > chip_info->mtreg_max) + return -EINVAL; + + ret = i2c_smbus_write_byte(data->client, BH1750_POWER_DOWN); + if (ret < 0) + return ret; + + regval = (val & chip_info->int_time_high_mask) >> 5; + ret = i2c_smbus_write_byte(data->client, + BH1750_CHANGE_INT_TIME_H_BIT | regval); + if (ret < 0) + return ret; + + regval = val & chip_info->int_time_low_mask; + ret = i2c_smbus_write_byte(data->client, + BH1750_CHANGE_INT_TIME_L_BIT | regval); + if (ret < 0) + return ret; + + data->mtreg = val; + + return 0; +} + +static int bh1750_read(struct bh1750_data *data, int *val) +{ + int ret; + __be16 result; + const struct bh1750_chip_info *chip_info = data->chip_info; + unsigned long delay = chip_info->mtreg_to_usec * data->mtreg; + + /* + * BH1721 will enter continuous mode on receiving this command. + * Note, that this eliminates need for bh1750_resume(). + */ + ret = i2c_smbus_write_byte(data->client, BH1750_ONE_TIME_H_RES_MODE); + if (ret < 0) + return ret; + + usleep_range(delay + 15000, delay + 40000); + + ret = i2c_master_recv(data->client, (char *)&result, 2); + if (ret < 0) + return ret; + + *val = be16_to_cpu(result); + + return 0; +} + +static int bh1750_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret, tmp; + struct bh1750_data *data = iio_priv(indio_dev); + const struct bh1750_chip_info *chip_info = data->chip_info; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + mutex_lock(&data->lock); + ret = bh1750_read(data, val); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + tmp = chip_info->mtreg_to_scale / data->mtreg; + *val = tmp / 1000000; + *val2 = tmp % 1000000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = chip_info->mtreg_to_usec * data->mtreg; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int bh1750_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + struct bh1750_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + + mutex_lock(&data->lock); + ret = bh1750_change_int_time(data, val2); + mutex_unlock(&data->lock); + return ret; + default: + return -EINVAL; + } +} + +static ssize_t bh1750_show_int_time_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + size_t len = 0; + struct bh1750_data *data = iio_priv(dev_to_iio_dev(dev)); + const struct bh1750_chip_info *chip_info = data->chip_info; + + for (i = chip_info->mtreg_min; i <= chip_info->mtreg_max; i += chip_info->inc) + len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06d ", + chip_info->mtreg_to_usec * i); + + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEV_ATTR_INT_TIME_AVAIL(bh1750_show_int_time_available); + +static struct attribute *bh1750_attributes[] = { + &iio_dev_attr_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group bh1750_attribute_group = { + .attrs = bh1750_attributes, +}; + +static const struct iio_info bh1750_info = { + .attrs = &bh1750_attribute_group, + .read_raw = bh1750_read_raw, + .write_raw = bh1750_write_raw, +}; + +static const struct iio_chan_spec bh1750_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME) + } +}; + +static int bh1750_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + int ret, usec; + struct bh1750_data *data; + struct iio_dev *indio_dev; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C | + I2C_FUNC_SMBUS_WRITE_BYTE)) + return -EOPNOTSUPP; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->chip_info = &bh1750_chip_info_tbl[id->driver_data]; + + /* Get reset GPIO from device tree */ + data->reset_gpio = devm_gpiod_get_optional(&client->dev, + "reset", GPIOD_OUT_HIGH); + + if (IS_ERR(data->reset_gpio)) + return dev_err_probe(&client->dev, PTR_ERR(data->reset_gpio), + "Failed to get reset GPIO\n"); + + /* Perform hardware reset if GPIO is provided */ + if (data->reset_gpio) { + /* Perform reset sequence: low-high */ + gpiod_set_value_cansleep(data->reset_gpio, 1); + fsleep(BH1750_RESET_DELAY_US); + gpiod_set_value_cansleep(data->reset_gpio, 0); + fsleep(BH1750_RESET_DELAY_US); + + dev_dbg(&client->dev, "BH1750 reset completed via GPIO\n"); + } + + usec = data->chip_info->mtreg_to_usec * data->chip_info->mtreg_default; + ret = bh1750_change_int_time(data, usec); + if (ret < 0) + return ret; + + mutex_init(&data->lock); + indio_dev->info = &bh1750_info; + indio_dev->name = id->name; + indio_dev->channels = bh1750_channels; + indio_dev->num_channels = ARRAY_SIZE(bh1750_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + return iio_device_register(indio_dev); +} + +static void bh1750_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct bh1750_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + mutex_lock(&data->lock); + i2c_smbus_write_byte(client, BH1750_POWER_DOWN); + mutex_unlock(&data->lock); +} + +static int bh1750_suspend(struct device *dev) +{ + int ret; + struct bh1750_data *data = + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + /* + * This is mainly for BH1721 which doesn't enter power down + * mode automatically. + */ + mutex_lock(&data->lock); + ret = i2c_smbus_write_byte(data->client, BH1750_POWER_DOWN); + mutex_unlock(&data->lock); + + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(bh1750_pm_ops, bh1750_suspend, NULL); + +static const struct i2c_device_id bh1750_id[] = { + { .name = "bh1710", .driver_data = BH1710 }, + { .name = "bh1715", .driver_data = BH1750 }, + { .name = "bh1721", .driver_data = BH1721 }, + { .name = "bh1750", .driver_data = BH1750 }, + { .name = "bh1751", .driver_data = BH1750 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bh1750_id); + +static const struct of_device_id bh1750_of_match[] = { + { .compatible = "rohm,bh1710", }, + { .compatible = "rohm,bh1715", }, + { .compatible = "rohm,bh1721", }, + { .compatible = "rohm,bh1750", }, + { .compatible = "rohm,bh1751", }, + { } +}; +MODULE_DEVICE_TABLE(of, bh1750_of_match); + +static struct i2c_driver bh1750_driver = { + .driver = { + .name = "bh1750", + .of_match_table = bh1750_of_match, + .pm = pm_sleep_ptr(&bh1750_pm_ops), + }, + .probe = bh1750_probe, + .remove = bh1750_remove, + .id_table = bh1750_id, + +}; +module_i2c_driver(bh1750_driver); + +MODULE_AUTHOR("Tomasz Duszynski <tduszyns@gmail.com>"); +MODULE_DESCRIPTION("ROHM BH1710/BH1715/BH1721/BH1750/BH1751 als driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/bh1780.c b/drivers/iio/light/bh1780.c new file mode 100644 index 000000000..5447b990f --- /dev/null +++ b/drivers/iio/light/bh1780.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ROHM 1780GLI Ambient Light Sensor Driver + * + * Copyright (C) 2016 Linaro Ltd. + * Author: Linus Walleij <linus.walleij@linaro.org> + * Loosely based on the previous BH1780 ALS misc driver + * Copyright (C) 2010 Texas Instruments + * Author: Hemanth V <hemanthv@ti.com> + */ +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/bitops.h> + +#define BH1780_CMD_BIT BIT(7) +#define BH1780_REG_CONTROL 0x00 +#define BH1780_REG_PARTID 0x0A +#define BH1780_REG_MANFID 0x0B +#define BH1780_REG_DLOW 0x0C +#define BH1780_REG_DHIGH 0x0D + +#define BH1780_REVMASK GENMASK(3,0) +#define BH1780_POWMASK GENMASK(1,0) +#define BH1780_POFF (0x0) +#define BH1780_PON (0x3) + +/* power on settling time in ms */ +#define BH1780_PON_DELAY 2 +/* max time before value available in ms */ +#define BH1780_INTERVAL 250 + +struct bh1780_data { + struct i2c_client *client; +}; + +static int bh1780_write(struct bh1780_data *bh1780, u8 reg, u8 val) +{ + int ret = i2c_smbus_write_byte_data(bh1780->client, + BH1780_CMD_BIT | reg, + val); + if (ret < 0) + dev_err(&bh1780->client->dev, + "i2c_smbus_write_byte_data failed error " + "%d, register %01x\n", + ret, reg); + return ret; +} + +static int bh1780_read(struct bh1780_data *bh1780, u8 reg) +{ + int ret = i2c_smbus_read_byte_data(bh1780->client, + BH1780_CMD_BIT | reg); + if (ret < 0) + dev_err(&bh1780->client->dev, + "i2c_smbus_read_byte_data failed error " + "%d, register %01x\n", + ret, reg); + return ret; +} + +static int bh1780_read_word(struct bh1780_data *bh1780, u8 reg) +{ + int ret = i2c_smbus_read_word_data(bh1780->client, + BH1780_CMD_BIT | reg); + if (ret < 0) + dev_err(&bh1780->client->dev, + "i2c_smbus_read_word_data failed error " + "%d, register %01x\n", + ret, reg); + return ret; +} + +static int bh1780_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct bh1780_data *bh1780 = iio_priv(indio_dev); + int ret; + + if (!readval) + return bh1780_write(bh1780, (u8)reg, (u8)writeval); + + ret = bh1780_read(bh1780, (u8)reg); + if (ret < 0) + return ret; + + *readval = ret; + + return 0; +} + +static int bh1780_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bh1780_data *bh1780 = iio_priv(indio_dev); + int value; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + pm_runtime_get_sync(&bh1780->client->dev); + value = bh1780_read_word(bh1780, BH1780_REG_DLOW); + pm_runtime_put_autosuspend(&bh1780->client->dev); + if (value < 0) + return value; + *val = value; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = BH1780_INTERVAL * 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_info bh1780_info = { + .read_raw = bh1780_read_raw, + .debugfs_reg_access = bh1780_debugfs_reg_access, +}; + +static const struct iio_chan_spec bh1780_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) + } +}; + +static int bh1780_probe(struct i2c_client *client) +{ + int ret; + struct bh1780_data *bh1780; + struct i2c_adapter *adapter = client->adapter; + struct iio_dev *indio_dev; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*bh1780)); + if (!indio_dev) + return -ENOMEM; + + bh1780 = iio_priv(indio_dev); + bh1780->client = client; + i2c_set_clientdata(client, indio_dev); + + /* Power up the device */ + ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_PON); + if (ret < 0) + return ret; + msleep(BH1780_PON_DELAY); + pm_runtime_get_noresume(&client->dev); + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + + ret = bh1780_read(bh1780, BH1780_REG_PARTID); + if (ret < 0) + goto out_disable_pm; + dev_info(&client->dev, + "Ambient Light Sensor, Rev : %lu\n", + (ret & BH1780_REVMASK)); + + /* + * As the device takes 250 ms to even come up with a fresh + * measurement after power-on, do not shut it down unnecessarily. + * Set autosuspend to a five seconds. + */ + pm_runtime_set_autosuspend_delay(&client->dev, 5000); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_put(&client->dev); + + indio_dev->info = &bh1780_info; + indio_dev->name = "bh1780"; + indio_dev->channels = bh1780_channels; + indio_dev->num_channels = ARRAY_SIZE(bh1780_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret) + goto out_disable_pm; + return 0; + +out_disable_pm: + pm_runtime_put_noidle(&client->dev); + pm_runtime_disable(&client->dev); + return ret; +} + +static void bh1780_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct bh1780_data *bh1780 = iio_priv(indio_dev); + int ret; + + iio_device_unregister(indio_dev); + pm_runtime_get_sync(&client->dev); + pm_runtime_put_noidle(&client->dev); + pm_runtime_disable(&client->dev); + ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_POFF); + if (ret < 0) + dev_err(&client->dev, "failed to power off (%pe)\n", + ERR_PTR(ret)); +} + +static int bh1780_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct bh1780_data *bh1780 = iio_priv(indio_dev); + int ret; + + ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_POFF); + if (ret < 0) { + dev_err(dev, "failed to runtime suspend\n"); + return ret; + } + + return 0; +} + +static int bh1780_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct bh1780_data *bh1780 = iio_priv(indio_dev); + int ret; + + ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_PON); + if (ret < 0) { + dev_err(dev, "failed to runtime resume\n"); + return ret; + } + + /* Wait for power on, then for a value to be available */ + msleep(BH1780_PON_DELAY + BH1780_INTERVAL); + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(bh1780_dev_pm_ops, bh1780_runtime_suspend, + bh1780_runtime_resume, NULL); + +static const struct i2c_device_id bh1780_id[] = { + { .name = "bh1780" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, bh1780_id); + +static const struct of_device_id of_bh1780_match[] = { + { .compatible = "rohm,bh1780gli", }, + { } +}; +MODULE_DEVICE_TABLE(of, of_bh1780_match); + +static struct i2c_driver bh1780_driver = { + .probe = bh1780_probe, + .remove = bh1780_remove, + .id_table = bh1780_id, + .driver = { + .name = "bh1780", + .pm = pm_ptr(&bh1780_dev_pm_ops), + .of_match_table = of_bh1780_match, + }, +}; + +module_i2c_driver(bh1780_driver); + +MODULE_DESCRIPTION("ROHM BH1780GLI Ambient Light Sensor Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); diff --git a/drivers/iio/light/cm32181.c b/drivers/iio/light/cm32181.c new file mode 100644 index 000000000..b32a94028 --- /dev/null +++ b/drivers/iio/light/cm32181.c @@ -0,0 +1,550 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Capella Microsystems Inc. + * Author: Kevin Tsai <ktsai@capellamicro.com> + */ + +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/regulator/consumer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/init.h> + +/* Registers Address */ +#define CM32181_REG_ADDR_CMD 0x00 +#define CM32181_REG_ADDR_WH 0x01 +#define CM32181_REG_ADDR_WL 0x02 +#define CM32181_REG_ADDR_TEST 0x03 +#define CM32181_REG_ADDR_ALS 0x04 +#define CM32181_REG_ADDR_STATUS 0x06 +#define CM32181_REG_ADDR_ID 0x07 + +/* Number of Configurable Registers */ +#define CM32181_CONF_REG_NUM 4 + +/* CMD register */ +#define CM32181_CMD_ALS_DISABLE BIT(0) +#define CM32181_CMD_ALS_INT_EN BIT(1) +#define CM32181_CMD_ALS_THRES_WINDOW BIT(2) + +#define CM32181_CMD_ALS_PERS_SHIFT 4 +#define CM32181_CMD_ALS_PERS_MASK (0x03 << CM32181_CMD_ALS_PERS_SHIFT) +#define CM32181_CMD_ALS_PERS_DEFAULT (0x01 << CM32181_CMD_ALS_PERS_SHIFT) + +#define CM32181_CMD_ALS_IT_SHIFT 6 +#define CM32181_CMD_ALS_IT_MASK (0x0F << CM32181_CMD_ALS_IT_SHIFT) +#define CM32181_CMD_ALS_IT_DEFAULT (0x00 << CM32181_CMD_ALS_IT_SHIFT) + +#define CM32181_CMD_ALS_SM_SHIFT 11 +#define CM32181_CMD_ALS_SM_MASK (0x03 << CM32181_CMD_ALS_SM_SHIFT) +#define CM32181_CMD_ALS_SM_DEFAULT (0x01 << CM32181_CMD_ALS_SM_SHIFT) + +#define CM32181_LUX_PER_BIT 500 /* ALS_SM=01 IT=800ms */ +#define CM32181_LUX_PER_BIT_RESOLUTION 100000 +#define CM32181_LUX_PER_BIT_BASE_IT 800000 /* Based on IT=800ms */ +#define CM32181_CALIBSCALE_DEFAULT 100000 +#define CM32181_CALIBSCALE_RESOLUTION 100000 + +#define SMBUS_ALERT_RESPONSE_ADDRESS 0x0c + +/* CPM0 Index 0: device-id (3218 or 32181), 1: Unknown, 2: init_regs_bitmap */ +#define CPM0_REGS_BITMAP 2 +#define CPM0_HEADER_SIZE 3 + +/* CPM1 Index 0: lux_per_bit, 1: calibscale, 2: resolution (100000) */ +#define CPM1_LUX_PER_BIT 0 +#define CPM1_CALIBSCALE 1 +#define CPM1_SIZE 3 + +/* CM3218 Family */ +static const int cm3218_als_it_bits[] = { 0, 1, 2, 3 }; +static const int cm3218_als_it_values[] = { 100000, 200000, 400000, 800000 }; + +/* CM32181 Family */ +static const int cm32181_als_it_bits[] = { 12, 8, 0, 1, 2, 3 }; +static const int cm32181_als_it_values[] = { + 25000, 50000, 100000, 200000, 400000, 800000 +}; + +struct cm32181_chip { + struct i2c_client *client; + struct device *dev; + struct mutex lock; + u16 conf_regs[CM32181_CONF_REG_NUM]; + unsigned long init_regs_bitmap; + int calibscale; + int lux_per_bit; + int lux_per_bit_base_it; + int num_als_it; + const int *als_it_bits; + const int *als_it_values; +}; + +static int cm32181_read_als_it(struct cm32181_chip *cm32181, int *val2); + +#ifdef CONFIG_ACPI +/** + * cm32181_acpi_get_cpm() - Get CPM object from ACPI + * @dev: pointer of struct device. + * @obj_name: pointer of ACPI object name. + * @values: pointer of array for return elements. + * @count: maximum size of return array. + * + * Convert ACPI CPM table to array. + * + * Return: -ENODEV for fail. Otherwise is number of elements. + */ +static int cm32181_acpi_get_cpm(struct device *dev, char *obj_name, + u64 *values, int count) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *cpm, *elem; + acpi_handle handle; + acpi_status status; + int i; + + handle = ACPI_HANDLE(dev); + if (!handle) + return -ENODEV; + + status = acpi_evaluate_object(handle, obj_name, NULL, &buffer); + if (ACPI_FAILURE(status)) { + dev_err(dev, "object %s not found\n", obj_name); + return -ENODEV; + } + + cpm = buffer.pointer; + if (cpm->package.count > count) + dev_warn(dev, "%s table contains %u values, only using first %d values\n", + obj_name, cpm->package.count, count); + + count = min_t(int, cpm->package.count, count); + for (i = 0; i < count; i++) { + elem = &(cpm->package.elements[i]); + values[i] = elem->integer.value; + } + + kfree(buffer.pointer); + + return count; +} + +static void cm32181_acpi_parse_cpm_tables(struct cm32181_chip *cm32181) +{ + u64 vals[CPM0_HEADER_SIZE + CM32181_CONF_REG_NUM]; + struct device *dev = cm32181->dev; + int i, count; + + count = cm32181_acpi_get_cpm(dev, "CPM0", vals, ARRAY_SIZE(vals)); + if (count <= CPM0_HEADER_SIZE) + return; + + count -= CPM0_HEADER_SIZE; + + cm32181->init_regs_bitmap = vals[CPM0_REGS_BITMAP]; + cm32181->init_regs_bitmap &= GENMASK(count - 1, 0); + for_each_set_bit(i, &cm32181->init_regs_bitmap, count) + cm32181->conf_regs[i] = vals[CPM0_HEADER_SIZE + i]; + + count = cm32181_acpi_get_cpm(dev, "CPM1", vals, ARRAY_SIZE(vals)); + if (count != CPM1_SIZE) + return; + + cm32181->lux_per_bit = vals[CPM1_LUX_PER_BIT]; + + /* Check for uncalibrated devices */ + if (vals[CPM1_CALIBSCALE] == CM32181_CALIBSCALE_DEFAULT) + return; + + cm32181->calibscale = vals[CPM1_CALIBSCALE]; + /* CPM1 lux_per_bit is for the current it value */ + cm32181_read_als_it(cm32181, &cm32181->lux_per_bit_base_it); +} +#else +static void cm32181_acpi_parse_cpm_tables(struct cm32181_chip *cm32181) +{ +} +#endif /* CONFIG_ACPI */ + +/** + * cm32181_reg_init() - Initialize CM32181 registers + * @cm32181: pointer of struct cm32181. + * + * Initialize CM32181 ambient light sensor register to default values. + * + * Return: 0 for success; otherwise for error code. + */ +static int cm32181_reg_init(struct cm32181_chip *cm32181) +{ + struct i2c_client *client = cm32181->client; + int i; + s32 ret; + + ret = i2c_smbus_read_word_data(client, CM32181_REG_ADDR_ID); + if (ret < 0) + return ret; + + /* check device ID */ + switch (ret & 0xFF) { + case 0x18: /* CM3218 */ + cm32181->num_als_it = ARRAY_SIZE(cm3218_als_it_bits); + cm32181->als_it_bits = cm3218_als_it_bits; + cm32181->als_it_values = cm3218_als_it_values; + break; + case 0x81: /* CM32181 */ + case 0x82: /* CM32182, fully compat. with CM32181 */ + cm32181->num_als_it = ARRAY_SIZE(cm32181_als_it_bits); + cm32181->als_it_bits = cm32181_als_it_bits; + cm32181->als_it_values = cm32181_als_it_values; + break; + default: + return -ENODEV; + } + + /* Default Values */ + cm32181->conf_regs[CM32181_REG_ADDR_CMD] = + CM32181_CMD_ALS_IT_DEFAULT | CM32181_CMD_ALS_SM_DEFAULT; + cm32181->init_regs_bitmap = BIT(CM32181_REG_ADDR_CMD); + cm32181->calibscale = CM32181_CALIBSCALE_DEFAULT; + cm32181->lux_per_bit = CM32181_LUX_PER_BIT; + cm32181->lux_per_bit_base_it = CM32181_LUX_PER_BIT_BASE_IT; + + cm32181_acpi_parse_cpm_tables(cm32181); + + /* Initialize registers*/ + for_each_set_bit(i, &cm32181->init_regs_bitmap, CM32181_CONF_REG_NUM) { + ret = i2c_smbus_write_word_data(client, i, + cm32181->conf_regs[i]); + if (ret < 0) + return ret; + } + + return 0; +} + +/** + * cm32181_read_als_it() - Get sensor integration time (ms) + * @cm32181: pointer of struct cm32181 + * @val2: pointer of int to load the als_it value. + * + * Report the current integration time in milliseconds. + * + * Return: IIO_VAL_INT_PLUS_MICRO for success, otherwise -EINVAL. + */ +static int cm32181_read_als_it(struct cm32181_chip *cm32181, int *val2) +{ + u16 als_it; + int i; + + als_it = cm32181->conf_regs[CM32181_REG_ADDR_CMD]; + als_it &= CM32181_CMD_ALS_IT_MASK; + als_it >>= CM32181_CMD_ALS_IT_SHIFT; + for (i = 0; i < cm32181->num_als_it; i++) { + if (als_it == cm32181->als_it_bits[i]) { + *val2 = cm32181->als_it_values[i]; + return IIO_VAL_INT_PLUS_MICRO; + } + } + + return -EINVAL; +} + +/** + * cm32181_write_als_it() - Write sensor integration time + * @cm32181: pointer of struct cm32181. + * @val: integration time by millisecond. + * + * Convert integration time (ms) to sensor value. + * + * Return: i2c_smbus_write_word_data command return value. + */ +static int cm32181_write_als_it(struct cm32181_chip *cm32181, int val) +{ + struct i2c_client *client = cm32181->client; + u16 als_it; + int ret, i, n; + + n = cm32181->num_als_it; + for (i = 0; i < n; i++) + if (val <= cm32181->als_it_values[i]) + break; + if (i >= n) + i = n - 1; + + als_it = cm32181->als_it_bits[i]; + als_it <<= CM32181_CMD_ALS_IT_SHIFT; + + mutex_lock(&cm32181->lock); + cm32181->conf_regs[CM32181_REG_ADDR_CMD] &= + ~CM32181_CMD_ALS_IT_MASK; + cm32181->conf_regs[CM32181_REG_ADDR_CMD] |= + als_it; + ret = i2c_smbus_write_word_data(client, CM32181_REG_ADDR_CMD, + cm32181->conf_regs[CM32181_REG_ADDR_CMD]); + mutex_unlock(&cm32181->lock); + + return ret; +} + +/** + * cm32181_get_lux() - report current lux value + * @cm32181: pointer of struct cm32181. + * + * Convert sensor raw data to lux. It depends on integration + * time and calibscale variable. + * + * Return: Positive value is lux, otherwise is error code. + */ +static int cm32181_get_lux(struct cm32181_chip *cm32181) +{ + struct i2c_client *client = cm32181->client; + int ret; + int als_it; + u64 lux; + + ret = cm32181_read_als_it(cm32181, &als_it); + if (ret < 0) + return -EINVAL; + + lux = cm32181->lux_per_bit; + lux *= cm32181->lux_per_bit_base_it; + lux = div_u64(lux, als_it); + + ret = i2c_smbus_read_word_data(client, CM32181_REG_ADDR_ALS); + if (ret < 0) + return ret; + + lux *= ret; + lux *= cm32181->calibscale; + lux = div_u64(lux, CM32181_CALIBSCALE_RESOLUTION); + lux = div_u64(lux, CM32181_LUX_PER_BIT_RESOLUTION); + + if (lux > 0xFFFF) + lux = 0xFFFF; + + return lux; +} + +static int cm32181_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cm32181_chip *cm32181 = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = cm32181_get_lux(cm32181); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + *val = cm32181->calibscale; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + ret = cm32181_read_als_it(cm32181, val2); + return ret; + } + + return -EINVAL; +} + +static int cm32181_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cm32181_chip *cm32181 = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + cm32181->calibscale = val; + return 0; + case IIO_CHAN_INFO_INT_TIME: + ret = cm32181_write_als_it(cm32181, val2); + return ret; + } + + return -EINVAL; +} + +/** + * cm32181_get_it_available() - Get available ALS IT value + * @dev: pointer of struct device. + * @attr: pointer of struct device_attribute. + * @buf: pointer of return string buffer. + * + * Display the available integration time values by millisecond. + * + * Return: string length. + */ +static ssize_t cm32181_get_it_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cm32181_chip *cm32181 = iio_priv(dev_to_iio_dev(dev)); + int i, n, len; + + n = cm32181->num_als_it; + for (i = 0, len = 0; i < n; i++) + len += sprintf(buf + len, "0.%06u ", cm32181->als_it_values[i]); + return len + sprintf(buf + len, "\n"); +} + +static const struct iio_chan_spec cm32181_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = + BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + } +}; + +static IIO_DEVICE_ATTR(in_illuminance_integration_time_available, + S_IRUGO, cm32181_get_it_available, NULL, 0); + +static struct attribute *cm32181_attributes[] = { + &iio_dev_attr_in_illuminance_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group cm32181_attribute_group = { + .attrs = cm32181_attributes +}; + +static const struct iio_info cm32181_info = { + .read_raw = &cm32181_read_raw, + .write_raw = &cm32181_write_raw, + .attrs = &cm32181_attribute_group, +}; + +static void cm32181_unregister_dummy_client(void *data) +{ + struct i2c_client *client = data; + + /* Unregister the dummy client */ + i2c_unregister_device(client); +} + +static int cm32181_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct cm32181_chip *cm32181; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*cm32181)); + if (!indio_dev) + return -ENOMEM; + + i2c_set_clientdata(client, indio_dev); + + /* + * Some ACPI systems list 2 I2C resources for the CM3218 sensor, the + * SMBus Alert Response Address (ARA, 0x0c) and the actual I2C address. + * Detect this and take the following step to deal with it: + * 1. When a SMBus Alert capable sensor has an Alert asserted, it will + * not respond on its actual I2C address. Read a byte from the ARA + * to clear any pending Alerts. + * 2. Create a "dummy" client for the actual I2C address and + * use that client to communicate with the sensor. + */ + if (ACPI_HANDLE(dev) && client->addr == SMBUS_ALERT_RESPONSE_ADDRESS) { + struct i2c_board_info board_info = { .type = "dummy" }; + + i2c_smbus_read_byte(client); + + client = i2c_acpi_new_device(dev, 1, &board_info); + if (IS_ERR(client)) + return PTR_ERR(client); + + ret = devm_add_action_or_reset(dev, cm32181_unregister_dummy_client, client); + if (ret) + return ret; + } + + cm32181 = iio_priv(indio_dev); + cm32181->client = client; + cm32181->dev = dev; + + mutex_init(&cm32181->lock); + indio_dev->channels = cm32181_channels; + indio_dev->num_channels = ARRAY_SIZE(cm32181_channels); + indio_dev->info = &cm32181_info; + indio_dev->name = dev_name(dev); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = cm32181_reg_init(cm32181); + if (ret) { + dev_err(dev, "%s: register init failed\n", __func__); + return ret; + } + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) { + dev_err(dev, "%s: register device failed\n", __func__); + return ret; + } + + return 0; +} + +static int cm32181_suspend(struct device *dev) +{ + struct cm32181_chip *cm32181 = iio_priv(dev_get_drvdata(dev)); + struct i2c_client *client = cm32181->client; + + return i2c_smbus_write_word_data(client, CM32181_REG_ADDR_CMD, + CM32181_CMD_ALS_DISABLE); +} + +static int cm32181_resume(struct device *dev) +{ + struct cm32181_chip *cm32181 = iio_priv(dev_get_drvdata(dev)); + struct i2c_client *client = cm32181->client; + + return i2c_smbus_write_word_data(client, CM32181_REG_ADDR_CMD, + cm32181->conf_regs[CM32181_REG_ADDR_CMD]); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(cm32181_pm_ops, cm32181_suspend, cm32181_resume); + +static const struct of_device_id cm32181_of_match[] = { + { .compatible = "capella,cm3218" }, + { .compatible = "capella,cm32181" }, + { } +}; +MODULE_DEVICE_TABLE(of, cm32181_of_match); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id cm32181_acpi_match[] = { + { "CPLM3218", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, cm32181_acpi_match); +#endif + +static struct i2c_driver cm32181_driver = { + .driver = { + .name = "cm32181", + .acpi_match_table = ACPI_PTR(cm32181_acpi_match), + .of_match_table = cm32181_of_match, + .pm = pm_sleep_ptr(&cm32181_pm_ops), + }, + .probe = cm32181_probe, +}; + +module_i2c_driver(cm32181_driver); + +MODULE_AUTHOR("Kevin Tsai <ktsai@capellamicro.com>"); +MODULE_DESCRIPTION("CM32181 ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/cm3232.c b/drivers/iio/light/cm3232.c new file mode 100644 index 000000000..5f2545263 --- /dev/null +++ b/drivers/iio/light/cm3232.c @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * CM3232 Ambient Light Sensor + * + * Copyright (C) 2014-2015 Capella Microsystems Inc. + * Author: Kevin Tsai <ktsai@capellamicro.com> + * + * IIO driver for CM3232 (7-bit I2C slave address 0x10). + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/init.h> + +/* Registers Address */ +#define CM3232_REG_ADDR_CMD 0x00 +#define CM3232_REG_ADDR_ALS 0x50 +#define CM3232_REG_ADDR_ID 0x53 + +#define CM3232_CMD_ALS_DISABLE BIT(0) + +#define CM3232_CMD_ALS_IT_SHIFT 2 +#define CM3232_CMD_ALS_IT_MASK (BIT(2) | BIT(3) | BIT(4)) +#define CM3232_CMD_ALS_IT_DEFAULT (0x01 << CM3232_CMD_ALS_IT_SHIFT) + +#define CM3232_CMD_ALS_RESET BIT(6) + +#define CM3232_CMD_DEFAULT CM3232_CMD_ALS_IT_DEFAULT + +#define CM3232_HW_ID 0x32 +#define CM3232_CALIBSCALE_DEFAULT 100000 +#define CM3232_CALIBSCALE_RESOLUTION 100000 +#define CM3232_MLUX_PER_LUX 1000 + +#define CM3232_MLUX_PER_BIT_DEFAULT 64 +#define CM3232_MLUX_PER_BIT_BASE_IT 100000 + +static const struct { + int val; + int val2; + u8 it; +} cm3232_als_it_scales[] = { + {0, 100000, 0}, /* 0.100000 */ + {0, 200000, 1}, /* 0.200000 */ + {0, 400000, 2}, /* 0.400000 */ + {0, 800000, 3}, /* 0.800000 */ + {1, 600000, 4}, /* 1.600000 */ + {3, 200000, 5}, /* 3.200000 */ +}; + +struct cm3232_als_info { + u8 regs_cmd_default; + u8 hw_id; + int mlux_per_bit; + int mlux_per_bit_base_it; +}; + +static const struct cm3232_als_info cm3232_als_info_default = { + .regs_cmd_default = CM3232_CMD_DEFAULT, + .hw_id = CM3232_HW_ID, + .mlux_per_bit = CM3232_MLUX_PER_BIT_DEFAULT, + .mlux_per_bit_base_it = CM3232_MLUX_PER_BIT_BASE_IT, +}; + +struct cm3232_chip { + struct i2c_client *client; + const struct cm3232_als_info *als_info; + int calibscale; + u8 regs_cmd; + u16 regs_als; +}; + +/** + * cm3232_reg_init() - Initialize CM3232 + * @chip: pointer of struct cm3232_chip. + * + * Check and initialize CM3232 ambient light sensor. + * + * Return: 0 for success; otherwise for error code. + */ +static int cm3232_reg_init(struct cm3232_chip *chip) +{ + struct i2c_client *client = chip->client; + s32 ret; + + chip->als_info = &cm3232_als_info_default; + + /* Disable and reset device */ + chip->regs_cmd = CM3232_CMD_ALS_DISABLE | CM3232_CMD_ALS_RESET; + ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + chip->regs_cmd); + if (ret < 0) { + dev_err(&chip->client->dev, "Error writing reg_cmd\n"); + return ret; + } + + /* Identify device */ + ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ID); + if (ret < 0) { + dev_err(&chip->client->dev, "Error reading addr_id\n"); + return ret; + } + + if ((ret & 0xFF) != chip->als_info->hw_id) + return -ENODEV; + + /* Register default value */ + chip->regs_cmd = chip->als_info->regs_cmd_default; + + /* Configure register */ + ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + chip->regs_cmd); + if (ret < 0) + dev_err(&chip->client->dev, "Error writing reg_cmd\n"); + + return ret; +} + +/** + * cm3232_read_als_it() - Get sensor integration time + * @chip: pointer of struct cm3232_chip + * @val: pointer of int to load the integration (sec). + * @val2: pointer of int to load the integration time (microsecond). + * + * Report the current integration time. + * + * Return: IIO_VAL_INT_PLUS_MICRO for success, otherwise -EINVAL. + */ +static int cm3232_read_als_it(struct cm3232_chip *chip, int *val, int *val2) +{ + u16 als_it; + int i; + + als_it = chip->regs_cmd; + als_it &= CM3232_CMD_ALS_IT_MASK; + als_it >>= CM3232_CMD_ALS_IT_SHIFT; + for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) { + if (als_it == cm3232_als_it_scales[i].it) { + *val = cm3232_als_it_scales[i].val; + *val2 = cm3232_als_it_scales[i].val2; + return IIO_VAL_INT_PLUS_MICRO; + } + } + + return -EINVAL; +} + +/** + * cm3232_write_als_it() - Write sensor integration time + * @chip: pointer of struct cm3232_chip. + * @val: integration time in second. + * @val2: integration time in microsecond. + * + * Convert integration time to sensor value. + * + * Return: i2c_smbus_write_byte_data command return value. + */ +static int cm3232_write_als_it(struct cm3232_chip *chip, int val, int val2) +{ + struct i2c_client *client = chip->client; + u16 als_it, cmd; + int i; + s32 ret; + + for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) { + if (val == cm3232_als_it_scales[i].val && + val2 == cm3232_als_it_scales[i].val2) { + + als_it = cm3232_als_it_scales[i].it; + als_it <<= CM3232_CMD_ALS_IT_SHIFT; + + cmd = chip->regs_cmd & ~CM3232_CMD_ALS_IT_MASK; + cmd |= als_it; + ret = i2c_smbus_write_byte_data(client, + CM3232_REG_ADDR_CMD, + cmd); + if (ret < 0) + return ret; + chip->regs_cmd = cmd; + return 0; + } + } + return -EINVAL; +} + +/** + * cm3232_get_lux() - report current lux value + * @chip: pointer of struct cm3232_chip. + * + * Convert sensor data to lux. It depends on integration + * time and calibscale variable. + * + * Return: Zero or positive value is lux, otherwise error code. + */ +static int cm3232_get_lux(struct cm3232_chip *chip) +{ + struct i2c_client *client = chip->client; + const struct cm3232_als_info *als_info = chip->als_info; + int ret; + int val, val2; + int als_it; + u64 lux; + + /* Calculate mlux per bit based on als_it */ + ret = cm3232_read_als_it(chip, &val, &val2); + if (ret < 0) + return -EINVAL; + als_it = val * 1000000 + val2; + lux = (__force u64)als_info->mlux_per_bit; + lux *= als_info->mlux_per_bit_base_it; + lux = div_u64(lux, als_it); + + ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ALS); + if (ret < 0) { + dev_err(&client->dev, "Error reading reg_addr_als\n"); + return ret; + } + + chip->regs_als = (u16)ret; + lux *= chip->regs_als; + lux *= chip->calibscale; + lux = div_u64(lux, CM3232_CALIBSCALE_RESOLUTION); + lux = div_u64(lux, CM3232_MLUX_PER_LUX); + + if (lux > 0xFFFF) + lux = 0xFFFF; + + return (int)lux; +} + +static int cm3232_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cm3232_chip *chip = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = cm3232_get_lux(chip); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + *val = chip->calibscale; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + return cm3232_read_als_it(chip, val, val2); + } + + return -EINVAL; +} + +static int cm3232_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cm3232_chip *chip = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + chip->calibscale = val; + return 0; + case IIO_CHAN_INFO_INT_TIME: + return cm3232_write_als_it(chip, val, val2); + } + + return -EINVAL; +} + +/** + * cm3232_get_it_available() - Get available ALS IT value + * @dev: pointer of struct device. + * @attr: pointer of struct device_attribute. + * @buf: pointer of return string buffer. + * + * Display the available integration time in second. + * + * Return: string length. + */ +static ssize_t cm3232_get_it_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, len; + + for (i = 0, len = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%u.%06u ", + cm3232_als_it_scales[i].val, + cm3232_als_it_scales[i].val2); + return len + scnprintf(buf + len, PAGE_SIZE - len, "\n"); +} + +static const struct iio_chan_spec cm3232_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = + BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + } +}; + +static IIO_DEVICE_ATTR(in_illuminance_integration_time_available, + S_IRUGO, cm3232_get_it_available, NULL, 0); + +static struct attribute *cm3232_attributes[] = { + &iio_dev_attr_in_illuminance_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group cm3232_attribute_group = { + .attrs = cm3232_attributes +}; + +static const struct iio_info cm3232_info = { + .read_raw = &cm3232_read_raw, + .write_raw = &cm3232_write_raw, + .attrs = &cm3232_attribute_group, +}; + +static int cm3232_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct cm3232_chip *chip; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + chip->client = client; + chip->calibscale = CM3232_CALIBSCALE_DEFAULT; + + indio_dev->channels = cm3232_channels; + indio_dev->num_channels = ARRAY_SIZE(cm3232_channels); + indio_dev->info = &cm3232_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = cm3232_reg_init(chip); + if (ret) { + dev_err(&client->dev, + "%s: register init failed\n", + __func__); + return ret; + } + + return iio_device_register(indio_dev); +} + +static void cm3232_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + CM3232_CMD_ALS_DISABLE); + + iio_device_unregister(indio_dev); +} + +static const struct i2c_device_id cm3232_id[] = { + { .name = "cm3232" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cm3232_id); + +static int cm3232_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct cm3232_chip *chip = iio_priv(indio_dev); + struct i2c_client *client = chip->client; + int ret; + + chip->regs_cmd |= CM3232_CMD_ALS_DISABLE; + ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + chip->regs_cmd); + + return ret; +} + +static int cm3232_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct cm3232_chip *chip = iio_priv(indio_dev); + struct i2c_client *client = chip->client; + int ret; + + chip->regs_cmd &= ~CM3232_CMD_ALS_DISABLE; + ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + chip->regs_cmd | CM3232_CMD_ALS_RESET); + + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(cm3232_pm_ops, cm3232_suspend, cm3232_resume); + +static const struct of_device_id cm3232_of_match[] = { + {.compatible = "capella,cm3232"}, + { } +}; +MODULE_DEVICE_TABLE(of, cm3232_of_match); + +static struct i2c_driver cm3232_driver = { + .driver = { + .name = "cm3232", + .of_match_table = cm3232_of_match, + .pm = pm_sleep_ptr(&cm3232_pm_ops), + }, + .id_table = cm3232_id, + .probe = cm3232_probe, + .remove = cm3232_remove, +}; + +module_i2c_driver(cm3232_driver); + +MODULE_AUTHOR("Kevin Tsai <ktsai@capellamicro.com>"); +MODULE_DESCRIPTION("CM3232 ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/cm3323.c b/drivers/iio/light/cm3323.c new file mode 100644 index 000000000..506c91f44 --- /dev/null +++ b/drivers/iio/light/cm3323.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * CM3323 - Capella Color Light Sensor + * + * Copyright (c) 2015, Intel Corporation. + * + * IIO driver for CM3323 (7-bit I2C slave address 0x10) + * + * TODO: calibscale to correct the lens factor + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/mutex.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define CM3323_DRV_NAME "cm3323" + +#define CM3323_CMD_CONF 0x00 +#define CM3323_CMD_RED_DATA 0x08 +#define CM3323_CMD_GREEN_DATA 0x09 +#define CM3323_CMD_BLUE_DATA 0x0A +#define CM3323_CMD_CLEAR_DATA 0x0B + +#define CM3323_CONF_SD_BIT BIT(0) /* sensor disable */ +#define CM3323_CONF_AF_BIT BIT(1) /* auto/manual force mode */ +#define CM3323_CONF_IT_MASK GENMASK(6, 4) +#define CM3323_CONF_IT_SHIFT 4 + +#define CM3323_INT_TIME_AVAILABLE "0.04 0.08 0.16 0.32 0.64 1.28" + +static const struct { + int val; + int val2; +} cm3323_int_time[] = { + {0, 40000}, /* 40 ms */ + {0, 80000}, /* 80 ms */ + {0, 160000}, /* 160 ms */ + {0, 320000}, /* 320 ms */ + {0, 640000}, /* 640 ms */ + {1, 280000}, /* 1280 ms */ +}; + +struct cm3323_data { + struct i2c_client *client; + u16 reg_conf; + struct mutex mutex; +}; + +#define CM3323_COLOR_CHANNEL(_color, _addr) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), \ + .channel2 = IIO_MOD_LIGHT_##_color, \ + .address = _addr, \ +} + +static const struct iio_chan_spec cm3323_channels[] = { + CM3323_COLOR_CHANNEL(RED, CM3323_CMD_RED_DATA), + CM3323_COLOR_CHANNEL(GREEN, CM3323_CMD_GREEN_DATA), + CM3323_COLOR_CHANNEL(BLUE, CM3323_CMD_BLUE_DATA), + CM3323_COLOR_CHANNEL(CLEAR, CM3323_CMD_CLEAR_DATA), +}; + +static IIO_CONST_ATTR_INT_TIME_AVAIL(CM3323_INT_TIME_AVAILABLE); + +static struct attribute *cm3323_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group cm3323_attribute_group = { + .attrs = cm3323_attributes, +}; + +static int cm3323_init(struct iio_dev *indio_dev) +{ + int ret; + struct cm3323_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + + ret = i2c_smbus_read_word_data(data->client, CM3323_CMD_CONF); + if (ret < 0) + return dev_err_probe(dev, ret, "Error reading reg_conf\n"); + + /* enable sensor and set auto force mode */ + ret &= ~(CM3323_CONF_SD_BIT | CM3323_CONF_AF_BIT); + data->reg_conf = ret; + + ret = i2c_smbus_write_word_data(data->client, CM3323_CMD_CONF, data->reg_conf); + if (ret < 0) + return dev_err_probe(dev, ret, "Error writing reg_conf\n"); + + return 0; +} + +static void cm3323_disable(void *data) +{ + int ret; + struct iio_dev *indio_dev = data; + struct cm3323_data *cm_data = iio_priv(indio_dev); + + ret = i2c_smbus_write_word_data(cm_data->client, CM3323_CMD_CONF, + CM3323_CONF_SD_BIT); + if (ret < 0) + dev_err(&cm_data->client->dev, "Error writing reg_conf\n"); +} + +static int cm3323_set_it_bits(struct cm3323_data *data, int val, int val2) +{ + int i, ret; + u16 reg_conf; + + for (i = 0; i < ARRAY_SIZE(cm3323_int_time); i++) { + if (val == cm3323_int_time[i].val && + val2 == cm3323_int_time[i].val2) { + reg_conf = data->reg_conf & ~CM3323_CONF_IT_MASK; + reg_conf |= i << CM3323_CONF_IT_SHIFT; + + ret = i2c_smbus_write_word_data(data->client, + CM3323_CMD_CONF, + reg_conf); + if (ret < 0) + return ret; + + data->reg_conf = reg_conf; + + return 0; + } + } + + return -EINVAL; +} + +static int cm3323_get_it_bits(struct cm3323_data *data) +{ + int bits; + + bits = (data->reg_conf & CM3323_CONF_IT_MASK) >> + CM3323_CONF_IT_SHIFT; + + if (bits >= ARRAY_SIZE(cm3323_int_time)) + return -EINVAL; + + return bits; +} + +static int cm3323_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret; + struct cm3323_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->mutex); + ret = i2c_smbus_read_word_data(data->client, chan->address); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + *val = ret; + mutex_unlock(&data->mutex); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + mutex_lock(&data->mutex); + ret = cm3323_get_it_bits(data); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + + *val = cm3323_int_time[ret].val; + *val2 = cm3323_int_time[ret].val2; + mutex_unlock(&data->mutex); + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int cm3323_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct cm3323_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + mutex_lock(&data->mutex); + ret = cm3323_set_it_bits(data, val, val2); + mutex_unlock(&data->mutex); + + return ret; + default: + return -EINVAL; + } +} + +static const struct iio_info cm3323_info = { + .read_raw = cm3323_read_raw, + .write_raw = cm3323_write_raw, + .attrs = &cm3323_attribute_group, +}; + +static int cm3323_probe(struct i2c_client *client) +{ + struct cm3323_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + mutex_init(&data->mutex); + + indio_dev->info = &cm3323_info; + indio_dev->name = CM3323_DRV_NAME; + indio_dev->channels = cm3323_channels; + indio_dev->num_channels = ARRAY_SIZE(cm3323_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = cm3323_init(indio_dev); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(&client->dev, cm3323_disable, indio_dev); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id cm3323_id[] = { + { .name = "cm3323" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cm3323_id); + +static const struct of_device_id cm3323_of_match[] = { + { .compatible = "capella,cm3323", }, + { } +}; +MODULE_DEVICE_TABLE(of, cm3323_of_match); + +static struct i2c_driver cm3323_driver = { + .driver = { + .name = CM3323_DRV_NAME, + .of_match_table = cm3323_of_match, + }, + .probe = cm3323_probe, + .id_table = cm3323_id, +}; + +module_i2c_driver(cm3323_driver); + +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>"); +MODULE_DESCRIPTION("Capella CM3323 Color Light Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/cm3605.c b/drivers/iio/light/cm3605.c new file mode 100644 index 000000000..34c127593 --- /dev/null +++ b/drivers/iio/light/cm3605.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * CM3605 Ambient Light and Proximity Sensor + * + * Copyright (C) 2016 Linaro Ltd. + * Author: Linus Walleij <linus.walleij@linaro.org> + * + * This hardware was found in the very first Nexus One handset from Google/HTC + * and an early endavour into mobile light and proximity sensors. + */ + +#include <linux/module.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/iio/consumer.h> /* To get our ADC channel */ +#include <linux/iio/types.h> /* To deal with our ADC channel */ +#include <linux/init.h> +#include <linux/leds.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/math64.h> +#include <linux/pm.h> + +#define CM3605_PROX_CHANNEL 0 +#define CM3605_ALS_CHANNEL 1 +#define CM3605_AOUT_TYP_MAX_MV 1550 +/* It should not go above 1.650V according to the data sheet */ +#define CM3605_AOUT_MAX_MV 1650 + +/** + * struct cm3605 - CM3605 state + * @dev: pointer to parent device + * @vdd: regulator controlling VDD + * @aset: sleep enable GPIO, high = sleep + * @aout: IIO ADC channel to convert the AOUT signal + * @als_max: maximum LUX detection (depends on RSET) + * @dir: proximity direction: start as FALLING + * @led: trigger for the infrared LED used by the proximity sensor + */ +struct cm3605 { + struct device *dev; + struct regulator *vdd; + struct gpio_desc *aset; + struct iio_channel *aout; + s32 als_max; + enum iio_event_direction dir; + struct led_trigger *led; +}; + +static irqreturn_t cm3605_prox_irq(int irq, void *d) +{ + struct iio_dev *indio_dev = d; + struct cm3605 *cm3605 = iio_priv(indio_dev); + u64 ev; + + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, CM3605_PROX_CHANNEL, + IIO_EV_TYPE_THRESH, cm3605->dir); + iio_push_event(indio_dev, ev, iio_get_time_ns(indio_dev)); + + /* Invert the edge for each event */ + if (cm3605->dir == IIO_EV_DIR_RISING) + cm3605->dir = IIO_EV_DIR_FALLING; + else + cm3605->dir = IIO_EV_DIR_RISING; + + return IRQ_HANDLED; +} + +static int cm3605_get_lux(struct cm3605 *cm3605) +{ + int ret, res; + s64 lux; + + ret = iio_read_channel_processed(cm3605->aout, &res); + if (ret < 0) + return ret; + + dev_dbg(cm3605->dev, "read %d mV from ADC\n", res); + + /* + * AOUT has an offset of ~30mV then linear at dark + * then goes from 2.54 up to 650 LUX yielding 1.55V + * (1550 mV) so scale the returned value to this interval + * using simple linear interpolation. + */ + if (res < 30) + return 0; + if (res > CM3605_AOUT_MAX_MV) + dev_err(cm3605->dev, "device out of range\n"); + + /* Remove bias */ + lux = res - 30; + + /* Linear interpolation between 0 and ALS typ max */ + lux *= cm3605->als_max; + lux = div64_s64(lux, CM3605_AOUT_TYP_MAX_MV); + + return lux; +} + +static int cm3605_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cm3605 *cm3605 = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + ret = cm3605_get_lux(cm3605); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_info cm3605_info = { + .read_raw = cm3605_read_raw, +}; + +static const struct iio_event_spec cm3605_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec cm3605_channels[] = { + { + .type = IIO_PROXIMITY, + .event_spec = cm3605_events, + .num_event_specs = ARRAY_SIZE(cm3605_events), + }, + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .channel = CM3605_ALS_CHANNEL, + }, +}; + +static int cm3605_probe(struct platform_device *pdev) +{ + struct cm3605 *cm3605; + struct iio_dev *indio_dev; + struct device *dev = &pdev->dev; + enum iio_chan_type ch_type; + u32 rset; + int irq; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*cm3605)); + if (!indio_dev) + return -ENOMEM; + platform_set_drvdata(pdev, indio_dev); + + cm3605 = iio_priv(indio_dev); + cm3605->dev = dev; + cm3605->dir = IIO_EV_DIR_FALLING; + + ret = device_property_read_u32(dev, "capella,aset-resistance-ohms", &rset); + if (ret) { + dev_info(dev, "no RSET specified, assuming 100K\n"); + rset = 100000; + } + switch (rset) { + case 50000: + cm3605->als_max = 650; + break; + case 100000: + cm3605->als_max = 300; + break; + case 300000: + cm3605->als_max = 100; + break; + case 600000: + cm3605->als_max = 50; + break; + default: + dev_info(dev, "non-standard resistance\n"); + return -EINVAL; + } + + cm3605->aout = devm_iio_channel_get(dev, "aout"); + if (IS_ERR(cm3605->aout)) { + ret = PTR_ERR(cm3605->aout); + ret = (ret == -ENODEV) ? -EPROBE_DEFER : ret; + return dev_err_probe(dev, ret, "failed to get AOUT ADC channel\n"); + } + ret = iio_get_channel_type(cm3605->aout, &ch_type); + if (ret < 0) + return ret; + if (ch_type != IIO_VOLTAGE) { + dev_err(dev, "wrong type of IIO channel specified for AOUT\n"); + return -EINVAL; + } + + cm3605->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(cm3605->vdd)) + return dev_err_probe(dev, PTR_ERR(cm3605->vdd), + "failed to get VDD regulator\n"); + + ret = regulator_enable(cm3605->vdd); + if (ret) { + dev_err(dev, "failed to enable VDD regulator\n"); + return ret; + } + + cm3605->aset = devm_gpiod_get(dev, "aset", GPIOD_OUT_HIGH); + if (IS_ERR(cm3605->aset)) { + ret = dev_err_probe(dev, PTR_ERR(cm3605->aset), "no ASET GPIO\n"); + goto out_disable_vdd; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + goto out_disable_aset; + } + + ret = devm_request_threaded_irq(dev, irq, cm3605_prox_irq, + NULL, 0, "cm3605", indio_dev); + if (ret) + goto out_disable_aset; + + /* Just name the trigger the same as the driver */ + led_trigger_register_simple("cm3605", &cm3605->led); + led_trigger_event(cm3605->led, LED_FULL); + + indio_dev->info = &cm3605_info; + indio_dev->name = "cm3605"; + indio_dev->channels = cm3605_channels; + indio_dev->num_channels = ARRAY_SIZE(cm3605_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret) + goto out_remove_trigger; + dev_info(dev, "Capella Microsystems CM3605 enabled range 0..%d LUX\n", + cm3605->als_max); + + return 0; + +out_remove_trigger: + led_trigger_event(cm3605->led, LED_OFF); + led_trigger_unregister_simple(cm3605->led); +out_disable_aset: + gpiod_set_value_cansleep(cm3605->aset, 0); +out_disable_vdd: + regulator_disable(cm3605->vdd); + return ret; +} + +static void cm3605_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct cm3605 *cm3605 = iio_priv(indio_dev); + + led_trigger_event(cm3605->led, LED_OFF); + led_trigger_unregister_simple(cm3605->led); + gpiod_set_value_cansleep(cm3605->aset, 0); + iio_device_unregister(indio_dev); + regulator_disable(cm3605->vdd); +} + +static int cm3605_pm_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct cm3605 *cm3605 = iio_priv(indio_dev); + + led_trigger_event(cm3605->led, LED_OFF); + regulator_disable(cm3605->vdd); + + return 0; +} + +static int cm3605_pm_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct cm3605 *cm3605 = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(cm3605->vdd); + if (ret) + dev_err(dev, "failed to enable regulator in resume path\n"); + led_trigger_event(cm3605->led, LED_FULL); + + return 0; +} +static DEFINE_SIMPLE_DEV_PM_OPS(cm3605_dev_pm_ops, cm3605_pm_suspend, + cm3605_pm_resume); + +static const struct of_device_id cm3605_of_match[] = { + {.compatible = "capella,cm3605"}, + { } +}; +MODULE_DEVICE_TABLE(of, cm3605_of_match); + +static struct platform_driver cm3605_driver = { + .driver = { + .name = "cm3605", + .of_match_table = cm3605_of_match, + .pm = pm_sleep_ptr(&cm3605_dev_pm_ops), + }, + .probe = cm3605_probe, + .remove = cm3605_remove, +}; +module_platform_driver(cm3605_driver); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); +MODULE_DESCRIPTION("CM3605 ambient light and proximity sensor driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_CONSUMER"); diff --git a/drivers/iio/light/cm36651.c b/drivers/iio/light/cm36651.c new file mode 100644 index 000000000..6ebd8ce4c --- /dev/null +++ b/drivers/iio/light/cm36651.c @@ -0,0 +1,742 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Beomho Seo <beomho.seo@samsung.com> + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/regulator/consumer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> + +/* Slave address 0x19 for PS of 7 bit addressing protocol for I2C */ +#define CM36651_I2C_ADDR_PS 0x19 +/* Alert Response Address */ +#define CM36651_ARA 0x0C + +/* Ambient light sensor */ +#define CM36651_CS_CONF1 0x00 +#define CM36651_CS_CONF2 0x01 +#define CM36651_ALS_WH_M 0x02 +#define CM36651_ALS_WH_L 0x03 +#define CM36651_ALS_WL_M 0x04 +#define CM36651_ALS_WL_L 0x05 +#define CM36651_CS_CONF3 0x06 +#define CM36651_CS_CONF_REG_NUM 0x02 + +/* Proximity sensor */ +#define CM36651_PS_CONF1 0x00 +#define CM36651_PS_THD 0x01 +#define CM36651_PS_CANC 0x02 +#define CM36651_PS_CONF2 0x03 +#define CM36651_PS_REG_NUM 0x04 + +/* CS_CONF1 command code */ +#define CM36651_ALS_ENABLE 0x00 +#define CM36651_ALS_DISABLE 0x01 +#define CM36651_ALS_INT_EN 0x02 +#define CM36651_ALS_THRES 0x04 + +/* CS_CONF2 command code */ +#define CM36651_CS_CONF2_DEFAULT_BIT 0x08 + +/* CS_CONF3 channel integration time */ +#define CM36651_CS_IT1 0x00 /* Integration time 80 msec */ +#define CM36651_CS_IT2 0x40 /* Integration time 160 msec */ +#define CM36651_CS_IT3 0x80 /* Integration time 320 msec */ +#define CM36651_CS_IT4 0xC0 /* Integration time 640 msec */ + +/* PS_CONF1 command code */ +#define CM36651_PS_ENABLE 0x00 +#define CM36651_PS_DISABLE 0x01 +#define CM36651_PS_INT_EN 0x02 +#define CM36651_PS_PERS2 0x04 +#define CM36651_PS_PERS3 0x08 +#define CM36651_PS_PERS4 0x0C + +/* PS_CONF1 command code: integration time */ +#define CM36651_PS_IT1 0x00 /* Integration time 0.32 msec */ +#define CM36651_PS_IT2 0x10 /* Integration time 0.42 msec */ +#define CM36651_PS_IT3 0x20 /* Integration time 0.52 msec */ +#define CM36651_PS_IT4 0x30 /* Integration time 0.64 msec */ + +/* PS_CONF1 command code: duty ratio */ +#define CM36651_PS_DR1 0x00 /* Duty ratio 1/80 */ +#define CM36651_PS_DR2 0x40 /* Duty ratio 1/160 */ +#define CM36651_PS_DR3 0x80 /* Duty ratio 1/320 */ +#define CM36651_PS_DR4 0xC0 /* Duty ratio 1/640 */ + +/* PS_THD command code */ +#define CM36651_PS_INITIAL_THD 0x05 + +/* PS_CANC command code */ +#define CM36651_PS_CANC_DEFAULT 0x00 + +/* PS_CONF2 command code */ +#define CM36651_PS_HYS1 0x00 +#define CM36651_PS_HYS2 0x01 +#define CM36651_PS_SMART_PERS_EN 0x02 +#define CM36651_PS_DIR_INT 0x04 +#define CM36651_PS_MS 0x10 + +#define CM36651_CS_COLOR_NUM 4 + +#define CM36651_CLOSE_PROXIMITY 0x32 +#define CM36651_FAR_PROXIMITY 0x33 + +#define CM36651_CS_INT_TIME_AVAIL "0.08 0.16 0.32 0.64" +#define CM36651_PS_INT_TIME_AVAIL "0.000320 0.000420 0.000520 0.000640" + +enum cm36651_operation_mode { + CM36651_LIGHT_EN, + CM36651_PROXIMITY_EN, + CM36651_PROXIMITY_EV_EN, +}; + +enum cm36651_light_channel_idx { + CM36651_LIGHT_CHANNEL_IDX_RED, + CM36651_LIGHT_CHANNEL_IDX_GREEN, + CM36651_LIGHT_CHANNEL_IDX_BLUE, + CM36651_LIGHT_CHANNEL_IDX_CLEAR, +}; + +enum cm36651_command { + CM36651_CMD_READ_RAW_LIGHT, + CM36651_CMD_READ_RAW_PROXIMITY, + CM36651_CMD_PROX_EV_EN, + CM36651_CMD_PROX_EV_DIS, +}; + +static const u8 cm36651_cs_reg[CM36651_CS_CONF_REG_NUM] = { + CM36651_CS_CONF1, + CM36651_CS_CONF2, +}; + +static const u8 cm36651_ps_reg[CM36651_PS_REG_NUM] = { + CM36651_PS_CONF1, + CM36651_PS_THD, + CM36651_PS_CANC, + CM36651_PS_CONF2, +}; + +struct cm36651_data { + const struct cm36651_platform_data *pdata; + struct i2c_client *client; + struct i2c_client *ps_client; + struct i2c_client *ara_client; + struct mutex lock; + struct regulator *vled_reg; + unsigned long flags; + int cs_int_time[CM36651_CS_COLOR_NUM]; + int ps_int_time; + u8 cs_ctrl_regs[CM36651_CS_CONF_REG_NUM]; + u8 ps_ctrl_regs[CM36651_PS_REG_NUM]; + u16 color[CM36651_CS_COLOR_NUM]; +}; + +static int cm36651_setup_reg(struct cm36651_data *cm36651) +{ + struct i2c_client *client = cm36651->client; + struct i2c_client *ps_client = cm36651->ps_client; + int i, ret; + + /* CS initialization */ + cm36651->cs_ctrl_regs[CM36651_CS_CONF1] = CM36651_ALS_ENABLE | + CM36651_ALS_THRES; + cm36651->cs_ctrl_regs[CM36651_CS_CONF2] = CM36651_CS_CONF2_DEFAULT_BIT; + + for (i = 0; i < CM36651_CS_CONF_REG_NUM; i++) { + ret = i2c_smbus_write_byte_data(client, cm36651_cs_reg[i], + cm36651->cs_ctrl_regs[i]); + if (ret < 0) + return ret; + } + + /* PS initialization */ + cm36651->ps_ctrl_regs[CM36651_PS_CONF1] = CM36651_PS_ENABLE | + CM36651_PS_IT2; + cm36651->ps_ctrl_regs[CM36651_PS_THD] = CM36651_PS_INITIAL_THD; + cm36651->ps_ctrl_regs[CM36651_PS_CANC] = CM36651_PS_CANC_DEFAULT; + cm36651->ps_ctrl_regs[CM36651_PS_CONF2] = CM36651_PS_HYS2 | + CM36651_PS_DIR_INT | CM36651_PS_SMART_PERS_EN; + + for (i = 0; i < CM36651_PS_REG_NUM; i++) { + ret = i2c_smbus_write_byte_data(ps_client, cm36651_ps_reg[i], + cm36651->ps_ctrl_regs[i]); + if (ret < 0) + return ret; + } + + /* Set shutdown mode */ + ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF1, + CM36651_ALS_DISABLE); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(cm36651->ps_client, + CM36651_PS_CONF1, CM36651_PS_DISABLE); + if (ret < 0) + return ret; + + return 0; +} + +static int cm36651_read_output(struct cm36651_data *cm36651, + struct iio_chan_spec const *chan, int *val) +{ + struct i2c_client *client = cm36651->client; + int ret = -EINVAL; + + switch (chan->type) { + case IIO_LIGHT: + *val = i2c_smbus_read_word_data(client, chan->address); + if (*val < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF1, + CM36651_ALS_DISABLE); + if (ret < 0) + return ret; + + ret = IIO_VAL_INT; + break; + case IIO_PROXIMITY: + *val = i2c_smbus_read_byte(cm36651->ps_client); + if (*val < 0) + return ret; + + if (!test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) { + ret = i2c_smbus_write_byte_data(cm36651->ps_client, + CM36651_PS_CONF1, CM36651_PS_DISABLE); + if (ret < 0) + return ret; + } + + ret = IIO_VAL_INT; + break; + default: + break; + } + + return ret; +} + +static irqreturn_t cm36651_irq_handler(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct cm36651_data *cm36651 = iio_priv(indio_dev); + struct i2c_client *client = cm36651->client; + int ev_dir, ret; + u64 ev_code; + + /* + * The PS INT pin is an active low signal that PS INT move logic low + * when the object is detect. Once the MCU host received the PS INT + * "LOW" signal, the Host needs to read the data at Alert Response + * Address(ARA) to clear the PS INT signal. After clearing the PS + * INT pin, the PS INT signal toggles from low to high. + */ + ret = i2c_smbus_read_byte(cm36651->ara_client); + if (ret < 0) { + dev_err(&client->dev, + "%s: Data read failed: %d\n", __func__, ret); + return IRQ_HANDLED; + } + switch (ret) { + case CM36651_CLOSE_PROXIMITY: + ev_dir = IIO_EV_DIR_RISING; + break; + case CM36651_FAR_PROXIMITY: + ev_dir = IIO_EV_DIR_FALLING; + break; + default: + dev_err(&client->dev, + "%s: Data read wrong: %d\n", __func__, ret); + return IRQ_HANDLED; + } + + ev_code = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, + CM36651_CMD_READ_RAW_PROXIMITY, + IIO_EV_TYPE_THRESH, ev_dir); + + iio_push_event(indio_dev, ev_code, iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; +} + +static int cm36651_set_operation_mode(struct cm36651_data *cm36651, int cmd) +{ + struct i2c_client *client = cm36651->client; + struct i2c_client *ps_client = cm36651->ps_client; + int ret = -EINVAL; + + switch (cmd) { + case CM36651_CMD_READ_RAW_LIGHT: + ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF1, + cm36651->cs_ctrl_regs[CM36651_CS_CONF1]); + break; + case CM36651_CMD_READ_RAW_PROXIMITY: + if (test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) + return CM36651_PROXIMITY_EV_EN; + + ret = i2c_smbus_write_byte_data(ps_client, CM36651_PS_CONF1, + cm36651->ps_ctrl_regs[CM36651_PS_CONF1]); + break; + case CM36651_CMD_PROX_EV_EN: + if (test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) { + dev_err(&client->dev, + "Already proximity event enable state\n"); + return ret; + } + set_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags); + + ret = i2c_smbus_write_byte_data(ps_client, + cm36651_ps_reg[CM36651_PS_CONF1], + CM36651_PS_INT_EN | CM36651_PS_PERS2 | CM36651_PS_IT2); + + if (ret < 0) { + dev_err(&client->dev, "Proximity enable event failed\n"); + return ret; + } + break; + case CM36651_CMD_PROX_EV_DIS: + if (!test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) { + dev_err(&client->dev, + "Already proximity event disable state\n"); + return ret; + } + clear_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags); + ret = i2c_smbus_write_byte_data(ps_client, + CM36651_PS_CONF1, CM36651_PS_DISABLE); + break; + } + + if (ret < 0) + dev_err(&client->dev, "Write register failed\n"); + + return ret; +} + +static int cm36651_read_channel(struct cm36651_data *cm36651, + struct iio_chan_spec const *chan, int *val) +{ + struct i2c_client *client = cm36651->client; + int cmd, ret; + + if (chan->type == IIO_LIGHT) + cmd = CM36651_CMD_READ_RAW_LIGHT; + else if (chan->type == IIO_PROXIMITY) + cmd = CM36651_CMD_READ_RAW_PROXIMITY; + else + return -EINVAL; + + ret = cm36651_set_operation_mode(cm36651, cmd); + if (ret < 0) { + dev_err(&client->dev, "CM36651 set operation mode failed\n"); + return ret; + } + /* Delay for work after enable operation */ + msleep(50); + ret = cm36651_read_output(cm36651, chan, val); + if (ret < 0) { + dev_err(&client->dev, "CM36651 read output failed\n"); + return ret; + } + + return ret; +} + +static int cm36651_read_int_time(struct cm36651_data *cm36651, + struct iio_chan_spec const *chan, int *val2) +{ + switch (chan->type) { + case IIO_LIGHT: + if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT1) + *val2 = 80000; + else if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT2) + *val2 = 160000; + else if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT3) + *val2 = 320000; + else if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT4) + *val2 = 640000; + else + return -EINVAL; + break; + case IIO_PROXIMITY: + if (cm36651->ps_int_time == CM36651_PS_IT1) + *val2 = 320; + else if (cm36651->ps_int_time == CM36651_PS_IT2) + *val2 = 420; + else if (cm36651->ps_int_time == CM36651_PS_IT3) + *val2 = 520; + else if (cm36651->ps_int_time == CM36651_PS_IT4) + *val2 = 640; + else + return -EINVAL; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int cm36651_write_int_time(struct cm36651_data *cm36651, + struct iio_chan_spec const *chan, int val) +{ + struct i2c_client *client = cm36651->client; + struct i2c_client *ps_client = cm36651->ps_client; + int int_time, ret; + + switch (chan->type) { + case IIO_LIGHT: + if (val == 80000) + int_time = CM36651_CS_IT1; + else if (val == 160000) + int_time = CM36651_CS_IT2; + else if (val == 320000) + int_time = CM36651_CS_IT3; + else if (val == 640000) + int_time = CM36651_CS_IT4; + else + return -EINVAL; + + ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF3, + int_time >> 2 * (chan->address)); + if (ret < 0) { + dev_err(&client->dev, "CS integration time write failed\n"); + return ret; + } + cm36651->cs_int_time[chan->address] = int_time; + break; + case IIO_PROXIMITY: + if (val == 320) + int_time = CM36651_PS_IT1; + else if (val == 420) + int_time = CM36651_PS_IT2; + else if (val == 520) + int_time = CM36651_PS_IT3; + else if (val == 640) + int_time = CM36651_PS_IT4; + else + return -EINVAL; + + ret = i2c_smbus_write_byte_data(ps_client, + CM36651_PS_CONF1, int_time); + if (ret < 0) { + dev_err(&client->dev, "PS integration time write failed\n"); + return ret; + } + cm36651->ps_int_time = int_time; + break; + default: + return -EINVAL; + } + + return ret; +} + +static int cm36651_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cm36651_data *cm36651 = iio_priv(indio_dev); + int ret; + + mutex_lock(&cm36651->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = cm36651_read_channel(cm36651, chan, val); + break; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + ret = cm36651_read_int_time(cm36651, chan, val2); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&cm36651->lock); + + return ret; +} + +static int cm36651_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cm36651_data *cm36651 = iio_priv(indio_dev); + struct i2c_client *client = cm36651->client; + int ret = -EINVAL; + + if (mask == IIO_CHAN_INFO_INT_TIME) { + ret = cm36651_write_int_time(cm36651, chan, val2); + if (ret < 0) + dev_err(&client->dev, "Integration time write failed\n"); + } + + return ret; +} + +static int cm36651_read_prox_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct cm36651_data *cm36651 = iio_priv(indio_dev); + + *val = cm36651->ps_ctrl_regs[CM36651_PS_THD]; + + return 0; +} + +static int cm36651_write_prox_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct cm36651_data *cm36651 = iio_priv(indio_dev); + struct i2c_client *client = cm36651->client; + int ret; + + if (val < 3 || val > 255) + return -EINVAL; + + cm36651->ps_ctrl_regs[CM36651_PS_THD] = val; + ret = i2c_smbus_write_byte_data(cm36651->ps_client, CM36651_PS_THD, + cm36651->ps_ctrl_regs[CM36651_PS_THD]); + + if (ret < 0) { + dev_err(&client->dev, "PS threshold write failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int cm36651_write_prox_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct cm36651_data *cm36651 = iio_priv(indio_dev); + int cmd, ret; + + mutex_lock(&cm36651->lock); + + cmd = state ? CM36651_CMD_PROX_EV_EN : CM36651_CMD_PROX_EV_DIS; + ret = cm36651_set_operation_mode(cm36651, cmd); + + mutex_unlock(&cm36651->lock); + + return ret; +} + +static int cm36651_read_prox_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct cm36651_data *cm36651 = iio_priv(indio_dev); + int event_en; + + mutex_lock(&cm36651->lock); + + event_en = test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags); + + mutex_unlock(&cm36651->lock); + + return event_en; +} + +#define CM36651_LIGHT_CHANNEL(_color, _idx) { \ + .type = IIO_LIGHT, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .address = _idx, \ + .modified = 1, \ + .channel2 = IIO_MOD_LIGHT_##_color, \ +} \ + +static const struct iio_event_spec cm36651_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + } +}; + +static const struct iio_chan_spec cm36651_channels[] = { + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME), + .event_spec = cm36651_event_spec, + .num_event_specs = ARRAY_SIZE(cm36651_event_spec), + }, + CM36651_LIGHT_CHANNEL(RED, CM36651_LIGHT_CHANNEL_IDX_RED), + CM36651_LIGHT_CHANNEL(GREEN, CM36651_LIGHT_CHANNEL_IDX_GREEN), + CM36651_LIGHT_CHANNEL(BLUE, CM36651_LIGHT_CHANNEL_IDX_BLUE), + CM36651_LIGHT_CHANNEL(CLEAR, CM36651_LIGHT_CHANNEL_IDX_CLEAR), +}; + +static IIO_CONST_ATTR(in_illuminance_integration_time_available, + CM36651_CS_INT_TIME_AVAIL); +static IIO_CONST_ATTR(in_proximity_integration_time_available, + CM36651_PS_INT_TIME_AVAIL); + +static struct attribute *cm36651_attributes[] = { + &iio_const_attr_in_illuminance_integration_time_available.dev_attr.attr, + &iio_const_attr_in_proximity_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group cm36651_attribute_group = { + .attrs = cm36651_attributes +}; + +static const struct iio_info cm36651_info = { + .read_raw = &cm36651_read_raw, + .write_raw = &cm36651_write_raw, + .read_event_value = &cm36651_read_prox_thresh, + .write_event_value = &cm36651_write_prox_thresh, + .read_event_config = &cm36651_read_prox_event_config, + .write_event_config = &cm36651_write_prox_event_config, + .attrs = &cm36651_attribute_group, +}; + +static int cm36651_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct cm36651_data *cm36651; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*cm36651)); + if (!indio_dev) + return -ENOMEM; + + cm36651 = iio_priv(indio_dev); + + cm36651->vled_reg = devm_regulator_get(&client->dev, "vled"); + if (IS_ERR(cm36651->vled_reg)) + return dev_err_probe(&client->dev, PTR_ERR(cm36651->vled_reg), + "get regulator vled failed\n"); + + ret = regulator_enable(cm36651->vled_reg); + if (ret) { + dev_err(&client->dev, "enable regulator vled failed\n"); + return ret; + } + + i2c_set_clientdata(client, indio_dev); + + cm36651->client = client; + cm36651->ps_client = i2c_new_dummy_device(client->adapter, + CM36651_I2C_ADDR_PS); + if (IS_ERR(cm36651->ps_client)) { + dev_err(&client->dev, "%s: new i2c device failed\n", __func__); + ret = PTR_ERR(cm36651->ps_client); + goto error_disable_reg; + } + + cm36651->ara_client = i2c_new_dummy_device(client->adapter, CM36651_ARA); + if (IS_ERR(cm36651->ara_client)) { + dev_err(&client->dev, "%s: new i2c device failed\n", __func__); + ret = PTR_ERR(cm36651->ara_client); + goto error_i2c_unregister_ps; + } + + mutex_init(&cm36651->lock); + indio_dev->channels = cm36651_channels; + indio_dev->num_channels = ARRAY_SIZE(cm36651_channels); + indio_dev->info = &cm36651_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = cm36651_setup_reg(cm36651); + if (ret) { + dev_err(&client->dev, "%s: register setup failed\n", __func__); + goto error_i2c_unregister_ara; + } + + ret = request_threaded_irq(client->irq, NULL, cm36651_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "cm36651", indio_dev); + if (ret) { + dev_err(&client->dev, "%s: request irq failed\n", __func__); + goto error_i2c_unregister_ara; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&client->dev, "%s: register device failed\n", __func__); + goto error_free_irq; + } + + return 0; + +error_free_irq: + free_irq(client->irq, indio_dev); +error_i2c_unregister_ara: + i2c_unregister_device(cm36651->ara_client); +error_i2c_unregister_ps: + i2c_unregister_device(cm36651->ps_client); +error_disable_reg: + regulator_disable(cm36651->vled_reg); + return ret; +} + +static void cm36651_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct cm36651_data *cm36651 = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(cm36651->vled_reg); + free_irq(client->irq, indio_dev); + i2c_unregister_device(cm36651->ps_client); + i2c_unregister_device(cm36651->ara_client); +} + +static const struct i2c_device_id cm36651_id[] = { + { .name = "cm36651" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, cm36651_id); + +static const struct of_device_id cm36651_of_match[] = { + { .compatible = "capella,cm36651" }, + { } +}; +MODULE_DEVICE_TABLE(of, cm36651_of_match); + +static struct i2c_driver cm36651_driver = { + .driver = { + .name = "cm36651", + .of_match_table = cm36651_of_match, + }, + .probe = cm36651_probe, + .remove = cm36651_remove, + .id_table = cm36651_id, +}; + +module_i2c_driver(cm36651_driver); + +MODULE_AUTHOR("Beomho Seo <beomho.seo@samsung.com>"); +MODULE_DESCRIPTION("CM36651 proximity/ambient light sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/cros_ec_light_prox.c b/drivers/iio/light/cros_ec_light_prox.c new file mode 100644 index 000000000..7ab565b1f --- /dev/null +++ b/drivers/iio/light/cros_ec_light_prox.c @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * cros_ec_light_prox - Driver for light and prox sensors behing CrosEC. + * + * Copyright (C) 2017 Google, Inc + */ + +#include <linux/device.h> +#include <linux/iio/buffer.h> +#include <linux/iio/common/cros_ec_sensors_core.h> +#include <linux/iio/iio.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/iio/trigger.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +/* + * We only represent one entry for light or proximity. EC is merging different + * light sensors to return the what the eye would see. For proximity, we + * currently support only one light source. + */ +#define CROS_EC_LIGHT_PROX_MAX_CHANNELS (1 + 1) + +/* State data for ec_sensors iio driver. */ +struct cros_ec_light_prox_state { + /* Shared by all sensors */ + struct cros_ec_sensors_core_state core; + + struct iio_chan_spec channels[CROS_EC_LIGHT_PROX_MAX_CHANNELS]; +}; + +static int cros_ec_light_prox_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cros_ec_light_prox_state *st = iio_priv(indio_dev); + u16 data = 0; + s64 val64; + int ret; + int idx = chan->scan_index; + + mutex_lock(&st->core.cmd_lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_PROXIMITY) { + ret = cros_ec_sensors_read_cmd(indio_dev, 1 << idx, + (s16 *)&data); + if (ret) + break; + *val = data; + ret = IIO_VAL_INT; + } else { + ret = -EINVAL; + } + break; + case IIO_CHAN_INFO_PROCESSED: + if (chan->type == IIO_LIGHT) { + ret = cros_ec_sensors_read_cmd(indio_dev, 1 << idx, + (s16 *)&data); + if (ret) + break; + /* + * The data coming from the light sensor is + * pre-processed and represents the ambient light + * illuminance reading expressed in lux. + */ + *val = data; + ret = IIO_VAL_INT; + } else { + ret = -EINVAL; + } + break; + case IIO_CHAN_INFO_CALIBBIAS: + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_OFFSET; + st->core.param.sensor_offset.flags = 0; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret) + break; + + /* Save values */ + st->core.calib[0].offset = + st->core.resp->sensor_offset.offset[0]; + + *val = st->core.calib[idx].offset; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBSCALE: + /* + * RANGE is used for calibration + * scale is a number x.y, where x is coded on 16 bits, + * y coded on 16 bits, between 0 and 9999. + */ + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; + st->core.param.sensor_range.data = EC_MOTION_SENSE_NO_VALUE; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret) + break; + + val64 = st->core.resp->sensor_range.ret; + *val = val64 >> 16; + *val2 = (val64 & 0xffff) * 100; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = cros_ec_sensors_core_read(&st->core, chan, val, val2, + mask); + break; + } + + mutex_unlock(&st->core.cmd_lock); + + return ret; +} + +static int cros_ec_light_prox_write(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cros_ec_light_prox_state *st = iio_priv(indio_dev); + int ret; + int idx = chan->scan_index; + + mutex_lock(&st->core.cmd_lock); + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + st->core.calib[idx].offset = val; + /* Send to EC for each axis, even if not complete */ + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_OFFSET; + st->core.param.sensor_offset.flags = MOTION_SENSE_SET_OFFSET; + st->core.param.sensor_offset.offset[0] = + st->core.calib[0].offset; + st->core.param.sensor_offset.temp = + EC_MOTION_SENSE_INVALID_CALIB_TEMP; + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + break; + case IIO_CHAN_INFO_CALIBSCALE: + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; + st->core.curr_range = (val << 16) | (val2 / 100); + st->core.param.sensor_range.data = st->core.curr_range; + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret == 0) + st->core.range_updated = true; + break; + default: + ret = cros_ec_sensors_core_write(&st->core, chan, val, val2, + mask); + break; + } + + mutex_unlock(&st->core.cmd_lock); + + return ret; +} + +static const struct iio_info cros_ec_light_prox_info = { + .read_raw = &cros_ec_light_prox_read, + .write_raw = &cros_ec_light_prox_write, + .read_avail = &cros_ec_sensors_core_read_avail, +}; + +static int cros_ec_light_prox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct cros_ec_light_prox_state *state; + struct iio_chan_spec *channel; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + ret = cros_ec_sensors_core_init(pdev, indio_dev, true, + cros_ec_sensors_capture); + if (ret) + return ret; + + indio_dev->info = &cros_ec_light_prox_info; + state = iio_priv(indio_dev); + channel = state->channels; + + /* Common part */ + channel->info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_SAMP_FREQ); + channel->info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ); + channel->scan_type.realbits = CROS_EC_SENSOR_BITS; + channel->scan_type.storagebits = CROS_EC_SENSOR_BITS; + channel->scan_type.shift = 0; + channel->scan_index = 0; + channel->ext_info = cros_ec_sensors_ext_info; + channel->scan_type.sign = 'u'; + + /* Sensor specific */ + switch (state->core.type) { + case MOTIONSENSE_TYPE_LIGHT: + channel->type = IIO_LIGHT; + channel->info_mask_separate = + BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CALIBSCALE); + break; + case MOTIONSENSE_TYPE_PROX: + channel->type = IIO_PROXIMITY; + channel->info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CALIBSCALE); + break; + default: + dev_warn(dev, "Unknown motion sensor\n"); + return -EINVAL; + } + + channel++; + *channel = IIO_CHAN_SOFT_TIMESTAMP(1); + + indio_dev->channels = state->channels; + + indio_dev->num_channels = CROS_EC_LIGHT_PROX_MAX_CHANNELS; + + state->core.read_ec_sensors_data = cros_ec_sensors_read_cmd; + + return cros_ec_sensors_core_register(dev, indio_dev, + cros_ec_sensors_push_data); +} + +static const struct platform_device_id cros_ec_light_prox_ids[] = { + { + .name = "cros-ec-prox", + }, + { + .name = "cros-ec-light", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, cros_ec_light_prox_ids); + +static struct platform_driver cros_ec_light_prox_platform_driver = { + .driver = { + .name = "cros-ec-light-prox", + .pm = &cros_ec_sensors_pm_ops, + }, + .probe = cros_ec_light_prox_probe, + .id_table = cros_ec_light_prox_ids, +}; +module_platform_driver(cros_ec_light_prox_platform_driver); + +MODULE_DESCRIPTION("ChromeOS EC light/proximity sensors driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/gp2ap002.c b/drivers/iio/light/gp2ap002.c new file mode 100644 index 000000000..1fd40d601 --- /dev/null +++ b/drivers/iio/light/gp2ap002.c @@ -0,0 +1,732 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * These are the two Sharp GP2AP002 variants supported by this driver: + * GP2AP002A00F Ambient Light and Proximity Sensor + * GP2AP002S00F Proximity Sensor + * + * Copyright (C) 2020 Linaro Ltd. + * Author: Linus Walleij <linus.walleij@linaro.org> + * + * Based partly on the code in Sony Ericssons GP2AP00200F driver by + * Courtney Cavin and Oskar Andero in drivers/input/misc/gp2ap002a00f.c + * Based partly on a Samsung misc driver submitted by + * Donggeun Kim & Minkyu Kang in 2011: + * https://lore.kernel.org/lkml/1315556546-7445-1-git-send-email-dg77.kim@samsung.com/ + * Based partly on a submission by + * Jonathan Bakker and PaweÅ‚ Chmiel in january 2019: + * https://lore.kernel.org/linux-input/20190125175045.22576-1-pawel.mikolaj.chmiel@gmail.com/ + * Based partly on code from the Samsung GT-S7710 by <mjchen@sta.samsung.com> + * Based partly on the code in LG Electronics GP2AP00200F driver by + * Kenobi Lee <sungyoung.lee@lge.com> and EunYoung Cho <ey.cho@lge.com> + */ +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/iio/consumer.h> /* To get our ADC channel */ +#include <linux/iio/types.h> /* To deal with our ADC channel */ +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/regulator/consumer.h> +#include <linux/pm_runtime.h> +#include <linux/interrupt.h> +#include <linux/bits.h> +#include <linux/math64.h> +#include <linux/pm.h> + +#define GP2AP002_PROX_CHANNEL 0 +#define GP2AP002_ALS_CHANNEL 1 + +/* ------------------------------------------------------------------------ */ +/* ADDRESS SYMBOL DATA Init R/W */ +/* D7 D6 D5 D4 D3 D2 D1 D0 */ +/* ------------------------------------------------------------------------ */ +/* 0 PROX X X X X X X X VO H'00 R */ +/* 1 GAIN X X X X LED0 X X X H'00 W */ +/* 2 HYS HYSD HYSC1 HYSC0 X HYSF3 HYSF2 HYSF1 HYSF0 H'00 W */ +/* 3 CYCLE X X CYCL2 CYCL1 CYCL0 OSC2 X X H'00 W */ +/* 4 OPMOD X X X ASD X X VCON SSD H'00 W */ +/* 6 CON X X X OCON1 OCON0 X X X H'00 W */ +/* ------------------------------------------------------------------------ */ +/* VO :Proximity sensing result(0: no detection, 1: detection) */ +/* LED0 :Select switch for LED driver's On-registence(0:2x higher, 1:normal)*/ +/* HYSD/HYSF :Adjusts the receiver sensitivity */ +/* OSC :Select switch internal clocl frequency hoppling(0:effective) */ +/* CYCL :Determine the detection cycle(typically 8ms, up to 128x) */ +/* SSD :Software Shutdown function(0:shutdown, 1:operating) */ +/* VCON :VOUT output method control(0:normal, 1:interrupt) */ +/* ASD :Select switch for analog sleep function(0:ineffective, 1:effective)*/ +/* OCON :Select switch for enabling/disabling VOUT (00:enable, 11:disable) */ + +#define GP2AP002_PROX 0x00 +#define GP2AP002_GAIN 0x01 +#define GP2AP002_HYS 0x02 +#define GP2AP002_CYCLE 0x03 +#define GP2AP002_OPMOD 0x04 +#define GP2AP002_CON 0x06 + +#define GP2AP002_PROX_VO_DETECT BIT(0) + +/* Setting this bit to 0 means 2x higher LED resistance */ +#define GP2AP002_GAIN_LED_NORMAL BIT(3) + +/* + * These bits adjusts the proximity sensitivity, determining characteristics + * of the detection distance and its hysteresis. + */ +#define GP2AP002_HYS_HYSD_SHIFT 7 +#define GP2AP002_HYS_HYSD_MASK BIT(7) +#define GP2AP002_HYS_HYSC_SHIFT 5 +#define GP2AP002_HYS_HYSC_MASK GENMASK(6, 5) +#define GP2AP002_HYS_HYSF_SHIFT 0 +#define GP2AP002_HYS_HYSF_MASK GENMASK(3, 0) +#define GP2AP002_HYS_MASK (GP2AP002_HYS_HYSD_MASK | \ + GP2AP002_HYS_HYSC_MASK | \ + GP2AP002_HYS_HYSF_MASK) + +/* + * These values determine the detection cycle response time + * 0: 8ms, 1: 16ms, 2: 32ms, 3: 64ms, 4: 128ms, + * 5: 256ms, 6: 512ms, 7: 1024ms + */ +#define GP2AP002_CYCLE_CYCL_SHIFT 3 +#define GP2AP002_CYCLE_CYCL_MASK GENMASK(5, 3) + +/* + * Select switch for internal clock frequency hopping + * 0: effective, + * 1: ineffective + */ +#define GP2AP002_CYCLE_OSC_EFFECTIVE 0 +#define GP2AP002_CYCLE_OSC_INEFFECTIVE BIT(2) +#define GP2AP002_CYCLE_OSC_MASK BIT(2) + +/* Analog sleep effective */ +#define GP2AP002_OPMOD_ASD BIT(4) +/* Enable chip */ +#define GP2AP002_OPMOD_SSD_OPERATING BIT(0) +/* IRQ mode */ +#define GP2AP002_OPMOD_VCON_IRQ BIT(1) +#define GP2AP002_OPMOD_MASK (BIT(0) | BIT(1) | BIT(4)) + +/* + * Select switch for enabling/disabling Vout pin + * 0: enable + * 2: force to go Low + * 3: force to go High + */ +#define GP2AP002_CON_OCON_SHIFT 3 +#define GP2AP002_CON_OCON_ENABLE (0x0 << GP2AP002_CON_OCON_SHIFT) +#define GP2AP002_CON_OCON_LOW (0x2 << GP2AP002_CON_OCON_SHIFT) +#define GP2AP002_CON_OCON_HIGH (0x3 << GP2AP002_CON_OCON_SHIFT) +#define GP2AP002_CON_OCON_MASK (0x3 << GP2AP002_CON_OCON_SHIFT) + +/** + * struct gp2ap002 - GP2AP002 state + * @map: regmap pointer for the i2c regmap + * @dev: pointer to parent device + * @vdd: regulator controlling VDD + * @vio: regulator controlling VIO + * @alsout: IIO ADC channel to convert the ALSOUT signal + * @hys_far: hysteresis control from device tree + * @hys_close: hysteresis control from device tree + * @is_gp2ap002s00f: this is the GP2AP002F variant of the chip + * @irq: the IRQ line used by this device + * @enabled: we cannot read the status of the hardware so we need to + * keep track of whether the event is enabled using this state variable + */ +struct gp2ap002 { + struct regmap *map; + struct device *dev; + struct regulator *vdd; + struct regulator *vio; + struct iio_channel *alsout; + u8 hys_far; + u8 hys_close; + bool is_gp2ap002s00f; + int irq; + bool enabled; +}; + +static irqreturn_t gp2ap002_prox_irq(int irq, void *d) +{ + struct iio_dev *indio_dev = d; + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev); + u64 ev; + int val; + int ret; + + if (!gp2ap002->enabled) + goto err_retrig; + + ret = regmap_read(gp2ap002->map, GP2AP002_PROX, &val); + if (ret) { + dev_err(gp2ap002->dev, "error reading proximity\n"); + goto err_retrig; + } + + if (val & GP2AP002_PROX_VO_DETECT) { + /* Close */ + dev_dbg(gp2ap002->dev, "close\n"); + ret = regmap_write(gp2ap002->map, GP2AP002_HYS, + gp2ap002->hys_far); + if (ret) + dev_err(gp2ap002->dev, + "error setting up proximity hysteresis\n"); + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, GP2AP002_PROX_CHANNEL, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING); + } else { + /* Far */ + dev_dbg(gp2ap002->dev, "far\n"); + ret = regmap_write(gp2ap002->map, GP2AP002_HYS, + gp2ap002->hys_close); + if (ret) + dev_err(gp2ap002->dev, + "error setting up proximity hysteresis\n"); + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, GP2AP002_PROX_CHANNEL, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING); + } + iio_push_event(indio_dev, ev, iio_get_time_ns(indio_dev)); + + /* + * After changing hysteresis, we need to wait for one detection + * cycle to see if anything changed, or we will just trigger the + * previous interrupt again. A detection cycle depends on the CYCLE + * register, we are hard-coding ~8 ms in probe() so wait some more + * than this, 20-30 ms. + */ + usleep_range(20000, 30000); + +err_retrig: + ret = regmap_write(gp2ap002->map, GP2AP002_CON, + GP2AP002_CON_OCON_ENABLE); + if (ret) + dev_err(gp2ap002->dev, "error setting up VOUT control\n"); + + return IRQ_HANDLED; +} + +/* + * This array maps current and lux. + * + * Ambient light sensing range is 3 to 55000 lux. + * + * This mapping is based on the following formula. + * illuminance = 10 ^ (current[mA] / 10) + * + * When the ADC measures 0, return 0 lux. + */ +static const u16 gp2ap002_illuminance_table[] = { + 0, 1, 1, 2, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 25, 32, 40, 50, 63, 79, + 100, 126, 158, 200, 251, 316, 398, 501, 631, 794, 1000, 1259, 1585, + 1995, 2512, 3162, 3981, 5012, 6310, 7943, 10000, 12589, 15849, 19953, + 25119, 31623, 39811, 50119, +}; + +static int gp2ap002_get_lux(struct gp2ap002 *gp2ap002) +{ + int ret, res; + u16 lux; + + ret = iio_read_channel_processed(gp2ap002->alsout, &res); + if (ret < 0) + return ret; + + dev_dbg(gp2ap002->dev, "read %d mA from ADC\n", res); + + /* ensure we don't under/overflow */ + res = clamp(res, 0, (int)ARRAY_SIZE(gp2ap002_illuminance_table) - 1); + lux = gp2ap002_illuminance_table[res]; + + return (int)lux; +} + +static int gp2ap002_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev); + int ret; + + pm_runtime_get_sync(gp2ap002->dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + ret = gp2ap002_get_lux(gp2ap002); + if (ret < 0) + goto out; + *val = ret; + ret = IIO_VAL_INT; + goto out; + default: + ret = -EINVAL; + goto out; + } + default: + ret = -EINVAL; + } + +out: + pm_runtime_put_autosuspend(gp2ap002->dev); + + return ret; +} + +static int gp2ap002_init(struct gp2ap002 *gp2ap002) +{ + int ret; + + /* Set up the IR LED resistance */ + ret = regmap_write(gp2ap002->map, GP2AP002_GAIN, + GP2AP002_GAIN_LED_NORMAL); + if (ret) { + dev_err(gp2ap002->dev, "error setting up LED gain\n"); + return ret; + } + ret = regmap_write(gp2ap002->map, GP2AP002_HYS, gp2ap002->hys_far); + if (ret) { + dev_err(gp2ap002->dev, + "error setting up proximity hysteresis\n"); + return ret; + } + + /* Disable internal frequency hopping */ + ret = regmap_write(gp2ap002->map, GP2AP002_CYCLE, + GP2AP002_CYCLE_OSC_INEFFECTIVE); + if (ret) { + dev_err(gp2ap002->dev, + "error setting up internal frequency hopping\n"); + return ret; + } + + /* Enable chip and IRQ, disable analog sleep */ + ret = regmap_write(gp2ap002->map, GP2AP002_OPMOD, + GP2AP002_OPMOD_SSD_OPERATING | + GP2AP002_OPMOD_VCON_IRQ); + if (ret) { + dev_err(gp2ap002->dev, "error setting up operation mode\n"); + return ret; + } + + /* Interrupt on VOUT enabled */ + ret = regmap_write(gp2ap002->map, GP2AP002_CON, + GP2AP002_CON_OCON_ENABLE); + if (ret) + dev_err(gp2ap002->dev, "error setting up VOUT control\n"); + + return ret; +} + +static int gp2ap002_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev); + + /* + * We just keep track of this internally, as it is not possible to + * query the hardware. + */ + return gp2ap002->enabled; +} + +static int gp2ap002_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev); + int ret; + + if (state == gp2ap002->enabled) + return 0; + + if (state) { + /* + * This will bring the regulators up (unless they are on + * already) and reintialize the sensor by using runtime_pm + * callbacks. + */ + ret = pm_runtime_resume_and_get(gp2ap002->dev); + if (ret) + return ret; + + } else { + pm_runtime_put_autosuspend(gp2ap002->dev); + } + + gp2ap002->enabled = state; + + return 0; +} + +static const struct iio_info gp2ap002_info = { + .read_raw = gp2ap002_read_raw, + .read_event_config = gp2ap002_read_event_config, + .write_event_config = gp2ap002_write_event_config, +}; + +static const struct iio_event_spec gp2ap002_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec gp2ap002_channels[] = { + { + .type = IIO_PROXIMITY, + .event_spec = gp2ap002_events, + .num_event_specs = ARRAY_SIZE(gp2ap002_events), + }, + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .channel = GP2AP002_ALS_CHANNEL, + }, +}; + +/* + * We need a special regmap because this hardware expects to + * write single bytes to registers but read a 16bit word on some + * variants and discard the lower 8 bits so combine + * i2c_smbus_read_word_data() with i2c_smbus_write_byte_data() + * selectively like this. + */ +static int gp2ap002_regmap_i2c_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct device *dev = context; + struct i2c_client *i2c = to_i2c_client(dev); + int ret; + + ret = i2c_smbus_read_word_data(i2c, reg); + if (ret < 0) + return ret; + + *val = (ret >> 8) & 0xFF; + + return 0; +} + +static int gp2ap002_regmap_i2c_write(void *context, unsigned int reg, + unsigned int val) +{ + struct device *dev = context; + struct i2c_client *i2c = to_i2c_client(dev); + + return i2c_smbus_write_byte_data(i2c, reg, val); +} + +static const struct regmap_bus gp2ap002_regmap_bus = { + .reg_read = gp2ap002_regmap_i2c_read, + .reg_write = gp2ap002_regmap_i2c_write, +}; + +static int gp2ap002_probe(struct i2c_client *client) +{ + struct gp2ap002 *gp2ap002; + struct iio_dev *indio_dev; + struct device *dev = &client->dev; + enum iio_chan_type ch_type; + static const struct regmap_config config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = GP2AP002_CON, + }; + struct regmap *regmap; + int num_chan; + const char *compat; + u8 val; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*gp2ap002)); + if (!indio_dev) + return -ENOMEM; + i2c_set_clientdata(client, indio_dev); + + gp2ap002 = iio_priv(indio_dev); + gp2ap002->dev = dev; + + /* + * Check the device compatible like this makes it possible to use + * ACPI PRP0001 for registering the sensor using device tree + * properties. + */ + ret = device_property_read_string(dev, "compatible", &compat); + if (ret) { + dev_err(dev, "cannot check compatible\n"); + return ret; + } + gp2ap002->is_gp2ap002s00f = !strcmp(compat, "sharp,gp2ap002s00f"); + + regmap = devm_regmap_init(dev, &gp2ap002_regmap_bus, dev, &config); + if (IS_ERR(regmap)) { + dev_err(dev, "Failed to register i2c regmap %ld\n", PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + gp2ap002->map = regmap; + + /* + * The hysteresis settings are coded into the device tree as values + * to be written into the hysteresis register. The datasheet defines + * modes "A", "B1" and "B2" with fixed values to be use but vendor + * code trees for actual devices are tweaking these values and refer to + * modes named things like "B1.5". To be able to support any devices, + * we allow passing an arbitrary hysteresis setting for "near" and + * "far". + */ + + /* Check the device tree for the IR LED hysteresis */ + ret = device_property_read_u8(dev, "sharp,proximity-far-hysteresis", + &val); + if (ret) { + dev_err(dev, "failed to obtain proximity far setting\n"); + return ret; + } + dev_dbg(dev, "proximity far setting %02x\n", val); + gp2ap002->hys_far = val; + + ret = device_property_read_u8(dev, "sharp,proximity-close-hysteresis", + &val); + if (ret) { + dev_err(dev, "failed to obtain proximity close setting\n"); + return ret; + } + dev_dbg(dev, "proximity close setting %02x\n", val); + gp2ap002->hys_close = val; + + /* The GP2AP002A00F has a light sensor too */ + if (!gp2ap002->is_gp2ap002s00f) { + gp2ap002->alsout = devm_iio_channel_get(dev, "alsout"); + if (IS_ERR(gp2ap002->alsout)) { + ret = PTR_ERR(gp2ap002->alsout); + ret = (ret == -ENODEV) ? -EPROBE_DEFER : ret; + return dev_err_probe(dev, ret, "failed to get ALSOUT ADC channel\n"); + } + ret = iio_get_channel_type(gp2ap002->alsout, &ch_type); + if (ret < 0) + return ret; + if (ch_type != IIO_CURRENT) { + dev_err(dev, + "wrong type of IIO channel specified for ALSOUT\n"); + return -EINVAL; + } + } + + gp2ap002->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(gp2ap002->vdd)) + return dev_err_probe(dev, PTR_ERR(gp2ap002->vdd), + "failed to get VDD regulator\n"); + + gp2ap002->vio = devm_regulator_get(dev, "vio"); + if (IS_ERR(gp2ap002->vio)) + return dev_err_probe(dev, PTR_ERR(gp2ap002->vio), + "failed to get VIO regulator\n"); + + /* Operating voltage 2.4V .. 3.6V according to datasheet */ + ret = regulator_set_voltage(gp2ap002->vdd, 2400000, 3600000); + if (ret) { + dev_err(dev, "failed to sett VDD voltage\n"); + return ret; + } + + /* VIO should be between 1.65V and VDD */ + ret = regulator_get_voltage(gp2ap002->vdd); + if (ret < 0) { + dev_err(dev, "failed to get VDD voltage\n"); + return ret; + } + ret = regulator_set_voltage(gp2ap002->vio, 1650000, ret); + if (ret) { + dev_err(dev, "failed to set VIO voltage\n"); + return ret; + } + + ret = regulator_enable(gp2ap002->vdd); + if (ret) { + dev_err(dev, "failed to enable VDD regulator\n"); + return ret; + } + ret = regulator_enable(gp2ap002->vio); + if (ret) { + dev_err(dev, "failed to enable VIO regulator\n"); + goto out_disable_vdd; + } + + msleep(20); + + /* + * Initialize the device and signal to runtime PM that now we are + * definitely up and using power. + */ + ret = gp2ap002_init(gp2ap002); + if (ret) { + dev_err(dev, "initialization failed\n"); + goto out_disable_vio; + } + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + gp2ap002->enabled = false; + + ret = devm_request_threaded_irq(dev, client->irq, NULL, + gp2ap002_prox_irq, IRQF_ONESHOT, + "gp2ap002", indio_dev); + if (ret) + goto out_put_pm; + gp2ap002->irq = client->irq; + + /* + * As the device takes 20 ms + regulator delay to come up with a fresh + * measurement after power-on, do not shut it down unnecessarily. + * Set autosuspend to a one second. + */ + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + pm_runtime_put(dev); + + indio_dev->info = &gp2ap002_info; + indio_dev->name = "gp2ap002"; + indio_dev->channels = gp2ap002_channels; + /* Skip light channel for the proximity-only sensor */ + num_chan = ARRAY_SIZE(gp2ap002_channels); + if (gp2ap002->is_gp2ap002s00f) + num_chan--; + indio_dev->num_channels = num_chan; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret) + goto out_disable_pm; + dev_dbg(dev, "Sharp GP2AP002 probed successfully\n"); + + return 0; + +out_put_pm: + pm_runtime_put_noidle(dev); +out_disable_pm: + pm_runtime_disable(dev); +out_disable_vio: + regulator_disable(gp2ap002->vio); +out_disable_vdd: + regulator_disable(gp2ap002->vdd); + return ret; +} + +static void gp2ap002_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev); + struct device *dev = &client->dev; + + pm_runtime_get_sync(dev); + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + iio_device_unregister(indio_dev); + regulator_disable(gp2ap002->vio); + regulator_disable(gp2ap002->vdd); +} + +static int gp2ap002_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev); + int ret; + + /* Deactivate the IRQ */ + disable_irq(gp2ap002->irq); + + /* Disable chip and IRQ, everything off */ + ret = regmap_write(gp2ap002->map, GP2AP002_OPMOD, 0x00); + if (ret) { + enable_irq(gp2ap002->irq); + dev_err(gp2ap002->dev, "error setting up operation mode\n"); + return ret; + } + /* + * As these regulators may be shared, at least we are now in + * sleep even if the regulators aren't really turned off. + */ + regulator_disable(gp2ap002->vio); + regulator_disable(gp2ap002->vdd); + + return 0; +} + +static int gp2ap002_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(gp2ap002->vdd); + if (ret) { + dev_err(dev, "failed to enable VDD regulator in resume path\n"); + return ret; + } + ret = regulator_enable(gp2ap002->vio); + if (ret) { + dev_err(dev, "failed to enable VIO regulator in resume path\n"); + goto out_disable_vdd; + } + + msleep(20); + + ret = gp2ap002_init(gp2ap002); + if (ret) { + dev_err(dev, "re-initialization failed\n"); + goto out_disable_vio; + } + + /* Re-activate the IRQ */ + enable_irq(gp2ap002->irq); + + return 0; + +out_disable_vio: + regulator_disable(gp2ap002->vio); +out_disable_vdd: + regulator_disable(gp2ap002->vdd); + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(gp2ap002_dev_pm_ops, gp2ap002_runtime_suspend, + gp2ap002_runtime_resume, NULL); + +static const struct i2c_device_id gp2ap002_id_table[] = { + { .name = "gp2ap002" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, gp2ap002_id_table); + +static const struct of_device_id gp2ap002_of_match[] = { + { .compatible = "sharp,gp2ap002a00f" }, + { .compatible = "sharp,gp2ap002s00f" }, + { } +}; +MODULE_DEVICE_TABLE(of, gp2ap002_of_match); + +static struct i2c_driver gp2ap002_driver = { + .driver = { + .name = "gp2ap002", + .of_match_table = gp2ap002_of_match, + .pm = pm_ptr(&gp2ap002_dev_pm_ops), + }, + .probe = gp2ap002_probe, + .remove = gp2ap002_remove, + .id_table = gp2ap002_id_table, +}; +module_i2c_driver(gp2ap002_driver); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); +MODULE_DESCRIPTION("GP2AP002 ambient light and proximity sensor driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_CONSUMER"); diff --git a/drivers/iio/light/gp2ap020a00f.c b/drivers/iio/light/gp2ap020a00f.c new file mode 100644 index 000000000..63591b7ec --- /dev/null +++ b/drivers/iio/light/gp2ap020a00f.c @@ -0,0 +1,1551 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Jacek Anaszewski <j.anaszewski@samsung.com> + * + * IIO features supported by the driver: + * + * Read-only raw channels: + * - illuminance_clear [lux] + * - illuminance_ir + * - proximity + * + * Triggered buffer: + * - illuminance_clear + * - illuminance_ir + * - proximity + * + * Events: + * - illuminance_clear (rising and falling) + * - proximity (rising and falling) + * - both falling and rising thresholds for the proximity events + * must be set to the values greater than 0. + * + * The driver supports triggered buffers for all the three + * channels as well as high and low threshold events for the + * illuminance_clear and proxmimity channels. Triggers + * can be enabled simultaneously with both illuminance_clear + * events. Proximity events cannot be enabled simultaneously + * with any triggers or illuminance events. Enabling/disabling + * one of the proximity events automatically enables/disables + * the other one. + */ + +#include <linux/cleanup.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irq_work.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/unaligned.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +/* Registers */ +#define GP2AP020A00F_OP_REG 0x00 /* Basic operations */ +#define GP2AP020A00F_ALS_REG 0x01 /* ALS related settings */ +#define GP2AP020A00F_PS_REG 0x02 /* PS related settings */ +#define GP2AP020A00F_LED_REG 0x03 /* LED reg */ +#define GP2AP020A00F_TL_L_REG 0x04 /* ALS: Threshold low LSB */ +#define GP2AP020A00F_TL_H_REG 0x05 /* ALS: Threshold low MSB */ +#define GP2AP020A00F_TH_L_REG 0x06 /* ALS: Threshold high LSB */ +#define GP2AP020A00F_TH_H_REG 0x07 /* ALS: Threshold high MSB */ +#define GP2AP020A00F_PL_L_REG 0x08 /* PS: Threshold low LSB */ +#define GP2AP020A00F_PL_H_REG 0x09 /* PS: Threshold low MSB */ +#define GP2AP020A00F_PH_L_REG 0x0a /* PS: Threshold high LSB */ +#define GP2AP020A00F_PH_H_REG 0x0b /* PS: Threshold high MSB */ +#define GP2AP020A00F_D0_L_REG 0x0c /* ALS result: Clear/Illuminance LSB */ +#define GP2AP020A00F_D0_H_REG 0x0d /* ALS result: Clear/Illuminance MSB */ +#define GP2AP020A00F_D1_L_REG 0x0e /* ALS result: IR LSB */ +#define GP2AP020A00F_D1_H_REG 0x0f /* ALS result: IR LSB */ +#define GP2AP020A00F_D2_L_REG 0x10 /* PS result LSB */ +#define GP2AP020A00F_D2_H_REG 0x11 /* PS result MSB */ +#define GP2AP020A00F_NUM_REGS 0x12 /* Number of registers */ + +/* OP_REG bits */ +#define GP2AP020A00F_OP3_MASK 0x80 /* Software shutdown */ +#define GP2AP020A00F_OP3_SHUTDOWN 0x00 +#define GP2AP020A00F_OP3_OPERATION 0x80 +#define GP2AP020A00F_OP2_MASK 0x40 /* Auto shutdown/Continuous mode */ +#define GP2AP020A00F_OP2_AUTO_SHUTDOWN 0x00 +#define GP2AP020A00F_OP2_CONT_OPERATION 0x40 +#define GP2AP020A00F_OP_MASK 0x30 /* Operating mode selection */ +#define GP2AP020A00F_OP_ALS_AND_PS 0x00 +#define GP2AP020A00F_OP_ALS 0x10 +#define GP2AP020A00F_OP_PS 0x20 +#define GP2AP020A00F_OP_DEBUG 0x30 +#define GP2AP020A00F_PROX_MASK 0x08 /* PS: detection/non-detection */ +#define GP2AP020A00F_PROX_NON_DETECT 0x00 +#define GP2AP020A00F_PROX_DETECT 0x08 +#define GP2AP020A00F_FLAG_P 0x04 /* PS: interrupt result */ +#define GP2AP020A00F_FLAG_A 0x02 /* ALS: interrupt result */ +#define GP2AP020A00F_TYPE_MASK 0x01 /* Output data type selection */ +#define GP2AP020A00F_TYPE_MANUAL_CALC 0x00 +#define GP2AP020A00F_TYPE_AUTO_CALC 0x01 + +/* ALS_REG bits */ +#define GP2AP020A00F_PRST_MASK 0xc0 /* Number of measurement cycles */ +#define GP2AP020A00F_PRST_ONCE 0x00 +#define GP2AP020A00F_PRST_4_CYCLES 0x40 +#define GP2AP020A00F_PRST_8_CYCLES 0x80 +#define GP2AP020A00F_PRST_16_CYCLES 0xc0 +#define GP2AP020A00F_RES_A_MASK 0x38 /* ALS: Resolution */ +#define GP2AP020A00F_RES_A_800ms 0x00 +#define GP2AP020A00F_RES_A_400ms 0x08 +#define GP2AP020A00F_RES_A_200ms 0x10 +#define GP2AP020A00F_RES_A_100ms 0x18 +#define GP2AP020A00F_RES_A_25ms 0x20 +#define GP2AP020A00F_RES_A_6_25ms 0x28 +#define GP2AP020A00F_RES_A_1_56ms 0x30 +#define GP2AP020A00F_RES_A_0_39ms 0x38 +#define GP2AP020A00F_RANGE_A_MASK 0x07 /* ALS: Max measurable range */ +#define GP2AP020A00F_RANGE_A_x1 0x00 +#define GP2AP020A00F_RANGE_A_x2 0x01 +#define GP2AP020A00F_RANGE_A_x4 0x02 +#define GP2AP020A00F_RANGE_A_x8 0x03 +#define GP2AP020A00F_RANGE_A_x16 0x04 +#define GP2AP020A00F_RANGE_A_x32 0x05 +#define GP2AP020A00F_RANGE_A_x64 0x06 +#define GP2AP020A00F_RANGE_A_x128 0x07 + +/* PS_REG bits */ +#define GP2AP020A00F_ALC_MASK 0x80 /* Auto light cancel */ +#define GP2AP020A00F_ALC_ON 0x80 +#define GP2AP020A00F_ALC_OFF 0x00 +#define GP2AP020A00F_INTTYPE_MASK 0x40 /* Interrupt type setting */ +#define GP2AP020A00F_INTTYPE_LEVEL 0x00 +#define GP2AP020A00F_INTTYPE_PULSE 0x40 +#define GP2AP020A00F_RES_P_MASK 0x38 /* PS: Resolution */ +#define GP2AP020A00F_RES_P_800ms_x2 0x00 +#define GP2AP020A00F_RES_P_400ms_x2 0x08 +#define GP2AP020A00F_RES_P_200ms_x2 0x10 +#define GP2AP020A00F_RES_P_100ms_x2 0x18 +#define GP2AP020A00F_RES_P_25ms_x2 0x20 +#define GP2AP020A00F_RES_P_6_25ms_x2 0x28 +#define GP2AP020A00F_RES_P_1_56ms_x2 0x30 +#define GP2AP020A00F_RES_P_0_39ms_x2 0x38 +#define GP2AP020A00F_RANGE_P_MASK 0x07 /* PS: Max measurable range */ +#define GP2AP020A00F_RANGE_P_x1 0x00 +#define GP2AP020A00F_RANGE_P_x2 0x01 +#define GP2AP020A00F_RANGE_P_x4 0x02 +#define GP2AP020A00F_RANGE_P_x8 0x03 +#define GP2AP020A00F_RANGE_P_x16 0x04 +#define GP2AP020A00F_RANGE_P_x32 0x05 +#define GP2AP020A00F_RANGE_P_x64 0x06 +#define GP2AP020A00F_RANGE_P_x128 0x07 + +/* LED reg bits */ +#define GP2AP020A00F_INTVAL_MASK 0xc0 /* Intermittent operating */ +#define GP2AP020A00F_INTVAL_0 0x00 +#define GP2AP020A00F_INTVAL_4 0x40 +#define GP2AP020A00F_INTVAL_8 0x80 +#define GP2AP020A00F_INTVAL_16 0xc0 +#define GP2AP020A00F_IS_MASK 0x30 /* ILED drive peak current */ +#define GP2AP020A00F_IS_13_8mA 0x00 +#define GP2AP020A00F_IS_27_5mA 0x10 +#define GP2AP020A00F_IS_55mA 0x20 +#define GP2AP020A00F_IS_110mA 0x30 +#define GP2AP020A00F_PIN_MASK 0x0c /* INT terminal setting */ +#define GP2AP020A00F_PIN_ALS_OR_PS 0x00 +#define GP2AP020A00F_PIN_ALS 0x04 +#define GP2AP020A00F_PIN_PS 0x08 +#define GP2AP020A00F_PIN_PS_DETECT 0x0c +#define GP2AP020A00F_FREQ_MASK 0x02 /* LED modulation frequency */ +#define GP2AP020A00F_FREQ_327_5kHz 0x00 +#define GP2AP020A00F_FREQ_81_8kHz 0x02 +#define GP2AP020A00F_RST 0x01 /* Software reset */ + +#define GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR 0 +#define GP2AP020A00F_SCAN_MODE_LIGHT_IR 1 +#define GP2AP020A00F_SCAN_MODE_PROXIMITY 2 +#define GP2AP020A00F_CHAN_TIMESTAMP 3 + +#define GP2AP020A00F_DATA_READY_TIMEOUT msecs_to_jiffies(1000) +#define GP2AP020A00F_DATA_REG(chan) (GP2AP020A00F_D0_L_REG + (chan) * 2) +#define GP2AP020A00F_THRESH_REG(th_val_id) (GP2AP020A00F_TL_L_REG + (th_val_id) * 2) +#define GP2AP020A00F_THRESH_VAL_ID(reg_addr) ((reg_addr - 4) / 2) + +#define GP2AP020A00F_SUBTRACT_MODE 0 +#define GP2AP020A00F_ADD_MODE 1 + +#define GP2AP020A00F_MAX_CHANNELS 3 + +enum gp2ap020a00f_opmode { + GP2AP020A00F_OPMODE_READ_RAW_CLEAR, + GP2AP020A00F_OPMODE_READ_RAW_IR, + GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_OPMODE_PS, + GP2AP020A00F_OPMODE_ALS_AND_PS, + GP2AP020A00F_OPMODE_PROX_DETECT, + GP2AP020A00F_OPMODE_SHUTDOWN, + GP2AP020A00F_NUM_OPMODES +}; + +enum gp2ap020a00f_cmd { + GP2AP020A00F_CMD_READ_RAW_CLEAR, + GP2AP020A00F_CMD_READ_RAW_IR, + GP2AP020A00F_CMD_READ_RAW_PROXIMITY, + GP2AP020A00F_CMD_TRIGGER_CLEAR_EN, + GP2AP020A00F_CMD_TRIGGER_CLEAR_DIS, + GP2AP020A00F_CMD_TRIGGER_IR_EN, + GP2AP020A00F_CMD_TRIGGER_IR_DIS, + GP2AP020A00F_CMD_TRIGGER_PROX_EN, + GP2AP020A00F_CMD_TRIGGER_PROX_DIS, + GP2AP020A00F_CMD_ALS_HIGH_EV_EN, + GP2AP020A00F_CMD_ALS_HIGH_EV_DIS, + GP2AP020A00F_CMD_ALS_LOW_EV_EN, + GP2AP020A00F_CMD_ALS_LOW_EV_DIS, + GP2AP020A00F_CMD_PROX_HIGH_EV_EN, + GP2AP020A00F_CMD_PROX_HIGH_EV_DIS, + GP2AP020A00F_CMD_PROX_LOW_EV_EN, + GP2AP020A00F_CMD_PROX_LOW_EV_DIS, +}; + +enum gp2ap020a00f_flags { + GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, + GP2AP020A00F_FLAG_ALS_IR_TRIGGER, + GP2AP020A00F_FLAG_PROX_TRIGGER, + GP2AP020A00F_FLAG_PROX_RISING_EV, + GP2AP020A00F_FLAG_PROX_FALLING_EV, + GP2AP020A00F_FLAG_ALS_RISING_EV, + GP2AP020A00F_FLAG_ALS_FALLING_EV, + GP2AP020A00F_FLAG_LUX_MODE_HI, + GP2AP020A00F_FLAG_DATA_READY, +}; + +enum gp2ap020a00f_thresh_val_id { + GP2AP020A00F_THRESH_TL, + GP2AP020A00F_THRESH_TH, + GP2AP020A00F_THRESH_PL, + GP2AP020A00F_THRESH_PH, +}; + +struct gp2ap020a00f_data { + struct i2c_client *client; + struct mutex lock; + char *buffer; + struct regulator *vled_reg; + unsigned long flags; + enum gp2ap020a00f_opmode cur_opmode; + struct iio_trigger *trig; + struct regmap *regmap; + unsigned int thresh_val[4]; + struct irq_work work; + wait_queue_head_t data_ready_queue; +}; + +static const u8 gp2ap020a00f_reg_init_tab[] = { + [GP2AP020A00F_OP_REG] = GP2AP020A00F_OP3_SHUTDOWN, + [GP2AP020A00F_ALS_REG] = GP2AP020A00F_RES_A_25ms | + GP2AP020A00F_RANGE_A_x8, + [GP2AP020A00F_PS_REG] = GP2AP020A00F_ALC_ON | + GP2AP020A00F_RES_P_1_56ms_x2 | + GP2AP020A00F_RANGE_P_x4, + [GP2AP020A00F_LED_REG] = GP2AP020A00F_INTVAL_0 | + GP2AP020A00F_IS_110mA | + GP2AP020A00F_FREQ_327_5kHz, + [GP2AP020A00F_TL_L_REG] = 0, + [GP2AP020A00F_TL_H_REG] = 0, + [GP2AP020A00F_TH_L_REG] = 0, + [GP2AP020A00F_TH_H_REG] = 0, + [GP2AP020A00F_PL_L_REG] = 0, + [GP2AP020A00F_PL_H_REG] = 0, + [GP2AP020A00F_PH_L_REG] = 0, + [GP2AP020A00F_PH_H_REG] = 0, +}; + +static bool gp2ap020a00f_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case GP2AP020A00F_OP_REG: + case GP2AP020A00F_D0_L_REG: + case GP2AP020A00F_D0_H_REG: + case GP2AP020A00F_D1_L_REG: + case GP2AP020A00F_D1_H_REG: + case GP2AP020A00F_D2_L_REG: + case GP2AP020A00F_D2_H_REG: + return true; + default: + return false; + } +} + +static const struct regmap_config gp2ap020a00f_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = GP2AP020A00F_D2_H_REG, + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = gp2ap020a00f_is_volatile_reg, +}; + +static const struct gp2ap020a00f_mutable_config_regs { + u8 op_reg; + u8 als_reg; + u8 ps_reg; + u8 led_reg; +} opmode_regs_settings[GP2AP020A00F_NUM_OPMODES] = { + [GP2AP020A00F_OPMODE_READ_RAW_CLEAR] = { + GP2AP020A00F_OP_ALS | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_AUTO_CALC, + GP2AP020A00F_PRST_ONCE, + GP2AP020A00F_INTTYPE_LEVEL, + GP2AP020A00F_PIN_ALS + }, + [GP2AP020A00F_OPMODE_READ_RAW_IR] = { + GP2AP020A00F_OP_ALS | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_MANUAL_CALC, + GP2AP020A00F_PRST_ONCE, + GP2AP020A00F_INTTYPE_LEVEL, + GP2AP020A00F_PIN_ALS + }, + [GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY] = { + GP2AP020A00F_OP_PS | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_MANUAL_CALC, + GP2AP020A00F_PRST_ONCE, + GP2AP020A00F_INTTYPE_LEVEL, + GP2AP020A00F_PIN_PS + }, + [GP2AP020A00F_OPMODE_PROX_DETECT] = { + GP2AP020A00F_OP_PS | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_MANUAL_CALC, + GP2AP020A00F_PRST_4_CYCLES, + GP2AP020A00F_INTTYPE_PULSE, + GP2AP020A00F_PIN_PS_DETECT + }, + [GP2AP020A00F_OPMODE_ALS] = { + GP2AP020A00F_OP_ALS | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_AUTO_CALC, + GP2AP020A00F_PRST_ONCE, + GP2AP020A00F_INTTYPE_LEVEL, + GP2AP020A00F_PIN_ALS + }, + [GP2AP020A00F_OPMODE_PS] = { + GP2AP020A00F_OP_PS | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_MANUAL_CALC, + GP2AP020A00F_PRST_4_CYCLES, + GP2AP020A00F_INTTYPE_LEVEL, + GP2AP020A00F_PIN_PS + }, + [GP2AP020A00F_OPMODE_ALS_AND_PS] = { + GP2AP020A00F_OP_ALS_AND_PS + | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_AUTO_CALC, + GP2AP020A00F_PRST_4_CYCLES, + GP2AP020A00F_INTTYPE_LEVEL, + GP2AP020A00F_PIN_ALS_OR_PS + }, + [GP2AP020A00F_OPMODE_SHUTDOWN] = { GP2AP020A00F_OP3_SHUTDOWN, }, +}; + +static int gp2ap020a00f_set_operation_mode(struct gp2ap020a00f_data *data, + enum gp2ap020a00f_opmode op) +{ + unsigned int op_reg_val; + int err; + + if (op != GP2AP020A00F_OPMODE_SHUTDOWN) { + err = regmap_read(data->regmap, GP2AP020A00F_OP_REG, + &op_reg_val); + if (err < 0) + return err; + /* + * Shutdown the device if the operation being executed entails + * mode transition. + */ + if ((opmode_regs_settings[op].op_reg & GP2AP020A00F_OP_MASK) != + (op_reg_val & GP2AP020A00F_OP_MASK)) { + /* set shutdown mode */ + err = regmap_update_bits(data->regmap, + GP2AP020A00F_OP_REG, GP2AP020A00F_OP3_MASK, + GP2AP020A00F_OP3_SHUTDOWN); + if (err < 0) + return err; + } + + err = regmap_update_bits(data->regmap, GP2AP020A00F_ALS_REG, + GP2AP020A00F_PRST_MASK, opmode_regs_settings[op].als_reg); + if (err < 0) + return err; + + err = regmap_update_bits(data->regmap, GP2AP020A00F_PS_REG, + GP2AP020A00F_INTTYPE_MASK, opmode_regs_settings[op].ps_reg); + if (err < 0) + return err; + + err = regmap_update_bits(data->regmap, GP2AP020A00F_LED_REG, + GP2AP020A00F_PIN_MASK, opmode_regs_settings[op].led_reg); + if (err < 0) + return err; + } + + /* Set OP_REG and apply operation mode (power on / off) */ + err = regmap_update_bits(data->regmap, + GP2AP020A00F_OP_REG, + GP2AP020A00F_OP_MASK | GP2AP020A00F_OP2_MASK | + GP2AP020A00F_OP3_MASK | GP2AP020A00F_TYPE_MASK, + opmode_regs_settings[op].op_reg); + if (err < 0) + return err; + + data->cur_opmode = op; + + return 0; +} + +static bool gp2ap020a00f_als_enabled(struct gp2ap020a00f_data *data) +{ + return test_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &data->flags) || + test_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &data->flags) || + test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags) || + test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags); +} + +static bool gp2ap020a00f_prox_detect_enabled(struct gp2ap020a00f_data *data) +{ + return test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags) || + test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags); +} + +static int gp2ap020a00f_write_event_threshold(struct gp2ap020a00f_data *data, + enum gp2ap020a00f_thresh_val_id th_val_id, + bool enable) +{ + __le16 thresh_buf = 0; + unsigned int thresh_reg_val; + + if (!enable) + thresh_reg_val = 0; + else if (test_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags) && + th_val_id != GP2AP020A00F_THRESH_PL && + th_val_id != GP2AP020A00F_THRESH_PH) + /* + * For the high lux mode ALS threshold has to be scaled down + * to allow for proper comparison with the output value. + */ + thresh_reg_val = data->thresh_val[th_val_id] / 16; + else + thresh_reg_val = min(data->thresh_val[th_val_id], 16000U); + + thresh_buf = cpu_to_le16(thresh_reg_val); + + return regmap_bulk_write(data->regmap, + GP2AP020A00F_THRESH_REG(th_val_id), + &thresh_buf, sizeof(thresh_buf)); +} + +static int gp2ap020a00f_alter_opmode(struct gp2ap020a00f_data *data, + enum gp2ap020a00f_opmode diff_mode, int add_sub) +{ + enum gp2ap020a00f_opmode new_mode; + + if (diff_mode != GP2AP020A00F_OPMODE_ALS && + diff_mode != GP2AP020A00F_OPMODE_PS) + return -EINVAL; + + if (add_sub == GP2AP020A00F_ADD_MODE) { + if (data->cur_opmode == GP2AP020A00F_OPMODE_SHUTDOWN) + new_mode = diff_mode; + else + new_mode = GP2AP020A00F_OPMODE_ALS_AND_PS; + } else { + if (data->cur_opmode == GP2AP020A00F_OPMODE_ALS_AND_PS) + new_mode = (diff_mode == GP2AP020A00F_OPMODE_ALS) ? + GP2AP020A00F_OPMODE_PS : + GP2AP020A00F_OPMODE_ALS; + else + new_mode = GP2AP020A00F_OPMODE_SHUTDOWN; + } + + return gp2ap020a00f_set_operation_mode(data, new_mode); +} + +static int gp2ap020a00f_exec_cmd(struct gp2ap020a00f_data *data, + enum gp2ap020a00f_cmd cmd) +{ + int err; + + switch (cmd) { + case GP2AP020A00F_CMD_READ_RAW_CLEAR: + if (data->cur_opmode != GP2AP020A00F_OPMODE_SHUTDOWN) + return -EBUSY; + return gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_READ_RAW_CLEAR); + case GP2AP020A00F_CMD_READ_RAW_IR: + if (data->cur_opmode != GP2AP020A00F_OPMODE_SHUTDOWN) + return -EBUSY; + return gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_READ_RAW_IR); + case GP2AP020A00F_CMD_READ_RAW_PROXIMITY: + if (data->cur_opmode != GP2AP020A00F_OPMODE_SHUTDOWN) + return -EBUSY; + return gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY); + case GP2AP020A00F_CMD_TRIGGER_CLEAR_EN: + if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) + return -EBUSY; + if (!gp2ap020a00f_als_enabled(data)) + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_ADD_MODE); + else + err = 0; + set_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &data->flags); + return err; + case GP2AP020A00F_CMD_TRIGGER_CLEAR_DIS: + clear_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &data->flags); + if (gp2ap020a00f_als_enabled(data)) + break; + return gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_SUBTRACT_MODE); + case GP2AP020A00F_CMD_TRIGGER_IR_EN: + if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) + return -EBUSY; + if (!gp2ap020a00f_als_enabled(data)) + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_ADD_MODE); + else + err = 0; + set_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &data->flags); + return err; + case GP2AP020A00F_CMD_TRIGGER_IR_DIS: + clear_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &data->flags); + if (gp2ap020a00f_als_enabled(data)) + break; + return gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_SUBTRACT_MODE); + case GP2AP020A00F_CMD_TRIGGER_PROX_EN: + if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) + return -EBUSY; + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_PS, + GP2AP020A00F_ADD_MODE); + set_bit(GP2AP020A00F_FLAG_PROX_TRIGGER, &data->flags); + return err; + case GP2AP020A00F_CMD_TRIGGER_PROX_DIS: + clear_bit(GP2AP020A00F_FLAG_PROX_TRIGGER, &data->flags); + return gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_PS, + GP2AP020A00F_SUBTRACT_MODE); + case GP2AP020A00F_CMD_ALS_HIGH_EV_EN: + if (test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags)) + return 0; + if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) + return -EBUSY; + if (!gp2ap020a00f_als_enabled(data)) { + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_ADD_MODE); + if (err < 0) + return err; + } + set_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags); + return gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TH, true); + case GP2AP020A00F_CMD_ALS_HIGH_EV_DIS: + if (!test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags)) + return 0; + clear_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags); + if (!gp2ap020a00f_als_enabled(data)) { + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_SUBTRACT_MODE); + if (err < 0) + return err; + } + return gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TH, false); + case GP2AP020A00F_CMD_ALS_LOW_EV_EN: + if (test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags)) + return 0; + if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) + return -EBUSY; + if (!gp2ap020a00f_als_enabled(data)) { + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_ADD_MODE); + if (err < 0) + return err; + } + set_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags); + return gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TL, true); + case GP2AP020A00F_CMD_ALS_LOW_EV_DIS: + if (!test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags)) + return 0; + clear_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags); + if (!gp2ap020a00f_als_enabled(data)) { + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_SUBTRACT_MODE); + if (err < 0) + return err; + } + return gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TL, false); + case GP2AP020A00F_CMD_PROX_HIGH_EV_EN: + if (test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags)) + return 0; + if (gp2ap020a00f_als_enabled(data) || + data->cur_opmode == GP2AP020A00F_OPMODE_PS) + return -EBUSY; + if (!gp2ap020a00f_prox_detect_enabled(data)) { + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_PROX_DETECT); + if (err < 0) + return err; + } + set_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags); + return gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_PH, true); + case GP2AP020A00F_CMD_PROX_HIGH_EV_DIS: + if (!test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags)) + return 0; + clear_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags); + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_SHUTDOWN); + if (err < 0) + return err; + return gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_PH, false); + case GP2AP020A00F_CMD_PROX_LOW_EV_EN: + if (test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags)) + return 0; + if (gp2ap020a00f_als_enabled(data) || + data->cur_opmode == GP2AP020A00F_OPMODE_PS) + return -EBUSY; + if (!gp2ap020a00f_prox_detect_enabled(data)) { + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_PROX_DETECT); + if (err < 0) + return err; + } + set_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags); + return gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_PL, true); + case GP2AP020A00F_CMD_PROX_LOW_EV_DIS: + if (!test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags)) + return 0; + clear_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags); + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_SHUTDOWN); + if (err < 0) + return err; + return gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_PL, false); + } + + return 0; +} + +static int wait_conversion_complete_irq(struct gp2ap020a00f_data *data) +{ + int ret; + + ret = wait_event_timeout(data->data_ready_queue, + test_bit(GP2AP020A00F_FLAG_DATA_READY, + &data->flags), + GP2AP020A00F_DATA_READY_TIMEOUT); + clear_bit(GP2AP020A00F_FLAG_DATA_READY, &data->flags); + + return ret > 0 ? 0 : -ETIME; +} + +static int gp2ap020a00f_read_output(struct gp2ap020a00f_data *data, + unsigned int output_reg, int *val) +{ + __le16 reg_buf; + int err; + + err = wait_conversion_complete_irq(data); + if (err < 0) + dev_dbg(&data->client->dev, "data ready timeout\n"); + + err = regmap_bulk_read(data->regmap, output_reg, ®_buf, sizeof(reg_buf)); + if (err < 0) + return err; + + *val = le16_to_cpu(reg_buf); + + return err; +} + +static bool gp2ap020a00f_adjust_lux_mode(struct gp2ap020a00f_data *data, + int output_val) +{ + u8 new_range = 0xff; + int err; + + if (!test_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags)) { + if (output_val > 16000) { + set_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags); + new_range = GP2AP020A00F_RANGE_A_x128; + } + } else { + if (output_val < 1000) { + clear_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags); + new_range = GP2AP020A00F_RANGE_A_x8; + } + } + + if (new_range != 0xff) { + /* Clear als threshold registers to avoid spurious + * events caused by lux mode transition. + */ + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TH, false); + if (err < 0) { + dev_err(&data->client->dev, + "Clearing als threshold register failed.\n"); + return false; + } + + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TL, false); + if (err < 0) { + dev_err(&data->client->dev, + "Clearing als threshold register failed.\n"); + return false; + } + + /* Change lux mode */ + err = regmap_update_bits(data->regmap, + GP2AP020A00F_OP_REG, + GP2AP020A00F_OP3_MASK, + GP2AP020A00F_OP3_SHUTDOWN); + + if (err < 0) { + dev_err(&data->client->dev, + "Shutting down the device failed.\n"); + return false; + } + + err = regmap_update_bits(data->regmap, + GP2AP020A00F_ALS_REG, + GP2AP020A00F_RANGE_A_MASK, + new_range); + + if (err < 0) { + dev_err(&data->client->dev, + "Adjusting device lux mode failed.\n"); + return false; + } + + err = regmap_update_bits(data->regmap, + GP2AP020A00F_OP_REG, + GP2AP020A00F_OP3_MASK, + GP2AP020A00F_OP3_OPERATION); + + if (err < 0) { + dev_err(&data->client->dev, + "Powering up the device failed.\n"); + return false; + } + + /* Adjust als threshold register values to the new lux mode */ + if (test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags)) { + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TH, true); + if (err < 0) { + dev_err(&data->client->dev, + "Adjusting als threshold value failed.\n"); + return false; + } + } + + if (test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags)) { + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TL, true); + if (err < 0) { + dev_err(&data->client->dev, + "Adjusting als threshold value failed.\n"); + return false; + } + } + + return true; + } + + return false; +} + +static void gp2ap020a00f_output_to_lux(struct gp2ap020a00f_data *data, + int *output_val) +{ + if (test_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags)) + *output_val *= 16; +} + +static void gp2ap020a00f_iio_trigger_work(struct irq_work *work) +{ + struct gp2ap020a00f_data *data = + container_of(work, struct gp2ap020a00f_data, work); + + iio_trigger_poll(data->trig); +} + +static irqreturn_t gp2ap020a00f_prox_sensing_handler(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct gp2ap020a00f_data *priv = iio_priv(indio_dev); + unsigned int op_reg_val; + int ret; + + /* Read interrupt flags */ + ret = regmap_read(priv->regmap, GP2AP020A00F_OP_REG, &op_reg_val); + if (ret < 0) + return IRQ_HANDLED; + + if (gp2ap020a00f_prox_detect_enabled(priv)) { + if (op_reg_val & GP2AP020A00F_PROX_DETECT) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE( + IIO_PROXIMITY, + GP2AP020A00F_SCAN_MODE_PROXIMITY, + IIO_EV_TYPE_ROC, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + } else { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE( + IIO_PROXIMITY, + GP2AP020A00F_SCAN_MODE_PROXIMITY, + IIO_EV_TYPE_ROC, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + } + } + + return IRQ_HANDLED; +} + +static irqreturn_t gp2ap020a00f_thresh_event_handler(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct gp2ap020a00f_data *priv = iio_priv(indio_dev); + unsigned int output_val, op_reg_val; + __le16 d0_reg_buf; + u8 op_reg_flags; + int thresh_val_id, ret; + + /* Read interrupt flags */ + ret = regmap_read(priv->regmap, GP2AP020A00F_OP_REG, &op_reg_val); + if (ret < 0) + goto done; + + op_reg_flags = op_reg_val & (GP2AP020A00F_FLAG_A | GP2AP020A00F_FLAG_P + | GP2AP020A00F_PROX_DETECT); + + op_reg_val &= (~GP2AP020A00F_FLAG_A & ~GP2AP020A00F_FLAG_P + & ~GP2AP020A00F_PROX_DETECT); + + /* Clear interrupt flags (if not in INTTYPE_PULSE mode) */ + if (priv->cur_opmode != GP2AP020A00F_OPMODE_PROX_DETECT) { + ret = regmap_write(priv->regmap, GP2AP020A00F_OP_REG, op_reg_val); + if (ret < 0) + goto done; + } + + if (op_reg_flags & GP2AP020A00F_FLAG_A) { + /* Check D0 register to assess if the lux mode + * transition is required. + */ + ret = regmap_bulk_read(priv->regmap, GP2AP020A00F_D0_L_REG, + &d0_reg_buf, sizeof(d0_reg_buf)); + if (ret < 0) + goto done; + + output_val = le16_to_cpu(d0_reg_buf); + + if (gp2ap020a00f_adjust_lux_mode(priv, output_val)) + goto done; + + gp2ap020a00f_output_to_lux(priv, &output_val); + + /* + * We need to check output value to distinguish + * between high and low ambient light threshold event. + */ + if (test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &priv->flags)) { + thresh_val_id = + GP2AP020A00F_THRESH_VAL_ID(GP2AP020A00F_TH_L_REG); + if (output_val > priv->thresh_val[thresh_val_id]) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE( + IIO_LIGHT, + GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR, + IIO_MOD_LIGHT_CLEAR, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + } + + if (test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &priv->flags)) { + thresh_val_id = + GP2AP020A00F_THRESH_VAL_ID(GP2AP020A00F_TL_L_REG); + if (output_val < priv->thresh_val[thresh_val_id]) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE( + IIO_LIGHT, + GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR, + IIO_MOD_LIGHT_CLEAR, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + } + } + + if (priv->cur_opmode == GP2AP020A00F_OPMODE_READ_RAW_CLEAR || + priv->cur_opmode == GP2AP020A00F_OPMODE_READ_RAW_IR || + priv->cur_opmode == GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY) { + set_bit(GP2AP020A00F_FLAG_DATA_READY, &priv->flags); + wake_up(&priv->data_ready_queue); + goto done; + } + + if (test_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &priv->flags) || + test_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &priv->flags) || + test_bit(GP2AP020A00F_FLAG_PROX_TRIGGER, &priv->flags)) + /* This fires off the trigger. */ + irq_work_queue(&priv->work); + +done: + return IRQ_HANDLED; +} + +static irqreturn_t gp2ap020a00f_trigger_handler(int irq, void *data) +{ + struct iio_poll_func *pf = data; + struct iio_dev *indio_dev = pf->indio_dev; + struct gp2ap020a00f_data *priv = iio_priv(indio_dev); + size_t d_size = 0; + int i, out_val, ret; + + iio_for_each_active_channel(indio_dev, i) { + ret = regmap_bulk_read(priv->regmap, GP2AP020A00F_DATA_REG(i), + &priv->buffer[d_size], 2); + if (ret < 0) + goto done; + + if (i == GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR || + i == GP2AP020A00F_SCAN_MODE_LIGHT_IR) { + out_val = get_unaligned_le16(&priv->buffer[d_size]); + gp2ap020a00f_output_to_lux(priv, &out_val); + put_unaligned_le32(out_val, &priv->buffer[d_size]); + d_size += 4; + } else { + d_size += 2; + } + } + + iio_push_to_buffers_with_timestamp(indio_dev, priv->buffer, pf->timestamp); +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int gp2ap020a00f_get_thresh_reg(const struct iio_chan_spec *chan, + enum iio_event_direction event_dir) +{ + switch (chan->type) { + case IIO_PROXIMITY: + if (event_dir == IIO_EV_DIR_RISING) + return GP2AP020A00F_PH_L_REG; + else + return GP2AP020A00F_PL_L_REG; + case IIO_LIGHT: + if (event_dir == IIO_EV_DIR_RISING) + return GP2AP020A00F_TH_L_REG; + else + return GP2AP020A00F_TL_L_REG; + default: + return -EINVAL; + } +} + +static int gp2ap020a00f_write_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + bool event_en = false; + u8 thresh_val_id; + int thresh_reg_l; + + guard(mutex)(&data->lock); + + thresh_reg_l = gp2ap020a00f_get_thresh_reg(chan, dir); + if (thresh_reg_l < 0) + return thresh_reg_l; + + thresh_val_id = GP2AP020A00F_THRESH_VAL_ID(thresh_reg_l); + if (thresh_val_id > GP2AP020A00F_THRESH_PH) + return -EINVAL; + + switch (thresh_reg_l) { + case GP2AP020A00F_TH_L_REG: + event_en = test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags); + break; + case GP2AP020A00F_TL_L_REG: + event_en = test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags); + break; + case GP2AP020A00F_PH_L_REG: + if (val == 0) + return -EINVAL; + + event_en = test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags); + break; + case GP2AP020A00F_PL_L_REG: + if (val == 0) + return -EINVAL; + + event_en = test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags); + break; + } + + data->thresh_val[thresh_val_id] = val; + return gp2ap020a00f_write_event_threshold(data, thresh_val_id, event_en); +} + +static int gp2ap020a00f_read_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + int thresh_reg_l; + + guard(mutex)(&data->lock); + + thresh_reg_l = gp2ap020a00f_get_thresh_reg(chan, dir); + if (thresh_reg_l < 0) + return thresh_reg_l; + if (thresh_reg_l > GP2AP020A00F_PH_L_REG) + return -EINVAL; + + *val = data->thresh_val[GP2AP020A00F_THRESH_VAL_ID(thresh_reg_l)]; + + return IIO_VAL_INT; +} + +static int gp2ap020a00f_write_prox_event_config(struct iio_dev *indio_dev, + int state) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + enum gp2ap020a00f_cmd cmd_high_ev, cmd_low_ev; + int err; + + cmd_high_ev = state ? GP2AP020A00F_CMD_PROX_HIGH_EV_EN : + GP2AP020A00F_CMD_PROX_HIGH_EV_DIS; + cmd_low_ev = state ? GP2AP020A00F_CMD_PROX_LOW_EV_EN : + GP2AP020A00F_CMD_PROX_LOW_EV_DIS; + + /* + * In order to enable proximity detection feature in the device + * both high and low threshold registers have to be written + * with different values, greater than zero. + */ + if (state) { + if (data->thresh_val[GP2AP020A00F_THRESH_PL] == 0) + return -EINVAL; + + if (data->thresh_val[GP2AP020A00F_THRESH_PH] == 0) + return -EINVAL; + } + + err = gp2ap020a00f_exec_cmd(data, cmd_high_ev); + if (err < 0) + return err; + + err = gp2ap020a00f_exec_cmd(data, cmd_low_ev); + if (err < 0) + return err; + + free_irq(data->client->irq, indio_dev); + + if (state) + err = request_threaded_irq(data->client->irq, NULL, + &gp2ap020a00f_prox_sensing_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "gp2ap020a00f_prox_sensing", + indio_dev); + else { + err = request_threaded_irq(data->client->irq, NULL, + &gp2ap020a00f_thresh_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "gp2ap020a00f_thresh_event", + indio_dev); + } + + return err; +} + +static int gp2ap020a00f_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + enum gp2ap020a00f_cmd cmd; + + guard(mutex)(&data->lock); + + switch (chan->type) { + case IIO_PROXIMITY: + return gp2ap020a00f_write_prox_event_config(indio_dev, state); + case IIO_LIGHT: + if (dir == IIO_EV_DIR_RISING) { + cmd = state ? GP2AP020A00F_CMD_ALS_HIGH_EV_EN : + GP2AP020A00F_CMD_ALS_HIGH_EV_DIS; + return gp2ap020a00f_exec_cmd(data, cmd); + } else { + cmd = state ? GP2AP020A00F_CMD_ALS_LOW_EV_EN : + GP2AP020A00F_CMD_ALS_LOW_EV_DIS; + return gp2ap020a00f_exec_cmd(data, cmd); + } + default: + return -EINVAL; + } +} + +static int gp2ap020a00f_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + + switch (chan->type) { + case IIO_PROXIMITY: + if (dir == IIO_EV_DIR_RISING) + return test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags); + else + return test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags); + case IIO_LIGHT: + if (dir == IIO_EV_DIR_RISING) + return test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags); + else + return test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags); + default: + return -EINVAL; + } +} + +static int gp2ap020a00f_read_channel(struct gp2ap020a00f_data *data, + struct iio_chan_spec const *chan, int *val) +{ + struct device *dev = &data->client->dev; + enum gp2ap020a00f_cmd cmd; + int err; + + switch (chan->scan_index) { + case GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR: + cmd = GP2AP020A00F_CMD_READ_RAW_CLEAR; + break; + case GP2AP020A00F_SCAN_MODE_LIGHT_IR: + cmd = GP2AP020A00F_CMD_READ_RAW_IR; + break; + case GP2AP020A00F_SCAN_MODE_PROXIMITY: + cmd = GP2AP020A00F_CMD_READ_RAW_PROXIMITY; + break; + default: + return -EINVAL; + } + + err = gp2ap020a00f_exec_cmd(data, cmd); + if (err < 0) { + dev_err(dev, "gp2ap020a00f_exec_cmd failed\n"); + return err; + } + + err = gp2ap020a00f_read_output(data, chan->address, val); + if (err < 0) + dev_err(dev, "gp2ap020a00f_read_output failed\n"); + + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_SHUTDOWN); + if (err < 0) + dev_err(dev, "Failed to shut down the device.\n"); + + if (cmd == GP2AP020A00F_CMD_READ_RAW_CLEAR || + cmd == GP2AP020A00F_CMD_READ_RAW_IR) + gp2ap020a00f_output_to_lux(data, val); + + return err; +} + +static int gp2ap020a00f_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + int err = -EINVAL; + + if (mask == IIO_CHAN_INFO_RAW) { + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + err = gp2ap020a00f_read_channel(data, chan, val); + iio_device_release_direct(indio_dev); + } + return err < 0 ? err : IIO_VAL_INT; +} + +static const struct iio_event_spec gp2ap020a00f_event_spec_light[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_event_spec gp2ap020a00f_event_spec_prox[] = { + { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec gp2ap020a00f_channels[] = { + { + .type = IIO_LIGHT, + .channel2 = IIO_MOD_LIGHT_CLEAR, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_type = { + .sign = 'u', + .realbits = 24, + .shift = 0, + .storagebits = 32, + .endianness = IIO_LE, + }, + .scan_index = GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR, + .address = GP2AP020A00F_D0_L_REG, + .event_spec = gp2ap020a00f_event_spec_light, + .num_event_specs = ARRAY_SIZE(gp2ap020a00f_event_spec_light), + }, + { + .type = IIO_LIGHT, + .channel2 = IIO_MOD_LIGHT_IR, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_type = { + .sign = 'u', + .realbits = 24, + .shift = 0, + .storagebits = 32, + .endianness = IIO_LE, + }, + .scan_index = GP2AP020A00F_SCAN_MODE_LIGHT_IR, + .address = GP2AP020A00F_D1_L_REG, + }, + { + .type = IIO_PROXIMITY, + .modified = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_type = { + .sign = 'u', + .realbits = 16, + .shift = 0, + .storagebits = 16, + .endianness = IIO_LE, + }, + .scan_index = GP2AP020A00F_SCAN_MODE_PROXIMITY, + .address = GP2AP020A00F_D2_L_REG, + .event_spec = gp2ap020a00f_event_spec_prox, + .num_event_specs = ARRAY_SIZE(gp2ap020a00f_event_spec_prox), + }, + IIO_CHAN_SOFT_TIMESTAMP(GP2AP020A00F_CHAN_TIMESTAMP), +}; + +static const struct iio_info gp2ap020a00f_info = { + .read_raw = &gp2ap020a00f_read_raw, + .read_event_value = &gp2ap020a00f_read_event_val, + .read_event_config = &gp2ap020a00f_read_event_config, + .write_event_value = &gp2ap020a00f_write_event_val, + .write_event_config = &gp2ap020a00f_write_event_config, +}; + +static int gp2ap020a00f_buffer_postenable(struct iio_dev *indio_dev) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + int i, err; + + guard(mutex)(&data->lock); + + /* + * Enable triggers according to the scan_mask. Enabling either + * LIGHT_CLEAR or LIGHT_IR scan mode results in enabling ALS + * module in the device, which generates samples in both D0 (clear) + * and D1 (ir) registers. As the two registers are bound to the + * two separate IIO channels they are treated in the driver logic + * as if they were controlled independently. + */ + iio_for_each_active_channel(indio_dev, i) { + switch (i) { + case GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR: + err = gp2ap020a00f_exec_cmd(data, + GP2AP020A00F_CMD_TRIGGER_CLEAR_EN); + break; + case GP2AP020A00F_SCAN_MODE_LIGHT_IR: + err = gp2ap020a00f_exec_cmd(data, + GP2AP020A00F_CMD_TRIGGER_IR_EN); + break; + case GP2AP020A00F_SCAN_MODE_PROXIMITY: + err = gp2ap020a00f_exec_cmd(data, + GP2AP020A00F_CMD_TRIGGER_PROX_EN); + break; + default: + err = -EINVAL; + break; + } + if (err) + return err; + } + + data->buffer = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (!data->buffer) + return -ENOMEM; + + return 0; +} + +static int gp2ap020a00f_buffer_predisable(struct iio_dev *indio_dev) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + int i, err; + + guard(mutex)(&data->lock); + + iio_for_each_active_channel(indio_dev, i) { + switch (i) { + case GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR: + err = gp2ap020a00f_exec_cmd(data, + GP2AP020A00F_CMD_TRIGGER_CLEAR_DIS); + break; + case GP2AP020A00F_SCAN_MODE_LIGHT_IR: + err = gp2ap020a00f_exec_cmd(data, + GP2AP020A00F_CMD_TRIGGER_IR_DIS); + break; + case GP2AP020A00F_SCAN_MODE_PROXIMITY: + err = gp2ap020a00f_exec_cmd(data, + GP2AP020A00F_CMD_TRIGGER_PROX_DIS); + break; + default: + err = -EINVAL; + break; + } + if (err) + return err; + } + + kfree(data->buffer); + return 0; +} + +static const struct iio_buffer_setup_ops gp2ap020a00f_buffer_setup_ops = { + .postenable = &gp2ap020a00f_buffer_postenable, + .predisable = &gp2ap020a00f_buffer_predisable, +}; + +static int gp2ap020a00f_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct device *dev = &client->dev; + struct gp2ap020a00f_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int err; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + + data->vled_reg = devm_regulator_get(dev, "vled"); + if (IS_ERR(data->vled_reg)) + return PTR_ERR(data->vled_reg); + + err = regulator_enable(data->vled_reg); + if (err) + return err; + + regmap = devm_regmap_init_i2c(client, &gp2ap020a00f_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "Regmap initialization failed.\n"); + err = PTR_ERR(regmap); + goto error_regulator_disable; + } + + /* Initialize device registers */ + err = regmap_bulk_write(regmap, GP2AP020A00F_OP_REG, + gp2ap020a00f_reg_init_tab, + ARRAY_SIZE(gp2ap020a00f_reg_init_tab)); + + if (err < 0) { + dev_err(dev, "Device initialization failed.\n"); + goto error_regulator_disable; + } + + i2c_set_clientdata(client, indio_dev); + + data->client = client; + data->cur_opmode = GP2AP020A00F_OPMODE_SHUTDOWN; + data->regmap = regmap; + init_waitqueue_head(&data->data_ready_queue); + + mutex_init(&data->lock); + indio_dev->channels = gp2ap020a00f_channels; + indio_dev->num_channels = ARRAY_SIZE(gp2ap020a00f_channels); + indio_dev->info = &gp2ap020a00f_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Allocate buffer */ + err = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + &gp2ap020a00f_trigger_handler, &gp2ap020a00f_buffer_setup_ops); + if (err < 0) + goto error_regulator_disable; + + /* Allocate trigger */ + data->trig = devm_iio_trigger_alloc(dev, "%s-trigger", indio_dev->name); + if (data->trig == NULL) { + err = -ENOMEM; + dev_err(dev, "Failed to allocate iio trigger.\n"); + goto error_uninit_buffer; + } + + /* This needs to be requested here for read_raw calls to work. */ + err = request_threaded_irq(client->irq, NULL, + &gp2ap020a00f_thresh_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "gp2ap020a00f_als_event", + indio_dev); + if (err < 0) { + dev_err(dev, "Irq request failed.\n"); + goto error_uninit_buffer; + } + + init_irq_work(&data->work, gp2ap020a00f_iio_trigger_work); + + err = iio_trigger_register(data->trig); + if (err < 0) { + dev_err(dev, "Failed to register iio trigger.\n"); + goto error_free_irq; + } + + err = iio_device_register(indio_dev); + if (err < 0) + goto error_trigger_unregister; + + return 0; + +error_trigger_unregister: + iio_trigger_unregister(data->trig); +error_free_irq: + free_irq(client->irq, indio_dev); +error_uninit_buffer: + iio_triggered_buffer_cleanup(indio_dev); +error_regulator_disable: + regulator_disable(data->vled_reg); + + return err; +} + +static void gp2ap020a00f_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + struct device *dev = &client->dev; + int err; + + err = gp2ap020a00f_set_operation_mode(data, GP2AP020A00F_OPMODE_SHUTDOWN); + if (err < 0) + dev_err(dev, "Failed to power off the device.\n"); + + iio_device_unregister(indio_dev); + iio_trigger_unregister(data->trig); + free_irq(client->irq, indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + regulator_disable(data->vled_reg); +} + +static const struct i2c_device_id gp2ap020a00f_id[] = { + { .name = "gp2ap020a00f" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, gp2ap020a00f_id); + +static const struct of_device_id gp2ap020a00f_of_match[] = { + { .compatible = "sharp,gp2ap020a00f" }, + { } +}; +MODULE_DEVICE_TABLE(of, gp2ap020a00f_of_match); + +static struct i2c_driver gp2ap020a00f_driver = { + .driver = { + .name = "gp2ap020a00f", + .of_match_table = gp2ap020a00f_of_match, + }, + .probe = gp2ap020a00f_probe, + .remove = gp2ap020a00f_remove, + .id_table = gp2ap020a00f_id, +}; +module_i2c_driver(gp2ap020a00f_driver); + +MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>"); +MODULE_DESCRIPTION("Sharp GP2AP020A00F Proximity/ALS sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c new file mode 100644 index 000000000..929343e42 --- /dev/null +++ b/drivers/iio/light/hid-sensor-als.c @@ -0,0 +1,469 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID Sensors Driver + * Copyright (c) 2012, Intel Corporation. + */ +#include <linux/bitops.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/hid-sensor-hub.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include "../common/hid-sensors/hid-sensor-trigger.h" + +enum { + CHANNEL_SCAN_INDEX_INTENSITY, + CHANNEL_SCAN_INDEX_ILLUM, + CHANNEL_SCAN_INDEX_COLOR_TEMP, + CHANNEL_SCAN_INDEX_CHROMATICITY_X, + CHANNEL_SCAN_INDEX_CHROMATICITY_Y, + CHANNEL_SCAN_INDEX_MAX +}; + +#define CHANNEL_SCAN_INDEX_TIMESTAMP CHANNEL_SCAN_INDEX_MAX + +struct als_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info als[CHANNEL_SCAN_INDEX_MAX]; + struct iio_chan_spec channels[CHANNEL_SCAN_INDEX_MAX + 1]; + struct { + u32 illum[CHANNEL_SCAN_INDEX_MAX]; + aligned_s64 timestamp; + } scan; + int scale_pre_decml[CHANNEL_SCAN_INDEX_MAX]; + int scale_post_decml[CHANNEL_SCAN_INDEX_MAX]; + int scale_precision[CHANNEL_SCAN_INDEX_MAX]; + int value_offset; + int num_channels; + s64 timestamp; + unsigned long als_scan_mask[2]; +}; + +/* The order of usage ids must match scan index starting from CHANNEL_SCAN_INDEX_INTENSITY */ +static const u32 als_usage_ids[] = { + HID_USAGE_SENSOR_LIGHT_ILLUM, + HID_USAGE_SENSOR_LIGHT_ILLUM, + HID_USAGE_SENSOR_LIGHT_COLOR_TEMPERATURE, + HID_USAGE_SENSOR_LIGHT_CHROMATICITY_X, + HID_USAGE_SENSOR_LIGHT_CHROMATICITY_Y, +}; + +static const u32 als_sensitivity_addresses[] = { + HID_USAGE_SENSOR_DATA_LIGHT, + HID_USAGE_SENSOR_LIGHT_ILLUM, +}; + +/* Channel definitions */ +static const struct iio_chan_spec als_channels[] = { + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS) | + BIT(IIO_CHAN_INFO_HYSTERESIS_RELATIVE), + .scan_index = CHANNEL_SCAN_INDEX_INTENSITY, + }, + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS) | + BIT(IIO_CHAN_INFO_HYSTERESIS_RELATIVE), + .scan_index = CHANNEL_SCAN_INDEX_ILLUM, + }, + { + .type = IIO_COLORTEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS) | + BIT(IIO_CHAN_INFO_HYSTERESIS_RELATIVE), + .scan_index = CHANNEL_SCAN_INDEX_COLOR_TEMP, + }, + { + .type = IIO_CHROMATICITY, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS) | + BIT(IIO_CHAN_INFO_HYSTERESIS_RELATIVE), + .scan_index = CHANNEL_SCAN_INDEX_CHROMATICITY_X, + }, + { + .type = IIO_CHROMATICITY, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS) | + BIT(IIO_CHAN_INFO_HYSTERESIS_RELATIVE), + .scan_index = CHANNEL_SCAN_INDEX_CHROMATICITY_Y, + }, + IIO_CHAN_SOFT_TIMESTAMP(CHANNEL_SCAN_INDEX_TIMESTAMP) +}; + +/* Channel read_raw handler */ +static int als_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct als_state *als_state = iio_priv(indio_dev); + struct hid_sensor_hub_device *hsdev = als_state->common_attributes.hsdev; + int report_id = -1; + u32 address; + int ret_type; + s32 min; + + *val = 0; + *val2 = 0; + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->scan_index) { + case CHANNEL_SCAN_INDEX_INTENSITY: + case CHANNEL_SCAN_INDEX_ILLUM: + report_id = als_state->als[chan->scan_index].report_id; + min = als_state->als[chan->scan_index].logical_minimum; + address = HID_USAGE_SENSOR_LIGHT_ILLUM; + break; + case CHANNEL_SCAN_INDEX_COLOR_TEMP: + report_id = als_state->als[chan->scan_index].report_id; + min = als_state->als[chan->scan_index].logical_minimum; + address = HID_USAGE_SENSOR_LIGHT_COLOR_TEMPERATURE; + break; + case CHANNEL_SCAN_INDEX_CHROMATICITY_X: + report_id = als_state->als[chan->scan_index].report_id; + min = als_state->als[chan->scan_index].logical_minimum; + address = HID_USAGE_SENSOR_LIGHT_CHROMATICITY_X; + break; + case CHANNEL_SCAN_INDEX_CHROMATICITY_Y: + report_id = als_state->als[chan->scan_index].report_id; + min = als_state->als[chan->scan_index].logical_minimum; + address = HID_USAGE_SENSOR_LIGHT_CHROMATICITY_Y; + break; + default: + report_id = -1; + break; + } + if (report_id >= 0) { + hid_sensor_power_state(&als_state->common_attributes, + true); + *val = sensor_hub_input_attr_get_raw_value( + hsdev, hsdev->usage, address, report_id, + SENSOR_HUB_SYNC, min < 0); + hid_sensor_power_state(&als_state->common_attributes, + false); + } else { + *val = 0; + return -EINVAL; + } + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + if (chan->scan_index >= CHANNEL_SCAN_INDEX_MAX) + return -EINVAL; + *val = als_state->scale_pre_decml[chan->scan_index]; + *val2 = als_state->scale_post_decml[chan->scan_index]; + ret_type = als_state->scale_precision[chan->scan_index]; + break; + case IIO_CHAN_INFO_OFFSET: + *val = als_state->value_offset; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &als_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret_type = hid_sensor_read_raw_hyst_value( + &als_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS_RELATIVE: + ret_type = hid_sensor_read_raw_hyst_rel_value( + &als_state->common_attributes, val, val2); + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int als_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct als_state *als_state = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &als_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_write_raw_hyst_value( + &als_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS_RELATIVE: + ret = hid_sensor_write_raw_hyst_rel_value( + &als_state->common_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info als_info = { + .read_raw = &als_read_raw, + .write_raw = &als_write_raw, +}; + +/* Callback handler to send event after all samples are received and captured */ +static int als_proc_event(struct hid_sensor_hub_device *hsdev, + u32 usage_id, void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct als_state *als_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "als_proc_event\n"); + if (atomic_read(&als_state->common_attributes.data_ready)) { + if (!als_state->timestamp) + als_state->timestamp = iio_get_time_ns(indio_dev); + + iio_push_to_buffers_with_ts(indio_dev, &als_state->scan, + sizeof(als_state->scan), + als_state->timestamp); + als_state->timestamp = 0; + } + + return 0; +} + +/* Capture samples in local storage */ +static int als_capture_sample(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct als_state *als_state = iio_priv(indio_dev); + int ret = -EINVAL; + u32 sample_data = *(u32 *)raw_data; + + switch (usage_id) { + case HID_USAGE_SENSOR_LIGHT_ILLUM: + als_state->scan.illum[CHANNEL_SCAN_INDEX_INTENSITY] = sample_data; + als_state->scan.illum[CHANNEL_SCAN_INDEX_ILLUM] = sample_data; + ret = 0; + break; + case HID_USAGE_SENSOR_LIGHT_COLOR_TEMPERATURE: + als_state->scan.illum[CHANNEL_SCAN_INDEX_COLOR_TEMP] = sample_data; + ret = 0; + break; + case HID_USAGE_SENSOR_LIGHT_CHROMATICITY_X: + als_state->scan.illum[CHANNEL_SCAN_INDEX_CHROMATICITY_X] = sample_data; + ret = 0; + break; + case HID_USAGE_SENSOR_LIGHT_CHROMATICITY_Y: + als_state->scan.illum[CHANNEL_SCAN_INDEX_CHROMATICITY_Y] = sample_data; + ret = 0; + break; + case HID_USAGE_SENSOR_TIME_TIMESTAMP: + als_state->timestamp = hid_sensor_convert_timestamp(&als_state->common_attributes, + *(s64 *)raw_data); + ret = 0; + break; + default: + break; + } + + return ret; +} + +/* Parse report which is specific to an usage id*/ +static int als_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + u32 usage_id, + struct als_state *st) +{ + struct iio_chan_spec *channels; + int ret, index = 0; + int i; + + channels = st->channels; + + for (i = 0; i < CHANNEL_SCAN_INDEX_MAX; ++i) { + ret = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + usage_id, + als_usage_ids[i], + &st->als[i]); + if (ret < 0) + continue; + + channels[index] = als_channels[i]; + st->als_scan_mask[0] |= BIT(i); + channels[index].scan_type = (struct iio_scan_type) { + .format = 's', + .realbits = BYTES_TO_BITS(st->als[i].size), + .storagebits = BITS_PER_TYPE(u32), + }; + ++index; + + st->scale_precision[i] = hid_sensor_format_scale(usage_id, + &st->als[i], &st->scale_pre_decml[i], + &st->scale_post_decml[i]); + + dev_dbg(&pdev->dev, "als %x:%x\n", st->als[i].index, + st->als[i].report_id); + } + + st->num_channels = index; + /* Return success even if one usage id is present */ + if (index) + ret = 0; + + return ret; +} + +/* Function to initialize the processing for usage id */ +static int hid_als_probe(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + int ret = 0; + static const char *name = "als"; + struct iio_dev *indio_dev; + struct als_state *als_state; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct als_state)); + if (!indio_dev) + return -ENOMEM; + platform_set_drvdata(pdev, indio_dev); + + als_state = iio_priv(indio_dev); + als_state->common_attributes.hsdev = hsdev; + als_state->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, + hsdev->usage, + &als_state->common_attributes, + als_sensitivity_addresses, + ARRAY_SIZE(als_sensitivity_addresses)); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + + ret = als_parse_report(pdev, hsdev, + hsdev->usage, + als_state); + if (ret) { + dev_err(&pdev->dev, "failed to setup attributes\n"); + return ret; + } + + /* Add timestamp channel */ + als_state->channels[als_state->num_channels] = als_channels[CHANNEL_SCAN_INDEX_TIMESTAMP]; + + /* +1 for adding timestamp channel */ + indio_dev->num_channels = als_state->num_channels + 1; + + indio_dev->channels = als_state->channels; + indio_dev->available_scan_masks = als_state->als_scan_mask; + + indio_dev->info = &als_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + atomic_set(&als_state->common_attributes.data_ready, 0); + + ret = hid_sensor_setup_trigger(indio_dev, name, + &als_state->common_attributes); + if (ret < 0) { + dev_err(&pdev->dev, "trigger setup failed\n"); + return ret; + } + + als_state->callbacks.send_event = als_proc_event; + als_state->callbacks.capture_sample = als_capture_sample; + als_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, hsdev->usage, &als_state->callbacks); + if (ret < 0) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_remove_trigger; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_callback; + } + + return ret; + +error_remove_callback: + sensor_hub_remove_callback(hsdev, hsdev->usage); +error_remove_trigger: + hid_sensor_remove_trigger(indio_dev, &als_state->common_attributes); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static void hid_als_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct als_state *als_state = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + sensor_hub_remove_callback(hsdev, hsdev->usage); + hid_sensor_remove_trigger(indio_dev, &als_state->common_attributes); +} + +static const struct platform_device_id hid_als_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200041", + }, + { + /* Format: HID-SENSOR-custom_sensor_tag-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-LISS-0041", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, hid_als_ids); + +static struct platform_driver hid_als_platform_driver = { + .id_table = hid_als_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_als_probe, + .remove = hid_als_remove, +}; +module_platform_driver(hid_als_platform_driver); + +MODULE_DESCRIPTION("HID Sensor ALS"); +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HID"); diff --git a/drivers/iio/light/hid-sensor-prox.c b/drivers/iio/light/hid-sensor-prox.c new file mode 100644 index 000000000..63fd3eff1 --- /dev/null +++ b/drivers/iio/light/hid-sensor-prox.c @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID Sensors Driver + * Copyright (c) 2014, Intel Corporation. + */ +#include <linux/bitops.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/hid-sensor-hub.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include "../common/hid-sensors/hid-sensor-trigger.h" + +static const u32 prox_usage_ids[] = { + HID_USAGE_SENSOR_HUMAN_PRESENCE, + HID_USAGE_SENSOR_HUMAN_PROXIMITY, + HID_USAGE_SENSOR_HUMAN_ATTENTION, +}; + +#define MAX_CHANNELS ARRAY_SIZE(prox_usage_ids) + +enum { + HID_HUMAN_PRESENCE, + HID_HUMAN_PROXIMITY, + HID_HUMAN_ATTENTION, +}; + +struct prox_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info prox_attr[MAX_CHANNELS]; + struct iio_chan_spec channels[MAX_CHANNELS]; + u32 channel2usage[MAX_CHANNELS]; + u32 human_presence[MAX_CHANNELS]; + int scale_pre_decml[MAX_CHANNELS]; + int scale_post_decml[MAX_CHANNELS]; + int scale_precision[MAX_CHANNELS]; + unsigned long scan_mask[2]; /* One entry plus one terminator. */ + int num_channels; +}; + +static const u32 prox_sensitivity_addresses[] = { + HID_USAGE_SENSOR_HUMAN_PRESENCE, + HID_USAGE_SENSOR_DATA_PRESENCE, +}; + +#define PROX_CHANNEL(_is_proximity, _channel) \ + {\ + .type = _is_proximity ? IIO_PROXIMITY : IIO_ATTENTION,\ + .info_mask_separate = \ + (_is_proximity ? BIT(IIO_CHAN_INFO_RAW) :\ + BIT(IIO_CHAN_INFO_PROCESSED)) |\ + BIT(IIO_CHAN_INFO_OFFSET) |\ + BIT(IIO_CHAN_INFO_SCALE) |\ + BIT(IIO_CHAN_INFO_SAMP_FREQ) |\ + BIT(IIO_CHAN_INFO_HYSTERESIS),\ + .indexed = _is_proximity,\ + .channel = _channel,\ + } + +/* Channel definitions (same order as prox_usage_ids) */ +static const struct iio_chan_spec prox_channels[] = { + PROX_CHANNEL(true, HID_HUMAN_PRESENCE), + PROX_CHANNEL(true, HID_HUMAN_PROXIMITY), + PROX_CHANNEL(false, 0), +}; + +/* Channel read_raw handler */ +static int prox_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct prox_state *prox_state = iio_priv(indio_dev); + struct hid_sensor_hub_device *hsdev; + int report_id; + u32 address; + int ret_type; + s32 min; + + *val = 0; + *val2 = 0; + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + if (chan->scan_index >= prox_state->num_channels) + return -EINVAL; + address = prox_state->channel2usage[chan->scan_index]; + report_id = prox_state->prox_attr[chan->scan_index].report_id; + hsdev = prox_state->common_attributes.hsdev; + min = prox_state->prox_attr[chan->scan_index].logical_minimum; + hid_sensor_power_state(&prox_state->common_attributes, true); + *val = sensor_hub_input_attr_get_raw_value(hsdev, + hsdev->usage, + address, + report_id, + SENSOR_HUB_SYNC, + min < 0); + if (prox_state->channel2usage[chan->scan_index] == + HID_USAGE_SENSOR_HUMAN_ATTENTION) + *val *= 100; + hid_sensor_power_state(&prox_state->common_attributes, false); + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + if (chan->scan_index >= prox_state->num_channels) + return -EINVAL; + + *val = prox_state->scale_pre_decml[chan->scan_index]; + *val2 = prox_state->scale_post_decml[chan->scan_index]; + ret_type = prox_state->scale_precision[chan->scan_index]; + break; + case IIO_CHAN_INFO_OFFSET: + *val = 0; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &prox_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret_type = hid_sensor_read_raw_hyst_value( + &prox_state->common_attributes, val, val2); + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int prox_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct prox_state *prox_state = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &prox_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_write_raw_hyst_value( + &prox_state->common_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info prox_info = { + .read_raw = &prox_read_raw, + .write_raw = &prox_write_raw, +}; + +/* Callback handler to send event after all samples are received and captured */ +static int prox_proc_event(struct hid_sensor_hub_device *hsdev, + u32 usage_id, void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct prox_state *prox_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "prox_proc_event\n"); + if (atomic_read(&prox_state->common_attributes.data_ready)) { + dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n"); + iio_push_to_buffers(indio_dev, &prox_state->human_presence); + } + + return 0; +} + +/* Capture samples in local storage */ +static int prox_capture_sample(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct prox_state *prox_state = iio_priv(indio_dev); + int multiplier = 1; + int chan; + + for (chan = 0; chan < prox_state->num_channels; chan++) + if (prox_state->channel2usage[chan] == usage_id) + break; + if (chan == prox_state->num_channels) + return -EINVAL; + + if (usage_id == HID_USAGE_SENSOR_HUMAN_ATTENTION) + multiplier = 100; + + switch (raw_len) { + case 1: + prox_state->human_presence[chan] = *(u8 *)raw_data * multiplier; + return 0; + case 2: + prox_state->human_presence[chan] = *(u16 *)raw_data * multiplier; + return 0; + case 4: + prox_state->human_presence[chan] = *(u32 *)raw_data * multiplier; + return 0; + } + + return -EINVAL; +} + +/* Parse report which is specific to an usage id*/ +static int prox_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct prox_state *st) +{ + struct iio_chan_spec *channels = st->channels; + int index = 0; + int ret; + int i; + + for (i = 0; i < MAX_CHANNELS; i++) { + u32 usage_id = prox_usage_ids[i]; + + ret = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + hsdev->usage, + usage_id, + &st->prox_attr[index]); + if (ret < 0) + continue; + st->channel2usage[index] = usage_id; + st->scan_mask[0] |= BIT(index); + channels[index] = prox_channels[i]; + channels[index].scan_index = index; + channels[index].scan_type = (struct iio_scan_type) { + .format = 's', + .realbits = BYTES_TO_BITS(st->prox_attr[index].size), + .storagebits = BITS_PER_TYPE(u32), + }; + dev_dbg(&pdev->dev, "prox %x:%x\n", st->prox_attr[index].index, + st->prox_attr[index].report_id); + st->scale_precision[index] = + hid_sensor_format_scale(usage_id, &st->prox_attr[index], + &st->scale_pre_decml[index], + &st->scale_post_decml[index]); + index++; + } + + if (!index) + return ret; + + st->num_channels = index; + + return 0; +} + +/* Function to initialize the processing for usage id */ +static int hid_prox_probe(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + int ret = 0; + static const char *name = "prox"; + struct iio_dev *indio_dev; + struct prox_state *prox_state; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct prox_state)); + if (!indio_dev) + return -ENOMEM; + platform_set_drvdata(pdev, indio_dev); + + prox_state = iio_priv(indio_dev); + prox_state->common_attributes.hsdev = hsdev; + prox_state->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, hsdev->usage, + &prox_state->common_attributes, + prox_sensitivity_addresses, + ARRAY_SIZE(prox_sensitivity_addresses)); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + + ret = prox_parse_report(pdev, hsdev, prox_state); + if (ret) { + dev_err(&pdev->dev, "failed to setup attributes\n"); + return ret; + } + + indio_dev->num_channels = prox_state->num_channels; + indio_dev->channels = prox_state->channels; + indio_dev->available_scan_masks = prox_state->scan_mask; + indio_dev->info = &prox_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + atomic_set(&prox_state->common_attributes.data_ready, 0); + + ret = hid_sensor_setup_trigger(indio_dev, name, + &prox_state->common_attributes); + if (ret) { + dev_err(&pdev->dev, "trigger setup failed\n"); + return ret; + } + + prox_state->callbacks.send_event = prox_proc_event; + prox_state->callbacks.capture_sample = prox_capture_sample; + prox_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, hsdev->usage, + &prox_state->callbacks); + if (ret < 0) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_remove_trigger; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_callback; + } + + return ret; + +error_remove_callback: + sensor_hub_remove_callback(hsdev, hsdev->usage); +error_remove_trigger: + hid_sensor_remove_trigger(indio_dev, &prox_state->common_attributes); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static void hid_prox_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct prox_state *prox_state = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + sensor_hub_remove_callback(hsdev, hsdev->usage); + hid_sensor_remove_trigger(indio_dev, &prox_state->common_attributes); +} + +static const struct platform_device_id hid_prox_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200011", + }, + { + /* Format: HID-SENSOR-tag-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-LISS-0226", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, hid_prox_ids); + +static struct platform_driver hid_prox_platform_driver = { + .id_table = hid_prox_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_prox_probe, + .remove = hid_prox_remove, +}; +module_platform_driver(hid_prox_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Proximity"); +MODULE_AUTHOR("Archana Patni <archana.patni@intel.com>"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HID"); diff --git a/drivers/iio/light/iqs621-als.c b/drivers/iio/light/iqs621-als.c new file mode 100644 index 000000000..cd5843e3e --- /dev/null +++ b/drivers/iio/light/iqs621-als.c @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS621/622 Ambient Light Sensors + * + * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com> + */ + +#include <linux/cleanup.h> +#include <linux/device.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/kernel.h> +#include <linux/mfd/iqs62x.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/notifier.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define IQS621_ALS_FLAGS_LIGHT BIT(7) +#define IQS621_ALS_FLAGS_RANGE GENMASK(3, 0) + +#define IQS621_ALS_UI_OUT 0x17 + +#define IQS621_ALS_THRESH_DARK 0x80 +#define IQS621_ALS_THRESH_LIGHT 0x81 + +#define IQS622_IR_RANGE 0x15 +#define IQS622_IR_FLAGS 0x16 +#define IQS622_IR_FLAGS_TOUCH BIT(1) +#define IQS622_IR_FLAGS_PROX BIT(0) + +#define IQS622_IR_UI_OUT 0x17 + +#define IQS622_IR_THRESH_PROX 0x91 +#define IQS622_IR_THRESH_TOUCH 0x92 + +struct iqs621_als_private { + struct iqs62x_core *iqs62x; + struct iio_dev *indio_dev; + struct notifier_block notifier; + struct mutex lock; + bool light_en; + bool range_en; + bool prox_en; + u8 als_flags; + u8 ir_flags_mask; + u8 ir_flags; + u8 thresh_light; + u8 thresh_dark; + u8 thresh_prox; +}; + +static int iqs621_als_init(struct iqs621_als_private *iqs621_als) +{ + struct iqs62x_core *iqs62x = iqs621_als->iqs62x; + unsigned int event_mask = 0; + int ret; + + switch (iqs621_als->ir_flags_mask) { + case IQS622_IR_FLAGS_TOUCH: + ret = regmap_write(iqs62x->regmap, IQS622_IR_THRESH_TOUCH, + iqs621_als->thresh_prox); + break; + + case IQS622_IR_FLAGS_PROX: + ret = regmap_write(iqs62x->regmap, IQS622_IR_THRESH_PROX, + iqs621_als->thresh_prox); + break; + + default: + ret = regmap_write(iqs62x->regmap, IQS621_ALS_THRESH_LIGHT, + iqs621_als->thresh_light); + if (ret) + return ret; + + ret = regmap_write(iqs62x->regmap, IQS621_ALS_THRESH_DARK, + iqs621_als->thresh_dark); + } + + if (ret) + return ret; + + if (iqs621_als->light_en || iqs621_als->range_en) + event_mask |= iqs62x->dev_desc->als_mask; + + if (iqs621_als->prox_en) + event_mask |= iqs62x->dev_desc->ir_mask; + + return regmap_clear_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK, + event_mask); +} + +static int iqs621_als_notifier(struct notifier_block *notifier, + unsigned long event_flags, void *context) +{ + struct iqs62x_event_data *event_data = context; + struct iqs621_als_private *iqs621_als; + struct iio_dev *indio_dev; + bool light_new, light_old; + bool prox_new, prox_old; + u8 range_new, range_old; + s64 timestamp; + int ret; + + iqs621_als = container_of(notifier, struct iqs621_als_private, + notifier); + indio_dev = iqs621_als->indio_dev; + timestamp = iio_get_time_ns(indio_dev); + + guard(mutex)(&iqs621_als->lock); + + if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) { + ret = iqs621_als_init(iqs621_als); + if (ret) { + dev_err(indio_dev->dev.parent, + "Failed to re-initialize device: %d\n", ret); + return NOTIFY_BAD; + } + return NOTIFY_OK; + } + + if (!iqs621_als->light_en && !iqs621_als->range_en && !iqs621_als->prox_en) + return NOTIFY_DONE; + + /* IQS621 only */ + light_new = event_data->als_flags & IQS621_ALS_FLAGS_LIGHT; + light_old = iqs621_als->als_flags & IQS621_ALS_FLAGS_LIGHT; + + if (iqs621_als->light_en && light_new && !light_old) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + else if (iqs621_als->light_en && !light_new && light_old) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + + /* IQS621 and IQS622 */ + range_new = event_data->als_flags & IQS621_ALS_FLAGS_RANGE; + range_old = iqs621_als->als_flags & IQS621_ALS_FLAGS_RANGE; + + if (iqs621_als->range_en && (range_new > range_old)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_RISING), + timestamp); + else if (iqs621_als->range_en && (range_new < range_old)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_FALLING), + timestamp); + + /* IQS622 only */ + prox_new = event_data->ir_flags & iqs621_als->ir_flags_mask; + prox_old = iqs621_als->ir_flags & iqs621_als->ir_flags_mask; + + if (iqs621_als->prox_en && prox_new && !prox_old) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + else if (iqs621_als->prox_en && !prox_new && prox_old) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + + iqs621_als->als_flags = event_data->als_flags; + iqs621_als->ir_flags = event_data->ir_flags; + return NOTIFY_OK; +} + +static void iqs621_als_notifier_unregister(void *context) +{ + struct iqs621_als_private *iqs621_als = context; + struct iio_dev *indio_dev = iqs621_als->indio_dev; + int ret; + + ret = blocking_notifier_chain_unregister(&iqs621_als->iqs62x->nh, + &iqs621_als->notifier); + if (ret) + dev_err(indio_dev->dev.parent, + "Failed to unregister notifier: %d\n", ret); +} + +static int iqs621_als_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs621_als->iqs62x; + int ret; + __le16 val_buf; + + switch (chan->type) { + case IIO_INTENSITY: + ret = regmap_read(iqs62x->regmap, chan->address, val); + if (ret) + return ret; + + *val &= IQS621_ALS_FLAGS_RANGE; + return IIO_VAL_INT; + + case IIO_PROXIMITY: + case IIO_LIGHT: + ret = regmap_raw_read(iqs62x->regmap, chan->address, &val_buf, + sizeof(val_buf)); + if (ret) + return ret; + + *val = le16_to_cpu(val_buf); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int iqs621_als_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + + guard(mutex)(&iqs621_als->lock); + + switch (chan->type) { + case IIO_LIGHT: + return iqs621_als->light_en; + + case IIO_INTENSITY: + return iqs621_als->range_en; + + case IIO_PROXIMITY: + return iqs621_als->prox_en; + + default: + return -EINVAL; + } +} + +static int iqs621_als_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs621_als->iqs62x; + unsigned int val; + int ret; + + guard(mutex)(&iqs621_als->lock); + + ret = regmap_read(iqs62x->regmap, iqs62x->dev_desc->als_flags, &val); + if (ret) + return ret; + iqs621_als->als_flags = val; + + switch (chan->type) { + case IIO_LIGHT: + ret = regmap_update_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK, + iqs62x->dev_desc->als_mask, + iqs621_als->range_en || state ? 0 : + 0xFF); + if (ret) + return ret; + iqs621_als->light_en = state; + + return 0; + + case IIO_INTENSITY: + ret = regmap_update_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK, + iqs62x->dev_desc->als_mask, + iqs621_als->light_en || state ? 0 : + 0xFF); + if (ret) + return ret; + iqs621_als->range_en = state; + + return 0; + + case IIO_PROXIMITY: + ret = regmap_read(iqs62x->regmap, IQS622_IR_FLAGS, &val); + if (ret) + return ret; + iqs621_als->ir_flags = val; + + ret = regmap_update_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK, + iqs62x->dev_desc->ir_mask, + state ? 0 : 0xFF); + if (ret) + return ret; + iqs621_als->prox_en = state; + + return 0; + + default: + return -EINVAL; + } +} + +static int iqs621_als_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + + guard(mutex)(&iqs621_als->lock); + + switch (dir) { + case IIO_EV_DIR_RISING: + *val = iqs621_als->thresh_light * 16; + return IIO_VAL_INT; + + case IIO_EV_DIR_FALLING: + *val = iqs621_als->thresh_dark * 4; + return IIO_VAL_INT; + + case IIO_EV_DIR_EITHER: + if (iqs621_als->ir_flags_mask == IQS622_IR_FLAGS_TOUCH) + *val = iqs621_als->thresh_prox * 4; + else + *val = iqs621_als->thresh_prox; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int iqs621_als_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs621_als->iqs62x; + unsigned int thresh_reg, thresh_val; + u8 ir_flags_mask, *thresh_cache; + int ret; + + guard(mutex)(&iqs621_als->lock); + + switch (dir) { + case IIO_EV_DIR_RISING: + thresh_reg = IQS621_ALS_THRESH_LIGHT; + thresh_val = val / 16; + + thresh_cache = &iqs621_als->thresh_light; + ir_flags_mask = 0; + break; + + case IIO_EV_DIR_FALLING: + thresh_reg = IQS621_ALS_THRESH_DARK; + thresh_val = val / 4; + + thresh_cache = &iqs621_als->thresh_dark; + ir_flags_mask = 0; + break; + + case IIO_EV_DIR_EITHER: + /* + * The IQS622 supports two detection thresholds, both measured + * in the same arbitrary units reported by read_raw: proximity + * (0 through 255 in steps of 1), and touch (0 through 1020 in + * steps of 4). + * + * Based on the single detection threshold chosen by the user, + * select the hardware threshold that gives the best trade-off + * between range and resolution. + * + * By default, the close-range (but coarse) touch threshold is + * chosen during probe. + */ + switch (val) { + case 0 ... 255: + thresh_reg = IQS622_IR_THRESH_PROX; + thresh_val = val; + + ir_flags_mask = IQS622_IR_FLAGS_PROX; + break; + + case 256 ... 1020: + thresh_reg = IQS622_IR_THRESH_TOUCH; + thresh_val = val / 4; + + ir_flags_mask = IQS622_IR_FLAGS_TOUCH; + break; + + default: + return -EINVAL; + } + + thresh_cache = &iqs621_als->thresh_prox; + break; + + default: + return -EINVAL; + } + + if (thresh_val > 0xFF) + return -EINVAL; + + ret = regmap_write(iqs62x->regmap, thresh_reg, thresh_val); + if (ret) + return ret; + + *thresh_cache = thresh_val; + iqs621_als->ir_flags_mask = ir_flags_mask; + + return 0; +} + +static const struct iio_info iqs621_als_info = { + .read_raw = &iqs621_als_read_raw, + .read_event_config = iqs621_als_read_event_config, + .write_event_config = iqs621_als_write_event_config, + .read_event_value = iqs621_als_read_event_value, + .write_event_value = iqs621_als_write_event_value, +}; + +static const struct iio_event_spec iqs621_als_range_events[] = { + { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_event_spec iqs621_als_light_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const struct iio_chan_spec iqs621_als_channels[] = { + { + .type = IIO_INTENSITY, + .address = IQS621_ALS_FLAGS, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = iqs621_als_range_events, + .num_event_specs = ARRAY_SIZE(iqs621_als_range_events), + }, + { + .type = IIO_LIGHT, + .address = IQS621_ALS_UI_OUT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .event_spec = iqs621_als_light_events, + .num_event_specs = ARRAY_SIZE(iqs621_als_light_events), + }, +}; + +static const struct iio_event_spec iqs622_als_prox_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const struct iio_chan_spec iqs622_als_channels[] = { + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_LIGHT_BOTH, + .address = IQS622_ALS_FLAGS, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = iqs621_als_range_events, + .num_event_specs = ARRAY_SIZE(iqs621_als_range_events), + .modified = true, + }, + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_LIGHT_IR, + .address = IQS622_IR_RANGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .modified = true, + }, + { + .type = IIO_PROXIMITY, + .address = IQS622_IR_UI_OUT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = iqs622_als_prox_events, + .num_event_specs = ARRAY_SIZE(iqs622_als_prox_events), + }, +}; + +static int iqs621_als_probe(struct platform_device *pdev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); + struct iqs621_als_private *iqs621_als; + struct iio_dev *indio_dev; + unsigned int val; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs621_als)); + if (!indio_dev) + return -ENOMEM; + + iqs621_als = iio_priv(indio_dev); + iqs621_als->iqs62x = iqs62x; + iqs621_als->indio_dev = indio_dev; + + if (iqs62x->dev_desc->prod_num == IQS622_PROD_NUM) { + ret = regmap_read(iqs62x->regmap, IQS622_IR_THRESH_TOUCH, + &val); + if (ret) + return ret; + iqs621_als->thresh_prox = val; + iqs621_als->ir_flags_mask = IQS622_IR_FLAGS_TOUCH; + + indio_dev->channels = iqs622_als_channels; + indio_dev->num_channels = ARRAY_SIZE(iqs622_als_channels); + } else { + ret = regmap_read(iqs62x->regmap, IQS621_ALS_THRESH_LIGHT, + &val); + if (ret) + return ret; + iqs621_als->thresh_light = val; + + ret = regmap_read(iqs62x->regmap, IQS621_ALS_THRESH_DARK, + &val); + if (ret) + return ret; + iqs621_als->thresh_dark = val; + + indio_dev->channels = iqs621_als_channels; + indio_dev->num_channels = ARRAY_SIZE(iqs621_als_channels); + } + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = iqs62x->dev_desc->dev_name; + indio_dev->info = &iqs621_als_info; + + mutex_init(&iqs621_als->lock); + + iqs621_als->notifier.notifier_call = iqs621_als_notifier; + ret = blocking_notifier_chain_register(&iqs621_als->iqs62x->nh, + &iqs621_als->notifier); + if (ret) { + dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); + return ret; + } + + ret = devm_add_action_or_reset(&pdev->dev, + iqs621_als_notifier_unregister, + iqs621_als); + if (ret) + return ret; + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static struct platform_driver iqs621_als_platform_driver = { + .driver = { + .name = "iqs621-als", + }, + .probe = iqs621_als_probe, +}; +module_platform_driver(iqs621_als_platform_driver); + +MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>"); +MODULE_DESCRIPTION("Azoteq IQS621/622 Ambient Light Sensors"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:iqs621-als"); diff --git a/drivers/iio/light/isl29018.c b/drivers/iio/light/isl29018.c new file mode 100644 index 000000000..759cb71ed --- /dev/null +++ b/drivers/iio/light/isl29018.c @@ -0,0 +1,859 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * A iio driver for the light sensor ISL 29018/29023/29035. + * + * IIO driver for monitoring ambient light intensity in luxi, proximity + * sensing and infrared sensing. + * + * Copyright (c) 2010, NVIDIA Corporation. + */ + +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define ISL29018_CONV_TIME_MS 100 + +#define ISL29018_REG_ADD_COMMAND1 0x00 +#define ISL29018_CMD1_OPMODE_SHIFT 5 +#define ISL29018_CMD1_OPMODE_MASK (7 << ISL29018_CMD1_OPMODE_SHIFT) +#define ISL29018_CMD1_OPMODE_POWER_DOWN 0 +#define ISL29018_CMD1_OPMODE_ALS_ONCE 1 +#define ISL29018_CMD1_OPMODE_IR_ONCE 2 +#define ISL29018_CMD1_OPMODE_PROX_ONCE 3 + +#define ISL29018_REG_ADD_COMMAND2 0x01 +#define ISL29018_CMD2_RESOLUTION_SHIFT 2 +#define ISL29018_CMD2_RESOLUTION_MASK (0x3 << ISL29018_CMD2_RESOLUTION_SHIFT) + +#define ISL29018_CMD2_RANGE_SHIFT 0 +#define ISL29018_CMD2_RANGE_MASK (0x3 << ISL29018_CMD2_RANGE_SHIFT) + +#define ISL29018_CMD2_SCHEME_SHIFT 7 +#define ISL29018_CMD2_SCHEME_MASK (0x1 << ISL29018_CMD2_SCHEME_SHIFT) + +#define ISL29018_REG_ADD_DATA_LSB 0x02 +#define ISL29018_REG_ADD_DATA_MSB 0x03 + +#define ISL29018_REG_TEST 0x08 +#define ISL29018_TEST_SHIFT 0 +#define ISL29018_TEST_MASK (0xFF << ISL29018_TEST_SHIFT) + +#define ISL29035_REG_DEVICE_ID 0x0F +#define ISL29035_DEVICE_ID_SHIFT 0x03 +#define ISL29035_DEVICE_ID_MASK (0x7 << ISL29035_DEVICE_ID_SHIFT) +#define ISL29035_DEVICE_ID 0x5 +#define ISL29035_BOUT_SHIFT 0x07 +#define ISL29035_BOUT_MASK (0x01 << ISL29035_BOUT_SHIFT) + +enum isl29018_int_time { + ISL29018_INT_TIME_16, + ISL29018_INT_TIME_12, + ISL29018_INT_TIME_8, + ISL29018_INT_TIME_4, +}; + +static const unsigned int isl29018_int_utimes[3][4] = { + {90000, 5630, 351, 21}, + {90000, 5600, 352, 22}, + {105000, 6500, 410, 25}, +}; + +static const struct isl29018_scale { + unsigned int scale; + unsigned int uscale; +} isl29018_scales[4][4] = { + { {0, 15258}, {0, 61035}, {0, 244140}, {0, 976562} }, + { {0, 244140}, {0, 976562}, {3, 906250}, {15, 625000} }, + { {3, 906250}, {15, 625000}, {62, 500000}, {250, 0} }, + { {62, 500000}, {250, 0}, {1000, 0}, {4000, 0} } +}; + +struct isl29018_chip { + struct regmap *regmap; + struct mutex lock; + int type; + unsigned int calibscale; + unsigned int ucalibscale; + unsigned int int_time; + struct isl29018_scale scale; + int prox_scheme; + bool suspended; + struct regulator *vcc_reg; +}; + +static int isl29018_set_integration_time(struct isl29018_chip *chip, + unsigned int utime) +{ + unsigned int i; + int ret; + unsigned int int_time, new_int_time; + + for (i = 0; i < ARRAY_SIZE(isl29018_int_utimes[chip->type]); ++i) { + if (utime == isl29018_int_utimes[chip->type][i]) { + new_int_time = i; + break; + } + } + + if (i >= ARRAY_SIZE(isl29018_int_utimes[chip->type])) + return -EINVAL; + + ret = regmap_update_bits(chip->regmap, ISL29018_REG_ADD_COMMAND2, + ISL29018_CMD2_RESOLUTION_MASK, + i << ISL29018_CMD2_RESOLUTION_SHIFT); + if (ret < 0) + return ret; + + /* Keep the same range when integration time changes */ + int_time = chip->int_time; + for (i = 0; i < ARRAY_SIZE(isl29018_scales[int_time]); ++i) { + if (chip->scale.scale == isl29018_scales[int_time][i].scale && + chip->scale.uscale == isl29018_scales[int_time][i].uscale) { + chip->scale = isl29018_scales[new_int_time][i]; + break; + } + } + chip->int_time = new_int_time; + + return 0; +} + +static int isl29018_set_scale(struct isl29018_chip *chip, int scale, int uscale) +{ + unsigned int i; + int ret; + struct isl29018_scale new_scale; + + for (i = 0; i < ARRAY_SIZE(isl29018_scales[chip->int_time]); ++i) { + if (scale == isl29018_scales[chip->int_time][i].scale && + uscale == isl29018_scales[chip->int_time][i].uscale) { + new_scale = isl29018_scales[chip->int_time][i]; + break; + } + } + + if (i >= ARRAY_SIZE(isl29018_scales[chip->int_time])) + return -EINVAL; + + ret = regmap_update_bits(chip->regmap, ISL29018_REG_ADD_COMMAND2, + ISL29018_CMD2_RANGE_MASK, + i << ISL29018_CMD2_RANGE_SHIFT); + if (ret < 0) + return ret; + + chip->scale = new_scale; + + return 0; +} + +static int isl29018_read_sensor_input(struct isl29018_chip *chip, int mode) +{ + int status; + unsigned int lsb; + unsigned int msb; + struct device *dev = regmap_get_device(chip->regmap); + + /* Set mode */ + status = regmap_write(chip->regmap, ISL29018_REG_ADD_COMMAND1, + mode << ISL29018_CMD1_OPMODE_SHIFT); + if (status) { + dev_err(dev, + "Error in setting operating mode err %d\n", status); + return status; + } + msleep(ISL29018_CONV_TIME_MS); + status = regmap_read(chip->regmap, ISL29018_REG_ADD_DATA_LSB, &lsb); + if (status < 0) { + dev_err(dev, + "Error in reading LSB DATA with err %d\n", status); + return status; + } + + status = regmap_read(chip->regmap, ISL29018_REG_ADD_DATA_MSB, &msb); + if (status < 0) { + dev_err(dev, + "Error in reading MSB DATA with error %d\n", status); + return status; + } + dev_vdbg(dev, "MSB 0x%x and LSB 0x%x\n", msb, lsb); + + return (msb << 8) | lsb; +} + +static int isl29018_read_lux(struct isl29018_chip *chip, int *lux) +{ + int lux_data; + unsigned int data_x_range; + + lux_data = isl29018_read_sensor_input(chip, + ISL29018_CMD1_OPMODE_ALS_ONCE); + if (lux_data < 0) + return lux_data; + + data_x_range = lux_data * chip->scale.scale + + lux_data * chip->scale.uscale / 1000000; + *lux = data_x_range * chip->calibscale + + data_x_range * chip->ucalibscale / 1000000; + + return 0; +} + +static int isl29018_read_ir(struct isl29018_chip *chip, int *ir) +{ + int ir_data; + + ir_data = isl29018_read_sensor_input(chip, + ISL29018_CMD1_OPMODE_IR_ONCE); + if (ir_data < 0) + return ir_data; + + *ir = ir_data; + + return 0; +} + +static int isl29018_read_proximity_ir(struct isl29018_chip *chip, int scheme, + int *near_ir) +{ + int status; + int prox_data = -1; + int ir_data = -1; + struct device *dev = regmap_get_device(chip->regmap); + + /* Do proximity sensing with required scheme */ + status = regmap_update_bits(chip->regmap, ISL29018_REG_ADD_COMMAND2, + ISL29018_CMD2_SCHEME_MASK, + scheme << ISL29018_CMD2_SCHEME_SHIFT); + if (status) { + dev_err(dev, "Error in setting operating mode\n"); + return status; + } + + prox_data = isl29018_read_sensor_input(chip, + ISL29018_CMD1_OPMODE_PROX_ONCE); + if (prox_data < 0) + return prox_data; + + if (scheme == 1) { + *near_ir = prox_data; + return 0; + } + + ir_data = isl29018_read_sensor_input(chip, + ISL29018_CMD1_OPMODE_IR_ONCE); + if (ir_data < 0) + return ir_data; + + if (prox_data >= ir_data) + *near_ir = prox_data - ir_data; + else + *near_ir = 0; + + return 0; +} + +static ssize_t in_illuminance_scale_available_show + (struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + unsigned int i; + int len = 0; + + mutex_lock(&chip->lock); + for (i = 0; i < ARRAY_SIZE(isl29018_scales[chip->int_time]); ++i) + len += sysfs_emit_at(buf, len, "%d.%06d ", + isl29018_scales[chip->int_time][i].scale, + isl29018_scales[chip->int_time][i].uscale); + mutex_unlock(&chip->lock); + + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t in_illuminance_integration_time_available_show + (struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + unsigned int i; + int len = 0; + + for (i = 0; i < ARRAY_SIZE(isl29018_int_utimes[chip->type]); ++i) + len += sysfs_emit_at(buf, len, "0.%06d ", + isl29018_int_utimes[chip->type][i]); + + buf[len - 1] = '\n'; + + return len; +} + +/* + * From ISL29018 Data Sheet (FN6619.4, Oct 8, 2012) regarding the + * infrared suppression: + * + * Proximity Sensing Scheme: Bit 7. This bit programs the function + * of the proximity detection. Logic 0 of this bit, Scheme 0, makes + * full n (4, 8, 12, 16) bits (unsigned) proximity detection. The range + * of Scheme 0 proximity count is from 0 to 2^n. Logic 1 of this bit, + * Scheme 1, makes n-1 (3, 7, 11, 15) bits (2's complementary) + * proximity_less_ambient detection. The range of Scheme 1 + * proximity count is from -2^(n-1) to 2^(n-1) . The sign bit is extended + * for resolutions less than 16. While Scheme 0 has wider dynamic + * range, Scheme 1 proximity detection is less affected by the + * ambient IR noise variation. + * + * 0 Sensing IR from LED and ambient + * 1 Sensing IR from LED with ambient IR rejection + */ +static ssize_t proximity_on_chip_ambient_infrared_suppression_show + (struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + + /* + * Return the "proximity scheme" i.e. if the chip does on chip + * infrared suppression (1 means perform on chip suppression) + */ + return sysfs_emit(buf, "%d\n", chip->prox_scheme); +} + +static ssize_t proximity_on_chip_ambient_infrared_suppression_store + (struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + int val; + + if (kstrtoint(buf, 10, &val)) + return -EINVAL; + if (!(val == 0 || val == 1)) + return -EINVAL; + + /* + * Get the "proximity scheme" i.e. if the chip does on chip + * infrared suppression (1 means perform on chip suppression) + */ + mutex_lock(&chip->lock); + chip->prox_scheme = val; + mutex_unlock(&chip->lock); + + return count; +} + +static int isl29018_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct isl29018_chip *chip = iio_priv(indio_dev); + int ret = -EINVAL; + + mutex_lock(&chip->lock); + if (chip->suspended) { + ret = -EBUSY; + goto write_done; + } + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_LIGHT) { + chip->calibscale = val; + chip->ucalibscale = val2; + ret = 0; + } + break; + case IIO_CHAN_INFO_INT_TIME: + if (chan->type == IIO_LIGHT && !val) + ret = isl29018_set_integration_time(chip, val2); + break; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_LIGHT) + ret = isl29018_set_scale(chip, val, val2); + break; + default: + break; + } + +write_done: + mutex_unlock(&chip->lock); + + return ret; +} + +static int isl29018_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret = -EINVAL; + struct isl29018_chip *chip = iio_priv(indio_dev); + + mutex_lock(&chip->lock); + if (chip->suspended) { + ret = -EBUSY; + goto read_done; + } + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + ret = isl29018_read_lux(chip, val); + break; + case IIO_INTENSITY: + ret = isl29018_read_ir(chip, val); + break; + case IIO_PROXIMITY: + ret = isl29018_read_proximity_ir(chip, + chip->prox_scheme, + val); + break; + default: + break; + } + if (!ret) + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_INT_TIME: + if (chan->type == IIO_LIGHT) { + *val = 0; + *val2 = isl29018_int_utimes[chip->type][chip->int_time]; + ret = IIO_VAL_INT_PLUS_MICRO; + } + break; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_LIGHT) { + *val = chip->scale.scale; + *val2 = chip->scale.uscale; + ret = IIO_VAL_INT_PLUS_MICRO; + } + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_LIGHT) { + *val = chip->calibscale; + *val2 = chip->ucalibscale; + ret = IIO_VAL_INT_PLUS_MICRO; + } + break; + default: + break; + } + +read_done: + mutex_unlock(&chip->lock); + + return ret; +} + +#define ISL29018_LIGHT_CHANNEL { \ + .type = IIO_LIGHT, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ +} + +#define ISL29018_IR_CHANNEL { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .channel2 = IIO_MOD_LIGHT_IR, \ +} + +#define ISL29018_PROXIMITY_CHANNEL { \ + .type = IIO_PROXIMITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ +} + +static const struct iio_chan_spec isl29018_channels[] = { + ISL29018_LIGHT_CHANNEL, + ISL29018_IR_CHANNEL, + ISL29018_PROXIMITY_CHANNEL, +}; + +static const struct iio_chan_spec isl29023_channels[] = { + ISL29018_LIGHT_CHANNEL, + ISL29018_IR_CHANNEL, +}; + +static IIO_DEVICE_ATTR_RO(in_illuminance_integration_time_available, 0); +static IIO_DEVICE_ATTR_RO(in_illuminance_scale_available, 0); +static IIO_DEVICE_ATTR_RW(proximity_on_chip_ambient_infrared_suppression, 0); + +#define ISL29018_DEV_ATTR(name) (&iio_dev_attr_##name.dev_attr.attr) + +static struct attribute *isl29018_attributes[] = { + ISL29018_DEV_ATTR(in_illuminance_scale_available), + ISL29018_DEV_ATTR(in_illuminance_integration_time_available), + ISL29018_DEV_ATTR(proximity_on_chip_ambient_infrared_suppression), + NULL +}; + +static struct attribute *isl29023_attributes[] = { + ISL29018_DEV_ATTR(in_illuminance_scale_available), + ISL29018_DEV_ATTR(in_illuminance_integration_time_available), + NULL +}; + +static const struct attribute_group isl29018_group = { + .attrs = isl29018_attributes, +}; + +static const struct attribute_group isl29023_group = { + .attrs = isl29023_attributes, +}; + +enum { + isl29018, + isl29023, + isl29035, +}; + +static int isl29018_chip_init(struct isl29018_chip *chip) +{ + int status; + struct device *dev = regmap_get_device(chip->regmap); + + if (chip->type == isl29035) { + unsigned int id; + + status = regmap_read(chip->regmap, ISL29035_REG_DEVICE_ID, &id); + if (status < 0) { + dev_err(dev, + "Error reading ID register with error %d\n", + status); + return status; + } + + id = (id & ISL29035_DEVICE_ID_MASK) >> ISL29035_DEVICE_ID_SHIFT; + + if (id != ISL29035_DEVICE_ID) + return -ENODEV; + + /* Clear brownout bit */ + status = regmap_clear_bits(chip->regmap, + ISL29035_REG_DEVICE_ID, + ISL29035_BOUT_MASK); + if (status < 0) + return status; + } + + /* + * Code added per Intersil Application Note 1534: + * When VDD sinks to approximately 1.8V or below, some of + * the part's registers may change their state. When VDD + * recovers to 2.25V (or greater), the part may thus be in an + * unknown mode of operation. The user can return the part to + * a known mode of operation either by (a) setting VDD = 0V for + * 1 second or more and then powering back up with a slew rate + * of 0.5V/ms or greater, or (b) via I2C disable all ALS/PROX + * conversions, clear the test registers, and then rewrite all + * registers to the desired values. + * ... + * For ISL29011, ISL29018, ISL29021, ISL29023 + * 1. Write 0x00 to register 0x08 (TEST) + * 2. Write 0x00 to register 0x00 (CMD1) + * 3. Rewrite all registers to the desired values + * + * ISL29018 Data Sheet (FN6619.1, Feb 11, 2010) essentially says + * the same thing EXCEPT the data sheet asks for a 1ms delay after + * writing the CMD1 register. + */ + status = regmap_write(chip->regmap, ISL29018_REG_TEST, 0x0); + if (status < 0) { + dev_err(dev, "Failed to clear isl29018 TEST reg.(%d)\n", + status); + return status; + } + + /* + * See Intersil AN1534 comments above. + * "Operating Mode" (COMMAND1) register is reprogrammed when + * data is read from the device. + */ + status = regmap_write(chip->regmap, ISL29018_REG_ADD_COMMAND1, 0); + if (status < 0) { + dev_err(dev, "Failed to clear isl29018 CMD1 reg.(%d)\n", + status); + return status; + } + + usleep_range(1000, 2000); /* per data sheet, page 10 */ + + /* Set defaults */ + status = isl29018_set_scale(chip, chip->scale.scale, + chip->scale.uscale); + if (status < 0) { + dev_err(dev, "Init of isl29018 fails\n"); + return status; + } + + status = isl29018_set_integration_time(chip, + isl29018_int_utimes[chip->type][chip->int_time]); + if (status < 0) + dev_err(dev, "Init of isl29018 fails\n"); + + return status; +} + +static const struct iio_info isl29018_info = { + .attrs = &isl29018_group, + .read_raw = isl29018_read_raw, + .write_raw = isl29018_write_raw, +}; + +static const struct iio_info isl29023_info = { + .attrs = &isl29023_group, + .read_raw = isl29018_read_raw, + .write_raw = isl29018_write_raw, +}; + +static bool isl29018_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ISL29018_REG_ADD_DATA_LSB: + case ISL29018_REG_ADD_DATA_MSB: + case ISL29018_REG_ADD_COMMAND1: + case ISL29018_REG_TEST: + case ISL29035_REG_DEVICE_ID: + return true; + default: + return false; + } +} + +static const struct regmap_config isl29018_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = isl29018_is_volatile_reg, + .max_register = ISL29018_REG_TEST, + .num_reg_defaults_raw = ISL29018_REG_TEST + 1, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct regmap_config isl29035_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = isl29018_is_volatile_reg, + .max_register = ISL29035_REG_DEVICE_ID, + .num_reg_defaults_raw = ISL29035_REG_DEVICE_ID + 1, + .cache_type = REGCACHE_RBTREE, +}; + +struct isl29018_chip_info { + const struct iio_chan_spec *channels; + int num_channels; + const struct iio_info *indio_info; + const struct regmap_config *regmap_cfg; +}; + +static const struct isl29018_chip_info isl29018_chip_info_tbl[] = { + [isl29018] = { + .channels = isl29018_channels, + .num_channels = ARRAY_SIZE(isl29018_channels), + .indio_info = &isl29018_info, + .regmap_cfg = &isl29018_regmap_config, + }, + [isl29023] = { + .channels = isl29023_channels, + .num_channels = ARRAY_SIZE(isl29023_channels), + .indio_info = &isl29023_info, + .regmap_cfg = &isl29018_regmap_config, + }, + [isl29035] = { + .channels = isl29023_channels, + .num_channels = ARRAY_SIZE(isl29023_channels), + .indio_info = &isl29023_info, + .regmap_cfg = &isl29035_regmap_config, + }, +}; + +static void isl29018_disable_regulator_action(void *_data) +{ + struct isl29018_chip *chip = _data; + int err; + + err = regulator_disable(chip->vcc_reg); + if (err) + pr_err("failed to disable isl29018's VCC regulator!\n"); +} + +static int isl29018_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct isl29018_chip *chip; + struct iio_dev *indio_dev; + const void *ddata = NULL; + const char *name; + int dev_id; + int err; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + i2c_set_clientdata(client, indio_dev); + + if (id) { + name = id->name; + dev_id = id->driver_data; + } else { + name = iio_get_acpi_device_name_and_data(&client->dev, &ddata); + dev_id = (intptr_t)ddata; + } + + mutex_init(&chip->lock); + + chip->type = dev_id; + chip->calibscale = 1; + chip->ucalibscale = 0; + chip->int_time = ISL29018_INT_TIME_16; + chip->scale = isl29018_scales[chip->int_time][0]; + chip->suspended = false; + + chip->vcc_reg = devm_regulator_get(&client->dev, "vcc"); + if (IS_ERR(chip->vcc_reg)) + return dev_err_probe(&client->dev, PTR_ERR(chip->vcc_reg), + "failed to get VCC regulator!\n"); + + err = regulator_enable(chip->vcc_reg); + if (err) { + dev_err(&client->dev, "failed to enable VCC regulator!\n"); + return err; + } + + err = devm_add_action_or_reset(&client->dev, isl29018_disable_regulator_action, + chip); + if (err) { + dev_err(&client->dev, "failed to setup regulator cleanup action!\n"); + return err; + } + + chip->regmap = devm_regmap_init_i2c(client, + isl29018_chip_info_tbl[dev_id].regmap_cfg); + if (IS_ERR(chip->regmap)) { + err = PTR_ERR(chip->regmap); + dev_err(&client->dev, "regmap initialization fails: %d\n", err); + return err; + } + + err = isl29018_chip_init(chip); + if (err) + return err; + + indio_dev->info = isl29018_chip_info_tbl[dev_id].indio_info; + indio_dev->channels = isl29018_chip_info_tbl[dev_id].channels; + indio_dev->num_channels = isl29018_chip_info_tbl[dev_id].num_channels; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int isl29018_suspend(struct device *dev) +{ + struct isl29018_chip *chip = iio_priv(dev_get_drvdata(dev)); + int ret; + + mutex_lock(&chip->lock); + + /* + * Since this driver uses only polling commands, we are by default in + * auto shutdown (ie, power-down) mode. + * So we do not have much to do here. + */ + chip->suspended = true; + ret = regulator_disable(chip->vcc_reg); + if (ret) + dev_err(dev, "failed to disable VCC regulator\n"); + + mutex_unlock(&chip->lock); + + return ret; +} + +static int isl29018_resume(struct device *dev) +{ + struct isl29018_chip *chip = iio_priv(dev_get_drvdata(dev)); + int err; + + mutex_lock(&chip->lock); + + err = regulator_enable(chip->vcc_reg); + if (err) { + dev_err(dev, "failed to enable VCC regulator\n"); + mutex_unlock(&chip->lock); + return err; + } + + err = isl29018_chip_init(chip); + if (!err) + chip->suspended = false; + + mutex_unlock(&chip->lock); + + return err; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(isl29018_pm_ops, isl29018_suspend, + isl29018_resume); + +static const struct acpi_device_id isl29018_acpi_match[] = { + {"ISL29018", isl29018}, + {"ISL29023", isl29023}, + {"ISL29035", isl29035}, + { } +}; +MODULE_DEVICE_TABLE(acpi, isl29018_acpi_match); + +static const struct i2c_device_id isl29018_id[] = { + { .name = "isl29018", .driver_data = isl29018 }, + { .name = "isl29023", .driver_data = isl29023 }, + { .name = "isl29035", .driver_data = isl29035 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, isl29018_id); + +static const struct of_device_id isl29018_of_match[] = { + { .compatible = "isil,isl29018", }, + { .compatible = "isil,isl29023", }, + { .compatible = "isil,isl29035", }, + { } +}; +MODULE_DEVICE_TABLE(of, isl29018_of_match); + +static struct i2c_driver isl29018_driver = { + .driver = { + .name = "isl29018", + .acpi_match_table = isl29018_acpi_match, + .pm = pm_sleep_ptr(&isl29018_pm_ops), + .of_match_table = isl29018_of_match, + }, + .probe = isl29018_probe, + .id_table = isl29018_id, +}; +module_i2c_driver(isl29018_driver); + +MODULE_DESCRIPTION("ISL29018 Ambient Light Sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/isl29028.c b/drivers/iio/light/isl29028.c new file mode 100644 index 000000000..33deb1726 --- /dev/null +++ b/drivers/iio/light/isl29028.c @@ -0,0 +1,695 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO driver for the light sensor ISL29028. + * ISL29028 is Concurrent Ambient Light and Proximity Sensor + * + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2016-2017 Brian Masney <masneyb@onstation.org> + * + * Datasheets: + * - http://www.intersil.com/content/dam/Intersil/documents/isl2/isl29028.pdf + * - http://www.intersil.com/content/dam/Intersil/documents/isl2/isl29030.pdf + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/regmap.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/pm_runtime.h> + +#define ISL29028_CONV_TIME_MS 100 + +#define ISL29028_REG_CONFIGURE 0x01 + +#define ISL29028_CONF_ALS_IR_MODE_ALS 0 +#define ISL29028_CONF_ALS_IR_MODE_IR BIT(0) +#define ISL29028_CONF_ALS_IR_MODE_MASK BIT(0) + +#define ISL29028_CONF_ALS_RANGE_LOW_LUX 0 +#define ISL29028_CONF_ALS_RANGE_HIGH_LUX BIT(1) +#define ISL29028_CONF_ALS_RANGE_MASK BIT(1) + +#define ISL29028_CONF_ALS_DIS 0 +#define ISL29028_CONF_ALS_EN BIT(2) +#define ISL29028_CONF_ALS_EN_MASK BIT(2) + +#define ISL29028_CONF_PROX_SLP_SH 4 +#define ISL29028_CONF_PROX_SLP_MASK (7 << ISL29028_CONF_PROX_SLP_SH) + +#define ISL29028_CONF_PROX_EN BIT(7) +#define ISL29028_CONF_PROX_EN_MASK BIT(7) + +#define ISL29028_REG_INTERRUPT 0x02 + +#define ISL29028_REG_PROX_DATA 0x08 +#define ISL29028_REG_ALSIR_L 0x09 +#define ISL29028_REG_ALSIR_U 0x0A + +#define ISL29028_REG_TEST1_MODE 0x0E +#define ISL29028_REG_TEST2_MODE 0x0F + +#define ISL29028_NUM_REGS (ISL29028_REG_TEST2_MODE + 1) + +#define ISL29028_POWER_OFF_DELAY_MS 2000 + +struct isl29028_prox_data { + int sampling_int; + int sampling_fract; + int sleep_time; +}; + +static const struct isl29028_prox_data isl29028_prox_data[] = { + { 1, 250000, 800 }, + { 2, 500000, 400 }, + { 5, 0, 200 }, + { 10, 0, 100 }, + { 13, 300000, 75 }, + { 20, 0, 50 }, + { 80, 0, 13 }, /* + * Note: Data sheet lists 12.5 ms sleep time. + * Round up a half millisecond for msleep(). + */ + { 100, 0, 0 } +}; + +enum isl29028_als_ir_mode { + ISL29028_MODE_NONE = 0, + ISL29028_MODE_ALS, + ISL29028_MODE_IR, +}; + +struct isl29028_chip { + struct mutex lock; + struct regmap *regmap; + int prox_sampling_int; + int prox_sampling_frac; + bool enable_prox; + int lux_scale; + enum isl29028_als_ir_mode als_ir_mode; +}; + +static int isl29028_find_prox_sleep_index(int sampling_int, int sampling_fract) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(isl29028_prox_data); ++i) { + if (isl29028_prox_data[i].sampling_int == sampling_int && + isl29028_prox_data[i].sampling_fract == sampling_fract) + return i; + } + + return -EINVAL; +} + +static int isl29028_set_proxim_sampling(struct isl29028_chip *chip, + int sampling_int, int sampling_fract) +{ + struct device *dev = regmap_get_device(chip->regmap); + int sleep_index, ret; + + sleep_index = isl29028_find_prox_sleep_index(sampling_int, + sampling_fract); + if (sleep_index < 0) + return sleep_index; + + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + ISL29028_CONF_PROX_SLP_MASK, + sleep_index << ISL29028_CONF_PROX_SLP_SH); + + if (ret < 0) { + dev_err(dev, "%s(): Error %d setting the proximity sampling\n", + __func__, ret); + return ret; + } + + chip->prox_sampling_int = sampling_int; + chip->prox_sampling_frac = sampling_fract; + + return ret; +} + +static int isl29028_enable_proximity(struct isl29028_chip *chip) +{ + int prox_index, ret; + + ret = isl29028_set_proxim_sampling(chip, chip->prox_sampling_int, + chip->prox_sampling_frac); + if (ret < 0) + return ret; + + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + ISL29028_CONF_PROX_EN_MASK, + ISL29028_CONF_PROX_EN); + if (ret < 0) + return ret; + + /* Wait for conversion to be complete for first sample */ + prox_index = isl29028_find_prox_sleep_index(chip->prox_sampling_int, + chip->prox_sampling_frac); + if (prox_index < 0) + return prox_index; + + msleep(isl29028_prox_data[prox_index].sleep_time); + + return 0; +} + +static int isl29028_set_als_scale(struct isl29028_chip *chip, int lux_scale) +{ + struct device *dev = regmap_get_device(chip->regmap); + int val = (lux_scale == 2000) ? ISL29028_CONF_ALS_RANGE_HIGH_LUX : + ISL29028_CONF_ALS_RANGE_LOW_LUX; + int ret; + + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + ISL29028_CONF_ALS_RANGE_MASK, val); + if (ret < 0) { + dev_err(dev, "%s(): Error %d setting the ALS scale\n", __func__, + ret); + return ret; + } + + chip->lux_scale = lux_scale; + + return ret; +} + +static int isl29028_set_als_ir_mode(struct isl29028_chip *chip, + enum isl29028_als_ir_mode mode) +{ + int ret; + + if (chip->als_ir_mode == mode) + return 0; + + ret = isl29028_set_als_scale(chip, chip->lux_scale); + if (ret < 0) + return ret; + + switch (mode) { + case ISL29028_MODE_ALS: + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + ISL29028_CONF_ALS_IR_MODE_MASK, + ISL29028_CONF_ALS_IR_MODE_ALS); + if (ret < 0) + return ret; + + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + ISL29028_CONF_ALS_RANGE_MASK, + ISL29028_CONF_ALS_RANGE_HIGH_LUX); + break; + case ISL29028_MODE_IR: + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + ISL29028_CONF_ALS_IR_MODE_MASK, + ISL29028_CONF_ALS_IR_MODE_IR); + break; + case ISL29028_MODE_NONE: + return regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + ISL29028_CONF_ALS_EN_MASK, + ISL29028_CONF_ALS_DIS); + } + + if (ret < 0) + return ret; + + /* Enable the ALS/IR */ + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + ISL29028_CONF_ALS_EN_MASK, + ISL29028_CONF_ALS_EN); + if (ret < 0) + return ret; + + /* Need to wait for conversion time if ALS/IR mode enabled */ + msleep(ISL29028_CONV_TIME_MS); + + chip->als_ir_mode = mode; + + return 0; +} + +static int isl29028_read_als_ir(struct isl29028_chip *chip, int *als_ir) +{ + struct device *dev = regmap_get_device(chip->regmap); + unsigned int lsb; + unsigned int msb; + int ret; + + ret = regmap_read(chip->regmap, ISL29028_REG_ALSIR_L, &lsb); + if (ret < 0) { + dev_err(dev, + "%s(): Error %d reading register ALSIR_L\n", + __func__, ret); + return ret; + } + + ret = regmap_read(chip->regmap, ISL29028_REG_ALSIR_U, &msb); + if (ret < 0) { + dev_err(dev, + "%s(): Error %d reading register ALSIR_U\n", + __func__, ret); + return ret; + } + + *als_ir = ((msb & 0xF) << 8) | (lsb & 0xFF); + + return 0; +} + +static int isl29028_read_proxim(struct isl29028_chip *chip, int *prox) +{ + struct device *dev = regmap_get_device(chip->regmap); + unsigned int data; + int ret; + + if (!chip->enable_prox) { + ret = isl29028_enable_proximity(chip); + if (ret < 0) + return ret; + + chip->enable_prox = true; + } + + ret = regmap_read(chip->regmap, ISL29028_REG_PROX_DATA, &data); + if (ret < 0) { + dev_err(dev, "%s(): Error %d reading register PROX_DATA\n", + __func__, ret); + return ret; + } + + *prox = data; + + return 0; +} + +static int isl29028_als_get(struct isl29028_chip *chip, int *als_data) +{ + struct device *dev = regmap_get_device(chip->regmap); + int ret; + int als_ir_data; + + ret = isl29028_set_als_ir_mode(chip, ISL29028_MODE_ALS); + if (ret < 0) { + dev_err(dev, "%s(): Error %d enabling ALS mode\n", __func__, + ret); + return ret; + } + + ret = isl29028_read_als_ir(chip, &als_ir_data); + if (ret < 0) + return ret; + + /* + * convert als data count to lux. + * if lux_scale = 125, lux = count * 0.031 + * if lux_scale = 2000, lux = count * 0.49 + */ + if (chip->lux_scale == 125) + als_ir_data = (als_ir_data * 31) / 1000; + else + als_ir_data = (als_ir_data * 49) / 100; + + *als_data = als_ir_data; + + return 0; +} + +static int isl29028_ir_get(struct isl29028_chip *chip, int *ir_data) +{ + struct device *dev = regmap_get_device(chip->regmap); + int ret; + + ret = isl29028_set_als_ir_mode(chip, ISL29028_MODE_IR); + if (ret < 0) { + dev_err(dev, "%s(): Error %d enabling IR mode\n", __func__, + ret); + return ret; + } + + return isl29028_read_als_ir(chip, ir_data); +} + +/* Channel IO */ +static int isl29028_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct isl29028_chip *chip = iio_priv(indio_dev); + struct device *dev = regmap_get_device(chip->regmap); + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + mutex_lock(&chip->lock); + + ret = -EINVAL; + switch (chan->type) { + case IIO_PROXIMITY: + if (mask != IIO_CHAN_INFO_SAMP_FREQ) { + dev_err(dev, + "%s(): proximity: Mask value 0x%08lx is not supported\n", + __func__, mask); + break; + } + + if (val < 1 || val > 100) { + dev_err(dev, + "%s(): proximity: Sampling frequency %d is not in the range [1:100]\n", + __func__, val); + break; + } + + ret = isl29028_set_proxim_sampling(chip, val, val2); + break; + case IIO_LIGHT: + if (mask != IIO_CHAN_INFO_SCALE) { + dev_err(dev, + "%s(): light: Mask value 0x%08lx is not supported\n", + __func__, mask); + break; + } + + if (val != 125 && val != 2000) { + dev_err(dev, + "%s(): light: Lux scale %d is not in the set {125, 2000}\n", + __func__, val); + break; + } + + ret = isl29028_set_als_scale(chip, val); + break; + default: + dev_err(dev, "%s(): Unsupported channel type %x\n", + __func__, chan->type); + break; + } + + mutex_unlock(&chip->lock); + + if (ret < 0) + return ret; + + ret = pm_runtime_put_autosuspend(dev); + if (ret < 0) + return ret; + + return 0; +} + +static int isl29028_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct isl29028_chip *chip = iio_priv(indio_dev); + struct device *dev = regmap_get_device(chip->regmap); + int ret, pm_ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + mutex_lock(&chip->lock); + + ret = -EINVAL; + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + ret = isl29028_als_get(chip, val); + break; + case IIO_INTENSITY: + ret = isl29028_ir_get(chip, val); + break; + case IIO_PROXIMITY: + ret = isl29028_read_proxim(chip, val); + break; + default: + break; + } + + if (ret < 0) + break; + + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + if (chan->type != IIO_PROXIMITY) + break; + + *val = chip->prox_sampling_int; + *val2 = chip->prox_sampling_frac; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_LIGHT) + break; + *val = chip->lux_scale; + ret = IIO_VAL_INT; + break; + default: + dev_err(dev, "%s(): mask value 0x%08lx is not supported\n", + __func__, mask); + break; + } + + mutex_unlock(&chip->lock); + + if (ret < 0) + return ret; + + /** + * Preserve the ret variable if the call to + * pm_runtime_put_autosuspend() is successful so the reading + * (if applicable) is returned to user space. + */ + pm_ret = pm_runtime_put_autosuspend(dev); + if (pm_ret < 0) + return pm_ret; + + return ret; +} + +static IIO_CONST_ATTR(in_proximity_sampling_frequency_available, + "1.25 2.5 5 10 13.3 20 80 100"); +static IIO_CONST_ATTR(in_illuminance_scale_available, "125 2000"); + +#define ISL29028_CONST_ATTR(name) (&iio_const_attr_##name.dev_attr.attr) +static struct attribute *isl29028_attributes[] = { + ISL29028_CONST_ATTR(in_proximity_sampling_frequency_available), + ISL29028_CONST_ATTR(in_illuminance_scale_available), + NULL, +}; + +static const struct attribute_group isl29108_group = { + .attrs = isl29028_attributes, +}; + +static const struct iio_chan_spec isl29028_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_INTENSITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + } +}; + +static const struct iio_info isl29028_info = { + .attrs = &isl29108_group, + .read_raw = isl29028_read_raw, + .write_raw = isl29028_write_raw, +}; + +static int isl29028_clear_configure_reg(struct isl29028_chip *chip) +{ + struct device *dev = regmap_get_device(chip->regmap); + int ret; + + ret = regmap_write(chip->regmap, ISL29028_REG_CONFIGURE, 0x0); + if (ret < 0) + dev_err(dev, "%s(): Error %d clearing the CONFIGURE register\n", + __func__, ret); + + chip->als_ir_mode = ISL29028_MODE_NONE; + chip->enable_prox = false; + + return ret; +} + +static bool isl29028_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ISL29028_REG_INTERRUPT: + case ISL29028_REG_PROX_DATA: + case ISL29028_REG_ALSIR_L: + case ISL29028_REG_ALSIR_U: + return true; + default: + return false; + } +} + +static const struct regmap_config isl29028_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = isl29028_is_volatile_reg, + .max_register = ISL29028_NUM_REGS - 1, + .num_reg_defaults_raw = ISL29028_NUM_REGS, + .cache_type = REGCACHE_MAPLE, +}; + +static int isl29028_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct isl29028_chip *chip; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + i2c_set_clientdata(client, indio_dev); + mutex_init(&chip->lock); + + chip->regmap = devm_regmap_init_i2c(client, &isl29028_regmap_config); + if (IS_ERR(chip->regmap)) { + ret = PTR_ERR(chip->regmap); + dev_err(&client->dev, "%s: Error %d initializing regmap\n", + __func__, ret); + return ret; + } + + chip->enable_prox = false; + chip->prox_sampling_int = 20; + chip->prox_sampling_frac = 0; + chip->lux_scale = 2000; + + ret = regmap_write(chip->regmap, ISL29028_REG_TEST1_MODE, 0x0); + if (ret < 0) { + dev_err(&client->dev, + "%s(): Error %d writing to TEST1_MODE register\n", + __func__, ret); + return ret; + } + + ret = regmap_write(chip->regmap, ISL29028_REG_TEST2_MODE, 0x0); + if (ret < 0) { + dev_err(&client->dev, + "%s(): Error %d writing to TEST2_MODE register\n", + __func__, ret); + return ret; + } + + ret = isl29028_clear_configure_reg(chip); + if (ret < 0) + return ret; + + indio_dev->info = &isl29028_info; + indio_dev->channels = isl29028_channels; + indio_dev->num_channels = ARRAY_SIZE(isl29028_channels); + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + ISL29028_POWER_OFF_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, + "%s(): iio registration failed with error %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static void isl29028_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct isl29028_chip *chip = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + isl29028_clear_configure_reg(chip); +} + +static int isl29028_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct isl29028_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->lock); + + ret = isl29028_clear_configure_reg(chip); + + mutex_unlock(&chip->lock); + + return ret; +} + +static int isl29028_resume(struct device *dev) +{ + /** + * The specific component (ALS/IR or proximity) will enable itself as + * needed the next time that the user requests a reading. This is done + * above in isl29028_set_als_ir_mode() and isl29028_enable_proximity(). + */ + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(isl29028_pm_ops, isl29028_suspend, + isl29028_resume, NULL); + +static const struct i2c_device_id isl29028_id[] = { + { .name = "isl29028" }, + { .name = "isl29030" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, isl29028_id); + +static const struct of_device_id isl29028_of_match[] = { + { .compatible = "isl,isl29028", }, /* for backward compat., don't use */ + { .compatible = "isil,isl29028", }, + { .compatible = "isil,isl29030", }, + { } +}; +MODULE_DEVICE_TABLE(of, isl29028_of_match); + +static struct i2c_driver isl29028_driver = { + .driver = { + .name = "isl29028", + .pm = pm_ptr(&isl29028_pm_ops), + .of_match_table = isl29028_of_match, + }, + .probe = isl29028_probe, + .remove = isl29028_remove, + .id_table = isl29028_id, +}; + +module_i2c_driver(isl29028_driver); + +MODULE_DESCRIPTION("ISL29028 Ambient Light and Proximity Sensor driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); diff --git a/drivers/iio/light/isl29125.c b/drivers/iio/light/isl29125.c new file mode 100644 index 000000000..9e0b7e6a3 --- /dev/null +++ b/drivers/iio/light/isl29125.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * isl29125.c - Support for Intersil ISL29125 RGB light sensor + * + * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net> + * + * RGB light sensor with 16-bit channels for red, green, blue); + * 7-bit I2C slave address 0x44 + * + * TODO: interrupt support, IR compensation, thresholds, 12bit + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/pm.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> + +#define ISL29125_DRV_NAME "isl29125" + +#define ISL29125_DEVICE_ID 0x00 +#define ISL29125_CONF1 0x01 +#define ISL29125_CONF2 0x02 +#define ISL29125_CONF3 0x03 +#define ISL29125_STATUS 0x08 +#define ISL29125_GREEN_DATA 0x09 +#define ISL29125_RED_DATA 0x0b +#define ISL29125_BLUE_DATA 0x0d + +#define ISL29125_ID 0x7d + +#define ISL29125_MODE_MASK GENMASK(2, 0) +#define ISL29125_MODE_PD 0x0 +#define ISL29125_MODE_G 0x1 +#define ISL29125_MODE_R 0x2 +#define ISL29125_MODE_B 0x3 +#define ISL29125_MODE_RGB 0x5 + +#define ISL29125_SENSING_RANGE_0 5722 /* 375 lux full range */ +#define ISL29125_SENSING_RANGE_1 152590 /* 10k lux full range */ + +#define ISL29125_MODE_RANGE BIT(3) + +#define ISL29125_STATUS_CONV BIT(1) + +struct isl29125_data { + struct i2c_client *client; + u8 conf1; +}; + +#define ISL29125_CHANNEL(_color, _si) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .channel2 = IIO_MOD_LIGHT_##_color, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +static const struct iio_chan_spec isl29125_channels[] = { + ISL29125_CHANNEL(GREEN, 0), + ISL29125_CHANNEL(RED, 1), + ISL29125_CHANNEL(BLUE, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct { + u8 mode, data; +} isl29125_regs[] = { + {ISL29125_MODE_G, ISL29125_GREEN_DATA}, + {ISL29125_MODE_R, ISL29125_RED_DATA}, + {ISL29125_MODE_B, ISL29125_BLUE_DATA}, +}; + +static int isl29125_read_data(struct isl29125_data *data, int si) +{ + int tries = 5; + int ret; + + ret = i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + data->conf1 | isl29125_regs[si].mode); + if (ret < 0) + return ret; + + msleep(101); + + while (tries--) { + ret = i2c_smbus_read_byte_data(data->client, ISL29125_STATUS); + if (ret < 0) + goto fail; + if (ret & ISL29125_STATUS_CONV) + break; + msleep(20); + } + + if (tries < 0) { + dev_err(&data->client->dev, "data not ready\n"); + ret = -EIO; + goto fail; + } + + ret = i2c_smbus_read_word_data(data->client, isl29125_regs[si].data); + +fail: + i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, data->conf1); + return ret; +} + +static int isl29125_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct isl29125_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = isl29125_read_data(data, chan->scan_index); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + if (data->conf1 & ISL29125_MODE_RANGE) + *val2 = ISL29125_SENSING_RANGE_1; /*10k lux full range*/ + else + *val2 = ISL29125_SENSING_RANGE_0; /*375 lux full range*/ + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int isl29125_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct isl29125_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (val != 0) + return -EINVAL; + if (val2 == ISL29125_SENSING_RANGE_1) + data->conf1 |= ISL29125_MODE_RANGE; + else if (val2 == ISL29125_SENSING_RANGE_0) + data->conf1 &= ~ISL29125_MODE_RANGE; + else + return -EINVAL; + return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + data->conf1); + default: + return -EINVAL; + } +} + +static irqreturn_t isl29125_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct isl29125_data *data = iio_priv(indio_dev); + int i, j = 0; + /* Ensure timestamp is naturally aligned */ + struct { + u16 chans[3]; + aligned_s64 timestamp; + } scan = { }; + + iio_for_each_active_channel(indio_dev, i) { + int ret = i2c_smbus_read_word_data(data->client, + isl29125_regs[i].data); + if (ret < 0) + goto done; + + scan.chans[j++] = ret; + } + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static IIO_CONST_ATTR(scale_available, "0.005722 0.152590"); + +static struct attribute *isl29125_attributes[] = { + &iio_const_attr_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group isl29125_attribute_group = { + .attrs = isl29125_attributes, +}; + +static const struct iio_info isl29125_info = { + .read_raw = isl29125_read_raw, + .write_raw = isl29125_write_raw, + .attrs = &isl29125_attribute_group, +}; + +static int isl29125_buffer_postenable(struct iio_dev *indio_dev) +{ + struct isl29125_data *data = iio_priv(indio_dev); + + data->conf1 |= ISL29125_MODE_RGB; + return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + data->conf1); +} + +static int isl29125_buffer_predisable(struct iio_dev *indio_dev) +{ + struct isl29125_data *data = iio_priv(indio_dev); + + data->conf1 &= ~ISL29125_MODE_MASK; + data->conf1 |= ISL29125_MODE_PD; + return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + data->conf1); +} + +static const struct iio_buffer_setup_ops isl29125_buffer_setup_ops = { + .postenable = isl29125_buffer_postenable, + .predisable = isl29125_buffer_predisable, +}; + +static int isl29125_probe(struct i2c_client *client) +{ + struct isl29125_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (indio_dev == NULL) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + indio_dev->info = &isl29125_info; + indio_dev->name = ISL29125_DRV_NAME; + indio_dev->channels = isl29125_channels; + indio_dev->num_channels = ARRAY_SIZE(isl29125_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = i2c_smbus_read_byte_data(data->client, ISL29125_DEVICE_ID); + if (ret < 0) + return ret; + if (ret != ISL29125_ID) + return -ENODEV; + + data->conf1 = ISL29125_MODE_PD | ISL29125_MODE_RANGE; + ret = i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + data->conf1); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, ISL29125_STATUS, 0); + if (ret < 0) + return ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + isl29125_trigger_handler, &isl29125_buffer_setup_ops); + if (ret < 0) + return ret; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto buffer_cleanup; + + return 0; + +buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); + return ret; +} + +static int isl29125_powerdown(struct isl29125_data *data) +{ + return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + (data->conf1 & ~ISL29125_MODE_MASK) | ISL29125_MODE_PD); +} + +static void isl29125_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + isl29125_powerdown(iio_priv(indio_dev)); +} + +static int isl29125_suspend(struct device *dev) +{ + struct isl29125_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return isl29125_powerdown(data); +} + +static int isl29125_resume(struct device *dev) +{ + struct isl29125_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + data->conf1); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(isl29125_pm_ops, isl29125_suspend, + isl29125_resume); + +static const struct i2c_device_id isl29125_id[] = { + { .name = "isl29125" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, isl29125_id); + +static struct i2c_driver isl29125_driver = { + .driver = { + .name = ISL29125_DRV_NAME, + .pm = pm_sleep_ptr(&isl29125_pm_ops), + }, + .probe = isl29125_probe, + .remove = isl29125_remove, + .id_table = isl29125_id, +}; +module_i2c_driver(isl29125_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("ISL29125 RGB light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/isl76682.c b/drivers/iio/light/isl76682.c new file mode 100644 index 000000000..9b9052d08 --- /dev/null +++ b/drivers/iio/light/isl76682.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO driver for the light sensor ISL76682. + * ISL76682 is Ambient Light Sensor + * + * Copyright (c) 2023 Marek Vasut <marex@denx.de> + */ + +#include <linux/array_size.h> +#include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/types.h> + +#include <linux/iio/iio.h> + +#define ISL76682_REG_COMMAND 0x00 + +#define ISL76682_COMMAND_EN BIT(7) +#define ISL76682_COMMAND_MODE_CONTINUOUS BIT(6) +#define ISL76682_COMMAND_LIGHT_IR BIT(5) + +#define ISL76682_COMMAND_RANGE_LUX_1K 0x0 +#define ISL76682_COMMAND_RANGE_LUX_4K 0x1 +#define ISL76682_COMMAND_RANGE_LUX_16K 0x2 +#define ISL76682_COMMAND_RANGE_LUX_64K 0x3 +#define ISL76682_COMMAND_RANGE_LUX_MASK GENMASK(1, 0) + +#define ISL76682_REG_ALSIR_L 0x01 + +#define ISL76682_REG_ALSIR_U 0x02 + +#define ISL76682_NUM_REGS (ISL76682_REG_ALSIR_U + 1) + +#define ISL76682_CONV_TIME_MS 100 +#define ISL76682_INT_TIME_US 90000 + +#define ISL76682_ADC_MAX (BIT(16) - 1) + +struct isl76682_chip { + /* + * Lock to synchronize access to device command register + * and the content of range variable below. + */ + struct mutex lock; + struct regmap *regmap; + u8 range; + u8 command; +}; + +struct isl76682_range { + u8 range; + u32 als; + u32 ir; +}; + +static const struct isl76682_range isl76682_range_table[] = { + { ISL76682_COMMAND_RANGE_LUX_1K, 15000, 10500 }, + { ISL76682_COMMAND_RANGE_LUX_4K, 60000, 42000 }, + { ISL76682_COMMAND_RANGE_LUX_16K, 240000, 168000 }, + { ISL76682_COMMAND_RANGE_LUX_64K, 960000, 673000 } +}; + +static int isl76682_get(struct isl76682_chip *chip, bool mode_ir, int *data) +{ + u8 command; + int ret; + + command = ISL76682_COMMAND_EN | ISL76682_COMMAND_MODE_CONTINUOUS | + chip->range; + + if (mode_ir) + command |= ISL76682_COMMAND_LIGHT_IR; + + if (command != chip->command) { + ret = regmap_write(chip->regmap, ISL76682_REG_COMMAND, command); + if (ret) + return ret; + + /* Need to wait for conversion time if ALS/IR mode enabled */ + msleep(ISL76682_CONV_TIME_MS); + + chip->command = command; + } + + ret = regmap_bulk_read(chip->regmap, ISL76682_REG_ALSIR_L, data, 2); + *data &= ISL76682_ADC_MAX; + return ret; +} + +static int isl76682_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct isl76682_chip *chip = iio_priv(indio_dev); + int i; + + if (mask != IIO_CHAN_INFO_SCALE) + return -EINVAL; + + if (val != 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(isl76682_range_table); i++) { + if (chan->type == IIO_LIGHT && val2 != isl76682_range_table[i].als) + continue; + if (chan->type == IIO_INTENSITY && val2 != isl76682_range_table[i].ir) + continue; + + scoped_guard(mutex, &chip->lock) + chip->range = isl76682_range_table[i].range; + return 0; + } + + return -EINVAL; +} + +static int isl76682_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct isl76682_chip *chip = iio_priv(indio_dev); + int ret; + int i; + + guard(mutex)(&chip->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + ret = isl76682_get(chip, false, val); + return (ret < 0) ? ret : IIO_VAL_INT; + case IIO_INTENSITY: + ret = isl76682_get(chip, true, val); + return (ret < 0) ? ret : IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ARRAY_SIZE(isl76682_range_table); i++) { + if (chip->range != isl76682_range_table[i].range) + continue; + + *val = 0; + switch (chan->type) { + case IIO_LIGHT: + *val2 = isl76682_range_table[i].als; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_INTENSITY: + *val2 = isl76682_range_table[i].ir; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + } + return -EINVAL; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = ISL76682_INT_TIME_US; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int illuminance_scale_available[] = { + 0, 15000, + 0, 60000, + 0, 240000, + 0, 960000, +}; + +static int intensity_scale_available[] = { + 0, 10500, + 0, 42000, + 0, 168000, + 0, 673000, +}; + +static int isl76682_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_SCALE: + switch (chan->type) { + case IIO_LIGHT: + *vals = illuminance_scale_available; + *length = ARRAY_SIZE(illuminance_scale_available); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case IIO_INTENSITY: + *vals = intensity_scale_available; + *length = ARRAY_SIZE(intensity_scale_available); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec isl76682_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + }, { + .type = IIO_INTENSITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + } +}; + +static const struct iio_info isl76682_info = { + .read_avail = isl76682_read_avail, + .read_raw = isl76682_read_raw, + .write_raw = isl76682_write_raw, +}; + +static int isl76682_clear_configure_reg(struct isl76682_chip *chip) +{ + struct device *dev = regmap_get_device(chip->regmap); + int ret; + + ret = regmap_write(chip->regmap, ISL76682_REG_COMMAND, 0x0); + if (ret < 0) + dev_err(dev, "Error %d clearing the CONFIGURE register\n", ret); + + /* + * In the success case, the command register was zeroed out. + * + * In the error case, we do not know in which state the command + * register is, so we assume it is zeroed out, so that it would + * be reprogrammed at the next data read out, and at that time + * we hope it would be reprogrammed successfully. That is very + * much a best effort approach. + */ + chip->command = 0; + + return ret; +} + +static void isl76682_reset_action(void *chip) +{ + isl76682_clear_configure_reg(chip); +} + +static bool isl76682_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ISL76682_REG_ALSIR_L: + case ISL76682_REG_ALSIR_U: + return true; + default: + return false; + } +} + +static const struct regmap_config isl76682_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = isl76682_is_volatile_reg, + .max_register = ISL76682_NUM_REGS - 1, + .num_reg_defaults_raw = ISL76682_NUM_REGS, + .cache_type = REGCACHE_FLAT, +}; + +static int isl76682_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct isl76682_chip *chip; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + mutex_init(&chip->lock); + + chip->regmap = devm_regmap_init_i2c(client, &isl76682_regmap_config); + ret = PTR_ERR_OR_ZERO(chip->regmap); + if (ret) + return dev_err_probe(dev, ret, "Error initializing regmap\n"); + + chip->range = ISL76682_COMMAND_RANGE_LUX_1K; + + ret = isl76682_clear_configure_reg(chip); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(dev, isl76682_reset_action, chip); + if (ret) + return ret; + + indio_dev->info = &isl76682_info; + indio_dev->channels = isl76682_channels; + indio_dev->num_channels = ARRAY_SIZE(isl76682_channels); + indio_dev->name = "isl76682"; + indio_dev->modes = INDIO_DIRECT_MODE; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct i2c_device_id isl76682_id[] = { + { .name = "isl76682" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, isl76682_id); + +static const struct of_device_id isl76682_of_match[] = { + { .compatible = "isil,isl76682" }, + { } +}; +MODULE_DEVICE_TABLE(of, isl76682_of_match); + +static struct i2c_driver isl76682_driver = { + .driver = { + .name = "isl76682", + .of_match_table = isl76682_of_match, + }, + .probe = isl76682_probe, + .id_table = isl76682_id, +}; +module_i2c_driver(isl76682_driver); + +MODULE_DESCRIPTION("ISL76682 Ambient Light Sensor driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Marek Vasut <marex@denx.de>"); diff --git a/drivers/iio/light/jsa1212.c b/drivers/iio/light/jsa1212.c new file mode 100644 index 000000000..038ee4972 --- /dev/null +++ b/drivers/iio/light/jsa1212.c @@ -0,0 +1,449 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * JSA1212 Ambient Light & Proximity Sensor Driver + * + * Copyright (c) 2014, Intel Corporation. + * + * JSA1212 I2C slave address: 0x44(ADDR tied to GND), 0x45(ADDR tied to VDD) + * + * TODO: Interrupt support, thresholds, range support. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +/* JSA1212 reg address */ +#define JSA1212_CONF_REG 0x01 +#define JSA1212_INT_REG 0x02 +#define JSA1212_PXS_LT_REG 0x03 +#define JSA1212_PXS_HT_REG 0x04 +#define JSA1212_ALS_TH1_REG 0x05 +#define JSA1212_ALS_TH2_REG 0x06 +#define JSA1212_ALS_TH3_REG 0x07 +#define JSA1212_PXS_DATA_REG 0x08 +#define JSA1212_ALS_DT1_REG 0x09 +#define JSA1212_ALS_DT2_REG 0x0A +#define JSA1212_ALS_RNG_REG 0x0B +#define JSA1212_MAX_REG 0x0C + +/* JSA1212 reg masks */ +#define JSA1212_CONF_MASK 0xFF +#define JSA1212_INT_MASK 0xFF +#define JSA1212_PXS_LT_MASK 0xFF +#define JSA1212_PXS_HT_MASK 0xFF +#define JSA1212_ALS_TH1_MASK 0xFF +#define JSA1212_ALS_TH2_LT_MASK 0x0F +#define JSA1212_ALS_TH2_HT_MASK 0xF0 +#define JSA1212_ALS_TH3_MASK 0xFF +#define JSA1212_PXS_DATA_MASK 0xFF +#define JSA1212_ALS_DATA_MASK 0x0FFF +#define JSA1212_ALS_DT1_MASK 0xFF +#define JSA1212_ALS_DT2_MASK 0x0F +#define JSA1212_ALS_RNG_MASK 0x07 + +/* JSA1212 CONF REG bits */ +#define JSA1212_CONF_PXS_MASK 0x80 +#define JSA1212_CONF_PXS_ENABLE 0x80 +#define JSA1212_CONF_PXS_DISABLE 0x00 +#define JSA1212_CONF_ALS_MASK 0x04 +#define JSA1212_CONF_ALS_ENABLE 0x04 +#define JSA1212_CONF_ALS_DISABLE 0x00 +#define JSA1212_CONF_IRDR_MASK 0x08 +/* Proxmity sensing IRDR current sink settings */ +#define JSA1212_CONF_IRDR_200MA 0x08 +#define JSA1212_CONF_IRDR_100MA 0x00 +#define JSA1212_CONF_PXS_SLP_MASK 0x70 +#define JSA1212_CONF_PXS_SLP_0MS 0x70 +#define JSA1212_CONF_PXS_SLP_12MS 0x60 +#define JSA1212_CONF_PXS_SLP_50MS 0x50 +#define JSA1212_CONF_PXS_SLP_75MS 0x40 +#define JSA1212_CONF_PXS_SLP_100MS 0x30 +#define JSA1212_CONF_PXS_SLP_200MS 0x20 +#define JSA1212_CONF_PXS_SLP_400MS 0x10 +#define JSA1212_CONF_PXS_SLP_800MS 0x00 + +/* JSA1212 INT REG bits */ +#define JSA1212_INT_CTRL_MASK 0x01 +#define JSA1212_INT_CTRL_EITHER 0x00 +#define JSA1212_INT_CTRL_BOTH 0x01 +#define JSA1212_INT_ALS_PRST_MASK 0x06 +#define JSA1212_INT_ALS_PRST_1CONV 0x00 +#define JSA1212_INT_ALS_PRST_4CONV 0x02 +#define JSA1212_INT_ALS_PRST_8CONV 0x04 +#define JSA1212_INT_ALS_PRST_16CONV 0x06 +#define JSA1212_INT_ALS_FLAG_MASK 0x08 +#define JSA1212_INT_ALS_FLAG_CLR 0x00 +#define JSA1212_INT_PXS_PRST_MASK 0x60 +#define JSA1212_INT_PXS_PRST_1CONV 0x00 +#define JSA1212_INT_PXS_PRST_4CONV 0x20 +#define JSA1212_INT_PXS_PRST_8CONV 0x40 +#define JSA1212_INT_PXS_PRST_16CONV 0x60 +#define JSA1212_INT_PXS_FLAG_MASK 0x80 +#define JSA1212_INT_PXS_FLAG_CLR 0x00 + +/* JSA1212 ALS RNG REG bits */ +#define JSA1212_ALS_RNG_0_2048 0x00 +#define JSA1212_ALS_RNG_0_1024 0x01 +#define JSA1212_ALS_RNG_0_512 0x02 +#define JSA1212_ALS_RNG_0_256 0x03 +#define JSA1212_ALS_RNG_0_128 0x04 + +/* JSA1212 INT threshold range */ +#define JSA1212_ALS_TH_MIN 0x0000 +#define JSA1212_ALS_TH_MAX 0x0FFF +#define JSA1212_PXS_TH_MIN 0x00 +#define JSA1212_PXS_TH_MAX 0xFF + +#define JSA1212_ALS_DELAY_MS 200 +#define JSA1212_PXS_DELAY_MS 100 + +#define JSA1212_DRIVER_NAME "jsa1212" + +enum jsa1212_op_mode { + JSA1212_OPMODE_ALS_EN, + JSA1212_OPMODE_PXS_EN, +}; + +struct jsa1212_data { + struct i2c_client *client; + struct mutex lock; + u8 als_rng_idx; + bool als_en; /* ALS enable status */ + bool pxs_en; /* proximity enable status */ + struct regmap *regmap; +}; + +/* ALS range idx to val mapping */ +static const int jsa1212_als_range_val[] = {2048, 1024, 512, 256, 128, + 128, 128, 128}; + +/* Enables or disables ALS function based on status */ +static int jsa1212_als_enable(struct jsa1212_data *data, u8 status) +{ + int ret; + + ret = regmap_update_bits(data->regmap, JSA1212_CONF_REG, + JSA1212_CONF_ALS_MASK, + status); + if (ret < 0) + return ret; + + data->als_en = !!status; + + return 0; +} + +/* Enables or disables PXS function based on status */ +static int jsa1212_pxs_enable(struct jsa1212_data *data, u8 status) +{ + int ret; + + ret = regmap_update_bits(data->regmap, JSA1212_CONF_REG, + JSA1212_CONF_PXS_MASK, + status); + if (ret < 0) + return ret; + + data->pxs_en = !!status; + + return 0; +} + +static int jsa1212_read_als_data(struct jsa1212_data *data, + unsigned int *val) +{ + int ret; + __le16 als_data; + + ret = jsa1212_als_enable(data, JSA1212_CONF_ALS_ENABLE); + if (ret < 0) + return ret; + + /* Delay for data output */ + msleep(JSA1212_ALS_DELAY_MS); + + /* Read 12 bit data */ + ret = regmap_bulk_read(data->regmap, JSA1212_ALS_DT1_REG, &als_data, 2); + if (ret < 0) { + dev_err(&data->client->dev, "als data read err\n"); + goto als_data_read_err; + } + + *val = le16_to_cpu(als_data); + +als_data_read_err: + return jsa1212_als_enable(data, JSA1212_CONF_ALS_DISABLE); +} + +static int jsa1212_read_pxs_data(struct jsa1212_data *data, + unsigned int *val) +{ + int ret; + unsigned int pxs_data; + + ret = jsa1212_pxs_enable(data, JSA1212_CONF_PXS_ENABLE); + if (ret < 0) + return ret; + + /* Delay for data output */ + msleep(JSA1212_PXS_DELAY_MS); + + /* Read out all data */ + ret = regmap_read(data->regmap, JSA1212_PXS_DATA_REG, &pxs_data); + if (ret < 0) { + dev_err(&data->client->dev, "pxs data read err\n"); + goto pxs_data_read_err; + } + + *val = pxs_data & JSA1212_PXS_DATA_MASK; + +pxs_data_read_err: + return jsa1212_pxs_enable(data, JSA1212_CONF_PXS_DISABLE); +} + +static int jsa1212_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct jsa1212_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->lock); + switch (chan->type) { + case IIO_LIGHT: + ret = jsa1212_read_als_data(data, val); + break; + case IIO_PROXIMITY: + ret = jsa1212_read_pxs_data(data, val); + break; + default: + ret = -EINVAL; + break; + } + mutex_unlock(&data->lock); + return ret < 0 ? ret : IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_LIGHT: + *val = jsa1212_als_range_val[data->als_rng_idx]; + *val2 = BIT(12); /* Max 12 bit value */ + return IIO_VAL_FRACTIONAL; + default: + break; + } + break; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_chan_spec jsa1212_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; + +static const struct iio_info jsa1212_info = { + .read_raw = &jsa1212_read_raw, +}; + +static int jsa1212_chip_init(struct jsa1212_data *data) +{ + int ret; + + ret = regmap_write(data->regmap, JSA1212_CONF_REG, + (JSA1212_CONF_PXS_SLP_50MS | + JSA1212_CONF_IRDR_200MA)); + if (ret < 0) + return ret; + + ret = regmap_write(data->regmap, JSA1212_INT_REG, + JSA1212_INT_ALS_PRST_4CONV); + if (ret < 0) + return ret; + + data->als_rng_idx = JSA1212_ALS_RNG_0_2048; + + return 0; +} + +static bool jsa1212_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case JSA1212_PXS_DATA_REG: + case JSA1212_ALS_DT1_REG: + case JSA1212_ALS_DT2_REG: + case JSA1212_INT_REG: + return true; + default: + return false; + } +} + +static const struct regmap_config jsa1212_regmap_config = { + .name = "jsa1212_regmap", + .reg_bits = 8, + .val_bits = 8, + .max_register = JSA1212_MAX_REG, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = jsa1212_is_volatile_reg, +}; + +static int jsa1212_probe(struct i2c_client *client) +{ + struct jsa1212_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &jsa1212_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Regmap initialization failed.\n"); + return PTR_ERR(regmap); + } + + data = iio_priv(indio_dev); + + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + + mutex_init(&data->lock); + + ret = jsa1212_chip_init(data); + if (ret < 0) + return ret; + + indio_dev->channels = jsa1212_channels; + indio_dev->num_channels = ARRAY_SIZE(jsa1212_channels); + indio_dev->name = JSA1212_DRIVER_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + indio_dev->info = &jsa1212_info; + + ret = iio_device_register(indio_dev); + if (ret < 0) + dev_err(&client->dev, "%s: register device failed\n", __func__); + + return ret; +} + + /* power off the device */ +static int jsa1212_power_off(struct jsa1212_data *data) +{ + int ret; + + mutex_lock(&data->lock); + + ret = regmap_update_bits(data->regmap, JSA1212_CONF_REG, + JSA1212_CONF_ALS_MASK | + JSA1212_CONF_PXS_MASK, + JSA1212_CONF_ALS_DISABLE | + JSA1212_CONF_PXS_DISABLE); + + if (ret < 0) + dev_err(&data->client->dev, "power off cmd failed\n"); + + mutex_unlock(&data->lock); + + return ret; +} + +static void jsa1212_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct jsa1212_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + jsa1212_power_off(data); +} + +static int jsa1212_suspend(struct device *dev) +{ + struct jsa1212_data *data; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return jsa1212_power_off(data); +} + +static int jsa1212_resume(struct device *dev) +{ + int ret = 0; + struct jsa1212_data *data; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + mutex_lock(&data->lock); + + if (data->als_en) { + ret = jsa1212_als_enable(data, JSA1212_CONF_ALS_ENABLE); + if (ret < 0) { + dev_err(dev, "als resume failed\n"); + goto unlock_and_ret; + } + } + + if (data->pxs_en) { + ret = jsa1212_pxs_enable(data, JSA1212_CONF_PXS_ENABLE); + if (ret < 0) + dev_err(dev, "pxs resume failed\n"); + } + +unlock_and_ret: + mutex_unlock(&data->lock); + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(jsa1212_pm_ops, jsa1212_suspend, + jsa1212_resume); + +static const struct acpi_device_id jsa1212_acpi_match[] = { + {"JSA1212", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, jsa1212_acpi_match); + +static const struct i2c_device_id jsa1212_id[] = { + { .name = JSA1212_DRIVER_NAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, jsa1212_id); + +static struct i2c_driver jsa1212_driver = { + .driver = { + .name = JSA1212_DRIVER_NAME, + .pm = pm_sleep_ptr(&jsa1212_pm_ops), + .acpi_match_table = jsa1212_acpi_match, + }, + .probe = jsa1212_probe, + .remove = jsa1212_remove, + .id_table = jsa1212_id, +}; +module_i2c_driver(jsa1212_driver); + +MODULE_AUTHOR("Sathya Kuppuswamy <sathyanarayanan.kuppuswamy@linux.intel.com>"); +MODULE_DESCRIPTION("JSA1212 proximity/ambient light sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c new file mode 100644 index 000000000..99f0b9030 --- /dev/null +++ b/drivers/iio/light/lm3533-als.c @@ -0,0 +1,922 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * lm3533-als.c -- LM3533 Ambient Light Sensor driver + * + * Copyright (C) 2011-2012 Texas Instruments + * + * Author: Johan Hovold <jhovold@gmail.com> + */ + +#include <linux/atomic.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/mfd/core.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include <linux/mfd/lm3533.h> + + +#define LM3533_ALS_RESISTOR_MIN 1 +#define LM3533_ALS_RESISTOR_MAX 127 +#define LM3533_ALS_CHANNEL_CURRENT_MAX 2 +#define LM3533_ALS_THRESH_MAX 3 +#define LM3533_ALS_ZONE_MAX 4 + +#define LM3533_REG_ALS_RESISTOR_SELECT 0x30 +#define LM3533_REG_ALS_CONF 0x31 +#define LM3533_REG_ALS_ZONE_INFO 0x34 +#define LM3533_REG_ALS_READ_ADC_RAW 0x37 +#define LM3533_REG_ALS_READ_ADC_AVERAGE 0x38 +#define LM3533_REG_ALS_BOUNDARY_BASE 0x50 +#define LM3533_REG_ALS_TARGET_BASE 0x60 + +#define LM3533_ALS_ENABLE_MASK 0x01 +#define LM3533_ALS_INPUT_MODE_MASK 0x02 +#define LM3533_ALS_INT_ENABLE_MASK 0x01 + +#define LM3533_ALS_ZONE_SHIFT 2 +#define LM3533_ALS_ZONE_MASK 0x1c + +#define LM3533_ALS_FLAG_INT_ENABLED 1 + + +struct lm3533_als { + struct lm3533 *lm3533; + struct platform_device *pdev; + + unsigned long flags; + int irq; + + atomic_t zone; + struct mutex thresh_mutex; +}; + + +static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average, + int *adc) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 reg; + u8 val; + int ret; + + if (average) + reg = LM3533_REG_ALS_READ_ADC_AVERAGE; + else + reg = LM3533_REG_ALS_READ_ADC_RAW; + + ret = lm3533_read(als->lm3533, reg, &val); + if (ret) { + dev_err(&indio_dev->dev, "failed to read adc\n"); + return ret; + } + + *adc = val; + + return 0; +} + +static int _lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 val; + int ret; + + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val); + if (ret) { + dev_err(&indio_dev->dev, "failed to read zone\n"); + return ret; + } + + val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT; + *zone = min_t(u8, val, LM3533_ALS_ZONE_MAX); + + return 0; +} + +static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone) +{ + struct lm3533_als *als = iio_priv(indio_dev); + int ret; + + if (test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags)) { + *zone = atomic_read(&als->zone); + } else { + ret = _lm3533_als_get_zone(indio_dev, zone); + if (ret) + return ret; + } + + return 0; +} + +/* + * channel output channel 0..2 + * zone zone 0..4 + */ +static inline u8 lm3533_als_get_target_reg(unsigned channel, unsigned zone) +{ + return LM3533_REG_ALS_TARGET_BASE + 5 * channel + zone; +} + +static int lm3533_als_get_target(struct iio_dev *indio_dev, unsigned channel, + unsigned zone, u8 *val) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 reg; + int ret; + + if (channel > LM3533_ALS_CHANNEL_CURRENT_MAX) + return -EINVAL; + + if (zone > LM3533_ALS_ZONE_MAX) + return -EINVAL; + + reg = lm3533_als_get_target_reg(channel, zone); + ret = lm3533_read(als->lm3533, reg, val); + if (ret) + dev_err(&indio_dev->dev, "failed to get target current\n"); + + return ret; +} + +static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned channel, + unsigned zone, u8 val) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 reg; + int ret; + + if (channel > LM3533_ALS_CHANNEL_CURRENT_MAX) + return -EINVAL; + + if (zone > LM3533_ALS_ZONE_MAX) + return -EINVAL; + + reg = lm3533_als_get_target_reg(channel, zone); + ret = lm3533_write(als->lm3533, reg, val); + if (ret) + dev_err(&indio_dev->dev, "failed to set target current\n"); + + return ret; +} + +static int lm3533_als_get_current(struct iio_dev *indio_dev, unsigned channel, + int *val) +{ + u8 zone; + u8 target; + int ret; + + ret = lm3533_als_get_zone(indio_dev, &zone); + if (ret) + return ret; + + ret = lm3533_als_get_target(indio_dev, channel, zone, &target); + if (ret) + return ret; + + *val = target; + + return 0; +} + +static int lm3533_als_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + ret = lm3533_als_get_adc(indio_dev, false, val); + break; + case IIO_CURRENT: + ret = lm3533_als_get_current(indio_dev, chan->channel, + val); + break; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_AVERAGE_RAW: + ret = lm3533_als_get_adc(indio_dev, true, val); + break; + default: + return -EINVAL; + } + + if (ret) + return ret; + + return IIO_VAL_INT; +} + +#define CHANNEL_CURRENT(_channel) \ + { \ + .type = IIO_CURRENT, \ + .channel = _channel, \ + .indexed = true, \ + .output = true, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + } + +static const struct iio_chan_spec lm3533_als_channels[] = { + { + .type = IIO_LIGHT, + .channel = 0, + .indexed = true, + .info_mask_separate = BIT(IIO_CHAN_INFO_AVERAGE_RAW) | + BIT(IIO_CHAN_INFO_RAW), + }, + CHANNEL_CURRENT(0), + CHANNEL_CURRENT(1), + CHANNEL_CURRENT(2), +}; + +static irqreturn_t lm3533_als_isr(int irq, void *dev_id) +{ + + struct iio_dev *indio_dev = dev_id; + struct lm3533_als *als = iio_priv(indio_dev); + u8 zone; + int ret; + + /* Clear interrupt by reading the ALS zone register. */ + ret = _lm3533_als_get_zone(indio_dev, &zone); + if (ret) + goto out; + + atomic_set(&als->zone, zone); + + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); +out: + return IRQ_HANDLED; +} + +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 mask = LM3533_ALS_INT_ENABLE_MASK; + u8 val; + int ret; + + if (enable) + val = mask; + else + val = 0; + + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask); + if (ret) { + dev_err(&indio_dev->dev, "failed to set int mode %d\n", + enable); + return ret; + } + + return 0; +} + +static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 mask = LM3533_ALS_INT_ENABLE_MASK; + u8 val; + int ret; + + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val); + if (ret) { + dev_err(&indio_dev->dev, "failed to get int mode\n"); + return ret; + } + + *enable = !!(val & mask); + + return 0; +} + +static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising) +{ + u8 offset = !raising; + + return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset; +} + +static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr, + bool raising, u8 *val) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 reg; + int ret; + + if (nr > LM3533_ALS_THRESH_MAX) + return -EINVAL; + + reg = lm3533_als_get_threshold_reg(nr, raising); + ret = lm3533_read(als->lm3533, reg, val); + if (ret) + dev_err(&indio_dev->dev, "failed to get threshold\n"); + + return ret; +} + +static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr, + bool raising, u8 val) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 val2; + u8 reg, reg2; + int ret; + + if (nr > LM3533_ALS_THRESH_MAX) + return -EINVAL; + + reg = lm3533_als_get_threshold_reg(nr, raising); + reg2 = lm3533_als_get_threshold_reg(nr, !raising); + + mutex_lock(&als->thresh_mutex); + ret = lm3533_read(als->lm3533, reg2, &val2); + if (ret) { + dev_err(&indio_dev->dev, "failed to get threshold\n"); + goto out; + } + /* + * This device does not allow negative hysteresis (in fact, it uses + * whichever value is smaller as the lower bound) so we need to make + * sure that thresh_falling <= thresh_raising. + */ + if ((raising && (val < val2)) || (!raising && (val > val2))) { + ret = -EINVAL; + goto out; + } + + ret = lm3533_write(als->lm3533, reg, val); + if (ret) { + dev_err(&indio_dev->dev, "failed to set threshold\n"); + goto out; + } +out: + mutex_unlock(&als->thresh_mutex); + + return ret; +} + +static int lm3533_als_get_hysteresis(struct iio_dev *indio_dev, unsigned nr, + u8 *val) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 falling; + u8 raising; + int ret; + + if (nr > LM3533_ALS_THRESH_MAX) + return -EINVAL; + + mutex_lock(&als->thresh_mutex); + ret = lm3533_als_get_threshold(indio_dev, nr, false, &falling); + if (ret) + goto out; + ret = lm3533_als_get_threshold(indio_dev, nr, true, &raising); + if (ret) + goto out; + + *val = raising - falling; +out: + mutex_unlock(&als->thresh_mutex); + + return ret; +} + +static ssize_t show_thresh_either_en(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct lm3533_als *als = iio_priv(indio_dev); + int enable; + int ret; + + if (als->irq) { + ret = lm3533_als_get_int_mode(indio_dev, &enable); + if (ret) + return ret; + } else { + enable = 0; + } + + return sysfs_emit(buf, "%u\n", enable); +} + +static ssize_t store_thresh_either_en(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct lm3533_als *als = iio_priv(indio_dev); + unsigned long enable; + bool int_enabled; + u8 zone; + int ret; + + if (!als->irq) + return -EBUSY; + + if (kstrtoul(buf, 0, &enable)) + return -EINVAL; + + int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags); + + if (enable && !int_enabled) { + ret = lm3533_als_get_zone(indio_dev, &zone); + if (ret) + return ret; + + atomic_set(&als->zone, zone); + + set_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags); + } + + ret = lm3533_als_set_int_mode(indio_dev, enable); + if (ret) { + if (!int_enabled) + clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags); + + return ret; + } + + if (!enable) + clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags); + + return len; +} + +static ssize_t show_zone(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + u8 zone; + int ret; + + ret = lm3533_als_get_zone(indio_dev, &zone); + if (ret) + return ret; + + return sysfs_emit(buf, "%u\n", zone); +} + +enum lm3533_als_attribute_type { + LM3533_ATTR_TYPE_HYSTERESIS, + LM3533_ATTR_TYPE_TARGET, + LM3533_ATTR_TYPE_THRESH_FALLING, + LM3533_ATTR_TYPE_THRESH_RAISING, +}; + +struct lm3533_als_attribute { + struct device_attribute dev_attr; + enum lm3533_als_attribute_type type; + u8 val1; + u8 val2; +}; + +static inline struct lm3533_als_attribute * +to_lm3533_als_attr(struct device_attribute *attr) +{ + return container_of(attr, struct lm3533_als_attribute, dev_attr); +} + +static ssize_t show_als_attr(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr); + u8 val; + int ret; + + switch (als_attr->type) { + case LM3533_ATTR_TYPE_HYSTERESIS: + ret = lm3533_als_get_hysteresis(indio_dev, als_attr->val1, + &val); + break; + case LM3533_ATTR_TYPE_TARGET: + ret = lm3533_als_get_target(indio_dev, als_attr->val1, + als_attr->val2, &val); + break; + case LM3533_ATTR_TYPE_THRESH_FALLING: + ret = lm3533_als_get_threshold(indio_dev, als_attr->val1, + false, &val); + break; + case LM3533_ATTR_TYPE_THRESH_RAISING: + ret = lm3533_als_get_threshold(indio_dev, als_attr->val1, + true, &val); + break; + default: + ret = -ENXIO; + } + + if (ret) + return ret; + + return sysfs_emit(buf, "%u\n", val); +} + +static ssize_t store_als_attr(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr); + u8 val; + int ret; + + if (kstrtou8(buf, 0, &val)) + return -EINVAL; + + switch (als_attr->type) { + case LM3533_ATTR_TYPE_TARGET: + ret = lm3533_als_set_target(indio_dev, als_attr->val1, + als_attr->val2, val); + break; + case LM3533_ATTR_TYPE_THRESH_FALLING: + ret = lm3533_als_set_threshold(indio_dev, als_attr->val1, + false, val); + break; + case LM3533_ATTR_TYPE_THRESH_RAISING: + ret = lm3533_als_set_threshold(indio_dev, als_attr->val1, + true, val); + break; + default: + ret = -ENXIO; + } + + if (ret) + return ret; + + return len; +} + +#define ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \ + { .dev_attr = __ATTR(_name, _mode, _show, _store), \ + .type = _type, \ + .val1 = _val1, \ + .val2 = _val2 } + +#define LM3533_ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \ + struct lm3533_als_attribute lm3533_als_attr_##_name = \ + ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) + +#define ALS_TARGET_ATTR_RW(_channel, _zone) \ + LM3533_ALS_ATTR(out_current##_channel##_current##_zone##_raw, \ + S_IRUGO | S_IWUSR, \ + show_als_attr, store_als_attr, \ + LM3533_ATTR_TYPE_TARGET, _channel, _zone) +/* + * ALS output current values (ALS mapper targets) + * + * out_current[0-2]_current[0-4]_raw 0-255 + */ +static ALS_TARGET_ATTR_RW(0, 0); +static ALS_TARGET_ATTR_RW(0, 1); +static ALS_TARGET_ATTR_RW(0, 2); +static ALS_TARGET_ATTR_RW(0, 3); +static ALS_TARGET_ATTR_RW(0, 4); + +static ALS_TARGET_ATTR_RW(1, 0); +static ALS_TARGET_ATTR_RW(1, 1); +static ALS_TARGET_ATTR_RW(1, 2); +static ALS_TARGET_ATTR_RW(1, 3); +static ALS_TARGET_ATTR_RW(1, 4); + +static ALS_TARGET_ATTR_RW(2, 0); +static ALS_TARGET_ATTR_RW(2, 1); +static ALS_TARGET_ATTR_RW(2, 2); +static ALS_TARGET_ATTR_RW(2, 3); +static ALS_TARGET_ATTR_RW(2, 4); + +#define ALS_THRESH_FALLING_ATTR_RW(_nr) \ + LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_falling_value, \ + S_IRUGO | S_IWUSR, \ + show_als_attr, store_als_attr, \ + LM3533_ATTR_TYPE_THRESH_FALLING, _nr, 0) + +#define ALS_THRESH_RAISING_ATTR_RW(_nr) \ + LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_raising_value, \ + S_IRUGO | S_IWUSR, \ + show_als_attr, store_als_attr, \ + LM3533_ATTR_TYPE_THRESH_RAISING, _nr, 0) +/* + * ALS Zone thresholds (boundaries) + * + * in_illuminance0_thresh[0-3]_falling_value 0-255 + * in_illuminance0_thresh[0-3]_raising_value 0-255 + */ +static ALS_THRESH_FALLING_ATTR_RW(0); +static ALS_THRESH_FALLING_ATTR_RW(1); +static ALS_THRESH_FALLING_ATTR_RW(2); +static ALS_THRESH_FALLING_ATTR_RW(3); + +static ALS_THRESH_RAISING_ATTR_RW(0); +static ALS_THRESH_RAISING_ATTR_RW(1); +static ALS_THRESH_RAISING_ATTR_RW(2); +static ALS_THRESH_RAISING_ATTR_RW(3); + +#define ALS_HYSTERESIS_ATTR_RO(_nr) \ + LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_hysteresis, \ + S_IRUGO, show_als_attr, NULL, \ + LM3533_ATTR_TYPE_HYSTERESIS, _nr, 0) +/* + * ALS Zone threshold hysteresis + * + * threshY_hysteresis = threshY_raising - threshY_falling + * + * in_illuminance0_thresh[0-3]_hysteresis 0-255 + * in_illuminance0_thresh[0-3]_hysteresis 0-255 + */ +static ALS_HYSTERESIS_ATTR_RO(0); +static ALS_HYSTERESIS_ATTR_RO(1); +static ALS_HYSTERESIS_ATTR_RO(2); +static ALS_HYSTERESIS_ATTR_RO(3); + +#define ILLUMINANCE_ATTR_RO(_name) \ + DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL) +#define ILLUMINANCE_ATTR_RW(_name) \ + DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR, \ + show_##_name, store_##_name) +/* + * ALS Zone threshold-event enable + * + * in_illuminance0_thresh_either_en 0,1 + */ +static ILLUMINANCE_ATTR_RW(thresh_either_en); + +/* + * ALS Current Zone + * + * in_illuminance0_zone 0-4 + */ +static ILLUMINANCE_ATTR_RO(zone); + +static struct attribute *lm3533_als_event_attributes[] = { + &dev_attr_in_illuminance0_thresh_either_en.attr, + &lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr, + NULL +}; + +static const struct attribute_group lm3533_als_event_attribute_group = { + .attrs = lm3533_als_event_attributes +}; + +static struct attribute *lm3533_als_attributes[] = { + &dev_attr_in_illuminance0_zone.attr, + &lm3533_als_attr_out_current0_current0_raw.dev_attr.attr, + &lm3533_als_attr_out_current0_current1_raw.dev_attr.attr, + &lm3533_als_attr_out_current0_current2_raw.dev_attr.attr, + &lm3533_als_attr_out_current0_current3_raw.dev_attr.attr, + &lm3533_als_attr_out_current0_current4_raw.dev_attr.attr, + &lm3533_als_attr_out_current1_current0_raw.dev_attr.attr, + &lm3533_als_attr_out_current1_current1_raw.dev_attr.attr, + &lm3533_als_attr_out_current1_current2_raw.dev_attr.attr, + &lm3533_als_attr_out_current1_current3_raw.dev_attr.attr, + &lm3533_als_attr_out_current1_current4_raw.dev_attr.attr, + &lm3533_als_attr_out_current2_current0_raw.dev_attr.attr, + &lm3533_als_attr_out_current2_current1_raw.dev_attr.attr, + &lm3533_als_attr_out_current2_current2_raw.dev_attr.attr, + &lm3533_als_attr_out_current2_current3_raw.dev_attr.attr, + &lm3533_als_attr_out_current2_current4_raw.dev_attr.attr, + NULL +}; + +static const struct attribute_group lm3533_als_attribute_group = { + .attrs = lm3533_als_attributes +}; + +static int lm3533_als_set_input_mode(struct lm3533_als *als, bool pwm_mode) +{ + u8 mask = LM3533_ALS_INPUT_MODE_MASK; + u8 val; + int ret; + + if (pwm_mode) + val = mask; /* pwm input */ + else + val = 0; /* analog input */ + + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, val, mask); + if (ret) { + dev_err(&als->pdev->dev, "failed to set input mode %d\n", + pwm_mode); + return ret; + } + + return 0; +} + +static int lm3533_als_set_resistor(struct lm3533_als *als, u8 val) +{ + int ret; + + if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX) { + dev_err(&als->pdev->dev, "invalid resistor value\n"); + return -EINVAL; + } + + ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val); + if (ret) { + dev_err(&als->pdev->dev, "failed to set resistor\n"); + return ret; + } + + return 0; +} + +static int lm3533_als_setup(struct lm3533_als *als, + const struct lm3533_als_platform_data *pdata) +{ + int ret; + + ret = lm3533_als_set_input_mode(als, pdata->pwm_mode); + if (ret) + return ret; + + /* ALS input is always high impedance in PWM-mode. */ + if (!pdata->pwm_mode) { + ret = lm3533_als_set_resistor(als, pdata->r_select); + if (ret) + return ret; + } + + return 0; +} + +static int lm3533_als_setup_irq(struct lm3533_als *als, void *dev) +{ + u8 mask = LM3533_ALS_INT_ENABLE_MASK; + int ret; + + /* Make sure interrupts are disabled. */ + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, 0, mask); + if (ret) { + dev_err(&als->pdev->dev, "failed to disable interrupts\n"); + return ret; + } + + ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + dev_name(&als->pdev->dev), dev); + if (ret) { + dev_err(&als->pdev->dev, "failed to request irq %d\n", + als->irq); + return ret; + } + + return 0; +} + +static int lm3533_als_enable(struct lm3533_als *als) +{ + u8 mask = LM3533_ALS_ENABLE_MASK; + int ret; + + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask); + if (ret) + dev_err(&als->pdev->dev, "failed to enable ALS\n"); + + return ret; +} + +static int lm3533_als_disable(struct lm3533_als *als) +{ + u8 mask = LM3533_ALS_ENABLE_MASK; + int ret; + + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask); + if (ret) + dev_err(&als->pdev->dev, "failed to disable ALS\n"); + + return ret; +} + +static const struct iio_info lm3533_als_info = { + .attrs = &lm3533_als_attribute_group, + .event_attrs = &lm3533_als_event_attribute_group, + .read_raw = &lm3533_als_read_raw, +}; + +static int lm3533_als_probe(struct platform_device *pdev) +{ + const struct lm3533_als_platform_data *pdata; + struct lm3533 *lm3533; + struct lm3533_als *als; + struct iio_dev *indio_dev; + int ret; + + lm3533 = dev_get_drvdata(pdev->dev.parent); + if (!lm3533) + return -EINVAL; + + pdata = dev_get_platdata(&pdev->dev); + if (!pdata) { + dev_err(&pdev->dev, "no platform data\n"); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*als)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->info = &lm3533_als_info; + indio_dev->channels = lm3533_als_channels; + indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels); + indio_dev->name = dev_name(&pdev->dev); + iio_device_set_parent(indio_dev, pdev->dev.parent); + indio_dev->modes = INDIO_DIRECT_MODE; + + als = iio_priv(indio_dev); + als->lm3533 = lm3533; + als->pdev = pdev; + als->irq = lm3533->irq; + atomic_set(&als->zone, 0); + mutex_init(&als->thresh_mutex); + + platform_set_drvdata(pdev, indio_dev); + + if (als->irq) { + ret = lm3533_als_setup_irq(als, indio_dev); + if (ret) + return ret; + } + + ret = lm3533_als_setup(als, pdata); + if (ret) + goto err_free_irq; + + ret = lm3533_als_enable(als); + if (ret) + goto err_free_irq; + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "failed to register ALS\n"); + goto err_disable; + } + + return 0; + +err_disable: + lm3533_als_disable(als); +err_free_irq: + if (als->irq) + free_irq(als->irq, indio_dev); + + return ret; +} + +static void lm3533_als_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct lm3533_als *als = iio_priv(indio_dev); + + lm3533_als_set_int_mode(indio_dev, false); + iio_device_unregister(indio_dev); + lm3533_als_disable(als); + if (als->irq) + free_irq(als->irq, indio_dev); +} + +static struct platform_driver lm3533_als_driver = { + .driver = { + .name = "lm3533-als", + }, + .probe = lm3533_als_probe, + .remove = lm3533_als_remove, +}; +module_platform_driver(lm3533_als_driver); + +MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>"); +MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:lm3533-als"); diff --git a/drivers/iio/light/ltr390.c b/drivers/iio/light/ltr390.c new file mode 100644 index 000000000..bc031f2c3 --- /dev/null +++ b/drivers/iio/light/ltr390.c @@ -0,0 +1,915 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IIO driver for Lite-On LTR390 ALS and UV sensor + * (7-bit I2C slave address 0x53) + * + * Based on the work of: + * Shreeya Patel and Shi Zhigang (LTRF216 Driver) + * + * Copyright (C) 2023 Anshul Dalal <anshulusr@gmail.com> + * + * Datasheet: + * https://optoelectronics.liteon.com/upload/download/DS86-2015-0004/LTR-390UV_Final_%20DS_V1%201.pdf + * + * TODO: + * - Support for configurable gain and resolution + * - Sensor suspend/resume support + * - Add support for reading the ALS + * - Interrupt support + */ + +#include <linux/bitfield.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/math.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> + +#include <linux/iio/iio.h> +#include <linux/iio/events.h> + +#include <linux/unaligned.h> + +#define LTR390_MAIN_CTRL 0x00 +#define LTR390_ALS_UVS_MEAS_RATE 0x04 +#define LTR390_ALS_UVS_GAIN 0x05 +#define LTR390_PART_ID 0x06 +#define LTR390_MAIN_STATUS 0x07 + +#define LTR390_ALS_DATA 0x0D +#define LTR390_ALS_DATA_BYTE(n) (LTR390_ALS_DATA + (n)) + +#define LTR390_UVS_DATA 0x10 +#define LTR390_UVS_DATA_BYTE(n) (LTR390_UVS_DATA + (n)) + +#define LTR390_INT_CFG 0x19 +#define LTR390_INT_PST 0x1A + +#define LTR390_THRESH_UP 0x21 +#define LTR390_THRESH_UP_BYTE(n) (LTR390_THRESH_UP + (n)) + +#define LTR390_THRESH_LOW 0x24 +#define LTR390_THRESH_LOW_BYTE(n) (LTR390_THRESH_LOW + (n)) + +#define LTR390_PART_NUMBER_ID 0xb +#define LTR390_ALS_UVS_GAIN_MASK GENMASK(2, 0) +#define LTR390_ALS_UVS_MEAS_RATE_MASK GENMASK(2, 0) +#define LTR390_ALS_UVS_INT_TIME_MASK GENMASK(6, 4) +#define LTR390_ALS_UVS_INT_TIME(x) FIELD_PREP(LTR390_ALS_UVS_INT_TIME_MASK, (x)) +#define LTR390_INT_PST_MASK GENMASK(7, 4) +#define LTR390_INT_PST_VAL(x) FIELD_PREP(LTR390_INT_PST_MASK, (x)) + +#define LTR390_SW_RESET BIT(4) +#define LTR390_UVS_MODE BIT(3) +#define LTR390_SENSOR_ENABLE BIT(1) +#define LTR390_LS_INT_EN BIT(2) +#define LTR390_LS_INT_SEL_UVS BIT(5) + +#define LTR390_FRACTIONAL_PRECISION 100 + +/* + * At 20-bit resolution (integration time: 400ms) and 18x gain, 2300 counts of + * the sensor are equal to 1 UV Index [Datasheet Page#8]. + * + * For the default resolution of 18-bit (integration time: 100ms) and default + * gain of 3x, the counts/uvi are calculated as follows: + * 2300 / ((3/18) * (100/400)) = 95.83 + */ +#define LTR390_COUNTS_PER_UVI 96 + +/* + * Window Factor is needed when the device is under Window glass with coated + * tinted ink. This is to compensate for the light loss due to the lower + * transmission rate of the window glass and helps * in calculating lux. + */ +#define LTR390_WINDOW_FACTOR 1 + +enum ltr390_mode { + LTR390_SET_ALS_MODE, + LTR390_SET_UVS_MODE, +}; + +enum ltr390_meas_rate { + LTR390_GET_FREQ, + LTR390_GET_PERIOD, +}; + +struct ltr390_data { + struct regmap *regmap; + struct i2c_client *client; + /* Protects device from simultaneous reads */ + struct mutex lock; + enum ltr390_mode mode; + int gain; + int int_time_us; + bool irq_enabled; +}; + +static const struct regmap_range ltr390_readable_reg_ranges[] = { + regmap_reg_range(LTR390_MAIN_CTRL, LTR390_MAIN_CTRL), + regmap_reg_range(LTR390_ALS_UVS_MEAS_RATE, LTR390_MAIN_STATUS), + regmap_reg_range(LTR390_ALS_DATA_BYTE(0), LTR390_UVS_DATA_BYTE(2)), + regmap_reg_range(LTR390_INT_CFG, LTR390_INT_PST), + regmap_reg_range(LTR390_THRESH_UP_BYTE(0), LTR390_THRESH_LOW_BYTE(2)), +}; + +static const struct regmap_access_table ltr390_readable_reg_table = { + .yes_ranges = ltr390_readable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(ltr390_readable_reg_ranges), +}; + +static const struct regmap_range ltr390_writeable_reg_ranges[] = { + regmap_reg_range(LTR390_MAIN_CTRL, LTR390_MAIN_CTRL), + regmap_reg_range(LTR390_ALS_UVS_MEAS_RATE, LTR390_ALS_UVS_GAIN), + regmap_reg_range(LTR390_INT_CFG, LTR390_INT_PST), + regmap_reg_range(LTR390_THRESH_UP_BYTE(0), LTR390_THRESH_LOW_BYTE(2)), +}; + +static const struct regmap_access_table ltr390_writeable_reg_table = { + .yes_ranges = ltr390_writeable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(ltr390_writeable_reg_ranges), +}; + +static const struct regmap_config ltr390_regmap_config = { + .name = "ltr390", + .reg_bits = 8, + .reg_stride = 1, + .val_bits = 8, + .max_register = LTR390_THRESH_LOW_BYTE(2), + .rd_table = <r390_readable_reg_table, + .wr_table = <r390_writeable_reg_table, +}; + +/* Sampling frequency is in mili Hz and mili Seconds */ +static const int ltr390_samp_freq_table[][2] = { + [0] = { 40000, 25 }, + [1] = { 20000, 50 }, + [2] = { 10000, 100 }, + [3] = { 5000, 200 }, + [4] = { 2000, 500 }, + [5] = { 1000, 1000 }, + [6] = { 500, 2000 }, + [7] = { 500, 2000 }, +}; + +static int ltr390_register_read(struct ltr390_data *data, u8 register_address) +{ + struct device *dev = &data->client->dev; + int ret; + u8 receive_buffer[3]; + + ret = regmap_bulk_read(data->regmap, register_address, receive_buffer, + sizeof(receive_buffer)); + if (ret) { + dev_err(dev, "failed to read measurement data"); + return ret; + } + + return get_unaligned_le24(receive_buffer); +} + +static int ltr390_set_mode(struct ltr390_data *data, enum ltr390_mode mode) +{ + int ret; + + if (data->mode == mode) + return 0; + + switch (mode) { + case LTR390_SET_ALS_MODE: + ret = regmap_clear_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_UVS_MODE); + break; + + case LTR390_SET_UVS_MODE: + ret = regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_UVS_MODE); + break; + } + + if (ret) + return ret; + + data->mode = mode; + return 0; +} + +static int ltr390_counts_per_uvi(struct ltr390_data *data) +{ + const int orig_gain = 18; + const int orig_int_time = 400; + + return DIV_ROUND_CLOSEST(23 * data->gain * data->int_time_us, 10 * orig_gain * orig_int_time); +} + +static int ltr390_get_samp_freq_or_period(struct ltr390_data *data, + enum ltr390_meas_rate option) +{ + int ret, value; + + ret = regmap_read(data->regmap, LTR390_ALS_UVS_MEAS_RATE, &value); + if (ret < 0) + return ret; + value = FIELD_GET(LTR390_ALS_UVS_MEAS_RATE_MASK, value); + + return ltr390_samp_freq_table[value][option]; +} + + +static int ltr390_do_read_raw(struct iio_dev *iio_device, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret; + struct ltr390_data *data = iio_priv(iio_device); + + guard(mutex)(&data->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_UVINDEX: + ret = ltr390_set_mode(data, LTR390_SET_UVS_MODE); + if (ret < 0) + return ret; + + ret = ltr390_register_read(data, LTR390_UVS_DATA); + if (ret < 0) + return ret; + break; + + case IIO_LIGHT: + ret = ltr390_set_mode(data, LTR390_SET_ALS_MODE); + if (ret < 0) + return ret; + + ret = ltr390_register_read(data, LTR390_ALS_DATA); + if (ret < 0) + return ret; + break; + + default: + return -EINVAL; + } + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_UVINDEX: + *val = LTR390_WINDOW_FACTOR * LTR390_FRACTIONAL_PRECISION; + *val2 = ltr390_counts_per_uvi(data); + return IIO_VAL_FRACTIONAL; + + case IIO_LIGHT: + *val = LTR390_WINDOW_FACTOR * 6 * 100; + *val2 = data->gain * data->int_time_us; + return IIO_VAL_FRACTIONAL; + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_INT_TIME: + *val = data->int_time_us; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = ltr390_get_samp_freq_or_period(data, LTR390_GET_FREQ); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int ltr390_read_raw(struct iio_dev *iio_device, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct ltr390_data *data = iio_priv(iio_device); + struct device *dev = &data->client->dev; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "runtime PM failed to resume: %d\n", ret); + return ret; + } + + ret = ltr390_do_read_raw(iio_device, chan, val, val2, mask); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* integration time in us */ +static const int ltr390_int_time_map_us[] = { 400000, 200000, 100000, 50000, 25000, 12500 }; +static const int ltr390_gain_map[] = { 1, 3, 6, 9, 18 }; +static const int ltr390_freq_map[] = { 40000, 20000, 10000, 5000, 2000, 1000, 500, 500 }; + +static const struct iio_event_spec ltr390_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD), + } +}; + +static const struct iio_chan_spec ltr390_channels[] = { + /* UV sensor */ + { + .type = IIO_UVINDEX, + .scan_index = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .event_spec = ltr390_event_spec, + .num_event_specs = ARRAY_SIZE(ltr390_event_spec), + }, + /* ALS sensor */ + { + .type = IIO_LIGHT, + .scan_index = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .event_spec = ltr390_event_spec, + .num_event_specs = ARRAY_SIZE(ltr390_event_spec), + }, +}; + +static int ltr390_set_gain(struct ltr390_data *data, int val) +{ + int ret, idx; + + for (idx = 0; idx < ARRAY_SIZE(ltr390_gain_map); idx++) { + if (ltr390_gain_map[idx] != val) + continue; + + guard(mutex)(&data->lock); + ret = regmap_update_bits(data->regmap, + LTR390_ALS_UVS_GAIN, + LTR390_ALS_UVS_GAIN_MASK, idx); + if (ret) + return ret; + + data->gain = ltr390_gain_map[idx]; + return 0; + } + + return -EINVAL; +} + +static int ltr390_set_int_time(struct ltr390_data *data, int val) +{ + int ret, idx; + + for (idx = 0; idx < ARRAY_SIZE(ltr390_int_time_map_us); idx++) { + if (ltr390_int_time_map_us[idx] != val) + continue; + + guard(mutex)(&data->lock); + ret = regmap_update_bits(data->regmap, + LTR390_ALS_UVS_MEAS_RATE, + LTR390_ALS_UVS_INT_TIME_MASK, + LTR390_ALS_UVS_INT_TIME(idx)); + if (ret) + return ret; + + data->int_time_us = ltr390_int_time_map_us[idx]; + return 0; + } + + return -EINVAL; +} + +static int ltr390_set_samp_freq(struct ltr390_data *data, int val) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(ltr390_samp_freq_table); idx++) { + if (ltr390_samp_freq_table[idx][0] != val) + continue; + + guard(mutex)(&data->lock); + return regmap_update_bits(data->regmap, + LTR390_ALS_UVS_MEAS_RATE, + LTR390_ALS_UVS_MEAS_RATE_MASK, idx); + } + + return -EINVAL; +} + +static int ltr390_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_SCALE: + *length = ARRAY_SIZE(ltr390_gain_map); + *type = IIO_VAL_INT; + *vals = ltr390_gain_map; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_INT_TIME: + *length = ARRAY_SIZE(ltr390_int_time_map_us); + *type = IIO_VAL_INT; + *vals = ltr390_int_time_map_us; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *length = ARRAY_SIZE(ltr390_freq_map); + *type = IIO_VAL_INT; + *vals = ltr390_freq_map; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ltr390_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ltr390_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (val2 != 0) + return -EINVAL; + + return ltr390_set_gain(data, val); + + case IIO_CHAN_INFO_INT_TIME: + if (val2 != 0) + return -EINVAL; + + return ltr390_set_int_time(data, val); + + case IIO_CHAN_INFO_SAMP_FREQ: + if (val2 != 0) + return -EINVAL; + + return ltr390_set_samp_freq(data, val); + + default: + return -EINVAL; + } +} + +static int ltr390_read_intr_prst(struct ltr390_data *data, int *val) +{ + int ret, prst, samp_period; + + samp_period = ltr390_get_samp_freq_or_period(data, LTR390_GET_PERIOD); + ret = regmap_read(data->regmap, LTR390_INT_PST, &prst); + if (ret < 0) + return ret; + *val = prst * samp_period; + + return IIO_VAL_INT; +} + +static int ltr390_write_intr_prst(struct ltr390_data *data, int val) +{ + int ret, samp_period, new_val; + + samp_period = ltr390_get_samp_freq_or_period(data, LTR390_GET_PERIOD); + + /* persist period should be greater than or equal to samp period */ + if (val < samp_period) + return -EINVAL; + + new_val = DIV_ROUND_UP(val, samp_period); + if (new_val < 0 || new_val > 0x0f) + return -EINVAL; + + guard(mutex)(&data->lock); + ret = regmap_update_bits(data->regmap, + LTR390_INT_PST, + LTR390_INT_PST_MASK, + LTR390_INT_PST_VAL(new_val)); + if (ret) + return ret; + + return 0; +} + +static int ltr390_read_threshold(struct iio_dev *indio_dev, + enum iio_event_direction dir, + int *val, int *val2) +{ + struct ltr390_data *data = iio_priv(indio_dev); + int ret; + + switch (dir) { + case IIO_EV_DIR_RISING: + ret = ltr390_register_read(data, LTR390_THRESH_UP); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + + case IIO_EV_DIR_FALLING: + ret = ltr390_register_read(data, LTR390_THRESH_LOW); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ltr390_write_threshold(struct iio_dev *indio_dev, + enum iio_event_direction dir, + int val, int val2) +{ + struct ltr390_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + switch (dir) { + case IIO_EV_DIR_RISING: + return regmap_bulk_write(data->regmap, LTR390_THRESH_UP, &val, 3); + + case IIO_EV_DIR_FALLING: + return regmap_bulk_write(data->regmap, LTR390_THRESH_LOW, &val, 3); + + default: + return -EINVAL; + } +} + +static int ltr390_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + return ltr390_read_threshold(indio_dev, dir, val, val2); + + case IIO_EV_INFO_PERIOD: + return ltr390_read_intr_prst(iio_priv(indio_dev), val); + + default: + return -EINVAL; + } +} + +static int ltr390_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + if (val2 != 0) + return -EINVAL; + + return ltr390_write_threshold(indio_dev, dir, val, val2); + + case IIO_EV_INFO_PERIOD: + if (val2 != 0) + return -EINVAL; + + return ltr390_write_intr_prst(iio_priv(indio_dev), val); + + default: + return -EINVAL; + } +} + +static int ltr390_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ltr390_data *data = iio_priv(indio_dev); + int ret, status; + + ret = regmap_read(data->regmap, LTR390_INT_CFG, &status); + if (ret < 0) + return ret; + + return FIELD_GET(LTR390_LS_INT_EN, status); +} + +static int ltr390_do_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct ltr390_data *data = iio_priv(indio_dev); + int ret; + + if (!state) + return regmap_clear_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_EN); + + ret = regmap_set_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_EN); + if (ret < 0) + return ret; + + switch (chan->type) { + case IIO_LIGHT: + ret = ltr390_set_mode(data, LTR390_SET_ALS_MODE); + if (ret < 0) + return ret; + + return regmap_clear_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_SEL_UVS); + + case IIO_UVINDEX: + ret = ltr390_set_mode(data, LTR390_SET_UVS_MODE); + if (ret < 0) + return ret; + + return regmap_set_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_SEL_UVS); + + default: + return -EINVAL; + } +} + +static int ltr390_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + int ret; + struct ltr390_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + + guard(mutex)(&data->lock); + + if (state && !data->irq_enabled) { + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "runtime PM failed to resume: %d\n", ret); + return ret; + } + data->irq_enabled = true; + } + + ret = ltr390_do_event_config(indio_dev, chan, type, dir, state); + + if (!state && data->irq_enabled) { + data->irq_enabled = false; + pm_runtime_put_autosuspend(dev); + } + + return ret; +} + +static int ltr390_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct ltr390_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + + if (readval) + return regmap_read(data->regmap, reg, readval); + + return regmap_write(data->regmap, reg, writeval); +} + +static const struct iio_info ltr390_info = { + .read_raw = ltr390_read_raw, + .write_raw = ltr390_write_raw, + .read_avail = ltr390_read_avail, + .read_event_value = ltr390_read_event_value, + .read_event_config = ltr390_read_event_config, + .write_event_value = ltr390_write_event_value, + .write_event_config = ltr390_write_event_config, + .debugfs_reg_access = ltr390_debugfs_reg_access, +}; + +static irqreturn_t ltr390_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ltr390_data *data = iio_priv(indio_dev); + int ret, status; + + /* Reading the status register to clear the interrupt flag, Datasheet pg: 17*/ + ret = regmap_read(data->regmap, LTR390_MAIN_STATUS, &status); + if (ret < 0) + return ret; + + switch (data->mode) { + case LTR390_SET_ALS_MODE: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + break; + + case LTR390_SET_UVS_MODE: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_UVINDEX, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + break; + } + + return IRQ_HANDLED; +} + +static void ltr390_powerdown(void *priv) +{ + struct ltr390_data *data = priv; + struct device *dev = &data->client->dev; + int ret; + + guard(mutex)(&data->lock); + + /* Ensure that power off and interrupts are disabled */ + if (data->irq_enabled) { + ret = regmap_clear_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_EN); + if (ret < 0) + dev_err(dev, "failed to disable interrupts\n"); + + data->irq_enabled = false; + pm_runtime_put_autosuspend(dev); + } + + ret = regmap_clear_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE); + if (ret < 0) + dev_err(dev, "failed to disable sensor\n"); +} + +static int ltr390_pm_init(struct ltr390_data *data) +{ + int ret; + struct device *dev = &data->client->dev; + + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + return dev_err_probe(dev, ret, "failed to enable runtime PM\n"); + + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + return 0; +} + +static int ltr390_probe(struct i2c_client *client) +{ + struct ltr390_data *data; + struct iio_dev *indio_dev; + struct device *dev; + int ret, part_number; + + dev = &client->dev; + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + i2c_set_clientdata(client, indio_dev); + + data = iio_priv(indio_dev); + data->regmap = devm_regmap_init_i2c(client, <r390_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "regmap initialization failed\n"); + + data->client = client; + /* default value of integration time from pg: 15 of the datasheet */ + data->int_time_us = 100000; + /* default value of gain from pg: 16 of the datasheet */ + data->gain = 3; + /* default mode for ltr390 is ALS mode */ + data->mode = LTR390_SET_ALS_MODE; + /* default value of irq_enabled is false */ + data->irq_enabled = false; + + mutex_init(&data->lock); + + indio_dev->info = <r390_info; + indio_dev->channels = ltr390_channels; + indio_dev->num_channels = ARRAY_SIZE(ltr390_channels); + indio_dev->name = "ltr390"; + + ret = regmap_read(data->regmap, LTR390_PART_ID, &part_number); + if (ret) + return dev_err_probe(dev, ret, + "failed to get sensor's part id\n"); + /* Lower 4 bits of `part_number` change with hardware revisions */ + if (part_number >> 4 != LTR390_PART_NUMBER_ID) + dev_info(dev, "received invalid product id: 0x%x", part_number); + dev_dbg(dev, "LTR390, product id: 0x%x\n", part_number); + + /* reset sensor, chip fails to respond to this, so ignore any errors */ + regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SW_RESET); + + /* Wait for the registers to reset before proceeding */ + usleep_range(1000, 2000); + + ret = regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE); + if (ret) + return dev_err_probe(dev, ret, "failed to enable the sensor\n"); + + ret = devm_add_action_or_reset(dev, ltr390_powerdown, data); + if (ret) + return dev_err_probe(dev, ret, "failed to add action or reset\n"); + + if (client->irq) { + ret = devm_request_threaded_irq(dev, client->irq, + NULL, ltr390_interrupt_handler, + IRQF_ONESHOT, + "ltr390_thresh_event", + indio_dev); + if (ret) + return ret; + } + + ret = ltr390_pm_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize runtime PM\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static int ltr390_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ltr390_data *data = iio_priv(indio_dev); + + return regmap_clear_bits(data->regmap, LTR390_MAIN_CTRL, + LTR390_SENSOR_ENABLE); +} + +static int ltr390_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ltr390_data *data = iio_priv(indio_dev); + + return regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, + LTR390_SENSOR_ENABLE); +} + +static int ltr390_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ltr390_data *data = iio_priv(indio_dev); + + return regmap_clear_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE); +} + +static int ltr390_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ltr390_data *data = iio_priv(indio_dev); + + return regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE); +} + +static const struct dev_pm_ops ltr390_pm_ops = { + SYSTEM_SLEEP_PM_OPS(ltr390_suspend, ltr390_resume) + RUNTIME_PM_OPS(ltr390_runtime_suspend, ltr390_runtime_resume, NULL) +}; + +static const struct i2c_device_id ltr390_id[] = { + { .name = "ltr390" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltr390_id); + +static const struct of_device_id ltr390_of_table[] = { + { .compatible = "liteon,ltr390" }, + { } +}; +MODULE_DEVICE_TABLE(of, ltr390_of_table); + +static struct i2c_driver ltr390_driver = { + .driver = { + .name = "ltr390", + .of_match_table = ltr390_of_table, + .pm = pm_ptr(<r390_pm_ops), + }, + .probe = ltr390_probe, + .id_table = ltr390_id, +}; +module_i2c_driver(ltr390_driver); + +MODULE_AUTHOR("Anshul Dalal <anshulusr@gmail.com>"); +MODULE_DESCRIPTION("Lite-On LTR390 ALS and UV sensor Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/ltr501.c b/drivers/iio/light/ltr501.c new file mode 100644 index 000000000..fa0d16890 --- /dev/null +++ b/drivers/iio/light/ltr501.c @@ -0,0 +1,1633 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Support for Lite-On LTR501 and similar ambient light and proximity sensors. + * + * Copyright 2014 Peter Meerwald <pmeerw@pmeerw.net> + * + * 7-bit I2C slave address 0x23 + * + * TODO: IR LED characteristics + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +#include <linux/iio/iio.h> +#include <linux/iio/events.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> + +#define LTR501_ALS_CONTR 0x80 /* ALS operation mode, SW reset */ +#define LTR501_PS_CONTR 0x81 /* PS operation mode */ +#define LTR501_PS_MEAS_RATE 0x84 /* measurement rate*/ +#define LTR501_ALS_MEAS_RATE 0x85 /* ALS integ time, measurement rate*/ +#define LTR501_PART_ID 0x86 +#define LTR501_MANUFAC_ID 0x87 +#define LTR501_ALS_DATA1 0x88 /* 16-bit, little endian */ +#define LTR501_ALS_DATA1_UPPER 0x89 /* upper 8 bits of LTR501_ALS_DATA1 */ +#define LTR501_ALS_DATA0 0x8a /* 16-bit, little endian */ +#define LTR501_ALS_DATA0_UPPER 0x8b /* upper 8 bits of LTR501_ALS_DATA0 */ +#define LTR501_ALS_PS_STATUS 0x8c +#define LTR501_PS_DATA 0x8d /* 16-bit, little endian */ +#define LTR501_PS_DATA_UPPER 0x8e /* upper 8 bits of LTR501_PS_DATA */ +#define LTR501_INTR 0x8f /* output mode, polarity, mode */ +#define LTR501_PS_THRESH_UP 0x90 /* 11 bit, ps upper threshold */ +#define LTR501_PS_THRESH_LOW 0x92 /* 11 bit, ps lower threshold */ +#define LTR501_ALS_THRESH_UP 0x97 /* 16 bit, ALS upper threshold */ +#define LTR501_ALS_THRESH_LOW 0x99 /* 16 bit, ALS lower threshold */ +#define LTR501_INTR_PRST 0x9e /* ps thresh, als thresh */ +#define LTR501_MAX_REG 0x9f + +#define LTR501_ALS_CONTR_SW_RESET BIT(2) +#define LTR501_CONTR_PS_GAIN_MASK (BIT(3) | BIT(2)) +#define LTR501_CONTR_PS_GAIN_SHIFT 2 +#define LTR501_CONTR_ALS_GAIN_MASK BIT(3) +#define LTR501_CONTR_ACTIVE BIT(1) + +#define LTR501_STATUS_ALS_INTR BIT(3) +#define LTR501_STATUS_ALS_RDY BIT(2) +#define LTR501_STATUS_PS_INTR BIT(1) +#define LTR501_STATUS_PS_RDY BIT(0) + +#define LTR501_PS_DATA_MASK 0x7ff +#define LTR501_PS_THRESH_MASK 0x7ff +#define LTR501_ALS_THRESH_MASK 0xffff + +#define LTR501_ALS_DEF_PERIOD 500000 +#define LTR501_PS_DEF_PERIOD 100000 + +#define LTR501_LUX_CONV(vis_coeff, vis_data, ir_coeff, ir_data) \ + ((vis_coeff * vis_data) - (ir_coeff * ir_data)) + +static const int int_time_mapping[] = {100000, 50000, 200000, 400000}; + +static const struct reg_field reg_field_it = + REG_FIELD(LTR501_ALS_MEAS_RATE, 3, 4); +static const struct reg_field reg_field_als_intr = + REG_FIELD(LTR501_INTR, 1, 1); +static const struct reg_field reg_field_ps_intr = + REG_FIELD(LTR501_INTR, 0, 0); +static const struct reg_field reg_field_als_rate = + REG_FIELD(LTR501_ALS_MEAS_RATE, 0, 2); +static const struct reg_field reg_field_ps_rate = + REG_FIELD(LTR501_PS_MEAS_RATE, 0, 3); +static const struct reg_field reg_field_als_prst = + REG_FIELD(LTR501_INTR_PRST, 0, 3); +static const struct reg_field reg_field_ps_prst = + REG_FIELD(LTR501_INTR_PRST, 4, 7); + +struct ltr501_samp_table { + int freq_val; /* repetition frequency in micro HZ*/ + int time_val; /* repetition rate in micro seconds */ +}; + +#define LTR501_RESERVED_GAIN -1 + +enum { + ltr501 = 0, + ltr559, + ltr301, + ltr303, +}; + +struct ltr501_gain { + int scale; + int uscale; +}; + +static const struct ltr501_gain ltr501_als_gain_tbl[] = { + {1, 0}, + {0, 5000}, +}; + +static const struct ltr501_gain ltr559_als_gain_tbl[] = { + {1, 0}, + {0, 500000}, + {0, 250000}, + {0, 125000}, + {LTR501_RESERVED_GAIN, LTR501_RESERVED_GAIN}, + {LTR501_RESERVED_GAIN, LTR501_RESERVED_GAIN}, + {0, 20000}, + {0, 10000}, +}; + +static const struct ltr501_gain ltr501_ps_gain_tbl[] = { + {1, 0}, + {0, 250000}, + {0, 125000}, + {0, 62500}, +}; + +static const struct ltr501_gain ltr559_ps_gain_tbl[] = { + {0, 62500}, /* x16 gain */ + {0, 31250}, /* x32 gain */ + {0, 15625}, /* bits X1 are for x64 gain */ + {0, 15624}, +}; + +struct ltr501_chip_info { + u8 partid; + const struct ltr501_gain *als_gain; + int als_gain_tbl_size; + const struct ltr501_gain *ps_gain; + int ps_gain_tbl_size; + u8 als_mode_active; + u8 als_gain_mask; + u8 als_gain_shift; + struct iio_chan_spec const *channels; + const int no_channels; + const struct iio_info *info; + const struct iio_info *info_no_irq; +}; + +struct ltr501_data { + struct i2c_client *client; + struct mutex lock_als, lock_ps; + const struct ltr501_chip_info *chip_info; + u8 als_contr, ps_contr; + int als_period, ps_period; /* period in micro seconds */ + struct regmap *regmap; + struct regmap_field *reg_it; + struct regmap_field *reg_als_intr; + struct regmap_field *reg_ps_intr; + struct regmap_field *reg_als_rate; + struct regmap_field *reg_ps_rate; + struct regmap_field *reg_als_prst; + struct regmap_field *reg_ps_prst; + uint32_t near_level; +}; + +static const struct ltr501_samp_table ltr501_als_samp_table[] = { + {20000000, 50000}, {10000000, 100000}, + {5000000, 200000}, {2000000, 500000}, + {1000000, 1000000}, {500000, 2000000}, + {500000, 2000000}, {500000, 2000000} +}; + +static const struct ltr501_samp_table ltr501_ps_samp_table[] = { + {20000000, 50000}, {14285714, 70000}, + {10000000, 100000}, {5000000, 200000}, + {2000000, 500000}, {1000000, 1000000}, + {500000, 2000000}, {500000, 2000000}, + {500000, 2000000} +}; + +static int ltr501_match_samp_freq(const struct ltr501_samp_table *tab, + int len, int val, int val2) +{ + int i, freq; + + freq = val * 1000000 + val2; + + for (i = 0; i < len; i++) { + if (tab[i].freq_val == freq) + return i; + } + + return -EINVAL; +} + +static int ltr501_als_read_samp_freq(const struct ltr501_data *data, + int *val, int *val2) +{ + int ret, i; + + ret = regmap_field_read(data->reg_als_rate, &i); + if (ret < 0) + return ret; + + if (i < 0 || i >= ARRAY_SIZE(ltr501_als_samp_table)) + return -EINVAL; + + *val = ltr501_als_samp_table[i].freq_val / 1000000; + *val2 = ltr501_als_samp_table[i].freq_val % 1000000; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int ltr501_ps_read_samp_freq(const struct ltr501_data *data, + int *val, int *val2) +{ + int ret, i; + + ret = regmap_field_read(data->reg_ps_rate, &i); + if (ret < 0) + return ret; + + if (i < 0 || i >= ARRAY_SIZE(ltr501_ps_samp_table)) + return -EINVAL; + + *val = ltr501_ps_samp_table[i].freq_val / 1000000; + *val2 = ltr501_ps_samp_table[i].freq_val % 1000000; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int ltr501_als_write_samp_freq(struct ltr501_data *data, + int val, int val2) +{ + int i, ret; + + i = ltr501_match_samp_freq(ltr501_als_samp_table, + ARRAY_SIZE(ltr501_als_samp_table), + val, val2); + + if (i < 0) + return i; + + mutex_lock(&data->lock_als); + ret = regmap_field_write(data->reg_als_rate, i); + mutex_unlock(&data->lock_als); + + return ret; +} + +static int ltr501_ps_write_samp_freq(struct ltr501_data *data, + int val, int val2) +{ + int i, ret; + + i = ltr501_match_samp_freq(ltr501_ps_samp_table, + ARRAY_SIZE(ltr501_ps_samp_table), + val, val2); + + if (i < 0) + return i; + + mutex_lock(&data->lock_ps); + ret = regmap_field_write(data->reg_ps_rate, i); + mutex_unlock(&data->lock_ps); + + return ret; +} + +static int ltr501_als_read_samp_period(const struct ltr501_data *data, int *val) +{ + int ret, i; + + ret = regmap_field_read(data->reg_als_rate, &i); + if (ret < 0) + return ret; + + if (i < 0 || i >= ARRAY_SIZE(ltr501_als_samp_table)) + return -EINVAL; + + *val = ltr501_als_samp_table[i].time_val; + + return IIO_VAL_INT; +} + +static int ltr501_ps_read_samp_period(const struct ltr501_data *data, int *val) +{ + int ret, i; + + ret = regmap_field_read(data->reg_ps_rate, &i); + if (ret < 0) + return ret; + + if (i < 0 || i >= ARRAY_SIZE(ltr501_ps_samp_table)) + return -EINVAL; + + *val = ltr501_ps_samp_table[i].time_val; + + return IIO_VAL_INT; +} + +/* IR and visible spectrum coeff's are given in data sheet */ +static unsigned long ltr501_calculate_lux(u16 vis_data, u16 ir_data) +{ + unsigned long ratio, lux; + + if (vis_data == 0) + return 0; + + /* multiply numerator by 100 to avoid handling ratio < 1 */ + ratio = DIV_ROUND_UP(ir_data * 100, ir_data + vis_data); + + if (ratio < 45) + lux = LTR501_LUX_CONV(1774, vis_data, -1105, ir_data); + else if (ratio >= 45 && ratio < 64) + lux = LTR501_LUX_CONV(3772, vis_data, 1336, ir_data); + else if (ratio >= 64 && ratio < 85) + lux = LTR501_LUX_CONV(1690, vis_data, 169, ir_data); + else + lux = 0; + + return lux / 1000; +} + +static int ltr501_drdy(const struct ltr501_data *data, u8 drdy_mask) +{ + int tries = 100; + int ret, status; + + while (tries--) { + ret = regmap_read(data->regmap, LTR501_ALS_PS_STATUS, &status); + if (ret < 0) + return ret; + if ((status & drdy_mask) == drdy_mask) + return 0; + msleep(25); + } + + dev_err(&data->client->dev, "ltr501_drdy() failed, data not ready\n"); + return -EIO; +} + +static int ltr501_set_it_time(struct ltr501_data *data, int it) +{ + int ret, i, index = -1, status; + + for (i = 0; i < ARRAY_SIZE(int_time_mapping); i++) { + if (int_time_mapping[i] == it) { + index = i; + break; + } + } + /* Make sure integ time index is valid */ + if (index < 0) + return -EINVAL; + + ret = regmap_read(data->regmap, LTR501_ALS_CONTR, &status); + if (ret < 0) + return ret; + + if (status & LTR501_CONTR_ALS_GAIN_MASK) { + /* + * 200 ms and 400 ms integ time can only be + * used in dynamic range 1 + */ + if (index > 1) + return -EINVAL; + } else + /* 50 ms integ time can only be used in dynamic range 2 */ + if (index == 1) + return -EINVAL; + + return regmap_field_write(data->reg_it, index); +} + +/* read int time in micro seconds */ +static int ltr501_read_it_time(const struct ltr501_data *data, + int *val, int *val2) +{ + int ret, index; + + ret = regmap_field_read(data->reg_it, &index); + if (ret < 0) + return ret; + + /* Make sure integ time index is valid */ + if (index < 0 || index >= ARRAY_SIZE(int_time_mapping)) + return -EINVAL; + + *val2 = int_time_mapping[index]; + *val = 0; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int ltr501_read_als(const struct ltr501_data *data, __le16 buf[2]) +{ + int ret; + + ret = ltr501_drdy(data, LTR501_STATUS_ALS_RDY); + if (ret < 0) + return ret; + /* always read both ALS channels in given order */ + return regmap_bulk_read(data->regmap, LTR501_ALS_DATA1, + buf, 2 * sizeof(__le16)); +} + +static int ltr501_read_ps(const struct ltr501_data *data) +{ + __le16 status; + int ret; + + ret = ltr501_drdy(data, LTR501_STATUS_PS_RDY); + if (ret < 0) + return ret; + + ret = regmap_bulk_read(data->regmap, LTR501_PS_DATA, + &status, sizeof(status)); + if (ret < 0) + return ret; + + return le16_to_cpu(status); +} + +static int ltr501_read_intr_prst(const struct ltr501_data *data, + enum iio_chan_type type, + int *val2) +{ + int ret, samp_period, prst; + + switch (type) { + case IIO_INTENSITY: + ret = regmap_field_read(data->reg_als_prst, &prst); + if (ret < 0) + return ret; + + ret = ltr501_als_read_samp_period(data, &samp_period); + + if (ret < 0) + return ret; + *val2 = samp_period * prst; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_PROXIMITY: + ret = regmap_field_read(data->reg_ps_prst, &prst); + if (ret < 0) + return ret; + + ret = ltr501_ps_read_samp_period(data, &samp_period); + + if (ret < 0) + return ret; + + *val2 = samp_period * prst; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int ltr501_write_intr_prst(struct ltr501_data *data, + enum iio_chan_type type, + int val, int val2) +{ + int ret, samp_period, new_val; + unsigned long period; + + if (val < 0 || val2 < 0) + return -EINVAL; + + /* period in microseconds */ + period = ((val * 1000000) + val2); + + switch (type) { + case IIO_INTENSITY: + ret = ltr501_als_read_samp_period(data, &samp_period); + if (ret < 0) + return ret; + + /* period should be atleast equal to sampling period */ + if (period < samp_period) + return -EINVAL; + + new_val = DIV_ROUND_UP(period, samp_period); + if (new_val < 0 || new_val > 0x0f) + return -EINVAL; + + mutex_lock(&data->lock_als); + ret = regmap_field_write(data->reg_als_prst, new_val); + mutex_unlock(&data->lock_als); + if (ret >= 0) + data->als_period = period; + + return ret; + case IIO_PROXIMITY: + ret = ltr501_ps_read_samp_period(data, &samp_period); + if (ret < 0) + return ret; + + /* period should be atleast equal to rate */ + if (period < samp_period) + return -EINVAL; + + new_val = DIV_ROUND_UP(period, samp_period); + if (new_val < 0 || new_val > 0x0f) + return -EINVAL; + + mutex_lock(&data->lock_ps); + ret = regmap_field_write(data->reg_ps_prst, new_val); + mutex_unlock(&data->lock_ps); + if (ret >= 0) + data->ps_period = period; + + return ret; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static ssize_t ltr501_read_near_level(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ltr501_data *data = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", data->near_level); +} + +static const struct iio_chan_spec_ext_info ltr501_ext_info[] = { + { + .name = "nearlevel", + .shared = IIO_SEPARATE, + .read = ltr501_read_near_level, + }, + { } +}; + +static const struct iio_event_spec ltr501_als_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD), + }, + +}; + +static const struct iio_event_spec ltr501_pxs_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD), + }, +}; + +#define LTR501_INTENSITY_CHANNEL(_idx, _addr, _mod, _shared, \ + _evspec, _evsize) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .address = (_addr), \ + .channel2 = (_mod), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = (_shared), \ + .scan_index = (_idx), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + .event_spec = _evspec,\ + .num_event_specs = _evsize,\ +} + +#define LTR501_LIGHT_CHANNEL() { \ + .type = IIO_LIGHT, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .scan_index = -1, \ +} + +static const struct iio_chan_spec ltr501_channels[] = { + LTR501_LIGHT_CHANNEL(), + LTR501_INTENSITY_CHANNEL(0, LTR501_ALS_DATA0, IIO_MOD_LIGHT_BOTH, 0, + ltr501_als_event_spec, + ARRAY_SIZE(ltr501_als_event_spec)), + LTR501_INTENSITY_CHANNEL(1, LTR501_ALS_DATA1, IIO_MOD_LIGHT_IR, + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + NULL, 0), + { + .type = IIO_PROXIMITY, + .address = LTR501_PS_DATA, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 2, + .scan_type = { + .sign = 'u', + .realbits = 11, + .storagebits = 16, + .endianness = IIO_CPU, + }, + .event_spec = ltr501_pxs_event_spec, + .num_event_specs = ARRAY_SIZE(ltr501_pxs_event_spec), + .ext_info = ltr501_ext_info, + }, + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec ltr301_channels[] = { + LTR501_LIGHT_CHANNEL(), + LTR501_INTENSITY_CHANNEL(0, LTR501_ALS_DATA0, IIO_MOD_LIGHT_BOTH, 0, + ltr501_als_event_spec, + ARRAY_SIZE(ltr501_als_event_spec)), + LTR501_INTENSITY_CHANNEL(1, LTR501_ALS_DATA1, IIO_MOD_LIGHT_IR, + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + NULL, 0), + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static int ltr501_read_info_raw(struct ltr501_data *data, + struct iio_chan_spec const *chan, + int *val) +{ + __le16 buf[2]; + int ret; + + switch (chan->type) { + case IIO_INTENSITY: + mutex_lock(&data->lock_als); + ret = ltr501_read_als(data, buf); + mutex_unlock(&data->lock_als); + if (ret < 0) + return ret; + *val = le16_to_cpu(chan->address == LTR501_ALS_DATA1 ? + buf[0] : buf[1]); + return IIO_VAL_INT; + case IIO_PROXIMITY: + mutex_lock(&data->lock_ps); + ret = ltr501_read_ps(data); + mutex_unlock(&data->lock_ps); + if (ret < 0) + return ret; + *val = ret & LTR501_PS_DATA_MASK; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ltr501_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ltr501_data *data = iio_priv(indio_dev); + __le16 buf[2]; + int ret, i; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + mutex_lock(&data->lock_als); + ret = ltr501_read_als(data, buf); + mutex_unlock(&data->lock_als); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + *val = ltr501_calculate_lux(le16_to_cpu(buf[1]), + le16_to_cpu(buf[0])); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ltr501_read_info_raw(data, chan, val); + + iio_device_release_direct(indio_dev); + return ret; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_INTENSITY: + i = (data->als_contr & data->chip_info->als_gain_mask) + >> data->chip_info->als_gain_shift; + *val = data->chip_info->als_gain[i].scale; + *val2 = data->chip_info->als_gain[i].uscale; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_PROXIMITY: + i = (data->ps_contr & LTR501_CONTR_PS_GAIN_MASK) >> + LTR501_CONTR_PS_GAIN_SHIFT; + *val = data->chip_info->ps_gain[i].scale; + *val2 = data->chip_info->ps_gain[i].uscale; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_INTENSITY: + return ltr501_read_it_time(data, val, val2); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_INTENSITY: + return ltr501_als_read_samp_freq(data, val, val2); + case IIO_PROXIMITY: + return ltr501_ps_read_samp_freq(data, val, val2); + default: + return -EINVAL; + } + } + return -EINVAL; +} + +static int ltr501_get_gain_index(const struct ltr501_gain *gain, int size, + int val, int val2) +{ + int i; + + for (i = 0; i < size; i++) + if (val == gain[i].scale && val2 == gain[i].uscale) + return i; + + return -EINVAL; +} + +static int __ltr501_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ltr501_data *data = iio_priv(indio_dev); + int i, ret, freq_val, freq_val2; + const struct ltr501_chip_info *info = data->chip_info; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_INTENSITY: + i = ltr501_get_gain_index(info->als_gain, + info->als_gain_tbl_size, + val, val2); + if (i < 0) + return i; + + data->als_contr &= ~info->als_gain_mask; + data->als_contr |= i << info->als_gain_shift; + + return regmap_write(data->regmap, LTR501_ALS_CONTR, + data->als_contr); + case IIO_PROXIMITY: + i = ltr501_get_gain_index(info->ps_gain, + info->ps_gain_tbl_size, + val, val2); + if (i < 0) + return i; + + data->ps_contr &= ~LTR501_CONTR_PS_GAIN_MASK; + data->ps_contr |= i << LTR501_CONTR_PS_GAIN_SHIFT; + + return regmap_write(data->regmap, LTR501_PS_CONTR, + data->ps_contr); + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_INTENSITY: + if (val != 0) + return -EINVAL; + + mutex_lock(&data->lock_als); + ret = ltr501_set_it_time(data, val2); + mutex_unlock(&data->lock_als); + return ret; + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_INTENSITY: + ret = ltr501_als_read_samp_freq(data, &freq_val, + &freq_val2); + if (ret < 0) + return ret; + + ret = ltr501_als_write_samp_freq(data, val, val2); + if (ret < 0) + return ret; + + /* update persistence count when changing frequency */ + ret = ltr501_write_intr_prst(data, chan->type, + 0, data->als_period); + + if (ret < 0) + /* Do not ovewrite error */ + ltr501_als_write_samp_freq(data, freq_val, + freq_val2); + return ret; + case IIO_PROXIMITY: + ret = ltr501_ps_read_samp_freq(data, &freq_val, + &freq_val2); + if (ret < 0) + return ret; + + ret = ltr501_ps_write_samp_freq(data, val, val2); + if (ret < 0) + return ret; + + /* update persistence count when changing frequency */ + ret = ltr501_write_intr_prst(data, chan->type, + 0, data->ps_period); + + if (ret < 0) + /* Do not overwrite error */ + ltr501_ps_write_samp_freq(data, freq_val, + freq_val2); + return ret; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ltr501_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = __ltr501_write_raw(indio_dev, chan, val, val2, mask); + + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ltr501_read_thresh(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + const struct ltr501_data *data = iio_priv(indio_dev); + int ret, thresh_data; + + switch (chan->type) { + case IIO_INTENSITY: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = regmap_bulk_read(data->regmap, + LTR501_ALS_THRESH_UP, + &thresh_data, 2); + if (ret < 0) + return ret; + *val = thresh_data & LTR501_ALS_THRESH_MASK; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + ret = regmap_bulk_read(data->regmap, + LTR501_ALS_THRESH_LOW, + &thresh_data, 2); + if (ret < 0) + return ret; + *val = thresh_data & LTR501_ALS_THRESH_MASK; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_PROXIMITY: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = regmap_bulk_read(data->regmap, + LTR501_PS_THRESH_UP, + &thresh_data, 2); + if (ret < 0) + return ret; + *val = thresh_data & LTR501_PS_THRESH_MASK; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + ret = regmap_bulk_read(data->regmap, + LTR501_PS_THRESH_LOW, + &thresh_data, 2); + if (ret < 0) + return ret; + *val = thresh_data & LTR501_PS_THRESH_MASK; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int ltr501_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct ltr501_data *data = iio_priv(indio_dev); + int ret; + + if (val < 0) + return -EINVAL; + + switch (chan->type) { + case IIO_INTENSITY: + if (val > LTR501_ALS_THRESH_MASK) + return -EINVAL; + switch (dir) { + case IIO_EV_DIR_RISING: + mutex_lock(&data->lock_als); + ret = regmap_bulk_write(data->regmap, + LTR501_ALS_THRESH_UP, + &val, 2); + mutex_unlock(&data->lock_als); + return ret; + case IIO_EV_DIR_FALLING: + mutex_lock(&data->lock_als); + ret = regmap_bulk_write(data->regmap, + LTR501_ALS_THRESH_LOW, + &val, 2); + mutex_unlock(&data->lock_als); + return ret; + default: + return -EINVAL; + } + case IIO_PROXIMITY: + if (val > LTR501_PS_THRESH_MASK) + return -EINVAL; + switch (dir) { + case IIO_EV_DIR_RISING: + mutex_lock(&data->lock_ps); + ret = regmap_bulk_write(data->regmap, + LTR501_PS_THRESH_UP, + &val, 2); + mutex_unlock(&data->lock_ps); + return ret; + case IIO_EV_DIR_FALLING: + mutex_lock(&data->lock_ps); + ret = regmap_bulk_write(data->regmap, + LTR501_PS_THRESH_LOW, + &val, 2); + mutex_unlock(&data->lock_ps); + return ret; + default: + return -EINVAL; + } + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int ltr501_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + return ltr501_read_thresh(indio_dev, chan, type, dir, + info, val, val2); + case IIO_EV_INFO_PERIOD: + ret = ltr501_read_intr_prst(iio_priv(indio_dev), + chan->type, val2); + *val = *val2 / 1000000; + *val2 = *val2 % 1000000; + return ret; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int ltr501_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + if (val2 != 0) + return -EINVAL; + return ltr501_write_thresh(indio_dev, chan, type, dir, + info, val, val2); + case IIO_EV_INFO_PERIOD: + return ltr501_write_intr_prst(iio_priv(indio_dev), chan->type, + val, val2); + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int ltr501_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ltr501_data *data = iio_priv(indio_dev); + int ret, status; + + switch (chan->type) { + case IIO_INTENSITY: + ret = regmap_field_read(data->reg_als_intr, &status); + if (ret < 0) + return ret; + return status; + case IIO_PROXIMITY: + ret = regmap_field_read(data->reg_ps_intr, &status); + if (ret < 0) + return ret; + return status; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int ltr501_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct ltr501_data *data = iio_priv(indio_dev); + int ret; + + switch (chan->type) { + case IIO_INTENSITY: + mutex_lock(&data->lock_als); + ret = regmap_field_write(data->reg_als_intr, state); + mutex_unlock(&data->lock_als); + return ret; + case IIO_PROXIMITY: + mutex_lock(&data->lock_ps); + ret = regmap_field_write(data->reg_ps_intr, state); + mutex_unlock(&data->lock_ps); + return ret; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static ssize_t ltr501_show_proximity_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ltr501_data *data = iio_priv(dev_to_iio_dev(dev)); + const struct ltr501_chip_info *info = data->chip_info; + ssize_t len = 0; + int i; + + for (i = 0; i < info->ps_gain_tbl_size; i++) { + if (info->ps_gain[i].scale == LTR501_RESERVED_GAIN) + continue; + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ", + info->ps_gain[i].scale, + info->ps_gain[i].uscale); + } + + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t ltr501_show_intensity_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ltr501_data *data = iio_priv(dev_to_iio_dev(dev)); + const struct ltr501_chip_info *info = data->chip_info; + ssize_t len = 0; + int i; + + for (i = 0; i < info->als_gain_tbl_size; i++) { + if (info->als_gain[i].scale == LTR501_RESERVED_GAIN) + continue; + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ", + info->als_gain[i].scale, + info->als_gain[i].uscale); + } + + buf[len - 1] = '\n'; + + return len; +} + +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.05 0.1 0.2 0.4"); +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("20 10 5 2 1 0.5"); + +static IIO_DEVICE_ATTR(in_proximity_scale_available, S_IRUGO, + ltr501_show_proximity_scale_avail, NULL, 0); +static IIO_DEVICE_ATTR(in_intensity_scale_available, S_IRUGO, + ltr501_show_intensity_scale_avail, NULL, 0); + +static struct attribute *ltr501_attributes[] = { + &iio_dev_attr_in_proximity_scale_available.dev_attr.attr, + &iio_dev_attr_in_intensity_scale_available.dev_attr.attr, + &iio_const_attr_integration_time_available.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static struct attribute *ltr301_attributes[] = { + &iio_dev_attr_in_intensity_scale_available.dev_attr.attr, + &iio_const_attr_integration_time_available.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group ltr501_attribute_group = { + .attrs = ltr501_attributes, +}; + +static const struct attribute_group ltr301_attribute_group = { + .attrs = ltr301_attributes, +}; + +static const struct iio_info ltr501_info_no_irq = { + .read_raw = ltr501_read_raw, + .write_raw = ltr501_write_raw, + .attrs = <r501_attribute_group, +}; + +static const struct iio_info ltr501_info = { + .read_raw = ltr501_read_raw, + .write_raw = ltr501_write_raw, + .attrs = <r501_attribute_group, + .read_event_value = <r501_read_event, + .write_event_value = <r501_write_event, + .read_event_config = <r501_read_event_config, + .write_event_config = <r501_write_event_config, +}; + +static const struct iio_info ltr301_info_no_irq = { + .read_raw = ltr501_read_raw, + .write_raw = ltr501_write_raw, + .attrs = <r301_attribute_group, +}; + +static const struct iio_info ltr301_info = { + .read_raw = ltr501_read_raw, + .write_raw = ltr501_write_raw, + .attrs = <r301_attribute_group, + .read_event_value = <r501_read_event, + .write_event_value = <r501_write_event, + .read_event_config = <r501_read_event_config, + .write_event_config = <r501_write_event_config, +}; + +static const struct ltr501_chip_info ltr501_chip_info_tbl[] = { + [ltr501] = { + .partid = 0x08, + .als_gain = ltr501_als_gain_tbl, + .als_gain_tbl_size = ARRAY_SIZE(ltr501_als_gain_tbl), + .ps_gain = ltr501_ps_gain_tbl, + .ps_gain_tbl_size = ARRAY_SIZE(ltr501_ps_gain_tbl), + .als_mode_active = BIT(0) | BIT(1), + .als_gain_mask = BIT(3), + .als_gain_shift = 3, + .info = <r501_info, + .info_no_irq = <r501_info_no_irq, + .channels = ltr501_channels, + .no_channels = ARRAY_SIZE(ltr501_channels), + }, + [ltr559] = { + .partid = 0x09, + .als_gain = ltr559_als_gain_tbl, + .als_gain_tbl_size = ARRAY_SIZE(ltr559_als_gain_tbl), + .ps_gain = ltr559_ps_gain_tbl, + .ps_gain_tbl_size = ARRAY_SIZE(ltr559_ps_gain_tbl), + .als_mode_active = BIT(0), + .als_gain_mask = BIT(2) | BIT(3) | BIT(4), + .als_gain_shift = 2, + .info = <r501_info, + .info_no_irq = <r501_info_no_irq, + .channels = ltr501_channels, + .no_channels = ARRAY_SIZE(ltr501_channels), + }, + [ltr301] = { + .partid = 0x08, + .als_gain = ltr501_als_gain_tbl, + .als_gain_tbl_size = ARRAY_SIZE(ltr501_als_gain_tbl), + .als_mode_active = BIT(0) | BIT(1), + .als_gain_mask = BIT(3), + .als_gain_shift = 3, + .info = <r301_info, + .info_no_irq = <r301_info_no_irq, + .channels = ltr301_channels, + .no_channels = ARRAY_SIZE(ltr301_channels), + }, + [ltr303] = { + .partid = 0x0A, + .als_gain = ltr559_als_gain_tbl, + .als_gain_tbl_size = ARRAY_SIZE(ltr559_als_gain_tbl), + .als_mode_active = BIT(0), + .als_gain_mask = BIT(2) | BIT(3) | BIT(4), + .als_gain_shift = 2, + .info = <r301_info, + .info_no_irq = <r301_info_no_irq, + .channels = ltr301_channels, + .no_channels = ARRAY_SIZE(ltr301_channels), + }, +}; + +static int ltr501_write_contr(struct ltr501_data *data, u8 als_val, u8 ps_val) +{ + int ret; + + ret = regmap_write(data->regmap, LTR501_ALS_CONTR, als_val); + if (ret < 0) + return ret; + + return regmap_write(data->regmap, LTR501_PS_CONTR, ps_val); +} + +static irqreturn_t ltr501_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ltr501_data *data = iio_priv(indio_dev); + struct { + u16 channels[3]; + aligned_s64 ts; + } scan = { }; + __le16 als_buf[2]; + u8 mask = 0; + int j = 0; + int ret, psdata; + + /* figure out which data needs to be ready */ + if (test_bit(0, indio_dev->active_scan_mask) || + test_bit(1, indio_dev->active_scan_mask)) + mask |= LTR501_STATUS_ALS_RDY; + if (test_bit(2, indio_dev->active_scan_mask)) + mask |= LTR501_STATUS_PS_RDY; + + ret = ltr501_drdy(data, mask); + if (ret < 0) + goto done; + + if (mask & LTR501_STATUS_ALS_RDY) { + ret = regmap_bulk_read(data->regmap, LTR501_ALS_DATA1, + als_buf, sizeof(als_buf)); + if (ret < 0) + goto done; + if (test_bit(0, indio_dev->active_scan_mask)) + scan.channels[j++] = le16_to_cpu(als_buf[1]); + if (test_bit(1, indio_dev->active_scan_mask)) + scan.channels[j++] = le16_to_cpu(als_buf[0]); + } + + if (mask & LTR501_STATUS_PS_RDY) { + ret = regmap_bulk_read(data->regmap, LTR501_PS_DATA, + &psdata, 2); + if (ret < 0) + goto done; + scan.channels[j++] = psdata & LTR501_PS_DATA_MASK; + } + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t ltr501_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ltr501_data *data = iio_priv(indio_dev); + int ret, status; + + ret = regmap_read(data->regmap, LTR501_ALS_PS_STATUS, &status); + if (ret < 0) { + dev_err(&data->client->dev, + "irq read int reg failed\n"); + return IRQ_HANDLED; + } + + if (status & LTR501_STATUS_ALS_INTR) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + + if (status & LTR501_STATUS_PS_INTR) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; +} + +static int ltr501_init(struct ltr501_data *data) +{ + int ret, status; + + ret = regmap_read(data->regmap, LTR501_ALS_CONTR, &status); + if (ret < 0) + return ret; + + data->als_contr = status | data->chip_info->als_mode_active; + + ret = regmap_read(data->regmap, LTR501_PS_CONTR, &status); + if (ret < 0) + return ret; + + data->ps_contr = status | LTR501_CONTR_ACTIVE; + + ret = ltr501_read_intr_prst(data, IIO_INTENSITY, &data->als_period); + if (ret < 0) + return ret; + + ret = ltr501_read_intr_prst(data, IIO_PROXIMITY, &data->ps_period); + if (ret < 0) + return ret; + + return ltr501_write_contr(data, data->als_contr, data->ps_contr); +} + +static bool ltr501_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LTR501_ALS_DATA1: + case LTR501_ALS_DATA1_UPPER: + case LTR501_ALS_DATA0: + case LTR501_ALS_DATA0_UPPER: + case LTR501_ALS_PS_STATUS: + case LTR501_PS_DATA: + case LTR501_PS_DATA_UPPER: + return true; + default: + return false; + } +} + +static const struct regmap_config ltr501_regmap_config = { + .name = "ltr501_regmap", + .reg_bits = 8, + .val_bits = 8, + .max_register = LTR501_MAX_REG, + .cache_type = REGCACHE_MAPLE, + .volatile_reg = ltr501_is_volatile_reg, +}; + +static int ltr501_powerdown(struct ltr501_data *data) +{ + return ltr501_write_contr(data, data->als_contr & + ~data->chip_info->als_mode_active, + data->ps_contr & ~LTR501_CONTR_ACTIVE); +} + +static int ltr501_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + static const char * const regulator_names[] = { "vdd", "vddio" }; + struct ltr501_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + const void *ddata = NULL; + int partid, chip_idx; + const char *name; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, <r501_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Regmap initialization failed.\n"); + return PTR_ERR(regmap); + } + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + mutex_init(&data->lock_als); + mutex_init(&data->lock_ps); + + ret = devm_regulator_bulk_get_enable(&client->dev, + ARRAY_SIZE(regulator_names), + regulator_names); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to get regulators\n"); + + data->reg_it = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_it); + if (IS_ERR(data->reg_it)) { + dev_err(&client->dev, "Integ time reg field init failed.\n"); + return PTR_ERR(data->reg_it); + } + + data->reg_als_intr = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_als_intr); + if (IS_ERR(data->reg_als_intr)) { + dev_err(&client->dev, "ALS intr mode reg field init failed\n"); + return PTR_ERR(data->reg_als_intr); + } + + data->reg_ps_intr = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_ps_intr); + if (IS_ERR(data->reg_ps_intr)) { + dev_err(&client->dev, "PS intr mode reg field init failed.\n"); + return PTR_ERR(data->reg_ps_intr); + } + + data->reg_als_rate = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_als_rate); + if (IS_ERR(data->reg_als_rate)) { + dev_err(&client->dev, "ALS samp rate field init failed.\n"); + return PTR_ERR(data->reg_als_rate); + } + + data->reg_ps_rate = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_ps_rate); + if (IS_ERR(data->reg_ps_rate)) { + dev_err(&client->dev, "PS samp rate field init failed.\n"); + return PTR_ERR(data->reg_ps_rate); + } + + data->reg_als_prst = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_als_prst); + if (IS_ERR(data->reg_als_prst)) { + dev_err(&client->dev, "ALS prst reg field init failed\n"); + return PTR_ERR(data->reg_als_prst); + } + + data->reg_ps_prst = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_ps_prst); + if (IS_ERR(data->reg_ps_prst)) { + dev_err(&client->dev, "PS prst reg field init failed.\n"); + return PTR_ERR(data->reg_ps_prst); + } + + ret = regmap_read(data->regmap, LTR501_PART_ID, &partid); + if (ret < 0) + return ret; + + if (id) { + name = id->name; + chip_idx = id->driver_data; + } else { + name = iio_get_acpi_device_name_and_data(&client->dev, &ddata); + chip_idx = (intptr_t)ddata; + } + if (!name) + return -ENODEV; + + data->chip_info = <r501_chip_info_tbl[chip_idx]; + + if ((partid >> 4) != data->chip_info->partid) + return -ENODEV; + + if (device_property_read_u32(&client->dev, "proximity-near-level", + &data->near_level)) + data->near_level = 0; + + indio_dev->info = data->chip_info->info; + indio_dev->channels = data->chip_info->channels; + indio_dev->num_channels = data->chip_info->no_channels; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = ltr501_init(data); + if (ret < 0) + return ret; + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, ltr501_interrupt_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "ltr501_thresh_event", + indio_dev); + if (ret) + return ret; + } else { + indio_dev->info = data->chip_info->info_no_irq; + } + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + ltr501_trigger_handler, NULL); + if (ret) + goto powerdown_on_error; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_unreg_buffer; + + return 0; + +error_unreg_buffer: + iio_triggered_buffer_cleanup(indio_dev); +powerdown_on_error: + ltr501_powerdown(data); + return ret; +} + +static void ltr501_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + ltr501_powerdown(iio_priv(indio_dev)); +} + +static int ltr501_suspend(struct device *dev) +{ + struct ltr501_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return ltr501_powerdown(data); +} + +static int ltr501_resume(struct device *dev) +{ + struct ltr501_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + + return ltr501_write_contr(data, data->als_contr, + data->ps_contr); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ltr501_pm_ops, ltr501_suspend, ltr501_resume); + +static const struct acpi_device_id ltr_acpi_match[] = { + { "LTER0301", ltr301 }, + /* https://www.catalog.update.microsoft.com/Search.aspx?q=lter0303 */ + { "LTER0303", ltr303 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, ltr_acpi_match); + +static const struct i2c_device_id ltr501_id[] = { + { .name = "ltr501", .driver_data = ltr501 }, + { .name = "ltr559", .driver_data = ltr559 }, + { .name = "ltr301", .driver_data = ltr301 }, + { .name = "ltr303", .driver_data = ltr303 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltr501_id); + +static const struct of_device_id ltr501_of_match[] = { + { .compatible = "liteon,ltr501", }, + { .compatible = "liteon,ltr559", }, + { .compatible = "liteon,ltr301", }, + { .compatible = "liteon,ltr303", }, + { } +}; +MODULE_DEVICE_TABLE(of, ltr501_of_match); + +static struct i2c_driver ltr501_driver = { + .driver = { + .name = "ltr501", + .of_match_table = ltr501_of_match, + .pm = pm_sleep_ptr(<r501_pm_ops), + .acpi_match_table = ltr_acpi_match, + }, + .probe = ltr501_probe, + .remove = ltr501_remove, + .id_table = ltr501_id, +}; + +module_i2c_driver(ltr501_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("Lite-On LTR501 ambient light and proximity sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/ltrf216a.c b/drivers/iio/light/ltrf216a.c new file mode 100644 index 000000000..0a8851342 --- /dev/null +++ b/drivers/iio/light/ltrf216a.c @@ -0,0 +1,581 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * LTRF216A Ambient Light Sensor + * + * Copyright (C) 2022 Collabora, Ltd. + * Author: Shreeya Patel <shreeya.patel@collabora.com> + * + * Copyright (C) 2021 Lite-On Technology Corp (Singapore) + * Author: Shi Zhigang <Zhigang.Shi@liteon.com> + * + * IIO driver for LTRF216A (7-bit I2C slave address 0x53). + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> + +#include <linux/iio/iio.h> + +#include <linux/unaligned.h> + +#define LTRF216A_ALS_RESET_MASK BIT(4) +#define LTRF216A_ALS_DATA_STATUS BIT(3) +#define LTRF216A_ALS_ENABLE_MASK BIT(1) +#define LTRF216A_MAIN_CTRL 0x00 +#define LTRF216A_ALS_MEAS_RES 0x04 +#define LTRF216A_ALS_GAIN 0x05 +#define LTRF216A_PART_ID 0x06 +#define LTRF216A_MAIN_STATUS 0x07 +#define LTRF216A_ALS_CLEAR_DATA_0 0x0a +#define LTRF216A_ALS_CLEAR_DATA_1 0x0b +#define LTRF216A_ALS_CLEAR_DATA_2 0x0c +#define LTRF216A_ALS_DATA_0 0x0d +#define LTRF216A_ALS_DATA_1 0x0e +#define LTRF216A_ALS_DATA_2 0x0f +#define LTRF216A_INT_CFG 0x19 +#define LTRF216A_INT_PST 0x1a +#define LTRF216A_ALS_THRES_UP_0 0x21 +#define LTRF216A_ALS_THRES_UP_1 0x22 +#define LTRF216A_ALS_THRES_UP_2 0x23 +#define LTRF216A_ALS_THRES_LOW_0 0x24 +#define LTRF216A_ALS_THRES_LOW_1 0x25 +#define LTRF216A_ALS_THRES_LOW_2 0x26 +#define LTRF216A_ALS_READ_DATA_DELAY_US 20000 + +static const int ltrf216a_int_time_available[][2] = { + { 0, 400000 }, + { 0, 200000 }, + { 0, 100000 }, + { 0, 50000 }, + { 0, 25000 }, +}; + +static const int ltrf216a_int_time_reg[][2] = { + { 400, 0x03 }, + { 200, 0x13 }, + { 100, 0x22 }, + { 50, 0x31 }, + { 25, 0x40 }, +}; + +struct ltr_chip_info { + /* Chip contains CLEAR_DATA_0/1/2 registers at offset 0xa..0xc */ + bool has_clear_data; + /* Lux calculation multiplier for ALS data */ + int lux_multiplier; +}; + +/* + * Window Factor is needed when the device is under Window glass + * with coated tinted ink. This is to compensate for the light loss + * due to the lower transmission rate of the window glass and helps + * in calculating lux. + */ +#define LTRF216A_WIN_FAC 1 + +struct ltrf216a_data { + struct regmap *regmap; + struct i2c_client *client; + const struct ltr_chip_info *info; + u32 int_time; + u16 int_time_fac; + u8 als_gain_fac; + /* + * Protects regmap accesses and makes sure integration time + * remains constant during the measurement of lux. + */ + struct mutex lock; +}; + +static const struct iio_chan_spec ltrf216a_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_INT_TIME), + }, +}; + +static void ltrf216a_reset(struct iio_dev *indio_dev) +{ + struct ltrf216a_data *data = iio_priv(indio_dev); + + /* reset sensor, chip fails to respond to this, so ignore any errors */ + regmap_write(data->regmap, LTRF216A_MAIN_CTRL, LTRF216A_ALS_RESET_MASK); + + /* reset time */ + usleep_range(1000, 2000); +} + +static int ltrf216a_enable(struct iio_dev *indio_dev) +{ + struct ltrf216a_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + int ret; + + /* enable sensor */ + ret = regmap_set_bits(data->regmap, + LTRF216A_MAIN_CTRL, LTRF216A_ALS_ENABLE_MASK); + if (ret) { + dev_err(dev, "failed to enable sensor: %d\n", ret); + return ret; + } + + /* sleep for one integration cycle after enabling the device */ + msleep(ltrf216a_int_time_reg[0][0]); + + return 0; +} + +static int ltrf216a_disable(struct iio_dev *indio_dev) +{ + struct ltrf216a_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + int ret; + + ret = regmap_write(data->regmap, LTRF216A_MAIN_CTRL, 0); + if (ret) + dev_err(dev, "failed to disable sensor: %d\n", ret); + + return ret; +} + +static void ltrf216a_cleanup(void *data) +{ + struct iio_dev *indio_dev = data; + + ltrf216a_disable(indio_dev); +} + +static int ltrf216a_set_int_time(struct ltrf216a_data *data, int itime) +{ + struct device *dev = &data->client->dev; + unsigned int i; + u8 reg_val; + int ret; + + for (i = 0; i < ARRAY_SIZE(ltrf216a_int_time_available); i++) { + if (ltrf216a_int_time_available[i][1] == itime) + break; + } + if (i == ARRAY_SIZE(ltrf216a_int_time_available)) + return -EINVAL; + + reg_val = ltrf216a_int_time_reg[i][1]; + + ret = regmap_write(data->regmap, LTRF216A_ALS_MEAS_RES, reg_val); + if (ret) { + dev_err(dev, "failed to set integration time: %d\n", ret); + return ret; + } + + data->int_time_fac = ltrf216a_int_time_reg[i][0]; + data->int_time = itime; + + return 0; +} + +static int ltrf216a_get_int_time(struct ltrf216a_data *data, + int *val, int *val2) +{ + *val = 0; + *val2 = data->int_time; + return IIO_VAL_INT_PLUS_MICRO; +} + +static int ltrf216a_set_power_state(struct ltrf216a_data *data, bool on) +{ + struct device *dev = &data->client->dev; + int ret = 0; + + if (on) { + ret = pm_runtime_resume_and_get(dev); + if (ret) { + dev_err(dev, "failed to resume runtime PM: %d\n", ret); + return ret; + } + } else { + pm_runtime_put_autosuspend(dev); + } + + return ret; +} + +static int ltrf216a_read_data(struct ltrf216a_data *data, u8 addr) +{ + struct device *dev = &data->client->dev; + int ret, val; + u8 buf[3]; + + ret = regmap_read_poll_timeout(data->regmap, LTRF216A_MAIN_STATUS, + val, val & LTRF216A_ALS_DATA_STATUS, + LTRF216A_ALS_READ_DATA_DELAY_US, + LTRF216A_ALS_READ_DATA_DELAY_US * 50); + if (ret) { + dev_err(dev, "failed to wait for measurement data: %d\n", ret); + return ret; + } + + ret = regmap_bulk_read(data->regmap, addr, buf, sizeof(buf)); + if (ret) { + dev_err(dev, "failed to read measurement data: %d\n", ret); + return ret; + } + + return get_unaligned_le24(&buf[0]); +} + +static int ltrf216a_get_lux(struct ltrf216a_data *data) +{ + int ret, greendata; + u64 lux; + + ret = ltrf216a_set_power_state(data, true); + if (ret) + return ret; + + greendata = ltrf216a_read_data(data, LTRF216A_ALS_DATA_0); + ltrf216a_set_power_state(data, false); + if (greendata < 0) + return greendata; + + lux = greendata * data->info->lux_multiplier * LTRF216A_WIN_FAC; + + return lux; +} + +static int ltrf216a_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct ltrf216a_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = ltrf216a_set_power_state(data, true); + if (ret) + return ret; + mutex_lock(&data->lock); + ret = ltrf216a_read_data(data, LTRF216A_ALS_DATA_0); + mutex_unlock(&data->lock); + ltrf216a_set_power_state(data, false); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_PROCESSED: + mutex_lock(&data->lock); + ret = ltrf216a_get_lux(data); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = ret; + *val2 = data->als_gain_fac * data->int_time_fac; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_INT_TIME: + mutex_lock(&data->lock); + ret = ltrf216a_get_int_time(data, val, val2); + mutex_unlock(&data->lock); + return ret; + default: + return -EINVAL; + } +} + +static int ltrf216a_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct ltrf216a_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + mutex_lock(&data->lock); + ret = ltrf216a_set_int_time(data, val2); + mutex_unlock(&data->lock); + return ret; + default: + return -EINVAL; + } +} + +static int ltrf216a_read_available(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(ltrf216a_int_time_available) * 2; + *vals = (const int *)ltrf216a_int_time_available; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_info ltrf216a_info = { + .read_raw = ltrf216a_read_raw, + .write_raw = ltrf216a_write_raw, + .read_avail = ltrf216a_read_available, +}; + +static bool ltrf216a_readable_reg(struct device *dev, unsigned int reg) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct ltrf216a_data *data = iio_priv(indio_dev); + + switch (reg) { + case LTRF216A_MAIN_CTRL: + case LTRF216A_ALS_MEAS_RES: + case LTRF216A_ALS_GAIN: + case LTRF216A_PART_ID: + case LTRF216A_MAIN_STATUS: + case LTRF216A_ALS_DATA_0: + case LTRF216A_ALS_DATA_1: + case LTRF216A_ALS_DATA_2: + case LTRF216A_INT_CFG: + case LTRF216A_INT_PST: + case LTRF216A_ALS_THRES_UP_0: + case LTRF216A_ALS_THRES_UP_1: + case LTRF216A_ALS_THRES_UP_2: + case LTRF216A_ALS_THRES_LOW_0: + case LTRF216A_ALS_THRES_LOW_1: + case LTRF216A_ALS_THRES_LOW_2: + return true; + case LTRF216A_ALS_CLEAR_DATA_0: + case LTRF216A_ALS_CLEAR_DATA_1: + case LTRF216A_ALS_CLEAR_DATA_2: + return data->info->has_clear_data; + default: + return false; + } +} + +static bool ltrf216a_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LTRF216A_MAIN_CTRL: + case LTRF216A_ALS_MEAS_RES: + case LTRF216A_ALS_GAIN: + case LTRF216A_INT_CFG: + case LTRF216A_INT_PST: + case LTRF216A_ALS_THRES_UP_0: + case LTRF216A_ALS_THRES_UP_1: + case LTRF216A_ALS_THRES_UP_2: + case LTRF216A_ALS_THRES_LOW_0: + case LTRF216A_ALS_THRES_LOW_1: + case LTRF216A_ALS_THRES_LOW_2: + return true; + default: + return false; + } +} + +static bool ltrf216a_volatile_reg(struct device *dev, unsigned int reg) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct ltrf216a_data *data = iio_priv(indio_dev); + + switch (reg) { + case LTRF216A_MAIN_STATUS: + case LTRF216A_ALS_DATA_0: + case LTRF216A_ALS_DATA_1: + case LTRF216A_ALS_DATA_2: + return true; + /* + * If these registers are not present on a chip (like LTR-308), + * the missing registers are not considered volatile. + */ + case LTRF216A_ALS_CLEAR_DATA_0: + case LTRF216A_ALS_CLEAR_DATA_1: + case LTRF216A_ALS_CLEAR_DATA_2: + return data->info->has_clear_data; + default: + return false; + } +} + +static bool ltrf216a_precious_reg(struct device *dev, unsigned int reg) +{ + return reg == LTRF216A_MAIN_STATUS; +} + +static const struct regmap_config ltrf216a_regmap_config = { + .name = "ltrf216a", + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, + .max_register = LTRF216A_ALS_THRES_LOW_2, + .readable_reg = ltrf216a_readable_reg, + .writeable_reg = ltrf216a_writable_reg, + .volatile_reg = ltrf216a_volatile_reg, + .precious_reg = ltrf216a_precious_reg, + .disable_locking = true, +}; + +static int ltrf216a_probe(struct i2c_client *client) +{ + struct ltrf216a_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + + data->regmap = devm_regmap_init_i2c(client, <rf216a_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(&client->dev, PTR_ERR(data->regmap), + "regmap initialization failed\n"); + + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->info = i2c_get_match_data(client); + + mutex_init(&data->lock); + + indio_dev->info = <rf216a_info; + indio_dev->name = "ltrf216a"; + indio_dev->channels = ltrf216a_channels; + indio_dev->num_channels = ARRAY_SIZE(ltrf216a_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = pm_runtime_set_active(&client->dev); + if (ret) + return ret; + + /* reset sensor, chip fails to respond to this, so ignore any errors */ + ltrf216a_reset(indio_dev); + + ret = regmap_reinit_cache(data->regmap, <rf216a_regmap_config); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to reinit regmap cache\n"); + + ret = ltrf216a_enable(indio_dev); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&client->dev, ltrf216a_cleanup, + indio_dev); + if (ret) + return ret; + + ret = devm_pm_runtime_enable(&client->dev); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to enable runtime PM\n"); + + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + + data->int_time = 100000; + data->int_time_fac = 100; + data->als_gain_fac = 3; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int ltrf216a_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct ltrf216a_data *data = iio_priv(indio_dev); + int ret; + + ret = ltrf216a_disable(indio_dev); + if (ret) + return ret; + + regcache_cache_only(data->regmap, true); + + return 0; +} + +static int ltrf216a_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct ltrf216a_data *data = iio_priv(indio_dev); + int ret; + + regcache_cache_only(data->regmap, false); + regcache_mark_dirty(data->regmap); + ret = regcache_sync(data->regmap); + if (ret) + goto cache_only; + + ret = ltrf216a_enable(indio_dev); + if (ret) + goto cache_only; + + return 0; + +cache_only: + regcache_cache_only(data->regmap, true); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(ltrf216a_pm_ops, ltrf216a_runtime_suspend, + ltrf216a_runtime_resume, NULL); + +static const struct ltr_chip_info ltr308_chip_info = { + .has_clear_data = false, + .lux_multiplier = 60, +}; + +static const struct ltr_chip_info ltrf216a_chip_info = { + .has_clear_data = true, + .lux_multiplier = 45, +}; + +static const struct i2c_device_id ltrf216a_id[] = { + { .name = "ltr308", .driver_data = (kernel_ulong_t)<r308_chip_info }, + { .name = "ltrf216a", .driver_data = (kernel_ulong_t)<rf216a_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltrf216a_id); + +static const struct of_device_id ltrf216a_of_match[] = { + { .compatible = "liteon,ltr308", .data = <r308_chip_info }, + { .compatible = "liteon,ltrf216a", .data = <rf216a_chip_info }, + /* For Valve's Steamdeck device, an ACPI platform using PRP0001 */ + { .compatible = "ltr,ltrf216a", .data = <rf216a_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ltrf216a_of_match); + +static struct i2c_driver ltrf216a_driver = { + .driver = { + .name = "ltrf216a", + .pm = pm_ptr(<rf216a_pm_ops), + .of_match_table = ltrf216a_of_match, + }, + .probe = ltrf216a_probe, + .id_table = ltrf216a_id, +}; +module_i2c_driver(ltrf216a_driver); + +MODULE_AUTHOR("Shreeya Patel <shreeya.patel@collabora.com>"); +MODULE_AUTHOR("Shi Zhigang <Zhigang.Shi@liteon.com>"); +MODULE_DESCRIPTION("LTRF216A ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/lv0104cs.c b/drivers/iio/light/lv0104cs.c new file mode 100644 index 000000000..eba82c334 --- /dev/null +++ b/drivers/iio/light/lv0104cs.c @@ -0,0 +1,529 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * lv0104cs.c: LV0104CS Ambient Light Sensor Driver + * + * Copyright (C) 2018 + * Author: Jeff LaBundy <jeff@labundy.com> + * + * 7-bit I2C slave address: 0x13 + * + * Link to data sheet: https://www.onsemi.com/pub/Collateral/LV0104CS-D.PDF + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define LV0104CS_REGVAL_MEASURE 0xE0 +#define LV0104CS_REGVAL_SLEEP 0x00 + +#define LV0104CS_SCALE_0_25X 0 +#define LV0104CS_SCALE_1X 1 +#define LV0104CS_SCALE_2X 2 +#define LV0104CS_SCALE_8X 3 +#define LV0104CS_SCALE_SHIFT 3 + +#define LV0104CS_INTEG_12_5MS 0 +#define LV0104CS_INTEG_100MS 1 +#define LV0104CS_INTEG_200MS 2 +#define LV0104CS_INTEG_SHIFT 1 + +#define LV0104CS_CALIBSCALE_UNITY 31 + +struct lv0104cs_private { + struct i2c_client *client; + struct mutex lock; + u8 calibscale; + u8 scale; + u8 int_time; +}; + +struct lv0104cs_mapping { + int val; + int val2; + u8 regval; +}; + +static const struct lv0104cs_mapping lv0104cs_calibscales[] = { + { 0, 666666, 0x81 }, + { 0, 800000, 0x82 }, + { 0, 857142, 0x83 }, + { 0, 888888, 0x84 }, + { 0, 909090, 0x85 }, + { 0, 923076, 0x86 }, + { 0, 933333, 0x87 }, + { 0, 941176, 0x88 }, + { 0, 947368, 0x89 }, + { 0, 952380, 0x8A }, + { 0, 956521, 0x8B }, + { 0, 960000, 0x8C }, + { 0, 962962, 0x8D }, + { 0, 965517, 0x8E }, + { 0, 967741, 0x8F }, + { 0, 969696, 0x90 }, + { 0, 971428, 0x91 }, + { 0, 972972, 0x92 }, + { 0, 974358, 0x93 }, + { 0, 975609, 0x94 }, + { 0, 976744, 0x95 }, + { 0, 977777, 0x96 }, + { 0, 978723, 0x97 }, + { 0, 979591, 0x98 }, + { 0, 980392, 0x99 }, + { 0, 981132, 0x9A }, + { 0, 981818, 0x9B }, + { 0, 982456, 0x9C }, + { 0, 983050, 0x9D }, + { 0, 983606, 0x9E }, + { 0, 984126, 0x9F }, + { 1, 0, 0x80 }, + { 1, 16129, 0xBF }, + { 1, 16666, 0xBE }, + { 1, 17241, 0xBD }, + { 1, 17857, 0xBC }, + { 1, 18518, 0xBB }, + { 1, 19230, 0xBA }, + { 1, 20000, 0xB9 }, + { 1, 20833, 0xB8 }, + { 1, 21739, 0xB7 }, + { 1, 22727, 0xB6 }, + { 1, 23809, 0xB5 }, + { 1, 24999, 0xB4 }, + { 1, 26315, 0xB3 }, + { 1, 27777, 0xB2 }, + { 1, 29411, 0xB1 }, + { 1, 31250, 0xB0 }, + { 1, 33333, 0xAF }, + { 1, 35714, 0xAE }, + { 1, 38461, 0xAD }, + { 1, 41666, 0xAC }, + { 1, 45454, 0xAB }, + { 1, 50000, 0xAA }, + { 1, 55555, 0xA9 }, + { 1, 62500, 0xA8 }, + { 1, 71428, 0xA7 }, + { 1, 83333, 0xA6 }, + { 1, 100000, 0xA5 }, + { 1, 125000, 0xA4 }, + { 1, 166666, 0xA3 }, + { 1, 250000, 0xA2 }, + { 1, 500000, 0xA1 }, +}; + +static const struct lv0104cs_mapping lv0104cs_scales[] = { + { 0, 250000, LV0104CS_SCALE_0_25X << LV0104CS_SCALE_SHIFT }, + { 1, 0, LV0104CS_SCALE_1X << LV0104CS_SCALE_SHIFT }, + { 2, 0, LV0104CS_SCALE_2X << LV0104CS_SCALE_SHIFT }, + { 8, 0, LV0104CS_SCALE_8X << LV0104CS_SCALE_SHIFT }, +}; + +static const struct lv0104cs_mapping lv0104cs_int_times[] = { + { 0, 12500, LV0104CS_INTEG_12_5MS << LV0104CS_INTEG_SHIFT }, + { 0, 100000, LV0104CS_INTEG_100MS << LV0104CS_INTEG_SHIFT }, + { 0, 200000, LV0104CS_INTEG_200MS << LV0104CS_INTEG_SHIFT }, +}; + +static int lv0104cs_write_reg(struct i2c_client *client, u8 regval) +{ + int ret; + + ret = i2c_master_send(client, (char *)®val, sizeof(regval)); + if (ret < 0) + return ret; + if (ret != sizeof(regval)) + return -EIO; + + return 0; +} + +static int lv0104cs_read_adc(struct i2c_client *client, u16 *adc_output) +{ + __be16 regval; + int ret; + + ret = i2c_master_recv(client, (char *)®val, sizeof(regval)); + if (ret < 0) + return ret; + if (ret != sizeof(regval)) + return -EIO; + + *adc_output = be16_to_cpu(regval); + + return 0; +} + +static int lv0104cs_get_lux(struct lv0104cs_private *lv0104cs, + int *val, int *val2) +{ + u8 regval = LV0104CS_REGVAL_MEASURE; + u16 adc_output; + int ret; + + regval |= lv0104cs_scales[lv0104cs->scale].regval; + regval |= lv0104cs_int_times[lv0104cs->int_time].regval; + ret = lv0104cs_write_reg(lv0104cs->client, regval); + if (ret) + return ret; + + /* wait for integration time to pass (with margin) */ + switch (lv0104cs->int_time) { + case LV0104CS_INTEG_12_5MS: + msleep(50); + break; + + case LV0104CS_INTEG_100MS: + msleep(150); + break; + + case LV0104CS_INTEG_200MS: + msleep(250); + break; + + default: + return -EINVAL; + } + + ret = lv0104cs_read_adc(lv0104cs->client, &adc_output); + if (ret) + return ret; + + ret = lv0104cs_write_reg(lv0104cs->client, LV0104CS_REGVAL_SLEEP); + if (ret) + return ret; + + /* convert ADC output to lux */ + switch (lv0104cs->scale) { + case LV0104CS_SCALE_0_25X: + *val = adc_output * 4; + *val2 = 0; + return 0; + + case LV0104CS_SCALE_1X: + *val = adc_output; + *val2 = 0; + return 0; + + case LV0104CS_SCALE_2X: + *val = adc_output / 2; + *val2 = (adc_output % 2) * 500000; + return 0; + + case LV0104CS_SCALE_8X: + *val = adc_output / 8; + *val2 = (adc_output % 8) * 125000; + return 0; + + default: + return -EINVAL; + } +} + +static int lv0104cs_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct lv0104cs_private *lv0104cs = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_LIGHT) + return -EINVAL; + + mutex_lock(&lv0104cs->lock); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = lv0104cs_get_lux(lv0104cs, val, val2); + if (ret) + goto err_mutex; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + + case IIO_CHAN_INFO_CALIBSCALE: + *val = lv0104cs_calibscales[lv0104cs->calibscale].val; + *val2 = lv0104cs_calibscales[lv0104cs->calibscale].val2; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + + case IIO_CHAN_INFO_SCALE: + *val = lv0104cs_scales[lv0104cs->scale].val; + *val2 = lv0104cs_scales[lv0104cs->scale].val2; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + + case IIO_CHAN_INFO_INT_TIME: + *val = lv0104cs_int_times[lv0104cs->int_time].val; + *val2 = lv0104cs_int_times[lv0104cs->int_time].val2; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + + default: + ret = -EINVAL; + } + +err_mutex: + mutex_unlock(&lv0104cs->lock); + + return ret; +} + +static int lv0104cs_set_calibscale(struct lv0104cs_private *lv0104cs, + int val, int val2) +{ + int calibscale = val * 1000000 + val2; + int floor, ceil, mid; + int ret, i, index; + + /* round to nearest quantized calibscale (sensitivity) */ + for (i = 0; i < ARRAY_SIZE(lv0104cs_calibscales) - 1; i++) { + floor = lv0104cs_calibscales[i].val * 1000000 + + lv0104cs_calibscales[i].val2; + ceil = lv0104cs_calibscales[i + 1].val * 1000000 + + lv0104cs_calibscales[i + 1].val2; + mid = (floor + ceil) / 2; + + /* round down */ + if (calibscale >= floor && calibscale < mid) { + index = i; + break; + } + + /* round up */ + if (calibscale >= mid && calibscale <= ceil) { + index = i + 1; + break; + } + } + + if (i == ARRAY_SIZE(lv0104cs_calibscales) - 1) + return -EINVAL; + + mutex_lock(&lv0104cs->lock); + + /* set calibscale (sensitivity) */ + ret = lv0104cs_write_reg(lv0104cs->client, + lv0104cs_calibscales[index].regval); + if (ret) + goto err_mutex; + + lv0104cs->calibscale = index; + +err_mutex: + mutex_unlock(&lv0104cs->lock); + + return ret; +} + +static int lv0104cs_set_scale(struct lv0104cs_private *lv0104cs, + int val, int val2) +{ + int i; + + /* hard matching */ + for (i = 0; i < ARRAY_SIZE(lv0104cs_scales); i++) { + if (val != lv0104cs_scales[i].val) + continue; + + if (val2 == lv0104cs_scales[i].val2) + break; + } + + if (i == ARRAY_SIZE(lv0104cs_scales)) + return -EINVAL; + + mutex_lock(&lv0104cs->lock); + lv0104cs->scale = i; + mutex_unlock(&lv0104cs->lock); + + return 0; +} + +static int lv0104cs_set_int_time(struct lv0104cs_private *lv0104cs, + int val, int val2) +{ + int i; + + /* hard matching */ + for (i = 0; i < ARRAY_SIZE(lv0104cs_int_times); i++) { + if (val != lv0104cs_int_times[i].val) + continue; + + if (val2 == lv0104cs_int_times[i].val2) + break; + } + + if (i == ARRAY_SIZE(lv0104cs_int_times)) + return -EINVAL; + + mutex_lock(&lv0104cs->lock); + lv0104cs->int_time = i; + mutex_unlock(&lv0104cs->lock); + + return 0; +} + +static int lv0104cs_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct lv0104cs_private *lv0104cs = iio_priv(indio_dev); + + if (chan->type != IIO_LIGHT) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + return lv0104cs_set_calibscale(lv0104cs, val, val2); + + case IIO_CHAN_INFO_SCALE: + return lv0104cs_set_scale(lv0104cs, val, val2); + + case IIO_CHAN_INFO_INT_TIME: + return lv0104cs_set_int_time(lv0104cs, val, val2); + + default: + return -EINVAL; + } +} + +static ssize_t lv0104cs_show_calibscale_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(lv0104cs_calibscales); i++) { + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ", + lv0104cs_calibscales[i].val, + lv0104cs_calibscales[i].val2); + } + + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t lv0104cs_show_scale_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(lv0104cs_scales); i++) { + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ", + lv0104cs_scales[i].val, + lv0104cs_scales[i].val2); + } + + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t lv0104cs_show_int_time_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(lv0104cs_int_times); i++) { + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ", + lv0104cs_int_times[i].val, + lv0104cs_int_times[i].val2); + } + + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEVICE_ATTR(calibscale_available, 0444, + lv0104cs_show_calibscale_avail, NULL, 0); +static IIO_DEVICE_ATTR(scale_available, 0444, + lv0104cs_show_scale_avail, NULL, 0); +static IIO_DEV_ATTR_INT_TIME_AVAIL(lv0104cs_show_int_time_avail); + +static struct attribute *lv0104cs_attributes[] = { + &iio_dev_attr_calibscale_available.dev_attr.attr, + &iio_dev_attr_scale_available.dev_attr.attr, + &iio_dev_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group lv0104cs_attribute_group = { + .attrs = lv0104cs_attributes, +}; + +static const struct iio_info lv0104cs_info = { + .attrs = &lv0104cs_attribute_group, + .read_raw = &lv0104cs_read_raw, + .write_raw = &lv0104cs_write_raw, +}; + +static const struct iio_chan_spec lv0104cs_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + }, +}; + +static int lv0104cs_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct lv0104cs_private *lv0104cs; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*lv0104cs)); + if (!indio_dev) + return -ENOMEM; + + lv0104cs = iio_priv(indio_dev); + + i2c_set_clientdata(client, lv0104cs); + lv0104cs->client = client; + + mutex_init(&lv0104cs->lock); + + lv0104cs->calibscale = LV0104CS_CALIBSCALE_UNITY; + lv0104cs->scale = LV0104CS_SCALE_1X; + lv0104cs->int_time = LV0104CS_INTEG_200MS; + + ret = lv0104cs_write_reg(lv0104cs->client, + lv0104cs_calibscales[LV0104CS_CALIBSCALE_UNITY].regval); + if (ret) + return ret; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = lv0104cs_channels; + indio_dev->num_channels = ARRAY_SIZE(lv0104cs_channels); + indio_dev->name = client->name; + indio_dev->info = &lv0104cs_info; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id lv0104cs_id[] = { + { .name = "lv0104cs" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lv0104cs_id); + +static struct i2c_driver lv0104cs_i2c_driver = { + .driver = { + .name = "lv0104cs", + }, + .id_table = lv0104cs_id, + .probe = lv0104cs_probe, +}; +module_i2c_driver(lv0104cs_i2c_driver); + +MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>"); +MODULE_DESCRIPTION("LV0104CS Ambient Light Sensor Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/max44000.c b/drivers/iio/light/max44000.c new file mode 100644 index 000000000..8c344f1b3 --- /dev/null +++ b/drivers/iio/light/max44000.c @@ -0,0 +1,624 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MAX44000 Ambient and Infrared Proximity Sensor + * + * Copyright (c) 2016, Intel Corporation. + * + * Data sheet: https://datasheets.maximintegrated.com/en/ds/MAX44000.pdf + * + * 7-bit I2C slave address 0x4a + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/util_macros.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define MAX44000_DRV_NAME "max44000" + +/* Registers in datasheet order */ +#define MAX44000_REG_STATUS 0x00 +#define MAX44000_REG_CFG_MAIN 0x01 +#define MAX44000_REG_CFG_RX 0x02 +#define MAX44000_REG_CFG_TX 0x03 +#define MAX44000_REG_ALS_DATA_HI 0x04 +#define MAX44000_REG_ALS_DATA_LO 0x05 +#define MAX44000_REG_PRX_DATA 0x16 +#define MAX44000_REG_ALS_UPTHR_HI 0x06 +#define MAX44000_REG_ALS_UPTHR_LO 0x07 +#define MAX44000_REG_ALS_LOTHR_HI 0x08 +#define MAX44000_REG_ALS_LOTHR_LO 0x09 +#define MAX44000_REG_PST 0x0a +#define MAX44000_REG_PRX_IND 0x0b +#define MAX44000_REG_PRX_THR 0x0c +#define MAX44000_REG_TRIM_GAIN_GREEN 0x0f +#define MAX44000_REG_TRIM_GAIN_IR 0x10 + +/* REG_CFG bits */ +#define MAX44000_CFG_ALSINTE 0x01 +#define MAX44000_CFG_PRXINTE 0x02 +#define MAX44000_CFG_MASK 0x1c +#define MAX44000_CFG_MODE_SHUTDOWN 0x00 +#define MAX44000_CFG_MODE_ALS_GIR 0x04 +#define MAX44000_CFG_MODE_ALS_G 0x08 +#define MAX44000_CFG_MODE_ALS_IR 0x0c +#define MAX44000_CFG_MODE_ALS_PRX 0x10 +#define MAX44000_CFG_MODE_PRX 0x14 +#define MAX44000_CFG_TRIM 0x20 + +/* + * Upper 4 bits are not documented but start as 1 on powerup + * Setting them to 0 causes proximity to misbehave so set them to 1 + */ +#define MAX44000_REG_CFG_RX_DEFAULT 0xf0 + +/* REG_RX bits */ +#define MAX44000_CFG_RX_ALSTIM_MASK 0x0c +#define MAX44000_CFG_RX_ALSTIM_SHIFT 2 +#define MAX44000_CFG_RX_ALSPGA_MASK 0x03 +#define MAX44000_CFG_RX_ALSPGA_SHIFT 0 + +/* REG_TX bits */ +#define MAX44000_LED_CURRENT_MASK 0xf +#define MAX44000_LED_CURRENT_MAX 11 +#define MAX44000_LED_CURRENT_DEFAULT 6 + +#define MAX44000_ALSDATA_OVERFLOW 0x4000 + +struct max44000_data { + struct mutex lock; + struct regmap *regmap; +}; + +/* Default scale is set to the minimum of 0.03125 or 1 / (1 << 5) lux */ +#define MAX44000_ALS_TO_LUX_DEFAULT_FRACTION_LOG2 5 + +/* Scale can be multiplied by up to 128x via ALSPGA for measurement gain */ +static const int max44000_alspga_shift[] = {0, 2, 4, 7}; +#define MAX44000_ALSPGA_MAX_SHIFT 7 + +/* + * Scale can be multiplied by up to 64x via ALSTIM because of lost resolution + * + * This scaling factor is hidden from userspace and instead accounted for when + * reading raw values from the device. + * + * This makes it possible to cleanly expose ALSPGA as IIO_CHAN_INFO_SCALE and + * ALSTIM as IIO_CHAN_INFO_INT_TIME without the values affecting each other. + * + * Handling this internally is also required for buffer support because the + * channel's scan_type can't be modified dynamically. + */ +#define MAX44000_ALSTIM_SHIFT(alstim) (2 * (alstim)) + +/* Available integration times with pretty manual alignment: */ +static const int max44000_int_time_avail_ns_array[] = { + 100000000, + 25000000, + 6250000, + 1562500, +}; +static const char max44000_int_time_avail_str[] = + "0.100 " + "0.025 " + "0.00625 " + "0.0015625"; + +/* Available scales (internal to ulux) with pretty manual alignment: */ +static const int max44000_scale_avail_ulux_array[] = { + 31250, + 125000, + 500000, + 4000000, +}; +static const char max44000_scale_avail_str[] = + "0.03125 " + "0.125 " + "0.5 " + "4"; + +#define MAX44000_SCAN_INDEX_ALS 0 +#define MAX44000_SCAN_INDEX_PRX 1 + +static const struct iio_chan_spec max44000_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + .scan_index = MAX44000_SCAN_INDEX_ALS, + .scan_type = { + .sign = 'u', + .realbits = 14, + .storagebits = 16, + } + }, + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = MAX44000_SCAN_INDEX_PRX, + .scan_type = { + .sign = 'u', + .realbits = 8, + .storagebits = 16, + } + }, + IIO_CHAN_SOFT_TIMESTAMP(2), + { + .type = IIO_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .extend_name = "led", + .output = 1, + .scan_index = -1, + }, +}; + +static int max44000_read_alstim(struct max44000_data *data) +{ + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, MAX44000_REG_CFG_RX, &val); + if (ret < 0) + return ret; + return (val & MAX44000_CFG_RX_ALSTIM_MASK) >> MAX44000_CFG_RX_ALSTIM_SHIFT; +} + +static int max44000_write_alstim(struct max44000_data *data, int val) +{ + return regmap_write_bits(data->regmap, MAX44000_REG_CFG_RX, + MAX44000_CFG_RX_ALSTIM_MASK, + val << MAX44000_CFG_RX_ALSTIM_SHIFT); +} + +static int max44000_read_alspga(struct max44000_data *data) +{ + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, MAX44000_REG_CFG_RX, &val); + if (ret < 0) + return ret; + return (val & MAX44000_CFG_RX_ALSPGA_MASK) >> MAX44000_CFG_RX_ALSPGA_SHIFT; +} + +static int max44000_write_alspga(struct max44000_data *data, int val) +{ + return regmap_write_bits(data->regmap, MAX44000_REG_CFG_RX, + MAX44000_CFG_RX_ALSPGA_MASK, + val << MAX44000_CFG_RX_ALSPGA_SHIFT); +} + +static int max44000_read_alsval(struct max44000_data *data) +{ + u16 regval; + __be16 val; + int alstim, ret; + + ret = regmap_bulk_read(data->regmap, MAX44000_REG_ALS_DATA_HI, + &val, sizeof(val)); + if (ret < 0) + return ret; + alstim = ret = max44000_read_alstim(data); + if (ret < 0) + return ret; + + regval = be16_to_cpu(val); + + /* + * Overflow is explained on datasheet page 17. + * + * It's a warning that either the G or IR channel has become saturated + * and that the value in the register is likely incorrect. + * + * The recommendation is to change the scale (ALSPGA). + * The driver just returns the max representable value. + */ + if (regval & MAX44000_ALSDATA_OVERFLOW) + return 0x3FFF; + + return regval << MAX44000_ALSTIM_SHIFT(alstim); +} + +static int max44000_write_led_current_raw(struct max44000_data *data, int val) +{ + /* Maybe we should clamp the value instead? */ + if (val < 0 || val > MAX44000_LED_CURRENT_MAX) + return -ERANGE; + if (val >= 8) + val += 4; + return regmap_write_bits(data->regmap, MAX44000_REG_CFG_TX, + MAX44000_LED_CURRENT_MASK, val); +} + +static int max44000_read_led_current_raw(struct max44000_data *data) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, MAX44000_REG_CFG_TX, ®val); + if (ret < 0) + return ret; + regval &= MAX44000_LED_CURRENT_MASK; + if (regval >= 8) + regval -= 4; + return regval; +} + +static int max44000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max44000_data *data = iio_priv(indio_dev); + int alstim, alspga; + unsigned int regval; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + mutex_lock(&data->lock); + ret = max44000_read_alsval(data); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + + case IIO_PROXIMITY: + mutex_lock(&data->lock); + ret = regmap_read(data->regmap, MAX44000_REG_PRX_DATA, ®val); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = regval; + return IIO_VAL_INT; + + case IIO_CURRENT: + mutex_lock(&data->lock); + ret = max44000_read_led_current_raw(data); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_CURRENT: + /* Output register is in 10s of miliamps */ + *val = 10; + return IIO_VAL_INT; + + case IIO_LIGHT: + mutex_lock(&data->lock); + alspga = ret = max44000_read_alspga(data); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + /* Avoid negative shifts */ + *val = (1 << MAX44000_ALSPGA_MAX_SHIFT); + *val2 = MAX44000_ALS_TO_LUX_DEFAULT_FRACTION_LOG2 + + MAX44000_ALSPGA_MAX_SHIFT + - max44000_alspga_shift[alspga]; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_INT_TIME: + mutex_lock(&data->lock); + alstim = ret = max44000_read_alstim(data); + mutex_unlock(&data->lock); + + if (ret < 0) + return ret; + *val = 0; + *val2 = max44000_int_time_avail_ns_array[alstim]; + return IIO_VAL_INT_PLUS_NANO; + + default: + return -EINVAL; + } +} + +static int max44000_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct max44000_data *data = iio_priv(indio_dev); + int ret; + + if (mask == IIO_CHAN_INFO_RAW && chan->type == IIO_CURRENT) { + mutex_lock(&data->lock); + ret = max44000_write_led_current_raw(data, val); + mutex_unlock(&data->lock); + return ret; + } else if (mask == IIO_CHAN_INFO_INT_TIME && chan->type == IIO_LIGHT) { + s64 valns = val * NSEC_PER_SEC + val2; + int alstim = find_closest_descending(valns, + max44000_int_time_avail_ns_array, + ARRAY_SIZE(max44000_int_time_avail_ns_array)); + mutex_lock(&data->lock); + ret = max44000_write_alstim(data, alstim); + mutex_unlock(&data->lock); + return ret; + } else if (mask == IIO_CHAN_INFO_SCALE && chan->type == IIO_LIGHT) { + s64 valus = val * USEC_PER_SEC + val2; + int alspga = find_closest(valus, + max44000_scale_avail_ulux_array, + ARRAY_SIZE(max44000_scale_avail_ulux_array)); + mutex_lock(&data->lock); + ret = max44000_write_alspga(data, alspga); + mutex_unlock(&data->lock); + return ret; + } + + return -EINVAL; +} + +static int max44000_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + if (mask == IIO_CHAN_INFO_INT_TIME && chan->type == IIO_LIGHT) + return IIO_VAL_INT_PLUS_NANO; + else if (mask == IIO_CHAN_INFO_SCALE && chan->type == IIO_LIGHT) + return IIO_VAL_INT_PLUS_MICRO; + else + return IIO_VAL_INT; +} + +static IIO_CONST_ATTR(illuminance_integration_time_available, max44000_int_time_avail_str); +static IIO_CONST_ATTR(illuminance_scale_available, max44000_scale_avail_str); + +static struct attribute *max44000_attributes[] = { + &iio_const_attr_illuminance_integration_time_available.dev_attr.attr, + &iio_const_attr_illuminance_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group max44000_attribute_group = { + .attrs = max44000_attributes, +}; + +static const struct iio_info max44000_info = { + .read_raw = max44000_read_raw, + .write_raw = max44000_write_raw, + .write_raw_get_fmt = max44000_write_raw_get_fmt, + .attrs = &max44000_attribute_group, +}; + +static bool max44000_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX44000_REG_STATUS: + case MAX44000_REG_CFG_MAIN: + case MAX44000_REG_CFG_RX: + case MAX44000_REG_CFG_TX: + case MAX44000_REG_ALS_DATA_HI: + case MAX44000_REG_ALS_DATA_LO: + case MAX44000_REG_PRX_DATA: + case MAX44000_REG_ALS_UPTHR_HI: + case MAX44000_REG_ALS_UPTHR_LO: + case MAX44000_REG_ALS_LOTHR_HI: + case MAX44000_REG_ALS_LOTHR_LO: + case MAX44000_REG_PST: + case MAX44000_REG_PRX_IND: + case MAX44000_REG_PRX_THR: + case MAX44000_REG_TRIM_GAIN_GREEN: + case MAX44000_REG_TRIM_GAIN_IR: + return true; + default: + return false; + } +} + +static bool max44000_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX44000_REG_CFG_MAIN: + case MAX44000_REG_CFG_RX: + case MAX44000_REG_CFG_TX: + case MAX44000_REG_ALS_UPTHR_HI: + case MAX44000_REG_ALS_UPTHR_LO: + case MAX44000_REG_ALS_LOTHR_HI: + case MAX44000_REG_ALS_LOTHR_LO: + case MAX44000_REG_PST: + case MAX44000_REG_PRX_IND: + case MAX44000_REG_PRX_THR: + case MAX44000_REG_TRIM_GAIN_GREEN: + case MAX44000_REG_TRIM_GAIN_IR: + return true; + default: + return false; + } +} + +static bool max44000_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX44000_REG_STATUS: + case MAX44000_REG_ALS_DATA_HI: + case MAX44000_REG_ALS_DATA_LO: + case MAX44000_REG_PRX_DATA: + return true; + default: + return false; + } +} + +static bool max44000_precious_reg(struct device *dev, unsigned int reg) +{ + return reg == MAX44000_REG_STATUS; +} + +static const struct regmap_config max44000_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = MAX44000_REG_PRX_DATA, + .readable_reg = max44000_readable_reg, + .writeable_reg = max44000_writeable_reg, + .volatile_reg = max44000_volatile_reg, + .precious_reg = max44000_precious_reg, + + .use_single_read = true, + .use_single_write = true, + .cache_type = REGCACHE_RBTREE, +}; + +static irqreturn_t max44000_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct max44000_data *data = iio_priv(indio_dev); + int index = 0; + unsigned int regval; + int ret; + struct { + u16 channels[2]; + aligned_s64 ts; + } scan = { }; + + + mutex_lock(&data->lock); + if (test_bit(MAX44000_SCAN_INDEX_ALS, indio_dev->active_scan_mask)) { + ret = max44000_read_alsval(data); + if (ret < 0) + goto out_unlock; + scan.channels[index++] = ret; + } + if (test_bit(MAX44000_SCAN_INDEX_PRX, indio_dev->active_scan_mask)) { + ret = regmap_read(data->regmap, MAX44000_REG_PRX_DATA, ®val); + if (ret < 0) + goto out_unlock; + scan.channels[index] = regval; + } + mutex_unlock(&data->lock); + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; + +out_unlock: + mutex_unlock(&data->lock); + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int max44000_probe(struct i2c_client *client) +{ + struct max44000_data *data; + struct iio_dev *indio_dev; + int ret, reg; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + data = iio_priv(indio_dev); + data->regmap = devm_regmap_init_i2c(client, &max44000_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "regmap_init failed!\n"); + return PTR_ERR(data->regmap); + } + + mutex_init(&data->lock); + indio_dev->info = &max44000_info; + indio_dev->name = MAX44000_DRV_NAME; + indio_dev->channels = max44000_channels; + indio_dev->num_channels = ARRAY_SIZE(max44000_channels); + + /* + * The device doesn't have a reset function so we just clear some + * important bits at probe time to ensure sane operation. + * + * Since we don't support interrupts/events the threshold values are + * not important. We also don't touch trim values. + */ + + /* Reset ALS scaling bits */ + ret = regmap_write(data->regmap, MAX44000_REG_CFG_RX, + MAX44000_REG_CFG_RX_DEFAULT); + if (ret < 0) { + dev_err(&client->dev, "failed to write default CFG_RX: %d\n", + ret); + return ret; + } + + /* + * By default the LED pulse used for the proximity sensor is disabled. + * Set a middle value so that we get some sort of valid data by default. + */ + ret = max44000_write_led_current_raw(data, MAX44000_LED_CURRENT_DEFAULT); + if (ret < 0) { + dev_err(&client->dev, "failed to write init config: %d\n", ret); + return ret; + } + + /* Reset CFG bits to ALS_PRX mode which allows easy reading of both values. */ + reg = MAX44000_CFG_TRIM | MAX44000_CFG_MODE_ALS_PRX; + ret = regmap_write(data->regmap, MAX44000_REG_CFG_MAIN, reg); + if (ret < 0) { + dev_err(&client->dev, "failed to write init config: %d\n", ret); + return ret; + } + + /* Read status at least once to clear any stale interrupt bits. */ + ret = regmap_read(data->regmap, MAX44000_REG_STATUS, ®); + if (ret < 0) { + dev_err(&client->dev, "failed to read init status: %d\n", ret); + return ret; + } + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL, + max44000_trigger_handler, NULL); + if (ret < 0) { + dev_err(&client->dev, "iio triggered buffer setup failed\n"); + return ret; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id max44000_id[] = { + { .name = "max44000" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max44000_id); + +static const struct acpi_device_id max44000_acpi_match[] = { + {"MAX44000", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, max44000_acpi_match); + +static struct i2c_driver max44000_driver = { + .driver = { + .name = MAX44000_DRV_NAME, + .acpi_match_table = max44000_acpi_match, + }, + .probe = max44000_probe, + .id_table = max44000_id, +}; + +module_i2c_driver(max44000_driver); + +MODULE_AUTHOR("Crestez Dan Leonard <leonard.crestez@intel.com>"); +MODULE_DESCRIPTION("MAX44000 Ambient and Infrared Proximity Sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/max44009.c b/drivers/iio/light/max44009.c new file mode 100644 index 000000000..82b8a806c --- /dev/null +++ b/drivers/iio/light/max44009.c @@ -0,0 +1,554 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * max44009.c - Support for MAX44009 Ambient Light Sensor + * + * Copyright (c) 2019 Robert Eshleman <bobbyeshleman@gmail.com> + * + * Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX44009.pdf + * + * TODO: Support continuous mode and configuring from manual mode to + * automatic mode. + * + * Default I2C address: 0x4a + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/bits.h> +#include <linux/i2c.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/util_macros.h> + +#define MAX44009_DRV_NAME "max44009" + +/* Registers in datasheet order */ +#define MAX44009_REG_INT_STATUS 0x0 +#define MAX44009_REG_INT_EN 0x1 +#define MAX44009_REG_CFG 0x2 +#define MAX44009_REG_LUX_HI 0x3 +#define MAX44009_REG_LUX_LO 0x4 +#define MAX44009_REG_UPPER_THR 0x5 +#define MAX44009_REG_LOWER_THR 0x6 +#define MAX44009_REG_THR_TIMER 0x7 + +#define MAX44009_CFG_TIM_MASK GENMASK(2, 0) +#define MAX44009_CFG_MAN_MODE_MASK BIT(6) + +/* The maximum rising threshold for the max44009 */ +#define MAX44009_MAXIMUM_THRESHOLD 7520256 + +#define MAX44009_THRESH_EXP_MASK (0xf << 4) +#define MAX44009_THRESH_EXP_RSHIFT 4 +#define MAX44009_THRESH_MANT_LSHIFT 4 +#define MAX44009_THRESH_MANT_MASK 0xf + +#define MAX44009_UPPER_THR_MINIMUM 15 + +/* The max44009 always scales raw readings by 0.045 and is non-configurable */ +#define MAX44009_SCALE_NUMERATOR 45 +#define MAX44009_SCALE_DENOMINATOR 1000 + +/* The fixed-point fractional multiplier for de-scaling threshold values */ +#define MAX44009_FRACT_MULT 1000000 + +static const u32 max44009_int_time_ns_array[] = { + 800000000, + 400000000, + 200000000, + 100000000, + 50000000, /* Manual mode only */ + 25000000, /* Manual mode only */ + 12500000, /* Manual mode only */ + 6250000, /* Manual mode only */ +}; + +static const char max44009_int_time_str[] = + "0.8 " + "0.4 " + "0.2 " + "0.1 " + "0.05 " + "0.025 " + "0.0125 " + "0.00625"; + +struct max44009_data { + struct i2c_client *client; + struct mutex lock; +}; + +static const struct iio_event_spec max44009_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec max44009_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_INT_TIME), + .event_spec = max44009_event_spec, + .num_event_specs = ARRAY_SIZE(max44009_event_spec), + }, +}; + +static int max44009_read_int_time(struct max44009_data *data) +{ + + int ret = i2c_smbus_read_byte_data(data->client, MAX44009_REG_CFG); + + if (ret < 0) + return ret; + + return max44009_int_time_ns_array[ret & MAX44009_CFG_TIM_MASK]; +} + +static int max44009_write_int_time(struct max44009_data *data, + int val, int val2) +{ + struct i2c_client *client = data->client; + int ret, int_time, config; + s64 ns; + + ns = val * NSEC_PER_SEC + val2; + int_time = find_closest_descending( + ns, + max44009_int_time_ns_array, + ARRAY_SIZE(max44009_int_time_ns_array)); + + ret = i2c_smbus_read_byte_data(client, MAX44009_REG_CFG); + if (ret < 0) + return ret; + + config = ret; + config &= int_time; + + /* + * To set the integration time, the device must also be in manual + * mode. + */ + config |= MAX44009_CFG_MAN_MODE_MASK; + + return i2c_smbus_write_byte_data(client, MAX44009_REG_CFG, config); +} + +static int max44009_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct max44009_data *data = iio_priv(indio_dev); + int ret; + + if (mask == IIO_CHAN_INFO_INT_TIME && chan->type == IIO_LIGHT) { + mutex_lock(&data->lock); + ret = max44009_write_int_time(data, val, val2); + mutex_unlock(&data->lock); + return ret; + } + return -EINVAL; +} + +static int max44009_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + return IIO_VAL_INT_PLUS_NANO; +} + +static int max44009_lux_raw(u8 hi, u8 lo) +{ + int mantissa; + int exponent; + + /* + * The mantissa consists of the low nibble of the Lux High Byte + * and the low nibble of the Lux Low Byte. + */ + mantissa = ((hi & 0xf) << 4) | (lo & 0xf); + + /* The exponent byte is just the upper nibble of the Lux High Byte */ + exponent = (hi >> 4) & 0xf; + + /* + * The exponent value is base 2 to the power of the raw exponent byte. + */ + exponent = 1 << exponent; + + return exponent * mantissa; +} + +#define MAX44009_READ_LUX_XFER_LEN (4) + +static int max44009_read_lux_raw(struct max44009_data *data) +{ + int ret; + u8 hireg = MAX44009_REG_LUX_HI; + u8 loreg = MAX44009_REG_LUX_LO; + u8 lo = 0; + u8 hi = 0; + + struct i2c_msg msgs[] = { + { + .addr = data->client->addr, + .flags = 0, + .len = sizeof(hireg), + .buf = &hireg, + }, + { + .addr = data->client->addr, + .flags = I2C_M_RD, + .len = sizeof(hi), + .buf = &hi, + }, + { + .addr = data->client->addr, + .flags = 0, + .len = sizeof(loreg), + .buf = &loreg, + }, + { + .addr = data->client->addr, + .flags = I2C_M_RD, + .len = sizeof(lo), + .buf = &lo, + } + }; + + /* + * Use i2c_transfer instead of smbus read because i2c_transfer + * does NOT use a stop bit between address write and data read. + * Using a stop bit causes disjoint upper/lower byte reads and + * reduces accuracy. + */ + ret = i2c_transfer(data->client->adapter, + msgs, MAX44009_READ_LUX_XFER_LEN); + + if (ret != MAX44009_READ_LUX_XFER_LEN) + return -EIO; + + return max44009_lux_raw(hi, lo); +} + +static int max44009_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct max44009_data *data = iio_priv(indio_dev); + int lux_raw; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + ret = max44009_read_lux_raw(data); + if (ret < 0) + return ret; + lux_raw = ret; + + *val = lux_raw * MAX44009_SCALE_NUMERATOR; + *val2 = MAX44009_SCALE_DENOMINATOR; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_LIGHT: + ret = max44009_read_int_time(data); + if (ret < 0) + return ret; + + *val2 = ret; + *val = 0; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static IIO_CONST_ATTR(illuminance_integration_time_available, + max44009_int_time_str); + +static struct attribute *max44009_attributes[] = { + &iio_const_attr_illuminance_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group max44009_attribute_group = { + .attrs = max44009_attributes, +}; + +static int max44009_threshold_byte_from_fraction(int integral, int fractional) +{ + int mantissa, exp; + + if ((integral <= 0 && fractional <= 0) || + integral > MAX44009_MAXIMUM_THRESHOLD || + (integral == MAX44009_MAXIMUM_THRESHOLD && fractional != 0)) + return -EINVAL; + + /* Reverse scaling of fixed-point integral */ + mantissa = integral * MAX44009_SCALE_DENOMINATOR; + mantissa /= MAX44009_SCALE_NUMERATOR; + + /* Reverse scaling of fixed-point fractional */ + mantissa += fractional / MAX44009_FRACT_MULT * + (MAX44009_SCALE_DENOMINATOR / MAX44009_SCALE_NUMERATOR); + + for (exp = 0; mantissa > 0xff; exp++) + mantissa >>= 1; + + mantissa >>= 4; + mantissa &= 0xf; + exp <<= 4; + + return exp | mantissa; +} + +static int max44009_get_thr_reg(enum iio_event_direction dir) +{ + switch (dir) { + case IIO_EV_DIR_RISING: + return MAX44009_REG_UPPER_THR; + case IIO_EV_DIR_FALLING: + return MAX44009_REG_LOWER_THR; + default: + return -EINVAL; + } +} + +static int max44009_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct max44009_data *data = iio_priv(indio_dev); + int reg, threshold; + + if (info != IIO_EV_INFO_VALUE || chan->type != IIO_LIGHT) + return -EINVAL; + + threshold = max44009_threshold_byte_from_fraction(val, val2); + if (threshold < 0) + return threshold; + + reg = max44009_get_thr_reg(dir); + if (reg < 0) + return reg; + + return i2c_smbus_write_byte_data(data->client, reg, threshold); +} + +static int max44009_read_threshold(struct iio_dev *indio_dev, + enum iio_event_direction dir) +{ + struct max44009_data *data = iio_priv(indio_dev); + int byte, reg; + int mantissa, exponent; + + reg = max44009_get_thr_reg(dir); + if (reg < 0) + return reg; + + byte = i2c_smbus_read_byte_data(data->client, reg); + if (byte < 0) + return byte; + + mantissa = byte & MAX44009_THRESH_MANT_MASK; + mantissa <<= MAX44009_THRESH_MANT_LSHIFT; + + /* + * To get the upper threshold, always adds the minimum upper threshold + * value to the shifted byte value (see datasheet). + */ + if (dir == IIO_EV_DIR_RISING) + mantissa += MAX44009_UPPER_THR_MINIMUM; + + /* + * Exponent is base 2 to the power of the threshold exponent byte + * value + */ + exponent = byte & MAX44009_THRESH_EXP_MASK; + exponent >>= MAX44009_THRESH_EXP_RSHIFT; + + return (1 << exponent) * mantissa; +} + +static int max44009_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + int ret; + int threshold; + + if (chan->type != IIO_LIGHT || type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + ret = max44009_read_threshold(indio_dev, dir); + if (ret < 0) + return ret; + threshold = ret; + + *val = threshold * MAX44009_SCALE_NUMERATOR; + *val2 = MAX44009_SCALE_DENOMINATOR; + + return IIO_VAL_FRACTIONAL; +} + +static int max44009_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct max44009_data *data = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_LIGHT || type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + ret = i2c_smbus_write_byte_data(data->client, + MAX44009_REG_INT_EN, state); + if (ret < 0) + return ret; + + /* + * Set device to trigger interrupt immediately upon exceeding + * the threshold limit. + */ + return i2c_smbus_write_byte_data(data->client, + MAX44009_REG_THR_TIMER, 0); +} + +static int max44009_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct max44009_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_LIGHT || type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + return i2c_smbus_read_byte_data(data->client, MAX44009_REG_INT_EN); +} + +static const struct iio_info max44009_info = { + .read_raw = max44009_read_raw, + .write_raw = max44009_write_raw, + .write_raw_get_fmt = max44009_write_raw_get_fmt, + .read_event_value = max44009_read_event_value, + .read_event_config = max44009_read_event_config, + .write_event_value = max44009_write_event_value, + .write_event_config = max44009_write_event_config, + .attrs = &max44009_attribute_group, +}; + +static irqreturn_t max44009_threaded_irq_handler(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct max44009_data *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_byte_data(data->client, MAX44009_REG_INT_STATUS); + if (ret) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int max44009_probe(struct i2c_client *client) +{ + struct max44009_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + indio_dev->info = &max44009_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = MAX44009_DRV_NAME; + indio_dev->channels = max44009_channels; + indio_dev->num_channels = ARRAY_SIZE(max44009_channels); + mutex_init(&data->lock); + + /* Clear any stale interrupt bit */ + ret = i2c_smbus_read_byte_data(client, MAX44009_REG_CFG); + if (ret < 0) + return ret; + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, + max44009_threaded_irq_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT | IRQF_SHARED, + "max44009_event", + indio_dev); + if (ret < 0) + return ret; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct of_device_id max44009_of_match[] = { + { .compatible = "maxim,max44009" }, + { } +}; +MODULE_DEVICE_TABLE(of, max44009_of_match); + +static const struct i2c_device_id max44009_id[] = { + { .name = "max44009" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max44009_id); + +static struct i2c_driver max44009_driver = { + .driver = { + .name = MAX44009_DRV_NAME, + .of_match_table = max44009_of_match, + }, + .probe = max44009_probe, + .id_table = max44009_id, +}; +module_i2c_driver(max44009_driver); + +MODULE_AUTHOR("Robert Eshleman <bobbyeshleman@gmail.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MAX44009 ambient light sensor driver"); diff --git a/drivers/iio/light/noa1305.c b/drivers/iio/light/noa1305.c new file mode 100644 index 000000000..6731f1d9e --- /dev/null +++ b/drivers/iio/light/noa1305.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Support for ON Semiconductor NOA1305 ambient light sensor + * + * Copyright (C) 2016 Emcraft Systems + * Copyright (C) 2019 Collabora Ltd. + */ + +#include <linux/delay.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> +#include <linux/regulator/consumer.h> + +#define NOA1305_REG_POWER_CONTROL 0x0 +#define NOA1305_POWER_CONTROL_DOWN 0x00 +#define NOA1305_POWER_CONTROL_ON 0x08 +#define NOA1305_REG_RESET 0x1 +#define NOA1305_RESET_RESET 0x10 +#define NOA1305_REG_INTEGRATION_TIME 0x2 +#define NOA1305_INTEGR_TIME_800MS 0x00 +#define NOA1305_INTEGR_TIME_400MS 0x01 +#define NOA1305_INTEGR_TIME_200MS 0x02 +#define NOA1305_INTEGR_TIME_100MS 0x03 +#define NOA1305_INTEGR_TIME_50MS 0x04 +#define NOA1305_INTEGR_TIME_25MS 0x05 +#define NOA1305_INTEGR_TIME_12_5MS 0x06 +#define NOA1305_INTEGR_TIME_6_25MS 0x07 +#define NOA1305_INTEGR_TIME_MASK 0x07 +#define NOA1305_REG_INT_SELECT 0x3 +#define NOA1305_INT_SEL_ACTIVE_HIGH 0x01 +#define NOA1305_INT_SEL_ACTIVE_LOW 0x02 +#define NOA1305_INT_SEL_INACTIVE 0x03 +#define NOA1305_REG_INT_THRESH_LSB 0x4 +#define NOA1305_REG_INT_THRESH_MSB 0x5 +#define NOA1305_REG_ALS_DATA_LSB 0x6 +#define NOA1305_REG_ALS_DATA_MSB 0x7 +#define NOA1305_REG_DEVICE_ID_LSB 0x8 +#define NOA1305_REG_DEVICE_ID_MSB 0x9 + +#define NOA1305_DEVICE_ID 0x0519 +#define NOA1305_DRIVER_NAME "noa1305" + +static int noa1305_scale_available[] = { + 100, 8 * 77, /* 800 ms */ + 100, 4 * 77, /* 400 ms */ + 100, 2 * 77, /* 200 ms */ + 100, 1 * 77, /* 100 ms */ + 1000, 5 * 77, /* 50 ms */ + 10000, 25 * 77, /* 25 ms */ + 100000, 125 * 77, /* 12.5 ms */ + 1000000, 625 * 77, /* 6.25 ms */ +}; + +static int noa1305_int_time_available[] = { + 0, 800000, /* 800 ms */ + 0, 400000, /* 400 ms */ + 0, 200000, /* 200 ms */ + 0, 100000, /* 100 ms */ + 0, 50000, /* 50 ms */ + 0, 25000, /* 25 ms */ + 0, 12500, /* 12.5 ms */ + 0, 6250, /* 6.25 ms */ +}; + +struct noa1305_priv { + struct i2c_client *client; + struct regmap *regmap; +}; + +static int noa1305_measure(struct noa1305_priv *priv, int *val) +{ + __le16 data; + int ret; + + ret = regmap_bulk_read(priv->regmap, NOA1305_REG_ALS_DATA_LSB, &data, + 2); + if (ret < 0) + return ret; + + *val = le16_to_cpu(data); + + return IIO_VAL_INT; +} + +static int noa1305_scale(struct noa1305_priv *priv, int *val, int *val2) +{ + int data; + int ret; + + ret = regmap_read(priv->regmap, NOA1305_REG_INTEGRATION_TIME, &data); + if (ret < 0) + return ret; + + /* + * Lux = count / (<Integration Constant> * <Integration Time>) + * + * Integration Constant = 7.7 + * Integration Time in Seconds + */ + data &= NOA1305_INTEGR_TIME_MASK; + *val = noa1305_scale_available[2 * data + 0]; + *val2 = noa1305_scale_available[2 * data + 1]; + + return IIO_VAL_FRACTIONAL; +} + +static int noa1305_int_time(struct noa1305_priv *priv, int *val, int *val2) +{ + int data; + int ret; + + ret = regmap_read(priv->regmap, NOA1305_REG_INTEGRATION_TIME, &data); + if (ret < 0) + return ret; + + data &= NOA1305_INTEGR_TIME_MASK; + *val = noa1305_int_time_available[2 * data + 0]; + *val2 = noa1305_int_time_available[2 * data + 1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static const struct iio_chan_spec noa1305_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), + } +}; + +static int noa1305_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, + int *length, long mask) +{ + if (chan->type != IIO_LIGHT) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = noa1305_scale_available; + *length = ARRAY_SIZE(noa1305_scale_available); + *type = IIO_VAL_FRACTIONAL; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_INT_TIME: + *vals = noa1305_int_time_available; + *length = ARRAY_SIZE(noa1305_int_time_available); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int noa1305_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct noa1305_priv *priv = iio_priv(indio_dev); + + if (chan->type != IIO_LIGHT) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return noa1305_measure(priv, val); + case IIO_CHAN_INFO_SCALE: + return noa1305_scale(priv, val, val2); + case IIO_CHAN_INFO_INT_TIME: + return noa1305_int_time(priv, val, val2); + default: + return -EINVAL; + } +} + +static int noa1305_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct noa1305_priv *priv = iio_priv(indio_dev); + int i; + + if (chan->type != IIO_LIGHT) + return -EINVAL; + + if (mask != IIO_CHAN_INFO_INT_TIME) + return -EINVAL; + + if (val) /* >= 1s integration time not supported */ + return -EINVAL; + + /* Look up integration time register settings and write it if found. */ + for (i = 0; i < ARRAY_SIZE(noa1305_int_time_available) / 2; i++) + if (noa1305_int_time_available[2 * i + 1] == val2) + return regmap_write(priv->regmap, NOA1305_REG_INTEGRATION_TIME, i); + + return -EINVAL; +} + +static const struct iio_info noa1305_info = { + .read_avail = noa1305_read_avail, + .read_raw = noa1305_read_raw, + .write_raw = noa1305_write_raw, +}; + +static bool noa1305_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NOA1305_REG_POWER_CONTROL: + case NOA1305_REG_RESET: + case NOA1305_REG_INTEGRATION_TIME: + case NOA1305_REG_INT_SELECT: + case NOA1305_REG_INT_THRESH_LSB: + case NOA1305_REG_INT_THRESH_MSB: + return true; + default: + return false; + } +} + +static const struct regmap_config noa1305_regmap_config = { + .name = NOA1305_DRIVER_NAME, + .reg_bits = 8, + .val_bits = 8, + .max_register = NOA1305_REG_DEVICE_ID_MSB, + .writeable_reg = noa1305_writable_reg, +}; + +static int noa1305_probe(struct i2c_client *client) +{ + struct noa1305_priv *priv; + struct iio_dev *indio_dev; + struct regmap *regmap; + __le16 data; + unsigned int dev_id; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &noa1305_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Regmap initialization failed.\n"); + return PTR_ERR(regmap); + } + + priv = iio_priv(indio_dev); + + ret = devm_regulator_get_enable(&client->dev, "vin"); + if (ret) + return dev_err_probe(&client->dev, ret, + "get regulator vin failed\n"); + + i2c_set_clientdata(client, indio_dev); + priv->client = client; + priv->regmap = regmap; + + ret = regmap_bulk_read(regmap, NOA1305_REG_DEVICE_ID_LSB, &data, 2); + if (ret < 0) { + dev_err(&client->dev, "ID reading failed: %d\n", ret); + return ret; + } + + dev_id = le16_to_cpu(data); + if (dev_id != NOA1305_DEVICE_ID) { + dev_err(&client->dev, "Unknown device ID: 0x%x\n", dev_id); + return -ENODEV; + } + + ret = regmap_write(regmap, NOA1305_REG_POWER_CONTROL, + NOA1305_POWER_CONTROL_ON); + if (ret < 0) { + dev_err(&client->dev, "Enabling power control failed\n"); + return ret; + } + + ret = regmap_write(regmap, NOA1305_REG_RESET, NOA1305_RESET_RESET); + if (ret < 0) { + dev_err(&client->dev, "Device reset failed\n"); + return ret; + } + + ret = regmap_write(regmap, NOA1305_REG_INTEGRATION_TIME, + NOA1305_INTEGR_TIME_800MS); + if (ret < 0) { + dev_err(&client->dev, "Setting integration time failed\n"); + return ret; + } + + indio_dev->info = &noa1305_info; + indio_dev->channels = noa1305_channels; + indio_dev->num_channels = ARRAY_SIZE(noa1305_channels); + indio_dev->name = NOA1305_DRIVER_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_device_register(&client->dev, indio_dev); + if (ret) + dev_err(&client->dev, "registering device failed\n"); + + return ret; +} + +static const struct of_device_id noa1305_of_match[] = { + { .compatible = "onnn,noa1305" }, + { } +}; +MODULE_DEVICE_TABLE(of, noa1305_of_match); + +static const struct i2c_device_id noa1305_ids[] = { + { .name = "noa1305" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, noa1305_ids); + +static struct i2c_driver noa1305_driver = { + .driver = { + .name = NOA1305_DRIVER_NAME, + .of_match_table = noa1305_of_match, + }, + .probe = noa1305_probe, + .id_table = noa1305_ids, +}; + +module_i2c_driver(noa1305_driver); + +MODULE_AUTHOR("Sergei Miroshnichenko <sergeimir@emcraft.com>"); +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.com"); +MODULE_DESCRIPTION("ON Semiconductor NOA1305 ambient light sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/opt3001.c b/drivers/iio/light/opt3001.c new file mode 100644 index 000000000..a94835301 --- /dev/null +++ b/drivers/iio/light/opt3001.c @@ -0,0 +1,981 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * opt3001.c - Texas Instruments OPT3001 Light Sensor + * + * Copyright (C) 2014 Texas Instruments Incorporated - https://www.ti.com + * + * Author: Andreas Dannenberg <dannenberg@ti.com> + * Based on previous work from: Felipe Balbi <balbi@ti.com> + */ + +#include <linux/array_size.h> +#include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/types.h> +#include <linux/wait.h> + +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define OPT3001_RESULT 0x00 +#define OPT3001_CONFIGURATION 0x01 +#define OPT3001_LOW_LIMIT 0x02 +#define OPT3001_HIGH_LIMIT 0x03 +#define OPT3001_MANUFACTURER_ID 0x7e +#define OPT3001_DEVICE_ID 0x7f + +#define OPT3001_CONFIGURATION_RN_MASK GENMASK(15, 12) +#define OPT3001_CONFIGURATION_RN_AUTO (0xc << 12) + +#define OPT3001_CONFIGURATION_CT BIT(11) + +#define OPT3001_CONFIGURATION_M_MASK GENMASK(10, 9) +#define OPT3001_CONFIGURATION_M_SHUTDOWN (0 << 9) +#define OPT3001_CONFIGURATION_M_SINGLE (1 << 9) +#define OPT3001_CONFIGURATION_M_CONTINUOUS (2 << 9) /* also 3 << 9 */ + +#define OPT3001_CONFIGURATION_CRF BIT(7) +#define OPT3001_CONFIGURATION_FH BIT(6) +#define OPT3001_CONFIGURATION_FL BIT(5) +#define OPT3001_CONFIGURATION_L BIT(4) +#define OPT3001_CONFIGURATION_POL BIT(3) +#define OPT3001_CONFIGURATION_ME BIT(2) + +#define OPT3001_CONFIGURATION_FC_MASK GENMASK(1, 0) + +/* The end-of-conversion enable is located in the low-limit register */ +#define OPT3001_LOW_LIMIT_EOC_ENABLE 0xc000 + +#define OPT3001_REG_EXPONENT(n) ((n) >> 12) +#define OPT3001_REG_MANTISSA(n) ((n) & 0xfff) + +#define OPT3001_INT_TIME_LONG 800000 +#define OPT3001_INT_TIME_SHORT 100000 + +/* + * Time to wait for conversion result to be ready. The device datasheet + * sect. 6.5 states results are ready after total integration time plus 3ms. + * This results in worst-case max values of 113ms or 883ms, respectively. + * Add some slack to be on the safe side. + */ +#define OPT3001_RESULT_READY_SHORT 150 +#define OPT3001_RESULT_READY_LONG 1000 + +struct opt3001_scale { + int val; + int val2; +}; + +struct opt3001_chip_info { + const struct iio_chan_spec (*channels)[2]; + enum iio_chan_type chan_type; + int num_channels; + + const struct opt3001_scale (*scales)[12]; + /* + * Factor as specified by conversion equation in datasheet. + * eg. 0.01 (scaled to integer 10) for opt3001. + */ + int factor_whole; + /* + * Factor to compensate for potentially scaled factor_whole. + */ + int factor_integer; + /* + * Factor used to align decimal part of processed value to six decimal + * places. + */ + int factor_decimal; + + bool has_id; +}; + +struct opt3001 { + struct i2c_client *client; + + /* + * Ensure data capture and read-modify-write sequences are + * not interrupted. + */ + struct mutex lock; + + /* Allows for IRQs to bypass locking mechanism */ + bool ok_to_ignore_lock; + bool result_ready; + wait_queue_head_t result_ready_queue; + u16 result; + const struct opt3001_chip_info *chip_info; + + u32 int_time; + u32 mode; + + u16 high_thresh_mantissa; + u16 low_thresh_mantissa; + + u8 high_thresh_exp; + u8 low_thresh_exp; + + bool use_irq; +}; + +static const struct opt3001_scale opt3001_scales[] = { + { + .val = 40, + .val2 = 950000, + }, + { + .val = 81, + .val2 = 900000, + }, + { + .val = 163, + .val2 = 800000, + }, + { + .val = 327, + .val2 = 600000, + }, + { + .val = 655, + .val2 = 200000, + }, + { + .val = 1310, + .val2 = 400000, + }, + { + .val = 2620, + .val2 = 800000, + }, + { + .val = 5241, + .val2 = 600000, + }, + { + .val = 10483, + .val2 = 200000, + }, + { + .val = 20966, + .val2 = 400000, + }, + { + .val = 41932, + .val2 = 800000, + }, + { + .val = 83865, + .val2 = 600000, + }, +}; + +static const struct opt3001_scale opt3002_scales[] = { + { + .val = 4914, + .val2 = 0, + }, + { + .val = 9828, + .val2 = 0, + }, + { + .val = 19656, + .val2 = 0, + }, + { + .val = 39312, + .val2 = 0, + }, + { + .val = 78624, + .val2 = 0, + }, + { + .val = 157248, + .val2 = 0, + }, + { + .val = 314496, + .val2 = 0, + }, + { + .val = 628992, + .val2 = 0, + }, + { + .val = 1257984, + .val2 = 0, + }, + { + .val = 2515968, + .val2 = 0, + }, + { + .val = 5031936, + .val2 = 0, + }, + { + .val = 10063872, + .val2 = 0, + }, +}; + +static int opt3001_find_scale(const struct opt3001 *opt, int val, int val2, + u8 *exponent) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(*opt->chip_info->scales); i++) { + const struct opt3001_scale *scale = &(*opt->chip_info->scales)[i]; + /* + * Compare the integer and micro parts to determine value scale. + */ + if (val < scale->val || + (val == scale->val && val2 <= scale->val2)) { + *exponent = i; + return 0; + } + } + + return -EINVAL; +} + +static void opt3001_to_iio_ret(struct opt3001 *opt, u8 exponent, u16 mantissa, + int *val, int *val2) +{ + int ret; + int whole = opt->chip_info->factor_whole; + int integer = opt->chip_info->factor_integer; + int decimal = opt->chip_info->factor_decimal; + + ret = whole * (mantissa << exponent); + *val = ret / integer; + *val2 = (ret - (*val * integer)) * decimal; +} + +static void opt3001_set_mode(struct opt3001 *opt, u16 *reg, u16 mode) +{ + *reg &= ~OPT3001_CONFIGURATION_M_MASK; + *reg |= mode; + opt->mode = mode; +} + +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.1 0.8"); + +static struct attribute *opt3001_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group opt3001_attribute_group = { + .attrs = opt3001_attributes, +}; + +static const struct iio_event_spec opt3001_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec opt3001_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_INT_TIME), + .event_spec = opt3001_event_spec, + .num_event_specs = ARRAY_SIZE(opt3001_event_spec), + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct iio_chan_spec opt3002_channels[] = { + { + .type = IIO_INTENSITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME), + .event_spec = opt3001_event_spec, + .num_event_specs = ARRAY_SIZE(opt3001_event_spec), + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static int opt3001_start_conversion(struct opt3001 *opt) +{ + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + u16 reg; + int ret; + + /* Reset data-ready indicator flag */ + opt->result_ready = false; + + /* Configure for single-conversion mode and start a new conversion */ + ret = i2c_smbus_read_word_swapped(client, OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + return ret; + } + + reg = ret; + opt3001_set_mode(opt, ®, OPT3001_CONFIGURATION_M_SINGLE); + + ret = i2c_smbus_write_word_swapped(client, OPT3001_CONFIGURATION, reg); + if (ret) + dev_err(dev, "failed to write register %02x\n", + OPT3001_CONFIGURATION); + + return ret; +} + +static int opt3001_get_processed_irq(struct opt3001 *opt) +{ + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + u16 value; + int ret; + + /* + * Enable the end-of-conversion interrupt mechanism. Note that doing so + * will overwrite the low-level limit value however we will restore this + * value later on. + */ + ret = i2c_smbus_write_word_swapped(client, + OPT3001_LOW_LIMIT, + OPT3001_LOW_LIMIT_EOC_ENABLE); + if (ret < 0) { + dev_err(dev, "failed to write register %02x\n", + OPT3001_LOW_LIMIT); + return ret; + } + + /* Allow IRQ to access the device despite lock being set */ + opt->ok_to_ignore_lock = true; + + ret = opt3001_start_conversion(opt); + if (ret) + goto err; + + if (wait_event_timeout(opt->result_ready_queue, opt->result_ready, + msecs_to_jiffies(OPT3001_RESULT_READY_LONG))) + ret = 0; + else + ret = -ETIMEDOUT; + +err: + opt->ok_to_ignore_lock = false; + + if (ret < 0) + return ret; + + /* + * Disable the end-of-conversion interrupt mechanism by restoring the + * low-level limit value (clearing OPT3001_LOW_LIMIT_EOC_ENABLE). Note + * that selectively clearing those enable bits would affect the actual + * limit value due to bit-overlap and therefore can't be done. + */ + value = (opt->low_thresh_exp << 12) | opt->low_thresh_mantissa; + ret = i2c_smbus_write_word_swapped(client, OPT3001_LOW_LIMIT, value); + if (ret) + dev_err(dev, "failed to write register %02x\n", + OPT3001_LOW_LIMIT); + + return ret; +} + +static int opt3001_get_processed_noirq(struct opt3001 *opt) +{ + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + int ret; + + ret = opt3001_start_conversion(opt); + if (ret) + return ret; + + if (opt->int_time == OPT3001_INT_TIME_SHORT) + msleep(OPT3001_RESULT_READY_SHORT); + else + msleep(OPT3001_RESULT_READY_LONG); + + /* Check result ready flag */ + ret = i2c_smbus_read_word_swapped(client, OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + return ret; + } + + if (!(ret & OPT3001_CONFIGURATION_CRF)) + return -ETIMEDOUT; + + /* Obtain value */ + ret = i2c_smbus_read_word_swapped(client, OPT3001_RESULT); + if (ret < 0) { + dev_err(dev, "failed to read register %02x\n", + OPT3001_RESULT); + return ret; + } + + opt->result = ret; + opt->result_ready = true; + + return 0; +} + +static int opt3001_get_processed(struct opt3001 *opt, int *val, int *val2) +{ + u16 mantissa; + u8 exponent; + int ret; + + if (opt->use_irq) + ret = opt3001_get_processed_irq(opt); + else + ret = opt3001_get_processed_noirq(opt); + if (ret) + return ret; + + exponent = OPT3001_REG_EXPONENT(opt->result); + mantissa = OPT3001_REG_MANTISSA(opt->result); + + opt3001_to_iio_ret(opt, exponent, mantissa, val, val2); + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int opt3001_get_int_time(struct opt3001 *opt, int *val, int *val2) +{ + *val = 0; + *val2 = opt->int_time; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int opt3001_set_int_time(struct opt3001 *opt, int time) +{ + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + int ret; + u16 reg; + + ret = i2c_smbus_read_word_swapped(client, OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + return ret; + } + + reg = ret; + + switch (time) { + case OPT3001_INT_TIME_SHORT: + reg &= ~OPT3001_CONFIGURATION_CT; + opt->int_time = OPT3001_INT_TIME_SHORT; + break; + case OPT3001_INT_TIME_LONG: + reg |= OPT3001_CONFIGURATION_CT; + opt->int_time = OPT3001_INT_TIME_LONG; + break; + default: + return -EINVAL; + } + + return i2c_smbus_write_word_swapped(client, OPT3001_CONFIGURATION, reg); +} + +static int opt3001_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct opt3001 *opt = iio_priv(iio); + + if (opt->mode == OPT3001_CONFIGURATION_M_CONTINUOUS) + return -EBUSY; + + if (chan->type != opt->chip_info->chan_type) + return -EINVAL; + + guard(mutex)(&opt->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + return opt3001_get_processed(opt, val, val2); + case IIO_CHAN_INFO_INT_TIME: + return opt3001_get_int_time(opt, val, val2); + default: + return -EINVAL; + } +} + +static int opt3001_write_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct opt3001 *opt = iio_priv(iio); + + if (opt->mode == OPT3001_CONFIGURATION_M_CONTINUOUS) + return -EBUSY; + + if (chan->type != opt->chip_info->chan_type) + return -EINVAL; + + if (mask != IIO_CHAN_INFO_INT_TIME) + return -EINVAL; + + if (val != 0) + return -EINVAL; + + guard(mutex)(&opt->lock); + + return opt3001_set_int_time(opt, val2); +} + +static int opt3001_read_event_value(struct iio_dev *iio, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct opt3001 *opt = iio_priv(iio); + + guard(mutex)(&opt->lock); + + switch (dir) { + case IIO_EV_DIR_RISING: + opt3001_to_iio_ret(opt, opt->high_thresh_exp, + opt->high_thresh_mantissa, val, val2); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_EV_DIR_FALLING: + opt3001_to_iio_ret(opt, opt->low_thresh_exp, + opt->low_thresh_mantissa, val, val2); + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int opt3001_write_event_value(struct iio_dev *iio, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct opt3001 *opt = iio_priv(iio); + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + int ret; + int whole; + int integer; + int decimal; + + u16 mantissa; + u16 value; + u16 reg; + + u8 exponent; + + if (val < 0) + return -EINVAL; + + guard(mutex)(&opt->lock); + + ret = opt3001_find_scale(opt, val, val2, &exponent); + if (ret < 0) { + dev_err(dev, "can't find scale for %d.%06u\n", val, val2); + return ret; + } + + whole = opt->chip_info->factor_whole; + integer = opt->chip_info->factor_integer; + decimal = opt->chip_info->factor_decimal; + + mantissa = (((val * integer) + (val2 / decimal)) / whole) >> exponent; + + value = (exponent << 12) | mantissa; + + switch (dir) { + case IIO_EV_DIR_RISING: + reg = OPT3001_HIGH_LIMIT; + opt->high_thresh_mantissa = mantissa; + opt->high_thresh_exp = exponent; + break; + case IIO_EV_DIR_FALLING: + reg = OPT3001_LOW_LIMIT; + opt->low_thresh_mantissa = mantissa; + opt->low_thresh_exp = exponent; + break; + default: + return -EINVAL; + } + + ret = i2c_smbus_write_word_swapped(client, reg, value); + if (ret < 0) { + dev_err(dev, "failed to write register %02x\n", reg); + return ret; + } + + return 0; +} + +static int opt3001_read_event_config(struct iio_dev *iio, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct opt3001 *opt = iio_priv(iio); + + return opt->mode == OPT3001_CONFIGURATION_M_CONTINUOUS; +} + +static int opt3001_write_event_config(struct iio_dev *iio, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct opt3001 *opt = iio_priv(iio); + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + int ret; + u16 mode; + u16 reg; + + if (state && opt->mode == OPT3001_CONFIGURATION_M_CONTINUOUS) + return 0; + + if (!state && opt->mode == OPT3001_CONFIGURATION_M_SHUTDOWN) + return 0; + + guard(mutex)(&opt->lock); + + mode = state ? OPT3001_CONFIGURATION_M_CONTINUOUS + : OPT3001_CONFIGURATION_M_SHUTDOWN; + + ret = i2c_smbus_read_word_swapped(client, OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + return ret; + } + + reg = ret; + opt3001_set_mode(opt, ®, mode); + + ret = i2c_smbus_write_word_swapped(client, OPT3001_CONFIGURATION, reg); + if (ret < 0) { + dev_err(dev, "failed to write register %02x\n", + OPT3001_CONFIGURATION); + return ret; + } + + return 0; +} + +static const struct iio_info opt3001_info = { + .attrs = &opt3001_attribute_group, + .read_raw = opt3001_read_raw, + .write_raw = opt3001_write_raw, + .read_event_value = opt3001_read_event_value, + .write_event_value = opt3001_write_event_value, + .read_event_config = opt3001_read_event_config, + .write_event_config = opt3001_write_event_config, +}; + +static int opt3001_read_id(struct opt3001 *opt) +{ + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + char manufacturer[2]; + u16 device_id; + int ret; + + ret = i2c_smbus_read_word_swapped(client, OPT3001_MANUFACTURER_ID); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to read register %02x\n", + OPT3001_MANUFACTURER_ID); + + manufacturer[0] = ret >> 8; + manufacturer[1] = ret & 0xff; + + ret = i2c_smbus_read_word_swapped(client, OPT3001_DEVICE_ID); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to read register %02x\n", + OPT3001_DEVICE_ID); + + device_id = ret; + + dev_info(dev, "Found %c%c OPT%04x\n", manufacturer[0], manufacturer[1], + device_id); + + return 0; +} + +static void opt3001_power_off(void *data) +{ + struct opt3001 *opt = data; + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + u16 reg_val; + int ret; + + ret = i2c_smbus_read_word_swapped(client, OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + return; + } + + reg_val = ret; + opt3001_set_mode(opt, ®_val, OPT3001_CONFIGURATION_M_SHUTDOWN); + + ret = i2c_smbus_write_word_swapped(client, OPT3001_CONFIGURATION, reg_val); + if (ret < 0) + dev_err(dev, "failed to write to register %02x\n", + OPT3001_CONFIGURATION); +} + +static int opt3001_configure(struct opt3001 *opt) +{ + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + int ret; + u16 reg; + + ret = i2c_smbus_read_word_swapped(client, OPT3001_CONFIGURATION); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + + reg = ret; + + /* Enable automatic full-scale setting mode */ + reg &= ~OPT3001_CONFIGURATION_RN_MASK; + reg |= OPT3001_CONFIGURATION_RN_AUTO; + + /* Reflect status of the device's integration time setting */ + if (reg & OPT3001_CONFIGURATION_CT) + opt->int_time = OPT3001_INT_TIME_LONG; + else + opt->int_time = OPT3001_INT_TIME_SHORT; + + /* Ensure device is in shutdown initially */ + opt3001_set_mode(opt, ®, OPT3001_CONFIGURATION_M_SHUTDOWN); + + /* Configure for latched window-style comparison operation */ + reg |= OPT3001_CONFIGURATION_L; + reg &= ~OPT3001_CONFIGURATION_POL; + reg &= ~OPT3001_CONFIGURATION_ME; + reg &= ~OPT3001_CONFIGURATION_FC_MASK; + + ret = i2c_smbus_write_word_swapped(client, OPT3001_CONFIGURATION, reg); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to write register %02x\n", + OPT3001_CONFIGURATION); + + ret = devm_add_action_or_reset(dev, opt3001_power_off, opt); + if (ret) + return dev_err_probe(dev, ret, + "failed to register power off function\n"); + + ret = i2c_smbus_read_word_swapped(client, OPT3001_LOW_LIMIT); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to read register %02x\n", + OPT3001_LOW_LIMIT); + + opt->low_thresh_mantissa = OPT3001_REG_MANTISSA(ret); + opt->low_thresh_exp = OPT3001_REG_EXPONENT(ret); + + ret = i2c_smbus_read_word_swapped(client, OPT3001_HIGH_LIMIT); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to read register %02x\n", + OPT3001_HIGH_LIMIT); + + opt->high_thresh_mantissa = OPT3001_REG_MANTISSA(ret); + opt->high_thresh_exp = OPT3001_REG_EXPONENT(ret); + + return 0; +} + +static irqreturn_t opt3001_irq(int irq, void *_iio) +{ + struct iio_dev *iio = _iio; + struct opt3001 *opt = iio_priv(iio); + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + int ret; + bool wake_result_ready_queue = false; + enum iio_chan_type chan_type = opt->chip_info->chan_type; + bool ok_to_ignore_lock = opt->ok_to_ignore_lock; + + if (!ok_to_ignore_lock) + mutex_lock(&opt->lock); + + ret = i2c_smbus_read_word_swapped(client, OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + goto out; + } + + if ((ret & OPT3001_CONFIGURATION_M_MASK) == + OPT3001_CONFIGURATION_M_CONTINUOUS) { + if (ret & OPT3001_CONFIGURATION_FH) + iio_push_event(iio, + IIO_UNMOD_EVENT_CODE(chan_type, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(iio)); + if (ret & OPT3001_CONFIGURATION_FL) + iio_push_event(iio, + IIO_UNMOD_EVENT_CODE(chan_type, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(iio)); + } else if (ret & OPT3001_CONFIGURATION_CRF) { + ret = i2c_smbus_read_word_swapped(client, OPT3001_RESULT); + if (ret < 0) { + dev_err(dev, "failed to read register %02x\n", + OPT3001_RESULT); + goto out; + } + opt->result = ret; + opt->result_ready = true; + wake_result_ready_queue = true; + } + +out: + if (!ok_to_ignore_lock) + mutex_unlock(&opt->lock); + + if (wake_result_ready_queue) + wake_up(&opt->result_ready_queue); + + return IRQ_HANDLED; +} + +static int opt3001_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + + struct iio_dev *iio; + struct opt3001 *opt; + int irq = client->irq; + int ret; + + iio = devm_iio_device_alloc(dev, sizeof(*opt)); + if (!iio) + return -ENOMEM; + + opt = iio_priv(iio); + opt->client = client; + opt->chip_info = i2c_get_match_data(client); + + ret = devm_mutex_init(dev, &opt->lock); + if (ret) + return ret; + + init_waitqueue_head(&opt->result_ready_queue); + + if (opt->chip_info->has_id) { + ret = opt3001_read_id(opt); + if (ret) + return ret; + } + + ret = opt3001_configure(opt); + if (ret) + return ret; + + iio->name = client->name; + iio->channels = *opt->chip_info->channels; + iio->num_channels = opt->chip_info->num_channels; + iio->modes = INDIO_DIRECT_MODE; + iio->info = &opt3001_info; + + /* Make use of INT pin only if valid IRQ no. is given */ + if (irq > 0) { + ret = devm_request_threaded_irq(dev, irq, NULL, opt3001_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "opt3001", iio); + if (ret) + return ret; + + opt->use_irq = true; + } else { + dev_dbg(dev, "enabling interrupt-less operation\n"); + } + + return devm_iio_device_register(dev, iio); +} + +static const struct opt3001_chip_info opt3001_chip_information = { + .channels = &opt3001_channels, + .chan_type = IIO_LIGHT, + .num_channels = ARRAY_SIZE(opt3001_channels), + .scales = &opt3001_scales, + .factor_whole = 10, + .factor_integer = 1000, + .factor_decimal = 1000, + .has_id = true, +}; + +static const struct opt3001_chip_info opt3002_chip_information = { + .channels = &opt3002_channels, + .chan_type = IIO_INTENSITY, + .num_channels = ARRAY_SIZE(opt3002_channels), + .scales = &opt3002_scales, + .factor_whole = 12, + .factor_integer = 10, + .factor_decimal = 100000, + .has_id = false, +}; + +static const struct i2c_device_id opt3001_id[] = { + { .name = "opt3001", .driver_data = (kernel_ulong_t)&opt3001_chip_information }, + { .name = "opt3002", .driver_data = (kernel_ulong_t)&opt3002_chip_information }, + { } /* Terminating Entry */ +}; +MODULE_DEVICE_TABLE(i2c, opt3001_id); + +static const struct of_device_id opt3001_of_match[] = { + { .compatible = "ti,opt3001", .data = &opt3001_chip_information }, + { .compatible = "ti,opt3002", .data = &opt3002_chip_information }, + { } +}; +MODULE_DEVICE_TABLE(of, opt3001_of_match); + +static struct i2c_driver opt3001_driver = { + .probe = opt3001_probe, + .id_table = opt3001_id, + + .driver = { + .name = "opt3001", + .of_match_table = opt3001_of_match, + }, +}; + +module_i2c_driver(opt3001_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Andreas Dannenberg <dannenberg@ti.com>"); +MODULE_DESCRIPTION("Texas Instruments OPT3001 Light Sensor Driver"); diff --git a/drivers/iio/light/opt4001.c b/drivers/iio/light/opt4001.c new file mode 100644 index 000000000..aa3d87995 --- /dev/null +++ b/drivers/iio/light/opt4001.c @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 Axis Communications AB + * + * Datasheet: https://www.ti.com/lit/gpn/opt4001 + * + * Device driver for the Texas Instruments OPT4001. + */ + +#include <linux/bitfield.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +/* OPT4001 register set */ +#define OPT4001_LIGHT1_MSB 0x00 +#define OPT4001_LIGHT1_LSB 0x01 +#define OPT4001_CTRL 0x0A +#define OPT4001_DEVICE_ID 0x11 + +/* OPT4001 register mask */ +#define OPT4001_EXPONENT_MASK GENMASK(15, 12) +#define OPT4001_MSB_MASK GENMASK(11, 0) +#define OPT4001_LSB_MASK GENMASK(15, 8) +#define OPT4001_COUNTER_MASK GENMASK(7, 4) +#define OPT4001_CRC_MASK GENMASK(3, 0) + +/* OPT4001 device id mask */ +#define OPT4001_DEVICE_ID_MASK GENMASK(11, 0) + +/* OPT4001 control registers mask */ +#define OPT4001_CTRL_QWAKE_MASK GENMASK(15, 15) +#define OPT4001_CTRL_RANGE_MASK GENMASK(13, 10) +#define OPT4001_CTRL_CONV_TIME_MASK GENMASK(9, 6) +#define OPT4001_CTRL_OPER_MODE_MASK GENMASK(5, 4) +#define OPT4001_CTRL_LATCH_MASK GENMASK(3, 3) +#define OPT4001_CTRL_INT_POL_MASK GENMASK(2, 2) +#define OPT4001_CTRL_FAULT_COUNT_MASK GENMASK(1, 0) + +/* OPT4001 constants */ +#define OPT4001_DEVICE_ID_VAL 0x121 + +/* OPT4001 operating modes */ +#define OPT4001_CTRL_OPER_MODE_OFF 0x0 +#define OPT4001_CTRL_OPER_MODE_FORCED 0x1 +#define OPT4001_CTRL_OPER_MODE_ONE_SHOT 0x2 +#define OPT4001_CTRL_OPER_MODE_CONTINUOUS 0x3 + +/* OPT4001 conversion control register definitions */ +#define OPT4001_CTRL_CONVERSION_0_6MS 0x0 +#define OPT4001_CTRL_CONVERSION_1MS 0x1 +#define OPT4001_CTRL_CONVERSION_1_8MS 0x2 +#define OPT4001_CTRL_CONVERSION_3_4MS 0x3 +#define OPT4001_CTRL_CONVERSION_6_5MS 0x4 +#define OPT4001_CTRL_CONVERSION_12_7MS 0x5 +#define OPT4001_CTRL_CONVERSION_25MS 0x6 +#define OPT4001_CTRL_CONVERSION_50MS 0x7 +#define OPT4001_CTRL_CONVERSION_100MS 0x8 +#define OPT4001_CTRL_CONVERSION_200MS 0x9 +#define OPT4001_CTRL_CONVERSION_400MS 0xa +#define OPT4001_CTRL_CONVERSION_800MS 0xb + +/* OPT4001 scale light level range definitions */ +#define OPT4001_CTRL_LIGHT_SCALE_AUTO 12 + +/* OPT4001 default values */ +#define OPT4001_DEFAULT_CONVERSION_TIME OPT4001_CTRL_CONVERSION_800MS + +/* + * The different packaging of OPT4001 has different constants used when calculating + * lux values. + */ +struct opt4001_chip_info { + int mul; + int div; + const char *name; +}; + +struct opt4001_chip { + struct regmap *regmap; + struct i2c_client *client; + u8 int_time; + const struct opt4001_chip_info *chip_info; +}; + +static const struct opt4001_chip_info opt4001_sot_5x3_info = { + .mul = 4375, + .div = 10000000, + .name = "opt4001-sot-5x3" +}; + +static const struct opt4001_chip_info opt4001_picostar_info = { + .mul = 3125, + .div = 10000000, + .name = "opt4001-picostar" +}; + +static const int opt4001_int_time_available[][2] = { + { 0, 600 }, + { 0, 1000 }, + { 0, 1800 }, + { 0, 3400 }, + { 0, 6500 }, + { 0, 12700 }, + { 0, 25000 }, + { 0, 50000 }, + { 0, 100000 }, + { 0, 200000 }, + { 0, 400000 }, + { 0, 800000 }, +}; + +/* + * Conversion time is integration time + time to set register + * this is used as integration time. + */ +static const int opt4001_int_time_reg[][2] = { + { 600, OPT4001_CTRL_CONVERSION_0_6MS }, + { 1000, OPT4001_CTRL_CONVERSION_1MS }, + { 1800, OPT4001_CTRL_CONVERSION_1_8MS }, + { 3400, OPT4001_CTRL_CONVERSION_3_4MS }, + { 6500, OPT4001_CTRL_CONVERSION_6_5MS }, + { 12700, OPT4001_CTRL_CONVERSION_12_7MS }, + { 25000, OPT4001_CTRL_CONVERSION_25MS }, + { 50000, OPT4001_CTRL_CONVERSION_50MS }, + { 100000, OPT4001_CTRL_CONVERSION_100MS }, + { 200000, OPT4001_CTRL_CONVERSION_200MS }, + { 400000, OPT4001_CTRL_CONVERSION_400MS }, + { 800000, OPT4001_CTRL_CONVERSION_800MS }, +}; + +static int opt4001_als_time_to_index(const u32 als_integration_time) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(opt4001_int_time_available); i++) { + if (als_integration_time == opt4001_int_time_available[i][1]) + return i; + } + + return -EINVAL; +} + +static u8 opt4001_calculate_crc(u8 exp, u32 mantissa, u8 count) +{ + u8 crc; + + crc = (hweight32(mantissa) + hweight32(exp) + hweight32(count)) % 2; + crc |= ((hweight32(mantissa & 0xAAAAA) + hweight32(exp & 0xA) + + hweight32(count & 0xA)) % 2) << 1; + crc |= ((hweight32(mantissa & 0x88888) + hweight32(exp & 0x8) + + hweight32(count & 0x8)) % 2) << 2; + crc |= (hweight32(mantissa & 0x80808) % 2) << 3; + + return crc; +} + +static int opt4001_read_lux_value(struct iio_dev *indio_dev, + int *val, int *val2) +{ + struct opt4001_chip *chip = iio_priv(indio_dev); + struct device *dev = &chip->client->dev; + unsigned int light1; + unsigned int light2; + u16 msb; + u16 lsb; + u8 exp; + u8 count; + u8 crc; + u8 calc_crc; + u64 lux_raw; + u32 rem; + int ret; + + ret = regmap_read(chip->regmap, OPT4001_LIGHT1_MSB, &light1); + if (ret < 0) { + dev_err(dev, "Failed to read data bytes"); + return ret; + } + + ret = regmap_read(chip->regmap, OPT4001_LIGHT1_LSB, &light2); + if (ret < 0) { + dev_err(dev, "Failed to read data bytes"); + return ret; + } + + count = FIELD_GET(OPT4001_COUNTER_MASK, light2); + exp = FIELD_GET(OPT4001_EXPONENT_MASK, light1); + crc = FIELD_GET(OPT4001_CRC_MASK, light2); + msb = FIELD_GET(OPT4001_MSB_MASK, light1); + lsb = FIELD_GET(OPT4001_LSB_MASK, light2); + lux_raw = (msb << 8) + lsb; + calc_crc = opt4001_calculate_crc(exp, lux_raw, count); + if (calc_crc != crc) + return -EIO; + + lux_raw = lux_raw << exp; + lux_raw = lux_raw * chip->chip_info->mul; + *val = div_u64_rem(lux_raw, chip->chip_info->div, &rem); + *val2 = rem * 100; + + return IIO_VAL_INT_PLUS_NANO; +} + +static int opt4001_set_conf(struct opt4001_chip *chip) +{ + struct device *dev = &chip->client->dev; + u16 reg; + int ret; + + reg = FIELD_PREP(OPT4001_CTRL_RANGE_MASK, OPT4001_CTRL_LIGHT_SCALE_AUTO); + reg |= FIELD_PREP(OPT4001_CTRL_CONV_TIME_MASK, chip->int_time); + reg |= FIELD_PREP(OPT4001_CTRL_OPER_MODE_MASK, OPT4001_CTRL_OPER_MODE_CONTINUOUS); + + ret = regmap_write(chip->regmap, OPT4001_CTRL, reg); + if (ret) + dev_err(dev, "Failed to set configuration\n"); + + return ret; +} + +static void opt4001_chip_off_action(void *data) +{ + struct opt4001_chip *chip = data; + int ret; + + ret = regmap_clear_bits(chip->regmap, OPT4001_CTRL, OPT4001_CTRL_OPER_MODE_MASK); + if (ret) + dev_err(&chip->client->dev, "Failed to power down\n"); +} + +static const struct iio_chan_spec opt4001_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) + }, +}; + +static int opt4001_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct opt4001_chip *chip = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + return opt4001_read_lux_value(indio_dev, val, val2); + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = opt4001_int_time_reg[chip->int_time][0]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int opt4001_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct opt4001_chip *chip = iio_priv(indio_dev); + int int_time; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val) + return -EINVAL; + + int_time = opt4001_als_time_to_index(val2); + if (int_time < 0) + return int_time; + chip->int_time = int_time; + return opt4001_set_conf(chip); + default: + return -EINVAL; + } +} + +static int opt4001_read_available(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(opt4001_int_time_available) * 2; + *vals = (const int *)opt4001_int_time_available; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + + default: + return -EINVAL; + } +} + +static const struct iio_info opt4001_info_no_irq = { + .read_raw = opt4001_read_raw, + .write_raw = opt4001_write_raw, + .read_avail = opt4001_read_available, +}; + +static int opt4001_load_defaults(struct opt4001_chip *chip) +{ + chip->int_time = OPT4001_DEFAULT_CONVERSION_TIME; + + return opt4001_set_conf(chip); +} + +static bool opt4001_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case OPT4001_LIGHT1_MSB: + case OPT4001_LIGHT1_LSB: + case OPT4001_CTRL: + case OPT4001_DEVICE_ID: + return true; + default: + return false; + } +} + +static bool opt4001_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case OPT4001_CTRL: + return true; + default: + return false; + } +} + +static bool opt4001_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case OPT4001_LIGHT1_MSB: + case OPT4001_LIGHT1_LSB: + return true; + default: + return false; + } +} + +static const struct regmap_config opt4001_regmap_config = { + .name = "opt4001", + .reg_bits = 8, + .val_bits = 16, + .cache_type = REGCACHE_RBTREE, + .max_register = OPT4001_DEVICE_ID, + .readable_reg = opt4001_readable_reg, + .writeable_reg = opt4001_writable_reg, + .volatile_reg = opt4001_volatile_reg, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + +static int opt4001_probe(struct i2c_client *client) +{ + struct opt4001_chip *chip; + struct iio_dev *indio_dev; + int ret; + uint dev_id; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + ret = devm_regulator_get_enable(&client->dev, "vdd"); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to enable vdd supply\n"); + + chip->regmap = devm_regmap_init_i2c(client, &opt4001_regmap_config); + if (IS_ERR(chip->regmap)) + return dev_err_probe(&client->dev, PTR_ERR(chip->regmap), + "regmap initialization failed\n"); + chip->client = client; + + indio_dev->info = &opt4001_info_no_irq; + + ret = regmap_reinit_cache(chip->regmap, &opt4001_regmap_config); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to reinit regmap cache\n"); + + ret = regmap_read(chip->regmap, OPT4001_DEVICE_ID, &dev_id); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to read the device ID register\n"); + + dev_id = FIELD_GET(OPT4001_DEVICE_ID_MASK, dev_id); + if (dev_id != OPT4001_DEVICE_ID_VAL) + dev_warn(&client->dev, "Device ID: %#04x unknown\n", dev_id); + + chip->chip_info = i2c_get_match_data(client); + + indio_dev->channels = opt4001_channels; + indio_dev->num_channels = ARRAY_SIZE(opt4001_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = chip->chip_info->name; + + ret = opt4001_load_defaults(chip); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to set sensor defaults\n"); + + ret = devm_add_action_or_reset(&client->dev, + opt4001_chip_off_action, + chip); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +/* + * The compatible string determines which constants to use depending on + * opt4001 packaging + */ +static const struct i2c_device_id opt4001_id[] = { + { .name = "opt4001-sot-5x3", .driver_data = (kernel_ulong_t)&opt4001_sot_5x3_info }, + { .name = "opt4001-picostar", .driver_data = (kernel_ulong_t)&opt4001_picostar_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, opt4001_id); + +static const struct of_device_id opt4001_of_match[] = { + { .compatible = "ti,opt4001-sot-5x3", .data = &opt4001_sot_5x3_info}, + { .compatible = "ti,opt4001-picostar", .data = &opt4001_picostar_info}, + { } +}; +MODULE_DEVICE_TABLE(of, opt4001_of_match); + +static struct i2c_driver opt4001_driver = { + .driver = { + .name = "opt4001", + .of_match_table = opt4001_of_match, + }, + .probe = opt4001_probe, + .id_table = opt4001_id, +}; +module_i2c_driver(opt4001_driver); + +MODULE_AUTHOR("Stefan Windfeldt-Prytz <stefan.windfeldt-prytz@axis.com>"); +MODULE_DESCRIPTION("Texas Instruments opt4001 ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/opt4060.c b/drivers/iio/light/opt4060.c new file mode 100644 index 000000000..8253a6a8b --- /dev/null +++ b/drivers/iio/light/opt4060.c @@ -0,0 +1,1328 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 Axis Communications AB + * + * Datasheet: https://www.ti.com/lit/gpn/opt4060 + * + * Device driver for the Texas Instruments OPT4060 RGBW Color Sensor. + */ + +#include <linux/bitfield.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/math64.h> +#include <linux/units.h> +#include <linux/limits.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/mutex.h> +#include <linux/regulator/consumer.h> +#include <linux/iio/events.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +/* OPT4060 register set */ +#define OPT4060_RED_MSB 0x00 +#define OPT4060_RED_LSB 0x01 +#define OPT4060_GREEN_MSB 0x02 +#define OPT4060_GREEN_LSB 0x03 +#define OPT4060_BLUE_MSB 0x04 +#define OPT4060_BLUE_LSB 0x05 +#define OPT4060_CLEAR_MSB 0x06 +#define OPT4060_CLEAR_LSB 0x07 +#define OPT4060_THRESHOLD_LOW 0x08 +#define OPT4060_THRESHOLD_HIGH 0x09 +#define OPT4060_CTRL 0x0a +#define OPT4060_INT_CTRL 0x0b +#define OPT4060_RES_CTRL 0x0c +#define OPT4060_DEVICE_ID 0x11 + +/* OPT4060 register mask */ +#define OPT4060_EXPONENT_MASK GENMASK(15, 12) +#define OPT4060_MSB_MASK GENMASK(11, 0) +#define OPT4060_LSB_MASK GENMASK(15, 8) +#define OPT4060_COUNTER_MASK GENMASK(7, 4) +#define OPT4060_CRC_MASK GENMASK(3, 0) + +/* OPT4060 device id mask */ +#define OPT4060_DEVICE_ID_MASK GENMASK(11, 0) + +/* OPT4060 control register masks */ +#define OPT4060_CTRL_QWAKE_MASK BIT(15) +#define OPT4060_CTRL_RANGE_MASK GENMASK(13, 10) +#define OPT4060_CTRL_CONV_TIME_MASK GENMASK(9, 6) +#define OPT4060_CTRL_OPER_MODE_MASK GENMASK(5, 4) +#define OPT4060_CTRL_LATCH_MASK BIT(3) +#define OPT4060_CTRL_INT_POL_MASK BIT(2) +#define OPT4060_CTRL_FAULT_COUNT_MASK GENMASK(1, 0) + +/* OPT4060 interrupt control register masks */ +#define OPT4060_INT_CTRL_THRESH_SEL GENMASK(6, 5) +#define OPT4060_INT_CTRL_OUTPUT BIT(4) +#define OPT4060_INT_CTRL_INT_CFG GENMASK(3, 2) +#define OPT4060_INT_CTRL_THRESHOLD 0x0 +#define OPT4060_INT_CTRL_NEXT_CH 0x1 +#define OPT4060_INT_CTRL_ALL_CH 0x3 + +/* OPT4060 result control register masks */ +#define OPT4060_RES_CTRL_OVERLOAD BIT(3) +#define OPT4060_RES_CTRL_CONV_READY BIT(2) +#define OPT4060_RES_CTRL_FLAG_H BIT(1) +#define OPT4060_RES_CTRL_FLAG_L BIT(0) + +/* OPT4060 constants */ +#define OPT4060_DEVICE_ID_VAL 0x821 + +/* OPT4060 operating modes */ +#define OPT4060_CTRL_OPER_MODE_OFF 0x0 +#define OPT4060_CTRL_OPER_MODE_FORCED 0x1 +#define OPT4060_CTRL_OPER_MODE_ONE_SHOT 0x2 +#define OPT4060_CTRL_OPER_MODE_CONTINUOUS 0x3 + +/* OPT4060 conversion control register definitions */ +#define OPT4060_CTRL_CONVERSION_0_6MS 0x0 +#define OPT4060_CTRL_CONVERSION_1MS 0x1 +#define OPT4060_CTRL_CONVERSION_1_8MS 0x2 +#define OPT4060_CTRL_CONVERSION_3_4MS 0x3 +#define OPT4060_CTRL_CONVERSION_6_5MS 0x4 +#define OPT4060_CTRL_CONVERSION_12_7MS 0x5 +#define OPT4060_CTRL_CONVERSION_25MS 0x6 +#define OPT4060_CTRL_CONVERSION_50MS 0x7 +#define OPT4060_CTRL_CONVERSION_100MS 0x8 +#define OPT4060_CTRL_CONVERSION_200MS 0x9 +#define OPT4060_CTRL_CONVERSION_400MS 0xa +#define OPT4060_CTRL_CONVERSION_800MS 0xb + +/* OPT4060 fault count control register definitions */ +#define OPT4060_CTRL_FAULT_COUNT_1 0x0 +#define OPT4060_CTRL_FAULT_COUNT_2 0x1 +#define OPT4060_CTRL_FAULT_COUNT_4 0x2 +#define OPT4060_CTRL_FAULT_COUNT_8 0x3 + +/* OPT4060 scale light level range definitions */ +#define OPT4060_CTRL_LIGHT_SCALE_AUTO 12 + +/* OPT4060 default values */ +#define OPT4060_DEFAULT_CONVERSION_TIME OPT4060_CTRL_CONVERSION_50MS + +/* + * enum opt4060_chan_type - OPT4060 channel types + * @OPT4060_RED: Red channel. + * @OPT4060_GREEN: Green channel. + * @OPT4060_BLUE: Blue channel. + * @OPT4060_CLEAR: Clear (white) channel. + * @OPT4060_ILLUM: Calculated illuminance channel. + * @OPT4060_NUM_CHANS: Number of channel types. + */ +enum opt4060_chan_type { + OPT4060_RED, + OPT4060_GREEN, + OPT4060_BLUE, + OPT4060_CLEAR, + OPT4060_ILLUM, + OPT4060_NUM_CHANS +}; + +struct opt4060_chip { + struct regmap *regmap; + struct device *dev; + struct iio_trigger *trig; + u8 int_time; + int irq; + /* + * Mutex for protecting sensor irq settings. Switching between interrupt + * on each sample and on thresholds needs to be synchronized. + */ + struct mutex irq_setting_lock; + /* + * Mutex for protecting event enabling. + */ + struct mutex event_enabling_lock; + struct completion completion; + bool thresh_event_lo_active; + bool thresh_event_hi_active; +}; + +struct opt4060_channel_factor { + u32 mul; + u32 div; +}; + +static const int opt4060_int_time_available[][2] = { + { 0, 600 }, + { 0, 1000 }, + { 0, 1800 }, + { 0, 3400 }, + { 0, 6500 }, + { 0, 12700 }, + { 0, 25000 }, + { 0, 50000 }, + { 0, 100000 }, + { 0, 200000 }, + { 0, 400000 }, + { 0, 800000 }, +}; + +/* + * Conversion time is integration time + time to set register + * this is used as integration time. + */ +static const int opt4060_int_time_reg[][2] = { + { 600, OPT4060_CTRL_CONVERSION_0_6MS }, + { 1000, OPT4060_CTRL_CONVERSION_1MS }, + { 1800, OPT4060_CTRL_CONVERSION_1_8MS }, + { 3400, OPT4060_CTRL_CONVERSION_3_4MS }, + { 6500, OPT4060_CTRL_CONVERSION_6_5MS }, + { 12700, OPT4060_CTRL_CONVERSION_12_7MS }, + { 25000, OPT4060_CTRL_CONVERSION_25MS }, + { 50000, OPT4060_CTRL_CONVERSION_50MS }, + { 100000, OPT4060_CTRL_CONVERSION_100MS }, + { 200000, OPT4060_CTRL_CONVERSION_200MS }, + { 400000, OPT4060_CTRL_CONVERSION_400MS }, + { 800000, OPT4060_CTRL_CONVERSION_800MS }, +}; + +static int opt4060_als_time_to_index(const u32 als_integration_time) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(opt4060_int_time_available); i++) { + if (als_integration_time == opt4060_int_time_available[i][1]) + return i; + } + + return -EINVAL; +} + +static u8 opt4060_calculate_crc(u8 exp, u32 mantissa, u8 count) +{ + u8 crc; + + /* + * Calculates a 4-bit CRC from a 20-bit mantissa, 4-bit exponent and a 4-bit counter. + * crc[0] = XOR(mantissa[19:0], exp[3:0], count[3:0]) + * crc[1] = XOR(mantissa[1,3,5,7,9,11,13,15,17,19], exp[1,3], count[1,3]) + * crc[2] = XOR(mantissa[3,7,11,15,19], exp[3], count[3]) + * crc[3] = XOR(mantissa[3,11,19]) + */ + crc = (hweight32(mantissa) + hweight32(exp) + hweight32(count)) % 2; + crc |= ((hweight32(mantissa & 0xAAAAA) + hweight32(exp & 0xA) + + hweight32(count & 0xA)) % 2) << 1; + crc |= ((hweight32(mantissa & 0x88888) + hweight32(exp & 0x8) + + hweight32(count & 0x8)) % 2) << 2; + crc |= (hweight32(mantissa & 0x80808) % 2) << 3; + + return crc; +} + +static int opt4060_set_int_state(struct opt4060_chip *chip, u32 state) +{ + int ret; + unsigned int regval; + + guard(mutex)(&chip->irq_setting_lock); + + regval = FIELD_PREP(OPT4060_INT_CTRL_INT_CFG, state); + ret = regmap_update_bits(chip->regmap, OPT4060_INT_CTRL, + OPT4060_INT_CTRL_INT_CFG, regval); + if (ret) + dev_err(chip->dev, "Failed to set interrupt config\n"); + return ret; +} + +static int opt4060_set_sampling_mode(struct opt4060_chip *chip, + bool continuous) +{ + unsigned int reg; + int ret; + + ret = regmap_read(chip->regmap, OPT4060_CTRL, ®); + if (ret < 0) { + dev_err(chip->dev, "Failed to read ctrl register\n"); + return ret; + } + reg &= ~OPT4060_CTRL_OPER_MODE_MASK; + if (continuous) + reg |= FIELD_PREP(OPT4060_CTRL_OPER_MODE_MASK, + OPT4060_CTRL_OPER_MODE_CONTINUOUS); + else + reg |= FIELD_PREP(OPT4060_CTRL_OPER_MODE_MASK, + OPT4060_CTRL_OPER_MODE_ONE_SHOT); + + /* + * Trigger a new conversions by writing to CRTL register. It is not + * possible to use regmap_update_bits() since that will only write when + * data is modified. + */ + ret = regmap_write(chip->regmap, OPT4060_CTRL, reg); + if (ret) + dev_err(chip->dev, "Failed to set ctrl register\n"); + return ret; +} + +static bool opt4060_event_active(struct opt4060_chip *chip) +{ + return chip->thresh_event_lo_active || chip->thresh_event_hi_active; +} + +static int opt4060_set_state_common(struct opt4060_chip *chip, + bool continuous_sampling, + bool continuous_irq) +{ + int ret = 0; + + /* It is important to setup irq before sampling to avoid missing samples. */ + if (continuous_irq) + ret = opt4060_set_int_state(chip, OPT4060_INT_CTRL_ALL_CH); + else + ret = opt4060_set_int_state(chip, OPT4060_INT_CTRL_THRESHOLD); + if (ret) { + dev_err(chip->dev, "Failed to set irq state.\n"); + return ret; + } + + if (continuous_sampling || opt4060_event_active(chip)) + ret = opt4060_set_sampling_mode(chip, true); + else + ret = opt4060_set_sampling_mode(chip, false); + if (ret) + dev_err(chip->dev, "Failed to set sampling state.\n"); + return ret; +} + +/* + * Function for setting the driver state for sampling and irq. Either direct + * mode of buffer mode will be claimed during the transition to prevent races + * between sysfs read, buffer or events. + */ +static int opt4060_set_driver_state(struct iio_dev *indio_dev, + bool continuous_sampling, + bool continuous_irq) +{ + struct opt4060_chip *chip = iio_priv(indio_dev); + + IIO_DEV_GUARD_CURRENT_MODE(indio_dev); + + /* + * If we manage to claim buffer mode and we are using our own trigger, + * IRQ and sampling must go to or remain continuous. + */ + if (iio_buffer_enabled(indio_dev) && + iio_trigger_validate_own_device(indio_dev->trig, indio_dev)) + return opt4060_set_state_common(chip, true, true); + + /* + * This path means that we managed to claim direct mode. In this case + * the buffer isn't enabled and it's okay to leave continuous mode for + * sampling and/or irq. + */ + return opt4060_set_state_common(chip, continuous_sampling, continuous_irq); +} + +/* + * This function is called with framework mutex locked. + */ +static int opt4060_trigger_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct opt4060_chip *chip = iio_priv(indio_dev); + + return opt4060_set_state_common(chip, state, state); +} + +static int opt4060_read_raw_value(struct opt4060_chip *chip, + unsigned long address, u32 *raw) +{ + int ret; + u16 result[2]; + u32 mantissa_raw; + u16 msb, lsb; + u8 exp, count, crc, calc_crc; + + ret = regmap_bulk_read(chip->regmap, address, result, 2); + if (ret) { + dev_err(chip->dev, "Reading channel data failed\n"); + return ret; + } + exp = FIELD_GET(OPT4060_EXPONENT_MASK, result[0]); + msb = FIELD_GET(OPT4060_MSB_MASK, result[0]); + count = FIELD_GET(OPT4060_COUNTER_MASK, result[1]); + crc = FIELD_GET(OPT4060_CRC_MASK, result[1]); + lsb = FIELD_GET(OPT4060_LSB_MASK, result[1]); + mantissa_raw = (msb << 8) + lsb; + calc_crc = opt4060_calculate_crc(exp, mantissa_raw, count); + if (calc_crc != crc) + return -EIO; + *raw = mantissa_raw << exp; + return 0; +} + +static int opt4060_trigger_new_samples(struct iio_dev *indio_dev) +{ + struct opt4060_chip *chip = iio_priv(indio_dev); + int ret; + + /* + * The conversion time should be 500us startup time plus the integration time + * times the number of channels. An exact timeout isn't critical, it's better + * not to get incorrect errors in the log. Setting the timeout to double the + * theoretical time plus and extra 100ms margin. + */ + unsigned int timeout_us = (500 + OPT4060_NUM_CHANS * + opt4060_int_time_reg[chip->int_time][0]) * 2 + 100000; + + /* Setting the state in one shot mode with irq on each sample. */ + ret = opt4060_set_driver_state(indio_dev, false, true); + if (ret) + return ret; + + if (chip->irq) { + guard(mutex)(&chip->irq_setting_lock); + reinit_completion(&chip->completion); + if (wait_for_completion_timeout(&chip->completion, + usecs_to_jiffies(timeout_us)) == 0) { + dev_err(chip->dev, "Completion timed out.\n"); + return -ETIME; + } + } else { + unsigned int ready; + + ret = regmap_read_poll_timeout(chip->regmap, OPT4060_RES_CTRL, + ready, (ready & OPT4060_RES_CTRL_CONV_READY), + 1000, timeout_us); + if (ret) + dev_err(chip->dev, "Conversion ready did not finish within timeout.\n"); + } + /* Setting the state in one shot mode with irq on thresholds. */ + return opt4060_set_driver_state(indio_dev, false, false); +} + +static int opt4060_read_chan_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + struct opt4060_chip *chip = iio_priv(indio_dev); + u32 adc_raw; + int ret; + + ret = opt4060_trigger_new_samples(indio_dev); + if (ret) { + dev_err(chip->dev, "Failed to trigger new samples.\n"); + return ret; + } + + ret = opt4060_read_raw_value(chip, chan->address, &adc_raw); + if (ret) { + dev_err(chip->dev, "Reading raw channel data failed.\n"); + return ret; + } + *val = adc_raw; + return IIO_VAL_INT; +} + +/* + * Returns the scale values used for red, green and blue. Scales the raw value + * so that for a particular test light source, typically white, the measurement + * intensity is the same across different color channels. + */ +static int opt4060_get_chan_scale(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + struct opt4060_chip *chip = iio_priv(indio_dev); + + switch (chan->scan_index) { + case OPT4060_RED: + /* 2.4 */ + *val = 2; + *val2 = 400000; + break; + case OPT4060_GREEN: + /* 1.0 */ + *val = 1; + *val2 = 0; + break; + case OPT4060_BLUE: + /* 1.3 */ + *val = 1; + *val2 = 300000; + break; + default: + dev_err(chip->dev, "Unexpected channel index.\n"); + return -EINVAL; + } + return IIO_VAL_INT_PLUS_MICRO; +} + +static int opt4060_calc_illuminance(struct opt4060_chip *chip, int *val) +{ + u32 lux_raw; + int ret; + + /* The green wide spectral channel is used for illuminance. */ + ret = opt4060_read_raw_value(chip, OPT4060_GREEN_MSB, &lux_raw); + if (ret) { + dev_err(chip->dev, "Reading raw channel data failed\n"); + return ret; + } + + /* Illuminance is calculated by ADC_RAW * 2.15e-3. */ + *val = DIV_U64_ROUND_CLOSEST((u64)(lux_raw * 215), 1000); + return ret; +} + +static int opt4060_read_illuminance(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct opt4060_chip *chip = iio_priv(indio_dev); + int ret; + + ret = opt4060_trigger_new_samples(indio_dev); + if (ret) { + dev_err(chip->dev, "Failed to trigger new samples.\n"); + return ret; + } + ret = opt4060_calc_illuminance(chip, val); + if (ret) { + dev_err(chip->dev, "Failed to calculate illuminance.\n"); + return ret; + } + + return IIO_VAL_INT; +} + +static int opt4060_set_int_time(struct opt4060_chip *chip) +{ + unsigned int regval; + int ret; + + regval = FIELD_PREP(OPT4060_CTRL_CONV_TIME_MASK, chip->int_time); + ret = regmap_update_bits(chip->regmap, OPT4060_CTRL, + OPT4060_CTRL_CONV_TIME_MASK, regval); + if (ret) + dev_err(chip->dev, "Failed to set integration time.\n"); + + return ret; +} + +static int opt4060_power_down(struct opt4060_chip *chip) +{ + int ret; + + ret = regmap_clear_bits(chip->regmap, OPT4060_CTRL, OPT4060_CTRL_OPER_MODE_MASK); + if (ret) + dev_err(chip->dev, "Failed to power down\n"); + + return ret; +} + +static void opt4060_chip_off_action(void *chip) +{ + opt4060_power_down(chip); +} + +#define _OPT4060_COLOR_CHANNEL(_color, _mask, _ev_spec, _num_ev_spec) \ +{ \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .channel2 = IIO_MOD_LIGHT_##_color, \ + .info_mask_separate = _mask, \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), \ + .address = OPT4060_##_color##_MSB, \ + .scan_index = OPT4060_##_color, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_CPU, \ + }, \ + .event_spec = _ev_spec, \ + .num_event_specs = _num_ev_spec, \ +} + +#define OPT4060_COLOR_CHANNEL(_color, _mask) \ + _OPT4060_COLOR_CHANNEL(_color, _mask, opt4060_event_spec, \ + ARRAY_SIZE(opt4060_event_spec)) \ + +#define OPT4060_COLOR_CHANNEL_NO_EVENTS(_color, _mask) \ + _OPT4060_COLOR_CHANNEL(_color, _mask, NULL, 0) \ + +#define OPT4060_LIGHT_CHANNEL(_channel) \ +{ \ + .type = IIO_LIGHT, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), \ + .scan_index = OPT4060_##_channel, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_CPU, \ + }, \ +} + +static const struct iio_event_spec opt4060_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_PERIOD), + }, +}; + +static const struct iio_chan_spec opt4060_channels[] = { + OPT4060_COLOR_CHANNEL(RED, BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE)), + OPT4060_COLOR_CHANNEL(GREEN, BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE)), + OPT4060_COLOR_CHANNEL(BLUE, BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE)), + OPT4060_COLOR_CHANNEL(CLEAR, BIT(IIO_CHAN_INFO_RAW)), + OPT4060_LIGHT_CHANNEL(ILLUM), + IIO_CHAN_SOFT_TIMESTAMP(OPT4060_NUM_CHANS), +}; + +static const struct iio_chan_spec opt4060_channels_no_events[] = { + OPT4060_COLOR_CHANNEL_NO_EVENTS(RED, BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE)), + OPT4060_COLOR_CHANNEL_NO_EVENTS(GREEN, BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE)), + OPT4060_COLOR_CHANNEL_NO_EVENTS(BLUE, BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE)), + OPT4060_COLOR_CHANNEL_NO_EVENTS(CLEAR, BIT(IIO_CHAN_INFO_RAW)), + OPT4060_LIGHT_CHANNEL(ILLUM), + IIO_CHAN_SOFT_TIMESTAMP(OPT4060_NUM_CHANS), +}; + +static int opt4060_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct opt4060_chip *chip = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return opt4060_read_chan_raw(indio_dev, chan, val); + case IIO_CHAN_INFO_SCALE: + return opt4060_get_chan_scale(indio_dev, chan, val, val2); + case IIO_CHAN_INFO_PROCESSED: + return opt4060_read_illuminance(indio_dev, chan, val); + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = opt4060_int_time_reg[chip->int_time][0]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int opt4060_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct opt4060_chip *chip = iio_priv(indio_dev); + int int_time; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val) + return -EINVAL; + + int_time = opt4060_als_time_to_index(val2); + if (int_time < 0) + return int_time; + chip->int_time = int_time; + return opt4060_set_int_time(chip); + default: + return -EINVAL; + } +} + +static int opt4060_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static u32 opt4060_calc_th_reg(u32 adc_val) +{ + u32 th_val, th_exp, bits; + /* + * The threshold registers take 4 bits of exponent and 12 bits of data + * ADC = TH_VAL << (8 + TH_EXP) + */ + bits = fls(adc_val); + + if (bits > 31) + th_exp = 11; /* Maximum exponent */ + else if (bits > 20) + th_exp = bits - 20; + else + th_exp = 0; + th_val = (adc_val >> (8 + th_exp)) & 0xfff; + + return (th_exp << 12) + th_val; +} + +static u32 opt4060_calc_val_from_th_reg(u32 th_reg) +{ + /* + * The threshold registers take 4 bits of exponent and 12 bits of data + * ADC = TH_VAL << (8 + TH_EXP) + */ + u32 th_val, th_exp; + + th_exp = (th_reg >> 12) & 0xf; + th_val = th_reg & 0xfff; + + return th_val << (8 + th_exp); +} + +static int opt4060_read_available(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(opt4060_int_time_available) * 2; + *vals = (const int *)opt4060_int_time_available; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + + default: + return -EINVAL; + } +} + +static ssize_t opt4060_read_ev_period(struct opt4060_chip *chip, int *val, + int *val2) +{ + int ret, pers, fault_count, int_time; + u64 uval; + u32 rem; + + int_time = opt4060_int_time_reg[chip->int_time][0]; + + ret = regmap_read(chip->regmap, OPT4060_CTRL, &fault_count); + if (ret < 0) + return ret; + + fault_count = fault_count & OPT4060_CTRL_FAULT_COUNT_MASK; + switch (fault_count) { + case OPT4060_CTRL_FAULT_COUNT_2: + pers = 2; + break; + case OPT4060_CTRL_FAULT_COUNT_4: + pers = 4; + break; + case OPT4060_CTRL_FAULT_COUNT_8: + pers = 8; + break; + + default: + pers = 1; + break; + } + + uval = mul_u32_u32(int_time, pers); + *val = div_u64_rem(uval, MICRO, &rem); + *val2 = rem; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static ssize_t opt4060_write_ev_period(struct opt4060_chip *chip, int val, + int val2) +{ + u64 uval, int_time; + unsigned int regval, fault_count_val; + + uval = mul_u32_u32(val, MICRO) + val2; + int_time = opt4060_int_time_reg[chip->int_time][0]; + + /* Check if the period is closest to 1, 2, 4 or 8 times integration time.*/ + if (uval <= int_time) + fault_count_val = OPT4060_CTRL_FAULT_COUNT_1; + else if (uval <= int_time * 2) + fault_count_val = OPT4060_CTRL_FAULT_COUNT_2; + else if (uval <= int_time * 4) + fault_count_val = OPT4060_CTRL_FAULT_COUNT_4; + else + fault_count_val = OPT4060_CTRL_FAULT_COUNT_8; + + regval = FIELD_PREP(OPT4060_CTRL_FAULT_COUNT_MASK, fault_count_val); + return regmap_update_bits(chip->regmap, OPT4060_CTRL, + OPT4060_CTRL_FAULT_COUNT_MASK, regval); +} + +static int opt4060_get_channel_sel(struct opt4060_chip *chip, int *ch_sel) +{ + int ret; + u32 regval; + + ret = regmap_read(chip->regmap, OPT4060_INT_CTRL, ®val); + if (ret) { + dev_err(chip->dev, "Failed to get channel selection.\n"); + return ret; + } + *ch_sel = FIELD_GET(OPT4060_INT_CTRL_THRESH_SEL, regval); + return ret; +} + +static int opt4060_set_channel_sel(struct opt4060_chip *chip, int ch_sel) +{ + int ret; + u32 regval; + + regval = FIELD_PREP(OPT4060_INT_CTRL_THRESH_SEL, ch_sel); + ret = regmap_update_bits(chip->regmap, OPT4060_INT_CTRL, + OPT4060_INT_CTRL_THRESH_SEL, regval); + if (ret) + dev_err(chip->dev, "Failed to set channel selection.\n"); + return ret; +} + +static int opt4060_get_thresholds(struct opt4060_chip *chip, u32 *th_lo, u32 *th_hi) +{ + int ret; + u32 regval; + + ret = regmap_read(chip->regmap, OPT4060_THRESHOLD_LOW, ®val); + if (ret) { + dev_err(chip->dev, "Failed to read THRESHOLD_LOW.\n"); + return ret; + } + *th_lo = opt4060_calc_val_from_th_reg(regval); + + ret = regmap_read(chip->regmap, OPT4060_THRESHOLD_HIGH, ®val); + if (ret) { + dev_err(chip->dev, "Failed to read THRESHOLD_HIGH.\n"); + return ret; + } + *th_hi = opt4060_calc_val_from_th_reg(regval); + + return ret; +} + +static int opt4060_set_thresholds(struct opt4060_chip *chip, u32 th_lo, u32 th_hi) +{ + int ret; + + ret = regmap_write(chip->regmap, OPT4060_THRESHOLD_LOW, opt4060_calc_th_reg(th_lo)); + if (ret) { + dev_err(chip->dev, "Failed to write THRESHOLD_LOW.\n"); + return ret; + } + + ret = regmap_write(chip->regmap, OPT4060_THRESHOLD_HIGH, opt4060_calc_th_reg(th_hi)); + if (ret) + dev_err(chip->dev, "Failed to write THRESHOLD_HIGH.\n"); + + return ret; +} + +static int opt4060_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct opt4060_chip *chip = iio_priv(indio_dev); + u32 th_lo, th_hi; + int ret; + + if (chan->type != IIO_INTENSITY) + return -EINVAL; + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + ret = opt4060_get_thresholds(chip, &th_lo, &th_hi); + if (ret) + return ret; + if (dir == IIO_EV_DIR_FALLING) { + *val = th_lo; + ret = IIO_VAL_INT; + } else if (dir == IIO_EV_DIR_RISING) { + *val = th_hi; + ret = IIO_VAL_INT; + } + return ret; + case IIO_EV_INFO_PERIOD: + return opt4060_read_ev_period(chip, val, val2); + default: + return -EINVAL; + } +} + +static int opt4060_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct opt4060_chip *chip = iio_priv(indio_dev); + u32 th_lo, th_hi; + int ret; + + if (chan->type != IIO_INTENSITY) + return -EINVAL; + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + ret = opt4060_get_thresholds(chip, &th_lo, &th_hi); + if (ret) + return ret; + if (dir == IIO_EV_DIR_FALLING) + th_lo = val; + else if (dir == IIO_EV_DIR_RISING) + th_hi = val; + return opt4060_set_thresholds(chip, th_lo, th_hi); + case IIO_EV_INFO_PERIOD: + return opt4060_write_ev_period(chip, val, val2); + default: + return -EINVAL; + } +} + +static int opt4060_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + int ch_sel, ch_idx = chan->scan_index; + struct opt4060_chip *chip = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_INTENSITY) + return -EINVAL; + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + ret = opt4060_get_channel_sel(chip, &ch_sel); + if (ret) + return ret; + + if (((dir == IIO_EV_DIR_FALLING) && chip->thresh_event_lo_active) || + ((dir == IIO_EV_DIR_RISING) && chip->thresh_event_hi_active)) + return ch_sel == ch_idx; + + return ret; +} + +static int opt4060_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + int ch_sel, ch_idx = chan->scan_index; + struct opt4060_chip *chip = iio_priv(indio_dev); + int ret; + + guard(mutex)(&chip->event_enabling_lock); + + if (chan->type != IIO_INTENSITY) + return -EINVAL; + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + ret = opt4060_get_channel_sel(chip, &ch_sel); + if (ret) + return ret; + + if (state) { + /* Only one channel can be active at the same time */ + if ((chip->thresh_event_lo_active || chip->thresh_event_hi_active) && + (ch_idx != ch_sel)) + return -EBUSY; + if (dir == IIO_EV_DIR_FALLING) + chip->thresh_event_lo_active = true; + else if (dir == IIO_EV_DIR_RISING) + chip->thresh_event_hi_active = true; + ret = opt4060_set_channel_sel(chip, ch_idx); + if (ret) + return ret; + } else { + if (ch_idx == ch_sel) { + if (dir == IIO_EV_DIR_FALLING) + chip->thresh_event_lo_active = false; + else if (dir == IIO_EV_DIR_RISING) + chip->thresh_event_hi_active = false; + } + } + + return opt4060_set_driver_state(indio_dev, + chip->thresh_event_hi_active | + chip->thresh_event_lo_active, + false); +} + +static const struct iio_info opt4060_info = { + .read_raw = opt4060_read_raw, + .write_raw = opt4060_write_raw, + .write_raw_get_fmt = opt4060_write_raw_get_fmt, + .read_avail = opt4060_read_available, + .read_event_value = opt4060_read_event, + .write_event_value = opt4060_write_event, + .read_event_config = opt4060_read_event_config, + .write_event_config = opt4060_write_event_config, +}; + +static const struct iio_info opt4060_info_no_irq = { + .read_raw = opt4060_read_raw, + .write_raw = opt4060_write_raw, + .write_raw_get_fmt = opt4060_write_raw_get_fmt, + .read_avail = opt4060_read_available, +}; + +static int opt4060_load_defaults(struct opt4060_chip *chip) +{ + u16 reg; + int ret; + + chip->int_time = OPT4060_DEFAULT_CONVERSION_TIME; + + /* Set initial MIN/MAX thresholds */ + ret = opt4060_set_thresholds(chip, 0, UINT_MAX); + if (ret) + return ret; + + /* + * Setting auto-range, latched window for thresholds, one-shot conversion + * and quick wake-up mode as default. + */ + reg = FIELD_PREP(OPT4060_CTRL_RANGE_MASK, + OPT4060_CTRL_LIGHT_SCALE_AUTO); + reg |= FIELD_PREP(OPT4060_CTRL_CONV_TIME_MASK, chip->int_time); + reg |= FIELD_PREP(OPT4060_CTRL_OPER_MODE_MASK, + OPT4060_CTRL_OPER_MODE_ONE_SHOT); + reg |= OPT4060_CTRL_QWAKE_MASK | OPT4060_CTRL_LATCH_MASK; + + ret = regmap_write(chip->regmap, OPT4060_CTRL, reg); + if (ret) + dev_err(chip->dev, "Failed to set configuration\n"); + + return ret; +} + +static bool opt4060_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg <= OPT4060_CLEAR_LSB || reg == OPT4060_RES_CTRL; +} + +static bool opt4060_writable_reg(struct device *dev, unsigned int reg) +{ + return reg >= OPT4060_THRESHOLD_LOW || reg >= OPT4060_INT_CTRL; +} + +static bool opt4060_readonly_reg(struct device *dev, unsigned int reg) +{ + return reg == OPT4060_DEVICE_ID; +} + +static bool opt4060_readable_reg(struct device *dev, unsigned int reg) +{ + /* Volatile, writable and read-only registers are readable. */ + return opt4060_volatile_reg(dev, reg) || opt4060_writable_reg(dev, reg) || + opt4060_readonly_reg(dev, reg); +} + +static const struct regmap_config opt4060_regmap_config = { + .name = "opt4060", + .reg_bits = 8, + .val_bits = 16, + .cache_type = REGCACHE_MAPLE, + .max_register = OPT4060_DEVICE_ID, + .readable_reg = opt4060_readable_reg, + .writeable_reg = opt4060_writable_reg, + .volatile_reg = opt4060_volatile_reg, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + +static const struct iio_trigger_ops opt4060_trigger_ops = { + .set_trigger_state = opt4060_trigger_set_state, +}; + +static irqreturn_t opt4060_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *idev = pf->indio_dev; + struct opt4060_chip *chip = iio_priv(idev); + struct { + u32 chan[OPT4060_NUM_CHANS]; + aligned_s64 ts; + } raw = { }; + int i = 0; + int chan, ret; + + /* If the trigger is not from this driver, a new sample is needed.*/ + if (iio_trigger_validate_own_device(idev->trig, idev)) + opt4060_trigger_new_samples(idev); + + iio_for_each_active_channel(idev, chan) { + if (chan == OPT4060_ILLUM) + ret = opt4060_calc_illuminance(chip, &raw.chan[i++]); + else + ret = opt4060_read_raw_value(chip, + idev->channels[chan].address, + &raw.chan[i++]); + if (ret) { + dev_err(chip->dev, "Reading channel data failed\n"); + goto err_read; + } + } + + iio_push_to_buffers_with_ts(idev, &raw, sizeof(raw), pf->timestamp); +err_read: + iio_trigger_notify_done(idev->trig); + return IRQ_HANDLED; +} + +static irqreturn_t opt4060_irq_thread(int irq, void *private) +{ + struct iio_dev *idev = private; + struct opt4060_chip *chip = iio_priv(idev); + int ret, dummy; + unsigned int int_res; + + ret = regmap_read(chip->regmap, OPT4060_RES_CTRL, &int_res); + if (ret < 0) { + dev_err(chip->dev, "Failed to read interrupt reasons.\n"); + return IRQ_NONE; + } + + /* Read OPT4060_CTRL to clear interrupt */ + ret = regmap_read(chip->regmap, OPT4060_CTRL, &dummy); + if (ret < 0) { + dev_err(chip->dev, "Failed to clear interrupt\n"); + return IRQ_NONE; + } + + /* Handle events */ + if (int_res & (OPT4060_RES_CTRL_FLAG_H | OPT4060_RES_CTRL_FLAG_L)) { + u64 code; + int chan = 0; + + ret = opt4060_get_channel_sel(chip, &chan); + if (ret) { + dev_err(chip->dev, "Failed to read threshold channel.\n"); + return IRQ_NONE; + } + + /* Check if the interrupt is from the lower threshold */ + if (int_res & OPT4060_RES_CTRL_FLAG_L) { + code = IIO_MOD_EVENT_CODE(IIO_INTENSITY, + chan, + idev->channels[chan].channel2, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING); + iio_push_event(idev, code, iio_get_time_ns(idev)); + } + /* Check if the interrupt is from the upper threshold */ + if (int_res & OPT4060_RES_CTRL_FLAG_H) { + code = IIO_MOD_EVENT_CODE(IIO_INTENSITY, + chan, + idev->channels[chan].channel2, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING); + iio_push_event(idev, code, iio_get_time_ns(idev)); + } + } + + /* Handle conversion ready */ + if (int_res & OPT4060_RES_CTRL_CONV_READY) { + /* Signal completion for potentially waiting reads */ + complete(&chip->completion); + + /* Handle data ready triggers */ + if (iio_buffer_enabled(idev)) + iio_trigger_poll_nested(chip->trig); + } + return IRQ_HANDLED; +} + +static int opt4060_setup_buffer(struct opt4060_chip *chip, struct iio_dev *idev) +{ + int ret; + + ret = devm_iio_triggered_buffer_setup(chip->dev, idev, + &iio_pollfunc_store_time, + opt4060_trigger_handler, NULL); + if (ret) + return dev_err_probe(chip->dev, ret, + "Buffer setup failed.\n"); + return ret; +} + +static int opt4060_setup_trigger(struct opt4060_chip *chip, struct iio_dev *idev) +{ + struct iio_trigger *data_trigger; + char *name; + int ret; + + data_trigger = devm_iio_trigger_alloc(chip->dev, "%s-data-ready-dev%d", + idev->name, iio_device_id(idev)); + if (!data_trigger) + return -ENOMEM; + + /* + * The data trigger allows for sample capture on each new conversion + * ready interrupt. + */ + chip->trig = data_trigger; + data_trigger->ops = &opt4060_trigger_ops; + iio_trigger_set_drvdata(data_trigger, idev); + ret = devm_iio_trigger_register(chip->dev, data_trigger); + if (ret) + return dev_err_probe(chip->dev, ret, + "Data ready trigger registration failed\n"); + + name = devm_kasprintf(chip->dev, GFP_KERNEL, "%s-opt4060", + dev_name(chip->dev)); + if (!name) + return -ENOMEM; + + ret = devm_request_threaded_irq(chip->dev, chip->irq, NULL, opt4060_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + name, idev); + if (ret) + return ret; + + init_completion(&chip->completion); + + ret = devm_mutex_init(chip->dev, &chip->irq_setting_lock); + if (ret) + return ret; + + ret = devm_mutex_init(chip->dev, &chip->event_enabling_lock); + if (ret) + return ret; + + ret = regmap_write_bits(chip->regmap, OPT4060_INT_CTRL, + OPT4060_INT_CTRL_OUTPUT, + OPT4060_INT_CTRL_OUTPUT); + if (ret) + return dev_err_probe(chip->dev, ret, + "Failed to set interrupt as output\n"); + + return 0; +} + +static int opt4060_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct opt4060_chip *chip; + struct iio_dev *indio_dev; + int ret; + unsigned int regval, dev_id; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable vdd supply\n"); + + chip->regmap = devm_regmap_init_i2c(client, &opt4060_regmap_config); + if (IS_ERR(chip->regmap)) + return dev_err_probe(dev, PTR_ERR(chip->regmap), + "regmap initialization failed\n"); + + chip->dev = dev; + chip->irq = client->irq; + + ret = regmap_reinit_cache(chip->regmap, &opt4060_regmap_config); + if (ret) + return dev_err_probe(dev, ret, + "failed to reinit regmap cache\n"); + + ret = regmap_read(chip->regmap, OPT4060_DEVICE_ID, ®val); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to read the device ID register\n"); + + dev_id = FIELD_GET(OPT4060_DEVICE_ID_MASK, regval); + if (dev_id != OPT4060_DEVICE_ID_VAL) + dev_info(dev, "Device ID: %#04x unknown\n", dev_id); + + if (chip->irq) { + indio_dev->info = &opt4060_info; + indio_dev->channels = opt4060_channels; + indio_dev->num_channels = ARRAY_SIZE(opt4060_channels); + } else { + indio_dev->info = &opt4060_info_no_irq; + indio_dev->channels = opt4060_channels_no_events; + indio_dev->num_channels = ARRAY_SIZE(opt4060_channels_no_events); + } + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = "opt4060"; + + ret = opt4060_load_defaults(chip); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to set sensor defaults\n"); + + ret = devm_add_action_or_reset(dev, opt4060_chip_off_action, chip); + if (ret < 0) + return ret; + + ret = opt4060_setup_buffer(chip, indio_dev); + if (ret) + return ret; + + if (chip->irq) { + ret = opt4060_setup_trigger(chip, indio_dev); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct i2c_device_id opt4060_id[] = { + { .name = "opt4060" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, opt4060_id); + +static const struct of_device_id opt4060_of_match[] = { + { .compatible = "ti,opt4060" }, + { } +}; +MODULE_DEVICE_TABLE(of, opt4060_of_match); + +static struct i2c_driver opt4060_driver = { + .driver = { + .name = "opt4060", + .of_match_table = opt4060_of_match, + }, + .probe = opt4060_probe, + .id_table = opt4060_id, +}; +module_i2c_driver(opt4060_driver); + +MODULE_AUTHOR("Per-Daniel Olsson <perdaniel.olsson@axis.com>"); +MODULE_DESCRIPTION("Texas Instruments OPT4060 RGBW color sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/pa12203001.c b/drivers/iio/light/pa12203001.c new file mode 100644 index 000000000..c0f4db8d9 --- /dev/null +++ b/drivers/iio/light/pa12203001.c @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 Intel Corporation + * + * Driver for TXC PA12203001 Proximity and Ambient Light Sensor. + * + * To do: Interrupt support. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> + +#define PA12203001_DRIVER_NAME "pa12203001" + +#define PA12203001_REG_CFG0 0x00 +#define PA12203001_REG_CFG1 0x01 +#define PA12203001_REG_CFG2 0x02 +#define PA12203001_REG_CFG3 0x03 + +#define PA12203001_REG_ADL 0x0b +#define PA12203001_REG_PDH 0x0e + +#define PA12203001_REG_POFS 0x10 +#define PA12203001_REG_PSET 0x11 + +#define PA12203001_ALS_EN_MASK BIT(0) +#define PA12203001_PX_EN_MASK BIT(1) +#define PA12203001_PX_NORMAL_MODE_MASK GENMASK(7, 6) +#define PA12203001_AFSR_MASK GENMASK(5, 4) +#define PA12203001_AFSR_SHIFT 4 + +#define PA12203001_PSCAN 0x03 + +/* als range 31000, ps, als disabled */ +#define PA12203001_REG_CFG0_DEFAULT 0x30 + +/* led current: 100 mA */ +#define PA12203001_REG_CFG1_DEFAULT 0x20 + +/* ps mode: normal, interrupts not active */ +#define PA12203001_REG_CFG2_DEFAULT 0xcc + +#define PA12203001_REG_CFG3_DEFAULT 0x00 + +#define PA12203001_SLEEP_DELAY_MS 3000 + +#define PA12203001_CHIP_ENABLE 0xff +#define PA12203001_CHIP_DISABLE 0x00 + +/* available scales: corresponding to [500, 4000, 7000, 31000] lux */ +static const int pa12203001_scales[] = { 7629, 61036, 106813, 473029}; + +struct pa12203001_data { + struct i2c_client *client; + + /* protect device states */ + struct mutex lock; + + bool als_enabled; + bool px_enabled; + bool als_needs_enable; + bool px_needs_enable; + + struct regmap *map; +}; + +static const struct { + u8 reg; + u8 val; +} regvals[] = { + {PA12203001_REG_CFG0, PA12203001_REG_CFG0_DEFAULT}, + {PA12203001_REG_CFG1, PA12203001_REG_CFG1_DEFAULT}, + {PA12203001_REG_CFG2, PA12203001_REG_CFG2_DEFAULT}, + {PA12203001_REG_CFG3, PA12203001_REG_CFG3_DEFAULT}, + {PA12203001_REG_PSET, PA12203001_PSCAN}, +}; + +static IIO_CONST_ATTR(in_illuminance_scale_available, + "0.007629 0.061036 0.106813 0.473029"); + +static struct attribute *pa12203001_attrs[] = { + &iio_const_attr_in_illuminance_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group pa12203001_attr_group = { + .attrs = pa12203001_attrs, +}; + +static const struct iio_chan_spec pa12203001_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; + +static const struct regmap_range pa12203001_volatile_regs_ranges[] = { + regmap_reg_range(PA12203001_REG_ADL, PA12203001_REG_ADL + 1), + regmap_reg_range(PA12203001_REG_PDH, PA12203001_REG_PDH), +}; + +static const struct regmap_access_table pa12203001_volatile_regs = { + .yes_ranges = pa12203001_volatile_regs_ranges, + .n_yes_ranges = ARRAY_SIZE(pa12203001_volatile_regs_ranges), +}; + +static const struct regmap_config pa12203001_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = PA12203001_REG_PSET, + .cache_type = REGCACHE_RBTREE, + .volatile_table = &pa12203001_volatile_regs, +}; + +static inline int pa12203001_als_enable(struct pa12203001_data *data, u8 enable) +{ + int ret; + + ret = regmap_update_bits(data->map, PA12203001_REG_CFG0, + PA12203001_ALS_EN_MASK, enable); + if (ret < 0) + return ret; + + data->als_enabled = !!enable; + + return 0; +} + +static inline int pa12203001_px_enable(struct pa12203001_data *data, u8 enable) +{ + int ret; + + ret = regmap_update_bits(data->map, PA12203001_REG_CFG0, + PA12203001_PX_EN_MASK, enable); + if (ret < 0) + return ret; + + data->px_enabled = !!enable; + + return 0; +} + +static int pa12203001_set_power_state(struct pa12203001_data *data, bool on, + u8 mask) +{ +#ifdef CONFIG_PM + int ret; + + if (on && (mask & PA12203001_ALS_EN_MASK)) { + mutex_lock(&data->lock); + if (data->px_enabled) { + ret = pa12203001_als_enable(data, + PA12203001_ALS_EN_MASK); + if (ret < 0) + goto err; + } else { + data->als_needs_enable = true; + } + mutex_unlock(&data->lock); + } + + if (on && (mask & PA12203001_PX_EN_MASK)) { + mutex_lock(&data->lock); + if (data->als_enabled) { + ret = pa12203001_px_enable(data, PA12203001_PX_EN_MASK); + if (ret < 0) + goto err; + } else { + data->px_needs_enable = true; + } + mutex_unlock(&data->lock); + } + + if (on) + return pm_runtime_resume_and_get(&data->client->dev); + + return pm_runtime_put_autosuspend(&data->client->dev); + +err: + mutex_unlock(&data->lock); + return ret; + +#endif + return 0; +} + +static int pa12203001_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct pa12203001_data *data = iio_priv(indio_dev); + int ret; + u8 dev_mask; + unsigned int reg_byte; + __le16 reg_word; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + dev_mask = PA12203001_ALS_EN_MASK; + ret = pa12203001_set_power_state(data, true, dev_mask); + if (ret < 0) + return ret; + /* + * ALS ADC value is stored in registers + * PA12203001_REG_ADL and in PA12203001_REG_ADL + 1. + */ + ret = regmap_bulk_read(data->map, PA12203001_REG_ADL, + ®_word, 2); + if (ret < 0) + goto reg_err; + + *val = le16_to_cpu(reg_word); + ret = pa12203001_set_power_state(data, false, dev_mask); + if (ret < 0) + return ret; + break; + case IIO_PROXIMITY: + dev_mask = PA12203001_PX_EN_MASK; + ret = pa12203001_set_power_state(data, true, dev_mask); + if (ret < 0) + return ret; + ret = regmap_read(data->map, PA12203001_REG_PDH, + ®_byte); + if (ret < 0) + goto reg_err; + + *val = reg_byte; + ret = pa12203001_set_power_state(data, false, dev_mask); + if (ret < 0) + return ret; + break; + default: + return -EINVAL; + } + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(data->map, PA12203001_REG_CFG0, ®_byte); + if (ret < 0) + return ret; + *val = 0; + reg_byte = (reg_byte & PA12203001_AFSR_MASK); + *val2 = pa12203001_scales[reg_byte >> 4]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + +reg_err: + pa12203001_set_power_state(data, false, dev_mask); + return ret; +} + +static int pa12203001_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct pa12203001_data *data = iio_priv(indio_dev); + int i, ret, new_val; + unsigned int reg_byte; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(data->map, PA12203001_REG_CFG0, ®_byte); + if (val != 0 || ret < 0) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(pa12203001_scales); i++) { + if (val2 == pa12203001_scales[i]) { + new_val = i << PA12203001_AFSR_SHIFT; + return regmap_update_bits(data->map, + PA12203001_REG_CFG0, + PA12203001_AFSR_MASK, + new_val); + } + } + break; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_info pa12203001_info = { + .read_raw = pa12203001_read_raw, + .write_raw = pa12203001_write_raw, + .attrs = &pa12203001_attr_group, +}; + +static int pa12203001_init(struct iio_dev *indio_dev) +{ + struct pa12203001_data *data = iio_priv(indio_dev); + int i, ret; + + for (i = 0; i < ARRAY_SIZE(regvals); i++) { + ret = regmap_write(data->map, regvals[i].reg, regvals[i].val); + if (ret < 0) + return ret; + } + + return 0; +} + +static int pa12203001_power_chip(struct iio_dev *indio_dev, u8 state) +{ + struct pa12203001_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + ret = pa12203001_als_enable(data, state); + if (ret < 0) + goto out; + + ret = pa12203001_px_enable(data, state); + +out: + mutex_unlock(&data->lock); + return ret; +} + +static int pa12203001_probe(struct i2c_client *client) +{ + struct pa12203001_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, + sizeof(struct pa12203001_data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + data->map = devm_regmap_init_i2c(client, &pa12203001_regmap_config); + if (IS_ERR(data->map)) + return PTR_ERR(data->map); + + mutex_init(&data->lock); + + indio_dev->info = &pa12203001_info; + indio_dev->name = PA12203001_DRIVER_NAME; + indio_dev->channels = pa12203001_channels; + indio_dev->num_channels = ARRAY_SIZE(pa12203001_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = pa12203001_init(indio_dev); + if (ret < 0) + return ret; + + ret = pa12203001_power_chip(indio_dev, PA12203001_CHIP_ENABLE); + if (ret < 0) + return ret; + + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto out_err; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + PA12203001_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto out_err; + + return 0; + +out_err: + pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE); + return ret; +} + +static void pa12203001_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + int ret; + + iio_device_unregister(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + ret = pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE); + if (ret) + dev_warn(&client->dev, "Failed to power down (%pe)\n", + ERR_PTR(ret)); +} + +#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM) +static int pa12203001_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + + return pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE); +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int pa12203001_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + + return pa12203001_power_chip(indio_dev, PA12203001_CHIP_ENABLE); +} +#endif + +#ifdef CONFIG_PM +static int pa12203001_runtime_resume(struct device *dev) +{ + struct pa12203001_data *data; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + mutex_lock(&data->lock); + if (data->als_needs_enable) { + pa12203001_als_enable(data, PA12203001_ALS_EN_MASK); + data->als_needs_enable = false; + } + if (data->px_needs_enable) { + pa12203001_px_enable(data, PA12203001_PX_EN_MASK); + data->px_needs_enable = false; + } + mutex_unlock(&data->lock); + + return 0; +} +#endif + +static const struct dev_pm_ops pa12203001_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pa12203001_suspend, pa12203001_resume) + SET_RUNTIME_PM_OPS(pa12203001_suspend, pa12203001_runtime_resume, NULL) +}; + +static const struct acpi_device_id pa12203001_acpi_match[] = { + { "TXCPA122", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(acpi, pa12203001_acpi_match); + +static const struct i2c_device_id pa12203001_id[] = { + { .name = "txcpa122" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, pa12203001_id); + +static struct i2c_driver pa12203001_driver = { + .driver = { + .name = PA12203001_DRIVER_NAME, + .pm = &pa12203001_pm_ops, + .acpi_match_table = pa12203001_acpi_match, + }, + .probe = pa12203001_probe, + .remove = pa12203001_remove, + .id_table = pa12203001_id, + +}; +module_i2c_driver(pa12203001_driver); + +MODULE_AUTHOR("Adriana Reus <adriana.reus@intel.com>"); +MODULE_DESCRIPTION("Driver for TXC PA12203001 Proximity and Light Sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/rohm-bu27034.c b/drivers/iio/light/rohm-bu27034.c new file mode 100644 index 000000000..28d111ac8 --- /dev/null +++ b/drivers/iio/light/rohm-bu27034.c @@ -0,0 +1,1329 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BU27034ANUC ROHM Ambient Light Sensor + * + * Copyright (c) 2023, ROHM Semiconductor. + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/units.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/iio-gts-helper.h> +#include <linux/iio/kfifo_buf.h> + +#define BU27034_REG_SYSTEM_CONTROL 0x40 +#define BU27034_MASK_SW_RESET BIT(7) +#define BU27034_MASK_PART_ID GENMASK(5, 0) +#define BU27034_ID 0x19 +#define BU27034_REG_MODE_CONTROL1 0x41 +#define BU27034_MASK_MEAS_MODE GENMASK(2, 0) + +#define BU27034_REG_MODE_CONTROL2 0x42 +#define BU27034_MASK_D01_GAIN GENMASK(7, 3) + +#define BU27034_REG_MODE_CONTROL3 0x43 +#define BU27034_REG_MODE_CONTROL4 0x44 +#define BU27034_MASK_MEAS_EN BIT(0) +#define BU27034_MASK_VALID BIT(7) +#define BU27034_NUM_HW_DATA_CHANS 2 +#define BU27034_REG_DATA0_LO 0x50 +#define BU27034_REG_DATA1_LO 0x52 +#define BU27034_REG_DATA1_HI 0x53 +#define BU27034_REG_MANUFACTURER_ID 0x92 +#define BU27034_REG_MAX BU27034_REG_MANUFACTURER_ID + +/* + * The BU27034 does not have interrupt to trigger the data read when a + * measurement has finished. Hence we poll the VALID bit in a thread. We will + * try to wake the thread BU27034_MEAS_WAIT_PREMATURE_MS milliseconds before + * the expected sampling time to prevent the drifting. + * + * If we constantly wake up a bit too late we would eventually skip a sample. + * And because the sleep can't wake up _exactly_ at given time this would be + * inevitable even if the sensor clock would be perfectly phase-locked to CPU + * clock - which we can't say is the case. + * + * This is still fragile. No matter how big advance do we have, we will still + * risk of losing a sample because things can in a rainy-day scenario be + * delayed a lot. Yet, more we reserve the time for polling, more we also lose + * the performance by spending cycles polling the register. So, selecting this + * value is a balancing dance between severity of wasting CPU time and severity + * of losing samples. + * + * In most cases losing the samples is not _that_ crucial because light levels + * tend to change slowly. + * + * Other option that was pointed to me would be always sleeping 1/2 of the + * measurement time, checking the VALID bit and just sleeping again if the bit + * was not set. That should be pretty tolerant against missing samples due to + * the scheduling delays while also not wasting much of cycles for polling. + * Downside is that the time-stamps would be very inaccurate as the wake-up + * would not really be tied to the sensor toggling the valid bit. This would also + * result 'jumps' in the time-stamps when the delay drifted so that wake-up was + * performed during the consecutive wake-ups (Or, when sensor and CPU clocks + * were very different and scheduling the wake-ups was very close to given + * timeout - and when the time-outs were very close to the actual sensor + * sampling, Eg. once in a blue moon, two consecutive time-outs would occur + * without having a sample ready). + */ +#define BU27034_MEAS_WAIT_PREMATURE_MS 5 +#define BU27034_DATA_WAIT_TIME_US 1000 +#define BU27034_TOTAL_DATA_WAIT_TIME_US (BU27034_MEAS_WAIT_PREMATURE_MS * 1000) + +#define BU27034_RETRY_LIMIT 18 + +enum { + BU27034_CHAN_ALS, + BU27034_CHAN_DATA0, + BU27034_CHAN_DATA1, + BU27034_NUM_CHANS +}; + +static const unsigned long bu27034_scan_masks[] = { + GENMASK(BU27034_CHAN_DATA1, BU27034_CHAN_DATA0), + GENMASK(BU27034_CHAN_DATA1, BU27034_CHAN_ALS), 0 +}; + +/* + * Available scales with gain 1x - 1024x, timings 55, 100, 200, 400 mS + * Time impacts to gain: 1x, 2x, 4x, 8x. + * + * => Max total gain is HWGAIN * gain by integration time (8 * 1024) = 8192 + * if 1x gain is scale 1, scale for 2x gain is 0.5, 4x => 0.25, + * ... 8192x => 0.0001220703125 => 122070.3125 nanos + * + * Using NANO precision for scale, we must use scale 16x corresponding gain 1x + * to avoid precision loss. (8x would result scale 976 562.5(nanos). + */ +#define BU27034_SCALE_1X 16 + +/* See the data sheet for the "Gain Setting" table */ +#define BU27034_GSEL_1X 0x00 /* 00000 */ +#define BU27034_GSEL_4X 0x08 /* 01000 */ +#define BU27034_GSEL_32X 0x0b /* 01011 */ +#define BU27034_GSEL_256X 0x18 /* 11000 */ +#define BU27034_GSEL_512X 0x19 /* 11001 */ +#define BU27034_GSEL_1024X 0x1a /* 11010 */ + +/* Available gain settings */ +static const struct iio_gain_sel_pair bu27034_gains[] = { + GAIN_SCALE_GAIN(1, BU27034_GSEL_1X), + GAIN_SCALE_GAIN(4, BU27034_GSEL_4X), + GAIN_SCALE_GAIN(32, BU27034_GSEL_32X), + GAIN_SCALE_GAIN(256, BU27034_GSEL_256X), + GAIN_SCALE_GAIN(512, BU27034_GSEL_512X), + GAIN_SCALE_GAIN(1024, BU27034_GSEL_1024X), +}; + +/* + * Measurement modes are 55, 100, 200 and 400 mS modes - which do have direct + * multiplying impact to the data register values (similar to gain). + * + * This means that if meas-mode is changed for example from 400 => 200, + * the scale is doubled. Eg, time impact to total gain is x1, x2, x4, x8. + */ +#define BU27034_MEAS_MODE_100MS 0 +#define BU27034_MEAS_MODE_55MS 1 +#define BU27034_MEAS_MODE_200MS 2 +#define BU27034_MEAS_MODE_400MS 4 + +static const struct iio_itime_sel_mul bu27034_itimes[] = { + GAIN_SCALE_ITIME_US(400000, BU27034_MEAS_MODE_400MS, 8), + GAIN_SCALE_ITIME_US(200000, BU27034_MEAS_MODE_200MS, 4), + GAIN_SCALE_ITIME_US(100000, BU27034_MEAS_MODE_100MS, 2), + GAIN_SCALE_ITIME_US(55000, BU27034_MEAS_MODE_55MS, 1), +}; + +#define BU27034_CHAN_DATA(_name) \ +{ \ + .type = IIO_INTENSITY, \ + .channel = BU27034_CHAN_##_name, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .address = BU27034_REG_##_name##_LO, \ + .scan_index = BU27034_CHAN_##_name, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .indexed = 1, \ +} + +static const struct iio_chan_spec bu27034_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .channel = BU27034_CHAN_ALS, + .scan_index = BU27034_CHAN_ALS, + .scan_type = { + .sign = 'u', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + /* + * The BU27034 DATA0 and DATA1 channels are both on the visible light + * area (mostly). The data0 sensitivity peaks at 500nm, DATA1 at 600nm. + * These wave lengths are cyan(ish) and orange(ish), making these + * sub-optiomal candidates for R/G/B standardization. Hence the + * colour modifier is omitted. + */ + BU27034_CHAN_DATA(DATA0), + BU27034_CHAN_DATA(DATA1), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +struct bu27034_data { + struct regmap *regmap; + struct device *dev; + /* + * Protect gain and time during scale adjustment and data reading. + * Protect measurement enabling/disabling. + */ + struct mutex mutex; + struct iio_gts gts; + struct task_struct *task; + __le16 raw[BU27034_NUM_HW_DATA_CHANS]; + struct { + u32 mlux; + __le16 channels[BU27034_NUM_HW_DATA_CHANS]; + aligned_s64 ts; + } scan; +}; + +static const struct regmap_range bu27034_volatile_ranges[] = { + { + .range_min = BU27034_REG_SYSTEM_CONTROL, + .range_max = BU27034_REG_SYSTEM_CONTROL, + }, { + .range_min = BU27034_REG_MODE_CONTROL4, + .range_max = BU27034_REG_MODE_CONTROL4, + }, { + .range_min = BU27034_REG_DATA0_LO, + .range_max = BU27034_REG_DATA1_HI, + }, +}; + +static const struct regmap_access_table bu27034_volatile_regs = { + .yes_ranges = &bu27034_volatile_ranges[0], + .n_yes_ranges = ARRAY_SIZE(bu27034_volatile_ranges), +}; + +static const struct regmap_range bu27034_read_only_ranges[] = { + { + .range_min = BU27034_REG_DATA0_LO, + .range_max = BU27034_REG_DATA1_HI, + }, { + .range_min = BU27034_REG_MANUFACTURER_ID, + .range_max = BU27034_REG_MANUFACTURER_ID, + } +}; + +static const struct regmap_access_table bu27034_ro_regs = { + .no_ranges = &bu27034_read_only_ranges[0], + .n_no_ranges = ARRAY_SIZE(bu27034_read_only_ranges), +}; + +static const struct regmap_config bu27034_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = BU27034_REG_MAX, + .cache_type = REGCACHE_RBTREE, + .volatile_table = &bu27034_volatile_regs, + .wr_table = &bu27034_ro_regs, +}; + +struct bu27034_gain_check { + int old_gain; + int new_gain; + int chan; +}; + +static int bu27034_get_gain_sel(struct bu27034_data *data, int chan) +{ + int reg[] = { + [BU27034_CHAN_DATA0] = BU27034_REG_MODE_CONTROL2, + [BU27034_CHAN_DATA1] = BU27034_REG_MODE_CONTROL3, + }; + int ret, val; + + ret = regmap_read(data->regmap, reg[chan], &val); + if (ret) + return ret; + + return FIELD_GET(BU27034_MASK_D01_GAIN, val); +} + +static int bu27034_get_gain(struct bu27034_data *data, int chan, int *gain) +{ + int ret, sel; + + ret = bu27034_get_gain_sel(data, chan); + if (ret < 0) + return ret; + + sel = ret; + + ret = iio_gts_find_gain_by_sel(&data->gts, sel); + if (ret < 0) { + dev_err(data->dev, "chan %u: unknown gain value 0x%x\n", chan, + sel); + + return ret; + } + + *gain = ret; + + return 0; +} + +static int bu27034_get_int_time(struct bu27034_data *data) +{ + int ret, sel; + + ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL1, &sel); + if (ret) + return ret; + + return iio_gts_find_int_time_by_sel(&data->gts, + sel & BU27034_MASK_MEAS_MODE); +} + +static int _bu27034_get_scale(struct bu27034_data *data, int channel, int *val, + int *val2) +{ + int gain, ret; + + ret = bu27034_get_gain(data, channel, &gain); + if (ret) + return ret; + + ret = bu27034_get_int_time(data); + if (ret < 0) + return ret; + + return iio_gts_get_scale(&data->gts, gain, ret, val, val2); +} + +static int bu27034_get_scale(struct bu27034_data *data, int channel, int *val, + int *val2) +{ + int ret; + + if (channel == BU27034_CHAN_ALS) { + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + } + + mutex_lock(&data->mutex); + ret = _bu27034_get_scale(data, channel, val, val2); + mutex_unlock(&data->mutex); + if (ret) + return ret; + + return IIO_VAL_INT_PLUS_NANO; +} + +/* Caller should hold the lock to protect lux reading */ +static int bu27034_write_gain_sel(struct bu27034_data *data, int chan, int sel) +{ + static const int reg[] = { + [BU27034_CHAN_DATA0] = BU27034_REG_MODE_CONTROL2, + [BU27034_CHAN_DATA1] = BU27034_REG_MODE_CONTROL3, + }; + int mask, val; + + val = FIELD_PREP(BU27034_MASK_D01_GAIN, sel); + mask = BU27034_MASK_D01_GAIN; + + return regmap_update_bits(data->regmap, reg[chan], mask, val); +} + +static int bu27034_set_gain(struct bu27034_data *data, int chan, int gain) +{ + int ret; + + ret = iio_gts_find_sel_by_gain(&data->gts, gain); + if (ret < 0) + return ret; + + return bu27034_write_gain_sel(data, chan, ret); +} + +/* Caller should hold the lock to protect data->int_time */ +static int bu27034_set_int_time(struct bu27034_data *data, int time) +{ + int ret; + + ret = iio_gts_find_sel_by_int_time(&data->gts, time); + if (ret < 0) + return ret; + + return regmap_update_bits(data->regmap, BU27034_REG_MODE_CONTROL1, + BU27034_MASK_MEAS_MODE, ret); +} + +/* + * We try to change the time in such way that the scale is maintained for + * given channels by adjusting gain so that it compensates the time change. + */ +static int bu27034_try_set_int_time(struct bu27034_data *data, int time_us) +{ + struct bu27034_gain_check gains[] = { + { .chan = BU27034_CHAN_DATA0 }, + { .chan = BU27034_CHAN_DATA1 }, + }; + int numg = ARRAY_SIZE(gains); + int ret, int_time_old, i; + + guard(mutex)(&data->mutex); + ret = bu27034_get_int_time(data); + if (ret < 0) + return ret; + + int_time_old = ret; + + if (!iio_gts_valid_time(&data->gts, time_us)) { + dev_err(data->dev, "Unsupported integration time %u\n", + time_us); + return -EINVAL; + } + + if (time_us == int_time_old) + return 0; + + for (i = 0; i < numg; i++) { + ret = bu27034_get_gain(data, gains[i].chan, &gains[i].old_gain); + if (ret) + return 0; + + ret = iio_gts_find_new_gain_by_old_gain_time(&data->gts, + gains[i].old_gain, + int_time_old, time_us, + &gains[i].new_gain); + if (ret) { + int scale1, scale2; + bool ok; + + _bu27034_get_scale(data, gains[i].chan, &scale1, &scale2); + dev_dbg(data->dev, + "chan %u, can't support time %u with scale %u %u\n", + gains[i].chan, time_us, scale1, scale2); + + if (gains[i].new_gain < 0) + return ret; + + /* + * If caller requests for integration time change and we + * can't support the scale - then the caller should be + * prepared to 'pick up the pieces and deal with the + * fact that the scale changed'. + */ + ret = iio_find_closest_gain_low(&data->gts, + gains[i].new_gain, &ok); + + if (!ok) + dev_dbg(data->dev, + "optimal gain out of range for chan %u\n", + gains[i].chan); + + if (ret < 0) { + dev_dbg(data->dev, + "Total gain increase. Risk of saturation"); + ret = iio_gts_get_min_gain(&data->gts); + if (ret < 0) + return ret; + } + dev_dbg(data->dev, "chan %u scale changed\n", + gains[i].chan); + gains[i].new_gain = ret; + dev_dbg(data->dev, "chan %u new gain %u\n", + gains[i].chan, gains[i].new_gain); + } + } + + for (i = 0; i < numg; i++) { + ret = bu27034_set_gain(data, gains[i].chan, gains[i].new_gain); + if (ret) + return ret; + } + + return bu27034_set_int_time(data, time_us); +} + +static int bu27034_set_scale(struct bu27034_data *data, int chan, + int val, int val2) +{ + int ret, time_sel, gain_sel, i; + bool found = false; + + if (chan == BU27034_CHAN_ALS) { + if (val == 0 && val2 == 1000000) + return 0; + + return -EINVAL; + } + + guard(mutex)(&data->mutex); + ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL1, &time_sel); + if (ret) + return ret; + + ret = iio_gts_find_gain_sel_for_scale_using_time(&data->gts, time_sel, + val, val2, &gain_sel); + if (ret) { + /* + * Could not support scale with given time. Need to change time. + * We still want to maintain the scale for all channels + */ + struct bu27034_gain_check gain; + int new_time_sel; + + /* + * Populate information for the other channel which should also + * maintain the scale. + */ + if (chan == BU27034_CHAN_DATA0) + gain.chan = BU27034_CHAN_DATA1; + else if (chan == BU27034_CHAN_DATA1) + gain.chan = BU27034_CHAN_DATA0; + + ret = bu27034_get_gain(data, gain.chan, &gain.old_gain); + if (ret) + return ret; + + /* + * Iterate through all the times to see if we find one which + * can support requested scale for requested channel, while + * maintaining the scale for the other channel + */ + for (i = 0; i < data->gts.num_itime; i++) { + new_time_sel = data->gts.itime_table[i].sel; + + if (new_time_sel == time_sel) + continue; + + /* Can we provide requested scale with this time? */ + ret = iio_gts_find_gain_sel_for_scale_using_time( + &data->gts, new_time_sel, val, val2, + &gain_sel); + if (ret) + continue; + + /* Can the other channel maintain scale? */ + ret = iio_gts_find_new_gain_sel_by_old_gain_time( + &data->gts, gain.old_gain, time_sel, + new_time_sel, &gain.new_gain); + if (!ret) { + /* Yes - we found suitable time */ + found = true; + break; + } + } + if (!found) { + dev_dbg(data->dev, + "Can't set scale maintaining other channel\n"); + return -EINVAL; + } + + ret = bu27034_set_gain(data, gain.chan, gain.new_gain); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, BU27034_REG_MODE_CONTROL1, + BU27034_MASK_MEAS_MODE, new_time_sel); + if (ret) + return ret; + } + + return bu27034_write_gain_sel(data, chan, gain_sel); +} + +/* + * for (D1/D0 < 1.5): + * lx = (0.001193 * D0 + (-0.0000747) * D1) * ((D1/D0 – 1.5) * (0.25) + 1) + * + * => -0.000745625 * D0 + 0.0002515625 * D1 + -0.000018675 * D1 * D1 / D0 + * + * => (6.44 * ch1 / gain1 + 19.088 * ch0 / gain0 - + * 0.47808 * ch1 * ch1 * gain0 / gain1 / gain1 / ch0) / + * mt + * + * Else + * lx = 0.001193 * D0 - 0.0000747 * D1 + * + * => (1.91232 * ch1 / gain1 + 30.5408 * ch0 / gain0 + + * [0 * ch1 * ch1 * gain0 / gain1 / gain1 / ch0] ) / + * mt + * + * This can be unified to format: + * lx = [ + * A * ch1 * ch1 * gain0 / (ch0 * gain1 * gain1) + + * B * ch1 / gain1 + + * C * ch0 / gain0 + * ] / mt + * + * For case 1: + * A = -0.47808, + * B = 6.44, + * C = 19.088 + * + * For case 2: + * A = 0 + * B = 1.91232 + * C = 30.5408 + */ + +struct bu27034_lx_coeff { + unsigned int A; + unsigned int B; + unsigned int C; + /* Indicate which of the coefficients above are negative */ + bool is_neg[3]; +}; + +static inline u64 gain_mul_div_helper(u64 val, unsigned int gain, + unsigned int div) +{ + /* + * Max gain for a channel is 4096. The max u64 (0xffffffffffffffffULL) + * divided by 4096 is 0xFFFFFFFFFFFFF (GENMASK_ULL(51, 0)) (floored). + * Thus, the 0xFFFFFFFFFFFFF is the largest value we can safely multiply + * with the gain, no matter what gain is set. + * + * So, multiplication with max gain may overflow if val is greater than + * 0xFFFFFFFFFFFFF (52 bits set).. + * + * If this is the case we divide first. + */ + if (val < GENMASK_ULL(51, 0)) { + val *= gain; + do_div(val, div); + } else { + do_div(val, div); + val *= gain; + } + + return val; +} + +static u64 bu27034_fixp_calc_t1_64bit(unsigned int coeff, unsigned int ch0, + unsigned int ch1, unsigned int gain0, + unsigned int gain1) +{ + unsigned int helper; + u64 helper64; + + helper64 = (u64)coeff * (u64)ch1 * (u64)ch1; + + helper = gain1 * gain1; + if (helper > ch0) { + do_div(helper64, helper); + + return gain_mul_div_helper(helper64, gain0, ch0); + } + + do_div(helper64, ch0); + + return gain_mul_div_helper(helper64, gain0, helper); + +} + +static u64 bu27034_fixp_calc_t1(unsigned int coeff, unsigned int ch0, + unsigned int ch1, unsigned int gain0, + unsigned int gain1) +{ + unsigned int helper, tmp; + + /* + * Here we could overflow even the 64bit value. Hence we + * multiply with gain0 only after the divisions - even though + * it may result loss of accuracy + */ + helper = coeff * ch1 * ch1; + tmp = helper * gain0; + + helper = ch1 * ch1; + + if (check_mul_overflow(helper, coeff, &helper)) + return bu27034_fixp_calc_t1_64bit(coeff, ch0, ch1, gain0, gain1); + + if (check_mul_overflow(helper, gain0, &tmp)) + return bu27034_fixp_calc_t1_64bit(coeff, ch0, ch1, gain0, gain1); + + return tmp / (gain1 * gain1) / ch0; + +} + +static u64 bu27034_fixp_calc_t23(unsigned int coeff, unsigned int ch, + unsigned int gain) +{ + unsigned int helper; + u64 helper64; + + if (!check_mul_overflow(coeff, ch, &helper)) + return helper / gain; + + helper64 = (u64)coeff * (u64)ch; + do_div(helper64, gain); + + return helper64; +} + +static int bu27034_fixp_calc_lx(unsigned int ch0, unsigned int ch1, + unsigned int gain0, unsigned int gain1, + unsigned int meastime, int coeff_idx) +{ + static const struct bu27034_lx_coeff coeff[] = { + { + .A = 4780800, /* -0.47808 */ + .B = 64400000, /* 6.44 */ + .C = 190880000, /* 19.088 */ + .is_neg = { true, false, false }, + }, { + .A = 0, /* 0 */ + .B = 19123200, /* 1.91232 */ + .C = 305408000, /* 30.5408 */ + /* All terms positive */ + }, + }; + const struct bu27034_lx_coeff *c = &coeff[coeff_idx]; + u64 res = 0, terms[3]; + int i; + + if (coeff_idx >= ARRAY_SIZE(coeff)) + return -EINVAL; + + terms[0] = bu27034_fixp_calc_t1(c->A, ch0, ch1, gain0, gain1); + terms[1] = bu27034_fixp_calc_t23(c->B, ch1, gain1); + terms[2] = bu27034_fixp_calc_t23(c->C, ch0, gain0); + + /* First, add positive terms */ + for (i = 0; i < 3; i++) + if (!c->is_neg[i]) + res += terms[i]; + + /* No positive term => zero lux */ + if (!res) + return 0; + + /* Then, subtract negative terms (if any) */ + for (i = 0; i < 3; i++) + if (c->is_neg[i]) { + /* + * If the negative term is greater than positive - then + * the darkness has taken over and we are all doomed! Eh, + * I mean, then we can just return 0 lx and go out + */ + if (terms[i] >= res) + return 0; + + res -= terms[i]; + } + + meastime *= 10; + do_div(res, meastime); + + return (int) res; +} + +static bool bu27034_has_valid_sample(struct bu27034_data *data) +{ + int ret, val; + + ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL4, &val); + if (ret) { + dev_err(data->dev, "Read failed %d\n", ret); + + return false; + } + + return val & BU27034_MASK_VALID; +} + +/* + * Reading the register where VALID bit is clears this bit. (So does changing + * any gain / integration time configuration registers) The bit gets + * set when we have acquired new data. We use this bit to indicate data + * validity. + */ +static void bu27034_invalidate_read_data(struct bu27034_data *data) +{ + bu27034_has_valid_sample(data); +} + +static int bu27034_read_result(struct bu27034_data *data, int chan, int *res) +{ + int reg[] = { + [BU27034_CHAN_DATA0] = BU27034_REG_DATA0_LO, + [BU27034_CHAN_DATA1] = BU27034_REG_DATA1_LO, + }; + int valid, ret; + __le16 val; + + ret = regmap_read_poll_timeout(data->regmap, BU27034_REG_MODE_CONTROL4, + valid, (valid & BU27034_MASK_VALID), + BU27034_DATA_WAIT_TIME_US, 0); + if (ret) + return ret; + + ret = regmap_bulk_read(data->regmap, reg[chan], &val, sizeof(val)); + if (ret) + return ret; + + *res = le16_to_cpu(val); + + return 0; +} + +static int bu27034_get_result_unlocked(struct bu27034_data *data, __le16 *res, + int size) +{ + int ret = 0, retry_cnt = 0; + +retry: + /* Get new value from sensor if data is ready */ + if (bu27034_has_valid_sample(data)) { + ret = regmap_bulk_read(data->regmap, BU27034_REG_DATA0_LO, + res, size); + if (ret) + return ret; + + bu27034_invalidate_read_data(data); + } else { + /* No new data in sensor. Wait and retry */ + retry_cnt++; + + if (retry_cnt > BU27034_RETRY_LIMIT) { + dev_err(data->dev, "No data from sensor\n"); + + return -ETIMEDOUT; + } + + msleep(25); + + goto retry; + } + + return ret; +} + +static int bu27034_meas_set(struct bu27034_data *data, bool en) +{ + if (en) + return regmap_set_bits(data->regmap, BU27034_REG_MODE_CONTROL4, + BU27034_MASK_MEAS_EN); + + return regmap_clear_bits(data->regmap, BU27034_REG_MODE_CONTROL4, + BU27034_MASK_MEAS_EN); +} + +static int bu27034_get_single_result(struct bu27034_data *data, int chan, + int *val) +{ + int ret; + + if (chan < BU27034_CHAN_DATA0 || chan > BU27034_CHAN_DATA1) + return -EINVAL; + + ret = bu27034_meas_set(data, true); + if (ret) + return ret; + + ret = bu27034_get_int_time(data); + if (ret < 0) + return ret; + + msleep(ret / 1000); + + return bu27034_read_result(data, chan, val); +} + +/* + * The formula given by vendor for computing luxes out of data0 and data1 + * (in open air) is as follows: + * + * Let's mark: + * D0 = data0/ch0_gain/meas_time_ms * 25600 + * D1 = data1/ch1_gain/meas_time_ms * 25600 + * + * Then: + * If (D1/D0 < 1.5) + * lx = (0.001193 * D0 + (-0.0000747) * D1) * ((D1 / D0 – 1.5) * 0.25 + 1) + * Else + * lx = (0.001193 * D0 + (-0.0000747) * D1) + * + * We use it here. Users who have for example some colored lens + * need to modify the calculation but I hope this gives a starting point for + * those working with such devices. + */ + +static int bu27034_calc_mlux(struct bu27034_data *data, __le16 *res, int *val) +{ + unsigned int gain0, gain1, meastime; + unsigned int d1_d0_ratio_scaled; + u16 ch0, ch1; + u64 helper64; + int ret; + + /* + * We return 0 lux if calculation fails. This should be reasonably + * easy to spot from the buffers especially if raw-data channels show + * valid values + */ + *val = 0; + + ch0 = max_t(u16, 1, le16_to_cpu(res[0])); + ch1 = max_t(u16, 1, le16_to_cpu(res[1])); + + ret = bu27034_get_gain(data, BU27034_CHAN_DATA0, &gain0); + if (ret) + return ret; + + ret = bu27034_get_gain(data, BU27034_CHAN_DATA1, &gain1); + if (ret) + return ret; + + ret = bu27034_get_int_time(data); + if (ret < 0) + return ret; + + meastime = ret; + + d1_d0_ratio_scaled = (unsigned int)ch1 * (unsigned int)gain0 * 100; + helper64 = (u64)ch1 * (u64)gain0 * 100LLU; + + if (helper64 != d1_d0_ratio_scaled) { + unsigned int div = (unsigned int)ch0 * gain1; + + do_div(helper64, div); + d1_d0_ratio_scaled = helper64; + } else { + d1_d0_ratio_scaled /= ch0 * gain1; + } + + if (d1_d0_ratio_scaled < 150) + ret = bu27034_fixp_calc_lx(ch0, ch1, gain0, gain1, meastime, 0); + else + ret = bu27034_fixp_calc_lx(ch0, ch1, gain0, gain1, meastime, 1); + + if (ret < 0) + return ret; + + *val = ret; + + return 0; + +} + +static int bu27034_get_mlux(struct bu27034_data *data, int chan, int *val) +{ + __le16 res[BU27034_NUM_HW_DATA_CHANS]; + int ret; + + ret = bu27034_meas_set(data, true); + if (ret) + return ret; + + ret = bu27034_get_result_unlocked(data, &res[0], sizeof(res)); + if (ret) + return ret; + + ret = bu27034_calc_mlux(data, res, val); + if (ret) + return ret; + + ret = bu27034_meas_set(data, false); + if (ret) + dev_err(data->dev, "failed to disable measurement\n"); + + return 0; +} + +static int bu27034_read_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bu27034_data *data = iio_priv(idev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = bu27034_get_int_time(data); + if (*val2 < 0) + return *val2; + + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_HARDWAREGAIN: + ret = bu27034_get_gain(data, chan->channel, val); + if (ret) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + return bu27034_get_scale(data, chan->channel, val, val2); + + case IIO_CHAN_INFO_RAW: + { + int (*result_get)(struct bu27034_data *data, int chan, int *val); + + if (chan->type == IIO_INTENSITY) + result_get = bu27034_get_single_result; + else if (chan->type == IIO_LIGHT) + result_get = bu27034_get_mlux; + else + return -EINVAL; + + /* Don't mess with measurement enabling while buffering */ + if (!iio_device_claim_direct(idev)) + return -EBUSY; + + mutex_lock(&data->mutex); + /* + * Reading one channel at a time is inefficient but we + * don't care here. Buffered version should be used if + * performance is an issue. + */ + ret = result_get(data, chan->channel, val); + + mutex_unlock(&data->mutex); + iio_device_release_direct(idev); + + if (ret) + return ret; + + return IIO_VAL_INT; + } + default: + return -EINVAL; + } +} + +static int bu27034_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + struct bu27034_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_INT_TIME: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_HARDWAREGAIN: + dev_dbg(data->dev, + "HARDWAREGAIN is read-only, use scale to set\n"); + return -EINVAL; + default: + return -EINVAL; + } +} + +static int bu27034_write_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bu27034_data *data = iio_priv(idev); + int ret; + + if (!iio_device_claim_direct(idev)) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + ret = bu27034_set_scale(data, chan->channel, val, val2); + break; + case IIO_CHAN_INFO_INT_TIME: + if (!val) + ret = bu27034_try_set_int_time(data, val2); + else + ret = -EINVAL; + break; + default: + ret = -EINVAL; + break; + } + + iio_device_release_direct(idev); + + return ret; +} + +static int bu27034_read_avail(struct iio_dev *idev, + struct iio_chan_spec const *chan, const int **vals, + int *type, int *length, long mask) +{ + struct bu27034_data *data = iio_priv(idev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return iio_gts_avail_times(&data->gts, vals, type, length); + case IIO_CHAN_INFO_SCALE: + return iio_gts_all_avail_scales(&data->gts, vals, type, length); + default: + return -EINVAL; + } +} + +static const struct iio_info bu27034_info = { + .read_raw = &bu27034_read_raw, + .write_raw = &bu27034_write_raw, + .write_raw_get_fmt = &bu27034_write_raw_get_fmt, + .read_avail = &bu27034_read_avail, +}; + +static int bu27034_chip_init(struct bu27034_data *data) +{ + int ret, sel; + + /* Reset */ + ret = regmap_write_bits(data->regmap, BU27034_REG_SYSTEM_CONTROL, + BU27034_MASK_SW_RESET, BU27034_MASK_SW_RESET); + if (ret) + return dev_err_probe(data->dev, ret, "Sensor reset failed\n"); + + msleep(1); + + ret = regmap_reinit_cache(data->regmap, &bu27034_regmap); + if (ret) { + dev_err(data->dev, "Failed to reinit reg cache\n"); + return ret; + } + + /* + * Read integration time here to ensure it is in regmap cache. We do + * this to speed-up the int-time acquisition in the start of the buffer + * handling thread where longer delays could make it more likely we end + * up skipping a sample, and where the longer delays make timestamps + * less accurate. + */ + ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL1, &sel); + if (ret) + dev_err(data->dev, "reading integration time failed\n"); + + return 0; +} + +static int bu27034_wait_for_data(struct bu27034_data *data) +{ + int ret, val; + + ret = regmap_read_poll_timeout(data->regmap, BU27034_REG_MODE_CONTROL4, + val, val & BU27034_MASK_VALID, + BU27034_DATA_WAIT_TIME_US, + BU27034_TOTAL_DATA_WAIT_TIME_US); + if (ret) { + dev_err(data->dev, "data polling %s\n", + !(val & BU27034_MASK_VALID) ? "timeout" : "fail"); + + return ret; + } + + ret = regmap_bulk_read(data->regmap, BU27034_REG_DATA0_LO, + &data->scan.channels[0], + sizeof(data->scan.channels)); + if (ret) + return ret; + + bu27034_invalidate_read_data(data); + + return 0; +} + +static int bu27034_buffer_thread(void *arg) +{ + struct iio_dev *idev = arg; + struct bu27034_data *data; + int wait_ms; + + data = iio_priv(idev); + + wait_ms = bu27034_get_int_time(data); + wait_ms /= 1000; + + wait_ms -= BU27034_MEAS_WAIT_PREMATURE_MS; + + while (!kthread_should_stop()) { + int ret; + int64_t tstamp; + + msleep(wait_ms); + ret = bu27034_wait_for_data(data); + if (ret) + continue; + + tstamp = iio_get_time_ns(idev); + + if (test_bit(BU27034_CHAN_ALS, idev->active_scan_mask)) { + int mlux; + + ret = bu27034_calc_mlux(data, &data->scan.channels[0], + &mlux); + if (ret) + dev_err(data->dev, "failed to calculate lux\n"); + + /* + * The maximum Milli lux value we get with gain 1x time + * 55mS data ch0 = 0xffff ch1 = 0xffff fits in 26 bits + * so there should be no problem returning int from + * computations and casting it to u32 + */ + data->scan.mlux = (u32)mlux; + } + iio_push_to_buffers_with_ts(idev, &data->scan, + sizeof(data->scan), tstamp); + } + + return 0; +} + +static int bu27034_buffer_enable(struct iio_dev *idev) +{ + struct bu27034_data *data = iio_priv(idev); + struct task_struct *task; + int ret; + + guard(mutex)(&data->mutex); + ret = bu27034_meas_set(data, true); + if (ret) + return ret; + + task = kthread_run(bu27034_buffer_thread, idev, + "bu27034-buffering-%u", + iio_device_id(idev)); + if (IS_ERR(task)) + return PTR_ERR(task); + + data->task = task; + + return 0; +} + +static int bu27034_buffer_disable(struct iio_dev *idev) +{ + struct bu27034_data *data = iio_priv(idev); + + guard(mutex)(&data->mutex); + if (data->task) { + kthread_stop(data->task); + data->task = NULL; + } + + return bu27034_meas_set(data, false); +} + +static const struct iio_buffer_setup_ops bu27034_buffer_ops = { + .postenable = &bu27034_buffer_enable, + .predisable = &bu27034_buffer_disable, +}; + +static int bu27034_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct bu27034_data *data; + struct regmap *regmap; + struct iio_dev *idev; + unsigned int part_id, reg; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &bu27034_regmap); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to initialize Regmap\n"); + + idev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!idev) + return -ENOMEM; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulator\n"); + + data = iio_priv(idev); + + ret = regmap_read(regmap, BU27034_REG_SYSTEM_CONTROL, ®); + if (ret) + return dev_err_probe(dev, ret, "Failed to access sensor\n"); + + part_id = FIELD_GET(BU27034_MASK_PART_ID, reg); + + if (part_id != BU27034_ID) + dev_warn(dev, "unknown device 0x%x\n", part_id); + + ret = devm_iio_init_iio_gts(dev, BU27034_SCALE_1X, 0, bu27034_gains, + ARRAY_SIZE(bu27034_gains), bu27034_itimes, + ARRAY_SIZE(bu27034_itimes), &data->gts); + if (ret) + return ret; + + mutex_init(&data->mutex); + data->regmap = regmap; + data->dev = dev; + + idev->channels = bu27034_channels; + idev->num_channels = ARRAY_SIZE(bu27034_channels); + idev->name = "bu27034"; + idev->info = &bu27034_info; + + idev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + idev->available_scan_masks = bu27034_scan_masks; + + ret = bu27034_chip_init(data); + if (ret) + return ret; + + ret = devm_iio_kfifo_buffer_setup(dev, idev, &bu27034_buffer_ops); + if (ret) + return dev_err_probe(dev, ret, "buffer setup failed\n"); + + ret = devm_iio_device_register(dev, idev); + if (ret < 0) + return dev_err_probe(dev, ret, + "Unable to register iio device\n"); + + return ret; +} + +static const struct of_device_id bu27034_of_match[] = { + { .compatible = "rohm,bu27034anuc" }, + { } +}; +MODULE_DEVICE_TABLE(of, bu27034_of_match); + +static struct i2c_driver bu27034_i2c_driver = { + .driver = { + .name = "bu27034-als", + .of_match_table = bu27034_of_match, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = bu27034_probe, +}; +module_i2c_driver(bu27034_i2c_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>"); +MODULE_DESCRIPTION("ROHM BU27034 ambient light sensor driver"); +MODULE_IMPORT_NS("IIO_GTS_HELPER"); diff --git a/drivers/iio/light/rpr0521.c b/drivers/iio/light/rpr0521.c new file mode 100644 index 000000000..7d82efc31 --- /dev/null +++ b/drivers/iio/light/rpr0521.c @@ -0,0 +1,1125 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * RPR-0521 ROHM Ambient Light and Proximity Sensor + * + * Copyright (c) 2015, Intel Corporation. + * + * IIO driver for RPR-0521RS (7-bit I2C slave address 0x38). + * + * TODO: illuminance channel + */ + +#include <linux/module.h> +#include <linux/cleanup.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/sysfs.h> +#include <linux/pm_runtime.h> + +#define RPR0521_REG_SYSTEM_CTRL 0x40 +#define RPR0521_REG_MODE_CTRL 0x41 +#define RPR0521_REG_ALS_CTRL 0x42 +#define RPR0521_REG_PXS_CTRL 0x43 +#define RPR0521_REG_PXS_DATA 0x44 /* 16-bit, little endian */ +#define RPR0521_REG_ALS_DATA0 0x46 /* 16-bit, little endian */ +#define RPR0521_REG_ALS_DATA1 0x48 /* 16-bit, little endian */ +#define RPR0521_REG_INTERRUPT 0x4A +#define RPR0521_REG_PS_OFFSET_LSB 0x53 +#define RPR0521_REG_ID 0x92 + +#define RPR0521_MODE_ALS_MASK BIT(7) +#define RPR0521_MODE_PXS_MASK BIT(6) +#define RPR0521_MODE_MEAS_TIME_MASK GENMASK(3, 0) +#define RPR0521_ALS_DATA0_GAIN_MASK GENMASK(5, 4) +#define RPR0521_ALS_DATA0_GAIN_SHIFT 4 +#define RPR0521_ALS_DATA1_GAIN_MASK GENMASK(3, 2) +#define RPR0521_ALS_DATA1_GAIN_SHIFT 2 +#define RPR0521_PXS_GAIN_MASK GENMASK(5, 4) +#define RPR0521_PXS_GAIN_SHIFT 4 +#define RPR0521_PXS_PERSISTENCE_MASK GENMASK(3, 0) +#define RPR0521_INTERRUPT_INT_TRIG_PS_MASK BIT(0) +#define RPR0521_INTERRUPT_INT_TRIG_ALS_MASK BIT(1) +#define RPR0521_INTERRUPT_INT_REASSERT_MASK BIT(3) +#define RPR0521_INTERRUPT_ALS_INT_STATUS_MASK BIT(6) +#define RPR0521_INTERRUPT_PS_INT_STATUS_MASK BIT(7) + +#define RPR0521_MODE_ALS_ENABLE BIT(7) +#define RPR0521_MODE_ALS_DISABLE 0x00 +#define RPR0521_MODE_PXS_ENABLE BIT(6) +#define RPR0521_MODE_PXS_DISABLE 0x00 +#define RPR0521_PXS_PERSISTENCE_DRDY 0x00 + +#define RPR0521_INTERRUPT_INT_TRIG_PS_ENABLE BIT(0) +#define RPR0521_INTERRUPT_INT_TRIG_PS_DISABLE 0x00 +#define RPR0521_INTERRUPT_INT_TRIG_ALS_ENABLE BIT(1) +#define RPR0521_INTERRUPT_INT_TRIG_ALS_DISABLE 0x00 +#define RPR0521_INTERRUPT_INT_REASSERT_ENABLE BIT(3) +#define RPR0521_INTERRUPT_INT_REASSERT_DISABLE 0x00 + +#define RPR0521_MANUFACT_ID 0xE0 +#define RPR0521_DEFAULT_MEAS_TIME 0x06 /* ALS - 100ms, PXS - 100ms */ + +#define RPR0521_DRV_NAME "RPR0521" + +#define RPR0521_SLEEP_DELAY_MS 2000 + +#define RPR0521_ALS_SCALE_AVAIL "0.007812 0.015625 0.5 1" +#define RPR0521_PXS_SCALE_AVAIL "0.125 0.5 1" + +struct rpr0521_gain { + int scale; + int uscale; +}; + +static const struct rpr0521_gain rpr0521_als_gain[4] = { + {1, 0}, /* x1 */ + {0, 500000}, /* x2 */ + {0, 15625}, /* x64 */ + {0, 7812}, /* x128 */ +}; + +static const struct rpr0521_gain rpr0521_pxs_gain[3] = { + {1, 0}, /* x1 */ + {0, 500000}, /* x2 */ + {0, 125000}, /* x4 */ +}; + +enum rpr0521_channel { + RPR0521_CHAN_PXS, + RPR0521_CHAN_ALS_DATA0, + RPR0521_CHAN_ALS_DATA1, +}; + +struct rpr0521_reg_desc { + u8 address; + u8 device_mask; +}; + +static const struct rpr0521_reg_desc rpr0521_data_reg[] = { + [RPR0521_CHAN_PXS] = { + .address = RPR0521_REG_PXS_DATA, + .device_mask = RPR0521_MODE_PXS_MASK, + }, + [RPR0521_CHAN_ALS_DATA0] = { + .address = RPR0521_REG_ALS_DATA0, + .device_mask = RPR0521_MODE_ALS_MASK, + }, + [RPR0521_CHAN_ALS_DATA1] = { + .address = RPR0521_REG_ALS_DATA1, + .device_mask = RPR0521_MODE_ALS_MASK, + }, +}; + +static const struct rpr0521_gain_info { + u8 reg; + u8 mask; + u8 shift; + const struct rpr0521_gain *gain; + int size; +} rpr0521_gain[] = { + [RPR0521_CHAN_PXS] = { + .reg = RPR0521_REG_PXS_CTRL, + .mask = RPR0521_PXS_GAIN_MASK, + .shift = RPR0521_PXS_GAIN_SHIFT, + .gain = rpr0521_pxs_gain, + .size = ARRAY_SIZE(rpr0521_pxs_gain), + }, + [RPR0521_CHAN_ALS_DATA0] = { + .reg = RPR0521_REG_ALS_CTRL, + .mask = RPR0521_ALS_DATA0_GAIN_MASK, + .shift = RPR0521_ALS_DATA0_GAIN_SHIFT, + .gain = rpr0521_als_gain, + .size = ARRAY_SIZE(rpr0521_als_gain), + }, + [RPR0521_CHAN_ALS_DATA1] = { + .reg = RPR0521_REG_ALS_CTRL, + .mask = RPR0521_ALS_DATA1_GAIN_MASK, + .shift = RPR0521_ALS_DATA1_GAIN_SHIFT, + .gain = rpr0521_als_gain, + .size = ARRAY_SIZE(rpr0521_als_gain), + }, +}; + +struct rpr0521_samp_freq { + int als_hz; + int als_uhz; + int pxs_hz; + int pxs_uhz; +}; + +static const struct rpr0521_samp_freq rpr0521_samp_freq_i[13] = { +/* {ALS, PXS}, W==currently writable option */ + {0, 0, 0, 0}, /* W0000, 0=standby */ + {0, 0, 100, 0}, /* 0001 */ + {0, 0, 25, 0}, /* 0010 */ + {0, 0, 10, 0}, /* 0011 */ + {0, 0, 2, 500000}, /* 0100 */ + {10, 0, 20, 0}, /* 0101 */ + {10, 0, 10, 0}, /* W0110 */ + {10, 0, 2, 500000}, /* 0111 */ + {2, 500000, 20, 0}, /* 1000, measurement 100ms, sleep 300ms */ + {2, 500000, 10, 0}, /* 1001, measurement 100ms, sleep 300ms */ + {2, 500000, 0, 0}, /* 1010, high sensitivity mode */ + {2, 500000, 2, 500000}, /* W1011, high sensitivity mode */ + {20, 0, 20, 0} /* 1100, ALS_data x 0.5, see specification P.18 */ +}; + +struct rpr0521_data { + struct i2c_client *client; + + /* protect device params updates (e.g state, gain) */ + struct mutex lock; + + /* device active status */ + bool als_dev_en; + bool pxs_dev_en; + + struct iio_trigger *drdy_trigger0; + s64 irq_timestamp; + + /* optimize runtime pm ops - enable/disable device only if needed */ + bool als_ps_need_en; + bool pxs_ps_need_en; + bool als_need_dis; + bool pxs_need_dis; + + struct regmap *regmap; + + /* + * Ensure correct naturally aligned timestamp. + * Note that the read will put garbage data into + * the padding but this should not be a problem + */ + struct { + __le16 channels[3]; + u8 garbage; + aligned_s64 ts; + } scan; +}; + +static IIO_CONST_ATTR(in_intensity_scale_available, RPR0521_ALS_SCALE_AVAIL); +static IIO_CONST_ATTR(in_proximity_scale_available, RPR0521_PXS_SCALE_AVAIL); + +/* + * Start with easy freq first, whole table of freq combinations is more + * complicated. + */ +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("2.5 10"); + +static struct attribute *rpr0521_attributes[] = { + &iio_const_attr_in_intensity_scale_available.dev_attr.attr, + &iio_const_attr_in_proximity_scale_available.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group rpr0521_attribute_group = { + .attrs = rpr0521_attributes, +}; + +/* Order of the channel data in buffer */ +enum rpr0521_scan_index_order { + RPR0521_CHAN_INDEX_PXS, + RPR0521_CHAN_INDEX_BOTH, + RPR0521_CHAN_INDEX_IR, +}; + +static const unsigned long rpr0521_available_scan_masks[] = { + BIT(RPR0521_CHAN_INDEX_PXS) | BIT(RPR0521_CHAN_INDEX_BOTH) | + BIT(RPR0521_CHAN_INDEX_IR), + 0 +}; + +static const struct iio_chan_spec rpr0521_channels[] = { + { + .type = IIO_PROXIMITY, + .address = RPR0521_CHAN_PXS, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = RPR0521_CHAN_INDEX_PXS, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .modified = 1, + .address = RPR0521_CHAN_ALS_DATA0, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = RPR0521_CHAN_INDEX_BOTH, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .modified = 1, + .address = RPR0521_CHAN_ALS_DATA1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = RPR0521_CHAN_INDEX_IR, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, +}; + +static int rpr0521_als_enable(struct rpr0521_data *data, u8 status) +{ + int ret; + + ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL, + RPR0521_MODE_ALS_MASK, + status); + if (ret < 0) + return ret; + + if (status & RPR0521_MODE_ALS_MASK) + data->als_dev_en = true; + else + data->als_dev_en = false; + + return 0; +} + +static int rpr0521_pxs_enable(struct rpr0521_data *data, u8 status) +{ + int ret; + + ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL, + RPR0521_MODE_PXS_MASK, + status); + if (ret < 0) + return ret; + + if (status & RPR0521_MODE_PXS_MASK) + data->pxs_dev_en = true; + else + data->pxs_dev_en = false; + + return 0; +} + +/** + * rpr0521_set_power_state - handles runtime PM state and sensors enabled status + * + * @data: rpr0521 device private data + * @on: state to be set for devices in @device_mask + * @device_mask: bitmask specifying for which device we need to update @on state + * + * Calls for this function must be balanced so that each ON should have matching + * OFF. Otherwise pm usage_count gets out of sync. + */ +static int rpr0521_set_power_state(struct rpr0521_data *data, bool on, + u8 device_mask) +{ +#ifdef CONFIG_PM + int ret; + + if (device_mask & RPR0521_MODE_ALS_MASK) { + data->als_ps_need_en = on; + data->als_need_dis = !on; + } + + if (device_mask & RPR0521_MODE_PXS_MASK) { + data->pxs_ps_need_en = on; + data->pxs_need_dis = !on; + } + + /* + * On: _resume() is called only when we are suspended + * Off: _suspend() is called after delay if _resume() is not + * called before that. + * Note: If either measurement is re-enabled before _suspend(), + * both stay enabled until _suspend(). + */ + if (on) + ret = pm_runtime_resume_and_get(&data->client->dev); + else + ret = pm_runtime_put_autosuspend(&data->client->dev); + if (ret < 0) { + dev_err(&data->client->dev, + "Failed: rpr0521_set_power_state for %d, ret %d\n", + on, ret); + return ret; + } + + if (on) { + /* If _resume() was not called, enable measurement now. */ + if (data->als_ps_need_en) { + ret = rpr0521_als_enable(data, RPR0521_MODE_ALS_ENABLE); + if (ret) + return ret; + data->als_ps_need_en = false; + } + + if (data->pxs_ps_need_en) { + ret = rpr0521_pxs_enable(data, RPR0521_MODE_PXS_ENABLE); + if (ret) + return ret; + data->pxs_ps_need_en = false; + } + } +#endif + return 0; +} + +/* Interrupt register tells if this sensor caused the interrupt or not. */ +static inline bool rpr0521_is_triggered(struct rpr0521_data *data) +{ + int ret; + int reg; + + ret = regmap_read(data->regmap, RPR0521_REG_INTERRUPT, ®); + if (ret < 0) + return false; /* Reg read failed. */ + if (reg & + (RPR0521_INTERRUPT_ALS_INT_STATUS_MASK | + RPR0521_INTERRUPT_PS_INT_STATUS_MASK)) + return true; + else + return false; /* Int not from this sensor. */ +} + +/* IRQ to trigger handler */ +static irqreturn_t rpr0521_drdy_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct rpr0521_data *data = iio_priv(indio_dev); + + data->irq_timestamp = iio_get_time_ns(indio_dev); + /* + * We need to wake the thread to read the interrupt reg. It + * is not possible to do that here because regmap_read takes a + * mutex. + */ + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t rpr0521_drdy_irq_thread(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct rpr0521_data *data = iio_priv(indio_dev); + + if (rpr0521_is_triggered(data)) { + iio_trigger_poll_nested(data->drdy_trigger0); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static irqreturn_t rpr0521_trigger_consumer_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct rpr0521_data *data = iio_priv(indio_dev); + int err; + + /* Use irq timestamp when reasonable. */ + if (iio_trigger_using_own(indio_dev) && data->irq_timestamp) { + pf->timestamp = data->irq_timestamp; + data->irq_timestamp = 0; + } + /* Other chained trigger polls get timestamp only here. */ + if (!pf->timestamp) + pf->timestamp = iio_get_time_ns(indio_dev); + + err = regmap_bulk_read(data->regmap, RPR0521_REG_PXS_DATA, + data->scan.channels, + (3 * 2) + 1); /* 3 * 16-bit + (discarded) int clear reg. */ + if (!err) + iio_push_to_buffers_with_ts(indio_dev, &data->scan, + sizeof(data->scan), pf->timestamp); + else + dev_err(&data->client->dev, + "Trigger consumer can't read from sensor.\n"); + pf->timestamp = 0; + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int rpr0521_write_int_enable(struct rpr0521_data *data) +{ + int err; + + /* Interrupt after each measurement */ + err = regmap_update_bits(data->regmap, RPR0521_REG_PXS_CTRL, + RPR0521_PXS_PERSISTENCE_MASK, + RPR0521_PXS_PERSISTENCE_DRDY); + if (err) { + dev_err(&data->client->dev, "PS control reg write fail.\n"); + return -EBUSY; + } + + /* Ignore latch and mode because of drdy */ + err = regmap_write(data->regmap, RPR0521_REG_INTERRUPT, + RPR0521_INTERRUPT_INT_REASSERT_DISABLE | + RPR0521_INTERRUPT_INT_TRIG_ALS_DISABLE | + RPR0521_INTERRUPT_INT_TRIG_PS_ENABLE + ); + if (err) { + dev_err(&data->client->dev, "Interrupt setup write fail.\n"); + return -EBUSY; + } + + return 0; +} + +static int rpr0521_write_int_disable(struct rpr0521_data *data) +{ + /* Don't care of clearing mode, assert and latch. */ + return regmap_write(data->regmap, RPR0521_REG_INTERRUPT, + RPR0521_INTERRUPT_INT_TRIG_ALS_DISABLE | + RPR0521_INTERRUPT_INT_TRIG_PS_DISABLE + ); +} + +/* + * Trigger producer enable / disable. Note that there will be trigs only when + * measurement data is ready to be read. + */ +static int rpr0521_pxs_drdy_set_state(struct iio_trigger *trigger, + bool enable_drdy) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trigger); + struct rpr0521_data *data = iio_priv(indio_dev); + int err; + + if (enable_drdy) + err = rpr0521_write_int_enable(data); + else + err = rpr0521_write_int_disable(data); + if (err) + dev_err(&data->client->dev, "rpr0521_pxs_drdy_set_state failed\n"); + + return err; +} + +static const struct iio_trigger_ops rpr0521_trigger_ops = { + .set_trigger_state = rpr0521_pxs_drdy_set_state, + }; + + +static int rpr0521_buffer_preenable(struct iio_dev *indio_dev) +{ + int err; + struct rpr0521_data *data = iio_priv(indio_dev); + + mutex_lock(&data->lock); + err = rpr0521_set_power_state(data, true, + (RPR0521_MODE_PXS_MASK | RPR0521_MODE_ALS_MASK)); + mutex_unlock(&data->lock); + if (err) + dev_err(&data->client->dev, "_buffer_preenable fail\n"); + + return err; +} + +static int rpr0521_buffer_postdisable(struct iio_dev *indio_dev) +{ + int err; + struct rpr0521_data *data = iio_priv(indio_dev); + + mutex_lock(&data->lock); + err = rpr0521_set_power_state(data, false, + (RPR0521_MODE_PXS_MASK | RPR0521_MODE_ALS_MASK)); + mutex_unlock(&data->lock); + if (err) + dev_err(&data->client->dev, "_buffer_postdisable fail\n"); + + return err; +} + +static const struct iio_buffer_setup_ops rpr0521_buffer_setup_ops = { + .preenable = rpr0521_buffer_preenable, + .postdisable = rpr0521_buffer_postdisable, +}; + +static int rpr0521_get_gain(struct rpr0521_data *data, int chan, + int *val, int *val2) +{ + int ret, reg, idx; + + ret = regmap_read(data->regmap, rpr0521_gain[chan].reg, ®); + if (ret < 0) + return ret; + + idx = (rpr0521_gain[chan].mask & reg) >> rpr0521_gain[chan].shift; + *val = rpr0521_gain[chan].gain[idx].scale; + *val2 = rpr0521_gain[chan].gain[idx].uscale; + + return 0; +} + +static int rpr0521_set_gain(struct rpr0521_data *data, int chan, + int val, int val2) +{ + int i, idx = -EINVAL; + + /* get gain index */ + for (i = 0; i < rpr0521_gain[chan].size; i++) + if (val == rpr0521_gain[chan].gain[i].scale && + val2 == rpr0521_gain[chan].gain[i].uscale) { + idx = i; + break; + } + + if (idx < 0) + return idx; + + return regmap_update_bits(data->regmap, rpr0521_gain[chan].reg, + rpr0521_gain[chan].mask, + idx << rpr0521_gain[chan].shift); +} + +static int rpr0521_read_samp_freq(struct rpr0521_data *data, + enum iio_chan_type chan_type, + int *val, int *val2) +{ + int reg, ret; + + ret = regmap_read(data->regmap, RPR0521_REG_MODE_CTRL, ®); + if (ret < 0) + return ret; + + reg &= RPR0521_MODE_MEAS_TIME_MASK; + if (reg >= ARRAY_SIZE(rpr0521_samp_freq_i)) + return -EINVAL; + + switch (chan_type) { + case IIO_INTENSITY: + *val = rpr0521_samp_freq_i[reg].als_hz; + *val2 = rpr0521_samp_freq_i[reg].als_uhz; + return 0; + + case IIO_PROXIMITY: + *val = rpr0521_samp_freq_i[reg].pxs_hz; + *val2 = rpr0521_samp_freq_i[reg].pxs_uhz; + return 0; + + default: + return -EINVAL; + } +} + +static int rpr0521_write_samp_freq_common(struct rpr0521_data *data, + enum iio_chan_type chan_type, + int val, int val2) +{ + int i; + + /* + * Ignore channel + * both pxs and als are setup only to same freq because of simplicity + */ + switch (val) { + case 0: + i = 0; + break; + + case 2: + if (val2 != 500000) + return -EINVAL; + + i = 11; + break; + + case 10: + i = 6; + break; + + default: + return -EINVAL; + } + + return regmap_update_bits(data->regmap, + RPR0521_REG_MODE_CTRL, + RPR0521_MODE_MEAS_TIME_MASK, + i); +} + +static int rpr0521_read_ps_offset(struct rpr0521_data *data, int *offset) +{ + int ret; + __le16 buffer; + + ret = regmap_bulk_read(data->regmap, + RPR0521_REG_PS_OFFSET_LSB, &buffer, sizeof(buffer)); + + if (ret < 0) { + dev_err(&data->client->dev, "Failed to read PS OFFSET register\n"); + return ret; + } + *offset = le16_to_cpu(buffer); + + return ret; +} + +static int rpr0521_write_ps_offset(struct rpr0521_data *data, int offset) +{ + int ret; + __le16 buffer; + + buffer = cpu_to_le16(offset & 0x3ff); + ret = regmap_raw_write(data->regmap, + RPR0521_REG_PS_OFFSET_LSB, &buffer, sizeof(buffer)); + + if (ret < 0) { + dev_err(&data->client->dev, "Failed to write PS OFFSET register\n"); + return ret; + } + + return ret; +} + +static int rpr0521_read_info_raw(struct rpr0521_data *data, + struct iio_chan_spec const *chan, + int *val) +{ + u8 device_mask; + __le16 raw_data; + int ret; + + device_mask = rpr0521_data_reg[chan->address].device_mask; + + guard(mutex)(&data->lock); + ret = rpr0521_set_power_state(data, true, device_mask); + if (ret < 0) + return ret; + + ret = regmap_bulk_read(data->regmap, + rpr0521_data_reg[chan->address].address, + &raw_data, sizeof(raw_data)); + if (ret < 0) { + rpr0521_set_power_state(data, false, device_mask); + return ret; + } + + ret = rpr0521_set_power_state(data, false, device_mask); + if (ret < 0) + return ret; + + *val = le16_to_cpu(raw_data); + + return 0; +} + +static int rpr0521_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct rpr0521_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_INTENSITY && chan->type != IIO_PROXIMITY) + return -EINVAL; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = rpr0521_read_info_raw(data, chan, val); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + mutex_lock(&data->lock); + ret = rpr0521_get_gain(data, chan->address, val, val2); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&data->lock); + ret = rpr0521_read_samp_freq(data, chan->type, val, val2); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_OFFSET: + mutex_lock(&data->lock); + ret = rpr0521_read_ps_offset(data, val); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int rpr0521_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct rpr0521_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + mutex_lock(&data->lock); + ret = rpr0521_set_gain(data, chan->address, val, val2); + mutex_unlock(&data->lock); + + return ret; + + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&data->lock); + ret = rpr0521_write_samp_freq_common(data, chan->type, + val, val2); + mutex_unlock(&data->lock); + + return ret; + + case IIO_CHAN_INFO_OFFSET: + mutex_lock(&data->lock); + ret = rpr0521_write_ps_offset(data, val); + mutex_unlock(&data->lock); + + return ret; + + default: + return -EINVAL; + } +} + +static const struct iio_info rpr0521_info = { + .read_raw = rpr0521_read_raw, + .write_raw = rpr0521_write_raw, + .attrs = &rpr0521_attribute_group, +}; + +static int rpr0521_init(struct rpr0521_data *data) +{ + int ret; + int id; + + ret = regmap_read(data->regmap, RPR0521_REG_ID, &id); + if (ret < 0) { + dev_err(&data->client->dev, "Failed to read REG_ID register\n"); + return ret; + } + + if (id != RPR0521_MANUFACT_ID) { + dev_err(&data->client->dev, "Wrong id, got %x, expected %x\n", + id, RPR0521_MANUFACT_ID); + return -ENODEV; + } + + /* set default measurement time - 100 ms for both ALS and PS */ + ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL, + RPR0521_MODE_MEAS_TIME_MASK, + RPR0521_DEFAULT_MEAS_TIME); + if (ret) { + pr_err("regmap_update_bits returned %d\n", ret); + return ret; + } + +#ifndef CONFIG_PM + ret = rpr0521_als_enable(data, RPR0521_MODE_ALS_ENABLE); + if (ret < 0) + return ret; + ret = rpr0521_pxs_enable(data, RPR0521_MODE_PXS_ENABLE); + if (ret < 0) + return ret; +#endif + + data->irq_timestamp = 0; + + return 0; +} + +static int rpr0521_poweroff(struct rpr0521_data *data) +{ + int ret; + int tmp; + + ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL, + RPR0521_MODE_ALS_MASK | + RPR0521_MODE_PXS_MASK, + RPR0521_MODE_ALS_DISABLE | + RPR0521_MODE_PXS_DISABLE); + if (ret < 0) + return ret; + + data->als_dev_en = false; + data->pxs_dev_en = false; + + /* + * Int pin keeps state after power off. Set pin to high impedance + * mode to prevent power drain. + */ + ret = regmap_read(data->regmap, RPR0521_REG_INTERRUPT, &tmp); + if (ret) { + dev_err(&data->client->dev, "Failed to reset int pin.\n"); + return ret; + } + + return 0; +} + +static bool rpr0521_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RPR0521_REG_MODE_CTRL: + case RPR0521_REG_ALS_CTRL: + case RPR0521_REG_PXS_CTRL: + return false; + default: + return true; + } +} + +static const struct regmap_config rpr0521_regmap_config = { + .name = "rpr0521_regmap", + + .reg_bits = 8, + .val_bits = 8, + + .max_register = RPR0521_REG_ID, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = rpr0521_is_volatile_reg, +}; + +static int rpr0521_probe(struct i2c_client *client) +{ + struct rpr0521_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &rpr0521_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "regmap_init failed!\n"); + return PTR_ERR(regmap); + } + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + + mutex_init(&data->lock); + + indio_dev->info = &rpr0521_info; + indio_dev->name = RPR0521_DRV_NAME; + indio_dev->channels = rpr0521_channels; + indio_dev->num_channels = ARRAY_SIZE(rpr0521_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = rpr0521_init(data); + if (ret < 0) { + dev_err(&client->dev, "rpr0521 chip init failed\n"); + return ret; + } + + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto err_poweroff; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, RPR0521_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + /* + * If sensor write/read is needed in _probe after _use_autosuspend, + * sensor needs to be _resumed first using rpr0521_set_power_state(). + */ + + /* IRQ to trigger setup */ + if (client->irq) { + /* Trigger0 producer setup */ + data->drdy_trigger0 = devm_iio_trigger_alloc( + indio_dev->dev.parent, + "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); + if (!data->drdy_trigger0) { + ret = -ENOMEM; + goto err_pm_disable; + } + data->drdy_trigger0->ops = &rpr0521_trigger_ops; + indio_dev->available_scan_masks = rpr0521_available_scan_masks; + iio_trigger_set_drvdata(data->drdy_trigger0, indio_dev); + + /* Ties irq to trigger producer handler. */ + ret = devm_request_threaded_irq(&client->dev, client->irq, + rpr0521_drdy_irq_handler, rpr0521_drdy_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "rpr0521_event", indio_dev); + if (ret) + goto err_pm_disable; + + ret = devm_iio_trigger_register(indio_dev->dev.parent, + data->drdy_trigger0); + if (ret) { + dev_err(&client->dev, "iio trigger register failed\n"); + goto err_pm_disable; + } + + /* + * Now whole pipe from physical interrupt (irq defined by + * devicetree to device) to trigger0 output is set up. + */ + + /* Trigger consumer setup */ + ret = devm_iio_triggered_buffer_setup(indio_dev->dev.parent, + indio_dev, + iio_pollfunc_store_time, + rpr0521_trigger_consumer_handler, + &rpr0521_buffer_setup_ops); + if (ret < 0) { + dev_err(&client->dev, "iio triggered buffer setup failed\n"); + goto err_pm_disable; + } + } + + ret = iio_device_register(indio_dev); + if (ret) + goto err_pm_disable; + + return 0; + +err_pm_disable: + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); +err_poweroff: + rpr0521_poweroff(data); + + return ret; +} + +static void rpr0521_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + rpr0521_poweroff(iio_priv(indio_dev)); +} + +static int rpr0521_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct rpr0521_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + /* If measurements are enabled, enable them on resume */ + if (!data->als_need_dis) + data->als_ps_need_en = data->als_dev_en; + if (!data->pxs_need_dis) + data->pxs_ps_need_en = data->pxs_dev_en; + + /* disable channels and sets {als,pxs}_dev_en to false */ + ret = rpr0521_poweroff(data); + regcache_mark_dirty(data->regmap); + mutex_unlock(&data->lock); + + return ret; +} + +static int rpr0521_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct rpr0521_data *data = iio_priv(indio_dev); + int ret; + + ret = regcache_sync(data->regmap); + if (ret < 0) + return ret; + + if (data->als_ps_need_en) { + ret = rpr0521_als_enable(data, RPR0521_MODE_ALS_ENABLE); + if (ret < 0) + return ret; + data->als_ps_need_en = false; + } + + if (data->pxs_ps_need_en) { + ret = rpr0521_pxs_enable(data, RPR0521_MODE_PXS_ENABLE); + if (ret < 0) + return ret; + data->pxs_ps_need_en = false; + } + msleep(100); //wait for first measurement result + + return 0; +} + +static const struct dev_pm_ops rpr0521_pm_ops = { + RUNTIME_PM_OPS(rpr0521_runtime_suspend, rpr0521_runtime_resume, NULL) +}; + +static const struct acpi_device_id rpr0521_acpi_match[] = { + {"RPR0521", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, rpr0521_acpi_match); + +static const struct i2c_device_id rpr0521_id[] = { + { .name = "rpr0521" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, rpr0521_id); + +static struct i2c_driver rpr0521_driver = { + .driver = { + .name = RPR0521_DRV_NAME, + .pm = pm_ptr(&rpr0521_pm_ops), + .acpi_match_table = rpr0521_acpi_match, + }, + .probe = rpr0521_probe, + .remove = rpr0521_remove, + .id_table = rpr0521_id, +}; + +module_i2c_driver(rpr0521_driver); + +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>"); +MODULE_DESCRIPTION("RPR0521 ROHM Ambient Light and Proximity Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/si1133.c b/drivers/iio/light/si1133.c new file mode 100644 index 000000000..22073ded8 --- /dev/null +++ b/drivers/iio/light/si1133.c @@ -0,0 +1,1088 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * si1133.c - Support for Silabs SI1133 combined ambient + * light and UV index sensors + * + * Copyright 2018 Maxime Roussin-Belanger <maxime.roussinbelanger@gmail.com> + */ + +#include <linux/array_size.h> +#include <linux/bitops.h> +#include <linux/cleanup.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/math.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/types.h> +#include <linux/unaligned.h> +#include <linux/util_macros.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define SI1133_REG_PART_ID 0x00 +#define SI1133_REG_REV_ID 0x01 +#define SI1133_REG_MFR_ID 0x02 +#define SI1133_REG_INFO0 0x03 +#define SI1133_REG_INFO1 0x04 + +#define SI1133_PART_ID 0x33 + +#define SI1133_REG_HOSTIN0 0x0A +#define SI1133_REG_COMMAND 0x0B +#define SI1133_REG_IRQ_ENABLE 0x0F +#define SI1133_REG_RESPONSE1 0x10 +#define SI1133_REG_RESPONSE0 0x11 +#define SI1133_REG_IRQ_STATUS 0x12 +#define SI1133_REG_MEAS_RATE 0x1A + +#define SI1133_IRQ_CHANNEL_ENABLE 0xF + +#define SI1133_CMD_RESET_CTR 0x00 +#define SI1133_CMD_RESET_SW 0x01 +#define SI1133_CMD_FORCE 0x11 +#define SI1133_CMD_START_AUTONOMOUS 0x13 +#define SI1133_CMD_PARAM_SET 0x80 +#define SI1133_CMD_PARAM_QUERY 0x40 +#define SI1133_CMD_PARAM_MASK 0x3F + +#define SI1133_CMD_ERR_MASK BIT(4) +#define SI1133_CMD_SEQ_MASK 0xF +#define SI1133_MAX_CMD_CTR 0xF + +#define SI1133_PARAM_REG_CHAN_LIST 0x01 +#define SI1133_PARAM_REG_ADCCONFIG(x) (((x) * 4) + 2) +#define SI1133_PARAM_REG_ADCSENS(x) (((x) * 4) + 3) +#define SI1133_PARAM_REG_ADCPOST(x) (((x) * 4) + 4) + +#define SI1133_ADCMUX_MASK 0x1F + +#define SI1133_ADCCONFIG_DECIM_RATE(x) ((x) << 5) + +#define SI1133_ADCSENS_SCALE_MASK 0x70 +#define SI1133_ADCSENS_SCALE_SHIFT 4 +#define SI1133_ADCSENS_HSIG_MASK BIT(7) +#define SI1133_ADCSENS_HSIG_SHIFT 7 +#define SI1133_ADCSENS_HW_GAIN_MASK 0xF +#define SI1133_ADCSENS_NB_MEAS(x) (fls(x) << SI1133_ADCSENS_SCALE_SHIFT) + +#define SI1133_ADCPOST_24BIT_EN BIT(6) +#define SI1133_ADCPOST_POSTSHIFT_BITQTY(x) (((x) & GENMASK(2, 0)) << 3) + +#define SI1133_PARAM_ADCMUX_SMALL_IR 0x0 +#define SI1133_PARAM_ADCMUX_MED_IR 0x1 +#define SI1133_PARAM_ADCMUX_LARGE_IR 0x2 +#define SI1133_PARAM_ADCMUX_WHITE 0xB +#define SI1133_PARAM_ADCMUX_LARGE_WHITE 0xD +#define SI1133_PARAM_ADCMUX_UV 0x18 +#define SI1133_PARAM_ADCMUX_UV_DEEP 0x19 + +#define SI1133_ERR_INVALID_CMD 0x0 +#define SI1133_ERR_INVALID_LOCATION_CMD 0x1 +#define SI1133_ERR_SATURATION_ADC_OR_OVERFLOW_ACCUMULATION 0x2 +#define SI1133_ERR_OUTPUT_BUFFER_OVERFLOW 0x3 + +#define SI1133_COMPLETION_TIMEOUT_MS 500 + +#define SI1133_CMD_MINSLEEP_US_LOW 5000 +#define SI1133_CMD_MINSLEEP_US_HIGH 7500 +#define SI1133_CMD_TIMEOUT_MS 25 + +#define SI1133_REG_HOSTOUT(x) ((x) + 0x13) + +#define SI1133_X_ORDER_MASK 0x0070 +#define SI1133_Y_ORDER_MASK 0x0007 +#define si1133_get_x_order(m) (((m) & SI1133_X_ORDER_MASK) >> 4) +#define si1133_get_y_order(m) ((m) & SI1133_Y_ORDER_MASK) + +#define SI1133_LUX_ADC_MASK 0xE +#define SI1133_ADC_THRESHOLD 16000 +#define SI1133_INPUT_FRACTION_HIGH 7 +#define SI1133_INPUT_FRACTION_LOW 15 +#define SI1133_LUX_OUTPUT_FRACTION 12 +#define SI1133_LUX_BUFFER_SIZE 9 +#define SI1133_MEASURE_BUFFER_SIZE 3 + +static const int si1133_scale_available[] = { + 1, 2, 4, 8, 16, 32, 64, 128}; + +static IIO_CONST_ATTR(scale_available, "1 2 4 8 16 32 64 128"); + +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.0244 0.0488 0.0975 0.195 0.390 0.780 " + "1.560 3.120 6.24 12.48 25.0 50.0"); + +/* A.K.A. HW_GAIN in datasheet */ +enum si1133_int_time { + _24_4_us = 0, + _48_8_us = 1, + _97_5_us = 2, + _195_0_us = 3, + _390_0_us = 4, + _780_0_us = 5, + _1_560_0_us = 6, + _3_120_0_us = 7, + _6_240_0_us = 8, + _12_480_0_us = 9, + _25_ms = 10, + _50_ms = 11, +}; + +/* Integration time in milliseconds, nanoseconds */ +static const int si1133_int_time_table[][2] = { + [_24_4_us] = {0, 24400}, + [_48_8_us] = {0, 48800}, + [_97_5_us] = {0, 97500}, + [_195_0_us] = {0, 195000}, + [_390_0_us] = {0, 390000}, + [_780_0_us] = {0, 780000}, + [_1_560_0_us] = {1, 560000}, + [_3_120_0_us] = {3, 120000}, + [_6_240_0_us] = {6, 240000}, + [_12_480_0_us] = {12, 480000}, + [_25_ms] = {25, 000000}, + [_50_ms] = {50, 000000}, +}; + +static const struct regmap_range si1133_reg_ranges[] = { + regmap_reg_range(0x00, 0x02), + regmap_reg_range(0x0A, 0x0B), + regmap_reg_range(0x0F, 0x0F), + regmap_reg_range(0x10, 0x12), + regmap_reg_range(0x13, 0x2C), +}; + +static const struct regmap_range si1133_reg_ro_ranges[] = { + regmap_reg_range(0x00, 0x02), + regmap_reg_range(0x10, 0x2C), +}; + +static const struct regmap_range si1133_precious_ranges[] = { + regmap_reg_range(0x12, 0x12), +}; + +static const struct regmap_access_table si1133_write_ranges_table = { + .yes_ranges = si1133_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(si1133_reg_ranges), + .no_ranges = si1133_reg_ro_ranges, + .n_no_ranges = ARRAY_SIZE(si1133_reg_ro_ranges), +}; + +static const struct regmap_access_table si1133_read_ranges_table = { + .yes_ranges = si1133_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(si1133_reg_ranges), +}; + +static const struct regmap_access_table si1133_precious_table = { + .yes_ranges = si1133_precious_ranges, + .n_yes_ranges = ARRAY_SIZE(si1133_precious_ranges), +}; + +static const struct regmap_config si1133_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0x2C, + + .wr_table = &si1133_write_ranges_table, + .rd_table = &si1133_read_ranges_table, + + .precious_table = &si1133_precious_table, +}; + +struct si1133_data { + struct regmap *regmap; + struct i2c_client *client; + + /* Lock protecting one command at a time can be processed */ + struct mutex mutex; + + int rsp_seq; + u8 scan_mask; + u8 adc_sens[6]; + u8 adc_config[6]; + + struct completion completion; +}; + +struct si1133_coeff { + s16 info; + u16 mag; +}; + +struct si1133_lux_coeff { + struct si1133_coeff coeff_high[4]; + struct si1133_coeff coeff_low[9]; +}; + +static const struct si1133_lux_coeff lux_coeff = { + { + { 0, 209}, + { 1665, 93}, + { 2064, 65}, + {-2671, 234} + }, + { + { 0, 0}, + { 1921, 29053}, + {-1022, 36363}, + { 2320, 20789}, + { -367, 57909}, + {-1774, 38240}, + { -608, 46775}, + {-1503, 51831}, + {-1886, 58928} + } +}; + +static int si1133_calculate_polynomial_inner(s32 input, u8 fraction, u16 mag, + s8 shift) +{ + return ((input << fraction) / mag) << shift; +} + +static int si1133_calculate_output(s32 x, s32 y, u8 x_order, u8 y_order, + u8 input_fraction, s8 sign, + const struct si1133_coeff *coeffs) +{ + s8 shift; + int x1 = 1; + int x2 = 1; + int y1 = 1; + int y2 = 1; + + shift = ((u16)coeffs->info & 0xFF00) >> 8; + shift ^= 0xFF; + shift += 1; + shift = -shift; + + if (x_order > 0) { + x1 = si1133_calculate_polynomial_inner(x, input_fraction, + coeffs->mag, shift); + if (x_order > 1) + x2 = x1; + } + + if (y_order > 0) { + y1 = si1133_calculate_polynomial_inner(y, input_fraction, + coeffs->mag, shift); + if (y_order > 1) + y2 = y1; + } + + return sign * x1 * x2 * y1 * y2; +} + +/* + * The algorithm is from: + * https://siliconlabs.github.io/Gecko_SDK_Doc/efm32zg/html/si1133_8c_source.html#l00716 + */ +static int si1133_calc_polynomial(s32 x, s32 y, u8 input_fraction, u8 num_coeff, + const struct si1133_coeff *coeffs) +{ + u8 x_order, y_order; + u8 counter; + s8 sign; + int output = 0; + + for (counter = 0; counter < num_coeff; counter++) { + if (coeffs->info < 0) + sign = -1; + else + sign = 1; + + x_order = si1133_get_x_order(coeffs->info); + y_order = si1133_get_y_order(coeffs->info); + + if ((x_order == 0) && (y_order == 0)) + output += + sign * coeffs->mag << SI1133_LUX_OUTPUT_FRACTION; + else + output += si1133_calculate_output(x, y, x_order, + y_order, + input_fraction, sign, + coeffs); + coeffs++; + } + + return abs(output); +} + +static int si1133_cmd_reset_sw(struct si1133_data *data) +{ + struct device *dev = &data->client->dev; + unsigned int resp; + unsigned long timeout; + int err; + + err = regmap_write(data->regmap, SI1133_REG_COMMAND, + SI1133_CMD_RESET_SW); + if (err) + return err; + + timeout = jiffies + msecs_to_jiffies(SI1133_CMD_TIMEOUT_MS); + while (true) { + err = regmap_read(data->regmap, SI1133_REG_RESPONSE0, &resp); + if (err == -ENXIO) { + usleep_range(SI1133_CMD_MINSLEEP_US_LOW, + SI1133_CMD_MINSLEEP_US_HIGH); + continue; + } + + if ((resp & SI1133_MAX_CMD_CTR) == SI1133_MAX_CMD_CTR) + break; + + if (time_after(jiffies, timeout)) { + dev_warn(dev, "Timeout on reset ctr resp: %d\n", resp); + return -ETIMEDOUT; + } + } + + if (!err) + data->rsp_seq = SI1133_MAX_CMD_CTR; + + return err; +} + +static int si1133_parse_response_err(struct device *dev, u32 resp, u8 cmd) +{ + resp &= 0xF; + + switch (resp) { + case SI1133_ERR_OUTPUT_BUFFER_OVERFLOW: + dev_warn(dev, "Output buffer overflow: 0x%02x\n", cmd); + return -EOVERFLOW; + case SI1133_ERR_SATURATION_ADC_OR_OVERFLOW_ACCUMULATION: + dev_warn(dev, "Saturation of the ADC or overflow of accumulation: 0x%02x\n", + cmd); + return -EOVERFLOW; + case SI1133_ERR_INVALID_LOCATION_CMD: + dev_warn(dev, + "Parameter access to an invalid location: 0x%02x\n", + cmd); + return -EINVAL; + case SI1133_ERR_INVALID_CMD: + dev_warn(dev, "Invalid command 0x%02x\n", cmd); + return -EINVAL; + default: + dev_warn(dev, "Unknown error 0x%02x\n", cmd); + return -EINVAL; + } +} + +static int si1133_cmd_reset_counter(struct si1133_data *data) +{ + int err = regmap_write(data->regmap, SI1133_REG_COMMAND, + SI1133_CMD_RESET_CTR); + if (err) + return err; + + data->rsp_seq = 0; + + return 0; +} + +static int si1133_command(struct si1133_data *data, u8 cmd) +{ + unsigned long timeout; + struct device *dev = &data->client->dev; + u32 resp; + int err; + int expected_seq; + + guard(mutex)(&data->mutex); + + expected_seq = (data->rsp_seq + 1) & SI1133_MAX_CMD_CTR; + + if (cmd == SI1133_CMD_FORCE) { + /* Flush pending IRQs from a previous timeout. */ + regmap_read(data->regmap, SI1133_REG_IRQ_STATUS, &resp); + regmap_write(data->regmap, SI1133_REG_IRQ_ENABLE, + SI1133_IRQ_CHANNEL_ENABLE); + + reinit_completion(&data->completion); + } + + err = regmap_write(data->regmap, SI1133_REG_COMMAND, cmd); + if (err) { + dev_warn(dev, "Failed to write command 0x%02x, ret=%d\n", cmd, + err); + return err; + } + + if (cmd == SI1133_CMD_FORCE) { + /* wait for irq */ + timeout = msecs_to_jiffies(SI1133_COMPLETION_TIMEOUT_MS); + if (!wait_for_completion_timeout(&data->completion, timeout)) { + regmap_write(data->regmap, SI1133_REG_IRQ_ENABLE, 0); + return -ETIMEDOUT; + } + err = regmap_read(data->regmap, SI1133_REG_RESPONSE0, &resp); + if (err) + return err; + } else { + err = regmap_read_poll_timeout(data->regmap, + SI1133_REG_RESPONSE0, resp, + (resp & SI1133_CMD_SEQ_MASK) == + expected_seq || + (resp & SI1133_CMD_ERR_MASK), + SI1133_CMD_MINSLEEP_US_LOW, + SI1133_CMD_TIMEOUT_MS * 1000); + if (err) { + dev_warn(dev, + "Failed to read command 0x%02x, ret=%d\n", + cmd, err); + /* + * Reset counter on err to prevent software and hardware + * counters being out of sync. + */ + si1133_cmd_reset_counter(data); + return err; + } + } + + if (resp & SI1133_CMD_ERR_MASK) { + err = si1133_parse_response_err(dev, resp, cmd); + si1133_cmd_reset_counter(data); + } else { + data->rsp_seq = expected_seq; + } + + return err; +} + +static int si1133_param_set(struct si1133_data *data, u8 param, u32 value) +{ + int err = regmap_write(data->regmap, SI1133_REG_HOSTIN0, value); + + if (err) + return err; + + return si1133_command(data, SI1133_CMD_PARAM_SET | + (param & SI1133_CMD_PARAM_MASK)); +} + +static int si1133_param_query(struct si1133_data *data, u8 param, u32 *result) +{ + int err = si1133_command(data, SI1133_CMD_PARAM_QUERY | + (param & SI1133_CMD_PARAM_MASK)); + if (err) + return err; + + return regmap_read(data->regmap, SI1133_REG_RESPONSE1, result); +} + +#define SI1133_CHANNEL(_ch, _type) \ + .type = _type, \ + .channel = _ch, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ + +static const struct iio_chan_spec si1133_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .channel = 0, + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_WHITE, IIO_INTENSITY) + .channel2 = IIO_MOD_LIGHT_BOTH, + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_LARGE_WHITE, IIO_INTENSITY) + .channel2 = IIO_MOD_LIGHT_BOTH, + .extend_name = "large", + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_SMALL_IR, IIO_INTENSITY) + .extend_name = "small", + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_MED_IR, IIO_INTENSITY) + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_LARGE_IR, IIO_INTENSITY) + .extend_name = "large", + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_UV, IIO_UVINDEX) + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_UV_DEEP, IIO_UVINDEX) + .modified = 1, + .channel2 = IIO_MOD_LIGHT_DUV, + } +}; + +static int si1133_get_int_time_index(int milliseconds, int nanoseconds) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(si1133_int_time_table); i++) { + if (milliseconds == si1133_int_time_table[i][0] && + nanoseconds == si1133_int_time_table[i][1]) + return i; + } + return -EINVAL; +} + +static int si1133_set_integration_time(struct si1133_data *data, u8 adc, + int milliseconds, int nanoseconds) +{ + int index; + + index = si1133_get_int_time_index(milliseconds, nanoseconds); + if (index < 0) + return index; + + data->adc_sens[adc] &= 0xF0; + data->adc_sens[adc] |= index; + + return si1133_param_set(data, SI1133_PARAM_REG_ADCSENS(0), + data->adc_sens[adc]); +} + +static int si1133_set_chlist(struct si1133_data *data, u8 scan_mask) +{ + /* channel list already set, no need to reprogram */ + if (data->scan_mask == scan_mask) + return 0; + + data->scan_mask = scan_mask; + + return si1133_param_set(data, SI1133_PARAM_REG_CHAN_LIST, scan_mask); +} + +static int si1133_chan_set_adcconfig(struct si1133_data *data, u8 adc, + u8 adc_config) +{ + int err; + + err = si1133_param_set(data, SI1133_PARAM_REG_ADCCONFIG(adc), + adc_config); + if (err) + return err; + + data->adc_config[adc] = adc_config; + + return 0; +} + +static int si1133_update_adcconfig(struct si1133_data *data, uint8_t adc, + u8 mask, u8 shift, u8 value) +{ + u32 adc_config; + int err; + + err = si1133_param_query(data, SI1133_PARAM_REG_ADCCONFIG(adc), + &adc_config); + if (err) + return err; + + adc_config &= ~mask; + adc_config |= (value << shift); + + return si1133_chan_set_adcconfig(data, adc, adc_config); +} + +static int si1133_set_adcmux(struct si1133_data *data, u8 adc, u8 mux) +{ + if ((mux & data->adc_config[adc]) == mux) + return 0; /* mux already set to correct value */ + + return si1133_update_adcconfig(data, adc, SI1133_ADCMUX_MASK, 0, mux); +} + +static int si1133_force_measurement(struct si1133_data *data) +{ + return si1133_command(data, SI1133_CMD_FORCE); +} + +static int si1133_bulk_read(struct si1133_data *data, u8 start_reg, u8 length, + u8 *buffer) +{ + int err; + + err = si1133_force_measurement(data); + if (err) + return err; + + return regmap_bulk_read(data->regmap, start_reg, buffer, length); +} + +static int si1133_measure(struct si1133_data *data, + struct iio_chan_spec const *chan, + int *val) +{ + int err; + + u8 buffer[SI1133_MEASURE_BUFFER_SIZE]; + + err = si1133_set_adcmux(data, 0, chan->channel); + if (err) + return err; + + /* Deactivate lux measurements if they were active */ + err = si1133_set_chlist(data, BIT(0)); + if (err) + return err; + + err = si1133_bulk_read(data, SI1133_REG_HOSTOUT(0), sizeof(buffer), + buffer); + if (err) + return err; + + *val = sign_extend32(get_unaligned_be24(&buffer[0]), 23); + + return err; +} + +static irqreturn_t si1133_threaded_irq_handler(int irq, void *private) +{ + struct iio_dev *iio_dev = private; + struct si1133_data *data = iio_priv(iio_dev); + u32 irq_status; + int err; + + err = regmap_read(data->regmap, SI1133_REG_IRQ_STATUS, &irq_status); + if (err) { + dev_err_ratelimited(&iio_dev->dev, "Error reading IRQ\n"); + goto out; + } + + if (irq_status != data->scan_mask) + return IRQ_NONE; + +out: + complete(&data->completion); + + return IRQ_HANDLED; +} + +static int si1133_scale_to_swgain(int scale_integer, int scale_fractional) +{ + scale_integer = find_closest(scale_integer, si1133_scale_available, + ARRAY_SIZE(si1133_scale_available)); + if (scale_integer < 0 || + scale_integer > ARRAY_SIZE(si1133_scale_available) || + scale_fractional != 0) + return -EINVAL; + + return scale_integer; +} + +static int si1133_chan_set_adcsens(struct si1133_data *data, u8 adc, + u8 adc_sens) +{ + int err; + + err = si1133_param_set(data, SI1133_PARAM_REG_ADCSENS(adc), adc_sens); + if (err) + return err; + + data->adc_sens[adc] = adc_sens; + + return 0; +} + +static int si1133_update_adcsens(struct si1133_data *data, u8 mask, + u8 shift, u8 value) +{ + int err; + u32 adc_sens; + + err = si1133_param_query(data, SI1133_PARAM_REG_ADCSENS(0), + &adc_sens); + if (err) + return err; + + adc_sens &= ~mask; + adc_sens |= (value << shift); + + return si1133_chan_set_adcsens(data, 0, adc_sens); +} + +static int si1133_get_lux(struct si1133_data *data, int *val) +{ + int err; + int lux; + s32 high_vis; + s32 low_vis; + s32 ir; + u8 buffer[SI1133_LUX_BUFFER_SIZE]; + + /* Activate lux channels */ + err = si1133_set_chlist(data, SI1133_LUX_ADC_MASK); + if (err) + return err; + + err = si1133_bulk_read(data, SI1133_REG_HOSTOUT(0), + SI1133_LUX_BUFFER_SIZE, buffer); + if (err) + return err; + + high_vis = sign_extend32(get_unaligned_be24(&buffer[0]), 23); + + low_vis = sign_extend32(get_unaligned_be24(&buffer[3]), 23); + + ir = sign_extend32(get_unaligned_be24(&buffer[6]), 23); + + if (high_vis > SI1133_ADC_THRESHOLD || ir > SI1133_ADC_THRESHOLD) + lux = si1133_calc_polynomial(high_vis, ir, + SI1133_INPUT_FRACTION_HIGH, + ARRAY_SIZE(lux_coeff.coeff_high), + &lux_coeff.coeff_high[0]); + else + lux = si1133_calc_polynomial(low_vis, ir, + SI1133_INPUT_FRACTION_LOW, + ARRAY_SIZE(lux_coeff.coeff_low), + &lux_coeff.coeff_low[0]); + + *val = lux >> SI1133_LUX_OUTPUT_FRACTION; + + return err; +} + +static int si1133_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct si1133_data *data = iio_priv(iio_dev); + u8 adc_sens = data->adc_sens[0]; + int err; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + err = si1133_get_lux(data, val); + if (err) + return err; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_UVINDEX: + err = si1133_measure(data, chan, val); + if (err) + return err; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_UVINDEX: + adc_sens &= SI1133_ADCSENS_HW_GAIN_MASK; + + *val = si1133_int_time_table[adc_sens][0]; + *val2 = si1133_int_time_table[adc_sens][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_UVINDEX: + adc_sens &= SI1133_ADCSENS_SCALE_MASK; + adc_sens >>= SI1133_ADCSENS_SCALE_SHIFT; + + *val = BIT(adc_sens); + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_HARDWAREGAIN: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_UVINDEX: + adc_sens >>= SI1133_ADCSENS_HSIG_SHIFT; + + *val = adc_sens; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int si1133_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct si1133_data *data = iio_priv(iio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_UVINDEX: + val = si1133_scale_to_swgain(val, val2); + if (val < 0) + return val; + + return si1133_update_adcsens(data, + SI1133_ADCSENS_SCALE_MASK, + SI1133_ADCSENS_SCALE_SHIFT, + val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + return si1133_set_integration_time(data, 0, val, val2); + case IIO_CHAN_INFO_HARDWAREGAIN: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_UVINDEX: + if (val != 0 && val != 1) + return -EINVAL; + + return si1133_update_adcsens(data, + SI1133_ADCSENS_HSIG_MASK, + SI1133_ADCSENS_HSIG_SHIFT, + val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static struct attribute *si1133_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + &iio_const_attr_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group si1133_attribute_group = { + .attrs = si1133_attributes, +}; + +static const struct iio_info si1133_info = { + .read_raw = si1133_read_raw, + .write_raw = si1133_write_raw, + .attrs = &si1133_attribute_group, +}; + +/* + * si1133_init_lux_channels - Configure 3 different channels(adc) (1,2 and 3) + * The channel configuration for the lux measurement was taken from : + * https://siliconlabs.github.io/Gecko_SDK_Doc/efm32zg/html/si1133_8c_source.html#l00578 + * + * Reserved the channel 0 for the other raw measurements + */ +static int si1133_init_lux_channels(struct si1133_data *data) +{ + int err; + + err = si1133_chan_set_adcconfig(data, 1, + SI1133_ADCCONFIG_DECIM_RATE(1) | + SI1133_PARAM_ADCMUX_LARGE_WHITE); + if (err) + return err; + + err = si1133_param_set(data, SI1133_PARAM_REG_ADCPOST(1), + SI1133_ADCPOST_24BIT_EN | + SI1133_ADCPOST_POSTSHIFT_BITQTY(0)); + if (err) + return err; + err = si1133_chan_set_adcsens(data, 1, SI1133_ADCSENS_HSIG_MASK | + SI1133_ADCSENS_NB_MEAS(64) | _48_8_us); + if (err) + return err; + + err = si1133_chan_set_adcconfig(data, 2, + SI1133_ADCCONFIG_DECIM_RATE(1) | + SI1133_PARAM_ADCMUX_LARGE_WHITE); + if (err) + return err; + + err = si1133_param_set(data, SI1133_PARAM_REG_ADCPOST(2), + SI1133_ADCPOST_24BIT_EN | + SI1133_ADCPOST_POSTSHIFT_BITQTY(2)); + if (err) + return err; + + err = si1133_chan_set_adcsens(data, 2, SI1133_ADCSENS_HSIG_MASK | + SI1133_ADCSENS_NB_MEAS(1) | _3_120_0_us); + if (err) + return err; + + err = si1133_chan_set_adcconfig(data, 3, + SI1133_ADCCONFIG_DECIM_RATE(1) | + SI1133_PARAM_ADCMUX_MED_IR); + if (err) + return err; + + err = si1133_param_set(data, SI1133_PARAM_REG_ADCPOST(3), + SI1133_ADCPOST_24BIT_EN | + SI1133_ADCPOST_POSTSHIFT_BITQTY(2)); + if (err) + return err; + + return si1133_chan_set_adcsens(data, 3, SI1133_ADCSENS_HSIG_MASK | + SI1133_ADCSENS_NB_MEAS(64) | _48_8_us); +} + +static int si1133_initialize(struct si1133_data *data) +{ + int err; + + err = si1133_cmd_reset_sw(data); + if (err) + return err; + + /* Turn off autonomous mode */ + err = si1133_param_set(data, SI1133_REG_MEAS_RATE, 0); + if (err) + return err; + + err = si1133_init_lux_channels(data); + if (err) + return err; + + return regmap_write(data->regmap, SI1133_REG_IRQ_ENABLE, + SI1133_IRQ_CHANNEL_ENABLE); +} + +static int si1133_validate_ids(struct iio_dev *iio_dev) +{ + struct si1133_data *data = iio_priv(iio_dev); + + unsigned int part_id, rev_id, mfr_id; + int err; + + err = regmap_read(data->regmap, SI1133_REG_PART_ID, &part_id); + if (err) + return err; + + err = regmap_read(data->regmap, SI1133_REG_REV_ID, &rev_id); + if (err) + return err; + + err = regmap_read(data->regmap, SI1133_REG_MFR_ID, &mfr_id); + if (err) + return err; + + dev_info(&iio_dev->dev, + "Device ID part 0x%02x rev 0x%02x mfr 0x%02x\n", + part_id, rev_id, mfr_id); + if (part_id != SI1133_PART_ID) { + dev_err(&iio_dev->dev, + "Part ID mismatch got 0x%02x, expected 0x%02x\n", + part_id, SI1133_PART_ID); + return -ENODEV; + } + + return 0; +} + +static int si1133_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct si1133_data *data; + struct iio_dev *iio_dev; + int err; + + iio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!iio_dev) + return -ENOMEM; + + data = iio_priv(iio_dev); + + init_completion(&data->completion); + + data->regmap = devm_regmap_init_i2c(client, &si1133_regmap_config); + if (IS_ERR(data->regmap)) { + err = PTR_ERR(data->regmap); + dev_err(&client->dev, "Failed to initialise regmap: %d\n", err); + return err; + } + + i2c_set_clientdata(client, iio_dev); + data->client = client; + + iio_dev->name = id->name; + iio_dev->channels = si1133_channels; + iio_dev->num_channels = ARRAY_SIZE(si1133_channels); + iio_dev->info = &si1133_info; + iio_dev->modes = INDIO_DIRECT_MODE; + + mutex_init(&data->mutex); + + err = si1133_validate_ids(iio_dev); + if (err) + return err; + + err = si1133_initialize(data); + if (err) { + dev_err(&client->dev, + "Error when initializing chip: %d\n", err); + return err; + } + + if (!client->irq) { + dev_err(&client->dev, + "Required interrupt not provided, cannot proceed\n"); + return -EINVAL; + } + + err = devm_request_threaded_irq(&client->dev, client->irq, + NULL, + si1133_threaded_irq_handler, + IRQF_ONESHOT | IRQF_SHARED, + client->name, iio_dev); + if (err) { + dev_warn(&client->dev, "Request irq %d failed: %i\n", + client->irq, err); + return err; + } + + return devm_iio_device_register(&client->dev, iio_dev); +} + +static const struct i2c_device_id si1133_ids[] = { + { .name = "si1133" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, si1133_ids); + +static struct i2c_driver si1133_driver = { + .driver = { + .name = "si1133", + }, + .probe = si1133_probe, + .id_table = si1133_ids, +}; + +module_i2c_driver(si1133_driver); + +MODULE_AUTHOR("Maxime Roussin-Belanger <maxime.roussinbelanger@gmail.com>"); +MODULE_DESCRIPTION("Silabs SI1133, UV index sensor and ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/si1145.c b/drivers/iio/light/si1145.c new file mode 100644 index 000000000..bb7c99041 --- /dev/null +++ b/drivers/iio/light/si1145.c @@ -0,0 +1,1358 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * si1145.c - Support for Silabs SI1132 and SI1141/2/3/5/6/7 combined ambient + * light, UV index and proximity sensors + * + * Copyright 2014-16 Peter Meerwald-Stadler <pmeerw@pmeerw.net> + * Copyright 2016 Crestez Dan Leonard <leonard.crestez@intel.com> + * + * SI1132 (7-bit I2C slave address 0x60) + * SI1141/2/3 (7-bit I2C slave address 0x5a) + * SI1145/6/6 (7-bit I2C slave address 0x60) + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/irq.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/buffer.h> +#include <linux/util_macros.h> + +#define SI1145_REG_PART_ID 0x00 +#define SI1145_REG_REV_ID 0x01 +#define SI1145_REG_SEQ_ID 0x02 +#define SI1145_REG_INT_CFG 0x03 +#define SI1145_REG_IRQ_ENABLE 0x04 +#define SI1145_REG_IRQ_MODE 0x05 +#define SI1145_REG_HW_KEY 0x07 +#define SI1145_REG_MEAS_RATE 0x08 +#define SI1145_REG_PS_LED21 0x0f +#define SI1145_REG_PS_LED3 0x10 +#define SI1145_REG_UCOEF1 0x13 +#define SI1145_REG_UCOEF2 0x14 +#define SI1145_REG_UCOEF3 0x15 +#define SI1145_REG_UCOEF4 0x16 +#define SI1145_REG_PARAM_WR 0x17 +#define SI1145_REG_COMMAND 0x18 +#define SI1145_REG_RESPONSE 0x20 +#define SI1145_REG_IRQ_STATUS 0x21 +#define SI1145_REG_ALSVIS_DATA 0x22 +#define SI1145_REG_ALSIR_DATA 0x24 +#define SI1145_REG_PS1_DATA 0x26 +#define SI1145_REG_PS2_DATA 0x28 +#define SI1145_REG_PS3_DATA 0x2a +#define SI1145_REG_AUX_DATA 0x2c +#define SI1145_REG_PARAM_RD 0x2e +#define SI1145_REG_CHIP_STAT 0x30 + +#define SI1145_UCOEF1_DEFAULT 0x7b +#define SI1145_UCOEF2_DEFAULT 0x6b +#define SI1145_UCOEF3_DEFAULT 0x01 +#define SI1145_UCOEF4_DEFAULT 0x00 + +/* Helper to figure out PS_LED register / shift per channel */ +#define SI1145_PS_LED_REG(ch) \ + (((ch) == 2) ? SI1145_REG_PS_LED3 : SI1145_REG_PS_LED21) +#define SI1145_PS_LED_SHIFT(ch) \ + (((ch) == 1) ? 4 : 0) + +/* Parameter offsets */ +#define SI1145_PARAM_CHLIST 0x01 +#define SI1145_PARAM_PSLED12_SELECT 0x02 +#define SI1145_PARAM_PSLED3_SELECT 0x03 +#define SI1145_PARAM_PS_ENCODING 0x05 +#define SI1145_PARAM_ALS_ENCODING 0x06 +#define SI1145_PARAM_PS1_ADC_MUX 0x07 +#define SI1145_PARAM_PS2_ADC_MUX 0x08 +#define SI1145_PARAM_PS3_ADC_MUX 0x09 +#define SI1145_PARAM_PS_ADC_COUNTER 0x0a +#define SI1145_PARAM_PS_ADC_GAIN 0x0b +#define SI1145_PARAM_PS_ADC_MISC 0x0c +#define SI1145_PARAM_ALS_ADC_MUX 0x0d +#define SI1145_PARAM_ALSIR_ADC_MUX 0x0e +#define SI1145_PARAM_AUX_ADC_MUX 0x0f +#define SI1145_PARAM_ALSVIS_ADC_COUNTER 0x10 +#define SI1145_PARAM_ALSVIS_ADC_GAIN 0x11 +#define SI1145_PARAM_ALSVIS_ADC_MISC 0x12 +#define SI1145_PARAM_LED_RECOVERY 0x1c +#define SI1145_PARAM_ALSIR_ADC_COUNTER 0x1d +#define SI1145_PARAM_ALSIR_ADC_GAIN 0x1e +#define SI1145_PARAM_ALSIR_ADC_MISC 0x1f +#define SI1145_PARAM_ADC_OFFSET 0x1a + +/* Channel enable masks for CHLIST parameter */ +#define SI1145_CHLIST_EN_PS1 BIT(0) +#define SI1145_CHLIST_EN_PS2 BIT(1) +#define SI1145_CHLIST_EN_PS3 BIT(2) +#define SI1145_CHLIST_EN_ALSVIS BIT(4) +#define SI1145_CHLIST_EN_ALSIR BIT(5) +#define SI1145_CHLIST_EN_AUX BIT(6) +#define SI1145_CHLIST_EN_UV BIT(7) + +/* Proximity measurement mode for ADC_MISC parameter */ +#define SI1145_PS_ADC_MODE_NORMAL BIT(2) +/* Signal range mask for ADC_MISC parameter */ +#define SI1145_ADC_MISC_RANGE BIT(5) + +/* Commands for REG_COMMAND */ +#define SI1145_CMD_NOP 0x00 +#define SI1145_CMD_RESET 0x01 +#define SI1145_CMD_PS_FORCE 0x05 +#define SI1145_CMD_ALS_FORCE 0x06 +#define SI1145_CMD_PSALS_FORCE 0x07 +#define SI1145_CMD_PS_PAUSE 0x09 +#define SI1145_CMD_ALS_PAUSE 0x0a +#define SI1145_CMD_PSALS_PAUSE 0x0b +#define SI1145_CMD_PS_AUTO 0x0d +#define SI1145_CMD_ALS_AUTO 0x0e +#define SI1145_CMD_PSALS_AUTO 0x0f +#define SI1145_CMD_PARAM_QUERY 0x80 +#define SI1145_CMD_PARAM_SET 0xa0 + +#define SI1145_RSP_INVALID_SETTING 0x80 +#define SI1145_RSP_COUNTER_MASK 0x0F + +/* Minimum sleep after each command to ensure it's received */ +#define SI1145_COMMAND_MINSLEEP_MS 5 +/* Return -ETIMEDOUT after this long */ +#define SI1145_COMMAND_TIMEOUT_MS 25 + +/* Interrupt configuration masks for INT_CFG register */ +#define SI1145_INT_CFG_OE BIT(0) /* enable interrupt */ +#define SI1145_INT_CFG_MODE BIT(1) /* auto reset interrupt pin */ + +/* Interrupt enable masks for IRQ_ENABLE register */ +#define SI1145_MASK_ALL_IE (BIT(4) | BIT(3) | BIT(2) | BIT(0)) + +#define SI1145_MUX_TEMP 0x65 +#define SI1145_MUX_VDD 0x75 + +/* Proximity LED current; see Table 2 in datasheet */ +#define SI1145_LED_CURRENT_45mA 0x04 + +enum { + SI1132, + SI1141, + SI1142, + SI1143, + SI1145, + SI1146, + SI1147, +}; + +struct si1145_part_info { + u8 part; + const struct iio_info *iio_info; + const struct iio_chan_spec *channels; + unsigned int num_channels; + unsigned int num_leds; + bool uncompressed_meas_rate; +}; + +/** + * struct si1145_data - si1145 chip state data + * @client: I2C client + * @lock: mutex to protect shared state. + * @cmdlock: Low-level mutex to protect command execution only + * @rsp_seq: Next expected response number or -1 if counter reset required + * @scan_mask: Saved scan mask to avoid duplicate set_chlist + * @autonomous: If automatic measurements are active (for buffer support) + * @part_info: Part information + * @trig: Pointer to iio trigger + * @meas_rate: Value of MEAS_RATE register. Only set in HW in auto mode + * @buffer: Used to pack data read from sensor. + */ +struct si1145_data { + struct i2c_client *client; + struct mutex lock; + struct mutex cmdlock; + int rsp_seq; + const struct si1145_part_info *part_info; + unsigned long scan_mask; + bool autonomous; + struct iio_trigger *trig; + int meas_rate; + /* + * Ensure timestamp will be naturally aligned if present. + * Maximum buffer size (may be only partly used if not all + * channels are enabled): + * 6*2 bytes channels data + 4 bytes alignment + + * 8 bytes timestamp + */ + u8 buffer[24] __aligned(8); +}; + +/* + * __si1145_command_reset() - Send CMD_NOP and wait for response 0 + * + * Does not modify data->rsp_seq + * + * Return: 0 on success and -errno on error. + */ +static int __si1145_command_reset(struct si1145_data *data) +{ + struct device *dev = &data->client->dev; + unsigned long stop_jiffies; + int ret; + + ret = i2c_smbus_write_byte_data(data->client, SI1145_REG_COMMAND, + SI1145_CMD_NOP); + if (ret < 0) + return ret; + msleep(SI1145_COMMAND_MINSLEEP_MS); + + stop_jiffies = jiffies + SI1145_COMMAND_TIMEOUT_MS * HZ / 1000; + while (true) { + ret = i2c_smbus_read_byte_data(data->client, + SI1145_REG_RESPONSE); + if (ret <= 0) + return ret; + if (time_after(jiffies, stop_jiffies)) { + dev_warn(dev, "timeout on reset\n"); + return -ETIMEDOUT; + } + msleep(SI1145_COMMAND_MINSLEEP_MS); + } +} + +/* + * si1145_command() - Execute a command and poll the response register + * + * All conversion overflows are reported as -EOVERFLOW + * INVALID_SETTING is reported as -EINVAL + * Timeouts are reported as -ETIMEDOUT + * + * Return: 0 on success or -errno on failure + */ +static int si1145_command(struct si1145_data *data, u8 cmd) +{ + struct device *dev = &data->client->dev; + unsigned long stop_jiffies; + int ret; + + mutex_lock(&data->cmdlock); + + if (data->rsp_seq < 0) { + ret = __si1145_command_reset(data); + if (ret < 0) { + dev_err(dev, "failed to reset command counter, ret=%d\n", + ret); + goto out; + } + data->rsp_seq = 0; + } + + ret = i2c_smbus_write_byte_data(data->client, SI1145_REG_COMMAND, cmd); + if (ret) { + dev_warn(dev, "failed to write command, ret=%d\n", ret); + goto out; + } + /* Sleep a little to ensure the command is received */ + msleep(SI1145_COMMAND_MINSLEEP_MS); + + stop_jiffies = jiffies + SI1145_COMMAND_TIMEOUT_MS * HZ / 1000; + while (true) { + ret = i2c_smbus_read_byte_data(data->client, + SI1145_REG_RESPONSE); + if (ret < 0) { + dev_warn(dev, "failed to read response, ret=%d\n", ret); + break; + } + + if ((ret & ~SI1145_RSP_COUNTER_MASK) == 0) { + if (ret == data->rsp_seq) { + if (time_after(jiffies, stop_jiffies)) { + dev_warn(dev, "timeout on command 0x%02x\n", + cmd); + ret = -ETIMEDOUT; + break; + } + msleep(SI1145_COMMAND_MINSLEEP_MS); + continue; + } + if (ret == ((data->rsp_seq + 1) & + SI1145_RSP_COUNTER_MASK)) { + data->rsp_seq = ret; + ret = 0; + break; + } + dev_warn(dev, "unexpected response counter %d instead of %d\n", + ret, (data->rsp_seq + 1) & + SI1145_RSP_COUNTER_MASK); + ret = -EIO; + } else { + if (ret == SI1145_RSP_INVALID_SETTING) { + dev_warn(dev, "INVALID_SETTING error on command 0x%02x\n", + cmd); + ret = -EINVAL; + } else { + /* All overflows are treated identically */ + dev_dbg(dev, "overflow, ret=%d, cmd=0x%02x\n", + ret, cmd); + ret = -EOVERFLOW; + } + } + + /* Force a counter reset next time */ + data->rsp_seq = -1; + break; + } + +out: + mutex_unlock(&data->cmdlock); + + return ret; +} + +static int si1145_param_update(struct si1145_data *data, u8 op, u8 param, + u8 value) +{ + int ret; + + ret = i2c_smbus_write_byte_data(data->client, + SI1145_REG_PARAM_WR, value); + if (ret < 0) + return ret; + + return si1145_command(data, op | (param & 0x1F)); +} + +static int si1145_param_set(struct si1145_data *data, u8 param, u8 value) +{ + return si1145_param_update(data, SI1145_CMD_PARAM_SET, param, value); +} + +/* Set param. Returns negative errno or current value */ +static int si1145_param_query(struct si1145_data *data, u8 param) +{ + int ret; + + ret = si1145_command(data, SI1145_CMD_PARAM_QUERY | (param & 0x1F)); + if (ret < 0) + return ret; + + return i2c_smbus_read_byte_data(data->client, SI1145_REG_PARAM_RD); +} + +/* Expand 8 bit compressed value to 16 bit, see Silabs AN498 */ +static u16 si1145_uncompress(u8 x) +{ + u16 result = 0; + u8 exponent = 0; + + if (x < 8) + return 0; + + exponent = (x & 0xf0) >> 4; + result = 0x10 | (x & 0x0f); + + if (exponent >= 4) + return result << (exponent - 4); + return result >> (4 - exponent); +} + +/* Compress 16 bit value to 8 bit, see Silabs AN498 */ +static u8 si1145_compress(u16 x) +{ + u32 exponent = 0; + u32 significand = 0; + u32 tmp = x; + + if (x == 0x0000) + return 0x00; + if (x == 0x0001) + return 0x08; + + while (1) { + tmp >>= 1; + exponent += 1; + if (tmp == 1) + break; + } + + if (exponent < 5) { + significand = x << (4 - exponent); + return (exponent << 4) | (significand & 0xF); + } + + significand = x >> (exponent - 5); + if (significand & 1) { + significand += 2; + if (significand & 0x0040) { + exponent += 1; + significand >>= 1; + } + } + + return (exponent << 4) | ((significand >> 1) & 0xF); +} + +/* Write meas_rate in hardware */ +static int si1145_set_meas_rate(struct si1145_data *data, int interval) +{ + if (data->part_info->uncompressed_meas_rate) + return i2c_smbus_write_word_data(data->client, + SI1145_REG_MEAS_RATE, interval); + else + return i2c_smbus_write_byte_data(data->client, + SI1145_REG_MEAS_RATE, interval); +} + +static int si1145_read_samp_freq(struct si1145_data *data, int *val, int *val2) +{ + *val = 32000; + if (data->part_info->uncompressed_meas_rate) + *val2 = data->meas_rate; + else + *val2 = si1145_uncompress(data->meas_rate); + return IIO_VAL_FRACTIONAL; +} + +/* Set the samp freq in driver private data */ +static int si1145_store_samp_freq(struct si1145_data *data, int val) +{ + int ret = 0; + int meas_rate; + + if (val <= 0 || val > 32000) + return -ERANGE; + meas_rate = 32000 / val; + + mutex_lock(&data->lock); + if (data->autonomous) { + ret = si1145_set_meas_rate(data, meas_rate); + if (ret) + goto out; + } + if (data->part_info->uncompressed_meas_rate) + data->meas_rate = meas_rate; + else + data->meas_rate = si1145_compress(meas_rate); + +out: + mutex_unlock(&data->lock); + + return ret; +} + +static irqreturn_t si1145_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct si1145_data *data = iio_priv(indio_dev); + int i, j = 0; + int ret; + u8 irq_status = 0; + + if (!data->autonomous) { + ret = si1145_command(data, SI1145_CMD_PSALS_FORCE); + if (ret < 0 && ret != -EOVERFLOW) + goto done; + } else { + irq_status = ret = i2c_smbus_read_byte_data(data->client, + SI1145_REG_IRQ_STATUS); + if (ret < 0) + goto done; + if (!(irq_status & SI1145_MASK_ALL_IE)) + goto done; + } + + iio_for_each_active_channel(indio_dev, i) { + int run = 1; + + while (i + run < iio_get_masklength(indio_dev)) { + if (!test_bit(i + run, indio_dev->active_scan_mask)) + break; + if (indio_dev->channels[i + run].address != + indio_dev->channels[i].address + 2 * run) + break; + run++; + } + + ret = i2c_smbus_read_i2c_block_data_or_emulated( + data->client, indio_dev->channels[i].address, + sizeof(u16) * run, &data->buffer[j]); + if (ret < 0) + goto done; + j += run * sizeof(u16); + i += run - 1; + } + + if (data->autonomous) { + ret = i2c_smbus_write_byte_data(data->client, + SI1145_REG_IRQ_STATUS, + irq_status & SI1145_MASK_ALL_IE); + if (ret < 0) + goto done; + } + + iio_push_to_buffers_with_ts(indio_dev, data->buffer, + sizeof(data->buffer), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int si1145_set_chlist(struct iio_dev *indio_dev, unsigned long scan_mask) +{ + struct si1145_data *data = iio_priv(indio_dev); + u8 reg = 0, mux; + int ret; + int i; + + /* channel list already set, no need to reprogram */ + if (data->scan_mask == scan_mask) + return 0; + + for_each_set_bit(i, &scan_mask, iio_get_masklength(indio_dev)) { + switch (indio_dev->channels[i].address) { + case SI1145_REG_ALSVIS_DATA: + reg |= SI1145_CHLIST_EN_ALSVIS; + break; + case SI1145_REG_ALSIR_DATA: + reg |= SI1145_CHLIST_EN_ALSIR; + break; + case SI1145_REG_PS1_DATA: + reg |= SI1145_CHLIST_EN_PS1; + break; + case SI1145_REG_PS2_DATA: + reg |= SI1145_CHLIST_EN_PS2; + break; + case SI1145_REG_PS3_DATA: + reg |= SI1145_CHLIST_EN_PS3; + break; + case SI1145_REG_AUX_DATA: + switch (indio_dev->channels[i].type) { + case IIO_UVINDEX: + reg |= SI1145_CHLIST_EN_UV; + break; + default: + reg |= SI1145_CHLIST_EN_AUX; + if (indio_dev->channels[i].type == IIO_TEMP) + mux = SI1145_MUX_TEMP; + else + mux = SI1145_MUX_VDD; + ret = si1145_param_set(data, + SI1145_PARAM_AUX_ADC_MUX, mux); + if (ret < 0) + return ret; + + break; + } + } + } + + data->scan_mask = scan_mask; + ret = si1145_param_set(data, SI1145_PARAM_CHLIST, reg); + + return ret < 0 ? ret : 0; +} + +static int si1145_measure(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct si1145_data *data = iio_priv(indio_dev); + u8 cmd; + int ret; + + ret = si1145_set_chlist(indio_dev, BIT(chan->scan_index)); + if (ret < 0) + return ret; + + cmd = (chan->type == IIO_PROXIMITY) ? SI1145_CMD_PS_FORCE : + SI1145_CMD_ALS_FORCE; + ret = si1145_command(data, cmd); + if (ret < 0 && ret != -EOVERFLOW) + return ret; + + return i2c_smbus_read_word_data(data->client, chan->address); +} + +/* + * Conversion between iio scale and ADC_GAIN values + * These could be further adjusted but proximity/intensity are dimensionless + */ +static const int si1145_proximity_scale_available[] = { + 128, 64, 32, 16, 8, 4}; +static const int si1145_intensity_scale_available[] = { + 128, 64, 32, 16, 8, 4, 2, 1}; +static IIO_CONST_ATTR(in_proximity_scale_available, + "128 64 32 16 8 4"); +static IIO_CONST_ATTR(in_intensity_scale_available, + "128 64 32 16 8 4 2 1"); +static IIO_CONST_ATTR(in_intensity_ir_scale_available, + "128 64 32 16 8 4 2 1"); + +static int si1145_scale_from_adcgain(int regval) +{ + return 128 >> regval; +} + +static int si1145_proximity_adcgain_from_scale(int val, int val2) +{ + val = find_closest_descending(val, si1145_proximity_scale_available, + ARRAY_SIZE(si1145_proximity_scale_available)); + if (val < 0 || val > 5 || val2 != 0) + return -EINVAL; + + return val; +} + +static int si1145_intensity_adcgain_from_scale(int val, int val2) +{ + val = find_closest_descending(val, si1145_intensity_scale_available, + ARRAY_SIZE(si1145_intensity_scale_available)); + if (val < 0 || val > 7 || val2 != 0) + return -EINVAL; + + return val; +} + +static int si1145_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct si1145_data *data = iio_priv(indio_dev); + int ret; + u8 reg; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_PROXIMITY: + case IIO_VOLTAGE: + case IIO_TEMP: + case IIO_UVINDEX: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = si1145_measure(indio_dev, chan); + iio_device_release_direct(indio_dev); + + if (ret < 0) + return ret; + + *val = ret; + + return IIO_VAL_INT; + case IIO_CURRENT: + ret = i2c_smbus_read_byte_data(data->client, + SI1145_PS_LED_REG(chan->channel)); + if (ret < 0) + return ret; + + *val = (ret >> SI1145_PS_LED_SHIFT(chan->channel)) + & 0x0f; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PROXIMITY: + reg = SI1145_PARAM_PS_ADC_GAIN; + break; + case IIO_INTENSITY: + if (chan->channel2 == IIO_MOD_LIGHT_IR) + reg = SI1145_PARAM_ALSIR_ADC_GAIN; + else + reg = SI1145_PARAM_ALSVIS_ADC_GAIN; + break; + case IIO_TEMP: + *val = 28; + *val2 = 571429; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_UVINDEX: + *val = 0; + *val2 = 10000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + + ret = si1145_param_query(data, reg); + if (ret < 0) + return ret; + + *val = si1145_scale_from_adcgain(ret & 0x07); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + /* + * -ADC offset - ADC counts @ 25°C - + * 35 * ADC counts / °C + */ + *val = -256 - 11136 + 25 * 35; + return IIO_VAL_INT; + default: + /* + * All ADC measurements have are by default offset + * by -256 + * See AN498 5.6.3 + */ + ret = si1145_param_query(data, SI1145_PARAM_ADC_OFFSET); + if (ret < 0) + return ret; + *val = -si1145_uncompress(ret); + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SAMP_FREQ: + return si1145_read_samp_freq(data, val, val2); + default: + return -EINVAL; + } +} + +static int si1145_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct si1145_data *data = iio_priv(indio_dev); + u8 reg1, reg2, shift; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PROXIMITY: + val = si1145_proximity_adcgain_from_scale(val, val2); + if (val < 0) + return val; + reg1 = SI1145_PARAM_PS_ADC_GAIN; + reg2 = SI1145_PARAM_PS_ADC_COUNTER; + break; + case IIO_INTENSITY: + val = si1145_intensity_adcgain_from_scale(val, val2); + if (val < 0) + return val; + if (chan->channel2 == IIO_MOD_LIGHT_IR) { + reg1 = SI1145_PARAM_ALSIR_ADC_GAIN; + reg2 = SI1145_PARAM_ALSIR_ADC_COUNTER; + } else { + reg1 = SI1145_PARAM_ALSVIS_ADC_GAIN; + reg2 = SI1145_PARAM_ALSVIS_ADC_COUNTER; + } + break; + default: + return -EINVAL; + } + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = si1145_param_set(data, reg1, val); + if (ret < 0) { + iio_device_release_direct(indio_dev); + return ret; + } + /* Set recovery period to one's complement of gain */ + ret = si1145_param_set(data, reg2, (~val & 0x07) << 4); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_CURRENT) + return -EINVAL; + + if (val < 0 || val > 15 || val2 != 0) + return -EINVAL; + + reg1 = SI1145_PS_LED_REG(chan->channel); + shift = SI1145_PS_LED_SHIFT(chan->channel); + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = i2c_smbus_read_byte_data(data->client, reg1); + if (ret < 0) { + iio_device_release_direct(indio_dev); + return ret; + } + ret = i2c_smbus_write_byte_data(data->client, reg1, + (ret & ~(0x0f << shift)) | + ((val & 0x0f) << shift)); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return si1145_store_samp_freq(data, val); + default: + return -EINVAL; + } +} + +#define SI1145_ST { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ +} + +#define SI1145_INTENSITY_CHANNEL(_si) { \ + .type = IIO_INTENSITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_type = SI1145_ST, \ + .scan_index = _si, \ + .address = SI1145_REG_ALSVIS_DATA, \ +} + +#define SI1145_INTENSITY_IR_CHANNEL(_si) { \ + .type = IIO_INTENSITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .modified = 1, \ + .channel2 = IIO_MOD_LIGHT_IR, \ + .scan_type = SI1145_ST, \ + .scan_index = _si, \ + .address = SI1145_REG_ALSIR_DATA, \ +} + +#define SI1145_TEMP_CHANNEL(_si) { \ + .type = IIO_TEMP, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_type = SI1145_ST, \ + .scan_index = _si, \ + .address = SI1145_REG_AUX_DATA, \ +} + +#define SI1145_UV_CHANNEL(_si) { \ + .type = IIO_UVINDEX, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_type = SI1145_ST, \ + .scan_index = _si, \ + .address = SI1145_REG_AUX_DATA, \ +} + +#define SI1145_PROXIMITY_CHANNEL(_si, _ch) { \ + .type = IIO_PROXIMITY, \ + .indexed = 1, \ + .channel = _ch, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_type = SI1145_ST, \ + .scan_index = _si, \ + .address = SI1145_REG_PS1_DATA + _ch * 2, \ +} + +#define SI1145_VOLTAGE_CHANNEL(_si) { \ + .type = IIO_VOLTAGE, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_type = SI1145_ST, \ + .scan_index = _si, \ + .address = SI1145_REG_AUX_DATA, \ +} + +#define SI1145_CURRENT_CHANNEL(_ch) { \ + .type = IIO_CURRENT, \ + .indexed = 1, \ + .channel = _ch, \ + .output = 1, \ + .scan_index = -1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ +} + +static const struct iio_chan_spec si1132_channels[] = { + SI1145_INTENSITY_CHANNEL(0), + SI1145_INTENSITY_IR_CHANNEL(1), + SI1145_TEMP_CHANNEL(2), + SI1145_VOLTAGE_CHANNEL(3), + SI1145_UV_CHANNEL(4), + IIO_CHAN_SOFT_TIMESTAMP(6), +}; + +static const struct iio_chan_spec si1141_channels[] = { + SI1145_INTENSITY_CHANNEL(0), + SI1145_INTENSITY_IR_CHANNEL(1), + SI1145_PROXIMITY_CHANNEL(2, 0), + SI1145_TEMP_CHANNEL(3), + SI1145_VOLTAGE_CHANNEL(4), + IIO_CHAN_SOFT_TIMESTAMP(5), + SI1145_CURRENT_CHANNEL(0), +}; + +static const struct iio_chan_spec si1142_channels[] = { + SI1145_INTENSITY_CHANNEL(0), + SI1145_INTENSITY_IR_CHANNEL(1), + SI1145_PROXIMITY_CHANNEL(2, 0), + SI1145_PROXIMITY_CHANNEL(3, 1), + SI1145_TEMP_CHANNEL(4), + SI1145_VOLTAGE_CHANNEL(5), + IIO_CHAN_SOFT_TIMESTAMP(6), + SI1145_CURRENT_CHANNEL(0), + SI1145_CURRENT_CHANNEL(1), +}; + +static const struct iio_chan_spec si1143_channels[] = { + SI1145_INTENSITY_CHANNEL(0), + SI1145_INTENSITY_IR_CHANNEL(1), + SI1145_PROXIMITY_CHANNEL(2, 0), + SI1145_PROXIMITY_CHANNEL(3, 1), + SI1145_PROXIMITY_CHANNEL(4, 2), + SI1145_TEMP_CHANNEL(5), + SI1145_VOLTAGE_CHANNEL(6), + IIO_CHAN_SOFT_TIMESTAMP(7), + SI1145_CURRENT_CHANNEL(0), + SI1145_CURRENT_CHANNEL(1), + SI1145_CURRENT_CHANNEL(2), +}; + +static const struct iio_chan_spec si1145_channels[] = { + SI1145_INTENSITY_CHANNEL(0), + SI1145_INTENSITY_IR_CHANNEL(1), + SI1145_PROXIMITY_CHANNEL(2, 0), + SI1145_TEMP_CHANNEL(3), + SI1145_VOLTAGE_CHANNEL(4), + SI1145_UV_CHANNEL(5), + IIO_CHAN_SOFT_TIMESTAMP(6), + SI1145_CURRENT_CHANNEL(0), +}; + +static const struct iio_chan_spec si1146_channels[] = { + SI1145_INTENSITY_CHANNEL(0), + SI1145_INTENSITY_IR_CHANNEL(1), + SI1145_TEMP_CHANNEL(2), + SI1145_VOLTAGE_CHANNEL(3), + SI1145_UV_CHANNEL(4), + SI1145_PROXIMITY_CHANNEL(5, 0), + SI1145_PROXIMITY_CHANNEL(6, 1), + IIO_CHAN_SOFT_TIMESTAMP(7), + SI1145_CURRENT_CHANNEL(0), + SI1145_CURRENT_CHANNEL(1), +}; + +static const struct iio_chan_spec si1147_channels[] = { + SI1145_INTENSITY_CHANNEL(0), + SI1145_INTENSITY_IR_CHANNEL(1), + SI1145_PROXIMITY_CHANNEL(2, 0), + SI1145_PROXIMITY_CHANNEL(3, 1), + SI1145_PROXIMITY_CHANNEL(4, 2), + SI1145_TEMP_CHANNEL(5), + SI1145_VOLTAGE_CHANNEL(6), + SI1145_UV_CHANNEL(7), + IIO_CHAN_SOFT_TIMESTAMP(8), + SI1145_CURRENT_CHANNEL(0), + SI1145_CURRENT_CHANNEL(1), + SI1145_CURRENT_CHANNEL(2), +}; + +static struct attribute *si1132_attributes[] = { + &iio_const_attr_in_intensity_scale_available.dev_attr.attr, + &iio_const_attr_in_intensity_ir_scale_available.dev_attr.attr, + NULL, +}; + +static struct attribute *si114x_attributes[] = { + &iio_const_attr_in_intensity_scale_available.dev_attr.attr, + &iio_const_attr_in_intensity_ir_scale_available.dev_attr.attr, + &iio_const_attr_in_proximity_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group si1132_attribute_group = { + .attrs = si1132_attributes, +}; + +static const struct attribute_group si114x_attribute_group = { + .attrs = si114x_attributes, +}; + + +static const struct iio_info si1132_info = { + .read_raw = si1145_read_raw, + .write_raw = si1145_write_raw, + .attrs = &si1132_attribute_group, +}; + +static const struct iio_info si114x_info = { + .read_raw = si1145_read_raw, + .write_raw = si1145_write_raw, + .attrs = &si114x_attribute_group, +}; + +#define SI1145_PART(id, iio_info, chans, leds, uncompressed_meas_rate) \ + {id, iio_info, chans, ARRAY_SIZE(chans), leds, uncompressed_meas_rate} + +static const struct si1145_part_info si1145_part_info[] = { + [SI1132] = SI1145_PART(0x32, &si1132_info, si1132_channels, 0, true), + [SI1141] = SI1145_PART(0x41, &si114x_info, si1141_channels, 1, false), + [SI1142] = SI1145_PART(0x42, &si114x_info, si1142_channels, 2, false), + [SI1143] = SI1145_PART(0x43, &si114x_info, si1143_channels, 3, false), + [SI1145] = SI1145_PART(0x45, &si114x_info, si1145_channels, 1, true), + [SI1146] = SI1145_PART(0x46, &si114x_info, si1146_channels, 2, true), + [SI1147] = SI1145_PART(0x47, &si114x_info, si1147_channels, 3, true), +}; + +static int si1145_initialize(struct si1145_data *data) +{ + struct i2c_client *client = data->client; + int ret; + + ret = i2c_smbus_write_byte_data(client, SI1145_REG_COMMAND, + SI1145_CMD_RESET); + if (ret < 0) + return ret; + msleep(SI1145_COMMAND_TIMEOUT_MS); + + /* Hardware key, magic value */ + ret = i2c_smbus_write_byte_data(client, SI1145_REG_HW_KEY, 0x17); + if (ret < 0) + return ret; + msleep(SI1145_COMMAND_TIMEOUT_MS); + + /* Turn off autonomous mode */ + ret = si1145_set_meas_rate(data, 0); + if (ret < 0) + return ret; + + /* Initialize sampling freq to 10 Hz */ + ret = si1145_store_samp_freq(data, 10); + if (ret < 0) + return ret; + + /* Set LED currents to 45 mA; have 4 bits, see Table 2 in datasheet */ + switch (data->part_info->num_leds) { + case 3: + ret = i2c_smbus_write_byte_data(client, + SI1145_REG_PS_LED3, + SI1145_LED_CURRENT_45mA); + if (ret < 0) + return ret; + fallthrough; + case 2: + ret = i2c_smbus_write_byte_data(client, + SI1145_REG_PS_LED21, + (SI1145_LED_CURRENT_45mA << 4) | + SI1145_LED_CURRENT_45mA); + break; + case 1: + ret = i2c_smbus_write_byte_data(client, + SI1145_REG_PS_LED21, + SI1145_LED_CURRENT_45mA); + break; + default: + ret = 0; + break; + } + if (ret < 0) + return ret; + + /* Set normal proximity measurement mode */ + ret = si1145_param_set(data, SI1145_PARAM_PS_ADC_MISC, + SI1145_PS_ADC_MODE_NORMAL); + if (ret < 0) + return ret; + + ret = si1145_param_set(data, SI1145_PARAM_PS_ADC_GAIN, 0x01); + if (ret < 0) + return ret; + + /* ADC_COUNTER should be one complement of ADC_GAIN */ + ret = si1145_param_set(data, SI1145_PARAM_PS_ADC_COUNTER, 0x06 << 4); + if (ret < 0) + return ret; + + /* Set ALS visible measurement mode */ + ret = si1145_param_set(data, SI1145_PARAM_ALSVIS_ADC_MISC, + SI1145_ADC_MISC_RANGE); + if (ret < 0) + return ret; + + ret = si1145_param_set(data, SI1145_PARAM_ALSVIS_ADC_GAIN, 0x03); + if (ret < 0) + return ret; + + ret = si1145_param_set(data, SI1145_PARAM_ALSVIS_ADC_COUNTER, + 0x04 << 4); + if (ret < 0) + return ret; + + /* Set ALS IR measurement mode */ + ret = si1145_param_set(data, SI1145_PARAM_ALSIR_ADC_MISC, + SI1145_ADC_MISC_RANGE); + if (ret < 0) + return ret; + + ret = si1145_param_set(data, SI1145_PARAM_ALSIR_ADC_GAIN, 0x01); + if (ret < 0) + return ret; + + ret = si1145_param_set(data, SI1145_PARAM_ALSIR_ADC_COUNTER, + 0x06 << 4); + if (ret < 0) + return ret; + + /* + * Initialize UCOEF to default values in datasheet + * These registers are normally zero on reset + */ + if (data->part_info == &si1145_part_info[SI1132] || + data->part_info == &si1145_part_info[SI1145] || + data->part_info == &si1145_part_info[SI1146] || + data->part_info == &si1145_part_info[SI1147]) { + ret = i2c_smbus_write_byte_data(data->client, + SI1145_REG_UCOEF1, + SI1145_UCOEF1_DEFAULT); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(data->client, + SI1145_REG_UCOEF2, SI1145_UCOEF2_DEFAULT); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(data->client, + SI1145_REG_UCOEF3, SI1145_UCOEF3_DEFAULT); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(data->client, + SI1145_REG_UCOEF4, SI1145_UCOEF4_DEFAULT); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + * Program the channels we want to measure with CMD_PSALS_AUTO. No need for + * _postdisable as we stop with CMD_PSALS_PAUSE; single measurement (direct) + * mode reprograms the channels list anyway... + */ +static int si1145_buffer_preenable(struct iio_dev *indio_dev) +{ + struct si1145_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + ret = si1145_set_chlist(indio_dev, *indio_dev->active_scan_mask); + mutex_unlock(&data->lock); + + return ret; +} + +static bool si1145_validate_scan_mask(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct si1145_data *data = iio_priv(indio_dev); + unsigned int count = 0; + int i; + + /* Check that at most one AUX channel is enabled */ + for_each_set_bit(i, scan_mask, data->part_info->num_channels) { + if (indio_dev->channels[i].address == SI1145_REG_AUX_DATA) + count++; + } + + return count <= 1; +} + +static const struct iio_buffer_setup_ops si1145_buffer_setup_ops = { + .preenable = si1145_buffer_preenable, + .validate_scan_mask = si1145_validate_scan_mask, +}; + +/* + * si1145_trigger_set_state() - Set trigger state + * + * When not using triggers interrupts are disabled and measurement rate is + * set to zero in order to minimize power consumption. + */ +static int si1145_trigger_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct si1145_data *data = iio_priv(indio_dev); + int err = 0, ret; + + mutex_lock(&data->lock); + + if (state) { + data->autonomous = true; + err = i2c_smbus_write_byte_data(data->client, + SI1145_REG_INT_CFG, SI1145_INT_CFG_OE); + if (err < 0) + goto disable; + err = i2c_smbus_write_byte_data(data->client, + SI1145_REG_IRQ_ENABLE, SI1145_MASK_ALL_IE); + if (err < 0) + goto disable; + err = si1145_set_meas_rate(data, data->meas_rate); + if (err < 0) + goto disable; + err = si1145_command(data, SI1145_CMD_PSALS_AUTO); + if (err < 0) + goto disable; + } else { +disable: + /* Disable as much as possible skipping errors */ + ret = si1145_command(data, SI1145_CMD_PSALS_PAUSE); + if (ret < 0 && !err) + err = ret; + ret = si1145_set_meas_rate(data, 0); + if (ret < 0 && !err) + err = ret; + ret = i2c_smbus_write_byte_data(data->client, + SI1145_REG_IRQ_ENABLE, 0); + if (ret < 0 && !err) + err = ret; + ret = i2c_smbus_write_byte_data(data->client, + SI1145_REG_INT_CFG, 0); + if (ret < 0 && !err) + err = ret; + data->autonomous = false; + } + + mutex_unlock(&data->lock); + return err; +} + +static const struct iio_trigger_ops si1145_trigger_ops = { + .set_trigger_state = si1145_trigger_set_state, +}; + +static int si1145_probe_trigger(struct iio_dev *indio_dev) +{ + struct si1145_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + struct iio_trigger *trig; + int ret; + + trig = devm_iio_trigger_alloc(&client->dev, + "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); + if (!trig) + return -ENOMEM; + + trig->ops = &si1145_trigger_ops; + iio_trigger_set_drvdata(trig, indio_dev); + + ret = devm_request_irq(&client->dev, client->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_FALLING | IRQF_NO_THREAD, + "si1145_irq", + trig); + if (ret) + return ret; + + ret = devm_iio_trigger_register(&client->dev, trig); + if (ret) + return ret; + + data->trig = trig; + indio_dev->trig = iio_trigger_get(data->trig); + + return 0; +} + +static int si1145_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct si1145_data *data; + struct iio_dev *indio_dev; + u8 part_id, rev_id, seq_id; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->part_info = &si1145_part_info[id->driver_data]; + + part_id = ret = i2c_smbus_read_byte_data(data->client, + SI1145_REG_PART_ID); + if (ret < 0) + return ret; + rev_id = ret = i2c_smbus_read_byte_data(data->client, + SI1145_REG_REV_ID); + if (ret < 0) + return ret; + seq_id = ret = i2c_smbus_read_byte_data(data->client, + SI1145_REG_SEQ_ID); + if (ret < 0) + return ret; + dev_info(&client->dev, "device ID part 0x%02x rev 0x%02x seq 0x%02x\n", + part_id, rev_id, seq_id); + if (part_id != data->part_info->part) { + dev_err(&client->dev, "part ID mismatch got 0x%02x, expected 0x%02x\n", + part_id, data->part_info->part); + return -ENODEV; + } + + indio_dev->name = id->name; + indio_dev->channels = data->part_info->channels; + indio_dev->num_channels = data->part_info->num_channels; + indio_dev->info = data->part_info->iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + mutex_init(&data->lock); + mutex_init(&data->cmdlock); + + ret = si1145_initialize(data); + if (ret < 0) + return ret; + + ret = devm_iio_triggered_buffer_setup(&client->dev, + indio_dev, NULL, + si1145_trigger_handler, &si1145_buffer_setup_ops); + if (ret < 0) + return ret; + + if (client->irq) { + ret = si1145_probe_trigger(indio_dev); + if (ret < 0) + return ret; + } else { + dev_info(&client->dev, "no irq, using polling\n"); + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id si1145_ids[] = { + { .name = "si1132", .driver_data = SI1132 }, + { .name = "si1141", .driver_data = SI1141 }, + { .name = "si1142", .driver_data = SI1142 }, + { .name = "si1143", .driver_data = SI1143 }, + { .name = "si1145", .driver_data = SI1145 }, + { .name = "si1146", .driver_data = SI1146 }, + { .name = "si1147", .driver_data = SI1147 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, si1145_ids); + +static struct i2c_driver si1145_driver = { + .driver = { + .name = "si1145", + }, + .probe = si1145_probe, + .id_table = si1145_ids, +}; + +module_i2c_driver(si1145_driver); + +MODULE_AUTHOR("Peter Meerwald-Stadler <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("Silabs SI1132 and SI1141/2/3/5/6/7 proximity, ambient light and UV index sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/st_uvis25.h b/drivers/iio/light/st_uvis25.h new file mode 100644 index 000000000..78bc56aad --- /dev/null +++ b/drivers/iio/light/st_uvis25.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * STMicroelectronics uvis25 sensor driver + * + * Copyright 2017 STMicroelectronics Inc. + * + * Lorenzo Bianconi <lorenzo.bianconi83@gmail.com> + */ + +#ifndef ST_UVIS25_H +#define ST_UVIS25_H + +#define ST_UVIS25_DEV_NAME "uvis25" + +#include <linux/iio/iio.h> + +/** + * struct st_uvis25_hw - ST UVIS25 sensor instance + * @regmap: Register map of the device. + * @trig: The trigger in use by the driver. + * @enabled: Status of the sensor (false->off, true->on). + * @irq: Device interrupt line (I2C or SPI). + */ +struct st_uvis25_hw { + struct regmap *regmap; + + struct iio_trigger *trig; + bool enabled; + int irq; +}; + +extern const struct dev_pm_ops st_uvis25_pm_ops; + +int st_uvis25_probe(struct device *dev, int irq, struct regmap *regmap); + +#endif /* ST_UVIS25_H */ diff --git a/drivers/iio/light/st_uvis25_core.c b/drivers/iio/light/st_uvis25_core.c new file mode 100644 index 000000000..94d5261b2 --- /dev/null +++ b/drivers/iio/light/st_uvis25_core.c @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics uvis25 sensor driver + * + * Copyright 2017 STMicroelectronics Inc. + * + * Lorenzo Bianconi <lorenzo.bianconi83@gmail.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/iio/sysfs.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/interrupt.h> +#include <linux/irqreturn.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/buffer.h> +#include <linux/regmap.h> + +#include "st_uvis25.h" + +#define ST_UVIS25_REG_WHOAMI_ADDR 0x0f +#define ST_UVIS25_REG_WHOAMI_VAL 0xca +#define ST_UVIS25_REG_CTRL1_ADDR 0x20 +#define ST_UVIS25_REG_ODR_MASK BIT(0) +#define ST_UVIS25_REG_BDU_MASK BIT(1) +#define ST_UVIS25_REG_CTRL2_ADDR 0x21 +#define ST_UVIS25_REG_BOOT_MASK BIT(7) +#define ST_UVIS25_REG_CTRL3_ADDR 0x22 +#define ST_UVIS25_REG_HL_MASK BIT(7) +#define ST_UVIS25_REG_STATUS_ADDR 0x27 +#define ST_UVIS25_REG_UV_DA_MASK BIT(0) +#define ST_UVIS25_REG_OUT_ADDR 0x28 + +static const struct iio_chan_spec st_uvis25_channels[] = { + { + .type = IIO_UVINDEX, + .address = ST_UVIS25_REG_OUT_ADDR, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 8, + .storagebits = 8, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static int st_uvis25_check_whoami(struct st_uvis25_hw *hw) +{ + int err, data; + + err = regmap_read(hw->regmap, ST_UVIS25_REG_WHOAMI_ADDR, &data); + if (err < 0) { + dev_err(regmap_get_device(hw->regmap), + "failed to read whoami register\n"); + return err; + } + + if (data != ST_UVIS25_REG_WHOAMI_VAL) { + dev_err(regmap_get_device(hw->regmap), + "wrong whoami {%02x vs %02x}\n", + data, ST_UVIS25_REG_WHOAMI_VAL); + return -ENODEV; + } + + return 0; +} + +static int st_uvis25_set_enable(struct st_uvis25_hw *hw, bool enable) +{ + int err; + + err = regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL1_ADDR, + ST_UVIS25_REG_ODR_MASK, enable); + if (err < 0) + return err; + + hw->enabled = enable; + + return 0; +} + +static int st_uvis25_read_oneshot(struct st_uvis25_hw *hw, u8 addr, int *val) +{ + int err; + + err = st_uvis25_set_enable(hw, true); + if (err < 0) + return err; + + msleep(1500); + + /* + * in order to avoid possible race conditions with interrupt + * generation, disable the sensor first and then poll output + * register. That sequence guarantees the interrupt will be reset + * when irq line is unmasked + */ + err = st_uvis25_set_enable(hw, false); + if (err < 0) + return err; + + err = regmap_read(hw->regmap, addr, val); + + return err < 0 ? err : IIO_VAL_INT; +} + +static int st_uvis25_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *ch, + int *val, int *val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(iio_dev)) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: { + struct st_uvis25_hw *hw = iio_priv(iio_dev); + + /* + * mask irq line during oneshot read since the sensor + * does not export the capability to disable data-ready line + * in the register map and it is enabled by default. + * If the line is unmasked during read_raw() it will be set + * active and never reset since the trigger is disabled + */ + if (hw->irq > 0) + disable_irq(hw->irq); + ret = st_uvis25_read_oneshot(hw, ch->address, val); + if (hw->irq > 0) + enable_irq(hw->irq); + break; + } + default: + ret = -EINVAL; + break; + } + + iio_device_release_direct(iio_dev); + + return ret; +} + +static irqreturn_t st_uvis25_trigger_handler_thread(int irq, void *private) +{ + struct st_uvis25_hw *hw = private; + int err, status; + + err = regmap_read(hw->regmap, ST_UVIS25_REG_STATUS_ADDR, &status); + if (err < 0) + return IRQ_HANDLED; + + if (!(status & ST_UVIS25_REG_UV_DA_MASK)) + return IRQ_NONE; + + iio_trigger_poll_nested(hw->trig); + + return IRQ_HANDLED; +} + +static int st_uvis25_allocate_trigger(struct iio_dev *iio_dev) +{ + struct st_uvis25_hw *hw = iio_priv(iio_dev); + struct device *dev = regmap_get_device(hw->regmap); + bool irq_active_low = false; + unsigned long irq_type; + int err; + + irq_type = irq_get_trigger_type(hw->irq); + switch (irq_type) { + case IRQF_TRIGGER_HIGH: + case IRQF_TRIGGER_RISING: + break; + case IRQF_TRIGGER_LOW: + case IRQF_TRIGGER_FALLING: + irq_active_low = true; + break; + default: + dev_info(dev, "mode %lx unsupported\n", irq_type); + return -EINVAL; + } + + err = regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL3_ADDR, + ST_UVIS25_REG_HL_MASK, irq_active_low); + if (err < 0) + return err; + + err = devm_request_threaded_irq(dev, hw->irq, NULL, + st_uvis25_trigger_handler_thread, + irq_type | IRQF_ONESHOT, + iio_dev->name, hw); + if (err) + return err; + + hw->trig = devm_iio_trigger_alloc(dev, "%s-trigger", + iio_dev->name); + if (!hw->trig) + return -ENOMEM; + + iio_trigger_set_drvdata(hw->trig, iio_dev); + + return devm_iio_trigger_register(dev, hw->trig); +} + +static int st_uvis25_buffer_preenable(struct iio_dev *iio_dev) +{ + return st_uvis25_set_enable(iio_priv(iio_dev), true); +} + +static int st_uvis25_buffer_postdisable(struct iio_dev *iio_dev) +{ + return st_uvis25_set_enable(iio_priv(iio_dev), false); +} + +static const struct iio_buffer_setup_ops st_uvis25_buffer_ops = { + .preenable = st_uvis25_buffer_preenable, + .postdisable = st_uvis25_buffer_postdisable, +}; + +static irqreturn_t st_uvis25_buffer_handler_thread(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio_dev = pf->indio_dev; + struct st_uvis25_hw *hw = iio_priv(iio_dev); + unsigned int val; + int err; + /* Ensure timestamp is naturally aligned */ + struct { + u8 chan; + aligned_s64 ts; + } scan = { }; + + + err = regmap_read(hw->regmap, ST_UVIS25_REG_OUT_ADDR, &val); + if (err < 0) + goto out; + + scan.chan = val; + + iio_push_to_buffers_with_ts(iio_dev, &scan, sizeof(scan), + iio_get_time_ns(iio_dev)); + +out: + iio_trigger_notify_done(hw->trig); + + return IRQ_HANDLED; +} + +static int st_uvis25_allocate_buffer(struct iio_dev *iio_dev) +{ + struct st_uvis25_hw *hw = iio_priv(iio_dev); + + return devm_iio_triggered_buffer_setup(regmap_get_device(hw->regmap), + iio_dev, NULL, + st_uvis25_buffer_handler_thread, + &st_uvis25_buffer_ops); +} + +static const struct iio_info st_uvis25_info = { + .read_raw = st_uvis25_read_raw, +}; + +static int st_uvis25_init_sensor(struct st_uvis25_hw *hw) +{ + int err; + + err = regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL2_ADDR, + ST_UVIS25_REG_BOOT_MASK, 1); + if (err < 0) + return err; + + msleep(2000); + + return regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL1_ADDR, + ST_UVIS25_REG_BDU_MASK, 1); +} + +int st_uvis25_probe(struct device *dev, int irq, struct regmap *regmap) +{ + struct st_uvis25_hw *hw; + struct iio_dev *iio_dev; + int err; + + iio_dev = devm_iio_device_alloc(dev, sizeof(*hw)); + if (!iio_dev) + return -ENOMEM; + + dev_set_drvdata(dev, iio_dev); + + hw = iio_priv(iio_dev); + hw->irq = irq; + hw->regmap = regmap; + + err = st_uvis25_check_whoami(hw); + if (err < 0) + return err; + + iio_dev->modes = INDIO_DIRECT_MODE; + iio_dev->channels = st_uvis25_channels; + iio_dev->num_channels = ARRAY_SIZE(st_uvis25_channels); + iio_dev->name = ST_UVIS25_DEV_NAME; + iio_dev->info = &st_uvis25_info; + + err = st_uvis25_init_sensor(hw); + if (err < 0) + return err; + + if (hw->irq > 0) { + err = st_uvis25_allocate_buffer(iio_dev); + if (err < 0) + return err; + + err = st_uvis25_allocate_trigger(iio_dev); + if (err) + return err; + } + + return devm_iio_device_register(dev, iio_dev); +} +EXPORT_SYMBOL_NS(st_uvis25_probe, "IIO_UVIS25"); + +static int st_uvis25_suspend(struct device *dev) +{ + struct iio_dev *iio_dev = dev_get_drvdata(dev); + struct st_uvis25_hw *hw = iio_priv(iio_dev); + + return regmap_clear_bits(hw->regmap, ST_UVIS25_REG_CTRL1_ADDR, + ST_UVIS25_REG_ODR_MASK); +} + +static int st_uvis25_resume(struct device *dev) +{ + struct iio_dev *iio_dev = dev_get_drvdata(dev); + struct st_uvis25_hw *hw = iio_priv(iio_dev); + + if (hw->enabled) + return regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL1_ADDR, + ST_UVIS25_REG_ODR_MASK, 1); + + return 0; +} + +EXPORT_NS_SIMPLE_DEV_PM_OPS(st_uvis25_pm_ops, st_uvis25_suspend, st_uvis25_resume, IIO_UVIS25); + +MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>"); +MODULE_DESCRIPTION("STMicroelectronics uvis25 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/st_uvis25_i2c.c b/drivers/iio/light/st_uvis25_i2c.c new file mode 100644 index 000000000..d6b5ba139 --- /dev/null +++ b/drivers/iio/light/st_uvis25_i2c.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics uvis25 i2c driver + * + * Copyright 2017 STMicroelectronics Inc. + * + * Lorenzo Bianconi <lorenzo.bianconi83@gmail.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/regmap.h> + +#include "st_uvis25.h" + +#define UVIS25_I2C_AUTO_INCREMENT BIT(7) + +static const struct regmap_config st_uvis25_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .write_flag_mask = UVIS25_I2C_AUTO_INCREMENT, + .read_flag_mask = UVIS25_I2C_AUTO_INCREMENT, +}; + +static int st_uvis25_i2c_probe(struct i2c_client *client) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &st_uvis25_i2c_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to register i2c regmap %ld\n", + PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return st_uvis25_probe(&client->dev, client->irq, regmap); +} + +static const struct of_device_id st_uvis25_i2c_of_match[] = { + { .compatible = "st,uvis25", }, + { } +}; +MODULE_DEVICE_TABLE(of, st_uvis25_i2c_of_match); + +static const struct i2c_device_id st_uvis25_i2c_id_table[] = { + { .name = ST_UVIS25_DEV_NAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, st_uvis25_i2c_id_table); + +static struct i2c_driver st_uvis25_driver = { + .driver = { + .name = "st_uvis25_i2c", + .pm = pm_sleep_ptr(&st_uvis25_pm_ops), + .of_match_table = st_uvis25_i2c_of_match, + }, + .probe = st_uvis25_i2c_probe, + .id_table = st_uvis25_i2c_id_table, +}; +module_i2c_driver(st_uvis25_driver); + +MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>"); +MODULE_DESCRIPTION("STMicroelectronics uvis25 i2c driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_UVIS25"); diff --git a/drivers/iio/light/st_uvis25_spi.c b/drivers/iio/light/st_uvis25_spi.c new file mode 100644 index 000000000..0f78a0abf --- /dev/null +++ b/drivers/iio/light/st_uvis25_spi.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics uvis25 spi driver + * + * Copyright 2017 STMicroelectronics Inc. + * + * Lorenzo Bianconi <lorenzo.bianconi83@gmail.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/regmap.h> + +#include "st_uvis25.h" + +#define UVIS25_SENSORS_SPI_READ BIT(7) +#define UVIS25_SPI_AUTO_INCREMENT BIT(6) + +static const struct regmap_config st_uvis25_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .read_flag_mask = UVIS25_SENSORS_SPI_READ | UVIS25_SPI_AUTO_INCREMENT, + .write_flag_mask = UVIS25_SPI_AUTO_INCREMENT, +}; + +static int st_uvis25_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &st_uvis25_spi_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to register spi regmap %ld\n", + PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return st_uvis25_probe(&spi->dev, spi->irq, regmap); +} + +static const struct of_device_id st_uvis25_spi_of_match[] = { + { .compatible = "st,uvis25", }, + { } +}; +MODULE_DEVICE_TABLE(of, st_uvis25_spi_of_match); + +static const struct spi_device_id st_uvis25_spi_id_table[] = { + { .name = ST_UVIS25_DEV_NAME }, + { } +}; +MODULE_DEVICE_TABLE(spi, st_uvis25_spi_id_table); + +static struct spi_driver st_uvis25_driver = { + .driver = { + .name = "st_uvis25_spi", + .pm = pm_sleep_ptr(&st_uvis25_pm_ops), + .of_match_table = st_uvis25_spi_of_match, + }, + .probe = st_uvis25_spi_probe, + .id_table = st_uvis25_spi_id_table, +}; +module_spi_driver(st_uvis25_driver); + +MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>"); +MODULE_DESCRIPTION("STMicroelectronics uvis25 spi driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_UVIS25"); diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c new file mode 100644 index 000000000..7c8a1d2b2 --- /dev/null +++ b/drivers/iio/light/stk3310.c @@ -0,0 +1,811 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Sensortek STK3310/STK3311 Ambient Light and Proximity Sensor + * + * Copyright (c) 2015, Intel Corporation. + * + * IIO driver for STK3310/STK3311. 7-bit I2C address: 0x48. + */ + +#include <linux/array_size.h> +#include <linux/bits.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/sprintf.h> +#include <linux/sysfs.h> +#include <linux/types.h> + +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/types.h> + +#include <asm/byteorder.h> + +#define STK3310_REG_STATE 0x00 +#define STK3310_REG_PSCTRL 0x01 +#define STK3310_REG_ALSCTRL 0x02 +#define STK3310_REG_INT 0x04 +#define STK3310_REG_THDH_PS 0x06 +#define STK3310_REG_THDL_PS 0x08 +#define STK3310_REG_FLAG 0x10 +#define STK3310_REG_PS_DATA_MSB 0x11 +#define STK3310_REG_PS_DATA_LSB 0x12 +#define STK3310_REG_ALS_DATA_MSB 0x13 +#define STK3310_REG_ALS_DATA_LSB 0x14 +#define STK3310_REG_ID 0x3E +#define STK3310_MAX_REG 0x80 + +#define STK3310_STATE_EN_PS BIT(0) +#define STK3310_STATE_EN_ALS BIT(1) +#define STK3310_STATE_STANDBY 0x00 + +#define STK3013_CHIP_ID_VAL 0x31 +#define STK3310_CHIP_ID_VAL 0x13 +#define STK3311_CHIP_ID_VAL 0x1D +#define STK3311A_CHIP_ID_VAL 0x15 +#define STK3311S34_CHIP_ID_VAL 0x1E +#define STK3311X_CHIP_ID_VAL 0x12 +#define STK3335_CHIP_ID_VAL 0x51 +#define STK3310_PSINT_EN 0x01 +#define STK3310_PS_MAX_VAL 0xFFFF + +#define STK3310_DRIVER_NAME "stk3310" + +#define STK3310_SCALE_AVAILABLE "6.4 1.6 0.4 0.1" + +#define STK3310_IT_AVAILABLE \ + "0.000185 0.000370 0.000741 0.001480 0.002960 0.005920 0.011840 " \ + "0.023680 0.047360 0.094720 0.189440 0.378880 0.757760 1.515520 " \ + "3.031040 6.062080" + +#define STK3310_REGFIELD(name) \ + do { \ + data->reg_##name = \ + devm_regmap_field_alloc(&client->dev, regmap, \ + stk3310_reg_field_##name); \ + if (IS_ERR(data->reg_##name)) { \ + dev_err(&client->dev, "reg field alloc failed.\n"); \ + return PTR_ERR(data->reg_##name); \ + } \ + } while (0) + +static const struct reg_field stk3310_reg_field_state = + REG_FIELD(STK3310_REG_STATE, 0, 2); +static const struct reg_field stk3310_reg_field_als_gain = + REG_FIELD(STK3310_REG_ALSCTRL, 4, 5); +static const struct reg_field stk3310_reg_field_ps_gain = + REG_FIELD(STK3310_REG_PSCTRL, 4, 5); +static const struct reg_field stk3310_reg_field_als_it = + REG_FIELD(STK3310_REG_ALSCTRL, 0, 3); +static const struct reg_field stk3310_reg_field_ps_it = + REG_FIELD(STK3310_REG_PSCTRL, 0, 3); +static const struct reg_field stk3310_reg_field_int_ps = + REG_FIELD(STK3310_REG_INT, 0, 2); +static const struct reg_field stk3310_reg_field_flag_psint = + REG_FIELD(STK3310_REG_FLAG, 4, 4); +static const struct reg_field stk3310_reg_field_flag_nf = + REG_FIELD(STK3310_REG_FLAG, 0, 0); + +static const u8 stk3310_chip_ids[] = { + STK3013_CHIP_ID_VAL, + STK3310_CHIP_ID_VAL, + STK3311A_CHIP_ID_VAL, + STK3311S34_CHIP_ID_VAL, + STK3311X_CHIP_ID_VAL, + STK3311_CHIP_ID_VAL, + STK3335_CHIP_ID_VAL, +}; + +/* Estimate maximum proximity values with regard to measurement scale. */ +static const int stk3310_ps_max[4] = { + STK3310_PS_MAX_VAL / 640, + STK3310_PS_MAX_VAL / 160, + STK3310_PS_MAX_VAL / 40, + STK3310_PS_MAX_VAL / 10 +}; + +static const int stk3310_scale_table[][2] = { + {6, 400000}, {1, 600000}, {0, 400000}, {0, 100000} +}; + +/* Integration time in seconds, microseconds */ +static const int stk3310_it_table[][2] = { + {0, 185}, {0, 370}, {0, 741}, {0, 1480}, + {0, 2960}, {0, 5920}, {0, 11840}, {0, 23680}, + {0, 47360}, {0, 94720}, {0, 189440}, {0, 378880}, + {0, 757760}, {1, 515520}, {3, 31040}, {6, 62080}, +}; + +struct stk3310_data { + struct i2c_client *client; + struct mutex lock; + u64 timestamp; + struct regmap *regmap; + struct regmap_field *reg_state; + struct regmap_field *reg_als_gain; + struct regmap_field *reg_ps_gain; + struct regmap_field *reg_als_it; + struct regmap_field *reg_ps_it; + struct regmap_field *reg_int_ps; + struct regmap_field *reg_flag_psint; + struct regmap_field *reg_flag_nf; + u32 ps_thdl; + u32 ps_thdh; + u32 ps_near_level; + bool als_enabled; + bool ps_enabled; + bool ps_int_enabled; +}; + +static const struct iio_event_spec stk3310_events[] = { + /* Proximity event */ + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + /* Out-of-proximity event */ + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static ssize_t stk3310_read_near_level(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + char *buf) +{ + struct stk3310_data *data = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", data->ps_near_level); +} + +static const struct iio_chan_spec_ext_info stk3310_ext_info[] = { + { + .name = "nearlevel", + .shared = IIO_SEPARATE, + .read = stk3310_read_near_level, + }, + { } +}; + +static const struct iio_chan_spec stk3310_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_PROXIMITY, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + .event_spec = stk3310_events, + .num_event_specs = ARRAY_SIZE(stk3310_events), + .ext_info = stk3310_ext_info, + } +}; + +static IIO_CONST_ATTR(in_illuminance_scale_available, STK3310_SCALE_AVAILABLE); + +static IIO_CONST_ATTR(in_proximity_scale_available, STK3310_SCALE_AVAILABLE); + +static IIO_CONST_ATTR(in_illuminance_integration_time_available, + STK3310_IT_AVAILABLE); + +static IIO_CONST_ATTR(in_proximity_integration_time_available, + STK3310_IT_AVAILABLE); + +static struct attribute *stk3310_attributes[] = { + &iio_const_attr_in_illuminance_scale_available.dev_attr.attr, + &iio_const_attr_in_proximity_scale_available.dev_attr.attr, + &iio_const_attr_in_illuminance_integration_time_available.dev_attr.attr, + &iio_const_attr_in_proximity_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group stk3310_attribute_group = { + .attrs = stk3310_attributes +}; + +static int stk3310_check_chip_id(const u8 chip_id) +{ + for (int i = 0; i < ARRAY_SIZE(stk3310_chip_ids); i++) { + if (chip_id == stk3310_chip_ids[i]) + return 0; + } + + return -ENODEV; +} + +static int stk3310_get_index(const int table[][2], int table_size, + int val, int val2) +{ + int i; + + for (i = 0; i < table_size; i++) { + if (val == table[i][0] && val2 == table[i][1]) + return i; + } + + return -EINVAL; +} + +static int stk3310_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + u8 reg; + __be16 buf; + int ret; + struct stk3310_data *data = iio_priv(indio_dev); + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + /* Only proximity interrupts are implemented at the moment. */ + if (dir == IIO_EV_DIR_RISING) + reg = STK3310_REG_THDH_PS; + else if (dir == IIO_EV_DIR_FALLING) + reg = STK3310_REG_THDL_PS; + else + return -EINVAL; + + mutex_lock(&data->lock); + ret = regmap_bulk_read(data->regmap, reg, &buf, sizeof(buf)); + mutex_unlock(&data->lock); + if (ret < 0) { + dev_err(&data->client->dev, "register read failed\n"); + return ret; + } + *val = be16_to_cpu(buf); + + return IIO_VAL_INT; +} + +static int stk3310_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + u8 reg; + __be16 buf; + int ret; + unsigned int index; + struct stk3310_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + ret = regmap_field_read(data->reg_ps_gain, &index); + if (ret < 0) + return ret; + + if (val < 0 || val > stk3310_ps_max[index]) + return -EINVAL; + + if (dir == IIO_EV_DIR_RISING) + reg = STK3310_REG_THDH_PS; + else if (dir == IIO_EV_DIR_FALLING) + reg = STK3310_REG_THDL_PS; + else + return -EINVAL; + + buf = cpu_to_be16(val); + ret = regmap_bulk_write(data->regmap, reg, &buf, sizeof(buf)); + if (ret < 0) { + dev_err(&client->dev, "failed to set PS threshold!\n"); + return ret; + } + + if (reg == STK3310_REG_THDH_PS) + data->ps_thdh = val; + else + data->ps_thdl = val; + + return 0; +} + +static int stk3310_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + unsigned int event_val; + int ret; + struct stk3310_data *data = iio_priv(indio_dev); + + ret = regmap_field_read(data->reg_int_ps, &event_val); + if (ret < 0) + return ret; + + return event_val; +} + +static int stk3310_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + int ret; + struct stk3310_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + /* Set INT_PS value */ + mutex_lock(&data->lock); + ret = regmap_field_write(data->reg_int_ps, state); + if (ret < 0) { + dev_err(&client->dev, "failed to set interrupt mode\n"); + mutex_unlock(&data->lock); + return ret; + } + + data->ps_int_enabled = state; + + mutex_unlock(&data->lock); + + return 0; +} + +static int stk3310_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + u8 reg; + __be16 buf; + int ret; + unsigned int index; + struct stk3310_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_LIGHT) + reg = STK3310_REG_ALS_DATA_MSB; + else + reg = STK3310_REG_PS_DATA_MSB; + + mutex_lock(&data->lock); + ret = regmap_bulk_read(data->regmap, reg, &buf, sizeof(buf)); + if (ret < 0) { + dev_err(&client->dev, "register read failed\n"); + mutex_unlock(&data->lock); + return ret; + } + *val = be16_to_cpu(buf); + mutex_unlock(&data->lock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + if (chan->type == IIO_LIGHT) + ret = regmap_field_read(data->reg_als_it, &index); + else + ret = regmap_field_read(data->reg_ps_it, &index); + if (ret < 0) + return ret; + + *val = stk3310_it_table[index][0]; + *val2 = stk3310_it_table[index][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_LIGHT) + ret = regmap_field_read(data->reg_als_gain, &index); + else + ret = regmap_field_read(data->reg_ps_gain, &index); + if (ret < 0) + return ret; + + *val = stk3310_scale_table[index][0]; + *val2 = stk3310_scale_table[index][1]; + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; +} + +static int stk3310_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + int index; + struct stk3310_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + index = stk3310_get_index(stk3310_it_table, + ARRAY_SIZE(stk3310_it_table), + val, val2); + if (index < 0) + return -EINVAL; + mutex_lock(&data->lock); + if (chan->type == IIO_LIGHT) + ret = regmap_field_write(data->reg_als_it, index); + else + ret = regmap_field_write(data->reg_ps_it, index); + if (ret < 0) + dev_err(&data->client->dev, + "sensor configuration failed\n"); + mutex_unlock(&data->lock); + return ret; + + case IIO_CHAN_INFO_SCALE: + index = stk3310_get_index(stk3310_scale_table, + ARRAY_SIZE(stk3310_scale_table), + val, val2); + if (index < 0) + return -EINVAL; + mutex_lock(&data->lock); + if (chan->type == IIO_LIGHT) + ret = regmap_field_write(data->reg_als_gain, index); + else + ret = regmap_field_write(data->reg_ps_gain, index); + if (ret < 0) + dev_err(&data->client->dev, + "sensor configuration failed\n"); + mutex_unlock(&data->lock); + return ret; + } + + return -EINVAL; +} + +static const struct iio_info stk3310_info = { + .read_raw = stk3310_read_raw, + .write_raw = stk3310_write_raw, + .attrs = &stk3310_attribute_group, + .read_event_value = stk3310_read_event, + .write_event_value = stk3310_write_event, + .read_event_config = stk3310_read_event_config, + .write_event_config = stk3310_write_event_config, +}; + +static int stk3310_set_state(struct stk3310_data *data, u8 state) +{ + int ret; + struct i2c_client *client = data->client; + + /* 3-bit state; 0b100 is not supported. */ + if (state > 7 || state == 4) + return -EINVAL; + + mutex_lock(&data->lock); + ret = regmap_field_write(data->reg_state, state); + if (ret < 0) { + dev_err(&client->dev, "failed to change sensor state\n"); + } else if (state != STK3310_STATE_STANDBY) { + /* Don't reset the 'enabled' flags if we're going in standby */ + data->ps_enabled = !!(state & STK3310_STATE_EN_PS); + data->als_enabled = !!(state & STK3310_STATE_EN_ALS); + } + mutex_unlock(&data->lock); + + return ret; +} + +static int stk3310_init(struct iio_dev *indio_dev) +{ + int ret; + int chipid; + u8 state; + struct stk3310_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + ret = regmap_read(data->regmap, STK3310_REG_ID, &chipid); + if (ret < 0) + return ret; + + ret = stk3310_check_chip_id(chipid); + if (ret < 0) + dev_info(&client->dev, "new unknown chip id: 0x%x\n", chipid); + + state = STK3310_STATE_EN_ALS | STK3310_STATE_EN_PS; + ret = stk3310_set_state(data, state); + if (ret < 0) { + dev_err(&client->dev, "failed to enable sensor"); + return ret; + } + + /* Enable PS interrupts */ + ret = regmap_field_write(data->reg_int_ps, STK3310_PSINT_EN); + if (ret < 0) { + dev_err(&client->dev, "failed to enable interrupts!\n"); + return ret; + } + + data->ps_int_enabled = true; + data->ps_thdh = STK3310_PS_MAX_VAL; + + return 0; +} + +static bool stk3310_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STK3310_REG_ALS_DATA_MSB: + case STK3310_REG_ALS_DATA_LSB: + case STK3310_REG_PS_DATA_LSB: + case STK3310_REG_PS_DATA_MSB: + case STK3310_REG_FLAG: + return true; + default: + return false; + } +} + +static const struct regmap_config stk3310_regmap_config = { + .name = "stk3310_regmap", + .reg_bits = 8, + .val_bits = 8, + .max_register = STK3310_MAX_REG, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = stk3310_is_volatile_reg, +}; + +static int stk3310_regmap_init(struct stk3310_data *data) +{ + struct regmap *regmap; + struct i2c_client *client; + + client = data->client; + regmap = devm_regmap_init_i2c(client, &stk3310_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "regmap initialization failed.\n"); + return PTR_ERR(regmap); + } + data->regmap = regmap; + + STK3310_REGFIELD(state); + STK3310_REGFIELD(als_gain); + STK3310_REGFIELD(ps_gain); + STK3310_REGFIELD(als_it); + STK3310_REGFIELD(ps_it); + STK3310_REGFIELD(int_ps); + STK3310_REGFIELD(flag_psint); + STK3310_REGFIELD(flag_nf); + + return 0; +} + +static irqreturn_t stk3310_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct stk3310_data *data = iio_priv(indio_dev); + + data->timestamp = iio_get_time_ns(indio_dev); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t stk3310_irq_event_handler(int irq, void *private) +{ + int ret; + unsigned int dir; + u64 event; + + struct iio_dev *indio_dev = private; + struct stk3310_data *data = iio_priv(indio_dev); + + /* Read FLAG_NF to figure out what threshold has been met. */ + mutex_lock(&data->lock); + ret = regmap_field_read(data->reg_flag_nf, &dir); + if (ret < 0) { + dev_err(&data->client->dev, "register read failed: %d\n", ret); + goto out; + } + event = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1, + IIO_EV_TYPE_THRESH, + (dir ? IIO_EV_DIR_FALLING : + IIO_EV_DIR_RISING)); + iio_push_event(indio_dev, event, data->timestamp); + + /* Reset the interrupt flag */ + ret = regmap_field_write(data->reg_flag_psint, 0); + if (ret < 0) + dev_err(&data->client->dev, "failed to reset interrupts\n"); +out: + mutex_unlock(&data->lock); + + return IRQ_HANDLED; +} + +static int stk3310_probe(struct i2c_client *client) +{ + int ret; + struct iio_dev *indio_dev; + struct stk3310_data *data; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + i2c_set_clientdata(client, indio_dev); + + device_property_read_u32(&client->dev, "proximity-near-level", + &data->ps_near_level); + + mutex_init(&data->lock); + + ret = stk3310_regmap_init(data); + if (ret < 0) + return ret; + + indio_dev->info = &stk3310_info; + indio_dev->name = STK3310_DRIVER_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = stk3310_channels; + indio_dev->num_channels = ARRAY_SIZE(stk3310_channels); + + ret = stk3310_init(indio_dev); + if (ret < 0) + return ret; + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + stk3310_irq_handler, + stk3310_irq_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "stk3310_event", indio_dev); + if (ret) + goto err_standby; + } + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "device_register failed\n"); + goto err_standby; + } + + return 0; + +err_standby: + stk3310_set_state(data, STK3310_STATE_STANDBY); + return ret; +} + +static void stk3310_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + stk3310_set_state(iio_priv(indio_dev), STK3310_STATE_STANDBY); +} + +static int stk3310_suspend(struct device *dev) +{ + struct stk3310_data *data; + int ret; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + if (data->ps_int_enabled) { + ret = regmap_field_write(data->reg_int_ps, 0x0); + if (ret < 0) { + dev_err(dev, "failed to disable ps int at suspend.\n"); + return ret; + } + } + + return stk3310_set_state(data, STK3310_STATE_STANDBY); +} + +static int stk3310_resume(struct device *dev) +{ + u8 state = 0; + struct stk3310_data *data; + __be16 buf; + int ret; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + if (data->ps_enabled) + state |= STK3310_STATE_EN_PS; + if (data->als_enabled) + state |= STK3310_STATE_EN_ALS; + + ret = stk3310_set_state(data, state); + if (ret < 0) + return ret; + + if (data->ps_thdl != 0x0) { + buf = cpu_to_be16(data->ps_thdl); + ret = regmap_bulk_write(data->regmap, STK3310_REG_THDL_PS, &buf, sizeof(buf)); + if (ret < 0) { + dev_err(dev, "failed to set reg THDL_PS at resume.\n"); + return ret; + } + } + + if (data->ps_thdh != STK3310_PS_MAX_VAL) { + buf = cpu_to_be16(data->ps_thdh); + ret = regmap_bulk_write(data->regmap, STK3310_REG_THDH_PS, &buf, sizeof(buf)); + if (ret < 0) { + dev_err(dev, "failed to set reg THDH_PS at resume.\n"); + return ret; + } + } + + if (data->ps_int_enabled) { + ret = regmap_field_write(data->reg_int_ps, STK3310_PSINT_EN); + if (ret < 0) { + dev_err(dev, "failed to enable ps int at resume.\n"); + return ret; + } + } + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(stk3310_pm_ops, stk3310_suspend, + stk3310_resume); + +static const struct i2c_device_id stk3310_i2c_id[] = { + { .name = "STK3013" }, + { .name = "STK3310" }, + { .name = "STK3311" }, + { .name = "STK3335" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, stk3310_i2c_id); + +static const struct acpi_device_id stk3310_acpi_id[] = { + {"STK3013", 0}, + {"STK3310", 0}, + {"STK3311", 0}, + { } +}; + +MODULE_DEVICE_TABLE(acpi, stk3310_acpi_id); + +static const struct of_device_id stk3310_of_match[] = { + { .compatible = "sensortek,stk3013", }, + { .compatible = "sensortek,stk3310", }, + { .compatible = "sensortek,stk3311", }, + { .compatible = "sensortek,stk3335", }, + { } +}; +MODULE_DEVICE_TABLE(of, stk3310_of_match); + +static struct i2c_driver stk3310_driver = { + .driver = { + .name = "stk3310", + .of_match_table = stk3310_of_match, + .pm = pm_sleep_ptr(&stk3310_pm_ops), + .acpi_match_table = stk3310_acpi_id, + }, + .probe = stk3310_probe, + .remove = stk3310_remove, + .id_table = stk3310_i2c_id, +}; + +module_i2c_driver(stk3310_driver); + +MODULE_AUTHOR("Tiberiu Breana <tiberiu.a.breana@intel.com>"); +MODULE_DESCRIPTION("STK3310 Ambient Light and Proximity Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/tcs3414.c b/drivers/iio/light/tcs3414.c new file mode 100644 index 000000000..458178fb6 --- /dev/null +++ b/drivers/iio/light/tcs3414.c @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tcs3414.c - Support for TAOS TCS3414 digital color sensor + * + * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net> + * + * Digital color sensor with 16-bit channels for red, green, blue, clear); + * 7-bit I2C slave address 0x39 (TCS3414) or 0x29, 0x49, 0x59 (TCS3413, + * TCS3415, TCS3416, resp.) + * + * TODO: sync, interrupt support, thresholds, prescaler + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/pm.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> + +#define TCS3414_DRV_NAME "tcs3414" + +#define TCS3414_COMMAND BIT(7) +#define TCS3414_COMMAND_WORD (TCS3414_COMMAND | BIT(5)) + +#define TCS3414_CONTROL (TCS3414_COMMAND | 0x00) +#define TCS3414_TIMING (TCS3414_COMMAND | 0x01) +#define TCS3414_ID (TCS3414_COMMAND | 0x04) +#define TCS3414_GAIN (TCS3414_COMMAND | 0x07) +#define TCS3414_DATA_GREEN (TCS3414_COMMAND_WORD | 0x10) +#define TCS3414_DATA_RED (TCS3414_COMMAND_WORD | 0x12) +#define TCS3414_DATA_BLUE (TCS3414_COMMAND_WORD | 0x14) +#define TCS3414_DATA_CLEAR (TCS3414_COMMAND_WORD | 0x16) + +#define TCS3414_CONTROL_ADC_VALID BIT(4) +#define TCS3414_CONTROL_ADC_EN BIT(1) +#define TCS3414_CONTROL_POWER BIT(0) + +#define TCS3414_INTEG_MASK GENMASK(1, 0) +#define TCS3414_INTEG_12MS 0x0 +#define TCS3414_INTEG_100MS 0x1 +#define TCS3414_INTEG_400MS 0x2 + +#define TCS3414_GAIN_MASK GENMASK(5, 4) +#define TCS3414_GAIN_SHIFT 4 + +struct tcs3414_data { + struct i2c_client *client; + u8 control; + u8 gain; + u8 timing; +}; + +#define TCS3414_CHANNEL(_color, _si, _addr) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .channel2 = IIO_MOD_LIGHT_##_color, \ + .address = _addr, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +/* scale factors: 1/gain */ +static const int tcs3414_scales[][2] = { + {1, 0}, {0, 250000}, {0, 62500}, {0, 15625} +}; + +/* integration time in ms */ +static const int tcs3414_times[] = { 12, 100, 400 }; + +static const struct iio_chan_spec tcs3414_channels[] = { + TCS3414_CHANNEL(GREEN, 0, TCS3414_DATA_GREEN), + TCS3414_CHANNEL(RED, 1, TCS3414_DATA_RED), + TCS3414_CHANNEL(BLUE, 2, TCS3414_DATA_BLUE), + TCS3414_CHANNEL(CLEAR, 3, TCS3414_DATA_CLEAR), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static int tcs3414_req_data(struct tcs3414_data *data) +{ + int tries = 25; + int ret; + + ret = i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control | TCS3414_CONTROL_ADC_EN); + if (ret < 0) + return ret; + + while (tries--) { + ret = i2c_smbus_read_byte_data(data->client, TCS3414_CONTROL); + if (ret < 0) + return ret; + if (ret & TCS3414_CONTROL_ADC_VALID) + break; + msleep(20); + } + + ret = i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control); + if (ret < 0) + return ret; + + if (tries < 0) { + dev_err(&data->client->dev, "data not ready\n"); + return -EIO; + } + + return 0; +} + +static int tcs3414_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tcs3414_data *data = iio_priv(indio_dev); + int i, ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = tcs3414_req_data(data); + if (ret < 0) { + iio_device_release_direct(indio_dev); + return ret; + } + ret = i2c_smbus_read_word_data(data->client, chan->address); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + i = (data->gain & TCS3414_GAIN_MASK) >> TCS3414_GAIN_SHIFT; + *val = tcs3414_scales[i][0]; + *val2 = tcs3414_scales[i][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = tcs3414_times[data->timing & TCS3414_INTEG_MASK] * 1000; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int tcs3414_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct tcs3414_data *data = iio_priv(indio_dev); + int i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ARRAY_SIZE(tcs3414_scales); i++) { + if (val == tcs3414_scales[i][0] && + val2 == tcs3414_scales[i][1]) { + data->gain &= ~TCS3414_GAIN_MASK; + data->gain |= i << TCS3414_GAIN_SHIFT; + return i2c_smbus_write_byte_data( + data->client, TCS3414_GAIN, + data->gain); + } + } + return -EINVAL; + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(tcs3414_times); i++) { + if (val2 == tcs3414_times[i] * 1000) { + data->timing &= ~TCS3414_INTEG_MASK; + data->timing |= i; + return i2c_smbus_write_byte_data( + data->client, TCS3414_TIMING, + data->timing); + } + } + return -EINVAL; + default: + return -EINVAL; + } +} + +static irqreturn_t tcs3414_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct tcs3414_data *data = iio_priv(indio_dev); + int i, j = 0; + /* Ensure timestamp is naturally aligned */ + struct { + u16 chans[4]; + aligned_s64 timestamp; + } scan = { }; + + + iio_for_each_active_channel(indio_dev, i) { + int ret = i2c_smbus_read_word_data(data->client, + TCS3414_DATA_GREEN + 2*i); + if (ret < 0) + goto done; + + scan.chans[j++] = ret; + } + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static IIO_CONST_ATTR(scale_available, "1 0.25 0.0625 0.015625"); +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.012 0.1 0.4"); + +static struct attribute *tcs3414_attributes[] = { + &iio_const_attr_scale_available.dev_attr.attr, + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group tcs3414_attribute_group = { + .attrs = tcs3414_attributes, +}; + +static const struct iio_info tcs3414_info = { + .read_raw = tcs3414_read_raw, + .write_raw = tcs3414_write_raw, + .attrs = &tcs3414_attribute_group, +}; + +static int tcs3414_buffer_postenable(struct iio_dev *indio_dev) +{ + struct tcs3414_data *data = iio_priv(indio_dev); + + data->control |= TCS3414_CONTROL_ADC_EN; + return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control); +} + +static int tcs3414_buffer_predisable(struct iio_dev *indio_dev) +{ + struct tcs3414_data *data = iio_priv(indio_dev); + + data->control &= ~TCS3414_CONTROL_ADC_EN; + return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control); +} + +static const struct iio_buffer_setup_ops tcs3414_buffer_setup_ops = { + .postenable = tcs3414_buffer_postenable, + .predisable = tcs3414_buffer_predisable, +}; + +static int tcs3414_powerdown(struct tcs3414_data *data) +{ + return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control & ~(TCS3414_CONTROL_POWER | + TCS3414_CONTROL_ADC_EN)); +} + +static void tcs3414_powerdown_cleanup(void *data) +{ + tcs3414_powerdown(data); +} + +static int tcs3414_probe(struct i2c_client *client) +{ + struct tcs3414_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (indio_dev == NULL) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + indio_dev->info = &tcs3414_info; + indio_dev->name = TCS3414_DRV_NAME; + indio_dev->channels = tcs3414_channels; + indio_dev->num_channels = ARRAY_SIZE(tcs3414_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = i2c_smbus_read_byte_data(data->client, TCS3414_ID); + if (ret < 0) + return ret; + + switch (ret & 0xf0) { + case 0x00: + dev_info(&client->dev, "TCS3404 found\n"); + break; + case 0x10: + dev_info(&client->dev, "TCS3413/14/15/16 found\n"); + break; + default: + return -ENODEV; + } + + data->control = TCS3414_CONTROL_POWER; + ret = i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(&client->dev, tcs3414_powerdown_cleanup, + data); + if (ret < 0) + return ret; + + data->timing = TCS3414_INTEG_12MS; /* free running */ + ret = i2c_smbus_write_byte_data(data->client, TCS3414_TIMING, + data->timing); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, TCS3414_GAIN); + if (ret < 0) + return ret; + data->gain = ret; + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL, + tcs3414_trigger_handler, &tcs3414_buffer_setup_ops); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int tcs3414_suspend(struct device *dev) +{ + struct tcs3414_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return tcs3414_powerdown(data); +} + +static int tcs3414_resume(struct device *dev) +{ + struct tcs3414_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(tcs3414_pm_ops, tcs3414_suspend, + tcs3414_resume); + +static const struct i2c_device_id tcs3414_id[] = { + { .name = "tcs3414" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tcs3414_id); + +static struct i2c_driver tcs3414_driver = { + .driver = { + .name = TCS3414_DRV_NAME, + .pm = pm_sleep_ptr(&tcs3414_pm_ops), + }, + .probe = tcs3414_probe, + .id_table = tcs3414_id, +}; +module_i2c_driver(tcs3414_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("TCS3414 digital color sensors driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c new file mode 100644 index 000000000..b392943e5 --- /dev/null +++ b/drivers/iio/light/tcs3472.c @@ -0,0 +1,864 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tcs3472.c - Support for TAOS TCS3472 color light-to-digital converter + * + * Copyright (c) 2013 Peter Meerwald <pmeerw@pmeerw.net> + * + * Color light sensor with 16-bit channels for red, green, blue, clear); + * 7-bit I2C slave address 0x39 (TCS34721, TCS34723) or 0x29 (TCS34725, + * TCS34727) + * + * Datasheet: http://ams.com/eng/content/download/319364/1117183/file/TCS3472_Datasheet_EN_v2.pdf + */ + +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/units.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define TCS3472_DRV_NAME "tcs3472" + +#define TCS3472_COMMAND BIT(7) +#define TCS3472_AUTO_INCR BIT(5) +#define TCS3472_SPECIAL_FUNC (BIT(5) | BIT(6)) + +#define TCS3472_INTR_CLEAR (TCS3472_COMMAND | TCS3472_SPECIAL_FUNC | 0x06) + +#define TCS3472_ENABLE (TCS3472_COMMAND | 0x00) +#define TCS3472_ATIME (TCS3472_COMMAND | 0x01) +#define TCS3472_WTIME (TCS3472_COMMAND | 0x03) +#define TCS3472_AILT (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x04) +#define TCS3472_AIHT (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x06) +#define TCS3472_PERS (TCS3472_COMMAND | 0x0c) +#define TCS3472_CONFIG (TCS3472_COMMAND | 0x0d) +#define TCS3472_CONTROL (TCS3472_COMMAND | 0x0f) +#define TCS3472_ID (TCS3472_COMMAND | 0x12) +#define TCS3472_STATUS (TCS3472_COMMAND | 0x13) +#define TCS3472_CDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x14) +#define TCS3472_RDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x16) +#define TCS3472_GDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x18) +#define TCS3472_BDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x1a) + +#define TCS3472_STATUS_AINT BIT(4) +#define TCS3472_STATUS_AVALID BIT(0) +#define TCS3472_ENABLE_AIEN BIT(4) +#define TCS3472_ENABLE_WEN BIT(3) +#define TCS3472_ENABLE_AEN BIT(1) +#define TCS3472_ENABLE_PON BIT(0) +#define TCS3472_ENABLE_RUN \ + (TCS3472_ENABLE_AEN | TCS3472_ENABLE_PON | TCS3472_ENABLE_WEN) +#define TCS3472_CONTROL_AGAIN_MASK (BIT(0) | BIT(1)) +#define TCS3472_CONFIG_WLONG BIT(1) + +#define TCS3472_ATIME_TO_US(atime) (((256) - (atime)) * 2400) + +struct tcs3472_data { + struct i2c_client *client; + struct mutex lock; + int target_freq_hz; + int target_freq_uhz; + u16 low_thresh; + u16 high_thresh; + u8 enable; + u8 enable_pre_suspend; + u8 control; + u8 atime; + u8 apers; + u8 wtime; + bool wlong; +}; + +static const struct iio_event_spec tcs3472_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD), + }, +}; + +#define TCS3472_CHANNEL(_color, _si, _addr) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .channel2 = IIO_MOD_LIGHT_##_color, \ + .address = _addr, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + .event_spec = _si ? NULL : tcs3472_events, \ + .num_event_specs = _si ? 0 : ARRAY_SIZE(tcs3472_events), \ +} + +static const int tcs3472_agains[] = { 1, 4, 16, 60 }; + +static const struct iio_chan_spec tcs3472_channels[] = { + TCS3472_CHANNEL(CLEAR, 0, TCS3472_CDATA), + TCS3472_CHANNEL(RED, 1, TCS3472_RDATA), + TCS3472_CHANNEL(GREEN, 2, TCS3472_GDATA), + TCS3472_CHANNEL(BLUE, 3, TCS3472_BDATA), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +/* + * The chip's cycle time is the sum of three components: + * - ATIME: the programmable RGBC integration time. + * - The fixed RGBC initialization time (2400 us). + * - WTIME: the wait time, used only if WEN is set. If WLONG is active, + * the wait step is multiplied by 12 (2400 us -> 28800 us). + */ +static unsigned int tcs3472_cycle_time_us(struct tcs3472_data *data) +{ + unsigned int atime_us = TCS3472_ATIME_TO_US(data->atime); + unsigned int init_us = 2400; + unsigned int wtime_us; + + if (!(data->enable & TCS3472_ENABLE_WEN)) + wtime_us = 0; + else if (data->wlong) + wtime_us = (256 - data->wtime) * 28800; + else + wtime_us = (256 - data->wtime) * 2400; + + return atime_us + init_us + wtime_us; +} + +/* + * Convert a cycle time in microseconds to a frequency in Hz and microhertz. + * + * Given cycle_us = T (the cycle period in microseconds), the corresponding + * frequency is: + * f = 1e6 / T [Hz] + * + * The result is split into the IIO_VAL_INT_PLUS_MICRO format: + * val = floor(1e6 / T) [Hz] + * val2 = (1e6 mod T) * 1e6 / T [microhertz] + */ +static void tcs3472_cycle_to_freq(unsigned int cycle_us, int *val, int *val2) +{ + *val = USEC_PER_SEC / cycle_us; + *val2 = div_u64((u64)(USEC_PER_SEC % cycle_us) * USEC_PER_SEC, + cycle_us); +} + +static int tcs3472_req_data(struct tcs3472_data *data) +{ + /* + * The worst-case cycle time is reached with ATIME=0x00, WTIME=0x00 + * and WLONG=1. So: + * 614 ms (Max Integration Time) + * + 2.4 ms (RGBC Init) + * + 7.37 s (Max Wait Time) + * = ~ 8 s (Total Max cycle time). + * Use that as a polling upper bound; in normal operation the loop + * exits as soon as AVALID is set. So the total number of tries in 8 + * seconds considering a polling period of 20 ms is 400. + * Considering a 20% margin due to oscillator tolerance, the total + * duration becomes approximately 9.8 seconds, which corresponds to + * about 480 steps. Therefore, setting it to 500 appears to be a + * reasonable and safe trade-off. + */ + int tries = 500; + int ret; + + while (tries--) { + ret = i2c_smbus_read_byte_data(data->client, TCS3472_STATUS); + if (ret < 0) + return ret; + if (ret & TCS3472_STATUS_AVALID) + break; + msleep(20); + } + + if (tries < 0) { + dev_err(&data->client->dev, "data not ready\n"); + return -EIO; + } + + return 0; +} + +static int tcs3472_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tcs3472_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = tcs3472_req_data(data); + if (ret < 0) { + iio_device_release_direct(indio_dev); + return ret; + } + ret = i2c_smbus_read_word_data(data->client, chan->address); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + *val = tcs3472_agains[data->control & + TCS3472_CONTROL_AGAIN_MASK]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = TCS3472_ATIME_TO_US(data->atime); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: { + unsigned int cycle_us; + + guard(mutex)(&data->lock); + cycle_us = tcs3472_cycle_time_us(data); + tcs3472_cycle_to_freq(cycle_us, val, val2); + return IIO_VAL_INT_PLUS_MICRO; + } + default: + return -EINVAL; + } +} + +/* + * __tcs3472_set_sampling_freq() - implementation of sampling frequency + * configuration. The caller must hold data->lock. + */ +static int __tcs3472_set_sampling_freq(struct tcs3472_data *data, + int val, int val2) +{ + unsigned int atime_us; + unsigned int init_us = 2400; + u64 cycle_us; + s64 wait_us; + int wtime; + bool wlong = false; + u8 config; + int ret; + + if (val < 0 || val2 < 0 || (val == 0 && val2 == 0)) + return -EINVAL; + + atime_us = TCS3472_ATIME_TO_US(data->atime); + + /* + * cycle_us = 1 / freq, expressed in microseconds. + * Numerator: 1 [s] = PSEC_PER_SEC [ps] + * Denominator: freq [Hz] * MICROHZ_PER_HZ + val2 [uHz] = freq in [uHz] + * Result: ps / uHz = us + */ + cycle_us = div64_u64(PSEC_PER_SEC, (u64)val * MICROHZ_PER_HZ + val2); + + /* + * wait_us can be negative when the requested frequency is too high + * to be reached, or very large when the requested frequency is + * close to zero. Use s64 to cover the full range: + * + * cycle_us = PSEC_PER_SEC / (val * MICROHZ_PER_HZ + val2) + * + * The divisor of the formula above reaches its maximum when + * val = val2 = INT_MAX: + * INT_MAX * MICROHZ_PER_HZ + INT_MAX = ~2.15e18 + * so cycle_us_min = floor(1e12 / 2.15e18) = 0. + * + * The divisor reaches its minimum (1) when val = 0 and val2 = 1, + * so cycle_us_max = 1e12 / 1 = 1e12. + * + * Therefore: + * wait_us_min = 0 - 2400 - 612000 = -616800 + * wait_us_max = 1e12 - 2400 - 2400 = 999999995200 + * + * Both fit comfortably in s64. + */ + wait_us = (s64)cycle_us - init_us - atime_us; + if (wait_us < 2400) { + if (data->enable & TCS3472_ENABLE_WEN) { + u8 enable = data->enable & ~TCS3472_ENABLE_WEN; + + ret = i2c_smbus_write_byte_data(data->client, + TCS3472_ENABLE, enable); + if (ret) + return ret; + + data->enable = enable; + } + + data->target_freq_hz = val; + data->target_freq_uhz = val2; + return 0; + } + + /* + * Wait state is needed: make sure WEN is active before programming + * WTIME (and possibly WLONG). + */ + if (!(data->enable & TCS3472_ENABLE_WEN)) { + u8 enable = data->enable | TCS3472_ENABLE_WEN; + + ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, + enable); + if (ret) + return ret; + + data->enable = enable; + } + + wtime = 256 - DIV_ROUND_CLOSEST_ULL(wait_us, 2400); + if (wtime < 0) { + /* + * If wait_us is too high (so the requested frequency is too + * low), the resulting wait exceeds what WTIME can represent + * (max 614 ms without WLONG). Enable WLONG, whose step is 12x + * longer (28.8 ms instead of 2.4 ms), and recompute. + */ + wlong = true; + wtime = 256 - DIV_ROUND_CLOSEST_ULL(wait_us, 28800); + } + + if (wlong != data->wlong) { + ret = i2c_smbus_read_byte_data(data->client, TCS3472_CONFIG); + if (ret < 0) + return ret; + + config = ret; + if (wlong) + config |= TCS3472_CONFIG_WLONG; + else + config &= ~TCS3472_CONFIG_WLONG; + + ret = i2c_smbus_write_byte_data(data->client, TCS3472_CONFIG, + config); + if (ret) + return ret; + + data->wlong = wlong; + } + + /* + * If the requested wait is so long that even WLONG cannot + * cover it, wtime may still be negative. Saturate to 0, + * which is the largest possible wait (256 * 28.8 ms = 7.37 s). + */ + wtime = clamp(wtime, 0, 255); + ret = i2c_smbus_write_byte_data(data->client, TCS3472_WTIME, wtime); + if (ret) + return ret; + + data->wtime = wtime; + data->target_freq_hz = val; + data->target_freq_uhz = val2; + + return 0; +} + +static int tcs3472_set_sampling_freq(struct tcs3472_data *data, + int val, int val2) +{ + guard(mutex)(&data->lock); + return __tcs3472_set_sampling_freq(data, val, val2); +} + +static int tcs3472_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct tcs3472_data *data = iio_priv(indio_dev); + int ret; + int i; + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + if (val2 != 0) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(tcs3472_agains); i++) { + if (val == tcs3472_agains[i]) { + data->control &= ~TCS3472_CONTROL_AGAIN_MASK; + data->control |= i; + return i2c_smbus_write_byte_data( + data->client, TCS3472_CONTROL, + data->control); + } + } + return -EINVAL; + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + for (i = 0; i < 256; i++) { + if (val2 != (256 - i) * 2400) + continue; + + guard(mutex)(&data->lock); + + ret = i2c_smbus_write_byte_data(data->client, + TCS3472_ATIME, i); + if (ret) + return ret; + + data->atime = i; + + /* + * ATIME just changed, so the cycle time changed too. + * Re-run the sampling frequency logic to recompute + * WTIME and preserve the user's last requested + * frequency. Lock is already held. + */ + return __tcs3472_set_sampling_freq(data, + data->target_freq_hz, + data->target_freq_uhz); + } + return -EINVAL; + case IIO_CHAN_INFO_SAMP_FREQ: + return tcs3472_set_sampling_freq(data, val, val2); + default: + return -EINVAL; + } +} + +/* + * Translation from APERS field value to the number of consecutive out-of-range + * clear channel values before an interrupt is generated + */ +static const int tcs3472_intr_pers[] = { + 0, 1, 2, 3, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60 +}; + +static int tcs3472_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int *val, + int *val2) +{ + struct tcs3472_data *data = iio_priv(indio_dev); + unsigned int period; + + guard(mutex)(&data->lock); + + switch (info) { + case IIO_EV_INFO_VALUE: + *val = (dir == IIO_EV_DIR_RISING) ? + data->high_thresh : data->low_thresh; + return IIO_VAL_INT; + case IIO_EV_INFO_PERIOD: + period = tcs3472_cycle_time_us(data) * + tcs3472_intr_pers[data->apers]; + *val = period / USEC_PER_SEC; + *val2 = period % USEC_PER_SEC; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int tcs3472_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int val, + int val2) +{ + struct tcs3472_data *data = iio_priv(indio_dev); + int ret; + u8 command; + int period; + int i; + + guard(mutex)(&data->lock); + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + command = TCS3472_AIHT; + break; + case IIO_EV_DIR_FALLING: + command = TCS3472_AILT; + break; + default: + return -EINVAL; + } + ret = i2c_smbus_write_word_data(data->client, command, val); + if (ret) + return ret; + + if (dir == IIO_EV_DIR_RISING) + data->high_thresh = val; + else + data->low_thresh = val; + + return 0; + case IIO_EV_INFO_PERIOD:{ + unsigned int cycle_us; + + period = val * USEC_PER_SEC + val2; + cycle_us = tcs3472_cycle_time_us(data); + for (i = 1; i < ARRAY_SIZE(tcs3472_intr_pers) - 1; i++) { + if (period <= cycle_us * tcs3472_intr_pers[i]) + break; + } + ret = i2c_smbus_write_byte_data(data->client, TCS3472_PERS, i); + if (ret) + return ret; + + data->apers = i; + + return 0; + } + default: + return -EINVAL; + } +} + +static int tcs3472_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + struct tcs3472_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + + return (data->enable & TCS3472_ENABLE_AIEN) ? 1 : 0; +} + +static int tcs3472_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct tcs3472_data *data = iio_priv(indio_dev); + int ret = 0; + u8 enable_old; + + guard(mutex)(&data->lock); + + enable_old = data->enable; + + if (state) + data->enable |= TCS3472_ENABLE_AIEN; + else + data->enable &= ~TCS3472_ENABLE_AIEN; + + if (enable_old != data->enable) { + ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, + data->enable); + if (ret) { + data->enable = enable_old; + return ret; + } + } + + return 0; +} + +static irqreturn_t tcs3472_event_handler(int irq, void *priv) +{ + struct iio_dev *indio_dev = priv; + struct tcs3472_data *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_byte_data(data->client, TCS3472_STATUS); + if (ret >= 0 && (ret & TCS3472_STATUS_AINT)) { + iio_push_event(indio_dev, IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + + i2c_smbus_read_byte_data(data->client, TCS3472_INTR_CLEAR); + } + + return IRQ_HANDLED; +} + +static irqreturn_t tcs3472_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct tcs3472_data *data = iio_priv(indio_dev); + int i, j = 0; + /* Ensure timestamp is naturally aligned */ + struct { + u16 chans[4]; + aligned_s64 timestamp; + } scan = { }; + + int ret = tcs3472_req_data(data); + if (ret < 0) + goto done; + + iio_for_each_active_channel(indio_dev, i) { + ret = i2c_smbus_read_word_data(data->client, + TCS3472_CDATA + 2*i); + if (ret < 0) + goto done; + + scan.chans[j++] = ret; + } + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static ssize_t tcs3472_show_int_time_available(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + size_t len = 0; + int i; + + for (i = 1; i <= 256; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06d ", + 2400 * i); + + /* replace trailing space by newline */ + buf[len - 1] = '\n'; + + return len; +} + +static IIO_CONST_ATTR(calibscale_available, "1 4 16 60"); +static IIO_DEV_ATTR_INT_TIME_AVAIL(tcs3472_show_int_time_available); + +static struct attribute *tcs3472_attributes[] = { + &iio_const_attr_calibscale_available.dev_attr.attr, + &iio_dev_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group tcs3472_attribute_group = { + .attrs = tcs3472_attributes, +}; + +static const struct iio_info tcs3472_info = { + .read_raw = tcs3472_read_raw, + .write_raw = tcs3472_write_raw, + .read_event_value = tcs3472_read_event, + .write_event_value = tcs3472_write_event, + .read_event_config = tcs3472_read_event_config, + .write_event_config = tcs3472_write_event_config, + .attrs = &tcs3472_attribute_group, +}; + +static int tcs3472_powerdown(struct tcs3472_data *data) +{ + int ret; + + guard(mutex)(&data->lock); + + data->enable_pre_suspend = data->enable; + + ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, + data->enable & ~TCS3472_ENABLE_RUN); + if (ret) + return ret; + + return 0; +} + +static void tcs3472_powerdown_action(void *data) +{ + tcs3472_powerdown(data); +} + +static int tcs3472_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct tcs3472_data *data; + struct iio_dev *indio_dev; + unsigned int cycle_us; + int ret; + u8 enable; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + indio_dev->info = &tcs3472_info; + indio_dev->name = TCS3472_DRV_NAME; + indio_dev->channels = tcs3472_channels; + indio_dev->num_channels = ARRAY_SIZE(tcs3472_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = i2c_smbus_read_byte_data(data->client, TCS3472_ID); + if (ret < 0) + return ret; + + if (ret == 0x44) + dev_info(dev, "TCS34721/34725 found\n"); + else if (ret == 0x4d) + dev_info(dev, "TCS34723/34727 found\n"); + else + return -ENODEV; + + ret = i2c_smbus_read_byte_data(data->client, TCS3472_CONTROL); + if (ret < 0) + return ret; + data->control = ret; + + ret = i2c_smbus_read_byte_data(data->client, TCS3472_ATIME); + if (ret < 0) + return ret; + data->atime = ret; + + ret = i2c_smbus_read_byte_data(data->client, TCS3472_WTIME); + if (ret < 0) + return ret; + data->wtime = ret; + + ret = i2c_smbus_read_byte_data(data->client, TCS3472_CONFIG); + if (ret < 0) + return ret; + data->wlong = (ret & TCS3472_CONFIG_WLONG) ? 1 : 0; + + ret = i2c_smbus_read_word_data(data->client, TCS3472_AILT); + if (ret < 0) + return ret; + data->low_thresh = ret; + + ret = i2c_smbus_read_word_data(data->client, TCS3472_AIHT); + if (ret < 0) + return ret; + data->high_thresh = ret; + + data->apers = 1; + ret = i2c_smbus_write_byte_data(data->client, TCS3472_PERS, + data->apers); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, TCS3472_ENABLE); + if (ret < 0) + return ret; + + /* + * Enable the chip in its full running state, including WEN. The + * actual wait time is controlled by the WTIME and WLONG registers, + * which retain their power-on defaults until userspace writes to + * sampling_frequency. + */ + enable = (ret | TCS3472_ENABLE_RUN) & ~TCS3472_ENABLE_AIEN; + + ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, enable); + if (ret < 0) + return ret; + + data->enable = enable; + + /* + * Initialize target frequency from the chip's current state so that + * subsequent integration_time changes via IIO_CHAN_INFO_INT_TIME can + * preserve a meaningful sampling rate, even before userspace writes + * sampling_frequency for the first time. + */ + cycle_us = tcs3472_cycle_time_us(data); + tcs3472_cycle_to_freq(cycle_us, &data->target_freq_hz, + &data->target_freq_uhz); + + ret = devm_add_action_or_reset(dev, tcs3472_powerdown_action, data); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + tcs3472_trigger_handler, NULL); + if (ret < 0) + return ret; + + if (client->irq) { + ret = devm_request_threaded_irq(dev, client->irq, NULL, + tcs3472_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_SHARED | + IRQF_ONESHOT, + client->name, indio_dev); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static int tcs3472_suspend(struct device *dev) +{ + struct tcs3472_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return tcs3472_powerdown(data); +} + +static int tcs3472_resume(struct device *dev) +{ + struct tcs3472_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + int ret; + + guard(mutex)(&data->lock); + + /* + * Restore the full ENABLE register from the snapshot taken in + * tcs3472_powerdown(). This preserves the user's last + * sampling_frequency configuration (in particular the WEN bit) + * across suspend/resume. + */ + ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, + data->enable_pre_suspend); + if (ret) + return ret; + + data->enable = data->enable_pre_suspend; + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(tcs3472_pm_ops, tcs3472_suspend, + tcs3472_resume); + +static const struct i2c_device_id tcs3472_id[] = { + { .name = "tcs3472" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tcs3472_id); + +static struct i2c_driver tcs3472_driver = { + .driver = { + .name = TCS3472_DRV_NAME, + .pm = pm_sleep_ptr(&tcs3472_pm_ops), + }, + .probe = tcs3472_probe, + .id_table = tcs3472_id, +}; +module_i2c_driver(tcs3472_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("TCS3472 color light sensors driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/tsl2563.c b/drivers/iio/light/tsl2563.c new file mode 100644 index 000000000..7bef0c311 --- /dev/null +++ b/drivers/iio/light/tsl2563.c @@ -0,0 +1,872 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/iio/light/tsl2563.c + * + * Copyright (C) 2008 Nokia Corporation + * + * Written by Timo O. Karjalainen <timo.o.karjalainen@nokia.com> + * Contact: Amit Kucheria <amit.kucheria@verdurent.com> + * + * Converted to IIO driver + * Amit Kucheria <amit.kucheria@verdurent.com> + */ + +#include <linux/bits.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/math.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/property.h> +#include <linux/sched.h> +#include <linux/slab.h> + +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +/* Use this many bits for fraction part. */ +#define ADC_FRAC_BITS 14 + +/* Given number of 1/10000's in ADC_FRAC_BITS precision. */ +#define FRAC10K(f) (((f) * BIT(ADC_FRAC_BITS)) / (10000)) + +/* Bits used for fraction in calibration coefficients.*/ +#define CALIB_FRAC_BITS 10 +/* Decimal 10^(digits in sysfs presentation) */ +#define CALIB_BASE_SYSFS 1000 + +#define TSL2563_CMD BIT(7) +#define TSL2563_CLEARINT BIT(6) + +#define TSL2563_REG_CTRL 0x00 +#define TSL2563_REG_TIMING 0x01 +#define TSL2563_REG_LOW 0x02 /* data0 low threshold, 2 bytes */ +#define TSL2563_REG_HIGH 0x04 /* data0 high threshold, 2 bytes */ +#define TSL2563_REG_INT 0x06 +#define TSL2563_REG_ID 0x0a +#define TSL2563_REG_DATA0 0x0c /* broadband sensor value, 2 bytes */ +#define TSL2563_REG_DATA1 0x0e /* infrared sensor value, 2 bytes */ + +#define TSL2563_CMD_POWER_ON 0x03 +#define TSL2563_CMD_POWER_OFF 0x00 +#define TSL2563_CTRL_POWER_MASK GENMASK(1, 0) + +#define TSL2563_TIMING_13MS 0x00 +#define TSL2563_TIMING_100MS 0x01 +#define TSL2563_TIMING_400MS 0x02 +#define TSL2563_TIMING_MASK GENMASK(1, 0) +#define TSL2563_TIMING_GAIN16 0x10 +#define TSL2563_TIMING_GAIN1 0x00 + +#define TSL2563_INT_DISABLED 0x00 +#define TSL2563_INT_LEVEL 0x10 +#define TSL2563_INT_MASK GENMASK(5, 4) +#define TSL2563_INT_PERSIST(n) ((n) & GENMASK(3, 0)) + +struct tsl2563_gainlevel_coeff { + u8 gaintime; + u16 min; + u16 max; +}; + +static const struct tsl2563_gainlevel_coeff tsl2563_gainlevel_table[] = { + { + .gaintime = TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN16, + .min = 0, + .max = 65534, + }, { + .gaintime = TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN1, + .min = 2048, + .max = 65534, + }, { + .gaintime = TSL2563_TIMING_100MS | TSL2563_TIMING_GAIN1, + .min = 4095, + .max = 37177, + }, { + .gaintime = TSL2563_TIMING_13MS | TSL2563_TIMING_GAIN1, + .min = 3000, + .max = 65535, + }, +}; + +struct tsl2563_chip { + struct mutex lock; + struct i2c_client *client; + struct delayed_work poweroff_work; + + /* Remember state for suspend and resume functions */ + bool suspended; + + struct tsl2563_gainlevel_coeff const *gainlevel; + + u16 low_thres; + u16 high_thres; + u8 intr; + bool int_enabled; + + /* Calibration coefficients */ + u32 calib0; + u32 calib1; + int cover_comp_gain; + + /* Cache current values, to be returned while suspended */ + u32 data0; + u32 data1; +}; + +static int tsl2563_set_power(struct tsl2563_chip *chip, int on) +{ + struct i2c_client *client = chip->client; + u8 cmd; + + cmd = on ? TSL2563_CMD_POWER_ON : TSL2563_CMD_POWER_OFF; + return i2c_smbus_write_byte_data(client, + TSL2563_CMD | TSL2563_REG_CTRL, cmd); +} + +/* + * Return value is 0 for off, 1 for on, or a negative error + * code if reading failed. + */ +static int tsl2563_get_power(struct tsl2563_chip *chip) +{ + struct i2c_client *client = chip->client; + int ret; + + ret = i2c_smbus_read_byte_data(client, TSL2563_CMD | TSL2563_REG_CTRL); + if (ret < 0) + return ret; + + return (ret & TSL2563_CTRL_POWER_MASK) == TSL2563_CMD_POWER_ON; +} + +static int tsl2563_configure(struct tsl2563_chip *chip) +{ + int ret; + + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_TIMING, + chip->gainlevel->gaintime); + if (ret) + goto error_ret; + ret = i2c_smbus_write_word_data(chip->client, + TSL2563_CMD | TSL2563_REG_HIGH, + chip->high_thres); + if (ret) + goto error_ret; + ret = i2c_smbus_write_word_data(chip->client, + TSL2563_CMD | TSL2563_REG_LOW, + chip->low_thres); + if (ret) + goto error_ret; +/* + * Interrupt register is automatically written anyway if it is relevant + * so is not here. + */ +error_ret: + return ret; +} + +static void tsl2563_poweroff_work(struct work_struct *work) +{ + struct tsl2563_chip *chip = + container_of(work, struct tsl2563_chip, poweroff_work.work); + tsl2563_set_power(chip, 0); +} + +static int tsl2563_detect(struct tsl2563_chip *chip) +{ + int ret; + + ret = tsl2563_set_power(chip, 1); + if (ret) + return ret; + + ret = tsl2563_get_power(chip); + if (ret < 0) + return ret; + + return ret ? 0 : -ENODEV; +} + +static int tsl2563_read_id(struct tsl2563_chip *chip, u8 *id) +{ + struct i2c_client *client = chip->client; + int ret; + + ret = i2c_smbus_read_byte_data(client, TSL2563_CMD | TSL2563_REG_ID); + if (ret < 0) + return ret; + + *id = ret; + + return 0; +} + +static int tsl2563_configure_irq(struct tsl2563_chip *chip, bool enable) +{ + int ret; + + chip->intr &= ~TSL2563_INT_MASK; + if (enable) + chip->intr |= TSL2563_INT_LEVEL; + + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_INT, + chip->intr); + if (ret < 0) + return ret; + + chip->int_enabled = enable; + return 0; +} + +/* + * "Normalized" ADC value is one obtained with 400ms of integration time and + * 16x gain. This function returns the number of bits of shift needed to + * convert between normalized values and HW values obtained using given + * timing and gain settings. + */ +static int tsl2563_adc_shiftbits(u8 timing) +{ + int shift = 0; + + switch (timing & TSL2563_TIMING_MASK) { + case TSL2563_TIMING_13MS: + shift += 5; + break; + case TSL2563_TIMING_100MS: + shift += 2; + break; + case TSL2563_TIMING_400MS: + /* no-op */ + break; + } + + if (!(timing & TSL2563_TIMING_GAIN16)) + shift += 4; + + return shift; +} + +/* Convert a HW ADC value to normalized scale. */ +static u32 tsl2563_normalize_adc(u16 adc, u8 timing) +{ + return adc << tsl2563_adc_shiftbits(timing); +} + +static void tsl2563_wait_adc(struct tsl2563_chip *chip) +{ + unsigned int delay; + + switch (chip->gainlevel->gaintime & TSL2563_TIMING_MASK) { + case TSL2563_TIMING_13MS: + delay = 14; + break; + case TSL2563_TIMING_100MS: + delay = 101; + break; + default: + delay = 402; + } + /* + * TODO: Make sure that we wait at least required delay but why we + * have to extend it one tick more? + */ + schedule_timeout_interruptible(msecs_to_jiffies(delay) + 2); +} + +static int tsl2563_adjust_gainlevel(struct tsl2563_chip *chip, u16 adc) +{ + struct i2c_client *client = chip->client; + + if (adc > chip->gainlevel->max || adc < chip->gainlevel->min) { + + (adc > chip->gainlevel->max) ? + chip->gainlevel++ : chip->gainlevel--; + + i2c_smbus_write_byte_data(client, + TSL2563_CMD | TSL2563_REG_TIMING, + chip->gainlevel->gaintime); + + tsl2563_wait_adc(chip); + tsl2563_wait_adc(chip); + + return 1; + } else + return 0; +} + +static int tsl2563_get_adc(struct tsl2563_chip *chip) +{ + struct i2c_client *client = chip->client; + u16 adc0, adc1; + int retry = 1; + int ret = 0; + + if (chip->suspended) + goto out; + + if (!chip->int_enabled) { + cancel_delayed_work_sync(&chip->poweroff_work); + + if (!tsl2563_get_power(chip)) { + ret = tsl2563_set_power(chip, 1); + if (ret) + goto out; + ret = tsl2563_configure(chip); + if (ret) + goto out; + tsl2563_wait_adc(chip); + } + } + + while (retry) { + ret = i2c_smbus_read_word_data(client, + TSL2563_CMD | TSL2563_REG_DATA0); + if (ret < 0) + goto out; + adc0 = ret; + + ret = i2c_smbus_read_word_data(client, + TSL2563_CMD | TSL2563_REG_DATA1); + if (ret < 0) + goto out; + adc1 = ret; + + retry = tsl2563_adjust_gainlevel(chip, adc0); + } + + chip->data0 = tsl2563_normalize_adc(adc0, chip->gainlevel->gaintime); + chip->data1 = tsl2563_normalize_adc(adc1, chip->gainlevel->gaintime); + + if (!chip->int_enabled) + schedule_delayed_work(&chip->poweroff_work, 5 * HZ); + + ret = 0; +out: + return ret; +} + +static inline int tsl2563_calib_to_sysfs(u32 calib) +{ + return (int)DIV_ROUND_CLOSEST(calib * CALIB_BASE_SYSFS, BIT(CALIB_FRAC_BITS)); +} + +static inline u32 tsl2563_calib_from_sysfs(int value) +{ + /* Make a fraction from a number n that was multiplied with b. */ + return (((u32) value) << CALIB_FRAC_BITS) / CALIB_BASE_SYSFS; +} + +/* + * Conversions between lux and ADC values. + * + * The basic formula is lux = c0 * adc0 - c1 * adc1, where c0 and c1 are + * appropriate constants. Different constants are needed for different + * kinds of light, determined by the ratio adc1/adc0 (basically the ratio + * of the intensities in infrared and visible wavelengths). lux_table below + * lists the upper threshold of the adc1/adc0 ratio and the corresponding + * constants. + */ + +struct tsl2563_lux_coeff { + unsigned long ch_ratio; + unsigned long ch0_coeff; + unsigned long ch1_coeff; +}; + +static const struct tsl2563_lux_coeff lux_table[] = { + { + .ch_ratio = FRAC10K(1300), + .ch0_coeff = FRAC10K(315), + .ch1_coeff = FRAC10K(262), + }, { + .ch_ratio = FRAC10K(2600), + .ch0_coeff = FRAC10K(337), + .ch1_coeff = FRAC10K(430), + }, { + .ch_ratio = FRAC10K(3900), + .ch0_coeff = FRAC10K(363), + .ch1_coeff = FRAC10K(529), + }, { + .ch_ratio = FRAC10K(5200), + .ch0_coeff = FRAC10K(392), + .ch1_coeff = FRAC10K(605), + }, { + .ch_ratio = FRAC10K(6500), + .ch0_coeff = FRAC10K(229), + .ch1_coeff = FRAC10K(291), + }, { + .ch_ratio = FRAC10K(8000), + .ch0_coeff = FRAC10K(157), + .ch1_coeff = FRAC10K(180), + }, { + .ch_ratio = FRAC10K(13000), + .ch0_coeff = FRAC10K(34), + .ch1_coeff = FRAC10K(26), + }, { + .ch_ratio = ULONG_MAX, + .ch0_coeff = 0, + .ch1_coeff = 0, + }, +}; + +/* Convert normalized, scaled ADC values to lux. */ +static unsigned int tsl2563_adc_to_lux(u32 adc0, u32 adc1) +{ + const struct tsl2563_lux_coeff *lp = lux_table; + unsigned long ratio, lux, ch0 = adc0, ch1 = adc1; + + ratio = ch0 ? ((ch1 << ADC_FRAC_BITS) / ch0) : ULONG_MAX; + + while (lp->ch_ratio < ratio) + lp++; + + lux = ch0 * lp->ch0_coeff - ch1 * lp->ch1_coeff; + + return (unsigned int) (lux >> ADC_FRAC_BITS); +} + +/* Apply calibration coefficient to ADC count. */ +static u32 tsl2563_calib_adc(u32 adc, u32 calib) +{ + unsigned long scaled = adc; + + scaled *= calib; + scaled >>= CALIB_FRAC_BITS; + + return (u32) scaled; +} + +static int tsl2563_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + + if (mask != IIO_CHAN_INFO_CALIBSCALE) + return -EINVAL; + if (chan->channel2 == IIO_MOD_LIGHT_BOTH) + chip->calib0 = tsl2563_calib_from_sysfs(val); + else if (chan->channel2 == IIO_MOD_LIGHT_IR) + chip->calib1 = tsl2563_calib_from_sysfs(val); + else + return -EINVAL; + + return 0; +} + +static int tsl2563_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret = -EINVAL; + u32 calib0, calib1; + struct tsl2563_chip *chip = iio_priv(indio_dev); + + mutex_lock(&chip->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + ret = tsl2563_get_adc(chip); + if (ret) + goto error_ret; + calib0 = tsl2563_calib_adc(chip->data0, chip->calib0) * + chip->cover_comp_gain; + calib1 = tsl2563_calib_adc(chip->data1, chip->calib1) * + chip->cover_comp_gain; + *val = tsl2563_adc_to_lux(calib0, calib1); + ret = IIO_VAL_INT; + break; + case IIO_INTENSITY: + ret = tsl2563_get_adc(chip); + if (ret) + goto error_ret; + if (chan->channel2 == IIO_MOD_LIGHT_BOTH) + *val = chip->data0; + else + *val = chip->data1; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->channel2 == IIO_MOD_LIGHT_BOTH) + *val = tsl2563_calib_to_sysfs(chip->calib0); + else + *val = tsl2563_calib_to_sysfs(chip->calib1); + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + goto error_ret; + } + +error_ret: + mutex_unlock(&chip->lock); + return ret; +} + +static const struct iio_event_spec tsl2563_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec tsl2563_channels[] = { + { + .type = IIO_LIGHT, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .channel = 0, + }, { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .event_spec = tsl2563_events, + .num_event_specs = ARRAY_SIZE(tsl2563_events), + }, { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + } +}; + +static int tsl2563_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int *val, + int *val2) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + + switch (dir) { + case IIO_EV_DIR_RISING: + *val = chip->high_thres; + break; + case IIO_EV_DIR_FALLING: + *val = chip->low_thres; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static int tsl2563_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int val, + int val2) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->lock); + + if (dir == IIO_EV_DIR_RISING) + ret = i2c_smbus_write_word_data(chip->client, + TSL2563_CMD | TSL2563_REG_HIGH, val); + else + ret = i2c_smbus_write_word_data(chip->client, + TSL2563_CMD | TSL2563_REG_LOW, val); + if (ret) + goto error_ret; + + if (dir == IIO_EV_DIR_RISING) + chip->high_thres = val; + else + chip->low_thres = val; + +error_ret: + mutex_unlock(&chip->lock); + + return ret; +} + +static irqreturn_t tsl2563_event_handler(int irq, void *private) +{ + struct iio_dev *dev_info = private; + struct tsl2563_chip *chip = iio_priv(dev_info); + + iio_push_event(dev_info, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(dev_info)); + + /* clear the interrupt and push the event */ + i2c_smbus_write_byte(chip->client, TSL2563_CMD | TSL2563_CLEARINT); + return IRQ_HANDLED; +} + +static int tsl2563_write_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + int ret = 0; + + mutex_lock(&chip->lock); + if (state && !(chip->intr & TSL2563_INT_MASK)) { + /* ensure the chip is actually on */ + cancel_delayed_work_sync(&chip->poweroff_work); + if (!tsl2563_get_power(chip)) { + ret = tsl2563_set_power(chip, 1); + if (ret) + goto out; + ret = tsl2563_configure(chip); + if (ret) + goto out; + } + ret = tsl2563_configure_irq(chip, true); + } + + if (!state && (chip->intr & TSL2563_INT_MASK)) { + ret = tsl2563_configure_irq(chip, false); + /* now the interrupt is not enabled, we can go to sleep */ + schedule_delayed_work(&chip->poweroff_work, 5 * HZ); + } +out: + mutex_unlock(&chip->lock); + + return ret; +} + +static int tsl2563_read_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->lock); + ret = i2c_smbus_read_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_INT); + mutex_unlock(&chip->lock); + if (ret < 0) + return ret; + + return !!(ret & TSL2563_INT_MASK); +} + +static const struct iio_info tsl2563_info_no_irq = { + .read_raw = &tsl2563_read_raw, + .write_raw = &tsl2563_write_raw, +}; + +static const struct iio_info tsl2563_info = { + .read_raw = &tsl2563_read_raw, + .write_raw = &tsl2563_write_raw, + .read_event_value = &tsl2563_read_thresh, + .write_event_value = &tsl2563_write_thresh, + .read_event_config = &tsl2563_read_interrupt_config, + .write_event_config = &tsl2563_write_interrupt_config, +}; + +static int tsl2563_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct tsl2563_chip *chip; + unsigned long irq_flags; + u8 id = 0; + int err; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + i2c_set_clientdata(client, indio_dev); + chip->client = client; + + err = tsl2563_detect(chip); + if (err) + return dev_err_probe(dev, err, "detect error\n"); + + err = tsl2563_read_id(chip, &id); + if (err) + return dev_err_probe(dev, err, "read id error\n"); + + mutex_init(&chip->lock); + + /* Default values used until userspace says otherwise */ + chip->low_thres = 0x0; + chip->high_thres = 0xffff; + chip->gainlevel = tsl2563_gainlevel_table; + chip->intr = TSL2563_INT_PERSIST(4); + chip->calib0 = tsl2563_calib_from_sysfs(CALIB_BASE_SYSFS); + chip->calib1 = tsl2563_calib_from_sysfs(CALIB_BASE_SYSFS); + + chip->cover_comp_gain = 1; + device_property_read_u32(dev, "amstaos,cover-comp-gain", &chip->cover_comp_gain); + + dev_info(dev, "model %d, rev. %d\n", id >> 4, id & 0x0f); + indio_dev->name = client->name; + indio_dev->channels = tsl2563_channels; + indio_dev->num_channels = ARRAY_SIZE(tsl2563_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + if (client->irq) + indio_dev->info = &tsl2563_info; + else + indio_dev->info = &tsl2563_info_no_irq; + + if (client->irq) { + irq_flags = irq_get_trigger_type(client->irq); + if (irq_flags == IRQF_TRIGGER_NONE) + irq_flags = IRQF_TRIGGER_RISING; + irq_flags |= IRQF_ONESHOT; + + err = devm_request_threaded_irq(dev, client->irq, + NULL, + &tsl2563_event_handler, + irq_flags, + "tsl2563_event", + indio_dev); + if (err) + return err; + } + + err = tsl2563_configure(chip); + if (err) + return dev_err_probe(dev, err, "configure error\n"); + + INIT_DELAYED_WORK(&chip->poweroff_work, tsl2563_poweroff_work); + + /* The interrupt cannot yet be enabled so this is fine without lock */ + schedule_delayed_work(&chip->poweroff_work, 5 * HZ); + + err = iio_device_register(indio_dev); + if (err) { + dev_err_probe(dev, err, "iio registration error\n"); + goto fail; + } + + return 0; + +fail: + cancel_delayed_work_sync(&chip->poweroff_work); + return err; +} + +static void tsl2563_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct tsl2563_chip *chip = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (!chip->int_enabled) + cancel_delayed_work_sync(&chip->poweroff_work); + /* Ensure that interrupts are disabled - then flush any bottom halves */ + tsl2563_configure_irq(chip, false); + tsl2563_set_power(chip, 0); +} + +static int tsl2563_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2563_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_set_power(chip, 0); + if (ret) + goto out; + + chip->suspended = true; + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static int tsl2563_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2563_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_set_power(chip, 1); + if (ret) + goto out; + + ret = tsl2563_configure(chip); + if (ret) + goto out; + + chip->suspended = false; + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(tsl2563_pm_ops, tsl2563_suspend, + tsl2563_resume); + +static const struct i2c_device_id tsl2563_id[] = { + { .name = "tsl2560" }, + { .name = "tsl2561" }, + { .name = "tsl2562" }, + { .name = "tsl2563" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tsl2563_id); + +static const struct of_device_id tsl2563_of_match[] = { + { .compatible = "amstaos,tsl2560" }, + { .compatible = "amstaos,tsl2561" }, + { .compatible = "amstaos,tsl2562" }, + { .compatible = "amstaos,tsl2563" }, + { } +}; +MODULE_DEVICE_TABLE(of, tsl2563_of_match); + +static struct i2c_driver tsl2563_i2c_driver = { + .driver = { + .name = "tsl2563", + .of_match_table = tsl2563_of_match, + .pm = pm_sleep_ptr(&tsl2563_pm_ops), + }, + .probe = tsl2563_probe, + .remove = tsl2563_remove, + .id_table = tsl2563_id, +}; +module_i2c_driver(tsl2563_i2c_driver); + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("tsl2563 light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/tsl2583.c b/drivers/iio/light/tsl2583.c new file mode 100644 index 000000000..53fd423aa --- /dev/null +++ b/drivers/iio/light/tsl2583.c @@ -0,0 +1,935 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Device driver for monitoring ambient light intensity (lux) + * within the TAOS tsl258x family of devices (tsl2580, tsl2581, tsl2583). + * + * Copyright (c) 2011, TAOS Corporation. + * Copyright (c) 2016-2017 Brian Masney <masneyb@onstation.org> + */ + +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/mutex.h> +#include <linux/unistd.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/pm_runtime.h> + +/* Device Registers and Masks */ +#define TSL2583_CNTRL 0x00 +#define TSL2583_ALS_TIME 0X01 +#define TSL2583_INTERRUPT 0x02 +#define TSL2583_GAIN 0x07 +#define TSL2583_REVID 0x11 +#define TSL2583_CHIPID 0x12 +#define TSL2583_ALS_CHAN0LO 0x14 +#define TSL2583_ALS_CHAN0HI 0x15 +#define TSL2583_ALS_CHAN1LO 0x16 +#define TSL2583_ALS_CHAN1HI 0x17 +#define TSL2583_TMR_LO 0x18 +#define TSL2583_TMR_HI 0x19 + +/* tsl2583 cmd reg masks */ +#define TSL2583_CMD_REG 0x80 +#define TSL2583_CMD_SPL_FN 0x60 +#define TSL2583_CMD_ALS_INT_CLR 0x01 + +/* tsl2583 cntrl reg masks */ +#define TSL2583_CNTL_ADC_ENBL 0x02 +#define TSL2583_CNTL_PWR_OFF 0x00 +#define TSL2583_CNTL_PWR_ON 0x01 + +/* tsl2583 status reg masks */ +#define TSL2583_STA_ADC_VALID 0x01 +#define TSL2583_STA_ADC_INTR 0x10 + +/* Lux calculation constants */ +#define TSL2583_LUX_CALC_OVER_FLOW 65535 + +#define TSL2583_INTERRUPT_DISABLED 0x00 + +#define TSL2583_CHIP_ID 0x90 +#define TSL2583_CHIP_ID_MASK 0xf0 + +#define TSL2583_POWER_OFF_DELAY_MS 2000 + +/* Per-device data */ +struct tsl2583_als_info { + u16 als_ch0; + u16 als_ch1; + u16 lux; +}; + +struct tsl2583_lux { + unsigned int ratio; + unsigned int ch0; + unsigned int ch1; +}; + +static const struct tsl2583_lux tsl2583_default_lux[] = { + { 9830, 8520, 15729 }, + { 12452, 10807, 23344 }, + { 14746, 6383, 11705 }, + { 17695, 4063, 6554 }, + { 0, 0, 0 } /* Termination segment */ +}; + +#define TSL2583_MAX_LUX_TABLE_ENTRIES 11 + +struct tsl2583_settings { + int als_time; + int als_gain; + int als_gain_trim; + int als_cal_target; + + /* + * This structure is intentionally large to accommodate updates via + * sysfs. Sized to 11 = max 10 segments + 1 termination segment. + * Assumption is that one and only one type of glass used. + */ + struct tsl2583_lux als_device_lux[TSL2583_MAX_LUX_TABLE_ENTRIES]; +}; + +struct tsl2583_chip { + struct mutex als_mutex; + struct i2c_client *client; + struct tsl2583_als_info als_cur_info; + struct tsl2583_settings als_settings; + int als_time_scale; + int als_saturation; +}; + +struct gainadj { + s16 ch0; + s16 ch1; + s16 mean; +}; + +/* Index = (0 - 3) Used to validate the gain selection index */ +static const struct gainadj gainadj[] = { + { 1, 1, 1 }, + { 8, 8, 8 }, + { 16, 16, 16 }, + { 107, 115, 111 } +}; + +/* + * Provides initial operational parameter defaults. + * These defaults may be changed through the device's sysfs files. + */ +static void tsl2583_defaults(struct tsl2583_chip *chip) +{ + /* + * The integration time must be a multiple of 50ms and within the + * range [50, 600] ms. + */ + chip->als_settings.als_time = 100; + + /* + * This is an index into the gainadj table. Assume clear glass as the + * default. + */ + chip->als_settings.als_gain = 0; + + /* Default gain trim to account for aperture effects */ + chip->als_settings.als_gain_trim = 1000; + + /* Known external ALS reading used for calibration */ + chip->als_settings.als_cal_target = 130; + + /* Default lux table. */ + memcpy(chip->als_settings.als_device_lux, tsl2583_default_lux, + sizeof(tsl2583_default_lux)); +} + +/* + * Reads and calculates current lux value. + * The raw ch0 and ch1 values of the ambient light sensed in the last + * integration cycle are read from the device. + * Time scale factor array values are adjusted based on the integration time. + * The raw values are multiplied by a scale factor, and device gain is obtained + * using gain index. Limit checks are done next, then the ratio of a multiple + * of ch1 value, to the ch0 value, is calculated. The array als_device_lux[] + * declared above is then scanned to find the first ratio value that is just + * above the ratio we just calculated. The ch0 and ch1 multiplier constants in + * the array are then used along with the time scale factor array values, to + * calculate the lux. + */ +static int tsl2583_get_lux(struct iio_dev *indio_dev) +{ + u16 ch0, ch1; /* separated ch0/ch1 data from device */ + u32 lux; /* raw lux calculated from device data */ + u64 lux64; + u32 ratio; + u8 buf[5]; + struct tsl2583_lux *p; + struct tsl2583_chip *chip = iio_priv(indio_dev); + int i, ret; + + ret = i2c_smbus_read_byte_data(chip->client, TSL2583_CMD_REG); + if (ret < 0) { + dev_err(&chip->client->dev, "%s: failed to read CMD_REG register\n", + __func__); + goto done; + } + + /* is data new & valid */ + if (!(ret & TSL2583_STA_ADC_INTR)) { + dev_err(&chip->client->dev, "%s: data not valid; returning last value\n", + __func__); + ret = chip->als_cur_info.lux; /* return LAST VALUE */ + goto done; + } + + for (i = 0; i < 4; i++) { + int reg = TSL2583_CMD_REG | (TSL2583_ALS_CHAN0LO + i); + + ret = i2c_smbus_read_byte_data(chip->client, reg); + if (ret < 0) { + dev_err(&chip->client->dev, "%s: failed to read register %x\n", + __func__, reg); + goto done; + } + buf[i] = ret; + } + + /* + * Clear the pending interrupt status bit on the chip to allow the next + * integration cycle to start. This has to be done even though this + * driver currently does not support interrupts. + */ + ret = i2c_smbus_write_byte(chip->client, + (TSL2583_CMD_REG | TSL2583_CMD_SPL_FN | + TSL2583_CMD_ALS_INT_CLR)); + if (ret < 0) { + dev_err(&chip->client->dev, "%s: failed to clear the interrupt bit\n", + __func__); + goto done; /* have no data, so return failure */ + } + + /* extract ALS/lux data */ + ch0 = le16_to_cpup((const __le16 *)&buf[0]); + ch1 = le16_to_cpup((const __le16 *)&buf[2]); + + chip->als_cur_info.als_ch0 = ch0; + chip->als_cur_info.als_ch1 = ch1; + + if ((ch0 >= chip->als_saturation) || (ch1 >= chip->als_saturation)) + goto return_max; + + if (!ch0) { + /* + * The sensor appears to be in total darkness so set the + * calculated lux to 0 and return early to avoid a division by + * zero below when calculating the ratio. + */ + ret = 0; + chip->als_cur_info.lux = 0; + goto done; + } + + /* calculate ratio */ + ratio = (ch1 << 15) / ch0; + + /* convert to unscaled lux using the pointer to the table */ + for (p = (struct tsl2583_lux *)chip->als_settings.als_device_lux; + p->ratio != 0 && p->ratio < ratio; p++) + ; + + if (p->ratio == 0) { + lux = 0; + } else { + u32 ch0lux, ch1lux; + + ch0lux = ((ch0 * p->ch0) + + (gainadj[chip->als_settings.als_gain].ch0 >> 1)) + / gainadj[chip->als_settings.als_gain].ch0; + ch1lux = ((ch1 * p->ch1) + + (gainadj[chip->als_settings.als_gain].ch1 >> 1)) + / gainadj[chip->als_settings.als_gain].ch1; + + /* note: lux is 31 bit max at this point */ + if (ch1lux > ch0lux) { + dev_dbg(&chip->client->dev, "%s: No Data - Returning 0\n", + __func__); + ret = 0; + chip->als_cur_info.lux = 0; + goto done; + } + + lux = ch0lux - ch1lux; + } + + /* adjust for active time scale */ + if (chip->als_time_scale == 0) + lux = 0; + else + lux = (lux + (chip->als_time_scale >> 1)) / + chip->als_time_scale; + + /* + * Adjust for active gain scale. + * The tsl2583_default_lux tables above have a factor of 8192 built in, + * so we need to shift right. + * User-specified gain provides a multiplier. + * Apply user-specified gain before shifting right to retain precision. + * Use 64 bits to avoid overflow on multiplication. + * Then go back to 32 bits before division to avoid using div_u64(). + */ + lux64 = lux; + lux64 = lux64 * chip->als_settings.als_gain_trim; + lux64 >>= 13; + lux = lux64; + lux = DIV_ROUND_CLOSEST(lux, 1000); + + if (lux > TSL2583_LUX_CALC_OVER_FLOW) { /* check for overflow */ +return_max: + lux = TSL2583_LUX_CALC_OVER_FLOW; + } + + /* Update the structure with the latest VALID lux. */ + chip->als_cur_info.lux = lux; + ret = lux; + +done: + return ret; +} + +/* + * Obtain single reading and calculate the als_gain_trim (later used + * to derive actual lux). + * Return updated gain_trim value. + */ +static int tsl2583_als_calibrate(struct iio_dev *indio_dev) +{ + struct tsl2583_chip *chip = iio_priv(indio_dev); + unsigned int gain_trim_val; + int ret; + int lux_val; + + ret = i2c_smbus_read_byte_data(chip->client, + TSL2583_CMD_REG | TSL2583_CNTRL); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to read from the CNTRL register\n", + __func__); + return ret; + } + + if ((ret & (TSL2583_CNTL_ADC_ENBL | TSL2583_CNTL_PWR_ON)) + != (TSL2583_CNTL_ADC_ENBL | TSL2583_CNTL_PWR_ON)) { + dev_err(&chip->client->dev, + "%s: Device is not powered on and/or ADC is not enabled\n", + __func__); + return -EINVAL; + } else if ((ret & TSL2583_STA_ADC_VALID) != TSL2583_STA_ADC_VALID) { + dev_err(&chip->client->dev, + "%s: The two ADC channels have not completed an integration cycle\n", + __func__); + return -ENODATA; + } + + lux_val = tsl2583_get_lux(indio_dev); + if (lux_val < 0) { + dev_err(&chip->client->dev, "%s: failed to get lux\n", + __func__); + return lux_val; + } + + /* Avoid division by zero of lux_value later on */ + if (lux_val == 0) { + dev_err(&chip->client->dev, + "%s: lux_val of 0 will produce out of range trim_value\n", + __func__); + return -ENODATA; + } + + gain_trim_val = (unsigned int)(((chip->als_settings.als_cal_target) + * chip->als_settings.als_gain_trim) / lux_val); + if ((gain_trim_val < 250) || (gain_trim_val > 4000)) { + dev_err(&chip->client->dev, + "%s: trim_val of %d is not within the range [250, 4000]\n", + __func__, gain_trim_val); + return -ENODATA; + } + + chip->als_settings.als_gain_trim = (int)gain_trim_val; + + return 0; +} + +static int tsl2583_set_als_time(struct tsl2583_chip *chip) +{ + int als_count, als_time, ret; + u8 val; + + /* determine als integration register */ + als_count = DIV_ROUND_CLOSEST(chip->als_settings.als_time * 100, 270); + if (!als_count) + als_count = 1; /* ensure at least one cycle */ + + /* convert back to time (encompasses overrides) */ + als_time = DIV_ROUND_CLOSEST(als_count * 27, 10); + + val = 256 - als_count; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2583_CMD_REG | TSL2583_ALS_TIME, + val); + if (ret < 0) { + dev_err(&chip->client->dev, "%s: failed to set the als time to %d\n", + __func__, val); + return ret; + } + + /* set chip struct re scaling and saturation */ + chip->als_saturation = als_count * 922; /* 90% of full scale */ + chip->als_time_scale = DIV_ROUND_CLOSEST(als_time, 50); + + return ret; +} + +static int tsl2583_set_als_gain(struct tsl2583_chip *chip) +{ + int ret; + + /* Set the gain based on als_settings struct */ + ret = i2c_smbus_write_byte_data(chip->client, + TSL2583_CMD_REG | TSL2583_GAIN, + chip->als_settings.als_gain); + if (ret < 0) + dev_err(&chip->client->dev, + "%s: failed to set the gain to %d\n", __func__, + chip->als_settings.als_gain); + + return ret; +} + +static int tsl2583_set_power_state(struct tsl2583_chip *chip, u8 state) +{ + int ret; + + ret = i2c_smbus_write_byte_data(chip->client, + TSL2583_CMD_REG | TSL2583_CNTRL, state); + if (ret < 0) + dev_err(&chip->client->dev, + "%s: failed to set the power state to %d\n", __func__, + state); + + return ret; +} + +/* + * Turn the device on. + * Configuration must be set before calling this function. + */ +static int tsl2583_chip_init_and_power_on(struct iio_dev *indio_dev) +{ + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret; + + /* Power on the device; ADC off. */ + ret = tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_ON); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(chip->client, + TSL2583_CMD_REG | TSL2583_INTERRUPT, + TSL2583_INTERRUPT_DISABLED); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to disable interrupts\n", __func__); + return ret; + } + + ret = tsl2583_set_als_time(chip); + if (ret < 0) + return ret; + + ret = tsl2583_set_als_gain(chip); + if (ret < 0) + return ret; + + usleep_range(3000, 3500); + + return tsl2583_set_power_state(chip, + TSL2583_CNTL_PWR_ON | TSL2583_CNTL_ADC_ENBL); +} + +/* Sysfs Interface Functions */ + +static ssize_t in_illuminance_input_target_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->als_mutex); + ret = sysfs_emit(buf, "%d\n", chip->als_settings.als_cal_target); + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static ssize_t in_illuminance_input_target_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int value; + + if (kstrtoint(buf, 0, &value) || !value) + return -EINVAL; + + mutex_lock(&chip->als_mutex); + chip->als_settings.als_cal_target = value; + mutex_unlock(&chip->als_mutex); + + return len; +} + +static ssize_t in_illuminance_calibrate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int value, ret; + + if (kstrtoint(buf, 0, &value) || value != 1) + return -EINVAL; + + mutex_lock(&chip->als_mutex); + + ret = tsl2583_als_calibrate(indio_dev); + if (ret < 0) + goto done; + + ret = len; +done: + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static ssize_t in_illuminance_lux_table_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + unsigned int i; + int offset = 0; + + for (i = 0; i < ARRAY_SIZE(chip->als_settings.als_device_lux); i++) { + offset += sysfs_emit_at(buf, offset, "%u,%u,%u,", + chip->als_settings.als_device_lux[i].ratio, + chip->als_settings.als_device_lux[i].ch0, + chip->als_settings.als_device_lux[i].ch1); + if (chip->als_settings.als_device_lux[i].ratio == 0) { + /* + * We just printed the first "0" entry. + * Now get rid of the extra "," and break. + */ + offset--; + break; + } + } + + offset += sysfs_emit_at(buf, offset, "\n"); + + return offset; +} + +static ssize_t in_illuminance_lux_table_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + const unsigned int max_ints = TSL2583_MAX_LUX_TABLE_ENTRIES * 3; + int value[TSL2583_MAX_LUX_TABLE_ENTRIES * 3 + 1]; + int ret = -EINVAL; + unsigned int n; + + mutex_lock(&chip->als_mutex); + + get_options(buf, ARRAY_SIZE(value), value); + + /* + * We now have an array of ints starting at value[1], and + * enumerated by value[0]. + * We expect each group of three ints is one table entry, + * and the last table entry is all 0. + */ + n = value[0]; + if ((n % 3) || n < 6 || n > max_ints) { + dev_err(dev, + "%s: The number of entries in the lux table must be a multiple of 3 and within the range [6, %d]\n", + __func__, max_ints); + goto done; + } + if ((value[n - 2] | value[n - 1] | value[n]) != 0) { + dev_err(dev, "%s: The last 3 entries in the lux table must be zeros.\n", + __func__); + goto done; + } + + memcpy(chip->als_settings.als_device_lux, &value[1], + value[0] * sizeof(value[1])); + + ret = len; + +done: + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static IIO_CONST_ATTR(in_illuminance_calibscale_available, "1 8 16 111"); +static IIO_CONST_ATTR(in_illuminance_integration_time_available, + "0.050 0.100 0.150 0.200 0.250 0.300 0.350 0.400 0.450 0.500 0.550 0.600 0.650"); +static IIO_DEVICE_ATTR_RW(in_illuminance_input_target, 0); +static IIO_DEVICE_ATTR_WO(in_illuminance_calibrate, 0); +static IIO_DEVICE_ATTR_RW(in_illuminance_lux_table, 0); + +static struct attribute *sysfs_attrs_ctrl[] = { + &iio_const_attr_in_illuminance_calibscale_available.dev_attr.attr, + &iio_const_attr_in_illuminance_integration_time_available.dev_attr.attr, + &iio_dev_attr_in_illuminance_input_target.dev_attr.attr, + &iio_dev_attr_in_illuminance_calibrate.dev_attr.attr, + &iio_dev_attr_in_illuminance_lux_table.dev_attr.attr, + NULL +}; + +static const struct attribute_group tsl2583_attribute_group = { + .attrs = sysfs_attrs_ctrl, +}; + +static const struct iio_chan_spec tsl2583_channels[] = { + { + .type = IIO_LIGHT, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, + { + .type = IIO_LIGHT, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + }, +}; + +static int tsl2583_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret, pm_ret; + + ret = pm_runtime_resume_and_get(&chip->client->dev); + if (ret < 0) + return ret; + + mutex_lock(&chip->als_mutex); + + ret = -EINVAL; + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_LIGHT) { + ret = tsl2583_get_lux(indio_dev); + if (ret < 0) + goto read_done; + + /* + * From page 20 of the TSL2581, TSL2583 data + * sheet (TAOS134 − MARCH 2011): + * + * One of the photodiodes (channel 0) is + * sensitive to both visible and infrared light, + * while the second photodiode (channel 1) is + * sensitive primarily to infrared light. + */ + if (chan->channel2 == IIO_MOD_LIGHT_BOTH) + *val = chip->als_cur_info.als_ch0; + else + *val = chip->als_cur_info.als_ch1; + + ret = IIO_VAL_INT; + } + break; + case IIO_CHAN_INFO_PROCESSED: + if (chan->type == IIO_LIGHT) { + ret = tsl2583_get_lux(indio_dev); + if (ret < 0) + goto read_done; + + *val = ret; + ret = IIO_VAL_INT; + } + break; + case IIO_CHAN_INFO_CALIBBIAS: + if (chan->type == IIO_LIGHT) { + *val = chip->als_settings.als_gain_trim; + ret = IIO_VAL_INT; + } + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_LIGHT) { + *val = gainadj[chip->als_settings.als_gain].mean; + ret = IIO_VAL_INT; + } + break; + case IIO_CHAN_INFO_INT_TIME: + if (chan->type == IIO_LIGHT) { + *val = 0; + *val2 = chip->als_settings.als_time; + ret = IIO_VAL_INT_PLUS_MICRO; + } + break; + default: + break; + } + +read_done: + mutex_unlock(&chip->als_mutex); + + if (ret < 0) { + pm_runtime_put_autosuspend(&chip->client->dev); + return ret; + } + + /* + * Preserve the ret variable if the call to + * pm_runtime_put_autosuspend() is successful so the reading + * (if applicable) is returned to user space. + */ + pm_ret = pm_runtime_put_autosuspend(&chip->client->dev); + if (pm_ret < 0) + return pm_ret; + + return ret; +} + +static int tsl2583_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret; + + ret = pm_runtime_resume_and_get(&chip->client->dev); + if (ret < 0) + return ret; + + mutex_lock(&chip->als_mutex); + + ret = -EINVAL; + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + if (chan->type == IIO_LIGHT) { + chip->als_settings.als_gain_trim = val; + ret = 0; + } + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_LIGHT) { + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(gainadj); i++) { + if (gainadj[i].mean == val) { + chip->als_settings.als_gain = i; + ret = tsl2583_set_als_gain(chip); + break; + } + } + } + break; + case IIO_CHAN_INFO_INT_TIME: + if (chan->type == IIO_LIGHT && !val && val2 >= 50 && + val2 <= 650 && !(val2 % 50)) { + chip->als_settings.als_time = val2; + ret = tsl2583_set_als_time(chip); + } + break; + default: + break; + } + + mutex_unlock(&chip->als_mutex); + + if (ret < 0) { + pm_runtime_put_autosuspend(&chip->client->dev); + return ret; + } + + ret = pm_runtime_put_autosuspend(&chip->client->dev); + if (ret < 0) + return ret; + + return 0; +} + +static const struct iio_info tsl2583_info = { + .attrs = &tsl2583_attribute_group, + .read_raw = tsl2583_read_raw, + .write_raw = tsl2583_write_raw, +}; + +static int tsl2583_probe(struct i2c_client *clientp) +{ + int ret; + struct tsl2583_chip *chip; + struct iio_dev *indio_dev; + + if (!i2c_check_functionality(clientp->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&clientp->dev, "%s: i2c smbus byte data functionality is unsupported\n", + __func__); + return -EOPNOTSUPP; + } + + indio_dev = devm_iio_device_alloc(&clientp->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + chip->client = clientp; + i2c_set_clientdata(clientp, indio_dev); + + mutex_init(&chip->als_mutex); + + ret = i2c_smbus_read_byte_data(clientp, + TSL2583_CMD_REG | TSL2583_CHIPID); + if (ret < 0) { + dev_err(&clientp->dev, + "%s: failed to read the chip ID register\n", __func__); + return ret; + } + + if ((ret & TSL2583_CHIP_ID_MASK) != TSL2583_CHIP_ID) { + dev_err(&clientp->dev, "%s: received an unknown chip ID %x\n", + __func__, ret); + return -EINVAL; + } + + indio_dev->info = &tsl2583_info; + indio_dev->channels = tsl2583_channels; + indio_dev->num_channels = ARRAY_SIZE(tsl2583_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = chip->client->name; + + pm_runtime_enable(&clientp->dev); + pm_runtime_set_autosuspend_delay(&clientp->dev, + TSL2583_POWER_OFF_DELAY_MS); + pm_runtime_use_autosuspend(&clientp->dev); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&clientp->dev, "%s: iio registration failed\n", + __func__); + return ret; + } + + /* Load up the V2 defaults (these are hard coded defaults for now) */ + tsl2583_defaults(chip); + + dev_info(&clientp->dev, "Light sensor found.\n"); + + return 0; +} + +static void tsl2583_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct tsl2583_chip *chip = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_OFF); +} + +static int tsl2583_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->als_mutex); + + ret = tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_OFF); + + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static int tsl2583_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->als_mutex); + + ret = tsl2583_chip_init_and_power_on(indio_dev); + + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(tsl2583_pm_ops, tsl2583_suspend, + tsl2583_resume, NULL); + +static const struct i2c_device_id tsl2583_idtable[] = { + { .name = "tsl2580" }, + { .name = "tsl2581" }, + { .name = "tsl2583" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tsl2583_idtable); + +static const struct of_device_id tsl2583_of_match[] = { + { .compatible = "amstaos,tsl2580", }, + { .compatible = "amstaos,tsl2581", }, + { .compatible = "amstaos,tsl2583", }, + { } +}; +MODULE_DEVICE_TABLE(of, tsl2583_of_match); + +/* Driver definition */ +static struct i2c_driver tsl2583_driver = { + .driver = { + .name = "tsl2583", + .pm = pm_ptr(&tsl2583_pm_ops), + .of_match_table = tsl2583_of_match, + }, + .id_table = tsl2583_idtable, + .probe = tsl2583_probe, + .remove = tsl2583_remove, +}; +module_i2c_driver(tsl2583_driver); + +MODULE_AUTHOR("J. August Brenner <jbrenner@taosinc.com>"); +MODULE_AUTHOR("Brian Masney <masneyb@onstation.org>"); +MODULE_DESCRIPTION("TAOS tsl2583 ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/tsl2591.c b/drivers/iio/light/tsl2591.c new file mode 100644 index 000000000..ef3ed9635 --- /dev/null +++ b/drivers/iio/light/tsl2591.c @@ -0,0 +1,1152 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Joe Sandom <joe.g.sandom@gmail.com> + * + * Datasheet: https://ams.com/tsl25911#tab/documents + * + * Device driver for the TAOS TSL2591. This is a very-high sensitivity + * light-to-digital converter that transforms light intensity into a digital + * signal. + */ + +#include <linux/bitfield.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm_runtime.h> +#include <linux/sysfs.h> + +#include <linux/unaligned.h> + +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +/* ADC integration time, field value to time in ms */ +#define TSL2591_FVAL_TO_MSEC(x) (((x) + 1) * 100) +/* ADC integration time, field value to time in seconds */ +#define TSL2591_FVAL_TO_SEC(x) ((x) + 1) +/* ADC integration time, time in seconds to field value */ +#define TSL2591_SEC_TO_FVAL(x) ((x) - 1) + +/* TSL2591 register set */ +#define TSL2591_ENABLE 0x00 +#define TSL2591_CONTROL 0x01 +#define TSL2591_AILTL 0x04 +#define TSL2591_AILTH 0x05 +#define TSL2591_AIHTL 0x06 +#define TSL2591_AIHTH 0x07 +#define TSL2591_NP_AILTL 0x08 +#define TSL2591_NP_AILTH 0x09 +#define TSL2591_NP_AIHTL 0x0A +#define TSL2591_NP_AIHTH 0x0B +#define TSL2591_PERSIST 0x0C +#define TSL2591_PACKAGE_ID 0x11 +#define TSL2591_DEVICE_ID 0x12 +#define TSL2591_STATUS 0x13 +#define TSL2591_C0_DATAL 0x14 +#define TSL2591_C0_DATAH 0x15 +#define TSL2591_C1_DATAL 0x16 +#define TSL2591_C1_DATAH 0x17 + +/* TSL2591 command register definitions */ +#define TSL2591_CMD_NOP 0xA0 +#define TSL2591_CMD_SF_INTSET 0xE4 +#define TSL2591_CMD_SF_CALS_I 0xE5 +#define TSL2591_CMD_SF_CALS_NPI 0xE7 +#define TSL2591_CMD_SF_CNP_ALSI 0xEA + +/* TSL2591 enable register definitions */ +#define TSL2591_PWR_ON 0x01 +#define TSL2591_PWR_OFF 0x00 +#define TSL2591_ENABLE_ALS 0x02 +#define TSL2591_ENABLE_ALS_INT 0x10 +#define TSL2591_ENABLE_SLEEP_INT 0x40 +#define TSL2591_ENABLE_NP_INT 0x80 + +/* TSL2591 control register definitions */ +#define TSL2591_CTRL_ALS_INTEGRATION_100MS 0x00 +#define TSL2591_CTRL_ALS_INTEGRATION_200MS 0x01 +#define TSL2591_CTRL_ALS_INTEGRATION_300MS 0x02 +#define TSL2591_CTRL_ALS_INTEGRATION_400MS 0x03 +#define TSL2591_CTRL_ALS_INTEGRATION_500MS 0x04 +#define TSL2591_CTRL_ALS_INTEGRATION_600MS 0x05 +#define TSL2591_CTRL_ALS_LOW_GAIN 0x00 +#define TSL2591_CTRL_ALS_MED_GAIN 0x10 +#define TSL2591_CTRL_ALS_HIGH_GAIN 0x20 +#define TSL2591_CTRL_ALS_MAX_GAIN 0x30 +#define TSL2591_CTRL_SYS_RESET 0x80 + +/* TSL2591 persist register definitions */ +#define TSL2591_PRST_ALS_INT_CYCLE_0 0x00 +#define TSL2591_PRST_ALS_INT_CYCLE_ANY 0x01 +#define TSL2591_PRST_ALS_INT_CYCLE_2 0x02 +#define TSL2591_PRST_ALS_INT_CYCLE_3 0x03 +#define TSL2591_PRST_ALS_INT_CYCLE_5 0x04 +#define TSL2591_PRST_ALS_INT_CYCLE_10 0x05 +#define TSL2591_PRST_ALS_INT_CYCLE_15 0x06 +#define TSL2591_PRST_ALS_INT_CYCLE_20 0x07 +#define TSL2591_PRST_ALS_INT_CYCLE_25 0x08 +#define TSL2591_PRST_ALS_INT_CYCLE_30 0x09 +#define TSL2591_PRST_ALS_INT_CYCLE_35 0x0A +#define TSL2591_PRST_ALS_INT_CYCLE_40 0x0B +#define TSL2591_PRST_ALS_INT_CYCLE_45 0x0C +#define TSL2591_PRST_ALS_INT_CYCLE_50 0x0D +#define TSL2591_PRST_ALS_INT_CYCLE_55 0x0E +#define TSL2591_PRST_ALS_INT_CYCLE_60 0x0F +#define TSL2591_PRST_ALS_INT_CYCLE_MAX (BIT(4) - 1) + +/* TSL2591 PID register mask */ +#define TSL2591_PACKAGE_ID_MASK GENMASK(5, 4) + +/* TSL2591 ID register mask */ +#define TSL2591_DEVICE_ID_MASK GENMASK(7, 0) + +/* TSL2591 status register masks */ +#define TSL2591_STS_ALS_VALID_MASK BIT(0) +#define TSL2591_STS_ALS_INT_MASK BIT(4) +#define TSL2591_STS_NPERS_INT_MASK BIT(5) +#define TSL2591_STS_VAL_HIGH_MASK BIT(0) + +/* TSL2591 constant values */ +#define TSL2591_PACKAGE_ID_VAL 0x00 +#define TSL2591_DEVICE_ID_VAL 0x50 + +/* Power off suspend delay time MS */ +#define TSL2591_POWER_OFF_DELAY_MS 2000 + +/* TSL2591 default values */ +#define TSL2591_DEFAULT_ALS_INT_TIME TSL2591_CTRL_ALS_INTEGRATION_300MS +#define TSL2591_DEFAULT_ALS_GAIN TSL2591_CTRL_ALS_MED_GAIN +#define TSL2591_DEFAULT_ALS_PERSIST TSL2591_PRST_ALS_INT_CYCLE_ANY +#define TSL2591_DEFAULT_ALS_LOWER_THRESH 100 +#define TSL2591_DEFAULT_ALS_UPPER_THRESH 1500 + +/* TSL2591 number of data registers */ +#define TSL2591_NUM_DATA_REGISTERS 4 + +/* TSL2591 number of valid status reads on ADC complete */ +#define TSL2591_ALS_STS_VALID_COUNT 10 + +/* TSL2591 delay period between polls when checking for ALS valid flag */ +#define TSL2591_DELAY_PERIOD_US 10000 + +/* TSL2591 maximum values */ +#define TSL2591_MAX_ALS_INT_TIME_MS 600 +#define TSL2591_ALS_MAX_VALUE (BIT(16) - 1) + +/* + * LUX calculations; + * AGAIN values from Adafruit's TSL2591 Arduino library + * https://github.com/adafruit/Adafruit_TSL2591_Library + */ +#define TSL2591_CTRL_ALS_LOW_GAIN_MULTIPLIER 1 +#define TSL2591_CTRL_ALS_MED_GAIN_MULTIPLIER 25 +#define TSL2591_CTRL_ALS_HIGH_GAIN_MULTIPLIER 428 +#define TSL2591_CTRL_ALS_MAX_GAIN_MULTIPLIER 9876 +#define TSL2591_LUX_COEFFICIENT 408 + +struct tsl2591_als_settings { + u16 als_lower_thresh; + u16 als_upper_thresh; + u8 als_int_time; + u8 als_persist; + u8 als_gain; +}; + +struct tsl2591_chip { + struct tsl2591_als_settings als_settings; + struct i2c_client *client; + /* + * Keep als_settings in sync with hardware state + * and ensure multiple readers are serialized. + */ + struct mutex als_mutex; + bool events_enabled; +}; + +/* + * Period table is ALS persist cycle x integration time setting + * Integration times: 100ms, 200ms, 300ms, 400ms, 500ms, 600ms + * ALS cycles: 1, 2, 3, 5, 10, 20, 25, 30, 35, 40, 45, 50, 55, 60 + */ +static const char * const tsl2591_als_period_list[] = { + "0.1 0.2 0.3 0.5 1.0 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0", + "0.2 0.4 0.6 1.0 2.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0", + "0.3 0.6 0.9 1.5 3.0 6.0 7.5 9.0 10.5 12.0 13.5 15.0 16.5 18.0", + "0.4 0.8 1.2 2.0 4.0 8.0 10.0 12.0 14.0 16.0 18.0 20.0 22.0 24.0", + "0.5 1.0 1.5 2.5 5.0 10.0 12.5 15.0 17.5 20.0 22.5 25.0 27.5 30.0", + "0.6 1.2 1.8 3.0 6.0 12.0 15.0 18.0 21.0 24.0 27.0 30.0 33.0 36.0", +}; + +static const int tsl2591_int_time_available[] = { + 1, 2, 3, 4, 5, 6, +}; + +static const int tsl2591_calibscale_available[] = { + 1, 25, 428, 9876, +}; + +static const u8 tsl2591_persist_table[] = { + [TSL2591_PRST_ALS_INT_CYCLE_ANY] = 1, + [TSL2591_PRST_ALS_INT_CYCLE_2] = 2, + [TSL2591_PRST_ALS_INT_CYCLE_3] = 3, + [TSL2591_PRST_ALS_INT_CYCLE_5] = 5, + [TSL2591_PRST_ALS_INT_CYCLE_10] = 10, + [TSL2591_PRST_ALS_INT_CYCLE_15] = 15, + [TSL2591_PRST_ALS_INT_CYCLE_20] = 20, + [TSL2591_PRST_ALS_INT_CYCLE_25] = 25, + [TSL2591_PRST_ALS_INT_CYCLE_30] = 30, + [TSL2591_PRST_ALS_INT_CYCLE_35] = 35, + [TSL2591_PRST_ALS_INT_CYCLE_40] = 40, + [TSL2591_PRST_ALS_INT_CYCLE_45] = 45, + [TSL2591_PRST_ALS_INT_CYCLE_50] = 50, + [TSL2591_PRST_ALS_INT_CYCLE_55] = 55, + [TSL2591_PRST_ALS_INT_CYCLE_60] = 60, +}; + +static int tsl2591_set_als_lower_threshold(struct tsl2591_chip *chip, + u16 als_lower_threshold); +static int tsl2591_set_als_upper_threshold(struct tsl2591_chip *chip, + u16 als_upper_threshold); + +static int tsl2591_gain_to_multiplier(const u8 als_gain) +{ + switch (als_gain) { + case TSL2591_CTRL_ALS_LOW_GAIN: + return TSL2591_CTRL_ALS_LOW_GAIN_MULTIPLIER; + case TSL2591_CTRL_ALS_MED_GAIN: + return TSL2591_CTRL_ALS_MED_GAIN_MULTIPLIER; + case TSL2591_CTRL_ALS_HIGH_GAIN: + return TSL2591_CTRL_ALS_HIGH_GAIN_MULTIPLIER; + case TSL2591_CTRL_ALS_MAX_GAIN: + return TSL2591_CTRL_ALS_MAX_GAIN_MULTIPLIER; + default: + return -EINVAL; + } +} + +static int tsl2591_multiplier_to_gain(const u32 multiplier) +{ + switch (multiplier) { + case TSL2591_CTRL_ALS_LOW_GAIN_MULTIPLIER: + return TSL2591_CTRL_ALS_LOW_GAIN; + case TSL2591_CTRL_ALS_MED_GAIN_MULTIPLIER: + return TSL2591_CTRL_ALS_MED_GAIN; + case TSL2591_CTRL_ALS_HIGH_GAIN_MULTIPLIER: + return TSL2591_CTRL_ALS_HIGH_GAIN; + case TSL2591_CTRL_ALS_MAX_GAIN_MULTIPLIER: + return TSL2591_CTRL_ALS_MAX_GAIN; + default: + return -EINVAL; + } +} + +static int tsl2591_persist_cycle_to_lit(const u8 als_persist) +{ + if (als_persist >= ARRAY_SIZE(tsl2591_persist_table) || + !tsl2591_persist_table[als_persist]) + return -EINVAL; + + return tsl2591_persist_table[als_persist]; +} + +static int tsl2591_persist_lit_to_cycle(const u8 als_persist) +{ + for (unsigned int i = TSL2591_PRST_ALS_INT_CYCLE_ANY; + i < ARRAY_SIZE(tsl2591_persist_table); i++) { + if (als_persist == tsl2591_persist_table[i]) + return i; + } + + return -EINVAL; +} + +static int tsl2591_compatible_int_time(struct tsl2591_chip *chip, + const u32 als_integration_time) +{ + switch (als_integration_time) { + case TSL2591_CTRL_ALS_INTEGRATION_100MS: + case TSL2591_CTRL_ALS_INTEGRATION_200MS: + case TSL2591_CTRL_ALS_INTEGRATION_300MS: + case TSL2591_CTRL_ALS_INTEGRATION_400MS: + case TSL2591_CTRL_ALS_INTEGRATION_500MS: + case TSL2591_CTRL_ALS_INTEGRATION_600MS: + return 0; + default: + return -EINVAL; + } +} + +static int tsl2591_als_time_to_fval(const u32 als_integration_time) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(tsl2591_int_time_available); i++) { + if (als_integration_time == tsl2591_int_time_available[i]) + return TSL2591_SEC_TO_FVAL(als_integration_time); + } + + return -EINVAL; +} + +static int tsl2591_compatible_gain(struct tsl2591_chip *chip, const u8 als_gain) +{ + switch (als_gain) { + case TSL2591_CTRL_ALS_LOW_GAIN: + case TSL2591_CTRL_ALS_MED_GAIN: + case TSL2591_CTRL_ALS_HIGH_GAIN: + case TSL2591_CTRL_ALS_MAX_GAIN: + return 0; + default: + return -EINVAL; + } +} + +static int tsl2591_check_als_valid(struct i2c_client *client) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, TSL2591_CMD_NOP | TSL2591_STATUS); + if (ret < 0) { + dev_err(&client->dev, "Failed to read register\n"); + return -EINVAL; + } + + return FIELD_GET(TSL2591_STS_ALS_VALID_MASK, ret); +} + +static int tsl2591_wait_adc_complete(struct tsl2591_chip *chip) +{ + struct tsl2591_als_settings settings = chip->als_settings; + struct i2c_client *client = chip->client; + int delay; + int val; + int ret; + + delay = TSL2591_FVAL_TO_MSEC(settings.als_int_time); + if (!delay) + return -EINVAL; + + /* + * Sleep for ALS integration time to allow enough time or an ADC read + * cycle to complete. Check status after delay for ALS valid. + */ + msleep(delay); + + /* Check for status ALS valid flag for up to 100ms */ + ret = readx_poll_timeout(tsl2591_check_als_valid, client, + val, val == TSL2591_STS_VAL_HIGH_MASK, + TSL2591_DELAY_PERIOD_US, + TSL2591_DELAY_PERIOD_US * TSL2591_ALS_STS_VALID_COUNT); + if (ret) + dev_err(&client->dev, "Timed out waiting for valid ALS data\n"); + + return ret; +} + +/* + * tsl2591_read_channel_data - Reads raw channel data and calculates lux + * + * Formula for lux calculation; + * Derived from Adafruit's TSL2591 library + * Link: https://github.com/adafruit/Adafruit_TSL2591_Library + * Counts Per Lux (CPL) = (ATIME_ms * AGAIN) / LUX DF + * lux = ((C0DATA - C1DATA) * (1 - (C1DATA / C0DATA))) / CPL + * + * Scale values to get more representative value of lux i.e. + * lux = ((C0DATA - C1DATA) * (1000 - ((C1DATA * 1000) / C0DATA))) / CPL + * + * Channel 0 = IR + Visible + * Channel 1 = IR only + */ +static int tsl2591_read_channel_data(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + struct tsl2591_chip *chip = iio_priv(indio_dev); + struct tsl2591_als_settings *settings = &chip->als_settings; + struct i2c_client *client = chip->client; + u8 als_data[TSL2591_NUM_DATA_REGISTERS]; + int counts_per_lux, int_time_fval, gain_multi, lux; + u16 als_ch0, als_ch1; + int ret; + + ret = tsl2591_wait_adc_complete(chip); + if (ret < 0) { + dev_err(&client->dev, "No data available. Err: %d\n", ret); + return ret; + } + + ret = i2c_smbus_read_i2c_block_data(client, + TSL2591_CMD_NOP | TSL2591_C0_DATAL, + sizeof(als_data), als_data); + if (ret < 0) { + dev_err(&client->dev, "Failed to read data bytes"); + return ret; + } + + als_ch0 = get_unaligned_le16(&als_data[0]); + als_ch1 = get_unaligned_le16(&als_data[2]); + + switch (chan->type) { + case IIO_INTENSITY: + if (chan->channel2 == IIO_MOD_LIGHT_BOTH) + *val = als_ch0; + else if (chan->channel2 == IIO_MOD_LIGHT_IR) + *val = als_ch1; + else + return -EINVAL; + break; + case IIO_LIGHT: + gain_multi = tsl2591_gain_to_multiplier(settings->als_gain); + if (gain_multi < 0) { + dev_err(&client->dev, "Invalid multiplier"); + return gain_multi; + } + + int_time_fval = TSL2591_FVAL_TO_MSEC(settings->als_int_time); + /* Calculate counts per lux value */ + counts_per_lux = (int_time_fval * gain_multi) / TSL2591_LUX_COEFFICIENT; + + dev_dbg(&client->dev, "Counts Per Lux: %d\n", counts_per_lux); + + /* Calculate lux value */ + lux = ((als_ch0 - als_ch1) * + (1000 - ((als_ch1 * 1000) / als_ch0))) / counts_per_lux; + + dev_dbg(&client->dev, "Raw lux calculation: %d\n", lux); + + /* Divide by 1000 to get real lux value before scaling */ + *val = lux / 1000; + + /* Get the decimal part of lux reading */ + *val2 = (lux - (*val * 1000)) * 1000; + + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tsl2591_set_als_gain_int_time(struct tsl2591_chip *chip) +{ + struct tsl2591_als_settings als_settings = chip->als_settings; + struct i2c_client *client = chip->client; + int ret; + + ret = i2c_smbus_write_byte_data(client, + TSL2591_CMD_NOP | TSL2591_CONTROL, + als_settings.als_int_time | als_settings.als_gain); + if (ret) + dev_err(&client->dev, "Failed to set als gain & int time\n"); + + return ret; +} + +static int tsl2591_set_als_lower_threshold(struct tsl2591_chip *chip, + u16 als_lower_threshold) +{ + struct tsl2591_als_settings als_settings = chip->als_settings; + struct i2c_client *client = chip->client; + u16 als_upper_threshold; + u8 als_lower_l; + u8 als_lower_h; + int ret; + + chip->als_settings.als_lower_thresh = als_lower_threshold; + + /* + * Lower threshold should not be greater or equal to upper. + * If this is the case, then assert upper threshold to new lower + * threshold + 1 to avoid ordering issues when setting thresholds. + */ + if (als_lower_threshold >= als_settings.als_upper_thresh) { + als_upper_threshold = als_lower_threshold + 1; + tsl2591_set_als_upper_threshold(chip, als_upper_threshold); + } + + als_lower_l = als_lower_threshold; + als_lower_h = als_lower_threshold >> 8; + + ret = i2c_smbus_write_byte_data(client, + TSL2591_CMD_NOP | TSL2591_AILTL, + als_lower_l); + if (ret) { + dev_err(&client->dev, "Failed to set als lower threshold\n"); + return ret; + } + + ret = i2c_smbus_write_byte_data(client, + TSL2591_CMD_NOP | TSL2591_AILTH, + als_lower_h); + if (ret) { + dev_err(&client->dev, "Failed to set als lower threshold\n"); + return ret; + } + + return 0; +} + +static int tsl2591_set_als_upper_threshold(struct tsl2591_chip *chip, + u16 als_upper_threshold) +{ + struct tsl2591_als_settings als_settings = chip->als_settings; + struct i2c_client *client = chip->client; + u16 als_lower_threshold; + u8 als_upper_l; + u8 als_upper_h; + int ret; + + if (als_upper_threshold > TSL2591_ALS_MAX_VALUE) + return -EINVAL; + + chip->als_settings.als_upper_thresh = als_upper_threshold; + + /* + * Upper threshold should not be less than lower. If this + * is the case, then assert lower threshold to new upper + * threshold - 1 to avoid ordering issues when setting thresholds. + */ + if (als_upper_threshold < als_settings.als_lower_thresh) { + als_lower_threshold = als_upper_threshold - 1; + tsl2591_set_als_lower_threshold(chip, als_lower_threshold); + } + + als_upper_l = als_upper_threshold; + als_upper_h = als_upper_threshold >> 8; + + ret = i2c_smbus_write_byte_data(client, + TSL2591_CMD_NOP | TSL2591_AIHTL, + als_upper_l); + if (ret) { + dev_err(&client->dev, "Failed to set als upper threshold\n"); + return ret; + } + + ret = i2c_smbus_write_byte_data(client, + TSL2591_CMD_NOP | TSL2591_AIHTH, + als_upper_h); + if (ret) { + dev_err(&client->dev, "Failed to set als upper threshold\n"); + return ret; + } + + return 0; +} + +static int tsl2591_set_als_persist_cycle(struct tsl2591_chip *chip, + u8 als_persist) +{ + struct i2c_client *client = chip->client; + int ret; + + ret = i2c_smbus_write_byte_data(client, + TSL2591_CMD_NOP | TSL2591_PERSIST, + als_persist); + if (ret) + dev_err(&client->dev, "Failed to set als persist cycle\n"); + + chip->als_settings.als_persist = als_persist; + + return ret; +} + +static int tsl2591_set_power_state(struct tsl2591_chip *chip, u8 state) +{ + struct i2c_client *client = chip->client; + int ret; + + ret = i2c_smbus_write_byte_data(client, + TSL2591_CMD_NOP | TSL2591_ENABLE, + state); + if (ret) + dev_err(&client->dev, + "Failed to set the power state to %#04x\n", state); + + return ret; +} + +static ssize_t tsl2591_in_illuminance_period_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2591_chip *chip = iio_priv(indio_dev); + + return sysfs_emit(buf, "%s\n", + tsl2591_als_period_list[chip->als_settings.als_int_time]); +} + +static IIO_DEVICE_ATTR_RO(tsl2591_in_illuminance_period_available, 0); + +static struct attribute *tsl2591_event_attrs_ctrl[] = { + &iio_dev_attr_tsl2591_in_illuminance_period_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group tsl2591_event_attribute_group = { + .attrs = tsl2591_event_attrs_ctrl, +}; + +static const struct iio_event_spec tsl2591_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec tsl2591_channels[] = { + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE) + }, + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .event_spec = tsl2591_events, + .num_event_specs = ARRAY_SIZE(tsl2591_events), + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE) + }, + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE) + }, +}; + +static int tsl2591_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tsl2591_chip *chip = iio_priv(indio_dev); + struct i2c_client *client = chip->client; + int ret; + + pm_runtime_get_sync(&client->dev); + + mutex_lock(&chip->als_mutex); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_INTENSITY) { + ret = -EINVAL; + goto err_unlock; + } + + ret = tsl2591_read_channel_data(indio_dev, chan, val, val2); + if (ret < 0) + goto err_unlock; + + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_PROCESSED: + if (chan->type != IIO_LIGHT) { + ret = -EINVAL; + goto err_unlock; + } + + ret = tsl2591_read_channel_data(indio_dev, chan, val, val2); + if (ret < 0) + break; + + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_INT_TIME: + if (chan->type != IIO_INTENSITY) { + ret = -EINVAL; + goto err_unlock; + } + + *val = TSL2591_FVAL_TO_SEC(chip->als_settings.als_int_time); + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type != IIO_INTENSITY) { + ret = -EINVAL; + goto err_unlock; + } + + *val = tsl2591_gain_to_multiplier(chip->als_settings.als_gain); + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + +err_unlock: + mutex_unlock(&chip->als_mutex); + + pm_runtime_put_autosuspend(&client->dev); + + return ret; +} + +static int tsl2591_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct tsl2591_chip *chip = iio_priv(indio_dev); + int int_time; + int gain; + int ret; + + mutex_lock(&chip->als_mutex); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + int_time = tsl2591_als_time_to_fval(val); + if (int_time < 0) { + ret = int_time; + goto err_unlock; + } + ret = tsl2591_compatible_int_time(chip, int_time); + if (ret < 0) + goto err_unlock; + + chip->als_settings.als_int_time = int_time; + break; + case IIO_CHAN_INFO_CALIBSCALE: + gain = tsl2591_multiplier_to_gain(val); + if (gain < 0) { + ret = gain; + goto err_unlock; + } + ret = tsl2591_compatible_gain(chip, gain); + if (ret < 0) + goto err_unlock; + + chip->als_settings.als_gain = gain; + break; + default: + ret = -EINVAL; + goto err_unlock; + } + + ret = tsl2591_set_als_gain_int_time(chip); + +err_unlock: + mutex_unlock(&chip->als_mutex); + return ret; +} + +static int tsl2591_read_available(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(tsl2591_int_time_available); + *vals = tsl2591_int_time_available; + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_CALIBSCALE: + *length = ARRAY_SIZE(tsl2591_calibscale_available); + *vals = tsl2591_calibscale_available; + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int tsl2591_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, + int *val2) +{ + struct tsl2591_chip *chip = iio_priv(indio_dev); + struct i2c_client *client = chip->client; + int als_persist, int_time, period; + int ret; + + mutex_lock(&chip->als_mutex); + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + *val = chip->als_settings.als_upper_thresh; + break; + case IIO_EV_DIR_FALLING: + *val = chip->als_settings.als_lower_thresh; + break; + default: + ret = -EINVAL; + goto err_unlock; + } + ret = IIO_VAL_INT; + break; + case IIO_EV_INFO_PERIOD: + ret = i2c_smbus_read_byte_data(client, + TSL2591_CMD_NOP | TSL2591_PERSIST); + if (ret <= 0 || ret > TSL2591_PRST_ALS_INT_CYCLE_MAX) + goto err_unlock; + + als_persist = tsl2591_persist_cycle_to_lit(ret); + int_time = TSL2591_FVAL_TO_MSEC(chip->als_settings.als_int_time); + period = als_persist * (int_time * MSEC_PER_SEC); + + *val = period / USEC_PER_SEC; + *val2 = period % USEC_PER_SEC; + + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + break; + } + +err_unlock: + mutex_unlock(&chip->als_mutex); + return ret; +} + +static int tsl2591_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, + int val2) +{ + struct tsl2591_chip *chip = iio_priv(indio_dev); + int period, int_time, als_persist; + int ret; + + if (val < 0 || val2 < 0) + return -EINVAL; + + mutex_lock(&chip->als_mutex); + + switch (info) { + case IIO_EV_INFO_VALUE: + if (val > TSL2591_ALS_MAX_VALUE) { + ret = -EINVAL; + goto err_unlock; + } + + switch (dir) { + case IIO_EV_DIR_RISING: + ret = tsl2591_set_als_upper_threshold(chip, val); + if (ret < 0) + goto err_unlock; + break; + case IIO_EV_DIR_FALLING: + ret = tsl2591_set_als_lower_threshold(chip, val); + if (ret < 0) + goto err_unlock; + break; + default: + ret = -EINVAL; + goto err_unlock; + } + break; + case IIO_EV_INFO_PERIOD: + int_time = TSL2591_FVAL_TO_MSEC(chip->als_settings.als_int_time); + + period = ((val * MSEC_PER_SEC) + + (val2 / MSEC_PER_SEC)) / int_time; + + als_persist = tsl2591_persist_lit_to_cycle(period); + if (als_persist < 0) { + ret = -EINVAL; + goto err_unlock; + } + + ret = tsl2591_set_als_persist_cycle(chip, als_persist); + if (ret < 0) + goto err_unlock; + break; + default: + ret = -EINVAL; + break; + } + +err_unlock: + mutex_unlock(&chip->als_mutex); + return ret; +} + +static int tsl2591_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct tsl2591_chip *chip = iio_priv(indio_dev); + + return chip->events_enabled; +} + +static int tsl2591_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct tsl2591_chip *chip = iio_priv(indio_dev); + struct i2c_client *client = chip->client; + + if (state && !chip->events_enabled) { + chip->events_enabled = true; + pm_runtime_get_sync(&client->dev); + } else if (!state && chip->events_enabled) { + chip->events_enabled = false; + pm_runtime_put_autosuspend(&client->dev); + } + + return 0; +} + +static const struct iio_info tsl2591_info = { + .event_attrs = &tsl2591_event_attribute_group, + .read_raw = tsl2591_read_raw, + .write_raw = tsl2591_write_raw, + .read_avail = tsl2591_read_available, + .read_event_value = tsl2591_read_event_value, + .write_event_value = tsl2591_write_event_value, + .read_event_config = tsl2591_read_event_config, + .write_event_config = tsl2591_write_event_config, +}; + +static const struct iio_info tsl2591_info_no_irq = { + .read_raw = tsl2591_read_raw, + .write_raw = tsl2591_write_raw, + .read_avail = tsl2591_read_available, +}; + +static int tsl2591_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2591_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->als_mutex); + ret = tsl2591_set_power_state(chip, TSL2591_PWR_OFF); + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static int tsl2591_resume(struct device *dev) +{ + int power_state = TSL2591_PWR_ON | TSL2591_ENABLE_ALS; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2591_chip *chip = iio_priv(indio_dev); + int ret; + + if (chip->events_enabled) + power_state |= TSL2591_ENABLE_ALS_INT; + + mutex_lock(&chip->als_mutex); + ret = tsl2591_set_power_state(chip, power_state); + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(tsl2591_pm_ops, tsl2591_suspend, + tsl2591_resume, NULL); + +static irqreturn_t tsl2591_event_handler(int irq, void *private) +{ + struct iio_dev *dev_info = private; + struct tsl2591_chip *chip = iio_priv(dev_info); + struct i2c_client *client = chip->client; + + if (!chip->events_enabled) + return IRQ_NONE; + + iio_push_event(dev_info, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(dev_info)); + + /* Clear ALS irq */ + i2c_smbus_write_byte(client, TSL2591_CMD_SF_CALS_NPI); + + return IRQ_HANDLED; +} + +static int tsl2591_load_defaults(struct tsl2591_chip *chip) +{ + int ret; + + chip->als_settings.als_int_time = TSL2591_DEFAULT_ALS_INT_TIME; + chip->als_settings.als_gain = TSL2591_DEFAULT_ALS_GAIN; + chip->als_settings.als_lower_thresh = TSL2591_DEFAULT_ALS_LOWER_THRESH; + chip->als_settings.als_upper_thresh = TSL2591_DEFAULT_ALS_UPPER_THRESH; + + ret = tsl2591_set_als_gain_int_time(chip); + if (ret < 0) + return ret; + + ret = tsl2591_set_als_persist_cycle(chip, TSL2591_DEFAULT_ALS_PERSIST); + if (ret < 0) + return ret; + + ret = tsl2591_set_als_lower_threshold(chip, TSL2591_DEFAULT_ALS_LOWER_THRESH); + if (ret < 0) + return ret; + + ret = tsl2591_set_als_upper_threshold(chip, TSL2591_DEFAULT_ALS_UPPER_THRESH); + if (ret < 0) + return ret; + + return 0; +} + +static void tsl2591_chip_off(void *data) +{ + struct iio_dev *indio_dev = data; + struct tsl2591_chip *chip = iio_priv(indio_dev); + struct i2c_client *client = chip->client; + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + + tsl2591_set_power_state(chip, TSL2591_PWR_OFF); +} + +static int tsl2591_probe(struct i2c_client *client) +{ + struct tsl2591_chip *chip; + struct iio_dev *indio_dev; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, + "I2C smbus byte data functionality is not supported\n"); + return -EOPNOTSUPP; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + chip->client = client; + i2c_set_clientdata(client, indio_dev); + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, tsl2591_event_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "tsl2591_irq", indio_dev); + if (ret) + return ret; + indio_dev->info = &tsl2591_info; + } else { + indio_dev->info = &tsl2591_info_no_irq; + } + + mutex_init(&chip->als_mutex); + + ret = i2c_smbus_read_byte_data(client, + TSL2591_CMD_NOP | TSL2591_DEVICE_ID); + if (ret < 0) { + dev_err(&client->dev, + "Failed to read the device ID register\n"); + return ret; + } + ret = FIELD_GET(TSL2591_DEVICE_ID_MASK, ret); + if (ret != TSL2591_DEVICE_ID_VAL) { + dev_err(&client->dev, "Device ID: %#04x unknown\n", ret); + return -EINVAL; + } + + indio_dev->channels = tsl2591_channels; + indio_dev->num_channels = ARRAY_SIZE(tsl2591_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = chip->client->name; + chip->events_enabled = false; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + TSL2591_POWER_OFF_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + /* + * Add chip off to automatically managed path and disable runtime + * power management. This ensures that the chip power management + * is handled correctly on driver remove. tsl2591_chip_off() must be + * added to the managed path after pm runtime is enabled and before + * any error exit paths are met to ensure we're not left in a state + * of pm runtime not being disabled properly. + */ + ret = devm_add_action_or_reset(&client->dev, tsl2591_chip_off, + indio_dev); + if (ret < 0) + return -EINVAL; + + ret = tsl2591_load_defaults(chip); + if (ret < 0) { + dev_err(&client->dev, "Failed to load sensor defaults\n"); + return -EINVAL; + } + + ret = i2c_smbus_write_byte(client, TSL2591_CMD_SF_CALS_NPI); + if (ret < 0) { + dev_err(&client->dev, "Failed to clear als irq\n"); + return -EINVAL; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct of_device_id tsl2591_of_match[] = { + { .compatible = "amstaos,tsl2591"}, + { } +}; +MODULE_DEVICE_TABLE(of, tsl2591_of_match); + +static struct i2c_driver tsl2591_driver = { + .driver = { + .name = "tsl2591", + .pm = pm_ptr(&tsl2591_pm_ops), + .of_match_table = tsl2591_of_match, + }, + .probe = tsl2591_probe +}; +module_i2c_driver(tsl2591_driver); + +MODULE_AUTHOR("Joe Sandom <joe.g.sandom@gmail.com>"); +MODULE_DESCRIPTION("TAOS tsl2591 ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/tsl2772.c b/drivers/iio/light/tsl2772.c new file mode 100644 index 000000000..4486a1d9d --- /dev/null +++ b/drivers/iio/light/tsl2772.c @@ -0,0 +1,1956 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Device driver for monitoring ambient light intensity in (lux) and proximity + * detection (prox) within the TAOS TSL2571, TSL2671, TMD2671, TSL2771, TMD2771, + * TSL2572, TSL2672, TMD2672, TSL2772, and TMD2772 devices. + * + * Copyright (c) 2012, TAOS Corporation. + * Copyright (c) 2017-2018 Brian Masney <masneyb@onstation.org> + */ + +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/property.h> +#include <linux/slab.h> + +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/platform_data/tsl2772.h> +#include <linux/regulator/consumer.h> + +/* Cal defs */ +#define PROX_STAT_CAL 0 +#define PROX_STAT_SAMP 1 +#define MAX_SAMPLES_CAL 200 + +/* TSL2772 Device ID */ +#define TRITON_ID 0x00 +#define SWORDFISH_ID 0x30 +#define HALIBUT_ID 0x20 + +/* Lux calculation constants */ +#define TSL2772_LUX_CALC_OVER_FLOW 65535 + +/* + * TAOS Register definitions - Note: depending on device, some of these register + * are not used and the register address is benign. + */ + +/* Register offsets */ +#define TSL2772_MAX_CONFIG_REG 16 + +/* Device Registers and Masks */ +#define TSL2772_CNTRL 0x00 +#define TSL2772_ALS_TIME 0X01 +#define TSL2772_PRX_TIME 0x02 +#define TSL2772_WAIT_TIME 0x03 +#define TSL2772_ALS_MINTHRESHLO 0X04 +#define TSL2772_ALS_MINTHRESHHI 0X05 +#define TSL2772_ALS_MAXTHRESHLO 0X06 +#define TSL2772_ALS_MAXTHRESHHI 0X07 +#define TSL2772_PRX_MINTHRESHLO 0X08 +#define TSL2772_PRX_MINTHRESHHI 0X09 +#define TSL2772_PRX_MAXTHRESHLO 0X0A +#define TSL2772_PRX_MAXTHRESHHI 0X0B +#define TSL2772_PERSISTENCE 0x0C +#define TSL2772_ALS_PRX_CONFIG 0x0D +#define TSL2772_PRX_COUNT 0x0E +#define TSL2772_GAIN 0x0F +#define TSL2772_NOTUSED 0x10 +#define TSL2772_REVID 0x11 +#define TSL2772_CHIPID 0x12 +#define TSL2772_STATUS 0x13 +#define TSL2772_ALS_CHAN0LO 0x14 +#define TSL2772_ALS_CHAN0HI 0x15 +#define TSL2772_ALS_CHAN1LO 0x16 +#define TSL2772_ALS_CHAN1HI 0x17 +#define TSL2772_PRX_LO 0x18 +#define TSL2772_PRX_HI 0x19 + +/* tsl2772 cmd reg masks */ +#define TSL2772_CMD_REG 0x80 +#define TSL2772_CMD_SPL_FN 0x60 +#define TSL2772_CMD_REPEAT_PROTO 0x00 +#define TSL2772_CMD_AUTOINC_PROTO 0x20 + +#define TSL2772_CMD_PROX_INT_CLR 0X05 +#define TSL2772_CMD_ALS_INT_CLR 0x06 +#define TSL2772_CMD_PROXALS_INT_CLR 0X07 + +/* tsl2772 cntrl reg masks */ +#define TSL2772_CNTL_ADC_ENBL 0x02 +#define TSL2772_CNTL_PWR_ON 0x01 + +/* tsl2772 status reg masks */ +#define TSL2772_STA_ADC_VALID 0x01 +#define TSL2772_STA_PRX_VALID 0x02 +#define TSL2772_STA_ADC_PRX_VALID (TSL2772_STA_ADC_VALID | \ + TSL2772_STA_PRX_VALID) +#define TSL2772_STA_ALS_INTR 0x10 +#define TSL2772_STA_PRX_INTR 0x20 + +/* tsl2772 cntrl reg masks */ +#define TSL2772_CNTL_REG_CLEAR 0x00 +#define TSL2772_CNTL_PROX_INT_ENBL 0X20 +#define TSL2772_CNTL_ALS_INT_ENBL 0X10 +#define TSL2772_CNTL_WAIT_TMR_ENBL 0X08 +#define TSL2772_CNTL_PROX_DET_ENBL 0X04 +#define TSL2772_CNTL_PWRON 0x01 +#define TSL2772_CNTL_ALSPON_ENBL 0x03 +#define TSL2772_CNTL_INTALSPON_ENBL 0x13 +#define TSL2772_CNTL_PROXPON_ENBL 0x0F +#define TSL2772_CNTL_INTPROXPON_ENBL 0x2F + +#define TSL2772_ALS_GAIN_TRIM_MIN 250 +#define TSL2772_ALS_GAIN_TRIM_MAX 4000 + +#define TSL2772_MAX_PROX_LEDS 2 + +#define TSL2772_BOOT_MIN_SLEEP_TIME 10000 +#define TSL2772_BOOT_MAX_SLEEP_TIME 28000 + +/* Device family members */ +enum { + tsl2571, + tsl2671, + tmd2671, + tsl2771, + tmd2771, + tsl2572, + tsl2672, + tmd2672, + tsl2772, + tmd2772, + apds9900, + apds9930, +}; + +enum { + TSL2772_CHIP_UNKNOWN = 0, + TSL2772_CHIP_WORKING = 1, + TSL2772_CHIP_SUSPENDED = 2 +}; + +enum { + TSL2772_SUPPLY_VDD = 0, + TSL2772_SUPPLY_VDDIO = 1, + TSL2772_NUM_SUPPLIES = 2 +}; + +/* Per-device data */ +struct tsl2772_als_info { + u16 als_ch0; + u16 als_ch1; + u16 lux; +}; + +struct tsl2772_chip_info { + int chan_table_elements; + struct iio_chan_spec channel_with_events[4]; + struct iio_chan_spec channel_without_events[4]; + const struct iio_info *info; +}; + +static const int tsl2772_led_currents[][2] = { + { 100000, TSL2772_100_mA }, + { 50000, TSL2772_50_mA }, + { 25000, TSL2772_25_mA }, + { 13000, TSL2772_13_mA }, + { 0, 0 } +}; + +struct tsl2772_chip { + kernel_ulong_t id; + struct mutex prox_mutex; + struct mutex als_mutex; + struct i2c_client *client; + struct regulator_bulk_data supplies[TSL2772_NUM_SUPPLIES]; + u16 prox_data; + struct tsl2772_als_info als_cur_info; + struct tsl2772_settings settings; + struct tsl2772_platform_data *pdata; + int als_gain_time_scale; + int als_saturation; + int tsl2772_chip_status; + u8 tsl2772_config[TSL2772_MAX_CONFIG_REG]; + const struct tsl2772_chip_info *chip_info; + const struct iio_info *info; + s64 event_timestamp; + /* + * This structure is intentionally large to accommodate + * updates via sysfs. + * Sized to 9 = max 8 segments + 1 termination segment + */ + struct tsl2772_lux tsl2772_device_lux[TSL2772_MAX_LUX_TABLE_SIZE]; +}; + +/* + * Different devices require different coefficients, and these numbers were + * derived from the 'Lux Equation' section of the various device datasheets. + * All of these coefficients assume a Glass Attenuation (GA) factor of 1. + * The coefficients are multiplied by 1000 to avoid floating point operations. + * The two rows in each table correspond to the Lux1 and Lux2 equations from + * the datasheets. + */ +static const struct tsl2772_lux tsl2x71_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = { + { 53000, 106000 }, + { 31800, 53000 }, + { 0, 0 }, +}; + +static const struct tsl2772_lux tmd2x71_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = { + { 24000, 48000 }, + { 14400, 24000 }, + { 0, 0 }, +}; + +static const struct tsl2772_lux tsl2x72_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = { + { 60000, 112200 }, + { 37800, 60000 }, + { 0, 0 }, +}; + +static const struct tsl2772_lux tmd2x72_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = { + { 20000, 35000 }, + { 12600, 20000 }, + { 0, 0 }, +}; + +static const struct tsl2772_lux apds9900_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = { + { 52000, 115960 }, + { 36400, 73840 }, + { 0, 0 }, +}; + +static const struct tsl2772_lux apds9930_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = { + { 52000, 96824 }, + { 38792, 67132 }, + { 0, 0 }, +}; + +static const struct tsl2772_lux *tsl2772_default_lux_table_group[] = { + [tsl2571] = tsl2x71_lux_table, + [tsl2671] = tsl2x71_lux_table, + [tmd2671] = tmd2x71_lux_table, + [tsl2771] = tsl2x71_lux_table, + [tmd2771] = tmd2x71_lux_table, + [tsl2572] = tsl2x72_lux_table, + [tsl2672] = tsl2x72_lux_table, + [tmd2672] = tmd2x72_lux_table, + [tsl2772] = tsl2x72_lux_table, + [tmd2772] = tmd2x72_lux_table, + [apds9900] = apds9900_lux_table, + [apds9930] = apds9930_lux_table, +}; + +static const struct tsl2772_settings tsl2772_default_settings = { + .als_time = 255, /* 2.72 / 2.73 ms */ + .als_gain = 0, + .prox_time = 255, /* 2.72 / 2.73 ms */ + .prox_gain = 0, + .wait_time = 255, + .als_prox_config = 0, + .als_gain_trim = 1000, + .als_cal_target = 150, + .als_persistence = 1, + .als_interrupt_en = false, + .als_thresh_low = 200, + .als_thresh_high = 256, + .prox_persistence = 1, + .prox_interrupt_en = false, + .prox_thres_low = 0, + .prox_thres_high = 512, + .prox_max_samples_cal = 30, + .prox_pulse_count = 8, + .prox_diode = TSL2772_DIODE1, + .prox_power = TSL2772_100_mA +}; + +static const s16 tsl2772_als_gain[] = { + 1, + 8, + 16, + 120 +}; + +static const s16 tsl2772_prox_gain[] = { + 1, + 2, + 4, + 8 +}; + +static const int tsl2772_int_time_avail[][6] = { + [tsl2571] = { 0, 2720, 0, 2720, 0, 696000 }, + [tsl2671] = { 0, 2720, 0, 2720, 0, 696000 }, + [tmd2671] = { 0, 2720, 0, 2720, 0, 696000 }, + [tsl2771] = { 0, 2720, 0, 2720, 0, 696000 }, + [tmd2771] = { 0, 2720, 0, 2720, 0, 696000 }, + [tsl2572] = { 0, 2730, 0, 2730, 0, 699000 }, + [tsl2672] = { 0, 2730, 0, 2730, 0, 699000 }, + [tmd2672] = { 0, 2730, 0, 2730, 0, 699000 }, + [tsl2772] = { 0, 2730, 0, 2730, 0, 699000 }, + [tmd2772] = { 0, 2730, 0, 2730, 0, 699000 }, + [apds9900] = { 0, 2720, 0, 2720, 0, 696000 }, + [apds9930] = { 0, 2730, 0, 2730, 0, 699000 }, +}; + +static int tsl2772_int_calibscale_avail[] = { 1, 8, 16, 120 }; + +static int tsl2772_prox_calibscale_avail[] = { 1, 2, 4, 8 }; + +/* Channel variations */ +enum { + ALS, + PRX, + ALSPRX, + PRX2, + ALSPRX2, +}; + +static const u8 device_channel_config[] = { + [tsl2571] = ALS, + [tsl2671] = PRX, + [tmd2671] = PRX, + [tsl2771] = ALSPRX, + [tmd2771] = ALSPRX, + [tsl2572] = ALS, + [tsl2672] = PRX2, + [tmd2672] = PRX2, + [tsl2772] = ALSPRX2, + [tmd2772] = ALSPRX2, + [apds9900] = ALSPRX, + [apds9930] = ALSPRX2, +}; + +static int tsl2772_read_status(struct tsl2772_chip *chip) +{ + int ret; + + ret = i2c_smbus_read_byte_data(chip->client, + TSL2772_CMD_REG | TSL2772_STATUS); + if (ret < 0) + dev_err(&chip->client->dev, + "%s: failed to read STATUS register: %d\n", __func__, + ret); + + return ret; +} + +static int tsl2772_write_control_reg(struct tsl2772_chip *chip, u8 data) +{ + int ret; + + ret = i2c_smbus_write_byte_data(chip->client, + TSL2772_CMD_REG | TSL2772_CNTRL, data); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to write to control register %x: %d\n", + __func__, data, ret); + } + + return ret; +} + +static int tsl2772_read_autoinc_regs(struct tsl2772_chip *chip, int lower_reg, + int upper_reg) +{ + u8 buf[2]; + int ret; + + ret = i2c_smbus_write_byte(chip->client, + TSL2772_CMD_REG | TSL2772_CMD_AUTOINC_PROTO | + lower_reg); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to enable auto increment protocol: %d\n", + __func__, ret); + return ret; + } + + ret = i2c_smbus_read_byte_data(chip->client, + TSL2772_CMD_REG | lower_reg); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to read from register %x: %d\n", __func__, + lower_reg, ret); + return ret; + } + buf[0] = ret; + + ret = i2c_smbus_read_byte_data(chip->client, + TSL2772_CMD_REG | upper_reg); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to read from register %x: %d\n", __func__, + upper_reg, ret); + return ret; + } + buf[1] = ret; + + ret = i2c_smbus_write_byte(chip->client, + TSL2772_CMD_REG | TSL2772_CMD_REPEAT_PROTO | + lower_reg); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to enable repeated byte protocol: %d\n", + __func__, ret); + return ret; + } + + return le16_to_cpup((const __le16 *)&buf[0]); +} + +/** + * tsl2772_get_lux() - Reads and calculates current lux value. + * @indio_dev: pointer to IIO device + * + * The raw ch0 and ch1 values of the ambient light sensed in the last + * integration cycle are read from the device. The raw values are multiplied + * by a device-specific scale factor, and divided by the integration time and + * device gain. The code supports multiple lux equations through the lux table + * coefficients. A lux gain trim is applied to each lux equation, and then the + * maximum lux within the interval 0..65535 is selected. + */ +static int tsl2772_get_lux(struct iio_dev *indio_dev) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + struct tsl2772_lux *p; + int max_lux, ret; + bool overflow; + + mutex_lock(&chip->als_mutex); + + if (chip->tsl2772_chip_status != TSL2772_CHIP_WORKING) { + dev_err(&chip->client->dev, "%s: device is not enabled\n", + __func__); + ret = -EBUSY; + goto out_unlock; + } + + ret = tsl2772_read_status(chip); + if (ret < 0) + goto out_unlock; + + if (!(ret & TSL2772_STA_ADC_VALID)) { + dev_err(&chip->client->dev, + "%s: data not valid yet\n", __func__); + ret = chip->als_cur_info.lux; /* return LAST VALUE */ + goto out_unlock; + } + + ret = tsl2772_read_autoinc_regs(chip, TSL2772_ALS_CHAN0LO, + TSL2772_ALS_CHAN0HI); + if (ret < 0) + goto out_unlock; + chip->als_cur_info.als_ch0 = ret; + + ret = tsl2772_read_autoinc_regs(chip, TSL2772_ALS_CHAN1LO, + TSL2772_ALS_CHAN1HI); + if (ret < 0) + goto out_unlock; + chip->als_cur_info.als_ch1 = ret; + + if (chip->als_cur_info.als_ch0 >= chip->als_saturation) { + max_lux = TSL2772_LUX_CALC_OVER_FLOW; + goto update_struct_with_max_lux; + } + + if (!chip->als_cur_info.als_ch0) { + /* have no data, so return LAST VALUE */ + ret = chip->als_cur_info.lux; + goto out_unlock; + } + + max_lux = 0; + overflow = false; + for (p = (struct tsl2772_lux *)chip->tsl2772_device_lux; p->ch0 != 0; + p++) { + int lux; + + lux = ((chip->als_cur_info.als_ch0 * p->ch0) - + (chip->als_cur_info.als_ch1 * p->ch1)) / + chip->als_gain_time_scale; + + /* + * The als_gain_trim can have a value within the range 250..4000 + * and is a multiplier for the lux. A trim of 1000 makes no + * changes to the lux, less than 1000 scales it down, and + * greater than 1000 scales it up. + */ + lux = (lux * chip->settings.als_gain_trim) / 1000; + + if (lux > TSL2772_LUX_CALC_OVER_FLOW) { + overflow = true; + continue; + } + + max_lux = max(max_lux, lux); + } + + if (overflow && max_lux == 0) + max_lux = TSL2772_LUX_CALC_OVER_FLOW; + +update_struct_with_max_lux: + chip->als_cur_info.lux = max_lux; + ret = max_lux; + +out_unlock: + mutex_unlock(&chip->als_mutex); + + return ret; +} + +/** + * tsl2772_get_prox() - Reads proximity data registers and updates + * chip->prox_data. + * + * @indio_dev: pointer to IIO device + */ +static int tsl2772_get_prox(struct iio_dev *indio_dev) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->prox_mutex); + + ret = tsl2772_read_status(chip); + if (ret < 0) + goto prox_poll_err; + + switch (chip->id) { + case tsl2571: + case tsl2671: + case tmd2671: + case tsl2771: + case tmd2771: + if (!(ret & TSL2772_STA_ADC_VALID)) { + ret = -EINVAL; + goto prox_poll_err; + } + break; + case tsl2572: + case tsl2672: + case tmd2672: + case tsl2772: + case tmd2772: + case apds9900: + case apds9930: + if (!(ret & TSL2772_STA_PRX_VALID)) { + ret = -EINVAL; + goto prox_poll_err; + } + break; + } + + ret = tsl2772_read_autoinc_regs(chip, TSL2772_PRX_LO, TSL2772_PRX_HI); + if (ret < 0) + goto prox_poll_err; + chip->prox_data = ret; + +prox_poll_err: + mutex_unlock(&chip->prox_mutex); + + return ret; +} + +static int tsl2772_read_prox_led_current(struct tsl2772_chip *chip) +{ + struct device *dev = &chip->client->dev; + int ret, tmp, i; + + ret = device_property_read_u32(dev, "led-max-microamp", &tmp); + if (ret < 0) + return ret; + + for (i = 0; tsl2772_led_currents[i][0] != 0; i++) { + if (tmp == tsl2772_led_currents[i][0]) { + chip->settings.prox_power = tsl2772_led_currents[i][1]; + return 0; + } + } + + dev_err(dev, "Invalid value %d for led-max-microamp\n", tmp); + + return -EINVAL; +} + +static int tsl2772_read_prox_diodes(struct tsl2772_chip *chip) +{ + struct device *dev = &chip->client->dev; + int i, ret, num_leds, prox_diode_mask; + u32 leds[TSL2772_MAX_PROX_LEDS]; + + ret = device_property_count_u32(dev, "amstaos,proximity-diodes"); + if (ret < 0) + return ret; + + num_leds = ret; + if (num_leds > TSL2772_MAX_PROX_LEDS) + num_leds = TSL2772_MAX_PROX_LEDS; + + ret = device_property_read_u32_array(dev, "amstaos,proximity-diodes", leds, num_leds); + if (ret < 0) { + dev_err(dev, "Invalid value for amstaos,proximity-diodes: %d.\n", ret); + return ret; + } + + prox_diode_mask = 0; + for (i = 0; i < num_leds; i++) { + if (leds[i] == 0) + prox_diode_mask |= TSL2772_DIODE0; + else if (leds[i] == 1) + prox_diode_mask |= TSL2772_DIODE1; + else { + dev_err(dev, "Invalid value %d in amstaos,proximity-diodes.\n", leds[i]); + return -EINVAL; + } + } + chip->settings.prox_diode = prox_diode_mask; + + return 0; +} + +static void tsl2772_parse_dt(struct tsl2772_chip *chip) +{ + tsl2772_read_prox_led_current(chip); + tsl2772_read_prox_diodes(chip); +} + +/** + * tsl2772_defaults() - Populates the device nominal operating parameters + * with those provided by a 'platform' data struct or + * with prefined defaults. + * + * @chip: pointer to device structure. + */ +static void tsl2772_defaults(struct tsl2772_chip *chip) +{ + /* If Operational settings defined elsewhere.. */ + if (chip->pdata && chip->pdata->platform_default_settings) + memcpy(&chip->settings, chip->pdata->platform_default_settings, + sizeof(tsl2772_default_settings)); + else + memcpy(&chip->settings, &tsl2772_default_settings, + sizeof(tsl2772_default_settings)); + + /* Load up the proper lux table. */ + if (chip->pdata && chip->pdata->platform_lux_table[0].ch0 != 0) + memcpy(chip->tsl2772_device_lux, + chip->pdata->platform_lux_table, + sizeof(chip->pdata->platform_lux_table)); + else + memcpy(chip->tsl2772_device_lux, + tsl2772_default_lux_table_group[chip->id], + TSL2772_DEFAULT_TABLE_BYTES); + + tsl2772_parse_dt(chip); +} + +/** + * tsl2772_als_calibrate() - Obtain single reading and calculate + * the als_gain_trim. + * + * @indio_dev: pointer to IIO device + */ +static int tsl2772_als_calibrate(struct iio_dev *indio_dev) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + int ret, lux_val; + + ret = i2c_smbus_read_byte_data(chip->client, + TSL2772_CMD_REG | TSL2772_CNTRL); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to read from the CNTRL register\n", + __func__); + return ret; + } + + if ((ret & (TSL2772_CNTL_ADC_ENBL | TSL2772_CNTL_PWR_ON)) + != (TSL2772_CNTL_ADC_ENBL | TSL2772_CNTL_PWR_ON)) { + dev_err(&chip->client->dev, + "%s: Device is not powered on and/or ADC is not enabled\n", + __func__); + return -EINVAL; + } else if ((ret & TSL2772_STA_ADC_VALID) != TSL2772_STA_ADC_VALID) { + dev_err(&chip->client->dev, + "%s: The two ADC channels have not completed an integration cycle\n", + __func__); + return -ENODATA; + } + + lux_val = tsl2772_get_lux(indio_dev); + if (lux_val < 0) { + dev_err(&chip->client->dev, + "%s: failed to get lux\n", __func__); + return lux_val; + } + if (lux_val == 0) + return -ERANGE; + + ret = (chip->settings.als_cal_target * chip->settings.als_gain_trim) / + lux_val; + if (ret < TSL2772_ALS_GAIN_TRIM_MIN || ret > TSL2772_ALS_GAIN_TRIM_MAX) + return -ERANGE; + + chip->settings.als_gain_trim = ret; + + return ret; +} + +static void tsl2772_disable_regulators_action(void *_data) +{ + struct tsl2772_chip *chip = _data; + + regulator_bulk_disable(ARRAY_SIZE(chip->supplies), chip->supplies); +} + +static int tsl2772_chip_on(struct iio_dev *indio_dev) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + int ret, i, als_count, als_time_us; + u8 *dev_reg, reg_val; + + /* Non calculated parameters */ + chip->tsl2772_config[TSL2772_ALS_TIME] = chip->settings.als_time; + chip->tsl2772_config[TSL2772_PRX_TIME] = chip->settings.prox_time; + chip->tsl2772_config[TSL2772_WAIT_TIME] = chip->settings.wait_time; + chip->tsl2772_config[TSL2772_ALS_PRX_CONFIG] = + chip->settings.als_prox_config; + + chip->tsl2772_config[TSL2772_ALS_MINTHRESHLO] = + (chip->settings.als_thresh_low) & 0xFF; + chip->tsl2772_config[TSL2772_ALS_MINTHRESHHI] = + (chip->settings.als_thresh_low >> 8) & 0xFF; + chip->tsl2772_config[TSL2772_ALS_MAXTHRESHLO] = + (chip->settings.als_thresh_high) & 0xFF; + chip->tsl2772_config[TSL2772_ALS_MAXTHRESHHI] = + (chip->settings.als_thresh_high >> 8) & 0xFF; + chip->tsl2772_config[TSL2772_PERSISTENCE] = + (chip->settings.prox_persistence & 0xFF) << 4 | + (chip->settings.als_persistence & 0xFF); + + chip->tsl2772_config[TSL2772_PRX_COUNT] = + chip->settings.prox_pulse_count; + chip->tsl2772_config[TSL2772_PRX_MINTHRESHLO] = + (chip->settings.prox_thres_low) & 0xFF; + chip->tsl2772_config[TSL2772_PRX_MINTHRESHHI] = + (chip->settings.prox_thres_low >> 8) & 0xFF; + chip->tsl2772_config[TSL2772_PRX_MAXTHRESHLO] = + (chip->settings.prox_thres_high) & 0xFF; + chip->tsl2772_config[TSL2772_PRX_MAXTHRESHHI] = + (chip->settings.prox_thres_high >> 8) & 0xFF; + + /* and make sure we're not already on */ + if (chip->tsl2772_chip_status == TSL2772_CHIP_WORKING) { + /* if forcing a register update - turn off, then on */ + dev_info(&chip->client->dev, "device is already enabled\n"); + return -EINVAL; + } + + /* Set the gain based on tsl2772_settings struct */ + chip->tsl2772_config[TSL2772_GAIN] = + (chip->settings.als_gain & 0xFF) | + ((chip->settings.prox_gain & 0xFF) << 2) | + (chip->settings.prox_diode << 4) | + (chip->settings.prox_power << 6); + + /* set chip time scaling and saturation */ + als_count = 256 - chip->settings.als_time; + als_time_us = als_count * tsl2772_int_time_avail[chip->id][3]; + chip->als_saturation = als_count * 768; /* 75% of full scale */ + chip->als_gain_time_scale = als_time_us * + tsl2772_als_gain[chip->settings.als_gain]; + + /* + * TSL2772 Specific power-on / adc enable sequence + * Power on the device 1st. + */ + ret = tsl2772_write_control_reg(chip, TSL2772_CNTL_PWR_ON); + if (ret < 0) + return ret; + + /* + * Use the following shadow copy for our delay before enabling ADC. + * Write all the registers. + */ + for (i = 0, dev_reg = chip->tsl2772_config; + i < TSL2772_MAX_CONFIG_REG; i++) { + int reg = TSL2772_CMD_REG + i; + + ret = i2c_smbus_write_byte_data(chip->client, reg, + *dev_reg++); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to write to register %x: %d\n", + __func__, reg, ret); + return ret; + } + } + + /* Power-on settling time */ + usleep_range(3000, 3500); + + reg_val = TSL2772_CNTL_PWR_ON | TSL2772_CNTL_ADC_ENBL | + TSL2772_CNTL_PROX_DET_ENBL; + if (chip->settings.als_interrupt_en) + reg_val |= TSL2772_CNTL_ALS_INT_ENBL; + if (chip->settings.prox_interrupt_en) + reg_val |= TSL2772_CNTL_PROX_INT_ENBL; + + ret = tsl2772_write_control_reg(chip, reg_val); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte(chip->client, + TSL2772_CMD_REG | TSL2772_CMD_SPL_FN | + TSL2772_CMD_PROXALS_INT_CLR); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to clear interrupt status: %d\n", + __func__, ret); + return ret; + } + + chip->tsl2772_chip_status = TSL2772_CHIP_WORKING; + + return ret; +} + +static int tsl2772_chip_off(struct iio_dev *indio_dev) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + + /* turn device off */ + chip->tsl2772_chip_status = TSL2772_CHIP_SUSPENDED; + return tsl2772_write_control_reg(chip, 0x00); +} + +static void tsl2772_chip_off_action(void *data) +{ + struct iio_dev *indio_dev = data; + + tsl2772_chip_off(indio_dev); +} + +/** + * tsl2772_invoke_change - power cycle the device to implement the user + * parameters + * @indio_dev: pointer to IIO device + * + * Obtain and lock both ALS and PROX resources, determine and save device state + * (On/Off), cycle device to implement updated parameter, put device back into + * proper state, and unlock resource. + */ +static int tsl2772_invoke_change(struct iio_dev *indio_dev) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + int device_status = chip->tsl2772_chip_status; + int ret; + + mutex_lock(&chip->als_mutex); + mutex_lock(&chip->prox_mutex); + + if (device_status == TSL2772_CHIP_WORKING) { + ret = tsl2772_chip_off(indio_dev); + if (ret < 0) + goto unlock; + } + + ret = tsl2772_chip_on(indio_dev); + +unlock: + mutex_unlock(&chip->prox_mutex); + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static int tsl2772_prox_cal(struct iio_dev *indio_dev) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + int prox_history[MAX_SAMPLES_CAL + 1]; + int i, ret, mean, max, sample_sum; + + if (chip->settings.prox_max_samples_cal < 1 || + chip->settings.prox_max_samples_cal > MAX_SAMPLES_CAL) + return -EINVAL; + + for (i = 0; i < chip->settings.prox_max_samples_cal; i++) { + usleep_range(15000, 17500); + ret = tsl2772_get_prox(indio_dev); + if (ret < 0) + return ret; + + prox_history[i] = chip->prox_data; + } + + sample_sum = 0; + max = INT_MIN; + for (i = 0; i < chip->settings.prox_max_samples_cal; i++) { + sample_sum += prox_history[i]; + max = max(max, prox_history[i]); + } + mean = sample_sum / chip->settings.prox_max_samples_cal; + + chip->settings.prox_thres_high = (max << 1) - mean; + + return tsl2772_invoke_change(indio_dev); +} + +static int tsl2772_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_INTENSITY) { + *length = ARRAY_SIZE(tsl2772_int_calibscale_avail); + *vals = tsl2772_int_calibscale_avail; + } else { + *length = ARRAY_SIZE(tsl2772_prox_calibscale_avail); + *vals = tsl2772_prox_calibscale_avail; + } + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_INT_TIME: + *length = ARRAY_SIZE(tsl2772_int_time_avail[chip->id]); + *vals = tsl2772_int_time_avail[chip->id]; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_RANGE; + } + + return -EINVAL; +} + +static ssize_t in_illuminance0_target_input_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tsl2772_chip *chip = iio_priv(dev_to_iio_dev(dev)); + + return scnprintf(buf, PAGE_SIZE, "%d\n", chip->settings.als_cal_target); +} + +static ssize_t in_illuminance0_target_input_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2772_chip *chip = iio_priv(indio_dev); + u16 value; + int ret; + + if (kstrtou16(buf, 0, &value)) + return -EINVAL; + + chip->settings.als_cal_target = value; + ret = tsl2772_invoke_change(indio_dev); + if (ret < 0) + return ret; + + return len; +} + +static ssize_t in_illuminance0_calibrate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + bool value; + int ret; + + if (kstrtobool(buf, &value) || !value) + return -EINVAL; + + ret = tsl2772_als_calibrate(indio_dev); + if (ret < 0) + return ret; + + ret = tsl2772_invoke_change(indio_dev); + if (ret < 0) + return ret; + + return len; +} + +static ssize_t in_illuminance0_lux_table_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tsl2772_chip *chip = iio_priv(dev_to_iio_dev(dev)); + int i = 0; + int offset = 0; + + while (i < TSL2772_MAX_LUX_TABLE_SIZE) { + offset += scnprintf(buf + offset, PAGE_SIZE - offset, "%u,%u,", + chip->tsl2772_device_lux[i].ch0, + chip->tsl2772_device_lux[i].ch1); + if (chip->tsl2772_device_lux[i].ch0 == 0) { + /* + * We just printed the first "0" entry. + * Now get rid of the extra "," and break. + */ + offset--; + break; + } + i++; + } + + offset += scnprintf(buf + offset, PAGE_SIZE - offset, "\n"); + return offset; +} + +static ssize_t in_illuminance0_lux_table_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2772_chip *chip = iio_priv(indio_dev); + int value[ARRAY_SIZE(chip->tsl2772_device_lux) * 2 + 1]; + int n, ret; + + get_options(buf, ARRAY_SIZE(value), value); + + /* + * We now have an array of ints starting at value[1], and + * enumerated by value[0]. + * We expect each group of two ints to be one table entry, + * and the last table entry is all 0. + */ + n = value[0]; + if ((n % 2) || n < 4 || + n > ((ARRAY_SIZE(chip->tsl2772_device_lux) - 1) * 2)) + return -EINVAL; + + if ((value[(n - 1)] | value[n]) != 0) + return -EINVAL; + + if (chip->tsl2772_chip_status == TSL2772_CHIP_WORKING) { + ret = tsl2772_chip_off(indio_dev); + if (ret < 0) + return ret; + } + + /* Zero out the table */ + memset(chip->tsl2772_device_lux, 0, sizeof(chip->tsl2772_device_lux)); + memcpy(chip->tsl2772_device_lux, &value[1], (value[0] * 4)); + + ret = tsl2772_invoke_change(indio_dev); + if (ret < 0) + return ret; + + return len; +} + +static ssize_t in_proximity0_calibrate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + bool value; + int ret; + + if (kstrtobool(buf, &value) || !value) + return -EINVAL; + + ret = tsl2772_prox_cal(indio_dev); + if (ret < 0) + return ret; + + ret = tsl2772_invoke_change(indio_dev); + if (ret < 0) + return ret; + + return len; +} + +static int tsl2772_read_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + + if (chan->type == IIO_INTENSITY) + return chip->settings.als_interrupt_en; + else + return chip->settings.prox_interrupt_en; +} + +static int tsl2772_write_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool val) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + + if (chan->type == IIO_INTENSITY) + chip->settings.als_interrupt_en = val; + else + chip->settings.prox_interrupt_en = val; + + return tsl2772_invoke_change(indio_dev); +} + +static int tsl2772_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + int ret = -EINVAL, count, persistence; + u8 time; + + switch (info) { + case IIO_EV_INFO_VALUE: + if (chan->type == IIO_INTENSITY) { + switch (dir) { + case IIO_EV_DIR_RISING: + chip->settings.als_thresh_high = val; + ret = 0; + break; + case IIO_EV_DIR_FALLING: + chip->settings.als_thresh_low = val; + ret = 0; + break; + default: + break; + } + } else { + switch (dir) { + case IIO_EV_DIR_RISING: + chip->settings.prox_thres_high = val; + ret = 0; + break; + case IIO_EV_DIR_FALLING: + chip->settings.prox_thres_low = val; + ret = 0; + break; + default: + break; + } + } + break; + case IIO_EV_INFO_PERIOD: + if (chan->type == IIO_INTENSITY) + time = chip->settings.als_time; + else + time = chip->settings.prox_time; + + count = 256 - time; + persistence = ((val * 1000000) + val2) / + (count * tsl2772_int_time_avail[chip->id][3]); + + if (chan->type == IIO_INTENSITY) { + /* ALS filter values are 1, 2, 3, 5, 10, 15, ..., 60 */ + if (persistence > 3) + persistence = (persistence / 5) + 3; + + chip->settings.als_persistence = persistence; + } else { + chip->settings.prox_persistence = persistence; + } + + ret = 0; + break; + default: + break; + } + + if (ret < 0) + return ret; + + return tsl2772_invoke_change(indio_dev); +} + +static int tsl2772_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + int filter_delay, persistence; + u8 time; + + switch (info) { + case IIO_EV_INFO_VALUE: + if (chan->type == IIO_INTENSITY) { + switch (dir) { + case IIO_EV_DIR_RISING: + *val = chip->settings.als_thresh_high; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + *val = chip->settings.als_thresh_low; + return IIO_VAL_INT; + default: + return -EINVAL; + } + } else { + switch (dir) { + case IIO_EV_DIR_RISING: + *val = chip->settings.prox_thres_high; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + *val = chip->settings.prox_thres_low; + return IIO_VAL_INT; + default: + return -EINVAL; + } + } + break; + case IIO_EV_INFO_PERIOD: + if (chan->type == IIO_INTENSITY) { + time = chip->settings.als_time; + persistence = chip->settings.als_persistence; + + /* ALS filter values are 1, 2, 3, 5, 10, 15, ..., 60 */ + if (persistence > 3) + persistence = (persistence - 3) * 5; + } else { + time = chip->settings.prox_time; + persistence = chip->settings.prox_persistence; + } + + filter_delay = persistence * (256 - time) * + tsl2772_int_time_avail[chip->id][3]; + + *val = filter_delay / 1000000; + *val2 = filter_delay % 1000000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int tsl2772_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + tsl2772_get_lux(indio_dev); + *val = chip->als_cur_info.lux; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_INTENSITY: + tsl2772_get_lux(indio_dev); + if (chan->channel == 0) + *val = chip->als_cur_info.als_ch0; + else + *val = chip->als_cur_info.als_ch1; + return IIO_VAL_INT; + case IIO_PROXIMITY: + tsl2772_get_prox(indio_dev); + *val = chip->prox_data; + return IIO_VAL_INT; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_INTENSITY) + *val = tsl2772_als_gain[chip->settings.als_gain]; + else + *val = tsl2772_prox_gain[chip->settings.prox_gain]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + *val = chip->settings.als_gain_trim; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = (256 - chip->settings.als_time) * + tsl2772_int_time_avail[chip->id][3]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int tsl2772_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_INTENSITY) { + switch (val) { + case 1: + chip->settings.als_gain = 0; + break; + case 8: + chip->settings.als_gain = 1; + break; + case 16: + chip->settings.als_gain = 2; + break; + case 120: + chip->settings.als_gain = 3; + break; + default: + return -EINVAL; + } + } else { + switch (val) { + case 1: + chip->settings.prox_gain = 0; + break; + case 2: + chip->settings.prox_gain = 1; + break; + case 4: + chip->settings.prox_gain = 2; + break; + case 8: + chip->settings.prox_gain = 3; + break; + default: + return -EINVAL; + } + } + break; + case IIO_CHAN_INFO_CALIBBIAS: + if (val < TSL2772_ALS_GAIN_TRIM_MIN || + val > TSL2772_ALS_GAIN_TRIM_MAX) + return -EINVAL; + + chip->settings.als_gain_trim = val; + break; + case IIO_CHAN_INFO_INT_TIME: + if (val != 0 || val2 < tsl2772_int_time_avail[chip->id][1] || + val2 > tsl2772_int_time_avail[chip->id][5]) + return -EINVAL; + + chip->settings.als_time = 256 - + (val2 / tsl2772_int_time_avail[chip->id][3]); + break; + default: + return -EINVAL; + } + + return tsl2772_invoke_change(indio_dev); +} + +static DEVICE_ATTR_RW(in_illuminance0_target_input); + +static DEVICE_ATTR_WO(in_illuminance0_calibrate); + +static DEVICE_ATTR_WO(in_proximity0_calibrate); + +static DEVICE_ATTR_RW(in_illuminance0_lux_table); + +/* Use the default register values to identify the Taos device */ +static int tsl2772_device_id_verif(int id, int target) +{ + switch (target) { + case tsl2571: + case tsl2671: + case tsl2771: + return (id & 0xf0) == TRITON_ID; + case tmd2671: + case tmd2771: + case apds9900: + return (id & 0xf0) == HALIBUT_ID; + case tsl2572: + case tsl2672: + case tmd2672: + case tsl2772: + case tmd2772: + case apds9930: + return (id & 0xf0) == SWORDFISH_ID; + } + + return -EINVAL; +} + +static irqreturn_t tsl2772_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct tsl2772_chip *chip = iio_priv(indio_dev); + s64 timestamp = iio_get_time_ns(indio_dev); + int ret; + + ret = tsl2772_read_status(chip); + if (ret < 0) + return IRQ_HANDLED; + + /* What type of interrupt do we need to process */ + if (ret & TSL2772_STA_PRX_INTR) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + timestamp); + } + + if (ret & TSL2772_STA_ALS_INTR) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + timestamp); + } + + ret = i2c_smbus_write_byte(chip->client, + TSL2772_CMD_REG | TSL2772_CMD_SPL_FN | + TSL2772_CMD_PROXALS_INT_CLR); + if (ret < 0) + dev_err(&chip->client->dev, + "%s: failed to clear interrupt status: %d\n", + __func__, ret); + + return IRQ_HANDLED; +} + +static struct attribute *tsl2772_ALS_device_attrs[] = { + &dev_attr_in_illuminance0_target_input.attr, + &dev_attr_in_illuminance0_calibrate.attr, + &dev_attr_in_illuminance0_lux_table.attr, + NULL +}; + +static struct attribute *tsl2772_PRX_device_attrs[] = { + &dev_attr_in_proximity0_calibrate.attr, + NULL +}; + +static struct attribute *tsl2772_ALSPRX_device_attrs[] = { + &dev_attr_in_illuminance0_target_input.attr, + &dev_attr_in_illuminance0_calibrate.attr, + &dev_attr_in_illuminance0_lux_table.attr, + NULL +}; + +static struct attribute *tsl2772_PRX2_device_attrs[] = { + &dev_attr_in_proximity0_calibrate.attr, + NULL +}; + +static struct attribute *tsl2772_ALSPRX2_device_attrs[] = { + &dev_attr_in_illuminance0_target_input.attr, + &dev_attr_in_illuminance0_calibrate.attr, + &dev_attr_in_illuminance0_lux_table.attr, + &dev_attr_in_proximity0_calibrate.attr, + NULL +}; + +static const struct attribute_group tsl2772_device_attr_group_tbl[] = { + [ALS] = { + .attrs = tsl2772_ALS_device_attrs, + }, + [PRX] = { + .attrs = tsl2772_PRX_device_attrs, + }, + [ALSPRX] = { + .attrs = tsl2772_ALSPRX_device_attrs, + }, + [PRX2] = { + .attrs = tsl2772_PRX2_device_attrs, + }, + [ALSPRX2] = { + .attrs = tsl2772_ALSPRX2_device_attrs, + }, +}; + +#define TSL2772_DEVICE_INFO(type)[type] = \ + { \ + .attrs = &tsl2772_device_attr_group_tbl[type], \ + .read_raw = &tsl2772_read_raw, \ + .read_avail = &tsl2772_read_avail, \ + .write_raw = &tsl2772_write_raw, \ + .read_event_value = &tsl2772_read_event_value, \ + .write_event_value = &tsl2772_write_event_value, \ + .read_event_config = &tsl2772_read_interrupt_config, \ + .write_event_config = &tsl2772_write_interrupt_config, \ + } + +static const struct iio_info tsl2772_device_info[] = { + TSL2772_DEVICE_INFO(ALS), + TSL2772_DEVICE_INFO(PRX), + TSL2772_DEVICE_INFO(ALSPRX), + TSL2772_DEVICE_INFO(PRX2), + TSL2772_DEVICE_INFO(ALSPRX2), +}; + +static const struct iio_event_spec tsl2772_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct tsl2772_chip_info tsl2772_chip_info_tbl[] = { + [ALS] = { + .channel_with_events = { + { + .type = IIO_LIGHT, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .event_spec = tsl2772_events, + .num_event_specs = ARRAY_SIZE(tsl2772_events), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 1, + }, + }, + .channel_without_events = { + { + .type = IIO_LIGHT, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 1, + }, + }, + .chan_table_elements = 3, + .info = &tsl2772_device_info[ALS], + }, + [PRX] = { + .channel_with_events = { + { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = tsl2772_events, + .num_event_specs = ARRAY_SIZE(tsl2772_events), + }, + }, + .channel_without_events = { + { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, + }, + .chan_table_elements = 1, + .info = &tsl2772_device_info[PRX], + }, + [ALSPRX] = { + .channel_with_events = { + { + .type = IIO_LIGHT, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .event_spec = tsl2772_events, + .num_event_specs = ARRAY_SIZE(tsl2772_events), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = tsl2772_events, + .num_event_specs = ARRAY_SIZE(tsl2772_events), + }, + }, + .channel_without_events = { + { + .type = IIO_LIGHT, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, + }, + .chan_table_elements = 4, + .info = &tsl2772_device_info[ALSPRX], + }, + [PRX2] = { + .channel_with_events = { + { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_CALIBSCALE), + .event_spec = tsl2772_events, + .num_event_specs = ARRAY_SIZE(tsl2772_events), + }, + }, + .channel_without_events = { + { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_CALIBSCALE), + }, + }, + .chan_table_elements = 1, + .info = &tsl2772_device_info[PRX2], + }, + [ALSPRX2] = { + .channel_with_events = { + { + .type = IIO_LIGHT, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .event_spec = tsl2772_events, + .num_event_specs = ARRAY_SIZE(tsl2772_events), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_CALIBSCALE), + .event_spec = tsl2772_events, + .num_event_specs = ARRAY_SIZE(tsl2772_events), + }, + }, + .channel_without_events = { + { + .type = IIO_LIGHT, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_CALIBSCALE), + }, + }, + .chan_table_elements = 4, + .info = &tsl2772_device_info[ALSPRX2], + }, +}; + +static int tsl2772_probe(struct i2c_client *clientp) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(clientp); + struct iio_dev *indio_dev; + struct tsl2772_chip *chip; + int ret; + + indio_dev = devm_iio_device_alloc(&clientp->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + chip->client = clientp; + i2c_set_clientdata(clientp, indio_dev); + + chip->supplies[TSL2772_SUPPLY_VDD].supply = "vdd"; + chip->supplies[TSL2772_SUPPLY_VDDIO].supply = "vddio"; + + ret = devm_regulator_bulk_get(&clientp->dev, + ARRAY_SIZE(chip->supplies), + chip->supplies); + if (ret < 0) + return dev_err_probe(&clientp->dev, ret, "Failed to get regulators\n"); + + ret = regulator_bulk_enable(ARRAY_SIZE(chip->supplies), chip->supplies); + if (ret < 0) { + dev_err(&clientp->dev, "Failed to enable regulators: %d\n", + ret); + return ret; + } + + ret = devm_add_action_or_reset(&clientp->dev, + tsl2772_disable_regulators_action, + chip); + if (ret < 0) { + dev_err(&clientp->dev, "Failed to setup regulator cleanup action %d\n", + ret); + return ret; + } + + usleep_range(TSL2772_BOOT_MIN_SLEEP_TIME, TSL2772_BOOT_MAX_SLEEP_TIME); + + ret = i2c_smbus_read_byte_data(chip->client, + TSL2772_CMD_REG | TSL2772_CHIPID); + if (ret < 0) + return ret; + + if (tsl2772_device_id_verif(ret, id->driver_data) <= 0) { + dev_info(&chip->client->dev, + "%s: i2c device found does not match expected id\n", + __func__); + return -EINVAL; + } + + ret = i2c_smbus_write_byte(clientp, TSL2772_CMD_REG | TSL2772_CNTRL); + if (ret < 0) { + dev_err(&clientp->dev, + "%s: Failed to write to CMD register: %d\n", + __func__, ret); + return ret; + } + + mutex_init(&chip->als_mutex); + mutex_init(&chip->prox_mutex); + + chip->tsl2772_chip_status = TSL2772_CHIP_UNKNOWN; + chip->pdata = dev_get_platdata(&clientp->dev); + chip->id = id->driver_data; + chip->chip_info = + &tsl2772_chip_info_tbl[device_channel_config[id->driver_data]]; + + indio_dev->info = chip->chip_info->info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = chip->client->name; + indio_dev->num_channels = chip->chip_info->chan_table_elements; + + if (clientp->irq) { + indio_dev->channels = chip->chip_info->channel_with_events; + + ret = devm_request_threaded_irq(&clientp->dev, clientp->irq, + NULL, + &tsl2772_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "TSL2772_event", + indio_dev); + if (ret) + return ret; + } else { + indio_dev->channels = chip->chip_info->channel_without_events; + } + + tsl2772_defaults(chip); + ret = tsl2772_chip_on(indio_dev); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(&clientp->dev, + tsl2772_chip_off_action, + indio_dev); + if (ret < 0) + return ret; + + return devm_iio_device_register(&clientp->dev, indio_dev); +} + +static int tsl2772_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2772_chip *chip = iio_priv(indio_dev); + int ret; + + ret = tsl2772_chip_off(indio_dev); + regulator_bulk_disable(ARRAY_SIZE(chip->supplies), chip->supplies); + + return ret; +} + +static int tsl2772_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2772_chip *chip = iio_priv(indio_dev); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(chip->supplies), chip->supplies); + if (ret < 0) + return ret; + + usleep_range(TSL2772_BOOT_MIN_SLEEP_TIME, TSL2772_BOOT_MAX_SLEEP_TIME); + + return tsl2772_chip_on(indio_dev); +} + +static const struct i2c_device_id tsl2772_idtable[] = { + { .name = "tsl2571", .driver_data = tsl2571 }, + { .name = "tsl2671", .driver_data = tsl2671 }, + { .name = "tmd2671", .driver_data = tmd2671 }, + { .name = "tsl2771", .driver_data = tsl2771 }, + { .name = "tmd2771", .driver_data = tmd2771 }, + { .name = "tsl2572", .driver_data = tsl2572 }, + { .name = "tsl2672", .driver_data = tsl2672 }, + { .name = "tmd2672", .driver_data = tmd2672 }, + { .name = "tsl2772", .driver_data = tsl2772 }, + { .name = "tmd2772", .driver_data = tmd2772 }, + { .name = "apds9900", .driver_data = apds9900 }, + { .name = "apds9901", .driver_data = apds9900 }, + { .name = "apds9930", .driver_data = apds9930 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, tsl2772_idtable); + +static const struct of_device_id tsl2772_of_match[] = { + { .compatible = "amstaos,tsl2571" }, + { .compatible = "amstaos,tsl2671" }, + { .compatible = "amstaos,tmd2671" }, + { .compatible = "amstaos,tsl2771" }, + { .compatible = "amstaos,tmd2771" }, + { .compatible = "amstaos,tsl2572" }, + { .compatible = "amstaos,tsl2672" }, + { .compatible = "amstaos,tmd2672" }, + { .compatible = "amstaos,tsl2772" }, + { .compatible = "amstaos,tmd2772" }, + { .compatible = "avago,apds9900" }, + { .compatible = "avago,apds9901" }, + { .compatible = "avago,apds9930" }, + { } +}; +MODULE_DEVICE_TABLE(of, tsl2772_of_match); + +static const struct dev_pm_ops tsl2772_pm_ops = { + .suspend = tsl2772_suspend, + .resume = tsl2772_resume, +}; + +static struct i2c_driver tsl2772_driver = { + .driver = { + .name = "tsl2772", + .of_match_table = tsl2772_of_match, + .pm = &tsl2772_pm_ops, + }, + .id_table = tsl2772_idtable, + .probe = tsl2772_probe, +}; + +module_i2c_driver(tsl2772_driver); + +MODULE_AUTHOR("J. August Brenner <Jon.Brenner@ams.com>"); +MODULE_AUTHOR("Brian Masney <masneyb@onstation.org>"); +MODULE_DESCRIPTION("TAOS tsl2772 ambient and proximity light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/tsl4531.c b/drivers/iio/light/tsl4531.c new file mode 100644 index 000000000..2f91ed8a9 --- /dev/null +++ b/drivers/iio/light/tsl4531.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tsl4531.c - Support for TAOS TSL4531 ambient light sensor + * + * Copyright 2013 Peter Meerwald <pmeerw@pmeerw.net> + * + * IIO driver for the TSL4531x family + * TSL45311/TSL45313: 7-bit I2C slave address 0x39 + * TSL45315/TSL45317: 7-bit I2C slave address 0x29 + * + * TODO: single cycle measurement + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define TSL4531_DRV_NAME "tsl4531" + +#define TSL4531_COMMAND BIT(7) + +#define TSL4531_CONTROL (TSL4531_COMMAND | 0x00) +#define TSL4531_CONFIG (TSL4531_COMMAND | 0x01) +#define TSL4531_DATA (TSL4531_COMMAND | 0x04) +#define TSL4531_ID (TSL4531_COMMAND | 0x0a) + +/* operating modes in control register */ +#define TSL4531_MODE_POWERDOWN 0x00 +#define TSL4531_MODE_SINGLE_ADC 0x02 +#define TSL4531_MODE_NORMAL 0x03 + +/* integration time control in config register */ +#define TSL4531_TCNTRL_400MS 0x00 +#define TSL4531_TCNTRL_200MS 0x01 +#define TSL4531_TCNTRL_100MS 0x02 + +/* part number in id register */ +#define TSL45311_ID 0x8 +#define TSL45313_ID 0x9 +#define TSL45315_ID 0xa +#define TSL45317_ID 0xb +#define TSL4531_ID_SHIFT 4 + +struct tsl4531_data { + struct i2c_client *client; + struct mutex lock; + int int_time; +}; + +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.1 0.2 0.4"); + +static struct attribute *tsl4531_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group tsl4531_attribute_group = { + .attrs = tsl4531_attributes, +}; + +static const struct iio_chan_spec tsl4531_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME) + } +}; + +static int tsl4531_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tsl4531_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_smbus_read_word_data(data->client, + TSL4531_DATA); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* 0.. 1x, 1 .. 2x, 2 .. 4x */ + *val = 1 << data->int_time; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + if (data->int_time == 0) + *val2 = 400000; + else if (data->int_time == 1) + *val2 = 200000; + else if (data->int_time == 2) + *val2 = 100000; + else + return -EINVAL; + *val = 0; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int tsl4531_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct tsl4531_data *data = iio_priv(indio_dev); + int int_time, ret; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + if (val2 == 400000) + int_time = 0; + else if (val2 == 200000) + int_time = 1; + else if (val2 == 100000) + int_time = 2; + else + return -EINVAL; + mutex_lock(&data->lock); + ret = i2c_smbus_write_byte_data(data->client, + TSL4531_CONFIG, int_time); + if (ret >= 0) + data->int_time = int_time; + mutex_unlock(&data->lock); + return ret; + default: + return -EINVAL; + } +} + +static const struct iio_info tsl4531_info = { + .read_raw = tsl4531_read_raw, + .write_raw = tsl4531_write_raw, + .attrs = &tsl4531_attribute_group, +}; + +static int tsl4531_check_id(struct i2c_client *client) +{ + int ret = i2c_smbus_read_byte_data(client, TSL4531_ID); + if (ret < 0) + return ret; + + switch (ret >> TSL4531_ID_SHIFT) { + case TSL45311_ID: + case TSL45313_ID: + case TSL45315_ID: + case TSL45317_ID: + return 0; + default: + return -ENODEV; + } +} + +static int tsl4531_probe(struct i2c_client *client) +{ + struct tsl4531_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + + ret = tsl4531_check_id(client); + if (ret) { + dev_err(&client->dev, "no TSL4531 sensor\n"); + return ret; + } + + ret = i2c_smbus_write_byte_data(data->client, TSL4531_CONTROL, + TSL4531_MODE_NORMAL); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, TSL4531_CONFIG, + TSL4531_TCNTRL_400MS); + if (ret < 0) + return ret; + + indio_dev->info = &tsl4531_info; + indio_dev->channels = tsl4531_channels; + indio_dev->num_channels = ARRAY_SIZE(tsl4531_channels); + indio_dev->name = TSL4531_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + return iio_device_register(indio_dev); +} + +static int tsl4531_powerdown(struct i2c_client *client) +{ + return i2c_smbus_write_byte_data(client, TSL4531_CONTROL, + TSL4531_MODE_POWERDOWN); +} + +static void tsl4531_remove(struct i2c_client *client) +{ + iio_device_unregister(i2c_get_clientdata(client)); + tsl4531_powerdown(client); +} + +static int tsl4531_suspend(struct device *dev) +{ + return tsl4531_powerdown(to_i2c_client(dev)); +} + +static int tsl4531_resume(struct device *dev) +{ + return i2c_smbus_write_byte_data(to_i2c_client(dev), TSL4531_CONTROL, + TSL4531_MODE_NORMAL); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(tsl4531_pm_ops, tsl4531_suspend, + tsl4531_resume); + +static const struct i2c_device_id tsl4531_id[] = { + { .name = "tsl4531" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tsl4531_id); + +static struct i2c_driver tsl4531_driver = { + .driver = { + .name = TSL4531_DRV_NAME, + .pm = pm_sleep_ptr(&tsl4531_pm_ops), + }, + .probe = tsl4531_probe, + .remove = tsl4531_remove, + .id_table = tsl4531_id, +}; + +module_i2c_driver(tsl4531_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("TAOS TSL4531 ambient light sensors driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/us5182d.c b/drivers/iio/light/us5182d.c new file mode 100644 index 000000000..ab518311f --- /dev/null +++ b/drivers/iio/light/us5182d.c @@ -0,0 +1,979 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 Intel Corporation + * + * Driver for UPISEMI us5182d Proximity and Ambient Light Sensor. + * + * To do: Interrupt support. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/iio/sysfs.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> + +#define US5182D_REG_CFG0 0x00 +#define US5182D_CFG0_ONESHOT_EN BIT(6) +#define US5182D_CFG0_SHUTDOWN_EN BIT(7) +#define US5182D_CFG0_WORD_ENABLE BIT(0) +#define US5182D_CFG0_PROX BIT(3) +#define US5182D_CFG0_PX_IRQ BIT(2) + +#define US5182D_REG_CFG1 0x01 +#define US5182D_CFG1_ALS_RES16 BIT(4) +#define US5182D_CFG1_AGAIN_DEFAULT 0x00 + +#define US5182D_REG_CFG2 0x02 +#define US5182D_CFG2_PX_RES16 BIT(4) +#define US5182D_CFG2_PXGAIN_DEFAULT BIT(2) + +#define US5182D_REG_CFG3 0x03 +#define US5182D_CFG3_LED_CURRENT100 (BIT(4) | BIT(5)) +#define US5182D_CFG3_INT_SOURCE_PX BIT(3) + +#define US5182D_REG_CFG4 0x10 + +/* + * Registers for tuning the auto dark current cancelling feature. + * DARK_TH(reg 0x27,0x28) - threshold (counts) for auto dark cancelling. + * when ALS > DARK_TH --> ALS_Code = ALS - Upper(0x2A) * Dark + * when ALS < DARK_TH --> ALS_Code = ALS - Lower(0x29) * Dark + */ +#define US5182D_REG_UDARK_TH 0x27 +#define US5182D_REG_DARK_AUTO_EN 0x2b +#define US5182D_REG_AUTO_LDARK_GAIN 0x29 +#define US5182D_REG_AUTO_HDARK_GAIN 0x2a + +/* Thresholds for events: px low (0x08-l, 0x09-h), px high (0x0a-l 0x0b-h) */ +#define US5182D_REG_PXL_TH 0x08 +#define US5182D_REG_PXH_TH 0x0a + +#define US5182D_REG_PXL_TH_DEFAULT 1000 +#define US5182D_REG_PXH_TH_DEFAULT 30000 + +#define US5182D_OPMODE_ALS 0x01 +#define US5182D_OPMODE_PX 0x02 +#define US5182D_OPMODE_SHIFT 4 + +#define US5182D_REG_DARK_AUTO_EN_DEFAULT 0x80 +#define US5182D_REG_AUTO_LDARK_GAIN_DEFAULT 0x16 +#define US5182D_REG_AUTO_HDARK_GAIN_DEFAULT 0x00 + +#define US5182D_REG_ADL 0x0c +#define US5182D_REG_PDL 0x0e + +#define US5182D_REG_MODE_STORE 0x21 +#define US5182D_STORE_MODE 0x01 + +#define US5182D_REG_CHIPID 0xb2 + +#define US5182D_OPMODE_MASK GENMASK(5, 4) +#define US5182D_AGAIN_MASK 0x07 +#define US5182D_RESET_CHIP 0x01 + +#define US5182D_CHIPID 0x26 +#define US5182D_DRV_NAME "us5182d" + +#define US5182D_GA_RESOLUTION 1000 + +#define US5182D_READ_BYTE 1 +#define US5182D_READ_WORD 2 +#define US5182D_OPSTORE_SLEEP_TIME 20 /* ms */ +#define US5182D_SLEEP_MS 3000 /* ms */ +#define US5182D_PXH_TH_DISABLE 0xffff +#define US5182D_PXL_TH_DISABLE 0x0000 + +/* Available ranges: [12354, 7065, 3998, 2202, 1285, 498, 256, 138] lux */ +static const int us5182d_scales[] = {188500, 107800, 61000, 33600, 19600, 7600, + 3900, 2100}; + +/* + * Experimental thresholds that work with US5182D sensor on evaluation board + * roughly between 12-32 lux + */ +static u16 us5182d_dark_ths_vals[] = {170, 200, 512, 512, 800, 2000, 4000, + 8000}; + +enum mode { + US5182D_ALS_PX, + US5182D_ALS_ONLY, + US5182D_PX_ONLY +}; + +enum pmode { + US5182D_CONTINUOUS, + US5182D_ONESHOT +}; + +struct us5182d_data { + struct i2c_client *client; + struct mutex lock; + + /* Glass attenuation factor */ + u32 ga; + + /* Dark gain tuning */ + u8 lower_dark_gain; + u8 upper_dark_gain; + u16 *us5182d_dark_ths; + + u16 px_low_th; + u16 px_high_th; + + int rising_en; + int falling_en; + + u8 opmode; + u8 power_mode; + + bool als_enabled; + bool px_enabled; + + bool default_continuous; +}; + +static IIO_CONST_ATTR(in_illuminance_scale_available, + "0.0021 0.0039 0.0076 0.0196 0.0336 0.061 0.1078 0.1885"); + +static struct attribute *us5182d_attrs[] = { + &iio_const_attr_in_illuminance_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group us5182d_attr_group = { + .attrs = us5182d_attrs, +}; + +static const struct { + u8 reg; + u8 val; +} us5182d_regvals[] = { + {US5182D_REG_CFG0, US5182D_CFG0_WORD_ENABLE}, + {US5182D_REG_CFG1, US5182D_CFG1_ALS_RES16}, + {US5182D_REG_CFG2, (US5182D_CFG2_PX_RES16 | + US5182D_CFG2_PXGAIN_DEFAULT)}, + {US5182D_REG_CFG3, US5182D_CFG3_LED_CURRENT100 | + US5182D_CFG3_INT_SOURCE_PX}, + {US5182D_REG_CFG4, 0x00}, +}; + +static const struct iio_event_spec us5182d_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec us5182d_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = us5182d_events, + .num_event_specs = ARRAY_SIZE(us5182d_events), + } +}; + +static int us5182d_oneshot_en(struct us5182d_data *data) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0); + if (ret < 0) + return ret; + + /* + * In oneshot mode the chip will power itself down after taking the + * required measurement. + */ + ret = ret | US5182D_CFG0_ONESHOT_EN; + + return i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret); +} + +static int us5182d_set_opmode(struct us5182d_data *data, u8 mode) +{ + int ret; + + if (mode == data->opmode) + return 0; + + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0); + if (ret < 0) + return ret; + + /* update mode */ + ret = ret & ~US5182D_OPMODE_MASK; + ret = ret | (mode << US5182D_OPMODE_SHIFT); + + /* + * After updating the operating mode, the chip requires that + * the operation is stored, by writing 1 in the STORE_MODE + * register (auto-clearing). + */ + ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_MODE_STORE, + US5182D_STORE_MODE); + if (ret < 0) + return ret; + + data->opmode = mode; + msleep(US5182D_OPSTORE_SLEEP_TIME); + + return 0; +} + +static int us5182d_als_enable(struct us5182d_data *data) +{ + int ret; + u8 mode; + + if (data->power_mode == US5182D_ONESHOT) { + ret = us5182d_set_opmode(data, US5182D_ALS_ONLY); + if (ret < 0) + return ret; + data->px_enabled = false; + } + + if (data->als_enabled) + return 0; + + mode = data->px_enabled ? US5182D_ALS_PX : US5182D_ALS_ONLY; + + ret = us5182d_set_opmode(data, mode); + if (ret < 0) + return ret; + + data->als_enabled = true; + + return 0; +} + +static int us5182d_px_enable(struct us5182d_data *data) +{ + int ret; + u8 mode; + + if (data->power_mode == US5182D_ONESHOT) { + ret = us5182d_set_opmode(data, US5182D_PX_ONLY); + if (ret < 0) + return ret; + data->als_enabled = false; + } + + if (data->px_enabled) + return 0; + + mode = data->als_enabled ? US5182D_ALS_PX : US5182D_PX_ONLY; + + ret = us5182d_set_opmode(data, mode); + if (ret < 0) + return ret; + + data->px_enabled = true; + + return 0; +} + +static int us5182d_get_als(struct us5182d_data *data) +{ + int ret; + unsigned long result; + + ret = us5182d_als_enable(data); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(data->client, + US5182D_REG_ADL); + if (ret < 0) + return ret; + + result = ret * data->ga / US5182D_GA_RESOLUTION; + if (result > 0xffff) + result = 0xffff; + + return result; +} + +static int us5182d_get_px(struct us5182d_data *data) +{ + int ret; + + ret = us5182d_px_enable(data); + if (ret < 0) + return ret; + + return i2c_smbus_read_word_data(data->client, + US5182D_REG_PDL); +} + +static int us5182d_shutdown_en(struct us5182d_data *data, u8 state) +{ + int ret; + + if (data->power_mode == US5182D_ONESHOT) + return 0; + + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0); + if (ret < 0) + return ret; + + ret = ret & ~US5182D_CFG0_SHUTDOWN_EN; + ret = ret | state; + + ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret); + if (ret < 0) + return ret; + + if (state & US5182D_CFG0_SHUTDOWN_EN) { + data->als_enabled = false; + data->px_enabled = false; + } + + return ret; +} + + +static int us5182d_set_power_state(struct us5182d_data *data, bool on) +{ + if (data->power_mode == US5182D_ONESHOT) + return 0; + + if (on) + return pm_runtime_resume_and_get(&data->client->dev); + + return pm_runtime_put_autosuspend(&data->client->dev); +} + +static int us5182d_read_value(struct us5182d_data *data, + struct iio_chan_spec const *chan) +{ + int ret, value; + + mutex_lock(&data->lock); + + if (data->power_mode == US5182D_ONESHOT) { + ret = us5182d_oneshot_en(data); + if (ret < 0) + goto out_err; + } + + ret = us5182d_set_power_state(data, true); + if (ret < 0) + goto out_err; + + if (chan->type == IIO_LIGHT) + ret = us5182d_get_als(data); + else + ret = us5182d_get_px(data); + if (ret < 0) + goto out_poweroff; + + value = ret; + + ret = us5182d_set_power_state(data, false); + if (ret < 0) + goto out_err; + + mutex_unlock(&data->lock); + return value; + +out_poweroff: + us5182d_set_power_state(data, false); +out_err: + mutex_unlock(&data->lock); + return ret; +} + +static int us5182d_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = us5182d_read_value(data, chan); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG1); + if (ret < 0) + return ret; + *val = 0; + *val2 = us5182d_scales[ret & US5182D_AGAIN_MASK]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +/** + * us5182d_update_dark_th - update Darh_Th registers + * @data: us5182d_data structure + * @index: index in us5182d_dark_ths array to use for the updated value + * + * Function needs to be called with a lock held because it needs two i2c write + * byte operations as these registers (0x27 0x28) don't work in word mode + * accessing. + */ +static int us5182d_update_dark_th(struct us5182d_data *data, int index) +{ + __be16 dark_th = cpu_to_be16(data->us5182d_dark_ths[index]); + int ret; + + ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_UDARK_TH, + ((u8 *)&dark_th)[0]); + if (ret < 0) + return ret; + + return i2c_smbus_write_byte_data(data->client, US5182D_REG_UDARK_TH + 1, + ((u8 *)&dark_th)[1]); +} + +/** + * us5182d_apply_scale - update the ALS scale + * @data: us5182d_data structure + * @index: index in us5182d_scales array to use for the updated value + * + * Function needs to be called with a lock held as we're having more than one + * i2c operation. + */ +static int us5182d_apply_scale(struct us5182d_data *data, int index) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG1); + if (ret < 0) + return ret; + + ret = ret & (~US5182D_AGAIN_MASK); + ret |= index; + + ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG1, ret); + if (ret < 0) + return ret; + + return us5182d_update_dark_th(data, index); +} + +static int us5182d_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int ret, i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (val != 0) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(us5182d_scales); i++) + if (val2 == us5182d_scales[i]) { + mutex_lock(&data->lock); + ret = us5182d_apply_scale(data, i); + mutex_unlock(&data->lock); + return ret; + } + break; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int us5182d_setup_prox(struct iio_dev *indio_dev, + enum iio_event_direction dir, u16 val) +{ + struct us5182d_data *data = iio_priv(indio_dev); + + if (dir == IIO_EV_DIR_FALLING) + return i2c_smbus_write_word_data(data->client, + US5182D_REG_PXL_TH, val); + else if (dir == IIO_EV_DIR_RISING) + return i2c_smbus_write_word_data(data->client, + US5182D_REG_PXH_TH, val); + + return 0; +} + +static int us5182d_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int *val, + int *val2) +{ + struct us5182d_data *data = iio_priv(indio_dev); + + switch (dir) { + case IIO_EV_DIR_RISING: + mutex_lock(&data->lock); + *val = data->px_high_th; + mutex_unlock(&data->lock); + break; + case IIO_EV_DIR_FALLING: + mutex_lock(&data->lock); + *val = data->px_low_th; + mutex_unlock(&data->lock); + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static int us5182d_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int val, + int val2) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int ret; + + if (val < 0 || val > USHRT_MAX || val2 != 0) + return -EINVAL; + + switch (dir) { + case IIO_EV_DIR_RISING: + mutex_lock(&data->lock); + if (data->rising_en) { + ret = us5182d_setup_prox(indio_dev, dir, val); + if (ret < 0) + goto err; + } + data->px_high_th = val; + mutex_unlock(&data->lock); + break; + case IIO_EV_DIR_FALLING: + mutex_lock(&data->lock); + if (data->falling_en) { + ret = us5182d_setup_prox(indio_dev, dir, val); + if (ret < 0) + goto err; + } + data->px_low_th = val; + mutex_unlock(&data->lock); + break; + default: + return -EINVAL; + } + + return 0; +err: + mutex_unlock(&data->lock); + return ret; +} + +static int us5182d_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int ret; + + switch (dir) { + case IIO_EV_DIR_RISING: + mutex_lock(&data->lock); + ret = data->rising_en; + mutex_unlock(&data->lock); + break; + case IIO_EV_DIR_FALLING: + mutex_lock(&data->lock); + ret = data->falling_en; + mutex_unlock(&data->lock); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int us5182d_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int ret; + u16 new_th; + + mutex_lock(&data->lock); + + switch (dir) { + case IIO_EV_DIR_RISING: + if (data->rising_en == state) { + mutex_unlock(&data->lock); + return 0; + } + new_th = US5182D_PXH_TH_DISABLE; + if (state) { + data->power_mode = US5182D_CONTINUOUS; + ret = us5182d_set_power_state(data, true); + if (ret < 0) + goto err; + ret = us5182d_px_enable(data); + if (ret < 0) + goto err_poweroff; + new_th = data->px_high_th; + } + ret = us5182d_setup_prox(indio_dev, dir, new_th); + if (ret < 0) + goto err_poweroff; + data->rising_en = state; + break; + case IIO_EV_DIR_FALLING: + if (data->falling_en == state) { + mutex_unlock(&data->lock); + return 0; + } + new_th = US5182D_PXL_TH_DISABLE; + if (state) { + data->power_mode = US5182D_CONTINUOUS; + ret = us5182d_set_power_state(data, true); + if (ret < 0) + goto err; + ret = us5182d_px_enable(data); + if (ret < 0) + goto err_poweroff; + new_th = data->px_low_th; + } + ret = us5182d_setup_prox(indio_dev, dir, new_th); + if (ret < 0) + goto err_poweroff; + data->falling_en = state; + break; + default: + ret = -EINVAL; + goto err; + } + + if (!state) { + ret = us5182d_set_power_state(data, false); + if (ret < 0) + goto err; + } + + if (!data->falling_en && !data->rising_en && !data->default_continuous) + data->power_mode = US5182D_ONESHOT; + + mutex_unlock(&data->lock); + return 0; + +err_poweroff: + if (state) + us5182d_set_power_state(data, false); +err: + mutex_unlock(&data->lock); + return ret; +} + +static const struct iio_info us5182d_info = { + .read_raw = us5182d_read_raw, + .write_raw = us5182d_write_raw, + .attrs = &us5182d_attr_group, + .read_event_value = &us5182d_read_thresh, + .write_event_value = &us5182d_write_thresh, + .read_event_config = &us5182d_read_event_config, + .write_event_config = &us5182d_write_event_config, +}; + +static int us5182d_reset(struct iio_dev *indio_dev) +{ + struct us5182d_data *data = iio_priv(indio_dev); + + return i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG3, + US5182D_RESET_CHIP); +} + +static int us5182d_init(struct iio_dev *indio_dev) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int i, ret; + + ret = us5182d_reset(indio_dev); + if (ret < 0) + return ret; + + data->opmode = 0; + data->power_mode = US5182D_CONTINUOUS; + data->px_low_th = US5182D_REG_PXL_TH_DEFAULT; + data->px_high_th = US5182D_REG_PXH_TH_DEFAULT; + + for (i = 0; i < ARRAY_SIZE(us5182d_regvals); i++) { + ret = i2c_smbus_write_byte_data(data->client, + us5182d_regvals[i].reg, + us5182d_regvals[i].val); + if (ret < 0) + return ret; + } + + data->als_enabled = true; + data->px_enabled = true; + + if (!data->default_continuous) { + ret = us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); + if (ret < 0) + return ret; + data->power_mode = US5182D_ONESHOT; + } + + return ret; +} + +static void us5182d_get_platform_data(struct iio_dev *indio_dev) +{ + struct us5182d_data *data = iio_priv(indio_dev); + + if (device_property_read_u32(&data->client->dev, "upisemi,glass-coef", + &data->ga)) + data->ga = US5182D_GA_RESOLUTION; + if (device_property_read_u16_array(&data->client->dev, + "upisemi,dark-ths", + data->us5182d_dark_ths, + ARRAY_SIZE(us5182d_dark_ths_vals))) + data->us5182d_dark_ths = us5182d_dark_ths_vals; + if (device_property_read_u8(&data->client->dev, + "upisemi,upper-dark-gain", + &data->upper_dark_gain)) + data->upper_dark_gain = US5182D_REG_AUTO_HDARK_GAIN_DEFAULT; + if (device_property_read_u8(&data->client->dev, + "upisemi,lower-dark-gain", + &data->lower_dark_gain)) + data->lower_dark_gain = US5182D_REG_AUTO_LDARK_GAIN_DEFAULT; + data->default_continuous = device_property_read_bool(&data->client->dev, + "upisemi,continuous"); +} + +static int us5182d_dark_gain_config(struct iio_dev *indio_dev) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int ret; + + ret = us5182d_update_dark_th(data, US5182D_CFG1_AGAIN_DEFAULT); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, + US5182D_REG_AUTO_LDARK_GAIN, + data->lower_dark_gain); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, + US5182D_REG_AUTO_HDARK_GAIN, + data->upper_dark_gain); + if (ret < 0) + return ret; + + return i2c_smbus_write_byte_data(data->client, US5182D_REG_DARK_AUTO_EN, + US5182D_REG_DARK_AUTO_EN_DEFAULT); +} + +static irqreturn_t us5182d_irq_thread_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct us5182d_data *data = iio_priv(indio_dev); + enum iio_event_direction dir; + int ret; + u64 ev; + + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0); + if (ret < 0) { + dev_err(&data->client->dev, "i2c transfer error in irq\n"); + return IRQ_HANDLED; + } + + dir = ret & US5182D_CFG0_PROX ? IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING; + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1, IIO_EV_TYPE_THRESH, dir); + + iio_push_event(indio_dev, ev, iio_get_time_ns(indio_dev)); + + ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, + ret & ~US5182D_CFG0_PX_IRQ); + if (ret < 0) + dev_err(&data->client->dev, "i2c transfer error in irq\n"); + + return IRQ_HANDLED; +} + +static int us5182d_probe(struct i2c_client *client) +{ + struct us5182d_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + mutex_init(&data->lock); + + indio_dev->info = &us5182d_info; + indio_dev->name = US5182D_DRV_NAME; + indio_dev->channels = us5182d_channels; + indio_dev->num_channels = ARRAY_SIZE(us5182d_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CHIPID); + if (ret != US5182D_CHIPID) { + dev_err(&data->client->dev, + "Failed to detect US5182 light chip\n"); + return (ret < 0) ? ret : -ENODEV; + } + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, + us5182d_irq_thread_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "us5182d-irq", indio_dev); + if (ret < 0) + return ret; + } else + dev_warn(&client->dev, "no valid irq found\n"); + + us5182d_get_platform_data(indio_dev); + ret = us5182d_init(indio_dev); + if (ret < 0) + return ret; + + ret = us5182d_dark_gain_config(indio_dev); + if (ret < 0) + goto out_err; + + if (data->default_continuous) { + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto out_err; + } + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + US5182D_SLEEP_MS); + pm_runtime_use_autosuspend(&client->dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto out_err; + + return 0; + +out_err: + us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); + return ret; + +} + +static void us5182d_remove(struct i2c_client *client) +{ + struct us5182d_data *data = iio_priv(i2c_get_clientdata(client)); + int ret; + + iio_device_unregister(i2c_get_clientdata(client)); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + ret = us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); + if (ret) + dev_warn(&client->dev, "Failed to shut down (%pe)\n", + ERR_PTR(ret)); +} + +static int us5182d_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct us5182d_data *data = iio_priv(indio_dev); + + if (data->power_mode == US5182D_CONTINUOUS) + return us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); + + return 0; +} + +static int us5182d_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct us5182d_data *data = iio_priv(indio_dev); + + if (data->power_mode == US5182D_CONTINUOUS) + return us5182d_shutdown_en(data, + ~US5182D_CFG0_SHUTDOWN_EN & 0xff); + + return 0; +} + +static const struct dev_pm_ops us5182d_pm_ops = { + SYSTEM_SLEEP_PM_OPS(us5182d_suspend, us5182d_resume) + RUNTIME_PM_OPS(us5182d_suspend, us5182d_resume, NULL) +}; + +static const struct acpi_device_id us5182d_acpi_match[] = { + { "USD5182", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(acpi, us5182d_acpi_match); + +static const struct i2c_device_id us5182d_id[] = { + { .name = "usd5182" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, us5182d_id); + +static const struct of_device_id us5182d_of_match[] = { + { .compatible = "upisemi,usd5182" }, + { } +}; +MODULE_DEVICE_TABLE(of, us5182d_of_match); + +static struct i2c_driver us5182d_driver = { + .driver = { + .name = US5182D_DRV_NAME, + .pm = pm_ptr(&us5182d_pm_ops), + .of_match_table = us5182d_of_match, + .acpi_match_table = us5182d_acpi_match, + }, + .probe = us5182d_probe, + .remove = us5182d_remove, + .id_table = us5182d_id, + +}; +module_i2c_driver(us5182d_driver); + +MODULE_AUTHOR("Adriana Reus <adriana.reus@intel.com>"); +MODULE_DESCRIPTION("Driver for us5182d Proximity and Light Sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/vcnl4000.c b/drivers/iio/light/vcnl4000.c new file mode 100644 index 000000000..336468d59 --- /dev/null +++ b/drivers/iio/light/vcnl4000.c @@ -0,0 +1,2059 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * vcnl4000.c - Support for Vishay VCNL4000/4010/4020/4040/4200 combined ambient + * light and proximity sensor + * + * Copyright 2012 Peter Meerwald <pmeerw@pmeerw.net> + * Copyright 2019 Pursim SPC + * Copyright 2020 Mathieu Othacehe <m.othacehe@gmail.com> + * + * IIO driver for: + * VCNL4000/10/20 (7-bit I2C slave address 0x13) + * VCNL4040 (7-bit I2C slave address 0x60) + * VCNL4200 (7-bit I2C slave address 0x51) + * + * TODO: + * allow to adjust IR current + * interrupts (VCNL4040, VCNL4200) + */ + +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/units.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define VCNL4000_DRV_NAME "vcnl4000" +#define VCNL4000_PROD_ID 0x01 +#define VCNL4010_PROD_ID 0x02 /* for VCNL4020, VCNL4010 */ +#define VCNL4040_PROD_ID 0x86 +#define VCNL4200_PROD_ID 0x58 + +#define VCNL4000_COMMAND 0x80 /* Command register */ +#define VCNL4000_PROD_REV 0x81 /* Product ID and Revision ID */ +#define VCNL4010_PROX_RATE 0x82 /* Proximity rate */ +#define VCNL4000_LED_CURRENT 0x83 /* IR LED current for proximity mode */ +#define VCNL4000_AL_PARAM 0x84 /* Ambient light parameter register */ +#define VCNL4010_ALS_PARAM 0x84 /* ALS rate */ +#define VCNL4000_AL_RESULT_HI 0x85 /* Ambient light result register, MSB */ +#define VCNL4000_AL_RESULT_LO 0x86 /* Ambient light result register, LSB */ +#define VCNL4000_PS_RESULT_HI 0x87 /* Proximity result register, MSB */ +#define VCNL4000_PS_RESULT_LO 0x88 /* Proximity result register, LSB */ +#define VCNL4000_PS_MEAS_FREQ 0x89 /* Proximity test signal frequency */ +#define VCNL4010_INT_CTRL 0x89 /* Interrupt control */ +#define VCNL4000_PS_MOD_ADJ 0x8a /* Proximity modulator timing adjustment */ +#define VCNL4010_LOW_THR_HI 0x8a /* Low threshold, MSB */ +#define VCNL4010_LOW_THR_LO 0x8b /* Low threshold, LSB */ +#define VCNL4010_HIGH_THR_HI 0x8c /* High threshold, MSB */ +#define VCNL4010_HIGH_THR_LO 0x8d /* High threshold, LSB */ +#define VCNL4010_ISR 0x8e /* Interrupt status */ + +#define VCNL4200_AL_CONF 0x00 /* Ambient light configuration */ +#define VCNL4200_PS_CONF1 0x03 /* Proximity configuration */ +#define VCNL4200_PS_CONF3 0x04 /* Proximity configuration */ +#define VCNL4040_PS_THDL_LM 0x06 /* Proximity threshold low */ +#define VCNL4040_PS_THDH_LM 0x07 /* Proximity threshold high */ +#define VCNL4040_ALS_THDL_LM 0x02 /* Ambient light threshold low */ +#define VCNL4040_ALS_THDH_LM 0x01 /* Ambient light threshold high */ +#define VCNL4200_PS_DATA 0x08 /* Proximity data */ +#define VCNL4200_AL_DATA 0x09 /* Ambient light data */ +#define VCNL4040_INT_FLAGS 0x0b /* Interrupt register */ +#define VCNL4200_INT_FLAGS 0x0d /* Interrupt register */ +#define VCNL4200_DEV_ID 0x0e /* Device ID, slave address and version */ + +#define VCNL4040_DEV_ID 0x0c /* Device ID and version */ + +/* Bit masks for COMMAND register */ +#define VCNL4000_AL_RDY BIT(6) /* ALS data ready? */ +#define VCNL4000_PS_RDY BIT(5) /* proximity data ready? */ +#define VCNL4000_AL_OD BIT(4) /* start on-demand ALS measurement */ +#define VCNL4000_PS_OD BIT(3) /* start on-demand proximity measurement */ +#define VCNL4000_ALS_EN BIT(2) /* start ALS measurement */ +#define VCNL4000_PROX_EN BIT(1) /* start proximity measurement */ +#define VCNL4000_SELF_TIMED_EN BIT(0) /* start self-timed measurement */ + +#define VCNL4040_ALS_CONF_ALS_SHUTDOWN BIT(0) +#define VCNL4040_ALS_CONF_IT GENMASK(7, 6) /* Ambient integration time */ +#define VCNL4040_ALS_CONF_INT_EN BIT(1) /* Ambient light Interrupt enable */ +#define VCNL4040_ALS_CONF_PERS GENMASK(3, 2) /* Ambient interrupt persistence setting */ +#define VCNL4040_PS_CONF1_PS_SHUTDOWN BIT(0) +#define VCNL4040_PS_CONF2_PS_IT GENMASK(3, 1) /* Proximity integration time */ +#define VCNL4040_CONF1_PS_PERS GENMASK(5, 4) /* Proximity interrupt persistence setting */ +#define VCNL4040_PS_CONF2_PS_HD BIT(11) /* Proximity high definition */ +#define VCNL4040_PS_CONF2_PS_INT GENMASK(9, 8) /* Proximity interrupt mode */ +#define VCNL4040_PS_CONF3_MPS GENMASK(6, 5) /* Proximity multi pulse number */ +#define VCNL4040_PS_MS_LED_I GENMASK(10, 8) /* Proximity current */ +#define VCNL4040_PS_IF_AWAY BIT(8) /* Proximity event cross low threshold */ +#define VCNL4040_PS_IF_CLOSE BIT(9) /* Proximity event cross high threshold */ +#define VCNL4040_ALS_RISING BIT(12) /* Ambient Light cross high threshold */ +#define VCNL4040_ALS_FALLING BIT(13) /* Ambient Light cross low threshold */ + +/* Bit masks for interrupt registers. */ +#define VCNL4010_INT_THR_SEL BIT(0) /* Select threshold interrupt source */ +#define VCNL4010_INT_THR_EN BIT(1) /* Threshold interrupt type */ +#define VCNL4010_INT_ALS_EN BIT(2) /* Enable on ALS data ready */ +#define VCNL4010_INT_PROX_EN BIT(3) /* Enable on proximity data ready */ + +#define VCNL4010_INT_THR_HIGH 0 /* High threshold exceeded */ +#define VCNL4010_INT_THR_LOW 1 /* Low threshold exceeded */ +#define VCNL4010_INT_ALS 2 /* ALS data ready */ +#define VCNL4010_INT_PROXIMITY 3 /* Proximity data ready */ + +#define VCNL4010_INT_THR \ + (BIT(VCNL4010_INT_THR_LOW) | BIT(VCNL4010_INT_THR_HIGH)) +#define VCNL4010_INT_DRDY \ + (BIT(VCNL4010_INT_PROXIMITY) | BIT(VCNL4010_INT_ALS)) + +#define VCNL4040_CONF3_PS_MPS_16BITS 3 /* 8 multi pulses */ +#define VCNL4040_CONF3_PS_LED_I_16BITS 3 /* 120 mA */ + +#define VCNL4040_CONF3_PS_SAMPLE_16BITS \ + (FIELD_PREP(VCNL4040_PS_CONF3_MPS, VCNL4040_CONF3_PS_MPS_16BITS) | \ + FIELD_PREP(VCNL4040_PS_MS_LED_I, VCNL4040_CONF3_PS_LED_I_16BITS)) + +static const int vcnl4010_prox_sampling_frequency[][2] = { + {1, 950000}, + {3, 906250}, + {7, 812500}, + {16, 625000}, + {31, 250000}, + {62, 500000}, + {125, 0}, + {250, 0}, +}; + +static const int vcnl4040_ps_it_times[][2] = { + {0, 100}, + {0, 150}, + {0, 200}, + {0, 250}, + {0, 300}, + {0, 350}, + {0, 400}, + {0, 800}, +}; + +static const int vcnl4200_ps_it_times[][2] = { + {0, 96}, + {0, 144}, + {0, 192}, + {0, 384}, + {0, 768}, + {0, 864}, +}; + +static const int vcnl4040_als_it_times[][2] = { + {0, 80000}, + {0, 160000}, + {0, 320000}, + {0, 640000}, +}; + +static const int vcnl4200_als_it_times[][2] = { + {0, 50000}, + {0, 100000}, + {0, 200000}, + {0, 400000}, +}; + +static const int vcnl4040_ps_calibbias_ua[][2] = { + {0, 50000}, + {0, 75000}, + {0, 100000}, + {0, 120000}, + {0, 140000}, + {0, 160000}, + {0, 180000}, + {0, 200000}, +}; + +static const int vcnl4040_als_persistence[] = {1, 2, 4, 8}; +static const int vcnl4040_ps_persistence[] = {1, 2, 3, 4}; +static const int vcnl4040_ps_oversampling_ratio[] = {1, 2, 4, 8}; + +#define VCNL4000_SLEEP_DELAY_MS 2000 /* before we enter pm_runtime_suspend */ + +struct vcnl4200_channel { + u8 reg; + ktime_t last_measurement; + ktime_t sampling_rate; + struct mutex lock; +}; + +struct vcnl4000_data { + struct i2c_client *client; + int rev; + int al_scale; + int ps_scale; + u8 ps_int; /* proximity interrupt mode */ + u8 als_int; /* ambient light interrupt mode*/ + const struct vcnl4000_chip_spec *chip_spec; + struct mutex vcnl4000_lock; + struct vcnl4200_channel vcnl4200_al; + struct vcnl4200_channel vcnl4200_ps; + uint32_t near_level; +}; + +struct vcnl4000_chip_spec { + const char *prod; + struct iio_chan_spec const *channels; + const int num_channels; + const struct iio_info *info; + const struct iio_buffer_setup_ops *buffer_setup_ops; + int (*init)(struct vcnl4000_data *data); + int (*measure_light)(struct vcnl4000_data *data, int *val); + int (*measure_proximity)(struct vcnl4000_data *data, int *val); + int (*set_power_state)(struct vcnl4000_data *data, bool on); + irqreturn_t (*irq_thread)(int irq, void *priv); + irqreturn_t (*trig_buffer_func)(int irq, void *priv); + + u8 int_reg; + const int(*ps_it_times)[][2]; + const int num_ps_it_times; + const int(*als_it_times)[][2]; + const int num_als_it_times; + const unsigned int ulux_step; + const int prod_id; +}; + +static int vcnl4000_set_power_state(struct vcnl4000_data *data, bool on) +{ + /* no suspend op */ + return 0; +} + +static int vcnl4000_init(struct vcnl4000_data *data) +{ + int ret, prod_id; + + ret = i2c_smbus_read_byte_data(data->client, VCNL4000_PROD_REV); + if (ret < 0) + return ret; + + prod_id = ret >> 4; + switch (prod_id) { + case VCNL4000_PROD_ID: + if (data->chip_spec->prod_id != VCNL4000_PROD_ID) + dev_warn(&data->client->dev, + "wrong device id, use vcnl4000"); + break; + case VCNL4010_PROD_ID: + if (data->chip_spec->prod_id != VCNL4010_PROD_ID) + dev_warn(&data->client->dev, + "wrong device id, use vcnl4010/4020"); + break; + default: + return -ENODEV; + } + + data->rev = ret & 0xf; + data->al_scale = 250000; + + return 0; +}; + +static ssize_t vcnl4000_write_als_enable(struct vcnl4000_data *data, bool en) +{ + int ret; + + guard(mutex)(&data->vcnl4000_lock); + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF); + if (ret < 0) + return ret; + + if (en) + ret &= ~VCNL4040_ALS_CONF_ALS_SHUTDOWN; + else + ret |= VCNL4040_ALS_CONF_ALS_SHUTDOWN; + + return i2c_smbus_write_word_data(data->client, VCNL4200_AL_CONF, ret); +} + +static ssize_t vcnl4000_write_ps_enable(struct vcnl4000_data *data, bool en) +{ + int ret; + + guard(mutex)(&data->vcnl4000_lock); + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1); + if (ret < 0) + return ret; + + if (en) + ret &= ~VCNL4040_PS_CONF1_PS_SHUTDOWN; + else + ret |= VCNL4040_PS_CONF1_PS_SHUTDOWN; + + return i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF1, ret); +} + +static int vcnl4200_set_power_state(struct vcnl4000_data *data, bool on) +{ + int ret; + + /* Do not power down if interrupts are enabled */ + if (!on && (data->ps_int || data->als_int)) + return 0; + + ret = vcnl4000_write_als_enable(data, on); + if (ret < 0) + return ret; + + ret = vcnl4000_write_ps_enable(data, on); + if (ret < 0) + return ret; + + if (on) { + /* Wait at least one integration cycle before fetching data */ + data->vcnl4200_al.last_measurement = ktime_get(); + data->vcnl4200_ps.last_measurement = ktime_get(); + } + + return 0; +} + +static int vcnl4200_init(struct vcnl4000_data *data) +{ + struct i2c_client *client = data->client; + struct device *dev = &client->dev; + int ret, id; + u16 regval; + + ret = i2c_smbus_read_word_data(client, VCNL4200_DEV_ID); + if (ret < 0) + return ret; + + id = ret & 0xff; + + if (id != VCNL4200_PROD_ID) { + ret = i2c_smbus_read_word_data(client, VCNL4040_DEV_ID); + if (ret < 0) + return ret; + + id = ret & 0xff; + + if (id != VCNL4040_PROD_ID) + return -ENODEV; + } + + dev_dbg(dev, "device id 0x%x", id); + + data->rev = (ret >> 8) & 0xf; + data->ps_int = 0; + data->als_int = 0; + + data->vcnl4200_al.reg = VCNL4200_AL_DATA; + data->vcnl4200_ps.reg = VCNL4200_PS_DATA; + switch (id) { + case VCNL4200_PROD_ID: + /* Default wait time is 50ms, add 20% tolerance. */ + data->vcnl4200_al.sampling_rate = ktime_set(0, 60000 * 1000); + /* Default wait time is 4.8ms, add 20% tolerance. */ + data->vcnl4200_ps.sampling_rate = ktime_set(0, 5760 * 1000); + break; + case VCNL4040_PROD_ID: + /* Default wait time is 80ms, add 20% tolerance. */ + data->vcnl4200_al.sampling_rate = ktime_set(0, 96000 * 1000); + /* Default wait time is 5ms, add 20% tolerance. */ + data->vcnl4200_ps.sampling_rate = ktime_set(0, 6000 * 1000); + break; + } + data->al_scale = data->chip_spec->ulux_step; + data->ps_scale = 16; + + ret = devm_mutex_init(dev, &data->vcnl4200_al.lock); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &data->vcnl4200_ps.lock); + if (ret) + return ret; + + /* Use 16 bits proximity sensor readings */ + ret = i2c_smbus_read_word_data(client, VCNL4200_PS_CONF1); + if (ret < 0) + return ret; + + regval = ret | VCNL4040_PS_CONF2_PS_HD; + ret = i2c_smbus_write_word_data(client, VCNL4200_PS_CONF1, regval); + if (ret < 0) + return ret; + + /* Align proximity sensor sample rate to 16 bits data width */ + ret = i2c_smbus_read_word_data(client, VCNL4200_PS_CONF3); + if (ret < 0) + return ret; + + regval = ret | VCNL4040_CONF3_PS_SAMPLE_16BITS; + ret = i2c_smbus_write_word_data(client, VCNL4200_PS_CONF3, regval); + if (ret < 0) + return ret; + + return 0; +}; + +static int vcnl4000_read_data(struct vcnl4000_data *data, u8 data_reg, int *val) +{ + s32 ret; + + ret = i2c_smbus_read_word_swapped(data->client, data_reg); + if (ret < 0) + return ret; + + *val = ret; + return 0; +} + +static int vcnl4000_write_data(struct vcnl4000_data *data, u8 data_reg, int val) +{ + if (val > U16_MAX) + return -ERANGE; + + return i2c_smbus_write_word_swapped(data->client, data_reg, val); +} + + +static int vcnl4000_measure(struct vcnl4000_data *data, u8 req_mask, + u8 rdy_mask, u8 data_reg, int *val) +{ + int tries = 20; + int ret; + + guard(mutex)(&data->vcnl4000_lock); + + ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, + req_mask); + if (ret < 0) + return ret; + + /* wait for data to become ready */ + while (tries--) { + ret = i2c_smbus_read_byte_data(data->client, VCNL4000_COMMAND); + if (ret < 0) + return ret; + if (ret & rdy_mask) + break; + msleep(20); /* measurement takes up to 100 ms */ + } + + if (tries < 0) { + dev_err(&data->client->dev, + "vcnl4000_measure() failed, data not ready\n"); + return -EIO; + } + + return vcnl4000_read_data(data, data_reg, val); +} + +static int vcnl4200_measure(struct vcnl4000_data *data, + struct vcnl4200_channel *chan, int *val) +{ + int ret; + s64 delta; + ktime_t next_measurement; + + scoped_guard(mutex, &chan->lock) { + next_measurement = ktime_add(chan->last_measurement, + chan->sampling_rate); + delta = ktime_us_delta(next_measurement, ktime_get()); + if (delta > 0) + usleep_range(delta, delta + 500); + chan->last_measurement = ktime_get(); + } + + ret = i2c_smbus_read_word_data(data->client, chan->reg); + if (ret < 0) + return ret; + + *val = ret; + + return 0; +} + +static int vcnl4000_measure_light(struct vcnl4000_data *data, int *val) +{ + return vcnl4000_measure(data, + VCNL4000_AL_OD, VCNL4000_AL_RDY, + VCNL4000_AL_RESULT_HI, val); +} + +static int vcnl4200_measure_light(struct vcnl4000_data *data, int *val) +{ + return vcnl4200_measure(data, &data->vcnl4200_al, val); +} + +static int vcnl4000_measure_proximity(struct vcnl4000_data *data, int *val) +{ + return vcnl4000_measure(data, + VCNL4000_PS_OD, VCNL4000_PS_RDY, + VCNL4000_PS_RESULT_HI, val); +} + +static int vcnl4200_measure_proximity(struct vcnl4000_data *data, int *val) +{ + return vcnl4200_measure(data, &data->vcnl4200_ps, val); +} + +static int vcnl4010_read_proxy_samp_freq(struct vcnl4000_data *data, int *val, + int *val2) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, VCNL4010_PROX_RATE); + if (ret < 0) + return ret; + + if (ret >= ARRAY_SIZE(vcnl4010_prox_sampling_frequency)) + return -EINVAL; + + *val = vcnl4010_prox_sampling_frequency[ret][0]; + *val2 = vcnl4010_prox_sampling_frequency[ret][1]; + + return 0; +} + +static bool vcnl4010_is_in_periodic_mode(struct vcnl4000_data *data) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, VCNL4000_COMMAND); + if (ret < 0) + return false; + + return !!(ret & VCNL4000_SELF_TIMED_EN); +} + +static int vcnl4040_read_als_it(struct vcnl4000_data *data, int *val, int *val2) +{ + int ret; + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF); + if (ret < 0) + return ret; + + ret = FIELD_GET(VCNL4040_ALS_CONF_IT, ret); + if (ret >= data->chip_spec->num_als_it_times) + return -EINVAL; + + *val = (*data->chip_spec->als_it_times)[ret][0]; + *val2 = (*data->chip_spec->als_it_times)[ret][1]; + + return 0; +} + +static ssize_t vcnl4040_write_als_it(struct vcnl4000_data *data, int val) +{ + unsigned int i; + int ret; + u16 regval; + + for (i = 0; i < data->chip_spec->num_als_it_times; i++) { + if (val == (*data->chip_spec->als_it_times)[i][1]) + break; + } + + if (i == data->chip_spec->num_als_it_times) + return -EINVAL; + + data->vcnl4200_al.sampling_rate = ktime_set(0, val * 1200); + data->al_scale = div_u64(mul_u32_u32(data->chip_spec->ulux_step, + (*data->chip_spec->als_it_times)[0][1]), + val); + + guard(mutex)(&data->vcnl4000_lock); + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF); + if (ret < 0) + return ret; + + regval = FIELD_PREP(VCNL4040_ALS_CONF_IT, i); + regval |= (ret & ~VCNL4040_ALS_CONF_IT); + return i2c_smbus_write_word_data(data->client, VCNL4200_AL_CONF, regval); +} + +static int vcnl4040_read_ps_it(struct vcnl4000_data *data, int *val, int *val2) +{ + int ret; + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1); + if (ret < 0) + return ret; + + ret = FIELD_GET(VCNL4040_PS_CONF2_PS_IT, ret); + + if (ret >= data->chip_spec->num_ps_it_times) + return -EINVAL; + + *val = (*data->chip_spec->ps_it_times)[ret][0]; + *val2 = (*data->chip_spec->ps_it_times)[ret][1]; + + return 0; +} + +static ssize_t vcnl4040_write_ps_it(struct vcnl4000_data *data, int val) +{ + unsigned int i; + int ret, index = -1; + u16 regval; + + for (i = 0; i < data->chip_spec->num_ps_it_times; i++) { + if (val == (*data->chip_spec->ps_it_times)[i][1]) { + index = i; + break; + } + } + + if (index < 0) + return -EINVAL; + + data->vcnl4200_ps.sampling_rate = ktime_set(0, val * 60 * NSEC_PER_USEC); + + guard(mutex)(&data->vcnl4000_lock); + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1); + if (ret < 0) + return ret; + + regval = (ret & ~VCNL4040_PS_CONF2_PS_IT) | + FIELD_PREP(VCNL4040_PS_CONF2_PS_IT, index); + return i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF1, regval); +} + +static ssize_t vcnl4040_read_als_period(struct vcnl4000_data *data, int *val, int *val2) +{ + int ret, ret_pers, it; + int64_t val_c; + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF); + if (ret < 0) + return ret; + + ret_pers = FIELD_GET(VCNL4040_ALS_CONF_PERS, ret); + if (ret_pers >= ARRAY_SIZE(vcnl4040_als_persistence)) + return -EINVAL; + + it = FIELD_GET(VCNL4040_ALS_CONF_IT, ret); + if (it >= data->chip_spec->num_als_it_times) + return -EINVAL; + + val_c = mul_u32_u32((*data->chip_spec->als_it_times)[it][1], + vcnl4040_als_persistence[ret_pers]); + *val = div_u64_rem(val_c, MICRO, val2); + + return IIO_VAL_INT_PLUS_MICRO; +} + +static ssize_t vcnl4040_write_als_period(struct vcnl4000_data *data, int val, int val2) +{ + unsigned int i; + int ret, it; + u16 regval; + u64 val_n = mul_u32_u32(val, MICRO) + val2; + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF); + if (ret < 0) + return ret; + + it = FIELD_GET(VCNL4040_ALS_CONF_IT, ret); + if (it >= data->chip_spec->num_als_it_times) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(vcnl4040_als_persistence) - 1; i++) { + if (val_n < mul_u32_u32(vcnl4040_als_persistence[i], + (*data->chip_spec->als_it_times)[it][1])) + break; + } + + guard(mutex)(&data->vcnl4000_lock); + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF); + if (ret < 0) + return ret; + + regval = FIELD_PREP(VCNL4040_ALS_CONF_PERS, i); + regval |= (ret & ~VCNL4040_ALS_CONF_PERS); + return i2c_smbus_write_word_data(data->client, VCNL4200_AL_CONF, regval); +} + +static ssize_t vcnl4040_read_ps_period(struct vcnl4000_data *data, int *val, int *val2) +{ + int ret, ret_pers, it; + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1); + if (ret < 0) + return ret; + + ret_pers = FIELD_GET(VCNL4040_CONF1_PS_PERS, ret); + if (ret_pers >= ARRAY_SIZE(vcnl4040_ps_persistence)) + return -EINVAL; + + it = FIELD_GET(VCNL4040_PS_CONF2_PS_IT, ret); + if (it >= data->chip_spec->num_ps_it_times) + return -EINVAL; + + *val = (*data->chip_spec->ps_it_times)[it][0]; + *val2 = (*data->chip_spec->ps_it_times)[it][1] * + vcnl4040_ps_persistence[ret_pers]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static ssize_t vcnl4040_write_ps_period(struct vcnl4000_data *data, int val, int val2) +{ + int ret, it, i; + u16 regval; + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1); + if (ret < 0) + return ret; + + it = FIELD_GET(VCNL4040_PS_CONF2_PS_IT, ret); + if (it >= data->chip_spec->num_ps_it_times) + return -EINVAL; + + if (val > 0) + i = ARRAY_SIZE(vcnl4040_ps_persistence) - 1; + else { + for (i = 0; i < ARRAY_SIZE(vcnl4040_ps_persistence) - 1; i++) { + if (val2 <= vcnl4040_ps_persistence[i] * + (*data->chip_spec->ps_it_times)[it][1]) + break; + } + } + + guard(mutex)(&data->vcnl4000_lock); + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1); + if (ret < 0) + return ret; + + regval = FIELD_PREP(VCNL4040_CONF1_PS_PERS, i); + regval |= (ret & ~VCNL4040_CONF1_PS_PERS); + return i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF1, regval); +} + +static ssize_t vcnl4040_read_ps_oversampling_ratio(struct vcnl4000_data *data, int *val) +{ + int ret; + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF3); + if (ret < 0) + return ret; + + ret = FIELD_GET(VCNL4040_PS_CONF3_MPS, ret); + if (ret >= ARRAY_SIZE(vcnl4040_ps_oversampling_ratio)) + return -EINVAL; + + *val = vcnl4040_ps_oversampling_ratio[ret]; + + return ret; +} + +static ssize_t vcnl4040_write_ps_oversampling_ratio(struct vcnl4000_data *data, int val) +{ + unsigned int i; + int ret; + u16 regval; + + for (i = 0; i < ARRAY_SIZE(vcnl4040_ps_oversampling_ratio); i++) { + if (val == vcnl4040_ps_oversampling_ratio[i]) + break; + } + + if (i >= ARRAY_SIZE(vcnl4040_ps_oversampling_ratio)) + return -EINVAL; + + guard(mutex)(&data->vcnl4000_lock); + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF3); + if (ret < 0) + return ret; + + regval = FIELD_PREP(VCNL4040_PS_CONF3_MPS, i); + regval |= (ret & ~VCNL4040_PS_CONF3_MPS); + return i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF3, regval); +} + +static ssize_t vcnl4040_read_ps_calibbias(struct vcnl4000_data *data, int *val, int *val2) +{ + int ret; + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF3); + if (ret < 0) + return ret; + + ret = FIELD_GET(VCNL4040_PS_MS_LED_I, ret); + if (ret >= ARRAY_SIZE(vcnl4040_ps_calibbias_ua)) + return -EINVAL; + + *val = vcnl4040_ps_calibbias_ua[ret][0]; + *val2 = vcnl4040_ps_calibbias_ua[ret][1]; + + return ret; +} + +static ssize_t vcnl4040_write_ps_calibbias(struct vcnl4000_data *data, int val) +{ + unsigned int i; + int ret; + u16 regval; + + for (i = 0; i < ARRAY_SIZE(vcnl4040_ps_calibbias_ua); i++) { + if (val == vcnl4040_ps_calibbias_ua[i][1]) + break; + } + + if (i >= ARRAY_SIZE(vcnl4040_ps_calibbias_ua)) + return -EINVAL; + + guard(mutex)(&data->vcnl4000_lock); + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF3); + if (ret < 0) + return ret; + + regval = (ret & ~VCNL4040_PS_MS_LED_I); + regval |= FIELD_PREP(VCNL4040_PS_MS_LED_I, i); + return i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF3, regval); +} + +static int vcnl4000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = pm_runtime_resume_and_get(&data->client->dev); + if (ret < 0) + return ret; + + switch (chan->type) { + case IIO_LIGHT: + ret = data->chip_spec->measure_light(data, val); + if (!ret) + ret = IIO_VAL_INT; + break; + case IIO_PROXIMITY: + ret = data->chip_spec->measure_proximity(data, val); + *val2 = data->ps_scale; + if (!ret) + ret = IIO_VAL_FRACTIONAL; + break; + default: + ret = -EINVAL; + } + pm_runtime_put_autosuspend(&data->client->dev); + return ret; + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_LIGHT) + return -EINVAL; + + *val = 0; + *val2 = data->al_scale; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_LIGHT: + ret = vcnl4040_read_als_it(data, val, val2); + break; + case IIO_PROXIMITY: + ret = vcnl4040_read_ps_it(data, val, val2); + break; + default: + return -EINVAL; + } + if (ret < 0) + return ret; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_PROXIMITY: + ret = vcnl4040_read_ps_oversampling_ratio(data, val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_PROXIMITY: + ret = vcnl4040_read_ps_calibbias(data, val, val2); + if (ret < 0) + return ret; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int vcnl4040_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + switch (chan->type) { + case IIO_LIGHT: + return vcnl4040_write_als_it(data, val2); + case IIO_PROXIMITY: + return vcnl4040_write_ps_it(data, val2); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_PROXIMITY: + return vcnl4040_write_ps_oversampling_ratio(data, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_PROXIMITY: + return vcnl4040_write_ps_calibbias(data, val2); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int vcnl4040_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_LIGHT: + *vals = (int *)(*data->chip_spec->als_it_times); + *length = 2 * data->chip_spec->num_als_it_times; + break; + case IIO_PROXIMITY: + *vals = (int *)(*data->chip_spec->ps_it_times); + *length = 2 * data->chip_spec->num_ps_it_times; + break; + default: + return -EINVAL; + } + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_PROXIMITY: + *vals = (int *)vcnl4040_ps_oversampling_ratio; + *length = ARRAY_SIZE(vcnl4040_ps_oversampling_ratio); + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_PROXIMITY: + *vals = (int *)vcnl4040_ps_calibbias_ua; + *length = 2 * ARRAY_SIZE(vcnl4040_ps_calibbias_ua); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int vcnl4010_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_SCALE: { + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + /* Protect against event capture. */ + if (vcnl4010_is_in_periodic_mode(data)) + return -EBUSY; + + return vcnl4000_read_raw(indio_dev, chan, val, val2, mask); + } + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_PROXIMITY: + ret = vcnl4010_read_proxy_samp_freq(data, val, val2); + if (ret < 0) + return ret; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int vcnl4010_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_SAMP_FREQ: + *vals = (int *)vcnl4010_prox_sampling_frequency; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = 2 * ARRAY_SIZE(vcnl4010_prox_sampling_frequency); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int vcnl4010_write_proxy_samp_freq(struct vcnl4000_data *data, int val, + int val2) +{ + unsigned int i; + int index = -1; + + for (i = 0; i < ARRAY_SIZE(vcnl4010_prox_sampling_frequency); i++) { + if (val == vcnl4010_prox_sampling_frequency[i][0] && + val2 == vcnl4010_prox_sampling_frequency[i][1]) { + index = i; + break; + } + } + + if (index < 0) + return -EINVAL; + + return i2c_smbus_write_byte_data(data->client, VCNL4010_PROX_RATE, + index); +} + +static int vcnl4010_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + /* Protect against event capture. */ + if (vcnl4010_is_in_periodic_mode(data)) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_PROXIMITY: + return vcnl4010_write_proxy_samp_freq(data, val, val2); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int vcnl4010_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + int ret; + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = vcnl4000_read_data(data, VCNL4010_HIGH_THR_HI, + val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + ret = vcnl4000_read_data(data, VCNL4010_LOW_THR_HI, + val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int vcnl4010_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + int ret; + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = vcnl4000_write_data(data, VCNL4010_HIGH_THR_HI, + val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + ret = vcnl4000_write_data(data, VCNL4010_LOW_THR_HI, + val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int vcnl4040_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + int ret; + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_LIGHT: + switch (info) { + case IIO_EV_INFO_PERIOD: + return vcnl4040_read_als_period(data, val, val2); + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = i2c_smbus_read_word_data(data->client, + VCNL4040_ALS_THDH_LM); + break; + case IIO_EV_DIR_FALLING: + ret = i2c_smbus_read_word_data(data->client, + VCNL4040_ALS_THDL_LM); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + break; + case IIO_PROXIMITY: + switch (info) { + case IIO_EV_INFO_PERIOD: + return vcnl4040_read_ps_period(data, val, val2); + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = i2c_smbus_read_word_data(data->client, + VCNL4040_PS_THDH_LM); + break; + case IIO_EV_DIR_FALLING: + ret = i2c_smbus_read_word_data(data->client, + VCNL4040_PS_THDL_LM); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; +} + +static int vcnl4040_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + int ret; + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_LIGHT: + switch (info) { + case IIO_EV_INFO_PERIOD: + return vcnl4040_write_als_period(data, val, val2); + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = i2c_smbus_write_word_data(data->client, + VCNL4040_ALS_THDH_LM, + val); + break; + case IIO_EV_DIR_FALLING: + ret = i2c_smbus_write_word_data(data->client, + VCNL4040_ALS_THDL_LM, + val); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + break; + case IIO_PROXIMITY: + switch (info) { + case IIO_EV_INFO_PERIOD: + return vcnl4040_write_ps_period(data, val, val2); + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = i2c_smbus_write_word_data(data->client, + VCNL4040_PS_THDH_LM, + val); + break; + case IIO_EV_DIR_FALLING: + ret = i2c_smbus_write_word_data(data->client, + VCNL4040_PS_THDL_LM, + val); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + if (ret < 0) + return ret; + return IIO_VAL_INT; +} + +static bool vcnl4010_is_thr_enabled(struct vcnl4000_data *data) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, VCNL4010_INT_CTRL); + if (ret < 0) + return false; + + return !!(ret & VCNL4010_INT_THR_EN); +} + +static int vcnl4010_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_PROXIMITY: + return vcnl4010_is_thr_enabled(data); + default: + return -EINVAL; + } +} + +static int vcnl4010_config_threshold_enable(struct vcnl4000_data *data) +{ + int ret; + + /* Enable periodic measurement of proximity data. */ + ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, + VCNL4000_SELF_TIMED_EN | VCNL4000_PROX_EN); + if (ret < 0) + return ret; + + /* + * Enable interrupts on threshold, for proximity data by + * default. + */ + return i2c_smbus_write_byte_data(data->client, VCNL4010_INT_CTRL, + VCNL4010_INT_THR_EN); +} + +static int vcnl4010_config_threshold_disable(struct vcnl4000_data *data) +{ + int ret; + + if (!vcnl4010_is_thr_enabled(data)) + return 0; + + ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, 0); + if (ret < 0) + return ret; + + return i2c_smbus_write_byte_data(data->client, VCNL4010_INT_CTRL, 0); +} + +static int vcnl4010_config_threshold(struct iio_dev *indio_dev, bool state) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + + if (state) { + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + return vcnl4010_config_threshold_enable(data); + } else { + return vcnl4010_config_threshold_disable(data); + } +} + +static int vcnl4010_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + switch (chan->type) { + case IIO_PROXIMITY: + return vcnl4010_config_threshold(indio_dev, state); + default: + return -EINVAL; + } +} + +static int vcnl4040_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + int ret; + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_LIGHT: + ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF); + if (ret < 0) + return ret; + + data->als_int = FIELD_GET(VCNL4040_ALS_CONF_INT_EN, ret); + + return data->als_int; + case IIO_PROXIMITY: + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1); + if (ret < 0) + return ret; + + data->ps_int = FIELD_GET(VCNL4040_PS_CONF2_PS_INT, ret); + + return (dir == IIO_EV_DIR_RISING) ? + FIELD_GET(VCNL4040_PS_IF_AWAY, ret) : + FIELD_GET(VCNL4040_PS_IF_CLOSE, ret); + default: + return -EINVAL; + } +} + +static int vcnl4040_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + int ret; + u16 val, mask; + struct vcnl4000_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->vcnl4000_lock); + + switch (chan->type) { + case IIO_LIGHT: + ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF); + if (ret < 0) + return ret; + + mask = VCNL4040_ALS_CONF_INT_EN; + if (state) + val = (ret | mask); + else + val = (ret & ~mask); + + data->als_int = FIELD_GET(VCNL4040_ALS_CONF_INT_EN, val); + return i2c_smbus_write_word_data(data->client, VCNL4200_AL_CONF, val); + case IIO_PROXIMITY: + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1); + if (ret < 0) + return ret; + + if (dir == IIO_EV_DIR_RISING) + mask = VCNL4040_PS_IF_AWAY; + else + mask = VCNL4040_PS_IF_CLOSE; + + val = state ? (ret | mask) : (ret & ~mask); + + data->ps_int = FIELD_GET(VCNL4040_PS_CONF2_PS_INT, val); + return i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF1, val); + default: + return -EINVAL; + } +} + +static irqreturn_t vcnl4040_irq_thread(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct vcnl4000_data *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_word_data(data->client, data->chip_spec->int_reg); + if (ret < 0) + return IRQ_HANDLED; + + if (ret & VCNL4040_PS_IF_CLOSE) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + } + + if (ret & VCNL4040_PS_IF_AWAY) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + } + + if (ret & VCNL4040_ALS_FALLING) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + } + + if (ret & VCNL4040_ALS_RISING) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + } + + return IRQ_HANDLED; +} + +static ssize_t vcnl4000_read_near_level(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + char *buf) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", data->near_level); +} + +static irqreturn_t vcnl4010_irq_thread(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct vcnl4000_data *data = iio_priv(indio_dev); + unsigned long isr; + int ret; + + ret = i2c_smbus_read_byte_data(data->client, VCNL4010_ISR); + if (ret < 0) + goto end; + + isr = ret; + + if (isr & VCNL4010_INT_THR) { + if (test_bit(VCNL4010_INT_THR_LOW, &isr)) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE( + IIO_PROXIMITY, + 1, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + } + + if (test_bit(VCNL4010_INT_THR_HIGH, &isr)) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE( + IIO_PROXIMITY, + 1, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + } + + i2c_smbus_write_byte_data(data->client, VCNL4010_ISR, + isr & VCNL4010_INT_THR); + } + + if (isr & VCNL4010_INT_DRDY && iio_buffer_enabled(indio_dev)) + iio_trigger_poll_nested(indio_dev->trig); + +end: + return IRQ_HANDLED; +} + +static irqreturn_t vcnl4010_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct vcnl4000_data *data = iio_priv(indio_dev); + const unsigned long *active_scan_mask = indio_dev->active_scan_mask; + struct { + u16 chan; + aligned_s64 ts; + } scan = { }; + bool data_read = false; + unsigned long isr; + int val = 0; + int ret; + + ret = i2c_smbus_read_byte_data(data->client, VCNL4010_ISR); + if (ret < 0) + goto end; + + isr = ret; + + if (test_bit(0, active_scan_mask)) { + if (test_bit(VCNL4010_INT_PROXIMITY, &isr)) { + ret = vcnl4000_read_data(data, + VCNL4000_PS_RESULT_HI, + &val); + if (ret < 0) + goto end; + + scan.chan = val; + data_read = true; + } + } + + ret = i2c_smbus_write_byte_data(data->client, VCNL4010_ISR, + isr & VCNL4010_INT_DRDY); + if (ret < 0) + goto end; + + if (!data_read) + goto end; + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + +end: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int vcnl4010_buffer_postenable(struct iio_dev *indio_dev) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + int ret; + int cmd; + + /* Do not enable the buffer if we are already capturing events. */ + if (vcnl4010_is_in_periodic_mode(data)) + return -EBUSY; + + ret = i2c_smbus_write_byte_data(data->client, VCNL4010_INT_CTRL, + VCNL4010_INT_PROX_EN); + if (ret < 0) + return ret; + + cmd = VCNL4000_SELF_TIMED_EN | VCNL4000_PROX_EN; + return i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, cmd); +} + +static int vcnl4010_buffer_predisable(struct iio_dev *indio_dev) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_write_byte_data(data->client, VCNL4010_INT_CTRL, 0); + if (ret < 0) + return ret; + + return i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, 0); +} + +static const struct iio_buffer_setup_ops vcnl4010_buffer_ops = { + .postenable = &vcnl4010_buffer_postenable, + .predisable = &vcnl4010_buffer_predisable, +}; + +static const struct iio_chan_spec_ext_info vcnl4000_ext_info[] = { + { + .name = "nearlevel", + .shared = IIO_SEPARATE, + .read = vcnl4000_read_near_level, + }, + { } +}; + +static const struct iio_event_spec vcnl4000_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + } +}; + +static const struct iio_event_spec vcnl4040_als_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_PERIOD), + }, +}; + +static const struct iio_event_spec vcnl4040_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_PERIOD), + }, +}; + +static const struct iio_chan_spec vcnl4000_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .ext_info = vcnl4000_ext_info, + } +}; + +static const struct iio_chan_spec vcnl4010_channels[] = { + { + .type = IIO_LIGHT, + .scan_index = -1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_PROXIMITY, + .scan_index = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .event_spec = vcnl4000_event_spec, + .num_event_specs = ARRAY_SIZE(vcnl4000_event_spec), + .ext_info = vcnl4000_ext_info, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct iio_chan_spec vcnl4040_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME), + .event_spec = vcnl4040_als_event_spec, + .num_event_specs = ARRAY_SIZE(vcnl4040_als_event_spec), + }, { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .ext_info = vcnl4000_ext_info, + .event_spec = vcnl4040_event_spec, + .num_event_specs = ARRAY_SIZE(vcnl4040_event_spec), + } +}; + +static const struct iio_chan_spec cm36672p_channels[] = { + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .ext_info = vcnl4000_ext_info, + .event_spec = vcnl4040_event_spec, + .num_event_specs = ARRAY_SIZE(vcnl4040_event_spec), + }, +}; + +static const struct iio_info vcnl4000_info = { + .read_raw = vcnl4000_read_raw, +}; + +static const struct iio_info vcnl4010_info = { + .read_raw = vcnl4010_read_raw, + .read_avail = vcnl4010_read_avail, + .write_raw = vcnl4010_write_raw, + .read_event_value = vcnl4010_read_event, + .write_event_value = vcnl4010_write_event, + .read_event_config = vcnl4010_read_event_config, + .write_event_config = vcnl4010_write_event_config, +}; + +static const struct iio_info vcnl4040_info = { + .read_raw = vcnl4000_read_raw, + .write_raw = vcnl4040_write_raw, + .read_event_value = vcnl4040_read_event, + .write_event_value = vcnl4040_write_event, + .read_event_config = vcnl4040_read_event_config, + .write_event_config = vcnl4040_write_event_config, + .read_avail = vcnl4040_read_avail, +}; + +static const struct vcnl4000_chip_spec cm36672p_spec = { + .prod = "CM36672P", + .init = vcnl4200_init, + .measure_proximity = vcnl4200_measure_proximity, + .set_power_state = vcnl4200_set_power_state, + .channels = cm36672p_channels, + .num_channels = ARRAY_SIZE(cm36672p_channels), + .info = &vcnl4040_info, + .irq_thread = vcnl4040_irq_thread, + .int_reg = VCNL4040_INT_FLAGS, + .ps_it_times = &vcnl4040_ps_it_times, + .num_ps_it_times = ARRAY_SIZE(vcnl4040_ps_it_times), + .prod_id = VCNL4040_PROD_ID, +}; + +static const struct vcnl4000_chip_spec vcnl4000_spec = { + .prod = "VCNL4000", + .init = vcnl4000_init, + .measure_light = vcnl4000_measure_light, + .measure_proximity = vcnl4000_measure_proximity, + .set_power_state = vcnl4000_set_power_state, + .channels = vcnl4000_channels, + .num_channels = ARRAY_SIZE(vcnl4000_channels), + .info = &vcnl4000_info, + .prod_id = VCNL4000_PROD_ID, +}; + +static const struct vcnl4000_chip_spec vcnl4010_spec = { + .prod = "VCNL4010/4020", + .init = vcnl4000_init, + .measure_light = vcnl4000_measure_light, + .measure_proximity = vcnl4000_measure_proximity, + .set_power_state = vcnl4000_set_power_state, + .channels = vcnl4010_channels, + .num_channels = ARRAY_SIZE(vcnl4010_channels), + .info = &vcnl4010_info, + .irq_thread = vcnl4010_irq_thread, + .trig_buffer_func = vcnl4010_trigger_handler, + .buffer_setup_ops = &vcnl4010_buffer_ops, + .prod_id = VCNL4010_PROD_ID, +}; + +static const struct vcnl4000_chip_spec vcnl4040_spec = { + .prod = "VCNL4040", + .init = vcnl4200_init, + .measure_light = vcnl4200_measure_light, + .measure_proximity = vcnl4200_measure_proximity, + .set_power_state = vcnl4200_set_power_state, + .channels = vcnl4040_channels, + .num_channels = ARRAY_SIZE(vcnl4040_channels), + .info = &vcnl4040_info, + .irq_thread = vcnl4040_irq_thread, + .int_reg = VCNL4040_INT_FLAGS, + .ps_it_times = &vcnl4040_ps_it_times, + .num_ps_it_times = ARRAY_SIZE(vcnl4040_ps_it_times), + .als_it_times = &vcnl4040_als_it_times, + .num_als_it_times = ARRAY_SIZE(vcnl4040_als_it_times), + .ulux_step = 100000, + .prod_id = VCNL4040_PROD_ID, +}; + +static const struct vcnl4000_chip_spec vcnl4200_spec = { + .prod = "VCNL4200", + .init = vcnl4200_init, + .measure_light = vcnl4200_measure_light, + .measure_proximity = vcnl4200_measure_proximity, + .set_power_state = vcnl4200_set_power_state, + .channels = vcnl4040_channels, + .num_channels = ARRAY_SIZE(vcnl4000_channels), + .info = &vcnl4040_info, + .irq_thread = vcnl4040_irq_thread, + .int_reg = VCNL4200_INT_FLAGS, + .ps_it_times = &vcnl4200_ps_it_times, + .num_ps_it_times = ARRAY_SIZE(vcnl4200_ps_it_times), + .als_it_times = &vcnl4200_als_it_times, + .num_als_it_times = ARRAY_SIZE(vcnl4200_als_it_times), + .ulux_step = 24000, + .prod_id = VCNL4200_PROD_ID, +}; + +static const struct iio_trigger_ops vcnl4010_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + +static int vcnl4010_probe_trigger(struct iio_dev *indio_dev) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + struct iio_trigger *trigger; + + trigger = devm_iio_trigger_alloc(&client->dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!trigger) + return -ENOMEM; + + trigger->ops = &vcnl4010_trigger_ops; + iio_trigger_set_drvdata(trigger, indio_dev); + + return devm_iio_trigger_register(&client->dev, trigger); +} + +static void vcnl4000_cleanup(void *data) +{ + struct iio_dev *indio_dev = data; + struct vcnl4000_data *chip = iio_priv(indio_dev); + struct device *dev = &chip->client->dev; + int ret; + + ret = chip->chip_spec->set_power_state(chip, false); + if (ret) + dev_warn(dev, "Failed to power down (%pe)", ERR_PTR(ret)); +} + +static int vcnl4000_probe(struct i2c_client *client) +{ + static const char * const regulator_names[] = { "vdd", "vio", "vled" }; + struct device *dev = &client->dev; + struct vcnl4000_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->chip_spec = i2c_get_match_data(client); + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names), + regulator_names); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &data->vcnl4000_lock); + if (ret) + return ret; + + ret = data->chip_spec->init(data); + if (ret < 0) + return ret; + + ret = data->chip_spec->set_power_state(data, true); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, vcnl4000_cleanup, indio_dev); + if (ret) + return ret; + + dev_dbg(dev, "%s Ambient light/proximity sensor, Rev: %02x\n", + data->chip_spec->prod, data->rev); + + device_property_read_u32(dev, "proximity-near-level", &data->near_level); + + indio_dev->info = data->chip_spec->info; + indio_dev->channels = data->chip_spec->channels; + indio_dev->num_channels = data->chip_spec->num_channels; + indio_dev->name = VCNL4000_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (data->chip_spec->trig_buffer_func && + data->chip_spec->buffer_setup_ops) { + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + data->chip_spec->trig_buffer_func, + data->chip_spec->buffer_setup_ops); + if (ret < 0) + return ret; + } + + if (client->irq && data->chip_spec->irq_thread) { + ret = devm_request_threaded_irq(dev, client->irq, NULL, + data->chip_spec->irq_thread, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "vcnl4000_irq", + indio_dev); + if (ret < 0) + return ret; + + ret = vcnl4010_probe_trigger(indio_dev); + if (ret < 0) + return ret; + } + + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + return ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(dev, VCNL4000_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(dev); + + return 0; +} + +static const struct of_device_id vcnl_4000_of_match[] = { + { .compatible = "capella,cm36672p", .data = &cm36672p_spec }, + /* Capella CM36686 is fully compatible with Vishay VCNL4040 */ + { .compatible = "capella,cm36686", .data = &vcnl4040_spec }, + { .compatible = "vishay,vcnl4000", .data = &vcnl4000_spec }, + { .compatible = "vishay,vcnl4010", .data = &vcnl4010_spec }, + { .compatible = "vishay,vcnl4020", .data = &vcnl4010_spec }, + { .compatible = "vishay,vcnl4040", .data = &vcnl4040_spec }, + { .compatible = "vishay,vcnl4200", .data = &vcnl4200_spec }, + { } +}; +MODULE_DEVICE_TABLE(of, vcnl_4000_of_match); + +static int vcnl4000_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct vcnl4000_data *data = iio_priv(indio_dev); + + return data->chip_spec->set_power_state(data, false); +} + +static int vcnl4000_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct vcnl4000_data *data = iio_priv(indio_dev); + + return data->chip_spec->set_power_state(data, true); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(vcnl4000_pm_ops, vcnl4000_runtime_suspend, + vcnl4000_runtime_resume, NULL); + +static const struct i2c_device_id vcnl4000_id[] = { + { .name = "cm36672p", .driver_data = (kernel_ulong_t)&cm36672p_spec }, + { .name = "cm36686", .driver_data = (kernel_ulong_t)&vcnl4040_spec }, + { .name = "vcnl4000", .driver_data = (kernel_ulong_t)&vcnl4000_spec }, + { .name = "vcnl4010", .driver_data = (kernel_ulong_t)&vcnl4010_spec }, + { .name = "vcnl4020", .driver_data = (kernel_ulong_t)&vcnl4010_spec }, + { .name = "vcnl4040", .driver_data = (kernel_ulong_t)&vcnl4040_spec }, + { .name = "vcnl4200", .driver_data = (kernel_ulong_t)&vcnl4200_spec }, + { } +}; +MODULE_DEVICE_TABLE(i2c, vcnl4000_id); + +static struct i2c_driver vcnl4000_driver = { + .driver = { + .name = VCNL4000_DRV_NAME, + .pm = pm_ptr(&vcnl4000_pm_ops), + .of_match_table = vcnl_4000_of_match, + }, + .probe = vcnl4000_probe, + .id_table = vcnl4000_id, +}; + +module_i2c_driver(vcnl4000_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_AUTHOR("Mathieu Othacehe <m.othacehe@gmail.com>"); +MODULE_DESCRIPTION("Vishay VCNL4000 proximity/ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/vcnl4035.c b/drivers/iio/light/vcnl4035.c new file mode 100644 index 000000000..ce1bd42b0 --- /dev/null +++ b/drivers/iio/light/vcnl4035.c @@ -0,0 +1,684 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * VCNL4035 Ambient Light and Proximity Sensor - 7-bit I2C slave address 0x60 + * + * Copyright (c) 2018, DENX Software Engineering GmbH + * Author: Parthiban Nallathambi <pn@denx.de> + * + * TODO: Proximity + */ +#include <linux/bitops.h> +#include <linux/bitfield.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define VCNL4035_DRV_NAME "vcnl4035" + +/* Device registers */ +#define VCNL4035_ALS_CONF 0x00 +#define VCNL4035_ALS_THDH 0x01 +#define VCNL4035_ALS_THDL 0x02 +#define VCNL4035_ALS_DATA 0x0B +#define VCNL4035_WHITE_DATA 0x0C +#define VCNL4035_INT_FLAG 0x0D +#define VCNL4035_DEV_ID 0x0E + +/* Register masks */ +#define VCNL4035_MODE_ALS_MASK BIT(0) +#define VCNL4035_MODE_ALS_WHITE_CHAN BIT(8) +#define VCNL4035_MODE_ALS_INT_MASK BIT(1) +#define VCNL4035_ALS_IT_MASK GENMASK(7, 5) +#define VCNL4035_ALS_PERS_MASK GENMASK(3, 2) +#define VCNL4035_INT_ALS_IF_H_MASK BIT(12) +#define VCNL4035_INT_ALS_IF_L_MASK BIT(13) +#define VCNL4035_DEV_ID_MASK GENMASK(7, 0) + +/* Default values */ +#define VCNL4035_MODE_ALS_ENABLE BIT(0) +#define VCNL4035_MODE_ALS_DISABLE 0x00 +#define VCNL4035_MODE_ALS_INT_ENABLE BIT(1) +#define VCNL4035_MODE_ALS_INT_DISABLE 0 +#define VCNL4035_DEV_ID_VAL 0x80 +#define VCNL4035_ALS_IT_DEFAULT 0x01 +#define VCNL4035_ALS_PERS_DEFAULT 0x00 +#define VCNL4035_ALS_THDH_DEFAULT 5000 +#define VCNL4035_ALS_THDL_DEFAULT 100 +#define VCNL4035_SLEEP_DELAY_MS 2000 + +struct vcnl4035_data { + struct i2c_client *client; + struct regmap *regmap; + unsigned int als_it_val; + unsigned int als_persistence; + unsigned int als_thresh_low; + unsigned int als_thresh_high; + struct iio_trigger *drdy_trigger0; +}; + +static inline bool vcnl4035_is_triggered(struct vcnl4035_data *data) +{ + int ret; + int reg; + + ret = regmap_read(data->regmap, VCNL4035_INT_FLAG, ®); + if (ret < 0) + return false; + + return !!(reg & + (VCNL4035_INT_ALS_IF_H_MASK | VCNL4035_INT_ALS_IF_L_MASK)); +} + +static irqreturn_t vcnl4035_drdy_irq_thread(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct vcnl4035_data *data = iio_priv(indio_dev); + + if (vcnl4035_is_triggered(data)) { + iio_push_event(indio_dev, IIO_UNMOD_EVENT_CODE(IIO_LIGHT, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + iio_trigger_poll_nested(data->drdy_trigger0); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +/* Triggered buffer */ +static irqreturn_t vcnl4035_trigger_consumer_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct vcnl4035_data *data = iio_priv(indio_dev); + /* Ensure naturally aligned timestamp */ + struct { + u16 als_data; + aligned_s64 timestamp; + } buffer = { }; + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, VCNL4035_ALS_DATA, &val); + if (ret < 0) { + dev_err(&data->client->dev, + "Trigger consumer can't read from sensor.\n"); + goto fail_read; + } + + buffer.als_data = val; + iio_push_to_buffers_with_timestamp(indio_dev, &buffer, + iio_get_time_ns(indio_dev)); + +fail_read: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int vcnl4035_als_drdy_set_state(struct iio_trigger *trigger, + bool enable_drdy) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trigger); + struct vcnl4035_data *data = iio_priv(indio_dev); + int val = enable_drdy ? VCNL4035_MODE_ALS_INT_ENABLE : + VCNL4035_MODE_ALS_INT_DISABLE; + + return regmap_update_bits(data->regmap, VCNL4035_ALS_CONF, + VCNL4035_MODE_ALS_INT_MASK, + val); +} + +static const struct iio_trigger_ops vcnl4035_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, + .set_trigger_state = vcnl4035_als_drdy_set_state, +}; + +static int vcnl4035_read_info_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + struct vcnl4035_data *data = iio_priv(indio_dev); + int ret; + int raw_data; + unsigned int reg; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + if (chan->channel) + reg = VCNL4035_ALS_DATA; + else + reg = VCNL4035_WHITE_DATA; + ret = regmap_read(data->regmap, reg, &raw_data); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + *val = raw_data; + + return IIO_VAL_INT; +} + +/* + * Device IT INT Time (ms) Scale (lux/step) + * 000 50 0.064 + * 001 100 0.032 + * 010 200 0.016 + * 100 400 0.008 + * 101 - 111 800 0.004 + * Values are proportional, so ALS INT is selected for input due to + * simplicity reason. Integration time value and scaling is + * calculated based on device INT value + * + * Raw value needs to be scaled using ALS steps + */ +static int vcnl4035_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct vcnl4035_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = pm_runtime_resume_and_get(&data->client->dev); + if (ret < 0) + return ret; + ret = vcnl4035_read_info_raw(indio_dev, chan, val); + pm_runtime_put_autosuspend(&data->client->dev); + return ret; + case IIO_CHAN_INFO_INT_TIME: + *val = 50; + if (data->als_it_val) + *val = data->als_it_val * 100; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 64; + if (!data->als_it_val) + *val2 = 1000; + else + *val2 = data->als_it_val * 2 * 1000; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +static int vcnl4035_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + struct vcnl4035_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val <= 0 || val > 800) + return -EINVAL; + + ret = pm_runtime_resume_and_get(&data->client->dev); + if (ret < 0) + return ret; + + ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF, + VCNL4035_ALS_IT_MASK, + val / 100); + if (!ret) + data->als_it_val = val / 100; + + pm_runtime_put_autosuspend(&data->client->dev); + return ret; + default: + return -EINVAL; + } +} + +/* No direct ABI for persistence and threshold, so eventing */ +static int vcnl4035_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, + int *val, int *val2) +{ + struct vcnl4035_data *data = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + *val = data->als_thresh_high; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + *val = data->als_thresh_low; + return IIO_VAL_INT; + default: + return -EINVAL; + } + break; + case IIO_EV_INFO_PERIOD: + *val = data->als_persistence; + return IIO_VAL_INT; + default: + return -EINVAL; + } + +} + +static int vcnl4035_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int val, + int val2) +{ + struct vcnl4035_data *data = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + /* 16 bit threshold range 0 - 65535 */ + if (val < 0 || val > 65535) + return -EINVAL; + if (dir == IIO_EV_DIR_RISING) { + if (val < data->als_thresh_low) + return -EINVAL; + ret = regmap_write(data->regmap, VCNL4035_ALS_THDH, + val); + if (ret) + return ret; + data->als_thresh_high = val; + } else { + if (val > data->als_thresh_high) + return -EINVAL; + ret = regmap_write(data->regmap, VCNL4035_ALS_THDL, + val); + if (ret) + return ret; + data->als_thresh_low = val; + } + return ret; + case IIO_EV_INFO_PERIOD: + /* allow only 1 2 4 8 as persistence value */ + if (val < 0 || val > 8 || hweight8(val) != 1) + return -EINVAL; + ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF, + VCNL4035_ALS_PERS_MASK, val); + if (!ret) + data->als_persistence = val; + return ret; + default: + return -EINVAL; + } +} + +static IIO_CONST_ATTR_INT_TIME_AVAIL("50 100 200 400 800"); + +static struct attribute *vcnl4035_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group vcnl4035_attribute_group = { + .attrs = vcnl4035_attributes, +}; + +static const struct iio_info vcnl4035_info = { + .read_raw = vcnl4035_read_raw, + .write_raw = vcnl4035_write_raw, + .read_event_value = vcnl4035_read_thresh, + .write_event_value = vcnl4035_write_thresh, + .attrs = &vcnl4035_attribute_group, +}; + +static const struct iio_event_spec vcnl4035_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_PERIOD), + }, +}; + +enum vcnl4035_scan_index_order { + VCNL4035_CHAN_INDEX_LIGHT, + VCNL4035_CHAN_INDEX_WHITE_LED, +}; + +static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = { + .validate_scan_mask = &iio_validate_scan_mask_onehot, +}; + +static const struct iio_chan_spec vcnl4035_channels[] = { + { + .type = IIO_LIGHT, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .event_spec = vcnl4035_event_spec, + .num_event_specs = ARRAY_SIZE(vcnl4035_event_spec), + .scan_index = VCNL4035_CHAN_INDEX_LIGHT, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_INTENSITY, + .channel = 1, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = VCNL4035_CHAN_INDEX_WHITE_LED, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, +}; + +static int vcnl4035_set_als_power_state(struct vcnl4035_data *data, u8 status) +{ + return regmap_update_bits(data->regmap, VCNL4035_ALS_CONF, + VCNL4035_MODE_ALS_MASK, + status); +} + +static int vcnl4035_init(struct vcnl4035_data *data) +{ + int ret; + int id; + + ret = regmap_read(data->regmap, VCNL4035_DEV_ID, &id); + if (ret < 0) { + dev_err(&data->client->dev, "Failed to read DEV_ID register\n"); + return ret; + } + + id = FIELD_GET(VCNL4035_DEV_ID_MASK, id); + if (id != VCNL4035_DEV_ID_VAL) { + dev_err(&data->client->dev, "Wrong id, got %x, expected %x\n", + id, VCNL4035_DEV_ID_VAL); + return -ENODEV; + } + + ret = vcnl4035_set_als_power_state(data, VCNL4035_MODE_ALS_ENABLE); + if (ret < 0) + return ret; + + /* ALS white channel enable */ + ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF, + VCNL4035_MODE_ALS_WHITE_CHAN, + 1); + if (ret) { + dev_err(&data->client->dev, "set white channel enable %d\n", + ret); + return ret; + } + + /* set default integration time - 100 ms for ALS */ + ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF, + VCNL4035_ALS_IT_MASK, + VCNL4035_ALS_IT_DEFAULT); + if (ret) { + dev_err(&data->client->dev, "set default ALS IT returned %d\n", + ret); + return ret; + } + data->als_it_val = VCNL4035_ALS_IT_DEFAULT; + + /* set default persistence time - 1 for ALS */ + ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF, + VCNL4035_ALS_PERS_MASK, + VCNL4035_ALS_PERS_DEFAULT); + if (ret) { + dev_err(&data->client->dev, "set default PERS returned %d\n", + ret); + return ret; + } + data->als_persistence = VCNL4035_ALS_PERS_DEFAULT; + + /* set default HIGH threshold for ALS */ + ret = regmap_write(data->regmap, VCNL4035_ALS_THDH, + VCNL4035_ALS_THDH_DEFAULT); + if (ret) { + dev_err(&data->client->dev, "set default THDH returned %d\n", + ret); + return ret; + } + data->als_thresh_high = VCNL4035_ALS_THDH_DEFAULT; + + /* set default LOW threshold for ALS */ + ret = regmap_write(data->regmap, VCNL4035_ALS_THDL, + VCNL4035_ALS_THDL_DEFAULT); + if (ret) { + dev_err(&data->client->dev, "set default THDL returned %d\n", + ret); + return ret; + } + data->als_thresh_low = VCNL4035_ALS_THDL_DEFAULT; + + return 0; +} + +static bool vcnl4035_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case VCNL4035_ALS_CONF: + case VCNL4035_DEV_ID: + return false; + default: + return true; + } +} + +static const struct regmap_config vcnl4035_regmap_config = { + .name = "vcnl4035_regmap", + .reg_bits = 8, + .val_bits = 16, + .max_register = VCNL4035_DEV_ID, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = vcnl4035_is_volatile_reg, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static int vcnl4035_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct vcnl4035_data *data = iio_priv(indio_dev); + + data->drdy_trigger0 = devm_iio_trigger_alloc( + indio_dev->dev.parent, + "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); + if (!data->drdy_trigger0) + return -ENOMEM; + + data->drdy_trigger0->ops = &vcnl4035_trigger_ops; + iio_trigger_set_drvdata(data->drdy_trigger0, indio_dev); + ret = devm_iio_trigger_register(indio_dev->dev.parent, + data->drdy_trigger0); + if (ret) { + dev_err(&data->client->dev, "iio trigger register failed\n"); + return ret; + } + + /* Trigger setup */ + ret = devm_iio_triggered_buffer_setup(indio_dev->dev.parent, indio_dev, + NULL, vcnl4035_trigger_consumer_handler, + &iio_triggered_buffer_setup_ops); + if (ret < 0) { + dev_err(&data->client->dev, "iio triggered buffer setup failed\n"); + return ret; + } + + /* IRQ to trigger mapping */ + ret = devm_request_threaded_irq(&data->client->dev, data->client->irq, + NULL, vcnl4035_drdy_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "vcnl4035_event", indio_dev); + if (ret < 0) + dev_err(&data->client->dev, "request irq %d for trigger0 failed\n", + data->client->irq); + return ret; +} + +static int vcnl4035_probe(struct i2c_client *client) +{ + struct vcnl4035_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &vcnl4035_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "regmap_init failed!\n"); + return PTR_ERR(regmap); + } + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + + indio_dev->info = &vcnl4035_info; + indio_dev->name = VCNL4035_DRV_NAME; + indio_dev->channels = vcnl4035_channels; + indio_dev->num_channels = ARRAY_SIZE(vcnl4035_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = vcnl4035_init(data); + if (ret < 0) { + dev_err(&client->dev, "vcnl4035 chip init failed\n"); + return ret; + } + + if (client->irq > 0) { + ret = vcnl4035_probe_trigger(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "vcnl4035 unable init trigger\n"); + goto fail_poweroff; + } + } + + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto fail_poweroff; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto fail_poweroff; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, VCNL4035_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + return 0; + +fail_poweroff: + vcnl4035_set_als_power_state(data, VCNL4035_MODE_ALS_DISABLE); + return ret; +} + +static void vcnl4035_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + int ret; + + pm_runtime_dont_use_autosuspend(&client->dev); + pm_runtime_disable(&client->dev); + iio_device_unregister(indio_dev); + pm_runtime_set_suspended(&client->dev); + + ret = vcnl4035_set_als_power_state(iio_priv(indio_dev), + VCNL4035_MODE_ALS_DISABLE); + if (ret) + dev_warn(&client->dev, "Failed to put device into standby (%pe)\n", + ERR_PTR(ret)); +} + +static int vcnl4035_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct vcnl4035_data *data = iio_priv(indio_dev); + int ret; + + ret = vcnl4035_set_als_power_state(data, VCNL4035_MODE_ALS_DISABLE); + regcache_mark_dirty(data->regmap); + + return ret; +} + +static int vcnl4035_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct vcnl4035_data *data = iio_priv(indio_dev); + int ret; + + ret = regcache_sync(data->regmap); + if (ret < 0) + return ret; + + ret = vcnl4035_set_als_power_state(data, VCNL4035_MODE_ALS_ENABLE); + if (ret < 0) + return ret; + + /* wait for 1 ALS integration cycle */ + msleep(data->als_it_val * 100); + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(vcnl4035_pm_ops, vcnl4035_runtime_suspend, + vcnl4035_runtime_resume, NULL); + +static const struct i2c_device_id vcnl4035_id[] = { + { .name = "vcnl4035" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, vcnl4035_id); + +static const struct of_device_id vcnl4035_of_match[] = { + { .compatible = "vishay,vcnl4035", }, + { } +}; +MODULE_DEVICE_TABLE(of, vcnl4035_of_match); + +static struct i2c_driver vcnl4035_driver = { + .driver = { + .name = VCNL4035_DRV_NAME, + .pm = pm_ptr(&vcnl4035_pm_ops), + .of_match_table = vcnl4035_of_match, + }, + .probe = vcnl4035_probe, + .remove = vcnl4035_remove, + .id_table = vcnl4035_id, +}; + +module_i2c_driver(vcnl4035_driver); + +MODULE_AUTHOR("Parthiban Nallathambi <pn@denx.de>"); +MODULE_DESCRIPTION("VCNL4035 Ambient Light Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/veml3235.c b/drivers/iio/light/veml3235.c new file mode 100644 index 000000000..d8a392230 --- /dev/null +++ b/drivers/iio/light/veml3235.c @@ -0,0 +1,547 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * VEML3235 Ambient Light Sensor + * + * Copyright (c) 2024, Javier Carrasco <javier.carrasco.cruz@gmail.com> + * + * Datasheet: https://www.vishay.com/docs/80131/veml3235.pdf + * Appnote-80222: https://www.vishay.com/docs/80222/designingveml3235.pdf + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/iio-gts-helper.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +#define VEML3235_REG_CONF 0x00 +#define VEML3235_REG_WH_DATA 0x04 +#define VEML3235_REG_ALS_DATA 0x05 +#define VEML3235_REG_ID 0x09 + +#define VEML3235_CONF_SD BIT(0) +#define VEML3235_CONF_SD0 BIT(15) + +struct veml3235_rf { + struct regmap_field *it; + struct regmap_field *gain; + struct regmap_field *id; +}; + +struct veml3235_data { + struct i2c_client *client; + struct device *dev; + struct regmap *regmap; + struct veml3235_rf rf; + struct iio_gts gts; +}; + +static const struct iio_itime_sel_mul veml3235_it_sel[] = { + GAIN_SCALE_ITIME_US(50000, 0, 1), + GAIN_SCALE_ITIME_US(100000, 1, 2), + GAIN_SCALE_ITIME_US(200000, 2, 4), + GAIN_SCALE_ITIME_US(400000, 3, 8), + GAIN_SCALE_ITIME_US(800000, 4, 16), +}; + +/* + * The MSB (DG) doubles the value of the rest of the field, which leads to + * two possible combinations to obtain gain = 2 and gain = 4. The gain + * handling can be simplified by restricting DG = 1 to the only gain that + * really requires it, gain = 8. Note that "X10" is a reserved value. + */ +#define VEML3235_SEL_GAIN_X1 0 +#define VEML3235_SEL_GAIN_X2 1 +#define VEML3235_SEL_GAIN_X4 3 +#define VEML3235_SEL_GAIN_X8 7 +static const struct iio_gain_sel_pair veml3235_gain_sel[] = { + GAIN_SCALE_GAIN(1, VEML3235_SEL_GAIN_X1), + GAIN_SCALE_GAIN(2, VEML3235_SEL_GAIN_X2), + GAIN_SCALE_GAIN(4, VEML3235_SEL_GAIN_X4), + GAIN_SCALE_GAIN(8, VEML3235_SEL_GAIN_X8), +}; + +static int veml3235_power_on(struct veml3235_data *data) +{ + int ret; + + ret = regmap_clear_bits(data->regmap, VEML3235_REG_CONF, + VEML3235_CONF_SD | VEML3235_CONF_SD0); + if (ret) + return ret; + + /* Wait 4 ms to let processor & oscillator start correctly */ + fsleep(4000); + + return 0; +} + +static int veml3235_shut_down(struct veml3235_data *data) +{ + return regmap_set_bits(data->regmap, VEML3235_REG_CONF, + VEML3235_CONF_SD | VEML3235_CONF_SD0); +} + +static void veml3235_shut_down_action(void *data) +{ + veml3235_shut_down(data); +} + +enum veml3235_chan { + CH_ALS, + CH_WHITE, +}; + +static const struct iio_chan_spec veml3235_channels[] = { + { + .type = IIO_LIGHT, + .channel = CH_ALS, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_INTENSITY, + .channel = CH_WHITE, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static const struct regmap_range veml3235_readable_ranges[] = { + regmap_reg_range(VEML3235_REG_CONF, VEML3235_REG_ID), +}; + +static const struct regmap_access_table veml3235_readable_table = { + .yes_ranges = veml3235_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(veml3235_readable_ranges), +}; + +static const struct regmap_range veml3235_writable_ranges[] = { + regmap_reg_range(VEML3235_REG_CONF, VEML3235_REG_CONF), +}; + +static const struct regmap_access_table veml3235_writable_table = { + .yes_ranges = veml3235_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(veml3235_writable_ranges), +}; + +static const struct regmap_range veml3235_volatile_ranges[] = { + regmap_reg_range(VEML3235_REG_WH_DATA, VEML3235_REG_ALS_DATA), +}; + +static const struct regmap_access_table veml3235_volatile_table = { + .yes_ranges = veml3235_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(veml3235_volatile_ranges), +}; + +static const struct regmap_config veml3235_regmap_config = { + .name = "veml3235_regmap", + .reg_bits = 8, + .val_bits = 16, + .max_register = VEML3235_REG_ID, + .val_format_endian = REGMAP_ENDIAN_LITTLE, + .rd_table = &veml3235_readable_table, + .wr_table = &veml3235_writable_table, + .volatile_table = &veml3235_volatile_table, + .cache_type = REGCACHE_MAPLE, +}; + +static int veml3235_get_it(struct veml3235_data *data, int *val, int *val2) +{ + int ret, it_idx; + + ret = regmap_field_read(data->rf.it, &it_idx); + if (ret) + return ret; + + ret = iio_gts_find_int_time_by_sel(&data->gts, it_idx); + if (ret < 0) + return ret; + + *val2 = ret; + *val = 0; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int veml3235_set_it(struct iio_dev *indio_dev, int val, int val2) +{ + struct veml3235_data *data = iio_priv(indio_dev); + int ret, gain_idx, it_idx, new_gain, prev_gain, prev_it; + bool in_range; + + if (val || !iio_gts_valid_time(&data->gts, val2)) + return -EINVAL; + + ret = regmap_field_read(data->rf.it, &it_idx); + if (ret) + return ret; + + ret = regmap_field_read(data->rf.gain, &gain_idx); + if (ret) + return ret; + + prev_it = iio_gts_find_int_time_by_sel(&data->gts, it_idx); + if (prev_it < 0) + return prev_it; + + if (prev_it == val2) + return 0; + + prev_gain = iio_gts_find_gain_by_sel(&data->gts, gain_idx); + if (prev_gain < 0) + return prev_gain; + + ret = iio_gts_find_new_gain_by_gain_time_min(&data->gts, prev_gain, prev_it, + val2, &new_gain, &in_range); + if (ret) + return ret; + + if (!in_range) + dev_dbg(data->dev, "Optimal gain out of range\n"); + + ret = iio_gts_find_sel_by_int_time(&data->gts, val2); + if (ret < 0) + return ret; + + ret = regmap_field_write(data->rf.it, ret); + if (ret) + return ret; + + ret = iio_gts_find_sel_by_gain(&data->gts, new_gain); + if (ret < 0) + return ret; + + return regmap_field_write(data->rf.gain, ret); +} + +static int veml3235_set_scale(struct iio_dev *indio_dev, int val, int val2) +{ + struct veml3235_data *data = iio_priv(indio_dev); + int ret, it_idx, gain_sel, time_sel; + + ret = regmap_field_read(data->rf.it, &it_idx); + if (ret) + return ret; + + ret = iio_gts_find_gain_time_sel_for_scale(&data->gts, val, val2, + &gain_sel, &time_sel); + if (ret) + return ret; + + ret = regmap_field_write(data->rf.it, time_sel); + if (ret) + return ret; + + return regmap_field_write(data->rf.gain, gain_sel); +} + +static int veml3235_get_scale(struct veml3235_data *data, int *val, int *val2) +{ + int gain, it, reg, ret; + + ret = regmap_field_read(data->rf.gain, ®); + if (ret) { + dev_err(data->dev, "failed to read gain %d\n", ret); + return ret; + } + + gain = iio_gts_find_gain_by_sel(&data->gts, reg); + if (gain < 0) + return gain; + + ret = regmap_field_read(data->rf.it, ®); + if (ret) { + dev_err(data->dev, "failed to read integration time %d\n", ret); + return ret; + } + + it = iio_gts_find_int_time_by_sel(&data->gts, reg); + if (it < 0) + return it; + + ret = iio_gts_get_scale(&data->gts, gain, it, val, val2); + if (ret) + return ret; + + return IIO_VAL_INT_PLUS_NANO; +} + +static int veml3235_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct veml3235_data *data = iio_priv(indio_dev); + struct regmap *regmap = data->regmap; + int ret, reg; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + ret = regmap_read(regmap, VEML3235_REG_ALS_DATA, ®); + if (ret < 0) + return ret; + + *val = reg; + return IIO_VAL_INT; + case IIO_INTENSITY: + ret = regmap_read(regmap, VEML3235_REG_WH_DATA, ®); + if (ret < 0) + return ret; + + *val = reg; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + return veml3235_get_it(data, val, val2); + case IIO_CHAN_INFO_SCALE: + return veml3235_get_scale(data, val, val2); + default: + return -EINVAL; + } +} + +static int veml3235_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct veml3235_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return iio_gts_avail_times(&data->gts, vals, type, length); + case IIO_CHAN_INFO_SCALE: + return iio_gts_all_avail_scales(&data->gts, vals, type, length); + default: + return -EINVAL; + } +} + +static int veml3235_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_INT_TIME: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int veml3235_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return veml3235_set_it(indio_dev, val, val2); + case IIO_CHAN_INFO_SCALE: + return veml3235_set_scale(indio_dev, val, val2); + } + + return -EINVAL; +} + +static void veml3235_read_id(struct veml3235_data *data) +{ + int ret, reg; + + ret = regmap_field_read(data->rf.id, ®); + if (ret) { + dev_info(data->dev, "failed to read ID\n"); + return; + } + + if (reg != 0x35) + dev_info(data->dev, "Unknown ID %d\n", reg); +} + +static const struct reg_field veml3235_rf_it = + REG_FIELD(VEML3235_REG_CONF, 4, 6); + +static const struct reg_field veml3235_rf_gain = + REG_FIELD(VEML3235_REG_CONF, 11, 13); + +static const struct reg_field veml3235_rf_id = + REG_FIELD(VEML3235_REG_ID, 0, 7); + +static int veml3235_regfield_init(struct veml3235_data *data) +{ + struct regmap *regmap = data->regmap; + struct device *dev = data->dev; + struct regmap_field *rm_field; + struct veml3235_rf *rf = &data->rf; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml3235_rf_it); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->it = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml3235_rf_gain); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->gain = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml3235_rf_id); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->id = rm_field; + + return 0; +} + +static int veml3235_hw_init(struct iio_dev *indio_dev) +{ + struct veml3235_data *data = iio_priv(indio_dev); + struct device *dev = data->dev; + int ret; + + ret = devm_iio_init_iio_gts(data->dev, 0, 272640000, + veml3235_gain_sel, ARRAY_SIZE(veml3235_gain_sel), + veml3235_it_sel, ARRAY_SIZE(veml3235_it_sel), + &data->gts); + if (ret) + return dev_err_probe(data->dev, ret, "failed to init iio gts\n"); + + /* Set gain to 1 and integration time to 100 ms */ + ret = regmap_field_write(data->rf.gain, 0x00); + if (ret) + return dev_err_probe(data->dev, ret, "failed to set gain\n"); + + ret = regmap_field_write(data->rf.it, 0x01); + if (ret) + return dev_err_probe(data->dev, ret, + "failed to set integration time\n"); + + ret = veml3235_power_on(data); + if (ret) + return dev_err_probe(dev, ret, "failed to power on\n"); + + return devm_add_action_or_reset(dev, veml3235_shut_down_action, data); +} + +static const struct iio_info veml3235_info = { + .read_raw = veml3235_read_raw, + .read_avail = veml3235_read_avail, + .write_raw = veml3235_write_raw, + .write_raw_get_fmt = veml3235_write_raw_get_fmt, +}; + +static int veml3235_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct veml3235_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_i2c(client, &veml3235_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "failed to setup regmap\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->dev = dev; + data->regmap = regmap; + + ret = veml3235_regfield_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to init regfield\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable regulator\n"); + + indio_dev->name = "veml3235"; + indio_dev->channels = veml3235_channels; + indio_dev->num_channels = ARRAY_SIZE(veml3235_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &veml3235_info; + + veml3235_read_id(data); + + ret = veml3235_hw_init(indio_dev); + if (ret < 0) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static int veml3235_runtime_suspend(struct device *dev) +{ + struct veml3235_data *data = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = veml3235_shut_down(data); + if (ret < 0) + dev_err(data->dev, "failed to suspend: %d\n", ret); + + return ret; +} + +static int veml3235_runtime_resume(struct device *dev) +{ + struct veml3235_data *data = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = veml3235_power_on(data); + if (ret < 0) + dev_err(data->dev, "failed to resume: %d\n", ret); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(veml3235_pm_ops, veml3235_runtime_suspend, + veml3235_runtime_resume, NULL); + +static const struct of_device_id veml3235_of_match[] = { + { .compatible = "vishay,veml3235" }, + { } +}; +MODULE_DEVICE_TABLE(of, veml3235_of_match); + +static const struct i2c_device_id veml3235_id[] = { + { .name = "veml3235" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, veml3235_id); + +static struct i2c_driver veml3235_driver = { + .driver = { + .name = "veml3235", + .of_match_table = veml3235_of_match, + .pm = pm_ptr(&veml3235_pm_ops), + }, + .probe = veml3235_probe, + .id_table = veml3235_id, +}; +module_i2c_driver(veml3235_driver); + +MODULE_AUTHOR("Javier Carrasco <javier.carrasco.cruz@gmail.com>"); +MODULE_DESCRIPTION("VEML3235 Ambient Light Sensor"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_GTS_HELPER"); diff --git a/drivers/iio/light/veml3328.c b/drivers/iio/light/veml3328.c new file mode 100644 index 000000000..7ff175392 --- /dev/null +++ b/drivers/iio/light/veml3328.c @@ -0,0 +1,422 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Vishay VEML3328 RGBCIR light sensor driver + * + * Copyright (c) 2026 Joshua Crofts <joshua.crofts1@gmail.com> + * + * Datasheet: https://www.vishay.com/docs/84968/veml3328.pdf + */ + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/time.h> +#include <linux/types.h> + +#include <linux/iio/iio.h> + +#define VEML3328_REG_CONF 0x00 +#define VEML3328_REG_ID 0x0c +#define VEML3328_REG_DATA_C 0x04 +#define VEML3328_REG_DATA_R 0x05 +#define VEML3328_REG_DATA_G 0x06 +#define VEML3328_REG_DATA_B 0x07 +#define VEML3328_REG_DATA_IR 0x08 + +#define VEML3328_CONF_IT_MASK GENMASK(5, 4) +#define VEML3328_CONF_GAIN_MASK GENMASK(11, 10) + +#define VEML3328_MAX_IT_TIME (400 * USEC_PER_MSEC) + +#define VEML3328_ID_VAL 0x28 + +#define VEML3328_SHUTDOWN (BIT(0) | BIT(15)) + +struct veml3328_data { + struct regmap *regmap; + /* Ensure read-modify-write sequences are not interrupted. */ + struct mutex lock; +}; + +static const struct regmap_config veml3328_regmap_config = { + .name = "veml3328", + .reg_bits = 8, + .val_bits = 16, + .max_register = VEML3328_REG_ID, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +#define VEML3328_CHAN_SPEC(_color, _addr) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .channel2 = IIO_MOD_LIGHT_##_color, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), \ + .address = _addr, \ +} + +static const struct iio_chan_spec veml3328_channels[] = { + { + .type = IIO_LIGHT, + + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), + .address = VEML3328_REG_DATA_G, + }, + VEML3328_CHAN_SPEC(CLEAR, VEML3328_REG_DATA_C), + VEML3328_CHAN_SPEC(RED, VEML3328_REG_DATA_R), + VEML3328_CHAN_SPEC(GREEN, VEML3328_REG_DATA_G), + VEML3328_CHAN_SPEC(BLUE, VEML3328_REG_DATA_B), + VEML3328_CHAN_SPEC(IR, VEML3328_REG_DATA_IR), +}; + +/* + * Precomputed scale values (micro units). + * Formula for calculation: 0.384 * (50000 / IT_us) * (1 / Gain) + * Gain indexes: 0 (x0.5), 1 (x1), 2 (x2), 3 (x4) + * IT indexes: 0 (50ms), 1 (100ms), 2 (200ms), 3 (400ms) + */ +static const int veml3328_scale_vals[4][8] = { + { 0, 768000, 0, 384000, 0, 192000, 0, 96000 }, + { 0, 384000, 0, 192000, 0, 96000, 0, 48000 }, + { 0, 192000, 0, 96000, 0, 48000, 0, 24000 }, + { 0, 96000, 0, 48000, 0, 24000, 0, 12000 }, +}; + +/* integration times in microseconds */ +static const int veml3328_it_times[][2] = { + { 0, 50 * USEC_PER_MSEC }, + { 0, 100 * USEC_PER_MSEC }, + { 0, 200 * USEC_PER_MSEC }, + { 0, 400 * USEC_PER_MSEC }, +}; + +static int veml3328_power_down(struct veml3328_data *data) +{ + return regmap_set_bits(data->regmap, VEML3328_REG_CONF, + VEML3328_SHUTDOWN); +} + +static int veml3328_power_up(struct veml3328_data *data) +{ + int ret; + + ret = regmap_clear_bits(data->regmap, VEML3328_REG_CONF, + VEML3328_SHUTDOWN); + if (ret) + return ret; + + /* + * Sleep for maximum integration time to ensure sensor is powered on + * correctly. + */ + fsleep(VEML3328_MAX_IT_TIME); + + return 0; +} + +static void veml3328_power_down_action(void *data) +{ + veml3328_power_down(data); +} + +static int veml3328_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct veml3328_data *data = iio_priv(indio_dev); + struct regmap *regmap = data->regmap; + struct device *dev = regmap_get_device(regmap); + unsigned int reg_val; + int it_inx, gain_inx; + int ret; + + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(regmap, chan->address, ®_val); + if (ret) + return ret; + + *val = reg_val; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_INT_TIME: + ret = regmap_read(regmap, VEML3328_REG_CONF, ®_val); + if (ret) + return ret; + + it_inx = FIELD_GET(VEML3328_CONF_IT_MASK, reg_val); + if (it_inx >= ARRAY_SIZE(veml3328_it_times)) + return -EINVAL; + + *val = veml3328_it_times[it_inx][0]; + *val2 = veml3328_it_times[it_inx][1]; + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(regmap, VEML3328_REG_CONF, ®_val); + if (ret) + return ret; + + it_inx = FIELD_GET(VEML3328_CONF_IT_MASK, reg_val); + gain_inx = FIELD_GET(VEML3328_CONF_GAIN_MASK, reg_val); + + if (it_inx >= ARRAY_SIZE(veml3328_it_times) || gain_inx >= 4) + return -EINVAL; + + /* Stride by 2 through the flattened array to match (val, val2) */ + *val = veml3328_scale_vals[it_inx][gain_inx * 2]; + *val2 = veml3328_scale_vals[it_inx][gain_inx * 2 + 1]; + + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + } +} + +static int veml3328_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct veml3328_data *data = iio_priv(indio_dev); + struct regmap *regmap = data->regmap; + struct device *dev = regmap_get_device(data->regmap); + unsigned int reg_val; + int ret, it_inx; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *length = ARRAY_SIZE(veml3328_it_times) * 2; + *vals = (const int *)veml3328_it_times; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_SCALE: { + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = regmap_read(regmap, VEML3328_REG_CONF, ®_val); + if (ret) + return ret; + + it_inx = FIELD_GET(VEML3328_CONF_IT_MASK, reg_val); + if (it_inx >= ARRAY_SIZE(veml3328_it_times)) + return -EINVAL; + + *length = 8; + *vals = (const int *)veml3328_scale_vals[it_inx]; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + } + default: + return -EINVAL; + } +} + +static int veml3328_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct veml3328_data *data = iio_priv(indio_dev); + struct regmap *regmap = data->regmap; + struct device *dev = regmap_get_device(regmap); + unsigned int reg_val; + int i, it_inx; + int ret; + + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + guard(mutex)(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(veml3328_it_times); i++) { + if (veml3328_it_times[i][1] == val2) + break; + } + + if (i == ARRAY_SIZE(veml3328_it_times)) + return -EINVAL; + + return regmap_update_bits(regmap, VEML3328_REG_CONF, + VEML3328_CONF_IT_MASK, + FIELD_PREP(VEML3328_CONF_IT_MASK, i)); + + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(regmap, VEML3328_REG_CONF, ®_val); + if (ret) + return ret; + + it_inx = FIELD_GET(VEML3328_CONF_IT_MASK, reg_val); + if (it_inx >= ARRAY_SIZE(veml3328_it_times)) + return -EINVAL; + + for (i = 0; i < 4; i++) { + if (val == veml3328_scale_vals[it_inx][i * 2] && + val2 == veml3328_scale_vals[it_inx][i * 2 + 1]) + break; + } + + if (i == 4) + return -EINVAL; + + return regmap_update_bits(regmap, VEML3328_REG_CONF, + VEML3328_CONF_GAIN_MASK, + FIELD_PREP(VEML3328_CONF_GAIN_MASK, i)); + + default: + return -EINVAL; + } +} + +static const struct iio_info veml3328_info = { + .read_raw = veml3328_read_raw, + .write_raw = veml3328_write_raw, + .read_avail = veml3328_read_avail, +}; + +static int veml3328_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct veml3328_data *data; + struct iio_dev *indio_dev; + unsigned int reg_val; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + + data->regmap = devm_regmap_init_i2c(client, &veml3328_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "Failed to initialize regmap\n"); + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulator\n"); + + ret = regmap_read(data->regmap, VEML3328_REG_ID, ®_val); + if (ret) + return dev_err_probe(dev, ret, "Failed to read ID register\n"); + + if ((reg_val & 0xff) != VEML3328_ID_VAL) + dev_warn(dev, "Unknown device ID: 0x%02x\n", reg_val & 0xff); + + ret = veml3328_power_up(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to power on sensor\n"); + + ret = devm_add_action_or_reset(dev, veml3328_power_down_action, data); + if (ret) + return dev_err_probe(dev, ret, "Failed to register teardown\n"); + + indio_dev->name = "veml3328"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &veml3328_info; + indio_dev->channels = veml3328_channels; + indio_dev->num_channels = ARRAY_SIZE(veml3328_channels); + + pm_runtime_set_active(dev); + pm_runtime_set_autosuspend_delay(dev, 2000); + pm_runtime_use_autosuspend(dev); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static int veml3328_runtime_suspend(struct device *dev) +{ + struct veml3328_data *data = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = veml3328_power_down(data); + if (ret) + dev_err(dev, "Failed to suspend: %d\n", ret); + + return ret; +} + +static int veml3328_runtime_resume(struct device *dev) +{ + struct veml3328_data *data = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = veml3328_power_up(data); + if (ret) + dev_err(dev, "Failed to resume: %d\n", ret); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(veml3328_pm_ops, + veml3328_runtime_suspend, + veml3328_runtime_resume, + NULL); + +static const struct of_device_id veml3328_of_match[] = { + { .compatible = "vishay,veml3328" }, + { } +}; +MODULE_DEVICE_TABLE(of, veml3328_of_match); + +static const struct i2c_device_id veml3328_id[] = { + { .name = "veml3328" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, veml3328_id); + +static struct i2c_driver veml3328_driver = { + .driver = { + .name = "veml3328", + .of_match_table = veml3328_of_match, + .pm = pm_ptr(&veml3328_pm_ops), + }, + .probe = veml3328_probe, + .id_table = veml3328_id, +}; +module_i2c_driver(veml3328_driver); + +MODULE_AUTHOR("Joshua Crofts <joshua.crofts1@gmail.com>"); +MODULE_DESCRIPTION("VEML3328 RGBCIR Light Sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/veml6030.c b/drivers/iio/light/veml6030.c new file mode 100644 index 000000000..ec9c127e7 --- /dev/null +++ b/drivers/iio/light/veml6030.c @@ -0,0 +1,1238 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * VEML6030, VMEL6035 and VEML7700 Ambient Light Sensors + * + * Copyright (c) 2019, Rishi Gupta <gupt21@gmail.com> + * + * VEML6030: + * Datasheet: https://www.vishay.com/docs/84366/veml6030.pdf + * Appnote-84367: https://www.vishay.com/docs/84367/designingveml6030.pdf + * + * VEML6035: + * Datasheet: https://www.vishay.com/docs/84889/veml6035.pdf + * Appnote-84944: https://www.vishay.com/docs/84944/designingveml6035.pdf + * + * VEML7700: + * Datasheet: https://www.vishay.com/docs/84286/veml7700.pdf + * Appnote-84323: https://www.vishay.com/docs/84323/designingveml7700.pdf + */ + +#include <linux/bitfield.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/regmap.h> +#include <linux/interrupt.h> +#include <linux/pm_runtime.h> +#include <linux/units.h> +#include <linux/regulator/consumer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/iio/iio-gts-helper.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +/* Device registers */ +#define VEML6030_REG_ALS_CONF 0x00 +#define VEML6030_REG_ALS_WH 0x01 +#define VEML6030_REG_ALS_WL 0x02 +#define VEML6030_REG_ALS_PSM 0x03 +#define VEML6030_REG_ALS_DATA 0x04 +#define VEML6030_REG_WH_DATA 0x05 +#define VEML6030_REG_ALS_INT 0x06 +#define VEML6030_REG_DATA(ch) (VEML6030_REG_ALS_DATA + (ch)) + +/* Bit masks for specific functionality */ +#define VEML6030_ALS_IT GENMASK(9, 6) +#define VEML6030_PSM GENMASK(2, 1) +#define VEML6030_ALS_PERS GENMASK(5, 4) +#define VEML6030_ALS_GAIN GENMASK(12, 11) +#define VEML6030_PSM_EN BIT(0) +#define VEML6030_INT_TH_LOW BIT(15) +#define VEML6030_INT_TH_HIGH BIT(14) +#define VEML6030_ALS_INT_EN BIT(1) +#define VEML6030_ALS_SD BIT(0) + +#define VEML6035_GAIN_M GENMASK(12, 10) +#define VEML6035_GAIN BIT(10) +#define VEML6035_DG BIT(11) +#define VEML6035_SENS BIT(12) +#define VEML6035_INT_CHAN BIT(3) +#define VEML6035_CHAN_EN BIT(2) + +/* Regfields */ +#define VEML6030_GAIN_RF REG_FIELD(VEML6030_REG_ALS_CONF, 11, 12) +#define VEML6030_IT_RF REG_FIELD(VEML6030_REG_ALS_CONF, 6, 9) + +#define VEML6035_GAIN_RF REG_FIELD(VEML6030_REG_ALS_CONF, 10, 12) + +/* Maximum scales x 10000 to work with integers */ +#define VEML6030_MAX_SCALE 21504 +#define VEML6035_MAX_SCALE 4096 + +enum veml6030_scan { + VEML6030_SCAN_ALS, + VEML6030_SCAN_WH, + VEML6030_SCAN_TIMESTAMP, +}; + +struct veml6030_rf { + struct regmap_field *it; + struct regmap_field *gain; +}; + +struct veml603x_chip { + const char *name; + const struct iio_chan_spec *channels; + const int num_channels; + const struct reg_field gain_rf; + const struct reg_field it_rf; + const int max_scale; + int (*hw_init)(struct iio_dev *indio_dev, struct device *dev); + int (*set_info)(struct iio_dev *indio_dev); +}; + +/* + * The resolution depends on both gain and integration time. The + * cur_resolution stores one of the resolution mentioned in the + * table during startup and gets updated whenever integration time + * or gain is changed. + * + * Table 'resolution and maximum detection range' in the appnotes + * is visualized as a 2D array. The cur_gain stores index of gain + * in this table (0-3 for VEML6030, 0-5 for VEML6035) while the + * cur_integration_time holds index of integration time (0-5). + */ +struct veml6030_data { + struct i2c_client *client; + struct regmap *regmap; + struct veml6030_rf rf; + const struct veml603x_chip *chip; + struct iio_gts gts; + +}; + +#define VEML6030_SEL_IT_25MS 0x0C +#define VEML6030_SEL_IT_50MS 0x08 +#define VEML6030_SEL_IT_100MS 0x00 +#define VEML6030_SEL_IT_200MS 0x01 +#define VEML6030_SEL_IT_400MS 0x02 +#define VEML6030_SEL_IT_800MS 0x03 +static const struct iio_itime_sel_mul veml6030_it_sel[] = { + GAIN_SCALE_ITIME_US(25000, VEML6030_SEL_IT_25MS, 1), + GAIN_SCALE_ITIME_US(50000, VEML6030_SEL_IT_50MS, 2), + GAIN_SCALE_ITIME_US(100000, VEML6030_SEL_IT_100MS, 4), + GAIN_SCALE_ITIME_US(200000, VEML6030_SEL_IT_200MS, 8), + GAIN_SCALE_ITIME_US(400000, VEML6030_SEL_IT_400MS, 16), + GAIN_SCALE_ITIME_US(800000, VEML6030_SEL_IT_800MS, 32), +}; + +/* Gains are multiplied by 8 to work with integers. The values in the + * iio-gts tables don't need corrections because the maximum value of + * the scale refers to GAIN = x1, and the rest of the values are + * obtained from the resulting linear function. + */ +#define VEML6030_SEL_MILLI_GAIN_X125 2 +#define VEML6030_SEL_MILLI_GAIN_X250 3 +#define VEML6030_SEL_MILLI_GAIN_X1000 0 +#define VEML6030_SEL_MILLI_GAIN_X2000 1 +static const struct iio_gain_sel_pair veml6030_gain_sel[] = { + GAIN_SCALE_GAIN(1, VEML6030_SEL_MILLI_GAIN_X125), + GAIN_SCALE_GAIN(2, VEML6030_SEL_MILLI_GAIN_X250), + GAIN_SCALE_GAIN(8, VEML6030_SEL_MILLI_GAIN_X1000), + GAIN_SCALE_GAIN(16, VEML6030_SEL_MILLI_GAIN_X2000), +}; + +#define VEML6035_SEL_MILLI_GAIN_X125 4 +#define VEML6035_SEL_MILLI_GAIN_X250 5 +#define VEML6035_SEL_MILLI_GAIN_X500 7 +#define VEML6035_SEL_MILLI_GAIN_X1000 0 +#define VEML6035_SEL_MILLI_GAIN_X2000 1 +#define VEML6035_SEL_MILLI_GAIN_X4000 3 +static const struct iio_gain_sel_pair veml6035_gain_sel[] = { + GAIN_SCALE_GAIN(1, VEML6035_SEL_MILLI_GAIN_X125), + GAIN_SCALE_GAIN(2, VEML6035_SEL_MILLI_GAIN_X250), + GAIN_SCALE_GAIN(4, VEML6035_SEL_MILLI_GAIN_X500), + GAIN_SCALE_GAIN(8, VEML6035_SEL_MILLI_GAIN_X1000), + GAIN_SCALE_GAIN(16, VEML6035_SEL_MILLI_GAIN_X2000), + GAIN_SCALE_GAIN(32, VEML6035_SEL_MILLI_GAIN_X4000), +}; + +/* + * Persistence = 1/2/4/8 x integration time + * Minimum time for which light readings must stay above configured + * threshold to assert the interrupt. + */ +static const char * const period_values[] = { + "0.1 0.2 0.4 0.8", + "0.2 0.4 0.8 1.6", + "0.4 0.8 1.6 3.2", + "0.8 1.6 3.2 6.4", + "0.05 0.1 0.2 0.4", + "0.025 0.050 0.1 0.2" +}; + +/* + * Return list of valid period values in seconds corresponding to + * the currently active integration time. + */ +static ssize_t in_illuminance_period_available_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct veml6030_data *data = iio_priv(dev_to_iio_dev(dev)); + int ret, reg, x; + + ret = regmap_read(data->regmap, VEML6030_REG_ALS_CONF, ®); + if (ret) { + dev_err(&data->client->dev, + "can't read als conf register %d\n", ret); + return ret; + } + + ret = ((reg >> 6) & 0xF); + switch (ret) { + case 0: + case 1: + case 2: + case 3: + x = ret; + break; + case 8: + x = 4; + break; + case 12: + x = 5; + break; + default: + return -EINVAL; + } + + return sysfs_emit(buf, "%s\n", period_values[x]); +} + +static IIO_DEVICE_ATTR_RO(in_illuminance_period_available, 0); + +static struct attribute *veml6030_event_attributes[] = { + &iio_dev_attr_in_illuminance_period_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group veml6030_event_attr_group = { + .attrs = veml6030_event_attributes, +}; + +static int veml6030_als_pwr_on(struct veml6030_data *data) +{ + int ret; + + ret = regmap_clear_bits(data->regmap, VEML6030_REG_ALS_CONF, + VEML6030_ALS_SD); + if (ret) + return ret; + + /* Wait 4 ms to let processor & oscillator start correctly */ + fsleep(4000); + + return 0; +} + +static int veml6030_als_shut_down(struct veml6030_data *data) +{ + return regmap_set_bits(data->regmap, VEML6030_REG_ALS_CONF, + VEML6030_ALS_SD); +} + +static void veml6030_als_shut_down_action(void *data) +{ + veml6030_als_shut_down(data); +} + +static const struct iio_event_spec veml6030_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +/* Channel number */ +enum veml6030_chan { + CH_ALS, + CH_WHITE, +}; + +static const struct iio_chan_spec veml6030_channels[] = { + { + .type = IIO_LIGHT, + .channel = CH_ALS, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .event_spec = veml6030_event_spec, + .num_event_specs = ARRAY_SIZE(veml6030_event_spec), + .scan_index = VEML6030_SCAN_ALS, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_INTENSITY, + .channel = CH_WHITE, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6030_SCAN_WH, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(VEML6030_SCAN_TIMESTAMP), +}; + +static const struct iio_chan_spec veml7700_channels[] = { + { + .type = IIO_LIGHT, + .channel = CH_ALS, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6030_SCAN_ALS, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_INTENSITY, + .channel = CH_WHITE, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6030_SCAN_WH, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(VEML6030_SCAN_TIMESTAMP), +}; + +static const struct regmap_range veml6030_readable_ranges[] = { + regmap_reg_range(VEML6030_REG_ALS_CONF, VEML6030_REG_ALS_INT), +}; + +static const struct regmap_access_table veml6030_readable_table = { + .yes_ranges = veml6030_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(veml6030_readable_ranges), +}; + +static const struct regmap_range veml6030_writable_ranges[] = { + regmap_reg_range(VEML6030_REG_ALS_CONF, VEML6030_REG_ALS_PSM), +}; + +static const struct regmap_access_table veml6030_writable_table = { + .yes_ranges = veml6030_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(veml6030_writable_ranges), +}; + +static const struct regmap_range veml6030_volatile_ranges[] = { + regmap_reg_range(VEML6030_REG_ALS_DATA, VEML6030_REG_WH_DATA), +}; + +static const struct regmap_access_table veml6030_volatile_table = { + .yes_ranges = veml6030_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(veml6030_volatile_ranges), +}; + +static const struct regmap_config veml6030_regmap_config = { + .name = "veml6030_regmap", + .reg_bits = 8, + .val_bits = 16, + .max_register = VEML6030_REG_ALS_INT, + .val_format_endian = REGMAP_ENDIAN_LITTLE, + .rd_table = &veml6030_readable_table, + .wr_table = &veml6030_writable_table, + .volatile_table = &veml6030_volatile_table, + .cache_type = REGCACHE_RBTREE, +}; + +static int veml6030_get_it(struct veml6030_data *data, int *val, int *val2) +{ + int ret, it_idx; + + ret = regmap_field_read(data->rf.it, &it_idx); + if (ret) + return ret; + + ret = iio_gts_find_int_time_by_sel(&data->gts, it_idx); + if (ret < 0) + return ret; + + *val2 = ret; + *val = 0; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int veml6030_set_it(struct iio_dev *indio_dev, int val, int val2) +{ + struct veml6030_data *data = iio_priv(indio_dev); + int ret, gain_idx, it_idx, new_gain, prev_gain, prev_it; + bool in_range; + + if (val || !iio_gts_valid_time(&data->gts, val2)) + return -EINVAL; + + ret = regmap_field_read(data->rf.it, &it_idx); + if (ret) + return ret; + + ret = regmap_field_read(data->rf.gain, &gain_idx); + if (ret) + return ret; + + prev_it = iio_gts_find_int_time_by_sel(&data->gts, it_idx); + if (prev_it < 0) + return prev_it; + + if (prev_it == val2) + return 0; + + prev_gain = iio_gts_find_gain_by_sel(&data->gts, gain_idx); + if (prev_gain < 0) + return prev_gain; + + ret = iio_gts_find_new_gain_by_gain_time_min(&data->gts, prev_gain, prev_it, + val2, &new_gain, &in_range); + if (ret) + return ret; + + if (!in_range) + dev_dbg(&data->client->dev, "Optimal gain out of range\n"); + + ret = iio_gts_find_sel_by_int_time(&data->gts, val2); + if (ret < 0) + return ret; + + ret = regmap_field_write(data->rf.it, ret); + if (ret) + return ret; + + ret = iio_gts_find_sel_by_gain(&data->gts, new_gain); + if (ret < 0) + return ret; + + return regmap_field_write(data->rf.gain, ret); +} + +static int veml6030_read_persistence(struct iio_dev *indio_dev, + int *val, int *val2) +{ + int ret, reg, period, x, y; + struct veml6030_data *data = iio_priv(indio_dev); + + ret = veml6030_get_it(data, &x, &y); + if (ret < 0) + return ret; + + ret = regmap_read(data->regmap, VEML6030_REG_ALS_CONF, ®); + if (ret) { + dev_err(&data->client->dev, + "can't read als conf register %d\n", ret); + } + + /* integration time multiplied by 1/2/4/8 */ + period = y * (1 << ((reg >> 4) & 0x03)); + + *val = period / 1000000; + *val2 = period % 1000000; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int veml6030_write_persistence(struct iio_dev *indio_dev, + int val, int val2) +{ + int ret, period, x, y; + struct veml6030_data *data = iio_priv(indio_dev); + + ret = veml6030_get_it(data, &x, &y); + if (ret < 0) + return ret; + + if (!val) { + period = val2 / y; + } else { + if ((val == 1) && (val2 == 600000)) + period = 1600000 / y; + else if ((val == 3) && (val2 == 200000)) + period = 3200000 / y; + else if ((val == 6) && (val2 == 400000)) + period = 6400000 / y; + else + period = -1; + } + + if (period <= 0 || period > 8 || hweight8(period) != 1) + return -EINVAL; + + ret = regmap_update_bits(data->regmap, VEML6030_REG_ALS_CONF, + VEML6030_ALS_PERS, (ffs(period) - 1) << 4); + if (ret) + dev_err(&data->client->dev, + "can't set persistence value %d\n", ret); + + return ret; +} + +static int veml6030_set_scale(struct iio_dev *indio_dev, int val, int val2) +{ + int ret, gain_sel, it_sel; + struct veml6030_data *data = iio_priv(indio_dev); + + ret = iio_gts_find_gain_time_sel_for_scale(&data->gts, val, val2, + &gain_sel, &it_sel); + if (ret) + return ret; + + ret = regmap_field_write(data->rf.it, it_sel); + if (ret) + return ret; + + ret = regmap_field_write(data->rf.gain, gain_sel); + if (ret) + return ret; + + return 0; +} + +static int veml6030_read_thresh(struct iio_dev *indio_dev, + int *val, int *val2, int dir) +{ + int ret, reg; + struct veml6030_data *data = iio_priv(indio_dev); + + if (dir == IIO_EV_DIR_RISING) + ret = regmap_read(data->regmap, VEML6030_REG_ALS_WH, ®); + else + ret = regmap_read(data->regmap, VEML6030_REG_ALS_WL, ®); + if (ret) { + dev_err(&data->client->dev, + "can't read als threshold value %d\n", ret); + return ret; + } + + *val = reg & 0xffff; + return IIO_VAL_INT; +} + +static int veml6030_write_thresh(struct iio_dev *indio_dev, + int val, int val2, int dir) +{ + int ret; + struct veml6030_data *data = iio_priv(indio_dev); + + if (val > 0xFFFF || val < 0 || val2) + return -EINVAL; + + if (dir == IIO_EV_DIR_RISING) { + ret = regmap_write(data->regmap, VEML6030_REG_ALS_WH, val); + if (ret) + dev_err(&data->client->dev, + "can't set high threshold %d\n", ret); + } else { + ret = regmap_write(data->regmap, VEML6030_REG_ALS_WL, val); + if (ret) + dev_err(&data->client->dev, + "can't set low threshold %d\n", ret); + } + + return ret; +} + +static int veml6030_get_total_gain(struct veml6030_data *data) +{ + int gain, it, reg, ret; + + ret = regmap_field_read(data->rf.gain, ®); + if (ret) + return ret; + + gain = iio_gts_find_gain_by_sel(&data->gts, reg); + if (gain < 0) + return gain; + + ret = regmap_field_read(data->rf.it, ®); + if (ret) + return ret; + + it = iio_gts_find_int_time_by_sel(&data->gts, reg); + if (it < 0) + return it; + + return iio_gts_get_total_gain(&data->gts, gain, it); +} + +static int veml6030_get_scale(struct veml6030_data *data, int *val, int *val2) +{ + int gain, it, reg, ret; + + ret = regmap_field_read(data->rf.gain, ®); + if (ret) + return ret; + + gain = iio_gts_find_gain_by_sel(&data->gts, reg); + if (gain < 0) + return gain; + + ret = regmap_field_read(data->rf.it, ®); + if (ret) + return ret; + + it = iio_gts_find_int_time_by_sel(&data->gts, reg); + if (it < 0) + return it; + + ret = iio_gts_get_scale(&data->gts, gain, it, val, val2); + if (ret) + return ret; + + return IIO_VAL_INT_PLUS_NANO; +} + +static int veml6030_process_als(struct veml6030_data *data, int raw, + int *val, int *val2) +{ + int total_gain; + + total_gain = veml6030_get_total_gain(data); + if (total_gain < 0) + return total_gain; + + *val = raw * data->chip->max_scale / total_gain / 10000; + *val2 = raw * data->chip->max_scale / total_gain % 10000 * 100; + + return IIO_VAL_INT_PLUS_MICRO; +} + +/* + * Provide both raw as well as light reading in lux. + * light (in lux) = resolution * raw reading + */ +static int veml6030_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret, reg; + struct veml6030_data *data = iio_priv(indio_dev); + struct regmap *regmap = data->regmap; + struct device *dev = &data->client->dev; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + ret = regmap_read(regmap, VEML6030_REG_ALS_DATA, ®); + if (ret < 0) { + dev_err(dev, "can't read als data %d\n", ret); + return ret; + } + if (mask == IIO_CHAN_INFO_PROCESSED) + return veml6030_process_als(data, reg, val, val2); + + *val = reg; + return IIO_VAL_INT; + case IIO_INTENSITY: + ret = regmap_read(regmap, VEML6030_REG_WH_DATA, ®); + if (ret < 0) { + dev_err(dev, "can't read white data %d\n", ret); + return ret; + } + *val = reg; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + return veml6030_get_it(data, val, val2); + case IIO_CHAN_INFO_SCALE: + return veml6030_get_scale(data, val, val2); + default: + return -EINVAL; + } +} + +static int veml6030_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct veml6030_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return iio_gts_avail_times(&data->gts, vals, type, length); + case IIO_CHAN_INFO_SCALE: + return iio_gts_all_avail_scales(&data->gts, vals, type, length); + } + + return -EINVAL; +} + +static int veml6030_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return veml6030_set_it(indio_dev, val, val2); + case IIO_CHAN_INFO_SCALE: + return veml6030_set_scale(indio_dev, val, val2); + default: + return -EINVAL; + } +} + +static int veml6030_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_INT_TIME: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int veml6030_read_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, + int *val, int *val2) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + case IIO_EV_DIR_FALLING: + return veml6030_read_thresh(indio_dev, val, val2, dir); + default: + return -EINVAL; + } + break; + case IIO_EV_INFO_PERIOD: + return veml6030_read_persistence(indio_dev, val, val2); + default: + return -EINVAL; + } +} + +static int veml6030_write_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, + int val, int val2) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + return veml6030_write_thresh(indio_dev, val, val2, dir); + case IIO_EV_INFO_PERIOD: + return veml6030_write_persistence(indio_dev, val, val2); + default: + return -EINVAL; + } +} + +static int veml6030_read_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + int ret, reg; + struct veml6030_data *data = iio_priv(indio_dev); + + ret = regmap_read(data->regmap, VEML6030_REG_ALS_CONF, ®); + if (ret) { + dev_err(&data->client->dev, + "can't read als conf register %d\n", ret); + return ret; + } + + if (reg & VEML6030_ALS_INT_EN) + return 1; + else + return 0; +} + +/* + * Sensor should not be measuring light when interrupt is configured. + * Therefore correct sequence to configure interrupt functionality is: + * shut down -> enable/disable interrupt -> power on + * + * state = 1 enables interrupt, state = 0 disables interrupt + */ +static int veml6030_write_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + int ret; + struct veml6030_data *data = iio_priv(indio_dev); + + ret = veml6030_als_shut_down(data); + if (ret < 0) { + dev_err(&data->client->dev, + "can't disable als to configure interrupt %d\n", ret); + return ret; + } + + /* enable interrupt + power on */ + ret = regmap_update_bits(data->regmap, VEML6030_REG_ALS_CONF, + VEML6030_ALS_INT_EN | VEML6030_ALS_SD, state << 1); + if (ret) + dev_err(&data->client->dev, + "can't enable interrupt & poweron als %d\n", ret); + + return ret; +} + +static const struct iio_info veml6030_info = { + .read_raw = veml6030_read_raw, + .read_avail = veml6030_read_avail, + .write_raw = veml6030_write_raw, + .write_raw_get_fmt = veml6030_write_raw_get_fmt, + .read_event_value = veml6030_read_event_val, + .write_event_value = veml6030_write_event_val, + .read_event_config = veml6030_read_interrupt_config, + .write_event_config = veml6030_write_interrupt_config, + .event_attrs = &veml6030_event_attr_group, +}; + +static const struct iio_info veml6030_info_no_irq = { + .read_raw = veml6030_read_raw, + .read_avail = veml6030_read_avail, + .write_raw = veml6030_write_raw, + .write_raw_get_fmt = veml6030_write_raw_get_fmt, +}; + +static irqreturn_t veml6030_event_handler(int irq, void *private) +{ + int ret, reg, evtdir; + struct iio_dev *indio_dev = private; + struct veml6030_data *data = iio_priv(indio_dev); + + ret = regmap_read(data->regmap, VEML6030_REG_ALS_INT, ®); + if (ret) { + dev_err(&data->client->dev, + "can't read als interrupt register %d\n", ret); + return IRQ_HANDLED; + } + + /* Spurious interrupt handling */ + if (!(reg & (VEML6030_INT_TH_HIGH | VEML6030_INT_TH_LOW))) + return IRQ_NONE; + + if (reg & VEML6030_INT_TH_HIGH) + evtdir = IIO_EV_DIR_RISING; + else + evtdir = IIO_EV_DIR_FALLING; + + iio_push_event(indio_dev, IIO_UNMOD_EVENT_CODE(IIO_LIGHT, + 0, + IIO_EV_TYPE_THRESH, + evtdir), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; +} + +static irqreturn_t veml6030_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio = pf->indio_dev; + struct veml6030_data *data = iio_priv(iio); + unsigned int reg; + int ch, ret, i = 0; + struct { + u16 chans[2]; + aligned_s64 timestamp; + } scan = { }; + + iio_for_each_active_channel(iio, ch) { + ret = regmap_read(data->regmap, VEML6030_REG_DATA(ch), + ®); + if (ret) + goto done; + + scan.chans[i++] = reg; + } + + iio_push_to_buffers_with_ts(iio, &scan, sizeof(scan), pf->timestamp); + +done: + iio_trigger_notify_done(iio->trig); + + return IRQ_HANDLED; +} + +static int veml6030_set_info(struct iio_dev *indio_dev) +{ + struct veml6030_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + int ret; + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, veml6030_event_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + indio_dev->name, indio_dev); + if (ret < 0) + return ret; + + indio_dev->info = &veml6030_info; + } else { + indio_dev->info = &veml6030_info_no_irq; + } + + return 0; +} + +static int veml7700_set_info(struct iio_dev *indio_dev) +{ + indio_dev->info = &veml6030_info_no_irq; + + return 0; +} + +static int veml6030_regfield_init(struct iio_dev *indio_dev) +{ + struct veml6030_data *data = iio_priv(indio_dev); + struct regmap *regmap = data->regmap; + struct device *dev = &data->client->dev; + struct regmap_field *rm_field; + struct veml6030_rf *rf = &data->rf; + + rm_field = devm_regmap_field_alloc(dev, regmap, data->chip->it_rf); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->it = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, data->chip->gain_rf); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->gain = rm_field; + + return 0; +} + +/* + * Set ALS gain to 1/8, integration time to 100 ms, PSM to mode 2, + * persistence to 1 x integration time and the threshold + * interrupt disabled by default. First shutdown the sensor, + * update registers and then power on the sensor. + */ +static int veml6030_hw_init(struct iio_dev *indio_dev, struct device *dev) +{ + int ret, val; + struct veml6030_data *data = iio_priv(indio_dev); + + ret = devm_iio_init_iio_gts(dev, 2, 150400000, + veml6030_gain_sel, ARRAY_SIZE(veml6030_gain_sel), + veml6030_it_sel, ARRAY_SIZE(veml6030_it_sel), + &data->gts); + if (ret) + return dev_err_probe(dev, ret, "failed to init iio gts\n"); + + ret = veml6030_als_shut_down(data); + if (ret) + return dev_err_probe(dev, ret, "can't shutdown als\n"); + + ret = regmap_write(data->regmap, VEML6030_REG_ALS_CONF, 0x1001); + if (ret) + return dev_err_probe(dev, ret, "can't setup als configs\n"); + + ret = regmap_update_bits(data->regmap, VEML6030_REG_ALS_PSM, + VEML6030_PSM | VEML6030_PSM_EN, 0x03); + if (ret) + return dev_err_probe(dev, ret, "can't setup default PSM\n"); + + ret = regmap_write(data->regmap, VEML6030_REG_ALS_WH, 0xFFFF); + if (ret) + return dev_err_probe(dev, ret, "can't setup high threshold\n"); + + ret = regmap_write(data->regmap, VEML6030_REG_ALS_WL, 0x0000); + if (ret) + return dev_err_probe(dev, ret, "can't setup low threshold\n"); + + ret = veml6030_als_pwr_on(data); + if (ret) + return dev_err_probe(dev, ret, "can't poweron als\n"); + + ret = devm_add_action_or_reset(dev, veml6030_als_shut_down_action, data); + if (ret < 0) + return ret; + + /* Clear stale interrupt status bits if any during start */ + ret = regmap_read(data->regmap, VEML6030_REG_ALS_INT, &val); + if (ret < 0) + return dev_err_probe(dev, ret, + "can't clear als interrupt status\n"); + + return ret; +} + +/* + * Set ALS gain to 1/8, integration time to 100 ms, ALS and WHITE + * channel enabled, ALS channel interrupt, PSM enabled, + * PSM_WAIT = 0.8 s, persistence to 1 x integration time and the + * threshold interrupt disabled by default. First shutdown the sensor, + * update registers and then power on the sensor. + */ +static int veml6035_hw_init(struct iio_dev *indio_dev, struct device *dev) +{ + int ret, val; + struct veml6030_data *data = iio_priv(indio_dev); + + ret = devm_iio_init_iio_gts(dev, 0, 409600000, + veml6035_gain_sel, ARRAY_SIZE(veml6035_gain_sel), + veml6030_it_sel, ARRAY_SIZE(veml6030_it_sel), + &data->gts); + if (ret) + return dev_err_probe(dev, ret, "failed to init iio gts\n"); + + ret = veml6030_als_shut_down(data); + if (ret) + return dev_err_probe(dev, ret, "can't shutdown als\n"); + + ret = regmap_write(data->regmap, VEML6030_REG_ALS_CONF, + VEML6035_SENS | VEML6035_CHAN_EN | VEML6030_ALS_SD); + if (ret) + return dev_err_probe(dev, ret, "can't setup als configs\n"); + + ret = regmap_update_bits(data->regmap, VEML6030_REG_ALS_PSM, + VEML6030_PSM | VEML6030_PSM_EN, 0x03); + if (ret) + return dev_err_probe(dev, ret, "can't setup default PSM\n"); + + ret = regmap_write(data->regmap, VEML6030_REG_ALS_WH, 0xFFFF); + if (ret) + return dev_err_probe(dev, ret, "can't setup high threshold\n"); + + ret = regmap_write(data->regmap, VEML6030_REG_ALS_WL, 0x0000); + if (ret) + return dev_err_probe(dev, ret, "can't setup low threshold\n"); + + ret = veml6030_als_pwr_on(data); + if (ret) + return dev_err_probe(dev, ret, "can't poweron als\n"); + + ret = devm_add_action_or_reset(dev, veml6030_als_shut_down_action, data); + if (ret < 0) + return ret; + + /* Clear stale interrupt status bits if any during start */ + ret = regmap_read(data->regmap, VEML6030_REG_ALS_INT, &val); + if (ret < 0) + return dev_err_probe(dev, ret, + "can't clear als interrupt status\n"); + + return 0; +} + +static int veml6030_probe(struct i2c_client *client) +{ + int ret; + struct veml6030_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return dev_err_probe(&client->dev, -EOPNOTSUPP, + "i2c adapter doesn't support plain i2c\n"); + + regmap = devm_regmap_init_i2c(client, &veml6030_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&client->dev, PTR_ERR(regmap), + "can't setup regmap\n"); + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + + ret = devm_regulator_get_enable(&client->dev, "vdd"); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to enable regulator\n"); + + data->chip = i2c_get_match_data(client); + if (!data->chip) + return -EINVAL; + + indio_dev->name = data->chip->name; + indio_dev->channels = data->chip->channels; + indio_dev->num_channels = data->chip->num_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = data->chip->set_info(indio_dev); + if (ret < 0) + return ret; + + ret = veml6030_regfield_init(indio_dev); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to init regfields\n"); + + ret = data->chip->hw_init(indio_dev, &client->dev); + if (ret < 0) + return ret; + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL, + veml6030_trigger_handler, NULL); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to register triggered buffer"); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int veml6030_runtime_suspend(struct device *dev) +{ + int ret; + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct veml6030_data *data = iio_priv(indio_dev); + + ret = veml6030_als_shut_down(data); + if (ret < 0) + dev_err(&data->client->dev, "can't suspend als %d\n", ret); + + return ret; +} + +static int veml6030_runtime_resume(struct device *dev) +{ + int ret; + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct veml6030_data *data = iio_priv(indio_dev); + + ret = veml6030_als_pwr_on(data); + if (ret < 0) + dev_err(&data->client->dev, "can't resume als %d\n", ret); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(veml6030_pm_ops, veml6030_runtime_suspend, + veml6030_runtime_resume, NULL); + +static const struct veml603x_chip veml6030_chip = { + .name = "veml6030", + .channels = veml6030_channels, + .num_channels = ARRAY_SIZE(veml6030_channels), + .gain_rf = VEML6030_GAIN_RF, + .it_rf = VEML6030_IT_RF, + .max_scale = VEML6030_MAX_SCALE, + .hw_init = veml6030_hw_init, + .set_info = veml6030_set_info, +}; + +static const struct veml603x_chip veml6035_chip = { + .name = "veml6035", + .channels = veml6030_channels, + .num_channels = ARRAY_SIZE(veml6030_channels), + .gain_rf = VEML6035_GAIN_RF, + .it_rf = VEML6030_IT_RF, + .max_scale = VEML6035_MAX_SCALE, + .hw_init = veml6035_hw_init, + .set_info = veml6030_set_info, +}; + +static const struct veml603x_chip veml7700_chip = { + .name = "veml7700", + .channels = veml7700_channels, + .num_channels = ARRAY_SIZE(veml7700_channels), + .gain_rf = VEML6030_GAIN_RF, + .it_rf = VEML6030_IT_RF, + .max_scale = VEML6030_MAX_SCALE, + .hw_init = veml6030_hw_init, + .set_info = veml7700_set_info, +}; + +static const struct of_device_id veml6030_of_match[] = { + { + .compatible = "vishay,veml6030", + .data = &veml6030_chip, + }, + { + .compatible = "vishay,veml6035", + .data = &veml6035_chip, + }, + { + .compatible = "vishay,veml7700", + .data = &veml7700_chip, + }, + { } +}; +MODULE_DEVICE_TABLE(of, veml6030_of_match); + +static const struct i2c_device_id veml6030_id[] = { + { .name = "veml6030", .driver_data = (kernel_ulong_t)&veml6030_chip }, + { .name = "veml6035", .driver_data = (kernel_ulong_t)&veml6035_chip }, + { .name = "veml7700", .driver_data = (kernel_ulong_t)&veml7700_chip }, + { } +}; +MODULE_DEVICE_TABLE(i2c, veml6030_id); + +static struct i2c_driver veml6030_driver = { + .driver = { + .name = "veml6030", + .of_match_table = veml6030_of_match, + .pm = pm_ptr(&veml6030_pm_ops), + }, + .probe = veml6030_probe, + .id_table = veml6030_id, +}; +module_i2c_driver(veml6030_driver); + +MODULE_AUTHOR("Rishi Gupta <gupt21@gmail.com>"); +MODULE_DESCRIPTION("VEML6030 Ambient Light Sensor"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_GTS_HELPER"); diff --git a/drivers/iio/light/veml6040.c b/drivers/iio/light/veml6040.c new file mode 100644 index 000000000..0960784b8 --- /dev/null +++ b/drivers/iio/light/veml6040.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Vishay VEML6040 RGBW light sensor driver + * + * Copyright (C) 2024 Sentec AG + * Author: Arthur Becker <arthur.becker@sentec.com> + * + */ + +#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 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_MS 0 +#define VEML6040_CONF_IT_80_MS 1 +#define VEML6040_CONF_IT_160_MS 2 +#define VEML6040_CONF_IT_320_MS 3 +#define VEML6040_CONF_IT_640_MS 4 +#define VEML6040_CONF_IT_1280_MS 5 + +/* VEML6040 Read Only Registers */ +#define VEML6040_REG_R 0x08 +#define VEML6040_REG_G 0x09 +#define VEML6040_REG_B 0x0A +#define VEML6040_REG_W 0x0B + +static const int veml6040_it_ms[] = { 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, + .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: + ret = regmap_read(regmap, chan->address, ®); + if (ret) { + dev_err(dev, "Data read failed: %d\n", ret); + return ret; + } + *val = reg; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_INT_TIME: + ret = regmap_read(regmap, VEML6040_CONF_REG, ®); + if (ret) { + dev_err(dev, "Data read failed: %d\n", ret); + return ret; + } + it_index = FIELD_GET(VEML6040_CONF_IT_MSK, reg); + if (it_index >= ARRAY_SIZE(veml6040_it_ms)) { + dev_err(dev, "Invalid Integration Time Set"); + return -EINVAL; + } + *val = veml6040_it_ms[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_it_ms); i++) { + if (veml6040_it_ms[i] != val) + continue; + + return regmap_update_bits(data->regmap, + VEML6040_CONF_REG, + VEML6040_CONF_IT_MSK, + FIELD_PREP(VEML6040_CONF_IT_MSK, i)); + } + return -EINVAL; + default: + return -EINVAL; + } +} + +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_it_ms); + *vals = veml6040_it_ms; + *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, + .address = VEML6040_REG_R, + .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, + .address = VEML6040_REG_G, + .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, + .address = VEML6040_REG_B, + .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, + .address = VEML6040_REG_W, + .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 void veml6040_shutdown_action(void *data) +{ + struct veml6040_data *veml6040_data = data; + + regmap_update_bits(veml6040_data->regmap, VEML6040_CONF_REG, + VEML6040_CONF_SD_MSK, VEML6040_CONF_SD_MSK); +} + +static int veml6040_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct veml6040_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + const int init_config = + FIELD_PREP(VEML6040_CONF_IT_MSK, VEML6040_CONF_IT_40_MS) | + FIELD_PREP(VEML6040_CONF_AF_MSK, 0) | + FIELD_PREP(VEML6040_CONF_SD_MSK, 0); + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return dev_err_probe(dev, -EOPNOTSUPP, + "I2C adapter doesn't support plain I2C\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &veml6040_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(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 = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return ret; + + ret = regmap_write(regmap, VEML6040_CONF_REG, init_config); + if (ret) + return dev_err_probe(dev, ret, + "Could not set initial config\n"); + + ret = devm_add_action_or_reset(dev, veml6040_shutdown_action, data); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct i2c_device_id veml6040_id_table[] = { + { .name = "veml6040" }, + { } +}; +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@sentec.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/veml6046x00.c b/drivers/iio/light/veml6046x00.c new file mode 100644 index 000000000..b40b70679 --- /dev/null +++ b/drivers/iio/light/veml6046x00.c @@ -0,0 +1,1029 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * VEML6046X00 High Accuracy RGBIR Color Sensor + * + * Copyright (c) 2025 Andreas Klinger <ak@it-klinger.de> + */ + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/time.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <asm/byteorder.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +/* + * Device registers + * Those which are accessed as bulk io are omitted + */ +#define VEML6046X00_REG_CONF0 0x00 +#define VEML6046X00_REG_CONF1 0x01 +#define VEML6046X00_REG_THDH 0x04 +#define VEML6046X00_REG_THDL 0x06 +#define VEML6046X00_REG_R 0x10 +#define VEML6046X00_REG_G 0x12 +#define VEML6046X00_REG_B 0x14 +#define VEML6046X00_REG_IR 0x16 +#define VEML6046X00_REG_ID 0x18 +#define VEML6046X00_REG_INT 0x1A +#define VEML6046X00_REG_INT_H 0x1B + +/* Bit masks for specific functionality */ +#define VEML6046X00_CONF0_ON_0 BIT(0) +#define VEML6046X00_CONF0_INT BIT(1) +#define VEML6046X00_CONF0_AF_TRIG BIT(2) +#define VEML6046X00_CONF0_AF BIT(3) +#define VEML6046X00_CONF0_IT GENMASK(6, 4) +#define VEML6046X00_CONF1_CAL BIT(0) +#define VEML6046X00_CONF1_PERS GENMASK(2, 1) +#define VEML6046X00_CONF1_GAIN GENMASK(4, 3) +#define VEML6046X00_CONF1_PD_D2 BIT(6) +#define VEML6046X00_CONF1_ON_1 BIT(7) +#define VEML6046X00_INT_TH_H BIT(1) +#define VEML6046X00_INT_TH_L BIT(2) +#define VEML6046X00_INT_DRDY BIT(3) +#define VEML6046X00_INT_MASK \ + (VEML6046X00_INT_TH_H | VEML6046X00_INT_TH_L | VEML6046X00_INT_DRDY) + +#define VEML6046X00_GAIN_1 0x0 +#define VEML6046X00_GAIN_2 0x1 +#define VEML6046X00_GAIN_0_66 0x2 +#define VEML6046X00_GAIN_0_5 0x3 + +#define VEML6046X00_PD_2_2 0x0 +#define VEML6046X00_PD_1_2 BIT(6) + +/* Autosuspend delay */ +#define VEML6046X00_AUTOSUSPEND_MS (3 * MSEC_PER_SEC) + +enum veml6046x00_scan { + VEML6046X00_SCAN_R, + VEML6046X00_SCAN_G, + VEML6046X00_SCAN_B, + VEML6046X00_SCAN_IR, + VEML6046X00_SCAN_TIMESTAMP, +}; + +/** + * struct veml6046x00_rf - Regmap field of configuration registers. + * @int_en: Interrupt enable of green channel. + * @mode: Mode of operation. + * Driver uses always Active force mode. + * @trig: Trigger to be set in active force mode for starting + * measurement. + * @it: Integration time. + * @pers: Persistense - Number of threshold crossing for triggering + * interrupt. + */ +struct veml6046x00_rf { + struct regmap_field *int_en; + struct regmap_field *mode; + struct regmap_field *trig; + struct regmap_field *it; + struct regmap_field *pers; +}; + +/** + * struct veml6046x00_data - Private data of driver. + * @regmap: Regmap definition of sensor. + * @trig: Industrial-IO trigger. + * @rf: Regmap field of configuration. + */ +struct veml6046x00_data { + struct regmap *regmap; + struct iio_trigger *trig; + struct veml6046x00_rf rf; +}; + +/** + * DOC: Valid integration times (IT) + * + * static const int veml6046x00_it contains the array with valid IT. + * + * Register value to be read or written in regmap_field it on veml6046x00 is + * identical with array index. + * This means there is no separate translation table between valid integration + * times and register values needed. The index of the array is identical with + * the register value. + * + * The array is in the form as expected by the callback of the sysfs attribute + * integration_time_available (IIO_CHAN_INFO_INT_TIME). So there is no + * additional conversion needed. + */ +static const int veml6046x00_it[][2] = { + { 0, 3125 }, + { 0, 6250 }, + { 0, 12500 }, + { 0, 25000 }, + { 0, 50000 }, + { 0, 100000 }, + { 0, 200000 }, + { 0, 400000 }, +}; + +/** + * DOC: Handling of gain and photodiode size (PD) + * + * Gains here in the driver are not exactly the same as in the datasheet of the + * sensor. The gain in the driver is a combination of the gain of the sensor + * with the photodiode size (PD). + * The following combinations are possible: + * gain(driver) = gain(sensor) * PD + * 0.25 = x0.5 * 1/2 + * 0.33 = x0.66 * 1/2 + * 0.5 = x0.5 * 2/2 + * 0.66 = x0.66 * 2/2 + * 1 = x1 * 2/2 + * 2 = x2 * 2/2 + */ + +/** + * struct veml6046x00_gain_pd - Translation of gain and photodiode size (PD). + * @gain_sen: Gain used in the sensor as described in the datasheet of the + * sensor + * @pd: Photodiode size in the sensor + * + * This is the translation table from the gain used in the driver (and also used + * by the userspace interface in sysfs) to the gain and PD used in the sensor + * hardware. + * + * There are six gain values visible to the user (0.25 .. 2) which translate to + * two different gains in the sensor hardware (x0.5 .. x2) and two PD (1/2 and + * 2/2). Theoretical are there eight combinations, but gain values 0.5 and 1 are + * doubled and therefore the combination with the larger PD (2/2) is taken as + * more photodiode cells are supposed to deliver a more precise result. + */ +struct veml6046x00_gain_pd { + unsigned int gain_sen; + unsigned int pd; +}; + +static const struct veml6046x00_gain_pd veml6046x00_gain_pd[] = { + { .gain_sen = VEML6046X00_GAIN_0_5, .pd = VEML6046X00_PD_1_2 }, + { .gain_sen = VEML6046X00_GAIN_0_66, .pd = VEML6046X00_PD_1_2 }, + { .gain_sen = VEML6046X00_GAIN_0_5, .pd = VEML6046X00_PD_2_2 }, + { .gain_sen = VEML6046X00_GAIN_0_66, .pd = VEML6046X00_PD_2_2 }, + { .gain_sen = VEML6046X00_GAIN_1, .pd = VEML6046X00_PD_2_2 }, + { .gain_sen = VEML6046X00_GAIN_2, .pd = VEML6046X00_PD_2_2 }, +}; + +/** + * DOC: Factors for calculation of lux + * + * static const int veml6046x00_it_gains contains the factors for calculation of + * lux. + * + * Depending on the set up integration time (IT), gain and photodiode size (PD) + * the measured raw values are different if the light is constant. As the gain + * and PD are already coupled in the driver (see &struct veml6046x00_gain_pd) + * there are two dimensions remaining: IT and gain(driver). + * + * The array of available factors for a certain IT are grouped together in the + * same form as expected by the callback of scale_available + * (IIO_CHAN_INFO_SCALE). + * + * Factors for lux / raw count are taken directly from the datasheet. + */ +static const int veml6046x00_it_gains[][6][2] = { + /* integration time: 3.125 ms */ + { + { 5, 376000 }, /* gain: x0.25 */ + { 4, 72700 }, /* gain: x0.33 */ + { 2, 688000 }, /* gain: x0.5 */ + { 2, 36400 }, /* gain: x0.66 */ + { 1, 344000 }, /* gain: x1 */ + { 0, 672000 }, /* gain: x2 */ + }, + /* integration time: 6.25 ms */ + { + { 2, 688000 }, /* gain: x0.25 */ + { 2, 36350 }, /* gain: x0.33 */ + { 1, 344000 }, /* gain: x0.5 */ + { 1, 18200 }, /* gain: x0.66 */ + { 0, 672000 }, /* gain: x1 */ + { 0, 336000 }, /* gain: x2 */ + }, + /* integration time: 12.5 ms */ + { + { 1, 344000 }, /* gain: x0.25 */ + { 1, 18175 }, /* gain: x0.33 */ + { 0, 672000 }, /* gain: x0.5 */ + { 0, 509100 }, /* gain: x0.66 */ + { 0, 336000 }, /* gain: x1 */ + { 0, 168000 }, /* gain: x2 */ + }, + /* integration time: 25 ms */ + { + { 0, 672000 }, /* gain: x0.25 */ + { 0, 509087 }, /* gain: x0.33 */ + { 0, 336000 }, /* gain: x0.5 */ + { 0, 254550 }, /* gain: x0.66 */ + { 0, 168000 }, /* gain: x1 */ + { 0, 84000 }, /* gain: x2 */ + }, + /* integration time: 50 ms */ + { + { 0, 336000 }, /* gain: x0.25 */ + { 0, 254543 }, /* gain: x0.33 */ + { 0, 168000 }, /* gain: x0.5 */ + { 0, 127275 }, /* gain: x0.66 */ + { 0, 84000 }, /* gain: x1 */ + { 0, 42000 }, /* gain: x2 */ + }, + /* integration time: 100 ms */ + { + { 0, 168000 }, /* gain: x0.25 */ + { 0, 127271 }, /* gain: x0.33 */ + { 0, 84000 }, /* gain: x0.5 */ + { 0, 63637 }, /* gain: x0.66 */ + { 0, 42000 }, /* gain: x1 */ + { 0, 21000 }, /* gain: x2 */ + }, + /* integration time: 200 ms */ + { + { 0, 84000 }, /* gain: x0.25 */ + { 0, 63635 }, /* gain: x0.33 */ + { 0, 42000 }, /* gain: x0.5 */ + { 0, 31818 }, /* gain: x0.66 */ + { 0, 21000 }, /* gain: x1 */ + { 0, 10500 }, /* gain: x2 */ + }, + /* integration time: 400 ms */ + { + { 0, 42000 }, /* gain: x0.25 */ + { 0, 31817 }, /* gain: x0.33 */ + { 0, 21000 }, /* gain: x0.5 */ + { 0, 15909 }, /* gain: x0.66 */ + { 0, 10500 }, /* gain: x1 */ + { 0, 5250 }, /* gain: x2 */ + }, +}; + +/* + * Two bits (RGB_ON_0 and RGB_ON_1) must be cleared to power on the device. + */ +static int veml6046x00_power_on(struct veml6046x00_data *data) +{ + int ret; + struct device *dev = regmap_get_device(data->regmap); + + ret = regmap_clear_bits(data->regmap, VEML6046X00_REG_CONF0, + VEML6046X00_CONF0_ON_0); + if (ret) { + dev_err(dev, "Failed to set bit for power on %d\n", ret); + return ret; + } + + return regmap_clear_bits(data->regmap, VEML6046X00_REG_CONF1, + VEML6046X00_CONF1_ON_1); +} + +/* + * Two bits (RGB_ON_0 and RGB_ON_1) must be set to power off the device. + */ +static int veml6046x00_shutdown(struct veml6046x00_data *data) +{ + int ret; + struct device *dev = regmap_get_device(data->regmap); + + ret = regmap_set_bits(data->regmap, VEML6046X00_REG_CONF0, + VEML6046X00_CONF0_ON_0); + if (ret) { + dev_err(dev, "Failed to set bit for shutdown %d\n", ret); + return ret; + } + + return regmap_set_bits(data->regmap, VEML6046X00_REG_CONF1, + VEML6046X00_CONF1_ON_1); +} + +static void veml6046x00_shutdown_action(void *data) +{ + veml6046x00_shutdown(data); +} + +static const struct iio_chan_spec veml6046x00_channels[] = { + { + .type = IIO_INTENSITY, + .address = VEML6046X00_REG_R, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_RED, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6046X00_SCAN_R, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .address = VEML6046X00_REG_G, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_GREEN, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6046X00_SCAN_G, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .address = VEML6046X00_REG_B, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BLUE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6046X00_SCAN_B, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .address = VEML6046X00_REG_IR, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6046X00_SCAN_IR, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(VEML6046X00_SCAN_TIMESTAMP), +}; + +static const struct regmap_config veml6046x00_regmap_config = { + .name = "veml6046x00_regm", + .reg_bits = 8, + .val_bits = 8, + .max_register = VEML6046X00_REG_INT_H, +}; + +static const struct reg_field veml6046x00_rf_int_en = + REG_FIELD(VEML6046X00_REG_CONF0, 1, 1); + +static const struct reg_field veml6046x00_rf_trig = + REG_FIELD(VEML6046X00_REG_CONF0, 2, 2); + +static const struct reg_field veml6046x00_rf_mode = + REG_FIELD(VEML6046X00_REG_CONF0, 3, 3); + +static const struct reg_field veml6046x00_rf_it = + REG_FIELD(VEML6046X00_REG_CONF0, 4, 6); + +static const struct reg_field veml6046x00_rf_pers = + REG_FIELD(VEML6046X00_REG_CONF1, 1, 2); + +static int veml6046x00_regfield_init(struct veml6046x00_data *data) +{ + struct regmap *regmap = data->regmap; + struct device *dev = regmap_get_device(data->regmap); + struct regmap_field *rm_field; + struct veml6046x00_rf *rf = &data->rf; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_int_en); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->int_en = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_mode); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->mode = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_trig); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->trig = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_it); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->it = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_pers); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->pers = rm_field; + + return 0; +} + +static int veml6046x00_get_it_index(struct veml6046x00_data *data) +{ + int ret; + unsigned int reg; + + ret = regmap_field_read(data->rf.it, ®); + if (ret) + return ret; + + /* register value is identical with index of array */ + if (reg >= ARRAY_SIZE(veml6046x00_it)) + return -EINVAL; + + return reg; +} + +static int veml6046x00_get_it_usec(struct veml6046x00_data *data, unsigned int *it_usec) +{ + int ret; + unsigned int reg; + + ret = regmap_field_read(data->rf.it, ®); + if (ret) + return ret; + + if (reg >= ARRAY_SIZE(veml6046x00_it)) + return -EINVAL; + + *it_usec = veml6046x00_it[reg][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int veml6046x00_set_it(struct iio_dev *iio, int val, int val2) +{ + struct veml6046x00_data *data = iio_priv(iio); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(veml6046x00_it); i++) { + if ((veml6046x00_it[i][0] == val) && + (veml6046x00_it[i][1] == val2)) + return regmap_field_write(data->rf.it, i); + } + + return -EINVAL; +} + +static int veml6046x00_get_val_gain_idx(struct veml6046x00_data *data, int val, + int val2) +{ + unsigned int i; + int it_idx; + + it_idx = veml6046x00_get_it_index(data); + if (it_idx < 0) + return it_idx; + + for (i = 0; i < ARRAY_SIZE(veml6046x00_it_gains[it_idx]); i++) { + if ((veml6046x00_it_gains[it_idx][i][0] == val) && + (veml6046x00_it_gains[it_idx][i][1] == val2)) + return i; + } + + return -EINVAL; +} + +static int veml6046x00_get_gain_idx(struct veml6046x00_data *data) +{ + int ret; + unsigned int i, reg, reg_gain, reg_pd; + + ret = regmap_read(data->regmap, VEML6046X00_REG_CONF1, ®); + if (ret) + return ret; + + reg_gain = FIELD_GET(VEML6046X00_CONF1_GAIN, reg); + reg_pd = reg & VEML6046X00_CONF1_PD_D2; + + for (i = 0; i < ARRAY_SIZE(veml6046x00_gain_pd); i++) { + if ((veml6046x00_gain_pd[i].gain_sen == reg_gain) && + (veml6046x00_gain_pd[i].pd == reg_pd)) + return i; + } + + return -EINVAL; +} + +static int veml6046x00_set_scale(struct iio_dev *iio, int val, int val2) +{ + struct veml6046x00_data *data = iio_priv(iio); + unsigned int new_scale; + int gain_idx; + + gain_idx = veml6046x00_get_val_gain_idx(data, val, val2); + if (gain_idx < 0) + return gain_idx; + + new_scale = FIELD_PREP(VEML6046X00_CONF1_GAIN, + veml6046x00_gain_pd[gain_idx].gain_sen) | + veml6046x00_gain_pd[gain_idx].pd; + + return regmap_update_bits(data->regmap, VEML6046X00_REG_CONF1, + VEML6046X00_CONF1_GAIN | + VEML6046X00_CONF1_PD_D2, + new_scale); +} + +static int veml6046x00_get_scale(struct veml6046x00_data *data, + int *val, int *val2) +{ + int gain_idx, it_idx; + + gain_idx = veml6046x00_get_gain_idx(data); + if (gain_idx < 0) + return gain_idx; + + it_idx = veml6046x00_get_it_index(data); + if (it_idx < 0) + return it_idx; + + *val = veml6046x00_it_gains[it_idx][gain_idx][0]; + *val2 = veml6046x00_it_gains[it_idx][gain_idx][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +/** + * veml6046x00_read_data_ready() - Read data ready bit + * @data: Private data. + * + * Helper function for reading data ready bit from interrupt register. + * + * Return: + * * %1 - Data is available (AF_DATA_READY is set) + * * %0 - No data available + * * %-EIO - Error during bulk read + */ +static int veml6046x00_read_data_ready(struct veml6046x00_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + u8 reg[2]; + + /* + * Note from the vendor, but not explicitly in the datasheet: we + * should always read both registers together. + */ + ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_INT, + ®, sizeof(reg)); + if (ret) { + dev_err(dev, "Failed to read interrupt register %d\n", ret); + return -EIO; + } + + if (reg[1] & VEML6046X00_INT_DRDY) + return 1; + + return 0; +} + +/** + * veml6046x00_wait_data_available() - Wait until data is available + * @iio: Industrial IO. + * @usecs: Microseconds to wait for data. + * + * This function waits for a certain bit in the interrupt register which signals + * that there is data to be read available. + * + * It tries it two times with a waiting time of usecs in between. + * + * Return: + * * %1 - Data is available (AF_DATA_READY is set) + * * %0 - Timeout, no data available after usecs timeout + * * %-EIO - Error during bulk read + */ +static int veml6046x00_wait_data_available(struct iio_dev *iio, unsigned int usecs) +{ + struct veml6046x00_data *data = iio_priv(iio); + int ret; + + ret = veml6046x00_read_data_ready(data); + if (ret) + return ret; + + fsleep(usecs); + return veml6046x00_read_data_ready(data); +} + +static int veml6046x00_single_read(struct iio_dev *iio, + enum iio_modifier modifier, int *val) +{ + struct veml6046x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + unsigned int addr, it_usec; + int ret; + __le16 reg; + + switch (modifier) { + case IIO_MOD_LIGHT_RED: + addr = VEML6046X00_REG_R; + break; + case IIO_MOD_LIGHT_GREEN: + addr = VEML6046X00_REG_G; + break; + case IIO_MOD_LIGHT_BLUE: + addr = VEML6046X00_REG_B; + break; + case IIO_MOD_LIGHT_IR: + addr = VEML6046X00_REG_IR; + break; + default: + return -EINVAL; + } + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = veml6046x00_get_it_usec(data, &it_usec); + if (ret < 0) { + dev_err(dev, "Failed to get integration time ret: %d", ret); + goto out; + } + + ret = regmap_field_write(data->rf.mode, 1); + if (ret) { + dev_err(dev, "Failed to write mode ret: %d", ret); + goto out; + } + + ret = regmap_field_write(data->rf.trig, 1); + if (ret) { + dev_err(dev, "Failed to write trigger ret: %d", ret); + goto out; + } + + /* integration time + 12.5 % to ensure completion */ + fsleep(it_usec + it_usec / 8); + + ret = veml6046x00_wait_data_available(iio, it_usec * 4); + if (ret < 0) + goto out; + if (ret == 0) { + ret = -EAGAIN; + goto out; + } + + if (!iio_device_claim_direct(iio)) { + ret = -EBUSY; + goto out; + } + + ret = regmap_bulk_read(data->regmap, addr, ®, sizeof(reg)); + iio_device_release_direct(iio); + if (ret) + goto out; + + *val = le16_to_cpu(reg); + + ret = IIO_VAL_INT; + +out: + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static int veml6046x00_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct veml6046x00_data *data = iio_priv(iio); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_INTENSITY) + return -EINVAL; + return veml6046x00_single_read(iio, chan->channel2, val); + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + return veml6046x00_get_it_usec(data, val2); + case IIO_CHAN_INFO_SCALE: + return veml6046x00_get_scale(data, val, val2); + default: + return -EINVAL; + } +} + +static int veml6046x00_read_avail(struct iio_dev *iio, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct veml6046x00_data *data = iio_priv(iio); + int it_idx; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *vals = (int *)&veml6046x00_it; + *length = 2 * ARRAY_SIZE(veml6046x00_it); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + it_idx = veml6046x00_get_it_index(data); + if (it_idx < 0) + return it_idx; + *vals = (int *)&veml6046x00_it_gains[it_idx]; + *length = 2 * ARRAY_SIZE(veml6046x00_it_gains[it_idx]); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int veml6046x00_write_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return veml6046x00_set_it(iio, val, val2); + case IIO_CHAN_INFO_SCALE: + return veml6046x00_set_scale(iio, val, val2); + default: + return -EINVAL; + } +} + +static const struct iio_info veml6046x00_info_no_irq = { + .read_raw = veml6046x00_read_raw, + .read_avail = veml6046x00_read_avail, + .write_raw = veml6046x00_write_raw, +}; + +static int veml6046x00_buffer_preenable(struct iio_dev *iio) +{ + struct veml6046x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regmap_field_write(data->rf.mode, 0); + if (ret) { + dev_err(dev, "Failed to set mode %d\n", ret); + return ret; + } + + ret = regmap_field_write(data->rf.trig, 0); + if (ret) { + /* + * no unrolling of mode as it is set appropriately with next + * single read. + */ + dev_err(dev, "Failed to set trigger %d\n", ret); + return ret; + } + + return pm_runtime_resume_and_get(dev); +} + +static int veml6046x00_buffer_postdisable(struct iio_dev *iio) +{ + struct veml6046x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regmap_field_write(data->rf.mode, 1); + if (ret) { + dev_err(dev, "Failed to set mode %d\n", ret); + return ret; + } + + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static const struct iio_buffer_setup_ops veml6046x00_buffer_setup_ops = { + .preenable = veml6046x00_buffer_preenable, + .postdisable = veml6046x00_buffer_postdisable, +}; + +static irqreturn_t veml6046x00_trig_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio = pf->indio_dev; + struct veml6046x00_data *data = iio_priv(iio); + int ret; + struct { + __le16 chans[4]; + aligned_s64 timestamp; + } scan; + + ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_R, + &scan.chans, sizeof(scan.chans)); + if (ret) + goto done; + + iio_push_to_buffers_with_ts(iio, &scan, sizeof(scan), + iio_get_time_ns(iio)); + +done: + iio_trigger_notify_done(iio->trig); + + return IRQ_HANDLED; +} + +static int veml6046x00_validate_part_id(struct veml6046x00_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + unsigned int part_id; + int ret; + __le16 reg; + + ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_ID, + ®, sizeof(reg)); + if (ret) + return dev_err_probe(dev, ret, "Failed to read ID\n"); + + part_id = le16_to_cpu(reg); + if (part_id != 0x01) + dev_info(dev, "Unknown ID %#04x\n", part_id); + + return 0; +} + +static int veml6046x00_setup_device(struct iio_dev *iio) +{ + struct veml6046x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + int ret; + __le16 reg16; + + reg16 = cpu_to_le16(VEML6046X00_CONF0_AF); + ret = regmap_bulk_write(data->regmap, VEML6046X00_REG_CONF0, + ®16, sizeof(reg16)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set configuration\n"); + + reg16 = cpu_to_le16(0); + ret = regmap_bulk_write(data->regmap, VEML6046X00_REG_THDL, + ®16, sizeof(reg16)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set low threshold\n"); + + reg16 = cpu_to_le16(U16_MAX); + ret = regmap_bulk_write(data->regmap, VEML6046X00_REG_THDH, + ®16, sizeof(reg16)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set high threshold\n"); + + ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_INT, + ®16, sizeof(reg16)); + if (ret) + return dev_err_probe(dev, ret, "Failed to clear interrupts\n"); + + return 0; +} + +static int veml6046x00_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct veml6046x00_data *data; + struct iio_dev *iio; + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &veml6046x00_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to set regmap\n"); + + iio = devm_iio_device_alloc(dev, sizeof(*data)); + if (!iio) + return -ENOMEM; + + data = iio_priv(iio); + /* struct iio_dev is retrieved via dev_get_drvdata(). */ + i2c_set_clientdata(i2c, iio); + data->regmap = regmap; + + ret = veml6046x00_regfield_init(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to init regfield\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulator\n"); + + /* bring device in a known state and switch device on */ + ret = veml6046x00_setup_device(iio); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(dev, veml6046x00_shutdown_action, data); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to add shut down action\n"); + + ret = pm_runtime_set_active(dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to activate PM runtime\n"); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable PM runtime\n"); + + pm_runtime_get_noresume(dev); + pm_runtime_set_autosuspend_delay(dev, VEML6046X00_AUTOSUSPEND_MS); + pm_runtime_use_autosuspend(dev); + + ret = veml6046x00_validate_part_id(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to validate device ID\n"); + + iio->name = "veml6046x00"; + iio->channels = veml6046x00_channels; + iio->num_channels = ARRAY_SIZE(veml6046x00_channels); + iio->modes = INDIO_DIRECT_MODE; + + iio->info = &veml6046x00_info_no_irq; + + ret = devm_iio_triggered_buffer_setup(dev, iio, NULL, + veml6046x00_trig_handler, + &veml6046x00_buffer_setup_ops); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register triggered buffer"); + + pm_runtime_put_autosuspend(dev); + + ret = devm_iio_device_register(dev, iio); + if (ret) + return dev_err_probe(dev, ret, "Failed to register iio device"); + + return 0; +} + +static int veml6046x00_runtime_suspend(struct device *dev) +{ + struct veml6046x00_data *data = iio_priv(dev_get_drvdata(dev)); + + return veml6046x00_shutdown(data); +} + +static int veml6046x00_runtime_resume(struct device *dev) +{ + struct veml6046x00_data *data = iio_priv(dev_get_drvdata(dev)); + + return veml6046x00_power_on(data); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(veml6046x00_pm_ops, + veml6046x00_runtime_suspend, + veml6046x00_runtime_resume, NULL); + +static const struct of_device_id veml6046x00_of_match[] = { + { .compatible = "vishay,veml6046x00" }, + { } +}; +MODULE_DEVICE_TABLE(of, veml6046x00_of_match); + +static const struct i2c_device_id veml6046x00_id[] = { + { .name = "veml6046x00" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, veml6046x00_id); + +static struct i2c_driver veml6046x00_driver = { + .driver = { + .name = "veml6046x00", + .of_match_table = veml6046x00_of_match, + .pm = pm_ptr(&veml6046x00_pm_ops), + }, + .probe = veml6046x00_probe, + .id_table = veml6046x00_id, +}; +module_i2c_driver(veml6046x00_driver); + +MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>"); +MODULE_DESCRIPTION("VEML6046X00 RGBIR Color Sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/veml6070.c b/drivers/iio/light/veml6070.c new file mode 100644 index 000000000..c99336bd9 --- /dev/null +++ b/drivers/iio/light/veml6070.c @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * veml6070.c - Support for Vishay VEML6070 UV A light sensor + * + * Copyright 2016 Peter Meerwald-Stadler <pmeerw@pmeerw.net> + * + * IIO driver for VEML6070 (7-bit I2C slave addresses 0x38 and 0x39) + * + * TODO: ACK signal + */ + +#include <linux/bitfield.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/units.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define VEML6070_DRV_NAME "veml6070" + +#define VEML6070_ADDR_CONFIG_DATA_MSB 0x38 /* read: MSB data, write: config */ +#define VEML6070_ADDR_DATA_LSB 0x39 /* LSB data */ + +#define VEML6070_COMMAND_ACK BIT(5) /* raise interrupt when over threshold */ +#define VEML6070_COMMAND_IT GENMASK(3, 2) /* bit mask integration time */ +#define VEML6070_COMMAND_RSRVD BIT(1) /* reserved, set to 1 */ +#define VEML6070_COMMAND_SD BIT(0) /* shutdown mode when set */ + +#define VEML6070_IT_05 0x00 +#define VEML6070_IT_10 0x01 +#define VEML6070_IT_20 0x02 +#define VEML6070_IT_40 0x03 + +#define VEML6070_MIN_RSET_KOHM 75 +#define VEML6070_MIN_IT_US 15625 /* Rset = 75 kohm, IT = 1/2 */ + +struct veml6070_data { + struct i2c_client *client1; + struct i2c_client *client2; + u8 config; + struct mutex lock; + u32 rset; + int it[4][2]; +}; + +static int veml6070_calc_it(struct device *dev, struct veml6070_data *data) +{ + int i, tmp_it; + + data->rset = 270000; + device_property_read_u32(dev, "vishay,rset-ohms", &data->rset); + + if (data->rset < 75000 || data->rset > 1200000) + return dev_err_probe(dev, -EINVAL, "Rset out of range\n"); + + /* + * convert to kohm to avoid overflows and work with the same units as + * in the datasheet and simplify UVI operations. + */ + data->rset /= KILO; + + tmp_it = VEML6070_MIN_IT_US * data->rset / VEML6070_MIN_RSET_KOHM; + for (i = 0; i < ARRAY_SIZE(data->it); i++) { + data->it[i][0] = (tmp_it << i) / MICRO; + data->it[i][1] = (tmp_it << i) % MICRO; + } + + return 0; +} + +static int veml6070_get_it(struct veml6070_data *data, int *val, int *val2) +{ + int it_idx = FIELD_GET(VEML6070_COMMAND_IT, data->config); + + *val = data->it[it_idx][0]; + *val2 = data->it[it_idx][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int veml6070_set_it(struct veml6070_data *data, int val, int val2) +{ + int it_idx; + + for (it_idx = 0; it_idx < ARRAY_SIZE(data->it); it_idx++) { + if (data->it[it_idx][0] == val && data->it[it_idx][1] == val2) + break; + } + + if (it_idx >= ARRAY_SIZE(data->it)) + return -EINVAL; + + data->config = (data->config & ~VEML6070_COMMAND_IT) | + FIELD_PREP(VEML6070_COMMAND_IT, it_idx); + + return i2c_smbus_write_byte(data->client1, data->config); +} + +static int veml6070_read(struct veml6070_data *data) +{ + int ret, it_ms, val, val2; + u8 msb, lsb; + + guard(mutex)(&data->lock); + + /* disable shutdown */ + ret = i2c_smbus_write_byte(data->client1, + data->config & ~VEML6070_COMMAND_SD); + if (ret < 0) + return ret; + + veml6070_get_it(data, &val, &val2); + it_ms = val * MILLI + val2 / (MICRO / MILLI); + msleep(it_ms + 10); + + ret = i2c_smbus_read_byte(data->client2); /* read MSB, address 0x39 */ + if (ret < 0) + return ret; + + msb = ret; + + ret = i2c_smbus_read_byte(data->client1); /* read LSB, address 0x38 */ + if (ret < 0) + return ret; + + lsb = ret; + + /* shutdown again */ + ret = i2c_smbus_write_byte(data->client1, data->config); + if (ret < 0) + return ret; + + return (msb << 8) | lsb; +} + +static const struct iio_chan_spec veml6070_channels[] = { + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_UV, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_UVINDEX, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), + } +}; + +static int veml6070_to_uv_index(struct veml6070_data *data, unsigned int val) +{ + /* + * conversion of raw UV intensity values to UV index depends on + * integration time (IT) and value of the resistor connected to + * the RSET pin. + */ + unsigned int uvi[11] = { + 187, 373, 560, /* low */ + 746, 933, 1120, /* moderate */ + 1308, 1494, /* high */ + 1681, 1868, 2054}; /* very high */ + int i, it_idx; + + it_idx = FIELD_GET(VEML6070_COMMAND_IT, data->config); + + if (!it_idx) + val = (val * 270 / data->rset) << 1; + else + val = (val * 270 / data->rset) >> (it_idx - 1); + + for (i = 0; i < ARRAY_SIZE(uvi); i++) + if (val <= uvi[i]) + return i; + + return 11; /* extreme */ +} + +static int veml6070_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct veml6070_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + ret = veml6070_read(data); + if (ret < 0) + return ret; + if (mask == IIO_CHAN_INFO_PROCESSED) + *val = veml6070_to_uv_index(data, ret); + else + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + return veml6070_get_it(data, val, val2); + default: + return -EINVAL; + } +} + +static int veml6070_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct veml6070_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *vals = (int *)data->it; + *length = 2 * ARRAY_SIZE(data->it); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int veml6070_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct veml6070_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return veml6070_set_it(data, val, val2); + default: + return -EINVAL; + } +} + +static const struct iio_info veml6070_info = { + .read_raw = veml6070_read_raw, + .read_avail = veml6070_read_avail, + .write_raw = veml6070_write_raw, +}; + +static int veml6070_probe(struct i2c_client *client) +{ + struct veml6070_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client1 = client; + mutex_init(&data->lock); + + indio_dev->info = &veml6070_info; + indio_dev->channels = veml6070_channels; + indio_dev->num_channels = ARRAY_SIZE(veml6070_channels); + indio_dev->name = VEML6070_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = veml6070_calc_it(&client->dev, data); + if (ret < 0) + return ret; + + ret = devm_regulator_get_enable(&client->dev, "vdd"); + if (ret < 0) + return ret; + + data->client2 = devm_i2c_new_dummy_device(&client->dev, client->adapter, + VEML6070_ADDR_DATA_LSB); + if (IS_ERR(data->client2)) + return dev_err_probe(&client->dev, PTR_ERR(data->client2), + "i2c device for second chip address failed\n"); + + data->config = FIELD_PREP(VEML6070_COMMAND_IT, VEML6070_IT_10) | + VEML6070_COMMAND_RSRVD | VEML6070_COMMAND_SD; + ret = i2c_smbus_write_byte(data->client1, data->config); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id veml6070_id[] = { + { .name = "veml6070" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, veml6070_id); + +static const struct of_device_id veml6070_of_match[] = { + { .compatible = "vishay,veml6070" }, + { } +}; +MODULE_DEVICE_TABLE(of, veml6070_of_match); + +static struct i2c_driver veml6070_driver = { + .driver = { + .name = VEML6070_DRV_NAME, + .of_match_table = veml6070_of_match, + }, + .probe = veml6070_probe, + .id_table = veml6070_id, +}; + +module_i2c_driver(veml6070_driver); + +MODULE_AUTHOR("Peter Meerwald-Stadler <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("Vishay VEML6070 UV A light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/veml6075.c b/drivers/iio/light/veml6075.c new file mode 100644 index 000000000..59187244a --- /dev/null +++ b/drivers/iio/light/veml6075.c @@ -0,0 +1,482 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Vishay VEML6075 UVA and UVB light sensor + * + * Copyright 2023 Javier Carrasco <javier.carrasco.cruz@gmail.com> + * + * 7-bit I2C slave, address 0x10 + */ + +#include <linux/bitfield.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/units.h> + +#include <linux/iio/iio.h> + +#define VEML6075_CMD_CONF 0x00 /* configuration register */ +#define VEML6075_CMD_UVA 0x07 /* UVA channel */ +#define VEML6075_CMD_UVB 0x09 /* UVB channel */ +#define VEML6075_CMD_COMP1 0x0A /* visible light compensation */ +#define VEML6075_CMD_COMP2 0x0B /* infrarred light compensation */ +#define VEML6075_CMD_ID 0x0C /* device ID */ + +#define VEML6075_CONF_IT GENMASK(6, 4) /* intregration time */ +#define VEML6075_CONF_HD BIT(3) /* dynamic setting */ +#define VEML6075_CONF_TRIG BIT(2) /* trigger */ +#define VEML6075_CONF_AF BIT(1) /* active force enable */ +#define VEML6075_CONF_SD BIT(0) /* shutdown */ + +#define VEML6075_IT_50_MS 0x00 +#define VEML6075_IT_100_MS 0x01 +#define VEML6075_IT_200_MS 0x02 +#define VEML6075_IT_400_MS 0x03 +#define VEML6075_IT_800_MS 0x04 + +#define VEML6075_AF_DISABLE 0x00 +#define VEML6075_AF_ENABLE 0x01 + +#define VEML6075_SD_DISABLE 0x00 +#define VEML6075_SD_ENABLE 0x01 + +/* Open-air coefficients and responsivity */ +#define VEML6075_A_COEF 2220 +#define VEML6075_B_COEF 1330 +#define VEML6075_C_COEF 2950 +#define VEML6075_D_COEF 1740 +#define VEML6075_UVA_RESP 1461 +#define VEML6075_UVB_RESP 2591 + +static const int veml6075_it_ms[] = { 50, 100, 200, 400, 800 }; + +struct veml6075_data { + struct i2c_client *client; + struct regmap *regmap; + /* + * prevent integration time modification and triggering + * measurements while a measurement is underway. + */ + struct mutex lock; +}; + +/* channel number */ +enum veml6075_chan { + CH_UVA, + CH_UVB, +}; + +static const struct iio_chan_spec veml6075_channels[] = { + { + .type = IIO_INTENSITY, + .channel = CH_UVA, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_UVA, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_INTENSITY, + .channel = CH_UVB, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_UVB, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_UVINDEX, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), + }, +}; + +static int veml6075_request_measurement(struct veml6075_data *data) +{ + int ret, conf, int_time, int_index; + + ret = regmap_read(data->regmap, VEML6075_CMD_CONF, &conf); + if (ret < 0) + return ret; + + /* disable shutdown and trigger measurement */ + ret = regmap_write(data->regmap, VEML6075_CMD_CONF, + (conf | VEML6075_CONF_TRIG) & ~VEML6075_CONF_SD); + if (ret < 0) + return ret; + + /* + * A measurement requires between 1.30 and 1.40 times the integration + * time for all possible configurations. Using a 1.50 factor simplifies + * operations and ensures reliability under all circumstances. + */ + int_index = FIELD_GET(VEML6075_CONF_IT, conf); + if (int_index >= ARRAY_SIZE(veml6075_it_ms)) + return -EINVAL; + + int_time = veml6075_it_ms[int_index]; + msleep(int_time + (int_time / 2)); + + /* shutdown again, data registers are still accessible */ + return regmap_update_bits(data->regmap, VEML6075_CMD_CONF, + VEML6075_CONF_SD, VEML6075_CONF_SD); +} + +static int veml6075_uva_comp(int raw_uva, int comp1, int comp2) +{ + int comp1a_c, comp2a_c, uva_comp; + + comp1a_c = (comp1 * VEML6075_A_COEF) / 1000U; + comp2a_c = (comp2 * VEML6075_B_COEF) / 1000U; + uva_comp = raw_uva - comp1a_c - comp2a_c; + + return clamp_val(uva_comp, 0, U16_MAX); +} + +static int veml6075_uvb_comp(int raw_uvb, int comp1, int comp2) +{ + int comp1b_c, comp2b_c, uvb_comp; + + comp1b_c = (comp1 * VEML6075_C_COEF) / 1000U; + comp2b_c = (comp2 * VEML6075_D_COEF) / 1000U; + uvb_comp = raw_uvb - comp1b_c - comp2b_c; + + return clamp_val(uvb_comp, 0, U16_MAX); +} + +static int veml6075_read_comp(struct veml6075_data *data, int *c1, int *c2) +{ + int ret; + + ret = regmap_read(data->regmap, VEML6075_CMD_COMP1, c1); + if (ret < 0) + return ret; + + return regmap_read(data->regmap, VEML6075_CMD_COMP2, c2); +} + +static int veml6075_read_uv_direct(struct veml6075_data *data, int chan, + int *val) +{ + int c1, c2, ret; + + guard(mutex)(&data->lock); + + ret = veml6075_request_measurement(data); + if (ret < 0) + return ret; + + ret = veml6075_read_comp(data, &c1, &c2); + if (ret < 0) + return ret; + + switch (chan) { + case CH_UVA: + ret = regmap_read(data->regmap, VEML6075_CMD_UVA, val); + if (ret < 0) + return ret; + + *val = veml6075_uva_comp(*val, c1, c2); + return IIO_VAL_INT; + case CH_UVB: + ret = regmap_read(data->regmap, VEML6075_CMD_UVB, val); + if (ret < 0) + return ret; + + *val = veml6075_uvb_comp(*val, c1, c2); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int veml6075_read_int_time_index(struct veml6075_data *data) +{ + int ret, conf, int_index; + + ret = regmap_read(data->regmap, VEML6075_CMD_CONF, &conf); + if (ret < 0) + return ret; + + int_index = FIELD_GET(VEML6075_CONF_IT, conf); + if (int_index >= ARRAY_SIZE(veml6075_it_ms)) + return -EINVAL; + + return int_index; +} + +static int veml6075_read_int_time_ms(struct veml6075_data *data, int *val) +{ + int int_index; + + guard(mutex)(&data->lock); + int_index = veml6075_read_int_time_index(data); + if (int_index < 0) + return int_index; + + *val = veml6075_it_ms[int_index]; + + return IIO_VAL_INT; +} + +static int veml6075_get_uvi_micro(struct veml6075_data *data, int uva_comp, + int uvb_comp) +{ + int uvia_micro = uva_comp * VEML6075_UVA_RESP; + int uvib_micro = uvb_comp * VEML6075_UVB_RESP; + int int_index; + + int_index = veml6075_read_int_time_index(data); + if (int_index < 0) + return int_index; + + switch (int_index) { + case VEML6075_IT_50_MS: + return uvia_micro + uvib_micro; + case VEML6075_IT_100_MS: + case VEML6075_IT_200_MS: + case VEML6075_IT_400_MS: + case VEML6075_IT_800_MS: + return (uvia_micro + uvib_micro) / (2 << int_index); + default: + return -EINVAL; + } +} + +static int veml6075_read_uvi(struct veml6075_data *data, int *val, int *val2) +{ + int ret, c1, c2, uva, uvb, uvi_micro; + + guard(mutex)(&data->lock); + + ret = veml6075_request_measurement(data); + if (ret < 0) + return ret; + + ret = veml6075_read_comp(data, &c1, &c2); + if (ret < 0) + return ret; + + ret = regmap_read(data->regmap, VEML6075_CMD_UVA, &uva); + if (ret < 0) + return ret; + + ret = regmap_read(data->regmap, VEML6075_CMD_UVB, &uvb); + if (ret < 0) + return ret; + + uvi_micro = veml6075_get_uvi_micro(data, veml6075_uva_comp(uva, c1, c2), + veml6075_uvb_comp(uvb, c1, c2)); + if (uvi_micro < 0) + return uvi_micro; + + *val = uvi_micro / MICRO; + *val2 = uvi_micro % MICRO; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int veml6075_read_responsivity(int chan, int *val, int *val2) +{ + /* scale = 1 / resp */ + switch (chan) { + case CH_UVA: + /* resp = 0.93 c/uW/cm2: scale = 1.75268817 */ + *val = 1; + *val2 = 75268817; + return IIO_VAL_INT_PLUS_NANO; + case CH_UVB: + /* resp = 2.1 c/uW/cm2: scale = 0.476190476 */ + *val = 0; + *val2 = 476190476; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static int veml6075_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(veml6075_it_ms); + *vals = veml6075_it_ms; + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + + default: + return -EINVAL; + } +} + +static int veml6075_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct veml6075_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return veml6075_read_uv_direct(data, chan->channel, val); + case IIO_CHAN_INFO_PROCESSED: + return veml6075_read_uvi(data, val, val2); + case IIO_CHAN_INFO_INT_TIME: + return veml6075_read_int_time_ms(data, val); + case IIO_CHAN_INFO_SCALE: + return veml6075_read_responsivity(chan->channel, val, val2); + default: + return -EINVAL; + } +} + +static int veml6075_write_int_time_ms(struct veml6075_data *data, int val) +{ + int i = ARRAY_SIZE(veml6075_it_ms); + + guard(mutex)(&data->lock); + + while (i-- > 0) { + if (val == veml6075_it_ms[i]) + break; + } + if (i < 0) + return -EINVAL; + + return regmap_update_bits(data->regmap, VEML6075_CMD_CONF, + VEML6075_CONF_IT, + FIELD_PREP(VEML6075_CONF_IT, i)); +} + +static int veml6075_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct veml6075_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return veml6075_write_int_time_ms(data, val); + default: + return -EINVAL; + } +} + +static const struct iio_info veml6075_info = { + .read_avail = veml6075_read_avail, + .read_raw = veml6075_read_raw, + .write_raw = veml6075_write_raw, +}; + +static bool veml6075_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case VEML6075_CMD_CONF: + case VEML6075_CMD_UVA: + case VEML6075_CMD_UVB: + case VEML6075_CMD_COMP1: + case VEML6075_CMD_COMP2: + case VEML6075_CMD_ID: + return true; + default: + return false; + } +} + +static bool veml6075_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case VEML6075_CMD_CONF: + return true; + default: + return false; + } +} + +static const struct regmap_config veml6075_regmap_config = { + .name = "veml6075", + .reg_bits = 8, + .val_bits = 16, + .max_register = VEML6075_CMD_ID, + .readable_reg = veml6075_readable_reg, + .writeable_reg = veml6075_writable_reg, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static int veml6075_probe(struct i2c_client *client) +{ + struct veml6075_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int config, ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &veml6075_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + data = iio_priv(indio_dev); + data->client = client; + data->regmap = regmap; + + mutex_init(&data->lock); + + indio_dev->name = "veml6075"; + indio_dev->info = &veml6075_info; + indio_dev->channels = veml6075_channels; + indio_dev->num_channels = ARRAY_SIZE(veml6075_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_regulator_get_enable(&client->dev, "vdd"); + if (ret < 0) + return ret; + + /* default: 100ms integration time, active force enable, shutdown */ + config = FIELD_PREP(VEML6075_CONF_IT, VEML6075_IT_100_MS) | + FIELD_PREP(VEML6075_CONF_AF, VEML6075_AF_ENABLE) | + FIELD_PREP(VEML6075_CONF_SD, VEML6075_SD_ENABLE); + ret = regmap_write(data->regmap, VEML6075_CMD_CONF, config); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id veml6075_id[] = { + { .name = "veml6075" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, veml6075_id); + +static const struct of_device_id veml6075_of_match[] = { + { .compatible = "vishay,veml6075" }, + { } +}; +MODULE_DEVICE_TABLE(of, veml6075_of_match); + +static struct i2c_driver veml6075_driver = { + .driver = { + .name = "veml6075", + .of_match_table = veml6075_of_match, + }, + .probe = veml6075_probe, + .id_table = veml6075_id, +}; + +module_i2c_driver(veml6075_driver); + +MODULE_AUTHOR("Javier Carrasco <javier.carrasco.cruz@gmail.com>"); +MODULE_DESCRIPTION("Vishay VEML6075 UVA and UVB light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/vl6180.c b/drivers/iio/light/vl6180.c new file mode 100644 index 000000000..aef8a5333 --- /dev/null +++ b/drivers/iio/light/vl6180.c @@ -0,0 +1,771 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * vl6180.c - Support for STMicroelectronics VL6180 ALS, range and proximity + * sensor + * + * Copyright 2017 Peter Meerwald-Stadler <pmeerw@pmeerw.net> + * Copyright 2017 Manivannan Sadhasivam <manivannanece23@gmail.com> + * + * IIO driver for VL6180 (7-bit I2C slave address 0x29) + * + * Range: 0 to 100mm + * ALS: < 1 Lux up to 100 kLux + * IR: 850nm + * + * TODO: irq, threshold events, continuous mode, hardware buffer + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/util_macros.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define VL6180_DRV_NAME "vl6180" + +/* Device identification register and value */ +#define VL6180_MODEL_ID 0x000 +#define VL6180_MODEL_ID_VAL 0xb4 + +/* Configuration registers */ +#define VL6180_INTR_CONFIG 0x014 +#define VL6180_INTR_CLEAR 0x015 +#define VL6180_OUT_OF_RESET 0x016 +#define VL6180_HOLD 0x017 +#define VL6180_RANGE_START 0x018 +#define VL6180_RANGE_INTER_MEAS_TIME 0x01b +#define VL6180_ALS_START 0x038 +#define VL6180_ALS_INTER_MEAS_TIME 0x03e +#define VL6180_ALS_GAIN 0x03f +#define VL6180_ALS_IT 0x040 + +/* Status registers */ +#define VL6180_RANGE_STATUS 0x04d +#define VL6180_ALS_STATUS 0x04e +#define VL6180_INTR_STATUS 0x04f + +/* Result value registers */ +#define VL6180_ALS_VALUE 0x050 +#define VL6180_RANGE_VALUE 0x062 +#define VL6180_RANGE_RATE 0x066 + +/* bits of the RANGE_START and ALS_START register */ +#define VL6180_MODE_CONT BIT(1) /* continuous mode */ +#define VL6180_STARTSTOP BIT(0) /* start measurement, auto-reset */ + +/* bits of the INTR_STATUS and INTR_CONFIG register */ +#define VL6180_ALS_READY BIT(5) +#define VL6180_RANGE_READY BIT(2) + +/* bits of the INTR_CLEAR register */ +#define VL6180_CLEAR_ERROR BIT(2) +#define VL6180_CLEAR_ALS BIT(1) +#define VL6180_CLEAR_RANGE BIT(0) + +/* bits of the HOLD register */ +#define VL6180_HOLD_ON BIT(0) + +/* default value for the ALS_IT register */ +#define VL6180_ALS_IT_100 0x63 /* 100 ms */ + +/* values for the ALS_GAIN register */ +#define VL6180_ALS_GAIN_1 0x46 +#define VL6180_ALS_GAIN_1_25 0x45 +#define VL6180_ALS_GAIN_1_67 0x44 +#define VL6180_ALS_GAIN_2_5 0x43 +#define VL6180_ALS_GAIN_5 0x42 +#define VL6180_ALS_GAIN_10 0x41 +#define VL6180_ALS_GAIN_20 0x40 +#define VL6180_ALS_GAIN_40 0x47 + +struct vl6180_data { + struct i2c_client *client; + struct mutex lock; + struct completion completion; + struct iio_trigger *trig; + unsigned int als_gain_milli; + unsigned int als_it_ms; + unsigned int als_meas_rate; + unsigned int range_meas_rate; +}; + +enum { VL6180_ALS, VL6180_RANGE, VL6180_PROX }; + +/** + * struct vl6180_chan_regs - Registers for accessing channels + * @drdy_mask: Data ready bit in status register + * @start_reg: Conversion start register + * @value_reg: Result value register + * @word: Register word length + */ +struct vl6180_chan_regs { + u8 drdy_mask; + u16 start_reg, value_reg; + bool word; +}; + +static const struct vl6180_chan_regs vl6180_chan_regs_table[] = { + [VL6180_ALS] = { + .drdy_mask = VL6180_ALS_READY, + .start_reg = VL6180_ALS_START, + .value_reg = VL6180_ALS_VALUE, + .word = true, + }, + [VL6180_RANGE] = { + .drdy_mask = VL6180_RANGE_READY, + .start_reg = VL6180_RANGE_START, + .value_reg = VL6180_RANGE_VALUE, + .word = false, + }, + [VL6180_PROX] = { + .drdy_mask = VL6180_RANGE_READY, + .start_reg = VL6180_RANGE_START, + .value_reg = VL6180_RANGE_RATE, + .word = true, + }, +}; + +static int vl6180_read(struct i2c_client *client, u16 cmd, void *databuf, + u8 len) +{ + __be16 cmdbuf = cpu_to_be16(cmd); + struct i2c_msg msgs[2] = { + { .addr = client->addr, .len = sizeof(cmdbuf), .buf = (u8 *) &cmdbuf }, + { .addr = client->addr, .len = len, .buf = databuf, + .flags = I2C_M_RD } }; + int ret; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + dev_err(&client->dev, "failed reading register 0x%04x\n", cmd); + + return ret; +} + +static int vl6180_read_byte(struct i2c_client *client, u16 cmd) +{ + u8 data; + int ret; + + ret = vl6180_read(client, cmd, &data, sizeof(data)); + if (ret < 0) + return ret; + + return data; +} + +static int vl6180_read_word(struct i2c_client *client, u16 cmd) +{ + __be16 data; + int ret; + + ret = vl6180_read(client, cmd, &data, sizeof(data)); + if (ret < 0) + return ret; + + return be16_to_cpu(data); +} + +static int vl6180_write_byte(struct i2c_client *client, u16 cmd, u8 val) +{ + u8 buf[3]; + struct i2c_msg msgs[1] = { + { .addr = client->addr, .len = sizeof(buf), .buf = (u8 *) &buf } }; + int ret; + + buf[0] = cmd >> 8; + buf[1] = cmd & 0xff; + buf[2] = val; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) { + dev_err(&client->dev, "failed writing register 0x%04x\n", cmd); + return ret; + } + + return 0; +} + +static int vl6180_write_word(struct i2c_client *client, u16 cmd, u16 val) +{ + __be16 buf[2]; + struct i2c_msg msgs[1] = { + { .addr = client->addr, .len = sizeof(buf), .buf = (u8 *) &buf } }; + int ret; + + buf[0] = cpu_to_be16(cmd); + buf[1] = cpu_to_be16(val); + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) { + dev_err(&client->dev, "failed writing register 0x%04x\n", cmd); + return ret; + } + + return 0; +} + +static int vl6180_measure(struct vl6180_data *data, int addr) +{ + struct i2c_client *client = data->client; + unsigned long time_left; + int tries = 20, ret; + u16 value; + + mutex_lock(&data->lock); + reinit_completion(&data->completion); + + /* Start single shot measurement */ + ret = vl6180_write_byte(client, + vl6180_chan_regs_table[addr].start_reg, VL6180_STARTSTOP); + if (ret < 0) + goto fail; + + if (client->irq) { + time_left = wait_for_completion_timeout(&data->completion, HZ / 10); + if (time_left == 0) { + ret = -ETIMEDOUT; + goto fail; + } + } else { + while (tries--) { + ret = vl6180_read_byte(client, VL6180_INTR_STATUS); + if (ret < 0) + goto fail; + + if (ret & vl6180_chan_regs_table[addr].drdy_mask) + break; + msleep(20); + } + + if (tries < 0) { + ret = -EIO; + goto fail; + } + } + + /* Read result value from appropriate registers */ + ret = vl6180_chan_regs_table[addr].word ? + vl6180_read_word(client, vl6180_chan_regs_table[addr].value_reg) : + vl6180_read_byte(client, vl6180_chan_regs_table[addr].value_reg); + if (ret < 0) + goto fail; + value = ret; + + /* Clear the interrupt flag after data read */ + ret = vl6180_write_byte(client, VL6180_INTR_CLEAR, + VL6180_CLEAR_ERROR | VL6180_CLEAR_ALS | VL6180_CLEAR_RANGE); + if (ret < 0) + goto fail; + + ret = value; + +fail: + mutex_unlock(&data->lock); + + return ret; +} + +static const struct iio_chan_spec vl6180_channels[] = { + { + .type = IIO_LIGHT, + .address = VL6180_ALS, + .scan_index = VL6180_ALS, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + }, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, { + .type = IIO_DISTANCE, + .address = VL6180_RANGE, + .scan_index = VL6180_RANGE, + .scan_type = { + .sign = 'u', + .realbits = 8, + .storagebits = 8, + }, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, { + .type = IIO_PROXIMITY, + .address = VL6180_PROX, + .scan_index = VL6180_PROX, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + }, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +/* + * Available Ambient Light Sensor gain settings, 1/1000th, and + * corresponding setting for the VL6180_ALS_GAIN register + */ +static const int vl6180_als_gain_tab[8] = { + 1000, 1250, 1670, 2500, 5000, 10000, 20000, 40000 +}; +static const u8 vl6180_als_gain_tab_bits[8] = { + VL6180_ALS_GAIN_1, VL6180_ALS_GAIN_1_25, + VL6180_ALS_GAIN_1_67, VL6180_ALS_GAIN_2_5, + VL6180_ALS_GAIN_5, VL6180_ALS_GAIN_10, + VL6180_ALS_GAIN_20, VL6180_ALS_GAIN_40 +}; + +static int vl6180_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct vl6180_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = vl6180_measure(data, chan->address); + if (ret < 0) + return ret; + *val = ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + *val = data->als_it_ms; + *val2 = 1000; + + return IIO_VAL_FRACTIONAL; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_LIGHT: + /* one ALS count is 0.32 Lux @ gain 1, IT 100 ms */ + *val = 32000; /* 0.32 * 1000 * 100 */ + *val2 = data->als_gain_milli * data->als_it_ms; + + return IIO_VAL_FRACTIONAL; + + case IIO_DISTANCE: + *val = 0; /* sensor reports mm, scale to meter */ + *val2 = 1000; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_HARDWAREGAIN: + *val = data->als_gain_milli; + *val2 = 1000; + + return IIO_VAL_FRACTIONAL; + + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_DISTANCE: + *val = data->range_meas_rate; + return IIO_VAL_INT; + case IIO_LIGHT: + *val = data->als_meas_rate; + return IIO_VAL_INT; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static IIO_CONST_ATTR(als_gain_available, "1 1.25 1.67 2.5 5 10 20 40"); + +static struct attribute *vl6180_attributes[] = { + &iio_const_attr_als_gain_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group vl6180_attribute_group = { + .attrs = vl6180_attributes, +}; + +/* HOLD is needed before updating any config registers */ +static int vl6180_hold(struct vl6180_data *data, bool hold) +{ + return vl6180_write_byte(data->client, VL6180_HOLD, + hold ? VL6180_HOLD_ON : 0); +} + +static int vl6180_set_als_gain(struct vl6180_data *data, int val, int val2) +{ + int i, ret, gain; + + if (val < 1 || val > 40) + return -EINVAL; + + gain = (val * 1000000 + val2) / 1000; + if (gain < 1 || gain > 40000) + return -EINVAL; + + i = find_closest(gain, vl6180_als_gain_tab, + ARRAY_SIZE(vl6180_als_gain_tab)); + + mutex_lock(&data->lock); + ret = vl6180_hold(data, true); + if (ret < 0) + goto fail; + + ret = vl6180_write_byte(data->client, VL6180_ALS_GAIN, + vl6180_als_gain_tab_bits[i]); + + if (ret >= 0) + data->als_gain_milli = vl6180_als_gain_tab[i]; + +fail: + vl6180_hold(data, false); + mutex_unlock(&data->lock); + return ret; +} + +static int vl6180_set_it(struct vl6180_data *data, int val, int val2) +{ + int ret, it_ms; + + it_ms = DIV_ROUND_CLOSEST(val2, 1000); /* round to ms */ + if (val != 0 || it_ms < 1 || it_ms > 512) + return -EINVAL; + + mutex_lock(&data->lock); + ret = vl6180_hold(data, true); + if (ret < 0) + goto fail; + + ret = vl6180_write_word(data->client, VL6180_ALS_IT, it_ms - 1); + + if (ret >= 0) + data->als_it_ms = it_ms; + +fail: + vl6180_hold(data, false); + mutex_unlock(&data->lock); + + return ret; +} + +static int vl6180_meas_reg_val_from_mhz(unsigned int mhz) +{ + unsigned int period = DIV_ROUND_CLOSEST(1000 * 1000, mhz); + unsigned int reg_val = 0; + + if (period > 10) + reg_val = period < 2550 ? (DIV_ROUND_CLOSEST(period, 10) - 1) : 254; + + return reg_val; +} + +static int vl6180_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct vl6180_data *data = iio_priv(indio_dev); + unsigned int reg_val; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return vl6180_set_it(data, val, val2); + + case IIO_CHAN_INFO_HARDWAREGAIN: + if (chan->type != IIO_LIGHT) + return -EINVAL; + + return vl6180_set_als_gain(data, val, val2); + + case IIO_CHAN_INFO_SAMP_FREQ: + { + guard(mutex)(&data->lock); + switch (chan->type) { + case IIO_DISTANCE: + data->range_meas_rate = val; + reg_val = vl6180_meas_reg_val_from_mhz(val); + return vl6180_write_byte(data->client, + VL6180_RANGE_INTER_MEAS_TIME, reg_val); + + case IIO_LIGHT: + data->als_meas_rate = val; + reg_val = vl6180_meas_reg_val_from_mhz(val); + return vl6180_write_byte(data->client, + VL6180_ALS_INTER_MEAS_TIME, reg_val); + + default: + return -EINVAL; + } + } + + default: + return -EINVAL; + } +} + +static irqreturn_t vl6180_threaded_irq(int irq, void *priv) +{ + struct iio_dev *indio_dev = priv; + struct vl6180_data *data = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev)) + iio_trigger_poll_nested(indio_dev->trig); + else + complete(&data->completion); + + return IRQ_HANDLED; +} + +static irqreturn_t vl6180_trigger_handler(int irq, void *priv) +{ + struct iio_poll_func *pf = priv; + struct iio_dev *indio_dev = pf->indio_dev; + struct vl6180_data *data = iio_priv(indio_dev); + s64 time_ns = iio_get_time_ns(indio_dev); + int ret, bit, i = 0; + struct { + u16 chan[2]; + aligned_s64 timestamp; + } scan = { }; + + + iio_for_each_active_channel(indio_dev, bit) { + if (vl6180_chan_regs_table[bit].word) + ret = vl6180_read_word(data->client, + vl6180_chan_regs_table[bit].value_reg); + else + ret = vl6180_read_byte(data->client, + vl6180_chan_regs_table[bit].value_reg); + + if (ret < 0) { + dev_err(&data->client->dev, + "failed to read from value regs: %d\n", ret); + return IRQ_HANDLED; + } + + scan.chan[i++] = ret; + } + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), time_ns); + iio_trigger_notify_done(indio_dev->trig); + + /* Clear the interrupt flag after data read */ + ret = vl6180_write_byte(data->client, VL6180_INTR_CLEAR, + VL6180_CLEAR_ERROR | VL6180_CLEAR_ALS | VL6180_CLEAR_RANGE); + if (ret < 0) + dev_err(&data->client->dev, "failed to clear irq: %d\n", ret); + + return IRQ_HANDLED; +} + +static const struct iio_info vl6180_info = { + .read_raw = vl6180_read_raw, + .write_raw = vl6180_write_raw, + .attrs = &vl6180_attribute_group, + .validate_trigger = iio_validate_own_trigger, +}; + +static int vl6180_buffer_postenable(struct iio_dev *indio_dev) +{ + struct vl6180_data *data = iio_priv(indio_dev); + int bit; + + iio_for_each_active_channel(indio_dev, bit) + return vl6180_write_byte(data->client, + vl6180_chan_regs_table[bit].start_reg, + VL6180_MODE_CONT | VL6180_STARTSTOP); + + return -EINVAL; +} + +static int vl6180_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct vl6180_data *data = iio_priv(indio_dev); + int bit; + + iio_for_each_active_channel(indio_dev, bit) + return vl6180_write_byte(data->client, + vl6180_chan_regs_table[bit].start_reg, + VL6180_STARTSTOP); + + return -EINVAL; +} + +static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = { + .postenable = &vl6180_buffer_postenable, + .postdisable = &vl6180_buffer_postdisable, +}; + +static const struct iio_trigger_ops vl6180_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + +static int vl6180_init(struct vl6180_data *data, struct iio_dev *indio_dev) +{ + struct i2c_client *client = data->client; + int ret; + + ret = vl6180_read_byte(client, VL6180_MODEL_ID); + if (ret < 0) + return ret; + + if (ret != VL6180_MODEL_ID_VAL) { + dev_err(&client->dev, "invalid model ID %02x\n", ret); + return -ENODEV; + } + + ret = vl6180_hold(data, true); + if (ret < 0) + return ret; + + ret = vl6180_read_byte(client, VL6180_OUT_OF_RESET); + if (ret < 0) + return ret; + + /* + * Detect false reset condition here. This bit is always set when the + * system comes out of reset. + */ + if (ret != 0x01) + dev_info(&client->dev, "device is not fresh out of reset\n"); + + /* Enable ALS and Range ready interrupts */ + ret = vl6180_write_byte(client, VL6180_INTR_CONFIG, + VL6180_ALS_READY | VL6180_RANGE_READY); + if (ret < 0) + return ret; + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL, + &vl6180_trigger_handler, + &iio_triggered_buffer_setup_ops); + if (ret) + return ret; + + /* Default Range inter-measurement time: 50ms or 20000 mHz */ + ret = vl6180_write_byte(client, VL6180_RANGE_INTER_MEAS_TIME, + vl6180_meas_reg_val_from_mhz(20000)); + if (ret < 0) + return ret; + data->range_meas_rate = 20000; + + /* Default ALS inter-measurement time: 10ms or 100000 mHz */ + ret = vl6180_write_byte(client, VL6180_ALS_INTER_MEAS_TIME, + vl6180_meas_reg_val_from_mhz(100000)); + if (ret < 0) + return ret; + data->als_meas_rate = 100000; + + /* ALS integration time: 100ms */ + data->als_it_ms = 100; + ret = vl6180_write_word(client, VL6180_ALS_IT, VL6180_ALS_IT_100); + if (ret < 0) + return ret; + + /* ALS gain: 1 */ + data->als_gain_milli = 1000; + ret = vl6180_write_byte(client, VL6180_ALS_GAIN, VL6180_ALS_GAIN_1); + if (ret < 0) + return ret; + + ret = vl6180_write_byte(client, VL6180_OUT_OF_RESET, 0x00); + if (ret < 0) + return ret; + + return vl6180_hold(data, false); +} + +static int vl6180_probe(struct i2c_client *client) +{ + struct vl6180_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + + indio_dev->info = &vl6180_info; + indio_dev->channels = vl6180_channels; + indio_dev->num_channels = ARRAY_SIZE(vl6180_channels); + indio_dev->name = VL6180_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = vl6180_init(data, indio_dev); + if (ret < 0) + return ret; + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, vl6180_threaded_irq, + IRQF_ONESHOT, + indio_dev->name, indio_dev); + if (ret) + return ret; + + init_completion(&data->completion); + + data->trig = devm_iio_trigger_alloc(&client->dev, "%s-dev%d", + indio_dev->name, iio_device_id(indio_dev)); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = &vl6180_trigger_ops; + iio_trigger_set_drvdata(data->trig, indio_dev); + ret = devm_iio_trigger_register(&client->dev, data->trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(data->trig); + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct of_device_id vl6180_of_match[] = { + { .compatible = "st,vl6180", }, + { } +}; +MODULE_DEVICE_TABLE(of, vl6180_of_match); + +static const struct i2c_device_id vl6180_id[] = { + { .name = "vl6180" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, vl6180_id); + +static struct i2c_driver vl6180_driver = { + .driver = { + .name = VL6180_DRV_NAME, + .of_match_table = vl6180_of_match, + }, + .probe = vl6180_probe, + .id_table = vl6180_id, +}; + +module_i2c_driver(vl6180_driver); + +MODULE_AUTHOR("Peter Meerwald-Stadler <pmeerw@pmeerw.net>"); +MODULE_AUTHOR("Manivannan Sadhasivam <manivannanece23@gmail.com>"); +MODULE_DESCRIPTION("STMicro VL6180 ALS, range and proximity sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/zopt2201.c b/drivers/iio/light/zopt2201.c new file mode 100644 index 000000000..b53be5f73 --- /dev/null +++ b/drivers/iio/light/zopt2201.c @@ -0,0 +1,536 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * zopt2201.c - Support for IDT ZOPT2201 ambient light and UV B sensor + * + * Copyright 2017 Peter Meerwald-Stadler <pmeerw@pmeerw.net> + * + * Datasheet: https://www.idt.com/document/dst/zopt2201-datasheet + * 7-bit I2C slave addresses 0x53 (default) or 0x52 (programmed) + * + * TODO: interrupt support, ALS/UVB raw mode + */ + +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/unaligned.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define ZOPT2201_DRV_NAME "zopt2201" + +/* Registers */ +#define ZOPT2201_MAIN_CTRL 0x00 +#define ZOPT2201_LS_MEAS_RATE 0x04 +#define ZOPT2201_LS_GAIN 0x05 +#define ZOPT2201_PART_ID 0x06 +#define ZOPT2201_MAIN_STATUS 0x07 +#define ZOPT2201_ALS_DATA 0x0d /* LSB first, 13 to 20 bits */ +#define ZOPT2201_UVB_DATA 0x10 /* LSB first, 13 to 20 bits */ +#define ZOPT2201_UV_COMP_DATA 0x13 /* LSB first, 13 to 20 bits */ +#define ZOPT2201_COMP_DATA 0x16 /* LSB first, 13 to 20 bits */ +#define ZOPT2201_INT_CFG 0x19 +#define ZOPT2201_INT_PST 0x1a + +#define ZOPT2201_MAIN_CTRL_LS_MODE BIT(3) /* 0 .. ALS, 1 .. UV B */ +#define ZOPT2201_MAIN_CTRL_LS_EN BIT(1) + +/* Values for ZOPT2201_LS_MEAS_RATE resolution / bit width */ +#define ZOPT2201_MEAS_RES_20BIT 0 /* takes 400 ms */ +#define ZOPT2201_MEAS_RES_19BIT 1 /* takes 200 ms */ +#define ZOPT2201_MEAS_RES_18BIT 2 /* takes 100 ms, default */ +#define ZOPT2201_MEAS_RES_17BIT 3 /* takes 50 ms */ +#define ZOPT2201_MEAS_RES_16BIT 4 /* takes 25 ms */ +#define ZOPT2201_MEAS_RES_13BIT 5 /* takes 3.125 ms */ +#define ZOPT2201_MEAS_RES_SHIFT 4 + +/* Values for ZOPT2201_LS_MEAS_RATE measurement rate */ +#define ZOPT2201_MEAS_FREQ_25MS 0 +#define ZOPT2201_MEAS_FREQ_50MS 1 +#define ZOPT2201_MEAS_FREQ_100MS 2 /* default */ +#define ZOPT2201_MEAS_FREQ_200MS 3 +#define ZOPT2201_MEAS_FREQ_500MS 4 +#define ZOPT2201_MEAS_FREQ_1000MS 5 +#define ZOPT2201_MEAS_FREQ_2000MS 6 + +/* Values for ZOPT2201_LS_GAIN */ +#define ZOPT2201_LS_GAIN_1 0 +#define ZOPT2201_LS_GAIN_3 1 +#define ZOPT2201_LS_GAIN_6 2 +#define ZOPT2201_LS_GAIN_9 3 +#define ZOPT2201_LS_GAIN_18 4 + +/* Values for ZOPT2201_MAIN_STATUS */ +#define ZOPT2201_MAIN_STATUS_POWERON BIT(5) +#define ZOPT2201_MAIN_STATUS_INT BIT(4) +#define ZOPT2201_MAIN_STATUS_DRDY BIT(3) + +#define ZOPT2201_PART_NUMBER 0xb2 + +struct zopt2201_data { + struct i2c_client *client; + struct mutex lock; + u8 gain; + u8 res; + u8 rate; +}; + +static const struct { + unsigned int gain; /* gain factor */ + unsigned int scale; /* micro lux per count */ +} zopt2201_gain_als[] = { + { 1, 19200000 }, + { 3, 6400000 }, + { 6, 3200000 }, + { 9, 2133333 }, + { 18, 1066666 }, +}; + +static const struct { + unsigned int gain; /* gain factor */ + unsigned int scale; /* micro W/m2 per count */ +} zopt2201_gain_uvb[] = { + { 1, 460800 }, + { 3, 153600 }, + { 6, 76800 }, + { 9, 51200 }, + { 18, 25600 }, +}; + +static const struct { + unsigned int bits; /* sensor resolution in bits */ + unsigned long us; /* measurement time in micro seconds */ +} zopt2201_resolution[] = { + { 20, 400000 }, + { 19, 200000 }, + { 18, 100000 }, + { 17, 50000 }, + { 16, 25000 }, + { 13, 3125 }, +}; + +struct zopt2201_scale { + unsigned int scale, uscale; /* scale factor as integer + micro */ + u8 gain; /* gain register value */ + u8 res; /* resolution register value */ +}; + +static const struct zopt2201_scale zopt2201_scale_als[] = { + { 19, 200000, 0, 5 }, + { 6, 400000, 1, 5 }, + { 3, 200000, 2, 5 }, + { 2, 400000, 0, 4 }, + { 2, 133333, 3, 5 }, + { 1, 200000, 0, 3 }, + { 1, 66666, 4, 5 }, + { 0, 800000, 1, 4 }, + { 0, 600000, 0, 2 }, + { 0, 400000, 2, 4 }, + { 0, 300000, 0, 1 }, + { 0, 266666, 3, 4 }, + { 0, 200000, 2, 3 }, + { 0, 150000, 0, 0 }, + { 0, 133333, 4, 4 }, + { 0, 100000, 2, 2 }, + { 0, 66666, 4, 3 }, + { 0, 50000, 2, 1 }, + { 0, 33333, 4, 2 }, + { 0, 25000, 2, 0 }, + { 0, 16666, 4, 1 }, + { 0, 8333, 4, 0 }, +}; + +static const struct zopt2201_scale zopt2201_scale_uvb[] = { + { 0, 460800, 0, 5 }, + { 0, 153600, 1, 5 }, + { 0, 76800, 2, 5 }, + { 0, 57600, 0, 4 }, + { 0, 51200, 3, 5 }, + { 0, 28800, 0, 3 }, + { 0, 25600, 4, 5 }, + { 0, 19200, 1, 4 }, + { 0, 14400, 0, 2 }, + { 0, 9600, 2, 4 }, + { 0, 7200, 0, 1 }, + { 0, 6400, 3, 4 }, + { 0, 4800, 2, 3 }, + { 0, 3600, 0, 0 }, + { 0, 3200, 4, 4 }, + { 0, 2400, 2, 2 }, + { 0, 1600, 4, 3 }, + { 0, 1200, 2, 1 }, + { 0, 800, 4, 2 }, + { 0, 600, 2, 0 }, + { 0, 400, 4, 1 }, + { 0, 200, 4, 0 }, +}; + +static int zopt2201_enable_mode(struct zopt2201_data *data, bool uvb_mode) +{ + u8 out = ZOPT2201_MAIN_CTRL_LS_EN; + + if (uvb_mode) + out |= ZOPT2201_MAIN_CTRL_LS_MODE; + + return i2c_smbus_write_byte_data(data->client, ZOPT2201_MAIN_CTRL, out); +} + +static int zopt2201_read(struct zopt2201_data *data, u8 reg) +{ + struct i2c_client *client = data->client; + int tries = 10; + u8 buf[3]; + int ret; + + guard(mutex)(&data->lock); + ret = zopt2201_enable_mode(data, reg == ZOPT2201_UVB_DATA); + if (ret < 0) + return ret; + + while (tries--) { + unsigned long t = zopt2201_resolution[data->res].us; + + if (t <= 20000) + usleep_range(t, t + 1000); + else + msleep(t / 1000); + ret = i2c_smbus_read_byte_data(client, ZOPT2201_MAIN_STATUS); + if (ret < 0) + return ret; + if (ret & ZOPT2201_MAIN_STATUS_DRDY) + break; + } + + if (tries < 0) { + ret = -ETIMEDOUT; + return ret; + } + + ret = i2c_smbus_read_i2c_block_data(client, reg, sizeof(buf), buf); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, ZOPT2201_MAIN_CTRL, 0x00); + if (ret < 0) + return ret; + + return get_unaligned_le24(&buf[0]); +} + +static const struct iio_chan_spec zopt2201_channels[] = { + { + .type = IIO_LIGHT, + .address = ZOPT2201_ALS_DATA, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_UV, + .address = ZOPT2201_UVB_DATA, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_UVINDEX, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, +}; + +static int zopt2201_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct zopt2201_data *data = iio_priv(indio_dev); + u64 tmp; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = zopt2201_read(data, chan->address); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_PROCESSED: + ret = zopt2201_read(data, ZOPT2201_UVB_DATA); + if (ret < 0) + return ret; + *val = ret * 18 * + (1 << (20 - zopt2201_resolution[data->res].bits)) / + zopt2201_gain_uvb[data->gain].gain; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->address) { + case ZOPT2201_ALS_DATA: + *val = zopt2201_gain_als[data->gain].scale; + break; + case ZOPT2201_UVB_DATA: + *val = zopt2201_gain_uvb[data->gain].scale; + break; + default: + return -EINVAL; + } + + *val2 = 1000000; + *val2 *= (1 << (zopt2201_resolution[data->res].bits - 13)); + tmp = div_s64(*val * 1000000ULL, *val2); + *val = div_s64_rem(tmp, 1000000, val2); + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = zopt2201_resolution[data->res].us; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int zopt2201_set_resolution(struct zopt2201_data *data, u8 res) +{ + int ret; + + ret = i2c_smbus_write_byte_data(data->client, ZOPT2201_LS_MEAS_RATE, + (res << ZOPT2201_MEAS_RES_SHIFT) | + data->rate); + if (ret < 0) + return ret; + + data->res = res; + + return 0; +} + +static int zopt2201_write_resolution(struct zopt2201_data *data, + int val, int val2) +{ + int i; + + if (val != 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(zopt2201_resolution); i++) + if (val2 == zopt2201_resolution[i].us) { + guard(mutex)(&data->lock); + return zopt2201_set_resolution(data, i); + } + + return -EINVAL; +} + +static int zopt2201_set_gain(struct zopt2201_data *data, u8 gain) +{ + int ret; + + ret = i2c_smbus_write_byte_data(data->client, ZOPT2201_LS_GAIN, gain); + if (ret < 0) + return ret; + + data->gain = gain; + + return 0; +} + +static int zopt2201_write_scale_by_idx(struct zopt2201_data *data, int idx, + const struct zopt2201_scale *zopt2201_scale_array) +{ + int ret; + + guard(mutex)(&data->lock); + ret = zopt2201_set_resolution(data, zopt2201_scale_array[idx].res); + if (ret < 0) + return ret; + + return zopt2201_set_gain(data, zopt2201_scale_array[idx].gain); +} + +static int zopt2201_write_scale_als(struct zopt2201_data *data, + int val, int val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(zopt2201_scale_als); i++) + if (val == zopt2201_scale_als[i].scale && + val2 == zopt2201_scale_als[i].uscale) + return zopt2201_write_scale_by_idx(data, i, zopt2201_scale_als); + + return -EINVAL; +} + +static int zopt2201_write_scale_uvb(struct zopt2201_data *data, + int val, int val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(zopt2201_scale_uvb); i++) + if (val == zopt2201_scale_uvb[i].scale && + val2 == zopt2201_scale_uvb[i].uscale) + return zopt2201_write_scale_by_idx(data, i, zopt2201_scale_uvb); + + return -EINVAL; +} + +static int zopt2201_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct zopt2201_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return zopt2201_write_resolution(data, val, val2); + case IIO_CHAN_INFO_SCALE: + switch (chan->address) { + case ZOPT2201_ALS_DATA: + return zopt2201_write_scale_als(data, val, val2); + case ZOPT2201_UVB_DATA: + return zopt2201_write_scale_uvb(data, val, val2); + default: + return -EINVAL; + } + } + + return -EINVAL; +} + +static ssize_t zopt2201_show_int_time_available(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + size_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(zopt2201_resolution); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06lu ", + zopt2201_resolution[i].us); + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEV_ATTR_INT_TIME_AVAIL(zopt2201_show_int_time_available); + +static ssize_t zopt2201_show_als_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(zopt2201_scale_als); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06u ", + zopt2201_scale_als[i].scale, + zopt2201_scale_als[i].uscale); + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t zopt2201_show_uvb_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(zopt2201_scale_uvb); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06u ", + zopt2201_scale_uvb[i].scale, + zopt2201_scale_uvb[i].uscale); + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEVICE_ATTR(in_illuminance_scale_available, 0444, + zopt2201_show_als_scale_avail, NULL, 0); +static IIO_DEVICE_ATTR(in_intensity_uv_scale_available, 0444, + zopt2201_show_uvb_scale_avail, NULL, 0); + +static struct attribute *zopt2201_attributes[] = { + &iio_dev_attr_integration_time_available.dev_attr.attr, + &iio_dev_attr_in_illuminance_scale_available.dev_attr.attr, + &iio_dev_attr_in_intensity_uv_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group zopt2201_attribute_group = { + .attrs = zopt2201_attributes, +}; + +static const struct iio_info zopt2201_info = { + .read_raw = zopt2201_read_raw, + .write_raw = zopt2201_write_raw, + .attrs = &zopt2201_attribute_group, +}; + +static int zopt2201_probe(struct i2c_client *client) +{ + struct zopt2201_data *data; + struct iio_dev *indio_dev; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) + return -EOPNOTSUPP; + + ret = i2c_smbus_read_byte_data(client, ZOPT2201_PART_ID); + if (ret < 0) + return ret; + if (ret != ZOPT2201_PART_NUMBER) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + + indio_dev->info = &zopt2201_info; + indio_dev->channels = zopt2201_channels; + indio_dev->num_channels = ARRAY_SIZE(zopt2201_channels); + indio_dev->name = ZOPT2201_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + data->rate = ZOPT2201_MEAS_FREQ_100MS; + ret = zopt2201_set_resolution(data, ZOPT2201_MEAS_RES_18BIT); + if (ret < 0) + return ret; + + ret = zopt2201_set_gain(data, ZOPT2201_LS_GAIN_3); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id zopt2201_id[] = { + { .name = "zopt2201" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, zopt2201_id); + +static struct i2c_driver zopt2201_driver = { + .driver = { + .name = ZOPT2201_DRV_NAME, + }, + .probe = zopt2201_probe, + .id_table = zopt2201_id, +}; + +module_i2c_driver(zopt2201_driver); + +MODULE_AUTHOR("Peter Meerwald-Stadler <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("IDT ZOPT2201 ambient light and UV B sensor driver"); +MODULE_LICENSE("GPL"); |
