From cf2439b6ab4ea5fe9884b3fc20789d69ad4f773a Mon Sep 17 00:00:00 2001 From: "makarand.karvekar" Date: Thu, 21 Oct 2010 15:23:23 -0500 Subject: [PATCH] misc: add capacitive proximity calibration driver capacitive proximity(cap-prox) calibration scheme to rule out proximity detection due to some light conductive surfaces as device covers and table-top. Change-Id: I64d566a168befb82a610de6094044eeca294c6c4 Signed-off-by: makarand.karvekar --- drivers/misc/Kconfig | 8 + drivers/misc/Makefile | 1 + drivers/misc/cap_prox.c | 450 +++++++++++++++++++++++++++++++++++++++ include/linux/cap_prox.h | 54 +++++ 4 files changed, 513 insertions(+) create mode 100755 drivers/misc/cap_prox.c create mode 100755 include/linux/cap_prox.h diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 89fa7e153a6e..e872aa1da391 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -351,6 +351,14 @@ config SENSORS_AK8975 If you say yes here you get support for Asahi Kasei's orientation sensor AK8975. +config SENSORS_CAP_PROX + tristate "Motorola Capacitive Proximity Sensor" + default n + depends on I2C + help + Say yes here if you wish to include the Motorola + Capacitive Proximity Sensor driver. + config SENSORS_KXTF9 tristate "KXTF9 Accelerometer" default n diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index de167814b48a..9cd2d8b7bd68 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -42,6 +42,7 @@ obj-$(CONFIG_WL127X_RFKILL) += wl127x-rfkill.o obj-$(CONFIG_APANIC) += apanic.o obj-$(CONFIG_SENSORS_AK8975) += akm8975.o obj-$(CONFIG_SENSORS_KXTF9) += kxtf9.o +obj-$(CONFIG_SENSORS_CAP_PROX) += cap_prox.o obj-$(CONFIG_SENSORS_MAX9635) += max9635.o obj-$(CONFIG_SENSORS_NCT1008) += nct1008.o obj-$(CONFIG_SENSORS_L3G4200D) += l3g4200d.o diff --git a/drivers/misc/cap_prox.c b/drivers/misc/cap_prox.c new file mode 100755 index 000000000000..89e4f9fd9ef8 --- /dev/null +++ b/drivers/misc/cap_prox.c @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2010 Motorola, Inc. + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define CAP_PROX_NAME "cap-prox" +#define CP_STATUS_NUM_KEYS_ENABLED 0x20 + +#define CP_STATUS_KEY3_IN_DETECT 0x24 +#define CP_STATUS_KEY1_IN_DETECT 0x21 +#define CP_STATUS_KEY1_KEY3_IN_DETECT 0x25 +#define CP_STATUS_KEY1_KEY3_EN_FORCE_DETECT 0x75 + +struct cap_prox_msg { + uint8_t status; + uint16_t ref_key1; + uint16_t ref_key3; + uint8_t chip_id; + uint8_t sw_ver; + uint8_t reserved8; + uint16_t signal1; + uint16_t signal2; + uint16_t signal3; + uint16_t signal4; + uint16_t save_ref1; + uint16_t save_ref2; + uint16_t save_ref3; + uint16_t save_ref4; +} __attribute__ ((packed)); + +struct cap_prox_data { + struct i2c_client *client; + struct input_dev *input_dev; + struct delayed_work input_work; + struct work_struct irq_work; + struct cap_prox_platform_data *pdata; + + atomic_t enabled; + spinlock_t irq_lock; + int irq_state; +}; + +static uint32_t cp_dbg; +module_param_named(cp_debug, cp_dbg, uint, 0664); + +struct cap_prox_data *cap_prox_misc_data; +struct workqueue_struct *cp_irq_wq; + +static void cap_prox_irq_enable(struct cap_prox_data *cp, int enable) +{ + unsigned long flags; + + spin_lock_irqsave(&cp->irq_lock, flags); + if (cp->irq_state != enable) { + if (enable) + enable_irq(cp->client->irq); + else + disable_irq_nosync(cp->client->irq); + cp->irq_state = enable; + } + spin_unlock_irqrestore(&cp->irq_lock, flags); +} + +static irqreturn_t cap_prox_irq_handler(int irq, void *dev_id) +{ + struct cap_prox_data *cp = dev_id; + + cap_prox_irq_enable(cp, 0); + queue_work(cp_irq_wq, &cp->irq_work); + + return IRQ_HANDLED; +} + +static int cap_prox_write(struct cap_prox_data *cp, void *buf, int buf_sz) +{ + int retries = 10; + int ret; + + do { + ret = i2c_master_send(cp->client, (char *)buf, buf_sz); + } while ((ret < buf_sz) && (--retries > 0)); + + if (ret < 0) + pr_info("%s: Error while trying to write %d bytes\n", __func__, + buf_sz); + else if (ret != buf_sz) { + pr_info("%s: Write %d bytes, expected %d\n", __func__, + ret, buf_sz); + ret = -EIO; + } + return ret; +} + +static int cap_prox_read(struct cap_prox_data *cp, void *buf, int buf_sz) +{ + int retries = 10; + int ret; + + do { + memset(buf, 0, buf_sz); + ret = i2c_master_recv(cp->client, (char *)buf, buf_sz); + } while ((ret < 0) && (--retries > 0)); + + if (ret < 0) + pr_info("%s: Error while trying to read %d bytes\n", __func__, + buf_sz); + else if (ret != buf_sz) { + pr_info("%s: Read %d bytes, expected %d\n", __func__, + ret, buf_sz); + ret = -EIO; + } + + return ret >= 0 ? 0 : ret; +} + +static void cap_prox_calibrate(struct cap_prox_data *cp) { + cap_prox_write(cp, &cp->pdata->plat_cap_prox_cfg.calibrate,1); + if (cp_dbg) + pr_info("%s: Send Calibrate 0x%x\n", __func__, + cp->pdata->plat_cap_prox_cfg.calibrate); +} + +static int cap_prox_read_data(struct cap_prox_data *cp) +{ + struct cap_prox_msg *msg; + uint8_t status; + int ret = -1; + int key1_ref_drift = 0; + int key3_ref_drift = 0; + int ref_drift_diff = 0; + int key1_save_drift = 0; + int key3_save_drift = 0; + int save_drift_diff = 0; + uint8_t mesg_buf[sizeof(struct cap_prox_msg)]; + + + ret = cap_prox_read(cp, mesg_buf, sizeof(struct cap_prox_msg)); + if (ret) { + status = CP_STATUS_NUM_KEYS_ENABLED; + pr_info("MK: read failed \n"); + goto read_fail_ret; + } + msg = (struct cap_prox_msg *)mesg_buf; + + if (cp_dbg & 0x02) { + pr_info("%s: Cap-Prox data \n", __func__); + pr_info(" msg->status 0x%2x \n",msg->status); + pr_info(" msg->ref_key1 0x%x \n",msg->ref_key1); + pr_info(" msg->ref_key3 0x%x \n",msg->ref_key3); + pr_info(" msg->chip_id 0x%2x \n",msg->chip_id); + pr_info(" msg->sw_ver 0x%2x \n",msg->sw_ver); + pr_info(" msg->signal1 0x%x \n",msg->signal1); + pr_info(" msg->signal2 0x%x \n",msg->signal2); + pr_info(" msg->signal3 0x%x \n",msg->signal3); + pr_info(" msg->signal4 0x%x \n",msg->signal4); + pr_info(" msg->save_ref1 0x%x \n",msg->save_ref1); + pr_info(" msg->save_ref2 0x%x \n",msg->save_ref2); + pr_info(" msg->save_ref3 0x%x \n",msg->save_ref3); + pr_info(" msg->save_ref4 0x%x \n\n",msg->save_ref4); + } + + key1_ref_drift = abs(msg->ref_key1 - msg->signal1); + key3_ref_drift = abs(msg->ref_key3 - msg->signal3); + ref_drift_diff = abs(key3_ref_drift - key1_ref_drift); + key1_save_drift = abs(msg->save_ref1 - msg->signal1); + key3_save_drift = abs(msg->save_ref3 - msg->signal3); + save_drift_diff = abs(key3_save_drift - key1_save_drift); + + if (cp_dbg) { + pr_info("%s: Key1 ref drift %d \n", __func__, key1_ref_drift); + pr_info("%s: key3 ref drift %d \n", __func__, key3_ref_drift); + pr_info("%s: Key1 Key3 ref drift diff %d \n\n", + __func__, ref_drift_diff); + pr_info("%s: Key1 save drift %d \n", __func__, key1_save_drift); + pr_info("%s: key3 save drift %d \n", __func__, key3_save_drift); + pr_info("%s: Key1 Key3 drift diff %d \n\n", + __func__, save_drift_diff); + } + + switch (msg->status) { + + case CP_STATUS_KEY1_IN_DETECT: + if (key1_ref_drift < cp->pdata->key1_ref_drift_thres_l) + cap_prox_calibrate(cp); + break; + case CP_STATUS_KEY3_IN_DETECT: + if (key3_ref_drift < cp->pdata->key3_ref_drift_thres_l) + cap_prox_calibrate(cp); + break; + case CP_STATUS_KEY1_KEY3_EN_FORCE_DETECT: + if ((save_drift_diff < cp->pdata->save_drift_diff_thres) && + (key1_save_drift < cp->pdata->key1_save_drift_thres) && + (key3_save_drift < cp->pdata->key3_save_drift_thres)) { + + status = msg->status & 0xF0; + cap_prox_write(cp,&status,1); + } + break; + case CP_STATUS_KEY1_KEY3_IN_DETECT: + if ((key3_ref_drift < cp->pdata->key1_ref_drift_thres_h) && + (key1_ref_drift < cp->pdata->key3_ref_drift_thres_h) && + (ref_drift_diff < cp->pdata->ref_drift_diff_thres)) + cap_prox_calibrate(cp); + break; + default: + pr_info("%s: Cap-prox message 0x%x\n", __func__, + msg->status); + break; + } + status = msg->status; + +read_fail_ret: + return status; +} + +static int cap_prox_hw_init(struct cap_prox_data *cp) +{ + pr_info("%s: HW init\n", __func__); + cap_prox_calibrate(cp); + cap_prox_write(cp,&cp->pdata->plat_cap_prox_cfg.thres_key1,1); + cap_prox_write(cp,&cp->pdata->plat_cap_prox_cfg.thres_key2,1); + msleep(200); + + return 0; +} + +static void cap_prox_irq_work_func(struct work_struct *work) +{ + int ret; + u8 buf[2]; + struct cap_prox_data *cp = + container_of(work, struct cap_prox_data, irq_work); + + cancel_delayed_work_sync(&cp->input_work); + ret = cap_prox_read(cp, buf, 2); + + if (cp_dbg) + pr_info("%s: Cap-Prox Status: [0x%x][0x%x] \n", + __func__, buf[0], buf[1]); + + if (buf[0] != CP_STATUS_NUM_KEYS_ENABLED) + schedule_delayed_work(&cp->input_work, + msecs_to_jiffies(cp->pdata->poll_interval)); + + cap_prox_irq_enable(cp, 1); + +} + +static struct miscdevice cap_prox_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = CAP_PROX_NAME, +}; + +static void cap_prox_input_work_func(struct work_struct *work) +{ + int ret = 0; + struct cap_prox_data *cp = container_of((struct delayed_work *)work, + struct cap_prox_data, input_work); + ret = cap_prox_read_data(cp); + + if (ret != CP_STATUS_NUM_KEYS_ENABLED) + schedule_delayed_work(&cp->input_work, + msecs_to_jiffies(cp->pdata->poll_interval)); +} + +static int cap_prox_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cap_prox_platform_data *pdata = client->dev.platform_data; + struct cap_prox_data *cp; + struct cap_prox_msg *msg; + uint8_t mesg_buf[sizeof(struct cap_prox_msg)]; + int err = -1; + + if (pdata == NULL) { + pr_err("%s: platform data required\n", __func__); + return -ENODEV; + } else if (!client->irq) { + pr_err("%s: polling mode currently not supported\n", __func__); + return -ENODEV; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + pr_err("%s: need I2C_FUNC_I2C\n", __func__); + return -ENODEV; + } + + cp = kzalloc(sizeof(struct cap_prox_data), GFP_KERNEL); + if (cp == NULL) { + err = -ENOMEM; + goto err_out1; + } + + cp->pdata = pdata; + cp->client = client; + i2c_set_clientdata(client, cp); + spin_lock_init(&cp->irq_lock); + cp->irq_state = 1; + + INIT_WORK(&cp->irq_work, cap_prox_irq_work_func); + INIT_DELAYED_WORK(&cp->input_work, cap_prox_input_work_func); + + err = cap_prox_hw_init(cp); + if (err < 0) + goto err_out2; + + err = misc_register(&cap_prox_misc_device); + if (err < 0) { + dev_err(&client->dev, "misc register failed: %d\n", err); + goto err_out2; + } + + err = cap_prox_read(cp, mesg_buf, sizeof(struct cap_prox_msg)); + if (err) + goto err_out3; + msg = (struct cap_prox_msg *)mesg_buf; + pr_info("%s: msg->status 0x%2x \n", __func__, msg->status); + + /* Could be booted with body proximity, so force detect */ + cap_prox_write(cp,&cp->pdata->plat_cap_prox_cfg.force_detect,1); + + err = request_irq(cp->client->irq, cap_prox_irq_handler, + IRQF_TRIGGER_FALLING, "cap_prox_irq", cp); + if (err < 0) { + dev_err(&client->dev, "request irq failed: %d\n", err); + goto err_out3; + } + + pr_info("%s: Request IRQ = %d\n", __func__, cp->client->irq); + + cap_prox_write(cp, &cp->pdata->plat_cap_prox_cfg.address_ptr, 1); + + dev_info(&client->dev, "cap-prox probed\n"); + + return 0; + +err_out3: + misc_deregister(&cap_prox_misc_device); +err_out2: + kfree(cp); +err_out1: + return err; +} + +static int __devexit cap_prox_remove(struct i2c_client *client) +{ + struct cap_prox_data *cp = i2c_get_clientdata(client); + + free_irq(cp->client->irq, cp); + misc_deregister(&cap_prox_misc_device); + i2c_set_clientdata(client, NULL); + kfree(cp); + + return 0; +} + +static int cap_prox_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct cap_prox_data *cp = i2c_get_clientdata(client); + + if (cp_dbg & 0x4) + pr_info("%s: Suspending\n", __func__); + + cap_prox_irq_enable(cp, 0); + cancel_delayed_work_sync(&cp->input_work); + + return 0; +} + +static int cap_prox_resume(struct i2c_client *client) +{ + struct cap_prox_data *cp = i2c_get_clientdata(client); + + if (cp_dbg & 0x4) + pr_info("%s: Resuming\n", __func__); + + schedule_delayed_work(&cp->input_work, + msecs_to_jiffies(cp->pdata->min_poll_interval)); + cap_prox_irq_enable(cp, 1); + + return 0; +} + +static const struct i2c_device_id cap_prox_id[] = { + { CAP_PROX_NAME, 0 }, + { } +}; + +static struct i2c_driver cap_prox_driver = { + .probe = cap_prox_probe, + .remove = cap_prox_remove, + .suspend = cap_prox_suspend, + .resume = cap_prox_resume, + .id_table = cap_prox_id, + .driver = { + .name = CAP_PROX_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __devinit cap_prox_init(void) +{ + cp_irq_wq = create_singlethread_workqueue("cp_irq_wq"); + if (cp_irq_wq == NULL) { + pr_err("%s: No memory for cp_irq_wq\n", __func__); + return -ENOMEM; + } + return i2c_add_driver(&cap_prox_driver); +} + +static void __exit cap_prox_exit(void) +{ + i2c_del_driver(&cap_prox_driver); + if (cp_irq_wq) + destroy_workqueue(cp_irq_wq); +} + +module_init(cap_prox_init); +module_exit(cap_prox_exit); + +MODULE_AUTHOR("Motorola"); +MODULE_DESCRIPTION("Capacitive Proximity Sensor Driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/cap_prox.h b/include/linux/cap_prox.h new file mode 100755 index 000000000000..f8607acd18b2 --- /dev/null +++ b/include/linux/cap_prox.h @@ -0,0 +1,54 @@ +/* + * include/linux/cap_prox.h + * + * Copyright (C) 2010 Motorola, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _LINUX_CAP_PROX_H +#define _LINUX_CAP_PROX_H + +struct cap_prox_cfg { + uint8_t lp_mode; + uint8_t address_ptr; + uint8_t reset; + uint8_t key_enable_mask; + uint8_t data_integration; + uint8_t neg_drift_rate; + uint8_t pos_drift_rate; + uint8_t force_detect; + uint8_t calibrate; + uint8_t thres_key1; + uint8_t ref_backup; + uint8_t thres_key2; + uint8_t reserved12; + uint8_t drift_hold_time; + uint8_t reserved14; + uint8_t reserved15; +} __attribute__ ((packed)); + +struct cap_prox_platform_data { + int poll_interval; + int min_poll_interval; + uint8_t key1_ref_drift_thres_l; + uint8_t key3_ref_drift_thres_l; + uint8_t key1_ref_drift_thres_h; + uint8_t key3_ref_drift_thres_h; + uint8_t ref_drift_diff_thres; + uint8_t key1_save_drift_thres; + uint8_t key3_save_drift_thres; + uint8_t save_drift_diff_thres; + + struct cap_prox_cfg plat_cap_prox_cfg; +}; + +#endif /* _LINUX_CAP_PROX_H */ -- 2.34.1