diff options
Diffstat (limited to 'drivers/iio/light')
-rw-r--r-- | drivers/iio/light/Kconfig | 37 | ||||
-rw-r--r-- | drivers/iio/light/Makefile | 3 | ||||
-rw-r--r-- | drivers/iio/light/acpi-als.c | 231 | ||||
-rw-r--r-- | drivers/iio/light/bh1750.c | 334 | ||||
-rw-r--r-- | drivers/iio/light/hid-sensor-als.c | 14 | ||||
-rw-r--r-- | drivers/iio/light/hid-sensor-prox.c | 2 | ||||
-rw-r--r-- | drivers/iio/light/ltr501.c | 1284 | ||||
-rw-r--r-- | drivers/iio/light/stk3310.c | 722 | ||||
-rw-r--r-- | drivers/iio/light/tsl2563.c | 36 | ||||
-rw-r--r-- | drivers/iio/light/tsl4531.c | 10 |
10 files changed, 2558 insertions, 115 deletions
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 01a1a16ab7be..e6198b7c9cbf 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -5,6 +5,19 @@ menu "Light sensors" +config ACPI_ALS + tristate "ACPI Ambient Light Sensor" + depends on ACPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IIO_KFIFO_BUF + help + Say Y here if you want to build a driver for the ACPI0008 + Ambient Light Sensor. + + To compile this driver as a module, choose M here: the module will + be called acpi-als. + config ADJD_S311 tristate "ADJD-S311-CR999 digital color sensor" select IIO_BUFFER @@ -37,6 +50,16 @@ config APDS9300 To compile this driver as a module, choose M here: the module will be called apds9300. +config BH1750 + tristate "ROHM BH1750 ambient light sensor" + depends on I2C + help + Say Y here to build support for the ROHM BH1710, BH1715, BH1721, + BH1750, BH1751 ambient light sensors. + + To compile this driver as a module, choose M here: the module will + be called bh1750. + config CM32181 depends on I2C tristate "CM32181 driver" @@ -169,11 +192,23 @@ config LTR501 select IIO_TRIGGERED_BUFFER help If you say yes here you get support for the Lite-On LTR-501ALS-01 - ambient light and proximity sensor. + ambient light and proximity sensor. This driver also supports LTR-559 + ALS/PS or LTR-301 ALS sensors. This driver can also be built as a module. If so, the module will be called ltr501. +config STK3310 + tristate "STK3310 ALS and proximity sensor" + depends on I2C + help + Say yes here to get support for the Sensortek STK3310 ambient light + and proximity sensor. The STK3311 model is also supported by this + driver. + + Choosing M will build the driver as a module. If so, the module + will be called stk3310. + config TCS3414 tristate "TAOS TCS3414 digital color sensor" depends on I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index ad7c30fe443b..e2d50fd59c66 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -3,9 +3,11 @@ # # 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_BH1750) += bh1750.o obj-$(CONFIG_CM32181) += cm32181.o obj-$(CONFIG_CM3232) += cm3232.o obj-$(CONFIG_CM3323) += cm3323.o @@ -18,6 +20,7 @@ obj-$(CONFIG_JSA1212) += jsa1212.o obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o obj-$(CONFIG_LTR501) += ltr501.o obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o +obj-$(CONFIG_STK3310) += stk3310.o obj-$(CONFIG_TCS3414) += tcs3414.o obj-$(CONFIG_TCS3472) += tcs3472.o obj-$(CONFIG_TSL4531) += tsl4531.o diff --git a/drivers/iio/light/acpi-als.c b/drivers/iio/light/acpi-als.c new file mode 100644 index 000000000000..1dafa0756bfa --- /dev/null +++ b/drivers/iio/light/acpi-als.c @@ -0,0 +1,231 @@ +/* + * 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, + }, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, +}; + +/* + * 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 EVT_NR_SOURCES ARRAY_SIZE(acpi_als_channels) +#define EVT_BUFFER_SIZE \ + (sizeof(s64) + (EVT_NR_SOURCES * sizeof(s32))) + +struct acpi_als { + struct acpi_device *device; + struct mutex lock; + + s32 evt_buffer[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 + * 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 als_read_value(struct acpi_als *als, char *prop, s32 *val) +{ + unsigned long long temp_val; + acpi_status status; + + status = acpi_evaluate_integer(als->device->handle, prop, NULL, + &temp_val); + + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Error reading ALS %s", prop)); + return -EIO; + } + + *val = temp_val; + + return 0; +} + +static void acpi_als_notify(struct acpi_device *device, u32 event) +{ + struct iio_dev *indio_dev = acpi_driver_data(device); + struct acpi_als *als = iio_priv(indio_dev); + s32 *buffer = als->evt_buffer; + s64 time_ns = iio_get_time_ns(); + s32 val; + int ret; + + mutex_lock(&als->lock); + + memset(buffer, 0, EVT_BUFFER_SIZE); + + switch (event) { + case ACPI_ALS_NOTIFY_ILLUMINANCE: + ret = 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_RAW) + return -EINVAL; + + /* we support only illumination (_ALI) so far. */ + if (chan->type != IIO_LIGHT) + return -EINVAL; + + ret = als_read_value(als, ACPI_ALS_ILLUMINANCE, &temp_val); + if (ret < 0) + return ret; + + *val = temp_val; + + return IIO_VAL_INT; +} + +static const struct iio_info acpi_als_info = { + .driver_module = THIS_MODULE, + .read_raw = acpi_als_read_raw, +}; + +static int acpi_als_add(struct acpi_device *device) +{ + struct acpi_als *als; + struct iio_dev *indio_dev; + struct iio_buffer *buffer; + + indio_dev = devm_iio_device_alloc(&device->dev, sizeof(*als)); + if (!indio_dev) + return -ENOMEM; + + als = iio_priv(indio_dev); + + device->driver_data = indio_dev; + als->device = device; + mutex_init(&als->lock); + + indio_dev->name = ACPI_ALS_DEVICE_NAME; + indio_dev->dev.parent = &device->dev; + indio_dev->info = &acpi_als_info; + indio_dev->modes = INDIO_BUFFER_SOFTWARE; + indio_dev->channels = acpi_als_channels; + indio_dev->num_channels = ARRAY_SIZE(acpi_als_channels); + + buffer = devm_iio_kfifo_allocate(&device->dev); + if (!buffer) + return -ENOMEM; + + iio_device_attach_buffer(indio_dev, buffer); + + return devm_iio_device_register(&device->dev, indio_dev); +} + +static const struct acpi_device_id acpi_als_device_ids[] = { + {"ACPI0008", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(acpi, acpi_als_device_ids); + +static struct acpi_driver acpi_als_driver = { + .name = "acpi_als", + .class = ACPI_ALS_CLASS, + .ids = acpi_als_device_ids, + .ops = { + .add = acpi_als_add, + .notify = acpi_als_notify, + }, +}; + +module_acpi_driver(acpi_als_driver); + +MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>"); +MODULE_AUTHOR("Martin Liska <marxin.liska@gmail.com>"); +MODULE_AUTHOR("Marek Vasut <marex@denx.de>"); +MODULE_DESCRIPTION("ACPI Ambient Light Sensor Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/bh1750.c b/drivers/iio/light/bh1750.c new file mode 100644 index 000000000000..564c2b3c1a83 --- /dev/null +++ b/drivers/iio/light/bh1750.c @@ -0,0 +1,334 @@ +/* + * ROHM BH1710/BH1715/BH1721/BH1750/BH1751 ambient light sensor driver + * + * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Data sheets: + * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1710fvc-e.pdf + * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1715fvc-e.pdf + * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1721fvc-e.pdf + * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1750fvi-e.pdf + * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1751fvi-e.pdf + * + * 7-bit I2C slave addresses: + * 0x23 (ADDR pin low) + * 0x5C (ADDR pin high) + * + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/module.h> + +#define BH1750_POWER_DOWN 0x00 +#define BH1750_ONE_TIME_H_RES_MODE 0x20 /* auto-mode for BH1721 */ +#define BH1750_CHANGE_INT_TIME_H_BIT 0x40 +#define BH1750_CHANGE_INT_TIME_L_BIT 0x60 + +enum { + BH1710, + BH1721, + BH1750, +}; + +struct bh1750_chip_info; +struct bh1750_data { + struct i2c_client *client; + struct mutex lock; + const struct bh1750_chip_info *chip_info; + u16 mtreg; +}; + +struct bh1750_chip_info { + u16 mtreg_min; + u16 mtreg_max; + u16 mtreg_default; + int mtreg_to_usec; + int mtreg_to_scale; + + /* + * For BH1710/BH1721 all possible integration time values won't fit + * into one page so displaying is limited to every second one. + * Note, that user can still write proper values which were not + * listed. + */ + int inc; + + u16 int_time_low_mask; + u16 int_time_high_mask; +} + +static const bh1750_chip_info_tbl[] = { + [BH1710] = { 140, 1022, 300, 400, 250000000, 2, 0x001F, 0x03E0 }, + [BH1721] = { 140, 1020, 300, 400, 250000000, 2, 0x0010, 0x03E0 }, + [BH1750] = { 31, 254, 69, 1740, 57500000, 1, 0x001F, 0x00E0 }, +}; + +static int bh1750_change_int_time(struct bh1750_data *data, int usec) +{ + int ret; + u16 val; + u8 regval; + const struct bh1750_chip_info *chip_info = data->chip_info; + + if ((usec % chip_info->mtreg_to_usec) != 0) + return -EINVAL; + + val = usec / chip_info->mtreg_to_usec; + if (val < chip_info->mtreg_min || val > chip_info->mtreg_max) + return -EINVAL; + + ret = i2c_smbus_write_byte(data->client, BH1750_POWER_DOWN); + if (ret < 0) + return ret; + + regval = (val & chip_info->int_time_high_mask) >> 5; + ret = i2c_smbus_write_byte(data->client, + BH1750_CHANGE_INT_TIME_H_BIT | regval); + if (ret < 0) + return ret; + + regval = val & chip_info->int_time_low_mask; + ret = i2c_smbus_write_byte(data->client, + BH1750_CHANGE_INT_TIME_L_BIT | regval); + if (ret < 0) + return ret; + + data->mtreg = val; + + return 0; +} + +static int bh1750_read(struct bh1750_data *data, int *val) +{ + int ret; + __be16 result; + const struct bh1750_chip_info *chip_info = data->chip_info; + unsigned long delay = chip_info->mtreg_to_usec * data->mtreg; + + /* + * BH1721 will enter continuous mode on receiving this command. + * Note, that this eliminates need for bh1750_resume(). + */ + ret = i2c_smbus_write_byte(data->client, BH1750_ONE_TIME_H_RES_MODE); + if (ret < 0) + return ret; + + usleep_range(delay + 15000, delay + 40000); + + ret = i2c_master_recv(data->client, (char *)&result, 2); + if (ret < 0) + return ret; + + *val = be16_to_cpu(result); + + return 0; +} + +static int bh1750_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret, tmp; + struct bh1750_data *data = iio_priv(indio_dev); + const struct bh1750_chip_info *chip_info = data->chip_info; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + mutex_lock(&data->lock); + ret = bh1750_read(data, val); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + tmp = chip_info->mtreg_to_scale / data->mtreg; + *val = tmp / 1000000; + *val2 = tmp % 1000000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = chip_info->mtreg_to_usec * data->mtreg; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int bh1750_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + struct bh1750_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + + mutex_lock(&data->lock); + ret = bh1750_change_int_time(data, val2); + mutex_unlock(&data->lock); + return ret; + default: + return -EINVAL; + } +} + +static ssize_t bh1750_show_int_time_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + size_t len = 0; + struct bh1750_data *data = iio_priv(dev_to_iio_dev(dev)); + const struct bh1750_chip_info *chip_info = data->chip_info; + + for (i = chip_info->mtreg_min; i <= chip_info->mtreg_max; i += chip_info->inc) + len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06d ", + chip_info->mtreg_to_usec * i); + + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEV_ATTR_INT_TIME_AVAIL(bh1750_show_int_time_available); + +static struct attribute *bh1750_attributes[] = { + &iio_dev_attr_integration_time_available.dev_attr.attr, + NULL, +}; + +static struct attribute_group bh1750_attribute_group = { + .attrs = bh1750_attributes, +}; + +static const struct iio_info bh1750_info = { + .driver_module = THIS_MODULE, + .attrs = &bh1750_attribute_group, + .read_raw = bh1750_read_raw, + .write_raw = bh1750_write_raw, +}; + +static const struct iio_chan_spec bh1750_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME) + } +}; + +static int bh1750_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret, usec; + struct bh1750_data *data; + struct iio_dev *indio_dev; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C | + I2C_FUNC_SMBUS_WRITE_BYTE)) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->chip_info = &bh1750_chip_info_tbl[id->driver_data]; + + usec = data->chip_info->mtreg_to_usec * data->chip_info->mtreg_default; + ret = bh1750_change_int_time(data, usec); + if (ret < 0) + return ret; + + mutex_init(&data->lock); + indio_dev->dev.parent = &client->dev; + indio_dev->info = &bh1750_info; + indio_dev->name = id->name; + indio_dev->channels = bh1750_channels; + indio_dev->num_channels = ARRAY_SIZE(bh1750_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + return iio_device_register(indio_dev); +} + +static int bh1750_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct bh1750_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + mutex_lock(&data->lock); + i2c_smbus_write_byte(client, BH1750_POWER_DOWN); + mutex_unlock(&data->lock); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int bh1750_suspend(struct device *dev) +{ + int ret; + struct bh1750_data *data = + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + /* + * This is mainly for BH1721 which doesn't enter power down + * mode automatically. + */ + mutex_lock(&data->lock); + ret = i2c_smbus_write_byte(data->client, BH1750_POWER_DOWN); + mutex_unlock(&data->lock); + + return ret; +} + +static SIMPLE_DEV_PM_OPS(bh1750_pm_ops, bh1750_suspend, NULL); +#define BH1750_PM_OPS (&bh1750_pm_ops) +#else +#define BH1750_PM_OPS NULL +#endif + +static const struct i2c_device_id bh1750_id[] = { + { "bh1710", BH1710 }, + { "bh1715", BH1750 }, + { "bh1721", BH1721 }, + { "bh1750", BH1750 }, + { "bh1751", BH1750 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bh1750_id); + +static struct i2c_driver bh1750_driver = { + .driver = { + .name = "bh1750", + .owner = THIS_MODULE, + .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/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c index 1609ecdd01b0..8bb1f90ecd51 100644 --- a/drivers/iio/light/hid-sensor-als.c +++ b/drivers/iio/light/hid-sensor-als.c @@ -263,7 +263,6 @@ static int hid_als_probe(struct platform_device *pdev) struct iio_dev *indio_dev; struct als_state *als_state; struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; - struct iio_chan_spec *channels; indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct als_state)); if (!indio_dev) @@ -281,20 +280,21 @@ static int hid_als_probe(struct platform_device *pdev) return ret; } - channels = kmemdup(als_channels, sizeof(als_channels), GFP_KERNEL); - if (!channels) { + indio_dev->channels = kmemdup(als_channels, + sizeof(als_channels), GFP_KERNEL); + if (!indio_dev->channels) { dev_err(&pdev->dev, "failed to duplicate channels\n"); return -ENOMEM; } - ret = als_parse_report(pdev, hsdev, channels, - HID_USAGE_SENSOR_ALS, als_state); + ret = als_parse_report(pdev, hsdev, + (struct iio_chan_spec *)indio_dev->channels, + HID_USAGE_SENSOR_ALS, als_state); if (ret) { dev_err(&pdev->dev, "failed to setup attributes\n"); goto error_free_dev_mem; } - indio_dev->channels = channels; indio_dev->num_channels = ARRAY_SIZE(als_channels); indio_dev->dev.parent = &pdev->dev; @@ -361,7 +361,7 @@ static int hid_als_remove(struct platform_device *pdev) return 0; } -static struct platform_device_id hid_als_ids[] = { +static const struct platform_device_id hid_als_ids[] = { { /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ .name = "HID-SENSOR-200041", diff --git a/drivers/iio/light/hid-sensor-prox.c b/drivers/iio/light/hid-sensor-prox.c index ef60bae738e3..0d248476f4c9 100644 --- a/drivers/iio/light/hid-sensor-prox.c +++ b/drivers/iio/light/hid-sensor-prox.c @@ -350,7 +350,7 @@ static int hid_prox_remove(struct platform_device *pdev) return 0; } -static struct platform_device_id hid_prox_ids[] = { +static const struct platform_device_id hid_prox_ids[] = { { /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ .name = "HID-SENSOR-200011", diff --git a/drivers/iio/light/ltr501.c b/drivers/iio/light/ltr501.c index 78b87839c4b9..1ef7d3773ab9 100644 --- a/drivers/iio/light/ltr501.c +++ b/drivers/iio/light/ltr501.c @@ -9,15 +9,18 @@ * * 7-bit I2C slave address 0x23 * - * TODO: interrupt, threshold, measurement rate, IR LED characteristics + * TODO: IR LED characteristics */ #include <linux/module.h> #include <linux/i2c.h> #include <linux/err.h> #include <linux/delay.h> +#include <linux/regmap.h> +#include <linux/acpi.h> #include <linux/iio/iio.h> +#include <linux/iio/events.h> #include <linux/iio/sysfs.h> #include <linux/iio/trigger_consumer.h> #include <linux/iio/buffer.h> @@ -27,12 +30,21 @@ #define LTR501_ALS_CONTR 0x80 /* ALS operation mode, SW reset */ #define LTR501_PS_CONTR 0x81 /* PS operation mode */ +#define LTR501_PS_MEAS_RATE 0x84 /* measurement rate*/ +#define LTR501_ALS_MEAS_RATE 0x85 /* ALS integ time, measurement rate*/ #define LTR501_PART_ID 0x86 #define LTR501_MANUFAC_ID 0x87 #define LTR501_ALS_DATA1 0x88 /* 16-bit, little endian */ #define LTR501_ALS_DATA0 0x8a /* 16-bit, little endian */ #define LTR501_ALS_PS_STATUS 0x8c #define LTR501_PS_DATA 0x8d /* 16-bit, little endian */ +#define LTR501_INTR 0x8f /* output mode, polarity, mode */ +#define LTR501_PS_THRESH_UP 0x90 /* 11 bit, ps upper threshold */ +#define LTR501_PS_THRESH_LOW 0x92 /* 11 bit, ps lower threshold */ +#define LTR501_ALS_THRESH_UP 0x97 /* 16 bit, ALS upper threshold */ +#define LTR501_ALS_THRESH_LOW 0x99 /* 16 bit, ALS lower threshold */ +#define LTR501_INTR_PRST 0x9e /* ps thresh, als thresh */ +#define LTR501_MAX_REG 0x9f #define LTR501_ALS_CONTR_SW_RESET BIT(2) #define LTR501_CONTR_PS_GAIN_MASK (BIT(3) | BIT(2)) @@ -40,28 +52,288 @@ #define LTR501_CONTR_ALS_GAIN_MASK BIT(3) #define LTR501_CONTR_ACTIVE BIT(1) +#define LTR501_STATUS_ALS_INTR BIT(3) #define LTR501_STATUS_ALS_RDY BIT(2) +#define LTR501_STATUS_PS_INTR BIT(1) #define LTR501_STATUS_PS_RDY BIT(0) #define LTR501_PS_DATA_MASK 0x7ff +#define LTR501_PS_THRESH_MASK 0x7ff +#define LTR501_ALS_THRESH_MASK 0xffff + +#define LTR501_ALS_DEF_PERIOD 500000 +#define LTR501_PS_DEF_PERIOD 100000 + +#define LTR501_REGMAP_NAME "ltr501_regmap" + +#define LTR501_LUX_CONV(vis_coeff, vis_data, ir_coeff, ir_data) \ + ((vis_coeff * vis_data) - (ir_coeff * ir_data)) + +static const int int_time_mapping[] = {100000, 50000, 200000, 400000}; + +static const struct reg_field reg_field_it = + REG_FIELD(LTR501_ALS_MEAS_RATE, 3, 4); +static const struct reg_field reg_field_als_intr = + REG_FIELD(LTR501_INTR, 0, 0); +static const struct reg_field reg_field_ps_intr = + REG_FIELD(LTR501_INTR, 1, 1); +static const struct reg_field reg_field_als_rate = + REG_FIELD(LTR501_ALS_MEAS_RATE, 0, 2); +static const struct reg_field reg_field_ps_rate = + REG_FIELD(LTR501_PS_MEAS_RATE, 0, 3); +static const struct reg_field reg_field_als_prst = + REG_FIELD(LTR501_INTR_PRST, 0, 3); +static const struct reg_field reg_field_ps_prst = + REG_FIELD(LTR501_INTR_PRST, 4, 7); + +struct ltr501_samp_table { + int freq_val; /* repetition frequency in micro HZ*/ + int time_val; /* repetition rate in micro seconds */ +}; + +#define LTR501_RESERVED_GAIN -1 + +enum { + ltr501 = 0, + ltr559, + ltr301, +}; + +struct ltr501_gain { + int scale; + int uscale; +}; + +static struct ltr501_gain ltr501_als_gain_tbl[] = { + {1, 0}, + {0, 5000}, +}; + +static struct ltr501_gain ltr559_als_gain_tbl[] = { + {1, 0}, + {0, 500000}, + {0, 250000}, + {0, 125000}, + {LTR501_RESERVED_GAIN, LTR501_RESERVED_GAIN}, + {LTR501_RESERVED_GAIN, LTR501_RESERVED_GAIN}, + {0, 20000}, + {0, 10000}, +}; + +static struct ltr501_gain ltr501_ps_gain_tbl[] = { + {1, 0}, + {0, 250000}, + {0, 125000}, + {0, 62500}, +}; + +static struct ltr501_gain ltr559_ps_gain_tbl[] = { + {0, 62500}, /* x16 gain */ + {0, 31250}, /* x32 gain */ + {0, 15625}, /* bits X1 are for x64 gain */ + {0, 15624}, +}; + +struct ltr501_chip_info { + u8 partid; + struct ltr501_gain *als_gain; + int als_gain_tbl_size; + struct ltr501_gain *ps_gain; + int ps_gain_tbl_size; + u8 als_mode_active; + u8 als_gain_mask; + u8 als_gain_shift; + struct iio_chan_spec const *channels; + const int no_channels; + const struct iio_info *info; + const struct iio_info *info_no_irq; +}; struct ltr501_data { struct i2c_client *client; struct mutex lock_als, lock_ps; + struct ltr501_chip_info *chip_info; u8 als_contr, ps_contr; + int als_period, ps_period; /* period in micro seconds */ + struct regmap *regmap; + struct regmap_field *reg_it; + struct regmap_field *reg_als_intr; + struct regmap_field *reg_ps_intr; + struct regmap_field *reg_als_rate; + struct regmap_field *reg_ps_rate; + struct regmap_field *reg_als_prst; + struct regmap_field *reg_ps_prst; +}; + +static const struct ltr501_samp_table ltr501_als_samp_table[] = { + {20000000, 50000}, {10000000, 100000}, + {5000000, 200000}, {2000000, 500000}, + {1000000, 1000000}, {500000, 2000000}, + {500000, 2000000}, {500000, 2000000} }; +static const struct ltr501_samp_table ltr501_ps_samp_table[] = { + {20000000, 50000}, {14285714, 70000}, + {10000000, 100000}, {5000000, 200000}, + {2000000, 500000}, {1000000, 1000000}, + {500000, 2000000}, {500000, 2000000}, + {500000, 2000000} +}; + +static unsigned int ltr501_match_samp_freq(const struct ltr501_samp_table *tab, + int len, int val, int val2) +{ + int i, freq; + + freq = val * 1000000 + val2; + + for (i = 0; i < len; i++) { + if (tab[i].freq_val == freq) + return i; + } + + return -EINVAL; +} + +static int ltr501_als_read_samp_freq(struct ltr501_data *data, + int *val, int *val2) +{ + int ret, i; + + ret = regmap_field_read(data->reg_als_rate, &i); + if (ret < 0) + return ret; + + if (i < 0 || i >= ARRAY_SIZE(ltr501_als_samp_table)) + return -EINVAL; + + *val = ltr501_als_samp_table[i].freq_val / 1000000; + *val2 = ltr501_als_samp_table[i].freq_val % 1000000; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int ltr501_ps_read_samp_freq(struct ltr501_data *data, + int *val, int *val2) +{ + int ret, i; + + ret = regmap_field_read(data->reg_ps_rate, &i); + if (ret < 0) + return ret; + + if (i < 0 || i >= ARRAY_SIZE(ltr501_ps_samp_table)) + return -EINVAL; + + *val = ltr501_ps_samp_table[i].freq_val / 1000000; + *val2 = ltr501_ps_samp_table[i].freq_val % 1000000; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int ltr501_als_write_samp_freq(struct ltr501_data *data, + int val, int val2) +{ + int i, ret; + + i = ltr501_match_samp_freq(ltr501_als_samp_table, + ARRAY_SIZE(ltr501_als_samp_table), + val, val2); + + if (i < 0) + return i; + + mutex_lock(&data->lock_als); + ret = regmap_field_write(data->reg_als_rate, i); + mutex_unlock(&data->lock_als); + + return ret; +} + +static int ltr501_ps_write_samp_freq(struct ltr501_data *data, + int val, int val2) +{ + int i, ret; + + i = ltr501_match_samp_freq(ltr501_ps_samp_table, + ARRAY_SIZE(ltr501_ps_samp_table), + val, val2); + + if (i < 0) + return i; + + mutex_lock(&data->lock_ps); + ret = regmap_field_write(data->reg_ps_rate, i); + mutex_unlock(&data->lock_ps); + + return ret; +} + +static int ltr501_als_read_samp_period(struct ltr501_data *data, int *val) +{ + int ret, i; + + ret = regmap_field_read(data->reg_als_rate, &i); + if (ret < 0) + return ret; + + if (i < 0 || i >= ARRAY_SIZE(ltr501_als_samp_table)) + return -EINVAL; + + *val = ltr501_als_samp_table[i].time_val; + + return IIO_VAL_INT; +} + +static int ltr501_ps_read_samp_period(struct ltr501_data *data, int *val) +{ + int ret, i; + + ret = regmap_field_read(data->reg_ps_rate, &i); + if (ret < 0) + return ret; + + if (i < 0 || i >= ARRAY_SIZE(ltr501_ps_samp_table)) + return -EINVAL; + + *val = ltr501_ps_samp_table[i].time_val; + + return IIO_VAL_INT; +} + +/* IR and visible spectrum coeff's are given in data sheet */ +static unsigned long ltr501_calculate_lux(u16 vis_data, u16 ir_data) +{ + unsigned long ratio, lux; + + if (vis_data == 0) + return 0; + + /* multiply numerator by 100 to avoid handling ratio < 1 */ + ratio = DIV_ROUND_UP(ir_data * 100, ir_data + vis_data); + + if (ratio < 45) + lux = LTR501_LUX_CONV(1774, vis_data, -1105, ir_data); + else if (ratio >= 45 && ratio < 64) + lux = LTR501_LUX_CONV(3772, vis_data, 1336, ir_data); + else if (ratio >= 64 && ratio < 85) + lux = LTR501_LUX_CONV(1690, vis_data, 169, ir_data); + else + lux = 0; + + return lux / 1000; +} + static int ltr501_drdy(struct ltr501_data *data, u8 drdy_mask) { int tries = 100; - int ret; + int ret, status; while (tries--) { - ret = i2c_smbus_read_byte_data(data->client, - LTR501_ALS_PS_STATUS); + ret = regmap_read(data->regmap, LTR501_ALS_PS_STATUS, &status); if (ret < 0) return ret; - if ((ret & drdy_mask) == drdy_mask) + if ((status & drdy_mask) == drdy_mask) return 0; msleep(25); } @@ -70,25 +342,221 @@ static int ltr501_drdy(struct ltr501_data *data, u8 drdy_mask) return -EIO; } +static int ltr501_set_it_time(struct ltr501_data *data, int it) +{ + int ret, i, index = -1, status; + + for (i = 0; i < ARRAY_SIZE(int_time_mapping); i++) { + if (int_time_mapping[i] == it) { + index = i; + break; + } + } + /* Make sure integ time index is valid */ + if (index < 0) + return -EINVAL; + + ret = regmap_read(data->regmap, LTR501_ALS_CONTR, &status); + if (ret < 0) + return ret; + + if (status & LTR501_CONTR_ALS_GAIN_MASK) { + /* + * 200 ms and 400 ms integ time can only be + * used in dynamic range 1 + */ + if (index > 1) + return -EINVAL; + } else + /* 50 ms integ time can only be used in dynamic range 2 */ + if (index == 1) + return -EINVAL; + + return regmap_field_write(data->reg_it, index); +} + +/* read int time in micro seconds */ +static int ltr501_read_it_time(struct ltr501_data *data, int *val, int *val2) +{ + int ret, index; + + ret = regmap_field_read(data->reg_it, &index); + if (ret < 0) + return ret; + + /* Make sure integ time index is valid */ + if (index < 0 || index >= ARRAY_SIZE(int_time_mapping)) + return -EINVAL; + + *val2 = int_time_mapping[index]; + *val = 0; + + return IIO_VAL_INT_PLUS_MICRO; +} + static int ltr501_read_als(struct ltr501_data *data, __le16 buf[2]) { - int ret = ltr501_drdy(data, LTR501_STATUS_ALS_RDY); + int ret; + + ret = ltr501_drdy(data, LTR501_STATUS_ALS_RDY); if (ret < 0) return ret; /* always read both ALS channels in given order */ - return i2c_smbus_read_i2c_block_data(data->client, - LTR501_ALS_DATA1, 2 * sizeof(__le16), (u8 *) buf); + return regmap_bulk_read(data->regmap, LTR501_ALS_DATA1, + buf, 2 * sizeof(__le16)); } static int ltr501_read_ps(struct ltr501_data *data) { - int ret = ltr501_drdy(data, LTR501_STATUS_PS_RDY); + int ret, status; + + ret = ltr501_drdy(data, LTR501_STATUS_PS_RDY); if (ret < 0) return ret; - return i2c_smbus_read_word_data(data->client, LTR501_PS_DATA); + + ret = regmap_bulk_read(data->regmap, LTR501_PS_DATA, + &status, 2); + if (ret < 0) + return ret; + + return status; +} + +static int ltr501_read_intr_prst(struct ltr501_data *data, + enum iio_chan_type type, + int *val2) +{ + int ret, samp_period, prst; + + switch (type) { + case IIO_INTENSITY: + ret = regmap_field_read(data->reg_als_prst, &prst); + if (ret < 0) + return ret; + + ret = ltr501_als_read_samp_period(data, &samp_period); + + if (ret < 0) + return ret; + *val2 = samp_period * prst; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_PROXIMITY: + ret = regmap_field_read(data->reg_ps_prst, &prst); + if (ret < 0) + return ret; + + ret = ltr501_ps_read_samp_period(data, &samp_period); + + if (ret < 0) + return ret; + + *val2 = samp_period * prst; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + + return -EINVAL; } -#define LTR501_INTENSITY_CHANNEL(_idx, _addr, _mod, _shared) { \ +static int ltr501_write_intr_prst(struct ltr501_data *data, + enum iio_chan_type type, + int val, int val2) +{ + int ret, samp_period, new_val; + unsigned long period; + + if (val < 0 || val2 < 0) + return -EINVAL; + + /* period in microseconds */ + period = ((val * 1000000) + val2); + + switch (type) { + case IIO_INTENSITY: + ret = ltr501_als_read_samp_period(data, &samp_period); + if (ret < 0) + return ret; + + /* period should be atleast equal to sampling period */ + if (period < samp_period) + return -EINVAL; + + new_val = DIV_ROUND_UP(period, samp_period); + if (new_val < 0 || new_val > 0x0f) + return -EINVAL; + + mutex_lock(&data->lock_als); + ret = regmap_field_write(data->reg_als_prst, new_val); + mutex_unlock(&data->lock_als); + if (ret >= 0) + data->als_period = period; + + return ret; + case IIO_PROXIMITY: + ret = ltr501_ps_read_samp_period(data, &samp_period); + if (ret < 0) + return ret; + + /* period should be atleast equal to rate */ + if (period < samp_period) + return -EINVAL; + + new_val = DIV_ROUND_UP(period, samp_period); + if (new_val < 0 || new_val > 0x0f) + return -EINVAL; + + mutex_lock(&data->lock_ps); + ret = regmap_field_write(data->reg_ps_prst, new_val); + mutex_unlock(&data->lock_ps); + if (ret >= 0) + data->ps_period = period; + + return ret; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static const struct iio_event_spec ltr501_als_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD), + }, + +}; + +static const struct iio_event_spec ltr501_pxs_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD), + }, +}; + +#define LTR501_INTENSITY_CHANNEL(_idx, _addr, _mod, _shared, \ + _evspec, _evsize) { \ .type = IIO_INTENSITY, \ .modified = 1, \ .address = (_addr), \ @@ -101,13 +569,27 @@ static int ltr501_read_ps(struct ltr501_data *data) .realbits = 16, \ .storagebits = 16, \ .endianness = IIO_CPU, \ - } \ + }, \ + .event_spec = _evspec,\ + .num_event_specs = _evsize,\ +} + +#define LTR501_LIGHT_CHANNEL() { \ + .type = IIO_LIGHT, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .scan_index = -1, \ } static const struct iio_chan_spec ltr501_channels[] = { - LTR501_INTENSITY_CHANNEL(0, LTR501_ALS_DATA0, IIO_MOD_LIGHT_BOTH, 0), + LTR501_LIGHT_CHANNEL(), + LTR501_INTENSITY_CHANNEL(0, LTR501_ALS_DATA0, IIO_MOD_LIGHT_BOTH, 0, + ltr501_als_event_spec, + ARRAY_SIZE(ltr501_als_event_spec)), LTR501_INTENSITY_CHANNEL(1, LTR501_ALS_DATA1, IIO_MOD_LIGHT_IR, - BIT(IIO_CHAN_INFO_SCALE)), + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + NULL, 0), { .type = IIO_PROXIMITY, .address = LTR501_PS_DATA, @@ -120,23 +602,51 @@ static const struct iio_chan_spec ltr501_channels[] = { .storagebits = 16, .endianness = IIO_CPU, }, + .event_spec = ltr501_pxs_event_spec, + .num_event_specs = ARRAY_SIZE(ltr501_pxs_event_spec), }, IIO_CHAN_SOFT_TIMESTAMP(3), }; -static const int ltr501_ps_gain[4][2] = { - {1, 0}, {0, 250000}, {0, 125000}, {0, 62500} +static const struct iio_chan_spec ltr301_channels[] = { + LTR501_LIGHT_CHANNEL(), + LTR501_INTENSITY_CHANNEL(0, LTR501_ALS_DATA0, IIO_MOD_LIGHT_BOTH, 0, + ltr501_als_event_spec, + ARRAY_SIZE(ltr501_als_event_spec)), + LTR501_INTENSITY_CHANNEL(1, LTR501_ALS_DATA1, IIO_MOD_LIGHT_IR, + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + NULL, 0), + IIO_CHAN_SOFT_TIMESTAMP(2), }; static int ltr501_read_raw(struct iio_dev *indio_dev, - struct iio_chan_spec const *chan, - int *val, int *val2, long mask) + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) { struct ltr501_data *data = iio_priv(indio_dev); __le16 buf[2]; int ret, i; switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + switch (chan->type) { + case IIO_LIGHT: + mutex_lock(&data->lock_als); + ret = ltr501_read_als(data, buf); + mutex_unlock(&data->lock_als); + if (ret < 0) + return ret; + *val = ltr501_calculate_lux(le16_to_cpu(buf[1]), + le16_to_cpu(buf[0])); + return IIO_VAL_INT; + default: + return -EINVAL; + } case IIO_CHAN_INFO_RAW: if (iio_buffer_enabled(indio_dev)) return -EBUSY; @@ -149,7 +659,7 @@ static int ltr501_read_raw(struct iio_dev *indio_dev, if (ret < 0) return ret; *val = le16_to_cpu(chan->address == LTR501_ALS_DATA1 ? - buf[0] : buf[1]); + buf[0] : buf[1]); return IIO_VAL_INT; case IIO_PROXIMITY: mutex_lock(&data->lock_ps); @@ -165,45 +675,59 @@ static int ltr501_read_raw(struct iio_dev *indio_dev, case IIO_CHAN_INFO_SCALE: switch (chan->type) { case IIO_INTENSITY: - if (data->als_contr & LTR501_CONTR_ALS_GAIN_MASK) { - *val = 0; - *val2 = 5000; - return IIO_VAL_INT_PLUS_MICRO; - } else { - *val = 1; - *val2 = 0; - return IIO_VAL_INT; - } + i = (data->als_contr & data->chip_info->als_gain_mask) + >> data->chip_info->als_gain_shift; + *val = data->chip_info->als_gain[i].scale; + *val2 = data->chip_info->als_gain[i].uscale; + return IIO_VAL_INT_PLUS_MICRO; case IIO_PROXIMITY: i = (data->ps_contr & LTR501_CONTR_PS_GAIN_MASK) >> LTR501_CONTR_PS_GAIN_SHIFT; - *val = ltr501_ps_gain[i][0]; - *val2 = ltr501_ps_gain[i][1]; + *val = data->chip_info->ps_gain[i].scale; + *val2 = data->chip_info->ps_gain[i].uscale; return IIO_VAL_INT_PLUS_MICRO; default: return -EINVAL; } + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_INTENSITY: + return ltr501_read_it_time(data, val, val2); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_INTENSITY: + return ltr501_als_read_samp_freq(data, val, val2); + case IIO_PROXIMITY: + return ltr501_ps_read_samp_freq(data, val, val2); + default: + return -EINVAL; + } } return -EINVAL; } -static int ltr501_get_ps_gain_index(int val, int val2) +static int ltr501_get_gain_index(struct ltr501_gain *gain, int size, + int val, int val2) { int i; - for (i = 0; i < ARRAY_SIZE(ltr501_ps_gain); i++) - if (val == ltr501_ps_gain[i][0] && val2 == ltr501_ps_gain[i][1]) + for (i = 0; i < size; i++) + if (val == gain[i].scale && val2 == gain[i].uscale) return i; return -1; } static int ltr501_write_raw(struct iio_dev *indio_dev, - struct iio_chan_spec const *chan, - int val, int val2, long mask) + struct iio_chan_spec const *chan, + int val, int val2, long mask) { struct ltr501_data *data = iio_priv(indio_dev); - int i; + int i, ret, freq_val, freq_val2; + struct ltr501_chip_info *info = data->chip_info; if (iio_buffer_enabled(indio_dev)) return -EBUSY; @@ -212,35 +736,382 @@ static int ltr501_write_raw(struct iio_dev *indio_dev, case IIO_CHAN_INFO_SCALE: switch (chan->type) { case IIO_INTENSITY: - if (val == 0 && val2 == 5000) - data->als_contr |= LTR501_CONTR_ALS_GAIN_MASK; - else if (val == 1 && val2 == 0) - data->als_contr &= ~LTR501_CONTR_ALS_GAIN_MASK; - else + i = ltr501_get_gain_index(info->als_gain, + info->als_gain_tbl_size, + val, val2); + if (i < 0) return -EINVAL; - return i2c_smbus_write_byte_data(data->client, - LTR501_ALS_CONTR, data->als_contr); + + data->als_contr &= ~info->als_gain_mask; + data->als_contr |= i << info->als_gain_shift; + + return regmap_write(data->regmap, LTR501_ALS_CONTR, + data->als_contr); case IIO_PROXIMITY: - i = ltr501_get_ps_gain_index(val, val2); + i = ltr501_get_gain_index(info->ps_gain, + info->ps_gain_tbl_size, + val, val2); if (i < 0) return -EINVAL; data->ps_contr &= ~LTR501_CONTR_PS_GAIN_MASK; data->ps_contr |= i << LTR501_CONTR_PS_GAIN_SHIFT; - return i2c_smbus_write_byte_data(data->client, - LTR501_PS_CONTR, data->ps_contr); + + return regmap_write(data->regmap, LTR501_PS_CONTR, + data->ps_contr); default: return -EINVAL; } + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_INTENSITY: + if (val != 0) + return -EINVAL; + mutex_lock(&data->lock_als); + i = ltr501_set_it_time(data, val2); + mutex_unlock(&data->lock_als); + return i; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_INTENSITY: + ret = ltr501_als_read_samp_freq(data, &freq_val, + &freq_val2); + if (ret < 0) + return ret; + + ret = ltr501_als_write_samp_freq(data, val, val2); + if (ret < 0) + return ret; + + /* update persistence count when changing frequency */ + ret = ltr501_write_intr_prst(data, chan->type, + 0, data->als_period); + + if (ret < 0) + return ltr501_als_write_samp_freq(data, + freq_val, + freq_val2); + return ret; + case IIO_PROXIMITY: + ret = ltr501_ps_read_samp_freq(data, &freq_val, + &freq_val2); + if (ret < 0) + return ret; + + ret = ltr501_ps_write_samp_freq(data, val, val2); + if (ret < 0) + return ret; + + /* update persistence count when changing frequency */ + ret = ltr501_write_intr_prst(data, chan->type, + 0, data->ps_period); + + if (ret < 0) + return ltr501_ps_write_samp_freq(data, + freq_val, + freq_val2); + return ret; + default: + return -EINVAL; + } + } + return -EINVAL; +} + +static int ltr501_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct ltr501_data *data = iio_priv(indio_dev); + int ret, thresh_data; + + switch (chan->type) { + case IIO_INTENSITY: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = regmap_bulk_read(data->regmap, + LTR501_ALS_THRESH_UP, + &thresh_data, 2); + if (ret < 0) + return ret; + *val = thresh_data & LTR501_ALS_THRESH_MASK; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + ret = regmap_bulk_read(data->regmap, + LTR501_ALS_THRESH_LOW, + &thresh_data, 2); + if (ret < 0) + return ret; + *val = thresh_data & LTR501_ALS_THRESH_MASK; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_PROXIMITY: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = regmap_bulk_read(data->regmap, + LTR501_PS_THRESH_UP, + &thresh_data, 2); + if (ret < 0) + return ret; + *val = thresh_data & LTR501_PS_THRESH_MASK; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + ret = regmap_bulk_read(data->regmap, + LTR501_PS_THRESH_LOW, + &thresh_data, 2); + if (ret < 0) + return ret; + *val = thresh_data & LTR501_PS_THRESH_MASK; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; } + return -EINVAL; } -static IIO_CONST_ATTR(in_proximity_scale_available, "1 0.25 0.125 0.0625"); -static IIO_CONST_ATTR(in_intensity_scale_available, "1 0.005"); +static int ltr501_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct ltr501_data *data = iio_priv(indio_dev); + int ret; + + if (val < 0) + return -EINVAL; + + switch (chan->type) { + case IIO_INTENSITY: + if (val > LTR501_ALS_THRESH_MASK) + return -EINVAL; + switch (dir) { + case IIO_EV_DIR_RISING: + mutex_lock(&data->lock_als); + ret = regmap_bulk_write(data->regmap, + LTR501_ALS_THRESH_UP, + &val, 2); + mutex_unlock(&data->lock_als); + return ret; + case IIO_EV_DIR_FALLING: + mutex_lock(&data->lock_als); + ret = regmap_bulk_write(data->regmap, + LTR501_ALS_THRESH_LOW, + &val, 2); + mutex_unlock(&data->lock_als); + return ret; + default: + return -EINVAL; + } + case IIO_PROXIMITY: + if (val > LTR501_PS_THRESH_MASK) + return -EINVAL; + switch (dir) { + case IIO_EV_DIR_RISING: + mutex_lock(&data->lock_ps); + ret = regmap_bulk_write(data->regmap, + LTR501_PS_THRESH_UP, + &val, 2); + mutex_unlock(&data->lock_ps); + return ret; + case IIO_EV_DIR_FALLING: + mutex_lock(&data->lock_ps); + ret = regmap_bulk_write(data->regmap, + LTR501_PS_THRESH_LOW, + &val, 2); + mutex_unlock(&data->lock_ps); + return ret; + default: + return -EINVAL; + } + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int ltr501_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + return ltr501_read_thresh(indio_dev, chan, type, dir, + info, val, val2); + case IIO_EV_INFO_PERIOD: + ret = ltr501_read_intr_prst(iio_priv(indio_dev), + chan->type, val2); + *val = *val2 / 1000000; + *val2 = *val2 % 1000000; + return ret; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int ltr501_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + if (val2 != 0) + return -EINVAL; + return ltr501_write_thresh(indio_dev, chan, type, dir, + info, val, val2); + case IIO_EV_INFO_PERIOD: + return ltr501_write_intr_prst(iio_priv(indio_dev), chan->type, + val, val2); + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int ltr501_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ltr501_data *data = iio_priv(indio_dev); + int ret, status; + + switch (chan->type) { + case IIO_INTENSITY: + ret = regmap_field_read(data->reg_als_intr, &status); + if (ret < 0) + return ret; + return status; + case IIO_PROXIMITY: + ret = regmap_field_read(data->reg_ps_intr, &status); + if (ret < 0) + return ret; + return status; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int ltr501_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, int state) +{ + struct ltr501_data *data = iio_priv(indio_dev); + int ret; + + /* only 1 and 0 are valid inputs */ + if (state != 1 && state != 0) + return -EINVAL; + + switch (chan->type) { + case IIO_INTENSITY: + mutex_lock(&data->lock_als); + ret = regmap_field_write(data->reg_als_intr, state); + mutex_unlock(&data->lock_als); + return ret; + case IIO_PROXIMITY: + mutex_lock(&data->lock_ps); + ret = regmap_field_write(data->reg_ps_intr, state); + mutex_unlock(&data->lock_ps); + return ret; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static ssize_t ltr501_show_proximity_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ltr501_data *data = iio_priv(dev_to_iio_dev(dev)); + struct ltr501_chip_info *info = data->chip_info; + ssize_t len = 0; + int i; + + for (i = 0; i < info->ps_gain_tbl_size; i++) { + if (info->ps_gain[i].scale == LTR501_RESERVED_GAIN) + continue; + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ", + info->ps_gain[i].scale, + info->ps_gain[i].uscale); + } + + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t ltr501_show_intensity_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ltr501_data *data = iio_priv(dev_to_iio_dev(dev)); + struct ltr501_chip_info *info = data->chip_info; + ssize_t len = 0; + int i; + + for (i = 0; i < info->als_gain_tbl_size; i++) { + if (info->als_gain[i].scale == LTR501_RESERVED_GAIN) + continue; + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ", + info->als_gain[i].scale, + info->als_gain[i].uscale); + } + + buf[len - 1] = '\n'; + + return len; +} + +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.05 0.1 0.2 0.4"); +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("20 10 5 2 1 0.5"); + +static IIO_DEVICE_ATTR(in_proximity_scale_available, S_IRUGO, + ltr501_show_proximity_scale_avail, NULL, 0); +static IIO_DEVICE_ATTR(in_intensity_scale_available, S_IRUGO, + ltr501_show_intensity_scale_avail, NULL, 0); static struct attribute *ltr501_attributes[] = { - &iio_const_attr_in_proximity_scale_available.dev_attr.attr, - &iio_const_attr_in_intensity_scale_available.dev_attr.attr, + &iio_dev_attr_in_proximity_scale_available.dev_attr.attr, + &iio_dev_attr_in_intensity_scale_available.dev_attr.attr, + &iio_const_attr_integration_time_available.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static struct attribute *ltr301_attributes[] = { + &iio_dev_attr_in_intensity_scale_available.dev_attr.attr, + &iio_const_attr_integration_time_available.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, NULL }; @@ -248,20 +1119,98 @@ static const struct attribute_group ltr501_attribute_group = { .attrs = ltr501_attributes, }; +static const struct attribute_group ltr301_attribute_group = { + .attrs = ltr301_attributes, +}; + +static const struct iio_info ltr501_info_no_irq = { + .read_raw = ltr501_read_raw, + .write_raw = ltr501_write_raw, + .attrs = <r501_attribute_group, + .driver_module = THIS_MODULE, +}; + static const struct iio_info ltr501_info = { .read_raw = ltr501_read_raw, .write_raw = ltr501_write_raw, .attrs = <r501_attribute_group, + .read_event_value = <r501_read_event, + .write_event_value = <r501_write_event, + .read_event_config = <r501_read_event_config, + .write_event_config = <r501_write_event_config, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info ltr301_info_no_irq = { + .read_raw = ltr501_read_raw, + .write_raw = ltr501_write_raw, + .attrs = <r301_attribute_group, .driver_module = THIS_MODULE, }; -static int ltr501_write_contr(struct i2c_client *client, u8 als_val, u8 ps_val) +static const struct iio_info ltr301_info = { + .read_raw = ltr501_read_raw, + .write_raw = ltr501_write_raw, + .attrs = <r301_attribute_group, + .read_event_value = <r501_read_event, + .write_event_value = <r501_write_event, + .read_event_config = <r501_read_event_config, + .write_event_config = <r501_write_event_config, + .driver_module = THIS_MODULE, +}; + +static struct ltr501_chip_info ltr501_chip_info_tbl[] = { + [ltr501] = { + .partid = 0x08, + .als_gain = ltr501_als_gain_tbl, + .als_gain_tbl_size = ARRAY_SIZE(ltr501_als_gain_tbl), + .ps_gain = ltr501_ps_gain_tbl, + .ps_gain_tbl_size = ARRAY_SIZE(ltr501_ps_gain_tbl), + .als_mode_active = BIT(0) | BIT(1), + .als_gain_mask = BIT(3), + .als_gain_shift = 3, + .info = <r501_info, + .info_no_irq = <r501_info_no_irq, + .channels = ltr501_channels, + .no_channels = ARRAY_SIZE(ltr501_channels), + }, + [ltr559] = { + .partid = 0x09, + .als_gain = ltr559_als_gain_tbl, + .als_gain_tbl_size = ARRAY_SIZE(ltr559_als_gain_tbl), + .ps_gain = ltr559_ps_gain_tbl, + .ps_gain_tbl_size = ARRAY_SIZE(ltr559_ps_gain_tbl), + .als_mode_active = BIT(1), + .als_gain_mask = BIT(2) | BIT(3) | BIT(4), + .als_gain_shift = 2, + .info = <r501_info, + .info_no_irq = <r501_info_no_irq, + .channels = ltr501_channels, + .no_channels = ARRAY_SIZE(ltr501_channels), + }, + [ltr301] = { + .partid = 0x08, + .als_gain = ltr501_als_gain_tbl, + .als_gain_tbl_size = ARRAY_SIZE(ltr501_als_gain_tbl), + .als_mode_active = BIT(0) | BIT(1), + .als_gain_mask = BIT(3), + .als_gain_shift = 3, + .info = <r301_info, + .info_no_irq = <r301_info_no_irq, + .channels = ltr301_channels, + .no_channels = ARRAY_SIZE(ltr301_channels), + }, +}; + +static int ltr501_write_contr(struct ltr501_data *data, u8 als_val, u8 ps_val) { - int ret = i2c_smbus_write_byte_data(client, LTR501_ALS_CONTR, als_val); + int ret; + + ret = regmap_write(data->regmap, LTR501_ALS_CONTR, als_val); if (ret < 0) return ret; - return i2c_smbus_write_byte_data(client, LTR501_PS_CONTR, ps_val); + return regmap_write(data->regmap, LTR501_PS_CONTR, ps_val); } static irqreturn_t ltr501_trigger_handler(int irq, void *p) @@ -273,13 +1222,13 @@ static irqreturn_t ltr501_trigger_handler(int irq, void *p) __le16 als_buf[2]; u8 mask = 0; int j = 0; - int ret; + int ret, psdata; memset(buf, 0, sizeof(buf)); /* figure out which data needs to be ready */ if (test_bit(0, indio_dev->active_scan_mask) || - test_bit(1, indio_dev->active_scan_mask)) + test_bit(1, indio_dev->active_scan_mask)) mask |= LTR501_STATUS_ALS_RDY; if (test_bit(2, indio_dev->active_scan_mask)) mask |= LTR501_STATUS_PS_RDY; @@ -289,8 +1238,8 @@ static irqreturn_t ltr501_trigger_handler(int irq, void *p) goto done; if (mask & LTR501_STATUS_ALS_RDY) { - ret = i2c_smbus_read_i2c_block_data(data->client, - LTR501_ALS_DATA1, sizeof(als_buf), (u8 *) als_buf); + ret = regmap_bulk_read(data->regmap, LTR501_ALS_DATA1, + (u8 *)als_buf, sizeof(als_buf)); if (ret < 0) return ret; if (test_bit(0, indio_dev->active_scan_mask)) @@ -300,14 +1249,14 @@ static irqreturn_t ltr501_trigger_handler(int irq, void *p) } if (mask & LTR501_STATUS_PS_RDY) { - ret = i2c_smbus_read_word_data(data->client, LTR501_PS_DATA); + ret = regmap_bulk_read(data->regmap, LTR501_PS_DATA, + &psdata, 2); if (ret < 0) goto done; - buf[j++] = ret & LTR501_PS_DATA_MASK; + buf[j++] = psdata & LTR501_PS_DATA_MASK; } - iio_push_to_buffers_with_timestamp(indio_dev, buf, - iio_get_time_ns()); + iio_push_to_buffers_with_timestamp(indio_dev, buf, iio_get_time_ns()); done: iio_trigger_notify_done(indio_dev->trig); @@ -315,67 +1264,225 @@ done: return IRQ_HANDLED; } +static irqreturn_t ltr501_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ltr501_data *data = iio_priv(indio_dev); + int ret, status; + + ret = regmap_read(data->regmap, LTR501_ALS_PS_STATUS, &status); + if (ret < 0) { + dev_err(&data->client->dev, + "irq read int reg failed\n"); + return IRQ_HANDLED; + } + + if (status & LTR501_STATUS_ALS_INTR) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns()); + + if (status & LTR501_STATUS_PS_INTR) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns()); + + return IRQ_HANDLED; +} + static int ltr501_init(struct ltr501_data *data) { - int ret; + int ret, status; - ret = i2c_smbus_read_byte_data(data->client, LTR501_ALS_CONTR); + ret = regmap_read(data->regmap, LTR501_ALS_CONTR, &status); if (ret < 0) return ret; - data->als_contr = ret | LTR501_CONTR_ACTIVE; - ret = i2c_smbus_read_byte_data(data->client, LTR501_PS_CONTR); + data->als_contr = ret | data->chip_info->als_mode_active; + + ret = regmap_read(data->regmap, LTR501_PS_CONTR, &status); if (ret < 0) return ret; - data->ps_contr = ret | LTR501_CONTR_ACTIVE; - return ltr501_write_contr(data->client, data->als_contr, - data->ps_contr); + data->ps_contr = status | LTR501_CONTR_ACTIVE; + + ret = ltr501_read_intr_prst(data, IIO_INTENSITY, &data->als_period); + if (ret < 0) + return ret; + + ret = ltr501_read_intr_prst(data, IIO_PROXIMITY, &data->ps_period); + if (ret < 0) + return ret; + + return ltr501_write_contr(data, data->als_contr, data->ps_contr); +} + +static bool ltr501_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LTR501_ALS_DATA1: + case LTR501_ALS_DATA0: + case LTR501_ALS_PS_STATUS: + case LTR501_PS_DATA: + return true; + default: + return false; + } } +static struct regmap_config ltr501_regmap_config = { + .name = LTR501_REGMAP_NAME, + .reg_bits = 8, + .val_bits = 8, + .max_register = LTR501_MAX_REG, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = ltr501_is_volatile_reg, +}; + static int ltr501_powerdown(struct ltr501_data *data) { - return ltr501_write_contr(data->client, - data->als_contr & ~LTR501_CONTR_ACTIVE, + return ltr501_write_contr(data, data->als_contr & + ~data->chip_info->als_mode_active, data->ps_contr & ~LTR501_CONTR_ACTIVE); } +static const char *ltr501_match_acpi_device(struct device *dev, int *chip_idx) +{ + const struct acpi_device_id *id; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return NULL; + *chip_idx = id->driver_data; + return dev_name(dev); +} + static int ltr501_probe(struct i2c_client *client, - const struct i2c_device_id *id) + const struct i2c_device_id *id) { struct ltr501_data *data; struct iio_dev *indio_dev; - int ret; + struct regmap *regmap; + int ret, partid, chip_idx = 0; + const char *name = NULL; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; + regmap = devm_regmap_init_i2c(client, <r501_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Regmap initialization failed.\n"); + return PTR_ERR(regmap); + } + data = iio_priv(indio_dev); i2c_set_clientdata(client, indio_dev); data->client = client; + data->regmap = regmap; mutex_init(&data->lock_als); mutex_init(&data->lock_ps); - ret = i2c_smbus_read_byte_data(data->client, LTR501_PART_ID); + data->reg_it = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_it); + if (IS_ERR(data->reg_it)) { + dev_err(&client->dev, "Integ time reg field init failed.\n"); + return PTR_ERR(data->reg_it); + } + + data->reg_als_intr = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_als_intr); + if (IS_ERR(data->reg_als_intr)) { + dev_err(&client->dev, "ALS intr mode reg field init failed\n"); + return PTR_ERR(data->reg_als_intr); + } + + data->reg_ps_intr = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_ps_intr); + if (IS_ERR(data->reg_ps_intr)) { + dev_err(&client->dev, "PS intr mode reg field init failed.\n"); + return PTR_ERR(data->reg_ps_intr); + } + + data->reg_als_rate = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_als_rate); + if (IS_ERR(data->reg_als_rate)) { + dev_err(&client->dev, "ALS samp rate field init failed.\n"); + return PTR_ERR(data->reg_als_rate); + } + + data->reg_ps_rate = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_ps_rate); + if (IS_ERR(data->reg_ps_rate)) { + dev_err(&client->dev, "PS samp rate field init failed.\n"); + return PTR_ERR(data->reg_ps_rate); + } + + data->reg_als_prst = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_als_prst); + if (IS_ERR(data->reg_als_prst)) { + dev_err(&client->dev, "ALS prst reg field init failed\n"); + return PTR_ERR(data->reg_als_prst); + } + + data->reg_ps_prst = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_ps_prst); + if (IS_ERR(data->reg_ps_prst)) { + dev_err(&client->dev, "PS prst reg field init failed.\n"); + return PTR_ERR(data->reg_ps_prst); + } + + ret = regmap_read(data->regmap, LTR501_PART_ID, &partid); if (ret < 0) return ret; - if ((ret >> 4) != 0x8) + + if (id) { + name = id->name; + chip_idx = id->driver_data; + } else if (ACPI_HANDLE(&client->dev)) { + name = ltr501_match_acpi_device(&client->dev, &chip_idx); + } else { + return -ENODEV; + } + + data->chip_info = <r501_chip_info_tbl[chip_idx]; + + if ((partid >> 4) != data->chip_info->partid) return -ENODEV; indio_dev->dev.parent = &client->dev; - indio_dev->info = <r501_info; - indio_dev->channels = ltr501_channels; - indio_dev->num_channels = ARRAY_SIZE(ltr501_channels); - indio_dev->name = LTR501_DRV_NAME; + indio_dev->info = data->chip_info->info; + indio_dev->channels = data->chip_info->channels; + indio_dev->num_channels = data->chip_info->no_channels; + indio_dev->name = name; indio_dev->modes = INDIO_DIRECT_MODE; ret = ltr501_init(data); if (ret < 0) return ret; + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, ltr501_interrupt_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "ltr501_thresh_event", + indio_dev); + if (ret) { + dev_err(&client->dev, "request irq (%d) failed\n", + client->irq); + return ret; + } + } else { + indio_dev->info = data->chip_info->info_no_irq; + } + ret = iio_triggered_buffer_setup(indio_dev, NULL, - ltr501_trigger_handler, NULL); + ltr501_trigger_handler, NULL); if (ret) goto powerdown_on_error; @@ -407,24 +1514,34 @@ static int ltr501_remove(struct i2c_client *client) static int ltr501_suspend(struct device *dev) { struct ltr501_data *data = iio_priv(i2c_get_clientdata( - to_i2c_client(dev))); + to_i2c_client(dev))); return ltr501_powerdown(data); } static int ltr501_resume(struct device *dev) { struct ltr501_data *data = iio_priv(i2c_get_clientdata( - to_i2c_client(dev))); + to_i2c_client(dev))); - return ltr501_write_contr(data->client, data->als_contr, + return ltr501_write_contr(data, data->als_contr, data->ps_contr); } #endif static SIMPLE_DEV_PM_OPS(ltr501_pm_ops, ltr501_suspend, ltr501_resume); +static const struct acpi_device_id ltr_acpi_match[] = { + {"LTER0501", ltr501}, + {"LTER0559", ltr559}, + {"LTER0301", ltr301}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, ltr_acpi_match); + static const struct i2c_device_id ltr501_id[] = { - { "ltr501", 0 }, + { "ltr501", ltr501}, + { "ltr559", ltr559}, + { "ltr301", ltr301}, { } }; MODULE_DEVICE_TABLE(i2c, ltr501_id); @@ -433,6 +1550,7 @@ static struct i2c_driver ltr501_driver = { .driver = { .name = LTR501_DRV_NAME, .pm = <r501_pm_ops, + .acpi_match_table = ACPI_PTR(ltr_acpi_match), .owner = THIS_MODULE, }, .probe = ltr501_probe, diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c new file mode 100644 index 000000000000..fee4297d7c8f --- /dev/null +++ b/drivers/iio/light/stk3310.c @@ -0,0 +1,722 @@ +/** + * Sensortek STK3310/STK3311 Ambient Light and Proximity Sensor + * + * Copyright (c) 2015, Intel Corporation. + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * IIO driver for STK3310/STK3311. 7-bit I2C address: 0x48. + */ + +#include <linux/acpi.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/gpio/consumer.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define STK3310_REG_STATE 0x00 +#define STK3310_REG_PSCTRL 0x01 +#define STK3310_REG_ALSCTRL 0x02 +#define STK3310_REG_INT 0x04 +#define STK3310_REG_THDH_PS 0x06 +#define STK3310_REG_THDL_PS 0x08 +#define STK3310_REG_FLAG 0x10 +#define STK3310_REG_PS_DATA_MSB 0x11 +#define STK3310_REG_PS_DATA_LSB 0x12 +#define STK3310_REG_ALS_DATA_MSB 0x13 +#define STK3310_REG_ALS_DATA_LSB 0x14 +#define STK3310_REG_ID 0x3E +#define STK3310_MAX_REG 0x80 + +#define STK3310_STATE_EN_PS 0x01 +#define STK3310_STATE_EN_ALS 0x02 +#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_THRESH_MAX 0xFFFF + +#define STK3310_DRIVER_NAME "stk3310" +#define STK3310_REGMAP_NAME "stk3310_regmap" +#define STK3310_EVENT "stk3310_event" +#define STK3310_GPIO "stk3310_gpio" + +#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); +/* + * Maximum PS values with regard to scale. Used to export the 'inverse' + * PS value (high values for far objects, low values for near objects). + */ +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, +}; + +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_FALLING, + .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, + .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; + u16 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. + */ + if (dir == IIO_EV_DIR_RISING) + reg = STK3310_REG_THDL_PS; + else if (dir == IIO_EV_DIR_FALLING) + reg = STK3310_REG_THDH_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; + } + regmap_field_read(data->reg_ps_gain, &index); + *val = swab16(stk3310_ps_max[index] - 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; + u16 buf; + int ret; + unsigned int index; + struct stk3310_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + regmap_field_read(data->reg_ps_gain, &index); + if (val > stk3310_ps_max[index]) + 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 + return -EINVAL; + + buf = swab16(stk3310_ps_max[index] - 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; + struct stk3310_data *data = iio_priv(indio_dev); + + regmap_field_read(data->reg_int_ps, &event_val); + + 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; + u16 buf; + int ret; + unsigned int index; + struct stk3310_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_LIGHT) + reg = STK3310_REG_ALS_DATA_MSB; + else if (chan->type == IIO_PROXIMITY) + reg = STK3310_REG_PS_DATA_MSB; + else + return -EINVAL; + 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 = 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; + } + mutex_unlock(&data->lock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + if (chan->type == IIO_LIGHT) + regmap_field_read(data->reg_als_it, &index); + else + regmap_field_read(data->reg_ps_it, &index); + *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) + regmap_field_read(data->reg_als_gain, &index); + else + regmap_field_read(data->reg_ps_gain, &index); + *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); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + index = stk3310_get_index(stk3310_it_table, + ARRAY_SIZE(stk3310_it_table), + val, val2); + if (index < 0) + return -EINVAL; + mutex_lock(&data->lock); + if (chan->type == IIO_LIGHT) + ret = regmap_field_write(data->reg_als_it, index); + else + ret = regmap_field_write(data->reg_ps_it, index); + if (ret < 0) + dev_err(&data->client->dev, + "sensor configuration failed\n"); + mutex_unlock(&data->lock); + return ret; + + case IIO_CHAN_INFO_SCALE: + index = stk3310_get_index(stk3310_scale_table, + ARRAY_SIZE(stk3310_scale_table), + val, val2); + if (index < 0) + return -EINVAL; + mutex_lock(&data->lock); + if (chan->type == IIO_LIGHT) + ret = regmap_field_write(data->reg_als_gain, index); + else + ret = regmap_field_write(data->reg_ps_gain, index); + if (ret < 0) + dev_err(&data->client->dev, + "sensor configuration failed\n"); + mutex_unlock(&data->lock); + return ret; + } + + return -EINVAL; +} + +static const struct iio_info stk3310_info = { + .driver_module = THIS_MODULE, + .read_raw = stk3310_read_raw, + .write_raw = stk3310_write_raw, + .attrs = &stk3310_attribute_group, + .read_event_value = stk3310_read_event, + .write_event_value = stk3310_write_event, + .read_event_config = stk3310_read_event_config, + .write_event_config = stk3310_write_event_config, +}; + +static int stk3310_set_state(struct stk3310_data *data, u8 state) +{ + int ret; + struct i2c_client *client = data->client; + + /* 3-bit state; 0b100 is not supported. */ + if (state > 7 || state == 4) + return -EINVAL; + + mutex_lock(&data->lock); + ret = regmap_field_write(data->reg_state, state); + if (ret < 0) { + dev_err(&client->dev, "failed to change sensor state\n"); + } else if (state != STK3310_STATE_STANDBY) { + /* Don't reset the 'enabled' flags if we're going in standby */ + data->ps_enabled = !!(state & 0x01); + data->als_enabled = !!(state & 0x02); + } + 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; + + regmap_read(data->regmap, STK3310_REG_ID, &chipid); + 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 int stk3310_gpio_probe(struct i2c_client *client) +{ + struct device *dev; + struct gpio_desc *gpio; + int ret; + + if (!client) + return -EINVAL; + + dev = &client->dev; + + /* gpio interrupt pin */ + gpio = devm_gpiod_get_index(dev, STK3310_GPIO, 0); + if (IS_ERR(gpio)) { + dev_err(dev, "acpi gpio get index failed\n"); + return PTR_ERR(gpio); + } + + ret = gpiod_direction_input(gpio); + if (ret) + return ret; + + ret = gpiod_to_irq(gpio); + dev_dbg(dev, "GPIO resource, no:%d irq:%d\n", desc_to_gpio(gpio), ret); + + return ret; +} + +static bool stk3310_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STK3310_REG_ALS_DATA_MSB: + case STK3310_REG_ALS_DATA_LSB: + case STK3310_REG_PS_DATA_LSB: + case STK3310_REG_PS_DATA_MSB: + case STK3310_REG_FLAG: + return true; + default: + return false; + } +} + +static struct regmap_config stk3310_regmap_config = { + .name = STK3310_REGMAP_NAME, + .reg_bits = 8, + .val_bits = 8, + .max_register = STK3310_MAX_REG, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = stk3310_is_volatile_reg, +}; + +static int stk3310_regmap_init(struct stk3310_data *data) +{ + struct regmap *regmap; + struct i2c_client *client; + + client = data->client; + regmap = devm_regmap_init_i2c(client, &stk3310_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "regmap initialization failed.\n"); + return PTR_ERR(regmap); + } + data->regmap = regmap; + + STK3310_REGFIELD(state); + STK3310_REGFIELD(als_gain); + STK3310_REGFIELD(ps_gain); + STK3310_REGFIELD(als_it); + STK3310_REGFIELD(ps_it); + STK3310_REGFIELD(int_ps); + STK3310_REGFIELD(flag_psint); + STK3310_REGFIELD(flag_nf); + + return 0; +} + +static irqreturn_t stk3310_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct stk3310_data *data = iio_priv(indio_dev); + + data->timestamp = iio_get_time_ns(); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t stk3310_irq_event_handler(int irq, void *private) +{ + int ret; + unsigned int dir; + u64 event; + + struct iio_dev *indio_dev = private; + struct stk3310_data *data = iio_priv(indio_dev); + + /* Read FLAG_NF to figure out what threshold has been met. */ + mutex_lock(&data->lock); + ret = regmap_field_read(data->reg_flag_nf, &dir); + if (ret < 0) { + dev_err(&data->client->dev, "register read failed\n"); + mutex_unlock(&data->lock); + return ret; + } + event = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1, + IIO_EV_TYPE_THRESH, + (dir ? IIO_EV_DIR_RISING : + IIO_EV_DIR_FALLING)); + 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; + + 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) + client->irq = stk3310_gpio_probe(client); + + 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); + } + + 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) +{ + int 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}, + {} +}; + +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/tsl2563.c b/drivers/iio/light/tsl2563.c index 94daa9fc1247..12731d6b89ec 100644 --- a/drivers/iio/light/tsl2563.c +++ b/drivers/iio/light/tsl2563.c @@ -240,7 +240,7 @@ static int tsl2563_read_id(struct tsl2563_chip *chip, u8 *id) * convert between normalized values and HW values obtained using given * timing and gain settings. */ -static int adc_shiftbits(u8 timing) +static int tsl2563_adc_shiftbits(u8 timing) { int shift = 0; @@ -263,9 +263,9 @@ static int adc_shiftbits(u8 timing) } /* Convert a HW ADC value to normalized scale. */ -static u32 normalize_adc(u16 adc, u8 timing) +static u32 tsl2563_normalize_adc(u16 adc, u8 timing) { - return adc << adc_shiftbits(timing); + return adc << tsl2563_adc_shiftbits(timing); } static void tsl2563_wait_adc(struct tsl2563_chip *chip) @@ -350,8 +350,8 @@ static int tsl2563_get_adc(struct tsl2563_chip *chip) retry = tsl2563_adjust_gainlevel(chip, adc0); } - chip->data0 = normalize_adc(adc0, chip->gainlevel->gaintime); - chip->data1 = normalize_adc(adc1, chip->gainlevel->gaintime); + chip->data0 = tsl2563_normalize_adc(adc0, chip->gainlevel->gaintime); + chip->data1 = tsl2563_normalize_adc(adc1, chip->gainlevel->gaintime); if (!chip->int_enabled) schedule_delayed_work(&chip->poweroff_work, 5 * HZ); @@ -361,13 +361,13 @@ out: return ret; } -static inline int calib_to_sysfs(u32 calib) +static inline int tsl2563_calib_to_sysfs(u32 calib) { return (int) (((calib * CALIB_BASE_SYSFS) + CALIB_FRAC_HALF) >> CALIB_FRAC_BITS); } -static inline u32 calib_from_sysfs(int value) +static inline u32 tsl2563_calib_from_sysfs(int value) { return (((u32) value) << CALIB_FRAC_BITS) / CALIB_BASE_SYSFS; } @@ -426,7 +426,7 @@ static const struct tsl2563_lux_coeff lux_table[] = { }; /* Convert normalized, scaled ADC values to lux. */ -static unsigned int adc_to_lux(u32 adc0, u32 adc1) +static unsigned int tsl2563_adc_to_lux(u32 adc0, u32 adc1) { const struct tsl2563_lux_coeff *lp = lux_table; unsigned long ratio, lux, ch0 = adc0, ch1 = adc1; @@ -442,7 +442,7 @@ static unsigned int adc_to_lux(u32 adc0, u32 adc1) } /* Apply calibration coefficient to ADC count. */ -static u32 calib_adc(u32 adc, u32 calib) +static u32 tsl2563_calib_adc(u32 adc, u32 calib) { unsigned long scaled = adc; @@ -463,9 +463,9 @@ static int tsl2563_write_raw(struct iio_dev *indio_dev, if (mask != IIO_CHAN_INFO_CALIBSCALE) return -EINVAL; if (chan->channel2 == IIO_MOD_LIGHT_BOTH) - chip->calib0 = calib_from_sysfs(val); + chip->calib0 = tsl2563_calib_from_sysfs(val); else if (chan->channel2 == IIO_MOD_LIGHT_IR) - chip->calib1 = calib_from_sysfs(val); + chip->calib1 = tsl2563_calib_from_sysfs(val); else return -EINVAL; @@ -491,11 +491,11 @@ static int tsl2563_read_raw(struct iio_dev *indio_dev, ret = tsl2563_get_adc(chip); if (ret) goto error_ret; - calib0 = calib_adc(chip->data0, chip->calib0) * + calib0 = tsl2563_calib_adc(chip->data0, chip->calib0) * chip->cover_comp_gain; - calib1 = calib_adc(chip->data1, chip->calib1) * + calib1 = tsl2563_calib_adc(chip->data1, chip->calib1) * chip->cover_comp_gain; - *val = adc_to_lux(calib0, calib1); + *val = tsl2563_adc_to_lux(calib0, calib1); ret = IIO_VAL_INT; break; case IIO_INTENSITY: @@ -515,9 +515,9 @@ static int tsl2563_read_raw(struct iio_dev *indio_dev, case IIO_CHAN_INFO_CALIBSCALE: if (chan->channel2 == IIO_MOD_LIGHT_BOTH) - *val = calib_to_sysfs(chip->calib0); + *val = tsl2563_calib_to_sysfs(chip->calib0); else - *val = calib_to_sysfs(chip->calib1); + *val = tsl2563_calib_to_sysfs(chip->calib1); ret = IIO_VAL_INT; break; default: @@ -750,8 +750,8 @@ static int tsl2563_probe(struct i2c_client *client, chip->high_thres = 0xffff; chip->gainlevel = tsl2563_gainlevel_table; chip->intr = TSL2563_INT_PERSIST(4); - chip->calib0 = calib_from_sysfs(CALIB_BASE_SYSFS); - chip->calib1 = calib_from_sysfs(CALIB_BASE_SYSFS); + chip->calib0 = tsl2563_calib_from_sysfs(CALIB_BASE_SYSFS); + chip->calib1 = tsl2563_calib_from_sysfs(CALIB_BASE_SYSFS); if (pdata) chip->cover_comp_gain = pdata->cover_comp_gain; diff --git a/drivers/iio/light/tsl4531.c b/drivers/iio/light/tsl4531.c index 0763b8632573..63c26e2d5d97 100644 --- a/drivers/iio/light/tsl4531.c +++ b/drivers/iio/light/tsl4531.c @@ -24,12 +24,12 @@ #define TSL4531_DRV_NAME "tsl4531" -#define TCS3472_COMMAND BIT(7) +#define TSL4531_COMMAND BIT(7) -#define TSL4531_CONTROL (TCS3472_COMMAND | 0x00) -#define TSL4531_CONFIG (TCS3472_COMMAND | 0x01) -#define TSL4531_DATA (TCS3472_COMMAND | 0x04) -#define TSL4531_ID (TCS3472_COMMAND | 0x0a) +#define TSL4531_CONTROL (TSL4531_COMMAND | 0x00) +#define TSL4531_CONFIG (TSL4531_COMMAND | 0x01) +#define TSL4531_DATA (TSL4531_COMMAND | 0x04) +#define TSL4531_ID (TSL4531_COMMAND | 0x0a) /* operating modes in control register */ #define TSL4531_MODE_POWERDOWN 0x00 |