aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/sensor
diff options
context:
space:
mode:
authorcodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
committercodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
commitc6da2cfeb05178a11c6d062a06f8078150ee492f (patch)
treef3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/sensor
parentc6d7c4dbff353eac7919342ae6b3299a378160a6 (diff)
downloadkernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2
samsung update 1
Diffstat (limited to 'drivers/sensor')
-rw-r--r--drivers/sensor/Kconfig123
-rw-r--r--drivers/sensor/Makefile22
-rw-r--r--drivers/sensor/ak8975-reg.h48
-rw-r--r--drivers/sensor/ak8975.c876
-rw-r--r--drivers/sensor/bh1721.c760
-rw-r--r--drivers/sensor/bma254_driver.c1431
-rw-r--r--drivers/sensor/bmp180.c778
-rw-r--r--drivers/sensor/cm3663.c993
-rw-r--r--drivers/sensor/cm36651.c1360
-rw-r--r--drivers/sensor/gp2a_analog.c787
-rw-r--r--drivers/sensor/gp2a_light.c732
-rw-r--r--drivers/sensor/gp2a_proximity.c863
-rw-r--r--drivers/sensor/k3dh.c803
-rw-r--r--drivers/sensor/k3dh_reg.h154
-rw-r--r--drivers/sensor/k3g.c1372
-rw-r--r--drivers/sensor/lps331ap.c1767
-rw-r--r--drivers/sensor/lsm330dlc_accel.c1402
-rw-r--r--drivers/sensor/lsm330dlc_gyro.c1643
-rw-r--r--drivers/sensor/pas2m110.c1009
-rw-r--r--drivers/sensor/sensors_core.c75
-rw-r--r--drivers/sensor/taos.c1372
21 files changed, 18370 insertions, 0 deletions
diff --git a/drivers/sensor/Kconfig b/drivers/sensor/Kconfig
new file mode 100644
index 0000000..4b0190a
--- /dev/null
+++ b/drivers/sensor/Kconfig
@@ -0,0 +1,123 @@
+#
+# Sensor drivers configuration
+#
+menuconfig SENSORS_CORE
+ bool "Sensor devices"
+ help
+ Say Y here, and a list of sensors drivers will be displayed.
+ Everything that didn't fit into the other categories is here. This option
+ doesn't affect the kernel.
+
+ If unsure, say Y.
+
+if SENSORS_CORE
+config SENSORS_AK8975C
+ tristate "AK8975 compass support"
+ default n
+ depends on I2C
+ help
+ If you say yes here you get support for Asahi Kasei's
+ orientation sensor AK8975.
+
+config SENSORS_BMP180
+ depends on I2C && SYSFS
+ tristate "BMP180 digital pressure sensor"
+ default n
+ help
+ If you say yes here you get support for the Bosch Sensortec
+ BMP180 digital pressure sensor.
+ To compile this driver as a module, choose M here: the
+ module will be called bmp180.
+
+config SENSORS_CM3663
+ depends on I2C
+ tristate "CM3663 ambient light and proximity input device"
+ default n
+ help
+ This option enables proximity & light sensors using cm3663 driver.
+
+config SENSORS_PAS2M110
+ depends on I2C && GENERIC_GPIO
+ tristate "PAS2M110 ambient light and proximity input device"
+ default n
+
+config SENSORS_BMA254
+ tristate "BMA254 Acceleration Sensor Driver"
+ depends on I2C
+ default n
+ help
+ If you say yes here you get support for Bosch-Sensortec's
+ BMA254 Acceleration Sensor.
+
+config SENSORS_TAOS
+ depends on I2C
+ tristate "TAOS driver"
+ default n
+ help
+ If you say yes here you get support for TAOS's
+ TMD27723 proximity & light sensor using taos driver.
+
+config SENSORS_GP2A
+ depends on I2C
+ tristate "GP2AP020A00F driver"
+ default n
+ help
+ This option enables proximity & light sensors using gp2ap020a00f driver.
+
+config SENSORS_GP2A_ANALOG
+ depends on I2C
+ tristate "GP2A analog driver"
+ default n
+ help
+ This option enables proximity & light sensors using analog gp2a driver.
+
+config SENSORS_CM36651
+ depends on I2C
+ tristate "CM36651 driver"
+ default n
+ help
+ Say Y here if you use cm36651.
+ This option enables proximity & RGB sensors using
+ Capella cm36651 device driver.
+
+ Say N here if you do not use cm36651.
+
+config SENSORS_BH1721
+ depends on I2C
+ tristate "BH1721 driver"
+ default n
+ help
+ Say Y here if you use BH1721.
+ This option enables light sensor using
+ ROHM BH1721 device driver.
+
+ Say N here if you do not use BH1721.
+
+config SENSORS_K3DH
+ tristate "K3DH acceleration sensor support"
+ depends on I2C
+ default n
+ help
+ Driver for STMicroelectronic K3DH accelerometer.
+
+config SENSORS_K3G
+ tristate "K3G driver for s5pc210"
+ depends on I2C
+ default n
+ help
+ This option enables gyro sensors using K3G driver.
+
+config SENSORS_LSM330DLC
+ depends on I2C
+ tristate "STMicro LSM330DLC driver"
+ default n
+ help
+ Driver for STMicro LSM330DLC
+
+config SENSORS_LPS331
+ tristate "STMicro LPS331 driver"
+ default n
+ depends on I2C
+ help
+ Driver for STMicro LPS331
+endif
diff --git a/drivers/sensor/Makefile b/drivers/sensor/Makefile
new file mode 100644
index 0000000..862074d
--- /dev/null
+++ b/drivers/sensor/Makefile
@@ -0,0 +1,22 @@
+#
+# Makefile for the sensors drivers.
+#
+
+# Each configuration option enables a list of files.
+
+obj-$(CONFIG_SENSORS_CORE) += sensors_core.o
+obj-$(CONFIG_SENSORS_AK8975C) += ak8975.o
+obj-$(CONFIG_SENSORS_BMP180) += bmp180.o
+obj-$(CONFIG_SENSORS_CM3663) += cm3663.o
+obj-$(CONFIG_SENSORS_BMA254) += bma254_driver.o
+obj-$(CONFIG_SENSORS_TAOS) += taos.o
+obj-$(CONFIG_SENSORS_GP2A) += gp2a_proximity.o gp2a_light.o
+obj-$(CONFIG_SENSORS_GP2A_ANALOG) += gp2a_analog.o
+obj-$(CONFIG_SENSORS_K3G) += k3g.o
+obj-$(CONFIG_SENSORS_K3DH) += k3dh.o
+obj-$(CONFIG_SENSORS_LSM330DLC) += lsm330dlc_accel.o lsm330dlc_gyro.o
+obj-$(CONFIG_SENSORS_LPS331) += lps331ap.o
+obj-$(CONFIG_SENSORS_CM36651) += cm36651.o
+# VE_GROUP
+obj-$(CONFIG_SENSORS_PAS2M110) += pas2m110.o
+obj-$(CONFIG_SENSORS_BH1721) += bh1721.o
diff --git a/drivers/sensor/ak8975-reg.h b/drivers/sensor/ak8975-reg.h
new file mode 100644
index 0000000..1a78a27
--- /dev/null
+++ b/drivers/sensor/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/sensor/ak8975.c b/drivers/sensor/ak8975.c
new file mode 100644
index 0000000..3d90128
--- /dev/null
+++ b/drivers/sensor/ak8975.c
@@ -0,0 +1,876 @@
+/*
+ * Copyright (C) 2010, Samsung Electronics Co. Ltd. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+*/
+
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/sensor/ak8975.h>
+#include <linux/completion.h>
+#include "ak8975-reg.h"
+#include <linux/sensor/sensors_core.h>
+
+#undef FACTORY_TEST
+#undef MAGNETIC_LOGGING
+
+#define VENDOR "AKM"
+#define CHIP_ID "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
+
+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];
+ int irq;
+};
+
+#ifdef FACTORY_TEST
+static bool ak8975_selftest_passed;
+static s16 sf_x, sf_y, sf_z;
+#endif
+
+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;
+}
+
+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;
+}
+
+static int akm8975_wait_for_data_ready(struct akm8975_data *akm)
+{
+ int data_ready = gpio_get_value(akm->pdata->gpio_data_ready_int);
+ int err;
+
+ if (data_ready)
+ return 0;
+
+ enable_irq(akm->irq);
+
+ err = wait_for_completion_timeout(&akm->data_ready, 2*HZ);
+ if (err > 0)
+ return 0;
+
+ akm8975_disable_irq(akm);
+
+ if (err == 0) {
+ pr_err("akm: wait timed out\n");
+ return -ETIMEDOUT;
+ }
+
+ pr_err("akm: wait restart\n");
+ return err;
+}
+
+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\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)\n",
+ __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];
+
+ printk(KERN_INFO "%s:ST1=%d, x=%d, y=%d, z=%d, ST2=%d\n",
+ __func__, rwbuf.data[0], x, y, z, rwbuf.data[7]);
+ #endif
+
+ mutex_unlock(&akm->lock);
+ if (ret != sizeof(rwbuf.data)) {
+ pr_err("%s : failed to read %d bytes of mag data\n",
+ __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,
+};
+
+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)\n",
+ __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)\n",
+ __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 | IRQF_ONESHOT, "akm_int", akm);
+ if (rc < 0) {
+ pr_err("%s: request_irq(%d) failed for gpio %d (%d)\n",
+ __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;
+}
+
+#ifdef FACTORY_TEST
+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\n",
+ __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\n",
+ __func__, x, y, z);
+ if ((x >= -100) && (x <= 100))
+ pr_info("%s: x passed self test, expect -100<=x<=100\n",
+ __func__);
+ else
+ pr_info("%s: x failed self test, expect -100<=x<=100\n",
+ __func__);
+ if ((y >= -100) && (y <= 100))
+ pr_info("%s: y passed self test, expect -100<=y<=100\n",
+ __func__);
+ else
+ pr_info("%s: y failed self test, expect -100<=y<=100\n",
+ __func__);
+ if ((z >= -1000) && (z <= -300))
+ pr_info("%s: z passed self test, expect -1000<=z<=-300\n",
+ __func__);
+ else
+ pr_info("%s: z failed self test, expect -1000<=z<=-300\n",
+ __func__);
+
+ 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;
+}
+
+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\n", __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\n", __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\n", __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)\n",
+ __func__, data[0] & 0x01);
+
+done:
+ return sprintf(buf, "%d,%d,%d\n", x, y, z);
+}
+
+static ssize_t ak8975_show_vendor(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", VENDOR);
+}
+
+static ssize_t ak8975_show_name(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", CHIP_ID);
+}
+
+static DEVICE_ATTR(raw_data, 0664,
+ ak8975_show_raw_data, NULL);
+static DEVICE_ATTR(vendor, 0664,
+ ak8975_show_vendor, NULL);
+static DEVICE_ATTR(name, 0664,
+ ak8975_show_name, NULL);
+
+#ifdef FACTORY_TEST
+static DEVICE_ATTR(ak8975_asa, 0664,
+ ak8975c_get_asa, NULL);
+static DEVICE_ATTR(ak8975_selftest, 0664,
+ ak8975c_get_selftest, NULL);
+static DEVICE_ATTR(ak8975_chk_registers, 0664,
+ ak8975c_check_registers, NULL);
+static DEVICE_ATTR(ak8975_chk_cntl, 0664,
+ ak8975c_check_cntl, NULL);
+static DEVICE_ATTR(status, 0664,
+ ak8975c_get_status, NULL);
+static DEVICE_ATTR(adc, 0664,
+ ak8975_adc, NULL);
+#endif
+
+int akm8975_probe(struct i2c_client *client,
+ const struct i2c_device_id *devid)
+{
+ struct akm8975_data *akm;
+ int err;
+
+ printk(KERN_INFO "%s is called.\n", __func__);
+ if (client->dev.platform_data == NULL && client->irq == 0) {
+ dev_err(&client->dev, "platform data & irq are NULL.\n");
+ err = -ENODEV;
+ goto exit_platform_data_null;
+ }
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "I2C check failed, exiting.\n");
+ 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\n");
+ 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) {
+ pr_err("%s: akm8975_ecs_set_mode_power_down fail(err=%d)\n",
+ __func__, err);
+ goto exit_set_mode_power_down_failed;
+ }
+
+ err = akm8975_setup_irq(akm);
+ if (err) {
+ pr_err("%s: could not setup irq\n", __func__);
+ goto exit_setup_irq;
+ }
+
+ 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.\n", __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\n", __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\n",
+ __func__);
+ goto exit_i2c_failed;
+ } else
+ pr_info("%s: asa_x = %d, asa_y = %d, asa_z = %d\n", __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\n");
+ goto exit_i2c_failed;
+ }
+
+ akm->dev = sensors_classdev_register("magnetic_sensor");
+ if (IS_ERR(akm->dev)) {
+ printk(KERN_ERR "Failed to create device!");
+ goto exit_class_create_failed;
+ }
+
+ if (device_create_file(akm->dev, &dev_attr_raw_data) < 0) {
+ printk(KERN_ERR "Failed to create device file(%s)!\n",
+ dev_attr_raw_data.attr.name);
+ goto exit_device_create_raw_data;
+ }
+
+ if (device_create_file(akm->dev, &dev_attr_vendor) < 0) {
+ printk(KERN_ERR "Failed to create device file(%s)!\n",
+ dev_attr_name.attr.name);
+ goto exit_device_create_vendor;
+ }
+
+ if (device_create_file(akm->dev, &dev_attr_name) < 0) {
+ printk(KERN_ERR "Failed to create device file(%s)!\n",
+ dev_attr_raw_data.attr.name);
+ goto exit_device_create_name;
+ }
+
+#ifdef FACTORY_TEST
+ ak8975c_selftest(akm);
+
+ if (device_create_file(akm->dev, &dev_attr_adc) < 0) {
+ printk(KERN_ERR "Failed to create device file(%s)!\n",
+ dev_attr_adc.attr.name);
+ goto exit_device_create_file1;
+ }
+
+ if (device_create_file(akm->dev, &dev_attr_status) < 0) {
+ printk(KERN_ERR "Failed to create device file(%s)!\n",
+ dev_attr_status.attr.name);
+ goto exit_device_create_file2;
+ }
+
+ if (device_create_file(akm->dev, &dev_attr_ak8975_asa) < 0) {
+ printk(KERN_ERR "Failed to create device file(%s)!\n",
+ dev_attr_ak8975_asa.attr.name);
+ goto exit_device_create_file3;
+ }
+ if (device_create_file(akm->dev, &dev_attr_ak8975_selftest) < 0) {
+ printk(KERN_ERR "Failed to create device file(%s)!\n",
+ dev_attr_ak8975_selftest.attr.name);
+ goto exit_device_create_file4;
+ }
+ if (device_create_file(akm->dev,\
+ &dev_attr_ak8975_chk_registers) < 0) {
+ printk(KERN_ERR "Failed to create device file(%s)!\n",
+ dev_attr_ak8975_chk_registers.attr.name);
+ goto exit_device_create_file5;
+ }
+ if (device_create_file(akm->dev, &dev_attr_ak8975_chk_cntl) < 0) {
+ printk(KERN_ERR "Failed to create device file(%s)!\n",
+ dev_attr_ak8975_chk_cntl.attr.name);
+ goto exit_device_create_file6;
+ }
+#endif
+ dev_set_drvdata(akm->dev, akm);
+
+printk(KERN_INFO "%s is successful.\n", __func__);
+return 0;
+
+#ifdef FACTORY_TEST
+exit_device_create_file6:
+ device_remove_file(akm->dev, &dev_attr_ak8975_chk_registers);
+exit_device_create_file5:
+ device_remove_file(akm->dev, &dev_attr_ak8975_selftest);
+exit_device_create_file4:
+ device_remove_file(akm->dev, &dev_attr_ak8975_asa);
+exit_device_create_file3:
+ device_remove_file(akm->dev, &dev_attr_status);
+exit_device_create_file2:
+ device_remove_file(akm->dev, &dev_attr_adc);
+exit_device_create_file1:
+ device_remove_file(akm->dev, &dev_attr_name);
+#endif
+exit_device_create_name:
+ device_remove_file(akm->dev, &dev_attr_vendor);
+exit_device_create_vendor:
+ device_remove_file(akm->dev, &dev_attr_raw_data);
+exit_device_create_raw_data:
+ sensors_classdev_unregister(akm->dev);
+exit_class_create_failed:
+exit_i2c_failed:
+ misc_deregister(&akm->akmd_device);
+exit_akmd_device_register_failed:
+ free_irq(akm->irq, akm);
+ gpio_free(akm->pdata->gpio_data_ready_int);
+exit_setup_irq:
+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);
+
+ #ifdef FACTORY_TEST
+ device_remove_file(akm->dev, &dev_attr_adc);
+ device_remove_file(akm->dev, &dev_attr_status);
+ device_remove_file(akm->dev, &dev_attr_ak8975_asa);
+ device_remove_file(akm->dev, &dev_attr_ak8975_selftest);
+ device_remove_file(akm->dev, &dev_attr_ak8975_chk_registers);
+ #endif
+ device_remove_file(akm->dev, &dev_attr_name);
+ device_remove_file(akm->dev, &dev_attr_vendor);
+ device_remove_file(akm->dev, &dev_attr_raw_data);
+ sensors_classdev_unregister(akm->dev);
+ misc_deregister(&akm->akmd_device);
+ free_irq(akm->irq, akm);
+ gpio_free(akm->pdata->gpio_data_ready_int);
+ 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/sensor/bh1721.c b/drivers/sensor/bh1721.c
new file mode 100644
index 0000000..a002c4a
--- /dev/null
+++ b/drivers/sensor/bh1721.c
@@ -0,0 +1,760 @@
+/*
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/sensor/bh1721.h>
+#include <linux/sensor/sensors_core.h>
+
+#define VENDOR_NAME "ROHM"
+#define CHIP_NAME "BH1721"
+
+#define SENSOR_AL3201_ADDR 0x1c
+#define SENSOR_BH1721FVC_ADDR 0x23
+
+#define BH1721FVC_DRV_NAME "bh1721fvc"
+#define DRIVER_VERSION "1.1"
+
+#define LIMIT_RESET_COUNT 5
+
+#define LUX_MIN_VALUE 0
+#define LUX_MAX_VALUE 65528
+
+#define ALS_BUFFER_NUM 10
+
+#define bh1721fvc_dbmsg(str, args...) pr_debug("%s: " str, __func__, ##args)
+
+enum BH1721FVC_STATE {
+ POWER_DOWN = 0,
+ POWER_ON,
+ AUTO_MEASURE,
+ H_MEASURE,
+ L_MEASURE,
+};
+
+static const u8 commands[] = {
+ 0x00, /* Power Down */
+ 0x01, /* Power On */
+ 0x10, /* Continuously Auto-Resolution Mode */
+ 0x12, /* Continuously H-Resolution Mode */
+ 0x13, /* Continuously L-Resolution Mode */
+};
+
+struct bh1721fvc_data {
+ int (*reset)(void);
+ struct bh1721fvc_platform_data *pdata;
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+ struct work_struct work_light;
+ struct hrtimer timer;
+ struct mutex lock;
+ struct workqueue_struct *wq;
+ struct 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;
+};
+
+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(raw_data, 0644, factory_file_illuminance_show, NULL);
+static DEVICE_ATTR(vendor, 0644, get_vendor_name, NULL);
+static DEVICE_ATTR(name, 0644, get_chip_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);
+#if 0
+ input_report_abs(bh1721fvc->input_dev, ABS_MISC, result);
+#else
+ if (result == 0) /* EV_REL does not send 0. */
+ input_report_rel(bh1721fvc->input_dev, REL_MISC, -1);
+ else
+ input_report_rel(bh1721fvc->input_dev, REL_MISC,
+ result);
+#endif
+ 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);
+
+ pr_info("%s: is started!\n", __func__);
+ 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;
+ }
+
+ if (pdata != NULL) {
+ 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";
+#if 0
+ 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);
+#else
+ input_set_capability(input_dev, EV_REL, REL_MISC);
+#endif
+ 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;
+ }
+
+ /* set sysfs for light sensor */
+ bh1721fvc->light_dev = sensors_classdev_register("light_sensor");
+ if (IS_ERR(bh1721fvc->light_dev)) {
+ pr_err("%s: could not create light_dev\n", __func__);
+ goto err_light_device_create;
+ }
+
+ if (device_create_file(bh1721fvc->light_dev, &dev_attr_raw_data) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_raw_data.attr.name);
+ goto err_light_device_create_file1;
+ }
+
+ if (device_create_file(bh1721fvc->light_dev, &dev_attr_vendor) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_vendor.attr.name);
+ goto err_light_device_create_file2;
+ }
+
+ if (device_create_file(bh1721fvc->light_dev, &dev_attr_name) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_name.attr.name);
+ goto err_light_device_create_file3;
+ }
+
+ dev_set_drvdata(bh1721fvc->light_dev, bh1721fvc);
+ pr_info("%s: success!\n", __func__);
+
+
+ goto done;
+
+err_light_device_create_file3:
+ device_remove_file(bh1721fvc->light_dev, &dev_attr_vendor);
+err_light_device_create_file2:
+ device_remove_file(bh1721fvc->light_dev, &dev_attr_raw_data);
+err_light_device_create_file1:
+ sensors_classdev_unregister(bh1721fvc->light_dev);
+err_light_device_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->light_dev, &dev_attr_name);
+ device_remove_file(bh1721fvc->light_dev, &dev_attr_vendor);
+ device_remove_file(bh1721fvc->light_dev, &dev_attr_raw_data);
+ sensors_classdev_unregister(bh1721fvc->light_dev);
+ sysfs_remove_group(&bh1721fvc->input_dev->dev.kobj,
+ &bh1721fvc_attribute_group);
+ input_unregister_device(bh1721fvc->input_dev);
+
+ if (bh1721fvc_is_measuring(bh1721fvc))
+ bh1721fvc_disable(bh1721fvc);
+
+ destroy_workqueue(bh1721fvc->wq);
+ mutex_destroy(&bh1721fvc->lock);
+ kfree(bh1721fvc);
+
+ bh1721fvc_dbmsg("bh1721fvc_remove -\n");
+ return 0;
+}
+
+static int bh1721fvc_suspend(struct device *dev)
+{
+ int err = 0;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bh1721fvc_data *bh1721fvc = i2c_get_clientdata(client);
+
+ if (bh1721fvc_is_measuring(bh1721fvc)) {
+ err = bh1721fvc_disable(bh1721fvc);
+ if (err)
+ pr_err("%s: could not disable\n", __func__);
+ }
+
+ return err;
+}
+
+static int bh1721fvc_resume(struct device *dev)
+{
+ int err = 0;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bh1721fvc_data *bh1721fvc = i2c_get_clientdata(client);
+
+ bh1721fvc_dbmsg("bh1721fvc_resume +\n");
+
+ if (bh1721fvc_is_measuring(bh1721fvc)) {
+ err = bh1721fvc_enable(bh1721fvc);
+ if (err)
+ pr_err("%s: could not enable\n", __func__);
+ }
+
+ bh1721fvc_dbmsg("bh1721fvc_resume -\n");
+ return err;
+}
+
+static const struct i2c_device_id bh1721fvc_id[] = {
+ { BH1721FVC_DRV_NAME, 0 },
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, bh1721fvc_id);
+
+static const struct dev_pm_ops bh1721fvc_pm_ops = {
+ .suspend = bh1721fvc_suspend,
+ .resume = bh1721fvc_resume,
+};
+
+static struct i2c_driver bh1721fvc_driver = {
+ .driver = {
+ .name = BH1721FVC_DRV_NAME,
+ .owner = THIS_MODULE,
+ .pm = &bh1721fvc_pm_ops,
+ },
+ .probe = bh1721fvc_probe,
+ .remove = bh1721fvc_remove,
+ .id_table = bh1721fvc_id,
+};
+
+static int __init bh1721fvc_init(void)
+{
+ return i2c_add_driver(&bh1721fvc_driver);
+}
+module_init(bh1721fvc_init);
+
+static void __exit bh1721fvc_exit(void)
+{
+ bh1721fvc_dbmsg("bh1721fvc_exit +\n");
+ i2c_del_driver(&bh1721fvc_driver);
+ bh1721fvc_dbmsg("bh1721fvc_exit -\n");
+}
+module_exit(bh1721fvc_exit);
+
+MODULE_AUTHOR("WonHyoung Lee <whlee@sta.samsung.com>");
+MODULE_DESCRIPTION("BH1721FVC Ambient light sensor driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(DRIVER_VERSION);
diff --git a/drivers/sensor/bma254_driver.c b/drivers/sensor/bma254_driver.c
new file mode 100644
index 0000000..49fc56e
--- /dev/null
+++ b/drivers/sensor/bma254_driver.c
@@ -0,0 +1,1431 @@
+/* Date: 2011/8/8 11:00:00
+ * Revision: 1.6
+ */
+
+/*
+ * This software program is licensed subject to the GNU General Public License
+ * (GPL).Version 2,June 1991, available at http://www.fsf.org/copyleft/gpl.html
+
+ * (C) Copyright 2011 Bosch Sensortec GmbH
+ * All Rights Reserved
+ */
+
+
+/* file BMA254.c
+ brief This file contains all function implementations for the BMA254 in linux
+
+*/
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/workqueue.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/earlysuspend.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+
+#define SENSOR_NAME "bma254"
+#define GRAVITY_EARTH 9806550
+#define ABSMIN_2G (-GRAVITY_EARTH * 2)
+#define ABSMAX_2G (GRAVITY_EARTH * 2)
+#define SLOPE_THRESHOLD_VALUE 32
+#define SLOPE_DURATION_VALUE 1
+#define INTERRUPT_LATCH_MODE 13
+#define INTERRUPT_ENABLE 1
+#define INTERRUPT_DISABLE 0
+#define MAP_SLOPE_INTERRUPT 2
+#define SLOPE_X_INDEX 5
+#define SLOPE_Y_INDEX 6
+#define SLOPE_Z_INDEX 7
+
+#define BMA254_MIN_DELAY 1
+#define BMA254_DEFAULT_DELAY 200
+
+#define BMA254_CHIP_ID 0xFA
+#define BMA254_RANGE_SET 0
+#define BMA254_BW_SET 4
+
+
+/*
+ *
+ * register definitions
+ *
+ */
+
+#define BMA254_CHIP_ID_REG 0x00
+#define BMA254_VERSION_REG 0x01
+#define BMA254_X_AXIS_LSB_REG 0x02
+#define BMA254_X_AXIS_MSB_REG 0x03
+#define BMA254_Y_AXIS_LSB_REG 0x04
+#define BMA254_Y_AXIS_MSB_REG 0x05
+#define BMA254_Z_AXIS_LSB_REG 0x06
+#define BMA254_Z_AXIS_MSB_REG 0x07
+#define BMA254_TEMP_RD_REG 0x08
+#define BMA254_STATUS1_REG 0x09
+#define BMA254_STATUS2_REG 0x0A
+#define BMA254_STATUS_TAP_SLOPE_REG 0x0B
+#define BMA254_STATUS_ORIENT_HIGH_REG 0x0C
+#define BMA254_RANGE_SEL_REG 0x0F
+#define BMA254_BW_SEL_REG 0x10
+#define BMA254_MODE_CTRL_REG 0x11
+#define BMA254_LOW_NOISE_CTRL_REG 0x12
+#define BMA254_DATA_CTRL_REG 0x13
+#define BMA254_RESET_REG 0x14
+#define BMA254_INT_ENABLE1_REG 0x16
+#define BMA254_INT_ENABLE2_REG 0x17
+#define BMA254_INT1_PAD_SEL_REG 0x19
+#define BMA254_INT_DATA_SEL_REG 0x1A
+#define BMA254_INT2_PAD_SEL_REG 0x1B
+#define BMA254_INT_SRC_REG 0x1E
+#define BMA254_INT_SET_REG 0x20
+#define BMA254_INT_CTRL_REG 0x21
+#define BMA254_LOW_DURN_REG 0x22
+#define BMA254_LOW_THRES_REG 0x23
+#define BMA254_LOW_HIGH_HYST_REG 0x24
+#define BMA254_HIGH_DURN_REG 0x25
+#define BMA254_HIGH_THRES_REG 0x26
+#define BMA254_SLOPE_DURN_REG 0x27
+#define BMA254_SLOPE_THRES_REG 0x28
+#define BMA254_TAP_PARAM_REG 0x2A
+#define BMA254_TAP_THRES_REG 0x2B
+#define BMA254_ORIENT_PARAM_REG 0x2C
+#define BMA254_THETA_BLOCK_REG 0x2D
+#define BMA254_THETA_FLAT_REG 0x2E
+#define BMA254_FLAT_HOLD_TIME_REG 0x2F
+#define BMA254_STATUS_LOW_POWER_REG 0x31
+#define BMA254_SELF_TEST_REG 0x32
+#define BMA254_EEPROM_CTRL_REG 0x33
+#define BMA254_SERIAL_CTRL_REG 0x34
+#define BMA254_CTRL_UNLOCK_REG 0x35
+#define BMA254_OFFSET_CTRL_REG 0x36
+#define BMA254_OFFSET_PARAMS_REG 0x37
+#define BMA254_OFFSET_FILT_X_REG 0x38
+#define BMA254_OFFSET_FILT_Y_REG 0x39
+#define BMA254_OFFSET_FILT_Z_REG 0x3A
+#define BMA254_OFFSET_UNFILT_X_REG 0x3B
+#define BMA254_OFFSET_UNFILT_Y_REG 0x3C
+#define BMA254_OFFSET_UNFILT_Z_REG 0x3D
+#define BMA254_SPARE_0_REG 0x3E
+#define BMA254_SPARE_1_REG 0x3F
+
+
+
+
+#define BMA254_ACC_X_LSB__POS 4
+#define BMA254_ACC_X_LSB__LEN 4
+#define BMA254_ACC_X_LSB__MSK 0xF0
+#define BMA254_ACC_X_LSB__REG BMA254_X_AXIS_LSB_REG
+
+#define BMA254_ACC_X_MSB__POS 0
+#define BMA254_ACC_X_MSB__LEN 8
+#define BMA254_ACC_X_MSB__MSK 0xFF
+#define BMA254_ACC_X_MSB__REG BMA254_X_AXIS_MSB_REG
+
+#define BMA254_ACC_Y_LSB__POS 4
+#define BMA254_ACC_Y_LSB__LEN 4
+#define BMA254_ACC_Y_LSB__MSK 0xF0
+#define BMA254_ACC_Y_LSB__REG BMA254_Y_AXIS_LSB_REG
+
+#define BMA254_ACC_Y_MSB__POS 0
+#define BMA254_ACC_Y_MSB__LEN 8
+#define BMA254_ACC_Y_MSB__MSK 0xFF
+#define BMA254_ACC_Y_MSB__REG BMA254_Y_AXIS_MSB_REG
+
+#define BMA254_ACC_Z_LSB__POS 4
+#define BMA254_ACC_Z_LSB__LEN 4
+#define BMA254_ACC_Z_LSB__MSK 0xF0
+#define BMA254_ACC_Z_LSB__REG BMA254_Z_AXIS_LSB_REG
+
+#define BMA254_ACC_Z_MSB__POS 0
+#define BMA254_ACC_Z_MSB__LEN 8
+#define BMA254_ACC_Z_MSB__MSK 0xFF
+#define BMA254_ACC_Z_MSB__REG BMA254_Z_AXIS_MSB_REG
+
+#define BMA254_RANGE_SEL__POS 0
+#define BMA254_RANGE_SEL__LEN 4
+#define BMA254_RANGE_SEL__MSK 0x0F
+#define BMA254_RANGE_SEL__REG BMA254_RANGE_SEL_REG
+
+#define BMA254_BANDWIDTH__POS 0
+#define BMA254_BANDWIDTH__LEN 5
+#define BMA254_BANDWIDTH__MSK 0x1F
+#define BMA254_BANDWIDTH__REG BMA254_BW_SEL_REG
+
+#define BMA254_EN_LOW_POWER__POS 6
+#define BMA254_EN_LOW_POWER__LEN 1
+#define BMA254_EN_LOW_POWER__MSK 0x40
+#define BMA254_EN_LOW_POWER__REG BMA254_MODE_CTRL_REG
+
+#define BMA254_EN_SUSPEND__POS 7
+#define BMA254_EN_SUSPEND__LEN 1
+#define BMA254_EN_SUSPEND__MSK 0x80
+#define BMA254_EN_SUSPEND__REG BMA254_MODE_CTRL_REG
+
+#define BMA254_GET_BITSLICE(regvar, bitname)\
+ ((regvar & bitname##__MSK) >> bitname##__POS)
+
+
+#define BMA254_SET_BITSLICE(regvar, bitname, val)\
+ ((regvar & ~bitname##__MSK) | ((val<<bitname##__POS)&bitname##__MSK))
+
+
+/* range and bandwidth */
+
+#define BMA254_RANGE_2G 3
+#define BMA254_RANGE_4G 5
+#define BMA254_RANGE_8G 8
+#define BMA254_RANGE_16G 12
+
+
+#define BMA254_BW_7_81HZ 0x08
+#define BMA254_BW_15_63HZ 0x09
+#define BMA254_BW_31_25HZ 0x0A
+#define BMA254_BW_62_50HZ 0x0B
+#define BMA254_BW_125HZ 0x0C
+#define BMA254_BW_250HZ 0x0D
+#define BMA254_BW_500HZ 0x0E
+#define BMA254_BW_1000HZ 0x0F
+
+/* mode settings */
+
+#define BMA254_MODE_NORMAL 0
+#define BMA254_MODE_LOWPOWER 1
+#define BMA254_MODE_SUSPEND 2
+
+
+#define BMA254_EN_SELF_TEST__POS 0
+#define BMA254_EN_SELF_TEST__LEN 2
+#define BMA254_EN_SELF_TEST__MSK 0x03
+#define BMA254_EN_SELF_TEST__REG BMA254_SELF_TEST_REG
+
+
+
+#define BMA254_NEG_SELF_TEST__POS 2
+#define BMA254_NEG_SELF_TEST__LEN 1
+#define BMA254_NEG_SELF_TEST__MSK 0x04
+#define BMA254_NEG_SELF_TEST__REG BMA254_SELF_TEST_REG
+
+#define BMA254_EN_FAST_COMP__POS 5
+#define BMA254_EN_FAST_COMP__LEN 2
+#define BMA254_EN_FAST_COMP__MSK 0x60
+#define BMA254_EN_FAST_COMP__REG BMA254_OFFSET_CTRL_REG
+
+#define BMA254_FAST_COMP_RDY_S__POS 4
+#define BMA254_FAST_COMP_RDY_S__LEN 1
+#define BMA254_FAST_COMP_RDY_S__MSK 0x10
+#define BMA254_FAST_COMP_RDY_S__REG BMA254_OFFSET_CTRL_REG
+
+#define BMA254_COMP_TARGET_OFFSET_X__POS 1
+#define BMA254_COMP_TARGET_OFFSET_X__LEN 2
+#define BMA254_COMP_TARGET_OFFSET_X__MSK 0x06
+#define BMA254_COMP_TARGET_OFFSET_X__REG BMA254_OFFSET_PARAMS_REG
+
+#define BMA254_COMP_TARGET_OFFSET_Y__POS 3
+#define BMA254_COMP_TARGET_OFFSET_Y__LEN 2
+#define BMA254_COMP_TARGET_OFFSET_Y__MSK 0x18
+#define BMA254_COMP_TARGET_OFFSET_Y__REG BMA254_OFFSET_PARAMS_REG
+
+#define BMA254_COMP_TARGET_OFFSET_Z__POS 5
+#define BMA254_COMP_TARGET_OFFSET_Z__LEN 2
+#define BMA254_COMP_TARGET_OFFSET_Z__MSK 0x60
+#define BMA254_COMP_TARGET_OFFSET_Z__REG BMA254_OFFSET_PARAMS_REG
+
+
+static const u8 bma254_valid_range[] = {
+ BMA254_RANGE_2G,
+ BMA254_RANGE_4G,
+ BMA254_RANGE_8G,
+ BMA254_RANGE_16G,
+};
+
+static const u8 bma254_valid_bw[] = {
+ BMA254_BW_7_81HZ,
+ BMA254_BW_15_63HZ,
+ BMA254_BW_31_25HZ,
+ BMA254_BW_62_50HZ,
+ BMA254_BW_125HZ,
+ BMA254_BW_250HZ,
+ BMA254_BW_500HZ,
+ BMA254_BW_1000HZ,
+};
+
+struct bma254acc {
+ s16 x,
+ y,
+ z;
+};
+
+struct bma254_data {
+ struct i2c_client *bma254_client;
+ atomic_t delay;
+ atomic_t enable;
+ unsigned char mode;
+ struct input_dev *input;
+ struct bma254acc value;
+ struct mutex value_mutex;
+ struct mutex enable_mutex;
+ struct mutex mode_mutex;
+ struct delayed_work work;
+ struct work_struct irq_work;
+ struct early_suspend early_suspend;
+
+ atomic_t selftest_result;
+};
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+static void bma254_early_suspend(struct early_suspend *h);
+static void bma254_late_resume(struct early_suspend *h);
+#endif
+
+static int bma254_smbus_read_byte(struct i2c_client *client,
+ unsigned char reg_addr, unsigned char *data)
+{
+ s32 dummy;
+ dummy = i2c_smbus_read_byte_data(client, reg_addr);
+ if (dummy < 0)
+ return -1;
+ *data = dummy & 0x000000ff;
+
+ return 0;
+}
+
+static int bma254_smbus_write_byte(struct i2c_client *client,
+ unsigned char reg_addr, unsigned char *data)
+{
+ s32 dummy;
+ dummy = i2c_smbus_write_byte_data(client, reg_addr, *data);
+ if (dummy < 0)
+ return -1;
+ return 0;
+}
+
+static int bma254_smbus_read_byte_block(struct i2c_client *client,
+ unsigned char reg_addr, unsigned char *data, unsigned char len)
+{
+ s32 dummy;
+ dummy = i2c_smbus_read_i2c_block_data(client, reg_addr, len, data);
+ if (dummy < 0)
+ return -1;
+ return 0;
+}
+
+static int bma254_set_mode(struct i2c_client *client, unsigned char Mode)
+{
+ int comres = 0;
+ unsigned char data1;
+
+ if (client == NULL) {
+ comres = -1;
+ } else{
+ if (Mode < 3) {
+ comres = bma254_smbus_read_byte(client,
+ BMA254_EN_LOW_POWER__REG, &data1);
+ switch (Mode) {
+ case BMA254_MODE_NORMAL:
+ data1 = BMA254_SET_BITSLICE(data1,
+ BMA254_EN_LOW_POWER, 0);
+ data1 = BMA254_SET_BITSLICE(data1,
+ BMA254_EN_SUSPEND, 0);
+ break;
+ case BMA254_MODE_LOWPOWER:
+ data1 = BMA254_SET_BITSLICE(data1,
+ BMA254_EN_LOW_POWER, 1);
+ data1 = BMA254_SET_BITSLICE(data1,
+ BMA254_EN_SUSPEND, 0);
+ break;
+ case BMA254_MODE_SUSPEND:
+ data1 = BMA254_SET_BITSLICE(data1,
+ BMA254_EN_LOW_POWER, 0);
+ data1 = BMA254_SET_BITSLICE(data1,
+ BMA254_EN_SUSPEND, 1);
+ break;
+ default:
+ break;
+ }
+
+ comres += bma254_smbus_write_byte(client,
+ BMA254_EN_LOW_POWER__REG, &data1);
+ } else{
+ comres = -1;
+ }
+ }
+
+ return comres;
+}
+
+static int bma254_get_mode(struct i2c_client *client, unsigned char *Mode)
+{
+ int comres = 0;
+
+ if (client == NULL) {
+ comres = -1;
+ } else{
+ comres = bma254_smbus_read_byte(client,
+ BMA254_EN_LOW_POWER__REG, Mode);
+ *Mode = (*Mode) >> 6;
+ }
+
+ return comres;
+}
+
+static int bma254_set_range(struct i2c_client *client, unsigned char Range)
+{
+ int comres = 0;
+ unsigned char data1;
+ int i;
+
+ if (client == NULL) {
+ comres = -1;
+ } else{
+ for (i = 0; i < ARRAY_SIZE(bma254_valid_range); i++) {
+ if (bma254_valid_range[i] == Range)
+ break;
+ }
+
+ if (ARRAY_SIZE(bma254_valid_range) > i) {
+ comres = bma254_smbus_read_byte(client,
+ BMA254_RANGE_SEL_REG, &data1);
+
+ data1 = BMA254_SET_BITSLICE(data1,
+ BMA254_RANGE_SEL, Range);
+
+ comres += bma254_smbus_write_byte(client,
+ BMA254_RANGE_SEL_REG, &data1);
+ } else {
+ comres = -EINVAL;
+ }
+ }
+
+ return comres;
+}
+
+static int bma254_get_range(struct i2c_client *client, unsigned char *Range)
+{
+ int comres = 0;
+ unsigned char data;
+
+ if (client == NULL) {
+ comres = -1;
+ } else{
+ comres = bma254_smbus_read_byte(client, BMA254_RANGE_SEL__REG,
+ &data);
+ data = BMA254_GET_BITSLICE(data, BMA254_RANGE_SEL);
+ *Range = data;
+ }
+
+ return comres;
+}
+
+
+static int bma254_set_bandwidth(struct i2c_client *client, unsigned char BW)
+{
+ int comres = 0;
+ unsigned char data;
+ int i = 0;
+
+ if (client == NULL) {
+ comres = -1;
+ } else {
+
+ for (i = 0; i < ARRAY_SIZE(bma254_valid_bw); i++) {
+ if (bma254_valid_bw[i] == BW)
+ break;
+ }
+
+ if (ARRAY_SIZE(bma254_valid_bw) > i) {
+ comres = bma254_smbus_read_byte(client,
+ BMA254_BANDWIDTH__REG, &data);
+ data = BMA254_SET_BITSLICE(data, BMA254_BANDWIDTH, BW);
+ comres += bma254_smbus_write_byte(client,
+ BMA254_BANDWIDTH__REG, &data);
+ } else {
+ comres = -EINVAL;
+ }
+ }
+
+ return comres;
+}
+
+static int bma254_get_bandwidth(struct i2c_client *client, unsigned char *BW)
+{
+ int comres = 0;
+ unsigned char data;
+
+ if (client == NULL) {
+ comres = -1;
+ } else{
+ comres = bma254_smbus_read_byte(client, BMA254_BANDWIDTH__REG,
+ &data);
+ data = BMA254_GET_BITSLICE(data, BMA254_BANDWIDTH);
+ if (data < BMA254_BW_7_81HZ)
+ *BW = BMA254_BW_7_81HZ;
+ else if (data > BMA254_BW_1000HZ)
+ *BW = BMA254_BW_1000HZ;
+ else
+ *BW = data;
+ }
+
+ return comres;
+}
+
+static int bma254_read_accel_xyz(struct i2c_client *client,
+ struct bma254acc *acc)
+{
+ int comres;
+ unsigned char data[6];
+ if (client == NULL) {
+ comres = -1;
+ } else{
+ comres = bma254_smbus_read_byte_block(client,
+ BMA254_ACC_X_LSB__REG, data, 6);
+
+ acc->x = BMA254_GET_BITSLICE(data[0], BMA254_ACC_X_LSB)
+ |(BMA254_GET_BITSLICE(data[1], BMA254_ACC_X_MSB)
+ <<BMA254_ACC_X_LSB__LEN);
+ acc->x = acc->x << (sizeof(short)*8-(BMA254_ACC_X_LSB__LEN
+ + BMA254_ACC_X_MSB__LEN));
+ acc->x = acc->x >> (sizeof(short)*8-(BMA254_ACC_X_LSB__LEN
+ + BMA254_ACC_X_MSB__LEN));
+ acc->y = BMA254_GET_BITSLICE(data[2], BMA254_ACC_Y_LSB)
+ | (BMA254_GET_BITSLICE(data[3], BMA254_ACC_Y_MSB)
+ <<BMA254_ACC_Y_LSB__LEN);
+ acc->y = acc->y << (sizeof(short)*8-(BMA254_ACC_Y_LSB__LEN
+ + BMA254_ACC_Y_MSB__LEN));
+ acc->y = acc->y >> (sizeof(short)*8-(BMA254_ACC_Y_LSB__LEN
+ + BMA254_ACC_Y_MSB__LEN));
+
+ acc->z = BMA254_GET_BITSLICE(data[4], BMA254_ACC_Z_LSB)
+ | (BMA254_GET_BITSLICE(data[5], BMA254_ACC_Z_MSB)
+ <<BMA254_ACC_Z_LSB__LEN);
+ acc->z = acc->z << (sizeof(short)*8-(BMA254_ACC_Z_LSB__LEN
+ + BMA254_ACC_Z_MSB__LEN));
+ acc->z = acc->z >> (sizeof(short)*8-(BMA254_ACC_Z_LSB__LEN
+ + BMA254_ACC_Z_MSB__LEN));
+ }
+
+ return comres;
+}
+
+static void bma254_work_func(struct work_struct *work)
+{
+ struct bma254_data *bma254 = container_of((struct delayed_work *)work,
+ struct bma254_data, work);
+ static struct bma254acc acc;
+ unsigned long delay = msecs_to_jiffies(atomic_read(&bma254->delay));
+
+ bma254_read_accel_xyz(bma254->bma254_client, &acc);
+
+ input_report_abs(bma254->input, ABS_X, acc.x);
+ input_report_abs(bma254->input, ABS_Y, acc.y);
+ input_report_abs(bma254->input, ABS_Z, acc.z);
+ input_sync(bma254->input);
+ mutex_lock(&bma254->value_mutex);
+ bma254->value = acc;
+ mutex_unlock(&bma254->value_mutex);
+ schedule_delayed_work(&bma254->work, delay);
+}
+
+static ssize_t bma254_range_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned char data;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+
+ if (bma254_get_range(bma254->bma254_client, &data) < 0)
+ return sprintf(buf, "Read error\n");
+
+ return sprintf(buf, "%d\n", data);
+}
+
+static ssize_t bma254_range_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long data;
+ int error;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+
+ error = strict_strtoul(buf, 10, &data);
+ if (error)
+ return error;
+
+ if (bma254_set_range(bma254->bma254_client, (unsigned char) data) < 0)
+ return -EINVAL;
+
+ return count;
+}
+
+static ssize_t bma254_bandwidth_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned char data;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+
+ if (bma254_get_bandwidth(bma254->bma254_client, &data) < 0)
+ return sprintf(buf, "Read error\n");
+
+ return sprintf(buf, "%d\n", data);
+
+}
+
+static ssize_t bma254_bandwidth_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long data;
+ int error;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+
+ error = strict_strtoul(buf, 10, &data);
+ if (error)
+ return error;
+ if (bma254_set_bandwidth(bma254->bma254_client,
+ (unsigned char) data) < 0)
+ return -EINVAL;
+
+ return count;
+}
+
+static ssize_t bma254_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned char data;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+
+ if (bma254_get_mode(bma254->bma254_client, &data) < 0)
+ return sprintf(buf, "Read error\n");
+
+ return sprintf(buf, "%d\n", data);
+}
+
+static ssize_t bma254_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long data;
+ int error;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+
+ error = strict_strtoul(buf, 10, &data);
+ if (error)
+ return error;
+ if (bma254_set_mode(bma254->bma254_client, (unsigned char) data) < 0)
+ return -EINVAL;
+
+ return count;
+}
+
+
+static ssize_t bma254_value_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct input_dev *input = to_input_dev(dev);
+ struct bma254_data *bma254 = input_get_drvdata(input);
+ int err;
+
+ mutex_lock(&bma254->value_mutex);
+ err = sprintf(buf, "%d %d %d\n", bma254->value.x, bma254->value.y,
+ bma254->value.z);
+ mutex_unlock(&bma254->value_mutex);
+
+ return err;
+}
+
+static ssize_t bma254_delay_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+
+ return sprintf(buf, "%d\n", atomic_read(&bma254->delay));
+
+}
+
+static ssize_t bma254_delay_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long data;
+ int error;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+
+ error = strict_strtoul(buf, 10, &data);
+ if (error)
+ return error;
+
+ if (data <= 0)
+ return -EINVAL;
+
+ if (data < BMA254_MIN_DELAY)
+ data = BMA254_MIN_DELAY;
+
+ atomic_set(&bma254->delay, (unsigned int) data);
+
+ return count;
+}
+
+
+static ssize_t bma254_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+
+ return sprintf(buf, "%d\n", atomic_read(&bma254->enable));
+
+}
+
+static void bma254_set_enable(struct device *dev, int enable)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+ int pre_enable = atomic_read(&bma254->enable);
+
+ mutex_lock(&bma254->enable_mutex);
+ if (enable) {
+ if (pre_enable == 0) {
+ schedule_delayed_work(&bma254->work,
+ msecs_to_jiffies(
+ atomic_read(&bma254->delay)));
+ atomic_set(&bma254->enable, 1);
+ }
+
+ } else {
+ if (pre_enable == 1) {
+ cancel_delayed_work_sync(&bma254->work);
+ atomic_set(&bma254->enable, 0);
+ }
+ }
+ mutex_unlock(&bma254->enable_mutex);
+
+}
+
+static ssize_t bma254_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long data;
+ int error;
+
+ error = strict_strtoul(buf, 10, &data);
+ if (error)
+ return error;
+ if ((data == 0) || (data == 1))
+ bma254_set_enable(dev, data);
+
+ return count;
+}
+
+static ssize_t bma254_update_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+
+ mutex_lock(&bma254->value_mutex);
+ bma254_read_accel_xyz(bma254->bma254_client, &bma254->value);
+ mutex_unlock(&bma254->value_mutex);
+ return count;
+}
+
+static int bma254_set_selftest_st(struct i2c_client *client,
+ unsigned char selftest)
+{
+ int comres = 0;
+ unsigned char data;
+
+ comres = bma254_smbus_read_byte(client,
+ BMA254_EN_SELF_TEST__REG, &data);
+ data = BMA254_SET_BITSLICE(data,
+ BMA254_EN_SELF_TEST, selftest);
+ comres = bma254_smbus_write_byte(client,
+ BMA254_EN_SELF_TEST__REG, &data);
+
+ return comres;
+}
+
+static int bma254_set_selftest_stn(struct i2c_client *client, unsigned char stn)
+{
+ int comres = 0;
+ unsigned char data;
+
+ comres = bma254_smbus_read_byte(client,
+ BMA254_NEG_SELF_TEST__REG, &data);
+ data = BMA254_SET_BITSLICE(data, BMA254_NEG_SELF_TEST, stn);
+ comres = bma254_smbus_write_byte(client,
+ BMA254_NEG_SELF_TEST__REG, &data);
+
+ return comres;
+}
+
+static int bma254_read_accel_x(struct i2c_client *client, short *a_x)
+{
+ int comres;
+ unsigned char data[2];
+
+ comres = bma254_smbus_read_byte_block(client,
+ BMA254_ACC_X_LSB__REG, data, 2);
+ *a_x = BMA254_GET_BITSLICE(data[0], BMA254_ACC_X_LSB)
+ | (BMA254_GET_BITSLICE(data[1], BMA254_ACC_X_MSB)
+ <<BMA254_ACC_X_LSB__LEN);
+ *a_x = *a_x << (sizeof(short)*8
+ -(BMA254_ACC_X_LSB__LEN+BMA254_ACC_X_MSB__LEN));
+ *a_x = *a_x >> (sizeof(short)*8
+ -(BMA254_ACC_X_LSB__LEN+BMA254_ACC_X_MSB__LEN));
+
+ return comres;
+}
+static int bma254_read_accel_y(struct i2c_client *client, short *a_y)
+{
+ int comres;
+ unsigned char data[2];
+
+ comres = bma254_smbus_read_byte_block(client,
+ BMA254_ACC_Y_LSB__REG, data, 2);
+ *a_y = BMA254_GET_BITSLICE(data[0], BMA254_ACC_Y_LSB)
+ | (BMA254_GET_BITSLICE(data[1], BMA254_ACC_Y_MSB)
+ <<BMA254_ACC_Y_LSB__LEN);
+ *a_y = *a_y << (sizeof(short)*8
+ -(BMA254_ACC_Y_LSB__LEN+BMA254_ACC_Y_MSB__LEN));
+ *a_y = *a_y >> (sizeof(short)*8
+ -(BMA254_ACC_Y_LSB__LEN+BMA254_ACC_Y_MSB__LEN));
+
+ return comres;
+}
+
+static int bma254_read_accel_z(struct i2c_client *client, short *a_z)
+{
+ int comres;
+ unsigned char data[2];
+
+ comres = bma254_smbus_read_byte_block(client,
+ BMA254_ACC_Z_LSB__REG, data, 2);
+ *a_z = BMA254_GET_BITSLICE(data[0], BMA254_ACC_Z_LSB)
+ | BMA254_GET_BITSLICE(data[1], BMA254_ACC_Z_MSB)
+ <<BMA254_ACC_Z_LSB__LEN;
+ *a_z = *a_z << (sizeof(short)*8
+ -(BMA254_ACC_Z_LSB__LEN+BMA254_ACC_Z_MSB__LEN));
+ *a_z = *a_z >> (sizeof(short)*8
+ -(BMA254_ACC_Z_LSB__LEN+BMA254_ACC_Z_MSB__LEN));
+
+ return comres;
+}
+
+static ssize_t bma254_selftest_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+
+ return sprintf(buf, "%d\n", atomic_read(&bma254->selftest_result));
+}
+
+static ssize_t bma254_selftest_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long data;
+ unsigned char clear_value = 0;
+ int error;
+ short value1 = 0;
+ short value2 = 0;
+ short diff = 0;
+ unsigned long result = 0;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+
+ error = strict_strtoul(buf, 10, &data);
+ if (error)
+ return error;
+
+ if (data != 1)
+ return -EINVAL;
+ /* set to 2 G range */
+ if (bma254_set_range(bma254->bma254_client, 0) < 0)
+ return -EINVAL;
+
+ bma254_smbus_write_byte(bma254->bma254_client, 0x32, &clear_value);
+ /* 1 for x-axis*/
+ bma254_set_selftest_st(bma254->bma254_client, 1);
+ /* positive direction*/
+ bma254_set_selftest_stn(bma254->bma254_client, 0);
+ usleep_range(10000, 20000);
+ bma254_read_accel_x(bma254->bma254_client, &value1);
+ /* negative direction*/
+ bma254_set_selftest_stn(bma254->bma254_client, 1);
+ usleep_range(10000, 20000);
+ bma254_read_accel_x(bma254->bma254_client, &value2);
+ diff = value1-value2;
+
+ pr_info("diff x is %d, value1 is %d, value2 is %d\n",
+ diff, value1, value2);
+
+ if (abs(diff) < 204)
+ result |= 1;
+
+ /* 2 for y-axis*/
+ bma254_set_selftest_st(bma254->bma254_client, 2);
+ /* positive direction*/
+ bma254_set_selftest_stn(bma254->bma254_client, 0);
+ usleep_range(10000, 20000);
+ bma254_read_accel_y(bma254->bma254_client, &value1);
+ /* negative direction*/
+ bma254_set_selftest_stn(bma254->bma254_client, 1);
+ usleep_range(10000, 20000);
+ bma254_read_accel_y(bma254->bma254_client, &value2);
+ diff = value1-value2;
+ pr_info("diff y is %d, value1 is %d, value2 is %d\n",
+ diff, value1, value2);
+ if (abs(diff) < 204)
+ result |= 2;
+
+ /* 3 for z-axis*/
+ bma254_set_selftest_st(bma254->bma254_client, 3);
+ /* positive direction*/
+ bma254_set_selftest_stn(bma254->bma254_client, 0);
+ usleep_range(10000, 20000);
+ bma254_read_accel_z(bma254->bma254_client, &value1);
+ /* negative direction*/
+ bma254_set_selftest_stn(bma254->bma254_client, 1);
+ usleep_range(10000, 20000);
+ bma254_read_accel_z(bma254->bma254_client, &value2);
+ diff = value1 - value2;
+
+ pr_info("diff z is %d, value1 is %d, value2 is %d\n",
+ diff, value1, value2);
+ if (abs(diff) < 102)
+ result |= 4;
+
+ atomic_set(&bma254->selftest_result, (unsigned int) result);
+
+ pr_info("self test finished\n");
+
+
+ return count;
+}
+
+static int bma254_set_offset_target_x(struct i2c_client *client,
+ unsigned char offsettarget)
+{
+ int comres = 0;
+ unsigned char data;
+
+ comres = bma254_smbus_read_byte(client,
+ BMA254_COMP_TARGET_OFFSET_X__REG, &data);
+ data = BMA254_SET_BITSLICE(data,
+ BMA254_COMP_TARGET_OFFSET_X, offsettarget);
+ comres = bma254_smbus_write_byte(client,
+ BMA254_COMP_TARGET_OFFSET_X__REG, &data);
+
+ return comres;
+}
+
+static int bma254_get_offset_target_x(struct i2c_client *client,
+ unsigned char *offsettarget)
+{
+ int comres = 0 ;
+ unsigned char data;
+
+ comres = bma254_smbus_read_byte(client,
+ BMA254_OFFSET_PARAMS_REG, &data);
+ data = BMA254_GET_BITSLICE(data, BMA254_COMP_TARGET_OFFSET_X);
+ *offsettarget = data;
+
+ return comres;
+}
+
+static int bma254_set_offset_target_y(struct i2c_client *client,
+ unsigned char offsettarget)
+{
+ int comres = 0;
+ unsigned char data;
+
+ comres = bma254_smbus_read_byte(client,
+ BMA254_COMP_TARGET_OFFSET_Y__REG, &data);
+ data = BMA254_SET_BITSLICE(data,
+ BMA254_COMP_TARGET_OFFSET_Y, offsettarget);
+ comres = bma254_smbus_write_byte(client,
+ BMA254_COMP_TARGET_OFFSET_Y__REG, &data);
+
+ return comres;
+}
+
+static int bma254_get_offset_target_y(struct i2c_client *client,
+ unsigned char *offsettarget)
+{
+ int comres = 0 ;
+ unsigned char data;
+
+ comres = bma254_smbus_read_byte(client,
+ BMA254_OFFSET_PARAMS_REG, &data);
+ data = BMA254_GET_BITSLICE(data, BMA254_COMP_TARGET_OFFSET_Y);
+ *offsettarget = data;
+
+ return comres;
+}
+
+static int bma254_set_offset_target_z(struct i2c_client *client,
+ unsigned char offsettarget)
+{
+ int comres = 0;
+ unsigned char data;
+
+ comres = bma254_smbus_read_byte(client,
+ BMA254_COMP_TARGET_OFFSET_Z__REG, &data);
+ data = BMA254_SET_BITSLICE(data,
+ BMA254_COMP_TARGET_OFFSET_Z, offsettarget);
+ comres = bma254_smbus_write_byte(client,
+ BMA254_COMP_TARGET_OFFSET_Z__REG, &data);
+
+ return comres;
+}
+
+static int bma254_get_offset_target_z(struct i2c_client *client,
+ unsigned char *offsettarget)
+{
+ int comres = 0 ;
+ unsigned char data;
+
+ comres = bma254_smbus_read_byte(client,
+ BMA254_OFFSET_PARAMS_REG, &data);
+ data = BMA254_GET_BITSLICE(data, BMA254_COMP_TARGET_OFFSET_Z);
+ *offsettarget = data;
+
+ return comres;
+}
+
+static int bma254_get_cal_ready(struct i2c_client *client,
+ unsigned char *calrdy)
+{
+ int comres = 0 ;
+ unsigned char data;
+
+ comres = bma254_smbus_read_byte(client,
+ BMA254_OFFSET_CTRL_REG, &data);
+ data = BMA254_GET_BITSLICE(data, BMA254_FAST_COMP_RDY_S);
+ *calrdy = data;
+
+ return comres;
+}
+
+static int bma254_set_cal_trigger(struct i2c_client *client,
+ unsigned char caltrigger)
+{
+ int comres = 0;
+ unsigned char data;
+
+ comres = bma254_smbus_read_byte(client,
+ BMA254_EN_FAST_COMP__REG, &data);
+ data = BMA254_SET_BITSLICE(data,
+ BMA254_EN_FAST_COMP, caltrigger);
+ comres = bma254_smbus_write_byte(client,
+ BMA254_EN_FAST_COMP__REG, &data);
+
+ return comres;
+}
+
+
+static ssize_t bma254_fast_calibration_x_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned char data;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+
+ if (bma254_get_offset_target_x(bma254->bma254_client, &data) < 0)
+ return sprintf(buf, "Read error\n");
+
+ return sprintf(buf, "%d\n", data);
+}
+
+static ssize_t bma254_fast_calibration_x_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long data;
+ signed char tmp;
+ unsigned char timeout = 0;
+ int error;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+
+ error = strict_strtoul(buf, 10, &data);
+ if (error)
+ return error;
+
+ if (bma254_set_offset_target_x(bma254->bma254_client,
+ (unsigned char)data) < 0)
+ return -EINVAL;
+
+ if (bma254_set_cal_trigger(bma254->bma254_client, 1) < 0)
+ return -EINVAL;
+
+ do {
+ usleep_range(2000, 3000);
+ bma254_get_cal_ready(bma254->bma254_client, &tmp);
+
+ pr_info("wait 2ms and got cal ready flag is %d\n",
+ tmp);
+ timeout++;
+ if (timeout == 50) {
+ pr_err("get fast calibration ready error\n");
+ return -EINVAL;
+ };
+
+ } while (tmp == 0);
+
+ pr_info("x axis fast calibration finished\n");
+ return count;
+}
+
+static ssize_t bma254_fast_calibration_y_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+
+
+ unsigned char data;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+
+ if (bma254_get_offset_target_y(bma254->bma254_client, &data) < 0)
+ return sprintf(buf, "Read error\n");
+
+ return sprintf(buf, "%d\n", data);
+
+}
+
+static ssize_t bma254_fast_calibration_y_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long data;
+ signed char tmp;
+ unsigned char timeout = 0;
+ int error;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+
+ error = strict_strtoul(buf, 10, &data);
+ if (error)
+ return error;
+
+ if (bma254_set_offset_target_y(bma254->bma254_client,
+ (unsigned char)data) < 0)
+ return -EINVAL;
+
+ if (bma254_set_cal_trigger(bma254->bma254_client, 2) < 0)
+ return -EINVAL;
+
+ do {
+ usleep_range(2000, 3000);
+ bma254_get_cal_ready(bma254->bma254_client, &tmp);
+
+ pr_info("wait 2ms and got cal ready flag is %d\n",
+ tmp);
+ timeout++;
+ if (timeout == 50) {
+ pr_err("get fast calibration ready error\n");
+ return -EINVAL;
+ };
+
+ } while (tmp == 0);
+
+ pr_info("y axis fast calibration finished\n");
+ return count;
+}
+
+static ssize_t bma254_fast_calibration_z_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+
+
+ unsigned char data;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+
+ if (bma254_get_offset_target_z(bma254->bma254_client, &data) < 0)
+ return sprintf(buf, "Read error\n");
+
+ return sprintf(buf, "%d\n", data);
+
+}
+
+static ssize_t bma254_fast_calibration_z_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long data;
+ signed char tmp;
+ unsigned char timeout = 0;
+ int error;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma254_data *bma254 = i2c_get_clientdata(client);
+
+ error = strict_strtoul(buf, 10, &data);
+ if (error)
+ return error;
+
+ if (bma254_set_offset_target_z(bma254->bma254_client,
+ (unsigned char)data) < 0)
+ return -EINVAL;
+
+ if (bma254_set_cal_trigger(bma254->bma254_client, 3) < 0)
+ return -EINVAL;
+
+ do {
+ usleep_range(2000, 3000);
+ bma254_get_cal_ready(bma254->bma254_client, &tmp);
+
+ pr_info("wait 2ms and got cal ready flag is %d\n",
+ tmp);
+ timeout++;
+ if (timeout == 50) {
+ pr_err("get fast calibration ready error\n");
+ return -EINVAL;
+ }
+ } while (tmp == 0);
+
+ pr_info("z axis fast calibration finished\n");
+ return count;
+}
+
+
+
+static DEVICE_ATTR(range, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH,
+ bma254_range_show, bma254_range_store);
+static DEVICE_ATTR(bandwidth, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH,
+ bma254_bandwidth_show, bma254_bandwidth_store);
+static DEVICE_ATTR(mode, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH,
+ bma254_mode_show, bma254_mode_store);
+static DEVICE_ATTR(value, S_IRUGO,
+ bma254_value_show, NULL);
+static DEVICE_ATTR(delay, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH,
+ bma254_delay_show, bma254_delay_store);
+static DEVICE_ATTR(enable, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH,
+ bma254_enable_show, bma254_enable_store);
+static DEVICE_ATTR(update, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH,
+ NULL, bma254_update_store);
+static DEVICE_ATTR(selftest, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH,
+ bma254_selftest_show, bma254_selftest_store);
+static DEVICE_ATTR(fast_calibration_x, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH,
+ bma254_fast_calibration_x_show,
+ bma254_fast_calibration_x_store);
+static DEVICE_ATTR(fast_calibration_y, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH,
+ bma254_fast_calibration_y_show,
+ bma254_fast_calibration_y_store);
+static DEVICE_ATTR(fast_calibration_z, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH,
+ bma254_fast_calibration_z_show,
+ bma254_fast_calibration_z_store);
+
+static struct attribute *bma254_attributes[] = {
+ &dev_attr_range.attr,
+ &dev_attr_bandwidth.attr,
+ &dev_attr_mode.attr,
+ &dev_attr_value.attr,
+ &dev_attr_delay.attr,
+ &dev_attr_enable.attr,
+ &dev_attr_update.attr,
+ &dev_attr_selftest.attr,
+ &dev_attr_fast_calibration_x.attr,
+ &dev_attr_fast_calibration_y.attr,
+ &dev_attr_fast_calibration_z.attr,
+ NULL
+};
+
+static struct attribute_group bma254_attribute_group = {
+ .attrs = bma254_attributes
+};
+
+static int bma254_input_init(struct bma254_data *bma254)
+{
+ struct input_dev *dev;
+ int err;
+
+ dev = input_allocate_device();
+ if (!dev)
+ return -ENOMEM;
+ dev->name = SENSOR_NAME;
+ dev->id.bustype = BUS_I2C;
+
+ input_set_capability(dev, EV_ABS, ABS_MISC);
+ input_set_abs_params(dev, ABS_X, ABSMIN_2G, ABSMAX_2G, 0, 0);
+ input_set_abs_params(dev, ABS_Y, ABSMIN_2G, ABSMAX_2G, 0, 0);
+ input_set_abs_params(dev, ABS_Z, ABSMIN_2G, ABSMAX_2G, 0, 0);
+ input_set_drvdata(dev, bma254);
+
+ err = input_register_device(dev);
+ if (err < 0) {
+ pr_err("bma254_input_init input_register_device=%d\n", err);
+ input_free_device(dev);
+ return err;
+ }
+ bma254->input = dev;
+
+ return 0;
+}
+
+static void bma254_input_delete(struct bma254_data *bma254)
+{
+ struct input_dev *dev = bma254->input;
+
+ input_unregister_device(dev);
+ input_free_device(dev);
+}
+
+static int bma254_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int err = 0;
+ int tempvalue;
+ struct bma254_data *data;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ pr_err("i2c_check_functionality error\n");
+ goto exit;
+ }
+ data = kzalloc(sizeof(struct bma254_data), GFP_KERNEL);
+ if (!data) {
+ err = -ENOMEM;
+ goto exit;
+ }
+ /* read chip id */
+ tempvalue = 0;
+ tempvalue = i2c_smbus_read_word_data(client, BMA254_CHIP_ID_REG);
+
+ if ((tempvalue&0x00FF) == BMA254_CHIP_ID) {
+ pr_info("Bosch Sensortec Device detected!\n" \
+ "BMA254 registered I2C driver!\n");
+ } else{
+ pr_err("Bosch Sensortec Device not found, "\
+ "i2c error %d\n", tempvalue);
+ err = -1;
+ goto kfree_exit;
+ }
+ i2c_set_clientdata(client, data);
+ data->bma254_client = client;
+ mutex_init(&data->value_mutex);
+ mutex_init(&data->mode_mutex);
+ mutex_init(&data->enable_mutex);
+ bma254_set_bandwidth(client, BMA254_BW_SET);
+ bma254_set_range(client, BMA254_RANGE_SET);
+
+ INIT_DELAYED_WORK(&data->work, bma254_work_func);
+ atomic_set(&data->delay, BMA254_DEFAULT_DELAY);
+ atomic_set(&data->enable, 0);
+ err = bma254_input_init(data);
+ if (err < 0)
+ goto kfree_exit;
+
+ err = sysfs_create_group(&data->input->dev.kobj,
+ &bma254_attribute_group);
+ if (err < 0)
+ goto error_sysfs;
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
+ data->early_suspend.suspend = bma254_early_suspend;
+ data->early_suspend.resume = bma254_late_resume;
+ register_early_suspend(&data->early_suspend);
+#endif
+
+ return 0;
+
+error_sysfs:
+ bma254_input_delete(data);
+
+kfree_exit:
+ kfree(data);
+exit:
+ return err;
+}
+
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+static void bma254_early_suspend(struct early_suspend *h)
+{
+ struct bma254_data *data =
+ container_of(h, struct bma254_data, early_suspend);
+
+ mutex_lock(&data->enable_mutex);
+ if (atomic_read(&data->enable) == 1) {
+ bma254_set_mode(data->bma254_client, BMA254_MODE_SUSPEND);
+ cancel_delayed_work_sync(&data->work);
+ }
+ mutex_unlock(&data->enable_mutex);
+}
+
+
+static void bma254_late_resume(struct early_suspend *h)
+{
+ struct bma254_data *data =
+ container_of(h, struct bma254_data, early_suspend);
+
+ mutex_lock(&data->enable_mutex);
+ if (atomic_read(&data->enable) == 1) {
+ bma254_set_mode(data->bma254_client, BMA254_MODE_NORMAL);
+ schedule_delayed_work(&data->work,
+ msecs_to_jiffies(atomic_read(&data->delay)));
+ }
+ mutex_unlock(&data->enable_mutex);
+}
+#endif
+
+static int bma254_remove(struct i2c_client *client)
+{
+ struct bma254_data *data = i2c_get_clientdata(client);
+
+ bma254_set_enable(&client->dev, 0);
+ unregister_early_suspend(&data->early_suspend);
+ sysfs_remove_group(&data->input->dev.kobj, &bma254_attribute_group);
+ bma254_input_delete(data);
+ kfree(data);
+ return 0;
+}
+
+
+static const struct i2c_device_id bma254_id[] = {
+ { SENSOR_NAME, 0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, bma254_id);
+
+static struct i2c_driver bma254_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = SENSOR_NAME,
+ },
+ .id_table = bma254_id,
+ .probe = bma254_probe,
+ .remove = bma254_remove,
+
+};
+
+static int __init BMA254_init(void)
+{
+ return i2c_add_driver(&bma254_driver);
+}
+
+static void __exit BMA254_exit(void)
+{
+ i2c_del_driver(&bma254_driver);
+}
+
+MODULE_AUTHOR("Albert Zhang <xu.zhang@bosch-sensortec.com>");
+MODULE_DESCRIPTION("BMA254 driver");
+MODULE_LICENSE("GPL");
+
+module_init(BMA254_init);
+module_exit(BMA254_exit);
diff --git a/drivers/sensor/bmp180.c b/drivers/sensor/bmp180.c
new file mode 100644
index 0000000..21b5589
--- /dev/null
+++ b/drivers/sensor/bmp180.c
@@ -0,0 +1,778 @@
+/*
+ * Copyright (C) 2011 Samsung Electronics. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input-polldev.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/sensor/sensors_core.h>
+
+#define BMP180_DRV_NAME "bmp180"
+#define DRIVER_VERSION "1.0"
+
+/* Register definitions */
+#define BMP180_TAKE_MEAS_REG 0xf4
+#define BMP180_READ_MEAS_REG_U 0xf6
+#define BMP180_READ_MEAS_REG_L 0xf7
+#define BMP180_READ_MEAS_REG_XL 0xf8
+
+/*
+ * Bytes defined by the spec to take measurements
+ * Temperature will take 4.5ms before EOC
+ */
+#define BMP180_MEAS_TEMP 0x2e
+/* 4.5ms wait for measurement */
+#define BMP180_MEAS_PRESS_OVERSAMP_0 0x34
+/* 7.5ms wait for measurement */
+#define BMP180_MEAS_PRESS_OVERSAMP_1 0x74
+/* 13.5ms wait for measurement */
+#define BMP180_MEAS_PRESS_OVERSAMP_2 0xb4
+/* 25.5ms wait for measurement */
+#define BMP180_MEAS_PRESS_OVERSAMP_3 0xf4
+
+/*
+ * EEPROM registers each is a two byte value so there is
+ * an upper byte and a lower byte
+ */
+#define BMP180_EEPROM_AC1_U 0xaa
+#define BMP180_EEPROM_AC1_L 0xab
+#define BMP180_EEPROM_AC2_U 0xac
+#define BMP180_EEPROM_AC2_L 0xad
+#define BMP180_EEPROM_AC3_U 0xae
+#define BMP180_EEPROM_AC3_L 0xaf
+#define BMP180_EEPROM_AC4_U 0xb0
+#define BMP180_EEPROM_AC4_L 0xb1
+#define BMP180_EEPROM_AC5_U 0xb2
+#define BMP180_EEPROM_AC5_L 0xb3
+#define BMP180_EEPROM_AC6_U 0xb4
+#define BMP180_EEPROM_AC6_L 0xb5
+#define BMP180_EEPROM_B1_U 0xb6
+#define BMP180_EEPROM_B1_L 0xb7
+#define BMP180_EEPROM_B2_U 0xb8
+#define BMP180_EEPROM_B2_L 0xb9
+#define BMP180_EEPROM_MB_U 0xba
+#define BMP180_EEPROM_MB_L 0xbb
+#define BMP180_EEPROM_MC_U 0xbc
+#define BMP180_EEPROM_MC_L 0xbd
+#define BMP180_EEPROM_MD_U 0xbe
+#define BMP180_EEPROM_MD_L 0xbf
+
+#define I2C_TRIES 5
+#define AUTO_INCREMENT 0x80
+
+#define DELAY_LOWBOUND (50 * NSEC_PER_MSEC)
+#define DELAY_UPBOUND (500 * NSEC_PER_MSEC)
+#define DELAY_DEFAULT (200 * NSEC_PER_MSEC)
+
+#define PRESSURE_MAX 125000
+#define PRESSURE_MIN 95000
+#define PRESSURE_FUZZ 5
+#define PRESSURE_FLAT 5
+
+#define FACTORY_TEST
+#ifdef FACTORY_TEST
+#define TEMP_MAX 3000
+#define TEMP_MIN -3000
+#define SEA_LEVEL_MAX 999999
+#define SEA_LEVEL_MIN -1
+#endif
+
+struct bmp180_eeprom_data {
+ s16 AC1, AC2, AC3;
+ u16 AC4, AC5, AC6;
+ s16 B1, B2;
+ s16 MB, MC, MD;
+};
+
+struct bmp180_data {
+ struct i2c_client *client;
+ struct mutex lock;
+ struct workqueue_struct *wq;
+ struct work_struct work_pressure;
+ struct input_dev *input_dev;
+ struct hrtimer timer;
+#ifdef FACTORY_TEST
+ struct device *dev;
+#endif
+ ktime_t poll_delay;
+ u8 oversampling_rate;
+ struct bmp180_eeprom_data bmp180_eeprom_vals;
+ bool enabled;
+ bool on_before_suspend;
+};
+
+static int bmp180_i2c_read(struct i2c_client *client, u8 cmd,
+ u8 *buf, int len)
+{
+ int err;
+ int tries = 0;
+
+ do {
+ err = i2c_smbus_read_i2c_block_data(client, cmd, len, buf);
+ if (err == len)
+ return 0;
+ } while (++tries < I2C_TRIES);
+
+ return err;
+}
+
+static int bmp180_i2c_write(struct i2c_client *client, u8 cmd, u8 data)
+{
+ int err;
+ int tries = 0;
+
+ do {
+ err = i2c_smbus_write_byte_data(client, cmd, data);
+ if (!err)
+ return 0;
+ } while (++tries < I2C_TRIES);
+
+ return err;
+}
+
+static void bmp180_enable(struct bmp180_data *barom)
+{
+ pr_debug("%s: bmp180_enable\n", __func__);
+ if (!barom->enabled) {
+ barom->enabled = true;
+ pr_debug("%s: start timer\n", __func__);
+ hrtimer_start(&barom->timer, barom->poll_delay,
+ HRTIMER_MODE_REL);
+ }
+}
+
+static void bmp180_disable(struct bmp180_data *barom)
+{
+ pr_debug("%s: bmp180_disable\n", __func__);
+ if (barom->enabled) {
+ barom->enabled = false;
+ pr_debug("%s: stop timer\n", __func__);
+ hrtimer_cancel(&barom->timer);
+ cancel_work_sync(&barom->work_pressure);
+ }
+}
+
+static int bmp180_get_raw_temperature(struct bmp180_data *barom,
+ u16 *raw_temperature)
+{
+ int err;
+ u16 buf;
+
+ pr_debug("%s: read uncompensated temperature value\n", __func__);
+ err = bmp180_i2c_write(barom->client, BMP180_TAKE_MEAS_REG,
+ BMP180_MEAS_TEMP);
+ if (err) {
+ pr_err("%s: can't write BMP180_TAKE_MEAS_REG\n", __func__);
+ return err;
+ }
+
+ usleep_range(5000, 7000);
+
+ err = bmp180_i2c_read(barom->client, BMP180_READ_MEAS_REG_U,
+ (u8 *)&buf, 2);
+ if (err) {
+ pr_err("%s: Fail to read uncompensated temperature\n",
+ __func__);
+ return err;
+ }
+ *raw_temperature = be16_to_cpu(buf);
+ pr_debug("%s: uncompensated temperature: %d\n",
+ __func__, *raw_temperature);
+ return err;
+}
+
+static int bmp180_get_raw_pressure(struct bmp180_data *barom,
+ u32 *raw_pressure)
+{
+ int err;
+ u32 buf = 0;
+
+ pr_debug("%s: read uncompensated pressure value\n", __func__);
+
+ err = bmp180_i2c_write(barom->client, BMP180_TAKE_MEAS_REG,
+ BMP180_MEAS_PRESS_OVERSAMP_0 |
+ (barom->oversampling_rate << 6));
+ if (err) {
+ pr_err("%s: can't write BMP180_TAKE_MEAS_REG\n", __func__);
+ return err;
+ }
+
+ msleep(2+(3 << barom->oversampling_rate));
+
+ err = bmp180_i2c_read(barom->client, BMP180_READ_MEAS_REG_U,
+ ((u8 *)&buf)+1, 3);
+ if (err) {
+ pr_err("%s: Fail to read uncompensated pressure\n", __func__);
+ return err;
+ }
+
+ *raw_pressure = be32_to_cpu(buf);
+ *raw_pressure >>= (8 - barom->oversampling_rate);
+ pr_debug("%s: uncompensated pressure: %d\n",
+ __func__, *raw_pressure);
+
+ return err;
+}
+
+static void bmp180_get_pressure_data(struct work_struct *work)
+{
+ u16 raw_temperature;
+ u32 raw_pressure;
+ long x1, x2, x3, b3, b5, b6;
+ unsigned long b4, b7;
+ long p;
+ int pressure;
+
+ struct bmp180_data *barom =
+ container_of(work, struct bmp180_data, work_pressure);
+
+ if (bmp180_get_raw_temperature(barom, &raw_temperature)) {
+ pr_err("%s: can't read uncompensated temperature\n", __func__);
+ return;
+ }
+
+#ifdef FACTORY_TEST
+ x1 = ((raw_temperature - barom->bmp180_eeprom_vals.AC6)
+ * barom->bmp180_eeprom_vals.AC5) >> 15;
+ x2 = (barom->bmp180_eeprom_vals.MC << 11)
+ / (x1 + barom->bmp180_eeprom_vals.MD);
+ input_report_abs(barom->input_dev, ABS_MISC, (x1 + x2 + 8) >> 4);
+ input_sync(barom->input_dev);
+#endif
+
+ if (bmp180_get_raw_pressure(barom, &raw_pressure)) {
+ pr_err("%s: Fail to read uncompensated pressure\n", __func__);
+ return;
+ }
+
+ x1 = ((raw_temperature - barom->bmp180_eeprom_vals.AC6) *
+ barom->bmp180_eeprom_vals.AC5) >> 15;
+ x2 = (barom->bmp180_eeprom_vals.MC << 11) /
+ (x1 + barom->bmp180_eeprom_vals.MD);
+ b5 = x1 + x2;
+
+ b6 = (b5 - 4000);
+ x1 = (barom->bmp180_eeprom_vals.B2 * ((b6 * b6) >> 12)) >> 11;
+ x2 = (barom->bmp180_eeprom_vals.AC2 * b6) >> 11;
+ x3 = x1 + x2;
+ b3 = (((((long)barom->bmp180_eeprom_vals.AC1) * 4 +
+ x3) << barom->oversampling_rate) + 2) >> 2;
+ x1 = (barom->bmp180_eeprom_vals.AC3 * b6) >> 13;
+ x2 = (barom->bmp180_eeprom_vals.B1 * (b6 * b6 >> 12)) >> 16;
+ x3 = ((x1 + x2) + 2) >> 2;
+ b4 = (barom->bmp180_eeprom_vals.AC4 *
+ (unsigned long)(x3 + 32768)) >> 15;
+ b7 = ((unsigned long)raw_pressure - b3) *
+ (50000 >> barom->oversampling_rate);
+ if (b7 < 0x80000000)
+ p = (b7 * 2) / b4;
+ else
+ p = (b7 / b4) * 2;
+
+ x1 = (p >> 8) * (p >> 8);
+ x1 = (x1 * 3038) >> 16;
+ x2 = (-7357 * p) >> 16;
+ pressure = p + ((x1 + x2 + 3791) >> 4);
+ pr_debug("%s: calibrated pressure: %d\n",
+ __func__, pressure);
+
+ input_report_abs(barom->input_dev, ABS_PRESSURE, pressure);
+ input_sync(barom->input_dev);
+
+ return;
+}
+
+static int bmp180_input_init(struct bmp180_data *barom)
+{
+ int err;
+
+ pr_debug("%s: enter\n", __func__);
+ barom->input_dev = input_allocate_device();
+ if (!barom->input_dev) {
+ pr_err("%s: could not allocate input device\n", __func__);
+ return -ENOMEM;
+ }
+ input_set_drvdata(barom->input_dev, barom);
+ barom->input_dev->name = "barometer_sensor";
+ input_set_capability(barom->input_dev, EV_ABS, ABS_PRESSURE);
+
+ /* Need to define the correct min and max */
+ input_set_abs_params(barom->input_dev, ABS_PRESSURE,
+ PRESSURE_MIN, PRESSURE_MAX,
+ PRESSURE_FUZZ, PRESSURE_FLAT);
+
+#ifdef FACTORY_TEST
+ input_set_capability(barom->input_dev, EV_ABS, ABS_VOLUME);
+ input_set_capability(barom->input_dev, EV_ABS, ABS_MISC);
+ input_set_abs_params(barom->input_dev, ABS_VOLUME,
+ SEA_LEVEL_MIN, SEA_LEVEL_MAX, 0, 0);
+ input_set_abs_params(barom->input_dev, ABS_MISC,
+ TEMP_MIN, TEMP_MAX, 0, 0);
+#endif
+
+ pr_debug("%s: registering barometer input device\n", __func__);
+
+ err = input_register_device(barom->input_dev);
+ if (err) {
+ pr_err("%s: unable to register input polled device %s\n",
+ __func__, barom->input_dev->name);
+ goto err_register_device;
+ }
+
+ goto done;
+
+err_register_device:
+ input_free_device(barom->input_dev);
+done:
+ return err;
+}
+
+static void bmp180_input_cleanup(struct bmp180_data *barom)
+{
+ input_unregister_device(barom->input_dev);
+ input_free_device(barom->input_dev);
+}
+
+static int bmp180_read_store_eeprom_val(struct bmp180_data *barom)
+{
+ int err;
+ u16 buf[11];
+
+ err = bmp180_i2c_read(barom->client, BMP180_EEPROM_AC1_U,
+ (u8 *)buf, 22);
+ if (err) {
+ pr_err("%s: Cannot read EEPROM values\n", __func__);
+ return err;
+ }
+ barom->bmp180_eeprom_vals.AC1 = be16_to_cpu(buf[0]);
+ barom->bmp180_eeprom_vals.AC2 = be16_to_cpu(buf[1]);
+ barom->bmp180_eeprom_vals.AC3 = be16_to_cpu(buf[2]);
+ barom->bmp180_eeprom_vals.AC4 = be16_to_cpu(buf[3]);
+ barom->bmp180_eeprom_vals.AC5 = be16_to_cpu(buf[4]);
+ barom->bmp180_eeprom_vals.AC6 = be16_to_cpu(buf[5]);
+ barom->bmp180_eeprom_vals.B1 = be16_to_cpu(buf[6]);
+ barom->bmp180_eeprom_vals.B2 = be16_to_cpu(buf[7]);
+ barom->bmp180_eeprom_vals.MB = be16_to_cpu(buf[8]);
+ barom->bmp180_eeprom_vals.MC = be16_to_cpu(buf[9]);
+ barom->bmp180_eeprom_vals.MD = be16_to_cpu(buf[10]);
+ return 0;
+}
+
+static enum hrtimer_restart bmp180_timer_func(struct hrtimer *timer)
+{
+ struct bmp180_data *barom = container_of(timer,
+ struct bmp180_data, timer);
+
+ pr_debug("%s: start\n", __func__);
+ queue_work(barom->wq, &barom->work_pressure);
+ hrtimer_forward_now(&barom->timer, barom->poll_delay);
+ return HRTIMER_RESTART;
+}
+
+static ssize_t bmp180_poll_delay_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct bmp180_data *barom = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%lld\n",
+ ktime_to_ns(barom->poll_delay));
+}
+
+static ssize_t bmp180_poll_delay_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int err;
+ int64_t new_delay;
+ struct bmp180_data *barom = dev_get_drvdata(dev);
+
+ err = strict_strtoll(buf, 10, &new_delay);
+ if (err < 0)
+ return err;
+
+ pr_debug("%s: new delay = %lldns, old delay = %lldns\n",
+ __func__, new_delay, ktime_to_ns(barom->poll_delay));
+
+ if (new_delay < DELAY_LOWBOUND || new_delay > DELAY_UPBOUND)
+ return -EINVAL;
+
+ mutex_lock(&barom->lock);
+ if (new_delay != ktime_to_ns(barom->poll_delay))
+ barom->poll_delay = ns_to_ktime(new_delay);
+
+ mutex_unlock(&barom->lock);
+
+ return size;
+}
+
+static ssize_t bmp180_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct bmp180_data *barom = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", barom->enabled);
+}
+
+static ssize_t bmp180_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ bool new_value;
+ struct bmp180_data *barom = dev_get_drvdata(dev);
+
+ pr_debug("%s: enable %s\n", __func__, 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;
+ }
+
+ pr_debug("%s: new_value = %d, old state = %d\n",
+ __func__, new_value, barom->enabled);
+
+ mutex_lock(&barom->lock);
+ if (new_value)
+ bmp180_enable(barom);
+ else
+ bmp180_disable(barom);
+
+ mutex_unlock(&barom->lock);
+
+ return size;
+}
+
+static ssize_t bmp180_oversampling_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct bmp180_data *barom = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", barom->oversampling_rate);
+}
+
+static ssize_t bmp180_oversampling_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct bmp180_data *barom = dev_get_drvdata(dev);
+
+ unsigned long oversampling;
+ int success = strict_strtoul(buf, 10, &oversampling);
+ if (success == 0) {
+ if (oversampling > 3)
+ oversampling = 3;
+ barom->oversampling_rate = oversampling;
+ return count;
+ }
+ return success;
+}
+
+static DEVICE_ATTR(poll_delay, S_IRUGO | S_IWUSR | S_IWGRP,
+ bmp180_poll_delay_show, bmp180_poll_delay_store);
+
+static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
+ bmp180_enable_show, bmp180_enable_store);
+
+static DEVICE_ATTR(oversampling, S_IWUSR | S_IRUGO,
+ bmp180_oversampling_show, bmp180_oversampling_store);
+
+static struct attribute *bmp180_sysfs_attrs[] = {
+ &dev_attr_enable.attr,
+ &dev_attr_poll_delay.attr,
+ &dev_attr_oversampling.attr,
+ NULL
+};
+
+static struct attribute_group bmp180_attribute_group = {
+ .attrs = bmp180_sysfs_attrs,
+};
+
+#ifdef FACTORY_TEST
+static ssize_t eeprom_check_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct bmp180_data *barom = dev_get_drvdata(dev);
+ int val = -1;
+
+ if (barom->bmp180_eeprom_vals.AC1 == 0 ||
+ barom->bmp180_eeprom_vals.AC1 == 0xFFFF)
+ goto done;
+ if (barom->bmp180_eeprom_vals.AC2 == 0 ||
+ barom->bmp180_eeprom_vals.AC2 == 0xFFFF)
+ goto done;
+ if (barom->bmp180_eeprom_vals.AC3 == 0 ||
+ barom->bmp180_eeprom_vals.AC3 == 0xFFFF)
+ goto done;
+ if (barom->bmp180_eeprom_vals.AC4 == 0 ||
+ barom->bmp180_eeprom_vals.AC4 == 0xFFFF)
+ goto done;
+ if (barom->bmp180_eeprom_vals.AC5 == 0 ||
+ barom->bmp180_eeprom_vals.AC5 == 0xFFFF)
+ goto done;
+ if (barom->bmp180_eeprom_vals.AC6 == 0 ||
+ barom->bmp180_eeprom_vals.AC6 == 0xFFFF)
+ goto done;
+ if (barom->bmp180_eeprom_vals.B1 == 0 ||
+ barom->bmp180_eeprom_vals.B1 == 0xFFFF)
+ goto done;
+ if (barom->bmp180_eeprom_vals.B2 == 0 ||
+ barom->bmp180_eeprom_vals.B2 == 0xFFFF)
+ goto done;
+ if (barom->bmp180_eeprom_vals.MB == 0 ||
+ barom->bmp180_eeprom_vals.MB == 0xFFFF)
+ goto done;
+ if (barom->bmp180_eeprom_vals.MC == 0 ||
+ barom->bmp180_eeprom_vals.MC == 0xFFFF)
+ goto done;
+ if (barom->bmp180_eeprom_vals.MC == 0 ||
+ barom->bmp180_eeprom_vals.MD == 0xFFFF)
+ goto done;
+
+ val = 1;
+
+done:
+ return sprintf(buf, "%d", val);
+}
+
+static ssize_t sea_level_pressure_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct bmp180_data *barom = dev_get_drvdata(dev);
+ int new_sea_level_pressure;
+
+ sscanf(buf, "%d", &new_sea_level_pressure);
+
+ input_report_abs(barom->input_dev, ABS_VOLUME, new_sea_level_pressure);
+ input_sync(barom->input_dev);
+
+ return size;
+}
+
+static DEVICE_ATTR(eeprom_check, S_IRUSR | S_IRGRP | S_IWGRP,
+ eeprom_check_show, NULL);
+
+static DEVICE_ATTR(sea_level_pressure, S_IRUGO | S_IWUSR | S_IWGRP,
+ NULL, sea_level_pressure_store);
+#endif
+
+static int __devinit bmp180_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int err;
+ struct bmp180_data *barom;
+
+ pr_debug("%s: enter\n", __func__);
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ pr_err("%s: client not i2c capable\n", __func__);
+ return -EIO;
+ }
+
+ barom = kzalloc(sizeof(*barom), GFP_KERNEL);
+ if (!barom) {
+ pr_err("%s: failed to allocate memory for module\n", __func__);
+ return -ENOMEM;
+ }
+
+ mutex_init(&barom->lock);
+ barom->client = client;
+
+ i2c_set_clientdata(client, barom);
+
+ err = bmp180_read_store_eeprom_val(barom);
+ if (err) {
+ pr_err("%s: Reading the EEPROM failed\n", __func__);
+ err = -ENODEV;
+ goto err_read_eeprom;
+ }
+
+ hrtimer_init(&barom->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ barom->poll_delay = ns_to_ktime(DELAY_DEFAULT);
+ barom->timer.function = bmp180_timer_func;
+
+ barom->wq = create_singlethread_workqueue("bmp180_wq");
+ if (!barom->wq) {
+ err = -ENOMEM;
+ pr_err("%s: could not create workqueue\n", __func__);
+ goto err_create_workqueue;
+ }
+
+ INIT_WORK(&barom->work_pressure, bmp180_get_pressure_data);
+
+ err = bmp180_input_init(barom);
+ if (err) {
+ pr_err("%s: could not create input device\n", __func__);
+ goto err_input_init;
+ }
+ err = sysfs_create_group(&barom->input_dev->dev.kobj,
+ &bmp180_attribute_group);
+ if (err) {
+ pr_err("%s: could not create sysfs group\n", __func__);
+ goto err_sysfs_create_group;
+ }
+
+#ifdef FACTORY_TEST
+ /* sysfs for factory test */
+ barom->dev = sensors_classdev_register("barometer_sensor");
+ if (IS_ERR(barom->dev)) {
+ err = PTR_ERR(barom->dev);
+ pr_err("%s: device_create failed[%d]\n", __func__, err);
+ goto err_device_create;
+ }
+
+ err = device_create_file(barom->dev, &dev_attr_sea_level_pressure);
+ if (err < 0) {
+ pr_err("%s: device_create_fil(sea_level_pressure) failed\n",
+ __func__);
+ goto err_device_create_file1;
+ }
+
+ err = device_create_file(barom->dev, &dev_attr_eeprom_check);
+ if (err < 0) {
+ pr_err("%s: device_create_file(eeprom_check) failed\n",
+ __func__);
+ goto err_device_create_file2;
+ }
+
+ dev_set_drvdata(barom->dev, barom);
+#endif
+ goto done;
+
+#ifdef FACTORY_TEST
+err_device_create_file2:
+ device_remove_file(barom->dev, &dev_attr_sea_level_pressure);
+err_device_create_file1:
+ sensors_classdev_unregister(barom->dev);
+err_device_create:
+ sysfs_remove_group(&barom->input_dev->dev.kobj,
+ &bmp180_attribute_group);
+#endif
+
+err_sysfs_create_group:
+ bmp180_input_cleanup(barom);
+err_input_init:
+ destroy_workqueue(barom->wq);
+err_create_workqueue:
+err_read_eeprom:
+ mutex_destroy(&barom->lock);
+ kfree(barom);
+done:
+ return err;
+}
+
+static int __devexit bmp180_remove(struct i2c_client *client)
+{
+ /* TO DO: revisit ordering here once _probe order is finalized */
+ struct bmp180_data *barom = i2c_get_clientdata(client);
+
+ pr_debug("%s: bmp180_remove +\n", __func__);
+ device_remove_file(barom->dev, &dev_attr_sea_level_pressure);
+ sensors_classdev_unregister(barom->dev);
+ sysfs_remove_group(&barom->input_dev->dev.kobj,
+ &bmp180_attribute_group);
+
+ bmp180_disable(barom);
+
+ bmp180_input_cleanup(barom);
+
+ destroy_workqueue(barom->wq);
+
+ mutex_destroy(&barom->lock);
+ kfree(barom);
+
+ pr_debug("%s: bmp180_remove -\n", __func__);
+ return 0;
+}
+
+static int bmp180_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bmp180_data *barom = i2c_get_clientdata(client);
+ pr_debug("%s: on_before_suspend %d\n",
+ __func__, barom->on_before_suspend);
+
+ if (barom->on_before_suspend)
+ bmp180_enable(barom);
+ return 0;
+}
+
+static int bmp180_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bmp180_data *barom = i2c_get_clientdata(client);
+
+ barom->on_before_suspend = barom->enabled;
+ pr_debug("%s: on_before_suspend %d\n",
+ __func__, barom->on_before_suspend);
+ bmp180_disable(barom);
+ return 0;
+}
+
+static const struct i2c_device_id bmp180_id[] = {
+ {BMP180_DRV_NAME, 0},
+ {},
+};
+
+MODULE_DEVICE_TABLE(i2c, bmp180_id);
+static const struct dev_pm_ops bmp180_pm_ops = {
+ .suspend = bmp180_suspend,
+ .resume = bmp180_resume,
+};
+
+static struct i2c_driver bmp180_driver = {
+ .driver = {
+ .name = BMP180_DRV_NAME,
+ .owner = THIS_MODULE,
+ .pm = &bmp180_pm_ops,
+ },
+ .probe = bmp180_probe,
+ .remove = __devexit_p(bmp180_remove),
+ .id_table = bmp180_id,
+};
+
+static int __init bmp180_init(void)
+{
+ pr_debug("%s: _init\n", __func__);
+ return i2c_add_driver(&bmp180_driver);
+}
+
+static void __exit bmp180_exit(void)
+{
+ pr_debug("%s: _exit +\n", __func__);
+ i2c_del_driver(&bmp180_driver);
+ pr_debug("%s: _exit -\n", __func__);
+ return;
+}
+
+MODULE_AUTHOR("Hyoung Wook Ham <hwham@sta.samsung.com>");
+MODULE_DESCRIPTION("BMP180 Pressure sensor driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(DRIVER_VERSION);
+
+module_init(bmp180_init);
+module_exit(bmp180_exit);
diff --git a/drivers/sensor/cm3663.c b/drivers/sensor/cm3663.c
new file mode 100644
index 0000000..c2228fb
--- /dev/null
+++ b/drivers/sensor/cm3663.c
@@ -0,0 +1,993 @@
+/* linux/driver/input/misc/cm3663.c
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/i2c.h>
+#include <linux/errno.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/miscdevice.h>
+#include <linux/gpio.h>
+#include <linux/wakelock.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/workqueue.h>
+#include <linux/uaccess.h>
+#include <linux/sensor/cm3663.h>
+#if defined(CONFIG_MACH_S2PLUS)
+#include <linux/sensor/sensors_core.h>
+#endif
+
+#define PROX_READ_NUM 40
+
+/* ADDSEL is LOW */
+#define REGS_ARA 0x18
+#define REGS_ALS_CMD 0x20
+#define REGS_ALS_MSB 0x21
+#define REGS_INIT 0x22
+#define REGS_ALS_LSB 0x23
+#define REGS_PS_CMD 0xB0
+#define REGS_PS_DATA 0xB1
+#define REGS_PS_THD 0xB2
+
+static u8 reg_defaults[8] = {
+ 0x00, /* ARA: read only register */
+ 0x00, /* ALS_CMD: als cmd */
+ 0x00, /* ALS_MSB: read only register */
+ 0x20, /* INIT: interrupt disable */
+ 0x00, /* ALS_LSB: read only register */
+ 0x30, /* PS_CMD: interrupt disable */
+ 0x00, /* PS_DATA: read only register */
+ 0x0A, /* PS_THD: 10 */
+};
+
+
+enum {
+ LIGHT_ENABLED = BIT(0),
+ PROXIMITY_ENABLED = BIT(1),
+};
+
+/* driver data */
+struct cm3663_data {
+ struct input_dev *proximity_input_dev;
+ struct input_dev *light_input_dev;
+ struct i2c_client *i2c_client;
+ struct work_struct work_light;
+ struct work_struct work_prox;
+ struct hrtimer light_timer;
+ struct hrtimer prox_timer;
+ struct mutex power_lock;
+ struct wake_lock prx_wake_lock;
+ struct workqueue_struct *light_wq;
+ struct workqueue_struct *prox_wq;
+ struct class *lightsensor_class;
+ struct class *proximity_class;
+ struct device *switch_cmd_dev;
+ struct device *proximity_dev;
+ struct cm3663_platform_data *pdata;
+ bool als_buf_initialized;
+ bool on;
+ int als_index_count;
+ int irq;
+ int avg[3];
+ ktime_t light_poll_delay;
+ ktime_t prox_poll_delay;
+ u8 power_state;
+};
+
+int cm3663_i2c_read(struct cm3663_data *cm3663, u8 addr, u8 *val)
+{
+ int err = 0;
+ int retry = 10;
+ struct i2c_msg msg[1];
+ struct i2c_client *client = cm3663->i2c_client;
+
+ if ((client == NULL) || (!client->adapter))
+ return -ENODEV;
+
+ msg->addr = addr >> 1;
+ msg->flags = 1;
+ msg->len = 1;
+ msg->buf = val;
+
+ if (addr == REGS_ARA)
+ retry = 2;
+
+ while (retry--) {
+ err = i2c_transfer(client->adapter, msg, 1);
+ if (err >= 0)
+ return err;
+ }
+
+ if (addr == REGS_ARA)
+ return 0;
+
+ pr_err("%s: i2c read failed at addr 0x%x: %d\n", __func__, addr, err);
+ return err;
+}
+
+int cm3663_i2c_write(struct cm3663_data *cm3663, u8 addr, u8 val)
+{
+ u8 data = val;
+ int err = 0;
+ int retry = 10;
+ struct i2c_msg msg[1];
+ struct i2c_client *client = cm3663->i2c_client;
+
+ if ((client == NULL) || (!client->adapter))
+ return -ENODEV;
+
+ msg->addr = addr >> 1;
+ msg->flags = 0;
+ msg->len = 1;
+ msg->buf = &data;
+
+ while (retry--) {
+ err = i2c_transfer(client->adapter, msg, 1);
+ if (err >= 0)
+ return err;
+ }
+
+ pr_err("%s: i2c write failed at addr 0x%x: %d\n", __func__, addr, err);
+ return err;
+}
+
+static void cm3663_light_enable(struct cm3663_data *cm3663)
+{
+ u8 tmp;
+#if defined(CONFIG_MACH_S2PLUS)
+ printk(KERN_INFO "[%s]\n", __func__);
+#endif
+ cm3663_i2c_read(cm3663, REGS_ARA, &tmp);
+ cm3663_i2c_read(cm3663, REGS_ARA, &tmp);
+ cm3663_i2c_read(cm3663, REGS_ARA, &tmp);
+ cm3663_i2c_write(cm3663, REGS_INIT, reg_defaults[3]);
+ cm3663_i2c_write(cm3663, REGS_ALS_CMD, reg_defaults[1]);
+ hrtimer_start(&cm3663->light_timer, cm3663->light_poll_delay,
+ HRTIMER_MODE_REL);
+}
+
+static void cm3663_light_disable(struct cm3663_data *cm3663)
+{
+#if defined(CONFIG_MACH_S2PLUS)
+ printk(KERN_INFO "[%s]\n", __func__);
+#endif
+ cm3663_i2c_write(cm3663, REGS_ALS_CMD, 0x01);
+ hrtimer_cancel(&cm3663->light_timer);
+ cancel_work_sync(&cm3663->work_light);
+}
+
+static int lightsensor_get_alsvalue(struct cm3663_data *cm3663)
+{
+ int value = 0;
+ u8 als_high, als_low;
+
+ /* get ALS */
+ cm3663_i2c_read(cm3663, REGS_ALS_LSB, &als_low);
+ cm3663_i2c_read(cm3663, REGS_ALS_MSB, &als_high);
+
+ value = ((als_high << 8) | als_low) * 5;
+ return value;
+}
+
+static void proxsensor_get_avgvalue(struct cm3663_data *cm3663)
+{
+ int min = 0, max = 0, avg = 0;
+ int i;
+ u8 proximity_value = 0;
+
+ for (i = 0; i < PROX_READ_NUM; i++) {
+ msleep(40);
+ cm3663_i2c_read(cm3663, REGS_PS_DATA, &proximity_value);
+ avg += proximity_value;
+
+ if (!i)
+ min = proximity_value;
+ else if (proximity_value < min)
+ min = proximity_value;
+
+ if (proximity_value > max)
+ max = proximity_value;
+ }
+ avg /= PROX_READ_NUM;
+
+ cm3663->avg[0] = min;
+ cm3663->avg[1] = avg;
+ cm3663->avg[2] = max;
+}
+
+static ssize_t proximity_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cm3663_data *cm3663 = dev_get_drvdata(dev);
+ u8 proximity_value = 0;
+
+ if (!(cm3663->power_state & PROXIMITY_ENABLED)) {
+ mutex_lock(&cm3663->power_lock);
+ cm3663->pdata->proximity_power(1);
+ cm3663_i2c_write(cm3663, REGS_PS_CMD, reg_defaults[5]);
+ mutex_unlock(&cm3663->power_lock);
+ }
+
+ msleep(20);
+ cm3663_i2c_read(cm3663, REGS_PS_DATA, &proximity_value);
+
+ if (!(cm3663->power_state & PROXIMITY_ENABLED)) {
+ mutex_lock(&cm3663->power_lock);
+ cm3663_i2c_write(cm3663, REGS_PS_CMD, 0x01);
+ cm3663->pdata->proximity_power(0);
+ mutex_unlock(&cm3663->power_lock);
+ }
+
+ return sprintf(buf, "%d", proximity_value);
+}
+
+static ssize_t lightsensor_file_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cm3663_data *cm3663 = dev_get_drvdata(dev);
+ int adc = 0;
+
+ if (!(cm3663->power_state & LIGHT_ENABLED))
+ cm3663_light_enable(cm3663);
+
+ adc = lightsensor_get_alsvalue(cm3663);
+
+ if (!(cm3663->power_state & LIGHT_ENABLED))
+ cm3663_light_disable(cm3663);
+
+ return sprintf(buf, "%d\n", adc);
+}
+
+static ssize_t poll_delay_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cm3663_data *cm3663 = dev_get_drvdata(dev);
+ return sprintf(buf, "%lld\n", ktime_to_ns(cm3663->light_poll_delay));
+}
+
+static ssize_t poll_delay_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct cm3663_data *cm3663 = dev_get_drvdata(dev);
+ int64_t new_delay;
+ int err;
+
+ err = strict_strtoll(buf, 10, &new_delay);
+ if (err < 0)
+ return err;
+
+ mutex_lock(&cm3663->power_lock);
+ if (new_delay != ktime_to_ns(cm3663->light_poll_delay)) {
+ cm3663->light_poll_delay = ns_to_ktime(new_delay);
+ if (cm3663->power_state & LIGHT_ENABLED) {
+ cm3663_light_disable(cm3663);
+ cm3663_light_enable(cm3663);
+ }
+ }
+ mutex_unlock(&cm3663->power_lock);
+
+ return size;
+}
+
+static ssize_t light_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cm3663_data *cm3663 = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n",
+ (cm3663->power_state & LIGHT_ENABLED) ? 1 : 0);
+}
+
+static ssize_t proximity_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cm3663_data *cm3663 = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n",
+ (cm3663->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 cm3663_data *cm3663 = 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(&cm3663->power_lock);
+ if (new_value && !(cm3663->power_state & LIGHT_ENABLED)) {
+ cm3663->power_state |= LIGHT_ENABLED;
+ cm3663_light_enable(cm3663);
+ } else if (!new_value && (cm3663->power_state & LIGHT_ENABLED)) {
+ cm3663_light_disable(cm3663);
+ cm3663->power_state &= ~LIGHT_ENABLED;
+ }
+ mutex_unlock(&cm3663->power_lock);
+ return size;
+}
+
+static ssize_t proximity_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct cm3663_data *cm3663 = dev_get_drvdata(dev);
+ bool new_value;
+ u8 tmp;
+#if defined(CONFIG_MACH_S2PLUS)
+ u8 val = 1;
+#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(&cm3663->power_lock);
+ if (new_value && !(cm3663->power_state & PROXIMITY_ENABLED)) {
+ cm3663->pdata->proximity_power(1);
+ cm3663->power_state |= PROXIMITY_ENABLED;
+ cm3663_i2c_read(cm3663, REGS_ARA, &tmp);
+ cm3663_i2c_read(cm3663, REGS_ARA, &tmp);
+ cm3663_i2c_read(cm3663, REGS_ARA, &tmp);
+ cm3663_i2c_write(cm3663, REGS_INIT, reg_defaults[3]);
+ cm3663_i2c_write(cm3663, REGS_PS_THD, reg_defaults[7]);
+ cm3663_i2c_write(cm3663, REGS_PS_CMD, reg_defaults[5]);
+
+#if defined(CONFIG_MACH_S2PLUS)
+ /* report the first value */
+ val = gpio_get_value(cm3663->i2c_client->irq);
+ if (val < 0) {
+ pr_err("%s: gpio_get_value error %d\n", __func__, val);
+ return IRQ_HANDLED;
+ }
+
+ cm3663_i2c_read(cm3663, REGS_PS_DATA, &tmp);
+ printk(KERN_INFO "%s: proximity value = %d, val = %d\n",
+ __func__, tmp, val);
+
+ /* 0 is close, 1 is far */
+ input_report_abs(cm3663->proximity_input_dev,
+ ABS_DISTANCE, val);
+ input_sync(cm3663->proximity_input_dev);
+ wake_lock_timeout(&cm3663->prx_wake_lock, 3*HZ);
+#endif
+
+ enable_irq(cm3663->irq);
+ enable_irq_wake(cm3663->irq);
+ } else if (!new_value && (cm3663->power_state & PROXIMITY_ENABLED)) {
+ cm3663->power_state &= ~PROXIMITY_ENABLED;
+ disable_irq_wake(cm3663->irq);
+ disable_irq(cm3663->irq);
+ cm3663_i2c_write(cm3663, REGS_PS_CMD, 0x01);
+ cm3663->pdata->proximity_power(0);
+ }
+ mutex_unlock(&cm3663->power_lock);
+ return size;
+}
+
+static ssize_t proximity_avg_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cm3663_data *cm3663 = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d,%d,%d\n",
+ cm3663->avg[0], cm3663->avg[1], cm3663->avg[2]);
+}
+
+static ssize_t proximity_avg_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct cm3663_data *cm3663 = 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(&cm3663->power_lock);
+ if (new_value) {
+ if (!(cm3663->power_state & PROXIMITY_ENABLED)) {
+ cm3663->pdata->proximity_power(1);
+ cm3663_i2c_write(cm3663, REGS_PS_CMD, reg_defaults[5]);
+ }
+ hrtimer_start(&cm3663->prox_timer, cm3663->prox_poll_delay,
+ HRTIMER_MODE_REL);
+ } else if (!new_value) {
+ hrtimer_cancel(&cm3663->prox_timer);
+ cancel_work_sync(&cm3663->work_prox);
+ if (!(cm3663->power_state & PROXIMITY_ENABLED)) {
+ cm3663_i2c_write(cm3663, REGS_PS_CMD, 0x01);
+ cm3663->pdata->proximity_power(0);
+ }
+ }
+ mutex_unlock(&cm3663->power_lock);
+
+ return size;
+}
+
+#if defined(CONFIG_MACH_S2PLUS)
+static DEVICE_ATTR(prox_avg, 0644,
+ proximity_avg_show, proximity_avg_store);
+static DEVICE_ATTR(state, 0644, proximity_state_show, NULL);
+
+static DEVICE_ATTR(lux, 0644, lightsensor_file_state_show,
+ NULL);
+#else
+static DEVICE_ATTR(proximity_avg, 0644,
+ proximity_avg_show, proximity_avg_store);
+static DEVICE_ATTR(proximity_state, 0644, proximity_state_show, NULL);
+
+static DEVICE_ATTR(lightsensor_file_state, 0644, lightsensor_file_state_show,
+ NULL);
+#endif
+
+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 void cm3663_work_func_light(struct work_struct *work)
+{
+ int i;
+ int als;
+ struct cm3663_data *cm3663 = container_of(work, struct cm3663_data,
+ work_light);
+
+ als = lightsensor_get_alsvalue(cm3663);
+#if defined(CONFIG_MACH_S2PLUS)
+ input_report_rel(cm3663->light_input_dev,
+ REL_MISC, als + 1);
+#else
+ input_report_abs(cm3663->light_input_dev,
+ ABS_MISC, als + 1);
+#endif
+ input_sync(cm3663->light_input_dev);
+}
+
+static void cm3663_work_func_prox(struct work_struct *work)
+{
+ struct cm3663_data *cm3663 = container_of(work, struct cm3663_data,
+ work_prox);
+ proxsensor_get_avgvalue(cm3663);
+}
+
+/* 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 cm3663_light_timer_func(struct hrtimer *timer)
+{
+ struct cm3663_data *cm3663
+ = container_of(timer, struct cm3663_data, light_timer);
+ queue_work(cm3663->light_wq, &cm3663->work_light);
+ hrtimer_forward_now(&cm3663->light_timer, cm3663->light_poll_delay);
+ return HRTIMER_RESTART;
+}
+
+static enum hrtimer_restart cm3663_prox_timer_func(struct hrtimer *timer)
+{
+ struct cm3663_data *cm3663
+ = container_of(timer, struct cm3663_data, prox_timer);
+ queue_work(cm3663->prox_wq, &cm3663->work_prox);
+ hrtimer_forward_now(&cm3663->prox_timer, cm3663->prox_poll_delay);
+ return HRTIMER_RESTART;
+}
+
+/* interrupt happened due to transition/change of near/far proximity state */
+irqreturn_t cm3663_irq_thread_fn(int irq, void *data)
+{
+ struct cm3663_data *ip = data;
+ u8 val = 1;
+ u8 tmp;
+
+ val = gpio_get_value(ip->i2c_client->irq);
+ if (val < 0) {
+ pr_err("%s: gpio_get_value error %d\n", __func__, val);
+ return IRQ_HANDLED;
+ }
+
+ /* for debugging : going to be removed */
+ cm3663_i2c_read(ip, REGS_PS_DATA, &tmp);
+ pr_err("%s: proximity value = %d, val = %d\n", __func__, tmp, 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);
+
+ return IRQ_HANDLED;
+}
+
+static int cm3663_setup_irq(struct cm3663_data *cm3663)
+{
+ int rc = -EIO;
+ int irq;
+
+ irq = gpio_to_irq(cm3663->i2c_client->irq);
+ rc = request_threaded_irq(irq, NULL, cm3663_irq_thread_fn,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "proximity_int", cm3663);
+ if (rc < 0) {
+ pr_err("%s: request_irq(%d) failed for gpio %d (%d)\n",
+ __func__, irq, irq, rc);
+ return rc;
+ }
+
+ /* start with interrupts disabled */
+ disable_irq(irq);
+ cm3663->irq = irq;
+
+ return rc;
+}
+
+static int cm3663_setup_reg(struct cm3663_data *cm3663)
+{
+ int err = 0;
+ u8 tmp;
+
+ /* initializing the proximity and light sensor registers */
+ mutex_lock(&cm3663->power_lock);
+ cm3663->pdata->proximity_power(1);
+ cm3663_i2c_read(cm3663, REGS_ARA, &tmp);
+ cm3663_i2c_read(cm3663, REGS_ARA, &tmp);
+ cm3663_i2c_read(cm3663, REGS_ARA, &tmp);
+ cm3663_i2c_write(cm3663, REGS_INIT, reg_defaults[3]);
+ cm3663_i2c_write(cm3663, REGS_PS_THD, reg_defaults[7]);
+ cm3663_i2c_write(cm3663, REGS_PS_CMD, reg_defaults[5]);
+ mutex_unlock(&cm3663->power_lock);
+
+ /* printing the inital proximity value with no contact */
+ msleep(50);
+ err = cm3663_i2c_read(cm3663, REGS_PS_DATA, &tmp);
+ if (err < 0) {
+ pr_err("%s: read ps_data failed\n", __func__);
+ err = -EIO;
+ }
+ pr_err("%s: initial proximity value = %d\n", __func__, tmp);
+ mutex_lock(&cm3663->power_lock);
+ cm3663_i2c_write(cm3663, REGS_PS_CMD, 0x01);
+ cm3663->pdata->proximity_power(0);
+ mutex_unlock(&cm3663->power_lock);
+
+ return err;
+}
+
+static int cm3663_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret = -ENODEV;
+ struct input_dev *input_dev;
+ struct cm3663_data *cm3663;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ pr_err("%s: i2c functionality check failed!\n", __func__);
+ return ret;
+ }
+
+ cm3663 = kzalloc(sizeof(struct cm3663_data), GFP_KERNEL);
+ if (!cm3663) {
+ pr_err("%s: failed to alloc memory for module data\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ cm3663->pdata = client->dev.platform_data;
+ cm3663->i2c_client = client;
+ i2c_set_clientdata(client, cm3663);
+
+ /* wake lock init */
+ wake_lock_init(&cm3663->prx_wake_lock, WAKE_LOCK_SUSPEND,
+ "prx_wake_lock");
+ mutex_init(&cm3663->power_lock);
+
+ /* setup initial registers */
+ ret = cm3663_setup_reg(cm3663);
+ if (ret < 0) {
+ pr_err("%s: could not setup regs\n", __func__);
+ goto err_setup_reg;
+ }
+
+ ret = cm3663_setup_irq(cm3663);
+ 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;
+ }
+ cm3663->proximity_input_dev = input_dev;
+ input_set_drvdata(input_dev, cm3663);
+ 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);
+
+ 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;
+ }
+
+ /* light_timer settings. we poll for light values using a timer. */
+ hrtimer_init(&cm3663->light_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ cm3663->light_poll_delay = ns_to_ktime(200 * NSEC_PER_MSEC);
+ cm3663->light_timer.function = cm3663_light_timer_func;
+
+ /* prox_timer settings. we poll for light values using a timer. */
+ hrtimer_init(&cm3663->prox_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ cm3663->prox_poll_delay = ns_to_ktime(2000 * NSEC_PER_MSEC);
+ cm3663->prox_timer.function = cm3663_prox_timer_func;
+
+ /* the timer just fires off a work queue request. we need a thread
+ to read the i2c (can be slow and blocking). */
+ cm3663->light_wq = create_singlethread_workqueue("cm3663_light_wq");
+ if (!cm3663->light_wq) {
+ ret = -ENOMEM;
+ pr_err("%s: could not create light workqueue\n", __func__);
+ goto err_create_light_workqueue;
+ }
+ cm3663->prox_wq = create_singlethread_workqueue("cm3663_prox_wq");
+ if (!cm3663->prox_wq) {
+ ret = -ENOMEM;
+ pr_err("%s: could not create prox workqueue\n", __func__);
+ goto err_create_prox_workqueue;
+ }
+
+ /* this is the thread function we run on the work queue */
+ INIT_WORK(&cm3663->work_light, cm3663_work_func_light);
+ INIT_WORK(&cm3663->work_prox, cm3663_work_func_prox);
+
+ /* 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, cm3663);
+ input_dev->name = "light_sensor";
+#if defined(CONFIG_MACH_S2PLUS)
+ input_set_capability(input_dev, EV_REL, REL_MISC);
+#else
+ input_set_capability(input_dev, EV_ABS, ABS_MISC);
+ input_set_abs_params(input_dev, ABS_MISC, 0, 1, 0, 0);
+#endif
+
+ 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;
+ }
+ cm3663->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;
+ }
+
+#if defined(CONFIG_MACH_S2PLUS)
+ /* set sysfs for proximity sensor */
+ cm3663->proximity_dev = sensors_classdev_register("proximity_sensor");
+ if (IS_ERR(cm3663->proximity_dev)) {
+ pr_err("%s: could not create proximity_dev\n", __func__);
+ goto err_proximity_classdev_create;
+ }
+
+ if (device_create_file(cm3663->proximity_dev,
+ &dev_attr_state) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_state.attr.name);
+ goto err_proximity_device_create_file1;
+ }
+
+ if (device_create_file(cm3663->proximity_dev,
+ &dev_attr_prox_avg) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_prox_avg.attr.name);
+ goto err_proximity_device_create_file2;
+ }
+ dev_set_drvdata(cm3663->proximity_dev, cm3663);
+
+ /* set sysfs for light sensor */
+ cm3663->switch_cmd_dev = sensors_classdev_register("light_sensor");
+ if (IS_ERR(cm3663->switch_cmd_dev)) {
+ pr_err("%s: could not create switch_cmd_dev\n", __func__);
+ goto err_light_classdev_create;
+ }
+ if (device_create_file(cm3663->switch_cmd_dev,
+ &dev_attr_lux) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_lux.attr.name);
+ goto err_light_device_create_file;
+ }
+ dev_set_drvdata(cm3663->switch_cmd_dev, cm3663);
+#else
+ /* set sysfs for proximity sensor */
+ cm3663->proximity_class = class_create(THIS_MODULE, "proximity");
+ if (IS_ERR(cm3663->proximity_class)) {
+ pr_err("%s: could not create proximity_class\n", __func__);
+ goto err_proximity_class_create;
+ }
+
+ cm3663->proximity_dev = device_create(cm3663->proximity_class,
+ NULL, 0, NULL, "proximity");
+ if (IS_ERR(cm3663->proximity_dev)) {
+ pr_err("%s: could not create proximity_dev\n", __func__);
+ goto err_proximity_device_create;
+ }
+
+ if (device_create_file(cm3663->proximity_dev,
+ &dev_attr_proximity_state) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_proximity_state.attr.name);
+ goto err_proximity_device_create_file1;
+ }
+
+ if (device_create_file(cm3663->proximity_dev,
+ &dev_attr_proximity_avg) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_proximity_avg.attr.name);
+ goto err_proximity_device_create_file2;
+ }
+ dev_set_drvdata(cm3663->proximity_dev, cm3663);
+
+ /* set sysfs for light sensor */
+ cm3663->lightsensor_class = class_create(THIS_MODULE, "lightsensor");
+ if (IS_ERR(cm3663->lightsensor_class)) {
+ pr_err("%s: could not create lightsensor_class\n", __func__);
+ goto err_light_class_create;
+ }
+
+ cm3663->switch_cmd_dev = device_create(cm3663->lightsensor_class,
+ NULL, 0, NULL, "switch_cmd");
+ if (IS_ERR(cm3663->switch_cmd_dev)) {
+ pr_err("%s: could not create switch_cmd_dev\n", __func__);
+ goto err_light_device_create;
+ }
+
+ if (device_create_file(cm3663->switch_cmd_dev,
+ &dev_attr_lightsensor_file_state) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_lightsensor_file_state.attr.name);
+ goto err_light_device_create_file;
+ }
+ dev_set_drvdata(cm3663->switch_cmd_dev, cm3663);
+#endif
+
+ goto done;
+
+/* error, unwind it all */
+#if defined(CONFIG_MACH_S2PLUS)
+err_light_device_create_file:
+ sensors_classdev_unregister(cm3663->switch_cmd_dev);
+err_light_classdev_create:
+ device_remove_file(cm3663->proximity_dev, &dev_attr_prox_avg);
+err_proximity_device_create_file2:
+ device_remove_file(cm3663->proximity_dev, &dev_attr_state);
+err_proximity_device_create_file1:
+ sensors_classdev_unregister(cm3663->proximity_dev);
+err_proximity_classdev_create:
+#else
+err_light_device_create_file:
+ device_destroy(cm3663->lightsensor_class, 0);
+err_light_device_create:
+ class_destroy(cm3663->lightsensor_class);
+err_light_class_create:
+ device_remove_file(cm3663->proximity_dev, &dev_attr_proximity_avg);
+err_proximity_device_create_file2:
+ device_remove_file(cm3663->proximity_dev, &dev_attr_proximity_state);
+err_proximity_device_create_file1:
+ device_destroy(cm3663->proximity_class, 0);
+err_proximity_device_create:
+ class_destroy(cm3663->proximity_class);
+err_proximity_class_create:
+#endif
+ sysfs_remove_group(&input_dev->dev.kobj,
+ &light_attribute_group);
+err_sysfs_create_group_light:
+ input_unregister_device(cm3663->light_input_dev);
+err_input_register_device_light:
+err_input_allocate_device_light:
+ destroy_workqueue(cm3663->prox_wq);
+err_create_prox_workqueue:
+ destroy_workqueue(cm3663->light_wq);
+err_create_light_workqueue:
+ sysfs_remove_group(&cm3663->proximity_input_dev->dev.kobj,
+ &proximity_attribute_group);
+err_sysfs_create_group_proximity:
+ input_unregister_device(cm3663->proximity_input_dev);
+err_input_register_device_proximity:
+err_input_allocate_device_proximity:
+ free_irq(cm3663->irq, 0);
+err_setup_irq:
+err_setup_reg:
+ mutex_destroy(&cm3663->power_lock);
+ wake_lock_destroy(&cm3663->prx_wake_lock);
+ kfree(cm3663);
+done:
+ return ret;
+}
+
+static int cm3663_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
+ cm3663->power_state because we use that state in resume.
+ */
+ struct i2c_client *client = to_i2c_client(dev);
+ struct cm3663_data *cm3663 = i2c_get_clientdata(client);
+ if (cm3663->power_state & LIGHT_ENABLED)
+ cm3663_light_disable(cm3663);
+
+ return 0;
+}
+
+static int cm3663_resume(struct device *dev)
+{
+ /* Turn power back on if we were before suspend. */
+ struct i2c_client *client = to_i2c_client(dev);
+ struct cm3663_data *cm3663 = i2c_get_clientdata(client);
+
+ if (cm3663->power_state & LIGHT_ENABLED)
+ cm3663_light_enable(cm3663);
+ return 0;
+}
+
+static int cm3663_i2c_remove(struct i2c_client *client)
+{
+ struct cm3663_data *cm3663 = i2c_get_clientdata(client);
+
+#if defined(CONFIG_MACH_S2PLUS)
+ device_remove_file(cm3663->switch_cmd_dev,
+ &dev_attr_lux);
+ sensors_classdev_unregister(cm3663->switch_cmd_dev);
+ device_remove_file(cm3663->proximity_dev, &dev_attr_prox_avg);
+ device_remove_file(cm3663->proximity_dev, &dev_attr_state);
+ sensors_classdev_unregister(cm3663->proximity_dev);
+#else
+ device_remove_file(cm3663->proximity_dev,
+ &dev_attr_proximity_avg);
+ device_remove_file(cm3663->switch_cmd_dev,
+ &dev_attr_lightsensor_file_state);
+ device_destroy(cm3663->lightsensor_class, 0);
+ class_destroy(cm3663->lightsensor_class);
+ device_remove_file(cm3663->proximity_dev, &dev_attr_proximity_avg);
+ device_remove_file(cm3663->proximity_dev, &dev_attr_proximity_state);
+ device_destroy(cm3663->proximity_class, 0);
+ class_destroy(cm3663->proximity_class);
+#endif
+ sysfs_remove_group(&cm3663->light_input_dev->dev.kobj,
+ &light_attribute_group);
+ input_unregister_device(cm3663->light_input_dev);
+ sysfs_remove_group(&cm3663->proximity_input_dev->dev.kobj,
+ &proximity_attribute_group);
+ input_unregister_device(cm3663->proximity_input_dev);
+ free_irq(cm3663->irq, NULL);
+ if (cm3663->power_state) {
+ if (cm3663->power_state & LIGHT_ENABLED)
+ cm3663_light_disable(cm3663);
+ if (cm3663->power_state & PROXIMITY_ENABLED) {
+ cm3663_i2c_write(cm3663, REGS_PS_CMD, 0x01);
+ cm3663->pdata->proximity_power(0);
+ }
+ }
+ destroy_workqueue(cm3663->light_wq);
+ destroy_workqueue(cm3663->prox_wq);
+ mutex_destroy(&cm3663->power_lock);
+ wake_lock_destroy(&cm3663->prx_wake_lock);
+ kfree(cm3663);
+ return 0;
+}
+
+static const struct i2c_device_id cm3663_device_id[] = {
+ {"cm3663", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cm3663_device_id);
+
+static const struct dev_pm_ops cm3663_pm_ops = {
+ .suspend = cm3663_suspend,
+ .resume = cm3663_resume
+};
+
+static struct i2c_driver cm3663_i2c_driver = {
+ .driver = {
+ .name = "cm3663",
+ .owner = THIS_MODULE,
+ .pm = &cm3663_pm_ops
+ },
+ .probe = cm3663_i2c_probe,
+ .remove = cm3663_i2c_remove,
+ .id_table = cm3663_device_id,
+};
+
+
+static int __init cm3663_init(void)
+{
+ return i2c_add_driver(&cm3663_i2c_driver);
+}
+
+static void __exit cm3663_exit(void)
+{
+ i2c_del_driver(&cm3663_i2c_driver);
+}
+
+module_init(cm3663_init);
+module_exit(cm3663_exit);
+
+MODULE_AUTHOR("tim.sk.lee@samsung.com");
+MODULE_DESCRIPTION("Optical Sensor driver for cm3663");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sensor/cm36651.c b/drivers/sensor/cm36651.c
new file mode 100644
index 0000000..b9650d5
--- /dev/null
+++ b/drivers/sensor/cm36651.c
@@ -0,0 +1,1360 @@
+/* driver/sensor/cm36651.c
+ * Copyright (c) 2011 SAMSUNG
+ *
+ * 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.
+ *
+ * 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 Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/i2c.h>
+#include <linux/errno.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/wakelock.h>
+#include <linux/input.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+#include <linux/sensor/cm36651.h>
+#include <linux/sensor/sensors_core.h>
+
+/* For debugging */
+#undef CM36651_DEBUG
+
+#define VENDOR "CAPELLA"
+#define CHIP_ID "CM36651"
+
+#define I2C_M_WR 0 /* for i2c Write */
+#define I2c_M_RD 1 /* for i2c Read */
+
+#define REL_RED REL_X
+#define REL_GREEN REL_Y
+#define REL_BLUE REL_Z
+#define REL_WHITE REL_MISC
+
+/* slave addresses */
+#define CM36651_ALS 0x30 /* 7bits : 0x18 */
+#define CM36651_PS 0x32 /* 7bits : 0x19 */
+
+/* register addresses */
+/* Ambient light sensor */
+#define CS_CONF1 0x00
+#define CS_CONF2 0x01
+#define ALS_WH_M 0x02
+#define ALS_WH_L 0x03
+#define ALS_WL_M 0x04
+#define ALS_WL_L 0x05
+#define CS_CONF3 0x06
+
+#define RED 0x00
+#define GREEN 0x01
+#define BLUE 0x02
+#define WHITE 0x03
+
+/* Proximity sensor */
+#define PS_CONF1 0x00
+#define PS_THD 0x01
+#define PS_CANC 0x02
+#define PS_CONF2 0x03
+
+#define ALS_REG_NUM 3
+#define PS_REG_NUM 4
+
+/* Intelligent Cancelation*/
+#define CM36651_CANCELATION
+#ifdef CM36651_CANCELATION
+#define CANCELATION_FILE_PATH "/efs/prox_cal"
+#ifndef CONFIG_MACH_M0
+#define CANCELATION_THRESHOLD 7
+#else
+#define CANCELATION_THRESHOLD 6
+#endif
+#endif
+
+#define PROX_READ_NUM 40
+ /*lightsnesor log time 6SEC 200mec X 30*/
+#define LIGHT_LOG_TIME 30
+enum {
+ LIGHT_ENABLED = BIT(0),
+ PROXIMITY_ENABLED = BIT(1),
+};
+
+/* register settings */
+static u8 als_reg_setting[ALS_REG_NUM][2] = {
+ {0x00, 0x04}, /* CS_CONF1 */
+ {0x01, 0x08}, /* CS_CONF2 */
+/* Don't care.
+ * {0x02, 0x00}, ALS_WH_M
+ * {0x03, 0x00}, ALS_WH_L
+ * {0x04, 0x00}, ALS_WL_M
+ * {0x05, 0x00}, ALS_WL_L
+ */
+ {0x06, 0x00} /* CS_CONF3 */
+};
+
+/* Change threshold value on the midas-sensor.c */
+static u8 ps_reg_setting[PS_REG_NUM][2] = {
+ {0x00, 0x3C}, /* PS_CONF1 */
+ {0x01, 0x09}, /* PS_THD */
+ {0x02, 0x00}, /* PS_CANC */
+ {0x03, 0x13}, /* PS_CONF2 */
+};
+
+ /* driver data */
+struct cm36651_data {
+ struct i2c_client *i2c_client;
+ struct wake_lock prx_wake_lock;
+ struct input_dev *proximity_input_dev;
+ struct input_dev *light_input_dev;
+ struct cm36651_platform_data *pdata;
+ struct mutex power_lock;
+ struct mutex read_lock;
+ struct hrtimer light_timer;
+ struct hrtimer prox_timer;
+ struct workqueue_struct *light_wq;
+ struct workqueue_struct *prox_wq;
+ struct work_struct work_light;
+ struct work_struct work_prox;
+ struct device *proximity_dev;
+ struct device *light_dev;
+ ktime_t light_poll_delay;
+ ktime_t prox_poll_delay;
+ int irq;
+ u8 power_state;
+ int avg[3];
+ u16 color[4];
+ int count_log_time;
+#ifdef CM36651_CANCELATION
+ u8 default_threshold;
+#endif
+};
+
+int cm36651_i2c_read_byte(struct cm36651_data *cm36651, u8 addr, u8 * val)
+{
+ int err = 0;
+ int retry = 3;
+ struct i2c_msg msg[1];
+ struct i2c_client *client = cm36651->i2c_client;
+
+ if ((client == NULL) || (!client->adapter))
+ return -ENODEV;
+
+ /* send slave address & command */
+ msg->addr = addr >> 1;
+ msg->flags = I2C_M_RD;
+ msg->len = 1;
+ msg->buf = val;
+
+ while (retry--) {
+ err = i2c_transfer(client->adapter, msg, 1);
+ if (err >= 0)
+ return err;
+ }
+ pr_err("%s: i2c read failed at addr 0x%x: %d\n", __func__, addr, err);
+ return err;
+}
+
+int cm36651_i2c_read_word(struct cm36651_data *cm36651, u8 addr, u8 command,
+ u16 *val)
+{
+ int err = 0;
+ int retry = 3;
+ struct i2c_client *client = cm36651->i2c_client;
+ struct i2c_msg msg[2];
+ unsigned char data[2] = {0,};
+ u16 value = 0;
+
+ if ((client == NULL) || (!client->adapter))
+ return -ENODEV;
+
+ while (retry--) {
+ /* send slave address & command */
+ msg[0].addr = addr>>1;
+ msg[0].flags = I2C_M_WR;
+ msg[0].len = 1;
+ msg[0].buf = &command;
+
+ /* read word data */
+ msg[1].addr = addr>>1;
+ msg[1].flags = I2C_M_RD;
+ msg[1].len = 2;
+ msg[1].buf = data;
+
+ err = i2c_transfer(client->adapter, msg, 2);
+
+ if (err >= 0) {
+ value = (u16)data[1];
+ *val = (value << 8) | (u16)data[0];
+ return err;
+ }
+ }
+ printk(KERN_ERR "%s, i2c transfer error ret=%d\n", __func__, err);
+ return err;
+}
+
+int cm36651_i2c_write_byte(struct cm36651_data *cm36651, u8 addr, u8 command,
+ u8 val)
+{
+ int err = 0;
+ struct i2c_client *client = cm36651->i2c_client;
+ struct i2c_msg msg[1];
+ unsigned char data[2];
+ int retry = 3;
+
+ if ((client == NULL) || (!client->adapter))
+ return -ENODEV;
+
+ while (retry--) {
+ data[0] = command;
+ data[1] = val;
+
+ /* send slave address & command */
+ msg->addr = addr>>1;
+ msg->flags = I2C_M_WR;
+ msg->len = 2;
+ msg->buf = data;
+
+ err = i2c_transfer(client->adapter, msg, 1);
+
+ if (err >= 0)
+ return 0;
+ }
+ pr_err("%s, i2c transfer error(%d)\n", __func__, err);
+ return err;
+}
+
+static void cm36651_light_enable(struct cm36651_data *cm36651)
+{
+ /* enable setting */
+ cm36651_i2c_write_byte(cm36651, CM36651_ALS, CS_CONF1,
+ als_reg_setting[0][1]);
+ cm36651_i2c_write_byte(cm36651, CM36651_ALS, CS_CONF2,
+ als_reg_setting[1][1]);
+
+ hrtimer_start(&cm36651->light_timer, cm36651->light_poll_delay,
+ HRTIMER_MODE_REL);
+}
+
+static void cm36651_light_disable(struct cm36651_data *cm36651)
+{
+ /* disable setting */
+ cm36651_i2c_write_byte(cm36651, CM36651_ALS, CS_CONF1,
+ 0x01);
+ hrtimer_cancel(&cm36651->light_timer);
+ cancel_work_sync(&cm36651->work_light);
+}
+
+/* sysfs */
+static ssize_t cm36651_poll_delay_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cm36651_data *cm36651 = dev_get_drvdata(dev);
+ return sprintf(buf, "%lld\n", ktime_to_ns(cm36651->light_poll_delay));
+}
+
+static ssize_t cm36651_poll_delay_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct cm36651_data *cm36651 = dev_get_drvdata(dev);
+ int64_t new_delay;
+ int err;
+
+ err = strict_strtoll(buf, 10, &new_delay);
+ if (err < 0)
+ return err;
+
+ mutex_lock(&cm36651->power_lock);
+ if (new_delay != ktime_to_ns(cm36651->light_poll_delay)) {
+ cm36651->light_poll_delay = ns_to_ktime(new_delay);
+ if (cm36651->power_state & LIGHT_ENABLED) {
+ cm36651_light_disable(cm36651);
+ cm36651_light_enable(cm36651);
+ }
+ pr_info("%s, poll_delay = %lld\n", __func__, new_delay);
+ }
+ mutex_unlock(&cm36651->power_lock);
+
+ return size;
+}
+
+static ssize_t light_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct cm36651_data *cm36651 = 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(&cm36651->power_lock);
+ pr_info("%s,new_value=%d\n", __func__, new_value);
+ if (new_value && !(cm36651->power_state & LIGHT_ENABLED)) {
+ cm36651->power_state |= LIGHT_ENABLED;
+ cm36651_light_enable(cm36651);
+ } else if (!new_value && (cm36651->power_state & LIGHT_ENABLED)) {
+ cm36651_light_disable(cm36651);
+ cm36651->power_state &= ~LIGHT_ENABLED;
+ }
+ mutex_unlock(&cm36651->power_lock);
+ return size;
+}
+
+static ssize_t light_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cm36651_data *cm36651 = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n",
+ (cm36651->power_state & LIGHT_ENABLED) ? 1 : 0);
+}
+
+#ifdef CM36651_CANCELATION
+static int proximity_open_cancelation(struct cm36651_data *data)
+{
+ struct file *cancel_filp = NULL;
+ int err = 0;
+ mm_segment_t old_fs;
+
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+
+ cancel_filp = filp_open(CANCELATION_FILE_PATH, O_RDONLY, 0666);
+ if (IS_ERR(cancel_filp)) {
+ err = PTR_ERR(cancel_filp);
+ if (err != -ENOENT)
+ pr_err("%s: Can't open cancelation file\n", __func__);
+ set_fs(old_fs);
+ return err;
+ }
+
+ err = cancel_filp->f_op->read(cancel_filp,
+ (char *)&ps_reg_setting[2][1], sizeof(u8), &cancel_filp->f_pos);
+ if (err != sizeof(u8)) {
+ pr_err("%s: Can't read the cancel data from file\n", __func__);
+ err = -EIO;
+ }
+
+ if (ps_reg_setting[2][1] != 0) /*If there is an offset cal data. */
+ ps_reg_setting[1][1] = CANCELATION_THRESHOLD;
+ pr_info("%s: proximity ps_data = %d, ps_thresh = %d\n",
+ __func__, ps_reg_setting[2][1], ps_reg_setting[1][1]);
+
+ filp_close(cancel_filp, current->files);
+ set_fs(old_fs);
+
+ return err;
+}
+
+static int proximity_store_cancelation(struct device *dev, bool do_calib)
+{
+ struct cm36651_data *cm36651 = dev_get_drvdata(dev);
+ struct file *cancel_filp = NULL;
+ mm_segment_t old_fs;
+ int err = 0;
+
+ if (do_calib) {
+#ifndef CONFIG_MACH_M0
+ mutex_lock(&cm36651->read_lock);
+ cm36651_i2c_read_byte(cm36651, CM36651_PS,
+ &ps_reg_setting[2][1]);
+ mutex_unlock(&cm36651->read_lock);
+#else
+ ps_reg_setting[2][1] = 7;
+#endif
+ ps_reg_setting[1][1] = CANCELATION_THRESHOLD;
+ } else { /* reset */
+ ps_reg_setting[2][1] = 0;
+ ps_reg_setting[1][1] = cm36651->default_threshold;
+ }
+
+ cm36651_i2c_write_byte(cm36651, CM36651_PS, PS_THD,
+ ps_reg_setting[1][1]);
+ cm36651_i2c_write_byte(cm36651, CM36651_PS, PS_CANC,
+ ps_reg_setting[2][1]);
+ pr_info("%s: prox_cal = 0x%x, prox_thresh = %d\n",
+ __func__, ps_reg_setting[2][1], ps_reg_setting[1][1]);
+
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+
+ cancel_filp = filp_open(CANCELATION_FILE_PATH,
+ O_CREAT | O_TRUNC | O_WRONLY, 0666);
+ if (IS_ERR(cancel_filp)) {
+ pr_err("%s: Can't open cancelation file\n", __func__);
+ set_fs(old_fs);
+ err = PTR_ERR(cancel_filp);
+ return err;
+ }
+
+ err = cancel_filp->f_op->write(cancel_filp,
+ (char *)&ps_reg_setting[2][1], sizeof(u8), &cancel_filp->f_pos);
+ if (err != sizeof(u8)) {
+ pr_err("%s: Can't write the cancel data to file\n", __func__);
+ err = -EIO;
+ }
+
+ filp_close(cancel_filp, current->files);
+ set_fs(old_fs);
+
+ if (!do_calib) /* delay for clearing */
+ msleep(150);
+
+ return err;
+}
+
+static ssize_t proximity_cancel_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ bool do_calib;
+ int err;
+
+ if (sysfs_streq(buf, "1")) /* calibrate cancelation value */
+ do_calib = true;
+ else if (sysfs_streq(buf, "0")) /* reset cancelation value */
+ do_calib = false;
+ else {
+ pr_debug("%s: invalid value %d\n", __func__, *buf);
+ return -EINVAL;
+ }
+
+ err = proximity_store_cancelation(dev, do_calib);
+ if (err < 0) {
+ pr_err("%s: proximity_store_cancelation() failed\n", __func__);
+ return err;
+ }
+
+ return size;
+}
+
+static ssize_t proximity_cancel_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d,%d\n", ps_reg_setting[2][1],
+ ps_reg_setting[1][1]);
+}
+#endif
+
+static ssize_t proximity_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct cm36651_data *cm36651 = dev_get_drvdata(dev);
+ bool new_value;
+ int err = 0;
+
+ 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(&cm36651->power_lock);
+ pr_info("%s, new_value = %d, threshold = %d\n", __func__, new_value,
+ ps_reg_setting[1][1]);
+ if (new_value && !(cm36651->power_state & PROXIMITY_ENABLED)) {
+ u8 val = 1;
+ int i;
+ if (cm36651->pdata->cm36651_led_on) {
+ cm36651->pdata->cm36651_led_on(true);
+ msleep(20);
+ }
+#ifdef CM36651_CANCELATION
+ /* open cancelation data */
+ err = proximity_open_cancelation(cm36651);
+ if (err < 0 && err != -ENOENT)
+ pr_err("%s: proximity_open_cancelation() failed\n",
+ __func__);
+#endif
+ cm36651->power_state |= PROXIMITY_ENABLED;
+ /* enable settings */
+ for (i = 0; i < 4; i++) {
+ cm36651_i2c_write_byte(cm36651, CM36651_PS,
+ ps_reg_setting[i][0], ps_reg_setting[i][1]);
+ }
+
+ val = gpio_get_value(cm36651->pdata->irq);
+ /* 0 is close, 1 is far */
+ input_report_abs(cm36651->proximity_input_dev,
+ ABS_DISTANCE, val);
+ input_sync(cm36651->proximity_input_dev);
+
+ enable_irq(cm36651->irq);
+ enable_irq_wake(cm36651->irq);
+ } else if (!new_value && (cm36651->power_state & PROXIMITY_ENABLED)) {
+ cm36651->power_state &= ~PROXIMITY_ENABLED;
+ disable_irq_wake(cm36651->irq);
+ disable_irq(cm36651->irq);
+ /* disable settings */
+ cm36651_i2c_write_byte(cm36651, CM36651_PS, PS_CONF1,
+ 0x01);
+ if (cm36651->pdata->cm36651_led_on)
+ cm36651->pdata->cm36651_led_on(false);
+ }
+ mutex_unlock(&cm36651->power_lock);
+ return size;
+}
+
+static ssize_t proximity_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cm36651_data *cm36651 = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n",
+ (cm36651->power_state & PROXIMITY_ENABLED) ? 1 : 0);
+}
+
+static DEVICE_ATTR(poll_delay, S_IRUGO | S_IWUSR | S_IWGRP,
+ cm36651_poll_delay_show, cm36651_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,
+};
+
+/* proximity sysfs */
+static ssize_t proximity_avg_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cm36651_data *cm36651 = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d,%d,%d\n", cm36651->avg[0],
+ cm36651->avg[1], cm36651->avg[2]);
+}
+
+static ssize_t proximity_avg_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct cm36651_data *cm36651 = dev_get_drvdata(dev);
+ bool new_value = false;
+
+ 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;
+ }
+
+ pr_info("%s, average enable = %d\n", __func__, new_value);
+ mutex_lock(&cm36651->power_lock);
+ if (new_value) {
+ if (!(cm36651->power_state & PROXIMITY_ENABLED)) {
+ if (cm36651->pdata->cm36651_led_on) {
+ cm36651->pdata->cm36651_led_on(true);
+ msleep(20);
+ }
+ cm36651_i2c_write_byte(cm36651, CM36651_PS, PS_CONF1,
+ ps_reg_setting[0][1]);
+ }
+ hrtimer_start(&cm36651->prox_timer, cm36651->prox_poll_delay,
+ HRTIMER_MODE_REL);
+ } else if (!new_value) {
+ hrtimer_cancel(&cm36651->prox_timer);
+ cancel_work_sync(&cm36651->work_prox);
+ if (!(cm36651->power_state & PROXIMITY_ENABLED)) {
+ cm36651_i2c_write_byte(cm36651, CM36651_PS, PS_CONF1,
+ 0x01);
+ if (cm36651->pdata->cm36651_led_on)
+ cm36651->pdata->cm36651_led_on(false);
+ }
+ }
+ mutex_unlock(&cm36651->power_lock);
+
+ return size;
+}
+
+static ssize_t proximity_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cm36651_data *cm36651 = dev_get_drvdata(dev);
+ u8 proximity_value = 0;
+
+ mutex_lock(&cm36651->power_lock);
+ if (!(cm36651->power_state & PROXIMITY_ENABLED)) {
+ if (cm36651->pdata->cm36651_led_on) {
+ cm36651->pdata->cm36651_led_on(true);
+ msleep(20);
+ }
+ cm36651_i2c_write_byte(cm36651, CM36651_PS, PS_CONF1,
+ ps_reg_setting[0][1]);
+ }
+
+ mutex_lock(&cm36651->read_lock);
+ cm36651_i2c_read_byte(cm36651, CM36651_PS, &proximity_value);
+ mutex_unlock(&cm36651->read_lock);
+
+ if (!(cm36651->power_state & PROXIMITY_ENABLED)) {
+ cm36651_i2c_write_byte(cm36651, CM36651_PS, PS_CONF1, 0x01);
+ if (cm36651->pdata->cm36651_led_on)
+ cm36651->pdata->cm36651_led_on(false);
+ }
+ mutex_unlock(&cm36651->power_lock);
+
+ return sprintf(buf, "%d\n", proximity_value);
+}
+
+static ssize_t proximity_thresh_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "prox_threshold = %d\n", ps_reg_setting[1][1]);
+}
+
+static ssize_t proximity_thresh_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct cm36651_data *cm36651 = dev_get_drvdata(dev);
+ u8 thresh_value = 0x09;
+ int err = 0;
+
+ err = kstrtou8(buf, 10, &thresh_value);
+ if (err < 0)
+ pr_err("%s, kstrtoint failed.", __func__);
+
+ ps_reg_setting[1][1] = thresh_value;
+ err = cm36651_i2c_write_byte(cm36651, CM36651_PS,
+ PS_THD, ps_reg_setting[1][1]);
+ if (err < 0) {
+ pr_err("%s: cm36651_ps_reg is failed. %d\n", __func__,
+ err);
+ return err;
+ }
+ pr_info("%s, new threshold = 0x%x\n",
+ __func__, ps_reg_setting[1][1]);
+ msleep(150);
+
+ return size;
+}
+
+#ifdef CM36651_CANCELATION
+static DEVICE_ATTR(prox_cal, 0644, proximity_cancel_show,
+ proximity_cancel_store);
+#endif
+static DEVICE_ATTR(prox_avg, 0644, proximity_avg_show,
+ proximity_avg_store);
+static DEVICE_ATTR(state, 0644, proximity_state_show, NULL);
+static struct device_attribute attr_prox_raw = __ATTR(raw_data, 0644,
+ proximity_state_show, NULL);
+static DEVICE_ATTR(prox_thresh, 0644, proximity_thresh_show,
+ proximity_thresh_store);
+
+/* light sysfs */
+static ssize_t light_lux_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cm36651_data *cm36651 = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u,%u,%u,%u\n",
+ cm36651->color[0]+1, cm36651->color[1]+1,
+ cm36651->color[2]+1, cm36651->color[3]+1);
+}
+
+static ssize_t light_data_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cm36651_data *cm36651 = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u,%u,%u,%u\n",
+ cm36651->color[0]+1, cm36651->color[1]+1,
+ cm36651->color[2]+1, cm36651->color[3]+1);
+}
+
+static DEVICE_ATTR(lux, 0644, light_lux_show, NULL);
+static DEVICE_ATTR(raw_data, 0644, light_data_show, NULL);
+
+/* sysfs for vendor & name */
+static ssize_t cm36651_vendor_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", VENDOR);
+}
+
+static ssize_t cm36651_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", CHIP_ID);
+}
+static DEVICE_ATTR(vendor, 0644, cm36651_vendor_show, NULL);
+static DEVICE_ATTR(name, 0644, cm36651_name_show, NULL);
+
+/* interrupt happened due to transition/change of near/far proximity state */
+irqreturn_t cm36651_irq_thread_fn(int irq, void *data)
+{
+ struct cm36651_data *cm36651 = data;
+ u8 val = 1;
+#ifdef CM36651_DEBUG
+ static int count;
+#endif
+ u8 ps_data = 0;
+
+ val = gpio_get_value(cm36651->pdata->irq);
+ cm36651_i2c_read_byte(cm36651, CM36651_PS, &ps_data);
+#ifdef CM36651_DEBUG
+ pr_info("%s: count = %d\n", __func__, count++);
+#endif
+ /* 0 is close, 1 is far */
+ input_report_abs(cm36651->proximity_input_dev, ABS_DISTANCE, val);
+ input_sync(cm36651->proximity_input_dev);
+ wake_lock_timeout(&cm36651->prx_wake_lock, 3 * HZ);
+ pr_info("%s: val = %d, ps_data = %d (close:0, far:1)\n",
+ __func__, val, ps_data);
+
+ return IRQ_HANDLED;
+}
+
+static int cm36651_setup_reg(struct cm36651_data *cm36651)
+{
+ int err = 0, i = 0;
+ u8 tmp = 0;
+
+ /* ALS initialization */
+ for (i = 0; i < ALS_REG_NUM; i++) {
+ err = cm36651_i2c_write_byte(cm36651,
+ CM36651_ALS, als_reg_setting[i][0],
+ als_reg_setting[i][1]);
+ if (err < 0) {
+ pr_err("%s: cm36651_als_reg is failed. %d\n", __func__,
+ err);
+ return err;
+ }
+ }
+
+ /* PS initialization */
+ for (i = 0; i < PS_REG_NUM; i++) {
+ err = cm36651_i2c_write_byte(cm36651, CM36651_PS,
+ ps_reg_setting[i][0], ps_reg_setting[i][1]);
+ if (err < 0) {
+ pr_err("%s: cm36651_ps_reg is failed. %d\n", __func__,
+ err);
+ return err;
+ }
+ }
+
+ /* printing the inital proximity value with no contact */
+ msleep(50);
+ mutex_lock(&cm36651->read_lock);
+ err = cm36651_i2c_read_byte(cm36651, CM36651_PS, &tmp);
+ mutex_unlock(&cm36651->read_lock);
+ if (err < 0) {
+ pr_err("%s: read ps_data failed\n", __func__);
+ err = -EIO;
+ }
+ pr_err("%s: initial proximity value = %d\n", __func__, tmp);
+
+ /* turn off */
+ cm36651_i2c_write_byte(cm36651, CM36651_ALS, CS_CONF1, 0x01);
+ cm36651_i2c_write_byte(cm36651, CM36651_ALS, PS_CONF1, 0x01);
+
+ pr_info("%s is success.", __func__);
+ return err;
+}
+
+static int cm36651_setup_irq(struct cm36651_data *cm36651)
+{
+ int rc = -EIO;
+ struct cm36651_platform_data *pdata = cm36651->pdata;
+
+ rc = gpio_request(pdata->irq, "gpio_proximity_out");
+ if (rc < 0) {
+ pr_err("%s: gpio %d request failed (%d)\n",
+ __func__, pdata->irq, rc);
+ return rc;
+ }
+
+ rc = gpio_direction_input(pdata->irq);
+ if (rc < 0) {
+ pr_err("%s: failed to set gpio %d as input (%d)\n",
+ __func__, pdata->irq, rc);
+ goto err_gpio_direction_input;
+ }
+
+ cm36651->irq = gpio_to_irq(pdata->irq);
+ rc = request_threaded_irq(cm36651->irq, NULL,
+ cm36651_irq_thread_fn,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "proximity_int", cm36651);
+ if (rc < 0) {
+ pr_err("%s: request_irq(%d) failed for gpio %d (%d)\n",
+ __func__, cm36651->irq, pdata->irq, rc);
+ goto err_request_irq;
+ }
+
+ /* start with interrupts disabled */
+ disable_irq(cm36651->irq);
+
+ pr_err("%s, success\n", __func__);
+
+ goto done;
+
+err_request_irq:
+err_gpio_direction_input:
+ gpio_free(pdata->irq);
+done:
+ return rc;
+}
+
+/* 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 cm36651_light_timer_func(struct hrtimer *timer)
+{
+ struct cm36651_data *cm36651
+ = container_of(timer, struct cm36651_data, light_timer);
+ queue_work(cm36651->light_wq, &cm36651->work_light);
+ hrtimer_forward_now(&cm36651->light_timer, cm36651->light_poll_delay);
+ return HRTIMER_RESTART;
+}
+
+static void cm36651_work_func_light(struct work_struct *work)
+{
+ struct cm36651_data *cm36651 = container_of(work, struct cm36651_data,
+ work_light);
+
+ mutex_lock(&cm36651->read_lock);
+ cm36651_i2c_read_word(cm36651, CM36651_ALS, RED, &cm36651->color[0]);
+ cm36651_i2c_read_word(cm36651, CM36651_ALS, GREEN, &cm36651->color[1]);
+ cm36651_i2c_read_word(cm36651, CM36651_ALS, BLUE, &cm36651->color[2]);
+ cm36651_i2c_read_word(cm36651, CM36651_ALS, WHITE, &cm36651->color[3]);
+ mutex_unlock(&cm36651->read_lock);
+
+ input_report_rel(cm36651->light_input_dev, REL_RED,
+ cm36651->color[0]+1);
+ input_report_rel(cm36651->light_input_dev, REL_GREEN,
+ cm36651->color[1]+1);
+ input_report_rel(cm36651->light_input_dev, REL_BLUE,
+ cm36651->color[2]+1);
+ input_report_rel(cm36651->light_input_dev, REL_WHITE,
+ cm36651->color[3]+1);
+ input_sync(cm36651->light_input_dev);
+
+ if (cm36651->count_log_time >= LIGHT_LOG_TIME) {
+ pr_info("%s, red = %u green = %u blue = %u white = %u\n",
+ __func__, cm36651->color[0]+1, cm36651->color[1]+1,
+ cm36651->color[2]+1, cm36651->color[3]+1);
+ cm36651->count_log_time = 0;
+ } else
+ cm36651->count_log_time++;
+
+#ifdef CM36651_DEBUG
+ pr_info("%s, red = %u green = %u blue = %u white = %u\n",
+ __func__, cm36651->color[0]+1, cm36651->color[1]+1,
+ cm36651->color[2]+1, val_whitecm36651->color[3]1);
+#endif
+}
+
+static void proxsensor_get_avg_val(struct cm36651_data *cm36651)
+{
+ int min = 0, max = 0, avg = 0;
+ int i;
+ u8 ps_data = 0;
+
+ for (i = 0; i < PROX_READ_NUM; i++) {
+ msleep(40);
+ cm36651_i2c_read_byte(cm36651, CM36651_PS, &ps_data);
+ avg += ps_data;
+
+ if (!i)
+ min = ps_data;
+ else if (ps_data < min)
+ min = ps_data;
+
+ if (ps_data > max)
+ max = ps_data;
+ }
+ avg /= PROX_READ_NUM;
+
+ cm36651->avg[0] = min;
+ cm36651->avg[1] = avg;
+ cm36651->avg[2] = max;
+}
+
+static void cm36651_work_func_prox(struct work_struct *work)
+{
+ struct cm36651_data *cm36651 = container_of(work, struct cm36651_data,
+ work_prox);
+ proxsensor_get_avg_val(cm36651);
+}
+
+static enum hrtimer_restart cm36651_prox_timer_func(struct hrtimer *timer)
+{
+ struct cm36651_data *cm36651
+ = container_of(timer, struct cm36651_data, prox_timer);
+ queue_work(cm36651->prox_wq, &cm36651->work_prox);
+ hrtimer_forward_now(&cm36651->prox_timer, cm36651->prox_poll_delay);
+ return HRTIMER_RESTART;
+}
+
+static int cm36651_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret = -ENODEV;
+ struct cm36651_data *cm36651 = NULL;
+
+ pr_info("%s is called.\n", __func__);
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ pr_err("%s: i2c functionality check failed!\n", __func__);
+ return ret;
+ }
+
+ cm36651 = kzalloc(sizeof(struct cm36651_data), GFP_KERNEL);
+ if (!cm36651) {
+ pr_err
+ ("%s: failed to alloc memory for RGB sensor module data\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ cm36651->pdata = client->dev.platform_data;
+ cm36651->i2c_client = client;
+ i2c_set_clientdata(client, cm36651);
+ mutex_init(&cm36651->power_lock);
+ mutex_init(&cm36651->read_lock);
+
+ /* wake lock init for proximity sensor */
+ wake_lock_init(&cm36651->prx_wake_lock, WAKE_LOCK_SUSPEND,
+ "prx_wake_lock");
+ if (cm36651->pdata->cm36651_led_on) {
+ cm36651->pdata->cm36651_led_on(true);
+ msleep(20);
+ }
+ /* Check if the device is there or not. */
+ ret = cm36651_i2c_write_byte(cm36651, CM36651_PS, CS_CONF1, 0x01);
+ if (ret < 0) {
+ pr_err("%s: cm36651 is not connected.(%d)\n", __func__, ret);
+ goto err_setup_reg;
+ }
+ /* setup initial registers */
+ if (cm36651->pdata->cm36651_get_threshold)
+ ps_reg_setting[1][1] = cm36651->pdata->cm36651_get_threshold();
+#ifdef CM36651_CANCELATION
+ cm36651->default_threshold = ps_reg_setting[1][1];
+#endif
+ ret = cm36651_setup_reg(cm36651);
+ if (ret < 0) {
+ pr_err("%s: could not setup regs\n", __func__);
+ goto err_setup_reg;
+ }
+ if (cm36651->pdata->cm36651_led_on)
+ cm36651->pdata->cm36651_led_on(false);
+
+ /* allocate proximity input_device */
+ cm36651->proximity_input_dev = input_allocate_device();
+ if (!cm36651->proximity_input_dev) {
+ pr_err("%s: could not allocate proximity input device\n",
+ __func__);
+ goto err_input_allocate_device_proximity;
+ }
+
+ input_set_drvdata(cm36651->proximity_input_dev, cm36651);
+ cm36651->proximity_input_dev->name = "proximity_sensor";
+ input_set_capability(cm36651->proximity_input_dev, EV_ABS,
+ ABS_DISTANCE);
+ input_set_abs_params(cm36651->proximity_input_dev, ABS_DISTANCE, 0, 1,
+ 0, 0);
+
+ ret = input_register_device(cm36651->proximity_input_dev);
+ if (ret < 0) {
+ input_free_device(cm36651->proximity_input_dev);
+ pr_err("%s: could not register input device\n", __func__);
+ goto err_input_register_device_proximity;
+ }
+
+ ret = sysfs_create_group(&cm36651->proximity_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;
+ }
+
+ /* setup irq */
+ ret = cm36651_setup_irq(cm36651);
+ if (ret) {
+ pr_err("%s: could not setup irq\n", __func__);
+ goto err_setup_irq;
+ }
+
+ /* For factory test mode, we use timer to get average proximity data. */
+ /* prox_timer settings. we poll for light values using a timer. */
+ hrtimer_init(&cm36651->prox_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ cm36651->prox_poll_delay = ns_to_ktime(2000 * NSEC_PER_MSEC);/*2 sec*/
+ cm36651->prox_timer.function = cm36651_prox_timer_func;
+
+ /* the timer just fires off a work queue request. we need a thread
+ to read the i2c (can be slow and blocking). */
+ cm36651->prox_wq = create_singlethread_workqueue("cm36651_prox_wq");
+ if (!cm36651->prox_wq) {
+ ret = -ENOMEM;
+ pr_err("%s: could not create prox workqueue\n", __func__);
+ goto err_create_prox_workqueue;
+ }
+ /* this is the thread function we run on the work queue */
+ INIT_WORK(&cm36651->work_prox, cm36651_work_func_prox);
+
+ /* allocate lightsensor input_device */
+ cm36651->light_input_dev = input_allocate_device();
+ if (!cm36651->light_input_dev) {
+ pr_err("%s: could not allocate light input device\n", __func__);
+ goto err_input_allocate_device_light;
+ }
+
+ input_set_drvdata(cm36651->light_input_dev, cm36651);
+ cm36651->light_input_dev->name = "light_sensor";
+ input_set_capability(cm36651->light_input_dev, EV_REL, REL_RED);
+ input_set_capability(cm36651->light_input_dev, EV_REL, REL_GREEN);
+ input_set_capability(cm36651->light_input_dev, EV_REL, REL_BLUE);
+ input_set_capability(cm36651->light_input_dev, EV_REL, REL_WHITE);
+
+ ret = input_register_device(cm36651->light_input_dev);
+ if (ret < 0) {
+ input_free_device(cm36651->light_input_dev);
+ pr_err("%s: could not register input device\n", __func__);
+ goto err_input_register_device_light;
+ }
+
+ ret = sysfs_create_group(&cm36651->light_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;
+ }
+
+ /* light_timer settings. we poll for light values using a timer. */
+ hrtimer_init(&cm36651->light_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ cm36651->light_poll_delay = ns_to_ktime(200 * NSEC_PER_MSEC);
+ cm36651->light_timer.function = cm36651_light_timer_func;
+
+ /* the timer just fires off a work queue request. we need a thread
+ to read the i2c (can be slow and blocking). */
+ cm36651->light_wq = create_singlethread_workqueue("cm36651_light_wq");
+ if (!cm36651->light_wq) {
+ ret = -ENOMEM;
+ pr_err("%s: could not create light workqueue\n", __func__);
+ goto err_create_light_workqueue;
+ }
+
+ /* this is the thread function we run on the work queue */
+ INIT_WORK(&cm36651->work_light, cm36651_work_func_light);
+
+ /* set sysfs for proximity sensor */
+ cm36651->proximity_dev = sensors_classdev_register("proximity_sensor");
+ if (IS_ERR(cm36651->proximity_dev)) {
+ pr_err("%s: could not create proximity_dev\n", __func__);
+ goto err_proximity_device_create;
+ }
+
+ if (device_create_file(cm36651->proximity_dev, &dev_attr_state) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_state.attr.name);
+ goto err_proximity_device_create_file1;
+ }
+
+ if (device_create_file(cm36651->proximity_dev, &attr_prox_raw) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ attr_prox_raw.attr.name);
+ goto err_proximity_device_create_file7;
+ }
+
+#ifdef CM36651_CANCELATION
+ if (device_create_file(cm36651->proximity_dev,
+ &dev_attr_prox_cal) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_prox_cal.attr.name);
+ goto err_proximity_device_create_file2;
+ }
+#endif
+ if (device_create_file(cm36651->proximity_dev,
+ &dev_attr_prox_avg) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_prox_avg.attr.name);
+ goto err_proximity_device_create_file3;
+ }
+
+ if (device_create_file(cm36651->proximity_dev,
+ &dev_attr_prox_thresh) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_prox_thresh.attr.name);
+ goto err_proximity_device_create_file4;
+ }
+
+ if (device_create_file(cm36651->proximity_dev,
+ &dev_attr_vendor) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_vendor.attr.name);
+ goto err_proximity_device_create_file5;
+ }
+
+ if (device_create_file(cm36651->proximity_dev,
+ &dev_attr_name) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_name.attr.name);
+ goto err_proximity_device_create_file6;
+ }
+
+ dev_set_drvdata(cm36651->proximity_dev, cm36651);
+
+ /* set sysfs for light sensor */
+ cm36651->light_dev = sensors_classdev_register("light_sensor");
+ if (IS_ERR(cm36651->light_dev)) {
+ pr_err("%s: could not create light_dev\n", __func__);
+ goto err_light_device_create;
+ }
+
+ if (device_create_file(cm36651->light_dev, &dev_attr_lux) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_lux.attr.name);
+ goto err_light_device_create_file1;
+ }
+
+ if (device_create_file(cm36651->light_dev, &dev_attr_raw_data) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_raw_data.attr.name);
+ goto err_light_device_create_file2;
+ }
+
+ if (device_create_file(cm36651->light_dev, &dev_attr_vendor) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_vendor.attr.name);
+ goto err_light_device_create_file3;
+ }
+
+ if (device_create_file(cm36651->light_dev, &dev_attr_name) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_name.attr.name);
+ goto err_light_device_create_file4;
+ }
+
+ dev_set_drvdata(cm36651->light_dev, cm36651);
+
+ pr_info("%s is success.\n", __func__);
+ goto done;
+
+/* error, unwind it all */
+err_light_device_create_file4:
+ device_remove_file(cm36651->light_dev, &dev_attr_vendor);
+err_light_device_create_file3:
+ device_remove_file(cm36651->light_dev, &dev_attr_raw_data);
+err_light_device_create_file2:
+ device_remove_file(cm36651->light_dev, &dev_attr_lux);
+err_light_device_create_file1:
+ sensors_classdev_unregister(cm36651->light_dev);
+err_light_device_create:
+ device_remove_file(cm36651->proximity_dev, &dev_attr_name);
+err_proximity_device_create_file5:
+ device_remove_file(cm36651->proximity_dev, &dev_attr_vendor);
+err_proximity_device_create_file6:
+ device_remove_file(cm36651->proximity_dev, &dev_attr_prox_thresh);
+err_proximity_device_create_file4:
+ device_remove_file(cm36651->proximity_dev, &dev_attr_prox_avg);
+err_proximity_device_create_file3:
+#ifdef CM36651_CANCELATION
+ device_remove_file(cm36651->proximity_dev, &dev_attr_prox_cal);
+err_proximity_device_create_file2:
+#endif
+ device_remove_file(cm36651->proximity_dev, &attr_prox_raw);
+err_proximity_device_create_file7:
+ device_remove_file(cm36651->proximity_dev, &dev_attr_state);
+err_proximity_device_create_file1:
+ sensors_classdev_unregister(cm36651->proximity_dev);
+err_proximity_device_create:
+ destroy_workqueue(cm36651->light_wq);
+err_create_light_workqueue:
+ sysfs_remove_group(&cm36651->light_input_dev->dev.kobj,
+ &light_attribute_group);
+err_sysfs_create_group_light:
+ input_unregister_device(cm36651->light_input_dev);
+err_input_register_device_light:
+err_input_allocate_device_light:
+ destroy_workqueue(cm36651->prox_wq);
+err_create_prox_workqueue:
+ free_irq(cm36651->irq, cm36651);
+ gpio_free(cm36651->pdata->irq);
+err_setup_irq:
+ sysfs_remove_group(&cm36651->proximity_input_dev->dev.kobj,
+ &proximity_attribute_group);
+err_sysfs_create_group_proximity:
+ input_unregister_device(cm36651->proximity_input_dev);
+err_input_register_device_proximity:
+err_input_allocate_device_proximity:
+err_setup_reg:
+ wake_lock_destroy(&cm36651->prx_wake_lock);
+ mutex_destroy(&cm36651->read_lock);
+ mutex_destroy(&cm36651->power_lock);
+ kfree(cm36651);
+done:
+ return ret;
+}
+
+static int cm36651_i2c_remove(struct i2c_client *client)
+{
+ struct cm36651_data *cm36651 = i2c_get_clientdata(client);
+
+ /* free irq */
+ if (cm36651->power_state & PROXIMITY_ENABLED) {
+ disable_irq_wake(cm36651->irq);
+ disable_irq(cm36651->irq);
+ }
+ free_irq(cm36651->irq, cm36651);
+ gpio_free(cm36651->pdata->irq);
+
+ /* device off */
+ if (cm36651->power_state & LIGHT_ENABLED)
+ cm36651_light_disable(cm36651);
+ if (cm36651->power_state & PROXIMITY_ENABLED) {
+ cm36651_i2c_write_byte(cm36651, CM36651_PS, PS_CONF1,
+ 0x01);
+ if (cm36651->pdata->cm36651_led_on)
+ cm36651->pdata->cm36651_led_on(false);
+ }
+
+ /* destroy workqueue */
+ destroy_workqueue(cm36651->light_wq);
+ destroy_workqueue(cm36651->prox_wq);
+
+ /* sysfs destroy */
+ device_remove_file(cm36651->light_dev, &dev_attr_name);
+ device_remove_file(cm36651->light_dev, &dev_attr_vendor);
+ device_remove_file(cm36651->light_dev, &dev_attr_raw_data);
+ device_remove_file(cm36651->light_dev, &dev_attr_lux);
+ sensors_classdev_unregister(cm36651->light_dev);
+
+ device_remove_file(cm36651->proximity_dev, &dev_attr_name);
+ device_remove_file(cm36651->proximity_dev, &dev_attr_vendor);
+ device_remove_file(cm36651->proximity_dev, &dev_attr_prox_thresh);
+ device_remove_file(cm36651->proximity_dev, &dev_attr_prox_avg);
+#ifdef CM36651_CANCELATION
+ device_remove_file(cm36651->proximity_dev, &dev_attr_prox_cal);
+#endif
+ device_remove_file(cm36651->proximity_dev, &attr_prox_raw);
+ device_remove_file(cm36651->proximity_dev, &dev_attr_state);
+ sensors_classdev_unregister(cm36651->proximity_dev);
+
+ /* input device destroy */
+ sysfs_remove_group(&cm36651->light_input_dev->dev.kobj,
+ &light_attribute_group);
+ input_unregister_device(cm36651->light_input_dev);
+ sysfs_remove_group(&cm36651->proximity_input_dev->dev.kobj,
+ &proximity_attribute_group);
+ input_unregister_device(cm36651->proximity_input_dev);
+
+ /* lock destroy */
+ mutex_destroy(&cm36651->read_lock);
+ mutex_destroy(&cm36651->power_lock);
+ wake_lock_destroy(&cm36651->prx_wake_lock);
+
+ kfree(cm36651);
+
+ return 0;
+}
+
+static int cm36651_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
+ cm36651->power_state because we use that state in resume.
+ */
+ struct cm36651_data *cm36651 = dev_get_drvdata(dev);
+
+ if (cm36651->power_state & LIGHT_ENABLED)
+ cm36651_light_disable(cm36651);
+
+ return 0;
+}
+
+static int cm36651_resume(struct device *dev)
+{
+ struct cm36651_data *cm36651 = dev_get_drvdata(dev);
+
+ if (cm36651->power_state & LIGHT_ENABLED)
+ cm36651_light_enable(cm36651);
+
+ return 0;
+}
+
+static const struct i2c_device_id cm36651_device_id[] = {
+ {"cm36651", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, cm36651_device_id);
+
+static const struct dev_pm_ops cm36651_pm_ops = {
+ .suspend = cm36651_suspend,
+ .resume = cm36651_resume
+};
+
+static struct i2c_driver cm36651_i2c_driver = {
+ .driver = {
+ .name = "cm36651",
+ .owner = THIS_MODULE,
+ .pm = &cm36651_pm_ops},
+ .probe = cm36651_i2c_probe,
+ .remove = cm36651_i2c_remove,
+ .id_table = cm36651_device_id,
+};
+
+static int __init cm36651_init(void)
+{
+ return i2c_add_driver(&cm36651_i2c_driver);
+}
+
+static void __exit cm36651_exit(void)
+{
+ i2c_del_driver(&cm36651_i2c_driver);
+}
+
+module_init(cm36651_init);
+module_exit(cm36651_exit);
+
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_DESCRIPTION("RGB Sensor device driver for cm36651");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sensor/gp2a_analog.c b/drivers/sensor/gp2a_analog.c
new file mode 100644
index 0000000..cd25462
--- /dev/null
+++ b/drivers/sensor/gp2a_analog.c
@@ -0,0 +1,787 @@
+/* Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/i2c.h>
+#include <linux/errno.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/wakelock.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/workqueue.h>
+#include <linux/uaccess.h>
+#include <linux/sensor/gp2a_analog.h>
+#include <plat/adc.h>
+
+
+/* Note about power vs enable/disable:
+ * The chip has two functions, proximity and ambient light sensing.
+ * There is no separate power enablement to the two functions (unlike
+ * the Capella CM3602/3623).
+ * This module implements two drivers: /dev/proximity and /dev/light.
+ * When either driver is enabled (via sysfs attributes), we give power
+ * to the chip. When both are disabled, we remove power from the chip.
+ * In suspend, we remove power if light is disabled but not if proximity is
+ * enabled (proximity is allowed to wakeup from suspend).
+ *
+ * There are no ioctls for either driver interfaces. Output is via
+ * input device framework and control via sysfs attributes.
+ */
+
+
+#define gp2a_dbgmsg(str, args...) pr_debug("%s: " str, __func__, ##args)
+
+#define ADC_BUFFER_NUM 6
+
+/* 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 */
+
+/* sensor type */
+#define LIGHT 0
+#define PROXIMITY 1
+#define ALL 2
+
+#if defined(CONFIG_MACH_Q1_BD)
+/* light sensor adc channel */
+#define ALS_IOUT_ADC 2
+#endif
+
+static u8 reg_defaults[5] = {
+ 0x00, /* PROX: read only register */
+ 0x08, /* GAIN: large LED drive level */
+ 0x40, /* HYS: receiver sensitivity - B1 mode */
+ 0x04, /* CYCLE: */
+ 0x01, /* OPMOD: normal operating mode */
+};
+
+enum {
+ LIGHT_ENABLED = BIT(0),
+ PROXIMITY_ENABLED = BIT(1),
+};
+
+
+/* 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 class *lightsensor_class;
+ struct device *switch_cmd_dev;
+ int irq;
+ struct work_struct work_light;
+ struct hrtimer timer;
+ ktime_t light_poll_delay;
+ int adc_value_buf[ADC_BUFFER_NUM];
+ int adc_index_count;
+ bool adc_buf_initialized;
+ bool on;
+ u8 power_state;
+ struct mutex power_lock;
+ struct wake_lock prx_wake_lock;
+ struct workqueue_struct *wq;
+ struct mutex adc_lock;
+ char val_state;
+ struct s3c_adc_client *padc;
+ struct platform_device *pdev_gp2a_adc;
+};
+
+int gp2a_i2c_write(struct gp2a_data *gp2a, u8 reg, u8 *val)
+{
+ int err = 0;
+ struct i2c_msg msg[1];
+ unsigned char data[2];
+ int retry = 10;
+ struct i2c_client *client = gp2a->i2c_client;
+
+ if ((client == NULL) || (!client->adapter))
+ return -ENODEV;
+
+ data[0] = reg;
+ data[1] = *val;
+
+ msg->addr = client->addr;
+ msg->flags = 0; /* write */
+ msg->len = 2;
+ msg->buf = data;
+
+ while (retry--) {
+ err = i2c_transfer(client->adapter, msg, 1);
+ if (err == 1)
+ return 0;
+ }
+
+ pr_err("%s failed\n", __func__);
+ 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);
+ /* mark the adc buff as not initialized
+ * so that it will be filled again on next light sensor start
+ */
+ gp2a->adc_buf_initialized = false;
+}
+
+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_lock);
+ 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_lock);
+
+ 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_lock);
+ 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)) {
+#if defined(CONFIG_MACH_Q1_BD)
+ if (!gp2a->power_state)
+ gp2a->pdata->power(true);
+#endif
+ 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;
+#if defined(CONFIG_MACH_Q1_BD)
+ if (!gp2a->power_state)
+ gp2a->pdata->power(false);
+#endif
+ }
+ mutex_unlock(&gp2a->power_lock);
+ 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;
+
+ 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_lock);
+ 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)) {
+#if defined(CONFIG_MACH_Q1_BD)
+ if (!gp2a->power_state)
+ gp2a->pdata->power(true);
+#endif
+ gp2a->power_state |= PROXIMITY_ENABLED;
+ gp2a_i2c_write(gp2a, REGS_GAIN, &reg_defaults[1]);
+ gp2a_i2c_write(gp2a, REGS_HYS, &reg_defaults[2]);
+ gp2a_i2c_write(gp2a, REGS_CYCLE, &reg_defaults[3]);
+ gp2a_i2c_write(gp2a, REGS_OPMOD, &reg_defaults[4]);
+ enable_irq(gp2a->irq);
+ enable_irq_wake(gp2a->irq);
+ } else if (!new_value && (gp2a->power_state & PROXIMITY_ENABLED)) {
+ disable_irq_wake(gp2a->irq);
+ disable_irq(gp2a->irq);
+ gp2a_i2c_write(gp2a, REGS_OPMOD, &reg_defaults[0]);
+ gp2a->power_state &= ~PROXIMITY_ENABLED;
+#if defined(CONFIG_MACH_Q1_BD)
+ if (!gp2a->power_state)
+ gp2a->pdata->power(false);
+#endif
+ }
+ mutex_unlock(&gp2a->power_lock);
+ return size;
+}
+
+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 int lightsensor_get_adcvalue(struct gp2a_data *gp2a)
+{
+ int i = 0;
+ int j = 0;
+ unsigned int adc_total = 0;
+ int adc_avr_value;
+ unsigned int adc_index = 0;
+ unsigned int adc_max = 0;
+ unsigned int adc_min = 0;
+ int value = 0;
+
+ /* get ADC */
+ /* value = gp2a->pdata->light_adc_value(); */
+ value = s3c_adc_read(gp2a->padc, ALS_IOUT_ADC);
+ if (value < 0)
+ pr_err("%s: s3c_adc_read() failed(%d)\n", __func__, value);
+
+ adc_index = (gp2a->adc_index_count++) % ADC_BUFFER_NUM;
+
+ /*ADC buffer initialize (light sensor off ---> light sensor on) */
+ if (!gp2a->adc_buf_initialized) {
+ gp2a->adc_buf_initialized = true;
+ for (j = 0; j < ADC_BUFFER_NUM; j++)
+ gp2a->adc_value_buf[j] = value;
+ } else
+ gp2a->adc_value_buf[adc_index] = value;
+
+ adc_max = gp2a->adc_value_buf[0];
+ adc_min = gp2a->adc_value_buf[0];
+
+ for (i = 0; i < ADC_BUFFER_NUM; i++) {
+ adc_total += gp2a->adc_value_buf[i];
+
+ if (adc_max < gp2a->adc_value_buf[i])
+ adc_max = gp2a->adc_value_buf[i];
+
+ if (adc_min > gp2a->adc_value_buf[i])
+ adc_min = gp2a->adc_value_buf[i];
+ }
+ adc_avr_value = (adc_total-(adc_max+adc_min))/(ADC_BUFFER_NUM-2);
+
+ if (gp2a->adc_index_count == ADC_BUFFER_NUM-1)
+ gp2a->adc_index_count = 0;
+
+ return adc_avr_value;
+}
+
+static void gp2a_work_func_light(struct work_struct *work)
+{
+ struct gp2a_data *gp2a = container_of(work, struct gp2a_data,
+ work_light);
+ int adc = 0;
+
+ mutex_lock(&gp2a->adc_lock);
+ adc = lightsensor_get_adcvalue(gp2a);
+ mutex_unlock(&gp2a->adc_lock);
+
+ 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;
+}
+
+/* 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;
+ 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->val_state) {
+ if (val)
+ setting = 0x40;
+ else
+ setting = 0x20;
+ gp2a_i2c_write(ip, REGS_HYS, &setting);
+ }
+
+ ip->val_state = 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);
+ 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 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;
+
+ mutex_lock(&gp2a->adc_lock);
+ adc = lightsensor_get_adcvalue(gp2a);
+ mutex_unlock(&gp2a->adc_lock);
+ return sprintf(buf, "%d\n", adc);
+}
+
+static DEVICE_ATTR(lightsensor_file_state, 0644, lightsensor_file_state_show,
+ NULL);
+
+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;
+
+ 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;
+ }
+
+ gp2a->val_state = 1;
+ 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_lock);
+ mutex_init(&gp2a->adc_lock);
+
+ 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(200 * 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);
+
+ /* 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 */
+ gp2a->pdev_gp2a_adc = platform_device_alloc("gp2a-adc", -1);
+ if (!gp2a->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(gp2a->pdev_gp2a_adc, NULL, NULL, 0);
+
+ if (IS_ERR(gp2a->padc)) {
+ dev_err(&gp2a->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_state) < 0)
+ pr_err("Failed to create device file(%s)!\n",
+ dev_attr_lightsensor_file_state.attr.name);
+
+ dev_set_drvdata(gp2a->switch_cmd_dev, gp2a);
+
+ /* set initial proximity value as 1 */
+ input_report_abs(gp2a->proximity_input_dev, ABS_DISTANCE, 1);
+ input_sync(gp2a->proximity_input_dev);
+
+ goto done;
+
+ /* error, unwind it all */
+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(gp2a->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, gp2a);
+ gpio_free(gp2a->pdata->p_out);
+err_setup_irq:
+ mutex_destroy(&gp2a->power_lock);
+ mutex_destroy(&gp2a->adc_lock);
+ 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 defined(CONFIG_MACH_Q1_BD)
+ if (gp2a->power_state == LIGHT_ENABLED)
+ gp2a->pdata->power(false);
+#endif
+ return 0;
+}
+
+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 defined(CONFIG_MACH_Q1_BD)
+ if (gp2a->power_state == LIGHT_ENABLED)
+ gp2a->pdata->power(true);
+#endif
+ 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(gp2a->pdev_gp2a_adc);
+ free_irq(gp2a->irq, gp2a);
+ gpio_free(gp2a->pdata->p_out);
+ if (gp2a->power_state) {
+ if (gp2a->power_state & LIGHT_ENABLED)
+ gp2a_light_disable(gp2a);
+#if defined(CONFIG_MACH_Q1_BD)
+ gp2a->pdata->power(false);
+#endif
+ }
+ destroy_workqueue(gp2a->wq);
+ mutex_destroy(&gp2a->power_lock);
+ mutex_destroy(&gp2a->adc_lock);
+ 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,
+};
+
+
+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/sensor/gp2a_light.c b/drivers/sensor/gp2a_light.c
new file mode 100644
index 0000000..8b20814
--- /dev/null
+++ b/drivers/sensor/gp2a_light.c
@@ -0,0 +1,732 @@
+/*
+ * Copyright (c) 2011 SAMSUNG
+ *
+ * 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.
+ *
+ * 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 Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/platform_device.h>
+#include <linux/sensor/gp2a.h>
+#include <linux/slab.h>
+#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
+#include <linux/sensor/sensors_core.h>
+
+/* for debugging */
+#undef DEBUG
+
+/*********** for debug ***************************/
+#if 1
+#define gprintk(fmt, x...) printk(KERN_INFO "%s(%d): " fmt\
+, __func__ , __LINE__, ## x)
+#else
+#define gprintk(x...) do { } while (0)
+#endif
+/***********************************************/
+
+#define SENSOR_NAME "light_sensor"
+#define SENSOR_MAX_DELAY (2000) /* 2000 ms */
+#define LIGHT_BUFFER_NUM 5
+
+struct sensor_data {
+ struct mutex mutex;
+ struct delayed_work work;
+ struct device *light_dev;
+ struct input_dev *input_dev;
+ struct workqueue_struct *wq;
+ int enabled;
+ int delay;
+ int light_buffer;
+ int light_count;
+};
+
+/* global var */
+static const int adc_table[4] = {
+ 15, /*15 lux */
+ 140, /* 150 lux */
+ 1490, /* 1500 lux */
+ 15000, /* 15000 lux */
+};
+
+static const int adc_table_030a[4] = {
+ 15, /*15 lux */
+ 150, /* 150 lux */
+ 1512, /* 1500 lux */
+ 14397, /* 15000 lux */
+};
+
+static struct platform_device *sensor_pdev;
+static bool first_value = true;
+u8 lightsensor_mode; /* 0 = low, 1 = high */
+
+/* prototype */
+static int lightsensor_get_adc(void);
+static int lightsensor_onoff(u8 onoff);
+
+/* Light Sysfs interface */
+static ssize_t lightsensor_file_state_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int adc = 0;
+
+ adc = lightsensor_get_adcvalue();
+
+ return sprintf(buf, "%d\n", adc);
+}
+
+static ssize_t
+light_delay_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct sensor_data *data = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", data->delay);
+}
+
+static ssize_t
+light_delay_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct sensor_data *data = dev_get_drvdata(dev);
+ int delay;
+ int err = 0;
+
+ err = kstrtoint(buf, 10, &delay);
+
+ if (err)
+ printk(KERN_ERR "%s, kstrtoint failed.", __func__);
+
+ if (delay < 0)
+ return count;
+
+ delay = delay / 1000000; /* ns to msec */
+
+ gprintk("new_delay = %d, old_delay = %d", delay, data->delay);
+
+ if (SENSOR_MAX_DELAY < delay)
+ delay = SENSOR_MAX_DELAY;
+
+ data->delay = delay;
+
+ mutex_lock(&data->mutex);
+
+ if (data->enabled) {
+ cancel_delayed_work_sync(&data->work);
+ queue_delayed_work(data->wq, &data->work,
+ msecs_to_jiffies(delay));
+ }
+
+ mutex_unlock(&data->mutex);
+
+ return count;
+}
+
+static ssize_t
+light_enable_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct sensor_data *data = dev_get_drvdata(dev);
+ int enabled;
+
+ enabled = data->enabled;
+
+ return sprintf(buf, "%d\n", enabled);
+}
+
+static ssize_t
+light_enable_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct sensor_data *data = dev_get_drvdata(dev);
+ int value;
+ int err = 0;
+
+ err = kstrtoint(buf, 10, &value);
+
+ if (err)
+ printk(KERN_ERR "%s, kstrtoint failed.", __func__);
+
+ gprintk("value = %d\n", value);
+
+ if (value != 0 && value != 1)
+ return count;
+
+ mutex_lock(&data->mutex);
+
+ if (data->enabled && !value) {
+ cancel_delayed_work_sync(&data->work);
+ gprintk("timer canceled.\n");
+ lightsensor_onoff(0);
+ data->enabled = value;
+ }
+ if (!data->enabled && value) {
+ lightsensor_onoff(1);
+ data->enabled = value;
+ first_value = true;
+ queue_delayed_work(data->wq, &data->work, 0);
+ gprintk("timer started.\n");
+ }
+
+ mutex_unlock(&data->mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(poll_delay, 0664, light_delay_show, light_delay_store);
+static DEVICE_ATTR(enable, 0664, light_enable_show, light_enable_store);
+static DEVICE_ATTR(lux, 0664, lightsensor_file_state_show, NULL);
+
+static struct attribute *lightsensor_attributes[] = {
+ &dev_attr_poll_delay.attr,
+ &dev_attr_enable.attr,
+ NULL
+};
+
+static struct attribute_group lightsensor_attribute_group = {
+ .attrs = lightsensor_attributes
+};
+
+static int lightsensor_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct sensor_data *data = platform_get_drvdata(pdev);
+ int rt = 0;
+
+ mutex_lock(&data->mutex);
+
+ if (data->enabled) {
+ rt = cancel_delayed_work_sync(&data->work);
+ gprintk(": The timer is cancled.\n");
+ }
+
+ mutex_unlock(&data->mutex);
+
+ return rt;
+}
+
+static int lightsensor_resume(struct platform_device *pdev)
+{
+ struct sensor_data *data = platform_get_drvdata(pdev);
+ int rt = 0;
+
+ data->light_count = 0;
+ data->light_buffer = 0;
+ first_value = true;
+
+ mutex_lock(&data->mutex);
+
+ if (data->enabled) {
+ rt = queue_delayed_work(data->wq, &data->work, 0);
+ gprintk(": The timer is started.\n");
+ }
+
+ mutex_unlock(&data->mutex);
+
+ return rt;
+}
+
+int lightsensor_get_adc(void)
+{
+ unsigned char get_data[4] = { 0, };
+ int D0_raw_data;
+ int D1_raw_data;
+ int D0_data;
+ int D1_data;
+ int lx = 0;
+ u8 value;
+ int light_alpha;
+ int light_beta;
+ static int lx_prev;
+ int ret = 0;
+ int d0_boundary = 93;
+
+ ret = opt_i2c_read(DATA0_LSB, get_data, sizeof(get_data));
+ if (ret < 0)
+ return lx_prev;
+ D0_raw_data = (get_data[1] << 8) | get_data[0]; /* clear */
+ D1_raw_data = (get_data[3] << 8) | get_data[2]; /* IR */
+ if (is_gp2a030a()) {
+ if (100 * D1_raw_data <= 41 * D0_raw_data) {
+ light_alpha = 736;
+ light_beta = 0;
+ } else if (100 * D1_raw_data <= 62 * D0_raw_data) {
+ light_alpha = 1855;
+ light_beta = 2693;
+ } else if (100 * D1_raw_data <= d0_boundary * D0_raw_data) {
+ light_alpha = 544;
+ light_beta = 595;
+ } else {
+ light_alpha = 0;
+ light_beta = 0;
+ }
+ } else {
+ if (lightsensor_mode) { /* HIGH_MODE */
+ if (100 * D1_raw_data <= 32 * D0_raw_data) {
+ light_alpha = 800;
+ light_beta = 0;
+ } else if (100 * D1_raw_data <= 67 * D0_raw_data) {
+ light_alpha = 2015;
+ light_beta = 2925;
+ } else if (100 * D1_raw_data <=
+ d0_boundary * D0_raw_data) {
+ light_alpha = 56;
+ light_beta = 12;
+ } else {
+ light_alpha = 0;
+ light_beta = 0;
+ }
+ } else { /* LOW_MODE */
+ if (100 * D1_raw_data <= 32 * D0_raw_data) {
+ light_alpha = 800;
+ light_beta = 0;
+ } else if (100 * D1_raw_data <= 67 * D0_raw_data) {
+ light_alpha = 2015;
+ light_beta = 2925;
+ } else if (100 * D1_raw_data <=
+ d0_boundary * D0_raw_data) {
+ light_alpha = 547;
+ light_beta = 599;
+ } else {
+ light_alpha = 0;
+ light_beta = 0;
+ }
+ }
+ }
+
+ if (lightsensor_mode) { /* HIGH_MODE */
+ D0_data = D0_raw_data * 16;
+ D1_data = D1_raw_data * 16;
+ } else { /* LOW_MODE */
+ D0_data = D0_raw_data;
+ D1_data = D1_raw_data;
+ }
+ if (is_gp2a030a()) {
+ if (D0_data < 3) {
+ lx = 0;
+#ifdef DEBUG
+ gprintk("lx is 0 : D0=%d, D1=%d\n", D0_raw_data,
+ D1_raw_data);
+#endif
+ } else if (lightsensor_mode == 0
+ && (D0_raw_data >= 16000 || D1_raw_data >= 16000)
+ && (D0_raw_data <= 16383 && D1_raw_data <= 16383)) {
+#ifdef DEBUG
+ gprintk("need to changed HIGH_MODE D0=%d, D1=%d\n",
+ D0_raw_data, D1_raw_data);
+#endif
+ lx = lx_prev;
+ } else if (100 * D1_data > d0_boundary * D0_data) {
+ lx = lx_prev;
+#ifdef DEBUG
+gprintk
+ ("Data range over so ues prev_lx value=%d D0=%d, D1=%d mode=%d\n",
+ lx, D0_data, D1_data, lightsensor_mode);
+#endif
+ return lx;
+ } else {
+ lx = (int)((light_alpha / 10 * D0_data * 33)
+ - (light_beta / 10 * D1_data * 33)) / 1000;
+#ifdef DEBUG
+ gprintk
+ ("D0=%d, D1=%d, lx=%d mode=%d a=%d, b=%d prev_lx=%d\n",
+ D0_raw_data, D1_raw_data, lx, lightsensor_mode,
+ light_alpha, light_beta, lx_prev);
+#endif
+ }
+ } else {
+ if ((D0_data == 0 || D1_data == 0)
+ && (D0_data < 300 && D1_data < 300)) {
+ lx = 0;
+#ifdef DEBUG
+ gprintk("lx is 0 : D0=%d, D1=%d\n", D0_raw_data,
+ D1_raw_data);
+#endif
+ } else if ((lightsensor_mode == 0)
+ && (D0_raw_data >= 16000 || D1_raw_data >= 16000)
+ && (D0_raw_data <= 16383 && D1_raw_data <= 16383)) {
+#ifdef DEBUG
+ gprintk("need to changed HIGH_MODE D0=%d, D1=%d\n",
+ D0_raw_data, D1_raw_data);
+#endif
+ lx = lx_prev;
+ } else if ((100 * D1_data > d0_boundary * D0_data)
+ || (100 * D1_data < 15 * D0_data)) {
+ lx = lx_prev;
+#ifdef DEBUG
+ gprintk
+ ("Data range over so ues prev_lx value=%d D0=%d, D1=%d mode=%d\n",
+ lx, D0_data, D1_data, lightsensor_mode);
+#endif
+ return lx;
+ } else {
+ lx = (int)((light_alpha / 10 * D0_data * 33)
+ - (light_beta / 10 * D1_data * 33)) / 1000;
+#ifdef DEBUG
+ gprintk
+ ("D0=%d, D1=%d, lx=%d mode=%d a=%d, b=%d prev_lx=%d\n",
+ D0_raw_data, D1_raw_data, lx, lightsensor_mode,
+ light_alpha, light_beta, lx_prev);
+#endif
+ }
+ }
+
+ lx_prev = lx;
+
+ if (lightsensor_mode) { /* HIGH MODE */
+ if (D0_raw_data < 1000) {
+#ifdef DEBUG
+ gprintk("change to LOW_MODE detection=%d\n",
+ proximity_sensor_detection);
+#endif
+ lightsensor_mode = 0; /* change to LOW MODE */
+
+ value = 0x0C;
+ opt_i2c_write(COMMAND1, &value);
+
+ if (proximity_sensor_detection)
+ value = 0x23;
+ else
+ value = 0x63;
+ opt_i2c_write(COMMAND2, &value);
+
+ if (proximity_enable)
+ value = 0xCC;
+ else
+ value = 0xDC;
+ opt_i2c_write(COMMAND1, &value);
+ }
+ } else { /* LOW MODE */
+ if (D0_raw_data > 16000 || D1_raw_data > 16000) {
+#ifdef DEBUG
+ gprintk("change to HIGH_MODE detection=%d\n",
+ proximity_sensor_detection);
+#endif
+ lightsensor_mode = 1; /* change to HIGH MODE */
+
+ value = 0x0C;
+ opt_i2c_write(COMMAND1, &value);
+
+ if (proximity_sensor_detection)
+ value = 0x27;
+ else
+ value = 0x67;
+ opt_i2c_write(COMMAND2, &value);
+
+ if (proximity_enable)
+ value = 0xCC;
+ else
+ value = 0xDC;
+ opt_i2c_write(COMMAND1, &value);
+ }
+ }
+
+ return lx;
+}
+
+int lightsensor_get_adcvalue(void)
+{
+ int i, j, value, adc_avr_value;
+ unsigned int adc_total = 0, adc_max, adc_min, adc_index;
+ static unsigned int adc_index_count;
+ static int adc_value_buf[ADC_BUFFER_NUM] = { 0, };
+
+ value = lightsensor_get_adc();
+
+ adc_index = (adc_index_count++) % ADC_BUFFER_NUM;
+
+ /*ADC buffer initialize (light sensor off -> light sensor on) */
+ if (first_value == true) {
+ for (j = 0; j < ADC_BUFFER_NUM; j++)
+ adc_value_buf[j] = value;
+ first_value = false;
+ } else {
+ adc_value_buf[adc_index] = value;
+ }
+
+ adc_max = adc_value_buf[0];
+ adc_min = adc_value_buf[0];
+
+ for (i = 0; i < ADC_BUFFER_NUM; i++) {
+ adc_total += adc_value_buf[i];
+
+ if (adc_max < adc_value_buf[i])
+ adc_max = adc_value_buf[i];
+
+ if (adc_min > adc_value_buf[i])
+ adc_min = adc_value_buf[i];
+ }
+ adc_avr_value =
+ (adc_total - (adc_max + adc_min)) / (ADC_BUFFER_NUM - 2);
+
+ if (adc_index_count == ADC_BUFFER_NUM - 1)
+ adc_index_count = 0;
+
+ return adc_avr_value;
+}
+
+static int lightsensor_onoff(u8 onoff)
+{
+ u8 value = 0;
+
+#ifdef DEBUG
+ gprintk("lightsensor_onoff = %d\n", onoff);
+ gprintk("proximity_enable onoff = %d\n", proximity_enable);
+#endif
+
+ if (onoff) {
+ /*in calling, must turn on proximity sensor */
+ if (proximity_enable == 0) {
+ value = 0x01;
+ opt_i2c_write(COMMAND4, &value);
+
+ value = 0x63;
+ opt_i2c_write(COMMAND2, &value);
+ /*OP3 : 1(operating mode) OP2 :1
+ (coutinuous operating mode)
+ OP1 : 01(ALS mode) TYPE=0(auto) */
+ value = 0xD0;
+ opt_i2c_write(COMMAND1, &value);
+ /* other setting have defualt value. */
+ }
+ } else {
+ /*in calling, must turn on proximity sensor */
+ if (proximity_enable == 0) {
+ value = 0x00; /*shutdown mode */
+ opt_i2c_write((u8) (COMMAND1), &value);
+ }
+ }
+
+ return 0;
+}
+
+static void gp2a_work_func_light(struct work_struct *work)
+{
+ struct sensor_data *data = container_of((struct delayed_work *)work,
+ struct sensor_data, work);
+ int i;
+ int adc = 0;
+
+ adc = lightsensor_get_adcvalue();
+
+ if (is_gp2a030a()) {
+ for (i = 0; ARRAY_SIZE(adc_table_030a); i++)
+ if (adc <= adc_table_030a[i])
+ break;
+ } else {
+ for (i = 0; ARRAY_SIZE(adc_table); i++)
+ if (adc <= adc_table[i])
+ break;
+ }
+
+ if (data->light_buffer == i) {
+ if (data->light_count++ == LIGHT_BUFFER_NUM) {
+ input_report_rel(data->input_dev, REL_MISC, adc);
+ input_sync(data->input_dev);
+ data->light_count = 0;
+ }
+ } else {
+ data->light_buffer = i;
+ data->light_count = 0;
+ }
+
+ if (data->enabled)
+ queue_delayed_work(data->wq, &data->work,
+ msecs_to_jiffies(data->delay));
+}
+
+static int lightsensor_probe(struct platform_device *pdev)
+{
+ struct sensor_data *data = NULL;
+ int rt = -ENXIO;
+ unsigned char get_data = 0;
+
+ /* Check I2C communication */
+ rt = opt_i2c_read(DATA0_LSB, &get_data, sizeof(get_data));
+
+ if (rt < 0) {
+ pr_err("%s failed : threre is no such device.\n", __func__);
+ return rt;
+ }
+
+ gprintk("probe start!\n");
+
+ data = kzalloc(sizeof(struct sensor_data), GFP_KERNEL);
+ if (!data) {
+ pr_err("%s: failed to alloc memory for module data\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ data->enabled = 0;
+
+ data->input_dev = input_allocate_device();
+ if (!data->input_dev) {
+ pr_err("%s: could not allocate input device\n", __func__);
+ rt = -ENOMEM;
+ goto err_input_allocate_device_light;
+ }
+
+ input_set_capability(data->input_dev, EV_REL, REL_MISC);
+ data->input_dev->name = SENSOR_NAME;
+
+ rt = input_register_device(data->input_dev);
+ if (rt) {
+ pr_err("%s: could not register input device\n", __func__);
+ input_free_device(data->input_dev);
+ goto err_input_register_device_light;
+ }
+ input_set_drvdata(data->input_dev, data);
+
+ rt = sysfs_create_group(&data->input_dev->dev.kobj,
+ &lightsensor_attribute_group);
+ if (rt) {
+ pr_err("%s: could not create sysfs group\n", __func__);
+ goto err_sysfs_create_group_light;
+ }
+ mutex_init(&data->mutex);
+
+ data->light_dev = sensors_classdev_register("light_sensor");
+ if (IS_ERR(data->light_dev)) {
+ pr_err("%s: could not create light_dev\n", __func__);
+ goto err_light_device_create;
+ }
+
+ if (device_create_file(data->light_dev, &dev_attr_lux) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_lux.attr.name);
+ goto err_light_device_create_file;
+ }
+ dev_set_drvdata(data->light_dev, data);
+
+ data->wq = create_singlethread_workqueue("gp2a_wq");
+ if (!data->wq) {
+ rt = -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_DELAYED_WORK(&data->work, gp2a_work_func_light);
+
+ /* set platdata */
+ platform_set_drvdata(pdev, data);
+
+ gprintk("probe success!\n");
+
+ goto done;
+
+/* error, unwind it all */
+err_create_workqueue:
+ device_remove_file(data->light_dev, &dev_attr_lux);
+err_light_device_create_file:
+ sensors_classdev_unregister(data->light_dev);
+err_light_device_create:
+ mutex_destroy(&data->mutex);
+ sysfs_remove_group(&data->input_dev->dev.kobj,
+ &lightsensor_attribute_group);
+err_sysfs_create_group_light:
+ input_unregister_device(data->input_dev);
+err_input_register_device_light:
+err_input_allocate_device_light:
+ kfree(data);
+
+done:
+ return rt;
+}
+
+static int lightsensor_remove(struct platform_device *pdev)
+{
+ struct sensor_data *data = platform_get_drvdata(pdev);
+ int rt = 0;
+
+ if (data != NULL) {
+ sysfs_remove_group(&data->input_dev->dev.kobj,
+ &lightsensor_attribute_group);
+
+ device_remove_file(data->light_dev, &dev_attr_lux);
+ sensors_classdev_unregister(data->light_dev);
+
+ cancel_delayed_work_sync(&data->work);
+ flush_workqueue(data->wq);
+ destroy_workqueue(data->wq);
+ input_unregister_device(data->input_dev);
+ mutex_destroy(&data->mutex);
+ kfree(data);
+ }
+
+ return rt;
+}
+
+static void lightsensor_shutdown(struct platform_device *pdev)
+{
+ struct sensor_data *data = platform_get_drvdata(pdev);
+
+ if (data != NULL) {
+ sysfs_remove_group(&data->input_dev->dev.kobj,
+ &lightsensor_attribute_group);
+ device_remove_file(data->light_dev, &dev_attr_lux);
+ sensors_classdev_unregister(data->light_dev);
+
+ cancel_delayed_work_sync(&data->work);
+ flush_workqueue(data->wq);
+ destroy_workqueue(data->wq);
+ input_unregister_device(data->input_dev);
+ kfree(data);
+ }
+}
+
+/*
+ * Module init and exit
+ */
+static struct platform_driver lightsensor_driver = {
+ .probe = lightsensor_probe,
+ .remove = lightsensor_remove,
+ .suspend = lightsensor_suspend,
+ .resume = lightsensor_resume,
+ .shutdown = lightsensor_shutdown,
+ .driver = {
+ .name = SENSOR_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init lightsensor_init(void)
+{
+ sensor_pdev = platform_device_register_simple(SENSOR_NAME, 0, NULL, 0);
+ if (IS_ERR(sensor_pdev))
+ return -1;
+
+ return platform_driver_register(&lightsensor_driver);
+}
+
+module_init(lightsensor_init);
+
+static void __exit lightsensor_exit(void)
+{
+ platform_driver_unregister(&lightsensor_driver);
+ platform_device_unregister(sensor_pdev);
+}
+
+module_exit(lightsensor_exit);
+
+MODULE_AUTHOR("SAMSUNG");
+MODULE_DESCRIPTION("Optical Sensor driver for GP2AP020A00F");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sensor/gp2a_proximity.c b/drivers/sensor/gp2a_proximity.c
new file mode 100644
index 0000000..8ac0d2a
--- /dev/null
+++ b/drivers/sensor/gp2a_proximity.c
@@ -0,0 +1,863 @@
+/*
+ * Copyright (c) 2011 SAMSUNG
+ *
+ * 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.
+ *
+ * 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 Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/i2c.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/miscdevice.h>
+#include <linux/platform_device.h>
+#include <linux/leds.h>
+#include <linux/gpio.h>
+#include <mach/hardware.h>
+#include <linux/wakelock.h>
+#include <linux/input.h>
+#include <linux/workqueue.h>
+#include <linux/uaccess.h>
+#include <linux/sensor/gp2a.h>
+#include <linux/slab.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gpio.h>
+#include <mach/gpio-midas.h>
+#include <linux/sensor/sensors_core.h>
+
+/*********** for debug *******************************/
+#undef DEBUG
+
+#if 1
+#define gprintk(fmt, x...) printk(KERN_INFO "%s(%d): "\
+fmt, __func__ , __LINE__, ## x)
+#else
+#define gprintk(x...) do { } while (0)
+#endif
+/**************************************************/
+#define PROX_READ_NUM 40
+
+/* global var */
+static struct i2c_driver opt_i2c_driver;
+static struct i2c_client *opt_i2c_client;
+
+int proximity_enable;
+char proximity_sensor_detection;
+static char proximity_avg_on;
+
+struct gp2a_data {
+ struct input_dev *input_dev;
+ struct work_struct work; /* for proximity sensor */
+ struct device *proximity_dev;
+ struct gp2a_platform_data *pdata;
+ struct wake_lock prx_wake_lock;
+ struct hrtimer prox_timer;
+ struct workqueue_struct *prox_wq;
+ struct work_struct work_prox;
+ int enabled;
+ int proximity_data;
+ int irq;
+ int average[3]; /*for proximity adc average */
+ ktime_t prox_poll_delay;
+};
+
+/* initial value for sensor register */
+#define COL 8
+static u8 gp2a_original_image_030a[COL][2] = {
+ /* {Regster, Value} */
+ /*PRST :01(4 cycle at Detection/Non-detection),
+ ALSresolution :16bit, range *128 //0x1F -> 5F by sharp */
+ {0x01, 0x63},
+ /*ALC : 0, INTTYPE : 1, PS mode resolution : 12bit, range*1 */
+ {0x02, 0x1A},
+ /*LED drive current 110mA, Detection/Non-detection judgment output */
+ {0x03, 0x3C},
+ /* {0x04 , 0x00}, */
+ /* {0x05 , 0x00}, */
+ /* {0x06 , 0xFF}, */
+ /* {0x07 , 0xFF}, */
+ {0x08, 0x09}, /*PS mode LTH(Loff): (??mm) */
+ {0x09, 0x00}, /*PS mode LTH(Loff) : */
+ {0x0A, 0x0A}, /*PS mode HTH(Lon) : (??mm) */
+ {0x0B, 0x00}, /* PS mode HTH(Lon) : */
+ /* {0x13 , 0x08}, by sharp for internal calculation (type:0) */
+ /*alternating mode (PS+ALS), TYPE=1
+ (0:externel 1:auto calculated mode) //umfa.cal */
+ {0x00, 0xC0}
+};
+
+static u8 gp2a_original_image[COL][2] = {
+ /* {Regster, Value} */
+ /*PRST :01(4 cycle at Detection/Non-detection),
+ ALSresolution :16bit, range *128 //0x1F -> 5F by sharp */
+ {0x01, 0x63},
+ /*ALC : 0, INTTYPE : 1, PS mode resolution : 12bit, range*1 */
+ {0x02, 0x72},
+ /*LED drive current 110mA, Detection/Non-detection judgment output */
+ {0x03, 0x3C},
+ /* {0x04 , 0x00}, */
+ /* {0x05 , 0x00}, */
+ /* {0x06 , 0xFF}, */
+ /* {0x07 , 0xFF}, */
+ {0x08, 0x07}, /*PS mode LTH(Loff): (??mm) */
+ {0x09, 0x00}, /*PS mode LTH(Loff) : */
+ {0x0A, 0x08}, /*PS mode HTH(Lon) : (??mm) */
+ {0x0B, 0x00}, /* PS mode HTH(Lon) : */
+ /* {0x13 , 0x08}, by sharp for internal calculation (type:0) */
+ /*alternating mode (PS+ALS), TYPE=1
+ (0:externel 1:auto calculated mode) //umfa.cal */
+ {0x00, 0xC0}
+};
+
+static int proximity_onoff(u8 onoff);
+
+int is_gp2a030a(void)
+{
+#if defined(CONFIG_MACH_C1) || defined(CONFIG_MACH_C1VZW) || \
+ defined(CONFIG_MACH_M0) || defined(CONFIG_MACH_M3)
+ return (system_rev != 0 && system_rev != 3);
+#endif
+ return 0;
+}
+/* Proximity Sysfs interface */
+static ssize_t
+proximity_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gp2a_data *data = dev_get_drvdata(dev);
+ int enabled;
+
+ enabled = data->enabled;
+
+ return sprintf(buf, "%d\n", enabled);
+}
+
+static ssize_t
+proximity_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct gp2a_data *data = dev_get_drvdata(dev);
+ int value = 0;
+ char input;
+ int err = 0;
+
+ err = kstrtoint(buf, 10, &value);
+
+ if (err)
+ printk(KERN_ERR "%s, kstrtoint failed.", __func__);
+
+ if (value != 0 && value != 1)
+ return count;
+
+ gprintk("value = %d\n", value);
+
+ if (data->enabled && !value) { /* Proximity power off */
+ disable_irq(data->irq);
+
+ proximity_enable = value;
+ proximity_onoff(0);
+ disable_irq_wake(data->irq);
+#ifndef CONFIG_MACH_MIDAS_02_BD
+ data->pdata->gp2a_led_on(false);
+#endif
+ } else if (!data->enabled && value) { /* proximity power on */
+ data->pdata->gp2a_led_on(true);
+ /*msleep(1); */
+
+ proximity_enable = value;
+ proximity_onoff(1);
+ enable_irq_wake(data->irq);
+ msleep(160);
+
+ input = gpio_get_value(data->pdata->p_out);
+ input_report_abs(data->input_dev, ABS_DISTANCE, input);
+ input_sync(data->input_dev);
+
+ enable_irq(data->irq);
+ }
+ data->enabled = value;
+
+ return count;
+}
+
+static ssize_t proximity_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int D2_data = 0;
+ unsigned char get_D2_data[2] = { 0, };
+
+ msleep(20);
+ opt_i2c_read(0x10, get_D2_data, sizeof(get_D2_data));
+ D2_data = (get_D2_data[1] << 8) | get_D2_data[0];
+
+ return sprintf(buf, "%d\n", D2_data);
+}
+
+static ssize_t proximity_avg_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gp2a_data *data = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d, %d, %d\n"\
+ , data->average[0], data->average[1], data->average[2]);
+}
+
+static ssize_t proximity_avg_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct gp2a_data *data = 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;
+ }
+
+ if (new_value && !proximity_avg_on) {
+ if (!(proximity_enable)) {
+ /*data->pdata->gp2a_led_on(true);*/
+ proximity_onoff(1);
+ }
+ hrtimer_start(&data->prox_timer, data->prox_poll_delay,
+ HRTIMER_MODE_REL);
+ proximity_avg_on = 1;
+ } else if (!new_value && proximity_avg_on) {
+ int i;
+
+ cancel_work_sync(&data->work_prox);
+ hrtimer_cancel(&data->prox_timer);
+ proximity_avg_on = 0;
+
+ if (!(proximity_enable)) {
+ proximity_onoff(0);
+ /*data->pdata->gp2a_led_on(false);*/
+ }
+
+ for (i = 0 ; i < 3 ; i++)
+ data->average[i] = 0;
+ }
+
+ return size;
+}
+
+static DEVICE_ATTR(enable, 0664, proximity_enable_show, proximity_enable_store);
+static DEVICE_ATTR(prox_avg, 0664, proximity_avg_show, proximity_avg_store);
+static DEVICE_ATTR(state, 0664, proximity_state_show, NULL);
+
+static struct attribute *proximity_attributes[] = {
+ &dev_attr_enable.attr,
+ &dev_attr_state.attr,
+ NULL
+};
+
+static struct attribute_group proximity_attribute_group = {
+ .attrs = proximity_attributes
+};
+
+static void proxsensor_get_avgvalue(struct gp2a_data *data)
+{
+ int min = 0, max = 0, avg = 0;
+ int i;
+ u8 proximity_value = 0;
+ unsigned char get_D2_data[2] = { 0, };
+
+ for (i = 0; i < PROX_READ_NUM; i++) {
+ msleep(20);
+ opt_i2c_read(0x10, get_D2_data, sizeof(get_D2_data));
+ proximity_value = (get_D2_data[1] << 8) | get_D2_data[0];
+ avg += proximity_value;
+
+ if (!i)
+ min = proximity_value;
+ else if (proximity_value < min)
+ min = proximity_value;
+
+ if (proximity_value > max)
+ max = proximity_value;
+ }
+ avg /= PROX_READ_NUM;
+
+ data->average[0] = min;
+ data->average[1] = avg;
+ data->average[2] = max;
+}
+
+static void gp2a_work_func_prox_avg(struct work_struct *work)
+{
+ struct gp2a_data *data = container_of(work, struct gp2a_data,
+ work_prox);
+ proxsensor_get_avgvalue(data);
+}
+
+static enum hrtimer_restart gp2a_prox_timer_func(struct hrtimer *timer)
+{
+ struct gp2a_data *data
+ = container_of(timer, struct gp2a_data, prox_timer);
+ queue_work(data->prox_wq, &data->work_prox);
+ hrtimer_forward_now(&data->prox_timer, data->prox_poll_delay);
+ return HRTIMER_RESTART;
+}
+
+irqreturn_t gp2a_irq_handler(int irq, void *gp2a_data_p)
+{
+ struct gp2a_data *data = gp2a_data_p;
+
+ wake_lock_timeout(&data->prx_wake_lock, 3 * HZ);
+
+ schedule_work(&data->work);
+
+ gprintk("[PROXIMITY] IRQ_HANDLED.\n");
+ return IRQ_HANDLED;
+}
+
+/***************************************************
+ *
+ * function : gp2a_setup_irq
+ * description : This function sets up the interrupt.
+ *
+ */
+static int gp2a_setup_irq(struct gp2a_data *gp2a)
+{
+ int rc = -EIO;
+ struct gp2a_platform_data *pdata = gp2a->pdata;
+ int irq;
+
+ pr_err("%s, start\n", __func__);
+
+ 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;
+
+ pr_err("%s, success\n", __func__);
+
+ goto done;
+
+err_request_irq:
+err_gpio_direction_input:
+ gpio_free(pdata->p_out);
+done:
+ return rc;
+}
+
+/***************************************************
+ *
+ * function : gp2a_work_func_prox
+ * description : This function is for proximity sensor work function.
+ * when INT signal is occured , it gets value the interrupt pin.
+ */
+static void gp2a_work_func_prox(struct work_struct *work)
+{
+ struct gp2a_data *gp2a = container_of((struct work_struct *)work,
+ struct gp2a_data, work);
+
+ unsigned char value;
+ char result;
+ int ret;
+
+ /* 0 : proximity, 1 : away */
+ result = gpio_get_value(gp2a->pdata->p_out);
+ proximity_sensor_detection = !result;
+
+ input_report_abs(gp2a->input_dev, ABS_DISTANCE, result);
+ input_sync(gp2a->input_dev);
+ gprintk("Proximity value = %d\n", result);
+
+ disable_irq(gp2a->irq);
+
+ value = 0x0C;
+ ret = opt_i2c_write(COMMAND1, &value); /*Software reset */
+
+ if (result == 0) { /* detection = Falling Edge */
+ if (lightsensor_mode == 0) /* Low mode */
+ value = 0x23;
+ else /* High mode */
+ value = 0x27;
+ ret = opt_i2c_write(COMMAND2, &value);
+ } else { /* none Detection */
+ if (lightsensor_mode == 0) /* Low mode */
+ value = 0x63;
+ else /* High mode */
+ value = 0x67;
+ ret = opt_i2c_write(COMMAND2, &value);
+ }
+
+ enable_irq(gp2a->irq);
+
+ value = 0xCC;
+ ret = opt_i2c_write(COMMAND1, &value);
+
+ gp2a->proximity_data = result;
+#ifdef DEBUG
+ gprintk("proximity = %d, lightsensor_mode=%d\n",
+ result, lightsensor_mode);
+#endif
+}
+
+static int opt_i2c_init(void)
+{
+ if (i2c_add_driver(&opt_i2c_driver)) {
+ printk(KERN_ERR "i2c_add_driver failed\n");
+ return -ENODEV;
+ }
+ return 0;
+}
+
+int opt_i2c_read(u8 reg, unsigned char *rbuf, int len)
+{
+ int ret = -1;
+ struct i2c_msg msg;
+ /*int i;*/
+
+ if ((opt_i2c_client == NULL) || (!opt_i2c_client->adapter)) {
+ printk(KERN_ERR "%s %d (opt_i2c_client == NULL)\n",
+ __func__, __LINE__);
+ return -ENODEV;
+ }
+
+ /*gprintk("register num : 0x%x\n", reg); */
+
+ msg.addr = opt_i2c_client->addr;
+ msg.flags = I2C_M_WR;
+ msg.len = 1;
+ msg.buf = &reg;
+
+ ret = i2c_transfer(opt_i2c_client->adapter, &msg, 1);
+
+ if (ret >= 0) {
+ msg.flags = I2c_M_RD;
+ msg.len = len;
+ msg.buf = rbuf;
+ ret = i2c_transfer(opt_i2c_client->adapter, &msg, 1);
+ }
+
+ if (ret < 0)
+ printk(KERN_ERR "%s, i2c transfer error ret=%d\n"\
+ , __func__, ret);
+
+ /* for (i=0;i<len;i++)
+ gprintk("0x%x, 0x%x\n", reg++,rbuf[i]); */
+
+ return ret;
+}
+
+int opt_i2c_write(u8 reg, u8 *val)
+{
+ int err = 0;
+ struct i2c_msg msg[1];
+ unsigned char data[2];
+ int retry = 3;
+
+ if ((opt_i2c_client == NULL) || (!opt_i2c_client->adapter))
+ return -ENODEV;
+
+ while (retry--) {
+ data[0] = reg;
+ data[1] = *val;
+
+ msg->addr = opt_i2c_client->addr;
+ msg->flags = I2C_M_WR;
+ msg->len = 2;
+ msg->buf = data;
+
+ err = i2c_transfer(opt_i2c_client->adapter, msg, 1);
+ /*gprintk("0x%x, 0x%x\n", reg, *val); */
+
+ if (err >= 0)
+ return 0;
+ }
+ printk(KERN_ERR "%s, i2c transfer error(%d)\n", __func__, err);
+ return err;
+}
+
+static int proximity_input_init(struct gp2a_data *data)
+{
+ int err = 0;
+
+ gprintk("start\n");
+
+ data->input_dev = input_allocate_device();
+ if (!data->input_dev) {
+ printk(KERN_ERR "%s, error\n", __func__);
+ return -ENOMEM;
+ }
+
+ input_set_capability(data->input_dev, EV_ABS, ABS_DISTANCE);
+ input_set_abs_params(data->input_dev, ABS_DISTANCE, 0, 1, 0, 0);
+
+ data->input_dev->name = "proximity_sensor";
+ input_set_drvdata(data->input_dev, data);
+
+ err = input_register_device(data->input_dev);
+ if (err < 0) {
+ input_free_device(data->input_dev);
+ return err;
+ }
+
+ gprintk("success\n");
+ return 0;
+}
+
+static int gp2a_opt_probe(struct platform_device *pdev)
+{
+ struct gp2a_data *gp2a;
+ struct gp2a_platform_data *pdata = pdev->dev.platform_data;
+ u8 value = 0;
+ int err = 0;
+
+ gprintk("probe start!\n");
+
+ if (!pdata) {
+ pr_err("%s: missing pdata!\n", __func__);
+ return err;
+ }
+
+ if (!pdata->gp2a_led_on) {
+ pr_err("%s: incomplete pdata!\n", __func__);
+ return err;
+ }
+ /* gp2a power on */
+ pdata->gp2a_led_on(true);
+
+ /* allocate driver_data */
+ gp2a = kzalloc(sizeof(struct gp2a_data), GFP_KERNEL);
+ if (!gp2a) {
+ pr_err("kzalloc error\n");
+ return -ENOMEM;
+ }
+
+ proximity_enable = 0;
+ proximity_sensor_detection = 0;
+ proximity_avg_on = 0;
+ gp2a->enabled = 0;
+ gp2a->pdata = pdata;
+
+ /* prox_timer settings. we poll for prox_avg values using a timer. */
+ hrtimer_init(&gp2a->prox_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ gp2a->prox_poll_delay = ns_to_ktime(2000 * NSEC_PER_MSEC);
+ gp2a->prox_timer.function = gp2a_prox_timer_func;
+
+ gp2a->prox_wq = create_singlethread_workqueue("gp2a_prox_wq");
+ if (!gp2a->prox_wq) {
+ err = -ENOMEM;
+ pr_err("%s: could not create prox workqueue\n", __func__);
+ goto err_create_prox_workqueue;
+ }
+
+ INIT_WORK(&gp2a->work_prox, gp2a_work_func_prox_avg);
+ INIT_WORK(&gp2a->work, gp2a_work_func_prox);
+
+ err = proximity_input_init(gp2a);
+ if (err < 0)
+ goto error_setup_reg;
+
+ err = sysfs_create_group(&gp2a->input_dev->dev.kobj,
+ &proximity_attribute_group);
+ if (err < 0)
+ goto err_sysfs_create_group_proximity;
+
+ /* set platdata */
+ platform_set_drvdata(pdev, gp2a);
+
+ /* wake lock init */
+ wake_lock_init(&gp2a->prx_wake_lock, WAKE_LOCK_SUSPEND,
+ "prx_wake_lock");
+
+ /* init i2c */
+ opt_i2c_init();
+
+ if (opt_i2c_client == NULL) {
+ pr_err("opt_probe failed : i2c_client is NULL\n");
+ goto err_no_device;
+ } else
+ printk(KERN_INFO "opt_i2c_client : (0x%p), address = %x\n",
+ opt_i2c_client, opt_i2c_client->addr);
+
+ /* GP2A Regs INIT SETTINGS and Check I2C communication */
+ value = 0x00;
+ /* shutdown mode op[3]=0 */
+ err = opt_i2c_write((u8) (COMMAND1), &value);
+
+ if (err < 0) {
+ pr_err("%s failed : threre is no such device.\n", __func__);
+ goto err_no_device;
+ }
+
+ /* Setup irq */
+ err = gp2a_setup_irq(gp2a);
+ if (err) {
+ pr_err("%s: could not setup irq\n", __func__);
+ goto err_setup_irq;
+ }
+
+ /* set sysfs for proximity sensor */
+ gp2a->proximity_dev = sensors_classdev_register("proximity_sensor");
+ if (IS_ERR(gp2a->proximity_dev)) {
+ pr_err("%s: could not create proximity_dev\n", __func__);
+ goto err_proximity_device_create;
+ }
+
+ if (device_create_file(gp2a->proximity_dev, &dev_attr_state) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_state.attr.name);
+ goto err_proximity_device_create_file1;
+ }
+
+ if (device_create_file(gp2a->proximity_dev, &dev_attr_prox_avg) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_prox_avg.attr.name);
+ goto err_proximity_device_create_file2;
+ }
+ dev_set_drvdata(gp2a->proximity_dev, gp2a);
+
+ device_init_wakeup(&pdev->dev, 1);
+
+ gprintk("probe success!\n");
+
+ return 0;
+
+err_proximity_device_create_file2:
+ device_remove_file(gp2a->proximity_dev, &dev_attr_state);
+err_proximity_device_create_file1:
+ sensors_classdev_unregister(gp2a->proximity_dev);
+err_proximity_device_create:
+ gpio_free(pdata->p_out);
+err_setup_irq:
+err_no_device:
+ sysfs_remove_group(&gp2a->input_dev->dev.kobj,
+ &proximity_attribute_group);
+ wake_lock_destroy(&gp2a->prx_wake_lock);
+err_sysfs_create_group_proximity:
+ input_unregister_device(gp2a->input_dev);
+error_setup_reg:
+ destroy_workqueue(gp2a->prox_wq);
+err_create_prox_workqueue:
+ kfree(gp2a);
+ return err;
+}
+
+static int gp2a_opt_remove(struct platform_device *pdev)
+{
+ struct gp2a_data *gp2a = platform_get_drvdata(pdev);
+
+ if (gp2a == NULL) {
+ printk(KERN_ERR "%s, gp2a_data is NULL!!!!!\n", __func__);
+ return -1;
+ }
+
+ device_remove_file(gp2a->proximity_dev, &dev_attr_prox_avg);
+ device_remove_file(gp2a->proximity_dev, &dev_attr_state);
+ sensors_classdev_unregister(gp2a->proximity_dev);
+
+ if (gp2a->input_dev != NULL) {
+ sysfs_remove_group(&gp2a->input_dev->dev.kobj,
+ &proximity_attribute_group);
+ input_unregister_device(gp2a->input_dev);
+ if (gp2a->input_dev != NULL)
+ kfree(gp2a->input_dev);
+ }
+
+ destroy_workqueue(gp2a->prox_wq);
+ wake_lock_destroy(&gp2a->prx_wake_lock);
+ device_init_wakeup(&pdev->dev, 0);
+ free_irq(gp2a->irq, gp2a);
+ gpio_free(gp2a->pdata->p_out);
+ kfree(gp2a);
+
+ return 0;
+}
+
+static int gp2a_opt_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct gp2a_data *gp2a = platform_get_drvdata(pdev);
+
+ gprintk("\n");
+
+ if (gp2a->enabled) {
+ if (device_may_wakeup(&pdev->dev))
+ enable_irq_wake(gp2a->irq);
+ }
+
+ if (proximity_avg_on) {
+ cancel_work_sync(&gp2a->work_prox);
+ hrtimer_cancel(&gp2a->prox_timer);
+ }
+
+ return 0;
+}
+
+static int gp2a_opt_resume(struct platform_device *pdev)
+{
+ struct gp2a_data *gp2a = platform_get_drvdata(pdev);
+
+ gprintk("\n");
+
+ if (gp2a->enabled) {
+ if (device_may_wakeup(&pdev->dev))
+ enable_irq_wake(gp2a->irq);
+ }
+
+ if (proximity_avg_on)
+ hrtimer_start(&gp2a->prox_timer, gp2a->prox_poll_delay,
+ HRTIMER_MODE_REL);
+
+ return 0;
+}
+
+static int proximity_onoff(u8 onoff)
+{
+ u8 value;
+ int i;
+ int err = 0;
+
+#ifdef DEBUG
+ gprintk("proximity turn on/off = %d\n", onoff);
+#endif
+
+ /* already on light sensor, so must simultaneously
+ turn on light sensor and proximity sensor */
+ if (onoff) {
+ for (i = 0; i < COL; i++) {
+ if (is_gp2a030a())
+ err =
+ opt_i2c_write(gp2a_original_image_030a[i][0]
+ , &gp2a_original_image_030a[i][1]);
+ else
+ err =
+ opt_i2c_write(gp2a_original_image[i][0],
+ &gp2a_original_image[i][1]);
+ if (err < 0)
+ printk(KERN_ERR
+ "%s : turnning on error i = %d, err=%d\n",
+ __func__, i, err);
+ lightsensor_mode = 0;
+ }
+ } else { /* light sensor turn on and proximity turn off */
+ if (lightsensor_mode)
+ value = 0x67; /*resolution :16bit, range: *8(HIGH) */
+ else
+ value = 0x63; /* resolution :16bit, range: *128(LOW) */
+ opt_i2c_write(COMMAND2, &value);
+ /* OP3 : 1(operating mode)
+ OP2 :1(coutinuous operating mode) OP1 : 01(ALS mode) */
+ value = 0xD0;
+ opt_i2c_write(COMMAND1, &value);
+ }
+
+ return 0;
+}
+
+static int opt_i2c_remove(struct i2c_client *client)
+{
+ kfree(client);
+ opt_i2c_client = NULL;
+
+ return 0;
+}
+
+static int opt_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ gprintk("start!!!\n");
+
+ if (client == NULL)
+ printk(KERN_ERR "GP2A i2c client is NULL !!!\n");
+
+ opt_i2c_client = client;
+ gprintk("end!!!\n");
+
+ return 0;
+}
+
+static const struct i2c_device_id opt_device_id[] = {
+ {"gp2a", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, opt_device_id);
+
+static struct i2c_driver opt_i2c_driver = {
+ .driver = {
+ .name = "gp2a",
+ .owner = THIS_MODULE,
+ },
+ .probe = opt_i2c_probe,
+ .remove = opt_i2c_remove,
+ .id_table = opt_device_id,
+};
+
+static struct platform_driver gp2a_opt_driver = {
+ .probe = gp2a_opt_probe,
+ .remove = gp2a_opt_remove,
+ .suspend = gp2a_opt_suspend,
+ .resume = gp2a_opt_resume,
+ .driver = {
+ .name = "gp2a-opt",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init gp2a_opt_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&gp2a_opt_driver);
+ return ret;
+}
+
+static void __exit gp2a_opt_exit(void)
+{
+ platform_driver_unregister(&gp2a_opt_driver);
+}
+
+module_init(gp2a_opt_init);
+module_exit(gp2a_opt_exit);
+
+MODULE_AUTHOR("SAMSUNG");
+MODULE_DESCRIPTION("Optical Sensor driver for GP2AP020A00F");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sensor/k3dh.c b/drivers/sensor/k3dh.c
new file mode 100644
index 0000000..0d3f0da
--- /dev/null
+++ b/drivers/sensor/k3dh.c
@@ -0,0 +1,803 @@
+/*
+ * STMicroelectronics k3dh acceleration sensor driver
+ *
+ * Copyright (C) 2010 Samsung Electronics Co.Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/sensor/sensors_core.h>
+#include <linux/sensor/k3dh.h>
+#include "k3dh_reg.h"
+
+/* For Debugging */
+#if 1
+#define k3dh_dbgmsg(str, args...) pr_debug("%s: " str, __func__, ##args)
+#endif
+#define k3dh_infomsg(str, args...) pr_info("%s: " str, __func__, ##args)
+
+#define VENDOR "STM"
+#define CHIP_ID "K3DH"
+
+/* The default settings when sensor is on is for all 3 axis to be enabled
+ * and output data rate set to 400Hz. Output is via a ioctl read call.
+ */
+#define DEFAULT_POWER_ON_SETTING (ODR400 | ENABLE_ALL_AXES)
+#define ACC_DEV_MAJOR 241
+
+#define CALIBRATION_FILE_PATH "/efs/calibration_data"
+#define CAL_DATA_AMOUNT 20
+
+static const struct odr_delay {
+ u8 odr; /* odr reg setting */
+ s64 delay_ns; /* odr in ns */
+} odr_delay_table[] = {
+ { ODR1344, 744047LL }, /* 1344Hz */
+ { ODR400, 2500000LL }, /* 400Hz */
+ { ODR200, 5000000LL }, /* 200Hz */
+ { ODR100, 10000000LL }, /* 100Hz */
+ { ODR50, 20000000LL }, /* 50Hz */
+ { ODR25, 40000000LL }, /* 25Hz */
+ { ODR10, 100000000LL }, /* 10Hz */
+ { ODR1, 1000000000LL }, /* 1Hz */
+};
+
+/* K3DH acceleration data */
+struct k3dh_acc {
+ s16 x;
+ s16 y;
+ s16 z;
+};
+
+struct k3dh_data {
+ struct i2c_client *client;
+ struct miscdevice k3dh_device;
+ struct mutex read_lock;
+ struct mutex write_lock;
+ struct completion data_ready;
+#ifdef CONFIG_MACH_U1
+ struct class *acc_class;
+#else
+ struct device *dev;
+#endif
+ struct k3dh_acc cal_data;
+ struct k3dh_acc acc_xyz;
+ u8 ctrl_reg1_shadow;
+ atomic_t opened; /* opened implies enabled */
+};
+
+/* Read X,Y and Z-axis acceleration raw data */
+static int k3dh_read_accel_raw_xyz(struct k3dh_data *data,
+ struct k3dh_acc *acc)
+{
+ int err;
+ s8 reg = OUT_X_L | AC; /* read from OUT_X_L to OUT_Z_H by auto-inc */
+ u8 acc_data[6];
+
+ err = i2c_smbus_read_i2c_block_data(data->client, reg,
+ sizeof(acc_data), acc_data);
+ if (err != sizeof(acc_data)) {
+ pr_err("%s : failed to read 6 bytes for getting x/y/z\n",
+ __func__);
+ return -EIO;
+ }
+
+ acc->x = (acc_data[1] << 8) | acc_data[0];
+ acc->y = (acc_data[3] << 8) | acc_data[2];
+ acc->z = (acc_data[5] << 8) | acc_data[4];
+
+ acc->x = acc->x >> 4;
+ acc->y = acc->y >> 4;
+ #if defined(CONFIG_MACH_U1_NA_SPR_REV05) \
+ || defined(CONFIG_MACH_U1_NA_SPR_EPIC2_REV00) \
+ || defined(CONFIG_MACH_U1_NA_USCC_REV05) \
+ || defined(CONFIG_MACH_Q1_BD)
+ acc->z = -acc->z >> 4;
+ #else
+ acc->z = acc->z >> 4;
+ #endif
+
+ return 0;
+}
+
+static int k3dh_read_accel_xyz(struct k3dh_data *data,
+ struct k3dh_acc *acc)
+{
+ int err = 0;
+
+ mutex_lock(&data->read_lock);
+ err = k3dh_read_accel_raw_xyz(data, acc);
+ mutex_unlock(&data->read_lock);
+ if (err < 0) {
+ pr_err("%s: k3dh_read_accel_raw_xyz() failed\n", __func__);
+ return err;
+ }
+
+ acc->x -= data->cal_data.x;
+ acc->y -= data->cal_data.y;
+ acc->z -= data->cal_data.z;
+
+ return err;
+}
+
+static int k3dh_open_calibration(struct k3dh_data *data)
+{
+ struct file *cal_filp = NULL;
+ int err = 0;
+ mm_segment_t old_fs;
+
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+
+ cal_filp = filp_open(CALIBRATION_FILE_PATH, O_RDONLY, 0666);
+ if (IS_ERR(cal_filp)) {
+ err = PTR_ERR(cal_filp);
+ if (err != -ENOENT)
+ pr_err("%s: Can't open calibration file\n", __func__);
+ set_fs(old_fs);
+ return err;
+ }
+
+ err = cal_filp->f_op->read(cal_filp,
+ (char *)&data->cal_data, 3 * sizeof(s16), &cal_filp->f_pos);
+ if (err != 3 * sizeof(s16)) {
+ pr_err("%s: Can't read the cal data from file\n", __func__);
+ err = -EIO;
+ }
+
+ k3dh_dbgmsg("%s: (%u,%u,%u)\n", __func__,
+ data->cal_data.x, data->cal_data.y, data->cal_data.z);
+
+ filp_close(cal_filp, current->files);
+ set_fs(old_fs);
+
+ return err;
+}
+
+static int k3dh_do_calibrate(struct device *dev, bool do_calib)
+{
+ struct k3dh_data *acc_data = dev_get_drvdata(dev);
+ struct k3dh_acc data = { 0, };
+ struct file *cal_filp = NULL;
+ int sum[3] = { 0, };
+ int err = 0;
+ int i;
+ mm_segment_t old_fs;
+
+ if (do_calib) {
+ for (i = 0; i < CAL_DATA_AMOUNT; i++) {
+ mutex_lock(&acc_data->read_lock);
+ err = k3dh_read_accel_raw_xyz(acc_data, &data);
+ mutex_unlock(&acc_data->read_lock);
+ if (err < 0) {
+ pr_err("%s: k3dh_read_accel_raw_xyz() "
+ "failed in the %dth loop\n",
+ __func__, i);
+ return err;
+ }
+
+ sum[0] += data.x;
+ sum[1] += data.y;
+ sum[2] += data.z;
+ }
+
+ acc_data->cal_data.x = sum[0] / CAL_DATA_AMOUNT;
+ acc_data->cal_data.y = sum[1] / CAL_DATA_AMOUNT;
+ acc_data->cal_data.z = (sum[2] / CAL_DATA_AMOUNT) - 1024;
+ } else {
+ acc_data->cal_data.x = 0;
+ acc_data->cal_data.y = 0;
+ acc_data->cal_data.z = 0;
+ }
+
+ printk(KERN_INFO "%s: cal data (%d,%d,%d)\n", __func__,
+ acc_data->cal_data.x, acc_data->cal_data.y, acc_data->cal_data.z);
+
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+
+ cal_filp = filp_open(CALIBRATION_FILE_PATH,
+ O_CREAT | O_TRUNC | O_WRONLY, 0666);
+ if (IS_ERR(cal_filp)) {
+ pr_err("%s: Can't open calibration file\n", __func__);
+ set_fs(old_fs);
+ err = PTR_ERR(cal_filp);
+ return err;
+ }
+
+ err = cal_filp->f_op->write(cal_filp,
+ (char *)&acc_data->cal_data, 3 * sizeof(s16), &cal_filp->f_pos);
+ if (err != 3 * sizeof(s16)) {
+ pr_err("%s: Can't write the cal data to file\n", __func__);
+ err = -EIO;
+ }
+
+ filp_close(cal_filp, current->files);
+ set_fs(old_fs);
+
+ return err;
+}
+
+static int k3dh_accel_enable(struct k3dh_data *data)
+{
+ int err = 0;
+
+ if (atomic_read(&data->opened) == 0) {
+ err = k3dh_open_calibration(data);
+ if (err < 0 && err != -ENOENT)
+ pr_err("%s: k3dh_open_calibration() failed\n",
+ __func__);
+ data->ctrl_reg1_shadow = DEFAULT_POWER_ON_SETTING;
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG1,
+ DEFAULT_POWER_ON_SETTING);
+ if (err)
+ pr_err("%s: i2c write ctrl_reg1 failed\n", __func__);
+
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG4,
+ CTRL_REG4_HR);
+ if (err)
+ pr_err("%s: i2c write ctrl_reg4 failed\n", __func__);
+ }
+
+ atomic_add(1, &data->opened);
+
+ return err;
+}
+
+static int k3dh_accel_disable(struct k3dh_data *data)
+{
+ int err = 0;
+
+ atomic_sub(1, &data->opened);
+ if (atomic_read(&data->opened) == 0) {
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG1,
+ PM_OFF);
+ data->ctrl_reg1_shadow = PM_OFF;
+ }
+
+ return err;
+}
+
+/* open command for K3DH device file */
+static int k3dh_open(struct inode *inode, struct file *file)
+{
+ k3dh_infomsg("is called.\n");
+ return 0;
+}
+
+/* release command for K3DH device file */
+static int k3dh_close(struct inode *inode, struct file *file)
+{
+ k3dh_infomsg("is called.\n");
+ return 0;
+}
+
+static s64 k3dh_get_delay(struct k3dh_data *data)
+{
+ int i;
+ u8 odr;
+ s64 delay = -1;
+
+ odr = data->ctrl_reg1_shadow & ODR_MASK;
+ for (i = 0; i < ARRAY_SIZE(odr_delay_table); i++) {
+ if (odr == odr_delay_table[i].odr) {
+ delay = odr_delay_table[i].delay_ns;
+ break;
+ }
+ }
+ return delay;
+}
+
+static int k3dh_set_delay(struct k3dh_data *data, s64 delay_ns)
+{
+ int odr_value = ODR1;
+ int res = 0;
+ int i;
+
+ /* round to the nearest delay that is less than
+ * the requested value (next highest freq)
+ */
+ for (i = 0; i < ARRAY_SIZE(odr_delay_table); i++) {
+ if (delay_ns < odr_delay_table[i].delay_ns)
+ break;
+ }
+ if (i > 0)
+ i--;
+ odr_value = odr_delay_table[i].odr;
+ delay_ns = odr_delay_table[i].delay_ns;
+
+ k3dh_infomsg("old=%lldns, new=%lldns, odr=0x%x/0x%x\n",
+ k3dh_get_delay(data), delay_ns, odr_value,
+ data->ctrl_reg1_shadow);
+ mutex_lock(&data->write_lock);
+ if (odr_value != (data->ctrl_reg1_shadow & ODR_MASK)) {
+ u8 ctrl = (data->ctrl_reg1_shadow & ~ODR_MASK);
+ ctrl |= odr_value;
+ data->ctrl_reg1_shadow = ctrl;
+ res = i2c_smbus_write_byte_data(data->client, CTRL_REG1, ctrl);
+ }
+ mutex_unlock(&data->write_lock);
+ return res;
+}
+
+/* ioctl command for K3DH device file */
+static long k3dh_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int err = 0;
+ struct k3dh_data *data = container_of(file->private_data,
+ struct k3dh_data, k3dh_device);
+ s64 delay_ns;
+ int enable = 0;
+
+ /* cmd mapping */
+ switch (cmd) {
+ case K3DH_IOCTL_SET_ENABLE:
+ if (copy_from_user(&enable, (void __user *)arg,
+ sizeof(enable)))
+ return -EFAULT;
+ k3dh_infomsg("opened = %d, enable = %d\n",
+ atomic_read(&data->opened), enable);
+ if (enable)
+ err = k3dh_accel_enable(data);
+ else
+ err = k3dh_accel_disable(data);
+ break;
+ case K3DH_IOCTL_SET_DELAY:
+ if (copy_from_user(&delay_ns, (void __user *)arg,
+ sizeof(delay_ns)))
+ return -EFAULT;
+ err = k3dh_set_delay(data, delay_ns);
+ break;
+ case K3DH_IOCTL_GET_DELAY:
+ delay_ns = k3dh_get_delay(data);
+ if (put_user(delay_ns, (s64 __user *)arg))
+ return -EFAULT;
+ break;
+ case K3DH_IOCTL_READ_ACCEL_XYZ:
+ err = k3dh_read_accel_xyz(data, &data->acc_xyz);
+ if (err)
+ break;
+ if (copy_to_user((void __user *)arg,
+ &data->acc_xyz, sizeof(data->acc_xyz)))
+ return -EFAULT;
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+
+ return err;
+}
+
+static int k3dh_suspend(struct device *dev)
+{
+ int res = 0;
+ struct k3dh_data *data = dev_get_drvdata(dev);
+
+ if (atomic_read(&data->opened) > 0)
+ res = i2c_smbus_write_byte_data(data->client,
+ CTRL_REG1, PM_OFF);
+
+ return res;
+}
+
+static int k3dh_resume(struct device *dev)
+{
+ int res = 0;
+ struct k3dh_data *data = dev_get_drvdata(dev);
+
+ if (atomic_read(&data->opened) > 0)
+ res = i2c_smbus_write_byte_data(data->client, CTRL_REG1,
+ data->ctrl_reg1_shadow);
+
+ return res;
+}
+
+static const struct dev_pm_ops k3dh_pm_ops = {
+ .suspend = k3dh_suspend,
+ .resume = k3dh_resume,
+};
+
+static const struct file_operations k3dh_fops = {
+ .owner = THIS_MODULE,
+ .open = k3dh_open,
+ .release = k3dh_close,
+ .unlocked_ioctl = k3dh_ioctl,
+};
+
+static ssize_t k3dh_fs_read(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct k3dh_data *data = dev_get_drvdata(dev);
+
+#ifdef CONFIG_MACH_U1
+ int err = 0;
+ int on;
+
+ mutex_lock(&data->write_lock);
+ on = atomic_read(&data->opened);
+ if (on == 0) {
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG1,
+ DEFAULT_POWER_ON_SETTING);
+ }
+ mutex_unlock(&data->write_lock);
+
+ if (err < 0) {
+ pr_err("%s: i2c write ctrl_reg1 failed\n", __func__);
+ return err;
+ }
+
+ err = k3dh_read_accel_xyz(data, &data->acc_xyz);
+ if (err < 0) {
+ pr_err("%s: k3dh_read_accel_xyz failed\n", __func__);
+ return err;
+ }
+
+ if (on == 0) {
+ mutex_lock(&data->write_lock);
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG1,
+ PM_OFF);
+ mutex_unlock(&data->write_lock);
+ if (err)
+ pr_err("%s: i2c write ctrl_reg1 failed\n", __func__);
+ }
+#endif
+ return sprintf(buf, "%d,%d,%d\n",
+ data->acc_xyz.x, data->acc_xyz.y, data->acc_xyz.z);
+}
+
+static ssize_t k3dh_calibration_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int err;
+ struct k3dh_data *data = dev_get_drvdata(dev);
+
+ err = k3dh_open_calibration(data);
+ if (err < 0)
+ pr_err("%s: k3dh_open_calibration() failed\n", __func__);
+
+ if (!data->cal_data.x && !data->cal_data.y && !data->cal_data.z)
+ err = -1;
+
+ return sprintf(buf, "%d %d %d %d\n",
+ err, data->cal_data.x, data->cal_data.y, data->cal_data.z);
+}
+
+static ssize_t k3dh_calibration_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct k3dh_data *data = dev_get_drvdata(dev);
+ bool do_calib;
+ int err;
+
+ if (sysfs_streq(buf, "1"))
+ do_calib = true;
+ else if (sysfs_streq(buf, "0"))
+ do_calib = false;
+ else {
+ pr_debug("%s: invalid value %d\n", __func__, *buf);
+ return -EINVAL;
+ }
+
+ if (atomic_read(&data->opened) == 0) {
+ /* if off, turn on the device.*/
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG1,
+ DEFAULT_POWER_ON_SETTING);
+ if (err) {
+ pr_err("%s: i2c write ctrl_reg1 failed(err=%d)\n",
+ __func__, err);
+ }
+ }
+
+ err = k3dh_do_calibrate(dev, do_calib);
+ if (err < 0) {
+ pr_err("%s: k3dh_do_calibrate() failed\n", __func__);
+ return err;
+ }
+
+ if (atomic_read(&data->opened) == 0) {
+ /* if off, turn on the device.*/
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG1,
+ PM_OFF);
+ if (err) {
+ pr_err("%s: i2c write ctrl_reg1 failed(err=%d)\n",
+ __func__, err);
+ }
+ }
+
+ return count;
+}
+
+#ifdef CONFIG_MACH_U1
+static DEVICE_ATTR(acc_file, 0664, k3dh_fs_read, NULL);
+#else
+static ssize_t k3dh_accel_vendor_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", VENDOR);
+}
+
+static ssize_t k3dh_accel_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", CHIP_ID);
+}
+
+static DEVICE_ATTR(name, 0664, k3dh_accel_name_show, NULL);
+static DEVICE_ATTR(vendor, 0664, k3dh_accel_vendor_show, NULL);
+static DEVICE_ATTR(raw_data, 0664, k3dh_fs_read, NULL);
+#endif
+static DEVICE_ATTR(calibration, 0664,
+ k3dh_calibration_show, k3dh_calibration_store);
+
+void k3dh_shutdown(struct i2c_client *client)
+{
+ int res = 0;
+ struct k3dh_data *data = i2c_get_clientdata(client);
+
+ k3dh_infomsg("is called.\n");
+
+ res = i2c_smbus_write_byte_data(data->client,
+ CTRL_REG1, PM_OFF);
+ if (res < 0)
+ pr_err("%s: pm_off failed %d\n", __func__, res);
+}
+
+static int k3dh_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct k3dh_data *data;
+#ifdef CONFIG_MACH_U1
+ struct device *dev_t, *dev_cal;
+#endif
+ struct accel_platform_data *pdata;
+ int err;
+
+ k3dh_infomsg("is started.\n");
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
+ I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
+ pr_err("%s: i2c functionality check failed!\n", __func__);
+ err = -ENODEV;
+ goto exit;
+ }
+
+ data = kzalloc(sizeof(struct k3dh_data), GFP_KERNEL);
+ if (data == NULL) {
+ dev_err(&client->dev,
+ "failed to allocate memory for module data\n");
+ err = -ENOMEM;
+ goto exit;
+ }
+
+ /* Checking device */
+ err = i2c_smbus_write_byte_data(client, CTRL_REG1,
+ PM_OFF);
+ if (err) {
+ pr_err("%s: there is no such device, err = %d\n",
+ __func__, err);
+ goto err_read_reg;
+ }
+
+ data->client = client;
+ i2c_set_clientdata(client, data);
+
+ init_completion(&data->data_ready);
+ mutex_init(&data->read_lock);
+ mutex_init(&data->write_lock);
+ atomic_set(&data->opened, 0);
+
+ /* sensor HAL expects to find /dev/accelerometer */
+ data->k3dh_device.minor = MISC_DYNAMIC_MINOR;
+ data->k3dh_device.name = ACC_DEV_NAME;
+ data->k3dh_device.fops = &k3dh_fops;
+
+ err = misc_register(&data->k3dh_device);
+ if (err) {
+ pr_err("%s: misc_register failed\n", __FILE__);
+ goto err_misc_register;
+ }
+
+ pdata = client->dev.platform_data;
+
+#ifdef CONFIG_MACH_U1
+ /* creating class/device for test */
+ data->acc_class = class_create(THIS_MODULE, "accelerometer");
+ if (IS_ERR(data->acc_class)) {
+ pr_err("%s: class create failed(accelerometer)\n", __func__);
+ err = PTR_ERR(data->acc_class);
+ goto err_class_create;
+ }
+
+ dev_t = device_create(data->acc_class, NULL,
+ MKDEV(ACC_DEV_MAJOR, 0), "%s", "accelerometer");
+ if (IS_ERR(dev_t)) {
+ pr_err("%s: class create failed(accelerometer)\n", __func__);
+ err = PTR_ERR(dev_t);
+ goto err_acc_device_create;
+ }
+
+ err = device_create_file(dev_t, &dev_attr_acc_file);
+ if (err < 0) {
+ pr_err("%s: Failed to create device file(%s)\n",
+ __func__, dev_attr_acc_file.attr.name);
+ goto err_acc_device_create_file;
+ }
+ dev_set_drvdata(dev_t, data);
+
+ /* creating device for calibration */
+ dev_cal = device_create(sec_class, NULL, 0, NULL, "gsensorcal");
+ if (IS_ERR(dev_cal)) {
+ pr_err("%s: class create failed(gsensorcal)\n", __func__);
+ err = PTR_ERR(dev_cal);
+ goto err_cal_device_create;
+ }
+
+ err = device_create_file(dev_cal, &dev_attr_calibration);
+ if (err < 0) {
+ pr_err("%s: Failed to create device file(%s)\n",
+ __func__, dev_attr_calibration.attr.name);
+ goto err_cal_device_create_file;
+ }
+ dev_set_drvdata(dev_cal, data);
+#else
+ /* creating device for test & calibration */
+ data->dev = sensors_classdev_register("accelerometer_sensor");
+ if (IS_ERR(data->dev)) {
+ pr_err("%s: class create failed(accelerometer_sensor)\n",
+ __func__);
+ err = PTR_ERR(data->dev);
+ goto err_acc_device_create;
+ }
+
+ err = device_create_file(data->dev, &dev_attr_raw_data);
+ if (err < 0) {
+ pr_err("%s: Failed to create device file(%s)\n",
+ __func__, dev_attr_raw_data.attr.name);
+ goto err_acc_device_create_file;
+ }
+
+ err = device_create_file(data->dev, &dev_attr_calibration);
+ if (err < 0) {
+ pr_err("%s: Failed to create device file(%s)\n",
+ __func__, dev_attr_calibration.attr.name);
+ goto err_cal_device_create_file;
+ }
+
+ err = device_create_file(data->dev, &dev_attr_vendor);
+ if (err < 0) {
+ pr_err("%s: Failed to create device file(%s)\n",
+ __func__, dev_attr_vendor.attr.name);
+ goto err_vendor_device_create_file;
+ }
+
+ err = device_create_file(data->dev, &dev_attr_name);
+ if (err < 0) {
+ pr_err("%s: Failed to create device file(%s)\n",
+ __func__, dev_attr_name.attr.name);
+ goto err_name_device_create_file;
+ }
+
+ dev_set_drvdata(data->dev, data);
+#endif
+
+ k3dh_infomsg("is successful.\n");
+
+ return 0;
+
+#ifdef CONFIG_MACH_U1
+err_cal_device_create_file:
+ device_destroy(sec_class, 0);
+err_cal_device_create:
+ device_remove_file(dev_t, &dev_attr_acc_file);
+err_acc_device_create_file:
+ device_destroy(data->acc_class, MKDEV(ACC_DEV_MAJOR, 0));
+err_acc_device_create:
+ class_destroy(data->acc_class);
+err_class_create:
+ misc_deregister(&data->k3dh_device);
+#else
+err_name_device_create_file:
+ device_remove_file(data->dev, &dev_attr_vendor);
+err_vendor_device_create_file:
+ device_remove_file(data->dev, &dev_attr_calibration);
+err_cal_device_create_file:
+ device_remove_file(data->dev, &dev_attr_raw_data);
+err_acc_device_create_file:
+ sensors_classdev_unregister(data->dev);
+err_acc_device_create:
+ misc_deregister(&data->k3dh_device);
+#endif
+err_misc_register:
+ mutex_destroy(&data->read_lock);
+ mutex_destroy(&data->write_lock);
+err_read_reg:
+ kfree(data);
+exit:
+ return err;
+}
+
+static int k3dh_remove(struct i2c_client *client)
+{
+ struct k3dh_data *data = i2c_get_clientdata(client);
+ int err = 0;
+
+ if (atomic_read(&data->opened) > 0) {
+ err = i2c_smbus_write_byte_data(data->client,
+ CTRL_REG1, PM_OFF);
+ if (err < 0)
+ pr_err("%s: pm_off failed %d\n", __func__, err);
+ }
+
+#ifdef CONFIG_MACH_U1
+ device_destroy(sec_class, 0);
+ device_destroy(data->acc_class, MKDEV(ACC_DEV_MAJOR, 0));
+ class_destroy(data->acc_class);
+#else
+ device_remove_file(data->dev, &dev_attr_name);
+ device_remove_file(data->dev, &dev_attr_vendor);
+ device_remove_file(data->dev, &dev_attr_calibration);
+ device_remove_file(data->dev, &dev_attr_raw_data);
+ sensors_classdev_unregister(data->dev);
+#endif
+ misc_deregister(&data->k3dh_device);
+ mutex_destroy(&data->read_lock);
+ mutex_destroy(&data->write_lock);
+ kfree(data);
+
+ return 0;
+}
+
+static const struct i2c_device_id k3dh_id[] = {
+ { "k3dh", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, k3dh_id);
+
+static struct i2c_driver k3dh_driver = {
+ .probe = k3dh_probe,
+ .shutdown = k3dh_shutdown,
+ .remove = __devexit_p(k3dh_remove),
+ .id_table = k3dh_id,
+ .driver = {
+ .pm = &k3dh_pm_ops,
+ .owner = THIS_MODULE,
+ .name = "k3dh",
+ },
+};
+
+static int __init k3dh_init(void)
+{
+ return i2c_add_driver(&k3dh_driver);
+}
+
+static void __exit k3dh_exit(void)
+{
+ i2c_del_driver(&k3dh_driver);
+}
+
+module_init(k3dh_init);
+module_exit(k3dh_exit);
+
+MODULE_DESCRIPTION("k3dh accelerometer driver");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sensor/k3dh_reg.h b/drivers/sensor/k3dh_reg.h
new file mode 100644
index 0000000..846d0d7
--- /dev/null
+++ b/drivers/sensor/k3dh_reg.h
@@ -0,0 +1,154 @@
+/*
+ * STMicroelectronics kr3dh acceleration sensor driver
+ *
+ * Copyright (C) 2010 Samsung Electronics Co.Ltd
+ *
+ * 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.
+ */
+
+
+/* kr3dh i2c slave address */
+#define KR3DH_I2C_ADDR 0x19
+
+/* kr3dh registers */
+#define STATUS_AUX 0x07
+#define OUT_1_L 0x08
+#define OUT_1_H 0x09
+#define OUT_2_L 0x0A
+#define OUT_2_H 0x0B
+#define OUT_3_L 0x0C
+#define OUT_3_H 0x0D
+#define INT_COUNTER 0x0E
+#define WHO_AM_I 0x0F
+#define TEMP_CFG_REG 0x1F
+#define CTRL_REG1 0x20 /* power control reg */
+#define CTRL_REG2 0x21 /* power control reg */
+#define CTRL_REG3 0x22 /* power control reg */
+#define CTRL_REG4 0x23 /* interrupt control reg */
+#define CTRL_REG5 0x24 /* interrupt control reg */
+#define CTRL_REG6 0x25
+#define REFERENCE 0x26
+#define STATUS_REG 0x27
+#define OUT_X_L 0x28
+#define OUT_X_H 0x29
+#define OUT_Y_L 0x2A
+#define OUT_Y_H 0x2B
+#define OUT_Z_L 0x2C
+#define OUT_Z_H 0x2D
+#define FIFO_CTRL_REG 0x2E
+#define FIFO_SRC_REG 0x2F
+#define INT1_CFG 0x30
+#define INT1_SRC 0x31
+#define INT1_THS 0x32
+#define INT1_DURATION 0x33
+#define CLICK_CFG 0x38
+#define CLICK_SRC 0x39
+#define CLICK_THS 0x3A
+#define TIME_LIMIT 0x3B
+#define TIME_LATENCY 0x3C
+#define TIME_WINDOW 0x3D
+
+/* CTRL_REG1 */
+#define CTRL_REG1_ODR3 (1 << 7)
+#define CTRL_REG1_ODR2 (1 << 6)
+#define CTRL_REG1_ODR1 (1 << 5)
+#define CTRL_REG1_ODR0 (1 << 4)
+#define CTRL_REG1_LPEN (1 << 3)
+#define CTRL_REG1_Zen (1 << 2)
+#define CTRL_REG1_Yen (1 << 1)
+#define CTRL_REG1_Xen (1 << 0)
+
+#define PM_OFF 0x00
+#define ENABLE_ALL_AXES 0x07
+
+#define ODR1 0x10 /* 1Hz output data rate */
+#define ODR10 0x20 /* 10Hz output data rate */
+#define ODR25 0x30 /* 10Hz output data rate */
+#define ODR50 0x40 /* 50Hz output data rate */
+#define ODR100 0x50 /* 100Hz output data rate */
+#define ODR200 0x60 /* 100Hz output data rate */
+#define ODR400 0x70 /* 400Hz output data rate */
+#define ODR1344 0x90 /* 1344Hz output data rate */
+#define ODR_MASK 0xf0
+
+/* CTRL_REG2 */
+#define CTRL_REG2_HPM1 (1 << 7)
+#define CTRL_REG2_HPM0 (1 << 6)
+#define CTRL_REG2_HPCF2 (1 << 5)
+#define CTRL_REG2_HPCF1 (1 << 4)
+#define CTRL_REG2_FDS (1 << 3)
+#define CTRL_REG2_HPPCLICK (1 << 2)
+#define CTRL_REG2_HPIS2 (1 << 1)
+#define CTRL_REG2_HPIS1 (1 << 0)
+
+#define HPM_Normal (CTRL_REG2_HPM1)
+#define HPM_Filter (CTRL_REG2_HPM0)
+
+/* CTRL_REG3 */
+#define I1_CLICK (1 << 7)
+#define I1_AOI1 (1 << 6)
+#define I1_AOI2 (1 << 5)
+#define I1_DRDY1 (1 << 4)
+#define I1_DRDY2 (1 << 3)
+#define I1_WTM (1 << 2)
+#define I1_OVERRUN (1 << 1)
+
+/* CTRL_REG4 */
+#define CTRL_REG4_BLE (1 << 6)
+#define CTRL_REG4_FS1 (1 << 5)
+#define CTRL_REG4_FS0 (1 << 4)
+#define CTRL_REG4_HR (1 << 3)
+#define CTRL_REG4_ST1 (1 << 2)
+#define CTRL_REG4_ST0 (1 << 1)
+#define CTRL_REG4_SIM (1 << 0)
+
+#define FS2g 0x00
+#define FS4g (CTRL_REG4_FS0)
+#define FS8g (CTRL_REG4_FS1)
+#define FS16g (CTRL_REG4_FS1|CTRL_REG4_FS0)
+
+/* CTRL_REG5 */
+#define BOOT (1 << 7)
+#define FIFO_EN (1 << 6)
+#define LIR_INT1 (1 << 3)
+#define D4D_INT1 (1 << 2)
+
+/* STATUS_REG */
+#define ZYXOR (1 << 7)
+#define ZOR (1 << 6)
+#define YOR (1 << 5)
+#define XOR (1 << 4)
+#define ZYXDA (1 << 3)
+#define ZDA (1 << 2)
+#define YDA (1 << 1)
+#define XDA (1 << 0)
+
+/* INT1_CFG */
+#define INT_CFG_AOI (1 << 7)
+#define INT_CFG_6D (1 << 6)
+#define INT_CFG_ZHIE (1 << 5)
+#define INT_CFG_ZLIE (1 << 4)
+#define INT_CFG_YHIE (1 << 3)
+#define INT_CFG_YLIE (1 << 2)
+#define INT_CFG_XHIE (1 << 1)
+#define INT_CFG_XLIE (1 << 0)
+
+/* INT1_SRC */
+#define IA (1 << 6)
+#define ZH (1 << 5)
+#define ZL (1 << 4)
+#define YH (1 << 3)
+#define YL (1 << 2)
+#define XH (1 << 1)
+#define XL (1 << 0)
+
+/* Register Auto-increase */
+#define AC (1 << 7)
diff --git a/drivers/sensor/k3g.c b/drivers/sensor/k3g.c
new file mode 100644
index 0000000..7ca6d6f
--- /dev/null
+++ b/drivers/sensor/k3g.c
@@ -0,0 +1,1372 @@
+/*
+ * Copyright (C) 2010, Samsung Electronics Co. Ltd. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/uaccess.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <asm/div64.h>
+#include <linux/delay.h>
+#include <linux/completion.h>
+#include <linux/sensor/k3g.h>
+
+/* k3g chip id */
+#define DEVICE_ID 0xD3
+
+/* k3g gyroscope registers */
+#define WHO_AM_I 0x0F
+#define CTRL_REG1 0x20 /* power control reg */
+#define CTRL_REG2 0x21 /* power control reg */
+#define CTRL_REG3 0x22 /* power control reg */
+#define CTRL_REG4 0x23 /* interrupt control reg */
+#define CTRL_REG5 0x24 /* interrupt control reg */
+#define OUT_TEMP 0x26 /* Temperature data */
+#define STATUS_REG 0x27
+#define AXISDATA_REG 0x28
+#define OUT_Y_L 0x2A
+#define FIFO_CTRL_REG 0x2E
+#define FIFO_SRC_REG 0x2F
+#define PM_OFF 0x00
+#define PM_NORMAL 0x08
+#define ENABLE_ALL_AXES 0x07
+#define BYPASS_MODE 0x00
+#define FIFO_MODE 0x20
+#define FIFO_EMPTY 0x20
+#define AC (1 << 7) /* register auto-increment bit */
+
+/* odr settings */
+#define FSS_MASK 0x1F
+#define ODR_MASK 0xF0
+#define ODR105_BW12_5 0x00 /* ODR = 105Hz; BW = 12.5Hz */
+#define ODR105_BW25 0x10 /* ODR = 105Hz; BW = 25Hz */
+#define ODR210_BW12_5 0x40 /* ODR = 210Hz; BW = 12.5Hz */
+#define ODR210_BW25 0x50 /* ODR = 210Hz; BW = 25Hz */
+#define ODR210_BW50 0x60 /* ODR = 210Hz; BW = 50Hz */
+#define ODR210_BW70 0x70 /* ODR = 210Hz; BW = 70Hz */
+#define ODR420_BW20 0x80 /* ODR = 420Hz; BW = 20Hz */
+#define ODR420_BW25 0x90 /* ODR = 420Hz; BW = 25Hz */
+#define ODR420_BW50 0xA0 /* ODR = 420Hz; BW = 50Hz */
+#define ODR420_BW110 0xB0 /* ODR = 420Hz; BW = 110Hz */
+#define ODR840_BW30 0xC0 /* ODR = 840Hz; BW = 30Hz */
+#define ODR840_BW35 0xD0 /* ODR = 840Hz; BW = 35Hz */
+#define ODR840_BW50 0xE0 /* ODR = 840Hz; BW = 50Hz */
+#define ODR840_BW110 0xF0 /* ODR = 840Hz; BW = 110Hz */
+
+/* full scale selection */
+#define DPS250 250
+#define DPS500 500
+#define DPS2000 2000
+#define FS_MASK 0x30
+#define FS_250DPS 0x00
+#define FS_500DPS 0x10
+#define FS_2000DPS 0x20
+#define DEFAULT_DPS DPS500
+#define FS_DEFULAT_DPS FS_500DPS
+
+/* self tset settings */
+#define MIN_ST 175
+#define MAX_ST 875
+#define FIFO_TEST_WTM 0x1F
+#define MIN_ZERO_RATE -1714
+#define MAX_ZERO_RATE 1714
+
+/* max and min entry */
+#define MAX_ENTRY 20
+#define MAX_DELAY (MAX_ENTRY * 9523809LL)
+
+#define K3G_MAJOR 102
+#define K3G_MINOR 4
+
+/* default register setting for device init */
+static const char default_ctrl_regs[] = {
+ 0x3F, /* 105HZ, PM-normal, xyz enable */
+ 0x00, /* normal mode */
+ 0x04, /* fifo wtm interrupt on */
+ 0x90, /* block data update, 500d/s */
+ 0x40, /* fifo enable */
+};
+
+static const struct odr_delay {
+ u8 odr; /* odr reg setting */
+ u32 delay_ns; /* odr in ns */
+} odr_delay_table[] = {
+/* { ODR840_BW110, 1190476LL }, */ /* 840Hz */
+/* { ODR420_BW110, 2380952LL }, */ /* 420Hz */
+ { ODR210_BW70, 4761904LL }, /* 210Hz */
+ { ODR105_BW25, 9523809LL }, /* 105Hz */
+};
+
+/*
+ * K3G gyroscope data
+ * brief structure containing gyroscope values for yaw, pitch and roll in
+ * signed short
+ */
+struct k3g_t {
+ s16 x;
+ s16 y;
+ s16 z;
+};
+
+struct k3g_data {
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+ struct mutex lock;
+ struct workqueue_struct *k3g_wq;
+ struct work_struct work;
+ struct hrtimer timer;
+ struct class *k3g_gyro_dev_class;
+ struct device *dev;
+ struct completion data_ready;
+ bool enable;
+ bool drop_next_event;
+ bool fifo_test; /* is self_test or not? */
+ bool interruptible; /* interrupt or polling? */
+ int entries; /* number of fifo entries */
+ int dps; /* scale selection */
+ u8 fifo_data[sizeof(struct k3g_t) * 32]; /* fifo data entries */
+ u8 ctrl_regs[5]; /* saving register settings */
+ u32 time_to_read; /* time needed to read one entry */
+ ktime_t polling_delay; /* polling time for timer */
+};
+
+static int k3g_read_fifo_status(struct k3g_data *k3g_data)
+{
+ int fifo_status;
+
+ fifo_status = i2c_smbus_read_byte_data(k3g_data->client, FIFO_SRC_REG);
+ if (fifo_status < 0) {
+ pr_err("%s: failed to read fifo source register\n",
+ __func__);
+ return fifo_status;
+ }
+ return (fifo_status & FSS_MASK) + !(fifo_status & FIFO_EMPTY);
+}
+
+static int k3g_restart_fifo(struct k3g_data *k3g_data)
+{
+ int res = 0;
+
+ res = i2c_smbus_write_byte_data(k3g_data->client,
+ FIFO_CTRL_REG, BYPASS_MODE);
+ if (res < 0) {
+ pr_err("%s : failed to set bypass_mode\n", __func__);
+ return res;
+ }
+
+ res = i2c_smbus_write_byte_data(k3g_data->client,
+ FIFO_CTRL_REG, FIFO_MODE | (k3g_data->entries - 1));
+
+ if (res < 0)
+ pr_err("%s : failed to set fifo_mode\n", __func__);
+
+ return res;
+}
+
+static void set_polling_delay(struct k3g_data *k3g_data, int res)
+{
+ s64 delay_ns;
+
+ delay_ns = k3g_data->entries + 1 - res;
+ if (delay_ns < 0)
+ delay_ns = 0;
+
+ delay_ns = delay_ns * k3g_data->time_to_read;
+ k3g_data->polling_delay = ns_to_ktime(delay_ns);
+}
+
+/* gyroscope data readout */
+static int k3g_read_gyro_values(struct i2c_client *client,
+ struct k3g_t *data, int total_read)
+{
+ int err;
+ int len = sizeof(*data) * (total_read ? (total_read - 1) : 1);
+ struct k3g_data *k3g_data = i2c_get_clientdata(client);
+ struct i2c_msg msg[2];
+ u8 reg_buf;
+
+ msg[0].addr = client->addr;
+ msg[0].buf = &reg_buf;
+ msg[0].flags = 0;
+ msg[0].len = 1;
+
+ msg[1].addr = client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].buf = k3g_data->fifo_data;
+
+ if (total_read > 1) {
+ reg_buf = AXISDATA_REG | AC;
+ msg[1].len = len;
+
+ err = i2c_transfer(client->adapter, msg, 2);
+ if (err != 2)
+ return (err < 0) ? err : -EIO;
+ }
+
+ reg_buf = AXISDATA_REG;
+ msg[1].len = 1;
+ err = i2c_transfer(client->adapter, msg, 2);
+ if (err != 2)
+ return (err < 0) ? err : -EIO;
+
+ reg_buf = OUT_Y_L | AC;
+ msg[1].len = sizeof(*data);
+ err = i2c_transfer(client->adapter, msg, 2);
+ if (err != 2)
+ return (err < 0) ? err : -EIO;
+
+ data->y = (k3g_data->fifo_data[1] << 8) | k3g_data->fifo_data[0];
+ data->z = (k3g_data->fifo_data[3] << 8) | k3g_data->fifo_data[2];
+ data->x = (k3g_data->fifo_data[5] << 8) | k3g_data->fifo_data[4];
+
+ return 0;
+}
+
+static int k3g_report_gyro_values(struct k3g_data *k3g_data)
+{
+ int res;
+ struct k3g_t data;
+
+ res = k3g_read_gyro_values(k3g_data->client, &data,
+ k3g_data->entries + k3g_data->drop_next_event);
+ if (res < 0)
+ return res;
+
+ res = k3g_read_fifo_status(k3g_data);
+
+ k3g_data->drop_next_event = !res;
+
+ if (res >= 31 - k3g_data->entries) {
+ /* reset fifo to start again - data isn't trustworthy,
+ * our locked read might not have worked and we
+ * could have done i2c read in mid register update
+ */
+ return k3g_restart_fifo(k3g_data);
+ }
+
+ input_report_rel(k3g_data->input_dev, REL_RX, data.x);
+ input_report_rel(k3g_data->input_dev, REL_RY, data.y);
+ input_report_rel(k3g_data->input_dev, REL_RZ, data.z);
+ input_sync(k3g_data->input_dev);
+
+ return res;
+}
+
+static enum hrtimer_restart k3g_timer_func(struct hrtimer *timer)
+{
+ struct k3g_data *k3g_data = container_of(timer, struct k3g_data, timer);
+ queue_work(k3g_data->k3g_wq, &k3g_data->work);
+ return HRTIMER_NORESTART;
+}
+
+static void k3g_work_func(struct work_struct *work)
+{
+ int res;
+ struct k3g_data *k3g_data = container_of(work, struct k3g_data, work);
+
+ do {
+ res = k3g_read_fifo_status(k3g_data);
+ if (res < 0)
+ return;
+
+ if (res < k3g_data->entries) {
+ pr_warn("%s: fifo entries are less than we want\n",
+ __func__);
+ goto timer_set;
+ }
+
+ res = k3g_report_gyro_values(k3g_data);
+ if (res < 0)
+ return;
+timer_set:
+ set_polling_delay(k3g_data, res);
+
+ } while (!ktime_to_ns(k3g_data->polling_delay));
+
+ hrtimer_start(&k3g_data->timer,
+ k3g_data->polling_delay, HRTIMER_MODE_REL);
+}
+
+static irqreturn_t k3g_interrupt_thread(int irq, void *k3g_data_p)
+{
+ int res;
+ struct k3g_data *k3g_data = k3g_data_p;
+
+ if (unlikely(k3g_data->fifo_test)) {
+ disable_irq_nosync(irq);
+ complete(&k3g_data->data_ready);
+ return IRQ_HANDLED;
+ }
+
+ res = k3g_report_gyro_values(k3g_data);
+ if (res < 0)
+ pr_err("%s: failed to report gyro values\n", __func__);
+
+ return IRQ_HANDLED;
+}
+
+static ssize_t k3g_selftest_dps_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct k3g_data *k3g_data = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n", k3g_data->dps);
+}
+static ssize_t k3g_force_sleep(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int err = 5;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct k3g_data *k3g_data = i2c_get_clientdata(client);
+
+ err = i2c_smbus_write_byte_data(k3g_data->client,
+ CTRL_REG1, 0x00);
+ if (err == 0)
+ err = 1;
+ else
+ err = 0;
+
+ return sprintf(buf, "%d\n", err);
+}
+static ssize_t k3g_selftest_dps_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct k3g_data *k3g_data = dev_get_drvdata(dev);
+ int new_dps = 0;
+ int err;
+ u8 ctrl;
+
+ sscanf(buf, "%d", &new_dps);
+
+ /* check out dps validity */
+ if (new_dps != DPS250 && new_dps != DPS500 && new_dps != DPS2000) {
+ pr_err("%s: wrong dps(%d)\n", __func__, new_dps);
+ return -1;
+ }
+
+ ctrl = (k3g_data->ctrl_regs[3] & ~FS_MASK);
+
+ if (new_dps == DPS250)
+ ctrl |= FS_250DPS;
+ else if (new_dps == DPS500)
+ ctrl |= FS_500DPS;
+ else if (new_dps == DPS2000)
+ ctrl |= FS_2000DPS;
+ else
+ ctrl |= FS_DEFULAT_DPS;
+
+ /* apply new dps */
+ mutex_lock(&k3g_data->lock);
+ k3g_data->ctrl_regs[3] = ctrl;
+
+ err = i2c_smbus_write_byte_data(k3g_data->client, CTRL_REG4, ctrl);
+ if (err < 0) {
+ pr_err("%s: updating dps failed\n", __func__);
+ mutex_unlock(&k3g_data->lock);
+ return err;
+ }
+ mutex_unlock(&k3g_data->lock);
+
+ k3g_data->dps = new_dps;
+ pr_err("%s: %d dps stored\n", __func__, k3g_data->dps);
+
+ return count;
+}
+
+static ssize_t k3g_show_enable(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct k3g_data *k3g_data = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n", k3g_data->enable);
+}
+
+static ssize_t k3g_set_enable(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ int err = 0;
+ struct k3g_data *k3g_data = dev_get_drvdata(dev);
+ bool new_enable;
+
+ if (sysfs_streq(buf, "1"))
+ new_enable = true;
+ else if (sysfs_streq(buf, "0"))
+ new_enable = false;
+ else {
+ pr_debug("%s: invalid value %d\n", __func__, *buf);
+ return -EINVAL;
+ }
+
+ if (new_enable == k3g_data->enable)
+ return size;
+
+ mutex_lock(&k3g_data->lock);
+ if (new_enable) {
+ err = i2c_smbus_write_i2c_block_data(k3g_data->client,
+ CTRL_REG1 | AC, sizeof(k3g_data->ctrl_regs),
+ k3g_data->ctrl_regs);
+ if (err < 0) {
+ err = -EIO;
+ goto unlock;
+ }
+
+ /* reset fifo entries */
+ err = k3g_restart_fifo(k3g_data);
+ if (err < 0) {
+ err = -EIO;
+ goto turn_off;
+ }
+
+ if (k3g_data->interruptible)
+ enable_irq(k3g_data->client->irq);
+ else {
+ set_polling_delay(k3g_data, 0);
+ hrtimer_start(&k3g_data->timer,
+ k3g_data->polling_delay, HRTIMER_MODE_REL);
+ }
+ } else {
+ if (k3g_data->interruptible)
+ disable_irq(k3g_data->client->irq);
+ else {
+ hrtimer_cancel(&k3g_data->timer);
+ cancel_work_sync(&k3g_data->work);
+ }
+ /* turning off */
+ err = i2c_smbus_write_byte_data(k3g_data->client,
+ CTRL_REG1, 0x00);
+ if (err < 0)
+ goto unlock;
+ }
+ k3g_data->enable = new_enable;
+
+turn_off:
+ if (err < 0)
+ i2c_smbus_write_byte_data(k3g_data->client,
+ CTRL_REG1, 0x00);
+unlock:
+ mutex_unlock(&k3g_data->lock);
+
+ return err ? err : size;
+}
+
+static ssize_t k3g_show_delay(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct k3g_data *k3g_data = dev_get_drvdata(dev);
+ u64 delay;
+
+ delay = k3g_data->time_to_read * k3g_data->entries;
+ delay = ktime_to_ns(ns_to_ktime(delay));
+
+ return sprintf(buf, "%lld\n", delay);
+}
+
+static ssize_t k3g_set_delay(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct k3g_data *k3g_data = dev_get_drvdata(dev);
+ int odr_value = ODR105_BW25;
+ int res = 0;
+ int i;
+ u64 delay_ns;
+ u8 ctrl;
+
+ res = strict_strtoll(buf, 10, &delay_ns);
+ if (res < 0)
+ return res;
+
+ mutex_lock(&k3g_data->lock);
+ if (!k3g_data->interruptible)
+ hrtimer_cancel(&k3g_data->timer);
+ else
+ disable_irq(k3g_data->client->irq);
+
+ /* round to the nearest supported ODR that is less than
+ * the requested value
+ */
+ for (i = 0; i < ARRAY_SIZE(odr_delay_table); i++)
+ if (delay_ns <= odr_delay_table[i].delay_ns) {
+ odr_value = odr_delay_table[i].odr;
+ delay_ns = odr_delay_table[i].delay_ns;
+ k3g_data->time_to_read = delay_ns;
+ k3g_data->entries = 1;
+ break;
+ }
+
+ if (delay_ns >= odr_delay_table[1].delay_ns) {
+ if (delay_ns >= MAX_DELAY) {
+ k3g_data->entries = MAX_ENTRY;
+ delay_ns = MAX_DELAY;
+ } else {
+ do_div(delay_ns, odr_delay_table[1].delay_ns);
+ k3g_data->entries = delay_ns;
+ }
+ k3g_data->time_to_read = odr_delay_table[1].delay_ns;
+ }
+
+ if (odr_value != (k3g_data->ctrl_regs[0] & ODR_MASK)) {
+ ctrl = (k3g_data->ctrl_regs[0] & ~ODR_MASK);
+ ctrl |= odr_value;
+ k3g_data->ctrl_regs[0] = ctrl;
+ res = i2c_smbus_write_byte_data(k3g_data->client,
+ CTRL_REG1, ctrl);
+ }
+
+ /* (re)start fifo */
+ k3g_restart_fifo(k3g_data);
+
+ if (!k3g_data->interruptible) {
+ delay_ns = k3g_data->entries * k3g_data->time_to_read;
+ k3g_data->polling_delay = ns_to_ktime(delay_ns);
+ if (k3g_data->enable)
+ hrtimer_start(&k3g_data->timer,
+ k3g_data->polling_delay, HRTIMER_MODE_REL);
+ } else
+ enable_irq(k3g_data->client->irq);
+
+ mutex_unlock(&k3g_data->lock);
+
+ return size;
+}
+
+static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
+ k3g_show_enable, k3g_set_enable);
+static DEVICE_ATTR(poll_delay, S_IRUGO | S_IWUSR | S_IWGRP,
+ k3g_show_delay, k3g_set_delay);
+
+
+/*************************************************************************/
+/* K3G Sysfs */
+/*************************************************************************/
+
+/* Device Initialization */
+static int device_init(struct k3g_data *data)
+{
+ int err;
+ u8 buf[5];
+
+ buf[0] = 0x6f;
+ buf[1] = 0x00;
+ buf[2] = 0x00;
+ buf[3] = 0x00;
+ buf[4] = 0x00;
+ err = i2c_smbus_write_i2c_block_data(data->client,
+ CTRL_REG1 | AC, sizeof(buf), buf);
+ if (err < 0)
+ pr_err("%s: CTRL_REGs i2c writing failed\n", __func__);
+
+ return err;
+}
+
+static ssize_t k3g_power_on(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct k3g_data *data = dev_get_drvdata(dev);
+ int err;
+
+ err = device_init(data);
+ if (err < 0) {
+ pr_err("%s: device_init() failed\n", __func__);
+ return 0;
+ }
+
+ printk(KERN_INFO "[%s] result of device init = %d\n", __func__, err);
+
+ return sprintf(buf, "%d\n", (err < 0 ? 0 : 1));
+}
+
+static ssize_t k3g_get_temp(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct k3g_data *data = dev_get_drvdata(dev);
+ char temp;
+
+ temp = i2c_smbus_read_byte_data(data->client, OUT_TEMP);
+ if (temp < 0) {
+ pr_err("%s: STATUS_REGS i2c reading failed\n", __func__);
+ return 0;
+ }
+
+ printk(KERN_INFO "[%s] read temperature : %d\n", __func__, temp);
+
+ return sprintf(buf, "%d\n", temp);
+}
+
+static int k3g_fifo_self_test(struct k3g_data *k3g_data)
+{
+ struct k3g_t raw_data;
+ int err;
+ int i, j;
+ s16 raw[3] = { 0, };
+ u8 reg[5];
+ u8 fifo_pass = 2;
+ u8 status_reg;
+
+ k3g_data->fifo_test = true;
+
+ /* fifo mode, enable interrupt, 500dps */
+ reg[0] = 0x6F;
+ reg[1] = 0x00;
+ reg[2] = 0x04;
+ reg[3] = 0x90;
+ reg[4] = 0x40;
+
+ for (i = 0; i < 10; i++) {
+ err = i2c_smbus_write_i2c_block_data(k3g_data->client,
+ CTRL_REG1 | AC, sizeof(reg), reg);
+ if (err >= 0)
+ break;
+ }
+ if (err < 0) {
+ pr_err("%s: CTRL_REGs i2c writing failed\n", __func__);
+ goto exit;
+ }
+
+ /* Power up, wait for 800ms for stable output */
+ msleep(800);
+
+ for (i = 0; i < 10; i++) {
+ err = i2c_smbus_write_byte_data(k3g_data->client,
+ FIFO_CTRL_REG, BYPASS_MODE);
+ if (err >= 0)
+ break;
+ }
+ if (err < 0) {
+ pr_err("%s : failed to set bypass_mode\n", __func__);
+ goto exit;
+ }
+
+ for (i = 0; i < 10; i++) {
+ err = i2c_smbus_write_byte_data(k3g_data->client,
+ FIFO_CTRL_REG, FIFO_MODE | FIFO_TEST_WTM);
+ if (err >= 0)
+ break;
+ }
+ if (err < 0) {
+ pr_err("%s: failed to set fifo_mode\n", __func__);
+ goto exit;
+ }
+
+ /* if interrupt mode */
+ if (!k3g_data->enable && k3g_data->interruptible) {
+ enable_irq(k3g_data->client->irq);
+ err = wait_for_completion_timeout(&k3g_data->data_ready, 5*HZ);
+ if (err <= 0) {
+ disable_irq(k3g_data->client->irq);
+ if (!err)
+ pr_err("%s: wait timed out\n", __func__);
+ goto exit;
+ }
+ }
+
+ /* wait for 32 fifo entries */
+ msleep(200);
+
+ /* check out watermark status */
+ status_reg = i2c_smbus_read_byte_data(k3g_data->client, FIFO_SRC_REG);
+ if (!(status_reg & 0x80)) {
+ pr_err("%s: Watermark level is not enough\n", __func__);
+ goto exit;
+ }
+
+ /* read fifo entries */
+ err = k3g_read_gyro_values(k3g_data->client,
+ &raw_data, FIFO_TEST_WTM + 2);
+ if (err < 0) {
+ pr_err("%s: k3g_read_gyro_values() failed\n", __func__);
+ goto exit;
+ }
+
+ /* print out fifo data */
+ printk(KERN_INFO "[gyro_self_test] fifo data\n");
+ for (i = 0; i < sizeof(raw_data) * (FIFO_TEST_WTM + 1);
+ i += sizeof(raw_data)) {
+ raw[0] = (k3g_data->fifo_data[i+1] << 8)
+ | k3g_data->fifo_data[i];
+ raw[1] = (k3g_data->fifo_data[i+3] << 8)
+ | k3g_data->fifo_data[i+2];
+ raw[2] = (k3g_data->fifo_data[i+5] << 8)
+ | k3g_data->fifo_data[i+4];
+ pr_err("%2dth: %8d %8d %8d\n", i/6, raw[0], raw[1], raw[2]);
+
+ for (j = 0; j < 3; j++) {
+ if (raw[j] < MIN_ZERO_RATE || raw[j] > MAX_ZERO_RATE) {
+ pr_err("%s: %dth data(%d) is out of zero-rate",
+ __func__, i/6, raw[j]);
+ pr_err("%s: fifo test failed\n", __func__);
+ fifo_pass = 0;
+ goto exit;
+ }
+ }
+ }
+
+ fifo_pass = 1;
+
+exit:
+ k3g_data->fifo_test = false;
+
+ /* make sure clearing interrupt */
+ enable_irq(k3g_data->client->irq);
+ disable_irq(k3g_data->client->irq);
+
+ /* 1: success, 0: fail, 2: retry */
+ return fifo_pass;
+}
+
+static int k3g_bypass_self_test(struct k3g_data *k3g_data,
+ int NOST[3], int ST[3])
+{
+ int differ_x = 0, differ_y = 0, differ_z = 0;
+ int err;
+ int i, j;
+ s16 raw[3] = { 0, };
+ s32 temp;
+ u8 gyro_data[6] = { 0, };
+ u8 reg[5];
+ u8 bZYXDA = 0;
+ u8 bypass_pass = 2;
+
+ /* Initialize Sensor, turn on sensor, enable P/R/Y */
+ /* Set BDU=1, Set ODR=200Hz, Cut-Off Frequency=50Hz, FS=2000dps */
+ reg[0] = 0x6f;
+ reg[1] = 0x00;
+ reg[2] = 0x00;
+ reg[3] = 0xA0;
+ reg[4] = 0x02;
+
+ for (i = 0; i < 10; i++) {
+ err = i2c_smbus_write_i2c_block_data(k3g_data->client,
+ CTRL_REG1 | AC, sizeof(reg), reg);
+ if (err >= 0)
+ break;
+ }
+ if (err < 0) {
+ pr_err("%s: CTRL_REGs i2c writing failed\n", __func__);
+ goto exit;
+ }
+
+ for (i = 0; i < 10; i++) {
+ err = i2c_smbus_write_byte_data(k3g_data->client,
+ FIFO_CTRL_REG, BYPASS_MODE);
+ if (err >= 0)
+ break;
+ }
+ if (err < 0) {
+ pr_err("%s : failed to set bypass_mode\n", __func__);
+ goto exit;
+ }
+
+ /* Power up, wait for 800ms for stable output */
+ msleep(800);
+
+ /* Read 5 samples output before self-test on */
+ for (i = 0; i < 5; i++) {
+ /* check ZYXDA ready bit */
+ for (j = 0; j < 10; j++) {
+ temp = i2c_smbus_read_byte_data(k3g_data->client,
+ STATUS_REG);
+ if (temp >= 0) {
+ bZYXDA = temp & 0x08;
+ if (!bZYXDA) {
+ usleep_range(10000, 10000);
+ pr_err("%s: %d,%d: no_data_ready",
+ __func__, i, j);
+ continue;
+ } else
+ break;
+ }
+ }
+ if (temp < 0) {
+ pr_err("%s: STATUS_REGS i2c reading failed\n",
+ __func__);
+ goto exit;
+ }
+
+ for (j = 0; j < 10; j++) {
+ err = i2c_smbus_read_i2c_block_data(k3g_data->client,
+ AXISDATA_REG | AC,
+ sizeof(gyro_data), gyro_data);
+ if (err >= 0)
+ break;
+ }
+ if (err < 0) {
+ pr_err("%s: CTRL_REGs i2c reading failed\n", __func__);
+ goto exit;
+ }
+
+ raw[0] = (gyro_data[1] << 8) | gyro_data[0];
+ raw[1] = (gyro_data[3] << 8) | gyro_data[2];
+ raw[2] = (gyro_data[5] << 8) | gyro_data[4];
+
+ NOST[0] += raw[0];
+ NOST[1] += raw[1];
+ NOST[2] += raw[2];
+
+ printk(KERN_INFO "[gyro_self_test] raw[0] = %d\n", raw[0]);
+ printk(KERN_INFO "[gyro_self_test] raw[1] = %d\n", raw[1]);
+ printk(KERN_INFO "[gyro_self_test] raw[2] = %d\n\n", raw[2]);
+ }
+
+ for (i = 0; i < 3; i++)
+ printk(KERN_INFO "[gyro_self_test] "
+ "SUM of NOST[%d] = %d\n", i, NOST[i]);
+
+ /* calculate average of NOST and covert from ADC to DPS */
+ for (i = 0; i < 3; i++) {
+ NOST[i] = (NOST[i] / 5) * 70 / 1000;
+ printk(KERN_INFO "[gyro_self_test] "
+ "AVG of NOST[%d] = %d\n", i, NOST[i]);
+ }
+ printk(KERN_INFO "\n");
+
+ /* Enable Self Test */
+ reg[0] = 0xA2;
+ for (i = 0; i < 10; i++) {
+ err = i2c_smbus_write_byte_data(k3g_data->client,
+ CTRL_REG4, reg[0]);
+ if (err >= 0)
+ break;
+ }
+ if (temp < 0) {
+ pr_err("%s: CTRL_REG4 i2c writing failed\n", __func__);
+ goto exit;
+ }
+
+ msleep(100);
+
+ /* Read 5 samples output after self-test on */
+ for (i = 0; i < 5; i++) {
+ /* check ZYXDA ready bit */
+ for (j = 0; j < 10; j++) {
+ temp = i2c_smbus_read_byte_data(k3g_data->client,
+ STATUS_REG);
+ if (temp >= 0) {
+ bZYXDA = temp & 0x08;
+ if (!bZYXDA) {
+ usleep_range(10000, 10000);
+ pr_err("%s: %d,%d: no_data_ready",
+ __func__, i, j);
+ continue;
+ } else
+ break;
+ }
+
+ }
+ if (temp < 0) {
+ pr_err("%s: STATUS_REGS i2c reading failed\n",
+ __func__);
+ goto exit;
+ }
+
+ for (j = 0; j < 10; j++) {
+ err = i2c_smbus_read_i2c_block_data(k3g_data->client,
+ AXISDATA_REG | AC,
+ sizeof(gyro_data), gyro_data);
+ if (err >= 0)
+ break;
+ }
+ if (err < 0) {
+ pr_err("%s: CTRL_REGs i2c reading failed\n", __func__);
+ goto exit;
+ }
+
+ raw[0] = (gyro_data[1] << 8) | gyro_data[0];
+ raw[1] = (gyro_data[3] << 8) | gyro_data[2];
+ raw[2] = (gyro_data[5] << 8) | gyro_data[4];
+
+ ST[0] += raw[0];
+ ST[1] += raw[1];
+ ST[2] += raw[2];
+
+ printk(KERN_INFO "[gyro_self_test] raw[0] = %d\n", raw[0]);
+ printk(KERN_INFO "[gyro_self_test] raw[1] = %d\n", raw[1]);
+ printk(KERN_INFO "[gyro_self_test] raw[2] = %d\n\n", raw[2]);
+ }
+
+ for (i = 0; i < 3; i++)
+ printk(KERN_INFO "[gyro_self_test] "
+ "SUM of ST[%d] = %d\n", i, ST[i]);
+
+ /* calculate average of ST and convert from ADC to dps */
+ for (i = 0; i < 3; i++) {
+ /* When FS=2000, 70 mdps/digit */
+ ST[i] = (ST[i] / 5) * 70 / 1000;
+ printk(KERN_INFO "[gyro_self_test] "
+ "AVG of ST[%d] = %d\n", i, ST[i]);
+ }
+
+ /* check whether pass or not */
+ if (ST[0] >= NOST[0]) /* for x */
+ differ_x = ST[0] - NOST[0];
+ else
+ differ_x = NOST[0] - ST[0];
+
+ if (ST[1] >= NOST[1]) /* for y */
+ differ_y = ST[1] - NOST[1];
+ else
+ differ_y = NOST[1] - ST[1];
+
+ if (ST[2] >= NOST[2]) /* for z */
+ differ_z = ST[2] - NOST[2];
+ else
+ differ_z = NOST[2] - ST[2];
+
+ printk(KERN_INFO "[gyro_self_test] differ x:%d, y:%d, z:%d\n",
+ differ_x, differ_y, differ_z);
+
+ if ((MIN_ST <= differ_x && differ_x <= MAX_ST)
+ && (MIN_ST <= differ_y && differ_y <= MAX_ST)
+ && (MIN_ST <= differ_z && differ_z <= MAX_ST))
+ bypass_pass = 1;
+ else
+ bypass_pass = 0;
+
+exit:
+ return bypass_pass;
+}
+
+static ssize_t k3g_self_test(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct k3g_data *data = dev_get_drvdata(dev);
+ int NOST[3] = { 0, }, ST[3] = { 0, };
+ int err;
+ int i;
+ u8 backup_regs[5];
+ u8 fifo_pass = 2;
+ u8 bypass_pass = 2;
+
+ /* before starting self-test, backup register */
+ for (i = 0; i < 10; i++) {
+ err = i2c_smbus_read_i2c_block_data(data->client,
+ CTRL_REG1 | AC, sizeof(backup_regs), backup_regs);
+ if (err >= 0)
+ break;
+ }
+ if (err < 0) {
+ pr_err("%s: CTRL_REGs i2c reading failed\n", __func__);
+ goto exit;
+ }
+
+ for (i = 0; i < 5; i++)
+ printk(KERN_INFO "[gyro_self_test] "
+ "backup reg[%d] = %2x\n", i, backup_regs[i]);
+
+ /* fifo self test */
+ printk(KERN_INFO "\n[gyro_self_test] fifo self-test\n");
+
+ fifo_pass = k3g_fifo_self_test(data);
+ if (fifo_pass)
+ printk(KERN_INFO "[gyro_self_test] fifo self-test success\n");
+ else if (!fifo_pass)
+ printk(KERN_INFO "[gyro_self_test] fifo self-test fail\n");
+ else
+ printk(KERN_INFO "[gyro_self_test] fifo self-test restry\n");
+
+ /* bypass self test */
+ printk(KERN_INFO "\n[gyro_self_test] bypass self-test\n");
+
+ bypass_pass = k3g_bypass_self_test(data, NOST, ST);
+ if (bypass_pass)
+ printk(KERN_INFO "[gyro_self_test] bypass self-test success\n\n");
+ else if (!fifo_pass)
+ printk(KERN_INFO "[gyro_self_test] bypass self-test fail\n\n");
+ else
+ printk(KERN_INFO "[gyro_self_test] bypass self-test restry\n\n");
+
+ /* restore backup register */
+ for (i = 0; i < 10; i++) {
+ err = i2c_smbus_write_i2c_block_data(data->client,
+ CTRL_REG1 | AC, sizeof(backup_regs),
+ backup_regs);
+ if (err >= 0)
+ break;
+ }
+ if (err < 0)
+ pr_err("%s: CTRL_REGs i2c writing failed\n", __func__);
+
+exit:
+ if (!data->enable) {
+ /* If k3g is not enabled, make it go to the power down mode. */
+ err = i2c_smbus_write_byte_data(data->client,
+ CTRL_REG1, 0x00);
+ if (err < 0)
+ pr_err("%s: CTRL_REGs i2c writing failed\n", __func__);
+ }
+
+ if (fifo_pass == 2 && bypass_pass == 2)
+ printk(KERN_INFO "[gyro_self_test] self-test result : retry\n");
+ else
+ printk(KERN_INFO "[gyro_self_test] self-test result : %s\n",
+ fifo_pass & bypass_pass ? "pass" : "fail");
+
+ return sprintf(buf, "%d,%d,%d,%d,%d,%d,%d,%d\n",
+ NOST[0], NOST[1], NOST[2], ST[0], ST[1], ST[2],
+ fifo_pass & bypass_pass, fifo_pass);
+}
+
+
+
+static DEVICE_ATTR(gyro_power_on, 0664,
+ k3g_power_on, NULL);
+static DEVICE_ATTR(gyro_get_temp, 0664,
+ k3g_get_temp, NULL);
+static DEVICE_ATTR(gyro_selftest, 0664,
+ k3g_self_test, NULL);
+static DEVICE_ATTR(gyro_selftest_dps, 0664,
+ k3g_selftest_dps_show, k3g_selftest_dps_store);
+static DEVICE_ATTR(gyro_force_sleep, 0664,
+ k3g_force_sleep, NULL);
+
+static const struct file_operations k3g_fops = {
+ .owner = THIS_MODULE,
+};
+
+/*************************************************************************/
+/* End of K3G Sysfs */
+/*************************************************************************/
+
+
+static int k3g_probe(struct i2c_client *client,
+ const struct i2c_device_id *devid)
+{
+ int ret;
+ int err = 0;
+ struct k3g_data *data;
+ struct input_dev *input_dev;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (data == NULL) {
+ dev_err(&client->dev,
+ "failed to allocate memory for module data\n");
+ err = -ENOMEM;
+ goto exit;
+ }
+
+ data->client = client;
+
+ /* read chip id */
+ ret = i2c_smbus_read_byte_data(client, WHO_AM_I);
+ if (ret != DEVICE_ID) {
+ if (ret < 0) {
+ pr_err("%s: i2c for reading chip id failed\n",
+ __func__);
+ err = ret;
+ } else {
+ pr_err("%s : Device identification failed\n",
+ __func__);
+ err = -ENODEV;
+ }
+ goto err_read_reg;
+ }
+
+ mutex_init(&data->lock);
+ init_completion(&data->data_ready);
+
+ /* allocate gyro input_device */
+ 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;
+ }
+
+ data->input_dev = input_dev;
+ input_set_drvdata(input_dev, data);
+ input_dev->name = "gyro_sensor";
+ /* X */
+ input_set_capability(input_dev, EV_REL, REL_RX);
+ input_set_abs_params(input_dev, REL_RX, -2048, 2047, 0, 0);
+ /* Y */
+ input_set_capability(input_dev, EV_REL, REL_RY);
+ input_set_abs_params(input_dev, REL_RY, -2048, 2047, 0, 0);
+ /* Z */
+ input_set_capability(input_dev, EV_REL, REL_RZ);
+ input_set_abs_params(input_dev, REL_RZ, -2048, 2047, 0, 0);
+
+ err = input_register_device(input_dev);
+ if (err < 0) {
+ pr_err("%s: could not register input device\n", __func__);
+ input_free_device(data->input_dev);
+ goto err_input_register_device;
+ }
+
+ memcpy(&data->ctrl_regs, &default_ctrl_regs, sizeof(default_ctrl_regs));
+
+ i2c_set_clientdata(client, data);
+ dev_set_drvdata(&input_dev->dev, data);
+
+ if (data->client->irq >= 0) { /* interrupt */
+ data->interruptible = true;
+ err = request_threaded_irq(data->client->irq, NULL,
+ k3g_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+ "k3g", data);
+ if (err < 0) {
+ pr_err("%s: can't allocate irq.\n", __func__);
+ goto err_request_irq;
+ }
+ disable_irq(data->client->irq);
+
+ } else { /* polling */
+ u64 delay_ns;
+ data->ctrl_regs[2] = 0x00; /* disable interrupt */
+ /* hrtimer settings. we poll for gyro values using a timer. */
+ hrtimer_init(&data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ data->polling_delay = ns_to_ktime(200 * NSEC_PER_MSEC);
+ data->time_to_read = 10000000LL;
+ delay_ns = ktime_to_ns(data->polling_delay);
+ do_div(delay_ns, data->time_to_read);
+ data->entries = delay_ns;
+ data->timer.function = k3g_timer_func;
+
+ /* the timer just fires off a work queue request.
+ We need a thread to read i2c (can be slow and blocking). */
+ data->k3g_wq = create_singlethread_workqueue("k3g_wq");
+ if (!data->k3g_wq) {
+ err = -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(&data->work, k3g_work_func);
+ }
+
+ if (device_create_file(&input_dev->dev,
+ &dev_attr_enable) < 0) {
+ pr_err("%s: Failed to create device file(%s)!\n", __func__,
+ dev_attr_enable.attr.name);
+ goto err_device_create_file;
+ }
+
+ if (device_create_file(&input_dev->dev,
+ &dev_attr_poll_delay) < 0) {
+ pr_err("%s: Failed to create device file(%s)!\n", __func__,
+ dev_attr_poll_delay.attr.name);
+ goto err_device_create_file2;
+ }
+
+ /* register a char dev */
+ err = register_chrdev(K3G_MAJOR, "k3g", &k3g_fops);
+ if (err < 0) {
+ pr_err("%s: Failed to register chrdev(k3g)\n", __func__);
+ goto err_register_chrdev;
+ }
+
+ /* create k3g-dev device class */
+ data->k3g_gyro_dev_class = class_create(THIS_MODULE, "K3G_GYRO-dev");
+ if (IS_ERR(data->k3g_gyro_dev_class)) {
+ pr_err("%s: Failed to create class(K3G_GYRO-dev\n", __func__);
+ err = PTR_ERR(data->k3g_gyro_dev_class);
+ goto err_class_create;
+ }
+
+ /* create device node for k3g digital gyroscope */
+ data->dev = device_create(data->k3g_gyro_dev_class, NULL,
+ MKDEV(K3G_MAJOR, 0), NULL, "k3g");
+
+ if (IS_ERR(data->dev)) {
+ pr_err("%s: Failed to create device(k3g)\n", __func__);
+ err = PTR_ERR(data->dev);
+ goto err_device_create;
+ }
+
+ if (device_create_file(data->dev, &dev_attr_gyro_power_on) < 0) {
+ pr_err("%s: Failed to create device file(%s)!\n", __func__,
+ dev_attr_gyro_power_on.attr.name);
+ goto device_create_file3;
+ }
+
+ if (device_create_file(data->dev, &dev_attr_gyro_get_temp) < 0) {
+ pr_err("%s: Failed to create device file(%s)!\n", __func__,
+ dev_attr_gyro_get_temp.attr.name);
+ goto device_create_file4;
+ }
+
+ if (device_create_file(data->dev, &dev_attr_gyro_selftest) < 0) {
+ pr_err("%s: Failed to create device file(%s)!\n", __func__,
+ dev_attr_gyro_selftest.attr.name);
+ goto device_create_file5;
+ }
+
+ if (device_create_file(data->dev, &dev_attr_gyro_selftest_dps) < 0) {
+ pr_err("%s: Failed to create device file(%s)!\n", __func__,
+ dev_attr_gyro_selftest_dps.attr.name);
+ goto device_create_file6;
+ }
+
+ if (device_create_file(data->dev, &dev_attr_gyro_force_sleep) < 0) {
+ pr_err("%s: Failed to create device file(%s)!\n", __func__,
+ dev_attr_gyro_force_sleep.attr.name);
+ goto device_create_file7;
+ }
+
+
+ dev_set_drvdata(data->dev, data);
+
+ return 0;
+device_create_file7:
+ device_remove_file(data->dev, &dev_attr_gyro_force_sleep);
+device_create_file6:
+ device_remove_file(data->dev, &dev_attr_gyro_selftest);
+device_create_file5:
+ device_remove_file(data->dev, &dev_attr_gyro_get_temp);
+device_create_file4:
+ device_remove_file(data->dev, &dev_attr_gyro_power_on);
+device_create_file3:
+ device_destroy(data->k3g_gyro_dev_class, MKDEV(K3G_MAJOR, 0));
+err_device_create:
+ class_destroy(data->k3g_gyro_dev_class);
+err_class_create:
+ unregister_chrdev(K3G_MAJOR, "k3g");
+err_register_chrdev:
+ device_remove_file(&input_dev->dev, &dev_attr_poll_delay);
+err_device_create_file2:
+ device_remove_file(&input_dev->dev, &dev_attr_enable);
+err_device_create_file:
+ if (data->interruptible) {
+ enable_irq(data->client->irq);
+ free_irq(data->client->irq, data);
+ } else
+ destroy_workqueue(data->k3g_wq);
+ input_unregister_device(data->input_dev);
+err_create_workqueue:
+err_request_irq:
+err_input_register_device:
+err_input_allocate_device:
+ mutex_destroy(&data->lock);
+err_read_reg:
+ kfree(data);
+exit:
+ return err;
+}
+
+static int k3g_remove(struct i2c_client *client)
+{
+ int err = 0;
+ struct k3g_data *k3g_data = i2c_get_clientdata(client);
+
+ device_remove_file(k3g_data->dev, &dev_attr_gyro_selftest_dps);
+ device_remove_file(k3g_data->dev, &dev_attr_gyro_force_sleep);
+ device_remove_file(k3g_data->dev, &dev_attr_gyro_selftest);
+ device_remove_file(k3g_data->dev, &dev_attr_gyro_get_temp);
+ device_remove_file(k3g_data->dev, &dev_attr_gyro_power_on);
+ device_destroy(k3g_data->k3g_gyro_dev_class, MKDEV(K3G_MAJOR, 0));
+ class_destroy(k3g_data->k3g_gyro_dev_class);
+ unregister_chrdev(K3G_MAJOR, "k3g");
+ device_remove_file(&k3g_data->input_dev->dev, &dev_attr_enable);
+ device_remove_file(&k3g_data->input_dev->dev, &dev_attr_poll_delay);
+
+ if (k3g_data->enable)
+ err = i2c_smbus_write_byte_data(k3g_data->client,
+ CTRL_REG1, 0x00);
+ if (k3g_data->interruptible) {
+ if (!k3g_data->enable) /* no disable_irq before free_irq */
+ enable_irq(k3g_data->client->irq);
+ free_irq(k3g_data->client->irq, k3g_data);
+
+ } else {
+ hrtimer_cancel(&k3g_data->timer);
+ cancel_work_sync(&k3g_data->work);
+ destroy_workqueue(k3g_data->k3g_wq);
+ }
+
+ input_unregister_device(k3g_data->input_dev);
+ mutex_destroy(&k3g_data->lock);
+ kfree(k3g_data);
+
+ return err;
+}
+
+static int k3g_suspend(struct device *dev)
+{
+ int err = 0;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct k3g_data *k3g_data = i2c_get_clientdata(client);
+
+ if (k3g_data->enable) {
+ mutex_lock(&k3g_data->lock);
+ if (!k3g_data->interruptible) {
+ hrtimer_cancel(&k3g_data->timer);
+ cancel_work_sync(&k3g_data->work);
+ }
+ err = i2c_smbus_write_byte_data(k3g_data->client,
+ CTRL_REG1, 0x00);
+ mutex_unlock(&k3g_data->lock);
+ }
+
+ return err;
+}
+
+static int k3g_resume(struct device *dev)
+{
+ int err = 0;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct k3g_data *k3g_data = i2c_get_clientdata(client);
+
+ if (k3g_data->enable) {
+ mutex_lock(&k3g_data->lock);
+ if (!k3g_data->interruptible)
+ hrtimer_start(&k3g_data->timer,
+ k3g_data->polling_delay, HRTIMER_MODE_REL);
+ err = i2c_smbus_write_i2c_block_data(client,
+ CTRL_REG1 | AC, sizeof(k3g_data->ctrl_regs),
+ k3g_data->ctrl_regs);
+ mutex_unlock(&k3g_data->lock);
+ }
+
+ return err;
+}
+
+static const struct dev_pm_ops k3g_pm_ops = {
+ .suspend = k3g_suspend,
+ .resume = k3g_resume
+};
+
+static const struct i2c_device_id k3g_id[] = {
+ { "k3g", 0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, k3g_id);
+
+static struct i2c_driver k3g_driver = {
+ .probe = k3g_probe,
+ .remove = __devexit_p(k3g_remove),
+ .id_table = k3g_id,
+ .driver = {
+ .pm = &k3g_pm_ops,
+ .owner = THIS_MODULE,
+ .name = "k3g"
+ },
+};
+
+static int __init k3g_init(void)
+{
+ return i2c_add_driver(&k3g_driver);
+}
+
+static void __exit k3g_exit(void)
+{
+ i2c_del_driver(&k3g_driver);
+}
+
+module_init(k3g_init);
+module_exit(k3g_exit);
+
+MODULE_DESCRIPTION("k3g digital gyroscope driver");
+MODULE_AUTHOR("tim.sk.lee@samsung.com");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sensor/lps331ap.c b/drivers/sensor/lps331ap.c
new file mode 100644
index 0000000..6990f25
--- /dev/null
+++ b/drivers/sensor/lps331ap.c
@@ -0,0 +1,1767 @@
+/*
+* drivers/sensors/lps331ap.c
+*
+* STMicroelectronics LPS331AP Pressure / Temperature Sensor module driver
+*
+* Copyright (C) 2010 STMicroelectronics- MSH - Motion Mems BU - Application Team
+* Matteo Dameno (matteo.dameno@st.com)
+*
+*
+* Both authors are willing to be considered the contact and update points for
+* the driver.
+*
+** Output data from the device are available from the assigned
+* /sys/class/input/inputX device;
+*
+* LPS3311AP can be controlled by sysfs interface looking inside:
+* /sys/class/input/inputX/
+*
+* LPS331AP make available two i2C addresses selectable from platform_data
+* by the LPS001WP_PRS_I2C_SAD_H or LPS001WP_PRS_I2C_SAD_L.
+*
+* Read pressures and temperatures output can be converted in units of
+* measurement by dividing them respectively for SENSITIVITY_P and SENSITIVITY_T.
+* Temperature values must then be added by the constant float TEMPERATURE_OFFSET
+* expressed as Celsius degrees.
+*
+* Obtained values are then expessed as
+* mbar (=0.1 kPa) and Celsius degrees.
+*
+* To use autozero feature you can write 0 zero or 1 to its corresponding sysfs
+* file. This lets you to write current temperature and pressure into reference
+* registers or to reset them.
+*
+* 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
+*
+*/
+/******************************************************************************
+ Revision 1.0.0 2011/Feb/14:
+ first release
+ moved to input/misc
+ Revision 1.0.1 2011/Apr/04:
+ xxx
+ Revision 1.0.2 2011/Sep/01:
+ corrects ord bug, forces BDU enable
+ Revision 1.0.3 2011/Sep/15:
+ introduces compansation params reading and sysfs file to get them
+ Revision 1.0.4 2011/Dec/12:
+ sets maximum allowable resolution modes dynamically with ODR;
+ Revision 1.0.5 2012/Feb/29:
+ introduces more compansation params and extends sysfs file content
+ format to get them; reduces minimum polling period define;
+ enables by default DELTA_EN bit1 on CTRL_REG1
+ corrects bug on TSH acquisition
+ Revision 1.0.6 2012/Mar/30:
+ introduces one more compansation param and extends sysfs file content
+ format to get it.
+ Revision 1.0.6.1 2012/Apr/12:
+ changes Resolution settings for 25Hz to TEMP AVG=128 and PRES AVG=384.
+******************************************************************************/
+
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/workqueue.h>
+#include <linux/gpio.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/uaccess.h>
+#include <linux/fs.h>
+#include <linux/sensor/lps331ap.h>
+#include <linux/sensor/sensors_core.h>
+
+#undef LPS331_DEBUG
+
+#define VENDOR "STM"
+#define CHIP_ID "LPS331"
+
+#define PR_ABS_MAX 8388607 /* 24 bit 2'compl */
+#define PR_ABS_MIN -8388608
+
+#ifdef SHRT_MAX
+#define TEMP_MAX SHRT_MAX
+#define TEMP_MIN SHRT_MIN
+#else
+#define TEMP_MAX SHORT_MAX
+#define TEMP_MIN SHORT_MIN
+#endif
+
+#define ALTITUDE_MAX (99999)
+#define ALTITUDE_MIN (-1)
+
+#define WHOAMI_LPS331AP_PRS 0xBB /* Expctd content for WAI */
+
+/* CONTROL REGISTERS */
+#define REF_P_XL 0x08 /* pressure reference */
+#define REF_P_L 0x09 /* pressure reference */
+#define REF_P_H 0x0A /* pressure reference */
+#define REF_T_L 0x0B /* temperature reference */
+#define REF_T_H 0x0C /* temperature reference */
+
+#define WHO_AM_I 0x0F /* WhoAmI register */
+#define TP_RESOL 0x10 /* Pres Temp resolution set*/
+#define DGAIN_L 0x18 /* Dig Gain (3 regs) */
+
+#define CTRL_REG1 0x20 /* power / ODR control reg */
+#define CTRL_REG2 0x21 /* boot reg */
+#define CTRL_REG3 0x22 /* interrupt control reg */
+#define INT_CFG_REG 0x23 /* interrupt config reg */
+#define INT_SRC_REG 0x24 /* interrupt source reg */
+#define THS_P_L 0x25 /* pressure threshold */
+#define THS_P_H 0x26 /* pressure threshold */
+#define STATUS_REG 0X27 /* status reg */
+
+#define PRESS_OUT_XL 0x28 /* press output (3 regs) */
+#define TEMP_OUT_L 0x2B /* temper output (2 regs) */
+#define COMPENS_L 0x30 /* compensation reg (9 regs) */
+#define DELTAREG_1 0x3C /* deltaPressure reg1 */
+#define DELTAREG_2 0x3D /* deltaPressure reg2 */
+#define DELTAREG_3 0x3E /* deltaPressure reg3 */
+#define DELTA_T1 0x3B /* deltaTemp1 reg */
+#define DELTA_T2T3 0x3F /* deltaTemp2, deltaTemp3 reg */
+#define CALIB_SETUP 0x1E /* calibrationSetup reg */
+#define CALIB_STP_MASK 0x80 /* mask to catch calibSetup info */
+
+/* REGISTERS ALIASES */
+#define P_REF_INDATA_REG REF_P_XL
+#define T_REF_INDATA_REG REF_T_L
+#define P_THS_INDATA_REG THS_P_L
+#define P_OUTDATA_REG PRESS_OUT_XL
+#define T_OUTDATA_REG TEMP_OUT_L
+#define OUTDATA_REG PRESS_OUT_XL
+
+/* REGISTERS MASKS */
+#define LPS331AP_PRS_ENABLE_MASK 0x80 /* ctrl_reg1 */
+#define LPS331AP_PRS_ODR_MASK 0x70 /* ctrl_reg1 */
+#define LPS331AP_PRS_DIFF_MASK 0x08 /* ctrl_reg1 */
+#define LPS331AP_PRS_BDU_MASK 0x04 /* ctrl_reg1 */
+#define LPS331AP_PRS_DELTA_EN_MASK 0x02 /* ctrl_reg1 */
+#define LPS331AP_PRS_AUTOZ_MASK 0x02 /* ctrl_reg2 */
+
+#define LPS331AP_PRS_PM_NORMAL 0x80 /* Power Normal Mode*/
+#define LPS331AP_PRS_PM_OFF 0x00 /* Power Down */
+
+#define LPS331AP_PRS_DIFF_ON 0x08 /* En Difference circuitry */
+#define LPS331AP_PRS_DIFF_OFF 0x00 /* Dis Difference circuitry */
+
+#define LPS331AP_PRS_AUTOZ_ON 0x02 /* En AutoZero Function */
+#define LPS331AP_PRS_AUTOZ_OFF 0x00 /* Dis Difference Function */
+
+#define LPS331AP_PRS_BDU_ON 0x04 /* En BDU Block Data Upd */
+#define LPS331AP_PRS_DELTA_EN_ON 0x02 /* En Delta Press registers */
+#define LPS331AP_PRS_RES_AVGTEMP_064 0x60
+#define LPS331AP_PRS_RES_AVGTEMP_128 0x70
+#define LPS331AP_PRS_RES_AVGPRES_512 0x0A
+#define LPS331AP_PRS_RES_AVGPRES_384 0x09
+
+#define LPS331AP_PRS_RES_MAX (LPS331AP_PRS_RES_AVGTEMP_128 | \
+ LPS331AP_PRS_RES_AVGPRES_512)
+ /* Max Resol. for 1/7/12,5Hz */
+
+#define LPS331AP_PRS_RES_25HZ (LPS331AP_PRS_RES_AVGTEMP_128 | \
+ LPS331AP_PRS_RES_AVGPRES_384)
+ /* Max Resol. for 25Hz */
+
+#define FUZZ 0
+#define FLAT 0
+
+#define I2C_AUTO_INCREMENT 0x80
+
+/* RESUME STATE INDICES */
+#define LPS331AP_RES_REF_P_XL 0
+#define LPS331AP_RES_REF_P_L 1
+#define LPS331AP_RES_REF_P_H 2
+#define LPS331AP_RES_REF_T_L 3
+#define LPS331AP_RES_REF_T_H 4
+#define LPS331AP_RES_TP_RESOL 5
+#define LPS331AP_RES_CTRL_REG1 6
+#define LPS331AP_RES_CTRL_REG2 7
+#define LPS331AP_RES_CTRL_REG3 8
+#define LPS331AP_RES_INT_CFG_REG 9
+#define LPS331AP_RES_THS_P_L 10
+#define LPS331AP_RES_THS_P_H 11
+#define RESUME_ENTRIES 12
+/* end RESUME STATE INDICES */
+
+/* Pressure Sensor Operating Mode */
+#define LPS331AP_PRS_DIFF_ENABLE 1
+#define LPS331AP_PRS_DIFF_DISABLE 0
+#define LPS331AP_PRS_AUTOZ_ENABLE 1
+#define LPS331AP_PRS_AUTOZ_DISABLE 0
+
+/* poll delays */
+#define DELAY_DEFAULT 200
+#define DELAY_MINIMUM 40
+/* calibration file path */
+#define CALIBRATION_FILE_PATH "/efs/FactoryApp/baro_delta"
+
+static const struct {
+ unsigned int cutoff_ms;
+ unsigned int mask;
+} lps331ap_prs_odr_table[] = {
+ {40, LPS331AP_PRS_ODR_25_25 },
+ {80, LPS331AP_PRS_ODR_12_12 },
+ {143, LPS331AP_PRS_ODR_7_7 },
+ {1000, LPS331AP_PRS_ODR_1_1 },
+};
+
+struct lps331ap_prs_data {
+ struct i2c_client *client;
+ struct mutex lock;
+ struct delayed_work input_work;
+ struct input_dev *input_dev;
+ struct device *dev;
+
+ unsigned int poll_delay;
+
+ int hw_initialized;
+ /* hw_working = 0 means not tested yet */
+ int hw_working;
+
+ atomic_t enabled;
+
+ u8 resume_state[RESUME_ENTRIES];
+
+#ifdef LPS331_DEBUG
+ u8 reg_addr;
+#endif
+
+ u32 TSL, TSH; /* temperature points 1 - 2 - 3 */
+ u32 TCV1, TCV2, TCV3;
+ u32 TCS1, TCS2, TCS3;
+ u32 digGain;
+ s8 deltaT1, deltaT2, deltaT3;
+ s32 pressure_cal;
+ u8 testVer;
+};
+
+struct outputdata {
+ s32 press;
+ s16 temperature;
+};
+
+static void lps331ap_open_calibration(struct lps331ap_prs_data *prs);
+
+static int lps331ap_prs_i2c_read(struct lps331ap_prs_data *prs,
+ u8 *buf, int len)
+{
+ int err;
+ struct i2c_msg msgs[] = {
+ {
+ .addr = prs->client->addr,
+ .flags = prs->client->flags & I2C_M_TEN,
+ .len = 1,
+ .buf = buf,
+ }, {
+ .addr = prs->client->addr,
+ .flags = (prs->client->flags & I2C_M_TEN) | I2C_M_RD,
+ .len = len,
+ .buf = buf,
+ },
+ };
+
+ err = i2c_transfer(prs->client->adapter, msgs, 2);
+ if (err != 2) {
+ dev_err(&prs->client->dev, "read transfer error\n");
+ err = -EIO;
+ }
+ return 0;
+}
+
+static int lps331ap_prs_i2c_write(struct lps331ap_prs_data *prs,
+ u8 *buf, int len)
+{
+ int err;
+ struct i2c_msg msgs[] = {
+ {
+ .addr = prs->client->addr,
+ .flags = prs->client->flags & I2C_M_TEN,
+ .len = len + 1,
+ .buf = buf,
+ },
+ };
+
+ err = i2c_transfer(prs->client->adapter, msgs, 1);
+ if (err != 1) {
+ dev_err(&prs->client->dev, "write transfer error\n");
+ err = -EIO;
+ }
+ return 0;
+}
+
+static int lps331ap_prs_register_write(struct lps331ap_prs_data *prs, u8 *buf,
+ u8 reg_address, u8 new_value)
+{
+ int err;
+
+ /* Sets configuration register at reg_address
+ * NOTE: this is a straight overwrite */
+ buf[0] = reg_address;
+ buf[1] = new_value;
+ err = lps331ap_prs_i2c_write(prs, buf, 1);
+ if (err < 0)
+ return err;
+ return err;
+}
+
+static int lps331ap_prs_register_read(struct lps331ap_prs_data *prs, u8 *buf,
+ u8 reg_address)
+{
+ int err;
+ buf[0] = (reg_address);
+ err = lps331ap_prs_i2c_read(prs, buf, 1);
+
+ return err;
+}
+
+static int lps331ap_prs_register_update(struct lps331ap_prs_data *prs, u8 *buf,
+ u8 reg_address, u8 mask, u8 new_bit_values)
+{
+ int err;
+ u8 init_val;
+ u8 updated_val;
+ err = lps331ap_prs_register_read(prs, buf, reg_address);
+ if (!(err < 0)) {
+ init_val = buf[0];
+ updated_val = ((mask & new_bit_values) | ((~mask) & init_val));
+ err = lps331ap_prs_register_write(prs, buf, reg_address,
+ updated_val);
+ }
+ return err;
+}
+
+static int lps331ap_prs_hw_init(struct lps331ap_prs_data *prs)
+{
+ int err;
+ u8 buf[6];
+
+ dev_dbg(&prs->client->dev, "%s: hw init start\n"\
+ , LPS331AP_PRS_DEV_NAME);
+
+ buf[0] = WHO_AM_I;
+ err = lps331ap_prs_i2c_read(prs, buf, 1);
+ if (err < 0)
+ goto error_firstread;
+ else
+ prs->hw_working = 1;
+ if (buf[0] != WHOAMI_LPS331AP_PRS) {
+ err = -ENODEV;
+ goto error_unknown_device;
+ }
+
+ buf[0] = (I2C_AUTO_INCREMENT | P_REF_INDATA_REG);
+ buf[1] = prs->resume_state[LPS331AP_RES_REF_P_XL];
+ buf[2] = prs->resume_state[LPS331AP_RES_REF_P_L];
+ buf[3] = prs->resume_state[LPS331AP_RES_REF_P_H];
+ buf[4] = prs->resume_state[LPS331AP_RES_REF_T_L];
+ buf[5] = prs->resume_state[LPS331AP_RES_REF_T_H];
+ err = lps331ap_prs_i2c_write(prs, buf, 5);
+ if (err < 0)
+ goto err_resume_state;
+
+ buf[0] = TP_RESOL;
+ buf[1] = prs->resume_state[LPS331AP_RES_TP_RESOL];
+ err = lps331ap_prs_i2c_write(prs, buf, 1);
+ if (err < 0)
+ goto err_resume_state;
+
+ buf[0] = (I2C_AUTO_INCREMENT | P_THS_INDATA_REG);
+ buf[1] = prs->resume_state[LPS331AP_RES_THS_P_L];
+ buf[2] = prs->resume_state[LPS331AP_RES_THS_P_H];
+ err = lps331ap_prs_i2c_write(prs, buf, 2);
+ if (err < 0)
+ goto err_resume_state;
+
+ buf[0] = (I2C_AUTO_INCREMENT | CTRL_REG1);
+ buf[1] = prs->resume_state[LPS331AP_RES_CTRL_REG1];
+ buf[2] = prs->resume_state[LPS331AP_RES_CTRL_REG2];
+ buf[3] = prs->resume_state[LPS331AP_RES_CTRL_REG3];
+ err = lps331ap_prs_i2c_write(prs, buf, 3);
+ if (err < 0)
+ goto err_resume_state;
+
+ buf[0] = INT_CFG_REG;
+ buf[1] = prs->resume_state[LPS331AP_RES_INT_CFG_REG];
+ err = lps331ap_prs_i2c_write(prs, buf, 1);
+ if (err < 0)
+ goto err_resume_state;
+
+ prs->hw_initialized = 1;
+ dev_dbg(&prs->client->dev, "%s: hw init done\n", LPS331AP_PRS_DEV_NAME);
+ return 0;
+
+error_firstread:
+ prs->hw_working = 0;
+ dev_warn(&prs->client->dev, "Error reading WHO_AM_I: is device "
+ "available/working?\n");
+ goto err_resume_state;
+error_unknown_device:
+ dev_err(&prs->client->dev,
+ "device unknown. Expected: 0x%02x,"
+ " Replies: 0x%02x\n", WHOAMI_LPS331AP_PRS, buf[0]);
+err_resume_state:
+ prs->hw_initialized = 0;
+ dev_err(&prs->client->dev, "hw init error 0x%02x,0x%02x: %d\n", buf[0],
+ buf[1], err);
+ return err;
+}
+
+static void lps331ap_prs_device_power_off(struct lps331ap_prs_data *prs)
+{
+ int err;
+ u8 buf[2] = { CTRL_REG1, LPS331AP_PRS_PM_OFF };
+
+ err = lps331ap_prs_i2c_write(prs, buf, 1);
+ if (err < 0)
+ dev_err(&prs->client->dev, "soft power off failed: %d\n", err);
+
+ prs->hw_initialized = 0;
+}
+
+static int lps331ap_prs_device_power_on(struct lps331ap_prs_data *prs)
+{
+ int err = -1;
+
+ if (!prs->hw_initialized) {
+ err = lps331ap_prs_hw_init(prs);
+ if (prs->hw_working == 1 && err < 0) {
+ lps331ap_prs_device_power_off(prs);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+int lps331ap_prs_update_odr(struct lps331ap_prs_data *prs, int delay_ms)
+{
+ int err = -1;
+ int i;
+
+ u8 buf[2];
+ u8 init_val, updated_val;
+ u8 curr_val, new_val;
+ u8 mask = LPS331AP_PRS_ODR_MASK;
+ u8 resol = LPS331AP_PRS_RES_MAX;
+
+ /* Following, looks for the longest possible odr interval scrolling the
+ * odr_table vector from the end (longest period) backward (shortest
+ * period), to support the poll_interval requested by the system.
+ * It must be the longest period shorter then the set poll period.*/
+ for (i = ARRAY_SIZE(lps331ap_prs_odr_table) - 1; i >= 0; i--) {
+ if ((lps331ap_prs_odr_table[i].cutoff_ms <= delay_ms)
+ || (i == 0))
+ break;
+ }
+
+ new_val = lps331ap_prs_odr_table[i].mask;
+ if (new_val == LPS331AP_PRS_ODR_25_25)
+ resol = LPS331AP_PRS_RES_25HZ;
+
+ if (atomic_read(&prs->enabled)) {
+ buf[0] = CTRL_REG1;
+ err = lps331ap_prs_i2c_read(prs, buf, 1);
+ if (err < 0)
+ goto error;
+ /* work on all but ENABLE bits */
+ init_val = buf[0];
+ prs->resume_state[LPS331AP_RES_CTRL_REG1] = init_val ;
+
+ /* disable */
+ curr_val = ((LPS331AP_PRS_ENABLE_MASK & LPS331AP_PRS_PM_OFF)
+ | ((~LPS331AP_PRS_ENABLE_MASK) & init_val));
+ buf[0] = CTRL_REG1;
+ buf[1] = curr_val;
+ err = lps331ap_prs_i2c_write(prs, buf, 1);
+ if (err < 0)
+ goto error;
+
+ buf[0] = CTRL_REG1;
+ updated_val = ((mask & new_val) | ((~mask) & curr_val));
+
+ buf[0] = CTRL_REG1;
+ buf[1] = updated_val;
+ err = lps331ap_prs_i2c_write(prs, buf, 1);
+ if (err < 0)
+ goto error;
+
+ /* enable */
+ curr_val = ((LPS331AP_PRS_ENABLE_MASK &
+ LPS331AP_PRS_PM_NORMAL)
+ | ((~LPS331AP_PRS_ENABLE_MASK) & updated_val));
+ buf[0] = CTRL_REG1;
+ buf[1] = curr_val;
+ err = lps331ap_prs_i2c_write(prs, buf, 1);
+ if (err < 0)
+ goto error;
+
+ buf[0] = TP_RESOL;
+ buf[1] = resol;
+ err = lps331ap_prs_i2c_write(prs, buf, 1);
+ if (err < 0)
+ goto error;
+
+ prs->resume_state[LPS331AP_RES_CTRL_REG1] = curr_val;
+ prs->resume_state[LPS331AP_RES_TP_RESOL] = resol;
+ }
+ return err;
+
+error:
+ dev_err(&prs->client->dev, "update odr failed 0x%02x,0x%02x: %d\n",
+ buf[0], buf[1], err);
+
+ return err;
+}
+
+static int lps331ap_prs_set_press_reference(struct lps331ap_prs_data *prs,
+ s32 new_reference)
+{
+ int err;
+ u8 bit_valuesXL, bit_valuesL, bit_valuesH;
+ u8 buf[4];
+
+ bit_valuesXL = (u8) (new_reference & 0x0000FF);
+ bit_valuesL = (u8)((new_reference & 0x00FF00) >> 8);
+ bit_valuesH = (u8)((new_reference & 0xFF0000) >> 16);
+
+ buf[0] = (I2C_AUTO_INCREMENT | P_REF_INDATA_REG);
+ buf[1] = bit_valuesXL;
+ buf[2] = bit_valuesL;
+ buf[3] = bit_valuesH;
+
+ err = lps331ap_prs_i2c_write(prs, buf, 3);
+ if (err < 0)
+ return err;
+
+ prs->resume_state[LPS331AP_RES_REF_P_XL] = bit_valuesXL;
+ prs->resume_state[LPS331AP_RES_REF_P_L] = bit_valuesL;
+ prs->resume_state[LPS331AP_RES_REF_P_H] = bit_valuesH;
+
+ return err;
+}
+
+static int lps331ap_prs_get_press_reference(struct lps331ap_prs_data *prs,
+ s32 *buf32)
+{
+ int err;
+ u8 bit_valuesXL, bit_valuesL, bit_valuesH;
+ u8 buf[3];
+ u16 temp = 0;
+
+ buf[0] = (I2C_AUTO_INCREMENT | P_REF_INDATA_REG);
+ err = lps331ap_prs_i2c_read(prs, buf, 3);
+
+ if (err < 0)
+ return err;
+
+ bit_valuesXL = buf[0];
+ bit_valuesL = buf[1];
+ bit_valuesH = buf[2];
+
+ temp = ((bit_valuesH) << 8) | (bit_valuesL);
+ *buf32 = (s32)((((s16) temp) << 8) | (bit_valuesXL));
+#ifdef LPS331_DEBUG
+ dev_dbg(&prs->client->dev, "%s val: %+d",\
+ LPS331AP_PRS_DEV_NAME, *buf32);
+#endif
+ return err;
+}
+
+static int lps331ap_prs_set_temperature_reference(struct lps331ap_prs_data *prs,
+ s16 new_reference)
+{
+ int err;
+ u8 bit_valuesL, bit_valuesH;
+ u8 buf[3];
+
+ bit_valuesL = (u8) (new_reference & 0x00FF);
+ bit_valuesH = (u8)((new_reference & 0xFF00) >> 8);
+
+ buf[0] = (I2C_AUTO_INCREMENT | T_REF_INDATA_REG);
+ buf[1] = bit_valuesL;
+ buf[2] = bit_valuesH;
+ err = lps331ap_prs_i2c_write(prs, buf, 2);
+
+ if (err < 0)
+ return err;
+
+ prs->resume_state[LPS331AP_RES_REF_T_L] = bit_valuesL;
+ prs->resume_state[LPS331AP_RES_REF_T_H] = bit_valuesH;
+ return err;
+}
+
+static int lps331ap_prs_get_temperature_reference(struct lps331ap_prs_data *prs,
+ s16 *buf16)
+{
+ int err;
+
+ u8 bit_valuesL, bit_valuesH;
+ u8 buf[2] = {0};
+ u16 temp = 0;
+
+ buf[0] = (I2C_AUTO_INCREMENT | T_REF_INDATA_REG);
+ err = lps331ap_prs_i2c_read(prs, buf, 2);
+ if (err < 0)
+ return err;
+
+ bit_valuesL = buf[0];
+ bit_valuesH = buf[1];
+
+ temp = (((u16) bit_valuesH) << 8);
+ *buf16 = (s16)(temp | ((u16) bit_valuesL));
+
+ return err;
+}
+
+
+static int lps331ap_prs_autozero_manage(struct lps331ap_prs_data *prs,
+ u8 control)
+{
+ int err;
+ u8 buf[6];
+ u8 const mask = LPS331AP_PRS_AUTOZ_MASK;
+ u8 bit_values = LPS331AP_PRS_AUTOZ_OFF;
+ u8 init_val;
+
+ if (control >= LPS331AP_PRS_AUTOZ_ENABLE) {
+ bit_values = LPS331AP_PRS_AUTOZ_ON;
+ buf[0] = CTRL_REG2;
+ err = lps331ap_prs_i2c_read(prs, buf, 1);
+ if (err < 0)
+ return err;
+
+ init_val = buf[0];
+ prs->resume_state[LPS331AP_RES_CTRL_REG2] = init_val;
+
+ err = lps331ap_prs_register_update(prs, buf, CTRL_REG2,
+ mask, bit_values);
+ if (err < 0)
+ return err;
+ } else {
+ buf[0] = (I2C_AUTO_INCREMENT | P_REF_INDATA_REG);
+ buf[1] = 0;
+ buf[2] = 0;
+ buf[3] = 0;
+ buf[4] = 0;
+ buf[5] = 0;
+ err = lps331ap_prs_i2c_write(prs, buf, 5);
+ if (err < 0)
+ return err;
+ prs->resume_state[LPS331AP_RES_REF_P_XL] = 0;
+ prs->resume_state[LPS331AP_RES_REF_P_L] = 0;
+ prs->resume_state[LPS331AP_RES_REF_P_H] = 0;
+ prs->resume_state[LPS331AP_RES_REF_T_L] = 0;
+ prs->resume_state[LPS331AP_RES_REF_T_H] = 0;
+ }
+ return 0;
+}
+
+
+static int lps331ap_prs_get_presstemp_data(struct lps331ap_prs_data *prs,
+ struct outputdata *out)
+{
+ int err = 0;
+ /* Data bytes from hardware PRESS_OUT_XL,PRESS_OUT_L,PRESS_OUT_H, */
+ /* TEMP_OUT_L, TEMP_OUT_H */
+
+ u8 prs_data[5] = {0,};
+
+ s32 pressure = 0;
+ s16 temperature = 0;
+
+ int regToRead = 5;
+
+ prs_data[0] = (I2C_AUTO_INCREMENT | OUTDATA_REG);
+ err = lps331ap_prs_i2c_read(prs, prs_data, regToRead);
+ if (err < 0)
+ return err;
+
+#ifdef LPS331_DEBUG
+ dev_dbg(&prs->client->dev, "temp out tH = 0x%02x, tL = 0x%02x,"
+ "press_out: pH = 0x%02x, pL = 0x%02x, pXL= 0x%02x\n",
+ prs_data[4],
+ prs_data[3],
+ prs_data[2],
+ prs_data[1],
+ prs_data[0]);
+#endif
+
+ pressure = (s32)((((s8) prs_data[2]) << 16) |
+ (prs_data[1] << 8) |
+ (prs_data[0]));
+ temperature = (s16) ((((s8) prs_data[4]) << 8) | (prs_data[3]));
+
+ out->press = pressure;
+ out->temperature = temperature;
+
+ return err;
+}
+
+static int lps331ap_prs_acquire_compensation_data(struct lps331ap_prs_data *prs)
+{
+ int err;
+ /* Data bytes from hardware PRESS_OUT_XL,PRESS_OUT_L,PRESS_OUT_H, */
+ /* TEMP_OUT_L, TEMP_OUT_H */
+
+ u8 compens_data[10];
+ u8 gain_data[3];
+ u8 delta_data[2];
+ u8 dT1, dT23;
+ u8 calSetup;
+ int regToRead = 10;
+
+ compens_data[0] = (I2C_AUTO_INCREMENT | COMPENS_L);
+ err = lps331ap_prs_i2c_read(prs, compens_data, regToRead);
+ if (err < 0)
+ return err;
+
+ regToRead = 3;
+ gain_data[0] = (I2C_AUTO_INCREMENT | DGAIN_L);
+ err = lps331ap_prs_i2c_read(prs, gain_data, regToRead);
+ if (err < 0)
+ return err;
+
+ regToRead = 1;
+ delta_data[0] = (DELTA_T1);
+ err = lps331ap_prs_i2c_read(prs, delta_data, regToRead);
+ if (err < 0)
+ return err;
+ dT1 = delta_data[0];
+
+ regToRead = 1;
+ delta_data[0] = (DELTA_T2T3);
+ err = lps331ap_prs_i2c_read(prs, delta_data, regToRead);
+ if (err < 0)
+ return err;
+ dT23 = delta_data[0];
+
+ regToRead = 1;
+ delta_data[0] = (CALIB_SETUP);
+ err = lps331ap_prs_i2c_read(prs, delta_data, regToRead);
+ if (err < 0)
+ return err;
+ calSetup = delta_data[0];
+
+#ifdef LPS331_DEBUG
+ /* dT1 = 0xD1;
+ dT23 = 0x20;
+ calSetup = 0x80;
+ dev_info(&prs_client->dev, "forced registers 0x3b, 0x3f, 0x1e"
+ " values for debug\n"); */
+ dev_info(&prs->client->dev, "reg\n 0x30 = 0x%02x\n 0x31 = 0x%02x\n "
+ "0x32 = 0x%02x\n 0x33 = 0x%02x\n 0x34 = 0x%02x\n "
+ "0x35 = 0x%02x\n 0x36 = 0x%02x\n 0x37 = 0x%02x\n "
+ "0x38 = 0x%02x\n 0x39 = 0x%02x\n",
+ compens_data[0],
+ compens_data[1],
+ compens_data[2],
+ compens_data[3],
+ compens_data[4],
+ compens_data[5],
+ compens_data[6],
+ compens_data[7],
+ compens_data[8],
+ compens_data[9]
+ );
+
+ dev_info(&prs->client->dev,
+ "reg\n 0x18 = 0x%02x\n 0x19 = 0x%02x\n 0x1A = 0x%02x\n",
+ gain_data[0],
+ gain_data[1],
+ gain_data[2]
+ );
+
+ dev_info(&prs->client->dev,
+ "reg\n 0x3b = 0x%02x\n 0x3f = 0x%02x\n 0x1e = 0x%02x\n",
+ dT1,
+ dT23,
+ calSetup
+ );
+
+#endif
+
+ prs->TSL = (u16) ((compens_data[0] & 0xFC) >> 2);
+
+ prs->TSH = (u16) (compens_data[1] & 0x3F);
+
+ prs->TCV1 = (u32) ((((compens_data[3] & 0x03) << 16) |
+ ((compens_data[2] & 0xFF) << 8) |
+ (compens_data[1] & 0xC0)) >> 6);
+
+ prs->TCV2 = (u32) ((((compens_data[4] & 0x3F) << 8) |
+ (compens_data[3] & 0xFC)) >> 2);
+
+ prs->TCV3 = (u32) ((((compens_data[6] & 0x03) << 16) |
+ ((compens_data[5] & 0xFF) << 8) |
+ (compens_data[4] & 0xC0)) >> 6);
+
+
+
+ prs->TCS1 = (u32) ((((compens_data[7] & 0x0F) << 8) |
+ (compens_data[6] & 0xFC)) >> 2);
+
+ prs->TCS2 = (u32) ((((compens_data[8] & 0x3F) << 8) |
+ (compens_data[7] & 0xF0)) >> 4);
+
+ prs->TCS3 = (u32) ((((compens_data[9] & 0xFF) << 8) |
+ (compens_data[8] & 0xC0)) >> 6);
+
+ prs->digGain = (u32) ((((gain_data[2] & 0x0F) << 16) |
+ ((gain_data[1] & 0xFF) << 8) |
+ (gain_data[0] & 0xFC)) >> 2);
+
+#ifdef LPS331_DEBUG
+ /*dT1 = 0xE0;*/
+ dev_info(&prs->client->dev, "test dT1 = 0x%08x\n", dT1);
+#endif
+ prs->deltaT1 = (((s8)(dT1 & 0xF0)) >> 4);
+#ifdef LPS331_DEBUG
+ dev_info(&prs->client->dev, "test deltaT1 = 0x%08x\n", prs->deltaT1);
+
+ /*dT23 = 0xE0;*/
+ dev_info(&prs->client->dev, "test dT23 = 0x%08x\n", dT23);
+#endif
+ prs->deltaT2 = (((s8)(dT23 & 0xF0)) >> 4);
+#ifdef LPS331_DEBUG
+ dev_info(&prs->client->dev, "test deltaT2 = 0x%08x\n", prs->deltaT2);
+
+ /*dT23 = 0x0E;*/
+ dev_info(&prs->client->dev, "test dT23 = 0x%08x\n", dT23);
+#endif
+ prs->deltaT3 = (((s8)((dT23 & 0x0F) << 4)) >> 4);
+#ifdef LPS331_DEBUG
+ dev_info(&prs->client->dev, "test deltaT3 = 0x%08x\n", prs->deltaT3);
+ /* calSetup = 0xe0; */
+ dev_info(&prs->client->dev, "test calSetup = 0x%08x\n", calSetup);
+#endif
+#ifdef LPS331_DEBUG
+ dev_info(&prs->client->dev, "reg TSL = %d, TSH = %d,"
+ " TCV1 = %d, TCV2 = %d, TCV3 = %d,"
+ " TCS1 = %d, TCS2 = %d, TCS3 = %d,"
+ " DGAIN = %d,"
+ " deltaT1 = %d, deltaT2 = %d,"
+ " deltaT3 = %d,"
+ " testVer = %d\n",
+ prs->TSL,
+ prs->TSH,
+ prs->TCV1,
+ prs->TCV2,
+ prs->TCV3,
+ prs->TCS1,
+ prs->TCS2,
+ prs->TCS3,
+ prs->digGain,
+ prs->deltaT1,
+ prs->deltaT2,
+ prs->deltaT3,
+ prs->testVer
+ );
+#endif
+
+ return err;
+}
+
+static void lps331ap_prs_report_values(struct lps331ap_prs_data *prs,
+ struct outputdata *out)
+{
+ if (out->press == 0) {
+ pr_info("%s, our->press = 0\n", __func__);
+ out->press = -1;
+ }
+ if (out->temperature == 0) {
+ pr_info("%s, our->temperature = 0\n", __func__);
+ out->temperature = -1;
+ }
+ input_report_rel(prs->input_dev, REL_X, out->press);
+ input_report_rel(prs->input_dev, REL_Z, out->temperature);
+ input_sync(prs->input_dev);
+#ifdef LPS331_DEBUG
+ pr_info("%s, pressure = %d, temperature = %d\n",
+ __func__, out->press, out->temperature);
+#endif
+}
+
+static ssize_t lps331ap_get_press_ref(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int err;
+ struct lps331ap_prs_data *prs = dev_get_drvdata(dev);
+ s32 val = 0;
+
+ mutex_lock(&prs->lock);
+ err = lps331ap_prs_get_press_reference(prs, &val);
+ mutex_unlock(&prs->lock);
+ if (err < 0)
+ return err;
+
+ return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t lps331ap_set_press_ref(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int err = -1;
+ struct lps331ap_prs_data *prs = dev_get_drvdata(dev);
+ long val = 0;
+
+ if (strict_strtol(buf, 10, &val))
+ return -EINVAL;
+
+ if (val < PR_ABS_MIN || val > PR_ABS_MAX)
+ return -EINVAL;
+
+ mutex_lock(&prs->lock);
+ err = lps331ap_prs_set_press_reference(prs, val);
+ mutex_unlock(&prs->lock);
+ if (err < 0)
+ return err;
+ return size;
+}
+
+static ssize_t lps331ap_get_temperature_ref(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int err;
+ struct lps331ap_prs_data *prs = dev_get_drvdata(dev);
+ s16 val = 0;
+
+ mutex_lock(&prs->lock);
+ err = lps331ap_prs_get_temperature_reference(prs, &val);
+ mutex_unlock(&prs->lock);
+ if (err < 0)
+ return err;
+
+ return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t lps331ap_set_temperature_ref(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int err = -1;
+ struct lps331ap_prs_data *prs = dev_get_drvdata(dev);
+ long val = 0;
+
+ if (strict_strtol(buf, 10, &val))
+ return -EINVAL;
+
+
+ if (val < TEMP_MIN || val > TEMP_MAX)
+ return -EINVAL;
+
+
+ mutex_lock(&prs->lock);
+ err = lps331ap_prs_set_temperature_reference(prs, val);
+ mutex_unlock(&prs->lock);
+ if (err < 0)
+ return err;
+ return size;
+}
+
+static ssize_t lps331ap_set_autozero(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int err;
+ struct lps331ap_prs_data *prs = dev_get_drvdata(dev);
+ unsigned long val;
+
+ if (strict_strtoul(buf, 10, &val))
+ return -EINVAL;
+
+ mutex_lock(&prs->lock);
+ err = lps331ap_prs_autozero_manage(prs, (u8) val);
+ mutex_unlock(&prs->lock);
+ if (err < 0)
+ return err;
+ return size;
+}
+
+static ssize_t lps331ap_get_compensation_param(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lps331ap_prs_data *prs = dev_get_drvdata(dev);
+ return sprintf(buf,
+ "%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
+ prs->TSL,
+ prs->TSH,
+ prs->TCV1,
+ prs->TCV2,
+ prs->TCV3,
+ prs->TCS1,
+ prs->TCS2,
+ prs->TCS3,
+ prs->digGain,
+ prs->deltaT1,
+ prs->deltaT2,
+ prs->deltaT3,
+ prs->testVer
+ );
+}
+
+#ifdef LPS331_DEBUG
+static ssize_t lps331ap_reg_set(struct device *dev,\
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ int rc;
+ struct lps331ap_prs_data *prs = dev_get_drvdata(dev);
+ u8 x[2];
+ unsigned long val;
+
+ if (strict_strtoul(buf, 16, &val))
+ return -EINVAL;
+ mutex_lock(&prs->lock);
+ x[0] = prs->reg_addr;
+ mutex_unlock(&prs->lock);
+ x[1] = val;
+ rc = lps331ap_prs_i2c_write(prs, x, 1);
+ if (rc < 0)
+ return rc;
+ return size;
+}
+
+static ssize_t lps331ap_reg_get(struct device *dev,\
+ struct device_attribute *attr, char *buf)
+{
+ ssize_t ret;
+ struct lps331ap_prs_data *prs = dev_get_drvdata(dev);
+ int rc;
+ u8 data;
+
+ mutex_lock(&prs->lock);
+ data = prs->reg_addr;
+ mutex_unlock(&prs->lock);
+ rc = lps331ap_prs_i2c_read(prs, &data, 1);
+ if (rc < 0)
+ return rc;
+ ret = sprintf(buf, "0x%02x\n", data);
+ return ret;
+}
+
+static ssize_t lps331ap_addr_set(struct device *dev, \
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct lps331ap_prs_data *prs = dev_get_drvdata(dev);
+ unsigned long val;
+ if (strict_strtoul(buf, 16, &val))
+ return -EINVAL;
+ mutex_lock(&prs->lock);
+ prs->reg_addr = val;
+ mutex_unlock(&prs->lock);
+ return size;
+}
+#endif
+static int lps331ap_prs_enable(struct lps331ap_prs_data *prs)
+{
+ int err = 0;
+
+ if (!atomic_cmpxchg(&prs->enabled, 0, 1)) {
+ lps331ap_open_calibration(prs);
+ err = lps331ap_prs_device_power_on(prs);
+ if (err < 0) {
+ atomic_set(&prs->enabled, 0);
+ return err;
+ }
+ schedule_delayed_work(&prs->input_work,
+ msecs_to_jiffies(prs->poll_delay));
+ }
+
+ return 0;
+}
+
+static int lps331ap_prs_disable(struct lps331ap_prs_data *prs)
+{
+ printk(KERN_INFO "%s\n", __func__);
+ if (atomic_cmpxchg(&prs->enabled, 1, 0)) {
+ cancel_delayed_work_sync(&prs->input_work);
+ lps331ap_prs_device_power_off(prs);
+ }
+
+ return 0;
+}
+
+static ssize_t lps331ap_get_poll_delay(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int val;
+ struct lps331ap_prs_data *prs = dev_get_drvdata(dev);
+ mutex_lock(&prs->lock);
+ val = prs->poll_delay;
+ mutex_unlock(&prs->lock);
+ return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t lps331ap_set_poll_delay(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct lps331ap_prs_data *prs = dev_get_drvdata(dev);
+ unsigned long delay_ms = 0;
+ unsigned int delay_min = DELAY_MINIMUM;
+
+ if (strict_strtoul(buf, 10, &delay_ms))
+ return -EINVAL;
+ if (!delay_ms)
+ return -EINVAL;
+
+ printk(KERN_INFO "%s, delay_ms passed = %ld\n", __func__, delay_ms);
+ delay_ms = max_t(unsigned int, (unsigned int)delay_ms, delay_min);
+
+ if (delay_ms == DELAY_MINIMUM)
+ printk(KERN_INFO "%s, minimum delay is 40ms!\n", __func__);
+
+ mutex_lock(&prs->lock);
+ prs->poll_delay = (unsigned int)delay_ms;
+ lps331ap_prs_update_odr(prs, delay_ms);
+ mutex_unlock(&prs->lock);
+ return size;
+}
+
+static ssize_t lps331ap_get_enable(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lps331ap_prs_data *prs = dev_get_drvdata(dev);
+ int val = atomic_read(&prs->enabled);
+ return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t lps331ap_set_enable(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct lps331ap_prs_data *prs = dev_get_drvdata(dev);
+ unsigned long val;
+
+ printk(KERN_INFO "%s\n", __func__);
+ if (strict_strtoul(buf, 10, &val))
+ return -EINVAL;
+
+ if (val)
+ lps331ap_prs_enable(prs);
+ else
+ lps331ap_prs_disable(prs);
+
+ return size;
+}
+
+static DEVICE_ATTR(poll_delay, 0664,
+ lps331ap_get_poll_delay, lps331ap_set_poll_delay);
+
+static DEVICE_ATTR(enable, 0664,
+ lps331ap_get_enable, lps331ap_set_enable);
+
+static DEVICE_ATTR(pressure_reference_level, 0664,
+ lps331ap_get_press_ref, lps331ap_set_press_ref);
+static DEVICE_ATTR(temperature_reference_level, 0664,
+ lps331ap_get_temperature_ref, lps331ap_set_temperature_ref);
+static DEVICE_ATTR(enable_autozero, 0220,
+ NULL, lps331ap_set_autozero);
+static DEVICE_ATTR(compensation_param, 0444,
+ lps331ap_get_compensation_param, NULL);
+#ifdef LPS331_DEBUG
+static DEVICE_ATTR(reg_value, 0664,
+ lps331ap_reg_get, lps331ap_reg_set);
+static DEVICE_ATTR(reg_addr, 0220,
+ NULL, lps331ap_addr_set);
+#endif
+static struct attribute *barometer_sysfs_attrs[] = {
+ &dev_attr_enable.attr,
+ &dev_attr_poll_delay.attr,
+ &dev_attr_pressure_reference_level.attr,
+ &dev_attr_temperature_reference_level.attr,
+ &dev_attr_enable_autozero.attr,
+ &dev_attr_compensation_param.attr,
+#ifdef LPS331_DEBUG
+ &dev_attr_reg_value.attr,
+ &dev_attr_reg_addr.attr,
+#endif
+ NULL
+};
+
+static struct attribute_group barometer_attribute_group = {
+ .attrs = barometer_sysfs_attrs,
+};
+
+static ssize_t sea_level_pressure_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct lps331ap_prs_data *prs = dev_get_drvdata(dev);
+ int new_sea_level_pressure;
+
+ sscanf(buf, "%d", &new_sea_level_pressure);
+
+ if (new_sea_level_pressure == 0) {
+ pr_info("%s, our->temperature = 0\n", __func__);
+ new_sea_level_pressure = -1;
+ }
+ input_report_rel(prs->input_dev, REL_Y, new_sea_level_pressure);
+ input_sync(prs->input_dev);
+
+ return size;
+}
+
+static void lps331ap_save_caldata(struct lps331ap_prs_data *prs,
+ s32 pressure_cal)
+{
+ u8 pressure_offset[3] = {0,};
+ int err = 0;
+ u8 buf[4] = {0,};
+#ifdef LPS331_DEBUG
+ struct file *cal_filp = NULL;
+ mm_segment_t old_fs;
+ char buf_ref[10] = {0,};
+ int i, buf_size = 0;
+#endif
+
+ pr_info("%s, pressure_cal = %d\n", __func__, pressure_cal);
+ prs->pressure_cal = pressure_cal;
+
+ /* set delta register */
+ pressure_offset[0] = (u8)(0xFF & (s32)prs->pressure_cal);
+ pressure_offset[1] = (u8)(0xFF & (s32)prs->pressure_cal >> 8);
+ pressure_offset[2] = (u8)(0xFF & (s32)prs->pressure_cal >> 16);
+ buf[0] = (I2C_AUTO_INCREMENT | DELTAREG_1);
+ buf[1] = pressure_offset[0];
+ buf[2] = pressure_offset[1];
+ buf[3] = pressure_offset[2];
+ err = lps331ap_prs_i2c_write(prs, buf, 3);
+ if (err < 0) {
+ pr_err("%s, save_cal_data failed(err=%d)\n",
+ __func__, err);
+ return;
+ }
+#ifdef LPS331_DEBUG
+ /* change to string */
+ sprintf(buf_ref, "%d", prs->pressure_cal);
+ /* get size of the cal value */
+ for (i = 0; i < 10; i++) {
+ if (buf_ref[i] == '\0') {
+ buf_size = i+1;
+ break;
+ }
+ }
+
+ /* Save in the file. */
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+
+ cal_filp = filp_open(CALIBRATION_FILE_PATH,
+ O_CREAT | O_TRUNC | O_WRONLY, 0666);
+ if (IS_ERR(cal_filp)) {
+ set_fs(old_fs);
+ err = PTR_ERR(cal_filp);
+ pr_err("%s: Can't open calibration file(err=%d)\n",
+ __func__, err);
+ return;
+ }
+
+ err = cal_filp->f_op->write(cal_filp,
+ buf_ref, buf_size * sizeof(char), &cal_filp->f_pos);
+ if (err < 0) {
+ pr_err("%s: Can't write the cal data to file(err=%d)\n",
+ __func__, err);
+ }
+
+ filp_close(cal_filp, current->files);
+ set_fs(old_fs);
+#endif
+}
+
+static void lps331ap_open_calibration(struct lps331ap_prs_data *prs)
+{
+ struct file *cal_filp = NULL;
+ int err = 0;
+ mm_segment_t old_fs;
+ u8 buf[4] = {0,};
+ char buf_ref[10] = {0,};
+
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+
+ cal_filp = filp_open(CALIBRATION_FILE_PATH, O_RDONLY, 0666);
+ if (IS_ERR(cal_filp)) {
+ err = PTR_ERR(cal_filp);
+ if (err != -ENOENT)
+ pr_err("%s: Can't open calibration file(err=%d)\n",
+ __func__, err);
+ set_fs(old_fs);
+ return;
+ }
+ err = cal_filp->f_op->read(cal_filp,
+ buf_ref, 10 * sizeof(char), &cal_filp->f_pos);
+ if (err < 0) {
+ pr_err("%s: Can't read the cal data from file (err=%d)\n",
+ __func__, err);
+ return;
+ }
+ filp_close(cal_filp, current->files);
+ set_fs(old_fs);
+
+ err = kstrtoint(buf_ref, 10, &prs->pressure_cal);
+ if (err < 0) {
+ pr_err("%s, kstrtoint failed.(err = %d)", __func__, err);
+ return;
+ }
+ pr_info("%s, prs->pressure_cal = %d\n", __func__, prs->pressure_cal);
+ if (prs->pressure_cal < PR_ABS_MIN || prs->pressure_cal > PR_ABS_MAX) {
+ pr_err("%s, wrong offset value!!!\n", __func__);
+ return;
+ }
+
+ buf[0] = (I2C_AUTO_INCREMENT | DELTAREG_1);
+ buf[1] = (u8)(0xFF & prs->pressure_cal);
+ buf[2] = (u8)(0xFF & prs->pressure_cal >> 8);
+ buf[3] = (u8)(0xFF & prs->pressure_cal >> 16);
+ err = lps331ap_prs_i2c_write(prs, buf, 3);
+ if (err < 0)
+ pr_err("%s, write_delta_reg failed(err=%d)\n", __func__, err);
+
+ return;
+}
+
+static ssize_t lps331ap_cabratioin_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct lps331ap_prs_data *prs = dev_get_drvdata(dev);
+ int pressure_cal = 0, err = 0;
+
+ err = kstrtoint(buf, 10, &pressure_cal);
+ if (err < 0) {
+ pr_err("%s, kstrtoint failed.(err = %d)", __func__, err);
+ return err;
+ }
+
+ if (pressure_cal < PR_ABS_MIN || pressure_cal > PR_ABS_MAX)
+ return -EINVAL;
+
+ lps331ap_save_caldata(prs, (s32)pressure_cal);
+
+ return size;
+}
+
+static ssize_t lps331ap_cabratioin_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct lps331ap_prs_data *prs = dev_get_drvdata(dev);
+
+ if (!atomic_read(&prs->enabled))
+ lps331ap_open_calibration(prs);
+
+ return sprintf(buf, "%d\n", prs->pressure_cal);
+}
+
+/* sysfs for vendor & name */
+static ssize_t lps331_vendor_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", VENDOR);
+}
+
+static ssize_t lps331_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", CHIP_ID);
+}
+static DEVICE_ATTR(vendor, 0644, lps331_vendor_show, NULL);
+static DEVICE_ATTR(name, 0644, lps331_name_show, NULL);
+
+static DEVICE_ATTR(calibration, 0664,
+ lps331ap_cabratioin_show, lps331ap_cabratioin_store);
+static DEVICE_ATTR(sea_level_pressure, 0664,
+ NULL, sea_level_pressure_store);
+
+static void lps331ap_prs_input_work_func(struct work_struct *work)
+{
+ struct lps331ap_prs_data *prs = container_of(
+ (struct delayed_work *)work,
+ struct lps331ap_prs_data,
+ input_work);
+
+ static struct outputdata output;
+ int err = 0;
+
+ mutex_lock(&prs->lock);
+ err = lps331ap_prs_get_presstemp_data(prs, &output);
+ if (err < 0)
+ dev_err(&prs->client->dev, "get_pressure_data failed\n");
+ else
+ lps331ap_prs_report_values(prs, &output);
+
+ schedule_delayed_work(&prs->input_work,
+ msecs_to_jiffies(prs->poll_delay));
+ mutex_unlock(&prs->lock);
+}
+
+int lps331ap_prs_input_open(struct input_dev *input)
+{
+ struct lps331ap_prs_data *prs = input_get_drvdata(input);
+
+ return lps331ap_prs_enable(prs);
+}
+
+void lps331ap_prs_input_close(struct input_dev *dev)
+{
+ lps331ap_prs_disable(input_get_drvdata(dev));
+}
+
+static int lps331ap_prs_input_init(struct lps331ap_prs_data *prs)
+{
+ int err;
+
+ INIT_DELAYED_WORK(&prs->input_work, lps331ap_prs_input_work_func);
+ prs->input_dev = input_allocate_device();
+ if (!prs->input_dev) {
+ err = -ENOMEM;
+ dev_err(&prs->client->dev, "input device allocate failed\n");
+ goto err0;
+ }
+
+ prs->input_dev->name = "barometer_sensor";
+ input_set_drvdata(prs->input_dev, prs);
+
+ /* temperature */
+ input_set_capability(prs->input_dev, EV_REL, REL_Z);
+
+ /* reference altitude */
+ input_set_capability(prs->input_dev, EV_REL, REL_Y);
+
+ /* pressure */
+ input_set_capability(prs->input_dev, EV_REL, REL_X);
+
+ err = input_register_device(prs->input_dev);
+ if (err) {
+ dev_err(&prs->client->dev,
+ "unable to register input polled device %s\n",
+ prs->input_dev->name);
+ goto err1;
+ }
+
+ return 0;
+
+err1:
+ input_free_device(prs->input_dev);
+err0:
+ return err;
+}
+
+static int lps331ap_prs_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct lps331ap_prs_data *prs;
+ int err = -1;
+ int tempvalue;
+
+ pr_info("%s: probe start.\n", LPS331AP_PRS_DEV_NAME);
+
+ if (client->dev.platform_data == NULL) {
+ dev_err(&client->dev, "platform data is NULL. exiting.\n");
+ err = -ENODATA;
+ goto err_exit_check_functionality_failed;
+ }
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "client not i2c capable\n");
+ err = -ENODEV;
+ goto err_exit_check_functionality_failed;
+ }
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE |
+ I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_WORD_DATA)) {
+ dev_err(&client->dev, "client not smb-i2c capable:2\n");
+ err = -EIO;
+ goto err_exit_check_functionality_failed;
+ }
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_I2C_BLOCK)) {
+ dev_err(&client->dev, "client not smb-i2c capable:3\n");
+ err = -EIO;
+ goto err_exit_check_functionality_failed;
+ }
+
+ prs = kzalloc(sizeof(struct lps331ap_prs_data), GFP_KERNEL);
+ if (prs == NULL) {
+ err = -ENOMEM;
+ dev_err(&client->dev,
+ "failed to allocate memory for module data: "
+ "%d\n", err);
+ goto err_exit_alloc_data_failed;
+ }
+
+ mutex_init(&prs->lock);
+ mutex_lock(&prs->lock);
+
+ prs->client = client;
+ i2c_set_clientdata(client, prs);
+
+ if (i2c_smbus_read_byte(client) < 0) {
+ pr_err("%s:i2c_smbus_read_byte error!!\n",
+ LPS331AP_PRS_DEV_NAME);
+ goto err_mutexunlockfreedata;
+ } else {
+ dev_dbg(&prs->client->dev, "%s Device detected!\n",
+ LPS331AP_PRS_DEV_NAME);
+ }
+
+ /* read chip id */
+ tempvalue = i2c_smbus_read_word_data(client, WHO_AM_I);
+ if ((tempvalue & 0x00FF) == WHOAMI_LPS331AP_PRS) {
+ pr_info("%s I2C driver registered!\n", LPS331AP_PRS_DEV_NAME);
+ } else {
+ prs->client = NULL;
+ err = -ENODEV;
+ dev_err(&client->dev, "I2C driver not registered."
+ " Device unknown: %d\n", err);
+ goto err_mutexunlockfreedata;
+ }
+
+ memset(prs->resume_state, 0, ARRAY_SIZE(prs->resume_state));
+ /* init registers which need values different from zero */
+ prs->resume_state[LPS331AP_RES_CTRL_REG1] =
+ (
+ (LPS331AP_PRS_ENABLE_MASK & LPS331AP_PRS_PM_NORMAL) |
+ (LPS331AP_PRS_ODR_MASK & LPS331AP_PRS_ODR_1_1) |
+ (LPS331AP_PRS_BDU_MASK & LPS331AP_PRS_BDU_ON) |
+ (LPS331AP_PRS_DELTA_EN_MASK & LPS331AP_PRS_DELTA_EN_ON)
+ );
+
+ prs->resume_state[LPS331AP_RES_TP_RESOL] = LPS331AP_PRS_RES_MAX;
+/*
+ prs->resume_state[LPS331AP_RES_CTRL_REG1] =
+ (LPS331AP_PRS_PM_NORMAL | LPS331AP_PRS_ODR_1_1 |
+ LPS331AP_PRS_BDU_ON));
+ prs->resume_state[LPS331AP_RES_CTRL_REG2] = 0x00;
+ prs->resume_state[LPS331AP_RES_CTRL_REG3] = 0x00;
+ prs->resume_state[LPS331AP_RES_REF_P_L] = 0x00;
+ prs->resume_state[LPS331AP_RES_REF_P_H] = 0x00;
+ prs->resume_state[LPS331AP_RES_THS_P_L] = 0x00;
+ prs->resume_state[LPS331AP_RES_THS_P_H] = 0x00;
+ prs->resume_state[LPS331AP_RES_INT_CFG] = 0x00;
+*/
+
+ err = lps331ap_prs_device_power_on(prs);
+ if (err < 0) {
+ dev_err(&client->dev, "power on failed: %d\n", err);
+ goto err_mutexunlockfreedata;
+ }
+
+ atomic_set(&prs->enabled, 1);
+
+ prs->poll_delay = DELAY_DEFAULT;
+ err = lps331ap_prs_update_odr(prs, prs->poll_delay);
+ if (err < 0) {
+ dev_err(&client->dev, "update_odr failed\n");
+ goto err_power_off;
+ }
+
+ err = lps331ap_prs_input_init(prs);
+ if (err < 0) {
+ dev_err(&client->dev, "input init failed\n");
+ goto err_power_off;
+ }
+
+ err = lps331ap_prs_acquire_compensation_data(prs);
+ if (err < 0) {
+ dev_err(&client->dev, "compensation data acquisition failed\n");
+ goto err_input_cleanup;
+ }
+
+ err = sysfs_create_group(&prs->input_dev->dev.kobj,
+ &barometer_attribute_group);
+ if (err) {
+ pr_err("%s: could not create sysfs group\n", __func__);
+ goto err_input_cleanup;
+ }
+
+ lps331ap_prs_device_power_off(prs);
+
+ /* As default, do not report information */
+ atomic_set(&prs->enabled, 0);
+
+ /* sysfs for factory test */
+ prs->dev = sensors_classdev_register("barometer_sensor");
+ if (IS_ERR(prs->dev)) {
+ err = PTR_ERR(prs->dev);
+ pr_err("%s: device_create failed[%d]\n", __func__, err);
+ goto err_device_create;
+ }
+
+ err = device_create_file(prs->dev, &dev_attr_sea_level_pressure);
+ if (err < 0) {
+ pr_err("%s: device_create(sea_level_pressure) failed\n",
+ __func__);
+ goto err_device_create_file1;
+ }
+
+ err = device_create_file(prs->dev, &dev_attr_calibration);
+ if (err < 0) {
+ pr_err("%s: device_create(vendor) failed\n",
+ __func__);
+ goto err_device_create_file2;
+ }
+
+ err = device_create_file(prs->dev, &dev_attr_vendor);
+ if (err < 0) {
+ pr_err("%s: device_create(vendor) failed\n",
+ __func__);
+ goto err_device_create_file3;
+ }
+
+ err = device_create_file(prs->dev, &dev_attr_name);
+ if (err < 0) {
+ pr_err("%s: device_create(name) failed\n",
+ __func__);
+ goto err_device_create_file4;
+ }
+
+ dev_set_drvdata(prs->dev, prs);
+
+ mutex_unlock(&prs->lock);
+
+ dev_info(&client->dev, "%s: probed\n", LPS331AP_PRS_DEV_NAME);
+
+ return 0;
+
+/* error, unwind it all */
+err_device_create_file4:
+ device_remove_file(prs->dev, &dev_attr_vendor);
+err_device_create_file3:
+ device_remove_file(prs->dev, &dev_attr_calibration);
+err_device_create_file2:
+ device_remove_file(prs->dev, &dev_attr_sea_level_pressure);
+err_device_create_file1:
+ sensors_classdev_unregister(prs->dev);
+err_device_create:
+ sysfs_remove_group(&prs->input_dev->dev.kobj,
+ &barometer_attribute_group);
+err_input_cleanup:
+ input_unregister_device(prs->input_dev);
+err_power_off:
+ lps331ap_prs_device_power_off(prs);
+err_mutexunlockfreedata:
+ mutex_unlock(&prs->lock);
+ kfree(prs);
+err_exit_alloc_data_failed:
+err_exit_check_functionality_failed:
+ pr_err("%s: Driver Init failed\n", LPS331AP_PRS_DEV_NAME);
+ return err;
+}
+
+static int __devexit lps331ap_prs_remove(struct i2c_client *client)
+{
+ struct lps331ap_prs_data *prs = i2c_get_clientdata(client);
+
+ device_remove_file(prs->dev, &dev_attr_calibration);
+ device_remove_file(prs->dev, &dev_attr_name);
+ device_remove_file(prs->dev, &dev_attr_vendor);
+ device_remove_file(prs->dev, &dev_attr_sea_level_pressure);
+ sensors_classdev_unregister(prs->dev);
+ input_unregister_device(prs->input_dev);
+ lps331ap_prs_device_power_off(prs);
+ sysfs_remove_group(&prs->input_dev->dev.kobj,
+ &barometer_attribute_group);
+
+ kfree(prs);
+
+ return 0;
+}
+
+static int lps331ap_prs_resume(struct i2c_client *client)
+{
+ struct lps331ap_prs_data *prs = i2c_get_clientdata(client);
+ int err = 0;
+ u8 buf[2] = {0,};
+
+ if (atomic_read(&prs->enabled)) {
+ buf[0] = CTRL_REG1;
+ buf[1] = prs->resume_state[LPS331AP_RES_CTRL_REG1];
+ err = lps331ap_prs_i2c_write(prs, buf, 1);
+ if (err < 0)
+ dev_err(&prs->client->dev, "resume failed: %d\n", err);
+ schedule_delayed_work(&prs->input_work,
+ msecs_to_jiffies(prs->poll_delay));
+ pr_info("%s\n", __func__);
+ }
+
+ return err;
+}
+
+static int lps331ap_prs_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+ struct lps331ap_prs_data *prs = i2c_get_clientdata(client);
+ int err = 0;
+ u8 buf[2] = {0,};
+
+ if (atomic_read(&prs->enabled)) {
+ cancel_delayed_work_sync(&prs->input_work);
+ buf[0] = CTRL_REG1;
+ buf[1] = LPS331AP_PRS_PM_OFF;
+ err = lps331ap_prs_i2c_write(prs, buf, 1);
+ if (err < 0)
+ dev_err(&prs->client->dev, "suspend failed: %d\n", err);
+ pr_info("%s\n", __func__);
+ }
+
+ return err;
+}
+
+static const struct i2c_device_id lps331ap_prs_id[]
+ = { { LPS331AP_PRS_DEV_NAME, 0}, { },};
+
+MODULE_DEVICE_TABLE(i2c, lps331ap_prs_id);
+
+static struct i2c_driver lps331ap_prs_driver = {
+ .driver = {
+ .name = LPS331AP_PRS_DEV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = lps331ap_prs_probe,
+ .remove = __devexit_p(lps331ap_prs_remove),
+ .id_table = lps331ap_prs_id,
+ .resume = lps331ap_prs_resume,
+ .suspend = lps331ap_prs_suspend,
+};
+
+static int __init lps331ap_prs_init(void)
+{
+#ifdef LPS331_DEBUG
+ pr_debug("%s barometer driver: init\n", LPS331AP_PRS_DEV_NAME);
+#endif
+ return i2c_add_driver(&lps331ap_prs_driver);
+}
+
+static void __exit lps331ap_prs_exit(void)
+{
+#ifdef LPS331_DEBUG
+ pr_debug("%s barometer driver exit\n", LPS331AP_PRS_DEV_NAME);
+#endif
+ i2c_del_driver(&lps331ap_prs_driver);
+ return;
+}
+
+module_init(lps331ap_prs_init);
+module_exit(lps331ap_prs_exit);
+
+MODULE_DESCRIPTION("STMicrolelectronics lps331ap pressure sensor sysfs driver");
+MODULE_AUTHOR("Matteo Dameno, Carmine Iascone, STMicroelectronics");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sensor/lsm330dlc_accel.c b/drivers/sensor/lsm330dlc_accel.c
new file mode 100644
index 0000000..a564d52
--- /dev/null
+++ b/drivers/sensor/lsm330dlc_accel.c
@@ -0,0 +1,1402 @@
+/*
+ * STMicroelectronics lsm330dlc_accel acceleration sensor driver
+ *
+ * Copyright (C) 2011 Samsung Electronics Co.Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/sensor/lsm330dlc_accel.h>
+#include <linux/sensor/sensors_core.h>
+#include <linux/printk.h>
+#include <linux/wakelock.h>
+
+/* For debugging */
+#if 1
+#define accel_dbgmsg(str, args...)\
+ pr_info("%s: " str, __func__, ##args)
+#else
+#define accel_dbgmsg(str, args...)\
+ pr_debug("%s: " str, __func__, ##args)
+#endif
+
+#undef LSM330DLC_ACCEL_LOGGING
+#undef DEBUG_ODR
+#undef DEBUG_REACTIVE_ALERT
+/* It will be used, when google fusion is enabled. */
+#undef USES_INPUT_DEV
+
+#define VENDOR "STM"
+#define CHIP_ID "LSM330"
+
+/* The default settings when sensor is on is for all 3 axis to be enabled
+ * and output data rate set to 400Hz. Output is via a ioctl read call.
+ */
+#define DEFAULT_POWER_ON_SETTING (ODR400 | ENABLE_ALL_AXES)
+
+#ifdef USES_MOVEMENT_RECOGNITION
+#define DEFAULT_CTRL3_SETTING 0x60 /* INT1_A enable */
+#define DEFAULT_CTRL6_SETTING 0x20 /* INT2_A enable */
+#define DEFAULT_INTERRUPT_SETTING 0x0A /* INT1_A XH,YH : enable */
+#define DEFAULT_INTERRUPT2_SETTING 0x20 /* INT2_A ZH enable */
+#define DEFAULT_THRESHOLD 0x7F /* 2032mg (16*0x7F) */
+#define DYNAMIC_THRESHOLD 300 /* mg */
+#define DYNAMIC_THRESHOLD2 700 /* mg */
+#define MOVEMENT_DURATION 0x00 /*INT1_A (DURATION/odr)ms*/
+enum {
+ OFF = 0,
+ ON = 1
+};
+#define ABS(a) (((a) < 0) ? -(a) : (a))
+#define MAX(a, b) (((a) > (b)) ? (a) : (b))
+#endif
+
+#define ACC_DEV_NAME "accelerometer"
+
+#define CALIBRATION_FILE_PATH "/efs/calibration_data"
+#define CAL_DATA_AMOUNT 20
+
+static const struct odr_delay {
+ u8 odr; /* odr reg setting */
+ u64 delay_ns; /* odr in ns */
+} odr_delay_table[] = {
+ { ODR1344, 744047LL }, /* 1344Hz */
+ { ODR400, 2500000LL }, /* 400Hz */
+ { ODR200, 5000000LL }, /* 200Hz */
+ { ODR100, 10000000LL }, /* 100Hz */
+ { ODR50, 20000000LL }, /* 50Hz */
+ { ODR25, 40000000LL }, /* 25Hz */
+ { ODR10, 100000000LL }, /* 10Hz */
+ { ODR1, 1000000000LL }, /* 1Hz */
+};
+
+/* It will be used, when google fusion is enabled. */
+static const int position_map[][3][3] = {
+ {{-1, 0, 0}, { 0, -1, 0}, { 0, 0, 1} }, /* 0 top/upper-left */
+ {{ 0, -1, 0}, { 1, 0, 0}, { 0, 0, 1} }, /* 1 top/upper-right */
+ {{ 1, 0, 0}, { 0, 1, 0}, { 0, 0, 1} }, /* 2 top/lower-right */
+ {{ 0, 1, 0}, {-1, 0, 0}, { 0, 0, 1} }, /* 3 top/lower-left */
+ {{ 1, 0, 0}, { 0, -1, 0}, { 0, 0, -1} }, /* 4 bottom/upper-left */
+ {{ 0, 1, 0}, { 1, 0, 0}, { 0, 0, -1} }, /* 5 bottom/upper-right */
+ {{-1, 0, 0}, { 0, 1, 0}, { 0, 0, -1} }, /* 6 bottom/lower-right */
+ {{ 0, -1, 0}, {-1, 0, 0}, { 0, 0, -1} }, /* 7 bottom/lower-left*/
+};
+
+struct lsm330dlc_accel_data {
+ struct i2c_client *client;
+ struct miscdevice lsm330dlc_accel_device;
+ struct mutex read_lock;
+ struct mutex write_lock;
+ struct lsm330dlc_acc cal_data;
+ struct device *dev;
+ u8 ctrl_reg1_shadow;
+ atomic_t opened; /* opened implies enabled */
+#ifdef USES_MOVEMENT_RECOGNITION
+ int movement_recog_flag;
+ unsigned char interrupt_state;
+ struct wake_lock reactive_wake_lock;
+#endif
+ ktime_t poll_delay;
+#ifdef USES_INPUT_DEV
+ struct input_dev *input_dev;
+ struct work_struct work;
+ struct workqueue_struct *work_queue;
+ struct hrtimer timer;
+#endif
+ int position;
+ struct lsm330dlc_acc acc_xyz;
+ bool axis_adjust;
+};
+
+ /* Read X,Y and Z-axis acceleration raw data */
+static int lsm330dlc_accel_read_raw_xyz(struct lsm330dlc_accel_data *data,
+ struct lsm330dlc_acc *acc)
+{
+ int err;
+ s8 reg = OUT_X_L | AC; /* read from OUT_X_L to OUT_Z_H by auto-inc */
+ u8 acc_data[6];
+
+ err = i2c_smbus_read_i2c_block_data(data->client, reg,
+ sizeof(acc_data), acc_data);
+ if (err != sizeof(acc_data)) {
+ pr_err("%s : failed to read 6 bytes for getting x/y/z\n",
+ __func__);
+ return -EIO;
+ }
+
+ acc->x = (acc_data[1] << 8) | acc_data[0];
+ acc->y = (acc_data[3] << 8) | acc_data[2];
+ acc->z = (acc_data[5] << 8) | acc_data[4];
+
+ acc->x = acc->x >> 4;
+ acc->y = acc->y >> 4;
+ acc->z = acc->z >> 4;
+
+ return 0;
+}
+
+static int lsm330dlc_accel_read_xyz(struct lsm330dlc_accel_data *data,
+ struct lsm330dlc_acc *acc)
+{
+ int err = 0;
+
+ mutex_lock(&data->read_lock);
+ err = lsm330dlc_accel_read_raw_xyz(data, acc);
+ mutex_unlock(&data->read_lock);
+ if (err < 0) {
+ pr_err("%s: lsm330dlc_accel_read_xyz() failed\n", __func__);
+ return err;
+ }
+
+ acc->x -= data->cal_data.x;
+ acc->y -= data->cal_data.y;
+ acc->z -= data->cal_data.z;
+
+ return err;
+}
+
+static int lsm330dlc_accel_open_calibration(struct lsm330dlc_accel_data *data)
+{
+ struct file *cal_filp = NULL;
+ int err = 0;
+ mm_segment_t old_fs;
+
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+
+ cal_filp = filp_open(CALIBRATION_FILE_PATH, O_RDONLY, 0666);
+ if (IS_ERR(cal_filp)) {
+ err = PTR_ERR(cal_filp);
+ if (err != -ENOENT)
+ pr_err("%s: Can't open calibration file\n", __func__);
+ set_fs(old_fs);
+ return err;
+ }
+
+ err = cal_filp->f_op->read(cal_filp,
+ (char *)&data->cal_data, 3 * sizeof(s16), &cal_filp->f_pos);
+ if (err != 3 * sizeof(s16)) {
+ pr_err("%s: Can't read the cal data from file\n", __func__);
+ err = -EIO;
+ }
+
+ accel_dbgmsg("(%d,%d,%d)\n",
+ data->cal_data.x, data->cal_data.y, data->cal_data.z);
+
+ filp_close(cal_filp, current->files);
+ set_fs(old_fs);
+
+ return err;
+}
+
+static int lsm330dlc_accel_do_calibrate(struct device *dev, bool do_calib)
+{
+ struct lsm330dlc_accel_data *acc_data = dev_get_drvdata(dev);
+ struct lsm330dlc_acc data = { 0, };
+ struct file *cal_filp = NULL;
+ int sum[3] = { 0, };
+ int err = 0;
+ int i;
+ mm_segment_t old_fs;
+
+ if (do_calib) {
+ for (i = 0; i < CAL_DATA_AMOUNT; i++) {
+ mutex_lock(&acc_data->read_lock);
+ err = lsm330dlc_accel_read_raw_xyz(acc_data, &data);
+ mutex_unlock(&acc_data->read_lock);
+ if (err < 0) {
+ pr_err("%s: lsm330dlc_accel_read_raw_xyz() "
+ "failed in the %dth loop\n",
+ __func__, i);
+ return err;
+ }
+
+ sum[0] += data.x;
+ sum[1] += data.y;
+ sum[2] += data.z;
+ }
+
+ acc_data->cal_data.x = sum[0] / CAL_DATA_AMOUNT;
+ acc_data->cal_data.y = sum[1] / CAL_DATA_AMOUNT;
+ acc_data->cal_data.z = (sum[2] / CAL_DATA_AMOUNT);
+ if (acc_data->cal_data.z > 0) {
+ acc_data->cal_data.z -= 1024;
+ pr_info("acc_data->cal_data.z= %d\n",
+ acc_data->cal_data.z);
+ } else if (acc_data->cal_data.z < 0) {
+ acc_data->cal_data.z += 1024;
+ pr_info("acc_data->cal_data.z= %d\n",
+ acc_data->cal_data.z);
+ }
+ } else {
+ acc_data->cal_data.x = 0;
+ acc_data->cal_data.y = 0;
+ acc_data->cal_data.z = 0;
+ }
+
+ printk(KERN_INFO "%s: cal data (%d,%d,%d)\n", __func__,
+ acc_data->cal_data.x, acc_data->cal_data.y
+ , acc_data->cal_data.z);
+
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+
+ cal_filp = filp_open(CALIBRATION_FILE_PATH,
+ O_CREAT | O_TRUNC | O_WRONLY, 0666);
+ if (IS_ERR(cal_filp)) {
+ pr_err("%s: Can't open calibration file\n", __func__);
+ set_fs(old_fs);
+ err = PTR_ERR(cal_filp);
+ return err;
+ }
+
+ err = cal_filp->f_op->write(cal_filp,
+ (char *)&acc_data->cal_data, 3 * sizeof(s16), &cal_filp->f_pos);
+ if (err != 3 * sizeof(s16)) {
+ pr_err("%s: Can't write the cal data to file\n", __func__);
+ err = -EIO;
+ }
+
+ filp_close(cal_filp, current->files);
+ set_fs(old_fs);
+
+ return err;
+}
+
+static int lsm330dlc_accel_enable(struct lsm330dlc_accel_data *data)
+{
+ int err = 0;
+
+ mutex_lock(&data->write_lock);
+ if (atomic_read(&data->opened) == 0) {
+ err = lsm330dlc_accel_open_calibration(data);
+ if (err < 0 && err != -ENOENT)
+ pr_err("%s: lsm330dlc_accel_open_calibration() failed\n",
+ __func__);
+ data->ctrl_reg1_shadow = DEFAULT_POWER_ON_SETTING;
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG1,
+ DEFAULT_POWER_ON_SETTING);
+ if (err)
+ pr_err("%s: i2c write ctrl_reg1 failed\n", __func__);
+
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG4,
+ CTRL_REG4_HR);
+ if (err)
+ pr_err("%s: i2c write ctrl_reg4 failed\n", __func__);
+#ifdef USES_INPUT_DEV
+ hrtimer_start(&data->timer, data->poll_delay, HRTIMER_MODE_REL);
+#endif
+ }
+
+ atomic_set(&data->opened, 1);
+ mutex_unlock(&data->write_lock);
+
+ return err;
+}
+
+static int lsm330dlc_accel_disable(struct lsm330dlc_accel_data *data)
+{
+ int err = 0;
+
+ mutex_lock(&data->write_lock);
+#ifdef USES_MOVEMENT_RECOGNITION
+ if (data->movement_recog_flag == ON) {
+ accel_dbgmsg("LOW_PWR_MODE.\n");
+ err = i2c_smbus_write_byte_data(data->client,
+ CTRL_REG1, LOW_PWR_MODE);
+ if (atomic_read(&data->opened) == 1)
+ data->ctrl_reg1_shadow = PM_OFF;
+ } else if (atomic_read(&data->opened) == 1) {
+#else
+ if (atomic_read(&data->opened) == 1) {
+#endif
+#ifdef USES_INPUT_DEV
+ hrtimer_cancel(&data->timer);
+ cancel_work_sync(&data->work);
+#endif
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG1,
+ PM_OFF);
+ data->ctrl_reg1_shadow = PM_OFF;
+ }
+ atomic_set(&data->opened, 0);
+ mutex_unlock(&data->write_lock);
+
+ return err;
+}
+
+/* open command for lsm330dlc_accel device file */
+static int lsm330dlc_accel_open(struct inode *inode, struct file *file)
+{
+ accel_dbgmsg("is called.\n");
+ return 0;
+}
+
+/* release command for lsm330dlc_accel device file */
+static int lsm330dlc_accel_close(struct inode *inode, struct file *file)
+{
+ accel_dbgmsg("is called.\n");
+ return 0;
+}
+
+static int lsm330dlc_accel_set_delay(struct lsm330dlc_accel_data *data
+ , u64 delay_ns)
+{
+ int odr_value = ODR1;
+ int err = 0, i;
+
+ /* round to the nearest delay that is less than
+ * the requested value (next highest freq)
+ */
+ mutex_lock(&data->write_lock);
+#ifdef USES_INPUT_DEV
+ if (atomic_read(&data->opened))
+ hrtimer_cancel(&data->timer);
+#endif
+
+ for (i = 0; i < ARRAY_SIZE(odr_delay_table); i++) {
+ if (delay_ns < odr_delay_table[i].delay_ns)
+ break;
+ }
+ if (i > 0)
+ i--;
+ odr_value = odr_delay_table[i].odr;
+
+ accel_dbgmsg("old=%lldns,new=%lldns,odr=0x%x,opened=%d\n",
+ ktime_to_ns(data->poll_delay), delay_ns, odr_value,
+ atomic_read(&data->opened));
+ data->poll_delay = ns_to_ktime(delay_ns);
+ if (odr_value != (data->ctrl_reg1_shadow & ODR_MASK)) {
+ u8 ctrl = (data->ctrl_reg1_shadow & ~ODR_MASK);
+ ctrl |= odr_value;
+ data->ctrl_reg1_shadow = ctrl;
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG1, ctrl);
+ if (err < 0)
+ pr_err("%s: i2c write ctrl_reg1 failed(err=%d)\n",
+ __func__, err);
+ }
+
+#ifdef USES_INPUT_DEV
+ if (atomic_read(&data->opened))
+ hrtimer_start(&data->timer, data->poll_delay,
+ HRTIMER_MODE_REL);
+#endif
+ mutex_unlock(&data->write_lock);
+ return err;
+}
+
+/* ioctl command for lsm330dlc_accel device file */
+static long lsm330dlc_accel_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int err = 0;
+ struct lsm330dlc_accel_data *data =
+ container_of(file->private_data, struct lsm330dlc_accel_data,
+ lsm330dlc_accel_device);
+ u64 delay_ns;
+ int enable = 0, j;
+ s16 raw[3] = {0,};
+ struct lsm330dlc_acc xyz_adjusted = {0,};
+
+ /* cmd mapping */
+ switch (cmd) {
+ case LSM330DLC_ACCEL_IOCTL_SET_ENABLE:
+ if (copy_from_user(&enable, (void __user *)arg,
+ sizeof(enable)))
+ return -EFAULT;
+
+ accel_dbgmsg("opened = %d, enable = %d\n",
+ atomic_read(&data->opened), enable);
+ if (enable)
+ err = lsm330dlc_accel_enable(data);
+ else
+ err = lsm330dlc_accel_disable(data);
+ break;
+ case LSM330DLC_ACCEL_IOCTL_SET_DELAY:
+ if (copy_from_user(&delay_ns, (void __user *)arg,
+ sizeof(delay_ns)))
+ return -EFAULT;
+ err = lsm330dlc_accel_set_delay(data, delay_ns);
+
+ break;
+ case LSM330DLC_ACCEL_IOCTL_GET_DELAY:
+ if (put_user(ktime_to_ns(data->poll_delay), (u64 __user *)arg))
+ return -EFAULT;
+ break;
+ case LSM330DLC_ACCEL_IOCTL_READ_XYZ:
+ err = lsm330dlc_accel_read_xyz(data, &data->acc_xyz);
+ #ifdef LSM330DLC_ACCEL_LOGGING
+ accel_dbgmsg("raw x = %d, y = %d, z = %d\n",
+ data->acc_xyz.x, data->acc_xyz.y,
+ data->acc_xyz.z);
+ #endif
+ if (err)
+ break;
+ if (data->axis_adjust) {
+ raw[0] = data->acc_xyz.x;
+ raw[1] = data->acc_xyz.y;
+ raw[2] = data->acc_xyz.z;
+ for (j = 0; j < 3; j++) {
+ xyz_adjusted.x +=
+ (position_map[data->position][0][j] * raw[j]);
+ xyz_adjusted.y +=
+ (position_map[data->position][1][j] * raw[j]);
+ xyz_adjusted.z +=
+ (position_map[data->position][2][j] * raw[j]);
+ }
+#ifdef LSM330DLC_ACCEL_LOGGING
+ accel_dbgmsg("adjusted x = %d, y = %d, z = %d\n",
+ xyz_adjusted.x, xyz_adjusted.y, xyz_adjusted.z);
+#endif
+ if (copy_to_user((void __user *)arg,
+ &xyz_adjusted, sizeof(xyz_adjusted)))
+ return -EFAULT;
+ } else
+ if (copy_to_user((void __user *)arg,
+ &data->acc_xyz, sizeof(data->acc_xyz)))
+ return -EFAULT;
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+
+ return err;
+}
+
+static int lsm330dlc_accel_suspend(struct device *dev)
+{
+ int res = 0;
+ struct lsm330dlc_accel_data *data = dev_get_drvdata(dev);
+
+#ifdef USES_INPUT_DEV
+ if (atomic_read(&data->opened) > 0) {
+ hrtimer_cancel(&data->timer);
+ cancel_work_sync(&data->work);
+ }
+#endif
+
+#ifdef USES_MOVEMENT_RECOGNITION
+ if (data->movement_recog_flag == ON) {
+ accel_dbgmsg("LOW_PWR_MODE.\n");
+ res = i2c_smbus_write_byte_data(data->client,
+ CTRL_REG1, LOW_PWR_MODE);
+ } else if (atomic_read(&data->opened) > 0) {
+#else
+ if (atomic_read(&data->opened) > 0) {
+#endif
+ accel_dbgmsg("PM_OFF.\n");
+ res = i2c_smbus_write_byte_data(data->client,
+ CTRL_REG1, PM_OFF);
+ }
+
+ return res;
+}
+
+static int lsm330dlc_accel_resume(struct device *dev)
+{
+ int res = 0;
+ struct lsm330dlc_accel_data *data = dev_get_drvdata(dev);
+
+ if (atomic_read(&data->opened) > 0) {
+ accel_dbgmsg("ctrl_reg1_shadow = 0x%x\n"
+ , data->ctrl_reg1_shadow);
+ res = i2c_smbus_write_byte_data(data->client, CTRL_REG1,
+ data->ctrl_reg1_shadow);
+#ifdef USES_INPUT_DEV
+ hrtimer_start(&data->timer,
+ data->poll_delay, HRTIMER_MODE_REL);
+#endif
+ }
+
+ return res;
+}
+
+static const struct dev_pm_ops lsm330dlc_accel_pm_ops = {
+ .suspend = lsm330dlc_accel_suspend,
+ .resume = lsm330dlc_accel_resume,
+};
+
+static const struct file_operations lsm330dlc_accel_fops = {
+ .owner = THIS_MODULE,
+ .open = lsm330dlc_accel_open,
+ .release = lsm330dlc_accel_close,
+ .unlocked_ioctl = lsm330dlc_accel_ioctl,
+};
+
+static ssize_t lsm330dlc_accel_fs_read(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lsm330dlc_accel_data *data = dev_get_drvdata(dev);
+
+ if (data->axis_adjust) {
+ int i, j;
+ s16 raw[3] = {0,}, accel_adjusted[3] = {0,};
+
+ raw[0] = data->acc_xyz.x;
+ raw[1] = data->acc_xyz.y;
+ raw[2] = data->acc_xyz.z;
+ for (i = 0; i < 3; i++) {
+ for (j = 0; j < 3; j++)
+ accel_adjusted[i]
+ += position_map[data->position][i][j] * raw[j];
+ }
+#ifdef CONFIG_SLP
+ return sprintf(buf, "raw:%d,%d,%d adjust:%d,%d,%d\n",
+ raw[0], raw[1], raw[2], accel_adjusted[0],
+ accel_adjusted[1], accel_adjusted[2]);
+#else
+ return sprintf(buf, "%d,%d,%d\n",
+ accel_adjusted[0], accel_adjusted[1],
+ accel_adjusted[2]);
+#endif
+ } else
+ return sprintf(buf, "%d,%d,%d\n",
+ data->acc_xyz.x, data->acc_xyz.y, data->acc_xyz.z);
+}
+
+static ssize_t lsm330dlc_accel_calibration_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int err;
+ struct lsm330dlc_accel_data *data = dev_get_drvdata(dev);
+
+ err = lsm330dlc_accel_open_calibration(data);
+ if (err < 0)
+ pr_err("%s: lsm330dlc_accel_open_calibration() failed\n"\
+ , __func__);
+
+ if (!data->cal_data.x && !data->cal_data.y && !data->cal_data.z)
+ err = -1;
+
+ return sprintf(buf, "%d %d %d %d\n",
+ err, data->cal_data.x, data->cal_data.y, data->cal_data.z);
+}
+
+static ssize_t lsm330dlc_accel_calibration_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct lsm330dlc_accel_data *data = dev_get_drvdata(dev);
+ bool do_calib;
+ int err;
+
+ if (sysfs_streq(buf, "1"))
+ do_calib = true;
+ else if (sysfs_streq(buf, "0"))
+ do_calib = false;
+ else {
+ pr_debug("%s: invalid value %d\n", __func__, *buf);
+ return -EINVAL;
+ }
+
+ if (atomic_read(&data->opened) == 0) {
+ /* if off, turn on the device.*/
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG1,
+ DEFAULT_POWER_ON_SETTING);
+ if (err) {
+ pr_err("%s: i2c write ctrl_reg1 failed(err=%d)\n",
+ __func__, err);
+ }
+ }
+
+ err = lsm330dlc_accel_do_calibrate(dev, do_calib);
+ if (err < 0) {
+ pr_err("%s: lsm330dlc_accel_do_calibrate() failed\n", __func__);
+ return err;
+ }
+
+ if (atomic_read(&data->opened) == 0) {
+ /* if off, turn on the device.*/
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG1,
+ PM_OFF);
+ if (err) {
+ pr_err("%s: i2c write ctrl_reg1 failed(err=%d)\n",
+ __func__, err);
+ }
+ }
+
+ return count;
+}
+
+#ifdef USES_MOVEMENT_RECOGNITION
+static ssize_t lsm330dlc_accel_reactive_alert_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int onoff = OFF, err = 0, ctrl_reg = 0;
+ struct lsm330dlc_accel_data *data = dev_get_drvdata(dev);
+ bool factory_test = false;
+ struct lsm330dlc_acc raw_data;
+ u8 thresh1 = 0, thresh2 = 0;
+
+ if (sysfs_streq(buf, "1"))
+ onoff = ON;
+ else if (sysfs_streq(buf, "0"))
+ onoff = OFF;
+ else if (sysfs_streq(buf, "2")) {
+ onoff = ON;
+ factory_test = true;
+ accel_dbgmsg("factory_test = %d\n", factory_test);
+ } else {
+ pr_err("%s: invalid value %d\n", __func__, *buf);
+ return -EINVAL;
+ }
+
+ if (onoff == ON && data->movement_recog_flag == OFF) {
+ accel_dbgmsg("reactive alert is on.\n");
+ data->interrupt_state = 0; /* Init interrupt state.*/
+
+ if (atomic_read(&data->opened) == 0) {
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG1,
+ FASTEST_MODE);
+ if (err) {
+ ctrl_reg = CTRL_REG1;
+ goto err_i2c_write;
+ }
+ /* trun on time, T = 7/odr ms */
+ usleep_range(10000, 10000);
+ }
+ enable_irq(data->client->irq);
+ if (device_may_wakeup(&data->client->dev))
+ enable_irq_wake(data->client->irq);
+ /* Get x, y, z data to set threshold1, threshold2. */
+ err = lsm330dlc_accel_read_xyz(data, &raw_data);
+ accel_dbgmsg("raw x = %d, y = %d, z = %d\n",
+ raw_data.x, raw_data.y, raw_data.z);
+ if (err < 0) {
+ pr_err("%s: lsm330dlc_accel_read_xyz failed\n",
+ __func__);
+ goto exit;
+ }
+ if (atomic_read(&data->opened) == 0) {
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG1,
+ LOW_PWR_MODE); /* Change to 50Hz*/
+ if (err) {
+ ctrl_reg = CTRL_REG1;
+ goto err_i2c_write;
+ }
+ }
+ /* Change raw data to threshold value & settng threshold */
+ thresh1 = (MAX(ABS(raw_data.x), ABS(raw_data.y))
+ + DYNAMIC_THRESHOLD)/16;
+ if (factory_test == true)
+ thresh2 = 0; /* for z axis */
+ else
+ thresh2 = (ABS(raw_data.z) + DYNAMIC_THRESHOLD2)/16;
+ accel_dbgmsg("threshold1 = 0x%x, threshold2 = 0x%x\n",
+ thresh1, thresh2);
+ err = i2c_smbus_write_byte_data(data->client, INT1_THS
+ , thresh1);
+ if (err) {
+ ctrl_reg = INT1_THS;
+ goto err_i2c_write;
+ }
+ ctrl_reg = INT2_THS;
+ err = i2c_smbus_write_byte_data(data->client, INT2_THS,
+ thresh2);
+ if (err) {
+ ctrl_reg = INT2_THS;
+ goto err_i2c_write;
+ }
+ /* INT_A enable */
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG3,
+ DEFAULT_CTRL3_SETTING);
+ if (err) {
+ ctrl_reg = CTRL_REG3;
+ goto err_i2c_write;
+ }
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG6,
+ DEFAULT_CTRL6_SETTING);
+ if (err) {
+ ctrl_reg = CTRL_REG6;
+ goto err_i2c_write;
+ }
+
+ data->movement_recog_flag = ON;
+ } else if (onoff == OFF && data->movement_recog_flag == ON) {
+ accel_dbgmsg("reactive alert is off.\n");
+ /* INT_A disable */
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG3,
+ PM_OFF);
+ if (err) {
+ ctrl_reg = CTRL_REG3;
+ goto err_i2c_write;
+ }
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG6,
+ PM_OFF);
+ if (err) {
+ ctrl_reg = CTRL_REG6;
+ goto err_i2c_write;
+ }
+ if (device_may_wakeup(&data->client->dev))
+ disable_irq_wake(data->client->irq);
+ disable_irq_nosync(data->client->irq);
+ /* return the power state */
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG1,
+ data->ctrl_reg1_shadow);
+ if (err) {
+ ctrl_reg = CTRL_REG1;
+ goto err_i2c_write;
+ }
+ data->movement_recog_flag = OFF;
+ data->interrupt_state = 0; /* Init interrupt state.*/
+ }
+ return count;
+err_i2c_write:
+ pr_err("%s: i2c write ctrl_reg = 0x%d failed(err=%d)\n",
+ __func__, ctrl_reg, err);
+exit:
+ return ((err < 0) ? err : -err);
+}
+
+static ssize_t lsm330dlc_accel_reactive_alert_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct lsm330dlc_accel_data *data = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", data->interrupt_state);
+}
+#endif
+
+#ifdef DEBUG_ODR
+static ssize_t lsm330dlc_accel_odr_read(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lsm330dlc_accel_data *data = dev_get_drvdata(dev);
+ u8 odr_data = 0, fs_data = 0;
+
+ /* read output data rate & operation mode */
+ odr_data = (u8)i2c_smbus_read_byte_data(data->client, CTRL_REG1);
+ if (odr_data < 0)
+ pr_err("%s, read CTRL_REG1 failed\n", __func__);
+
+ /* read full scale setting */
+ fs_data = (u8)i2c_smbus_read_byte_data(data->client, CTRL_REG4);
+ if (fs_data < 0)
+ pr_err("%s, read CTRL_REG4 failed\n", __func__);
+
+ return sprintf(buf, "odr:0x%x,fs:0x%x\n",
+ odr_data, fs_data);
+}
+#endif
+
+#ifdef USES_INPUT_DEV
+static ssize_t lsm330dlc_accel_delay_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lsm330dlc_accel_data *data = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%lld\n", ktime_to_ns(data->poll_delay));
+}
+
+static ssize_t lsm330dlc_accel_delay_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int err = 0;
+ u64 delay_ns = 0;
+ struct lsm330dlc_accel_data *data = dev_get_drvdata(dev);
+
+ err = strict_strtoll(buf, 10, &delay_ns);
+
+ if (err < 0)
+ return err;
+
+ lsm330dlc_accel_set_delay(data, delay_ns);
+
+ return count;
+}
+
+static ssize_t lsm330dlc_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct lsm330dlc_accel_data *data = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n", atomic_read(&data->opened));
+}
+
+static ssize_t lsm330dlc_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct lsm330dlc_accel_data *data = dev_get_drvdata(dev);
+ int enable = -1, err = 0;
+
+ err = kstrtoint(buf, 10, &enable);
+ if (err < 0)
+ pr_err("%s, kstrtoint failed.", __func__);
+
+ accel_dbgmsg("opened = %d, enable = %d\n",
+ atomic_read(&data->opened), enable);
+
+ if (enable)
+ lsm330dlc_accel_enable(data);
+ else
+ lsm330dlc_accel_disable(data);
+
+ return count;
+}
+
+static DEVICE_ATTR(poll_delay, 0644,
+ lsm330dlc_accel_delay_show, lsm330dlc_accel_delay_store);
+static DEVICE_ATTR(enable, 0644,
+ lsm330dlc_enable_show, lsm330dlc_enable_store);
+
+static struct attribute *lsm330dlc_sysfs_attrs[] = {
+ &dev_attr_enable.attr,
+ &dev_attr_poll_delay.attr,
+ NULL
+};
+
+static struct attribute_group lsm330dlc_attribute_group = {
+ .attrs = lsm330dlc_sysfs_attrs,
+};
+#endif
+
+static ssize_t
+lsm330dlc_accel_position_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct lsm330dlc_accel_data *data = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", data->position);
+}
+
+static ssize_t
+lsm330dlc_accel_position_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct lsm330dlc_accel_data *data = dev_get_drvdata(dev);
+ int err = 0;
+
+ err = kstrtoint(buf, 10, &data->position);
+ if (err < 0)
+ pr_err("%s, kstrtoint failed.", __func__);
+
+ return count;
+}
+
+static ssize_t lsm330dlc_accel_vendor_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", VENDOR);
+}
+
+static ssize_t lsm330dlc_accel_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", CHIP_ID);
+}
+
+static DEVICE_ATTR(name, 0664,
+ lsm330dlc_accel_name_show, NULL);
+
+static DEVICE_ATTR(vendor, 0664,
+ lsm330dlc_accel_vendor_show, NULL);
+
+static DEVICE_ATTR(position, 0664,
+ lsm330dlc_accel_position_show, lsm330dlc_accel_position_store);
+
+static DEVICE_ATTR(calibration, 0664,
+ lsm330dlc_accel_calibration_show
+ , lsm330dlc_accel_calibration_store);
+static DEVICE_ATTR(raw_data, 0664, lsm330dlc_accel_fs_read, NULL);
+#ifdef USES_MOVEMENT_RECOGNITION
+static DEVICE_ATTR(reactive_alert, 0664,
+ lsm330dlc_accel_reactive_alert_show,
+ lsm330dlc_accel_reactive_alert_store);
+#endif
+
+#ifdef DEBUG_ODR
+static DEVICE_ATTR(odr, 0664, lsm330dlc_accel_odr_read, NULL);
+#endif
+
+void lsm330dlc_accel_shutdown(struct i2c_client *client)
+{
+ int res = 0;
+ struct lsm330dlc_accel_data *data = i2c_get_clientdata(client);
+
+ accel_dbgmsg("is called.\n");
+#ifdef USES_INPUT_DEV
+ if (atomic_read(&data->opened) > 0) {
+ hrtimer_cancel(&data->timer);
+ cancel_work_sync(&data->work);
+ }
+#endif
+ res = i2c_smbus_write_byte_data(data->client,
+ CTRL_REG1, PM_OFF);
+ if (res < 0)
+ pr_err("%s: pm_off failed %d\n", __func__, res);
+}
+
+#ifdef USES_MOVEMENT_RECOGNITION
+static irqreturn_t lsm330dlc_accel_interrupt_thread(int irq\
+ , void *lsm330dlc_accel_data_p)
+{
+ struct lsm330dlc_accel_data *data = lsm330dlc_accel_data_p;
+#ifdef DEBUG_REACTIVE_ALERT
+ u8 int1_src_reg = 0, int2_src_reg = 0;
+#else
+ int err = 0;
+#endif
+
+#ifndef DEBUG_REACTIVE_ALERT
+ /* INT1_A disable */
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG3,
+ PM_OFF);
+ if (err)
+ pr_err("%s: i2c write ctrl_reg3 failed\n", __func__);
+
+ /* INT2_A disable */
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG6,
+ PM_OFF);
+ if (err)
+ pr_err("%s: i2c write ctrl_reg6 failed\n", __func__);
+#else
+ int1_src_reg = (u8)i2c_smbus_read_byte_data(data->client, INT1_SRC);
+ if (int1_src_reg < 0)
+ pr_err("%s, read int1_src failed\n", __func__);
+ accel_dbgmsg("interrupt source reg1 = 0x%x\n", int1_src_reg);
+
+ int2_src_reg = (u8)i2c_smbus_read_byte_data(data->client, INT2_SRC);
+ if (int2_src_reg < 0)
+ pr_err("%s, read int2_src failed\n", __func__);
+ accel_dbgmsg("interrupt source reg2 = 0x%x\n", int2_src_reg);
+#endif
+
+ data->interrupt_state = 1;
+ wake_lock_timeout(&data->reactive_wake_lock, msecs_to_jiffies(2000));
+ accel_dbgmsg("irq is handled\n");
+
+ return IRQ_HANDLED;
+}
+#endif
+
+#ifdef USES_INPUT_DEV
+static enum hrtimer_restart lsm330dlc_timer_func(struct hrtimer *timer)
+{
+ struct lsm330dlc_accel_data *data
+ = container_of(timer, struct lsm330dlc_accel_data, timer);
+ queue_work(data->work_queue, &data->work);
+ hrtimer_forward_now(&data->timer, data->poll_delay);
+ return HRTIMER_RESTART;
+}
+
+static void lsm330dlc_work_func(struct work_struct *work)
+{
+ struct lsm330dlc_accel_data *data
+ = container_of(work, struct lsm330dlc_accel_data, work);
+ s16 raw[3] = {0,}, accel_adjusted[3] = {0,};
+ int i, j;
+
+ lsm330dlc_accel_read_xyz(data, &data->acc_xyz);
+
+ if (data->axis_adjust) {
+ raw[0] = data->acc_xyz.x;
+ raw[1] = data->acc_xyz.y;
+ raw[2] = data->acc_xyz.z;
+ for (i = 0; i < 3; i++) {
+ for (j = 0; j < 3; j++)
+ accel_adjusted[i]
+ += position_map[data->position][i][j] * raw[j];
+ }
+ } else {
+ accel_adjusted[0] = data->acc_xyz.x;
+ accel_adjusted[1] = data->acc_xyz.y;
+ accel_adjusted[2] = data->acc_xyz.z;
+ }
+ input_report_rel(data->input_dev, REL_X, accel_adjusted[0]);
+ input_report_rel(data->input_dev, REL_Y, accel_adjusted[1]);
+ input_report_rel(data->input_dev, REL_Z, accel_adjusted[2]);
+ input_sync(data->input_dev);
+#ifdef LSM330DLC_ACCEL_LOGGING
+ accel_dbgmsg("raw x = %d, y = %d, z = %d\n",
+ accel_adjusted[0], accel_adjusted[1], accel_adjusted[2]);
+#endif
+}
+#endif
+
+static int lsm330dlc_accel_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct lsm330dlc_accel_data *data;
+ struct accel_platform_data *pdata;
+ int err = 0;
+
+ accel_dbgmsg("is started\n");
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
+ I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
+ pr_err("%s: i2c functionality check failed!\n", __func__);
+ err = -ENODEV;
+ goto exit;
+ }
+
+ data = kzalloc(sizeof(struct lsm330dlc_accel_data), GFP_KERNEL);
+ if (data == NULL) {
+ dev_err(&client->dev,
+ "failed to allocate memory for module data\n");
+ err = -ENOMEM;
+ goto exit;
+ }
+
+ /* Checking device */
+ err = i2c_smbus_write_byte_data(client, CTRL_REG1,
+ PM_OFF);
+ if (err) {
+ pr_err("%s: there is no such device, err = %d\n",
+ __func__, err);
+ goto err_read_reg;
+ }
+
+ data->client = client;
+ i2c_set_clientdata(client, data);
+
+ mutex_init(&data->read_lock);
+ mutex_init(&data->write_lock);
+ atomic_set(&data->opened, 0);
+#ifdef USES_INPUT_DEV
+ hrtimer_init(&data->timer,
+ CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ data->poll_delay = ns_to_ktime(200 * NSEC_PER_MSEC);
+ data->timer.function = lsm330dlc_timer_func;
+ data->work_queue =
+ create_singlethread_workqueue("lsm330dlc_workqueue");
+ if (!data->work_queue) {
+ err = -ENOMEM;
+ pr_err("%s: count not create workqueue\n", __func__);
+ goto err_create_workqueue;
+ }
+
+ INIT_WORK(&data->work, lsm330dlc_work_func);
+
+ data->input_dev = input_allocate_device();
+ if (!data->input_dev) {
+ pr_err("%s: count not allocate input device\n", __func__);
+ err = -ENOMEM;
+ goto err_input_allocate;
+ }
+
+ input_set_drvdata(data->input_dev, data);
+ data->input_dev->name = "accelerometer_sensor";
+ input_set_capability(data->input_dev, EV_REL, REL_X);
+ input_set_capability(data->input_dev, EV_REL, REL_Y);
+ input_set_capability(data->input_dev, EV_REL, REL_Z);
+
+ err = input_register_device(data->input_dev);
+ if (err < 0) {
+ input_free_device(data->input_dev);
+ pr_err("%s: could not register input device\n", __func__);
+ goto err_input_allocate;
+ }
+
+ err = sysfs_create_group(&data->input_dev->dev.kobj,
+ &lsm330dlc_attribute_group);
+ if (err) {
+ pr_err("%s: could not create sysfs group\n", __func__);
+ goto err_create_sysfs;
+ }
+#else
+ /* sensor HAL expects to find /dev/accelerometer */
+ data->lsm330dlc_accel_device.minor = MISC_DYNAMIC_MINOR;
+ data->lsm330dlc_accel_device.name = ACC_DEV_NAME;
+ data->lsm330dlc_accel_device.fops = &lsm330dlc_accel_fops;
+
+ err = misc_register(&data->lsm330dlc_accel_device);
+ if (err) {
+ pr_err("%s: misc_register failed\n", __FILE__);
+ goto err_misc_register;
+ }
+#endif
+
+#ifdef USES_MOVEMENT_RECOGNITION
+ data->movement_recog_flag = OFF;
+ /* wake lock init for accelerometer sensor */
+ wake_lock_init(&data->reactive_wake_lock, WAKE_LOCK_SUSPEND,
+ "reactive_wake_lock");
+ err = i2c_smbus_write_byte_data(data->client, INT1_THS
+ , DEFAULT_THRESHOLD);
+ if (err) {
+ pr_err("%s: i2c write int1_ths failed\n", __func__);
+ goto err_request_irq;
+ }
+
+ err = i2c_smbus_write_byte_data(data->client, INT1_DURATION
+ , MOVEMENT_DURATION);
+ if (err) {
+ pr_err("%s: i2c write int1_duration failed\n"
+ , __func__);
+ goto err_request_irq;
+ }
+
+ err = i2c_smbus_write_byte_data(data->client, INT1_CFG,
+ DEFAULT_INTERRUPT_SETTING);
+ if (err) {
+ pr_err("%s: i2c write int1_cfg failed\n", __func__);
+ goto err_request_irq;
+ }
+
+ err = i2c_smbus_write_byte_data(data->client, INT2_THS
+ , DEFAULT_THRESHOLD);
+ if (err) {
+ pr_err("%s: i2c write int2_ths failed\n", __func__);
+ goto err_request_irq;
+ }
+
+ err = i2c_smbus_write_byte_data(data->client, INT2_DURATION
+ , MOVEMENT_DURATION);
+ if (err) {
+ pr_err("%s: i2c write int1_duration failed\n"
+ , __func__);
+ goto err_request_irq;
+ }
+ err = i2c_smbus_write_byte_data(data->client, INT2_CFG,
+ DEFAULT_INTERRUPT2_SETTING);
+ if (err) {
+ pr_err("%s: i2c write ctrl_reg3 failed\n", __func__);
+ goto err_request_irq;
+ }
+
+ err = request_threaded_irq(data->client->irq, NULL,
+ lsm330dlc_accel_interrupt_thread\
+ , IRQF_TRIGGER_RISING | IRQF_ONESHOT,\
+ "lsm330dlc_accel", data);
+ if (err < 0) {
+ pr_err("%s: can't allocate irq.\n", __func__);
+ goto err_request_irq;
+ }
+
+ disable_irq(data->client->irq);
+ device_init_wakeup(&data->client->dev, 1);
+#endif
+
+ /* creating device for test & calibration */
+ data->dev = sensors_classdev_register("accelerometer_sensor");
+ if (IS_ERR(data->dev)) {
+ pr_err("%s: class create failed(accelerometer_sensor)\n",
+ __func__);
+ err = PTR_ERR(data->dev);
+ goto err_acc_device_create;
+ }
+
+ err = device_create_file(data->dev, &dev_attr_raw_data);
+ if (err < 0) {
+ pr_err("%s: Failed to create device file(%s)\n",
+ __func__, dev_attr_raw_data.attr.name);
+ goto err_acc_device_create_file;
+ }
+
+ err = device_create_file(data->dev, &dev_attr_calibration);
+ if (err < 0) {
+ pr_err("%s: Failed to create device file(%s)\n",
+ __func__, dev_attr_calibration.attr.name);
+ goto err_cal_device_create_file;
+ }
+
+#ifdef USES_MOVEMENT_RECOGNITION
+ err = device_create_file(data->dev, &dev_attr_reactive_alert);
+ if (err < 0) {
+ pr_err("%s: Failed to create device file(%s)\n",
+ __func__, dev_attr_reactive_alert.attr.name);
+ goto err_reactive_device_create_file;
+ }
+#endif
+
+#ifdef DEBUG_ODR
+ err = device_create_file(data->dev, &dev_attr_odr);
+ if (err < 0) {
+ pr_err("%s: Failed to create device file(%s)\n",
+ __func__, dev_attr_odr.attr.name);
+ goto err_odr_device_create_file;
+ }
+#endif
+
+ /* set mounting position of the sensor */
+ pdata = client->dev.platform_data;
+ if (!pdata) {
+ /*Set by default position 2, it doesn't adjust raw value*/
+ data->position = 2;
+ data->axis_adjust = false;
+ accel_dbgmsg("using defualt position = %d\n", data->position);
+ } else {
+ data->position = pdata->accel_get_position();
+ data->axis_adjust = pdata->axis_adjust;
+ accel_dbgmsg("successful, position = %d\n", data->position);
+ }
+ err = device_create_file(data->dev, &dev_attr_position);
+ if (err < 0) {
+ pr_err("%s: Failed to create device file(%s)\n",
+ __func__, dev_attr_position.attr.name);
+ goto err_position_device_create_file;
+ }
+ err = device_create_file(data->dev, &dev_attr_vendor);
+ if (err < 0) {
+ pr_err("%s: Failed to create device file(%s)\n",
+ __func__, dev_attr_vendor.attr.name);
+ goto err_vendor_device_create_file;
+ }
+ err = device_create_file(data->dev, &dev_attr_name);
+ if (err < 0) {
+ pr_err("%s: Failed to create device file(%s)\n",
+ __func__, dev_attr_name.attr.name);
+ goto err_name_device_create_file;
+ }
+
+ dev_set_drvdata(data->dev, data);
+
+ return 0;
+
+err_name_device_create_file:
+ device_remove_file(data->dev, &dev_attr_vendor);
+err_vendor_device_create_file:
+ device_remove_file(data->dev, &dev_attr_position);
+err_position_device_create_file:
+#ifdef DEBUG_ODR
+ device_remove_file(data->dev, &dev_attr_odr);
+err_odr_device_create_file:
+#ifdef USES_MOVEMENT_RECOGNITION
+ device_remove_file(data->dev, &dev_attr_reactive_alert);
+#else
+ device_remove_file(data->dev, &dev_attr_calibration);
+#endif
+#endif
+#ifdef USES_MOVEMENT_RECOGNITION
+err_reactive_device_create_file:
+ device_remove_file(data->dev, &dev_attr_calibration);
+#endif
+err_cal_device_create_file:
+ device_remove_file(data->dev, &dev_attr_raw_data);
+err_acc_device_create_file:
+ sensors_classdev_unregister(data->dev);
+err_acc_device_create:
+#ifdef USES_MOVEMENT_RECOGNITION
+ free_irq(data->client->irq, data);
+err_request_irq:
+ wake_lock_destroy(&data->reactive_wake_lock);
+#endif
+#ifdef USES_INPUT_DEV
+ sysfs_remove_group(&data->input_dev->dev.kobj,
+ &lsm330dlc_attribute_group);
+err_create_sysfs:
+ input_unregister_device(data->input_dev);
+err_input_allocate:
+ destroy_workqueue(data->work_queue);
+err_create_workqueue:
+#else
+ misc_deregister(&data->lsm330dlc_accel_device);
+err_misc_register:
+#endif
+ mutex_destroy(&data->read_lock);
+ mutex_destroy(&data->write_lock);
+err_read_reg:
+ kfree(data);
+exit:
+ return err;
+}
+
+static int lsm330dlc_accel_remove(struct i2c_client *client)
+{
+ struct lsm330dlc_accel_data *data = i2c_get_clientdata(client);
+ int err = 0;
+
+ if (atomic_read(&data->opened) > 0) {
+#ifdef USES_INPUT_DEV
+ hrtimer_cancel(&data->timer);
+ cancel_work_sync(&data->work);
+#endif
+ err = i2c_smbus_write_byte_data(data->client,
+ CTRL_REG1, PM_OFF);
+ if (err < 0)
+ pr_err("%s: pm_off failed %d\n", __func__, err);
+ }
+
+ device_remove_file(data->dev, &dev_attr_name);
+ device_remove_file(data->dev, &dev_attr_vendor);
+ device_remove_file(data->dev, &dev_attr_position);
+#ifdef DEBUG_ODR
+ device_remove_file(data->dev, &dev_attr_odr);
+#endif
+#ifdef USES_MOVEMENT_RECOGNITION
+ wake_lock_destroy(&data->reactive_wake_lock);
+ device_remove_file(data->dev, &dev_attr_reactive_alert);
+#endif
+ device_remove_file(data->dev, &dev_attr_calibration);
+ device_remove_file(data->dev, &dev_attr_raw_data);
+ sensors_classdev_unregister(data->dev);
+
+#ifdef USES_MOVEMENT_RECOGNITION
+ device_init_wakeup(&data->client->dev, 0);
+ free_irq(data->client->irq, data);
+#endif
+#ifdef USES_INPUT_DEV
+ destroy_workqueue(data->work_queue);
+ sysfs_remove_group(&data->input_dev->dev.kobj,
+ &lsm330dlc_attribute_group);
+ input_unregister_device(data->input_dev);
+#else
+ misc_deregister(&data->lsm330dlc_accel_device);
+#endif
+ mutex_destroy(&data->read_lock);
+ mutex_destroy(&data->write_lock);
+ kfree(data);
+
+ return 0;
+}
+
+static const struct i2c_device_id lsm330dlc_accel_id[] = {
+ { "lsm330dlc_accel", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, lsm330dlc_accel_id);
+
+static struct i2c_driver lsm330dlc_accel_driver = {
+ .probe = lsm330dlc_accel_probe,
+ .shutdown = lsm330dlc_accel_shutdown,
+ .remove = __devexit_p(lsm330dlc_accel_remove),
+ .id_table = lsm330dlc_accel_id,
+ .driver = {
+ .pm = &lsm330dlc_accel_pm_ops,
+ .owner = THIS_MODULE,
+ .name = "lsm330dlc_accel",
+ },
+};
+
+static int __init lsm330dlc_accel_init(void)
+{
+ return i2c_add_driver(&lsm330dlc_accel_driver);
+}
+
+static void __exit lsm330dlc_accel_exit(void)
+{
+ i2c_del_driver(&lsm330dlc_accel_driver);
+}
+
+module_init(lsm330dlc_accel_init);
+module_exit(lsm330dlc_accel_exit);
+
+MODULE_DESCRIPTION("LSM330DLC accelerometer driver");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sensor/lsm330dlc_gyro.c b/drivers/sensor/lsm330dlc_gyro.c
new file mode 100644
index 0000000..1c48308
--- /dev/null
+++ b/drivers/sensor/lsm330dlc_gyro.c
@@ -0,0 +1,1643 @@
+/*
+ * Copyright (C) 2011, Samsung Electronics Co. Ltd. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/uaccess.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <asm/div64.h>
+#include <linux/delay.h>
+#include <linux/ioctl.h>
+#include <linux/miscdevice.h>
+#include <linux/completion.h>
+#include <linux/sensor/lsm330dlc_gyro.h>
+#include <linux/sensor/sensors_core.h>
+
+#undef LOGGING_GYRO
+#undef DEBUG_REGISTER
+
+#define VENDOR "STM"
+#define CHIP_ID "LSM330"
+
+#define CALIBRATION_FILE_PATH "/efs/gyro_cal_data"
+
+/* lsm330dlc_gyro chip id */
+#define DEVICE_ID 0xD4
+/* lsm330dlc_gyro gyroscope registers */
+#define WHO_AM_I 0x0F
+#define CTRL_REG1 0x20 /* power control reg */
+#define CTRL_REG2 0x21 /* trigger & filter setting control reg */
+#define CTRL_REG3 0x22 /* interrupt control reg */
+#define CTRL_REG4 0x23 /* data control reg */
+#define CTRL_REG5 0x24 /* fifo en & filter en control reg */
+#define OUT_TEMP 0x26 /* Temperature data */
+#define STATUS_REG 0x27
+#define AXISDATA_REG 0x28
+#define OUT_Y_L 0x2A
+#define FIFO_CTRL_REG 0x2E
+#define FIFO_SRC_REG 0x2F
+#define PM_OFF 0x00
+#define PM_NORMAL 0x08
+#define ENABLE_ALL_AXES 0x07
+#define BYPASS_MODE 0x00
+#define FIFO_MODE 0x20
+#define FIFO_EMPTY 0x20
+#define AC (1 << 7) /* register auto-increment bit */
+
+/* odr settings */
+#define FSS_MASK 0x1F
+#define ODR_MASK 0xF0
+#define ODR95_BW12_5 0x00 /* ODR = 95Hz; BW = 12.5Hz */
+#define ODR95_BW25 0x11 /* ODR = 95Hz; BW = 25Hz */
+#define ODR190_BW12_5 0x40 /* ODR = 190Hz; BW = 12.5Hz */
+#define ODR190_BW25 0x50 /* ODR = 190Hz; BW = 25Hz */
+#define ODR190_BW50 0x60 /* ODR = 190Hz; BW = 50Hz */
+#define ODR190_BW70 0x70 /* ODR = 190Hz; BW = 70Hz */
+#define ODR380_BW20 0x80 /* ODR = 380Hz; BW = 20Hz */
+#define ODR380_BW25 0x90 /* ODR = 380Hz; BW = 25Hz */
+#define ODR380_BW50 0xA0 /* ODR = 380Hz; BW = 50Hz */
+#define ODR380_BW100 0xB0 /* ODR = 380Hz; BW = 100Hz */
+#define ODR760_BW30 0xC0 /* ODR = 760Hz; BW = 30Hz */
+#define ODR760_BW35 0xD0 /* ODR = 760Hz; BW = 35Hz */
+#define ODR760_BW50 0xE0 /* ODR = 760Hz; BW = 50Hz */
+#define ODR760_BW100 0xF0 /* ODR = 760Hz; BW = 100Hz */
+
+/* full scale selection */
+#define DPS250 250
+#define DPS500 500
+#define DPS2000 2000
+#define FS_MASK 0x30
+#define FS_250DPS 0x00
+#define FS_500DPS 0x10
+#define FS_2000DPS 0x20
+#define DEFAULT_DPS DPS500
+#define FS_DEFULAT_DPS FS_500DPS
+
+/* self tset settings */
+#define MIN_ST 175
+#define MAX_ST 875
+#define FIFO_TEST_WTM 0x1F
+#define MIN_ZERO_RATE -1714
+#define MAX_ZERO_RATE 1714 /* 30*1000/17.5 */
+
+/* max and min entry */
+#define MAX_ENTRY 20
+#define MAX_DELAY (MAX_ENTRY * 10000000LL) /* 200ms */
+
+/* default register setting for device init */
+static const char default_ctrl_regs_bypass[] = {
+ 0x3F, /* 95HZ, BW25, PM-normal, xyz enable */
+ 0x00, /* normal mode */
+ 0x00, /* fifo wtm interrupt off */
+ 0x90, /* block data update, 500d/s */
+ 0x00, /* fifo disable */
+};
+static const char default_ctrl_regs_fifo[] = {
+ 0x3F, /* 95HZ, BW25, PM-normal, xyz enable */
+ 0x00, /* normal mode */
+ 0x04, /* fifo wtm interrupt on */
+ 0x90, /* block data update, 500d/s */
+ 0x40, /* fifo enable */
+};
+
+static const struct odr_delay {
+ u8 odr; /* odr reg setting */
+ u64 delay_ns; /* odr in ns */
+} odr_delay_table[] = {
+ { ODR760_BW100, 1315789LL },/* 760Hz */
+ { ODR380_BW100, 2631578LL },/* 380Hz */
+ { ODR190_BW70, 5263157LL }, /* 190Hz */
+ { ODR95_BW25, 10526315LL }, /* 95Hz */
+};
+
+/*
+ * lsm330dlc_gyro gyroscope data
+ * brief structure containing gyroscope values for yaw, pitch and roll in
+ * signed short
+ */
+struct gyro_t {
+ s16 x;
+ s16 y;
+ s16 z;
+};
+
+struct lsm330dlc_gyro_data {
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+ struct mutex lock;
+ struct workqueue_struct *lsm330dlc_gyro_wq;
+ struct work_struct work;
+ struct hrtimer timer;
+ atomic_t opened;
+ bool enable;
+ bool drop_next_event;
+ bool self_test; /* is self_test or not? */
+ bool interruptible; /* interrupt or polling? */
+ int entries; /* number of fifo entries */
+ int dps; /* scale selection */
+ /* fifo data entries */
+ u8 fifo_data[sizeof(struct gyro_t) * 32];
+ u8 ctrl_regs[5]; /* saving register settings */
+ u32 time_to_read; /* time needed to read one entry */
+ ktime_t polling_delay; /* polling time for timer */
+ struct gyro_t xyz_data;
+
+ struct device *dev;
+ struct completion data_ready;
+#ifdef LOGGING_GYRO
+ int count;
+#endif
+ struct gyro_t cal_data;
+};
+
+static int lsm330dlc_gyro_open_calibration(struct lsm330dlc_gyro_data *data)
+{
+ struct file *cal_filp = NULL;
+ int err = 0;
+ mm_segment_t old_fs;
+
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+
+ cal_filp = filp_open(CALIBRATION_FILE_PATH, O_RDONLY, 0666);
+ if (IS_ERR(cal_filp)) {
+ err = PTR_ERR(cal_filp);
+ if (err != -ENOENT)
+ pr_err("%s: Can't open calibration file\n", __func__);
+ set_fs(old_fs);
+ return err;
+ }
+
+ err = cal_filp->f_op->read(cal_filp,
+ (char *)&data->cal_data, 3 * sizeof(s16), &cal_filp->f_pos);
+ if (err != 3 * sizeof(s16)) {
+ pr_err("%s: Can't read the cal data from file\n", __func__);
+ err = -EIO;
+ }
+
+ pr_info("%s: (%d,%d,%d)\n", __func__,
+ data->cal_data.x, data->cal_data.y, data->cal_data.z);
+
+ filp_close(cal_filp, current->files);
+ set_fs(old_fs);
+
+ return err;
+}
+
+static int lsm330dlc_gyro_restart_fifo(struct lsm330dlc_gyro_data *data)
+{
+ int res = 0;
+
+ res = i2c_smbus_write_byte_data(data->client,
+ FIFO_CTRL_REG, BYPASS_MODE);
+ if (res < 0) {
+ pr_err("%s : failed to set bypass_mode\n", __func__);
+ return res;
+ }
+
+ res = i2c_smbus_write_byte_data(data->client,
+ FIFO_CTRL_REG, FIFO_MODE | (data->entries - 1));
+
+ if (res < 0)
+ pr_err("%s : failed to set fifo_mode\n", __func__);
+
+ return res;
+}
+
+/* gyroscope data readout */
+static int lsm330dlc_gyro_read_values(struct i2c_client *client,
+ struct gyro_t *data, int total_read)
+{
+ int err;
+ int len = sizeof(*data) * (total_read ? (total_read - 1) : 1);
+ struct lsm330dlc_gyro_data *gyro_data = i2c_get_clientdata(client);
+ struct i2c_msg msg[2];
+ u8 reg_buf;
+
+ msg[0].addr = client->addr;
+ msg[0].buf = &reg_buf;
+ msg[0].flags = 0;
+ msg[0].len = 1;
+
+ msg[1].addr = client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].buf = gyro_data->fifo_data;
+
+ if (total_read > 1) {
+ reg_buf = AXISDATA_REG | AC;
+ msg[1].len = len;
+
+ err = i2c_transfer(client->adapter, msg, 2);
+ if (err != 2)
+ return (err < 0) ? err : -EIO;
+ }
+
+ reg_buf = AXISDATA_REG | AC;
+ msg[1].len = sizeof(*data);
+ err = i2c_transfer(client->adapter, msg, 2);
+ if (err != 2)
+ return (err < 0) ? err : -EIO;
+
+ data->x = (gyro_data->fifo_data[1] << 8) | gyro_data->fifo_data[0];
+ data->y = (gyro_data->fifo_data[3] << 8) | gyro_data->fifo_data[2];
+ data->z = (gyro_data->fifo_data[5] << 8) | gyro_data->fifo_data[4];
+
+ return 0;
+}
+
+static int lsm330dlc_gyro_report_values\
+ (struct lsm330dlc_gyro_data *gyro_data)
+{
+ int res;
+ struct gyro_t data;
+
+ res = lsm330dlc_gyro_read_values(gyro_data->client, &data,
+ gyro_data->entries);
+ if (res < 0)
+ return res;
+
+ data.x -= gyro_data->cal_data.x;
+ data.y -= gyro_data->cal_data.y;
+ data.z -= gyro_data->cal_data.z;
+
+ input_report_rel(gyro_data->input_dev, REL_RX, data.x);
+ input_report_rel(gyro_data->input_dev, REL_RY, data.y);
+ input_report_rel(gyro_data->input_dev, REL_RZ, data.z);
+ input_sync(gyro_data->input_dev);
+
+ lsm330dlc_gyro_restart_fifo(gyro_data);
+
+#ifdef LOGGING_GYRO
+ printk(KERN_INFO "%s, x = %d, y = %d, z = %d, count = %d\n"
+ , __func__, data.x, data.y, data.z, gyro_data->count);
+#endif
+
+ return res;
+}
+
+static enum hrtimer_restart lsm330dlc_gyro_timer_func(struct hrtimer *timer)
+{
+ struct lsm330dlc_gyro_data *gyro_data\
+ = container_of(timer, struct lsm330dlc_gyro_data, timer);
+ queue_work(gyro_data->lsm330dlc_gyro_wq, &gyro_data->work);
+ hrtimer_forward_now(&gyro_data->timer, gyro_data->polling_delay);
+ return HRTIMER_RESTART;
+}
+
+static void lsm330dlc_gyro_work_func(struct work_struct *work)
+{
+ int res, retry = 3;
+ struct lsm330dlc_gyro_data *gyro_data\
+ = container_of(work, struct lsm330dlc_gyro_data, work);
+ s32 status = 0;
+
+ do {
+ status = i2c_smbus_read_byte_data(gyro_data->client,
+ STATUS_REG);
+ if (status & 0x08)
+ break;
+ } while (retry--);
+
+ if (status & 0x08 && gyro_data->self_test == false) {
+ res = lsm330dlc_gyro_read_values(gyro_data->client,
+ &gyro_data->xyz_data, 0);
+ if (res < 0)
+ pr_err("%s, reading data fail(res = %d)\n",
+ __func__, res);
+ } else
+ pr_warn("%s, use last data.\n", __func__);
+
+ gyro_data->xyz_data.x -= gyro_data->cal_data.x;
+ gyro_data->xyz_data.y -= gyro_data->cal_data.y;
+ gyro_data->xyz_data.z -= gyro_data->cal_data.z;
+
+ input_report_rel(gyro_data->input_dev, REL_RX, gyro_data->xyz_data.x);
+ input_report_rel(gyro_data->input_dev, REL_RY, gyro_data->xyz_data.y);
+ input_report_rel(gyro_data->input_dev, REL_RZ, gyro_data->xyz_data.z);
+ input_sync(gyro_data->input_dev);
+
+#ifdef LOGGING_GYRO
+ pr_info("%s, x = %d, y = %d, z = %d\n"
+ , __func__, gyro_data->xyz_data.x, gyro_data->xyz_data.y,
+ gyro_data->xyz_data.z);
+#endif
+}
+
+static irqreturn_t lsm330dlc_gyro_interrupt_thread(int irq\
+ , void *lsm330dlc_gyro_data_p)
+{
+ int res;
+ struct lsm330dlc_gyro_data *data = lsm330dlc_gyro_data_p;
+
+ if (unlikely(data->self_test)) {
+ disable_irq_nosync(irq);
+ complete(&data->data_ready);
+ return IRQ_HANDLED;
+ }
+#ifdef LOGGING_GYRO
+ data->count++;
+#endif
+ res = lsm330dlc_gyro_report_values(data);
+ if (res < 0)
+ pr_err("%s: failed to report gyro values\n", __func__);
+
+ return IRQ_HANDLED;
+}
+
+static ssize_t lsm330dlc_gyro_selftest_dps_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lsm330dlc_gyro_data *data = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n", data->dps);
+}
+
+#ifdef DEBUG_REGISTER
+static ssize_t register_data_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lsm330dlc_gyro_data *data = dev_get_drvdata(dev);
+ struct i2c_msg msg[2];
+ u8 reg1, reg2, reg3, reg4, reg5, fifo_ctrl, fifo_src;
+ u8 reg_buf;
+ int err;
+
+ msg[0].addr = data->client->addr;
+ msg[0].buf = &reg_buf;
+ msg[0].flags = 0;
+ msg[0].len = 1;
+
+ reg_buf = CTRL_REG1;
+ msg[1].addr = data->client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].len = 1;
+ msg[1].buf = &reg1;
+
+ err = i2c_transfer(data->client->adapter, msg, 2);
+ if (err != 2)
+ return (err < 0) ? err : -EIO;
+
+ reg_buf = CTRL_REG2;
+ msg[1].buf = &reg2;
+
+ err = i2c_transfer(data->client->adapter, msg, 2);
+ if (err != 2)
+ return (err < 0) ? err : -EIO;
+
+ reg_buf = CTRL_REG3;
+ msg[1].buf = &reg3;
+
+ err = i2c_transfer(data->client->adapter, msg, 2);
+ if (err != 2)
+ return (err < 0) ? err : -EIO;
+
+ reg_buf = CTRL_REG4;
+ msg[1].buf = &reg4;
+
+ err = i2c_transfer(data->client->adapter, msg, 2);
+ if (err != 2)
+ return (err < 0) ? err : -EIO;
+
+ reg_buf = CTRL_REG5;
+ msg[1].buf = &reg5;
+
+ err = i2c_transfer(data->client->adapter, msg, 2);
+ if (err != 2)
+ return (err < 0) ? err : -EIO;
+
+ reg_buf = FIFO_CTRL_REG;
+ msg[1].buf = &fifo_ctrl;
+
+ err = i2c_transfer(data->client->adapter, msg, 2);
+ if (err != 2)
+ return (err < 0) ? err : -EIO;
+
+ reg_buf = FIFO_SRC_REG;
+ msg[1].buf = &fifo_src;
+
+ err = i2c_transfer(data->client->adapter, msg, 2);
+ if (err != 2)
+ return (err < 0) ? err : -EIO;
+
+ return sprintf(buf, "0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n"\
+ , reg1, reg2, reg3, reg4, reg5, fifo_ctrl, fifo_src);
+}
+#endif
+
+static ssize_t lsm330dlc_gyro_selftest_dps_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct lsm330dlc_gyro_data *data = dev_get_drvdata(dev);
+ int new_dps = 0;
+ int err;
+ u8 ctrl;
+
+ sscanf(buf, "%d", &new_dps);
+
+ /* check out dps validity */
+ if (new_dps != DPS250 && new_dps != DPS500 && new_dps != DPS2000) {
+ pr_err("%s: wrong dps(%d)\n", __func__, new_dps);
+ return -1;
+ }
+
+ ctrl = (data->ctrl_regs[3] & ~FS_MASK);
+
+ if (new_dps == DPS250)
+ ctrl |= FS_250DPS;
+ else if (new_dps == DPS500)
+ ctrl |= FS_500DPS;
+ else if (new_dps == DPS2000)
+ ctrl |= FS_2000DPS;
+ else
+ ctrl |= FS_DEFULAT_DPS;
+
+ /* apply new dps */
+ mutex_lock(&data->lock);
+ data->ctrl_regs[3] = ctrl;
+
+ err = i2c_smbus_write_byte_data(data->client, CTRL_REG4, ctrl);
+ if (err < 0) {
+ pr_err("%s: updating dps failed\n", __func__);
+ mutex_unlock(&data->lock);
+ return err;
+ }
+ mutex_unlock(&data->lock);
+
+ data->dps = new_dps;
+ pr_err("%s: %d dps stored\n", __func__, data->dps);
+
+ return count;
+}
+
+static ssize_t lsm330dlc_gyro_show_enable(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lsm330dlc_gyro_data *data = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n", data->enable);
+}
+
+static ssize_t lsm330dlc_gyro_set_enable(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ int err = 0;
+ struct lsm330dlc_gyro_data *data = dev_get_drvdata(dev);
+ bool new_enable;
+
+ if (sysfs_streq(buf, "1"))
+ new_enable = true;
+ else if (sysfs_streq(buf, "0"))
+ new_enable = false;
+ else {
+ pr_debug("%s: invalid value %d\n", __func__, *buf);
+ return -EINVAL;
+ }
+
+ printk(KERN_INFO "%s, %d enable : %d.\n", __func__, __LINE__,\
+ new_enable);
+
+ if (new_enable == data->enable)
+ return size;
+
+ mutex_lock(&data->lock);
+ if (new_enable) {
+ /* load cal data */
+ err = lsm330dlc_gyro_open_calibration(data);
+ if (err < 0 && err != -ENOENT)
+ pr_err("%s: lsm330dlc_gyro_open_calibration() failed\n",
+ __func__);
+ /* turning on */
+ err = i2c_smbus_write_i2c_block_data(data->client,
+ CTRL_REG1 | AC, sizeof(data->ctrl_regs),
+ data->ctrl_regs);
+ if (err < 0) {
+ err = -EIO;
+ goto unlock;
+ }
+
+ if (data->interruptible) {
+ enable_irq(data->client->irq);
+
+ /* reset fifo entries */
+ err = lsm330dlc_gyro_restart_fifo(data);
+ if (err < 0) {
+ err = -EIO;
+ goto turn_off;
+ }
+ }
+
+ if (!data->interruptible) {
+ hrtimer_start(&data->timer,
+ data->polling_delay, HRTIMER_MODE_REL);
+ }
+ } else {
+ if (data->interruptible)
+ disable_irq(data->client->irq);
+ else {
+ hrtimer_cancel(&data->timer);
+ cancel_work_sync(&data->work);
+ }
+ /* turning off */
+ err = i2c_smbus_write_byte_data(data->client,
+ CTRL_REG1, 0x00);
+ if (err < 0)
+ goto unlock;
+ }
+ data->enable = new_enable;
+
+ printk(KERN_INFO "%s, %d lsm330dlc_gyro enable is done.\n",\
+ __func__, __LINE__);
+
+turn_off:
+ if (err < 0)
+ i2c_smbus_write_byte_data(data->client,
+ CTRL_REG1, 0x00);
+unlock:
+ mutex_unlock(&data->lock);
+
+ return err ? err : size;
+}
+
+static u64 lsm330dlc_gyro_get_delay_ns(struct lsm330dlc_gyro_data *k3)
+{
+ u64 delay = -1;
+
+ delay = k3->time_to_read * k3->entries;
+ delay = ktime_to_ns(ns_to_ktime(delay));
+
+ return delay;
+}
+
+static int lsm330dlc_gyro_set_delay_ns(struct lsm330dlc_gyro_data *k3\
+ , u64 delay_ns)
+{
+ int res = 0;
+ int odr_value = ODR95_BW25;
+ int i = 0, odr_table_size = ARRAY_SIZE(odr_delay_table) - 1;
+ u8 ctrl;
+ u64 new_delay = 0;
+
+ mutex_lock(&k3->lock);
+ if (!k3->interruptible) {
+ hrtimer_cancel(&k3->timer);
+ cancel_work_sync(&k3->work);
+ } else
+ disable_irq(k3->client->irq);
+
+ /* round to the nearest supported ODR that is equal or above than
+ * the requested value
+ * 10ms(entries = 1), 20ms(entries = 2),
+ * 67ms(entries = 6), 200ms(entries = 20)
+ */
+ if (delay_ns == 10000000LL && k3->interruptible)
+ delay_ns = odr_delay_table[odr_table_size].delay_ns;
+
+ if (delay_ns >= MAX_DELAY) {/* > max delay */
+ k3->entries = MAX_ENTRY;
+ odr_value = odr_delay_table[odr_table_size].odr;
+ k3->time_to_read = odr_delay_table[odr_table_size].delay_ns;
+ new_delay = MAX_DELAY;
+ } else if (delay_ns <= odr_delay_table[0].delay_ns) { /* < min delay */
+ k3->entries = 1;
+ odr_value = odr_delay_table[0].odr;
+ k3->time_to_read = odr_delay_table[0].delay_ns;
+ new_delay = odr_delay_table[0].delay_ns;
+ } else {
+ for (i = odr_table_size; i >= 0; i--) {
+ if (delay_ns >= odr_delay_table[i].delay_ns) {
+ new_delay = delay_ns;
+ do_div(delay_ns, odr_delay_table[i].delay_ns);
+ k3->entries = delay_ns;
+ odr_value = odr_delay_table[i].odr;
+ k3->time_to_read = odr_delay_table[i].delay_ns;
+ break;
+ }
+ }
+ }
+
+ if (k3->interruptible)
+ pr_info("%s, k3->entries=%d, odr_value=0x%x\n", __func__,
+ k3->entries, odr_value);
+
+ if (odr_value != (k3->ctrl_regs[0] & ODR_MASK)) {
+ ctrl = (k3->ctrl_regs[0] & ~ODR_MASK);
+ ctrl |= odr_value;
+ k3->ctrl_regs[0] = ctrl;
+ res = i2c_smbus_write_byte_data(k3->client, CTRL_REG1, ctrl);
+ }
+
+ if (k3->interruptible) {
+ enable_irq(k3->client->irq);
+
+ /* (re)start fifo */
+ lsm330dlc_gyro_restart_fifo(k3);
+ }
+
+ if (!k3->interruptible) {
+ pr_info("%s, delay_ns=%lld, odr_value=0x%x\n",
+ __func__, new_delay, odr_value);
+ k3->polling_delay = ns_to_ktime(new_delay);
+ if (k3->enable)
+ hrtimer_start(&k3->timer,
+ k3->polling_delay, HRTIMER_MODE_REL);
+ }
+
+ mutex_unlock(&k3->lock);
+
+ return res;
+}
+
+static ssize_t lsm330dlc_gyro_show_delay(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lsm330dlc_gyro_data *data = dev_get_drvdata(dev);
+ u64 delay;
+
+ if (!data->interruptible)
+ return sprintf(buf, "%lld\n", ktime_to_ns(data->polling_delay));
+
+ delay = lsm330dlc_gyro_get_delay_ns(data);
+
+ return sprintf(buf, "%lld\n", delay);
+}
+
+static ssize_t lsm330dlc_gyro_set_delay(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ int res;
+ struct lsm330dlc_gyro_data *data = dev_get_drvdata(dev);
+ u64 delay_ns;
+
+ res = strict_strtoll(buf, 10, &delay_ns);
+ if (res < 0)
+ return res;
+
+ lsm330dlc_gyro_set_delay_ns(data, delay_ns);
+
+ return size;
+}
+
+static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
+ lsm330dlc_gyro_show_enable, lsm330dlc_gyro_set_enable);
+static DEVICE_ATTR(poll_delay, S_IRUGO | S_IWUSR | S_IWGRP,
+ lsm330dlc_gyro_show_delay, lsm330dlc_gyro_set_delay);
+
+/*************************************************************************/
+/* lsm330dlc_gyro Sysfs */
+/*************************************************************************/
+
+/* Device Initialization */
+static int device_init(struct lsm330dlc_gyro_data *data)
+{
+ int err;
+ u8 buf[5];
+
+ buf[0] = 0x6f;
+ buf[1] = 0x00;
+ buf[2] = 0x00;
+ buf[3] = 0x00;
+ buf[4] = 0x00;
+ err = i2c_smbus_write_i2c_block_data(data->client,
+ CTRL_REG1 | AC, sizeof(buf), buf);
+ if (err < 0)
+ pr_err("%s: CTRL_REGs i2c writing failed\n", __func__);
+
+ return err;
+}
+static ssize_t lsm330dlc_gyro_power_off(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lsm330dlc_gyro_data *data = dev_get_drvdata(dev);
+ int err;
+
+ err = i2c_smbus_write_byte_data(data->client,
+ CTRL_REG1, 0x00);
+
+ printk(KERN_INFO "[%s] result of power off = %d\n", __func__, err);
+
+ return sprintf(buf, "%d\n", (err < 0 ? 0 : 1));
+}
+
+static ssize_t lsm330dlc_gyro_power_on(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lsm330dlc_gyro_data *data = dev_get_drvdata(dev);
+ int err;
+
+ err = device_init(data);
+ if (err < 0) {
+ pr_err("%s: device_init() failed\n", __func__);
+ return 0;
+ }
+
+ printk(KERN_INFO "[%s] result of device init = %d\n", __func__, err);
+
+ return sprintf(buf, "%d\n", 1);
+}
+
+static ssize_t lsm330dlc_gyro_get_temp(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lsm330dlc_gyro_data *data = dev_get_drvdata(dev);
+ char temp;
+
+ temp = i2c_smbus_read_byte_data(data->client, OUT_TEMP);
+ if (temp < 0) {
+ pr_err("%s: STATUS_REGS i2c reading failed\n", __func__);
+ return 0;
+ }
+
+ printk(KERN_INFO "[%s] read temperature : %d\n", __func__, temp);
+
+ return sprintf(buf, "%d\n", temp);
+}
+
+static int lsm330dlc_gyro_fifo_self_test(struct lsm330dlc_gyro_data *data,\
+ u8 *cal_pass, s16 *zero_rate_data)
+{
+ struct gyro_t raw_data;
+ int err;
+ int i, j;
+ s16 raw[3] = {0,}, zero_rate_delta[3] = {0,};
+ int sum_raw[3] = {0,};
+ bool zero_rate_read_2nd = false;
+ u8 reg[5];
+ u8 fifo_pass = 2;
+ u8 status_reg;
+ struct file *cal_filp = NULL;
+ mm_segment_t old_fs;
+
+ /* fifo mode, enable interrupt, 500dps */
+ reg[0] = 0x6F;
+ reg[1] = 0x00;
+ reg[2] = 0x04;
+ reg[3] = 0x90;
+ reg[4] = 0x40;
+
+ for (i = 0; i < 10; i++) {
+ err = i2c_smbus_write_i2c_block_data(data->client,
+ CTRL_REG1 | AC, sizeof(reg), reg);
+ if (err >= 0)
+ break;
+ }
+ if (err < 0) {
+ pr_err("%s: CTRL_REGs i2c writing failed\n", __func__);
+ goto exit;
+ }
+
+ /* Power up, wait for 800ms for stable output */
+ msleep(800);
+
+read_zero_rate_again:
+ for (i = 0; i < 10; i++) {
+ err = i2c_smbus_write_byte_data(data->client,
+ FIFO_CTRL_REG, BYPASS_MODE);
+ if (err >= 0)
+ break;
+ }
+ if (err < 0) {
+ pr_err("%s : failed to set bypass_mode\n", __func__);
+ goto exit;
+ }
+
+ for (i = 0; i < 10; i++) {
+ err = i2c_smbus_write_byte_data(data->client,
+ FIFO_CTRL_REG, FIFO_MODE | FIFO_TEST_WTM);
+ if (err >= 0)
+ break;
+ }
+ if (err < 0) {
+ pr_err("%s: failed to set fifo_mode\n", __func__);
+ goto exit;
+ }
+
+ /* if interrupt mode */
+ if (!data->enable && data->interruptible) {
+ enable_irq(data->client->irq);
+ err = wait_for_completion_timeout(&data->data_ready, 5*HZ);
+ msleep(200);
+ if (err <= 0) {
+ disable_irq(data->client->irq);
+ if (!err)
+ pr_err("%s: wait timed out\n", __func__);
+ goto exit;
+ }
+ /* if polling mode */
+ } else
+ msleep(200);
+
+ /* check out watermark status */
+ status_reg = i2c_smbus_read_byte_data(data->client, FIFO_SRC_REG);
+ if (!(status_reg & 0x80)) {
+ pr_err("%s: Watermark level is not enough\n", __func__);
+ goto exit;
+ }
+
+ /* read fifo entries */
+ err = lsm330dlc_gyro_read_values(data->client,
+ &raw_data, FIFO_TEST_WTM + 2);
+ if (err < 0) {
+ pr_err("%s: lsm330dlc_gyro_read_values() failed\n", __func__);
+ goto exit;
+ }
+
+ /* print out fifo data */
+ printk(KERN_INFO "[gyro_self_test] fifo data\n");
+ for (i = 0; i < sizeof(raw_data) * (FIFO_TEST_WTM + 1);
+ i += sizeof(raw_data)) {
+ raw[0] = (data->fifo_data[i+1] << 8)
+ | data->fifo_data[i];
+ raw[1] = (data->fifo_data[i+3] << 8)
+ | data->fifo_data[i+2];
+ raw[2] = (data->fifo_data[i+5] << 8)
+ | data->fifo_data[i+4];
+ pr_info("%2dth: %8d %8d %8d\n", i/6, raw[0], raw[1], raw[2]);
+
+ /* for calibration of gyro sensor data */
+ sum_raw[0] += raw[0];
+ sum_raw[1] += raw[1];
+ sum_raw[2] += raw[2];
+
+ for (j = 0; j < 3; j++) {
+ if (raw[j] < MIN_ZERO_RATE || raw[j] > MAX_ZERO_RATE) {
+ pr_err("%s: %dth data(%d) is out of zero-rate",
+ __func__, i/6, raw[j]);
+ pr_err("%s: fifo test failed\n", __func__);
+ fifo_pass = 0;
+ *cal_pass = 0;
+ goto exit;
+ }
+ }
+ }
+
+ /* zero_rate_data */
+ zero_rate_data[0] = raw[0] * 175 / 10000;
+ zero_rate_data[1] = raw[1] * 175 / 10000;
+ zero_rate_data[2] = raw[2] * 175 / 10000;
+
+ if (zero_rate_read_2nd == true) {
+ /* check zero_rate second time */
+ zero_rate_delta[0] -= zero_rate_data[0];
+ zero_rate_delta[1] -= zero_rate_data[1];
+ zero_rate_delta[2] -= zero_rate_data[2];
+ pr_info("[gyro_self_test] zero rate second: %8d %8d %8d\n",
+ zero_rate_data[0], zero_rate_data[1],
+ zero_rate_data[2]);
+ pr_info("[gyro_self_test] zero rate delta: %8d %8d %8d\n",
+ zero_rate_delta[0], zero_rate_delta[1],
+ zero_rate_delta[2]);
+
+ if ((-5 < zero_rate_delta[0] && zero_rate_delta[0] < 5) &&
+ (-5 < zero_rate_delta[1] && zero_rate_delta[1] < 5) &&
+ (-5 < zero_rate_delta[2] && zero_rate_delta[2] < 5)) {
+ /* calibration of gyro sensor data */
+ data->cal_data.x = sum_raw[0]/(FIFO_TEST_WTM + 1);
+ data->cal_data.y = sum_raw[1]/(FIFO_TEST_WTM + 1);
+ data->cal_data.z = sum_raw[2]/(FIFO_TEST_WTM + 1);
+ pr_info("%s: cal data (%d,%d,%d)\n", __func__,
+ data->cal_data.x, data->cal_data.y,
+ data->cal_data.z);
+
+ /* save cal data */
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+
+ cal_filp = filp_open(CALIBRATION_FILE_PATH,
+ O_CREAT | O_TRUNC | O_WRONLY, 0666);
+ if (IS_ERR(cal_filp)) {
+ pr_err("%s: Can't open calibration file\n",
+ __func__);
+ set_fs(old_fs);
+ err = PTR_ERR(cal_filp);
+ return err;
+ }
+
+ err = cal_filp->f_op->write(cal_filp,
+ (char *)&data->cal_data, 3 * sizeof(s16),
+ &cal_filp->f_pos);
+ if (err != 3 * sizeof(s16)) {
+ pr_err("%s: Can't write the cal data to file\n",
+ __func__);
+ err = -EIO;
+ }
+
+ filp_close(cal_filp, current->files);
+ set_fs(old_fs);
+
+ *cal_pass = 1;
+ } /* else calibration is failed */
+ } else { /* check zero_rate first time, go to check again */
+ zero_rate_read_2nd = true;
+ sum_raw[0] = 0;
+ sum_raw[1] = 0;
+ sum_raw[2] = 0;
+ zero_rate_delta[0] = zero_rate_data[0];
+ zero_rate_delta[1] = zero_rate_data[1];
+ zero_rate_delta[2] = zero_rate_data[2];
+ pr_info("[gyro_self_test] zero rate first: %8d %8d %8d\n",
+ zero_rate_data[0], zero_rate_data[1],
+ zero_rate_data[2]);
+ goto read_zero_rate_again;
+ }
+
+ fifo_pass = 1;
+exit:
+ /* 1: success, 0: fail, 2: retry */
+ return fifo_pass;
+}
+
+static int lsm330dlc_gyro_bypass_self_test\
+ (struct lsm330dlc_gyro_data *gyro_data, int NOST[3], int ST[3])
+{
+ int differ_x = 0, differ_y = 0, differ_z = 0;
+ int err, i, j, sample_num = 6;
+ s16 raw[3] = {0,};
+ s32 temp = 0;
+ u8 data[6] = {0,};
+ u8 reg[5];
+ u8 bZYXDA = 0;
+ u8 bypass_pass = 2;
+
+ /* Initialize Sensor, turn on sensor, enable P/R/Y */
+ /* Set BDU=1, Set ODR=200Hz, Cut-Off Frequency=50Hz, FS=2000dps */
+ reg[0] = 0x6f;
+ reg[1] = 0x00;
+ reg[2] = 0x00;
+ reg[3] = 0xA0;
+ reg[4] = 0x02;
+
+ for (i = 0; i < 10; i++) {
+ err = i2c_smbus_write_i2c_block_data(gyro_data->client,
+ CTRL_REG1 | AC, sizeof(reg), reg);
+ if (err >= 0)
+ break;
+ }
+ if (err < 0) {
+ pr_err("%s: CTRL_REGs i2c writing failed\n", __func__);
+ goto exit;
+ }
+
+ for (i = 0; i < 10; i++) {
+ err = i2c_smbus_write_byte_data(gyro_data->client,
+ FIFO_CTRL_REG, BYPASS_MODE);
+ if (err >= 0)
+ break;
+ }
+ if (err < 0) {
+ pr_err("%s : failed to set bypass_mode\n", __func__);
+ goto exit;
+ }
+
+ /* Power up, wait for 800ms for stable output */
+ msleep(800);
+
+ /* Read 5 samples output before self-test on */
+ for (i = 0; i < 5; i++) {
+ /* check ZYXDA ready bit */
+ for (j = 0; j < 10; j++) {
+ temp = i2c_smbus_read_byte_data(gyro_data->client,
+ STATUS_REG);
+ if (temp >= 0) {
+ bZYXDA = temp & 0x08;
+ if (!bZYXDA) {
+ usleep_range(10000, 20000);
+ pr_err("%s: %d,%d: no_data_ready",
+ __func__, i, j);
+ continue;
+ } else
+ break;
+ }
+ }
+ if (temp < 0) {
+ pr_err("%s: STATUS_REGS i2c reading failed\n",
+ __func__);
+ goto exit;
+ }
+
+ for (j = 0; j < 6; j++) {
+ data[j] = i2c_smbus_read_byte_data(gyro_data->client,
+ AXISDATA_REG+j);
+ }
+ if (i == 0)
+ continue;
+
+ raw[0] = (data[1] << 8) | data[0];
+ raw[1] = (data[3] << 8) | data[2];
+ raw[2] = (data[5] << 8) | data[4];
+
+ NOST[0] += raw[0];
+ NOST[1] += raw[1];
+ NOST[2] += raw[2];
+
+ printk(KERN_INFO "[gyro_self_test] raw[0] = %d\n", raw[0]);
+ printk(KERN_INFO "[gyro_self_test] raw[1] = %d\n", raw[1]);
+ printk(KERN_INFO "[gyro_self_test] raw[2] = %d\n\n", raw[2]);
+ }
+
+ for (i = 0; i < 3; i++)
+ printk(KERN_INFO "[gyro_self_test] "
+ "SUM of NOST[%d] = %d\n", i, NOST[i]);
+
+ /* calculate average of NOST and covert from ADC to DPS */
+ for (i = 0; i < 3; i++) {
+ NOST[i] = (NOST[i] / 5) * 70 / 1000;
+ printk(KERN_INFO "[gyro_self_test] "
+ "AVG of NOST[%d] = %d\n", i, NOST[i]);
+ }
+ printk(KERN_INFO "\n");
+
+ /* Enable Self Test */
+ reg[0] = 0xA2;
+ for (i = 0; i < 10; i++) {
+ err = i2c_smbus_write_byte_data(gyro_data->client,
+ CTRL_REG4, reg[0]);
+ if (err >= 0)
+ break;
+ }
+ if (temp < 0) {
+ pr_err("%s: CTRL_REG4 i2c writing failed\n", __func__);
+ goto exit;
+ }
+
+ msleep(100);
+
+ /* Read 5 samples output after self-test on */
+ /* The first data is useless on the LSM330DLC selftest. */
+ for (i = 0; i < sample_num; i++) {
+ /* check ZYXDA ready bit */
+ for (j = 0; j < 10; j++) {
+ temp = i2c_smbus_read_byte_data(gyro_data->client,
+ STATUS_REG);
+ if (temp >= 0) {
+ bZYXDA = temp & 0x08;
+ if (!bZYXDA) {
+ usleep_range(10000, 20000);
+ pr_err("%s: %d,%d: no_data_ready",
+ __func__, i, j);
+ continue;
+ } else
+ break;
+ }
+
+ }
+ if (temp < 0) {
+ pr_err("%s: STATUS_REGS i2c reading failed\n",
+ __func__);
+ goto exit;
+ }
+
+ for (j = 0; j < 6; j++) {
+ data[j] = i2c_smbus_read_byte_data(gyro_data->client,
+ AXISDATA_REG+j);
+ }
+ if (i == 0)
+ continue;
+
+ raw[0] = (data[1] << 8) | data[0];
+ raw[1] = (data[3] << 8) | data[2];
+ raw[2] = (data[5] << 8) | data[4];
+
+ ST[0] += raw[0];
+ ST[1] += raw[1];
+ ST[2] += raw[2];
+
+ printk(KERN_INFO "[gyro_self_test] raw[0] = %d\n", raw[0]);
+ printk(KERN_INFO "[gyro_self_test] raw[1] = %d\n", raw[1]);
+ printk(KERN_INFO "[gyro_self_test] raw[2] = %d\n\n", raw[2]);
+ }
+
+ for (i = 0; i < 3; i++)
+ printk(KERN_INFO "[gyro_self_test] "
+ "SUM of ST[%d] = %d\n", i, ST[i]);
+
+ /* calculate average of ST and convert from ADC to dps */
+ for (i = 0; i < 3; i++) {
+ /* When FS=2000, 70 mdps/digit */
+ ST[i] = (ST[i] / 5) * 70 / 1000;
+ printk(KERN_INFO "[gyro_self_test] "
+ "AVG of ST[%d] = %d\n", i, ST[i]);
+ }
+
+ /* check whether pass or not */
+ if (ST[0] >= NOST[0]) /* for x */
+ differ_x = ST[0] - NOST[0];
+ else
+ differ_x = NOST[0] - ST[0];
+
+ if (ST[1] >= NOST[1]) /* for y */
+ differ_y = ST[1] - NOST[1];
+ else
+ differ_y = NOST[1] - ST[1];
+
+ if (ST[2] >= NOST[2]) /* for z */
+ differ_z = ST[2] - NOST[2];
+ else
+ differ_z = NOST[2] - ST[2];
+
+ printk(KERN_INFO "[gyro_self_test] differ x:%d, y:%d, z:%d\n",
+ differ_x, differ_y, differ_z);
+
+ if ((MIN_ST <= differ_x && differ_x <= MAX_ST)
+ && (MIN_ST <= differ_y && differ_y <= MAX_ST)
+ && (MIN_ST <= differ_z && differ_z <= MAX_ST))
+ bypass_pass = 1;
+ else
+ bypass_pass = 0;
+
+exit:
+ return bypass_pass;
+}
+
+static ssize_t lsm330dlc_gyro_self_test(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lsm330dlc_gyro_data *data = dev_get_drvdata(dev);
+ int NOST[3] = {0,}, ST[3] = {0,};
+ int err;
+ int i;
+ u8 backup_regs[5] = {0,};
+ u8 fifo_pass = 2;
+ u8 bypass_pass = 2;
+ u8 cal_pass = 0;
+ s16 zero_rate_data[3] = {0,};
+
+ data->self_test = true;
+ /* before starting self-test, backup register */
+ for (i = 0; i < 10; i++) {
+ err = i2c_smbus_read_i2c_block_data(data->client,
+ CTRL_REG1 | AC, sizeof(backup_regs), backup_regs);
+ if (err >= 0)
+ break;
+ }
+ if (err < 0) {
+ pr_err("%s: CTRL_REGs i2c reading failed\n", __func__);
+ goto exit;
+ }
+
+ for (i = 0; i < 5; i++)
+ printk(KERN_INFO "[gyro_self_test] "
+ "backup reg[%d] = %2x\n", i, backup_regs[i]);
+
+ /* fifo self test & gyro calibration */
+ printk(KERN_INFO "\n[gyro_self_test] fifo self-test\n");
+
+ fifo_pass = lsm330dlc_gyro_fifo_self_test(data,
+ &cal_pass, zero_rate_data);
+
+ /* fifo self test result */
+ if (fifo_pass == 1)
+ printk(KERN_INFO "[gyro_self_test] fifo self-test success\n");
+ else if (!fifo_pass)
+ printk(KERN_INFO "[gyro_self_test] fifo self-test fail\n");
+ else
+ printk(KERN_INFO "[gyro_self_test] fifo self-test restry\n");
+
+ /* calibration result */
+ if (cal_pass == 1)
+ printk(KERN_INFO "[gyro_self_test] calibration success\n");
+ else if (!cal_pass)
+ printk(KERN_INFO "[gyro_self_test] calibration fail\n");
+
+ /* bypass self test */
+ printk(KERN_INFO "\n[gyro_self_test] bypass self-test\n");
+
+ bypass_pass = lsm330dlc_gyro_bypass_self_test(data, NOST, ST);
+ if (bypass_pass == 1)
+ printk(KERN_INFO "[gyro_self_test] bypass self-test success\n\n");
+ else if (!bypass_pass)
+ printk(KERN_INFO "[gyro_self_test] bypass self-test fail\n\n");
+ else
+ printk(KERN_INFO "[gyro_self_test] bypass self-test restry\n\n");
+
+ /* restore backup register */
+ for (i = 0; i < 10; i++) {
+ err = i2c_smbus_write_i2c_block_data(data->client,
+ CTRL_REG1 | AC, sizeof(backup_regs),
+ backup_regs);
+ if (err >= 0)
+ break;
+ }
+ if (err < 0)
+ pr_err("%s: CTRL_REGs i2c writing failed\n", __func__);
+
+exit:
+ data->self_test = false;
+ if (!data->enable) {
+ /* If gyro is not enabled, make it go to the power down mode. */
+ err = i2c_smbus_write_byte_data(data->client,
+ CTRL_REG1, 0x00);
+ if (err < 0)
+ pr_err("%s: CTRL_REGs i2c writing failed\n", __func__);
+ }
+
+ if (fifo_pass == 2 && bypass_pass == 2)
+ printk(KERN_INFO "[gyro_self_test] self-test result : retry\n");
+ else
+ printk(KERN_INFO "[gyro_self_test] self-test result : %s\n",
+ fifo_pass & bypass_pass & cal_pass ? "pass" : "fail");
+
+ return sprintf(buf, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
+ NOST[0], NOST[1], NOST[2], ST[0], ST[1], ST[2],
+ zero_rate_data[0], zero_rate_data[1], zero_rate_data[2],
+ fifo_pass & bypass_pass & cal_pass, fifo_pass, cal_pass);
+}
+
+static ssize_t lsm330dlc_gyro_vendor_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", VENDOR);
+}
+
+static ssize_t lsm330dlc_gyro_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", CHIP_ID);
+}
+
+static DEVICE_ATTR(power_off, 0664,
+ lsm330dlc_gyro_power_off, NULL);
+static DEVICE_ATTR(power_on, 0664,
+ lsm330dlc_gyro_power_on, NULL);
+static DEVICE_ATTR(temperature, 0664,
+ lsm330dlc_gyro_get_temp, NULL);
+static DEVICE_ATTR(selftest, 0664,
+ lsm330dlc_gyro_self_test, NULL);
+static DEVICE_ATTR(selftest_dps, 0664,
+ lsm330dlc_gyro_selftest_dps_show, lsm330dlc_gyro_selftest_dps_store);
+static DEVICE_ATTR(vendor, 0664,
+ lsm330dlc_gyro_vendor_show, NULL);
+static DEVICE_ATTR(name, 0664,
+ lsm330dlc_gyro_name_show, NULL);
+
+#ifdef DEBUG_REGISTER
+static DEVICE_ATTR(reg_data, 0664,
+ register_data_show, NULL);
+#endif
+
+/*************************************************************************/
+/* End of lsm330dlc_gyro Sysfs */
+/*************************************************************************/
+static int lsm330dlc_gyro_probe(struct i2c_client *client,
+ const struct i2c_device_id *devid)
+{
+ int ret;
+ int err = 0;
+ struct lsm330dlc_gyro_data *data;
+ struct input_dev *input_dev;
+
+ pr_info("%s, started", __func__);
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (data == NULL) {
+ dev_err(&client->dev,
+ "failed to allocate memory for module data\n");
+ err = -ENOMEM;
+ goto exit;
+ }
+
+ data->client = client;
+ data->drop_next_event = 0;
+
+ /* read chip id */
+ ret = i2c_smbus_read_byte_data(client, WHO_AM_I);
+ if (ret != DEVICE_ID) {
+ if (ret < 0) {
+ pr_err("%s: i2c for reading chip id failed\n",
+ __func__);
+ err = ret;
+ } else {
+ pr_err("%s : Device identification failed\n",
+ __func__);
+ err = -ENODEV;
+ }
+ goto err_read_reg;
+ }
+
+ mutex_init(&data->lock);
+ atomic_set(&data->opened, 0);
+ init_completion(&data->data_ready);
+
+ /* allocate gyro input_device */
+ 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;
+ }
+
+ data->input_dev = input_dev;
+ input_set_drvdata(input_dev, data);
+ input_dev->name = L3GD20_GYR_INPUT_NAME;
+ /* X */
+ input_set_capability(input_dev, EV_REL, REL_RX);
+ /* Y */
+ input_set_capability(input_dev, EV_REL, REL_RY);
+ /* Z */
+ input_set_capability(input_dev, EV_REL, REL_RZ);
+
+ err = input_register_device(input_dev);
+ if (err < 0) {
+ pr_err("%s: could not register input device\n", __func__);
+ input_free_device(data->input_dev);
+ goto err_input_register_device;
+ }
+
+ i2c_set_clientdata(client, data);
+ dev_set_drvdata(&input_dev->dev, data);
+
+ if (data->client->irq >= 0) { /* interrupt */
+ memcpy(&data->ctrl_regs, &default_ctrl_regs_fifo,
+ sizeof(default_ctrl_regs_fifo));
+ data->interruptible = true;
+ data->entries = 1;
+ err = request_threaded_irq(data->client->irq, NULL,
+ lsm330dlc_gyro_interrupt_thread\
+ , IRQF_TRIGGER_RISING | IRQF_ONESHOT,\
+ "lsm330dlc_gyro", data);
+ if (err < 0) {
+ pr_err("%s: can't allocate irq.\n", __func__);
+ goto err_request_irq;
+ }
+ disable_irq(data->client->irq);
+ } else { /* polling */
+ memcpy(&data->ctrl_regs, &default_ctrl_regs_bypass,
+ sizeof(default_ctrl_regs_bypass));
+ data->ctrl_regs[2] = 0x00; /* disable interrupt */
+ /* hrtimer settings. we poll for gyro values using a timer. */
+ hrtimer_init(&data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ data->polling_delay = ns_to_ktime(200 * NSEC_PER_MSEC);
+ data->timer.function = lsm330dlc_gyro_timer_func;
+
+ /* the timer just fires off a work queue request.
+ We need a thread to read i2c (can be slow and blocking). */
+ data->lsm330dlc_gyro_wq \
+ = create_singlethread_workqueue("lsm330dlc_gyro_wq");
+ if (!data->lsm330dlc_gyro_wq) {
+ err = -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(&data->work, lsm330dlc_gyro_work_func);
+ }
+
+ if (device_create_file(&input_dev->dev,
+ &dev_attr_enable) < 0) {
+ pr_err("%s: Failed to create device file(%s)!\n", __func__,
+ dev_attr_enable.attr.name);
+ goto err_device_create_file;
+ }
+
+ if (device_create_file(&input_dev->dev,
+ &dev_attr_poll_delay) < 0) {
+ pr_err("%s: Failed to create device file(%s)!\n", __func__,
+ dev_attr_poll_delay.attr.name);
+ goto err_device_create_file2;
+ }
+
+ /* create device node for lsm330dlc_gyro digital gyroscope */
+ data->dev = sensors_classdev_register("gyro_sensor");
+
+ if (IS_ERR(data->dev)) {
+ pr_err("%s: Failed to create device(gyro)\n", __func__);
+ err = PTR_ERR(data->dev);
+ goto err_device_create;
+ }
+
+ if (device_create_file(data->dev, &dev_attr_power_on) < 0) {
+ pr_err("%s: Failed to create device file(%s)!\n", __func__,
+ dev_attr_power_on.attr.name);
+ goto err_device_create_file3;
+ }
+
+ if (device_create_file(data->dev, &dev_attr_power_off) < 0) {
+ pr_err("%s: Failed to create device file(%s)!\n", __func__,
+ dev_attr_power_off.attr.name);
+ goto err_device_create_file4;
+ }
+
+ if (device_create_file(data->dev, &dev_attr_temperature) < 0) {
+ pr_err("%s: Failed to create device file(%s)!\n", __func__,
+ dev_attr_temperature.attr.name);
+ goto err_device_create_file5;
+ }
+
+ if (device_create_file(data->dev, &dev_attr_selftest) < 0) {
+ pr_err("%s: Failed to create device file(%s)!\n", __func__,
+ dev_attr_selftest.attr.name);
+ goto err_device_create_file6;
+ }
+
+ if (device_create_file(data->dev, &dev_attr_selftest_dps) < 0) {
+ pr_err("%s: Failed to create device file(%s)!\n", __func__,
+ dev_attr_selftest_dps.attr.name);
+ goto err_device_create_file7;
+ }
+
+ if (device_create_file(data->dev, &dev_attr_vendor) < 0) {
+ pr_err("%s: Failed to create device file(%s)!\n", __func__,
+ dev_attr_vendor.attr.name);
+ goto err_device_create_file8;
+ }
+
+ if (device_create_file(data->dev, &dev_attr_name) < 0) {
+ pr_err("%s: Failed to create device file(%s)!\n", __func__,
+ dev_attr_name.attr.name);
+ goto err_device_create_file9;
+ }
+
+#ifdef DEBUG_REGISTER
+ if (device_create_file(data->dev, &dev_attr_reg_data) < 0) {
+ pr_err("%s: Failed to create device file(%s)!\n", __func__,
+ dev_attr_reg_data.attr.name);
+ goto err_device_create_file10;
+ }
+#endif
+
+ dev_set_drvdata(data->dev, data);
+ pr_info("%s, successful", __func__);
+
+ return 0;
+
+#ifdef DEBUG_REGISTER
+err_device_create_file10:
+ device_remove_file(data->dev, &dev_attr_name);
+#endif
+err_device_create_file9:
+ device_remove_file(data->dev, &dev_attr_vendor);
+err_device_create_file8:
+ device_remove_file(data->dev, &dev_attr_selftest_dps);
+err_device_create_file7:
+ device_remove_file(data->dev, &dev_attr_selftest);
+err_device_create_file6:
+ device_remove_file(data->dev, &dev_attr_temperature);
+err_device_create_file5:
+ device_remove_file(data->dev, &dev_attr_power_off);
+err_device_create_file4:
+ device_remove_file(data->dev, &dev_attr_power_on);
+err_device_create_file3:
+ sensors_classdev_unregister(data->dev);
+err_device_create:
+ device_remove_file(&input_dev->dev, &dev_attr_poll_delay);
+err_device_create_file2:
+ device_remove_file(&input_dev->dev, &dev_attr_enable);
+err_device_create_file:
+ if (data->interruptible)
+ free_irq(data->client->irq, data);
+ else
+ destroy_workqueue(data->lsm330dlc_gyro_wq);
+err_create_workqueue:
+err_request_irq:
+ input_unregister_device(data->input_dev);
+err_input_register_device:
+err_input_allocate_device:
+ mutex_destroy(&data->lock);
+err_read_reg:
+ kfree(data);
+exit:
+ return err;
+}
+
+static int lsm330dlc_gyro_remove(struct i2c_client *client)
+{
+ int err = 0;
+ struct lsm330dlc_gyro_data *data = i2c_get_clientdata(client);
+
+ device_remove_file(data->dev, &dev_attr_vendor);
+ device_remove_file(data->dev, &dev_attr_name);
+ device_remove_file(data->dev, &dev_attr_selftest_dps);
+ device_remove_file(data->dev, &dev_attr_selftest);
+ device_remove_file(data->dev, &dev_attr_temperature);
+ device_remove_file(data->dev, &dev_attr_power_on);
+ device_remove_file(data->dev, &dev_attr_power_off);
+#ifdef DEBUG_REGISTER
+ device_remove_file(data->dev, &dev_attr_reg_data);
+#endif
+ sensors_classdev_unregister(data->dev);
+
+ if (data->interruptible) {
+ if (data->enable)
+ disable_irq(data->client->irq);
+ free_irq(data->client->irq, data);
+ } else {
+ hrtimer_cancel(&data->timer);
+ cancel_work_sync(&data->work);
+ destroy_workqueue(data->lsm330dlc_gyro_wq);
+ }
+
+ if (data->enable)
+ err = i2c_smbus_write_byte_data(data->client,
+ CTRL_REG1, 0x00);
+
+ device_remove_file(&data->input_dev->dev, &dev_attr_enable);
+ device_remove_file(&data->input_dev->dev, &dev_attr_poll_delay);
+ input_unregister_device(data->input_dev);
+ mutex_destroy(&data->lock);
+ kfree(data);
+
+ return err;
+}
+
+static int lsm330dlc_gyro_suspend(struct device *dev)
+{
+ int err = 0;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct lsm330dlc_gyro_data *data = i2c_get_clientdata(client);
+
+ if (data->enable) {
+ mutex_lock(&data->lock);
+ if (!data->interruptible) {
+ hrtimer_cancel(&data->timer);
+ cancel_work_sync(&data->work);
+ } else
+ disable_irq(data->client->irq);
+
+ err = i2c_smbus_write_byte_data(data->client,
+ CTRL_REG1, 0x00);
+ mutex_unlock(&data->lock);
+ }
+
+ return err;
+}
+
+static int lsm330dlc_gyro_resume(struct device *dev)
+{
+ int err = 0;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct lsm330dlc_gyro_data *data = i2c_get_clientdata(client);
+
+ if (data->enable) {
+ mutex_lock(&data->lock);
+ err = i2c_smbus_write_i2c_block_data(client,
+ CTRL_REG1 | AC, sizeof(data->ctrl_regs),
+ data->ctrl_regs);
+
+ if (data->interruptible) {
+ enable_irq(data->client->irq);
+
+ lsm330dlc_gyro_restart_fifo(data);
+ }
+
+ if (!data->interruptible)
+ hrtimer_start(&data->timer,
+ data->polling_delay, HRTIMER_MODE_REL);
+
+ mutex_unlock(&data->lock);
+ }
+
+ return err;
+}
+
+static const struct dev_pm_ops lsm330dlc_gyro_pm_ops = {
+ .suspend = lsm330dlc_gyro_suspend,
+ .resume = lsm330dlc_gyro_resume
+};
+
+static const struct i2c_device_id lsm330dlc_gyro_id[] = {
+ { LSM330DLC_GYRO_DEV_NAME, 0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, lsm330dlc_gyro_id);
+
+static struct i2c_driver lsm330dlc_gyro_driver = {
+ .probe = lsm330dlc_gyro_probe,
+ .remove = __devexit_p(lsm330dlc_gyro_remove),
+ .id_table = lsm330dlc_gyro_id,
+ .driver = {
+ .pm = &lsm330dlc_gyro_pm_ops,
+ .owner = THIS_MODULE,
+ .name = LSM330DLC_GYRO_DEV_NAME,
+ },
+};
+
+static int __init lsm330dlc_gyro_init(void)
+{
+ return i2c_add_driver(&lsm330dlc_gyro_driver);
+}
+
+static void __exit lsm330dlc_gyro_exit(void)
+{
+ i2c_del_driver(&lsm330dlc_gyro_driver);
+}
+
+module_init(lsm330dlc_gyro_init);
+module_exit(lsm330dlc_gyro_exit);
+
+MODULE_DESCRIPTION("LSM330DLC digital gyroscope driver");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sensor/pas2m110.c b/drivers/sensor/pas2m110.c
new file mode 100644
index 0000000..20e78c5
--- /dev/null
+++ b/drivers/sensor/pas2m110.c
@@ -0,0 +1,1009 @@
+/* linux/driver/input/misc/pas2m110.c
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/i2c.h>
+#include <linux/errno.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/miscdevice.h>
+#include <linux/gpio.h>
+#include <linux/wakelock.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/workqueue.h>
+#include <linux/uaccess.h>
+#include <linux/sensor/pas2m110.h>
+
+#define LIGHT_BUFFER_NUM 3
+#define PROX_READ_NUM 40
+
+/* ADDSEL is LOW */
+#define REGS_PS1_CMD 0x00
+#define REGS_PS2_CMD 0x01
+#define REGS_ALS1_CMD 0x02
+#define REGS_ALS2_CMD 0x03
+#define REGS_ALS_LSB 0x04
+#define REGS_ALS_MSB 0x05
+#define REGS_PS_DATA 0x06
+
+
+#define SUM_BUFFER_SIZE 5
+#define TOTAL_BUFFER_SIZE 3 /* average */
+
+int sum_value[SUM_BUFFER_SIZE] = {0, };
+u8 sum_cnt = 0, buf_cnt = 0;
+int sum_buffer[TOTAL_BUFFER_SIZE] = {0,};
+int use_value = 100;
+
+
+
+enum {
+ PS_CMD1,
+ PS_CMD2,
+ ALS_CMD1,
+ ALS_CMD2,
+ ALS_LSB,
+ ALS_MSB,
+ PS_DATA,
+};
+
+static u8 reg_defaults[7] = {
+ 0x51, /* ps1 register - Gain High1, 110% Current, 8msec, PS Active*/
+ 0x08, /* ps2 register
+ Level Low, PS interrupt output,
+ ALS interrupt disable, PS interrupt disable */
+ 0x31, /* als1 register
+ 128Step, *8Lux, 12.5ms, Manual mode start, ALS Active */
+ 0x01, /* als2 register - fine ALS 66.7%*/
+ 0x00, /* als lsb data */
+ 0x00, /* als msb data */
+ 0x00, /* PS_DATA:*/
+};
+
+static const int adc_table[4] = {
+ 15,
+ 150,
+ 1500,
+ 15000,
+};
+
+enum {
+ LIGHT_ENABLED = BIT(0),
+ PROXIMITY_ENABLED = BIT(1),
+};
+
+/* driver data */
+struct pas2m110_data {
+ struct input_dev *proximity_input_dev;
+ struct input_dev *light_input_dev;
+ struct i2c_client *i2c_client;
+ struct work_struct work_light;
+ struct work_struct work_prox;
+ struct hrtimer light_timer;
+ struct hrtimer prox_timer;
+ struct mutex power_lock;
+ struct wake_lock prx_wake_lock;
+ struct workqueue_struct *light_wq;
+ struct workqueue_struct *prox_wq;
+ struct class *lightsensor_class;
+ struct class *proximity_class;
+ struct device *switch_cmd_dev;
+ struct device *proximity_dev;
+ struct pas2m110_platform_data *pdata;
+ bool als_buf_initialized;
+ bool on;
+ int als_index_count;
+ int irq;
+ int avg[3];
+ int light_count;
+ int light_buffer;
+ ktime_t light_poll_delay;
+ ktime_t prox_poll_delay;
+ u8 power_state;
+};
+
+int pas2m110_i2c_read(struct pas2m110_data *pas2m110, u8 cmd, u8 *val)
+{
+ int err = 0;
+ int retry = 10;
+ struct i2c_client *client = pas2m110->i2c_client;
+
+ if ((client == NULL) || (!client->adapter))
+ return -ENODEV;
+
+ while (retry--) {
+ err = i2c_smbus_read_i2c_block_data(client, cmd, 1, val);
+ if (err >= 0)
+ return err;
+ }
+
+ pr_err("%s: i2c read failed at cmd 0x%x: %d\n", __func__, cmd, err);
+ return err;
+}
+
+int pas2m110_i2c_write(struct pas2m110_data *pas2m110, u8 cmd, u8 val)
+{
+ u8 data[2] = {0, };
+ int err = 0;
+ int retry = 10;
+ struct i2c_msg msg[1];
+ struct i2c_client *client = pas2m110->i2c_client;
+
+ if ((client == NULL) || (!client->adapter))
+ return -ENODEV;
+
+ data[0] = cmd;
+ data[1] = val;
+
+ msg->addr = 0x88 >> 1;
+ msg->flags = 0;
+ msg->len = 2;
+ msg->buf = data;
+
+ while (retry--) {
+ err = i2c_transfer(client->adapter, msg, 1);
+ if (err >= 0)
+ return err;
+ }
+
+ pr_err("%s: i2c write failed at addr 0x%x: %d\n", __func__, cmd, err);
+ return err;
+}
+
+static void pas2m110_light_enable(struct pas2m110_data *pas2m110)
+{
+ pas2m110->light_count = 0;
+ pas2m110->light_buffer = 0;
+ pas2m110_i2c_write(pas2m110, REGS_ALS1_CMD, reg_defaults[2]);
+ pas2m110_i2c_write(pas2m110, REGS_ALS2_CMD, reg_defaults[3]);
+
+ hrtimer_start(&pas2m110->light_timer, pas2m110->light_poll_delay,
+ HRTIMER_MODE_REL);
+}
+
+static void pas2m110_light_disable(struct pas2m110_data *pas2m110)
+{
+ pas2m110_i2c_write(pas2m110, REGS_ALS1_CMD,
+ reg_defaults[2]&(0x01^0xFF));
+ hrtimer_cancel(&pas2m110->light_timer);
+ cancel_work_sync(&pas2m110->work_light);
+}
+
+static int lightsensor_get_alsvalue(struct pas2m110_data *pas2m110)
+{
+ int value = 0;
+ u8 als_high, als_low;
+ u8 i, j;
+ int temp;
+ int sum_value2[SUM_BUFFER_SIZE];
+ int total_value = 0;
+
+ /* get ALS */
+ pas2m110_i2c_read(pas2m110, REGS_ALS_LSB, &als_low);
+ pas2m110_i2c_read(pas2m110, REGS_ALS_MSB, &als_high);
+
+ value = ((als_high << 8) | als_low); /* *1 */
+
+ /* filter 1 */
+ sum_value[sum_cnt++] = value;
+ if (sum_cnt == SUM_BUFFER_SIZE)
+ sum_cnt = 0;
+
+ for (i = 0; i < SUM_BUFFER_SIZE; i++)
+ sum_value2[i] = sum_value[i];
+
+ for (i = 0; i < (SUM_BUFFER_SIZE/2+1); i++) {
+ for (j = i; j < SUM_BUFFER_SIZE; j++) {
+ if (sum_value2[i] > sum_value2[j]) {
+ temp = sum_value2[j];
+ sum_value2[j] = sum_value2[i];
+ sum_value2[i] = temp;
+ }
+ }
+ }
+
+ /* filter 2 */
+ sum_buffer[buf_cnt++] = sum_value2[SUM_BUFFER_SIZE/2];
+ if (buf_cnt == TOTAL_BUFFER_SIZE)
+ buf_cnt = 0;
+
+ for (i = 0; i < TOTAL_BUFFER_SIZE; i++)
+ total_value += sum_buffer[i];
+
+ value = total_value/TOTAL_BUFFER_SIZE;
+
+ /* filter 3 */
+ if (0 == value)
+ use_value = 0;
+ else if (0 < value && value <= 8)
+ use_value = 4;
+ else if (8 < value && value <= 16)
+ use_value = 8;
+ else if (16 < value && value <= 200) {
+ if ((use_value - (use_value/10)) > value)
+ use_value = value;
+ else if (value > (use_value + (use_value/10)))
+ use_value = value;
+ } else if (value > 200) {
+ if ((use_value - (use_value*5/100)) > value)
+ use_value = value;
+ else if (value > (use_value + (use_value*5/100)))
+ use_value = value;
+ }
+
+ return use_value;
+}
+
+static void proxsensor_get_avgvalue(struct pas2m110_data *pas2m110)
+{
+ int min = 0, max = 0, avg = 0;
+ int i;
+ u8 proximity_value = 0;
+
+ for (i = 0; i < PROX_READ_NUM; i++) {
+ msleep(40);
+
+ /*Use PS[0] bit Check */
+ pas2m110_i2c_read(pas2m110, REGS_PS_DATA, &proximity_value);
+
+ if (proximity_value == 252)
+ proximity_value = 0;
+ avg += proximity_value;
+
+ if (!i)
+ min = proximity_value;
+ else if (proximity_value < min)
+ min = proximity_value;
+
+ if (proximity_value > max)
+ max = proximity_value;
+ }
+ avg /= PROX_READ_NUM;
+
+ pas2m110->avg[0] = min;
+ pas2m110->avg[1] = avg;
+ pas2m110->avg[2] = max;
+}
+
+static ssize_t pas2m110_proximity_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pas2m110_data *pas2m110 = dev_get_drvdata(dev);
+ u8 proximity_value = 0;
+
+ if (!(pas2m110->power_state & PROXIMITY_ENABLED)) {
+ mutex_lock(&pas2m110->power_lock);
+ pas2m110->pdata->proximity_power(1);
+ pas2m110_i2c_write(pas2m110,
+ REGS_PS1_CMD, reg_defaults[0]);
+ pas2m110_i2c_write(pas2m110,
+ REGS_PS2_CMD, reg_defaults[1]);
+ mutex_unlock(&pas2m110->power_lock);
+ }
+
+ msleep(20);
+
+ /* Use PS[0] bit Check */
+ pas2m110_i2c_read(pas2m110, REGS_PS_DATA, &proximity_value);
+
+ if (proximity_value == 252)
+ proximity_value = 0;
+
+ if (!(pas2m110->power_state & PROXIMITY_ENABLED)) {
+ mutex_lock(&pas2m110->power_lock);
+
+ /* PS sleep */
+ pas2m110_i2c_write(pas2m110,
+ REGS_PS1_CMD, reg_defaults[0]&(0x01^0xFF));
+
+ pas2m110->pdata->proximity_power(0);
+ mutex_unlock(&pas2m110->power_lock);
+ }
+
+ return sprintf(buf, "%d", proximity_value);
+}
+
+static ssize_t pas2m110_lightsensor_file_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pas2m110_data *pas2m110 = dev_get_drvdata(dev);
+ int adc = 0;
+
+ if (!(pas2m110->power_state & LIGHT_ENABLED))
+ pas2m110_light_enable(pas2m110);
+
+ adc = lightsensor_get_alsvalue(pas2m110);
+
+ if (!(pas2m110->power_state & LIGHT_ENABLED))
+ pas2m110_light_disable(pas2m110);
+
+ return sprintf(buf, "%d\n", adc);
+}
+
+static ssize_t poll_delay_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pas2m110_data *pas2m110 = dev_get_drvdata(dev);
+ return sprintf(buf, "%lld\n", ktime_to_ns(pas2m110->light_poll_delay));
+}
+
+static ssize_t poll_delay_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct pas2m110_data *pas2m110 = dev_get_drvdata(dev);
+ int64_t new_delay;
+ int err;
+
+ err = strict_strtoll(buf, 10, &new_delay);
+ if (err < 0)
+ return err;
+
+ mutex_lock(&pas2m110->power_lock);
+ if (new_delay != ktime_to_ns(pas2m110->light_poll_delay)) {
+ pas2m110->light_poll_delay = ns_to_ktime(new_delay);
+ if (pas2m110->power_state & LIGHT_ENABLED) {
+ pas2m110_light_disable(pas2m110);
+ pas2m110_light_enable(pas2m110);
+ }
+ }
+ mutex_unlock(&pas2m110->power_lock);
+
+ return size;
+}
+
+static ssize_t pas2m110_light_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pas2m110_data *pas2m110 = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n",
+ (pas2m110->power_state & LIGHT_ENABLED) ? 1 : 0);
+}
+
+static ssize_t pas2m110_proximity_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pas2m110_data *pas2m110 = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n",
+ (pas2m110->power_state & PROXIMITY_ENABLED) ? 1 : 0);
+}
+
+static ssize_t pas2m110_light_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct pas2m110_data *pas2m110 = 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(&pas2m110->power_lock);
+ if (new_value && !(pas2m110->power_state & LIGHT_ENABLED)) {
+ pas2m110->power_state |= LIGHT_ENABLED;
+ pas2m110_light_enable(pas2m110);
+ } else if (!new_value && (pas2m110->power_state & LIGHT_ENABLED)) {
+ pas2m110_light_disable(pas2m110);
+ pas2m110->power_state &= ~LIGHT_ENABLED;
+ }
+ mutex_unlock(&pas2m110->power_lock);
+ return size;
+}
+
+static ssize_t pas2m110_proximity_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct pas2m110_data *pas2m110 = 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(&pas2m110->power_lock);
+ if (new_value && !(pas2m110->power_state & PROXIMITY_ENABLED)) {
+ pas2m110->power_state |= PROXIMITY_ENABLED;
+ pas2m110->pdata->proximity_power(1);
+ pas2m110_i2c_write(pas2m110,
+ REGS_PS1_CMD, reg_defaults[0]);
+ msleep(90);
+ pas2m110_i2c_write(pas2m110,
+ REGS_PS2_CMD, reg_defaults[1]);
+ enable_irq(pas2m110->irq);
+ enable_irq_wake(pas2m110->irq);
+ } else if (!new_value && (pas2m110->power_state & PROXIMITY_ENABLED)) {
+ pas2m110->power_state &= ~PROXIMITY_ENABLED;
+ disable_irq_wake(pas2m110->irq);
+ disable_irq(pas2m110->irq);
+ pas2m110_i2c_write(pas2m110,
+ REGS_PS2_CMD, reg_defaults[1]|0x04);
+ /* PS sleep */
+ pas2m110_i2c_write(pas2m110,
+ REGS_PS1_CMD, reg_defaults[0]&(0x01^0xFF));
+ pas2m110->pdata->proximity_power(0);
+ }
+ mutex_unlock(&pas2m110->power_lock);
+ return size;
+}
+
+static ssize_t proximity_avg_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pas2m110_data *pas2m110 = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d,%d,%d\n",
+ pas2m110->avg[0], pas2m110->avg[1], pas2m110->avg[2]);
+}
+
+static ssize_t pas2m110_proximity_avg_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct pas2m110_data *pas2m110 = 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(&pas2m110->power_lock);
+ if (new_value) {
+ if (!(pas2m110->power_state & PROXIMITY_ENABLED)) {
+ pas2m110->pdata->proximity_power(1);
+ pas2m110_i2c_write(pas2m110,
+ REGS_PS1_CMD, reg_defaults[0]);
+ pas2m110_i2c_write(pas2m110,
+ REGS_PS2_CMD, reg_defaults[1]);
+ }
+ hrtimer_start(&pas2m110->prox_timer, pas2m110->prox_poll_delay,
+ HRTIMER_MODE_REL);
+ } else if (!new_value) {
+ hrtimer_cancel(&pas2m110->prox_timer);
+ cancel_work_sync(&pas2m110->work_prox);
+ if (!(pas2m110->power_state & PROXIMITY_ENABLED)) {
+ /* ALS interrupt disable */
+ pas2m110_i2c_write(pas2m110,
+ REGS_PS1_CMD, reg_defaults[0]&(0x01^0xFF));
+ pas2m110->pdata->proximity_power(0);
+ }
+ }
+ mutex_unlock(&pas2m110->power_lock);
+
+ return size;
+}
+
+static DEVICE_ATTR(proximity_avg, 0644,
+ proximity_avg_show, pas2m110_proximity_avg_store);
+
+static DEVICE_ATTR(proximity_state, 0644,
+ pas2m110_proximity_state_show, NULL);
+
+static DEVICE_ATTR(lightsensor_file_state, 0644,
+ pas2m110_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,
+ pas2m110_light_enable_show, pas2m110_light_enable_store);
+
+static struct device_attribute dev_attr_proximity_enable =
+ __ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
+ pas2m110_proximity_enable_show,
+ pas2m110_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 void pas2m110_work_func_light(struct work_struct *work)
+{
+ int i;
+ int als;
+ struct pas2m110_data *pas2m110 = container_of(work,
+ struct pas2m110_data, work_light);
+
+ als = lightsensor_get_alsvalue(pas2m110);
+
+ for (i = 0; ARRAY_SIZE(adc_table); i++)
+ if (als <= adc_table[i])
+ break;
+
+ if (pas2m110->light_buffer == i) {
+ if (pas2m110->light_count++ == LIGHT_BUFFER_NUM) {
+ input_report_abs(pas2m110->light_input_dev,
+ ABS_MISC, als + 1);
+ input_sync(pas2m110->light_input_dev);
+ pas2m110->light_count = 0;
+ }
+ } else {
+ pas2m110->light_buffer = i;
+ pas2m110->light_count = 0;
+ }
+}
+
+static void pas2m110_work_func_prox(struct work_struct *work)
+{
+ struct pas2m110_data *pas2m110 = container_of(work,
+ struct pas2m110_data, work_prox);
+ proxsensor_get_avgvalue(pas2m110);
+}
+
+/* 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 pas2m110_light_timer_func(struct hrtimer *timer)
+{
+ struct pas2m110_data *pas2m110 = container_of(timer,
+ struct pas2m110_data, light_timer);
+ queue_work(pas2m110->light_wq, &pas2m110->work_light);
+ hrtimer_forward_now(&pas2m110->light_timer, pas2m110->light_poll_delay);
+ return HRTIMER_RESTART;
+}
+
+static enum hrtimer_restart pas2m110_prox_timer_func(struct hrtimer *timer)
+{
+ struct pas2m110_data *pas2m110
+ = container_of(timer, struct pas2m110_data, prox_timer);
+ queue_work(pas2m110->prox_wq, &pas2m110->work_prox);
+ hrtimer_forward_now(&pas2m110->prox_timer, pas2m110->prox_poll_delay);
+ return HRTIMER_RESTART;
+}
+
+/* interrupt happened due to transition/change of near/far proximity state */
+irqreturn_t pas2m110_irq_thread_fn(int irq, void *data)
+{
+ struct pas2m110_data *ip = data;
+ u8 val = 1;
+ u8 tmp;
+
+ val = gpio_get_value(ip->i2c_client->irq);
+ if (val < 0) {
+ pr_err("%s: gpio_get_value error %d\n", __func__, val);
+ return IRQ_HANDLED;
+ }
+
+ /* for debugging : going to be removed */
+ pas2m110_i2c_read(ip, REGS_PS_DATA, &tmp);
+ if (tmp == 252)
+ tmp = 0;
+ pr_err("%s: proximity value = %d, val = %d\n", __func__, tmp, 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);
+
+ return IRQ_HANDLED;
+}
+
+static int pas2m110_setup_irq(struct pas2m110_data *pas2m110)
+{
+ int rc = -EIO;
+ int irq;
+
+ irq = gpio_to_irq(pas2m110->i2c_client->irq);
+ rc = request_threaded_irq(irq, NULL, pas2m110_irq_thread_fn,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "proximity_int", pas2m110);
+ if (rc < 0) {
+ pr_err("%s: request_irq(%d) failed for gpio %d (%d)\n",
+ __func__, irq, irq, rc);
+ return rc;
+ }
+
+ /* start with interrupts disabled */
+ disable_irq(irq);
+ pas2m110->irq = irq;
+
+ return rc;
+}
+
+static int pas2m110_setup_reg(struct pas2m110_data *pas2m110)
+{
+ int err = 0;
+ u8 tmp;
+ /* initializing the proximity and light sensor registers */
+ mutex_lock(&pas2m110->power_lock);
+ pas2m110->pdata->proximity_power(1);
+ pas2m110_i2c_write(pas2m110, REGS_PS2_CMD, reg_defaults[1]|0x04);
+ pas2m110_i2c_write(pas2m110, REGS_PS1_CMD, reg_defaults[0]);
+/* pas2m110_i2c_write(pas2m110, REGS_PS2_CMD, reg_defaults[1]); */
+ mutex_unlock(&pas2m110->power_lock);
+
+ /* printing the inital proximity value with no contact */
+ msleep(50);
+ err = pas2m110_i2c_read(pas2m110, REGS_PS_DATA, &tmp);
+ if (tmp == 252)
+ tmp = 0;
+
+ /* while(1) msleep(100); */
+
+ if (err < 0) {
+ pr_err("%s: read ps_data failed\n", __func__);
+ err = -EIO;
+ }
+ pr_err("%s: initial proximity value = %d\n", __func__, tmp);
+ mutex_lock(&pas2m110->power_lock);
+ pas2m110_i2c_write(pas2m110,
+ REGS_PS2_CMD, reg_defaults[1]|0x04);
+ /* PS sleep */
+ pas2m110_i2c_write(pas2m110,
+ REGS_PS1_CMD, reg_defaults[0]&(0x01^0xFF));
+ pas2m110->pdata->proximity_power(0);
+ mutex_unlock(&pas2m110->power_lock);
+
+ return err;
+}
+
+static int pas2m110_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret = -ENODEV;
+ struct input_dev *input_dev;
+ struct pas2m110_data *pas2m110;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ pr_err("%s: i2c functionality check failed!\n", __func__);
+ return ret;
+ }
+
+ pas2m110 = kzalloc(sizeof(struct pas2m110_data), GFP_KERNEL);
+ if (!pas2m110) {
+ pr_err("%s: failed to alloc memory for module data\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ pas2m110->pdata = client->dev.platform_data;
+ pas2m110->i2c_client = client;
+ i2c_set_clientdata(client, pas2m110);
+
+ /* wake lock init */
+ wake_lock_init(&pas2m110->prx_wake_lock, WAKE_LOCK_SUSPEND,
+ "prx_wake_lock");
+ mutex_init(&pas2m110->power_lock);
+
+ /* setup initial registers */
+ ret = pas2m110_setup_reg(pas2m110);
+ if (ret < 0) {
+ pr_err("%s: could not setup regs\n", __func__);
+ goto err_setup_reg;
+ }
+
+ ret = pas2m110_setup_irq(pas2m110);
+ 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;
+ }
+ pas2m110->proximity_input_dev = input_dev;
+ input_set_drvdata(input_dev, pas2m110);
+ 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);
+
+ 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;
+ }
+
+ /* light_timer settings. we poll for light values using a timer. */
+ hrtimer_init(&pas2m110->light_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ pas2m110->light_poll_delay = ns_to_ktime(200 * NSEC_PER_MSEC);
+ pas2m110->light_timer.function = pas2m110_light_timer_func;
+
+ /* prox_timer settings. we poll for light values using a timer. */
+ hrtimer_init(&pas2m110->prox_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ pas2m110->prox_poll_delay = ns_to_ktime(2000 * NSEC_PER_MSEC);
+ pas2m110->prox_timer.function = pas2m110_prox_timer_func;
+
+ /* the timer just fires off a work queue request. we need a thread
+ to read the i2c (can be slow and blocking). */
+ pas2m110->light_wq = create_singlethread_workqueue("pas2m110_light_wq");
+ if (!pas2m110->light_wq) {
+ ret = -ENOMEM;
+ pr_err("%s: could not create light workqueue\n", __func__);
+ goto err_create_light_workqueue;
+ }
+ pas2m110->prox_wq = create_singlethread_workqueue("pas2m110_prox_wq");
+ if (!pas2m110->prox_wq) {
+ ret = -ENOMEM;
+ pr_err("%s: could not create prox workqueue\n", __func__);
+ goto err_create_prox_workqueue;
+ }
+
+ /* this is the thread function we run on the work queue */
+ INIT_WORK(&pas2m110->work_light, pas2m110_work_func_light);
+ INIT_WORK(&pas2m110->work_prox, pas2m110_work_func_prox);
+
+ /* 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, pas2m110);
+ 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);
+
+ 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;
+ }
+ pas2m110->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;
+ }
+
+ /* set sysfs for proximity sensor */
+ pas2m110->proximity_class = class_create(THIS_MODULE, "proximity");
+ if (IS_ERR(pas2m110->proximity_class)) {
+ pr_err("%s: could not create proximity_class\n", __func__);
+ goto err_proximity_class_create;
+ }
+
+ pas2m110->proximity_dev = device_create(pas2m110->proximity_class,
+ NULL, 0, NULL, "proximity");
+ if (IS_ERR(pas2m110->proximity_dev)) {
+ pr_err("%s: could not create proximity_dev\n", __func__);
+ goto err_proximity_device_create;
+ }
+
+ if (device_create_file(pas2m110->proximity_dev,
+ &dev_attr_proximity_state) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_proximity_state.attr.name);
+ goto err_proximity_device_create_file1;
+ }
+
+ if (device_create_file(pas2m110->proximity_dev,
+ &dev_attr_proximity_avg) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_proximity_avg.attr.name);
+ goto err_proximity_device_create_file2;
+ }
+ dev_set_drvdata(pas2m110->proximity_dev, pas2m110);
+
+ /* set sysfs for light sensor */
+ pas2m110->lightsensor_class = class_create(THIS_MODULE, "lightsensor");
+ if (IS_ERR(pas2m110->lightsensor_class)) {
+ pr_err("%s: could not create lightsensor_class\n", __func__);
+ goto err_light_class_create;
+ }
+
+ pas2m110->switch_cmd_dev = device_create(pas2m110->lightsensor_class,
+ NULL, 0, NULL, "switch_cmd");
+ if (IS_ERR(pas2m110->switch_cmd_dev)) {
+ pr_err("%s: could not create switch_cmd_dev\n", __func__);
+ goto err_light_device_create;
+ }
+
+ if (device_create_file(pas2m110->switch_cmd_dev,
+ &dev_attr_lightsensor_file_state) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_lightsensor_file_state.attr.name);
+ goto err_light_device_create_file;
+ }
+ dev_set_drvdata(pas2m110->switch_cmd_dev, pas2m110);
+
+ goto done;
+
+/* error, unwind it all */
+err_light_device_create_file:
+ device_destroy(pas2m110->lightsensor_class, 0);
+err_light_device_create:
+ class_destroy(pas2m110->lightsensor_class);
+err_light_class_create:
+ device_remove_file(pas2m110->proximity_dev, &dev_attr_proximity_avg);
+err_proximity_device_create_file2:
+ device_remove_file(pas2m110->proximity_dev, &dev_attr_proximity_state);
+err_proximity_device_create_file1:
+ device_destroy(pas2m110->proximity_class, 0);
+err_proximity_device_create:
+ class_destroy(pas2m110->proximity_class);
+err_proximity_class_create:
+ sysfs_remove_group(&input_dev->dev.kobj,
+ &light_attribute_group);
+err_sysfs_create_group_light:
+ input_unregister_device(pas2m110->light_input_dev);
+err_input_register_device_light:
+err_input_allocate_device_light:
+ destroy_workqueue(pas2m110->prox_wq);
+err_create_prox_workqueue:
+ destroy_workqueue(pas2m110->light_wq);
+err_create_light_workqueue:
+ sysfs_remove_group(&pas2m110->proximity_input_dev->dev.kobj,
+ &proximity_attribute_group);
+err_sysfs_create_group_proximity:
+ input_unregister_device(pas2m110->proximity_input_dev);
+err_input_register_device_proximity:
+err_input_allocate_device_proximity:
+ free_irq(pas2m110->irq, 0);
+err_setup_irq:
+err_setup_reg:
+ mutex_destroy(&pas2m110->power_lock);
+ wake_lock_destroy(&pas2m110->prx_wake_lock);
+ kfree(pas2m110);
+done:
+ return ret;
+}
+
+static int pas2m110_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
+ pas2m110->power_state because we use that state in resume.
+ */
+ struct i2c_client *client = to_i2c_client(dev);
+ struct pas2m110_data *pas2m110 = i2c_get_clientdata(client);
+ if (pas2m110->power_state & LIGHT_ENABLED)
+ pas2m110_light_disable(pas2m110);
+ return 0;
+}
+
+static int pas2m110_resume(struct device *dev)
+{
+ /* Turn power back on if we were before suspend. */
+ struct i2c_client *client = to_i2c_client(dev);
+ struct pas2m110_data *pas2m110 = i2c_get_clientdata(client);
+ if (pas2m110->power_state & LIGHT_ENABLED)
+ pas2m110_light_enable(pas2m110);
+ return 0;
+}
+
+static int pas2m110_i2c_remove(struct i2c_client *client)
+{
+ struct pas2m110_data *pas2m110 = i2c_get_clientdata(client);
+
+ device_remove_file(pas2m110->proximity_dev,
+ &dev_attr_proximity_avg);
+ device_remove_file(pas2m110->switch_cmd_dev,
+ &dev_attr_lightsensor_file_state);
+ device_destroy(pas2m110->lightsensor_class, 0);
+ class_destroy(pas2m110->lightsensor_class);
+ device_remove_file(pas2m110->proximity_dev, &dev_attr_proximity_avg);
+ device_remove_file(pas2m110->proximity_dev, &dev_attr_proximity_state);
+ device_destroy(pas2m110->proximity_class, 0);
+ class_destroy(pas2m110->proximity_class);
+ sysfs_remove_group(&pas2m110->light_input_dev->dev.kobj,
+ &light_attribute_group);
+ input_unregister_device(pas2m110->light_input_dev);
+ sysfs_remove_group(&pas2m110->proximity_input_dev->dev.kobj,
+ &proximity_attribute_group);
+ input_unregister_device(pas2m110->proximity_input_dev);
+ free_irq(pas2m110->irq, NULL);
+ if (pas2m110->power_state) {
+ if (pas2m110->power_state & LIGHT_ENABLED)
+ pas2m110_light_disable(pas2m110);
+ if (pas2m110->power_state & PROXIMITY_ENABLED) {
+ /* cm3663_i2c_write(cm3663, REGS_PS_CMD, 0x01); */
+ pas2m110_i2c_write(pas2m110,
+ REGS_PS2_CMD, reg_defaults[1]|0x04);
+ /* PS sleep */
+ pas2m110_i2c_write(pas2m110,
+ REGS_PS1_CMD, reg_defaults[0]&(0x01^0xFF));
+ pas2m110->pdata->proximity_power(0);
+ }
+ }
+ destroy_workqueue(pas2m110->light_wq);
+ destroy_workqueue(pas2m110->prox_wq);
+ mutex_destroy(&pas2m110->power_lock);
+ wake_lock_destroy(&pas2m110->prx_wake_lock);
+ kfree(pas2m110);
+ return 0;
+}
+
+static const struct i2c_device_id pas2m110_device_id[] = {
+ {"pas2m110", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, pas2m110_device_id);
+
+static const struct dev_pm_ops pas2m110_pm_ops = {
+ .suspend = pas2m110_suspend,
+ .resume = pas2m110_resume
+};
+
+static struct i2c_driver pas2m110_i2c_driver = {
+ .driver = {
+ .name = "pas2m110",
+ .owner = THIS_MODULE,
+ .pm = &pas2m110_pm_ops
+ },
+ .probe = pas2m110_i2c_probe,
+ .remove = pas2m110_i2c_remove,
+ .id_table = pas2m110_device_id,
+};
+
+
+static int __init pas2m110_init(void)
+{
+ return i2c_add_driver(&pas2m110_i2c_driver);
+}
+
+static void __exit pas2m110_exit(void)
+{
+ i2c_del_driver(&pas2m110_i2c_driver);
+}
+
+module_init(pas2m110_init);
+module_exit(pas2m110_exit);
+
+MODULE_AUTHOR("tim.sk.lee@samsung.com");
+MODULE_DESCRIPTION("Optical Sensor driver for pas2m110");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sensor/sensors_core.c b/drivers/sensor/sensors_core.c
new file mode 100644
index 0000000..a414492
--- /dev/null
+++ b/drivers/sensor/sensors_core.c
@@ -0,0 +1,75 @@
+/*
+ * /driver/sensors/sensors_core.c
+ *
+ * Copyright (C) 2011 Samsung Electronics Co.Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/err.h>
+#include <linux/sensor/sensors_core.h>
+
+struct class *sensors_class;
+
+/**
+* sensors_classdev_register - create new sensor device in sensors_class.
+* @dev: The device to register.
+*/
+struct device *sensors_classdev_register(char *sensors_name)
+{
+ struct device *dev;
+ int retval = -ENODEV;
+
+ dev = device_create(sensors_class, NULL, 0,
+ NULL, "%s", sensors_name);
+ if (IS_ERR(dev))
+ return ERR_PTR(retval);
+
+ printk(KERN_INFO "Registered sensors device: %s\n", sensors_name);
+ return dev;
+}
+EXPORT_SYMBOL_GPL(sensors_classdev_register);
+
+/**
+* sensors_classdev_unregister - unregisters a object of sensor device.
+*
+*/
+void sensors_classdev_unregister(struct device *dev)
+{
+ device_unregister(dev);
+}
+EXPORT_SYMBOL_GPL(sensors_classdev_unregister);
+
+static int __init sensors_class_init(void)
+{
+ sensors_class = class_create(THIS_MODULE, "sensors");
+ if (IS_ERR(sensors_class)) {
+ pr_err("Failed to create class(sensors)!\n");
+ return PTR_ERR(sensors_class);
+ }
+
+ return 0;
+}
+
+static void __exit sensors_class_exit(void)
+{
+ class_destroy(sensors_class);
+}
+
+subsys_initcall(sensors_class_init);
+module_exit(sensors_class_exit);
+
+MODULE_DESCRIPTION("Universal sensors core class");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sensor/taos.c b/drivers/sensor/taos.c
new file mode 100644
index 0000000..d7515b0
--- /dev/null
+++ b/drivers/sensor/taos.c
@@ -0,0 +1,1372 @@
+/*
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ */
+
+/* file taos.c
+ brief Optical Sensor(proximity sensor) driver for TAOSP002S00F
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+#include <linux/uaccess.h>
+#include <linux/unistd.h>
+#include <linux/delay.h>
+#include <linux/miscdevice.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/workqueue.h>
+#include <linux/irq.h>
+#include <mach/gpio.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/wakelock.h>
+#include <linux/errno.h>
+#include <linux/mutex.h>
+#include <linux/sensor/taos.h>
+
+#define TAOS_DEBUG 1
+#define IRQ_WAKE 1
+
+/* Triton register offsets */
+#define CNTRL 0x00
+#define ALS_TIME 0X01
+#define PRX_TIME 0x02
+#define WAIT_TIME 0x03
+#define ALS_MINTHRESHLO 0X04
+#define ALS_MINTHRESHHI 0X05
+#define ALS_MAXTHRESHLO 0X06
+#define ALS_MAXTHRESHHI 0X07
+#define PRX_MINTHRESHLO 0X08
+#define PRX_MINTHRESHHI 0X09
+#define PRX_MAXTHRESHLO 0X0A
+#define PRX_MAXTHRESHHI 0X0B
+#define INTERRUPT 0x0C
+#define PRX_CFG 0x0D
+#define PRX_COUNT 0x0E
+#define GAIN 0x0F
+#define REVID 0x11
+#define CHIPID 0x12
+#define STATUS 0x13
+#define ALS_CHAN0LO 0x14
+#define ALS_CHAN0HI 0x15
+#define ALS_CHAN1LO 0x16
+#define ALS_CHAN1HI 0x17
+#define PRX_LO 0x18
+#define PRX_HI 0x19
+#define TEST_STATUS 0x1F
+
+/* Triton cmd reg masks */
+#define CMD_REG 0X80
+#define CMD_BYTE_RW 0x00
+#define CMD_WORD_BLK_RW 0x20
+#define CMD_SPL_FN 0x60
+#define CMD_PROX_INTCLR 0X05
+#define CMD_ALS_INTCLR 0X06
+#define CMD_PROXALS_INTCLR 0X07
+#define CMD_TST_REG 0X08
+#define CMD_USER_REG 0X09
+
+/* Triton cntrl reg masks */
+#define CNTL_REG_CLEAR 0x00
+#define CNTL_PROX_INT_ENBL 0X20
+#define CNTL_ALS_INT_ENBL 0X10
+#define CNTL_WAIT_TMR_ENBL 0X08
+#define CNTL_PROX_DET_ENBL 0X04
+#define CNTL_ADC_ENBL 0x02
+#define CNTL_PWRON 0x01
+#define CNTL_ALSPON_ENBL 0x03
+#define CNTL_INTALSPON_ENBL 0x13
+#define CNTL_PROXPON_ENBL 0x0F
+#define CNTL_INTPROXPON_ENBL 0x2F
+
+/* Triton status reg masks */
+#define STA_ADCVALID 0x01
+#define STA_PRXVALID 0x02
+#define STA_ADC_PRX_VALID 0x03
+#define STA_ADCINTR 0x10
+#define STA_PRXINTR 0x20
+
+/* Lux constants */
+#define SCALE_MILLILUX 3
+#define FILTER_DEPTH 3
+#define GAINFACTOR 9
+
+/* Thresholds */
+#define ALS_THRESHOLD_LO_LIMIT 0x0000
+#define ALS_THRESHOLD_HI_LIMIT 0xFFFF
+#define PROX_THRESHOLD_LO_LIMIT 0x0000
+#define PROX_THRESHOLD_HI_LIMIT 0xFFFF
+
+/* Device default configuration */
+#define CALIB_TGT_PARAM 300000
+#define SCALE_FACTOR_PARAM 1
+#define GAIN_TRIM_PARAM 512
+#define GAIN_PARAM 1
+#define ALS_THRSH_HI_PARAM 0xFFFF
+#define ALS_THRSH_LO_PARAM 0
+#define PRX_THRSH_HI_PARAM 0x28A /* 700 -> 650 */
+#define PRX_THRSH_LO_PARAM 0x208 /* 550 -> 520 */
+#define ALS_TIME_PARAM 0xF6 /* 50ms -> 27ms */
+#define PRX_ADC_TIME_PARAM 0xFF /* [HSS] Original value : 0XEE */
+#define PRX_WAIT_TIME_PARAM 0xFF /* 2.73ms */
+#define INTR_FILTER_PARAM 0x63 /* 33 -> 63 */
+#define PRX_CONFIG_PARAM 0x00
+#define PRX_PULSE_CNT_PARAM 0x0A
+#define PRX_GAIN_PARAM 0x28 /* 21 */
+#define LIGHT_BUFFER_NUM 5
+
+#define SENSOR_DEFAULT_DELAY (200)
+#define SENSOR_MAX_DELAY (2000)
+#define ABS_STATUS (ABS_BRAKE)
+#define ABS_WAKE (ABS_MISC)
+#define ABS_CONTROL_REPORT (ABS_THROTTLE)
+
+static enum taos_als_fops_status taos_als_status = TAOS_ALS_CLOSED;
+static enum taos_prx_fops_status taos_prx_status = TAOS_PRX_CLOSED;
+static enum taos_chip_working_status taos_chip_status = TAOS_CHIP_UNKNOWN;
+
+static const int adc_table[4] = {
+ 15,
+ 150,
+ 1450,
+ 13000,
+};
+
+/* i2c write routine for taos */
+static int opt_i2c_write(struct i2c_client *client, u8 reg, u8 *val)
+{
+ int err;
+ struct i2c_msg msg[1];
+ unsigned char data[2];
+
+ if (client == NULL)
+ return -ENODEV;
+
+ data[0] = reg;
+ data[1] = *val;
+
+ msg->addr = client->addr;
+ msg->flags = I2C_M_WR;
+ msg->len = 2;
+ msg->buf = data;
+
+ err = i2c_transfer(client->adapter, msg, 1);
+
+ if (err >= 0)
+ return 0;
+
+ pr_err("[TAOS] %s %d i2c_transfer error : reg[%X], err[%d]\n",\
+ __func__, __LINE__, reg, err);
+ return err;
+}
+
+static int taos_get_lux(struct taos_data *taos)
+{
+ int i = 0;
+ int integration_time = 27; /* 49->27 */
+ int als_gain = 1;
+ int irfactor = 0, irfactor2 = 0;
+ int calculated_lux = 0;
+ u8 prox_int_thresh[4];
+
+ if (taos_als_status == TAOS_ALS_CLOSED) {
+ pr_info("!!! Light sensor had been off return 0\n");
+ return 0;
+ }
+
+ taos->cleardata = i2c_smbus_read_word_data(taos->client,
+ CMD_REG | ALS_CHAN0LO);
+ taos->irdata = i2c_smbus_read_word_data(taos->client,
+ CMD_REG | ALS_CHAN1LO);
+
+ irfactor = 100 * taos->cleardata - 175 * taos->irdata;
+ irfactor2 = 63 * taos->cleardata - 100 * taos->irdata;
+
+ if (irfactor2 > irfactor)
+ irfactor = irfactor2;
+
+ if (0 > irfactor)
+ irfactor = 0;
+
+ calculated_lux = 20*irfactor / (integration_time * als_gain);
+
+ if (calculated_lux > MAX_LUX)
+ calculated_lux = MAX_LUX;
+
+ /* QUICK FIX : Code for saturation case */
+ /* Saturation value seems to be affected by setting ALS_TIME,
+ xEE (=238) which sets max count to 20480. */
+ /* change ATIME value from 50ms to 27ms, so condition 18000 -> 9000 */
+ if (taos->cleardata >= 9000 || taos->irdata >= 9000) {
+ if (taos->proximity_value == 1) {
+ taos->proximity_value = 0;
+ prox_int_thresh[0] = (0x0000) & 0xFF;
+ prox_int_thresh[1] = (0x0000 >> 8) & 0xFF;
+ prox_int_thresh[2] = (PRX_THRSH_HI_PARAM) & 0xFF;
+ prox_int_thresh[3] = (PRX_THRSH_HI_PARAM >> 8) & 0xFF;
+ for (i = 0; i < 4; i++)
+ opt_i2c_write(taos->client,
+ (CMD_REG|(PRX_MINTHRESHLO + i)),
+ &prox_int_thresh[i]);
+
+ if (USE_INPUT_DEVICE) {
+ input_report_abs(taos->proximity_input_dev,
+ ABS_DISTANCE, !taos->proximity_value);
+ pr_info("[PROXIMITY] [%s] saturation. proximity_value is %d (1: close, 0: far)\n",
+ __func__, taos->proximity_value);
+ input_sync(taos->proximity_input_dev);
+ usleep_range(1000, 2000);
+ }
+ }
+ }
+
+ return calculated_lux;
+}
+
+static int taos_chip_on(struct taos_data *taos)
+{
+ u8 value;
+ u8 prox_int_thresh[4];
+ int err = 0;
+ int i;
+ int fail_num = 0;
+
+ pr_info("[TAOS] %s\n", __func__);
+ value = CNTL_REG_CLEAR;
+ err = opt_i2c_write(taos->client, (CMD_REG|CNTRL), &value);
+ if (err < 0) {
+ pr_info("[diony] i2c_smbus_write_byte_data to clr ctrl reg failed in ioctl prox_on\n");
+ fail_num++;
+ }
+ value = ALS_TIME_PARAM;
+ err = opt_i2c_write(taos->client, (CMD_REG|ALS_TIME), &value);
+ if (err < 0) {
+ pr_info("[diony] i2c_smbus_write_byte_data to als time reg failed in ioctl prox_on\n");
+ fail_num++;
+ }
+ value = PRX_ADC_TIME_PARAM;
+ err = opt_i2c_write(taos->client, (CMD_REG|PRX_TIME), &value);
+ if (err < 0) {
+ pr_info("[diony] i2c_smbus_write_byte_data to prox time reg failed in ioctl prox_on\n");
+ fail_num++;
+ }
+ value = PRX_WAIT_TIME_PARAM;
+ err = opt_i2c_write(taos->client, (CMD_REG|WAIT_TIME), &value);
+ if (err < 0) {
+ pr_info("[diony] i2c_smbus_write_byte_data to wait time reg failed in ioctl prox_on\n");
+ fail_num++;
+ }
+ value = INTR_FILTER_PARAM;
+ err = opt_i2c_write(taos->client, (CMD_REG|INTERRUPT), &value);
+ if (err < 0) {
+ pr_info("[diony] i2c_smbus_write_byte_data to interrupt reg failed in ioctl prox_on\n");
+ fail_num++;
+ }
+ value = PRX_CONFIG_PARAM;
+ err = opt_i2c_write(taos->client, (CMD_REG|PRX_CFG), &value);
+ if (err < 0) {
+ pr_info("[diony] i2c_smbus_write_byte_data to prox cfg reg failed in ioctl prox_on\n");
+ fail_num++;
+ }
+
+ if (taos->chipID == 0x39)
+ value = 0x08;
+ else if (taos->chipID == 0x29)
+ value = 0x0A;
+ else
+ value = 0x08;
+
+ err = opt_i2c_write(taos->client, (CMD_REG|PRX_COUNT), &value);
+ if (err < 0) {
+ pr_info("[diony] i2c_smbus_write_byte_data to prox cnt reg failed in ioctl prox_on\n");
+ fail_num++;
+ }
+ if (taos->chipID == 0x39)
+ value = PRX_GAIN_PARAM; /* 100mA, ch1, pgain 4x, again 1x */
+ else if (taos->chipID == 0x29)
+ value = 0x20; /* 100mA, ch1, pgain 1x, again 1x */
+ else
+ value = PRX_GAIN_PARAM;
+
+ err = opt_i2c_write(taos->client, (CMD_REG|GAIN), &value);
+ if (err < 0) {
+ pr_info("[diony] i2c_smbus_write_byte_data to prox gain reg failed in ioctl prox_on\n");
+ fail_num++;
+ }
+ prox_int_thresh[0] = (0x0000) & 0xFF;
+ prox_int_thresh[1] = (0x0000 >> 8) & 0xFF;
+ prox_int_thresh[2] = (PRX_THRSH_HI_PARAM) & 0xFF;
+ prox_int_thresh[3] = (PRX_THRSH_HI_PARAM >> 8) & 0xFF;
+ for (i = 0; i < 4; i++) {
+ err = opt_i2c_write(taos->client,
+ (CMD_REG|(PRX_MINTHRESHLO+i)), &prox_int_thresh[i]);
+ if (err < 0) {
+ pr_info("[diony]i2c_smbus_write_byte_data to prox int thrsh regs failed in ioctl prox_on\n");
+ fail_num++;
+ }
+ }
+ value = CNTL_INTPROXPON_ENBL;
+ err = opt_i2c_write(taos->client, (CMD_REG|CNTRL), &value);
+ if (err < 0) {
+ pr_info("[diony]i2c_smbus_write_byte_data to ctrl reg "
+ "failed in ioctl prox_on\n");
+ fail_num++;
+ }
+
+ usleep_range(12000, 20000);
+
+ if (fail_num == 0) {
+#if IRQ_WAKE
+ err = irq_set_irq_wake(taos->irq, 1); /* enable:1, disable:0 */
+ pr_info("[TAOS] register wakeup source = %d\n", err);
+ if (err)
+ pr_err("[TAOS] register wakeup source failed\n");
+#endif
+ taos_chip_status = TAOS_CHIP_WORKING;
+ } else
+ pr_err("I2C failed in taos_chip_on, # of fail I2C=[%d]\n",
+ fail_num);
+
+ return err;
+}
+
+static int taos_chip_off(struct taos_data *taos)
+{
+ int ret = 0;
+ u8 reg_cntrl;
+ int err = 0;
+
+ pr_info("[TAOS] %s\n", __func__);
+
+#if IRQ_WAKE
+ err = irq_set_irq_wake(taos->irq, 0); /* enable : 1, disable : 0 */
+ if (err)
+ pr_err("[TAOS] register wakeup source failed\n");
+#endif
+
+ reg_cntrl = CNTL_REG_CLEAR;
+
+ ret = opt_i2c_write(taos->client, (CMD_REG | CNTRL), &reg_cntrl);
+ if (ret < 0) {
+ pr_err("opt_i2c_write failed in taos_chip_off\n");
+ return ret;
+ }
+
+ taos_chip_status = TAOS_CHIP_SLEEP;
+
+ return ret;
+}
+
+/************************************************************
+ *
+ * function : taos_on
+ * description : This function is power-on function for optical sensor.
+ *
+ * int type : Sensor type. Two values is available (PROXIMITY,LIGHT).
+ * it support power-on function separately.
+ *
+ *
+ */
+
+static void taos_on(struct taos_data *taos, int type)
+{
+ int err = 0;
+
+ pr_info("%s : type=%d (0:light, 1:prox, 2:all)\n",
+ __func__, type);
+
+ taos_chip_on(taos);
+
+#if IRQ_WAKE
+ err = irq_set_irq_wake(taos->irq, 1); /* enable : 1, disable : 0 */
+ if (err)
+ pr_err("[TAOS] register wakeup source failed\n");
+#endif
+ if (type == TAOS_PROXIMITY || type == TAOS_ALL) {
+ pr_info("enable irq for proximity\n");
+ enable_irq(taos->irq);
+ taos->proximity_enable = 1;
+ taos_prx_status = TAOS_PRX_OPENED;
+ taos->proximity_value = 0;
+
+ pr_info("[TAOS_PROXIMITY] %s: timer start for prox sensor\n",
+ __func__);
+ hrtimer_start(&taos->ptimer, taos->prox_polling_time,
+ HRTIMER_MODE_REL);
+ }
+ if (type == TAOS_LIGHT || type == TAOS_ALL) {
+ pr_info("[TAOS_LIGHT] %s: timer start for light sensor\n",
+ __func__);
+ taos->light_polling_time = ktime_set(0, 0);
+ taos->light_polling_time =
+ ktime_add_us(taos->light_polling_time, 200000);
+
+ hrtimer_start(&taos->timer, taos->light_polling_time,
+ HRTIMER_MODE_REL);
+ taos->light_enable = 1;
+ msleep(50); /* for sure validation of first polling value */
+ taos_als_status = TAOS_ALS_OPENED;
+ }
+}
+
+/******************************************************************
+ *
+ * function : taos_off
+ * description : This function is power-off function for optical sensor.
+ *
+ * int type : Sensor type. Three values is available (PROXIMITY,LIGHT,ALL).
+ * it support power-on function separately.
+ *
+ *
+ */
+
+static void taos_off(struct taos_data *taos, int type)
+{
+#ifdef TAOS_DEBUG
+ pr_info("%s : type=%d (0:light, 1:prox, 2:all)\n",
+ __func__, type);
+#endif
+
+ if (type == TAOS_PROXIMITY || type == TAOS_ALL) {
+ pr_info("[TAOS] %s: disable irq for proximity\n",
+ __func__);
+ disable_irq(taos->irq);
+ hrtimer_cancel(&taos->ptimer);
+ taos->proximity_enable = 0;
+ taos_prx_status = TAOS_PRX_CLOSED;
+ taos->proximity_value = 0; /* initialize proximity_value */
+ }
+
+ if (type == TAOS_LIGHT || type == TAOS_ALL) {
+ pr_info("[TAOS] %s: timer cancel for light sensor\n",
+ __func__);
+ hrtimer_cancel(&taos->timer);
+ taos->light_enable = 0;
+ taos_als_status = TAOS_ALS_CLOSED;
+ }
+
+ if (taos_prx_status == TAOS_PRX_CLOSED &&
+ taos_als_status == TAOS_ALS_CLOSED &&
+ taos_chip_status == TAOS_CHIP_WORKING)
+ taos_chip_off(taos);
+}
+
+/*************************************************************************/
+/* TAOS sysfs */
+/*************************************************************************/
+
+static void taos_light_enable(struct taos_data *taos)
+{
+ taos->light_enable = 1;
+ taos->light_count = 0;
+ taos->light_buffer = 0;
+ hrtimer_start(&taos->timer, taos->light_polling_time, HRTIMER_MODE_REL);
+}
+
+static void taos_light_disable(struct taos_data *taos)
+{
+ hrtimer_cancel(&taos->timer);
+ cancel_work_sync(&taos->work_light);
+ taos->light_enable = 0;
+}
+static void taos_ptime_disable(struct taos_data *taos)
+{
+ hrtimer_cancel(&taos->ptimer);
+ cancel_work_sync(&taos->work_ptime);
+ taos->proximity_enable = 0;
+}
+static ssize_t poll_delay_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct taos_data *taos = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%lld\n", ktime_to_ns(taos->light_polling_time));
+}
+
+static ssize_t poll_delay_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct taos_data *taos = dev_get_drvdata(dev);
+ int64_t new_delay;
+ int err;
+
+ err = strict_strtoll(buf, 10, &new_delay);
+ if (err < 0)
+ return err;
+#if TAOS_DEBUG
+ pr_info("[TAOS] %s: new delay = %lldns, old delay = %lldns\n",\
+ __func__, new_delay, ktime_to_ns(taos->light_polling_time));
+#endif
+ mutex_lock(&taos->power_lock);
+ if (new_delay != ktime_to_ns(taos->light_polling_time)) {
+ taos->light_polling_time = ns_to_ktime(new_delay);
+ if (taos->light_enable) {
+ taos_light_disable(taos);
+ taos_light_enable(taos);
+ }
+ }
+ mutex_unlock(&taos->power_lock);
+
+ return size;
+}
+
+static ssize_t light_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct taos_data *taos = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", (taos->light_enable) ? 1 : 0);
+}
+
+static ssize_t proximity_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct taos_data *taos = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", (taos->proximity_enable) ? 1 : 0);
+}
+
+static ssize_t light_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct taos_data *taos = dev_get_drvdata(dev);
+ int value;
+
+ sscanf(buf, "%d", &value);
+#if TAOS_DEBUG
+ pr_info("[TAOS] %s: input value = %d\n", __func__, value);
+#endif
+
+ if (value == 1 && taos->light_enable == OFF) {
+ taos_on(taos, TAOS_LIGHT);
+ value = ON;
+ pr_info("[TAOS] *#0589# test start : [%d]\n", value);
+ } else if (value == 0 && taos->light_enable == ON) {
+ taos_off(taos, TAOS_LIGHT);
+
+ value = OFF;
+ pr_info("[TAOS] *#0589# test end : [%d]\n", value);
+ }
+
+ return size;
+}
+
+static ssize_t proximity_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct taos_data *taos = dev_get_drvdata(dev);
+ int value;
+
+ sscanf(buf, "%d", &value);
+#if TAOS_DEBUG
+ pr_info("[TAOS] %s: input value = %d\n", __func__, value);
+#endif
+ if (value == 1 && taos->proximity_enable == OFF) {
+ /* reset Interrupt pin */
+ /* to active Interrupt, TMD2771x IR. pin shoud be reset. */
+ i2c_smbus_write_byte(taos->client,
+ (CMD_REG|CMD_SPL_FN|CMD_PROXALS_INTCLR));
+
+ taos_on(taos, TAOS_PROXIMITY);
+ input_report_abs(taos->proximity_input_dev,
+ ABS_DISTANCE, !taos->proximity_value);
+ input_sync(taos->proximity_input_dev);
+ pr_info("[TAOS] Temp : Power ON, chipID=%X\n", taos->chipID);
+ } else if (value == 0 && taos->proximity_enable == ON) {
+ taos_off(taos, TAOS_PROXIMITY);
+
+ pr_info("[TAOS_PROXIMITY] Temporary : Power OFF\n");
+ }
+
+ return size;
+}
+
+static ssize_t lightsensor_file_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct taos_data *taos = dev_get_drvdata(dev);
+ int adc = 0;
+
+ if (taos->light_enable) {
+ adc = taos_get_lux(taos);
+ return sprintf(buf, "%d\n", adc);
+ } else {
+ return sprintf(buf, "%d\n", adc);
+ }
+}
+
+static ssize_t proximity_delay_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct taos_data *taos = dev_get_drvdata(dev);
+ int delay;
+
+ delay = taos->delay;
+ return sprintf(buf, "%d\n", delay);
+}
+
+static ssize_t proximity_delay_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct input_dev *input_data = to_input_dev(dev);
+ struct taos_data *taos = input_get_drvdata(input_data);
+ int delay = 0; /*simple_strtoul(buf, NULL, 10);*/
+ int err = 0;
+
+ err = kstrtouint(buf, 10, &delay);
+ if (err)
+ pr_err("%s, kstrtouint failed.(err:%d)",
+ __func__, err);
+
+ pr_info("delay = %d\n", delay);
+ if (delay < 0)
+ return size;
+
+ if (SENSOR_MAX_DELAY < delay)
+ delay = SENSOR_MAX_DELAY;
+
+ taos->delay = delay;
+
+ input_report_abs(input_data, ABS_CONTROL_REPORT,
+ taos->proximity_enable<<16 | delay);
+
+ return size;
+}
+
+static ssize_t proximity_wake_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct input_dev *input_data = to_input_dev(dev);
+ static int cnt = 1;
+
+ input_report_abs(input_data, ABS_WAKE, cnt++);
+
+ return count;
+}
+
+static ssize_t proximity_data_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct taos_data *taos = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", taos->proximity_value);
+}
+
+static ssize_t proximity_adc_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct taos_data *taos = dev_get_drvdata(dev);
+ u16 proximity_value = 0;
+
+ proximity_value = i2c_smbus_read_word_data(taos->client,
+ CMD_REG | PRX_LO);
+ if (proximity_value > TAOS_PROX_MAX)
+ proximity_value = TAOS_PROX_MAX;
+ return sprintf(buf, "%d\n", proximity_value);
+}
+
+static ssize_t proximity_avg_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct taos_data *taos = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d,%d,%d\n",
+ taos->avg[0], taos->avg[1], taos->avg[2]);
+}
+
+static ssize_t proximity_avg_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ return proximity_enable_store(dev, attr, buf, size);
+}
+
+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_poll_delay =
+ __ATTR(poll_delay, S_IRUGO | S_IWUSR | S_IWGRP,
+ poll_delay_show, poll_delay_store);
+static struct device_attribute dev_attr_light_adc =
+ __ATTR(light_adc, S_IRUGO | S_IWUSR | S_IWGRP,
+ lightsensor_file_state_show, NULL);
+
+static struct device_attribute dev_attr_proximity_enable =
+ __ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
+ proximity_enable_show, proximity_enable_store);
+static struct device_attribute dev_attr_proximity_delay =
+ __ATTR(delay, S_IRUGO | S_IWUSR | S_IWGRP,
+ proximity_delay_show, proximity_delay_store);
+static struct device_attribute dev_attr_proximity_wake =
+ __ATTR(wake, S_IRUGO | S_IWUSR | S_IWGRP,
+ NULL, proximity_wake_store);
+static struct device_attribute dev_attr_proximity_data =
+ __ATTR(data, S_IRUGO | S_IWUSR | S_IWGRP,
+ proximity_data_show, NULL);
+static struct device_attribute dev_attr_proximity_state =
+ __ATTR(proximity_state, S_IRUGO | S_IWUSR | S_IWGRP,
+ proximity_adc_show, NULL);
+static struct device_attribute dev_attr_proximity_avg =
+ __ATTR(proximity_avg, S_IRUGO | S_IWUSR | S_IWGRP,
+ proximity_avg_show, proximity_avg_store);
+
+static struct attribute *light_sysfs_attrs[] = {
+ &dev_attr_light_enable.attr,
+ &dev_attr_poll_delay.attr,
+ &dev_attr_light_adc.attr,
+ NULL
+};
+
+static struct attribute_group light_attribute_group = {
+ .attrs = light_sysfs_attrs,
+};
+
+static struct attribute *proximity_sysfs_attrs[] = {
+ &dev_attr_proximity_enable.attr,
+ &dev_attr_proximity_delay.attr,
+ &dev_attr_proximity_wake.attr,
+ &dev_attr_proximity_data.attr,
+ &dev_attr_proximity_state.attr,
+ &dev_attr_proximity_avg.attr,
+ NULL
+};
+
+static struct attribute_group proximity_attribute_group = {
+ .attrs = proximity_sysfs_attrs,
+};
+
+
+/****************************************************************
+ *
+ * function : taos_work_func_prox
+ * description : This function is for proximity sensor (using B-1 Mode).
+ * when INT signal is occured , it gets value from VO register.
+ *
+ *
+ */
+#if USE_INTERRUPT
+static void taos_work_func_prox(struct work_struct *work)
+{
+ struct taos_data *taos
+ = container_of(work, struct taos_data, work_prox);
+ u16 adc_data;
+ u16 threshold_high;
+ u16 threshold_low;
+ u8 prox_int_thresh[4];
+ int i;
+
+ /* change Threshold */
+ adc_data = i2c_smbus_read_word_data(taos->client, CMD_REG | 0x18);
+ threshold_high = i2c_smbus_read_word_data(taos->client,
+ (CMD_REG | PRX_MAXTHRESHLO));
+ threshold_low = i2c_smbus_read_word_data(taos->client,
+ (CMD_REG | PRX_MINTHRESHLO));
+ pr_info("[TAOS] %s %d, high(%x), low(%x)\n",
+ __func__, adc_data, threshold_high, threshold_low);
+
+ /* this is protection code for saturation */
+ if (taos_get_lux(taos) >= 1500) {
+
+#if TAOS_DEBUG
+ pr_info("[PROXIMITY] [%s] saturation. adc_data=[%X], threshold_high=[%X], threshold_min=[%X]\n",
+ __func__, adc_data, threshold_high, threshold_low);
+#endif
+ taos->proximity_value = 0;
+ prox_int_thresh[0] = (0x0000) & 0xFF;
+ prox_int_thresh[1] = (0x0000 >> 8) & 0xFF;
+ prox_int_thresh[2] = (PRX_THRSH_HI_PARAM) & 0xFF;
+ prox_int_thresh[3] = (PRX_THRSH_HI_PARAM >> 8) & 0xFF;
+ for (i = 0; i < 4; i++)
+ opt_i2c_write(taos->client,
+ (CMD_REG|(PRX_MINTHRESHLO + i)),
+ &prox_int_thresh[i]);
+ } /* end of protection code for saturation */
+ else if ((threshold_high == (PRX_THRSH_HI_PARAM))
+ && (adc_data >= (PRX_THRSH_HI_PARAM))) {
+#if TAOS_DEBUG
+ pr_info("[PROXIMITY] [%s] +++ adc_data=[%X], threshold_high=[%X], threshold_min=[%X]\n",
+ __func__, adc_data, threshold_high, threshold_low);
+#endif
+ taos->proximity_value = 1;
+ prox_int_thresh[0] = (PRX_THRSH_LO_PARAM) & 0xFF;
+ prox_int_thresh[1] = (PRX_THRSH_LO_PARAM >> 8) & 0xFF;
+ prox_int_thresh[2] = (0xFFFF) & 0xFF;
+ prox_int_thresh[3] = (0xFFFF >> 8) & 0xFF;
+ for (i = 0; i < 4; i++)
+ opt_i2c_write(taos->client,
+ (CMD_REG|(PRX_MINTHRESHLO + i)),
+ &prox_int_thresh[i]);
+ } else if ((threshold_high == (0xFFFF))
+ && (adc_data <= (PRX_THRSH_LO_PARAM))) {
+#if TAOS_DEBUG
+ pr_info("[PROXIMITY] [%s] --- adc_data=[%X], threshold_high=[%X], threshold_min=[%X]\n",
+ __func__, adc_data, threshold_high, threshold_low);
+#endif
+ taos->proximity_value = 0;
+ prox_int_thresh[0] = (0x0000) & 0xFF;
+ prox_int_thresh[1] = (0x0000 >> 8) & 0xFF;
+ prox_int_thresh[2] = (PRX_THRSH_HI_PARAM) & 0xFF;
+ prox_int_thresh[3] = (PRX_THRSH_HI_PARAM >> 8) & 0xFF;
+ for (i = 0; i < 4; i++)
+ opt_i2c_write(taos->client,
+ (CMD_REG|(PRX_MINTHRESHLO + i)),
+ &prox_int_thresh[i]);
+ } else {
+ pr_err("[PROXIMITY] [%s] Not Common!adc_data=[%X], threshold_high=[%X], threshold_min=[%X]\n",
+ __func__, adc_data, threshold_high, threshold_low);
+ }
+
+ if (USE_INPUT_DEVICE) {
+ input_report_abs(taos->proximity_input_dev,
+ ABS_DISTANCE, !taos->proximity_value);
+ pr_info("[PROXIMITY] [%s] proximity_value is %d (1: close, 0: far)\n",
+ __func__, taos->proximity_value);
+ input_sync(taos->proximity_input_dev);
+ usleep_range(1000, 2000);
+ }
+
+ /* reset Interrupt pin */
+ /* to active Interrupt, TMD2771x Interuupt pin shoud be reset. */
+ i2c_smbus_write_byte(taos->client,
+ (CMD_REG|CMD_SPL_FN|CMD_PROXALS_INTCLR));
+
+ /* enable INT */
+ enable_irq(taos->irq);
+}
+
+
+static irqreturn_t taos_irq_handler(int irq, void *dev_id)
+{
+ struct taos_data *taos = dev_id;
+
+#ifdef TAOS_DEBUG
+ pr_info("[PROXIMITY] taos->irq = %d\n", taos->irq);
+#endif
+
+ if (taos->irq != -1) {
+ wake_lock_timeout(&taos->prx_wake_lock, 3*HZ);
+ disable_irq_nosync(taos->irq);
+ queue_work(taos->taos_wq, &taos->work_prox);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int taos_setup_irq(struct taos_data *taos)
+{
+ int rc = -EIO;
+ struct taos_platform_data *pdata = taos->client->dev.platform_data;
+ int irq;
+
+ pr_err("%s, start\n", __func__);
+
+ 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,
+ taos_irq_handler,
+ IRQF_DISABLED|IRQ_TYPE_EDGE_FALLING,
+ "taos_int", taos);
+ 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;
+ }
+
+ rc = irq_set_irq_wake(irq, 1);
+ if (rc < 0)
+ pr_err("[TAOS] irq_set_irq_wake failed (%d)\n", rc);
+
+ /* start with interrupts disabled */
+ taos->irq = irq;
+
+ pr_err("%s, success\n", __func__);
+
+ goto done;
+
+err_request_irq:
+err_gpio_direction_input:
+ gpio_free(pdata->p_out);
+done:
+ return rc;
+}
+
+static void taos_work_func_light(struct work_struct *work)
+{
+ struct taos_data *taos
+ = container_of(work, struct taos_data, work_light);
+ int i = 0;
+ int lux = 0;
+/* state_type level_state = LIGHT_INIT; */
+
+ /* read value */
+ lux = taos_get_lux(taos);
+
+ for (i = 0 ; ARRAY_SIZE(adc_table) ; i++)
+ if (lux <= adc_table[i])
+ break;
+
+ if (taos->light_buffer == i) {
+ if (taos->light_count++ == LIGHT_BUFFER_NUM) {
+#if TAOS_DEBUG
+ pr_info("[TAOS_LIGHT] taos_work_func_light called lux=[%d], ch[0]=[%d], ch[1]=[%d]\n",
+ lux, taos->cleardata, taos->irdata);
+#endif
+ input_report_abs(taos->light_input_dev, ABS_MISC, lux);
+ input_sync(taos->light_input_dev);
+ taos->light_count = 0;
+ }
+ } else {
+ taos->light_buffer = i;
+ taos->light_count = 0;
+ }
+}
+
+static enum hrtimer_restart taos_timer_func(struct hrtimer *timer)
+{
+ struct taos_data *taos = container_of(timer, struct taos_data, timer);
+
+ queue_work(taos->taos_wq, &taos->work_light);
+ taos->light_polling_time = ktime_set(0, 0);
+ taos->light_polling_time
+ = ktime_add_us(taos->light_polling_time, 200000);
+ hrtimer_start(&taos->timer, taos->light_polling_time, HRTIMER_MODE_REL);
+
+ return HRTIMER_NORESTART;
+
+}
+
+
+static void taos_work_func_ptime(struct work_struct *work)
+{
+ struct taos_data *taos
+ = container_of(work, struct taos_data, work_ptime);
+ u16 proximity_value = 0;
+ int min = 0, max = 0, avg = 0;
+ int i = 0;
+
+ for (i = 0 ; i < PROX_READ_NUM ; i++) {
+ proximity_value = i2c_smbus_read_word_data(taos->client,
+ CMD_REG | PRX_LO);
+ if (proximity_value > TAOS_PROX_MIN) {
+ if (proximity_value > TAOS_PROX_MAX)
+ proximity_value = TAOS_PROX_MAX;
+ avg += proximity_value;
+ if (!i)
+ min = proximity_value;
+ if (proximity_value < min)
+ min = proximity_value;
+ if (proximity_value > max)
+ max = proximity_value;
+ } else {
+ proximity_value = TAOS_PROX_MIN;
+ break;
+ }
+ msleep(40);
+ }
+
+ if (i == 0) {
+ avg = proximity_value;
+ min = max = TAOS_PROX_MIN;
+ } else
+ avg /= i;
+
+ taos->avg[0] = min;
+ taos->avg[1] = avg;
+ taos->avg[2] = max;
+}
+
+static enum hrtimer_restart taos_ptimer_func(struct hrtimer *timer)
+{
+ struct taos_data *taos = container_of(timer, struct taos_data, ptimer);
+
+ queue_work(taos->taos_test_wq, &taos->work_ptime);
+ hrtimer_forward_now(&taos->ptimer, taos->prox_polling_time);
+
+ return HRTIMER_RESTART;
+}
+
+#endif
+
+/*************************************************************************/
+/* TAOS file operations */
+/*************************************************************************/
+
+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 taos_opt_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int err = 0;
+ struct input_dev *input_dev;
+ struct taos_data *taos;
+
+ pr_info("[TAOS] taos_opt_probe\n");
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ pr_err("[TAOS] i2c_check_functionality error\n");
+ err = -ENODEV;
+ goto exit;
+ }
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA)) {
+ pr_err("[TAOS] byte op is not permited.\n");
+ goto exit;
+ }
+
+ /* OK. For now, we presume we have a valid client. We now create the
+ client structure, even though we cannot fill it completely yet. */
+ taos = kzalloc(sizeof(struct taos_data), GFP_KERNEL);
+ if (!taos) {
+ err = -ENOMEM;
+ goto exit;
+ }
+ memset(taos, 0, sizeof(struct taos_data));
+ taos->client = client;
+ i2c_set_clientdata(client, taos);
+
+ taos->irdata = 0; /*Ch[1] */
+ taos->cleardata = 0; /*Ch[0] */
+ taos->chipID = 0;
+
+ usleep_range(12000, 20000);
+
+ /* wake lock init */
+ wake_lock_init(&taos->prx_wake_lock,
+ WAKE_LOCK_SUSPEND, "prx_wake_lock");
+ mutex_init(&taos->power_lock);
+
+ /* allocate proximity input_device */
+ if (USE_INPUT_DEVICE) {
+ input_dev = input_allocate_device();
+ if (input_dev == NULL) {
+ pr_err("Failed to allocate input device\n");
+ err = -ENOMEM;
+ goto err_input_allocate_device_proximity;
+ }
+ taos->proximity_input_dev = input_dev;
+ input_set_drvdata(input_dev, taos);
+ 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);
+
+ err = input_register_device(input_dev);
+ if (err) {
+ pr_err("Unable to register %s input device\n",
+ input_dev->name);
+ goto err_input_register_device_proximity;
+ }
+
+ err = sysfs_create_group(&input_dev->dev.kobj,
+ &proximity_attribute_group);
+ if (err) {
+ pr_err("%s: could not create sysfs group\n", __func__);
+ goto err_sysfs_create_group_proximity;
+ }
+ }
+
+#if USE_INTERRUPT
+ /* WORK QUEUE Settings */
+ taos->taos_wq = create_singlethread_workqueue("taos_wq");
+ if (!taos->taos_wq) {
+ err = -ENOMEM;
+ pr_err("%s: could not create workqueue\n", __func__);
+ goto err_create_workqueue;
+ }
+ INIT_WORK(&taos->work_prox, taos_work_func_prox);
+ INIT_WORK(&taos->work_light, taos_work_func_light);
+
+ taos->taos_test_wq = create_singlethread_workqueue("taos_test_wq");
+ if (!taos->taos_test_wq) {
+ err = -ENOMEM;
+ pr_err("%s: could not create workqueue\n", __func__);
+ goto err_create_workqueue;
+ }
+ INIT_WORK(&taos->work_ptime, taos_work_func_ptime);
+#endif
+
+ /* hrtimer settings. we poll for light values using a timer. */
+ hrtimer_init(&taos->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ taos->timer.function = taos_timer_func;
+
+ hrtimer_init(&taos->ptimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ taos->prox_polling_time = ns_to_ktime(2000 * NSEC_PER_MSEC);
+ taos->ptimer.function = taos_ptimer_func;
+
+ /* allocate lightsensor-level input_device */
+ if (USE_INPUT_DEVICE) {
+ 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, taos);
+ 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);
+
+ err = input_register_device(input_dev);
+ if (err) {
+ pr_err("Unable to register %s input device\n",
+ input_dev->name);
+ goto err_input_register_device_light;
+ }
+ taos->light_input_dev = input_dev;
+ err = sysfs_create_group(&input_dev->dev.kobj,
+ &light_attribute_group);
+ if (err) {
+ pr_err("%s: could not create sysfs group\n", __func__);
+ goto err_sysfs_create_group_light;
+ }
+ }
+
+ /* misc device Settings */
+ err = misc_register(&light_device);
+ if (err)
+ pr_err(KERN_ERR "[TAOS] %s: misc_register failed - light\n",
+ __func__);
+
+ /* set sysfs for proximity sensor */
+ taos->proximity_class = class_create(THIS_MODULE, "proximity");
+ if (IS_ERR(taos->proximity_class))
+ pr_err("%s: could not create proximity_class\n", __func__);
+
+ taos->proximity_dev = device_create(taos->proximity_class,
+ NULL, 0, NULL, "proximity");
+ if (IS_ERR(taos->proximity_dev))
+ pr_err("%s: could not create proximity_dev\n", __func__);
+
+ if (device_create_file(taos->proximity_dev,
+ &dev_attr_proximity_state) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_proximity_state.attr.name);
+ }
+
+ if (device_create_file(taos->proximity_dev,
+ &dev_attr_proximity_avg) < 0) {
+ pr_err("%s: could not create device file(%s)!\n", __func__,
+ dev_attr_proximity_avg.attr.name);
+ }
+ dev_set_drvdata(taos->proximity_dev, taos);
+
+ /* set sysfs for light sensor */
+ taos->lightsensor_class = class_create(THIS_MODULE, "lightsensor");
+ if (IS_ERR(taos->lightsensor_class))
+ pr_err("Failed to create class(lightsensor)!\n");
+
+ taos->switch_cmd_dev = device_create(taos->lightsensor_class,
+ NULL, 0, NULL, "switch_cmd");
+ if (IS_ERR(taos->switch_cmd_dev))
+ pr_err("Failed to create device(switch_cmd_dev)!\n");
+
+ if (device_create_file(taos->switch_cmd_dev,
+ &dev_attr_light_adc) < 0)
+ pr_err("Failed to create device file(%s)!\n",
+ dev_attr_light_adc.attr.name);
+
+ dev_set_drvdata(taos->switch_cmd_dev, taos);
+ usleep_range(2000, 3000);
+
+ taos_setup_irq(taos);
+
+ taos->chipID = i2c_smbus_read_byte_data(client, CMD_REG | CHIPID);
+ pr_info("[TAOS] %s: chipID[%X]\n", __func__, taos->chipID);
+
+ /* maintain power-down mode before using sensor */
+ taos_off(taos, TAOS_ALL);
+ pr_info("%s: done!!\n", __func__);
+
+ return 0;
+
+err_sysfs_create_group_light:
+ input_unregister_device(taos->light_input_dev);
+err_input_register_device_light:
+err_input_allocate_device_light:
+ destroy_workqueue(taos->taos_wq);
+ destroy_workqueue(taos->taos_test_wq);
+err_create_workqueue:
+ sysfs_remove_group(&taos->proximity_input_dev->dev.kobj,
+ &proximity_attribute_group);
+err_sysfs_create_group_proximity:
+ input_unregister_device(taos->proximity_input_dev);
+err_input_register_device_proximity:
+err_input_allocate_device_proximity:
+ mutex_destroy(&taos->power_lock);
+ wake_lock_destroy(&taos->prx_wake_lock);
+ kfree(taos);
+ taos = NULL;
+exit:
+ taos = NULL;
+ return err;
+}
+
+
+static int taos_opt_remove(struct i2c_client *client)
+{
+ struct taos_data *taos = i2c_get_clientdata(client);
+#ifdef TAOS_DEBUG
+ pr_info("%s\n", __func__);
+#endif
+ if (USE_INPUT_DEVICE) {
+ sysfs_remove_group(&taos->light_input_dev->dev.kobj,
+ &light_attribute_group);
+ input_unregister_device(taos->light_input_dev);
+ sysfs_remove_group(&taos->proximity_input_dev->dev.kobj,
+ &proximity_attribute_group);
+ input_unregister_device(taos->proximity_input_dev);
+ }
+ if (taos->light_enable)
+ taos_light_disable(taos);
+ if (taos->proximity_enable) {
+ taos_ptime_disable(taos);
+ disable_irq(taos->irq);
+ }
+ if (taos->taos_wq)
+ destroy_workqueue(taos->taos_wq);
+ if (taos->taos_test_wq)
+ destroy_workqueue(taos->taos_test_wq);
+ mutex_destroy(&taos->power_lock);
+ wake_lock_destroy(&taos->prx_wake_lock);
+ kfree(taos);
+ return 0;
+}
+
+static void taos_opt_shutdown(struct i2c_client *client)
+{
+ struct taos_data *taos = i2c_get_clientdata(client);
+#ifdef TAOS_DEBUG
+ pr_info("%s\n", __func__);
+#endif
+ if (USE_INPUT_DEVICE) {
+ sysfs_remove_group(&taos->light_input_dev->dev.kobj,
+ &light_attribute_group);
+ input_unregister_device(taos->light_input_dev);
+ sysfs_remove_group(&taos->proximity_input_dev->dev.kobj,
+ &proximity_attribute_group);
+ input_unregister_device(taos->proximity_input_dev);
+ }
+ if (taos->light_enable)
+ taos_light_disable(taos);
+ if (taos->proximity_enable) {
+ taos_ptime_disable(taos);
+ disable_irq(taos->irq);
+ }
+ if (taos->taos_wq)
+ destroy_workqueue(taos->taos_wq);
+ if (taos->taos_test_wq)
+ destroy_workqueue(taos->taos_test_wq);
+ mutex_destroy(&taos->power_lock);
+ wake_lock_destroy(&taos->prx_wake_lock);
+ kfree(taos);
+}
+#ifdef CONFIG_PM
+static int taos_opt_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
+ */
+#ifdef TAOS_DEBUG
+ pr_info("[%s] TAOS !!suspend mode!!\n", __func__);
+#endif
+
+ return 0;
+}
+
+static int taos_opt_resume(struct device *dev)
+{
+#ifdef TAOS_DEBUG
+ pr_info("[%s] TAOS !!resume mode!!\n", __func__);
+#endif
+
+ return 0;
+}
+#else
+#define taos_opt_suspend NULL
+#define taos_opt_resume NULL
+#endif
+
+static const struct i2c_device_id taos_id[] = {
+ { "taos", 0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, taos_id);
+
+static const struct dev_pm_ops taos_pm_ops = {
+ .suspend = taos_opt_suspend,
+ .resume = taos_opt_resume,
+};
+
+static struct i2c_driver taos_opt_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "taos",
+ .pm = &taos_pm_ops
+ },
+ .probe = taos_opt_probe,
+ .remove = taos_opt_remove,
+ .shutdown = taos_opt_shutdown,
+ .id_table = taos_id,
+};
+
+static int __init taos_opt_init(void)
+{
+
+#ifdef TAOS_DEBUG
+ pr_info("%s\n", __func__);
+#endif
+ return i2c_add_driver(&taos_opt_driver);
+}
+
+static void __exit taos_opt_exit(void)
+{
+ i2c_del_driver(&taos_opt_driver);
+#ifdef TAOS_DEBUG
+ pr_info("%s\n", __func__);
+#endif
+}
+
+module_init(taos_opt_init);
+module_exit(taos_opt_exit);
+
+MODULE_AUTHOR("SAMSUNG");
+MODULE_DESCRIPTION("Optical Sensor driver for taosp002s00f");
+MODULE_LICENSE("GPL");