summaryrefslogtreecommitdiff
path: root/drivers/fingerprint/fpc1020_ree.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/fingerprint/fpc1020_ree.c')
-rw-r--r--drivers/fingerprint/fpc1020_ree.c609
1 files changed, 609 insertions, 0 deletions
diff --git a/drivers/fingerprint/fpc1020_ree.c b/drivers/fingerprint/fpc1020_ree.c
new file mode 100644
index 000000000000..166040a550f2
--- /dev/null
+++ b/drivers/fingerprint/fpc1020_ree.c
@@ -0,0 +1,609 @@
+/*
+ * ZUK FPC1150 REE driver
+ * Copyright (c) 2012-2015, The Linux Foundation. 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 and
+ * only 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.
+ */
+#define pr_fmt(fmt) "ZUK-FPC: %s: " fmt, __func__
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/clk.h>
+#include <linux/fs.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/fb.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/platform_device.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/io.h>
+#include <linux/of_gpio.h>
+#include <linux/input.h>
+
+#define FPC1020_TOUCH_DEV_NAME "fpc1020tp"
+
+#define FPC1020_RESET_LOW_US 1000
+#define FPC1020_RESET_HIGH1_US 100
+#define FPC1020_RESET_HIGH2_US 1250
+#define FPC_TTW_HOLD_TIME 1500
+
+struct fpc1020_data {
+ struct device *dev;
+ struct pinctrl *pin;
+ wait_queue_head_t wq_irq_return;
+ /*Set pins*/
+ int reset_gpio;
+ int irq_gpio;
+ int irq;
+ bool irq_enabled;
+ int wakeup_enabled;
+ struct notifier_block fb_notif;
+ /*Input device*/
+ struct input_dev *input_dev;
+ struct work_struct pm_work;
+ struct work_struct input_report_work;
+ struct workqueue_struct *fpc1020_wq;
+ u8 report_key;
+ int screen_on;
+ int proximity_state; /* 0:far 1:near */
+};
+
+static void config_irq(struct fpc1020_data *fpc1020, bool enabled)
+{
+ if (enabled != fpc1020->irq_enabled) {
+ if (enabled)
+ enable_irq(gpio_to_irq(fpc1020->irq_gpio));
+ else
+ disable_irq(gpio_to_irq(fpc1020->irq_gpio));
+
+ dev_info(fpc1020->dev, "%s: %s fpc irq ---\n", __func__,
+ enabled ? "enable" : "disable");
+ fpc1020->irq_enabled = enabled;
+ } else {
+ dev_info(fpc1020->dev, "%s: dual config irq status: %s\n", __func__,
+ enabled ? "true" : "false");
+ }
+}
+
+/* From drivers/input/keyboard/gpio_keys.c */
+extern bool home_button_pressed(void);
+extern void reset_home_button(void);
+
+bool reset;
+
+static bool utouch_disable;
+
+static int fb_notifier_callback(struct notifier_block *self,
+ unsigned long event, void *data);
+
+static ssize_t irq_get(struct device *device,
+ struct device_attribute *attribute,
+ char *buffer)
+{
+ struct fpc1020_data *fpc1020 = dev_get_drvdata(device);
+ int irq = gpio_get_value(fpc1020->irq_gpio);
+
+ return scnprintf(buffer, PAGE_SIZE, "%i\n", irq);
+}
+
+static ssize_t irq_set(struct device *device,
+ struct device_attribute *attribute,
+ const char *buffer, size_t count)
+{
+ int retval = 0;
+ u64 val;
+ struct fpc1020_data *fpc1020 = dev_get_drvdata(device);
+
+ retval = kstrtou64(buffer, 0, &val);
+ if (val)
+ enable_irq(fpc1020->irq);
+ else if (!val)
+ disable_irq(fpc1020->irq);
+ else
+ return -ENOENT;
+ return strnlen(buffer, count);
+}
+
+static DEVICE_ATTR(irq, S_IRUSR | S_IWUSR, irq_get, irq_set);
+
+static ssize_t get_key(struct device *device,
+ struct device_attribute *attribute, char *buffer)
+{
+ struct fpc1020_data *fpc1020 = dev_get_drvdata(device);
+
+ return scnprintf(buffer, PAGE_SIZE, "%i\n", fpc1020->report_key);
+}
+
+static ssize_t set_key(struct device *device,
+ struct device_attribute *attribute,
+ const char *buffer, size_t count)
+{
+ int retval = 0;
+ u64 val;
+ struct fpc1020_data *fpc1020 = dev_get_drvdata(device);
+ bool home_pressed;
+
+ retval = kstrtou64(buffer, 0, &val);
+ if (!retval && !utouch_disable) {
+ if (val == KEY_HOME)
+ /* Convert to U-touch long press keyValue */
+ val = KEY_NAVI_LONG;
+
+ home_pressed = home_button_pressed();
+
+ if (val && home_pressed)
+ val = 0;
+
+ pr_info("home key pressed = %d\n", (int)home_pressed);
+ fpc1020->report_key = (int)val;
+ queue_work(fpc1020->fpc1020_wq, &fpc1020->input_report_work);
+
+ if (!val) {
+ pr_info("calling home key reset");
+ reset_home_button();
+ }
+ } else if (retval)
+ return -ENOENT;
+ return strnlen(buffer, count);
+}
+
+static DEVICE_ATTR(key, S_IRUSR | S_IWUSR, get_key, set_key);
+
+static ssize_t utouch_store_disable(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ int value;
+ if (1 != sscanf(buf, "%d", &value)) {
+ dev_err(dev, "Failed to parse integer: <%s>\n", buf);
+ return -EINVAL;
+ }
+ if (value == 1) {
+ utouch_disable = true;
+ pr_info("utouch disabled\n");
+ } else {
+ utouch_disable = false;
+ pr_info("utouch enabled\n");
+ }
+ return count;
+}
+
+static ssize_t utouch_show_disable(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ if (utouch_disable)
+ return sprintf(buf, "1\n");
+ else
+ return sprintf(buf, "0\n");
+}
+static DEVICE_ATTR(utouch_disable, S_IRUGO|S_IWUSR, utouch_show_disable, utouch_store_disable);
+
+static ssize_t enable_wakeup_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
+ char c;
+
+ c = fpc1020->wakeup_enabled ? '1' : '0';
+ return scnprintf(buf, PAGE_SIZE, "%c\n", c);
+}
+
+static ssize_t enable_wakeup_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
+ int i;
+
+ if (sscanf(buf, "%u", &i) == 1 && i < 2) {
+ fpc1020->wakeup_enabled = (i == 1);
+
+ dev_info(dev, "%s\n", i ? "wakeup enabled" : "wakeup disabled");
+ return count;
+ } else {
+ dev_info(dev, "%s: wakeup_enabled write error\n", __func__);
+ return -EINVAL;
+ }
+}
+static DEVICE_ATTR(enable_wakeup, S_IWUSR | S_IRUSR, enable_wakeup_show,
+ enable_wakeup_store);
+
+
+static ssize_t proximity_state_set(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
+ int rc, val;
+ rc = kstrtoint(buf, 10, &val);
+ if (rc)
+ return -EINVAL;
+ fpc1020->proximity_state = !!val;
+ if (!fpc1020->screen_on) {
+ if (fpc1020->proximity_state) {
+ /* Disable IRQ when screen is off and proximity sensor is covered */
+ config_irq(fpc1020, false);
+ } else if (fpc1020->wakeup_enabled) {
+ /* Enable IRQ when screen is off and proximity sensor is uncovered,
+ but only if fingerprint wake up is enabled */
+ config_irq(fpc1020, true);
+ }
+ }
+ return count;
+}
+static DEVICE_ATTR(proximity_state, S_IWUSR, NULL, proximity_state_set);
+
+static struct attribute *attributes[] = {
+ &dev_attr_irq.attr,
+ &dev_attr_key.attr,
+ &dev_attr_enable_wakeup.attr,
+ &dev_attr_proximity_state.attr,
+ &dev_attr_utouch_disable.attr,
+ NULL
+};
+
+static const struct attribute_group attribute_group = {
+ .attrs = attributes,
+};
+
+static void fpc1020_report_work_func(struct work_struct *work)
+{
+ struct fpc1020_data *fpc1020 = NULL;
+
+ fpc1020 = container_of(work, struct fpc1020_data, input_report_work);
+ if (fpc1020->screen_on) {
+ pr_info("Report key value = %d\n", (int)fpc1020->report_key);
+ input_report_key(fpc1020->input_dev, fpc1020->report_key, 1);
+ input_sync(fpc1020->input_dev);
+ msleep(30);
+ input_report_key(fpc1020->input_dev, fpc1020->report_key, 0);
+ input_sync(fpc1020->input_dev);
+ fpc1020->report_key = 0;
+ }
+}
+
+static void fpc1020_hw_reset(struct fpc1020_data *fpc1020)
+{
+ pr_info("HW reset\n");
+ gpio_set_value(fpc1020->reset_gpio, 1);
+ udelay(FPC1020_RESET_HIGH1_US);
+
+ gpio_set_value(fpc1020->reset_gpio, 0);
+ udelay(FPC1020_RESET_LOW_US);
+
+ gpio_set_value(fpc1020->reset_gpio, 1);
+ udelay(FPC1020_RESET_HIGH2_US);
+}
+
+static int fpc1020_get_pins(struct fpc1020_data *fpc1020)
+{
+ int retval = 0;
+ struct device_node *np = fpc1020->dev->of_node;
+
+ fpc1020->irq_gpio = of_get_named_gpio(np, "fpc,gpio_irq", 0);
+ if (!gpio_is_valid(fpc1020->irq_gpio)) {
+ pr_err("IRQ request failed.\n");
+ goto err;
+ }
+ fpc1020->reset_gpio = of_get_named_gpio(np, "fpc,gpio_reset", 0);
+ if (!gpio_is_valid(fpc1020->reset_gpio)) {
+ pr_err("RESET pin request failed\n");
+ goto err;
+ }
+
+ fpc1020->pin = pinctrl_get_select_default(fpc1020->dev);
+ if (IS_ERR_OR_NULL(fpc1020->pin)) {
+ pr_err("pinctrl get failed.\n");
+ goto err;
+ }
+
+ return 0;
+err:
+ pr_err("%s, err\n", __func__);
+ fpc1020->irq = -EINVAL;
+ fpc1020->irq_gpio = fpc1020->reset_gpio = -EINVAL;
+ retval = -ENODEV;
+ return retval;
+}
+
+static irqreturn_t fpc1020_irq_handler(int irq, void *_fpc1020)
+{
+ struct fpc1020_data *fpc1020 = _fpc1020;
+
+ pr_info("fpc1020 IRQ interrupt\n");
+ /* Make sure 'wakeup_enabled' is updated before using it
+ ** since this is interrupt context (other thread...) */
+ smp_rmb();
+ if (fpc1020->wakeup_enabled && !fpc1020->screen_on) {
+ pm_wakeup_event(fpc1020->dev, 5000);
+ }
+ sysfs_notify(&fpc1020->dev->kobj, NULL, dev_attr_irq.attr.name);
+ return IRQ_HANDLED;
+}
+
+static int fpc1020_initial_irq(struct fpc1020_data *fpc1020)
+{
+ int retval = 0;
+ int irqf;
+ if (!gpio_is_valid(fpc1020->irq_gpio)) {
+ pr_err("IRQ pin(%d) is not valid\n", fpc1020->irq_gpio);
+ return -EINVAL;
+ }
+
+ retval = gpio_request(fpc1020->irq_gpio, "fpc_irq");
+ if (retval) {
+ pr_err("IRQ(%d) request failed\n", fpc1020->irq_gpio);
+ return -EINVAL;
+ }
+
+ retval = gpio_direction_input(fpc1020->irq_gpio);
+ if (retval) {
+ pr_err("Set input(%d) failed\n", fpc1020->irq_gpio);
+ return -EINVAL;
+ }
+
+ fpc1020->irq = gpio_to_irq(fpc1020->irq_gpio);
+ if (fpc1020->irq < 0) {
+ pr_err("gpio_to_irq(%d) failed\n", fpc1020->irq_gpio);
+ return -EINVAL;
+ }
+
+ irqf = IRQF_TRIGGER_RISING | IRQF_ONESHOT;
+ if (of_property_read_bool(fpc1020->dev->of_node, "fpc,enable-wakeup")) {
+ irqf |= IRQF_NO_SUSPEND;
+ device_init_wakeup(fpc1020->dev, 1);
+ fpc1020->wakeup_enabled = 1;
+ }
+
+ retval = devm_request_threaded_irq(fpc1020->dev,
+ fpc1020->irq, NULL, fpc1020_irq_handler, irqf,
+ dev_name(fpc1020->dev), fpc1020);
+
+ if (retval) {
+ pr_err("request irq %i failed.\n", fpc1020->irq);
+ fpc1020->irq = -EINVAL;
+ return -EINVAL;
+ }
+
+ dev_info(fpc1020->dev, "requested irq %d\n", fpc1020->irq);
+ /* Request that the interrupt should be wakeable*/
+ if (fpc1020->wakeup_enabled) {
+ enable_irq_wake(fpc1020->irq);
+ }
+ fpc1020->irq_enabled = true;
+
+ return 0;
+}
+
+static int fpc1020_manage_sysfs(struct fpc1020_data *fpc1020)
+{
+ int retval = 0;
+
+ retval = sysfs_create_group(&fpc1020->dev->kobj, &attribute_group);
+ if (retval) {
+ pr_err("Could not create sysfs\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int fpc1020_alloc_input_dev(struct fpc1020_data *fpc1020)
+{
+ int retval = 0;
+
+ fpc1020->input_dev = input_allocate_device();
+ if (!fpc1020->input_dev) {
+ pr_info("Input allocate device failed\n");
+ retval = -ENOMEM;
+ return retval;
+ }
+
+ fpc1020->input_dev->name = "fpc1020tp";
+ set_bit(EV_KEY, fpc1020->input_dev->evbit);
+ set_bit(KEY_BACK, fpc1020->input_dev->keybit);
+ set_bit(KEY_LEFT, fpc1020->input_dev->keybit);
+ set_bit(KEY_RIGHT, fpc1020->input_dev->keybit);
+ set_bit(KEY_NAVI_LONG, fpc1020->input_dev->keybit);
+ input_set_capability(fpc1020->input_dev, EV_KEY, KEY_NAVI_LEFT);
+ input_set_capability(fpc1020->input_dev, EV_KEY, KEY_NAVI_RIGHT);
+ input_set_capability(fpc1020->input_dev, EV_KEY, KEY_BACK);
+ input_set_capability(fpc1020->input_dev, EV_KEY, KEY_NAVI_LONG);
+
+ /* Register the input device */
+ retval = input_register_device(fpc1020->input_dev);
+ if (retval) {
+ pr_err("Input_register_device failed.\n");
+ input_free_device(fpc1020->input_dev);
+ fpc1020->input_dev = NULL;
+ }
+ return retval;
+}
+
+static void set_fingerprintd_nice(int nice)
+{
+ struct task_struct *p;
+
+ read_lock(&tasklist_lock);
+ for_each_process(p) {
+ if (!memcmp(p->comm, "fingerprint@2.1", 16)) {
+ pr_debug("fingerprint nice changed to %i\n", nice);
+ set_user_nice(p, nice);
+ break;
+ }
+ }
+ read_unlock(&tasklist_lock);
+}
+
+static void fpc1020_suspend_resume(struct work_struct *work)
+{
+ struct fpc1020_data *fpc1020 =
+ container_of(work, typeof(*fpc1020), pm_work);
+
+ /* Escalate fingerprintd priority when screen is off */
+ if (!fpc1020->screen_on)
+ set_fingerprintd_nice(MIN_NICE);
+ else
+ set_fingerprintd_nice(0);
+}
+
+static int fb_notifier_callback(struct notifier_block *self,
+ unsigned long event, void *data)
+{
+ int *blank;
+ struct fb_event *evdata = data;
+
+ struct fpc1020_data *fpc1020 = container_of(self, struct fpc1020_data, fb_notif);
+ blank = evdata->data;
+ if (evdata && evdata->data && event == FB_EVENT_BLANK && fpc1020) {
+ blank = evdata->data;
+ if (*blank == FB_BLANK_UNBLANK) {
+ pr_debug("ScreenOn\n");
+ fpc1020->screen_on = 1;
+ queue_work(fpc1020->fpc1020_wq, &fpc1020->pm_work);
+ /* Unconditionally enable IRQ when screen turns on */
+ config_irq(fpc1020, true);
+ } else if (*blank == FB_BLANK_POWERDOWN) {
+ pr_debug("ScreenOff\n");
+ fpc1020->screen_on = 0;
+ if (!fpc1020->wakeup_enabled)
+ config_irq(fpc1020, false);
+ queue_work(fpc1020->fpc1020_wq, &fpc1020->pm_work);
+ }
+ }
+ return 0;
+}
+
+static int fpc1020_probe(struct platform_device *pdev)
+{
+ int retval = 0;
+ struct device *dev = &pdev->dev;
+
+ struct fpc1020_data *fpc1020 = devm_kzalloc(dev,
+ sizeof(struct fpc1020_data), GFP_KERNEL);
+ if (fpc1020 == NULL) {
+ pr_err("fpc1020 allocation error\n");
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ fpc1020->dev = &pdev->dev;
+ dev_set_drvdata(dev, fpc1020);
+
+ retval = fpc1020_get_pins(fpc1020);
+ if (retval != 0) {
+ pr_err("Get pins failed\n");
+ goto error;
+ }
+
+ fpc1020->wakeup_enabled = 0;
+
+ /*create sfs nodes*/
+ retval = fpc1020_manage_sysfs(fpc1020);
+ if (retval != 0) {
+ pr_err("Create sysfs nodes failed\n");
+ goto error;
+ }
+
+ /*create input device for navigation*/
+ retval = fpc1020_alloc_input_dev(fpc1020);
+ if (retval != 0) {
+ pr_err("Allocate input device failed\n");
+ goto error_remove_sysfs;
+ }
+
+ fpc1020->fpc1020_wq = alloc_workqueue("fpc1020_wq", WQ_HIGHPRI, 1);
+ if (!fpc1020->fpc1020_wq) {
+ pr_err("Create input workqueue failed\n");
+ goto error_unregister_device;
+ }
+ INIT_WORK(&fpc1020->input_report_work, fpc1020_report_work_func);
+ INIT_WORK(&fpc1020->pm_work, fpc1020_suspend_resume);
+ gpio_direction_output(fpc1020->reset_gpio, 1);
+ /*Do HW reset*/
+ fpc1020_hw_reset(fpc1020);
+
+ fpc1020->fb_notif.notifier_call = fb_notifier_callback;
+ retval = fb_register_client(&fpc1020->fb_notif);
+ if (retval) {
+ pr_err("Unable to register fb_notifier : %d\n", retval);
+ goto error_destroy_workqueue;
+ }
+
+ device_init_wakeup(dev, true);
+
+ retval = fpc1020_initial_irq(fpc1020);
+ if (retval != 0) {
+ pr_err("IRQ initialized failure\n");
+ goto error_unregister_client;
+ }
+
+ /* Disable IRQ */
+ disable_irq(fpc1020->irq);
+ /* Enable irq wake */
+ enable_irq_wake(fpc1020->irq);
+ return 0;
+
+error_unregister_client:
+ fb_unregister_client(&fpc1020->fb_notif);
+
+error_destroy_workqueue:
+ destroy_workqueue(fpc1020->fpc1020_wq);
+
+error_unregister_device:
+ input_unregister_device(fpc1020->input_dev);
+
+error_remove_sysfs:
+ sysfs_remove_group(&fpc1020->dev->kobj, &attribute_group);
+
+error:
+ if (fpc1020 != NULL)
+ kzfree(fpc1020);
+
+ return retval;
+}
+
+static int fpc1020_remove(struct platform_device *pdev)
+{
+ int retval = 0;
+ return retval;
+}
+
+static struct of_device_id fpc1020_match[] = {
+ {
+ .compatible = "fpc,fpc1020",
+ },
+ {}
+};
+
+static struct platform_driver fpc1020_plat_driver = {
+ .probe = fpc1020_probe,
+ .remove = fpc1020_remove,
+ .driver = {
+ .name = "fpc1020",
+ .owner = THIS_MODULE,
+ .of_match_table = fpc1020_match,
+ },
+};
+
+static int fpc1020_init(void)
+{
+ return platform_driver_register(&fpc1020_plat_driver);
+}
+
+static void fpc1020_exit(void)
+{
+ platform_driver_unregister(&fpc1020_plat_driver);
+}
+
+module_init(fpc1020_init);
+module_exit(fpc1020_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("ZUK ShenQi <lvxin1@zuk.com>");
+MODULE_DESCRIPTION("FPC1020 fingerprint sensor ree driver.");