diff options
| author | Sri Krishna chowdary <schowdary@nvidia.com> | 2014-01-29 10:18:03 +0530 |
|---|---|---|
| committer | Sachin Nikam <snikam@nvidia.com> | 2014-02-10 00:36:15 -0800 |
| commit | 2c5a25157ef38621d20153a0bcdd295647e82047 (patch) | |
| tree | a3fe1aee69e5b6b10650cea69dea6157d6791c54 | |
| parent | 58556ad388017d68ddcf540eea2c3047adf69550 (diff) | |
staging: iio: light: IQS253 capacitive sensor
This patch set addresses the following
- In normal mode, IQS253 supports proximity detection upto 2 cm
- In stylus mode, i.e., when AP is in suspend, it acts as a wake up
- Also, DT bindings for IQS253 are added
bug 1420230
Change-Id: I2b1548f8b108dc3b513b34df83c52e24a6cb09bd
Signed-off-by: Sri Krishna chowdary <schowdary@nvidia.com>
Reviewed-on: http://git-master/r/359782
(cherry picked from commit 19fbb45f5c3c1dc9d9ec445923ca951132bf89a2)
Reviewed-on: http://git-master/r/363699
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Sachin Nikam <snikam@nvidia.com>
| -rw-r--r-- | Documentation/devicetree/bindings/staging/iio/light/iqs253-ps.txt | 24 | ||||
| -rw-r--r-- | drivers/staging/iio/light/Kconfig | 12 | ||||
| -rw-r--r-- | drivers/staging/iio/light/Makefile | 1 | ||||
| -rw-r--r-- | drivers/staging/iio/light/iqs253.c | 568 |
4 files changed, 605 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/staging/iio/light/iqs253-ps.txt b/Documentation/devicetree/bindings/staging/iio/light/iqs253-ps.txt new file mode 100644 index 000000000000..7c536efeb995 --- /dev/null +++ b/Documentation/devicetree/bindings/staging/iio/light/iqs253-ps.txt @@ -0,0 +1,24 @@ +* IQS253 proximity sensor + +Required properties: +- compatible: must be "azoteq,iqs253" +- reg: i2c address of the device. It is one of 0x44-0x47. +- vendor: vendor of the hardware part. +- proximity,max-range: maximum range of this sensor's value in SI units. +- proximity,integration-time: minimum sampling period in nano seconds. +- proximity,power-consumed: rough estimate of this sensor's power consumption in mA. +- rdy-gpio: gpio to be used for i2c handshake with the sensor. +- wake-gpio: gpio to be used for wakeup on stylus insert/removal event. + +Example: + + iqs253@44 { + compatible = "azoteq,iqs253"; + reg = <0x44>; + vendor = "Azoteq"; + proximity,max-range = "2"; /* 2 cm */; + proximity,integration-time = "16000000"; /* 16 msec */ + proximity,power-consumed = "1.67"; /* mA */ + rdy-gpio = <&gpio TEGRA_GPIO(PK, 5) 1>; + wake-gpio = <&gpio TEGRA_GPIO(PW, 3) 1>; + }; diff --git a/drivers/staging/iio/light/Kconfig b/drivers/staging/iio/light/Kconfig index c8c61cee5579..9a1f35fc9881 100644 --- a/drivers/staging/iio/light/Kconfig +++ b/drivers/staging/iio/light/Kconfig @@ -123,4 +123,16 @@ config LS_SYSFS using helpers from this file, make sure device's platform data contains all the required information. +config SENSORS_IQS253 + tristate "IQS253 capacitive sensor" + depends on I2C + select LS_OF + select LS_SYFS + default n + help + Say Y to enable proximity detection using IQS253 capacitive sensor. + This driver exports sensor's specifications in DT node to user space. + Hence, it needs LS_OF and LS_SYSFS support. + This driver uses I2C to program the device. + endmenu diff --git a/drivers/staging/iio/light/Makefile b/drivers/staging/iio/light/Makefile index d0244a5fb0ca..7afa16109514 100644 --- a/drivers/staging/iio/light/Makefile +++ b/drivers/staging/iio/light/Makefile @@ -20,3 +20,4 @@ obj-$(CONFIG_TSL2583) += tsl2583.o obj-$(CONFIG_SENSORS_CM3217) += cm3217.o obj-$(CONFIG_LS_SYSFS) += ls_sysfs.o obj-$(CONFIG_LS_OF) += ls_dt.o +obj-$(CONFIG_SENSORS_IQS253) += iqs253.o diff --git a/drivers/staging/iio/light/iqs253.c b/drivers/staging/iio/light/iqs253.c new file mode 100644 index 000000000000..6d8c882026f2 --- /dev/null +++ b/drivers/staging/iio/light/iqs253.c @@ -0,0 +1,568 @@ +/* + * A iio driver for the capacitive sensor IQS253. + * + * IIO Light driver for monitoring proximity. + * + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/irqchip/tegra.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/light/ls_sysfs.h> +#include <linux/iio/light/ls_dt.h> + +/* registers */ +#define SYSFLAGS 0x10 +#define PROX_STATUS 0x31 +#define TOUCH_STATUS 0x35 +#define TARGET 0xC4 +#define COMP0 0xC5 +#define CH0_ATI_BASE 0xC8 +#define CH1_ATI_BASE 0xC9 +#define CH2_ATI_BASE 0xCA +#define CH0_PTH 0xCB +#define CH1_PTH 0xCC +#define CH2_PTH 0xCD +#define PROX_SETTINGS0 0xD1 +#define PROX_SETTINGS1 0xD2 +#define PROX_SETTINGS2 0xD3 +#define PROX_SETTINGS3 0xD4 +#define ACTIVE_CHAN 0xD5 +#define LOW_POWER 0xD6 +#define DYCAL_CHANS 0xD8 +#define EVENT_MODE_MASK 0xD9 +#define DEFAULT_COMMS_POINTER 0xDD + +#define IQS253_PROD_ID 41 + +#define STYLUS_ONLY 0x04 /* Channel 2 for stylus */ +#define PROXIMITY_ONLY 0x03 /* channel 0 and channel 1 for proximity */ + +#define CH0_COMPENSATION 0x55 + +#define PROX_TH_CH0 0x01 +#define PROX_TH_CH1 0x02 +#define PROX_TH_CH2 0x04 + +#define DISABLE_DYCAL 0x00 + +#define CH0_ATI_TH 0x17 +#define CH1_ATI_TH 0x17 +#define CH2_ATI_TH 0x19 + +#define EVENT_PROX_ONLY 0x01 + +#define PROX_SETTING_NORMAL 0x25 +#define PROX_SETTING_STYLUS 0x26 + +#define ATI_IN_PROGRESS 0x04 + +#define NUM_REG 17 + +struct iqs253_chip { + struct i2c_client *client; + const struct i2c_device_id *id; + u32 rdy_gpio; + u32 wake_gpio; + u32 mode; + u32 value; + struct regulator *vddhi; + u32 using_regulator; + struct lightsensor_spec *ls_spec; +}; + +enum mode { + MODE_NONE = -1, + NORMAL_MODE, + STYLUS_MODE, + NUM_MODE +}; + +struct reg_val_pair { + u8 reg; + u8 val; +}; + +struct reg_val_pair reg_val_map[NUM_MODE][NUM_REG] = { + { + { COMP0, CH0_COMPENSATION}, + { CH0_ATI_BASE, CH0_ATI_TH}, + { CH1_ATI_BASE, CH1_ATI_TH}, + { CH2_ATI_BASE, CH2_ATI_TH}, + { CH0_PTH, PROX_TH_CH0}, + { CH1_PTH, PROX_TH_CH1}, + { PROX_SETTINGS0, PROX_SETTING_NORMAL}, + { ACTIVE_CHAN, PROXIMITY_ONLY}, + { DYCAL_CHANS, DISABLE_DYCAL}, + { EVENT_MODE_MASK, EVENT_PROX_ONLY} + }, + { + { COMP0, CH0_COMPENSATION}, + { CH0_ATI_BASE, CH0_ATI_TH}, + { CH1_ATI_BASE, CH1_ATI_TH}, + { CH2_ATI_BASE, CH2_ATI_TH}, + { CH2_PTH, PROX_TH_CH2}, + { PROX_SETTINGS0, PROX_SETTING_STYLUS}, + { ACTIVE_CHAN, STYLUS_ONLY}, + { DYCAL_CHANS, DISABLE_DYCAL}, + { EVENT_MODE_MASK, EVENT_PROX_ONLY} + }, +}; + +static void iqs253_i2c_hand_shake(struct iqs253_chip *iqs253_chip) +{ + int retry_count = 10; + do { + gpio_direction_output(iqs253_chip->rdy_gpio, 0); + mdelay(10); + /* put to tristate */ + gpio_direction_input(iqs253_chip->rdy_gpio); + } while (gpio_get_value(iqs253_chip->rdy_gpio) && retry_count--); +} + +/* must call holding lock */ +static int iqs253_set(struct iqs253_chip *iqs253_chip, int mode) +{ + int ret = 0, i; + struct reg_val_pair *reg_val_pair_map; + + if ((mode != NORMAL_MODE) && (mode != STYLUS_MODE)) + return -EINVAL; + + reg_val_pair_map = reg_val_map[mode]; + + for (i = 0; i < NUM_REG; i++) { + if (!reg_val_pair_map[i].reg && !reg_val_pair_map[i].val) + continue; + + iqs253_i2c_hand_shake(iqs253_chip); + ret = i2c_smbus_write_byte_data(iqs253_chip->client, + reg_val_pair_map[i].reg, + reg_val_pair_map[i].val); + if (ret) { + dev_err(&iqs253_chip->client->dev, + "iqs253 write val:%x to reg:%x failed\n", + reg_val_pair_map[i].val, + reg_val_pair_map[i].reg); + return ret; + } + } + + /* wait for ATI to finish */ + do { + iqs253_i2c_hand_shake(iqs253_chip); + ret = i2c_smbus_read_byte_data(iqs253_chip->client, SYSFLAGS); + mdelay(10); + } while (ret & ATI_IN_PROGRESS); + + iqs253_chip->mode = mode; + return 0; +} + +/* device's registration with iio to facilitate user operations */ +static ssize_t iqs253_chan_regulator_enable( + struct iio_dev *indio_dev, uintptr_t private, + struct iio_chan_spec const *chan, + const char *buf, size_t len) +{ + int ret = 0; + u8 enable; + struct iqs253_chip *chip = iio_priv(indio_dev); + + if (chip->mode == STYLUS_MODE) + return -EINVAL; + + if (kstrtou8(buf, 10, &enable)) + return -EINVAL; + + if ((enable != 0) && (enable != 1)) + return -EINVAL; + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + if (enable == chip->using_regulator) + goto success; + + if (enable) + ret = regulator_enable(chip->vddhi); + else + ret = regulator_disable(chip->vddhi); + + if (ret) { + dev_err(&chip->client->dev, + "idname:%s func:%s line:%d enable:%d regulator logic failed\n", + chip->id->name, __func__, __LINE__, enable); + goto fail; + } + +success: + chip->using_regulator = enable; + chip->mode = MODE_NONE; +fail: + return ret ? ret : 1; +} + +static ssize_t iqs253_chan_normal_mode_enable( + struct iio_dev *indio_dev, uintptr_t private, + struct iio_chan_spec const *chan, + const char *buf, size_t len) +{ + int ret = 0; + u8 enable; + struct iqs253_chip *chip = iio_priv(indio_dev); + + if (chip->mode == STYLUS_MODE) + return -EINVAL; + + if (kstrtou8(buf, 10, &enable)) + return -EINVAL; + + if ((enable != 0) && (enable != 1)) + return -EINVAL; + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + if (!chip->using_regulator) + return -EINVAL; + + if (enable) + ret = iqs253_set(chip, NORMAL_MODE); + else + chip->mode = MODE_NONE; + + return ret ? ret : 1; +} + +/* + * chan_regulator_enable is used to enable regulators used by + * particular channel. + * chan_enable actually configures various registers to activate + * a particular channel. + */ +static const struct iio_chan_spec_ext_info iqs253_ext_info[] = { + { + .name = "regulator_enable", + .write = iqs253_chan_regulator_enable, + }, + { + .name = "enable", + .write = iqs253_chan_normal_mode_enable, + }, + { + }, +}; + +static const struct iio_chan_spec iqs253_channels[] = { + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .ext_info = iqs253_ext_info, + }, +}; + +static int iqs253_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long mask) +{ + struct iqs253_chip *chip = iio_priv(indio_dev); + int ret; + + if (chip->mode != NORMAL_MODE) + return -EINVAL; + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + iqs253_i2c_hand_shake(chip); + ret = i2c_smbus_read_byte_data(chip->client, PROX_STATUS); + chip->value = -1; + if (ret >= 0) { + if ((ret >= 0) && (chip->mode == NORMAL_MODE)) { + ret = ret & PROXIMITY_ONLY; + /* + * if both channel detect proximity => distance = 0; + * if only channel2 detects proximity => distance = 1; + * if no channel detects proximity => distance = 2; + */ + chip->value = (ret == 0x03) ? 0 : ret ? 1 : 2; + } + } + if (chip->value == -1) + return -EINVAL; + + *val = chip->value; /* cm */ + + return IIO_VAL_INT; +} + +static IIO_CONST_ATTR(vendor, "Azoteq"); +static IIO_CONST_ATTR(in_proximity_integration_time, + "16000000"); /* 16 msec */ +static IIO_CONST_ATTR(in_proximity_max_range, "2"); /* cm */ +static IIO_CONST_ATTR(in_proximity_power_consumed, "1.67"); /* mA */ + +static struct attribute *iqs253_attrs[] = { + &iio_const_attr_vendor.dev_attr.attr, + &iio_const_attr_in_proximity_integration_time.dev_attr.attr, + &iio_const_attr_in_proximity_max_range.dev_attr.attr, + &iio_const_attr_in_proximity_power_consumed.dev_attr.attr, + NULL +}; + +static struct attribute_group iqs253_attr_group = { + .name = "iqs253", + .attrs = iqs253_attrs +}; + +static struct iio_info iqs253_iio_info = { + .driver_module = THIS_MODULE, + .read_raw = &iqs253_read_raw, + .attrs = &iqs253_attr_group, +}; + +#ifdef CONFIG_PM_SLEEP +static int iqs253_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct iqs253_chip *chip = iio_priv(indio_dev); + int ret = 0; + + if (!chip->using_regulator) + ret = regulator_enable(chip->vddhi); + + if (ret) { + dev_err(&chip->client->dev, + "idname:%s func:%s line:%d regulator enable fails\n", + chip->id->name, __func__, __LINE__); + return ret; + } + + ret = iqs253_set(chip, STYLUS_MODE); + if (ret) { + dev_err(&chip->client->dev, + "idname:%s func:%s line:%d can not enable stylus mode\n", + chip->id->name, __func__, __LINE__); + return ret; + } + return tegra_pm_irq_set_wake(tegra_gpio_to_wake(chip->wake_gpio), 1); +} + +static int iqs253_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct iqs253_chip *chip = iio_priv(indio_dev); + int ret = 0; + + if (chip->using_regulator) { + ret = iqs253_set(chip, NORMAL_MODE); + } else { + chip->mode = MODE_NONE; + ret = regulator_disable(chip->vddhi); + } + + if (ret) { + dev_err(&chip->client->dev, + "idname:%s func:%s line:%d regulator enable fails\n", + chip->id->name, __func__, __LINE__); + return ret; + } + + return ret; +} + +static SIMPLE_DEV_PM_OPS(iqs253_pm_ops, iqs253_suspend, iqs253_resume); +#define IQS253_PM_OPS (&iqs253_pm_ops) +#else +#define IQS253_PM_OPS NULL +#endif + +static int iqs253_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct iqs253_chip *iqs253_chip; + struct iio_dev *indio_dev; + int rdy_gpio = -1, wake_gpio; + + rdy_gpio = of_get_named_gpio(client->dev.of_node, "rdy-gpio", 0); + if (rdy_gpio == -EPROBE_DEFER) + return -EPROBE_DEFER; + + if (!gpio_is_valid(rdy_gpio)) + return -EINVAL; + + wake_gpio = of_get_named_gpio(client->dev.of_node, "wake-gpio", 0); + if (wake_gpio == -EPROBE_DEFER) + return -EPROBE_DEFER; + + if (!gpio_is_valid(wake_gpio)) + return -EINVAL; + + indio_dev = iio_device_alloc(sizeof(*iqs253_chip)); + if (!indio_dev) + return -ENOMEM; + + i2c_set_clientdata(client, indio_dev); + iqs253_chip = iio_priv(indio_dev); + + iqs253_chip->ls_spec = of_get_ls_spec(&client->dev); + if (!iqs253_chip->ls_spec) { + dev_err(&client->dev, + "devname:%s func:%s line:%d invalid meta data\n", + id->name, __func__, __LINE__); + return -ENODATA; + } + + fill_ls_attrs(iqs253_chip->ls_spec, iqs253_attrs); + indio_dev->info = &iqs253_iio_info; + indio_dev->channels = iqs253_channels; + indio_dev->num_channels = 1; + indio_dev->name = id->name; + indio_dev->dev.parent = &client->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&client->dev, + "devname:%s func:%s line:%d iio_device_register fail\n", + id->name, __func__, __LINE__); + goto err_iio_register; + } + + iqs253_chip->client = client; + iqs253_chip->id = id; + iqs253_chip->mode = MODE_NONE; + iqs253_chip->vddhi = devm_regulator_get(&client->dev, "vddhi"); + if (IS_ERR(iqs253_chip->vddhi)) { + dev_err(&client->dev, + "devname:%s func:%s regulator vddhi not found\n", + id->name, __func__); + goto err_regulator_get; + } + + ret = gpio_request(rdy_gpio, "iqs253"); + if (ret) { + dev_err(&client->dev, + "devname:%s func:%s regulator vddhi not found\n", + id->name, __func__); + goto err_gpio_request; + } + iqs253_chip->rdy_gpio = rdy_gpio; + iqs253_chip->wake_gpio = wake_gpio; + + ret = regulator_enable(iqs253_chip->vddhi); + if (ret) { + dev_err(&client->dev, + "devname:%s func:%s regulator enable failed\n", + id->name, __func__); + goto err_gpio_request; + } + + iqs253_i2c_hand_shake(iqs253_chip); + ret = i2c_smbus_read_byte_data(iqs253_chip->client, 0); + if (ret != IQS253_PROD_ID) { + dev_err(&client->dev, + "devname:%s func:%s device not present\n", + id->name, __func__); + goto err_gpio_request; + + } + + ret = regulator_disable(iqs253_chip->vddhi); + if (ret) { + dev_err(&client->dev, + "devname:%s func:%s regulator disable failed\n", + id->name, __func__); + goto err_gpio_request; + } + + + dev_info(&client->dev, "devname:%s func:%s line:%d probe success\n", + id->name, __func__, __LINE__); + + return 0; + +err_gpio_request: +err_regulator_get: + iio_device_unregister(indio_dev); +err_iio_register: + iio_device_free(indio_dev); + + dev_err(&client->dev, "devname:%s func:%s line:%d probe failed\n", + id->name, __func__, __LINE__); + return ret; +} + +static int iqs253_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct iqs253_chip *chip = iio_priv(indio_dev); + gpio_free(chip->rdy_gpio); + iio_device_unregister(indio_dev); + iio_device_free(indio_dev); + return 0; +} + +static void iqs253_shutdown(struct i2c_client *client) +{ + iqs253_remove(client); +} + +static const struct i2c_device_id iqs253_id[] = { + {"iqs253", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, iqs253_id); + +static const struct of_device_id iqs253_of_match[] = { + { .compatible = "azoteq,iqs253", }, + { }, +}; +MODULE_DEVICE_TABLE(of, iqs253_of_match); + +static struct i2c_driver iqs253_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "iqs253", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(iqs253_of_match), + .pm = IQS253_PM_OPS, + }, + .probe = iqs253_probe, + .remove = iqs253_remove, + .shutdown = iqs253_shutdown, + .id_table = iqs253_id, +}; + +module_i2c_driver(iqs253_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("IQS253 Driver"); +MODULE_AUTHOR("Sri Krishna chowdary <schowdary@nvidia.com>"); |
