指纹识别驱动
/*
* FPC1020 fingerprint sensor device driver
*
* This driver will control the platform resources that the FPC fingerprint
* sensor needs to operate. The major things are probing the sensor to check
* that it is actually connected and let the Kernel know this and with that also
* enabling and disabling of regulators, enabling and disabling of platform
* clocks, controlling GPiOS such as SPI chip select, sensor reset line, sensor
* IRQ line, MISO and MOSI lines.
*
* The driver will expose most of its available functionality in sysfs which
* enables dynamic control of these features from eg. a user space process.
*
* The sensor's IRQ events will be pushed to Kernel's event handling system and
* are exposed in the drivers event node. This makes it possible for a user
* space process to poll the input node and receive IRQ events easily. Usually
* this node is available under /dev/input/eventX where 'X' is a number given by
* the event system. A user space process will need to traverse all the event
* nodes and ask for its parent's name (through EVIOCGNAME) which should match
* the value in device tree named input-device-name.
*
* This driver will NOT send any SPI commands to the sensor it only controls the
* electrical parts.
*
*
* Copyright (c) 2015 Fingerprint Cards AB <[email protected]>
*
* 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.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/regulator/consumer.h>
#include <soc/qcom/scm.h>
#include <linux/wakelock.h>
#include <linux/input.h>
#ifdef CONFIG_FB
#include <linux/fb.h>
#include <linux/notifier.h>
#endif
#include <linux/project_info.h>
static unsigned int ignor_home_for_ESD = 0;
module_param(ignor_home_for_ESD, uint, S_IRUGO | S_IWUSR);
#define FPC1020_RESET_LOW_US 1000
#define FPC1020_RESET_HIGH1_US 100
#define FPC1020_RESET_HIGH2_US 1250
#define FPC_TTW_HOLD_TIME 1000
/* Unused key value to avoid interfering with active keys */
#define KEY_FINGERPRINT 0x2ee
#define ONEPLUS_EDIT //Onplus modify for msm8996 platform and 15801 HW
struct fpc1020_data {
struct device *dev;
struct wake_lock ttw_wl;
int irq_gpio;
int rst_gpio;
int irq_num;
struct mutex lock;
bool prepared;
struct pinctrl *ts_pinctrl;
struct pinctrl_state *gpio_state_active;
struct pinctrl_state *gpio_state_suspend;
#ifdef ONEPLUS_EDIT
int EN_VDD_gpio;
int id0_gpio;
int id1_gpio;
int id2_gpio;
struct input_dev *input_dev;
int screen_state;//1: on 0:off
int sensor_version;//0x01:fpc1245 0x02:fpc1263
#endif
#if defined(CONFIG_FB)
struct notifier_block fb_notif;
#endif
struct work_struct pm_work;
int proximity_state; /* 0:far 1:near */
bool irq_enabled;
spinlock_t irq_lock;
struct completion irq_sent;
};
static int fpc1020_request_named_gpio(struct fpc1020_data *fpc1020,
const char *label, int *gpio)
{
struct device *dev = fpc1020->dev;
struct device_node *np = dev->of_node;
int rc = of_get_named_gpio(np, label, 0);
if (rc < 0) {
dev_err(dev, "failed to get '%s'\n", label);
*gpio = rc;
return rc;
}
*gpio = rc;
rc = devm_gpio_request(dev, *gpio, label);
if (rc) {
dev_err(dev, "failed to request gpio %d\n", *gpio);
return rc;
}
dev_info(dev, "%s - gpio: %d\n", label, *gpio);
return 0;
}
#ifndef ONEPLUS_EDIT
/* -------------------------------------------------------------------- */
static int fpc1020_pinctrl_init(struct fpc1020_data *fpc1020)
{
int ret = 0;
struct device *dev = fpc1020->dev;
fpc1020->ts_pinctrl = devm_pinctrl_get(dev);
if (IS_ERR_OR_NULL(fpc1020->ts_pinctrl)) {
dev_err(dev, "Target does not use pinctrl\n");
ret = PTR_ERR(fpc1020->ts_pinctrl);
goto err;
}
fpc1020->gpio_state_active =
pinctrl_lookup_state(fpc1020->ts_pinctrl, "pmx_fp_active");
if (IS_ERR_OR_NULL(fpc1020->gpio_state_active)) {
dev_err(dev, "cannot get active pinstate\n");
ret = PTR_ERR(fpc1020->gpio_state_active);
goto err;
}
fpc1020->gpio_state_suspend =
pinctrl_lookup_state(fpc1020->ts_pinctrl, "pmx_fp_suspend");
if (IS_ERR_OR_NULL(fpc1020->gpio_state_suspend)) {
dev_err(dev, "Cannot get sleep pinstate\n");
ret = PTR_ERR(fpc1020->gpio_state_suspend);
goto err;
}
return 0;
err:
fpc1020->ts_pinctrl = NULL;
fpc1020->gpio_state_active = NULL;
fpc1020->gpio_state_suspend = NULL;
return ret;
}
/* -------------------------------------------------------------------- */
static int fpc1020_pinctrl_select(struct fpc1020_data *fpc1020, bool on)
{
int ret = 0;
struct pinctrl_state *pins_state;
struct device *dev = fpc1020->dev;
pins_state = on ? fpc1020->gpio_state_active : fpc1020->gpio_state_suspend;
if (!IS_ERR_OR_NULL(pins_state)) {
ret = pinctrl_select_state(fpc1020->ts_pinctrl, pins_state);
if (ret) {
dev_err(dev, "can not set %s pins\n",
on ? "pmx_ts_active" : "pmx_ts_suspend");
return ret;
}
} else {
dev_err(dev, "not a valid '%s' pinstate\n",
on ? "pmx_ts_active" : "pmx_ts_suspend");
}
return ret;
}
#endif
/*
static int hw_reset(struct fpc1020_data *fpc1020)
{
int irq_gpio;
struct device *dev = fpc1020->dev;
int rc = select_pin_ctl(fpc1020, "fpc1020_reset_active");
if (rc)
goto exit;
usleep_range(FPC1020_RESET_HIGH1_US, FPC1020_RESET_HIGH1_US + 100);
rc = select_pin_ctl(fpc1020, "fpc1020_reset_reset");
if (rc)
goto exit;
usleep_range(FPC1020_RESET_LOW_US, FPC1020_RESET_LOW_US + 100);
rc = select_pin_ctl(fpc1020, "fpc1020_reset_active");
if (rc)
goto exit;
usleep_range(FPC1020_RESET_HIGH1_US, FPC1020_RESET_HIGH1_US + 100);
irq_gpio = gpio_get_value(fpc1020->irq_gpio);
dev_info(dev, "IRQ after reset %d\n", irq_gpio);
exit:
return rc;
}
static int hw_reset(struct fpc1020_data *fpc1020)
{
int irq_gpio;
struct device *dev = fpc1020->dev;
int counter = 2;
gpio_set_value(fpc1020->EN_VDD_gpio, 0);
mdelay(3);
gpio_set_value(fpc1020->EN_VDD_gpio, 1);
mdelay(3);
//gpio_direction_output(fpc1020->EN_VDD_gpio,1);
while (counter) {
counter--;
gpio_set_value(fpc1020->rst_gpio, 1);
udelay(FPC1020_RESET_HIGH1_US);
gpio_set_value(fpc1020->rst_gpio, 0);
udelay(FPC1020_RESET_LOW_US);
gpio_set_value(fpc1020->rst_gpio, 1);
udelay(FPC1020_RESET_HIGH2_US);
irq_gpio = gpio_get_value(fpc1020->irq_gpio);
dev_err(dev, "IRQ after reset %d\n", irq_gpio);
if (irq_gpio) {
//printk(KERN_INFO "%s OK !\n", __func__);
counter = 0;
} else {
dev_err(dev, "%s timed out,retrying ...\n",
__func__);
udelay(1250);
}
}
return 0;
}
static ssize_t hw_reset_set(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int rc;
struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
if (!strncmp(buf, "reset", strlen("reset")))
rc = hw_reset(fpc1020);
else
return -EINVAL;
return rc ? rc : count;
}
static DEVICE_ATTR(hw_reset, S_IWUSR, NULL, hw_reset_set);
*/
/**
* sysf node to check the interrupt status of the sensor, the interrupt
* handler should perform sysf_notify to allow userland to poll the node.
*/
static ssize_t irq_get(struct device* device,
struct device_attribute* attribute,
char* buffer)
{
struct fpc1020_data* fpc1020 = dev_get_drvdata(device);
bool irq_enabled;
int irq;
ssize_t count;
spin_lock(&fpc1020->irq_lock);
irq_enabled = fpc1020->irq_enabled;
spin_unlock(&fpc1020->irq_lock);
irq = irq_enabled && gpio_get_value(fpc1020->irq_gpio);
count = scnprintf(buffer, PAGE_SIZE, "%i\n", irq);
complete(&fpc1020->irq_sent);
return count;
}
/**
* writing to the irq node will just drop a printk message
* and return success, used for latency measurement.
*/
static ssize_t irq_ack(struct device* device,
struct device_attribute* attribute,
const char* buffer, size_t count)
{
struct fpc1020_data* fpc1020 = dev_get_drvdata(device);
dev_dbg(fpc1020->dev, "%s\n", __func__);
return count;
}
static DEVICE_ATTR(irq, S_IRUSR | S_IWUSR, irq_get, irq_ack);
//liuyan not merge now
/*#ifdef VENDOR_EDIT //WayneChang, 2015/12/02, add for key to abs, simulate key in abs through virtual key system
extern void int_touch(void);
extern struct completion key_cm;
extern bool virtual_key_enable;
bool key_home_pressed = false;
export_symbol(key_home_pressed);
#endif*/
static void set_fpc_irq(struct fpc1020_data *fpc1020, bool enable)
{
bool irq_enabled;
spin_lock(&fpc1020->irq_lock);
irq_enabled = fpc1020->irq_enabled;
fpc1020->irq_enabled = enable;
spin_unlock(&fpc1020->irq_lock);
if (enable == irq_enabled)
return;
if (enable)
enable_irq(gpio_to_irq(fpc1020->irq_gpio));
else
disable_irq(gpio_to_irq(fpc1020->irq_gpio));
}
static ssize_t report_home_set(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
//unsigned long time;
if(ignor_home_for_ESD)
return -EINVAL;
if (!strncmp(buf, "down", strlen("down")))
{
/*#ifdef VENDOR_EDIT //WayneChang, 2015/12/02, add for key to abs, simulate key in abs through virtual key system
if(virtual_key_enable){
key_home_pressed = true;
}else{*/
input_report_key(fpc1020->input_dev,
KEY_HOME, 1);
input_sync(fpc1020->input_dev);
/* }
#endif*/
}
else if (!strncmp(buf, "up", strlen("up")))
{
/*#ifdef VENDOR_EDIT //WayneChang, 2015/12/02, add for key to abs, simulate key in abs through virtual key system
if(virtual_key_enable){
key_home_pressed = false;
}else{*/
input_report_key(fpc1020->input_dev,
KEY_HOME, 0);
input_sync(fpc1020->input_dev);
/* }
#endif*/
}
else if (!strncmp(buf, "timeout", strlen("timeout")))
{
input_report_key(fpc1020->input_dev,KEY_F2,1);
input_sync(fpc1020->input_dev);
input_report_key(fpc1020->input_dev,KEY_F2,0);
input_sync(fpc1020->input_dev);
}
else
return -EINVAL;
/*#ifdef VENDOR_EDIT //WayneChang, 2015/12/02, add for key to abs, simulate key in abs through virtual key system
if(virtual_key_enable){
if(!key_home_pressed){
reinit_completion(&key_cm);
time = wait_for_completion_timeout(&key_cm,msecs_to_jiffies(60));
if (!time)
int_touch();
}else{
int_touch();
}
}
#endif*/
return count;
}
static DEVICE_ATTR(report_home, S_IWUSR, NULL, report_home_set);
static ssize_t update_info_set(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
//struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
if (!strncmp(buf, "n", strlen("n")))
{
push_component_info(FINGERPRINTS,"N/A" , "N/A");
}
return count;
}
static DEVICE_ATTR(update_info, S_IWUSR, NULL, update_info_set);
static ssize_t screen_state_get(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->screen_state);
}
static DEVICE_ATTR(screen_state, S_IRUSR , screen_state_get, NULL);
static ssize_t sensor_version_get(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->sensor_version);
}
static DEVICE_ATTR(sensor_version, S_IRUSR , sensor_version_get, NULL);
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_state)
set_fpc_irq(fpc1020, !fpc1020->proximity_state);
return count;
}
static DEVICE_ATTR(proximity_state, S_IWUSR, NULL, proximity_state_set);
static struct attribute *attributes[] = {
//&dev_attr_hw_reset.attr,
&dev_attr_irq.attr,
&dev_attr_report_home.attr,
&dev_attr_update_info.attr,
&dev_attr_screen_state.attr,
&dev_attr_sensor_version.attr,
&dev_attr_proximity_state.attr,
NULL
};
static const struct attribute_group attribute_group = {
.attrs = attributes,
};
int fpc1020_input_init(struct fpc1020_data *fpc1020)
{
int ERROR = 0;
dev_dbg(fpc1020->dev, "%s\n", __func__);
fpc1020->input_dev = input_allocate_device();
if (!fpc1020->input_dev) {
dev_err(fpc1020->dev, "Input_allocate_device failed.\n");
error = -ENOMEM;
}
if (!error) {
fpc1020->input_dev->name = "fpc1020";
/* Set event bits according to what events we are generating */
set_bit(EV_KEY, fpc1020->input_dev->evbit);
set_bit(KEY_POWER, fpc1020->input_dev->keybit);
set_bit(KEY_F2, fpc1020->input_dev->keybit);
set_bit(KEY_HOME, fpc1020->input_dev->keybit);
set_bit(KEY_FINGERPRINT, fpc1020->input_dev->keybit);
/* Register the input device */
error = input_register_device(fpc1020->input_dev);
if (error) {
dev_err(fpc1020->dev, "Input_register_device failed.\n");
input_free_device(fpc1020->input_dev);
fpc1020->input_dev = NULL;
}
}
return error;
}
/* -------------------------------------------------------------------- */
void fpc1020_input_destroy(struct fpc1020_data *fpc1020)
{
dev_dbg(fpc1020->dev, "%s\n", __func__);
if (fpc1020->input_dev != NULL)
input_free_device(fpc1020->input_dev);
}
static void set_fingerprintd_nice(int nice)
{
struct task_struct *p;
read_lock(&tasklist_lock);
for_each_process(p) {
if (!memcmp(p->comm, "fingerprintd", 13)) {
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);
if (fpc1020->screen_state) {
set_fpc_irq(fpc1020, true);
set_fingerprintd_nice(0);
} else {
/*
* Elevate fingerprintd priority when screen is off to ensure
* the fingerprint sensor is responsive and that the haptic
* response on successful verification always fires.
*/
set_fingerprintd_nice(-1);
}
sysfs_notify(&fpc1020->dev->kobj, NULL,
dev_attr_screen_state.attr.name);
}
#if defined(CONFIG_FB)
static int fb_notifier_callback(struct notifier_block *self, unsigned long event, void *data)
{
struct fpc1020_data *fpc1020 = container_of(self, struct fpc1020_data, fb_notif);
struct fb_event *evdata = data;
int *blank = evdata->data;
if (event != FB_early_EVENT_BLANK)
return 0;
if (*blank == FB_BLANK_UNBLANK) {
fpc1020->screen_state = 1;
queue_work(system_highpri_wq, &fpc1020->pm_work);
} else if (*blank == FB_BLANK_POWERDOWN) {
fpc1020->screen_state = 0;
queue_work(system_highpri_wq, &fpc1020->pm_work);
}
return 0;
}
#endif
static irqreturn_t fpc1020_irq_handler(int irq, void *handle)
{
struct fpc1020_data *fpc1020 = handle;
sysfs_notify(&fpc1020->dev->kobj, NULL, dev_attr_irq.attr.name);
reinit_completion(&fpc1020->irq_sent);
wait_for_completion_timeout(&fpc1020->irq_sent, msecs_to_jiffies(100));
if (fpc1020->screen_state)
return IRQ_HANDLED;
wake_lock_timeout(&fpc1020->ttw_wl, msecs_to_jiffies(FPC_TTW_HOLD_TIME));
/* Report button input to trigger cpu boost */
input_report_key(fpc1020->input_dev, KEY_FINGERPRINT, 1);
input_sync(fpc1020->input_dev);
input_report_key(fpc1020->input_dev, KEY_FINGERPRINT, 0);
input_sync(fpc1020->input_dev);
return IRQ_HANDLED;
}
static int fpc1020_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int rc = 0;
unsigned long irqf;
int id0, id1, id2;
struct device_node *np = dev->of_node;
struct fpc1020_data *fpc1020 = devm_kzalloc(dev, sizeof(*fpc1020),
GFP_KERNEL);
if (!fpc1020) {
dev_err(dev,
"failed to allocate memory for struct fpc1020_data\n");
rc = -ENOMEM;
goto exit;
}
printk(KERN_INFO "%s\n", __func__);
fpc1020->dev = dev;
dev_set_drvdata(dev, fpc1020);
if (!np) {
dev_err(dev, "no of node found\n");
rc = -EINVAL;
goto exit;
}
rc = fpc1020_request_named_gpio(fpc1020, "fpc,irq-gpio",
&fpc1020->irq_gpio);
if (rc)
goto exit;
rc = gpio_direction_input(fpc1020->irq_gpio);
if (rc) {
dev_err(fpc1020->dev,
"gpio_direction_input (irq) failed.\n");
goto exit;
}
/* in tz
rc = fpc1020_request_named_gpio(fpc1020, "fpc,reset-gpio",
&fpc1020->rst_gpio);
if (rc)
goto exit;*/
#ifdef ONEPLUS_EDIT
rc = fpc1020_request_named_gpio(fpc1020, "fpc,gpio_id0",
&fpc1020->id0_gpio);
if(gpio_is_valid(fpc1020->id0_gpio))
{
dev_err(dev, "%s: gpio_is_valid(fpc1020->id0_gpio=%d)\n", __func__,fpc1020->id0_gpio);
gpio_direction_input(fpc1020->id0_gpio);
}
rc = fpc1020_request_named_gpio(fpc1020, "fpc,gpio_id1",
&fpc1020->id1_gpio);
if(gpio_is_valid(fpc1020->id1_gpio))
{
dev_err(dev, "%s: gpio_is_valid(fpc1020->id1_gpio=%d)\n", __func__,fpc1020->id1_gpio);
gpio_direction_input(fpc1020->id1_gpio);
}
rc = fpc1020_request_named_gpio(fpc1020, "fpc,gpio_id2",
&fpc1020->id2_gpio);
if(gpio_is_valid(fpc1020->id2_gpio))
{
dev_err(dev, "%s: gpio_is_valid(fpc1020->id2_gpio=%d)\n", __func__,fpc1020->id2_gpio);
gpio_direction_input(fpc1020->id2_gpio);
}
/* in xbl
rc = fpc1020_request_named_gpio(fpc1020, "fpc,gpio_1V8_EN",
&fpc1020->EN_VDD_gpio);
if (rc)
goto exit;
gpio_direction_output(fpc1020->EN_VDD_gpio,1);*/
#else
rc = fpc1020_pinctrl_init(fpc1020);
if (rc)
goto exit;
rc = fpc1020_pinctrl_select(fpc1020, true);
if (rc)
goto exit;
#endif
rc = fpc1020_input_init(fpc1020);
if (rc)
goto exit;
INIT_WORK(&fpc1020->pm_work, fpc1020_suspend_resume);
#if defined(CONFIG_FB)
fpc1020->fb_notif.notifier_call = fb_notifier_callback;
rc = fb_register_client(&fpc1020->fb_notif);
if(rc)
dev_err(fpc1020->dev, "Unable to register fb_notifier: %d\n", rc);
fpc1020->screen_state = 1;
#endif
spin_lock_init(&fpc1020->irq_lock);
fpc1020->irq_enabled = true;
irqf = IRQF_TRIGGER_RISING | IRQF_ONESHOT;
mutex_init(&fpc1020->lock);
init_completion(&fpc1020->irq_sent);
rc = devm_request_threaded_irq(dev, gpio_to_irq(fpc1020->irq_gpio),
NULL, fpc1020_irq_handler, irqf,
dev_name(dev), fpc1020);
if (rc) {
dev_err(dev, "could not request irq %d\n",
gpio_to_irq(fpc1020->irq_gpio));
goto exit;
}
dev_info(dev, "requested irq %d\n", gpio_to_irq(fpc1020->irq_gpio));
/* Request that the interrupt should not be wakeable */
//disable_irq_wake( gpio_to_irq( fpc1020->irq_gpio ) );
enable_irq_wake( gpio_to_irq( fpc1020->irq_gpio ) );
wake_lock_init(&fpc1020->ttw_wl, WAKE_LOCK_SUSPEND, "fpc_ttw_wl");
device_init_wakeup(fpc1020->dev, 1);
rc = sysfs_create_group(&dev->kobj, &attribute_group);
if (rc) {
dev_err(dev, "could not create sysfs\n");
goto exit;
}
#if 0 //changhua remove HW reset here,move to HAL,after spi cs pin become high
rc = gpio_direction_output(fpc1020->rst_gpio, 1);
if (rc) {
dev_err(fpc1020->dev,
"gpio_direction_output (reset) failed.\n");
goto exit;
}
gpio_set_value(fpc1020->rst_gpio, 1);
udelay(FPC1020_RESET_HIGH1_US);
gpio_set_value(fpc1020->rst_gpio, 0);
udelay(FPC1020_RESET_LOW_US);
gpio_set_value(fpc1020->rst_gpio, 1);
udelay(FPC1020_RESET_HIGH2_US);
#endif
/**
* ID0(GPIO39) ID1(GPIO41) ID1(GPIO63)
* fpc1245
* O-film 1 1 1
* Primax 1 0 0
* truly 0 0 1
*
* fpc1263
* O-film 1 1 0
* Primax 0 0 0
* truly 0 1 1
*fingerchip/
* qtech 0 1 0
* Goodix 1 0 1
*
*/
fpc1020->sensor_version = 0x02;
id0 = gpio_get_value(fpc1020->id0_gpio);
id1 = gpio_get_value(fpc1020->id1_gpio);
id2 = gpio_get_value(fpc1020->id2_gpio);
if (id0 && id1 && id2) {
push_component_info(FINGERPRINTS, "fpc1245", "FPC(OF)");
fpc1020->sensor_version = 0x01;
} else if (id0 && !id1 && !id2) {
push_component_info(FINGERPRINTS, "fpc1245", "FPC(Primax)");
fpc1020->sensor_version = 0x01;
} else if (!id0 && !id1 && id2) {
push_component_info(FINGERPRINTS, "fpc1245", "FPC(truly)");
fpc1020->sensor_version = 0x01;
} else if (id0 && id1 && !id2) {
push_component_info(FINGERPRINTS, "fpc1263", "FPC(OF)");
} else if (!id0 && !id1 && !id2) {
push_component_info(FINGERPRINTS, "fpc1263", "FPC(Primax)");
} else if (!id0 && id1 && id2) {
push_component_info(FINGERPRINTS, "fpc1263", "FPC(truly)");
} else if (!id0 && id1 && !id2) {
push_component_info(FINGERPRINTS, "fpc1263", "FPC(f/p)");
} else if (id0 && !id1 && id2) {
push_component_info(FINGERPRINTS, "fpc1263", "FPC(Goodix)");
} else {
push_component_info(FINGERPRINTS, "fpc", "PC");
}
dev_info(dev, "%s: ok\n", __func__);
exit:
return rc;
}
static struct of_device_id fpc1020_of_match[] = {
{ .compatible = "fpc,fpc1020", },
{}
};
module_device_table(of, fpc1020_of_match);
static struct platform_driver fpc1020_driver = {
.driver = {
.name = "fpc1020",
.owner = THIS_MODULE,
.of_match_table = fpc1020_of_match,
},
.probe = fpc1020_probe,
};
module_platform_driver(fpc1020_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Aleksej Makarov");
MODULE_AUTHOR("Henrik Tillman <[email protected]>");
MODULE_DESCRIPTION("FPC1020 Fingerprint sensor device driver.");
相关阅读
一、修改activity_main.xml布局文件 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:
本特利内华达* Seismoprobe速度传感器系统设计用于测量绝对(相对于自由空间)轴承箱,套管或结构振动。双线系统由换能器和适当的电缆
ARM Cortex-A系列(A53、A57、A73等)处理器性能分类与对
在如今这个电子产品泛滥的年代,仅仅靠品牌或是外观已经不足以辨别产品的优劣,其内置的处理器自然也就成为了分辨产品是否高端的标准
最近有网友问小编这样一个问题:SDM439是什么CPU?对于SDM439处理器,小编也是第一次听说,根据网友的描述,在vivo官网的vivo U1参数中,处理
君正X1000芯片是针对语音识别功能做了专门定制的芯片,可以支持4个MIC,支持远场唤醒,功耗很低,适用于物联网、智能家居、智能音频、智