aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/input/misc
diff options
context:
space:
mode:
authorcodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
committercodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
commitc6da2cfeb05178a11c6d062a06f8078150ee492f (patch)
treef3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/input/misc
parentc6d7c4dbff353eac7919342ae6b3299a378160a6 (diff)
downloadkernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2
samsung update 1
Diffstat (limited to 'drivers/input/misc')
-rw-r--r--drivers/input/misc/Kconfig39
-rw-r--r--drivers/input/misc/Makefile6
-rwxr-xr-xdrivers/input/misc/ak8975-reg.h48
-rwxr-xr-xdrivers/input/misc/ak8975.c863
-rwxr-xr-xdrivers/input/misc/bh1721fvc.c826
-rwxr-xr-xdrivers/input/misc/gp2a.c1040
-rwxr-xr-xdrivers/input/misc/gp2a.h162
-rw-r--r--drivers/input/misc/gpio_axis.c192
-rw-r--r--drivers/input/misc/gpio_event.c260
-rw-r--r--drivers/input/misc/gpio_input.c376
-rw-r--r--drivers/input/misc/gpio_matrix.c441
-rw-r--r--drivers/input/misc/gpio_output.c97
-rw-r--r--drivers/input/misc/keychord.c387
13 files changed, 4735 insertions, 2 deletions
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 45dc6aa..8a356ef 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -11,6 +11,14 @@ menuconfig INPUT_MISC
If unsure, say Y.
if INPUT_MISC
+config SENSORS_BH1721FVC
+ tristate "ROHM BH1721FVC ambient light sensor"
+ depends on I2C
+ help
+ Say Y here if you have ROHM BH1721FVC hooked to an I2C bus.
+
+ This option eanbles the light sensor device driver for
+ the ROHM BH1721FVC chip.
config INPUT_88PM860X_ONKEY
tristate "88PM860x ONKEY support"
@@ -193,6 +201,17 @@ config INPUT_ATI_REMOTE2
To compile this driver as a module, choose M here: the module will be
called ati_remote2.
+config INPUT_KEYCHORD
+ tristate "Key chord input driver support"
+ help
+ Say Y here if you want to enable the key chord driver
+ accessible at /dev/keychord. This driver can be used
+ for receiving notifications when client specified key
+ combinations are pressed.
+
+ To compile this driver as a module, choose M here: the
+ module will be called keychord.
+
config INPUT_KEYSPAN_REMOTE
tristate "Keyspan DMR USB remote control (EXPERIMENTAL)"
depends on EXPERIMENTAL
@@ -294,6 +313,11 @@ config INPUT_SGI_BTNS
To compile this driver as a module, choose M here: the
module will be called sgi_btns.
+config INPUT_GPIO
+ tristate "GPIO driver support"
+ help
+ Say Y here if you want to support gpio based keys, wheels etc...
+
config HP_SDC_RTC
tristate "HP SDC Real Time Clock"
depends on (GSC || HP300) && SERIO
@@ -439,7 +463,20 @@ config INPUT_ADXL34X_SPI
Say Y here if you have ADXL345/6 hooked to a SPI bus.
To compile this driver as a module, choose M here: the
- module will be called adxl34x-spi.
+
+config OPTICAL_GP2A
+ depends on I2C && GENERIC_GPIO
+ tristate "GP2A ambient light and proximity input device"
+ default n
+ help
+ This option enables proximity & light sensors using gp2a driver.
+
+config OPTICAL_WAKE_ENABLE
+ depends on I2C && GENERIC_GPIO
+ tristate "Enabled ambient light and proximity to wake up gpio"
+ default n
+ help
+ This option enables proximity & light sensors using wake up gpio.
config INPUT_CMA3000
tristate "VTI CMA3000 Tri-axis accelerometer"
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 38efb2c..1d6cd26 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -22,8 +22,10 @@ obj-$(CONFIG_INPUT_CMA3000) += cma3000_d0x.o
obj-$(CONFIG_INPUT_CMA3000_I2C) += cma3000_d0x_i2c.o
obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o
obj-$(CONFIG_INPUT_DM355EVM) += dm355evm_keys.o
+obj-$(CONFIG_INPUT_GPIO) += gpio_event.o gpio_matrix.o gpio_input.o gpio_output.o gpio_axis.o
obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o
obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.o
+obj-$(CONFIG_INPUT_KEYCHORD) += keychord.o
obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o
obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o
obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o
@@ -45,4 +47,6 @@ obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o
obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o
obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o
obj-$(CONFIG_INPUT_YEALINK) += yealink.o
-
+obj-$(CONFIG_SENSORS_BH1721FVC) += bh1721fvc.o
+obj-$(CONFIG_MPU_SENSORS_AK8975) += ak8975.o
+obj-$(CONFIG_OPTICAL_GP2A) += gp2a.o \ No newline at end of file
diff --git a/drivers/input/misc/ak8975-reg.h b/drivers/input/misc/ak8975-reg.h
new file mode 100755
index 0000000..1a78a27
--- /dev/null
+++ b/drivers/input/misc/ak8975-reg.h
@@ -0,0 +1,48 @@
+/* linux/drivers/misc/ak8975-reg.h
+ *
+ * Copyright (c) 2010 Samsung Electronics Co., Ltd.
+ * http://www.samsung.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.
+*/
+#ifndef __AK8975_REG__
+#define __AK8975_REG__
+
+/* Compass device dependent definition */
+#define AK8975_MODE_SNG_MEASURE 0x01
+#define AK8975_MODE_SELF_TEST 0x08
+#define AK8975_MODE_FUSE_ACCESS 0x0F
+#define AK8975_MODE_POWER_DOWN 0x00
+
+/* Rx buffer size. i.e ST,TMPS,H1X,H1Y,H1Z*/
+#define SENSOR_DATA_SIZE 8
+
+/* Read/Write buffer size.*/
+#define RWBUF_SIZE 16
+
+/* AK8975 register address */
+#define AK8975_REG_WIA 0x00
+#define AK8975_REG_INFO 0x01
+#define AK8975_REG_ST1 0x02
+#define AK8975_REG_HXL 0x03
+#define AK8975_REG_HXH 0x04
+#define AK8975_REG_HYL 0x05
+#define AK8975_REG_HYH 0x06
+#define AK8975_REG_HZL 0x07
+#define AK8975_REG_HZH 0x08
+#define AK8975_REG_ST2 0x09
+#define AK8975_REG_CNTL 0x0A
+#define AK8975_REG_RSV 0x0B
+#define AK8975_REG_ASTC 0x0C
+#define AK8975_REG_TS1 0x0D
+#define AK8975_REG_TS2 0x0E
+#define AK8975_REG_I2CDIS 0x0F
+
+/* AK8975 fuse-rom address */
+#define AK8975_FUSE_ASAX 0x10
+#define AK8975_FUSE_ASAY 0x11
+#define AK8975_FUSE_ASAZ 0x12
+
+#endif /* __AK8975_REG__ */
diff --git a/drivers/input/misc/ak8975.c b/drivers/input/misc/ak8975.c
new file mode 100755
index 0000000..d95f229
--- /dev/null
+++ b/drivers/input/misc/ak8975.c
@@ -0,0 +1,863 @@
+/*
+ * Copyright (C) 2010, Samsung Electronics Co. Ltd. All Rights Reserved.
+ *
+ * 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.
+ *
+*/
+
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/i2c/ak8975.h>
+#include <linux/completion.h>
+#include "ak8975-reg.h"
+
+#define VENDOR_NAME "AKM"
+#define CHIP_NAME "AK8975C"
+
+
+#define AK8975_REG_CNTL 0x0A
+#define REG_CNTL_MODE_SHIFT 0
+#define REG_CNTL_MODE_MASK (0xF << REG_CNTL_MODE_SHIFT)
+#define REG_CNTL_MODE_POWER_DOWN 0
+#define REG_CNTL_MODE_ONCE 0x01
+#define REG_CNTL_MODE_SELF_TEST 0x08
+#define REG_CNTL_MODE_FUSE_ROM 0x0F
+
+#define AK8975_REG_RSVC 0x0B
+#define AK8975_REG_ASTC 0x0C
+#define AK8975_REG_TS1 0x0D
+#define AK8975_REG_TS2 0x0E
+#define AK8975_REG_I2CDIS 0x0F
+#define AK8975_REG_ASAX 0x10
+#define AK8975_REG_ASAY 0x11
+#define AK8975_REG_ASAZ 0x12
+
+#define USING_IRQ 0
+
+struct akm8975_data {
+ struct i2c_client *this_client;
+ struct akm8975_platform_data *pdata;
+ struct mutex lock;
+ struct miscdevice akmd_device;
+ struct completion data_ready;
+ struct device *dev;
+ wait_queue_head_t state_wq;
+ u8 asa[3];
+#if USING_IRQ
+ int irq;
+#endif
+};
+
+#ifdef FACTORY_TEST
+static bool ak8975_selftest_passed;
+static s16 sf_x, sf_y, sf_z;
+#endif
+
+extern int sensors_register(struct device *dev, void *drvdata,
+ struct device_attribute *attributes[], char *name);
+
+static s32 akm8975_ecs_set_mode_power_down(struct akm8975_data *akm)
+{
+ s32 ret;
+ ret = i2c_smbus_write_byte_data(akm->this_client,
+ AK8975_REG_CNTL, AK8975_MODE_POWER_DOWN);
+ return ret;
+}
+
+static int akm8975_ecs_set_mode(struct akm8975_data *akm, char mode)
+{
+ s32 ret;
+
+ switch (mode) {
+ case AK8975_MODE_SNG_MEASURE:
+ ret = i2c_smbus_write_byte_data(akm->this_client,
+ AK8975_REG_CNTL, AK8975_MODE_SNG_MEASURE);
+ break;
+ case AK8975_MODE_FUSE_ACCESS:
+ ret = i2c_smbus_write_byte_data(akm->this_client,
+ AK8975_REG_CNTL, AK8975_MODE_FUSE_ACCESS);
+ break;
+ case AK8975_MODE_POWER_DOWN:
+ ret = akm8975_ecs_set_mode_power_down(akm);
+ break;
+ case AK8975_MODE_SELF_TEST:
+ ret = i2c_smbus_write_byte_data(akm->this_client,
+ AK8975_REG_CNTL, AK8975_MODE_SELF_TEST);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ /* Wait at least 300us after changing mode. */
+ udelay(300);
+
+ return 0;
+}
+
+static int akmd_copy_in(unsigned int cmd, void __user *argp,
+ void *buf, size_t buf_size)
+{
+ if (!(cmd & IOC_IN))
+ return 0;
+ if (_IOC_SIZE(cmd) > buf_size)
+ return -EINVAL;
+ if (copy_from_user(buf, argp, _IOC_SIZE(cmd)))
+ return -EFAULT;
+ return 0;
+}
+
+static int akmd_copy_out(unsigned int cmd, void __user *argp,
+ void *buf, size_t buf_size)
+{
+ if (!(cmd & IOC_OUT))
+ return 0;
+ if (_IOC_SIZE(cmd) > buf_size)
+ return -EINVAL;
+ if (copy_to_user(argp, buf, _IOC_SIZE(cmd)))
+ return -EFAULT;
+ return 0;
+}
+
+#if USING_IRQ
+static void akm8975_disable_irq(struct akm8975_data *akm)
+{
+ disable_irq(akm->irq);
+ if (try_wait_for_completion(&akm->data_ready)) {
+ /* we actually got the interrupt before we could disable it
+ * so we need to enable again to undo our disable since the
+ * irq_handler already disabled it
+ */
+ enable_irq(akm->irq);
+ }
+}
+
+static irqreturn_t akm8975_irq_handler(int irq, void *data)
+{
+ struct akm8975_data *akm = data;
+ disable_irq_nosync(irq);
+ complete(&akm->data_ready);
+ return IRQ_HANDLED;
+}
+#endif
+
+static int akm8975_wait_for_data_ready(struct akm8975_data *akm)
+{
+#if USING_IRQ
+ int data_ready = gpio_get_value(akm->pdata->gpio_data_ready_int);
+ int err;
+
+ pr_info("1 akm8975_wait_for_data_ready: %d", data_ready);
+ if (data_ready)
+ return 0;
+
+ enable_irq(akm->irq);
+
+ err = wait_for_completion_timeout(&akm->data_ready, 5*HZ);
+ data_ready = gpio_get_value(akm->pdata->gpio_data_ready_int);
+ pr_info("2 akm8975_wait_for_data_ready: %d", data_ready);
+
+ if (err > 0)
+ return 0;
+
+ akm8975_disable_irq(akm);
+
+ if (err == 0) {
+ pr_err("akm: wait timed out");
+ return -ETIMEDOUT;
+ }
+
+ pr_err("akm: wait restart");
+ return err;
+#else
+ int err;
+ u8 buf;
+
+ int count = 10;
+
+ while (1) {
+ msleep(20);
+ err = i2c_smbus_read_i2c_block_data(akm->this_client,
+ AK8975_REG_ST1, sizeof(buf), &buf);
+
+ if (err != sizeof(buf)) {
+ pr_err("%s: read data over i2c failed", __func__);
+ return -1;
+ }
+
+ if (buf&0x1)
+ break;
+
+ count--;
+ if (!count)
+ break;
+ }
+
+ return 0;
+#endif
+
+}
+
+static ssize_t akmd_read(struct file *file, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct akm8975_data *akm = container_of(file->private_data,
+ struct akm8975_data, akmd_device);
+ short x = 0, y = 0, z = 0;
+ int ret;
+ u8 data[8];
+
+ mutex_lock(&akm->lock);
+ ret = akm8975_ecs_set_mode(akm, AK8975_MODE_SNG_MEASURE);
+ if (ret) {
+ mutex_unlock(&akm->lock);
+ goto done;
+ }
+ ret = akm8975_wait_for_data_ready(akm);
+ if (ret) {
+ mutex_unlock(&akm->lock);
+ goto done;
+ }
+ ret = i2c_smbus_read_i2c_block_data(akm->this_client, AK8975_REG_ST1,
+ sizeof(data), data);
+ mutex_unlock(&akm->lock);
+
+ if (ret != sizeof(data)) {
+ pr_err("%s: failed to read %d bytes of mag data",
+ __func__, sizeof(data));
+ goto done;
+ }
+
+ if (data[0] & 0x01) {
+ x = (data[2] << 8) + data[1];
+ y = (data[4] << 8) + data[3];
+ z = (data[6] << 8) + data[5];
+ } else
+ pr_err("%s: invalid raw data(st1 = %d)",
+ __func__, data[0] & 0x01);
+
+done:
+ return sprintf(buf, "%d,%d,%d\n", x, y, z);
+}
+
+static long akmd_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ struct akm8975_data *akm = container_of(file->private_data,
+ struct akm8975_data, akmd_device);
+ int ret;
+ #ifdef MAGNETIC_LOGGING
+ short x, y, z;
+ #endif
+ union {
+ char raw[RWBUF_SIZE];
+ int status;
+ char mode;
+ u8 data[8];
+ } rwbuf;
+
+ ret = akmd_copy_in(cmd, argp, rwbuf.raw, sizeof(rwbuf));
+ if (ret)
+ return ret;
+
+ switch (cmd) {
+ case ECS_IOCTL_WRITE:
+ if ((rwbuf.raw[0] < 2) || (rwbuf.raw[0] > (RWBUF_SIZE - 1)))
+ return -EINVAL;
+ if (copy_from_user(&rwbuf.raw[2], argp+2, rwbuf.raw[0]-1))
+ return -EFAULT;
+
+ ret = i2c_smbus_write_i2c_block_data(akm->this_client,
+ rwbuf.raw[1],
+ rwbuf.raw[0] - 1,
+ &rwbuf.raw[2]);
+ break;
+ case ECS_IOCTL_READ:
+ if ((rwbuf.raw[0] < 1) || (rwbuf.raw[0] > (RWBUF_SIZE - 1)))
+ return -EINVAL;
+
+ ret = i2c_smbus_read_i2c_block_data(akm->this_client,
+ rwbuf.raw[1],
+ rwbuf.raw[0],
+ &rwbuf.raw[1]);
+ if (ret < 0)
+ return ret;
+ if (copy_to_user(argp+1, rwbuf.raw+1, rwbuf.raw[0]))
+ return -EFAULT;
+ return 0;
+ case ECS_IOCTL_SET_MODE:
+ mutex_lock(&akm->lock);
+ ret = akm8975_ecs_set_mode(akm, rwbuf.mode);
+ mutex_unlock(&akm->lock);
+ break;
+ case ECS_IOCTL_GETDATA:
+ mutex_lock(&akm->lock);
+ ret = akm8975_wait_for_data_ready(akm);
+ if (ret) {
+ mutex_unlock(&akm->lock);
+ return ret;
+ }
+
+ ret = i2c_smbus_read_i2c_block_data(akm->this_client,
+ AK8975_REG_ST1,
+ sizeof(rwbuf.data),
+ rwbuf.data);
+
+ #ifdef MAGNETIC_LOGGING
+ x = (rwbuf.data[2] << 8) + rwbuf.data[1];
+ y = (rwbuf.data[4] << 8) + rwbuf.data[3];
+ z = (rwbuf.data[6] << 8) + rwbuf.data[5];
+
+ pr_info("%s: raw x = %d, y = %d, z = %d",
+ __func__, x, y, z);
+ #endif
+
+ mutex_unlock(&akm->lock);
+ if (ret != sizeof(rwbuf.data)) {
+ pr_err("%s : failed to read %d bytes of mag data",
+ __func__, sizeof(rwbuf.data));
+ return -EIO;
+ }
+ break;
+ default:
+ return -ENOTTY;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ return akmd_copy_out(cmd, argp, rwbuf.raw, sizeof(rwbuf));
+}
+
+static const struct file_operations akmd_fops = {
+ .owner = THIS_MODULE,
+ .open = nonseekable_open,
+ .read = akmd_read,
+ .unlocked_ioctl = akmd_ioctl,
+};
+
+#if USING_IRQ
+
+static int akm8975_setup_irq(struct akm8975_data *akm)
+{
+ int rc = -EIO;
+ struct akm8975_platform_data *pdata = akm->pdata;
+ int irq;
+
+ if (akm->this_client->irq)
+ irq = akm->this_client->irq;
+ else {
+ rc = gpio_request(pdata->gpio_data_ready_int, "gpio_akm_int");
+ if (rc < 0) {
+ pr_err("%s: gpio %d request failed (%d)",
+ __func__, pdata->gpio_data_ready_int, rc);
+ return rc;
+ }
+
+ rc = gpio_direction_input(pdata->gpio_data_ready_int);
+ if (rc < 0) {
+ pr_err("%s: failed to set gpio %d as input (%d)",
+ __func__, pdata->gpio_data_ready_int, rc);
+ goto err_request_irq;
+ }
+
+ irq = gpio_to_irq(pdata->gpio_data_ready_int);
+ }
+ /* trigger high so we don't miss initial interrupt if it
+ * is already pending
+ */
+ rc = request_irq(irq, akm8975_irq_handler, IRQF_TRIGGER_RISING,
+ "akm_int", akm);
+ if (rc < 0) {
+ pr_err("%s: request_irq(%d) failed for gpio %d (%d)",
+ __func__, irq,
+ pdata->gpio_data_ready_int, rc);
+ goto err_request_irq;
+ }
+
+ /* start with interrupt disabled until the driver is enabled */
+ akm->irq = irq;
+ akm8975_disable_irq(akm);
+
+ goto done;
+
+err_request_irq:
+ gpio_free(pdata->gpio_data_ready_int);
+done:
+ return rc;
+}
+
+#endif
+
+#if (defined DEBUG) || (defined FACTORY_TEST)
+#ifdef FACTORY_TEST
+static bool ak8975_selftest_passed;
+static s16 sf_x, sf_y, sf_z;
+#endif
+
+static void ak8975c_selftest(struct akm8975_data *ak_data)
+{
+ u8 buf[6];
+ s16 x, y, z;
+
+ /* read device info */
+ i2c_smbus_read_i2c_block_data(ak_data->this_client,
+ AK8975_REG_WIA, 2, buf);
+ pr_info("%s: device id = 0x%x, info = 0x%x",
+ __func__, buf[0], buf[1]);
+
+ /* set ATSC self test bit to 1 */
+ i2c_smbus_write_byte_data(ak_data->this_client,
+ AK8975_REG_ASTC, 0x40);
+
+ /* start self test */
+ i2c_smbus_write_byte_data(ak_data->this_client,
+ AK8975_REG_CNTL,
+ REG_CNTL_MODE_SELF_TEST);
+
+ /* wait for data ready */
+ while (1) {
+ msleep(20);
+ if (i2c_smbus_read_byte_data(ak_data->this_client,
+ AK8975_REG_ST1) == 1) {
+ break;
+ }
+ }
+
+ i2c_smbus_read_i2c_block_data(ak_data->this_client,
+ AK8975_REG_HXL, sizeof(buf), buf);
+
+ /* set ATSC self test bit to 0 */
+ i2c_smbus_write_byte_data(ak_data->this_client,
+ AK8975_REG_ASTC, 0x00);
+
+ x = buf[0] | (buf[1] << 8);
+ y = buf[2] | (buf[3] << 8);
+ z = buf[4] | (buf[5] << 8);
+
+ /* Hadj = (H*(Asa+128))/256 */
+ x = (x*(ak_data->asa[0] + 128)) >> 8;
+ y = (y*(ak_data->asa[1] + 128)) >> 8;
+ z = (z*(ak_data->asa[2] + 128)) >> 8;
+
+ pr_info("%s: self test x = %d, y = %d, z = %d",
+ __func__, x, y, z);
+ if ((x >= -100) && (x <= 100))
+ pr_info("%s: x passed self test, expect -100<=x<=100",
+ __func__);
+ else
+ pr_info("%s: x failed self test, expect -100<=x<=100",
+ __func__);
+ if ((y >= -100) && (y <= 100))
+ pr_info("%s: y passed self test, expect -100<=y<=100",
+ __func__);
+ else
+ pr_info("%s: y failed self test, expect -100<=y<=100",
+ __func__);
+ if ((z >= -1000) && (z <= -300))
+ pr_info("%s: z passed self test, expect -1000<=z<=-300",
+ __func__);
+ else
+ pr_info("%s: z failed self test, expect -1000<=z<=-300",
+ __func__);
+
+#ifdef FACTORY_TEST
+ if (((x >= -100) && (x <= 100)) && ((y >= -100) && (y <= 100)) &&
+ ((z >= -1000) && (z <= -300)))
+ ak8975_selftest_passed = 1;
+
+ sf_x = x;
+ sf_y = y;
+ sf_z = z;
+#endif
+}
+
+static ssize_t ak8975c_get_asa(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct akm8975_data *ak_data = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d, %d, %d\n", ak_data->asa[0],
+ ak_data->asa[1], ak_data->asa[2]);
+}
+
+static ssize_t ak8975c_get_selftest(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ ak8975c_selftest(dev_get_drvdata(dev));
+ return sprintf(buf, "%d, %d, %d, %d\n",
+ ak8975_selftest_passed, sf_x, sf_y, sf_z);
+}
+
+static ssize_t ak8975c_check_registers(struct device *dev,
+ struct device_attribute *attr, char *strbuf)
+{
+ struct akm8975_data *ak_data = dev_get_drvdata(dev);
+ u8 buf[13];
+
+ /* power down */
+ i2c_smbus_write_byte_data(ak_data->this_client,
+ AK8975_REG_CNTL, REG_CNTL_MODE_POWER_DOWN);
+
+ /* get the value */
+ i2c_smbus_read_i2c_block_data(ak_data->this_client,
+ AK8975_REG_WIA, 11, buf);
+
+ buf[11] = i2c_smbus_read_byte_data(ak_data->this_client,
+ AK8975_REG_ASTC);
+ buf[12] = i2c_smbus_read_byte_data(ak_data->this_client,
+ AK8975_REG_I2CDIS);
+
+ return sprintf(strbuf, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
+ buf[0], buf[1], buf[2], buf[3], buf[4], buf[5],
+ buf[6], buf[7], buf[8], buf[9], buf[10], buf[11],
+ buf[12]);
+}
+
+static ssize_t ak8975c_check_cntl(struct device *dev,
+ struct device_attribute *attr, char *strbuf)
+{
+ struct akm8975_data *ak_data = dev_get_drvdata(dev);
+ u8 buf;
+ int err;
+
+ /* power down */
+ err = i2c_smbus_write_byte_data(ak_data->this_client,
+ AK8975_REG_CNTL, REG_CNTL_MODE_POWER_DOWN);
+
+ buf = i2c_smbus_read_byte_data(ak_data->this_client,
+ AK8975_REG_CNTL);
+
+ return sprintf(strbuf, "%s\n", (!buf ? "OK" : "NG"));
+}
+
+static ssize_t ak8975c_get_status(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct akm8975_data *ak_data = dev_get_drvdata(dev);
+ int success;
+
+ if ((ak_data->asa[0] == 0) | (ak_data->asa[0] == 0xff)
+ | (ak_data->asa[1] == 0) | (ak_data->asa[1] == 0xff)
+ | (ak_data->asa[2] == 0) | (ak_data->asa[2] == 0xff))
+ success = 0;
+ else
+ success = 1;
+
+ return sprintf(buf, "%s\n", (success ? "OK" : "NG"));
+}
+
+static ssize_t ak8975_adc(struct device *dev,
+ struct device_attribute *attr, char *strbuf)
+{
+ struct akm8975_data *ak_data = dev_get_drvdata(dev);
+ u8 buf[8];
+ s16 x, y, z;
+ int err, success;
+
+ /* start ADC conversion */
+ err = i2c_smbus_write_byte_data(ak_data->this_client,
+ AK8975_REG_CNTL, REG_CNTL_MODE_ONCE);
+
+ /* wait for ADC conversion to complete */
+ err = akm8975_wait_for_data_ready(ak_data);
+ if (err) {
+ pr_err("%s: wait for data ready failed", __func__);
+ return err;
+ }
+ msleep(20);
+ /* get the value and report it */
+ err = i2c_smbus_read_i2c_block_data(ak_data->this_client,
+ AK8975_REG_ST1, sizeof(buf), buf);
+ if (err != sizeof(buf)) {
+ pr_err("%s: read data over i2c failed", __func__);
+ return err;
+ }
+
+ /* buf[0] is status1, buf[7] is status2 */
+ if ((buf[0] == 0) | (buf[7] == 1))
+ success = 0;
+ else
+ success = 1;
+
+ x = buf[1] | (buf[2] << 8);
+ y = buf[3] | (buf[4] << 8);
+ z = buf[5] | (buf[6] << 8);
+
+ pr_info("%s: raw x = %d, y = %d, z = %d", __func__, x, y, z);
+ return sprintf(strbuf, "%s, %d, %d, %d\n", (success ? "OK" : "NG"),
+ x, y, z);
+}
+#endif
+
+static ssize_t ak8975_show_raw_data(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct akm8975_data *akm = dev_get_drvdata(dev);
+ short x = 0, y = 0, z = 0;
+ int ret;
+ u8 data[8] = {0,};
+
+ mutex_lock(&akm->lock);
+ ret = akm8975_ecs_set_mode(akm, AK8975_MODE_SNG_MEASURE);
+ if (ret) {
+ mutex_unlock(&akm->lock);
+ goto done;
+ }
+ ret = akm8975_wait_for_data_ready(akm);
+ if (ret) {
+ mutex_unlock(&akm->lock);
+ goto done;
+ }
+ ret = i2c_smbus_read_i2c_block_data(akm->this_client, AK8975_REG_ST1,
+ sizeof(data), data);
+ mutex_unlock(&akm->lock);
+
+ if (ret != sizeof(data)) {
+ pr_err("%s: failed to read %d bytes of mag data\n",
+ __func__, sizeof(data));
+ goto done;
+ }
+
+ if (data[0] & 0x01) {
+ x = (data[2] << 8) + data[1];
+ y = (data[4] << 8) + data[3];
+ z = (data[6] << 8) + data[5];
+ } else
+ pr_err("%s: invalid raw data(st1 = %d)",
+ __func__, data[0] & 0x01);
+
+done:
+ return sprintf(buf, "%d,%d,%d\n", x, y, z);
+}
+static ssize_t get_vendor_name(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", VENDOR_NAME);
+}
+
+static ssize_t get_chip_name(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", CHIP_NAME);
+}
+static DEVICE_ATTR(vendor, S_IRUGO, get_vendor_name, NULL);
+static DEVICE_ATTR(name, S_IRUGO, get_chip_name, NULL);
+
+static DEVICE_ATTR(raw_data, 0664,
+ ak8975_show_raw_data, NULL);
+
+#ifdef FACTORY_TEST
+static DEVICE_ATTR(asa, 0664,
+ ak8975c_get_asa, NULL);
+static DEVICE_ATTR(selftest, 0664,
+ ak8975c_get_selftest, NULL);
+static DEVICE_ATTR(chk_registers, 0664,
+ ak8975c_check_registers, NULL);
+static DEVICE_ATTR(dac, 0664,
+ ak8975c_check_cntl, NULL);
+static DEVICE_ATTR(status, 0664,
+ ak8975c_get_status, NULL);
+static DEVICE_ATTR(adc, 0664,
+ ak8975_adc, NULL);
+
+static struct device_attribute *magnetic_sensor_attrs[] = {
+ &dev_attr_raw_data,
+ &dev_attr_asa,
+ &dev_attr_selftest,
+ &dev_attr_chk_registers,
+ &dev_attr_dac,
+ &dev_attr_status,
+ &dev_attr_adc,
+ &dev_attr_vendor,
+ &dev_attr_name,
+ NULL,
+};
+
+#else
+static struct device_attribute *magnetic_sensor_attrs[] = {
+ &dev_attr_raw_data,
+ &dev_attr_vendor,
+ &dev_attr_name,
+ NULL,
+};
+#endif
+
+int akm8975_probe(struct i2c_client *client,
+ const struct i2c_device_id *devid)
+{
+ struct akm8975_data *akm;
+ int err;
+
+ pr_info("%s is called.\n", __func__);
+ if (client->dev.platform_data == NULL && client->irq == 0) {
+ dev_err(&client->dev, "platform data & irq are NULL.");
+ err = -ENODEV;
+ goto exit_platform_data_null;
+ }
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "I2C check failed, exiting.");
+ err = -ENODEV;
+ goto exit_check_functionality_failed;
+ }
+
+ akm = kzalloc(sizeof(struct akm8975_data), GFP_KERNEL);
+ if (!akm) {
+ dev_err(&client->dev,
+ "failed to allocate memory for module data");
+ err = -ENOMEM;
+ goto exit_alloc_data_failed;
+ }
+
+ akm->pdata = client->dev.platform_data;
+ mutex_init(&akm->lock);
+ init_completion(&akm->data_ready);
+
+ i2c_set_clientdata(client, akm);
+ akm->this_client = client;
+
+ err = akm8975_ecs_set_mode_power_down(akm);
+ if (err < 0)
+ goto exit_set_mode_power_down_failed;
+
+#if USING_IRQ
+ akm->irq = client->irq;
+ err = akm8975_setup_irq(akm);
+ if (err) {
+ pr_err("%s: could not setup irq", __func__);
+ goto exit_setup_irq;
+ }
+#endif
+
+ akm->akmd_device.minor = MISC_DYNAMIC_MINOR;
+ akm->akmd_device.name = "akm8975";
+ akm->akmd_device.fops = &akmd_fops;
+
+ err = misc_register(&akm->akmd_device);
+ if (err) {
+ pr_err("%s, misc_register failed.", __func__);
+ goto exit_akmd_device_register_failed;
+ }
+
+ init_waitqueue_head(&akm->state_wq);
+
+ /* put into fuse access mode to read asa data */
+ err = i2c_smbus_write_byte_data(client, AK8975_REG_CNTL,
+ REG_CNTL_MODE_FUSE_ROM);
+ if (err) {
+ pr_err("%s: unable to enter fuse rom mode", __func__);
+ goto exit_i2c_failed;
+ }
+
+ err = i2c_smbus_read_i2c_block_data(client, AK8975_REG_ASAX,
+ sizeof(akm->asa), akm->asa);
+ if (err != sizeof(akm->asa)) {
+ pr_err("%s: unable to load factory sensitivity adjust values",
+ __func__);
+ goto exit_i2c_failed;
+ } else
+ pr_info("%s: asa_x = %d, asa_y = %d, asa_z = %d", __func__,
+ akm->asa[0], akm->asa[1], akm->asa[2]);
+
+ err = i2c_smbus_write_byte_data(client, AK8975_REG_CNTL,
+ REG_CNTL_MODE_POWER_DOWN);
+ if (err) {
+ dev_err(&client->dev, "Error in setting power down mode");
+ goto exit_i2c_failed;
+ }
+
+#ifdef FACTORY_TEST
+ ak8975c_selftest(akm);
+#endif
+ err = sensors_register(akm->dev, akm, magnetic_sensor_attrs,
+ "magnetic_sensor");
+ if (err) {
+ pr_info("%s: cound not register"
+ "magnetic sensor device(%d).", __func__, err);
+ goto exit_class_create_failed;
+ }
+
+ /* dev_set_drvdata(akm->dev, akm); */
+
+pr_info("%s is successful.", __func__);
+return 0;
+
+exit_class_create_failed:
+exit_i2c_failed:
+exit_akmd_device_register_failed:
+#if USING_IRQ
+ free_irq(akm->irq, akm);
+ gpio_free(akm->pdata->gpio_data_ready_int);
+exit_setup_irq:
+#endif
+exit_set_mode_power_down_failed:
+ mutex_destroy(&akm->lock);
+ kfree(akm);
+exit_alloc_data_failed:
+exit_check_functionality_failed:
+exit_platform_data_null:
+ return err;
+}
+
+static int __devexit akm8975_remove(struct i2c_client *client)
+{
+ struct akm8975_data *akm = i2c_get_clientdata(client);
+ misc_deregister(&akm->akmd_device);
+#if USING_IRQ
+ free_irq(akm->irq, akm);
+ gpio_free(akm->pdata->gpio_data_ready_int);
+#endif
+ mutex_destroy(&akm->lock);
+ kfree(akm);
+ return 0;
+}
+
+static const struct i2c_device_id akm8975_id[] = {
+ {AKM8975_I2C_NAME, 0 },
+ { }
+};
+
+static struct i2c_driver akm8975_driver = {
+ .probe = akm8975_probe,
+ .remove = akm8975_remove,
+ .id_table = akm8975_id,
+ .driver = {
+ .name = AKM8975_I2C_NAME,
+ },
+};
+
+static int __init akm8975_init(void)
+{
+ return i2c_add_driver(&akm8975_driver);
+}
+
+static void __exit akm8975_exit(void)
+{
+ i2c_del_driver(&akm8975_driver);
+}
+
+module_init(akm8975_init);
+module_exit(akm8975_exit);
+
+MODULE_DESCRIPTION("AKM8975 compass driver");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/bh1721fvc.c b/drivers/input/misc/bh1721fvc.c
new file mode 100755
index 0000000..5950550
--- /dev/null
+++ b/drivers/input/misc/bh1721fvc.c
@@ -0,0 +1,826 @@
+/*
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * 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.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/bh1721fvc.h>
+
+#define VENDOR_NAME "ROHM"
+#define CHIP_NAME "BH1721"
+
+#define SENSOR_AL3201_ADDR 0x1c
+#define SENSOR_BH1721FVC_ADDR 0x23
+
+#define BH1721FVC_DRV_NAME "bh1721fvc"
+#define DRIVER_VERSION "1.1"
+
+#define LIMIT_RESET_COUNT 5
+
+#define LUX_MIN_VALUE 0
+#define LUX_MAX_VALUE 65528
+
+#define ALS_BUFFER_NUM 10
+
+#define bh1721fvc_dbmsg(str, args...) pr_debug("%s: " str, __func__, ##args)
+
+enum BH1721FVC_STATE {
+ POWER_DOWN = 0,
+ POWER_ON,
+ AUTO_MEASURE,
+ H_MEASURE,
+ L_MEASURE,
+};
+
+static const u8 commands[] = {
+ 0x00, /* Power Down */
+ 0x01, /* Power On */
+ 0x10, /* Continuously Auto-Resolution Mode */
+ 0x12, /* Continuously H-Resolution Mode */
+ 0x13, /* Continuously L-Resolution Mode */
+};
+
+struct bh1721fvc_data {
+ int (*reset)(void);
+ struct bh1721fvc_platform_data *pdata;
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+ struct work_struct work_light;
+ struct hrtimer timer;
+ struct mutex lock;
+ struct workqueue_struct *wq;
+ struct class *factory_class;
+ struct device *factory_dev;
+ struct device *light_dev;
+ ktime_t light_poll_delay;
+ enum BH1721FVC_STATE state;
+ u8 measure_mode;
+ bool als_buf_initialized;
+ int als_value_buf[ALS_BUFFER_NUM];
+ int als_index_count;
+};
+
+extern int sensors_register(struct device *dev, void * drvdata,
+ struct device_attribute *attributes[], char *name);
+extern void sensors_unregister(struct device *dev);
+
+static int bh1721fvc_get_luxvalue(struct bh1721fvc_data *bh1721fvc, u16 *value);
+
+static int bh1721fvc_write_byte(struct i2c_client *client, u8 value)
+{
+ int retry;
+
+ for (retry = 0; retry < 5; retry++)
+ if (!i2c_smbus_write_byte(client, value))
+ return 0;
+
+ return -EIO;
+}
+
+static bool bh1721fvc_is_measuring(struct bh1721fvc_data *bh1721fvc)
+{
+ return ((bh1721fvc->state == H_MEASURE) ||
+ (bh1721fvc->state == L_MEASURE) ||
+ (bh1721fvc->state == AUTO_MEASURE));
+}
+
+static int bh1721fvc_enable(struct bh1721fvc_data *bh1721fvc)
+{
+ int err;
+
+ bh1721fvc_dbmsg("starting poll timer, delay %lldns\n",
+ ktime_to_ns(bh1721fvc->light_poll_delay));
+
+ err = bh1721fvc_write_byte(bh1721fvc->client, commands[POWER_ON]);
+ if (err) {
+ pr_err("%s: Failed to write byte (POWER_ON)\n", __func__);
+ goto err_power_on;
+ }
+ err = bh1721fvc_write_byte(bh1721fvc->client,
+ commands[bh1721fvc->measure_mode]);
+ if (err) {
+ pr_err("%s: Failed to write byte (measure mode)\n", __func__);
+ goto err_measure_mode;
+ }
+
+ if (bh1721fvc->measure_mode == H_MEASURE)
+ mdelay(120);
+ else if (bh1721fvc->measure_mode == L_MEASURE)
+ mdelay(16);
+ else /* AUTO_MEASURE */
+ mdelay(120 + 16);
+
+ hrtimer_start(&bh1721fvc->timer, bh1721fvc->light_poll_delay,
+ HRTIMER_MODE_REL);
+ goto done;
+
+err_measure_mode:
+err_power_on:
+ bh1721fvc_write_byte(bh1721fvc->client, commands[POWER_DOWN]);
+done:
+ return err;
+}
+
+static int bh1721fvc_disable(struct bh1721fvc_data *bh1721fvc)
+{
+ int err;
+
+ hrtimer_cancel(&bh1721fvc->timer);
+ cancel_work_sync(&bh1721fvc->work_light);
+ err = bh1721fvc_write_byte(bh1721fvc->client, commands[POWER_DOWN]);
+ if (unlikely(err != 0))
+ pr_err("%s: Failed to write byte (POWER_DOWN)\n", __func__);
+
+ return err;
+}
+
+static ssize_t bh1721fvc_poll_delay_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct bh1721fvc_data *bh1721fvc = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%lld\n",
+ ktime_to_ns(bh1721fvc->light_poll_delay));
+}
+
+static ssize_t bh1721fvc_poll_delay_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int err;
+ int64_t new_delay;
+ struct bh1721fvc_data *bh1721fvc = dev_get_drvdata(dev);
+
+ err = strict_strtoll(buf, 10, &new_delay);
+ if (err < 0)
+ return err;
+
+ bh1721fvc_dbmsg("new delay = %lldns, old delay = %lldns\n",
+ new_delay, ktime_to_ns(bh1721fvc->light_poll_delay));
+
+ mutex_lock(&bh1721fvc->lock);
+ if (new_delay != ktime_to_ns(bh1721fvc->light_poll_delay)) {
+ bh1721fvc->light_poll_delay = ns_to_ktime(new_delay);
+ if (bh1721fvc_is_measuring(bh1721fvc)) {
+ bh1721fvc_disable(bh1721fvc);
+ bh1721fvc_enable(bh1721fvc);
+ }
+
+ }
+ mutex_unlock(&bh1721fvc->lock);
+
+ return size;
+}
+
+static ssize_t bh1721fvc_light_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct bh1721fvc_data *bh1721fvc = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", bh1721fvc_is_measuring(bh1721fvc));
+}
+
+static ssize_t bh1721fvc_light_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int err = 0;
+ bool new_value = false;
+ struct bh1721fvc_data *bh1721fvc = dev_get_drvdata(dev);
+
+ bh1721fvc_dbmsg("enable %s\n", buf);
+
+ if (sysfs_streq(buf, "1")) {
+ new_value = true;
+ } else if (sysfs_streq(buf, "0")) {
+ new_value = false;
+ } else {
+ pr_err("%s: invalid value %d\n", __func__, *buf);
+ return -EINVAL;
+ }
+
+ bh1721fvc_dbmsg("new_value = %d, old state = %d\n",
+ new_value, bh1721fvc_is_measuring(bh1721fvc));
+
+ mutex_lock(&bh1721fvc->lock);
+ if (new_value && (!bh1721fvc_is_measuring(bh1721fvc))) {
+ err = bh1721fvc_enable(bh1721fvc);
+ if (!err) {
+ bh1721fvc->state = bh1721fvc->measure_mode;
+ } else {
+ pr_err("%s: couldn't enable", __func__);
+ bh1721fvc->state = POWER_DOWN;
+ }
+ bh1721fvc->als_buf_initialized = false;
+ } else if (!new_value && (bh1721fvc_is_measuring(bh1721fvc))) {
+ err = bh1721fvc_disable(bh1721fvc);
+ if (!err)
+ bh1721fvc->state = POWER_DOWN;
+ else
+ pr_err("%s: couldn't enable", __func__);
+ } else {
+ bh1721fvc_dbmsg("no nothing\n");
+ }
+
+ mutex_unlock(&bh1721fvc->lock);
+
+ return size;
+}
+
+static ssize_t bh1721fvc_light_sensor_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct bh1721fvc_data *bh1721fvc = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%s\n",
+ (bh1721fvc->measure_mode == AUTO_MEASURE) ? "auto" :
+ (bh1721fvc->measure_mode == H_MEASURE) ? "high" :
+ (bh1721fvc->measure_mode == L_MEASURE) ? "low" :
+ "invalid");
+}
+
+static ssize_t bh1721fvc_light_sensor_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ u8 measure_mode;
+ struct bh1721fvc_data *bh1721fvc = dev_get_drvdata(dev);
+
+ bh1721fvc_dbmsg("bh1721fvc_light_sensor_mode_store +\n");
+
+ if (sysfs_streq(buf, "auto")) {
+ measure_mode = AUTO_MEASURE;
+ } else if (sysfs_streq(buf, "high")) {
+ measure_mode = H_MEASURE;
+ } else if (sysfs_streq(buf, "low")) {
+ measure_mode = L_MEASURE;
+ } else {
+ pr_err("%s: invalid value %s\n", __func__, buf);
+ return -EINVAL;
+ }
+
+ mutex_lock(&bh1721fvc->lock);
+ if (bh1721fvc->measure_mode != measure_mode) {
+ bh1721fvc->measure_mode = measure_mode;
+ if (bh1721fvc_is_measuring(bh1721fvc)) {
+ bh1721fvc_disable(bh1721fvc);
+ bh1721fvc_enable(bh1721fvc);
+ bh1721fvc->state = measure_mode;
+ }
+ }
+ mutex_unlock(&bh1721fvc->lock);
+
+ bh1721fvc_dbmsg("bh1721fvc_light_sensor_mode_store -\n");
+
+ return size;
+}
+
+static DEVICE_ATTR(poll_delay, S_IRUGO | S_IWUSR | S_IWGRP,
+ bh1721fvc_poll_delay_show, bh1721fvc_poll_delay_store);
+
+static DEVICE_ATTR(sensor_mode, S_IRUGO | S_IWUSR | S_IWGRP,
+ bh1721fvc_light_sensor_mode_show,
+ bh1721fvc_light_sensor_mode_store);
+
+static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
+ bh1721fvc_light_enable_show, bh1721fvc_light_enable_store);
+
+static struct attribute *bh1721fvc_sysfs_attrs[] = {
+ &dev_attr_enable.attr,
+ &dev_attr_poll_delay.attr,
+ &dev_attr_sensor_mode.attr,
+ NULL
+};
+
+static struct attribute_group bh1721fvc_attribute_group = {
+ .attrs = bh1721fvc_sysfs_attrs,
+};
+
+static ssize_t factory_file_illuminance_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u16 lux;
+ int retry;
+ int err;
+ unsigned int result;
+ struct bh1721fvc_data *bh1721fvc = dev_get_drvdata(dev);
+
+ if (bh1721fvc->state == POWER_DOWN) {
+ err = bh1721fvc_write_byte(bh1721fvc->client,
+ commands[POWER_ON]);
+ if (err)
+ goto err_exit;
+
+ err = bh1721fvc_write_byte(bh1721fvc->client,
+ commands[AUTO_MEASURE]);
+ if (err)
+ goto err_exit;
+ msleep(210);
+ }
+
+ for (retry = 0; retry < 10; retry++) {
+ if (i2c_master_recv(bh1721fvc->client, (u8 *)&lux, 2) == 2) {
+ be16_to_cpus(&lux);
+ break;
+ }
+ }
+
+ if (retry == 10) {
+ pr_err("%s : I2C read failed.. retry %d\n", __func__, retry);
+ goto err_exit;
+ }
+
+ result = (lux * 10) / 12;
+ result = result * 139 / 13;
+
+ if (bh1721fvc->state == POWER_DOWN)
+ bh1721fvc_write_byte(bh1721fvc->client, commands[POWER_DOWN]);
+
+ return sprintf(buf, "%u\n", result);
+
+err_exit:
+ bh1721fvc_write_byte(bh1721fvc->client, commands[POWER_DOWN]);
+ return err;
+}
+
+static ssize_t get_vendor_name(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", VENDOR_NAME);
+}
+
+static ssize_t get_chip_name(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", CHIP_NAME);
+}
+
+static DEVICE_ATTR(lightsensor_file_cmd, S_IRUGO | S_IWUSR | S_IWGRP,
+ bh1721fvc_light_enable_show,
+ bh1721fvc_light_sensor_mode_store);
+
+static DEVICE_ATTR(lightsensor_file_illuminance, S_IRUGO,
+ factory_file_illuminance_show, NULL);
+
+static ssize_t sensor_info_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", SENSOR_BH1721FVC_ADDR);
+}
+
+static DEVICE_ATTR(sensor_info, S_IRUGO, sensor_info_show, NULL);
+
+
+static struct device_attribute dev_attr_light_raw_data =
+ __ATTR(raw_data, S_IRUGO, factory_file_illuminance_show, NULL);
+
+static struct device_attribute dev_attr_light_lux =
+ __ATTR(lux, S_IRUGO, factory_file_illuminance_show, NULL);
+
+static struct device_attribute dev_attr_light_adc =
+ __ATTR(adc, S_IRUGO, factory_file_illuminance_show, NULL);
+
+static struct device_attribute dev_attr_light_enable =
+ __ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
+ bh1721fvc_light_enable_show, bh1721fvc_light_enable_store);
+
+static DEVICE_ATTR(vendor, S_IRUGO, get_vendor_name, NULL);
+static DEVICE_ATTR(name, S_IRUGO, get_chip_name, NULL);
+
+static struct device_attribute *light_sensor_attrs[] = {
+ &dev_attr_light_raw_data,
+ &dev_attr_light_lux,
+ &dev_attr_light_adc,
+ &dev_attr_light_enable,
+ &dev_attr_vendor,
+ &dev_attr_name,
+ NULL
+};
+
+static int bh1721fvc_get_luxvalue(struct bh1721fvc_data *bh1721fvc, u16 *value)
+{
+ int retry;
+ int i = 0;
+ int j = 0;
+ unsigned int als_total = 0;
+ unsigned int als_index = 0;
+ unsigned int als_max = 0;
+ unsigned int als_min = 0;
+
+ for (retry = 0; retry < 10; retry++) {
+ if (i2c_master_recv(bh1721fvc->client, (u8 *)value, 2) == 2) {
+ be16_to_cpus(value);
+ break;
+ }
+ }
+
+ if (retry == 10) {
+ pr_err("%s : I2C read failed.. retry %d\n", __func__, retry);
+ return -EIO;
+ }
+
+ als_index = (bh1721fvc->als_index_count++) % ALS_BUFFER_NUM;
+
+ /*ALS buffer initialize (light sensor off ---> light sensor on) */
+ if (!bh1721fvc->als_buf_initialized) {
+ bh1721fvc->als_buf_initialized = true;
+ for (j = 0; j < ALS_BUFFER_NUM; j++)
+ bh1721fvc->als_value_buf[j] = *value;
+ } else
+ bh1721fvc->als_value_buf[als_index] = *value;
+
+ als_max = bh1721fvc->als_value_buf[0];
+ als_min = bh1721fvc->als_value_buf[0];
+
+ for (i = 0; i < ALS_BUFFER_NUM; i++) {
+ als_total += bh1721fvc->als_value_buf[i];
+
+ if (als_max < bh1721fvc->als_value_buf[i])
+ als_max = bh1721fvc->als_value_buf[i];
+
+ if (als_min > bh1721fvc->als_value_buf[i])
+ als_min = bh1721fvc->als_value_buf[i];
+ }
+ *value = (als_total-(als_max+als_min))/(ALS_BUFFER_NUM-2);
+
+ if (bh1721fvc->als_index_count >= ALS_BUFFER_NUM)
+ bh1721fvc->als_index_count = 0;
+
+ return 0;
+}
+
+
+static void bh1721fvc_work_func_light(struct work_struct *work)
+{
+ int err;
+ u16 lux;
+ u32 result;
+ struct bh1721fvc_data *bh1721fvc = container_of(work,
+ struct bh1721fvc_data, work_light);
+
+ err = bh1721fvc_get_luxvalue(bh1721fvc, &lux);
+ if (!err) {
+ result = lux;
+ result = (result * 10) / 12;
+ result = result * 139 / 13;
+ bh1721fvc_dbmsg("lux 0x%0X (%d)\n", result, result);
+ input_report_abs(bh1721fvc->input_dev, ABS_MISC, result);
+ input_sync(bh1721fvc->input_dev);
+ } else {
+ pr_err("%s: read word failed! (errno=%d)\n", __func__,
+ err);
+ }
+}
+
+static enum hrtimer_restart bh1721fvc_timer_func(struct hrtimer *timer)
+{
+ struct bh1721fvc_data *bh1721fvc = container_of(timer,
+ struct bh1721fvc_data, timer);
+
+ queue_work(bh1721fvc->wq, &bh1721fvc->work_light);
+ hrtimer_forward_now(&bh1721fvc->timer, bh1721fvc->light_poll_delay);
+ return HRTIMER_RESTART;
+}
+
+int bh1721fvc_test_luxvalue(struct bh1721fvc_data *bh1721fvc)
+{
+ unsigned int result;
+ int retry;
+ u16 lux;
+ int err;
+
+ if (bh1721fvc->state == POWER_DOWN) {
+ err = bh1721fvc_write_byte(bh1721fvc->client,
+ commands[POWER_ON]);
+ if (err)
+ return err;
+
+ err = bh1721fvc_write_byte(bh1721fvc->client,
+ commands[AUTO_MEASURE]);
+ if (err)
+ goto err_exit;
+
+ msleep(210);
+ }
+
+ for (retry = 0; retry < 5; retry++) {
+ if (i2c_master_recv(bh1721fvc->client, (u8 *)&lux, 2) == 2) {
+ be16_to_cpus(&lux);
+ break;
+ }
+ }
+
+ if (retry == 5) {
+ printk(KERN_ERR"I2C read failed.. retry %d\n", retry);
+ goto err_exit;
+ }
+
+ result = (lux * 10) / 12;
+ result = result * 139 / 13;
+
+ if (bh1721fvc->state == POWER_DOWN)
+ bh1721fvc_write_byte(bh1721fvc->client, commands[POWER_DOWN]);
+
+ return (int)result;
+
+err_exit:
+ bh1721fvc_write_byte(bh1721fvc->client, commands[POWER_DOWN]);
+ return err;
+}
+
+static int __devinit bh1721fvc_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int err = 0;
+ struct bh1721fvc_data *bh1721fvc;
+ struct input_dev *input_dev;
+ struct bh1721fvc_platform_data *pdata = client->dev.platform_data;
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_I2C))
+ return -EIO;
+
+ bh1721fvc = kzalloc(sizeof(*bh1721fvc), GFP_KERNEL);
+ if (!bh1721fvc) {
+ pr_err("%s, failed to alloc memory for module data\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ bh1721fvc->reset = pdata->reset;
+ if (!bh1721fvc->reset) {
+ pr_err("%s: reset callback is null\n", __func__);
+ err = -EIO;
+ goto err_reset_null;
+ }
+
+ err = bh1721fvc->reset();
+ if (err) {
+ pr_err("%s: Failed to reset\n", __func__);
+ goto err_reset_failed;
+ }
+
+ bh1721fvc->client = client;
+ i2c_set_clientdata(client, bh1721fvc);
+
+ mutex_init(&bh1721fvc->lock);
+ bh1721fvc->state = POWER_DOWN;
+ bh1721fvc->measure_mode = AUTO_MEASURE;
+
+ err = bh1721fvc_test_luxvalue(bh1721fvc);
+ if (err < 0) {
+ pr_err("%s: No search bh1721fvc lightsensor!\n", __func__);
+ goto err_test_lightsensor;
+ } else {
+ printk(KERN_ERR"Lux : %d\n", err);
+ }
+
+ hrtimer_init(&bh1721fvc->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+
+ bh1721fvc->light_poll_delay = ns_to_ktime(200 * NSEC_PER_MSEC);
+ bh1721fvc->timer.function = bh1721fvc_timer_func;
+
+ bh1721fvc->wq = alloc_workqueue("bh1721fvc_wq",
+ WQ_UNBOUND | WQ_RESCUER, 1);
+ if (!bh1721fvc->wq) {
+ err = -ENOMEM;
+ pr_err("%s: could not create workqueue\n", __func__);
+ goto err_create_workqueue;
+ }
+
+ INIT_WORK(&bh1721fvc->work_light, bh1721fvc_work_func_light);
+
+ input_dev = input_allocate_device();
+ if (!input_dev) {
+ pr_err("%s: could not allocate input device\n", __func__);
+ err = -ENOMEM;
+ goto err_input_allocate_device_light;
+ }
+ input_set_drvdata(input_dev, bh1721fvc);
+ input_dev->name = "light_sensor";
+ input_set_capability(input_dev, EV_ABS, ABS_MISC);
+ input_set_abs_params(input_dev, ABS_MISC,
+ LUX_MIN_VALUE, LUX_MAX_VALUE, 0, 0);
+ bh1721fvc_dbmsg("registering lightsensor-level input device\n");
+ err = input_register_device(input_dev);
+ if (err < 0) {
+ pr_err("%s: could not register input device\n", __func__);
+ input_free_device(input_dev);
+ goto err_input_register_device_light;
+ }
+ bh1721fvc->input_dev = input_dev;
+ err = sysfs_create_group(&input_dev->dev.kobj,
+ &bh1721fvc_attribute_group);
+ if (err) {
+ pr_err("%s: could not create sysfs group\n", __func__);
+ goto err_sysfs_create_group_light;
+ }
+
+ bh1721fvc->factory_class = class_create(THIS_MODULE, "lightsensor");
+
+ if (IS_ERR(bh1721fvc->factory_class)) {
+ pr_err("Failed to create class(lightsensor)!\n");
+ err = PTR_ERR(bh1721fvc->factory_class);
+ goto err_factory_sysfs_create;
+ }
+
+ bh1721fvc->factory_dev = device_create(bh1721fvc->factory_class, NULL,
+ 0, bh1721fvc, "switch_cmd");
+
+ if (IS_ERR(bh1721fvc->factory_dev)) {
+ pr_err("Failed to create device(switch_cmd_dev)!\n");
+ err = PTR_ERR(bh1721fvc->factory_dev);
+ goto err_factory_device_create;
+ }
+
+ err = device_create_file(bh1721fvc->factory_dev,
+ &dev_attr_lightsensor_file_cmd);
+
+ if (err < 0) {
+ pr_err("Failed to create device file(%s)!\n",
+ dev_attr_lightsensor_file_cmd.attr.name);
+ goto err_file_cmd_attr_create;
+ }
+
+ err = device_create_file(bh1721fvc->factory_dev,
+ &dev_attr_lightsensor_file_illuminance);
+
+ if (err < 0) {
+ pr_err("Failed to create device file(%s)!\n",
+ dev_attr_lightsensor_file_illuminance.attr.name);
+ goto err_illuminance_attr_create;
+ }
+
+ err = device_create_file(bh1721fvc->factory_dev,
+ &dev_attr_sensor_info);
+ if (err < 0) {
+ pr_err("Failed to create device file(%s)!\n",
+ dev_attr_sensor_info.attr.name);
+ goto err_sensor_info_attr_create;
+ }
+
+/* new sysfs */
+ err = sensors_register(bh1721fvc->light_dev,
+ bh1721fvc, light_sensor_attrs, "light_sensor");
+ if (err) {
+ pr_err("%s: cound not register light sensor device(%d).\n",
+ __func__, err);
+ goto out_light_sensor_register_failed;
+ }
+
+ pr_info("%s: success!\n", __func__);
+
+
+ goto done;
+
+out_light_sensor_register_failed:
+ sensors_unregister(bh1721fvc->light_dev);
+
+err_sensor_info_attr_create:
+ device_remove_file(bh1721fvc->factory_dev,
+ &dev_attr_lightsensor_file_illuminance);
+err_illuminance_attr_create:
+ device_remove_file(bh1721fvc->factory_dev,
+ &dev_attr_lightsensor_file_cmd);
+err_file_cmd_attr_create:
+ device_destroy(bh1721fvc->factory_class, 0);
+err_factory_device_create:
+ class_destroy(bh1721fvc->factory_class);
+err_factory_sysfs_create:
+ sysfs_remove_group(&bh1721fvc->input_dev->dev.kobj,
+ &bh1721fvc_attribute_group);
+err_sysfs_create_group_light:
+ input_unregister_device(bh1721fvc->input_dev);
+err_input_register_device_light:
+err_input_allocate_device_light:
+ destroy_workqueue(bh1721fvc->wq);
+err_create_workqueue:
+err_test_lightsensor:
+ mutex_destroy(&bh1721fvc->lock);
+err_reset_failed:
+err_reset_null:
+ kfree(bh1721fvc);
+done:
+ return err;
+}
+
+static int bh1721fvc_remove(struct i2c_client *client)
+{
+ struct bh1721fvc_data *bh1721fvc = i2c_get_clientdata(client);
+
+ device_remove_file(bh1721fvc->factory_dev,
+ &dev_attr_sensor_info);
+ device_remove_file(bh1721fvc->factory_dev,
+ &dev_attr_lightsensor_file_cmd);
+ device_remove_file(bh1721fvc->factory_dev,
+ &dev_attr_lightsensor_file_illuminance);
+ device_destroy(bh1721fvc->factory_class, 0);
+ class_destroy(bh1721fvc->factory_class);
+
+ sysfs_remove_group(&bh1721fvc->input_dev->dev.kobj,
+ &bh1721fvc_attribute_group);
+ input_unregister_device(bh1721fvc->input_dev);
+
+ if (bh1721fvc_is_measuring(bh1721fvc))
+ bh1721fvc_disable(bh1721fvc);
+
+ destroy_workqueue(bh1721fvc->wq);
+ mutex_destroy(&bh1721fvc->lock);
+ kfree(bh1721fvc);
+
+ bh1721fvc_dbmsg("bh1721fvc_remove -\n");
+ return 0;
+}
+
+static int bh1721fvc_suspend(struct device *dev)
+{
+ int err = 0;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bh1721fvc_data *bh1721fvc = i2c_get_clientdata(client);
+
+ if (bh1721fvc_is_measuring(bh1721fvc)) {
+ err = bh1721fvc_disable(bh1721fvc);
+ if (err)
+ pr_err("%s: could not disable\n", __func__);
+ }
+
+ return err;
+}
+
+static int bh1721fvc_resume(struct device *dev)
+{
+ int err = 0;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bh1721fvc_data *bh1721fvc = i2c_get_clientdata(client);
+
+ bh1721fvc_dbmsg("bh1721fvc_resume +\n");
+
+ if (bh1721fvc_is_measuring(bh1721fvc)) {
+ err = bh1721fvc_enable(bh1721fvc);
+ if (err)
+ pr_err("%s: could not enable\n", __func__);
+ }
+
+ bh1721fvc_dbmsg("bh1721fvc_resume -\n");
+ return err;
+}
+
+static const struct i2c_device_id bh1721fvc_id[] = {
+ { BH1721FVC_DRV_NAME, 0 },
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, bh1721fvc_id);
+
+static const struct dev_pm_ops bh1721fvc_pm_ops = {
+ .suspend = bh1721fvc_suspend,
+ .resume = bh1721fvc_resume,
+};
+
+static struct i2c_driver bh1721fvc_driver = {
+ .driver = {
+ .name = BH1721FVC_DRV_NAME,
+ .owner = THIS_MODULE,
+ .pm = &bh1721fvc_pm_ops,
+ },
+ .probe = bh1721fvc_probe,
+ .remove = bh1721fvc_remove,
+ .id_table = bh1721fvc_id,
+};
+
+static int __init bh1721fvc_init(void)
+{
+ return i2c_add_driver(&bh1721fvc_driver);
+}
+module_init(bh1721fvc_init);
+
+static void __exit bh1721fvc_exit(void)
+{
+ bh1721fvc_dbmsg("bh1721fvc_exit +\n");
+ i2c_del_driver(&bh1721fvc_driver);
+ bh1721fvc_dbmsg("bh1721fvc_exit -\n");
+}
+module_exit(bh1721fvc_exit);
+
+MODULE_AUTHOR("WonHyoung Lee <whlee@sta.samsung.com>");
+MODULE_DESCRIPTION("BH1721FVC Ambient light sensor driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(DRIVER_VERSION);
diff --git a/drivers/input/misc/gp2a.c b/drivers/input/misc/gp2a.c
new file mode 100755
index 0000000..05564cb
--- /dev/null
+++ b/drivers/input/misc/gp2a.c
@@ -0,0 +1,1040 @@
+/* linux/driver/input/misc/gp2a.c
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * 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.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/i2c.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/miscdevice.h>
+#include <linux/platform_device.h>
+#include <linux/leds.h>
+#include <linux/gpio.h>
+#include <linux/wakelock.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/workqueue.h>
+#include <linux/uaccess.h>
+#include <linux/gp2a.h>
+#include <plat/adc.h>
+
+/* Note about power vs enable/disable:
+ * The chip has two functions, proximity and ambient light sensing.
+ * There is no separate power enablement to the two functions (unlike
+ * the Capella CM3602/3623).
+ * This module implements two drivers: /dev/proximity and /dev/light.
+ * When either driver is enabled (via sysfs attributes), we give power
+ * to the chip. When both are disabled, we remove power from the chip.
+ * In suspend, we remove power if light is disabled but not if proximity is
+ * enabled (proximity is allowed to wakeup from suspend).
+ *
+ * There are no ioctls for either driver interfaces. Output is via
+ * input device framework and control via sysfs attributes.
+ */
+
+#if defined(CONFIG_MACH_P8)
+ #define GP2A_MODE_B
+#endif
+
+#define gp2a_dbgmsg(str, args...) pr_debug("%s: " str, __func__, ##args)
+
+#define VENDOR_NAME "SHARP"
+#define CHIP_NAME "GP2A0002"
+
+#define ADC_SAMPLE_NUM 5
+
+/* ADDSEL is LOW */
+#define REGS_PROX 0x0 /* Read Only */
+#define REGS_GAIN 0x1 /* Write Only */
+#define REGS_HYS 0x2 /* Write Only */
+#define REGS_CYCLE 0x3 /* Write Only */
+#define REGS_OPMOD 0x4 /* Write Only */
+#define REGS_CON 0x6 /* Write Only */
+
+/* sensor type */
+#define LIGHT 0
+#define PROXIMITY 1
+#define ALL 2
+
+#ifdef GP2A_MODE_B
+
+#define GP2A_MSK_PROX_VO 0x01
+#define GP2A_BIT_PROX_VO_NO_DETECTION 0x01
+#define GP2A_BIT_PROX_VO_DETECTION 0x00
+
+#define GP2A_BIT_OPMOD_SSD_SHUTDOWN_MODE 0x00
+#define GP2A_BIT_OPMOD_SSD_OPERATING_MODE 0x01
+#define GP2A_BIT_OPMOD_VCON_NORMAL_MODE 0x00
+#define GP2A_BIT_OPMOD_VCON_INTERRUPT_MODE 0x02
+#define GP2A_BIT_OPMOD_ASD_INEFFECTIVE 0x00
+#define GP2A_BIT_OPMOD_ASD_EFFECTIVE 0x10
+
+#define GP2A_BIT_CON_OCON_ENABLE_VOUT 0x00
+#define GP2A_BIT_CON_OCON_FORCE_VOUT_HIGH 0x18
+#define GP2A_BIT_CON_OCON_FORCE_VOUT_LOW 0x10
+
+#define GP2A_INPUT_RANGE_MIN 0
+#define GP2A_INPUT_RANGE_MAX 1
+#define GP2A_INPUT_RANGE_FUZZ 0
+#define GP2A_INPUT_RANGE_FLAT 0
+
+#define VO_0 0x40
+#define VO_1 0x20
+
+#else
+/* GP2A MODE A*/
+#define VO_0 0x40 /* 0x40 */
+#define VO_1 0x20 /* 0x20 */
+
+static u8 reg_defaults[5] = {
+ 0x00, /* PROX: read only register */
+ 0x08, /* GAIN: large LED drive level */
+ VO_0, /* HYS: receiver sensitivity */
+ 0x04, /* CYCLE: */
+ 0x01, /* OPMOD: normal operating mode */
+};
+
+#endif
+
+#define LIGHT_TIMER_PERIOD_MS 200
+
+#define LIGHT_FAKE_THRESHOLD 80
+#define LIGHT_FAKE_LIMIT (LIGHT_FAKE_THRESHOLD*2)
+
+/* light sensor adc channel */
+#define ALS_IOUT_ADC 9
+
+#if defined(CONFIG_MACH_P8)
+
+static const int adc_table[4] = {
+ 320,
+ 840,
+ 1400,
+ 1950,
+};
+
+#elif defined(CONFIG_MACH_P2)
+
+static const int adc_table[4] = {
+ 480,
+ 975,
+ 1535,
+ 2090,
+};
+
+#else
+
+static const int adc_table[4] = {
+ 430,
+ 925,
+ 1485,
+ 1950,
+};
+#endif
+
+struct gp2a_data;
+
+enum {
+ LIGHT_ENABLED = BIT(0),
+ PROXIMITY_ENABLED = BIT(1),
+};
+
+extern int sensors_register(struct device *dev, void * drvdata,
+ struct device_attribute *attributes[], char *name);
+extern void sensors_unregister(struct device *dev);
+
+struct platform_device *pdev_gp2a_adc;
+
+/* driver data */
+struct gp2a_data {
+ struct input_dev *proximity_input_dev;
+ struct input_dev *light_input_dev;
+ struct gp2a_platform_data *pdata;
+ struct i2c_client *i2c_client;
+ struct device *light_dev;
+ struct device *proximity_dev;
+ struct device *switch_cmd_dev;
+ struct class *lightsensor_class;
+ int irq;
+ struct work_struct work_light;
+ struct hrtimer timer;
+ ktime_t light_poll_delay;
+ bool on;
+ u8 power_state;
+ struct mutex power_mutex;
+ struct mutex adc_mutex;
+ struct wake_lock prx_wake_lock;
+ struct workqueue_struct *wq;
+ unsigned int adc_total;
+ struct s3c_adc_client *padc;
+ bool enable_wakeup;
+ int prox_value;
+#ifdef GP2A_MODE_B
+ struct work_struct work_proximity;
+#endif
+};
+
+int gp2a_i2c_write(struct gp2a_data *gp2a, u8 reg, u8 *val)
+{
+ int err = 0;
+ struct i2c_msg msg[1];
+ u8 data[2];
+ int retry = 10;
+ struct i2c_client *client = gp2a->i2c_client;
+
+ pr_info("%s\n", __func__);
+
+ if ((client == NULL) || (!client->adapter))
+ return -ENODEV;
+
+ while (retry--) {
+ data[0] = reg;
+ data[1] = *val;
+
+ msg->addr = client->addr;
+ msg->flags = 0; /* write */
+ msg->len = 2;
+ msg->buf = data;
+
+ err = i2c_transfer(client->adapter, msg, 1);
+
+ if (err >= 0)
+ return 0;
+ }
+ return err;
+}
+
+static void gp2a_light_enable(struct gp2a_data *gp2a)
+{
+ gp2a_dbgmsg("starting poll timer, delay %lldns\n",
+ ktime_to_ns(gp2a->light_poll_delay));
+ hrtimer_start(&gp2a->timer, gp2a->light_poll_delay, HRTIMER_MODE_REL);
+}
+
+static void gp2a_light_disable(struct gp2a_data *gp2a)
+{
+ gp2a_dbgmsg("cancelling poll timer\n");
+ hrtimer_cancel(&gp2a->timer);
+ cancel_work_sync(&gp2a->work_light);
+}
+
+static ssize_t poll_delay_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gp2a_data *gp2a = dev_get_drvdata(dev);
+ return sprintf(buf, "%lld\n", ktime_to_ns(gp2a->light_poll_delay));
+}
+
+static ssize_t poll_delay_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct gp2a_data *gp2a = dev_get_drvdata(dev);
+ int64_t new_delay;
+ int err;
+
+ err = strict_strtoll(buf, 10, &new_delay);
+ if (err < 0)
+ return err;
+
+ gp2a_dbgmsg("new delay = %lldns, old delay = %lldns\n",
+ new_delay, ktime_to_ns(gp2a->light_poll_delay));
+ mutex_lock(&gp2a->power_mutex);
+ if (new_delay != ktime_to_ns(gp2a->light_poll_delay)) {
+ gp2a->light_poll_delay = ns_to_ktime(new_delay);
+ if (gp2a->power_state & LIGHT_ENABLED) {
+ gp2a_light_disable(gp2a);
+ gp2a_light_enable(gp2a);
+ }
+ }
+ mutex_unlock(&gp2a->power_mutex);
+
+ return size;
+}
+
+static ssize_t light_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gp2a_data *gp2a = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n",
+ (gp2a->power_state & LIGHT_ENABLED) ? 1 : 0);
+}
+
+static ssize_t proximity_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gp2a_data *gp2a = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n",
+ (gp2a->power_state & PROXIMITY_ENABLED) ? 1 : 0);
+}
+
+static ssize_t light_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct gp2a_data *gp2a = dev_get_drvdata(dev);
+ bool new_value;
+
+ if (sysfs_streq(buf, "1"))
+ new_value = true;
+ else if (sysfs_streq(buf, "0"))
+ new_value = false;
+ else {
+ pr_err("%s: invalid value %d\n", __func__, *buf);
+ return -EINVAL;
+ }
+
+ mutex_lock(&gp2a->power_mutex);
+ gp2a_dbgmsg("new_value = %d, old state = %d\n",
+ new_value, (gp2a->power_state & LIGHT_ENABLED) ? 1 : 0);
+ if (new_value && !(gp2a->power_state & LIGHT_ENABLED)) {
+ gp2a->power_state |= LIGHT_ENABLED;
+ gp2a_light_enable(gp2a);
+ } else if (!new_value && (gp2a->power_state & LIGHT_ENABLED)) {
+ gp2a_light_disable(gp2a);
+ gp2a->power_state &= ~LIGHT_ENABLED;
+ }
+ mutex_unlock(&gp2a->power_mutex);
+ return size;
+}
+
+static ssize_t proximity_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct gp2a_data *gp2a = dev_get_drvdata(dev);
+ bool new_value;
+#ifdef GP2A_MODE_B
+ u8 val;
+#endif
+ if (sysfs_streq(buf, "1"))
+ new_value = true;
+ else if (sysfs_streq(buf, "0"))
+ new_value = false;
+ else {
+ pr_err("%s: invalid value %d\n", __func__, *buf);
+ return -EINVAL;
+ }
+
+ mutex_lock(&gp2a->power_mutex);
+ gp2a_dbgmsg("new_value = %d, old state = %d\n",
+ new_value, (gp2a->power_state & PROXIMITY_ENABLED) ? 1 : 0);
+ if (new_value && !(gp2a->power_state & PROXIMITY_ENABLED)) {
+ gp2a->power_state |= PROXIMITY_ENABLED;
+
+#ifdef GP2A_MODE_B
+ val = GP2A_BIT_OPMOD_SSD_OPERATING_MODE
+ | GP2A_BIT_OPMOD_VCON_NORMAL_MODE
+ | GP2A_BIT_OPMOD_ASD_INEFFECTIVE;
+ gp2a_i2c_write(gp2a, REGS_OPMOD, &val);
+
+ val = GP2A_BIT_CON_OCON_ENABLE_VOUT;
+ gp2a_i2c_write(gp2a, REGS_CON, &val);
+
+ val = VO_0;
+ gp2a_i2c_write(gp2a, REGS_HYS, &val);
+#else
+ gp2a_i2c_write(gp2a, REGS_GAIN, &reg_defaults[1]);
+ gp2a_i2c_write(gp2a, REGS_HYS, &reg_defaults[2]);
+ gp2a_i2c_write(gp2a, REGS_CYCLE, &reg_defaults[3]);
+ gp2a_i2c_write(gp2a, REGS_OPMOD, &reg_defaults[4]);
+ gp2a->prox_value = 1;
+#endif
+ enable_irq(gp2a->irq);
+ if (gp2a->enable_wakeup)
+ enable_irq_wake(gp2a->irq);
+ } else if (!new_value && (gp2a->power_state & PROXIMITY_ENABLED)) {
+ if (gp2a->enable_wakeup)
+ disable_irq_wake(gp2a->irq);
+ disable_irq(gp2a->irq);
+
+#ifdef GP2A_MODE_B
+ val = GP2A_BIT_CON_OCON_FORCE_VOUT_HIGH;
+ gp2a_i2c_write(gp2a, REGS_CON, &val);
+
+ val = GP2A_BIT_OPMOD_SSD_SHUTDOWN_MODE
+ | GP2A_BIT_OPMOD_VCON_NORMAL_MODE
+ | GP2A_BIT_OPMOD_ASD_INEFFECTIVE;
+ gp2a_i2c_write(gp2a, REGS_OPMOD, &val);
+#else
+ gp2a_i2c_write(gp2a, REGS_OPMOD, &reg_defaults[0]);
+#endif
+ gp2a->power_state &= ~PROXIMITY_ENABLED;
+ }
+ mutex_unlock(&gp2a->power_mutex);
+ return size;
+}
+
+static int lightsensor_get_adcvalue(struct gp2a_data *gp2a)
+{
+ int value = 0;
+ int fake_value;
+ unsigned int adc_avr_value;
+
+ /* get ADC */
+ /* value = gp2a->pdata->light_adc_value(); */
+
+ mutex_lock(&gp2a->adc_mutex);
+
+ value = s3c_adc_read(gp2a->padc, ALS_IOUT_ADC);
+
+ mutex_unlock(&gp2a->adc_mutex);
+
+ if (value < 0) {
+ pr_err("%s : ADC Fail, ret = %d", __func__, value);
+ value = 0;
+ }
+
+ gp2a->adc_total += value;
+
+ adc_avr_value = gp2a->adc_total/ADC_SAMPLE_NUM;
+
+ gp2a->adc_total -= adc_avr_value;
+
+ /* Cut off ADC Value
+ */
+#if 1
+ if (adc_avr_value < LIGHT_FAKE_LIMIT) {
+ fake_value =
+ (adc_avr_value < LIGHT_FAKE_THRESHOLD) ?
+ 0 : 2 * (adc_avr_value) - LIGHT_FAKE_LIMIT;
+ adc_avr_value = fake_value;
+ }
+#else
+ if (adc_avr_value < 10) {
+ gp2a->adc_total = 0;
+ adc_avr_value = 0;
+ }
+#endif
+ return adc_avr_value;
+}
+
+static ssize_t lightsensor_file_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gp2a_data *gp2a = dev_get_drvdata(dev);
+ int adc = 0;
+
+ adc = lightsensor_get_adcvalue(gp2a);
+ return sprintf(buf, "%d\n", adc);
+}
+
+static ssize_t proximity_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gp2a_data *gp2a = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n", gp2a->prox_value);
+}
+
+static ssize_t get_vendor_name(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", VENDOR_NAME);
+}
+
+static ssize_t get_chip_name(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", CHIP_NAME);
+}
+
+
+static DEVICE_ATTR(lightsensor_file_illuminance, S_IRUGO,
+ lightsensor_file_state_show, NULL);
+
+static DEVICE_ATTR(poll_delay, S_IRUGO | S_IWUSR | S_IWGRP,
+ poll_delay_show, poll_delay_store);
+
+
+static struct device_attribute dev_attr_light_enable =
+ __ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
+ light_enable_show, light_enable_store);
+
+static struct device_attribute dev_attr_proximity_enable =
+ __ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
+ proximity_enable_show, proximity_enable_store);
+
+static struct attribute *light_sysfs_attrs[] = {
+ &dev_attr_light_enable.attr,
+ &dev_attr_poll_delay.attr,
+ NULL
+};
+
+static struct attribute_group light_attribute_group = {
+ .attrs = light_sysfs_attrs,
+};
+
+static struct attribute *proximity_sysfs_attrs[] = {
+ &dev_attr_proximity_enable.attr,
+ NULL
+};
+
+static struct attribute_group proximity_attribute_group = {
+ .attrs = proximity_sysfs_attrs,
+};
+
+static struct device_attribute dev_attr_light_raw_data =
+ __ATTR(raw_data, S_IRUGO, lightsensor_file_state_show, NULL);
+
+static struct device_attribute dev_attr_light_lux =
+ __ATTR(lux, S_IRUGO, lightsensor_file_state_show, NULL);
+
+static struct device_attribute dev_attr_proximity_raw_data =
+ __ATTR(raw_data, S_IRUGO, proximity_state_show, NULL);
+
+static DEVICE_ATTR(vendor, S_IRUGO, get_vendor_name, NULL);
+static DEVICE_ATTR(name, S_IRUGO, get_chip_name, NULL);
+
+static struct device_attribute *light_sensor_attrs[] = {
+ &dev_attr_light_raw_data,
+ &dev_attr_light_lux,
+ &dev_attr_light_enable,
+ &dev_attr_vendor,
+ &dev_attr_name,
+ NULL
+};
+
+static struct device_attribute *proximity_sensor_attrs[] = {
+ &dev_attr_proximity_raw_data,
+ &dev_attr_proximity_enable,
+ &dev_attr_vendor,
+ &dev_attr_name,
+ NULL
+};
+
+static void gp2a_work_func_light(struct work_struct *work)
+{
+ int i;
+ struct gp2a_data *gp2a = container_of(work, struct gp2a_data,
+ work_light);
+ int adc = lightsensor_get_adcvalue(gp2a);
+
+ input_report_abs(gp2a->light_input_dev, ABS_MISC, adc);
+ input_sync(gp2a->light_input_dev);
+}
+
+/* This function is for light sensor. It operates every a few seconds.
+ * It asks for work to be done on a thread because i2c needs a thread
+ * context (slow and blocking) and then reschedules the timer to run again.
+ */
+static enum hrtimer_restart gp2a_timer_func(struct hrtimer *timer)
+{
+ struct gp2a_data *gp2a = container_of(timer, struct gp2a_data, timer);
+ queue_work(gp2a->wq, &gp2a->work_light);
+ hrtimer_forward_now(&gp2a->timer, gp2a->light_poll_delay);
+ return HRTIMER_RESTART;
+}
+
+#ifdef GP2A_MODE_B
+
+static void gp2a_work_func_proximity(struct work_struct *work)
+{
+ int ret;
+ u8 value;
+
+ struct gp2a_data *gp2a = container_of(work, struct gp2a_data,
+ work_proximity);
+ pr_info("%s : gp2a mode b", __func__);
+
+ mutex_lock(&gp2a->power_mutex);
+ /* GP2A initialized and powered on => do the job */
+ ret = gpio_get_value(gp2a->pdata->p_out);
+
+ if (ret < 0) {
+ pr_info("Failed to get GP2A proximity value "
+ "[errno=%d]; ignored", ret);
+ } else {
+ gp2a->prox_value = ret;
+
+ if (GP2A_BIT_PROX_VO_DETECTION == gp2a->prox_value) {
+ ret = GP2A_INPUT_RANGE_MIN;
+ pr_info("GP2A_INPUT_RANGE_MIN");
+ } else {
+ ret = GP2A_INPUT_RANGE_MAX;
+ pr_info("GP2A_INPUT_RANGE_MAX");
+ }
+ input_report_abs(gp2a->proximity_input_dev, ABS_DISTANCE, ret);
+ input_sync(gp2a->proximity_input_dev);
+ pr_info("input_report_abs proximity_input_dev");
+ }
+
+ if (ret)
+ value = VO_0;
+ else
+ value = VO_1;
+
+ pr_info("value = 0x%x\n", value);
+
+ gp2a_i2c_write(gp2a, REGS_HYS, &value);
+
+ /* enabling VOUT terminal in nomal operation */
+ value = 0x00;
+ gp2a_i2c_write(gp2a, REGS_CON, &value);
+
+ mutex_unlock(&gp2a->power_mutex);
+}
+#endif
+
+/* interrupt happened due to transition/change of near/far proximity state */
+irqreturn_t gp2a_irq_handler(int irq, void *data)
+{
+ struct gp2a_data *ip = data;
+
+#ifdef GP2A_MODE_B
+/* GP2A MODE B */
+ queue_work(ip->wq, &ip->work_proximity);
+#else
+/* GP2A MODE A */
+ u8 setting;
+ int val = gpio_get_value(ip->pdata->p_out);
+ if (val < 0) {
+ pr_err("%s: gpio_get_value error %d\n", __func__, val);
+ return IRQ_HANDLED;
+ }
+
+ if (val != ip->prox_value) {
+ if (val)
+ setting = VO_0;
+ else
+ setting = VO_1;
+ gp2a_i2c_write(ip, REGS_HYS, &setting);
+ }
+
+ ip->prox_value = val;
+ pr_err("gp2a: proximity val = %d\n", val);
+
+ /* 0 is close, 1 is far */
+ input_report_abs(ip->proximity_input_dev, ABS_DISTANCE, val);
+ input_sync(ip->proximity_input_dev);
+ wake_lock_timeout(&ip->prx_wake_lock, 3*HZ);
+#endif
+ return IRQ_HANDLED;
+}
+
+static int gp2a_setup_irq(struct gp2a_data *gp2a)
+{
+ int rc = -EIO;
+ struct gp2a_platform_data *pdata = gp2a->pdata;
+ int irq;
+
+ gp2a_dbgmsg("start\n");
+
+ rc = gpio_request(pdata->p_out, "gpio_proximity_out");
+ if (rc < 0) {
+ pr_err("%s: gpio %d request failed (%d)\n",
+ __func__, pdata->p_out, rc);
+ return rc;
+ }
+
+ rc = gpio_direction_input(pdata->p_out);
+ if (rc < 0) {
+ pr_err("%s: failed to set gpio %d as input (%d)\n",
+ __func__, pdata->p_out, rc);
+ goto err_gpio_direction_input;
+ }
+
+ irq = gpio_to_irq(pdata->p_out);
+ rc = request_threaded_irq(irq, NULL,
+ gp2a_irq_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "proximity_int",
+ gp2a);
+ if (rc < 0) {
+ pr_err("%s: request_irq(%d) failed for gpio %d (%d)\n",
+ __func__, irq, pdata->p_out, rc);
+ goto err_request_irq;
+ }
+
+ /* start with interrupts disabled */
+ disable_irq(irq);
+ gp2a->irq = irq;
+
+ gp2a_dbgmsg("success\n");
+
+ goto done;
+
+err_request_irq:
+err_gpio_direction_input:
+ gpio_free(pdata->p_out);
+done:
+ return rc;
+}
+
+
+
+static const struct file_operations light_fops = {
+ .owner = THIS_MODULE,
+};
+
+static struct miscdevice light_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "light",
+ .fops = &light_fops,
+};
+
+static int gp2a_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret = -ENODEV;
+ struct input_dev *input_dev;
+ struct gp2a_data *gp2a;
+ struct gp2a_platform_data *pdata = client->dev.platform_data;
+
+ pr_info("==============================\n");
+ pr_info("========= GP2A =======\n");
+ pr_info("==============================\n");
+
+ if (!pdata) {
+ pr_err("%s: missing pdata!\n", __func__);
+ return ret;
+ }
+ /*
+ if (!pdata->power || !pdata->light_adc_value) {
+ pr_err("%s: incomplete pdata!\n", __func__);
+ return ret;
+ }
+ */
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ pr_err("%s: i2c functionality check failed!\n", __func__);
+ return ret;
+ }
+
+ gp2a = kzalloc(sizeof(struct gp2a_data), GFP_KERNEL);
+ if (!gp2a) {
+ pr_err("%s: failed to alloc memory for module data\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+#if defined(CONFIG_OPTICAL_WAKE_ENABLE)
+ if (system_rev >= 0x03) {
+ pr_info("GP2A Reset GPIO = GPX0(1) (rev%02d)\n", system_rev);
+ gp2a->enable_wakeup = true;
+ } else {
+ pr_info("GP2A Reset GPIO = GPL0(6) (rev%02d)\n", system_rev);
+ gp2a->enable_wakeup = false;
+ }
+#else
+ gp2a->enable_wakeup = false;
+#endif
+
+ gp2a->pdata = pdata;
+ gp2a->i2c_client = client;
+ i2c_set_clientdata(client, gp2a);
+
+ /* wake lock init */
+ wake_lock_init(&gp2a->prx_wake_lock, WAKE_LOCK_SUSPEND,
+ "prx_wake_lock");
+ mutex_init(&gp2a->power_mutex);
+ mutex_init(&gp2a->adc_mutex);
+
+ ret = gp2a_setup_irq(gp2a);
+ if (ret) {
+ pr_err("%s: could not setup irq\n", __func__);
+ goto err_setup_irq;
+ }
+
+ /* allocate proximity input_device */
+ input_dev = input_allocate_device();
+ if (!input_dev) {
+ pr_err("%s: could not allocate input device\n", __func__);
+ goto err_input_allocate_device_proximity;
+ }
+
+ gp2a->proximity_input_dev = input_dev;
+ input_set_drvdata(input_dev, gp2a);
+ input_dev->name = "proximity_sensor";
+ input_set_capability(input_dev, EV_ABS, ABS_DISTANCE);
+ input_set_abs_params(input_dev, ABS_DISTANCE, 0, 1, 0, 0);
+
+ gp2a_dbgmsg("registering proximity input device\n");
+ ret = input_register_device(input_dev);
+ if (ret < 0) {
+ pr_err("%s: could not register input device\n", __func__);
+ input_free_device(input_dev);
+ goto err_input_register_device_proximity;
+ }
+
+ ret = sysfs_create_group(&input_dev->dev.kobj,
+ &proximity_attribute_group);
+ if (ret) {
+ pr_err("%s: could not create sysfs group\n", __func__);
+ goto err_sysfs_create_group_proximity;
+ }
+
+ /* hrtimer settings. we poll for light values using a timer. */
+ hrtimer_init(&gp2a->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ gp2a->light_poll_delay =
+ ns_to_ktime(LIGHT_TIMER_PERIOD_MS * NSEC_PER_MSEC);
+ gp2a->timer.function = gp2a_timer_func;
+
+ /* the timer just fires off a work queue request. we need a thread
+ to read the i2c (can be slow and blocking). */
+ gp2a->wq = create_singlethread_workqueue("gp2a_wq");
+ if (!gp2a->wq) {
+ ret = -ENOMEM;
+ pr_err("%s: could not create workqueue\n", __func__);
+ goto err_create_workqueue;
+ }
+
+ /* this is the thread function we run on the work queue */
+ INIT_WORK(&gp2a->work_light, gp2a_work_func_light);
+
+#ifdef GP2A_MODE_B
+ /* this is the thread function we run on the work queue */
+ INIT_WORK(&gp2a->work_proximity, gp2a_work_func_proximity);
+#endif
+
+ /* allocate lightsensor-level input_device */
+ input_dev = input_allocate_device();
+ if (!input_dev) {
+ pr_err("%s: could not allocate input device\n", __func__);
+ ret = -ENOMEM;
+ goto err_input_allocate_device_light;
+ }
+
+ input_set_drvdata(input_dev, gp2a);
+ input_dev->name = "light_sensor";
+ input_set_capability(input_dev, EV_ABS, ABS_MISC);
+ input_set_abs_params(input_dev, ABS_MISC, 0, 1, 0, 0);
+
+ gp2a_dbgmsg("registering lightsensor-level input device\n");
+ ret = input_register_device(input_dev);
+ if (ret < 0) {
+ pr_err("%s: could not register input device\n", __func__);
+ input_free_device(input_dev);
+ goto err_input_register_device_light;
+ }
+
+ gp2a->light_input_dev = input_dev;
+ ret = sysfs_create_group(&input_dev->dev.kobj,
+ &light_attribute_group);
+ if (ret) {
+ pr_err("%s: could not create sysfs group\n", __func__);
+ goto err_sysfs_create_group_light;
+ }
+
+ /* alloc platform device for adc client */
+ pdev_gp2a_adc = platform_device_alloc("gp2a-adc", -1);
+ if (!pdev_gp2a_adc) {
+ pr_err("%s: could not allocation pdev_gp2a_adc.\n", __func__);
+ ret = -ENOMEM;
+ goto err_platform_allocate_device_adc;
+ }
+
+ /* Register adc client */
+ gp2a->padc = s3c_adc_register(pdev_gp2a_adc, NULL, NULL, 0);
+
+ if (IS_ERR(gp2a->padc)) {
+ dev_err(&pdev_gp2a_adc->dev, "cannot register adc\n");
+ ret = PTR_ERR(gp2a->padc);
+ goto err_platform_register_device_adc;
+ }
+
+ /* set sysfs for light sensor */
+
+ ret = misc_register(&light_device);
+ if (ret)
+ pr_err(KERN_ERR "misc_register failed - light\n");
+
+ gp2a->lightsensor_class = class_create(THIS_MODULE, "lightsensor");
+ if (IS_ERR(gp2a->lightsensor_class))
+ pr_err("Failed to create class(lightsensor)!\n");
+
+ gp2a->switch_cmd_dev = device_create(gp2a->lightsensor_class,
+ NULL, 0, NULL, "switch_cmd");
+ if (IS_ERR(gp2a->switch_cmd_dev))
+ pr_err("Failed to create device(switch_cmd_dev)!\n");
+
+ if (device_create_file(gp2a->switch_cmd_dev,
+ &dev_attr_lightsensor_file_illuminance) < 0)
+ pr_err("Failed to create device file(%s)!\n",
+ dev_attr_lightsensor_file_illuminance.attr.name);
+
+ dev_set_drvdata(gp2a->switch_cmd_dev, gp2a);
+
+/* new sysfs */
+ ret = sensors_register(gp2a->light_dev, gp2a, light_sensor_attrs,
+ "light_sensor");
+ if (ret) {
+ pr_err("%s: cound not register light sensor device(%d).\n",
+ __func__, ret);
+ goto out_light_sensor_register_failed;
+ }
+
+ ret = sensors_register(gp2a->proximity_dev,
+ gp2a, proximity_sensor_attrs, "proximity_sensor");
+ if (ret) {
+ pr_err("%s: cound not register proximity sensor device(%d).\n",
+ __func__, ret);
+ goto out_proximity_sensor_register_failed;
+ }
+
+ /* set initial proximity value as 1 */
+ gp2a->prox_value = 1;
+ input_report_abs(gp2a->proximity_input_dev, ABS_DISTANCE, 1);
+ input_sync(gp2a->proximity_input_dev);
+
+ gp2a->adc_total = 0;
+
+ goto done;
+
+ /* error, unwind it all */
+out_light_sensor_register_failed:
+ sensors_unregister(gp2a->light_dev);
+out_proximity_sensor_register_failed:
+ sensors_unregister(gp2a->proximity_dev);
+
+err_sysfs_create_group_light:
+ input_unregister_device(gp2a->light_input_dev);
+err_input_register_device_light:
+err_input_allocate_device_light:
+ destroy_workqueue(gp2a->wq);
+err_platform_allocate_device_adc:
+ platform_device_unregister(pdev_gp2a_adc);
+err_platform_register_device_adc:
+ s3c_adc_release(gp2a->padc);
+err_create_workqueue:
+ sysfs_remove_group(&gp2a->proximity_input_dev->dev.kobj,
+ &proximity_attribute_group);
+err_sysfs_create_group_proximity:
+ input_unregister_device(gp2a->proximity_input_dev);
+err_input_register_device_proximity:
+err_input_allocate_device_proximity:
+ free_irq(gp2a->irq, 0);
+ gpio_free(gp2a->pdata->p_out);
+err_setup_irq:
+ mutex_destroy(&gp2a->adc_mutex);
+ mutex_destroy(&gp2a->power_mutex);
+
+ wake_lock_destroy(&gp2a->prx_wake_lock);
+ kfree(gp2a);
+done:
+ return ret;
+}
+
+static int gp2a_suspend(struct device *dev)
+{
+ /* We disable power only if proximity is disabled. If proximity
+ is enabled, we leave power on because proximity is allowed
+ to wake up device. We remove power without changing
+ gp2a->power_state because we use that state in resume.
+ */
+ struct i2c_client *client = to_i2c_client(dev);
+ struct gp2a_data *gp2a = i2c_get_clientdata(client);
+
+ if (gp2a->power_state & LIGHT_ENABLED)
+ gp2a_light_disable(gp2a);
+ /* if (gp2a->power_state == LIGHT_ENABLED)
+ gp2a->pdata->power(false); */
+ return 0;
+}
+
+static void gp2a_shutdown(struct i2c_client *client)
+{
+ struct gp2a_data *gp2a = i2c_get_clientdata(client);
+
+ pr_err("%s\n", __func__);
+ if (gp2a->power_state & LIGHT_ENABLED)
+ gp2a_light_disable(gp2a);
+ /* if (gp2a->power_state == LIGHT_ENABLED)
+ gp2a->pdata->power(false); */
+}
+
+static int gp2a_resume(struct device *dev)
+{
+ /* Turn power back on if we were before suspend. */
+ struct i2c_client *client = to_i2c_client(dev);
+ struct gp2a_data *gp2a = i2c_get_clientdata(client);
+ /* if (gp2a->power_state == LIGHT_ENABLED)
+ gp2a->pdata->power(true); */
+ if (gp2a->power_state & LIGHT_ENABLED)
+ gp2a_light_enable(gp2a);
+ return 0;
+}
+
+static int gp2a_i2c_remove(struct i2c_client *client)
+{
+ struct gp2a_data *gp2a = i2c_get_clientdata(client);
+ sysfs_remove_group(&gp2a->light_input_dev->dev.kobj,
+ &light_attribute_group);
+ input_unregister_device(gp2a->light_input_dev);
+ sysfs_remove_group(&gp2a->proximity_input_dev->dev.kobj,
+ &proximity_attribute_group);
+ input_unregister_device(gp2a->proximity_input_dev);
+
+ platform_device_unregister(pdev_gp2a_adc);
+ free_irq(gp2a->irq, NULL);
+ gpio_free(gp2a->pdata->p_out);
+ if (gp2a->power_state) {
+ if (gp2a->power_state & LIGHT_ENABLED)
+ gp2a_light_disable(gp2a);
+ /* gp2a->pdata->power(false); */
+ }
+
+ destroy_workqueue(gp2a->wq);
+ mutex_destroy(&gp2a->power_mutex);
+ mutex_destroy(&gp2a->adc_mutex);
+
+ wake_lock_destroy(&gp2a->prx_wake_lock);
+ s3c_adc_release(gp2a->padc);
+ kfree(gp2a);
+ return 0;
+}
+
+static const struct i2c_device_id gp2a_device_id[] = {
+ {"gp2a", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, gp2a_device_id);
+
+static const struct dev_pm_ops gp2a_pm_ops = {
+ .suspend = gp2a_suspend,
+ .resume = gp2a_resume,
+};
+
+static struct i2c_driver gp2a_i2c_driver = {
+ .driver = {
+ .name = "gp2a",
+ .owner = THIS_MODULE,
+ .pm = &gp2a_pm_ops
+ },
+ .probe = gp2a_i2c_probe,
+ .remove = gp2a_i2c_remove,
+ .id_table = gp2a_device_id,
+ .shutdown = gp2a_shutdown
+};
+
+static int __init gp2a_init(void)
+{
+ return i2c_add_driver(&gp2a_i2c_driver);
+}
+
+static void __exit gp2a_exit(void)
+{
+ i2c_del_driver(&gp2a_i2c_driver);
+}
+
+module_init(gp2a_init);
+module_exit(gp2a_exit);
+
+MODULE_AUTHOR("mjchen@sta.samsung.com");
+MODULE_DESCRIPTION("Optical Sensor driver for gp2ap002a00f");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/gp2a.h b/drivers/input/misc/gp2a.h
new file mode 100755
index 0000000..16db799
--- /dev/null
+++ b/drivers/input/misc/gp2a.h
@@ -0,0 +1,162 @@
+#ifndef __GP2A_H__
+#define __GP2A_H__
+
+#define I2C_M_WR 0 /* for i2c */
+#define I2c_M_RD 1 /* for i2c */
+
+
+//#define DELAY_PRX 1
+
+#define IRQ_GP2A_INT IRQ_EINT2 /*s3c64xx int number */
+
+#define I2C_DF_NOTIFY 0x01 /* for i2c */
+
+//ADDSEL is LOW
+#define GP2A_ADDR 0x88 /* slave addr for i2c */
+
+#define REGS_PROX 0x0 // Read Only
+#define REGS_GAIN 0x1 // Write Only
+#define REGS_HYS 0x2 // Write Only
+#define REGS_CYCLE 0x3 // Write Only
+#define REGS_OPMOD 0x4 // Write Only
+#if defined(CONFIG_GP2A_MODE_B)
+#define REGS_CON 0x6 // Write Only
+#endif
+
+/* sensor type */
+#define LIGHT 0
+#define PROXIMITY 1
+#define ALL 2
+
+/* power control */
+#define ON 1
+#define OFF 0
+
+/* IOCTL for proximity sensor */
+#define SHARP_GP2AP_IOC_MAGIC 'C'
+#define SHARP_GP2AP_OPEN _IO(SHARP_GP2AP_IOC_MAGIC,1)
+#define SHARP_GP2AP_CLOSE _IO(SHARP_GP2AP_IOC_MAGIC,2)
+
+/* input device for proximity sensor */
+#define USE_INPUT_DEVICE 0 /* 0 : No Use , 1: Use */
+
+
+#define INT_CLEAR 1 /* 0 = by polling operation, 1 = by interrupt operation */
+#define LIGHT_PERIOD 1 /* per sec */
+#define ADC_CHANNEL 9 /* index for s5pC110 9번 channel adc */
+
+/*for light sensor */
+#define STATE_NUM 3 /* number of states */
+#define LIGHT_LOW_LEVEL 1 /* brightness of lcd */
+#define LIGHT_MID_LEVEL 11
+#define LIGHT_HIGH_LEVEL 23
+
+#define ADC_BUFFER_NUM 6
+
+
+#define GUARDBAND_BOTTOM_ADC 700
+#define GUARDBAND_TOP_ADC 800
+
+
+
+
+/*
+ * STATE0 : 30 lux below
+ * STATE1 : 31~ 3000 lux
+ * STATE2 : 3000 lux over
+ */
+
+
+
+
+#define ADC_CUT_HIGH 1100 /* boundary line between STATE_0 and STATE_1 */
+#define ADC_CUT_LOW 220 /* boundary line between STATE_1 and STATE_2 */
+#define ADC_CUT_GAP 50 /* in order to prevent chattering condition */
+
+#define LIGHT_FOR_16LEVEL
+
+
+/* 16 level for premium model*/
+typedef enum t_light_state
+{
+ LIGHT_DIM = 0,
+ LIGHT_LEVEL1 = 1,
+ LIGHT_LEVEL2 = 2,
+ LIGHT_LEVEL3 = 3,
+ LIGHT_LEVEL4 = 4,
+ LIGHT_LEVEL5 = 5,
+ LIGHT_LEVEL6 = 6,
+ LIGHT_LEVEL7 = 7,
+ LIGHT_LEVEL8 = 8,
+ LIGHT_LEVEL9 = 9,
+ LIGHT_LEVEL10 = 10,
+ LIGHT_LEVEL11 = 11,
+ LIGHT_LEVEL12 = 12,
+ LIGHT_LEVEL13 = 13,
+ LIGHT_LEVEL14 = 14,
+ LIGHT_LEVEL15 = 15,
+ LIGHT_LEVEL16 = 16,
+ LIGHT_INIT = 17,
+}state_type;
+
+/* initial value for sensor register */
+static u8 gp2a_original_image[8] =
+{
+#if defined(CONFIG_GP2A_MODE_B)
+ 0x00, // REGS_PROX
+ 0x08, // REGS_GAIN
+ 0x40, // REGS_HYS
+ 0x04, // REGS_CYCLE
+ 0x03, // REGS_OPMOD
+#else
+ 0x00,
+ 0x08,
+ 0xC2,
+ 0x04,
+ 0x01,
+#endif
+};
+
+/* for state transition */
+struct _light_state {
+ state_type type;
+ int adc_bottom_limit;
+ int adc_top_limit;
+ int brightness;
+
+};
+
+/* driver data */
+struct gp2a_data {
+ struct input_dev *input_dev;
+ struct work_struct work_prox; /* for proximity sensor */
+ struct work_struct work_light; /* for light_sensor */
+ int irq;
+ struct hrtimer timer;
+ struct timer_list light_init_timer;
+
+};
+
+
+struct workqueue_struct *gp2a_wq;
+#if defined(CONFIG_GP2A_MODE_B)
+struct workqueue_struct *gp2a_wq_prox;
+#endif
+
+/* prototype */
+extern short gp2a_get_proximity_value(void);
+extern bool gp2a_get_lightsensor_status(void);
+int opt_i2c_read(u8 reg, u8 *val, unsigned int len );
+int opt_i2c_write( u8 reg, u8 *val );
+extern int s3c_adc_get_adc_data(int channel);
+void lightsensor_adjust_brightness(int level);
+extern int lightsensor_backlight_level_ctrl(int value);
+static int proximity_open(struct inode *ip, struct file *fp);
+static int proximity_release(struct inode *ip, struct file *fp);
+static int light_open(struct inode *ip, struct file *fp);
+static int light_release(struct inode *ip, struct file *fp);
+
+int lightsensor_get_adcvalue(void);
+
+
+#endif
diff --git a/drivers/input/misc/gpio_axis.c b/drivers/input/misc/gpio_axis.c
new file mode 100644
index 0000000..0acf4a5
--- /dev/null
+++ b/drivers/input/misc/gpio_axis.c
@@ -0,0 +1,192 @@
+/* drivers/input/misc/gpio_axis.c
+ *
+ * Copyright (C) 2007 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/gpio.h>
+#include <linux/gpio_event.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+
+struct gpio_axis_state {
+ struct gpio_event_input_devs *input_devs;
+ struct gpio_event_axis_info *info;
+ uint32_t pos;
+};
+
+uint16_t gpio_axis_4bit_gray_map_table[] = {
+ [0x0] = 0x0, [0x1] = 0x1, /* 0000 0001 */
+ [0x3] = 0x2, [0x2] = 0x3, /* 0011 0010 */
+ [0x6] = 0x4, [0x7] = 0x5, /* 0110 0111 */
+ [0x5] = 0x6, [0x4] = 0x7, /* 0101 0100 */
+ [0xc] = 0x8, [0xd] = 0x9, /* 1100 1101 */
+ [0xf] = 0xa, [0xe] = 0xb, /* 1111 1110 */
+ [0xa] = 0xc, [0xb] = 0xd, /* 1010 1011 */
+ [0x9] = 0xe, [0x8] = 0xf, /* 1001 1000 */
+};
+uint16_t gpio_axis_4bit_gray_map(struct gpio_event_axis_info *info, uint16_t in)
+{
+ return gpio_axis_4bit_gray_map_table[in];
+}
+
+uint16_t gpio_axis_5bit_singletrack_map_table[] = {
+ [0x10] = 0x00, [0x14] = 0x01, [0x1c] = 0x02, /* 10000 10100 11100 */
+ [0x1e] = 0x03, [0x1a] = 0x04, [0x18] = 0x05, /* 11110 11010 11000 */
+ [0x08] = 0x06, [0x0a] = 0x07, [0x0e] = 0x08, /* 01000 01010 01110 */
+ [0x0f] = 0x09, [0x0d] = 0x0a, [0x0c] = 0x0b, /* 01111 01101 01100 */
+ [0x04] = 0x0c, [0x05] = 0x0d, [0x07] = 0x0e, /* 00100 00101 00111 */
+ [0x17] = 0x0f, [0x16] = 0x10, [0x06] = 0x11, /* 10111 10110 00110 */
+ [0x02] = 0x12, [0x12] = 0x13, [0x13] = 0x14, /* 00010 10010 10011 */
+ [0x1b] = 0x15, [0x0b] = 0x16, [0x03] = 0x17, /* 11011 01011 00011 */
+ [0x01] = 0x18, [0x09] = 0x19, [0x19] = 0x1a, /* 00001 01001 11001 */
+ [0x1d] = 0x1b, [0x15] = 0x1c, [0x11] = 0x1d, /* 11101 10101 10001 */
+};
+uint16_t gpio_axis_5bit_singletrack_map(
+ struct gpio_event_axis_info *info, uint16_t in)
+{
+ return gpio_axis_5bit_singletrack_map_table[in];
+}
+
+static void gpio_event_update_axis(struct gpio_axis_state *as, int report)
+{
+ struct gpio_event_axis_info *ai = as->info;
+ int i;
+ int change;
+ uint16_t state = 0;
+ uint16_t pos;
+ uint16_t old_pos = as->pos;
+ for (i = ai->count - 1; i >= 0; i--)
+ state = (state << 1) | gpio_get_value(ai->gpio[i]);
+ pos = ai->map(ai, state);
+ if (ai->flags & GPIOEAF_PRINT_RAW)
+ pr_info("axis %d-%d raw %x, pos %d -> %d\n",
+ ai->type, ai->code, state, old_pos, pos);
+ if (report && pos != old_pos) {
+ if (ai->type == EV_REL) {
+ change = (ai->decoded_size + pos - old_pos) %
+ ai->decoded_size;
+ if (change > ai->decoded_size / 2)
+ change -= ai->decoded_size;
+ if (change == ai->decoded_size / 2) {
+ if (ai->flags & GPIOEAF_PRINT_EVENT)
+ pr_info("axis %d-%d unknown direction, "
+ "pos %d -> %d\n", ai->type,
+ ai->code, old_pos, pos);
+ change = 0; /* no closest direction */
+ }
+ if (ai->flags & GPIOEAF_PRINT_EVENT)
+ pr_info("axis %d-%d change %d\n",
+ ai->type, ai->code, change);
+ input_report_rel(as->input_devs->dev[ai->dev],
+ ai->code, change);
+ } else {
+ if (ai->flags & GPIOEAF_PRINT_EVENT)
+ pr_info("axis %d-%d now %d\n",
+ ai->type, ai->code, pos);
+ input_event(as->input_devs->dev[ai->dev],
+ ai->type, ai->code, pos);
+ }
+ input_sync(as->input_devs->dev[ai->dev]);
+ }
+ as->pos = pos;
+}
+
+static irqreturn_t gpio_axis_irq_handler(int irq, void *dev_id)
+{
+ struct gpio_axis_state *as = dev_id;
+ gpio_event_update_axis(as, 1);
+ return IRQ_HANDLED;
+}
+
+int gpio_event_axis_func(struct gpio_event_input_devs *input_devs,
+ struct gpio_event_info *info, void **data, int func)
+{
+ int ret;
+ int i;
+ int irq;
+ struct gpio_event_axis_info *ai;
+ struct gpio_axis_state *as;
+
+ ai = container_of(info, struct gpio_event_axis_info, info);
+ if (func == GPIO_EVENT_FUNC_SUSPEND) {
+ for (i = 0; i < ai->count; i++)
+ disable_irq(gpio_to_irq(ai->gpio[i]));
+ return 0;
+ }
+ if (func == GPIO_EVENT_FUNC_RESUME) {
+ for (i = 0; i < ai->count; i++)
+ enable_irq(gpio_to_irq(ai->gpio[i]));
+ return 0;
+ }
+
+ if (func == GPIO_EVENT_FUNC_INIT) {
+ *data = as = kmalloc(sizeof(*as), GFP_KERNEL);
+ if (as == NULL) {
+ ret = -ENOMEM;
+ goto err_alloc_axis_state_failed;
+ }
+ as->input_devs = input_devs;
+ as->info = ai;
+ if (ai->dev >= input_devs->count) {
+ pr_err("gpio_event_axis: bad device index %d >= %d "
+ "for %d:%d\n", ai->dev, input_devs->count,
+ ai->type, ai->code);
+ ret = -EINVAL;
+ goto err_bad_device_index;
+ }
+
+ input_set_capability(input_devs->dev[ai->dev],
+ ai->type, ai->code);
+ if (ai->type == EV_ABS) {
+ input_set_abs_params(input_devs->dev[ai->dev], ai->code,
+ 0, ai->decoded_size - 1, 0, 0);
+ }
+ for (i = 0; i < ai->count; i++) {
+ ret = gpio_request(ai->gpio[i], "gpio_event_axis");
+ if (ret < 0)
+ goto err_request_gpio_failed;
+ ret = gpio_direction_input(ai->gpio[i]);
+ if (ret < 0)
+ goto err_gpio_direction_input_failed;
+ ret = irq = gpio_to_irq(ai->gpio[i]);
+ if (ret < 0)
+ goto err_get_irq_num_failed;
+ ret = request_irq(irq, gpio_axis_irq_handler,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING,
+ "gpio_event_axis", as);
+ if (ret < 0)
+ goto err_request_irq_failed;
+ }
+ gpio_event_update_axis(as, 0);
+ return 0;
+ }
+
+ ret = 0;
+ as = *data;
+ for (i = ai->count - 1; i >= 0; i--) {
+ free_irq(gpio_to_irq(ai->gpio[i]), as);
+err_request_irq_failed:
+err_get_irq_num_failed:
+err_gpio_direction_input_failed:
+ gpio_free(ai->gpio[i]);
+err_request_gpio_failed:
+ ;
+ }
+err_bad_device_index:
+ kfree(as);
+ *data = NULL;
+err_alloc_axis_state_failed:
+ return ret;
+}
diff --git a/drivers/input/misc/gpio_event.c b/drivers/input/misc/gpio_event.c
new file mode 100644
index 0000000..a98be67
--- /dev/null
+++ b/drivers/input/misc/gpio_event.c
@@ -0,0 +1,260 @@
+/* drivers/input/misc/gpio_event.c
+ *
+ * Copyright (C) 2007 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/earlysuspend.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/gpio_event.h>
+#include <linux/hrtimer.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+struct gpio_event {
+ struct gpio_event_input_devs *input_devs;
+ const struct gpio_event_platform_data *info;
+ struct early_suspend early_suspend;
+ void *state[0];
+};
+
+static int gpio_input_event(
+ struct input_dev *dev, unsigned int type, unsigned int code, int value)
+{
+ int i;
+ int devnr;
+ int ret = 0;
+ int tmp_ret;
+ struct gpio_event_info **ii;
+ struct gpio_event *ip = input_get_drvdata(dev);
+
+ for (devnr = 0; devnr < ip->input_devs->count; devnr++)
+ if (ip->input_devs->dev[devnr] == dev)
+ break;
+ if (devnr == ip->input_devs->count) {
+ pr_err("gpio_input_event: unknown device %p\n", dev);
+ return -EIO;
+ }
+
+ for (i = 0, ii = ip->info->info; i < ip->info->info_count; i++, ii++) {
+ if ((*ii)->event) {
+ tmp_ret = (*ii)->event(ip->input_devs, *ii,
+ &ip->state[i],
+ devnr, type, code, value);
+ if (tmp_ret)
+ ret = tmp_ret;
+ }
+ }
+ return ret;
+}
+
+static int gpio_event_call_all_func(struct gpio_event *ip, int func)
+{
+ int i;
+ int ret;
+ struct gpio_event_info **ii;
+
+ if (func == GPIO_EVENT_FUNC_INIT || func == GPIO_EVENT_FUNC_RESUME) {
+ ii = ip->info->info;
+ for (i = 0; i < ip->info->info_count; i++, ii++) {
+ if ((*ii)->func == NULL) {
+ ret = -ENODEV;
+ pr_err("gpio_event_probe: Incomplete pdata, "
+ "no function\n");
+ goto err_no_func;
+ }
+ if (func == GPIO_EVENT_FUNC_RESUME && (*ii)->no_suspend)
+ continue;
+ ret = (*ii)->func(ip->input_devs, *ii, &ip->state[i],
+ func);
+ if (ret) {
+ pr_err("gpio_event_probe: function failed\n");
+ goto err_func_failed;
+ }
+ }
+ return 0;
+ }
+
+ ret = 0;
+ i = ip->info->info_count;
+ ii = ip->info->info + i;
+ while (i > 0) {
+ i--;
+ ii--;
+ if ((func & ~1) == GPIO_EVENT_FUNC_SUSPEND && (*ii)->no_suspend)
+ continue;
+ (*ii)->func(ip->input_devs, *ii, &ip->state[i], func & ~1);
+err_func_failed:
+err_no_func:
+ ;
+ }
+ return ret;
+}
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+void gpio_event_suspend(struct early_suspend *h)
+{
+ struct gpio_event *ip;
+ ip = container_of(h, struct gpio_event, early_suspend);
+ gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_SUSPEND);
+ ip->info->power(ip->info, 0);
+}
+
+void gpio_event_resume(struct early_suspend *h)
+{
+ struct gpio_event *ip;
+ ip = container_of(h, struct gpio_event, early_suspend);
+ ip->info->power(ip->info, 1);
+ gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_RESUME);
+}
+#endif
+
+static int gpio_event_probe(struct platform_device *pdev)
+{
+ int err;
+ struct gpio_event *ip;
+ struct gpio_event_platform_data *event_info;
+ int dev_count = 1;
+ int i;
+ int registered = 0;
+
+ event_info = pdev->dev.platform_data;
+ if (event_info == NULL) {
+ pr_err("gpio_event_probe: No pdata\n");
+ return -ENODEV;
+ }
+ if ((!event_info->name && !event_info->names[0]) ||
+ !event_info->info || !event_info->info_count) {
+ pr_err("gpio_event_probe: Incomplete pdata\n");
+ return -ENODEV;
+ }
+ if (!event_info->name)
+ while (event_info->names[dev_count])
+ dev_count++;
+ ip = kzalloc(sizeof(*ip) +
+ sizeof(ip->state[0]) * event_info->info_count +
+ sizeof(*ip->input_devs) +
+ sizeof(ip->input_devs->dev[0]) * dev_count, GFP_KERNEL);
+ if (ip == NULL) {
+ err = -ENOMEM;
+ pr_err("gpio_event_probe: Failed to allocate private data\n");
+ goto err_kp_alloc_failed;
+ }
+ ip->input_devs = (void*)&ip->state[event_info->info_count];
+ platform_set_drvdata(pdev, ip);
+
+ for (i = 0; i < dev_count; i++) {
+ struct input_dev *input_dev = input_allocate_device();
+ if (input_dev == NULL) {
+ err = -ENOMEM;
+ pr_err("gpio_event_probe: "
+ "Failed to allocate input device\n");
+ goto err_input_dev_alloc_failed;
+ }
+ input_set_drvdata(input_dev, ip);
+ input_dev->name = event_info->name ?
+ event_info->name : event_info->names[i];
+ input_dev->event = gpio_input_event;
+ ip->input_devs->dev[i] = input_dev;
+ }
+ ip->input_devs->count = dev_count;
+ ip->info = event_info;
+ if (event_info->power) {
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ ip->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
+ ip->early_suspend.suspend = gpio_event_suspend;
+ ip->early_suspend.resume = gpio_event_resume;
+ register_early_suspend(&ip->early_suspend);
+#endif
+ ip->info->power(ip->info, 1);
+ }
+
+ err = gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_INIT);
+ if (err)
+ goto err_call_all_func_failed;
+
+ for (i = 0; i < dev_count; i++) {
+ err = input_register_device(ip->input_devs->dev[i]);
+ if (err) {
+ pr_err("gpio_event_probe: Unable to register %s "
+ "input device\n", ip->input_devs->dev[i]->name);
+ goto err_input_register_device_failed;
+ }
+ registered++;
+ }
+
+ return 0;
+
+err_input_register_device_failed:
+ gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_UNINIT);
+err_call_all_func_failed:
+ if (event_info->power) {
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ unregister_early_suspend(&ip->early_suspend);
+#endif
+ ip->info->power(ip->info, 0);
+ }
+ for (i = 0; i < registered; i++)
+ input_unregister_device(ip->input_devs->dev[i]);
+ for (i = dev_count - 1; i >= registered; i--) {
+ input_free_device(ip->input_devs->dev[i]);
+err_input_dev_alloc_failed:
+ ;
+ }
+ kfree(ip);
+err_kp_alloc_failed:
+ return err;
+}
+
+static int gpio_event_remove(struct platform_device *pdev)
+{
+ struct gpio_event *ip = platform_get_drvdata(pdev);
+ int i;
+
+ gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_UNINIT);
+ if (ip->info->power) {
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ unregister_early_suspend(&ip->early_suspend);
+#endif
+ ip->info->power(ip->info, 0);
+ }
+ for (i = 0; i < ip->input_devs->count; i++)
+ input_unregister_device(ip->input_devs->dev[i]);
+ kfree(ip);
+ return 0;
+}
+
+static struct platform_driver gpio_event_driver = {
+ .probe = gpio_event_probe,
+ .remove = gpio_event_remove,
+ .driver = {
+ .name = GPIO_EVENT_DEV_NAME,
+ },
+};
+
+static int __devinit gpio_event_init(void)
+{
+ return platform_driver_register(&gpio_event_driver);
+}
+
+static void __exit gpio_event_exit(void)
+{
+ platform_driver_unregister(&gpio_event_driver);
+}
+
+module_init(gpio_event_init);
+module_exit(gpio_event_exit);
+
+MODULE_DESCRIPTION("GPIO Event Driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/input/misc/gpio_input.c b/drivers/input/misc/gpio_input.c
new file mode 100644
index 0000000..6a0c315
--- /dev/null
+++ b/drivers/input/misc/gpio_input.c
@@ -0,0 +1,376 @@
+/* drivers/input/misc/gpio_input.c
+ *
+ * Copyright (C) 2007 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/gpio.h>
+#include <linux/gpio_event.h>
+#include <linux/hrtimer.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/wakelock.h>
+
+enum {
+ DEBOUNCE_UNSTABLE = BIT(0), /* Got irq, while debouncing */
+ DEBOUNCE_PRESSED = BIT(1),
+ DEBOUNCE_NOTPRESSED = BIT(2),
+ DEBOUNCE_WAIT_IRQ = BIT(3), /* Stable irq state */
+ DEBOUNCE_POLL = BIT(4), /* Stable polling state */
+
+ DEBOUNCE_UNKNOWN =
+ DEBOUNCE_PRESSED | DEBOUNCE_NOTPRESSED,
+};
+
+struct gpio_key_state {
+ struct gpio_input_state *ds;
+ uint8_t debounce;
+};
+
+struct gpio_input_state {
+ struct gpio_event_input_devs *input_devs;
+ const struct gpio_event_input_info *info;
+ struct hrtimer timer;
+ int use_irq;
+ int debounce_count;
+ spinlock_t irq_lock;
+ struct wake_lock wake_lock;
+ struct gpio_key_state key_state[0];
+};
+
+static enum hrtimer_restart gpio_event_input_timer_func(struct hrtimer *timer)
+{
+ int i;
+ int pressed;
+ struct gpio_input_state *ds =
+ container_of(timer, struct gpio_input_state, timer);
+ unsigned gpio_flags = ds->info->flags;
+ unsigned npolarity;
+ int nkeys = ds->info->keymap_size;
+ const struct gpio_event_direct_entry *key_entry;
+ struct gpio_key_state *key_state;
+ unsigned long irqflags;
+ uint8_t debounce;
+ bool sync_needed;
+
+#if 0
+ key_entry = kp->keys_info->keymap;
+ key_state = kp->key_state;
+ for (i = 0; i < nkeys; i++, key_entry++, key_state++)
+ pr_info("gpio_read_detect_status %d %d\n", key_entry->gpio,
+ gpio_read_detect_status(key_entry->gpio));
+#endif
+ key_entry = ds->info->keymap;
+ key_state = ds->key_state;
+ sync_needed = false;
+ spin_lock_irqsave(&ds->irq_lock, irqflags);
+ for (i = 0; i < nkeys; i++, key_entry++, key_state++) {
+ debounce = key_state->debounce;
+ if (debounce & DEBOUNCE_WAIT_IRQ)
+ continue;
+ if (key_state->debounce & DEBOUNCE_UNSTABLE) {
+ debounce = key_state->debounce = DEBOUNCE_UNKNOWN;
+ enable_irq(gpio_to_irq(key_entry->gpio));
+ if (gpio_flags & GPIOEDF_PRINT_KEY_UNSTABLE)
+ pr_info("gpio_keys_scan_keys: key %x-%x, %d "
+ "(%d) continue debounce\n",
+ ds->info->type, key_entry->code,
+ i, key_entry->gpio);
+ }
+ npolarity = !(gpio_flags & GPIOEDF_ACTIVE_HIGH);
+ pressed = gpio_get_value(key_entry->gpio) ^ npolarity;
+ if (debounce & DEBOUNCE_POLL) {
+ if (pressed == !(debounce & DEBOUNCE_PRESSED)) {
+ ds->debounce_count++;
+ key_state->debounce = DEBOUNCE_UNKNOWN;
+ if (gpio_flags & GPIOEDF_PRINT_KEY_DEBOUNCE)
+ pr_info("gpio_keys_scan_keys: key %x-"
+ "%x, %d (%d) start debounce\n",
+ ds->info->type, key_entry->code,
+ i, key_entry->gpio);
+ }
+ continue;
+ }
+ if (pressed && (debounce & DEBOUNCE_NOTPRESSED)) {
+ if (gpio_flags & GPIOEDF_PRINT_KEY_DEBOUNCE)
+ pr_info("gpio_keys_scan_keys: key %x-%x, %d "
+ "(%d) debounce pressed 1\n",
+ ds->info->type, key_entry->code,
+ i, key_entry->gpio);
+ key_state->debounce = DEBOUNCE_PRESSED;
+ continue;
+ }
+ if (!pressed && (debounce & DEBOUNCE_PRESSED)) {
+ if (gpio_flags & GPIOEDF_PRINT_KEY_DEBOUNCE)
+ pr_info("gpio_keys_scan_keys: key %x-%x, %d "
+ "(%d) debounce pressed 0\n",
+ ds->info->type, key_entry->code,
+ i, key_entry->gpio);
+ key_state->debounce = DEBOUNCE_NOTPRESSED;
+ continue;
+ }
+ /* key is stable */
+ ds->debounce_count--;
+ if (ds->use_irq)
+ key_state->debounce |= DEBOUNCE_WAIT_IRQ;
+ else
+ key_state->debounce |= DEBOUNCE_POLL;
+ if (gpio_flags & GPIOEDF_PRINT_KEYS)
+ pr_info("gpio_keys_scan_keys: key %x-%x, %d (%d) "
+ "changed to %d\n", ds->info->type,
+ key_entry->code, i, key_entry->gpio, pressed);
+ input_event(ds->input_devs->dev[key_entry->dev], ds->info->type,
+ key_entry->code, pressed);
+ sync_needed = true;
+ }
+ if (sync_needed) {
+ for (i = 0; i < ds->input_devs->count; i++)
+ input_sync(ds->input_devs->dev[i]);
+ }
+
+#if 0
+ key_entry = kp->keys_info->keymap;
+ key_state = kp->key_state;
+ for (i = 0; i < nkeys; i++, key_entry++, key_state++) {
+ pr_info("gpio_read_detect_status %d %d\n", key_entry->gpio,
+ gpio_read_detect_status(key_entry->gpio));
+ }
+#endif
+
+ if (ds->debounce_count)
+ hrtimer_start(timer, ds->info->debounce_time, HRTIMER_MODE_REL);
+ else if (!ds->use_irq)
+ hrtimer_start(timer, ds->info->poll_time, HRTIMER_MODE_REL);
+ else
+ wake_unlock(&ds->wake_lock);
+
+ spin_unlock_irqrestore(&ds->irq_lock, irqflags);
+
+ return HRTIMER_NORESTART;
+}
+
+static irqreturn_t gpio_event_input_irq_handler(int irq, void *dev_id)
+{
+ struct gpio_key_state *ks = dev_id;
+ struct gpio_input_state *ds = ks->ds;
+ int keymap_index = ks - ds->key_state;
+ const struct gpio_event_direct_entry *key_entry;
+ unsigned long irqflags;
+ int pressed;
+
+ if (!ds->use_irq)
+ return IRQ_HANDLED;
+
+ key_entry = &ds->info->keymap[keymap_index];
+
+ if (ds->info->debounce_time.tv64) {
+ spin_lock_irqsave(&ds->irq_lock, irqflags);
+ if (ks->debounce & DEBOUNCE_WAIT_IRQ) {
+ ks->debounce = DEBOUNCE_UNKNOWN;
+ if (ds->debounce_count++ == 0) {
+ wake_lock(&ds->wake_lock);
+ hrtimer_start(
+ &ds->timer, ds->info->debounce_time,
+ HRTIMER_MODE_REL);
+ }
+ if (ds->info->flags & GPIOEDF_PRINT_KEY_DEBOUNCE)
+ pr_info("gpio_event_input_irq_handler: "
+ "key %x-%x, %d (%d) start debounce\n",
+ ds->info->type, key_entry->code,
+ keymap_index, key_entry->gpio);
+ } else {
+ disable_irq_nosync(irq);
+ ks->debounce = DEBOUNCE_UNSTABLE;
+ }
+ spin_unlock_irqrestore(&ds->irq_lock, irqflags);
+ } else {
+ pressed = gpio_get_value(key_entry->gpio) ^
+ !(ds->info->flags & GPIOEDF_ACTIVE_HIGH);
+ if (ds->info->flags & GPIOEDF_PRINT_KEYS)
+ pr_info("gpio_event_input_irq_handler: key %x-%x, %d "
+ "(%d) changed to %d\n",
+ ds->info->type, key_entry->code, keymap_index,
+ key_entry->gpio, pressed);
+ input_event(ds->input_devs->dev[key_entry->dev], ds->info->type,
+ key_entry->code, pressed);
+ input_sync(ds->input_devs->dev[key_entry->dev]);
+ }
+ return IRQ_HANDLED;
+}
+
+static int gpio_event_input_request_irqs(struct gpio_input_state *ds)
+{
+ int i;
+ int err;
+ unsigned int irq;
+ unsigned long req_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
+
+ for (i = 0; i < ds->info->keymap_size; i++) {
+ err = irq = gpio_to_irq(ds->info->keymap[i].gpio);
+ if (err < 0)
+ goto err_gpio_get_irq_num_failed;
+ err = request_irq(irq, gpio_event_input_irq_handler,
+ req_flags, "gpio_keys", &ds->key_state[i]);
+ if (err) {
+ pr_err("gpio_event_input_request_irqs: request_irq "
+ "failed for input %d, irq %d\n",
+ ds->info->keymap[i].gpio, irq);
+ goto err_request_irq_failed;
+ }
+ if (ds->info->info.no_suspend) {
+ err = enable_irq_wake(irq);
+ if (err) {
+ pr_err("gpio_event_input_request_irqs: "
+ "enable_irq_wake failed for input %d, "
+ "irq %d\n",
+ ds->info->keymap[i].gpio, irq);
+ goto err_enable_irq_wake_failed;
+ }
+ }
+ }
+ return 0;
+
+ for (i = ds->info->keymap_size - 1; i >= 0; i--) {
+ irq = gpio_to_irq(ds->info->keymap[i].gpio);
+ if (ds->info->info.no_suspend)
+ disable_irq_wake(irq);
+err_enable_irq_wake_failed:
+ free_irq(irq, &ds->key_state[i]);
+err_request_irq_failed:
+err_gpio_get_irq_num_failed:
+ ;
+ }
+ return err;
+}
+
+int gpio_event_input_func(struct gpio_event_input_devs *input_devs,
+ struct gpio_event_info *info, void **data, int func)
+{
+ int ret;
+ int i;
+ unsigned long irqflags;
+ struct gpio_event_input_info *di;
+ struct gpio_input_state *ds = *data;
+
+ di = container_of(info, struct gpio_event_input_info, info);
+
+ if (func == GPIO_EVENT_FUNC_SUSPEND) {
+ if (ds->use_irq)
+ for (i = 0; i < di->keymap_size; i++)
+ disable_irq(gpio_to_irq(di->keymap[i].gpio));
+ hrtimer_cancel(&ds->timer);
+ return 0;
+ }
+ if (func == GPIO_EVENT_FUNC_RESUME) {
+ spin_lock_irqsave(&ds->irq_lock, irqflags);
+ if (ds->use_irq)
+ for (i = 0; i < di->keymap_size; i++)
+ enable_irq(gpio_to_irq(di->keymap[i].gpio));
+ hrtimer_start(&ds->timer, ktime_set(0, 0), HRTIMER_MODE_REL);
+ spin_unlock_irqrestore(&ds->irq_lock, irqflags);
+ return 0;
+ }
+
+ if (func == GPIO_EVENT_FUNC_INIT) {
+ if (ktime_to_ns(di->poll_time) <= 0)
+ di->poll_time = ktime_set(0, 20 * NSEC_PER_MSEC);
+
+ *data = ds = kzalloc(sizeof(*ds) + sizeof(ds->key_state[0]) *
+ di->keymap_size, GFP_KERNEL);
+ if (ds == NULL) {
+ ret = -ENOMEM;
+ pr_err("gpio_event_input_func: "
+ "Failed to allocate private data\n");
+ goto err_ds_alloc_failed;
+ }
+ ds->debounce_count = di->keymap_size;
+ ds->input_devs = input_devs;
+ ds->info = di;
+ wake_lock_init(&ds->wake_lock, WAKE_LOCK_SUSPEND, "gpio_input");
+ spin_lock_init(&ds->irq_lock);
+
+ for (i = 0; i < di->keymap_size; i++) {
+ int dev = di->keymap[i].dev;
+ if (dev >= input_devs->count) {
+ pr_err("gpio_event_input_func: bad device "
+ "index %d >= %d for key code %d\n",
+ dev, input_devs->count,
+ di->keymap[i].code);
+ ret = -EINVAL;
+ goto err_bad_keymap;
+ }
+ input_set_capability(input_devs->dev[dev], di->type,
+ di->keymap[i].code);
+ ds->key_state[i].ds = ds;
+ ds->key_state[i].debounce = DEBOUNCE_UNKNOWN;
+ }
+
+ for (i = 0; i < di->keymap_size; i++) {
+ ret = gpio_request(di->keymap[i].gpio, "gpio_kp_in");
+ if (ret) {
+ pr_err("gpio_event_input_func: gpio_request "
+ "failed for %d\n", di->keymap[i].gpio);
+ goto err_gpio_request_failed;
+ }
+ ret = gpio_direction_input(di->keymap[i].gpio);
+ if (ret) {
+ pr_err("gpio_event_input_func: "
+ "gpio_direction_input failed for %d\n",
+ di->keymap[i].gpio);
+ goto err_gpio_configure_failed;
+ }
+ }
+
+ ret = gpio_event_input_request_irqs(ds);
+
+ spin_lock_irqsave(&ds->irq_lock, irqflags);
+ ds->use_irq = ret == 0;
+
+ pr_info("GPIO Input Driver: Start gpio inputs for %s%s in %s "
+ "mode\n", input_devs->dev[0]->name,
+ (input_devs->count > 1) ? "..." : "",
+ ret == 0 ? "interrupt" : "polling");
+
+ hrtimer_init(&ds->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ ds->timer.function = gpio_event_input_timer_func;
+ hrtimer_start(&ds->timer, ktime_set(0, 0), HRTIMER_MODE_REL);
+ spin_unlock_irqrestore(&ds->irq_lock, irqflags);
+ return 0;
+ }
+
+ ret = 0;
+ spin_lock_irqsave(&ds->irq_lock, irqflags);
+ hrtimer_cancel(&ds->timer);
+ if (ds->use_irq) {
+ for (i = di->keymap_size - 1; i >= 0; i--) {
+ int irq = gpio_to_irq(di->keymap[i].gpio);
+ if (ds->info->info.no_suspend)
+ disable_irq_wake(irq);
+ free_irq(irq, &ds->key_state[i]);
+ }
+ }
+ spin_unlock_irqrestore(&ds->irq_lock, irqflags);
+
+ for (i = di->keymap_size - 1; i >= 0; i--) {
+err_gpio_configure_failed:
+ gpio_free(di->keymap[i].gpio);
+err_gpio_request_failed:
+ ;
+ }
+err_bad_keymap:
+ wake_lock_destroy(&ds->wake_lock);
+ kfree(ds);
+err_ds_alloc_failed:
+ return ret;
+}
diff --git a/drivers/input/misc/gpio_matrix.c b/drivers/input/misc/gpio_matrix.c
new file mode 100644
index 0000000..eaa9e89
--- /dev/null
+++ b/drivers/input/misc/gpio_matrix.c
@@ -0,0 +1,441 @@
+/* drivers/input/misc/gpio_matrix.c
+ *
+ * Copyright (C) 2007 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/gpio.h>
+#include <linux/gpio_event.h>
+#include <linux/hrtimer.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/wakelock.h>
+
+struct gpio_kp {
+ struct gpio_event_input_devs *input_devs;
+ struct gpio_event_matrix_info *keypad_info;
+ struct hrtimer timer;
+ struct wake_lock wake_lock;
+ int current_output;
+ unsigned int use_irq:1;
+ unsigned int key_state_changed:1;
+ unsigned int last_key_state_changed:1;
+ unsigned int some_keys_pressed:2;
+ unsigned int disabled_irq:1;
+ unsigned long keys_pressed[0];
+};
+
+static void clear_phantom_key(struct gpio_kp *kp, int out, int in)
+{
+ struct gpio_event_matrix_info *mi = kp->keypad_info;
+ int key_index = out * mi->ninputs + in;
+ unsigned short keyentry = mi->keymap[key_index];
+ unsigned short keycode = keyentry & MATRIX_KEY_MASK;
+ unsigned short dev = keyentry >> MATRIX_CODE_BITS;
+
+ if (!test_bit(keycode, kp->input_devs->dev[dev]->key)) {
+ if (mi->flags & GPIOKPF_PRINT_PHANTOM_KEYS)
+ pr_info("gpiomatrix: phantom key %x, %d-%d (%d-%d) "
+ "cleared\n", keycode, out, in,
+ mi->output_gpios[out], mi->input_gpios[in]);
+ __clear_bit(key_index, kp->keys_pressed);
+ } else {
+ if (mi->flags & GPIOKPF_PRINT_PHANTOM_KEYS)
+ pr_info("gpiomatrix: phantom key %x, %d-%d (%d-%d) "
+ "not cleared\n", keycode, out, in,
+ mi->output_gpios[out], mi->input_gpios[in]);
+ }
+}
+
+static int restore_keys_for_input(struct gpio_kp *kp, int out, int in)
+{
+ int rv = 0;
+ int key_index;
+
+ key_index = out * kp->keypad_info->ninputs + in;
+ while (out < kp->keypad_info->noutputs) {
+ if (test_bit(key_index, kp->keys_pressed)) {
+ rv = 1;
+ clear_phantom_key(kp, out, in);
+ }
+ key_index += kp->keypad_info->ninputs;
+ out++;
+ }
+ return rv;
+}
+
+static void remove_phantom_keys(struct gpio_kp *kp)
+{
+ int out, in, inp;
+ int key_index;
+
+ if (kp->some_keys_pressed < 3)
+ return;
+
+ for (out = 0; out < kp->keypad_info->noutputs; out++) {
+ inp = -1;
+ key_index = out * kp->keypad_info->ninputs;
+ for (in = 0; in < kp->keypad_info->ninputs; in++, key_index++) {
+ if (test_bit(key_index, kp->keys_pressed)) {
+ if (inp == -1) {
+ inp = in;
+ continue;
+ }
+ if (inp >= 0) {
+ if (!restore_keys_for_input(kp, out + 1,
+ inp))
+ break;
+ clear_phantom_key(kp, out, inp);
+ inp = -2;
+ }
+ restore_keys_for_input(kp, out, in);
+ }
+ }
+ }
+}
+
+static void report_key(struct gpio_kp *kp, int key_index, int out, int in)
+{
+ struct gpio_event_matrix_info *mi = kp->keypad_info;
+ int pressed = test_bit(key_index, kp->keys_pressed);
+ unsigned short keyentry = mi->keymap[key_index];
+ unsigned short keycode = keyentry & MATRIX_KEY_MASK;
+ unsigned short dev = keyentry >> MATRIX_CODE_BITS;
+
+ if (pressed != test_bit(keycode, kp->input_devs->dev[dev]->key)) {
+ if (keycode == KEY_RESERVED) {
+ if (mi->flags & GPIOKPF_PRINT_UNMAPPED_KEYS)
+ pr_info("gpiomatrix: unmapped key, %d-%d "
+ "(%d-%d) changed to %d\n",
+ out, in, mi->output_gpios[out],
+ mi->input_gpios[in], pressed);
+ } else {
+ if (mi->flags & GPIOKPF_PRINT_MAPPED_KEYS)
+ pr_info("gpiomatrix: key %x, %d-%d (%d-%d) "
+ "changed to %d\n", keycode,
+ out, in, mi->output_gpios[out],
+ mi->input_gpios[in], pressed);
+ input_report_key(kp->input_devs->dev[dev], keycode, pressed);
+ }
+ }
+}
+
+static void report_sync(struct gpio_kp *kp)
+{
+ int i;
+
+ for (i = 0; i < kp->input_devs->count; i++)
+ input_sync(kp->input_devs->dev[i]);
+}
+
+static enum hrtimer_restart gpio_keypad_timer_func(struct hrtimer *timer)
+{
+ int out, in;
+ int key_index;
+ int gpio;
+ struct gpio_kp *kp = container_of(timer, struct gpio_kp, timer);
+ struct gpio_event_matrix_info *mi = kp->keypad_info;
+ unsigned gpio_keypad_flags = mi->flags;
+ unsigned polarity = !!(gpio_keypad_flags & GPIOKPF_ACTIVE_HIGH);
+
+ out = kp->current_output;
+ if (out == mi->noutputs) {
+ out = 0;
+ kp->last_key_state_changed = kp->key_state_changed;
+ kp->key_state_changed = 0;
+ kp->some_keys_pressed = 0;
+ } else {
+ key_index = out * mi->ninputs;
+ for (in = 0; in < mi->ninputs; in++, key_index++) {
+ gpio = mi->input_gpios[in];
+ if (gpio_get_value(gpio) ^ !polarity) {
+ if (kp->some_keys_pressed < 3)
+ kp->some_keys_pressed++;
+ kp->key_state_changed |= !__test_and_set_bit(
+ key_index, kp->keys_pressed);
+ } else
+ kp->key_state_changed |= __test_and_clear_bit(
+ key_index, kp->keys_pressed);
+ }
+ gpio = mi->output_gpios[out];
+ if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE)
+ gpio_set_value(gpio, !polarity);
+ else
+ gpio_direction_input(gpio);
+ out++;
+ }
+ kp->current_output = out;
+ if (out < mi->noutputs) {
+ gpio = mi->output_gpios[out];
+ if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE)
+ gpio_set_value(gpio, polarity);
+ else
+ gpio_direction_output(gpio, polarity);
+ hrtimer_start(timer, mi->settle_time, HRTIMER_MODE_REL);
+ return HRTIMER_NORESTART;
+ }
+ if (gpio_keypad_flags & GPIOKPF_DEBOUNCE) {
+ if (kp->key_state_changed) {
+ hrtimer_start(&kp->timer, mi->debounce_delay,
+ HRTIMER_MODE_REL);
+ return HRTIMER_NORESTART;
+ }
+ kp->key_state_changed = kp->last_key_state_changed;
+ }
+ if (kp->key_state_changed) {
+ if (gpio_keypad_flags & GPIOKPF_REMOVE_SOME_PHANTOM_KEYS)
+ remove_phantom_keys(kp);
+ key_index = 0;
+ for (out = 0; out < mi->noutputs; out++)
+ for (in = 0; in < mi->ninputs; in++, key_index++)
+ report_key(kp, key_index, out, in);
+ report_sync(kp);
+ }
+ if (!kp->use_irq || kp->some_keys_pressed) {
+ hrtimer_start(timer, mi->poll_time, HRTIMER_MODE_REL);
+ return HRTIMER_NORESTART;
+ }
+
+ /* No keys are pressed, reenable interrupt */
+ for (out = 0; out < mi->noutputs; out++) {
+ if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE)
+ gpio_set_value(mi->output_gpios[out], polarity);
+ else
+ gpio_direction_output(mi->output_gpios[out], polarity);
+ }
+ for (in = 0; in < mi->ninputs; in++)
+ enable_irq(gpio_to_irq(mi->input_gpios[in]));
+ wake_unlock(&kp->wake_lock);
+ return HRTIMER_NORESTART;
+}
+
+static irqreturn_t gpio_keypad_irq_handler(int irq_in, void *dev_id)
+{
+ int i;
+ struct gpio_kp *kp = dev_id;
+ struct gpio_event_matrix_info *mi = kp->keypad_info;
+ unsigned gpio_keypad_flags = mi->flags;
+
+ if (!kp->use_irq) {
+ /* ignore interrupt while registering the handler */
+ kp->disabled_irq = 1;
+ disable_irq_nosync(irq_in);
+ return IRQ_HANDLED;
+ }
+
+ for (i = 0; i < mi->ninputs; i++)
+ disable_irq_nosync(gpio_to_irq(mi->input_gpios[i]));
+ for (i = 0; i < mi->noutputs; i++) {
+ if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE)
+ gpio_set_value(mi->output_gpios[i],
+ !(gpio_keypad_flags & GPIOKPF_ACTIVE_HIGH));
+ else
+ gpio_direction_input(mi->output_gpios[i]);
+ }
+ wake_lock(&kp->wake_lock);
+ hrtimer_start(&kp->timer, ktime_set(0, 0), HRTIMER_MODE_REL);
+ return IRQ_HANDLED;
+}
+
+static int gpio_keypad_request_irqs(struct gpio_kp *kp)
+{
+ int i;
+ int err;
+ unsigned int irq;
+ unsigned long request_flags;
+ struct gpio_event_matrix_info *mi = kp->keypad_info;
+
+ switch (mi->flags & (GPIOKPF_ACTIVE_HIGH|GPIOKPF_LEVEL_TRIGGERED_IRQ)) {
+ default:
+ request_flags = IRQF_TRIGGER_FALLING;
+ break;
+ case GPIOKPF_ACTIVE_HIGH:
+ request_flags = IRQF_TRIGGER_RISING;
+ break;
+ case GPIOKPF_LEVEL_TRIGGERED_IRQ:
+ request_flags = IRQF_TRIGGER_LOW;
+ break;
+ case GPIOKPF_LEVEL_TRIGGERED_IRQ | GPIOKPF_ACTIVE_HIGH:
+ request_flags = IRQF_TRIGGER_HIGH;
+ break;
+ }
+
+ for (i = 0; i < mi->ninputs; i++) {
+ err = irq = gpio_to_irq(mi->input_gpios[i]);
+ if (err < 0)
+ goto err_gpio_get_irq_num_failed;
+ err = request_irq(irq, gpio_keypad_irq_handler, request_flags,
+ "gpio_kp", kp);
+ if (err) {
+ pr_err("gpiomatrix: request_irq failed for input %d, "
+ "irq %d\n", mi->input_gpios[i], irq);
+ goto err_request_irq_failed;
+ }
+ err = enable_irq_wake(irq);
+ if (err) {
+ pr_err("gpiomatrix: set_irq_wake failed for input %d, "
+ "irq %d\n", mi->input_gpios[i], irq);
+ }
+ disable_irq(irq);
+ if (kp->disabled_irq) {
+ kp->disabled_irq = 0;
+ enable_irq(irq);
+ }
+ }
+ return 0;
+
+ for (i = mi->noutputs - 1; i >= 0; i--) {
+ free_irq(gpio_to_irq(mi->input_gpios[i]), kp);
+err_request_irq_failed:
+err_gpio_get_irq_num_failed:
+ ;
+ }
+ return err;
+}
+
+int gpio_event_matrix_func(struct gpio_event_input_devs *input_devs,
+ struct gpio_event_info *info, void **data, int func)
+{
+ int i;
+ int err;
+ int key_count;
+ struct gpio_kp *kp;
+ struct gpio_event_matrix_info *mi;
+
+ mi = container_of(info, struct gpio_event_matrix_info, info);
+ if (func == GPIO_EVENT_FUNC_SUSPEND || func == GPIO_EVENT_FUNC_RESUME) {
+ /* TODO: disable scanning */
+ return 0;
+ }
+
+ if (func == GPIO_EVENT_FUNC_INIT) {
+ if (mi->keymap == NULL ||
+ mi->input_gpios == NULL ||
+ mi->output_gpios == NULL) {
+ err = -ENODEV;
+ pr_err("gpiomatrix: Incomplete pdata\n");
+ goto err_invalid_platform_data;
+ }
+ key_count = mi->ninputs * mi->noutputs;
+
+ *data = kp = kzalloc(sizeof(*kp) + sizeof(kp->keys_pressed[0]) *
+ BITS_TO_LONGS(key_count), GFP_KERNEL);
+ if (kp == NULL) {
+ err = -ENOMEM;
+ pr_err("gpiomatrix: Failed to allocate private data\n");
+ goto err_kp_alloc_failed;
+ }
+ kp->input_devs = input_devs;
+ kp->keypad_info = mi;
+ for (i = 0; i < key_count; i++) {
+ unsigned short keyentry = mi->keymap[i];
+ unsigned short keycode = keyentry & MATRIX_KEY_MASK;
+ unsigned short dev = keyentry >> MATRIX_CODE_BITS;
+ if (dev >= input_devs->count) {
+ pr_err("gpiomatrix: bad device index %d >= "
+ "%d for key code %d\n",
+ dev, input_devs->count, keycode);
+ err = -EINVAL;
+ goto err_bad_keymap;
+ }
+ if (keycode && keycode <= KEY_MAX)
+ input_set_capability(input_devs->dev[dev],
+ EV_KEY, keycode);
+ }
+
+ for (i = 0; i < mi->noutputs; i++) {
+ err = gpio_request(mi->output_gpios[i], "gpio_kp_out");
+ if (err) {
+ pr_err("gpiomatrix: gpio_request failed for "
+ "output %d\n", mi->output_gpios[i]);
+ goto err_request_output_gpio_failed;
+ }
+ if (gpio_cansleep(mi->output_gpios[i])) {
+ pr_err("gpiomatrix: unsupported output gpio %d,"
+ " can sleep\n", mi->output_gpios[i]);
+ err = -EINVAL;
+ goto err_output_gpio_configure_failed;
+ }
+ if (mi->flags & GPIOKPF_DRIVE_INACTIVE)
+ err = gpio_direction_output(mi->output_gpios[i],
+ !(mi->flags & GPIOKPF_ACTIVE_HIGH));
+ else
+ err = gpio_direction_input(mi->output_gpios[i]);
+ if (err) {
+ pr_err("gpiomatrix: gpio_configure failed for "
+ "output %d\n", mi->output_gpios[i]);
+ goto err_output_gpio_configure_failed;
+ }
+ }
+ for (i = 0; i < mi->ninputs; i++) {
+ err = gpio_request(mi->input_gpios[i], "gpio_kp_in");
+ if (err) {
+ pr_err("gpiomatrix: gpio_request failed for "
+ "input %d\n", mi->input_gpios[i]);
+ goto err_request_input_gpio_failed;
+ }
+ err = gpio_direction_input(mi->input_gpios[i]);
+ if (err) {
+ pr_err("gpiomatrix: gpio_direction_input failed"
+ " for input %d\n", mi->input_gpios[i]);
+ goto err_gpio_direction_input_failed;
+ }
+ }
+ kp->current_output = mi->noutputs;
+ kp->key_state_changed = 1;
+
+ hrtimer_init(&kp->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ kp->timer.function = gpio_keypad_timer_func;
+ wake_lock_init(&kp->wake_lock, WAKE_LOCK_SUSPEND, "gpio_kp");
+ err = gpio_keypad_request_irqs(kp);
+ kp->use_irq = err == 0;
+
+ pr_info("GPIO Matrix Keypad Driver: Start keypad matrix for "
+ "%s%s in %s mode\n", input_devs->dev[0]->name,
+ (input_devs->count > 1) ? "..." : "",
+ kp->use_irq ? "interrupt" : "polling");
+
+ if (kp->use_irq)
+ wake_lock(&kp->wake_lock);
+ hrtimer_start(&kp->timer, ktime_set(0, 0), HRTIMER_MODE_REL);
+
+ return 0;
+ }
+
+ err = 0;
+ kp = *data;
+
+ if (kp->use_irq)
+ for (i = mi->noutputs - 1; i >= 0; i--)
+ free_irq(gpio_to_irq(mi->input_gpios[i]), kp);
+
+ hrtimer_cancel(&kp->timer);
+ wake_lock_destroy(&kp->wake_lock);
+ for (i = mi->noutputs - 1; i >= 0; i--) {
+err_gpio_direction_input_failed:
+ gpio_free(mi->input_gpios[i]);
+err_request_input_gpio_failed:
+ ;
+ }
+ for (i = mi->noutputs - 1; i >= 0; i--) {
+err_output_gpio_configure_failed:
+ gpio_free(mi->output_gpios[i]);
+err_request_output_gpio_failed:
+ ;
+ }
+err_bad_keymap:
+ kfree(kp);
+err_kp_alloc_failed:
+err_invalid_platform_data:
+ return err;
+}
diff --git a/drivers/input/misc/gpio_output.c b/drivers/input/misc/gpio_output.c
new file mode 100644
index 0000000..2aac2fa
--- /dev/null
+++ b/drivers/input/misc/gpio_output.c
@@ -0,0 +1,97 @@
+/* drivers/input/misc/gpio_output.c
+ *
+ * Copyright (C) 2007 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/gpio.h>
+#include <linux/gpio_event.h>
+
+int gpio_event_output_event(
+ struct gpio_event_input_devs *input_devs, struct gpio_event_info *info,
+ void **data, unsigned int dev, unsigned int type,
+ unsigned int code, int value)
+{
+ int i;
+ struct gpio_event_output_info *oi;
+ oi = container_of(info, struct gpio_event_output_info, info);
+ if (type != oi->type)
+ return 0;
+ if (!(oi->flags & GPIOEDF_ACTIVE_HIGH))
+ value = !value;
+ for (i = 0; i < oi->keymap_size; i++)
+ if (dev == oi->keymap[i].dev && code == oi->keymap[i].code)
+ gpio_set_value(oi->keymap[i].gpio, value);
+ return 0;
+}
+
+int gpio_event_output_func(
+ struct gpio_event_input_devs *input_devs, struct gpio_event_info *info,
+ void **data, int func)
+{
+ int ret;
+ int i;
+ struct gpio_event_output_info *oi;
+ oi = container_of(info, struct gpio_event_output_info, info);
+
+ if (func == GPIO_EVENT_FUNC_SUSPEND || func == GPIO_EVENT_FUNC_RESUME)
+ return 0;
+
+ if (func == GPIO_EVENT_FUNC_INIT) {
+ int output_level = !(oi->flags & GPIOEDF_ACTIVE_HIGH);
+
+ for (i = 0; i < oi->keymap_size; i++) {
+ int dev = oi->keymap[i].dev;
+ if (dev >= input_devs->count) {
+ pr_err("gpio_event_output_func: bad device "
+ "index %d >= %d for key code %d\n",
+ dev, input_devs->count,
+ oi->keymap[i].code);
+ ret = -EINVAL;
+ goto err_bad_keymap;
+ }
+ input_set_capability(input_devs->dev[dev], oi->type,
+ oi->keymap[i].code);
+ }
+
+ for (i = 0; i < oi->keymap_size; i++) {
+ ret = gpio_request(oi->keymap[i].gpio,
+ "gpio_event_output");
+ if (ret) {
+ pr_err("gpio_event_output_func: gpio_request "
+ "failed for %d\n", oi->keymap[i].gpio);
+ goto err_gpio_request_failed;
+ }
+ ret = gpio_direction_output(oi->keymap[i].gpio,
+ output_level);
+ if (ret) {
+ pr_err("gpio_event_output_func: "
+ "gpio_direction_output failed for %d\n",
+ oi->keymap[i].gpio);
+ goto err_gpio_direction_output_failed;
+ }
+ }
+ return 0;
+ }
+
+ ret = 0;
+ for (i = oi->keymap_size - 1; i >= 0; i--) {
+err_gpio_direction_output_failed:
+ gpio_free(oi->keymap[i].gpio);
+err_gpio_request_failed:
+ ;
+ }
+err_bad_keymap:
+ return ret;
+}
+
diff --git a/drivers/input/misc/keychord.c b/drivers/input/misc/keychord.c
new file mode 100644
index 0000000..3ffab6d
--- /dev/null
+++ b/drivers/input/misc/keychord.c
@@ -0,0 +1,387 @@
+/*
+ * drivers/input/misc/keychord.c
+ *
+ * Copyright (C) 2008 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+*/
+
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/keychord.h>
+#include <linux/sched.h>
+
+#define KEYCHORD_NAME "keychord"
+#define BUFFER_SIZE 16
+
+MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>");
+MODULE_DESCRIPTION("Key chord input driver");
+MODULE_SUPPORTED_DEVICE("keychord");
+MODULE_LICENSE("GPL");
+
+#define NEXT_KEYCHORD(kc) ((struct input_keychord *) \
+ ((char *)kc + sizeof(struct input_keychord) + \
+ kc->count * sizeof(kc->keycodes[0])))
+
+struct keychord_device {
+ struct input_handler input_handler;
+ int registered;
+
+ /* list of keychords to monitor */
+ struct input_keychord *keychords;
+ int keychord_count;
+
+ /* bitmask of keys contained in our keychords */
+ unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
+ /* current state of the keys */
+ unsigned long keystate[BITS_TO_LONGS(KEY_CNT)];
+ /* number of keys that are currently pressed */
+ int key_down;
+
+ /* second input_device_id is needed for null termination */
+ struct input_device_id device_ids[2];
+
+ spinlock_t lock;
+ wait_queue_head_t waitq;
+ unsigned char head;
+ unsigned char tail;
+ __u16 buff[BUFFER_SIZE];
+};
+
+static int check_keychord(struct keychord_device *kdev,
+ struct input_keychord *keychord)
+{
+ int i;
+
+ if (keychord->count != kdev->key_down)
+ return 0;
+
+ for (i = 0; i < keychord->count; i++) {
+ if (!test_bit(keychord->keycodes[i], kdev->keystate))
+ return 0;
+ }
+
+ /* we have a match */
+ return 1;
+}
+
+static void keychord_event(struct input_handle *handle, unsigned int type,
+ unsigned int code, int value)
+{
+ struct keychord_device *kdev = handle->private;
+ struct input_keychord *keychord;
+ unsigned long flags;
+ int i, got_chord = 0;
+
+ if (type != EV_KEY || code >= KEY_MAX)
+ return;
+
+ spin_lock_irqsave(&kdev->lock, flags);
+ /* do nothing if key state did not change */
+ if (!test_bit(code, kdev->keystate) == !value)
+ goto done;
+ __change_bit(code, kdev->keystate);
+ if (value)
+ kdev->key_down++;
+ else
+ kdev->key_down--;
+
+ /* don't notify on key up */
+ if (!value)
+ goto done;
+ /* ignore this event if it is not one of the keys we are monitoring */
+ if (!test_bit(code, kdev->keybit))
+ goto done;
+
+ keychord = kdev->keychords;
+ if (!keychord)
+ goto done;
+
+ /* check to see if the keyboard state matches any keychords */
+ for (i = 0; i < kdev->keychord_count; i++) {
+ if (check_keychord(kdev, keychord)) {
+ kdev->buff[kdev->head] = keychord->id;
+ kdev->head = (kdev->head + 1) % BUFFER_SIZE;
+ got_chord = 1;
+ break;
+ }
+ /* skip to next keychord */
+ keychord = NEXT_KEYCHORD(keychord);
+ }
+
+done:
+ spin_unlock_irqrestore(&kdev->lock, flags);
+
+ if (got_chord)
+ wake_up_interruptible(&kdev->waitq);
+}
+
+static int keychord_connect(struct input_handler *handler,
+ struct input_dev *dev,
+ const struct input_device_id *id)
+{
+ int i, ret;
+ struct input_handle *handle;
+ struct keychord_device *kdev =
+ container_of(handler, struct keychord_device, input_handler);
+
+ /*
+ * ignore this input device if it does not contain any keycodes
+ * that we are monitoring
+ */
+ for (i = 0; i < KEY_MAX; i++) {
+ if (test_bit(i, kdev->keybit) && test_bit(i, dev->keybit))
+ break;
+ }
+ if (i == KEY_MAX)
+ return -ENODEV;
+
+ handle = kzalloc(sizeof(*handle), GFP_KERNEL);
+ if (!handle)
+ return -ENOMEM;
+
+ handle->dev = dev;
+ handle->handler = handler;
+ handle->name = KEYCHORD_NAME;
+ handle->private = kdev;
+
+ ret = input_register_handle(handle);
+ if (ret)
+ goto err_input_register_handle;
+
+ ret = input_open_device(handle);
+ if (ret)
+ goto err_input_open_device;
+
+ pr_info("keychord: using input dev %s for fevent\n", dev->name);
+
+ return 0;
+
+err_input_open_device:
+ input_unregister_handle(handle);
+err_input_register_handle:
+ kfree(handle);
+ return ret;
+}
+
+static void keychord_disconnect(struct input_handle *handle)
+{
+ input_close_device(handle);
+ input_unregister_handle(handle);
+ kfree(handle);
+}
+
+/*
+ * keychord_read is used to read keychord events from the driver
+ */
+static ssize_t keychord_read(struct file *file, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct keychord_device *kdev = file->private_data;
+ __u16 id;
+ int retval;
+ unsigned long flags;
+
+ if (count < sizeof(id))
+ return -EINVAL;
+ count = sizeof(id);
+
+ if (kdev->head == kdev->tail && (file->f_flags & O_NONBLOCK))
+ return -EAGAIN;
+
+ retval = wait_event_interruptible(kdev->waitq,
+ kdev->head != kdev->tail);
+ if (retval)
+ return retval;
+
+ spin_lock_irqsave(&kdev->lock, flags);
+ /* pop a keychord ID off the queue */
+ id = kdev->buff[kdev->tail];
+ kdev->tail = (kdev->tail + 1) % BUFFER_SIZE;
+ spin_unlock_irqrestore(&kdev->lock, flags);
+
+ if (copy_to_user(buffer, &id, count))
+ return -EFAULT;
+
+ return count;
+}
+
+/*
+ * keychord_write is used to configure the driver
+ */
+static ssize_t keychord_write(struct file *file, const char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct keychord_device *kdev = file->private_data;
+ struct input_keychord *keychords = 0;
+ struct input_keychord *keychord, *next, *end;
+ int ret, i, key;
+ unsigned long flags;
+
+ if (count < sizeof(struct input_keychord))
+ return -EINVAL;
+ keychords = kzalloc(count, GFP_KERNEL);
+ if (!keychords)
+ return -ENOMEM;
+
+ /* read list of keychords from userspace */
+ if (copy_from_user(keychords, buffer, count)) {
+ kfree(keychords);
+ return -EFAULT;
+ }
+
+ /* unregister handler before changing configuration */
+ if (kdev->registered) {
+ input_unregister_handler(&kdev->input_handler);
+ kdev->registered = 0;
+ }
+
+ spin_lock_irqsave(&kdev->lock, flags);
+ /* clear any existing configuration */
+ kfree(kdev->keychords);
+ kdev->keychords = 0;
+ kdev->keychord_count = 0;
+ kdev->key_down = 0;
+ memset(kdev->keybit, 0, sizeof(kdev->keybit));
+ memset(kdev->keystate, 0, sizeof(kdev->keystate));
+ kdev->head = kdev->tail = 0;
+
+ keychord = keychords;
+ end = (struct input_keychord *)((char *)keychord + count);
+
+ while (keychord < end) {
+ next = NEXT_KEYCHORD(keychord);
+ if (keychord->count <= 0 || next > end) {
+ pr_err("keychord: invalid keycode count %d\n",
+ keychord->count);
+ goto err_unlock_return;
+ }
+ if (keychord->version != KEYCHORD_VERSION) {
+ pr_err("keychord: unsupported version %d\n",
+ keychord->version);
+ goto err_unlock_return;
+ }
+
+ /* keep track of the keys we are monitoring in keybit */
+ for (i = 0; i < keychord->count; i++) {
+ key = keychord->keycodes[i];
+ if (key < 0 || key >= KEY_CNT) {
+ pr_err("keychord: keycode %d out of range\n",
+ key);
+ goto err_unlock_return;
+ }
+ __set_bit(key, kdev->keybit);
+ }
+
+ kdev->keychord_count++;
+ keychord = next;
+ }
+
+ kdev->keychords = keychords;
+ spin_unlock_irqrestore(&kdev->lock, flags);
+
+ ret = input_register_handler(&kdev->input_handler);
+ if (ret) {
+ kfree(keychords);
+ kdev->keychords = 0;
+ return ret;
+ }
+ kdev->registered = 1;
+
+ return count;
+
+err_unlock_return:
+ spin_unlock_irqrestore(&kdev->lock, flags);
+ kfree(keychords);
+ return -EINVAL;
+}
+
+static unsigned int keychord_poll(struct file *file, poll_table *wait)
+{
+ struct keychord_device *kdev = file->private_data;
+
+ poll_wait(file, &kdev->waitq, wait);
+
+ if (kdev->head != kdev->tail)
+ return POLLIN | POLLRDNORM;
+
+ return 0;
+}
+
+static int keychord_open(struct inode *inode, struct file *file)
+{
+ struct keychord_device *kdev;
+
+ kdev = kzalloc(sizeof(struct keychord_device), GFP_KERNEL);
+ if (!kdev)
+ return -ENOMEM;
+
+ spin_lock_init(&kdev->lock);
+ init_waitqueue_head(&kdev->waitq);
+
+ kdev->input_handler.event = keychord_event;
+ kdev->input_handler.connect = keychord_connect;
+ kdev->input_handler.disconnect = keychord_disconnect;
+ kdev->input_handler.name = KEYCHORD_NAME;
+ kdev->input_handler.id_table = kdev->device_ids;
+
+ kdev->device_ids[0].flags = INPUT_DEVICE_ID_MATCH_EVBIT;
+ __set_bit(EV_KEY, kdev->device_ids[0].evbit);
+
+ file->private_data = kdev;
+
+ return 0;
+}
+
+static int keychord_release(struct inode *inode, struct file *file)
+{
+ struct keychord_device *kdev = file->private_data;
+
+ if (kdev->registered)
+ input_unregister_handler(&kdev->input_handler);
+ kfree(kdev);
+
+ return 0;
+}
+
+static const struct file_operations keychord_fops = {
+ .owner = THIS_MODULE,
+ .open = keychord_open,
+ .release = keychord_release,
+ .read = keychord_read,
+ .write = keychord_write,
+ .poll = keychord_poll,
+};
+
+static struct miscdevice keychord_misc = {
+ .fops = &keychord_fops,
+ .name = KEYCHORD_NAME,
+ .minor = MISC_DYNAMIC_MINOR,
+};
+
+static int __init keychord_init(void)
+{
+ return misc_register(&keychord_misc);
+}
+
+static void __exit keychord_exit(void)
+{
+ misc_deregister(&keychord_misc);
+}
+
+module_init(keychord_init);
+module_exit(keychord_exit);