summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSri Krishna chowdary <schowdary@nvidia.com>2014-01-29 10:18:03 +0530
committerSachin Nikam <snikam@nvidia.com>2014-02-10 00:36:15 -0800
commit2c5a25157ef38621d20153a0bcdd295647e82047 (patch)
treea3fe1aee69e5b6b10650cea69dea6157d6791c54
parent58556ad388017d68ddcf540eea2c3047adf69550 (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.txt24
-rw-r--r--drivers/staging/iio/light/Kconfig12
-rw-r--r--drivers/staging/iio/light/Makefile1
-rw-r--r--drivers/staging/iio/light/iqs253.c568
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>");