summaryrefslogtreecommitdiffstats
path: root/kernel/drivers/iio/light
diff options
context:
space:
mode:
authorJosé Pekkarinen <jose.pekkarinen@nokia.com>2016-04-11 10:41:07 +0300
committerJosé Pekkarinen <jose.pekkarinen@nokia.com>2016-04-13 08:17:18 +0300
commite09b41010ba33a20a87472ee821fa407a5b8da36 (patch)
treed10dc367189862e7ca5c592f033dc3726e1df4e3 /kernel/drivers/iio/light
parentf93b97fd65072de626c074dbe099a1fff05ce060 (diff)
These changes are the raw update to linux-4.4.6-rt14. Kernel sources
are taken from kernel.org, and rt patch from the rt wiki download page. During the rebasing, the following patch collided: Force tick interrupt and get rid of softirq magic(I70131fb85). Collisions have been removed because its logic was found on the source already. Change-Id: I7f57a4081d9deaa0d9ccfc41a6c8daccdee3b769 Signed-off-by: José Pekkarinen <jose.pekkarinen@nokia.com>
Diffstat (limited to 'kernel/drivers/iio/light')
-rw-r--r--kernel/drivers/iio/light/Kconfig96
-rw-r--r--kernel/drivers/iio/light/Makefile8
-rw-r--r--kernel/drivers/iio/light/acpi-als.c233
-rw-r--r--kernel/drivers/iio/light/apds9300.c1
-rw-r--r--kernel/drivers/iio/light/apds9960.c1131
-rw-r--r--kernel/drivers/iio/light/bh1750.c333
-rw-r--r--kernel/drivers/iio/light/cm32181.c2
-rw-r--r--kernel/drivers/iio/light/cm3232.c2
-rw-r--r--kernel/drivers/iio/light/cm3323.c19
-rw-r--r--kernel/drivers/iio/light/cm36651.c2
-rw-r--r--kernel/drivers/iio/light/gp2ap020a00f.c2
-rw-r--r--kernel/drivers/iio/light/hid-sensor-als.c14
-rw-r--r--kernel/drivers/iio/light/hid-sensor-prox.c5
-rw-r--r--kernel/drivers/iio/light/isl29125.c13
-rw-r--r--kernel/drivers/iio/light/jsa1212.c1
-rw-r--r--kernel/drivers/iio/light/ltr501.c1285
-rw-r--r--kernel/drivers/iio/light/opt3001.c803
-rw-r--r--kernel/drivers/iio/light/pa12203001.c483
-rw-r--r--kernel/drivers/iio/light/rpr0521.c615
-rw-r--r--kernel/drivers/iio/light/stk3310.c698
-rw-r--r--kernel/drivers/iio/light/tcs3414.c1
-rw-r--r--kernel/drivers/iio/light/tcs3472.c1
-rw-r--r--kernel/drivers/iio/light/tsl2563.c36
-rw-r--r--kernel/drivers/iio/light/tsl4531.c20
-rw-r--r--kernel/drivers/iio/light/us5182d.c507
-rw-r--r--kernel/drivers/iio/light/vcnl4000.c1
26 files changed, 6171 insertions, 141 deletions
diff --git a/kernel/drivers/iio/light/Kconfig b/kernel/drivers/iio/light/Kconfig
index 01a1a16ab..cfd3df841 100644
--- a/kernel/drivers/iio/light/Kconfig
+++ b/kernel/drivers/iio/light/Kconfig
@@ -5,6 +5,19 @@
menu "Light sensors"
+config ACPI_ALS
+ tristate "ACPI Ambient Light Sensor"
+ depends on ACPI
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ select IIO_KFIFO_BUF
+ help
+ Say Y here if you want to build a driver for the ACPI0008
+ Ambient Light Sensor.
+
+ To compile this driver as a module, choose M here: the module will
+ be called acpi-als.
+
config ADJD_S311
tristate "ADJD-S311-CR999 digital color sensor"
select IIO_BUFFER
@@ -37,6 +50,29 @@ config APDS9300
To compile this driver as a module, choose M here: the
module will be called apds9300.
+config APDS9960
+ tristate "Avago APDS9960 gesture/RGB/ALS/proximity sensor"
+ select REGMAP_I2C
+ select IIO_BUFFER
+ select IIO_KFIFO_BUF
+ depends on I2C
+ help
+ Say Y here to build I2C interface support for the Avago
+ APDS9960 gesture/RGB/ALS/proximity sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called apds9960
+
+config BH1750
+ tristate "ROHM BH1750 ambient light sensor"
+ depends on I2C
+ help
+ Say Y here to build support for the ROHM BH1710, BH1715, BH1721,
+ BH1750, BH1751 ambient light sensors.
+
+ To compile this driver as a module, choose M here: the module will
+ be called bh1750.
+
config CM32181
depends on I2C
tristate "CM32181 driver"
@@ -63,7 +99,7 @@ config CM3323
depends on I2C
tristate "Capella CM3323 color light sensor"
help
- Say Y here if you want to build a driver for Capela CM3323
+ Say Y here if you want to build a driver for Capella CM3323
color sensor.
To compile this driver as a module, choose M here: the module will
@@ -145,6 +181,17 @@ config JSA1212
To compile this driver as a module, choose M here:
the module will be called jsa1212.
+config RPR0521
+ tristate "ROHM RPR0521 ALS and proximity sensor driver"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say Y here if you want to build support for ROHM's RPR0521
+ ambient light and proximity sensor device.
+
+ To compile this driver as a module, choose M here:
+ the module will be called rpr0521.
+
config SENSORS_LM3533
tristate "LM3533 ambient light sensor"
depends on MFD_LM3533
@@ -165,15 +212,50 @@ config SENSORS_LM3533
config LTR501
tristate "LTR-501ALS-01 light sensor"
depends on I2C
+ select REGMAP_I2C
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
help
If you say yes here you get support for the Lite-On LTR-501ALS-01
- ambient light and proximity sensor.
+ ambient light and proximity sensor. This driver also supports LTR-559
+ ALS/PS or LTR-301 ALS sensors.
This driver can also be built as a module. If so, the module
will be called ltr501.
+config OPT3001
+ tristate "Texas Instruments OPT3001 Light Sensor"
+ depends on I2C
+ help
+ If you say Y or M here, you get support for Texas Instruments
+ OPT3001 Ambient Light Sensor.
+
+ If built as a dynamically linked module, it will be called
+ opt3001.
+
+config PA12203001
+ tristate "TXC PA12203001 light and proximity sensor"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for the TXC PA12203001
+ ambient light and proximity sensor.
+
+ This driver can also be built as a module. If so, the module
+ will be called pa12203001.
+
+config STK3310
+ tristate "STK3310 ALS and proximity sensor"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say yes here to get support for the Sensortek STK3310 ambient light
+ and proximity sensor. The STK3311 model is also supported by this
+ driver.
+
+ Choosing M will build the driver as a module. If so, the module
+ will be called stk3310.
+
config TCS3414
tristate "TAOS TCS3414 digital color sensor"
depends on I2C
@@ -218,6 +300,16 @@ config TSL4531
To compile this driver as a module, choose M here: the
module will be called tsl4531.
+config US5182D
+ tristate "UPISEMI light and proximity sensor"
+ depends on I2C
+ help
+ If you say yes here you get support for the UPISEMI US5182D
+ ambient light and proximity sensor.
+
+ This driver can also be built as a module. If so, the module
+ will be called us5182d.
+
config VCNL4000
tristate "VCNL4000 combined ALS and proximity sensor"
depends on I2C
diff --git a/kernel/drivers/iio/light/Makefile b/kernel/drivers/iio/light/Makefile
index ad7c30fe4..b2c31053d 100644
--- a/kernel/drivers/iio/light/Makefile
+++ b/kernel/drivers/iio/light/Makefile
@@ -3,9 +3,12 @@
#
# When adding new entries keep the list in alphabetical order
+obj-$(CONFIG_ACPI_ALS) += acpi-als.o
obj-$(CONFIG_ADJD_S311) += adjd_s311.o
obj-$(CONFIG_AL3320A) += al3320a.o
obj-$(CONFIG_APDS9300) += apds9300.o
+obj-$(CONFIG_APDS9960) += apds9960.o
+obj-$(CONFIG_BH1750) += bh1750.o
obj-$(CONFIG_CM32181) += cm32181.o
obj-$(CONFIG_CM3232) += cm3232.o
obj-$(CONFIG_CM3323) += cm3323.o
@@ -17,8 +20,13 @@ obj-$(CONFIG_ISL29125) += isl29125.o
obj-$(CONFIG_JSA1212) += jsa1212.o
obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o
obj-$(CONFIG_LTR501) += ltr501.o
+obj-$(CONFIG_OPT3001) += opt3001.o
+obj-$(CONFIG_PA12203001) += pa12203001.o
+obj-$(CONFIG_RPR0521) += rpr0521.o
obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o
+obj-$(CONFIG_STK3310) += stk3310.o
obj-$(CONFIG_TCS3414) += tcs3414.o
obj-$(CONFIG_TCS3472) += tcs3472.o
obj-$(CONFIG_TSL4531) += tsl4531.o
+obj-$(CONFIG_US5182D) += us5182d.o
obj-$(CONFIG_VCNL4000) += vcnl4000.o
diff --git a/kernel/drivers/iio/light/acpi-als.c b/kernel/drivers/iio/light/acpi-als.c
new file mode 100644
index 000000000..53201d99a
--- /dev/null
+++ b/kernel/drivers/iio/light/acpi-als.c
@@ -0,0 +1,233 @@
+/*
+ * ACPI Ambient Light Sensor Driver
+ *
+ * Based on ALS driver:
+ * Copyright (C) 2009 Zhang Rui <rui.zhang@intel.com>
+ *
+ * Rework for IIO subsystem:
+ * Copyright (C) 2012-2013 Martin Liska <marxin.liska@gmail.com>
+ *
+ * Final cleanup and debugging:
+ * Copyright (C) 2013-2014 Marek Vasut <marex@denx.de>
+ * Copyright (C) 2015 Gabriele Mazzotta <gabriele.mzt@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/kfifo_buf.h>
+
+#define ACPI_ALS_CLASS "als"
+#define ACPI_ALS_DEVICE_NAME "acpi-als"
+#define ACPI_ALS_NOTIFY_ILLUMINANCE 0x80
+
+ACPI_MODULE_NAME("acpi-als");
+
+/*
+ * So far, there's only one channel in here, but the specification for
+ * ACPI0008 says there can be more to what the block can report. Like
+ * chromaticity and such. We are ready for incoming additions!
+ */
+static const struct iio_chan_spec acpi_als_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 32,
+ .storagebits = 32,
+ },
+ /* _RAW is here for backward ABI compatibility */
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_PROCESSED),
+ },
+};
+
+/*
+ * The event buffer contains timestamp and all the data from
+ * the ACPI0008 block. There are multiple, but so far we only
+ * support _ALI (illuminance). Once someone adds new channels
+ * to acpi_als_channels[], the evt_buffer below will grow
+ * automatically.
+ */
+#define ACPI_ALS_EVT_NR_SOURCES ARRAY_SIZE(acpi_als_channels)
+#define ACPI_ALS_EVT_BUFFER_SIZE \
+ (sizeof(s64) + (ACPI_ALS_EVT_NR_SOURCES * sizeof(s32)))
+
+struct acpi_als {
+ struct acpi_device *device;
+ struct mutex lock;
+
+ s32 evt_buffer[ACPI_ALS_EVT_BUFFER_SIZE];
+};
+
+/*
+ * All types of properties the ACPI0008 block can report. The ALI, ALC, ALT
+ * and ALP can all be handled by acpi_als_read_value() below, while the ALR is
+ * special.
+ *
+ * The _ALR property returns tables that can be used to fine-tune the values
+ * reported by the other props based on the particular hardware type and it's
+ * location (it contains tables for "rainy", "bright inhouse lighting" etc.).
+ *
+ * So far, we support only ALI (illuminance).
+ */
+#define ACPI_ALS_ILLUMINANCE "_ALI"
+#define ACPI_ALS_CHROMATICITY "_ALC"
+#define ACPI_ALS_COLOR_TEMP "_ALT"
+#define ACPI_ALS_POLLING "_ALP"
+#define ACPI_ALS_TABLES "_ALR"
+
+static int acpi_als_read_value(struct acpi_als *als, char *prop, s32 *val)
+{
+ unsigned long long temp_val;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(als->device->handle, prop, NULL,
+ &temp_val);
+
+ if (ACPI_FAILURE(status)) {
+ ACPI_EXCEPTION((AE_INFO, status, "Error reading ALS %s", prop));
+ return -EIO;
+ }
+
+ *val = temp_val;
+
+ return 0;
+}
+
+static void acpi_als_notify(struct acpi_device *device, u32 event)
+{
+ struct iio_dev *indio_dev = acpi_driver_data(device);
+ struct acpi_als *als = iio_priv(indio_dev);
+ s32 *buffer = als->evt_buffer;
+ s64 time_ns = iio_get_time_ns();
+ s32 val;
+ int ret;
+
+ mutex_lock(&als->lock);
+
+ memset(buffer, 0, ACPI_ALS_EVT_BUFFER_SIZE);
+
+ switch (event) {
+ case ACPI_ALS_NOTIFY_ILLUMINANCE:
+ ret = acpi_als_read_value(als, ACPI_ALS_ILLUMINANCE, &val);
+ if (ret < 0)
+ goto out;
+ *buffer++ = val;
+ break;
+ default:
+ /* Unhandled event */
+ dev_dbg(&device->dev, "Unhandled ACPI ALS event (%08x)!\n",
+ event);
+ goto out;
+ }
+
+ iio_push_to_buffers_with_timestamp(indio_dev, als->evt_buffer, time_ns);
+
+out:
+ mutex_unlock(&als->lock);
+}
+
+static int acpi_als_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct acpi_als *als = iio_priv(indio_dev);
+ s32 temp_val;
+ int ret;
+
+ if ((mask != IIO_CHAN_INFO_PROCESSED) && (mask != IIO_CHAN_INFO_RAW))
+ return -EINVAL;
+
+ /* we support only illumination (_ALI) so far. */
+ if (chan->type != IIO_LIGHT)
+ return -EINVAL;
+
+ ret = acpi_als_read_value(als, ACPI_ALS_ILLUMINANCE, &temp_val);
+ if (ret < 0)
+ return ret;
+
+ *val = temp_val;
+
+ return IIO_VAL_INT;
+}
+
+static const struct iio_info acpi_als_info = {
+ .driver_module = THIS_MODULE,
+ .read_raw = acpi_als_read_raw,
+};
+
+static int acpi_als_add(struct acpi_device *device)
+{
+ struct acpi_als *als;
+ struct iio_dev *indio_dev;
+ struct iio_buffer *buffer;
+
+ indio_dev = devm_iio_device_alloc(&device->dev, sizeof(*als));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ als = iio_priv(indio_dev);
+
+ device->driver_data = indio_dev;
+ als->device = device;
+ mutex_init(&als->lock);
+
+ indio_dev->name = ACPI_ALS_DEVICE_NAME;
+ indio_dev->dev.parent = &device->dev;
+ indio_dev->info = &acpi_als_info;
+ indio_dev->modes = INDIO_BUFFER_SOFTWARE;
+ indio_dev->channels = acpi_als_channels;
+ indio_dev->num_channels = ARRAY_SIZE(acpi_als_channels);
+
+ buffer = devm_iio_kfifo_allocate(&device->dev);
+ if (!buffer)
+ return -ENOMEM;
+
+ iio_device_attach_buffer(indio_dev, buffer);
+
+ return devm_iio_device_register(&device->dev, indio_dev);
+}
+
+static const struct acpi_device_id acpi_als_device_ids[] = {
+ {"ACPI0008", 0},
+ {},
+};
+
+MODULE_DEVICE_TABLE(acpi, acpi_als_device_ids);
+
+static struct acpi_driver acpi_als_driver = {
+ .name = "acpi_als",
+ .class = ACPI_ALS_CLASS,
+ .ids = acpi_als_device_ids,
+ .ops = {
+ .add = acpi_als_add,
+ .notify = acpi_als_notify,
+ },
+};
+
+module_acpi_driver(acpi_als_driver);
+
+MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
+MODULE_AUTHOR("Martin Liska <marxin.liska@gmail.com>");
+MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
+MODULE_DESCRIPTION("ACPI Ambient Light Sensor Driver");
+MODULE_LICENSE("GPL");
diff --git a/kernel/drivers/iio/light/apds9300.c b/kernel/drivers/iio/light/apds9300.c
index 9ddde0ca9..e1b9fa5a7 100644
--- a/kernel/drivers/iio/light/apds9300.c
+++ b/kernel/drivers/iio/light/apds9300.c
@@ -515,7 +515,6 @@ MODULE_DEVICE_TABLE(i2c, apds9300_id);
static struct i2c_driver apds9300_driver = {
.driver = {
.name = APDS9300_DRV_NAME,
- .owner = THIS_MODULE,
.pm = APDS9300_PM_OPS,
},
.probe = apds9300_probe,
diff --git a/kernel/drivers/iio/light/apds9960.c b/kernel/drivers/iio/light/apds9960.c
new file mode 100644
index 000000000..f6a07dc32
--- /dev/null
+++ b/kernel/drivers/iio/light/apds9960.c
@@ -0,0 +1,1131 @@
+/*
+ * apds9960.c - Support for Avago APDS9960 gesture/RGB/ALS/proximity sensor
+ *
+ * Copyright (C) 2015 Matt Ranostay <mranostay@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * TODO: gesture + proximity calib offsets
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/events.h>
+#include <linux/iio/kfifo_buf.h>
+#include <linux/iio/sysfs.h>
+#include <linux/of_gpio.h>
+
+#define APDS9960_REGMAP_NAME "apds9960_regmap"
+#define APDS9960_DRV_NAME "apds9960"
+
+#define APDS9960_REG_RAM_START 0x00
+#define APDS9960_REG_RAM_END 0x7f
+
+#define APDS9960_REG_ENABLE 0x80
+#define APDS9960_REG_ATIME 0x81
+#define APDS9960_REG_WTIME 0x83
+
+#define APDS9960_REG_AILTL 0x84
+#define APDS9960_REG_AILTH 0x85
+#define APDS9960_REG_AIHTL 0x86
+#define APDS9960_REG_AIHTH 0x87
+
+#define APDS9960_REG_PILT 0x89
+#define APDS9960_REG_PIHT 0x8b
+#define APDS9960_REG_PERS 0x8c
+
+#define APDS9960_REG_CONFIG_1 0x8d
+#define APDS9960_REG_PPULSE 0x8e
+
+#define APDS9960_REG_CONTROL 0x8f
+#define APDS9960_REG_CONTROL_AGAIN_MASK 0x03
+#define APDS9960_REG_CONTROL_PGAIN_MASK 0x0c
+#define APDS9960_REG_CONTROL_AGAIN_MASK_SHIFT 0
+#define APDS9960_REG_CONTROL_PGAIN_MASK_SHIFT 2
+
+#define APDS9960_REG_CONFIG_2 0x90
+#define APDS9960_REG_CONFIG_2_GGAIN_MASK 0x60
+#define APDS9960_REG_CONFIG_2_GGAIN_MASK_SHIFT 5
+
+#define APDS9960_REG_ID 0x92
+
+#define APDS9960_REG_STATUS 0x93
+#define APDS9960_REG_STATUS_PS_INT BIT(5)
+#define APDS9960_REG_STATUS_ALS_INT BIT(4)
+#define APDS9960_REG_STATUS_GINT BIT(2)
+
+#define APDS9960_REG_PDATA 0x9c
+#define APDS9960_REG_POFFSET_UR 0x9d
+#define APDS9960_REG_POFFSET_DL 0x9e
+#define APDS9960_REG_CONFIG_3 0x9f
+
+#define APDS9960_REG_GPENTH 0xa0
+#define APDS9960_REG_GEXTH 0xa1
+
+#define APDS9960_REG_GCONF_1 0xa2
+#define APDS9960_REG_GCONF_1_GFIFO_THRES_MASK 0xc0
+#define APDS9960_REG_GCONF_1_GFIFO_THRES_MASK_SHIFT 6
+
+#define APDS9960_REG_GCONF_2 0xa3
+#define APDS9960_REG_GOFFSET_U 0xa4
+#define APDS9960_REG_GOFFSET_D 0xa5
+#define APDS9960_REG_GPULSE 0xa6
+#define APDS9960_REG_GOFFSET_L 0xa7
+#define APDS9960_REG_GOFFSET_R 0xa9
+#define APDS9960_REG_GCONF_3 0xaa
+
+#define APDS9960_REG_GCONF_4 0xab
+#define APDS9960_REG_GFLVL 0xae
+#define APDS9960_REG_GSTATUS 0xaf
+
+#define APDS9960_REG_IFORCE 0xe4
+#define APDS9960_REG_PICLEAR 0xe5
+#define APDS9960_REG_CICLEAR 0xe6
+#define APDS9960_REG_AICLEAR 0xe7
+
+#define APDS9960_DEFAULT_PERS 0x33
+#define APDS9960_DEFAULT_GPENTH 0x50
+#define APDS9960_DEFAULT_GEXTH 0x40
+
+#define APDS9960_MAX_PXS_THRES_VAL 255
+#define APDS9960_MAX_ALS_THRES_VAL 0xffff
+#define APDS9960_MAX_INT_TIME_IN_US 1000000
+
+enum apds9960_als_channel_idx {
+ IDX_ALS_CLEAR, IDX_ALS_RED, IDX_ALS_GREEN, IDX_ALS_BLUE,
+};
+
+#define APDS9960_REG_ALS_BASE 0x94
+#define APDS9960_REG_ALS_CHANNEL(_colour) \
+ (APDS9960_REG_ALS_BASE + (IDX_ALS_##_colour * 2))
+
+enum apds9960_gesture_channel_idx {
+ IDX_DIR_UP, IDX_DIR_DOWN, IDX_DIR_LEFT, IDX_DIR_RIGHT,
+};
+
+#define APDS9960_REG_GFIFO_BASE 0xfc
+#define APDS9960_REG_GFIFO_DIR(_dir) \
+ (APDS9960_REG_GFIFO_BASE + IDX_DIR_##_dir)
+
+struct apds9960_data {
+ struct i2c_client *client;
+ struct iio_dev *indio_dev;
+ struct mutex lock;
+
+ /* regmap fields */
+ struct regmap *regmap;
+ struct regmap_field *reg_int_als;
+ struct regmap_field *reg_int_ges;
+ struct regmap_field *reg_int_pxs;
+
+ struct regmap_field *reg_enable_als;
+ struct regmap_field *reg_enable_ges;
+ struct regmap_field *reg_enable_pxs;
+
+ /* state */
+ int als_int;
+ int pxs_int;
+ int gesture_mode_running;
+
+ /* gain values */
+ int als_gain;
+ int pxs_gain;
+
+ /* integration time value in us */
+ int als_adc_int_us;
+
+ /* gesture buffer */
+ u8 buffer[4]; /* 4 8-bit channels */
+};
+
+static const struct reg_default apds9960_reg_defaults[] = {
+ /* Default ALS integration time = 2.48ms */
+ { APDS9960_REG_ATIME, 0xff },
+};
+
+static const struct regmap_range apds9960_volatile_ranges[] = {
+ regmap_reg_range(APDS9960_REG_STATUS,
+ APDS9960_REG_PDATA),
+ regmap_reg_range(APDS9960_REG_GFLVL,
+ APDS9960_REG_GSTATUS),
+ regmap_reg_range(APDS9960_REG_GFIFO_DIR(UP),
+ APDS9960_REG_GFIFO_DIR(RIGHT)),
+ regmap_reg_range(APDS9960_REG_IFORCE,
+ APDS9960_REG_AICLEAR),
+};
+
+static const struct regmap_access_table apds9960_volatile_table = {
+ .yes_ranges = apds9960_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(apds9960_volatile_ranges),
+};
+
+static const struct regmap_range apds9960_precious_ranges[] = {
+ regmap_reg_range(APDS9960_REG_RAM_START, APDS9960_REG_RAM_END),
+};
+
+static const struct regmap_access_table apds9960_precious_table = {
+ .yes_ranges = apds9960_precious_ranges,
+ .n_yes_ranges = ARRAY_SIZE(apds9960_precious_ranges),
+};
+
+static const struct regmap_range apds9960_readable_ranges[] = {
+ regmap_reg_range(APDS9960_REG_ENABLE,
+ APDS9960_REG_GSTATUS),
+ regmap_reg_range(APDS9960_REG_GFIFO_DIR(UP),
+ APDS9960_REG_GFIFO_DIR(RIGHT)),
+};
+
+static const struct regmap_access_table apds9960_readable_table = {
+ .yes_ranges = apds9960_readable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(apds9960_readable_ranges),
+};
+
+static const struct regmap_range apds9960_writeable_ranges[] = {
+ regmap_reg_range(APDS9960_REG_ENABLE, APDS9960_REG_CONFIG_2),
+ regmap_reg_range(APDS9960_REG_POFFSET_UR, APDS9960_REG_GCONF_4),
+ regmap_reg_range(APDS9960_REG_IFORCE, APDS9960_REG_AICLEAR),
+};
+
+static const struct regmap_access_table apds9960_writeable_table = {
+ .yes_ranges = apds9960_writeable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(apds9960_writeable_ranges),
+};
+
+static const struct regmap_config apds9960_regmap_config = {
+ .name = APDS9960_REGMAP_NAME,
+ .reg_bits = 8,
+ .val_bits = 8,
+ .use_single_rw = 1,
+
+ .volatile_table = &apds9960_volatile_table,
+ .precious_table = &apds9960_precious_table,
+ .rd_table = &apds9960_readable_table,
+ .wr_table = &apds9960_writeable_table,
+
+ .reg_defaults = apds9960_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(apds9960_reg_defaults),
+ .max_register = APDS9960_REG_GFIFO_DIR(RIGHT),
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static const struct iio_event_spec apds9960_pxs_event_spec[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ },
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ },
+};
+
+static const struct iio_event_spec apds9960_als_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),
+ },
+};
+
+#define APDS9960_GESTURE_CHANNEL(_dir, _si) { \
+ .type = IIO_PROXIMITY, \
+ .channel = _si + 1, \
+ .scan_index = _si, \
+ .indexed = 1, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 8, \
+ .storagebits = 8, \
+ }, \
+}
+
+#define APDS9960_INTENSITY_CHANNEL(_colour) { \
+ .type = IIO_INTENSITY, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_INT_TIME), \
+ .channel2 = IIO_MOD_LIGHT_##_colour, \
+ .address = APDS9960_REG_ALS_CHANNEL(_colour), \
+ .modified = 1, \
+ .scan_index = -1, \
+}
+
+static const unsigned long apds9960_scan_masks[] = {0xf, 0};
+
+static const struct iio_chan_spec apds9960_channels[] = {
+ {
+ .type = IIO_PROXIMITY,
+ .address = APDS9960_REG_PDATA,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+ .channel = 0,
+ .indexed = 0,
+ .scan_index = -1,
+
+ .event_spec = apds9960_pxs_event_spec,
+ .num_event_specs = ARRAY_SIZE(apds9960_pxs_event_spec),
+ },
+ /* Gesture Sensor */
+ APDS9960_GESTURE_CHANNEL(UP, 0),
+ APDS9960_GESTURE_CHANNEL(DOWN, 1),
+ APDS9960_GESTURE_CHANNEL(LEFT, 2),
+ APDS9960_GESTURE_CHANNEL(RIGHT, 3),
+ /* ALS */
+ {
+ .type = IIO_INTENSITY,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_INT_TIME),
+ .channel2 = IIO_MOD_LIGHT_CLEAR,
+ .address = APDS9960_REG_ALS_CHANNEL(CLEAR),
+ .modified = 1,
+ .scan_index = -1,
+
+ .event_spec = apds9960_als_event_spec,
+ .num_event_specs = ARRAY_SIZE(apds9960_als_event_spec),
+ },
+ /* RGB Sensor */
+ APDS9960_INTENSITY_CHANNEL(RED),
+ APDS9960_INTENSITY_CHANNEL(GREEN),
+ APDS9960_INTENSITY_CHANNEL(BLUE),
+};
+
+/* integration time in us */
+static const int apds9960_int_time[][2] =
+ { {28000, 246}, {100000, 219}, {200000, 182}, {700000, 0} };
+
+/* gain mapping */
+static const int apds9960_pxs_gain_map[] = {1, 2, 4, 8};
+static const int apds9960_als_gain_map[] = {1, 4, 16, 64};
+
+static IIO_CONST_ATTR(proximity_scale_available, "1 2 4 8");
+static IIO_CONST_ATTR(intensity_scale_available, "1 4 16 64");
+static IIO_CONST_ATTR_INT_TIME_AVAIL("0.028 0.1 0.2 0.7");
+
+static struct attribute *apds9960_attributes[] = {
+ &iio_const_attr_proximity_scale_available.dev_attr.attr,
+ &iio_const_attr_intensity_scale_available.dev_attr.attr,
+ &iio_const_attr_integration_time_available.dev_attr.attr,
+ NULL,
+};
+
+static struct attribute_group apds9960_attribute_group = {
+ .attrs = apds9960_attributes,
+};
+
+static const struct reg_field apds9960_reg_field_int_als =
+ REG_FIELD(APDS9960_REG_ENABLE, 4, 4);
+
+static const struct reg_field apds9960_reg_field_int_ges =
+ REG_FIELD(APDS9960_REG_GCONF_4, 1, 1);
+
+static const struct reg_field apds9960_reg_field_int_pxs =
+ REG_FIELD(APDS9960_REG_ENABLE, 5, 5);
+
+static const struct reg_field apds9960_reg_field_enable_als =
+ REG_FIELD(APDS9960_REG_ENABLE, 1, 1);
+
+static const struct reg_field apds9960_reg_field_enable_ges =
+ REG_FIELD(APDS9960_REG_ENABLE, 6, 6);
+
+static const struct reg_field apds9960_reg_field_enable_pxs =
+ REG_FIELD(APDS9960_REG_ENABLE, 2, 2);
+
+static int apds9960_set_it_time(struct apds9960_data *data, int val2)
+{
+ int ret = -EINVAL;
+ int idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(apds9960_int_time); idx++) {
+ if (apds9960_int_time[idx][0] == val2) {
+ mutex_lock(&data->lock);
+ ret = regmap_write(data->regmap, APDS9960_REG_ATIME,
+ apds9960_int_time[idx][1]);
+ if (!ret)
+ data->als_adc_int_us = val2;
+ mutex_unlock(&data->lock);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int apds9960_set_pxs_gain(struct apds9960_data *data, int val)
+{
+ int ret = -EINVAL;
+ int idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(apds9960_pxs_gain_map); idx++) {
+ if (apds9960_pxs_gain_map[idx] == val) {
+ /* pxs + gesture gains are mirrored */
+ mutex_lock(&data->lock);
+ ret = regmap_update_bits(data->regmap,
+ APDS9960_REG_CONTROL,
+ APDS9960_REG_CONTROL_PGAIN_MASK,
+ idx << APDS9960_REG_CONTROL_PGAIN_MASK_SHIFT);
+ if (ret) {
+ mutex_unlock(&data->lock);
+ break;
+ }
+
+ ret = regmap_update_bits(data->regmap,
+ APDS9960_REG_CONFIG_2,
+ APDS9960_REG_CONFIG_2_GGAIN_MASK,
+ idx << APDS9960_REG_CONFIG_2_GGAIN_MASK_SHIFT);
+ if (!ret)
+ data->pxs_gain = idx;
+ mutex_unlock(&data->lock);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int apds9960_set_als_gain(struct apds9960_data *data, int val)
+{
+ int ret = -EINVAL;
+ int idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(apds9960_als_gain_map); idx++) {
+ if (apds9960_als_gain_map[idx] == val) {
+ mutex_lock(&data->lock);
+ ret = regmap_update_bits(data->regmap,
+ APDS9960_REG_CONTROL,
+ APDS9960_REG_CONTROL_AGAIN_MASK, idx);
+ if (!ret)
+ data->als_gain = idx;
+ mutex_unlock(&data->lock);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+#ifdef CONFIG_PM
+static int apds9960_set_power_state(struct apds9960_data *data, bool on)
+{
+ struct device *dev = &data->client->dev;
+ int ret = 0;
+
+ mutex_lock(&data->lock);
+
+ if (on) {
+ int suspended;
+
+ suspended = pm_runtime_suspended(dev);
+ ret = pm_runtime_get_sync(dev);
+
+ /* Allow one integration cycle before allowing a reading */
+ if (suspended)
+ usleep_range(data->als_adc_int_us,
+ APDS9960_MAX_INT_TIME_IN_US);
+ } else {
+ pm_runtime_mark_last_busy(dev);
+ ret = pm_runtime_put_autosuspend(dev);
+ }
+
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+#else
+static int apds9960_set_power_state(struct apds9960_data *data, bool on)
+{
+ return 0;
+}
+#endif
+
+static int apds9960_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct apds9960_data *data = iio_priv(indio_dev);
+ __le16 buf;
+ int ret = -EINVAL;
+
+ if (data->gesture_mode_running)
+ return -EBUSY;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ apds9960_set_power_state(data, true);
+ switch (chan->type) {
+ case IIO_PROXIMITY:
+ ret = regmap_read(data->regmap, chan->address, val);
+ if (!ret)
+ ret = IIO_VAL_INT;
+ break;
+ case IIO_INTENSITY:
+ ret = regmap_bulk_read(data->regmap, chan->address,
+ &buf, 2);
+ if (!ret)
+ ret = IIO_VAL_INT;
+ *val = le16_to_cpu(buf);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ apds9960_set_power_state(data, false);
+ break;
+ case IIO_CHAN_INFO_INT_TIME:
+ /* RGB + ALS sensors only have integration time */
+ mutex_lock(&data->lock);
+ switch (chan->type) {
+ case IIO_INTENSITY:
+ *val = 0;
+ *val2 = data->als_adc_int_us;
+ ret = IIO_VAL_INT_PLUS_MICRO;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ mutex_unlock(&data->lock);
+ break;
+ case IIO_CHAN_INFO_SCALE:
+ mutex_lock(&data->lock);
+ switch (chan->type) {
+ case IIO_PROXIMITY:
+ *val = apds9960_pxs_gain_map[data->pxs_gain];
+ ret = IIO_VAL_INT;
+ break;
+ case IIO_INTENSITY:
+ *val = apds9960_als_gain_map[data->als_gain];
+ ret = IIO_VAL_INT;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ mutex_unlock(&data->lock);
+ break;
+ }
+
+ return ret;
+};
+
+static int apds9960_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct apds9960_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_INT_TIME:
+ /* RGB + ALS sensors only have int time */
+ switch (chan->type) {
+ case IIO_INTENSITY:
+ if (val != 0)
+ return -EINVAL;
+ return apds9960_set_it_time(data, val2);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ if (val2 != 0)
+ return -EINVAL;
+ switch (chan->type) {
+ case IIO_PROXIMITY:
+ return apds9960_set_pxs_gain(data, val);
+ case IIO_INTENSITY:
+ return apds9960_set_als_gain(data, val);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ };
+
+ return 0;
+}
+
+static inline int apds9960_get_thres_reg(const struct iio_chan_spec *chan,
+ enum iio_event_direction dir,
+ u8 *reg)
+{
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ switch (chan->type) {
+ case IIO_PROXIMITY:
+ *reg = APDS9960_REG_PIHT;
+ break;
+ case IIO_INTENSITY:
+ *reg = APDS9960_REG_AIHTL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case IIO_EV_DIR_FALLING:
+ switch (chan->type) {
+ case IIO_PROXIMITY:
+ *reg = APDS9960_REG_PILT;
+ break;
+ case IIO_INTENSITY:
+ *reg = APDS9960_REG_AILTL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int apds9960_read_event(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ u8 reg;
+ __le16 buf;
+ int ret = 0;
+ struct apds9960_data *data = iio_priv(indio_dev);
+
+ if (info != IIO_EV_INFO_VALUE)
+ return -EINVAL;
+
+ ret = apds9960_get_thres_reg(chan, dir, &reg);
+ if (ret < 0)
+ return ret;
+
+ if (chan->type == IIO_PROXIMITY) {
+ ret = regmap_read(data->regmap, reg, val);
+ if (ret < 0)
+ return ret;
+ } else if (chan->type == IIO_INTENSITY) {
+ ret = regmap_bulk_read(data->regmap, reg, &buf, 2);
+ if (ret < 0)
+ return ret;
+ *val = le16_to_cpu(buf);
+ } else
+ return -EINVAL;
+
+ *val2 = 0;
+
+ return IIO_VAL_INT;
+}
+
+static int apds9960_write_event(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ u8 reg;
+ __le16 buf;
+ int ret = 0;
+ struct apds9960_data *data = iio_priv(indio_dev);
+
+ if (info != IIO_EV_INFO_VALUE)
+ return -EINVAL;
+
+ ret = apds9960_get_thres_reg(chan, dir, &reg);
+ if (ret < 0)
+ return ret;
+
+ if (chan->type == IIO_PROXIMITY) {
+ if (val < 0 || val > APDS9960_MAX_PXS_THRES_VAL)
+ return -EINVAL;
+ ret = regmap_write(data->regmap, reg, val);
+ if (ret < 0)
+ return ret;
+ } else if (chan->type == IIO_INTENSITY) {
+ if (val < 0 || val > APDS9960_MAX_ALS_THRES_VAL)
+ return -EINVAL;
+ buf = cpu_to_le16(val);
+ ret = regmap_bulk_write(data->regmap, reg, &buf, 2);
+ if (ret < 0)
+ return ret;
+ } else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int apds9960_read_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ struct apds9960_data *data = iio_priv(indio_dev);
+
+ switch (chan->type) {
+ case IIO_PROXIMITY:
+ return data->pxs_int;
+ case IIO_INTENSITY:
+ return data->als_int;
+ default:
+ return -EINVAL;
+ }
+
+ 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)
+{
+ struct apds9960_data *data = iio_priv(indio_dev);
+ int ret;
+
+ state = !!state;
+
+ switch (chan->type) {
+ case IIO_PROXIMITY:
+ if (data->pxs_int == state)
+ return -EINVAL;
+
+ ret = regmap_field_write(data->reg_int_pxs, state);
+ if (ret)
+ return ret;
+ data->pxs_int = state;
+ apds9960_set_power_state(data, state);
+ break;
+ case IIO_INTENSITY:
+ if (data->als_int == state)
+ return -EINVAL;
+
+ ret = regmap_field_write(data->reg_int_als, state);
+ if (ret)
+ return ret;
+ data->als_int = state;
+ apds9960_set_power_state(data, state);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct iio_info apds9960_info = {
+ .driver_module = THIS_MODULE,
+ .attrs = &apds9960_attribute_group,
+ .read_raw = apds9960_read_raw,
+ .write_raw = apds9960_write_raw,
+ .read_event_value = apds9960_read_event,
+ .write_event_value = apds9960_write_event,
+ .read_event_config = apds9960_read_event_config,
+ .write_event_config = apds9960_write_event_config,
+
+};
+
+static inline int apds9660_fifo_is_empty(struct apds9960_data *data)
+{
+ int cnt;
+ int ret;
+
+ ret = regmap_read(data->regmap, APDS9960_REG_GFLVL, &cnt);
+ if (ret)
+ return ret;
+
+ return cnt;
+}
+
+static void apds9960_read_gesture_fifo(struct apds9960_data *data)
+{
+ int ret, cnt = 0;
+
+ mutex_lock(&data->lock);
+ data->gesture_mode_running = 1;
+
+ while (cnt-- || (cnt = apds9660_fifo_is_empty(data) > 0)) {
+ ret = regmap_bulk_read(data->regmap, APDS9960_REG_GFIFO_BASE,
+ &data->buffer, 4);
+
+ if (ret)
+ goto err_read;
+
+ iio_push_to_buffers(data->indio_dev, data->buffer);
+ }
+
+err_read:
+ data->gesture_mode_running = 0;
+ mutex_unlock(&data->lock);
+}
+
+static irqreturn_t apds9960_interrupt_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct apds9960_data *data = iio_priv(indio_dev);
+ int ret, status;
+
+ ret = regmap_read(data->regmap, APDS9960_REG_STATUS, &status);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "irq status reg read failed\n");
+ return IRQ_HANDLED;
+ }
+
+ if ((status & APDS9960_REG_STATUS_ALS_INT) && data->als_int) {
+ iio_push_event(indio_dev,
+ IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER),
+ iio_get_time_ns());
+ regmap_write(data->regmap, APDS9960_REG_CICLEAR, 1);
+ }
+
+ if ((status & APDS9960_REG_STATUS_PS_INT) && data->pxs_int) {
+ iio_push_event(indio_dev,
+ IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER),
+ iio_get_time_ns());
+ regmap_write(data->regmap, APDS9960_REG_PICLEAR, 1);
+ }
+
+ if (status & APDS9960_REG_STATUS_GINT)
+ apds9960_read_gesture_fifo(data);
+
+ return IRQ_HANDLED;
+}
+
+static int apds9960_set_powermode(struct apds9960_data *data, bool state)
+{
+ return regmap_update_bits(data->regmap, APDS9960_REG_ENABLE, 1, state);
+}
+
+static int apds9960_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct apds9960_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = regmap_field_write(data->reg_int_ges, 1);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(data->reg_enable_ges, 1);
+ if (ret)
+ return ret;
+
+ pm_runtime_get_sync(&data->client->dev);
+
+ return 0;
+}
+
+static int apds9960_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct apds9960_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = regmap_field_write(data->reg_enable_ges, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(data->reg_int_ges, 0);
+ if (ret)
+ return ret;
+
+ pm_runtime_put_autosuspend(&data->client->dev);
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops apds9960_buffer_setup_ops = {
+ .postenable = apds9960_buffer_postenable,
+ .predisable = apds9960_buffer_predisable,
+};
+
+static int apds9960_regfield_init(struct apds9960_data *data)
+{
+ struct device *dev = &data->client->dev;
+ struct regmap *regmap = data->regmap;
+
+ data->reg_int_als = devm_regmap_field_alloc(dev, regmap,
+ apds9960_reg_field_int_als);
+ if (IS_ERR(data->reg_int_als)) {
+ dev_err(dev, "INT ALS reg field init failed\n");
+ return PTR_ERR(data->reg_int_als);
+ }
+
+ data->reg_int_ges = devm_regmap_field_alloc(dev, regmap,
+ apds9960_reg_field_int_ges);
+ if (IS_ERR(data->reg_int_ges)) {
+ dev_err(dev, "INT gesture reg field init failed\n");
+ return PTR_ERR(data->reg_int_ges);
+ }
+
+ data->reg_int_pxs = devm_regmap_field_alloc(dev, regmap,
+ apds9960_reg_field_int_pxs);
+ if (IS_ERR(data->reg_int_pxs)) {
+ dev_err(dev, "INT pxs reg field init failed\n");
+ return PTR_ERR(data->reg_int_pxs);
+ }
+
+ data->reg_enable_als = devm_regmap_field_alloc(dev, regmap,
+ apds9960_reg_field_enable_als);
+ if (IS_ERR(data->reg_enable_als)) {
+ dev_err(dev, "Enable ALS reg field init failed\n");
+ return PTR_ERR(data->reg_enable_als);
+ }
+
+ data->reg_enable_ges = devm_regmap_field_alloc(dev, regmap,
+ apds9960_reg_field_enable_ges);
+ if (IS_ERR(data->reg_enable_ges)) {
+ dev_err(dev, "Enable gesture reg field init failed\n");
+ return PTR_ERR(data->reg_enable_ges);
+ }
+
+ data->reg_enable_pxs = devm_regmap_field_alloc(dev, regmap,
+ apds9960_reg_field_enable_pxs);
+ if (IS_ERR(data->reg_enable_pxs)) {
+ dev_err(dev, "Enable PXS reg field init failed\n");
+ return PTR_ERR(data->reg_enable_pxs);
+ }
+
+ return 0;
+}
+
+static int apds9960_chip_init(struct apds9960_data *data)
+{
+ int ret;
+
+ /* Default IT for ALS of 28 ms */
+ ret = apds9960_set_it_time(data, 28000);
+ if (ret)
+ return ret;
+
+ /* Ensure gesture interrupt is OFF */
+ ret = regmap_field_write(data->reg_int_ges, 0);
+ if (ret)
+ return ret;
+
+ /* Disable gesture sensor, since polling is useless from user-space */
+ ret = regmap_field_write(data->reg_enable_ges, 0);
+ if (ret)
+ return ret;
+
+ /* Ensure proximity interrupt is OFF */
+ ret = regmap_field_write(data->reg_int_pxs, 0);
+ if (ret)
+ return ret;
+
+ /* Enable proximity sensor for polling */
+ ret = regmap_field_write(data->reg_enable_pxs, 1);
+ if (ret)
+ return ret;
+
+ /* Ensure ALS interrupt is OFF */
+ ret = regmap_field_write(data->reg_int_als, 0);
+ if (ret)
+ return ret;
+
+ /* Enable ALS sensor for polling */
+ ret = regmap_field_write(data->reg_enable_als, 1);
+ if (ret)
+ return ret;
+ /*
+ * When enabled trigger an interrupt after 3 readings
+ * outside threshold for ALS + PXS
+ */
+ ret = regmap_write(data->regmap, APDS9960_REG_PERS,
+ APDS9960_DEFAULT_PERS);
+ if (ret)
+ return ret;
+
+ /*
+ * Wait for 4 event outside gesture threshold to prevent interrupt
+ * flooding.
+ */
+ ret = regmap_update_bits(data->regmap, APDS9960_REG_GCONF_1,
+ APDS9960_REG_GCONF_1_GFIFO_THRES_MASK,
+ BIT(0) << APDS9960_REG_GCONF_1_GFIFO_THRES_MASK_SHIFT);
+ if (ret)
+ return ret;
+
+ /* Default ENTER and EXIT thresholds for the GESTURE engine. */
+ ret = regmap_write(data->regmap, APDS9960_REG_GPENTH,
+ APDS9960_DEFAULT_GPENTH);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(data->regmap, APDS9960_REG_GEXTH,
+ APDS9960_DEFAULT_GEXTH);
+ if (ret)
+ return ret;
+
+ return apds9960_set_powermode(data, 1);
+}
+
+static int apds9960_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct apds9960_data *data;
+ struct iio_buffer *buffer;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ buffer = devm_iio_kfifo_allocate(&client->dev);
+ if (!buffer)
+ return -ENOMEM;
+
+ iio_device_attach_buffer(indio_dev, buffer);
+
+ indio_dev->info = &apds9960_info;
+ indio_dev->name = APDS9960_DRV_NAME;
+ indio_dev->channels = apds9960_channels;
+ indio_dev->num_channels = ARRAY_SIZE(apds9960_channels);
+ indio_dev->available_scan_masks = apds9960_scan_masks;
+ indio_dev->modes = (INDIO_BUFFER_SOFTWARE | INDIO_DIRECT_MODE);
+ indio_dev->setup_ops = &apds9960_buffer_setup_ops;
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+
+ data->regmap = devm_regmap_init_i2c(client, &apds9960_regmap_config);
+ if (IS_ERR(data->regmap)) {
+ dev_err(&client->dev, "regmap initialization failed.\n");
+ return PTR_ERR(data->regmap);
+ }
+
+ data->client = client;
+ data->indio_dev = indio_dev;
+ mutex_init(&data->lock);
+
+ ret = pm_runtime_set_active(&client->dev);
+ if (ret)
+ goto error_power_down;
+
+ pm_runtime_enable(&client->dev);
+ pm_runtime_set_autosuspend_delay(&client->dev, 5000);
+ pm_runtime_use_autosuspend(&client->dev);
+
+ apds9960_set_power_state(data, true);
+
+ ret = apds9960_regfield_init(data);
+ if (ret)
+ goto error_power_down;
+
+ ret = apds9960_chip_init(data);
+ if (ret)
+ goto error_power_down;
+
+ if (client->irq <= 0) {
+ dev_err(&client->dev, "no valid irq defined\n");
+ ret = -EINVAL;
+ goto error_power_down;
+ }
+ ret = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, apds9960_interrupt_handler,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ "apds9960_event",
+ indio_dev);
+ if (ret) {
+ dev_err(&client->dev, "request irq (%d) failed\n", client->irq);
+ goto error_power_down;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ goto error_power_down;
+
+ apds9960_set_power_state(data, false);
+
+ return 0;
+
+error_power_down:
+ apds9960_set_power_state(data, false);
+
+ return ret;
+}
+
+static int apds9960_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct apds9960_data *data = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ pm_runtime_disable(&client->dev);
+ pm_runtime_set_suspended(&client->dev);
+ apds9960_set_powermode(data, 0);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int apds9960_runtime_suspend(struct device *dev)
+{
+ struct apds9960_data *data =
+ iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+
+ return apds9960_set_powermode(data, 0);
+}
+
+static int apds9960_runtime_resume(struct device *dev)
+{
+ struct apds9960_data *data =
+ iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+
+ return apds9960_set_powermode(data, 1);
+}
+#endif
+
+static const struct dev_pm_ops apds9960_pm_ops = {
+ SET_RUNTIME_PM_OPS(apds9960_runtime_suspend,
+ apds9960_runtime_resume, NULL)
+};
+
+static const struct i2c_device_id apds9960_id[] = {
+ { "apds9960", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, apds9960_id);
+
+static struct i2c_driver apds9960_driver = {
+ .driver = {
+ .name = APDS9960_DRV_NAME,
+ .pm = &apds9960_pm_ops,
+ },
+ .probe = apds9960_probe,
+ .remove = apds9960_remove,
+ .id_table = apds9960_id,
+};
+module_i2c_driver(apds9960_driver);
+
+MODULE_AUTHOR("Matt Ranostay <mranostay@gmail.com>");
+MODULE_DESCRIPTION("ADPS9960 Gesture/RGB/ALS/Proximity sensor");
+MODULE_LICENSE("GPL");
diff --git a/kernel/drivers/iio/light/bh1750.c b/kernel/drivers/iio/light/bh1750.c
new file mode 100644
index 000000000..8b4164343
--- /dev/null
+++ b/kernel/drivers/iio/light/bh1750.c
@@ -0,0 +1,333 @@
+/*
+ * ROHM BH1710/BH1715/BH1721/BH1750/BH1751 ambient light sensor driver
+ *
+ * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Data sheets:
+ * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1710fvc-e.pdf
+ * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1715fvc-e.pdf
+ * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1721fvc-e.pdf
+ * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1750fvi-e.pdf
+ * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1751fvi-e.pdf
+ *
+ * 7-bit I2C slave addresses:
+ * 0x23 (ADDR pin low)
+ * 0x5C (ADDR pin high)
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/module.h>
+
+#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
+
+enum {
+ BH1710,
+ BH1721,
+ BH1750,
+};
+
+struct bh1750_chip_info;
+struct bh1750_data {
+ struct i2c_client *client;
+ struct mutex lock;
+ const struct bh1750_chip_info *chip_info;
+ u16 mtreg;
+};
+
+struct bh1750_chip_info {
+ u16 mtreg_min;
+ u16 mtreg_max;
+ u16 mtreg_default;
+ int mtreg_to_usec;
+ int mtreg_to_scale;
+
+ /*
+ * For BH1710/BH1721 all possible integration time values won't fit
+ * into one page so displaying is limited to every second one.
+ * Note, that user can still write proper values which were not
+ * listed.
+ */
+ int inc;
+
+ u16 int_time_low_mask;
+ u16 int_time_high_mask;
+}
+
+static const bh1750_chip_info_tbl[] = {
+ [BH1710] = { 140, 1022, 300, 400, 250000000, 2, 0x001F, 0x03E0 },
+ [BH1721] = { 140, 1020, 300, 400, 250000000, 2, 0x0010, 0x03E0 },
+ [BH1750] = { 31, 254, 69, 1740, 57500000, 1, 0x001F, 0x00E0 },
+};
+
+static int bh1750_change_int_time(struct bh1750_data *data, int usec)
+{
+ int ret;
+ u16 val;
+ u8 regval;
+ const struct bh1750_chip_info *chip_info = data->chip_info;
+
+ if ((usec % chip_info->mtreg_to_usec) != 0)
+ return -EINVAL;
+
+ val = usec / chip_info->mtreg_to_usec;
+ if (val < chip_info->mtreg_min || val > chip_info->mtreg_max)
+ return -EINVAL;
+
+ ret = i2c_smbus_write_byte(data->client, BH1750_POWER_DOWN);
+ if (ret < 0)
+ return ret;
+
+ regval = (val & chip_info->int_time_high_mask) >> 5;
+ ret = i2c_smbus_write_byte(data->client,
+ BH1750_CHANGE_INT_TIME_H_BIT | regval);
+ if (ret < 0)
+ return ret;
+
+ regval = val & chip_info->int_time_low_mask;
+ ret = i2c_smbus_write_byte(data->client,
+ BH1750_CHANGE_INT_TIME_L_BIT | regval);
+ if (ret < 0)
+ return ret;
+
+ data->mtreg = val;
+
+ return 0;
+}
+
+static int bh1750_read(struct bh1750_data *data, int *val)
+{
+ int ret;
+ __be16 result;
+ const struct bh1750_chip_info *chip_info = data->chip_info;
+ unsigned long delay = chip_info->mtreg_to_usec * data->mtreg;
+
+ /*
+ * BH1721 will enter continuous mode on receiving this command.
+ * Note, that this eliminates need for bh1750_resume().
+ */
+ ret = i2c_smbus_write_byte(data->client, BH1750_ONE_TIME_H_RES_MODE);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(delay + 15000, delay + 40000);
+
+ ret = i2c_master_recv(data->client, (char *)&result, 2);
+ if (ret < 0)
+ return ret;
+
+ *val = be16_to_cpu(result);
+
+ return 0;
+}
+
+static int bh1750_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ int ret, tmp;
+ struct bh1750_data *data = iio_priv(indio_dev);
+ const struct bh1750_chip_info *chip_info = data->chip_info;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->type) {
+ case IIO_LIGHT:
+ mutex_lock(&data->lock);
+ ret = bh1750_read(data, val);
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ tmp = chip_info->mtreg_to_scale / data->mtreg;
+ *val = tmp / 1000000;
+ *val2 = tmp % 1000000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_INT_TIME:
+ *val = 0;
+ *val2 = chip_info->mtreg_to_usec * data->mtreg;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bh1750_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ int ret;
+ struct bh1750_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_INT_TIME:
+ if (val != 0)
+ return -EINVAL;
+
+ mutex_lock(&data->lock);
+ ret = bh1750_change_int_time(data, val2);
+ mutex_unlock(&data->lock);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static ssize_t bh1750_show_int_time_available(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int i;
+ size_t len = 0;
+ struct bh1750_data *data = iio_priv(dev_to_iio_dev(dev));
+ const struct bh1750_chip_info *chip_info = data->chip_info;
+
+ for (i = chip_info->mtreg_min; i <= chip_info->mtreg_max; i += chip_info->inc)
+ len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06d ",
+ chip_info->mtreg_to_usec * i);
+
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static IIO_DEV_ATTR_INT_TIME_AVAIL(bh1750_show_int_time_available);
+
+static struct attribute *bh1750_attributes[] = {
+ &iio_dev_attr_integration_time_available.dev_attr.attr,
+ NULL,
+};
+
+static struct attribute_group bh1750_attribute_group = {
+ .attrs = bh1750_attributes,
+};
+
+static const struct iio_info bh1750_info = {
+ .driver_module = THIS_MODULE,
+ .attrs = &bh1750_attribute_group,
+ .read_raw = bh1750_read_raw,
+ .write_raw = bh1750_write_raw,
+};
+
+static const struct iio_chan_spec bh1750_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_INT_TIME)
+ }
+};
+
+static int bh1750_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret, usec;
+ struct bh1750_data *data;
+ struct iio_dev *indio_dev;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C |
+ I2C_FUNC_SMBUS_WRITE_BYTE))
+ return -ENODEV;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+ data->chip_info = &bh1750_chip_info_tbl[id->driver_data];
+
+ usec = data->chip_info->mtreg_to_usec * data->chip_info->mtreg_default;
+ ret = bh1750_change_int_time(data, usec);
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&data->lock);
+ indio_dev->dev.parent = &client->dev;
+ indio_dev->info = &bh1750_info;
+ indio_dev->name = id->name;
+ indio_dev->channels = bh1750_channels;
+ indio_dev->num_channels = ARRAY_SIZE(bh1750_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ return iio_device_register(indio_dev);
+}
+
+static int bh1750_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct bh1750_data *data = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+
+ mutex_lock(&data->lock);
+ i2c_smbus_write_byte(client, BH1750_POWER_DOWN);
+ mutex_unlock(&data->lock);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int bh1750_suspend(struct device *dev)
+{
+ int ret;
+ struct bh1750_data *data =
+ iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+
+ /*
+ * This is mainly for BH1721 which doesn't enter power down
+ * mode automatically.
+ */
+ mutex_lock(&data->lock);
+ ret = i2c_smbus_write_byte(data->client, BH1750_POWER_DOWN);
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(bh1750_pm_ops, bh1750_suspend, NULL);
+#define BH1750_PM_OPS (&bh1750_pm_ops)
+#else
+#define BH1750_PM_OPS NULL
+#endif
+
+static const struct i2c_device_id bh1750_id[] = {
+ { "bh1710", BH1710 },
+ { "bh1715", BH1750 },
+ { "bh1721", BH1721 },
+ { "bh1750", BH1750 },
+ { "bh1751", BH1750 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, bh1750_id);
+
+static struct i2c_driver bh1750_driver = {
+ .driver = {
+ .name = "bh1750",
+ .pm = BH1750_PM_OPS,
+ },
+ .probe = bh1750_probe,
+ .remove = bh1750_remove,
+ .id_table = bh1750_id,
+
+};
+module_i2c_driver(bh1750_driver);
+
+MODULE_AUTHOR("Tomasz Duszynski <tduszyns@gmail.com>");
+MODULE_DESCRIPTION("ROHM BH1710/BH1715/BH1721/BH1750/BH1751 als driver");
+MODULE_LICENSE("GPL v2");
diff --git a/kernel/drivers/iio/light/cm32181.c b/kernel/drivers/iio/light/cm32181.c
index 5d12ae54d..d6fd0dace 100644
--- a/kernel/drivers/iio/light/cm32181.c
+++ b/kernel/drivers/iio/light/cm32181.c
@@ -353,12 +353,12 @@ static const struct of_device_id cm32181_of_match[] = {
{ .compatible = "capella,cm32181" },
{ }
};
+MODULE_DEVICE_TABLE(of, cm32181_of_match);
static struct i2c_driver cm32181_driver = {
.driver = {
.name = "cm32181",
.of_match_table = of_match_ptr(cm32181_of_match),
- .owner = THIS_MODULE,
},
.id_table = cm32181_id,
.probe = cm32181_probe,
diff --git a/kernel/drivers/iio/light/cm3232.c b/kernel/drivers/iio/light/cm3232.c
index 39c8d99cc..fe89b6823 100644
--- a/kernel/drivers/iio/light/cm3232.c
+++ b/kernel/drivers/iio/light/cm3232.c
@@ -417,11 +417,11 @@ static const struct of_device_id cm3232_of_match[] = {
{.compatible = "capella,cm3232"},
{}
};
+MODULE_DEVICE_TABLE(of, cm3232_of_match);
static struct i2c_driver cm3232_driver = {
.driver = {
.name = "cm3232",
- .owner = THIS_MODULE,
.of_match_table = of_match_ptr(cm3232_of_match),
#ifdef CONFIG_PM_SLEEP
.pm = &cm3232_pm_ops,
diff --git a/kernel/drivers/iio/light/cm3323.c b/kernel/drivers/iio/light/cm3323.c
index a1d4905cc..d823c112d 100644
--- a/kernel/drivers/iio/light/cm3323.c
+++ b/kernel/drivers/iio/light/cm3323.c
@@ -29,7 +29,7 @@
#define CM3323_CONF_SD_BIT BIT(0) /* sensor disable */
#define CM3323_CONF_AF_BIT BIT(1) /* auto/manual force mode */
-#define CM3323_CONF_IT_MASK (BIT(4) | BIT(5) | BIT(6))
+#define CM3323_CONF_IT_MASK GENMASK(6, 4)
#define CM3323_CONF_IT_SHIFT 4
#define CM3323_INT_TIME_AVAILABLE "0.04 0.08 0.16 0.32 0.64 1.28"
@@ -133,9 +133,11 @@ static int cm3323_set_it_bits(struct cm3323_data *data, int val, int val2)
return ret;
data->reg_conf = reg_conf;
+
return 0;
}
}
+
return -EINVAL;
}
@@ -148,6 +150,7 @@ static int cm3323_get_it_bits(struct cm3323_data *data)
if (bits >= ARRAY_SIZE(cm3323_int_time))
return -EINVAL;
+
return bits;
}
@@ -155,7 +158,7 @@ static int cm3323_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val,
int *val2, long mask)
{
- int i, ret;
+ int ret;
struct cm3323_data *data = iio_priv(indio_dev);
switch (mask) {
@@ -172,14 +175,14 @@ static int cm3323_read_raw(struct iio_dev *indio_dev,
return IIO_VAL_INT;
case IIO_CHAN_INFO_INT_TIME:
mutex_lock(&data->mutex);
- i = cm3323_get_it_bits(data);
- if (i < 0) {
+ ret = cm3323_get_it_bits(data);
+ if (ret < 0) {
mutex_unlock(&data->mutex);
- return -EINVAL;
+ return ret;
}
- *val = cm3323_int_time[i].val;
- *val2 = cm3323_int_time[i].val2;
+ *val = cm3323_int_time[ret].val;
+ *val2 = cm3323_int_time[ret].val2;
mutex_unlock(&data->mutex);
return IIO_VAL_INT_PLUS_MICRO;
@@ -243,11 +246,13 @@ static int cm3323_probe(struct i2c_client *client,
dev_err(&client->dev, "cm3323 chip init failed\n");
return ret;
}
+
ret = iio_device_register(indio_dev);
if (ret < 0) {
dev_err(&client->dev, "failed to register iio dev\n");
goto err_init;
}
+
return 0;
err_init:
cm3323_disable(indio_dev);
diff --git a/kernel/drivers/iio/light/cm36651.c b/kernel/drivers/iio/light/cm36651.c
index 39fc67e82..c8d7b5ea7 100644
--- a/kernel/drivers/iio/light/cm36651.c
+++ b/kernel/drivers/iio/light/cm36651.c
@@ -731,12 +731,12 @@ static const struct of_device_id cm36651_of_match[] = {
{ .compatible = "capella,cm36651" },
{ }
};
+MODULE_DEVICE_TABLE(of, cm36651_of_match);
static struct i2c_driver cm36651_driver = {
.driver = {
.name = "cm36651",
.of_match_table = cm36651_of_match,
- .owner = THIS_MODULE,
},
.probe = cm36651_probe,
.remove = cm36651_remove,
diff --git a/kernel/drivers/iio/light/gp2ap020a00f.c b/kernel/drivers/iio/light/gp2ap020a00f.c
index 32b644983..6d41086f7 100644
--- a/kernel/drivers/iio/light/gp2ap020a00f.c
+++ b/kernel/drivers/iio/light/gp2ap020a00f.c
@@ -1634,13 +1634,13 @@ static const struct of_device_id gp2ap020a00f_of_match[] = {
{ .compatible = "sharp,gp2ap020a00f" },
{ }
};
+MODULE_DEVICE_TABLE(of, gp2ap020a00f_of_match);
#endif
static struct i2c_driver gp2ap020a00f_driver = {
.driver = {
.name = GP2A_I2C_NAME,
.of_match_table = of_match_ptr(gp2ap020a00f_of_match),
- .owner = THIS_MODULE,
},
.probe = gp2ap020a00f_probe,
.remove = gp2ap020a00f_remove,
diff --git a/kernel/drivers/iio/light/hid-sensor-als.c b/kernel/drivers/iio/light/hid-sensor-als.c
index 1609ecdd0..8bb1f90ec 100644
--- a/kernel/drivers/iio/light/hid-sensor-als.c
+++ b/kernel/drivers/iio/light/hid-sensor-als.c
@@ -263,7 +263,6 @@ static int hid_als_probe(struct platform_device *pdev)
struct iio_dev *indio_dev;
struct als_state *als_state;
struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
- struct iio_chan_spec *channels;
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct als_state));
if (!indio_dev)
@@ -281,20 +280,21 @@ static int hid_als_probe(struct platform_device *pdev)
return ret;
}
- channels = kmemdup(als_channels, sizeof(als_channels), GFP_KERNEL);
- if (!channels) {
+ indio_dev->channels = kmemdup(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, channels,
- HID_USAGE_SENSOR_ALS, als_state);
+ ret = als_parse_report(pdev, hsdev,
+ (struct iio_chan_spec *)indio_dev->channels,
+ HID_USAGE_SENSOR_ALS, als_state);
if (ret) {
dev_err(&pdev->dev, "failed to setup attributes\n");
goto error_free_dev_mem;
}
- indio_dev->channels = channels;
indio_dev->num_channels =
ARRAY_SIZE(als_channels);
indio_dev->dev.parent = &pdev->dev;
@@ -361,7 +361,7 @@ static int hid_als_remove(struct platform_device *pdev)
return 0;
}
-static struct platform_device_id hid_als_ids[] = {
+static const struct platform_device_id hid_als_ids[] = {
{
/* Format: HID-SENSOR-usage_id_in_hex_lowercase */
.name = "HID-SENSOR-200041",
diff --git a/kernel/drivers/iio/light/hid-sensor-prox.c b/kernel/drivers/iio/light/hid-sensor-prox.c
index ef60bae73..45ca056f0 100644
--- a/kernel/drivers/iio/light/hid-sensor-prox.c
+++ b/kernel/drivers/iio/light/hid-sensor-prox.c
@@ -284,8 +284,7 @@ static int hid_prox_probe(struct platform_device *pdev)
goto error_free_dev_mem;
}
- indio_dev->num_channels =
- ARRAY_SIZE(prox_channels);
+ indio_dev->num_channels = ARRAY_SIZE(prox_channels);
indio_dev->dev.parent = &pdev->dev;
indio_dev->info = &prox_info;
indio_dev->name = name;
@@ -350,7 +349,7 @@ static int hid_prox_remove(struct platform_device *pdev)
return 0;
}
-static struct platform_device_id hid_prox_ids[] = {
+static const struct platform_device_id hid_prox_ids[] = {
{
/* Format: HID-SENSOR-usage_id_in_hex_lowercase */
.name = "HID-SENSOR-200011",
diff --git a/kernel/drivers/iio/light/isl29125.c b/kernel/drivers/iio/light/isl29125.c
index c82f4a6f8..e2945a20e 100644
--- a/kernel/drivers/iio/light/isl29125.c
+++ b/kernel/drivers/iio/light/isl29125.c
@@ -197,9 +197,21 @@ done:
return IRQ_HANDLED;
}
+static IIO_CONST_ATTR(scale_available, "0.005722 0.152590");
+
+static struct attribute *isl29125_attributes[] = {
+ &iio_const_attr_scale_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group isl29125_attribute_group = {
+ .attrs = isl29125_attributes,
+};
+
static const struct iio_info isl29125_info = {
.read_raw = isl29125_read_raw,
.write_raw = isl29125_write_raw,
+ .attrs = &isl29125_attribute_group,
.driver_module = THIS_MODULE,
};
@@ -334,7 +346,6 @@ static struct i2c_driver isl29125_driver = {
.driver = {
.name = ISL29125_DRV_NAME,
.pm = &isl29125_pm_ops,
- .owner = THIS_MODULE,
},
.probe = isl29125_probe,
.remove = isl29125_remove,
diff --git a/kernel/drivers/iio/light/jsa1212.c b/kernel/drivers/iio/light/jsa1212.c
index 3a3af89be..c4e8c6b6c 100644
--- a/kernel/drivers/iio/light/jsa1212.c
+++ b/kernel/drivers/iio/light/jsa1212.c
@@ -457,7 +457,6 @@ static struct i2c_driver jsa1212_driver = {
.driver = {
.name = JSA1212_DRIVER_NAME,
.pm = JSA1212_PM_OPS,
- .owner = THIS_MODULE,
.acpi_match_table = ACPI_PTR(jsa1212_acpi_match),
},
.probe = jsa1212_probe,
diff --git a/kernel/drivers/iio/light/ltr501.c b/kernel/drivers/iio/light/ltr501.c
index 78b87839c..6bf89d8f3 100644
--- a/kernel/drivers/iio/light/ltr501.c
+++ b/kernel/drivers/iio/light/ltr501.c
@@ -9,15 +9,18 @@
*
* 7-bit I2C slave address 0x23
*
- * TODO: interrupt, threshold, measurement rate, IR LED characteristics
+ * TODO: IR LED characteristics
*/
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/delay.h>
+#include <linux/regmap.h>
+#include <linux/acpi.h>
#include <linux/iio/iio.h>
+#include <linux/iio/events.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/buffer.h>
@@ -27,12 +30,21 @@
#define LTR501_ALS_CONTR 0x80 /* ALS operation mode, SW reset */
#define LTR501_PS_CONTR 0x81 /* PS operation mode */
+#define LTR501_PS_MEAS_RATE 0x84 /* measurement rate*/
+#define LTR501_ALS_MEAS_RATE 0x85 /* ALS integ time, measurement rate*/
#define LTR501_PART_ID 0x86
#define LTR501_MANUFAC_ID 0x87
#define LTR501_ALS_DATA1 0x88 /* 16-bit, little endian */
#define LTR501_ALS_DATA0 0x8a /* 16-bit, little endian */
#define LTR501_ALS_PS_STATUS 0x8c
#define LTR501_PS_DATA 0x8d /* 16-bit, little endian */
+#define LTR501_INTR 0x8f /* output mode, polarity, mode */
+#define LTR501_PS_THRESH_UP 0x90 /* 11 bit, ps upper threshold */
+#define LTR501_PS_THRESH_LOW 0x92 /* 11 bit, ps lower threshold */
+#define LTR501_ALS_THRESH_UP 0x97 /* 16 bit, ALS upper threshold */
+#define LTR501_ALS_THRESH_LOW 0x99 /* 16 bit, ALS lower threshold */
+#define LTR501_INTR_PRST 0x9e /* ps thresh, als thresh */
+#define LTR501_MAX_REG 0x9f
#define LTR501_ALS_CONTR_SW_RESET BIT(2)
#define LTR501_CONTR_PS_GAIN_MASK (BIT(3) | BIT(2))
@@ -40,28 +52,288 @@
#define LTR501_CONTR_ALS_GAIN_MASK BIT(3)
#define LTR501_CONTR_ACTIVE BIT(1)
+#define LTR501_STATUS_ALS_INTR BIT(3)
#define LTR501_STATUS_ALS_RDY BIT(2)
+#define LTR501_STATUS_PS_INTR BIT(1)
#define LTR501_STATUS_PS_RDY BIT(0)
#define LTR501_PS_DATA_MASK 0x7ff
+#define LTR501_PS_THRESH_MASK 0x7ff
+#define LTR501_ALS_THRESH_MASK 0xffff
+
+#define LTR501_ALS_DEF_PERIOD 500000
+#define LTR501_PS_DEF_PERIOD 100000
+
+#define LTR501_REGMAP_NAME "ltr501_regmap"
+
+#define LTR501_LUX_CONV(vis_coeff, vis_data, ir_coeff, ir_data) \
+ ((vis_coeff * vis_data) - (ir_coeff * ir_data))
+
+static const int int_time_mapping[] = {100000, 50000, 200000, 400000};
+
+static const struct reg_field reg_field_it =
+ REG_FIELD(LTR501_ALS_MEAS_RATE, 3, 4);
+static const struct reg_field reg_field_als_intr =
+ REG_FIELD(LTR501_INTR, 0, 0);
+static const struct reg_field reg_field_ps_intr =
+ REG_FIELD(LTR501_INTR, 1, 1);
+static const struct reg_field reg_field_als_rate =
+ REG_FIELD(LTR501_ALS_MEAS_RATE, 0, 2);
+static const struct reg_field reg_field_ps_rate =
+ REG_FIELD(LTR501_PS_MEAS_RATE, 0, 3);
+static const struct reg_field reg_field_als_prst =
+ REG_FIELD(LTR501_INTR_PRST, 0, 3);
+static const struct reg_field reg_field_ps_prst =
+ REG_FIELD(LTR501_INTR_PRST, 4, 7);
+
+struct ltr501_samp_table {
+ int freq_val; /* repetition frequency in micro HZ*/
+ int time_val; /* repetition rate in micro seconds */
+};
+
+#define LTR501_RESERVED_GAIN -1
+
+enum {
+ ltr501 = 0,
+ ltr559,
+ ltr301,
+};
+
+struct ltr501_gain {
+ int scale;
+ int uscale;
+};
+
+static struct ltr501_gain ltr501_als_gain_tbl[] = {
+ {1, 0},
+ {0, 5000},
+};
+
+static struct ltr501_gain ltr559_als_gain_tbl[] = {
+ {1, 0},
+ {0, 500000},
+ {0, 250000},
+ {0, 125000},
+ {LTR501_RESERVED_GAIN, LTR501_RESERVED_GAIN},
+ {LTR501_RESERVED_GAIN, LTR501_RESERVED_GAIN},
+ {0, 20000},
+ {0, 10000},
+};
+
+static struct ltr501_gain ltr501_ps_gain_tbl[] = {
+ {1, 0},
+ {0, 250000},
+ {0, 125000},
+ {0, 62500},
+};
+
+static struct ltr501_gain ltr559_ps_gain_tbl[] = {
+ {0, 62500}, /* x16 gain */
+ {0, 31250}, /* x32 gain */
+ {0, 15625}, /* bits X1 are for x64 gain */
+ {0, 15624},
+};
+
+struct ltr501_chip_info {
+ u8 partid;
+ struct ltr501_gain *als_gain;
+ int als_gain_tbl_size;
+ struct ltr501_gain *ps_gain;
+ int ps_gain_tbl_size;
+ u8 als_mode_active;
+ u8 als_gain_mask;
+ u8 als_gain_shift;
+ struct iio_chan_spec const *channels;
+ const int no_channels;
+ const struct iio_info *info;
+ const struct iio_info *info_no_irq;
+};
struct ltr501_data {
struct i2c_client *client;
struct mutex lock_als, lock_ps;
+ struct ltr501_chip_info *chip_info;
u8 als_contr, ps_contr;
+ int als_period, ps_period; /* period in micro seconds */
+ struct regmap *regmap;
+ struct regmap_field *reg_it;
+ struct regmap_field *reg_als_intr;
+ struct regmap_field *reg_ps_intr;
+ struct regmap_field *reg_als_rate;
+ struct regmap_field *reg_ps_rate;
+ struct regmap_field *reg_als_prst;
+ struct regmap_field *reg_ps_prst;
+};
+
+static const struct ltr501_samp_table ltr501_als_samp_table[] = {
+ {20000000, 50000}, {10000000, 100000},
+ {5000000, 200000}, {2000000, 500000},
+ {1000000, 1000000}, {500000, 2000000},
+ {500000, 2000000}, {500000, 2000000}
};
+static const struct ltr501_samp_table ltr501_ps_samp_table[] = {
+ {20000000, 50000}, {14285714, 70000},
+ {10000000, 100000}, {5000000, 200000},
+ {2000000, 500000}, {1000000, 1000000},
+ {500000, 2000000}, {500000, 2000000},
+ {500000, 2000000}
+};
+
+static int ltr501_match_samp_freq(const struct ltr501_samp_table *tab,
+ int len, int val, int val2)
+{
+ int i, freq;
+
+ freq = val * 1000000 + val2;
+
+ for (i = 0; i < len; i++) {
+ if (tab[i].freq_val == freq)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int ltr501_als_read_samp_freq(struct ltr501_data *data,
+ int *val, int *val2)
+{
+ int ret, i;
+
+ ret = regmap_field_read(data->reg_als_rate, &i);
+ if (ret < 0)
+ return ret;
+
+ if (i < 0 || i >= ARRAY_SIZE(ltr501_als_samp_table))
+ return -EINVAL;
+
+ *val = ltr501_als_samp_table[i].freq_val / 1000000;
+ *val2 = ltr501_als_samp_table[i].freq_val % 1000000;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int ltr501_ps_read_samp_freq(struct ltr501_data *data,
+ int *val, int *val2)
+{
+ int ret, i;
+
+ ret = regmap_field_read(data->reg_ps_rate, &i);
+ if (ret < 0)
+ return ret;
+
+ if (i < 0 || i >= ARRAY_SIZE(ltr501_ps_samp_table))
+ return -EINVAL;
+
+ *val = ltr501_ps_samp_table[i].freq_val / 1000000;
+ *val2 = ltr501_ps_samp_table[i].freq_val % 1000000;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int ltr501_als_write_samp_freq(struct ltr501_data *data,
+ int val, int val2)
+{
+ int i, ret;
+
+ i = ltr501_match_samp_freq(ltr501_als_samp_table,
+ ARRAY_SIZE(ltr501_als_samp_table),
+ val, val2);
+
+ if (i < 0)
+ return i;
+
+ mutex_lock(&data->lock_als);
+ ret = regmap_field_write(data->reg_als_rate, i);
+ mutex_unlock(&data->lock_als);
+
+ return ret;
+}
+
+static int ltr501_ps_write_samp_freq(struct ltr501_data *data,
+ int val, int val2)
+{
+ int i, ret;
+
+ i = ltr501_match_samp_freq(ltr501_ps_samp_table,
+ ARRAY_SIZE(ltr501_ps_samp_table),
+ val, val2);
+
+ if (i < 0)
+ return i;
+
+ mutex_lock(&data->lock_ps);
+ ret = regmap_field_write(data->reg_ps_rate, i);
+ mutex_unlock(&data->lock_ps);
+
+ return ret;
+}
+
+static int ltr501_als_read_samp_period(struct ltr501_data *data, int *val)
+{
+ int ret, i;
+
+ ret = regmap_field_read(data->reg_als_rate, &i);
+ if (ret < 0)
+ return ret;
+
+ if (i < 0 || i >= ARRAY_SIZE(ltr501_als_samp_table))
+ return -EINVAL;
+
+ *val = ltr501_als_samp_table[i].time_val;
+
+ return IIO_VAL_INT;
+}
+
+static int ltr501_ps_read_samp_period(struct ltr501_data *data, int *val)
+{
+ int ret, i;
+
+ ret = regmap_field_read(data->reg_ps_rate, &i);
+ if (ret < 0)
+ return ret;
+
+ if (i < 0 || i >= ARRAY_SIZE(ltr501_ps_samp_table))
+ return -EINVAL;
+
+ *val = ltr501_ps_samp_table[i].time_val;
+
+ return IIO_VAL_INT;
+}
+
+/* IR and visible spectrum coeff's are given in data sheet */
+static unsigned long ltr501_calculate_lux(u16 vis_data, u16 ir_data)
+{
+ unsigned long ratio, lux;
+
+ if (vis_data == 0)
+ return 0;
+
+ /* multiply numerator by 100 to avoid handling ratio < 1 */
+ ratio = DIV_ROUND_UP(ir_data * 100, ir_data + vis_data);
+
+ if (ratio < 45)
+ lux = LTR501_LUX_CONV(1774, vis_data, -1105, ir_data);
+ else if (ratio >= 45 && ratio < 64)
+ lux = LTR501_LUX_CONV(3772, vis_data, 1336, ir_data);
+ else if (ratio >= 64 && ratio < 85)
+ lux = LTR501_LUX_CONV(1690, vis_data, 169, ir_data);
+ else
+ lux = 0;
+
+ return lux / 1000;
+}
+
static int ltr501_drdy(struct ltr501_data *data, u8 drdy_mask)
{
int tries = 100;
- int ret;
+ int ret, status;
while (tries--) {
- ret = i2c_smbus_read_byte_data(data->client,
- LTR501_ALS_PS_STATUS);
+ ret = regmap_read(data->regmap, LTR501_ALS_PS_STATUS, &status);
if (ret < 0)
return ret;
- if ((ret & drdy_mask) == drdy_mask)
+ if ((status & drdy_mask) == drdy_mask)
return 0;
msleep(25);
}
@@ -70,25 +342,221 @@ static int ltr501_drdy(struct ltr501_data *data, u8 drdy_mask)
return -EIO;
}
+static int ltr501_set_it_time(struct ltr501_data *data, int it)
+{
+ int ret, i, index = -1, status;
+
+ for (i = 0; i < ARRAY_SIZE(int_time_mapping); i++) {
+ if (int_time_mapping[i] == it) {
+ index = i;
+ break;
+ }
+ }
+ /* Make sure integ time index is valid */
+ if (index < 0)
+ return -EINVAL;
+
+ ret = regmap_read(data->regmap, LTR501_ALS_CONTR, &status);
+ if (ret < 0)
+ return ret;
+
+ if (status & LTR501_CONTR_ALS_GAIN_MASK) {
+ /*
+ * 200 ms and 400 ms integ time can only be
+ * used in dynamic range 1
+ */
+ if (index > 1)
+ return -EINVAL;
+ } else
+ /* 50 ms integ time can only be used in dynamic range 2 */
+ if (index == 1)
+ return -EINVAL;
+
+ return regmap_field_write(data->reg_it, index);
+}
+
+/* read int time in micro seconds */
+static int ltr501_read_it_time(struct ltr501_data *data, int *val, int *val2)
+{
+ int ret, index;
+
+ ret = regmap_field_read(data->reg_it, &index);
+ if (ret < 0)
+ return ret;
+
+ /* Make sure integ time index is valid */
+ if (index < 0 || index >= ARRAY_SIZE(int_time_mapping))
+ return -EINVAL;
+
+ *val2 = int_time_mapping[index];
+ *val = 0;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
static int ltr501_read_als(struct ltr501_data *data, __le16 buf[2])
{
- int ret = ltr501_drdy(data, LTR501_STATUS_ALS_RDY);
+ int ret;
+
+ ret = ltr501_drdy(data, LTR501_STATUS_ALS_RDY);
if (ret < 0)
return ret;
/* always read both ALS channels in given order */
- return i2c_smbus_read_i2c_block_data(data->client,
- LTR501_ALS_DATA1, 2 * sizeof(__le16), (u8 *) buf);
+ return regmap_bulk_read(data->regmap, LTR501_ALS_DATA1,
+ buf, 2 * sizeof(__le16));
}
static int ltr501_read_ps(struct ltr501_data *data)
{
- int ret = ltr501_drdy(data, LTR501_STATUS_PS_RDY);
+ int ret, status;
+
+ ret = ltr501_drdy(data, LTR501_STATUS_PS_RDY);
if (ret < 0)
return ret;
- return i2c_smbus_read_word_data(data->client, LTR501_PS_DATA);
+
+ ret = regmap_bulk_read(data->regmap, LTR501_PS_DATA,
+ &status, 2);
+ if (ret < 0)
+ return ret;
+
+ return status;
+}
+
+static int ltr501_read_intr_prst(struct ltr501_data *data,
+ enum iio_chan_type type,
+ int *val2)
+{
+ int ret, samp_period, prst;
+
+ switch (type) {
+ case IIO_INTENSITY:
+ ret = regmap_field_read(data->reg_als_prst, &prst);
+ if (ret < 0)
+ return ret;
+
+ ret = ltr501_als_read_samp_period(data, &samp_period);
+
+ if (ret < 0)
+ return ret;
+ *val2 = samp_period * prst;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_PROXIMITY:
+ ret = regmap_field_read(data->reg_ps_prst, &prst);
+ if (ret < 0)
+ return ret;
+
+ ret = ltr501_ps_read_samp_period(data, &samp_period);
+
+ if (ret < 0)
+ return ret;
+
+ *val2 = samp_period * prst;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+
+ return -EINVAL;
}
-#define LTR501_INTENSITY_CHANNEL(_idx, _addr, _mod, _shared) { \
+static int ltr501_write_intr_prst(struct ltr501_data *data,
+ enum iio_chan_type type,
+ int val, int val2)
+{
+ int ret, samp_period, new_val;
+ unsigned long period;
+
+ if (val < 0 || val2 < 0)
+ return -EINVAL;
+
+ /* period in microseconds */
+ period = ((val * 1000000) + val2);
+
+ switch (type) {
+ case IIO_INTENSITY:
+ ret = ltr501_als_read_samp_period(data, &samp_period);
+ if (ret < 0)
+ return ret;
+
+ /* period should be atleast equal to sampling period */
+ if (period < samp_period)
+ return -EINVAL;
+
+ new_val = DIV_ROUND_UP(period, samp_period);
+ if (new_val < 0 || new_val > 0x0f)
+ return -EINVAL;
+
+ mutex_lock(&data->lock_als);
+ ret = regmap_field_write(data->reg_als_prst, new_val);
+ mutex_unlock(&data->lock_als);
+ if (ret >= 0)
+ data->als_period = period;
+
+ return ret;
+ case IIO_PROXIMITY:
+ ret = ltr501_ps_read_samp_period(data, &samp_period);
+ if (ret < 0)
+ return ret;
+
+ /* period should be atleast equal to rate */
+ if (period < samp_period)
+ return -EINVAL;
+
+ new_val = DIV_ROUND_UP(period, samp_period);
+ if (new_val < 0 || new_val > 0x0f)
+ return -EINVAL;
+
+ mutex_lock(&data->lock_ps);
+ ret = regmap_field_write(data->reg_ps_prst, new_val);
+ mutex_unlock(&data->lock_ps);
+ if (ret >= 0)
+ data->ps_period = period;
+
+ return ret;
+ default:
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_event_spec ltr501_als_event_spec[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE),
+ }, {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE),
+ }, {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_EITHER,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_PERIOD),
+ },
+
+};
+
+static const struct iio_event_spec ltr501_pxs_event_spec[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE),
+ }, {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE),
+ }, {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_EITHER,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_PERIOD),
+ },
+};
+
+#define LTR501_INTENSITY_CHANNEL(_idx, _addr, _mod, _shared, \
+ _evspec, _evsize) { \
.type = IIO_INTENSITY, \
.modified = 1, \
.address = (_addr), \
@@ -101,13 +569,27 @@ static int ltr501_read_ps(struct ltr501_data *data)
.realbits = 16, \
.storagebits = 16, \
.endianness = IIO_CPU, \
- } \
+ }, \
+ .event_spec = _evspec,\
+ .num_event_specs = _evsize,\
+}
+
+#define LTR501_LIGHT_CHANNEL() { \
+ .type = IIO_LIGHT, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \
+ .scan_index = -1, \
}
static const struct iio_chan_spec ltr501_channels[] = {
- LTR501_INTENSITY_CHANNEL(0, LTR501_ALS_DATA0, IIO_MOD_LIGHT_BOTH, 0),
+ LTR501_LIGHT_CHANNEL(),
+ LTR501_INTENSITY_CHANNEL(0, LTR501_ALS_DATA0, IIO_MOD_LIGHT_BOTH, 0,
+ ltr501_als_event_spec,
+ ARRAY_SIZE(ltr501_als_event_spec)),
LTR501_INTENSITY_CHANNEL(1, LTR501_ALS_DATA1, IIO_MOD_LIGHT_IR,
- BIT(IIO_CHAN_INFO_SCALE)),
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ NULL, 0),
{
.type = IIO_PROXIMITY,
.address = LTR501_PS_DATA,
@@ -120,23 +602,51 @@ static const struct iio_chan_spec ltr501_channels[] = {
.storagebits = 16,
.endianness = IIO_CPU,
},
+ .event_spec = ltr501_pxs_event_spec,
+ .num_event_specs = ARRAY_SIZE(ltr501_pxs_event_spec),
},
IIO_CHAN_SOFT_TIMESTAMP(3),
};
-static const int ltr501_ps_gain[4][2] = {
- {1, 0}, {0, 250000}, {0, 125000}, {0, 62500}
+static const struct iio_chan_spec ltr301_channels[] = {
+ LTR501_LIGHT_CHANNEL(),
+ LTR501_INTENSITY_CHANNEL(0, LTR501_ALS_DATA0, IIO_MOD_LIGHT_BOTH, 0,
+ ltr501_als_event_spec,
+ ARRAY_SIZE(ltr501_als_event_spec)),
+ LTR501_INTENSITY_CHANNEL(1, LTR501_ALS_DATA1, IIO_MOD_LIGHT_IR,
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ NULL, 0),
+ IIO_CHAN_SOFT_TIMESTAMP(2),
};
static int ltr501_read_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)
{
struct ltr501_data *data = iio_priv(indio_dev);
__le16 buf[2];
int ret, i;
switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ if (iio_buffer_enabled(indio_dev))
+ return -EBUSY;
+
+ switch (chan->type) {
+ case IIO_LIGHT:
+ mutex_lock(&data->lock_als);
+ ret = ltr501_read_als(data, buf);
+ mutex_unlock(&data->lock_als);
+ if (ret < 0)
+ return ret;
+ *val = ltr501_calculate_lux(le16_to_cpu(buf[1]),
+ le16_to_cpu(buf[0]));
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
case IIO_CHAN_INFO_RAW:
if (iio_buffer_enabled(indio_dev))
return -EBUSY;
@@ -149,7 +659,7 @@ static int ltr501_read_raw(struct iio_dev *indio_dev,
if (ret < 0)
return ret;
*val = le16_to_cpu(chan->address == LTR501_ALS_DATA1 ?
- buf[0] : buf[1]);
+ buf[0] : buf[1]);
return IIO_VAL_INT;
case IIO_PROXIMITY:
mutex_lock(&data->lock_ps);
@@ -165,45 +675,59 @@ static int ltr501_read_raw(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_SCALE:
switch (chan->type) {
case IIO_INTENSITY:
- if (data->als_contr & LTR501_CONTR_ALS_GAIN_MASK) {
- *val = 0;
- *val2 = 5000;
- return IIO_VAL_INT_PLUS_MICRO;
- } else {
- *val = 1;
- *val2 = 0;
- return IIO_VAL_INT;
- }
+ i = (data->als_contr & data->chip_info->als_gain_mask)
+ >> data->chip_info->als_gain_shift;
+ *val = data->chip_info->als_gain[i].scale;
+ *val2 = data->chip_info->als_gain[i].uscale;
+ return IIO_VAL_INT_PLUS_MICRO;
case IIO_PROXIMITY:
i = (data->ps_contr & LTR501_CONTR_PS_GAIN_MASK) >>
LTR501_CONTR_PS_GAIN_SHIFT;
- *val = ltr501_ps_gain[i][0];
- *val2 = ltr501_ps_gain[i][1];
+ *val = data->chip_info->ps_gain[i].scale;
+ *val2 = data->chip_info->ps_gain[i].uscale;
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
+ case IIO_CHAN_INFO_INT_TIME:
+ switch (chan->type) {
+ case IIO_INTENSITY:
+ return ltr501_read_it_time(data, val, val2);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ switch (chan->type) {
+ case IIO_INTENSITY:
+ return ltr501_als_read_samp_freq(data, val, val2);
+ case IIO_PROXIMITY:
+ return ltr501_ps_read_samp_freq(data, val, val2);
+ default:
+ return -EINVAL;
+ }
}
return -EINVAL;
}
-static int ltr501_get_ps_gain_index(int val, int val2)
+static int ltr501_get_gain_index(struct ltr501_gain *gain, int size,
+ int val, int val2)
{
int i;
- for (i = 0; i < ARRAY_SIZE(ltr501_ps_gain); i++)
- if (val == ltr501_ps_gain[i][0] && val2 == ltr501_ps_gain[i][1])
+ for (i = 0; i < size; i++)
+ if (val == gain[i].scale && val2 == gain[i].uscale)
return i;
return -1;
}
static int ltr501_write_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)
{
struct ltr501_data *data = iio_priv(indio_dev);
- int i;
+ int i, ret, freq_val, freq_val2;
+ struct ltr501_chip_info *info = data->chip_info;
if (iio_buffer_enabled(indio_dev))
return -EBUSY;
@@ -212,35 +736,382 @@ static int ltr501_write_raw(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_SCALE:
switch (chan->type) {
case IIO_INTENSITY:
- if (val == 0 && val2 == 5000)
- data->als_contr |= LTR501_CONTR_ALS_GAIN_MASK;
- else if (val == 1 && val2 == 0)
- data->als_contr &= ~LTR501_CONTR_ALS_GAIN_MASK;
- else
+ i = ltr501_get_gain_index(info->als_gain,
+ info->als_gain_tbl_size,
+ val, val2);
+ if (i < 0)
return -EINVAL;
- return i2c_smbus_write_byte_data(data->client,
- LTR501_ALS_CONTR, data->als_contr);
+
+ data->als_contr &= ~info->als_gain_mask;
+ data->als_contr |= i << info->als_gain_shift;
+
+ return regmap_write(data->regmap, LTR501_ALS_CONTR,
+ data->als_contr);
case IIO_PROXIMITY:
- i = ltr501_get_ps_gain_index(val, val2);
+ i = ltr501_get_gain_index(info->ps_gain,
+ info->ps_gain_tbl_size,
+ val, val2);
if (i < 0)
return -EINVAL;
data->ps_contr &= ~LTR501_CONTR_PS_GAIN_MASK;
data->ps_contr |= i << LTR501_CONTR_PS_GAIN_SHIFT;
- return i2c_smbus_write_byte_data(data->client,
- LTR501_PS_CONTR, data->ps_contr);
+
+ return regmap_write(data->regmap, LTR501_PS_CONTR,
+ data->ps_contr);
default:
return -EINVAL;
}
+ case IIO_CHAN_INFO_INT_TIME:
+ switch (chan->type) {
+ case IIO_INTENSITY:
+ if (val != 0)
+ return -EINVAL;
+ mutex_lock(&data->lock_als);
+ i = ltr501_set_it_time(data, val2);
+ mutex_unlock(&data->lock_als);
+ return i;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ switch (chan->type) {
+ case IIO_INTENSITY:
+ ret = ltr501_als_read_samp_freq(data, &freq_val,
+ &freq_val2);
+ if (ret < 0)
+ return ret;
+
+ ret = ltr501_als_write_samp_freq(data, val, val2);
+ if (ret < 0)
+ return ret;
+
+ /* update persistence count when changing frequency */
+ ret = ltr501_write_intr_prst(data, chan->type,
+ 0, data->als_period);
+
+ if (ret < 0)
+ return ltr501_als_write_samp_freq(data,
+ freq_val,
+ freq_val2);
+ return ret;
+ case IIO_PROXIMITY:
+ ret = ltr501_ps_read_samp_freq(data, &freq_val,
+ &freq_val2);
+ if (ret < 0)
+ return ret;
+
+ ret = ltr501_ps_write_samp_freq(data, val, val2);
+ if (ret < 0)
+ return ret;
+
+ /* update persistence count when changing frequency */
+ ret = ltr501_write_intr_prst(data, chan->type,
+ 0, data->ps_period);
+
+ if (ret < 0)
+ return ltr501_ps_write_samp_freq(data,
+ freq_val,
+ freq_val2);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+ }
+ return -EINVAL;
+}
+
+static int ltr501_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 ltr501_data *data = iio_priv(indio_dev);
+ int ret, thresh_data;
+
+ switch (chan->type) {
+ case IIO_INTENSITY:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = regmap_bulk_read(data->regmap,
+ LTR501_ALS_THRESH_UP,
+ &thresh_data, 2);
+ if (ret < 0)
+ return ret;
+ *val = thresh_data & LTR501_ALS_THRESH_MASK;
+ return IIO_VAL_INT;
+ case IIO_EV_DIR_FALLING:
+ ret = regmap_bulk_read(data->regmap,
+ LTR501_ALS_THRESH_LOW,
+ &thresh_data, 2);
+ if (ret < 0)
+ return ret;
+ *val = thresh_data & LTR501_ALS_THRESH_MASK;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_PROXIMITY:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = regmap_bulk_read(data->regmap,
+ LTR501_PS_THRESH_UP,
+ &thresh_data, 2);
+ if (ret < 0)
+ return ret;
+ *val = thresh_data & LTR501_PS_THRESH_MASK;
+ return IIO_VAL_INT;
+ case IIO_EV_DIR_FALLING:
+ ret = regmap_bulk_read(data->regmap,
+ LTR501_PS_THRESH_LOW,
+ &thresh_data, 2);
+ if (ret < 0)
+ return ret;
+ *val = thresh_data & LTR501_PS_THRESH_MASK;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
}
+
return -EINVAL;
}
-static IIO_CONST_ATTR(in_proximity_scale_available, "1 0.25 0.125 0.0625");
-static IIO_CONST_ATTR(in_intensity_scale_available, "1 0.005");
+static int ltr501_write_thresh(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ struct ltr501_data *data = iio_priv(indio_dev);
+ int ret;
+
+ if (val < 0)
+ return -EINVAL;
+
+ switch (chan->type) {
+ case IIO_INTENSITY:
+ if (val > LTR501_ALS_THRESH_MASK)
+ return -EINVAL;
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ mutex_lock(&data->lock_als);
+ ret = regmap_bulk_write(data->regmap,
+ LTR501_ALS_THRESH_UP,
+ &val, 2);
+ mutex_unlock(&data->lock_als);
+ return ret;
+ case IIO_EV_DIR_FALLING:
+ mutex_lock(&data->lock_als);
+ ret = regmap_bulk_write(data->regmap,
+ LTR501_ALS_THRESH_LOW,
+ &val, 2);
+ mutex_unlock(&data->lock_als);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+ case IIO_PROXIMITY:
+ if (val > LTR501_PS_THRESH_MASK)
+ return -EINVAL;
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ mutex_lock(&data->lock_ps);
+ ret = regmap_bulk_write(data->regmap,
+ LTR501_PS_THRESH_UP,
+ &val, 2);
+ mutex_unlock(&data->lock_ps);
+ return ret;
+ case IIO_EV_DIR_FALLING:
+ mutex_lock(&data->lock_ps);
+ ret = regmap_bulk_write(data->regmap,
+ LTR501_PS_THRESH_LOW,
+ &val, 2);
+ mutex_unlock(&data->lock_ps);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+static int ltr501_read_event(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ int ret;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ return ltr501_read_thresh(indio_dev, chan, type, dir,
+ info, val, val2);
+ case IIO_EV_INFO_PERIOD:
+ ret = ltr501_read_intr_prst(iio_priv(indio_dev),
+ chan->type, val2);
+ *val = *val2 / 1000000;
+ *val2 = *val2 % 1000000;
+ return ret;
+ default:
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+static int ltr501_write_event(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ if (val2 != 0)
+ return -EINVAL;
+ return ltr501_write_thresh(indio_dev, chan, type, dir,
+ info, val, val2);
+ case IIO_EV_INFO_PERIOD:
+ return ltr501_write_intr_prst(iio_priv(indio_dev), chan->type,
+ val, val2);
+ default:
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+static int ltr501_read_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ struct ltr501_data *data = iio_priv(indio_dev);
+ int ret, status;
+
+ switch (chan->type) {
+ case IIO_INTENSITY:
+ ret = regmap_field_read(data->reg_als_intr, &status);
+ if (ret < 0)
+ return ret;
+ return status;
+ case IIO_PROXIMITY:
+ ret = regmap_field_read(data->reg_ps_intr, &status);
+ if (ret < 0)
+ return ret;
+ return status;
+ default:
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+static int ltr501_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir, int 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);
+ ret = regmap_field_write(data->reg_als_intr, state);
+ mutex_unlock(&data->lock_als);
+ return ret;
+ case IIO_PROXIMITY:
+ mutex_lock(&data->lock_ps);
+ ret = regmap_field_write(data->reg_ps_intr, state);
+ mutex_unlock(&data->lock_ps);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+static ssize_t ltr501_show_proximity_scale_avail(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ltr501_data *data = iio_priv(dev_to_iio_dev(dev));
+ struct ltr501_chip_info *info = data->chip_info;
+ ssize_t len = 0;
+ int i;
+
+ for (i = 0; i < info->ps_gain_tbl_size; i++) {
+ if (info->ps_gain[i].scale == LTR501_RESERVED_GAIN)
+ continue;
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ",
+ info->ps_gain[i].scale,
+ info->ps_gain[i].uscale);
+ }
+
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static ssize_t ltr501_show_intensity_scale_avail(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ltr501_data *data = iio_priv(dev_to_iio_dev(dev));
+ struct ltr501_chip_info *info = data->chip_info;
+ ssize_t len = 0;
+ int i;
+
+ for (i = 0; i < info->als_gain_tbl_size; i++) {
+ if (info->als_gain[i].scale == LTR501_RESERVED_GAIN)
+ continue;
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ",
+ info->als_gain[i].scale,
+ info->als_gain[i].uscale);
+ }
+
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static IIO_CONST_ATTR_INT_TIME_AVAIL("0.05 0.1 0.2 0.4");
+static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("20 10 5 2 1 0.5");
+
+static IIO_DEVICE_ATTR(in_proximity_scale_available, S_IRUGO,
+ ltr501_show_proximity_scale_avail, NULL, 0);
+static IIO_DEVICE_ATTR(in_intensity_scale_available, S_IRUGO,
+ ltr501_show_intensity_scale_avail, NULL, 0);
static struct attribute *ltr501_attributes[] = {
- &iio_const_attr_in_proximity_scale_available.dev_attr.attr,
- &iio_const_attr_in_intensity_scale_available.dev_attr.attr,
+ &iio_dev_attr_in_proximity_scale_available.dev_attr.attr,
+ &iio_dev_attr_in_intensity_scale_available.dev_attr.attr,
+ &iio_const_attr_integration_time_available.dev_attr.attr,
+ &iio_const_attr_sampling_frequency_available.dev_attr.attr,
+ NULL
+};
+
+static struct attribute *ltr301_attributes[] = {
+ &iio_dev_attr_in_intensity_scale_available.dev_attr.attr,
+ &iio_const_attr_integration_time_available.dev_attr.attr,
+ &iio_const_attr_sampling_frequency_available.dev_attr.attr,
NULL
};
@@ -248,20 +1119,98 @@ static const struct attribute_group ltr501_attribute_group = {
.attrs = ltr501_attributes,
};
+static const struct attribute_group ltr301_attribute_group = {
+ .attrs = ltr301_attributes,
+};
+
+static const struct iio_info ltr501_info_no_irq = {
+ .read_raw = ltr501_read_raw,
+ .write_raw = ltr501_write_raw,
+ .attrs = &ltr501_attribute_group,
+ .driver_module = THIS_MODULE,
+};
+
static const struct iio_info ltr501_info = {
.read_raw = ltr501_read_raw,
.write_raw = ltr501_write_raw,
.attrs = &ltr501_attribute_group,
+ .read_event_value = &ltr501_read_event,
+ .write_event_value = &ltr501_write_event,
+ .read_event_config = &ltr501_read_event_config,
+ .write_event_config = &ltr501_write_event_config,
+ .driver_module = THIS_MODULE,
+};
+
+static const struct iio_info ltr301_info_no_irq = {
+ .read_raw = ltr501_read_raw,
+ .write_raw = ltr501_write_raw,
+ .attrs = &ltr301_attribute_group,
.driver_module = THIS_MODULE,
};
-static int ltr501_write_contr(struct i2c_client *client, u8 als_val, u8 ps_val)
+static const struct iio_info ltr301_info = {
+ .read_raw = ltr501_read_raw,
+ .write_raw = ltr501_write_raw,
+ .attrs = &ltr301_attribute_group,
+ .read_event_value = &ltr501_read_event,
+ .write_event_value = &ltr501_write_event,
+ .read_event_config = &ltr501_read_event_config,
+ .write_event_config = &ltr501_write_event_config,
+ .driver_module = THIS_MODULE,
+};
+
+static struct ltr501_chip_info ltr501_chip_info_tbl[] = {
+ [ltr501] = {
+ .partid = 0x08,
+ .als_gain = ltr501_als_gain_tbl,
+ .als_gain_tbl_size = ARRAY_SIZE(ltr501_als_gain_tbl),
+ .ps_gain = ltr501_ps_gain_tbl,
+ .ps_gain_tbl_size = ARRAY_SIZE(ltr501_ps_gain_tbl),
+ .als_mode_active = BIT(0) | BIT(1),
+ .als_gain_mask = BIT(3),
+ .als_gain_shift = 3,
+ .info = &ltr501_info,
+ .info_no_irq = &ltr501_info_no_irq,
+ .channels = ltr501_channels,
+ .no_channels = ARRAY_SIZE(ltr501_channels),
+ },
+ [ltr559] = {
+ .partid = 0x09,
+ .als_gain = ltr559_als_gain_tbl,
+ .als_gain_tbl_size = ARRAY_SIZE(ltr559_als_gain_tbl),
+ .ps_gain = ltr559_ps_gain_tbl,
+ .ps_gain_tbl_size = ARRAY_SIZE(ltr559_ps_gain_tbl),
+ .als_mode_active = BIT(1),
+ .als_gain_mask = BIT(2) | BIT(3) | BIT(4),
+ .als_gain_shift = 2,
+ .info = &ltr501_info,
+ .info_no_irq = &ltr501_info_no_irq,
+ .channels = ltr501_channels,
+ .no_channels = ARRAY_SIZE(ltr501_channels),
+ },
+ [ltr301] = {
+ .partid = 0x08,
+ .als_gain = ltr501_als_gain_tbl,
+ .als_gain_tbl_size = ARRAY_SIZE(ltr501_als_gain_tbl),
+ .als_mode_active = BIT(0) | BIT(1),
+ .als_gain_mask = BIT(3),
+ .als_gain_shift = 3,
+ .info = &ltr301_info,
+ .info_no_irq = &ltr301_info_no_irq,
+ .channels = ltr301_channels,
+ .no_channels = ARRAY_SIZE(ltr301_channels),
+ },
+};
+
+static int ltr501_write_contr(struct ltr501_data *data, u8 als_val, u8 ps_val)
{
- int ret = i2c_smbus_write_byte_data(client, LTR501_ALS_CONTR, als_val);
+ int ret;
+
+ ret = regmap_write(data->regmap, LTR501_ALS_CONTR, als_val);
if (ret < 0)
return ret;
- return i2c_smbus_write_byte_data(client, LTR501_PS_CONTR, ps_val);
+ return regmap_write(data->regmap, LTR501_PS_CONTR, ps_val);
}
static irqreturn_t ltr501_trigger_handler(int irq, void *p)
@@ -273,13 +1222,13 @@ static irqreturn_t ltr501_trigger_handler(int irq, void *p)
__le16 als_buf[2];
u8 mask = 0;
int j = 0;
- int ret;
+ int ret, psdata;
memset(buf, 0, sizeof(buf));
/* 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))
+ test_bit(1, indio_dev->active_scan_mask))
mask |= LTR501_STATUS_ALS_RDY;
if (test_bit(2, indio_dev->active_scan_mask))
mask |= LTR501_STATUS_PS_RDY;
@@ -289,8 +1238,8 @@ static irqreturn_t ltr501_trigger_handler(int irq, void *p)
goto done;
if (mask & LTR501_STATUS_ALS_RDY) {
- ret = i2c_smbus_read_i2c_block_data(data->client,
- LTR501_ALS_DATA1, sizeof(als_buf), (u8 *) als_buf);
+ ret = regmap_bulk_read(data->regmap, LTR501_ALS_DATA1,
+ (u8 *)als_buf, sizeof(als_buf));
if (ret < 0)
return ret;
if (test_bit(0, indio_dev->active_scan_mask))
@@ -300,14 +1249,14 @@ static irqreturn_t ltr501_trigger_handler(int irq, void *p)
}
if (mask & LTR501_STATUS_PS_RDY) {
- ret = i2c_smbus_read_word_data(data->client, LTR501_PS_DATA);
+ ret = regmap_bulk_read(data->regmap, LTR501_PS_DATA,
+ &psdata, 2);
if (ret < 0)
goto done;
- buf[j++] = ret & LTR501_PS_DATA_MASK;
+ buf[j++] = psdata & LTR501_PS_DATA_MASK;
}
- iio_push_to_buffers_with_timestamp(indio_dev, buf,
- iio_get_time_ns());
+ iio_push_to_buffers_with_timestamp(indio_dev, buf, iio_get_time_ns());
done:
iio_trigger_notify_done(indio_dev->trig);
@@ -315,67 +1264,225 @@ done:
return IRQ_HANDLED;
}
+static irqreturn_t ltr501_interrupt_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct ltr501_data *data = iio_priv(indio_dev);
+ int ret, status;
+
+ ret = regmap_read(data->regmap, LTR501_ALS_PS_STATUS, &status);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "irq read int reg failed\n");
+ return IRQ_HANDLED;
+ }
+
+ if (status & LTR501_STATUS_ALS_INTR)
+ iio_push_event(indio_dev,
+ IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER),
+ iio_get_time_ns());
+
+ if (status & LTR501_STATUS_PS_INTR)
+ iio_push_event(indio_dev,
+ IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER),
+ iio_get_time_ns());
+
+ return IRQ_HANDLED;
+}
+
static int ltr501_init(struct ltr501_data *data)
{
- int ret;
+ int ret, status;
- ret = i2c_smbus_read_byte_data(data->client, LTR501_ALS_CONTR);
+ ret = regmap_read(data->regmap, LTR501_ALS_CONTR, &status);
if (ret < 0)
return ret;
- data->als_contr = ret | LTR501_CONTR_ACTIVE;
- ret = i2c_smbus_read_byte_data(data->client, LTR501_PS_CONTR);
+ data->als_contr = status | data->chip_info->als_mode_active;
+
+ ret = regmap_read(data->regmap, LTR501_PS_CONTR, &status);
if (ret < 0)
return ret;
- data->ps_contr = ret | LTR501_CONTR_ACTIVE;
- return ltr501_write_contr(data->client, data->als_contr,
- data->ps_contr);
+ data->ps_contr = status | LTR501_CONTR_ACTIVE;
+
+ ret = ltr501_read_intr_prst(data, IIO_INTENSITY, &data->als_period);
+ if (ret < 0)
+ return ret;
+
+ ret = ltr501_read_intr_prst(data, IIO_PROXIMITY, &data->ps_period);
+ if (ret < 0)
+ return ret;
+
+ return ltr501_write_contr(data, data->als_contr, data->ps_contr);
+}
+
+static bool ltr501_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case LTR501_ALS_DATA1:
+ case LTR501_ALS_DATA0:
+ case LTR501_ALS_PS_STATUS:
+ case LTR501_PS_DATA:
+ return true;
+ default:
+ return false;
+ }
}
+static struct regmap_config ltr501_regmap_config = {
+ .name = LTR501_REGMAP_NAME,
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = LTR501_MAX_REG,
+ .cache_type = REGCACHE_RBTREE,
+ .volatile_reg = ltr501_is_volatile_reg,
+};
+
static int ltr501_powerdown(struct ltr501_data *data)
{
- return ltr501_write_contr(data->client,
- data->als_contr & ~LTR501_CONTR_ACTIVE,
+ return ltr501_write_contr(data, data->als_contr &
+ ~data->chip_info->als_mode_active,
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)
+ const struct i2c_device_id *id)
{
struct ltr501_data *data;
struct iio_dev *indio_dev;
- int ret;
+ struct regmap *regmap;
+ int ret, partid, chip_idx = 0;
+ const char *name = NULL;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
+ regmap = devm_regmap_init_i2c(client, &ltr501_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&client->dev, "Regmap initialization failed.\n");
+ return PTR_ERR(regmap);
+ }
+
data = iio_priv(indio_dev);
i2c_set_clientdata(client, indio_dev);
data->client = client;
+ data->regmap = regmap;
mutex_init(&data->lock_als);
mutex_init(&data->lock_ps);
- ret = i2c_smbus_read_byte_data(data->client, LTR501_PART_ID);
+ data->reg_it = devm_regmap_field_alloc(&client->dev, regmap,
+ reg_field_it);
+ if (IS_ERR(data->reg_it)) {
+ dev_err(&client->dev, "Integ time reg field init failed.\n");
+ return PTR_ERR(data->reg_it);
+ }
+
+ data->reg_als_intr = devm_regmap_field_alloc(&client->dev, regmap,
+ reg_field_als_intr);
+ if (IS_ERR(data->reg_als_intr)) {
+ dev_err(&client->dev, "ALS intr mode reg field init failed\n");
+ return PTR_ERR(data->reg_als_intr);
+ }
+
+ data->reg_ps_intr = devm_regmap_field_alloc(&client->dev, regmap,
+ reg_field_ps_intr);
+ if (IS_ERR(data->reg_ps_intr)) {
+ dev_err(&client->dev, "PS intr mode reg field init failed.\n");
+ return PTR_ERR(data->reg_ps_intr);
+ }
+
+ data->reg_als_rate = devm_regmap_field_alloc(&client->dev, regmap,
+ reg_field_als_rate);
+ if (IS_ERR(data->reg_als_rate)) {
+ dev_err(&client->dev, "ALS samp rate field init failed.\n");
+ return PTR_ERR(data->reg_als_rate);
+ }
+
+ data->reg_ps_rate = devm_regmap_field_alloc(&client->dev, regmap,
+ reg_field_ps_rate);
+ if (IS_ERR(data->reg_ps_rate)) {
+ dev_err(&client->dev, "PS samp rate field init failed.\n");
+ return PTR_ERR(data->reg_ps_rate);
+ }
+
+ data->reg_als_prst = devm_regmap_field_alloc(&client->dev, regmap,
+ reg_field_als_prst);
+ if (IS_ERR(data->reg_als_prst)) {
+ dev_err(&client->dev, "ALS prst reg field init failed\n");
+ return PTR_ERR(data->reg_als_prst);
+ }
+
+ data->reg_ps_prst = devm_regmap_field_alloc(&client->dev, regmap,
+ reg_field_ps_prst);
+ if (IS_ERR(data->reg_ps_prst)) {
+ dev_err(&client->dev, "PS prst reg field init failed.\n");
+ return PTR_ERR(data->reg_ps_prst);
+ }
+
+ ret = regmap_read(data->regmap, LTR501_PART_ID, &partid);
if (ret < 0)
return ret;
- if ((ret >> 4) != 0x8)
+
+ 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;
+ }
+
+ data->chip_info = &ltr501_chip_info_tbl[chip_idx];
+
+ if ((partid >> 4) != data->chip_info->partid)
return -ENODEV;
indio_dev->dev.parent = &client->dev;
- indio_dev->info = &ltr501_info;
- indio_dev->channels = ltr501_channels;
- indio_dev->num_channels = ARRAY_SIZE(ltr501_channels);
- indio_dev->name = LTR501_DRV_NAME;
+ indio_dev->info = data->chip_info->info;
+ indio_dev->channels = data->chip_info->channels;
+ indio_dev->num_channels = data->chip_info->no_channels;
+ indio_dev->name = name;
indio_dev->modes = INDIO_DIRECT_MODE;
ret = ltr501_init(data);
if (ret < 0)
return ret;
+ if (client->irq > 0) {
+ ret = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, ltr501_interrupt_handler,
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ "ltr501_thresh_event",
+ indio_dev);
+ if (ret) {
+ dev_err(&client->dev, "request irq (%d) failed\n",
+ client->irq);
+ return ret;
+ }
+ } else {
+ indio_dev->info = data->chip_info->info_no_irq;
+ }
+
ret = iio_triggered_buffer_setup(indio_dev, NULL,
- ltr501_trigger_handler, NULL);
+ ltr501_trigger_handler, NULL);
if (ret)
goto powerdown_on_error;
@@ -407,24 +1514,34 @@ static int ltr501_remove(struct i2c_client *client)
static int ltr501_suspend(struct device *dev)
{
struct ltr501_data *data = iio_priv(i2c_get_clientdata(
- to_i2c_client(dev)));
+ to_i2c_client(dev)));
return ltr501_powerdown(data);
}
static int ltr501_resume(struct device *dev)
{
struct ltr501_data *data = iio_priv(i2c_get_clientdata(
- to_i2c_client(dev)));
+ to_i2c_client(dev)));
- return ltr501_write_contr(data->client, data->als_contr,
+ return ltr501_write_contr(data, data->als_contr,
data->ps_contr);
}
#endif
static 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},
+ { },
+};
+MODULE_DEVICE_TABLE(acpi, ltr_acpi_match);
+
static const struct i2c_device_id ltr501_id[] = {
- { "ltr501", 0 },
+ { "ltr501", ltr501},
+ { "ltr559", ltr559},
+ { "ltr301", ltr301},
{ }
};
MODULE_DEVICE_TABLE(i2c, ltr501_id);
@@ -433,7 +1550,7 @@ static struct i2c_driver ltr501_driver = {
.driver = {
.name = LTR501_DRV_NAME,
.pm = &ltr501_pm_ops,
- .owner = THIS_MODULE,
+ .acpi_match_table = ACPI_PTR(ltr_acpi_match),
},
.probe = ltr501_probe,
.remove = ltr501_remove,
diff --git a/kernel/drivers/iio/light/opt3001.c b/kernel/drivers/iio/light/opt3001.c
new file mode 100644
index 000000000..01e111e72
--- /dev/null
+++ b/kernel/drivers/iio/light/opt3001.c
@@ -0,0 +1,803 @@
+/**
+ * opt3001.c - Texas Instruments OPT3001 Light Sensor
+ *
+ * Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com
+ *
+ * Author: Andreas Dannenberg <dannenberg@ti.com>
+ * Based on previous work from: Felipe Balbi <balbi@ti.com>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 of the License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define OPT3001_RESULT 0x00
+#define OPT3001_CONFIGURATION 0x01
+#define OPT3001_LOW_LIMIT 0x02
+#define OPT3001_HIGH_LIMIT 0x03
+#define OPT3001_MANUFACTURER_ID 0x7e
+#define OPT3001_DEVICE_ID 0x7f
+
+#define OPT3001_CONFIGURATION_RN_MASK (0xf << 12)
+#define OPT3001_CONFIGURATION_RN_AUTO (0xc << 12)
+
+#define OPT3001_CONFIGURATION_CT BIT(11)
+
+#define OPT3001_CONFIGURATION_M_MASK (3 << 9)
+#define OPT3001_CONFIGURATION_M_SHUTDOWN (0 << 9)
+#define OPT3001_CONFIGURATION_M_SINGLE (1 << 9)
+#define OPT3001_CONFIGURATION_M_CONTINUOUS (2 << 9) /* also 3 << 9 */
+
+#define OPT3001_CONFIGURATION_OVF BIT(8)
+#define OPT3001_CONFIGURATION_CRF BIT(7)
+#define OPT3001_CONFIGURATION_FH BIT(6)
+#define OPT3001_CONFIGURATION_FL BIT(5)
+#define OPT3001_CONFIGURATION_L BIT(4)
+#define OPT3001_CONFIGURATION_POL BIT(3)
+#define OPT3001_CONFIGURATION_ME BIT(2)
+
+#define OPT3001_CONFIGURATION_FC_MASK (3 << 0)
+
+/* The end-of-conversion enable is located in the low-limit register */
+#define OPT3001_LOW_LIMIT_EOC_ENABLE 0xc000
+
+#define OPT3001_REG_EXPONENT(n) ((n) >> 12)
+#define OPT3001_REG_MANTISSA(n) ((n) & 0xfff)
+
+/*
+ * Time to wait for conversion result to be ready. The device datasheet
+ * worst-case max value is 880ms. Add some slack to be on the safe side.
+ */
+#define OPT3001_RESULT_READY_TIMEOUT msecs_to_jiffies(1000)
+
+struct opt3001 {
+ struct i2c_client *client;
+ struct device *dev;
+
+ struct mutex lock;
+ u16 ok_to_ignore_lock:1;
+ u16 result_ready:1;
+ wait_queue_head_t result_ready_queue;
+ u16 result;
+
+ u32 int_time;
+ u32 mode;
+
+ u16 high_thresh_mantissa;
+ u16 low_thresh_mantissa;
+
+ u8 high_thresh_exp;
+ u8 low_thresh_exp;
+};
+
+struct opt3001_scale {
+ int val;
+ int val2;
+};
+
+static const struct opt3001_scale opt3001_scales[] = {
+ {
+ .val = 40,
+ .val2 = 950000,
+ },
+ {
+ .val = 81,
+ .val2 = 900000,
+ },
+ {
+ .val = 163,
+ .val2 = 800000,
+ },
+ {
+ .val = 327,
+ .val2 = 600000,
+ },
+ {
+ .val = 655,
+ .val2 = 200000,
+ },
+ {
+ .val = 1310,
+ .val2 = 400000,
+ },
+ {
+ .val = 2620,
+ .val2 = 800000,
+ },
+ {
+ .val = 5241,
+ .val2 = 600000,
+ },
+ {
+ .val = 10483,
+ .val2 = 200000,
+ },
+ {
+ .val = 20966,
+ .val2 = 400000,
+ },
+ {
+ .val = 83865,
+ .val2 = 600000,
+ },
+};
+
+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];
+
+ /*
+ * Combine the integer and micro parts for comparison
+ * purposes. Use milli lux precision to avoid 32-bit integer
+ * overflows.
+ */
+ if ((val * 1000 + val2 / 1000) <=
+ (scale->val * 1000 + scale->val2 / 1000)) {
+ *exponent = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static void opt3001_to_iio_ret(struct opt3001 *opt, u8 exponent,
+ u16 mantissa, int *val, int *val2)
+{
+ int lux;
+
+ lux = 10 * (mantissa << exponent);
+ *val = lux / 1000;
+ *val2 = (lux - (*val * 1000)) * 1000;
+}
+
+static void opt3001_set_mode(struct opt3001 *opt, u16 *reg, u16 mode)
+{
+ *reg &= ~OPT3001_CONFIGURATION_M_MASK;
+ *reg |= mode;
+ opt->mode = mode;
+}
+
+static IIO_CONST_ATTR_INT_TIME_AVAIL("0.1 0.8");
+
+static struct attribute *opt3001_attributes[] = {
+ &iio_const_attr_integration_time_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group opt3001_attribute_group = {
+ .attrs = opt3001_attributes,
+};
+
+static const struct iio_event_spec opt3001_event_spec[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ },
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ },
+};
+
+static const struct iio_chan_spec opt3001_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
+ BIT(IIO_CHAN_INFO_INT_TIME),
+ .event_spec = opt3001_event_spec,
+ .num_event_specs = ARRAY_SIZE(opt3001_event_spec),
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static int opt3001_get_lux(struct opt3001 *opt, int *val, int *val2)
+{
+ int ret;
+ u16 mantissa;
+ u16 reg;
+ u8 exponent;
+ u16 value;
+
+ /*
+ * Enable the end-of-conversion interrupt mechanism. Note that doing
+ * so will overwrite the low-level limit value however we will restore
+ * this value later on.
+ */
+ ret = i2c_smbus_write_word_swapped(opt->client, OPT3001_LOW_LIMIT,
+ OPT3001_LOW_LIMIT_EOC_ENABLE);
+ if (ret < 0) {
+ dev_err(opt->dev, "failed to write register %02x\n",
+ OPT3001_LOW_LIMIT);
+ return ret;
+ }
+
+ /* Reset data-ready indicator flag (will be set in the IRQ routine) */
+ opt->result_ready = false;
+
+ /* Allow IRQ to access the device despite lock being set */
+ opt->ok_to_ignore_lock = true;
+
+ /* Configure for single-conversion mode and start a new conversion */
+ ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_CONFIGURATION);
+ if (ret < 0) {
+ dev_err(opt->dev, "failed to read register %02x\n",
+ OPT3001_CONFIGURATION);
+ goto err;
+ }
+
+ reg = ret;
+ opt3001_set_mode(opt, &reg, OPT3001_CONFIGURATION_M_SINGLE);
+
+ ret = i2c_smbus_write_word_swapped(opt->client, OPT3001_CONFIGURATION,
+ reg);
+ if (ret < 0) {
+ dev_err(opt->dev, "failed to write register %02x\n",
+ OPT3001_CONFIGURATION);
+ goto err;
+ }
+
+ /* Wait for the IRQ to indicate the conversion is complete */
+ ret = wait_event_timeout(opt->result_ready_queue, opt->result_ready,
+ OPT3001_RESULT_READY_TIMEOUT);
+
+err:
+ /* Disallow IRQ to access the device while lock is active */
+ opt->ok_to_ignore_lock = false;
+
+ if (ret == 0)
+ return -ETIMEDOUT;
+ else if (ret < 0)
+ return ret;
+
+ /*
+ * Disable the end-of-conversion interrupt mechanism by restoring the
+ * low-level limit value (clearing OPT3001_LOW_LIMIT_EOC_ENABLE). Note
+ * that selectively clearing those enable bits would affect the actual
+ * limit value due to bit-overlap and therefore can't be done.
+ */
+ value = (opt->low_thresh_exp << 12) | opt->low_thresh_mantissa;
+ ret = i2c_smbus_write_word_swapped(opt->client, OPT3001_LOW_LIMIT,
+ value);
+ if (ret < 0) {
+ dev_err(opt->dev, "failed to write register %02x\n",
+ OPT3001_LOW_LIMIT);
+ return ret;
+ }
+
+ exponent = OPT3001_REG_EXPONENT(opt->result);
+ mantissa = OPT3001_REG_MANTISSA(opt->result);
+
+ opt3001_to_iio_ret(opt, exponent, mantissa, val, val2);
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int opt3001_get_int_time(struct opt3001 *opt, int *val, int *val2)
+{
+ *val = 0;
+ *val2 = opt->int_time;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int opt3001_set_int_time(struct opt3001 *opt, int time)
+{
+ int ret;
+ u16 reg;
+
+ ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_CONFIGURATION);
+ if (ret < 0) {
+ dev_err(opt->dev, "failed to read register %02x\n",
+ OPT3001_CONFIGURATION);
+ return ret;
+ }
+
+ reg = ret;
+
+ switch (time) {
+ case 100000:
+ reg &= ~OPT3001_CONFIGURATION_CT;
+ opt->int_time = 100000;
+ break;
+ case 800000:
+ reg |= OPT3001_CONFIGURATION_CT;
+ opt->int_time = 800000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return i2c_smbus_write_word_swapped(opt->client, OPT3001_CONFIGURATION,
+ reg);
+}
+
+static int opt3001_read_raw(struct iio_dev *iio,
+ struct iio_chan_spec const *chan, int *val, int *val2,
+ long mask)
+{
+ struct opt3001 *opt = iio_priv(iio);
+ int ret;
+
+ if (opt->mode == OPT3001_CONFIGURATION_M_CONTINUOUS)
+ return -EBUSY;
+
+ if (chan->type != IIO_LIGHT)
+ return -EINVAL;
+
+ mutex_lock(&opt->lock);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ ret = opt3001_get_lux(opt, val, val2);
+ break;
+ case IIO_CHAN_INFO_INT_TIME:
+ ret = opt3001_get_int_time(opt, val, val2);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ mutex_unlock(&opt->lock);
+
+ return ret;
+}
+
+static int opt3001_write_raw(struct iio_dev *iio,
+ struct iio_chan_spec const *chan, int val, int val2,
+ long mask)
+{
+ struct opt3001 *opt = iio_priv(iio);
+ int ret;
+
+ if (opt->mode == OPT3001_CONFIGURATION_M_CONTINUOUS)
+ return -EBUSY;
+
+ if (chan->type != IIO_LIGHT)
+ return -EINVAL;
+
+ if (mask != IIO_CHAN_INFO_INT_TIME)
+ return -EINVAL;
+
+ if (val != 0)
+ return -EINVAL;
+
+ mutex_lock(&opt->lock);
+ ret = opt3001_set_int_time(opt, val2);
+ mutex_unlock(&opt->lock);
+
+ return ret;
+}
+
+static int opt3001_read_event_value(struct iio_dev *iio,
+ const struct iio_chan_spec *chan, enum iio_event_type type,
+ enum iio_event_direction dir, enum iio_event_info info,
+ int *val, int *val2)
+{
+ struct opt3001 *opt = iio_priv(iio);
+ int ret = IIO_VAL_INT_PLUS_MICRO;
+
+ mutex_lock(&opt->lock);
+
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ opt3001_to_iio_ret(opt, opt->high_thresh_exp,
+ opt->high_thresh_mantissa, val, val2);
+ break;
+ case IIO_EV_DIR_FALLING:
+ opt3001_to_iio_ret(opt, opt->low_thresh_exp,
+ opt->low_thresh_mantissa, val, val2);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ mutex_unlock(&opt->lock);
+
+ return ret;
+}
+
+static int opt3001_write_event_value(struct iio_dev *iio,
+ const struct iio_chan_spec *chan, enum iio_event_type type,
+ enum iio_event_direction dir, enum iio_event_info info,
+ int val, int val2)
+{
+ struct opt3001 *opt = iio_priv(iio);
+ int ret;
+
+ u16 mantissa;
+ u16 value;
+ u16 reg;
+
+ u8 exponent;
+
+ if (val < 0)
+ return -EINVAL;
+
+ mutex_lock(&opt->lock);
+
+ ret = opt3001_find_scale(opt, val, val2, &exponent);
+ if (ret < 0) {
+ dev_err(opt->dev, "can't find scale for %d.%06u\n", val, val2);
+ goto err;
+ }
+
+ mantissa = (((val * 1000) + (val2 / 1000)) / 10) >> exponent;
+ value = (exponent << 12) | mantissa;
+
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ reg = OPT3001_HIGH_LIMIT;
+ opt->high_thresh_mantissa = mantissa;
+ opt->high_thresh_exp = exponent;
+ break;
+ case IIO_EV_DIR_FALLING:
+ reg = OPT3001_LOW_LIMIT;
+ opt->low_thresh_mantissa = mantissa;
+ opt->low_thresh_exp = exponent;
+ break;
+ default:
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = i2c_smbus_write_word_swapped(opt->client, reg, value);
+ if (ret < 0) {
+ dev_err(opt->dev, "failed to write register %02x\n", reg);
+ goto err;
+ }
+
+err:
+ mutex_unlock(&opt->lock);
+
+ return ret;
+}
+
+static int opt3001_read_event_config(struct iio_dev *iio,
+ const struct iio_chan_spec *chan, enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ struct opt3001 *opt = iio_priv(iio);
+
+ return opt->mode == OPT3001_CONFIGURATION_M_CONTINUOUS;
+}
+
+static int opt3001_write_event_config(struct iio_dev *iio,
+ const struct iio_chan_spec *chan, enum iio_event_type type,
+ enum iio_event_direction dir, int state)
+{
+ struct opt3001 *opt = iio_priv(iio);
+ int ret;
+ u16 mode;
+ u16 reg;
+
+ if (state && opt->mode == OPT3001_CONFIGURATION_M_CONTINUOUS)
+ return 0;
+
+ if (!state && opt->mode == OPT3001_CONFIGURATION_M_SHUTDOWN)
+ return 0;
+
+ mutex_lock(&opt->lock);
+
+ mode = state ? OPT3001_CONFIGURATION_M_CONTINUOUS
+ : OPT3001_CONFIGURATION_M_SHUTDOWN;
+
+ ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_CONFIGURATION);
+ if (ret < 0) {
+ dev_err(opt->dev, "failed to read register %02x\n",
+ OPT3001_CONFIGURATION);
+ goto err;
+ }
+
+ reg = ret;
+ opt3001_set_mode(opt, &reg, mode);
+
+ ret = i2c_smbus_write_word_swapped(opt->client, OPT3001_CONFIGURATION,
+ reg);
+ if (ret < 0) {
+ dev_err(opt->dev, "failed to write register %02x\n",
+ OPT3001_CONFIGURATION);
+ goto err;
+ }
+
+err:
+ mutex_unlock(&opt->lock);
+
+ return ret;
+}
+
+static const struct iio_info opt3001_info = {
+ .driver_module = THIS_MODULE,
+ .attrs = &opt3001_attribute_group,
+ .read_raw = opt3001_read_raw,
+ .write_raw = opt3001_write_raw,
+ .read_event_value = opt3001_read_event_value,
+ .write_event_value = opt3001_write_event_value,
+ .read_event_config = opt3001_read_event_config,
+ .write_event_config = opt3001_write_event_config,
+};
+
+static int opt3001_read_id(struct opt3001 *opt)
+{
+ char manufacturer[2];
+ u16 device_id;
+ int ret;
+
+ ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_MANUFACTURER_ID);
+ if (ret < 0) {
+ dev_err(opt->dev, "failed to read register %02x\n",
+ OPT3001_MANUFACTURER_ID);
+ return ret;
+ }
+
+ manufacturer[0] = ret >> 8;
+ manufacturer[1] = ret & 0xff;
+
+ 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);
+ return ret;
+ }
+
+ device_id = ret;
+
+ dev_info(opt->dev, "Found %c%c OPT%04x\n", manufacturer[0],
+ manufacturer[1], device_id);
+
+ return 0;
+}
+
+static int opt3001_configure(struct opt3001 *opt)
+{
+ int ret;
+ u16 reg;
+
+ ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_CONFIGURATION);
+ if (ret < 0) {
+ dev_err(opt->dev, "failed to read register %02x\n",
+ OPT3001_CONFIGURATION);
+ return ret;
+ }
+
+ reg = ret;
+
+ /* Enable automatic full-scale setting mode */
+ reg &= ~OPT3001_CONFIGURATION_RN_MASK;
+ reg |= OPT3001_CONFIGURATION_RN_AUTO;
+
+ /* Reflect status of the device's integration time setting */
+ if (reg & OPT3001_CONFIGURATION_CT)
+ opt->int_time = 800000;
+ else
+ opt->int_time = 100000;
+
+ /* Ensure device is in shutdown initially */
+ opt3001_set_mode(opt, &reg, OPT3001_CONFIGURATION_M_SHUTDOWN);
+
+ /* Configure for latched window-style comparison operation */
+ reg |= OPT3001_CONFIGURATION_L;
+ reg &= ~OPT3001_CONFIGURATION_POL;
+ reg &= ~OPT3001_CONFIGURATION_ME;
+ reg &= ~OPT3001_CONFIGURATION_FC_MASK;
+
+ ret = i2c_smbus_write_word_swapped(opt->client, OPT3001_CONFIGURATION,
+ reg);
+ if (ret < 0) {
+ dev_err(opt->dev, "failed to write register %02x\n",
+ OPT3001_CONFIGURATION);
+ return ret;
+ }
+
+ ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_LOW_LIMIT);
+ if (ret < 0) {
+ dev_err(opt->dev, "failed to read register %02x\n",
+ OPT3001_LOW_LIMIT);
+ return ret;
+ }
+
+ opt->low_thresh_mantissa = OPT3001_REG_MANTISSA(ret);
+ opt->low_thresh_exp = OPT3001_REG_EXPONENT(ret);
+
+ ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_HIGH_LIMIT);
+ if (ret < 0) {
+ dev_err(opt->dev, "failed to read register %02x\n",
+ OPT3001_HIGH_LIMIT);
+ return ret;
+ }
+
+ opt->high_thresh_mantissa = OPT3001_REG_MANTISSA(ret);
+ opt->high_thresh_exp = OPT3001_REG_EXPONENT(ret);
+
+ return 0;
+}
+
+static irqreturn_t opt3001_irq(int irq, void *_iio)
+{
+ struct iio_dev *iio = _iio;
+ struct opt3001 *opt = iio_priv(iio);
+ int ret;
+
+ if (!opt->ok_to_ignore_lock)
+ mutex_lock(&opt->lock);
+
+ ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_CONFIGURATION);
+ if (ret < 0) {
+ dev_err(opt->dev, "failed to read register %02x\n",
+ OPT3001_CONFIGURATION);
+ goto out;
+ }
+
+ if ((ret & OPT3001_CONFIGURATION_M_MASK) ==
+ OPT3001_CONFIGURATION_M_CONTINUOUS) {
+ if (ret & OPT3001_CONFIGURATION_FH)
+ iio_push_event(iio,
+ IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_RISING),
+ iio_get_time_ns());
+ if (ret & OPT3001_CONFIGURATION_FL)
+ iio_push_event(iio,
+ IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_FALLING),
+ iio_get_time_ns());
+ } else if (ret & OPT3001_CONFIGURATION_CRF) {
+ ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_RESULT);
+ if (ret < 0) {
+ dev_err(opt->dev, "failed to read register %02x\n",
+ OPT3001_RESULT);
+ goto out;
+ }
+ opt->result = ret;
+ opt->result_ready = true;
+ wake_up(&opt->result_ready_queue);
+ }
+
+out:
+ if (!opt->ok_to_ignore_lock)
+ mutex_unlock(&opt->lock);
+
+ return IRQ_HANDLED;
+}
+
+static int opt3001_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+
+ struct iio_dev *iio;
+ struct opt3001 *opt;
+ int irq = client->irq;
+ int ret;
+
+ iio = devm_iio_device_alloc(dev, sizeof(*opt));
+ if (!iio)
+ return -ENOMEM;
+
+ opt = iio_priv(iio);
+ opt->client = client;
+ opt->dev = dev;
+
+ 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;
+
+ ret = opt3001_configure(opt);
+ if (ret)
+ return ret;
+
+ iio->name = client->name;
+ iio->channels = opt3001_channels;
+ iio->num_channels = ARRAY_SIZE(opt3001_channels);
+ iio->dev.parent = dev;
+ iio->modes = INDIO_DIRECT_MODE;
+ iio->info = &opt3001_info;
+
+ ret = devm_iio_device_register(dev, iio);
+ if (ret) {
+ dev_err(dev, "failed to register IIO device\n");
+ return ret;
+ }
+
+ ret = request_threaded_irq(irq, NULL, opt3001_irq,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ "opt3001", iio);
+ if (ret) {
+ dev_err(dev, "failed to request IRQ #%d\n", irq);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int opt3001_remove(struct i2c_client *client)
+{
+ struct iio_dev *iio = i2c_get_clientdata(client);
+ struct opt3001 *opt = iio_priv(iio);
+ int ret;
+ u16 reg;
+
+ free_irq(client->irq, iio);
+
+ ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_CONFIGURATION);
+ if (ret < 0) {
+ dev_err(opt->dev, "failed to read register %02x\n",
+ OPT3001_CONFIGURATION);
+ return ret;
+ }
+
+ reg = ret;
+ opt3001_set_mode(opt, &reg, OPT3001_CONFIGURATION_M_SHUTDOWN);
+
+ ret = i2c_smbus_write_word_swapped(opt->client, OPT3001_CONFIGURATION,
+ reg);
+ if (ret < 0) {
+ dev_err(opt->dev, "failed to write register %02x\n",
+ OPT3001_CONFIGURATION);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct i2c_device_id opt3001_id[] = {
+ { "opt3001", 0 },
+ { } /* Terminating Entry */
+};
+MODULE_DEVICE_TABLE(i2c, opt3001_id);
+
+static const struct of_device_id opt3001_of_match[] = {
+ { .compatible = "ti,opt3001" },
+ { }
+};
+
+static struct i2c_driver opt3001_driver = {
+ .probe = opt3001_probe,
+ .remove = opt3001_remove,
+ .id_table = opt3001_id,
+
+ .driver = {
+ .name = "opt3001",
+ .of_match_table = of_match_ptr(opt3001_of_match),
+ },
+};
+
+module_i2c_driver(opt3001_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Andreas Dannenberg <dannenberg@ti.com>");
+MODULE_DESCRIPTION("Texas Instruments OPT3001 Light Sensor Driver");
diff --git a/kernel/drivers/iio/light/pa12203001.c b/kernel/drivers/iio/light/pa12203001.c
new file mode 100644
index 000000000..45f7bde02
--- /dev/null
+++ b/kernel/drivers/iio/light/pa12203001.c
@@ -0,0 +1,483 @@
+/*
+ * Copyright (c) 2015 Intel Corporation
+ *
+ * Driver for TXC PA12203001 Proximity and Ambient Light Sensor.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ * To do: Interrupt support.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#define PA12203001_DRIVER_NAME "pa12203001"
+
+#define PA12203001_REG_CFG0 0x00
+#define PA12203001_REG_CFG1 0x01
+#define PA12203001_REG_CFG2 0x02
+#define PA12203001_REG_CFG3 0x03
+
+#define PA12203001_REG_ADL 0x0b
+#define PA12203001_REG_PDH 0x0e
+
+#define PA12203001_REG_POFS 0x10
+#define PA12203001_REG_PSET 0x11
+
+#define PA12203001_ALS_EN_MASK BIT(0)
+#define PA12203001_PX_EN_MASK BIT(1)
+#define PA12203001_PX_NORMAL_MODE_MASK GENMASK(7, 6)
+#define PA12203001_AFSR_MASK GENMASK(5, 4)
+#define PA12203001_AFSR_SHIFT 4
+
+#define PA12203001_PSCAN 0x03
+
+/* als range 31000, ps, als disabled */
+#define PA12203001_REG_CFG0_DEFAULT 0x30
+
+/* led current: 100 mA */
+#define PA12203001_REG_CFG1_DEFAULT 0x20
+
+/* ps mode: normal, interrupts not active */
+#define PA12203001_REG_CFG2_DEFAULT 0xcc
+
+#define PA12203001_REG_CFG3_DEFAULT 0x00
+
+#define PA12203001_SLEEP_DELAY_MS 3000
+
+#define PA12203001_CHIP_ENABLE 0xff
+#define PA12203001_CHIP_DISABLE 0x00
+
+/* available scales: corresponding to [500, 4000, 7000, 31000] lux */
+static const int pa12203001_scales[] = { 7629, 61036, 106813, 473029};
+
+struct pa12203001_data {
+ struct i2c_client *client;
+
+ /* protect device states */
+ struct mutex lock;
+
+ bool als_enabled;
+ bool px_enabled;
+ bool als_needs_enable;
+ bool px_needs_enable;
+
+ struct regmap *map;
+};
+
+static const struct {
+ u8 reg;
+ u8 val;
+} regvals[] = {
+ {PA12203001_REG_CFG0, PA12203001_REG_CFG0_DEFAULT},
+ {PA12203001_REG_CFG1, PA12203001_REG_CFG1_DEFAULT},
+ {PA12203001_REG_CFG2, PA12203001_REG_CFG2_DEFAULT},
+ {PA12203001_REG_CFG3, PA12203001_REG_CFG3_DEFAULT},
+ {PA12203001_REG_PSET, PA12203001_PSCAN},
+};
+
+static IIO_CONST_ATTR(in_illuminance_scale_available,
+ "0.007629 0.061036 0.106813 0.473029");
+
+static struct attribute *pa12203001_attrs[] = {
+ &iio_const_attr_in_illuminance_scale_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group pa12203001_attr_group = {
+ .attrs = pa12203001_attrs,
+};
+
+static const struct iio_chan_spec pa12203001_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ },
+ {
+ .type = IIO_PROXIMITY,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ }
+};
+
+static const struct regmap_range pa12203001_volatile_regs_ranges[] = {
+ regmap_reg_range(PA12203001_REG_ADL, PA12203001_REG_ADL + 1),
+ regmap_reg_range(PA12203001_REG_PDH, PA12203001_REG_PDH),
+};
+
+static const struct regmap_access_table pa12203001_volatile_regs = {
+ .yes_ranges = pa12203001_volatile_regs_ranges,
+ .n_yes_ranges = ARRAY_SIZE(pa12203001_volatile_regs_ranges),
+};
+
+static const struct regmap_config pa12203001_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = PA12203001_REG_PSET,
+ .cache_type = REGCACHE_RBTREE,
+ .volatile_table = &pa12203001_volatile_regs,
+};
+
+static inline int pa12203001_als_enable(struct pa12203001_data *data, u8 enable)
+{
+ int ret;
+
+ ret = regmap_update_bits(data->map, PA12203001_REG_CFG0,
+ PA12203001_ALS_EN_MASK, enable);
+ if (ret < 0)
+ return ret;
+
+ data->als_enabled = !!enable;
+
+ return 0;
+}
+
+static inline int pa12203001_px_enable(struct pa12203001_data *data, u8 enable)
+{
+ int ret;
+
+ ret = regmap_update_bits(data->map, PA12203001_REG_CFG0,
+ PA12203001_PX_EN_MASK, enable);
+ if (ret < 0)
+ return ret;
+
+ data->px_enabled = !!enable;
+
+ return 0;
+}
+
+static int pa12203001_set_power_state(struct pa12203001_data *data, bool on,
+ u8 mask)
+{
+#ifdef CONFIG_PM
+ int ret;
+
+ if (on && (mask & PA12203001_ALS_EN_MASK)) {
+ mutex_lock(&data->lock);
+ if (data->px_enabled) {
+ ret = pa12203001_als_enable(data,
+ PA12203001_ALS_EN_MASK);
+ if (ret < 0)
+ goto err;
+ } else {
+ data->als_needs_enable = true;
+ }
+ mutex_unlock(&data->lock);
+ }
+
+ if (on && (mask & PA12203001_PX_EN_MASK)) {
+ mutex_lock(&data->lock);
+ if (data->als_enabled) {
+ ret = pa12203001_px_enable(data, PA12203001_PX_EN_MASK);
+ if (ret < 0)
+ goto err;
+ } else {
+ data->px_needs_enable = true;
+ }
+ mutex_unlock(&data->lock);
+ }
+
+ if (on) {
+ ret = pm_runtime_get_sync(&data->client->dev);
+ if (ret < 0)
+ pm_runtime_put_noidle(&data->client->dev);
+
+ } else {
+ pm_runtime_mark_last_busy(&data->client->dev);
+ ret = pm_runtime_put_autosuspend(&data->client->dev);
+ }
+
+ return ret;
+
+err:
+ mutex_unlock(&data->lock);
+ return ret;
+
+#endif
+ return 0;
+}
+
+static int pa12203001_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct pa12203001_data *data = iio_priv(indio_dev);
+ int ret;
+ u8 dev_mask;
+ unsigned int reg_byte;
+ __le16 reg_word;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->type) {
+ case IIO_LIGHT:
+ dev_mask = PA12203001_ALS_EN_MASK;
+ ret = pa12203001_set_power_state(data, true, dev_mask);
+ if (ret < 0)
+ return ret;
+ /*
+ * ALS ADC value is stored in registers
+ * PA12203001_REG_ADL and in PA12203001_REG_ADL + 1.
+ */
+ ret = regmap_bulk_read(data->map, PA12203001_REG_ADL,
+ &reg_word, 2);
+ if (ret < 0)
+ goto reg_err;
+
+ *val = le16_to_cpu(reg_word);
+ ret = pa12203001_set_power_state(data, false, dev_mask);
+ if (ret < 0)
+ return ret;
+ break;
+ case IIO_PROXIMITY:
+ dev_mask = PA12203001_PX_EN_MASK;
+ ret = pa12203001_set_power_state(data, true, dev_mask);
+ if (ret < 0)
+ return ret;
+ ret = regmap_read(data->map, PA12203001_REG_PDH,
+ &reg_byte);
+ if (ret < 0)
+ goto reg_err;
+
+ *val = reg_byte;
+ ret = pa12203001_set_power_state(data, false, dev_mask);
+ if (ret < 0)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ ret = regmap_read(data->map, PA12203001_REG_CFG0, &reg_byte);
+ if (ret < 0)
+ return ret;
+ *val = 0;
+ reg_byte = (reg_byte & PA12203001_AFSR_MASK);
+ *val2 = pa12203001_scales[reg_byte >> 4];
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+
+reg_err:
+ pa12203001_set_power_state(data, false, dev_mask);
+ return ret;
+}
+
+static int pa12203001_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long mask)
+{
+ struct pa12203001_data *data = iio_priv(indio_dev);
+ int i, ret, new_val;
+ unsigned int reg_byte;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ ret = regmap_read(data->map, PA12203001_REG_CFG0, &reg_byte);
+ if (val != 0 || ret < 0)
+ return -EINVAL;
+ for (i = 0; i < ARRAY_SIZE(pa12203001_scales); i++) {
+ if (val2 == pa12203001_scales[i]) {
+ new_val = i << PA12203001_AFSR_SHIFT;
+ return regmap_update_bits(data->map,
+ PA12203001_REG_CFG0,
+ PA12203001_AFSR_MASK,
+ new_val);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_info pa12203001_info = {
+ .driver_module = THIS_MODULE,
+ .read_raw = pa12203001_read_raw,
+ .write_raw = pa12203001_write_raw,
+ .attrs = &pa12203001_attr_group,
+};
+
+static int pa12203001_init(struct iio_dev *indio_dev)
+{
+ struct pa12203001_data *data = iio_priv(indio_dev);
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(regvals); i++) {
+ ret = regmap_write(data->map, regvals[i].reg, regvals[i].val);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int pa12203001_power_chip(struct iio_dev *indio_dev, u8 state)
+{
+ struct pa12203001_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = pa12203001_als_enable(data, state);
+ if (ret < 0)
+ goto out;
+
+ ret = pa12203001_px_enable(data, state);
+
+out:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+static int pa12203001_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct pa12203001_data *data;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev,
+ sizeof(struct pa12203001_data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+
+ data->map = devm_regmap_init_i2c(client, &pa12203001_regmap_config);
+ if (IS_ERR(data->map))
+ return PTR_ERR(data->map);
+
+ mutex_init(&data->lock);
+
+ indio_dev->dev.parent = &client->dev;
+ indio_dev->info = &pa12203001_info;
+ indio_dev->name = PA12203001_DRIVER_NAME;
+ indio_dev->channels = pa12203001_channels;
+ indio_dev->num_channels = ARRAY_SIZE(pa12203001_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = pa12203001_init(indio_dev);
+ if (ret < 0)
+ return ret;
+
+ ret = pa12203001_power_chip(indio_dev, PA12203001_CHIP_ENABLE);
+ if (ret < 0)
+ return ret;
+
+ ret = pm_runtime_set_active(&client->dev);
+ if (ret < 0) {
+ pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE);
+ return ret;
+ }
+
+ pm_runtime_enable(&client->dev);
+ pm_runtime_set_autosuspend_delay(&client->dev,
+ PA12203001_SLEEP_DELAY_MS);
+ pm_runtime_use_autosuspend(&client->dev);
+
+ return iio_device_register(indio_dev);
+}
+
+static int pa12203001_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+ iio_device_unregister(indio_dev);
+
+ pm_runtime_disable(&client->dev);
+ pm_runtime_set_suspended(&client->dev);
+
+ return pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE);
+}
+
+#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM)
+static int pa12203001_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+
+ return pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE);
+}
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+static int pa12203001_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+
+ return pa12203001_power_chip(indio_dev, PA12203001_CHIP_ENABLE);
+}
+#endif
+
+#ifdef CONFIG_PM
+static int pa12203001_runtime_resume(struct device *dev)
+{
+ struct pa12203001_data *data;
+
+ data = iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+
+ mutex_lock(&data->lock);
+ if (data->als_needs_enable) {
+ pa12203001_als_enable(data, PA12203001_ALS_EN_MASK);
+ data->als_needs_enable = false;
+ }
+ if (data->px_needs_enable) {
+ pa12203001_px_enable(data, PA12203001_PX_EN_MASK);
+ data->px_needs_enable = false;
+ }
+ mutex_unlock(&data->lock);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops pa12203001_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pa12203001_suspend, pa12203001_resume)
+ SET_RUNTIME_PM_OPS(pa12203001_suspend, pa12203001_runtime_resume, NULL)
+};
+
+static const struct acpi_device_id pa12203001_acpi_match[] = {
+ { "TXCPA122", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(acpi, pa12203001_acpi_match);
+
+static const struct i2c_device_id pa12203001_id[] = {
+ {"txcpa122", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, pa12203001_id);
+
+static struct i2c_driver pa12203001_driver = {
+ .driver = {
+ .name = PA12203001_DRIVER_NAME,
+ .pm = &pa12203001_pm_ops,
+ .acpi_match_table = ACPI_PTR(pa12203001_acpi_match),
+ },
+ .probe = pa12203001_probe,
+ .remove = pa12203001_remove,
+ .id_table = pa12203001_id,
+
+};
+module_i2c_driver(pa12203001_driver);
+
+MODULE_AUTHOR("Adriana Reus <adriana.reus@intel.com>");
+MODULE_DESCRIPTION("Driver for TXC PA12203001 Proximity and Light Sensor");
+MODULE_LICENSE("GPL v2");
diff --git a/kernel/drivers/iio/light/rpr0521.c b/kernel/drivers/iio/light/rpr0521.c
new file mode 100644
index 000000000..4b75bb099
--- /dev/null
+++ b/kernel/drivers/iio/light/rpr0521.c
@@ -0,0 +1,615 @@
+/*
+ * RPR-0521 ROHM Ambient Light and Proximity Sensor
+ *
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This file is subject to the terms and conditions of version 2 of
+ * the GNU General Public License. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * IIO driver for RPR-0521RS (7-bit I2C slave address 0x38).
+ *
+ * TODO: illuminance channel, PM support, buffer
+ */
+
+#include <linux/module.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/sysfs.h>
+#include <linux/pm_runtime.h>
+
+#define RPR0521_REG_SYSTEM_CTRL 0x40
+#define RPR0521_REG_MODE_CTRL 0x41
+#define RPR0521_REG_ALS_CTRL 0x42
+#define RPR0521_REG_PXS_CTRL 0x43
+#define RPR0521_REG_PXS_DATA 0x44 /* 16-bit, little endian */
+#define RPR0521_REG_ALS_DATA0 0x46 /* 16-bit, little endian */
+#define RPR0521_REG_ALS_DATA1 0x48 /* 16-bit, little endian */
+#define RPR0521_REG_ID 0x92
+
+#define RPR0521_MODE_ALS_MASK BIT(7)
+#define RPR0521_MODE_PXS_MASK BIT(6)
+#define RPR0521_MODE_MEAS_TIME_MASK GENMASK(3, 0)
+#define RPR0521_ALS_DATA0_GAIN_MASK GENMASK(5, 4)
+#define RPR0521_ALS_DATA0_GAIN_SHIFT 4
+#define RPR0521_ALS_DATA1_GAIN_MASK GENMASK(3, 2)
+#define RPR0521_ALS_DATA1_GAIN_SHIFT 2
+#define RPR0521_PXS_GAIN_MASK GENMASK(5, 4)
+#define RPR0521_PXS_GAIN_SHIFT 4
+
+#define RPR0521_MODE_ALS_ENABLE BIT(7)
+#define RPR0521_MODE_ALS_DISABLE 0x00
+#define RPR0521_MODE_PXS_ENABLE BIT(6)
+#define RPR0521_MODE_PXS_DISABLE 0x00
+
+#define RPR0521_MANUFACT_ID 0xE0
+#define RPR0521_DEFAULT_MEAS_TIME 0x06 /* ALS - 100ms, PXS - 100ms */
+
+#define RPR0521_DRV_NAME "RPR0521"
+#define RPR0521_REGMAP_NAME "rpr0521_regmap"
+
+#define RPR0521_SLEEP_DELAY_MS 2000
+
+#define RPR0521_ALS_SCALE_AVAIL "0.007812 0.015625 0.5 1"
+#define RPR0521_PXS_SCALE_AVAIL "0.125 0.5 1"
+
+struct rpr0521_gain {
+ int scale;
+ int uscale;
+};
+
+static const struct rpr0521_gain rpr0521_als_gain[4] = {
+ {1, 0}, /* x1 */
+ {0, 500000}, /* x2 */
+ {0, 15625}, /* x64 */
+ {0, 7812}, /* x128 */
+};
+
+static const struct rpr0521_gain rpr0521_pxs_gain[3] = {
+ {1, 0}, /* x1 */
+ {0, 500000}, /* x2 */
+ {0, 125000}, /* x4 */
+};
+
+enum rpr0521_channel {
+ RPR0521_CHAN_ALS_DATA0,
+ RPR0521_CHAN_ALS_DATA1,
+ RPR0521_CHAN_PXS,
+};
+
+struct rpr0521_reg_desc {
+ u8 address;
+ u8 device_mask;
+};
+
+static const struct rpr0521_reg_desc rpr0521_data_reg[] = {
+ [RPR0521_CHAN_ALS_DATA0] = {
+ .address = RPR0521_REG_ALS_DATA0,
+ .device_mask = RPR0521_MODE_ALS_MASK,
+ },
+ [RPR0521_CHAN_ALS_DATA1] = {
+ .address = RPR0521_REG_ALS_DATA1,
+ .device_mask = RPR0521_MODE_ALS_MASK,
+ },
+ [RPR0521_CHAN_PXS] = {
+ .address = RPR0521_REG_PXS_DATA,
+ .device_mask = RPR0521_MODE_PXS_MASK,
+ },
+};
+
+static const struct rpr0521_gain_info {
+ u8 reg;
+ u8 mask;
+ u8 shift;
+ const struct rpr0521_gain *gain;
+ int size;
+} rpr0521_gain[] = {
+ [RPR0521_CHAN_ALS_DATA0] = {
+ .reg = RPR0521_REG_ALS_CTRL,
+ .mask = RPR0521_ALS_DATA0_GAIN_MASK,
+ .shift = RPR0521_ALS_DATA0_GAIN_SHIFT,
+ .gain = rpr0521_als_gain,
+ .size = ARRAY_SIZE(rpr0521_als_gain),
+ },
+ [RPR0521_CHAN_ALS_DATA1] = {
+ .reg = RPR0521_REG_ALS_CTRL,
+ .mask = RPR0521_ALS_DATA1_GAIN_MASK,
+ .shift = RPR0521_ALS_DATA1_GAIN_SHIFT,
+ .gain = rpr0521_als_gain,
+ .size = ARRAY_SIZE(rpr0521_als_gain),
+ },
+ [RPR0521_CHAN_PXS] = {
+ .reg = RPR0521_REG_PXS_CTRL,
+ .mask = RPR0521_PXS_GAIN_MASK,
+ .shift = RPR0521_PXS_GAIN_SHIFT,
+ .gain = rpr0521_pxs_gain,
+ .size = ARRAY_SIZE(rpr0521_pxs_gain),
+ },
+};
+
+struct rpr0521_data {
+ struct i2c_client *client;
+
+ /* protect device params updates (e.g state, gain) */
+ struct mutex lock;
+
+ /* device active status */
+ bool als_dev_en;
+ bool pxs_dev_en;
+
+ /* optimize runtime pm ops - enable device only if needed */
+ bool als_ps_need_en;
+ bool pxs_ps_need_en;
+
+ struct regmap *regmap;
+};
+
+static IIO_CONST_ATTR(in_intensity_scale_available, RPR0521_ALS_SCALE_AVAIL);
+static IIO_CONST_ATTR(in_proximity_scale_available, RPR0521_PXS_SCALE_AVAIL);
+
+static struct attribute *rpr0521_attributes[] = {
+ &iio_const_attr_in_intensity_scale_available.dev_attr.attr,
+ &iio_const_attr_in_proximity_scale_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group rpr0521_attribute_group = {
+ .attrs = rpr0521_attributes,
+};
+
+static const struct iio_chan_spec rpr0521_channels[] = {
+ {
+ .type = IIO_INTENSITY,
+ .modified = 1,
+ .address = RPR0521_CHAN_ALS_DATA0,
+ .channel2 = IIO_MOD_LIGHT_BOTH,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ },
+ {
+ .type = IIO_INTENSITY,
+ .modified = 1,
+ .address = RPR0521_CHAN_ALS_DATA1,
+ .channel2 = IIO_MOD_LIGHT_IR,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ },
+ {
+ .type = IIO_PROXIMITY,
+ .address = RPR0521_CHAN_PXS,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ }
+};
+
+static int rpr0521_als_enable(struct rpr0521_data *data, u8 status)
+{
+ int ret;
+
+ ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL,
+ RPR0521_MODE_ALS_MASK,
+ status);
+ if (ret < 0)
+ return ret;
+
+ data->als_dev_en = true;
+
+ return 0;
+}
+
+static int rpr0521_pxs_enable(struct rpr0521_data *data, u8 status)
+{
+ int ret;
+
+ ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL,
+ RPR0521_MODE_PXS_MASK,
+ status);
+ if (ret < 0)
+ return ret;
+
+ data->pxs_dev_en = true;
+
+ return 0;
+}
+
+/**
+ * rpr0521_set_power_state - handles runtime PM state and sensors enabled status
+ *
+ * @data: rpr0521 device private data
+ * @on: state to be set for devices in @device_mask
+ * @device_mask: bitmask specifying for which device we need to update @on state
+ *
+ * We rely on rpr0521_runtime_resume to enable our @device_mask devices, but
+ * if (for example) PXS was enabled (pxs_dev_en = true) by a previous call to
+ * rpr0521_runtime_resume and we want to enable ALS we MUST set ALS enable
+ * bit of RPR0521_REG_MODE_CTRL here because rpr0521_runtime_resume will not
+ * be called twice.
+ */
+static int rpr0521_set_power_state(struct rpr0521_data *data, bool on,
+ u8 device_mask)
+{
+#ifdef CONFIG_PM
+ int ret;
+ u8 update_mask = 0;
+
+ if (device_mask & RPR0521_MODE_ALS_MASK) {
+ if (on && !data->als_ps_need_en && data->pxs_dev_en)
+ update_mask |= RPR0521_MODE_ALS_MASK;
+ else
+ data->als_ps_need_en = on;
+ }
+
+ if (device_mask & RPR0521_MODE_PXS_MASK) {
+ if (on && !data->pxs_ps_need_en && data->als_dev_en)
+ update_mask |= RPR0521_MODE_PXS_MASK;
+ else
+ data->pxs_ps_need_en = on;
+ }
+
+ if (update_mask) {
+ ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL,
+ update_mask, update_mask);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (on) {
+ ret = pm_runtime_get_sync(&data->client->dev);
+ } else {
+ pm_runtime_mark_last_busy(&data->client->dev);
+ ret = pm_runtime_put_autosuspend(&data->client->dev);
+ }
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "Failed: rpr0521_set_power_state for %d, ret %d\n",
+ on, ret);
+ if (on)
+ pm_runtime_put_noidle(&data->client->dev);
+
+ return ret;
+ }
+#endif
+ return 0;
+}
+
+static int rpr0521_get_gain(struct rpr0521_data *data, int chan,
+ int *val, int *val2)
+{
+ int ret, reg, idx;
+
+ ret = regmap_read(data->regmap, rpr0521_gain[chan].reg, &reg);
+ if (ret < 0)
+ return ret;
+
+ idx = (rpr0521_gain[chan].mask & reg) >> rpr0521_gain[chan].shift;
+ *val = rpr0521_gain[chan].gain[idx].scale;
+ *val2 = rpr0521_gain[chan].gain[idx].uscale;
+
+ return 0;
+}
+
+static int rpr0521_set_gain(struct rpr0521_data *data, int chan,
+ int val, int val2)
+{
+ int i, idx = -EINVAL;
+
+ /* get gain index */
+ for (i = 0; i < rpr0521_gain[chan].size; i++)
+ if (val == rpr0521_gain[chan].gain[i].scale &&
+ val2 == rpr0521_gain[chan].gain[i].uscale) {
+ idx = i;
+ break;
+ }
+
+ if (idx < 0)
+ return idx;
+
+ return regmap_update_bits(data->regmap, rpr0521_gain[chan].reg,
+ rpr0521_gain[chan].mask,
+ idx << rpr0521_gain[chan].shift);
+}
+
+static int rpr0521_read_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;
+ u8 device_mask;
+ __le16 raw_data;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (chan->type != IIO_INTENSITY && chan->type != IIO_PROXIMITY)
+ return -EINVAL;
+
+ 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) {
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+
+ ret = regmap_bulk_read(data->regmap,
+ rpr0521_data_reg[chan->address].address,
+ &raw_data, 2);
+ if (ret < 0) {
+ rpr0521_set_power_state(data, false, device_mask);
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+
+ ret = rpr0521_set_power_state(data, false, device_mask);
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ return ret;
+
+ *val = le16_to_cpu(raw_data);
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ mutex_lock(&data->lock);
+ ret = rpr0521_get_gain(data, chan->address, val, val2);
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int rpr0521_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long mask)
+{
+ struct rpr0521_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ mutex_lock(&data->lock);
+ ret = rpr0521_set_gain(data, chan->address, val, val2);
+ mutex_unlock(&data->lock);
+
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info rpr0521_info = {
+ .driver_module = THIS_MODULE,
+ .read_raw = rpr0521_read_raw,
+ .write_raw = rpr0521_write_raw,
+ .attrs = &rpr0521_attribute_group,
+};
+
+static int rpr0521_init(struct rpr0521_data *data)
+{
+ int ret;
+ int id;
+
+ ret = regmap_read(data->regmap, RPR0521_REG_ID, &id);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Failed to read REG_ID register\n");
+ return ret;
+ }
+
+ if (id != RPR0521_MANUFACT_ID) {
+ dev_err(&data->client->dev, "Wrong id, got %x, expected %x\n",
+ id, RPR0521_MANUFACT_ID);
+ return -ENODEV;
+ }
+
+ /* set default measurement time - 100 ms for both ALS and PS */
+ ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL,
+ RPR0521_MODE_MEAS_TIME_MASK,
+ RPR0521_DEFAULT_MEAS_TIME);
+ if (ret) {
+ pr_err("regmap_update_bits returned %d\n", ret);
+ return ret;
+ }
+
+ ret = rpr0521_als_enable(data, RPR0521_MODE_ALS_ENABLE);
+ if (ret < 0)
+ return ret;
+ ret = rpr0521_pxs_enable(data, RPR0521_MODE_PXS_ENABLE);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int rpr0521_poweroff(struct rpr0521_data *data)
+{
+ int ret;
+
+ ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL,
+ RPR0521_MODE_ALS_MASK |
+ RPR0521_MODE_PXS_MASK,
+ RPR0521_MODE_ALS_DISABLE |
+ RPR0521_MODE_PXS_DISABLE);
+ if (ret < 0)
+ return ret;
+
+ data->als_dev_en = false;
+ data->pxs_dev_en = false;
+
+ return 0;
+}
+
+static bool rpr0521_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case RPR0521_REG_MODE_CTRL:
+ case RPR0521_REG_ALS_CTRL:
+ case RPR0521_REG_PXS_CTRL:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static const struct regmap_config rpr0521_regmap_config = {
+ .name = RPR0521_REGMAP_NAME,
+
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = RPR0521_REG_ID,
+ .cache_type = REGCACHE_RBTREE,
+ .volatile_reg = rpr0521_is_volatile_reg,
+};
+
+static int rpr0521_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct rpr0521_data *data;
+ struct iio_dev *indio_dev;
+ struct regmap *regmap;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ regmap = devm_regmap_init_i2c(client, &rpr0521_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&client->dev, "regmap_init failed!\n");
+ return PTR_ERR(regmap);
+ }
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+ data->regmap = regmap;
+
+ mutex_init(&data->lock);
+
+ indio_dev->dev.parent = &client->dev;
+ indio_dev->info = &rpr0521_info;
+ indio_dev->name = RPR0521_DRV_NAME;
+ indio_dev->channels = rpr0521_channels;
+ indio_dev->num_channels = ARRAY_SIZE(rpr0521_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = rpr0521_init(data);
+ if (ret < 0) {
+ dev_err(&client->dev, "rpr0521 chip init failed\n");
+ return ret;
+ }
+ ret = iio_device_register(indio_dev);
+ if (ret < 0)
+ return ret;
+
+ ret = pm_runtime_set_active(&client->dev);
+ if (ret < 0)
+ goto err_iio_unregister;
+
+ pm_runtime_enable(&client->dev);
+ pm_runtime_set_autosuspend_delay(&client->dev, RPR0521_SLEEP_DELAY_MS);
+ pm_runtime_use_autosuspend(&client->dev);
+
+ return 0;
+
+err_iio_unregister:
+ iio_device_unregister(indio_dev);
+ return ret;
+}
+
+static int rpr0521_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+ pm_runtime_disable(&client->dev);
+ pm_runtime_set_suspended(&client->dev);
+ pm_runtime_put_noidle(&client->dev);
+
+ iio_device_unregister(indio_dev);
+ rpr0521_poweroff(iio_priv(indio_dev));
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int rpr0521_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct rpr0521_data *data = iio_priv(indio_dev);
+ int ret;
+
+ /* disable channels and sets {als,pxs}_dev_en to false */
+ mutex_lock(&data->lock);
+ ret = rpr0521_poweroff(data);
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static int rpr0521_runtime_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct rpr0521_data *data = iio_priv(indio_dev);
+ int ret;
+
+ if (data->als_ps_need_en) {
+ ret = rpr0521_als_enable(data, RPR0521_MODE_ALS_ENABLE);
+ if (ret < 0)
+ return ret;
+ data->als_ps_need_en = false;
+ }
+
+ if (data->pxs_ps_need_en) {
+ ret = rpr0521_pxs_enable(data, RPR0521_MODE_PXS_ENABLE);
+ if (ret < 0)
+ return ret;
+ data->pxs_ps_need_en = false;
+ }
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops rpr0521_pm_ops = {
+ SET_RUNTIME_PM_OPS(rpr0521_runtime_suspend,
+ rpr0521_runtime_resume, NULL)
+};
+
+static const struct acpi_device_id rpr0521_acpi_match[] = {
+ {"RPR0521", 0},
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, rpr0521_acpi_match);
+
+static const struct i2c_device_id rpr0521_id[] = {
+ {"rpr0521", 0},
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, rpr0521_id);
+
+static struct i2c_driver rpr0521_driver = {
+ .driver = {
+ .name = RPR0521_DRV_NAME,
+ .pm = &rpr0521_pm_ops,
+ .acpi_match_table = ACPI_PTR(rpr0521_acpi_match),
+ },
+ .probe = rpr0521_probe,
+ .remove = rpr0521_remove,
+ .id_table = rpr0521_id,
+};
+
+module_i2c_driver(rpr0521_driver);
+
+MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>");
+MODULE_DESCRIPTION("RPR0521 ROHM Ambient Light and Proximity Sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/kernel/drivers/iio/light/stk3310.c b/kernel/drivers/iio/light/stk3310.c
new file mode 100644
index 000000000..42d334ba6
--- /dev/null
+++ b/kernel/drivers/iio/light/stk3310.c
@@ -0,0 +1,698 @@
+/**
+ * Sensortek STK3310/STK3311 Ambient Light and Proximity Sensor
+ *
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This file is subject to the terms and conditions of version 2 of
+ * the GNU General Public License. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * 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/regmap.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define STK3310_REG_STATE 0x00
+#define STK3310_REG_PSCTRL 0x01
+#define STK3310_REG_ALSCTRL 0x02
+#define STK3310_REG_INT 0x04
+#define STK3310_REG_THDH_PS 0x06
+#define STK3310_REG_THDL_PS 0x08
+#define STK3310_REG_FLAG 0x10
+#define STK3310_REG_PS_DATA_MSB 0x11
+#define STK3310_REG_PS_DATA_LSB 0x12
+#define STK3310_REG_ALS_DATA_MSB 0x13
+#define STK3310_REG_ALS_DATA_LSB 0x14
+#define STK3310_REG_ID 0x3E
+#define STK3310_MAX_REG 0x80
+
+#define STK3310_STATE_EN_PS BIT(0)
+#define STK3310_STATE_EN_ALS BIT(1)
+#define STK3310_STATE_STANDBY 0x00
+
+#define STK3310_CHIP_ID_VAL 0x13
+#define STK3311_CHIP_ID_VAL 0x1D
+#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"
+
+#define STK3310_IT_AVAILABLE \
+ "0.000185 0.000370 0.000741 0.001480 0.002960 0.005920 0.011840 " \
+ "0.023680 0.047360 0.094720 0.189440 0.378880 0.757760 1.515520 " \
+ "3.031040 6.062080"
+
+#define STK3310_REGFIELD(name) \
+ do { \
+ data->reg_##name = \
+ devm_regmap_field_alloc(&client->dev, regmap, \
+ stk3310_reg_field_##name); \
+ if (IS_ERR(data->reg_##name)) { \
+ dev_err(&client->dev, "reg field alloc failed.\n"); \
+ return PTR_ERR(data->reg_##name); \
+ } \
+ } while (0)
+
+static const struct reg_field stk3310_reg_field_state =
+ REG_FIELD(STK3310_REG_STATE, 0, 2);
+static const struct reg_field stk3310_reg_field_als_gain =
+ REG_FIELD(STK3310_REG_ALSCTRL, 4, 5);
+static const struct reg_field stk3310_reg_field_ps_gain =
+ REG_FIELD(STK3310_REG_PSCTRL, 4, 5);
+static const struct reg_field stk3310_reg_field_als_it =
+ REG_FIELD(STK3310_REG_ALSCTRL, 0, 3);
+static const struct reg_field stk3310_reg_field_ps_it =
+ REG_FIELD(STK3310_REG_PSCTRL, 0, 3);
+static const struct reg_field stk3310_reg_field_int_ps =
+ REG_FIELD(STK3310_REG_INT, 0, 2);
+static const struct reg_field stk3310_reg_field_flag_psint =
+ REG_FIELD(STK3310_REG_FLAG, 4, 4);
+static const struct reg_field stk3310_reg_field_flag_nf =
+ REG_FIELD(STK3310_REG_FLAG, 0, 0);
+
+/* Estimate maximum proximity values with regard to measurement scale. */
+static const int stk3310_ps_max[4] = {
+ STK3310_PS_MAX_VAL / 640,
+ STK3310_PS_MAX_VAL / 160,
+ STK3310_PS_MAX_VAL / 40,
+ STK3310_PS_MAX_VAL / 10
+};
+
+static const int stk3310_scale_table[][2] = {
+ {6, 400000}, {1, 600000}, {0, 400000}, {0, 100000}
+};
+
+/* Integration time in seconds, microseconds */
+static const int stk3310_it_table[][2] = {
+ {0, 185}, {0, 370}, {0, 741}, {0, 1480},
+ {0, 2960}, {0, 5920}, {0, 11840}, {0, 23680},
+ {0, 47360}, {0, 94720}, {0, 189440}, {0, 378880},
+ {0, 757760}, {1, 515520}, {3, 31040}, {6, 62080},
+};
+
+struct stk3310_data {
+ struct i2c_client *client;
+ struct mutex lock;
+ bool als_enabled;
+ bool ps_enabled;
+ u64 timestamp;
+ struct regmap *regmap;
+ struct regmap_field *reg_state;
+ struct regmap_field *reg_als_gain;
+ struct regmap_field *reg_ps_gain;
+ struct regmap_field *reg_als_it;
+ struct regmap_field *reg_ps_it;
+ struct regmap_field *reg_int_ps;
+ struct regmap_field *reg_flag_psint;
+ struct regmap_field *reg_flag_nf;
+};
+
+static const struct iio_event_spec stk3310_events[] = {
+ /* Proximity event */
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ },
+ /* Out-of-proximity event */
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ },
+};
+
+static const struct iio_chan_spec stk3310_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_INT_TIME),
+ },
+ {
+ .type = IIO_PROXIMITY,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_INT_TIME),
+ .event_spec = stk3310_events,
+ .num_event_specs = ARRAY_SIZE(stk3310_events),
+ }
+};
+
+static IIO_CONST_ATTR(in_illuminance_scale_available, STK3310_SCALE_AVAILABLE);
+
+static IIO_CONST_ATTR(in_proximity_scale_available, STK3310_SCALE_AVAILABLE);
+
+static IIO_CONST_ATTR(in_illuminance_integration_time_available,
+ STK3310_IT_AVAILABLE);
+
+static IIO_CONST_ATTR(in_proximity_integration_time_available,
+ STK3310_IT_AVAILABLE);
+
+static struct attribute *stk3310_attributes[] = {
+ &iio_const_attr_in_illuminance_scale_available.dev_attr.attr,
+ &iio_const_attr_in_proximity_scale_available.dev_attr.attr,
+ &iio_const_attr_in_illuminance_integration_time_available.dev_attr.attr,
+ &iio_const_attr_in_proximity_integration_time_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group stk3310_attribute_group = {
+ .attrs = stk3310_attributes
+};
+
+static int stk3310_get_index(const int table[][2], int table_size,
+ int val, int val2)
+{
+ int i;
+
+ for (i = 0; i < table_size; i++) {
+ if (val == table[i][0] && val2 == table[i][1])
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int stk3310_read_event(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ u8 reg;
+ __be16 buf;
+ int ret;
+ struct stk3310_data *data = iio_priv(indio_dev);
+
+ if (info != IIO_EV_INFO_VALUE)
+ return -EINVAL;
+
+ /* Only proximity interrupts are implemented at the moment. */
+ if (dir == IIO_EV_DIR_RISING)
+ reg = STK3310_REG_THDH_PS;
+ else if (dir == IIO_EV_DIR_FALLING)
+ reg = STK3310_REG_THDL_PS;
+ else
+ return -EINVAL;
+
+ mutex_lock(&data->lock);
+ ret = regmap_bulk_read(data->regmap, reg, &buf, 2);
+ mutex_unlock(&data->lock);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "register read failed\n");
+ return ret;
+ }
+ *val = be16_to_cpu(buf);
+
+ return IIO_VAL_INT;
+}
+
+static int stk3310_write_event(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ u8 reg;
+ __be16 buf;
+ int ret;
+ unsigned int index;
+ struct stk3310_data *data = iio_priv(indio_dev);
+ struct i2c_client *client = data->client;
+
+ ret = regmap_field_read(data->reg_ps_gain, &index);
+ if (ret < 0)
+ return ret;
+
+ if (val < 0 || val > stk3310_ps_max[index])
+ return -EINVAL;
+
+ if (dir == IIO_EV_DIR_RISING)
+ reg = STK3310_REG_THDH_PS;
+ else if (dir == IIO_EV_DIR_FALLING)
+ reg = STK3310_REG_THDL_PS;
+ else
+ return -EINVAL;
+
+ buf = cpu_to_be16(val);
+ ret = regmap_bulk_write(data->regmap, reg, &buf, 2);
+ if (ret < 0)
+ dev_err(&client->dev, "failed to set PS threshold!\n");
+
+ return ret;
+}
+
+static int stk3310_read_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ unsigned int event_val;
+ int ret;
+ struct stk3310_data *data = iio_priv(indio_dev);
+
+ ret = regmap_field_read(data->reg_int_ps, &event_val);
+ if (ret < 0)
+ return ret;
+
+ return event_val;
+}
+
+static int stk3310_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ int 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);
+ if (ret < 0)
+ dev_err(&client->dev, "failed to set interrupt mode\n");
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static int stk3310_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ u8 reg;
+ __be16 buf;
+ int ret;
+ unsigned int index;
+ struct stk3310_data *data = iio_priv(indio_dev);
+ struct i2c_client *client = data->client;
+
+ if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY)
+ return -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (chan->type == IIO_LIGHT)
+ reg = STK3310_REG_ALS_DATA_MSB;
+ else
+ reg = STK3310_REG_PS_DATA_MSB;
+
+ mutex_lock(&data->lock);
+ ret = regmap_bulk_read(data->regmap, reg, &buf, 2);
+ if (ret < 0) {
+ dev_err(&client->dev, "register read failed\n");
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+ *val = be16_to_cpu(buf);
+ mutex_unlock(&data->lock);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_INT_TIME:
+ if (chan->type == IIO_LIGHT)
+ ret = regmap_field_read(data->reg_als_it, &index);
+ else
+ ret = regmap_field_read(data->reg_ps_it, &index);
+ if (ret < 0)
+ return ret;
+
+ *val = stk3310_it_table[index][0];
+ *val2 = stk3310_it_table[index][1];
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->type == IIO_LIGHT)
+ ret = regmap_field_read(data->reg_als_gain, &index);
+ else
+ ret = regmap_field_read(data->reg_ps_gain, &index);
+ if (ret < 0)
+ return ret;
+
+ *val = stk3310_scale_table[index][0];
+ *val2 = stk3310_scale_table[index][1];
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+
+ return -EINVAL;
+}
+
+static int stk3310_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ int ret;
+ int index;
+ struct stk3310_data *data = iio_priv(indio_dev);
+
+ if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY)
+ return -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_INT_TIME:
+ index = stk3310_get_index(stk3310_it_table,
+ ARRAY_SIZE(stk3310_it_table),
+ val, val2);
+ if (index < 0)
+ return -EINVAL;
+ mutex_lock(&data->lock);
+ if (chan->type == IIO_LIGHT)
+ ret = regmap_field_write(data->reg_als_it, index);
+ else
+ ret = regmap_field_write(data->reg_ps_it, index);
+ if (ret < 0)
+ dev_err(&data->client->dev,
+ "sensor configuration failed\n");
+ mutex_unlock(&data->lock);
+ return ret;
+
+ case IIO_CHAN_INFO_SCALE:
+ index = stk3310_get_index(stk3310_scale_table,
+ ARRAY_SIZE(stk3310_scale_table),
+ val, val2);
+ if (index < 0)
+ return -EINVAL;
+ mutex_lock(&data->lock);
+ if (chan->type == IIO_LIGHT)
+ ret = regmap_field_write(data->reg_als_gain, index);
+ else
+ ret = regmap_field_write(data->reg_ps_gain, index);
+ if (ret < 0)
+ dev_err(&data->client->dev,
+ "sensor configuration failed\n");
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_info stk3310_info = {
+ .driver_module = THIS_MODULE,
+ .read_raw = stk3310_read_raw,
+ .write_raw = stk3310_write_raw,
+ .attrs = &stk3310_attribute_group,
+ .read_event_value = stk3310_read_event,
+ .write_event_value = stk3310_write_event,
+ .read_event_config = stk3310_read_event_config,
+ .write_event_config = stk3310_write_event_config,
+};
+
+static int stk3310_set_state(struct stk3310_data *data, u8 state)
+{
+ int ret;
+ struct i2c_client *client = data->client;
+
+ /* 3-bit state; 0b100 is not supported. */
+ if (state > 7 || state == 4)
+ return -EINVAL;
+
+ mutex_lock(&data->lock);
+ ret = regmap_field_write(data->reg_state, state);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed to change sensor state\n");
+ } else if (state != STK3310_STATE_STANDBY) {
+ /* Don't reset the 'enabled' flags if we're going in standby */
+ data->ps_enabled = !!(state & STK3310_STATE_EN_PS);
+ data->als_enabled = !!(state & STK3310_STATE_EN_ALS);
+ }
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static int stk3310_init(struct iio_dev *indio_dev)
+{
+ int ret;
+ int chipid;
+ u8 state;
+ struct stk3310_data *data = iio_priv(indio_dev);
+ struct i2c_client *client = data->client;
+
+ ret = regmap_read(data->regmap, STK3310_REG_ID, &chipid);
+ if (ret < 0)
+ return ret;
+
+ if (chipid != STK3310_CHIP_ID_VAL &&
+ chipid != STK3311_CHIP_ID_VAL) {
+ dev_err(&client->dev, "invalid chip id: 0x%x\n", chipid);
+ return -ENODEV;
+ }
+
+ state = STK3310_STATE_EN_ALS | STK3310_STATE_EN_PS;
+ ret = stk3310_set_state(data, state);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed to enable sensor");
+ return ret;
+ }
+
+ /* Enable PS interrupts */
+ ret = regmap_field_write(data->reg_int_ps, STK3310_PSINT_EN);
+ if (ret < 0)
+ dev_err(&client->dev, "failed to enable interrupts!\n");
+
+ return ret;
+}
+
+static bool stk3310_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case STK3310_REG_ALS_DATA_MSB:
+ case STK3310_REG_ALS_DATA_LSB:
+ case STK3310_REG_PS_DATA_LSB:
+ case STK3310_REG_PS_DATA_MSB:
+ case STK3310_REG_FLAG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static struct regmap_config stk3310_regmap_config = {
+ .name = STK3310_REGMAP_NAME,
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = STK3310_MAX_REG,
+ .cache_type = REGCACHE_RBTREE,
+ .volatile_reg = stk3310_is_volatile_reg,
+};
+
+static int stk3310_regmap_init(struct stk3310_data *data)
+{
+ struct regmap *regmap;
+ struct i2c_client *client;
+
+ client = data->client;
+ regmap = devm_regmap_init_i2c(client, &stk3310_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&client->dev, "regmap initialization failed.\n");
+ return PTR_ERR(regmap);
+ }
+ data->regmap = regmap;
+
+ STK3310_REGFIELD(state);
+ STK3310_REGFIELD(als_gain);
+ STK3310_REGFIELD(ps_gain);
+ STK3310_REGFIELD(als_it);
+ STK3310_REGFIELD(ps_it);
+ STK3310_REGFIELD(int_ps);
+ STK3310_REGFIELD(flag_psint);
+ STK3310_REGFIELD(flag_nf);
+
+ return 0;
+}
+
+static irqreturn_t stk3310_irq_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct stk3310_data *data = iio_priv(indio_dev);
+
+ data->timestamp = iio_get_time_ns();
+
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t stk3310_irq_event_handler(int irq, void *private)
+{
+ int ret;
+ unsigned int dir;
+ u64 event;
+
+ struct iio_dev *indio_dev = private;
+ struct stk3310_data *data = iio_priv(indio_dev);
+
+ /* Read FLAG_NF to figure out what threshold has been met. */
+ mutex_lock(&data->lock);
+ ret = regmap_field_read(data->reg_flag_nf, &dir);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "register read failed\n");
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+ event = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1,
+ IIO_EV_TYPE_THRESH,
+ (dir ? IIO_EV_DIR_FALLING :
+ IIO_EV_DIR_RISING));
+ iio_push_event(indio_dev, event, data->timestamp);
+
+ /* Reset the interrupt flag */
+ ret = regmap_field_write(data->reg_flag_psint, 0);
+ if (ret < 0)
+ dev_err(&data->client->dev, "failed to reset interrupts\n");
+ mutex_unlock(&data->lock);
+
+ return IRQ_HANDLED;
+}
+
+static int stk3310_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret;
+ struct iio_dev *indio_dev;
+ 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");
+ return -ENOMEM;
+ }
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ i2c_set_clientdata(client, indio_dev);
+ mutex_init(&data->lock);
+
+ ret = stk3310_regmap_init(data);
+ if (ret < 0)
+ return ret;
+
+ indio_dev->dev.parent = &client->dev;
+ indio_dev->info = &stk3310_info;
+ indio_dev->name = STK3310_DRIVER_NAME;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = stk3310_channels;
+ indio_dev->num_channels = ARRAY_SIZE(stk3310_channels);
+
+ ret = stk3310_init(indio_dev);
+ if (ret < 0)
+ return ret;
+
+ if (client->irq > 0) {
+ ret = devm_request_threaded_irq(&client->dev, client->irq,
+ stk3310_irq_handler,
+ stk3310_irq_event_handler,
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ STK3310_EVENT, indio_dev);
+ if (ret < 0) {
+ dev_err(&client->dev, "request irq %d failed\n",
+ client->irq);
+ goto err_standby;
+ }
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0) {
+ dev_err(&client->dev, "device_register failed\n");
+ goto err_standby;
+ }
+
+ return 0;
+
+err_standby:
+ stk3310_set_state(data, STK3310_STATE_STANDBY);
+ return ret;
+}
+
+static int stk3310_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+ iio_device_unregister(indio_dev);
+ return stk3310_set_state(iio_priv(indio_dev), STK3310_STATE_STANDBY);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int stk3310_suspend(struct device *dev)
+{
+ struct stk3310_data *data;
+
+ data = iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+
+ return stk3310_set_state(data, STK3310_STATE_STANDBY);
+}
+
+static int stk3310_resume(struct device *dev)
+{
+ u8 state = 0;
+ struct stk3310_data *data;
+
+ data = iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+ if (data->ps_enabled)
+ state |= STK3310_STATE_EN_PS;
+ if (data->als_enabled)
+ state |= STK3310_STATE_EN_ALS;
+
+ return stk3310_set_state(data, state);
+}
+
+static SIMPLE_DEV_PM_OPS(stk3310_pm_ops, stk3310_suspend, stk3310_resume);
+
+#define STK3310_PM_OPS (&stk3310_pm_ops)
+#else
+#define STK3310_PM_OPS NULL
+#endif
+
+static const struct i2c_device_id stk3310_i2c_id[] = {
+ {"STK3310", 0},
+ {"STK3311", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, stk3310_i2c_id);
+
+static const struct acpi_device_id stk3310_acpi_id[] = {
+ {"STK3310", 0},
+ {"STK3311", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(acpi, stk3310_acpi_id);
+
+static struct i2c_driver stk3310_driver = {
+ .driver = {
+ .name = "stk3310",
+ .pm = STK3310_PM_OPS,
+ .acpi_match_table = ACPI_PTR(stk3310_acpi_id),
+ },
+ .probe = stk3310_probe,
+ .remove = stk3310_remove,
+ .id_table = stk3310_i2c_id,
+};
+
+module_i2c_driver(stk3310_driver);
+
+MODULE_AUTHOR("Tiberiu Breana <tiberiu.a.breana@intel.com>");
+MODULE_DESCRIPTION("STK3310 Ambient Light and Proximity Sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/kernel/drivers/iio/light/tcs3414.c b/kernel/drivers/iio/light/tcs3414.c
index f8b1df018..f90f8c591 100644
--- a/kernel/drivers/iio/light/tcs3414.c
+++ b/kernel/drivers/iio/light/tcs3414.c
@@ -392,7 +392,6 @@ static struct i2c_driver tcs3414_driver = {
.driver = {
.name = TCS3414_DRV_NAME,
.pm = &tcs3414_pm_ops,
- .owner = THIS_MODULE,
},
.probe = tcs3414_probe,
.remove = tcs3414_remove,
diff --git a/kernel/drivers/iio/light/tcs3472.c b/kernel/drivers/iio/light/tcs3472.c
index 752569985..1b530bf04 100644
--- a/kernel/drivers/iio/light/tcs3472.c
+++ b/kernel/drivers/iio/light/tcs3472.c
@@ -366,7 +366,6 @@ static struct i2c_driver tcs3472_driver = {
.driver = {
.name = TCS3472_DRV_NAME,
.pm = &tcs3472_pm_ops,
- .owner = THIS_MODULE,
},
.probe = tcs3472_probe,
.remove = tcs3472_remove,
diff --git a/kernel/drivers/iio/light/tsl2563.c b/kernel/drivers/iio/light/tsl2563.c
index 94daa9fc1..12731d6b8 100644
--- a/kernel/drivers/iio/light/tsl2563.c
+++ b/kernel/drivers/iio/light/tsl2563.c
@@ -240,7 +240,7 @@ static int tsl2563_read_id(struct tsl2563_chip *chip, u8 *id)
* convert between normalized values and HW values obtained using given
* timing and gain settings.
*/
-static int adc_shiftbits(u8 timing)
+static int tsl2563_adc_shiftbits(u8 timing)
{
int shift = 0;
@@ -263,9 +263,9 @@ static int adc_shiftbits(u8 timing)
}
/* Convert a HW ADC value to normalized scale. */
-static u32 normalize_adc(u16 adc, u8 timing)
+static u32 tsl2563_normalize_adc(u16 adc, u8 timing)
{
- return adc << adc_shiftbits(timing);
+ return adc << tsl2563_adc_shiftbits(timing);
}
static void tsl2563_wait_adc(struct tsl2563_chip *chip)
@@ -350,8 +350,8 @@ static int tsl2563_get_adc(struct tsl2563_chip *chip)
retry = tsl2563_adjust_gainlevel(chip, adc0);
}
- chip->data0 = normalize_adc(adc0, chip->gainlevel->gaintime);
- chip->data1 = normalize_adc(adc1, chip->gainlevel->gaintime);
+ chip->data0 = tsl2563_normalize_adc(adc0, chip->gainlevel->gaintime);
+ chip->data1 = tsl2563_normalize_adc(adc1, chip->gainlevel->gaintime);
if (!chip->int_enabled)
schedule_delayed_work(&chip->poweroff_work, 5 * HZ);
@@ -361,13 +361,13 @@ out:
return ret;
}
-static inline int calib_to_sysfs(u32 calib)
+static inline int tsl2563_calib_to_sysfs(u32 calib)
{
return (int) (((calib * CALIB_BASE_SYSFS) +
CALIB_FRAC_HALF) >> CALIB_FRAC_BITS);
}
-static inline u32 calib_from_sysfs(int value)
+static inline u32 tsl2563_calib_from_sysfs(int value)
{
return (((u32) value) << CALIB_FRAC_BITS) / CALIB_BASE_SYSFS;
}
@@ -426,7 +426,7 @@ static const struct tsl2563_lux_coeff lux_table[] = {
};
/* Convert normalized, scaled ADC values to lux. */
-static unsigned int adc_to_lux(u32 adc0, u32 adc1)
+static unsigned int tsl2563_adc_to_lux(u32 adc0, u32 adc1)
{
const struct tsl2563_lux_coeff *lp = lux_table;
unsigned long ratio, lux, ch0 = adc0, ch1 = adc1;
@@ -442,7 +442,7 @@ static unsigned int adc_to_lux(u32 adc0, u32 adc1)
}
/* Apply calibration coefficient to ADC count. */
-static u32 calib_adc(u32 adc, u32 calib)
+static u32 tsl2563_calib_adc(u32 adc, u32 calib)
{
unsigned long scaled = adc;
@@ -463,9 +463,9 @@ static int tsl2563_write_raw(struct iio_dev *indio_dev,
if (mask != IIO_CHAN_INFO_CALIBSCALE)
return -EINVAL;
if (chan->channel2 == IIO_MOD_LIGHT_BOTH)
- chip->calib0 = calib_from_sysfs(val);
+ chip->calib0 = tsl2563_calib_from_sysfs(val);
else if (chan->channel2 == IIO_MOD_LIGHT_IR)
- chip->calib1 = calib_from_sysfs(val);
+ chip->calib1 = tsl2563_calib_from_sysfs(val);
else
return -EINVAL;
@@ -491,11 +491,11 @@ static int tsl2563_read_raw(struct iio_dev *indio_dev,
ret = tsl2563_get_adc(chip);
if (ret)
goto error_ret;
- calib0 = calib_adc(chip->data0, chip->calib0) *
+ calib0 = tsl2563_calib_adc(chip->data0, chip->calib0) *
chip->cover_comp_gain;
- calib1 = calib_adc(chip->data1, chip->calib1) *
+ calib1 = tsl2563_calib_adc(chip->data1, chip->calib1) *
chip->cover_comp_gain;
- *val = adc_to_lux(calib0, calib1);
+ *val = tsl2563_adc_to_lux(calib0, calib1);
ret = IIO_VAL_INT;
break;
case IIO_INTENSITY:
@@ -515,9 +515,9 @@ static int tsl2563_read_raw(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_CALIBSCALE:
if (chan->channel2 == IIO_MOD_LIGHT_BOTH)
- *val = calib_to_sysfs(chip->calib0);
+ *val = tsl2563_calib_to_sysfs(chip->calib0);
else
- *val = calib_to_sysfs(chip->calib1);
+ *val = tsl2563_calib_to_sysfs(chip->calib1);
ret = IIO_VAL_INT;
break;
default:
@@ -750,8 +750,8 @@ static int tsl2563_probe(struct i2c_client *client,
chip->high_thres = 0xffff;
chip->gainlevel = tsl2563_gainlevel_table;
chip->intr = TSL2563_INT_PERSIST(4);
- chip->calib0 = calib_from_sysfs(CALIB_BASE_SYSFS);
- chip->calib1 = calib_from_sysfs(CALIB_BASE_SYSFS);
+ 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;
diff --git a/kernel/drivers/iio/light/tsl4531.c b/kernel/drivers/iio/light/tsl4531.c
index 0763b8632..cf94ec72b 100644
--- a/kernel/drivers/iio/light/tsl4531.c
+++ b/kernel/drivers/iio/light/tsl4531.c
@@ -24,12 +24,12 @@
#define TSL4531_DRV_NAME "tsl4531"
-#define TCS3472_COMMAND BIT(7)
+#define TSL4531_COMMAND BIT(7)
-#define TSL4531_CONTROL (TCS3472_COMMAND | 0x00)
-#define TSL4531_CONFIG (TCS3472_COMMAND | 0x01)
-#define TSL4531_DATA (TCS3472_COMMAND | 0x04)
-#define TSL4531_ID (TCS3472_COMMAND | 0x0a)
+#define TSL4531_CONTROL (TSL4531_COMMAND | 0x00)
+#define TSL4531_CONFIG (TSL4531_COMMAND | 0x01)
+#define TSL4531_DATA (TSL4531_COMMAND | 0x04)
+#define TSL4531_ID (TSL4531_COMMAND | 0x0a)
/* operating modes in control register */
#define TSL4531_MODE_POWERDOWN 0x00
@@ -158,9 +158,9 @@ static int tsl4531_check_id(struct i2c_client *client)
case TSL45313_ID:
case TSL45315_ID:
case TSL45317_ID:
- return 1;
- default:
return 0;
+ default:
+ return -ENODEV;
}
}
@@ -180,9 +180,10 @@ static int tsl4531_probe(struct i2c_client *client,
data->client = client;
mutex_init(&data->lock);
- if (!tsl4531_check_id(client)) {
+ ret = tsl4531_check_id(client);
+ if (ret) {
dev_err(&client->dev, "no TSL4531 sensor\n");
- return -ENODEV;
+ return ret;
}
ret = i2c_smbus_write_byte_data(data->client, TSL4531_CONTROL,
@@ -247,7 +248,6 @@ static struct i2c_driver tsl4531_driver = {
.driver = {
.name = TSL4531_DRV_NAME,
.pm = TSL4531_PM_OPS,
- .owner = THIS_MODULE,
},
.probe = tsl4531_probe,
.remove = tsl4531_remove,
diff --git a/kernel/drivers/iio/light/us5182d.c b/kernel/drivers/iio/light/us5182d.c
new file mode 100644
index 000000000..49dab3cb3
--- /dev/null
+++ b/kernel/drivers/iio/light/us5182d.c
@@ -0,0 +1,507 @@
+/*
+ * Copyright (c) 2015 Intel Corporation
+ *
+ * Driver for UPISEMI us5182d Proximity and Ambient Light Sensor.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * To do: Interrupt support.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/mutex.h>
+
+#define US5182D_REG_CFG0 0x00
+#define US5182D_CFG0_ONESHOT_EN BIT(6)
+#define US5182D_CFG0_SHUTDOWN_EN BIT(7)
+#define US5182D_CFG0_WORD_ENABLE BIT(0)
+
+#define US5182D_REG_CFG1 0x01
+#define US5182D_CFG1_ALS_RES16 BIT(4)
+#define US5182D_CFG1_AGAIN_DEFAULT 0x00
+
+#define US5182D_REG_CFG2 0x02
+#define US5182D_CFG2_PX_RES16 BIT(4)
+#define US5182D_CFG2_PXGAIN_DEFAULT BIT(2)
+
+#define US5182D_REG_CFG3 0x03
+#define US5182D_CFG3_LED_CURRENT100 (BIT(4) | BIT(5))
+
+#define US5182D_REG_CFG4 0x10
+
+/*
+ * Registers for tuning the auto dark current cancelling feature.
+ * DARK_TH(reg 0x27,0x28) - threshold (counts) for auto dark cancelling.
+ * when ALS > DARK_TH --> ALS_Code = ALS - Upper(0x2A) * Dark
+ * when ALS < DARK_TH --> ALS_Code = ALS - Lower(0x29) * Dark
+ */
+#define US5182D_REG_UDARK_TH 0x27
+#define US5182D_REG_DARK_AUTO_EN 0x2b
+#define US5182D_REG_AUTO_LDARK_GAIN 0x29
+#define US5182D_REG_AUTO_HDARK_GAIN 0x2a
+
+#define US5182D_OPMODE_ALS 0x01
+#define US5182D_OPMODE_PX 0x02
+#define US5182D_OPMODE_SHIFT 4
+
+#define US5182D_REG_DARK_AUTO_EN_DEFAULT 0x80
+#define US5182D_REG_AUTO_LDARK_GAIN_DEFAULT 0x16
+#define US5182D_REG_AUTO_HDARK_GAIN_DEFAULT 0x00
+
+#define US5182D_REG_ADL 0x0c
+#define US5182D_REG_PDL 0x0e
+
+#define US5182D_REG_MODE_STORE 0x21
+#define US5182D_STORE_MODE 0x01
+
+#define US5182D_REG_CHIPID 0xb2
+
+#define US5182D_OPMODE_MASK GENMASK(5, 4)
+#define US5182D_AGAIN_MASK 0x07
+#define US5182D_RESET_CHIP 0x01
+
+#define US5182D_CHIPID 0x26
+#define US5182D_DRV_NAME "us5182d"
+
+#define US5182D_GA_RESOLUTION 1000
+
+#define US5182D_READ_BYTE 1
+#define US5182D_READ_WORD 2
+#define US5182D_OPSTORE_SLEEP_TIME 20 /* ms */
+
+/* Available ranges: [12354, 7065, 3998, 2202, 1285, 498, 256, 138] lux */
+static const int us5182d_scales[] = {188500, 107800, 61000, 33600, 19600, 7600,
+ 3900, 2100};
+
+/*
+ * Experimental thresholds that work with US5182D sensor on evaluation board
+ * roughly between 12-32 lux
+ */
+static u16 us5182d_dark_ths_vals[] = {170, 200, 512, 512, 800, 2000, 4000,
+ 8000};
+
+enum mode {
+ US5182D_ALS_PX,
+ US5182D_ALS_ONLY,
+ US5182D_PX_ONLY
+};
+
+struct us5182d_data {
+ struct i2c_client *client;
+ struct mutex lock;
+
+ /* Glass attenuation factor */
+ u32 ga;
+
+ /* Dark gain tuning */
+ u8 lower_dark_gain;
+ u8 upper_dark_gain;
+ u16 *us5182d_dark_ths;
+
+ u8 opmode;
+};
+
+static IIO_CONST_ATTR(in_illuminance_scale_available,
+ "0.0021 0.0039 0.0076 0.0196 0.0336 0.061 0.1078 0.1885");
+
+static struct attribute *us5182d_attrs[] = {
+ &iio_const_attr_in_illuminance_scale_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group us5182d_attr_group = {
+ .attrs = us5182d_attrs,
+};
+
+static const struct {
+ u8 reg;
+ u8 val;
+} us5182d_regvals[] = {
+ {US5182D_REG_CFG0, (US5182D_CFG0_SHUTDOWN_EN |
+ US5182D_CFG0_WORD_ENABLE)},
+ {US5182D_REG_CFG1, US5182D_CFG1_ALS_RES16},
+ {US5182D_REG_CFG2, (US5182D_CFG2_PX_RES16 |
+ US5182D_CFG2_PXGAIN_DEFAULT)},
+ {US5182D_REG_CFG3, US5182D_CFG3_LED_CURRENT100},
+ {US5182D_REG_MODE_STORE, US5182D_STORE_MODE},
+ {US5182D_REG_CFG4, 0x00},
+};
+
+static const struct iio_chan_spec us5182d_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ },
+ {
+ .type = IIO_PROXIMITY,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ }
+};
+
+static int us5182d_get_als(struct us5182d_data *data)
+{
+ int ret;
+ unsigned long result;
+
+ ret = i2c_smbus_read_word_data(data->client,
+ US5182D_REG_ADL);
+ if (ret < 0)
+ return ret;
+
+ result = ret * data->ga / US5182D_GA_RESOLUTION;
+ if (result > 0xffff)
+ result = 0xffff;
+
+ return result;
+}
+
+static int us5182d_set_opmode(struct us5182d_data *data, u8 mode)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * In oneshot mode the chip will power itself down after taking the
+ * required measurement.
+ */
+ ret = ret | US5182D_CFG0_ONESHOT_EN;
+
+ /* update mode */
+ ret = ret & ~US5182D_OPMODE_MASK;
+ ret = ret | (mode << US5182D_OPMODE_SHIFT);
+
+ /*
+ * After updating the operating mode, the chip requires that
+ * the operation is stored, by writing 1 in the STORE_MODE
+ * register (auto-clearing).
+ */
+ ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret);
+ if (ret < 0)
+ return ret;
+
+ if (mode == data->opmode)
+ return 0;
+
+ ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_MODE_STORE,
+ US5182D_STORE_MODE);
+ if (ret < 0)
+ return ret;
+
+ data->opmode = mode;
+ msleep(US5182D_OPSTORE_SLEEP_TIME);
+
+ return 0;
+}
+
+static int us5182d_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct us5182d_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->type) {
+ case IIO_LIGHT:
+ mutex_lock(&data->lock);
+ ret = us5182d_set_opmode(data, US5182D_OPMODE_ALS);
+ if (ret < 0)
+ goto out_err;
+
+ ret = us5182d_get_als(data);
+ if (ret < 0)
+ goto out_err;
+ mutex_unlock(&data->lock);
+ *val = ret;
+ return IIO_VAL_INT;
+ case IIO_PROXIMITY:
+ mutex_lock(&data->lock);
+ ret = us5182d_set_opmode(data, US5182D_OPMODE_PX);
+ if (ret < 0)
+ goto out_err;
+
+ ret = i2c_smbus_read_word_data(data->client,
+ US5182D_REG_PDL);
+ if (ret < 0)
+ goto out_err;
+ mutex_unlock(&data->lock);
+ *val = ret;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+
+ case IIO_CHAN_INFO_SCALE:
+ ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG1);
+ if (ret < 0)
+ return ret;
+
+ *val = 0;
+ *val2 = us5182d_scales[ret & US5182D_AGAIN_MASK];
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+out_err:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+/**
+ * us5182d_update_dark_th - update Darh_Th registers
+ * @data us5182d_data structure
+ * @index index in us5182d_dark_ths array to use for the updated value
+ *
+ * Function needs to be called with a lock held because it needs two i2c write
+ * byte operations as these registers (0x27 0x28) don't work in word mode
+ * accessing.
+ */
+static int us5182d_update_dark_th(struct us5182d_data *data, int index)
+{
+ __be16 dark_th = cpu_to_be16(data->us5182d_dark_ths[index]);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_UDARK_TH,
+ ((u8 *)&dark_th)[0]);
+ if (ret < 0)
+ return ret;
+
+ return i2c_smbus_write_byte_data(data->client, US5182D_REG_UDARK_TH + 1,
+ ((u8 *)&dark_th)[1]);
+}
+
+/**
+ * us5182d_apply_scale - update the ALS scale
+ * @data us5182d_data structure
+ * @index index in us5182d_scales array to use for the updated value
+ *
+ * Function needs to be called with a lock held as we're having more than one
+ * i2c operation.
+ */
+static int us5182d_apply_scale(struct us5182d_data *data, int index)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG1);
+ if (ret < 0)
+ return ret;
+
+ ret = ret & (~US5182D_AGAIN_MASK);
+ ret |= index;
+
+ ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG1, ret);
+ if (ret < 0)
+ return ret;
+
+ return us5182d_update_dark_th(data, index);
+}
+
+static int us5182d_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long mask)
+{
+ struct us5182d_data *data = iio_priv(indio_dev);
+ int ret, i;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ if (val != 0)
+ return -EINVAL;
+ for (i = 0; i < ARRAY_SIZE(us5182d_scales); i++)
+ if (val2 == us5182d_scales[i]) {
+ mutex_lock(&data->lock);
+ ret = us5182d_apply_scale(data, i);
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_info us5182d_info = {
+ .driver_module = THIS_MODULE,
+ .read_raw = us5182d_read_raw,
+ .write_raw = us5182d_write_raw,
+ .attrs = &us5182d_attr_group,
+};
+
+static int us5182d_reset(struct iio_dev *indio_dev)
+{
+ struct us5182d_data *data = iio_priv(indio_dev);
+
+ return i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG3,
+ US5182D_RESET_CHIP);
+}
+
+static int us5182d_init(struct iio_dev *indio_dev)
+{
+ struct us5182d_data *data = iio_priv(indio_dev);
+ int i, ret;
+
+ ret = us5182d_reset(indio_dev);
+ if (ret < 0)
+ return ret;
+
+ data->opmode = 0;
+ for (i = 0; i < ARRAY_SIZE(us5182d_regvals); i++) {
+ ret = i2c_smbus_write_byte_data(data->client,
+ us5182d_regvals[i].reg,
+ us5182d_regvals[i].val);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void us5182d_get_platform_data(struct iio_dev *indio_dev)
+{
+ struct us5182d_data *data = iio_priv(indio_dev);
+
+ if (device_property_read_u32(&data->client->dev, "upisemi,glass-coef",
+ &data->ga))
+ data->ga = US5182D_GA_RESOLUTION;
+ if (device_property_read_u16_array(&data->client->dev,
+ "upisemi,dark-ths",
+ data->us5182d_dark_ths,
+ ARRAY_SIZE(us5182d_dark_ths_vals)))
+ data->us5182d_dark_ths = us5182d_dark_ths_vals;
+ if (device_property_read_u8(&data->client->dev,
+ "upisemi,upper-dark-gain",
+ &data->upper_dark_gain))
+ data->upper_dark_gain = US5182D_REG_AUTO_HDARK_GAIN_DEFAULT;
+ if (device_property_read_u8(&data->client->dev,
+ "upisemi,lower-dark-gain",
+ &data->lower_dark_gain))
+ data->lower_dark_gain = US5182D_REG_AUTO_LDARK_GAIN_DEFAULT;
+}
+
+static int us5182d_dark_gain_config(struct iio_dev *indio_dev)
+{
+ struct us5182d_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = us5182d_update_dark_th(data, US5182D_CFG1_AGAIN_DEFAULT);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_write_byte_data(data->client,
+ US5182D_REG_AUTO_LDARK_GAIN,
+ data->lower_dark_gain);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_write_byte_data(data->client,
+ US5182D_REG_AUTO_HDARK_GAIN,
+ data->upper_dark_gain);
+ if (ret < 0)
+ return ret;
+
+ return i2c_smbus_write_byte_data(data->client, US5182D_REG_DARK_AUTO_EN,
+ US5182D_REG_DARK_AUTO_EN_DEFAULT);
+}
+
+static int us5182d_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct us5182d_data *data;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+
+ mutex_init(&data->lock);
+
+ indio_dev->dev.parent = &client->dev;
+ indio_dev->info = &us5182d_info;
+ indio_dev->name = US5182D_DRV_NAME;
+ indio_dev->channels = us5182d_channels;
+ indio_dev->num_channels = ARRAY_SIZE(us5182d_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CHIPID);
+ if (ret != US5182D_CHIPID) {
+ dev_err(&data->client->dev,
+ "Failed to detect US5182 light chip\n");
+ return (ret < 0) ? ret : -ENODEV;
+ }
+
+ us5182d_get_platform_data(indio_dev);
+ ret = us5182d_init(indio_dev);
+ if (ret < 0)
+ return ret;
+
+ ret = us5182d_dark_gain_config(indio_dev);
+ if (ret < 0)
+ return ret;
+
+ return iio_device_register(indio_dev);
+}
+
+static int us5182d_remove(struct i2c_client *client)
+{
+ iio_device_unregister(i2c_get_clientdata(client));
+ return i2c_smbus_write_byte_data(client, US5182D_REG_CFG0,
+ US5182D_CFG0_SHUTDOWN_EN);
+}
+
+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},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, us5182d_id);
+
+static struct i2c_driver us5182d_driver = {
+ .driver = {
+ .name = US5182D_DRV_NAME,
+ .acpi_match_table = ACPI_PTR(us5182d_acpi_match),
+ },
+ .probe = us5182d_probe,
+ .remove = us5182d_remove,
+ .id_table = us5182d_id,
+
+};
+module_i2c_driver(us5182d_driver);
+
+MODULE_AUTHOR("Adriana Reus <adriana.reus@intel.com>");
+MODULE_DESCRIPTION("Driver for us5182d Proximity and Light Sensor");
+MODULE_LICENSE("GPL v2");
diff --git a/kernel/drivers/iio/light/vcnl4000.c b/kernel/drivers/iio/light/vcnl4000.c
index d948c4778..c9d85bbc9 100644
--- a/kernel/drivers/iio/light/vcnl4000.c
+++ b/kernel/drivers/iio/light/vcnl4000.c
@@ -185,7 +185,6 @@ static int vcnl4000_probe(struct i2c_client *client,
static struct i2c_driver vcnl4000_driver = {
.driver = {
.name = VCNL4000_DRV_NAME,
- .owner = THIS_MODULE,
},
.probe = vcnl4000_probe,
.id_table = vcnl4000_id,