summaryrefslogtreecommitdiff
path: root/drivers/iio/light
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/iio/light')
-rw-r--r--drivers/iio/light/Kconfig172
-rw-r--r--drivers/iio/light/Makefile15
-rw-r--r--drivers/iio/light/acpi-als.c23
-rw-r--r--drivers/iio/light/adjd_s311.c19
-rw-r--r--drivers/iio/light/adux1020.c35
-rw-r--r--drivers/iio/light/al3000a.c210
-rw-r--r--drivers/iio/light/al3010.c110
-rw-r--r--drivers/iio/light/al3320a.c127
-rw-r--r--drivers/iio/light/apds9160.c1592
-rw-r--r--drivers/iio/light/apds9300.c27
-rw-r--r--drivers/iio/light/apds9306.c1359
-rw-r--r--drivers/iio/light/apds9960.c77
-rw-r--r--drivers/iio/light/as73211.c214
-rw-r--r--drivers/iio/light/bh1745.c901
-rw-r--r--drivers/iio/light/bh1750.c26
-rw-r--r--drivers/iio/light/bh1780.c9
-rw-r--r--drivers/iio/light/cm32181.c28
-rw-r--r--drivers/iio/light/cm3232.c44
-rw-r--r--drivers/iio/light/cm3323.c8
-rw-r--r--drivers/iio/light/cm3605.c8
-rw-r--r--drivers/iio/light/cm36651.c8
-rw-r--r--drivers/iio/light/cros_ec_light_prox.c2
-rw-r--r--drivers/iio/light/gp2ap002.c14
-rw-r--r--drivers/iio/light/gp2ap020a00f.c25
-rw-r--r--drivers/iio/light/hid-sensor-als.c195
-rw-r--r--drivers/iio/light/hid-sensor-prox.c241
-rw-r--r--drivers/iio/light/iqs621-als.c6
-rw-r--r--drivers/iio/light/isl29018.c48
-rw-r--r--drivers/iio/light/isl29028.c23
-rw-r--r--drivers/iio/light/isl29125.c28
-rw-r--r--drivers/iio/light/isl76682.c345
-rw-r--r--drivers/iio/light/jsa1212.c13
-rw-r--r--drivers/iio/light/lm3533-als.c10
-rw-r--r--drivers/iio/light/ltr390.c916
-rw-r--r--drivers/iio/light/ltr501.c230
-rw-r--r--drivers/iio/light/ltrf216a.c71
-rw-r--r--drivers/iio/light/lv0104cs.c4
-rw-r--r--drivers/iio/light/max44000.c28
-rw-r--r--drivers/iio/light/max44009.c20
-rw-r--r--drivers/iio/light/noa1305.c173
-rw-r--r--drivers/iio/light/opt3001.c202
-rw-r--r--drivers/iio/light/opt4001.c466
-rw-r--r--drivers/iio/light/opt4060.c1341
-rw-r--r--drivers/iio/light/pa12203001.c21
-rw-r--r--drivers/iio/light/rohm-bu27034.c1329
-rw-r--r--drivers/iio/light/rpr0521.c105
-rw-r--r--drivers/iio/light/si1133.c6
-rw-r--r--drivers/iio/light/si1145.c39
-rw-r--r--drivers/iio/light/st_uvis25.h5
-rw-r--r--drivers/iio/light/st_uvis25_core.c32
-rw-r--r--drivers/iio/light/st_uvis25_i2c.c8
-rw-r--r--drivers/iio/light/st_uvis25_spi.c6
-rw-r--r--drivers/iio/light/stk3310.c72
-rw-r--r--drivers/iio/light/tcs3414.c31
-rw-r--r--drivers/iio/light/tcs3472.c32
-rw-r--r--drivers/iio/light/tsl2563.c197
-rw-r--r--drivers/iio/light/tsl2583.c18
-rw-r--r--drivers/iio/light/tsl2591.c10
-rw-r--r--drivers/iio/light/tsl2772.c13
-rw-r--r--drivers/iio/light/tsl4531.c4
-rw-r--r--drivers/iio/light/us5182d.c28
-rw-r--r--drivers/iio/light/vcnl4000.c1194
-rw-r--r--drivers/iio/light/vcnl4035.c70
-rw-r--r--drivers/iio/light/veml3235.c547
-rw-r--r--drivers/iio/light/veml6030.c910
-rw-r--r--drivers/iio/light/veml6040.c280
-rw-r--r--drivers/iio/light/veml6046x00.c1030
-rw-r--r--drivers/iio/light/veml6070.c205
-rw-r--r--drivers/iio/light/veml6075.c478
-rw-r--r--drivers/iio/light/vl6180.c262
-rw-r--r--drivers/iio/light/zopt2201.c48
71 files changed, 14628 insertions, 1765 deletions
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index 0d4447df7200..ac1408d374c9 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -43,6 +43,16 @@ config ADUX1020
To compile this driver as a module, choose M here: the
module will be called adux1020.
+config AL3000A
+ tristate "AL3000a ambient light sensor"
+ 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"
depends on I2C
@@ -63,6 +73,17 @@ config AL3320A
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
@@ -73,6 +94,18 @@ config APDS9300
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
@@ -87,13 +120,14 @@ config APDS9960
module will be called apds9960
config AS73211
- tristate "AMS AS73211 XYZ color sensor"
+ 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 Sensor.
+ 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.
@@ -101,6 +135,19 @@ config AS73211
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
@@ -222,7 +269,6 @@ config SENSORS_ISL29018
tristate "Intersil 29018 light and proximity sensor"
depends on I2C
select REGMAP_I2C
- default n
help
If you say yes here you get support for ambient light sensing and
proximity infrared sensing from Intersil ISL29018.
@@ -252,6 +298,21 @@ config ISL29125
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
@@ -289,6 +350,20 @@ config JSA1212
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 sesnor 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
@@ -319,6 +394,17 @@ config SENSORS_LM3533
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
@@ -394,11 +480,35 @@ config OPT3001
depends on I2C
help
If you say Y or M here, you get support for Texas Instruments
- OPT3001 Ambient Light Sensor.
+ 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
@@ -577,17 +687,56 @@ config VCNL4035
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 VEML6030
- tristate "VEML6030 ambient light sensor"
+ 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
- ambient light sensor (ALS).
+ 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
@@ -598,6 +747,17 @@ config VEML6070
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
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index 6f23817fae6f..c0048e0d5ca8 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -7,11 +7,15 @@
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_AS73211) += as73211.o
+obj-$(CONFIG_BH1745) += bh1745.o
obj-$(CONFIG_BH1750) += bh1750.o
obj-$(CONFIG_BH1780) += bh1780.o
obj-$(CONFIG_CM32181) += cm32181.o
@@ -28,8 +32,10 @@ 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
@@ -37,9 +43,11 @@ 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_SENSORS_TSL2563) += tsl2563.o
obj-$(CONFIG_SI1133) += si1133.o
obj-$(CONFIG_SI1145) += si1145.o
obj-$(CONFIG_STK3310) += stk3310.o
@@ -48,6 +56,7 @@ 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
@@ -55,7 +64,11 @@ 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_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
index e1ff6f524f4b..d5d1a8b9c035 100644
--- a/drivers/iio/light/acpi-als.c
+++ b/drivers/iio/light/acpi-als.c
@@ -49,20 +49,10 @@ static const struct iio_chan_spec acpi_als_channels[] = {
IIO_CHAN_SOFT_TIMESTAMP(1),
};
-/*
- * The event buffer contains timestamp and all the data from
- * the ACPI0008 block. There are multiple, but so far we only
- * support _ALI (illuminance): One channel, padding and timestamp.
- */
-#define ACPI_ALS_EVT_BUFFER_SIZE \
- (sizeof(s32) + sizeof(s32) + sizeof(s64))
-
struct acpi_als {
struct acpi_device *device;
struct mutex lock;
struct iio_trigger *trig;
-
- s32 evt_buffer[ACPI_ALS_EVT_BUFFER_SIZE / sizeof(s32)] __aligned(8);
};
/*
@@ -108,7 +98,7 @@ static void acpi_als_notify(struct acpi_device *device, u32 event)
if (iio_buffer_enabled(indio_dev) && iio_trigger_using_own(indio_dev)) {
switch (event) {
case ACPI_ALS_NOTIFY_ILLUMINANCE:
- iio_trigger_poll_chained(als->trig);
+ iio_trigger_poll_nested(als->trig);
break;
default:
/* Unhandled event */
@@ -152,7 +142,10 @@ 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);
- s32 *buffer = als->evt_buffer;
+ struct {
+ s32 light;
+ aligned_s64 ts;
+ } scan = { };
s32 val;
int ret;
@@ -161,7 +154,7 @@ static irqreturn_t acpi_als_trigger_handler(int irq, void *p)
ret = acpi_als_read_value(als, ACPI_ALS_ILLUMINANCE, &val);
if (ret < 0)
goto out;
- *buffer = val;
+ scan.light = val;
/*
* When coming from own trigger via polls, set polling function
@@ -174,7 +167,7 @@ static irqreturn_t acpi_als_trigger_handler(int irq, void *p)
if (!pf->timestamp)
pf->timestamp = iio_get_time_ns(indio_dev);
- iio_push_to_buffers_with_timestamp(indio_dev, buffer, pf->timestamp);
+ 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);
@@ -230,7 +223,7 @@ static int acpi_als_add(struct acpi_device *device)
static const struct acpi_device_id acpi_als_device_ids[] = {
{"ACPI0008", 0},
- {},
+ { }
};
MODULE_DEVICE_TABLE(acpi, acpi_als_device_ids);
diff --git a/drivers/iio/light/adjd_s311.c b/drivers/iio/light/adjd_s311.c
index 210a90f44c53..edb3d9dc8bed 100644
--- a/drivers/iio/light/adjd_s311.c
+++ b/drivers/iio/light/adjd_s311.c
@@ -54,10 +54,6 @@
struct adjd_s311_data {
struct i2c_client *client;
- struct {
- s16 chans[4];
- s64 ts __aligned(8);
- } scan;
};
enum adjd_s311_channel_idx {
@@ -120,22 +116,25 @@ static irqreturn_t adjd_s311_trigger_handler(int irq, void *p)
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;
- for_each_set_bit(i, indio_dev->active_scan_mask,
- indio_dev->masklength) {
+ 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;
- data->scan.chans[j++] = ret & ADJD_S311_DATA_MASK;
+ scan.chans[j++] = ret & ADJD_S311_DATA_MASK;
}
- iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, time_ns);
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), time_ns);
done:
iio_trigger_notify_done(indio_dev->trig);
@@ -261,7 +260,7 @@ static int adjd_s311_probe(struct i2c_client *client)
}
static const struct i2c_device_id adjd_s311_id[] = {
- { "adjd_s311", 0 },
+ { "adjd_s311" },
{ }
};
MODULE_DEVICE_TABLE(i2c, adjd_s311_id);
@@ -270,7 +269,7 @@ static struct i2c_driver adjd_s311_driver = {
.driver = {
.name = ADJD_S311_DRV_NAME,
},
- .probe_new = adjd_s311_probe,
+ .probe = adjd_s311_probe,
.id_table = adjd_s311_id,
};
module_i2c_driver(adjd_s311_driver);
diff --git a/drivers/iio/light/adux1020.c b/drivers/iio/light/adux1020.c
index 606075350d01..66ff9c5fb66a 100644
--- a/drivers/iio/light/adux1020.c
+++ b/drivers/iio/light/adux1020.c
@@ -23,7 +23,6 @@
#include <linux/iio/sysfs.h>
#include <linux/iio/events.h>
-#define ADUX1020_REGMAP_NAME "adux1020_regmap"
#define ADUX1020_DRV_NAME "adux1020"
/* System registers */
@@ -114,11 +113,10 @@ static const struct adux1020_mode_data adux1020_modes[] = {
};
static const struct regmap_config adux1020_regmap_config = {
- .name = ADUX1020_REGMAP_NAME,
+ .name = "adux1020_regmap",
.reg_bits = 8,
.val_bits = 16,
.max_register = 0x6F,
- .cache_type = REGCACHE_NONE,
};
static const struct reg_sequence adux1020_def_conf[] = {
@@ -502,7 +500,8 @@ fail:
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, int state)
+ enum iio_event_direction dir,
+ bool state)
{
struct adux1020_data *data = iio_priv(indio_dev);
int ret, mask;
@@ -526,12 +525,11 @@ static int adux1020_write_event_config(struct iio_dev *indio_dev,
mask = ADUX1020_PROX_OFF1_INT;
if (state)
- state = 0;
+ ret = regmap_clear_bits(data->regmap,
+ ADUX1020_REG_INT_MASK, mask);
else
- state = mask;
-
- ret = regmap_update_bits(data->regmap, ADUX1020_REG_INT_MASK,
- mask, state);
+ ret = regmap_set_bits(data->regmap,
+ ADUX1020_REG_INT_MASK, mask);
if (ret < 0)
goto fail;
@@ -539,9 +537,8 @@ static int adux1020_write_event_config(struct iio_dev *indio_dev,
* Trigger proximity interrupt when the intensity is above
* or below threshold
*/
- ret = regmap_update_bits(data->regmap, ADUX1020_REG_PROX_TYPE,
- ADUX1020_PROX_TYPE,
- ADUX1020_PROX_TYPE);
+ ret = regmap_set_bits(data->regmap, ADUX1020_REG_PROX_TYPE,
+ ADUX1020_PROX_TYPE);
if (ret < 0)
goto fail;
@@ -748,8 +745,8 @@ static int adux1020_chip_init(struct adux1020_data *data)
dev_dbg(&client->dev, "Detected ADUX1020 with chip id: 0x%04x\n", val);
- ret = regmap_update_bits(data->regmap, ADUX1020_REG_SW_RESET,
- ADUX1020_SW_RESET, ADUX1020_SW_RESET);
+ ret = regmap_set_bits(data->regmap, ADUX1020_REG_SW_RESET,
+ ADUX1020_SW_RESET);
if (ret < 0)
return ret;
@@ -764,8 +761,8 @@ static int adux1020_chip_init(struct adux1020_data *data)
return ret;
/* Use LED_IREF for proximity mode */
- ret = regmap_update_bits(data->regmap, ADUX1020_REG_LED_CURRENT,
- ADUX1020_LED_PIREF_EN, 0);
+ ret = regmap_clear_bits(data->regmap, ADUX1020_REG_LED_CURRENT,
+ ADUX1020_LED_PIREF_EN);
if (ret < 0)
return ret;
@@ -821,8 +818,8 @@ static int adux1020_probe(struct i2c_client *client)
}
static const struct i2c_device_id adux1020_id[] = {
- { "adux1020", 0 },
- {}
+ { "adux1020" },
+ { }
};
MODULE_DEVICE_TABLE(i2c, adux1020_id);
@@ -837,7 +834,7 @@ static struct i2c_driver adux1020_driver = {
.name = ADUX1020_DRV_NAME,
.of_match_table = adux1020_of_match,
},
- .probe_new = adux1020_probe,
+ .probe = adux1020_probe,
.id_table = adux1020_id,
};
module_i2c_driver(adux1020_driver);
diff --git a/drivers/iio/light/al3000a.c b/drivers/iio/light/al3000a.c
new file mode 100644
index 000000000000..9871096cbab3
--- /dev/null
+++ b/drivers/iio/light/al3000a.c
@@ -0,0 +1,210 @@
+// 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/mod_devicetable.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[] = {
+ { "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
index 69cc723e2ac4..0932fa2b49fa 100644
--- a/drivers/iio/light/al3010.c
+++ b/drivers/iio/light/al3010.c
@@ -17,13 +17,12 @@
#include <linux/bitfield.h>
#include <linux/i2c.h>
#include <linux/module.h>
-#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/mod_devicetable.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
-#define AL3010_DRV_NAME "al3010"
-
#define AL3010_REG_SYSTEM 0x00
#define AL3010_REG_DATA_LOW 0x0c
#define AL3010_REG_CONFIG 0x10
@@ -46,8 +45,14 @@ static const int al3010_scales[][2] = {
{0, 1187200}, {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 i2c_client *client;
+ struct regmap *regmap;
};
static const struct iio_chan_spec al3010_channels[] = {
@@ -69,35 +74,36 @@ static const struct attribute_group al3010_attribute_group = {
.attrs = al3010_attributes,
};
-static int al3010_set_pwr(struct i2c_client *client, bool pwr)
+static int al3010_set_pwr_on(struct al3010_data *data)
{
- u8 val = pwr ? AL3010_CONFIG_ENABLE : AL3010_CONFIG_DISABLE;
- return i2c_smbus_write_byte_data(client, AL3010_REG_SYSTEM, val);
+ 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;
- al3010_set_pwr(data->client, false);
+ 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(data->client, true);
-
- if (ret < 0)
+ ret = al3010_set_pwr_on(data);
+ if (ret)
return ret;
- ret = i2c_smbus_write_byte_data(data->client, AL3010_REG_CONFIG,
- FIELD_PREP(AL3010_GAIN_MASK,
- AL3XXX_RANGE_3));
- if (ret < 0)
+ ret = devm_add_action_or_reset(dev, al3010_set_pwr_off, data);
+ if (ret)
return ret;
-
- return 0;
+ 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,
@@ -105,7 +111,7 @@ static int al3010_read_raw(struct iio_dev *indio_dev,
int *val2, long mask)
{
struct al3010_data *data = iio_priv(indio_dev);
- int ret;
+ int ret, gain, raw;
switch (mask) {
case IIO_CHAN_INFO_RAW:
@@ -114,21 +120,21 @@ static int al3010_read_raw(struct iio_dev *indio_dev,
* - low byte of output is stored at AL3010_REG_DATA_LOW
* - high byte of output is stored at AL3010_REG_DATA_LOW + 1
*/
- ret = i2c_smbus_read_word_data(data->client,
- AL3010_REG_DATA_LOW);
- if (ret < 0)
+ ret = regmap_read(data->regmap, AL3010_REG_DATA_LOW, &raw);
+ if (ret)
return ret;
- *val = ret;
+
+ *val = raw;
+
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
- ret = i2c_smbus_read_byte_data(data->client,
- AL3010_REG_CONFIG);
- if (ret < 0)
+ ret = regmap_read(data->regmap, AL3010_REG_CONFIG, &gain);
+ if (ret)
return ret;
- ret = FIELD_GET(AL3010_GAIN_MASK, ret);
- *val = al3010_scales[ret][0];
- *val2 = al3010_scales[ret][1];
+ gain = FIELD_GET(AL3010_GAIN_MASK, gain);
+ *val = al3010_scales[gain][0];
+ *val2 = al3010_scales[gain][1];
return IIO_VAL_INT_PLUS_MICRO;
}
@@ -140,7 +146,7 @@ static int al3010_write_raw(struct iio_dev *indio_dev,
int val2, long mask)
{
struct al3010_data *data = iio_priv(indio_dev);
- int i;
+ unsigned int i;
switch (mask) {
case IIO_CHAN_INFO_SCALE:
@@ -149,9 +155,8 @@ static int al3010_write_raw(struct iio_dev *indio_dev,
val2 != al3010_scales[i][1])
continue;
- return i2c_smbus_write_byte_data(data->client,
- AL3010_REG_CONFIG,
- FIELD_PREP(AL3010_GAIN_MASK, i));
+ return regmap_write(data->regmap, AL3010_REG_CONFIG,
+ FIELD_PREP(AL3010_GAIN_MASK, i));
}
break;
}
@@ -167,69 +172,70 @@ static const struct iio_info al3010_info = {
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(&client->dev, sizeof(*data));
+ 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->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_DRV_NAME;
+ 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 < 0) {
- dev_err(&client->dev, "al3010 chip init failed\n");
- return ret;
- }
-
- ret = devm_add_action_or_reset(&client->dev,
- al3010_set_pwr_off,
- data);
- if (ret < 0)
- return ret;
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to init ALS\n");
- return devm_iio_device_register(&client->dev, indio_dev);
+ return devm_iio_device_register(dev, indio_dev);
}
static int al3010_suspend(struct device *dev)
{
- return al3010_set_pwr(to_i2c_client(dev), false);
+ struct al3010_data *data = iio_priv(dev_get_drvdata(dev));
+
+ al3010_set_pwr_off(data);
+ return 0;
}
static int al3010_resume(struct device *dev)
{
- return al3010_set_pwr(to_i2c_client(dev), true);
+ 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[] = {
{"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_DRV_NAME,
+ .name = "al3010",
.of_match_table = al3010_of_match,
.pm = pm_sleep_ptr(&al3010_pm_ops),
},
- .probe_new = al3010_probe,
+ .probe = al3010_probe,
.id_table = al3010_id,
};
module_i2c_driver(al3010_driver);
diff --git a/drivers/iio/light/al3320a.c b/drivers/iio/light/al3320a.c
index 9ff28bbf34bb..63f5a85912fc 100644
--- a/drivers/iio/light/al3320a.c
+++ b/drivers/iio/light/al3320a.c
@@ -15,13 +15,12 @@
#include <linux/bitfield.h>
#include <linux/i2c.h>
#include <linux/module.h>
-#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/mod_devicetable.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
-#define AL3320A_DRV_NAME "al3320a"
-
#define AL3320A_REG_CONFIG 0x00
#define AL3320A_REG_STATUS 0x01
#define AL3320A_REG_INT 0x02
@@ -59,8 +58,14 @@ 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 i2c_client *client;
+ struct regmap *regmap;
};
static const struct iio_chan_spec al3320a_channels[] = {
@@ -82,45 +87,47 @@ static const struct attribute_group al3320a_attribute_group = {
.attrs = al3320a_attributes,
};
-static int al3320a_set_pwr(struct i2c_client *client, bool pwr)
+static int al3320a_set_pwr_on(struct al3320a_data *data)
{
- u8 val = pwr ? AL3320A_CONFIG_ENABLE : AL3320A_CONFIG_DISABLE;
- return i2c_smbus_write_byte_data(client, AL3320A_REG_CONFIG, val);
+ 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;
- al3320a_set_pwr(data->client, false);
+ 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(data->client, true);
-
- if (ret < 0)
+ ret = al3320a_set_pwr_on(data);
+ if (ret)
return ret;
- ret = i2c_smbus_write_byte_data(data->client, AL3320A_REG_CONFIG_RANGE,
- FIELD_PREP(AL3320A_GAIN_MASK,
- AL3320A_RANGE_3));
- if (ret < 0)
+ ret = devm_add_action_or_reset(dev, al3320a_set_pwr_off, data);
+ if (ret)
return ret;
- ret = i2c_smbus_write_byte_data(data->client, AL3320A_REG_MEAN_TIME,
- AL3320A_DEFAULT_MEAN_TIME);
- if (ret < 0)
+ ret = regmap_write(data->regmap, AL3320A_REG_CONFIG_RANGE,
+ FIELD_PREP(AL3320A_GAIN_MASK, AL3320A_RANGE_3));
+ if (ret)
return ret;
- ret = i2c_smbus_write_byte_data(data->client, AL3320A_REG_WAIT,
- AL3320A_DEFAULT_WAIT_TIME);
- if (ret < 0)
+ ret = regmap_write(data->regmap, AL3320A_REG_MEAN_TIME,
+ AL3320A_DEFAULT_MEAN_TIME);
+ if (ret)
return ret;
- return 0;
+ return regmap_write(data->regmap, AL3320A_REG_WAIT,
+ AL3320A_DEFAULT_WAIT_TIME);
}
static int al3320a_read_raw(struct iio_dev *indio_dev,
@@ -128,7 +135,7 @@ static int al3320a_read_raw(struct iio_dev *indio_dev,
int *val2, long mask)
{
struct al3320a_data *data = iio_priv(indio_dev);
- int ret;
+ int ret, gain, raw;
switch (mask) {
case IIO_CHAN_INFO_RAW:
@@ -137,21 +144,21 @@ static int al3320a_read_raw(struct iio_dev *indio_dev,
* - low byte of output is stored at AL3320A_REG_DATA_LOW
* - high byte of output is stored at AL3320A_REG_DATA_LOW + 1
*/
- ret = i2c_smbus_read_word_data(data->client,
- AL3320A_REG_DATA_LOW);
- if (ret < 0)
+ ret = regmap_read(data->regmap, AL3320A_REG_DATA_LOW, &raw);
+ if (ret)
return ret;
- *val = ret;
+
+ *val = raw;
+
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
- ret = i2c_smbus_read_byte_data(data->client,
- AL3320A_REG_CONFIG_RANGE);
- if (ret < 0)
+ ret = regmap_read(data->regmap, AL3320A_REG_CONFIG_RANGE, &gain);
+ if (ret)
return ret;
- ret = FIELD_GET(AL3320A_GAIN_MASK, ret);
- *val = al3320a_scales[ret][0];
- *val2 = al3320a_scales[ret][1];
+ gain = FIELD_GET(AL3320A_GAIN_MASK, gain);
+ *val = al3320a_scales[gain][0];
+ *val2 = al3320a_scales[gain][1];
return IIO_VAL_INT_PLUS_MICRO;
}
@@ -163,7 +170,7 @@ static int al3320a_write_raw(struct iio_dev *indio_dev,
int val2, long mask)
{
struct al3320a_data *data = iio_priv(indio_dev);
- int i;
+ unsigned int i;
switch (mask) {
case IIO_CHAN_INFO_SCALE:
@@ -172,9 +179,8 @@ static int al3320a_write_raw(struct iio_dev *indio_dev,
val2 != al3320a_scales[i][1])
continue;
- return i2c_smbus_write_byte_data(data->client,
- AL3320A_REG_CONFIG_RANGE,
- FIELD_PREP(AL3320A_GAIN_MASK, i));
+ return regmap_write(data->regmap, AL3320A_REG_CONFIG_RANGE,
+ FIELD_PREP(AL3320A_GAIN_MASK, i));
}
break;
}
@@ -190,70 +196,81 @@ static const struct iio_info al3320a_info = {
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(&client->dev, sizeof(*data));
+ 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->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_DRV_NAME;
+ 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(&client->dev, "al3320a chip init failed\n");
+ dev_err(dev, "al3320a chip init failed\n");
return ret;
}
- ret = devm_add_action_or_reset(&client->dev,
- al3320a_set_pwr_off,
- data);
- if (ret < 0)
- return ret;
-
- return devm_iio_device_register(&client->dev, indio_dev);
+ return devm_iio_device_register(dev, indio_dev);
}
static int al3320a_suspend(struct device *dev)
{
- return al3320a_set_pwr(to_i2c_client(dev), false);
+ struct al3320a_data *data = iio_priv(dev_get_drvdata(dev));
+
+ al3320a_set_pwr_off(data);
+ return 0;
}
static int al3320a_resume(struct device *dev)
{
- return al3320a_set_pwr(to_i2c_client(dev), true);
+ 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[] = {
- {"al3320a", 0},
- {}
+ { "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_DRV_NAME,
+ .name = "al3320a",
.of_match_table = al3320a_of_match,
.pm = pm_sleep_ptr(&al3320a_pm_ops),
+ .acpi_match_table = al3320a_acpi_match,
},
- .probe_new = al3320a_probe,
+ .probe = al3320a_probe,
.id_table = al3320a_id,
};
diff --git a/drivers/iio/light/apds9160.c b/drivers/iio/light/apds9160.c
new file mode 100644
index 000000000000..9b8af11b7b67
--- /dev/null
+++ b/drivers/iio/light/apds9160.c
@@ -0,0 +1,1592 @@
+// 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 substraction 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, &reg);
+ 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, &reg);
+ 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 dev_err_probe(dev, ret,
+ "request irq (%d) failed\n",
+ client->irq);
+ }
+ } 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[] = {
+ { "apds9160", 0 },
+ { }
+};
+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
index 15dfb753734f..05ba21675063 100644
--- a/drivers/iio/light/apds9300.c
+++ b/drivers/iio/light/apds9300.c
@@ -17,7 +17,6 @@
#include <linux/iio/events.h>
#define APDS9300_DRV_NAME "apds9300"
-#define APDS9300_IRQ_NAME "apds9300_event"
/* Command register bits */
#define APDS9300_CMD BIT(7) /* Select command register. Must write as 1 */
@@ -46,10 +45,10 @@
struct apds9300_data {
struct i2c_client *client;
struct mutex mutex;
- int power_state;
+ bool power_state;
int thresh_low;
int thresh_hi;
- int intr_en;
+ bool intr_en;
};
/* Lux calculation */
@@ -148,7 +147,7 @@ static int apds9300_set_thresh_hi(struct apds9300_data *data, int value)
return 0;
}
-static int apds9300_set_intr_state(struct apds9300_data *data, int state)
+static int apds9300_set_intr_state(struct apds9300_data *data, bool state)
{
int ret;
u8 cmd;
@@ -169,7 +168,7 @@ static int apds9300_set_intr_state(struct apds9300_data *data, int state)
return 0;
}
-static int apds9300_set_power_state(struct apds9300_data *data, int state)
+static int apds9300_set_power_state(struct apds9300_data *data, bool state)
{
int ret;
u8 cmd;
@@ -221,7 +220,7 @@ static int apds9300_chip_init(struct apds9300_data *data)
* Disable interrupt to ensure thai it is doesn't enable
* i.e. after device soft reset
*/
- ret = apds9300_set_intr_state(data, 0);
+ ret = apds9300_set_intr_state(data, false);
if (ret < 0)
goto err;
@@ -321,7 +320,7 @@ static int apds9300_read_interrupt_config(struct iio_dev *indio_dev,
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, int state)
+ enum iio_event_direction dir, bool state)
{
struct apds9300_data *data = iio_priv(indio_dev);
int ret;
@@ -432,7 +431,7 @@ static int apds9300_probe(struct i2c_client *client)
ret = devm_request_threaded_irq(&client->dev, client->irq,
NULL, apds9300_interrupt_handler,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
- APDS9300_IRQ_NAME, indio_dev);
+ "apds9300_event", indio_dev);
if (ret) {
dev_err(&client->dev, "irq request error %d\n", -ret);
goto err;
@@ -459,8 +458,8 @@ static void apds9300_remove(struct i2c_client *client)
iio_device_unregister(indio_dev);
/* Ensure that power off and interrupts are disabled */
- apds9300_set_intr_state(data, 0);
- apds9300_set_power_state(data, 0);
+ apds9300_set_intr_state(data, false);
+ apds9300_set_power_state(data, false);
}
static int apds9300_suspend(struct device *dev)
@@ -470,7 +469,7 @@ static int apds9300_suspend(struct device *dev)
int ret;
mutex_lock(&data->mutex);
- ret = apds9300_set_power_state(data, 0);
+ ret = apds9300_set_power_state(data, false);
mutex_unlock(&data->mutex);
return ret;
@@ -483,7 +482,7 @@ static int apds9300_resume(struct device *dev)
int ret;
mutex_lock(&data->mutex);
- ret = apds9300_set_power_state(data, 1);
+ ret = apds9300_set_power_state(data, true);
mutex_unlock(&data->mutex);
return ret;
@@ -493,7 +492,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(apds9300_pm_ops, apds9300_suspend,
apds9300_resume);
static const struct i2c_device_id apds9300_id[] = {
- { APDS9300_DRV_NAME, 0 },
+ { APDS9300_DRV_NAME },
{ }
};
@@ -504,7 +503,7 @@ static struct i2c_driver apds9300_driver = {
.name = APDS9300_DRV_NAME,
.pm = pm_sleep_ptr(&apds9300_pm_ops),
},
- .probe_new = apds9300_probe,
+ .probe = apds9300_probe,
.remove = apds9300_remove,
.id_table = apds9300_id,
};
diff --git a/drivers/iio/light/apds9306.c b/drivers/iio/light/apds9306.c
new file mode 100644
index 000000000000..7e68cca0edfa
--- /dev/null
+++ b/drivers/iio/light/apds9306.c
@@ -0,0 +1,1359 @@
+// 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
+ * @nlux_per_count: Nano lux per ADC count for a particular model
+ * @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 nlux_per_count;
+ 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];
+
+ ret = pm_runtime_resume_and_get(data->dev);
+ if (ret)
+ return ret;
+
+ 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);
+
+ pm_runtime_put_autosuspend(data->dev);
+
+ 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 = (struct apds9306_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 dev_err_probe(dev, ret,
+ "failed to assign interrupt.\n");
+ } 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
index cc5974a95bd3..785c5dbe2d08 100644
--- a/drivers/iio/light/apds9960.c
+++ b/drivers/iio/light/apds9960.c
@@ -25,7 +25,6 @@
#include <linux/iio/kfifo_buf.h>
#include <linux/iio/sysfs.h>
-#define APDS9960_REGMAP_NAME "apds9960_regmap"
#define APDS9960_DRV_NAME "apds9960"
#define APDS9960_REG_RAM_START 0x00
@@ -133,8 +132,8 @@ struct apds9960_data {
struct regmap_field *reg_enable_pxs;
/* state */
- int als_int;
- int pxs_int;
+ bool als_int;
+ bool pxs_int;
int gesture_mode_running;
/* gain values */
@@ -146,6 +145,25 @@ struct apds9960_data {
/* 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[] = {
@@ -202,7 +220,7 @@ static const struct regmap_access_table apds9960_writeable_table = {
};
static const struct regmap_config apds9960_regmap_config = {
- .name = APDS9960_REGMAP_NAME,
+ .name = "apds9960_regmap",
.reg_bits = 8,
.val_bits = 8,
.use_single_read = true,
@@ -216,7 +234,7 @@ static const struct regmap_config apds9960_regmap_config = {
.reg_defaults = apds9960_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(apds9960_reg_defaults),
.max_register = APDS9960_REG_GFIFO_DIR(RIGHT),
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
};
static const struct iio_event_spec apds9960_pxs_event_spec[] = {
@@ -255,6 +273,7 @@ static const struct iio_event_spec apds9960_als_event_spec[] = {
#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, \
@@ -282,7 +301,8 @@ static const struct iio_chan_spec apds9960_channels[] = {
{
.type = IIO_PROXIMITY,
.address = APDS9960_REG_PDATA,
- .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .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,
@@ -316,6 +336,28 @@ static const struct iio_chan_spec apds9960_channels[] = {
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},
@@ -453,7 +495,6 @@ static int apds9960_set_power_state(struct apds9960_data *data, bool on)
usleep_range(data->als_adc_int_us,
APDS9960_MAX_INT_TIME_IN_US);
} else {
- pm_runtime_mark_last_busy(dev);
ret = pm_runtime_put_autosuspend(dev);
}
@@ -531,6 +572,12 @@ static int apds9960_read_raw(struct iio_dev *indio_dev,
}
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;
@@ -564,6 +611,10 @@ static int apds9960_write_raw(struct iio_dev *indio_dev,
default:
return -EINVAL;
}
+ case IIO_CHAN_INFO_CALIBBIAS:
+ if (val2 != 0)
+ return -EINVAL;
+ return apds9960_set_calibbias(data, chan, val);
default:
return -EINVAL;
}
@@ -696,21 +747,17 @@ static int apds9960_read_event_config(struct iio_dev *indio_dev,
default:
return -EINVAL;
}
-
- return 0;
}
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,
- int state)
+ bool state)
{
struct apds9960_data *data = iio_priv(indio_dev);
int ret;
- state = !!state;
-
switch (chan->type) {
case IIO_PROXIMITY:
if (data->pxs_int == state)
@@ -1107,8 +1154,8 @@ static const struct dev_pm_ops apds9960_pm_ops = {
};
static const struct i2c_device_id apds9960_id[] = {
- { "apds9960", 0 },
- {}
+ { "apds9960" },
+ { }
};
MODULE_DEVICE_TABLE(i2c, apds9960_id);
@@ -1131,7 +1178,7 @@ static struct i2c_driver apds9960_driver = {
.pm = &apds9960_pm_ops,
.acpi_match_table = apds9960_acpi_match,
},
- .probe_new = apds9960_probe,
+ .probe = apds9960_probe,
.remove = apds9960_remove,
.id_table = apds9960_id,
};
diff --git a/drivers/iio/light/as73211.c b/drivers/iio/light/as73211.c
index 2307fc531752..32719f584c47 100644
--- a/drivers/iio/light/as73211.c
+++ b/drivers/iio/light/as73211.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
- * Support for AMS AS73211 JENCOLOR(R) Digital XYZ Sensor
+ * 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>
*
@@ -9,10 +10,13 @@
* Color light sensor with 16-bit channels for x, y, z and temperature);
* 7-bit I2C slave address 0x74 .. 0x77.
*
- * Datasheet: https://ams.com/documents/20143/36005/AS73211_DS000556_3-01.pdf
+ * 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>
@@ -84,6 +88,20 @@ 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.
@@ -94,6 +112,7 @@ static const int as73211_hardwaregain_avail[] = {
* @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;
@@ -104,6 +123,7 @@ struct as73211_data {
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) { \
@@ -138,6 +158,10 @@ struct as73211_data {
#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
@@ -154,6 +178,12 @@ struct as73211_data {
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,
@@ -176,6 +206,28 @@ static const struct iio_chan_spec as73211_channels[] = {
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)
{
/*
@@ -316,6 +368,48 @@ static int as73211_req_data(struct as73211_data *data)
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)
{
@@ -325,18 +419,17 @@ static int as73211_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec cons
case IIO_CHAN_INFO_RAW: {
int ret;
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret < 0)
- return ret;
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
ret = as73211_req_data(data);
if (ret < 0) {
- iio_device_release_direct_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
return ret;
}
ret = i2c_smbus_read_word_data(data->client, chan->address);
- iio_device_release_direct_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
if (ret < 0)
return ret;
@@ -355,30 +448,13 @@ static int as73211_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec cons
*val2 = AS73211_SCALE_TEMP_MICRO;
return IIO_VAL_INT_PLUS_MICRO;
- case IIO_INTENSITY: {
- unsigned int scale;
-
- switch (chan->channel2) {
- case IIO_MOD_X:
- scale = AS73211_SCALE_X;
- break;
- case IIO_MOD_Y:
- scale = AS73211_SCALE_Y;
- break;
- case IIO_MOD_Z:
- scale = AS73211_SCALE_Z;
- break;
- default:
- return -EINVAL;
- }
- scale /= as73211_gain(data);
- scale /= as73211_integration_time_1024cyc(data);
- *val = scale;
- return IIO_VAL_INT;
+ 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) */
@@ -441,6 +517,16 @@ static int _as73211_write_raw(struct iio_dev *indio_dev,
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, ... */
@@ -525,28 +611,14 @@ static int as73211_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec con
struct as73211_data *data = iio_priv(indio_dev);
int ret;
- mutex_lock(&data->mutex);
-
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret < 0)
- goto error_unlock;
+ guard(mutex)(&data->mutex);
- /* 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)
- goto error_release;
- }
+ 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);
-error_release:
- iio_device_release_direct_mode(indio_dev);
-error_unlock:
- mutex_unlock(&data->mutex);
return ret;
}
@@ -566,8 +638,8 @@ static irqreturn_t as73211_trigger_handler(int irq __always_unused, void *p)
struct as73211_data *data = iio_priv(indio_dev);
struct {
__le16 chan[4];
- s64 ts __aligned(8);
- } scan;
+ aligned_s64 ts;
+ } scan = { };
int data_result, ret;
mutex_lock(&data->mutex);
@@ -602,9 +674,12 @@ static irqreturn_t as73211_trigger_handler(int irq __always_unused, void *p)
/* AS73211 starts reading at address 2 */
ret = i2c_master_recv(data->client,
- (char *)&scan.chan[1], 3 * sizeof(scan.chan[1]));
+ (char *)&scan.chan[0], 3 * sizeof(scan.chan[0]));
if (ret < 0)
goto done;
+
+ /* Avoid pushing uninitialized data */
+ scan.chan[3] = 0;
}
if (data_result) {
@@ -612,9 +687,15 @@ static irqreturn_t as73211_trigger_handler(int irq __always_unused, void *p)
* Saturate all channels (in case of overflows). Temperature channel
* is not affected by overflows.
*/
- scan.chan[1] = cpu_to_le16(U16_MAX);
- scan.chan[2] = cpu_to_le16(U16_MAX);
- scan.chan[3] = cpu_to_le16(U16_MAX);
+ 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));
@@ -676,14 +757,19 @@ static int as73211_probe(struct i2c_client *client)
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 = as73211_channels;
- indio_dev->num_channels = ARRAY_SIZE(as73211_channels);
+ 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)
@@ -772,14 +858,28 @@ static int as73211_resume(struct device *dev)
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" },
+ { .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[] = {
- { "as73211", 0 },
+ { "as73211", (kernel_ulong_t)&as73211_spec },
+ { "as7331", (kernel_ulong_t)&as7331_spec },
{ }
};
MODULE_DEVICE_TABLE(i2c, as73211_id);
@@ -790,7 +890,7 @@ static struct i2c_driver as73211_driver = {
.of_match_table = as73211_of_match,
.pm = pm_sleep_ptr(&as73211_pm_ops),
},
- .probe_new = as73211_probe,
+ .probe = as73211_probe,
.id_table = as73211_id,
};
module_i2c_driver(as73211_driver);
diff --git a/drivers/iio/light/bh1745.c b/drivers/iio/light/bh1745.c
new file mode 100644
index 000000000000..10b00344bbed
--- /dev/null
+++ b/drivers/iio/light/bh1745.c
@@ -0,0 +1,901 @@
+// 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, &current_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 dev_err_probe(dev, ret,
+ "Request for IRQ failed\n");
+ }
+
+ 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[] = {
+ { "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
index 390c5b3ad4f6..764f88826fcb 100644
--- a/drivers/iio/light/bh1750.c
+++ b/drivers/iio/light/bh1750.c
@@ -22,12 +22,16 @@
#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,
@@ -40,6 +44,7 @@ struct bh1750_data {
struct mutex lock;
const struct bh1750_chip_info *chip_info;
u16 mtreg;
+ struct gpio_desc *reset_gpio;
};
struct bh1750_chip_info {
@@ -248,6 +253,25 @@ static int bh1750_probe(struct i2c_client *client)
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)
@@ -320,7 +344,7 @@ static struct i2c_driver bh1750_driver = {
.of_match_table = bh1750_of_match,
.pm = pm_sleep_ptr(&bh1750_pm_ops),
},
- .probe_new = bh1750_probe,
+ .probe = bh1750_probe,
.remove = bh1750_remove,
.id_table = bh1750_id,
diff --git a/drivers/iio/light/bh1780.c b/drivers/iio/light/bh1780.c
index da9039e5a839..5d3c6d5276ba 100644
--- a/drivers/iio/light/bh1780.c
+++ b/drivers/iio/light/bh1780.c
@@ -111,7 +111,6 @@ static int bh1780_read_raw(struct iio_dev *indio_dev,
value = bh1780_read_word(bh1780, BH1780_REG_DLOW);
if (value < 0)
return value;
- pm_runtime_mark_last_busy(&bh1780->client->dev);
pm_runtime_put_autosuspend(&bh1780->client->dev);
*val = value;
@@ -256,20 +255,20 @@ 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[] = {
- { "bh1780", 0 },
- { },
+ { "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_new = bh1780_probe,
+ .probe = bh1780_probe,
.remove = bh1780_remove,
.id_table = bh1780_id,
.driver = {
diff --git a/drivers/iio/light/cm32181.c b/drivers/iio/light/cm32181.c
index 001055d09750..bb90f738312a 100644
--- a/drivers/iio/light/cm32181.c
+++ b/drivers/iio/light/cm32181.c
@@ -217,8 +217,7 @@ static int cm32181_reg_init(struct cm32181_chip *cm32181)
cm32181->lux_per_bit = CM32181_LUX_PER_BIT;
cm32181->lux_per_bit_base_it = CM32181_LUX_PER_BIT_BASE_IT;
- if (ACPI_HANDLE(cm32181->dev))
- cm32181_acpi_parse_cpm_tables(cm32181);
+ cm32181_acpi_parse_cpm_tables(cm32181);
/* Initialize registers*/
for_each_set_bit(i, &cm32181->init_regs_bitmap, CM32181_CONF_REG_NUM) {
@@ -429,6 +428,14 @@ static const struct iio_info cm32181_info = {
.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;
@@ -440,6 +447,8 @@ static int cm32181_probe(struct i2c_client *client)
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.
@@ -458,9 +467,11 @@ static int cm32181_probe(struct i2c_client *client)
client = i2c_acpi_new_device(dev, 1, &board_info);
if (IS_ERR(client))
return PTR_ERR(client);
- }
- i2c_set_clientdata(client, indio_dev);
+ ret = devm_add_action_or_reset(dev, cm32181_unregister_dummy_client, client);
+ if (ret)
+ return ret;
+ }
cm32181 = iio_priv(indio_dev);
cm32181->client = client;
@@ -481,7 +492,7 @@ static int cm32181_probe(struct i2c_client *client)
ret = devm_iio_device_register(dev, indio_dev);
if (ret) {
- dev_err(dev, "%s: regist device failed\n", __func__);
+ dev_err(dev, "%s: register device failed\n", __func__);
return ret;
}
@@ -490,7 +501,8 @@ static int cm32181_probe(struct i2c_client *client)
static int cm32181_suspend(struct device *dev)
{
- struct i2c_client *client = to_i2c_client(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);
@@ -498,8 +510,8 @@ static int cm32181_suspend(struct device *dev)
static int cm32181_resume(struct device *dev)
{
- struct i2c_client *client = to_i2c_client(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]);
@@ -529,7 +541,7 @@ static struct i2c_driver cm32181_driver = {
.of_match_table = cm32181_of_match,
.pm = pm_sleep_ptr(&cm32181_pm_ops),
},
- .probe_new = cm32181_probe,
+ .probe = cm32181_probe,
};
module_i2c_driver(cm32181_driver);
diff --git a/drivers/iio/light/cm3232.c b/drivers/iio/light/cm3232.c
index 43e492f5051d..3a3ad6b4c468 100644
--- a/drivers/iio/light/cm3232.c
+++ b/drivers/iio/light/cm3232.c
@@ -54,22 +54,21 @@ static const struct {
struct cm3232_als_info {
u8 regs_cmd_default;
u8 hw_id;
- int calibscale;
int mlux_per_bit;
int mlux_per_bit_base_it;
};
-static struct cm3232_als_info cm3232_als_info_default = {
+static const struct cm3232_als_info cm3232_als_info_default = {
.regs_cmd_default = CM3232_CMD_DEFAULT,
.hw_id = CM3232_HW_ID,
- .calibscale = CM3232_CALIBSCALE_DEFAULT,
.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;
- struct cm3232_als_info *als_info;
+ const struct cm3232_als_info *als_info;
+ int calibscale;
u8 regs_cmd;
u16 regs_als;
};
@@ -89,6 +88,15 @@ static int cm3232_reg_init(struct cm3232_chip *chip)
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) {
@@ -99,15 +107,6 @@ static int cm3232_reg_init(struct cm3232_chip *chip)
if ((ret & 0xFF) != chip->als_info->hw_id)
return -ENODEV;
- /* 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;
- }
-
/* Register default value */
chip->regs_cmd = chip->als_info->regs_cmd_default;
@@ -199,7 +198,7 @@ static int cm3232_write_als_it(struct cm3232_chip *chip, int val, int val2)
static int cm3232_get_lux(struct cm3232_chip *chip)
{
struct i2c_client *client = chip->client;
- struct cm3232_als_info *als_info = chip->als_info;
+ const struct cm3232_als_info *als_info = chip->als_info;
int ret;
int val, val2;
int als_it;
@@ -222,7 +221,7 @@ static int cm3232_get_lux(struct cm3232_chip *chip)
chip->regs_als = (u16)ret;
lux *= chip->regs_als;
- lux *= als_info->calibscale;
+ lux *= chip->calibscale;
lux = div_u64(lux, CM3232_CALIBSCALE_RESOLUTION);
lux = div_u64(lux, CM3232_MLUX_PER_LUX);
@@ -237,7 +236,6 @@ static int cm3232_read_raw(struct iio_dev *indio_dev,
int *val, int *val2, long mask)
{
struct cm3232_chip *chip = iio_priv(indio_dev);
- struct cm3232_als_info *als_info = chip->als_info;
int ret;
switch (mask) {
@@ -248,7 +246,7 @@ static int cm3232_read_raw(struct iio_dev *indio_dev,
*val = ret;
return IIO_VAL_INT;
case IIO_CHAN_INFO_CALIBSCALE:
- *val = als_info->calibscale;
+ *val = chip->calibscale;
return IIO_VAL_INT;
case IIO_CHAN_INFO_INT_TIME:
return cm3232_read_als_it(chip, val, val2);
@@ -262,11 +260,10 @@ static int cm3232_write_raw(struct iio_dev *indio_dev,
int val, int val2, long mask)
{
struct cm3232_chip *chip = iio_priv(indio_dev);
- struct cm3232_als_info *als_info = chip->als_info;
switch (mask) {
case IIO_CHAN_INFO_CALIBSCALE:
- als_info->calibscale = val;
+ chip->calibscale = val;
return 0;
case IIO_CHAN_INFO_INT_TIME:
return cm3232_write_als_it(chip, val, val2);
@@ -339,6 +336,7 @@ static int cm3232_probe(struct i2c_client *client)
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);
@@ -368,8 +366,8 @@ static void cm3232_remove(struct i2c_client *client)
}
static const struct i2c_device_id cm3232_id[] = {
- {"cm3232", 0},
- {}
+ { "cm3232" },
+ { }
};
static int cm3232_suspend(struct device *dev)
@@ -406,7 +404,7 @@ MODULE_DEVICE_TABLE(i2c, cm3232_id);
static const struct of_device_id cm3232_of_match[] = {
{.compatible = "capella,cm3232"},
- {}
+ { }
};
MODULE_DEVICE_TABLE(of, cm3232_of_match);
@@ -417,7 +415,7 @@ static struct i2c_driver cm3232_driver = {
.pm = pm_sleep_ptr(&cm3232_pm_ops),
},
.id_table = cm3232_id,
- .probe_new = cm3232_probe,
+ .probe = cm3232_probe,
.remove = cm3232_remove,
};
diff --git a/drivers/iio/light/cm3323.c b/drivers/iio/light/cm3323.c
index e5ce7d0fd272..79ad6e2209ca 100644
--- a/drivers/iio/light/cm3323.c
+++ b/drivers/iio/light/cm3323.c
@@ -250,14 +250,14 @@ static int cm3323_probe(struct i2c_client *client)
}
static const struct i2c_device_id cm3323_id[] = {
- {"cm3323", 0},
- {}
+ { "cm3323" },
+ { }
};
MODULE_DEVICE_TABLE(i2c, cm3323_id);
static const struct of_device_id cm3323_of_match[] = {
{ .compatible = "capella,cm3323", },
- { /* sentinel */ }
+ { }
};
MODULE_DEVICE_TABLE(of, cm3323_of_match);
@@ -266,7 +266,7 @@ static struct i2c_driver cm3323_driver = {
.name = CM3323_DRV_NAME,
.of_match_table = cm3323_of_match,
},
- .probe_new = cm3323_probe,
+ .probe = cm3323_probe,
.id_table = cm3323_id,
};
diff --git a/drivers/iio/light/cm3605.c b/drivers/iio/light/cm3605.c
index 0b30db77f78b..0c17378e27d1 100644
--- a/drivers/iio/light/cm3605.c
+++ b/drivers/iio/light/cm3605.c
@@ -227,7 +227,7 @@ static int cm3605_probe(struct platform_device *pdev)
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
- ret = dev_err_probe(dev, irq, "failed to get irq\n");
+ ret = irq;
goto out_disable_aset;
}
@@ -266,7 +266,7 @@ out_disable_vdd:
return ret;
}
-static int cm3605_remove(struct platform_device *pdev)
+static void cm3605_remove(struct platform_device *pdev)
{
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct cm3605 *cm3605 = iio_priv(indio_dev);
@@ -276,8 +276,6 @@ static int cm3605_remove(struct platform_device *pdev)
gpiod_set_value_cansleep(cm3605->aset, 0);
iio_device_unregister(indio_dev);
regulator_disable(cm3605->vdd);
-
- return 0;
}
static int cm3605_pm_suspend(struct device *dev)
@@ -309,7 +307,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(cm3605_dev_pm_ops, cm3605_pm_suspend,
static const struct of_device_id cm3605_of_match[] = {
{.compatible = "capella,cm3605"},
- { },
+ { }
};
MODULE_DEVICE_TABLE(of, cm3605_of_match);
diff --git a/drivers/iio/light/cm36651.c b/drivers/iio/light/cm36651.c
index 1707dbf2ce26..446dd54d5037 100644
--- a/drivers/iio/light/cm36651.c
+++ b/drivers/iio/light/cm36651.c
@@ -529,7 +529,7 @@ 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,
- int state)
+ bool state)
{
struct cm36651_data *cm36651 = iio_priv(indio_dev);
int cmd, ret;
@@ -683,7 +683,7 @@ static int cm36651_probe(struct i2c_client *client)
ret = iio_device_register(indio_dev);
if (ret) {
- dev_err(&client->dev, "%s: regist device failed\n", __func__);
+ dev_err(&client->dev, "%s: register device failed\n", __func__);
goto error_free_irq;
}
@@ -713,7 +713,7 @@ static void cm36651_remove(struct i2c_client *client)
}
static const struct i2c_device_id cm36651_id[] = {
- { "cm36651", 0 },
+ { "cm36651" },
{ }
};
@@ -730,7 +730,7 @@ static struct i2c_driver cm36651_driver = {
.name = "cm36651",
.of_match_table = cm36651_of_match,
},
- .probe_new = cm36651_probe,
+ .probe = cm36651_probe,
.remove = cm36651_remove,
.id_table = cm36651_id,
};
diff --git a/drivers/iio/light/cros_ec_light_prox.c b/drivers/iio/light/cros_ec_light_prox.c
index 19e529c84e95..815806ceb5c8 100644
--- a/drivers/iio/light/cros_ec_light_prox.c
+++ b/drivers/iio/light/cros_ec_light_prox.c
@@ -249,7 +249,7 @@ static const struct platform_device_id cros_ec_light_prox_ids[] = {
{
.name = "cros-ec-light",
},
- { /* sentinel */ }
+ { }
};
MODULE_DEVICE_TABLE(platform, cros_ec_light_prox_ids);
diff --git a/drivers/iio/light/gp2ap002.c b/drivers/iio/light/gp2ap002.c
index c0430db0038a..a0d8a58f2704 100644
--- a/drivers/iio/light/gp2ap002.c
+++ b/drivers/iio/light/gp2ap002.c
@@ -271,7 +271,6 @@ static int gp2ap002_read_raw(struct iio_dev *indio_dev,
}
out:
- pm_runtime_mark_last_busy(gp2ap002->dev);
pm_runtime_put_autosuspend(gp2ap002->dev);
return ret;
@@ -340,7 +339,7 @@ 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,
- int state)
+ bool state)
{
struct gp2ap002 *gp2ap002 = iio_priv(indio_dev);
@@ -353,7 +352,6 @@ static int gp2ap002_write_event_config(struct iio_dev *indio_dev,
pm_runtime_get_sync(gp2ap002->dev);
gp2ap002->enabled = true;
} else {
- pm_runtime_mark_last_busy(gp2ap002->dev);
pm_runtime_put_autosuspend(gp2ap002->dev);
gp2ap002->enabled = false;
}
@@ -420,7 +418,7 @@ static int gp2ap002_regmap_i2c_write(void *context, unsigned int reg,
return i2c_smbus_write_byte_data(i2c, reg, val);
}
-static struct regmap_bus gp2ap002_regmap_bus = {
+static const struct regmap_bus gp2ap002_regmap_bus = {
.reg_read = gp2ap002_regmap_i2c_read,
.reg_write = gp2ap002_regmap_i2c_write,
};
@@ -692,15 +690,15 @@ 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[] = {
- { "gp2ap002", 0 },
- { },
+ { "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);
@@ -710,7 +708,7 @@ static struct i2c_driver gp2ap002_driver = {
.of_match_table = gp2ap002_of_match,
.pm = pm_ptr(&gp2ap002_dev_pm_ops),
},
- .probe_new = gp2ap002_probe,
+ .probe = gp2ap002_probe,
.remove = gp2ap002_remove,
.id_table = gp2ap002_id_table,
};
diff --git a/drivers/iio/light/gp2ap020a00f.c b/drivers/iio/light/gp2ap020a00f.c
index a5bf9da0d2f3..c7df4b258e2c 100644
--- a/drivers/iio/light/gp2ap020a00f.c
+++ b/drivers/iio/light/gp2ap020a00f.c
@@ -43,7 +43,7 @@
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include <linux/iio/buffer.h>
#include <linux/iio/events.h>
#include <linux/iio/iio.h>
@@ -237,7 +237,6 @@ enum gp2ap020a00f_thresh_val_id {
};
struct gp2ap020a00f_data {
- const struct gp2ap020a00f_platform_data *pdata;
struct i2c_client *client;
struct mutex lock;
char *buffer;
@@ -966,8 +965,7 @@ static irqreturn_t gp2ap020a00f_trigger_handler(int irq, void *data)
size_t d_size = 0;
int i, out_val, ret;
- for_each_set_bit(i, indio_dev->active_scan_mask,
- indio_dev->masklength) {
+ iio_for_each_active_channel(indio_dev, i) {
ret = regmap_bulk_read(priv->regmap,
GP2AP020A00F_DATA_REG(i),
&priv->buffer[d_size], 2);
@@ -1161,7 +1159,7 @@ 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,
- int state)
+ bool state)
{
struct gp2ap020a00f_data *data = iio_priv(indio_dev);
enum gp2ap020a00f_cmd cmd;
@@ -1285,12 +1283,11 @@ static int gp2ap020a00f_read_raw(struct iio_dev *indio_dev,
int err = -EINVAL;
if (mask == IIO_CHAN_INFO_RAW) {
- err = iio_device_claim_direct_mode(indio_dev);
- if (err)
- return err;
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
err = gp2ap020a00f_read_channel(data, chan, val);
- iio_device_release_direct_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
}
return err < 0 ? err : IIO_VAL_INT;
}
@@ -1398,8 +1395,7 @@ static int gp2ap020a00f_buffer_postenable(struct iio_dev *indio_dev)
* two separate IIO channels they are treated in the driver logic
* as if they were controlled independently.
*/
- for_each_set_bit(i, indio_dev->active_scan_mask,
- indio_dev->masklength) {
+ iio_for_each_active_channel(indio_dev, i) {
switch (i) {
case GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR:
err = gp2ap020a00f_exec_cmd(data,
@@ -1436,8 +1432,7 @@ static int gp2ap020a00f_buffer_predisable(struct iio_dev *indio_dev)
mutex_lock(&data->lock);
- for_each_set_bit(i, indio_dev->active_scan_mask,
- indio_dev->masklength) {
+ iio_for_each_active_channel(indio_dev, i) {
switch (i) {
case GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR:
err = gp2ap020a00f_exec_cmd(data,
@@ -1592,7 +1587,7 @@ static void gp2ap020a00f_remove(struct i2c_client *client)
}
static const struct i2c_device_id gp2ap020a00f_id[] = {
- { GP2A_I2C_NAME, 0 },
+ { GP2A_I2C_NAME },
{ }
};
@@ -1609,7 +1604,7 @@ static struct i2c_driver gp2ap020a00f_driver = {
.name = GP2A_I2C_NAME,
.of_match_table = gp2ap020a00f_of_match,
},
- .probe_new = gp2ap020a00f_probe,
+ .probe = gp2ap020a00f_probe,
.remove = gp2ap020a00f_remove,
.id_table = gp2ap020a00f_id,
};
diff --git a/drivers/iio/light/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c
index 5a1a625d8d16..384572844162 100644
--- a/drivers/iio/light/hid-sensor-als.c
+++ b/drivers/iio/light/hid-sensor-als.c
@@ -14,8 +14,11 @@
#include "../common/hid-sensors/hid-sensor-trigger.h"
enum {
- CHANNEL_SCAN_INDEX_INTENSITY = 0,
- CHANNEL_SCAN_INDEX_ILLUM = 1,
+ 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
};
@@ -24,16 +27,28 @@ enum {
struct als_state {
struct hid_sensor_hub_callbacks callbacks;
struct hid_sensor_common common_attributes;
- struct hid_sensor_hub_attribute_info als_illum;
+ 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];
- u64 timestamp __aligned(8);
+ aligned_s64 timestamp;
} scan;
int scale_pre_decml;
int scale_post_decml;
int scale_precision;
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[] = {
@@ -65,6 +80,40 @@ static const struct iio_chan_spec als_channels[] = {
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)
};
@@ -86,6 +135,7 @@ static int als_read_raw(struct iio_dev *indio_dev,
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;
@@ -98,10 +148,25 @@ static int als_read_raw(struct iio_dev *indio_dev,
switch (chan->scan_index) {
case CHANNEL_SCAN_INDEX_INTENSITY:
case CHANNEL_SCAN_INDEX_ILLUM:
- report_id = als_state->als_illum.report_id;
- min = als_state->als_illum.logical_minimum;
+ 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;
@@ -110,11 +175,8 @@ static int als_read_raw(struct iio_dev *indio_dev,
hid_sensor_power_state(&als_state->common_attributes,
true);
*val = sensor_hub_input_attr_get_raw_value(
- als_state->common_attributes.hsdev,
- HID_USAGE_SENSOR_ALS, address,
- report_id,
- SENSOR_HUB_SYNC,
- min < 0);
+ hsdev, hsdev->usage, address, report_id,
+ SENSOR_HUB_SYNC, min < 0);
hid_sensor_power_state(&als_state->common_attributes,
false);
} else {
@@ -200,8 +262,9 @@ static int als_proc_event(struct hid_sensor_hub_device *hsdev,
if (!als_state->timestamp)
als_state->timestamp = iio_get_time_ns(indio_dev);
- iio_push_to_buffers_with_timestamp(indio_dev, &als_state->scan,
- als_state->timestamp);
+ iio_push_to_buffers_with_ts(indio_dev, &als_state->scan,
+ sizeof(als_state->scan),
+ als_state->timestamp);
als_state->timestamp = 0;
}
@@ -225,9 +288,22 @@ static int als_capture_sample(struct hid_sensor_hub_device *hsdev,
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;
@@ -239,29 +315,40 @@ static int als_capture_sample(struct hid_sensor_hub_device *hsdev,
/* Parse report which is specific to an usage id*/
static int als_parse_report(struct platform_device *pdev,
struct hid_sensor_hub_device *hsdev,
- struct iio_chan_spec *channels,
unsigned usage_id,
struct als_state *st)
{
- int ret;
-
- ret = sensor_hub_input_get_attribute_info(hsdev, HID_INPUT_REPORT,
- usage_id,
- HID_USAGE_SENSOR_LIGHT_ILLUM,
- &st->als_illum);
- if (ret < 0)
- return ret;
- als_adjust_channel_bit_mask(channels, CHANNEL_SCAN_INDEX_INTENSITY,
- st->als_illum.size);
- als_adjust_channel_bit_mask(channels, CHANNEL_SCAN_INDEX_ILLUM,
- st->als_illum.size);
+ 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);
+ als_adjust_channel_bit_mask(channels, index, st->als[i].size);
+ ++index;
+
+ dev_dbg(&pdev->dev, "als %x:%x\n", st->als[i].index,
+ st->als[i].report_id);
+ }
- dev_dbg(&pdev->dev, "als %x:%x\n", st->als_illum.index,
- st->als_illum.report_id);
+ st->num_channels = index;
+ /* Return success even if one usage id is present */
+ if (index)
+ ret = 0;
- st->scale_precision = hid_sensor_format_scale(
- HID_USAGE_SENSOR_ALS,
- &st->als_illum,
+ st->scale_precision = hid_sensor_format_scale(usage_id,
+ &st->als[CHANNEL_SCAN_INDEX_INTENSITY],
&st->scale_pre_decml, &st->scale_post_decml);
return ret;
@@ -270,11 +357,11 @@ static int als_parse_report(struct platform_device *pdev,
/* 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;
- struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct als_state));
if (!indio_dev)
@@ -285,7 +372,8 @@ static int hid_als_probe(struct platform_device *pdev)
als_state->common_attributes.hsdev = hsdev;
als_state->common_attributes.pdev = pdev;
- ret = hid_sensor_parse_common_attributes(hsdev, HID_USAGE_SENSOR_ALS,
+ ret = hid_sensor_parse_common_attributes(hsdev,
+ hsdev->usage,
&als_state->common_attributes,
als_sensitivity_addresses,
ARRAY_SIZE(als_sensitivity_addresses));
@@ -294,23 +382,23 @@ static int hid_als_probe(struct platform_device *pdev)
return ret;
}
- indio_dev->channels = devm_kmemdup(&pdev->dev, als_channels,
- sizeof(als_channels), GFP_KERNEL);
- if (!indio_dev->channels) {
- dev_err(&pdev->dev, "failed to duplicate channels\n");
- return -ENOMEM;
- }
-
ret = als_parse_report(pdev, hsdev,
- (struct iio_chan_spec *)indio_dev->channels,
- HID_USAGE_SENSOR_ALS, als_state);
+ hsdev->usage,
+ als_state);
if (ret) {
dev_err(&pdev->dev, "failed to setup attributes\n");
return ret;
}
- indio_dev->num_channels =
- ARRAY_SIZE(als_channels);
+ /* 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;
@@ -333,8 +421,7 @@ static int hid_als_probe(struct platform_device *pdev)
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, HID_USAGE_SENSOR_ALS,
- &als_state->callbacks);
+ ret = sensor_hub_register_callback(hsdev, hsdev->usage, &als_state->callbacks);
if (ret < 0) {
dev_err(&pdev->dev, "callback reg failed\n");
goto error_iio_unreg;
@@ -350,17 +437,15 @@ error_remove_trigger:
}
/* Function to deinitialize the processing for usage id */
-static int hid_als_remove(struct platform_device *pdev)
+static void hid_als_remove(struct platform_device *pdev)
{
- struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
+ 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);
- sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_ALS);
+ sensor_hub_remove_callback(hsdev, hsdev->usage);
iio_device_unregister(indio_dev);
hid_sensor_remove_trigger(indio_dev, &als_state->common_attributes);
-
- return 0;
}
static const struct platform_device_id hid_als_ids[] = {
@@ -368,7 +453,11 @@ static const struct platform_device_id hid_als_ids[] = {
/* Format: HID-SENSOR-usage_id_in_hex_lowercase */
.name = "HID-SENSOR-200041",
},
- { /* sentinel */ }
+ {
+ /* Format: HID-SENSOR-custom_sensor_tag-usage_id_in_hex_lowercase */
+ .name = "HID-SENSOR-LISS-0041",
+ },
+ { }
};
MODULE_DEVICE_TABLE(platform, hid_als_ids);
@@ -386,4 +475,4 @@ 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);
+MODULE_IMPORT_NS("IIO_HID");
diff --git a/drivers/iio/light/hid-sensor-prox.c b/drivers/iio/light/hid-sensor-prox.c
index f10fa2abfe72..efa904a70d0e 100644
--- a/drivers/iio/light/hid-sensor-prox.c
+++ b/drivers/iio/light/hid-sensor-prox.c
@@ -13,16 +13,32 @@
#include <linux/iio/buffer.h>
#include "../common/hid-sensors/hid-sensor-trigger.h"
-#define CHANNEL_SCAN_INDEX_PRESENCE 0
+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;
- u32 human_presence;
- int scale_pre_decml;
- int scale_post_decml;
- int scale_precision;
+ 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[] = {
@@ -30,17 +46,25 @@ static const u32 prox_sensitivity_addresses[] = {
HID_USAGE_SENSOR_DATA_PRESENCE,
};
-/* Channel definitions */
-static const struct iio_chan_spec prox_channels[] = {
- {
- .type = IIO_PROXIMITY,
- .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),
- .scan_index = CHANNEL_SCAN_INDEX_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),
};
/* Adjust channel real bits based on report descriptor */
@@ -61,7 +85,8 @@ static int prox_read_raw(struct iio_dev *indio_dev,
long mask)
{
struct prox_state *prox_state = iio_priv(indio_dev);
- int report_id = -1;
+ struct hid_sensor_hub_device *hsdev;
+ int report_id;
u32 address;
int ret_type;
s32 min;
@@ -70,41 +95,36 @@ static int prox_read_raw(struct iio_dev *indio_dev,
*val2 = 0;
switch (mask) {
case IIO_CHAN_INFO_RAW:
- switch (chan->scan_index) {
- case CHANNEL_SCAN_INDEX_PRESENCE:
- report_id = prox_state->prox_attr.report_id;
- min = prox_state->prox_attr.logical_minimum;
- address = HID_USAGE_SENSOR_HUMAN_PRESENCE;
- break;
- default:
- report_id = -1;
- break;
- }
- if (report_id >= 0) {
- hid_sensor_power_state(&prox_state->common_attributes,
- true);
- *val = sensor_hub_input_attr_get_raw_value(
- prox_state->common_attributes.hsdev,
- HID_USAGE_SENSOR_PROX, address,
- report_id,
- SENSOR_HUB_SYNC,
- min < 0);
- hid_sensor_power_state(&prox_state->common_attributes,
- false);
- } else {
- *val = 0;
+ 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:
- *val = prox_state->scale_pre_decml;
- *val2 = prox_state->scale_post_decml;
- ret_type = prox_state->scale_precision;
+ 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 = hid_sensor_convert_exponent(
- prox_state->prox_attr.unit_expo);
+ *val = 0;
ret_type = IIO_VAL_INT;
break;
case IIO_CHAN_INFO_SAMP_FREQ:
@@ -154,14 +174,6 @@ static const struct iio_info prox_info = {
.write_raw = &prox_write_raw,
};
-/* Function to push data to buffer */
-static void hid_sensor_push_data(struct iio_dev *indio_dev, const void *data,
- int len)
-{
- dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n");
- iio_push_to_buffers(indio_dev, data);
-}
-
/* Callback handler to send event after all samples are received and captured */
static int prox_proc_event(struct hid_sensor_hub_device *hsdev,
unsigned usage_id,
@@ -171,10 +183,10 @@ static int prox_proc_event(struct hid_sensor_hub_device *hsdev,
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))
- hid_sensor_push_data(indio_dev,
- &prox_state->human_presence,
- sizeof(prox_state->human_presence));
+ 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;
}
@@ -187,52 +199,84 @@ static int prox_capture_sample(struct hid_sensor_hub_device *hsdev,
{
struct iio_dev *indio_dev = platform_get_drvdata(priv);
struct prox_state *prox_state = iio_priv(indio_dev);
- int ret = -EINVAL;
+ int multiplier = 1;
+ int chan;
- switch (usage_id) {
- case HID_USAGE_SENSOR_HUMAN_PRESENCE:
- prox_state->human_presence = *(u32 *)raw_data;
- ret = 0;
- break;
- default:
- break;
+ 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 ret;
+ 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 iio_chan_spec *channels,
- unsigned usage_id,
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;
+ prox_adjust_channel_bit_mask(channels, index,
+ st->prox_attr[index].size);
+ 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++;
+ }
- ret = sensor_hub_input_get_attribute_info(hsdev, HID_INPUT_REPORT,
- usage_id,
- HID_USAGE_SENSOR_HUMAN_PRESENCE,
- &st->prox_attr);
- if (ret < 0)
+ if (!index)
return ret;
- prox_adjust_channel_bit_mask(channels, CHANNEL_SCAN_INDEX_PRESENCE,
- st->prox_attr.size);
- dev_dbg(&pdev->dev, "prox %x:%x\n", st->prox_attr.index,
- st->prox_attr.report_id);
+ st->num_channels = index;
- return ret;
+ 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;
- struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
indio_dev = devm_iio_device_alloc(&pdev->dev,
sizeof(struct prox_state));
@@ -244,7 +288,7 @@ static int hid_prox_probe(struct platform_device *pdev)
prox_state->common_attributes.hsdev = hsdev;
prox_state->common_attributes.pdev = pdev;
- ret = hid_sensor_parse_common_attributes(hsdev, HID_USAGE_SENSOR_PROX,
+ ret = hid_sensor_parse_common_attributes(hsdev, hsdev->usage,
&prox_state->common_attributes,
prox_sensitivity_addresses,
ARRAY_SIZE(prox_sensitivity_addresses));
@@ -253,22 +297,15 @@ static int hid_prox_probe(struct platform_device *pdev)
return ret;
}
- indio_dev->channels = devm_kmemdup(&pdev->dev, prox_channels,
- sizeof(prox_channels), GFP_KERNEL);
- if (!indio_dev->channels) {
- dev_err(&pdev->dev, "failed to duplicate channels\n");
- return -ENOMEM;
- }
-
- ret = prox_parse_report(pdev, hsdev,
- (struct iio_chan_spec *)indio_dev->channels,
- HID_USAGE_SENSOR_PROX, prox_state);
+ 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 = ARRAY_SIZE(prox_channels);
+ 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;
@@ -291,8 +328,8 @@ static int hid_prox_probe(struct platform_device *pdev)
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, HID_USAGE_SENSOR_PROX,
- &prox_state->callbacks);
+ ret = sensor_hub_register_callback(hsdev, hsdev->usage,
+ &prox_state->callbacks);
if (ret < 0) {
dev_err(&pdev->dev, "callback reg failed\n");
goto error_iio_unreg;
@@ -308,17 +345,15 @@ error_remove_trigger:
}
/* Function to deinitialize the processing for usage id */
-static int hid_prox_remove(struct platform_device *pdev)
+static void hid_prox_remove(struct platform_device *pdev)
{
- struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
+ 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);
- sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_PROX);
+ sensor_hub_remove_callback(hsdev, hsdev->usage);
iio_device_unregister(indio_dev);
hid_sensor_remove_trigger(indio_dev, &prox_state->common_attributes);
-
- return 0;
}
static const struct platform_device_id hid_prox_ids[] = {
@@ -326,7 +361,11 @@ static const struct platform_device_id hid_prox_ids[] = {
/* Format: HID-SENSOR-usage_id_in_hex_lowercase */
.name = "HID-SENSOR-200011",
},
- { /* sentinel */ }
+ {
+ /* Format: HID-SENSOR-tag-usage_id_in_hex_lowercase */
+ .name = "HID-SENSOR-LISS-0226",
+ },
+ { }
};
MODULE_DEVICE_TABLE(platform, hid_prox_ids);
@@ -344,4 +383,4 @@ 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);
+MODULE_IMPORT_NS("IIO_HID");
diff --git a/drivers/iio/light/iqs621-als.c b/drivers/iio/light/iqs621-als.c
index 004ea890a4b2..b9f230210f07 100644
--- a/drivers/iio/light/iqs621-als.c
+++ b/drivers/iio/light/iqs621-als.c
@@ -86,8 +86,8 @@ static int iqs621_als_init(struct iqs621_als_private *iqs621_als)
if (iqs621_als->prox_en)
event_mask |= iqs62x->dev_desc->ir_mask;
- return regmap_update_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK,
- event_mask, 0);
+ return regmap_clear_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK,
+ event_mask);
}
static int iqs621_als_notifier(struct notifier_block *notifier,
@@ -271,7 +271,7 @@ 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,
- int state)
+ bool state)
{
struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);
struct iqs62x_core *iqs62x = iqs621_als->iqs62x;
diff --git a/drivers/iio/light/isl29018.c b/drivers/iio/light/isl29018.c
index 141845fb47f9..1b4c18423048 100644
--- a/drivers/iio/light/isl29018.c
+++ b/drivers/iio/light/isl29018.c
@@ -8,17 +8,18 @@
* Copyright (c) 2010, NVIDIA Corporation.
*/
-#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/err.h>
+#include <linux/mod_devicetable.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>
-#include <linux/acpi.h>
#define ISL29018_CONV_TIME_MS 100
@@ -550,9 +551,9 @@ static int isl29018_chip_init(struct isl29018_chip *chip)
return -ENODEV;
/* Clear brownout bit */
- status = regmap_update_bits(chip->regmap,
- ISL29035_REG_DEVICE_ID,
- ISL29035_BOUT_MASK, 0);
+ status = regmap_clear_bits(chip->regmap,
+ ISL29035_REG_DEVICE_ID,
+ ISL29035_BOUT_MASK);
if (status < 0)
return status;
}
@@ -687,20 +688,6 @@ static const struct isl29018_chip_info isl29018_chip_info_tbl[] = {
},
};
-static const char *isl29018_match_acpi_device(struct device *dev, int *data)
-{
- const struct acpi_device_id *id;
-
- id = acpi_match_device(dev->driver->acpi_match_table, dev);
-
- if (!id)
- return NULL;
-
- *data = (int)id->driver_data;
-
- return dev_name(dev);
-}
-
static void isl29018_disable_regulator_action(void *_data)
{
struct isl29018_chip *chip = _data;
@@ -716,9 +703,10 @@ 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;
- const char *name = NULL;
- int dev_id = 0;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip));
if (!indio_dev)
@@ -731,11 +719,11 @@ static int isl29018_probe(struct i2c_client *client)
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;
}
- if (ACPI_HANDLE(&client->dev))
- name = isl29018_match_acpi_device(&client->dev, &dev_id);
-
mutex_init(&chip->lock);
chip->type = dev_id;
@@ -832,21 +820,19 @@ static int isl29018_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(isl29018_pm_ops, isl29018_suspend,
isl29018_resume);
-#ifdef CONFIG_ACPI
static const struct acpi_device_id isl29018_acpi_match[] = {
{"ISL29018", isl29018},
{"ISL29023", isl29023},
{"ISL29035", isl29035},
- {},
+ { }
};
MODULE_DEVICE_TABLE(acpi, isl29018_acpi_match);
-#endif
static const struct i2c_device_id isl29018_id[] = {
{"isl29018", isl29018},
{"isl29023", isl29023},
{"isl29035", isl29035},
- {}
+ { }
};
MODULE_DEVICE_TABLE(i2c, isl29018_id);
@@ -854,18 +840,18 @@ 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 = ACPI_PTR(isl29018_acpi_match),
+ .acpi_match_table = isl29018_acpi_match,
.pm = pm_sleep_ptr(&isl29018_pm_ops),
.of_match_table = isl29018_of_match,
},
- .probe_new = isl29018_probe,
+ .probe = isl29018_probe,
.id_table = isl29018_id,
};
module_i2c_driver(isl29018_driver);
diff --git a/drivers/iio/light/isl29028.c b/drivers/iio/light/isl29028.c
index bcf3a556e41a..374bccad9119 100644
--- a/drivers/iio/light/isl29028.c
+++ b/drivers/iio/light/isl29028.c
@@ -336,16 +336,11 @@ static int isl29028_ir_get(struct isl29028_chip *chip, int *ir_data)
static int isl29028_set_pm_runtime_busy(struct isl29028_chip *chip, bool on)
{
struct device *dev = regmap_get_device(chip->regmap);
- int ret;
- if (on) {
- ret = pm_runtime_resume_and_get(dev);
- } else {
- pm_runtime_mark_last_busy(dev);
- ret = pm_runtime_put_autosuspend(dev);
- }
+ if (on)
+ return pm_runtime_resume_and_get(dev);
- return ret;
+ return pm_runtime_put_autosuspend(dev);
}
/* Channel IO */
@@ -562,7 +557,7 @@ static const struct regmap_config isl29028_regmap_config = {
.volatile_reg = isl29028_is_volatile_reg,
.max_register = ISL29028_NUM_REGS - 1,
.num_reg_defaults_raw = ISL29028_NUM_REGS,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
};
static int isl29028_probe(struct i2c_client *client)
@@ -678,9 +673,9 @@ static DEFINE_RUNTIME_DEV_PM_OPS(isl29028_pm_ops, isl29028_suspend,
isl29028_resume, NULL);
static const struct i2c_device_id isl29028_id[] = {
- {"isl29028", 0},
- {"isl29030", 0},
- {}
+ { "isl29028" },
+ { "isl29030" },
+ { }
};
MODULE_DEVICE_TABLE(i2c, isl29028_id);
@@ -688,7 +683,7 @@ 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);
@@ -698,7 +693,7 @@ static struct i2c_driver isl29028_driver = {
.pm = pm_ptr(&isl29028_pm_ops),
.of_match_table = isl29028_of_match,
},
- .probe_new = isl29028_probe,
+ .probe = isl29028_probe,
.remove = isl29028_remove,
.id_table = isl29028_id,
};
diff --git a/drivers/iio/light/isl29125.c b/drivers/iio/light/isl29125.c
index b4bd656ca169..3acb8a4f1d12 100644
--- a/drivers/iio/light/isl29125.c
+++ b/drivers/iio/light/isl29125.c
@@ -51,11 +51,6 @@
struct isl29125_data {
struct i2c_client *client;
u8 conf1;
- /* Ensure timestamp is naturally aligned */
- struct {
- u16 chans[3];
- s64 timestamp __aligned(8);
- } scan;
};
#define ISL29125_CHANNEL(_color, _si) { \
@@ -131,11 +126,10 @@ static int isl29125_read_raw(struct iio_dev *indio_dev,
switch (mask) {
case IIO_CHAN_INFO_RAW:
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret)
- return ret;
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
ret = isl29125_read_data(data, chan->scan_index);
- iio_device_release_direct_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
if (ret < 0)
return ret;
*val = ret;
@@ -180,18 +174,22 @@ static irqreturn_t isl29125_trigger_handler(int irq, void *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 = { };
- for_each_set_bit(i, indio_dev->active_scan_mask,
- indio_dev->masklength) {
+ 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;
- data->scan.chans[j++] = ret;
+ scan.chans[j++] = ret;
}
- iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
iio_get_time_ns(indio_dev));
done:
@@ -327,7 +325,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(isl29125_pm_ops, isl29125_suspend,
isl29125_resume);
static const struct i2c_device_id isl29125_id[] = {
- { "isl29125", 0 },
+ { "isl29125" },
{ }
};
MODULE_DEVICE_TABLE(i2c, isl29125_id);
@@ -337,7 +335,7 @@ static struct i2c_driver isl29125_driver = {
.name = ISL29125_DRV_NAME,
.pm = pm_sleep_ptr(&isl29125_pm_ops),
},
- .probe_new = isl29125_probe,
+ .probe = isl29125_probe,
.remove = isl29125_remove,
.id_table = isl29125_id,
};
diff --git a/drivers/iio/light/isl76682.c b/drivers/iio/light/isl76682.c
new file mode 100644
index 000000000000..b6f2fc9978f6
--- /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[] = {
+ { "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
index d3834d0a0635..6978d02a4df5 100644
--- a/drivers/iio/light/jsa1212.c
+++ b/drivers/iio/light/jsa1212.c
@@ -12,10 +12,10 @@
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
+#include <linux/mod_devicetable.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
-#include <linux/acpi.h>
#include <linux/regmap.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
@@ -106,7 +106,6 @@
#define JSA1212_PXS_DELAY_MS 100
#define JSA1212_DRIVER_NAME "jsa1212"
-#define JSA1212_REGMAP_NAME "jsa1212_regmap"
enum jsa1212_op_mode {
JSA1212_OPMODE_ALS_EN,
@@ -300,7 +299,7 @@ static bool jsa1212_is_volatile_reg(struct device *dev, unsigned int reg)
}
static const struct regmap_config jsa1212_regmap_config = {
- .name = JSA1212_REGMAP_NAME,
+ .name = "jsa1212_regmap",
.reg_bits = 8,
.val_bits = 8,
.max_register = JSA1212_MAX_REG,
@@ -424,12 +423,12 @@ static DEFINE_SIMPLE_DEV_PM_OPS(jsa1212_pm_ops, jsa1212_suspend,
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[] = {
- { JSA1212_DRIVER_NAME, 0 },
+ { JSA1212_DRIVER_NAME },
{ }
};
MODULE_DEVICE_TABLE(i2c, jsa1212_id);
@@ -438,9 +437,9 @@ static struct i2c_driver jsa1212_driver = {
.driver = {
.name = JSA1212_DRIVER_NAME,
.pm = pm_sleep_ptr(&jsa1212_pm_ops),
- .acpi_match_table = ACPI_PTR(jsa1212_acpi_match),
+ .acpi_match_table = jsa1212_acpi_match,
},
- .probe_new = jsa1212_probe,
+ .probe = jsa1212_probe,
.remove = jsa1212_remove,
.id_table = jsa1212_id,
};
diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c
index 827bc25269e9..99f0b903018c 100644
--- a/drivers/iio/light/lm3533-als.c
+++ b/drivers/iio/light/lm3533-als.c
@@ -754,7 +754,7 @@ static int lm3533_als_set_resistor(struct lm3533_als *als, u8 val)
}
static int lm3533_als_setup(struct lm3533_als *als,
- struct lm3533_als_platform_data *pdata)
+ const struct lm3533_als_platform_data *pdata)
{
int ret;
@@ -828,8 +828,8 @@ static const struct iio_info lm3533_als_info = {
static int lm3533_als_probe(struct platform_device *pdev)
{
+ const struct lm3533_als_platform_data *pdata;
struct lm3533 *lm3533;
- struct lm3533_als_platform_data *pdata;
struct lm3533_als *als;
struct iio_dev *indio_dev;
int ret;
@@ -838,7 +838,7 @@ static int lm3533_als_probe(struct platform_device *pdev)
if (!lm3533)
return -EINVAL;
- pdata = pdev->dev.platform_data;
+ pdata = dev_get_platdata(&pdev->dev);
if (!pdata) {
dev_err(&pdev->dev, "no platform data\n");
return -EINVAL;
@@ -895,7 +895,7 @@ err_free_irq:
return ret;
}
-static int lm3533_als_remove(struct platform_device *pdev)
+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);
@@ -905,8 +905,6 @@ static int lm3533_als_remove(struct platform_device *pdev)
lm3533_als_disable(als);
if (als->irq)
free_irq(als->irq, indio_dev);
-
- return 0;
}
static struct platform_driver lm3533_als_driver = {
diff --git a/drivers/iio/light/ltr390.c b/drivers/iio/light/ltr390.c
new file mode 100644
index 000000000000..fc387426fa87
--- /dev/null
+++ b/drivers/iio/light/ltr390.c
@@ -0,0 +1,916 @@
+// 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 simulataneous 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 = &ltr390_readable_reg_table,
+ .wr_table = &ltr390_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, &ltr390_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 = &ltr390_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 dev_err_probe(dev, ret,
+ "request irq (%d) failed\n", client->irq);
+ }
+
+ 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[] = {
+ { "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(&ltr390_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
index bdbd918213e4..022e0693983b 100644
--- a/drivers/iio/light/ltr501.c
+++ b/drivers/iio/light/ltr501.c
@@ -10,11 +10,11 @@
*/
#include <linux/module.h>
+#include <linux/mod_devicetable.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/regmap.h>
-#include <linux/acpi.h>
#include <linux/regulator/consumer.h>
#include <linux/iio/iio.h>
@@ -24,8 +24,6 @@
#include <linux/iio/buffer.h>
#include <linux/iio/triggered_buffer.h>
-#define LTR501_DRV_NAME "ltr501"
-
#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*/
@@ -65,8 +63,6 @@
#define LTR501_ALS_DEF_PERIOD 500000
#define LTR501_PS_DEF_PERIOD 100000
-#define LTR501_REGMAP_NAME "ltr501_regmap"
-
#define LTR501_LUX_CONV(vis_coeff, vis_data, ir_coeff, ir_data) \
((vis_coeff * vis_data) - (ir_coeff * ir_data))
@@ -541,7 +537,7 @@ static const struct iio_chan_spec_ext_info ltr501_ext_info[] = {
.shared = IIO_SEPARATE,
.read = ltr501_read_near_level,
},
- { /* sentinel */ }
+ { }
};
static const struct iio_event_spec ltr501_als_event_spec[] = {
@@ -646,6 +642,36 @@ static const struct iio_chan_spec ltr301_channels[] = {
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)
@@ -658,14 +684,13 @@ static int ltr501_read_raw(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_PROCESSED:
switch (chan->type) {
case IIO_LIGHT:
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret)
- return ret;
+ 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_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
if (ret < 0)
return ret;
*val = ltr501_calculate_lux(le16_to_cpu(buf[1]),
@@ -675,36 +700,12 @@ static int ltr501_read_raw(struct iio_dev *indio_dev,
return -EINVAL;
}
case IIO_CHAN_INFO_RAW:
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret)
- return ret;
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
- 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)
- break;
- *val = le16_to_cpu(chan->address == LTR501_ALS_DATA1 ?
- buf[0] : buf[1]);
- ret = IIO_VAL_INT;
- break;
- case IIO_PROXIMITY:
- mutex_lock(&data->lock_ps);
- ret = ltr501_read_ps(data);
- mutex_unlock(&data->lock_ps);
- if (ret < 0)
- break;
- *val = ret & LTR501_PS_DATA_MASK;
- ret = IIO_VAL_INT;
- break;
- default:
- ret = -EINVAL;
- break;
- }
+ ret = ltr501_read_info_raw(data, chan, val);
- iio_device_release_direct_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
return ret;
case IIO_CHAN_INFO_SCALE:
@@ -756,18 +757,14 @@ static int ltr501_get_gain_index(const struct ltr501_gain *gain, int size,
return -1;
}
-static int ltr501_write_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int val, int val2, long mask)
+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;
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret)
- return ret;
-
switch (mask) {
case IIO_CHAN_INFO_SCALE:
switch (chan->type) {
@@ -775,53 +772,43 @@ static int ltr501_write_raw(struct iio_dev *indio_dev,
i = ltr501_get_gain_index(info->als_gain,
info->als_gain_tbl_size,
val, val2);
- if (i < 0) {
- ret = -EINVAL;
- break;
- }
+ if (i < 0)
+ return -EINVAL;
data->als_contr &= ~info->als_gain_mask;
data->als_contr |= i << info->als_gain_shift;
- ret = regmap_write(data->regmap, LTR501_ALS_CONTR,
- data->als_contr);
- break;
+ 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) {
- ret = -EINVAL;
- break;
- }
+ if (i < 0)
+ return -EINVAL;
+
data->ps_contr &= ~LTR501_CONTR_PS_GAIN_MASK;
data->ps_contr |= i << LTR501_CONTR_PS_GAIN_SHIFT;
- ret = regmap_write(data->regmap, LTR501_PS_CONTR,
- data->ps_contr);
- break;
+ return regmap_write(data->regmap, LTR501_PS_CONTR,
+ data->ps_contr);
default:
- ret = -EINVAL;
- break;
+ return -EINVAL;
}
- break;
case IIO_CHAN_INFO_INT_TIME:
switch (chan->type) {
case IIO_INTENSITY:
- if (val != 0) {
- ret = -EINVAL;
- break;
- }
+ if (val != 0)
+ return -EINVAL;
+
mutex_lock(&data->lock_als);
ret = ltr501_set_it_time(data, val2);
mutex_unlock(&data->lock_als);
- break;
+ return ret;
default:
- ret = -EINVAL;
- break;
+ return -EINVAL;
}
- break;
case IIO_CHAN_INFO_SAMP_FREQ:
switch (chan->type) {
@@ -829,50 +816,61 @@ static int ltr501_write_raw(struct iio_dev *indio_dev,
ret = ltr501_als_read_samp_freq(data, &freq_val,
&freq_val2);
if (ret < 0)
- break;
+ return ret;
ret = ltr501_als_write_samp_freq(data, val, val2);
if (ret < 0)
- break;
+ return ret;
/* update persistence count when changing frequency */
ret = ltr501_write_intr_prst(data, chan->type,
0, data->als_period);
if (ret < 0)
- ret = ltr501_als_write_samp_freq(data, freq_val,
- freq_val2);
- break;
+ /* 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)
- break;
+ return ret;
ret = ltr501_ps_write_samp_freq(data, val, val2);
if (ret < 0)
- break;
+ return ret;
/* update persistence count when changing frequency */
ret = ltr501_write_intr_prst(data, chan->type,
0, data->ps_period);
if (ret < 0)
- ret = ltr501_ps_write_samp_freq(data, freq_val,
- freq_val2);
- break;
+ /* Do not overwrite error */
+ ltr501_ps_write_samp_freq(data, freq_val,
+ freq_val2);
+ return ret;
default:
- ret = -EINVAL;
- break;
+ return -EINVAL;
}
- break;
-
default:
- ret = -EINVAL;
- break;
+ 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);
- iio_device_release_direct_mode(indio_dev);
return ret;
}
@@ -1077,15 +1075,11 @@ static int ltr501_read_event_config(struct iio_dev *indio_dev,
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, int state)
+ enum iio_event_direction dir, bool state)
{
struct ltr501_data *data = iio_priv(indio_dev);
int ret;
- /* only 1 and 0 are valid inputs */
- if (state != 1 && state != 0)
- return -EINVAL;
-
switch (chan->type) {
case IIO_INTENSITY:
mutex_lock(&data->lock_als);
@@ -1284,15 +1278,13 @@ static irqreturn_t ltr501_trigger_handler(int irq, void *p)
struct ltr501_data *data = iio_priv(indio_dev);
struct {
u16 channels[3];
- s64 ts __aligned(8);
- } scan;
+ aligned_s64 ts;
+ } scan = { };
__le16 als_buf[2];
u8 mask = 0;
int j = 0;
int ret, psdata;
- memset(&scan, 0, sizeof(scan));
-
/* 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))
@@ -1323,8 +1315,8 @@ static irqreturn_t ltr501_trigger_handler(int irq, void *p)
scan.channels[j++] = psdata & LTR501_PS_DATA_MASK;
}
- iio_push_to_buffers_with_timestamp(indio_dev, &scan,
- iio_get_time_ns(indio_dev));
+ 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);
@@ -1406,11 +1398,11 @@ static bool ltr501_is_volatile_reg(struct device *dev, unsigned int reg)
}
static const struct regmap_config ltr501_regmap_config = {
- .name = LTR501_REGMAP_NAME,
+ .name = "ltr501_regmap",
.reg_bits = 8,
.val_bits = 8,
.max_register = LTR501_MAX_REG,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = ltr501_is_volatile_reg,
};
@@ -1421,17 +1413,6 @@ static int ltr501_powerdown(struct ltr501_data *data)
data->ps_contr & ~LTR501_CONTR_ACTIVE);
}
-static const char *ltr501_match_acpi_device(struct device *dev, int *chip_idx)
-{
- const struct acpi_device_id *id;
-
- id = acpi_match_device(dev->driver->acpi_match_table, dev);
- if (!id)
- return NULL;
- *chip_idx = id->driver_data;
- return dev_name(dev);
-}
-
static int ltr501_probe(struct i2c_client *client)
{
const struct i2c_device_id *id = i2c_client_get_device_id(client);
@@ -1439,8 +1420,10 @@ static int ltr501_probe(struct i2c_client *client)
struct ltr501_data *data;
struct iio_dev *indio_dev;
struct regmap *regmap;
- int ret, partid, chip_idx = 0;
- const char *name = NULL;
+ 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)
@@ -1522,11 +1505,12 @@ static int ltr501_probe(struct i2c_client *client)
if (id) {
name = id->name;
chip_idx = id->driver_data;
- } else if (ACPI_HANDLE(&client->dev)) {
- name = ltr501_match_acpi_device(&client->dev, &chip_idx);
} else {
- return -ENODEV;
+ name = iio_get_acpi_device_name_and_data(&client->dev, &ddata);
+ chip_idx = (intptr_t)ddata;
}
+ if (!name)
+ return -ENODEV;
data->chip_info = &ltr501_chip_info_tbl[chip_idx];
@@ -1609,10 +1593,10 @@ static int ltr501_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(ltr501_pm_ops, ltr501_suspend, ltr501_resume);
static const struct acpi_device_id ltr_acpi_match[] = {
- { "LTER0501", ltr501 },
- { "LTER0559", ltr559 },
{ "LTER0301", ltr301 },
- { },
+ /* https://www.catalog.update.microsoft.com/Search.aspx?q=lter0303 */
+ { "LTER0303", ltr303 },
+ { }
};
MODULE_DEVICE_TABLE(acpi, ltr_acpi_match);
@@ -1630,18 +1614,18 @@ static const struct of_device_id ltr501_of_match[] = {
{ .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_DRV_NAME,
+ .name = "ltr501",
.of_match_table = ltr501_of_match,
.pm = pm_sleep_ptr(&ltr501_pm_ops),
- .acpi_match_table = ACPI_PTR(ltr_acpi_match),
+ .acpi_match_table = ltr_acpi_match,
},
- .probe_new = ltr501_probe,
+ .probe = ltr501_probe,
.remove = ltr501_remove,
.id_table = ltr501_id,
};
diff --git a/drivers/iio/light/ltrf216a.c b/drivers/iio/light/ltrf216a.c
index 4b8ef36b6912..5f27f754fe1c 100644
--- a/drivers/iio/light/ltrf216a.c
+++ b/drivers/iio/light/ltrf216a.c
@@ -26,7 +26,7 @@
#include <linux/iio/iio.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#define LTRF216A_ALS_RESET_MASK BIT(4)
#define LTRF216A_ALS_DATA_STATUS BIT(3)
@@ -68,6 +68,13 @@ static const int ltrf216a_int_time_reg[][2] = {
{ 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
@@ -79,6 +86,7 @@ static const int ltrf216a_int_time_reg[][2] = {
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;
@@ -200,7 +208,6 @@ static int ltrf216a_set_power_state(struct ltrf216a_data *data, bool on)
return ret;
}
} else {
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
}
@@ -234,7 +241,7 @@ static int ltrf216a_read_data(struct ltrf216a_data *data, u8 addr)
static int ltrf216a_get_lux(struct ltrf216a_data *data)
{
int ret, greendata;
- u64 lux, div;
+ u64 lux;
ret = ltrf216a_set_power_state(data, true);
if (ret)
@@ -246,10 +253,9 @@ static int ltrf216a_get_lux(struct ltrf216a_data *data)
ltrf216a_set_power_state(data, false);
- lux = greendata * 45 * LTRF216A_WIN_FAC * 100;
- div = data->als_gain_fac * data->int_time_fac * 100;
+ lux = greendata * data->info->lux_multiplier * LTRF216A_WIN_FAC;
- return div_u64(lux, div);
+ return lux;
}
static int ltrf216a_read_raw(struct iio_dev *indio_dev,
@@ -279,7 +285,8 @@ static int ltrf216a_read_raw(struct iio_dev *indio_dev,
if (ret < 0)
return ret;
*val = ret;
- return IIO_VAL_INT;
+ *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);
@@ -334,15 +341,15 @@ static const struct iio_info ltrf216a_info = {
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_CLEAR_DATA_0:
- case LTRF216A_ALS_CLEAR_DATA_1:
- case LTRF216A_ALS_CLEAR_DATA_2:
case LTRF216A_ALS_DATA_0:
case LTRF216A_ALS_DATA_1:
case LTRF216A_ALS_DATA_2:
@@ -355,6 +362,10 @@ static bool ltrf216a_readable_reg(struct device *dev, unsigned int reg)
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;
}
@@ -382,15 +393,23 @@ static bool ltrf216a_writable_reg(struct device *dev, unsigned int reg)
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_CLEAR_DATA_0:
- case LTRF216A_ALS_CLEAR_DATA_1:
- case LTRF216A_ALS_CLEAR_DATA_2:
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;
}
@@ -433,6 +452,7 @@ static int ltrf216a_probe(struct i2c_client *client)
i2c_set_clientdata(client, indio_dev);
data->client = client;
+ data->info = i2c_get_match_data(client);
mutex_init(&data->lock);
@@ -520,16 +540,29 @@ cache_only:
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[] = {
- { "ltrf216a" },
- {}
+ { "ltr308", .driver_data = (kernel_ulong_t)&ltr308_chip_info },
+ { "ltrf216a", .driver_data = (kernel_ulong_t)&ltrf216a_chip_info },
+ { }
};
MODULE_DEVICE_TABLE(i2c, ltrf216a_id);
static const struct of_device_id ltrf216a_of_match[] = {
- { .compatible = "liteon,ltrf216a" },
- { .compatible = "ltr,ltrf216a" },
- {}
+ { .compatible = "liteon,ltr308", .data = &ltr308_chip_info },
+ { .compatible = "liteon,ltrf216a", .data = &ltrf216a_chip_info },
+ /* For Valve's Steamdeck device, an ACPI platform using PRP0001 */
+ { .compatible = "ltr,ltrf216a", .data = &ltrf216a_chip_info },
+ { }
};
MODULE_DEVICE_TABLE(of, ltrf216a_of_match);
@@ -539,7 +572,7 @@ static struct i2c_driver ltrf216a_driver = {
.pm = pm_ptr(&ltrf216a_pm_ops),
.of_match_table = ltrf216a_of_match,
},
- .probe_new = ltrf216a_probe,
+ .probe = ltrf216a_probe,
.id_table = ltrf216a_id,
};
module_i2c_driver(ltrf216a_driver);
diff --git a/drivers/iio/light/lv0104cs.c b/drivers/iio/light/lv0104cs.c
index c041fa0faa5d..916109ec3217 100644
--- a/drivers/iio/light/lv0104cs.c
+++ b/drivers/iio/light/lv0104cs.c
@@ -510,7 +510,7 @@ static int lv0104cs_probe(struct i2c_client *client)
}
static const struct i2c_device_id lv0104cs_id[] = {
- { "lv0104cs", 0 },
+ { "lv0104cs" },
{ }
};
MODULE_DEVICE_TABLE(i2c, lv0104cs_id);
@@ -520,7 +520,7 @@ static struct i2c_driver lv0104cs_i2c_driver = {
.name = "lv0104cs",
},
.id_table = lv0104cs_id,
- .probe_new = lv0104cs_probe,
+ .probe = lv0104cs_probe,
};
module_i2c_driver(lv0104cs_i2c_driver);
diff --git a/drivers/iio/light/max44000.c b/drivers/iio/light/max44000.c
index 5dcabc43a30e..039d45af3a7f 100644
--- a/drivers/iio/light/max44000.c
+++ b/drivers/iio/light/max44000.c
@@ -10,6 +10,7 @@
*/
#include <linux/module.h>
+#include <linux/mod_devicetable.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
@@ -19,7 +20,6 @@
#include <linux/iio/buffer.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
-#include <linux/acpi.h>
#define MAX44000_DRV_NAME "max44000"
@@ -75,11 +75,6 @@
struct max44000_data {
struct mutex lock;
struct regmap *regmap;
- /* Ensure naturally aligned timestamp */
- struct {
- u16 channels[2];
- s64 ts __aligned(8);
- } scan;
};
/* Default scale is set to the minimum of 0.03125 or 1 / (1 << 5) lux */
@@ -496,24 +491,29 @@ static irqreturn_t max44000_trigger_handler(int irq, void *p)
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;
- data->scan.channels[index++] = ret;
+ 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, &regval);
if (ret < 0)
goto out_unlock;
- data->scan.channels[index] = regval;
+ scan.channels[index] = regval;
}
mutex_unlock(&data->lock);
- iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
- iio_get_time_ns(indio_dev));
+ 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;
@@ -598,25 +598,23 @@ static int max44000_probe(struct i2c_client *client)
}
static const struct i2c_device_id max44000_id[] = {
- {"max44000", 0},
+ { "max44000" },
{ }
};
MODULE_DEVICE_TABLE(i2c, max44000_id);
-#ifdef CONFIG_ACPI
static const struct acpi_device_id max44000_acpi_match[] = {
{"MAX44000", 0},
{ }
};
MODULE_DEVICE_TABLE(acpi, max44000_acpi_match);
-#endif
static struct i2c_driver max44000_driver = {
.driver = {
.name = MAX44000_DRV_NAME,
- .acpi_match_table = ACPI_PTR(max44000_acpi_match),
+ .acpi_match_table = max44000_acpi_match,
},
- .probe_new = max44000_probe,
+ .probe = max44000_probe,
.id_table = max44000_id,
};
diff --git a/drivers/iio/light/max44009.c b/drivers/iio/light/max44009.c
index 801e5a0ad496..8cd7f5664e5b 100644
--- a/drivers/iio/light/max44009.c
+++ b/drivers/iio/light/max44009.c
@@ -422,7 +422,7 @@ 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,
- int state)
+ bool state)
{
struct max44009_data *data = iio_priv(indio_dev);
int ret;
@@ -487,8 +487,7 @@ static irqreturn_t max44009_threaded_irq_handler(int irq, void *p)
return IRQ_NONE;
}
-static int max44009_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+static int max44009_probe(struct i2c_client *client)
{
struct max44009_data *data;
struct iio_dev *indio_dev;
@@ -528,8 +527,14 @@ static int max44009_probe(struct i2c_client *client,
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[] = {
- { "max44009", 0 },
+ { "max44009" },
{ }
};
MODULE_DEVICE_TABLE(i2c, max44009_id);
@@ -537,18 +542,13 @@ 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);
-static const struct of_device_id max44009_of_match[] = {
- { .compatible = "maxim,max44009" },
- { }
-};
-MODULE_DEVICE_TABLE(of, max44009_of_match);
-
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
index eaf548d4649e..25f63da70297 100644
--- a/drivers/iio/light/noa1305.c
+++ b/drivers/iio/light/noa1305.c
@@ -29,6 +29,7 @@
#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
@@ -43,12 +44,34 @@
#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)
+static int noa1305_measure(struct noa1305_priv *priv, int *val)
{
__le16 data;
int ret;
@@ -58,7 +81,9 @@ static int noa1305_measure(struct noa1305_priv *priv)
if (ret < 0)
return ret;
- return le16_to_cpu(data);
+ *val = le16_to_cpu(data);
+
+ return IIO_VAL_INT;
}
static int noa1305_scale(struct noa1305_priv *priv, int *val, int *val2)
@@ -76,91 +101,113 @@ static int noa1305_scale(struct noa1305_priv *priv, int *val, int *val2)
* Integration Constant = 7.7
* Integration Time in Seconds
*/
- switch (data) {
- case NOA1305_INTEGR_TIME_800MS:
- *val = 100;
- *val2 = 77 * 8;
- break;
- case NOA1305_INTEGR_TIME_400MS:
- *val = 100;
- *val2 = 77 * 4;
- break;
- case NOA1305_INTEGR_TIME_200MS:
- *val = 100;
- *val2 = 77 * 2;
- break;
- case NOA1305_INTEGR_TIME_100MS:
- *val = 100;
- *val2 = 77;
- break;
- case NOA1305_INTEGR_TIME_50MS:
- *val = 1000;
- *val2 = 77 * 5;
- break;
- case NOA1305_INTEGR_TIME_25MS:
- *val = 10000;
- *val2 = 77 * 25;
- break;
- case NOA1305_INTEGR_TIME_12_5MS:
- *val = 100000;
- *val2 = 77 * 125;
- break;
- case NOA1305_INTEGR_TIME_6_25MS:
- *val = 1000000;
- *val2 = 77 * 625;
- break;
- default:
- return -EINVAL;
- }
+ 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 iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
{
- int ret = -EINVAL;
struct noa1305_priv *priv = iio_priv(indio_dev);
+ if (chan->type != IIO_LIGHT)
+ return -EINVAL;
+
switch (mask) {
case IIO_CHAN_INFO_RAW:
- switch (chan->type) {
- case IIO_LIGHT:
- ret = noa1305_measure(priv);
- if (ret < 0)
- return ret;
- *val = ret;
- return IIO_VAL_INT;
- default:
- break;
- }
- break;
+ return noa1305_measure(priv, val);
case IIO_CHAN_INFO_SCALE:
- switch (chan->type) {
- case IIO_LIGHT:
- return noa1305_scale(priv, val, val2);
- default:
- break;
- }
- break;
+ return noa1305_scale(priv, val, val2);
+ case IIO_CHAN_INFO_INT_TIME:
+ return noa1305_int_time(priv, val, val2);
default:
- break;
+ return -EINVAL;
}
+}
- return ret;
+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)
@@ -268,7 +315,7 @@ static const struct of_device_id noa1305_of_match[] = {
MODULE_DEVICE_TABLE(of, noa1305_of_match);
static const struct i2c_device_id noa1305_ids[] = {
- { "noa1305", 0 },
+ { "noa1305" },
{ }
};
MODULE_DEVICE_TABLE(i2c, noa1305_ids);
@@ -278,7 +325,7 @@ static struct i2c_driver noa1305_driver = {
.name = NOA1305_DRIVER_NAME,
.of_match_table = noa1305_of_match,
},
- .probe_new = noa1305_probe,
+ .probe = noa1305_probe,
.id_table = noa1305_ids,
};
diff --git a/drivers/iio/light/opt3001.c b/drivers/iio/light/opt3001.c
index ec4f5c2369c4..393a3d2fbe1d 100644
--- a/drivers/iio/light/opt3001.c
+++ b/drivers/iio/light/opt3001.c
@@ -70,6 +70,35 @@
#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 proccessed value to six decimal
+ * places.
+ */
+ int factor_decimal;
+
+ bool has_id;
+};
+
struct opt3001 {
struct i2c_client *client;
struct device *dev;
@@ -79,6 +108,7 @@ struct opt3001 {
bool result_ready;
wait_queue_head_t result_ready_queue;
u16 result;
+ const struct opt3001_chip_info *chip_info;
u32 int_time;
u32 mode;
@@ -92,11 +122,6 @@ struct opt3001 {
bool use_irq;
};
-struct opt3001_scale {
- int val;
- int val2;
-};
-
static const struct opt3001_scale opt3001_scales[] = {
{
.val = 40,
@@ -139,26 +164,77 @@ static const struct opt3001_scale opt3001_scales[] = {
.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)
{
int i;
-
- for (i = 0; i < ARRAY_SIZE(opt3001_scales); i++) {
- const struct opt3001_scale *scale = &opt3001_scales[i];
-
+ for (i = 0; i < ARRAY_SIZE(*opt->chip_info->scales); i++) {
+ const struct opt3001_scale *scale = &(*opt->chip_info->scales)[i];
/*
- * Combine the integer and micro parts for comparison
- * purposes. Use milli lux precision to avoid 32-bit integer
- * overflows.
+ * Compare the integer and micro parts to determine value scale.
*/
- if ((val * 1000 + val2 / 1000) <=
- (scale->val * 1000 + scale->val2 / 1000)) {
+ if (val < scale->val ||
+ (val == scale->val && val2 <= scale->val2)) {
*exponent = i;
return 0;
}
@@ -170,11 +246,14 @@ static int opt3001_find_scale(const struct opt3001 *opt, int val,
static void opt3001_to_iio_ret(struct opt3001 *opt, u8 exponent,
u16 mantissa, int *val, int *val2)
{
- int lux;
+ int ret;
+ int whole = opt->chip_info->factor_whole;
+ int integer = opt->chip_info->factor_integer;
+ int decimal = opt->chip_info->factor_decimal;
- lux = 10 * (mantissa << exponent);
- *val = lux / 1000;
- *val2 = (lux - (*val * 1000)) * 1000;
+ ret = whole * (mantissa << exponent);
+ *val = ret / integer;
+ *val2 = (ret - (*val * integer)) * decimal;
}
static void opt3001_set_mode(struct opt3001 *opt, u16 *reg, u16 mode)
@@ -221,7 +300,18 @@ static const struct iio_chan_spec opt3001_channels[] = {
IIO_CHAN_SOFT_TIMESTAMP(1),
};
-static int opt3001_get_lux(struct opt3001 *opt, int *val, int *val2)
+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_get_processed(struct opt3001 *opt, int *val, int *val2)
{
int ret;
u16 mantissa;
@@ -393,14 +483,15 @@ static int opt3001_read_raw(struct iio_dev *iio,
if (opt->mode == OPT3001_CONFIGURATION_M_CONTINUOUS)
return -EBUSY;
- if (chan->type != IIO_LIGHT)
+ if (chan->type != opt->chip_info->chan_type)
return -EINVAL;
mutex_lock(&opt->lock);
switch (mask) {
+ case IIO_CHAN_INFO_RAW:
case IIO_CHAN_INFO_PROCESSED:
- ret = opt3001_get_lux(opt, val, val2);
+ ret = opt3001_get_processed(opt, val, val2);
break;
case IIO_CHAN_INFO_INT_TIME:
ret = opt3001_get_int_time(opt, val, val2);
@@ -424,7 +515,7 @@ static int opt3001_write_raw(struct iio_dev *iio,
if (opt->mode == OPT3001_CONFIGURATION_M_CONTINUOUS)
return -EBUSY;
- if (chan->type != IIO_LIGHT)
+ if (chan->type != opt->chip_info->chan_type)
return -EINVAL;
if (mask != IIO_CHAN_INFO_INT_TIME)
@@ -475,6 +566,9 @@ static int opt3001_write_event_value(struct iio_dev *iio,
{
struct opt3001 *opt = iio_priv(iio);
int ret;
+ int whole;
+ int integer;
+ int decimal;
u16 mantissa;
u16 value;
@@ -493,7 +587,12 @@ static int opt3001_write_event_value(struct iio_dev *iio,
goto err;
}
- mantissa = (((val * 1000) + (val2 / 1000)) / 10) >> exponent;
+ 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) {
@@ -535,7 +634,7 @@ static int opt3001_read_event_config(struct iio_dev *iio,
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, int state)
+ enum iio_event_direction dir, bool state)
{
struct opt3001 *opt = iio_priv(iio);
int ret;
@@ -606,7 +705,7 @@ static int opt3001_read_id(struct opt3001 *opt)
ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_DEVICE_ID);
if (ret < 0) {
dev_err(opt->dev, "failed to read register %02x\n",
- OPT3001_DEVICE_ID);
+ OPT3001_DEVICE_ID);
return ret;
}
@@ -688,8 +787,10 @@ static irqreturn_t opt3001_irq(int irq, void *_iio)
struct opt3001 *opt = iio_priv(iio);
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 (!opt->ok_to_ignore_lock)
+ if (!ok_to_ignore_lock)
mutex_lock(&opt->lock);
ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_CONFIGURATION);
@@ -703,13 +804,13 @@ static irqreturn_t opt3001_irq(int irq, void *_iio)
OPT3001_CONFIGURATION_M_CONTINUOUS) {
if (ret & OPT3001_CONFIGURATION_FH)
iio_push_event(iio,
- IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0,
+ 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(IIO_LIGHT, 0,
+ IIO_UNMOD_EVENT_CODE(chan_type, 0,
IIO_EV_TYPE_THRESH,
IIO_EV_DIR_FALLING),
iio_get_time_ns(iio));
@@ -726,7 +827,7 @@ static irqreturn_t opt3001_irq(int irq, void *_iio)
}
out:
- if (!opt->ok_to_ignore_lock)
+ if (!ok_to_ignore_lock)
mutex_unlock(&opt->lock);
if (wake_result_ready_queue)
@@ -751,22 +852,25 @@ static int opt3001_probe(struct i2c_client *client)
opt = iio_priv(iio);
opt->client = client;
opt->dev = dev;
+ opt->chip_info = i2c_get_match_data(client);
mutex_init(&opt->lock);
init_waitqueue_head(&opt->result_ready_queue);
i2c_set_clientdata(client, iio);
- ret = opt3001_read_id(opt);
- if (ret)
- return ret;
+ 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 = opt3001_channels;
- iio->num_channels = ARRAY_SIZE(opt3001_channels);
+ iio->channels = *opt->chip_info->channels;
+ iio->num_channels = opt->chip_info->num_channels;
iio->modes = INDIO_DIRECT_MODE;
iio->info = &opt3001_info;
@@ -821,20 +925,44 @@ static void opt3001_remove(struct i2c_client *client)
}
}
+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[] = {
- { "opt3001", 0 },
+ { "opt3001", (kernel_ulong_t)&opt3001_chip_information },
+ { "opt3002", (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" },
+ { .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_new = opt3001_probe,
+ .probe = opt3001_probe,
.remove = opt3001_remove,
.id_table = opt3001_id,
diff --git a/drivers/iio/light/opt4001.c b/drivers/iio/light/opt4001.c
new file mode 100644
index 000000000000..95167273bb90
--- /dev/null
+++ b/drivers/iio/light/opt4001.c
@@ -0,0 +1,466 @@
+// 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 GENMASK(0, 1)
+
+/* 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;
+ 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, val2);
+ *val2 = *val2 * 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 int opt4001_power_down(struct opt4001_chip *chip)
+{
+ struct device *dev = &chip->client->dev;
+ int ret;
+ unsigned int reg;
+
+ ret = regmap_read(chip->regmap, OPT4001_DEVICE_ID, &reg);
+ if (ret) {
+ dev_err(dev, "Failed to read configuration\n");
+ return ret;
+ }
+
+ /* MODE_OFF is 0x0 so just set bits to 0 */
+ reg &= ~OPT4001_CTRL_OPER_MODE_MASK;
+
+ ret = regmap_write(chip->regmap, OPT4001_CTRL, reg);
+ if (ret)
+ dev_err(dev, "Failed to set configuration to power down\n");
+
+ return ret;
+}
+
+static void opt4001_chip_off_action(void *data)
+{
+ struct opt4001_chip *chip = data;
+
+ opt4001_power_down(chip);
+}
+
+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:
+ 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[] = {
+ { "opt4001-sot-5x3", (kernel_ulong_t)&opt4001_sot_5x3_info },
+ { "opt4001-picostar", (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 000000000000..981c704e7df5
--- /dev/null
+++ b/drivers/iio/light/opt4060.c
@@ -0,0 +1,1341 @@
+// 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, &reg);
+ 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);
+ int ret = 0;
+any_mode_retry:
+ if (iio_device_claim_buffer_mode(indio_dev)) {
+ /*
+ * This one is a *bit* hacky. If we cannot claim buffer mode,
+ * then try direct mode so that we make sure things cannot
+ * concurrently change. And we just keep trying until we get one
+ * of the modes...
+ */
+ if (!iio_device_claim_direct(indio_dev))
+ goto any_mode_retry;
+ /*
+ * 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.
+ */
+ ret = opt4060_set_state_common(chip, continuous_sampling,
+ continuous_irq);
+ iio_device_release_direct(indio_dev);
+ return ret;
+ } else {
+ /*
+ * This path means that we managed to claim buffer mode. In
+ * this case the buffer is enabled and irq and sampling must go
+ * to or remain continuous, but only if the trigger is from this
+ * device.
+ */
+ if (!iio_trigger_validate_own_device(indio_dev->trig, indio_dev))
+ ret = opt4060_set_state_common(chip, true, true);
+ else
+ ret = opt4060_set_state_common(chip, continuous_sampling,
+ continuous_irq);
+ iio_device_release_buffer_mode(indio_dev);
+ }
+ return ret;
+}
+
+/*
+ * 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:
+ 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;
+
+ 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, val2);
+
+ 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, &regval);
+ 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, &regval);
+ 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, &regval);
+ if (ret) {
+ dev_err(chip->dev, "Failed to read THRESHOLD_LOW.\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 dev_err_probe(chip->dev, ret, "Could not request IRQ\n");
+
+ 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, &regval);
+ 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[] = {
+ { "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
index 15a666f15c27..98a1f1624c75 100644
--- a/drivers/iio/light/pa12203001.c
+++ b/drivers/iio/light/pa12203001.c
@@ -185,15 +185,10 @@ static int pa12203001_set_power_state(struct pa12203001_data *data, bool on,
mutex_unlock(&data->lock);
}
- if (on) {
- ret = pm_runtime_resume_and_get(&data->client->dev);
+ if (on)
+ return pm_runtime_resume_and_get(&data->client->dev);
- } else {
- pm_runtime_mark_last_busy(&data->client->dev);
- ret = pm_runtime_put_autosuspend(&data->client->dev);
- }
-
- return ret;
+ return pm_runtime_put_autosuspend(&data->client->dev);
err:
mutex_unlock(&data->lock);
@@ -456,14 +451,14 @@ static const struct dev_pm_ops pa12203001_pm_ops = {
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[] = {
- { "txcpa122", 0 },
- {}
+ { "txcpa122" },
+ { }
};
MODULE_DEVICE_TABLE(i2c, pa12203001_id);
@@ -472,9 +467,9 @@ static struct i2c_driver pa12203001_driver = {
.driver = {
.name = PA12203001_DRIVER_NAME,
.pm = &pa12203001_pm_ops,
- .acpi_match_table = ACPI_PTR(pa12203001_acpi_match),
+ .acpi_match_table = pa12203001_acpi_match,
},
- .probe_new = pa12203001_probe,
+ .probe = pa12203001_probe,
.remove = pa12203001_remove,
.id_table = pa12203001_id,
diff --git a/drivers/iio/light/rohm-bu27034.c b/drivers/iio/light/rohm-bu27034.c
new file mode 100644
index 000000000000..28d111ac8c0a
--- /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, &reg);
+ 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
index 668e444f6049..9341c1d58cbe 100644
--- a/drivers/iio/light/rpr0521.c
+++ b/drivers/iio/light/rpr0521.c
@@ -10,11 +10,12 @@
*/
#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/cleanup.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/delay.h>
-#include <linux/acpi.h>
#include <linux/iio/iio.h>
#include <linux/iio/buffer.h>
@@ -68,8 +69,6 @@
#define RPR0521_DEFAULT_MEAS_TIME 0x06 /* ALS - 100ms, PXS - 100ms */
#define RPR0521_DRV_NAME "RPR0521"
-#define RPR0521_IRQ_NAME "rpr0521_event"
-#define RPR0521_REGMAP_NAME "rpr0521_regmap"
#define RPR0521_SLEEP_DELAY_MS 2000
@@ -203,7 +202,7 @@ struct rpr0521_data {
struct {
__le16 channels[3];
u8 garbage;
- s64 ts __aligned(8);
+ aligned_s64 ts;
} scan;
};
@@ -359,12 +358,10 @@ static int rpr0521_set_power_state(struct rpr0521_data *data, bool on,
* Note: If either measurement is re-enabled before _suspend(),
* both stay enabled until _suspend().
*/
- if (on) {
+ if (on)
ret = pm_runtime_resume_and_get(&data->client->dev);
- } else {
- pm_runtime_mark_last_busy(&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",
@@ -431,25 +428,13 @@ static irqreturn_t rpr0521_drdy_irq_thread(int irq, void *private)
struct rpr0521_data *data = iio_priv(indio_dev);
if (rpr0521_is_triggered(data)) {
- iio_trigger_poll_chained(data->drdy_trigger0);
+ iio_trigger_poll_nested(data->drdy_trigger0);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
-static irqreturn_t rpr0521_trigger_consumer_store_time(int irq, void *p)
-{
- struct iio_poll_func *pf = p;
- struct iio_dev *indio_dev = pf->indio_dev;
-
- /* Other trigger polls store time here. */
- if (!iio_trigger_using_own(indio_dev))
- pf->timestamp = iio_get_time_ns(indio_dev);
-
- return IRQ_WAKE_THREAD;
-}
-
static irqreturn_t rpr0521_trigger_consumer_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
@@ -470,8 +455,8 @@ static irqreturn_t rpr0521_trigger_consumer_handler(int irq, void *p)
data->scan.channels,
(3 * 2) + 1); /* 3 * 16-bit + (discarded) int clear reg. */
if (!err)
- iio_push_to_buffers_with_timestamp(indio_dev,
- &data->scan, pf->timestamp);
+ 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");
@@ -716,50 +701,58 @@ static int rpr0521_write_ps_offset(struct rpr0521_data *data, int offset)
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;
- int busy;
- u8 device_mask;
- __le16 raw_data;
switch (mask) {
case IIO_CHAN_INFO_RAW:
if (chan->type != IIO_INTENSITY && chan->type != IIO_PROXIMITY)
return -EINVAL;
- busy = iio_device_claim_direct_mode(indio_dev);
- if (busy)
+ if (!iio_device_claim_direct(indio_dev))
return -EBUSY;
- device_mask = rpr0521_data_reg[chan->address].device_mask;
-
- mutex_lock(&data->lock);
- ret = rpr0521_set_power_state(data, true, device_mask);
- if (ret < 0)
- goto rpr0521_read_raw_out;
-
- 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);
- goto rpr0521_read_raw_out;
- }
-
- ret = rpr0521_set_power_state(data, false, device_mask);
-
-rpr0521_read_raw_out:
- mutex_unlock(&data->lock);
- iio_device_release_direct_mode(indio_dev);
+ ret = rpr0521_read_info_raw(data, chan, val);
+ iio_device_release_direct(indio_dev);
if (ret < 0)
return ret;
- *val = le16_to_cpu(raw_data);
-
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
@@ -917,7 +910,7 @@ static bool rpr0521_is_volatile_reg(struct device *dev, unsigned int reg)
}
static const struct regmap_config rpr0521_regmap_config = {
- .name = RPR0521_REGMAP_NAME,
+ .name = "rpr0521_regmap",
.reg_bits = 8,
.val_bits = 8,
@@ -994,7 +987,7 @@ static int rpr0521_probe(struct i2c_client *client)
ret = devm_request_threaded_irq(&client->dev, client->irq,
rpr0521_drdy_irq_handler, rpr0521_drdy_irq_thread,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
- RPR0521_IRQ_NAME, indio_dev);
+ "rpr0521_event", indio_dev);
if (ret < 0) {
dev_err(&client->dev, "request irq %d for trigger0 failed\n",
client->irq);
@@ -1016,7 +1009,7 @@ static int rpr0521_probe(struct i2c_client *client)
/* Trigger consumer setup */
ret = devm_iio_triggered_buffer_setup(indio_dev->dev.parent,
indio_dev,
- rpr0521_trigger_consumer_store_time,
+ iio_pollfunc_store_time,
rpr0521_trigger_consumer_handler,
&rpr0521_buffer_setup_ops);
if (ret < 0) {
@@ -1109,7 +1102,7 @@ static const struct acpi_device_id rpr0521_acpi_match[] = {
MODULE_DEVICE_TABLE(acpi, rpr0521_acpi_match);
static const struct i2c_device_id rpr0521_id[] = {
- {"rpr0521", 0},
+ { "rpr0521" },
{ }
};
@@ -1119,9 +1112,9 @@ static struct i2c_driver rpr0521_driver = {
.driver = {
.name = RPR0521_DRV_NAME,
.pm = pm_ptr(&rpr0521_pm_ops),
- .acpi_match_table = ACPI_PTR(rpr0521_acpi_match),
+ .acpi_match_table = rpr0521_acpi_match,
},
- .probe_new = rpr0521_probe,
+ .probe = rpr0521_probe,
.remove = rpr0521_remove,
.id_table = rpr0521_id,
};
diff --git a/drivers/iio/light/si1133.c b/drivers/iio/light/si1133.c
index a08fbc8f5adb..44fa152dbd24 100644
--- a/drivers/iio/light/si1133.c
+++ b/drivers/iio/light/si1133.c
@@ -17,7 +17,7 @@
#include <linux/util_macros.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#define SI1133_REG_PART_ID 0x00
#define SI1133_REG_REV_ID 0x01
@@ -1055,7 +1055,7 @@ static int si1133_probe(struct i2c_client *client)
}
static const struct i2c_device_id si1133_ids[] = {
- { "si1133", 0 },
+ { "si1133" },
{ }
};
MODULE_DEVICE_TABLE(i2c, si1133_ids);
@@ -1064,7 +1064,7 @@ static struct i2c_driver si1133_driver = {
.driver = {
.name = "si1133",
},
- .probe_new = si1133_probe,
+ .probe = si1133_probe,
.id_table = si1133_ids,
};
diff --git a/drivers/iio/light/si1145.c b/drivers/iio/light/si1145.c
index f7126235f94c..f8eb251eca8d 100644
--- a/drivers/iio/light/si1145.c
+++ b/drivers/iio/light/si1145.c
@@ -465,11 +465,10 @@ static irqreturn_t si1145_trigger_handler(int irq, void *private)
goto done;
}
- for_each_set_bit(i, indio_dev->active_scan_mask,
- indio_dev->masklength) {
+ iio_for_each_active_channel(indio_dev, i) {
int run = 1;
- while (i + run < indio_dev->masklength) {
+ 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 !=
@@ -495,8 +494,9 @@ static irqreturn_t si1145_trigger_handler(int irq, void *private)
goto done;
}
- iio_push_to_buffers_with_timestamp(indio_dev, data->buffer,
- iio_get_time_ns(indio_dev));
+ 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);
@@ -514,7 +514,7 @@ static int si1145_set_chlist(struct iio_dev *indio_dev, unsigned long scan_mask)
if (data->scan_mask == scan_mask)
return 0;
- for_each_set_bit(i, &scan_mask, indio_dev->masklength) {
+ 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;
@@ -634,11 +634,10 @@ static int si1145_read_raw(struct iio_dev *indio_dev,
case IIO_VOLTAGE:
case IIO_TEMP:
case IIO_UVINDEX:
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret)
- return ret;
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
ret = si1145_measure(indio_dev, chan);
- iio_device_release_direct_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
if (ret < 0)
return ret;
@@ -751,18 +750,17 @@ static int si1145_write_raw(struct iio_dev *indio_dev,
return -EINVAL;
}
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret)
- return ret;
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
ret = si1145_param_set(data, reg1, val);
if (ret < 0) {
- iio_device_release_direct_mode(indio_dev);
+ 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_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
return ret;
case IIO_CHAN_INFO_RAW:
if (chan->type != IIO_CURRENT)
@@ -774,19 +772,18 @@ static int si1145_write_raw(struct iio_dev *indio_dev,
reg1 = SI1145_PS_LED_REG(chan->channel);
shift = SI1145_PS_LED_SHIFT(chan->channel);
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret)
- return ret;
+ 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_mode(indio_dev);
+ 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_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
return ret;
case IIO_CHAN_INFO_SAMP_FREQ:
return si1145_store_samp_freq(data, val);
@@ -1352,7 +1349,7 @@ static struct i2c_driver si1145_driver = {
.driver = {
.name = "si1145",
},
- .probe_new = si1145_probe,
+ .probe = si1145_probe,
.id_table = si1145_ids,
};
diff --git a/drivers/iio/light/st_uvis25.h b/drivers/iio/light/st_uvis25.h
index 283086887caf..78bc56aad129 100644
--- a/drivers/iio/light/st_uvis25.h
+++ b/drivers/iio/light/st_uvis25.h
@@ -27,11 +27,6 @@ struct st_uvis25_hw {
struct iio_trigger *trig;
bool enabled;
int irq;
- /* Ensure timestamp is naturally aligned */
- struct {
- u8 chan;
- s64 ts __aligned(8);
- } scan;
};
extern const struct dev_pm_ops st_uvis25_pm_ops;
diff --git a/drivers/iio/light/st_uvis25_core.c b/drivers/iio/light/st_uvis25_core.c
index c737d3e193ae..bcd729a9924e 100644
--- a/drivers/iio/light/st_uvis25_core.c
+++ b/drivers/iio/light/st_uvis25_core.c
@@ -117,9 +117,8 @@ static int st_uvis25_read_raw(struct iio_dev *iio_dev,
{
int ret;
- ret = iio_device_claim_direct_mode(iio_dev);
- if (ret)
- return ret;
+ if (!iio_device_claim_direct(iio_dev))
+ return -EBUSY;
switch (mask) {
case IIO_CHAN_INFO_PROCESSED: {
@@ -144,7 +143,7 @@ static int st_uvis25_read_raw(struct iio_dev *iio_dev,
break;
}
- iio_device_release_direct_mode(iio_dev);
+ iio_device_release_direct(iio_dev);
return ret;
}
@@ -161,7 +160,7 @@ static irqreturn_t st_uvis25_trigger_handler_thread(int irq, void *private)
if (!(status & ST_UVIS25_REG_UV_DA_MASK))
return IRQ_NONE;
- iio_trigger_poll_chained(hw->trig);
+ iio_trigger_poll_nested(hw->trig);
return IRQ_HANDLED;
}
@@ -174,8 +173,7 @@ static int st_uvis25_allocate_trigger(struct iio_dev *iio_dev)
unsigned long irq_type;
int err;
- irq_type = irqd_get_trigger_type(irq_get_irq_data(hw->irq));
-
+ irq_type = irq_get_trigger_type(hw->irq);
switch (irq_type) {
case IRQF_TRIGGER_HIGH:
case IRQF_TRIGGER_RISING:
@@ -236,15 +234,21 @@ static irqreturn_t st_uvis25_buffer_handler_thread(int irq, void *p)
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;
- hw->scan.chan = val;
+ scan.chan = val;
- iio_push_to_buffers_with_timestamp(iio_dev, &hw->scan,
- iio_get_time_ns(iio_dev));
+ iio_push_to_buffers_with_ts(iio_dev, &scan, sizeof(scan),
+ iio_get_time_ns(iio_dev));
out:
iio_trigger_notify_done(hw->trig);
@@ -291,7 +295,7 @@ int st_uvis25_probe(struct device *dev, int irq, struct regmap *regmap)
if (!iio_dev)
return -ENOMEM;
- dev_set_drvdata(dev, (void *)iio_dev);
+ dev_set_drvdata(dev, iio_dev);
hw = iio_priv(iio_dev);
hw->irq = irq;
@@ -323,15 +327,15 @@ int st_uvis25_probe(struct device *dev, int irq, struct regmap *regmap)
return devm_iio_device_register(dev, iio_dev);
}
-EXPORT_SYMBOL_NS(st_uvis25_probe, IIO_UVIS25);
+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_update_bits(hw->regmap, ST_UVIS25_REG_CTRL1_ADDR,
- ST_UVIS25_REG_ODR_MASK, 0);
+ return regmap_clear_bits(hw->regmap, ST_UVIS25_REG_CTRL1_ADDR,
+ ST_UVIS25_REG_ODR_MASK);
}
static int st_uvis25_resume(struct device *dev)
diff --git a/drivers/iio/light/st_uvis25_i2c.c b/drivers/iio/light/st_uvis25_i2c.c
index 2160e87bb498..5d9bb4d9be63 100644
--- a/drivers/iio/light/st_uvis25_i2c.c
+++ b/drivers/iio/light/st_uvis25_i2c.c
@@ -41,13 +41,13 @@ static int st_uvis25_i2c_probe(struct i2c_client *client)
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[] = {
{ ST_UVIS25_DEV_NAME },
- {},
+ { }
};
MODULE_DEVICE_TABLE(i2c, st_uvis25_i2c_id_table);
@@ -57,7 +57,7 @@ static struct i2c_driver st_uvis25_driver = {
.pm = pm_sleep_ptr(&st_uvis25_pm_ops),
.of_match_table = st_uvis25_i2c_of_match,
},
- .probe_new = st_uvis25_i2c_probe,
+ .probe = st_uvis25_i2c_probe,
.id_table = st_uvis25_i2c_id_table,
};
module_i2c_driver(st_uvis25_driver);
@@ -65,4 +65,4 @@ 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);
+MODULE_IMPORT_NS("IIO_UVIS25");
diff --git a/drivers/iio/light/st_uvis25_spi.c b/drivers/iio/light/st_uvis25_spi.c
index 86a232320d7d..a5aad74ce73e 100644
--- a/drivers/iio/light/st_uvis25_spi.c
+++ b/drivers/iio/light/st_uvis25_spi.c
@@ -42,13 +42,13 @@ static int st_uvis25_spi_probe(struct spi_device *spi)
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[] = {
{ ST_UVIS25_DEV_NAME },
- {},
+ { }
};
MODULE_DEVICE_TABLE(spi, st_uvis25_spi_id_table);
@@ -66,4 +66,4 @@ 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);
+MODULE_IMPORT_NS("IIO_UVIS25");
diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c
index 48ae6ff0015e..a75a83594a7e 100644
--- a/drivers/iio/light/stk3310.c
+++ b/drivers/iio/light/stk3310.c
@@ -7,11 +7,11 @@
* IIO driver for STK3310/STK3311. 7-bit I2C address: 0x48.
*/
-#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/mod_devicetable.h>
#include <linux/regmap.h>
#include <linux/iio/events.h>
#include <linux/iio/iio.h>
@@ -35,16 +35,17 @@
#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_REGMAP_NAME "stk3310_regmap"
-#define STK3310_EVENT "stk3310_event"
#define STK3310_SCALE_AVAILABLE "6.4 1.6 0.4 0.1"
@@ -81,6 +82,16 @@ static const struct reg_field stk3310_reg_field_flag_psint =
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,
@@ -152,7 +163,7 @@ static const struct iio_chan_spec_ext_info stk3310_ext_info[] = {
.shared = IIO_SEPARATE,
.read = stk3310_read_near_level,
},
- { /* sentinel */ }
+ { }
};
static const struct iio_chan_spec stk3310_channels[] = {
@@ -197,6 +208,16 @@ 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)
{
@@ -301,15 +322,12 @@ 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,
- int state)
+ bool state)
{
int ret;
struct stk3310_data *data = iio_priv(indio_dev);
struct i2c_client *client = data->client;
- if (state < 0 || state > 7)
- return -EINVAL;
-
/* Set INT_PS value */
mutex_lock(&data->lock);
ret = regmap_field_write(data->reg_int_ps, state);
@@ -473,13 +491,9 @@ static int stk3310_init(struct iio_dev *indio_dev)
if (ret < 0)
return ret;
- if (chipid != STK3310_CHIP_ID_VAL &&
- chipid != STK3311_CHIP_ID_VAL &&
- chipid != STK3311X_CHIP_ID_VAL &&
- chipid != STK3335_CHIP_ID_VAL) {
- dev_err(&client->dev, "invalid chip id: 0x%x\n", chipid);
- return -ENODEV;
- }
+ 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);
@@ -511,7 +525,7 @@ static bool stk3310_is_volatile_reg(struct device *dev, unsigned int reg)
}
static const struct regmap_config stk3310_regmap_config = {
- .name = STK3310_REGMAP_NAME,
+ .name = "stk3310_regmap",
.reg_bits = 8,
.val_bits = 8,
.max_register = STK3310_MAX_REG,
@@ -593,10 +607,8 @@ static int stk3310_probe(struct i2c_client *client)
struct stk3310_data *data;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
- if (!indio_dev) {
- dev_err(&client->dev, "iio allocation failed!\n");
+ if (!indio_dev)
return -ENOMEM;
- }
data = iio_priv(indio_dev);
data->client = client;
@@ -627,7 +639,7 @@ static int stk3310_probe(struct i2c_client *client)
stk3310_irq_event_handler,
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT,
- STK3310_EVENT, indio_dev);
+ "stk3310_event", indio_dev);
if (ret < 0) {
dev_err(&client->dev, "request irq %d failed\n",
client->irq);
@@ -683,27 +695,29 @@ static DEFINE_SIMPLE_DEV_PM_OPS(stk3310_pm_ops, stk3310_suspend,
stk3310_resume);
static const struct i2c_device_id stk3310_i2c_id[] = {
- {"STK3310", 0},
- {"STK3311", 0},
- {"STK3335", 0},
- {}
+ { "STK3013" },
+ { "STK3310" },
+ { "STK3311" },
+ { "STK3335" },
+ { }
};
MODULE_DEVICE_TABLE(i2c, stk3310_i2c_id);
static const struct acpi_device_id stk3310_acpi_id[] = {
+ {"STK3013", 0},
{"STK3310", 0},
{"STK3311", 0},
- {"STK3335", 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);
@@ -712,9 +726,9 @@ static struct i2c_driver stk3310_driver = {
.name = "stk3310",
.of_match_table = stk3310_of_match,
.pm = pm_sleep_ptr(&stk3310_pm_ops),
- .acpi_match_table = ACPI_PTR(stk3310_acpi_id),
+ .acpi_match_table = stk3310_acpi_id,
},
- .probe_new = stk3310_probe,
+ .probe = stk3310_probe,
.remove = stk3310_remove,
.id_table = stk3310_i2c_id,
};
diff --git a/drivers/iio/light/tcs3414.c b/drivers/iio/light/tcs3414.c
index 5100732fbaf0..5be461e6dbdb 100644
--- a/drivers/iio/light/tcs3414.c
+++ b/drivers/iio/light/tcs3414.c
@@ -53,11 +53,6 @@ struct tcs3414_data {
u8 control;
u8 gain;
u8 timing;
- /* Ensure timestamp is naturally aligned */
- struct {
- u16 chans[4];
- s64 timestamp __aligned(8);
- } scan;
};
#define TCS3414_CHANNEL(_color, _si, _addr) { \
@@ -134,16 +129,15 @@ static int tcs3414_read_raw(struct iio_dev *indio_dev,
switch (mask) {
case IIO_CHAN_INFO_RAW:
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret)
- return ret;
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
ret = tcs3414_req_data(data);
if (ret < 0) {
- iio_device_release_direct_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
return ret;
}
ret = i2c_smbus_read_word_data(data->client, chan->address);
- iio_device_release_direct_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
if (ret < 0)
return ret;
*val = ret;
@@ -205,18 +199,23 @@ static irqreturn_t tcs3414_trigger_handler(int irq, void *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 = { };
+
- for_each_set_bit(i, indio_dev->active_scan_mask,
- indio_dev->masklength) {
+ 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;
- data->scan.chans[j++] = ret;
+ scan.chans[j++] = ret;
}
- iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
iio_get_time_ns(indio_dev));
done:
@@ -363,7 +362,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tcs3414_pm_ops, tcs3414_suspend,
tcs3414_resume);
static const struct i2c_device_id tcs3414_id[] = {
- { "tcs3414", 0 },
+ { "tcs3414" },
{ }
};
MODULE_DEVICE_TABLE(i2c, tcs3414_id);
@@ -373,7 +372,7 @@ static struct i2c_driver tcs3414_driver = {
.name = TCS3414_DRV_NAME,
.pm = pm_sleep_ptr(&tcs3414_pm_ops),
},
- .probe_new = tcs3414_probe,
+ .probe = tcs3414_probe,
.id_table = tcs3414_id,
};
module_i2c_driver(tcs3414_driver);
diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c
index 6187c5487916..12429a3261b3 100644
--- a/drivers/iio/light/tcs3472.c
+++ b/drivers/iio/light/tcs3472.c
@@ -64,11 +64,6 @@ struct tcs3472_data {
u8 control;
u8 atime;
u8 apers;
- /* Ensure timestamp is naturally aligned */
- struct {
- u16 chans[4];
- s64 timestamp __aligned(8);
- } scan;
};
static const struct iio_event_spec tcs3472_events[] = {
@@ -148,16 +143,15 @@ static int tcs3472_read_raw(struct iio_dev *indio_dev,
switch (mask) {
case IIO_CHAN_INFO_RAW:
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret)
- return ret;
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
ret = tcs3472_req_data(data);
if (ret < 0) {
- iio_device_release_direct_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
return ret;
}
ret = i2c_smbus_read_word_data(data->client, chan->address);
- iio_device_release_direct_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
if (ret < 0)
return ret;
*val = ret;
@@ -327,7 +321,7 @@ static int tcs3472_read_event_config(struct iio_dev *indio_dev,
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, int state)
+ enum iio_event_direction dir, bool state)
{
struct tcs3472_data *data = iio_priv(indio_dev);
int ret = 0;
@@ -378,22 +372,26 @@ static irqreturn_t tcs3472_trigger_handler(int irq, void *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;
- for_each_set_bit(i, indio_dev->active_scan_mask,
- indio_dev->masklength) {
+ 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;
- data->scan.chans[j++] = ret;
+ scan.chans[j++] = ret;
}
- iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
iio_get_time_ns(indio_dev));
done:
@@ -599,7 +597,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tcs3472_pm_ops, tcs3472_suspend,
tcs3472_resume);
static const struct i2c_device_id tcs3472_id[] = {
- { "tcs3472", 0 },
+ { "tcs3472" },
{ }
};
MODULE_DEVICE_TABLE(i2c, tcs3472_id);
@@ -609,7 +607,7 @@ static struct i2c_driver tcs3472_driver = {
.name = TCS3472_DRV_NAME,
.pm = pm_sleep_ptr(&tcs3472_pm_ops),
},
- .probe_new = tcs3472_probe,
+ .probe = tcs3472_probe,
.remove = tcs3472_remove,
.id_table = tcs3472_id,
};
diff --git a/drivers/iio/light/tsl2563.c b/drivers/iio/light/tsl2563.c
index d0e42b73203a..f2af1cd7c2d1 100644
--- a/drivers/iio/light/tsl2563.c
+++ b/drivers/iio/light/tsl2563.c
@@ -11,69 +11,63 @@
* Amit Kucheria <amit.kucheria@verdurent.com>
*/
-#include <linux/module.h>
-#include <linux/mod_devicetable.h>
-#include <linux/property.h>
+#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/sched.h>
+#include <linux/math.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
#include <linux/mutex.h>
-#include <linux/delay.h>
#include <linux/pm.h>
-#include <linux/err.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>
-#include <linux/iio/events.h>
-#include <linux/platform_data/tsl2563.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) * (1L << (ADC_FRAC_BITS))) / (10000))
+#define FRAC10K(f) (((f) * BIT(ADC_FRAC_BITS)) / (10000))
/* Bits used for fraction in calibration coefficients.*/
#define CALIB_FRAC_BITS 10
-/* 0.5 in CALIB_FRAC_BITS precision */
-#define CALIB_FRAC_HALF (1 << (CALIB_FRAC_BITS - 1))
-/* Make a fraction from a number n that was multiplied with b. */
-#define CALIB_FRAC(n, b) (((n) << CALIB_FRAC_BITS) / (b))
/* Decimal 10^(digits in sysfs presentation) */
#define CALIB_BASE_SYSFS 1000
-#define TSL2563_CMD 0x80
-#define TSL2563_CLEARINT 0x40
+#define TSL2563_CMD BIT(7)
+#define TSL2563_CLEARINT BIT(6)
#define TSL2563_REG_CTRL 0x00
#define TSL2563_REG_TIMING 0x01
-#define TSL2563_REG_LOWLOW 0x02 /* data0 low threshold, 2 bytes */
-#define TSL2563_REG_LOWHIGH 0x03
-#define TSL2563_REG_HIGHLOW 0x04 /* data0 high threshold, 2 bytes */
-#define TSL2563_REG_HIGHHIGH 0x05
+#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_DATA0LOW 0x0c /* broadband sensor value, 2 bytes */
-#define TSL2563_REG_DATA0HIGH 0x0d
-#define TSL2563_REG_DATA1LOW 0x0e /* infrared sensor value, 2 bytes */
-#define TSL2563_REG_DATA1HIGH 0x0f
+#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 0x03
+#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 0x03
+#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_PERSIST(n) ((n) & 0x0F)
+#define TSL2563_INT_MASK GENMASK(5, 4)
+#define TSL2563_INT_PERSIST(n) ((n) & GENMASK(3, 0))
struct tsl2563_gainlevel_coeff {
u8 gaintime;
@@ -161,24 +155,16 @@ static int tsl2563_configure(struct tsl2563_chip *chip)
chip->gainlevel->gaintime);
if (ret)
goto error_ret;
- ret = i2c_smbus_write_byte_data(chip->client,
- TSL2563_CMD | TSL2563_REG_HIGHLOW,
- chip->high_thres & 0xFF);
- if (ret)
- goto error_ret;
- ret = i2c_smbus_write_byte_data(chip->client,
- TSL2563_CMD | TSL2563_REG_HIGHHIGH,
- (chip->high_thres >> 8) & 0xFF);
+ 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_byte_data(chip->client,
- TSL2563_CMD | TSL2563_REG_LOWLOW,
- chip->low_thres & 0xFF);
+ ret = i2c_smbus_write_word_data(chip->client,
+ TSL2563_CMD | TSL2563_REG_LOW,
+ chip->low_thres);
if (ret)
goto error_ret;
- ret = i2c_smbus_write_byte_data(chip->client,
- TSL2563_CMD | TSL2563_REG_LOWHIGH,
- (chip->low_thres >> 8) & 0xFF);
/*
* Interrupt register is automatically written anyway if it is relevant
* so is not here.
@@ -223,6 +209,24 @@ static int tsl2563_read_id(struct tsl2563_chip *chip, u8 *id)
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
@@ -325,13 +329,13 @@ static int tsl2563_get_adc(struct tsl2563_chip *chip)
while (retry) {
ret = i2c_smbus_read_word_data(client,
- TSL2563_CMD | TSL2563_REG_DATA0LOW);
+ TSL2563_CMD | TSL2563_REG_DATA0);
if (ret < 0)
goto out;
adc0 = ret;
ret = i2c_smbus_read_word_data(client,
- TSL2563_CMD | TSL2563_REG_DATA1LOW);
+ TSL2563_CMD | TSL2563_REG_DATA1);
if (ret < 0)
goto out;
adc1 = ret;
@@ -352,12 +356,12 @@ out:
static inline int tsl2563_calib_to_sysfs(u32 calib)
{
- return (int) (((calib * CALIB_BASE_SYSFS) +
- CALIB_FRAC_HALF) >> CALIB_FRAC_BITS);
+ 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;
}
@@ -584,20 +588,18 @@ static int tsl2563_write_thresh(struct iio_dev *indio_dev,
{
struct tsl2563_chip *chip = iio_priv(indio_dev);
int ret;
- u8 address;
+
+ mutex_lock(&chip->lock);
if (dir == IIO_EV_DIR_RISING)
- address = TSL2563_REG_HIGHLOW;
+ ret = i2c_smbus_write_word_data(chip->client,
+ TSL2563_CMD | TSL2563_REG_HIGH, val);
else
- address = TSL2563_REG_LOWLOW;
- mutex_lock(&chip->lock);
- ret = i2c_smbus_write_byte_data(chip->client, TSL2563_CMD | address,
- val & 0xFF);
+ ret = i2c_smbus_write_word_data(chip->client,
+ TSL2563_CMD | TSL2563_REG_LOW, val);
if (ret)
goto error_ret;
- ret = i2c_smbus_write_byte_data(chip->client,
- TSL2563_CMD | (address + 1),
- (val >> 8) & 0xFF);
+
if (dir == IIO_EV_DIR_RISING)
chip->high_thres = val;
else
@@ -628,15 +630,13 @@ static irqreturn_t tsl2563_event_handler(int irq, void *private)
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, int state)
+ 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 & 0x30)) {
- chip->intr &= ~0x30;
- chip->intr |= 0x10;
+ 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)) {
@@ -647,18 +647,11 @@ static int tsl2563_write_interrupt_config(struct iio_dev *indio_dev,
if (ret)
goto out;
}
- ret = i2c_smbus_write_byte_data(chip->client,
- TSL2563_CMD | TSL2563_REG_INT,
- chip->intr);
- chip->int_enabled = true;
+ ret = tsl2563_configure_irq(chip, true);
}
- if (!state && (chip->intr & 0x30)) {
- chip->intr &= ~0x30;
- ret = i2c_smbus_write_byte_data(chip->client,
- TSL2563_CMD | TSL2563_REG_INT,
- chip->intr);
- chip->int_enabled = false;
+ 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);
}
@@ -682,7 +675,7 @@ static int tsl2563_read_interrupt_config(struct iio_dev *indio_dev,
if (ret < 0)
return ret;
- return !!(ret & 0x30);
+ return !!(ret & TSL2563_INT_MASK);
}
static const struct iio_info tsl2563_info_no_irq = {
@@ -701,13 +694,14 @@ static const struct iio_info tsl2563_info = {
static int tsl2563_probe(struct i2c_client *client)
{
+ struct device *dev = &client->dev;
struct iio_dev *indio_dev;
struct tsl2563_chip *chip;
- struct tsl2563_platform_data *pdata = client->dev.platform_data;
- int err = 0;
+ unsigned long irq_flags;
u8 id = 0;
+ int err;
- indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip));
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*chip));
if (!indio_dev)
return -ENOMEM;
@@ -717,16 +711,12 @@ static int tsl2563_probe(struct i2c_client *client)
chip->client = client;
err = tsl2563_detect(chip);
- if (err) {
- dev_err(&client->dev, "detect error %d\n", -err);
- return err;
- }
+ if (err)
+ return dev_err_probe(dev, err, "detect error\n");
err = tsl2563_read_id(chip, &id);
- if (err) {
- dev_err(&client->dev, "read id error %d\n", -err);
- return err;
- }
+ if (err)
+ return dev_err_probe(dev, err, "read id error\n");
mutex_init(&chip->lock);
@@ -738,16 +728,10 @@ static int tsl2563_probe(struct i2c_client *client)
chip->calib0 = tsl2563_calib_from_sysfs(CALIB_BASE_SYSFS);
chip->calib1 = tsl2563_calib_from_sysfs(CALIB_BASE_SYSFS);
- if (pdata) {
- chip->cover_comp_gain = pdata->cover_comp_gain;
- } else {
- err = device_property_read_u32(&client->dev, "amstaos,cover-comp-gain",
- &chip->cover_comp_gain);
- if (err)
- chip->cover_comp_gain = 1;
- }
+ chip->cover_comp_gain = 1;
+ device_property_read_u32(dev, "amstaos,cover-comp-gain", &chip->cover_comp_gain);
- dev_info(&client->dev, "model %d, rev. %d\n", id >> 4, id & 0x0f);
+ 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);
@@ -759,23 +743,24 @@ static int tsl2563_probe(struct i2c_client *client)
indio_dev->info = &tsl2563_info_no_irq;
if (client->irq) {
- err = devm_request_threaded_irq(&client->dev, 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,
- IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ irq_flags,
"tsl2563_event",
indio_dev);
- if (err) {
- dev_err(&client->dev, "irq request error %d\n", -err);
- return err;
- }
+ if (err)
+ return dev_err_probe(dev, err, "irq request error\n");
}
err = tsl2563_configure(chip);
- if (err) {
- dev_err(&client->dev, "configure error %d\n", -err);
- return err;
- }
+ if (err)
+ return dev_err_probe(dev, err, "configure error\n");
INIT_DELAYED_WORK(&chip->poweroff_work, tsl2563_poweroff_work);
@@ -784,7 +769,7 @@ static int tsl2563_probe(struct i2c_client *client)
err = iio_device_register(indio_dev);
if (err) {
- dev_err(&client->dev, "iio registration error %d\n", -err);
+ dev_err_probe(dev, err, "iio registration error\n");
goto fail;
}
@@ -804,15 +789,13 @@ static void tsl2563_remove(struct i2c_client *client)
if (!chip->int_enabled)
cancel_delayed_work_sync(&chip->poweroff_work);
/* Ensure that interrupts are disabled - then flush any bottom halves */
- chip->intr &= ~0x30;
- i2c_smbus_write_byte_data(chip->client, TSL2563_CMD | TSL2563_REG_INT,
- chip->intr);
+ tsl2563_configure_irq(chip, false);
tsl2563_set_power(chip, 0);
}
static int tsl2563_suspend(struct device *dev)
{
- struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct tsl2563_chip *chip = iio_priv(indio_dev);
int ret;
@@ -831,7 +814,7 @@ out:
static int tsl2563_resume(struct device *dev)
{
- struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct tsl2563_chip *chip = iio_priv(indio_dev);
int ret;
@@ -860,7 +843,7 @@ static const struct i2c_device_id tsl2563_id[] = {
{ "tsl2561", 1 },
{ "tsl2562", 2 },
{ "tsl2563", 3 },
- {}
+ { }
};
MODULE_DEVICE_TABLE(i2c, tsl2563_id);
@@ -869,7 +852,7 @@ static const struct of_device_id tsl2563_of_match[] = {
{ .compatible = "amstaos,tsl2561" },
{ .compatible = "amstaos,tsl2562" },
{ .compatible = "amstaos,tsl2563" },
- {}
+ { }
};
MODULE_DEVICE_TABLE(of, tsl2563_of_match);
@@ -879,7 +862,7 @@ static struct i2c_driver tsl2563_i2c_driver = {
.of_match_table = tsl2563_of_match,
.pm = pm_sleep_ptr(&tsl2563_pm_ops),
},
- .probe_new = tsl2563_probe,
+ .probe = tsl2563_probe,
.remove = tsl2563_remove,
.id_table = tsl2563_id,
};
diff --git a/drivers/iio/light/tsl2583.c b/drivers/iio/light/tsl2583.c
index a05f1c0453d1..8801a491de77 100644
--- a/drivers/iio/light/tsl2583.c
+++ b/drivers/iio/light/tsl2583.c
@@ -641,16 +641,10 @@ static const struct iio_chan_spec tsl2583_channels[] = {
static int tsl2583_set_pm_runtime_busy(struct tsl2583_chip *chip, bool on)
{
- int ret;
+ if (on)
+ return pm_runtime_resume_and_get(&chip->client->dev);
- if (on) {
- ret = pm_runtime_resume_and_get(&chip->client->dev);
- } else {
- pm_runtime_mark_last_busy(&chip->client->dev);
- ret = pm_runtime_put_autosuspend(&chip->client->dev);
- }
-
- return ret;
+ return pm_runtime_put_autosuspend(&chip->client->dev);
}
static int tsl2583_read_raw(struct iio_dev *indio_dev,
@@ -922,7 +916,7 @@ static const struct i2c_device_id tsl2583_idtable[] = {
{ "tsl2580", 0 },
{ "tsl2581", 1 },
{ "tsl2583", 2 },
- {}
+ { }
};
MODULE_DEVICE_TABLE(i2c, tsl2583_idtable);
@@ -930,7 +924,7 @@ 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);
@@ -942,7 +936,7 @@ static struct i2c_driver tsl2583_driver = {
.of_match_table = tsl2583_of_match,
},
.id_table = tsl2583_idtable,
- .probe_new = tsl2583_probe,
+ .probe = tsl2583_probe,
.remove = tsl2583_remove,
};
module_i2c_driver(tsl2583_driver);
diff --git a/drivers/iio/light/tsl2591.c b/drivers/iio/light/tsl2591.c
index e485a556e6da..c5557867ea43 100644
--- a/drivers/iio/light/tsl2591.c
+++ b/drivers/iio/light/tsl2591.c
@@ -21,7 +21,7 @@
#include <linux/pm_runtime.h>
#include <linux/sysfs.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include <linux/iio/events.h>
#include <linux/iio/iio.h>
@@ -772,7 +772,6 @@ static int tsl2591_read_raw(struct iio_dev *indio_dev,
err_unlock:
mutex_unlock(&chip->als_mutex);
- pm_runtime_mark_last_busy(&client->dev);
pm_runtime_put_autosuspend(&client->dev);
return ret;
@@ -985,7 +984,7 @@ 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,
- int state)
+ bool state)
{
struct tsl2591_chip *chip = iio_priv(indio_dev);
struct i2c_client *client = chip->client;
@@ -995,7 +994,6 @@ static int tsl2591_write_event_config(struct iio_dev *indio_dev,
pm_runtime_get_sync(&client->dev);
} else if (!state && chip->events_enabled) {
chip->events_enabled = false;
- pm_runtime_mark_last_busy(&client->dev);
pm_runtime_put_autosuspend(&client->dev);
}
@@ -1204,7 +1202,7 @@ static int tsl2591_probe(struct i2c_client *client)
static const struct of_device_id tsl2591_of_match[] = {
{ .compatible = "amstaos,tsl2591"},
- {}
+ { }
};
MODULE_DEVICE_TABLE(of, tsl2591_of_match);
@@ -1214,7 +1212,7 @@ static struct i2c_driver tsl2591_driver = {
.pm = pm_ptr(&tsl2591_pm_ops),
.of_match_table = tsl2591_of_match,
},
- .probe_new = tsl2591_probe
+ .probe = tsl2591_probe
};
module_i2c_driver(tsl2591_driver);
diff --git a/drivers/iio/light/tsl2772.c b/drivers/iio/light/tsl2772.c
index ad50baa0202c..0b171106441a 100644
--- a/drivers/iio/light/tsl2772.c
+++ b/drivers/iio/light/tsl2772.c
@@ -601,6 +601,7 @@ static int tsl2772_read_prox_diodes(struct tsl2772_chip *chip)
return -EINVAL;
}
}
+ chip->settings.prox_diode = prox_diode_mask;
return 0;
}
@@ -1080,14 +1081,14 @@ 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,
- int val)
+ bool val)
{
struct tsl2772_chip *chip = iio_priv(indio_dev);
if (chan->type == IIO_INTENSITY)
- chip->settings.als_interrupt_en = val ? true : false;
+ chip->settings.als_interrupt_en = val;
else
- chip->settings.prox_interrupt_en = val ? true : false;
+ chip->settings.prox_interrupt_en = val;
return tsl2772_invoke_change(indio_dev);
}
@@ -1898,7 +1899,7 @@ static const struct i2c_device_id tsl2772_idtable[] = {
{ "tsl2772", tsl2772 },
{ "tmd2772", tmd2772 },
{ "apds9930", apds9930 },
- {}
+ { }
};
MODULE_DEVICE_TABLE(i2c, tsl2772_idtable);
@@ -1915,7 +1916,7 @@ static const struct of_device_id tsl2772_of_match[] = {
{ .compatible = "amstaos,tsl2772" },
{ .compatible = "amstaos,tmd2772" },
{ .compatible = "avago,apds9930" },
- {}
+ { }
};
MODULE_DEVICE_TABLE(of, tsl2772_of_match);
@@ -1931,7 +1932,7 @@ static struct i2c_driver tsl2772_driver = {
.pm = &tsl2772_pm_ops,
},
.id_table = tsl2772_idtable,
- .probe_new = tsl2772_probe,
+ .probe = tsl2772_probe,
};
module_i2c_driver(tsl2772_driver);
diff --git a/drivers/iio/light/tsl4531.c b/drivers/iio/light/tsl4531.c
index d95397eb1526..a5788c09ad02 100644
--- a/drivers/iio/light/tsl4531.c
+++ b/drivers/iio/light/tsl4531.c
@@ -227,7 +227,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tsl4531_pm_ops, tsl4531_suspend,
tsl4531_resume);
static const struct i2c_device_id tsl4531_id[] = {
- { "tsl4531", 0 },
+ { "tsl4531" },
{ }
};
MODULE_DEVICE_TABLE(i2c, tsl4531_id);
@@ -237,7 +237,7 @@ static struct i2c_driver tsl4531_driver = {
.name = TSL4531_DRV_NAME,
.pm = pm_sleep_ptr(&tsl4531_pm_ops),
},
- .probe_new = tsl4531_probe,
+ .probe = tsl4531_probe,
.remove = tsl4531_remove,
.id_table = tsl4531_id,
};
diff --git a/drivers/iio/light/us5182d.c b/drivers/iio/light/us5182d.c
index 8b2a0c99c8e6..d2f5a44892a8 100644
--- a/drivers/iio/light/us5182d.c
+++ b/drivers/iio/light/us5182d.c
@@ -9,7 +9,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/acpi.h>
+#include <linux/mod_devicetable.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/iio/events.h>
@@ -361,19 +361,13 @@ static int us5182d_shutdown_en(struct us5182d_data *data, u8 state)
static int us5182d_set_power_state(struct us5182d_data *data, bool on)
{
- int ret;
-
if (data->power_mode == US5182D_ONESHOT)
return 0;
- if (on) {
- ret = pm_runtime_resume_and_get(&data->client->dev);
- } else {
- pm_runtime_mark_last_busy(&data->client->dev);
- ret = pm_runtime_put_autosuspend(&data->client->dev);
- }
+ if (on)
+ return pm_runtime_resume_and_get(&data->client->dev);
- return ret;
+ return pm_runtime_put_autosuspend(&data->client->dev);
}
static int us5182d_read_value(struct us5182d_data *data,
@@ -627,7 +621,7 @@ static int us5182d_read_event_config(struct iio_dev *indio_dev,
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, int state)
+ enum iio_event_direction dir, bool state)
{
struct us5182d_data *data = iio_priv(indio_dev);
int ret;
@@ -949,21 +943,21 @@ static const struct dev_pm_ops us5182d_pm_ops = {
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[] = {
- { "usd5182", 0 },
- {}
+ { "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);
@@ -972,9 +966,9 @@ static struct i2c_driver us5182d_driver = {
.name = US5182D_DRV_NAME,
.pm = pm_ptr(&us5182d_pm_ops),
.of_match_table = us5182d_of_match,
- .acpi_match_table = ACPI_PTR(us5182d_acpi_match),
+ .acpi_match_table = us5182d_acpi_match,
},
- .probe_new = us5182d_probe,
+ .probe = us5182d_probe,
.remove = us5182d_remove,
.id_table = us5182d_id,
diff --git a/drivers/iio/light/vcnl4000.c b/drivers/iio/light/vcnl4000.c
index cc1a2062e76d..4dbb2294a843 100644
--- a/drivers/iio/light/vcnl4000.c
+++ b/drivers/iio/light/vcnl4000.c
@@ -24,6 +24,7 @@
#include <linux/delay.h>
#include <linux/pm_runtime.h>
#include <linux/interrupt.h>
+#include <linux/units.h>
#include <linux/iio/buffer.h>
#include <linux/iio/events.h>
@@ -60,8 +61,15 @@
#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 */
@@ -76,8 +84,20 @@
#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 */
@@ -95,6 +115,13 @@
#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},
@@ -117,6 +144,44 @@ static const int vcnl4040_ps_it_times[][2] = {
{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 */
enum vcnl4000_device_ids {
@@ -138,6 +203,9 @@ struct vcnl4000_data {
enum vcnl4000_device_ids id;
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;
@@ -150,11 +218,20 @@ struct vcnl4000_chip_spec {
struct iio_chan_spec const *channels;
const int num_channels;
const struct iio_info *info;
- bool irq_support;
+ 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;
};
static const struct i2c_device_id vcnl4000_id[] = {
@@ -199,7 +276,6 @@ static int vcnl4000_init(struct vcnl4000_data *data)
data->rev = ret & 0xf;
data->al_scale = 250000;
- mutex_init(&data->vcnl4000_lock);
return data->chip_spec->set_power_state(data, true);
};
@@ -254,6 +330,10 @@ 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;
@@ -274,6 +354,7 @@ static int vcnl4200_set_power_state(struct vcnl4000_data *data, bool on)
static int vcnl4200_init(struct vcnl4000_data *data)
{
int ret, id;
+ u16 regval;
ret = i2c_smbus_read_word_data(data->client, VCNL4200_DEV_ID);
if (ret < 0)
@@ -295,6 +376,8 @@ static int vcnl4200_init(struct vcnl4000_data *data)
dev_dbg(&data->client->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;
@@ -304,19 +387,41 @@ static int vcnl4200_init(struct vcnl4000_data *data)
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);
- data->al_scale = 24000;
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);
- data->al_scale = 120000;
break;
}
+ data->al_scale = data->chip_spec->ulux_step;
+ data->ps_scale = 16;
mutex_init(&data->vcnl4200_al.lock);
mutex_init(&data->vcnl4200_ps.lock);
+ /* Use 16 bits proximity sensor readings */
+ ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1);
+ if (ret < 0)
+ return ret;
+
+ regval = ret | VCNL4040_PS_CONF2_PS_HD;
+ ret = i2c_smbus_write_word_data(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(data->client, VCNL4200_PS_CONF3);
+ if (ret < 0)
+ return ret;
+
+ regval = ret | VCNL4040_CONF3_PS_SAMPLE_16BITS;
+ ret = i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF3,
+ regval);
+ if (ret < 0)
+ return ret;
+
ret = data->chip_spec->set_power_state(data, true);
if (ret < 0)
return ret;
@@ -471,15 +576,64 @@ static bool vcnl4010_is_in_periodic_mode(struct vcnl4000_data *data)
static int vcnl4000_set_pm_runtime_state(struct vcnl4000_data *data, bool on)
{
struct device *dev = &data->client->dev;
+
+ if (on)
+ return pm_runtime_resume_and_get(dev);
+
+ return pm_runtime_put_autosuspend(dev);
+}
+
+static int vcnl4040_read_als_it(struct vcnl4000_data *data, int *val, int *val2)
+{
int ret;
- if (on) {
- ret = pm_runtime_resume_and_get(dev);
- } else {
- pm_runtime_mark_last_busy(dev);
- ret = pm_runtime_put_autosuspend(dev);
+ 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);
+
+ mutex_lock(&data->vcnl4000_lock);
+
+ ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF);
+ if (ret < 0)
+ goto out_unlock;
+
+ regval = FIELD_PREP(VCNL4040_ALS_CONF_IT, i);
+ regval |= (ret & ~VCNL4040_ALS_CONF_IT);
+ ret = i2c_smbus_write_word_data(data->client,
+ VCNL4200_AL_CONF,
+ regval);
+
+out_unlock:
+ mutex_unlock(&data->vcnl4000_lock);
return ret;
}
@@ -493,11 +647,11 @@ static int vcnl4040_read_ps_it(struct vcnl4000_data *data, int *val, int *val2)
ret = FIELD_GET(VCNL4040_PS_CONF2_PS_IT, ret);
- if (ret >= ARRAY_SIZE(vcnl4040_ps_it_times))
+ if (ret >= data->chip_spec->num_ps_it_times)
return -EINVAL;
- *val = vcnl4040_ps_it_times[ret][0];
- *val2 = vcnl4040_ps_it_times[ret][1];
+ *val = (*data->chip_spec->ps_it_times)[ret][0];
+ *val2 = (*data->chip_spec->ps_it_times)[ret][1];
return 0;
}
@@ -508,8 +662,8 @@ static ssize_t vcnl4040_write_ps_it(struct vcnl4000_data *data, int val)
int ret, index = -1;
u16 regval;
- for (i = 0; i < ARRAY_SIZE(vcnl4040_ps_it_times); i++) {
- if (val == vcnl4040_ps_it_times[i][1]) {
+ 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;
}
@@ -518,6 +672,8 @@ static ssize_t vcnl4040_write_ps_it(struct vcnl4000_data *data, int val)
if (index < 0)
return -EINVAL;
+ data->vcnl4200_ps.sampling_rate = ktime_set(0, val * 60 * NSEC_PER_USEC);
+
mutex_lock(&data->vcnl4000_lock);
ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1);
@@ -534,6 +690,224 @@ out:
return ret;
}
+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;
+ }
+
+ mutex_lock(&data->vcnl4000_lock);
+
+ ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF);
+ if (ret < 0)
+ goto out_unlock;
+
+ regval = FIELD_PREP(VCNL4040_ALS_CONF_PERS, i);
+ regval |= (ret & ~VCNL4040_ALS_CONF_PERS);
+ ret = i2c_smbus_write_word_data(data->client, VCNL4200_AL_CONF,
+ regval);
+
+out_unlock:
+ mutex_unlock(&data->vcnl4000_lock);
+ return ret;
+}
+
+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;
+ }
+ }
+
+ mutex_lock(&data->vcnl4000_lock);
+
+ ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1);
+ if (ret < 0)
+ goto out_unlock;
+
+ regval = FIELD_PREP(VCNL4040_CONF1_PS_PERS, i);
+ regval |= (ret & ~VCNL4040_CONF1_PS_PERS);
+ ret = i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF1,
+ regval);
+
+out_unlock:
+ mutex_unlock(&data->vcnl4000_lock);
+ return ret;
+}
+
+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;
+
+ mutex_lock(&data->vcnl4000_lock);
+
+ ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF3);
+ if (ret < 0)
+ goto out_unlock;
+
+ regval = FIELD_PREP(VCNL4040_PS_CONF3_MPS, i);
+ regval |= (ret & ~VCNL4040_PS_CONF3_MPS);
+ ret = i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF3,
+ regval);
+
+out_unlock:
+ mutex_unlock(&data->vcnl4000_lock);
+ return ret;
+}
+
+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;
+
+ mutex_lock(&data->vcnl4000_lock);
+
+ ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF3);
+ if (ret < 0)
+ goto out_unlock;
+
+ regval = (ret & ~VCNL4040_PS_MS_LED_I);
+ regval |= FIELD_PREP(VCNL4040_PS_MS_LED_I, i);
+ ret = i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF3,
+ regval);
+
+out_unlock:
+ mutex_unlock(&data->vcnl4000_lock);
+ return ret;
+}
+
static int vcnl4000_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
@@ -555,8 +929,9 @@ static int vcnl4000_read_raw(struct iio_dev *indio_dev,
break;
case IIO_PROXIMITY:
ret = data->chip_spec->measure_proximity(data, val);
+ *val2 = data->ps_scale;
if (!ret)
- ret = IIO_VAL_INT;
+ ret = IIO_VAL_FRACTIONAL;
break;
default:
ret = -EINVAL;
@@ -571,12 +946,39 @@ static int vcnl4000_read_raw(struct iio_dev *indio_dev,
*val2 = data->al_scale;
return IIO_VAL_INT_PLUS_MICRO;
case IIO_CHAN_INFO_INT_TIME:
- if (chan->type != IIO_PROXIMITY)
+ 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;
- ret = vcnl4040_read_ps_it(data, val, val2);
+ }
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;
}
@@ -592,9 +994,28 @@ static int vcnl4040_write_raw(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_INT_TIME:
if (val != 0)
return -EINVAL;
- if (chan->type != IIO_PROXIMITY)
+ 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;
- return vcnl4040_write_ps_it(data, val2);
+ }
+ case IIO_CHAN_INFO_CALIBBIAS:
+ switch (chan->type) {
+ case IIO_PROXIMITY:
+ return vcnl4040_write_ps_calibbias(data, val2);
+ default:
+ return -EINVAL;
+ }
default:
return -EINVAL;
}
@@ -605,12 +1026,44 @@ static int vcnl4040_read_avail(struct iio_dev *indio_dev,
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:
- *vals = (int *)vcnl4040_ps_it_times;
+ 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;
- *length = 2 * ARRAY_SIZE(vcnl4040_ps_it_times);
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;
}
@@ -626,9 +1079,8 @@ static int vcnl4010_read_raw(struct iio_dev *indio_dev,
switch (mask) {
case IIO_CHAN_INFO_RAW:
case IIO_CHAN_INFO_SCALE:
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret)
- return ret;
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
/* Protect against event capture. */
if (vcnl4010_is_in_periodic_mode(data)) {
@@ -638,7 +1090,7 @@ static int vcnl4010_read_raw(struct iio_dev *indio_dev,
mask);
}
- iio_device_release_direct_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
return ret;
case IIO_CHAN_INFO_SAMP_FREQ:
switch (chan->type) {
@@ -699,9 +1151,8 @@ static int vcnl4010_write_raw(struct iio_dev *indio_dev,
int ret;
struct vcnl4000_data *data = iio_priv(indio_dev);
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret)
- return ret;
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
/* Protect against event capture. */
if (vcnl4010_is_in_periodic_mode(data)) {
@@ -725,7 +1176,7 @@ static int vcnl4010_write_raw(struct iio_dev *indio_dev,
}
end:
- iio_device_release_direct_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
return ret;
}
@@ -795,6 +1246,137 @@ static int vcnl4010_write_event(struct iio_dev *indio_dev,
}
}
+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;
@@ -821,53 +1403,59 @@ static int vcnl4010_read_event_config(struct iio_dev *indio_dev,
}
}
-static int vcnl4010_config_threshold(struct iio_dev *indio_dev, bool state)
+static int vcnl4010_config_threshold_enable(struct vcnl4000_data *data)
{
- struct vcnl4000_data *data = iio_priv(indio_dev);
int ret;
- int icr;
- int command;
- if (state) {
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret)
- return 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 periodic measurement of proximity data. */
- command = VCNL4000_SELF_TIMED_EN | VCNL4000_PROX_EN;
+ /*
+ * Enable interrupts on threshold, for proximity data by
+ * default.
+ */
+ return i2c_smbus_write_byte_data(data->client, VCNL4010_INT_CTRL,
+ VCNL4010_INT_THR_EN);
+}
- /*
- * Enable interrupts on threshold, for proximity data by
- * default.
- */
- icr = VCNL4010_INT_THR_EN;
- } else {
- if (!vcnl4010_is_thr_enabled(data))
- return 0;
+static int vcnl4010_config_threshold_disable(struct vcnl4000_data *data)
+{
+ int ret;
- command = 0;
- icr = 0;
- }
+ if (!vcnl4010_is_thr_enabled(data))
+ return 0;
- ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND,
- command);
+ ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, 0);
if (ret < 0)
- goto end;
+ return ret;
- ret = i2c_smbus_write_byte_data(data->client, VCNL4010_INT_CTRL, icr);
+ return i2c_smbus_write_byte_data(data->client, VCNL4010_INT_CTRL, 0);
+}
-end:
- if (state)
- iio_device_release_direct_mode(indio_dev);
+static int vcnl4010_config_threshold(struct iio_dev *indio_dev, bool state)
+{
+ struct vcnl4000_data *data = iio_priv(indio_dev);
+ int ret;
- return ret;
+ if (state) {
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+ ret = vcnl4010_config_threshold_enable(data);
+ iio_device_release_direct(indio_dev);
+ return ret;
+ } 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,
- int state)
+ bool state)
{
switch (chan->type) {
case IIO_PROXIMITY:
@@ -877,6 +1465,137 @@ static int vcnl4010_write_event_config(struct iio_dev *indio_dev,
}
}
+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 = -EINVAL;
+ u16 val, mask;
+ struct vcnl4000_data *data = iio_priv(indio_dev);
+
+ mutex_lock(&data->vcnl4000_lock);
+
+ switch (chan->type) {
+ case IIO_LIGHT:
+ ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF);
+ if (ret < 0)
+ goto out;
+
+ 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);
+ ret = i2c_smbus_write_word_data(data->client, VCNL4200_AL_CONF,
+ val);
+ break;
+ case IIO_PROXIMITY:
+ ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1);
+ if (ret < 0)
+ goto out;
+
+ 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);
+ ret = i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF1,
+ val);
+ break;
+ default:
+ break;
+ }
+
+out:
+ mutex_unlock(&data->vcnl4000_lock);
+
+ return ret;
+}
+
+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,
@@ -887,13 +1606,144 @@ static ssize_t vcnl4000_read_near_level(struct iio_dev *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,
},
- { /* sentinel */ }
+ { }
};
static const struct iio_event_spec vcnl4000_event_spec[] = {
@@ -912,6 +1762,38 @@ static const struct iio_event_spec vcnl4000_event_spec[] = {
}
};
+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,
@@ -953,13 +1835,23 @@ 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_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),
- .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME),
+ 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),
}
};
@@ -980,6 +1872,10 @@ static const struct iio_info vcnl4010_info = {
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,
};
@@ -993,7 +1889,6 @@ static const struct vcnl4000_chip_spec vcnl4000_chip_spec_cfg[] = {
.channels = vcnl4000_channels,
.num_channels = ARRAY_SIZE(vcnl4000_channels),
.info = &vcnl4000_info,
- .irq_support = false,
},
[VCNL4010] = {
.prod = "VCNL4010/4020",
@@ -1004,7 +1899,9 @@ static const struct vcnl4000_chip_spec vcnl4000_chip_spec_cfg[] = {
.channels = vcnl4010_channels,
.num_channels = ARRAY_SIZE(vcnl4010_channels),
.info = &vcnl4010_info,
- .irq_support = true,
+ .irq_thread = vcnl4010_irq_thread,
+ .trig_buffer_func = vcnl4010_trigger_handler,
+ .buffer_setup_ops = &vcnl4010_buffer_ops,
},
[VCNL4040] = {
.prod = "VCNL4040",
@@ -1015,7 +1912,13 @@ static const struct vcnl4000_chip_spec vcnl4000_chip_spec_cfg[] = {
.channels = vcnl4040_channels,
.num_channels = ARRAY_SIZE(vcnl4040_channels),
.info = &vcnl4040_info,
- .irq_support = false,
+ .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,
},
[VCNL4200] = {
.prod = "VCNL4200",
@@ -1023,141 +1926,19 @@ static const struct vcnl4000_chip_spec vcnl4000_chip_spec_cfg[] = {
.measure_light = vcnl4200_measure_light,
.measure_proximity = vcnl4200_measure_proximity,
.set_power_state = vcnl4200_set_power_state,
- .channels = vcnl4000_channels,
+ .channels = vcnl4040_channels,
.num_channels = ARRAY_SIZE(vcnl4000_channels),
- .info = &vcnl4000_info,
- .irq_support = false,
+ .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,
},
};
-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_chained(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;
- u16 buffer[8] __aligned(8) = {0}; /* 1x16-bit + naturally aligned ts */
- 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;
-
- buffer[0] = 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_timestamp(indio_dev, buffer,
- 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_trigger_ops vcnl4010_trigger_ops = {
.validate_device = iio_trigger_validate_own_device,
};
@@ -1197,6 +1978,8 @@ static int vcnl4000_probe(struct i2c_client *client)
data->id = id->driver_data;
data->chip_spec = &vcnl4000_chip_spec_cfg[data->id];
+ mutex_init(&data->vcnl4000_lock);
+
ret = data->chip_spec->init(data);
if (ret < 0)
return ret;
@@ -1214,22 +1997,25 @@ static int vcnl4000_probe(struct i2c_client *client)
indio_dev->name = VCNL4000_DRV_NAME;
indio_dev->modes = INDIO_DIRECT_MODE;
- if (client->irq && data->chip_spec->irq_support) {
+ if (data->chip_spec->trig_buffer_func &&
+ data->chip_spec->buffer_setup_ops) {
ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev,
NULL,
- vcnl4010_trigger_handler,
- &vcnl4010_buffer_ops);
+ data->chip_spec->trig_buffer_func,
+ data->chip_spec->buffer_setup_ops);
if (ret < 0) {
dev_err(&client->dev,
"unable to setup iio triggered buffer\n");
return ret;
}
+ }
+ if (client->irq && data->chip_spec->irq_thread) {
ret = devm_request_threaded_irq(&client->dev, client->irq,
- NULL, vcnl4010_irq_thread,
+ NULL, data->chip_spec->irq_thread,
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT,
- "vcnl4010_irq",
+ "vcnl4000_irq",
indio_dev);
if (ret < 0) {
dev_err(&client->dev, "irq request failed\n");
@@ -1280,7 +2066,7 @@ static const struct of_device_id vcnl_4000_of_match[] = {
.compatible = "vishay,vcnl4200",
.data = (void *)VCNL4200,
},
- {},
+ { }
};
MODULE_DEVICE_TABLE(of, vcnl_4000_of_match);
@@ -1326,7 +2112,7 @@ static struct i2c_driver vcnl4000_driver = {
.pm = pm_ptr(&vcnl4000_pm_ops),
.of_match_table = vcnl_4000_of_match,
},
- .probe_new = vcnl4000_probe,
+ .probe = vcnl4000_probe,
.id_table = vcnl4000_id,
.remove = vcnl4000_remove,
};
diff --git a/drivers/iio/light/vcnl4035.c b/drivers/iio/light/vcnl4035.c
index 84148b944000..963747927425 100644
--- a/drivers/iio/light/vcnl4035.c
+++ b/drivers/iio/light/vcnl4035.c
@@ -8,6 +8,7 @@
* TODO: Proximity
*/
#include <linux/bitops.h>
+#include <linux/bitfield.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
@@ -22,8 +23,6 @@
#include <linux/iio/triggered_buffer.h>
#define VCNL4035_DRV_NAME "vcnl4035"
-#define VCNL4035_IRQ_NAME "vcnl4035_event"
-#define VCNL4035_REGMAP_NAME "vcnl4035_regmap"
/* Device registers */
#define VCNL4035_ALS_CONF 0x00
@@ -42,6 +41,7 @@
#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)
@@ -89,7 +89,7 @@ static irqreturn_t vcnl4035_drdy_irq_thread(int irq, void *private)
IIO_EV_TYPE_THRESH,
IIO_EV_DIR_EITHER),
iio_get_time_ns(indio_dev));
- iio_trigger_poll_chained(data->drdy_trigger0);
+ iio_trigger_poll_nested(data->drdy_trigger0);
return IRQ_HANDLED;
}
@@ -103,7 +103,7 @@ static irqreturn_t vcnl4035_trigger_consumer_handler(int irq, void *p)
struct iio_dev *indio_dev = pf->indio_dev;
struct vcnl4035_data *data = iio_priv(indio_dev);
/* Ensure naturally aligned timestamp */
- u8 buffer[ALIGN(sizeof(u16), sizeof(s64)) + sizeof(s64)] __aligned(8);
+ u8 buffer[ALIGN(sizeof(u16), sizeof(s64)) + sizeof(s64)] __aligned(8) = { };
int ret;
ret = regmap_read(data->regmap, VCNL4035_ALS_DATA, (int *)buffer);
@@ -141,17 +141,37 @@ static const struct iio_trigger_ops vcnl4035_trigger_ops = {
static int vcnl4035_set_pm_runtime_state(struct vcnl4035_data *data, bool on)
{
- int ret;
struct device *dev = &data->client->dev;
- if (on) {
- ret = pm_runtime_resume_and_get(dev);
- } else {
- pm_runtime_mark_last_busy(dev);
- ret = pm_runtime_put_autosuspend(dev);
- }
+ if (on)
+ return pm_runtime_resume_and_get(dev);
- return ret;
+ return pm_runtime_put_autosuspend(dev);
+}
+
+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;
}
/*
@@ -173,28 +193,13 @@ static int vcnl4035_read_raw(struct iio_dev *indio_dev,
{
struct vcnl4035_data *data = iio_priv(indio_dev);
int ret;
- int raw_data;
- unsigned int reg;
switch (mask) {
case IIO_CHAN_INFO_RAW:
ret = vcnl4035_set_pm_runtime_state(data, true);
if (ret < 0)
return ret;
-
- ret = iio_device_claim_direct_mode(indio_dev);
- if (!ret) {
- if (chan->channel)
- reg = VCNL4035_ALS_DATA;
- else
- reg = VCNL4035_WHITE_DATA;
- ret = regmap_read(data->regmap, reg, &raw_data);
- iio_device_release_direct_mode(indio_dev);
- if (!ret) {
- *val = raw_data;
- ret = IIO_VAL_INT;
- }
- }
+ ret = vcnl4035_read_info_raw(indio_dev, chan, val);
vcnl4035_set_pm_runtime_state(data, false);
return ret;
case IIO_CHAN_INFO_INT_TIME:
@@ -413,6 +418,7 @@ static int vcnl4035_init(struct vcnl4035_data *data)
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);
@@ -490,7 +496,7 @@ static bool vcnl4035_is_volatile_reg(struct device *dev, unsigned int reg)
}
static const struct regmap_config vcnl4035_regmap_config = {
- .name = VCNL4035_REGMAP_NAME,
+ .name = "vcnl4035_regmap",
.reg_bits = 8,
.val_bits = 16,
.max_register = VCNL4035_DEV_ID,
@@ -532,7 +538,7 @@ static int vcnl4035_probe_trigger(struct iio_dev *indio_dev)
ret = devm_request_threaded_irq(&data->client->dev, data->client->irq,
NULL, vcnl4035_drdy_irq_thread,
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
- VCNL4035_IRQ_NAME, indio_dev);
+ "vcnl4035_event", indio_dev);
if (ret < 0)
dev_err(&data->client->dev, "request irq %d for trigger0 failed\n",
data->client->irq);
@@ -650,7 +656,7 @@ static DEFINE_RUNTIME_DEV_PM_OPS(vcnl4035_pm_ops, vcnl4035_runtime_suspend,
vcnl4035_runtime_resume, NULL);
static const struct i2c_device_id vcnl4035_id[] = {
- { "vcnl4035", 0 },
+ { "vcnl4035" },
{ }
};
MODULE_DEVICE_TABLE(i2c, vcnl4035_id);
@@ -667,7 +673,7 @@ static struct i2c_driver vcnl4035_driver = {
.pm = pm_ptr(&vcnl4035_pm_ops),
.of_match_table = vcnl4035_of_match,
},
- .probe_new = vcnl4035_probe,
+ .probe = vcnl4035_probe,
.remove = vcnl4035_remove,
.id_table = vcnl4035_id,
};
diff --git a/drivers/iio/light/veml3235.c b/drivers/iio/light/veml3235.c
new file mode 100644
index 000000000000..9309ad83ca9e
--- /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, &reg);
+ 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, &reg);
+ 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, &reg);
+ if (ret < 0)
+ return ret;
+
+ *val = reg;
+ return IIO_VAL_INT;
+ case IIO_INTENSITY:
+ ret = regmap_read(regmap, VEML3235_REG_WH_DATA, &reg);
+ 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, &reg);
+ 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[] = {
+ { "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/veml6030.c b/drivers/iio/light/veml6030.c
index e7d2d5d177d4..6bcacae3863c 100644
--- a/drivers/iio/light/veml6030.c
+++ b/drivers/iio/light/veml6030.c
@@ -1,22 +1,37 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * VEML6030 Ambient Light Sensor
+ * 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
@@ -26,6 +41,7 @@
#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)
@@ -38,44 +54,109 @@
#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 appnote 84367
+ * 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) while the cur_integration_time holds index
- * of integration time (0-5).
+ * 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;
- int cur_resolution;
- int cur_gain;
- int cur_integration_time;
+ struct veml6030_rf rf;
+ const struct veml603x_chip *chip;
+ struct iio_gts gts;
+
};
-/* Integration time available in seconds */
-static IIO_CONST_ATTR(in_illuminance_integration_time_available,
- "0.025 0.05 0.1 0.2 0.4 0.8");
+#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),
+};
-/*
- * Scale is 1/gain. Value 0.125 is ALS gain x (1/8), 0.25 is
- * ALS gain x (1/4), 1.0 = ALS gain x 1 and 2.0 is ALS gain x 2.
+/* 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.
*/
-static IIO_CONST_ATTR(in_illuminance_scale_available,
- "0.125 0.25 1.0 2.0");
-
-static struct attribute *veml6030_attributes[] = {
- &iio_const_attr_in_illuminance_integration_time_available.dev_attr.attr,
- &iio_const_attr_in_illuminance_scale_available.dev_attr.attr,
- NULL
+#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),
};
-static const struct attribute_group veml6030_attr_group = {
- .attrs = veml6030_attributes,
+#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),
};
/*
@@ -99,9 +180,8 @@ static const char * const period_values[] = {
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;
- struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
- struct veml6030_data *data = iio_priv(indio_dev);
ret = regmap_read(data->regmap, VEML6030_REG_ALS_CONF, &reg);
if (ret) {
@@ -144,14 +224,23 @@ static const struct attribute_group veml6030_event_attr_group = {
static int veml6030_als_pwr_on(struct veml6030_data *data)
{
- return regmap_update_bits(data->regmap, VEML6030_REG_ALS_CONF,
- VEML6030_ALS_SD, 0);
+ 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_update_bits(data->regmap, VEML6030_REG_ALS_CONF,
- VEML6030_ALS_SD, 1);
+ return regmap_set_bits(data->regmap, VEML6030_REG_ALS_CONF,
+ VEML6030_ALS_SD);
}
static void veml6030_als_shut_down_action(void *data)
@@ -190,8 +279,17 @@ static const struct iio_chan_spec veml6030_channels[] = {
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,
@@ -199,8 +297,85 @@ static const struct iio_chan_spec veml6030_channels[] = {
.modified = 1,
.channel2 = IIO_MOD_LIGHT_BOTH,
.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_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 = {
@@ -209,107 +384,79 @@ static const struct regmap_config veml6030_regmap_config = {
.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_intgrn_tm(struct iio_dev *indio_dev,
- int *val, int *val2)
+static int veml6030_get_it(struct veml6030_data *data, int *val, int *val2)
{
- int ret, reg;
- struct veml6030_data *data = iio_priv(indio_dev);
+ int ret, it_idx;
- ret = regmap_read(data->regmap, VEML6030_REG_ALS_CONF, &reg);
- if (ret) {
- dev_err(&data->client->dev,
- "can't read als conf register %d\n", ret);
+ ret = regmap_field_read(data->rf.it, &it_idx);
+ if (ret)
return ret;
- }
- switch ((reg >> 6) & 0xF) {
- case 0:
- *val2 = 100000;
- break;
- case 1:
- *val2 = 200000;
- break;
- case 2:
- *val2 = 400000;
- break;
- case 3:
- *val2 = 800000;
- break;
- case 8:
- *val2 = 50000;
- break;
- case 12:
- *val2 = 25000;
- break;
- default:
- return -EINVAL;
- }
+ 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_intgrn_tm(struct iio_dev *indio_dev,
- int val, int val2)
+static int veml6030_set_it(struct iio_dev *indio_dev, int val, int val2)
{
- int ret, new_int_time, int_idx;
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)
+ if (val || !iio_gts_valid_time(&data->gts, val2))
return -EINVAL;
- switch (val2) {
- case 25000:
- new_int_time = 0x300;
- int_idx = 5;
- break;
- case 50000:
- new_int_time = 0x200;
- int_idx = 4;
- break;
- case 100000:
- new_int_time = 0x00;
- int_idx = 3;
- break;
- case 200000:
- new_int_time = 0x40;
- int_idx = 2;
- break;
- case 400000:
- new_int_time = 0x80;
- int_idx = 1;
- break;
- case 800000:
- new_int_time = 0xC0;
- int_idx = 0;
- break;
- default:
- return -EINVAL;
- }
+ ret = regmap_field_read(data->rf.it, &it_idx);
+ if (ret)
+ return ret;
- ret = regmap_update_bits(data->regmap, VEML6030_REG_ALS_CONF,
- VEML6030_ALS_IT, new_int_time);
- if (ret) {
- dev_err(&data->client->dev,
- "can't update als integration time %d\n", ret);
+ ret = regmap_field_read(data->rf.gain, &gain_idx);
+ if (ret)
return ret;
- }
- /*
- * Cache current integration time and update resolution. For every
- * increase in integration time to next level, resolution is halved
- * and vice-versa.
- */
- if (data->cur_integration_time < int_idx)
- data->cur_resolution <<= int_idx - data->cur_integration_time;
- else if (data->cur_integration_time > int_idx)
- data->cur_resolution >>= data->cur_integration_time - int_idx;
+ prev_it = iio_gts_find_int_time_by_sel(&data->gts, it_idx);
+ if (prev_it < 0)
+ return prev_it;
- data->cur_integration_time = int_idx;
+ if (prev_it == val2)
+ return 0;
- return ret;
+ 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,
@@ -318,7 +465,7 @@ static int veml6030_read_persistence(struct iio_dev *indio_dev,
int ret, reg, period, x, y;
struct veml6030_data *data = iio_priv(indio_dev);
- ret = veml6030_get_intgrn_tm(indio_dev, &x, &y);
+ ret = veml6030_get_it(data, &x, &y);
if (ret < 0)
return ret;
@@ -343,7 +490,7 @@ static int veml6030_write_persistence(struct iio_dev *indio_dev,
int ret, period, x, y;
struct veml6030_data *data = iio_priv(indio_dev);
- ret = veml6030_get_intgrn_tm(indio_dev, &x, &y);
+ ret = veml6030_get_it(data, &x, &y);
if (ret < 0)
return ret;
@@ -372,86 +519,29 @@ static int veml6030_write_persistence(struct iio_dev *indio_dev,
return ret;
}
-static int veml6030_set_als_gain(struct iio_dev *indio_dev,
- int val, int val2)
+static int veml6030_set_scale(struct iio_dev *indio_dev, int val, int val2)
{
- int ret, new_gain, gain_idx;
+ int ret, gain_sel, it_idx, it_sel;
struct veml6030_data *data = iio_priv(indio_dev);
- if (val == 0 && val2 == 125000) {
- new_gain = 0x1000; /* 0x02 << 11 */
- gain_idx = 3;
- } else if (val == 0 && val2 == 250000) {
- new_gain = 0x1800;
- gain_idx = 2;
- } else if (val == 1 && val2 == 0) {
- new_gain = 0x00;
- gain_idx = 1;
- } else if (val == 2 && val2 == 0) {
- new_gain = 0x800;
- gain_idx = 0;
- } else {
- return -EINVAL;
- }
-
- ret = regmap_update_bits(data->regmap, VEML6030_REG_ALS_CONF,
- VEML6030_ALS_GAIN, new_gain);
- if (ret) {
- dev_err(&data->client->dev,
- "can't set als gain %d\n", ret);
+ ret = regmap_field_read(data->rf.it, &it_idx);
+ if (ret)
return ret;
- }
- /*
- * Cache currently set gain & update resolution. For every
- * increase in the gain to next level, resolution is halved
- * and vice-versa.
- */
- if (data->cur_gain < gain_idx)
- data->cur_resolution <<= gain_idx - data->cur_gain;
- else if (data->cur_gain > gain_idx)
- data->cur_resolution >>= data->cur_gain - gain_idx;
-
- data->cur_gain = gain_idx;
-
- return ret;
-}
-
-static int veml6030_get_als_gain(struct iio_dev *indio_dev,
- int *val, int *val2)
-{
- int ret, reg;
- 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_read(data->regmap, VEML6030_REG_ALS_CONF, &reg);
- if (ret) {
- dev_err(&data->client->dev,
- "can't read als conf register %d\n", ret);
+ ret = regmap_field_write(data->rf.it, it_sel);
+ if (ret)
return ret;
- }
- switch ((reg >> 11) & 0x03) {
- case 0:
- *val = 1;
- *val2 = 0;
- break;
- case 1:
- *val = 2;
- *val2 = 0;
- break;
- case 2:
- *val = 0;
- *val2 = 125000;
- break;
- case 3:
- *val = 0;
- *val2 = 250000;
- break;
- default:
- return -EINVAL;
- }
+ ret = regmap_field_write(data->rf.gain, gain_sel);
+ if (ret)
+ return ret;
- return IIO_VAL_INT_PLUS_MICRO;
+ return 0;
}
static int veml6030_read_thresh(struct iio_dev *indio_dev,
@@ -498,6 +588,71 @@ static int veml6030_write_thresh(struct iio_dev *indio_dev,
return ret;
}
+static int veml6030_get_total_gain(struct veml6030_data *data)
+{
+ int gain, it, reg, ret;
+
+ ret = regmap_field_read(data->rf.gain, &reg);
+ 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, &reg);
+ 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, &reg);
+ 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, &reg);
+ 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
@@ -521,11 +676,9 @@ static int veml6030_read_raw(struct iio_dev *indio_dev,
dev_err(dev, "can't read als data %d\n", ret);
return ret;
}
- if (mask == IIO_CHAN_INFO_PROCESSED) {
- *val = (reg * data->cur_resolution) / 10000;
- *val2 = (reg * data->cur_resolution) % 10000;
- return IIO_VAL_INT_PLUS_MICRO;
- }
+ if (mask == IIO_CHAN_INFO_PROCESSED)
+ return veml6030_process_als(data, reg, val, val2);
+
*val = reg;
return IIO_VAL_INT;
case IIO_INTENSITY:
@@ -534,48 +687,60 @@ static int veml6030_read_raw(struct iio_dev *indio_dev,
dev_err(dev, "can't read white data %d\n", ret);
return ret;
}
- if (mask == IIO_CHAN_INFO_PROCESSED) {
- *val = (reg * data->cur_resolution) / 10000;
- *val2 = (reg * data->cur_resolution) % 10000;
- return IIO_VAL_INT_PLUS_MICRO;
- }
*val = reg;
return IIO_VAL_INT;
default:
return -EINVAL;
}
case IIO_CHAN_INFO_INT_TIME:
- if (chan->type == IIO_LIGHT)
- return veml6030_get_intgrn_tm(indio_dev, val, val2);
- return -EINVAL;
+ return veml6030_get_it(data, val, val2);
case IIO_CHAN_INFO_SCALE:
- if (chan->type == IIO_LIGHT)
- return veml6030_get_als_gain(indio_dev, val, val2);
- return -EINVAL;
+ 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:
- switch (chan->type) {
- case IIO_LIGHT:
- return veml6030_set_intgrn_tm(indio_dev, val, val2);
- default:
- return -EINVAL;
- }
+ return veml6030_set_it(indio_dev, val, val2);
case IIO_CHAN_INFO_SCALE:
- switch (chan->type) {
- case IIO_LIGHT:
- return veml6030_set_als_gain(indio_dev, val, val2);
- default:
- return -EINVAL;
- }
+ 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;
}
@@ -647,14 +812,11 @@ static int veml6030_read_interrupt_config(struct iio_dev *indio_dev,
*/
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, int state)
+ enum iio_event_direction dir, bool state)
{
int ret;
struct veml6030_data *data = iio_priv(indio_dev);
- if (state < 0 || state > 1)
- return -EINVAL;
-
ret = veml6030_als_shut_down(data);
if (ret < 0) {
dev_err(&data->client->dev,
@@ -674,19 +836,21 @@ static int veml6030_write_interrupt_config(struct iio_dev *indio_dev,
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,
- .attrs = &veml6030_attr_group,
.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,
- .attrs = &veml6030_attr_group,
+ .write_raw_get_fmt = veml6030_write_raw_get_fmt,
};
static irqreturn_t veml6030_event_handler(int irq, void *private)
@@ -718,72 +882,199 @@ static irqreturn_t veml6030_event_handler(int irq, void *private)
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),
+ &reg);
+ 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 dev_err_probe(&client->dev, ret,
+ "irq %d request failed\n",
+ client->irq);
+
+ 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)
+static int veml6030_hw_init(struct iio_dev *indio_dev, struct device *dev)
{
int ret, val;
struct veml6030_data *data = iio_priv(indio_dev);
- struct i2c_client *client = data->client;
+
+ 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) {
- dev_err(&client->dev, "can't shutdown als %d\n", ret);
- return ret;
- }
+ 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) {
- dev_err(&client->dev, "can't setup als configs %d\n", ret);
- return ret;
- }
+ 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) {
- dev_err(&client->dev, "can't setup default PSM %d\n", ret);
- return ret;
- }
+ 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) {
- dev_err(&client->dev, "can't setup high threshold %d\n", ret);
- return ret;
- }
+ 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) {
- dev_err(&client->dev, "can't setup low threshold %d\n", ret);
- return ret;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "can't setup low threshold\n");
ret = veml6030_als_pwr_on(data);
- if (ret) {
- dev_err(&client->dev, "can't poweron als %d\n", ret);
- return ret;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "can't poweron als\n");
- /* Wait 4 ms to let processor & oscillator start correctly */
- usleep_range(4000, 4002);
+ 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) {
- dev_err(&client->dev,
- "can't clear als interrupt status %d\n", ret);
+ 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;
- }
- /* Cache currently active measurement parameters */
- data->cur_gain = 3;
- data->cur_resolution = 4608;
- data->cur_integration_time = 3;
+ /* 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;
+ return 0;
}
static int veml6030_probe(struct i2c_client *client)
@@ -793,16 +1084,14 @@ static int veml6030_probe(struct i2c_client *client)
struct iio_dev *indio_dev;
struct regmap *regmap;
- if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
- dev_err(&client->dev, "i2c adapter doesn't support plain i2c\n");
- return -EOPNOTSUPP;
- }
+ 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)) {
- dev_err(&client->dev, "can't setup regmap\n");
- return PTR_ERR(regmap);
- }
+ 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)
@@ -813,35 +1102,39 @@ static int veml6030_probe(struct i2c_client *client)
data->client = client;
data->regmap = regmap;
- indio_dev->name = "veml6030";
- indio_dev->channels = veml6030_channels;
- indio_dev->num_channels = ARRAY_SIZE(veml6030_channels);
- indio_dev->modes = INDIO_DIRECT_MODE;
+ ret = devm_regulator_get_enable(&client->dev, "vdd");
+ if (ret)
+ return dev_err_probe(&client->dev, ret,
+ "failed to enable regulator\n");
- if (client->irq) {
- ret = devm_request_threaded_irq(&client->dev, client->irq,
- NULL, veml6030_event_handler,
- IRQF_TRIGGER_LOW | IRQF_ONESHOT,
- "veml6030", indio_dev);
- if (ret < 0) {
- dev_err(&client->dev,
- "irq %d request failed\n", client->irq);
- return ret;
- }
- indio_dev->info = &veml6030_info;
- } else {
- indio_dev->info = &veml6030_info_no_irq;
- }
+ 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 = veml6030_hw_init(indio_dev);
+ ret = data->chip->set_info(indio_dev);
if (ret < 0)
return ret;
- ret = devm_add_action_or_reset(&client->dev,
- veml6030_als_shut_down_action, data);
+ 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);
}
@@ -874,14 +1167,60 @@ static int veml6030_runtime_resume(struct device *dev)
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" },
+ {
+ .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[] = {
- { "veml6030", 0 },
+ { "veml6030", (kernel_ulong_t)&veml6030_chip},
+ { "veml6035", (kernel_ulong_t)&veml6035_chip},
+ { "veml7700", (kernel_ulong_t)&veml7700_chip},
{ }
};
MODULE_DEVICE_TABLE(i2c, veml6030_id);
@@ -892,7 +1231,7 @@ static struct i2c_driver veml6030_driver = {
.of_match_table = veml6030_of_match,
.pm = pm_ptr(&veml6030_pm_ops),
},
- .probe_new = veml6030_probe,
+ .probe = veml6030_probe,
.id_table = veml6030_id,
};
module_i2c_driver(veml6030_driver);
@@ -900,3 +1239,4 @@ 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 000000000000..f563f9f0ee67
--- /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, &reg);
+ 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, &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[] = {
+ {"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 000000000000..e60f24d46e7b
--- /dev/null
+++ b/drivers/iio/light/veml6046x00.c
@@ -0,0 +1,1030 @@
+// 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/mod_devicetable.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, &reg);
+ 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, &reg);
+ 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, &reg);
+ 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,
+ &reg, 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, &reg, 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,
+ &reg, 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,
+ &reg16, 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,
+ &reg16, 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,
+ &reg16, 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,
+ &reg16, 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[] = {
+ { "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
index ee76a68deb24..6d4483c85f30 100644
--- a/drivers/iio/light/veml6070.c
+++ b/drivers/iio/light/veml6070.c
@@ -6,14 +6,16 @@
*
* IIO driver for VEML6070 (7-bit I2C slave addresses 0x38 and 0x39)
*
- * TODO: integration time, ACK signal
+ * 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>
@@ -28,50 +30,113 @@
#define VEML6070_COMMAND_RSRVD BIT(1) /* reserved, set to 1 */
#define VEML6070_COMMAND_SD BIT(0) /* shutdown mode when set */
-#define VEML6070_IT_10 0x04 /* integration time 1x */
+#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;
+ int ret, it_ms, val, val2;
u8 msb, lsb;
- mutex_lock(&data->lock);
+ guard(mutex)(&data->lock);
/* disable shutdown */
ret = i2c_smbus_write_byte(data->client1,
data->config & ~VEML6070_COMMAND_SD);
if (ret < 0)
- goto out;
+ return ret;
- msleep(125 + 10); /* measurement takes up to 125 ms for IT 1x */
+ 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)
- goto out;
+ return ret;
+
msb = ret;
ret = i2c_smbus_read_byte(data->client1); /* read LSB, address 0x38 */
if (ret < 0)
- goto out;
+ return ret;
+
lsb = ret;
/* shutdown again */
ret = i2c_smbus_write_byte(data->client1, data->config);
if (ret < 0)
- goto out;
+ return ret;
ret = (msb << 8) | lsb;
-out:
- mutex_unlock(&data->lock);
- return ret;
+ return 0;
}
static const struct iio_chan_spec veml6070_channels[] = {
@@ -80,26 +145,37 @@ static const struct iio_chan_spec veml6070_channels[] = {
.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(unsigned val)
+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 (default: 270 KOhm)
+ * the RSET pin.
*/
- unsigned uvi[11] = {
+ unsigned int uvi[11] = {
187, 373, 560, /* low */
746, 933, 1120, /* moderate */
1308, 1494, /* high */
1681, 1868, 2054}; /* very high */
- int i;
+ 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])
@@ -122,10 +198,44 @@ static int veml6070_read_raw(struct iio_dev *indio_dev,
if (ret < 0)
return ret;
if (mask == IIO_CHAN_INFO_PROCESSED)
- *val = veml6070_to_uv_index(ret);
+ *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;
}
@@ -133,8 +243,17 @@ static int veml6070_read_raw(struct iio_dev *indio_dev,
static const struct iio_info veml6070_info = {
.read_raw = veml6070_read_raw,
+ .read_avail = veml6070_read_avail,
+ .write_raw = veml6070_write_raw,
};
+static void veml6070_i2c_unreg(void *p)
+{
+ struct veml6070_data *data = p;
+
+ i2c_unregister_device(data->client2);
+}
+
static int veml6070_probe(struct i2c_client *client)
{
struct veml6070_data *data;
@@ -156,50 +275,50 @@ static int veml6070_probe(struct i2c_client *client)
indio_dev->name = VEML6070_DRV_NAME;
indio_dev->modes = INDIO_DIRECT_MODE;
- data->client2 = i2c_new_dummy_device(client->adapter, VEML6070_ADDR_DATA_LSB);
- if (IS_ERR(data->client2)) {
- dev_err(&client->dev, "i2c device for second chip address failed\n");
- return PTR_ERR(data->client2);
- }
-
- data->config = VEML6070_IT_10 | VEML6070_COMMAND_RSRVD |
- VEML6070_COMMAND_SD;
- ret = i2c_smbus_write_byte(data->client1, data->config);
+ ret = veml6070_calc_it(&client->dev, data);
if (ret < 0)
- goto fail;
+ return ret;
- ret = iio_device_register(indio_dev);
+ ret = devm_regulator_get_enable(&client->dev, "vdd");
if (ret < 0)
- goto fail;
+ return ret;
- return ret;
+ data->client2 = i2c_new_dummy_device(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");
-fail:
- i2c_unregister_device(data->client2);
- return ret;
-}
+ 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;
-static void veml6070_remove(struct i2c_client *client)
-{
- struct iio_dev *indio_dev = i2c_get_clientdata(client);
- struct veml6070_data *data = iio_priv(indio_dev);
+ ret = devm_add_action_or_reset(&client->dev, veml6070_i2c_unreg, data);
+ if (ret < 0)
+ return ret;
- iio_device_unregister(indio_dev);
- i2c_unregister_device(data->client2);
+ return devm_iio_device_register(&client->dev, indio_dev);
}
static const struct i2c_device_id veml6070_id[] = {
- { "veml6070", 0 },
+ { "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_new = veml6070_probe,
- .remove = veml6070_remove,
+ .probe = veml6070_probe,
.id_table = veml6070_id,
};
diff --git a/drivers/iio/light/veml6075.c b/drivers/iio/light/veml6075.c
new file mode 100644
index 000000000000..edbb43407054
--- /dev/null
+++ b/drivers/iio/light/veml6075.c
@@ -0,0 +1,478 @@
+// 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;
+
+ 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_time = veml6075_it_ms[FIELD_GET(VEML6075_CONF_IT, conf)];
+ 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[] = {
+ { "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
index 8b56df26c59e..c1314b144367 100644
--- a/drivers/iio/light/vl6180.c
+++ b/drivers/iio/light/vl6180.c
@@ -20,12 +20,15 @@
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/err.h>
-#include <linux/of.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"
@@ -39,7 +42,9 @@
#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
@@ -85,8 +90,12 @@
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 };
@@ -208,29 +217,40 @@ static int vl6180_write_word(struct i2c_client *client, u16 cmd, u16 val)
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;
- while (tries--) {
- ret = vl6180_read_byte(client, VL6180_INTR_STATUS);
- if (ret < 0)
+ 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 (ret & vl6180_chan_regs_table[addr].drdy_mask)
- break;
- msleep(20);
- }
-
- if (tries < 0) {
- ret = -EIO;
- goto fail;
+ if (tries < 0) {
+ ret = -EIO;
+ goto fail;
+ }
}
/* Read result value from appropriate registers */
@@ -259,20 +279,41 @@ 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_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_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),
};
/*
@@ -334,6 +375,18 @@ static int vl6180_read_raw(struct iio_dev *indio_dev,
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;
}
@@ -413,11 +466,23 @@ fail:
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:
@@ -428,18 +493,131 @@ static int vl6180_write_raw(struct iio_dev *indio_dev,
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 int vl6180_init(struct vl6180_data *data)
+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;
@@ -474,6 +652,26 @@ static int vl6180_init(struct vl6180_data *data)
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);
@@ -514,21 +712,45 @@ static int vl6180_probe(struct i2c_client *client)
indio_dev->name = VL6180_DRV_NAME;
indio_dev->modes = INDIO_DIRECT_MODE;
- ret = vl6180_init(data);
+ 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 dev_err_probe(&client->dev, ret, "devm_request_irq error\n");
+
+ 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[] = {
- { "vl6180", 0 },
+ { "vl6180" },
{ }
};
MODULE_DEVICE_TABLE(i2c, vl6180_id);
@@ -538,7 +760,7 @@ static struct i2c_driver vl6180_driver = {
.name = VL6180_DRV_NAME,
.of_match_table = vl6180_of_match,
},
- .probe_new = vl6180_probe,
+ .probe = vl6180_probe,
.id_table = vl6180_id,
};
diff --git a/drivers/iio/light/zopt2201.c b/drivers/iio/light/zopt2201.c
index e3bac8b56380..1dba1b949cc3 100644
--- a/drivers/iio/light/zopt2201.c
+++ b/drivers/iio/light/zopt2201.c
@@ -19,7 +19,7 @@
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#define ZOPT2201_DRV_NAME "zopt2201"
@@ -113,11 +113,13 @@ static const struct {
{ 13, 3125 },
};
-static const struct {
+struct zopt2201_scale {
unsigned int scale, uscale; /* scale factor as integer + micro */
u8 gain; /* gain register value */
u8 res; /* resolution register value */
-} zopt2201_scale_als[] = {
+};
+
+static const struct zopt2201_scale zopt2201_scale_als[] = {
{ 19, 200000, 0, 5 },
{ 6, 400000, 1, 5 },
{ 3, 200000, 2, 5 },
@@ -142,11 +144,7 @@ static const struct {
{ 0, 8333, 4, 0 },
};
-static const struct {
- unsigned int scale, uscale; /* scale factor as integer + micro */
- u8 gain; /* gain register value */
- u8 res; /* resolution register value */
-} zopt2201_scale_uvb[] = {
+static const struct zopt2201_scale zopt2201_scale_uvb[] = {
{ 0, 460800, 0, 5 },
{ 0, 153600, 1, 5 },
{ 0, 76800, 2, 5 },
@@ -348,16 +346,17 @@ static int zopt2201_set_gain(struct zopt2201_data *data, u8 gain)
return 0;
}
-static int zopt2201_write_scale_als_by_idx(struct zopt2201_data *data, int idx)
+static int zopt2201_write_scale_by_idx(struct zopt2201_data *data, int idx,
+ const struct zopt2201_scale *zopt2201_scale_array)
{
int ret;
mutex_lock(&data->lock);
- ret = zopt2201_set_resolution(data, zopt2201_scale_als[idx].res);
+ ret = zopt2201_set_resolution(data, zopt2201_scale_array[idx].res);
if (ret < 0)
goto unlock;
- ret = zopt2201_set_gain(data, zopt2201_scale_als[idx].gain);
+ ret = zopt2201_set_gain(data, zopt2201_scale_array[idx].gain);
unlock:
mutex_unlock(&data->lock);
@@ -371,29 +370,12 @@ static int zopt2201_write_scale_als(struct zopt2201_data *data,
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_als_by_idx(data, i);
- }
+ 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_by_idx(struct zopt2201_data *data, int idx)
-{
- int ret;
-
- mutex_lock(&data->lock);
- ret = zopt2201_set_resolution(data, zopt2201_scale_als[idx].res);
- if (ret < 0)
- goto unlock;
-
- ret = zopt2201_set_gain(data, zopt2201_scale_als[idx].gain);
-
-unlock:
- mutex_unlock(&data->lock);
- return ret;
-}
-
static int zopt2201_write_scale_uvb(struct zopt2201_data *data,
int val, int val2)
{
@@ -402,7 +384,7 @@ static int zopt2201_write_scale_uvb(struct zopt2201_data *data,
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_uvb_by_idx(data, i);
+ return zopt2201_write_scale_by_idx(data, i, zopt2201_scale_uvb);
return -EINVAL;
}
@@ -545,7 +527,7 @@ static int zopt2201_probe(struct i2c_client *client)
}
static const struct i2c_device_id zopt2201_id[] = {
- { "zopt2201", 0 },
+ { "zopt2201" },
{ }
};
MODULE_DEVICE_TABLE(i2c, zopt2201_id);
@@ -554,7 +536,7 @@ static struct i2c_driver zopt2201_driver = {
.driver = {
.name = ZOPT2201_DRV_NAME,
},
- .probe_new = zopt2201_probe,
+ .probe = zopt2201_probe,
.id_table = zopt2201_id,
};