diff options
author | Crestez Dan Leonard <leonard.crestez@intel.com> | 2016-04-18 16:31:53 +0200 |
---|---|---|
committer | Jonathan Cameron <jic23@kernel.org> | 2016-04-24 11:06:55 +0200 |
commit | b9567e6664643eb99367fb88d14fdba863b1688a (patch) | |
tree | f478f8c9e57bb6f60857a3595a841e3110285eba /drivers/iio | |
parent | iio: humidity: Add triggered buffer support for AM2315 (diff) | |
download | linux-b9567e6664643eb99367fb88d14fdba863b1688a.tar.xz linux-b9567e6664643eb99367fb88d14fdba863b1688a.zip |
max44000: Initial support
This just adds support for reporting illuminance with default settings.
Important default registers are written on probe because the device
otherwise lacks a reset function.
Signed-off-by: Crestez Dan Leonard <leonard.crestez@intel.com>
Signed-off-by: Jonathan Cameron <jic23@kernel.org>
Diffstat (limited to 'drivers/iio')
-rw-r--r-- | drivers/iio/light/Kconfig | 11 | ||||
-rw-r--r-- | drivers/iio/light/Makefile | 1 | ||||
-rw-r--r-- | drivers/iio/light/max44000.c | 324 |
3 files changed, 336 insertions, 0 deletions
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index cbd723236e38..7c566f516572 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -234,6 +234,17 @@ config LTR501 This driver can also be built as a module. If so, the module will be called ltr501. +config MAX44000 + tristate "MAX44000 Ambient and Infrared Proximity Sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here if you want to build support for Maxim Integrated's + MAX44000 ambient and infrared proximity sensor device. + + To compile this driver as a module, choose M here: + the module will be called max44000. + config OPT3001 tristate "Texas Instruments OPT3001 Light Sensor" depends on I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index 8b246a604a92..6f2a3c62de27 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_ISL29125) += isl29125.o obj-$(CONFIG_JSA1212) += jsa1212.o obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o obj-$(CONFIG_LTR501) += ltr501.o +obj-$(CONFIG_MAX44000) += max44000.o obj-$(CONFIG_OPT3001) += opt3001.o obj-$(CONFIG_PA12203001) += pa12203001.o obj-$(CONFIG_RPR0521) += rpr0521.o diff --git a/drivers/iio/light/max44000.c b/drivers/iio/light/max44000.c new file mode 100644 index 000000000000..5ed79345bb42 --- /dev/null +++ b/drivers/iio/light/max44000.c @@ -0,0 +1,324 @@ +/* + * MAX44000 Ambient and Infrared Proximity Sensor + * + * Copyright (c) 2016, Intel Corporation. + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * Data sheet: https://datasheets.maximintegrated.com/en/ds/MAX44000.pdf + * + * 7-bit I2C slave address 0x4a + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/util_macros.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/acpi.h> + +#define MAX44000_DRV_NAME "max44000" + +/* Registers in datasheet order */ +#define MAX44000_REG_STATUS 0x00 +#define MAX44000_REG_CFG_MAIN 0x01 +#define MAX44000_REG_CFG_RX 0x02 +#define MAX44000_REG_CFG_TX 0x03 +#define MAX44000_REG_ALS_DATA_HI 0x04 +#define MAX44000_REG_ALS_DATA_LO 0x05 +#define MAX44000_REG_PRX_DATA 0x16 +#define MAX44000_REG_ALS_UPTHR_HI 0x06 +#define MAX44000_REG_ALS_UPTHR_LO 0x07 +#define MAX44000_REG_ALS_LOTHR_HI 0x08 +#define MAX44000_REG_ALS_LOTHR_LO 0x09 +#define MAX44000_REG_PST 0x0a +#define MAX44000_REG_PRX_IND 0x0b +#define MAX44000_REG_PRX_THR 0x0c +#define MAX44000_REG_TRIM_GAIN_GREEN 0x0f +#define MAX44000_REG_TRIM_GAIN_IR 0x10 + +/* REG_CFG bits */ +#define MAX44000_CFG_ALSINTE 0x01 +#define MAX44000_CFG_PRXINTE 0x02 +#define MAX44000_CFG_MASK 0x1c +#define MAX44000_CFG_MODE_SHUTDOWN 0x00 +#define MAX44000_CFG_MODE_ALS_GIR 0x04 +#define MAX44000_CFG_MODE_ALS_G 0x08 +#define MAX44000_CFG_MODE_ALS_IR 0x0c +#define MAX44000_CFG_MODE_ALS_PRX 0x10 +#define MAX44000_CFG_MODE_PRX 0x14 +#define MAX44000_CFG_TRIM 0x20 + +/* + * Upper 4 bits are not documented but start as 1 on powerup + * Setting them to 0 causes proximity to misbehave so set them to 1 + */ +#define MAX44000_REG_CFG_RX_DEFAULT 0xf0 + +#define MAX44000_ALSDATA_OVERFLOW 0x4000 + +struct max44000_data { + struct mutex lock; + struct regmap *regmap; +}; + +/* Default scale is set to the minimum of 0.03125 or 1 / (1 << 5) lux */ +#define MAX44000_ALS_TO_LUX_DEFAULT_FRACTION_LOG2 5 + +static const struct iio_chan_spec max44000_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int max44000_read_alsval(struct max44000_data *data) +{ + u16 regval; + int ret; + + ret = regmap_bulk_read(data->regmap, MAX44000_REG_ALS_DATA_HI, + ®val, sizeof(regval)); + if (ret < 0) + return ret; + + regval = be16_to_cpu(regval); + + /* + * Overflow is explained on datasheet page 17. + * + * It's a warning that either the G or IR channel has become saturated + * and that the value in the register is likely incorrect. + * + * The recommendation is to change the scale (ALSPGA). + * The driver just returns the max representable value. + */ + if (regval & MAX44000_ALSDATA_OVERFLOW) + return 0x3FFF; + + return regval; +} + +static int max44000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max44000_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + mutex_lock(&data->lock); + ret = max44000_read_alsval(data); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_LIGHT: + *val = 1; + *val2 = MAX44000_ALS_TO_LUX_DEFAULT_FRACTION_LOG2; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static const struct iio_info max44000_info = { + .driver_module = THIS_MODULE, + .read_raw = max44000_read_raw, +}; + +static bool max44000_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX44000_REG_STATUS: + case MAX44000_REG_CFG_MAIN: + case MAX44000_REG_CFG_RX: + case MAX44000_REG_CFG_TX: + case MAX44000_REG_ALS_DATA_HI: + case MAX44000_REG_ALS_DATA_LO: + case MAX44000_REG_PRX_DATA: + case MAX44000_REG_ALS_UPTHR_HI: + case MAX44000_REG_ALS_UPTHR_LO: + case MAX44000_REG_ALS_LOTHR_HI: + case MAX44000_REG_ALS_LOTHR_LO: + case MAX44000_REG_PST: + case MAX44000_REG_PRX_IND: + case MAX44000_REG_PRX_THR: + case MAX44000_REG_TRIM_GAIN_GREEN: + case MAX44000_REG_TRIM_GAIN_IR: + return true; + default: + return false; + } +} + +static bool max44000_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX44000_REG_CFG_MAIN: + case MAX44000_REG_CFG_RX: + case MAX44000_REG_CFG_TX: + case MAX44000_REG_ALS_UPTHR_HI: + case MAX44000_REG_ALS_UPTHR_LO: + case MAX44000_REG_ALS_LOTHR_HI: + case MAX44000_REG_ALS_LOTHR_LO: + case MAX44000_REG_PST: + case MAX44000_REG_PRX_IND: + case MAX44000_REG_PRX_THR: + case MAX44000_REG_TRIM_GAIN_GREEN: + case MAX44000_REG_TRIM_GAIN_IR: + return true; + default: + return false; + } +} + +static bool max44000_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX44000_REG_STATUS: + case MAX44000_REG_ALS_DATA_HI: + case MAX44000_REG_ALS_DATA_LO: + case MAX44000_REG_PRX_DATA: + return true; + default: + return false; + } +} + +static bool max44000_precious_reg(struct device *dev, unsigned int reg) +{ + return reg == MAX44000_REG_STATUS; +} + +static const struct regmap_config max44000_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = MAX44000_REG_PRX_DATA, + .readable_reg = max44000_readable_reg, + .writeable_reg = max44000_writeable_reg, + .volatile_reg = max44000_volatile_reg, + .precious_reg = max44000_precious_reg, + + .use_single_rw = 1, + .cache_type = REGCACHE_RBTREE, +}; + +static int max44000_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max44000_data *data; + struct iio_dev *indio_dev; + int ret, reg; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + data = iio_priv(indio_dev); + data->regmap = devm_regmap_init_i2c(client, &max44000_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "regmap_init failed!\n"); + return PTR_ERR(data->regmap); + } + + i2c_set_clientdata(client, indio_dev); + mutex_init(&data->lock); + indio_dev->dev.parent = &client->dev; + indio_dev->info = &max44000_info; + indio_dev->name = MAX44000_DRV_NAME; + indio_dev->channels = max44000_channels; + indio_dev->num_channels = ARRAY_SIZE(max44000_channels); + + /* + * The device doesn't have a reset function so we just clear some + * important bits at probe time to ensure sane operation. + * + * Since we don't support interrupts/events the threshold values are + * not important. We also don't touch trim values. + */ + + /* Reset ALS scaling bits */ + ret = regmap_write(data->regmap, MAX44000_REG_CFG_RX, MAX44000_REG_CFG_RX_DEFAULT); + if (ret < 0) { + dev_err(&client->dev, "failed to write default CFG_RX: %d\n", ret); + return ret; + } + + /* Reset CFG bits to ALS-only mode and no interrupts */ + reg = MAX44000_CFG_TRIM | MAX44000_CFG_MODE_ALS_GIR; + ret = regmap_write(data->regmap, MAX44000_REG_CFG_MAIN, reg); + if (ret < 0) { + dev_err(&client->dev, "failed to write init config: %d\n", ret); + return ret; + } + + /* Read status at least once to clear any stale interrupt bits. */ + ret = regmap_read(data->regmap, MAX44000_REG_STATUS, ®); + if (ret < 0) { + dev_err(&client->dev, "failed to read init status: %d\n", ret); + return ret; + } + + return iio_device_register(indio_dev); +} + +static int max44000_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + + return 0; +} + +static const struct i2c_device_id max44000_id[] = { + {"max44000", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, max44000_id); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id max44000_acpi_match[] = { + {"MAX44000", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, max44000_acpi_match); +#endif + +static struct i2c_driver max44000_driver = { + .driver = { + .name = MAX44000_DRV_NAME, + .acpi_match_table = ACPI_PTR(max44000_acpi_match), + }, + .probe = max44000_probe, + .remove = max44000_remove, + .id_table = max44000_id, +}; + +module_i2c_driver(max44000_driver); + +MODULE_AUTHOR("Crestez Dan Leonard <leonard.crestez@intel.com>"); +MODULE_DESCRIPTION("MAX44000 Ambient and Infrared Proximity Sensor"); +MODULE_LICENSE("GPL v2"); |