diff options
Diffstat (limited to 'drivers/iio/light')
-rw-r--r-- | drivers/iio/light/Kconfig | 36 | ||||
-rw-r--r-- | drivers/iio/light/Makefile | 3 | ||||
-rw-r--r-- | drivers/iio/light/acpi-als.c | 18 | ||||
-rw-r--r-- | drivers/iio/light/apds9300.c | 1 | ||||
-rw-r--r-- | drivers/iio/light/bh1750.c | 1 | ||||
-rw-r--r-- | drivers/iio/light/cm32181.c | 2 | ||||
-rw-r--r-- | drivers/iio/light/cm3232.c | 2 | ||||
-rw-r--r-- | drivers/iio/light/cm3323.c | 21 | ||||
-rw-r--r-- | drivers/iio/light/cm36651.c | 2 | ||||
-rw-r--r-- | drivers/iio/light/gp2ap020a00f.c | 2 | ||||
-rw-r--r-- | drivers/iio/light/hid-sensor-prox.c | 3 | ||||
-rw-r--r-- | drivers/iio/light/isl29125.c | 13 | ||||
-rw-r--r-- | drivers/iio/light/jsa1212.c | 1 | ||||
-rw-r--r-- | drivers/iio/light/ltr501.c | 3 | ||||
-rw-r--r-- | drivers/iio/light/opt3001.c | 804 | ||||
-rw-r--r-- | drivers/iio/light/pa12203001.c | 483 | ||||
-rw-r--r-- | drivers/iio/light/rpr0521.c | 615 | ||||
-rw-r--r-- | drivers/iio/light/stk3310.c | 76 | ||||
-rw-r--r-- | drivers/iio/light/tcs3414.c | 3 | ||||
-rw-r--r-- | drivers/iio/light/tcs3472.c | 1 | ||||
-rw-r--r-- | drivers/iio/light/tsl4531.c | 1 | ||||
-rw-r--r-- | drivers/iio/light/vcnl4000.c | 1 |
22 files changed, 2010 insertions, 82 deletions
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index e6198b7c9cbf..7ed859a700c4 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -86,7 +86,7 @@ config CM3323 depends on I2C tristate "Capella CM3323 color light sensor" help - Say Y here if you want to build a driver for Capela CM3323 + Say Y here if you want to build a driver for Capella CM3323 color sensor. To compile this driver as a module, choose M here: the module will @@ -168,6 +168,17 @@ config JSA1212 To compile this driver as a module, choose M here: the module will be called jsa1212. +config RPR0521 + tristate "ROHM RPR0521 ALS and proximity sensor driver" + depends on I2C + select REGMAP_I2C + help + Say Y here if you want to build support for ROHM's RPR0521 + ambient light and proximity sensor device. + + To compile this driver as a module, choose M here: + the module will be called rpr0521. + config SENSORS_LM3533 tristate "LM3533 ambient light sensor" depends on MFD_LM3533 @@ -188,6 +199,7 @@ config SENSORS_LM3533 config LTR501 tristate "LTR-501ALS-01 light sensor" depends on I2C + select REGMAP_I2C select IIO_BUFFER select IIO_TRIGGERED_BUFFER help @@ -198,9 +210,31 @@ config LTR501 This driver can also be built as a module. If so, the module will be called ltr501. +config OPT3001 + tristate "Texas Instruments OPT3001 Light Sensor" + depends on I2C + help + If you say Y or M here, you get support for Texas Instruments + OPT3001 Ambient Light Sensor. + + If built as a dynamically linked module, it will be called + opt3001. + +config PA12203001 + tristate "TXC PA12203001 light and proximity sensor" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the TXC PA12203001 + ambient light and proximity sensor. + + This driver can also be built as a module. If so, the module + will be called pa12203001. + config STK3310 tristate "STK3310 ALS and proximity sensor" depends on I2C + select REGMAP_I2C help Say yes here to get support for the Sensortek STK3310 ambient light and proximity sensor. The STK3311 model is also supported by this diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index e2d50fd59c66..91c74c014b6f 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -19,6 +19,9 @@ obj-$(CONFIG_ISL29125) += isl29125.o obj-$(CONFIG_JSA1212) += jsa1212.o obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o obj-$(CONFIG_LTR501) += ltr501.o +obj-$(CONFIG_OPT3001) += opt3001.o +obj-$(CONFIG_PA12203001) += pa12203001.o +obj-$(CONFIG_RPR0521) += rpr0521.o obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o obj-$(CONFIG_STK3310) += stk3310.o obj-$(CONFIG_TCS3414) += tcs3414.o diff --git a/drivers/iio/light/acpi-als.c b/drivers/iio/light/acpi-als.c index 1dafa0756bfa..60537ec0c923 100644 --- a/drivers/iio/light/acpi-als.c +++ b/drivers/iio/light/acpi-als.c @@ -65,20 +65,20 @@ static const struct iio_chan_spec acpi_als_channels[] = { * to acpi_als_channels[], the evt_buffer below will grow * automatically. */ -#define EVT_NR_SOURCES ARRAY_SIZE(acpi_als_channels) -#define EVT_BUFFER_SIZE \ - (sizeof(s64) + (EVT_NR_SOURCES * sizeof(s32))) +#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[EVT_BUFFER_SIZE]; + 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 als_read_value() below, while the ALR is + * 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 @@ -93,7 +93,7 @@ struct acpi_als { #define ACPI_ALS_POLLING "_ALP" #define ACPI_ALS_TABLES "_ALR" -static int als_read_value(struct acpi_als *als, char *prop, s32 *val) +static int acpi_als_read_value(struct acpi_als *als, char *prop, s32 *val) { unsigned long long temp_val; acpi_status status; @@ -122,11 +122,11 @@ static void acpi_als_notify(struct acpi_device *device, u32 event) mutex_lock(&als->lock); - memset(buffer, 0, EVT_BUFFER_SIZE); + memset(buffer, 0, ACPI_ALS_EVT_BUFFER_SIZE); switch (event) { case ACPI_ALS_NOTIFY_ILLUMINANCE: - ret = als_read_value(als, ACPI_ALS_ILLUMINANCE, &val); + ret = acpi_als_read_value(als, ACPI_ALS_ILLUMINANCE, &val); if (ret < 0) goto out; *buffer++ = val; @@ -159,7 +159,7 @@ static int acpi_als_read_raw(struct iio_dev *indio_dev, if (chan->type != IIO_LIGHT) return -EINVAL; - ret = als_read_value(als, ACPI_ALS_ILLUMINANCE, &temp_val); + ret = acpi_als_read_value(als, ACPI_ALS_ILLUMINANCE, &temp_val); if (ret < 0) return ret; diff --git a/drivers/iio/light/apds9300.c b/drivers/iio/light/apds9300.c index 9ddde0ca9c34..e1b9fa5a7e91 100644 --- a/drivers/iio/light/apds9300.c +++ b/drivers/iio/light/apds9300.c @@ -515,7 +515,6 @@ MODULE_DEVICE_TABLE(i2c, apds9300_id); static struct i2c_driver apds9300_driver = { .driver = { .name = APDS9300_DRV_NAME, - .owner = THIS_MODULE, .pm = APDS9300_PM_OPS, }, .probe = apds9300_probe, diff --git a/drivers/iio/light/bh1750.c b/drivers/iio/light/bh1750.c index 564c2b3c1a83..8b4164343f20 100644 --- a/drivers/iio/light/bh1750.c +++ b/drivers/iio/light/bh1750.c @@ -319,7 +319,6 @@ MODULE_DEVICE_TABLE(i2c, bh1750_id); static struct i2c_driver bh1750_driver = { .driver = { .name = "bh1750", - .owner = THIS_MODULE, .pm = BH1750_PM_OPS, }, .probe = bh1750_probe, diff --git a/drivers/iio/light/cm32181.c b/drivers/iio/light/cm32181.c index 5d12ae54d088..d6fd0dace74f 100644 --- a/drivers/iio/light/cm32181.c +++ b/drivers/iio/light/cm32181.c @@ -353,12 +353,12 @@ static const struct of_device_id cm32181_of_match[] = { { .compatible = "capella,cm32181" }, { } }; +MODULE_DEVICE_TABLE(of, cm32181_of_match); static struct i2c_driver cm32181_driver = { .driver = { .name = "cm32181", .of_match_table = of_match_ptr(cm32181_of_match), - .owner = THIS_MODULE, }, .id_table = cm32181_id, .probe = cm32181_probe, diff --git a/drivers/iio/light/cm3232.c b/drivers/iio/light/cm3232.c index 39c8d99cc48e..fe89b6823217 100644 --- a/drivers/iio/light/cm3232.c +++ b/drivers/iio/light/cm3232.c @@ -417,11 +417,11 @@ static const struct of_device_id cm3232_of_match[] = { {.compatible = "capella,cm3232"}, {} }; +MODULE_DEVICE_TABLE(of, cm3232_of_match); static struct i2c_driver cm3232_driver = { .driver = { .name = "cm3232", - .owner = THIS_MODULE, .of_match_table = of_match_ptr(cm3232_of_match), #ifdef CONFIG_PM_SLEEP .pm = &cm3232_pm_ops, diff --git a/drivers/iio/light/cm3323.c b/drivers/iio/light/cm3323.c index 869033e48a1f..d823c112d54b 100644 --- a/drivers/iio/light/cm3323.c +++ b/drivers/iio/light/cm3323.c @@ -29,7 +29,7 @@ #define CM3323_CONF_SD_BIT BIT(0) /* sensor disable */ #define CM3323_CONF_AF_BIT BIT(1) /* auto/manual force mode */ -#define CM3323_CONF_IT_MASK (BIT(4) | BIT(5) | BIT(6)) +#define CM3323_CONF_IT_MASK GENMASK(6, 4) #define CM3323_CONF_IT_SHIFT 4 #define CM3323_INT_TIME_AVAILABLE "0.04 0.08 0.16 0.32 0.64 1.28" @@ -123,7 +123,7 @@ static int cm3323_set_it_bits(struct cm3323_data *data, int val, int val2) 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; + reg_conf = data->reg_conf & ~CM3323_CONF_IT_MASK; reg_conf |= i << CM3323_CONF_IT_SHIFT; ret = i2c_smbus_write_word_data(data->client, @@ -133,9 +133,11 @@ static int cm3323_set_it_bits(struct cm3323_data *data, int val, int val2) return ret; data->reg_conf = reg_conf; + return 0; } } + return -EINVAL; } @@ -148,6 +150,7 @@ static int cm3323_get_it_bits(struct cm3323_data *data) if (bits >= ARRAY_SIZE(cm3323_int_time)) return -EINVAL; + return bits; } @@ -155,7 +158,7 @@ static int cm3323_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) { - int i, ret; + int ret; struct cm3323_data *data = iio_priv(indio_dev); switch (mask) { @@ -172,14 +175,14 @@ static int cm3323_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT; case IIO_CHAN_INFO_INT_TIME: mutex_lock(&data->mutex); - i = cm3323_get_it_bits(data); - if (i < 0) { + ret = cm3323_get_it_bits(data); + if (ret < 0) { mutex_unlock(&data->mutex); - return -EINVAL; + return ret; } - *val = cm3323_int_time[i].val; - *val2 = cm3323_int_time[i].val2; + *val = cm3323_int_time[ret].val; + *val2 = cm3323_int_time[ret].val2; mutex_unlock(&data->mutex); return IIO_VAL_INT_PLUS_MICRO; @@ -243,11 +246,13 @@ static int cm3323_probe(struct i2c_client *client, dev_err(&client->dev, "cm3323 chip init failed\n"); return ret; } + ret = iio_device_register(indio_dev); if (ret < 0) { dev_err(&client->dev, "failed to register iio dev\n"); goto err_init; } + return 0; err_init: cm3323_disable(indio_dev); diff --git a/drivers/iio/light/cm36651.c b/drivers/iio/light/cm36651.c index 39fc67e82138..c8d7b5ea7e78 100644 --- a/drivers/iio/light/cm36651.c +++ b/drivers/iio/light/cm36651.c @@ -731,12 +731,12 @@ static const struct of_device_id cm36651_of_match[] = { { .compatible = "capella,cm36651" }, { } }; +MODULE_DEVICE_TABLE(of, cm36651_of_match); static struct i2c_driver cm36651_driver = { .driver = { .name = "cm36651", .of_match_table = cm36651_of_match, - .owner = THIS_MODULE, }, .probe = cm36651_probe, .remove = cm36651_remove, diff --git a/drivers/iio/light/gp2ap020a00f.c b/drivers/iio/light/gp2ap020a00f.c index 32b6449833fa..6d41086f7c64 100644 --- a/drivers/iio/light/gp2ap020a00f.c +++ b/drivers/iio/light/gp2ap020a00f.c @@ -1634,13 +1634,13 @@ static const struct of_device_id gp2ap020a00f_of_match[] = { { .compatible = "sharp,gp2ap020a00f" }, { } }; +MODULE_DEVICE_TABLE(of, gp2ap020a00f_of_match); #endif static struct i2c_driver gp2ap020a00f_driver = { .driver = { .name = GP2A_I2C_NAME, .of_match_table = of_match_ptr(gp2ap020a00f_of_match), - .owner = THIS_MODULE, }, .probe = gp2ap020a00f_probe, .remove = gp2ap020a00f_remove, diff --git a/drivers/iio/light/hid-sensor-prox.c b/drivers/iio/light/hid-sensor-prox.c index 0d248476f4c9..45ca056f019e 100644 --- a/drivers/iio/light/hid-sensor-prox.c +++ b/drivers/iio/light/hid-sensor-prox.c @@ -284,8 +284,7 @@ static int hid_prox_probe(struct platform_device *pdev) goto error_free_dev_mem; } - indio_dev->num_channels = - ARRAY_SIZE(prox_channels); + indio_dev->num_channels = ARRAY_SIZE(prox_channels); indio_dev->dev.parent = &pdev->dev; indio_dev->info = &prox_info; indio_dev->name = name; diff --git a/drivers/iio/light/isl29125.c b/drivers/iio/light/isl29125.c index c82f4a6f8464..e2945a20e5f6 100644 --- a/drivers/iio/light/isl29125.c +++ b/drivers/iio/light/isl29125.c @@ -197,9 +197,21 @@ done: return IRQ_HANDLED; } +static IIO_CONST_ATTR(scale_available, "0.005722 0.152590"); + +static struct attribute *isl29125_attributes[] = { + &iio_const_attr_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group isl29125_attribute_group = { + .attrs = isl29125_attributes, +}; + static const struct iio_info isl29125_info = { .read_raw = isl29125_read_raw, .write_raw = isl29125_write_raw, + .attrs = &isl29125_attribute_group, .driver_module = THIS_MODULE, }; @@ -334,7 +346,6 @@ static struct i2c_driver isl29125_driver = { .driver = { .name = ISL29125_DRV_NAME, .pm = &isl29125_pm_ops, - .owner = THIS_MODULE, }, .probe = isl29125_probe, .remove = isl29125_remove, diff --git a/drivers/iio/light/jsa1212.c b/drivers/iio/light/jsa1212.c index 3a3af89beaf9..c4e8c6b6c3c3 100644 --- a/drivers/iio/light/jsa1212.c +++ b/drivers/iio/light/jsa1212.c @@ -457,7 +457,6 @@ static struct i2c_driver jsa1212_driver = { .driver = { .name = JSA1212_DRIVER_NAME, .pm = JSA1212_PM_OPS, - .owner = THIS_MODULE, .acpi_match_table = ACPI_PTR(jsa1212_acpi_match), }, .probe = jsa1212_probe, diff --git a/drivers/iio/light/ltr501.c b/drivers/iio/light/ltr501.c index 1ef7d3773ab9..809a961b9a7f 100644 --- a/drivers/iio/light/ltr501.c +++ b/drivers/iio/light/ltr501.c @@ -1302,7 +1302,7 @@ static int ltr501_init(struct ltr501_data *data) if (ret < 0) return ret; - data->als_contr = ret | data->chip_info->als_mode_active; + data->als_contr = status | data->chip_info->als_mode_active; ret = regmap_read(data->regmap, LTR501_PS_CONTR, &status); if (ret < 0) @@ -1551,7 +1551,6 @@ static struct i2c_driver ltr501_driver = { .name = LTR501_DRV_NAME, .pm = <r501_pm_ops, .acpi_match_table = ACPI_PTR(ltr_acpi_match), - .owner = THIS_MODULE, }, .probe = ltr501_probe, .remove = ltr501_remove, diff --git a/drivers/iio/light/opt3001.c b/drivers/iio/light/opt3001.c new file mode 100644 index 000000000000..923aa6aef0ed --- /dev/null +++ b/drivers/iio/light/opt3001.c @@ -0,0 +1,804 @@ +/** + * opt3001.c - Texas Instruments OPT3001 Light Sensor + * + * Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Andreas Dannenberg <dannenberg@ti.com> + * Based on previous work from: Felipe Balbi <balbi@ti.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 of the License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define OPT3001_RESULT 0x00 +#define OPT3001_CONFIGURATION 0x01 +#define OPT3001_LOW_LIMIT 0x02 +#define OPT3001_HIGH_LIMIT 0x03 +#define OPT3001_MANUFACTURER_ID 0x7e +#define OPT3001_DEVICE_ID 0x7f + +#define OPT3001_CONFIGURATION_RN_MASK (0xf << 12) +#define OPT3001_CONFIGURATION_RN_AUTO (0xc << 12) + +#define OPT3001_CONFIGURATION_CT BIT(11) + +#define OPT3001_CONFIGURATION_M_MASK (3 << 9) +#define OPT3001_CONFIGURATION_M_SHUTDOWN (0 << 9) +#define OPT3001_CONFIGURATION_M_SINGLE (1 << 9) +#define OPT3001_CONFIGURATION_M_CONTINUOUS (2 << 9) /* also 3 << 9 */ + +#define OPT3001_CONFIGURATION_OVF BIT(8) +#define OPT3001_CONFIGURATION_CRF BIT(7) +#define OPT3001_CONFIGURATION_FH BIT(6) +#define OPT3001_CONFIGURATION_FL BIT(5) +#define OPT3001_CONFIGURATION_L BIT(4) +#define OPT3001_CONFIGURATION_POL BIT(3) +#define OPT3001_CONFIGURATION_ME BIT(2) + +#define OPT3001_CONFIGURATION_FC_MASK (3 << 0) + +/* The end-of-conversion enable is located in the low-limit register */ +#define OPT3001_LOW_LIMIT_EOC_ENABLE 0xc000 + +#define OPT3001_REG_EXPONENT(n) ((n) >> 12) +#define OPT3001_REG_MANTISSA(n) ((n) & 0xfff) + +/* + * Time to wait for conversion result to be ready. The device datasheet + * worst-case max value is 880ms. Add some slack to be on the safe side. + */ +#define OPT3001_RESULT_READY_TIMEOUT msecs_to_jiffies(1000) + +struct opt3001 { + struct i2c_client *client; + struct device *dev; + + struct mutex lock; + u16 ok_to_ignore_lock:1; + u16 result_ready:1; + wait_queue_head_t result_ready_queue; + u16 result; + + u32 int_time; + u32 mode; + + u16 high_thresh_mantissa; + u16 low_thresh_mantissa; + + u8 high_thresh_exp; + u8 low_thresh_exp; +}; + +struct opt3001_scale { + int val; + int val2; +}; + +static const struct opt3001_scale opt3001_scales[] = { + { + .val = 40, + .val2 = 950000, + }, + { + .val = 81, + .val2 = 900000, + }, + { + .val = 163, + .val2 = 800000, + }, + { + .val = 327, + .val2 = 600000, + }, + { + .val = 655, + .val2 = 200000, + }, + { + .val = 1310, + .val2 = 400000, + }, + { + .val = 2620, + .val2 = 800000, + }, + { + .val = 5241, + .val2 = 600000, + }, + { + .val = 10483, + .val2 = 200000, + }, + { + .val = 20966, + .val2 = 400000, + }, + { + .val = 83865, + .val2 = 600000, + }, +}; + +static int opt3001_find_scale(const struct opt3001 *opt, int val, + int val2, u8 *exponent) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(opt3001_scales); i++) { + const struct opt3001_scale *scale = &opt3001_scales[i]; + + /* + * Combine the integer and micro parts for comparison + * purposes. Use milli lux precision to avoid 32-bit integer + * overflows. + */ + if ((val * 1000 + val2 / 1000) <= + (scale->val * 1000 + scale->val2 / 1000)) { + *exponent = i; + return 0; + } + } + + return -EINVAL; +} + +static void opt3001_to_iio_ret(struct opt3001 *opt, u8 exponent, + u16 mantissa, int *val, int *val2) +{ + int lux; + + lux = 10 * (mantissa << exponent); + *val = lux / 1000; + *val2 = (lux - (*val * 1000)) * 1000; +} + +static void opt3001_set_mode(struct opt3001 *opt, u16 *reg, u16 mode) +{ + *reg &= ~OPT3001_CONFIGURATION_M_MASK; + *reg |= mode; + opt->mode = mode; +} + +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.1 0.8"); + +static struct attribute *opt3001_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group opt3001_attribute_group = { + .attrs = opt3001_attributes, +}; + +static const struct iio_event_spec opt3001_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec opt3001_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_INT_TIME), + .event_spec = opt3001_event_spec, + .num_event_specs = ARRAY_SIZE(opt3001_event_spec), + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static int opt3001_get_lux(struct opt3001 *opt, int *val, int *val2) +{ + int ret; + u16 mantissa; + u16 reg; + u8 exponent; + u16 value; + + /* + * Enable the end-of-conversion interrupt mechanism. Note that doing + * so will overwrite the low-level limit value however we will restore + * this value later on. + */ + ret = i2c_smbus_write_word_swapped(opt->client, OPT3001_LOW_LIMIT, + OPT3001_LOW_LIMIT_EOC_ENABLE); + if (ret < 0) { + dev_err(opt->dev, "failed to write register %02x\n", + OPT3001_LOW_LIMIT); + return ret; + } + + /* Reset data-ready indicator flag (will be set in the IRQ routine) */ + opt->result_ready = false; + + /* Allow IRQ to access the device despite lock being set */ + opt->ok_to_ignore_lock = true; + + /* Configure for single-conversion mode and start a new conversion */ + ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(opt->dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + goto err; + } + + reg = ret; + opt3001_set_mode(opt, ®, OPT3001_CONFIGURATION_M_SINGLE); + + ret = i2c_smbus_write_word_swapped(opt->client, OPT3001_CONFIGURATION, + reg); + if (ret < 0) { + dev_err(opt->dev, "failed to write register %02x\n", + OPT3001_CONFIGURATION); + goto err; + } + + /* Wait for the IRQ to indicate the conversion is complete */ + ret = wait_event_timeout(opt->result_ready_queue, opt->result_ready, + OPT3001_RESULT_READY_TIMEOUT); + +err: + /* Disallow IRQ to access the device while lock is active */ + opt->ok_to_ignore_lock = false; + + if (ret == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + /* + * Disable the end-of-conversion interrupt mechanism by restoring the + * low-level limit value (clearing OPT3001_LOW_LIMIT_EOC_ENABLE). Note + * that selectively clearing those enable bits would affect the actual + * limit value due to bit-overlap and therefore can't be done. + */ + value = (opt->low_thresh_exp << 12) | opt->low_thresh_mantissa; + ret = i2c_smbus_write_word_swapped(opt->client, OPT3001_LOW_LIMIT, + value); + if (ret < 0) { + dev_err(opt->dev, "failed to write register %02x\n", + OPT3001_LOW_LIMIT); + return ret; + } + + exponent = OPT3001_REG_EXPONENT(opt->result); + mantissa = OPT3001_REG_MANTISSA(opt->result); + + opt3001_to_iio_ret(opt, exponent, mantissa, val, val2); + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int opt3001_get_int_time(struct opt3001 *opt, int *val, int *val2) +{ + *val = 0; + *val2 = opt->int_time; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int opt3001_set_int_time(struct opt3001 *opt, int time) +{ + int ret; + u16 reg; + + ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(opt->dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + return ret; + } + + reg = ret; + + switch (time) { + case 100000: + reg &= ~OPT3001_CONFIGURATION_CT; + opt->int_time = 100000; + break; + case 800000: + reg |= OPT3001_CONFIGURATION_CT; + opt->int_time = 800000; + break; + default: + return -EINVAL; + } + + return i2c_smbus_write_word_swapped(opt->client, OPT3001_CONFIGURATION, + reg); +} + +static int opt3001_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + struct opt3001 *opt = iio_priv(iio); + int ret; + + if (opt->mode == OPT3001_CONFIGURATION_M_CONTINUOUS) + return -EBUSY; + + if (chan->type != IIO_LIGHT) + return -EINVAL; + + mutex_lock(&opt->lock); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = opt3001_get_lux(opt, val, val2); + break; + case IIO_CHAN_INFO_INT_TIME: + ret = opt3001_get_int_time(opt, val, val2); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&opt->lock); + + return ret; +} + +static int opt3001_write_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + struct opt3001 *opt = iio_priv(iio); + int ret; + + if (opt->mode == OPT3001_CONFIGURATION_M_CONTINUOUS) + return -EBUSY; + + if (chan->type != IIO_LIGHT) + return -EINVAL; + + if (mask != IIO_CHAN_INFO_INT_TIME) + return -EINVAL; + + if (val != 0) + return -EINVAL; + + mutex_lock(&opt->lock); + ret = opt3001_set_int_time(opt, val2); + mutex_unlock(&opt->lock); + + return ret; +} + +static int opt3001_read_event_value(struct iio_dev *iio, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, + int *val, int *val2) +{ + struct opt3001 *opt = iio_priv(iio); + int ret = IIO_VAL_INT_PLUS_MICRO; + + mutex_lock(&opt->lock); + + switch (dir) { + case IIO_EV_DIR_RISING: + opt3001_to_iio_ret(opt, opt->high_thresh_exp, + opt->high_thresh_mantissa, val, val2); + break; + case IIO_EV_DIR_FALLING: + opt3001_to_iio_ret(opt, opt->low_thresh_exp, + opt->low_thresh_mantissa, val, val2); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&opt->lock); + + return ret; +} + +static int opt3001_write_event_value(struct iio_dev *iio, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, + int val, int val2) +{ + struct opt3001 *opt = iio_priv(iio); + int ret; + + u16 mantissa; + u16 value; + u16 reg; + + u8 exponent; + + if (val < 0) + return -EINVAL; + + mutex_lock(&opt->lock); + + ret = opt3001_find_scale(opt, val, val2, &exponent); + if (ret < 0) { + dev_err(opt->dev, "can't find scale for %d.%06u\n", val, val2); + goto err; + } + + mantissa = (((val * 1000) + (val2 / 1000)) / 10) >> exponent; + value = (exponent << 12) | mantissa; + + switch (dir) { + case IIO_EV_DIR_RISING: + reg = OPT3001_HIGH_LIMIT; + opt->high_thresh_mantissa = mantissa; + opt->high_thresh_exp = exponent; + break; + case IIO_EV_DIR_FALLING: + reg = OPT3001_LOW_LIMIT; + opt->low_thresh_mantissa = mantissa; + opt->low_thresh_exp = exponent; + break; + default: + ret = -EINVAL; + goto err; + } + + ret = i2c_smbus_write_word_swapped(opt->client, reg, value); + if (ret < 0) { + dev_err(opt->dev, "failed to write register %02x\n", reg); + goto err; + } + +err: + mutex_unlock(&opt->lock); + + return ret; +} + +static int opt3001_read_event_config(struct iio_dev *iio, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + struct opt3001 *opt = iio_priv(iio); + + return opt->mode == OPT3001_CONFIGURATION_M_CONTINUOUS; +} + +static int opt3001_write_event_config(struct iio_dev *iio, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, int state) +{ + struct opt3001 *opt = iio_priv(iio); + int ret; + u16 mode; + u16 reg; + + if (state && opt->mode == OPT3001_CONFIGURATION_M_CONTINUOUS) + return 0; + + if (!state && opt->mode == OPT3001_CONFIGURATION_M_SHUTDOWN) + return 0; + + mutex_lock(&opt->lock); + + mode = state ? OPT3001_CONFIGURATION_M_CONTINUOUS + : OPT3001_CONFIGURATION_M_SHUTDOWN; + + ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(opt->dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + goto err; + } + + reg = ret; + opt3001_set_mode(opt, ®, mode); + + ret = i2c_smbus_write_word_swapped(opt->client, OPT3001_CONFIGURATION, + reg); + if (ret < 0) { + dev_err(opt->dev, "failed to write register %02x\n", + OPT3001_CONFIGURATION); + goto err; + } + +err: + mutex_unlock(&opt->lock); + + return ret; +} + +static const struct iio_info opt3001_info = { + .driver_module = THIS_MODULE, + .attrs = &opt3001_attribute_group, + .read_raw = opt3001_read_raw, + .write_raw = opt3001_write_raw, + .read_event_value = opt3001_read_event_value, + .write_event_value = opt3001_write_event_value, + .read_event_config = opt3001_read_event_config, + .write_event_config = opt3001_write_event_config, +}; + +static int opt3001_read_id(struct opt3001 *opt) +{ + char manufacturer[2]; + u16 device_id; + int ret; + + ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_MANUFACTURER_ID); + if (ret < 0) { + dev_err(opt->dev, "failed to read register %02x\n", + OPT3001_MANUFACTURER_ID); + return ret; + } + + manufacturer[0] = ret >> 8; + manufacturer[1] = ret & 0xff; + + ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_DEVICE_ID); + if (ret < 0) { + dev_err(opt->dev, "failed to read register %02x\n", + OPT3001_DEVICE_ID); + return ret; + } + + device_id = ret; + + dev_info(opt->dev, "Found %c%c OPT%04x\n", manufacturer[0], + manufacturer[1], device_id); + + return 0; +} + +static int opt3001_configure(struct opt3001 *opt) +{ + int ret; + u16 reg; + + ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(opt->dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + return ret; + } + + reg = ret; + + /* Enable automatic full-scale setting mode */ + reg &= ~OPT3001_CONFIGURATION_RN_MASK; + reg |= OPT3001_CONFIGURATION_RN_AUTO; + + /* Reflect status of the device's integration time setting */ + if (reg & OPT3001_CONFIGURATION_CT) + opt->int_time = 800000; + else + opt->int_time = 100000; + + /* Ensure device is in shutdown initially */ + opt3001_set_mode(opt, ®, OPT3001_CONFIGURATION_M_SHUTDOWN); + + /* Configure for latched window-style comparison operation */ + reg |= OPT3001_CONFIGURATION_L; + reg &= ~OPT3001_CONFIGURATION_POL; + reg &= ~OPT3001_CONFIGURATION_ME; + reg &= ~OPT3001_CONFIGURATION_FC_MASK; + + ret = i2c_smbus_write_word_swapped(opt->client, OPT3001_CONFIGURATION, + reg); + if (ret < 0) { + dev_err(opt->dev, "failed to write register %02x\n", + OPT3001_CONFIGURATION); + return ret; + } + + ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_LOW_LIMIT); + if (ret < 0) { + dev_err(opt->dev, "failed to read register %02x\n", + OPT3001_LOW_LIMIT); + return ret; + } + + opt->low_thresh_mantissa = OPT3001_REG_MANTISSA(ret); + opt->low_thresh_exp = OPT3001_REG_EXPONENT(ret); + + ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_HIGH_LIMIT); + if (ret < 0) { + dev_err(opt->dev, "failed to read register %02x\n", + OPT3001_HIGH_LIMIT); + return ret; + } + + opt->high_thresh_mantissa = OPT3001_REG_MANTISSA(ret); + opt->high_thresh_exp = OPT3001_REG_EXPONENT(ret); + + return 0; +} + +static irqreturn_t opt3001_irq(int irq, void *_iio) +{ + struct iio_dev *iio = _iio; + struct opt3001 *opt = iio_priv(iio); + int ret; + + if (!opt->ok_to_ignore_lock) + mutex_lock(&opt->lock); + + ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(opt->dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + goto out; + } + + if ((ret & OPT3001_CONFIGURATION_M_MASK) == + OPT3001_CONFIGURATION_M_CONTINUOUS) { + if (ret & OPT3001_CONFIGURATION_FH) + iio_push_event(iio, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns()); + if (ret & OPT3001_CONFIGURATION_FL) + iio_push_event(iio, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns()); + } else if (ret & OPT3001_CONFIGURATION_CRF) { + ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_RESULT); + if (ret < 0) { + dev_err(opt->dev, "failed to read register %02x\n", + OPT3001_RESULT); + goto out; + } + opt->result = ret; + opt->result_ready = true; + wake_up(&opt->result_ready_queue); + } + +out: + if (!opt->ok_to_ignore_lock) + mutex_unlock(&opt->lock); + + return IRQ_HANDLED; +} + +static int opt3001_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + + struct iio_dev *iio; + struct opt3001 *opt; + int irq = client->irq; + int ret; + + iio = devm_iio_device_alloc(dev, sizeof(*opt)); + if (!iio) + return -ENOMEM; + + opt = iio_priv(iio); + opt->client = client; + opt->dev = dev; + + mutex_init(&opt->lock); + init_waitqueue_head(&opt->result_ready_queue); + i2c_set_clientdata(client, iio); + + ret = opt3001_read_id(opt); + if (ret) + return ret; + + ret = opt3001_configure(opt); + if (ret) + return ret; + + iio->name = client->name; + iio->channels = opt3001_channels; + iio->num_channels = ARRAY_SIZE(opt3001_channels); + iio->dev.parent = dev; + iio->modes = INDIO_DIRECT_MODE; + iio->info = &opt3001_info; + + ret = devm_iio_device_register(dev, iio); + if (ret) { + dev_err(dev, "failed to register IIO device\n"); + return ret; + } + + ret = request_threaded_irq(irq, NULL, opt3001_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "opt3001", iio); + if (ret) { + dev_err(dev, "failed to request IRQ #%d\n", irq); + return ret; + } + + return 0; +} + +static int opt3001_remove(struct i2c_client *client) +{ + struct iio_dev *iio = i2c_get_clientdata(client); + struct opt3001 *opt = iio_priv(iio); + int ret; + u16 reg; + + free_irq(client->irq, iio); + + ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(opt->dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + return ret; + } + + reg = ret; + opt3001_set_mode(opt, ®, OPT3001_CONFIGURATION_M_SHUTDOWN); + + ret = i2c_smbus_write_word_swapped(opt->client, OPT3001_CONFIGURATION, + reg); + if (ret < 0) { + dev_err(opt->dev, "failed to write register %02x\n", + OPT3001_CONFIGURATION); + return ret; + } + + return 0; +} + +static const struct i2c_device_id opt3001_id[] = { + { "opt3001", 0 }, + { } /* Terminating Entry */ +}; +MODULE_DEVICE_TABLE(i2c, opt3001_id); + +static const struct of_device_id opt3001_of_match[] = { + { .compatible = "ti,opt3001" }, + { } +}; + +static struct i2c_driver opt3001_driver = { + .probe = opt3001_probe, + .remove = opt3001_remove, + .id_table = opt3001_id, + + .driver = { + .name = "opt3001", + .of_match_table = of_match_ptr(opt3001_of_match), + .owner = THIS_MODULE, + }, +}; + +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 000000000000..45f7bde02bbf --- /dev/null +++ b/drivers/iio/light/pa12203001.c @@ -0,0 +1,483 @@ +/* + * Copyright (c) 2015 Intel Corporation + * + * Driver for TXC PA12203001 Proximity and Ambient Light Sensor. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * To do: Interrupt support. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> + +#define PA12203001_DRIVER_NAME "pa12203001" + +#define PA12203001_REG_CFG0 0x00 +#define PA12203001_REG_CFG1 0x01 +#define PA12203001_REG_CFG2 0x02 +#define PA12203001_REG_CFG3 0x03 + +#define PA12203001_REG_ADL 0x0b +#define PA12203001_REG_PDH 0x0e + +#define PA12203001_REG_POFS 0x10 +#define PA12203001_REG_PSET 0x11 + +#define PA12203001_ALS_EN_MASK BIT(0) +#define PA12203001_PX_EN_MASK BIT(1) +#define PA12203001_PX_NORMAL_MODE_MASK GENMASK(7, 6) +#define PA12203001_AFSR_MASK GENMASK(5, 4) +#define PA12203001_AFSR_SHIFT 4 + +#define PA12203001_PSCAN 0x03 + +/* als range 31000, ps, als disabled */ +#define PA12203001_REG_CFG0_DEFAULT 0x30 + +/* led current: 100 mA */ +#define PA12203001_REG_CFG1_DEFAULT 0x20 + +/* ps mode: normal, interrupts not active */ +#define PA12203001_REG_CFG2_DEFAULT 0xcc + +#define PA12203001_REG_CFG3_DEFAULT 0x00 + +#define PA12203001_SLEEP_DELAY_MS 3000 + +#define PA12203001_CHIP_ENABLE 0xff +#define PA12203001_CHIP_DISABLE 0x00 + +/* available scales: corresponding to [500, 4000, 7000, 31000] lux */ +static const int pa12203001_scales[] = { 7629, 61036, 106813, 473029}; + +struct pa12203001_data { + struct i2c_client *client; + + /* protect device states */ + struct mutex lock; + + bool als_enabled; + bool px_enabled; + bool als_needs_enable; + bool px_needs_enable; + + struct regmap *map; +}; + +static const struct { + u8 reg; + u8 val; +} regvals[] = { + {PA12203001_REG_CFG0, PA12203001_REG_CFG0_DEFAULT}, + {PA12203001_REG_CFG1, PA12203001_REG_CFG1_DEFAULT}, + {PA12203001_REG_CFG2, PA12203001_REG_CFG2_DEFAULT}, + {PA12203001_REG_CFG3, PA12203001_REG_CFG3_DEFAULT}, + {PA12203001_REG_PSET, PA12203001_PSCAN}, +}; + +static IIO_CONST_ATTR(in_illuminance_scale_available, + "0.007629 0.061036 0.106813 0.473029"); + +static struct attribute *pa12203001_attrs[] = { + &iio_const_attr_in_illuminance_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group pa12203001_attr_group = { + .attrs = pa12203001_attrs, +}; + +static const struct iio_chan_spec pa12203001_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; + +static const struct regmap_range pa12203001_volatile_regs_ranges[] = { + regmap_reg_range(PA12203001_REG_ADL, PA12203001_REG_ADL + 1), + regmap_reg_range(PA12203001_REG_PDH, PA12203001_REG_PDH), +}; + +static const struct regmap_access_table pa12203001_volatile_regs = { + .yes_ranges = pa12203001_volatile_regs_ranges, + .n_yes_ranges = ARRAY_SIZE(pa12203001_volatile_regs_ranges), +}; + +static const struct regmap_config pa12203001_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = PA12203001_REG_PSET, + .cache_type = REGCACHE_RBTREE, + .volatile_table = &pa12203001_volatile_regs, +}; + +static inline int pa12203001_als_enable(struct pa12203001_data *data, u8 enable) +{ + int ret; + + ret = regmap_update_bits(data->map, PA12203001_REG_CFG0, + PA12203001_ALS_EN_MASK, enable); + if (ret < 0) + return ret; + + data->als_enabled = !!enable; + + return 0; +} + +static inline int pa12203001_px_enable(struct pa12203001_data *data, u8 enable) +{ + int ret; + + ret = regmap_update_bits(data->map, PA12203001_REG_CFG0, + PA12203001_PX_EN_MASK, enable); + if (ret < 0) + return ret; + + data->px_enabled = !!enable; + + return 0; +} + +static int pa12203001_set_power_state(struct pa12203001_data *data, bool on, + u8 mask) +{ +#ifdef CONFIG_PM + int ret; + + if (on && (mask & PA12203001_ALS_EN_MASK)) { + mutex_lock(&data->lock); + if (data->px_enabled) { + ret = pa12203001_als_enable(data, + PA12203001_ALS_EN_MASK); + if (ret < 0) + goto err; + } else { + data->als_needs_enable = true; + } + mutex_unlock(&data->lock); + } + + if (on && (mask & PA12203001_PX_EN_MASK)) { + mutex_lock(&data->lock); + if (data->als_enabled) { + ret = pa12203001_px_enable(data, PA12203001_PX_EN_MASK); + if (ret < 0) + goto err; + } else { + data->px_needs_enable = true; + } + mutex_unlock(&data->lock); + } + + if (on) { + ret = pm_runtime_get_sync(&data->client->dev); + if (ret < 0) + pm_runtime_put_noidle(&data->client->dev); + + } else { + pm_runtime_mark_last_busy(&data->client->dev); + ret = pm_runtime_put_autosuspend(&data->client->dev); + } + + return ret; + +err: + mutex_unlock(&data->lock); + return ret; + +#endif + return 0; +} + +static int pa12203001_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct pa12203001_data *data = iio_priv(indio_dev); + int ret; + u8 dev_mask; + unsigned int reg_byte; + __le16 reg_word; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + dev_mask = PA12203001_ALS_EN_MASK; + ret = pa12203001_set_power_state(data, true, dev_mask); + if (ret < 0) + return ret; + /* + * ALS ADC value is stored in registers + * PA12203001_REG_ADL and in PA12203001_REG_ADL + 1. + */ + ret = regmap_bulk_read(data->map, PA12203001_REG_ADL, + ®_word, 2); + if (ret < 0) + goto reg_err; + + *val = le16_to_cpu(reg_word); + ret = pa12203001_set_power_state(data, false, dev_mask); + if (ret < 0) + return ret; + break; + case IIO_PROXIMITY: + dev_mask = PA12203001_PX_EN_MASK; + ret = pa12203001_set_power_state(data, true, dev_mask); + if (ret < 0) + return ret; + ret = regmap_read(data->map, PA12203001_REG_PDH, + ®_byte); + if (ret < 0) + goto reg_err; + + *val = reg_byte; + ret = pa12203001_set_power_state(data, false, dev_mask); + if (ret < 0) + return ret; + break; + default: + return -EINVAL; + } + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(data->map, PA12203001_REG_CFG0, ®_byte); + if (ret < 0) + return ret; + *val = 0; + reg_byte = (reg_byte & PA12203001_AFSR_MASK); + *val2 = pa12203001_scales[reg_byte >> 4]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + +reg_err: + pa12203001_set_power_state(data, false, dev_mask); + return ret; +} + +static int pa12203001_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct pa12203001_data *data = iio_priv(indio_dev); + int i, ret, new_val; + unsigned int reg_byte; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(data->map, PA12203001_REG_CFG0, ®_byte); + if (val != 0 || ret < 0) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(pa12203001_scales); i++) { + if (val2 == pa12203001_scales[i]) { + new_val = i << PA12203001_AFSR_SHIFT; + return regmap_update_bits(data->map, + PA12203001_REG_CFG0, + PA12203001_AFSR_MASK, + new_val); + } + } + break; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_info pa12203001_info = { + .driver_module = THIS_MODULE, + .read_raw = pa12203001_read_raw, + .write_raw = pa12203001_write_raw, + .attrs = &pa12203001_attr_group, +}; + +static int pa12203001_init(struct iio_dev *indio_dev) +{ + struct pa12203001_data *data = iio_priv(indio_dev); + int i, ret; + + for (i = 0; i < ARRAY_SIZE(regvals); i++) { + ret = regmap_write(data->map, regvals[i].reg, regvals[i].val); + if (ret < 0) + return ret; + } + + return 0; +} + +static int pa12203001_power_chip(struct iio_dev *indio_dev, u8 state) +{ + struct pa12203001_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + ret = pa12203001_als_enable(data, state); + if (ret < 0) + goto out; + + ret = pa12203001_px_enable(data, state); + +out: + mutex_unlock(&data->lock); + return ret; +} + +static int pa12203001_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pa12203001_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, + sizeof(struct pa12203001_data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + data->map = devm_regmap_init_i2c(client, &pa12203001_regmap_config); + if (IS_ERR(data->map)) + return PTR_ERR(data->map); + + mutex_init(&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &pa12203001_info; + indio_dev->name = PA12203001_DRIVER_NAME; + indio_dev->channels = pa12203001_channels; + indio_dev->num_channels = ARRAY_SIZE(pa12203001_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = pa12203001_init(indio_dev); + if (ret < 0) + return ret; + + ret = pa12203001_power_chip(indio_dev, PA12203001_CHIP_ENABLE); + if (ret < 0) + return ret; + + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) { + pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE); + return ret; + } + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + PA12203001_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + return iio_device_register(indio_dev); +} + +static int pa12203001_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + return pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE); +} + +#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM) +static int pa12203001_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + + return pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE); +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int pa12203001_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + + return pa12203001_power_chip(indio_dev, PA12203001_CHIP_ENABLE); +} +#endif + +#ifdef CONFIG_PM +static int pa12203001_runtime_resume(struct device *dev) +{ + struct pa12203001_data *data; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + mutex_lock(&data->lock); + if (data->als_needs_enable) { + pa12203001_als_enable(data, PA12203001_ALS_EN_MASK); + data->als_needs_enable = false; + } + if (data->px_needs_enable) { + pa12203001_px_enable(data, PA12203001_PX_EN_MASK); + data->px_needs_enable = false; + } + mutex_unlock(&data->lock); + + return 0; +} +#endif + +static const struct dev_pm_ops pa12203001_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pa12203001_suspend, pa12203001_resume) + SET_RUNTIME_PM_OPS(pa12203001_suspend, pa12203001_runtime_resume, NULL) +}; + +static const struct acpi_device_id pa12203001_acpi_match[] = { + { "TXCPA122", 0}, + {} +}; + +MODULE_DEVICE_TABLE(acpi, pa12203001_acpi_match); + +static const struct i2c_device_id pa12203001_id[] = { + {"txcpa122", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, pa12203001_id); + +static struct i2c_driver pa12203001_driver = { + .driver = { + .name = PA12203001_DRIVER_NAME, + .pm = &pa12203001_pm_ops, + .acpi_match_table = ACPI_PTR(pa12203001_acpi_match), + }, + .probe = pa12203001_probe, + .remove = pa12203001_remove, + .id_table = pa12203001_id, + +}; +module_i2c_driver(pa12203001_driver); + +MODULE_AUTHOR("Adriana Reus <adriana.reus@intel.com>"); +MODULE_DESCRIPTION("Driver for TXC PA12203001 Proximity and Light Sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/rpr0521.c b/drivers/iio/light/rpr0521.c new file mode 100644 index 000000000000..4b75bb0998b3 --- /dev/null +++ b/drivers/iio/light/rpr0521.c @@ -0,0 +1,615 @@ +/* + * RPR-0521 ROHM Ambient Light and Proximity Sensor + * + * Copyright (c) 2015, Intel Corporation. + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * IIO driver for RPR-0521RS (7-bit I2C slave address 0x38). + * + * TODO: illuminance channel, PM support, buffer + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/delay.h> +#include <linux/acpi.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/pm_runtime.h> + +#define RPR0521_REG_SYSTEM_CTRL 0x40 +#define RPR0521_REG_MODE_CTRL 0x41 +#define RPR0521_REG_ALS_CTRL 0x42 +#define RPR0521_REG_PXS_CTRL 0x43 +#define RPR0521_REG_PXS_DATA 0x44 /* 16-bit, little endian */ +#define RPR0521_REG_ALS_DATA0 0x46 /* 16-bit, little endian */ +#define RPR0521_REG_ALS_DATA1 0x48 /* 16-bit, little endian */ +#define RPR0521_REG_ID 0x92 + +#define RPR0521_MODE_ALS_MASK BIT(7) +#define RPR0521_MODE_PXS_MASK BIT(6) +#define RPR0521_MODE_MEAS_TIME_MASK GENMASK(3, 0) +#define RPR0521_ALS_DATA0_GAIN_MASK GENMASK(5, 4) +#define RPR0521_ALS_DATA0_GAIN_SHIFT 4 +#define RPR0521_ALS_DATA1_GAIN_MASK GENMASK(3, 2) +#define RPR0521_ALS_DATA1_GAIN_SHIFT 2 +#define RPR0521_PXS_GAIN_MASK GENMASK(5, 4) +#define RPR0521_PXS_GAIN_SHIFT 4 + +#define RPR0521_MODE_ALS_ENABLE BIT(7) +#define RPR0521_MODE_ALS_DISABLE 0x00 +#define RPR0521_MODE_PXS_ENABLE BIT(6) +#define RPR0521_MODE_PXS_DISABLE 0x00 + +#define RPR0521_MANUFACT_ID 0xE0 +#define RPR0521_DEFAULT_MEAS_TIME 0x06 /* ALS - 100ms, PXS - 100ms */ + +#define RPR0521_DRV_NAME "RPR0521" +#define RPR0521_REGMAP_NAME "rpr0521_regmap" + +#define RPR0521_SLEEP_DELAY_MS 2000 + +#define RPR0521_ALS_SCALE_AVAIL "0.007812 0.015625 0.5 1" +#define RPR0521_PXS_SCALE_AVAIL "0.125 0.5 1" + +struct rpr0521_gain { + int scale; + int uscale; +}; + +static const struct rpr0521_gain rpr0521_als_gain[4] = { + {1, 0}, /* x1 */ + {0, 500000}, /* x2 */ + {0, 15625}, /* x64 */ + {0, 7812}, /* x128 */ +}; + +static const struct rpr0521_gain rpr0521_pxs_gain[3] = { + {1, 0}, /* x1 */ + {0, 500000}, /* x2 */ + {0, 125000}, /* x4 */ +}; + +enum rpr0521_channel { + RPR0521_CHAN_ALS_DATA0, + RPR0521_CHAN_ALS_DATA1, + RPR0521_CHAN_PXS, +}; + +struct rpr0521_reg_desc { + u8 address; + u8 device_mask; +}; + +static const struct rpr0521_reg_desc rpr0521_data_reg[] = { + [RPR0521_CHAN_ALS_DATA0] = { + .address = RPR0521_REG_ALS_DATA0, + .device_mask = RPR0521_MODE_ALS_MASK, + }, + [RPR0521_CHAN_ALS_DATA1] = { + .address = RPR0521_REG_ALS_DATA1, + .device_mask = RPR0521_MODE_ALS_MASK, + }, + [RPR0521_CHAN_PXS] = { + .address = RPR0521_REG_PXS_DATA, + .device_mask = RPR0521_MODE_PXS_MASK, + }, +}; + +static const struct rpr0521_gain_info { + u8 reg; + u8 mask; + u8 shift; + const struct rpr0521_gain *gain; + int size; +} rpr0521_gain[] = { + [RPR0521_CHAN_ALS_DATA0] = { + .reg = RPR0521_REG_ALS_CTRL, + .mask = RPR0521_ALS_DATA0_GAIN_MASK, + .shift = RPR0521_ALS_DATA0_GAIN_SHIFT, + .gain = rpr0521_als_gain, + .size = ARRAY_SIZE(rpr0521_als_gain), + }, + [RPR0521_CHAN_ALS_DATA1] = { + .reg = RPR0521_REG_ALS_CTRL, + .mask = RPR0521_ALS_DATA1_GAIN_MASK, + .shift = RPR0521_ALS_DATA1_GAIN_SHIFT, + .gain = rpr0521_als_gain, + .size = ARRAY_SIZE(rpr0521_als_gain), + }, + [RPR0521_CHAN_PXS] = { + .reg = RPR0521_REG_PXS_CTRL, + .mask = RPR0521_PXS_GAIN_MASK, + .shift = RPR0521_PXS_GAIN_SHIFT, + .gain = rpr0521_pxs_gain, + .size = ARRAY_SIZE(rpr0521_pxs_gain), + }, +}; + +struct rpr0521_data { + struct i2c_client *client; + + /* protect device params updates (e.g state, gain) */ + struct mutex lock; + + /* device active status */ + bool als_dev_en; + bool pxs_dev_en; + + /* optimize runtime pm ops - enable device only if needed */ + bool als_ps_need_en; + bool pxs_ps_need_en; + + struct regmap *regmap; +}; + +static IIO_CONST_ATTR(in_intensity_scale_available, RPR0521_ALS_SCALE_AVAIL); +static IIO_CONST_ATTR(in_proximity_scale_available, RPR0521_PXS_SCALE_AVAIL); + +static struct attribute *rpr0521_attributes[] = { + &iio_const_attr_in_intensity_scale_available.dev_attr.attr, + &iio_const_attr_in_proximity_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group rpr0521_attribute_group = { + .attrs = rpr0521_attributes, +}; + +static const struct iio_chan_spec rpr0521_channels[] = { + { + .type = IIO_INTENSITY, + .modified = 1, + .address = RPR0521_CHAN_ALS_DATA0, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_INTENSITY, + .modified = 1, + .address = RPR0521_CHAN_ALS_DATA1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_PROXIMITY, + .address = RPR0521_CHAN_PXS, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + } +}; + +static int rpr0521_als_enable(struct rpr0521_data *data, u8 status) +{ + int ret; + + ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL, + RPR0521_MODE_ALS_MASK, + status); + if (ret < 0) + return ret; + + data->als_dev_en = true; + + return 0; +} + +static int rpr0521_pxs_enable(struct rpr0521_data *data, u8 status) +{ + int ret; + + ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL, + RPR0521_MODE_PXS_MASK, + status); + if (ret < 0) + return ret; + + data->pxs_dev_en = true; + + return 0; +} + +/** + * rpr0521_set_power_state - handles runtime PM state and sensors enabled status + * + * @data: rpr0521 device private data + * @on: state to be set for devices in @device_mask + * @device_mask: bitmask specifying for which device we need to update @on state + * + * We rely on rpr0521_runtime_resume to enable our @device_mask devices, but + * if (for example) PXS was enabled (pxs_dev_en = true) by a previous call to + * rpr0521_runtime_resume and we want to enable ALS we MUST set ALS enable + * bit of RPR0521_REG_MODE_CTRL here because rpr0521_runtime_resume will not + * be called twice. + */ +static int rpr0521_set_power_state(struct rpr0521_data *data, bool on, + u8 device_mask) +{ +#ifdef CONFIG_PM + int ret; + u8 update_mask = 0; + + if (device_mask & RPR0521_MODE_ALS_MASK) { + if (on && !data->als_ps_need_en && data->pxs_dev_en) + update_mask |= RPR0521_MODE_ALS_MASK; + else + data->als_ps_need_en = on; + } + + if (device_mask & RPR0521_MODE_PXS_MASK) { + if (on && !data->pxs_ps_need_en && data->als_dev_en) + update_mask |= RPR0521_MODE_PXS_MASK; + else + data->pxs_ps_need_en = on; + } + + if (update_mask) { + ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL, + update_mask, update_mask); + if (ret < 0) + return ret; + } + + if (on) { + ret = pm_runtime_get_sync(&data->client->dev); + } else { + pm_runtime_mark_last_busy(&data->client->dev); + ret = pm_runtime_put_autosuspend(&data->client->dev); + } + if (ret < 0) { + dev_err(&data->client->dev, + "Failed: rpr0521_set_power_state for %d, ret %d\n", + on, ret); + if (on) + pm_runtime_put_noidle(&data->client->dev); + + return ret; + } +#endif + return 0; +} + +static int rpr0521_get_gain(struct rpr0521_data *data, int chan, + int *val, int *val2) +{ + int ret, reg, idx; + + ret = regmap_read(data->regmap, rpr0521_gain[chan].reg, ®); + if (ret < 0) + return ret; + + idx = (rpr0521_gain[chan].mask & reg) >> rpr0521_gain[chan].shift; + *val = rpr0521_gain[chan].gain[idx].scale; + *val2 = rpr0521_gain[chan].gain[idx].uscale; + + return 0; +} + +static int rpr0521_set_gain(struct rpr0521_data *data, int chan, + int val, int val2) +{ + int i, idx = -EINVAL; + + /* get gain index */ + for (i = 0; i < rpr0521_gain[chan].size; i++) + if (val == rpr0521_gain[chan].gain[i].scale && + val2 == rpr0521_gain[chan].gain[i].uscale) { + idx = i; + break; + } + + if (idx < 0) + return idx; + + return regmap_update_bits(data->regmap, rpr0521_gain[chan].reg, + rpr0521_gain[chan].mask, + idx << rpr0521_gain[chan].shift); +} + +static int rpr0521_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct rpr0521_data *data = iio_priv(indio_dev); + int ret; + u8 device_mask; + __le16 raw_data; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_INTENSITY && chan->type != IIO_PROXIMITY) + return -EINVAL; + + device_mask = rpr0521_data_reg[chan->address].device_mask; + + mutex_lock(&data->lock); + ret = rpr0521_set_power_state(data, true, device_mask); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } + + ret = regmap_bulk_read(data->regmap, + rpr0521_data_reg[chan->address].address, + &raw_data, 2); + if (ret < 0) { + rpr0521_set_power_state(data, false, device_mask); + mutex_unlock(&data->lock); + return ret; + } + + ret = rpr0521_set_power_state(data, false, device_mask); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + *val = le16_to_cpu(raw_data); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + mutex_lock(&data->lock); + ret = rpr0521_get_gain(data, chan->address, val, val2); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int rpr0521_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct rpr0521_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + mutex_lock(&data->lock); + ret = rpr0521_set_gain(data, chan->address, val, val2); + mutex_unlock(&data->lock); + + return ret; + default: + return -EINVAL; + } +} + +static const struct iio_info rpr0521_info = { + .driver_module = THIS_MODULE, + .read_raw = rpr0521_read_raw, + .write_raw = rpr0521_write_raw, + .attrs = &rpr0521_attribute_group, +}; + +static int rpr0521_init(struct rpr0521_data *data) +{ + int ret; + int id; + + ret = regmap_read(data->regmap, RPR0521_REG_ID, &id); + if (ret < 0) { + dev_err(&data->client->dev, "Failed to read REG_ID register\n"); + return ret; + } + + if (id != RPR0521_MANUFACT_ID) { + dev_err(&data->client->dev, "Wrong id, got %x, expected %x\n", + id, RPR0521_MANUFACT_ID); + return -ENODEV; + } + + /* set default measurement time - 100 ms for both ALS and PS */ + ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL, + RPR0521_MODE_MEAS_TIME_MASK, + RPR0521_DEFAULT_MEAS_TIME); + if (ret) { + pr_err("regmap_update_bits returned %d\n", ret); + return ret; + } + + ret = rpr0521_als_enable(data, RPR0521_MODE_ALS_ENABLE); + if (ret < 0) + return ret; + ret = rpr0521_pxs_enable(data, RPR0521_MODE_PXS_ENABLE); + if (ret < 0) + return ret; + + return 0; +} + +static int rpr0521_poweroff(struct rpr0521_data *data) +{ + int ret; + + ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL, + RPR0521_MODE_ALS_MASK | + RPR0521_MODE_PXS_MASK, + RPR0521_MODE_ALS_DISABLE | + RPR0521_MODE_PXS_DISABLE); + if (ret < 0) + return ret; + + data->als_dev_en = false; + data->pxs_dev_en = false; + + return 0; +} + +static bool rpr0521_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RPR0521_REG_MODE_CTRL: + case RPR0521_REG_ALS_CTRL: + case RPR0521_REG_PXS_CTRL: + return false; + default: + return true; + } +} + +static const struct regmap_config rpr0521_regmap_config = { + .name = RPR0521_REGMAP_NAME, + + .reg_bits = 8, + .val_bits = 8, + + .max_register = RPR0521_REG_ID, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = rpr0521_is_volatile_reg, +}; + +static int rpr0521_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct rpr0521_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &rpr0521_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "regmap_init failed!\n"); + return PTR_ERR(regmap); + } + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + + mutex_init(&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &rpr0521_info; + indio_dev->name = RPR0521_DRV_NAME; + indio_dev->channels = rpr0521_channels; + indio_dev->num_channels = ARRAY_SIZE(rpr0521_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = rpr0521_init(data); + if (ret < 0) { + dev_err(&client->dev, "rpr0521 chip init failed\n"); + return ret; + } + ret = iio_device_register(indio_dev); + if (ret < 0) + return ret; + + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto err_iio_unregister; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, RPR0521_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + return 0; + +err_iio_unregister: + iio_device_unregister(indio_dev); + return ret; +} + +static int rpr0521_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + + iio_device_unregister(indio_dev); + rpr0521_poweroff(iio_priv(indio_dev)); + + return 0; +} + +#ifdef CONFIG_PM +static int rpr0521_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct rpr0521_data *data = iio_priv(indio_dev); + int ret; + + /* disable channels and sets {als,pxs}_dev_en to false */ + mutex_lock(&data->lock); + ret = rpr0521_poweroff(data); + mutex_unlock(&data->lock); + + return ret; +} + +static int rpr0521_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct rpr0521_data *data = iio_priv(indio_dev); + int ret; + + if (data->als_ps_need_en) { + ret = rpr0521_als_enable(data, RPR0521_MODE_ALS_ENABLE); + if (ret < 0) + return ret; + data->als_ps_need_en = false; + } + + if (data->pxs_ps_need_en) { + ret = rpr0521_pxs_enable(data, RPR0521_MODE_PXS_ENABLE); + if (ret < 0) + return ret; + data->pxs_ps_need_en = false; + } + + return 0; +} +#endif + +static const struct dev_pm_ops rpr0521_pm_ops = { + SET_RUNTIME_PM_OPS(rpr0521_runtime_suspend, + rpr0521_runtime_resume, NULL) +}; + +static const struct acpi_device_id rpr0521_acpi_match[] = { + {"RPR0521", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, rpr0521_acpi_match); + +static const struct i2c_device_id rpr0521_id[] = { + {"rpr0521", 0}, + { } +}; + +MODULE_DEVICE_TABLE(i2c, rpr0521_id); + +static struct i2c_driver rpr0521_driver = { + .driver = { + .name = RPR0521_DRV_NAME, + .pm = &rpr0521_pm_ops, + .acpi_match_table = ACPI_PTR(rpr0521_acpi_match), + }, + .probe = rpr0521_probe, + .remove = rpr0521_remove, + .id_table = rpr0521_id, +}; + +module_i2c_driver(rpr0521_driver); + +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>"); +MODULE_DESCRIPTION("RPR0521 ROHM Ambient Light and Proximity Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c index fee4297d7c8f..f101bb5bddc7 100644 --- a/drivers/iio/light/stk3310.c +++ b/drivers/iio/light/stk3310.c @@ -43,7 +43,6 @@ #define STK3311_CHIP_ID_VAL 0x1D #define STK3310_PSINT_EN 0x01 #define STK3310_PS_MAX_VAL 0xFFFF -#define STK3310_THRESH_MAX 0xFFFF #define STK3310_DRIVER_NAME "stk3310" #define STK3310_REGMAP_NAME "stk3310_regmap" @@ -84,15 +83,13 @@ 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); -/* - * Maximum PS values with regard to scale. Used to export the 'inverse' - * PS value (high values for far objects, low values for near objects). - */ + +/* Estimate maximum proximity values with regard to measurement scale. */ static const int stk3310_ps_max[4] = { - STK3310_PS_MAX_VAL / 64, - STK3310_PS_MAX_VAL / 16, - STK3310_PS_MAX_VAL / 4, - STK3310_PS_MAX_VAL, + 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] = { @@ -128,14 +125,14 @@ static const struct iio_event_spec stk3310_events[] = { /* Proximity event */ { .type = IIO_EV_TYPE_THRESH, - .dir = IIO_EV_DIR_FALLING, + .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_RISING, + .dir = IIO_EV_DIR_FALLING, .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), }, @@ -203,25 +200,18 @@ static int stk3310_read_event(struct iio_dev *indio_dev, int *val, int *val2) { u8 reg; - u16 buf; + __be16 buf; int ret; - unsigned int index; struct stk3310_data *data = iio_priv(indio_dev); if (info != IIO_EV_INFO_VALUE) return -EINVAL; - /* - * Only proximity interrupts are implemented at the moment. - * Since we're inverting proximity values, the sensor's 'high' - * threshold will become our 'low' threshold, associated with - * 'near' events. Similarly, the sensor's 'low' threshold will - * be our 'high' threshold, associated with 'far' events. - */ + /* Only proximity interrupts are implemented at the moment. */ if (dir == IIO_EV_DIR_RISING) - reg = STK3310_REG_THDL_PS; - else if (dir == IIO_EV_DIR_FALLING) reg = STK3310_REG_THDH_PS; + else if (dir == IIO_EV_DIR_FALLING) + reg = STK3310_REG_THDL_PS; else return -EINVAL; @@ -232,8 +222,7 @@ static int stk3310_read_event(struct iio_dev *indio_dev, dev_err(&data->client->dev, "register read failed\n"); return ret; } - regmap_field_read(data->reg_ps_gain, &index); - *val = swab16(stk3310_ps_max[index] - buf); + *val = be16_to_cpu(buf); return IIO_VAL_INT; } @@ -246,7 +235,7 @@ static int stk3310_write_event(struct iio_dev *indio_dev, int val, int val2) { u8 reg; - u16 buf; + __be16 buf; int ret; unsigned int index; struct stk3310_data *data = iio_priv(indio_dev); @@ -257,13 +246,13 @@ static int stk3310_write_event(struct iio_dev *indio_dev, return -EINVAL; if (dir == IIO_EV_DIR_RISING) - reg = STK3310_REG_THDL_PS; - else if (dir == IIO_EV_DIR_FALLING) reg = STK3310_REG_THDH_PS; + else if (dir == IIO_EV_DIR_FALLING) + reg = STK3310_REG_THDL_PS; else return -EINVAL; - buf = swab16(stk3310_ps_max[index] - val); + 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"); @@ -312,7 +301,7 @@ static int stk3310_read_raw(struct iio_dev *indio_dev, int *val, int *val2, long mask) { u8 reg; - u16 buf; + __be16 buf; int ret; unsigned int index; struct stk3310_data *data = iio_priv(indio_dev); @@ -333,15 +322,7 @@ static int stk3310_read_raw(struct iio_dev *indio_dev, mutex_unlock(&data->lock); return ret; } - *val = swab16(buf); - if (chan->type == IIO_PROXIMITY) { - /* - * Invert the proximity data so we return low values - * for close objects and high values for far ones. - */ - regmap_field_read(data->reg_ps_gain, &index); - *val = stk3310_ps_max[index] - *val; - } + *val = be16_to_cpu(buf); mutex_unlock(&data->lock); return IIO_VAL_INT; case IIO_CHAN_INFO_INT_TIME: @@ -581,8 +562,8 @@ static irqreturn_t stk3310_irq_event_handler(int irq, void *private) } event = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1, IIO_EV_TYPE_THRESH, - (dir ? IIO_EV_DIR_RISING : - IIO_EV_DIR_FALLING)); + (dir ? IIO_EV_DIR_FALLING : + IIO_EV_DIR_RISING)); iio_push_event(indio_dev, event, data->timestamp); /* Reset the interrupt flag */ @@ -627,13 +608,7 @@ static int stk3310_probe(struct i2c_client *client, if (ret < 0) return ret; - ret = iio_device_register(indio_dev); - if (ret < 0) { - dev_err(&client->dev, "device_register failed\n"); - stk3310_set_state(data, STK3310_STATE_STANDBY); - } - - if (client->irq <= 0) + if (client->irq < 0) client->irq = stk3310_gpio_probe(client); if (client->irq >= 0) { @@ -648,6 +623,12 @@ static int stk3310_probe(struct i2c_client *client, client->irq); } + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "device_register failed\n"); + stk3310_set_state(data, STK3310_STATE_STANDBY); + } + return ret; } @@ -695,6 +676,7 @@ static const struct i2c_device_id stk3310_i2c_id[] = { {"STK3311", 0}, {} }; +MODULE_DEVICE_TABLE(i2c, stk3310_i2c_id); static const struct acpi_device_id stk3310_acpi_id[] = { {"STK3310", 0}, diff --git a/drivers/iio/light/tcs3414.c b/drivers/iio/light/tcs3414.c index 71c2bde275aa..f90f8c5919fe 100644 --- a/drivers/iio/light/tcs3414.c +++ b/drivers/iio/light/tcs3414.c @@ -185,7 +185,7 @@ static int tcs3414_write_raw(struct iio_dev *indio_dev, if (val != 0) return -EINVAL; for (i = 0; i < ARRAY_SIZE(tcs3414_times); i++) { - if (val == tcs3414_times[i] * 1000) { + if (val2 == tcs3414_times[i] * 1000) { data->timing &= ~TCS3414_INTEG_MASK; data->timing |= i; return i2c_smbus_write_byte_data( @@ -392,7 +392,6 @@ static struct i2c_driver tcs3414_driver = { .driver = { .name = TCS3414_DRV_NAME, .pm = &tcs3414_pm_ops, - .owner = THIS_MODULE, }, .probe = tcs3414_probe, .remove = tcs3414_remove, diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c index 752569985d1d..1b530bf04c89 100644 --- a/drivers/iio/light/tcs3472.c +++ b/drivers/iio/light/tcs3472.c @@ -366,7 +366,6 @@ static struct i2c_driver tcs3472_driver = { .driver = { .name = TCS3472_DRV_NAME, .pm = &tcs3472_pm_ops, - .owner = THIS_MODULE, }, .probe = tcs3472_probe, .remove = tcs3472_remove, diff --git a/drivers/iio/light/tsl4531.c b/drivers/iio/light/tsl4531.c index 63c26e2d5d97..26979183d27c 100644 --- a/drivers/iio/light/tsl4531.c +++ b/drivers/iio/light/tsl4531.c @@ -247,7 +247,6 @@ static struct i2c_driver tsl4531_driver = { .driver = { .name = TSL4531_DRV_NAME, .pm = TSL4531_PM_OPS, - .owner = THIS_MODULE, }, .probe = tsl4531_probe, .remove = tsl4531_remove, diff --git a/drivers/iio/light/vcnl4000.c b/drivers/iio/light/vcnl4000.c index d948c4778ba6..c9d85bbc9230 100644 --- a/drivers/iio/light/vcnl4000.c +++ b/drivers/iio/light/vcnl4000.c @@ -185,7 +185,6 @@ static int vcnl4000_probe(struct i2c_client *client, static struct i2c_driver vcnl4000_driver = { .driver = { .name = VCNL4000_DRV_NAME, - .owner = THIS_MODULE, }, .probe = vcnl4000_probe, .id_table = vcnl4000_id, |