diff options
author | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
---|---|---|
committer | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
commit | c6da2cfeb05178a11c6d062a06f8078150ee492f (patch) | |
tree | f3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/sensor | |
parent | c6d7c4dbff353eac7919342ae6b3299a378160a6 (diff) | |
download | kernel_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/Kconfig | 123 | ||||
-rw-r--r-- | drivers/sensor/Makefile | 22 | ||||
-rw-r--r-- | drivers/sensor/ak8975-reg.h | 48 | ||||
-rw-r--r-- | drivers/sensor/ak8975.c | 876 | ||||
-rw-r--r-- | drivers/sensor/bh1721.c | 760 | ||||
-rw-r--r-- | drivers/sensor/bma254_driver.c | 1431 | ||||
-rw-r--r-- | drivers/sensor/bmp180.c | 778 | ||||
-rw-r--r-- | drivers/sensor/cm3663.c | 993 | ||||
-rw-r--r-- | drivers/sensor/cm36651.c | 1360 | ||||
-rw-r--r-- | drivers/sensor/gp2a_analog.c | 787 | ||||
-rw-r--r-- | drivers/sensor/gp2a_light.c | 732 | ||||
-rw-r--r-- | drivers/sensor/gp2a_proximity.c | 863 | ||||
-rw-r--r-- | drivers/sensor/k3dh.c | 803 | ||||
-rw-r--r-- | drivers/sensor/k3dh_reg.h | 154 | ||||
-rw-r--r-- | drivers/sensor/k3g.c | 1372 | ||||
-rw-r--r-- | drivers/sensor/lps331ap.c | 1767 | ||||
-rw-r--r-- | drivers/sensor/lsm330dlc_accel.c | 1402 | ||||
-rw-r--r-- | drivers/sensor/lsm330dlc_gyro.c | 1643 | ||||
-rw-r--r-- | drivers/sensor/pas2m110.c | 1009 | ||||
-rw-r--r-- | drivers/sensor/sensors_core.c | 75 | ||||
-rw-r--r-- | drivers/sensor/taos.c | 1372 |
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, ®_defaults[1]); + gp2a_i2c_write(gp2a, REGS_HYS, ®_defaults[2]); + gp2a_i2c_write(gp2a, REGS_CYCLE, ®_defaults[3]); + gp2a_i2c_write(gp2a, REGS_OPMOD, ®_defaults[4]); + 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, ®_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 = ® + + 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 = ®_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 = ®_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 = ®_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 = ®1; + + err = i2c_transfer(data->client->adapter, msg, 2); + if (err != 2) + return (err < 0) ? err : -EIO; + + reg_buf = CTRL_REG2; + msg[1].buf = ®2; + + err = i2c_transfer(data->client->adapter, msg, 2); + if (err != 2) + return (err < 0) ? err : -EIO; + + reg_buf = CTRL_REG3; + msg[1].buf = ®3; + + err = i2c_transfer(data->client->adapter, msg, 2); + if (err != 2) + return (err < 0) ? err : -EIO; + + reg_buf = CTRL_REG4; + msg[1].buf = ®4; + + err = i2c_transfer(data->client->adapter, msg, 2); + if (err != 2) + return (err < 0) ? err : -EIO; + + reg_buf = CTRL_REG5; + msg[1].buf = ®5; + + 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), ®_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"); |