]> rtime.felk.cvut.cz Git - sojka/nv-tegra/linux-3.10.git/commitdiff
staging: iio: light: IQS253 capacitive sensor
authorSri Krishna chowdary <schowdary@nvidia.com>
Wed, 29 Jan 2014 04:48:03 +0000 (10:18 +0530)
committerSachin Nikam <snikam@nvidia.com>
Mon, 10 Feb 2014 08:36:15 +0000 (00:36 -0800)
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>
Documentation/devicetree/bindings/staging/iio/light/iqs253-ps.txt [new file with mode: 0644]
drivers/staging/iio/light/Kconfig
drivers/staging/iio/light/Makefile
drivers/staging/iio/light/iqs253.c [new file with mode: 0644]

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 (file)
index 0000000..7c536ef
--- /dev/null
@@ -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>;
+       };
index c8c61cee5579adaace061aa87288d713124b92ac..9a1f35fc98815f1e81e41d2cc03cb930b4708ebd 100644 (file)
@@ -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
index d0244a5fb0ca1cf2826ee1b07adcdbf741bbe9c8..7afa1610951478cbfe4bbbb854a13e794f0d1cbd 100644 (file)
@@ -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 (file)
index 0000000..6d8c882
--- /dev/null
@@ -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>");