v4.19.13 snapshot.
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
new file mode 100644
index 0000000..d66ea75
--- /dev/null
+++ b/drivers/iio/light/Kconfig
@@ -0,0 +1,493 @@
+
+#
+# Light sensors
+#
+# When adding new entries keep the list in alphabetical order
+
+menu "Light sensors"
+
+config ACPI_ALS
+	tristate "ACPI Ambient Light Sensor"
+	depends on ACPI
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	select IIO_KFIFO_BUF
+	help
+	 Say Y here if you want to build a driver for the ACPI0008
+	 Ambient Light Sensor.
+
+	 To compile this driver as a module, choose M here: the module will
+	 be called acpi-als.
+
+config ADJD_S311
+	tristate "ADJD-S311-CR999 digital color sensor"
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	depends on I2C
+	help
+	 If you say yes here you get support for the Avago ADJD-S311-CR999
+	 digital color light sensor.
+
+	 This driver can also be built as a module.  If so, the module
+	 will be called adjd_s311.
+
+config AL3320A
+	tristate "AL3320A ambient light sensor"
+	depends on I2C
+	help
+	 Say Y here if you want to build a driver for the Dyna Image AL3320A
+	 ambient light sensor.
+
+	 To compile this driver as a module, choose M here: the
+	 module will be called al3320a.
+
+config APDS9300
+	tristate "APDS9300 ambient light sensor"
+	depends on I2C
+	help
+	 Say Y here if you want to build a driver for the Avago APDS9300
+	 ambient light sensor.
+
+	 To compile this driver as a module, choose M here: the
+	 module will be called apds9300.
+
+config 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 BH1780
+	tristate "ROHM BH1780 ambient light sensor"
+	depends on I2C
+	help
+	 Say Y here to build support for the ROHM BH1780GLI ambient
+	 light sensor.
+
+	 To compile this driver as a module, choose M here: the module will
+	 be called bh1780.
+
+config CM32181
+	depends on I2C
+	tristate "CM32181 driver"
+	help
+	 Say Y here if you use cm32181.
+	 This option enables ambient light sensor using
+	 Capella cm32181 device driver.
+
+	 To compile this driver as a module, choose M here:
+	 the module will be called cm32181.
+
+config CM3232
+	depends on I2C
+	tristate "CM3232 ambient light sensor"
+	help
+	 Say Y here if you use cm3232.
+	 This option enables ambient light sensor using
+	 Capella Microsystems cm3232 device driver.
+
+	 To compile this driver as a module, choose M here:
+	 the module will be called cm3232.
+
+config CM3323
+	depends on I2C
+	tristate "Capella CM3323 color light sensor"
+	help
+	 Say Y here if you want to build a driver for Capella CM3323
+	 color sensor.
+
+	 To compile this driver as a module, choose M here: the module will
+	 be called cm3323.
+
+config CM3605
+	tristate "Capella CM3605 ambient light and proximity sensor"
+	depends on OF
+	help
+	 Say Y here if you want to build a driver for Capella CM3605
+	 ambient light and short range proximity sensor.
+
+	 To compile this driver as a module, choose M here: the module will
+	 be called cm3605.
+
+config CM36651
+	depends on I2C
+	tristate "CM36651 driver"
+	help
+	 Say Y here if you use cm36651.
+	 This option enables proximity & RGB sensor using
+	 Capella cm36651 device driver.
+
+	 To compile this driver as a module, choose M here:
+	 the module will be called cm36651.
+
+config IIO_CROS_EC_LIGHT_PROX
+	tristate "ChromeOS EC Light and Proximity Sensors"
+	depends on IIO_CROS_EC_SENSORS_CORE
+	help
+	  Say Y here if you use the light and proximity sensors
+	  presented by the ChromeOS EC Sensor hub.
+
+	  To compile this driver as a module, choose M here:
+	  the module will be called cros_ec_light_prox.
+
+config GP2AP020A00F
+	tristate "Sharp GP2AP020A00F Proximity/ALS sensor"
+	depends on I2C
+	select REGMAP_I2C
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	select IRQ_WORK
+	help
+	  Say Y here if you have a Sharp GP2AP020A00F proximity/ALS combo-chip
+	  hooked to an I2C bus.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gp2ap020a00f.
+
+config SENSORS_ISL29018
+	tristate "Intersil 29018 light and proximity sensor"
+	depends on I2C
+	select REGMAP_I2C
+	default n
+	help
+	 If you say yes here you get support for ambient light sensing and
+	 proximity infrared sensing from Intersil ISL29018.
+	 This driver will provide the measurements of ambient light intensity
+	 in lux, proximity infrared sensing and normal infrared sensing.
+	 Data from sensor is accessible via sysfs.
+
+config SENSORS_ISL29028
+	tristate "Intersil ISL29028 Concurrent Light and Proximity Sensor"
+	depends on I2C
+	select REGMAP_I2C
+	help
+	 Provides driver for the Intersil's ISL29028 device.
+	 This driver supports the sysfs interface to get the ALS, IR intensity,
+	 Proximity value via iio. The ISL29028 provides the concurrent sensing
+	 of ambient light and proximity.
+
+config ISL29125
+	tristate "Intersil ISL29125 digital color light sensor"
+	depends on I2C
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	help
+	  Say Y here if you want to build a driver for the Intersil ISL29125
+	  RGB light sensor for I2C.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called isl29125.
+
+config HID_SENSOR_ALS
+	depends on HID_SENSOR_HUB
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	select HID_SENSOR_IIO_COMMON
+	select HID_SENSOR_IIO_TRIGGER
+	tristate "HID ALS"
+	help
+	  Say yes here to build support for the HID SENSOR
+	  Ambient light sensor.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called hid-sensor-als.
+
+config HID_SENSOR_PROX
+	depends on HID_SENSOR_HUB
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	select HID_SENSOR_IIO_COMMON
+	select HID_SENSOR_IIO_TRIGGER
+	tristate "HID PROX"
+	help
+	  Say yes here to build support for the HID SENSOR
+	  Proximity sensor.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called hid-sensor-prox.
+
+config JSA1212
+	tristate "JSA1212 ALS and proximity sensor driver"
+	depends on I2C
+	select REGMAP_I2C
+	help
+	 Say Y here if you want to build a IIO driver for JSA1212
+	 proximity & ALS sensor device.
+
+	 To compile this driver as a module, choose M here:
+	 the module will be called jsa1212.
+
+config 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
+	help
+	  If you say yes here you get support for the ambient light sensor
+	  interface on National Semiconductor / TI LM3533 Lighting Power
+	  chips.
+
+	  The sensor interface can be used to control the LEDs and backlights
+	  of the chip through defining five light zones and three sets of
+	  corresponding output-current values.
+
+	  The driver provides raw and mean adc readings along with the current
+	  light zone through sysfs. A threshold event can be generated on zone
+	  changes. The ALS-control output values can be set per zone for the
+	  three current output channels.
+
+config LTR501
+	tristate "LTR-501ALS-01 light sensor"
+	depends on I2C
+	select REGMAP_I2C
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	help
+	 If you say yes here you get support for the Lite-On LTR-501ALS-01
+	 ambient light and proximity sensor. This driver also supports LTR-559
+	 ALS/PS or LTR-301 ALS sensors.
+
+	 This driver can also be built as a module.  If so, the module
+         will be called ltr501.
+
+config LV0104CS
+	tristate "LV0104CS Ambient Light Sensor"
+	depends on I2C
+	help
+	 Say Y here if you want to build support for the On Semiconductor
+	 LV0104CS ambient light sensor.
+
+	 To compile this driver as a module, choose M here:
+	 the module will be called lv0104cs.
+
+config MAX44000
+	tristate "MAX44000 Ambient and Infrared Proximity Sensor"
+	depends on I2C
+	select REGMAP_I2C
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	help
+	 Say Y here if you want to build support for Maxim Integrated's
+	 MAX44000 ambient and infrared proximity sensor device.
+
+	 To compile this driver as a module, choose M here:
+	 the module will be called max44000.
+
+config 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 SI1133
+	tristate "SI1133 UV Index Sensor and Ambient Light Sensor"
+	depends on I2C
+	select REGMAP_I2C
+	  help
+	  Say Y here if you want to build a driver for the Silicon Labs SI1133
+	  UV Index Sensor and Ambient Light Sensor chip.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called si1133.
+
+config SI1145
+	tristate "SI1132 and SI1141/2/3/5/6/7 combined ALS, UV index and proximity sensor"
+	depends on I2C
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	help
+	  Say Y here if you want to build a driver for the Silicon Labs SI1132 or
+	  SI1141/2/3/5/6/7 combined ambient light, UV index and proximity sensor
+	  chips.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called si1145.
+
+config STK3310
+	tristate "STK3310 ALS and proximity sensor"
+	depends on I2C
+	select REGMAP_I2C
+	help
+	 Say yes here to get support for the Sensortek STK3310 ambient light
+	 and proximity sensor. The STK3311 model is also supported by this
+	 driver.
+
+	 Choosing M will build the driver as a module. If so, the module
+	 will be called stk3310.
+
+config ST_UVIS25
+	tristate "STMicroelectronics UVIS25 sensor driver"
+	depends on (I2C || SPI)
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	select ST_UVIS25_I2C if (I2C)
+	select ST_UVIS25_SPI if (SPI_MASTER)
+	help
+	  Say yes here to build support for STMicroelectronics UVIS25
+	  uv sensor
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called st_uvis25.
+
+config ST_UVIS25_I2C
+	tristate
+	depends on ST_UVIS25
+	select REGMAP_I2C
+
+config ST_UVIS25_SPI
+	tristate
+	depends on ST_UVIS25
+	select REGMAP_SPI
+
+config TCS3414
+	tristate "TAOS TCS3414 digital color sensor"
+	depends on I2C
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	help
+	 If you say yes here you get support for the TAOS TCS3414
+	 family of digital color sensors.
+
+	 This driver can also be built as a module.  If so, the module
+	 will be called tcs3414.
+
+config TCS3472
+	tristate "TAOS TCS3472 color light-to-digital converter"
+	depends on I2C
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	help
+	 If you say yes here you get support for the TAOS TCS3472
+	 family of color light-to-digital converters with IR filter.
+
+	 This driver can also be built as a module.  If so, the module
+	 will be called tcs3472.
+
+config SENSORS_TSL2563
+	tristate "TAOS TSL2560, TSL2561, TSL2562 and TSL2563 ambient light sensors"
+	depends on I2C
+	help
+	 If you say yes here you get support for the Taos TSL2560,
+	 TSL2561, TSL2562 and TSL2563 ambient light sensors.
+
+	 This driver can also be built as a module.  If so, the module
+	 will be called tsl2563.
+
+config TSL2583
+	tristate "TAOS TSL2580, TSL2581 and TSL2583 light-to-digital converters"
+	depends on I2C
+	help
+	 Provides support for the TAOS tsl2580, tsl2581 and tsl2583 devices.
+	 Access ALS data via iio, sysfs.
+
+config TSL2772
+	tristate "TAOS TSL/TMD2x71 and TSL/TMD2x72 Family of light and proximity sensors"
+	depends on I2C
+	help
+	 Support for: tsl2571, tsl2671, tmd2671, tsl2771, tmd2771, tsl2572, tsl2672,
+	 tmd2672, tsl2772, tmd2772 devices.
+	 Provides iio_events and direct access via sysfs.
+
+config TSL4531
+	tristate "TAOS TSL4531 ambient light sensors"
+	depends on I2C
+	help
+	 Say Y here if you want to build a driver for the TAOS TSL4531 family
+	 of ambient light sensors with direct lux output.
+
+	 To compile this driver as a module, choose M here: the
+	 module will be called tsl4531.
+
+config US5182D
+	tristate "UPISEMI light and proximity sensor"
+	depends on I2C
+	help
+	 If you say yes here you get support for the UPISEMI US5182D
+	 ambient light and proximity sensor.
+
+	 This driver can also be built as a module.  If so, the module
+	 will be called us5182d.
+
+config VCNL4000
+	tristate "VCNL4000/4010/4020/4200 combined ALS and proximity sensor"
+	depends on I2C
+	help
+	 Say Y here if you want to build a driver for the Vishay VCNL4000,
+	 VCNL4010, VCNL4020, VCNL4200 combined ambient light and proximity
+	 sensor.
+
+	 To compile this driver as a module, choose M here: the
+	 module will be called vcnl4000.
+
+config VEML6070
+	tristate "VEML6070 UV A light sensor"
+	depends on I2C
+	help
+	 Say Y here if you want to build a driver for the Vishay VEML6070 UV A
+	 light sensor.
+
+	 To compile this driver as a module, choose M here: the
+	 module will be called veml6070.
+
+config VL6180
+	tristate "VL6180 ALS, range and proximity sensor"
+	depends on I2C
+	help
+	 Say Y here if you want to build a driver for the STMicroelectronics
+	 VL6180 combined ambient light, range and proximity sensor.
+
+	 To compile this driver as a module, choose M here: the
+	 module will be called vl6180.
+
+config ZOPT2201
+	tristate "ZOPT2201 ALS and UV B sensor"
+	depends on I2C
+	help
+	 Say Y here if you want to build a driver for the IDT
+	 ZOPT2201 ambient light and UV B sensor.
+
+	 To compile this driver as a module, choose M here: the
+	 module will be called zopt2201.
+
+endmenu
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
new file mode 100644
index 0000000..86337b1
--- /dev/null
+++ b/drivers/iio/light/Makefile
@@ -0,0 +1,50 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for IIO Light sensors
+#
+
+# When adding new entries keep the list in alphabetical order
+obj-$(CONFIG_ACPI_ALS)		+= acpi-als.o
+obj-$(CONFIG_ADJD_S311)		+= adjd_s311.o
+obj-$(CONFIG_AL3320A)		+= al3320a.o
+obj-$(CONFIG_APDS9300)		+= apds9300.o
+obj-$(CONFIG_APDS9960)		+= apds9960.o
+obj-$(CONFIG_BH1750)		+= bh1750.o
+obj-$(CONFIG_BH1780)		+= bh1780.o
+obj-$(CONFIG_CM32181)		+= cm32181.o
+obj-$(CONFIG_CM3232)		+= cm3232.o
+obj-$(CONFIG_CM3323)		+= cm3323.o
+obj-$(CONFIG_CM3605)		+= cm3605.o
+obj-$(CONFIG_CM36651)		+= cm36651.o
+obj-$(CONFIG_IIO_CROS_EC_LIGHT_PROX) += cros_ec_light_prox.o
+obj-$(CONFIG_GP2AP020A00F)	+= gp2ap020a00f.o
+obj-$(CONFIG_HID_SENSOR_ALS)	+= hid-sensor-als.o
+obj-$(CONFIG_HID_SENSOR_PROX)	+= hid-sensor-prox.o
+obj-$(CONFIG_SENSORS_ISL29018)	+= isl29018.o
+obj-$(CONFIG_SENSORS_ISL29028)	+= isl29028.o
+obj-$(CONFIG_ISL29125)		+= isl29125.o
+obj-$(CONFIG_JSA1212)		+= jsa1212.o
+obj-$(CONFIG_SENSORS_LM3533)	+= lm3533-als.o
+obj-$(CONFIG_LTR501)		+= ltr501.o
+obj-$(CONFIG_LV0104CS)		+= lv0104cs.o
+obj-$(CONFIG_MAX44000)		+= max44000.o
+obj-$(CONFIG_OPT3001)		+= opt3001.o
+obj-$(CONFIG_PA12203001)	+= pa12203001.o
+obj-$(CONFIG_RPR0521)		+= rpr0521.o
+obj-$(CONFIG_SENSORS_TSL2563)	+= tsl2563.o
+obj-$(CONFIG_SI1133)		+= si1133.o
+obj-$(CONFIG_SI1145)		+= si1145.o
+obj-$(CONFIG_STK3310)          += stk3310.o
+obj-$(CONFIG_ST_UVIS25)		+= st_uvis25_core.o
+obj-$(CONFIG_ST_UVIS25_I2C)	+= st_uvis25_i2c.o
+obj-$(CONFIG_ST_UVIS25_SPI)	+= st_uvis25_spi.o
+obj-$(CONFIG_TCS3414)		+= tcs3414.o
+obj-$(CONFIG_TCS3472)		+= tcs3472.o
+obj-$(CONFIG_TSL2583)		+= tsl2583.o
+obj-$(CONFIG_TSL2772)		+= tsl2772.o
+obj-$(CONFIG_TSL4531)		+= tsl4531.o
+obj-$(CONFIG_US5182D)		+= us5182d.o
+obj-$(CONFIG_VCNL4000)		+= vcnl4000.o
+obj-$(CONFIG_VEML6070)		+= veml6070.o
+obj-$(CONFIG_VL6180)		+= vl6180.o
+obj-$(CONFIG_ZOPT2201)		+= zopt2201.o
diff --git a/drivers/iio/light/acpi-als.c b/drivers/iio/light/acpi-als.c
new file mode 100644
index 0000000..c35e2f8
--- /dev/null
+++ b/drivers/iio/light/acpi-als.c
@@ -0,0 +1,232 @@
+/*
+ * 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(indio_dev);
+	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 = {
+	.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/drivers/iio/light/adjd_s311.c b/drivers/iio/light/adjd_s311.c
new file mode 100644
index 0000000..e45bb6a
--- /dev/null
+++ b/drivers/iio/light/adjd_s311.c
@@ -0,0 +1,320 @@
+/*
+ * adjd_s311.c - Support for ADJD-S311-CR999 digital color sensor
+ *
+ * Copyright (C) 2012 Peter Meerwald <pmeerw@pmeerw.net>
+ *
+ * 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.
+ *
+ * driver for ADJD-S311-CR999 digital color sensor (10-bit channels for
+ * red, green, blue, clear); 7-bit I2C slave address 0x74
+ *
+ * limitations: no calibration, no offset mode, no sleep mode
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/bitmap.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#define ADJD_S311_DRV_NAME "adjd_s311"
+
+#define ADJD_S311_CTRL		0x00
+#define ADJD_S311_CONFIG	0x01
+#define ADJD_S311_CAP_RED	0x06
+#define ADJD_S311_CAP_GREEN	0x07
+#define ADJD_S311_CAP_BLUE	0x08
+#define ADJD_S311_CAP_CLEAR	0x09
+#define ADJD_S311_INT_RED	0x0a
+#define ADJD_S311_INT_GREEN	0x0c
+#define ADJD_S311_INT_BLUE	0x0e
+#define ADJD_S311_INT_CLEAR	0x10
+#define ADJD_S311_DATA_RED	0x40
+#define ADJD_S311_DATA_GREEN	0x42
+#define ADJD_S311_DATA_BLUE	0x44
+#define ADJD_S311_DATA_CLEAR	0x46
+#define ADJD_S311_OFFSET_RED	0x48
+#define ADJD_S311_OFFSET_GREEN	0x49
+#define ADJD_S311_OFFSET_BLUE	0x4a
+#define ADJD_S311_OFFSET_CLEAR	0x4b
+
+#define ADJD_S311_CTRL_GOFS	0x02
+#define ADJD_S311_CTRL_GSSR	0x01
+#define ADJD_S311_CAP_MASK	0x0f
+#define ADJD_S311_INT_MASK	0x0fff
+#define ADJD_S311_DATA_MASK	0x03ff
+
+struct adjd_s311_data {
+	struct i2c_client *client;
+	u16 *buffer;
+};
+
+enum adjd_s311_channel_idx {
+	IDX_RED, IDX_GREEN, IDX_BLUE, IDX_CLEAR
+};
+
+#define ADJD_S311_DATA_REG(chan) (ADJD_S311_DATA_RED + (chan) * 2)
+#define ADJD_S311_INT_REG(chan) (ADJD_S311_INT_RED + (chan) * 2)
+#define ADJD_S311_CAP_REG(chan) (ADJD_S311_CAP_RED + (chan))
+
+static int adjd_s311_req_data(struct iio_dev *indio_dev)
+{
+	struct adjd_s311_data *data = iio_priv(indio_dev);
+	int tries = 10;
+
+	int ret = i2c_smbus_write_byte_data(data->client, ADJD_S311_CTRL,
+		ADJD_S311_CTRL_GSSR);
+	if (ret < 0)
+		return ret;
+
+	while (tries--) {
+		ret = i2c_smbus_read_byte_data(data->client, ADJD_S311_CTRL);
+		if (ret < 0)
+			return ret;
+		if (!(ret & ADJD_S311_CTRL_GSSR))
+			break;
+		msleep(20);
+	}
+
+	if (tries < 0) {
+		dev_err(&data->client->dev,
+			"adjd_s311_req_data() failed, data not ready\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int adjd_s311_read_data(struct iio_dev *indio_dev, u8 reg, int *val)
+{
+	struct adjd_s311_data *data = iio_priv(indio_dev);
+
+	int ret = adjd_s311_req_data(indio_dev);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_read_word_data(data->client, reg);
+	if (ret < 0)
+		return ret;
+
+	*val = ret & ADJD_S311_DATA_MASK;
+
+	return 0;
+}
+
+static irqreturn_t adjd_s311_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct adjd_s311_data *data = iio_priv(indio_dev);
+	s64 time_ns = iio_get_time_ns(indio_dev);
+	int i, j = 0;
+
+	int ret = adjd_s311_req_data(indio_dev);
+	if (ret < 0)
+		goto done;
+
+	for_each_set_bit(i, indio_dev->active_scan_mask,
+		indio_dev->masklength) {
+		ret = i2c_smbus_read_word_data(data->client,
+			ADJD_S311_DATA_REG(i));
+		if (ret < 0)
+			goto done;
+
+		data->buffer[j++] = ret & ADJD_S311_DATA_MASK;
+	}
+
+	iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, time_ns);
+
+done:
+	iio_trigger_notify_done(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
+#define ADJD_S311_CHANNEL(_color, _scan_idx) { \
+	.type = IIO_INTENSITY, \
+	.modified = 1, \
+	.address = (IDX_##_color), \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+		BIT(IIO_CHAN_INFO_HARDWAREGAIN) | \
+		BIT(IIO_CHAN_INFO_INT_TIME), \
+	.channel2 = (IIO_MOD_LIGHT_##_color), \
+	.scan_index = (_scan_idx), \
+	.scan_type = { \
+		.sign = 'u', \
+		.realbits = 10, \
+		.storagebits = 16, \
+		.endianness = IIO_CPU, \
+	}, \
+}
+
+static const struct iio_chan_spec adjd_s311_channels[] = {
+	ADJD_S311_CHANNEL(RED, 0),
+	ADJD_S311_CHANNEL(GREEN, 1),
+	ADJD_S311_CHANNEL(BLUE, 2),
+	ADJD_S311_CHANNEL(CLEAR, 3),
+	IIO_CHAN_SOFT_TIMESTAMP(4),
+};
+
+static int adjd_s311_read_raw(struct iio_dev *indio_dev,
+			   struct iio_chan_spec const *chan,
+			   int *val, int *val2, long mask)
+{
+	struct adjd_s311_data *data = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = adjd_s311_read_data(indio_dev,
+			ADJD_S311_DATA_REG(chan->address), val);
+		if (ret < 0)
+			return ret;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		ret = i2c_smbus_read_byte_data(data->client,
+			ADJD_S311_CAP_REG(chan->address));
+		if (ret < 0)
+			return ret;
+		*val = ret & ADJD_S311_CAP_MASK;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_INT_TIME:
+		ret = i2c_smbus_read_word_data(data->client,
+			ADJD_S311_INT_REG(chan->address));
+		if (ret < 0)
+			return ret;
+		*val = 0;
+		/*
+		 * not documented, based on measurement:
+		 * 4095 LSBs correspond to roughly 4 ms
+		 */
+		*val2 = ret & ADJD_S311_INT_MASK;
+		return IIO_VAL_INT_PLUS_MICRO;
+	}
+	return -EINVAL;
+}
+
+static int adjd_s311_write_raw(struct iio_dev *indio_dev,
+			       struct iio_chan_spec const *chan,
+			       int val, int val2, long mask)
+{
+	struct adjd_s311_data *data = iio_priv(indio_dev);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		if (val < 0 || val > ADJD_S311_CAP_MASK)
+			return -EINVAL;
+
+		return i2c_smbus_write_byte_data(data->client,
+			ADJD_S311_CAP_REG(chan->address), val);
+	case IIO_CHAN_INFO_INT_TIME:
+		if (val != 0 || val2 < 0 || val2 > ADJD_S311_INT_MASK)
+			return -EINVAL;
+
+		return i2c_smbus_write_word_data(data->client,
+			ADJD_S311_INT_REG(chan->address), val2);
+	}
+	return -EINVAL;
+}
+
+static int adjd_s311_update_scan_mode(struct iio_dev *indio_dev,
+	const unsigned long *scan_mask)
+{
+	struct adjd_s311_data *data = iio_priv(indio_dev);
+
+	kfree(data->buffer);
+	data->buffer = kmalloc(indio_dev->scan_bytes, GFP_KERNEL);
+	if (data->buffer == NULL)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static const struct iio_info adjd_s311_info = {
+	.read_raw = adjd_s311_read_raw,
+	.write_raw = adjd_s311_write_raw,
+	.update_scan_mode = adjd_s311_update_scan_mode,
+};
+
+static int adjd_s311_probe(struct i2c_client *client,
+			   const struct i2c_device_id *id)
+{
+	struct adjd_s311_data *data;
+	struct iio_dev *indio_dev;
+	int err;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (indio_dev == NULL)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->client = client;
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->info = &adjd_s311_info;
+	indio_dev->name = ADJD_S311_DRV_NAME;
+	indio_dev->channels = adjd_s311_channels;
+	indio_dev->num_channels = ARRAY_SIZE(adjd_s311_channels);
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	err = iio_triggered_buffer_setup(indio_dev, NULL,
+		adjd_s311_trigger_handler, NULL);
+	if (err < 0)
+		return err;
+
+	err = iio_device_register(indio_dev);
+	if (err)
+		goto exit_unreg_buffer;
+
+	dev_info(&client->dev, "ADJD-S311 color sensor registered\n");
+
+	return 0;
+
+exit_unreg_buffer:
+	iio_triggered_buffer_cleanup(indio_dev);
+	return err;
+}
+
+static int adjd_s311_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+	struct adjd_s311_data *data = iio_priv(indio_dev);
+
+	iio_device_unregister(indio_dev);
+	iio_triggered_buffer_cleanup(indio_dev);
+	kfree(data->buffer);
+
+	return 0;
+}
+
+static const struct i2c_device_id adjd_s311_id[] = {
+	{ "adjd_s311", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, adjd_s311_id);
+
+static struct i2c_driver adjd_s311_driver = {
+	.driver = {
+		.name	= ADJD_S311_DRV_NAME,
+	},
+	.probe		= adjd_s311_probe,
+	.remove		= adjd_s311_remove,
+	.id_table	= adjd_s311_id,
+};
+module_i2c_driver(adjd_s311_driver);
+
+MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
+MODULE_DESCRIPTION("ADJD-S311 color sensor");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/al3320a.c b/drivers/iio/light/al3320a.c
new file mode 100644
index 0000000..66623fa
--- /dev/null
+++ b/drivers/iio/light/al3320a.c
@@ -0,0 +1,231 @@
+/*
+ * AL3320A - Dyna Image Ambient Light Sensor
+ *
+ * Copyright (c) 2014, 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 AL3320A (7-bit I2C slave address 0x1C).
+ *
+ * TODO: interrupt support, thresholds
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define AL3320A_DRV_NAME "al3320a"
+
+#define AL3320A_REG_CONFIG		0x00
+#define AL3320A_REG_STATUS		0x01
+#define AL3320A_REG_INT			0x02
+#define AL3320A_REG_WAIT		0x06
+#define AL3320A_REG_CONFIG_RANGE	0x07
+#define AL3320A_REG_PERSIST		0x08
+#define AL3320A_REG_MEAN_TIME		0x09
+#define AL3320A_REG_ADUMMY		0x0A
+#define AL3320A_REG_DATA_LOW		0x22
+
+#define AL3320A_REG_LOW_THRESH_LOW	0x30
+#define AL3320A_REG_LOW_THRESH_HIGH	0x31
+#define AL3320A_REG_HIGH_THRESH_LOW	0x32
+#define AL3320A_REG_HIGH_THRESH_HIGH	0x33
+
+#define AL3320A_CONFIG_DISABLE		0x00
+#define AL3320A_CONFIG_ENABLE		0x01
+
+#define AL3320A_GAIN_SHIFT		1
+#define AL3320A_GAIN_MASK		(BIT(2) | BIT(1))
+
+/* chip params default values */
+#define AL3320A_DEFAULT_MEAN_TIME	4
+#define AL3320A_DEFAULT_WAIT_TIME	0 /* no waiting */
+
+#define AL3320A_SCALE_AVAILABLE "0.512 0.128 0.032 0.01"
+
+enum al3320a_range {
+	AL3320A_RANGE_1, /* 33.28 Klx */
+	AL3320A_RANGE_2, /* 8.32 Klx  */
+	AL3320A_RANGE_3, /* 2.08 Klx  */
+	AL3320A_RANGE_4  /* 0.65 Klx  */
+};
+
+static const int al3320a_scales[][2] = {
+	{0, 512000}, {0, 128000}, {0, 32000}, {0, 10000}
+};
+
+struct al3320a_data {
+	struct i2c_client *client;
+};
+
+static const struct iio_chan_spec al3320a_channels[] = {
+	{
+		.type	= IIO_LIGHT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+	}
+};
+
+static IIO_CONST_ATTR(in_illuminance_scale_available, AL3320A_SCALE_AVAILABLE);
+
+static struct attribute *al3320a_attributes[] = {
+	&iio_const_attr_in_illuminance_scale_available.dev_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group al3320a_attribute_group = {
+	.attrs = al3320a_attributes,
+};
+
+static int al3320a_init(struct al3320a_data *data)
+{
+	int ret;
+
+	/* power on */
+	ret = i2c_smbus_write_byte_data(data->client, AL3320A_REG_CONFIG,
+					AL3320A_CONFIG_ENABLE);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_write_byte_data(data->client, AL3320A_REG_CONFIG_RANGE,
+					AL3320A_RANGE_3 << AL3320A_GAIN_SHIFT);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_write_byte_data(data->client, AL3320A_REG_MEAN_TIME,
+					AL3320A_DEFAULT_MEAN_TIME);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_write_byte_data(data->client, AL3320A_REG_WAIT,
+					AL3320A_DEFAULT_WAIT_TIME);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int al3320a_read_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan, int *val,
+			    int *val2, long mask)
+{
+	struct al3320a_data *data = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		/*
+		 * ALS ADC value is stored in two adjacent registers:
+		 * - low byte of output is stored at AL3320A_REG_DATA_LOW
+		 * - high byte of output is stored at AL3320A_REG_DATA_LOW + 1
+		 */
+		ret = i2c_smbus_read_word_data(data->client,
+					       AL3320A_REG_DATA_LOW);
+		if (ret < 0)
+			return ret;
+		*val = ret;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		ret = i2c_smbus_read_byte_data(data->client,
+					       AL3320A_REG_CONFIG_RANGE);
+		if (ret < 0)
+			return ret;
+
+		ret = (ret & AL3320A_GAIN_MASK) >> AL3320A_GAIN_SHIFT;
+		*val = al3320a_scales[ret][0];
+		*val2 = al3320a_scales[ret][1];
+
+		return IIO_VAL_INT_PLUS_MICRO;
+	}
+	return -EINVAL;
+}
+
+static int al3320a_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan, int val,
+			     int val2, long mask)
+{
+	struct al3320a_data *data = iio_priv(indio_dev);
+	int i;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		for (i = 0; i < ARRAY_SIZE(al3320a_scales); i++) {
+			if (val == al3320a_scales[i][0] &&
+			    val2 == al3320a_scales[i][1])
+				return i2c_smbus_write_byte_data(data->client,
+					AL3320A_REG_CONFIG_RANGE,
+					i << AL3320A_GAIN_SHIFT);
+		}
+		break;
+	}
+	return -EINVAL;
+}
+
+static const struct iio_info al3320a_info = {
+	.read_raw	= al3320a_read_raw,
+	.write_raw	= al3320a_write_raw,
+	.attrs		= &al3320a_attribute_group,
+};
+
+static int al3320a_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	struct al3320a_data *data;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->client = client;
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->info = &al3320a_info;
+	indio_dev->name = AL3320A_DRV_NAME;
+	indio_dev->channels = al3320a_channels;
+	indio_dev->num_channels = ARRAY_SIZE(al3320a_channels);
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	ret = al3320a_init(data);
+	if (ret < 0) {
+		dev_err(&client->dev, "al3320a chip init failed\n");
+		return ret;
+	}
+	return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static int al3320a_remove(struct i2c_client *client)
+{
+	return i2c_smbus_write_byte_data(client, AL3320A_REG_CONFIG,
+					 AL3320A_CONFIG_DISABLE);
+}
+
+static const struct i2c_device_id al3320a_id[] = {
+	{"al3320a", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, al3320a_id);
+
+static struct i2c_driver al3320a_driver = {
+	.driver = {
+		.name = AL3320A_DRV_NAME,
+	},
+	.probe		= al3320a_probe,
+	.remove		= al3320a_remove,
+	.id_table	= al3320a_id,
+};
+
+module_i2c_driver(al3320a_driver);
+
+MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>");
+MODULE_DESCRIPTION("AL3320A Ambient Light Sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/light/apds9300.c b/drivers/iio/light/apds9300.c
new file mode 100644
index 0000000..5c15736
--- /dev/null
+++ b/drivers/iio/light/apds9300.c
@@ -0,0 +1,528 @@
+/*
+ * apds9300.c - IIO driver for Avago APDS9300 ambient light sensor
+ *
+ * Copyright 2013 Oleksandr Kravchenko <o.v.kravchenko@globallogic.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/interrupt.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/events.h>
+
+#define APDS9300_DRV_NAME "apds9300"
+#define APDS9300_IRQ_NAME "apds9300_event"
+
+/* Command register bits */
+#define APDS9300_CMD	BIT(7) /* Select command register. Must write as 1 */
+#define APDS9300_WORD	BIT(5) /* I2C write/read: if 1 word, if 0 byte */
+#define APDS9300_CLEAR	BIT(6) /* Interrupt clear. Clears pending interrupt */
+
+/* Register set */
+#define APDS9300_CONTROL	0x00 /* Control of basic functions */
+#define APDS9300_THRESHLOWLOW	0x02 /* Low byte of low interrupt threshold */
+#define APDS9300_THRESHHIGHLOW	0x04 /* Low byte of high interrupt threshold */
+#define APDS9300_INTERRUPT	0x06 /* Interrupt control */
+#define APDS9300_DATA0LOW	0x0c /* Low byte of ADC channel 0 */
+#define APDS9300_DATA1LOW	0x0e /* Low byte of ADC channel 1 */
+
+/* Power on/off value for APDS9300_CONTROL register */
+#define APDS9300_POWER_ON	0x03
+#define APDS9300_POWER_OFF	0x00
+
+/* Interrupts */
+#define APDS9300_INTR_ENABLE	0x10
+/* Interrupt Persist Function: Any value outside of threshold range */
+#define APDS9300_THRESH_INTR	0x01
+
+#define APDS9300_THRESH_MAX	0xffff /* Max threshold value */
+
+struct apds9300_data {
+	struct i2c_client *client;
+	struct mutex mutex;
+	int power_state;
+	int thresh_low;
+	int thresh_hi;
+	int intr_en;
+};
+
+/* Lux calculation */
+
+/* Calculated values 1000 * (CH1/CH0)^1.4 for CH1/CH0 from 0 to 0.52 */
+static const u16 apds9300_lux_ratio[] = {
+	0, 2, 4, 7, 11, 15, 19, 24, 29, 34, 40, 45, 51, 57, 64, 70, 77, 84, 91,
+	98, 105, 112, 120, 128, 136, 144, 152, 160, 168, 177, 185, 194, 203,
+	212, 221, 230, 239, 249, 258, 268, 277, 287, 297, 307, 317, 327, 337,
+	347, 358, 368, 379, 390, 400,
+};
+
+static unsigned long apds9300_calculate_lux(u16 ch0, u16 ch1)
+{
+	unsigned long lux, tmp;
+
+	/* avoid division by zero */
+	if (ch0 == 0)
+		return 0;
+
+	tmp = DIV_ROUND_UP(ch1 * 100, ch0);
+	if (tmp <= 52) {
+		lux = 3150 * ch0 - (unsigned long)DIV_ROUND_UP_ULL(ch0
+				* apds9300_lux_ratio[tmp] * 5930ull, 1000);
+	} else if (tmp <= 65) {
+		lux = 2290 * ch0 - 2910 * ch1;
+	} else if (tmp <= 80) {
+		lux = 1570 * ch0 - 1800 * ch1;
+	} else if (tmp <= 130) {
+		lux = 338 * ch0 - 260 * ch1;
+	} else {
+		lux = 0;
+	}
+
+	return lux / 100000;
+}
+
+static int apds9300_get_adc_val(struct apds9300_data *data, int adc_number)
+{
+	int ret;
+	u8 flags = APDS9300_CMD | APDS9300_WORD;
+
+	if (!data->power_state)
+		return -EBUSY;
+
+	/* Select ADC0 or ADC1 data register */
+	flags |= adc_number ? APDS9300_DATA1LOW : APDS9300_DATA0LOW;
+
+	ret = i2c_smbus_read_word_data(data->client, flags);
+	if (ret < 0)
+		dev_err(&data->client->dev,
+			"failed to read ADC%d value\n", adc_number);
+
+	return ret;
+}
+
+static int apds9300_set_thresh_low(struct apds9300_data *data, int value)
+{
+	int ret;
+
+	if (!data->power_state)
+		return -EBUSY;
+
+	if (value > APDS9300_THRESH_MAX)
+		return -EINVAL;
+
+	ret = i2c_smbus_write_word_data(data->client, APDS9300_THRESHLOWLOW
+			| APDS9300_CMD | APDS9300_WORD, value);
+	if (ret) {
+		dev_err(&data->client->dev, "failed to set thresh_low\n");
+		return ret;
+	}
+	data->thresh_low = value;
+
+	return 0;
+}
+
+static int apds9300_set_thresh_hi(struct apds9300_data *data, int value)
+{
+	int ret;
+
+	if (!data->power_state)
+		return -EBUSY;
+
+	if (value > APDS9300_THRESH_MAX)
+		return -EINVAL;
+
+	ret = i2c_smbus_write_word_data(data->client, APDS9300_THRESHHIGHLOW
+			| APDS9300_CMD | APDS9300_WORD, value);
+	if (ret) {
+		dev_err(&data->client->dev, "failed to set thresh_hi\n");
+		return ret;
+	}
+	data->thresh_hi = value;
+
+	return 0;
+}
+
+static int apds9300_set_intr_state(struct apds9300_data *data, int state)
+{
+	int ret;
+	u8 cmd;
+
+	if (!data->power_state)
+		return -EBUSY;
+
+	cmd = state ? APDS9300_INTR_ENABLE | APDS9300_THRESH_INTR : 0x00;
+	ret = i2c_smbus_write_byte_data(data->client,
+			APDS9300_INTERRUPT | APDS9300_CMD, cmd);
+	if (ret) {
+		dev_err(&data->client->dev,
+			"failed to set interrupt state %d\n", state);
+		return ret;
+	}
+	data->intr_en = state;
+
+	return 0;
+}
+
+static int apds9300_set_power_state(struct apds9300_data *data, int state)
+{
+	int ret;
+	u8 cmd;
+
+	cmd = state ? APDS9300_POWER_ON : APDS9300_POWER_OFF;
+	ret = i2c_smbus_write_byte_data(data->client,
+			APDS9300_CONTROL | APDS9300_CMD, cmd);
+	if (ret) {
+		dev_err(&data->client->dev,
+			"failed to set power state %d\n", state);
+		return ret;
+	}
+	data->power_state = state;
+
+	return 0;
+}
+
+static void apds9300_clear_intr(struct apds9300_data *data)
+{
+	int ret;
+
+	ret = i2c_smbus_write_byte(data->client, APDS9300_CLEAR | APDS9300_CMD);
+	if (ret < 0)
+		dev_err(&data->client->dev, "failed to clear interrupt\n");
+}
+
+static int apds9300_chip_init(struct apds9300_data *data)
+{
+	int ret;
+
+	/* Need to set power off to ensure that the chip is off */
+	ret = apds9300_set_power_state(data, 0);
+	if (ret < 0)
+		goto err;
+	/*
+	 * Probe the chip. To do so we try to power up the device and then to
+	 * read back the 0x03 code
+	 */
+	ret = apds9300_set_power_state(data, 1);
+	if (ret < 0)
+		goto err;
+	ret = i2c_smbus_read_byte_data(data->client,
+			APDS9300_CONTROL | APDS9300_CMD);
+	if (ret != APDS9300_POWER_ON) {
+		ret = -ENODEV;
+		goto err;
+	}
+	/*
+	 * Disable interrupt to ensure thai it is doesn't enable
+	 * i.e. after device soft reset
+	 */
+	ret = apds9300_set_intr_state(data, 0);
+	if (ret < 0)
+		goto err;
+
+	return 0;
+
+err:
+	dev_err(&data->client->dev, "failed to init the chip\n");
+	return ret;
+}
+
+static int apds9300_read_raw(struct iio_dev *indio_dev,
+		struct iio_chan_spec const *chan, int *val, int *val2,
+		long mask)
+{
+	int ch0, ch1, ret = -EINVAL;
+	struct apds9300_data *data = iio_priv(indio_dev);
+
+	mutex_lock(&data->mutex);
+	switch (chan->type) {
+	case IIO_LIGHT:
+		ch0 = apds9300_get_adc_val(data, 0);
+		if (ch0 < 0) {
+			ret = ch0;
+			break;
+		}
+		ch1 = apds9300_get_adc_val(data, 1);
+		if (ch1 < 0) {
+			ret = ch1;
+			break;
+		}
+		*val = apds9300_calculate_lux(ch0, ch1);
+		ret = IIO_VAL_INT;
+		break;
+	case IIO_INTENSITY:
+		ret = apds9300_get_adc_val(data, chan->channel);
+		if (ret < 0)
+			break;
+		*val = ret;
+		ret = IIO_VAL_INT;
+		break;
+	default:
+		break;
+	}
+	mutex_unlock(&data->mutex);
+
+	return ret;
+}
+
+static int apds9300_read_thresh(struct iio_dev *indio_dev,
+		const struct iio_chan_spec *chan, enum iio_event_type type,
+		enum iio_event_direction dir, enum iio_event_info info,
+		int *val, int *val2)
+{
+	struct apds9300_data *data = iio_priv(indio_dev);
+
+	switch (dir) {
+	case IIO_EV_DIR_RISING:
+		*val = data->thresh_hi;
+		break;
+	case IIO_EV_DIR_FALLING:
+		*val = data->thresh_low;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return IIO_VAL_INT;
+}
+
+static int apds9300_write_thresh(struct iio_dev *indio_dev,
+		const struct iio_chan_spec *chan, enum iio_event_type type,
+		enum iio_event_direction dir, enum iio_event_info info, int val,
+		int val2)
+{
+	struct apds9300_data *data = iio_priv(indio_dev);
+	int ret;
+
+	mutex_lock(&data->mutex);
+	if (dir == IIO_EV_DIR_RISING)
+		ret = apds9300_set_thresh_hi(data, val);
+	else
+		ret = apds9300_set_thresh_low(data, val);
+	mutex_unlock(&data->mutex);
+
+	return ret;
+}
+
+static int apds9300_read_interrupt_config(struct iio_dev *indio_dev,
+		const struct iio_chan_spec *chan,
+		enum iio_event_type type,
+		enum iio_event_direction dir)
+{
+	struct apds9300_data *data = iio_priv(indio_dev);
+
+	return data->intr_en;
+}
+
+static int apds9300_write_interrupt_config(struct iio_dev *indio_dev,
+		const struct iio_chan_spec *chan, enum iio_event_type type,
+		enum iio_event_direction dir, int state)
+{
+	struct apds9300_data *data = iio_priv(indio_dev);
+	int ret;
+
+	mutex_lock(&data->mutex);
+	ret = apds9300_set_intr_state(data, state);
+	mutex_unlock(&data->mutex);
+
+	return ret;
+}
+
+static const struct iio_info apds9300_info_no_irq = {
+	.read_raw	= apds9300_read_raw,
+};
+
+static const struct iio_info apds9300_info = {
+	.read_raw		= apds9300_read_raw,
+	.read_event_value	= apds9300_read_thresh,
+	.write_event_value	= apds9300_write_thresh,
+	.read_event_config	= apds9300_read_interrupt_config,
+	.write_event_config	= apds9300_write_interrupt_config,
+};
+
+static const struct iio_event_spec apds9300_event_spec[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_RISING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+			BIT(IIO_EV_INFO_ENABLE),
+	}, {
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_FALLING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+			BIT(IIO_EV_INFO_ENABLE),
+	},
+};
+
+static const struct iio_chan_spec apds9300_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.channel = 0,
+		.indexed = true,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+	}, {
+		.type = IIO_INTENSITY,
+		.channel = 0,
+		.channel2 = IIO_MOD_LIGHT_BOTH,
+		.indexed = true,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.event_spec = apds9300_event_spec,
+		.num_event_specs = ARRAY_SIZE(apds9300_event_spec),
+	}, {
+		.type = IIO_INTENSITY,
+		.channel = 1,
+		.channel2 = IIO_MOD_LIGHT_IR,
+		.indexed = true,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+	},
+};
+
+static irqreturn_t apds9300_interrupt_handler(int irq, void *private)
+{
+	struct iio_dev *dev_info = private;
+	struct apds9300_data *data = iio_priv(dev_info);
+
+	iio_push_event(dev_info,
+		       IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0,
+					    IIO_EV_TYPE_THRESH,
+					    IIO_EV_DIR_EITHER),
+		       iio_get_time_ns(dev_info));
+
+	apds9300_clear_intr(data);
+
+	return IRQ_HANDLED;
+}
+
+static int apds9300_probe(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	struct apds9300_data *data;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->client = client;
+
+	ret = apds9300_chip_init(data);
+	if (ret < 0)
+		goto err;
+
+	mutex_init(&data->mutex);
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->channels = apds9300_channels;
+	indio_dev->num_channels = ARRAY_SIZE(apds9300_channels);
+	indio_dev->name = APDS9300_DRV_NAME;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	if (client->irq)
+		indio_dev->info = &apds9300_info;
+	else
+		indio_dev->info = &apds9300_info_no_irq;
+
+	if (client->irq) {
+		ret = devm_request_threaded_irq(&client->dev, client->irq,
+				NULL, apds9300_interrupt_handler,
+				IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+				APDS9300_IRQ_NAME, indio_dev);
+		if (ret) {
+			dev_err(&client->dev, "irq request error %d\n", -ret);
+			goto err;
+		}
+	}
+
+	ret = iio_device_register(indio_dev);
+	if (ret < 0)
+		goto err;
+
+	return 0;
+
+err:
+	/* Ensure that power off in case of error */
+	apds9300_set_power_state(data, 0);
+	return ret;
+}
+
+static int apds9300_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+	struct apds9300_data *data = iio_priv(indio_dev);
+
+	iio_device_unregister(indio_dev);
+
+	/* Ensure that power off and interrupts are disabled */
+	apds9300_set_intr_state(data, 0);
+	apds9300_set_power_state(data, 0);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int apds9300_suspend(struct device *dev)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+	struct apds9300_data *data = iio_priv(indio_dev);
+	int ret;
+
+	mutex_lock(&data->mutex);
+	ret = apds9300_set_power_state(data, 0);
+	mutex_unlock(&data->mutex);
+
+	return ret;
+}
+
+static int apds9300_resume(struct device *dev)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+	struct apds9300_data *data = iio_priv(indio_dev);
+	int ret;
+
+	mutex_lock(&data->mutex);
+	ret = apds9300_set_power_state(data, 1);
+	mutex_unlock(&data->mutex);
+
+	return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(apds9300_pm_ops, apds9300_suspend, apds9300_resume);
+#define APDS9300_PM_OPS (&apds9300_pm_ops)
+#else
+#define APDS9300_PM_OPS NULL
+#endif
+
+static const struct i2c_device_id apds9300_id[] = {
+	{ APDS9300_DRV_NAME, 0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, apds9300_id);
+
+static struct i2c_driver apds9300_driver = {
+	.driver = {
+		.name	= APDS9300_DRV_NAME,
+		.pm	= APDS9300_PM_OPS,
+	},
+	.probe		= apds9300_probe,
+	.remove		= apds9300_remove,
+	.id_table	= apds9300_id,
+};
+
+module_i2c_driver(apds9300_driver);
+
+MODULE_AUTHOR("Kravchenko Oleksandr <o.v.kravchenko@globallogic.com>");
+MODULE_AUTHOR("GlobalLogic inc.");
+MODULE_DESCRIPTION("APDS9300 ambient light photo sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/apds9960.c b/drivers/iio/light/apds9960.c
new file mode 100644
index 0000000..1f112ae
--- /dev/null
+++ b/drivers/iio/light/apds9960.c
@@ -0,0 +1,1138 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * apds9960.c - Support for Avago APDS9960 gesture/RGB/ALS/proximity sensor
+ *
+ * Copyright (C) 2015, 2018
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ *
+ * TODO: gesture + proximity calib offsets
+ */
+
+#include <linux/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 const struct attribute_group apds9960_attribute_group = {
+	.attrs = apds9960_attributes,
+};
+
+static const struct reg_field apds9960_reg_field_int_als =
+				REG_FIELD(APDS9960_REG_ENABLE, 4, 4);
+
+static const struct reg_field apds9960_reg_field_int_ges =
+				REG_FIELD(APDS9960_REG_GCONF_4, 1, 1);
+
+static const struct reg_field apds9960_reg_field_int_pxs =
+				REG_FIELD(APDS9960_REG_ENABLE, 5, 5);
+
+static const struct reg_field apds9960_reg_field_enable_als =
+				REG_FIELD(APDS9960_REG_ENABLE, 1, 1);
+
+static const struct reg_field apds9960_reg_field_enable_ges =
+				REG_FIELD(APDS9960_REG_ENABLE, 6, 6);
+
+static const struct reg_field apds9960_reg_field_enable_pxs =
+				REG_FIELD(APDS9960_REG_ENABLE, 2, 2);
+
+static int apds9960_set_it_time(struct apds9960_data *data, int val2)
+{
+	int ret = -EINVAL;
+	int idx;
+
+	for (idx = 0; idx < ARRAY_SIZE(apds9960_int_time); idx++) {
+		if (apds9960_int_time[idx][0] == val2) {
+			mutex_lock(&data->lock);
+			ret = regmap_write(data->regmap, APDS9960_REG_ATIME,
+						 apds9960_int_time[idx][1]);
+			if (!ret)
+				data->als_adc_int_us = val2;
+			mutex_unlock(&data->lock);
+			break;
+		}
+	}
+
+	return ret;
+}
+
+static int apds9960_set_pxs_gain(struct apds9960_data *data, int val)
+{
+	int ret = -EINVAL;
+	int idx;
+
+	for (idx = 0; idx < ARRAY_SIZE(apds9960_pxs_gain_map); idx++) {
+		if (apds9960_pxs_gain_map[idx] == val) {
+			/* pxs + gesture gains are mirrored */
+			mutex_lock(&data->lock);
+			ret = regmap_update_bits(data->regmap,
+				APDS9960_REG_CONTROL,
+				APDS9960_REG_CONTROL_PGAIN_MASK,
+				idx << APDS9960_REG_CONTROL_PGAIN_MASK_SHIFT);
+			if (ret) {
+				mutex_unlock(&data->lock);
+				break;
+			}
+
+			ret = regmap_update_bits(data->regmap,
+				APDS9960_REG_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 = {
+	.attrs = &apds9960_attribute_group,
+	.read_raw = apds9960_read_raw,
+	.write_raw = apds9960_write_raw,
+	.read_event_value = apds9960_read_event,
+	.write_event_value = apds9960_write_event,
+	.read_event_config = apds9960_read_event_config,
+	.write_event_config = apds9960_write_event_config,
+
+};
+
+static inline int apds9660_fifo_is_empty(struct apds9960_data *data)
+{
+	int cnt;
+	int ret;
+
+	ret = regmap_read(data->regmap, APDS9960_REG_GFLVL, &cnt);
+	if (ret)
+		return ret;
+
+	return cnt;
+}
+
+static void apds9960_read_gesture_fifo(struct apds9960_data *data)
+{
+	int ret, cnt = 0;
+
+	mutex_lock(&data->lock);
+	data->gesture_mode_running = 1;
+
+	while (cnt || (cnt = apds9660_fifo_is_empty(data) > 0)) {
+		ret = regmap_bulk_read(data->regmap, APDS9960_REG_GFIFO_BASE,
+				      &data->buffer, 4);
+
+		if (ret)
+			goto err_read;
+
+		iio_push_to_buffers(data->indio_dev, data->buffer);
+		cnt--;
+	}
+
+err_read:
+	data->gesture_mode_running = 0;
+	mutex_unlock(&data->lock);
+}
+
+static irqreturn_t apds9960_interrupt_handler(int irq, void *private)
+{
+	struct iio_dev *indio_dev = private;
+	struct apds9960_data *data = iio_priv(indio_dev);
+	int ret, status;
+
+	ret = regmap_read(data->regmap, APDS9960_REG_STATUS, &status);
+	if (ret < 0) {
+		dev_err(&data->client->dev, "irq status reg read failed\n");
+		return IRQ_HANDLED;
+	}
+
+	if ((status & APDS9960_REG_STATUS_ALS_INT) && data->als_int) {
+		iio_push_event(indio_dev,
+			       IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0,
+						    IIO_EV_TYPE_THRESH,
+						    IIO_EV_DIR_EITHER),
+			       iio_get_time_ns(indio_dev));
+		regmap_write(data->regmap, APDS9960_REG_CICLEAR, 1);
+	}
+
+	if ((status & APDS9960_REG_STATUS_PS_INT) && data->pxs_int) {
+		iio_push_event(indio_dev,
+			       IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
+						    IIO_EV_TYPE_THRESH,
+						    IIO_EV_DIR_EITHER),
+			       iio_get_time_ns(indio_dev));
+		regmap_write(data->regmap, APDS9960_REG_PICLEAR, 1);
+	}
+
+	if (status & APDS9960_REG_STATUS_GINT)
+		apds9960_read_gesture_fifo(data);
+
+	return IRQ_HANDLED;
+}
+
+static int apds9960_set_powermode(struct apds9960_data *data, bool state)
+{
+	return regmap_update_bits(data->regmap, APDS9960_REG_ENABLE, 1, state);
+}
+
+static int apds9960_buffer_postenable(struct iio_dev *indio_dev)
+{
+	struct apds9960_data *data = iio_priv(indio_dev);
+	int ret;
+
+	ret = regmap_field_write(data->reg_int_ges, 1);
+	if (ret)
+		return ret;
+
+	ret = regmap_field_write(data->reg_enable_ges, 1);
+	if (ret)
+		return ret;
+
+	pm_runtime_get_sync(&data->client->dev);
+
+	return 0;
+}
+
+static int apds9960_buffer_predisable(struct iio_dev *indio_dev)
+{
+	struct apds9960_data *data = iio_priv(indio_dev);
+	int ret;
+
+	ret = regmap_field_write(data->reg_enable_ges, 0);
+	if (ret)
+		return ret;
+
+	ret = regmap_field_write(data->reg_int_ges, 0);
+	if (ret)
+		return ret;
+
+	pm_runtime_put_autosuspend(&data->client->dev);
+
+	return 0;
+}
+
+static const struct iio_buffer_setup_ops apds9960_buffer_setup_ops = {
+	.postenable = apds9960_buffer_postenable,
+	.predisable = apds9960_buffer_predisable,
+};
+
+static int apds9960_regfield_init(struct apds9960_data *data)
+{
+	struct device *dev = &data->client->dev;
+	struct regmap *regmap = data->regmap;
+
+	data->reg_int_als = devm_regmap_field_alloc(dev, regmap,
+						apds9960_reg_field_int_als);
+	if (IS_ERR(data->reg_int_als)) {
+		dev_err(dev, "INT ALS reg field init failed\n");
+		return PTR_ERR(data->reg_int_als);
+	}
+
+	data->reg_int_ges = devm_regmap_field_alloc(dev, regmap,
+						apds9960_reg_field_int_ges);
+	if (IS_ERR(data->reg_int_ges)) {
+		dev_err(dev, "INT gesture reg field init failed\n");
+		return PTR_ERR(data->reg_int_ges);
+	}
+
+	data->reg_int_pxs = devm_regmap_field_alloc(dev, regmap,
+						apds9960_reg_field_int_pxs);
+	if (IS_ERR(data->reg_int_pxs)) {
+		dev_err(dev, "INT pxs reg field init failed\n");
+		return PTR_ERR(data->reg_int_pxs);
+	}
+
+	data->reg_enable_als = devm_regmap_field_alloc(dev, regmap,
+						apds9960_reg_field_enable_als);
+	if (IS_ERR(data->reg_enable_als)) {
+		dev_err(dev, "Enable ALS reg field init failed\n");
+		return PTR_ERR(data->reg_enable_als);
+	}
+
+	data->reg_enable_ges = devm_regmap_field_alloc(dev, regmap,
+						apds9960_reg_field_enable_ges);
+	if (IS_ERR(data->reg_enable_ges)) {
+		dev_err(dev, "Enable gesture reg field init failed\n");
+		return PTR_ERR(data->reg_enable_ges);
+	}
+
+	data->reg_enable_pxs = devm_regmap_field_alloc(dev, regmap,
+						apds9960_reg_field_enable_pxs);
+	if (IS_ERR(data->reg_enable_pxs)) {
+		dev_err(dev, "Enable PXS reg field init failed\n");
+		return PTR_ERR(data->reg_enable_pxs);
+	}
+
+	return 0;
+}
+
+static int apds9960_chip_init(struct apds9960_data *data)
+{
+	int ret;
+
+	/* Default IT for ALS of 28 ms */
+	ret = apds9960_set_it_time(data, 28000);
+	if (ret)
+		return ret;
+
+	/* Ensure gesture interrupt is OFF */
+	ret = regmap_field_write(data->reg_int_ges, 0);
+	if (ret)
+		return ret;
+
+	/* Disable gesture sensor, since polling is useless from user-space */
+	ret = regmap_field_write(data->reg_enable_ges, 0);
+	if (ret)
+		return ret;
+
+	/* Ensure proximity interrupt is OFF */
+	ret = regmap_field_write(data->reg_int_pxs, 0);
+	if (ret)
+		return ret;
+
+	/* Enable proximity sensor for polling */
+	ret = regmap_field_write(data->reg_enable_pxs, 1);
+	if (ret)
+		return ret;
+
+	/* Ensure ALS interrupt is OFF */
+	ret = regmap_field_write(data->reg_int_als, 0);
+	if (ret)
+		return ret;
+
+	/* Enable ALS sensor for polling */
+	ret = regmap_field_write(data->reg_enable_als, 1);
+	if (ret)
+		return ret;
+	/*
+	 * When enabled trigger an interrupt after 3 readings
+	 * outside threshold for ALS + PXS
+	 */
+	ret = regmap_write(data->regmap, APDS9960_REG_PERS,
+			   APDS9960_DEFAULT_PERS);
+	if (ret)
+		return ret;
+
+	/*
+	 * Wait for 4 event outside gesture threshold to prevent interrupt
+	 * flooding.
+	 */
+	ret = regmap_update_bits(data->regmap, APDS9960_REG_GCONF_1,
+			APDS9960_REG_GCONF_1_GFIFO_THRES_MASK,
+			BIT(0) << APDS9960_REG_GCONF_1_GFIFO_THRES_MASK_SHIFT);
+	if (ret)
+		return ret;
+
+	/* Default ENTER and EXIT thresholds for the GESTURE engine. */
+	ret = regmap_write(data->regmap, APDS9960_REG_GPENTH,
+			   APDS9960_DEFAULT_GPENTH);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(data->regmap, APDS9960_REG_GEXTH,
+			   APDS9960_DEFAULT_GEXTH);
+	if (ret)
+		return ret;
+
+	return apds9960_set_powermode(data, 1);
+}
+
+static int apds9960_probe(struct i2c_client *client,
+			  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->dev.parent = &client->dev;
+	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_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+				pm_runtime_force_resume)
+	SET_RUNTIME_PM_OPS(apds9960_runtime_suspend,
+			   apds9960_runtime_resume, NULL)
+};
+
+static const struct i2c_device_id apds9960_id[] = {
+	{ "apds9960", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, apds9960_id);
+
+static const struct of_device_id apds9960_of_match[] = {
+	{ .compatible = "avago,apds9960" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, apds9960_of_match);
+
+static struct i2c_driver apds9960_driver = {
+	.driver = {
+		.name	= APDS9960_DRV_NAME,
+		.of_match_table = apds9960_of_match,
+		.pm	= &apds9960_pm_ops,
+	},
+	.probe		= apds9960_probe,
+	.remove		= apds9960_remove,
+	.id_table	= apds9960_id,
+};
+module_i2c_driver(apds9960_driver);
+
+MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
+MODULE_DESCRIPTION("ADPS9960 Gesture/RGB/ALS/Proximity sensor");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/bh1750.c b/drivers/iio/light/bh1750.c
new file mode 100644
index 0000000..a814828
--- /dev/null
+++ b/drivers/iio/light/bh1750.c
@@ -0,0 +1,332 @@
+/*
+ * 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 const struct attribute_group bh1750_attribute_group = {
+	.attrs = bh1750_attributes,
+};
+
+static const struct iio_info bh1750_info = {
+	.attrs = &bh1750_attribute_group,
+	.read_raw = bh1750_read_raw,
+	.write_raw = bh1750_write_raw,
+};
+
+static const struct iio_chan_spec bh1750_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE) |
+				      BIT(IIO_CHAN_INFO_INT_TIME)
+	}
+};
+
+static int bh1750_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	int ret, usec;
+	struct bh1750_data *data;
+	struct iio_dev *indio_dev;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C |
+				I2C_FUNC_SMBUS_WRITE_BYTE))
+		return -EOPNOTSUPP;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->client = client;
+	data->chip_info = &bh1750_chip_info_tbl[id->driver_data];
+
+	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/drivers/iio/light/bh1780.c b/drivers/iio/light/bh1780.c
new file mode 100644
index 0000000..036f3bb
--- /dev/null
+++ b/drivers/iio/light/bh1780.c
@@ -0,0 +1,298 @@
+/*
+ * ROHM 1780GLI Ambient Light Sensor Driver
+ *
+ * Copyright (C) 2016 Linaro Ltd.
+ * Author: Linus Walleij <linus.walleij@linaro.org>
+ * Loosely based on the previous BH1780 ALS misc driver
+ * Copyright (C) 2010 Texas Instruments
+ * Author: Hemanth V <hemanthv@ti.com>
+ */
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm_runtime.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/bitops.h>
+
+#define BH1780_CMD_BIT		BIT(7)
+#define BH1780_REG_CONTROL	0x00
+#define BH1780_REG_PARTID	0x0A
+#define BH1780_REG_MANFID	0x0B
+#define BH1780_REG_DLOW		0x0C
+#define BH1780_REG_DHIGH	0x0D
+
+#define BH1780_REVMASK		GENMASK(3,0)
+#define BH1780_POWMASK		GENMASK(1,0)
+#define BH1780_POFF		(0x0)
+#define BH1780_PON		(0x3)
+
+/* power on settling time in ms */
+#define BH1780_PON_DELAY	2
+/* max time before value available in ms */
+#define BH1780_INTERVAL		250
+
+struct bh1780_data {
+	struct i2c_client *client;
+};
+
+static int bh1780_write(struct bh1780_data *bh1780, u8 reg, u8 val)
+{
+	int ret = i2c_smbus_write_byte_data(bh1780->client,
+					    BH1780_CMD_BIT | reg,
+					    val);
+	if (ret < 0)
+		dev_err(&bh1780->client->dev,
+			"i2c_smbus_write_byte_data failed error "
+			"%d, register %01x\n",
+			ret, reg);
+	return ret;
+}
+
+static int bh1780_read(struct bh1780_data *bh1780, u8 reg)
+{
+	int ret = i2c_smbus_read_byte_data(bh1780->client,
+					   BH1780_CMD_BIT | reg);
+	if (ret < 0)
+		dev_err(&bh1780->client->dev,
+			"i2c_smbus_read_byte_data failed error "
+			"%d, register %01x\n",
+			ret, reg);
+	return ret;
+}
+
+static int bh1780_read_word(struct bh1780_data *bh1780, u8 reg)
+{
+	int ret = i2c_smbus_read_word_data(bh1780->client,
+					   BH1780_CMD_BIT | reg);
+	if (ret < 0)
+		dev_err(&bh1780->client->dev,
+			"i2c_smbus_read_word_data failed error "
+			"%d, register %01x\n",
+			ret, reg);
+	return ret;
+}
+
+static int bh1780_debugfs_reg_access(struct iio_dev *indio_dev,
+			      unsigned int reg, unsigned int writeval,
+			      unsigned int *readval)
+{
+	struct bh1780_data *bh1780 = iio_priv(indio_dev);
+	int ret;
+
+	if (!readval)
+		return bh1780_write(bh1780, (u8)reg, (u8)writeval);
+
+	ret = bh1780_read(bh1780, (u8)reg);
+	if (ret < 0)
+		return ret;
+
+	*readval = ret;
+
+	return 0;
+}
+
+static int bh1780_read_raw(struct iio_dev *indio_dev,
+			   struct iio_chan_spec const *chan,
+			   int *val, int *val2, long mask)
+{
+	struct bh1780_data *bh1780 = iio_priv(indio_dev);
+	int value;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		switch (chan->type) {
+		case IIO_LIGHT:
+			pm_runtime_get_sync(&bh1780->client->dev);
+			value = bh1780_read_word(bh1780, BH1780_REG_DLOW);
+			if (value < 0)
+				return value;
+			pm_runtime_mark_last_busy(&bh1780->client->dev);
+			pm_runtime_put_autosuspend(&bh1780->client->dev);
+			*val = value;
+
+			return IIO_VAL_INT;
+		default:
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_INT_TIME:
+		*val = 0;
+		*val2 = BH1780_INTERVAL * 1000;
+		return IIO_VAL_INT_PLUS_MICRO;
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info bh1780_info = {
+	.read_raw = bh1780_read_raw,
+	.debugfs_reg_access = bh1780_debugfs_reg_access,
+};
+
+static const struct iio_chan_spec bh1780_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_INT_TIME)
+	}
+};
+
+static int bh1780_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	int ret;
+	struct bh1780_data *bh1780;
+	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+	struct iio_dev *indio_dev;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+		return -EIO;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*bh1780));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	bh1780 = iio_priv(indio_dev);
+	bh1780->client = client;
+	i2c_set_clientdata(client, indio_dev);
+
+	/* Power up the device */
+	ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_PON);
+	if (ret < 0)
+		return ret;
+	msleep(BH1780_PON_DELAY);
+	pm_runtime_get_noresume(&client->dev);
+	pm_runtime_set_active(&client->dev);
+	pm_runtime_enable(&client->dev);
+
+	ret = bh1780_read(bh1780, BH1780_REG_PARTID);
+	if (ret < 0)
+		goto out_disable_pm;
+	dev_info(&client->dev,
+		 "Ambient Light Sensor, Rev : %lu\n",
+		 (ret & BH1780_REVMASK));
+
+	/*
+	 * As the device takes 250 ms to even come up with a fresh
+	 * measurement after power-on, do not shut it down unnecessarily.
+	 * Set autosuspend to a five seconds.
+	 */
+	pm_runtime_set_autosuspend_delay(&client->dev, 5000);
+	pm_runtime_use_autosuspend(&client->dev);
+	pm_runtime_put(&client->dev);
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->info = &bh1780_info;
+	indio_dev->name = "bh1780";
+	indio_dev->channels = bh1780_channels;
+	indio_dev->num_channels = ARRAY_SIZE(bh1780_channels);
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	ret = iio_device_register(indio_dev);
+	if (ret)
+		goto out_disable_pm;
+	return 0;
+
+out_disable_pm:
+	pm_runtime_put_noidle(&client->dev);
+	pm_runtime_disable(&client->dev);
+	return ret;
+}
+
+static int bh1780_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+	struct bh1780_data *bh1780 = iio_priv(indio_dev);
+	int ret;
+
+	iio_device_unregister(indio_dev);
+	pm_runtime_get_sync(&client->dev);
+	pm_runtime_put_noidle(&client->dev);
+	pm_runtime_disable(&client->dev);
+	ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_POFF);
+	if (ret < 0) {
+		dev_err(&client->dev, "failed to power off\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int bh1780_runtime_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+	struct bh1780_data *bh1780 = iio_priv(indio_dev);
+	int ret;
+
+	ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_POFF);
+	if (ret < 0) {
+		dev_err(dev, "failed to runtime suspend\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int bh1780_runtime_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+	struct bh1780_data *bh1780 = iio_priv(indio_dev);
+	int ret;
+
+	ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_PON);
+	if (ret < 0) {
+		dev_err(dev, "failed to runtime resume\n");
+		return ret;
+	}
+
+	/* Wait for power on, then for a value to be available */
+	msleep(BH1780_PON_DELAY + BH1780_INTERVAL);
+
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops bh1780_dev_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+				pm_runtime_force_resume)
+	SET_RUNTIME_PM_OPS(bh1780_runtime_suspend,
+			   bh1780_runtime_resume, NULL)
+};
+
+static const struct i2c_device_id bh1780_id[] = {
+	{ "bh1780", 0 },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(i2c, bh1780_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id of_bh1780_match[] = {
+	{ .compatible = "rohm,bh1780gli", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, of_bh1780_match);
+#endif
+
+static struct i2c_driver bh1780_driver = {
+	.probe		= bh1780_probe,
+	.remove		= bh1780_remove,
+	.id_table	= bh1780_id,
+	.driver = {
+		.name = "bh1780",
+		.pm = &bh1780_dev_pm_ops,
+		.of_match_table = of_match_ptr(of_bh1780_match),
+	},
+};
+
+module_i2c_driver(bh1780_driver);
+
+MODULE_DESCRIPTION("ROHM BH1780GLI Ambient Light Sensor Driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
diff --git a/drivers/iio/light/cm32181.c b/drivers/iio/light/cm32181.c
new file mode 100644
index 0000000..aebf7dd
--- /dev/null
+++ b/drivers/iio/light/cm32181.c
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2013 Capella Microsystems Inc.
+ * Author: Kevin Tsai <ktsai@capellamicro.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.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/regulator/consumer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/events.h>
+#include <linux/init.h>
+
+/* Registers Address */
+#define CM32181_REG_ADDR_CMD		0x00
+#define CM32181_REG_ADDR_ALS		0x04
+#define CM32181_REG_ADDR_STATUS		0x06
+#define CM32181_REG_ADDR_ID		0x07
+
+/* Number of Configurable Registers */
+#define CM32181_CONF_REG_NUM		0x01
+
+/* CMD register */
+#define CM32181_CMD_ALS_ENABLE		0x00
+#define CM32181_CMD_ALS_DISABLE		0x01
+#define CM32181_CMD_ALS_INT_EN		0x02
+
+#define CM32181_CMD_ALS_IT_SHIFT	6
+#define CM32181_CMD_ALS_IT_MASK		(0x0F << CM32181_CMD_ALS_IT_SHIFT)
+#define CM32181_CMD_ALS_IT_DEFAULT	(0x00 << CM32181_CMD_ALS_IT_SHIFT)
+
+#define CM32181_CMD_ALS_SM_SHIFT	11
+#define CM32181_CMD_ALS_SM_MASK		(0x03 << CM32181_CMD_ALS_SM_SHIFT)
+#define CM32181_CMD_ALS_SM_DEFAULT	(0x01 << CM32181_CMD_ALS_SM_SHIFT)
+
+#define CM32181_MLUX_PER_BIT		5	/* ALS_SM=01 IT=800ms */
+#define CM32181_MLUX_PER_BIT_BASE_IT	800000	/* Based on IT=800ms */
+#define	CM32181_CALIBSCALE_DEFAULT	1000
+#define CM32181_CALIBSCALE_RESOLUTION	1000
+#define MLUX_PER_LUX			1000
+
+static const u8 cm32181_reg[CM32181_CONF_REG_NUM] = {
+	CM32181_REG_ADDR_CMD,
+};
+
+static const int als_it_bits[] = {12, 8, 0, 1, 2, 3};
+static const int als_it_value[] = {25000, 50000, 100000, 200000, 400000,
+	800000};
+
+struct cm32181_chip {
+	struct i2c_client *client;
+	struct mutex lock;
+	u16 conf_regs[CM32181_CONF_REG_NUM];
+	int calibscale;
+};
+
+/**
+ * cm32181_reg_init() - Initialize CM32181 registers
+ * @cm32181:	pointer of struct cm32181.
+ *
+ * Initialize CM32181 ambient light sensor register to default values.
+ *
+ * Return: 0 for success; otherwise for error code.
+ */
+static int cm32181_reg_init(struct cm32181_chip *cm32181)
+{
+	struct i2c_client *client = cm32181->client;
+	int i;
+	s32 ret;
+
+	ret = i2c_smbus_read_word_data(client, CM32181_REG_ADDR_ID);
+	if (ret < 0)
+		return ret;
+
+	/* check device ID */
+	if ((ret & 0xFF) != 0x81)
+		return -ENODEV;
+
+	/* Default Values */
+	cm32181->conf_regs[CM32181_REG_ADDR_CMD] = CM32181_CMD_ALS_ENABLE |
+			CM32181_CMD_ALS_IT_DEFAULT | CM32181_CMD_ALS_SM_DEFAULT;
+	cm32181->calibscale = CM32181_CALIBSCALE_DEFAULT;
+
+	/* Initialize registers*/
+	for (i = 0; i < CM32181_CONF_REG_NUM; i++) {
+		ret = i2c_smbus_write_word_data(client, cm32181_reg[i],
+			cm32181->conf_regs[i]);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/**
+ *  cm32181_read_als_it() - Get sensor integration time (ms)
+ *  @cm32181:	pointer of struct cm32181
+ *  @val2:	pointer of int to load the als_it value.
+ *
+ *  Report the current integartion time by millisecond.
+ *
+ *  Return: IIO_VAL_INT_PLUS_MICRO for success, otherwise -EINVAL.
+ */
+static int cm32181_read_als_it(struct cm32181_chip *cm32181, int *val2)
+{
+	u16 als_it;
+	int i;
+
+	als_it = cm32181->conf_regs[CM32181_REG_ADDR_CMD];
+	als_it &= CM32181_CMD_ALS_IT_MASK;
+	als_it >>= CM32181_CMD_ALS_IT_SHIFT;
+	for (i = 0; i < ARRAY_SIZE(als_it_bits); i++) {
+		if (als_it == als_it_bits[i]) {
+			*val2 = als_it_value[i];
+			return IIO_VAL_INT_PLUS_MICRO;
+		}
+	}
+
+	return -EINVAL;
+}
+
+/**
+ * cm32181_write_als_it() - Write sensor integration time
+ * @cm32181:	pointer of struct cm32181.
+ * @val:	integration time by millisecond.
+ *
+ * Convert integration time (ms) to sensor value.
+ *
+ * Return: i2c_smbus_write_word_data command return value.
+ */
+static int cm32181_write_als_it(struct cm32181_chip *cm32181, int val)
+{
+	struct i2c_client *client = cm32181->client;
+	u16 als_it;
+	int ret, i, n;
+
+	n = ARRAY_SIZE(als_it_value);
+	for (i = 0; i < n; i++)
+		if (val <= als_it_value[i])
+			break;
+	if (i >= n)
+		i = n - 1;
+
+	als_it = als_it_bits[i];
+	als_it <<= CM32181_CMD_ALS_IT_SHIFT;
+
+	mutex_lock(&cm32181->lock);
+	cm32181->conf_regs[CM32181_REG_ADDR_CMD] &=
+		~CM32181_CMD_ALS_IT_MASK;
+	cm32181->conf_regs[CM32181_REG_ADDR_CMD] |=
+		als_it;
+	ret = i2c_smbus_write_word_data(client, CM32181_REG_ADDR_CMD,
+			cm32181->conf_regs[CM32181_REG_ADDR_CMD]);
+	mutex_unlock(&cm32181->lock);
+
+	return ret;
+}
+
+/**
+ * cm32181_get_lux() - report current lux value
+ * @cm32181:	pointer of struct cm32181.
+ *
+ * Convert sensor raw data to lux.  It depends on integration
+ * time and calibscale variable.
+ *
+ * Return: Positive value is lux, otherwise is error code.
+ */
+static int cm32181_get_lux(struct cm32181_chip *cm32181)
+{
+	struct i2c_client *client = cm32181->client;
+	int ret;
+	int als_it;
+	unsigned long lux;
+
+	ret = cm32181_read_als_it(cm32181, &als_it);
+	if (ret < 0)
+		return -EINVAL;
+
+	lux = CM32181_MLUX_PER_BIT;
+	lux *= CM32181_MLUX_PER_BIT_BASE_IT;
+	lux /= als_it;
+
+	ret = i2c_smbus_read_word_data(client, CM32181_REG_ADDR_ALS);
+	if (ret < 0)
+		return ret;
+
+	lux *= ret;
+	lux *= cm32181->calibscale;
+	lux /= CM32181_CALIBSCALE_RESOLUTION;
+	lux /= MLUX_PER_LUX;
+
+	if (lux > 0xFFFF)
+		lux = 0xFFFF;
+
+	return lux;
+}
+
+static int cm32181_read_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct cm32181_chip *cm32181 = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_PROCESSED:
+		ret = cm32181_get_lux(cm32181);
+		if (ret < 0)
+			return ret;
+		*val = ret;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_CALIBSCALE:
+		*val = cm32181->calibscale;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_INT_TIME:
+		*val = 0;
+		ret = cm32181_read_als_it(cm32181, val2);
+		return ret;
+	}
+
+	return -EINVAL;
+}
+
+static int cm32181_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int val, int val2, long mask)
+{
+	struct cm32181_chip *cm32181 = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_CALIBSCALE:
+		cm32181->calibscale = val;
+		return val;
+	case IIO_CHAN_INFO_INT_TIME:
+		ret = cm32181_write_als_it(cm32181, val2);
+		return ret;
+	}
+
+	return -EINVAL;
+}
+
+/**
+ * cm32181_get_it_available() - Get available ALS IT value
+ * @dev:	pointer of struct device.
+ * @attr:	pointer of struct device_attribute.
+ * @buf:	pointer of return string buffer.
+ *
+ * Display the available integration time values by millisecond.
+ *
+ * Return: string length.
+ */
+static ssize_t cm32181_get_it_available(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	int i, n, len;
+
+	n = ARRAY_SIZE(als_it_value);
+	for (i = 0, len = 0; i < n; i++)
+		len += sprintf(buf + len, "0.%06u ", als_it_value[i]);
+	return len + sprintf(buf + len, "\n");
+}
+
+static const struct iio_chan_spec cm32181_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate =
+			BIT(IIO_CHAN_INFO_PROCESSED) |
+			BIT(IIO_CHAN_INFO_CALIBSCALE) |
+			BIT(IIO_CHAN_INFO_INT_TIME),
+	}
+};
+
+static IIO_DEVICE_ATTR(in_illuminance_integration_time_available,
+			S_IRUGO, cm32181_get_it_available, NULL, 0);
+
+static struct attribute *cm32181_attributes[] = {
+	&iio_dev_attr_in_illuminance_integration_time_available.dev_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group cm32181_attribute_group = {
+	.attrs = cm32181_attributes
+};
+
+static const struct iio_info cm32181_info = {
+	.read_raw		= &cm32181_read_raw,
+	.write_raw		= &cm32181_write_raw,
+	.attrs			= &cm32181_attribute_group,
+};
+
+static int cm32181_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct cm32181_chip *cm32181;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*cm32181));
+	if (!indio_dev) {
+		dev_err(&client->dev, "devm_iio_device_alloc failed\n");
+		return -ENOMEM;
+	}
+
+	cm32181 = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	cm32181->client = client;
+
+	mutex_init(&cm32181->lock);
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->channels = cm32181_channels;
+	indio_dev->num_channels = ARRAY_SIZE(cm32181_channels);
+	indio_dev->info = &cm32181_info;
+	indio_dev->name = id->name;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	ret = cm32181_reg_init(cm32181);
+	if (ret) {
+		dev_err(&client->dev,
+			"%s: register init failed\n",
+			__func__);
+		return ret;
+	}
+
+	ret = devm_iio_device_register(&client->dev, indio_dev);
+	if (ret) {
+		dev_err(&client->dev,
+			"%s: regist device failed\n",
+			__func__);
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct i2c_device_id cm32181_id[] = {
+	{ "cm32181", 0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, cm32181_id);
+
+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),
+	},
+	.id_table       = cm32181_id,
+	.probe		= cm32181_probe,
+};
+
+module_i2c_driver(cm32181_driver);
+
+MODULE_AUTHOR("Kevin Tsai <ktsai@capellamicro.com>");
+MODULE_DESCRIPTION("CM32181 ambient light sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/cm3232.c b/drivers/iio/light/cm3232.c
new file mode 100644
index 0000000..c639cf2
--- /dev/null
+++ b/drivers/iio/light/cm3232.c
@@ -0,0 +1,438 @@
+/*
+ * CM3232 Ambient Light Sensor
+ *
+ * Copyright (C) 2014-2015 Capella Microsystems Inc.
+ * Author: Kevin Tsai <ktsai@capellamicro.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.
+ *
+ * IIO driver for CM3232 (7-bit I2C slave address 0x10).
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/init.h>
+
+/* Registers Address */
+#define CM3232_REG_ADDR_CMD		0x00
+#define CM3232_REG_ADDR_ALS		0x50
+#define CM3232_REG_ADDR_ID		0x53
+
+#define CM3232_CMD_ALS_DISABLE		BIT(0)
+
+#define CM3232_CMD_ALS_IT_SHIFT		2
+#define CM3232_CMD_ALS_IT_MASK		(BIT(2) | BIT(3) | BIT(4))
+#define CM3232_CMD_ALS_IT_DEFAULT	(0x01 << CM3232_CMD_ALS_IT_SHIFT)
+
+#define CM3232_CMD_ALS_RESET		BIT(6)
+
+#define CM3232_CMD_DEFAULT		CM3232_CMD_ALS_IT_DEFAULT
+
+#define CM3232_HW_ID			0x32
+#define CM3232_CALIBSCALE_DEFAULT	100000
+#define CM3232_CALIBSCALE_RESOLUTION	100000
+#define CM3232_MLUX_PER_LUX		1000
+
+#define CM3232_MLUX_PER_BIT_DEFAULT	64
+#define CM3232_MLUX_PER_BIT_BASE_IT	100000
+
+static const struct {
+	int val;
+	int val2;
+	u8 it;
+} cm3232_als_it_scales[] = {
+	{0, 100000, 0},	/* 0.100000 */
+	{0, 200000, 1},	/* 0.200000 */
+	{0, 400000, 2},	/* 0.400000 */
+	{0, 800000, 3},	/* 0.800000 */
+	{1, 600000, 4},	/* 1.600000 */
+	{3, 200000, 5},	/* 3.200000 */
+};
+
+struct cm3232_als_info {
+	u8 regs_cmd_default;
+	u8 hw_id;
+	int calibscale;
+	int mlux_per_bit;
+	int mlux_per_bit_base_it;
+};
+
+static struct cm3232_als_info cm3232_als_info_default = {
+	.regs_cmd_default = CM3232_CMD_DEFAULT,
+	.hw_id = CM3232_HW_ID,
+	.calibscale = CM3232_CALIBSCALE_DEFAULT,
+	.mlux_per_bit = CM3232_MLUX_PER_BIT_DEFAULT,
+	.mlux_per_bit_base_it = CM3232_MLUX_PER_BIT_BASE_IT,
+};
+
+struct cm3232_chip {
+	struct i2c_client *client;
+	struct cm3232_als_info *als_info;
+	u8 regs_cmd;
+	u16 regs_als;
+};
+
+/**
+ * cm3232_reg_init() - Initialize CM3232
+ * @chip:	pointer of struct cm3232_chip.
+ *
+ * Check and initialize CM3232 ambient light sensor.
+ *
+ * Return: 0 for success; otherwise for error code.
+ */
+static int cm3232_reg_init(struct cm3232_chip *chip)
+{
+	struct i2c_client *client = chip->client;
+	s32 ret;
+
+	chip->als_info = &cm3232_als_info_default;
+
+	/* Identify device */
+	ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ID);
+	if (ret < 0) {
+		dev_err(&chip->client->dev, "Error reading addr_id\n");
+		return ret;
+	}
+
+	if ((ret & 0xFF) != chip->als_info->hw_id)
+		return -ENODEV;
+
+	/* Disable and reset device */
+	chip->regs_cmd = CM3232_CMD_ALS_DISABLE | CM3232_CMD_ALS_RESET;
+	ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD,
+					chip->regs_cmd);
+	if (ret < 0) {
+		dev_err(&chip->client->dev, "Error writing reg_cmd\n");
+		return ret;
+	}
+
+	/* Register default value */
+	chip->regs_cmd = chip->als_info->regs_cmd_default;
+
+	/* Configure register */
+	ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD,
+					chip->regs_cmd);
+	if (ret < 0)
+		dev_err(&chip->client->dev, "Error writing reg_cmd\n");
+
+	return ret;
+}
+
+/**
+ *  cm3232_read_als_it() - Get sensor integration time
+ *  @chip:	pointer of struct cm3232_chip
+ *  @val:	pointer of int to load the integration (sec).
+ *  @val2:	pointer of int to load the integration time (microsecond).
+ *
+ *  Report the current integration time.
+ *
+ *  Return: IIO_VAL_INT_PLUS_MICRO for success, otherwise -EINVAL.
+ */
+static int cm3232_read_als_it(struct cm3232_chip *chip, int *val, int *val2)
+{
+	u16 als_it;
+	int i;
+
+	als_it = chip->regs_cmd;
+	als_it &= CM3232_CMD_ALS_IT_MASK;
+	als_it >>= CM3232_CMD_ALS_IT_SHIFT;
+	for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) {
+		if (als_it == cm3232_als_it_scales[i].it) {
+			*val = cm3232_als_it_scales[i].val;
+			*val2 = cm3232_als_it_scales[i].val2;
+			return IIO_VAL_INT_PLUS_MICRO;
+		}
+	}
+
+	return -EINVAL;
+}
+
+/**
+ * cm3232_write_als_it() - Write sensor integration time
+ * @chip:	pointer of struct cm3232_chip.
+ * @val:	integration time in second.
+ * @val2:	integration time in microsecond.
+ *
+ * Convert integration time to sensor value.
+ *
+ * Return: i2c_smbus_write_byte_data command return value.
+ */
+static int cm3232_write_als_it(struct cm3232_chip *chip, int val, int val2)
+{
+	struct i2c_client *client = chip->client;
+	u16 als_it, cmd;
+	int i;
+	s32 ret;
+
+	for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) {
+		if (val == cm3232_als_it_scales[i].val &&
+			val2 == cm3232_als_it_scales[i].val2) {
+
+			als_it = cm3232_als_it_scales[i].it;
+			als_it <<= CM3232_CMD_ALS_IT_SHIFT;
+
+			cmd = chip->regs_cmd & ~CM3232_CMD_ALS_IT_MASK;
+			cmd |= als_it;
+			ret = i2c_smbus_write_byte_data(client,
+							CM3232_REG_ADDR_CMD,
+							cmd);
+			if (ret < 0)
+				return ret;
+			chip->regs_cmd = cmd;
+			return 0;
+		}
+	}
+	return -EINVAL;
+}
+
+/**
+ * cm3232_get_lux() - report current lux value
+ * @chip:	pointer of struct cm3232_chip.
+ *
+ * Convert sensor data to lux.  It depends on integration
+ * time and calibscale variable.
+ *
+ * Return: Zero or positive value is lux, otherwise error code.
+ */
+static int cm3232_get_lux(struct cm3232_chip *chip)
+{
+	struct i2c_client *client = chip->client;
+	struct cm3232_als_info *als_info = chip->als_info;
+	int ret;
+	int val, val2;
+	int als_it;
+	u64 lux;
+
+	/* Calculate mlux per bit based on als_it */
+	ret = cm3232_read_als_it(chip, &val, &val2);
+	if (ret < 0)
+		return -EINVAL;
+	als_it = val * 1000000 + val2;
+	lux = (__force u64)als_info->mlux_per_bit;
+	lux *= als_info->mlux_per_bit_base_it;
+	lux = div_u64(lux, als_it);
+
+	ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ALS);
+	if (ret < 0) {
+		dev_err(&client->dev, "Error reading reg_addr_als\n");
+		return ret;
+	}
+
+	chip->regs_als = (u16)ret;
+	lux *= chip->regs_als;
+	lux *= als_info->calibscale;
+	lux = div_u64(lux, CM3232_CALIBSCALE_RESOLUTION);
+	lux = div_u64(lux, CM3232_MLUX_PER_LUX);
+
+	if (lux > 0xFFFF)
+		lux = 0xFFFF;
+
+	return (int)lux;
+}
+
+static int cm3232_read_raw(struct iio_dev *indio_dev,
+			struct iio_chan_spec const *chan,
+			int *val, int *val2, long mask)
+{
+	struct cm3232_chip *chip = iio_priv(indio_dev);
+	struct cm3232_als_info *als_info = chip->als_info;
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_PROCESSED:
+		ret = cm3232_get_lux(chip);
+		if (ret < 0)
+			return ret;
+		*val = ret;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_CALIBSCALE:
+		*val = als_info->calibscale;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_INT_TIME:
+		return cm3232_read_als_it(chip, val, val2);
+	}
+
+	return -EINVAL;
+}
+
+static int cm3232_write_raw(struct iio_dev *indio_dev,
+			struct iio_chan_spec const *chan,
+			int val, int val2, long mask)
+{
+	struct cm3232_chip *chip = iio_priv(indio_dev);
+	struct cm3232_als_info *als_info = chip->als_info;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_CALIBSCALE:
+		als_info->calibscale = val;
+		return 0;
+	case IIO_CHAN_INFO_INT_TIME:
+		return cm3232_write_als_it(chip, val, val2);
+	}
+
+	return -EINVAL;
+}
+
+/**
+ * cm3232_get_it_available() - Get available ALS IT value
+ * @dev:	pointer of struct device.
+ * @attr:	pointer of struct device_attribute.
+ * @buf:	pointer of return string buffer.
+ *
+ * Display the available integration time in second.
+ *
+ * Return: string length.
+ */
+static ssize_t cm3232_get_it_available(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	int i, len;
+
+	for (i = 0, len = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++)
+		len += scnprintf(buf + len, PAGE_SIZE - len, "%u.%06u ",
+			cm3232_als_it_scales[i].val,
+			cm3232_als_it_scales[i].val2);
+	return len + scnprintf(buf + len, PAGE_SIZE - len, "\n");
+}
+
+static const struct iio_chan_spec cm3232_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate =
+			BIT(IIO_CHAN_INFO_PROCESSED) |
+			BIT(IIO_CHAN_INFO_CALIBSCALE) |
+			BIT(IIO_CHAN_INFO_INT_TIME),
+	}
+};
+
+static IIO_DEVICE_ATTR(in_illuminance_integration_time_available,
+			S_IRUGO, cm3232_get_it_available, NULL, 0);
+
+static struct attribute *cm3232_attributes[] = {
+	&iio_dev_attr_in_illuminance_integration_time_available.dev_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group cm3232_attribute_group = {
+	.attrs = cm3232_attributes
+};
+
+static const struct iio_info cm3232_info = {
+	.read_raw		= &cm3232_read_raw,
+	.write_raw		= &cm3232_write_raw,
+	.attrs			= &cm3232_attribute_group,
+};
+
+static int cm3232_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct cm3232_chip *chip;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	chip = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	chip->client = client;
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->channels = cm3232_channels;
+	indio_dev->num_channels = ARRAY_SIZE(cm3232_channels);
+	indio_dev->info = &cm3232_info;
+	indio_dev->name = id->name;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	ret = cm3232_reg_init(chip);
+	if (ret) {
+		dev_err(&client->dev,
+			"%s: register init failed\n",
+			__func__);
+		return ret;
+	}
+
+	return iio_device_register(indio_dev);
+}
+
+static int cm3232_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+	i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD,
+		CM3232_CMD_ALS_DISABLE);
+
+	iio_device_unregister(indio_dev);
+
+	return 0;
+}
+
+static const struct i2c_device_id cm3232_id[] = {
+	{"cm3232", 0},
+	{}
+};
+
+#ifdef CONFIG_PM_SLEEP
+static int cm3232_suspend(struct device *dev)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+	struct cm3232_chip *chip = iio_priv(indio_dev);
+	struct i2c_client *client = chip->client;
+	int ret;
+
+	chip->regs_cmd |= CM3232_CMD_ALS_DISABLE;
+	ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD,
+					chip->regs_cmd);
+
+	return ret;
+}
+
+static int cm3232_resume(struct device *dev)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+	struct cm3232_chip *chip = iio_priv(indio_dev);
+	struct i2c_client *client = chip->client;
+	int ret;
+
+	chip->regs_cmd &= ~CM3232_CMD_ALS_DISABLE;
+	ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD,
+					chip->regs_cmd | CM3232_CMD_ALS_RESET);
+
+	return ret;
+}
+
+static const struct dev_pm_ops cm3232_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(cm3232_suspend, cm3232_resume)};
+#endif
+
+MODULE_DEVICE_TABLE(i2c, cm3232_id);
+
+static const struct of_device_id cm3232_of_match[] = {
+	{.compatible = "capella,cm3232"},
+	{}
+};
+MODULE_DEVICE_TABLE(of, cm3232_of_match);
+
+static struct i2c_driver cm3232_driver = {
+	.driver = {
+		.name	= "cm3232",
+		.of_match_table = of_match_ptr(cm3232_of_match),
+#ifdef CONFIG_PM_SLEEP
+		.pm	= &cm3232_pm_ops,
+#endif
+	},
+	.id_table	= cm3232_id,
+	.probe		= cm3232_probe,
+	.remove		= cm3232_remove,
+};
+
+module_i2c_driver(cm3232_driver);
+
+MODULE_AUTHOR("Kevin Tsai <ktsai@capellamicro.com>");
+MODULE_DESCRIPTION("CM3232 ambient light sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/cm3323.c b/drivers/iio/light/cm3323.c
new file mode 100644
index 0000000..83b08b6
--- /dev/null
+++ b/drivers/iio/light/cm3323.c
@@ -0,0 +1,290 @@
+/*
+ * CM3323 - Capella Color Light 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 CM3323 (7-bit I2C slave address 0x10)
+ *
+ * TODO: calibscale to correct the lens factor
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define CM3323_DRV_NAME "cm3323"
+
+#define CM3323_CMD_CONF		0x00
+#define CM3323_CMD_RED_DATA	0x08
+#define CM3323_CMD_GREEN_DATA	0x09
+#define CM3323_CMD_BLUE_DATA	0x0A
+#define CM3323_CMD_CLEAR_DATA	0x0B
+
+#define CM3323_CONF_SD_BIT	BIT(0) /* sensor disable */
+#define CM3323_CONF_AF_BIT	BIT(1) /* auto/manual force mode */
+#define CM3323_CONF_IT_MASK	GENMASK(6, 4)
+#define CM3323_CONF_IT_SHIFT	4
+
+#define CM3323_INT_TIME_AVAILABLE "0.04 0.08 0.16 0.32 0.64 1.28"
+
+static const struct {
+	int val;
+	int val2;
+} cm3323_int_time[] = {
+	{0, 40000},  /* 40 ms */
+	{0, 80000},  /* 80 ms */
+	{0, 160000}, /* 160 ms */
+	{0, 320000}, /* 320 ms */
+	{0, 640000}, /* 640 ms */
+	{1, 280000}, /* 1280 ms */
+};
+
+struct cm3323_data {
+	struct i2c_client *client;
+	u16 reg_conf;
+	struct mutex mutex;
+};
+
+#define CM3323_COLOR_CHANNEL(_color, _addr) { \
+	.type = IIO_INTENSITY, \
+	.modified = 1, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), \
+	.channel2 = IIO_MOD_LIGHT_##_color, \
+	.address = _addr, \
+}
+
+static const struct iio_chan_spec cm3323_channels[] = {
+	CM3323_COLOR_CHANNEL(RED, CM3323_CMD_RED_DATA),
+	CM3323_COLOR_CHANNEL(GREEN, CM3323_CMD_GREEN_DATA),
+	CM3323_COLOR_CHANNEL(BLUE, CM3323_CMD_BLUE_DATA),
+	CM3323_COLOR_CHANNEL(CLEAR, CM3323_CMD_CLEAR_DATA),
+};
+
+static IIO_CONST_ATTR_INT_TIME_AVAIL(CM3323_INT_TIME_AVAILABLE);
+
+static struct attribute *cm3323_attributes[] = {
+	&iio_const_attr_integration_time_available.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group cm3323_attribute_group = {
+	.attrs = cm3323_attributes,
+};
+
+static int cm3323_init(struct iio_dev *indio_dev)
+{
+	int ret;
+	struct cm3323_data *data = iio_priv(indio_dev);
+
+	ret = i2c_smbus_read_word_data(data->client, CM3323_CMD_CONF);
+	if (ret < 0) {
+		dev_err(&data->client->dev, "Error reading reg_conf\n");
+		return ret;
+	}
+
+	/* enable sensor and set auto force mode */
+	ret &= ~(CM3323_CONF_SD_BIT | CM3323_CONF_AF_BIT);
+
+	ret = i2c_smbus_write_word_data(data->client, CM3323_CMD_CONF, ret);
+	if (ret < 0) {
+		dev_err(&data->client->dev, "Error writing reg_conf\n");
+		return ret;
+	}
+
+	data->reg_conf = ret;
+
+	return 0;
+}
+
+static void cm3323_disable(struct iio_dev *indio_dev)
+{
+	int ret;
+	struct cm3323_data *data = iio_priv(indio_dev);
+
+	ret = i2c_smbus_write_word_data(data->client, CM3323_CMD_CONF,
+					CM3323_CONF_SD_BIT);
+	if (ret < 0)
+		dev_err(&data->client->dev, "Error writing reg_conf\n");
+}
+
+static int cm3323_set_it_bits(struct cm3323_data *data, int val, int val2)
+{
+	int i, ret;
+	u16 reg_conf;
+
+	for (i = 0; i < ARRAY_SIZE(cm3323_int_time); i++) {
+		if (val == cm3323_int_time[i].val &&
+		    val2 == cm3323_int_time[i].val2) {
+			reg_conf = data->reg_conf & ~CM3323_CONF_IT_MASK;
+			reg_conf |= i << CM3323_CONF_IT_SHIFT;
+
+			ret = i2c_smbus_write_word_data(data->client,
+							CM3323_CMD_CONF,
+							reg_conf);
+			if (ret < 0)
+				return ret;
+
+			data->reg_conf = reg_conf;
+
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int cm3323_get_it_bits(struct cm3323_data *data)
+{
+	int bits;
+
+	bits = (data->reg_conf & CM3323_CONF_IT_MASK) >>
+		CM3323_CONF_IT_SHIFT;
+
+	if (bits >= ARRAY_SIZE(cm3323_int_time))
+		return -EINVAL;
+
+	return bits;
+}
+
+static int cm3323_read_raw(struct iio_dev *indio_dev,
+			   struct iio_chan_spec const *chan, int *val,
+			   int *val2, long mask)
+{
+	int ret;
+	struct cm3323_data *data = iio_priv(indio_dev);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		mutex_lock(&data->mutex);
+		ret = i2c_smbus_read_word_data(data->client, chan->address);
+		if (ret < 0) {
+			mutex_unlock(&data->mutex);
+			return ret;
+		}
+		*val = ret;
+		mutex_unlock(&data->mutex);
+
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_INT_TIME:
+		mutex_lock(&data->mutex);
+		ret = cm3323_get_it_bits(data);
+		if (ret < 0) {
+			mutex_unlock(&data->mutex);
+			return ret;
+		}
+
+		*val = cm3323_int_time[ret].val;
+		*val2 = cm3323_int_time[ret].val2;
+		mutex_unlock(&data->mutex);
+
+		return IIO_VAL_INT_PLUS_MICRO;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int cm3323_write_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan, int val,
+			    int val2, long mask)
+{
+	struct cm3323_data *data = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_INT_TIME:
+		mutex_lock(&data->mutex);
+		ret = cm3323_set_it_bits(data, val, val2);
+		mutex_unlock(&data->mutex);
+
+		return ret;
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info cm3323_info = {
+	.read_raw	= cm3323_read_raw,
+	.write_raw	= cm3323_write_raw,
+	.attrs		= &cm3323_attribute_group,
+};
+
+static int cm3323_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct cm3323_data *data;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->client = client;
+
+	mutex_init(&data->mutex);
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->info = &cm3323_info;
+	indio_dev->name = CM3323_DRV_NAME;
+	indio_dev->channels = cm3323_channels;
+	indio_dev->num_channels = ARRAY_SIZE(cm3323_channels);
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	ret = cm3323_init(indio_dev);
+	if (ret < 0) {
+		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);
+	return ret;
+}
+
+static int cm3323_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+	iio_device_unregister(indio_dev);
+	cm3323_disable(indio_dev);
+
+	return 0;
+}
+
+static const struct i2c_device_id cm3323_id[] = {
+	{"cm3323", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, cm3323_id);
+
+static struct i2c_driver cm3323_driver = {
+	.driver = {
+		.name = CM3323_DRV_NAME,
+	},
+	.probe		= cm3323_probe,
+	.remove		= cm3323_remove,
+	.id_table	= cm3323_id,
+};
+
+module_i2c_driver(cm3323_driver);
+
+MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>");
+MODULE_DESCRIPTION("Capella CM3323 Color Light Sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/light/cm3605.c b/drivers/iio/light/cm3605.c
new file mode 100644
index 0000000..e454bc6
--- /dev/null
+++ b/drivers/iio/light/cm3605.c
@@ -0,0 +1,329 @@
+/*
+ * CM3605 Ambient Light and Proximity Sensor
+ *
+ * Copyright (C) 2016 Linaro Ltd.
+ * Author: Linus Walleij <linus.walleij@linaro.org>
+ *
+ * This hardware was found in the very first Nexus One handset from Google/HTC
+ * and an early endavour into mobile light and proximity sensors.
+ */
+
+#include <linux/module.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/events.h>
+#include <linux/iio/consumer.h> /* To get our ADC channel */
+#include <linux/iio/types.h> /* To deal with our ADC channel */
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/math64.h>
+#include <linux/pm.h>
+
+#define CM3605_PROX_CHANNEL 0
+#define CM3605_ALS_CHANNEL 1
+#define CM3605_AOUT_TYP_MAX_MV 1550
+/* It should not go above 1.650V according to the data sheet */
+#define CM3605_AOUT_MAX_MV 1650
+
+/**
+ * struct cm3605 - CM3605 state
+ * @dev: pointer to parent device
+ * @vdd: regulator controlling VDD
+ * @aset: sleep enable GPIO, high = sleep
+ * @aout: IIO ADC channel to convert the AOUT signal
+ * @als_max: maximum LUX detection (depends on RSET)
+ * @dir: proximity direction: start as FALLING
+ * @led: trigger for the infrared LED used by the proximity sensor
+ */
+struct cm3605 {
+	struct device *dev;
+	struct regulator *vdd;
+	struct gpio_desc *aset;
+	struct iio_channel *aout;
+	s32 als_max;
+	enum iio_event_direction dir;
+	struct led_trigger *led;
+};
+
+static irqreturn_t cm3605_prox_irq(int irq, void *d)
+{
+	struct iio_dev *indio_dev = d;
+	struct cm3605 *cm3605 = iio_priv(indio_dev);
+	u64 ev;
+
+	ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, CM3605_PROX_CHANNEL,
+				  IIO_EV_TYPE_THRESH, cm3605->dir);
+	iio_push_event(indio_dev, ev, iio_get_time_ns(indio_dev));
+
+	/* Invert the edge for each event */
+	if (cm3605->dir == IIO_EV_DIR_RISING)
+		cm3605->dir = IIO_EV_DIR_FALLING;
+	else
+		cm3605->dir = IIO_EV_DIR_RISING;
+
+	return IRQ_HANDLED;
+}
+
+static int cm3605_get_lux(struct cm3605 *cm3605)
+{
+	int ret, res;
+	s64 lux;
+
+	ret = iio_read_channel_processed(cm3605->aout, &res);
+	if (ret < 0)
+		return ret;
+
+	dev_dbg(cm3605->dev, "read %d mV from ADC\n", res);
+
+	/*
+	 * AOUT has an offset of ~30mV then linear at dark
+	 * then goes from 2.54 up to 650 LUX yielding 1.55V
+	 * (1550 mV) so scale the returned value to this interval
+	 * using simple linear interpolation.
+	 */
+	if (res < 30)
+		return 0;
+	if (res > CM3605_AOUT_MAX_MV)
+		dev_err(cm3605->dev, "device out of range\n");
+
+	/* Remove bias */
+	lux = res - 30;
+
+	/* Linear interpolation between 0 and ALS typ max */
+	lux *= cm3605->als_max;
+	lux = div64_s64(lux, CM3605_AOUT_TYP_MAX_MV);
+
+	return lux;
+}
+
+static int cm3605_read_raw(struct iio_dev *indio_dev,
+			   struct iio_chan_spec const *chan,
+			   int *val, int *val2, long mask)
+{
+	struct cm3605 *cm3605 = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		switch (chan->type) {
+		case IIO_LIGHT:
+			ret = cm3605_get_lux(cm3605);
+			if (ret < 0)
+				return ret;
+			*val = ret;
+			return IIO_VAL_INT;
+		default:
+			return -EINVAL;
+		}
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info cm3605_info = {
+	.read_raw = cm3605_read_raw,
+};
+
+static const struct iio_event_spec cm3605_events[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_EITHER,
+		.mask_separate = BIT(IIO_EV_INFO_ENABLE),
+	},
+};
+
+static const struct iio_chan_spec cm3605_channels[] = {
+	{
+		.type = IIO_PROXIMITY,
+		.event_spec = cm3605_events,
+		.num_event_specs = ARRAY_SIZE(cm3605_events),
+	},
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.channel = CM3605_ALS_CHANNEL,
+	},
+};
+
+static int cm3605_probe(struct platform_device *pdev)
+{
+	struct cm3605 *cm3605;
+	struct iio_dev *indio_dev;
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	enum iio_chan_type ch_type;
+	u32 rset;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*cm3605));
+	if (!indio_dev)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, indio_dev);
+
+	cm3605 = iio_priv(indio_dev);
+	cm3605->dev = dev;
+	cm3605->dir = IIO_EV_DIR_FALLING;
+
+	ret = of_property_read_u32(np, "capella,aset-resistance-ohms", &rset);
+	if (ret) {
+		dev_info(dev, "no RSET specified, assuming 100K\n");
+		rset = 100000;
+	}
+	switch (rset) {
+	case 50000:
+		cm3605->als_max = 650;
+		break;
+	case 100000:
+		cm3605->als_max = 300;
+		break;
+	case 300000:
+		cm3605->als_max = 100;
+		break;
+	case 600000:
+		cm3605->als_max = 50;
+		break;
+	default:
+		dev_info(dev, "non-standard resistance\n");
+		return -EINVAL;
+	}
+
+	cm3605->aout = devm_iio_channel_get(dev, "aout");
+	if (IS_ERR(cm3605->aout)) {
+		if (PTR_ERR(cm3605->aout) == -ENODEV) {
+			dev_err(dev, "no ADC, deferring...\n");
+			return -EPROBE_DEFER;
+		}
+		dev_err(dev, "failed to get AOUT ADC channel\n");
+		return PTR_ERR(cm3605->aout);
+	}
+	ret = iio_get_channel_type(cm3605->aout, &ch_type);
+	if (ret < 0)
+		return ret;
+	if (ch_type != IIO_VOLTAGE) {
+		dev_err(dev, "wrong type of IIO channel specified for AOUT\n");
+		return -EINVAL;
+	}
+
+	cm3605->vdd = devm_regulator_get(dev, "vdd");
+	if (IS_ERR(cm3605->vdd)) {
+		dev_err(dev, "failed to get VDD regulator\n");
+		return PTR_ERR(cm3605->vdd);
+	}
+	ret = regulator_enable(cm3605->vdd);
+	if (ret) {
+		dev_err(dev, "failed to enable VDD regulator\n");
+		return ret;
+	}
+
+	cm3605->aset = devm_gpiod_get(dev, "aset", GPIOD_OUT_HIGH);
+	if (IS_ERR(cm3605->aset)) {
+		dev_err(dev, "no ASET GPIO\n");
+		ret = PTR_ERR(cm3605->aset);
+		goto out_disable_vdd;
+	}
+
+	ret = devm_request_threaded_irq(dev, platform_get_irq(pdev, 0),
+			cm3605_prox_irq, NULL, 0, "cm3605", indio_dev);
+	if (ret) {
+		dev_err(dev, "unable to request IRQ\n");
+		goto out_disable_aset;
+	}
+
+	/* Just name the trigger the same as the driver */
+	led_trigger_register_simple("cm3605", &cm3605->led);
+	led_trigger_event(cm3605->led, LED_FULL);
+
+	indio_dev->dev.parent = dev;
+	indio_dev->info = &cm3605_info;
+	indio_dev->name = "cm3605";
+	indio_dev->channels = cm3605_channels;
+	indio_dev->num_channels = ARRAY_SIZE(cm3605_channels);
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	ret = iio_device_register(indio_dev);
+	if (ret)
+		goto out_remove_trigger;
+	dev_info(dev, "Capella Microsystems CM3605 enabled range 0..%d LUX\n",
+		 cm3605->als_max);
+
+	return 0;
+
+out_remove_trigger:
+	led_trigger_event(cm3605->led, LED_OFF);
+	led_trigger_unregister_simple(cm3605->led);
+out_disable_aset:
+	gpiod_set_value_cansleep(cm3605->aset, 0);
+out_disable_vdd:
+	regulator_disable(cm3605->vdd);
+	return ret;
+}
+
+static int cm3605_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+	struct cm3605 *cm3605 = iio_priv(indio_dev);
+
+	led_trigger_event(cm3605->led, LED_OFF);
+	led_trigger_unregister_simple(cm3605->led);
+	gpiod_set_value_cansleep(cm3605->aset, 0);
+	iio_device_unregister(indio_dev);
+	regulator_disable(cm3605->vdd);
+
+	return 0;
+}
+
+static int __maybe_unused cm3605_pm_suspend(struct device *dev)
+{
+	struct iio_dev *indio_dev = dev_get_drvdata(dev);
+	struct cm3605 *cm3605 = iio_priv(indio_dev);
+
+	led_trigger_event(cm3605->led, LED_OFF);
+	regulator_disable(cm3605->vdd);
+
+	return 0;
+}
+
+static int __maybe_unused cm3605_pm_resume(struct device *dev)
+{
+	struct iio_dev *indio_dev = dev_get_drvdata(dev);
+	struct cm3605 *cm3605 = iio_priv(indio_dev);
+	int ret;
+
+	ret = regulator_enable(cm3605->vdd);
+	if (ret)
+		dev_err(dev, "failed to enable regulator in resume path\n");
+	led_trigger_event(cm3605->led, LED_FULL);
+
+	return 0;
+}
+
+static const struct dev_pm_ops cm3605_dev_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(cm3605_pm_suspend,
+				cm3605_pm_resume)
+};
+
+static const struct of_device_id cm3605_of_match[] = {
+	{.compatible = "capella,cm3605"},
+	{ },
+};
+MODULE_DEVICE_TABLE(of, cm3605_of_match);
+
+static struct platform_driver cm3605_driver = {
+	.driver = {
+		.name = "cm3605",
+		.of_match_table = cm3605_of_match,
+		.pm = &cm3605_dev_pm_ops,
+	},
+	.probe = cm3605_probe,
+	.remove = cm3605_remove,
+};
+module_platform_driver(cm3605_driver);
+
+MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
+MODULE_DESCRIPTION("CM3605 ambient light and proximity sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/cm36651.c b/drivers/iio/light/cm36651.c
new file mode 100644
index 0000000..1dd8ed0
--- /dev/null
+++ b/drivers/iio/light/cm36651.c
@@ -0,0 +1,749 @@
+/*
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ * Author: Beomho Seo <beomho.seo@samsung.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.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/regulator/consumer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/events.h>
+
+/* Slave address 0x19 for PS of 7 bit addressing protocol for I2C */
+#define CM36651_I2C_ADDR_PS		0x19
+/* Alert Response Address */
+#define CM36651_ARA			0x0C
+
+/* Ambient light sensor */
+#define CM36651_CS_CONF1		0x00
+#define CM36651_CS_CONF2		0x01
+#define CM36651_ALS_WH_M		0x02
+#define CM36651_ALS_WH_L		0x03
+#define CM36651_ALS_WL_M		0x04
+#define CM36651_ALS_WL_L		0x05
+#define CM36651_CS_CONF3		0x06
+#define CM36651_CS_CONF_REG_NUM		0x02
+
+/* Proximity sensor */
+#define CM36651_PS_CONF1		0x00
+#define CM36651_PS_THD			0x01
+#define CM36651_PS_CANC			0x02
+#define CM36651_PS_CONF2		0x03
+#define CM36651_PS_REG_NUM		0x04
+
+/* CS_CONF1 command code */
+#define CM36651_ALS_ENABLE		0x00
+#define CM36651_ALS_DISABLE		0x01
+#define CM36651_ALS_INT_EN		0x02
+#define CM36651_ALS_THRES		0x04
+
+/* CS_CONF2 command code */
+#define CM36651_CS_CONF2_DEFAULT_BIT	0x08
+
+/* CS_CONF3 channel integration time */
+#define CM36651_CS_IT1			0x00 /* Integration time 80 msec */
+#define CM36651_CS_IT2			0x40 /* Integration time 160 msec */
+#define CM36651_CS_IT3			0x80 /* Integration time 320 msec */
+#define CM36651_CS_IT4			0xC0 /* Integration time 640 msec */
+
+/* PS_CONF1 command code */
+#define CM36651_PS_ENABLE		0x00
+#define CM36651_PS_DISABLE		0x01
+#define CM36651_PS_INT_EN		0x02
+#define CM36651_PS_PERS2		0x04
+#define CM36651_PS_PERS3		0x08
+#define CM36651_PS_PERS4		0x0C
+
+/* PS_CONF1 command code: integration time */
+#define CM36651_PS_IT1			0x00 /* Integration time 0.32 msec */
+#define CM36651_PS_IT2			0x10 /* Integration time 0.42 msec */
+#define CM36651_PS_IT3			0x20 /* Integration time 0.52 msec */
+#define CM36651_PS_IT4			0x30 /* Integration time 0.64 msec */
+
+/* PS_CONF1 command code: duty ratio */
+#define CM36651_PS_DR1			0x00 /* Duty ratio 1/80 */
+#define CM36651_PS_DR2			0x40 /* Duty ratio 1/160 */
+#define CM36651_PS_DR3			0x80 /* Duty ratio 1/320 */
+#define CM36651_PS_DR4			0xC0 /* Duty ratio 1/640 */
+
+/* PS_THD command code */
+#define CM36651_PS_INITIAL_THD		0x05
+
+/* PS_CANC command code */
+#define CM36651_PS_CANC_DEFAULT		0x00
+
+/* PS_CONF2 command code */
+#define CM36651_PS_HYS1			0x00
+#define CM36651_PS_HYS2			0x01
+#define CM36651_PS_SMART_PERS_EN	0x02
+#define CM36651_PS_DIR_INT		0x04
+#define CM36651_PS_MS			0x10
+
+#define CM36651_CS_COLOR_NUM		4
+
+#define CM36651_CLOSE_PROXIMITY		0x32
+#define CM36651_FAR_PROXIMITY			0x33
+
+#define CM36651_CS_INT_TIME_AVAIL	"0.08 0.16 0.32 0.64"
+#define CM36651_PS_INT_TIME_AVAIL	"0.000320 0.000420 0.000520 0.000640"
+
+enum cm36651_operation_mode {
+	CM36651_LIGHT_EN,
+	CM36651_PROXIMITY_EN,
+	CM36651_PROXIMITY_EV_EN,
+};
+
+enum cm36651_light_channel_idx {
+	CM36651_LIGHT_CHANNEL_IDX_RED,
+	CM36651_LIGHT_CHANNEL_IDX_GREEN,
+	CM36651_LIGHT_CHANNEL_IDX_BLUE,
+	CM36651_LIGHT_CHANNEL_IDX_CLEAR,
+};
+
+enum cm36651_command {
+	CM36651_CMD_READ_RAW_LIGHT,
+	CM36651_CMD_READ_RAW_PROXIMITY,
+	CM36651_CMD_PROX_EV_EN,
+	CM36651_CMD_PROX_EV_DIS,
+};
+
+static const u8 cm36651_cs_reg[CM36651_CS_CONF_REG_NUM] = {
+	CM36651_CS_CONF1,
+	CM36651_CS_CONF2,
+};
+
+static const u8 cm36651_ps_reg[CM36651_PS_REG_NUM] = {
+	CM36651_PS_CONF1,
+	CM36651_PS_THD,
+	CM36651_PS_CANC,
+	CM36651_PS_CONF2,
+};
+
+struct cm36651_data {
+	const struct cm36651_platform_data *pdata;
+	struct i2c_client *client;
+	struct i2c_client *ps_client;
+	struct i2c_client *ara_client;
+	struct mutex lock;
+	struct regulator *vled_reg;
+	unsigned long flags;
+	int cs_int_time[CM36651_CS_COLOR_NUM];
+	int ps_int_time;
+	u8 cs_ctrl_regs[CM36651_CS_CONF_REG_NUM];
+	u8 ps_ctrl_regs[CM36651_PS_REG_NUM];
+	u16 color[CM36651_CS_COLOR_NUM];
+};
+
+static int cm36651_setup_reg(struct cm36651_data *cm36651)
+{
+	struct i2c_client *client = cm36651->client;
+	struct i2c_client *ps_client = cm36651->ps_client;
+	int i, ret;
+
+	/* CS initialization */
+	cm36651->cs_ctrl_regs[CM36651_CS_CONF1] = CM36651_ALS_ENABLE |
+							     CM36651_ALS_THRES;
+	cm36651->cs_ctrl_regs[CM36651_CS_CONF2] = CM36651_CS_CONF2_DEFAULT_BIT;
+
+	for (i = 0; i < CM36651_CS_CONF_REG_NUM; i++) {
+		ret = i2c_smbus_write_byte_data(client, cm36651_cs_reg[i],
+						     cm36651->cs_ctrl_regs[i]);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* PS initialization */
+	cm36651->ps_ctrl_regs[CM36651_PS_CONF1] = CM36651_PS_ENABLE |
+								CM36651_PS_IT2;
+	cm36651->ps_ctrl_regs[CM36651_PS_THD] = CM36651_PS_INITIAL_THD;
+	cm36651->ps_ctrl_regs[CM36651_PS_CANC] = CM36651_PS_CANC_DEFAULT;
+	cm36651->ps_ctrl_regs[CM36651_PS_CONF2] = CM36651_PS_HYS2 |
+				CM36651_PS_DIR_INT | CM36651_PS_SMART_PERS_EN;
+
+	for (i = 0; i < CM36651_PS_REG_NUM; i++) {
+		ret = i2c_smbus_write_byte_data(ps_client, cm36651_ps_reg[i],
+						     cm36651->ps_ctrl_regs[i]);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* Set shutdown mode */
+	ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF1,
+							  CM36651_ALS_DISABLE);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_write_byte_data(cm36651->ps_client,
+					 CM36651_PS_CONF1, CM36651_PS_DISABLE);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int cm36651_read_output(struct cm36651_data *cm36651,
+				struct iio_chan_spec const *chan, int *val)
+{
+	struct i2c_client *client = cm36651->client;
+	int ret = -EINVAL;
+
+	switch (chan->type) {
+	case IIO_LIGHT:
+		*val = i2c_smbus_read_word_data(client, chan->address);
+		if (*val < 0)
+			return ret;
+
+		ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF1,
+							CM36651_ALS_DISABLE);
+		if (ret < 0)
+			return ret;
+
+		ret = IIO_VAL_INT;
+		break;
+	case IIO_PROXIMITY:
+		*val = i2c_smbus_read_byte(cm36651->ps_client);
+		if (*val < 0)
+			return ret;
+
+		if (!test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) {
+			ret = i2c_smbus_write_byte_data(cm36651->ps_client,
+					CM36651_PS_CONF1, CM36651_PS_DISABLE);
+			if (ret < 0)
+				return ret;
+		}
+
+		ret = IIO_VAL_INT;
+		break;
+	default:
+		break;
+	}
+
+	return ret;
+}
+
+static irqreturn_t cm36651_irq_handler(int irq, void *data)
+{
+	struct iio_dev *indio_dev = data;
+	struct cm36651_data *cm36651 = iio_priv(indio_dev);
+	struct i2c_client *client = cm36651->client;
+	int ev_dir, ret;
+	u64 ev_code;
+
+	/*
+	 * The PS INT pin is an active low signal that PS INT move logic low
+	 * when the object is detect. Once the MCU host received the PS INT
+	 * "LOW" signal, the Host needs to read the data at Alert Response
+	 * Address(ARA) to clear the PS INT signal. After clearing the PS
+	 * INT pin, the PS INT signal toggles from low to high.
+	 */
+	ret = i2c_smbus_read_byte(cm36651->ara_client);
+	if (ret < 0) {
+		dev_err(&client->dev,
+				"%s: Data read failed: %d\n", __func__, ret);
+		return IRQ_HANDLED;
+	}
+	switch (ret) {
+	case CM36651_CLOSE_PROXIMITY:
+		ev_dir = IIO_EV_DIR_RISING;
+		break;
+	case CM36651_FAR_PROXIMITY:
+		ev_dir = IIO_EV_DIR_FALLING;
+		break;
+	default:
+		dev_err(&client->dev,
+			"%s: Data read wrong: %d\n", __func__, ret);
+		return IRQ_HANDLED;
+	}
+
+	ev_code = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY,
+				CM36651_CMD_READ_RAW_PROXIMITY,
+				IIO_EV_TYPE_THRESH, ev_dir);
+
+	iio_push_event(indio_dev, ev_code, iio_get_time_ns(indio_dev));
+
+	return IRQ_HANDLED;
+}
+
+static int cm36651_set_operation_mode(struct cm36651_data *cm36651, int cmd)
+{
+	struct i2c_client *client = cm36651->client;
+	struct i2c_client *ps_client = cm36651->ps_client;
+	int ret = -EINVAL;
+
+	switch (cmd) {
+	case CM36651_CMD_READ_RAW_LIGHT:
+		ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF1,
+				cm36651->cs_ctrl_regs[CM36651_CS_CONF1]);
+		break;
+	case CM36651_CMD_READ_RAW_PROXIMITY:
+		if (test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags))
+			return CM36651_PROXIMITY_EV_EN;
+
+		ret = i2c_smbus_write_byte_data(ps_client, CM36651_PS_CONF1,
+				cm36651->ps_ctrl_regs[CM36651_PS_CONF1]);
+		break;
+	case CM36651_CMD_PROX_EV_EN:
+		if (test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) {
+			dev_err(&client->dev,
+				"Already proximity event enable state\n");
+			return ret;
+		}
+		set_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags);
+
+		ret = i2c_smbus_write_byte_data(ps_client,
+			cm36651_ps_reg[CM36651_PS_CONF1],
+			CM36651_PS_INT_EN | CM36651_PS_PERS2 | CM36651_PS_IT2);
+
+		if (ret < 0) {
+			dev_err(&client->dev, "Proximity enable event failed\n");
+			return ret;
+		}
+		break;
+	case CM36651_CMD_PROX_EV_DIS:
+		if (!test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) {
+			dev_err(&client->dev,
+				"Already proximity event disable state\n");
+			return ret;
+		}
+		clear_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags);
+		ret = i2c_smbus_write_byte_data(ps_client,
+					CM36651_PS_CONF1, CM36651_PS_DISABLE);
+		break;
+	}
+
+	if (ret < 0)
+		dev_err(&client->dev, "Write register failed\n");
+
+	return ret;
+}
+
+static int cm36651_read_channel(struct cm36651_data *cm36651,
+				struct iio_chan_spec const *chan, int *val)
+{
+	struct i2c_client *client = cm36651->client;
+	int cmd, ret;
+
+	if (chan->type == IIO_LIGHT)
+		cmd = CM36651_CMD_READ_RAW_LIGHT;
+	else if (chan->type == IIO_PROXIMITY)
+		cmd = CM36651_CMD_READ_RAW_PROXIMITY;
+	else
+		return -EINVAL;
+
+	ret = cm36651_set_operation_mode(cm36651, cmd);
+	if (ret < 0) {
+		dev_err(&client->dev, "CM36651 set operation mode failed\n");
+		return ret;
+	}
+	/* Delay for work after enable operation */
+	msleep(50);
+	ret = cm36651_read_output(cm36651, chan, val);
+	if (ret < 0) {
+		dev_err(&client->dev, "CM36651 read output failed\n");
+		return ret;
+	}
+
+	return ret;
+}
+
+static int cm36651_read_int_time(struct cm36651_data *cm36651,
+				struct iio_chan_spec const *chan, int *val2)
+{
+	switch (chan->type) {
+	case IIO_LIGHT:
+		if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT1)
+			*val2 = 80000;
+		else if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT2)
+			*val2 = 160000;
+		else if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT3)
+			*val2 = 320000;
+		else if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT4)
+			*val2 = 640000;
+		else
+			return -EINVAL;
+		break;
+	case IIO_PROXIMITY:
+		if (cm36651->ps_int_time == CM36651_PS_IT1)
+			*val2 = 320;
+		else if (cm36651->ps_int_time == CM36651_PS_IT2)
+			*val2 = 420;
+		else if (cm36651->ps_int_time == CM36651_PS_IT3)
+			*val2 = 520;
+		else if (cm36651->ps_int_time == CM36651_PS_IT4)
+			*val2 = 640;
+		else
+			return -EINVAL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int cm36651_write_int_time(struct cm36651_data *cm36651,
+				struct iio_chan_spec const *chan, int val)
+{
+	struct i2c_client *client = cm36651->client;
+	struct i2c_client *ps_client = cm36651->ps_client;
+	int int_time, ret;
+
+	switch (chan->type) {
+	case IIO_LIGHT:
+		if (val == 80000)
+			int_time = CM36651_CS_IT1;
+		else if (val == 160000)
+			int_time = CM36651_CS_IT2;
+		else if (val == 320000)
+			int_time = CM36651_CS_IT3;
+		else if (val == 640000)
+			int_time = CM36651_CS_IT4;
+		else
+			return -EINVAL;
+
+		ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF3,
+					   int_time >> 2 * (chan->address));
+		if (ret < 0) {
+			dev_err(&client->dev, "CS integration time write failed\n");
+			return ret;
+		}
+		cm36651->cs_int_time[chan->address] = int_time;
+		break;
+	case IIO_PROXIMITY:
+		if (val == 320)
+			int_time = CM36651_PS_IT1;
+		else if (val == 420)
+			int_time = CM36651_PS_IT2;
+		else if (val == 520)
+			int_time = CM36651_PS_IT3;
+		else if (val == 640)
+			int_time = CM36651_PS_IT4;
+		else
+			return -EINVAL;
+
+		ret = i2c_smbus_write_byte_data(ps_client,
+						CM36651_PS_CONF1, int_time);
+		if (ret < 0) {
+			dev_err(&client->dev, "PS integration time write failed\n");
+			return ret;
+		}
+		cm36651->ps_int_time = int_time;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static int cm36651_read_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct cm36651_data *cm36651 = iio_priv(indio_dev);
+	int ret;
+
+	mutex_lock(&cm36651->lock);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = cm36651_read_channel(cm36651, chan, val);
+		break;
+	case IIO_CHAN_INFO_INT_TIME:
+		*val = 0;
+		ret = cm36651_read_int_time(cm36651, chan, val2);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	mutex_unlock(&cm36651->lock);
+
+	return ret;
+}
+
+static int cm36651_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int val, int val2, long mask)
+{
+	struct cm36651_data *cm36651 = iio_priv(indio_dev);
+	struct i2c_client *client = cm36651->client;
+	int ret = -EINVAL;
+
+	if (mask == IIO_CHAN_INFO_INT_TIME) {
+		ret = cm36651_write_int_time(cm36651, chan, val2);
+		if (ret < 0)
+			dev_err(&client->dev, "Integration time write failed\n");
+	}
+
+	return ret;
+}
+
+static int cm36651_read_prox_thresh(struct iio_dev *indio_dev,
+					const struct iio_chan_spec *chan,
+					enum iio_event_type type,
+					enum iio_event_direction dir,
+					enum iio_event_info info,
+					int *val, int *val2)
+{
+	struct cm36651_data *cm36651 = iio_priv(indio_dev);
+
+	*val = cm36651->ps_ctrl_regs[CM36651_PS_THD];
+
+	return 0;
+}
+
+static int cm36651_write_prox_thresh(struct iio_dev *indio_dev,
+					const struct iio_chan_spec *chan,
+					enum iio_event_type type,
+					enum iio_event_direction dir,
+					enum iio_event_info info,
+					int val, int val2)
+{
+	struct cm36651_data *cm36651 = iio_priv(indio_dev);
+	struct i2c_client *client = cm36651->client;
+	int ret;
+
+	if (val < 3 || val > 255)
+		return -EINVAL;
+
+	cm36651->ps_ctrl_regs[CM36651_PS_THD] = val;
+	ret = i2c_smbus_write_byte_data(cm36651->ps_client, CM36651_PS_THD,
+					cm36651->ps_ctrl_regs[CM36651_PS_THD]);
+
+	if (ret < 0) {
+		dev_err(&client->dev, "PS threshold write failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int cm36651_write_prox_event_config(struct iio_dev *indio_dev,
+					const struct iio_chan_spec *chan,
+					enum iio_event_type type,
+					enum iio_event_direction dir,
+					int state)
+{
+	struct cm36651_data *cm36651 = iio_priv(indio_dev);
+	int cmd, ret = -EINVAL;
+
+	mutex_lock(&cm36651->lock);
+
+	cmd = state ? CM36651_CMD_PROX_EV_EN : CM36651_CMD_PROX_EV_DIS;
+	ret = cm36651_set_operation_mode(cm36651, cmd);
+
+	mutex_unlock(&cm36651->lock);
+
+	return ret;
+}
+
+static int cm36651_read_prox_event_config(struct iio_dev *indio_dev,
+					const struct iio_chan_spec *chan,
+					enum iio_event_type type,
+					enum iio_event_direction dir)
+{
+	struct cm36651_data *cm36651 = iio_priv(indio_dev);
+	int event_en;
+
+	mutex_lock(&cm36651->lock);
+
+	event_en = test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags);
+
+	mutex_unlock(&cm36651->lock);
+
+	return event_en;
+}
+
+#define CM36651_LIGHT_CHANNEL(_color, _idx) {		\
+	.type = IIO_LIGHT,				\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |	\
+			BIT(IIO_CHAN_INFO_INT_TIME),	\
+	.address = _idx,				\
+	.modified = 1,					\
+	.channel2 = IIO_MOD_LIGHT_##_color,		\
+}							\
+
+static const struct iio_event_spec cm36651_event_spec[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_EITHER,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+				BIT(IIO_EV_INFO_ENABLE),
+	}
+};
+
+static const struct iio_chan_spec cm36651_channels[] = {
+	{
+		.type = IIO_PROXIMITY,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				BIT(IIO_CHAN_INFO_INT_TIME),
+		.event_spec = cm36651_event_spec,
+		.num_event_specs = ARRAY_SIZE(cm36651_event_spec),
+	},
+	CM36651_LIGHT_CHANNEL(RED, CM36651_LIGHT_CHANNEL_IDX_RED),
+	CM36651_LIGHT_CHANNEL(GREEN, CM36651_LIGHT_CHANNEL_IDX_GREEN),
+	CM36651_LIGHT_CHANNEL(BLUE, CM36651_LIGHT_CHANNEL_IDX_BLUE),
+	CM36651_LIGHT_CHANNEL(CLEAR, CM36651_LIGHT_CHANNEL_IDX_CLEAR),
+};
+
+static IIO_CONST_ATTR(in_illuminance_integration_time_available,
+					CM36651_CS_INT_TIME_AVAIL);
+static IIO_CONST_ATTR(in_proximity_integration_time_available,
+					CM36651_PS_INT_TIME_AVAIL);
+
+static struct attribute *cm36651_attributes[] = {
+	&iio_const_attr_in_illuminance_integration_time_available.dev_attr.attr,
+	&iio_const_attr_in_proximity_integration_time_available.dev_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group cm36651_attribute_group = {
+	.attrs = cm36651_attributes
+};
+
+static const struct iio_info cm36651_info = {
+	.read_raw		= &cm36651_read_raw,
+	.write_raw		= &cm36651_write_raw,
+	.read_event_value	= &cm36651_read_prox_thresh,
+	.write_event_value	= &cm36651_write_prox_thresh,
+	.read_event_config	= &cm36651_read_prox_event_config,
+	.write_event_config	= &cm36651_write_prox_event_config,
+	.attrs			= &cm36651_attribute_group,
+};
+
+static int cm36651_probe(struct i2c_client *client,
+			     const struct i2c_device_id *id)
+{
+	struct cm36651_data *cm36651;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*cm36651));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	cm36651 = iio_priv(indio_dev);
+
+	cm36651->vled_reg = devm_regulator_get(&client->dev, "vled");
+	if (IS_ERR(cm36651->vled_reg)) {
+		dev_err(&client->dev, "get regulator vled failed\n");
+		return PTR_ERR(cm36651->vled_reg);
+	}
+
+	ret = regulator_enable(cm36651->vled_reg);
+	if (ret) {
+		dev_err(&client->dev, "enable regulator vled failed\n");
+		return ret;
+	}
+
+	i2c_set_clientdata(client, indio_dev);
+
+	cm36651->client = client;
+	cm36651->ps_client = i2c_new_dummy(client->adapter,
+						     CM36651_I2C_ADDR_PS);
+	if (!cm36651->ps_client) {
+		dev_err(&client->dev, "%s: new i2c device failed\n", __func__);
+		ret = -ENODEV;
+		goto error_disable_reg;
+	}
+
+	cm36651->ara_client = i2c_new_dummy(client->adapter, CM36651_ARA);
+	if (!cm36651->ara_client) {
+		dev_err(&client->dev, "%s: new i2c device failed\n", __func__);
+		ret = -ENODEV;
+		goto error_i2c_unregister_ps;
+	}
+
+	mutex_init(&cm36651->lock);
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->channels = cm36651_channels;
+	indio_dev->num_channels = ARRAY_SIZE(cm36651_channels);
+	indio_dev->info = &cm36651_info;
+	indio_dev->name = id->name;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	ret = cm36651_setup_reg(cm36651);
+	if (ret) {
+		dev_err(&client->dev, "%s: register setup failed\n", __func__);
+		goto error_i2c_unregister_ara;
+	}
+
+	ret = request_threaded_irq(client->irq, NULL, cm36651_irq_handler,
+					IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+							"cm36651", indio_dev);
+	if (ret) {
+		dev_err(&client->dev, "%s: request irq failed\n", __func__);
+		goto error_i2c_unregister_ara;
+	}
+
+	ret = iio_device_register(indio_dev);
+	if (ret) {
+		dev_err(&client->dev, "%s: regist device failed\n", __func__);
+		goto error_free_irq;
+	}
+
+	return 0;
+
+error_free_irq:
+	free_irq(client->irq, indio_dev);
+error_i2c_unregister_ara:
+	i2c_unregister_device(cm36651->ara_client);
+error_i2c_unregister_ps:
+	i2c_unregister_device(cm36651->ps_client);
+error_disable_reg:
+	regulator_disable(cm36651->vled_reg);
+	return ret;
+}
+
+static int cm36651_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+	struct cm36651_data *cm36651 = iio_priv(indio_dev);
+
+	iio_device_unregister(indio_dev);
+	regulator_disable(cm36651->vled_reg);
+	free_irq(client->irq, indio_dev);
+	i2c_unregister_device(cm36651->ps_client);
+	i2c_unregister_device(cm36651->ara_client);
+
+	return 0;
+}
+
+static const struct i2c_device_id cm36651_id[] = {
+	{ "cm36651", 0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, cm36651_id);
+
+static const struct of_device_id cm36651_of_match[] = {
+	{ .compatible = "capella,cm36651" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, cm36651_of_match);
+
+static struct i2c_driver cm36651_driver = {
+	.driver = {
+		.name	= "cm36651",
+		.of_match_table = cm36651_of_match,
+	},
+	.probe		= cm36651_probe,
+	.remove		= cm36651_remove,
+	.id_table	= cm36651_id,
+};
+
+module_i2c_driver(cm36651_driver);
+
+MODULE_AUTHOR("Beomho Seo <beomho.seo@samsung.com>");
+MODULE_DESCRIPTION("CM36651 proximity/ambient light sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/light/cros_ec_light_prox.c b/drivers/iio/light/cros_ec_light_prox.c
new file mode 100644
index 0000000..fd1609e
--- /dev/null
+++ b/drivers/iio/light/cros_ec_light_prox.c
@@ -0,0 +1,286 @@
+/*
+ * cros_ec_light_prox - Driver for light and prox sensors behing CrosEC.
+ *
+ * Copyright (C) 2017 Google, Inc
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/delay.h>
+#include <linux/device.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/common/cros_ec_sensors_core.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/kfifo_buf.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/kernel.h>
+#include <linux/mfd/cros_ec.h>
+#include <linux/mfd/cros_ec_commands.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+
+/*
+ * We only represent one entry for light or proximity. EC is merging different
+ * light sensors to return the what the eye would see. For proximity, we
+ * currently support only one light source.
+ */
+#define CROS_EC_LIGHT_PROX_MAX_CHANNELS (1 + 1)
+
+/* State data for ec_sensors iio driver. */
+struct cros_ec_light_prox_state {
+	/* Shared by all sensors */
+	struct cros_ec_sensors_core_state core;
+
+	struct iio_chan_spec channels[CROS_EC_LIGHT_PROX_MAX_CHANNELS];
+};
+
+static int cros_ec_light_prox_read(struct iio_dev *indio_dev,
+				   struct iio_chan_spec const *chan,
+				   int *val, int *val2, long mask)
+{
+	struct cros_ec_light_prox_state *st = iio_priv(indio_dev);
+	u16 data = 0;
+	s64 val64;
+	int ret = IIO_VAL_INT;
+	int idx = chan->scan_index;
+
+	mutex_lock(&st->core.cmd_lock);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		if (chan->type == IIO_PROXIMITY) {
+			if (cros_ec_sensors_read_cmd(indio_dev, 1 << idx,
+						     (s16 *)&data) < 0) {
+				ret = -EIO;
+				break;
+			}
+			*val = data;
+		} else {
+			ret = -EINVAL;
+		}
+		break;
+	case IIO_CHAN_INFO_PROCESSED:
+		if (chan->type == IIO_LIGHT) {
+			if (cros_ec_sensors_read_cmd(indio_dev, 1 << idx,
+						     (s16 *)&data) < 0) {
+				ret = -EIO;
+				break;
+			}
+			/*
+			 * The data coming from the light sensor is
+			 * pre-processed and represents the ambient light
+			 * illuminance reading expressed in lux.
+			 */
+			*val = data;
+			ret = IIO_VAL_INT;
+		} else {
+			ret = -EINVAL;
+		}
+		break;
+	case IIO_CHAN_INFO_CALIBBIAS:
+		st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_OFFSET;
+		st->core.param.sensor_offset.flags = 0;
+
+		if (cros_ec_motion_send_host_cmd(&st->core, 0)) {
+			ret = -EIO;
+			break;
+		}
+
+		/* Save values */
+		st->core.calib[0] = st->core.resp->sensor_offset.offset[0];
+
+		*val = st->core.calib[idx];
+		break;
+	case IIO_CHAN_INFO_CALIBSCALE:
+		/*
+		 * RANGE is used for calibration
+		 * scale is a number x.y, where x is coded on 16 bits,
+		 * y coded on 16 bits, between 0 and 9999.
+		 */
+		st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE;
+		st->core.param.sensor_range.data = EC_MOTION_SENSE_NO_VALUE;
+
+		if (cros_ec_motion_send_host_cmd(&st->core, 0)) {
+			ret = -EIO;
+			break;
+		}
+
+		val64 = st->core.resp->sensor_range.ret;
+		*val = val64 >> 16;
+		*val2 = (val64 & 0xffff) * 100;
+		ret = IIO_VAL_INT_PLUS_MICRO;
+		break;
+	default:
+		ret = cros_ec_sensors_core_read(&st->core, chan, val, val2,
+						mask);
+		break;
+	}
+
+	mutex_unlock(&st->core.cmd_lock);
+
+	return ret;
+}
+
+static int cros_ec_light_prox_write(struct iio_dev *indio_dev,
+			       struct iio_chan_spec const *chan,
+			       int val, int val2, long mask)
+{
+	struct cros_ec_light_prox_state *st = iio_priv(indio_dev);
+	int ret = 0;
+	int idx = chan->scan_index;
+
+	mutex_lock(&st->core.cmd_lock);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_CALIBBIAS:
+		st->core.calib[idx] = val;
+		/* Send to EC for each axis, even if not complete */
+		st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_OFFSET;
+		st->core.param.sensor_offset.flags = MOTION_SENSE_SET_OFFSET;
+		st->core.param.sensor_offset.offset[0] = st->core.calib[0];
+		st->core.param.sensor_offset.temp =
+					EC_MOTION_SENSE_INVALID_CALIB_TEMP;
+		if (cros_ec_motion_send_host_cmd(&st->core, 0))
+			ret = -EIO;
+		break;
+	case IIO_CHAN_INFO_CALIBSCALE:
+		st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE;
+		st->core.param.sensor_range.data = (val << 16) | (val2 / 100);
+		if (cros_ec_motion_send_host_cmd(&st->core, 0))
+			ret = -EIO;
+		break;
+	default:
+		ret = cros_ec_sensors_core_write(&st->core, chan, val, val2,
+						 mask);
+		break;
+	}
+
+	mutex_unlock(&st->core.cmd_lock);
+
+	return ret;
+}
+
+static const struct iio_info cros_ec_light_prox_info = {
+	.read_raw = &cros_ec_light_prox_read,
+	.write_raw = &cros_ec_light_prox_write,
+};
+
+static int cros_ec_light_prox_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
+	struct iio_dev *indio_dev;
+	struct cros_ec_light_prox_state *state;
+	struct iio_chan_spec *channel;
+	int ret;
+
+	if (!ec_dev || !ec_dev->ec_dev) {
+		dev_warn(dev, "No CROS EC device found.\n");
+		return -EINVAL;
+	}
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*state));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	ret = cros_ec_sensors_core_init(pdev, indio_dev, true);
+	if (ret)
+		return ret;
+
+	indio_dev->info = &cros_ec_light_prox_info;
+	state = iio_priv(indio_dev);
+	state->core.type = state->core.resp->info.type;
+	state->core.loc = state->core.resp->info.location;
+	channel = state->channels;
+
+	/* Common part */
+	channel->info_mask_shared_by_all =
+		BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+		BIT(IIO_CHAN_INFO_FREQUENCY);
+	channel->scan_type.realbits = CROS_EC_SENSOR_BITS;
+	channel->scan_type.storagebits = CROS_EC_SENSOR_BITS;
+	channel->scan_type.shift = 0;
+	channel->scan_index = 0;
+	channel->ext_info = cros_ec_sensors_ext_info;
+	channel->scan_type.sign = 'u';
+
+	state->core.calib[0] = 0;
+
+	/* Sensor specific */
+	switch (state->core.type) {
+	case MOTIONSENSE_TYPE_LIGHT:
+		channel->type = IIO_LIGHT;
+		channel->info_mask_separate =
+			BIT(IIO_CHAN_INFO_PROCESSED) |
+			BIT(IIO_CHAN_INFO_CALIBBIAS) |
+			BIT(IIO_CHAN_INFO_CALIBSCALE);
+		break;
+	case MOTIONSENSE_TYPE_PROX:
+		channel->type = IIO_PROXIMITY;
+		channel->info_mask_separate =
+			BIT(IIO_CHAN_INFO_RAW) |
+			BIT(IIO_CHAN_INFO_CALIBBIAS) |
+			BIT(IIO_CHAN_INFO_CALIBSCALE);
+		break;
+	default:
+		dev_warn(dev, "Unknown motion sensor\n");
+		return -EINVAL;
+	}
+
+	/* Timestamp */
+	channel++;
+	channel->type = IIO_TIMESTAMP;
+	channel->channel = -1;
+	channel->scan_index = 1;
+	channel->scan_type.sign = 's';
+	channel->scan_type.realbits = 64;
+	channel->scan_type.storagebits = 64;
+
+	indio_dev->channels = state->channels;
+
+	indio_dev->num_channels = CROS_EC_LIGHT_PROX_MAX_CHANNELS;
+
+	state->core.read_ec_sensors_data = cros_ec_sensors_read_cmd;
+
+	ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL,
+					      cros_ec_sensors_capture, NULL);
+	if (ret)
+		return ret;
+
+	return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct platform_device_id cros_ec_light_prox_ids[] = {
+	{
+		.name = "cros-ec-prox",
+	},
+	{
+		.name = "cros-ec-light",
+	},
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, cros_ec_light_prox_ids);
+
+static struct platform_driver cros_ec_light_prox_platform_driver = {
+	.driver = {
+		.name	= "cros-ec-light-prox",
+		.pm	= &cros_ec_sensors_pm_ops,
+	},
+	.probe		= cros_ec_light_prox_probe,
+	.id_table	= cros_ec_light_prox_ids,
+};
+module_platform_driver(cros_ec_light_prox_platform_driver);
+
+MODULE_DESCRIPTION("ChromeOS EC light/proximity sensors driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/light/gp2ap020a00f.c b/drivers/iio/light/gp2ap020a00f.c
new file mode 100644
index 0000000..44b13fb
--- /dev/null
+++ b/drivers/iio/light/gp2ap020a00f.c
@@ -0,0 +1,1644 @@
+/*
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ * Author: Jacek Anaszewski <j.anaszewski@samsung.com>
+ *
+ * IIO features supported by the driver:
+ *
+ * Read-only raw channels:
+ *   - illuminance_clear [lux]
+ *   - illuminance_ir
+ *   - proximity
+ *
+ * Triggered buffer:
+ *   - illuminance_clear
+ *   - illuminance_ir
+ *   - proximity
+ *
+ * Events:
+ *   - illuminance_clear (rising and falling)
+ *   - proximity (rising and falling)
+ *     - both falling and rising thresholds for the proximity events
+ *       must be set to the values greater than 0.
+ *
+ * The driver supports triggered buffers for all the three
+ * channels as well as high and low threshold events for the
+ * illuminance_clear and proxmimity channels. Triggers
+ * can be enabled simultaneously with both illuminance_clear
+ * events. Proximity events cannot be enabled simultaneously
+ * with any triggers or illuminance events. Enabling/disabling
+ * one of the proximity events automatically enables/disables
+ * the other one.
+ *
+ * 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.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irq_work.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#define GP2A_I2C_NAME "gp2ap020a00f"
+
+/* Registers */
+#define GP2AP020A00F_OP_REG	0x00 /* Basic operations */
+#define GP2AP020A00F_ALS_REG	0x01 /* ALS related settings */
+#define GP2AP020A00F_PS_REG	0x02 /* PS related settings */
+#define GP2AP020A00F_LED_REG	0x03 /* LED reg */
+#define GP2AP020A00F_TL_L_REG	0x04 /* ALS: Threshold low LSB */
+#define GP2AP020A00F_TL_H_REG	0x05 /* ALS: Threshold low MSB */
+#define GP2AP020A00F_TH_L_REG	0x06 /* ALS: Threshold high LSB */
+#define GP2AP020A00F_TH_H_REG	0x07 /* ALS: Threshold high MSB */
+#define GP2AP020A00F_PL_L_REG	0x08 /* PS: Threshold low LSB */
+#define GP2AP020A00F_PL_H_REG	0x09 /* PS: Threshold low MSB */
+#define GP2AP020A00F_PH_L_REG	0x0a /* PS: Threshold high LSB */
+#define GP2AP020A00F_PH_H_REG	0x0b /* PS: Threshold high MSB */
+#define GP2AP020A00F_D0_L_REG	0x0c /* ALS result: Clear/Illuminance LSB */
+#define GP2AP020A00F_D0_H_REG	0x0d /* ALS result: Clear/Illuminance MSB */
+#define GP2AP020A00F_D1_L_REG	0x0e /* ALS result: IR LSB */
+#define GP2AP020A00F_D1_H_REG	0x0f /* ALS result: IR LSB */
+#define GP2AP020A00F_D2_L_REG	0x10 /* PS result LSB */
+#define GP2AP020A00F_D2_H_REG	0x11 /* PS result MSB */
+#define GP2AP020A00F_NUM_REGS	0x12 /* Number of registers */
+
+/* OP_REG bits */
+#define GP2AP020A00F_OP3_MASK		0x80 /* Software shutdown */
+#define GP2AP020A00F_OP3_SHUTDOWN	0x00
+#define GP2AP020A00F_OP3_OPERATION	0x80
+#define GP2AP020A00F_OP2_MASK		0x40 /* Auto shutdown/Continuous mode */
+#define GP2AP020A00F_OP2_AUTO_SHUTDOWN	0x00
+#define GP2AP020A00F_OP2_CONT_OPERATION	0x40
+#define GP2AP020A00F_OP_MASK		0x30 /* Operating mode selection  */
+#define GP2AP020A00F_OP_ALS_AND_PS	0x00
+#define GP2AP020A00F_OP_ALS		0x10
+#define GP2AP020A00F_OP_PS		0x20
+#define GP2AP020A00F_OP_DEBUG		0x30
+#define GP2AP020A00F_PROX_MASK		0x08 /* PS: detection/non-detection */
+#define GP2AP020A00F_PROX_NON_DETECT	0x00
+#define GP2AP020A00F_PROX_DETECT	0x08
+#define GP2AP020A00F_FLAG_P		0x04 /* PS: interrupt result  */
+#define GP2AP020A00F_FLAG_A		0x02 /* ALS: interrupt result  */
+#define GP2AP020A00F_TYPE_MASK		0x01 /* Output data type selection */
+#define GP2AP020A00F_TYPE_MANUAL_CALC	0x00
+#define GP2AP020A00F_TYPE_AUTO_CALC	0x01
+
+/* ALS_REG bits */
+#define GP2AP020A00F_PRST_MASK		0xc0 /* Number of measurement cycles */
+#define GP2AP020A00F_PRST_ONCE		0x00
+#define GP2AP020A00F_PRST_4_CYCLES	0x40
+#define GP2AP020A00F_PRST_8_CYCLES	0x80
+#define GP2AP020A00F_PRST_16_CYCLES	0xc0
+#define GP2AP020A00F_RES_A_MASK		0x38 /* ALS: Resolution */
+#define GP2AP020A00F_RES_A_800ms	0x00
+#define GP2AP020A00F_RES_A_400ms	0x08
+#define GP2AP020A00F_RES_A_200ms	0x10
+#define GP2AP020A00F_RES_A_100ms	0x18
+#define GP2AP020A00F_RES_A_25ms		0x20
+#define GP2AP020A00F_RES_A_6_25ms	0x28
+#define GP2AP020A00F_RES_A_1_56ms	0x30
+#define GP2AP020A00F_RES_A_0_39ms	0x38
+#define GP2AP020A00F_RANGE_A_MASK	0x07 /* ALS: Max measurable range */
+#define GP2AP020A00F_RANGE_A_x1		0x00
+#define GP2AP020A00F_RANGE_A_x2		0x01
+#define GP2AP020A00F_RANGE_A_x4		0x02
+#define GP2AP020A00F_RANGE_A_x8		0x03
+#define GP2AP020A00F_RANGE_A_x16	0x04
+#define GP2AP020A00F_RANGE_A_x32	0x05
+#define GP2AP020A00F_RANGE_A_x64	0x06
+#define GP2AP020A00F_RANGE_A_x128	0x07
+
+/* PS_REG bits */
+#define GP2AP020A00F_ALC_MASK		0x80 /* Auto light cancel */
+#define GP2AP020A00F_ALC_ON		0x80
+#define GP2AP020A00F_ALC_OFF		0x00
+#define GP2AP020A00F_INTTYPE_MASK	0x40 /* Interrupt type setting */
+#define GP2AP020A00F_INTTYPE_LEVEL	0x00
+#define GP2AP020A00F_INTTYPE_PULSE	0x40
+#define GP2AP020A00F_RES_P_MASK		0x38 /* PS: Resolution */
+#define GP2AP020A00F_RES_P_800ms_x2	0x00
+#define GP2AP020A00F_RES_P_400ms_x2	0x08
+#define GP2AP020A00F_RES_P_200ms_x2	0x10
+#define GP2AP020A00F_RES_P_100ms_x2	0x18
+#define GP2AP020A00F_RES_P_25ms_x2	0x20
+#define GP2AP020A00F_RES_P_6_25ms_x2	0x28
+#define GP2AP020A00F_RES_P_1_56ms_x2	0x30
+#define GP2AP020A00F_RES_P_0_39ms_x2	0x38
+#define GP2AP020A00F_RANGE_P_MASK	0x07 /* PS: Max measurable range */
+#define GP2AP020A00F_RANGE_P_x1		0x00
+#define GP2AP020A00F_RANGE_P_x2		0x01
+#define GP2AP020A00F_RANGE_P_x4		0x02
+#define GP2AP020A00F_RANGE_P_x8		0x03
+#define GP2AP020A00F_RANGE_P_x16	0x04
+#define GP2AP020A00F_RANGE_P_x32	0x05
+#define GP2AP020A00F_RANGE_P_x64	0x06
+#define GP2AP020A00F_RANGE_P_x128	0x07
+
+/* LED reg bits */
+#define GP2AP020A00F_INTVAL_MASK	0xc0 /* Intermittent operating */
+#define GP2AP020A00F_INTVAL_0		0x00
+#define GP2AP020A00F_INTVAL_4		0x40
+#define GP2AP020A00F_INTVAL_8		0x80
+#define GP2AP020A00F_INTVAL_16		0xc0
+#define GP2AP020A00F_IS_MASK		0x30 /* ILED drive peak current */
+#define GP2AP020A00F_IS_13_8mA		0x00
+#define GP2AP020A00F_IS_27_5mA		0x10
+#define GP2AP020A00F_IS_55mA		0x20
+#define GP2AP020A00F_IS_110mA		0x30
+#define GP2AP020A00F_PIN_MASK		0x0c /* INT terminal setting */
+#define GP2AP020A00F_PIN_ALS_OR_PS	0x00
+#define GP2AP020A00F_PIN_ALS		0x04
+#define GP2AP020A00F_PIN_PS		0x08
+#define GP2AP020A00F_PIN_PS_DETECT	0x0c
+#define GP2AP020A00F_FREQ_MASK		0x02 /* LED modulation frequency */
+#define GP2AP020A00F_FREQ_327_5kHz	0x00
+#define GP2AP020A00F_FREQ_81_8kHz	0x02
+#define GP2AP020A00F_RST		0x01 /* Software reset */
+
+#define GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR	0
+#define GP2AP020A00F_SCAN_MODE_LIGHT_IR		1
+#define GP2AP020A00F_SCAN_MODE_PROXIMITY	2
+#define GP2AP020A00F_CHAN_TIMESTAMP		3
+
+#define GP2AP020A00F_DATA_READY_TIMEOUT		msecs_to_jiffies(1000)
+#define GP2AP020A00F_DATA_REG(chan)		(GP2AP020A00F_D0_L_REG + \
+							(chan) * 2)
+#define GP2AP020A00F_THRESH_REG(th_val_id)	(GP2AP020A00F_TL_L_REG + \
+							(th_val_id) * 2)
+#define GP2AP020A00F_THRESH_VAL_ID(reg_addr)	((reg_addr - 4) / 2)
+
+#define GP2AP020A00F_SUBTRACT_MODE	0
+#define GP2AP020A00F_ADD_MODE		1
+
+#define GP2AP020A00F_MAX_CHANNELS	3
+
+enum gp2ap020a00f_opmode {
+	GP2AP020A00F_OPMODE_READ_RAW_CLEAR,
+	GP2AP020A00F_OPMODE_READ_RAW_IR,
+	GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY,
+	GP2AP020A00F_OPMODE_ALS,
+	GP2AP020A00F_OPMODE_PS,
+	GP2AP020A00F_OPMODE_ALS_AND_PS,
+	GP2AP020A00F_OPMODE_PROX_DETECT,
+	GP2AP020A00F_OPMODE_SHUTDOWN,
+	GP2AP020A00F_NUM_OPMODES,
+};
+
+enum gp2ap020a00f_cmd {
+	GP2AP020A00F_CMD_READ_RAW_CLEAR,
+	GP2AP020A00F_CMD_READ_RAW_IR,
+	GP2AP020A00F_CMD_READ_RAW_PROXIMITY,
+	GP2AP020A00F_CMD_TRIGGER_CLEAR_EN,
+	GP2AP020A00F_CMD_TRIGGER_CLEAR_DIS,
+	GP2AP020A00F_CMD_TRIGGER_IR_EN,
+	GP2AP020A00F_CMD_TRIGGER_IR_DIS,
+	GP2AP020A00F_CMD_TRIGGER_PROX_EN,
+	GP2AP020A00F_CMD_TRIGGER_PROX_DIS,
+	GP2AP020A00F_CMD_ALS_HIGH_EV_EN,
+	GP2AP020A00F_CMD_ALS_HIGH_EV_DIS,
+	GP2AP020A00F_CMD_ALS_LOW_EV_EN,
+	GP2AP020A00F_CMD_ALS_LOW_EV_DIS,
+	GP2AP020A00F_CMD_PROX_HIGH_EV_EN,
+	GP2AP020A00F_CMD_PROX_HIGH_EV_DIS,
+	GP2AP020A00F_CMD_PROX_LOW_EV_EN,
+	GP2AP020A00F_CMD_PROX_LOW_EV_DIS,
+};
+
+enum gp2ap020a00f_flags {
+	GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER,
+	GP2AP020A00F_FLAG_ALS_IR_TRIGGER,
+	GP2AP020A00F_FLAG_PROX_TRIGGER,
+	GP2AP020A00F_FLAG_PROX_RISING_EV,
+	GP2AP020A00F_FLAG_PROX_FALLING_EV,
+	GP2AP020A00F_FLAG_ALS_RISING_EV,
+	GP2AP020A00F_FLAG_ALS_FALLING_EV,
+	GP2AP020A00F_FLAG_LUX_MODE_HI,
+	GP2AP020A00F_FLAG_DATA_READY,
+};
+
+enum gp2ap020a00f_thresh_val_id {
+	GP2AP020A00F_THRESH_TL,
+	GP2AP020A00F_THRESH_TH,
+	GP2AP020A00F_THRESH_PL,
+	GP2AP020A00F_THRESH_PH,
+};
+
+struct gp2ap020a00f_data {
+	const struct gp2ap020a00f_platform_data *pdata;
+	struct i2c_client *client;
+	struct mutex lock;
+	char *buffer;
+	struct regulator *vled_reg;
+	unsigned long flags;
+	enum gp2ap020a00f_opmode cur_opmode;
+	struct iio_trigger *trig;
+	struct regmap *regmap;
+	unsigned int thresh_val[4];
+	u8 debug_reg_addr;
+	struct irq_work work;
+	wait_queue_head_t data_ready_queue;
+};
+
+static const u8 gp2ap020a00f_reg_init_tab[] = {
+	[GP2AP020A00F_OP_REG] = GP2AP020A00F_OP3_SHUTDOWN,
+	[GP2AP020A00F_ALS_REG] = GP2AP020A00F_RES_A_25ms |
+				 GP2AP020A00F_RANGE_A_x8,
+	[GP2AP020A00F_PS_REG] = GP2AP020A00F_ALC_ON |
+				GP2AP020A00F_RES_P_1_56ms_x2 |
+				GP2AP020A00F_RANGE_P_x4,
+	[GP2AP020A00F_LED_REG] = GP2AP020A00F_INTVAL_0 |
+				 GP2AP020A00F_IS_110mA |
+				 GP2AP020A00F_FREQ_327_5kHz,
+	[GP2AP020A00F_TL_L_REG] = 0,
+	[GP2AP020A00F_TL_H_REG] = 0,
+	[GP2AP020A00F_TH_L_REG] = 0,
+	[GP2AP020A00F_TH_H_REG] = 0,
+	[GP2AP020A00F_PL_L_REG] = 0,
+	[GP2AP020A00F_PL_H_REG] = 0,
+	[GP2AP020A00F_PH_L_REG] = 0,
+	[GP2AP020A00F_PH_H_REG] = 0,
+};
+
+static bool gp2ap020a00f_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case GP2AP020A00F_OP_REG:
+	case GP2AP020A00F_D0_L_REG:
+	case GP2AP020A00F_D0_H_REG:
+	case GP2AP020A00F_D1_L_REG:
+	case GP2AP020A00F_D1_H_REG:
+	case GP2AP020A00F_D2_L_REG:
+	case GP2AP020A00F_D2_H_REG:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config gp2ap020a00f_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+
+	.max_register = GP2AP020A00F_D2_H_REG,
+	.cache_type = REGCACHE_RBTREE,
+
+	.volatile_reg = gp2ap020a00f_is_volatile_reg,
+};
+
+static const struct gp2ap020a00f_mutable_config_regs {
+	u8 op_reg;
+	u8 als_reg;
+	u8 ps_reg;
+	u8 led_reg;
+} opmode_regs_settings[GP2AP020A00F_NUM_OPMODES] = {
+	[GP2AP020A00F_OPMODE_READ_RAW_CLEAR] = {
+		GP2AP020A00F_OP_ALS | GP2AP020A00F_OP2_CONT_OPERATION
+		| GP2AP020A00F_OP3_OPERATION
+		| GP2AP020A00F_TYPE_AUTO_CALC,
+		GP2AP020A00F_PRST_ONCE,
+		GP2AP020A00F_INTTYPE_LEVEL,
+		GP2AP020A00F_PIN_ALS
+	},
+	[GP2AP020A00F_OPMODE_READ_RAW_IR] = {
+		GP2AP020A00F_OP_ALS | GP2AP020A00F_OP2_CONT_OPERATION
+		| GP2AP020A00F_OP3_OPERATION
+		| GP2AP020A00F_TYPE_MANUAL_CALC,
+		GP2AP020A00F_PRST_ONCE,
+		GP2AP020A00F_INTTYPE_LEVEL,
+		GP2AP020A00F_PIN_ALS
+	},
+	[GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY] = {
+		GP2AP020A00F_OP_PS | GP2AP020A00F_OP2_CONT_OPERATION
+		| GP2AP020A00F_OP3_OPERATION
+		| GP2AP020A00F_TYPE_MANUAL_CALC,
+		GP2AP020A00F_PRST_ONCE,
+		GP2AP020A00F_INTTYPE_LEVEL,
+		GP2AP020A00F_PIN_PS
+	},
+	[GP2AP020A00F_OPMODE_PROX_DETECT] = {
+		GP2AP020A00F_OP_PS | GP2AP020A00F_OP2_CONT_OPERATION
+		| GP2AP020A00F_OP3_OPERATION
+		| GP2AP020A00F_TYPE_MANUAL_CALC,
+		GP2AP020A00F_PRST_4_CYCLES,
+		GP2AP020A00F_INTTYPE_PULSE,
+		GP2AP020A00F_PIN_PS_DETECT
+	},
+	[GP2AP020A00F_OPMODE_ALS] = {
+		GP2AP020A00F_OP_ALS | GP2AP020A00F_OP2_CONT_OPERATION
+		| GP2AP020A00F_OP3_OPERATION
+		| GP2AP020A00F_TYPE_AUTO_CALC,
+		GP2AP020A00F_PRST_ONCE,
+		GP2AP020A00F_INTTYPE_LEVEL,
+		GP2AP020A00F_PIN_ALS
+	},
+	[GP2AP020A00F_OPMODE_PS] = {
+		GP2AP020A00F_OP_PS | GP2AP020A00F_OP2_CONT_OPERATION
+		| GP2AP020A00F_OP3_OPERATION
+		| GP2AP020A00F_TYPE_MANUAL_CALC,
+		GP2AP020A00F_PRST_4_CYCLES,
+		GP2AP020A00F_INTTYPE_LEVEL,
+		GP2AP020A00F_PIN_PS
+	},
+	[GP2AP020A00F_OPMODE_ALS_AND_PS] = {
+		GP2AP020A00F_OP_ALS_AND_PS
+		| GP2AP020A00F_OP2_CONT_OPERATION
+		| GP2AP020A00F_OP3_OPERATION
+		| GP2AP020A00F_TYPE_AUTO_CALC,
+		GP2AP020A00F_PRST_4_CYCLES,
+		GP2AP020A00F_INTTYPE_LEVEL,
+		GP2AP020A00F_PIN_ALS_OR_PS
+	},
+	[GP2AP020A00F_OPMODE_SHUTDOWN] = { GP2AP020A00F_OP3_SHUTDOWN, },
+};
+
+static int gp2ap020a00f_set_operation_mode(struct gp2ap020a00f_data *data,
+					enum gp2ap020a00f_opmode op)
+{
+	unsigned int op_reg_val;
+	int err;
+
+	if (op != GP2AP020A00F_OPMODE_SHUTDOWN) {
+		err = regmap_read(data->regmap, GP2AP020A00F_OP_REG,
+					&op_reg_val);
+		if (err < 0)
+			return err;
+		/*
+		 * Shutdown the device if the operation being executed entails
+		 * mode transition.
+		 */
+		if ((opmode_regs_settings[op].op_reg & GP2AP020A00F_OP_MASK) !=
+		    (op_reg_val & GP2AP020A00F_OP_MASK)) {
+			/* set shutdown mode */
+			err = regmap_update_bits(data->regmap,
+				GP2AP020A00F_OP_REG, GP2AP020A00F_OP3_MASK,
+				GP2AP020A00F_OP3_SHUTDOWN);
+			if (err < 0)
+				return err;
+		}
+
+		err = regmap_update_bits(data->regmap, GP2AP020A00F_ALS_REG,
+			GP2AP020A00F_PRST_MASK, opmode_regs_settings[op]
+								.als_reg);
+		if (err < 0)
+			return err;
+
+		err = regmap_update_bits(data->regmap, GP2AP020A00F_PS_REG,
+			GP2AP020A00F_INTTYPE_MASK, opmode_regs_settings[op]
+								.ps_reg);
+		if (err < 0)
+			return err;
+
+		err = regmap_update_bits(data->regmap, GP2AP020A00F_LED_REG,
+			GP2AP020A00F_PIN_MASK, opmode_regs_settings[op]
+								.led_reg);
+		if (err < 0)
+			return err;
+	}
+
+	/* Set OP_REG and apply operation mode (power on / off) */
+	err = regmap_update_bits(data->regmap,
+				 GP2AP020A00F_OP_REG,
+				 GP2AP020A00F_OP_MASK | GP2AP020A00F_OP2_MASK |
+				 GP2AP020A00F_OP3_MASK | GP2AP020A00F_TYPE_MASK,
+				 opmode_regs_settings[op].op_reg);
+	if (err < 0)
+		return err;
+
+	data->cur_opmode = op;
+
+	return 0;
+}
+
+static bool gp2ap020a00f_als_enabled(struct gp2ap020a00f_data *data)
+{
+	return test_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &data->flags) ||
+	       test_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &data->flags) ||
+	       test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags) ||
+	       test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags);
+}
+
+static bool gp2ap020a00f_prox_detect_enabled(struct gp2ap020a00f_data *data)
+{
+	return test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags) ||
+	       test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags);
+}
+
+static int gp2ap020a00f_write_event_threshold(struct gp2ap020a00f_data *data,
+				enum gp2ap020a00f_thresh_val_id th_val_id,
+				bool enable)
+{
+	__le16 thresh_buf = 0;
+	unsigned int thresh_reg_val;
+
+	if (!enable)
+		thresh_reg_val = 0;
+	else if (test_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags) &&
+		 th_val_id != GP2AP020A00F_THRESH_PL &&
+		 th_val_id != GP2AP020A00F_THRESH_PH)
+		/*
+		 * For the high lux mode ALS threshold has to be scaled down
+		 * to allow for proper comparison with the output value.
+		 */
+		thresh_reg_val = data->thresh_val[th_val_id] / 16;
+	else
+		thresh_reg_val = data->thresh_val[th_val_id] > 16000 ?
+					16000 :
+					data->thresh_val[th_val_id];
+
+	thresh_buf = cpu_to_le16(thresh_reg_val);
+
+	return regmap_bulk_write(data->regmap,
+				 GP2AP020A00F_THRESH_REG(th_val_id),
+				 (u8 *)&thresh_buf, 2);
+}
+
+static int gp2ap020a00f_alter_opmode(struct gp2ap020a00f_data *data,
+			enum gp2ap020a00f_opmode diff_mode, int add_sub)
+{
+	enum gp2ap020a00f_opmode new_mode;
+
+	if (diff_mode != GP2AP020A00F_OPMODE_ALS &&
+	    diff_mode != GP2AP020A00F_OPMODE_PS)
+		return -EINVAL;
+
+	if (add_sub == GP2AP020A00F_ADD_MODE) {
+		if (data->cur_opmode == GP2AP020A00F_OPMODE_SHUTDOWN)
+			new_mode =  diff_mode;
+		else
+			new_mode = GP2AP020A00F_OPMODE_ALS_AND_PS;
+	} else {
+		if (data->cur_opmode == GP2AP020A00F_OPMODE_ALS_AND_PS)
+			new_mode = (diff_mode == GP2AP020A00F_OPMODE_ALS) ?
+					GP2AP020A00F_OPMODE_PS :
+					GP2AP020A00F_OPMODE_ALS;
+		else
+			new_mode = GP2AP020A00F_OPMODE_SHUTDOWN;
+	}
+
+	return gp2ap020a00f_set_operation_mode(data, new_mode);
+}
+
+static int gp2ap020a00f_exec_cmd(struct gp2ap020a00f_data *data,
+					enum gp2ap020a00f_cmd cmd)
+{
+	int err = 0;
+
+	switch (cmd) {
+	case GP2AP020A00F_CMD_READ_RAW_CLEAR:
+		if (data->cur_opmode != GP2AP020A00F_OPMODE_SHUTDOWN)
+			return -EBUSY;
+		err = gp2ap020a00f_set_operation_mode(data,
+					GP2AP020A00F_OPMODE_READ_RAW_CLEAR);
+		break;
+	case GP2AP020A00F_CMD_READ_RAW_IR:
+		if (data->cur_opmode != GP2AP020A00F_OPMODE_SHUTDOWN)
+			return -EBUSY;
+		err = gp2ap020a00f_set_operation_mode(data,
+					GP2AP020A00F_OPMODE_READ_RAW_IR);
+		break;
+	case GP2AP020A00F_CMD_READ_RAW_PROXIMITY:
+		if (data->cur_opmode != GP2AP020A00F_OPMODE_SHUTDOWN)
+			return -EBUSY;
+		err = gp2ap020a00f_set_operation_mode(data,
+					GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY);
+		break;
+	case GP2AP020A00F_CMD_TRIGGER_CLEAR_EN:
+		if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT)
+			return -EBUSY;
+		if (!gp2ap020a00f_als_enabled(data))
+			err = gp2ap020a00f_alter_opmode(data,
+						GP2AP020A00F_OPMODE_ALS,
+						GP2AP020A00F_ADD_MODE);
+		set_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &data->flags);
+		break;
+	case GP2AP020A00F_CMD_TRIGGER_CLEAR_DIS:
+		clear_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &data->flags);
+		if (gp2ap020a00f_als_enabled(data))
+			break;
+		err = gp2ap020a00f_alter_opmode(data,
+						GP2AP020A00F_OPMODE_ALS,
+						GP2AP020A00F_SUBTRACT_MODE);
+		break;
+	case GP2AP020A00F_CMD_TRIGGER_IR_EN:
+		if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT)
+			return -EBUSY;
+		if (!gp2ap020a00f_als_enabled(data))
+			err = gp2ap020a00f_alter_opmode(data,
+						GP2AP020A00F_OPMODE_ALS,
+						GP2AP020A00F_ADD_MODE);
+		set_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &data->flags);
+		break;
+	case GP2AP020A00F_CMD_TRIGGER_IR_DIS:
+		clear_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &data->flags);
+		if (gp2ap020a00f_als_enabled(data))
+			break;
+		err = gp2ap020a00f_alter_opmode(data,
+						GP2AP020A00F_OPMODE_ALS,
+						GP2AP020A00F_SUBTRACT_MODE);
+		break;
+	case GP2AP020A00F_CMD_TRIGGER_PROX_EN:
+		if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT)
+			return -EBUSY;
+		err = gp2ap020a00f_alter_opmode(data,
+						GP2AP020A00F_OPMODE_PS,
+						GP2AP020A00F_ADD_MODE);
+		set_bit(GP2AP020A00F_FLAG_PROX_TRIGGER, &data->flags);
+		break;
+	case GP2AP020A00F_CMD_TRIGGER_PROX_DIS:
+		clear_bit(GP2AP020A00F_FLAG_PROX_TRIGGER, &data->flags);
+		err = gp2ap020a00f_alter_opmode(data,
+						GP2AP020A00F_OPMODE_PS,
+						GP2AP020A00F_SUBTRACT_MODE);
+		break;
+	case GP2AP020A00F_CMD_ALS_HIGH_EV_EN:
+		if (test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags))
+			return 0;
+		if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT)
+			return -EBUSY;
+		if (!gp2ap020a00f_als_enabled(data)) {
+			err = gp2ap020a00f_alter_opmode(data,
+						GP2AP020A00F_OPMODE_ALS,
+						GP2AP020A00F_ADD_MODE);
+			if (err < 0)
+				return err;
+		}
+		set_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags);
+		err =  gp2ap020a00f_write_event_threshold(data,
+					GP2AP020A00F_THRESH_TH, true);
+		break;
+	case GP2AP020A00F_CMD_ALS_HIGH_EV_DIS:
+		if (!test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags))
+			return 0;
+		clear_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags);
+		if (!gp2ap020a00f_als_enabled(data)) {
+			err = gp2ap020a00f_alter_opmode(data,
+						GP2AP020A00F_OPMODE_ALS,
+						GP2AP020A00F_SUBTRACT_MODE);
+			if (err < 0)
+				return err;
+		}
+		err =  gp2ap020a00f_write_event_threshold(data,
+					GP2AP020A00F_THRESH_TH, false);
+		break;
+	case GP2AP020A00F_CMD_ALS_LOW_EV_EN:
+		if (test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags))
+			return 0;
+		if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT)
+			return -EBUSY;
+		if (!gp2ap020a00f_als_enabled(data)) {
+			err = gp2ap020a00f_alter_opmode(data,
+						GP2AP020A00F_OPMODE_ALS,
+						GP2AP020A00F_ADD_MODE);
+			if (err < 0)
+				return err;
+		}
+		set_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags);
+		err =  gp2ap020a00f_write_event_threshold(data,
+					GP2AP020A00F_THRESH_TL, true);
+		break;
+	case GP2AP020A00F_CMD_ALS_LOW_EV_DIS:
+		if (!test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags))
+			return 0;
+		clear_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags);
+		if (!gp2ap020a00f_als_enabled(data)) {
+			err = gp2ap020a00f_alter_opmode(data,
+						GP2AP020A00F_OPMODE_ALS,
+						GP2AP020A00F_SUBTRACT_MODE);
+			if (err < 0)
+				return err;
+		}
+		err =  gp2ap020a00f_write_event_threshold(data,
+					GP2AP020A00F_THRESH_TL, false);
+		break;
+	case GP2AP020A00F_CMD_PROX_HIGH_EV_EN:
+		if (test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags))
+			return 0;
+		if (gp2ap020a00f_als_enabled(data) ||
+		    data->cur_opmode == GP2AP020A00F_OPMODE_PS)
+			return -EBUSY;
+		if (!gp2ap020a00f_prox_detect_enabled(data)) {
+			err = gp2ap020a00f_set_operation_mode(data,
+					GP2AP020A00F_OPMODE_PROX_DETECT);
+			if (err < 0)
+				return err;
+		}
+		set_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags);
+		err =  gp2ap020a00f_write_event_threshold(data,
+					GP2AP020A00F_THRESH_PH, true);
+		break;
+	case GP2AP020A00F_CMD_PROX_HIGH_EV_DIS:
+		if (!test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags))
+			return 0;
+		clear_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags);
+		err = gp2ap020a00f_set_operation_mode(data,
+					GP2AP020A00F_OPMODE_SHUTDOWN);
+		if (err < 0)
+			return err;
+		err =  gp2ap020a00f_write_event_threshold(data,
+					GP2AP020A00F_THRESH_PH, false);
+		break;
+	case GP2AP020A00F_CMD_PROX_LOW_EV_EN:
+		if (test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags))
+			return 0;
+		if (gp2ap020a00f_als_enabled(data) ||
+		    data->cur_opmode == GP2AP020A00F_OPMODE_PS)
+			return -EBUSY;
+		if (!gp2ap020a00f_prox_detect_enabled(data)) {
+			err = gp2ap020a00f_set_operation_mode(data,
+					GP2AP020A00F_OPMODE_PROX_DETECT);
+			if (err < 0)
+				return err;
+		}
+		set_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags);
+		err =  gp2ap020a00f_write_event_threshold(data,
+					GP2AP020A00F_THRESH_PL, true);
+		break;
+	case GP2AP020A00F_CMD_PROX_LOW_EV_DIS:
+		if (!test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags))
+			return 0;
+		clear_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags);
+		err = gp2ap020a00f_set_operation_mode(data,
+					GP2AP020A00F_OPMODE_SHUTDOWN);
+		if (err < 0)
+			return err;
+		err =  gp2ap020a00f_write_event_threshold(data,
+					GP2AP020A00F_THRESH_PL, false);
+		break;
+	}
+
+	return err;
+}
+
+static int wait_conversion_complete_irq(struct gp2ap020a00f_data *data)
+{
+	int ret;
+
+	ret = wait_event_timeout(data->data_ready_queue,
+				 test_bit(GP2AP020A00F_FLAG_DATA_READY,
+					  &data->flags),
+				 GP2AP020A00F_DATA_READY_TIMEOUT);
+	clear_bit(GP2AP020A00F_FLAG_DATA_READY, &data->flags);
+
+	return ret > 0 ? 0 : -ETIME;
+}
+
+static int gp2ap020a00f_read_output(struct gp2ap020a00f_data *data,
+					unsigned int output_reg, int *val)
+{
+	u8 reg_buf[2];
+	int err;
+
+	err = wait_conversion_complete_irq(data);
+	if (err < 0)
+		dev_dbg(&data->client->dev, "data ready timeout\n");
+
+	err = regmap_bulk_read(data->regmap, output_reg, reg_buf, 2);
+	if (err < 0)
+		return err;
+
+	*val = le16_to_cpup((__le16 *)reg_buf);
+
+	return err;
+}
+
+static bool gp2ap020a00f_adjust_lux_mode(struct gp2ap020a00f_data *data,
+				 int output_val)
+{
+	u8 new_range = 0xff;
+	int err;
+
+	if (!test_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags)) {
+		if (output_val > 16000) {
+			set_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags);
+			new_range = GP2AP020A00F_RANGE_A_x128;
+		}
+	} else {
+		if (output_val < 1000) {
+			clear_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags);
+			new_range = GP2AP020A00F_RANGE_A_x8;
+		}
+	}
+
+	if (new_range != 0xff) {
+		/* Clear als threshold registers to avoid spurious
+		 * events caused by lux mode transition.
+		 */
+		err =  gp2ap020a00f_write_event_threshold(data,
+					GP2AP020A00F_THRESH_TH, false);
+		if (err < 0) {
+			dev_err(&data->client->dev,
+				"Clearing als threshold register failed.\n");
+			return false;
+		}
+
+		err =  gp2ap020a00f_write_event_threshold(data,
+					GP2AP020A00F_THRESH_TL, false);
+		if (err < 0) {
+			dev_err(&data->client->dev,
+				"Clearing als threshold register failed.\n");
+			return false;
+		}
+
+		/* Change lux mode */
+		err = regmap_update_bits(data->regmap,
+			GP2AP020A00F_OP_REG,
+			GP2AP020A00F_OP3_MASK,
+			GP2AP020A00F_OP3_SHUTDOWN);
+
+		if (err < 0) {
+			dev_err(&data->client->dev,
+				"Shutting down the device failed.\n");
+			return false;
+		}
+
+		err = regmap_update_bits(data->regmap,
+			GP2AP020A00F_ALS_REG,
+			GP2AP020A00F_RANGE_A_MASK,
+			new_range);
+
+		if (err < 0) {
+			dev_err(&data->client->dev,
+				"Adjusting device lux mode failed.\n");
+			return false;
+		}
+
+		err = regmap_update_bits(data->regmap,
+			GP2AP020A00F_OP_REG,
+			GP2AP020A00F_OP3_MASK,
+			GP2AP020A00F_OP3_OPERATION);
+
+		if (err < 0) {
+			dev_err(&data->client->dev,
+				"Powering up the device failed.\n");
+			return false;
+		}
+
+		/* Adjust als threshold register values to the new lux mode */
+		if (test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags)) {
+			err =  gp2ap020a00f_write_event_threshold(data,
+					GP2AP020A00F_THRESH_TH, true);
+			if (err < 0) {
+				dev_err(&data->client->dev,
+				"Adjusting als threshold value failed.\n");
+				return false;
+			}
+		}
+
+		if (test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags)) {
+			err =  gp2ap020a00f_write_event_threshold(data,
+					GP2AP020A00F_THRESH_TL, true);
+			if (err < 0) {
+				dev_err(&data->client->dev,
+				"Adjusting als threshold value failed.\n");
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
+static void gp2ap020a00f_output_to_lux(struct gp2ap020a00f_data *data,
+						int *output_val)
+{
+	if (test_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags))
+		*output_val *= 16;
+}
+
+static void gp2ap020a00f_iio_trigger_work(struct irq_work *work)
+{
+	struct gp2ap020a00f_data *data =
+		container_of(work, struct gp2ap020a00f_data, work);
+
+	iio_trigger_poll(data->trig);
+}
+
+static irqreturn_t gp2ap020a00f_prox_sensing_handler(int irq, void *data)
+{
+	struct iio_dev *indio_dev = data;
+	struct gp2ap020a00f_data *priv = iio_priv(indio_dev);
+	unsigned int op_reg_val;
+	int ret;
+
+	/* Read interrupt flags */
+	ret = regmap_read(priv->regmap, GP2AP020A00F_OP_REG, &op_reg_val);
+	if (ret < 0)
+		return IRQ_HANDLED;
+
+	if (gp2ap020a00f_prox_detect_enabled(priv)) {
+		if (op_reg_val & GP2AP020A00F_PROX_DETECT) {
+			iio_push_event(indio_dev,
+			       IIO_UNMOD_EVENT_CODE(
+				    IIO_PROXIMITY,
+				    GP2AP020A00F_SCAN_MODE_PROXIMITY,
+				    IIO_EV_TYPE_ROC,
+				    IIO_EV_DIR_RISING),
+			       iio_get_time_ns(indio_dev));
+		} else {
+			iio_push_event(indio_dev,
+			       IIO_UNMOD_EVENT_CODE(
+				    IIO_PROXIMITY,
+				    GP2AP020A00F_SCAN_MODE_PROXIMITY,
+				    IIO_EV_TYPE_ROC,
+				    IIO_EV_DIR_FALLING),
+			       iio_get_time_ns(indio_dev));
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t gp2ap020a00f_thresh_event_handler(int irq, void *data)
+{
+	struct iio_dev *indio_dev = data;
+	struct gp2ap020a00f_data *priv = iio_priv(indio_dev);
+	u8 op_reg_flags, d0_reg_buf[2];
+	unsigned int output_val, op_reg_val;
+	int thresh_val_id, ret;
+
+	/* Read interrupt flags */
+	ret = regmap_read(priv->regmap, GP2AP020A00F_OP_REG,
+							&op_reg_val);
+	if (ret < 0)
+		goto done;
+
+	op_reg_flags = op_reg_val & (GP2AP020A00F_FLAG_A | GP2AP020A00F_FLAG_P
+					| GP2AP020A00F_PROX_DETECT);
+
+	op_reg_val &= (~GP2AP020A00F_FLAG_A & ~GP2AP020A00F_FLAG_P
+					& ~GP2AP020A00F_PROX_DETECT);
+
+	/* Clear interrupt flags (if not in INTTYPE_PULSE mode) */
+	if (priv->cur_opmode != GP2AP020A00F_OPMODE_PROX_DETECT) {
+		ret = regmap_write(priv->regmap, GP2AP020A00F_OP_REG,
+								op_reg_val);
+		if (ret < 0)
+			goto done;
+	}
+
+	if (op_reg_flags & GP2AP020A00F_FLAG_A) {
+		/* Check D0 register to assess if the lux mode
+		 * transition is required.
+		 */
+		ret = regmap_bulk_read(priv->regmap, GP2AP020A00F_D0_L_REG,
+							d0_reg_buf, 2);
+		if (ret < 0)
+			goto done;
+
+		output_val = le16_to_cpup((__le16 *)d0_reg_buf);
+
+		if (gp2ap020a00f_adjust_lux_mode(priv, output_val))
+			goto done;
+
+		gp2ap020a00f_output_to_lux(priv, &output_val);
+
+		/*
+		 * We need to check output value to distinguish
+		 * between high and low ambient light threshold event.
+		 */
+		if (test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &priv->flags)) {
+			thresh_val_id =
+			    GP2AP020A00F_THRESH_VAL_ID(GP2AP020A00F_TH_L_REG);
+			if (output_val > priv->thresh_val[thresh_val_id])
+				iio_push_event(indio_dev,
+				       IIO_MOD_EVENT_CODE(
+					    IIO_LIGHT,
+					    GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR,
+					    IIO_MOD_LIGHT_CLEAR,
+					    IIO_EV_TYPE_THRESH,
+					    IIO_EV_DIR_RISING),
+				       iio_get_time_ns(indio_dev));
+		}
+
+		if (test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &priv->flags)) {
+			thresh_val_id =
+			    GP2AP020A00F_THRESH_VAL_ID(GP2AP020A00F_TL_L_REG);
+			if (output_val < priv->thresh_val[thresh_val_id])
+				iio_push_event(indio_dev,
+				       IIO_MOD_EVENT_CODE(
+					    IIO_LIGHT,
+					    GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR,
+					    IIO_MOD_LIGHT_CLEAR,
+					    IIO_EV_TYPE_THRESH,
+					    IIO_EV_DIR_FALLING),
+				       iio_get_time_ns(indio_dev));
+		}
+	}
+
+	if (priv->cur_opmode == GP2AP020A00F_OPMODE_READ_RAW_CLEAR ||
+	    priv->cur_opmode == GP2AP020A00F_OPMODE_READ_RAW_IR ||
+	    priv->cur_opmode == GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY) {
+		set_bit(GP2AP020A00F_FLAG_DATA_READY, &priv->flags);
+		wake_up(&priv->data_ready_queue);
+		goto done;
+	}
+
+	if (test_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &priv->flags) ||
+	    test_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &priv->flags) ||
+	    test_bit(GP2AP020A00F_FLAG_PROX_TRIGGER, &priv->flags))
+		/* This fires off the trigger. */
+		irq_work_queue(&priv->work);
+
+done:
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t gp2ap020a00f_trigger_handler(int irq, void *data)
+{
+	struct iio_poll_func *pf = data;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct gp2ap020a00f_data *priv = iio_priv(indio_dev);
+	size_t d_size = 0;
+	int i, out_val, ret;
+
+	for_each_set_bit(i, indio_dev->active_scan_mask,
+		indio_dev->masklength) {
+		ret = regmap_bulk_read(priv->regmap,
+				GP2AP020A00F_DATA_REG(i),
+				&priv->buffer[d_size], 2);
+		if (ret < 0)
+			goto done;
+
+		if (i == GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR ||
+		    i == GP2AP020A00F_SCAN_MODE_LIGHT_IR) {
+			out_val = le16_to_cpup((__le16 *)&priv->buffer[d_size]);
+			gp2ap020a00f_output_to_lux(priv, &out_val);
+
+			put_unaligned_le32(out_val, &priv->buffer[d_size]);
+			d_size += 4;
+		} else {
+			d_size += 2;
+		}
+	}
+
+	iio_push_to_buffers_with_timestamp(indio_dev, priv->buffer,
+		pf->timestamp);
+done:
+	iio_trigger_notify_done(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
+static u8 gp2ap020a00f_get_thresh_reg(const struct iio_chan_spec *chan,
+					     enum iio_event_direction event_dir)
+{
+	switch (chan->type) {
+	case IIO_PROXIMITY:
+		if (event_dir == IIO_EV_DIR_RISING)
+			return GP2AP020A00F_PH_L_REG;
+		else
+			return GP2AP020A00F_PL_L_REG;
+	case IIO_LIGHT:
+		if (event_dir == IIO_EV_DIR_RISING)
+			return GP2AP020A00F_TH_L_REG;
+		else
+			return GP2AP020A00F_TL_L_REG;
+	default:
+		break;
+	}
+
+	return -EINVAL;
+}
+
+static int gp2ap020a00f_write_event_val(struct iio_dev *indio_dev,
+					const struct iio_chan_spec *chan,
+					enum iio_event_type type,
+					enum iio_event_direction dir,
+					enum iio_event_info info,
+					int val, int val2)
+{
+	struct gp2ap020a00f_data *data = iio_priv(indio_dev);
+	bool event_en = false;
+	u8 thresh_val_id;
+	u8 thresh_reg_l;
+	int err = 0;
+
+	mutex_lock(&data->lock);
+
+	thresh_reg_l = gp2ap020a00f_get_thresh_reg(chan, dir);
+	thresh_val_id = GP2AP020A00F_THRESH_VAL_ID(thresh_reg_l);
+
+	if (thresh_val_id > GP2AP020A00F_THRESH_PH) {
+		err = -EINVAL;
+		goto error_unlock;
+	}
+
+	switch (thresh_reg_l) {
+	case GP2AP020A00F_TH_L_REG:
+		event_en = test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV,
+							&data->flags);
+		break;
+	case GP2AP020A00F_TL_L_REG:
+		event_en = test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV,
+							&data->flags);
+		break;
+	case GP2AP020A00F_PH_L_REG:
+		if (val == 0) {
+			err = -EINVAL;
+			goto error_unlock;
+		}
+		event_en = test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV,
+							&data->flags);
+		break;
+	case GP2AP020A00F_PL_L_REG:
+		if (val == 0) {
+			err = -EINVAL;
+			goto error_unlock;
+		}
+		event_en = test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV,
+							&data->flags);
+		break;
+	}
+
+	data->thresh_val[thresh_val_id] = val;
+	err =  gp2ap020a00f_write_event_threshold(data, thresh_val_id,
+							event_en);
+error_unlock:
+	mutex_unlock(&data->lock);
+
+	return err;
+}
+
+static int gp2ap020a00f_read_event_val(struct iio_dev *indio_dev,
+				       const struct iio_chan_spec *chan,
+				       enum iio_event_type type,
+				       enum iio_event_direction dir,
+				       enum iio_event_info info,
+				       int *val, int *val2)
+{
+	struct gp2ap020a00f_data *data = iio_priv(indio_dev);
+	u8 thresh_reg_l;
+	int err = IIO_VAL_INT;
+
+	mutex_lock(&data->lock);
+
+	thresh_reg_l = gp2ap020a00f_get_thresh_reg(chan, dir);
+
+	if (thresh_reg_l > GP2AP020A00F_PH_L_REG) {
+		err = -EINVAL;
+		goto error_unlock;
+	}
+
+	*val = data->thresh_val[GP2AP020A00F_THRESH_VAL_ID(thresh_reg_l)];
+
+error_unlock:
+	mutex_unlock(&data->lock);
+
+	return err;
+}
+
+static int gp2ap020a00f_write_prox_event_config(struct iio_dev *indio_dev,
+						int state)
+{
+	struct gp2ap020a00f_data *data = iio_priv(indio_dev);
+	enum gp2ap020a00f_cmd cmd_high_ev, cmd_low_ev;
+	int err;
+
+	cmd_high_ev = state ? GP2AP020A00F_CMD_PROX_HIGH_EV_EN :
+			      GP2AP020A00F_CMD_PROX_HIGH_EV_DIS;
+	cmd_low_ev = state ? GP2AP020A00F_CMD_PROX_LOW_EV_EN :
+			     GP2AP020A00F_CMD_PROX_LOW_EV_DIS;
+
+	/*
+	 * In order to enable proximity detection feature in the device
+	 * both high and low threshold registers have to be written
+	 * with different values, greater than zero.
+	 */
+	if (state) {
+		if (data->thresh_val[GP2AP020A00F_THRESH_PL] == 0)
+			return -EINVAL;
+
+		if (data->thresh_val[GP2AP020A00F_THRESH_PH] == 0)
+			return -EINVAL;
+	}
+
+	err = gp2ap020a00f_exec_cmd(data, cmd_high_ev);
+	if (err < 0)
+		return err;
+
+	err = gp2ap020a00f_exec_cmd(data, cmd_low_ev);
+	if (err < 0)
+		return err;
+
+	free_irq(data->client->irq, indio_dev);
+
+	if (state)
+		err = request_threaded_irq(data->client->irq, NULL,
+					   &gp2ap020a00f_prox_sensing_handler,
+					   IRQF_TRIGGER_RISING |
+					   IRQF_TRIGGER_FALLING |
+					   IRQF_ONESHOT,
+					   "gp2ap020a00f_prox_sensing",
+					   indio_dev);
+	else {
+		err = request_threaded_irq(data->client->irq, NULL,
+					   &gp2ap020a00f_thresh_event_handler,
+					   IRQF_TRIGGER_FALLING |
+					   IRQF_ONESHOT,
+					   "gp2ap020a00f_thresh_event",
+					   indio_dev);
+	}
+
+	return err;
+}
+
+static int gp2ap020a00f_write_event_config(struct iio_dev *indio_dev,
+					   const struct iio_chan_spec *chan,
+					   enum iio_event_type type,
+					   enum iio_event_direction dir,
+					   int state)
+{
+	struct gp2ap020a00f_data *data = iio_priv(indio_dev);
+	enum gp2ap020a00f_cmd cmd;
+	int err;
+
+	mutex_lock(&data->lock);
+
+	switch (chan->type) {
+	case IIO_PROXIMITY:
+		err = gp2ap020a00f_write_prox_event_config(indio_dev, state);
+		break;
+	case IIO_LIGHT:
+		if (dir == IIO_EV_DIR_RISING) {
+			cmd = state ? GP2AP020A00F_CMD_ALS_HIGH_EV_EN :
+				      GP2AP020A00F_CMD_ALS_HIGH_EV_DIS;
+			err = gp2ap020a00f_exec_cmd(data, cmd);
+		} else {
+			cmd = state ? GP2AP020A00F_CMD_ALS_LOW_EV_EN :
+				      GP2AP020A00F_CMD_ALS_LOW_EV_DIS;
+			err = gp2ap020a00f_exec_cmd(data, cmd);
+		}
+		break;
+	default:
+		err = -EINVAL;
+	}
+
+	mutex_unlock(&data->lock);
+
+	return err;
+}
+
+static int gp2ap020a00f_read_event_config(struct iio_dev *indio_dev,
+					   const struct iio_chan_spec *chan,
+					   enum iio_event_type type,
+					   enum iio_event_direction dir)
+{
+	struct gp2ap020a00f_data *data = iio_priv(indio_dev);
+	int event_en = 0;
+
+	mutex_lock(&data->lock);
+
+	switch (chan->type) {
+	case IIO_PROXIMITY:
+		if (dir == IIO_EV_DIR_RISING)
+			event_en = test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV,
+								&data->flags);
+		else
+			event_en = test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV,
+								&data->flags);
+		break;
+	case IIO_LIGHT:
+		if (dir == IIO_EV_DIR_RISING)
+			event_en = test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV,
+								&data->flags);
+		else
+			event_en = test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV,
+								&data->flags);
+		break;
+	default:
+		event_en = -EINVAL;
+		break;
+	}
+
+	mutex_unlock(&data->lock);
+
+	return event_en;
+}
+
+static int gp2ap020a00f_read_channel(struct gp2ap020a00f_data *data,
+				struct iio_chan_spec const *chan, int *val)
+{
+	enum gp2ap020a00f_cmd cmd;
+	int err;
+
+	switch (chan->scan_index) {
+	case GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR:
+		cmd = GP2AP020A00F_CMD_READ_RAW_CLEAR;
+		break;
+	case GP2AP020A00F_SCAN_MODE_LIGHT_IR:
+		cmd = GP2AP020A00F_CMD_READ_RAW_IR;
+		break;
+	case GP2AP020A00F_SCAN_MODE_PROXIMITY:
+		cmd = GP2AP020A00F_CMD_READ_RAW_PROXIMITY;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	err = gp2ap020a00f_exec_cmd(data, cmd);
+	if (err < 0) {
+		dev_err(&data->client->dev,
+			"gp2ap020a00f_exec_cmd failed\n");
+		goto error_ret;
+	}
+
+	err = gp2ap020a00f_read_output(data, chan->address, val);
+	if (err < 0)
+		dev_err(&data->client->dev,
+			"gp2ap020a00f_read_output failed\n");
+
+	err = gp2ap020a00f_set_operation_mode(data,
+					GP2AP020A00F_OPMODE_SHUTDOWN);
+	if (err < 0)
+		dev_err(&data->client->dev,
+			"Failed to shut down the device.\n");
+
+	if (cmd == GP2AP020A00F_CMD_READ_RAW_CLEAR ||
+	    cmd == GP2AP020A00F_CMD_READ_RAW_IR)
+		gp2ap020a00f_output_to_lux(data, val);
+
+error_ret:
+	return err;
+}
+
+static int gp2ap020a00f_read_raw(struct iio_dev *indio_dev,
+			   struct iio_chan_spec const *chan,
+			   int *val, int *val2,
+			   long mask)
+{
+	struct gp2ap020a00f_data *data = iio_priv(indio_dev);
+	int err = -EINVAL;
+
+	if (mask == IIO_CHAN_INFO_RAW) {
+		err = iio_device_claim_direct_mode(indio_dev);
+		if (err)
+			return err;
+
+		err = gp2ap020a00f_read_channel(data, chan, val);
+		iio_device_release_direct_mode(indio_dev);
+	}
+	return err < 0 ? err : IIO_VAL_INT;
+}
+
+static const struct iio_event_spec gp2ap020a00f_event_spec_light[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_RISING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+			BIT(IIO_EV_INFO_ENABLE),
+	}, {
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_FALLING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+			BIT(IIO_EV_INFO_ENABLE),
+	},
+};
+
+static const struct iio_event_spec gp2ap020a00f_event_spec_prox[] = {
+	{
+		.type = IIO_EV_TYPE_ROC,
+		.dir = IIO_EV_DIR_RISING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+			BIT(IIO_EV_INFO_ENABLE),
+	}, {
+		.type = IIO_EV_TYPE_ROC,
+		.dir = IIO_EV_DIR_FALLING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+			BIT(IIO_EV_INFO_ENABLE),
+	},
+};
+
+static const struct iio_chan_spec gp2ap020a00f_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.channel2 = IIO_MOD_LIGHT_CLEAR,
+		.modified = 1,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 24,
+			.shift = 0,
+			.storagebits = 32,
+			.endianness = IIO_LE,
+		},
+		.scan_index = GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR,
+		.address = GP2AP020A00F_D0_L_REG,
+		.event_spec = gp2ap020a00f_event_spec_light,
+		.num_event_specs = ARRAY_SIZE(gp2ap020a00f_event_spec_light),
+	},
+	{
+		.type = IIO_LIGHT,
+		.channel2 = IIO_MOD_LIGHT_IR,
+		.modified = 1,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 24,
+			.shift = 0,
+			.storagebits = 32,
+			.endianness = IIO_LE,
+		},
+		.scan_index = GP2AP020A00F_SCAN_MODE_LIGHT_IR,
+		.address = GP2AP020A00F_D1_L_REG,
+	},
+	{
+		.type = IIO_PROXIMITY,
+		.modified = 0,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 16,
+			.shift = 0,
+			.storagebits = 16,
+			.endianness = IIO_LE,
+		},
+		.scan_index = GP2AP020A00F_SCAN_MODE_PROXIMITY,
+		.address = GP2AP020A00F_D2_L_REG,
+		.event_spec = gp2ap020a00f_event_spec_prox,
+		.num_event_specs = ARRAY_SIZE(gp2ap020a00f_event_spec_prox),
+	},
+	IIO_CHAN_SOFT_TIMESTAMP(GP2AP020A00F_CHAN_TIMESTAMP),
+};
+
+static const struct iio_info gp2ap020a00f_info = {
+	.read_raw = &gp2ap020a00f_read_raw,
+	.read_event_value = &gp2ap020a00f_read_event_val,
+	.read_event_config = &gp2ap020a00f_read_event_config,
+	.write_event_value = &gp2ap020a00f_write_event_val,
+	.write_event_config = &gp2ap020a00f_write_event_config,
+};
+
+static int gp2ap020a00f_buffer_postenable(struct iio_dev *indio_dev)
+{
+	struct gp2ap020a00f_data *data = iio_priv(indio_dev);
+	int i, err = 0;
+
+	mutex_lock(&data->lock);
+
+	/*
+	 * Enable triggers according to the scan_mask. Enabling either
+	 * LIGHT_CLEAR or LIGHT_IR scan mode results in enabling ALS
+	 * module in the device, which generates samples in both D0 (clear)
+	 * and D1 (ir) registers. As the two registers are bound to the
+	 * two separate IIO channels they are treated in the driver logic
+	 * as if they were controlled independently.
+	 */
+	for_each_set_bit(i, indio_dev->active_scan_mask,
+		indio_dev->masklength) {
+		switch (i) {
+		case GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR:
+			err = gp2ap020a00f_exec_cmd(data,
+					GP2AP020A00F_CMD_TRIGGER_CLEAR_EN);
+			break;
+		case GP2AP020A00F_SCAN_MODE_LIGHT_IR:
+			err = gp2ap020a00f_exec_cmd(data,
+					GP2AP020A00F_CMD_TRIGGER_IR_EN);
+			break;
+		case GP2AP020A00F_SCAN_MODE_PROXIMITY:
+			err = gp2ap020a00f_exec_cmd(data,
+					GP2AP020A00F_CMD_TRIGGER_PROX_EN);
+			break;
+		}
+	}
+
+	if (err < 0)
+		goto error_unlock;
+
+	data->buffer = kmalloc(indio_dev->scan_bytes, GFP_KERNEL);
+	if (!data->buffer) {
+		err = -ENOMEM;
+		goto error_unlock;
+	}
+
+	err = iio_triggered_buffer_postenable(indio_dev);
+
+error_unlock:
+	mutex_unlock(&data->lock);
+
+	return err;
+}
+
+static int gp2ap020a00f_buffer_predisable(struct iio_dev *indio_dev)
+{
+	struct gp2ap020a00f_data *data = iio_priv(indio_dev);
+	int i, err;
+
+	mutex_lock(&data->lock);
+
+	err = iio_triggered_buffer_predisable(indio_dev);
+	if (err < 0)
+		goto error_unlock;
+
+	for_each_set_bit(i, indio_dev->active_scan_mask,
+		indio_dev->masklength) {
+		switch (i) {
+		case GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR:
+			err = gp2ap020a00f_exec_cmd(data,
+					GP2AP020A00F_CMD_TRIGGER_CLEAR_DIS);
+			break;
+		case GP2AP020A00F_SCAN_MODE_LIGHT_IR:
+			err = gp2ap020a00f_exec_cmd(data,
+					GP2AP020A00F_CMD_TRIGGER_IR_DIS);
+			break;
+		case GP2AP020A00F_SCAN_MODE_PROXIMITY:
+			err = gp2ap020a00f_exec_cmd(data,
+					GP2AP020A00F_CMD_TRIGGER_PROX_DIS);
+			break;
+		}
+	}
+
+	if (err == 0)
+		kfree(data->buffer);
+
+error_unlock:
+	mutex_unlock(&data->lock);
+
+	return err;
+}
+
+static const struct iio_buffer_setup_ops gp2ap020a00f_buffer_setup_ops = {
+	.postenable = &gp2ap020a00f_buffer_postenable,
+	.predisable = &gp2ap020a00f_buffer_predisable,
+};
+
+static const struct iio_trigger_ops gp2ap020a00f_trigger_ops = {
+};
+
+static int gp2ap020a00f_probe(struct i2c_client *client,
+				const struct i2c_device_id *id)
+{
+	struct gp2ap020a00f_data *data;
+	struct iio_dev *indio_dev;
+	struct regmap *regmap;
+	int err;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+
+	data->vled_reg = devm_regulator_get(&client->dev, "vled");
+	if (IS_ERR(data->vled_reg))
+		return PTR_ERR(data->vled_reg);
+
+	err = regulator_enable(data->vled_reg);
+	if (err)
+		return err;
+
+	regmap = devm_regmap_init_i2c(client, &gp2ap020a00f_regmap_config);
+	if (IS_ERR(regmap)) {
+		dev_err(&client->dev, "Regmap initialization failed.\n");
+		err = PTR_ERR(regmap);
+		goto error_regulator_disable;
+	}
+
+	/* Initialize device registers */
+	err = regmap_bulk_write(regmap, GP2AP020A00F_OP_REG,
+			gp2ap020a00f_reg_init_tab,
+			ARRAY_SIZE(gp2ap020a00f_reg_init_tab));
+
+	if (err < 0) {
+		dev_err(&client->dev, "Device initialization failed.\n");
+		goto error_regulator_disable;
+	}
+
+	i2c_set_clientdata(client, indio_dev);
+
+	data->client = client;
+	data->cur_opmode = GP2AP020A00F_OPMODE_SHUTDOWN;
+	data->regmap = regmap;
+	init_waitqueue_head(&data->data_ready_queue);
+
+	mutex_init(&data->lock);
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->channels = gp2ap020a00f_channels;
+	indio_dev->num_channels = ARRAY_SIZE(gp2ap020a00f_channels);
+	indio_dev->info = &gp2ap020a00f_info;
+	indio_dev->name = id->name;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	/* Allocate buffer */
+	err = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time,
+		&gp2ap020a00f_trigger_handler, &gp2ap020a00f_buffer_setup_ops);
+	if (err < 0)
+		goto error_regulator_disable;
+
+	/* Allocate trigger */
+	data->trig = devm_iio_trigger_alloc(&client->dev, "%s-trigger",
+							indio_dev->name);
+	if (data->trig == NULL) {
+		err = -ENOMEM;
+		dev_err(&indio_dev->dev, "Failed to allocate iio trigger.\n");
+		goto error_uninit_buffer;
+	}
+
+	/* This needs to be requested here for read_raw calls to work. */
+	err = request_threaded_irq(client->irq, NULL,
+				   &gp2ap020a00f_thresh_event_handler,
+				   IRQF_TRIGGER_FALLING |
+				   IRQF_ONESHOT,
+				   "gp2ap020a00f_als_event",
+				   indio_dev);
+	if (err < 0) {
+		dev_err(&client->dev, "Irq request failed.\n");
+		goto error_uninit_buffer;
+	}
+
+	data->trig->ops = &gp2ap020a00f_trigger_ops;
+	data->trig->dev.parent = &data->client->dev;
+
+	init_irq_work(&data->work, gp2ap020a00f_iio_trigger_work);
+
+	err = iio_trigger_register(data->trig);
+	if (err < 0) {
+		dev_err(&client->dev, "Failed to register iio trigger.\n");
+		goto error_free_irq;
+	}
+
+	err = iio_device_register(indio_dev);
+	if (err < 0)
+		goto error_trigger_unregister;
+
+	return 0;
+
+error_trigger_unregister:
+	iio_trigger_unregister(data->trig);
+error_free_irq:
+	free_irq(client->irq, indio_dev);
+error_uninit_buffer:
+	iio_triggered_buffer_cleanup(indio_dev);
+error_regulator_disable:
+	regulator_disable(data->vled_reg);
+
+	return err;
+}
+
+static int gp2ap020a00f_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+	struct gp2ap020a00f_data *data = iio_priv(indio_dev);
+	int err;
+
+	err = gp2ap020a00f_set_operation_mode(data,
+					GP2AP020A00F_OPMODE_SHUTDOWN);
+	if (err < 0)
+		dev_err(&indio_dev->dev, "Failed to power off the device.\n");
+
+	iio_device_unregister(indio_dev);
+	iio_trigger_unregister(data->trig);
+	free_irq(client->irq, indio_dev);
+	iio_triggered_buffer_cleanup(indio_dev);
+	regulator_disable(data->vled_reg);
+
+	return 0;
+}
+
+static const struct i2c_device_id gp2ap020a00f_id[] = {
+	{ GP2A_I2C_NAME, 0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, gp2ap020a00f_id);
+
+#ifdef CONFIG_OF
+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),
+	},
+	.probe		= gp2ap020a00f_probe,
+	.remove		= gp2ap020a00f_remove,
+	.id_table	= gp2ap020a00f_id,
+};
+
+module_i2c_driver(gp2ap020a00f_driver);
+
+MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>");
+MODULE_DESCRIPTION("Sharp GP2AP020A00F Proximity/ALS sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/light/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c
new file mode 100644
index 0000000..94f3325
--- /dev/null
+++ b/drivers/iio/light/hid-sensor-als.c
@@ -0,0 +1,405 @@
+/*
+ * HID Sensors Driver
+ * Copyright (c) 2012, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions 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.
+ *
+ * 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.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/hid-sensor-hub.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include "../common/hid-sensors/hid-sensor-trigger.h"
+
+enum {
+	CHANNEL_SCAN_INDEX_INTENSITY = 0,
+	CHANNEL_SCAN_INDEX_ILLUM = 1,
+	CHANNEL_SCAN_INDEX_MAX
+};
+
+struct als_state {
+	struct hid_sensor_hub_callbacks callbacks;
+	struct hid_sensor_common common_attributes;
+	struct hid_sensor_hub_attribute_info als_illum;
+	u32 illum[CHANNEL_SCAN_INDEX_MAX];
+	int scale_pre_decml;
+	int scale_post_decml;
+	int scale_precision;
+	int value_offset;
+};
+
+/* Channel definitions */
+static const struct iio_chan_spec als_channels[] = {
+	{
+		.type = IIO_INTENSITY,
+		.modified = 1,
+		.channel2 = IIO_MOD_LIGHT_BOTH,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+		BIT(IIO_CHAN_INFO_SCALE) |
+		BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+		BIT(IIO_CHAN_INFO_HYSTERESIS),
+		.scan_index = CHANNEL_SCAN_INDEX_INTENSITY,
+	},
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+		BIT(IIO_CHAN_INFO_SCALE) |
+		BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+		BIT(IIO_CHAN_INFO_HYSTERESIS),
+		.scan_index = CHANNEL_SCAN_INDEX_ILLUM,
+	}
+};
+
+/* Adjust channel real bits based on report descriptor */
+static void als_adjust_channel_bit_mask(struct iio_chan_spec *channels,
+					int channel, int size)
+{
+	channels[channel].scan_type.sign = 's';
+	/* Real storage bits will change based on the report desc. */
+	channels[channel].scan_type.realbits = size * 8;
+	/* Maximum size of a sample to capture is u32 */
+	channels[channel].scan_type.storagebits = sizeof(u32) * 8;
+}
+
+/* Channel read_raw handler */
+static int als_read_raw(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *chan,
+			      int *val, int *val2,
+			      long mask)
+{
+	struct als_state *als_state = iio_priv(indio_dev);
+	int report_id = -1;
+	u32 address;
+	int ret_type;
+	s32 min;
+
+	*val = 0;
+	*val2 = 0;
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		switch (chan->scan_index) {
+		case  CHANNEL_SCAN_INDEX_INTENSITY:
+		case  CHANNEL_SCAN_INDEX_ILLUM:
+			report_id = als_state->als_illum.report_id;
+			min = als_state->als_illum.logical_minimum;
+			address = HID_USAGE_SENSOR_LIGHT_ILLUM;
+			break;
+		default:
+			report_id = -1;
+			break;
+		}
+		if (report_id >= 0) {
+			hid_sensor_power_state(&als_state->common_attributes,
+						true);
+			*val = sensor_hub_input_attr_get_raw_value(
+					als_state->common_attributes.hsdev,
+					HID_USAGE_SENSOR_ALS, address,
+					report_id,
+					SENSOR_HUB_SYNC,
+					min < 0);
+			hid_sensor_power_state(&als_state->common_attributes,
+						false);
+		} else {
+			*val = 0;
+			return -EINVAL;
+		}
+		ret_type = IIO_VAL_INT;
+		break;
+	case IIO_CHAN_INFO_SCALE:
+		*val = als_state->scale_pre_decml;
+		*val2 = als_state->scale_post_decml;
+		ret_type = als_state->scale_precision;
+		break;
+	case IIO_CHAN_INFO_OFFSET:
+		*val = als_state->value_offset;
+		ret_type = IIO_VAL_INT;
+		break;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		ret_type = hid_sensor_read_samp_freq_value(
+				&als_state->common_attributes, val, val2);
+		break;
+	case IIO_CHAN_INFO_HYSTERESIS:
+		ret_type = hid_sensor_read_raw_hyst_value(
+				&als_state->common_attributes, val, val2);
+		break;
+	default:
+		ret_type = -EINVAL;
+		break;
+	}
+
+	return ret_type;
+}
+
+/* Channel write_raw handler */
+static int als_write_raw(struct iio_dev *indio_dev,
+			       struct iio_chan_spec const *chan,
+			       int val,
+			       int val2,
+			       long mask)
+{
+	struct als_state *als_state = iio_priv(indio_dev);
+	int ret = 0;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		ret = hid_sensor_write_samp_freq_value(
+				&als_state->common_attributes, val, val2);
+		break;
+	case IIO_CHAN_INFO_HYSTERESIS:
+		ret = hid_sensor_write_raw_hyst_value(
+				&als_state->common_attributes, val, val2);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static const struct iio_info als_info = {
+	.read_raw = &als_read_raw,
+	.write_raw = &als_write_raw,
+};
+
+/* Function to push data to buffer */
+static void hid_sensor_push_data(struct iio_dev *indio_dev, const void *data,
+					int len)
+{
+	dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n");
+	iio_push_to_buffers(indio_dev, data);
+}
+
+/* Callback handler to send event after all samples are received and captured */
+static int als_proc_event(struct hid_sensor_hub_device *hsdev,
+				unsigned usage_id,
+				void *priv)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(priv);
+	struct als_state *als_state = iio_priv(indio_dev);
+
+	dev_dbg(&indio_dev->dev, "als_proc_event\n");
+	if (atomic_read(&als_state->common_attributes.data_ready))
+		hid_sensor_push_data(indio_dev,
+				&als_state->illum,
+				sizeof(als_state->illum));
+
+	return 0;
+}
+
+/* Capture samples in local storage */
+static int als_capture_sample(struct hid_sensor_hub_device *hsdev,
+				unsigned usage_id,
+				size_t raw_len, char *raw_data,
+				void *priv)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(priv);
+	struct als_state *als_state = iio_priv(indio_dev);
+	int ret = -EINVAL;
+	u32 sample_data = *(u32 *)raw_data;
+
+	switch (usage_id) {
+	case HID_USAGE_SENSOR_LIGHT_ILLUM:
+		als_state->illum[CHANNEL_SCAN_INDEX_INTENSITY] = sample_data;
+		als_state->illum[CHANNEL_SCAN_INDEX_ILLUM] = sample_data;
+		ret = 0;
+		break;
+	default:
+		break;
+	}
+
+	return ret;
+}
+
+/* Parse report which is specific to an usage id*/
+static int als_parse_report(struct platform_device *pdev,
+				struct hid_sensor_hub_device *hsdev,
+				struct iio_chan_spec *channels,
+				unsigned usage_id,
+				struct als_state *st)
+{
+	int ret;
+
+	ret = sensor_hub_input_get_attribute_info(hsdev, HID_INPUT_REPORT,
+			usage_id,
+			HID_USAGE_SENSOR_LIGHT_ILLUM,
+			&st->als_illum);
+	if (ret < 0)
+		return ret;
+	als_adjust_channel_bit_mask(channels, CHANNEL_SCAN_INDEX_INTENSITY,
+				    st->als_illum.size);
+	als_adjust_channel_bit_mask(channels, CHANNEL_SCAN_INDEX_ILLUM,
+					st->als_illum.size);
+
+	dev_dbg(&pdev->dev, "als %x:%x\n", st->als_illum.index,
+			st->als_illum.report_id);
+
+	st->scale_precision = hid_sensor_format_scale(
+				HID_USAGE_SENSOR_ALS,
+				&st->als_illum,
+				&st->scale_pre_decml, &st->scale_post_decml);
+
+	/* Set Sensitivity field ids, when there is no individual modifier */
+	if (st->common_attributes.sensitivity.index < 0) {
+		sensor_hub_input_get_attribute_info(hsdev,
+			HID_FEATURE_REPORT, usage_id,
+			HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS |
+			HID_USAGE_SENSOR_DATA_LIGHT,
+			&st->common_attributes.sensitivity);
+		dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n",
+			st->common_attributes.sensitivity.index,
+			st->common_attributes.sensitivity.report_id);
+	}
+	return ret;
+}
+
+/* Function to initialize the processing for usage id */
+static int hid_als_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	static const char *name = "als";
+	struct iio_dev *indio_dev;
+	struct als_state *als_state;
+	struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
+
+	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct als_state));
+	if (!indio_dev)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, indio_dev);
+
+	als_state = iio_priv(indio_dev);
+	als_state->common_attributes.hsdev = hsdev;
+	als_state->common_attributes.pdev = pdev;
+
+	ret = hid_sensor_parse_common_attributes(hsdev, HID_USAGE_SENSOR_ALS,
+					&als_state->common_attributes);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to setup common attributes\n");
+		return ret;
+	}
+
+	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,
+			       (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->num_channels =
+				ARRAY_SIZE(als_channels);
+	indio_dev->dev.parent = &pdev->dev;
+	indio_dev->info = &als_info;
+	indio_dev->name = name;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time,
+		NULL, NULL);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to initialize trigger buffer\n");
+		goto error_free_dev_mem;
+	}
+	atomic_set(&als_state->common_attributes.data_ready, 0);
+	ret = hid_sensor_setup_trigger(indio_dev, name,
+				&als_state->common_attributes);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "trigger setup failed\n");
+		goto error_unreg_buffer_funcs;
+	}
+
+	ret = iio_device_register(indio_dev);
+	if (ret) {
+		dev_err(&pdev->dev, "device register failed\n");
+		goto error_remove_trigger;
+	}
+
+	als_state->callbacks.send_event = als_proc_event;
+	als_state->callbacks.capture_sample = als_capture_sample;
+	als_state->callbacks.pdev = pdev;
+	ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_ALS,
+					&als_state->callbacks);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "callback reg failed\n");
+		goto error_iio_unreg;
+	}
+
+	return ret;
+
+error_iio_unreg:
+	iio_device_unregister(indio_dev);
+error_remove_trigger:
+	hid_sensor_remove_trigger(&als_state->common_attributes);
+error_unreg_buffer_funcs:
+	iio_triggered_buffer_cleanup(indio_dev);
+error_free_dev_mem:
+	kfree(indio_dev->channels);
+	return ret;
+}
+
+/* Function to deinitialize the processing for usage id */
+static int hid_als_remove(struct platform_device *pdev)
+{
+	struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+	struct als_state *als_state = iio_priv(indio_dev);
+
+	sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_ALS);
+	iio_device_unregister(indio_dev);
+	hid_sensor_remove_trigger(&als_state->common_attributes);
+	iio_triggered_buffer_cleanup(indio_dev);
+	kfree(indio_dev->channels);
+
+	return 0;
+}
+
+static const struct platform_device_id hid_als_ids[] = {
+	{
+		/* Format: HID-SENSOR-usage_id_in_hex_lowercase */
+		.name = "HID-SENSOR-200041",
+	},
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, hid_als_ids);
+
+static struct platform_driver hid_als_platform_driver = {
+	.id_table = hid_als_ids,
+	.driver = {
+		.name	= KBUILD_MODNAME,
+		.pm	= &hid_sensor_pm_ops,
+	},
+	.probe		= hid_als_probe,
+	.remove		= hid_als_remove,
+};
+module_platform_driver(hid_als_platform_driver);
+
+MODULE_DESCRIPTION("HID Sensor ALS");
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/hid-sensor-prox.c b/drivers/iio/light/hid-sensor-prox.c
new file mode 100644
index 0000000..cf5a0c2
--- /dev/null
+++ b/drivers/iio/light/hid-sensor-prox.c
@@ -0,0 +1,382 @@
+/*
+ * HID Sensors Driver
+ * Copyright (c) 2014, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.
+ *
+ */
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/hid-sensor-hub.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include "../common/hid-sensors/hid-sensor-trigger.h"
+
+#define CHANNEL_SCAN_INDEX_PRESENCE 0
+
+struct prox_state {
+	struct hid_sensor_hub_callbacks callbacks;
+	struct hid_sensor_common common_attributes;
+	struct hid_sensor_hub_attribute_info prox_attr;
+	u32 human_presence;
+};
+
+/* Channel definitions */
+static const struct iio_chan_spec prox_channels[] = {
+	{
+		.type = IIO_PROXIMITY,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+		BIT(IIO_CHAN_INFO_SCALE) |
+		BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+		BIT(IIO_CHAN_INFO_HYSTERESIS),
+		.scan_index = CHANNEL_SCAN_INDEX_PRESENCE,
+	}
+};
+
+/* Adjust channel real bits based on report descriptor */
+static void prox_adjust_channel_bit_mask(struct iio_chan_spec *channels,
+					int channel, int size)
+{
+	channels[channel].scan_type.sign = 's';
+	/* Real storage bits will change based on the report desc. */
+	channels[channel].scan_type.realbits = size * 8;
+	/* Maximum size of a sample to capture is u32 */
+	channels[channel].scan_type.storagebits = sizeof(u32) * 8;
+}
+
+/* Channel read_raw handler */
+static int prox_read_raw(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *chan,
+			      int *val, int *val2,
+			      long mask)
+{
+	struct prox_state *prox_state = iio_priv(indio_dev);
+	int report_id = -1;
+	u32 address;
+	int ret_type;
+	s32 min;
+
+	*val = 0;
+	*val2 = 0;
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		switch (chan->scan_index) {
+		case  CHANNEL_SCAN_INDEX_PRESENCE:
+			report_id = prox_state->prox_attr.report_id;
+			min = prox_state->prox_attr.logical_minimum;
+			address = HID_USAGE_SENSOR_HUMAN_PRESENCE;
+			break;
+		default:
+			report_id = -1;
+			break;
+		}
+		if (report_id >= 0) {
+			hid_sensor_power_state(&prox_state->common_attributes,
+						true);
+			*val = sensor_hub_input_attr_get_raw_value(
+				prox_state->common_attributes.hsdev,
+				HID_USAGE_SENSOR_PROX, address,
+				report_id,
+				SENSOR_HUB_SYNC,
+				min < 0);
+			hid_sensor_power_state(&prox_state->common_attributes,
+						false);
+		} else {
+			*val = 0;
+			return -EINVAL;
+		}
+		ret_type = IIO_VAL_INT;
+		break;
+	case IIO_CHAN_INFO_SCALE:
+		*val = prox_state->prox_attr.units;
+		ret_type = IIO_VAL_INT;
+		break;
+	case IIO_CHAN_INFO_OFFSET:
+		*val = hid_sensor_convert_exponent(
+				prox_state->prox_attr.unit_expo);
+		ret_type = IIO_VAL_INT;
+		break;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		ret_type = hid_sensor_read_samp_freq_value(
+				&prox_state->common_attributes, val, val2);
+		break;
+	case IIO_CHAN_INFO_HYSTERESIS:
+		ret_type = hid_sensor_read_raw_hyst_value(
+				&prox_state->common_attributes, val, val2);
+		break;
+	default:
+		ret_type = -EINVAL;
+		break;
+	}
+
+	return ret_type;
+}
+
+/* Channel write_raw handler */
+static int prox_write_raw(struct iio_dev *indio_dev,
+			       struct iio_chan_spec const *chan,
+			       int val,
+			       int val2,
+			       long mask)
+{
+	struct prox_state *prox_state = iio_priv(indio_dev);
+	int ret = 0;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		ret = hid_sensor_write_samp_freq_value(
+				&prox_state->common_attributes, val, val2);
+		break;
+	case IIO_CHAN_INFO_HYSTERESIS:
+		ret = hid_sensor_write_raw_hyst_value(
+				&prox_state->common_attributes, val, val2);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static const struct iio_info prox_info = {
+	.read_raw = &prox_read_raw,
+	.write_raw = &prox_write_raw,
+};
+
+/* Function to push data to buffer */
+static void hid_sensor_push_data(struct iio_dev *indio_dev, const void *data,
+					int len)
+{
+	dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n");
+	iio_push_to_buffers(indio_dev, data);
+}
+
+/* Callback handler to send event after all samples are received and captured */
+static int prox_proc_event(struct hid_sensor_hub_device *hsdev,
+				unsigned usage_id,
+				void *priv)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(priv);
+	struct prox_state *prox_state = iio_priv(indio_dev);
+
+	dev_dbg(&indio_dev->dev, "prox_proc_event\n");
+	if (atomic_read(&prox_state->common_attributes.data_ready))
+		hid_sensor_push_data(indio_dev,
+				&prox_state->human_presence,
+				sizeof(prox_state->human_presence));
+
+	return 0;
+}
+
+/* Capture samples in local storage */
+static int prox_capture_sample(struct hid_sensor_hub_device *hsdev,
+				unsigned usage_id,
+				size_t raw_len, char *raw_data,
+				void *priv)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(priv);
+	struct prox_state *prox_state = iio_priv(indio_dev);
+	int ret = -EINVAL;
+
+	switch (usage_id) {
+	case HID_USAGE_SENSOR_HUMAN_PRESENCE:
+		prox_state->human_presence = *(u32 *)raw_data;
+		ret = 0;
+		break;
+	default:
+		break;
+	}
+
+	return ret;
+}
+
+/* Parse report which is specific to an usage id*/
+static int prox_parse_report(struct platform_device *pdev,
+				struct hid_sensor_hub_device *hsdev,
+				struct iio_chan_spec *channels,
+				unsigned usage_id,
+				struct prox_state *st)
+{
+	int ret;
+
+	ret = sensor_hub_input_get_attribute_info(hsdev, HID_INPUT_REPORT,
+			usage_id,
+			HID_USAGE_SENSOR_HUMAN_PRESENCE,
+			&st->prox_attr);
+	if (ret < 0)
+		return ret;
+	prox_adjust_channel_bit_mask(channels, CHANNEL_SCAN_INDEX_PRESENCE,
+					st->prox_attr.size);
+
+	dev_dbg(&pdev->dev, "prox %x:%x\n", st->prox_attr.index,
+			st->prox_attr.report_id);
+
+	/* Set Sensitivity field ids, when there is no individual modifier */
+	if (st->common_attributes.sensitivity.index < 0) {
+		sensor_hub_input_get_attribute_info(hsdev,
+			HID_FEATURE_REPORT, usage_id,
+			HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS |
+			HID_USAGE_SENSOR_DATA_PRESENCE,
+			&st->common_attributes.sensitivity);
+		dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n",
+			st->common_attributes.sensitivity.index,
+			st->common_attributes.sensitivity.report_id);
+	}
+	if (st->common_attributes.sensitivity.index < 0)
+		sensor_hub_input_get_attribute_info(hsdev,
+			HID_FEATURE_REPORT, usage_id,
+			HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS |
+			HID_USAGE_SENSOR_HUMAN_PRESENCE,
+			&st->common_attributes.sensitivity);
+
+	return ret;
+}
+
+/* Function to initialize the processing for usage id */
+static int hid_prox_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	static const char *name = "prox";
+	struct iio_dev *indio_dev;
+	struct prox_state *prox_state;
+	struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
+
+	indio_dev = devm_iio_device_alloc(&pdev->dev,
+				sizeof(struct prox_state));
+	if (!indio_dev)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, indio_dev);
+
+	prox_state = iio_priv(indio_dev);
+	prox_state->common_attributes.hsdev = hsdev;
+	prox_state->common_attributes.pdev = pdev;
+
+	ret = hid_sensor_parse_common_attributes(hsdev, HID_USAGE_SENSOR_PROX,
+					&prox_state->common_attributes);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to setup common attributes\n");
+		return ret;
+	}
+
+	indio_dev->channels = kmemdup(prox_channels, sizeof(prox_channels),
+				      GFP_KERNEL);
+	if (!indio_dev->channels) {
+		dev_err(&pdev->dev, "failed to duplicate channels\n");
+		return -ENOMEM;
+	}
+
+	ret = prox_parse_report(pdev, hsdev,
+				(struct iio_chan_spec *)indio_dev->channels,
+				HID_USAGE_SENSOR_PROX, prox_state);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to setup attributes\n");
+		goto error_free_dev_mem;
+	}
+
+	indio_dev->num_channels = ARRAY_SIZE(prox_channels);
+	indio_dev->dev.parent = &pdev->dev;
+	indio_dev->info = &prox_info;
+	indio_dev->name = name;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time,
+		NULL, NULL);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to initialize trigger buffer\n");
+		goto error_free_dev_mem;
+	}
+	atomic_set(&prox_state->common_attributes.data_ready, 0);
+	ret = hid_sensor_setup_trigger(indio_dev, name,
+				&prox_state->common_attributes);
+	if (ret) {
+		dev_err(&pdev->dev, "trigger setup failed\n");
+		goto error_unreg_buffer_funcs;
+	}
+
+	ret = iio_device_register(indio_dev);
+	if (ret) {
+		dev_err(&pdev->dev, "device register failed\n");
+		goto error_remove_trigger;
+	}
+
+	prox_state->callbacks.send_event = prox_proc_event;
+	prox_state->callbacks.capture_sample = prox_capture_sample;
+	prox_state->callbacks.pdev = pdev;
+	ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_PROX,
+					&prox_state->callbacks);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "callback reg failed\n");
+		goto error_iio_unreg;
+	}
+
+	return ret;
+
+error_iio_unreg:
+	iio_device_unregister(indio_dev);
+error_remove_trigger:
+	hid_sensor_remove_trigger(&prox_state->common_attributes);
+error_unreg_buffer_funcs:
+	iio_triggered_buffer_cleanup(indio_dev);
+error_free_dev_mem:
+	kfree(indio_dev->channels);
+	return ret;
+}
+
+/* Function to deinitialize the processing for usage id */
+static int hid_prox_remove(struct platform_device *pdev)
+{
+	struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+	struct prox_state *prox_state = iio_priv(indio_dev);
+
+	sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_PROX);
+	iio_device_unregister(indio_dev);
+	hid_sensor_remove_trigger(&prox_state->common_attributes);
+	iio_triggered_buffer_cleanup(indio_dev);
+	kfree(indio_dev->channels);
+
+	return 0;
+}
+
+static const struct platform_device_id hid_prox_ids[] = {
+	{
+		/* Format: HID-SENSOR-usage_id_in_hex_lowercase */
+		.name = "HID-SENSOR-200011",
+	},
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, hid_prox_ids);
+
+static struct platform_driver hid_prox_platform_driver = {
+	.id_table = hid_prox_ids,
+	.driver = {
+		.name	= KBUILD_MODNAME,
+		.pm	= &hid_sensor_pm_ops,
+	},
+	.probe		= hid_prox_probe,
+	.remove		= hid_prox_remove,
+};
+module_platform_driver(hid_prox_platform_driver);
+
+MODULE_DESCRIPTION("HID Sensor Proximity");
+MODULE_AUTHOR("Archana Patni <archana.patni@intel.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/isl29018.c b/drivers/iio/light/isl29018.c
new file mode 100644
index 0000000..b45400f
--- /dev/null
+++ b/drivers/iio/light/isl29018.c
@@ -0,0 +1,847 @@
+/*
+ * A iio driver for the light sensor ISL 29018/29023/29035.
+ *
+ * IIO driver for monitoring ambient light intensity in luxi, proximity
+ * sensing and infrared sensing.
+ *
+ * Copyright (c) 2010, NVIDIA Corporation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/acpi.h>
+
+#define ISL29018_CONV_TIME_MS		100
+
+#define ISL29018_REG_ADD_COMMAND1	0x00
+#define ISL29018_CMD1_OPMODE_SHIFT	5
+#define ISL29018_CMD1_OPMODE_MASK	(7 << ISL29018_CMD1_OPMODE_SHIFT)
+#define ISL29018_CMD1_OPMODE_POWER_DOWN	0
+#define ISL29018_CMD1_OPMODE_ALS_ONCE	1
+#define ISL29018_CMD1_OPMODE_IR_ONCE	2
+#define ISL29018_CMD1_OPMODE_PROX_ONCE	3
+
+#define ISL29018_REG_ADD_COMMAND2	0x01
+#define ISL29018_CMD2_RESOLUTION_SHIFT	2
+#define ISL29018_CMD2_RESOLUTION_MASK	(0x3 << ISL29018_CMD2_RESOLUTION_SHIFT)
+
+#define ISL29018_CMD2_RANGE_SHIFT	0
+#define ISL29018_CMD2_RANGE_MASK	(0x3 << ISL29018_CMD2_RANGE_SHIFT)
+
+#define ISL29018_CMD2_SCHEME_SHIFT	7
+#define ISL29018_CMD2_SCHEME_MASK	(0x1 << ISL29018_CMD2_SCHEME_SHIFT)
+
+#define ISL29018_REG_ADD_DATA_LSB	0x02
+#define ISL29018_REG_ADD_DATA_MSB	0x03
+
+#define ISL29018_REG_TEST		0x08
+#define ISL29018_TEST_SHIFT		0
+#define ISL29018_TEST_MASK		(0xFF << ISL29018_TEST_SHIFT)
+
+#define ISL29035_REG_DEVICE_ID		0x0F
+#define ISL29035_DEVICE_ID_SHIFT	0x03
+#define ISL29035_DEVICE_ID_MASK		(0x7 << ISL29035_DEVICE_ID_SHIFT)
+#define ISL29035_DEVICE_ID		0x5
+#define ISL29035_BOUT_SHIFT		0x07
+#define ISL29035_BOUT_MASK		(0x01 << ISL29035_BOUT_SHIFT)
+
+enum isl29018_int_time {
+	ISL29018_INT_TIME_16,
+	ISL29018_INT_TIME_12,
+	ISL29018_INT_TIME_8,
+	ISL29018_INT_TIME_4,
+};
+
+static const unsigned int isl29018_int_utimes[3][4] = {
+	{90000, 5630, 351, 21},
+	{90000, 5600, 352, 22},
+	{105000, 6500, 410, 25},
+};
+
+static const struct isl29018_scale {
+	unsigned int scale;
+	unsigned int uscale;
+} isl29018_scales[4][4] = {
+	{ {0, 15258}, {0, 61035}, {0, 244140}, {0, 976562} },
+	{ {0, 244140}, {0, 976562}, {3, 906250}, {15, 625000} },
+	{ {3, 906250}, {15, 625000}, {62, 500000}, {250, 0} },
+	{ {62, 500000}, {250, 0}, {1000, 0}, {4000, 0} }
+};
+
+struct isl29018_chip {
+	struct regmap		*regmap;
+	struct mutex		lock;
+	int			type;
+	unsigned int		calibscale;
+	unsigned int		ucalibscale;
+	unsigned int		int_time;
+	struct isl29018_scale	scale;
+	int			prox_scheme;
+	bool			suspended;
+};
+
+static int isl29018_set_integration_time(struct isl29018_chip *chip,
+					 unsigned int utime)
+{
+	unsigned int i;
+	int ret;
+	unsigned int int_time, new_int_time;
+
+	for (i = 0; i < ARRAY_SIZE(isl29018_int_utimes[chip->type]); ++i) {
+		if (utime == isl29018_int_utimes[chip->type][i]) {
+			new_int_time = i;
+			break;
+		}
+	}
+
+	if (i >= ARRAY_SIZE(isl29018_int_utimes[chip->type]))
+		return -EINVAL;
+
+	ret = regmap_update_bits(chip->regmap, ISL29018_REG_ADD_COMMAND2,
+				 ISL29018_CMD2_RESOLUTION_MASK,
+				 i << ISL29018_CMD2_RESOLUTION_SHIFT);
+	if (ret < 0)
+		return ret;
+
+	/* Keep the same range when integration time changes */
+	int_time = chip->int_time;
+	for (i = 0; i < ARRAY_SIZE(isl29018_scales[int_time]); ++i) {
+		if (chip->scale.scale == isl29018_scales[int_time][i].scale &&
+		    chip->scale.uscale == isl29018_scales[int_time][i].uscale) {
+			chip->scale = isl29018_scales[new_int_time][i];
+			break;
+		}
+	}
+	chip->int_time = new_int_time;
+
+	return 0;
+}
+
+static int isl29018_set_scale(struct isl29018_chip *chip, int scale, int uscale)
+{
+	unsigned int i;
+	int ret;
+	struct isl29018_scale new_scale;
+
+	for (i = 0; i < ARRAY_SIZE(isl29018_scales[chip->int_time]); ++i) {
+		if (scale == isl29018_scales[chip->int_time][i].scale &&
+		    uscale == isl29018_scales[chip->int_time][i].uscale) {
+			new_scale = isl29018_scales[chip->int_time][i];
+			break;
+		}
+	}
+
+	if (i >= ARRAY_SIZE(isl29018_scales[chip->int_time]))
+		return -EINVAL;
+
+	ret = regmap_update_bits(chip->regmap, ISL29018_REG_ADD_COMMAND2,
+				 ISL29018_CMD2_RANGE_MASK,
+				 i << ISL29018_CMD2_RANGE_SHIFT);
+	if (ret < 0)
+		return ret;
+
+	chip->scale = new_scale;
+
+	return 0;
+}
+
+static int isl29018_read_sensor_input(struct isl29018_chip *chip, int mode)
+{
+	int status;
+	unsigned int lsb;
+	unsigned int msb;
+	struct device *dev = regmap_get_device(chip->regmap);
+
+	/* Set mode */
+	status = regmap_write(chip->regmap, ISL29018_REG_ADD_COMMAND1,
+			      mode << ISL29018_CMD1_OPMODE_SHIFT);
+	if (status) {
+		dev_err(dev,
+			"Error in setting operating mode err %d\n", status);
+		return status;
+	}
+	msleep(ISL29018_CONV_TIME_MS);
+	status = regmap_read(chip->regmap, ISL29018_REG_ADD_DATA_LSB, &lsb);
+	if (status < 0) {
+		dev_err(dev,
+			"Error in reading LSB DATA with err %d\n", status);
+		return status;
+	}
+
+	status = regmap_read(chip->regmap, ISL29018_REG_ADD_DATA_MSB, &msb);
+	if (status < 0) {
+		dev_err(dev,
+			"Error in reading MSB DATA with error %d\n", status);
+		return status;
+	}
+	dev_vdbg(dev, "MSB 0x%x and LSB 0x%x\n", msb, lsb);
+
+	return (msb << 8) | lsb;
+}
+
+static int isl29018_read_lux(struct isl29018_chip *chip, int *lux)
+{
+	int lux_data;
+	unsigned int data_x_range;
+
+	lux_data = isl29018_read_sensor_input(chip,
+					      ISL29018_CMD1_OPMODE_ALS_ONCE);
+	if (lux_data < 0)
+		return lux_data;
+
+	data_x_range = lux_data * chip->scale.scale +
+		       lux_data * chip->scale.uscale / 1000000;
+	*lux = data_x_range * chip->calibscale +
+	       data_x_range * chip->ucalibscale / 1000000;
+
+	return 0;
+}
+
+static int isl29018_read_ir(struct isl29018_chip *chip, int *ir)
+{
+	int ir_data;
+
+	ir_data = isl29018_read_sensor_input(chip,
+					     ISL29018_CMD1_OPMODE_IR_ONCE);
+	if (ir_data < 0)
+		return ir_data;
+
+	*ir = ir_data;
+
+	return 0;
+}
+
+static int isl29018_read_proximity_ir(struct isl29018_chip *chip, int scheme,
+				      int *near_ir)
+{
+	int status;
+	int prox_data = -1;
+	int ir_data = -1;
+	struct device *dev = regmap_get_device(chip->regmap);
+
+	/* Do proximity sensing with required scheme */
+	status = regmap_update_bits(chip->regmap, ISL29018_REG_ADD_COMMAND2,
+				    ISL29018_CMD2_SCHEME_MASK,
+				    scheme << ISL29018_CMD2_SCHEME_SHIFT);
+	if (status) {
+		dev_err(dev, "Error in setting operating mode\n");
+		return status;
+	}
+
+	prox_data = isl29018_read_sensor_input(chip,
+					       ISL29018_CMD1_OPMODE_PROX_ONCE);
+	if (prox_data < 0)
+		return prox_data;
+
+	if (scheme == 1) {
+		*near_ir = prox_data;
+		return 0;
+	}
+
+	ir_data = isl29018_read_sensor_input(chip,
+					     ISL29018_CMD1_OPMODE_IR_ONCE);
+	if (ir_data < 0)
+		return ir_data;
+
+	if (prox_data >= ir_data)
+		*near_ir = prox_data - ir_data;
+	else
+		*near_ir = 0;
+
+	return 0;
+}
+
+static ssize_t in_illuminance_scale_available_show
+			(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct isl29018_chip *chip = iio_priv(indio_dev);
+	unsigned int i;
+	int len = 0;
+
+	mutex_lock(&chip->lock);
+	for (i = 0; i < ARRAY_SIZE(isl29018_scales[chip->int_time]); ++i)
+		len += sprintf(buf + len, "%d.%06d ",
+			       isl29018_scales[chip->int_time][i].scale,
+			       isl29018_scales[chip->int_time][i].uscale);
+	mutex_unlock(&chip->lock);
+
+	buf[len - 1] = '\n';
+
+	return len;
+}
+
+static ssize_t in_illuminance_integration_time_available_show
+			(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct isl29018_chip *chip = iio_priv(indio_dev);
+	unsigned int i;
+	int len = 0;
+
+	for (i = 0; i < ARRAY_SIZE(isl29018_int_utimes[chip->type]); ++i)
+		len += sprintf(buf + len, "0.%06d ",
+			       isl29018_int_utimes[chip->type][i]);
+
+	buf[len - 1] = '\n';
+
+	return len;
+}
+
+/*
+ * From ISL29018 Data Sheet (FN6619.4, Oct 8, 2012) regarding the
+ * infrared suppression:
+ *
+ *   Proximity Sensing Scheme: Bit 7. This bit programs the function
+ * of the proximity detection. Logic 0 of this bit, Scheme 0, makes
+ * full n (4, 8, 12, 16) bits (unsigned) proximity detection. The range
+ * of Scheme 0 proximity count is from 0 to 2^n. Logic 1 of this bit,
+ * Scheme 1, makes n-1 (3, 7, 11, 15) bits (2's complementary)
+ * proximity_less_ambient detection. The range of Scheme 1
+ * proximity count is from -2^(n-1) to 2^(n-1) . The sign bit is extended
+ * for resolutions less than 16. While Scheme 0 has wider dynamic
+ * range, Scheme 1 proximity detection is less affected by the
+ * ambient IR noise variation.
+ *
+ * 0 Sensing IR from LED and ambient
+ * 1 Sensing IR from LED with ambient IR rejection
+ */
+static ssize_t proximity_on_chip_ambient_infrared_suppression_show
+			(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct isl29018_chip *chip = iio_priv(indio_dev);
+
+	/*
+	 * Return the "proximity scheme" i.e. if the chip does on chip
+	 * infrared suppression (1 means perform on chip suppression)
+	 */
+	return sprintf(buf, "%d\n", chip->prox_scheme);
+}
+
+static ssize_t proximity_on_chip_ambient_infrared_suppression_store
+			(struct device *dev, struct device_attribute *attr,
+			 const char *buf, size_t count)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct isl29018_chip *chip = iio_priv(indio_dev);
+	int val;
+
+	if (kstrtoint(buf, 10, &val))
+		return -EINVAL;
+	if (!(val == 0 || val == 1))
+		return -EINVAL;
+
+	/*
+	 * Get the "proximity scheme" i.e. if the chip does on chip
+	 * infrared suppression (1 means perform on chip suppression)
+	 */
+	mutex_lock(&chip->lock);
+	chip->prox_scheme = val;
+	mutex_unlock(&chip->lock);
+
+	return count;
+}
+
+static int isl29018_write_raw(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *chan,
+			      int val,
+			      int val2,
+			      long mask)
+{
+	struct isl29018_chip *chip = iio_priv(indio_dev);
+	int ret = -EINVAL;
+
+	mutex_lock(&chip->lock);
+	if (chip->suspended) {
+		ret = -EBUSY;
+		goto write_done;
+	}
+	switch (mask) {
+	case IIO_CHAN_INFO_CALIBSCALE:
+		if (chan->type == IIO_LIGHT) {
+			chip->calibscale = val;
+			chip->ucalibscale = val2;
+			ret = 0;
+		}
+		break;
+	case IIO_CHAN_INFO_INT_TIME:
+		if (chan->type == IIO_LIGHT && !val)
+			ret = isl29018_set_integration_time(chip, val2);
+		break;
+	case IIO_CHAN_INFO_SCALE:
+		if (chan->type == IIO_LIGHT)
+			ret = isl29018_set_scale(chip, val, val2);
+		break;
+	default:
+		break;
+	}
+
+write_done:
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+static int isl29018_read_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int *val,
+			     int *val2,
+			     long mask)
+{
+	int ret = -EINVAL;
+	struct isl29018_chip *chip = iio_priv(indio_dev);
+
+	mutex_lock(&chip->lock);
+	if (chip->suspended) {
+		ret = -EBUSY;
+		goto read_done;
+	}
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+	case IIO_CHAN_INFO_PROCESSED:
+		switch (chan->type) {
+		case IIO_LIGHT:
+			ret = isl29018_read_lux(chip, val);
+			break;
+		case IIO_INTENSITY:
+			ret = isl29018_read_ir(chip, val);
+			break;
+		case IIO_PROXIMITY:
+			ret = isl29018_read_proximity_ir(chip,
+							 chip->prox_scheme,
+							 val);
+			break;
+		default:
+			break;
+		}
+		if (!ret)
+			ret = IIO_VAL_INT;
+		break;
+	case IIO_CHAN_INFO_INT_TIME:
+		if (chan->type == IIO_LIGHT) {
+			*val = 0;
+			*val2 = isl29018_int_utimes[chip->type][chip->int_time];
+			ret = IIO_VAL_INT_PLUS_MICRO;
+		}
+		break;
+	case IIO_CHAN_INFO_SCALE:
+		if (chan->type == IIO_LIGHT) {
+			*val = chip->scale.scale;
+			*val2 = chip->scale.uscale;
+			ret = IIO_VAL_INT_PLUS_MICRO;
+		}
+		break;
+	case IIO_CHAN_INFO_CALIBSCALE:
+		if (chan->type == IIO_LIGHT) {
+			*val = chip->calibscale;
+			*val2 = chip->ucalibscale;
+			ret = IIO_VAL_INT_PLUS_MICRO;
+		}
+		break;
+	default:
+		break;
+	}
+
+read_done:
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+#define ISL29018_LIGHT_CHANNEL {					\
+	.type = IIO_LIGHT,						\
+	.indexed = 1,							\
+	.channel = 0,							\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |		\
+	BIT(IIO_CHAN_INFO_CALIBSCALE) |					\
+	BIT(IIO_CHAN_INFO_SCALE) |					\
+	BIT(IIO_CHAN_INFO_INT_TIME),					\
+}
+
+#define ISL29018_IR_CHANNEL {						\
+	.type = IIO_INTENSITY,						\
+	.modified = 1,							\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),			\
+	.channel2 = IIO_MOD_LIGHT_IR,					\
+}
+
+#define ISL29018_PROXIMITY_CHANNEL {					\
+	.type = IIO_PROXIMITY,						\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),			\
+}
+
+static const struct iio_chan_spec isl29018_channels[] = {
+	ISL29018_LIGHT_CHANNEL,
+	ISL29018_IR_CHANNEL,
+	ISL29018_PROXIMITY_CHANNEL,
+};
+
+static const struct iio_chan_spec isl29023_channels[] = {
+	ISL29018_LIGHT_CHANNEL,
+	ISL29018_IR_CHANNEL,
+};
+
+static IIO_DEVICE_ATTR_RO(in_illuminance_integration_time_available, 0);
+static IIO_DEVICE_ATTR_RO(in_illuminance_scale_available, 0);
+static IIO_DEVICE_ATTR_RW(proximity_on_chip_ambient_infrared_suppression, 0);
+
+#define ISL29018_DEV_ATTR(name) (&iio_dev_attr_##name.dev_attr.attr)
+
+static struct attribute *isl29018_attributes[] = {
+	ISL29018_DEV_ATTR(in_illuminance_scale_available),
+	ISL29018_DEV_ATTR(in_illuminance_integration_time_available),
+	ISL29018_DEV_ATTR(proximity_on_chip_ambient_infrared_suppression),
+	NULL
+};
+
+static struct attribute *isl29023_attributes[] = {
+	ISL29018_DEV_ATTR(in_illuminance_scale_available),
+	ISL29018_DEV_ATTR(in_illuminance_integration_time_available),
+	NULL
+};
+
+static const struct attribute_group isl29018_group = {
+	.attrs = isl29018_attributes,
+};
+
+static const struct attribute_group isl29023_group = {
+	.attrs = isl29023_attributes,
+};
+
+enum {
+	isl29018,
+	isl29023,
+	isl29035,
+};
+
+static int isl29018_chip_init(struct isl29018_chip *chip)
+{
+	int status;
+	struct device *dev = regmap_get_device(chip->regmap);
+
+	if (chip->type == isl29035) {
+		unsigned int id;
+
+		status = regmap_read(chip->regmap, ISL29035_REG_DEVICE_ID, &id);
+		if (status < 0) {
+			dev_err(dev,
+				"Error reading ID register with error %d\n",
+				status);
+			return status;
+		}
+
+		id = (id & ISL29035_DEVICE_ID_MASK) >> ISL29035_DEVICE_ID_SHIFT;
+
+		if (id != ISL29035_DEVICE_ID)
+			return -ENODEV;
+
+		/* Clear brownout bit */
+		status = regmap_update_bits(chip->regmap,
+					    ISL29035_REG_DEVICE_ID,
+					    ISL29035_BOUT_MASK, 0);
+		if (status < 0)
+			return status;
+	}
+
+	/*
+	 * Code added per Intersil Application Note 1534:
+	 *     When VDD sinks to approximately 1.8V or below, some of
+	 * the part's registers may change their state. When VDD
+	 * recovers to 2.25V (or greater), the part may thus be in an
+	 * unknown mode of operation. The user can return the part to
+	 * a known mode of operation either by (a) setting VDD = 0V for
+	 * 1 second or more and then powering back up with a slew rate
+	 * of 0.5V/ms or greater, or (b) via I2C disable all ALS/PROX
+	 * conversions, clear the test registers, and then rewrite all
+	 * registers to the desired values.
+	 * ...
+	 * For ISL29011, ISL29018, ISL29021, ISL29023
+	 * 1. Write 0x00 to register 0x08 (TEST)
+	 * 2. Write 0x00 to register 0x00 (CMD1)
+	 * 3. Rewrite all registers to the desired values
+	 *
+	 * ISL29018 Data Sheet (FN6619.1, Feb 11, 2010) essentially says
+	 * the same thing EXCEPT the data sheet asks for a 1ms delay after
+	 * writing the CMD1 register.
+	 */
+	status = regmap_write(chip->regmap, ISL29018_REG_TEST, 0x0);
+	if (status < 0) {
+		dev_err(dev, "Failed to clear isl29018 TEST reg.(%d)\n",
+			status);
+		return status;
+	}
+
+	/*
+	 * See Intersil AN1534 comments above.
+	 * "Operating Mode" (COMMAND1) register is reprogrammed when
+	 * data is read from the device.
+	 */
+	status = regmap_write(chip->regmap, ISL29018_REG_ADD_COMMAND1, 0);
+	if (status < 0) {
+		dev_err(dev, "Failed to clear isl29018 CMD1 reg.(%d)\n",
+			status);
+		return status;
+	}
+
+	usleep_range(1000, 2000);	/* per data sheet, page 10 */
+
+	/* Set defaults */
+	status = isl29018_set_scale(chip, chip->scale.scale,
+				    chip->scale.uscale);
+	if (status < 0) {
+		dev_err(dev, "Init of isl29018 fails\n");
+		return status;
+	}
+
+	status = isl29018_set_integration_time(chip,
+			isl29018_int_utimes[chip->type][chip->int_time]);
+	if (status < 0)
+		dev_err(dev, "Init of isl29018 fails\n");
+
+	return status;
+}
+
+static const struct iio_info isl29018_info = {
+	.attrs = &isl29018_group,
+	.read_raw = isl29018_read_raw,
+	.write_raw = isl29018_write_raw,
+};
+
+static const struct iio_info isl29023_info = {
+	.attrs = &isl29023_group,
+	.read_raw = isl29018_read_raw,
+	.write_raw = isl29018_write_raw,
+};
+
+static bool isl29018_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case ISL29018_REG_ADD_DATA_LSB:
+	case ISL29018_REG_ADD_DATA_MSB:
+	case ISL29018_REG_ADD_COMMAND1:
+	case ISL29018_REG_TEST:
+	case ISL29035_REG_DEVICE_ID:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config isl29018_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.volatile_reg = isl29018_is_volatile_reg,
+	.max_register = ISL29018_REG_TEST,
+	.num_reg_defaults_raw = ISL29018_REG_TEST + 1,
+	.cache_type = REGCACHE_RBTREE,
+};
+
+static const struct regmap_config isl29035_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.volatile_reg = isl29018_is_volatile_reg,
+	.max_register = ISL29035_REG_DEVICE_ID,
+	.num_reg_defaults_raw = ISL29035_REG_DEVICE_ID + 1,
+	.cache_type = REGCACHE_RBTREE,
+};
+
+struct isl29018_chip_info {
+	const struct iio_chan_spec *channels;
+	int num_channels;
+	const struct iio_info *indio_info;
+	const struct regmap_config *regmap_cfg;
+};
+
+static const struct isl29018_chip_info isl29018_chip_info_tbl[] = {
+	[isl29018] = {
+		.channels = isl29018_channels,
+		.num_channels = ARRAY_SIZE(isl29018_channels),
+		.indio_info = &isl29018_info,
+		.regmap_cfg = &isl29018_regmap_config,
+	},
+	[isl29023] = {
+		.channels = isl29023_channels,
+		.num_channels = ARRAY_SIZE(isl29023_channels),
+		.indio_info = &isl29023_info,
+		.regmap_cfg = &isl29018_regmap_config,
+	},
+	[isl29035] = {
+		.channels = isl29023_channels,
+		.num_channels = ARRAY_SIZE(isl29023_channels),
+		.indio_info = &isl29023_info,
+		.regmap_cfg = &isl29035_regmap_config,
+	},
+};
+
+static const char *isl29018_match_acpi_device(struct device *dev, int *data)
+{
+	const struct acpi_device_id *id;
+
+	id = acpi_match_device(dev->driver->acpi_match_table, dev);
+
+	if (!id)
+		return NULL;
+
+	*data = (int)id->driver_data;
+
+	return dev_name(dev);
+}
+
+static int isl29018_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct isl29018_chip *chip;
+	struct iio_dev *indio_dev;
+	int err;
+	const char *name = NULL;
+	int dev_id = 0;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	chip = iio_priv(indio_dev);
+
+	i2c_set_clientdata(client, indio_dev);
+
+	if (id) {
+		name = id->name;
+		dev_id = id->driver_data;
+	}
+
+	if (ACPI_HANDLE(&client->dev))
+		name = isl29018_match_acpi_device(&client->dev, &dev_id);
+
+	mutex_init(&chip->lock);
+
+	chip->type = dev_id;
+	chip->calibscale = 1;
+	chip->ucalibscale = 0;
+	chip->int_time = ISL29018_INT_TIME_16;
+	chip->scale = isl29018_scales[chip->int_time][0];
+	chip->suspended = false;
+
+	chip->regmap = devm_regmap_init_i2c(client,
+				isl29018_chip_info_tbl[dev_id].regmap_cfg);
+	if (IS_ERR(chip->regmap)) {
+		err = PTR_ERR(chip->regmap);
+		dev_err(&client->dev, "regmap initialization fails: %d\n", err);
+		return err;
+	}
+
+	err = isl29018_chip_init(chip);
+	if (err)
+		return err;
+
+	indio_dev->info = isl29018_chip_info_tbl[dev_id].indio_info;
+	indio_dev->channels = isl29018_chip_info_tbl[dev_id].channels;
+	indio_dev->num_channels = isl29018_chip_info_tbl[dev_id].num_channels;
+	indio_dev->name = name;
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int isl29018_suspend(struct device *dev)
+{
+	struct isl29018_chip *chip = iio_priv(dev_get_drvdata(dev));
+
+	mutex_lock(&chip->lock);
+
+	/*
+	 * Since this driver uses only polling commands, we are by default in
+	 * auto shutdown (ie, power-down) mode.
+	 * So we do not have much to do here.
+	 */
+	chip->suspended = true;
+
+	mutex_unlock(&chip->lock);
+
+	return 0;
+}
+
+static int isl29018_resume(struct device *dev)
+{
+	struct isl29018_chip *chip = iio_priv(dev_get_drvdata(dev));
+	int err;
+
+	mutex_lock(&chip->lock);
+
+	err = isl29018_chip_init(chip);
+	if (!err)
+		chip->suspended = false;
+
+	mutex_unlock(&chip->lock);
+
+	return err;
+}
+
+static SIMPLE_DEV_PM_OPS(isl29018_pm_ops, isl29018_suspend, isl29018_resume);
+#define ISL29018_PM_OPS (&isl29018_pm_ops)
+#else
+#define ISL29018_PM_OPS NULL
+#endif
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id isl29018_acpi_match[] = {
+	{"ISL29018", isl29018},
+	{"ISL29023", isl29023},
+	{"ISL29035", isl29035},
+	{},
+};
+MODULE_DEVICE_TABLE(acpi, isl29018_acpi_match);
+#endif
+
+static const struct i2c_device_id isl29018_id[] = {
+	{"isl29018", isl29018},
+	{"isl29023", isl29023},
+	{"isl29035", isl29035},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, isl29018_id);
+
+static const struct of_device_id isl29018_of_match[] = {
+	{ .compatible = "isil,isl29018", },
+	{ .compatible = "isil,isl29023", },
+	{ .compatible = "isil,isl29035", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, isl29018_of_match);
+
+static struct i2c_driver isl29018_driver = {
+	.driver	 = {
+			.name = "isl29018",
+			.acpi_match_table = ACPI_PTR(isl29018_acpi_match),
+			.pm = ISL29018_PM_OPS,
+			.of_match_table = isl29018_of_match,
+		    },
+	.probe	 = isl29018_probe,
+	.id_table = isl29018_id,
+};
+module_i2c_driver(isl29018_driver);
+
+MODULE_DESCRIPTION("ISL29018 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/isl29028.c b/drivers/iio/light/isl29028.c
new file mode 100644
index 0000000..f9912ab
--- /dev/null
+++ b/drivers/iio/light/isl29028.c
@@ -0,0 +1,728 @@
+/*
+ * IIO driver for the light sensor ISL29028.
+ * ISL29028 is Concurrent Ambient Light and Proximity Sensor
+ *
+ * Copyright (c) 2012, NVIDIA CORPORATION.  All rights reserved.
+ * Copyright (c) 2016-2017 Brian Masney <masneyb@onstation.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Datasheets:
+ *  - http://www.intersil.com/content/dam/Intersil/documents/isl2/isl29028.pdf
+ *  - http://www.intersil.com/content/dam/Intersil/documents/isl2/isl29030.pdf
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/pm_runtime.h>
+
+#define ISL29028_CONV_TIME_MS			100
+
+#define ISL29028_REG_CONFIGURE			0x01
+
+#define ISL29028_CONF_ALS_IR_MODE_ALS		0
+#define ISL29028_CONF_ALS_IR_MODE_IR		BIT(0)
+#define ISL29028_CONF_ALS_IR_MODE_MASK		BIT(0)
+
+#define ISL29028_CONF_ALS_RANGE_LOW_LUX		0
+#define ISL29028_CONF_ALS_RANGE_HIGH_LUX	BIT(1)
+#define ISL29028_CONF_ALS_RANGE_MASK		BIT(1)
+
+#define ISL29028_CONF_ALS_DIS			0
+#define ISL29028_CONF_ALS_EN			BIT(2)
+#define ISL29028_CONF_ALS_EN_MASK		BIT(2)
+
+#define ISL29028_CONF_PROX_SLP_SH		4
+#define ISL29028_CONF_PROX_SLP_MASK		(7 << ISL29028_CONF_PROX_SLP_SH)
+
+#define ISL29028_CONF_PROX_EN			BIT(7)
+#define ISL29028_CONF_PROX_EN_MASK		BIT(7)
+
+#define ISL29028_REG_INTERRUPT			0x02
+
+#define ISL29028_REG_PROX_DATA			0x08
+#define ISL29028_REG_ALSIR_L			0x09
+#define ISL29028_REG_ALSIR_U			0x0A
+
+#define ISL29028_REG_TEST1_MODE			0x0E
+#define ISL29028_REG_TEST2_MODE			0x0F
+
+#define ISL29028_NUM_REGS			(ISL29028_REG_TEST2_MODE + 1)
+
+#define ISL29028_POWER_OFF_DELAY_MS		2000
+
+struct isl29028_prox_data {
+	int sampling_int;
+	int sampling_fract;
+	int sleep_time;
+};
+
+static const struct isl29028_prox_data isl29028_prox_data[] = {
+	{   1, 250000, 800 },
+	{   2, 500000, 400 },
+	{   5,      0, 200 },
+	{  10,      0, 100 },
+	{  13, 300000,  75 },
+	{  20,      0,  50 },
+	{  80,      0,  13 }, /*
+			       * Note: Data sheet lists 12.5 ms sleep time.
+			       * Round up a half millisecond for msleep().
+			       */
+	{ 100,  0,   0 }
+};
+
+enum isl29028_als_ir_mode {
+	ISL29028_MODE_NONE = 0,
+	ISL29028_MODE_ALS,
+	ISL29028_MODE_IR,
+};
+
+struct isl29028_chip {
+	struct mutex			lock;
+	struct regmap			*regmap;
+	int				prox_sampling_int;
+	int				prox_sampling_frac;
+	bool				enable_prox;
+	int				lux_scale;
+	enum isl29028_als_ir_mode	als_ir_mode;
+};
+
+static int isl29028_find_prox_sleep_index(int sampling_int, int sampling_fract)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(isl29028_prox_data); ++i) {
+		if (isl29028_prox_data[i].sampling_int == sampling_int &&
+		    isl29028_prox_data[i].sampling_fract == sampling_fract)
+			return i;
+	}
+
+	return -EINVAL;
+}
+
+static int isl29028_set_proxim_sampling(struct isl29028_chip *chip,
+					int sampling_int, int sampling_fract)
+{
+	struct device *dev = regmap_get_device(chip->regmap);
+	int sleep_index, ret;
+
+	sleep_index = isl29028_find_prox_sleep_index(sampling_int,
+						     sampling_fract);
+	if (sleep_index < 0)
+		return sleep_index;
+
+	ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE,
+				 ISL29028_CONF_PROX_SLP_MASK,
+				 sleep_index << ISL29028_CONF_PROX_SLP_SH);
+
+	if (ret < 0) {
+		dev_err(dev, "%s(): Error %d setting the proximity sampling\n",
+			__func__, ret);
+		return ret;
+	}
+
+	chip->prox_sampling_int = sampling_int;
+	chip->prox_sampling_frac = sampling_fract;
+
+	return ret;
+}
+
+static int isl29028_enable_proximity(struct isl29028_chip *chip)
+{
+	int prox_index, ret;
+
+	ret = isl29028_set_proxim_sampling(chip, chip->prox_sampling_int,
+					   chip->prox_sampling_frac);
+	if (ret < 0)
+		return ret;
+
+	ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE,
+				 ISL29028_CONF_PROX_EN_MASK,
+				 ISL29028_CONF_PROX_EN);
+	if (ret < 0)
+		return ret;
+
+	/* Wait for conversion to be complete for first sample */
+	prox_index = isl29028_find_prox_sleep_index(chip->prox_sampling_int,
+						    chip->prox_sampling_frac);
+	if (prox_index < 0)
+		return prox_index;
+
+	msleep(isl29028_prox_data[prox_index].sleep_time);
+
+	return 0;
+}
+
+static int isl29028_set_als_scale(struct isl29028_chip *chip, int lux_scale)
+{
+	struct device *dev = regmap_get_device(chip->regmap);
+	int val = (lux_scale == 2000) ? ISL29028_CONF_ALS_RANGE_HIGH_LUX :
+					ISL29028_CONF_ALS_RANGE_LOW_LUX;
+	int ret;
+
+	ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE,
+				 ISL29028_CONF_ALS_RANGE_MASK, val);
+	if (ret < 0) {
+		dev_err(dev, "%s(): Error %d setting the ALS scale\n", __func__,
+			ret);
+		return ret;
+	}
+
+	chip->lux_scale = lux_scale;
+
+	return ret;
+}
+
+static int isl29028_set_als_ir_mode(struct isl29028_chip *chip,
+				    enum isl29028_als_ir_mode mode)
+{
+	int ret;
+
+	if (chip->als_ir_mode == mode)
+		return 0;
+
+	ret = isl29028_set_als_scale(chip, chip->lux_scale);
+	if (ret < 0)
+		return ret;
+
+	switch (mode) {
+	case ISL29028_MODE_ALS:
+		ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE,
+					 ISL29028_CONF_ALS_IR_MODE_MASK,
+					 ISL29028_CONF_ALS_IR_MODE_ALS);
+		if (ret < 0)
+			return ret;
+
+		ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE,
+					 ISL29028_CONF_ALS_RANGE_MASK,
+					 ISL29028_CONF_ALS_RANGE_HIGH_LUX);
+		break;
+	case ISL29028_MODE_IR:
+		ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE,
+					 ISL29028_CONF_ALS_IR_MODE_MASK,
+					 ISL29028_CONF_ALS_IR_MODE_IR);
+		break;
+	case ISL29028_MODE_NONE:
+		return regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE,
+					  ISL29028_CONF_ALS_EN_MASK,
+					  ISL29028_CONF_ALS_DIS);
+	}
+
+	if (ret < 0)
+		return ret;
+
+	/* Enable the ALS/IR */
+	ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE,
+				 ISL29028_CONF_ALS_EN_MASK,
+				 ISL29028_CONF_ALS_EN);
+	if (ret < 0)
+		return ret;
+
+	/* Need to wait for conversion time if ALS/IR mode enabled */
+	msleep(ISL29028_CONV_TIME_MS);
+
+	chip->als_ir_mode = mode;
+
+	return 0;
+}
+
+static int isl29028_read_als_ir(struct isl29028_chip *chip, int *als_ir)
+{
+	struct device *dev = regmap_get_device(chip->regmap);
+	unsigned int lsb;
+	unsigned int msb;
+	int ret;
+
+	ret = regmap_read(chip->regmap, ISL29028_REG_ALSIR_L, &lsb);
+	if (ret < 0) {
+		dev_err(dev,
+			"%s(): Error %d reading register ALSIR_L\n",
+			__func__, ret);
+		return ret;
+	}
+
+	ret = regmap_read(chip->regmap, ISL29028_REG_ALSIR_U, &msb);
+	if (ret < 0) {
+		dev_err(dev,
+			"%s(): Error %d reading register ALSIR_U\n",
+			__func__, ret);
+		return ret;
+	}
+
+	*als_ir = ((msb & 0xF) << 8) | (lsb & 0xFF);
+
+	return 0;
+}
+
+static int isl29028_read_proxim(struct isl29028_chip *chip, int *prox)
+{
+	struct device *dev = regmap_get_device(chip->regmap);
+	unsigned int data;
+	int ret;
+
+	if (!chip->enable_prox) {
+		ret = isl29028_enable_proximity(chip);
+		if (ret < 0)
+			return ret;
+
+		chip->enable_prox = true;
+	}
+
+	ret = regmap_read(chip->regmap, ISL29028_REG_PROX_DATA, &data);
+	if (ret < 0) {
+		dev_err(dev, "%s(): Error %d reading register PROX_DATA\n",
+			__func__, ret);
+		return ret;
+	}
+
+	*prox = data;
+
+	return 0;
+}
+
+static int isl29028_als_get(struct isl29028_chip *chip, int *als_data)
+{
+	struct device *dev = regmap_get_device(chip->regmap);
+	int ret;
+	int als_ir_data;
+
+	ret = isl29028_set_als_ir_mode(chip, ISL29028_MODE_ALS);
+	if (ret < 0) {
+		dev_err(dev, "%s(): Error %d enabling ALS mode\n", __func__,
+			ret);
+		return ret;
+	}
+
+	ret = isl29028_read_als_ir(chip, &als_ir_data);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * convert als data count to lux.
+	 * if lux_scale = 125,  lux = count * 0.031
+	 * if lux_scale = 2000, lux = count * 0.49
+	 */
+	if (chip->lux_scale == 125)
+		als_ir_data = (als_ir_data * 31) / 1000;
+	else
+		als_ir_data = (als_ir_data * 49) / 100;
+
+	*als_data = als_ir_data;
+
+	return 0;
+}
+
+static int isl29028_ir_get(struct isl29028_chip *chip, int *ir_data)
+{
+	struct device *dev = regmap_get_device(chip->regmap);
+	int ret;
+
+	ret = isl29028_set_als_ir_mode(chip, ISL29028_MODE_IR);
+	if (ret < 0) {
+		dev_err(dev, "%s(): Error %d enabling IR mode\n", __func__,
+			ret);
+		return ret;
+	}
+
+	return isl29028_read_als_ir(chip, ir_data);
+}
+
+static int isl29028_set_pm_runtime_busy(struct isl29028_chip *chip, bool on)
+{
+	struct device *dev = regmap_get_device(chip->regmap);
+	int ret;
+
+	if (on) {
+		ret = pm_runtime_get_sync(dev);
+		if (ret < 0)
+			pm_runtime_put_noidle(dev);
+	} else {
+		pm_runtime_mark_last_busy(dev);
+		ret = pm_runtime_put_autosuspend(dev);
+	}
+
+	return ret;
+}
+
+/* Channel IO */
+static int isl29028_write_raw(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *chan,
+			      int val, int val2, long mask)
+{
+	struct isl29028_chip *chip = iio_priv(indio_dev);
+	struct device *dev = regmap_get_device(chip->regmap);
+	int ret;
+
+	ret = isl29028_set_pm_runtime_busy(chip, true);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = -EINVAL;
+	switch (chan->type) {
+	case IIO_PROXIMITY:
+		if (mask != IIO_CHAN_INFO_SAMP_FREQ) {
+			dev_err(dev,
+				"%s(): proximity: Mask value 0x%08lx is not supported\n",
+				__func__, mask);
+			break;
+		}
+
+		if (val < 1 || val > 100) {
+			dev_err(dev,
+				"%s(): proximity: Sampling frequency %d is not in the range [1:100]\n",
+				__func__, val);
+			break;
+		}
+
+		ret = isl29028_set_proxim_sampling(chip, val, val2);
+		break;
+	case IIO_LIGHT:
+		if (mask != IIO_CHAN_INFO_SCALE) {
+			dev_err(dev,
+				"%s(): light: Mask value 0x%08lx is not supported\n",
+				__func__, mask);
+			break;
+		}
+
+		if (val != 125 && val != 2000) {
+			dev_err(dev,
+				"%s(): light: Lux scale %d is not in the set {125, 2000}\n",
+				__func__, val);
+			break;
+		}
+
+		ret = isl29028_set_als_scale(chip, val);
+		break;
+	default:
+		dev_err(dev, "%s(): Unsupported channel type %x\n",
+			__func__, chan->type);
+		break;
+	}
+
+	mutex_unlock(&chip->lock);
+
+	if (ret < 0)
+		return ret;
+
+	ret = isl29028_set_pm_runtime_busy(chip, false);
+	if (ret < 0)
+		return ret;
+
+	return ret;
+}
+
+static int isl29028_read_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int *val, int *val2, long mask)
+{
+	struct isl29028_chip *chip = iio_priv(indio_dev);
+	struct device *dev = regmap_get_device(chip->regmap);
+	int ret, pm_ret;
+
+	ret = isl29028_set_pm_runtime_busy(chip, true);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = -EINVAL;
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+	case IIO_CHAN_INFO_PROCESSED:
+		switch (chan->type) {
+		case IIO_LIGHT:
+			ret = isl29028_als_get(chip, val);
+			break;
+		case IIO_INTENSITY:
+			ret = isl29028_ir_get(chip, val);
+			break;
+		case IIO_PROXIMITY:
+			ret = isl29028_read_proxim(chip, val);
+			break;
+		default:
+			break;
+		}
+
+		if (ret < 0)
+			break;
+
+		ret = IIO_VAL_INT;
+		break;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		if (chan->type != IIO_PROXIMITY)
+			break;
+
+		*val = chip->prox_sampling_int;
+		*val2 = chip->prox_sampling_frac;
+		ret = IIO_VAL_INT;
+		break;
+	case IIO_CHAN_INFO_SCALE:
+		if (chan->type != IIO_LIGHT)
+			break;
+		*val = chip->lux_scale;
+		ret = IIO_VAL_INT;
+		break;
+	default:
+		dev_err(dev, "%s(): mask value 0x%08lx is not supported\n",
+			__func__, mask);
+		break;
+	}
+
+	mutex_unlock(&chip->lock);
+
+	if (ret < 0)
+		return ret;
+
+	/**
+	 * Preserve the ret variable if the call to
+	 * isl29028_set_pm_runtime_busy() is successful so the reading
+	 * (if applicable) is returned to user space.
+	 */
+	pm_ret = isl29028_set_pm_runtime_busy(chip, false);
+	if (pm_ret < 0)
+		return pm_ret;
+
+	return ret;
+}
+
+static IIO_CONST_ATTR(in_proximity_sampling_frequency_available,
+				"1.25 2.5 5 10 13.3 20 80 100");
+static IIO_CONST_ATTR(in_illuminance_scale_available, "125 2000");
+
+#define ISL29028_CONST_ATTR(name) (&iio_const_attr_##name.dev_attr.attr)
+static struct attribute *isl29028_attributes[] = {
+	ISL29028_CONST_ATTR(in_proximity_sampling_frequency_available),
+	ISL29028_CONST_ATTR(in_illuminance_scale_available),
+	NULL,
+};
+
+static const struct attribute_group isl29108_group = {
+	.attrs = isl29028_attributes,
+};
+
+static const struct iio_chan_spec isl29028_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
+		BIT(IIO_CHAN_INFO_SCALE),
+	}, {
+		.type = IIO_INTENSITY,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+	}, {
+		.type = IIO_PROXIMITY,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+		BIT(IIO_CHAN_INFO_SAMP_FREQ),
+	}
+};
+
+static const struct iio_info isl29028_info = {
+	.attrs = &isl29108_group,
+	.read_raw = isl29028_read_raw,
+	.write_raw = isl29028_write_raw,
+};
+
+static int isl29028_clear_configure_reg(struct isl29028_chip *chip)
+{
+	struct device *dev = regmap_get_device(chip->regmap);
+	int ret;
+
+	ret = regmap_write(chip->regmap, ISL29028_REG_CONFIGURE, 0x0);
+	if (ret < 0)
+		dev_err(dev, "%s(): Error %d clearing the CONFIGURE register\n",
+			__func__, ret);
+
+	chip->als_ir_mode = ISL29028_MODE_NONE;
+	chip->enable_prox = false;
+
+	return ret;
+}
+
+static bool isl29028_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case ISL29028_REG_INTERRUPT:
+	case ISL29028_REG_PROX_DATA:
+	case ISL29028_REG_ALSIR_L:
+	case ISL29028_REG_ALSIR_U:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config isl29028_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.volatile_reg = isl29028_is_volatile_reg,
+	.max_register = ISL29028_NUM_REGS - 1,
+	.num_reg_defaults_raw = ISL29028_NUM_REGS,
+	.cache_type = REGCACHE_RBTREE,
+};
+
+static int isl29028_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct isl29028_chip *chip;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	chip = iio_priv(indio_dev);
+
+	i2c_set_clientdata(client, indio_dev);
+	mutex_init(&chip->lock);
+
+	chip->regmap = devm_regmap_init_i2c(client, &isl29028_regmap_config);
+	if (IS_ERR(chip->regmap)) {
+		ret = PTR_ERR(chip->regmap);
+		dev_err(&client->dev, "%s: Error %d initializing regmap\n",
+			__func__, ret);
+		return ret;
+	}
+
+	chip->enable_prox  = false;
+	chip->prox_sampling_int = 20;
+	chip->prox_sampling_frac = 0;
+	chip->lux_scale = 2000;
+
+	ret = regmap_write(chip->regmap, ISL29028_REG_TEST1_MODE, 0x0);
+	if (ret < 0) {
+		dev_err(&client->dev,
+			"%s(): Error %d writing to TEST1_MODE register\n",
+			__func__, ret);
+		return ret;
+	}
+
+	ret = regmap_write(chip->regmap, ISL29028_REG_TEST2_MODE, 0x0);
+	if (ret < 0) {
+		dev_err(&client->dev,
+			"%s(): Error %d writing to TEST2_MODE register\n",
+			__func__, ret);
+		return ret;
+	}
+
+	ret = isl29028_clear_configure_reg(chip);
+	if (ret < 0)
+		return ret;
+
+	indio_dev->info = &isl29028_info;
+	indio_dev->channels = isl29028_channels;
+	indio_dev->num_channels = ARRAY_SIZE(isl29028_channels);
+	indio_dev->name = id->name;
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	pm_runtime_enable(&client->dev);
+	pm_runtime_set_autosuspend_delay(&client->dev,
+					 ISL29028_POWER_OFF_DELAY_MS);
+	pm_runtime_use_autosuspend(&client->dev);
+
+	ret = devm_iio_device_register(indio_dev->dev.parent, indio_dev);
+	if (ret < 0) {
+		dev_err(&client->dev,
+			"%s(): iio registration failed with error %d\n",
+			__func__, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int isl29028_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+	struct isl29028_chip *chip = iio_priv(indio_dev);
+
+	iio_device_unregister(indio_dev);
+
+	pm_runtime_disable(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+	pm_runtime_put_noidle(&client->dev);
+
+	return isl29028_clear_configure_reg(chip);
+}
+
+static int __maybe_unused isl29028_suspend(struct device *dev)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+	struct isl29028_chip *chip = iio_priv(indio_dev);
+	int ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = isl29028_clear_configure_reg(chip);
+
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+static int __maybe_unused isl29028_resume(struct device *dev)
+{
+	/**
+	 * The specific component (ALS/IR or proximity) will enable itself as
+	 * needed the next time that the user requests a reading. This is done
+	 * above in isl29028_set_als_ir_mode() and isl29028_enable_proximity().
+	 */
+	return 0;
+}
+
+static const struct dev_pm_ops isl29028_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+				pm_runtime_force_resume)
+	SET_RUNTIME_PM_OPS(isl29028_suspend, isl29028_resume, NULL)
+};
+
+static const struct i2c_device_id isl29028_id[] = {
+	{"isl29028", 0},
+	{"isl29030", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, isl29028_id);
+
+static const struct of_device_id isl29028_of_match[] = {
+	{ .compatible = "isl,isl29028", }, /* for backward compat., don't use */
+	{ .compatible = "isil,isl29028", },
+	{ .compatible = "isil,isl29030", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, isl29028_of_match);
+
+static struct i2c_driver isl29028_driver = {
+	.driver  = {
+		.name = "isl29028",
+		.pm = &isl29028_pm_ops,
+		.of_match_table = isl29028_of_match,
+	},
+	.probe	 = isl29028_probe,
+	.remove  = isl29028_remove,
+	.id_table = isl29028_id,
+};
+
+module_i2c_driver(isl29028_driver);
+
+MODULE_DESCRIPTION("ISL29028 Ambient Light and Proximity Sensor driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
diff --git a/drivers/iio/light/isl29125.c b/drivers/iio/light/isl29125.c
new file mode 100644
index 0000000..ed38edc
--- /dev/null
+++ b/drivers/iio/light/isl29125.c
@@ -0,0 +1,358 @@
+/*
+ * isl29125.c - Support for Intersil ISL29125 RGB light sensor
+ *
+ * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net>
+ *
+ * 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.
+ *
+ * RGB light sensor with 16-bit channels for red, green, blue);
+ * 7-bit I2C slave address 0x44
+ *
+ * TODO: interrupt support, IR compensation, thresholds, 12bit
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#define ISL29125_DRV_NAME "isl29125"
+
+#define ISL29125_DEVICE_ID 0x00
+#define ISL29125_CONF1 0x01
+#define ISL29125_CONF2 0x02
+#define ISL29125_CONF3 0x03
+#define ISL29125_STATUS 0x08
+#define ISL29125_GREEN_DATA 0x09
+#define ISL29125_RED_DATA 0x0b
+#define ISL29125_BLUE_DATA 0x0d
+
+#define ISL29125_ID 0x7d
+
+#define ISL29125_MODE_MASK GENMASK(2, 0)
+#define ISL29125_MODE_PD 0x0
+#define ISL29125_MODE_G 0x1
+#define ISL29125_MODE_R 0x2
+#define ISL29125_MODE_B 0x3
+#define ISL29125_MODE_RGB 0x5
+
+#define ISL29125_SENSING_RANGE_0 5722   /* 375 lux full range */
+#define ISL29125_SENSING_RANGE_1 152590 /* 10k lux full range */
+
+#define ISL29125_MODE_RANGE BIT(3)
+
+#define ISL29125_STATUS_CONV BIT(1)
+
+struct isl29125_data {
+	struct i2c_client *client;
+	u8 conf1;
+	u16 buffer[8]; /* 3x 16-bit, padding, 8 bytes timestamp */
+};
+
+#define ISL29125_CHANNEL(_color, _si) { \
+	.type = IIO_INTENSITY, \
+	.modified = 1, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+	.channel2 = IIO_MOD_LIGHT_##_color, \
+	.scan_index = _si, \
+	.scan_type = { \
+		.sign = 'u', \
+		.realbits = 16, \
+		.storagebits = 16, \
+		.endianness = IIO_CPU, \
+	}, \
+}
+
+static const struct iio_chan_spec isl29125_channels[] = {
+	ISL29125_CHANNEL(GREEN, 0),
+	ISL29125_CHANNEL(RED, 1),
+	ISL29125_CHANNEL(BLUE, 2),
+	IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static const struct {
+	u8 mode, data;
+} isl29125_regs[] = {
+	{ISL29125_MODE_G, ISL29125_GREEN_DATA},
+	{ISL29125_MODE_R, ISL29125_RED_DATA},
+	{ISL29125_MODE_B, ISL29125_BLUE_DATA},
+};
+
+static int isl29125_read_data(struct isl29125_data *data, int si)
+{
+	int tries = 5;
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(data->client, ISL29125_CONF1,
+		data->conf1 | isl29125_regs[si].mode);
+	if (ret < 0)
+		return ret;
+
+	msleep(101);
+
+	while (tries--) {
+		ret = i2c_smbus_read_byte_data(data->client, ISL29125_STATUS);
+		if (ret < 0)
+			goto fail;
+		if (ret & ISL29125_STATUS_CONV)
+			break;
+		msleep(20);
+	}
+
+	if (tries < 0) {
+		dev_err(&data->client->dev, "data not ready\n");
+		ret = -EIO;
+		goto fail;
+	}
+
+	ret = i2c_smbus_read_word_data(data->client, isl29125_regs[si].data);
+
+fail:
+	i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, data->conf1);
+	return ret;
+}
+
+static int isl29125_read_raw(struct iio_dev *indio_dev,
+			   struct iio_chan_spec const *chan,
+			   int *val, int *val2, long mask)
+{
+	struct isl29125_data *data = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = iio_device_claim_direct_mode(indio_dev);
+		if (ret)
+			return ret;
+		ret = isl29125_read_data(data, chan->scan_index);
+		iio_device_release_direct_mode(indio_dev);
+		if (ret < 0)
+			return ret;
+		*val = ret;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		*val = 0;
+		if (data->conf1 & ISL29125_MODE_RANGE)
+			*val2 = ISL29125_SENSING_RANGE_1; /*10k lux full range*/
+		else
+			*val2 = ISL29125_SENSING_RANGE_0; /*375 lux full range*/
+		return IIO_VAL_INT_PLUS_MICRO;
+	}
+	return -EINVAL;
+}
+
+static int isl29125_write_raw(struct iio_dev *indio_dev,
+			       struct iio_chan_spec const *chan,
+			       int val, int val2, long mask)
+{
+	struct isl29125_data *data = iio_priv(indio_dev);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		if (val != 0)
+			return -EINVAL;
+		if (val2 == ISL29125_SENSING_RANGE_1)
+			data->conf1 |= ISL29125_MODE_RANGE;
+		else if (val2 == ISL29125_SENSING_RANGE_0)
+			data->conf1 &= ~ISL29125_MODE_RANGE;
+		else
+			return -EINVAL;
+		return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1,
+			data->conf1);
+	default:
+		return -EINVAL;
+	}
+}
+
+static irqreturn_t isl29125_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct isl29125_data *data = iio_priv(indio_dev);
+	int i, j = 0;
+
+	for_each_set_bit(i, indio_dev->active_scan_mask,
+		indio_dev->masklength) {
+		int ret = i2c_smbus_read_word_data(data->client,
+			isl29125_regs[i].data);
+		if (ret < 0)
+			goto done;
+
+		data->buffer[j++] = ret;
+	}
+
+	iio_push_to_buffers_with_timestamp(indio_dev, data->buffer,
+		iio_get_time_ns(indio_dev));
+
+done:
+	iio_trigger_notify_done(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
+static IIO_CONST_ATTR(scale_available, "0.005722 0.152590");
+
+static struct attribute *isl29125_attributes[] = {
+	&iio_const_attr_scale_available.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group isl29125_attribute_group = {
+	.attrs = isl29125_attributes,
+};
+
+static const struct iio_info isl29125_info = {
+	.read_raw = isl29125_read_raw,
+	.write_raw = isl29125_write_raw,
+	.attrs = &isl29125_attribute_group,
+};
+
+static int isl29125_buffer_preenable(struct iio_dev *indio_dev)
+{
+	struct isl29125_data *data = iio_priv(indio_dev);
+
+	data->conf1 |= ISL29125_MODE_RGB;
+	return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1,
+		data->conf1);
+}
+
+static int isl29125_buffer_predisable(struct iio_dev *indio_dev)
+{
+	struct isl29125_data *data = iio_priv(indio_dev);
+	int ret;
+
+	ret = iio_triggered_buffer_predisable(indio_dev);
+	if (ret < 0)
+		return ret;
+
+	data->conf1 &= ~ISL29125_MODE_MASK;
+	data->conf1 |= ISL29125_MODE_PD;
+	return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1,
+		data->conf1);
+}
+
+static const struct iio_buffer_setup_ops isl29125_buffer_setup_ops = {
+	.preenable = isl29125_buffer_preenable,
+	.postenable = &iio_triggered_buffer_postenable,
+	.predisable = isl29125_buffer_predisable,
+};
+
+static int isl29125_probe(struct i2c_client *client,
+			   const struct i2c_device_id *id)
+{
+	struct isl29125_data *data;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (indio_dev == NULL)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->client = client;
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->info = &isl29125_info;
+	indio_dev->name = ISL29125_DRV_NAME;
+	indio_dev->channels = isl29125_channels;
+	indio_dev->num_channels = ARRAY_SIZE(isl29125_channels);
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	ret = i2c_smbus_read_byte_data(data->client, ISL29125_DEVICE_ID);
+	if (ret < 0)
+		return ret;
+	if (ret != ISL29125_ID)
+		return -ENODEV;
+
+	data->conf1 = ISL29125_MODE_PD | ISL29125_MODE_RANGE;
+	ret = i2c_smbus_write_byte_data(data->client, ISL29125_CONF1,
+		data->conf1);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_write_byte_data(data->client, ISL29125_STATUS, 0);
+	if (ret < 0)
+		return ret;
+
+	ret = iio_triggered_buffer_setup(indio_dev, NULL,
+		isl29125_trigger_handler, &isl29125_buffer_setup_ops);
+	if (ret < 0)
+		return ret;
+
+	ret = iio_device_register(indio_dev);
+	if (ret < 0)
+		goto buffer_cleanup;
+
+	return 0;
+
+buffer_cleanup:
+	iio_triggered_buffer_cleanup(indio_dev);
+	return ret;
+}
+
+static int isl29125_powerdown(struct isl29125_data *data)
+{
+	return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1,
+		(data->conf1 & ~ISL29125_MODE_MASK) | ISL29125_MODE_PD);
+}
+
+static int isl29125_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+	iio_device_unregister(indio_dev);
+	iio_triggered_buffer_cleanup(indio_dev);
+	isl29125_powerdown(iio_priv(indio_dev));
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int isl29125_suspend(struct device *dev)
+{
+	struct isl29125_data *data = iio_priv(i2c_get_clientdata(
+		to_i2c_client(dev)));
+	return isl29125_powerdown(data);
+}
+
+static int isl29125_resume(struct device *dev)
+{
+	struct isl29125_data *data = iio_priv(i2c_get_clientdata(
+		to_i2c_client(dev)));
+	return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1,
+		data->conf1);
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(isl29125_pm_ops, isl29125_suspend, isl29125_resume);
+
+static const struct i2c_device_id isl29125_id[] = {
+	{ "isl29125", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, isl29125_id);
+
+static struct i2c_driver isl29125_driver = {
+	.driver = {
+		.name	= ISL29125_DRV_NAME,
+		.pm	= &isl29125_pm_ops,
+	},
+	.probe		= isl29125_probe,
+	.remove		= isl29125_remove,
+	.id_table	= isl29125_id,
+};
+module_i2c_driver(isl29125_driver);
+
+MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
+MODULE_DESCRIPTION("ISL29125 RGB light sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/jsa1212.c b/drivers/iio/light/jsa1212.c
new file mode 100644
index 0000000..811505d
--- /dev/null
+++ b/drivers/iio/light/jsa1212.c
@@ -0,0 +1,466 @@
+/*
+ * JSA1212 Ambient Light & Proximity Sensor Driver
+ *
+ * Copyright (c) 2014, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions 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.
+ *
+ * JSA1212 I2C slave address: 0x44(ADDR tied to GND), 0x45(ADDR tied to VDD)
+ *
+ * TODO: Interrupt support, thresholds, range support.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/acpi.h>
+#include <linux/regmap.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+/* JSA1212 reg address */
+#define JSA1212_CONF_REG		0x01
+#define JSA1212_INT_REG			0x02
+#define JSA1212_PXS_LT_REG		0x03
+#define JSA1212_PXS_HT_REG		0x04
+#define JSA1212_ALS_TH1_REG		0x05
+#define JSA1212_ALS_TH2_REG		0x06
+#define JSA1212_ALS_TH3_REG		0x07
+#define JSA1212_PXS_DATA_REG		0x08
+#define JSA1212_ALS_DT1_REG		0x09
+#define JSA1212_ALS_DT2_REG		0x0A
+#define JSA1212_ALS_RNG_REG		0x0B
+#define JSA1212_MAX_REG			0x0C
+
+/* JSA1212 reg masks */
+#define JSA1212_CONF_MASK		0xFF
+#define JSA1212_INT_MASK		0xFF
+#define JSA1212_PXS_LT_MASK		0xFF
+#define JSA1212_PXS_HT_MASK		0xFF
+#define JSA1212_ALS_TH1_MASK		0xFF
+#define JSA1212_ALS_TH2_LT_MASK		0x0F
+#define JSA1212_ALS_TH2_HT_MASK		0xF0
+#define JSA1212_ALS_TH3_MASK		0xFF
+#define JSA1212_PXS_DATA_MASK		0xFF
+#define JSA1212_ALS_DATA_MASK		0x0FFF
+#define JSA1212_ALS_DT1_MASK		0xFF
+#define JSA1212_ALS_DT2_MASK		0x0F
+#define JSA1212_ALS_RNG_MASK		0x07
+
+/* JSA1212 CONF REG bits */
+#define JSA1212_CONF_PXS_MASK		0x80
+#define JSA1212_CONF_PXS_ENABLE		0x80
+#define JSA1212_CONF_PXS_DISABLE	0x00
+#define JSA1212_CONF_ALS_MASK		0x04
+#define JSA1212_CONF_ALS_ENABLE		0x04
+#define JSA1212_CONF_ALS_DISABLE	0x00
+#define JSA1212_CONF_IRDR_MASK		0x08
+/* Proxmity sensing IRDR current sink settings */
+#define JSA1212_CONF_IRDR_200MA		0x08
+#define JSA1212_CONF_IRDR_100MA		0x00
+#define JSA1212_CONF_PXS_SLP_MASK	0x70
+#define JSA1212_CONF_PXS_SLP_0MS	0x70
+#define JSA1212_CONF_PXS_SLP_12MS	0x60
+#define JSA1212_CONF_PXS_SLP_50MS	0x50
+#define JSA1212_CONF_PXS_SLP_75MS	0x40
+#define JSA1212_CONF_PXS_SLP_100MS	0x30
+#define JSA1212_CONF_PXS_SLP_200MS	0x20
+#define JSA1212_CONF_PXS_SLP_400MS	0x10
+#define JSA1212_CONF_PXS_SLP_800MS	0x00
+
+/* JSA1212 INT REG bits */
+#define JSA1212_INT_CTRL_MASK		0x01
+#define JSA1212_INT_CTRL_EITHER		0x00
+#define JSA1212_INT_CTRL_BOTH		0x01
+#define JSA1212_INT_ALS_PRST_MASK	0x06
+#define JSA1212_INT_ALS_PRST_1CONV	0x00
+#define JSA1212_INT_ALS_PRST_4CONV	0x02
+#define JSA1212_INT_ALS_PRST_8CONV	0x04
+#define JSA1212_INT_ALS_PRST_16CONV	0x06
+#define JSA1212_INT_ALS_FLAG_MASK	0x08
+#define JSA1212_INT_ALS_FLAG_CLR	0x00
+#define JSA1212_INT_PXS_PRST_MASK	0x60
+#define JSA1212_INT_PXS_PRST_1CONV	0x00
+#define JSA1212_INT_PXS_PRST_4CONV	0x20
+#define JSA1212_INT_PXS_PRST_8CONV	0x40
+#define JSA1212_INT_PXS_PRST_16CONV	0x60
+#define JSA1212_INT_PXS_FLAG_MASK	0x80
+#define JSA1212_INT_PXS_FLAG_CLR	0x00
+
+/* JSA1212 ALS RNG REG bits */
+#define JSA1212_ALS_RNG_0_2048		0x00
+#define JSA1212_ALS_RNG_0_1024		0x01
+#define JSA1212_ALS_RNG_0_512		0x02
+#define JSA1212_ALS_RNG_0_256		0x03
+#define JSA1212_ALS_RNG_0_128		0x04
+
+/* JSA1212 INT threshold range */
+#define JSA1212_ALS_TH_MIN	0x0000
+#define JSA1212_ALS_TH_MAX	0x0FFF
+#define JSA1212_PXS_TH_MIN	0x00
+#define JSA1212_PXS_TH_MAX	0xFF
+
+#define JSA1212_ALS_DELAY_MS	200
+#define JSA1212_PXS_DELAY_MS	100
+
+#define JSA1212_DRIVER_NAME	"jsa1212"
+#define JSA1212_REGMAP_NAME	"jsa1212_regmap"
+
+enum jsa1212_op_mode {
+	JSA1212_OPMODE_ALS_EN,
+	JSA1212_OPMODE_PXS_EN,
+};
+
+struct jsa1212_data {
+	struct i2c_client *client;
+	struct mutex lock;
+	u8 als_rng_idx;
+	bool als_en; /* ALS enable status */
+	bool pxs_en; /* proximity enable status */
+	struct regmap *regmap;
+};
+
+/* ALS range idx to val mapping */
+static const int jsa1212_als_range_val[] = {2048, 1024, 512, 256, 128,
+						128, 128, 128};
+
+/* Enables or disables ALS function based on status */
+static int jsa1212_als_enable(struct jsa1212_data *data, u8 status)
+{
+	int ret;
+
+	ret = regmap_update_bits(data->regmap, JSA1212_CONF_REG,
+				JSA1212_CONF_ALS_MASK,
+				status);
+	if (ret < 0)
+		return ret;
+
+	data->als_en = !!status;
+
+	return 0;
+}
+
+/* Enables or disables PXS function based on status */
+static int jsa1212_pxs_enable(struct jsa1212_data *data, u8 status)
+{
+	int ret;
+
+	ret = regmap_update_bits(data->regmap, JSA1212_CONF_REG,
+				JSA1212_CONF_PXS_MASK,
+				status);
+	if (ret < 0)
+		return ret;
+
+	data->pxs_en = !!status;
+
+	return 0;
+}
+
+static int jsa1212_read_als_data(struct jsa1212_data *data,
+				unsigned int *val)
+{
+	int ret;
+	__le16 als_data;
+
+	ret = jsa1212_als_enable(data, JSA1212_CONF_ALS_ENABLE);
+	if (ret < 0)
+		return ret;
+
+	/* Delay for data output */
+	msleep(JSA1212_ALS_DELAY_MS);
+
+	/* Read 12 bit data */
+	ret = regmap_bulk_read(data->regmap, JSA1212_ALS_DT1_REG, &als_data, 2);
+	if (ret < 0) {
+		dev_err(&data->client->dev, "als data read err\n");
+		goto als_data_read_err;
+	}
+
+	*val = le16_to_cpu(als_data);
+
+als_data_read_err:
+	return jsa1212_als_enable(data, JSA1212_CONF_ALS_DISABLE);
+}
+
+static int jsa1212_read_pxs_data(struct jsa1212_data *data,
+				unsigned int *val)
+{
+	int ret;
+	unsigned int pxs_data;
+
+	ret = jsa1212_pxs_enable(data, JSA1212_CONF_PXS_ENABLE);
+	if (ret < 0)
+		return ret;
+
+	/* Delay for data output */
+	msleep(JSA1212_PXS_DELAY_MS);
+
+	/* Read out all data */
+	ret = regmap_read(data->regmap, JSA1212_PXS_DATA_REG, &pxs_data);
+	if (ret < 0) {
+		dev_err(&data->client->dev, "pxs data read err\n");
+		goto pxs_data_read_err;
+	}
+
+	*val = pxs_data & JSA1212_PXS_DATA_MASK;
+
+pxs_data_read_err:
+	return jsa1212_pxs_enable(data, JSA1212_CONF_PXS_DISABLE);
+}
+
+static int jsa1212_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val, int *val2, long mask)
+{
+	int ret;
+	struct jsa1212_data *data = iio_priv(indio_dev);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		mutex_lock(&data->lock);
+		switch (chan->type) {
+		case IIO_LIGHT:
+			ret = jsa1212_read_als_data(data, val);
+			break;
+		case IIO_PROXIMITY:
+			ret = jsa1212_read_pxs_data(data, val);
+			break;
+		default:
+			ret = -EINVAL;
+			break;
+		}
+		mutex_unlock(&data->lock);
+		return ret < 0 ? ret : IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		switch (chan->type) {
+		case IIO_LIGHT:
+			*val = jsa1212_als_range_val[data->als_rng_idx];
+			*val2 = BIT(12); /* Max 12 bit value */
+			return IIO_VAL_FRACTIONAL;
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return -EINVAL;
+}
+
+static const struct iio_chan_spec jsa1212_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+			BIT(IIO_CHAN_INFO_SCALE),
+	},
+	{
+		.type = IIO_PROXIMITY,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+	}
+};
+
+static const struct iio_info jsa1212_info = {
+	.read_raw		= &jsa1212_read_raw,
+};
+
+static int jsa1212_chip_init(struct jsa1212_data *data)
+{
+	int ret;
+
+	ret = regmap_write(data->regmap, JSA1212_CONF_REG,
+				(JSA1212_CONF_PXS_SLP_50MS |
+				JSA1212_CONF_IRDR_200MA));
+	if (ret < 0)
+		return ret;
+
+	ret = regmap_write(data->regmap, JSA1212_INT_REG,
+				JSA1212_INT_ALS_PRST_4CONV);
+	if (ret < 0)
+		return ret;
+
+	data->als_rng_idx = JSA1212_ALS_RNG_0_2048;
+
+	return 0;
+}
+
+static bool jsa1212_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case JSA1212_PXS_DATA_REG:
+	case JSA1212_ALS_DT1_REG:
+	case JSA1212_ALS_DT2_REG:
+	case JSA1212_INT_REG:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config jsa1212_regmap_config = {
+	.name =  JSA1212_REGMAP_NAME,
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = JSA1212_MAX_REG,
+	.cache_type = REGCACHE_RBTREE,
+	.volatile_reg = jsa1212_is_volatile_reg,
+};
+
+static int jsa1212_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	struct jsa1212_data *data;
+	struct iio_dev *indio_dev;
+	struct regmap *regmap;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	regmap = devm_regmap_init_i2c(client, &jsa1212_regmap_config);
+	if (IS_ERR(regmap)) {
+		dev_err(&client->dev, "Regmap initialization failed.\n");
+		return PTR_ERR(regmap);
+	}
+
+	data = iio_priv(indio_dev);
+
+	i2c_set_clientdata(client, indio_dev);
+	data->client = client;
+	data->regmap = regmap;
+
+	mutex_init(&data->lock);
+
+	ret = jsa1212_chip_init(data);
+	if (ret < 0)
+		return ret;
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->channels = jsa1212_channels;
+	indio_dev->num_channels = ARRAY_SIZE(jsa1212_channels);
+	indio_dev->name = JSA1212_DRIVER_NAME;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	indio_dev->info = &jsa1212_info;
+
+	ret = iio_device_register(indio_dev);
+	if (ret < 0)
+		dev_err(&client->dev, "%s: register device failed\n", __func__);
+
+	return ret;
+}
+
+ /* power off the device */
+static int jsa1212_power_off(struct jsa1212_data *data)
+{
+	int ret;
+
+	mutex_lock(&data->lock);
+
+	ret = regmap_update_bits(data->regmap, JSA1212_CONF_REG,
+				JSA1212_CONF_ALS_MASK |
+				JSA1212_CONF_PXS_MASK,
+				JSA1212_CONF_ALS_DISABLE |
+				JSA1212_CONF_PXS_DISABLE);
+
+	if (ret < 0)
+		dev_err(&data->client->dev, "power off cmd failed\n");
+
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+
+static int jsa1212_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+	struct jsa1212_data *data = iio_priv(indio_dev);
+
+	iio_device_unregister(indio_dev);
+
+	return jsa1212_power_off(data);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int jsa1212_suspend(struct device *dev)
+{
+	struct jsa1212_data *data;
+
+	data = iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+
+	return jsa1212_power_off(data);
+}
+
+static int jsa1212_resume(struct device *dev)
+{
+	int ret = 0;
+	struct jsa1212_data *data;
+
+	data = iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+
+	mutex_lock(&data->lock);
+
+	if (data->als_en) {
+		ret = jsa1212_als_enable(data, JSA1212_CONF_ALS_ENABLE);
+		if (ret < 0) {
+			dev_err(dev, "als resume failed\n");
+			goto unlock_and_ret;
+		}
+	}
+
+	if (data->pxs_en) {
+		ret = jsa1212_pxs_enable(data, JSA1212_CONF_PXS_ENABLE);
+		if (ret < 0)
+			dev_err(dev, "pxs resume failed\n");
+	}
+
+unlock_and_ret:
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(jsa1212_pm_ops, jsa1212_suspend, jsa1212_resume);
+
+#define JSA1212_PM_OPS (&jsa1212_pm_ops)
+#else
+#define JSA1212_PM_OPS NULL
+#endif
+
+static const struct acpi_device_id jsa1212_acpi_match[] = {
+	{"JSA1212", 0},
+	{ },
+};
+MODULE_DEVICE_TABLE(acpi, jsa1212_acpi_match);
+
+static const struct i2c_device_id jsa1212_id[] = {
+	{ JSA1212_DRIVER_NAME, 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, jsa1212_id);
+
+static struct i2c_driver jsa1212_driver = {
+	.driver = {
+		.name	= JSA1212_DRIVER_NAME,
+		.pm	= JSA1212_PM_OPS,
+		.acpi_match_table = ACPI_PTR(jsa1212_acpi_match),
+	},
+	.probe		= jsa1212_probe,
+	.remove		= jsa1212_remove,
+	.id_table	= jsa1212_id,
+};
+module_i2c_driver(jsa1212_driver);
+
+MODULE_AUTHOR("Sathya Kuppuswamy <sathyanarayanan.kuppuswamy@linux.intel.com>");
+MODULE_DESCRIPTION("JSA1212 proximity/ambient light sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c
new file mode 100644
index 0000000..ff5a332
--- /dev/null
+++ b/drivers/iio/light/lm3533-als.c
@@ -0,0 +1,928 @@
+/*
+ * lm3533-als.c -- LM3533 Ambient Light Sensor driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@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.
+ */
+
+#include <linux/atomic.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_ALS_RESISTOR_MIN			1
+#define LM3533_ALS_RESISTOR_MAX			127
+#define LM3533_ALS_CHANNEL_CURRENT_MAX		2
+#define LM3533_ALS_THRESH_MAX			3
+#define LM3533_ALS_ZONE_MAX			4
+
+#define LM3533_REG_ALS_RESISTOR_SELECT		0x30
+#define LM3533_REG_ALS_CONF			0x31
+#define LM3533_REG_ALS_ZONE_INFO		0x34
+#define LM3533_REG_ALS_READ_ADC_RAW		0x37
+#define LM3533_REG_ALS_READ_ADC_AVERAGE		0x38
+#define LM3533_REG_ALS_BOUNDARY_BASE		0x50
+#define LM3533_REG_ALS_TARGET_BASE		0x60
+
+#define LM3533_ALS_ENABLE_MASK			0x01
+#define LM3533_ALS_INPUT_MODE_MASK		0x02
+#define LM3533_ALS_INT_ENABLE_MASK		0x01
+
+#define LM3533_ALS_ZONE_SHIFT			2
+#define LM3533_ALS_ZONE_MASK			0x1c
+
+#define LM3533_ALS_FLAG_INT_ENABLED		1
+
+
+struct lm3533_als {
+	struct lm3533 *lm3533;
+	struct platform_device *pdev;
+
+	unsigned long flags;
+	int irq;
+
+	atomic_t zone;
+	struct mutex thresh_mutex;
+};
+
+
+static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average,
+								int *adc)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 reg;
+	u8 val;
+	int ret;
+
+	if (average)
+		reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
+	else
+		reg = LM3533_REG_ALS_READ_ADC_RAW;
+
+	ret = lm3533_read(als->lm3533, reg, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to read adc\n");
+		return ret;
+	}
+
+	*adc = val;
+
+	return 0;
+}
+
+static int _lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to read zone\n");
+		return ret;
+	}
+
+	val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
+	*zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
+
+	return 0;
+}
+
+static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	int ret;
+
+	if (test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags)) {
+		*zone = atomic_read(&als->zone);
+	} else {
+		ret = _lm3533_als_get_zone(indio_dev, zone);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * channel	output channel 0..2
+ * zone		zone 0..4
+ */
+static inline u8 lm3533_als_get_target_reg(unsigned channel, unsigned zone)
+{
+	return LM3533_REG_ALS_TARGET_BASE + 5 * channel + zone;
+}
+
+static int lm3533_als_get_target(struct iio_dev *indio_dev, unsigned channel,
+							unsigned zone, u8 *val)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 reg;
+	int ret;
+
+	if (channel > LM3533_ALS_CHANNEL_CURRENT_MAX)
+		return -EINVAL;
+
+	if (zone > LM3533_ALS_ZONE_MAX)
+		return -EINVAL;
+
+	reg = lm3533_als_get_target_reg(channel, zone);
+	ret = lm3533_read(als->lm3533, reg, val);
+	if (ret)
+		dev_err(&indio_dev->dev, "failed to get target current\n");
+
+	return ret;
+}
+
+static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned channel,
+							unsigned zone, u8 val)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 reg;
+	int ret;
+
+	if (channel > LM3533_ALS_CHANNEL_CURRENT_MAX)
+		return -EINVAL;
+
+	if (zone > LM3533_ALS_ZONE_MAX)
+		return -EINVAL;
+
+	reg = lm3533_als_get_target_reg(channel, zone);
+	ret = lm3533_write(als->lm3533, reg, val);
+	if (ret)
+		dev_err(&indio_dev->dev, "failed to set target current\n");
+
+	return ret;
+}
+
+static int lm3533_als_get_current(struct iio_dev *indio_dev, unsigned channel,
+								int *val)
+{
+	u8 zone;
+	u8 target;
+	int ret;
+
+	ret = lm3533_als_get_zone(indio_dev, &zone);
+	if (ret)
+		return ret;
+
+	ret = lm3533_als_get_target(indio_dev, channel, zone, &target);
+	if (ret)
+		return ret;
+
+	*val = target;
+
+	return 0;
+}
+
+static int lm3533_als_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val, int *val2, long mask)
+{
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		switch (chan->type) {
+		case IIO_LIGHT:
+			ret = lm3533_als_get_adc(indio_dev, false, val);
+			break;
+		case IIO_CURRENT:
+			ret = lm3533_als_get_current(indio_dev, chan->channel,
+									val);
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	case IIO_CHAN_INFO_AVERAGE_RAW:
+		ret = lm3533_als_get_adc(indio_dev, true, val);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (ret)
+		return ret;
+
+	return IIO_VAL_INT;
+}
+
+#define CHANNEL_CURRENT(_channel)					\
+	{								\
+		.type		= IIO_CURRENT,				\
+		.channel	= _channel,				\
+		.indexed	= true,					\
+		.output		= true,					\
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
+	}
+
+static const struct iio_chan_spec lm3533_als_channels[] = {
+	{
+		.type		= IIO_LIGHT,
+		.channel	= 0,
+		.indexed	= true,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_AVERAGE_RAW) |
+				   BIT(IIO_CHAN_INFO_RAW),
+	},
+	CHANNEL_CURRENT(0),
+	CHANNEL_CURRENT(1),
+	CHANNEL_CURRENT(2),
+};
+
+static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
+{
+
+	struct iio_dev *indio_dev = dev_id;
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 zone;
+	int ret;
+
+	/* Clear interrupt by reading the ALS zone register. */
+	ret = _lm3533_als_get_zone(indio_dev, &zone);
+	if (ret)
+		goto out;
+
+	atomic_set(&als->zone, zone);
+
+	iio_push_event(indio_dev,
+		       IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
+					    0,
+					    IIO_EV_TYPE_THRESH,
+					    IIO_EV_DIR_EITHER),
+		       iio_get_time_ns(indio_dev));
+out:
+	return IRQ_HANDLED;
+}
+
+static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+	u8 val;
+	int ret;
+
+	if (enable)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to set int mode %d\n",
+								enable);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to get int mode\n");
+		return ret;
+	}
+
+	*enable = !!(val & mask);
+
+	return 0;
+}
+
+static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising)
+{
+	u8 offset = !raising;
+
+	return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset;
+}
+
+static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr,
+							bool raising, u8 *val)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 reg;
+	int ret;
+
+	if (nr > LM3533_ALS_THRESH_MAX)
+		return -EINVAL;
+
+	reg = lm3533_als_get_threshold_reg(nr, raising);
+	ret = lm3533_read(als->lm3533, reg, val);
+	if (ret)
+		dev_err(&indio_dev->dev, "failed to get threshold\n");
+
+	return ret;
+}
+
+static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr,
+							bool raising, u8 val)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 val2;
+	u8 reg, reg2;
+	int ret;
+
+	if (nr > LM3533_ALS_THRESH_MAX)
+		return -EINVAL;
+
+	reg = lm3533_als_get_threshold_reg(nr, raising);
+	reg2 = lm3533_als_get_threshold_reg(nr, !raising);
+
+	mutex_lock(&als->thresh_mutex);
+	ret = lm3533_read(als->lm3533, reg2, &val2);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to get threshold\n");
+		goto out;
+	}
+	/*
+	 * This device does not allow negative hysteresis (in fact, it uses
+	 * whichever value is smaller as the lower bound) so we need to make
+	 * sure that thresh_falling <= thresh_raising.
+	 */
+	if ((raising && (val < val2)) || (!raising && (val > val2))) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	ret = lm3533_write(als->lm3533, reg, val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to set threshold\n");
+		goto out;
+	}
+out:
+	mutex_unlock(&als->thresh_mutex);
+
+	return ret;
+}
+
+static int lm3533_als_get_hysteresis(struct iio_dev *indio_dev, unsigned nr,
+								u8 *val)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 falling;
+	u8 raising;
+	int ret;
+
+	if (nr > LM3533_ALS_THRESH_MAX)
+		return -EINVAL;
+
+	mutex_lock(&als->thresh_mutex);
+	ret = lm3533_als_get_threshold(indio_dev, nr, false, &falling);
+	if (ret)
+		goto out;
+	ret = lm3533_als_get_threshold(indio_dev, nr, true, &raising);
+	if (ret)
+		goto out;
+
+	*val = raising - falling;
+out:
+	mutex_unlock(&als->thresh_mutex);
+
+	return ret;
+}
+
+static ssize_t show_thresh_either_en(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	int enable;
+	int ret;
+
+	if (als->irq) {
+		ret = lm3533_als_get_int_mode(indio_dev, &enable);
+		if (ret)
+			return ret;
+	} else {
+		enable = 0;
+	}
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", enable);
+}
+
+static ssize_t store_thresh_either_en(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	unsigned long enable;
+	bool int_enabled;
+	u8 zone;
+	int ret;
+
+	if (!als->irq)
+		return -EBUSY;
+
+	if (kstrtoul(buf, 0, &enable))
+		return -EINVAL;
+
+	int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+	if (enable && !int_enabled) {
+		ret = lm3533_als_get_zone(indio_dev, &zone);
+		if (ret)
+			return ret;
+
+		atomic_set(&als->zone, zone);
+
+		set_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+	}
+
+	ret = lm3533_als_set_int_mode(indio_dev, enable);
+	if (ret) {
+		if (!int_enabled)
+			clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+		return ret;
+	}
+
+	if (!enable)
+		clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+	return len;
+}
+
+static ssize_t show_zone(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	u8 zone;
+	int ret;
+
+	ret = lm3533_als_get_zone(indio_dev, &zone);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
+}
+
+enum lm3533_als_attribute_type {
+	LM3533_ATTR_TYPE_HYSTERESIS,
+	LM3533_ATTR_TYPE_TARGET,
+	LM3533_ATTR_TYPE_THRESH_FALLING,
+	LM3533_ATTR_TYPE_THRESH_RAISING,
+};
+
+struct lm3533_als_attribute {
+	struct device_attribute dev_attr;
+	enum lm3533_als_attribute_type type;
+	u8 val1;
+	u8 val2;
+};
+
+static inline struct lm3533_als_attribute *
+to_lm3533_als_attr(struct device_attribute *attr)
+{
+	return container_of(attr, struct lm3533_als_attribute, dev_attr);
+}
+
+static ssize_t show_als_attr(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
+	u8 val;
+	int ret;
+
+	switch (als_attr->type) {
+	case LM3533_ATTR_TYPE_HYSTERESIS:
+		ret = lm3533_als_get_hysteresis(indio_dev, als_attr->val1,
+									&val);
+		break;
+	case LM3533_ATTR_TYPE_TARGET:
+		ret = lm3533_als_get_target(indio_dev, als_attr->val1,
+							als_attr->val2, &val);
+		break;
+	case LM3533_ATTR_TYPE_THRESH_FALLING:
+		ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
+								false, &val);
+		break;
+	case LM3533_ATTR_TYPE_THRESH_RAISING:
+		ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
+								true, &val);
+		break;
+	default:
+		ret = -ENXIO;
+	}
+
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_als_attr(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	switch (als_attr->type) {
+	case LM3533_ATTR_TYPE_TARGET:
+		ret = lm3533_als_set_target(indio_dev, als_attr->val1,
+							als_attr->val2, val);
+		break;
+	case LM3533_ATTR_TYPE_THRESH_FALLING:
+		ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
+								false, val);
+		break;
+	case LM3533_ATTR_TYPE_THRESH_RAISING:
+		ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
+								true, val);
+		break;
+	default:
+		ret = -ENXIO;
+	}
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)	\
+	{ .dev_attr	= __ATTR(_name, _mode, _show, _store),		\
+	  .type		= _type,					\
+	  .val1		= _val1,					\
+	  .val2		= _val2 }
+
+#define LM3533_ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \
+	struct lm3533_als_attribute lm3533_als_attr_##_name =		  \
+		ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)
+
+#define ALS_TARGET_ATTR_RW(_channel, _zone)				\
+	LM3533_ALS_ATTR(out_current##_channel##_current##_zone##_raw,	\
+				S_IRUGO | S_IWUSR,			\
+				show_als_attr, store_als_attr,		\
+				LM3533_ATTR_TYPE_TARGET, _channel, _zone)
+/*
+ * ALS output current values (ALS mapper targets)
+ *
+ * out_current[0-2]_current[0-4]_raw		0-255
+ */
+static ALS_TARGET_ATTR_RW(0, 0);
+static ALS_TARGET_ATTR_RW(0, 1);
+static ALS_TARGET_ATTR_RW(0, 2);
+static ALS_TARGET_ATTR_RW(0, 3);
+static ALS_TARGET_ATTR_RW(0, 4);
+
+static ALS_TARGET_ATTR_RW(1, 0);
+static ALS_TARGET_ATTR_RW(1, 1);
+static ALS_TARGET_ATTR_RW(1, 2);
+static ALS_TARGET_ATTR_RW(1, 3);
+static ALS_TARGET_ATTR_RW(1, 4);
+
+static ALS_TARGET_ATTR_RW(2, 0);
+static ALS_TARGET_ATTR_RW(2, 1);
+static ALS_TARGET_ATTR_RW(2, 2);
+static ALS_TARGET_ATTR_RW(2, 3);
+static ALS_TARGET_ATTR_RW(2, 4);
+
+#define ALS_THRESH_FALLING_ATTR_RW(_nr)					\
+	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_falling_value,	\
+			S_IRUGO | S_IWUSR,				\
+			show_als_attr, store_als_attr,		\
+			LM3533_ATTR_TYPE_THRESH_FALLING, _nr, 0)
+
+#define ALS_THRESH_RAISING_ATTR_RW(_nr)					\
+	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_raising_value,	\
+			S_IRUGO | S_IWUSR,				\
+			show_als_attr, store_als_attr,			\
+			LM3533_ATTR_TYPE_THRESH_RAISING, _nr, 0)
+/*
+ * ALS Zone thresholds (boundaries)
+ *
+ * in_illuminance0_thresh[0-3]_falling_value	0-255
+ * in_illuminance0_thresh[0-3]_raising_value	0-255
+ */
+static ALS_THRESH_FALLING_ATTR_RW(0);
+static ALS_THRESH_FALLING_ATTR_RW(1);
+static ALS_THRESH_FALLING_ATTR_RW(2);
+static ALS_THRESH_FALLING_ATTR_RW(3);
+
+static ALS_THRESH_RAISING_ATTR_RW(0);
+static ALS_THRESH_RAISING_ATTR_RW(1);
+static ALS_THRESH_RAISING_ATTR_RW(2);
+static ALS_THRESH_RAISING_ATTR_RW(3);
+
+#define ALS_HYSTERESIS_ATTR_RO(_nr)					\
+	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_hysteresis,	\
+			S_IRUGO, show_als_attr, NULL,			\
+			LM3533_ATTR_TYPE_HYSTERESIS, _nr, 0)
+/*
+ * ALS Zone threshold hysteresis
+ *
+ * threshY_hysteresis = threshY_raising - threshY_falling
+ *
+ * in_illuminance0_thresh[0-3]_hysteresis	0-255
+ * in_illuminance0_thresh[0-3]_hysteresis	0-255
+ */
+static ALS_HYSTERESIS_ATTR_RO(0);
+static ALS_HYSTERESIS_ATTR_RO(1);
+static ALS_HYSTERESIS_ATTR_RO(2);
+static ALS_HYSTERESIS_ATTR_RO(3);
+
+#define ILLUMINANCE_ATTR_RO(_name) \
+	DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL)
+#define ILLUMINANCE_ATTR_RW(_name) \
+	DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR, \
+						show_##_name, store_##_name)
+/*
+ * ALS Zone threshold-event enable
+ *
+ * in_illuminance0_thresh_either_en		0,1
+ */
+static ILLUMINANCE_ATTR_RW(thresh_either_en);
+
+/*
+ * ALS Current Zone
+ *
+ * in_illuminance0_zone		0-4
+ */
+static ILLUMINANCE_ATTR_RO(zone);
+
+static struct attribute *lm3533_als_event_attributes[] = {
+	&dev_attr_in_illuminance0_thresh_either_en.attr,
+	&lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group lm3533_als_event_attribute_group = {
+	.attrs = lm3533_als_event_attributes
+};
+
+static struct attribute *lm3533_als_attributes[] = {
+	&dev_attr_in_illuminance0_zone.attr,
+	&lm3533_als_attr_out_current0_current0_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current0_current1_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current0_current2_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current0_current3_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current0_current4_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current1_current0_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current1_current1_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current1_current2_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current1_current3_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current1_current4_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current2_current0_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current2_current1_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current2_current2_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current2_current3_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current2_current4_raw.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group lm3533_als_attribute_group = {
+	.attrs = lm3533_als_attributes
+};
+
+static int lm3533_als_set_input_mode(struct lm3533_als *als, bool pwm_mode)
+{
+	u8 mask = LM3533_ALS_INPUT_MODE_MASK;
+	u8 val;
+	int ret;
+
+	if (pwm_mode)
+		val = mask;	/* pwm input */
+	else
+		val = 0;	/* analog input */
+
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, val, mask);
+	if (ret) {
+		dev_err(&als->pdev->dev, "failed to set input mode %d\n",
+								pwm_mode);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int lm3533_als_set_resistor(struct lm3533_als *als, u8 val)
+{
+	int ret;
+
+	if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX) {
+		dev_err(&als->pdev->dev, "invalid resistor value\n");
+		return -EINVAL;
+	};
+
+	ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
+	if (ret) {
+		dev_err(&als->pdev->dev, "failed to set resistor\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int lm3533_als_setup(struct lm3533_als *als,
+			    struct lm3533_als_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_als_set_input_mode(als, pdata->pwm_mode);
+	if (ret)
+		return ret;
+
+	/* ALS input is always high impedance in PWM-mode. */
+	if (!pdata->pwm_mode) {
+		ret = lm3533_als_set_resistor(als, pdata->r_select);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int lm3533_als_setup_irq(struct lm3533_als *als, void *dev)
+{
+	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+	int ret;
+
+	/* Make sure interrupts are disabled. */
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, 0, mask);
+	if (ret) {
+		dev_err(&als->pdev->dev, "failed to disable interrupts\n");
+		return ret;
+	}
+
+	ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					dev_name(&als->pdev->dev), dev);
+	if (ret) {
+		dev_err(&als->pdev->dev, "failed to request irq %d\n",
+								als->irq);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int lm3533_als_enable(struct lm3533_als *als)
+{
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
+	if (ret)
+		dev_err(&als->pdev->dev, "failed to enable ALS\n");
+
+	return ret;
+}
+
+static int lm3533_als_disable(struct lm3533_als *als)
+{
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask);
+	if (ret)
+		dev_err(&als->pdev->dev, "failed to disable ALS\n");
+
+	return ret;
+}
+
+static const struct iio_info lm3533_als_info = {
+	.attrs		= &lm3533_als_attribute_group,
+	.event_attrs	= &lm3533_als_event_attribute_group,
+	.read_raw	= &lm3533_als_read_raw,
+};
+
+static int lm3533_als_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_als_platform_data *pdata;
+	struct lm3533_als *als;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*als));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	indio_dev->info = &lm3533_als_info;
+	indio_dev->channels = lm3533_als_channels;
+	indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
+	indio_dev->name = dev_name(&pdev->dev);
+	indio_dev->dev.parent = pdev->dev.parent;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	als = iio_priv(indio_dev);
+	als->lm3533 = lm3533;
+	als->pdev = pdev;
+	als->irq = lm3533->irq;
+	atomic_set(&als->zone, 0);
+	mutex_init(&als->thresh_mutex);
+
+	platform_set_drvdata(pdev, indio_dev);
+
+	if (als->irq) {
+		ret = lm3533_als_setup_irq(als, indio_dev);
+		if (ret)
+			return ret;
+	}
+
+	ret = lm3533_als_setup(als, pdata);
+	if (ret)
+		goto err_free_irq;
+
+	ret = lm3533_als_enable(als);
+	if (ret)
+		goto err_free_irq;
+
+	ret = iio_device_register(indio_dev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register ALS\n");
+		goto err_disable;
+	}
+
+	return 0;
+
+err_disable:
+	lm3533_als_disable(als);
+err_free_irq:
+	if (als->irq)
+		free_irq(als->irq, indio_dev);
+
+	return ret;
+}
+
+static int lm3533_als_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+
+	lm3533_als_set_int_mode(indio_dev, false);
+	iio_device_unregister(indio_dev);
+	lm3533_als_disable(als);
+	if (als->irq)
+		free_irq(als->irq, indio_dev);
+
+	return 0;
+}
+
+static struct platform_driver lm3533_als_driver = {
+	.driver	= {
+		.name	= "lm3533-als",
+	},
+	.probe		= lm3533_als_probe,
+	.remove		= lm3533_als_remove,
+};
+module_platform_driver(lm3533_als_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-als");
diff --git a/drivers/iio/light/ltr501.c b/drivers/iio/light/ltr501.c
new file mode 100644
index 0000000..830a2d4
--- /dev/null
+++ b/drivers/iio/light/ltr501.c
@@ -0,0 +1,1592 @@
+/*
+ * ltr501.c - Support for Lite-On LTR501 ambient light and proximity sensor
+ *
+ * Copyright 2014 Peter Meerwald <pmeerw@pmeerw.net>
+ *
+ * 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.
+ *
+ * 7-bit I2C slave address 0x23
+ *
+ * TODO: IR LED characteristics
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/regmap.h>
+#include <linux/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>
+#include <linux/iio/triggered_buffer.h>
+
+#define LTR501_DRV_NAME "ltr501"
+
+#define LTR501_ALS_CONTR 0x80 /* ALS operation mode, SW reset */
+#define LTR501_PS_CONTR 0x81 /* PS operation mode */
+#define LTR501_PS_MEAS_RATE 0x84 /* measurement rate*/
+#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))
+#define LTR501_CONTR_PS_GAIN_SHIFT 2
+#define LTR501_CONTR_ALS_GAIN_MASK BIT(3)
+#define LTR501_CONTR_ACTIVE BIT(1)
+
+#define LTR501_STATUS_ALS_INTR BIT(3)
+#define LTR501_STATUS_ALS_RDY BIT(2)
+#define LTR501_STATUS_PS_INTR BIT(1)
+#define LTR501_STATUS_PS_RDY BIT(0)
+
+#define LTR501_PS_DATA_MASK 0x7ff
+#define LTR501_PS_THRESH_MASK 0x7ff
+#define LTR501_ALS_THRESH_MASK 0xffff
+
+#define LTR501_ALS_DEF_PERIOD 500000
+#define LTR501_PS_DEF_PERIOD 100000
+
+#define LTR501_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, 1, 1);
+static const struct reg_field reg_field_ps_intr =
+				REG_FIELD(LTR501_INTR, 0, 0);
+static const struct reg_field reg_field_als_rate =
+				REG_FIELD(LTR501_ALS_MEAS_RATE, 0, 2);
+static const struct reg_field reg_field_ps_rate =
+				REG_FIELD(LTR501_PS_MEAS_RATE, 0, 3);
+static const struct reg_field reg_field_als_prst =
+				REG_FIELD(LTR501_INTR_PRST, 0, 3);
+static const struct reg_field reg_field_ps_prst =
+				REG_FIELD(LTR501_INTR_PRST, 4, 7);
+
+struct ltr501_samp_table {
+	int freq_val;  /* repetition frequency in micro HZ*/
+	int time_val; /* repetition rate in micro seconds */
+};
+
+#define LTR501_RESERVED_GAIN -1
+
+enum {
+	ltr501 = 0,
+	ltr559,
+	ltr301,
+};
+
+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, status;
+
+	while (tries--) {
+		ret = regmap_read(data->regmap, LTR501_ALS_PS_STATUS, &status);
+		if (ret < 0)
+			return ret;
+		if ((status & drdy_mask) == drdy_mask)
+			return 0;
+		msleep(25);
+	}
+
+	dev_err(&data->client->dev, "ltr501_drdy() failed, data not ready\n");
+	return -EIO;
+}
+
+static int ltr501_set_it_time(struct ltr501_data *data, int it)
+{
+	int ret, i, index = -1, status;
+
+	for (i = 0; i < ARRAY_SIZE(int_time_mapping); i++) {
+		if (int_time_mapping[i] == it) {
+			index = i;
+			break;
+		}
+	}
+	/* Make sure integ time index is valid */
+	if (index < 0)
+		return -EINVAL;
+
+	ret = regmap_read(data->regmap, LTR501_ALS_CONTR, &status);
+	if (ret < 0)
+		return ret;
+
+	if (status & LTR501_CONTR_ALS_GAIN_MASK) {
+		/*
+		 * 200 ms and 400 ms integ time can only be
+		 * used in dynamic range 1
+		 */
+		if (index > 1)
+			return -EINVAL;
+	} else
+		/* 50 ms integ time can only be used in dynamic range 2 */
+		if (index == 1)
+			return -EINVAL;
+
+	return regmap_field_write(data->reg_it, index);
+}
+
+/* read int time in micro seconds */
+static int ltr501_read_it_time(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;
+
+	ret = ltr501_drdy(data, LTR501_STATUS_ALS_RDY);
+	if (ret < 0)
+		return ret;
+	/* always read both ALS channels in given order */
+	return regmap_bulk_read(data->regmap, LTR501_ALS_DATA1,
+				buf, 2 * sizeof(__le16));
+}
+
+static int ltr501_read_ps(struct ltr501_data *data)
+{
+	int ret, status;
+
+	ret = ltr501_drdy(data, LTR501_STATUS_PS_RDY);
+	if (ret < 0)
+		return ret;
+
+	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;
+}
+
+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), \
+	.channel2 = (_mod), \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+	.info_mask_shared_by_type = (_shared), \
+	.scan_index = (_idx), \
+	.scan_type = { \
+		.sign = 'u', \
+		.realbits = 16, \
+		.storagebits = 16, \
+		.endianness = IIO_CPU, \
+	}, \
+	.event_spec = _evspec,\
+	.num_event_specs = _evsize,\
+}
+
+#define LTR501_LIGHT_CHANNEL() { \
+	.type = IIO_LIGHT, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \
+	.scan_index = -1, \
+}
+
+static const struct iio_chan_spec ltr501_channels[] = {
+	LTR501_LIGHT_CHANNEL(),
+	LTR501_INTENSITY_CHANNEL(0, LTR501_ALS_DATA0, IIO_MOD_LIGHT_BOTH, 0,
+				 ltr501_als_event_spec,
+				 ARRAY_SIZE(ltr501_als_event_spec)),
+	LTR501_INTENSITY_CHANNEL(1, LTR501_ALS_DATA1, IIO_MOD_LIGHT_IR,
+				 BIT(IIO_CHAN_INFO_SCALE) |
+				 BIT(IIO_CHAN_INFO_INT_TIME) |
+				 BIT(IIO_CHAN_INFO_SAMP_FREQ),
+				 NULL, 0),
+	{
+		.type = IIO_PROXIMITY,
+		.address = LTR501_PS_DATA,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+			BIT(IIO_CHAN_INFO_SCALE),
+		.scan_index = 2,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 11,
+			.storagebits = 16,
+			.endianness = IIO_CPU,
+		},
+		.event_spec = ltr501_pxs_event_spec,
+		.num_event_specs = ARRAY_SIZE(ltr501_pxs_event_spec),
+	},
+	IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static const struct iio_chan_spec ltr301_channels[] = {
+	LTR501_LIGHT_CHANNEL(),
+	LTR501_INTENSITY_CHANNEL(0, LTR501_ALS_DATA0, IIO_MOD_LIGHT_BOTH, 0,
+				 ltr501_als_event_spec,
+				 ARRAY_SIZE(ltr501_als_event_spec)),
+	LTR501_INTENSITY_CHANNEL(1, LTR501_ALS_DATA1, IIO_MOD_LIGHT_IR,
+				 BIT(IIO_CHAN_INFO_SCALE) |
+				 BIT(IIO_CHAN_INFO_INT_TIME) |
+				 BIT(IIO_CHAN_INFO_SAMP_FREQ),
+				 NULL, 0),
+	IIO_CHAN_SOFT_TIMESTAMP(2),
+};
+
+static int ltr501_read_raw(struct iio_dev *indio_dev,
+			   struct iio_chan_spec const *chan,
+			   int *val, int *val2, long mask)
+{
+	struct ltr501_data *data = iio_priv(indio_dev);
+	__le16 buf[2];
+	int ret, i;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_PROCESSED:
+		switch (chan->type) {
+		case IIO_LIGHT:
+			ret = iio_device_claim_direct_mode(indio_dev);
+			if (ret)
+				return ret;
+
+			mutex_lock(&data->lock_als);
+			ret = ltr501_read_als(data, buf);
+			mutex_unlock(&data->lock_als);
+			iio_device_release_direct_mode(indio_dev);
+			if (ret < 0)
+				return ret;
+			*val = ltr501_calculate_lux(le16_to_cpu(buf[1]),
+						    le16_to_cpu(buf[0]));
+			return IIO_VAL_INT;
+		default:
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_RAW:
+		ret = iio_device_claim_direct_mode(indio_dev);
+		if (ret)
+			return ret;
+
+		switch (chan->type) {
+		case IIO_INTENSITY:
+			mutex_lock(&data->lock_als);
+			ret = ltr501_read_als(data, buf);
+			mutex_unlock(&data->lock_als);
+			if (ret < 0)
+				break;
+			*val = le16_to_cpu(chan->address == LTR501_ALS_DATA1 ?
+					   buf[0] : buf[1]);
+			ret = IIO_VAL_INT;
+			break;
+		case IIO_PROXIMITY:
+			mutex_lock(&data->lock_ps);
+			ret = ltr501_read_ps(data);
+			mutex_unlock(&data->lock_ps);
+			if (ret < 0)
+				break;
+			*val = ret & LTR501_PS_DATA_MASK;
+			ret = IIO_VAL_INT;
+			break;
+		default:
+			ret = -EINVAL;
+			break;
+		}
+
+		iio_device_release_direct_mode(indio_dev);
+		return ret;
+
+	case IIO_CHAN_INFO_SCALE:
+		switch (chan->type) {
+		case IIO_INTENSITY:
+			i = (data->als_contr & data->chip_info->als_gain_mask)
+			     >> data->chip_info->als_gain_shift;
+			*val = data->chip_info->als_gain[i].scale;
+			*val2 = data->chip_info->als_gain[i].uscale;
+			return IIO_VAL_INT_PLUS_MICRO;
+		case IIO_PROXIMITY:
+			i = (data->ps_contr & LTR501_CONTR_PS_GAIN_MASK) >>
+				LTR501_CONTR_PS_GAIN_SHIFT;
+			*val = data->chip_info->ps_gain[i].scale;
+			*val2 = data->chip_info->ps_gain[i].uscale;
+			return IIO_VAL_INT_PLUS_MICRO;
+		default:
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_INT_TIME:
+		switch (chan->type) {
+		case IIO_INTENSITY:
+			return ltr501_read_it_time(data, val, val2);
+		default:
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		switch (chan->type) {
+		case IIO_INTENSITY:
+			return ltr501_als_read_samp_freq(data, val, val2);
+		case IIO_PROXIMITY:
+			return ltr501_ps_read_samp_freq(data, val, val2);
+		default:
+			return -EINVAL;
+		}
+	}
+	return -EINVAL;
+}
+
+static int ltr501_get_gain_index(struct ltr501_gain *gain, int size,
+				 int val, int val2)
+{
+	int i;
+
+	for (i = 0; i < size; i++)
+		if (val == gain[i].scale && val2 == gain[i].uscale)
+			return i;
+
+	return -1;
+}
+
+static int ltr501_write_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan,
+			    int val, int val2, long mask)
+{
+	struct ltr501_data *data = iio_priv(indio_dev);
+	int i, ret, freq_val, freq_val2;
+	struct ltr501_chip_info *info = data->chip_info;
+
+	ret = iio_device_claim_direct_mode(indio_dev);
+	if (ret)
+		return ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		switch (chan->type) {
+		case IIO_INTENSITY:
+			i = ltr501_get_gain_index(info->als_gain,
+						  info->als_gain_tbl_size,
+						  val, val2);
+			if (i < 0) {
+				ret = -EINVAL;
+				break;
+			}
+
+			data->als_contr &= ~info->als_gain_mask;
+			data->als_contr |= i << info->als_gain_shift;
+
+			ret = regmap_write(data->regmap, LTR501_ALS_CONTR,
+					   data->als_contr);
+			break;
+		case IIO_PROXIMITY:
+			i = ltr501_get_gain_index(info->ps_gain,
+						  info->ps_gain_tbl_size,
+						  val, val2);
+			if (i < 0) {
+				ret = -EINVAL;
+				break;
+			}
+			data->ps_contr &= ~LTR501_CONTR_PS_GAIN_MASK;
+			data->ps_contr |= i << LTR501_CONTR_PS_GAIN_SHIFT;
+
+			ret = regmap_write(data->regmap, LTR501_PS_CONTR,
+					   data->ps_contr);
+			break;
+		default:
+			ret = -EINVAL;
+			break;
+		}
+		break;
+
+	case IIO_CHAN_INFO_INT_TIME:
+		switch (chan->type) {
+		case IIO_INTENSITY:
+			if (val != 0) {
+				ret = -EINVAL;
+				break;
+			}
+			mutex_lock(&data->lock_als);
+			ret = ltr501_set_it_time(data, val2);
+			mutex_unlock(&data->lock_als);
+			break;
+		default:
+			ret = -EINVAL;
+			break;
+		}
+		break;
+
+	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)
+				break;
+
+			ret = ltr501_als_write_samp_freq(data, val, val2);
+			if (ret < 0)
+				break;
+
+			/* update persistence count when changing frequency */
+			ret = ltr501_write_intr_prst(data, chan->type,
+						     0, data->als_period);
+
+			if (ret < 0)
+				ret = ltr501_als_write_samp_freq(data, freq_val,
+								 freq_val2);
+			break;
+		case IIO_PROXIMITY:
+			ret = ltr501_ps_read_samp_freq(data, &freq_val,
+						       &freq_val2);
+			if (ret < 0)
+				break;
+
+			ret = ltr501_ps_write_samp_freq(data, val, val2);
+			if (ret < 0)
+				break;
+
+			/* update persistence count when changing frequency */
+			ret = ltr501_write_intr_prst(data, chan->type,
+						     0, data->ps_period);
+
+			if (ret < 0)
+				ret = ltr501_ps_write_samp_freq(data, freq_val,
+								freq_val2);
+			break;
+		default:
+			ret = -EINVAL;
+			break;
+		}
+		break;
+
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	iio_device_release_direct_mode(indio_dev);
+	return ret;
+}
+
+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 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_dev_attr_in_proximity_scale_available.dev_attr.attr,
+	&iio_dev_attr_in_intensity_scale_available.dev_attr.attr,
+	&iio_const_attr_integration_time_available.dev_attr.attr,
+	&iio_const_attr_sampling_frequency_available.dev_attr.attr,
+	NULL
+};
+
+static struct attribute *ltr301_attributes[] = {
+	&iio_dev_attr_in_intensity_scale_available.dev_attr.attr,
+	&iio_const_attr_integration_time_available.dev_attr.attr,
+	&iio_const_attr_sampling_frequency_available.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group ltr501_attribute_group = {
+	.attrs = ltr501_attributes,
+};
+
+static const struct attribute_group ltr301_attribute_group = {
+	.attrs = ltr301_attributes,
+};
+
+static const struct iio_info ltr501_info_no_irq = {
+	.read_raw = ltr501_read_raw,
+	.write_raw = ltr501_write_raw,
+	.attrs = &ltr501_attribute_group,
+};
+
+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,
+};
+
+static const struct iio_info ltr301_info_no_irq = {
+	.read_raw = ltr501_read_raw,
+	.write_raw = ltr501_write_raw,
+	.attrs = &ltr301_attribute_group,
+};
+
+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,
+};
+
+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;
+
+	ret = regmap_write(data->regmap, LTR501_ALS_CONTR, als_val);
+	if (ret < 0)
+		return ret;
+
+	return regmap_write(data->regmap, LTR501_PS_CONTR, ps_val);
+}
+
+static irqreturn_t ltr501_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct ltr501_data *data = iio_priv(indio_dev);
+	u16 buf[8];
+	__le16 als_buf[2];
+	u8 mask = 0;
+	int j = 0;
+	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))
+		mask |= LTR501_STATUS_ALS_RDY;
+	if (test_bit(2, indio_dev->active_scan_mask))
+		mask |= LTR501_STATUS_PS_RDY;
+
+	ret = ltr501_drdy(data, mask);
+	if (ret < 0)
+		goto done;
+
+	if (mask & LTR501_STATUS_ALS_RDY) {
+		ret = regmap_bulk_read(data->regmap, LTR501_ALS_DATA1,
+				       (u8 *)als_buf, sizeof(als_buf));
+		if (ret < 0)
+			return ret;
+		if (test_bit(0, indio_dev->active_scan_mask))
+			buf[j++] = le16_to_cpu(als_buf[1]);
+		if (test_bit(1, indio_dev->active_scan_mask))
+			buf[j++] = le16_to_cpu(als_buf[0]);
+	}
+
+	if (mask & LTR501_STATUS_PS_RDY) {
+		ret = regmap_bulk_read(data->regmap, LTR501_PS_DATA,
+				       &psdata, 2);
+		if (ret < 0)
+			goto done;
+		buf[j++] = psdata & LTR501_PS_DATA_MASK;
+	}
+
+	iio_push_to_buffers_with_timestamp(indio_dev, buf,
+					   iio_get_time_ns(indio_dev));
+
+done:
+	iio_trigger_notify_done(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t ltr501_interrupt_handler(int irq, void *private)
+{
+	struct iio_dev *indio_dev = private;
+	struct ltr501_data *data = iio_priv(indio_dev);
+	int ret, status;
+
+	ret = regmap_read(data->regmap, LTR501_ALS_PS_STATUS, &status);
+	if (ret < 0) {
+		dev_err(&data->client->dev,
+			"irq read int reg failed\n");
+		return IRQ_HANDLED;
+	}
+
+	if (status & LTR501_STATUS_ALS_INTR)
+		iio_push_event(indio_dev,
+			       IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0,
+						    IIO_EV_TYPE_THRESH,
+						    IIO_EV_DIR_EITHER),
+			       iio_get_time_ns(indio_dev));
+
+	if (status & LTR501_STATUS_PS_INTR)
+		iio_push_event(indio_dev,
+			       IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
+						    IIO_EV_TYPE_THRESH,
+						    IIO_EV_DIR_EITHER),
+			       iio_get_time_ns(indio_dev));
+
+	return IRQ_HANDLED;
+}
+
+static int ltr501_init(struct ltr501_data *data)
+{
+	int ret, status;
+
+	ret = regmap_read(data->regmap, LTR501_ALS_CONTR, &status);
+	if (ret < 0)
+		return ret;
+
+	data->als_contr = status | data->chip_info->als_mode_active;
+
+	ret = regmap_read(data->regmap, LTR501_PS_CONTR, &status);
+	if (ret < 0)
+		return ret;
+
+	data->ps_contr = status | LTR501_CONTR_ACTIVE;
+
+	ret = ltr501_read_intr_prst(data, IIO_INTENSITY, &data->als_period);
+	if (ret < 0)
+		return ret;
+
+	ret = ltr501_read_intr_prst(data, IIO_PROXIMITY, &data->ps_period);
+	if (ret < 0)
+		return ret;
+
+	return ltr501_write_contr(data, data->als_contr, data->ps_contr);
+}
+
+static bool ltr501_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case LTR501_ALS_DATA1:
+	case LTR501_ALS_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, 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)
+{
+	struct ltr501_data *data;
+	struct iio_dev *indio_dev;
+	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);
+
+	data->reg_it = devm_regmap_field_alloc(&client->dev, regmap,
+					       reg_field_it);
+	if (IS_ERR(data->reg_it)) {
+		dev_err(&client->dev, "Integ time reg field init failed.\n");
+		return PTR_ERR(data->reg_it);
+	}
+
+	data->reg_als_intr = devm_regmap_field_alloc(&client->dev, regmap,
+						     reg_field_als_intr);
+	if (IS_ERR(data->reg_als_intr)) {
+		dev_err(&client->dev, "ALS intr mode reg field init failed\n");
+		return PTR_ERR(data->reg_als_intr);
+	}
+
+	data->reg_ps_intr = devm_regmap_field_alloc(&client->dev, regmap,
+						    reg_field_ps_intr);
+	if (IS_ERR(data->reg_ps_intr)) {
+		dev_err(&client->dev, "PS intr mode reg field init failed.\n");
+		return PTR_ERR(data->reg_ps_intr);
+	}
+
+	data->reg_als_rate = devm_regmap_field_alloc(&client->dev, regmap,
+						     reg_field_als_rate);
+	if (IS_ERR(data->reg_als_rate)) {
+		dev_err(&client->dev, "ALS samp rate field init failed.\n");
+		return PTR_ERR(data->reg_als_rate);
+	}
+
+	data->reg_ps_rate = devm_regmap_field_alloc(&client->dev, regmap,
+						    reg_field_ps_rate);
+	if (IS_ERR(data->reg_ps_rate)) {
+		dev_err(&client->dev, "PS samp rate field init failed.\n");
+		return PTR_ERR(data->reg_ps_rate);
+	}
+
+	data->reg_als_prst = devm_regmap_field_alloc(&client->dev, regmap,
+						     reg_field_als_prst);
+	if (IS_ERR(data->reg_als_prst)) {
+		dev_err(&client->dev, "ALS prst reg field init failed\n");
+		return PTR_ERR(data->reg_als_prst);
+	}
+
+	data->reg_ps_prst = devm_regmap_field_alloc(&client->dev, regmap,
+						    reg_field_ps_prst);
+	if (IS_ERR(data->reg_ps_prst)) {
+		dev_err(&client->dev, "PS prst reg field init failed.\n");
+		return PTR_ERR(data->reg_ps_prst);
+	}
+
+	ret = regmap_read(data->regmap, LTR501_PART_ID, &partid);
+	if (ret < 0)
+		return ret;
+
+	if (id) {
+		name = id->name;
+		chip_idx = id->driver_data;
+	} else  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 = 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);
+	if (ret)
+		goto powerdown_on_error;
+
+	ret = iio_device_register(indio_dev);
+	if (ret)
+		goto error_unreg_buffer;
+
+	return 0;
+
+error_unreg_buffer:
+	iio_triggered_buffer_cleanup(indio_dev);
+powerdown_on_error:
+	ltr501_powerdown(data);
+	return ret;
+}
+
+static int ltr501_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+	iio_device_unregister(indio_dev);
+	iio_triggered_buffer_cleanup(indio_dev);
+	ltr501_powerdown(iio_priv(indio_dev));
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int ltr501_suspend(struct device *dev)
+{
+	struct ltr501_data *data = iio_priv(i2c_get_clientdata(
+					    to_i2c_client(dev)));
+	return ltr501_powerdown(data);
+}
+
+static int ltr501_resume(struct device *dev)
+{
+	struct ltr501_data *data = iio_priv(i2c_get_clientdata(
+					    to_i2c_client(dev)));
+
+	return ltr501_write_contr(data, data->als_contr,
+		data->ps_contr);
+}
+#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", ltr501},
+	{ "ltr559", ltr559},
+	{ "ltr301", ltr301},
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ltr501_id);
+
+static struct i2c_driver ltr501_driver = {
+	.driver = {
+		.name   = LTR501_DRV_NAME,
+		.pm	= &ltr501_pm_ops,
+		.acpi_match_table = ACPI_PTR(ltr_acpi_match),
+	},
+	.probe  = ltr501_probe,
+	.remove	= ltr501_remove,
+	.id_table = ltr501_id,
+};
+
+module_i2c_driver(ltr501_driver);
+
+MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
+MODULE_DESCRIPTION("Lite-On LTR501 ambient light and proximity sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/lv0104cs.c b/drivers/iio/light/lv0104cs.c
new file mode 100644
index 0000000..55b8e28
--- /dev/null
+++ b/drivers/iio/light/lv0104cs.c
@@ -0,0 +1,531 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * lv0104cs.c: LV0104CS Ambient Light Sensor Driver
+ *
+ * Copyright (C) 2018
+ * Author: Jeff LaBundy <jeff@labundy.com>
+ *
+ * 7-bit I2C slave address: 0x13
+ *
+ * Link to data sheet: http://www.onsemi.com/pub/Collateral/LV0104CS-D.PDF
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define LV0104CS_REGVAL_MEASURE		0xE0
+#define LV0104CS_REGVAL_SLEEP		0x00
+
+#define LV0104CS_SCALE_0_25X		0
+#define LV0104CS_SCALE_1X		1
+#define LV0104CS_SCALE_2X		2
+#define LV0104CS_SCALE_8X		3
+#define LV0104CS_SCALE_SHIFT		3
+
+#define LV0104CS_INTEG_12_5MS		0
+#define LV0104CS_INTEG_100MS		1
+#define LV0104CS_INTEG_200MS		2
+#define LV0104CS_INTEG_SHIFT		1
+
+#define LV0104CS_CALIBSCALE_UNITY	31
+
+struct lv0104cs_private {
+	struct i2c_client *client;
+	struct mutex lock;
+	u8 calibscale;
+	u8 scale;
+	u8 int_time;
+};
+
+struct lv0104cs_mapping {
+	int val;
+	int val2;
+	u8 regval;
+};
+
+static const struct lv0104cs_mapping lv0104cs_calibscales[] = {
+	{ 0, 666666, 0x81 },
+	{ 0, 800000, 0x82 },
+	{ 0, 857142, 0x83 },
+	{ 0, 888888, 0x84 },
+	{ 0, 909090, 0x85 },
+	{ 0, 923076, 0x86 },
+	{ 0, 933333, 0x87 },
+	{ 0, 941176, 0x88 },
+	{ 0, 947368, 0x89 },
+	{ 0, 952380, 0x8A },
+	{ 0, 956521, 0x8B },
+	{ 0, 960000, 0x8C },
+	{ 0, 962962, 0x8D },
+	{ 0, 965517, 0x8E },
+	{ 0, 967741, 0x8F },
+	{ 0, 969696, 0x90 },
+	{ 0, 971428, 0x91 },
+	{ 0, 972972, 0x92 },
+	{ 0, 974358, 0x93 },
+	{ 0, 975609, 0x94 },
+	{ 0, 976744, 0x95 },
+	{ 0, 977777, 0x96 },
+	{ 0, 978723, 0x97 },
+	{ 0, 979591, 0x98 },
+	{ 0, 980392, 0x99 },
+	{ 0, 981132, 0x9A },
+	{ 0, 981818, 0x9B },
+	{ 0, 982456, 0x9C },
+	{ 0, 983050, 0x9D },
+	{ 0, 983606, 0x9E },
+	{ 0, 984126, 0x9F },
+	{ 1, 0, 0x80 },
+	{ 1, 16129, 0xBF },
+	{ 1, 16666, 0xBE },
+	{ 1, 17241, 0xBD },
+	{ 1, 17857, 0xBC },
+	{ 1, 18518, 0xBB },
+	{ 1, 19230, 0xBA },
+	{ 1, 20000, 0xB9 },
+	{ 1, 20833, 0xB8 },
+	{ 1, 21739, 0xB7 },
+	{ 1, 22727, 0xB6 },
+	{ 1, 23809, 0xB5 },
+	{ 1, 24999, 0xB4 },
+	{ 1, 26315, 0xB3 },
+	{ 1, 27777, 0xB2 },
+	{ 1, 29411, 0xB1 },
+	{ 1, 31250, 0xB0 },
+	{ 1, 33333, 0xAF },
+	{ 1, 35714, 0xAE },
+	{ 1, 38461, 0xAD },
+	{ 1, 41666, 0xAC },
+	{ 1, 45454, 0xAB },
+	{ 1, 50000, 0xAA },
+	{ 1, 55555, 0xA9 },
+	{ 1, 62500, 0xA8 },
+	{ 1, 71428, 0xA7 },
+	{ 1, 83333, 0xA6 },
+	{ 1, 100000, 0xA5 },
+	{ 1, 125000, 0xA4 },
+	{ 1, 166666, 0xA3 },
+	{ 1, 250000, 0xA2 },
+	{ 1, 500000, 0xA1 },
+};
+
+static const struct lv0104cs_mapping lv0104cs_scales[] = {
+	{ 0, 250000, LV0104CS_SCALE_0_25X << LV0104CS_SCALE_SHIFT },
+	{ 1, 0, LV0104CS_SCALE_1X << LV0104CS_SCALE_SHIFT },
+	{ 2, 0, LV0104CS_SCALE_2X << LV0104CS_SCALE_SHIFT },
+	{ 8, 0, LV0104CS_SCALE_8X << LV0104CS_SCALE_SHIFT },
+};
+
+static const struct lv0104cs_mapping lv0104cs_int_times[] = {
+	{ 0, 12500, LV0104CS_INTEG_12_5MS << LV0104CS_INTEG_SHIFT },
+	{ 0, 100000, LV0104CS_INTEG_100MS << LV0104CS_INTEG_SHIFT },
+	{ 0, 200000, LV0104CS_INTEG_200MS << LV0104CS_INTEG_SHIFT },
+};
+
+static int lv0104cs_write_reg(struct i2c_client *client, u8 regval)
+{
+	int ret;
+
+	ret = i2c_master_send(client, (char *)&regval, sizeof(regval));
+	if (ret < 0)
+		return ret;
+	if (ret != sizeof(regval))
+		return -EIO;
+
+	return 0;
+}
+
+static int lv0104cs_read_adc(struct i2c_client *client, u16 *adc_output)
+{
+	__be16 regval;
+	int ret;
+
+	ret = i2c_master_recv(client, (char *)&regval, sizeof(regval));
+	if (ret < 0)
+		return ret;
+	if (ret != sizeof(regval))
+		return -EIO;
+
+	*adc_output = be16_to_cpu(regval);
+
+	return 0;
+}
+
+static int lv0104cs_get_lux(struct lv0104cs_private *lv0104cs,
+				int *val, int *val2)
+{
+	u8 regval = LV0104CS_REGVAL_MEASURE;
+	u16 adc_output;
+	int ret;
+
+	regval |= lv0104cs_scales[lv0104cs->scale].regval;
+	regval |= lv0104cs_int_times[lv0104cs->int_time].regval;
+	ret = lv0104cs_write_reg(lv0104cs->client, regval);
+	if (ret)
+		return ret;
+
+	/* wait for integration time to pass (with margin) */
+	switch (lv0104cs->int_time) {
+	case LV0104CS_INTEG_12_5MS:
+		msleep(50);
+		break;
+
+	case LV0104CS_INTEG_100MS:
+		msleep(150);
+		break;
+
+	case LV0104CS_INTEG_200MS:
+		msleep(250);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	ret = lv0104cs_read_adc(lv0104cs->client, &adc_output);
+	if (ret)
+		return ret;
+
+	ret = lv0104cs_write_reg(lv0104cs->client, LV0104CS_REGVAL_SLEEP);
+	if (ret)
+		return ret;
+
+	/* convert ADC output to lux */
+	switch (lv0104cs->scale) {
+	case LV0104CS_SCALE_0_25X:
+		*val = adc_output * 4;
+		*val2 = 0;
+		return 0;
+
+	case LV0104CS_SCALE_1X:
+		*val = adc_output;
+		*val2 = 0;
+		return 0;
+
+	case LV0104CS_SCALE_2X:
+		*val = adc_output / 2;
+		*val2 = (adc_output % 2) * 500000;
+		return 0;
+
+	case LV0104CS_SCALE_8X:
+		*val = adc_output / 8;
+		*val2 = (adc_output % 8) * 125000;
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int lv0104cs_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val, int *val2, long mask)
+{
+	struct lv0104cs_private *lv0104cs = iio_priv(indio_dev);
+	int ret;
+
+	if (chan->type != IIO_LIGHT)
+		return -EINVAL;
+
+	mutex_lock(&lv0104cs->lock);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_PROCESSED:
+		ret = lv0104cs_get_lux(lv0104cs, val, val2);
+		if (ret)
+			goto err_mutex;
+		ret = IIO_VAL_INT_PLUS_MICRO;
+		break;
+
+	case IIO_CHAN_INFO_CALIBSCALE:
+		*val = lv0104cs_calibscales[lv0104cs->calibscale].val;
+		*val2 = lv0104cs_calibscales[lv0104cs->calibscale].val2;
+		ret = IIO_VAL_INT_PLUS_MICRO;
+		break;
+
+	case IIO_CHAN_INFO_SCALE:
+		*val = lv0104cs_scales[lv0104cs->scale].val;
+		*val2 = lv0104cs_scales[lv0104cs->scale].val2;
+		ret = IIO_VAL_INT_PLUS_MICRO;
+		break;
+
+	case IIO_CHAN_INFO_INT_TIME:
+		*val = lv0104cs_int_times[lv0104cs->int_time].val;
+		*val2 = lv0104cs_int_times[lv0104cs->int_time].val2;
+		ret = IIO_VAL_INT_PLUS_MICRO;
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+err_mutex:
+	mutex_unlock(&lv0104cs->lock);
+
+	return ret;
+}
+
+static int lv0104cs_set_calibscale(struct lv0104cs_private *lv0104cs,
+				int val, int val2)
+{
+	int calibscale = val * 1000000 + val2;
+	int floor, ceil, mid;
+	int ret, i, index;
+
+	/* round to nearest quantized calibscale (sensitivity) */
+	for (i = 0; i < ARRAY_SIZE(lv0104cs_calibscales) - 1; i++) {
+		floor = lv0104cs_calibscales[i].val * 1000000
+				+ lv0104cs_calibscales[i].val2;
+		ceil = lv0104cs_calibscales[i + 1].val * 1000000
+				+ lv0104cs_calibscales[i + 1].val2;
+		mid = (floor + ceil) / 2;
+
+		/* round down */
+		if (calibscale >= floor && calibscale < mid) {
+			index = i;
+			break;
+		}
+
+		/* round up */
+		if (calibscale >= mid && calibscale <= ceil) {
+			index = i + 1;
+			break;
+		}
+	}
+
+	if (i == ARRAY_SIZE(lv0104cs_calibscales) - 1)
+		return -EINVAL;
+
+	mutex_lock(&lv0104cs->lock);
+
+	/* set calibscale (sensitivity) */
+	ret = lv0104cs_write_reg(lv0104cs->client,
+			lv0104cs_calibscales[index].regval);
+	if (ret)
+		goto err_mutex;
+
+	lv0104cs->calibscale = index;
+
+err_mutex:
+	mutex_unlock(&lv0104cs->lock);
+
+	return ret;
+}
+
+static int lv0104cs_set_scale(struct lv0104cs_private *lv0104cs,
+				int val, int val2)
+{
+	int i;
+
+	/* hard matching */
+	for (i = 0; i < ARRAY_SIZE(lv0104cs_scales); i++) {
+		if (val != lv0104cs_scales[i].val)
+			continue;
+
+		if (val2 == lv0104cs_scales[i].val2)
+			break;
+	}
+
+	if (i == ARRAY_SIZE(lv0104cs_scales))
+		return -EINVAL;
+
+	mutex_lock(&lv0104cs->lock);
+	lv0104cs->scale = i;
+	mutex_unlock(&lv0104cs->lock);
+
+	return 0;
+}
+
+static int lv0104cs_set_int_time(struct lv0104cs_private *lv0104cs,
+				int val, int val2)
+{
+	int i;
+
+	/* hard matching */
+	for (i = 0; i < ARRAY_SIZE(lv0104cs_int_times); i++) {
+		if (val != lv0104cs_int_times[i].val)
+			continue;
+
+		if (val2 == lv0104cs_int_times[i].val2)
+			break;
+	}
+
+	if (i == ARRAY_SIZE(lv0104cs_int_times))
+		return -EINVAL;
+
+	mutex_lock(&lv0104cs->lock);
+	lv0104cs->int_time = i;
+	mutex_unlock(&lv0104cs->lock);
+
+	return 0;
+}
+
+static int lv0104cs_write_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int val, int val2, long mask)
+{
+	struct lv0104cs_private *lv0104cs = iio_priv(indio_dev);
+
+	if (chan->type != IIO_LIGHT)
+		return -EINVAL;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_CALIBSCALE:
+		return lv0104cs_set_calibscale(lv0104cs, val, val2);
+
+	case IIO_CHAN_INFO_SCALE:
+		return lv0104cs_set_scale(lv0104cs, val, val2);
+
+	case IIO_CHAN_INFO_INT_TIME:
+		return lv0104cs_set_int_time(lv0104cs, val, val2);
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static ssize_t lv0104cs_show_calibscale_avail(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	ssize_t len = 0;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(lv0104cs_calibscales); i++) {
+		len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ",
+				lv0104cs_calibscales[i].val,
+				lv0104cs_calibscales[i].val2);
+	}
+
+	buf[len - 1] = '\n';
+
+	return len;
+}
+
+static ssize_t lv0104cs_show_scale_avail(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	ssize_t len = 0;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(lv0104cs_scales); i++) {
+		len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ",
+				lv0104cs_scales[i].val,
+				lv0104cs_scales[i].val2);
+	}
+
+	buf[len - 1] = '\n';
+
+	return len;
+}
+
+static ssize_t lv0104cs_show_int_time_avail(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	ssize_t len = 0;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(lv0104cs_int_times); i++) {
+		len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ",
+				lv0104cs_int_times[i].val,
+				lv0104cs_int_times[i].val2);
+	}
+
+	buf[len - 1] = '\n';
+
+	return len;
+}
+
+static IIO_DEVICE_ATTR(calibscale_available, 0444,
+				lv0104cs_show_calibscale_avail, NULL, 0);
+static IIO_DEVICE_ATTR(scale_available, 0444,
+				lv0104cs_show_scale_avail, NULL, 0);
+static IIO_DEV_ATTR_INT_TIME_AVAIL(lv0104cs_show_int_time_avail);
+
+static struct attribute *lv0104cs_attributes[] = {
+	&iio_dev_attr_calibscale_available.dev_attr.attr,
+	&iio_dev_attr_scale_available.dev_attr.attr,
+	&iio_dev_attr_integration_time_available.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group lv0104cs_attribute_group = {
+	.attrs = lv0104cs_attributes,
+};
+
+static const struct iio_info lv0104cs_info = {
+	.attrs = &lv0104cs_attribute_group,
+	.read_raw = &lv0104cs_read_raw,
+	.write_raw = &lv0104cs_write_raw,
+};
+
+static const struct iio_chan_spec lv0104cs_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
+				      BIT(IIO_CHAN_INFO_CALIBSCALE) |
+				      BIT(IIO_CHAN_INFO_SCALE) |
+				      BIT(IIO_CHAN_INFO_INT_TIME),
+	},
+};
+
+static int lv0104cs_probe(struct i2c_client *client,
+				const struct i2c_device_id *id)
+{
+	struct iio_dev *indio_dev;
+	struct lv0104cs_private *lv0104cs;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*lv0104cs));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	lv0104cs = iio_priv(indio_dev);
+
+	i2c_set_clientdata(client, lv0104cs);
+	lv0104cs->client = client;
+
+	mutex_init(&lv0104cs->lock);
+
+	lv0104cs->calibscale = LV0104CS_CALIBSCALE_UNITY;
+	lv0104cs->scale = LV0104CS_SCALE_1X;
+	lv0104cs->int_time = LV0104CS_INTEG_200MS;
+
+	ret = lv0104cs_write_reg(lv0104cs->client,
+			lv0104cs_calibscales[LV0104CS_CALIBSCALE_UNITY].regval);
+	if (ret)
+		return ret;
+
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->channels = lv0104cs_channels;
+	indio_dev->num_channels = ARRAY_SIZE(lv0104cs_channels);
+	indio_dev->name = client->name;
+	indio_dev->info = &lv0104cs_info;
+
+	return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct i2c_device_id lv0104cs_id[] = {
+	{ "lv0104cs", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, lv0104cs_id);
+
+static struct i2c_driver lv0104cs_i2c_driver = {
+	.driver = {
+		.name	= "lv0104cs",
+	},
+	.id_table	= lv0104cs_id,
+	.probe		= lv0104cs_probe,
+};
+module_i2c_driver(lv0104cs_i2c_driver);
+
+MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
+MODULE_DESCRIPTION("LV0104CS Ambient Light Sensor Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/max44000.c b/drivers/iio/light/max44000.c
new file mode 100644
index 0000000..bcdb0eb
--- /dev/null
+++ b/drivers/iio/light/max44000.c
@@ -0,0 +1,639 @@
+/*
+ * MAX44000 Ambient and Infrared Proximity Sensor
+ *
+ * Copyright (c) 2016, 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.
+ *
+ * Data sheet: https://datasheets.maximintegrated.com/en/ds/MAX44000.pdf
+ *
+ * 7-bit I2C slave address 0x4a
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/util_macros.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/acpi.h>
+
+#define MAX44000_DRV_NAME		"max44000"
+
+/* Registers in datasheet order */
+#define MAX44000_REG_STATUS		0x00
+#define MAX44000_REG_CFG_MAIN		0x01
+#define MAX44000_REG_CFG_RX		0x02
+#define MAX44000_REG_CFG_TX		0x03
+#define MAX44000_REG_ALS_DATA_HI	0x04
+#define MAX44000_REG_ALS_DATA_LO	0x05
+#define MAX44000_REG_PRX_DATA		0x16
+#define MAX44000_REG_ALS_UPTHR_HI	0x06
+#define MAX44000_REG_ALS_UPTHR_LO	0x07
+#define MAX44000_REG_ALS_LOTHR_HI	0x08
+#define MAX44000_REG_ALS_LOTHR_LO	0x09
+#define MAX44000_REG_PST		0x0a
+#define MAX44000_REG_PRX_IND		0x0b
+#define MAX44000_REG_PRX_THR		0x0c
+#define MAX44000_REG_TRIM_GAIN_GREEN	0x0f
+#define MAX44000_REG_TRIM_GAIN_IR	0x10
+
+/* REG_CFG bits */
+#define MAX44000_CFG_ALSINTE            0x01
+#define MAX44000_CFG_PRXINTE            0x02
+#define MAX44000_CFG_MASK               0x1c
+#define MAX44000_CFG_MODE_SHUTDOWN      0x00
+#define MAX44000_CFG_MODE_ALS_GIR       0x04
+#define MAX44000_CFG_MODE_ALS_G         0x08
+#define MAX44000_CFG_MODE_ALS_IR        0x0c
+#define MAX44000_CFG_MODE_ALS_PRX       0x10
+#define MAX44000_CFG_MODE_PRX           0x14
+#define MAX44000_CFG_TRIM               0x20
+
+/*
+ * Upper 4 bits are not documented but start as 1 on powerup
+ * Setting them to 0 causes proximity to misbehave so set them to 1
+ */
+#define MAX44000_REG_CFG_RX_DEFAULT 0xf0
+
+/* REG_RX bits */
+#define MAX44000_CFG_RX_ALSTIM_MASK	0x0c
+#define MAX44000_CFG_RX_ALSTIM_SHIFT	2
+#define MAX44000_CFG_RX_ALSPGA_MASK	0x03
+#define MAX44000_CFG_RX_ALSPGA_SHIFT	0
+
+/* REG_TX bits */
+#define MAX44000_LED_CURRENT_MASK	0xf
+#define MAX44000_LED_CURRENT_MAX	11
+#define MAX44000_LED_CURRENT_DEFAULT	6
+
+#define MAX44000_ALSDATA_OVERFLOW	0x4000
+
+struct max44000_data {
+	struct mutex lock;
+	struct regmap *regmap;
+};
+
+/* Default scale is set to the minimum of 0.03125 or 1 / (1 << 5) lux */
+#define MAX44000_ALS_TO_LUX_DEFAULT_FRACTION_LOG2 5
+
+/* Scale can be multiplied by up to 128x via ALSPGA for measurement gain */
+static const int max44000_alspga_shift[] = {0, 2, 4, 7};
+#define MAX44000_ALSPGA_MAX_SHIFT 7
+
+/*
+ * Scale can be multiplied by up to 64x via ALSTIM because of lost resolution
+ *
+ * This scaling factor is hidden from userspace and instead accounted for when
+ * reading raw values from the device.
+ *
+ * This makes it possible to cleanly expose ALSPGA as IIO_CHAN_INFO_SCALE and
+ * ALSTIM as IIO_CHAN_INFO_INT_TIME without the values affecting each other.
+ *
+ * Handling this internally is also required for buffer support because the
+ * channel's scan_type can't be modified dynamically.
+ */
+static const int max44000_alstim_shift[] = {0, 2, 4, 6};
+#define MAX44000_ALSTIM_SHIFT(alstim) (2 * (alstim))
+
+/* Available integration times with pretty manual alignment: */
+static const int max44000_int_time_avail_ns_array[] = {
+	   100000000,
+	    25000000,
+	     6250000,
+	     1562500,
+};
+static const char max44000_int_time_avail_str[] =
+	"0.100 "
+	"0.025 "
+	"0.00625 "
+	"0.0015625";
+
+/* Available scales (internal to ulux) with pretty manual alignment: */
+static const int max44000_scale_avail_ulux_array[] = {
+	    31250,
+	   125000,
+	   500000,
+	  4000000,
+};
+static const char max44000_scale_avail_str[] =
+	"0.03125 "
+	"0.125 "
+	"0.5 "
+	 "4";
+
+#define MAX44000_SCAN_INDEX_ALS 0
+#define MAX44000_SCAN_INDEX_PRX 1
+
+static const struct iio_chan_spec max44000_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) |
+					    BIT(IIO_CHAN_INFO_INT_TIME),
+		.scan_index = MAX44000_SCAN_INDEX_ALS,
+		.scan_type = {
+			.sign		= 'u',
+			.realbits	= 14,
+			.storagebits	= 16,
+		}
+	},
+	{
+		.type = IIO_PROXIMITY,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.scan_index = MAX44000_SCAN_INDEX_PRX,
+		.scan_type = {
+			.sign		= 'u',
+			.realbits	= 8,
+			.storagebits	= 16,
+		}
+	},
+	IIO_CHAN_SOFT_TIMESTAMP(2),
+	{
+		.type = IIO_CURRENT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.extend_name = "led",
+		.output = 1,
+		.scan_index = -1,
+	},
+};
+
+static int max44000_read_alstim(struct max44000_data *data)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(data->regmap, MAX44000_REG_CFG_RX, &val);
+	if (ret < 0)
+		return ret;
+	return (val & MAX44000_CFG_RX_ALSTIM_MASK) >> MAX44000_CFG_RX_ALSTIM_SHIFT;
+}
+
+static int max44000_write_alstim(struct max44000_data *data, int val)
+{
+	return regmap_write_bits(data->regmap, MAX44000_REG_CFG_RX,
+				 MAX44000_CFG_RX_ALSTIM_MASK,
+				 val << MAX44000_CFG_RX_ALSTIM_SHIFT);
+}
+
+static int max44000_read_alspga(struct max44000_data *data)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(data->regmap, MAX44000_REG_CFG_RX, &val);
+	if (ret < 0)
+		return ret;
+	return (val & MAX44000_CFG_RX_ALSPGA_MASK) >> MAX44000_CFG_RX_ALSPGA_SHIFT;
+}
+
+static int max44000_write_alspga(struct max44000_data *data, int val)
+{
+	return regmap_write_bits(data->regmap, MAX44000_REG_CFG_RX,
+				 MAX44000_CFG_RX_ALSPGA_MASK,
+				 val << MAX44000_CFG_RX_ALSPGA_SHIFT);
+}
+
+static int max44000_read_alsval(struct max44000_data *data)
+{
+	u16 regval;
+	__be16 val;
+	int alstim, ret;
+
+	ret = regmap_bulk_read(data->regmap, MAX44000_REG_ALS_DATA_HI,
+			       &val, sizeof(val));
+	if (ret < 0)
+		return ret;
+	alstim = ret = max44000_read_alstim(data);
+	if (ret < 0)
+		return ret;
+
+	regval = be16_to_cpu(val);
+
+	/*
+	 * Overflow is explained on datasheet page 17.
+	 *
+	 * It's a warning that either the G or IR channel has become saturated
+	 * and that the value in the register is likely incorrect.
+	 *
+	 * The recommendation is to change the scale (ALSPGA).
+	 * The driver just returns the max representable value.
+	 */
+	if (regval & MAX44000_ALSDATA_OVERFLOW)
+		return 0x3FFF;
+
+	return regval << MAX44000_ALSTIM_SHIFT(alstim);
+}
+
+static int max44000_write_led_current_raw(struct max44000_data *data, int val)
+{
+	/* Maybe we should clamp the value instead? */
+	if (val < 0 || val > MAX44000_LED_CURRENT_MAX)
+		return -ERANGE;
+	if (val >= 8)
+		val += 4;
+	return regmap_write_bits(data->regmap, MAX44000_REG_CFG_TX,
+				 MAX44000_LED_CURRENT_MASK, val);
+}
+
+static int max44000_read_led_current_raw(struct max44000_data *data)
+{
+	unsigned int regval;
+	int ret;
+
+	ret = regmap_read(data->regmap, MAX44000_REG_CFG_TX, &regval);
+	if (ret < 0)
+		return ret;
+	regval &= MAX44000_LED_CURRENT_MASK;
+	if (regval >= 8)
+		regval -= 4;
+	return regval;
+}
+
+static int max44000_read_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int *val, int *val2, long mask)
+{
+	struct max44000_data *data = iio_priv(indio_dev);
+	int alstim, alspga;
+	unsigned int regval;
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		switch (chan->type) {
+		case IIO_LIGHT:
+			mutex_lock(&data->lock);
+			ret = max44000_read_alsval(data);
+			mutex_unlock(&data->lock);
+			if (ret < 0)
+				return ret;
+			*val = ret;
+			return IIO_VAL_INT;
+
+		case IIO_PROXIMITY:
+			mutex_lock(&data->lock);
+			ret = regmap_read(data->regmap, MAX44000_REG_PRX_DATA, &regval);
+			mutex_unlock(&data->lock);
+			if (ret < 0)
+				return ret;
+			*val = regval;
+			return IIO_VAL_INT;
+
+		case IIO_CURRENT:
+			mutex_lock(&data->lock);
+			ret = max44000_read_led_current_raw(data);
+			mutex_unlock(&data->lock);
+			if (ret < 0)
+				return ret;
+			*val = ret;
+			return IIO_VAL_INT;
+
+		default:
+			return -EINVAL;
+		}
+
+	case IIO_CHAN_INFO_SCALE:
+		switch (chan->type) {
+		case IIO_CURRENT:
+			/* Output register is in 10s of miliamps */
+			*val = 10;
+			return IIO_VAL_INT;
+
+		case IIO_LIGHT:
+			mutex_lock(&data->lock);
+			alspga = ret = max44000_read_alspga(data);
+			mutex_unlock(&data->lock);
+			if (ret < 0)
+				return ret;
+
+			/* Avoid negative shifts */
+			*val = (1 << MAX44000_ALSPGA_MAX_SHIFT);
+			*val2 = MAX44000_ALS_TO_LUX_DEFAULT_FRACTION_LOG2
+					+ MAX44000_ALSPGA_MAX_SHIFT
+					- max44000_alspga_shift[alspga];
+			return IIO_VAL_FRACTIONAL_LOG2;
+
+		default:
+			return -EINVAL;
+		}
+
+	case IIO_CHAN_INFO_INT_TIME:
+		mutex_lock(&data->lock);
+		alstim = ret = max44000_read_alstim(data);
+		mutex_unlock(&data->lock);
+
+		if (ret < 0)
+			return ret;
+		*val = 0;
+		*val2 = max44000_int_time_avail_ns_array[alstim];
+		return IIO_VAL_INT_PLUS_NANO;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int max44000_write_raw(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *chan,
+			      int val, int val2, long mask)
+{
+	struct max44000_data *data = iio_priv(indio_dev);
+	int ret;
+
+	if (mask == IIO_CHAN_INFO_RAW && chan->type == IIO_CURRENT) {
+		mutex_lock(&data->lock);
+		ret = max44000_write_led_current_raw(data, val);
+		mutex_unlock(&data->lock);
+		return ret;
+	} else if (mask == IIO_CHAN_INFO_INT_TIME && chan->type == IIO_LIGHT) {
+		s64 valns = val * NSEC_PER_SEC + val2;
+		int alstim = find_closest_descending(valns,
+				max44000_int_time_avail_ns_array,
+				ARRAY_SIZE(max44000_int_time_avail_ns_array));
+		mutex_lock(&data->lock);
+		ret = max44000_write_alstim(data, alstim);
+		mutex_unlock(&data->lock);
+		return ret;
+	} else if (mask == IIO_CHAN_INFO_SCALE && chan->type == IIO_LIGHT) {
+		s64 valus = val * USEC_PER_SEC + val2;
+		int alspga = find_closest(valus,
+				max44000_scale_avail_ulux_array,
+				ARRAY_SIZE(max44000_scale_avail_ulux_array));
+		mutex_lock(&data->lock);
+		ret = max44000_write_alspga(data, alspga);
+		mutex_unlock(&data->lock);
+		return ret;
+	}
+
+	return -EINVAL;
+}
+
+static int max44000_write_raw_get_fmt(struct iio_dev *indio_dev,
+				      struct iio_chan_spec const *chan,
+				      long mask)
+{
+	if (mask == IIO_CHAN_INFO_INT_TIME && chan->type == IIO_LIGHT)
+		return IIO_VAL_INT_PLUS_NANO;
+	else if (mask == IIO_CHAN_INFO_SCALE && chan->type == IIO_LIGHT)
+		return IIO_VAL_INT_PLUS_MICRO;
+	else
+		return IIO_VAL_INT;
+}
+
+static IIO_CONST_ATTR(illuminance_integration_time_available, max44000_int_time_avail_str);
+static IIO_CONST_ATTR(illuminance_scale_available, max44000_scale_avail_str);
+
+static struct attribute *max44000_attributes[] = {
+	&iio_const_attr_illuminance_integration_time_available.dev_attr.attr,
+	&iio_const_attr_illuminance_scale_available.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group max44000_attribute_group = {
+	.attrs = max44000_attributes,
+};
+
+static const struct iio_info max44000_info = {
+	.read_raw		= max44000_read_raw,
+	.write_raw		= max44000_write_raw,
+	.write_raw_get_fmt	= max44000_write_raw_get_fmt,
+	.attrs			= &max44000_attribute_group,
+};
+
+static bool max44000_readable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case MAX44000_REG_STATUS:
+	case MAX44000_REG_CFG_MAIN:
+	case MAX44000_REG_CFG_RX:
+	case MAX44000_REG_CFG_TX:
+	case MAX44000_REG_ALS_DATA_HI:
+	case MAX44000_REG_ALS_DATA_LO:
+	case MAX44000_REG_PRX_DATA:
+	case MAX44000_REG_ALS_UPTHR_HI:
+	case MAX44000_REG_ALS_UPTHR_LO:
+	case MAX44000_REG_ALS_LOTHR_HI:
+	case MAX44000_REG_ALS_LOTHR_LO:
+	case MAX44000_REG_PST:
+	case MAX44000_REG_PRX_IND:
+	case MAX44000_REG_PRX_THR:
+	case MAX44000_REG_TRIM_GAIN_GREEN:
+	case MAX44000_REG_TRIM_GAIN_IR:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool max44000_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case MAX44000_REG_CFG_MAIN:
+	case MAX44000_REG_CFG_RX:
+	case MAX44000_REG_CFG_TX:
+	case MAX44000_REG_ALS_UPTHR_HI:
+	case MAX44000_REG_ALS_UPTHR_LO:
+	case MAX44000_REG_ALS_LOTHR_HI:
+	case MAX44000_REG_ALS_LOTHR_LO:
+	case MAX44000_REG_PST:
+	case MAX44000_REG_PRX_IND:
+	case MAX44000_REG_PRX_THR:
+	case MAX44000_REG_TRIM_GAIN_GREEN:
+	case MAX44000_REG_TRIM_GAIN_IR:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool max44000_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case MAX44000_REG_STATUS:
+	case MAX44000_REG_ALS_DATA_HI:
+	case MAX44000_REG_ALS_DATA_LO:
+	case MAX44000_REG_PRX_DATA:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool max44000_precious_reg(struct device *dev, unsigned int reg)
+{
+	return reg == MAX44000_REG_STATUS;
+}
+
+static const struct regmap_config max44000_regmap_config = {
+	.reg_bits	= 8,
+	.val_bits	= 8,
+
+	.max_register	= MAX44000_REG_PRX_DATA,
+	.readable_reg	= max44000_readable_reg,
+	.writeable_reg	= max44000_writeable_reg,
+	.volatile_reg	= max44000_volatile_reg,
+	.precious_reg	= max44000_precious_reg,
+
+	.use_single_rw	= 1,
+	.cache_type	= REGCACHE_RBTREE,
+};
+
+static irqreturn_t max44000_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct max44000_data *data = iio_priv(indio_dev);
+	u16 buf[8]; /* 2x u16 + padding + 8 bytes timestamp */
+	int index = 0;
+	unsigned int regval;
+	int ret;
+
+	mutex_lock(&data->lock);
+	if (test_bit(MAX44000_SCAN_INDEX_ALS, indio_dev->active_scan_mask)) {
+		ret = max44000_read_alsval(data);
+		if (ret < 0)
+			goto out_unlock;
+		buf[index++] = ret;
+	}
+	if (test_bit(MAX44000_SCAN_INDEX_PRX, indio_dev->active_scan_mask)) {
+		ret = regmap_read(data->regmap, MAX44000_REG_PRX_DATA, &regval);
+		if (ret < 0)
+			goto out_unlock;
+		buf[index] = regval;
+	}
+	mutex_unlock(&data->lock);
+
+	iio_push_to_buffers_with_timestamp(indio_dev, buf,
+					   iio_get_time_ns(indio_dev));
+	iio_trigger_notify_done(indio_dev->trig);
+	return IRQ_HANDLED;
+
+out_unlock:
+	mutex_unlock(&data->lock);
+	iio_trigger_notify_done(indio_dev->trig);
+	return IRQ_HANDLED;
+}
+
+static int max44000_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct max44000_data *data;
+	struct iio_dev *indio_dev;
+	int ret, reg;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+	data = iio_priv(indio_dev);
+	data->regmap = devm_regmap_init_i2c(client, &max44000_regmap_config);
+	if (IS_ERR(data->regmap)) {
+		dev_err(&client->dev, "regmap_init failed!\n");
+		return PTR_ERR(data->regmap);
+	}
+
+	i2c_set_clientdata(client, indio_dev);
+	mutex_init(&data->lock);
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->info = &max44000_info;
+	indio_dev->name = MAX44000_DRV_NAME;
+	indio_dev->channels = max44000_channels;
+	indio_dev->num_channels = ARRAY_SIZE(max44000_channels);
+
+	/*
+	 * The device doesn't have a reset function so we just clear some
+	 * important bits at probe time to ensure sane operation.
+	 *
+	 * Since we don't support interrupts/events the threshold values are
+	 * not important. We also don't touch trim values.
+	 */
+
+	/* Reset ALS scaling bits */
+	ret = regmap_write(data->regmap, MAX44000_REG_CFG_RX,
+			   MAX44000_REG_CFG_RX_DEFAULT);
+	if (ret < 0) {
+		dev_err(&client->dev, "failed to write default CFG_RX: %d\n",
+			ret);
+		return ret;
+	}
+
+	/*
+	 * By default the LED pulse used for the proximity sensor is disabled.
+	 * Set a middle value so that we get some sort of valid data by default.
+	 */
+	ret = max44000_write_led_current_raw(data, MAX44000_LED_CURRENT_DEFAULT);
+	if (ret < 0) {
+		dev_err(&client->dev, "failed to write init config: %d\n", ret);
+		return ret;
+	}
+
+	/* Reset CFG bits to ALS_PRX mode which allows easy reading of both values. */
+	reg = MAX44000_CFG_TRIM | MAX44000_CFG_MODE_ALS_PRX;
+	ret = regmap_write(data->regmap, MAX44000_REG_CFG_MAIN, reg);
+	if (ret < 0) {
+		dev_err(&client->dev, "failed to write init config: %d\n", ret);
+		return ret;
+	}
+
+	/* Read status at least once to clear any stale interrupt bits. */
+	ret = regmap_read(data->regmap, MAX44000_REG_STATUS, &reg);
+	if (ret < 0) {
+		dev_err(&client->dev, "failed to read init status: %d\n", ret);
+		return ret;
+	}
+
+	ret = iio_triggered_buffer_setup(indio_dev, NULL, max44000_trigger_handler, NULL);
+	if (ret < 0) {
+		dev_err(&client->dev, "iio triggered buffer setup failed\n");
+		return ret;
+	}
+
+	return iio_device_register(indio_dev);
+}
+
+static int max44000_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+	iio_device_unregister(indio_dev);
+	iio_triggered_buffer_cleanup(indio_dev);
+
+	return 0;
+}
+
+static const struct i2c_device_id max44000_id[] = {
+	{"max44000", 0},
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, max44000_id);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id max44000_acpi_match[] = {
+	{"MAX44000", 0},
+	{ }
+};
+MODULE_DEVICE_TABLE(acpi, max44000_acpi_match);
+#endif
+
+static struct i2c_driver max44000_driver = {
+	.driver = {
+		.name	= MAX44000_DRV_NAME,
+		.acpi_match_table = ACPI_PTR(max44000_acpi_match),
+	},
+	.probe		= max44000_probe,
+	.remove		= max44000_remove,
+	.id_table	= max44000_id,
+};
+
+module_i2c_driver(max44000_driver);
+
+MODULE_AUTHOR("Crestez Dan Leonard <leonard.crestez@intel.com>");
+MODULE_DESCRIPTION("MAX44000 Ambient and Infrared Proximity Sensor");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/light/opt3001.c b/drivers/iio/light/opt3001.c
new file mode 100644
index 0000000..54d88b6
--- /dev/null
+++ b/drivers/iio/light/opt3001.c
@@ -0,0 +1,859 @@
+/**
+ * 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)
+
+#define OPT3001_INT_TIME_LONG		800000
+#define OPT3001_INT_TIME_SHORT		100000
+
+/*
+ * Time to wait for conversion result to be ready. The device datasheet
+ * sect. 6.5 states results are ready after total integration time plus 3ms.
+ * This results in worst-case max values of 113ms or 883ms, respectively.
+ * Add some slack to be on the safe side.
+ */
+#define OPT3001_RESULT_READY_SHORT	150
+#define OPT3001_RESULT_READY_LONG	1000
+
+struct opt3001 {
+	struct i2c_client	*client;
+	struct device		*dev;
+
+	struct mutex		lock;
+	bool			ok_to_ignore_lock;
+	bool			result_ready;
+	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;
+
+	bool			use_irq;
+};
+
+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;
+	long timeout;
+
+	if (opt->use_irq) {
+		/*
+		 * 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;
+		}
+
+		/* Allow IRQ to access the device despite lock being set */
+		opt->ok_to_ignore_lock = true;
+	}
+
+	/* Reset data-ready indicator flag */
+	opt->result_ready = false;
+
+	/* Configure for single-conversion mode and start a new conversion */
+	ret = i2c_smbus_read_word_swapped(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;
+	}
+
+	if (opt->use_irq) {
+		/* Wait for the IRQ to indicate the conversion is complete */
+		ret = wait_event_timeout(opt->result_ready_queue,
+				opt->result_ready,
+				msecs_to_jiffies(OPT3001_RESULT_READY_LONG));
+	} else {
+		/* Sleep for result ready time */
+		timeout = (opt->int_time == OPT3001_INT_TIME_SHORT) ?
+			OPT3001_RESULT_READY_SHORT : OPT3001_RESULT_READY_LONG;
+		msleep(timeout);
+
+		/* Check result ready flag */
+		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;
+		}
+
+		if (!(ret & OPT3001_CONFIGURATION_CRF)) {
+			ret = -ETIMEDOUT;
+			goto err;
+		}
+
+		/* Obtain value */
+		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 err;
+		}
+		opt->result = ret;
+		opt->result_ready = true;
+	}
+
+err:
+	if (opt->use_irq)
+		/* 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;
+
+	if (opt->use_irq) {
+		/*
+		 * 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 OPT3001_INT_TIME_SHORT:
+		reg &= ~OPT3001_CONFIGURATION_CT;
+		opt->int_time = OPT3001_INT_TIME_SHORT;
+		break;
+	case OPT3001_INT_TIME_LONG:
+		reg |= OPT3001_CONFIGURATION_CT;
+		opt->int_time = OPT3001_INT_TIME_LONG;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return i2c_smbus_write_word_swapped(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 = {
+	.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 = OPT3001_INT_TIME_LONG;
+	else
+		opt->int_time = OPT3001_INT_TIME_SHORT;
+
+	/* 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(iio));
+		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(iio));
+	} 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;
+	}
+
+	/* Make use of INT pin only if valid IRQ no. is given */
+	if (irq > 0) {
+		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;
+		}
+		opt->use_irq = true;
+	} else {
+		dev_dbg(opt->dev, "enabling interrupt-less operation\n");
+	}
+
+	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;
+
+	if (opt->use_irq)
+		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" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, opt3001_of_match);
+
+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/drivers/iio/light/pa12203001.c b/drivers/iio/light/pa12203001.c
new file mode 100644
index 0000000..30ea1a0
--- /dev/null
+++ b/drivers/iio/light/pa12203001.c
@@ -0,0 +1,488 @@
+/*
+ * 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 = {
+	.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)
+		goto out_err;
+
+	pm_runtime_enable(&client->dev);
+	pm_runtime_set_autosuspend_delay(&client->dev,
+					 PA12203001_SLEEP_DELAY_MS);
+	pm_runtime_use_autosuspend(&client->dev);
+
+	ret = iio_device_register(indio_dev);
+	if (ret < 0)
+		goto out_err;
+
+	return 0;
+
+out_err:
+	pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE);
+	return ret;
+}
+
+static 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/drivers/iio/light/rpr0521.c b/drivers/iio/light/rpr0521.c
new file mode 100644
index 0000000..ffe9ce7
--- /dev/null
+++ b/drivers/iio/light/rpr0521.c
@@ -0,0 +1,1142 @@
+/*
+ * 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
+ */
+
+#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/buffer.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/sysfs.h>
+#include <linux/pm_runtime.h>
+
+#define RPR0521_REG_SYSTEM_CTRL		0x40
+#define RPR0521_REG_MODE_CTRL		0x41
+#define RPR0521_REG_ALS_CTRL		0x42
+#define RPR0521_REG_PXS_CTRL		0x43
+#define RPR0521_REG_PXS_DATA		0x44 /* 16-bit, little endian */
+#define RPR0521_REG_ALS_DATA0		0x46 /* 16-bit, little endian */
+#define RPR0521_REG_ALS_DATA1		0x48 /* 16-bit, little endian */
+#define RPR0521_REG_INTERRUPT		0x4A
+#define RPR0521_REG_PS_OFFSET_LSB	0x53
+#define RPR0521_REG_ID			0x92
+
+#define RPR0521_MODE_ALS_MASK		BIT(7)
+#define RPR0521_MODE_PXS_MASK		BIT(6)
+#define RPR0521_MODE_MEAS_TIME_MASK	GENMASK(3, 0)
+#define RPR0521_ALS_DATA0_GAIN_MASK	GENMASK(5, 4)
+#define RPR0521_ALS_DATA0_GAIN_SHIFT	4
+#define RPR0521_ALS_DATA1_GAIN_MASK	GENMASK(3, 2)
+#define RPR0521_ALS_DATA1_GAIN_SHIFT	2
+#define RPR0521_PXS_GAIN_MASK		GENMASK(5, 4)
+#define RPR0521_PXS_GAIN_SHIFT		4
+#define RPR0521_PXS_PERSISTENCE_MASK	GENMASK(3, 0)
+#define RPR0521_INTERRUPT_INT_TRIG_PS_MASK	BIT(0)
+#define RPR0521_INTERRUPT_INT_TRIG_ALS_MASK	BIT(1)
+#define RPR0521_INTERRUPT_INT_REASSERT_MASK	BIT(3)
+#define RPR0521_INTERRUPT_ALS_INT_STATUS_MASK	BIT(6)
+#define RPR0521_INTERRUPT_PS_INT_STATUS_MASK	BIT(7)
+
+#define RPR0521_MODE_ALS_ENABLE		BIT(7)
+#define RPR0521_MODE_ALS_DISABLE	0x00
+#define RPR0521_MODE_PXS_ENABLE		BIT(6)
+#define RPR0521_MODE_PXS_DISABLE	0x00
+#define RPR0521_PXS_PERSISTENCE_DRDY	0x00
+
+#define RPR0521_INTERRUPT_INT_TRIG_PS_ENABLE	BIT(0)
+#define RPR0521_INTERRUPT_INT_TRIG_PS_DISABLE	0x00
+#define RPR0521_INTERRUPT_INT_TRIG_ALS_ENABLE	BIT(1)
+#define RPR0521_INTERRUPT_INT_TRIG_ALS_DISABLE	0x00
+#define RPR0521_INTERRUPT_INT_REASSERT_ENABLE	BIT(3)
+#define RPR0521_INTERRUPT_INT_REASSERT_DISABLE	0x00
+
+#define RPR0521_MANUFACT_ID		0xE0
+#define RPR0521_DEFAULT_MEAS_TIME	0x06 /* ALS - 100ms, PXS - 100ms */
+
+#define RPR0521_DRV_NAME		"RPR0521"
+#define RPR0521_IRQ_NAME		"rpr0521_event"
+#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_PXS,
+	RPR0521_CHAN_ALS_DATA0,
+	RPR0521_CHAN_ALS_DATA1,
+};
+
+struct rpr0521_reg_desc {
+	u8 address;
+	u8 device_mask;
+};
+
+static const struct rpr0521_reg_desc rpr0521_data_reg[] = {
+	[RPR0521_CHAN_PXS]	= {
+		.address	= RPR0521_REG_PXS_DATA,
+		.device_mask	= RPR0521_MODE_PXS_MASK,
+	},
+	[RPR0521_CHAN_ALS_DATA0] = {
+		.address	= RPR0521_REG_ALS_DATA0,
+		.device_mask	= RPR0521_MODE_ALS_MASK,
+	},
+	[RPR0521_CHAN_ALS_DATA1] = {
+		.address	= RPR0521_REG_ALS_DATA1,
+		.device_mask	= RPR0521_MODE_ALS_MASK,
+	},
+};
+
+static const struct rpr0521_gain_info {
+	u8 reg;
+	u8 mask;
+	u8 shift;
+	const struct rpr0521_gain *gain;
+	int size;
+} rpr0521_gain[] = {
+	[RPR0521_CHAN_PXS] = {
+		.reg	= RPR0521_REG_PXS_CTRL,
+		.mask	= RPR0521_PXS_GAIN_MASK,
+		.shift	= RPR0521_PXS_GAIN_SHIFT,
+		.gain	= rpr0521_pxs_gain,
+		.size	= ARRAY_SIZE(rpr0521_pxs_gain),
+	},
+	[RPR0521_CHAN_ALS_DATA0] = {
+		.reg	= RPR0521_REG_ALS_CTRL,
+		.mask	= RPR0521_ALS_DATA0_GAIN_MASK,
+		.shift	= RPR0521_ALS_DATA0_GAIN_SHIFT,
+		.gain	= rpr0521_als_gain,
+		.size	= ARRAY_SIZE(rpr0521_als_gain),
+	},
+	[RPR0521_CHAN_ALS_DATA1] = {
+		.reg	= RPR0521_REG_ALS_CTRL,
+		.mask	= RPR0521_ALS_DATA1_GAIN_MASK,
+		.shift	= RPR0521_ALS_DATA1_GAIN_SHIFT,
+		.gain	= rpr0521_als_gain,
+		.size	= ARRAY_SIZE(rpr0521_als_gain),
+	},
+};
+
+struct rpr0521_samp_freq {
+	int	als_hz;
+	int	als_uhz;
+	int	pxs_hz;
+	int	pxs_uhz;
+};
+
+static const struct rpr0521_samp_freq rpr0521_samp_freq_i[13] = {
+/*	{ALS, PXS},		   W==currently writable option */
+	{0, 0, 0, 0},		/* W0000, 0=standby */
+	{0, 0, 100, 0},		/*  0001 */
+	{0, 0, 25, 0},		/*  0010 */
+	{0, 0, 10, 0},		/*  0011 */
+	{0, 0, 2, 500000},	/*  0100 */
+	{10, 0, 20, 0},		/*  0101 */
+	{10, 0, 10, 0},		/* W0110 */
+	{10, 0, 2, 500000},	/*  0111 */
+	{2, 500000, 20, 0},	/*  1000, measurement 100ms, sleep 300ms */
+	{2, 500000, 10, 0},	/*  1001, measurement 100ms, sleep 300ms */
+	{2, 500000, 0, 0},	/*  1010, high sensitivity mode */
+	{2, 500000, 2, 500000},	/* W1011, high sensitivity mode */
+	{20, 0, 20, 0}	/* 1100, ALS_data x 0.5, see specification P.18 */
+};
+
+struct rpr0521_data {
+	struct i2c_client *client;
+
+	/* protect device params updates (e.g state, gain) */
+	struct mutex lock;
+
+	/* device active status */
+	bool als_dev_en;
+	bool pxs_dev_en;
+
+	struct iio_trigger *drdy_trigger0;
+	s64 irq_timestamp;
+
+	/* optimize runtime pm ops - enable/disable device only if needed */
+	bool als_ps_need_en;
+	bool pxs_ps_need_en;
+	bool als_need_dis;
+	bool pxs_need_dis;
+
+	struct regmap *regmap;
+};
+
+static IIO_CONST_ATTR(in_intensity_scale_available, RPR0521_ALS_SCALE_AVAIL);
+static IIO_CONST_ATTR(in_proximity_scale_available, RPR0521_PXS_SCALE_AVAIL);
+
+/*
+ * Start with easy freq first, whole table of freq combinations is more
+ * complicated.
+ */
+static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("2.5 10");
+
+static struct attribute *rpr0521_attributes[] = {
+	&iio_const_attr_in_intensity_scale_available.dev_attr.attr,
+	&iio_const_attr_in_proximity_scale_available.dev_attr.attr,
+	&iio_const_attr_sampling_frequency_available.dev_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group rpr0521_attribute_group = {
+	.attrs = rpr0521_attributes,
+};
+
+/* Order of the channel data in buffer */
+enum rpr0521_scan_index_order {
+	RPR0521_CHAN_INDEX_PXS,
+	RPR0521_CHAN_INDEX_BOTH,
+	RPR0521_CHAN_INDEX_IR,
+};
+
+static const unsigned long rpr0521_available_scan_masks[] = {
+	BIT(RPR0521_CHAN_INDEX_PXS) | BIT(RPR0521_CHAN_INDEX_BOTH) |
+	BIT(RPR0521_CHAN_INDEX_IR),
+	0
+};
+
+static const struct iio_chan_spec rpr0521_channels[] = {
+	{
+		.type = IIO_PROXIMITY,
+		.address = RPR0521_CHAN_PXS,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+			BIT(IIO_CHAN_INFO_OFFSET) |
+			BIT(IIO_CHAN_INFO_SCALE),
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+		.scan_index = RPR0521_CHAN_INDEX_PXS,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 16,
+			.storagebits = 16,
+			.endianness = IIO_LE,
+		},
+	},
+	{
+		.type = IIO_INTENSITY,
+		.modified = 1,
+		.address = RPR0521_CHAN_ALS_DATA0,
+		.channel2 = IIO_MOD_LIGHT_BOTH,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+			BIT(IIO_CHAN_INFO_SCALE),
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+		.scan_index = RPR0521_CHAN_INDEX_BOTH,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 16,
+			.storagebits = 16,
+			.endianness = IIO_LE,
+		},
+	},
+	{
+		.type = IIO_INTENSITY,
+		.modified = 1,
+		.address = RPR0521_CHAN_ALS_DATA1,
+		.channel2 = IIO_MOD_LIGHT_IR,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+			BIT(IIO_CHAN_INFO_SCALE),
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+		.scan_index = RPR0521_CHAN_INDEX_IR,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 16,
+			.storagebits = 16,
+			.endianness = IIO_LE,
+		},
+	},
+};
+
+static int rpr0521_als_enable(struct rpr0521_data *data, u8 status)
+{
+	int ret;
+
+	ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL,
+				 RPR0521_MODE_ALS_MASK,
+				 status);
+	if (ret < 0)
+		return ret;
+
+	if (status & RPR0521_MODE_ALS_MASK)
+		data->als_dev_en = true;
+	else
+		data->als_dev_en = false;
+
+	return 0;
+}
+
+static int rpr0521_pxs_enable(struct rpr0521_data *data, u8 status)
+{
+	int ret;
+
+	ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL,
+				 RPR0521_MODE_PXS_MASK,
+				 status);
+	if (ret < 0)
+		return ret;
+
+	if (status & RPR0521_MODE_PXS_MASK)
+		data->pxs_dev_en = true;
+	else
+		data->pxs_dev_en = false;
+
+	return 0;
+}
+
+/**
+ * rpr0521_set_power_state - handles runtime PM state and sensors enabled status
+ *
+ * @data: rpr0521 device private data
+ * @on: state to be set for devices in @device_mask
+ * @device_mask: bitmask specifying for which device we need to update @on state
+ *
+ * Calls for this function must be balanced so that each ON should have matching
+ * OFF. Otherwise pm usage_count gets out of sync.
+ */
+static int rpr0521_set_power_state(struct rpr0521_data *data, bool on,
+				   u8 device_mask)
+{
+#ifdef CONFIG_PM
+	int ret;
+
+	if (device_mask & RPR0521_MODE_ALS_MASK) {
+		data->als_ps_need_en = on;
+		data->als_need_dis = !on;
+	}
+
+	if (device_mask & RPR0521_MODE_PXS_MASK) {
+		data->pxs_ps_need_en = on;
+		data->pxs_need_dis = !on;
+	}
+
+	/*
+	 * On: _resume() is called only when we are suspended
+	 * Off: _suspend() is called after delay if _resume() is not
+	 * called before that.
+	 * Note: If either measurement is re-enabled before _suspend(),
+	 * both stay enabled until _suspend().
+	 */
+	if (on) {
+		ret = pm_runtime_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;
+	}
+
+	if (on) {
+		/* If _resume() was not called, enable measurement now. */
+		if (data->als_ps_need_en) {
+			ret = rpr0521_als_enable(data, RPR0521_MODE_ALS_ENABLE);
+			if (ret)
+				return ret;
+			data->als_ps_need_en = false;
+		}
+
+		if (data->pxs_ps_need_en) {
+			ret = rpr0521_pxs_enable(data, RPR0521_MODE_PXS_ENABLE);
+			if (ret)
+				return ret;
+			data->pxs_ps_need_en = false;
+		}
+	}
+#endif
+	return 0;
+}
+
+/* Interrupt register tells if this sensor caused the interrupt or not. */
+static inline bool rpr0521_is_triggered(struct rpr0521_data *data)
+{
+	int ret;
+	int reg;
+
+	ret = regmap_read(data->regmap, RPR0521_REG_INTERRUPT, &reg);
+	if (ret < 0)
+		return false;   /* Reg read failed. */
+	if (reg &
+	    (RPR0521_INTERRUPT_ALS_INT_STATUS_MASK |
+	    RPR0521_INTERRUPT_PS_INT_STATUS_MASK))
+		return true;
+	else
+		return false;   /* Int not from this sensor. */
+}
+
+/* IRQ to trigger handler */
+static irqreturn_t rpr0521_drdy_irq_handler(int irq, void *private)
+{
+	struct iio_dev *indio_dev = private;
+	struct rpr0521_data *data = iio_priv(indio_dev);
+
+	data->irq_timestamp = iio_get_time_ns(indio_dev);
+	/*
+	 * We need to wake the thread to read the interrupt reg. It
+	 * is not possible to do that here because regmap_read takes a
+	 * mutex.
+	 */
+
+	return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t rpr0521_drdy_irq_thread(int irq, void *private)
+{
+	struct iio_dev *indio_dev = private;
+	struct rpr0521_data *data = iio_priv(indio_dev);
+
+	if (rpr0521_is_triggered(data)) {
+		iio_trigger_poll_chained(data->drdy_trigger0);
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+static irqreturn_t rpr0521_trigger_consumer_store_time(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+
+	/* Other trigger polls store time here. */
+	if (!iio_trigger_using_own(indio_dev))
+		pf->timestamp = iio_get_time_ns(indio_dev);
+
+	return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t rpr0521_trigger_consumer_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct rpr0521_data *data = iio_priv(indio_dev);
+	int err;
+
+	u8 buffer[16]; /* 3 16-bit channels + padding + ts */
+
+	/* Use irq timestamp when reasonable. */
+	if (iio_trigger_using_own(indio_dev) && data->irq_timestamp) {
+		pf->timestamp = data->irq_timestamp;
+		data->irq_timestamp = 0;
+	}
+	/* Other chained trigger polls get timestamp only here. */
+	if (!pf->timestamp)
+		pf->timestamp = iio_get_time_ns(indio_dev);
+
+	err = regmap_bulk_read(data->regmap, RPR0521_REG_PXS_DATA,
+		&buffer,
+		(3 * 2) + 1);	/* 3 * 16-bit + (discarded) int clear reg. */
+	if (!err)
+		iio_push_to_buffers_with_timestamp(indio_dev,
+						   buffer, pf->timestamp);
+	else
+		dev_err(&data->client->dev,
+			"Trigger consumer can't read from sensor.\n");
+	pf->timestamp = 0;
+
+	iio_trigger_notify_done(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
+static int rpr0521_write_int_enable(struct rpr0521_data *data)
+{
+	int err;
+
+	/* Interrupt after each measurement */
+	err = regmap_update_bits(data->regmap, RPR0521_REG_PXS_CTRL,
+		RPR0521_PXS_PERSISTENCE_MASK,
+		RPR0521_PXS_PERSISTENCE_DRDY);
+	if (err) {
+		dev_err(&data->client->dev, "PS control reg write fail.\n");
+		return -EBUSY;
+		}
+
+	/* Ignore latch and mode because of drdy */
+	err = regmap_write(data->regmap, RPR0521_REG_INTERRUPT,
+		RPR0521_INTERRUPT_INT_REASSERT_DISABLE |
+		RPR0521_INTERRUPT_INT_TRIG_ALS_DISABLE |
+		RPR0521_INTERRUPT_INT_TRIG_PS_ENABLE
+		);
+	if (err) {
+		dev_err(&data->client->dev, "Interrupt setup write fail.\n");
+		return -EBUSY;
+		}
+
+	return 0;
+}
+
+static int rpr0521_write_int_disable(struct rpr0521_data *data)
+{
+	/* Don't care of clearing mode, assert and latch. */
+	return regmap_write(data->regmap, RPR0521_REG_INTERRUPT,
+				RPR0521_INTERRUPT_INT_TRIG_ALS_DISABLE |
+				RPR0521_INTERRUPT_INT_TRIG_PS_DISABLE
+				);
+}
+
+/*
+ * Trigger producer enable / disable. Note that there will be trigs only when
+ * measurement data is ready to be read.
+ */
+static int rpr0521_pxs_drdy_set_state(struct iio_trigger *trigger,
+	bool enable_drdy)
+{
+	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trigger);
+	struct rpr0521_data *data = iio_priv(indio_dev);
+	int err;
+
+	if (enable_drdy)
+		err = rpr0521_write_int_enable(data);
+	else
+		err = rpr0521_write_int_disable(data);
+	if (err)
+		dev_err(&data->client->dev, "rpr0521_pxs_drdy_set_state failed\n");
+
+	return err;
+}
+
+static const struct iio_trigger_ops rpr0521_trigger_ops = {
+	.set_trigger_state = rpr0521_pxs_drdy_set_state,
+	};
+
+
+static int rpr0521_buffer_preenable(struct iio_dev *indio_dev)
+{
+	int err;
+	struct rpr0521_data *data = iio_priv(indio_dev);
+
+	mutex_lock(&data->lock);
+	err = rpr0521_set_power_state(data, true,
+		(RPR0521_MODE_PXS_MASK | RPR0521_MODE_ALS_MASK));
+	mutex_unlock(&data->lock);
+	if (err)
+		dev_err(&data->client->dev, "_buffer_preenable fail\n");
+
+	return err;
+}
+
+static int rpr0521_buffer_postdisable(struct iio_dev *indio_dev)
+{
+	int err;
+	struct rpr0521_data *data = iio_priv(indio_dev);
+
+	mutex_lock(&data->lock);
+	err = rpr0521_set_power_state(data, false,
+		(RPR0521_MODE_PXS_MASK | RPR0521_MODE_ALS_MASK));
+	mutex_unlock(&data->lock);
+	if (err)
+		dev_err(&data->client->dev, "_buffer_postdisable fail\n");
+
+	return err;
+}
+
+static const struct iio_buffer_setup_ops rpr0521_buffer_setup_ops = {
+	.preenable = rpr0521_buffer_preenable,
+	.postenable = iio_triggered_buffer_postenable,
+	.predisable = iio_triggered_buffer_predisable,
+	.postdisable = rpr0521_buffer_postdisable,
+};
+
+static int rpr0521_get_gain(struct rpr0521_data *data, int chan,
+			    int *val, int *val2)
+{
+	int ret, reg, idx;
+
+	ret = regmap_read(data->regmap, rpr0521_gain[chan].reg, &reg);
+	if (ret < 0)
+		return ret;
+
+	idx = (rpr0521_gain[chan].mask & reg) >> rpr0521_gain[chan].shift;
+	*val = rpr0521_gain[chan].gain[idx].scale;
+	*val2 = rpr0521_gain[chan].gain[idx].uscale;
+
+	return 0;
+}
+
+static int rpr0521_set_gain(struct rpr0521_data *data, int chan,
+			    int val, int val2)
+{
+	int i, idx = -EINVAL;
+
+	/* get gain index */
+	for (i = 0; i < rpr0521_gain[chan].size; i++)
+		if (val == rpr0521_gain[chan].gain[i].scale &&
+		    val2 == rpr0521_gain[chan].gain[i].uscale) {
+			idx = i;
+			break;
+		}
+
+	if (idx < 0)
+		return idx;
+
+	return regmap_update_bits(data->regmap, rpr0521_gain[chan].reg,
+				  rpr0521_gain[chan].mask,
+				  idx << rpr0521_gain[chan].shift);
+}
+
+static int rpr0521_read_samp_freq(struct rpr0521_data *data,
+				enum iio_chan_type chan_type,
+			    int *val, int *val2)
+{
+	int reg, ret;
+
+	ret = regmap_read(data->regmap, RPR0521_REG_MODE_CTRL, &reg);
+	if (ret < 0)
+		return ret;
+
+	reg &= RPR0521_MODE_MEAS_TIME_MASK;
+	if (reg >= ARRAY_SIZE(rpr0521_samp_freq_i))
+		return -EINVAL;
+
+	switch (chan_type) {
+	case IIO_INTENSITY:
+		*val = rpr0521_samp_freq_i[reg].als_hz;
+		*val2 = rpr0521_samp_freq_i[reg].als_uhz;
+		return 0;
+
+	case IIO_PROXIMITY:
+		*val = rpr0521_samp_freq_i[reg].pxs_hz;
+		*val2 = rpr0521_samp_freq_i[reg].pxs_uhz;
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int rpr0521_write_samp_freq_common(struct rpr0521_data *data,
+				enum iio_chan_type chan_type,
+				int val, int val2)
+{
+	int i;
+
+	/*
+	 * Ignore channel
+	 * both pxs and als are setup only to same freq because of simplicity
+	 */
+	switch (val) {
+	case 0:
+		i = 0;
+		break;
+
+	case 2:
+		if (val2 != 500000)
+			return -EINVAL;
+
+		i = 11;
+		break;
+
+	case 10:
+		i = 6;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return regmap_update_bits(data->regmap,
+		RPR0521_REG_MODE_CTRL,
+		RPR0521_MODE_MEAS_TIME_MASK,
+		i);
+}
+
+static int rpr0521_read_ps_offset(struct rpr0521_data *data, int *offset)
+{
+	int ret;
+	__le16 buffer;
+
+	ret = regmap_bulk_read(data->regmap,
+		RPR0521_REG_PS_OFFSET_LSB, &buffer, sizeof(buffer));
+
+	if (ret < 0) {
+		dev_err(&data->client->dev, "Failed to read PS OFFSET register\n");
+		return ret;
+	}
+	*offset = le16_to_cpu(buffer);
+
+	return ret;
+}
+
+static int rpr0521_write_ps_offset(struct rpr0521_data *data, int offset)
+{
+	int ret;
+	__le16 buffer;
+
+	buffer = cpu_to_le16(offset & 0x3ff);
+	ret = regmap_raw_write(data->regmap,
+		RPR0521_REG_PS_OFFSET_LSB, &buffer, sizeof(buffer));
+
+	if (ret < 0) {
+		dev_err(&data->client->dev, "Failed to write PS OFFSET register\n");
+		return ret;
+	}
+
+	return ret;
+}
+
+static int rpr0521_read_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan, int *val,
+			    int *val2, long mask)
+{
+	struct rpr0521_data *data = iio_priv(indio_dev);
+	int ret;
+	int busy;
+	u8 device_mask;
+	__le16 raw_data;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		if (chan->type != IIO_INTENSITY && chan->type != IIO_PROXIMITY)
+			return -EINVAL;
+
+		busy = iio_device_claim_direct_mode(indio_dev);
+		if (busy)
+			return -EBUSY;
+
+		device_mask = rpr0521_data_reg[chan->address].device_mask;
+
+		mutex_lock(&data->lock);
+		ret = rpr0521_set_power_state(data, true, device_mask);
+		if (ret < 0)
+			goto rpr0521_read_raw_out;
+
+		ret = regmap_bulk_read(data->regmap,
+				       rpr0521_data_reg[chan->address].address,
+				       &raw_data, sizeof(raw_data));
+		if (ret < 0) {
+			rpr0521_set_power_state(data, false, device_mask);
+			goto rpr0521_read_raw_out;
+		}
+
+		ret = rpr0521_set_power_state(data, false, device_mask);
+
+rpr0521_read_raw_out:
+		mutex_unlock(&data->lock);
+		iio_device_release_direct_mode(indio_dev);
+		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;
+
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		mutex_lock(&data->lock);
+		ret = rpr0521_read_samp_freq(data, chan->type, val, val2);
+		mutex_unlock(&data->lock);
+		if (ret < 0)
+			return ret;
+
+		return IIO_VAL_INT_PLUS_MICRO;
+
+	case IIO_CHAN_INFO_OFFSET:
+		mutex_lock(&data->lock);
+		ret = rpr0521_read_ps_offset(data, val);
+		mutex_unlock(&data->lock);
+		if (ret < 0)
+			return ret;
+
+		return IIO_VAL_INT;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int rpr0521_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan, int val,
+			     int val2, long mask)
+{
+	struct rpr0521_data *data = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		mutex_lock(&data->lock);
+		ret = rpr0521_set_gain(data, chan->address, val, val2);
+		mutex_unlock(&data->lock);
+
+		return ret;
+
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		mutex_lock(&data->lock);
+		ret = rpr0521_write_samp_freq_common(data, chan->type,
+						     val, val2);
+		mutex_unlock(&data->lock);
+
+		return ret;
+
+	case IIO_CHAN_INFO_OFFSET:
+		mutex_lock(&data->lock);
+		ret = rpr0521_write_ps_offset(data, val);
+		mutex_unlock(&data->lock);
+
+		return ret;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info rpr0521_info = {
+	.read_raw	= rpr0521_read_raw,
+	.write_raw	= rpr0521_write_raw,
+	.attrs		= &rpr0521_attribute_group,
+};
+
+static int rpr0521_init(struct rpr0521_data *data)
+{
+	int ret;
+	int id;
+
+	ret = regmap_read(data->regmap, RPR0521_REG_ID, &id);
+	if (ret < 0) {
+		dev_err(&data->client->dev, "Failed to read REG_ID register\n");
+		return ret;
+	}
+
+	if (id != RPR0521_MANUFACT_ID) {
+		dev_err(&data->client->dev, "Wrong id, got %x, expected %x\n",
+			id, RPR0521_MANUFACT_ID);
+		return -ENODEV;
+	}
+
+	/* set default measurement time - 100 ms for both ALS and PS */
+	ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL,
+				 RPR0521_MODE_MEAS_TIME_MASK,
+				 RPR0521_DEFAULT_MEAS_TIME);
+	if (ret) {
+		pr_err("regmap_update_bits returned %d\n", ret);
+		return ret;
+	}
+
+#ifndef CONFIG_PM
+	ret = rpr0521_als_enable(data, RPR0521_MODE_ALS_ENABLE);
+	if (ret < 0)
+		return ret;
+	ret = rpr0521_pxs_enable(data, RPR0521_MODE_PXS_ENABLE);
+	if (ret < 0)
+		return ret;
+#endif
+
+	data->irq_timestamp = 0;
+
+	return 0;
+}
+
+static int rpr0521_poweroff(struct rpr0521_data *data)
+{
+	int ret;
+	int tmp;
+
+	ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL,
+				 RPR0521_MODE_ALS_MASK |
+				 RPR0521_MODE_PXS_MASK,
+				 RPR0521_MODE_ALS_DISABLE |
+				 RPR0521_MODE_PXS_DISABLE);
+	if (ret < 0)
+		return ret;
+
+	data->als_dev_en = false;
+	data->pxs_dev_en = false;
+
+	/*
+	 * Int pin keeps state after power off. Set pin to high impedance
+	 * mode to prevent power drain.
+	 */
+	ret = regmap_read(data->regmap, RPR0521_REG_INTERRUPT, &tmp);
+	if (ret) {
+		dev_err(&data->client->dev, "Failed to reset int pin.\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static bool rpr0521_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case RPR0521_REG_MODE_CTRL:
+	case RPR0521_REG_ALS_CTRL:
+	case RPR0521_REG_PXS_CTRL:
+		return false;
+	default:
+		return true;
+	}
+}
+
+static const struct regmap_config rpr0521_regmap_config = {
+	.name		= RPR0521_REGMAP_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 = pm_runtime_set_active(&client->dev);
+	if (ret < 0)
+		goto err_poweroff;
+
+	pm_runtime_enable(&client->dev);
+	pm_runtime_set_autosuspend_delay(&client->dev, RPR0521_SLEEP_DELAY_MS);
+	pm_runtime_use_autosuspend(&client->dev);
+
+	/*
+	 * If sensor write/read is needed in _probe after _use_autosuspend,
+	 * sensor needs to be _resumed first using rpr0521_set_power_state().
+	 */
+
+	/* IRQ to trigger setup */
+	if (client->irq) {
+		/* Trigger0 producer setup */
+		data->drdy_trigger0 = devm_iio_trigger_alloc(
+			indio_dev->dev.parent,
+			"%s-dev%d", indio_dev->name, indio_dev->id);
+		if (!data->drdy_trigger0) {
+			ret = -ENOMEM;
+			goto err_pm_disable;
+		}
+		data->drdy_trigger0->dev.parent = indio_dev->dev.parent;
+		data->drdy_trigger0->ops = &rpr0521_trigger_ops;
+		indio_dev->available_scan_masks = rpr0521_available_scan_masks;
+		iio_trigger_set_drvdata(data->drdy_trigger0, indio_dev);
+
+		/* Ties irq to trigger producer handler. */
+		ret = devm_request_threaded_irq(&client->dev, client->irq,
+			rpr0521_drdy_irq_handler, rpr0521_drdy_irq_thread,
+			IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+			RPR0521_IRQ_NAME, indio_dev);
+		if (ret < 0) {
+			dev_err(&client->dev, "request irq %d for trigger0 failed\n",
+				client->irq);
+			goto err_pm_disable;
+			}
+
+		ret = devm_iio_trigger_register(indio_dev->dev.parent,
+						data->drdy_trigger0);
+		if (ret) {
+			dev_err(&client->dev, "iio trigger register failed\n");
+			goto err_pm_disable;
+		}
+
+		/*
+		 * Now whole pipe from physical interrupt (irq defined by
+		 * devicetree to device) to trigger0 output is set up.
+		 */
+
+		/* Trigger consumer setup */
+		ret = devm_iio_triggered_buffer_setup(indio_dev->dev.parent,
+			indio_dev,
+			rpr0521_trigger_consumer_store_time,
+			rpr0521_trigger_consumer_handler,
+			&rpr0521_buffer_setup_ops);
+		if (ret < 0) {
+			dev_err(&client->dev, "iio triggered buffer setup failed\n");
+			goto err_pm_disable;
+		}
+	}
+
+	ret = iio_device_register(indio_dev);
+	if (ret)
+		goto err_pm_disable;
+
+	return 0;
+
+err_pm_disable:
+	pm_runtime_disable(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+	pm_runtime_put_noidle(&client->dev);
+err_poweroff:
+	rpr0521_poweroff(data);
+
+	return ret;
+}
+
+static int rpr0521_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+	iio_device_unregister(indio_dev);
+
+	pm_runtime_disable(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+	pm_runtime_put_noidle(&client->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;
+
+	mutex_lock(&data->lock);
+	/* If measurements are enabled, enable them on resume */
+	if (!data->als_need_dis)
+		data->als_ps_need_en = data->als_dev_en;
+	if (!data->pxs_need_dis)
+		data->pxs_ps_need_en = data->pxs_dev_en;
+
+	/* disable channels and sets {als,pxs}_dev_en to false */
+	ret = rpr0521_poweroff(data);
+	regcache_mark_dirty(data->regmap);
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+
+static int rpr0521_runtime_resume(struct device *dev)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+	struct rpr0521_data *data = iio_priv(indio_dev);
+	int ret;
+
+	regcache_sync(data->regmap);
+	if (data->als_ps_need_en) {
+		ret = rpr0521_als_enable(data, RPR0521_MODE_ALS_ENABLE);
+		if (ret < 0)
+			return ret;
+		data->als_ps_need_en = false;
+	}
+
+	if (data->pxs_ps_need_en) {
+		ret = rpr0521_pxs_enable(data, RPR0521_MODE_PXS_ENABLE);
+		if (ret < 0)
+			return ret;
+		data->pxs_ps_need_en = false;
+	}
+	msleep(100);	//wait for first measurement result
+
+	return 0;
+}
+#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/drivers/iio/light/si1133.c b/drivers/iio/light/si1133.c
new file mode 100644
index 0000000..015a21f
--- /dev/null
+++ b/drivers/iio/light/si1133.c
@@ -0,0 +1,1071 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * si1133.c - Support for Silabs SI1133 combined ambient
+ * light and UV index sensors
+ *
+ * Copyright 2018 Maxime Roussin-Belanger <maxime.roussinbelanger@gmail.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#include <linux/util_macros.h>
+
+#define SI1133_REG_PART_ID		0x00
+#define SI1133_REG_REV_ID		0x01
+#define SI1133_REG_MFR_ID		0x02
+#define SI1133_REG_INFO0		0x03
+#define SI1133_REG_INFO1		0x04
+
+#define SI1133_PART_ID			0x33
+
+#define SI1133_REG_HOSTIN0		0x0A
+#define SI1133_REG_COMMAND		0x0B
+#define SI1133_REG_IRQ_ENABLE		0x0F
+#define SI1133_REG_RESPONSE1		0x10
+#define SI1133_REG_RESPONSE0		0x11
+#define SI1133_REG_IRQ_STATUS		0x12
+#define SI1133_REG_MEAS_RATE		0x1A
+
+#define SI1133_IRQ_CHANNEL_ENABLE	0xF
+
+#define SI1133_CMD_RESET_CTR		0x00
+#define SI1133_CMD_RESET_SW		0x01
+#define SI1133_CMD_FORCE		0x11
+#define SI1133_CMD_START_AUTONOMOUS	0x13
+#define SI1133_CMD_PARAM_SET		0x80
+#define SI1133_CMD_PARAM_QUERY		0x40
+#define SI1133_CMD_PARAM_MASK		0x3F
+
+#define SI1133_CMD_ERR_MASK		BIT(4)
+#define SI1133_CMD_SEQ_MASK		0xF
+#define SI1133_MAX_CMD_CTR		0xF
+
+#define SI1133_PARAM_REG_CHAN_LIST	0x01
+#define SI1133_PARAM_REG_ADCCONFIG(x)	((x) * 4) + 2
+#define SI1133_PARAM_REG_ADCSENS(x)	((x) * 4) + 3
+#define SI1133_PARAM_REG_ADCPOST(x)	((x) * 4) + 4
+
+#define SI1133_ADCMUX_MASK 0x1F
+
+#define SI1133_ADCCONFIG_DECIM_RATE(x)	(x) << 5
+
+#define SI1133_ADCSENS_SCALE_MASK 0x70
+#define SI1133_ADCSENS_SCALE_SHIFT 4
+#define SI1133_ADCSENS_HSIG_MASK BIT(7)
+#define SI1133_ADCSENS_HSIG_SHIFT 7
+#define SI1133_ADCSENS_HW_GAIN_MASK 0xF
+#define SI1133_ADCSENS_NB_MEAS(x)	fls(x) << SI1133_ADCSENS_SCALE_SHIFT
+
+#define SI1133_ADCPOST_24BIT_EN BIT(6)
+#define SI1133_ADCPOST_POSTSHIFT_BITQTY(x) (x & GENMASK(2, 0)) << 3
+
+#define SI1133_PARAM_ADCMUX_SMALL_IR	0x0
+#define SI1133_PARAM_ADCMUX_MED_IR	0x1
+#define SI1133_PARAM_ADCMUX_LARGE_IR	0x2
+#define SI1133_PARAM_ADCMUX_WHITE	0xB
+#define SI1133_PARAM_ADCMUX_LARGE_WHITE	0xD
+#define SI1133_PARAM_ADCMUX_UV		0x18
+#define SI1133_PARAM_ADCMUX_UV_DEEP	0x19
+
+#define SI1133_ERR_INVALID_CMD		0x0
+#define SI1133_ERR_INVALID_LOCATION_CMD 0x1
+#define SI1133_ERR_SATURATION_ADC_OR_OVERFLOW_ACCUMULATION 0x2
+#define SI1133_ERR_OUTPUT_BUFFER_OVERFLOW 0x3
+
+#define SI1133_COMPLETION_TIMEOUT_MS	500
+
+#define SI1133_CMD_MINSLEEP_US_LOW	5000
+#define SI1133_CMD_MINSLEEP_US_HIGH	7500
+#define SI1133_CMD_TIMEOUT_MS		25
+#define SI1133_CMD_LUX_TIMEOUT_MS	5000
+#define SI1133_CMD_TIMEOUT_US		SI1133_CMD_TIMEOUT_MS * 1000
+
+#define SI1133_REG_HOSTOUT(x)		(x) + 0x13
+
+#define SI1133_MEASUREMENT_FREQUENCY 1250
+
+#define SI1133_X_ORDER_MASK            0x0070
+#define SI1133_Y_ORDER_MASK            0x0007
+#define si1133_get_x_order(m)          ((m) & SI1133_X_ORDER_MASK) >> 4
+#define si1133_get_y_order(m)          ((m) & SI1133_Y_ORDER_MASK)
+
+#define SI1133_LUX_ADC_MASK		0xE
+#define SI1133_ADC_THRESHOLD		16000
+#define SI1133_INPUT_FRACTION_HIGH	7
+#define SI1133_INPUT_FRACTION_LOW	15
+#define SI1133_LUX_OUTPUT_FRACTION	12
+#define SI1133_LUX_BUFFER_SIZE		9
+
+static const int si1133_scale_available[] = {
+	1, 2, 4, 8, 16, 32, 64, 128};
+
+static IIO_CONST_ATTR(scale_available, "1 2 4 8 16 32 64 128");
+
+static IIO_CONST_ATTR_INT_TIME_AVAIL("0.0244 0.0488 0.0975 0.195 0.390 0.780 "
+				     "1.560 3.120 6.24 12.48 25.0 50.0");
+
+/* A.K.A. HW_GAIN in datasheet */
+enum si1133_int_time {
+	    _24_4_us = 0,
+	    _48_8_us = 1,
+	    _97_5_us = 2,
+	   _195_0_us = 3,
+	   _390_0_us = 4,
+	   _780_0_us = 5,
+	 _1_560_0_us = 6,
+	 _3_120_0_us = 7,
+	 _6_240_0_us = 8,
+	_12_480_0_us = 9,
+	_25_ms = 10,
+	_50_ms = 11,
+};
+
+/* Integration time in milliseconds, nanoseconds */
+static const int si1133_int_time_table[][2] = {
+	[_24_4_us] = {0, 24400},
+	[_48_8_us] = {0, 48800},
+	[_97_5_us] = {0, 97500},
+	[_195_0_us] = {0, 195000},
+	[_390_0_us] = {0, 390000},
+	[_780_0_us] = {0, 780000},
+	[_1_560_0_us] = {1, 560000},
+	[_3_120_0_us] = {3, 120000},
+	[_6_240_0_us] = {6, 240000},
+	[_12_480_0_us] = {12, 480000},
+	[_25_ms] = {25, 000000},
+	[_50_ms] = {50, 000000},
+};
+
+static const struct regmap_range si1133_reg_ranges[] = {
+	regmap_reg_range(0x00, 0x02),
+	regmap_reg_range(0x0A, 0x0B),
+	regmap_reg_range(0x0F, 0x0F),
+	regmap_reg_range(0x10, 0x12),
+	regmap_reg_range(0x13, 0x2C),
+};
+
+static const struct regmap_range si1133_reg_ro_ranges[] = {
+	regmap_reg_range(0x00, 0x02),
+	regmap_reg_range(0x10, 0x2C),
+};
+
+static const struct regmap_range si1133_precious_ranges[] = {
+	regmap_reg_range(0x12, 0x12),
+};
+
+static const struct regmap_access_table si1133_write_ranges_table = {
+	.yes_ranges	= si1133_reg_ranges,
+	.n_yes_ranges	= ARRAY_SIZE(si1133_reg_ranges),
+	.no_ranges	= si1133_reg_ro_ranges,
+	.n_no_ranges	= ARRAY_SIZE(si1133_reg_ro_ranges),
+};
+
+static const struct regmap_access_table si1133_read_ranges_table = {
+	.yes_ranges	= si1133_reg_ranges,
+	.n_yes_ranges	= ARRAY_SIZE(si1133_reg_ranges),
+};
+
+static const struct regmap_access_table si1133_precious_table = {
+	.yes_ranges	= si1133_precious_ranges,
+	.n_yes_ranges	= ARRAY_SIZE(si1133_precious_ranges),
+};
+
+static const struct regmap_config si1133_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+
+	.max_register = 0x2C,
+
+	.wr_table = &si1133_write_ranges_table,
+	.rd_table = &si1133_read_ranges_table,
+
+	.precious_table = &si1133_precious_table,
+};
+
+struct si1133_data {
+	struct regmap *regmap;
+	struct i2c_client *client;
+
+	/* Lock protecting one command at a time can be processed */
+	struct mutex mutex;
+
+	int rsp_seq;
+	u8 scan_mask;
+	u8 adc_sens[6];
+	u8 adc_config[6];
+
+	struct completion completion;
+};
+
+struct si1133_coeff {
+	s16 info;
+	u16 mag;
+};
+
+struct si1133_lux_coeff {
+	struct si1133_coeff coeff_high[4];
+	struct si1133_coeff coeff_low[9];
+};
+
+static const struct si1133_lux_coeff lux_coeff = {
+	{
+		{  0,   209},
+		{ 1665,  93},
+		{ 2064,  65},
+		{-2671, 234}
+	},
+	{
+		{    0,     0},
+		{ 1921, 29053},
+		{-1022, 36363},
+		{ 2320, 20789},
+		{ -367, 57909},
+		{-1774, 38240},
+		{ -608, 46775},
+		{-1503, 51831},
+		{-1886, 58928}
+	}
+};
+
+static int si1133_calculate_polynomial_inner(u32 input, u8 fraction, u16 mag,
+					     s8 shift)
+{
+	return ((input << fraction) / mag) << shift;
+}
+
+static int si1133_calculate_output(u32 x, u32 y, u8 x_order, u8 y_order,
+				   u8 input_fraction, s8 sign,
+				   const struct si1133_coeff *coeffs)
+{
+	s8 shift;
+	int x1 = 1;
+	int x2 = 1;
+	int y1 = 1;
+	int y2 = 1;
+
+	shift = ((u16)coeffs->info & 0xFF00) >> 8;
+	shift ^= 0xFF;
+	shift += 1;
+	shift = -shift;
+
+	if (x_order > 0) {
+		x1 = si1133_calculate_polynomial_inner(x, input_fraction,
+						       coeffs->mag, shift);
+		if (x_order > 1)
+			x2 = x1;
+	}
+
+	if (y_order > 0) {
+		y1 = si1133_calculate_polynomial_inner(y, input_fraction,
+						       coeffs->mag, shift);
+		if (y_order > 1)
+			y2 = y1;
+	}
+
+	return sign * x1 * x2 * y1 * y2;
+}
+
+/*
+ * The algorithm is from:
+ * https://siliconlabs.github.io/Gecko_SDK_Doc/efm32zg/html/si1133_8c_source.html#l00716
+ */
+static int si1133_calc_polynomial(u32 x, u32 y, u8 input_fraction, u8 num_coeff,
+				  const struct si1133_coeff *coeffs)
+{
+	u8 x_order, y_order;
+	u8 counter;
+	s8 sign;
+	int output = 0;
+
+	for (counter = 0; counter < num_coeff; counter++) {
+		if (coeffs->info < 0)
+			sign = -1;
+		else
+			sign = 1;
+
+		x_order = si1133_get_x_order(coeffs->info);
+		y_order = si1133_get_y_order(coeffs->info);
+
+		if ((x_order == 0) && (y_order == 0))
+			output +=
+			       sign * coeffs->mag << SI1133_LUX_OUTPUT_FRACTION;
+		else
+			output += si1133_calculate_output(x, y, x_order,
+							  y_order,
+							  input_fraction, sign,
+							  coeffs);
+		coeffs++;
+	}
+
+	return abs(output);
+}
+
+static int si1133_cmd_reset_sw(struct si1133_data *data)
+{
+	struct device *dev = &data->client->dev;
+	unsigned int resp;
+	unsigned long timeout;
+	int err;
+
+	err = regmap_write(data->regmap, SI1133_REG_COMMAND,
+			   SI1133_CMD_RESET_SW);
+	if (err)
+		return err;
+
+	timeout = jiffies + msecs_to_jiffies(SI1133_CMD_TIMEOUT_MS);
+	while (true) {
+		err = regmap_read(data->regmap, SI1133_REG_RESPONSE0, &resp);
+		if (err == -ENXIO) {
+			usleep_range(SI1133_CMD_MINSLEEP_US_LOW,
+				     SI1133_CMD_MINSLEEP_US_HIGH);
+			continue;
+		}
+
+		if ((resp & SI1133_MAX_CMD_CTR) == SI1133_MAX_CMD_CTR)
+			break;
+
+		if (time_after(jiffies, timeout)) {
+			dev_warn(dev, "Timeout on reset ctr resp: %d\n", resp);
+			return -ETIMEDOUT;
+		}
+	}
+
+	if (!err)
+		data->rsp_seq = SI1133_MAX_CMD_CTR;
+
+	return err;
+}
+
+static int si1133_parse_response_err(struct device *dev, u32 resp, u8 cmd)
+{
+	resp &= 0xF;
+
+	switch (resp) {
+	case SI1133_ERR_OUTPUT_BUFFER_OVERFLOW:
+		dev_warn(dev, "Output buffer overflow: %#02hhx\n", cmd);
+		return -EOVERFLOW;
+	case SI1133_ERR_SATURATION_ADC_OR_OVERFLOW_ACCUMULATION:
+		dev_warn(dev, "Saturation of the ADC or overflow of accumulation: %#02hhx\n",
+			 cmd);
+		return -EOVERFLOW;
+	case SI1133_ERR_INVALID_LOCATION_CMD:
+		dev_warn(dev,
+			 "Parameter access to an invalid location: %#02hhx\n",
+			 cmd);
+		return -EINVAL;
+	case SI1133_ERR_INVALID_CMD:
+		dev_warn(dev, "Invalid command %#02hhx\n", cmd);
+		return -EINVAL;
+	default:
+		dev_warn(dev, "Unknown error %#02hhx\n", cmd);
+		return -EINVAL;
+	}
+}
+
+static int si1133_cmd_reset_counter(struct si1133_data *data)
+{
+	int err = regmap_write(data->regmap, SI1133_REG_COMMAND,
+			       SI1133_CMD_RESET_CTR);
+	if (err)
+		return err;
+
+	data->rsp_seq = 0;
+
+	return 0;
+}
+
+static int si1133_command(struct si1133_data *data, u8 cmd)
+{
+	struct device *dev = &data->client->dev;
+	u32 resp;
+	int err;
+	int expected_seq;
+
+	mutex_lock(&data->mutex);
+
+	expected_seq = (data->rsp_seq + 1) & SI1133_MAX_CMD_CTR;
+
+	if (cmd == SI1133_CMD_FORCE)
+		reinit_completion(&data->completion);
+
+	err = regmap_write(data->regmap, SI1133_REG_COMMAND, cmd);
+	if (err) {
+		dev_warn(dev, "Failed to write command %#02hhx, ret=%d\n", cmd,
+			 err);
+		goto out;
+	}
+
+	if (cmd == SI1133_CMD_FORCE) {
+		/* wait for irq */
+		if (!wait_for_completion_timeout(&data->completion,
+			msecs_to_jiffies(SI1133_COMPLETION_TIMEOUT_MS))) {
+			err = -ETIMEDOUT;
+			goto out;
+		}
+		err = regmap_read(data->regmap, SI1133_REG_RESPONSE0, &resp);
+		if (err)
+			goto out;
+	} else {
+		err = regmap_read_poll_timeout(data->regmap,
+					       SI1133_REG_RESPONSE0, resp,
+					       (resp & SI1133_CMD_SEQ_MASK) ==
+					       expected_seq ||
+					       (resp & SI1133_CMD_ERR_MASK),
+					       SI1133_CMD_MINSLEEP_US_LOW,
+					       SI1133_CMD_TIMEOUT_MS * 1000);
+		if (err) {
+			dev_warn(dev,
+				 "Failed to read command %#02hhx, ret=%d\n",
+				 cmd, err);
+			goto out;
+		}
+	}
+
+	if (resp & SI1133_CMD_ERR_MASK) {
+		err = si1133_parse_response_err(dev, resp, cmd);
+		si1133_cmd_reset_counter(data);
+	} else {
+		data->rsp_seq = expected_seq;
+	}
+
+out:
+	mutex_unlock(&data->mutex);
+
+	return err;
+}
+
+static int si1133_param_set(struct si1133_data *data, u8 param, u32 value)
+{
+	int err = regmap_write(data->regmap, SI1133_REG_HOSTIN0, value);
+
+	if (err)
+		return err;
+
+	return si1133_command(data, SI1133_CMD_PARAM_SET |
+			      (param & SI1133_CMD_PARAM_MASK));
+}
+
+static int si1133_param_query(struct si1133_data *data, u8 param, u32 *result)
+{
+	int err = si1133_command(data, SI1133_CMD_PARAM_QUERY |
+				 (param & SI1133_CMD_PARAM_MASK));
+	if (err)
+		return err;
+
+	return regmap_read(data->regmap, SI1133_REG_RESPONSE1, result);
+}
+
+#define SI1133_CHANNEL(_ch, _type) \
+	.type = _type, \
+	.channel = _ch, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | \
+		BIT(IIO_CHAN_INFO_SCALE) | \
+		BIT(IIO_CHAN_INFO_HARDWAREGAIN), \
+
+static const struct iio_chan_spec si1133_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+		.channel = 0,
+	},
+	{
+		SI1133_CHANNEL(SI1133_PARAM_ADCMUX_WHITE, IIO_INTENSITY)
+		.channel2 = IIO_MOD_LIGHT_BOTH,
+	},
+	{
+		SI1133_CHANNEL(SI1133_PARAM_ADCMUX_LARGE_WHITE, IIO_INTENSITY)
+		.channel2 = IIO_MOD_LIGHT_BOTH,
+		.extend_name = "large",
+	},
+	{
+		SI1133_CHANNEL(SI1133_PARAM_ADCMUX_SMALL_IR, IIO_INTENSITY)
+		.extend_name = "small",
+		.modified = 1,
+		.channel2 = IIO_MOD_LIGHT_IR,
+	},
+	{
+		SI1133_CHANNEL(SI1133_PARAM_ADCMUX_MED_IR, IIO_INTENSITY)
+		.modified = 1,
+		.channel2 = IIO_MOD_LIGHT_IR,
+	},
+	{
+		SI1133_CHANNEL(SI1133_PARAM_ADCMUX_LARGE_IR, IIO_INTENSITY)
+		.extend_name = "large",
+		.modified = 1,
+		.channel2 = IIO_MOD_LIGHT_IR,
+	},
+	{
+		SI1133_CHANNEL(SI1133_PARAM_ADCMUX_UV, IIO_UVINDEX)
+	},
+	{
+		SI1133_CHANNEL(SI1133_PARAM_ADCMUX_UV_DEEP, IIO_UVINDEX)
+		.modified = 1,
+		.channel2 = IIO_MOD_LIGHT_DUV,
+	}
+};
+
+static int si1133_get_int_time_index(int milliseconds, int nanoseconds)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(si1133_int_time_table); i++) {
+		if (milliseconds == si1133_int_time_table[i][0] &&
+		    nanoseconds == si1133_int_time_table[i][1])
+			return i;
+	}
+	return -EINVAL;
+}
+
+static int si1133_set_integration_time(struct si1133_data *data, u8 adc,
+				       int milliseconds, int nanoseconds)
+{
+	int index;
+
+	index = si1133_get_int_time_index(milliseconds, nanoseconds);
+	if (index < 0)
+		return index;
+
+	data->adc_sens[adc] &= 0xF0;
+	data->adc_sens[adc] |= index;
+
+	return si1133_param_set(data, SI1133_PARAM_REG_ADCSENS(0),
+				data->adc_sens[adc]);
+}
+
+static int si1133_set_chlist(struct si1133_data *data, u8 scan_mask)
+{
+	/* channel list already set, no need to reprogram */
+	if (data->scan_mask == scan_mask)
+		return 0;
+
+	data->scan_mask = scan_mask;
+
+	return si1133_param_set(data, SI1133_PARAM_REG_CHAN_LIST, scan_mask);
+}
+
+static int si1133_chan_set_adcconfig(struct si1133_data *data, u8 adc,
+				     u8 adc_config)
+{
+	int err;
+
+	err = si1133_param_set(data, SI1133_PARAM_REG_ADCCONFIG(adc),
+			       adc_config);
+	if (err)
+		return err;
+
+	data->adc_config[adc] = adc_config;
+
+	return 0;
+}
+
+static int si1133_update_adcconfig(struct si1133_data *data, uint8_t adc,
+				   u8 mask, u8 shift, u8 value)
+{
+	u32 adc_config;
+	int err;
+
+	err = si1133_param_query(data, SI1133_PARAM_REG_ADCCONFIG(adc),
+				 &adc_config);
+	if (err)
+		return err;
+
+	adc_config &= ~mask;
+	adc_config |= (value << shift);
+
+	return si1133_chan_set_adcconfig(data, adc, adc_config);
+}
+
+static int si1133_set_adcmux(struct si1133_data *data, u8 adc, u8 mux)
+{
+	if ((mux & data->adc_config[adc]) == mux)
+		return 0; /* mux already set to correct value */
+
+	return si1133_update_adcconfig(data, adc, SI1133_ADCMUX_MASK, 0, mux);
+}
+
+static int si1133_force_measurement(struct si1133_data *data)
+{
+	return si1133_command(data, SI1133_CMD_FORCE);
+}
+
+static int si1133_bulk_read(struct si1133_data *data, u8 start_reg, u8 length,
+			    u8 *buffer)
+{
+	int err;
+
+	err = si1133_force_measurement(data);
+	if (err)
+		return err;
+
+	return regmap_bulk_read(data->regmap, start_reg, buffer, length);
+}
+
+static int si1133_measure(struct si1133_data *data,
+			  struct iio_chan_spec const *chan,
+			  int *val)
+{
+	int err;
+
+	__be16 resp;
+
+	err = si1133_set_adcmux(data, 0, chan->channel);
+	if (err)
+		return err;
+
+	/* Deactivate lux measurements if they were active */
+	err = si1133_set_chlist(data, BIT(0));
+	if (err)
+		return err;
+
+	err = si1133_bulk_read(data, SI1133_REG_HOSTOUT(0), sizeof(resp),
+			       (u8 *)&resp);
+	if (err)
+		return err;
+
+	*val = be16_to_cpu(resp);
+
+	return err;
+}
+
+static irqreturn_t si1133_threaded_irq_handler(int irq, void *private)
+{
+	struct iio_dev *iio_dev = private;
+	struct si1133_data *data = iio_priv(iio_dev);
+	u32 irq_status;
+	int err;
+
+	err = regmap_read(data->regmap, SI1133_REG_IRQ_STATUS, &irq_status);
+	if (err) {
+		dev_err_ratelimited(&iio_dev->dev, "Error reading IRQ\n");
+		goto out;
+	}
+
+	if (irq_status != data->scan_mask)
+		return IRQ_NONE;
+
+out:
+	complete(&data->completion);
+
+	return IRQ_HANDLED;
+}
+
+static int si1133_scale_to_swgain(int scale_integer, int scale_fractional)
+{
+	scale_integer = find_closest(scale_integer, si1133_scale_available,
+				     ARRAY_SIZE(si1133_scale_available));
+	if (scale_integer < 0 ||
+	    scale_integer > ARRAY_SIZE(si1133_scale_available) ||
+	    scale_fractional != 0)
+		return -EINVAL;
+
+	return scale_integer;
+}
+
+static int si1133_chan_set_adcsens(struct si1133_data *data, u8 adc,
+				   u8 adc_sens)
+{
+	int err;
+
+	err = si1133_param_set(data, SI1133_PARAM_REG_ADCSENS(adc), adc_sens);
+	if (err)
+		return err;
+
+	data->adc_sens[adc] = adc_sens;
+
+	return 0;
+}
+
+static int si1133_update_adcsens(struct si1133_data *data, u8 mask,
+				 u8 shift, u8 value)
+{
+	int err;
+	u32 adc_sens;
+
+	err = si1133_param_query(data, SI1133_PARAM_REG_ADCSENS(0),
+				 &adc_sens);
+	if (err)
+		return err;
+
+	adc_sens &= ~mask;
+	adc_sens |= (value << shift);
+
+	return si1133_chan_set_adcsens(data, 0, adc_sens);
+}
+
+static int si1133_get_lux(struct si1133_data *data, int *val)
+{
+	int err;
+	int lux;
+	u32 high_vis;
+	u32 low_vis;
+	u32 ir;
+	u8 buffer[SI1133_LUX_BUFFER_SIZE];
+
+	/* Activate lux channels */
+	err = si1133_set_chlist(data, SI1133_LUX_ADC_MASK);
+	if (err)
+		return err;
+
+	err = si1133_bulk_read(data, SI1133_REG_HOSTOUT(0),
+			       SI1133_LUX_BUFFER_SIZE, buffer);
+	if (err)
+		return err;
+
+	high_vis = (buffer[0] << 16) | (buffer[1] << 8) | buffer[2];
+	low_vis = (buffer[3] << 16) | (buffer[4] << 8) | buffer[5];
+	ir = (buffer[6] << 16) | (buffer[7] << 8) | buffer[8];
+
+	if (high_vis > SI1133_ADC_THRESHOLD || ir > SI1133_ADC_THRESHOLD)
+		lux = si1133_calc_polynomial(high_vis, ir,
+					     SI1133_INPUT_FRACTION_HIGH,
+					     ARRAY_SIZE(lux_coeff.coeff_high),
+					     &lux_coeff.coeff_high[0]);
+	else
+		lux = si1133_calc_polynomial(low_vis, ir,
+					     SI1133_INPUT_FRACTION_LOW,
+					     ARRAY_SIZE(lux_coeff.coeff_low),
+					     &lux_coeff.coeff_low[0]);
+
+	*val = lux >> SI1133_LUX_OUTPUT_FRACTION;
+
+	return err;
+}
+
+static int si1133_read_raw(struct iio_dev *iio_dev,
+			   struct iio_chan_spec const *chan,
+			   int *val, int *val2, long mask)
+{
+	struct si1133_data *data = iio_priv(iio_dev);
+	u8 adc_sens = data->adc_sens[0];
+	int err;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_PROCESSED:
+		switch (chan->type) {
+		case IIO_LIGHT:
+			err = si1133_get_lux(data, val);
+			if (err)
+				return err;
+
+			return IIO_VAL_INT;
+		default:
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_RAW:
+		switch (chan->type) {
+		case IIO_INTENSITY:
+		case IIO_UVINDEX:
+			err = si1133_measure(data, chan, val);
+			if (err)
+				return err;
+
+			return IIO_VAL_INT;
+		default:
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_INT_TIME:
+		switch (chan->type) {
+		case IIO_INTENSITY:
+		case IIO_UVINDEX:
+			adc_sens &= SI1133_ADCSENS_HW_GAIN_MASK;
+
+			*val = si1133_int_time_table[adc_sens][0];
+			*val2 = si1133_int_time_table[adc_sens][1];
+			return IIO_VAL_INT_PLUS_MICRO;
+		default:
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_SCALE:
+		switch (chan->type) {
+		case IIO_INTENSITY:
+		case IIO_UVINDEX:
+			adc_sens &= SI1133_ADCSENS_SCALE_MASK;
+			adc_sens >>= SI1133_ADCSENS_SCALE_SHIFT;
+
+			*val = BIT(adc_sens);
+
+			return IIO_VAL_INT;
+		default:
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		switch (chan->type) {
+		case IIO_INTENSITY:
+		case IIO_UVINDEX:
+			adc_sens >>= SI1133_ADCSENS_HSIG_SHIFT;
+
+			*val = adc_sens;
+
+			return IIO_VAL_INT;
+		default:
+			return -EINVAL;
+		}
+	default:
+		return -EINVAL;
+	}
+}
+
+static int si1133_write_raw(struct iio_dev *iio_dev,
+			    struct iio_chan_spec const *chan,
+			    int val, int val2, long mask)
+{
+	struct si1133_data *data = iio_priv(iio_dev);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		switch (chan->type) {
+		case IIO_INTENSITY:
+		case IIO_UVINDEX:
+			val = si1133_scale_to_swgain(val, val2);
+			if (val < 0)
+				return val;
+
+			return si1133_update_adcsens(data,
+						     SI1133_ADCSENS_SCALE_MASK,
+						     SI1133_ADCSENS_SCALE_SHIFT,
+						     val);
+		default:
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_INT_TIME:
+		return si1133_set_integration_time(data, 0, val, val2);
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		switch (chan->type) {
+		case IIO_INTENSITY:
+		case IIO_UVINDEX:
+			if (val != 0 && val != 1)
+				return -EINVAL;
+
+			return si1133_update_adcsens(data,
+						     SI1133_ADCSENS_HSIG_MASK,
+						     SI1133_ADCSENS_HSIG_SHIFT,
+						     val);
+		default:
+			return -EINVAL;
+		}
+	default:
+		return -EINVAL;
+	}
+}
+
+static struct attribute *si1133_attributes[] = {
+	&iio_const_attr_integration_time_available.dev_attr.attr,
+	&iio_const_attr_scale_available.dev_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group si1133_attribute_group = {
+	.attrs = si1133_attributes,
+};
+
+static const struct iio_info si1133_info = {
+	.read_raw = si1133_read_raw,
+	.write_raw = si1133_write_raw,
+	.attrs = &si1133_attribute_group,
+};
+
+/*
+ * si1133_init_lux_channels - Configure 3 different channels(adc) (1,2 and 3)
+ * The channel configuration for the lux measurement was taken from :
+ * https://siliconlabs.github.io/Gecko_SDK_Doc/efm32zg/html/si1133_8c_source.html#l00578
+ *
+ * Reserved the channel 0 for the other raw measurements
+ */
+static int si1133_init_lux_channels(struct si1133_data *data)
+{
+	int err;
+
+	err = si1133_chan_set_adcconfig(data, 1,
+					SI1133_ADCCONFIG_DECIM_RATE(1) |
+					SI1133_PARAM_ADCMUX_LARGE_WHITE);
+	if (err)
+		return err;
+
+	err = si1133_param_set(data, SI1133_PARAM_REG_ADCPOST(1),
+			       SI1133_ADCPOST_24BIT_EN |
+			       SI1133_ADCPOST_POSTSHIFT_BITQTY(0));
+	if (err)
+		return err;
+	err = si1133_chan_set_adcsens(data, 1, SI1133_ADCSENS_HSIG_MASK |
+				      SI1133_ADCSENS_NB_MEAS(64) | _48_8_us);
+	if (err)
+		return err;
+
+	err = si1133_chan_set_adcconfig(data, 2,
+					SI1133_ADCCONFIG_DECIM_RATE(1) |
+					SI1133_PARAM_ADCMUX_LARGE_WHITE);
+	if (err)
+		return err;
+
+	err = si1133_param_set(data, SI1133_PARAM_REG_ADCPOST(2),
+			       SI1133_ADCPOST_24BIT_EN |
+			       SI1133_ADCPOST_POSTSHIFT_BITQTY(2));
+	if (err)
+		return err;
+
+	err = si1133_chan_set_adcsens(data, 2, SI1133_ADCSENS_HSIG_MASK |
+				      SI1133_ADCSENS_NB_MEAS(1) | _3_120_0_us);
+	if (err)
+		return err;
+
+	err = si1133_chan_set_adcconfig(data, 3,
+					SI1133_ADCCONFIG_DECIM_RATE(1) |
+					SI1133_PARAM_ADCMUX_MED_IR);
+	if (err)
+		return err;
+
+	err = si1133_param_set(data, SI1133_PARAM_REG_ADCPOST(3),
+			       SI1133_ADCPOST_24BIT_EN |
+			       SI1133_ADCPOST_POSTSHIFT_BITQTY(2));
+	if (err)
+		return err;
+
+	return  si1133_chan_set_adcsens(data, 3, SI1133_ADCSENS_HSIG_MASK |
+					SI1133_ADCSENS_NB_MEAS(64) | _48_8_us);
+}
+
+static int si1133_initialize(struct si1133_data *data)
+{
+	int err;
+
+	err = si1133_cmd_reset_sw(data);
+	if (err)
+		return err;
+
+	/* Turn off autonomous mode */
+	err = si1133_param_set(data, SI1133_REG_MEAS_RATE, 0);
+	if (err)
+		return err;
+
+	err = si1133_init_lux_channels(data);
+	if (err)
+		return err;
+
+	return regmap_write(data->regmap, SI1133_REG_IRQ_ENABLE,
+			    SI1133_IRQ_CHANNEL_ENABLE);
+}
+
+static int si1133_validate_ids(struct iio_dev *iio_dev)
+{
+	struct si1133_data *data = iio_priv(iio_dev);
+
+	unsigned int part_id, rev_id, mfr_id;
+	int err;
+
+	err = regmap_read(data->regmap, SI1133_REG_PART_ID, &part_id);
+	if (err)
+		return err;
+
+	err = regmap_read(data->regmap, SI1133_REG_REV_ID, &rev_id);
+	if (err)
+		return err;
+
+	err = regmap_read(data->regmap, SI1133_REG_MFR_ID, &mfr_id);
+	if (err)
+		return err;
+
+	dev_info(&iio_dev->dev,
+		 "Device ID part %#02hhx rev %#02hhx mfr %#02hhx\n",
+		 part_id, rev_id, mfr_id);
+	if (part_id != SI1133_PART_ID) {
+		dev_err(&iio_dev->dev,
+			"Part ID mismatch got %#02hhx, expected %#02x\n",
+			part_id, SI1133_PART_ID);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int si1133_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct si1133_data *data;
+	struct iio_dev *iio_dev;
+	int err;
+
+	iio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!iio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(iio_dev);
+
+	init_completion(&data->completion);
+
+	data->regmap = devm_regmap_init_i2c(client, &si1133_regmap_config);
+	if (IS_ERR(data->regmap)) {
+		err = PTR_ERR(data->regmap);
+		dev_err(&client->dev, "Failed to initialise regmap: %d\n", err);
+		return err;
+	}
+
+	i2c_set_clientdata(client, iio_dev);
+	data->client = client;
+
+	iio_dev->dev.parent = &client->dev;
+	iio_dev->name = id->name;
+	iio_dev->channels = si1133_channels;
+	iio_dev->num_channels = ARRAY_SIZE(si1133_channels);
+	iio_dev->info = &si1133_info;
+	iio_dev->modes = INDIO_DIRECT_MODE;
+
+	mutex_init(&data->mutex);
+
+	err = si1133_validate_ids(iio_dev);
+	if (err)
+		return err;
+
+	err = si1133_initialize(data);
+	if (err) {
+		dev_err(&client->dev,
+			"Error when initializing chip: %d\n", err);
+		return err;
+	}
+
+	if (!client->irq) {
+		dev_err(&client->dev,
+			"Required interrupt not provided, cannot proceed\n");
+		return -EINVAL;
+	}
+
+	err = devm_request_threaded_irq(&client->dev, client->irq,
+					NULL,
+					si1133_threaded_irq_handler,
+					IRQF_ONESHOT | IRQF_SHARED,
+					client->name, iio_dev);
+	if (err) {
+		dev_warn(&client->dev, "Request irq %d failed: %i\n",
+			 client->irq, err);
+		return err;
+	}
+
+	return devm_iio_device_register(&client->dev, iio_dev);
+}
+
+static const struct i2c_device_id si1133_ids[] = {
+	{ "si1133", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si1133_ids);
+
+static struct i2c_driver si1133_driver = {
+	.driver = {
+	    .name   = "si1133",
+	},
+	.probe  = si1133_probe,
+	.id_table = si1133_ids,
+};
+
+module_i2c_driver(si1133_driver);
+
+MODULE_AUTHOR("Maxime Roussin-Belanger <maxime.roussinbelanger@gmail.com>");
+MODULE_DESCRIPTION("Silabs SI1133, UV index sensor and ambient light sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/si1145.c b/drivers/iio/light/si1145.c
new file mode 100644
index 0000000..76f16f9
--- /dev/null
+++ b/drivers/iio/light/si1145.c
@@ -0,0 +1,1401 @@
+/*
+ * si1145.c - Support for Silabs SI1132 and SI1141/2/3/5/6/7 combined ambient
+ * light, UV index and proximity sensors
+ *
+ * Copyright 2014-16 Peter Meerwald-Stadler <pmeerw@pmeerw.net>
+ * Copyright 2016 Crestez Dan Leonard <leonard.crestez@intel.com>
+ *
+ * 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.
+ *
+ * SI1132 (7-bit I2C slave address 0x60)
+ * SI1141/2/3 (7-bit I2C slave address 0x5a)
+ * SI1145/6/6 (7-bit I2C slave address 0x60)
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/buffer.h>
+#include <linux/util_macros.h>
+
+#define SI1145_REG_PART_ID		0x00
+#define SI1145_REG_REV_ID		0x01
+#define SI1145_REG_SEQ_ID		0x02
+#define SI1145_REG_INT_CFG		0x03
+#define SI1145_REG_IRQ_ENABLE		0x04
+#define SI1145_REG_IRQ_MODE		0x05
+#define SI1145_REG_HW_KEY		0x07
+#define SI1145_REG_MEAS_RATE		0x08
+#define SI1145_REG_PS_LED21		0x0f
+#define SI1145_REG_PS_LED3		0x10
+#define SI1145_REG_UCOEF1		0x13
+#define SI1145_REG_UCOEF2		0x14
+#define SI1145_REG_UCOEF3		0x15
+#define SI1145_REG_UCOEF4		0x16
+#define SI1145_REG_PARAM_WR		0x17
+#define SI1145_REG_COMMAND		0x18
+#define SI1145_REG_RESPONSE		0x20
+#define SI1145_REG_IRQ_STATUS		0x21
+#define SI1145_REG_ALSVIS_DATA		0x22
+#define SI1145_REG_ALSIR_DATA		0x24
+#define SI1145_REG_PS1_DATA		0x26
+#define SI1145_REG_PS2_DATA		0x28
+#define SI1145_REG_PS3_DATA		0x2a
+#define SI1145_REG_AUX_DATA		0x2c
+#define SI1145_REG_PARAM_RD		0x2e
+#define SI1145_REG_CHIP_STAT		0x30
+
+#define SI1145_UCOEF1_DEFAULT		0x7b
+#define SI1145_UCOEF2_DEFAULT		0x6b
+#define SI1145_UCOEF3_DEFAULT		0x01
+#define SI1145_UCOEF4_DEFAULT		0x00
+
+/* Helper to figure out PS_LED register / shift per channel */
+#define SI1145_PS_LED_REG(ch) \
+	(((ch) == 2) ? SI1145_REG_PS_LED3 : SI1145_REG_PS_LED21)
+#define SI1145_PS_LED_SHIFT(ch) \
+	(((ch) == 1) ? 4 : 0)
+
+/* Parameter offsets */
+#define SI1145_PARAM_CHLIST		0x01
+#define SI1145_PARAM_PSLED12_SELECT	0x02
+#define SI1145_PARAM_PSLED3_SELECT	0x03
+#define SI1145_PARAM_PS_ENCODING	0x05
+#define SI1145_PARAM_ALS_ENCODING	0x06
+#define SI1145_PARAM_PS1_ADC_MUX	0x07
+#define SI1145_PARAM_PS2_ADC_MUX	0x08
+#define SI1145_PARAM_PS3_ADC_MUX	0x09
+#define SI1145_PARAM_PS_ADC_COUNTER	0x0a
+#define SI1145_PARAM_PS_ADC_GAIN	0x0b
+#define SI1145_PARAM_PS_ADC_MISC	0x0c
+#define SI1145_PARAM_ALS_ADC_MUX	0x0d
+#define SI1145_PARAM_ALSIR_ADC_MUX	0x0e
+#define SI1145_PARAM_AUX_ADC_MUX	0x0f
+#define SI1145_PARAM_ALSVIS_ADC_COUNTER	0x10
+#define SI1145_PARAM_ALSVIS_ADC_GAIN	0x11
+#define SI1145_PARAM_ALSVIS_ADC_MISC	0x12
+#define SI1145_PARAM_LED_RECOVERY	0x1c
+#define SI1145_PARAM_ALSIR_ADC_COUNTER	0x1d
+#define SI1145_PARAM_ALSIR_ADC_GAIN	0x1e
+#define SI1145_PARAM_ALSIR_ADC_MISC	0x1f
+#define SI1145_PARAM_ADC_OFFSET		0x1a
+
+/* Channel enable masks for CHLIST parameter */
+#define SI1145_CHLIST_EN_PS1		BIT(0)
+#define SI1145_CHLIST_EN_PS2		BIT(1)
+#define SI1145_CHLIST_EN_PS3		BIT(2)
+#define SI1145_CHLIST_EN_ALSVIS		BIT(4)
+#define SI1145_CHLIST_EN_ALSIR		BIT(5)
+#define SI1145_CHLIST_EN_AUX		BIT(6)
+#define SI1145_CHLIST_EN_UV		BIT(7)
+
+/* Proximity measurement mode for ADC_MISC parameter */
+#define SI1145_PS_ADC_MODE_NORMAL	BIT(2)
+/* Signal range mask for ADC_MISC parameter */
+#define SI1145_ADC_MISC_RANGE		BIT(5)
+
+/* Commands for REG_COMMAND */
+#define SI1145_CMD_NOP			0x00
+#define SI1145_CMD_RESET		0x01
+#define SI1145_CMD_PS_FORCE		0x05
+#define SI1145_CMD_ALS_FORCE		0x06
+#define SI1145_CMD_PSALS_FORCE		0x07
+#define SI1145_CMD_PS_PAUSE		0x09
+#define SI1145_CMD_ALS_PAUSE		0x0a
+#define SI1145_CMD_PSALS_PAUSE		0x0b
+#define SI1145_CMD_PS_AUTO		0x0d
+#define SI1145_CMD_ALS_AUTO		0x0e
+#define SI1145_CMD_PSALS_AUTO		0x0f
+#define SI1145_CMD_PARAM_QUERY		0x80
+#define SI1145_CMD_PARAM_SET		0xa0
+
+#define SI1145_RSP_INVALID_SETTING	0x80
+#define SI1145_RSP_COUNTER_MASK		0x0F
+
+/* Minimum sleep after each command to ensure it's received */
+#define SI1145_COMMAND_MINSLEEP_MS	5
+/* Return -ETIMEDOUT after this long */
+#define SI1145_COMMAND_TIMEOUT_MS	25
+
+/* Interrupt configuration masks for INT_CFG register */
+#define SI1145_INT_CFG_OE		BIT(0) /* enable interrupt */
+#define SI1145_INT_CFG_MODE		BIT(1) /* auto reset interrupt pin */
+
+/* Interrupt enable masks for IRQ_ENABLE register */
+#define SI1145_MASK_ALL_IE		(BIT(4) | BIT(3) | BIT(2) | BIT(0))
+
+#define SI1145_MUX_TEMP			0x65
+#define SI1145_MUX_VDD			0x75
+
+/* Proximity LED current; see Table 2 in datasheet */
+#define SI1145_LED_CURRENT_45mA		0x04
+
+enum {
+	SI1132,
+	SI1141,
+	SI1142,
+	SI1143,
+	SI1145,
+	SI1146,
+	SI1147,
+};
+
+struct si1145_part_info {
+	u8 part;
+	const struct iio_info *iio_info;
+	const struct iio_chan_spec *channels;
+	unsigned int num_channels;
+	unsigned int num_leds;
+	bool uncompressed_meas_rate;
+};
+
+/**
+ * struct si1145_data - si1145 chip state data
+ * @client:	I2C client
+ * @lock:	mutex to protect shared state.
+ * @cmdlock:	Low-level mutex to protect command execution only
+ * @rsp_seq:	Next expected response number or -1 if counter reset required
+ * @scan_mask:	Saved scan mask to avoid duplicate set_chlist
+ * @autonomous: If automatic measurements are active (for buffer support)
+ * @part_info:	Part information
+ * @trig:	Pointer to iio trigger
+ * @meas_rate:	Value of MEAS_RATE register. Only set in HW in auto mode
+ */
+struct si1145_data {
+	struct i2c_client *client;
+	struct mutex lock;
+	struct mutex cmdlock;
+	int rsp_seq;
+	const struct si1145_part_info *part_info;
+	unsigned long scan_mask;
+	bool autonomous;
+	struct iio_trigger *trig;
+	int meas_rate;
+};
+
+/**
+ * __si1145_command_reset() - Send CMD_NOP and wait for response 0
+ *
+ * Does not modify data->rsp_seq
+ *
+ * Return: 0 on success and -errno on error.
+ */
+static int __si1145_command_reset(struct si1145_data *data)
+{
+	struct device *dev = &data->client->dev;
+	unsigned long stop_jiffies;
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(data->client, SI1145_REG_COMMAND,
+						      SI1145_CMD_NOP);
+	if (ret < 0)
+		return ret;
+	msleep(SI1145_COMMAND_MINSLEEP_MS);
+
+	stop_jiffies = jiffies + SI1145_COMMAND_TIMEOUT_MS * HZ / 1000;
+	while (true) {
+		ret = i2c_smbus_read_byte_data(data->client,
+					       SI1145_REG_RESPONSE);
+		if (ret <= 0)
+			return ret;
+		if (time_after(jiffies, stop_jiffies)) {
+			dev_warn(dev, "timeout on reset\n");
+			return -ETIMEDOUT;
+		}
+		msleep(SI1145_COMMAND_MINSLEEP_MS);
+		continue;
+	}
+}
+
+/**
+ * si1145_command() - Execute a command and poll the response register
+ *
+ * All conversion overflows are reported as -EOVERFLOW
+ * INVALID_SETTING is reported as -EINVAL
+ * Timeouts are reported as -ETIMEDOUT
+ *
+ * Return: 0 on success or -errno on failure
+ */
+static int si1145_command(struct si1145_data *data, u8 cmd)
+{
+	struct device *dev = &data->client->dev;
+	unsigned long stop_jiffies;
+	int ret;
+
+	mutex_lock(&data->cmdlock);
+
+	if (data->rsp_seq < 0) {
+		ret = __si1145_command_reset(data);
+		if (ret < 0) {
+			dev_err(dev, "failed to reset command counter, ret=%d\n",
+				ret);
+			goto out;
+		}
+		data->rsp_seq = 0;
+	}
+
+	ret = i2c_smbus_write_byte_data(data->client, SI1145_REG_COMMAND, cmd);
+	if (ret) {
+		dev_warn(dev, "failed to write command, ret=%d\n", ret);
+		goto out;
+	}
+	/* Sleep a little to ensure the command is received */
+	msleep(SI1145_COMMAND_MINSLEEP_MS);
+
+	stop_jiffies = jiffies + SI1145_COMMAND_TIMEOUT_MS * HZ / 1000;
+	while (true) {
+		ret = i2c_smbus_read_byte_data(data->client,
+					       SI1145_REG_RESPONSE);
+		if (ret < 0) {
+			dev_warn(dev, "failed to read response, ret=%d\n", ret);
+			break;
+		}
+
+		if ((ret & ~SI1145_RSP_COUNTER_MASK) == 0) {
+			if (ret == data->rsp_seq) {
+				if (time_after(jiffies, stop_jiffies)) {
+					dev_warn(dev, "timeout on command %#02hhx\n",
+						 cmd);
+					ret = -ETIMEDOUT;
+					break;
+				}
+				msleep(SI1145_COMMAND_MINSLEEP_MS);
+				continue;
+			}
+			if (ret == ((data->rsp_seq + 1) &
+				SI1145_RSP_COUNTER_MASK)) {
+				data->rsp_seq = ret;
+				ret = 0;
+				break;
+			}
+			dev_warn(dev, "unexpected response counter %d instead of %d\n",
+				 ret, (data->rsp_seq + 1) &
+					SI1145_RSP_COUNTER_MASK);
+			ret = -EIO;
+		} else {
+			if (ret == SI1145_RSP_INVALID_SETTING) {
+				dev_warn(dev, "INVALID_SETTING error on command %#02hhx\n",
+					 cmd);
+				ret = -EINVAL;
+			} else {
+				/* All overflows are treated identically */
+				dev_dbg(dev, "overflow, ret=%d, cmd=%#02hhx\n",
+					ret, cmd);
+				ret = -EOVERFLOW;
+			}
+		}
+
+		/* Force a counter reset next time */
+		data->rsp_seq = -1;
+		break;
+	}
+
+out:
+	mutex_unlock(&data->cmdlock);
+
+	return ret;
+}
+
+static int si1145_param_update(struct si1145_data *data, u8 op, u8 param,
+			       u8 value)
+{
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(data->client,
+		SI1145_REG_PARAM_WR, value);
+	if (ret < 0)
+		return ret;
+
+	return si1145_command(data, op | (param & 0x1F));
+}
+
+static int si1145_param_set(struct si1145_data *data, u8 param, u8 value)
+{
+	return si1145_param_update(data, SI1145_CMD_PARAM_SET, param, value);
+}
+
+/* Set param. Returns negative errno or current value */
+static int si1145_param_query(struct si1145_data *data, u8 param)
+{
+	int ret;
+
+	ret = si1145_command(data, SI1145_CMD_PARAM_QUERY | (param & 0x1F));
+	if (ret < 0)
+		return ret;
+
+	return i2c_smbus_read_byte_data(data->client, SI1145_REG_PARAM_RD);
+}
+
+/* Expand 8 bit compressed value to 16 bit, see Silabs AN498 */
+static u16 si1145_uncompress(u8 x)
+{
+	u16 result = 0;
+	u8 exponent = 0;
+
+	if (x < 8)
+		return 0;
+
+	exponent = (x & 0xf0) >> 4;
+	result = 0x10 | (x & 0x0f);
+
+	if (exponent >= 4)
+		return result << (exponent - 4);
+	return result >> (4 - exponent);
+}
+
+/* Compress 16 bit value to 8 bit, see Silabs AN498 */
+static u8 si1145_compress(u16 x)
+{
+	u32 exponent = 0;
+	u32 significand = 0;
+	u32 tmp = x;
+
+	if (x == 0x0000)
+		return 0x00;
+	if (x == 0x0001)
+		return 0x08;
+
+	while (1) {
+		tmp >>= 1;
+		exponent += 1;
+		if (tmp == 1)
+			break;
+	}
+
+	if (exponent < 5) {
+		significand = x << (4 - exponent);
+		return (exponent << 4) | (significand & 0xF);
+	}
+
+	significand = x >> (exponent - 5);
+	if (significand & 1) {
+		significand += 2;
+		if (significand & 0x0040) {
+			exponent += 1;
+			significand >>= 1;
+		}
+	}
+
+	return (exponent << 4) | ((significand >> 1) & 0xF);
+}
+
+/* Write meas_rate in hardware */
+static int si1145_set_meas_rate(struct si1145_data *data, int interval)
+{
+	if (data->part_info->uncompressed_meas_rate)
+		return i2c_smbus_write_word_data(data->client,
+			SI1145_REG_MEAS_RATE, interval);
+	else
+		return i2c_smbus_write_byte_data(data->client,
+			SI1145_REG_MEAS_RATE, interval);
+}
+
+static int si1145_read_samp_freq(struct si1145_data *data, int *val, int *val2)
+{
+	*val = 32000;
+	if (data->part_info->uncompressed_meas_rate)
+		*val2 = data->meas_rate;
+	else
+		*val2 = si1145_uncompress(data->meas_rate);
+	return IIO_VAL_FRACTIONAL;
+}
+
+/* Set the samp freq in driver private data */
+static int si1145_store_samp_freq(struct si1145_data *data, int val)
+{
+	int ret = 0;
+	int meas_rate;
+
+	if (val <= 0 || val > 32000)
+		return -ERANGE;
+	meas_rate = 32000 / val;
+
+	mutex_lock(&data->lock);
+	if (data->autonomous) {
+		ret = si1145_set_meas_rate(data, meas_rate);
+		if (ret)
+			goto out;
+	}
+	if (data->part_info->uncompressed_meas_rate)
+		data->meas_rate = meas_rate;
+	else
+		data->meas_rate = si1145_compress(meas_rate);
+
+out:
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+
+static irqreturn_t si1145_trigger_handler(int irq, void *private)
+{
+	struct iio_poll_func *pf = private;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct si1145_data *data = iio_priv(indio_dev);
+	/*
+	 * Maximum buffer size:
+	 *   6*2 bytes channels data + 4 bytes alignment +
+	 *   8 bytes timestamp
+	 */
+	u8 buffer[24];
+	int i, j = 0;
+	int ret;
+	u8 irq_status = 0;
+
+	if (!data->autonomous) {
+		ret = si1145_command(data, SI1145_CMD_PSALS_FORCE);
+		if (ret < 0 && ret != -EOVERFLOW)
+			goto done;
+	} else {
+		irq_status = ret = i2c_smbus_read_byte_data(data->client,
+				SI1145_REG_IRQ_STATUS);
+		if (ret < 0)
+			goto done;
+		if (!(irq_status & SI1145_MASK_ALL_IE))
+			goto done;
+	}
+
+	for_each_set_bit(i, indio_dev->active_scan_mask,
+		indio_dev->masklength) {
+		int run = 1;
+
+		while (i + run < indio_dev->masklength) {
+			if (!test_bit(i + run, indio_dev->active_scan_mask))
+				break;
+			if (indio_dev->channels[i + run].address !=
+				indio_dev->channels[i].address + 2 * run)
+				break;
+			run++;
+		}
+
+		ret = i2c_smbus_read_i2c_block_data_or_emulated(
+				data->client, indio_dev->channels[i].address,
+				sizeof(u16) * run, &buffer[j]);
+		if (ret < 0)
+			goto done;
+		j += run * sizeof(u16);
+		i += run - 1;
+	}
+
+	if (data->autonomous) {
+		ret = i2c_smbus_write_byte_data(data->client,
+				SI1145_REG_IRQ_STATUS,
+				irq_status & SI1145_MASK_ALL_IE);
+		if (ret < 0)
+			goto done;
+	}
+
+	iio_push_to_buffers_with_timestamp(indio_dev, buffer,
+		iio_get_time_ns(indio_dev));
+
+done:
+	iio_trigger_notify_done(indio_dev->trig);
+	return IRQ_HANDLED;
+}
+
+static int si1145_set_chlist(struct iio_dev *indio_dev, unsigned long scan_mask)
+{
+	struct si1145_data *data = iio_priv(indio_dev);
+	u8 reg = 0, mux;
+	int ret;
+	int i;
+
+	/* channel list already set, no need to reprogram */
+	if (data->scan_mask == scan_mask)
+		return 0;
+
+	for_each_set_bit(i, &scan_mask, indio_dev->masklength) {
+		switch (indio_dev->channels[i].address) {
+		case SI1145_REG_ALSVIS_DATA:
+			reg |= SI1145_CHLIST_EN_ALSVIS;
+			break;
+		case SI1145_REG_ALSIR_DATA:
+			reg |= SI1145_CHLIST_EN_ALSIR;
+			break;
+		case SI1145_REG_PS1_DATA:
+			reg |= SI1145_CHLIST_EN_PS1;
+			break;
+		case SI1145_REG_PS2_DATA:
+			reg |= SI1145_CHLIST_EN_PS2;
+			break;
+		case SI1145_REG_PS3_DATA:
+			reg |= SI1145_CHLIST_EN_PS3;
+			break;
+		case SI1145_REG_AUX_DATA:
+			switch (indio_dev->channels[i].type) {
+			case IIO_UVINDEX:
+				reg |= SI1145_CHLIST_EN_UV;
+				break;
+			default:
+				reg |= SI1145_CHLIST_EN_AUX;
+				if (indio_dev->channels[i].type == IIO_TEMP)
+					mux = SI1145_MUX_TEMP;
+				else
+					mux = SI1145_MUX_VDD;
+				ret = si1145_param_set(data,
+					SI1145_PARAM_AUX_ADC_MUX, mux);
+				if (ret < 0)
+					return ret;
+
+				break;
+			}
+		}
+	}
+
+	data->scan_mask = scan_mask;
+	ret = si1145_param_set(data, SI1145_PARAM_CHLIST, reg);
+
+	return ret < 0 ? ret : 0;
+}
+
+static int si1145_measure(struct iio_dev *indio_dev,
+			  struct iio_chan_spec const *chan)
+{
+	struct si1145_data *data = iio_priv(indio_dev);
+	u8 cmd;
+	int ret;
+
+	ret = si1145_set_chlist(indio_dev, BIT(chan->scan_index));
+	if (ret < 0)
+		return ret;
+
+	cmd = (chan->type == IIO_PROXIMITY) ? SI1145_CMD_PS_FORCE :
+		SI1145_CMD_ALS_FORCE;
+	ret = si1145_command(data, cmd);
+	if (ret < 0 && ret != -EOVERFLOW)
+		return ret;
+
+	return i2c_smbus_read_word_data(data->client, chan->address);
+}
+
+/*
+ * Conversion between iio scale and ADC_GAIN values
+ * These could be further adjusted but proximity/intensity are dimensionless
+ */
+static const int si1145_proximity_scale_available[] = {
+	128, 64, 32, 16, 8, 4};
+static const int si1145_intensity_scale_available[] = {
+	128, 64, 32, 16, 8, 4, 2, 1};
+static IIO_CONST_ATTR(in_proximity_scale_available,
+	"128 64 32 16 8 4");
+static IIO_CONST_ATTR(in_intensity_scale_available,
+	"128 64 32 16 8 4 2 1");
+static IIO_CONST_ATTR(in_intensity_ir_scale_available,
+	"128 64 32 16 8 4 2 1");
+
+static int si1145_scale_from_adcgain(int regval)
+{
+	return 128 >> regval;
+}
+
+static int si1145_proximity_adcgain_from_scale(int val, int val2)
+{
+	val = find_closest_descending(val, si1145_proximity_scale_available,
+				ARRAY_SIZE(si1145_proximity_scale_available));
+	if (val < 0 || val > 5 || val2 != 0)
+		return -EINVAL;
+
+	return val;
+}
+
+static int si1145_intensity_adcgain_from_scale(int val, int val2)
+{
+	val = find_closest_descending(val, si1145_intensity_scale_available,
+				ARRAY_SIZE(si1145_intensity_scale_available));
+	if (val < 0 || val > 7 || val2 != 0)
+		return -EINVAL;
+
+	return val;
+}
+
+static int si1145_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val, int *val2, long mask)
+{
+	struct si1145_data *data = iio_priv(indio_dev);
+	int ret;
+	u8 reg;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		switch (chan->type) {
+		case IIO_INTENSITY:
+		case IIO_PROXIMITY:
+		case IIO_VOLTAGE:
+		case IIO_TEMP:
+		case IIO_UVINDEX:
+			ret = iio_device_claim_direct_mode(indio_dev);
+			if (ret)
+				return ret;
+			ret = si1145_measure(indio_dev, chan);
+			iio_device_release_direct_mode(indio_dev);
+
+			if (ret < 0)
+				return ret;
+
+			*val = ret;
+
+			return IIO_VAL_INT;
+		case IIO_CURRENT:
+			ret = i2c_smbus_read_byte_data(data->client,
+				SI1145_PS_LED_REG(chan->channel));
+			if (ret < 0)
+				return ret;
+
+			*val = (ret >> SI1145_PS_LED_SHIFT(chan->channel))
+				& 0x0f;
+
+			return IIO_VAL_INT;
+		default:
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_SCALE:
+		switch (chan->type) {
+		case IIO_PROXIMITY:
+			reg = SI1145_PARAM_PS_ADC_GAIN;
+			break;
+		case IIO_INTENSITY:
+			if (chan->channel2 == IIO_MOD_LIGHT_IR)
+				reg = SI1145_PARAM_ALSIR_ADC_GAIN;
+			else
+				reg = SI1145_PARAM_ALSVIS_ADC_GAIN;
+			break;
+		case IIO_TEMP:
+			*val = 28;
+			*val2 = 571429;
+			return IIO_VAL_INT_PLUS_MICRO;
+		case IIO_UVINDEX:
+			*val = 0;
+			*val2 = 10000;
+			return IIO_VAL_INT_PLUS_MICRO;
+		default:
+			return -EINVAL;
+		}
+
+		ret = si1145_param_query(data, reg);
+		if (ret < 0)
+			return ret;
+
+		*val = si1145_scale_from_adcgain(ret & 0x07);
+
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_OFFSET:
+		switch (chan->type) {
+		case IIO_TEMP:
+			/*
+			 * -ADC offset - ADC counts @ 25°C -
+			 *   35 * ADC counts / °C
+			 */
+			*val = -256 - 11136 + 25 * 35;
+			return IIO_VAL_INT;
+		default:
+			/*
+			 * All ADC measurements have are by default offset
+			 * by -256
+			 * See AN498 5.6.3
+			 */
+			ret = si1145_param_query(data, SI1145_PARAM_ADC_OFFSET);
+			if (ret < 0)
+				return ret;
+			*val = -si1145_uncompress(ret);
+			return IIO_VAL_INT;
+		}
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		return si1145_read_samp_freq(data, val, val2);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int si1145_write_raw(struct iio_dev *indio_dev,
+			       struct iio_chan_spec const *chan,
+			       int val, int val2, long mask)
+{
+	struct si1145_data *data = iio_priv(indio_dev);
+	u8 reg1, reg2, shift;
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		switch (chan->type) {
+		case IIO_PROXIMITY:
+			val = si1145_proximity_adcgain_from_scale(val, val2);
+			if (val < 0)
+				return val;
+			reg1 = SI1145_PARAM_PS_ADC_GAIN;
+			reg2 = SI1145_PARAM_PS_ADC_COUNTER;
+			break;
+		case IIO_INTENSITY:
+			val = si1145_intensity_adcgain_from_scale(val, val2);
+			if (val < 0)
+				return val;
+			if (chan->channel2 == IIO_MOD_LIGHT_IR) {
+				reg1 = SI1145_PARAM_ALSIR_ADC_GAIN;
+				reg2 = SI1145_PARAM_ALSIR_ADC_COUNTER;
+			} else {
+				reg1 = SI1145_PARAM_ALSVIS_ADC_GAIN;
+				reg2 = SI1145_PARAM_ALSVIS_ADC_COUNTER;
+			}
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		ret = iio_device_claim_direct_mode(indio_dev);
+		if (ret)
+			return ret;
+
+		ret = si1145_param_set(data, reg1, val);
+		if (ret < 0) {
+			iio_device_release_direct_mode(indio_dev);
+			return ret;
+		}
+		/* Set recovery period to one's complement of gain */
+		ret = si1145_param_set(data, reg2, (~val & 0x07) << 4);
+		iio_device_release_direct_mode(indio_dev);
+		return ret;
+	case IIO_CHAN_INFO_RAW:
+		if (chan->type != IIO_CURRENT)
+			return -EINVAL;
+
+		if (val < 0 || val > 15 || val2 != 0)
+			return -EINVAL;
+
+		reg1 = SI1145_PS_LED_REG(chan->channel);
+		shift = SI1145_PS_LED_SHIFT(chan->channel);
+
+		ret = iio_device_claim_direct_mode(indio_dev);
+		if (ret)
+			return ret;
+
+		ret = i2c_smbus_read_byte_data(data->client, reg1);
+		if (ret < 0) {
+			iio_device_release_direct_mode(indio_dev);
+			return ret;
+		}
+		ret = i2c_smbus_write_byte_data(data->client, reg1,
+			(ret & ~(0x0f << shift)) |
+			((val & 0x0f) << shift));
+		iio_device_release_direct_mode(indio_dev);
+		return ret;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		return si1145_store_samp_freq(data, val);
+	default:
+		return -EINVAL;
+	}
+}
+
+#define SI1145_ST { \
+	.sign = 'u', \
+	.realbits = 16, \
+	.storagebits = 16, \
+	.endianness = IIO_LE, \
+}
+
+#define SI1145_INTENSITY_CHANNEL(_si) { \
+	.type = IIO_INTENSITY, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+			      BIT(IIO_CHAN_INFO_OFFSET) | \
+			      BIT(IIO_CHAN_INFO_SCALE), \
+	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+	.scan_type = SI1145_ST, \
+	.scan_index = _si, \
+	.address = SI1145_REG_ALSVIS_DATA, \
+}
+
+#define SI1145_INTENSITY_IR_CHANNEL(_si) { \
+	.type = IIO_INTENSITY, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+			      BIT(IIO_CHAN_INFO_OFFSET) | \
+			      BIT(IIO_CHAN_INFO_SCALE), \
+	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+	.modified = 1, \
+	.channel2 = IIO_MOD_LIGHT_IR, \
+	.scan_type = SI1145_ST, \
+	.scan_index = _si, \
+	.address = SI1145_REG_ALSIR_DATA, \
+}
+
+#define SI1145_TEMP_CHANNEL(_si) { \
+	.type = IIO_TEMP, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+			      BIT(IIO_CHAN_INFO_OFFSET) | \
+			      BIT(IIO_CHAN_INFO_SCALE), \
+	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+	.scan_type = SI1145_ST, \
+	.scan_index = _si, \
+	.address = SI1145_REG_AUX_DATA, \
+}
+
+#define SI1145_UV_CHANNEL(_si) { \
+	.type = IIO_UVINDEX, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+			      BIT(IIO_CHAN_INFO_SCALE), \
+	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+	.scan_type = SI1145_ST, \
+	.scan_index = _si, \
+	.address = SI1145_REG_AUX_DATA, \
+}
+
+#define SI1145_PROXIMITY_CHANNEL(_si, _ch) { \
+	.type = IIO_PROXIMITY, \
+	.indexed = 1, \
+	.channel = _ch, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+				    BIT(IIO_CHAN_INFO_OFFSET), \
+	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+	.scan_type = SI1145_ST, \
+	.scan_index = _si, \
+	.address = SI1145_REG_PS1_DATA + _ch * 2, \
+}
+
+#define SI1145_VOLTAGE_CHANNEL(_si) { \
+	.type = IIO_VOLTAGE, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+	.scan_type = SI1145_ST, \
+	.scan_index = _si, \
+	.address = SI1145_REG_AUX_DATA, \
+}
+
+#define SI1145_CURRENT_CHANNEL(_ch) { \
+	.type = IIO_CURRENT, \
+	.indexed = 1, \
+	.channel = _ch, \
+	.output = 1, \
+	.scan_index = -1, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+}
+
+static const struct iio_chan_spec si1132_channels[] = {
+	SI1145_INTENSITY_CHANNEL(0),
+	SI1145_INTENSITY_IR_CHANNEL(1),
+	SI1145_TEMP_CHANNEL(2),
+	SI1145_VOLTAGE_CHANNEL(3),
+	SI1145_UV_CHANNEL(4),
+	IIO_CHAN_SOFT_TIMESTAMP(6),
+};
+
+static const struct iio_chan_spec si1141_channels[] = {
+	SI1145_INTENSITY_CHANNEL(0),
+	SI1145_INTENSITY_IR_CHANNEL(1),
+	SI1145_PROXIMITY_CHANNEL(2, 0),
+	SI1145_TEMP_CHANNEL(3),
+	SI1145_VOLTAGE_CHANNEL(4),
+	IIO_CHAN_SOFT_TIMESTAMP(5),
+	SI1145_CURRENT_CHANNEL(0),
+};
+
+static const struct iio_chan_spec si1142_channels[] = {
+	SI1145_INTENSITY_CHANNEL(0),
+	SI1145_INTENSITY_IR_CHANNEL(1),
+	SI1145_PROXIMITY_CHANNEL(2, 0),
+	SI1145_PROXIMITY_CHANNEL(3, 1),
+	SI1145_TEMP_CHANNEL(4),
+	SI1145_VOLTAGE_CHANNEL(5),
+	IIO_CHAN_SOFT_TIMESTAMP(6),
+	SI1145_CURRENT_CHANNEL(0),
+	SI1145_CURRENT_CHANNEL(1),
+};
+
+static const struct iio_chan_spec si1143_channels[] = {
+	SI1145_INTENSITY_CHANNEL(0),
+	SI1145_INTENSITY_IR_CHANNEL(1),
+	SI1145_PROXIMITY_CHANNEL(2, 0),
+	SI1145_PROXIMITY_CHANNEL(3, 1),
+	SI1145_PROXIMITY_CHANNEL(4, 2),
+	SI1145_TEMP_CHANNEL(5),
+	SI1145_VOLTAGE_CHANNEL(6),
+	IIO_CHAN_SOFT_TIMESTAMP(7),
+	SI1145_CURRENT_CHANNEL(0),
+	SI1145_CURRENT_CHANNEL(1),
+	SI1145_CURRENT_CHANNEL(2),
+};
+
+static const struct iio_chan_spec si1145_channels[] = {
+	SI1145_INTENSITY_CHANNEL(0),
+	SI1145_INTENSITY_IR_CHANNEL(1),
+	SI1145_PROXIMITY_CHANNEL(2, 0),
+	SI1145_TEMP_CHANNEL(3),
+	SI1145_VOLTAGE_CHANNEL(4),
+	SI1145_UV_CHANNEL(5),
+	IIO_CHAN_SOFT_TIMESTAMP(6),
+	SI1145_CURRENT_CHANNEL(0),
+};
+
+static const struct iio_chan_spec si1146_channels[] = {
+	SI1145_INTENSITY_CHANNEL(0),
+	SI1145_INTENSITY_IR_CHANNEL(1),
+	SI1145_TEMP_CHANNEL(2),
+	SI1145_VOLTAGE_CHANNEL(3),
+	SI1145_UV_CHANNEL(4),
+	SI1145_PROXIMITY_CHANNEL(5, 0),
+	SI1145_PROXIMITY_CHANNEL(6, 1),
+	IIO_CHAN_SOFT_TIMESTAMP(7),
+	SI1145_CURRENT_CHANNEL(0),
+	SI1145_CURRENT_CHANNEL(1),
+};
+
+static const struct iio_chan_spec si1147_channels[] = {
+	SI1145_INTENSITY_CHANNEL(0),
+	SI1145_INTENSITY_IR_CHANNEL(1),
+	SI1145_PROXIMITY_CHANNEL(2, 0),
+	SI1145_PROXIMITY_CHANNEL(3, 1),
+	SI1145_PROXIMITY_CHANNEL(4, 2),
+	SI1145_TEMP_CHANNEL(5),
+	SI1145_VOLTAGE_CHANNEL(6),
+	SI1145_UV_CHANNEL(7),
+	IIO_CHAN_SOFT_TIMESTAMP(8),
+	SI1145_CURRENT_CHANNEL(0),
+	SI1145_CURRENT_CHANNEL(1),
+	SI1145_CURRENT_CHANNEL(2),
+};
+
+static struct attribute *si1132_attributes[] = {
+	&iio_const_attr_in_intensity_scale_available.dev_attr.attr,
+	&iio_const_attr_in_intensity_ir_scale_available.dev_attr.attr,
+	NULL,
+};
+
+static struct attribute *si114x_attributes[] = {
+	&iio_const_attr_in_intensity_scale_available.dev_attr.attr,
+	&iio_const_attr_in_intensity_ir_scale_available.dev_attr.attr,
+	&iio_const_attr_in_proximity_scale_available.dev_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group si1132_attribute_group = {
+	.attrs = si1132_attributes,
+};
+
+static const struct attribute_group si114x_attribute_group = {
+	.attrs = si114x_attributes,
+};
+
+
+static const struct iio_info si1132_info = {
+	.read_raw = si1145_read_raw,
+	.write_raw = si1145_write_raw,
+	.attrs = &si1132_attribute_group,
+};
+
+static const struct iio_info si114x_info = {
+	.read_raw = si1145_read_raw,
+	.write_raw = si1145_write_raw,
+	.attrs = &si114x_attribute_group,
+};
+
+#define SI1145_PART(id, iio_info, chans, leds, uncompressed_meas_rate) \
+	{id, iio_info, chans, ARRAY_SIZE(chans), leds, uncompressed_meas_rate}
+
+static const struct si1145_part_info si1145_part_info[] = {
+	[SI1132] = SI1145_PART(0x32, &si1132_info, si1132_channels, 0, true),
+	[SI1141] = SI1145_PART(0x41, &si114x_info, si1141_channels, 1, false),
+	[SI1142] = SI1145_PART(0x42, &si114x_info, si1142_channels, 2, false),
+	[SI1143] = SI1145_PART(0x43, &si114x_info, si1143_channels, 3, false),
+	[SI1145] = SI1145_PART(0x45, &si114x_info, si1145_channels, 1, true),
+	[SI1146] = SI1145_PART(0x46, &si114x_info, si1146_channels, 2, true),
+	[SI1147] = SI1145_PART(0x47, &si114x_info, si1147_channels, 3, true),
+};
+
+static int si1145_initialize(struct si1145_data *data)
+{
+	struct i2c_client *client = data->client;
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(client, SI1145_REG_COMMAND,
+					SI1145_CMD_RESET);
+	if (ret < 0)
+		return ret;
+	msleep(SI1145_COMMAND_TIMEOUT_MS);
+
+	/* Hardware key, magic value */
+	ret = i2c_smbus_write_byte_data(client, SI1145_REG_HW_KEY, 0x17);
+	if (ret < 0)
+		return ret;
+	msleep(SI1145_COMMAND_TIMEOUT_MS);
+
+	/* Turn off autonomous mode */
+	ret = si1145_set_meas_rate(data, 0);
+	if (ret < 0)
+		return ret;
+
+	/* Initialize sampling freq to 10 Hz */
+	ret = si1145_store_samp_freq(data, 10);
+	if (ret < 0)
+		return ret;
+
+	/* Set LED currents to 45 mA; have 4 bits, see Table 2 in datasheet */
+	switch (data->part_info->num_leds) {
+	case 3:
+		ret = i2c_smbus_write_byte_data(client,
+						SI1145_REG_PS_LED3,
+						SI1145_LED_CURRENT_45mA);
+		if (ret < 0)
+			return ret;
+		/* fallthrough */
+	case 2:
+		ret = i2c_smbus_write_byte_data(client,
+						SI1145_REG_PS_LED21,
+						(SI1145_LED_CURRENT_45mA << 4) |
+						SI1145_LED_CURRENT_45mA);
+		break;
+	case 1:
+		ret = i2c_smbus_write_byte_data(client,
+						SI1145_REG_PS_LED21,
+						SI1145_LED_CURRENT_45mA);
+		break;
+	default:
+		ret = 0;
+		break;
+	}
+	if (ret < 0)
+		return ret;
+
+	/* Set normal proximity measurement mode */
+	ret = si1145_param_set(data, SI1145_PARAM_PS_ADC_MISC,
+			       SI1145_PS_ADC_MODE_NORMAL);
+	if (ret < 0)
+		return ret;
+
+	ret = si1145_param_set(data, SI1145_PARAM_PS_ADC_GAIN, 0x01);
+	if (ret < 0)
+		return ret;
+
+	/* ADC_COUNTER should be one complement of ADC_GAIN */
+	ret = si1145_param_set(data, SI1145_PARAM_PS_ADC_COUNTER, 0x06 << 4);
+	if (ret < 0)
+		return ret;
+
+	/* Set ALS visible measurement mode */
+	ret = si1145_param_set(data, SI1145_PARAM_ALSVIS_ADC_MISC,
+			       SI1145_ADC_MISC_RANGE);
+	if (ret < 0)
+		return ret;
+
+	ret = si1145_param_set(data, SI1145_PARAM_ALSVIS_ADC_GAIN, 0x03);
+	if (ret < 0)
+		return ret;
+
+	ret = si1145_param_set(data, SI1145_PARAM_ALSVIS_ADC_COUNTER,
+			       0x04 << 4);
+	if (ret < 0)
+		return ret;
+
+	/* Set ALS IR measurement mode */
+	ret = si1145_param_set(data, SI1145_PARAM_ALSIR_ADC_MISC,
+			       SI1145_ADC_MISC_RANGE);
+	if (ret < 0)
+		return ret;
+
+	ret = si1145_param_set(data, SI1145_PARAM_ALSIR_ADC_GAIN, 0x01);
+	if (ret < 0)
+		return ret;
+
+	ret = si1145_param_set(data, SI1145_PARAM_ALSIR_ADC_COUNTER,
+			       0x06 << 4);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Initialize UCOEF to default values in datasheet
+	 * These registers are normally zero on reset
+	 */
+	if (data->part_info == &si1145_part_info[SI1132] ||
+		data->part_info == &si1145_part_info[SI1145] ||
+		data->part_info == &si1145_part_info[SI1146] ||
+		data->part_info == &si1145_part_info[SI1147]) {
+		ret = i2c_smbus_write_byte_data(data->client,
+						SI1145_REG_UCOEF1,
+						SI1145_UCOEF1_DEFAULT);
+		if (ret < 0)
+			return ret;
+		ret = i2c_smbus_write_byte_data(data->client,
+				SI1145_REG_UCOEF2, SI1145_UCOEF2_DEFAULT);
+		if (ret < 0)
+			return ret;
+		ret = i2c_smbus_write_byte_data(data->client,
+				SI1145_REG_UCOEF3, SI1145_UCOEF3_DEFAULT);
+		if (ret < 0)
+			return ret;
+		ret = i2c_smbus_write_byte_data(data->client,
+				SI1145_REG_UCOEF4, SI1145_UCOEF4_DEFAULT);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * Program the channels we want to measure with CMD_PSALS_AUTO. No need for
+ * _postdisable as we stop with CMD_PSALS_PAUSE; single measurement (direct)
+ * mode reprograms the channels list anyway...
+ */
+static int si1145_buffer_preenable(struct iio_dev *indio_dev)
+{
+	struct si1145_data *data = iio_priv(indio_dev);
+	int ret;
+
+	mutex_lock(&data->lock);
+	ret = si1145_set_chlist(indio_dev, *indio_dev->active_scan_mask);
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+
+static bool si1145_validate_scan_mask(struct iio_dev *indio_dev,
+			       const unsigned long *scan_mask)
+{
+	struct si1145_data *data = iio_priv(indio_dev);
+	unsigned int count = 0;
+	int i;
+
+	/* Check that at most one AUX channel is enabled */
+	for_each_set_bit(i, scan_mask, data->part_info->num_channels) {
+		if (indio_dev->channels[i].address == SI1145_REG_AUX_DATA)
+			count++;
+	}
+
+	return count <= 1;
+}
+
+static const struct iio_buffer_setup_ops si1145_buffer_setup_ops = {
+	.preenable = si1145_buffer_preenable,
+	.postenable = iio_triggered_buffer_postenable,
+	.predisable = iio_triggered_buffer_predisable,
+	.validate_scan_mask = si1145_validate_scan_mask,
+};
+
+/**
+ * si1145_trigger_set_state() - Set trigger state
+ *
+ * When not using triggers interrupts are disabled and measurement rate is
+ * set to zero in order to minimize power consumption.
+ */
+static int si1145_trigger_set_state(struct iio_trigger *trig, bool state)
+{
+	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+	struct si1145_data *data = iio_priv(indio_dev);
+	int err = 0, ret;
+
+	mutex_lock(&data->lock);
+
+	if (state) {
+		data->autonomous = true;
+		err = i2c_smbus_write_byte_data(data->client,
+				SI1145_REG_INT_CFG, SI1145_INT_CFG_OE);
+		if (err < 0)
+			goto disable;
+		err = i2c_smbus_write_byte_data(data->client,
+				SI1145_REG_IRQ_ENABLE, SI1145_MASK_ALL_IE);
+		if (err < 0)
+			goto disable;
+		err = si1145_set_meas_rate(data, data->meas_rate);
+		if (err < 0)
+			goto disable;
+		err = si1145_command(data, SI1145_CMD_PSALS_AUTO);
+		if (err < 0)
+			goto disable;
+	} else {
+disable:
+		/* Disable as much as possible skipping errors */
+		ret = si1145_command(data, SI1145_CMD_PSALS_PAUSE);
+		if (ret < 0 && !err)
+			err = ret;
+		ret = si1145_set_meas_rate(data, 0);
+		if (ret < 0 && !err)
+			err = ret;
+		ret = i2c_smbus_write_byte_data(data->client,
+						SI1145_REG_IRQ_ENABLE, 0);
+		if (ret < 0 && !err)
+			err = ret;
+		ret = i2c_smbus_write_byte_data(data->client,
+						SI1145_REG_INT_CFG, 0);
+		if (ret < 0 && !err)
+			err = ret;
+		data->autonomous = false;
+	}
+
+	mutex_unlock(&data->lock);
+	return err;
+}
+
+static const struct iio_trigger_ops si1145_trigger_ops = {
+	.set_trigger_state = si1145_trigger_set_state,
+};
+
+static int si1145_probe_trigger(struct iio_dev *indio_dev)
+{
+	struct si1145_data *data = iio_priv(indio_dev);
+	struct i2c_client *client = data->client;
+	struct iio_trigger *trig;
+	int ret;
+
+	trig = devm_iio_trigger_alloc(&client->dev,
+			"%s-dev%d", indio_dev->name, indio_dev->id);
+	if (!trig)
+		return -ENOMEM;
+
+	trig->dev.parent = &client->dev;
+	trig->ops = &si1145_trigger_ops;
+	iio_trigger_set_drvdata(trig, indio_dev);
+
+	ret = devm_request_irq(&client->dev, client->irq,
+			  iio_trigger_generic_data_rdy_poll,
+			  IRQF_TRIGGER_FALLING,
+			  "si1145_irq",
+			  trig);
+	if (ret < 0) {
+		dev_err(&client->dev, "irq request failed\n");
+		return ret;
+	}
+
+	ret = iio_trigger_register(trig);
+	if (ret)
+		return ret;
+
+	data->trig = trig;
+	indio_dev->trig = iio_trigger_get(data->trig);
+
+	return 0;
+}
+
+static void si1145_remove_trigger(struct iio_dev *indio_dev)
+{
+	struct si1145_data *data = iio_priv(indio_dev);
+
+	if (data->trig) {
+		iio_trigger_unregister(data->trig);
+		data->trig = NULL;
+	}
+}
+
+static int si1145_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct si1145_data *data;
+	struct iio_dev *indio_dev;
+	u8 part_id, rev_id, seq_id;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->client = client;
+	data->part_info = &si1145_part_info[id->driver_data];
+
+	part_id = ret = i2c_smbus_read_byte_data(data->client,
+						 SI1145_REG_PART_ID);
+	if (ret < 0)
+		return ret;
+	rev_id = ret = i2c_smbus_read_byte_data(data->client,
+						SI1145_REG_REV_ID);
+	if (ret < 0)
+		return ret;
+	seq_id = ret = i2c_smbus_read_byte_data(data->client,
+						SI1145_REG_SEQ_ID);
+	if (ret < 0)
+		return ret;
+	dev_info(&client->dev, "device ID part %#02hhx rev %#02hhx seq %#02hhx\n",
+			part_id, rev_id, seq_id);
+	if (part_id != data->part_info->part) {
+		dev_err(&client->dev, "part ID mismatch got %#02hhx, expected %#02x\n",
+				part_id, data->part_info->part);
+		return -ENODEV;
+	}
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->name = id->name;
+	indio_dev->channels = data->part_info->channels;
+	indio_dev->num_channels = data->part_info->num_channels;
+	indio_dev->info = data->part_info->iio_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	mutex_init(&data->lock);
+	mutex_init(&data->cmdlock);
+
+	ret = si1145_initialize(data);
+	if (ret < 0)
+		return ret;
+
+	ret = iio_triggered_buffer_setup(indio_dev, NULL,
+		si1145_trigger_handler, &si1145_buffer_setup_ops);
+	if (ret < 0)
+		return ret;
+
+	if (client->irq) {
+		ret = si1145_probe_trigger(indio_dev);
+		if (ret < 0)
+			goto error_free_buffer;
+	} else {
+		dev_info(&client->dev, "no irq, using polling\n");
+	}
+
+	ret = iio_device_register(indio_dev);
+	if (ret < 0)
+		goto error_free_trigger;
+
+	return 0;
+
+error_free_trigger:
+	si1145_remove_trigger(indio_dev);
+error_free_buffer:
+	iio_triggered_buffer_cleanup(indio_dev);
+
+	return ret;
+}
+
+static const struct i2c_device_id si1145_ids[] = {
+	{ "si1132", SI1132 },
+	{ "si1141", SI1141 },
+	{ "si1142", SI1142 },
+	{ "si1143", SI1143 },
+	{ "si1145", SI1145 },
+	{ "si1146", SI1146 },
+	{ "si1147", SI1147 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, si1145_ids);
+
+static int si1145_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+	iio_device_unregister(indio_dev);
+	si1145_remove_trigger(indio_dev);
+	iio_triggered_buffer_cleanup(indio_dev);
+
+	return 0;
+}
+
+static struct i2c_driver si1145_driver = {
+	.driver = {
+		.name   = "si1145",
+	},
+	.probe  = si1145_probe,
+	.remove = si1145_remove,
+	.id_table = si1145_ids,
+};
+
+module_i2c_driver(si1145_driver);
+
+MODULE_AUTHOR("Peter Meerwald-Stadler <pmeerw@pmeerw.net>");
+MODULE_DESCRIPTION("Silabs SI1132 and SI1141/2/3/5/6/7 proximity, ambient light and UV index sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/st_uvis25.h b/drivers/iio/light/st_uvis25.h
new file mode 100644
index 0000000..5e970ab
--- /dev/null
+++ b/drivers/iio/light/st_uvis25.h
@@ -0,0 +1,37 @@
+/*
+ * STMicroelectronics uvis25 sensor driver
+ *
+ * Copyright 2017 STMicroelectronics Inc.
+ *
+ * Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#ifndef ST_UVIS25_H
+#define ST_UVIS25_H
+
+#define ST_UVIS25_DEV_NAME		"uvis25"
+
+#include <linux/iio/iio.h>
+
+/**
+ * struct st_uvis25_hw - ST UVIS25 sensor instance
+ * @regmap: Register map of the device.
+ * @trig: The trigger in use by the driver.
+ * @enabled: Status of the sensor (false->off, true->on).
+ * @irq: Device interrupt line (I2C or SPI).
+ */
+struct st_uvis25_hw {
+	struct regmap *regmap;
+
+	struct iio_trigger *trig;
+	bool enabled;
+	int irq;
+};
+
+extern const struct dev_pm_ops st_uvis25_pm_ops;
+
+int st_uvis25_probe(struct device *dev, int irq, struct regmap *regmap);
+
+#endif /* ST_UVIS25_H */
diff --git a/drivers/iio/light/st_uvis25_core.c b/drivers/iio/light/st_uvis25_core.c
new file mode 100644
index 0000000..3026358
--- /dev/null
+++ b/drivers/iio/light/st_uvis25_core.c
@@ -0,0 +1,359 @@
+/*
+ * STMicroelectronics uvis25 sensor driver
+ *
+ * Copyright 2017 STMicroelectronics Inc.
+ *
+ * Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/iio/sysfs.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/interrupt.h>
+#include <linux/irqreturn.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/buffer.h>
+#include <linux/regmap.h>
+
+#include "st_uvis25.h"
+
+#define ST_UVIS25_REG_WHOAMI_ADDR	0x0f
+#define ST_UVIS25_REG_WHOAMI_VAL	0xca
+#define ST_UVIS25_REG_CTRL1_ADDR	0x20
+#define ST_UVIS25_REG_ODR_MASK		BIT(0)
+#define ST_UVIS25_REG_BDU_MASK		BIT(1)
+#define ST_UVIS25_REG_CTRL2_ADDR	0x21
+#define ST_UVIS25_REG_BOOT_MASK		BIT(7)
+#define ST_UVIS25_REG_CTRL3_ADDR	0x22
+#define ST_UVIS25_REG_HL_MASK		BIT(7)
+#define ST_UVIS25_REG_STATUS_ADDR	0x27
+#define ST_UVIS25_REG_UV_DA_MASK	BIT(0)
+#define ST_UVIS25_REG_OUT_ADDR		0x28
+
+static const struct iio_chan_spec st_uvis25_channels[] = {
+	{
+		.type = IIO_UVINDEX,
+		.address = ST_UVIS25_REG_OUT_ADDR,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+		.scan_index = 0,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 8,
+			.storagebits = 8,
+		},
+	},
+	IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static int st_uvis25_check_whoami(struct st_uvis25_hw *hw)
+{
+	int err, data;
+
+	err = regmap_read(hw->regmap, ST_UVIS25_REG_WHOAMI_ADDR, &data);
+	if (err < 0) {
+		dev_err(regmap_get_device(hw->regmap),
+			"failed to read whoami register\n");
+		return err;
+	}
+
+	if (data != ST_UVIS25_REG_WHOAMI_VAL) {
+		dev_err(regmap_get_device(hw->regmap),
+			"wrong whoami {%02x vs %02x}\n",
+			data, ST_UVIS25_REG_WHOAMI_VAL);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int st_uvis25_set_enable(struct st_uvis25_hw *hw, bool enable)
+{
+	int err;
+
+	err = regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL1_ADDR,
+				 ST_UVIS25_REG_ODR_MASK, enable);
+	if (err < 0)
+		return err;
+
+	hw->enabled = enable;
+
+	return 0;
+}
+
+static int st_uvis25_read_oneshot(struct st_uvis25_hw *hw, u8 addr, int *val)
+{
+	int err;
+
+	err = st_uvis25_set_enable(hw, true);
+	if (err < 0)
+		return err;
+
+	msleep(1500);
+
+	/*
+	 * in order to avoid possible race conditions with interrupt
+	 * generation, disable the sensor first and then poll output
+	 * register. That sequence guarantees the interrupt will be reset
+	 * when irq line is unmasked
+	 */
+	err = st_uvis25_set_enable(hw, false);
+	if (err < 0)
+		return err;
+
+	err = regmap_read(hw->regmap, addr, val);
+
+	return err < 0 ? err : IIO_VAL_INT;
+}
+
+static int st_uvis25_read_raw(struct iio_dev *iio_dev,
+			      struct iio_chan_spec const *ch,
+			      int *val, int *val2, long mask)
+{
+	int ret;
+
+	ret = iio_device_claim_direct_mode(iio_dev);
+	if (ret)
+		return ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_PROCESSED: {
+		struct st_uvis25_hw *hw = iio_priv(iio_dev);
+
+		/*
+		 * mask irq line during oneshot read since the sensor
+		 * does not export the capability to disable data-ready line
+		 * in the register map and it is enabled by default.
+		 * If the line is unmasked during read_raw() it will be set
+		 * active and never reset since the trigger is disabled
+		 */
+		if (hw->irq > 0)
+			disable_irq(hw->irq);
+		ret = st_uvis25_read_oneshot(hw, ch->address, val);
+		if (hw->irq > 0)
+			enable_irq(hw->irq);
+		break;
+	}
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	iio_device_release_direct_mode(iio_dev);
+
+	return ret;
+}
+
+static irqreturn_t st_uvis25_trigger_handler_thread(int irq, void *private)
+{
+	struct st_uvis25_hw *hw = private;
+	int err, status;
+
+	err = regmap_read(hw->regmap, ST_UVIS25_REG_STATUS_ADDR, &status);
+	if (err < 0)
+		return IRQ_HANDLED;
+
+	if (!(status & ST_UVIS25_REG_UV_DA_MASK))
+		return IRQ_NONE;
+
+	iio_trigger_poll_chained(hw->trig);
+
+	return IRQ_HANDLED;
+}
+
+static int st_uvis25_allocate_trigger(struct iio_dev *iio_dev)
+{
+	struct st_uvis25_hw *hw = iio_priv(iio_dev);
+	struct device *dev = regmap_get_device(hw->regmap);
+	bool irq_active_low = false;
+	unsigned long irq_type;
+	int err;
+
+	irq_type = irqd_get_trigger_type(irq_get_irq_data(hw->irq));
+
+	switch (irq_type) {
+	case IRQF_TRIGGER_HIGH:
+	case IRQF_TRIGGER_RISING:
+		break;
+	case IRQF_TRIGGER_LOW:
+	case IRQF_TRIGGER_FALLING:
+		irq_active_low = true;
+		break;
+	default:
+		dev_info(dev, "mode %lx unsupported\n", irq_type);
+		return -EINVAL;
+	}
+
+	err = regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL3_ADDR,
+				 ST_UVIS25_REG_HL_MASK, irq_active_low);
+	if (err < 0)
+		return err;
+
+	err = devm_request_threaded_irq(dev, hw->irq, NULL,
+					st_uvis25_trigger_handler_thread,
+					irq_type | IRQF_ONESHOT,
+					iio_dev->name, hw);
+	if (err) {
+		dev_err(dev, "failed to request trigger irq %d\n",
+			hw->irq);
+		return err;
+	}
+
+	hw->trig = devm_iio_trigger_alloc(dev, "%s-trigger",
+					  iio_dev->name);
+	if (!hw->trig)
+		return -ENOMEM;
+
+	iio_trigger_set_drvdata(hw->trig, iio_dev);
+	hw->trig->dev.parent = dev;
+
+	return devm_iio_trigger_register(dev, hw->trig);
+}
+
+static int st_uvis25_buffer_preenable(struct iio_dev *iio_dev)
+{
+	return st_uvis25_set_enable(iio_priv(iio_dev), true);
+}
+
+static int st_uvis25_buffer_postdisable(struct iio_dev *iio_dev)
+{
+	return st_uvis25_set_enable(iio_priv(iio_dev), false);
+}
+
+static const struct iio_buffer_setup_ops st_uvis25_buffer_ops = {
+	.preenable = st_uvis25_buffer_preenable,
+	.postenable = iio_triggered_buffer_postenable,
+	.predisable = iio_triggered_buffer_predisable,
+	.postdisable = st_uvis25_buffer_postdisable,
+};
+
+static irqreturn_t st_uvis25_buffer_handler_thread(int irq, void *p)
+{
+	u8 buffer[ALIGN(sizeof(u8), sizeof(s64)) + sizeof(s64)];
+	struct iio_poll_func *pf = p;
+	struct iio_dev *iio_dev = pf->indio_dev;
+	struct st_uvis25_hw *hw = iio_priv(iio_dev);
+	int err;
+
+	err = regmap_read(hw->regmap, ST_UVIS25_REG_OUT_ADDR, (int *)buffer);
+	if (err < 0)
+		goto out;
+
+	iio_push_to_buffers_with_timestamp(iio_dev, buffer,
+					   iio_get_time_ns(iio_dev));
+
+out:
+	iio_trigger_notify_done(hw->trig);
+
+	return IRQ_HANDLED;
+}
+
+static int st_uvis25_allocate_buffer(struct iio_dev *iio_dev)
+{
+	struct st_uvis25_hw *hw = iio_priv(iio_dev);
+
+	return devm_iio_triggered_buffer_setup(regmap_get_device(hw->regmap),
+					       iio_dev, NULL,
+					       st_uvis25_buffer_handler_thread,
+					       &st_uvis25_buffer_ops);
+}
+
+static const struct iio_info st_uvis25_info = {
+	.read_raw = st_uvis25_read_raw,
+};
+
+static int st_uvis25_init_sensor(struct st_uvis25_hw *hw)
+{
+	int err;
+
+	err = regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL2_ADDR,
+				 ST_UVIS25_REG_BOOT_MASK, 1);
+	if (err < 0)
+		return err;
+
+	msleep(2000);
+
+	return regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL1_ADDR,
+				  ST_UVIS25_REG_BDU_MASK, 1);
+}
+
+int st_uvis25_probe(struct device *dev, int irq, struct regmap *regmap)
+{
+	struct st_uvis25_hw *hw;
+	struct iio_dev *iio_dev;
+	int err;
+
+	iio_dev = devm_iio_device_alloc(dev, sizeof(*hw));
+	if (!iio_dev)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, (void *)iio_dev);
+
+	hw = iio_priv(iio_dev);
+	hw->irq = irq;
+	hw->regmap = regmap;
+
+	err = st_uvis25_check_whoami(hw);
+	if (err < 0)
+		return err;
+
+	iio_dev->modes = INDIO_DIRECT_MODE;
+	iio_dev->dev.parent = dev;
+	iio_dev->channels = st_uvis25_channels;
+	iio_dev->num_channels = ARRAY_SIZE(st_uvis25_channels);
+	iio_dev->name = ST_UVIS25_DEV_NAME;
+	iio_dev->info = &st_uvis25_info;
+
+	err = st_uvis25_init_sensor(hw);
+	if (err < 0)
+		return err;
+
+	if (hw->irq > 0) {
+		err = st_uvis25_allocate_buffer(iio_dev);
+		if (err < 0)
+			return err;
+
+		err = st_uvis25_allocate_trigger(iio_dev);
+		if (err)
+			return err;
+	}
+
+	return devm_iio_device_register(dev, iio_dev);
+}
+EXPORT_SYMBOL(st_uvis25_probe);
+
+static int __maybe_unused st_uvis25_suspend(struct device *dev)
+{
+	struct iio_dev *iio_dev = dev_get_drvdata(dev);
+	struct st_uvis25_hw *hw = iio_priv(iio_dev);
+
+	return regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL1_ADDR,
+				  ST_UVIS25_REG_ODR_MASK, 0);
+}
+
+static int __maybe_unused st_uvis25_resume(struct device *dev)
+{
+	struct iio_dev *iio_dev = dev_get_drvdata(dev);
+	struct st_uvis25_hw *hw = iio_priv(iio_dev);
+
+	if (hw->enabled)
+		return regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL1_ADDR,
+					  ST_UVIS25_REG_ODR_MASK, 1);
+
+	return 0;
+}
+
+const struct dev_pm_ops st_uvis25_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(st_uvis25_suspend, st_uvis25_resume)
+};
+EXPORT_SYMBOL(st_uvis25_pm_ops);
+
+MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>");
+MODULE_DESCRIPTION("STMicroelectronics uvis25 sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/light/st_uvis25_i2c.c b/drivers/iio/light/st_uvis25_i2c.c
new file mode 100644
index 0000000..afd6eb0
--- /dev/null
+++ b/drivers/iio/light/st_uvis25_i2c.c
@@ -0,0 +1,69 @@
+/*
+ * STMicroelectronics uvis25 i2c driver
+ *
+ * Copyright 2017 STMicroelectronics Inc.
+ *
+ * Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+
+#include "st_uvis25.h"
+
+#define UVIS25_I2C_AUTO_INCREMENT	BIT(7)
+
+static const struct regmap_config st_uvis25_i2c_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.write_flag_mask = UVIS25_I2C_AUTO_INCREMENT,
+	.read_flag_mask = UVIS25_I2C_AUTO_INCREMENT,
+};
+
+static int st_uvis25_i2c_probe(struct i2c_client *client,
+			       const struct i2c_device_id *id)
+{
+	struct regmap *regmap;
+
+	regmap = devm_regmap_init_i2c(client, &st_uvis25_i2c_regmap_config);
+	if (IS_ERR(regmap)) {
+		dev_err(&client->dev, "Failed to register i2c regmap %d\n",
+			(int)PTR_ERR(regmap));
+		return PTR_ERR(regmap);
+	}
+
+	return st_uvis25_probe(&client->dev, client->irq, regmap);
+}
+
+static const struct of_device_id st_uvis25_i2c_of_match[] = {
+	{ .compatible = "st,uvis25", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, st_uvis25_i2c_of_match);
+
+static const struct i2c_device_id st_uvis25_i2c_id_table[] = {
+	{ ST_UVIS25_DEV_NAME },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, st_uvis25_i2c_id_table);
+
+static struct i2c_driver st_uvis25_driver = {
+	.driver = {
+		.name = "st_uvis25_i2c",
+		.pm = &st_uvis25_pm_ops,
+		.of_match_table = of_match_ptr(st_uvis25_i2c_of_match),
+	},
+	.probe = st_uvis25_i2c_probe,
+	.id_table = st_uvis25_i2c_id_table,
+};
+module_i2c_driver(st_uvis25_driver);
+
+MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>");
+MODULE_DESCRIPTION("STMicroelectronics uvis25 i2c driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/light/st_uvis25_spi.c b/drivers/iio/light/st_uvis25_spi.c
new file mode 100644
index 0000000..cdfee5e
--- /dev/null
+++ b/drivers/iio/light/st_uvis25_spi.c
@@ -0,0 +1,68 @@
+/*
+ * STMicroelectronics uvis25 spi driver
+ *
+ * Copyright 2017 STMicroelectronics Inc.
+ *
+ * Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+
+#include "st_uvis25.h"
+
+#define UVIS25_SENSORS_SPI_READ		BIT(7)
+#define UVIS25_SPI_AUTO_INCREMENT	BIT(6)
+
+static const struct regmap_config st_uvis25_spi_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.read_flag_mask = UVIS25_SENSORS_SPI_READ | UVIS25_SPI_AUTO_INCREMENT,
+	.write_flag_mask = UVIS25_SPI_AUTO_INCREMENT,
+};
+
+static int st_uvis25_spi_probe(struct spi_device *spi)
+{
+	struct regmap *regmap;
+
+	regmap = devm_regmap_init_spi(spi, &st_uvis25_spi_regmap_config);
+	if (IS_ERR(regmap)) {
+		dev_err(&spi->dev, "Failed to register spi regmap %d\n",
+			(int)PTR_ERR(regmap));
+		return PTR_ERR(regmap);
+	}
+
+	return st_uvis25_probe(&spi->dev, spi->irq, regmap);
+}
+
+static const struct of_device_id st_uvis25_spi_of_match[] = {
+	{ .compatible = "st,uvis25", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, st_uvis25_spi_of_match);
+
+static const struct spi_device_id st_uvis25_spi_id_table[] = {
+	{ ST_UVIS25_DEV_NAME },
+	{},
+};
+MODULE_DEVICE_TABLE(spi, st_uvis25_spi_id_table);
+
+static struct spi_driver st_uvis25_driver = {
+	.driver = {
+		.name = "st_uvis25_spi",
+		.pm = &st_uvis25_pm_ops,
+		.of_match_table = of_match_ptr(st_uvis25_spi_of_match),
+	},
+	.probe = st_uvis25_spi_probe,
+	.id_table = st_uvis25_spi_id_table,
+};
+module_spi_driver(st_uvis25_driver);
+
+MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>");
+MODULE_DESCRIPTION("STMicroelectronics uvis25 spi driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c
new file mode 100644
index 0000000..6e2a169
--- /dev/null
+++ b/drivers/iio/light/stk3310.c
@@ -0,0 +1,696 @@
+/**
+ * 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/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 = {
+	.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(indio_dev);
+
+	return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t stk3310_irq_event_handler(int irq, void *private)
+{
+	int ret;
+	unsigned int dir;
+	u64 event;
+
+	struct iio_dev *indio_dev = private;
+	struct stk3310_data *data = iio_priv(indio_dev);
+
+	/* Read FLAG_NF to figure out what threshold has been met. */
+	mutex_lock(&data->lock);
+	ret = regmap_field_read(data->reg_flag_nf, &dir);
+	if (ret < 0) {
+		dev_err(&data->client->dev, "register read failed\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/drivers/iio/light/tcs3414.c b/drivers/iio/light/tcs3414.c
new file mode 100644
index 0000000..205e565
--- /dev/null
+++ b/drivers/iio/light/tcs3414.c
@@ -0,0 +1,401 @@
+/*
+ * tcs3414.c - Support for TAOS TCS3414 digital color sensor
+ *
+ * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net>
+ *
+ * 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.
+ *
+ * Digital color sensor with 16-bit channels for red, green, blue, clear);
+ * 7-bit I2C slave address 0x39 (TCS3414) or 0x29, 0x49, 0x59 (TCS3413,
+ * TCS3415, TCS3416, resp.)
+ *
+ * TODO: sync, interrupt support, thresholds, prescaler
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#define TCS3414_DRV_NAME "tcs3414"
+
+#define TCS3414_COMMAND BIT(7)
+#define TCS3414_COMMAND_WORD (TCS3414_COMMAND | BIT(5))
+
+#define TCS3414_CONTROL (TCS3414_COMMAND | 0x00)
+#define TCS3414_TIMING (TCS3414_COMMAND | 0x01)
+#define TCS3414_ID (TCS3414_COMMAND | 0x04)
+#define TCS3414_GAIN (TCS3414_COMMAND | 0x07)
+#define TCS3414_DATA_GREEN (TCS3414_COMMAND_WORD | 0x10)
+#define TCS3414_DATA_RED (TCS3414_COMMAND_WORD | 0x12)
+#define TCS3414_DATA_BLUE (TCS3414_COMMAND_WORD | 0x14)
+#define TCS3414_DATA_CLEAR (TCS3414_COMMAND_WORD | 0x16)
+
+#define TCS3414_CONTROL_ADC_VALID BIT(4)
+#define TCS3414_CONTROL_ADC_EN BIT(1)
+#define TCS3414_CONTROL_POWER BIT(0)
+
+#define TCS3414_INTEG_MASK GENMASK(1, 0)
+#define TCS3414_INTEG_12MS 0x0
+#define TCS3414_INTEG_100MS 0x1
+#define TCS3414_INTEG_400MS 0x2
+
+#define TCS3414_GAIN_MASK GENMASK(5, 4)
+#define TCS3414_GAIN_SHIFT 4
+
+struct tcs3414_data {
+	struct i2c_client *client;
+	u8 control;
+	u8 gain;
+	u8 timing;
+	u16 buffer[8]; /* 4x 16-bit + 8 bytes timestamp */
+};
+
+#define TCS3414_CHANNEL(_color, _si, _addr) { \
+	.type = IIO_INTENSITY, \
+	.modified = 1, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+		BIT(IIO_CHAN_INFO_INT_TIME), \
+	.channel2 = IIO_MOD_LIGHT_##_color, \
+	.address = _addr, \
+	.scan_index = _si, \
+	.scan_type = { \
+		.sign = 'u', \
+		.realbits = 16, \
+		.storagebits = 16, \
+		.endianness = IIO_CPU, \
+	}, \
+}
+
+/* scale factors: 1/gain */
+static const int tcs3414_scales[][2] = {
+	{1, 0}, {0, 250000}, {0, 62500}, {0, 15625}
+};
+
+/* integration time in ms */
+static const int tcs3414_times[] = { 12, 100, 400 };
+
+static const struct iio_chan_spec tcs3414_channels[] = {
+	TCS3414_CHANNEL(GREEN, 0, TCS3414_DATA_GREEN),
+	TCS3414_CHANNEL(RED, 1, TCS3414_DATA_RED),
+	TCS3414_CHANNEL(BLUE, 2, TCS3414_DATA_BLUE),
+	TCS3414_CHANNEL(CLEAR, 3, TCS3414_DATA_CLEAR),
+	IIO_CHAN_SOFT_TIMESTAMP(4),
+};
+
+static int tcs3414_req_data(struct tcs3414_data *data)
+{
+	int tries = 25;
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL,
+		data->control | TCS3414_CONTROL_ADC_EN);
+	if (ret < 0)
+		return ret;
+
+	while (tries--) {
+		ret = i2c_smbus_read_byte_data(data->client, TCS3414_CONTROL);
+		if (ret < 0)
+			return ret;
+		if (ret & TCS3414_CONTROL_ADC_VALID)
+			break;
+		msleep(20);
+	}
+
+	ret = i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL,
+		data->control);
+	if (ret < 0)
+		return ret;
+
+	if (tries < 0) {
+		dev_err(&data->client->dev, "data not ready\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int tcs3414_read_raw(struct iio_dev *indio_dev,
+			   struct iio_chan_spec const *chan,
+			   int *val, int *val2, long mask)
+{
+	struct tcs3414_data *data = iio_priv(indio_dev);
+	int i, ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = iio_device_claim_direct_mode(indio_dev);
+		if (ret)
+			return ret;
+		ret = tcs3414_req_data(data);
+		if (ret < 0) {
+			iio_device_release_direct_mode(indio_dev);
+			return ret;
+		}
+		ret = i2c_smbus_read_word_data(data->client, chan->address);
+		iio_device_release_direct_mode(indio_dev);
+		if (ret < 0)
+			return ret;
+		*val = ret;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		i = (data->gain & TCS3414_GAIN_MASK) >> TCS3414_GAIN_SHIFT;
+		*val = tcs3414_scales[i][0];
+		*val2 = tcs3414_scales[i][1];
+		return IIO_VAL_INT_PLUS_MICRO;
+	case IIO_CHAN_INFO_INT_TIME:
+		*val = 0;
+		*val2 = tcs3414_times[data->timing & TCS3414_INTEG_MASK] * 1000;
+		return IIO_VAL_INT_PLUS_MICRO;
+	}
+	return -EINVAL;
+}
+
+static int tcs3414_write_raw(struct iio_dev *indio_dev,
+			       struct iio_chan_spec const *chan,
+			       int val, int val2, long mask)
+{
+	struct tcs3414_data *data = iio_priv(indio_dev);
+	int i;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		for (i = 0; i < ARRAY_SIZE(tcs3414_scales); i++) {
+			if (val == tcs3414_scales[i][0] &&
+				val2 == tcs3414_scales[i][1]) {
+				data->gain &= ~TCS3414_GAIN_MASK;
+				data->gain |= i << TCS3414_GAIN_SHIFT;
+				return i2c_smbus_write_byte_data(
+					data->client, TCS3414_GAIN,
+					data->gain);
+			}
+		}
+		return -EINVAL;
+	case IIO_CHAN_INFO_INT_TIME:
+		if (val != 0)
+			return -EINVAL;
+		for (i = 0; i < ARRAY_SIZE(tcs3414_times); i++) {
+			if (val2 == tcs3414_times[i] * 1000) {
+				data->timing &= ~TCS3414_INTEG_MASK;
+				data->timing |= i;
+				return i2c_smbus_write_byte_data(
+					data->client, TCS3414_TIMING,
+					data->timing);
+			}
+		}
+		return -EINVAL;
+	default:
+		return -EINVAL;
+	}
+}
+
+static irqreturn_t tcs3414_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct tcs3414_data *data = iio_priv(indio_dev);
+	int i, j = 0;
+
+	for_each_set_bit(i, indio_dev->active_scan_mask,
+		indio_dev->masklength) {
+		int ret = i2c_smbus_read_word_data(data->client,
+			TCS3414_DATA_GREEN + 2*i);
+		if (ret < 0)
+			goto done;
+
+		data->buffer[j++] = ret;
+	}
+
+	iio_push_to_buffers_with_timestamp(indio_dev, data->buffer,
+		iio_get_time_ns(indio_dev));
+
+done:
+	iio_trigger_notify_done(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
+static IIO_CONST_ATTR(scale_available, "1 0.25 0.0625 0.015625");
+static IIO_CONST_ATTR_INT_TIME_AVAIL("0.012 0.1 0.4");
+
+static struct attribute *tcs3414_attributes[] = {
+	&iio_const_attr_scale_available.dev_attr.attr,
+	&iio_const_attr_integration_time_available.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group tcs3414_attribute_group = {
+	.attrs = tcs3414_attributes,
+};
+
+static const struct iio_info tcs3414_info = {
+	.read_raw = tcs3414_read_raw,
+	.write_raw = tcs3414_write_raw,
+	.attrs = &tcs3414_attribute_group,
+};
+
+static int tcs3414_buffer_preenable(struct iio_dev *indio_dev)
+{
+	struct tcs3414_data *data = iio_priv(indio_dev);
+
+	data->control |= TCS3414_CONTROL_ADC_EN;
+	return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL,
+		data->control);
+}
+
+static int tcs3414_buffer_predisable(struct iio_dev *indio_dev)
+{
+	struct tcs3414_data *data = iio_priv(indio_dev);
+	int ret;
+
+	ret = iio_triggered_buffer_predisable(indio_dev);
+	if (ret < 0)
+		return ret;
+
+	data->control &= ~TCS3414_CONTROL_ADC_EN;
+	return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL,
+		data->control);
+}
+
+static const struct iio_buffer_setup_ops tcs3414_buffer_setup_ops = {
+	.preenable = tcs3414_buffer_preenable,
+	.postenable = &iio_triggered_buffer_postenable,
+	.predisable = tcs3414_buffer_predisable,
+};
+
+static int tcs3414_probe(struct i2c_client *client,
+			   const struct i2c_device_id *id)
+{
+	struct tcs3414_data *data;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (indio_dev == NULL)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->client = client;
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->info = &tcs3414_info;
+	indio_dev->name = TCS3414_DRV_NAME;
+	indio_dev->channels = tcs3414_channels;
+	indio_dev->num_channels = ARRAY_SIZE(tcs3414_channels);
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	ret = i2c_smbus_read_byte_data(data->client, TCS3414_ID);
+	if (ret < 0)
+		return ret;
+
+	switch (ret & 0xf0) {
+	case 0x00:
+		dev_info(&client->dev, "TCS3404 found\n");
+		break;
+	case 0x10:
+		dev_info(&client->dev, "TCS3413/14/15/16 found\n");
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	data->control = TCS3414_CONTROL_POWER;
+	ret = i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL,
+		data->control);
+	if (ret < 0)
+		return ret;
+
+	data->timing = TCS3414_INTEG_12MS; /* free running */
+	ret = i2c_smbus_write_byte_data(data->client, TCS3414_TIMING,
+		data->timing);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_read_byte_data(data->client, TCS3414_GAIN);
+	if (ret < 0)
+		return ret;
+	data->gain = ret;
+
+	ret = iio_triggered_buffer_setup(indio_dev, NULL,
+		tcs3414_trigger_handler, &tcs3414_buffer_setup_ops);
+	if (ret < 0)
+		return ret;
+
+	ret = iio_device_register(indio_dev);
+	if (ret < 0)
+		goto buffer_cleanup;
+
+	return 0;
+
+buffer_cleanup:
+	iio_triggered_buffer_cleanup(indio_dev);
+	return ret;
+}
+
+static int tcs3414_powerdown(struct tcs3414_data *data)
+{
+	return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL,
+		data->control & ~(TCS3414_CONTROL_POWER |
+		TCS3414_CONTROL_ADC_EN));
+}
+
+static int tcs3414_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+	iio_device_unregister(indio_dev);
+	iio_triggered_buffer_cleanup(indio_dev);
+	tcs3414_powerdown(iio_priv(indio_dev));
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int tcs3414_suspend(struct device *dev)
+{
+	struct tcs3414_data *data = iio_priv(i2c_get_clientdata(
+		to_i2c_client(dev)));
+	return tcs3414_powerdown(data);
+}
+
+static int tcs3414_resume(struct device *dev)
+{
+	struct tcs3414_data *data = iio_priv(i2c_get_clientdata(
+		to_i2c_client(dev)));
+	return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL,
+		data->control);
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(tcs3414_pm_ops, tcs3414_suspend, tcs3414_resume);
+
+static const struct i2c_device_id tcs3414_id[] = {
+	{ "tcs3414", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, tcs3414_id);
+
+static struct i2c_driver tcs3414_driver = {
+	.driver = {
+		.name	= TCS3414_DRV_NAME,
+		.pm	= &tcs3414_pm_ops,
+	},
+	.probe		= tcs3414_probe,
+	.remove		= tcs3414_remove,
+	.id_table	= tcs3414_id,
+};
+module_i2c_driver(tcs3414_driver);
+
+MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
+MODULE_DESCRIPTION("TCS3414 digital color sensors driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c
new file mode 100644
index 0000000..e7923b5
--- /dev/null
+++ b/drivers/iio/light/tcs3472.c
@@ -0,0 +1,622 @@
+/*
+ * tcs3472.c - Support for TAOS TCS3472 color light-to-digital converter
+ *
+ * Copyright (c) 2013 Peter Meerwald <pmeerw@pmeerw.net>
+ *
+ * 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.
+ *
+ * Color light sensor with 16-bit channels for red, green, blue, clear);
+ * 7-bit I2C slave address 0x39 (TCS34721, TCS34723) or 0x29 (TCS34725,
+ * TCS34727)
+ *
+ * Datasheet: http://ams.com/eng/content/download/319364/1117183/file/TCS3472_Datasheet_EN_v2.pdf
+ *
+ * TODO: wait time
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/events.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#define TCS3472_DRV_NAME "tcs3472"
+
+#define TCS3472_COMMAND BIT(7)
+#define TCS3472_AUTO_INCR BIT(5)
+#define TCS3472_SPECIAL_FUNC (BIT(5) | BIT(6))
+
+#define TCS3472_INTR_CLEAR (TCS3472_COMMAND | TCS3472_SPECIAL_FUNC | 0x06)
+
+#define TCS3472_ENABLE (TCS3472_COMMAND | 0x00)
+#define TCS3472_ATIME (TCS3472_COMMAND | 0x01)
+#define TCS3472_WTIME (TCS3472_COMMAND | 0x03)
+#define TCS3472_AILT (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x04)
+#define TCS3472_AIHT (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x06)
+#define TCS3472_PERS (TCS3472_COMMAND | 0x0c)
+#define TCS3472_CONFIG (TCS3472_COMMAND | 0x0d)
+#define TCS3472_CONTROL (TCS3472_COMMAND | 0x0f)
+#define TCS3472_ID (TCS3472_COMMAND | 0x12)
+#define TCS3472_STATUS (TCS3472_COMMAND | 0x13)
+#define TCS3472_CDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x14)
+#define TCS3472_RDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x16)
+#define TCS3472_GDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x18)
+#define TCS3472_BDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x1a)
+
+#define TCS3472_STATUS_AINT BIT(4)
+#define TCS3472_STATUS_AVALID BIT(0)
+#define TCS3472_ENABLE_AIEN BIT(4)
+#define TCS3472_ENABLE_AEN BIT(1)
+#define TCS3472_ENABLE_PON BIT(0)
+#define TCS3472_CONTROL_AGAIN_MASK (BIT(0) | BIT(1))
+
+struct tcs3472_data {
+	struct i2c_client *client;
+	struct mutex lock;
+	u16 low_thresh;
+	u16 high_thresh;
+	u8 enable;
+	u8 control;
+	u8 atime;
+	u8 apers;
+	u16 buffer[8]; /* 4 16-bit channels + 64-bit timestamp */
+};
+
+static const struct iio_event_spec tcs3472_events[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_RISING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE),
+	}, {
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_FALLING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE),
+	}, {
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_EITHER,
+		.mask_separate = BIT(IIO_EV_INFO_ENABLE) |
+				 BIT(IIO_EV_INFO_PERIOD),
+	},
+};
+
+#define TCS3472_CHANNEL(_color, _si, _addr) { \
+	.type = IIO_INTENSITY, \
+	.modified = 1, \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBSCALE) | \
+		BIT(IIO_CHAN_INFO_INT_TIME), \
+	.channel2 = IIO_MOD_LIGHT_##_color, \
+	.address = _addr, \
+	.scan_index = _si, \
+	.scan_type = { \
+		.sign = 'u', \
+		.realbits = 16, \
+		.storagebits = 16, \
+		.endianness = IIO_CPU, \
+	}, \
+	.event_spec = _si ? NULL : tcs3472_events, \
+	.num_event_specs = _si ? 0 : ARRAY_SIZE(tcs3472_events), \
+}
+
+static const int tcs3472_agains[] = { 1, 4, 16, 60 };
+
+static const struct iio_chan_spec tcs3472_channels[] = {
+	TCS3472_CHANNEL(CLEAR, 0, TCS3472_CDATA),
+	TCS3472_CHANNEL(RED, 1, TCS3472_RDATA),
+	TCS3472_CHANNEL(GREEN, 2, TCS3472_GDATA),
+	TCS3472_CHANNEL(BLUE, 3, TCS3472_BDATA),
+	IIO_CHAN_SOFT_TIMESTAMP(4),
+};
+
+static int tcs3472_req_data(struct tcs3472_data *data)
+{
+	int tries = 50;
+	int ret;
+
+	while (tries--) {
+		ret = i2c_smbus_read_byte_data(data->client, TCS3472_STATUS);
+		if (ret < 0)
+			return ret;
+		if (ret & TCS3472_STATUS_AVALID)
+			break;
+		msleep(20);
+	}
+
+	if (tries < 0) {
+		dev_err(&data->client->dev, "data not ready\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int tcs3472_read_raw(struct iio_dev *indio_dev,
+			   struct iio_chan_spec const *chan,
+			   int *val, int *val2, long mask)
+{
+	struct tcs3472_data *data = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = iio_device_claim_direct_mode(indio_dev);
+		if (ret)
+			return ret;
+		ret = tcs3472_req_data(data);
+		if (ret < 0) {
+			iio_device_release_direct_mode(indio_dev);
+			return ret;
+		}
+		ret = i2c_smbus_read_word_data(data->client, chan->address);
+		iio_device_release_direct_mode(indio_dev);
+		if (ret < 0)
+			return ret;
+		*val = ret;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_CALIBSCALE:
+		*val = tcs3472_agains[data->control &
+			TCS3472_CONTROL_AGAIN_MASK];
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_INT_TIME:
+		*val = 0;
+		*val2 = (256 - data->atime) * 2400;
+		return IIO_VAL_INT_PLUS_MICRO;
+	}
+	return -EINVAL;
+}
+
+static int tcs3472_write_raw(struct iio_dev *indio_dev,
+			       struct iio_chan_spec const *chan,
+			       int val, int val2, long mask)
+{
+	struct tcs3472_data *data = iio_priv(indio_dev);
+	int i;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_CALIBSCALE:
+		if (val2 != 0)
+			return -EINVAL;
+		for (i = 0; i < ARRAY_SIZE(tcs3472_agains); i++) {
+			if (val == tcs3472_agains[i]) {
+				data->control &= ~TCS3472_CONTROL_AGAIN_MASK;
+				data->control |= i;
+				return i2c_smbus_write_byte_data(
+					data->client, TCS3472_CONTROL,
+					data->control);
+			}
+		}
+		return -EINVAL;
+	case IIO_CHAN_INFO_INT_TIME:
+		if (val != 0)
+			return -EINVAL;
+		for (i = 0; i < 256; i++) {
+			if (val2 == (256 - i) * 2400) {
+				data->atime = i;
+				return i2c_smbus_write_byte_data(
+					data->client, TCS3472_ATIME,
+					data->atime);
+			}
+
+		}
+		return -EINVAL;
+	}
+	return -EINVAL;
+}
+
+/*
+ * Translation from APERS field value to the number of consecutive out-of-range
+ * clear channel values before an interrupt is generated
+ */
+static const int tcs3472_intr_pers[] = {
+	0, 1, 2, 3, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60
+};
+
+static int tcs3472_read_event(struct iio_dev *indio_dev,
+	const struct iio_chan_spec *chan, enum iio_event_type type,
+	enum iio_event_direction dir, enum iio_event_info info, int *val,
+	int *val2)
+{
+	struct tcs3472_data *data = iio_priv(indio_dev);
+	int ret;
+	unsigned int period;
+
+	mutex_lock(&data->lock);
+
+	switch (info) {
+	case IIO_EV_INFO_VALUE:
+		*val = (dir == IIO_EV_DIR_RISING) ?
+			data->high_thresh : data->low_thresh;
+		ret = IIO_VAL_INT;
+		break;
+	case IIO_EV_INFO_PERIOD:
+		period = (256 - data->atime) * 2400 *
+			tcs3472_intr_pers[data->apers];
+		*val = period / USEC_PER_SEC;
+		*val2 = period % USEC_PER_SEC;
+		ret = IIO_VAL_INT_PLUS_MICRO;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+
+static int tcs3472_write_event(struct iio_dev *indio_dev,
+	const struct iio_chan_spec *chan, enum iio_event_type type,
+	enum iio_event_direction dir, enum iio_event_info info, int val,
+	int val2)
+{
+	struct tcs3472_data *data = iio_priv(indio_dev);
+	int ret;
+	u8 command;
+	int period;
+	int i;
+
+	mutex_lock(&data->lock);
+	switch (info) {
+	case IIO_EV_INFO_VALUE:
+		switch (dir) {
+		case IIO_EV_DIR_RISING:
+			command = TCS3472_AIHT;
+			break;
+		case IIO_EV_DIR_FALLING:
+			command = TCS3472_AILT;
+			break;
+		default:
+			ret = -EINVAL;
+			goto error;
+		}
+		ret = i2c_smbus_write_word_data(data->client, command, val);
+		if (ret)
+			goto error;
+
+		if (dir == IIO_EV_DIR_RISING)
+			data->high_thresh = val;
+		else
+			data->low_thresh = val;
+		break;
+	case IIO_EV_INFO_PERIOD:
+		period = val * USEC_PER_SEC + val2;
+		for (i = 1; i < ARRAY_SIZE(tcs3472_intr_pers) - 1; i++) {
+			if (period <= (256 - data->atime) * 2400 *
+					tcs3472_intr_pers[i])
+				break;
+		}
+		ret = i2c_smbus_write_byte_data(data->client, TCS3472_PERS, i);
+		if (ret)
+			goto error;
+
+		data->apers = i;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+error:
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+
+static int tcs3472_read_event_config(struct iio_dev *indio_dev,
+	const struct iio_chan_spec *chan, enum iio_event_type type,
+	enum iio_event_direction dir)
+{
+	struct tcs3472_data *data = iio_priv(indio_dev);
+	int ret;
+
+	mutex_lock(&data->lock);
+	ret = !!(data->enable & TCS3472_ENABLE_AIEN);
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+
+static int tcs3472_write_event_config(struct iio_dev *indio_dev,
+	const struct iio_chan_spec *chan, enum iio_event_type type,
+	enum iio_event_direction dir, int state)
+{
+	struct tcs3472_data *data = iio_priv(indio_dev);
+	int ret = 0;
+	u8 enable_old;
+
+	mutex_lock(&data->lock);
+
+	enable_old = data->enable;
+
+	if (state)
+		data->enable |= TCS3472_ENABLE_AIEN;
+	else
+		data->enable &= ~TCS3472_ENABLE_AIEN;
+
+	if (enable_old != data->enable) {
+		ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE,
+						data->enable);
+		if (ret)
+			data->enable = enable_old;
+	}
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+
+static irqreturn_t tcs3472_event_handler(int irq, void *priv)
+{
+	struct iio_dev *indio_dev = priv;
+	struct tcs3472_data *data = iio_priv(indio_dev);
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(data->client, TCS3472_STATUS);
+	if (ret >= 0 && (ret & TCS3472_STATUS_AINT)) {
+		iio_push_event(indio_dev, IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0,
+						IIO_EV_TYPE_THRESH,
+						IIO_EV_DIR_EITHER),
+				iio_get_time_ns(indio_dev));
+
+		i2c_smbus_read_byte_data(data->client, TCS3472_INTR_CLEAR);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t tcs3472_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct tcs3472_data *data = iio_priv(indio_dev);
+	int i, j = 0;
+
+	int ret = tcs3472_req_data(data);
+	if (ret < 0)
+		goto done;
+
+	for_each_set_bit(i, indio_dev->active_scan_mask,
+		indio_dev->masklength) {
+		ret = i2c_smbus_read_word_data(data->client,
+			TCS3472_CDATA + 2*i);
+		if (ret < 0)
+			goto done;
+
+		data->buffer[j++] = ret;
+	}
+
+	iio_push_to_buffers_with_timestamp(indio_dev, data->buffer,
+		iio_get_time_ns(indio_dev));
+
+done:
+	iio_trigger_notify_done(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
+static ssize_t tcs3472_show_int_time_available(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	size_t len = 0;
+	int i;
+
+	for (i = 1; i <= 256; i++)
+		len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06d ",
+			2400 * i);
+
+	/* replace trailing space by newline */
+	buf[len - 1] = '\n';
+
+	return len;
+}
+
+static IIO_CONST_ATTR(calibscale_available, "1 4 16 60");
+static IIO_DEV_ATTR_INT_TIME_AVAIL(tcs3472_show_int_time_available);
+
+static struct attribute *tcs3472_attributes[] = {
+	&iio_const_attr_calibscale_available.dev_attr.attr,
+	&iio_dev_attr_integration_time_available.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group tcs3472_attribute_group = {
+	.attrs = tcs3472_attributes,
+};
+
+static const struct iio_info tcs3472_info = {
+	.read_raw = tcs3472_read_raw,
+	.write_raw = tcs3472_write_raw,
+	.read_event_value = tcs3472_read_event,
+	.write_event_value = tcs3472_write_event,
+	.read_event_config = tcs3472_read_event_config,
+	.write_event_config = tcs3472_write_event_config,
+	.attrs = &tcs3472_attribute_group,
+};
+
+static int tcs3472_probe(struct i2c_client *client,
+			   const struct i2c_device_id *id)
+{
+	struct tcs3472_data *data;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (indio_dev == NULL)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->client = client;
+	mutex_init(&data->lock);
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->info = &tcs3472_info;
+	indio_dev->name = TCS3472_DRV_NAME;
+	indio_dev->channels = tcs3472_channels;
+	indio_dev->num_channels = ARRAY_SIZE(tcs3472_channels);
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	ret = i2c_smbus_read_byte_data(data->client, TCS3472_ID);
+	if (ret < 0)
+		return ret;
+
+	if (ret == 0x44)
+		dev_info(&client->dev, "TCS34721/34725 found\n");
+	else if (ret == 0x4d)
+		dev_info(&client->dev, "TCS34723/34727 found\n");
+	else
+		return -ENODEV;
+
+	ret = i2c_smbus_read_byte_data(data->client, TCS3472_CONTROL);
+	if (ret < 0)
+		return ret;
+	data->control = ret;
+
+	ret = i2c_smbus_read_byte_data(data->client, TCS3472_ATIME);
+	if (ret < 0)
+		return ret;
+	data->atime = ret;
+
+	ret = i2c_smbus_read_word_data(data->client, TCS3472_AILT);
+	if (ret < 0)
+		return ret;
+	data->low_thresh = ret;
+
+	ret = i2c_smbus_read_word_data(data->client, TCS3472_AIHT);
+	if (ret < 0)
+		return ret;
+	data->high_thresh = ret;
+
+	data->apers = 1;
+	ret = i2c_smbus_write_byte_data(data->client, TCS3472_PERS,
+					data->apers);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_read_byte_data(data->client, TCS3472_ENABLE);
+	if (ret < 0)
+		return ret;
+
+	/* enable device */
+	data->enable = ret | TCS3472_ENABLE_PON | TCS3472_ENABLE_AEN;
+	data->enable &= ~TCS3472_ENABLE_AIEN;
+	ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE,
+		data->enable);
+	if (ret < 0)
+		return ret;
+
+	ret = iio_triggered_buffer_setup(indio_dev, NULL,
+		tcs3472_trigger_handler, NULL);
+	if (ret < 0)
+		return ret;
+
+	if (client->irq) {
+		ret = request_threaded_irq(client->irq, NULL,
+					   tcs3472_event_handler,
+					   IRQF_TRIGGER_FALLING | IRQF_SHARED |
+					   IRQF_ONESHOT,
+					   client->name, indio_dev);
+		if (ret)
+			goto buffer_cleanup;
+	}
+
+	ret = iio_device_register(indio_dev);
+	if (ret < 0)
+		goto free_irq;
+
+	return 0;
+
+free_irq:
+	free_irq(client->irq, indio_dev);
+buffer_cleanup:
+	iio_triggered_buffer_cleanup(indio_dev);
+	return ret;
+}
+
+static int tcs3472_powerdown(struct tcs3472_data *data)
+{
+	int ret;
+	u8 enable_mask = TCS3472_ENABLE_AEN | TCS3472_ENABLE_PON;
+
+	mutex_lock(&data->lock);
+
+	ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE,
+		data->enable & ~enable_mask);
+	if (!ret)
+		data->enable &= ~enable_mask;
+
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+
+static int tcs3472_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+	iio_device_unregister(indio_dev);
+	free_irq(client->irq, indio_dev);
+	iio_triggered_buffer_cleanup(indio_dev);
+	tcs3472_powerdown(iio_priv(indio_dev));
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int tcs3472_suspend(struct device *dev)
+{
+	struct tcs3472_data *data = iio_priv(i2c_get_clientdata(
+		to_i2c_client(dev)));
+	return tcs3472_powerdown(data);
+}
+
+static int tcs3472_resume(struct device *dev)
+{
+	struct tcs3472_data *data = iio_priv(i2c_get_clientdata(
+		to_i2c_client(dev)));
+	int ret;
+	u8 enable_mask = TCS3472_ENABLE_AEN | TCS3472_ENABLE_PON;
+
+	mutex_lock(&data->lock);
+
+	ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE,
+		data->enable | enable_mask);
+	if (!ret)
+		data->enable |= enable_mask;
+
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(tcs3472_pm_ops, tcs3472_suspend, tcs3472_resume);
+
+static const struct i2c_device_id tcs3472_id[] = {
+	{ "tcs3472", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, tcs3472_id);
+
+static struct i2c_driver tcs3472_driver = {
+	.driver = {
+		.name	= TCS3472_DRV_NAME,
+		.pm	= &tcs3472_pm_ops,
+	},
+	.probe		= tcs3472_probe,
+	.remove		= tcs3472_remove,
+	.id_table	= tcs3472_id,
+};
+module_i2c_driver(tcs3472_driver);
+
+MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
+MODULE_DESCRIPTION("TCS3472 color light sensors driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/tsl2563.c b/drivers/iio/light/tsl2563.c
new file mode 100644
index 0000000..6bbb0b1
--- /dev/null
+++ b/drivers/iio/light/tsl2563.c
@@ -0,0 +1,908 @@
+/*
+ * drivers/iio/light/tsl2563.c
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Written by Timo O. Karjalainen <timo.o.karjalainen@nokia.com>
+ * Contact: Amit Kucheria <amit.kucheria@verdurent.com>
+ *
+ * Converted to IIO driver
+ * Amit Kucheria <amit.kucheria@verdurent.com>
+ *
+ * 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 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/events.h>
+#include <linux/platform_data/tsl2563.h>
+
+/* Use this many bits for fraction part. */
+#define ADC_FRAC_BITS		14
+
+/* Given number of 1/10000's in ADC_FRAC_BITS precision. */
+#define FRAC10K(f)		(((f) * (1L << (ADC_FRAC_BITS))) / (10000))
+
+/* Bits used for fraction in calibration coefficients.*/
+#define CALIB_FRAC_BITS		10
+/* 0.5 in CALIB_FRAC_BITS precision */
+#define CALIB_FRAC_HALF		(1 << (CALIB_FRAC_BITS - 1))
+/* Make a fraction from a number n that was multiplied with b. */
+#define CALIB_FRAC(n, b)	(((n) << CALIB_FRAC_BITS) / (b))
+/* Decimal 10^(digits in sysfs presentation) */
+#define CALIB_BASE_SYSFS	1000
+
+#define TSL2563_CMD		0x80
+#define TSL2563_CLEARINT	0x40
+
+#define TSL2563_REG_CTRL	0x00
+#define TSL2563_REG_TIMING	0x01
+#define TSL2563_REG_LOWLOW	0x02 /* data0 low threshold, 2 bytes */
+#define TSL2563_REG_LOWHIGH	0x03
+#define TSL2563_REG_HIGHLOW	0x04 /* data0 high threshold, 2 bytes */
+#define TSL2563_REG_HIGHHIGH	0x05
+#define TSL2563_REG_INT		0x06
+#define TSL2563_REG_ID		0x0a
+#define TSL2563_REG_DATA0LOW	0x0c /* broadband sensor value, 2 bytes */
+#define TSL2563_REG_DATA0HIGH	0x0d
+#define TSL2563_REG_DATA1LOW	0x0e /* infrared sensor value, 2 bytes */
+#define TSL2563_REG_DATA1HIGH	0x0f
+
+#define TSL2563_CMD_POWER_ON	0x03
+#define TSL2563_CMD_POWER_OFF	0x00
+#define TSL2563_CTRL_POWER_MASK	0x03
+
+#define TSL2563_TIMING_13MS	0x00
+#define TSL2563_TIMING_100MS	0x01
+#define TSL2563_TIMING_400MS	0x02
+#define TSL2563_TIMING_MASK	0x03
+#define TSL2563_TIMING_GAIN16	0x10
+#define TSL2563_TIMING_GAIN1	0x00
+
+#define TSL2563_INT_DISBLED	0x00
+#define TSL2563_INT_LEVEL	0x10
+#define TSL2563_INT_PERSIST(n)	((n) & 0x0F)
+
+struct tsl2563_gainlevel_coeff {
+	u8 gaintime;
+	u16 min;
+	u16 max;
+};
+
+static const struct tsl2563_gainlevel_coeff tsl2563_gainlevel_table[] = {
+	{
+		.gaintime	= TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN16,
+		.min		= 0,
+		.max		= 65534,
+	}, {
+		.gaintime	= TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN1,
+		.min		= 2048,
+		.max		= 65534,
+	}, {
+		.gaintime	= TSL2563_TIMING_100MS | TSL2563_TIMING_GAIN1,
+		.min		= 4095,
+		.max		= 37177,
+	}, {
+		.gaintime	= TSL2563_TIMING_13MS | TSL2563_TIMING_GAIN1,
+		.min		= 3000,
+		.max		= 65535,
+	},
+};
+
+struct tsl2563_chip {
+	struct mutex		lock;
+	struct i2c_client	*client;
+	struct delayed_work	poweroff_work;
+
+	/* Remember state for suspend and resume functions */
+	bool suspended;
+
+	struct tsl2563_gainlevel_coeff const *gainlevel;
+
+	u16			low_thres;
+	u16			high_thres;
+	u8			intr;
+	bool			int_enabled;
+
+	/* Calibration coefficients */
+	u32			calib0;
+	u32			calib1;
+	int			cover_comp_gain;
+
+	/* Cache current values, to be returned while suspended */
+	u32			data0;
+	u32			data1;
+};
+
+static int tsl2563_set_power(struct tsl2563_chip *chip, int on)
+{
+	struct i2c_client *client = chip->client;
+	u8 cmd;
+
+	cmd = on ? TSL2563_CMD_POWER_ON : TSL2563_CMD_POWER_OFF;
+	return i2c_smbus_write_byte_data(client,
+					 TSL2563_CMD | TSL2563_REG_CTRL, cmd);
+}
+
+/*
+ * Return value is 0 for off, 1 for on, or a negative error
+ * code if reading failed.
+ */
+static int tsl2563_get_power(struct tsl2563_chip *chip)
+{
+	struct i2c_client *client = chip->client;
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(client, TSL2563_CMD | TSL2563_REG_CTRL);
+	if (ret < 0)
+		return ret;
+
+	return (ret & TSL2563_CTRL_POWER_MASK) == TSL2563_CMD_POWER_ON;
+}
+
+static int tsl2563_configure(struct tsl2563_chip *chip)
+{
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(chip->client,
+			TSL2563_CMD | TSL2563_REG_TIMING,
+			chip->gainlevel->gaintime);
+	if (ret)
+		goto error_ret;
+	ret = i2c_smbus_write_byte_data(chip->client,
+			TSL2563_CMD | TSL2563_REG_HIGHLOW,
+			chip->high_thres & 0xFF);
+	if (ret)
+		goto error_ret;
+	ret = i2c_smbus_write_byte_data(chip->client,
+			TSL2563_CMD | TSL2563_REG_HIGHHIGH,
+			(chip->high_thres >> 8) & 0xFF);
+	if (ret)
+		goto error_ret;
+	ret = i2c_smbus_write_byte_data(chip->client,
+			TSL2563_CMD | TSL2563_REG_LOWLOW,
+			chip->low_thres & 0xFF);
+	if (ret)
+		goto error_ret;
+	ret = i2c_smbus_write_byte_data(chip->client,
+			TSL2563_CMD | TSL2563_REG_LOWHIGH,
+			(chip->low_thres >> 8) & 0xFF);
+/*
+ * Interrupt register is automatically written anyway if it is relevant
+ * so is not here.
+ */
+error_ret:
+	return ret;
+}
+
+static void tsl2563_poweroff_work(struct work_struct *work)
+{
+	struct tsl2563_chip *chip =
+		container_of(work, struct tsl2563_chip, poweroff_work.work);
+	tsl2563_set_power(chip, 0);
+}
+
+static int tsl2563_detect(struct tsl2563_chip *chip)
+{
+	int ret;
+
+	ret = tsl2563_set_power(chip, 1);
+	if (ret)
+		return ret;
+
+	ret = tsl2563_get_power(chip);
+	if (ret < 0)
+		return ret;
+
+	return ret ? 0 : -ENODEV;
+}
+
+static int tsl2563_read_id(struct tsl2563_chip *chip, u8 *id)
+{
+	struct i2c_client *client = chip->client;
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(client, TSL2563_CMD | TSL2563_REG_ID);
+	if (ret < 0)
+		return ret;
+
+	*id = ret;
+
+	return 0;
+}
+
+/*
+ * "Normalized" ADC value is one obtained with 400ms of integration time and
+ * 16x gain. This function returns the number of bits of shift needed to
+ * convert between normalized values and HW values obtained using given
+ * timing and gain settings.
+ */
+static int tsl2563_adc_shiftbits(u8 timing)
+{
+	int shift = 0;
+
+	switch (timing & TSL2563_TIMING_MASK) {
+	case TSL2563_TIMING_13MS:
+		shift += 5;
+		break;
+	case TSL2563_TIMING_100MS:
+		shift += 2;
+		break;
+	case TSL2563_TIMING_400MS:
+		/* no-op */
+		break;
+	}
+
+	if (!(timing & TSL2563_TIMING_GAIN16))
+		shift += 4;
+
+	return shift;
+}
+
+/* Convert a HW ADC value to normalized scale. */
+static u32 tsl2563_normalize_adc(u16 adc, u8 timing)
+{
+	return adc << tsl2563_adc_shiftbits(timing);
+}
+
+static void tsl2563_wait_adc(struct tsl2563_chip *chip)
+{
+	unsigned int delay;
+
+	switch (chip->gainlevel->gaintime & TSL2563_TIMING_MASK) {
+	case TSL2563_TIMING_13MS:
+		delay = 14;
+		break;
+	case TSL2563_TIMING_100MS:
+		delay = 101;
+		break;
+	default:
+		delay = 402;
+	}
+	/*
+	 * TODO: Make sure that we wait at least required delay but why we
+	 * have to extend it one tick more?
+	 */
+	schedule_timeout_interruptible(msecs_to_jiffies(delay) + 2);
+}
+
+static int tsl2563_adjust_gainlevel(struct tsl2563_chip *chip, u16 adc)
+{
+	struct i2c_client *client = chip->client;
+
+	if (adc > chip->gainlevel->max || adc < chip->gainlevel->min) {
+
+		(adc > chip->gainlevel->max) ?
+			chip->gainlevel++ : chip->gainlevel--;
+
+		i2c_smbus_write_byte_data(client,
+					  TSL2563_CMD | TSL2563_REG_TIMING,
+					  chip->gainlevel->gaintime);
+
+		tsl2563_wait_adc(chip);
+		tsl2563_wait_adc(chip);
+
+		return 1;
+	} else
+		return 0;
+}
+
+static int tsl2563_get_adc(struct tsl2563_chip *chip)
+{
+	struct i2c_client *client = chip->client;
+	u16 adc0, adc1;
+	int retry = 1;
+	int ret = 0;
+
+	if (chip->suspended)
+		goto out;
+
+	if (!chip->int_enabled) {
+		cancel_delayed_work(&chip->poweroff_work);
+
+		if (!tsl2563_get_power(chip)) {
+			ret = tsl2563_set_power(chip, 1);
+			if (ret)
+				goto out;
+			ret = tsl2563_configure(chip);
+			if (ret)
+				goto out;
+			tsl2563_wait_adc(chip);
+		}
+	}
+
+	while (retry) {
+		ret = i2c_smbus_read_word_data(client,
+				TSL2563_CMD | TSL2563_REG_DATA0LOW);
+		if (ret < 0)
+			goto out;
+		adc0 = ret;
+
+		ret = i2c_smbus_read_word_data(client,
+				TSL2563_CMD | TSL2563_REG_DATA1LOW);
+		if (ret < 0)
+			goto out;
+		adc1 = ret;
+
+		retry = tsl2563_adjust_gainlevel(chip, adc0);
+	}
+
+	chip->data0 = tsl2563_normalize_adc(adc0, chip->gainlevel->gaintime);
+	chip->data1 = tsl2563_normalize_adc(adc1, chip->gainlevel->gaintime);
+
+	if (!chip->int_enabled)
+		schedule_delayed_work(&chip->poweroff_work, 5 * HZ);
+
+	ret = 0;
+out:
+	return ret;
+}
+
+static inline int tsl2563_calib_to_sysfs(u32 calib)
+{
+	return (int) (((calib * CALIB_BASE_SYSFS) +
+		       CALIB_FRAC_HALF) >> CALIB_FRAC_BITS);
+}
+
+static inline u32 tsl2563_calib_from_sysfs(int value)
+{
+	return (((u32) value) << CALIB_FRAC_BITS) / CALIB_BASE_SYSFS;
+}
+
+/*
+ * Conversions between lux and ADC values.
+ *
+ * The basic formula is lux = c0 * adc0 - c1 * adc1, where c0 and c1 are
+ * appropriate constants. Different constants are needed for different
+ * kinds of light, determined by the ratio adc1/adc0 (basically the ratio
+ * of the intensities in infrared and visible wavelengths). lux_table below
+ * lists the upper threshold of the adc1/adc0 ratio and the corresponding
+ * constants.
+ */
+
+struct tsl2563_lux_coeff {
+	unsigned long ch_ratio;
+	unsigned long ch0_coeff;
+	unsigned long ch1_coeff;
+};
+
+static const struct tsl2563_lux_coeff lux_table[] = {
+	{
+		.ch_ratio	= FRAC10K(1300),
+		.ch0_coeff	= FRAC10K(315),
+		.ch1_coeff	= FRAC10K(262),
+	}, {
+		.ch_ratio	= FRAC10K(2600),
+		.ch0_coeff	= FRAC10K(337),
+		.ch1_coeff	= FRAC10K(430),
+	}, {
+		.ch_ratio	= FRAC10K(3900),
+		.ch0_coeff	= FRAC10K(363),
+		.ch1_coeff	= FRAC10K(529),
+	}, {
+		.ch_ratio	= FRAC10K(5200),
+		.ch0_coeff	= FRAC10K(392),
+		.ch1_coeff	= FRAC10K(605),
+	}, {
+		.ch_ratio	= FRAC10K(6500),
+		.ch0_coeff	= FRAC10K(229),
+		.ch1_coeff	= FRAC10K(291),
+	}, {
+		.ch_ratio	= FRAC10K(8000),
+		.ch0_coeff	= FRAC10K(157),
+		.ch1_coeff	= FRAC10K(180),
+	}, {
+		.ch_ratio	= FRAC10K(13000),
+		.ch0_coeff	= FRAC10K(34),
+		.ch1_coeff	= FRAC10K(26),
+	}, {
+		.ch_ratio	= ULONG_MAX,
+		.ch0_coeff	= 0,
+		.ch1_coeff	= 0,
+	},
+};
+
+/* Convert normalized, scaled ADC values to lux. */
+static unsigned int tsl2563_adc_to_lux(u32 adc0, u32 adc1)
+{
+	const struct tsl2563_lux_coeff *lp = lux_table;
+	unsigned long ratio, lux, ch0 = adc0, ch1 = adc1;
+
+	ratio = ch0 ? ((ch1 << ADC_FRAC_BITS) / ch0) : ULONG_MAX;
+
+	while (lp->ch_ratio < ratio)
+		lp++;
+
+	lux = ch0 * lp->ch0_coeff - ch1 * lp->ch1_coeff;
+
+	return (unsigned int) (lux >> ADC_FRAC_BITS);
+}
+
+/* Apply calibration coefficient to ADC count. */
+static u32 tsl2563_calib_adc(u32 adc, u32 calib)
+{
+	unsigned long scaled = adc;
+
+	scaled *= calib;
+	scaled >>= CALIB_FRAC_BITS;
+
+	return (u32) scaled;
+}
+
+static int tsl2563_write_raw(struct iio_dev *indio_dev,
+			       struct iio_chan_spec const *chan,
+			       int val,
+			       int val2,
+			       long mask)
+{
+	struct tsl2563_chip *chip = iio_priv(indio_dev);
+
+	if (mask != IIO_CHAN_INFO_CALIBSCALE)
+		return -EINVAL;
+	if (chan->channel2 == IIO_MOD_LIGHT_BOTH)
+		chip->calib0 = tsl2563_calib_from_sysfs(val);
+	else if (chan->channel2 == IIO_MOD_LIGHT_IR)
+		chip->calib1 = tsl2563_calib_from_sysfs(val);
+	else
+		return -EINVAL;
+
+	return 0;
+}
+
+static int tsl2563_read_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan,
+			    int *val,
+			    int *val2,
+			    long mask)
+{
+	int ret = -EINVAL;
+	u32 calib0, calib1;
+	struct tsl2563_chip *chip = iio_priv(indio_dev);
+
+	mutex_lock(&chip->lock);
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+	case IIO_CHAN_INFO_PROCESSED:
+		switch (chan->type) {
+		case IIO_LIGHT:
+			ret = tsl2563_get_adc(chip);
+			if (ret)
+				goto error_ret;
+			calib0 = tsl2563_calib_adc(chip->data0, chip->calib0) *
+				chip->cover_comp_gain;
+			calib1 = tsl2563_calib_adc(chip->data1, chip->calib1) *
+				chip->cover_comp_gain;
+			*val = tsl2563_adc_to_lux(calib0, calib1);
+			ret = IIO_VAL_INT;
+			break;
+		case IIO_INTENSITY:
+			ret = tsl2563_get_adc(chip);
+			if (ret)
+				goto error_ret;
+			if (chan->channel2 == IIO_MOD_LIGHT_BOTH)
+				*val = chip->data0;
+			else
+				*val = chip->data1;
+			ret = IIO_VAL_INT;
+			break;
+		default:
+			break;
+		}
+		break;
+
+	case IIO_CHAN_INFO_CALIBSCALE:
+		if (chan->channel2 == IIO_MOD_LIGHT_BOTH)
+			*val = tsl2563_calib_to_sysfs(chip->calib0);
+		else
+			*val = tsl2563_calib_to_sysfs(chip->calib1);
+		ret = IIO_VAL_INT;
+		break;
+	default:
+		ret = -EINVAL;
+		goto error_ret;
+	}
+
+error_ret:
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static const struct iio_event_spec tsl2563_events[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_RISING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+				BIT(IIO_EV_INFO_ENABLE),
+	}, {
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_FALLING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+				BIT(IIO_EV_INFO_ENABLE),
+	},
+};
+
+static const struct iio_chan_spec tsl2563_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.indexed = 1,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+		.channel = 0,
+	}, {
+		.type = IIO_INTENSITY,
+		.modified = 1,
+		.channel2 = IIO_MOD_LIGHT_BOTH,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+		BIT(IIO_CHAN_INFO_CALIBSCALE),
+		.event_spec = tsl2563_events,
+		.num_event_specs = ARRAY_SIZE(tsl2563_events),
+	}, {
+		.type = IIO_INTENSITY,
+		.modified = 1,
+		.channel2 = IIO_MOD_LIGHT_IR,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+		BIT(IIO_CHAN_INFO_CALIBSCALE),
+	}
+};
+
+static int tsl2563_read_thresh(struct iio_dev *indio_dev,
+	const struct iio_chan_spec *chan, enum iio_event_type type,
+	enum iio_event_direction dir, enum iio_event_info info, int *val,
+	int *val2)
+{
+	struct tsl2563_chip *chip = iio_priv(indio_dev);
+
+	switch (dir) {
+	case IIO_EV_DIR_RISING:
+		*val = chip->high_thres;
+		break;
+	case IIO_EV_DIR_FALLING:
+		*val = chip->low_thres;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return IIO_VAL_INT;
+}
+
+static int tsl2563_write_thresh(struct iio_dev *indio_dev,
+	const struct iio_chan_spec *chan, enum iio_event_type type,
+	enum iio_event_direction dir, enum iio_event_info info, int val,
+	int val2)
+{
+	struct tsl2563_chip *chip = iio_priv(indio_dev);
+	int ret;
+	u8 address;
+
+	if (dir == IIO_EV_DIR_RISING)
+		address = TSL2563_REG_HIGHLOW;
+	else
+		address = TSL2563_REG_LOWLOW;
+	mutex_lock(&chip->lock);
+	ret = i2c_smbus_write_byte_data(chip->client, TSL2563_CMD | address,
+					val & 0xFF);
+	if (ret)
+		goto error_ret;
+	ret = i2c_smbus_write_byte_data(chip->client,
+					TSL2563_CMD | (address + 1),
+					(val >> 8) & 0xFF);
+	if (dir == IIO_EV_DIR_RISING)
+		chip->high_thres = val;
+	else
+		chip->low_thres = val;
+
+error_ret:
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+static irqreturn_t tsl2563_event_handler(int irq, void *private)
+{
+	struct iio_dev *dev_info = private;
+	struct tsl2563_chip *chip = iio_priv(dev_info);
+
+	iio_push_event(dev_info,
+		       IIO_UNMOD_EVENT_CODE(IIO_INTENSITY,
+					    0,
+					    IIO_EV_TYPE_THRESH,
+					    IIO_EV_DIR_EITHER),
+		       iio_get_time_ns(dev_info));
+
+	/* clear the interrupt and push the event */
+	i2c_smbus_write_byte(chip->client, TSL2563_CMD | TSL2563_CLEARINT);
+	return IRQ_HANDLED;
+}
+
+static int tsl2563_write_interrupt_config(struct iio_dev *indio_dev,
+	const struct iio_chan_spec *chan, enum iio_event_type type,
+	enum iio_event_direction dir, int state)
+{
+	struct tsl2563_chip *chip = iio_priv(indio_dev);
+	int ret = 0;
+
+	mutex_lock(&chip->lock);
+	if (state && !(chip->intr & 0x30)) {
+		chip->intr &= ~0x30;
+		chip->intr |= 0x10;
+		/* ensure the chip is actually on */
+		cancel_delayed_work(&chip->poweroff_work);
+		if (!tsl2563_get_power(chip)) {
+			ret = tsl2563_set_power(chip, 1);
+			if (ret)
+				goto out;
+			ret = tsl2563_configure(chip);
+			if (ret)
+				goto out;
+		}
+		ret = i2c_smbus_write_byte_data(chip->client,
+						TSL2563_CMD | TSL2563_REG_INT,
+						chip->intr);
+		chip->int_enabled = true;
+	}
+
+	if (!state && (chip->intr & 0x30)) {
+		chip->intr &= ~0x30;
+		ret = i2c_smbus_write_byte_data(chip->client,
+						TSL2563_CMD | TSL2563_REG_INT,
+						chip->intr);
+		chip->int_enabled = false;
+		/* now the interrupt is not enabled, we can go to sleep */
+		schedule_delayed_work(&chip->poweroff_work, 5 * HZ);
+	}
+out:
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+static int tsl2563_read_interrupt_config(struct iio_dev *indio_dev,
+	const struct iio_chan_spec *chan, enum iio_event_type type,
+	enum iio_event_direction dir)
+{
+	struct tsl2563_chip *chip = iio_priv(indio_dev);
+	int ret;
+
+	mutex_lock(&chip->lock);
+	ret = i2c_smbus_read_byte_data(chip->client,
+				       TSL2563_CMD | TSL2563_REG_INT);
+	mutex_unlock(&chip->lock);
+	if (ret < 0)
+		return ret;
+
+	return !!(ret & 0x30);
+}
+
+static const struct iio_info tsl2563_info_no_irq = {
+	.read_raw = &tsl2563_read_raw,
+	.write_raw = &tsl2563_write_raw,
+};
+
+static const struct iio_info tsl2563_info = {
+	.read_raw = &tsl2563_read_raw,
+	.write_raw = &tsl2563_write_raw,
+	.read_event_value = &tsl2563_read_thresh,
+	.write_event_value = &tsl2563_write_thresh,
+	.read_event_config = &tsl2563_read_interrupt_config,
+	.write_event_config = &tsl2563_write_interrupt_config,
+};
+
+static int tsl2563_probe(struct i2c_client *client,
+				const struct i2c_device_id *device_id)
+{
+	struct iio_dev *indio_dev;
+	struct tsl2563_chip *chip;
+	struct tsl2563_platform_data *pdata = client->dev.platform_data;
+	struct device_node *np = client->dev.of_node;
+	int err = 0;
+	u8 id = 0;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	chip = iio_priv(indio_dev);
+
+	i2c_set_clientdata(client, chip);
+	chip->client = client;
+
+	err = tsl2563_detect(chip);
+	if (err) {
+		dev_err(&client->dev, "detect error %d\n", -err);
+		return err;
+	}
+
+	err = tsl2563_read_id(chip, &id);
+	if (err) {
+		dev_err(&client->dev, "read id error %d\n", -err);
+		return err;
+	}
+
+	mutex_init(&chip->lock);
+
+	/* Default values used until userspace says otherwise */
+	chip->low_thres = 0x0;
+	chip->high_thres = 0xffff;
+	chip->gainlevel = tsl2563_gainlevel_table;
+	chip->intr = TSL2563_INT_PERSIST(4);
+	chip->calib0 = tsl2563_calib_from_sysfs(CALIB_BASE_SYSFS);
+	chip->calib1 = tsl2563_calib_from_sysfs(CALIB_BASE_SYSFS);
+
+	if (pdata)
+		chip->cover_comp_gain = pdata->cover_comp_gain;
+	else if (np)
+		of_property_read_u32(np, "amstaos,cover-comp-gain",
+				     &chip->cover_comp_gain);
+	else
+		chip->cover_comp_gain = 1;
+
+	dev_info(&client->dev, "model %d, rev. %d\n", id >> 4, id & 0x0f);
+	indio_dev->name = client->name;
+	indio_dev->channels = tsl2563_channels;
+	indio_dev->num_channels = ARRAY_SIZE(tsl2563_channels);
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	if (client->irq)
+		indio_dev->info = &tsl2563_info;
+	else
+		indio_dev->info = &tsl2563_info_no_irq;
+
+	if (client->irq) {
+		err = devm_request_threaded_irq(&client->dev, client->irq,
+					   NULL,
+					   &tsl2563_event_handler,
+					   IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+					   "tsl2563_event",
+					   indio_dev);
+		if (err) {
+			dev_err(&client->dev, "irq request error %d\n", -err);
+			return err;
+		}
+	}
+
+	err = tsl2563_configure(chip);
+	if (err) {
+		dev_err(&client->dev, "configure error %d\n", -err);
+		return err;
+	}
+
+	INIT_DELAYED_WORK(&chip->poweroff_work, tsl2563_poweroff_work);
+
+	/* The interrupt cannot yet be enabled so this is fine without lock */
+	schedule_delayed_work(&chip->poweroff_work, 5 * HZ);
+
+	err = iio_device_register(indio_dev);
+	if (err) {
+		dev_err(&client->dev, "iio registration error %d\n", -err);
+		goto fail;
+	}
+
+	return 0;
+
+fail:
+	cancel_delayed_work_sync(&chip->poweroff_work);
+	return err;
+}
+
+static int tsl2563_remove(struct i2c_client *client)
+{
+	struct tsl2563_chip *chip = i2c_get_clientdata(client);
+	struct iio_dev *indio_dev = iio_priv_to_dev(chip);
+
+	iio_device_unregister(indio_dev);
+	if (!chip->int_enabled)
+		cancel_delayed_work(&chip->poweroff_work);
+	/* Ensure that interrupts are disabled - then flush any bottom halves */
+	chip->intr &= ~0x30;
+	i2c_smbus_write_byte_data(chip->client, TSL2563_CMD | TSL2563_REG_INT,
+				  chip->intr);
+	flush_scheduled_work();
+	tsl2563_set_power(chip, 0);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int tsl2563_suspend(struct device *dev)
+{
+	struct tsl2563_chip *chip = i2c_get_clientdata(to_i2c_client(dev));
+	int ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = tsl2563_set_power(chip, 0);
+	if (ret)
+		goto out;
+
+	chip->suspended = true;
+
+out:
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static int tsl2563_resume(struct device *dev)
+{
+	struct tsl2563_chip *chip = i2c_get_clientdata(to_i2c_client(dev));
+	int ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = tsl2563_set_power(chip, 1);
+	if (ret)
+		goto out;
+
+	ret = tsl2563_configure(chip);
+	if (ret)
+		goto out;
+
+	chip->suspended = false;
+
+out:
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(tsl2563_pm_ops, tsl2563_suspend, tsl2563_resume);
+#define TSL2563_PM_OPS (&tsl2563_pm_ops)
+#else
+#define TSL2563_PM_OPS NULL
+#endif
+
+static const struct i2c_device_id tsl2563_id[] = {
+	{ "tsl2560", 0 },
+	{ "tsl2561", 1 },
+	{ "tsl2562", 2 },
+	{ "tsl2563", 3 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, tsl2563_id);
+
+static const struct of_device_id tsl2563_of_match[] = {
+	{ .compatible = "amstaos,tsl2560" },
+	{ .compatible = "amstaos,tsl2561" },
+	{ .compatible = "amstaos,tsl2562" },
+	{ .compatible = "amstaos,tsl2563" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, tsl2563_of_match);
+
+static struct i2c_driver tsl2563_i2c_driver = {
+	.driver = {
+		.name	 = "tsl2563",
+		.of_match_table = tsl2563_of_match,
+		.pm	= TSL2563_PM_OPS,
+	},
+	.probe		= tsl2563_probe,
+	.remove		= tsl2563_remove,
+	.id_table	= tsl2563_id,
+};
+module_i2c_driver(tsl2563_i2c_driver);
+
+MODULE_AUTHOR("Nokia Corporation");
+MODULE_DESCRIPTION("tsl2563 light sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/tsl2583.c b/drivers/iio/light/tsl2583.c
new file mode 100644
index 0000000..4b5d998
--- /dev/null
+++ b/drivers/iio/light/tsl2583.c
@@ -0,0 +1,958 @@
+/*
+ * Device driver for monitoring ambient light intensity (lux)
+ * within the TAOS tsl258x family of devices (tsl2580, tsl2581, tsl2583).
+ *
+ * Copyright (c) 2011, TAOS Corporation.
+ * Copyright (c) 2016-2017 Brian Masney <masneyb@onstation.org>
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/i2c.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/mutex.h>
+#include <linux/unistd.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/pm_runtime.h>
+
+/* Device Registers and Masks */
+#define TSL2583_CNTRL			0x00
+#define TSL2583_ALS_TIME		0X01
+#define TSL2583_INTERRUPT		0x02
+#define TSL2583_GAIN			0x07
+#define TSL2583_REVID			0x11
+#define TSL2583_CHIPID			0x12
+#define TSL2583_ALS_CHAN0LO		0x14
+#define TSL2583_ALS_CHAN0HI		0x15
+#define TSL2583_ALS_CHAN1LO		0x16
+#define TSL2583_ALS_CHAN1HI		0x17
+#define TSL2583_TMR_LO			0x18
+#define TSL2583_TMR_HI			0x19
+
+/* tsl2583 cmd reg masks */
+#define TSL2583_CMD_REG			0x80
+#define TSL2583_CMD_SPL_FN		0x60
+#define TSL2583_CMD_ALS_INT_CLR		0x01
+
+/* tsl2583 cntrl reg masks */
+#define TSL2583_CNTL_ADC_ENBL		0x02
+#define TSL2583_CNTL_PWR_OFF		0x00
+#define TSL2583_CNTL_PWR_ON		0x01
+
+/* tsl2583 status reg masks */
+#define TSL2583_STA_ADC_VALID		0x01
+#define TSL2583_STA_ADC_INTR		0x10
+
+/* Lux calculation constants */
+#define TSL2583_LUX_CALC_OVER_FLOW	65535
+
+#define TSL2583_INTERRUPT_DISABLED	0x00
+
+#define TSL2583_CHIP_ID			0x90
+#define TSL2583_CHIP_ID_MASK		0xf0
+
+#define TSL2583_POWER_OFF_DELAY_MS	2000
+
+/* Per-device data */
+struct tsl2583_als_info {
+	u16 als_ch0;
+	u16 als_ch1;
+	u16 lux;
+};
+
+struct tsl2583_lux {
+	unsigned int ratio;
+	unsigned int ch0;
+	unsigned int ch1;
+};
+
+static const struct tsl2583_lux tsl2583_default_lux[] = {
+	{  9830,  8520, 15729 },
+	{ 12452, 10807, 23344 },
+	{ 14746,  6383, 11705 },
+	{ 17695,  4063,  6554 },
+	{     0,     0,     0 }  /* Termination segment */
+};
+
+#define TSL2583_MAX_LUX_TABLE_ENTRIES 11
+
+struct tsl2583_settings {
+	int als_time;
+	int als_gain;
+	int als_gain_trim;
+	int als_cal_target;
+
+	/*
+	 * This structure is intentionally large to accommodate updates via
+	 * sysfs. Sized to 11 = max 10 segments + 1 termination segment.
+	 * Assumption is that one and only one type of glass used.
+	 */
+	struct tsl2583_lux als_device_lux[TSL2583_MAX_LUX_TABLE_ENTRIES];
+};
+
+struct tsl2583_chip {
+	struct mutex als_mutex;
+	struct i2c_client *client;
+	struct tsl2583_als_info als_cur_info;
+	struct tsl2583_settings als_settings;
+	int als_time_scale;
+	int als_saturation;
+};
+
+struct gainadj {
+	s16 ch0;
+	s16 ch1;
+	s16 mean;
+};
+
+/* Index = (0 - 3) Used to validate the gain selection index */
+static const struct gainadj gainadj[] = {
+	{ 1, 1, 1 },
+	{ 8, 8, 8 },
+	{ 16, 16, 16 },
+	{ 107, 115, 111 }
+};
+
+/*
+ * Provides initial operational parameter defaults.
+ * These defaults may be changed through the device's sysfs files.
+ */
+static void tsl2583_defaults(struct tsl2583_chip *chip)
+{
+	/*
+	 * The integration time must be a multiple of 50ms and within the
+	 * range [50, 600] ms.
+	 */
+	chip->als_settings.als_time = 100;
+
+	/*
+	 * This is an index into the gainadj table. Assume clear glass as the
+	 * default.
+	 */
+	chip->als_settings.als_gain = 0;
+
+	/* Default gain trim to account for aperture effects */
+	chip->als_settings.als_gain_trim = 1000;
+
+	/* Known external ALS reading used for calibration */
+	chip->als_settings.als_cal_target = 130;
+
+	/* Default lux table. */
+	memcpy(chip->als_settings.als_device_lux, tsl2583_default_lux,
+	       sizeof(tsl2583_default_lux));
+}
+
+/*
+ * Reads and calculates current lux value.
+ * The raw ch0 and ch1 values of the ambient light sensed in the last
+ * integration cycle are read from the device.
+ * Time scale factor array values are adjusted based on the integration time.
+ * The raw values are multiplied by a scale factor, and device gain is obtained
+ * using gain index. Limit checks are done next, then the ratio of a multiple
+ * of ch1 value, to the ch0 value, is calculated. The array als_device_lux[]
+ * declared above is then scanned to find the first ratio value that is just
+ * above the ratio we just calculated. The ch0 and ch1 multiplier constants in
+ * the array are then used along with the time scale factor array values, to
+ * calculate the lux.
+ */
+static int tsl2583_get_lux(struct iio_dev *indio_dev)
+{
+	u16 ch0, ch1; /* separated ch0/ch1 data from device */
+	u32 lux; /* raw lux calculated from device data */
+	u64 lux64;
+	u32 ratio;
+	u8 buf[5];
+	struct tsl2583_lux *p;
+	struct tsl2583_chip *chip = iio_priv(indio_dev);
+	int i, ret;
+
+	ret = i2c_smbus_read_byte_data(chip->client, TSL2583_CMD_REG);
+	if (ret < 0) {
+		dev_err(&chip->client->dev, "%s: failed to read CMD_REG register\n",
+			__func__);
+		goto done;
+	}
+
+	/* is data new & valid */
+	if (!(ret & TSL2583_STA_ADC_INTR)) {
+		dev_err(&chip->client->dev, "%s: data not valid; returning last value\n",
+			__func__);
+		ret = chip->als_cur_info.lux; /* return LAST VALUE */
+		goto done;
+	}
+
+	for (i = 0; i < 4; i++) {
+		int reg = TSL2583_CMD_REG | (TSL2583_ALS_CHAN0LO + i);
+
+		ret = i2c_smbus_read_byte_data(chip->client, reg);
+		if (ret < 0) {
+			dev_err(&chip->client->dev, "%s: failed to read register %x\n",
+				__func__, reg);
+			goto done;
+		}
+		buf[i] = ret;
+	}
+
+	/*
+	 * Clear the pending interrupt status bit on the chip to allow the next
+	 * integration cycle to start. This has to be done even though this
+	 * driver currently does not support interrupts.
+	 */
+	ret = i2c_smbus_write_byte(chip->client,
+				   (TSL2583_CMD_REG | TSL2583_CMD_SPL_FN |
+				    TSL2583_CMD_ALS_INT_CLR));
+	if (ret < 0) {
+		dev_err(&chip->client->dev, "%s: failed to clear the interrupt bit\n",
+			__func__);
+		goto done; /* have no data, so return failure */
+	}
+
+	/* extract ALS/lux data */
+	ch0 = le16_to_cpup((const __le16 *)&buf[0]);
+	ch1 = le16_to_cpup((const __le16 *)&buf[2]);
+
+	chip->als_cur_info.als_ch0 = ch0;
+	chip->als_cur_info.als_ch1 = ch1;
+
+	if ((ch0 >= chip->als_saturation) || (ch1 >= chip->als_saturation))
+		goto return_max;
+
+	if (!ch0) {
+		/*
+		 * The sensor appears to be in total darkness so set the
+		 * calculated lux to 0 and return early to avoid a division by
+		 * zero below when calculating the ratio.
+		 */
+		ret = 0;
+		chip->als_cur_info.lux = 0;
+		goto done;
+	}
+
+	/* calculate ratio */
+	ratio = (ch1 << 15) / ch0;
+
+	/* convert to unscaled lux using the pointer to the table */
+	for (p = (struct tsl2583_lux *)chip->als_settings.als_device_lux;
+	     p->ratio != 0 && p->ratio < ratio; p++)
+		;
+
+	if (p->ratio == 0) {
+		lux = 0;
+	} else {
+		u32 ch0lux, ch1lux;
+
+		ch0lux = ((ch0 * p->ch0) +
+			  (gainadj[chip->als_settings.als_gain].ch0 >> 1))
+			 / gainadj[chip->als_settings.als_gain].ch0;
+		ch1lux = ((ch1 * p->ch1) +
+			  (gainadj[chip->als_settings.als_gain].ch1 >> 1))
+			 / gainadj[chip->als_settings.als_gain].ch1;
+
+		/* note: lux is 31 bit max at this point */
+		if (ch1lux > ch0lux) {
+			dev_dbg(&chip->client->dev, "%s: No Data - Returning 0\n",
+				__func__);
+			ret = 0;
+			chip->als_cur_info.lux = 0;
+			goto done;
+		}
+
+		lux = ch0lux - ch1lux;
+	}
+
+	/* adjust for active time scale */
+	if (chip->als_time_scale == 0)
+		lux = 0;
+	else
+		lux = (lux + (chip->als_time_scale >> 1)) /
+			chip->als_time_scale;
+
+	/*
+	 * Adjust for active gain scale.
+	 * The tsl2583_default_lux tables above have a factor of 8192 built in,
+	 * so we need to shift right.
+	 * User-specified gain provides a multiplier.
+	 * Apply user-specified gain before shifting right to retain precision.
+	 * Use 64 bits to avoid overflow on multiplication.
+	 * Then go back to 32 bits before division to avoid using div_u64().
+	 */
+	lux64 = lux;
+	lux64 = lux64 * chip->als_settings.als_gain_trim;
+	lux64 >>= 13;
+	lux = lux64;
+	lux = (lux + 500) / 1000;
+
+	if (lux > TSL2583_LUX_CALC_OVER_FLOW) { /* check for overflow */
+return_max:
+		lux = TSL2583_LUX_CALC_OVER_FLOW;
+	}
+
+	/* Update the structure with the latest VALID lux. */
+	chip->als_cur_info.lux = lux;
+	ret = lux;
+
+done:
+	return ret;
+}
+
+/*
+ * Obtain single reading and calculate the als_gain_trim (later used
+ * to derive actual lux).
+ * Return updated gain_trim value.
+ */
+static int tsl2583_als_calibrate(struct iio_dev *indio_dev)
+{
+	struct tsl2583_chip *chip = iio_priv(indio_dev);
+	unsigned int gain_trim_val;
+	int ret;
+	int lux_val;
+
+	ret = i2c_smbus_read_byte_data(chip->client,
+				       TSL2583_CMD_REG | TSL2583_CNTRL);
+	if (ret < 0) {
+		dev_err(&chip->client->dev,
+			"%s: failed to read from the CNTRL register\n",
+			__func__);
+		return ret;
+	}
+
+	if ((ret & (TSL2583_CNTL_ADC_ENBL | TSL2583_CNTL_PWR_ON))
+			!= (TSL2583_CNTL_ADC_ENBL | TSL2583_CNTL_PWR_ON)) {
+		dev_err(&chip->client->dev,
+			"%s: Device is not powered on and/or ADC is not enabled\n",
+			__func__);
+		return -EINVAL;
+	} else if ((ret & TSL2583_STA_ADC_VALID) != TSL2583_STA_ADC_VALID) {
+		dev_err(&chip->client->dev,
+			"%s: The two ADC channels have not completed an integration cycle\n",
+			__func__);
+		return -ENODATA;
+	}
+
+	lux_val = tsl2583_get_lux(indio_dev);
+	if (lux_val < 0) {
+		dev_err(&chip->client->dev, "%s: failed to get lux\n",
+			__func__);
+		return lux_val;
+	}
+
+	gain_trim_val = (unsigned int)(((chip->als_settings.als_cal_target)
+			* chip->als_settings.als_gain_trim) / lux_val);
+	if ((gain_trim_val < 250) || (gain_trim_val > 4000)) {
+		dev_err(&chip->client->dev,
+			"%s: trim_val of %d is not within the range [250, 4000]\n",
+			__func__, gain_trim_val);
+		return -ENODATA;
+	}
+
+	chip->als_settings.als_gain_trim = (int)gain_trim_val;
+
+	return 0;
+}
+
+static int tsl2583_set_als_time(struct tsl2583_chip *chip)
+{
+	int als_count, als_time, ret;
+	u8 val;
+
+	/* determine als integration register */
+	als_count = (chip->als_settings.als_time * 100 + 135) / 270;
+	if (!als_count)
+		als_count = 1; /* ensure at least one cycle */
+
+	/* convert back to time (encompasses overrides) */
+	als_time = (als_count * 27 + 5) / 10;
+
+	val = 256 - als_count;
+	ret = i2c_smbus_write_byte_data(chip->client,
+					TSL2583_CMD_REG | TSL2583_ALS_TIME,
+					val);
+	if (ret < 0) {
+		dev_err(&chip->client->dev, "%s: failed to set the als time to %d\n",
+			__func__, val);
+		return ret;
+	}
+
+	/* set chip struct re scaling and saturation */
+	chip->als_saturation = als_count * 922; /* 90% of full scale */
+	chip->als_time_scale = (als_time + 25) / 50;
+
+	return ret;
+}
+
+static int tsl2583_set_als_gain(struct tsl2583_chip *chip)
+{
+	int ret;
+
+	/* Set the gain based on als_settings struct */
+	ret = i2c_smbus_write_byte_data(chip->client,
+					TSL2583_CMD_REG | TSL2583_GAIN,
+					chip->als_settings.als_gain);
+	if (ret < 0)
+		dev_err(&chip->client->dev,
+			"%s: failed to set the gain to %d\n", __func__,
+			chip->als_settings.als_gain);
+
+	return ret;
+}
+
+static int tsl2583_set_power_state(struct tsl2583_chip *chip, u8 state)
+{
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(chip->client,
+					TSL2583_CMD_REG | TSL2583_CNTRL, state);
+	if (ret < 0)
+		dev_err(&chip->client->dev,
+			"%s: failed to set the power state to %d\n", __func__,
+			state);
+
+	return ret;
+}
+
+/*
+ * Turn the device on.
+ * Configuration must be set before calling this function.
+ */
+static int tsl2583_chip_init_and_power_on(struct iio_dev *indio_dev)
+{
+	struct tsl2583_chip *chip = iio_priv(indio_dev);
+	int ret;
+
+	/* Power on the device; ADC off. */
+	ret = tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_ON);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_write_byte_data(chip->client,
+					TSL2583_CMD_REG | TSL2583_INTERRUPT,
+					TSL2583_INTERRUPT_DISABLED);
+	if (ret < 0) {
+		dev_err(&chip->client->dev,
+			"%s: failed to disable interrupts\n", __func__);
+		return ret;
+	}
+
+	ret = tsl2583_set_als_time(chip);
+	if (ret < 0)
+		return ret;
+
+	ret = tsl2583_set_als_gain(chip);
+	if (ret < 0)
+		return ret;
+
+	usleep_range(3000, 3500);
+
+	ret = tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_ON |
+					    TSL2583_CNTL_ADC_ENBL);
+	if (ret < 0)
+		return ret;
+
+	return ret;
+}
+
+/* Sysfs Interface Functions */
+
+static ssize_t in_illuminance_input_target_show(struct device *dev,
+						struct device_attribute *attr,
+						char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct tsl2583_chip *chip = iio_priv(indio_dev);
+	int ret;
+
+	mutex_lock(&chip->als_mutex);
+	ret = sprintf(buf, "%d\n", chip->als_settings.als_cal_target);
+	mutex_unlock(&chip->als_mutex);
+
+	return ret;
+}
+
+static ssize_t in_illuminance_input_target_store(struct device *dev,
+						 struct device_attribute *attr,
+						 const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct tsl2583_chip *chip = iio_priv(indio_dev);
+	int value;
+
+	if (kstrtoint(buf, 0, &value) || !value)
+		return -EINVAL;
+
+	mutex_lock(&chip->als_mutex);
+	chip->als_settings.als_cal_target = value;
+	mutex_unlock(&chip->als_mutex);
+
+	return len;
+}
+
+static ssize_t in_illuminance_calibrate_store(struct device *dev,
+					      struct device_attribute *attr,
+					      const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct tsl2583_chip *chip = iio_priv(indio_dev);
+	int value, ret;
+
+	if (kstrtoint(buf, 0, &value) || value != 1)
+		return -EINVAL;
+
+	mutex_lock(&chip->als_mutex);
+
+	ret = tsl2583_als_calibrate(indio_dev);
+	if (ret < 0)
+		goto done;
+
+	ret = len;
+done:
+	mutex_unlock(&chip->als_mutex);
+
+	return ret;
+}
+
+static ssize_t in_illuminance_lux_table_show(struct device *dev,
+					     struct device_attribute *attr,
+					     char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct tsl2583_chip *chip = iio_priv(indio_dev);
+	unsigned int i;
+	int offset = 0;
+
+	for (i = 0; i < ARRAY_SIZE(chip->als_settings.als_device_lux); i++) {
+		offset += sprintf(buf + offset, "%u,%u,%u,",
+				  chip->als_settings.als_device_lux[i].ratio,
+				  chip->als_settings.als_device_lux[i].ch0,
+				  chip->als_settings.als_device_lux[i].ch1);
+		if (chip->als_settings.als_device_lux[i].ratio == 0) {
+			/*
+			 * We just printed the first "0" entry.
+			 * Now get rid of the extra "," and break.
+			 */
+			offset--;
+			break;
+		}
+	}
+
+	offset += sprintf(buf + offset, "\n");
+
+	return offset;
+}
+
+static ssize_t in_illuminance_lux_table_store(struct device *dev,
+					      struct device_attribute *attr,
+					      const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct tsl2583_chip *chip = iio_priv(indio_dev);
+	const unsigned int max_ints = TSL2583_MAX_LUX_TABLE_ENTRIES * 3;
+	int value[TSL2583_MAX_LUX_TABLE_ENTRIES * 3 + 1];
+	int ret = -EINVAL;
+	unsigned int n;
+
+	mutex_lock(&chip->als_mutex);
+
+	get_options(buf, ARRAY_SIZE(value), value);
+
+	/*
+	 * We now have an array of ints starting at value[1], and
+	 * enumerated by value[0].
+	 * We expect each group of three ints is one table entry,
+	 * and the last table entry is all 0.
+	 */
+	n = value[0];
+	if ((n % 3) || n < 6 || n > max_ints) {
+		dev_err(dev,
+			"%s: The number of entries in the lux table must be a multiple of 3 and within the range [6, %d]\n",
+			__func__, max_ints);
+		goto done;
+	}
+	if ((value[n - 2] | value[n - 1] | value[n]) != 0) {
+		dev_err(dev, "%s: The last 3 entries in the lux table must be zeros.\n",
+			__func__);
+		goto done;
+	}
+
+	memcpy(chip->als_settings.als_device_lux, &value[1],
+	       value[0] * sizeof(value[1]));
+
+	ret = len;
+
+done:
+	mutex_unlock(&chip->als_mutex);
+
+	return ret;
+}
+
+static IIO_CONST_ATTR(in_illuminance_calibscale_available, "1 8 16 111");
+static IIO_CONST_ATTR(in_illuminance_integration_time_available,
+		      "0.050 0.100 0.150 0.200 0.250 0.300 0.350 0.400 0.450 0.500 0.550 0.600 0.650");
+static IIO_DEVICE_ATTR_RW(in_illuminance_input_target, 0);
+static IIO_DEVICE_ATTR_WO(in_illuminance_calibrate, 0);
+static IIO_DEVICE_ATTR_RW(in_illuminance_lux_table, 0);
+
+static struct attribute *sysfs_attrs_ctrl[] = {
+	&iio_const_attr_in_illuminance_calibscale_available.dev_attr.attr,
+	&iio_const_attr_in_illuminance_integration_time_available.dev_attr.attr,
+	&iio_dev_attr_in_illuminance_input_target.dev_attr.attr,
+	&iio_dev_attr_in_illuminance_calibrate.dev_attr.attr,
+	&iio_dev_attr_in_illuminance_lux_table.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group tsl2583_attribute_group = {
+	.attrs = sysfs_attrs_ctrl,
+};
+
+static const struct iio_chan_spec tsl2583_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.modified = 1,
+		.channel2 = IIO_MOD_LIGHT_IR,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+	},
+	{
+		.type = IIO_LIGHT,
+		.modified = 1,
+		.channel2 = IIO_MOD_LIGHT_BOTH,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+	},
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
+				      BIT(IIO_CHAN_INFO_CALIBBIAS) |
+				      BIT(IIO_CHAN_INFO_CALIBSCALE) |
+				      BIT(IIO_CHAN_INFO_INT_TIME),
+	},
+};
+
+static int tsl2583_set_pm_runtime_busy(struct tsl2583_chip *chip, bool on)
+{
+	int ret;
+
+	if (on) {
+		ret = pm_runtime_get_sync(&chip->client->dev);
+		if (ret < 0)
+			pm_runtime_put_noidle(&chip->client->dev);
+	} else {
+		pm_runtime_mark_last_busy(&chip->client->dev);
+		ret = pm_runtime_put_autosuspend(&chip->client->dev);
+	}
+
+	return ret;
+}
+
+static int tsl2583_read_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct tsl2583_chip *chip = iio_priv(indio_dev);
+	int ret, pm_ret;
+
+	ret = tsl2583_set_pm_runtime_busy(chip, true);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&chip->als_mutex);
+
+	ret = -EINVAL;
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		if (chan->type == IIO_LIGHT) {
+			ret = tsl2583_get_lux(indio_dev);
+			if (ret < 0)
+				goto read_done;
+
+			/*
+			 * From page 20 of the TSL2581, TSL2583 data
+			 * sheet (TAOS134 − MARCH 2011):
+			 *
+			 * One of the photodiodes (channel 0) is
+			 * sensitive to both visible and infrared light,
+			 * while the second photodiode (channel 1) is
+			 * sensitive primarily to infrared light.
+			 */
+			if (chan->channel2 == IIO_MOD_LIGHT_BOTH)
+				*val = chip->als_cur_info.als_ch0;
+			else
+				*val = chip->als_cur_info.als_ch1;
+
+			ret = IIO_VAL_INT;
+		}
+		break;
+	case IIO_CHAN_INFO_PROCESSED:
+		if (chan->type == IIO_LIGHT) {
+			ret = tsl2583_get_lux(indio_dev);
+			if (ret < 0)
+				goto read_done;
+
+			*val = ret;
+			ret = IIO_VAL_INT;
+		}
+		break;
+	case IIO_CHAN_INFO_CALIBBIAS:
+		if (chan->type == IIO_LIGHT) {
+			*val = chip->als_settings.als_gain_trim;
+			ret = IIO_VAL_INT;
+		}
+		break;
+	case IIO_CHAN_INFO_CALIBSCALE:
+		if (chan->type == IIO_LIGHT) {
+			*val = gainadj[chip->als_settings.als_gain].mean;
+			ret = IIO_VAL_INT;
+		}
+		break;
+	case IIO_CHAN_INFO_INT_TIME:
+		if (chan->type == IIO_LIGHT) {
+			*val = 0;
+			*val2 = chip->als_settings.als_time;
+			ret = IIO_VAL_INT_PLUS_MICRO;
+		}
+		break;
+	default:
+		break;
+	}
+
+read_done:
+	mutex_unlock(&chip->als_mutex);
+
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Preserve the ret variable if the call to
+	 * tsl2583_set_pm_runtime_busy() is successful so the reading
+	 * (if applicable) is returned to user space.
+	 */
+	pm_ret = tsl2583_set_pm_runtime_busy(chip, false);
+	if (pm_ret < 0)
+		return pm_ret;
+
+	return ret;
+}
+
+static int tsl2583_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int val, int val2, long mask)
+{
+	struct tsl2583_chip *chip = iio_priv(indio_dev);
+	int ret;
+
+	ret = tsl2583_set_pm_runtime_busy(chip, true);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&chip->als_mutex);
+
+	ret = -EINVAL;
+	switch (mask) {
+	case IIO_CHAN_INFO_CALIBBIAS:
+		if (chan->type == IIO_LIGHT) {
+			chip->als_settings.als_gain_trim = val;
+			ret = 0;
+		}
+		break;
+	case IIO_CHAN_INFO_CALIBSCALE:
+		if (chan->type == IIO_LIGHT) {
+			unsigned int i;
+
+			for (i = 0; i < ARRAY_SIZE(gainadj); i++) {
+				if (gainadj[i].mean == val) {
+					chip->als_settings.als_gain = i;
+					ret = tsl2583_set_als_gain(chip);
+					break;
+				}
+			}
+		}
+		break;
+	case IIO_CHAN_INFO_INT_TIME:
+		if (chan->type == IIO_LIGHT && !val && val2 >= 50 &&
+		    val2 <= 650 && !(val2 % 50)) {
+			chip->als_settings.als_time = val2;
+			ret = tsl2583_set_als_time(chip);
+		}
+		break;
+	default:
+		break;
+	}
+
+	mutex_unlock(&chip->als_mutex);
+
+	if (ret < 0)
+		return ret;
+
+	ret = tsl2583_set_pm_runtime_busy(chip, false);
+	if (ret < 0)
+		return ret;
+
+	return ret;
+}
+
+static const struct iio_info tsl2583_info = {
+	.attrs = &tsl2583_attribute_group,
+	.read_raw = tsl2583_read_raw,
+	.write_raw = tsl2583_write_raw,
+};
+
+static int tsl2583_probe(struct i2c_client *clientp,
+			 const struct i2c_device_id *idp)
+{
+	int ret;
+	struct tsl2583_chip *chip;
+	struct iio_dev *indio_dev;
+
+	if (!i2c_check_functionality(clientp->adapter,
+				     I2C_FUNC_SMBUS_BYTE_DATA)) {
+		dev_err(&clientp->dev, "%s: i2c smbus byte data functionality is unsupported\n",
+			__func__);
+		return -EOPNOTSUPP;
+	}
+
+	indio_dev = devm_iio_device_alloc(&clientp->dev, sizeof(*chip));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	chip = iio_priv(indio_dev);
+	chip->client = clientp;
+	i2c_set_clientdata(clientp, indio_dev);
+
+	mutex_init(&chip->als_mutex);
+
+	ret = i2c_smbus_read_byte_data(clientp,
+				       TSL2583_CMD_REG | TSL2583_CHIPID);
+	if (ret < 0) {
+		dev_err(&clientp->dev,
+			"%s: failed to read the chip ID register\n", __func__);
+		return ret;
+	}
+
+	if ((ret & TSL2583_CHIP_ID_MASK) != TSL2583_CHIP_ID) {
+		dev_err(&clientp->dev, "%s: received an unknown chip ID %x\n",
+			__func__, ret);
+		return -EINVAL;
+	}
+
+	indio_dev->info = &tsl2583_info;
+	indio_dev->channels = tsl2583_channels;
+	indio_dev->num_channels = ARRAY_SIZE(tsl2583_channels);
+	indio_dev->dev.parent = &clientp->dev;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->name = chip->client->name;
+
+	pm_runtime_enable(&clientp->dev);
+	pm_runtime_set_autosuspend_delay(&clientp->dev,
+					 TSL2583_POWER_OFF_DELAY_MS);
+	pm_runtime_use_autosuspend(&clientp->dev);
+
+	ret = devm_iio_device_register(indio_dev->dev.parent, indio_dev);
+	if (ret) {
+		dev_err(&clientp->dev, "%s: iio registration failed\n",
+			__func__);
+		return ret;
+	}
+
+	/* Load up the V2 defaults (these are hard coded defaults for now) */
+	tsl2583_defaults(chip);
+
+	dev_info(&clientp->dev, "Light sensor found.\n");
+
+	return 0;
+}
+
+static int tsl2583_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+	struct tsl2583_chip *chip = iio_priv(indio_dev);
+
+	iio_device_unregister(indio_dev);
+
+	pm_runtime_disable(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+	pm_runtime_put_noidle(&client->dev);
+
+	return tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_OFF);
+}
+
+static int __maybe_unused tsl2583_suspend(struct device *dev)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+	struct tsl2583_chip *chip = iio_priv(indio_dev);
+	int ret;
+
+	mutex_lock(&chip->als_mutex);
+
+	ret = tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_OFF);
+
+	mutex_unlock(&chip->als_mutex);
+
+	return ret;
+}
+
+static int __maybe_unused tsl2583_resume(struct device *dev)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+	struct tsl2583_chip *chip = iio_priv(indio_dev);
+	int ret;
+
+	mutex_lock(&chip->als_mutex);
+
+	ret = tsl2583_chip_init_and_power_on(indio_dev);
+
+	mutex_unlock(&chip->als_mutex);
+
+	return ret;
+}
+
+static const struct dev_pm_ops tsl2583_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+				pm_runtime_force_resume)
+	SET_RUNTIME_PM_OPS(tsl2583_suspend, tsl2583_resume, NULL)
+};
+
+static const struct i2c_device_id tsl2583_idtable[] = {
+	{ "tsl2580", 0 },
+	{ "tsl2581", 1 },
+	{ "tsl2583", 2 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, tsl2583_idtable);
+
+static const struct of_device_id tsl2583_of_match[] = {
+	{ .compatible = "amstaos,tsl2580", },
+	{ .compatible = "amstaos,tsl2581", },
+	{ .compatible = "amstaos,tsl2583", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, tsl2583_of_match);
+
+/* Driver definition */
+static struct i2c_driver tsl2583_driver = {
+	.driver = {
+		.name = "tsl2583",
+		.pm = &tsl2583_pm_ops,
+		.of_match_table = tsl2583_of_match,
+	},
+	.id_table = tsl2583_idtable,
+	.probe = tsl2583_probe,
+	.remove = tsl2583_remove,
+};
+module_i2c_driver(tsl2583_driver);
+
+MODULE_AUTHOR("J. August Brenner <jbrenner@taosinc.com>");
+MODULE_AUTHOR("Brian Masney <masneyb@onstation.org>");
+MODULE_DESCRIPTION("TAOS tsl2583 ambient light sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/tsl2772.c b/drivers/iio/light/tsl2772.c
new file mode 100644
index 0000000..df5b2a0
--- /dev/null
+++ b/drivers/iio/light/tsl2772.c
@@ -0,0 +1,1802 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Device driver for monitoring ambient light intensity in (lux) and proximity
+ * detection (prox) within the TAOS TSL2571, TSL2671, TMD2671, TSL2771, TMD2771,
+ * TSL2572, TSL2672, TMD2672, TSL2772, and TMD2772 devices.
+ *
+ * Copyright (c) 2012, TAOS Corporation.
+ * Copyright (c) 2017-2018 Brian Masney <masneyb@onstation.org>
+ */
+
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/platform_data/tsl2772.h>
+
+/* Cal defs */
+#define PROX_STAT_CAL			0
+#define PROX_STAT_SAMP			1
+#define MAX_SAMPLES_CAL			200
+
+/* TSL2772 Device ID */
+#define TRITON_ID			0x00
+#define SWORDFISH_ID			0x30
+#define HALIBUT_ID			0x20
+
+/* Lux calculation constants */
+#define TSL2772_LUX_CALC_OVER_FLOW	65535
+
+/*
+ * TAOS Register definitions - Note: depending on device, some of these register
+ * are not used and the register address is benign.
+ */
+
+/* Register offsets */
+#define TSL2772_MAX_CONFIG_REG		16
+
+/* Device Registers and Masks */
+#define TSL2772_CNTRL			0x00
+#define TSL2772_ALS_TIME		0X01
+#define TSL2772_PRX_TIME		0x02
+#define TSL2772_WAIT_TIME		0x03
+#define TSL2772_ALS_MINTHRESHLO		0X04
+#define TSL2772_ALS_MINTHRESHHI		0X05
+#define TSL2772_ALS_MAXTHRESHLO		0X06
+#define TSL2772_ALS_MAXTHRESHHI		0X07
+#define TSL2772_PRX_MINTHRESHLO		0X08
+#define TSL2772_PRX_MINTHRESHHI		0X09
+#define TSL2772_PRX_MAXTHRESHLO		0X0A
+#define TSL2772_PRX_MAXTHRESHHI		0X0B
+#define TSL2772_PERSISTENCE		0x0C
+#define TSL2772_ALS_PRX_CONFIG		0x0D
+#define TSL2772_PRX_COUNT		0x0E
+#define TSL2772_GAIN			0x0F
+#define TSL2772_NOTUSED			0x10
+#define TSL2772_REVID			0x11
+#define TSL2772_CHIPID			0x12
+#define TSL2772_STATUS			0x13
+#define TSL2772_ALS_CHAN0LO		0x14
+#define TSL2772_ALS_CHAN0HI		0x15
+#define TSL2772_ALS_CHAN1LO		0x16
+#define TSL2772_ALS_CHAN1HI		0x17
+#define TSL2772_PRX_LO			0x18
+#define TSL2772_PRX_HI			0x19
+
+/* tsl2772 cmd reg masks */
+#define TSL2772_CMD_REG			0x80
+#define TSL2772_CMD_SPL_FN		0x60
+#define TSL2772_CMD_REPEAT_PROTO	0x00
+#define TSL2772_CMD_AUTOINC_PROTO	0x20
+
+#define TSL2772_CMD_PROX_INT_CLR	0X05
+#define TSL2772_CMD_ALS_INT_CLR		0x06
+#define TSL2772_CMD_PROXALS_INT_CLR	0X07
+
+/* tsl2772 cntrl reg masks */
+#define TSL2772_CNTL_ADC_ENBL		0x02
+#define TSL2772_CNTL_PWR_ON		0x01
+
+/* tsl2772 status reg masks */
+#define TSL2772_STA_ADC_VALID		0x01
+#define TSL2772_STA_PRX_VALID		0x02
+#define TSL2772_STA_ADC_PRX_VALID	(TSL2772_STA_ADC_VALID | \
+					 TSL2772_STA_PRX_VALID)
+#define TSL2772_STA_ALS_INTR		0x10
+#define TSL2772_STA_PRX_INTR		0x20
+
+/* tsl2772 cntrl reg masks */
+#define TSL2772_CNTL_REG_CLEAR		0x00
+#define TSL2772_CNTL_PROX_INT_ENBL	0X20
+#define TSL2772_CNTL_ALS_INT_ENBL	0X10
+#define TSL2772_CNTL_WAIT_TMR_ENBL	0X08
+#define TSL2772_CNTL_PROX_DET_ENBL	0X04
+#define TSL2772_CNTL_PWRON		0x01
+#define TSL2772_CNTL_ALSPON_ENBL	0x03
+#define TSL2772_CNTL_INTALSPON_ENBL	0x13
+#define TSL2772_CNTL_PROXPON_ENBL	0x0F
+#define TSL2772_CNTL_INTPROXPON_ENBL	0x2F
+
+#define TSL2772_ALS_GAIN_TRIM_MIN	250
+#define TSL2772_ALS_GAIN_TRIM_MAX	4000
+
+/* Device family members */
+enum {
+	tsl2571,
+	tsl2671,
+	tmd2671,
+	tsl2771,
+	tmd2771,
+	tsl2572,
+	tsl2672,
+	tmd2672,
+	tsl2772,
+	tmd2772
+};
+
+enum {
+	TSL2772_CHIP_UNKNOWN = 0,
+	TSL2772_CHIP_WORKING = 1,
+	TSL2772_CHIP_SUSPENDED = 2
+};
+
+/* Per-device data */
+struct tsl2772_als_info {
+	u16 als_ch0;
+	u16 als_ch1;
+	u16 lux;
+};
+
+struct tsl2772_chip_info {
+	int chan_table_elements;
+	struct iio_chan_spec channel_with_events[4];
+	struct iio_chan_spec channel_without_events[4];
+	const struct iio_info *info;
+};
+
+struct tsl2772_chip {
+	kernel_ulong_t id;
+	struct mutex prox_mutex;
+	struct mutex als_mutex;
+	struct i2c_client *client;
+	u16 prox_data;
+	struct tsl2772_als_info als_cur_info;
+	struct tsl2772_settings settings;
+	struct tsl2772_platform_data *pdata;
+	int als_gain_time_scale;
+	int als_saturation;
+	int tsl2772_chip_status;
+	u8 tsl2772_config[TSL2772_MAX_CONFIG_REG];
+	const struct tsl2772_chip_info	*chip_info;
+	const struct iio_info *info;
+	s64 event_timestamp;
+	/*
+	 * This structure is intentionally large to accommodate
+	 * updates via sysfs.
+	 * Sized to 9 = max 8 segments + 1 termination segment
+	 */
+	struct tsl2772_lux tsl2772_device_lux[TSL2772_MAX_LUX_TABLE_SIZE];
+};
+
+/*
+ * Different devices require different coefficents, and these numbers were
+ * derived from the 'Lux Equation' section of the various device datasheets.
+ * All of these coefficients assume a Glass Attenuation (GA) factor of 1.
+ * The coefficients are multiplied by 1000 to avoid floating point operations.
+ * The two rows in each table correspond to the Lux1 and Lux2 equations from
+ * the datasheets.
+ */
+static const struct tsl2772_lux tsl2x71_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = {
+	{ 53000, 106000 },
+	{ 31800,  53000 },
+	{ 0,          0 },
+};
+
+static const struct tsl2772_lux tmd2x71_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = {
+	{ 24000,  48000 },
+	{ 14400,  24000 },
+	{ 0,          0 },
+};
+
+static const struct tsl2772_lux tsl2x72_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = {
+	{ 60000, 112200 },
+	{ 37800,  60000 },
+	{     0,      0 },
+};
+
+static const struct tsl2772_lux tmd2x72_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = {
+	{ 20000,  35000 },
+	{ 12600,  20000 },
+	{     0,      0 },
+};
+
+static const struct tsl2772_lux *tsl2772_default_lux_table_group[] = {
+	[tsl2571] = tsl2x71_lux_table,
+	[tsl2671] = tsl2x71_lux_table,
+	[tmd2671] = tmd2x71_lux_table,
+	[tsl2771] = tsl2x71_lux_table,
+	[tmd2771] = tmd2x71_lux_table,
+	[tsl2572] = tsl2x72_lux_table,
+	[tsl2672] = tsl2x72_lux_table,
+	[tmd2672] = tmd2x72_lux_table,
+	[tsl2772] = tsl2x72_lux_table,
+	[tmd2772] = tmd2x72_lux_table,
+};
+
+static const struct tsl2772_settings tsl2772_default_settings = {
+	.als_time = 255, /* 2.72 / 2.73 ms */
+	.als_gain = 0,
+	.prox_time = 255, /* 2.72 / 2.73 ms */
+	.prox_gain = 0,
+	.wait_time = 255,
+	.als_prox_config = 0,
+	.als_gain_trim = 1000,
+	.als_cal_target = 150,
+	.als_persistence = 1,
+	.als_interrupt_en = false,
+	.als_thresh_low = 200,
+	.als_thresh_high = 256,
+	.prox_persistence = 1,
+	.prox_interrupt_en = false,
+	.prox_thres_low  = 0,
+	.prox_thres_high = 512,
+	.prox_max_samples_cal = 30,
+	.prox_pulse_count = 8,
+	.prox_diode = TSL2772_DIODE1,
+	.prox_power = TSL2772_100_mA
+};
+
+static const s16 tsl2772_als_gain[] = {
+	1,
+	8,
+	16,
+	120
+};
+
+static const s16 tsl2772_prox_gain[] = {
+	1,
+	2,
+	4,
+	8
+};
+
+static const int tsl2772_int_time_avail[][6] = {
+	[tsl2571] = { 0, 2720, 0, 2720, 0, 696000 },
+	[tsl2671] = { 0, 2720, 0, 2720, 0, 696000 },
+	[tmd2671] = { 0, 2720, 0, 2720, 0, 696000 },
+	[tsl2771] = { 0, 2720, 0, 2720, 0, 696000 },
+	[tmd2771] = { 0, 2720, 0, 2720, 0, 696000 },
+	[tsl2572] = { 0, 2730, 0, 2730, 0, 699000 },
+	[tsl2672] = { 0, 2730, 0, 2730, 0, 699000 },
+	[tmd2672] = { 0, 2730, 0, 2730, 0, 699000 },
+	[tsl2772] = { 0, 2730, 0, 2730, 0, 699000 },
+	[tmd2772] = { 0, 2730, 0, 2730, 0, 699000 },
+};
+
+static int tsl2772_int_calibscale_avail[] = { 1, 8, 16, 120 };
+
+static int tsl2772_prox_calibscale_avail[] = { 1, 2, 4, 8 };
+
+/* Channel variations */
+enum {
+	ALS,
+	PRX,
+	ALSPRX,
+	PRX2,
+	ALSPRX2,
+};
+
+static const u8 device_channel_config[] = {
+	[tsl2571] = ALS,
+	[tsl2671] = PRX,
+	[tmd2671] = PRX,
+	[tsl2771] = ALSPRX,
+	[tmd2771] = ALSPRX,
+	[tsl2572] = ALS,
+	[tsl2672] = PRX2,
+	[tmd2672] = PRX2,
+	[tsl2772] = ALSPRX2,
+	[tmd2772] = ALSPRX2
+};
+
+static int tsl2772_read_status(struct tsl2772_chip *chip)
+{
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(chip->client,
+				       TSL2772_CMD_REG | TSL2772_STATUS);
+	if (ret < 0)
+		dev_err(&chip->client->dev,
+			"%s: failed to read STATUS register: %d\n", __func__,
+			ret);
+
+	return ret;
+}
+
+static int tsl2772_write_control_reg(struct tsl2772_chip *chip, u8 data)
+{
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(chip->client,
+					TSL2772_CMD_REG | TSL2772_CNTRL, data);
+	if (ret < 0) {
+		dev_err(&chip->client->dev,
+			"%s: failed to write to control register %x: %d\n",
+			__func__, data, ret);
+	}
+
+	return ret;
+}
+
+static int tsl2772_read_autoinc_regs(struct tsl2772_chip *chip, int lower_reg,
+				     int upper_reg)
+{
+	u8 buf[2];
+	int ret;
+
+	ret = i2c_smbus_write_byte(chip->client,
+				   TSL2772_CMD_REG | TSL2772_CMD_AUTOINC_PROTO |
+				   lower_reg);
+	if (ret < 0) {
+		dev_err(&chip->client->dev,
+			"%s: failed to enable auto increment protocol: %d\n",
+			__func__, ret);
+		return ret;
+	}
+
+	ret = i2c_smbus_read_byte_data(chip->client,
+				       TSL2772_CMD_REG | lower_reg);
+	if (ret < 0) {
+		dev_err(&chip->client->dev,
+			"%s: failed to read from register %x: %d\n", __func__,
+			lower_reg, ret);
+		return ret;
+	}
+	buf[0] = ret;
+
+	ret = i2c_smbus_read_byte_data(chip->client,
+				       TSL2772_CMD_REG | upper_reg);
+	if (ret < 0) {
+		dev_err(&chip->client->dev,
+			"%s: failed to read from register %x: %d\n", __func__,
+			upper_reg, ret);
+		return ret;
+	}
+	buf[1] = ret;
+
+	ret = i2c_smbus_write_byte(chip->client,
+				   TSL2772_CMD_REG | TSL2772_CMD_REPEAT_PROTO |
+				   lower_reg);
+	if (ret < 0) {
+		dev_err(&chip->client->dev,
+			"%s: failed to enable repeated byte protocol: %d\n",
+			__func__, ret);
+		return ret;
+	}
+
+	return le16_to_cpup((const __le16 *)&buf[0]);
+}
+
+/**
+ * tsl2772_get_lux() - Reads and calculates current lux value.
+ * @indio_dev:	pointer to IIO device
+ *
+ * The raw ch0 and ch1 values of the ambient light sensed in the last
+ * integration cycle are read from the device. The raw values are multiplied
+ * by a device-specific scale factor, and divided by the integration time and
+ * device gain. The code supports multiple lux equations through the lux table
+ * coefficients. A lux gain trim is applied to each lux equation, and then the
+ * maximum lux within the interval 0..65535 is selected.
+ */
+static int tsl2772_get_lux(struct iio_dev *indio_dev)
+{
+	struct tsl2772_chip *chip = iio_priv(indio_dev);
+	struct tsl2772_lux *p;
+	int max_lux, ret;
+	bool overflow;
+
+	mutex_lock(&chip->als_mutex);
+
+	if (chip->tsl2772_chip_status != TSL2772_CHIP_WORKING) {
+		dev_err(&chip->client->dev, "%s: device is not enabled\n",
+			__func__);
+		ret = -EBUSY;
+		goto out_unlock;
+	}
+
+	ret = tsl2772_read_status(chip);
+	if (ret < 0)
+		goto out_unlock;
+
+	if (!(ret & TSL2772_STA_ADC_VALID)) {
+		dev_err(&chip->client->dev,
+			"%s: data not valid yet\n", __func__);
+		ret = chip->als_cur_info.lux; /* return LAST VALUE */
+		goto out_unlock;
+	}
+
+	ret = tsl2772_read_autoinc_regs(chip, TSL2772_ALS_CHAN0LO,
+					TSL2772_ALS_CHAN0HI);
+	if (ret < 0)
+		goto out_unlock;
+	chip->als_cur_info.als_ch0 = ret;
+
+	ret = tsl2772_read_autoinc_regs(chip, TSL2772_ALS_CHAN1LO,
+					TSL2772_ALS_CHAN1HI);
+	if (ret < 0)
+		goto out_unlock;
+	chip->als_cur_info.als_ch1 = ret;
+
+	if (chip->als_cur_info.als_ch0 >= chip->als_saturation) {
+		max_lux = TSL2772_LUX_CALC_OVER_FLOW;
+		goto update_struct_with_max_lux;
+	}
+
+	if (!chip->als_cur_info.als_ch0) {
+		/* have no data, so return LAST VALUE */
+		ret = chip->als_cur_info.lux;
+		goto out_unlock;
+	}
+
+	max_lux = 0;
+	overflow = false;
+	for (p = (struct tsl2772_lux *)chip->tsl2772_device_lux; p->ch0 != 0;
+	     p++) {
+		int lux;
+
+		lux = ((chip->als_cur_info.als_ch0 * p->ch0) -
+		       (chip->als_cur_info.als_ch1 * p->ch1)) /
+			chip->als_gain_time_scale;
+
+		/*
+		 * The als_gain_trim can have a value within the range 250..4000
+		 * and is a multiplier for the lux. A trim of 1000 makes no
+		 * changes to the lux, less than 1000 scales it down, and
+		 * greater than 1000 scales it up.
+		 */
+		lux = (lux * chip->settings.als_gain_trim) / 1000;
+
+		if (lux > TSL2772_LUX_CALC_OVER_FLOW) {
+			overflow = true;
+			continue;
+		}
+
+		max_lux = max(max_lux, lux);
+	}
+
+	if (overflow && max_lux == 0)
+		max_lux = TSL2772_LUX_CALC_OVER_FLOW;
+
+update_struct_with_max_lux:
+	chip->als_cur_info.lux = max_lux;
+	ret = max_lux;
+
+out_unlock:
+	mutex_unlock(&chip->als_mutex);
+
+	return ret;
+}
+
+/**
+ * tsl2772_get_prox() - Reads proximity data registers and updates
+ *                      chip->prox_data.
+ *
+ * @indio_dev:	pointer to IIO device
+ */
+static int tsl2772_get_prox(struct iio_dev *indio_dev)
+{
+	struct tsl2772_chip *chip = iio_priv(indio_dev);
+	int ret;
+
+	mutex_lock(&chip->prox_mutex);
+
+	ret = tsl2772_read_status(chip);
+	if (ret < 0)
+		goto prox_poll_err;
+
+	switch (chip->id) {
+	case tsl2571:
+	case tsl2671:
+	case tmd2671:
+	case tsl2771:
+	case tmd2771:
+		if (!(ret & TSL2772_STA_ADC_VALID)) {
+			ret = -EINVAL;
+			goto prox_poll_err;
+		}
+		break;
+	case tsl2572:
+	case tsl2672:
+	case tmd2672:
+	case tsl2772:
+	case tmd2772:
+		if (!(ret & TSL2772_STA_PRX_VALID)) {
+			ret = -EINVAL;
+			goto prox_poll_err;
+		}
+		break;
+	}
+
+	ret = tsl2772_read_autoinc_regs(chip, TSL2772_PRX_LO, TSL2772_PRX_HI);
+	if (ret < 0)
+		goto prox_poll_err;
+	chip->prox_data = ret;
+
+prox_poll_err:
+	mutex_unlock(&chip->prox_mutex);
+
+	return ret;
+}
+
+/**
+ * tsl2772_defaults() - Populates the device nominal operating parameters
+ *                      with those provided by a 'platform' data struct or
+ *                      with prefined defaults.
+ *
+ * @chip:               pointer to device structure.
+ */
+static void tsl2772_defaults(struct tsl2772_chip *chip)
+{
+	/* If Operational settings defined elsewhere.. */
+	if (chip->pdata && chip->pdata->platform_default_settings)
+		memcpy(&chip->settings, chip->pdata->platform_default_settings,
+		       sizeof(tsl2772_default_settings));
+	else
+		memcpy(&chip->settings, &tsl2772_default_settings,
+		       sizeof(tsl2772_default_settings));
+
+	/* Load up the proper lux table. */
+	if (chip->pdata && chip->pdata->platform_lux_table[0].ch0 != 0)
+		memcpy(chip->tsl2772_device_lux,
+		       chip->pdata->platform_lux_table,
+		       sizeof(chip->pdata->platform_lux_table));
+	else
+		memcpy(chip->tsl2772_device_lux,
+		       tsl2772_default_lux_table_group[chip->id],
+		       TSL2772_DEFAULT_TABLE_BYTES);
+}
+
+/**
+ * tsl2772_als_calibrate() -	Obtain single reading and calculate
+ *                              the als_gain_trim.
+ *
+ * @indio_dev:	pointer to IIO device
+ */
+static int tsl2772_als_calibrate(struct iio_dev *indio_dev)
+{
+	struct tsl2772_chip *chip = iio_priv(indio_dev);
+	int ret, lux_val;
+
+	ret = i2c_smbus_read_byte_data(chip->client,
+				       TSL2772_CMD_REG | TSL2772_CNTRL);
+	if (ret < 0) {
+		dev_err(&chip->client->dev,
+			"%s: failed to read from the CNTRL register\n",
+			__func__);
+		return ret;
+	}
+
+	if ((ret & (TSL2772_CNTL_ADC_ENBL | TSL2772_CNTL_PWR_ON))
+			!= (TSL2772_CNTL_ADC_ENBL | TSL2772_CNTL_PWR_ON)) {
+		dev_err(&chip->client->dev,
+			"%s: Device is not powered on and/or ADC is not enabled\n",
+			__func__);
+		return -EINVAL;
+	} else if ((ret & TSL2772_STA_ADC_VALID) != TSL2772_STA_ADC_VALID) {
+		dev_err(&chip->client->dev,
+			"%s: The two ADC channels have not completed an integration cycle\n",
+			__func__);
+		return -ENODATA;
+	}
+
+	lux_val = tsl2772_get_lux(indio_dev);
+	if (lux_val < 0) {
+		dev_err(&chip->client->dev,
+			"%s: failed to get lux\n", __func__);
+		return lux_val;
+	}
+	if (lux_val == 0)
+		return -ERANGE;
+
+	ret = (chip->settings.als_cal_target * chip->settings.als_gain_trim) /
+			lux_val;
+	if (ret < TSL2772_ALS_GAIN_TRIM_MIN || ret > TSL2772_ALS_GAIN_TRIM_MAX)
+		return -ERANGE;
+
+	chip->settings.als_gain_trim = ret;
+
+	return ret;
+}
+
+static int tsl2772_chip_on(struct iio_dev *indio_dev)
+{
+	struct tsl2772_chip *chip = iio_priv(indio_dev);
+	int ret, i, als_count, als_time_us;
+	u8 *dev_reg, reg_val;
+
+	/* Non calculated parameters */
+	chip->tsl2772_config[TSL2772_ALS_TIME] = chip->settings.als_time;
+	chip->tsl2772_config[TSL2772_PRX_TIME] = chip->settings.prox_time;
+	chip->tsl2772_config[TSL2772_WAIT_TIME] = chip->settings.wait_time;
+	chip->tsl2772_config[TSL2772_ALS_PRX_CONFIG] =
+		chip->settings.als_prox_config;
+
+	chip->tsl2772_config[TSL2772_ALS_MINTHRESHLO] =
+		(chip->settings.als_thresh_low) & 0xFF;
+	chip->tsl2772_config[TSL2772_ALS_MINTHRESHHI] =
+		(chip->settings.als_thresh_low >> 8) & 0xFF;
+	chip->tsl2772_config[TSL2772_ALS_MAXTHRESHLO] =
+		(chip->settings.als_thresh_high) & 0xFF;
+	chip->tsl2772_config[TSL2772_ALS_MAXTHRESHHI] =
+		(chip->settings.als_thresh_high >> 8) & 0xFF;
+	chip->tsl2772_config[TSL2772_PERSISTENCE] =
+		(chip->settings.prox_persistence & 0xFF) << 4 |
+		(chip->settings.als_persistence & 0xFF);
+
+	chip->tsl2772_config[TSL2772_PRX_COUNT] =
+			chip->settings.prox_pulse_count;
+	chip->tsl2772_config[TSL2772_PRX_MINTHRESHLO] =
+			(chip->settings.prox_thres_low) & 0xFF;
+	chip->tsl2772_config[TSL2772_PRX_MINTHRESHHI] =
+			(chip->settings.prox_thres_low >> 8) & 0xFF;
+	chip->tsl2772_config[TSL2772_PRX_MAXTHRESHLO] =
+			(chip->settings.prox_thres_high) & 0xFF;
+	chip->tsl2772_config[TSL2772_PRX_MAXTHRESHHI] =
+			(chip->settings.prox_thres_high >> 8) & 0xFF;
+
+	/* and make sure we're not already on */
+	if (chip->tsl2772_chip_status == TSL2772_CHIP_WORKING) {
+		/* if forcing a register update - turn off, then on */
+		dev_info(&chip->client->dev, "device is already enabled\n");
+		return -EINVAL;
+	}
+
+	/* Set the gain based on tsl2772_settings struct */
+	chip->tsl2772_config[TSL2772_GAIN] =
+		(chip->settings.als_gain & 0xFF) |
+		((chip->settings.prox_gain & 0xFF) << 2) |
+		(chip->settings.prox_diode << 4) |
+		(chip->settings.prox_power << 6);
+
+	/* set chip time scaling and saturation */
+	als_count = 256 - chip->settings.als_time;
+	als_time_us = als_count * tsl2772_int_time_avail[chip->id][3];
+	chip->als_saturation = als_count * 768; /* 75% of full scale */
+	chip->als_gain_time_scale = als_time_us *
+		tsl2772_als_gain[chip->settings.als_gain];
+
+	/*
+	 * TSL2772 Specific power-on / adc enable sequence
+	 * Power on the device 1st.
+	 */
+	ret = tsl2772_write_control_reg(chip, TSL2772_CNTL_PWR_ON);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Use the following shadow copy for our delay before enabling ADC.
+	 * Write all the registers.
+	 */
+	for (i = 0, dev_reg = chip->tsl2772_config;
+			i < TSL2772_MAX_CONFIG_REG; i++) {
+		int reg = TSL2772_CMD_REG + i;
+
+		ret = i2c_smbus_write_byte_data(chip->client, reg,
+						*dev_reg++);
+		if (ret < 0) {
+			dev_err(&chip->client->dev,
+				"%s: failed to write to register %x: %d\n",
+				__func__, reg, ret);
+			return ret;
+		}
+	}
+
+	/* Power-on settling time */
+	usleep_range(3000, 3500);
+
+	reg_val = TSL2772_CNTL_PWR_ON | TSL2772_CNTL_ADC_ENBL |
+		  TSL2772_CNTL_PROX_DET_ENBL;
+	if (chip->settings.als_interrupt_en)
+		reg_val |= TSL2772_CNTL_ALS_INT_ENBL;
+	if (chip->settings.prox_interrupt_en)
+		reg_val |= TSL2772_CNTL_PROX_INT_ENBL;
+
+	ret = tsl2772_write_control_reg(chip, reg_val);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_write_byte(chip->client,
+				   TSL2772_CMD_REG | TSL2772_CMD_SPL_FN |
+				   TSL2772_CMD_PROXALS_INT_CLR);
+	if (ret < 0) {
+		dev_err(&chip->client->dev,
+			"%s: failed to clear interrupt status: %d\n",
+			__func__, ret);
+		return ret;
+	}
+
+	chip->tsl2772_chip_status = TSL2772_CHIP_WORKING;
+
+	return ret;
+}
+
+static int tsl2772_chip_off(struct iio_dev *indio_dev)
+{
+	struct tsl2772_chip *chip = iio_priv(indio_dev);
+
+	/* turn device off */
+	chip->tsl2772_chip_status = TSL2772_CHIP_SUSPENDED;
+	return tsl2772_write_control_reg(chip, 0x00);
+}
+
+/**
+ * tsl2772_invoke_change - power cycle the device to implement the user
+ *                         parameters
+ * @indio_dev:	pointer to IIO device
+ *
+ * Obtain and lock both ALS and PROX resources, determine and save device state
+ * (On/Off), cycle device to implement updated parameter, put device back into
+ * proper state, and unlock resource.
+ */
+static int tsl2772_invoke_change(struct iio_dev *indio_dev)
+{
+	struct tsl2772_chip *chip = iio_priv(indio_dev);
+	int device_status = chip->tsl2772_chip_status;
+	int ret;
+
+	mutex_lock(&chip->als_mutex);
+	mutex_lock(&chip->prox_mutex);
+
+	if (device_status == TSL2772_CHIP_WORKING) {
+		ret = tsl2772_chip_off(indio_dev);
+		if (ret < 0)
+			goto unlock;
+	}
+
+	ret = tsl2772_chip_on(indio_dev);
+
+unlock:
+	mutex_unlock(&chip->prox_mutex);
+	mutex_unlock(&chip->als_mutex);
+
+	return ret;
+}
+
+static int tsl2772_prox_cal(struct iio_dev *indio_dev)
+{
+	struct tsl2772_chip *chip = iio_priv(indio_dev);
+	int prox_history[MAX_SAMPLES_CAL + 1];
+	int i, ret, mean, max, sample_sum;
+
+	if (chip->settings.prox_max_samples_cal < 1 ||
+	    chip->settings.prox_max_samples_cal > MAX_SAMPLES_CAL)
+		return -EINVAL;
+
+	for (i = 0; i < chip->settings.prox_max_samples_cal; i++) {
+		usleep_range(15000, 17500);
+		ret = tsl2772_get_prox(indio_dev);
+		if (ret < 0)
+			return ret;
+
+		prox_history[i] = chip->prox_data;
+	}
+
+	sample_sum = 0;
+	max = INT_MIN;
+	for (i = 0; i < chip->settings.prox_max_samples_cal; i++) {
+		sample_sum += prox_history[i];
+		max = max(max, prox_history[i]);
+	}
+	mean = sample_sum / chip->settings.prox_max_samples_cal;
+
+	chip->settings.prox_thres_high = (max << 1) - mean;
+
+	return tsl2772_invoke_change(indio_dev);
+}
+
+static int tsl2772_read_avail(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *chan,
+			      const int **vals, int *type, int *length,
+			      long mask)
+{
+	struct tsl2772_chip *chip = iio_priv(indio_dev);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_CALIBSCALE:
+		if (chan->type == IIO_INTENSITY) {
+			*length = ARRAY_SIZE(tsl2772_int_calibscale_avail);
+			*vals = tsl2772_int_calibscale_avail;
+		} else {
+			*length = ARRAY_SIZE(tsl2772_prox_calibscale_avail);
+			*vals = tsl2772_prox_calibscale_avail;
+		}
+		*type = IIO_VAL_INT;
+		return IIO_AVAIL_LIST;
+	case IIO_CHAN_INFO_INT_TIME:
+		*length = ARRAY_SIZE(tsl2772_int_time_avail[chip->id]);
+		*vals = tsl2772_int_time_avail[chip->id];
+		*type = IIO_VAL_INT_PLUS_MICRO;
+		return IIO_AVAIL_RANGE;
+	}
+
+	return -EINVAL;
+}
+
+static ssize_t in_illuminance0_target_input_show(struct device *dev,
+						 struct device_attribute *attr,
+						 char *buf)
+{
+	struct tsl2772_chip *chip = iio_priv(dev_to_iio_dev(dev));
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", chip->settings.als_cal_target);
+}
+
+static ssize_t in_illuminance0_target_input_store(struct device *dev,
+						  struct device_attribute *attr,
+						  const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct tsl2772_chip *chip = iio_priv(indio_dev);
+	u16 value;
+	int ret;
+
+	if (kstrtou16(buf, 0, &value))
+		return -EINVAL;
+
+	chip->settings.als_cal_target = value;
+	ret = tsl2772_invoke_change(indio_dev);
+	if (ret < 0)
+		return ret;
+
+	return len;
+}
+
+static ssize_t in_illuminance0_calibrate_store(struct device *dev,
+					       struct device_attribute *attr,
+					       const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	bool value;
+	int ret;
+
+	if (kstrtobool(buf, &value) || !value)
+		return -EINVAL;
+
+	ret = tsl2772_als_calibrate(indio_dev);
+	if (ret < 0)
+		return ret;
+
+	ret = tsl2772_invoke_change(indio_dev);
+	if (ret < 0)
+		return ret;
+
+	return len;
+}
+
+static ssize_t in_illuminance0_lux_table_show(struct device *dev,
+					      struct device_attribute *attr,
+					      char *buf)
+{
+	struct tsl2772_chip *chip = iio_priv(dev_to_iio_dev(dev));
+	int i = 0;
+	int offset = 0;
+
+	while (i < TSL2772_MAX_LUX_TABLE_SIZE) {
+		offset += snprintf(buf + offset, PAGE_SIZE, "%u,%u,",
+			chip->tsl2772_device_lux[i].ch0,
+			chip->tsl2772_device_lux[i].ch1);
+		if (chip->tsl2772_device_lux[i].ch0 == 0) {
+			/*
+			 * We just printed the first "0" entry.
+			 * Now get rid of the extra "," and break.
+			 */
+			offset--;
+			break;
+		}
+		i++;
+	}
+
+	offset += snprintf(buf + offset, PAGE_SIZE, "\n");
+	return offset;
+}
+
+static ssize_t in_illuminance0_lux_table_store(struct device *dev,
+					       struct device_attribute *attr,
+					       const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct tsl2772_chip *chip = iio_priv(indio_dev);
+	int value[ARRAY_SIZE(chip->tsl2772_device_lux) * 2 + 1];
+	int n, ret;
+
+	get_options(buf, ARRAY_SIZE(value), value);
+
+	/*
+	 * We now have an array of ints starting at value[1], and
+	 * enumerated by value[0].
+	 * We expect each group of two ints to be one table entry,
+	 * and the last table entry is all 0.
+	 */
+	n = value[0];
+	if ((n % 2) || n < 4 ||
+	    n > ((ARRAY_SIZE(chip->tsl2772_device_lux) - 1) * 2))
+		return -EINVAL;
+
+	if ((value[(n - 1)] | value[n]) != 0)
+		return -EINVAL;
+
+	if (chip->tsl2772_chip_status == TSL2772_CHIP_WORKING) {
+		ret = tsl2772_chip_off(indio_dev);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* Zero out the table */
+	memset(chip->tsl2772_device_lux, 0, sizeof(chip->tsl2772_device_lux));
+	memcpy(chip->tsl2772_device_lux, &value[1], (value[0] * 4));
+
+	ret = tsl2772_invoke_change(indio_dev);
+	if (ret < 0)
+		return ret;
+
+	return len;
+}
+
+static ssize_t in_proximity0_calibrate_store(struct device *dev,
+					     struct device_attribute *attr,
+					     const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	bool value;
+	int ret;
+
+	if (kstrtobool(buf, &value) || !value)
+		return -EINVAL;
+
+	ret = tsl2772_prox_cal(indio_dev);
+	if (ret < 0)
+		return ret;
+
+	ret = tsl2772_invoke_change(indio_dev);
+	if (ret < 0)
+		return ret;
+
+	return len;
+}
+
+static int tsl2772_read_interrupt_config(struct iio_dev *indio_dev,
+					 const struct iio_chan_spec *chan,
+					 enum iio_event_type type,
+					 enum iio_event_direction dir)
+{
+	struct tsl2772_chip *chip = iio_priv(indio_dev);
+
+	if (chan->type == IIO_INTENSITY)
+		return chip->settings.als_interrupt_en;
+	else
+		return chip->settings.prox_interrupt_en;
+}
+
+static int tsl2772_write_interrupt_config(struct iio_dev *indio_dev,
+					  const struct iio_chan_spec *chan,
+					  enum iio_event_type type,
+					  enum iio_event_direction dir,
+					  int val)
+{
+	struct tsl2772_chip *chip = iio_priv(indio_dev);
+
+	if (chan->type == IIO_INTENSITY)
+		chip->settings.als_interrupt_en = val ? true : false;
+	else
+		chip->settings.prox_interrupt_en = val ? true : false;
+
+	return tsl2772_invoke_change(indio_dev);
+}
+
+static int tsl2772_write_event_value(struct iio_dev *indio_dev,
+				     const struct iio_chan_spec *chan,
+				     enum iio_event_type type,
+				     enum iio_event_direction dir,
+				     enum iio_event_info info,
+				     int val, int val2)
+{
+	struct tsl2772_chip *chip = iio_priv(indio_dev);
+	int ret = -EINVAL, count, persistence;
+	u8 time;
+
+	switch (info) {
+	case IIO_EV_INFO_VALUE:
+		if (chan->type == IIO_INTENSITY) {
+			switch (dir) {
+			case IIO_EV_DIR_RISING:
+				chip->settings.als_thresh_high = val;
+				ret = 0;
+				break;
+			case IIO_EV_DIR_FALLING:
+				chip->settings.als_thresh_low = val;
+				ret = 0;
+				break;
+			default:
+				break;
+			}
+		} else {
+			switch (dir) {
+			case IIO_EV_DIR_RISING:
+				chip->settings.prox_thres_high = val;
+				ret = 0;
+				break;
+			case IIO_EV_DIR_FALLING:
+				chip->settings.prox_thres_low = val;
+				ret = 0;
+				break;
+			default:
+				break;
+			}
+		}
+		break;
+	case IIO_EV_INFO_PERIOD:
+		if (chan->type == IIO_INTENSITY)
+			time = chip->settings.als_time;
+		else
+			time = chip->settings.prox_time;
+
+		count = 256 - time;
+		persistence = ((val * 1000000) + val2) /
+			(count * tsl2772_int_time_avail[chip->id][3]);
+
+		if (chan->type == IIO_INTENSITY) {
+			/* ALS filter values are 1, 2, 3, 5, 10, 15, ..., 60 */
+			if (persistence > 3)
+				persistence = (persistence / 5) + 3;
+
+			chip->settings.als_persistence = persistence;
+		} else {
+			chip->settings.prox_persistence = persistence;
+		}
+
+		ret = 0;
+		break;
+	default:
+		break;
+	}
+
+	if (ret < 0)
+		return ret;
+
+	return tsl2772_invoke_change(indio_dev);
+}
+
+static int tsl2772_read_event_value(struct iio_dev *indio_dev,
+				    const struct iio_chan_spec *chan,
+				    enum iio_event_type type,
+				    enum iio_event_direction dir,
+				    enum iio_event_info info,
+				    int *val, int *val2)
+{
+	struct tsl2772_chip *chip = iio_priv(indio_dev);
+	int filter_delay, persistence;
+	u8 time;
+
+	switch (info) {
+	case IIO_EV_INFO_VALUE:
+		if (chan->type == IIO_INTENSITY) {
+			switch (dir) {
+			case IIO_EV_DIR_RISING:
+				*val = chip->settings.als_thresh_high;
+				return IIO_VAL_INT;
+			case IIO_EV_DIR_FALLING:
+				*val = chip->settings.als_thresh_low;
+				return IIO_VAL_INT;
+			default:
+				return -EINVAL;
+			}
+		} else {
+			switch (dir) {
+			case IIO_EV_DIR_RISING:
+				*val = chip->settings.prox_thres_high;
+				return IIO_VAL_INT;
+			case IIO_EV_DIR_FALLING:
+				*val = chip->settings.prox_thres_low;
+				return IIO_VAL_INT;
+			default:
+				return -EINVAL;
+			}
+		}
+		break;
+	case IIO_EV_INFO_PERIOD:
+		if (chan->type == IIO_INTENSITY) {
+			time = chip->settings.als_time;
+			persistence = chip->settings.als_persistence;
+
+			/* ALS filter values are 1, 2, 3, 5, 10, 15, ..., 60 */
+			if (persistence > 3)
+				persistence = (persistence - 3) * 5;
+		} else {
+			time = chip->settings.prox_time;
+			persistence = chip->settings.prox_persistence;
+		}
+
+		filter_delay = persistence * (256 - time) *
+			tsl2772_int_time_avail[chip->id][3];
+
+		*val = filter_delay / 1000000;
+		*val2 = filter_delay % 1000000;
+		return IIO_VAL_INT_PLUS_MICRO;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int tsl2772_read_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan,
+			    int *val,
+			    int *val2,
+			    long mask)
+{
+	struct tsl2772_chip *chip = iio_priv(indio_dev);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_PROCESSED:
+		switch (chan->type) {
+		case IIO_LIGHT:
+			tsl2772_get_lux(indio_dev);
+			*val = chip->als_cur_info.lux;
+			return IIO_VAL_INT;
+		default:
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_RAW:
+		switch (chan->type) {
+		case IIO_INTENSITY:
+			tsl2772_get_lux(indio_dev);
+			if (chan->channel == 0)
+				*val = chip->als_cur_info.als_ch0;
+			else
+				*val = chip->als_cur_info.als_ch1;
+			return IIO_VAL_INT;
+		case IIO_PROXIMITY:
+			tsl2772_get_prox(indio_dev);
+			*val = chip->prox_data;
+			return IIO_VAL_INT;
+		default:
+			return -EINVAL;
+		}
+		break;
+	case IIO_CHAN_INFO_CALIBSCALE:
+		if (chan->type == IIO_LIGHT)
+			*val = tsl2772_als_gain[chip->settings.als_gain];
+		else
+			*val = tsl2772_prox_gain[chip->settings.prox_gain];
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_CALIBBIAS:
+		*val = chip->settings.als_gain_trim;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_INT_TIME:
+		*val = 0;
+		*val2 = (256 - chip->settings.als_time) *
+			tsl2772_int_time_avail[chip->id][3];
+		return IIO_VAL_INT_PLUS_MICRO;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int tsl2772_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int val,
+			     int val2,
+			     long mask)
+{
+	struct tsl2772_chip *chip = iio_priv(indio_dev);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_CALIBSCALE:
+		if (chan->type == IIO_INTENSITY) {
+			switch (val) {
+			case 1:
+				chip->settings.als_gain = 0;
+				break;
+			case 8:
+				chip->settings.als_gain = 1;
+				break;
+			case 16:
+				chip->settings.als_gain = 2;
+				break;
+			case 120:
+				chip->settings.als_gain = 3;
+				break;
+			default:
+				return -EINVAL;
+			}
+		} else {
+			switch (val) {
+			case 1:
+				chip->settings.prox_gain = 0;
+				break;
+			case 2:
+				chip->settings.prox_gain = 1;
+				break;
+			case 4:
+				chip->settings.prox_gain = 2;
+				break;
+			case 8:
+				chip->settings.prox_gain = 3;
+				break;
+			default:
+				return -EINVAL;
+			}
+		}
+		break;
+	case IIO_CHAN_INFO_CALIBBIAS:
+		if (val < TSL2772_ALS_GAIN_TRIM_MIN ||
+		    val > TSL2772_ALS_GAIN_TRIM_MAX)
+			return -EINVAL;
+
+		chip->settings.als_gain_trim = val;
+		break;
+	case IIO_CHAN_INFO_INT_TIME:
+		if (val != 0 || val2 < tsl2772_int_time_avail[chip->id][1] ||
+		    val2 > tsl2772_int_time_avail[chip->id][5])
+			return -EINVAL;
+
+		chip->settings.als_time = 256 -
+			(val2 / tsl2772_int_time_avail[chip->id][3]);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return tsl2772_invoke_change(indio_dev);
+}
+
+static DEVICE_ATTR_RW(in_illuminance0_target_input);
+
+static DEVICE_ATTR_WO(in_illuminance0_calibrate);
+
+static DEVICE_ATTR_WO(in_proximity0_calibrate);
+
+static DEVICE_ATTR_RW(in_illuminance0_lux_table);
+
+/* Use the default register values to identify the Taos device */
+static int tsl2772_device_id_verif(int id, int target)
+{
+	switch (target) {
+	case tsl2571:
+	case tsl2671:
+	case tsl2771:
+		return (id & 0xf0) == TRITON_ID;
+	case tmd2671:
+	case tmd2771:
+		return (id & 0xf0) == HALIBUT_ID;
+	case tsl2572:
+	case tsl2672:
+	case tmd2672:
+	case tsl2772:
+	case tmd2772:
+		return (id & 0xf0) == SWORDFISH_ID;
+	}
+
+	return -EINVAL;
+}
+
+static irqreturn_t tsl2772_event_handler(int irq, void *private)
+{
+	struct iio_dev *indio_dev = private;
+	struct tsl2772_chip *chip = iio_priv(indio_dev);
+	s64 timestamp = iio_get_time_ns(indio_dev);
+	int ret;
+
+	ret = tsl2772_read_status(chip);
+	if (ret < 0)
+		return IRQ_HANDLED;
+
+	/* What type of interrupt do we need to process */
+	if (ret & TSL2772_STA_PRX_INTR) {
+		iio_push_event(indio_dev,
+			       IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY,
+						    0,
+						    IIO_EV_TYPE_THRESH,
+						    IIO_EV_DIR_EITHER),
+			       timestamp);
+	}
+
+	if (ret & TSL2772_STA_ALS_INTR) {
+		iio_push_event(indio_dev,
+			       IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
+						    0,
+						    IIO_EV_TYPE_THRESH,
+						    IIO_EV_DIR_EITHER),
+			       timestamp);
+	}
+
+	ret = i2c_smbus_write_byte(chip->client,
+				   TSL2772_CMD_REG | TSL2772_CMD_SPL_FN |
+				   TSL2772_CMD_PROXALS_INT_CLR);
+	if (ret < 0)
+		dev_err(&chip->client->dev,
+			"%s: failed to clear interrupt status: %d\n",
+			__func__, ret);
+
+	return IRQ_HANDLED;
+}
+
+static struct attribute *tsl2772_ALS_device_attrs[] = {
+	&dev_attr_in_illuminance0_target_input.attr,
+	&dev_attr_in_illuminance0_calibrate.attr,
+	&dev_attr_in_illuminance0_lux_table.attr,
+	NULL
+};
+
+static struct attribute *tsl2772_PRX_device_attrs[] = {
+	&dev_attr_in_proximity0_calibrate.attr,
+	NULL
+};
+
+static struct attribute *tsl2772_ALSPRX_device_attrs[] = {
+	&dev_attr_in_illuminance0_target_input.attr,
+	&dev_attr_in_illuminance0_calibrate.attr,
+	&dev_attr_in_illuminance0_lux_table.attr,
+	NULL
+};
+
+static struct attribute *tsl2772_PRX2_device_attrs[] = {
+	&dev_attr_in_proximity0_calibrate.attr,
+	NULL
+};
+
+static struct attribute *tsl2772_ALSPRX2_device_attrs[] = {
+	&dev_attr_in_illuminance0_target_input.attr,
+	&dev_attr_in_illuminance0_calibrate.attr,
+	&dev_attr_in_illuminance0_lux_table.attr,
+	&dev_attr_in_proximity0_calibrate.attr,
+	NULL
+};
+
+static const struct attribute_group tsl2772_device_attr_group_tbl[] = {
+	[ALS] = {
+		.attrs = tsl2772_ALS_device_attrs,
+	},
+	[PRX] = {
+		.attrs = tsl2772_PRX_device_attrs,
+	},
+	[ALSPRX] = {
+		.attrs = tsl2772_ALSPRX_device_attrs,
+	},
+	[PRX2] = {
+		.attrs = tsl2772_PRX2_device_attrs,
+	},
+	[ALSPRX2] = {
+		.attrs = tsl2772_ALSPRX2_device_attrs,
+	},
+};
+
+#define TSL2772_DEVICE_INFO(type)[type] = \
+	{ \
+		.attrs = &tsl2772_device_attr_group_tbl[type], \
+		.read_raw = &tsl2772_read_raw, \
+		.read_avail = &tsl2772_read_avail, \
+		.write_raw = &tsl2772_write_raw, \
+		.read_event_value = &tsl2772_read_event_value, \
+		.write_event_value = &tsl2772_write_event_value, \
+		.read_event_config = &tsl2772_read_interrupt_config, \
+		.write_event_config = &tsl2772_write_interrupt_config, \
+	}
+
+static const struct iio_info tsl2772_device_info[] = {
+	TSL2772_DEVICE_INFO(ALS),
+	TSL2772_DEVICE_INFO(PRX),
+	TSL2772_DEVICE_INFO(ALSPRX),
+	TSL2772_DEVICE_INFO(PRX2),
+	TSL2772_DEVICE_INFO(ALSPRX2),
+};
+
+static const struct iio_event_spec tsl2772_events[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_RISING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE),
+	}, {
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_FALLING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE),
+	}, {
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_EITHER,
+		.mask_separate = BIT(IIO_EV_INFO_PERIOD) |
+			BIT(IIO_EV_INFO_ENABLE),
+	},
+};
+
+static const struct tsl2772_chip_info tsl2772_chip_info_tbl[] = {
+	[ALS] = {
+		.channel_with_events = {
+			{
+			.type = IIO_LIGHT,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+			}, {
+			.type = IIO_INTENSITY,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				BIT(IIO_CHAN_INFO_INT_TIME) |
+				BIT(IIO_CHAN_INFO_CALIBSCALE) |
+				BIT(IIO_CHAN_INFO_CALIBBIAS),
+			.info_mask_separate_available =
+				BIT(IIO_CHAN_INFO_INT_TIME) |
+				BIT(IIO_CHAN_INFO_CALIBSCALE),
+			.event_spec = tsl2772_events,
+			.num_event_specs = ARRAY_SIZE(tsl2772_events),
+			}, {
+			.type = IIO_INTENSITY,
+			.indexed = 1,
+			.channel = 1,
+			},
+		},
+		.channel_without_events = {
+			{
+			.type = IIO_LIGHT,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+			}, {
+			.type = IIO_INTENSITY,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				BIT(IIO_CHAN_INFO_INT_TIME) |
+				BIT(IIO_CHAN_INFO_CALIBSCALE) |
+				BIT(IIO_CHAN_INFO_CALIBBIAS),
+			.info_mask_separate_available =
+				BIT(IIO_CHAN_INFO_INT_TIME) |
+				BIT(IIO_CHAN_INFO_CALIBSCALE),
+			}, {
+			.type = IIO_INTENSITY,
+			.indexed = 1,
+			.channel = 1,
+			},
+		},
+		.chan_table_elements = 3,
+		.info = &tsl2772_device_info[ALS],
+	},
+	[PRX] = {
+		.channel_with_events = {
+			{
+			.type = IIO_PROXIMITY,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+			.event_spec = tsl2772_events,
+			.num_event_specs = ARRAY_SIZE(tsl2772_events),
+			},
+		},
+		.channel_without_events = {
+			{
+			.type = IIO_PROXIMITY,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+			},
+		},
+		.chan_table_elements = 1,
+		.info = &tsl2772_device_info[PRX],
+	},
+	[ALSPRX] = {
+		.channel_with_events = {
+			{
+			.type = IIO_LIGHT,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+			}, {
+			.type = IIO_INTENSITY,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				BIT(IIO_CHAN_INFO_INT_TIME) |
+				BIT(IIO_CHAN_INFO_CALIBSCALE) |
+				BIT(IIO_CHAN_INFO_CALIBBIAS),
+			.info_mask_separate_available =
+				BIT(IIO_CHAN_INFO_INT_TIME) |
+				BIT(IIO_CHAN_INFO_CALIBSCALE),
+			.event_spec = tsl2772_events,
+			.num_event_specs = ARRAY_SIZE(tsl2772_events),
+			}, {
+			.type = IIO_INTENSITY,
+			.indexed = 1,
+			.channel = 1,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+			}, {
+			.type = IIO_PROXIMITY,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+			.event_spec = tsl2772_events,
+			.num_event_specs = ARRAY_SIZE(tsl2772_events),
+			},
+		},
+		.channel_without_events = {
+			{
+			.type = IIO_LIGHT,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+			}, {
+			.type = IIO_INTENSITY,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				BIT(IIO_CHAN_INFO_INT_TIME) |
+				BIT(IIO_CHAN_INFO_CALIBSCALE) |
+				BIT(IIO_CHAN_INFO_CALIBBIAS),
+			.info_mask_separate_available =
+				BIT(IIO_CHAN_INFO_INT_TIME) |
+				BIT(IIO_CHAN_INFO_CALIBSCALE),
+			}, {
+			.type = IIO_INTENSITY,
+			.indexed = 1,
+			.channel = 1,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+			}, {
+			.type = IIO_PROXIMITY,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+			},
+		},
+		.chan_table_elements = 4,
+		.info = &tsl2772_device_info[ALSPRX],
+	},
+	[PRX2] = {
+		.channel_with_events = {
+			{
+			.type = IIO_PROXIMITY,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				BIT(IIO_CHAN_INFO_CALIBSCALE),
+			.info_mask_separate_available =
+				BIT(IIO_CHAN_INFO_CALIBSCALE),
+			.event_spec = tsl2772_events,
+			.num_event_specs = ARRAY_SIZE(tsl2772_events),
+			},
+		},
+		.channel_without_events = {
+			{
+			.type = IIO_PROXIMITY,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				BIT(IIO_CHAN_INFO_CALIBSCALE),
+			.info_mask_separate_available =
+				BIT(IIO_CHAN_INFO_CALIBSCALE),
+			},
+		},
+		.chan_table_elements = 1,
+		.info = &tsl2772_device_info[PRX2],
+	},
+	[ALSPRX2] = {
+		.channel_with_events = {
+			{
+			.type = IIO_LIGHT,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+			}, {
+			.type = IIO_INTENSITY,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				BIT(IIO_CHAN_INFO_INT_TIME) |
+				BIT(IIO_CHAN_INFO_CALIBSCALE) |
+				BIT(IIO_CHAN_INFO_CALIBBIAS),
+			.info_mask_separate_available =
+				BIT(IIO_CHAN_INFO_INT_TIME) |
+				BIT(IIO_CHAN_INFO_CALIBSCALE),
+			.event_spec = tsl2772_events,
+			.num_event_specs = ARRAY_SIZE(tsl2772_events),
+			}, {
+			.type = IIO_INTENSITY,
+			.indexed = 1,
+			.channel = 1,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+			}, {
+			.type = IIO_PROXIMITY,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				BIT(IIO_CHAN_INFO_CALIBSCALE),
+			.info_mask_separate_available =
+				BIT(IIO_CHAN_INFO_CALIBSCALE),
+			.event_spec = tsl2772_events,
+			.num_event_specs = ARRAY_SIZE(tsl2772_events),
+			},
+		},
+		.channel_without_events = {
+			{
+			.type = IIO_LIGHT,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+			}, {
+			.type = IIO_INTENSITY,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				BIT(IIO_CHAN_INFO_INT_TIME) |
+				BIT(IIO_CHAN_INFO_CALIBSCALE) |
+				BIT(IIO_CHAN_INFO_CALIBBIAS),
+			.info_mask_separate_available =
+				BIT(IIO_CHAN_INFO_INT_TIME) |
+				BIT(IIO_CHAN_INFO_CALIBSCALE),
+			}, {
+			.type = IIO_INTENSITY,
+			.indexed = 1,
+			.channel = 1,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+			}, {
+			.type = IIO_PROXIMITY,
+			.indexed = 1,
+			.channel = 0,
+			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				BIT(IIO_CHAN_INFO_CALIBSCALE),
+			.info_mask_separate_available =
+				BIT(IIO_CHAN_INFO_CALIBSCALE),
+			},
+		},
+		.chan_table_elements = 4,
+		.info = &tsl2772_device_info[ALSPRX2],
+	},
+};
+
+static int tsl2772_probe(struct i2c_client *clientp,
+			 const struct i2c_device_id *id)
+{
+	struct iio_dev *indio_dev;
+	struct tsl2772_chip *chip;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&clientp->dev, sizeof(*chip));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	chip = iio_priv(indio_dev);
+	chip->client = clientp;
+	i2c_set_clientdata(clientp, indio_dev);
+
+	ret = i2c_smbus_read_byte_data(chip->client,
+				       TSL2772_CMD_REG | TSL2772_CHIPID);
+	if (ret < 0)
+		return ret;
+
+	if (tsl2772_device_id_verif(ret, id->driver_data) <= 0) {
+		dev_info(&chip->client->dev,
+			 "%s: i2c device found does not match expected id\n",
+				__func__);
+		return -EINVAL;
+	}
+
+	ret = i2c_smbus_write_byte(clientp, TSL2772_CMD_REG | TSL2772_CNTRL);
+	if (ret < 0) {
+		dev_err(&clientp->dev,
+			"%s: Failed to write to CMD register: %d\n",
+			__func__, ret);
+		return ret;
+	}
+
+	mutex_init(&chip->als_mutex);
+	mutex_init(&chip->prox_mutex);
+
+	chip->tsl2772_chip_status = TSL2772_CHIP_UNKNOWN;
+	chip->pdata = dev_get_platdata(&clientp->dev);
+	chip->id = id->driver_data;
+	chip->chip_info =
+		&tsl2772_chip_info_tbl[device_channel_config[id->driver_data]];
+
+	indio_dev->info = chip->chip_info->info;
+	indio_dev->dev.parent = &clientp->dev;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->name = chip->client->name;
+	indio_dev->num_channels = chip->chip_info->chan_table_elements;
+
+	if (clientp->irq) {
+		indio_dev->channels = chip->chip_info->channel_with_events;
+
+		ret = devm_request_threaded_irq(&clientp->dev, clientp->irq,
+						NULL,
+						&tsl2772_event_handler,
+						IRQF_TRIGGER_FALLING |
+						IRQF_ONESHOT,
+						"TSL2772_event",
+						indio_dev);
+		if (ret) {
+			dev_err(&clientp->dev,
+				"%s: irq request failed\n", __func__);
+			return ret;
+		}
+	} else {
+		indio_dev->channels = chip->chip_info->channel_without_events;
+	}
+
+	tsl2772_defaults(chip);
+	ret = tsl2772_chip_on(indio_dev);
+	if (ret < 0)
+		return ret;
+
+	ret = iio_device_register(indio_dev);
+	if (ret) {
+		tsl2772_chip_off(indio_dev);
+		dev_err(&clientp->dev,
+			"%s: iio registration failed\n", __func__);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int tsl2772_suspend(struct device *dev)
+{
+	struct iio_dev *indio_dev = dev_get_drvdata(dev);
+
+	return tsl2772_chip_off(indio_dev);
+}
+
+static int tsl2772_resume(struct device *dev)
+{
+	struct iio_dev *indio_dev = dev_get_drvdata(dev);
+
+	return tsl2772_chip_on(indio_dev);
+}
+
+static int tsl2772_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+	tsl2772_chip_off(indio_dev);
+
+	iio_device_unregister(indio_dev);
+
+	return 0;
+}
+
+static const struct i2c_device_id tsl2772_idtable[] = {
+	{ "tsl2571", tsl2571 },
+	{ "tsl2671", tsl2671 },
+	{ "tmd2671", tmd2671 },
+	{ "tsl2771", tsl2771 },
+	{ "tmd2771", tmd2771 },
+	{ "tsl2572", tsl2572 },
+	{ "tsl2672", tsl2672 },
+	{ "tmd2672", tmd2672 },
+	{ "tsl2772", tsl2772 },
+	{ "tmd2772", tmd2772 },
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, tsl2772_idtable);
+
+static const struct of_device_id tsl2772_of_match[] = {
+	{ .compatible = "amstaos,tsl2571" },
+	{ .compatible = "amstaos,tsl2671" },
+	{ .compatible = "amstaos,tmd2671" },
+	{ .compatible = "amstaos,tsl2771" },
+	{ .compatible = "amstaos,tmd2771" },
+	{ .compatible = "amstaos,tsl2572" },
+	{ .compatible = "amstaos,tsl2672" },
+	{ .compatible = "amstaos,tmd2672" },
+	{ .compatible = "amstaos,tsl2772" },
+	{ .compatible = "amstaos,tmd2772" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, tsl2772_of_match);
+
+static const struct dev_pm_ops tsl2772_pm_ops = {
+	.suspend = tsl2772_suspend,
+	.resume  = tsl2772_resume,
+};
+
+static struct i2c_driver tsl2772_driver = {
+	.driver = {
+		.name = "tsl2772",
+		.of_match_table = tsl2772_of_match,
+		.pm = &tsl2772_pm_ops,
+	},
+	.id_table = tsl2772_idtable,
+	.probe = tsl2772_probe,
+	.remove = tsl2772_remove,
+};
+
+module_i2c_driver(tsl2772_driver);
+
+MODULE_AUTHOR("J. August Brenner <Jon.Brenner@ams.com>");
+MODULE_AUTHOR("Brian Masney <masneyb@onstation.org>");
+MODULE_DESCRIPTION("TAOS tsl2772 ambient and proximity light sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/tsl4531.c b/drivers/iio/light/tsl4531.c
new file mode 100644
index 0000000..06171cb
--- /dev/null
+++ b/drivers/iio/light/tsl4531.c
@@ -0,0 +1,260 @@
+/*
+ * tsl4531.c - Support for TAOS TSL4531 ambient light sensor
+ *
+ * Copyright 2013 Peter Meerwald <pmeerw@pmeerw.net>
+ *
+ * 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 the TSL4531x family
+ *   TSL45311/TSL45313: 7-bit I2C slave address 0x39
+ *   TSL45315/TSL45317: 7-bit I2C slave address 0x29
+ *
+ * TODO: single cycle measurement
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define TSL4531_DRV_NAME "tsl4531"
+
+#define TSL4531_COMMAND BIT(7)
+
+#define TSL4531_CONTROL (TSL4531_COMMAND | 0x00)
+#define TSL4531_CONFIG (TSL4531_COMMAND | 0x01)
+#define TSL4531_DATA (TSL4531_COMMAND | 0x04)
+#define TSL4531_ID (TSL4531_COMMAND | 0x0a)
+
+/* operating modes in control register */
+#define TSL4531_MODE_POWERDOWN 0x00
+#define TSL4531_MODE_SINGLE_ADC 0x02
+#define TSL4531_MODE_NORMAL 0x03
+
+/* integration time control in config register */
+#define TSL4531_TCNTRL_400MS 0x00
+#define TSL4531_TCNTRL_200MS 0x01
+#define TSL4531_TCNTRL_100MS 0x02
+
+/* part number in id register */
+#define TSL45311_ID 0x8
+#define TSL45313_ID 0x9
+#define TSL45315_ID 0xa
+#define TSL45317_ID 0xb
+#define TSL4531_ID_SHIFT 4
+
+struct tsl4531_data {
+	struct i2c_client *client;
+	struct mutex lock;
+	int int_time;
+};
+
+static IIO_CONST_ATTR_INT_TIME_AVAIL("0.1 0.2 0.4");
+
+static struct attribute *tsl4531_attributes[] = {
+	&iio_const_attr_integration_time_available.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group tsl4531_attribute_group = {
+	.attrs = tsl4531_attributes,
+};
+
+static const struct iio_chan_spec tsl4531_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+			BIT(IIO_CHAN_INFO_SCALE) |
+			BIT(IIO_CHAN_INFO_INT_TIME)
+	}
+};
+
+static int tsl4531_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val, int *val2, long mask)
+{
+	struct tsl4531_data *data = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = i2c_smbus_read_word_data(data->client,
+			TSL4531_DATA);
+		if (ret < 0)
+			return ret;
+		*val = ret;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		/* 0.. 1x, 1 .. 2x, 2 .. 4x */
+		*val = 1 << data->int_time;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_INT_TIME:
+		if (data->int_time == 0)
+			*val2 = 400000;
+		else if (data->int_time == 1)
+			*val2 = 200000;
+		else if (data->int_time == 2)
+			*val2 = 100000;
+		else
+			return -EINVAL;
+		*val = 0;
+		return IIO_VAL_INT_PLUS_MICRO;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int tsl4531_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int val, int val2, long mask)
+{
+	struct tsl4531_data *data = iio_priv(indio_dev);
+	int int_time, ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_INT_TIME:
+		if (val != 0)
+			return -EINVAL;
+		if (val2 == 400000)
+			int_time = 0;
+		else if (val2 == 200000)
+			int_time = 1;
+		else if (val2 == 100000)
+			int_time = 2;
+		else
+			return -EINVAL;
+		mutex_lock(&data->lock);
+		ret = i2c_smbus_write_byte_data(data->client,
+			TSL4531_CONFIG, int_time);
+		if (ret >= 0)
+			data->int_time = int_time;
+		mutex_unlock(&data->lock);
+		return ret;
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info tsl4531_info = {
+	.read_raw = tsl4531_read_raw,
+	.write_raw = tsl4531_write_raw,
+	.attrs = &tsl4531_attribute_group,
+};
+
+static int tsl4531_check_id(struct i2c_client *client)
+{
+	int ret = i2c_smbus_read_byte_data(client, TSL4531_ID);
+	if (ret < 0)
+		return ret;
+
+	switch (ret >> TSL4531_ID_SHIFT) {
+	case TSL45311_ID:
+	case TSL45313_ID:
+	case TSL45315_ID:
+	case TSL45317_ID:
+		return 0;
+	default:
+		return -ENODEV;
+	}
+}
+
+static int tsl4531_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct tsl4531_data *data;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->client = client;
+	mutex_init(&data->lock);
+
+	ret = tsl4531_check_id(client);
+	if (ret) {
+		dev_err(&client->dev, "no TSL4531 sensor\n");
+		return ret;
+	}
+
+	ret = i2c_smbus_write_byte_data(data->client, TSL4531_CONTROL,
+		TSL4531_MODE_NORMAL);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_write_byte_data(data->client, TSL4531_CONFIG,
+		TSL4531_TCNTRL_400MS);
+	if (ret < 0)
+		return ret;
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->info = &tsl4531_info;
+	indio_dev->channels = tsl4531_channels;
+	indio_dev->num_channels = ARRAY_SIZE(tsl4531_channels);
+	indio_dev->name = TSL4531_DRV_NAME;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	return iio_device_register(indio_dev);
+}
+
+static int tsl4531_powerdown(struct i2c_client *client)
+{
+	return i2c_smbus_write_byte_data(client, TSL4531_CONTROL,
+		TSL4531_MODE_POWERDOWN);
+}
+
+static int tsl4531_remove(struct i2c_client *client)
+{
+	iio_device_unregister(i2c_get_clientdata(client));
+	tsl4531_powerdown(client);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int tsl4531_suspend(struct device *dev)
+{
+	return tsl4531_powerdown(to_i2c_client(dev));
+}
+
+static int tsl4531_resume(struct device *dev)
+{
+	return i2c_smbus_write_byte_data(to_i2c_client(dev), TSL4531_CONTROL,
+		TSL4531_MODE_NORMAL);
+}
+
+static SIMPLE_DEV_PM_OPS(tsl4531_pm_ops, tsl4531_suspend, tsl4531_resume);
+#define TSL4531_PM_OPS (&tsl4531_pm_ops)
+#else
+#define TSL4531_PM_OPS NULL
+#endif
+
+static const struct i2c_device_id tsl4531_id[] = {
+	{ "tsl4531", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, tsl4531_id);
+
+static struct i2c_driver tsl4531_driver = {
+	.driver = {
+		.name   = TSL4531_DRV_NAME,
+		.pm	= TSL4531_PM_OPS,
+	},
+	.probe  = tsl4531_probe,
+	.remove = tsl4531_remove,
+	.id_table = tsl4531_id,
+};
+
+module_i2c_driver(tsl4531_driver);
+
+MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
+MODULE_DESCRIPTION("TAOS TSL4531 ambient light sensors driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/us5182d.c b/drivers/iio/light/us5182d.c
new file mode 100644
index 0000000..68e5294
--- /dev/null
+++ b/drivers/iio/light/us5182d.c
@@ -0,0 +1,996 @@
+/*
+ * 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/events.h>
+#include <linux/iio/iio.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/iio/sysfs.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+
+#define US5182D_REG_CFG0				0x00
+#define US5182D_CFG0_ONESHOT_EN				BIT(6)
+#define US5182D_CFG0_SHUTDOWN_EN			BIT(7)
+#define US5182D_CFG0_WORD_ENABLE			BIT(0)
+#define US5182D_CFG0_PROX				BIT(3)
+#define US5182D_CFG0_PX_IRQ				BIT(2)
+
+#define US5182D_REG_CFG1				0x01
+#define US5182D_CFG1_ALS_RES16				BIT(4)
+#define US5182D_CFG1_AGAIN_DEFAULT			0x00
+
+#define US5182D_REG_CFG2				0x02
+#define US5182D_CFG2_PX_RES16				BIT(4)
+#define US5182D_CFG2_PXGAIN_DEFAULT			BIT(2)
+
+#define US5182D_REG_CFG3				0x03
+#define US5182D_CFG3_LED_CURRENT100			(BIT(4) | BIT(5))
+#define US5182D_CFG3_INT_SOURCE_PX			BIT(3)
+
+#define US5182D_REG_CFG4				0x10
+
+/*
+ * Registers for tuning the auto dark current cancelling feature.
+ * DARK_TH(reg 0x27,0x28) - threshold (counts) for auto dark cancelling.
+ * when ALS  > DARK_TH --> ALS_Code = ALS - Upper(0x2A) * Dark
+ * when ALS < DARK_TH --> ALS_Code = ALS - Lower(0x29) * Dark
+ */
+#define US5182D_REG_UDARK_TH			0x27
+#define US5182D_REG_DARK_AUTO_EN		0x2b
+#define US5182D_REG_AUTO_LDARK_GAIN		0x29
+#define US5182D_REG_AUTO_HDARK_GAIN		0x2a
+
+/* Thresholds for events: px low (0x08-l, 0x09-h), px high (0x0a-l 0x0b-h) */
+#define US5182D_REG_PXL_TH			0x08
+#define US5182D_REG_PXH_TH			0x0a
+
+#define US5182D_REG_PXL_TH_DEFAULT		1000
+#define US5182D_REG_PXH_TH_DEFAULT		30000
+
+#define US5182D_OPMODE_ALS			0x01
+#define US5182D_OPMODE_PX			0x02
+#define US5182D_OPMODE_SHIFT			4
+
+#define US5182D_REG_DARK_AUTO_EN_DEFAULT	0x80
+#define US5182D_REG_AUTO_LDARK_GAIN_DEFAULT	0x16
+#define US5182D_REG_AUTO_HDARK_GAIN_DEFAULT	0x00
+
+#define US5182D_REG_ADL				0x0c
+#define US5182D_REG_PDL				0x0e
+
+#define US5182D_REG_MODE_STORE			0x21
+#define US5182D_STORE_MODE			0x01
+
+#define US5182D_REG_CHIPID			0xb2
+
+#define US5182D_OPMODE_MASK			GENMASK(5, 4)
+#define US5182D_AGAIN_MASK			0x07
+#define US5182D_RESET_CHIP			0x01
+
+#define US5182D_CHIPID				0x26
+#define US5182D_DRV_NAME			"us5182d"
+
+#define US5182D_GA_RESOLUTION			1000
+
+#define US5182D_READ_BYTE			1
+#define US5182D_READ_WORD			2
+#define US5182D_OPSTORE_SLEEP_TIME		20 /* ms */
+#define US5182D_SLEEP_MS			3000 /* ms */
+#define US5182D_PXH_TH_DISABLE			0xffff
+#define US5182D_PXL_TH_DISABLE			0x0000
+
+/* Available ranges: [12354, 7065, 3998, 2202, 1285, 498, 256, 138] lux */
+static const int us5182d_scales[] = {188500, 107800, 61000, 33600, 19600, 7600,
+				     3900, 2100};
+
+/*
+ * Experimental thresholds that work with US5182D sensor on evaluation board
+ * roughly between 12-32 lux
+ */
+static u16 us5182d_dark_ths_vals[] = {170, 200, 512, 512, 800, 2000, 4000,
+				      8000};
+
+enum mode {
+	US5182D_ALS_PX,
+	US5182D_ALS_ONLY,
+	US5182D_PX_ONLY
+};
+
+enum pmode {
+	US5182D_CONTINUOUS,
+	US5182D_ONESHOT
+};
+
+struct us5182d_data {
+	struct i2c_client *client;
+	struct mutex lock;
+
+	/* Glass attenuation factor */
+	u32 ga;
+
+	/* Dark gain tuning */
+	u8 lower_dark_gain;
+	u8 upper_dark_gain;
+	u16 *us5182d_dark_ths;
+
+	u16 px_low_th;
+	u16 px_high_th;
+
+	int rising_en;
+	int falling_en;
+
+	u8 opmode;
+	u8 power_mode;
+
+	bool als_enabled;
+	bool px_enabled;
+
+	bool default_continuous;
+};
+
+static IIO_CONST_ATTR(in_illuminance_scale_available,
+		      "0.0021 0.0039 0.0076 0.0196 0.0336 0.061 0.1078 0.1885");
+
+static struct attribute *us5182d_attrs[] = {
+	&iio_const_attr_in_illuminance_scale_available.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group us5182d_attr_group = {
+	.attrs = us5182d_attrs,
+};
+
+static const struct {
+	u8 reg;
+	u8 val;
+} us5182d_regvals[] = {
+	{US5182D_REG_CFG0, US5182D_CFG0_WORD_ENABLE},
+	{US5182D_REG_CFG1, US5182D_CFG1_ALS_RES16},
+	{US5182D_REG_CFG2, (US5182D_CFG2_PX_RES16 |
+			    US5182D_CFG2_PXGAIN_DEFAULT)},
+	{US5182D_REG_CFG3, US5182D_CFG3_LED_CURRENT100 |
+			   US5182D_CFG3_INT_SOURCE_PX},
+	{US5182D_REG_CFG4, 0x00},
+};
+
+static const struct iio_event_spec us5182d_events[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_RISING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+				BIT(IIO_EV_INFO_ENABLE),
+	},
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_FALLING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+				BIT(IIO_EV_INFO_ENABLE),
+	},
+};
+
+static const struct iio_chan_spec us5182d_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+	},
+	{
+		.type = IIO_PROXIMITY,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.event_spec = us5182d_events,
+		.num_event_specs = ARRAY_SIZE(us5182d_events),
+	}
+};
+
+static int us5182d_oneshot_en(struct us5182d_data *data)
+{
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * In oneshot mode the chip will power itself down after taking the
+	 * required measurement.
+	 */
+	ret = ret | US5182D_CFG0_ONESHOT_EN;
+
+	return i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret);
+}
+
+static int us5182d_set_opmode(struct us5182d_data *data, u8 mode)
+{
+	int ret;
+
+	if (mode == data->opmode)
+		return 0;
+
+	ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0);
+	if (ret < 0)
+		return ret;
+
+	/* update mode */
+	ret = ret & ~US5182D_OPMODE_MASK;
+	ret = ret | (mode << US5182D_OPMODE_SHIFT);
+
+	/*
+	 * After updating the operating mode, the chip requires that
+	 * the operation is stored, by writing 1 in the STORE_MODE
+	 * register (auto-clearing).
+	 */
+	ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_MODE_STORE,
+					US5182D_STORE_MODE);
+	if (ret < 0)
+		return ret;
+
+	data->opmode = mode;
+	msleep(US5182D_OPSTORE_SLEEP_TIME);
+
+	return 0;
+}
+
+static int us5182d_als_enable(struct us5182d_data *data)
+{
+	int ret;
+	u8 mode;
+
+	if (data->power_mode == US5182D_ONESHOT) {
+		ret = us5182d_set_opmode(data, US5182D_ALS_ONLY);
+		if (ret < 0)
+			return ret;
+		data->px_enabled = false;
+	}
+
+	if (data->als_enabled)
+		return 0;
+
+	mode = data->px_enabled ? US5182D_ALS_PX : US5182D_ALS_ONLY;
+
+	ret = us5182d_set_opmode(data, mode);
+	if (ret < 0)
+		return ret;
+
+	data->als_enabled = true;
+
+	return 0;
+}
+
+static int us5182d_px_enable(struct us5182d_data *data)
+{
+	int ret;
+	u8 mode;
+
+	if (data->power_mode == US5182D_ONESHOT) {
+		ret = us5182d_set_opmode(data, US5182D_PX_ONLY);
+		if (ret < 0)
+			return ret;
+		data->als_enabled = false;
+	}
+
+	if (data->px_enabled)
+		return 0;
+
+	mode = data->als_enabled ? US5182D_ALS_PX : US5182D_PX_ONLY;
+
+	ret = us5182d_set_opmode(data, mode);
+	if (ret < 0)
+		return ret;
+
+	data->px_enabled = true;
+
+	return 0;
+}
+
+static int us5182d_get_als(struct us5182d_data *data)
+{
+	int ret;
+	unsigned long result;
+
+	ret = us5182d_als_enable(data);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_read_word_data(data->client,
+				       US5182D_REG_ADL);
+	if (ret < 0)
+		return ret;
+
+	result = ret * data->ga / US5182D_GA_RESOLUTION;
+	if (result > 0xffff)
+		result = 0xffff;
+
+	return result;
+}
+
+static int us5182d_get_px(struct us5182d_data *data)
+{
+	int ret;
+
+	ret = us5182d_px_enable(data);
+	if (ret < 0)
+		return ret;
+
+	return i2c_smbus_read_word_data(data->client,
+					US5182D_REG_PDL);
+}
+
+static int us5182d_shutdown_en(struct us5182d_data *data, u8 state)
+{
+	int ret;
+
+	if (data->power_mode == US5182D_ONESHOT)
+		return 0;
+
+	ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0);
+	if (ret < 0)
+		return ret;
+
+	ret = ret & ~US5182D_CFG0_SHUTDOWN_EN;
+	ret = ret | state;
+
+	ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret);
+	if (ret < 0)
+		return ret;
+
+	if (state & US5182D_CFG0_SHUTDOWN_EN) {
+		data->als_enabled = false;
+		data->px_enabled = false;
+	}
+
+	return ret;
+}
+
+
+static int us5182d_set_power_state(struct us5182d_data *data, bool on)
+{
+	int ret;
+
+	if (data->power_mode == US5182D_ONESHOT)
+		return 0;
+
+	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;
+}
+
+static int us5182d_read_value(struct us5182d_data *data,
+			      struct iio_chan_spec const *chan)
+{
+	int ret, value;
+
+	mutex_lock(&data->lock);
+
+	if (data->power_mode == US5182D_ONESHOT) {
+		ret = us5182d_oneshot_en(data);
+		if (ret < 0)
+			goto out_err;
+	}
+
+	ret = us5182d_set_power_state(data, true);
+	if (ret < 0)
+		goto out_err;
+
+	if (chan->type == IIO_LIGHT)
+		ret = us5182d_get_als(data);
+	else
+		ret = us5182d_get_px(data);
+	if (ret < 0)
+		goto out_poweroff;
+
+	value = ret;
+
+	ret = us5182d_set_power_state(data, false);
+	if (ret < 0)
+		goto out_err;
+
+	mutex_unlock(&data->lock);
+	return value;
+
+out_poweroff:
+	us5182d_set_power_state(data, false);
+out_err:
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+static int us5182d_read_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan, int *val,
+			    int *val2, long mask)
+{
+	struct us5182d_data *data = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = us5182d_read_value(data, chan);
+		if (ret < 0)
+			return ret;
+		*val = ret;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG1);
+		if (ret < 0)
+			return ret;
+		*val = 0;
+		*val2 = us5182d_scales[ret & US5182D_AGAIN_MASK];
+		return IIO_VAL_INT_PLUS_MICRO;
+	default:
+		return -EINVAL;
+	}
+}
+
+/**
+ * us5182d_update_dark_th - update Darh_Th registers
+ * @data	us5182d_data structure
+ * @index	index in us5182d_dark_ths array to use for the updated value
+ *
+ * Function needs to be called with a lock held because it needs two i2c write
+ * byte operations as these registers (0x27 0x28) don't work in word mode
+ * accessing.
+ */
+static int us5182d_update_dark_th(struct us5182d_data *data, int index)
+{
+	__be16 dark_th = cpu_to_be16(data->us5182d_dark_ths[index]);
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_UDARK_TH,
+					((u8 *)&dark_th)[0]);
+	if (ret < 0)
+		return ret;
+
+	return i2c_smbus_write_byte_data(data->client, US5182D_REG_UDARK_TH + 1,
+					((u8 *)&dark_th)[1]);
+}
+
+/**
+ * us5182d_apply_scale - update the ALS scale
+ * @data	us5182d_data structure
+ * @index	index in us5182d_scales array to use for the updated value
+ *
+ * Function needs to be called with a lock held as we're having more than one
+ * i2c operation.
+ */
+static int us5182d_apply_scale(struct us5182d_data *data, int index)
+{
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG1);
+	if (ret < 0)
+		return ret;
+
+	ret = ret & (~US5182D_AGAIN_MASK);
+	ret |= index;
+
+	ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG1, ret);
+	if (ret < 0)
+		return ret;
+
+	return us5182d_update_dark_th(data, index);
+}
+
+static int us5182d_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan, int val,
+			     int val2, long mask)
+{
+	struct us5182d_data *data = iio_priv(indio_dev);
+	int ret, i;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		if (val != 0)
+			return -EINVAL;
+		for (i = 0; i < ARRAY_SIZE(us5182d_scales); i++)
+			if (val2 == us5182d_scales[i]) {
+				mutex_lock(&data->lock);
+				ret = us5182d_apply_scale(data, i);
+				mutex_unlock(&data->lock);
+				return ret;
+			}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return -EINVAL;
+}
+
+static int us5182d_setup_prox(struct iio_dev *indio_dev,
+			      enum iio_event_direction dir, u16 val)
+{
+	struct us5182d_data *data = iio_priv(indio_dev);
+
+	if (dir == IIO_EV_DIR_FALLING)
+		return i2c_smbus_write_word_data(data->client,
+						 US5182D_REG_PXL_TH, val);
+	else if (dir == IIO_EV_DIR_RISING)
+		return i2c_smbus_write_word_data(data->client,
+						 US5182D_REG_PXH_TH, val);
+
+	return 0;
+}
+
+static int us5182d_read_thresh(struct iio_dev *indio_dev,
+	const struct iio_chan_spec *chan, enum iio_event_type type,
+	enum iio_event_direction dir, enum iio_event_info info, int *val,
+	int *val2)
+{
+	struct us5182d_data *data = iio_priv(indio_dev);
+
+	switch (dir) {
+	case IIO_EV_DIR_RISING:
+		mutex_lock(&data->lock);
+		*val = data->px_high_th;
+		mutex_unlock(&data->lock);
+		break;
+	case IIO_EV_DIR_FALLING:
+		mutex_lock(&data->lock);
+		*val = data->px_low_th;
+		mutex_unlock(&data->lock);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return IIO_VAL_INT;
+}
+
+static int us5182d_write_thresh(struct iio_dev *indio_dev,
+	const struct iio_chan_spec *chan, enum iio_event_type type,
+	enum iio_event_direction dir, enum iio_event_info info, int val,
+	int val2)
+{
+	struct us5182d_data *data = iio_priv(indio_dev);
+	int ret;
+
+	if (val < 0 || val > USHRT_MAX || val2 != 0)
+		return -EINVAL;
+
+	switch (dir) {
+	case IIO_EV_DIR_RISING:
+		mutex_lock(&data->lock);
+		if (data->rising_en) {
+			ret = us5182d_setup_prox(indio_dev, dir, val);
+			if (ret < 0)
+				goto err;
+		}
+		data->px_high_th = val;
+		mutex_unlock(&data->lock);
+		break;
+	case IIO_EV_DIR_FALLING:
+		mutex_lock(&data->lock);
+		if (data->falling_en) {
+			ret = us5182d_setup_prox(indio_dev, dir, val);
+			if (ret < 0)
+				goto err;
+		}
+		data->px_low_th = val;
+		mutex_unlock(&data->lock);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+err:
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+static int us5182d_read_event_config(struct iio_dev *indio_dev,
+	const struct iio_chan_spec *chan, enum iio_event_type type,
+	enum iio_event_direction dir)
+{
+	struct us5182d_data *data = iio_priv(indio_dev);
+	int ret;
+
+	switch (dir) {
+	case IIO_EV_DIR_RISING:
+		mutex_lock(&data->lock);
+		ret = data->rising_en;
+		mutex_unlock(&data->lock);
+		break;
+	case IIO_EV_DIR_FALLING:
+		mutex_lock(&data->lock);
+		ret = data->falling_en;
+		mutex_unlock(&data->lock);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int us5182d_write_event_config(struct iio_dev *indio_dev,
+	const struct iio_chan_spec *chan, enum iio_event_type type,
+	enum iio_event_direction dir, int state)
+{
+	struct us5182d_data *data = iio_priv(indio_dev);
+	int ret;
+	u16 new_th;
+
+	mutex_lock(&data->lock);
+
+	switch (dir) {
+	case IIO_EV_DIR_RISING:
+		if (data->rising_en == state) {
+			mutex_unlock(&data->lock);
+			return 0;
+		}
+		new_th = US5182D_PXH_TH_DISABLE;
+		if (state) {
+			data->power_mode = US5182D_CONTINUOUS;
+			ret = us5182d_set_power_state(data, true);
+			if (ret < 0)
+				goto err;
+			ret = us5182d_px_enable(data);
+			if (ret < 0)
+				goto err_poweroff;
+			new_th = data->px_high_th;
+		}
+		ret = us5182d_setup_prox(indio_dev, dir, new_th);
+		if (ret < 0)
+			goto err_poweroff;
+		data->rising_en = state;
+		break;
+	case IIO_EV_DIR_FALLING:
+		if (data->falling_en == state) {
+			mutex_unlock(&data->lock);
+			return 0;
+		}
+		new_th =  US5182D_PXL_TH_DISABLE;
+		if (state) {
+			data->power_mode = US5182D_CONTINUOUS;
+			ret = us5182d_set_power_state(data, true);
+			if (ret < 0)
+				goto err;
+			ret = us5182d_px_enable(data);
+			if (ret < 0)
+				goto err_poweroff;
+			new_th = data->px_low_th;
+		}
+		ret = us5182d_setup_prox(indio_dev, dir, new_th);
+		if (ret < 0)
+			goto err_poweroff;
+		data->falling_en = state;
+		break;
+	default:
+		ret = -EINVAL;
+		goto err;
+	}
+
+	if (!state) {
+		ret = us5182d_set_power_state(data, false);
+		if (ret < 0)
+			goto err;
+	}
+
+	if (!data->falling_en && !data->rising_en && !data->default_continuous)
+		data->power_mode = US5182D_ONESHOT;
+
+	mutex_unlock(&data->lock);
+	return 0;
+
+err_poweroff:
+	if (state)
+		us5182d_set_power_state(data, false);
+err:
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+static const struct iio_info us5182d_info = {
+	.read_raw = us5182d_read_raw,
+	.write_raw = us5182d_write_raw,
+	.attrs = &us5182d_attr_group,
+	.read_event_value = &us5182d_read_thresh,
+	.write_event_value = &us5182d_write_thresh,
+	.read_event_config = &us5182d_read_event_config,
+	.write_event_config = &us5182d_write_event_config,
+};
+
+static int us5182d_reset(struct iio_dev *indio_dev)
+{
+	struct us5182d_data *data = iio_priv(indio_dev);
+
+	return i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG3,
+					 US5182D_RESET_CHIP);
+}
+
+static int us5182d_init(struct iio_dev *indio_dev)
+{
+	struct us5182d_data *data = iio_priv(indio_dev);
+	int i, ret;
+
+	ret = us5182d_reset(indio_dev);
+	if (ret < 0)
+		return ret;
+
+	data->opmode = 0;
+	data->power_mode = US5182D_CONTINUOUS;
+	data->px_low_th = US5182D_REG_PXL_TH_DEFAULT;
+	data->px_high_th = US5182D_REG_PXH_TH_DEFAULT;
+
+	for (i = 0; i < ARRAY_SIZE(us5182d_regvals); i++) {
+		ret = i2c_smbus_write_byte_data(data->client,
+						us5182d_regvals[i].reg,
+						us5182d_regvals[i].val);
+		if (ret < 0)
+			return ret;
+	}
+
+	data->als_enabled = true;
+	data->px_enabled = true;
+
+	if (!data->default_continuous) {
+		ret = us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN);
+		if (ret < 0)
+			return ret;
+		data->power_mode = US5182D_ONESHOT;
+	}
+
+	return ret;
+}
+
+static void us5182d_get_platform_data(struct iio_dev *indio_dev)
+{
+	struct us5182d_data *data = iio_priv(indio_dev);
+
+	if (device_property_read_u32(&data->client->dev, "upisemi,glass-coef",
+				     &data->ga))
+		data->ga = US5182D_GA_RESOLUTION;
+	if (device_property_read_u16_array(&data->client->dev,
+					   "upisemi,dark-ths",
+					   data->us5182d_dark_ths,
+					   ARRAY_SIZE(us5182d_dark_ths_vals)))
+		data->us5182d_dark_ths = us5182d_dark_ths_vals;
+	if (device_property_read_u8(&data->client->dev,
+				    "upisemi,upper-dark-gain",
+				    &data->upper_dark_gain))
+		data->upper_dark_gain = US5182D_REG_AUTO_HDARK_GAIN_DEFAULT;
+	if (device_property_read_u8(&data->client->dev,
+				    "upisemi,lower-dark-gain",
+				    &data->lower_dark_gain))
+		data->lower_dark_gain = US5182D_REG_AUTO_LDARK_GAIN_DEFAULT;
+	data->default_continuous = device_property_read_bool(&data->client->dev,
+							     "upisemi,continuous");
+}
+
+static int  us5182d_dark_gain_config(struct iio_dev *indio_dev)
+{
+	struct us5182d_data *data = iio_priv(indio_dev);
+	int ret;
+
+	ret = us5182d_update_dark_th(data, US5182D_CFG1_AGAIN_DEFAULT);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_write_byte_data(data->client,
+					US5182D_REG_AUTO_LDARK_GAIN,
+					data->lower_dark_gain);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_write_byte_data(data->client,
+					US5182D_REG_AUTO_HDARK_GAIN,
+					data->upper_dark_gain);
+	if (ret < 0)
+		return ret;
+
+	return i2c_smbus_write_byte_data(data->client, US5182D_REG_DARK_AUTO_EN,
+					 US5182D_REG_DARK_AUTO_EN_DEFAULT);
+}
+
+static irqreturn_t us5182d_irq_thread_handler(int irq, void *private)
+{
+	struct iio_dev *indio_dev = private;
+	struct us5182d_data *data = iio_priv(indio_dev);
+	enum iio_event_direction dir;
+	int ret;
+	u64 ev;
+
+	ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0);
+	if (ret < 0) {
+		dev_err(&data->client->dev, "i2c transfer error in irq\n");
+		return IRQ_HANDLED;
+	}
+
+	dir = ret & US5182D_CFG0_PROX ? IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING;
+	ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1, IIO_EV_TYPE_THRESH, dir);
+
+	iio_push_event(indio_dev, ev, iio_get_time_ns(indio_dev));
+
+	ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0,
+					ret & ~US5182D_CFG0_PX_IRQ);
+	if (ret < 0)
+		dev_err(&data->client->dev, "i2c transfer error in irq\n");
+
+	return IRQ_HANDLED;
+}
+
+static int us5182d_probe(struct i2c_client *client,
+			 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;
+	}
+
+	if (client->irq > 0) {
+		ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+						us5182d_irq_thread_handler,
+						IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+						"us5182d-irq", indio_dev);
+		if (ret < 0)
+			return ret;
+	} else
+		dev_warn(&client->dev, "no valid irq found\n");
+
+	us5182d_get_platform_data(indio_dev);
+	ret = us5182d_init(indio_dev);
+	if (ret < 0)
+		return ret;
+
+	ret = us5182d_dark_gain_config(indio_dev);
+	if (ret < 0)
+		goto out_err;
+
+	if (data->default_continuous) {
+		ret = pm_runtime_set_active(&client->dev);
+		if (ret < 0)
+			goto out_err;
+	}
+
+	pm_runtime_enable(&client->dev);
+	pm_runtime_set_autosuspend_delay(&client->dev,
+					 US5182D_SLEEP_MS);
+	pm_runtime_use_autosuspend(&client->dev);
+
+	ret = iio_device_register(indio_dev);
+	if (ret < 0)
+		goto out_err;
+
+	return 0;
+
+out_err:
+	us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN);
+	return ret;
+
+}
+
+static int us5182d_remove(struct i2c_client *client)
+{
+	struct us5182d_data *data = iio_priv(i2c_get_clientdata(client));
+
+	iio_device_unregister(i2c_get_clientdata(client));
+
+	pm_runtime_disable(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+
+	return us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN);
+}
+
+#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM)
+static int us5182d_suspend(struct device *dev)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+	struct us5182d_data *data = iio_priv(indio_dev);
+
+	if (data->power_mode == US5182D_CONTINUOUS)
+		return us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN);
+
+	return 0;
+}
+
+static int us5182d_resume(struct device *dev)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+	struct us5182d_data *data = iio_priv(indio_dev);
+
+	if (data->power_mode == US5182D_CONTINUOUS)
+		return us5182d_shutdown_en(data,
+					   ~US5182D_CFG0_SHUTDOWN_EN & 0xff);
+
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops us5182d_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(us5182d_suspend, us5182d_resume)
+	SET_RUNTIME_PM_OPS(us5182d_suspend, us5182d_resume, NULL)
+};
+
+static const struct acpi_device_id us5182d_acpi_match[] = {
+	{ "USD5182", 0},
+	{}
+};
+
+MODULE_DEVICE_TABLE(acpi, us5182d_acpi_match);
+
+static const struct i2c_device_id us5182d_id[] = {
+		{"usd5182", 0},
+		{}
+};
+
+MODULE_DEVICE_TABLE(i2c, us5182d_id);
+
+static const struct of_device_id us5182d_of_match[] = {
+	{ .compatible = "upisemi,usd5182" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, us5182d_of_match);
+
+static struct i2c_driver us5182d_driver = {
+	.driver = {
+		.name = US5182D_DRV_NAME,
+		.pm = &us5182d_pm_ops,
+		.of_match_table = us5182d_of_match,
+		.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/drivers/iio/light/vcnl4000.c b/drivers/iio/light/vcnl4000.c
new file mode 100644
index 0000000..04fd0d4
--- /dev/null
+++ b/drivers/iio/light/vcnl4000.c
@@ -0,0 +1,378 @@
+/*
+ * vcnl4000.c - Support for Vishay VCNL4000/4010/4020/4200 combined ambient
+ * light and proximity sensor
+ *
+ * Copyright 2012 Peter Meerwald <pmeerw@pmeerw.net>
+ *
+ * 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:
+ *   VCNL4000/10/20 (7-bit I2C slave address 0x13)
+ *   VCNL4200 (7-bit I2C slave address 0x51)
+ *
+ * TODO:
+ *   allow to adjust IR current
+ *   proximity threshold and event handling
+ *   periodic ALS/proximity measurement (VCNL4010/20)
+ *   interrupts (VCNL4010/20, VCNL4200)
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define VCNL4000_DRV_NAME "vcnl4000"
+#define VCNL4000_PROD_ID	0x01
+#define VCNL4010_PROD_ID	0x02 /* for VCNL4020, VCNL4010 */
+#define VCNL4200_PROD_ID	0x58
+
+#define VCNL4000_COMMAND	0x80 /* Command register */
+#define VCNL4000_PROD_REV	0x81 /* Product ID and Revision ID */
+#define VCNL4000_LED_CURRENT	0x83 /* IR LED current for proximity mode */
+#define VCNL4000_AL_PARAM	0x84 /* Ambient light parameter register */
+#define VCNL4000_AL_RESULT_HI	0x85 /* Ambient light result register, MSB */
+#define VCNL4000_AL_RESULT_LO	0x86 /* Ambient light result register, LSB */
+#define VCNL4000_PS_RESULT_HI	0x87 /* Proximity result register, MSB */
+#define VCNL4000_PS_RESULT_LO	0x88 /* Proximity result register, LSB */
+#define VCNL4000_PS_MEAS_FREQ	0x89 /* Proximity test signal frequency */
+#define VCNL4000_PS_MOD_ADJ	0x8a /* Proximity modulator timing adjustment */
+
+#define VCNL4200_AL_CONF	0x00 /* Ambient light configuration */
+#define VCNL4200_PS_CONF1	0x03 /* Proximity configuration */
+#define VCNL4200_PS_DATA	0x08 /* Proximity data */
+#define VCNL4200_AL_DATA	0x09 /* Ambient light data */
+#define VCNL4200_DEV_ID		0x0e /* Device ID, slave address and version */
+
+/* Bit masks for COMMAND register */
+#define VCNL4000_AL_RDY		BIT(6) /* ALS data ready? */
+#define VCNL4000_PS_RDY		BIT(5) /* proximity data ready? */
+#define VCNL4000_AL_OD		BIT(4) /* start on-demand ALS measurement */
+#define VCNL4000_PS_OD		BIT(3) /* start on-demand proximity measurement */
+
+enum vcnl4000_device_ids {
+	VCNL4000,
+	VCNL4010,
+	VCNL4200,
+};
+
+struct vcnl4200_channel {
+	u8 reg;
+	ktime_t last_measurement;
+	ktime_t sampling_rate;
+	struct mutex lock;
+};
+
+struct vcnl4000_data {
+	struct i2c_client *client;
+	enum vcnl4000_device_ids id;
+	int rev;
+	int al_scale;
+	const struct vcnl4000_chip_spec *chip_spec;
+	struct mutex vcnl4000_lock;
+	struct vcnl4200_channel vcnl4200_al;
+	struct vcnl4200_channel vcnl4200_ps;
+};
+
+struct vcnl4000_chip_spec {
+	const char *prod;
+	int (*init)(struct vcnl4000_data *data);
+	int (*measure_light)(struct vcnl4000_data *data, int *val);
+	int (*measure_proximity)(struct vcnl4000_data *data, int *val);
+};
+
+static const struct i2c_device_id vcnl4000_id[] = {
+	{ "vcnl4000", VCNL4000 },
+	{ "vcnl4010", VCNL4010 },
+	{ "vcnl4020", VCNL4010 },
+	{ "vcnl4200", VCNL4200 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, vcnl4000_id);
+
+static int vcnl4000_init(struct vcnl4000_data *data)
+{
+	int ret, prod_id;
+
+	ret = i2c_smbus_read_byte_data(data->client, VCNL4000_PROD_REV);
+	if (ret < 0)
+		return ret;
+
+	prod_id = ret >> 4;
+	switch (prod_id) {
+	case VCNL4000_PROD_ID:
+		if (data->id != VCNL4000)
+			dev_warn(&data->client->dev,
+					"wrong device id, use vcnl4000");
+		break;
+	case VCNL4010_PROD_ID:
+		if (data->id != VCNL4010)
+			dev_warn(&data->client->dev,
+					"wrong device id, use vcnl4010/4020");
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	data->rev = ret & 0xf;
+	data->al_scale = 250000;
+	mutex_init(&data->vcnl4000_lock);
+
+	return 0;
+};
+
+static int vcnl4200_init(struct vcnl4000_data *data)
+{
+	int ret;
+
+	ret = i2c_smbus_read_word_data(data->client, VCNL4200_DEV_ID);
+	if (ret < 0)
+		return ret;
+
+	if ((ret & 0xff) != VCNL4200_PROD_ID)
+		return -ENODEV;
+
+	data->rev = (ret >> 8) & 0xf;
+
+	/* Set defaults and enable both channels */
+	ret = i2c_smbus_write_byte_data(data->client, VCNL4200_AL_CONF, 0x00);
+	if (ret < 0)
+		return ret;
+	ret = i2c_smbus_write_byte_data(data->client, VCNL4200_PS_CONF1, 0x00);
+	if (ret < 0)
+		return ret;
+
+	data->al_scale = 24000;
+	data->vcnl4200_al.reg = VCNL4200_AL_DATA;
+	data->vcnl4200_ps.reg = VCNL4200_PS_DATA;
+	/* Integration time is 50ms, but the experiments show 54ms in total. */
+	data->vcnl4200_al.sampling_rate = ktime_set(0, 54000 * 1000);
+	data->vcnl4200_ps.sampling_rate = ktime_set(0, 4200 * 1000);
+	data->vcnl4200_al.last_measurement = ktime_set(0, 0);
+	data->vcnl4200_ps.last_measurement = ktime_set(0, 0);
+	mutex_init(&data->vcnl4200_al.lock);
+	mutex_init(&data->vcnl4200_ps.lock);
+
+	return 0;
+};
+
+static int vcnl4000_measure(struct vcnl4000_data *data, u8 req_mask,
+				u8 rdy_mask, u8 data_reg, int *val)
+{
+	int tries = 20;
+	__be16 buf;
+	int ret;
+
+	mutex_lock(&data->vcnl4000_lock);
+
+	ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND,
+					req_mask);
+	if (ret < 0)
+		goto fail;
+
+	/* wait for data to become ready */
+	while (tries--) {
+		ret = i2c_smbus_read_byte_data(data->client, VCNL4000_COMMAND);
+		if (ret < 0)
+			goto fail;
+		if (ret & rdy_mask)
+			break;
+		msleep(20); /* measurement takes up to 100 ms */
+	}
+
+	if (tries < 0) {
+		dev_err(&data->client->dev,
+			"vcnl4000_measure() failed, data not ready\n");
+		ret = -EIO;
+		goto fail;
+	}
+
+	ret = i2c_smbus_read_i2c_block_data(data->client,
+		data_reg, sizeof(buf), (u8 *) &buf);
+	if (ret < 0)
+		goto fail;
+
+	mutex_unlock(&data->vcnl4000_lock);
+	*val = be16_to_cpu(buf);
+
+	return 0;
+
+fail:
+	mutex_unlock(&data->vcnl4000_lock);
+	return ret;
+}
+
+static int vcnl4200_measure(struct vcnl4000_data *data,
+		struct vcnl4200_channel *chan, int *val)
+{
+	int ret;
+	s64 delta;
+	ktime_t next_measurement;
+
+	mutex_lock(&chan->lock);
+
+	next_measurement = ktime_add(chan->last_measurement,
+			chan->sampling_rate);
+	delta = ktime_us_delta(next_measurement, ktime_get());
+	if (delta > 0)
+		usleep_range(delta, delta + 500);
+	chan->last_measurement = ktime_get();
+
+	mutex_unlock(&chan->lock);
+
+	ret = i2c_smbus_read_word_data(data->client, chan->reg);
+	if (ret < 0)
+		return ret;
+
+	*val = ret;
+
+	return 0;
+}
+
+static int vcnl4000_measure_light(struct vcnl4000_data *data, int *val)
+{
+	return vcnl4000_measure(data,
+			VCNL4000_AL_OD, VCNL4000_AL_RDY,
+			VCNL4000_AL_RESULT_HI, val);
+}
+
+static int vcnl4200_measure_light(struct vcnl4000_data *data, int *val)
+{
+	return vcnl4200_measure(data, &data->vcnl4200_al, val);
+}
+
+static int vcnl4000_measure_proximity(struct vcnl4000_data *data, int *val)
+{
+	return vcnl4000_measure(data,
+			VCNL4000_PS_OD, VCNL4000_PS_RDY,
+			VCNL4000_PS_RESULT_HI, val);
+}
+
+static int vcnl4200_measure_proximity(struct vcnl4000_data *data, int *val)
+{
+	return vcnl4200_measure(data, &data->vcnl4200_ps, val);
+}
+
+static const struct vcnl4000_chip_spec vcnl4000_chip_spec_cfg[] = {
+	[VCNL4000] = {
+		.prod = "VCNL4000",
+		.init = vcnl4000_init,
+		.measure_light = vcnl4000_measure_light,
+		.measure_proximity = vcnl4000_measure_proximity,
+	},
+	[VCNL4010] = {
+		.prod = "VCNL4010/4020",
+		.init = vcnl4000_init,
+		.measure_light = vcnl4000_measure_light,
+		.measure_proximity = vcnl4000_measure_proximity,
+	},
+	[VCNL4200] = {
+		.prod = "VCNL4200",
+		.init = vcnl4200_init,
+		.measure_light = vcnl4200_measure_light,
+		.measure_proximity = vcnl4200_measure_proximity,
+	},
+};
+
+static const struct iio_chan_spec vcnl4000_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+			BIT(IIO_CHAN_INFO_SCALE),
+	}, {
+		.type = IIO_PROXIMITY,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+	}
+};
+
+static int vcnl4000_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val, int *val2, long mask)
+{
+	int ret;
+	struct vcnl4000_data *data = iio_priv(indio_dev);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		switch (chan->type) {
+		case IIO_LIGHT:
+			ret = data->chip_spec->measure_light(data, val);
+			if (ret < 0)
+				return ret;
+			return IIO_VAL_INT;
+		case IIO_PROXIMITY:
+			ret = data->chip_spec->measure_proximity(data, val);
+			if (ret < 0)
+				return ret;
+			return IIO_VAL_INT;
+		default:
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_SCALE:
+		if (chan->type != IIO_LIGHT)
+			return -EINVAL;
+
+		*val = 0;
+		*val2 = data->al_scale;
+		return IIO_VAL_INT_PLUS_MICRO;
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info vcnl4000_info = {
+	.read_raw = vcnl4000_read_raw,
+};
+
+static int vcnl4000_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct vcnl4000_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;
+	data->id = id->driver_data;
+	data->chip_spec = &vcnl4000_chip_spec_cfg[data->id];
+
+	ret = data->chip_spec->init(data);
+	if (ret < 0)
+		return ret;
+
+	dev_dbg(&client->dev, "%s Ambient light/proximity sensor, Rev: %02x\n",
+		data->chip_spec->prod, data->rev);
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->info = &vcnl4000_info;
+	indio_dev->channels = vcnl4000_channels;
+	indio_dev->num_channels = ARRAY_SIZE(vcnl4000_channels);
+	indio_dev->name = VCNL4000_DRV_NAME;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static struct i2c_driver vcnl4000_driver = {
+	.driver = {
+		.name   = VCNL4000_DRV_NAME,
+	},
+	.probe  = vcnl4000_probe,
+	.id_table = vcnl4000_id,
+};
+
+module_i2c_driver(vcnl4000_driver);
+
+MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
+MODULE_DESCRIPTION("Vishay VCNL4000 proximity/ambient light sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/veml6070.c b/drivers/iio/light/veml6070.c
new file mode 100644
index 0000000..f4bf3c5
--- /dev/null
+++ b/drivers/iio/light/veml6070.c
@@ -0,0 +1,217 @@
+/*
+ * veml6070.c - Support for Vishay VEML6070 UV A light sensor
+ *
+ * Copyright 2016 Peter Meerwald-Stadler <pmeerw@pmeerw.net>
+ *
+ * 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 VEML6070 (7-bit I2C slave addresses 0x38 and 0x39)
+ *
+ * TODO: integration time, ACK signal
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define VEML6070_DRV_NAME "veml6070"
+
+#define VEML6070_ADDR_CONFIG_DATA_MSB 0x38 /* read: MSB data, write: config */
+#define VEML6070_ADDR_DATA_LSB	0x39 /* LSB data */
+
+#define VEML6070_COMMAND_ACK	BIT(5) /* raise interrupt when over threshold */
+#define VEML6070_COMMAND_IT	GENMASK(3, 2) /* bit mask integration time */
+#define VEML6070_COMMAND_RSRVD	BIT(1) /* reserved, set to 1 */
+#define VEML6070_COMMAND_SD	BIT(0) /* shutdown mode when set */
+
+#define VEML6070_IT_10	0x04 /* integration time 1x */
+
+struct veml6070_data {
+	struct i2c_client *client1;
+	struct i2c_client *client2;
+	u8 config;
+	struct mutex lock;
+};
+
+static int veml6070_read(struct veml6070_data *data)
+{
+	int ret;
+	u8 msb, lsb;
+
+	mutex_lock(&data->lock);
+
+	/* disable shutdown */
+	ret = i2c_smbus_write_byte(data->client1,
+	    data->config & ~VEML6070_COMMAND_SD);
+	if (ret < 0)
+		goto out;
+
+	msleep(125 + 10); /* measurement takes up to 125 ms for IT 1x */
+
+	ret = i2c_smbus_read_byte(data->client2); /* read MSB, address 0x39 */
+	if (ret < 0)
+		goto out;
+	msb = ret;
+
+	ret = i2c_smbus_read_byte(data->client1); /* read LSB, address 0x38 */
+	if (ret < 0)
+		goto out;
+	lsb = ret;
+
+	/* shutdown again */
+	ret = i2c_smbus_write_byte(data->client1, data->config);
+	if (ret < 0)
+		goto out;
+
+	ret = (msb << 8) | lsb;
+
+out:
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+static const struct iio_chan_spec veml6070_channels[] = {
+	{
+		.type = IIO_INTENSITY,
+		.modified = 1,
+		.channel2 = IIO_MOD_LIGHT_UV,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+	},
+	{
+		.type = IIO_UVINDEX,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+	}
+};
+
+static int veml6070_to_uv_index(unsigned val)
+{
+	/*
+	 * conversion of raw UV intensity values to UV index depends on
+	 * integration time (IT) and value of the resistor connected to
+	 * the RSET pin (default: 270 KOhm)
+	 */
+	unsigned uvi[11] = {
+		187, 373, 560, /* low */
+		746, 933, 1120, /* moderate */
+		1308, 1494, /* high */
+		1681, 1868, 2054}; /* very high */
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(uvi); i++)
+		if (val <= uvi[i])
+			return i;
+
+	return 11; /* extreme */
+}
+
+static int veml6070_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val, int *val2, long mask)
+{
+	struct veml6070_data *data = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+	case IIO_CHAN_INFO_PROCESSED:
+		ret = veml6070_read(data);
+		if (ret < 0)
+			return ret;
+		if (mask == IIO_CHAN_INFO_PROCESSED)
+			*val = veml6070_to_uv_index(ret);
+		else
+			*val = ret;
+		return IIO_VAL_INT;
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info veml6070_info = {
+	.read_raw = veml6070_read_raw,
+};
+
+static int veml6070_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct veml6070_data *data;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->client1 = client;
+	mutex_init(&data->lock);
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->info = &veml6070_info;
+	indio_dev->channels = veml6070_channels;
+	indio_dev->num_channels = ARRAY_SIZE(veml6070_channels);
+	indio_dev->name = VEML6070_DRV_NAME;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	data->client2 = i2c_new_dummy(client->adapter, VEML6070_ADDR_DATA_LSB);
+	if (!data->client2) {
+		dev_err(&client->dev, "i2c device for second chip address failed\n");
+		return -ENODEV;
+	}
+
+	data->config = VEML6070_IT_10 | VEML6070_COMMAND_RSRVD |
+		VEML6070_COMMAND_SD;
+	ret = i2c_smbus_write_byte(data->client1, data->config);
+	if (ret < 0)
+		goto fail;
+
+	ret = iio_device_register(indio_dev);
+	if (ret < 0)
+		goto fail;
+
+	return ret;
+
+fail:
+	i2c_unregister_device(data->client2);
+	return ret;
+}
+
+static int veml6070_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+	struct veml6070_data *data = iio_priv(indio_dev);
+
+	iio_device_unregister(indio_dev);
+	i2c_unregister_device(data->client2);
+
+	return 0;
+}
+
+static const struct i2c_device_id veml6070_id[] = {
+	{ "veml6070", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, veml6070_id);
+
+static struct i2c_driver veml6070_driver = {
+	.driver = {
+		.name   = VEML6070_DRV_NAME,
+	},
+	.probe  = veml6070_probe,
+	.remove  = veml6070_remove,
+	.id_table = veml6070_id,
+};
+
+module_i2c_driver(veml6070_driver);
+
+MODULE_AUTHOR("Peter Meerwald-Stadler <pmeerw@pmeerw.net>");
+MODULE_DESCRIPTION("Vishay VEML6070 UV A light sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/vl6180.c b/drivers/iio/light/vl6180.c
new file mode 100644
index 0000000..192c77e
--- /dev/null
+++ b/drivers/iio/light/vl6180.c
@@ -0,0 +1,554 @@
+/*
+ * vl6180.c - Support for STMicroelectronics VL6180 ALS, range and proximity
+ * sensor
+ *
+ * Copyright 2017 Peter Meerwald-Stadler <pmeerw@pmeerw.net>
+ * Copyright 2017 Manivannan Sadhasivam <manivannanece23@gmail.com>
+ *
+ * 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 VL6180 (7-bit I2C slave address 0x29)
+ *
+ * Range: 0 to 100mm
+ * ALS: < 1 Lux up to 100 kLux
+ * IR: 850nm
+ *
+ * TODO: irq, threshold events, continuous mode, hardware buffer
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <linux/util_macros.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define VL6180_DRV_NAME "vl6180"
+
+/* Device identification register and value */
+#define VL6180_MODEL_ID	0x000
+#define VL6180_MODEL_ID_VAL 0xb4
+
+/* Configuration registers */
+#define VL6180_INTR_CONFIG 0x014
+#define VL6180_INTR_CLEAR 0x015
+#define VL6180_OUT_OF_RESET 0x016
+#define VL6180_HOLD 0x017
+#define VL6180_RANGE_START 0x018
+#define VL6180_ALS_START 0x038
+#define VL6180_ALS_GAIN 0x03f
+#define VL6180_ALS_IT 0x040
+
+/* Status registers */
+#define VL6180_RANGE_STATUS 0x04d
+#define VL6180_ALS_STATUS 0x04e
+#define VL6180_INTR_STATUS 0x04f
+
+/* Result value registers */
+#define VL6180_ALS_VALUE 0x050
+#define VL6180_RANGE_VALUE 0x062
+#define VL6180_RANGE_RATE 0x066
+
+/* bits of the RANGE_START and ALS_START register */
+#define VL6180_MODE_CONT BIT(1) /* continuous mode */
+#define VL6180_STARTSTOP BIT(0) /* start measurement, auto-reset */
+
+/* bits of the INTR_STATUS and INTR_CONFIG register */
+#define VL6180_ALS_READY BIT(5)
+#define VL6180_RANGE_READY BIT(2)
+
+/* bits of the INTR_CLEAR register */
+#define VL6180_CLEAR_ERROR BIT(2)
+#define VL6180_CLEAR_ALS BIT(1)
+#define VL6180_CLEAR_RANGE BIT(0)
+
+/* bits of the HOLD register */
+#define VL6180_HOLD_ON BIT(0)
+
+/* default value for the ALS_IT register */
+#define VL6180_ALS_IT_100 0x63 /* 100 ms */
+
+/* values for the ALS_GAIN register */
+#define VL6180_ALS_GAIN_1 0x46
+#define VL6180_ALS_GAIN_1_25 0x45
+#define VL6180_ALS_GAIN_1_67 0x44
+#define VL6180_ALS_GAIN_2_5 0x43
+#define VL6180_ALS_GAIN_5 0x42
+#define VL6180_ALS_GAIN_10 0x41
+#define VL6180_ALS_GAIN_20 0x40
+#define VL6180_ALS_GAIN_40 0x47
+
+struct vl6180_data {
+	struct i2c_client *client;
+	struct mutex lock;
+	unsigned int als_gain_milli;
+	unsigned int als_it_ms;
+};
+
+enum { VL6180_ALS, VL6180_RANGE, VL6180_PROX };
+
+/**
+ * struct vl6180_chan_regs - Registers for accessing channels
+ * @drdy_mask:			Data ready bit in status register
+ * @start_reg:			Conversion start register
+ * @value_reg:			Result value register
+ * @word:			Register word length
+ */
+struct vl6180_chan_regs {
+	u8 drdy_mask;
+	u16 start_reg, value_reg;
+	bool word;
+};
+
+static const struct vl6180_chan_regs vl6180_chan_regs_table[] = {
+	[VL6180_ALS] = {
+		.drdy_mask = VL6180_ALS_READY,
+		.start_reg = VL6180_ALS_START,
+		.value_reg = VL6180_ALS_VALUE,
+		.word = true,
+	},
+	[VL6180_RANGE] = {
+		.drdy_mask = VL6180_RANGE_READY,
+		.start_reg = VL6180_RANGE_START,
+		.value_reg = VL6180_RANGE_VALUE,
+		.word = false,
+	},
+	[VL6180_PROX] = {
+		.drdy_mask = VL6180_RANGE_READY,
+		.start_reg = VL6180_RANGE_START,
+		.value_reg = VL6180_RANGE_RATE,
+		.word = true,
+	},
+};
+
+static int vl6180_read(struct i2c_client *client, u16 cmd, void *databuf,
+		       u8 len)
+{
+	__be16 cmdbuf = cpu_to_be16(cmd);
+	struct i2c_msg msgs[2] = {
+		{ .addr = client->addr, .len = sizeof(cmdbuf), .buf = (u8 *) &cmdbuf },
+		{ .addr = client->addr, .len = len, .buf = databuf,
+		  .flags = I2C_M_RD } };
+	int ret;
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret < 0)
+		dev_err(&client->dev, "failed reading register 0x%04x\n", cmd);
+
+	return ret;
+}
+
+static int vl6180_read_byte(struct i2c_client *client, u16 cmd)
+{
+	u8 data;
+	int ret;
+
+	ret = vl6180_read(client, cmd, &data, sizeof(data));
+	if (ret < 0)
+		return ret;
+
+	return data;
+}
+
+static int vl6180_read_word(struct i2c_client *client, u16 cmd)
+{
+	__be16 data;
+	int ret;
+
+	ret = vl6180_read(client, cmd, &data, sizeof(data));
+	if (ret < 0)
+		return ret;
+
+	return be16_to_cpu(data);
+}
+
+static int vl6180_write_byte(struct i2c_client *client, u16 cmd, u8 val)
+{
+	u8 buf[3];
+	struct i2c_msg msgs[1] = {
+		{ .addr = client->addr, .len = sizeof(buf), .buf = (u8 *) &buf } };
+	int ret;
+
+	buf[0] = cmd >> 8;
+	buf[1] = cmd & 0xff;
+	buf[2] = val;
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret < 0) {
+		dev_err(&client->dev, "failed writing register 0x%04x\n", cmd);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int vl6180_write_word(struct i2c_client *client, u16 cmd, u16 val)
+{
+	__be16 buf[2];
+	struct i2c_msg msgs[1] = {
+		{ .addr = client->addr, .len = sizeof(buf), .buf = (u8 *) &buf } };
+	int ret;
+
+	buf[0] = cpu_to_be16(cmd);
+	buf[1] = cpu_to_be16(val);
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret < 0) {
+		dev_err(&client->dev, "failed writing register 0x%04x\n", cmd);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int vl6180_measure(struct vl6180_data *data, int addr)
+{
+	struct i2c_client *client = data->client;
+	int tries = 20, ret;
+	u16 value;
+
+	mutex_lock(&data->lock);
+	/* Start single shot measurement */
+	ret = vl6180_write_byte(client,
+		vl6180_chan_regs_table[addr].start_reg, VL6180_STARTSTOP);
+	if (ret < 0)
+		goto fail;
+
+	while (tries--) {
+		ret = vl6180_read_byte(client, VL6180_INTR_STATUS);
+		if (ret < 0)
+			goto fail;
+
+		if (ret & vl6180_chan_regs_table[addr].drdy_mask)
+			break;
+		msleep(20);
+	}
+
+	if (tries < 0) {
+		ret = -EIO;
+		goto fail;
+	}
+
+	/* Read result value from appropriate registers */
+	ret = vl6180_chan_regs_table[addr].word ?
+		vl6180_read_word(client, vl6180_chan_regs_table[addr].value_reg) :
+		vl6180_read_byte(client, vl6180_chan_regs_table[addr].value_reg);
+	if (ret < 0)
+		goto fail;
+	value = ret;
+
+	/* Clear the interrupt flag after data read */
+	ret = vl6180_write_byte(client, VL6180_INTR_CLEAR,
+		VL6180_CLEAR_ERROR | VL6180_CLEAR_ALS | VL6180_CLEAR_RANGE);
+	if (ret < 0)
+		goto fail;
+
+	ret = value;
+
+fail:
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+
+static const struct iio_chan_spec vl6180_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.address = VL6180_ALS,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+			BIT(IIO_CHAN_INFO_INT_TIME) |
+			BIT(IIO_CHAN_INFO_SCALE) |
+			BIT(IIO_CHAN_INFO_HARDWAREGAIN),
+	}, {
+		.type = IIO_DISTANCE,
+		.address = VL6180_RANGE,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+			BIT(IIO_CHAN_INFO_SCALE),
+	}, {
+		.type = IIO_PROXIMITY,
+		.address = VL6180_PROX,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+	}
+};
+
+/*
+ * Available Ambient Light Sensor gain settings, 1/1000th, and
+ * corresponding setting for the VL6180_ALS_GAIN register
+ */
+static const int vl6180_als_gain_tab[8] = {
+	1000, 1250, 1670, 2500, 5000, 10000, 20000, 40000
+};
+static const u8 vl6180_als_gain_tab_bits[8] = {
+	VL6180_ALS_GAIN_1,    VL6180_ALS_GAIN_1_25,
+	VL6180_ALS_GAIN_1_67, VL6180_ALS_GAIN_2_5,
+	VL6180_ALS_GAIN_5,    VL6180_ALS_GAIN_10,
+	VL6180_ALS_GAIN_20,   VL6180_ALS_GAIN_40
+};
+
+static int vl6180_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val, int *val2, long mask)
+{
+	struct vl6180_data *data = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = vl6180_measure(data, chan->address);
+		if (ret < 0)
+			return ret;
+		*val = ret;
+
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_INT_TIME:
+		*val = data->als_it_ms;
+		*val2 = 1000;
+
+		return IIO_VAL_FRACTIONAL;
+
+	case IIO_CHAN_INFO_SCALE:
+		switch (chan->type) {
+		case IIO_LIGHT:
+			/* one ALS count is 0.32 Lux @ gain 1, IT 100 ms */
+			*val = 32000; /* 0.32 * 1000 * 100 */
+			*val2 = data->als_gain_milli * data->als_it_ms;
+
+			return IIO_VAL_FRACTIONAL;
+
+		case IIO_DISTANCE:
+			*val = 0; /* sensor reports mm, scale to meter */
+			*val2 = 1000;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		return IIO_VAL_INT_PLUS_MICRO;
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		*val = data->als_gain_milli;
+		*val2 = 1000;
+
+		return IIO_VAL_FRACTIONAL;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static IIO_CONST_ATTR(als_gain_available, "1 1.25 1.67 2.5 5 10 20 40");
+
+static struct attribute *vl6180_attributes[] = {
+	&iio_const_attr_als_gain_available.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group vl6180_attribute_group = {
+	.attrs = vl6180_attributes,
+};
+
+/* HOLD is needed before updating any config registers */
+static int vl6180_hold(struct vl6180_data *data, bool hold)
+{
+	return vl6180_write_byte(data->client, VL6180_HOLD,
+		hold ? VL6180_HOLD_ON : 0);
+}
+
+static int vl6180_set_als_gain(struct vl6180_data *data, int val, int val2)
+{
+	int i, ret, gain;
+
+	if (val < 1 || val > 40)
+		return -EINVAL;
+
+	gain = (val * 1000000 + val2) / 1000;
+	if (gain < 1 || gain > 40000)
+		return -EINVAL;
+
+	i = find_closest(gain, vl6180_als_gain_tab,
+			 ARRAY_SIZE(vl6180_als_gain_tab));
+
+	mutex_lock(&data->lock);
+	ret = vl6180_hold(data, true);
+	if (ret < 0)
+		goto fail;
+
+	ret = vl6180_write_byte(data->client, VL6180_ALS_GAIN,
+				vl6180_als_gain_tab_bits[i]);
+
+	if (ret >= 0)
+		data->als_gain_milli = vl6180_als_gain_tab[i];
+
+fail:
+	vl6180_hold(data, false);
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+static int vl6180_set_it(struct vl6180_data *data, int val, int val2)
+{
+	int ret, it_ms;
+
+	it_ms = (val2 + 500) / 1000; /* round to ms */
+	if (val != 0 || it_ms < 1 || it_ms > 512)
+		return -EINVAL;
+
+	mutex_lock(&data->lock);
+	ret = vl6180_hold(data, true);
+	if (ret < 0)
+		goto fail;
+
+	ret = vl6180_write_word(data->client, VL6180_ALS_IT, it_ms - 1);
+
+	if (ret >= 0)
+		data->als_it_ms = it_ms;
+
+fail:
+	vl6180_hold(data, false);
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+
+static int vl6180_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int val, int val2, long mask)
+{
+	struct vl6180_data *data = iio_priv(indio_dev);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_INT_TIME:
+		return vl6180_set_it(data, val, val2);
+
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		if (chan->type != IIO_LIGHT)
+			return -EINVAL;
+
+		return vl6180_set_als_gain(data, val, val2);
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info vl6180_info = {
+	.read_raw = vl6180_read_raw,
+	.write_raw = vl6180_write_raw,
+	.attrs = &vl6180_attribute_group,
+};
+
+static int vl6180_init(struct vl6180_data *data)
+{
+	struct i2c_client *client = data->client;
+	int ret;
+
+	ret = vl6180_read_byte(client, VL6180_MODEL_ID);
+	if (ret < 0)
+		return ret;
+
+	if (ret != VL6180_MODEL_ID_VAL) {
+		dev_err(&client->dev, "invalid model ID %02x\n", ret);
+		return -ENODEV;
+	}
+
+	ret = vl6180_hold(data, true);
+	if (ret < 0)
+		return ret;
+
+	ret = vl6180_read_byte(client, VL6180_OUT_OF_RESET);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Detect false reset condition here. This bit is always set when the
+	 * system comes out of reset.
+	 */
+	if (ret != 0x01)
+		dev_info(&client->dev, "device is not fresh out of reset\n");
+
+	/* Enable ALS and Range ready interrupts */
+	ret = vl6180_write_byte(client, VL6180_INTR_CONFIG,
+				VL6180_ALS_READY | VL6180_RANGE_READY);
+	if (ret < 0)
+		return ret;
+
+	/* ALS integration time: 100ms */
+	data->als_it_ms = 100;
+	ret = vl6180_write_word(client, VL6180_ALS_IT, VL6180_ALS_IT_100);
+	if (ret < 0)
+		return ret;
+
+	/* ALS gain: 1 */
+	data->als_gain_milli = 1000;
+	ret = vl6180_write_byte(client, VL6180_ALS_GAIN, VL6180_ALS_GAIN_1);
+	if (ret < 0)
+		return ret;
+
+	ret = vl6180_write_byte(client, VL6180_OUT_OF_RESET, 0x00);
+	if (ret < 0)
+		return ret;
+
+	return vl6180_hold(data, false);
+}
+
+static int vl6180_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct vl6180_data *data;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->client = client;
+	mutex_init(&data->lock);
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->info = &vl6180_info;
+	indio_dev->channels = vl6180_channels;
+	indio_dev->num_channels = ARRAY_SIZE(vl6180_channels);
+	indio_dev->name = VL6180_DRV_NAME;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	ret = vl6180_init(data);
+	if (ret < 0)
+		return ret;
+
+	return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct of_device_id vl6180_of_match[] = {
+	{ .compatible = "st,vl6180", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, vl6180_of_match);
+
+static const struct i2c_device_id vl6180_id[] = {
+	{ "vl6180", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, vl6180_id);
+
+static struct i2c_driver vl6180_driver = {
+	.driver = {
+		.name   = VL6180_DRV_NAME,
+		.of_match_table = of_match_ptr(vl6180_of_match),
+	},
+	.probe  = vl6180_probe,
+	.id_table = vl6180_id,
+};
+
+module_i2c_driver(vl6180_driver);
+
+MODULE_AUTHOR("Peter Meerwald-Stadler <pmeerw@pmeerw.net>");
+MODULE_AUTHOR("Manivannan Sadhasivam <manivannanece23@gmail.com>");
+MODULE_DESCRIPTION("STMicro VL6180 ALS, range and proximity sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/zopt2201.c b/drivers/iio/light/zopt2201.c
new file mode 100644
index 0000000..041ac9e
--- /dev/null
+++ b/drivers/iio/light/zopt2201.c
@@ -0,0 +1,568 @@
+/*
+ * zopt2201.c - Support for IDT ZOPT2201 ambient light and UV B sensor
+ *
+ * Copyright 2017 Peter Meerwald-Stadler <pmeerw@pmeerw.net>
+ *
+ * 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.
+ *
+ * Datasheet: https://www.idt.com/document/dst/zopt2201-datasheet
+ * 7-bit I2C slave addresses 0x53 (default) or 0x52 (programmed)
+ *
+ * TODO: interrupt support, ALS/UVB raw mode
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define ZOPT2201_DRV_NAME "zopt2201"
+
+/* Registers */
+#define ZOPT2201_MAIN_CTRL		0x00
+#define ZOPT2201_LS_MEAS_RATE		0x04
+#define ZOPT2201_LS_GAIN		0x05
+#define ZOPT2201_PART_ID		0x06
+#define ZOPT2201_MAIN_STATUS		0x07
+#define ZOPT2201_ALS_DATA		0x0d /* LSB first, 13 to 20 bits */
+#define ZOPT2201_UVB_DATA		0x10 /* LSB first, 13 to 20 bits */
+#define ZOPT2201_UV_COMP_DATA		0x13 /* LSB first, 13 to 20 bits */
+#define ZOPT2201_COMP_DATA		0x16 /* LSB first, 13 to 20 bits */
+#define ZOPT2201_INT_CFG		0x19
+#define ZOPT2201_INT_PST		0x1a
+
+#define ZOPT2201_MAIN_CTRL_LS_MODE	BIT(3) /* 0 .. ALS, 1 .. UV B */
+#define ZOPT2201_MAIN_CTRL_LS_EN	BIT(1)
+
+/* Values for ZOPT2201_LS_MEAS_RATE resolution / bit width */
+#define ZOPT2201_MEAS_RES_20BIT		0 /* takes 400 ms */
+#define ZOPT2201_MEAS_RES_19BIT		1 /* takes 200 ms */
+#define ZOPT2201_MEAS_RES_18BIT		2 /* takes 100 ms, default */
+#define ZOPT2201_MEAS_RES_17BIT		3 /* takes 50 ms */
+#define ZOPT2201_MEAS_RES_16BIT		4 /* takes 25 ms */
+#define ZOPT2201_MEAS_RES_13BIT		5 /* takes 3.125 ms */
+#define ZOPT2201_MEAS_RES_SHIFT		4
+
+/* Values for ZOPT2201_LS_MEAS_RATE measurement rate */
+#define ZOPT2201_MEAS_FREQ_25MS		0
+#define ZOPT2201_MEAS_FREQ_50MS		1
+#define ZOPT2201_MEAS_FREQ_100MS	2 /* default */
+#define ZOPT2201_MEAS_FREQ_200MS	3
+#define ZOPT2201_MEAS_FREQ_500MS	4
+#define ZOPT2201_MEAS_FREQ_1000MS	5
+#define ZOPT2201_MEAS_FREQ_2000MS	6
+
+/* Values for ZOPT2201_LS_GAIN */
+#define ZOPT2201_LS_GAIN_1		0
+#define ZOPT2201_LS_GAIN_3		1
+#define ZOPT2201_LS_GAIN_6		2
+#define ZOPT2201_LS_GAIN_9		3
+#define ZOPT2201_LS_GAIN_18		4
+
+/* Values for ZOPT2201_MAIN_STATUS */
+#define ZOPT2201_MAIN_STATUS_POWERON	BIT(5)
+#define ZOPT2201_MAIN_STATUS_INT	BIT(4)
+#define ZOPT2201_MAIN_STATUS_DRDY	BIT(3)
+
+#define ZOPT2201_PART_NUMBER		0xb2
+
+struct zopt2201_data {
+	struct i2c_client *client;
+	struct mutex lock;
+	u8 gain;
+	u8 res;
+	u8 rate;
+};
+
+static const struct {
+	unsigned int gain; /* gain factor */
+	unsigned int scale; /* micro lux per count */
+} zopt2201_gain_als[] = {
+	{  1, 19200000 },
+	{  3,  6400000 },
+	{  6,  3200000 },
+	{  9,  2133333 },
+	{ 18,  1066666 },
+};
+
+static const struct {
+	unsigned int gain; /* gain factor */
+	unsigned int scale; /* micro W/m2 per count */
+} zopt2201_gain_uvb[] = {
+	{  1, 460800 },
+	{  3, 153600 },
+	{  6,  76800 },
+	{  9,  51200 },
+	{ 18,  25600 },
+};
+
+static const struct {
+	unsigned int bits; /* sensor resolution in bits */
+	unsigned long us; /* measurement time in micro seconds */
+} zopt2201_resolution[] = {
+	{ 20, 400000 },
+	{ 19, 200000 },
+	{ 18, 100000 },
+	{ 17,  50000 },
+	{ 16,  25000 },
+	{ 13,   3125 },
+};
+
+static const struct {
+	unsigned int scale, uscale; /* scale factor as integer + micro */
+	u8 gain; /* gain register value */
+	u8 res; /* resolution register value */
+} zopt2201_scale_als[] = {
+	{ 19, 200000, 0, 5 },
+	{  6, 400000, 1, 5 },
+	{  3, 200000, 2, 5 },
+	{  2, 400000, 0, 4 },
+	{  2, 133333, 3, 5 },
+	{  1, 200000, 0, 3 },
+	{  1,  66666, 4, 5 },
+	{  0, 800000, 1, 4 },
+	{  0, 600000, 0, 2 },
+	{  0, 400000, 2, 4 },
+	{  0, 300000, 0, 1 },
+	{  0, 266666, 3, 4 },
+	{  0, 200000, 2, 3 },
+	{  0, 150000, 0, 0 },
+	{  0, 133333, 4, 4 },
+	{  0, 100000, 2, 2 },
+	{  0,  66666, 4, 3 },
+	{  0,  50000, 2, 1 },
+	{  0,  33333, 4, 2 },
+	{  0,  25000, 2, 0 },
+	{  0,  16666, 4, 1 },
+	{  0,   8333, 4, 0 },
+};
+
+static const struct {
+	unsigned int scale, uscale; /* scale factor as integer + micro */
+	u8 gain; /* gain register value */
+	u8 res; /* resolution register value */
+} zopt2201_scale_uvb[] = {
+	{ 0, 460800, 0, 5 },
+	{ 0, 153600, 1, 5 },
+	{ 0,  76800, 2, 5 },
+	{ 0,  57600, 0, 4 },
+	{ 0,  51200, 3, 5 },
+	{ 0,  28800, 0, 3 },
+	{ 0,  25600, 4, 5 },
+	{ 0,  19200, 1, 4 },
+	{ 0,  14400, 0, 2 },
+	{ 0,   9600, 2, 4 },
+	{ 0,   7200, 0, 1 },
+	{ 0,   6400, 3, 4 },
+	{ 0,   4800, 2, 3 },
+	{ 0,   3600, 0, 0 },
+	{ 0,   3200, 4, 4 },
+	{ 0,   2400, 2, 2 },
+	{ 0,   1600, 4, 3 },
+	{ 0,   1200, 2, 1 },
+	{ 0,    800, 4, 2 },
+	{ 0,    600, 2, 0 },
+	{ 0,    400, 4, 1 },
+	{ 0,    200, 4, 0 },
+};
+
+static int zopt2201_enable_mode(struct zopt2201_data *data, bool uvb_mode)
+{
+	u8 out = ZOPT2201_MAIN_CTRL_LS_EN;
+
+	if (uvb_mode)
+		out |= ZOPT2201_MAIN_CTRL_LS_MODE;
+
+	return i2c_smbus_write_byte_data(data->client, ZOPT2201_MAIN_CTRL, out);
+}
+
+static int zopt2201_read(struct zopt2201_data *data, u8 reg)
+{
+	struct i2c_client *client = data->client;
+	int tries = 10;
+	u8 buf[3];
+	int ret;
+
+	mutex_lock(&data->lock);
+	ret = zopt2201_enable_mode(data, reg == ZOPT2201_UVB_DATA);
+	if (ret < 0)
+		goto fail;
+
+	while (tries--) {
+		unsigned long t = zopt2201_resolution[data->res].us;
+
+		if (t <= 20000)
+			usleep_range(t, t + 1000);
+		else
+			msleep(t / 1000);
+		ret = i2c_smbus_read_byte_data(client, ZOPT2201_MAIN_STATUS);
+		if (ret < 0)
+			goto fail;
+		if (ret & ZOPT2201_MAIN_STATUS_DRDY)
+			break;
+	}
+
+	if (tries < 0) {
+		ret = -ETIMEDOUT;
+		goto fail;
+	}
+
+	ret = i2c_smbus_read_i2c_block_data(client, reg, sizeof(buf), buf);
+	if (ret < 0)
+		goto fail;
+
+	ret = i2c_smbus_write_byte_data(client, ZOPT2201_MAIN_CTRL, 0x00);
+	if (ret < 0)
+		goto fail;
+	mutex_unlock(&data->lock);
+
+	return (buf[2] << 16) | (buf[1] << 8) | buf[0];
+
+fail:
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+static const struct iio_chan_spec zopt2201_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.address = ZOPT2201_ALS_DATA,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
+	},
+	{
+		.type = IIO_INTENSITY,
+		.modified = 1,
+		.channel2 = IIO_MOD_LIGHT_UV,
+		.address = ZOPT2201_UVB_DATA,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
+	},
+	{
+		.type = IIO_UVINDEX,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+	},
+};
+
+static int zopt2201_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val, int *val2, long mask)
+{
+	struct zopt2201_data *data = iio_priv(indio_dev);
+	u64 tmp;
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = zopt2201_read(data, chan->address);
+		if (ret < 0)
+			return ret;
+		*val = ret;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_PROCESSED:
+		ret = zopt2201_read(data, ZOPT2201_UVB_DATA);
+		if (ret < 0)
+			return ret;
+		*val = ret * 18 *
+			(1 << (20 - zopt2201_resolution[data->res].bits)) /
+			zopt2201_gain_uvb[data->gain].gain;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		switch (chan->address) {
+		case ZOPT2201_ALS_DATA:
+			*val = zopt2201_gain_als[data->gain].scale;
+			break;
+		case ZOPT2201_UVB_DATA:
+			*val = zopt2201_gain_uvb[data->gain].scale;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		*val2 = 1000000;
+		*val2 *= (1 << (zopt2201_resolution[data->res].bits - 13));
+		tmp = div_s64(*val * 1000000ULL, *val2);
+		*val = div_s64_rem(tmp, 1000000, val2);
+
+		return IIO_VAL_INT_PLUS_MICRO;
+	case IIO_CHAN_INFO_INT_TIME:
+		*val = 0;
+		*val2 = zopt2201_resolution[data->res].us;
+		return IIO_VAL_INT_PLUS_MICRO;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int zopt2201_set_resolution(struct zopt2201_data *data, u8 res)
+{
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(data->client, ZOPT2201_LS_MEAS_RATE,
+					(res << ZOPT2201_MEAS_RES_SHIFT) |
+					data->rate);
+	if (ret < 0)
+		return ret;
+
+	data->res = res;
+
+	return 0;
+}
+
+static int zopt2201_write_resolution(struct zopt2201_data *data,
+				     int val, int val2)
+{
+	int i, ret;
+
+	if (val != 0)
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(zopt2201_resolution); i++)
+		if (val2 == zopt2201_resolution[i].us) {
+			mutex_lock(&data->lock);
+			ret = zopt2201_set_resolution(data, i);
+			mutex_unlock(&data->lock);
+			return ret;
+		}
+
+	return -EINVAL;
+}
+
+static int zopt2201_set_gain(struct zopt2201_data *data, u8 gain)
+{
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(data->client, ZOPT2201_LS_GAIN, gain);
+	if (ret < 0)
+		return ret;
+
+	data->gain = gain;
+
+	return 0;
+}
+
+static int zopt2201_write_scale_als_by_idx(struct zopt2201_data *data, int idx)
+{
+	int ret;
+
+	mutex_lock(&data->lock);
+	ret = zopt2201_set_resolution(data, zopt2201_scale_als[idx].res);
+	if (ret < 0)
+		goto unlock;
+
+	ret = zopt2201_set_gain(data, zopt2201_scale_als[idx].gain);
+
+unlock:
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+static int zopt2201_write_scale_als(struct zopt2201_data *data,
+				     int val, int val2)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(zopt2201_scale_als); i++)
+		if (val == zopt2201_scale_als[i].scale &&
+		    val2 == zopt2201_scale_als[i].uscale) {
+			return zopt2201_write_scale_als_by_idx(data, i);
+		}
+
+	return -EINVAL;
+}
+
+static int zopt2201_write_scale_uvb_by_idx(struct zopt2201_data *data, int idx)
+{
+	int ret;
+
+	mutex_lock(&data->lock);
+	ret = zopt2201_set_resolution(data, zopt2201_scale_als[idx].res);
+	if (ret < 0)
+		goto unlock;
+
+	ret = zopt2201_set_gain(data, zopt2201_scale_als[idx].gain);
+
+unlock:
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+static int zopt2201_write_scale_uvb(struct zopt2201_data *data,
+				     int val, int val2)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(zopt2201_scale_uvb); i++)
+		if (val == zopt2201_scale_uvb[i].scale &&
+		    val2 == zopt2201_scale_uvb[i].uscale)
+			return zopt2201_write_scale_uvb_by_idx(data, i);
+
+	return -EINVAL;
+}
+
+static int zopt2201_write_raw(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *chan,
+			      int val, int val2, long mask)
+{
+	struct zopt2201_data *data = iio_priv(indio_dev);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_INT_TIME:
+		return zopt2201_write_resolution(data, val, val2);
+	case IIO_CHAN_INFO_SCALE:
+		switch (chan->address) {
+		case ZOPT2201_ALS_DATA:
+			return zopt2201_write_scale_als(data, val, val2);
+		case ZOPT2201_UVB_DATA:
+			return zopt2201_write_scale_uvb(data, val, val2);
+		default:
+			return -EINVAL;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static ssize_t zopt2201_show_int_time_available(struct device *dev,
+						struct device_attribute *attr,
+						char *buf)
+{
+	size_t len = 0;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(zopt2201_resolution); i++)
+		len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06lu ",
+				 zopt2201_resolution[i].us);
+	buf[len - 1] = '\n';
+
+	return len;
+}
+
+static IIO_DEV_ATTR_INT_TIME_AVAIL(zopt2201_show_int_time_available);
+
+static ssize_t zopt2201_show_als_scale_avail(struct device *dev,
+					     struct device_attribute *attr,
+					     char *buf)
+{
+	ssize_t len = 0;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(zopt2201_scale_als); i++)
+		len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06u ",
+				 zopt2201_scale_als[i].scale,
+				 zopt2201_scale_als[i].uscale);
+	buf[len - 1] = '\n';
+
+	return len;
+}
+
+static ssize_t zopt2201_show_uvb_scale_avail(struct device *dev,
+					     struct device_attribute *attr,
+					     char *buf)
+{
+	ssize_t len = 0;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(zopt2201_scale_uvb); i++)
+		len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06u ",
+				 zopt2201_scale_uvb[i].scale,
+				 zopt2201_scale_uvb[i].uscale);
+	buf[len - 1] = '\n';
+
+	return len;
+}
+
+static IIO_DEVICE_ATTR(in_illuminance_scale_available, 0444,
+		       zopt2201_show_als_scale_avail, NULL, 0);
+static IIO_DEVICE_ATTR(in_intensity_uv_scale_available, 0444,
+		       zopt2201_show_uvb_scale_avail, NULL, 0);
+
+static struct attribute *zopt2201_attributes[] = {
+	&iio_dev_attr_integration_time_available.dev_attr.attr,
+	&iio_dev_attr_in_illuminance_scale_available.dev_attr.attr,
+	&iio_dev_attr_in_intensity_uv_scale_available.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group zopt2201_attribute_group = {
+	.attrs = zopt2201_attributes,
+};
+
+static const struct iio_info zopt2201_info = {
+	.read_raw = zopt2201_read_raw,
+	.write_raw = zopt2201_write_raw,
+	.attrs = &zopt2201_attribute_group,
+};
+
+static int zopt2201_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct zopt2201_data *data;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	if (!i2c_check_functionality(client->adapter,
+				     I2C_FUNC_SMBUS_READ_I2C_BLOCK))
+		return -EOPNOTSUPP;
+
+	ret = i2c_smbus_read_byte_data(client, ZOPT2201_PART_ID);
+	if (ret < 0)
+		return ret;
+	if (ret != ZOPT2201_PART_NUMBER)
+		return -ENODEV;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->client = client;
+	mutex_init(&data->lock);
+
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->info = &zopt2201_info;
+	indio_dev->channels = zopt2201_channels;
+	indio_dev->num_channels = ARRAY_SIZE(zopt2201_channels);
+	indio_dev->name = ZOPT2201_DRV_NAME;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	data->rate = ZOPT2201_MEAS_FREQ_100MS;
+	ret = zopt2201_set_resolution(data, ZOPT2201_MEAS_RES_18BIT);
+	if (ret < 0)
+		return ret;
+
+	ret = zopt2201_set_gain(data, ZOPT2201_LS_GAIN_3);
+	if (ret < 0)
+		return ret;
+
+	return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct i2c_device_id zopt2201_id[] = {
+	{ "zopt2201", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, zopt2201_id);
+
+static struct i2c_driver zopt2201_driver = {
+	.driver = {
+		.name   = ZOPT2201_DRV_NAME,
+	},
+	.probe  = zopt2201_probe,
+	.id_table = zopt2201_id,
+};
+
+module_i2c_driver(zopt2201_driver);
+
+MODULE_AUTHOR("Peter Meerwald-Stadler <pmeerw@pmeerw.net>");
+MODULE_DESCRIPTION("IDT ZOPT2201 ambient light and UV B sensor driver");
+MODULE_LICENSE("GPL");