From c6da2cfeb05178a11c6d062a06f8078150ee492f Mon Sep 17 00:00:00 2001 From: codeworkx Date: Sat, 2 Jun 2012 13:09:29 +0200 Subject: samsung update 1 --- drivers/input/misc/Kconfig | 39 +- drivers/input/misc/Makefile | 6 +- drivers/input/misc/ak8975-reg.h | 48 ++ drivers/input/misc/ak8975.c | 863 +++++++++++++++++++++++++++++++ drivers/input/misc/bh1721fvc.c | 826 ++++++++++++++++++++++++++++++ drivers/input/misc/gp2a.c | 1040 ++++++++++++++++++++++++++++++++++++++ drivers/input/misc/gp2a.h | 162 ++++++ drivers/input/misc/gpio_axis.c | 192 +++++++ drivers/input/misc/gpio_event.c | 260 ++++++++++ drivers/input/misc/gpio_input.c | 376 ++++++++++++++ drivers/input/misc/gpio_matrix.c | 441 ++++++++++++++++ drivers/input/misc/gpio_output.c | 97 ++++ drivers/input/misc/keychord.c | 387 ++++++++++++++ 13 files changed, 4735 insertions(+), 2 deletions(-) create mode 100755 drivers/input/misc/ak8975-reg.h create mode 100755 drivers/input/misc/ak8975.c create mode 100755 drivers/input/misc/bh1721fvc.c create mode 100755 drivers/input/misc/gp2a.c create mode 100755 drivers/input/misc/gp2a.h create mode 100644 drivers/input/misc/gpio_axis.c create mode 100644 drivers/input/misc/gpio_event.c create mode 100644 drivers/input/misc/gpio_input.c create mode 100644 drivers/input/misc/gpio_matrix.c create mode 100644 drivers/input/misc/gpio_output.c create mode 100644 drivers/input/misc/keychord.c (limited to 'drivers/input/misc') 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include + +#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 "); +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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, ®_defaults[1]); + gp2a_i2c_write(gp2a, REGS_HYS, ®_defaults[2]); + gp2a_i2c_write(gp2a, REGS_CYCLE, ®_defaults[3]); + gp2a_i2c_write(gp2a, REGS_OPMOD, ®_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, ®_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 +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include +#include + +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 +#include +#include + +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#define KEYCHORD_NAME "keychord" +#define BUFFER_SIZE 16 + +MODULE_AUTHOR("Mike Lockwood "); +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); -- cgit v1.1