| /* |
| * Copyright (C) 2018, 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/device.h> |
| #include <linux/mutex.h> |
| #include <linux/io.h> |
| #include <linux/gpio.h> |
| #include <linux/fb.h> |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| #include <linux/earlysuspend.h> |
| #else |
| #include <linux/notifier.h> |
| #endif |
| |
| #ifdef CONFIG_OF |
| #include <linux/of.h> |
| #include <linux/of_irq.h> |
| #include <linux/of_platform.h> |
| #endif |
| |
| #ifdef CONFIG_COMPAT |
| #include <linux/compat.h> |
| #endif |
| |
| #include <linux/clk.h> |
| #include <net/sock.h> |
| #include <linux/spi/spi.h> |
| #include <linux/spi/spidev.h> |
| |
| #include "gf_common.h" |
| #include "fingerprint.h" |
| |
| #define GF_DEV_NAME "goodix_fp" |
| #define GF_DEV_MAJOR 0 /* assigned */ |
| #define GF_CLASS_NAME "goodix_fp" |
| #define GF_NETLINK_ROUTE 25 |
| #define MAX_NL_MSG_LEN 16 |
| #define WAKELOCK_HOLD_TIME 500 /* in ms */ |
| |
| static LIST_HEAD(device_list); |
| static DEFINE_MUTEX(device_list_lock); |
| |
| static unsigned int bufsiz = (50 * 1024); |
| module_param(bufsiz, uint, S_IRUGO); |
| MODULE_PARM_DESC(bufsiz, "maximum data bytes for SPI message"); |
| |
| #ifdef CONFIG_OF |
| static const struct of_device_id gfspi_of_match[] = { |
| { .compatible = "goodix,fingerprint", }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, gfspi_of_match); |
| #endif |
| |
| extern int fingerprint_register(struct device *dev, void *drvdata, |
| struct device_attribute *attributes[], char *name); |
| extern void fingerprint_unregister(struct device *dev, |
| struct device_attribute *attributes[]); |
| |
| static struct gf_device *g_data; |
| |
| #ifdef ENABLE_SENSORS_FPRINT_SECURE |
| #if !defined(CONFIG_SENSORS_ET5XX) && !defined(CONFIG_SENSORS_VFS8XXX) |
| int fpsensor_goto_suspend = 0; |
| #endif |
| #endif |
| |
| #if defined(ENABLE_SENSORS_FPRINT_SECURE) |
| int fps_resume_set(void){ |
| int ret =0; |
| |
| if (fpsensor_goto_suspend) { |
| fpsensor_goto_suspend = 0; |
| #if defined(CONFIG_TZDEV) |
| if (!g_data->ldo_onoff) { |
| ret = exynos_smc(FP_CSMC_HANDLER_ID, FP_HANDLER_MAIN, FP_SET_POWEROFF, 0); |
| pr_info("gfspi %s: FP_SET_POWEROFF ret = %d\n", __func__, ret); |
| } else { |
| ret = exynos_smc(FP_CSMC_HANDLER_ID, FP_HANDLER_MAIN, FP_SET_POWERON_INACTIVE, 0); |
| pr_info("gfspi %s: FP_SET_POWERON_INACTIVE ret = %d\n", __func__, ret); |
| } |
| #else |
| ret = exynos_smc(MC_FC_FP_PM_RESUME, 0, 0, 0); |
| pr_info("gfspi %s : smc ret = %d\n", __func__, ret); |
| #endif |
| } |
| return ret; |
| } |
| #endif |
| |
| static ssize_t gfspi_bfs_values_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct gf_device *data = dev_get_drvdata(dev); |
| |
| return snprintf(buf, PAGE_SIZE, "\"FP_SPICLK\":\"%d\"\n", |
| data->spi->max_speed_hz); |
| } |
| |
| static ssize_t gfspi_type_check_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct gf_device *data = dev_get_drvdata(dev); |
| |
| return snprintf(buf, PAGE_SIZE, "%d\n", data->sensortype); |
| } |
| |
| static ssize_t gfspi_vendor_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return snprintf(buf, PAGE_SIZE, "%s\n", "GOODIX"); |
| } |
| |
| static ssize_t gfspi_name_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct gf_device *data = dev_get_drvdata(dev); |
| |
| return snprintf(buf, PAGE_SIZE, "%s\n", data->chipid); |
| } |
| |
| static ssize_t gfspi_adm_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return snprintf(buf, PAGE_SIZE, "%d\n", DETECT_ADM); |
| } |
| |
| static ssize_t gfspi_intcnt_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct gf_device *data = dev_get_drvdata(dev); |
| return snprintf(buf, PAGE_SIZE, "%d\n", data->interrupt_count); |
| } |
| |
| static ssize_t gfspi_intcnt_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, |
| size_t size) |
| { |
| struct gf_device *data = dev_get_drvdata(dev); |
| |
| if (sysfs_streq(buf, "c")) { |
| data->interrupt_count = 0; |
| pr_info("initialization is done\n"); |
| } |
| return size; |
| } |
| |
| static ssize_t gfspi_resetcnt_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct gf_device *data = dev_get_drvdata(dev); |
| return snprintf(buf, PAGE_SIZE, "%d\n", data->reset_count); |
| } |
| |
| static ssize_t gfspi_resetcnt_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, |
| size_t size) |
| { |
| struct gf_device *data = dev_get_drvdata(dev); |
| |
| if (sysfs_streq(buf, "c")) { |
| data->reset_count = 0; |
| pr_info("initialization is done\n"); |
| } |
| return size; |
| } |
| |
| static DEVICE_ATTR(bfs_values, 0444, gfspi_bfs_values_show, NULL); |
| static DEVICE_ATTR(type_check, 0444, gfspi_type_check_show, NULL); |
| static DEVICE_ATTR(vendor, 0444, gfspi_vendor_show, NULL); |
| static DEVICE_ATTR(name, 0444, gfspi_name_show, NULL); |
| static DEVICE_ATTR(adm, 0444, gfspi_adm_show, NULL); |
| static DEVICE_ATTR(intcnt, 0664, gfspi_intcnt_show, gfspi_intcnt_store); |
| static DEVICE_ATTR(resetcnt, 0664, gfspi_resetcnt_show, gfspi_resetcnt_store); |
| |
| static struct device_attribute *fp_attrs[] = { |
| &dev_attr_bfs_values, |
| &dev_attr_type_check, |
| &dev_attr_vendor, |
| &dev_attr_name, |
| &dev_attr_adm, |
| &dev_attr_intcnt, |
| &dev_attr_resetcnt, |
| NULL, |
| }; |
| |
| static void gfspi_enable_irq(struct gf_device *gf_dev) |
| { |
| if (gf_dev->irq_enabled == 1) { |
| pr_err("%s, irq already enabled\n", __func__); |
| } else { |
| enable_irq(gf_dev->irq); |
| enable_irq_wake(gf_dev->irq); |
| gf_dev->irq_enabled = 1; |
| pr_debug("%s enable interrupt!\n", __func__); |
| } |
| } |
| |
| static void gfspi_disable_irq(struct gf_device *gf_dev) |
| { |
| if (gf_dev->irq_enabled == 0) { |
| pr_err("%s, irq already disabled\n", __func__); |
| } else { |
| disable_irq_wake(gf_dev->irq); |
| disable_irq(gf_dev->irq); |
| gf_dev->irq_enabled = 0; |
| pr_debug("%s disable interrupt!\n", __func__); |
| } |
| } |
| |
| static void gfspi_netlink_send(struct gf_device *gf_dev, const int command) |
| { |
| struct nlmsghdr *nlh = NULL; |
| struct sk_buff *skb = NULL; |
| int ret; |
| |
| if (gf_dev->nl_sk == NULL) { |
| pr_err("%s : invalid socket\n", __func__); |
| return; |
| } |
| |
| if (gf_dev->pid == 0) { |
| pr_err("%s : invalid native process pid\n", __func__); |
| return; |
| } |
| |
| /* alloc data buffer for sending to native */ |
| /* malloc data space at least 1500 bytes, which is ethernet data length */ |
| skb = alloc_skb(MAX_NL_MSG_LEN, GFP_ATOMIC); |
| if (skb == NULL) |
| return; |
| |
| nlh = nlmsg_put(skb, 0, 0, 0, MAX_NL_MSG_LEN, 0); |
| if (!nlh) { |
| pr_err("%s : nlmsg_put failed\n", __func__); |
| kfree_skb(skb); |
| return; |
| } |
| |
| NETLINK_CB(skb).portid = 0; |
| NETLINK_CB(skb).dst_group = 0; |
| |
| *(char *)NLMSG_DATA(nlh) = command; |
| ret = netlink_unicast(gf_dev->nl_sk, skb, gf_dev->pid, MSG_DONTWAIT); |
| if (ret == 0) { |
| pr_err("%s : send failed\n", __func__); |
| return; |
| } |
| |
| } |
| |
| static void gfspi_netlink_recv(struct sk_buff *__skb) |
| { |
| struct sk_buff *skb = NULL; |
| struct nlmsghdr *nlh = NULL; |
| char str[128]; |
| |
| skb = skb_get(__skb); |
| if (skb == NULL) { |
| pr_err("%s : skb_get return NULL\n", __func__); |
| return; |
| } |
| |
| /* presume there is 5byte payload at leaset */ |
| if (skb->len >= NLMSG_SPACE(0)) { |
| nlh = nlmsg_hdr(skb); |
| memcpy(str, NLMSG_DATA(nlh), sizeof(str)); |
| g_data->pid = nlh->nlmsg_pid; |
| pr_info("%s : pid: %d, msg: %s\n", |
| __func__, g_data->pid, str); |
| } else { |
| pr_err("%s : not enough data length\n", __func__); |
| } |
| |
| kfree_skb(skb); |
| } |
| |
| static int gfspi_netlink_init(struct gf_device *gf_dev) |
| { |
| struct netlink_kernel_cfg cfg; |
| |
| memset(&cfg, 0, sizeof(struct netlink_kernel_cfg)); |
| cfg.input = gfspi_netlink_recv; |
| |
| gf_dev->nl_sk = |
| netlink_kernel_create(&init_net, GF_NETLINK_ROUTE, &cfg); |
| if (gf_dev->nl_sk == NULL) { |
| pr_err("%s : netlink create failed\n", __func__); |
| return -1; |
| } |
| |
| pr_info("%s : netlink create success\n", __func__); |
| return 0; |
| } |
| |
| static int gfspi_netlink_destroy(struct gf_device *gf_dev) |
| { |
| if (gf_dev->nl_sk != NULL) { |
| netlink_kernel_release(gf_dev->nl_sk); |
| gf_dev->nl_sk = NULL; |
| return 0; |
| } |
| |
| pr_err("%s : no netlink socket yet\n", __func__); |
| return -1; |
| } |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| static void gfspi_early_suspend(struct early_suspend *handler) |
| { |
| struct gf_device *gf_dev = NULL; |
| |
| gf_dev = container_of(handler, struct gf_device, early_suspend); |
| pr_info("%s\n", __func__); |
| |
| gfspi_netlink_send(gf_dev, GF_NETLINK_SCREEN_OFF); |
| } |
| |
| static void gfspi_late_resume(struct early_suspend *handler) |
| { |
| struct gf_device *gf_dev = NULL; |
| |
| gf_dev = container_of(handler, struct gf_device, early_suspend); |
| pr_info("%s\n", __func__); |
| |
| gfspi_netlink_send(gf_dev, GF_NETLINK_SCREEN_ON); |
| } |
| #else |
| static int gfspi_fb_notifier_callback(struct notifier_block *self, |
| unsigned long event, void *data) |
| { |
| struct gf_device *gf_dev = NULL; |
| struct fb_event *evdata = data; |
| unsigned int blank; |
| int retval = 0; |
| |
| /* If we aren't interested in this event, skip it immediately ... */ |
| if (event != FB_EARLY_EVENT_BLANK) { |
| pr_debug("%s event = %ld", __func__, event); |
| return 0; |
| } |
| |
| gf_dev = container_of(self, struct gf_device, notifier); |
| blank = *(int *)evdata->data; |
| |
| switch (blank) { |
| case FB_BLANK_UNBLANK: |
| pr_debug("%s : lcd on notify\n", __func__); |
| gfspi_netlink_send(gf_dev, GF_NETLINK_SCREEN_ON); |
| break; |
| |
| case FB_BLANK_POWERDOWN: |
| pr_debug("%s : lcd off notify\n", __func__); |
| gfspi_netlink_send(gf_dev, GF_NETLINK_SCREEN_OFF); |
| break; |
| |
| default: |
| pr_debug("%s : other notifier, ignore\n", __func__); |
| break; |
| } |
| return retval; |
| } |
| #endif /* CONFIG_HAS_EARLYSUSPEND */ |
| |
| static ssize_t gfspi_read(struct file *filp, char __user *buf, |
| size_t count, loff_t *f_pos) |
| { |
| return -EFAULT; |
| } |
| |
| static ssize_t gfspi_write(struct file *filp, const char __user *buf, |
| size_t count, loff_t *f_pos) |
| { |
| return -EFAULT; |
| } |
| |
| static irqreturn_t gfspi_irq(int irq, void *handle) |
| { |
| struct gf_device *gf_dev = (struct gf_device *)handle; |
| |
| pr_info("%s\n", __func__); |
| wake_lock_timeout(&gf_dev->wake_lock, |
| msecs_to_jiffies(WAKELOCK_HOLD_TIME)); |
| gfspi_netlink_send(gf_dev, GF_NETLINK_IRQ); |
| gf_dev->interrupt_count++; |
| |
| return IRQ_HANDLED; |
| } |
| |
| static long gfspi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) |
| { |
| struct gf_device *gf_dev = NULL; |
| unsigned int onoff = 0; |
| int retval = 0; |
| u8 buf = 0; |
| u8 netlink_route = GF_NETLINK_ROUTE; |
| |
| if (_IOC_TYPE(cmd) != GF_IOC_MAGIC) |
| return -EINVAL; |
| |
| /* Check access direction once here; don't repeat below. |
| * IOC_DIR is from the user perspective, while access_ok is |
| * from the kernel perspective; so they look reversed. |
| */ |
| if (_IOC_DIR(cmd) & _IOC_READ) |
| retval = !access_ok(VERIFY_WRITE, (void __user *)arg, |
| _IOC_SIZE(cmd)); |
| |
| if (retval == 0 && _IOC_DIR(cmd) & _IOC_WRITE) |
| retval = !access_ok(VERIFY_READ, (void __user *)arg, |
| _IOC_SIZE(cmd)); |
| |
| if (retval) { |
| pr_err("%s: access NOK\n", __func__); |
| return -EINVAL; |
| } |
| |
| gf_dev = (struct gf_device *)filp->private_data; |
| if (!gf_dev) { |
| pr_err("%s: gf_dev IS NULL\n", __func__); |
| return -EINVAL; |
| } |
| |
| switch (cmd) { |
| case GF_IOC_INIT: |
| pr_info("%s: GF_IOC_INIT\n", __func__); |
| if (copy_to_user((void __user *)arg, (void *)&netlink_route, |
| sizeof(u8))) { |
| retval = -EFAULT; |
| break; |
| } |
| |
| if (gf_dev->system_status) { |
| pr_info("%s: system re-started\n", __func__); |
| break; |
| } |
| |
| gf_dev->sig_count = 0; |
| gf_dev->system_status = 1; |
| break; |
| |
| case GF_IOC_EXIT: |
| pr_info("%s: GF_IOC_EXIT\n", __func__); |
| gfspi_disable_irq(gf_dev); |
| if (gf_dev->irq) { |
| free_irq(gf_dev->irq, gf_dev); |
| gf_dev->irq = 0; |
| } |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| if (gf_dev->early_suspend.suspend) |
| unregister_early_suspend(&gf_dev->early_suspend); |
| #else |
| fb_unregister_client(&gf_dev->notifier); |
| #endif |
| gf_dev->system_status = 0; |
| break; |
| |
| case GF_IOC_RESET: |
| pr_info("%s: GF_IOC_RESET\n", __func__); |
| gfspi_hw_reset(gf_dev, 0); |
| break; |
| |
| case GF_IOC_ENABLE_IRQ: |
| pr_info("%s: GF_IOC_ENABLE_IRQ\n", __func__); |
| gfspi_enable_irq(gf_dev); |
| break; |
| |
| case GF_IOC_DISABLE_IRQ: |
| pr_info("%s: GF_IOC_DISABLE_IRQ\n", __func__); |
| gfspi_disable_irq(gf_dev); |
| break; |
| |
| case GF_IOC_ENABLE_SPI_CLK: |
| pr_debug("%s: GF_IOC_ENABLE_SPI_CLK\n", __func__); |
| gfspi_spi_clk_enable(gf_dev); |
| break; |
| |
| case GF_IOC_DISABLE_SPI_CLK: |
| pr_debug("%s: GF_IOC_DISABLE_SPI_CLK\n", __func__); |
| gfspi_spi_clk_disable(gf_dev); |
| break; |
| |
| case GF_IOC_ENABLE_POWER: |
| pr_debug("%s: GF_IOC_ENABLE_POWER\n", __func__); |
| gfspi_hw_power_enable(gf_dev, 1); |
| break; |
| |
| case GF_IOC_DISABLE_POWER: |
| pr_debug("%s: GF_IOC_DISABLE_POWER\n", __func__); |
| gfspi_hw_power_enable(gf_dev, 0); |
| break; |
| |
| case GF_IOC_POWER_CONTROL: |
| if (copy_from_user(&onoff, (void __user *)arg, |
| sizeof(unsigned int))) { |
| pr_err("Failed to copy onoff value from user to kernel\n"); |
| retval = -EFAULT; |
| break; |
| } |
| pr_info("%s: GF_IOC_POWER_CONTROL %d\n", __func__, onoff); |
| gfspi_hw_power_enable(gf_dev, onoff); |
| break; |
| |
| case GF_IOC_ENTER_SLEEP_MODE: |
| break; |
| |
| case GF_IOC_GET_FW_INFO: |
| buf = gf_dev->need_update; |
| buf = 1; |
| pr_debug("%s: GET_FW_INFO : 0x%x\n", __func__, buf); |
| if (copy_to_user((void __user *)arg, (void *)&buf, |
| sizeof(u8))) { |
| pr_err("Failed to copy data to user\n"); |
| retval = -EFAULT; |
| } |
| |
| break; |
| case GF_IOC_REMOVE: |
| break; |
| |
| #ifndef ENABLE_SENSORS_FPRINT_SECURE |
| case GF_IOC_TRANSFER_RAW_CMD: |
| mutex_lock(&gf_dev->buf_lock); |
| retval = gfspi_ioctl_transfer_raw_cmd(gf_dev, arg, bufsiz); |
| mutex_unlock(&gf_dev->buf_lock); |
| break; |
| #endif /* !ENABLE_SENSORS_FPRINT_SECURE */ |
| #ifdef ENABLE_SENSORS_FPRINT_SECURE |
| case GF_IOC_SET_SENSOR_TYPE: |
| if (copy_from_user(&onoff, (void __user *)arg, |
| sizeof(unsigned int)) != 0) { |
| pr_err("Failed to copy sensor type from user to kernel\n"); |
| return -EFAULT; |
| } |
| if ((int)onoff >= SENSOR_OOO && (int)onoff < SENSOR_MAXIMUM) { |
| if ((int)onoff == SENSOR_OOO && gf_dev->sensortype == SENSOR_FAILED) { |
| pr_err("%s Maintain type check from out of oder :%s\n", |
| __func__, sensor_status[g_data->sensortype + 2]); |
| } else { |
| gf_dev->sensortype = (int)onoff; |
| pr_info("%s SET_SENSOR_TYPE :%s\n", |
| __func__, |
| sensor_status[g_data->sensortype + 2]); |
| } |
| } else { |
| pr_err("%s SET_SENSOR_TYPE : invalid value %d\n", |
| __func__, (int)onoff); |
| gf_dev->sensortype = SENSOR_UNKNOWN; |
| } |
| break; |
| |
| case GF_IOC_SPEEDUP: |
| if (copy_from_user(&onoff, (void __user *)arg, |
| sizeof(unsigned int)) != 0) { |
| pr_err("Failed to copy speedup from user to kernel\n"); |
| return -EFAULT; |
| } |
| #if defined(CONFIG_SECURE_OS_BOOSTER_API) |
| if (onoff) { |
| u8 retry_cnt = 0; |
| |
| pr_info("%s CPU_SPEEDUP ON\n", __func__); |
| do { |
| retval = secos_booster_start(onoff - 1); |
| retry_cnt++; |
| if (retval) { |
| pr_err |
| ("%s: booster start failed. (%d) retry: %d\n", |
| __func__, retval, retry_cnt); |
| if (retry_cnt < 7) |
| usleep_range(500, 510); |
| } |
| } while (retval && retry_cnt < 7); |
| } else { |
| pr_info("%s CPU_SPEEDUP OFF\n", __func__); |
| retval = secos_booster_stop(); |
| if (retval) |
| pr_err("%s: booster stop failed. (%d)\n", __func__, retval); |
| } |
| #elif defined(CONFIG_TZDEV_BOOST) |
| if (onoff) { |
| pr_info("%s CPU_SPEEDUP ON\n", __func__); |
| tz_boost_enable(); |
| } else { |
| pr_info("%s CPU_SPEEDUP OFF\n", __func__); |
| tz_boost_disable(); |
| } |
| #else |
| pr_err("%s CPU_SPEEDUP is not used\n", __func__); |
| #endif |
| break; |
| case GF_IOC_SET_LOCKSCREEN: |
| break; |
| #endif |
| case GF_IOC_GET_ORIENT: |
| pr_info("%s: GET_ORIENT: %d\n", __func__, gf_dev->orient); |
| if (copy_to_user((void __user *)arg, &(gf_dev->orient), |
| sizeof(gf_dev->orient))) { |
| pr_err("Failed to copy data to user\n"); |
| retval = -EFAULT; |
| } |
| break; |
| default: |
| pr_err("%s doesn't support this command(%x)\n", __func__, cmd); |
| break; |
| } |
| |
| return retval; |
| } |
| |
| #ifdef CONFIG_COMPAT |
| static long gfspi_compat_ioctl(struct file *filp, unsigned int cmd, |
| unsigned long arg) |
| { |
| int retval = 0; |
| |
| retval = filp->f_op->unlocked_ioctl(filp, cmd, arg); |
| |
| return retval; |
| } |
| #endif |
| |
| static unsigned int gfspi_poll(struct file *filp, |
| struct poll_table_struct *wait) |
| { |
| pr_err("Not support poll opertion in TEE version\n"); |
| return -EFAULT; |
| } |
| |
| static int gfspi_open(struct inode *inode, struct file *filp) |
| { |
| struct gf_device *gf_dev = NULL; |
| int status = -ENXIO; |
| |
| pr_info("%s\n", __func__); |
| mutex_lock(&device_list_lock); |
| list_for_each_entry(gf_dev, &device_list, device_entry) { |
| if (gf_dev->devno == inode->i_rdev) { |
| pr_info("%s, Found\n", __func__); |
| status = 0; |
| break; |
| } |
| } |
| mutex_unlock(&device_list_lock); |
| |
| if (status == 0) { |
| filp->private_data = gf_dev; |
| nonseekable_open(inode, filp); |
| pr_info("%s, Success to open device. irq = %d\n", |
| __func__, gf_dev->irq); |
| } else { |
| pr_err("%s, No device for minor %d\n", |
| __func__, iminor(inode)); |
| } |
| return status; |
| } |
| |
| static int gfspi_release(struct inode *inode, struct file *filp) |
| { |
| struct gf_device *gf_dev = NULL; |
| int status = 0; |
| |
| pr_info("%s\n", __func__); |
| gf_dev = filp->private_data; |
| if (gf_dev->irq) |
| gfspi_disable_irq(gf_dev); |
| gf_dev->need_update = 0; |
| return status; |
| } |
| |
| static const struct file_operations gfspi_fops = { |
| .owner = THIS_MODULE, |
| /* REVISIT switch to aio primitives, so that userspace |
| * gets more complete API coverage. It'll simplify things |
| * too, except for the locking. |
| */ |
| .write = gfspi_write, |
| .read = gfspi_read, |
| .unlocked_ioctl = gfspi_ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = gfspi_compat_ioctl, |
| #endif |
| .open = gfspi_open, |
| .release = gfspi_release, |
| .poll = gfspi_poll, |
| }; |
| |
| |
| static void gfspi_work_func_debug(struct work_struct *work) |
| { |
| struct gf_device *gf_dev = NULL; |
| u8 ldo_value = -1; |
| u8 rst_value = -1; |
| u8 irq_value = -1; |
| |
| gf_dev = container_of(work, struct gf_device, work_debug); |
| |
| if (gf_dev->pwr_gpio) |
| ldo_value = gpio_get_value(gf_dev->pwr_gpio); |
| if (gf_dev->reset_gpio) |
| rst_value = gpio_get_value(gf_dev->reset_gpio); |
| if (gf_dev->irq_gpio) |
| irq_value = gpio_get_value(gf_dev->irq_gpio); |
| |
| pr_info("%s ldo: %d, sleep: %d, irq: %d tz: %d type: %s\n", |
| __func__, |
| ldo_value, rst_value, irq_value, gf_dev->tz_mode, |
| sensor_status[gf_dev->sensortype + 2]); |
| } |
| |
| static void gfspi_enable_debug_timer(struct gf_device *gf_dev) |
| { |
| mod_timer(&gf_dev->dbg_timer, |
| round_jiffies_up(jiffies + FPSENSOR_DEBUG_TIMER_SEC)); |
| } |
| |
| static void gfspi_disable_debug_timer(struct gf_device *gf_dev) |
| { |
| del_timer_sync(&gf_dev->dbg_timer); |
| cancel_work_sync(&gf_dev->work_debug); |
| } |
| |
| static void gfspi_timer_func(unsigned long ptr) |
| { |
| queue_work(g_data->wq_dbg, &g_data->work_debug); |
| mod_timer(&g_data->dbg_timer, |
| round_jiffies_up(jiffies + FPSENSOR_DEBUG_TIMER_SEC)); |
| } |
| |
| static int gfspi_set_timer(struct gf_device *gf_dev) |
| { |
| int status = 0; |
| |
| setup_timer(&gf_dev->dbg_timer, |
| gfspi_timer_func, (unsigned long)gf_dev); |
| gf_dev->wq_dbg = create_singlethread_workqueue("gf_debug_wq"); |
| if (!gf_dev->wq_dbg) { |
| status = -ENOMEM; |
| pr_err("%s could not create workqueue\n", __func__); |
| return status; |
| } |
| INIT_WORK(&gf_dev->work_debug, gfspi_work_func_debug); |
| return status; |
| } |
| |
| void gfspi_hw_power_enable(struct gf_device *gf_dev, u8 onoff) |
| { |
| if (onoff && !gf_dev->ldo_onoff) { |
| gfspi_pin_control(gf_dev, 1); |
| if (gf_dev->pwr_gpio) |
| gpio_set_value(gf_dev->pwr_gpio, 1); |
| #if defined(ENABLE_SENSORS_FPRINT_SECURE) && defined(CONFIG_TZDEV) |
| pr_info("%s: FP_SET_POWERON_INACTIVE ret = %d\n", __func__, |
| exynos_smc(FP_CSMC_HANDLER_ID, FP_HANDLER_MAIN, FP_SET_POWERON_INACTIVE, 0)); |
| #endif |
| if (gf_dev->reset_gpio) { |
| usleep_range(11000, 11050); |
| gpio_set_value(gf_dev->reset_gpio, 1); |
| } |
| gf_dev->ldo_onoff = 1; |
| } else if (!onoff && gf_dev->ldo_onoff) { |
| #ifdef ENABLE_SENSORS_FPRINT_SECURE |
| #if defined (CONFIG_ARCH_EXYNOS9) || defined(CONFIG_ARCH_EXYNOS8)\ |
| || defined (CONFIG_ARCH_EXYNOS7) |
| #if defined(CONFIG_TZDEV) |
| pr_info("%s: FP_SET_POWEROFF ret = %d\n", __func__, |
| exynos_smc(FP_CSMC_HANDLER_ID, FP_HANDLER_MAIN, FP_SET_POWEROFF, 0)); |
| #else |
| pr_info("%s: cs_set smc ret = %d\n", __func__, |
| exynos_smc(MC_FC_FP_CS_SET, 0, 0, 0)); |
| #endif |
| #endif |
| #endif |
| if (gf_dev->reset_gpio) { |
| gpio_set_value(gf_dev->reset_gpio, 0); |
| usleep_range(11000, 11050); |
| } |
| if (gf_dev->pwr_gpio) |
| gpio_set_value(gf_dev->pwr_gpio, 0); |
| gf_dev->ldo_onoff = 0; |
| gfspi_pin_control(gf_dev, 0); |
| } else if (onoff == 0 || onoff == 1) { |
| pr_err("%s power is already %s\n", |
| __func__, |
| (gf_dev->ldo_onoff ? "Enabled" : "Disabled")); |
| } else { |
| pr_err("%s can't support this value:%d\n", __func__, onoff); |
| } |
| pr_info("%s status = %d\n", __func__, gf_dev->ldo_onoff); |
| } |
| |
| void gfspi_hw_reset(struct gf_device *gf_dev, u8 delay) |
| { |
| if (gf_dev == NULL) { |
| pr_err("%s, Input buff is NULL.\n", __func__); |
| return; |
| } |
| gpio_direction_output(gf_dev->reset_gpio, 1); |
| gpio_set_value(gf_dev->reset_gpio, 0); |
| usleep_range(3000, 3050); |
| gpio_set_value(gf_dev->reset_gpio, 1); |
| usleep_range((delay * 1000), ((delay * 1000) + 50)); |
| gf_dev->reset_count++; |
| } |
| |
| #ifndef ENABLE_SENSORS_FPRINT_SECURE |
| int gfspi_type_check(struct gf_device *gf_dev) |
| { |
| int status = -ENODEV; |
| unsigned char chipid[4] = {0x00, 0x00, 0x00, 0x00}; |
| u32 chipid32 = 0; |
| |
| gfspi_hw_power_enable(gf_dev, 1); |
| usleep_range(4950, 5000); |
| gfspi_hw_reset(gf_dev, 0); |
| |
| gfspi_spi_read_bytes(gf_dev, 0x0000, 4, chipid); |
| chipid32 = (chipid[2] << 16 | chipid[3] << 8 | chipid[0]); |
| if (GF_GW32J_CHIP_ID == chipid32) { |
| gf_dev->sensortype = SENSOR_GOODIX; |
| pr_info("%s sensor type is GW32J (%s:0x%x)\n", __func__, |
| sensor_status[gf_dev->sensortype + 2], chipid32); |
| status = 0; |
| } else if (GF_GW32N_CHIP_ID == chipid32) { |
| gf_dev->sensortype = SENSOR_GOODIX; |
| pr_info("%s sensor type is GW32N (%s:0x%x)\n", __func__, |
| sensor_status[gf_dev->sensortype + 2], chipid32); |
| status = 0; |
| } else if (GF_GW36H_CHIP_ID == chipid32) { |
| gf_dev->sensortype = SENSOR_GOODIX; |
| pr_info("%s sensor type is GW36H (%s:0x%x)\n", __func__, |
| sensor_status[gf_dev->sensortype + 2], chipid32); |
| status = 0; |
| } else if (GF_GW36C_CHIP_ID == chipid32) { |
| gf_dev->sensortype = SENSOR_GOODIX; |
| pr_info("%s sensor type is GW36C (%s:0x%x)\n", __func__, |
| sensor_status[gf_dev->sensortype + 2], chipid32); |
| status = 0; |
| } else { |
| gf_dev->sensortype = SENSOR_FAILED; |
| pr_err("%s sensor type is FAILED 0x%x\n", |
| __func__, chipid32); |
| } |
| gfspi_hw_power_enable(gf_dev, 0); |
| return status; |
| } |
| #endif |
| |
| |
| static int gfspi_probe(struct spi_device *spi) |
| { |
| struct gf_device *gf_dev = NULL; |
| int status = -EINVAL; |
| #ifndef ENABLE_SENSORS_FPRINT_SECURE |
| int retry = 0; |
| #endif |
| pr_info("%s\n", __func__); |
| |
| /* Allocate driver data */ |
| gf_dev = kzalloc(sizeof(struct gf_device), GFP_KERNEL); |
| if (!gf_dev) { |
| status = -ENOMEM; |
| return status; |
| } |
| |
| spin_lock_init(&gf_dev->spi_lock); |
| mutex_init(&gf_dev->buf_lock); |
| mutex_init(&gf_dev->release_lock); |
| |
| INIT_LIST_HEAD(&gf_dev->device_entry); |
| |
| gf_dev->device_count = 0; |
| gf_dev->system_status = 0; |
| gf_dev->need_update = 0; |
| gf_dev->ldo_onoff = 0; |
| gf_dev->pid = 0; |
| gf_dev->reset_count = 0; |
| gf_dev->interrupt_count = 0; |
| #ifdef ENABLE_SENSORS_FPRINT_SECURE |
| gf_dev->enabled_clk = 0; |
| gf_dev->tz_mode = true; |
| fpsensor_goto_suspend = 0; |
| #else |
| gf_dev->tz_mode = false; |
| #endif |
| |
| /* Initialize the driver data */ |
| gf_dev->spi = spi; |
| g_data = gf_dev; |
| |
| gf_dev->irq = 0; |
| spi_set_drvdata(spi, gf_dev); |
| |
| /* allocate buffer for SPI transfer */ |
| #ifndef ENABLE_SENSORS_FPRINT_SECURE |
| gf_dev->spi_buffer = kzalloc(bufsiz, GFP_KERNEL); |
| if (gf_dev->spi_buffer == NULL) { |
| status = -ENOMEM; |
| goto err_buf; |
| } |
| #endif |
| |
| /* get gpio info from dts or defination */ |
| status = gfspi_get_gpio_dts_info(&spi->dev, gf_dev); |
| if (status < 0) { |
| pr_err("%s, Failed to get gpio info:%d\n", __func__, status); |
| goto err_get_gpio; |
| } |
| gfspi_spi_setup_conf(gf_dev, 4); |
| |
| /* create class */ |
| gf_dev->class = class_create(THIS_MODULE, GF_CLASS_NAME); |
| if (IS_ERR(gf_dev->class)) { |
| pr_err("%s, Failed to create class.\n", __func__); |
| status = -ENODEV; |
| goto err_class_create; |
| } |
| |
| /* get device no */ |
| if (GF_DEV_MAJOR > 0) { |
| gf_dev->devno = MKDEV(GF_DEV_MAJOR, gf_dev->device_count++); |
| status = register_chrdev_region(gf_dev->devno, 1, GF_DEV_NAME); |
| } else { |
| status = alloc_chrdev_region(&gf_dev->devno, |
| gf_dev->device_count++, 1, GF_DEV_NAME); |
| } |
| if (status < 0) { |
| pr_err("%s, Failed to alloc devno.\n", __func__); |
| goto err_devno; |
| } else { |
| pr_info("%s, major=%d, minor=%d\n", |
| __func__, MAJOR(gf_dev->devno), |
| MINOR(gf_dev->devno)); |
| } |
| |
| /* create device */ |
| gf_dev->fp_device = device_create(gf_dev->class, &spi->dev, |
| gf_dev->devno, gf_dev, GF_DEV_NAME); |
| if (IS_ERR(gf_dev->fp_device)) { |
| pr_err("%s, Failed to create device.\n", __func__); |
| status = -ENODEV; |
| goto err_device; |
| } else { |
| mutex_lock(&device_list_lock); |
| list_add(&gf_dev->device_entry, &device_list); |
| mutex_unlock(&device_list_lock); |
| pr_info("%s, device create success.\n", __func__); |
| } |
| |
| #ifdef ENABLE_SENSORS_FPRINT_SECURE |
| gf_dev->sensortype = SENSOR_UNKNOWN; |
| #else |
| /* sensor hw type check */ |
| do { |
| status = gfspi_type_check(gf_dev); |
| pr_info("%s type (%u), retry (%d)\n", |
| __func__, gf_dev->sensortype, retry); |
| } while (!gf_dev->sensortype && ++retry < 3); |
| |
| if (status == -ENODEV) |
| pr_err("%s type_check failed\n", __func__); |
| #endif |
| |
| #if defined(DISABLED_GPIO_PROTECTION) |
| etspi_pin_control(etspi, 0); |
| #endif |
| |
| /* create sysfs */ |
| status = fingerprint_register(gf_dev->fp_device, |
| gf_dev, fp_attrs, "fingerprint"); |
| if (status) { |
| pr_err("%s sysfs register failed\n", __func__); |
| goto err_sysfs; |
| } |
| |
| /* cdev init and add */ |
| cdev_init(&gf_dev->cdev, &gfspi_fops); |
| gf_dev->cdev.owner = THIS_MODULE; |
| status = cdev_add(&gf_dev->cdev, gf_dev->devno, 1); |
| if (status) { |
| pr_err("%s, Failed to add cdev.\n", __func__); |
| goto err_cdev; |
| } |
| |
| /* netlink interface init */ |
| status = gfspi_netlink_init(gf_dev); |
| if (status == -1) { |
| pr_err("%s, Failed to init netlink.\n", __func__); |
| goto err_netlink_init; |
| } |
| |
| wake_lock_init(&gf_dev->wake_lock, WAKE_LOCK_SUSPEND, "gf_wake_lock"); |
| gf_dev->irq = gpio_to_irq(gf_dev->irq_gpio); |
| status = request_threaded_irq(gf_dev->irq, NULL, gfspi_irq, |
| IRQF_TRIGGER_RISING | IRQF_ONESHOT, "goodix_fp_irq", |
| gf_dev); |
| if (status) { |
| pr_err("%s irq thread request failed, retval=%d\n", |
| __func__, status); |
| goto err_request_irq; |
| } |
| |
| enable_irq_wake(gf_dev->irq); |
| gf_dev->irq_enabled = 1; |
| gfspi_disable_irq(gf_dev); |
| |
| status = gfspi_set_timer(gf_dev); |
| if (status) |
| goto err_debug_timer; |
| gfspi_enable_debug_timer(gf_dev); |
| |
| #if defined(CONFIG_HAS_EARLYSUSPEND) |
| pr_info("%s : register_early_suspend\n", __func__); |
| gf_dev->early_suspend.level = (EARLY_SUSPEND_LEVEL_DISABLE_FB - 1); |
| gf_dev->early_suspend.suspend = gfspi_early_suspend, |
| gf_dev->early_suspend.resume = gfspi_late_resume, |
| register_early_suspend(&gf_dev->early_suspend); |
| #else |
| /* register screen on/off callback */ |
| gf_dev->notifier.notifier_call = gfspi_fb_notifier_callback; |
| fb_register_client(&gf_dev->notifier); |
| #endif |
| #ifdef ENABLE_SENSORS_FPRINT_SECURE |
| #if defined (CONFIG_ARCH_EXYNOS9) || defined(CONFIG_ARCH_EXYNOS8)\ |
| || defined (CONFIG_ARCH_EXYNOS7) |
| /* prevent spi_cs line floating */ |
| #if !defined(CONFIG_TZDEV) |
| pr_info("%s: cs_set smc ret = %d\n", __func__, |
| exynos_smc(MC_FC_FP_CS_SET, 0, 0, 0)); |
| #endif |
| #endif |
| #endif |
| pr_info("%s probe finished\n", __func__); |
| return 0; |
| |
| err_debug_timer: |
| free_irq(gf_dev->irq, gf_dev); |
| err_request_irq: |
| gfspi_netlink_destroy(gf_dev); |
| err_netlink_init: |
| cdev_del(&gf_dev->cdev); |
| err_cdev: |
| fingerprint_unregister(gf_dev->fp_device, fp_attrs); |
| err_sysfs: |
| device_destroy(gf_dev->class, gf_dev->devno); |
| list_del(&gf_dev->device_entry); |
| err_device: |
| unregister_chrdev_region(gf_dev->devno, 1); |
| err_devno: |
| class_destroy(gf_dev->class); |
| err_class_create: |
| err_get_gpio: |
| #ifndef ENABLE_SENSORS_FPRINT_SECURE |
| kfree(gf_dev->spi_buffer); |
| err_buf: |
| #endif |
| mutex_destroy(&gf_dev->buf_lock); |
| mutex_destroy(&gf_dev->release_lock); |
| spi_set_drvdata(spi, NULL); |
| gf_dev->spi = NULL; |
| kfree(gf_dev); |
| gf_dev = NULL; |
| |
| pr_err("%s failed. %d", __func__, status); |
| return status; |
| } |
| |
| static int gfspi_remove(struct spi_device *spi) |
| { |
| struct gf_device *gf_dev = spi_get_drvdata(spi); |
| |
| pr_info("%s\n", __func__); |
| |
| /* make sure ops on existing fds can abort cleanly */ |
| if (gf_dev->irq) { |
| free_irq(gf_dev->irq, gf_dev); |
| gf_dev->irq_enabled = 0; |
| gf_dev->irq = 0; |
| } |
| |
| gfspi_disable_debug_timer(gf_dev); |
| wake_lock_destroy(&gf_dev->wake_lock); |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| if (gf_dev->early_suspend.suspend) |
| unregister_early_suspend(&gf_dev->early_suspend); |
| #else |
| fb_unregister_client(&gf_dev->notifier); |
| #endif |
| |
| #ifndef ENABLE_SENSORS_FPRINT_SECURE |
| mutex_lock(&gf_dev->release_lock); |
| if (gf_dev->spi_buffer != NULL) { |
| kfree(gf_dev->spi_buffer); |
| gf_dev->spi_buffer = NULL; |
| } |
| mutex_unlock(&gf_dev->release_lock); |
| #endif |
| fingerprint_unregister(gf_dev->fp_device, fp_attrs); |
| gfspi_netlink_destroy(gf_dev); |
| cdev_del(&gf_dev->cdev); |
| device_destroy(gf_dev->class, gf_dev->devno); |
| list_del(&gf_dev->device_entry); |
| |
| unregister_chrdev_region(gf_dev->devno, 1); |
| class_destroy(gf_dev->class); |
| gfspi_hw_power_enable(gf_dev, 0); |
| |
| spin_lock_irq(&gf_dev->spi_lock); |
| spi_set_drvdata(spi, NULL); |
| gf_dev->spi = NULL; |
| spin_unlock_irq(&gf_dev->spi_lock); |
| |
| mutex_destroy(&gf_dev->buf_lock); |
| mutex_destroy(&gf_dev->release_lock); |
| |
| kfree(gf_dev); |
| return 0; |
| } |
| |
| static int gfspi_pm_suspend(struct device *dev) |
| { |
| struct gf_device *gf_dev = dev_get_drvdata(dev); |
| #ifdef ENABLE_SENSORS_FPRINT_SECURE |
| #if defined (CONFIG_ARCH_EXYNOS9) || defined(CONFIG_ARCH_EXYNOS8)\ |
| || defined (CONFIG_ARCH_EXYNOS7) |
| int ret = 0; |
| |
| fpsensor_goto_suspend = 1; /* used by pinctrl_samsung.c */ |
| |
| if (!gf_dev->ldo_onoff) { |
| #if defined(CONFIG_TZDEV) |
| ret = exynos_smc(FP_CSMC_HANDLER_ID, FP_HANDLER_MAIN, FP_SET_POWEROFF, 0); |
| pr_info("%s: FP_SET_POWEROFF ret = %d\n", __func__, ret); |
| #else |
| ret = exynos_smc(MC_FC_FP_PM_SUSPEND, 0, 0, 0); |
| pr_info("%s: suspend smc ret = %d\n", __func__, ret); |
| #endif |
| } else { |
| #if defined(CONFIG_TZDEV) |
| ret = exynos_smc(FP_CSMC_HANDLER_ID, FP_HANDLER_MAIN, FP_SET_POWERON_INACTIVE, 0); |
| pr_info("%s: FP_SET_POWERON_INACTIVE ret = %d\n", __func__, ret); |
| #else |
| ret = exynos_smc(MC_FC_FP_PM_SUSPEND_CS_HIGH, 0, 0, 0); |
| pr_info("%s: suspend_cs_high smc ret = %d\n", __func__, ret); |
| #endif |
| } |
| #endif |
| #else |
| pr_info("%s\n", __func__); |
| #endif |
| gfspi_disable_debug_timer(gf_dev); |
| return 0; |
| } |
| |
| static int gfspi_pm_resume(struct device *dev) |
| { |
| struct gf_device *gf_dev = dev_get_drvdata(dev); |
| |
| pr_info("%s\n", __func__); |
| gfspi_enable_debug_timer(gf_dev); |
| #if defined(ENABLE_SENSORS_FPRINT_SECURE) |
| if (fpsensor_goto_suspend) { |
| fps_resume_set(); |
| } |
| #endif |
| return 0; |
| } |
| |
| static const struct dev_pm_ops gfspi_pm_ops = { |
| .suspend = gfspi_pm_suspend, |
| .resume = gfspi_pm_resume |
| }; |
| |
| static struct spi_driver gfspi_spi_driver = { |
| .driver = { |
| .name = GF_DEV_NAME, |
| .bus = &spi_bus_type, |
| .owner = THIS_MODULE, |
| .pm = &gfspi_pm_ops, |
| #ifdef CONFIG_OF |
| .of_match_table = gfspi_of_match, |
| #endif |
| }, |
| .probe = gfspi_probe, |
| .remove = gfspi_remove, |
| }; |
| |
| static int __init gfspi_init(void) |
| { |
| int status = 0; |
| |
| pr_info("%s\n", __func__); |
| |
| status = spi_register_driver(&gfspi_spi_driver); |
| if (status < 0) { |
| pr_err("%s, Failed to register SPI driver.\n", |
| __func__); |
| return -EINVAL; |
| } |
| return status; |
| } |
| module_init(gfspi_init); |
| |
| static void __exit gfspi_exit(void) |
| { |
| pr_info("%s\n", __func__); |
| spi_unregister_driver(&gfspi_spi_driver); |
| } |
| module_exit(gfspi_exit); |
| |
| |
| MODULE_AUTHOR("Samgsung"); |
| MODULE_DESCRIPTION("Goodix FP sensor"); |
| MODULE_LICENSE("GPL"); |