dlkm: add goodix fod driver

cherry-pick from (CR)
Goodix FOD driver is used for Goodix G5-GW9608
Fingerprint Over Display (FOD) sensor.

Change-Id: I31a45ec4239d63bcc520013e63ba7da1c900fb85
Signed-off-by: Yan Zhang <zhangy26@motorola.com>
Reviewed-on: https://gerrit.mot.com/1402926
SLTApproved: Slta Waiver
SME-Granted: SME Approvals Granted
Tested-by: Jira Key
Reviewed-by: Konstantin Makariev <kmakariev@motorola.com>
Submit-Approved: Jira Key
This commit is contained in:
Yan Zhang 2019-08-15 15:21:16 +08:00
commit 92e5e859ae
8 changed files with 1364 additions and 0 deletions

View file

@ -0,0 +1,18 @@
Goodix Fingerprint TEE driver
Required properties:
-compatible : should be "goodix,fingerprint".
-interrupt-parent : gpio interrupt parent declare
-fp-gpio-irq : irq gpio declare
-fp-gpio-reset : reset gpio declare
Example:
goodix_fp {
compatible = "goodix,fingerprint";
interrupt-parent = <&tlmm>;
fp-gpio-irq = <&tlmm 58 0x00>;
fp-gpio-reset = <&tlmm 59 0x00>;
status = "okay";
};

View file

@ -0,0 +1,9 @@
DLKM_DIR := motorola/kernel/modules
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := goodix_fod_mmi.ko
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT)
include $(DLKM_DIR)/AndroidKernelModule.mk

View file

@ -0,0 +1,9 @@
# add -Wall to try to catch everything we can.
EXTRA_CFLAGS += -Wall
EXTRA_CFLAGS += -I$(TOP)/motorola/kernel/modules/include \
goodix_fod_mmi-y += gf_spi.o netlink.o platform.o
obj-m += goodix_fod_mmi.o

View file

@ -0,0 +1,10 @@
KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build
all:
$(MAKE) -C $(KERNEL_SRC) M=$(shell pwd) modules $(KBUILD_OPTIONS)
modules_install:
$(MAKE) INSTALL_MOD_STRIP=1 -C $(KERNEL_SRC) M=$(shell pwd) modules_install
clean:
$(MAKE) -C $(KERNEL_SRC) M=$(PWD) clean

View file

@ -0,0 +1,887 @@
/*
* TEE driver for goodix fingerprint sensor
* Copyright (C) 2016 Goodix
*
* 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.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/input.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/ktime.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/of_gpio.h>
#include <linux/timer.h>
#include <linux/notifier.h>
#include <linux/fb.h>
#include <linux/pm_qos.h>
#include <linux/cpufreq.h>
#include "gf_spi.h"
#if defined(USE_SPI_BUS)
#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>
#elif defined(USE_PLATFORM_BUS)
#include <linux/platform_device.h>
#endif
#define VER_MAJOR 1
#define VER_MINOR 2
#define PATCH_LEVEL 4
#define EXTEND_VER 2
#define WAKELOCK_HOLD_TIME 500 /* in ms */
#define GF_SPIDEV_NAME "goodix,fingerprint"
/*device name after register in character*/
#define GF_DEV_NAME "goodix_fp"
#define GF_INPUT_NAME "qwerty" /*"goodix_fp" */
#define CHRD_DRIVER_NAME "goodix_fp_spi"
#define CLASS_NAME "goodix_fp"
#define N_SPI_MINORS 32 /* ... up to 256 */
static int SPIDEV_MAJOR;
static DECLARE_BITMAP(minors, N_SPI_MINORS);
static LIST_HEAD(device_list);
static DEFINE_MUTEX(device_list_lock);
static struct gf_dev gf;
static struct gf_key_map maps[] = {
{ EV_KEY, GF_KEY_INPUT_HOME },
{ EV_KEY, GF_KEY_INPUT_MENU },
{ EV_KEY, GF_KEY_INPUT_BACK },
{ EV_KEY, GF_KEY_INPUT_POWER },
#if defined(SUPPORT_NAV_EVENT)
{ EV_KEY, GF_NAV_INPUT_UP },
{ EV_KEY, GF_NAV_INPUT_DOWN },
{ EV_KEY, GF_NAV_INPUT_RIGHT },
{ EV_KEY, GF_NAV_INPUT_LEFT },
{ EV_KEY, GF_KEY_INPUT_CAMERA },
{ EV_KEY, GF_NAV_INPUT_CLICK },
{ EV_KEY, GF_NAV_INPUT_DOUBLE_CLICK },
{ EV_KEY, GF_NAV_INPUT_LONG_PRESS },
{ EV_KEY, GF_NAV_INPUT_HEAVY },
#endif
};
static void gf_enable_irq(struct gf_dev *gf_dev)
{
if (gf_dev->irq_enabled) {
pr_warn("IRQ has been enabled.\n");
} else {
enable_irq(gf_dev->irq);
gf_dev->irq_enabled = 1;
}
}
static void gf_disable_irq(struct gf_dev *gf_dev)
{
if (gf_dev->irq_enabled) {
gf_dev->irq_enabled = 0;
disable_irq(gf_dev->irq);
} else {
pr_warn("IRQ has been disabled.\n");
}
}
#ifdef AP_CONTROL_CLK
static long spi_clk_max_rate(struct clk *clk, unsigned long rate)
{
long lowest_available, nearest_low, step_size, cur;
long step_direction = -1;
long guess = rate;
int max_steps = 10;
cur = clk_round_rate(clk, rate);
if (cur == rate)
return rate;
/* if we got here then: cur > rate */
lowest_available = clk_round_rate(clk, 0);
if (lowest_available > rate)
return -EINVAL;
step_size = (rate - lowest_available) >> 1;
nearest_low = lowest_available;
while (max_steps-- && step_size) {
guess += step_size * step_direction;
cur = clk_round_rate(clk, guess);
if ((cur < rate) && (cur > nearest_low))
nearest_low = cur;
/*
* if we stepped too far, then start stepping in the other
* direction with half the step size
*/
if (((cur > rate) && (step_direction > 0))
|| ((cur < rate) && (step_direction < 0))) {
step_direction = -step_direction;
step_size >>= 1;
}
}
return nearest_low;
}
static void spi_clock_set(struct gf_dev *gf_dev, int speed)
{
long rate;
int rc;
rate = spi_clk_max_rate(gf_dev->core_clk, speed);
if (rate < 0) {
pr_info("%s: no match found for requested clock frequency:%d",
__func__, speed);
return;
}
rc = clk_set_rate(gf_dev->core_clk, rate);
}
static int gfspi_ioctl_clk_init(struct gf_dev *data)
{
pr_debug("%s: enter\n", __func__);
data->clk_enabled = 0;
data->core_clk = clk_get(&data->spi->dev, "core_clk");
if (IS_ERR_OR_NULL(data->core_clk)) {
pr_err("%s: fail to get core_clk\n", __func__);
return -EPERM;
}
data->iface_clk = clk_get(&data->spi->dev, "iface_clk");
if (IS_ERR_OR_NULL(data->iface_clk)) {
pr_err("%s: fail to get iface_clk\n", __func__);
clk_put(data->core_clk);
data->core_clk = NULL;
return -ENOENT;
}
return 0;
}
static int gfspi_ioctl_clk_enable(struct gf_dev *data)
{
int err;
pr_debug("%s: enter\n", __func__);
if (data->clk_enabled)
return 0;
err = clk_prepare_enable(data->core_clk);
if (err) {
pr_err("%s: fail to enable core_clk\n", __func__);
return -EPERM;
}
err = clk_prepare_enable(data->iface_clk);
if (err) {
pr_err("%s: fail to enable iface_clk\n", __func__);
clk_disable_unprepare(data->core_clk);
return -ENOENT;
}
data->clk_enabled = 1;
return 0;
}
static int gfspi_ioctl_clk_disable(struct gf_dev *data)
{
pr_debug("%s: enter\n", __func__);
if (!data->clk_enabled)
return 0;
clk_disable_unprepare(data->core_clk);
clk_disable_unprepare(data->iface_clk);
data->clk_enabled = 0;
return 0;
}
static int gfspi_ioctl_clk_uninit(struct gf_dev *data)
{
pr_debug("%s: enter\n", __func__);
if (data->clk_enabled)
gfspi_ioctl_clk_disable(data);
if (!IS_ERR_OR_NULL(data->core_clk)) {
clk_put(data->core_clk);
data->core_clk = NULL;
}
if (!IS_ERR_OR_NULL(data->iface_clk)) {
clk_put(data->iface_clk);
data->iface_clk = NULL;
}
return 0;
}
#endif
static void nav_event_input(struct gf_dev *gf_dev, gf_nav_event_t nav_event)
{
uint32_t nav_input = 0;
switch (nav_event) {
case GF_NAV_FINGER_DOWN:
pr_debug("%s nav finger down\n", __func__);
break;
case GF_NAV_FINGER_UP:
pr_debug("%s nav finger up\n", __func__);
break;
case GF_NAV_DOWN:
nav_input = GF_NAV_INPUT_DOWN;
pr_debug("%s nav down\n", __func__);
break;
case GF_NAV_UP:
nav_input = GF_NAV_INPUT_UP;
pr_debug("%s nav up\n", __func__);
break;
case GF_NAV_LEFT:
nav_input = GF_NAV_INPUT_LEFT;
pr_debug("%s nav left\n", __func__);
break;
case GF_NAV_RIGHT:
nav_input = GF_NAV_INPUT_RIGHT;
pr_debug("%s nav right\n", __func__);
break;
case GF_NAV_CLICK:
nav_input = GF_NAV_INPUT_CLICK;
pr_debug("%s nav click\n", __func__);
break;
case GF_NAV_HEAVY:
nav_input = GF_NAV_INPUT_HEAVY;
pr_debug("%s nav heavy\n", __func__);
break;
case GF_NAV_LONG_PRESS:
nav_input = GF_NAV_INPUT_LONG_PRESS;
pr_debug("%s nav long press\n", __func__);
break;
case GF_NAV_DOUBLE_CLICK:
nav_input = GF_NAV_INPUT_DOUBLE_CLICK;
pr_debug("%s nav double click\n", __func__);
break;
default:
pr_warn("%s unknown nav event: %d\n", __func__, nav_event);
break;
}
if ((nav_event != GF_NAV_FINGER_DOWN) &&
(nav_event != GF_NAV_FINGER_UP)) {
input_report_key(gf_dev->input, nav_input, 1);
input_sync(gf_dev->input);
input_report_key(gf_dev->input, nav_input, 0);
input_sync(gf_dev->input);
}
}
static void gf_kernel_key_input(struct gf_dev *gf_dev, struct gf_key *gf_key)
{
uint32_t key_input = 0;
if (gf_key->key == GF_KEY_HOME) {
key_input = GF_KEY_INPUT_HOME;
} else if (gf_key->key == GF_KEY_POWER) {
key_input = GF_KEY_INPUT_POWER;
} else if (gf_key->key == GF_KEY_CAMERA) {
key_input = GF_KEY_INPUT_CAMERA;
} else {
/* add special key define */
key_input = gf_key->key;
}
pr_info("%s: received key event[%d], key=%d, value=%d\n",
__func__, key_input, gf_key->key, gf_key->value);
if ((GF_KEY_POWER == gf_key->key || GF_KEY_CAMERA == gf_key->key)
&& (gf_key->value == 1)) {
input_report_key(gf_dev->input, key_input, 1);
input_sync(gf_dev->input);
input_report_key(gf_dev->input, key_input, 0);
input_sync(gf_dev->input);
}
if (gf_key->key == GF_KEY_HOME) {
input_report_key(gf_dev->input, key_input, gf_key->value);
input_sync(gf_dev->input);
}
}
static long gf_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct gf_dev *gf_dev = &gf;
struct gf_key gf_key;
#if defined(SUPPORT_NAV_EVENT)
gf_nav_event_t nav_event = GF_NAV_NONE;
#endif
int retval = 0;
u8 netlink_route = NETLINK_TEST;
struct gf_ioc_chip_info info;
if (_IOC_TYPE(cmd) != GF_IOC_MAGIC)
return -ENODEV;
if (_IOC_DIR(cmd) & _IOC_READ)
retval = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
retval = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (retval)
return -EFAULT;
switch (cmd) {
case GF_IOC_INIT:
pr_debug("%s GF_IOC_INIT\n", __func__);
if (copy_to_user((void __user *)arg, (void *)&netlink_route, sizeof(u8))) {
retval = -EFAULT;
break;
}
break;
case GF_IOC_EXIT:
pr_debug("%s GF_IOC_EXIT\n", __func__);
break;
case GF_IOC_DISABLE_IRQ:
pr_debug("%s GF_IOC_DISABEL_IRQ\n", __func__);
gf_disable_irq(gf_dev);
break;
case GF_IOC_ENABLE_IRQ:
pr_debug("%s GF_IOC_ENABLE_IRQ\n", __func__);
gf_enable_irq(gf_dev);
break;
case GF_IOC_RESET:
pr_debug("%s GF_IOC_RESET\n", __func__);
gf_hw_reset(gf_dev, 3);
break;
case GF_IOC_INPUT_KEY_EVENT:
if (copy_from_user(&gf_key, (void __user *)arg, sizeof(struct gf_key))) {
pr_err("failed to copy input key event from user to kernel\n");
retval = -EFAULT;
break;
}
gf_kernel_key_input(gf_dev, &gf_key);
break;
#if defined(SUPPORT_NAV_EVENT)
case GF_IOC_NAV_EVENT:
pr_debug("%s GF_IOC_NAV_EVENT\n", __func__);
if (copy_from_user(&nav_event, (void __user *)arg, sizeof(gf_nav_event_t))) {
pr_err("failed to copy nav event from user to kernel\n");
retval = -EFAULT;
break;
}
nav_event_input(gf_dev, nav_event);
break;
#endif
case GF_IOC_ENABLE_SPI_CLK:
pr_debug("%s GF_IOC_ENABLE_SPI_CLK\n", __func__);
#ifdef AP_CONTROL_CLK
gfspi_ioctl_clk_enable(gf_dev);
#else
pr_debug("Doesn't support control clock.\n");
#endif
break;
case GF_IOC_DISABLE_SPI_CLK:
pr_debug("%s GF_IOC_DISABLE_SPI_CLK\n", __func__);
#ifdef AP_CONTROL_CLK
gfspi_ioctl_clk_disable(gf_dev);
#else
pr_debug("Doesn't support control clock\n");
#endif
break;
case GF_IOC_ENABLE_POWER:
pr_debug("%s GF_IOC_ENABLE_POWER\n", __func__);
gf_power_on(gf_dev);
break;
case GF_IOC_DISABLE_POWER:
pr_debug("%s GF_IOC_DISABLE_POWER\n", __func__);
gf_power_off(gf_dev);
break;
case GF_IOC_ENTER_SLEEP_MODE:
pr_debug("%s GF_IOC_ENTER_SLEEP_MODE\n", __func__);
break;
case GF_IOC_GET_FW_INFO:
pr_debug("%s GF_IOC_GET_FW_INFO\n", __func__);
break;
case GF_IOC_REMOVE:
pr_debug("%s GF_IOC_REMOVE\n", __func__);
break;
case GF_IOC_CHIP_INFO:
pr_debug("%s GF_IOC_CHIP_INFO\n", __func__);
if (copy_from_user(&info, (void __user *)arg, sizeof(struct gf_ioc_chip_info))) {
retval = -EFAULT;
break;
}
pr_info("vendor_id : 0x%x\n", info.vendor_id);
pr_info("mode : 0x%x\n", info.mode);
pr_info("operation: 0x%x\n", info.operation);
break;
default:
pr_warn("unsupport cmd:0x%x\n", cmd);
break;
}
return retval;
}
#ifdef CONFIG_COMPAT
static long gf_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
return gf_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
}
#endif /*CONFIG_COMPAT*/
static irqreturn_t gf_irq(int irq, void *handle)
{
#if defined(GF_NETLINK_ENABLE)
char msg = GF_NET_EVENT_IRQ;
struct gf_dev *gf_dev = handle;
struct device *dev = &gf_dev->spi->dev;
pm_wakeup_event(dev, WAKELOCK_HOLD_TIME);
sendnlmsg(&msg);
#elif defined(GF_FASYNC)
struct gf_dev *gf_dev = &gf;
if (gf_dev->async)
kill_fasync(&gf_dev->async, SIGIO, POLL_IN);
#endif
return IRQ_HANDLED;
}
static int gf_open(struct inode *inode, struct file *filp)
{
struct gf_dev *gf_dev;
int status = -ENXIO;
mutex_lock(&device_list_lock);
list_for_each_entry(gf_dev, &device_list, device_entry) {
if (gf_dev->devt == inode->i_rdev) {
pr_info("Found\n");
status = 0;
break;
}
}
if (status == 0) {
gf_dev->users++;
filp->private_data = gf_dev;
nonseekable_open(inode, filp);
pr_info("Succeed to open device. irq = %d\n",
gf_dev->irq);
if (gf_dev->users == 1)
gf_enable_irq(gf_dev);
gf_hw_reset(gf_dev, 3);
} else {
pr_info("No device for minor %d\n", iminor(inode));
}
mutex_unlock(&device_list_lock);
return status;
}
#ifdef GF_FASYNC
static int gf_fasync(int fd, struct file *filp, int mode)
{
struct gf_dev *gf_dev = filp->private_data;
int ret;
ret = fasync_helper(fd, filp, mode, &gf_dev->async);
pr_info("ret = %d\n", ret);
return ret;
}
#endif
static int gf_release(struct inode *inode, struct file *filp)
{
struct gf_dev *gf_dev;
int status = 0;
mutex_lock(&device_list_lock);
gf_dev = filp->private_data;
filp->private_data = NULL;
/*last close?? */
gf_dev->users--;
if (!gf_dev->users) {
pr_info("disable_irq. irq = %d\n", gf_dev->irq);
gf_disable_irq(gf_dev);
/*power off the sensor*/
gf_power_off(gf_dev);
}
mutex_unlock(&device_list_lock);
return status;
}
static const struct file_operations gf_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.
*/
.unlocked_ioctl = gf_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = gf_compat_ioctl,
#endif /*CONFIG_COMPAT*/
.open = gf_open,
.release = gf_release,
#ifdef GF_FASYNC
.fasync = gf_fasync,
#endif
};
static int goodix_fb_state_chg_callback(struct notifier_block *nb,
unsigned long val, void *data)
{
struct gf_dev *gf_dev;
struct fb_event *evdata = data;
int blank;
char msg = 0;
if (val != FB_EARLY_EVENT_BLANK)
return 0;
pr_info("[info] %s go to the goodix_fb_state_chg_callback value = %d\n",
__func__, (int)val);
gf_dev = container_of(nb, struct gf_dev, notifier);
if (evdata && evdata->data && val == FB_EARLY_EVENT_BLANK && gf_dev) {
blank = *(int *)(evdata->data);
switch (blank) {
case FB_BLANK_POWERDOWN:
gf_dev->fb_black = 1;
#if defined(GF_NETLINK_ENABLE)
msg = GF_NET_EVENT_FB_BLACK;
sendnlmsg(&msg);
#elif defined(GF_FASYNC)
if (gf_dev->async)
kill_fasync(&gf_dev->async, SIGIO, POLL_IN);
#endif
break;
case FB_BLANK_UNBLANK:
gf_dev->fb_black = 0;
#if defined(GF_NETLINK_ENABLE)
msg = GF_NET_EVENT_FB_UNBLACK;
sendnlmsg(&msg);
#elif defined(GF_FASYNC)
if (gf_dev->async)
kill_fasync(&gf_dev->async, SIGIO, POLL_IN);
#endif
break;
default:
pr_info("%s defalut\n", __func__);
break;
}
}
return NOTIFY_OK;
}
static struct notifier_block goodix_noti_block = {
.notifier_call = goodix_fb_state_chg_callback,
};
static struct class *gf_class;
#if defined(USE_SPI_BUS)
static int gf_probe(struct spi_device *spi)
#elif defined(USE_PLATFORM_BUS)
static int gf_probe(struct platform_device *pdev)
#endif
{
struct gf_dev *gf_dev = &gf;
int status = -EINVAL;
unsigned long minor;
struct device *dev = NULL;
int i;
/* Initialize the driver data */
INIT_LIST_HEAD(&gf_dev->device_entry);
#if defined(USE_SPI_BUS)
gf_dev->spi = spi;
dev = &spi->dev;
#elif defined(USE_PLATFORM_BUS)
gf_dev->spi = pdev;
dev = &pdev->dev;
#endif
gf_dev->irq_gpio = -EINVAL;
gf_dev->reset_gpio = -EINVAL;
gf_dev->pwr_gpio = -EINVAL;
gf_dev->fb_black = 0;
if (gf_parse_dts(gf_dev))
goto error_hw;
/* If we can allocate a minor number, hook up this device.
* Reusing minors is fine so long as udev or mdev is working.
*/
mutex_lock(&device_list_lock);
minor = find_first_zero_bit(minors, N_SPI_MINORS);
if (minor < N_SPI_MINORS) {
struct device *dev;
gf_dev->devt = MKDEV(SPIDEV_MAJOR, minor);
dev = device_create(gf_class, &gf_dev->spi->dev, gf_dev->devt,
gf_dev, GF_DEV_NAME);
status = IS_ERR(dev) ? PTR_ERR(dev) : 0;
} else {
dev_dbg(&gf_dev->spi->dev, "no minor number available!\n");
status = -ENODEV;
mutex_unlock(&device_list_lock);
goto error_hw;
}
if (status == 0) {
set_bit(minor, minors);
list_add(&gf_dev->device_entry, &device_list);
} else {
gf_dev->devt = 0;
}
mutex_unlock(&device_list_lock);
if (status == 0) {
/*input device subsystem */
gf_dev->input = input_allocate_device();
if (gf_dev->input == NULL) {
pr_err("%s, failed to allocate input device\n", __func__);
status = -ENOMEM;
goto error_dev;
}
for (i = 0; i < ARRAY_SIZE(maps); i++)
input_set_capability(gf_dev->input, maps[i].type, maps[i].code);
gf_dev->input->name = GF_INPUT_NAME;
status = input_register_device(gf_dev->input);
if (status) {
pr_err("failed to register input device\n");
goto error_input;
}
}
#ifdef AP_CONTROL_CLK
pr_info("Get the clk resource.\n");
/* Enable spi clock */
if (gfspi_ioctl_clk_init(gf_dev))
goto gfspi_probe_clk_init_failed;
if (gfspi_ioctl_clk_enable(gf_dev))
goto gfspi_probe_clk_enable_failed;
spi_clock_set(gf_dev, 1000000);
#endif
gf_dev->notifier = goodix_noti_block;
fb_register_client(&gf_dev->notifier);
gf_dev->irq = gf_irq_num(gf_dev);
status = request_threaded_irq(gf_dev->irq, NULL, gf_irq,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"gf", gf_dev);
if (status) {
pr_err("failed to request IRQ:%d\n", gf_dev->irq);
goto err_irq;
}
enable_irq_wake(gf_dev->irq);
gf_dev->irq_enabled = 1;
gf_disable_irq(gf_dev);
device_init_wakeup(dev, true);
pr_info("version V%d.%d.%02d.%02d\n", VER_MAJOR,
VER_MINOR, PATCH_LEVEL, EXTEND_VER);
pr_info("gf_probe: status = %d\n", status);
return status;
err_irq:
input_unregister_device(gf_dev->input);
#ifdef AP_CONTROL_CLK
gfspi_probe_clk_enable_failed:
gfspi_ioctl_clk_uninit(gf_dev);
gfspi_probe_clk_init_failed:
#endif
error_input:
if (gf_dev->input != NULL)
input_free_device(gf_dev->input);
error_dev:
if (gf_dev->devt != 0) {
pr_info("Err: status = %d\n", status);
mutex_lock(&device_list_lock);
list_del(&gf_dev->device_entry);
device_destroy(gf_class, gf_dev->devt);
clear_bit(MINOR(gf_dev->devt), minors);
mutex_unlock(&device_list_lock);
}
error_hw:
gf_cleanup(gf_dev);
return status;
}
#if defined(USE_SPI_BUS)
static int gf_remove(struct spi_device *spi)
#elif defined(USE_PLATFORM_BUS)
static int gf_remove(struct platform_device *pdev)
#endif
{
struct gf_dev *gf_dev = &gf;
struct device *dev = &gf_dev->spi->dev;
device_init_wakeup(dev, false);
/* make sure ops on existing fds can abort cleanly */
if (gf_dev->irq)
free_irq(gf_dev->irq, gf_dev);
if (gf_dev->input != NULL)
input_unregister_device(gf_dev->input);
input_free_device(gf_dev->input);
/* prevent new opens */
mutex_lock(&device_list_lock);
list_del(&gf_dev->device_entry);
device_destroy(gf_class, gf_dev->devt);
clear_bit(MINOR(gf_dev->devt), minors);
if (gf_dev->users == 0)
gf_cleanup(gf_dev);
fb_unregister_client(&gf_dev->notifier);
mutex_unlock(&device_list_lock);
return 0;
}
static const struct of_device_id gx_match_table[] = {
{ .compatible = GF_SPIDEV_NAME },
{},
};
#if defined(USE_SPI_BUS)
static struct spi_driver gf_driver = {
#elif defined(USE_PLATFORM_BUS)
static struct platform_driver gf_driver = {
#endif
.driver = {
.name = GF_DEV_NAME,
.owner = THIS_MODULE,
.of_match_table = gx_match_table,
},
.probe = gf_probe,
.remove = gf_remove,
};
static int __init gf_init(void)
{
int status;
/* Claim our 256 reserved device numbers. Then register a class
* that will key udev/mdev to add/remove /dev nodes. Last, register
* the driver which manages those device numbers.
*/
BUILD_BUG_ON(N_SPI_MINORS > 256);
status = register_chrdev(SPIDEV_MAJOR, CHRD_DRIVER_NAME, &gf_fops);
if (status < 0) {
pr_warn("Failed to register char device!\n");
return status;
}
SPIDEV_MAJOR = status;
gf_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(gf_class)) {
unregister_chrdev(SPIDEV_MAJOR, gf_driver.driver.name);
pr_warn("Failed to create class.\n");
return PTR_ERR(gf_class);
}
#if defined(USE_PLATFORM_BUS)
status = platform_driver_register(&gf_driver);
#elif defined(USE_SPI_BUS)
status = spi_register_driver(&gf_driver);
#endif
if (status < 0) {
class_destroy(gf_class);
unregister_chrdev(SPIDEV_MAJOR, gf_driver.driver.name);
pr_warn("Failed to register SPI driver.\n");
}
#ifdef GF_NETLINK_ENABLE
netlink_init();
#endif
pr_info("status = 0x%x\n", status);
return 0;
}
module_init(gf_init);
static void __exit gf_exit(void)
{
#ifdef GF_NETLINK_ENABLE
netlink_exit();
#endif
#if defined(USE_PLATFORM_BUS)
platform_driver_unregister(&gf_driver);
#elif defined(USE_SPI_BUS)
spi_unregister_driver(&gf_driver);
#endif
class_destroy(gf_class);
unregister_chrdev(SPIDEV_MAJOR, gf_driver.driver.name);
}
module_exit(gf_exit);
MODULE_AUTHOR("Jiangtao Yi, <yijiangtao@goodix.com>");
MODULE_AUTHOR("Jandy Gou, <gouqingsong@goodix.com>");
MODULE_DESCRIPTION("goodix fingerprint sensor device driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,164 @@
/*
* driver definition for sensor driver
* Copyright (C) 2016 Goodix
*
* 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.
*
*/
#ifndef __GF_SPI_H
#define __GF_SPI_H
#include <linux/types.h>
#include <linux/notifier.h>
/**********************************************************/
enum FP_MODE {
GF_IMAGE_MODE = 0,
GF_KEY_MODE,
GF_SLEEP_MODE,
GF_FF_MODE,
GF_DEBUG_MODE = 0x56
};
#define SUPPORT_NAV_EVENT
#if defined(SUPPORT_NAV_EVENT)
#define GF_NAV_INPUT_UP KEY_UP
#define GF_NAV_INPUT_DOWN KEY_DOWN
#define GF_NAV_INPUT_LEFT KEY_LEFT
#define GF_NAV_INPUT_RIGHT KEY_RIGHT
#define GF_NAV_INPUT_CLICK KEY_VOLUMEDOWN
#define GF_NAV_INPUT_DOUBLE_CLICK KEY_VOLUMEUP
#define GF_NAV_INPUT_LONG_PRESS KEY_SEARCH
#define GF_NAV_INPUT_HEAVY KEY_CHAT
#endif
#define GF_KEY_INPUT_HOME KEY_HOME
#define GF_KEY_INPUT_MENU KEY_MENU
#define GF_KEY_INPUT_BACK KEY_BACK
#define GF_KEY_INPUT_POWER KEY_POWER
#define GF_KEY_INPUT_CAMERA KEY_CAMERA
#if defined(SUPPORT_NAV_EVENT)
typedef enum gf_nav_event {
GF_NAV_NONE = 0,
GF_NAV_FINGER_UP,
GF_NAV_FINGER_DOWN,
GF_NAV_UP,
GF_NAV_DOWN,
GF_NAV_LEFT,
GF_NAV_RIGHT,
GF_NAV_CLICK,
GF_NAV_HEAVY,
GF_NAV_LONG_PRESS,
GF_NAV_DOUBLE_CLICK,
} gf_nav_event_t;
#endif
typedef enum gf_key_event {
GF_KEY_NONE = 0,
GF_KEY_HOME,
GF_KEY_POWER,
GF_KEY_MENU,
GF_KEY_BACK,
GF_KEY_CAMERA,
} gf_key_event_t;
struct gf_key {
enum gf_key_event key;
uint32_t value; /* key down = 1, key up = 0 */
};
struct gf_key_map {
unsigned int type;
unsigned int code;
};
struct gf_ioc_chip_info {
unsigned char vendor_id;
unsigned char mode;
unsigned char operation;
unsigned char reserved[5];
};
#define GF_IOC_MAGIC 'g'
#define GF_IOC_INIT _IOR(GF_IOC_MAGIC, 0, uint8_t)
#define GF_IOC_EXIT _IO(GF_IOC_MAGIC, 1)
#define GF_IOC_RESET _IO(GF_IOC_MAGIC, 2)
#define GF_IOC_ENABLE_IRQ _IO(GF_IOC_MAGIC, 3)
#define GF_IOC_DISABLE_IRQ _IO(GF_IOC_MAGIC, 4)
#define GF_IOC_ENABLE_SPI_CLK _IOW(GF_IOC_MAGIC, 5, uint32_t)
#define GF_IOC_DISABLE_SPI_CLK _IO(GF_IOC_MAGIC, 6)
#define GF_IOC_ENABLE_POWER _IO(GF_IOC_MAGIC, 7)
#define GF_IOC_DISABLE_POWER _IO(GF_IOC_MAGIC, 8)
#define GF_IOC_INPUT_KEY_EVENT _IOW(GF_IOC_MAGIC, 9, struct gf_key)
#define GF_IOC_ENTER_SLEEP_MODE _IO(GF_IOC_MAGIC, 10)
#define GF_IOC_GET_FW_INFO _IOR(GF_IOC_MAGIC, 11, uint8_t)
#define GF_IOC_REMOVE _IO(GF_IOC_MAGIC, 12)
#define GF_IOC_CHIP_INFO _IOW(GF_IOC_MAGIC, 13, struct gf_ioc_chip_info)
#if defined(SUPPORT_NAV_EVENT)
#define GF_IOC_NAV_EVENT _IOW(GF_IOC_MAGIC, 14, gf_nav_event_t)
#define GF_IOC_MAXNR 15 /* THIS MACRO IS NOT USED NOW... */
#else
#define GF_IOC_MAXNR 14 /* THIS MACRO IS NOT USED NOW... */
#endif
/*#define AP_CONTROL_CLK 1*/
#define USE_PLATFORM_BUS 1
/*#define USE_SPI_BUS 1*/
/* #define GF_FASYNC 1*/ /*If support fasync mechanism.*/
#define GF_NETLINK_ENABLE 1
#define GF_NET_EVENT_IRQ 1
#define GF_NET_EVENT_FB_BLACK 2
#define GF_NET_EVENT_FB_UNBLACK 3
#define NETLINK_TEST 25
struct gf_dev {
dev_t devt;
struct list_head device_entry;
#if defined(USE_SPI_BUS)
struct spi_device *spi;
#elif defined(USE_PLATFORM_BUS)
struct platform_device *spi;
#endif
struct clk *core_clk;
struct clk *iface_clk;
struct input_dev *input;
/* buffer is NULL unless this device is open (users > 0) */
unsigned int users;
signed int irq_gpio;
signed int reset_gpio;
signed int pwr_gpio;
int irq;
int irq_enabled;
int clk_enabled;
int power_enabled;
#ifdef GF_FASYNC
struct fasync_struct *async;
#endif
struct notifier_block notifier;
char fb_black;
};
int gf_parse_dts(struct gf_dev *gf_dev);
void gf_cleanup(struct gf_dev *gf_dev);
int gf_power_on(struct gf_dev *gf_dev);
int gf_power_off(struct gf_dev *gf_dev);
int gf_hw_reset(struct gf_dev *gf_dev, unsigned int delay_ms);
int gf_irq_num(struct gf_dev *gf_dev);
int sendnlmsg(char *msg);
int netlink_init(void);
void netlink_exit(void);
#endif /*__GF_SPI_H*/

View file

@ -0,0 +1,112 @@
/*
* netlink interface
* Copyright (C) 2016 Goodix
*
* 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/init.h>
#include <linux/module.h>
#include <linux/timer.h>
#include <linux/time.h>
#include <linux/types.h>
#include <net/sock.h>
#include <net/netlink.h>
#include "gf_spi.h"
#define NETLINK_TEST 25
#define MAX_MSGSIZE 32
static int pid = -1;
static struct sock *nl_sk;
int sendnlmsg(char *msg)
{
struct sk_buff *skb;
struct nlmsghdr *nlh;
int len = NLMSG_SPACE(MAX_MSGSIZE);
int ret = 0;
if (!msg || !nl_sk || !pid)
return -ENODEV;
skb = alloc_skb(len, GFP_ATOMIC);
if (!skb)
return -ENOMEM;
nlh = nlmsg_put(skb, 0, 0, 0, MAX_MSGSIZE, 0);
if (!nlh) {
kfree_skb(skb);
return -EMSGSIZE;
}
NETLINK_CB(skb).portid = 0;
NETLINK_CB(skb).dst_group = 0;
memcpy(NLMSG_DATA(nlh), msg, sizeof(char));
pr_debug("send message: %d\n", *(char *)NLMSG_DATA(nlh));
ret = netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);
if (ret > 0)
ret = 0;
return ret;
}
static void nl_data_ready(struct sk_buff *__skb)
{
struct sk_buff *skb;
struct nlmsghdr *nlh;
char str[100];
skb = skb_get(__skb);
if (skb->len >= NLMSG_SPACE(0)) {
nlh = nlmsg_hdr(skb);
memcpy(str, NLMSG_DATA(nlh), sizeof(str));
pid = nlh->nlmsg_pid;
}
kfree_skb(skb);
}
int netlink_init(void)
{
struct netlink_kernel_cfg netlink_cfg;
memset(&netlink_cfg, 0, sizeof(struct netlink_kernel_cfg));
netlink_cfg.groups = 0;
netlink_cfg.flags = 0;
netlink_cfg.input = nl_data_ready;
netlink_cfg.cb_mutex = NULL;
nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST,
&netlink_cfg);
if (!nl_sk) {
pr_err("create netlink socket error\n");
return 1;
}
return 0;
}
void netlink_exit(void)
{
if (nl_sk != NULL) {
netlink_kernel_release(nl_sk);
nl_sk = NULL;
}
pr_info("self module exited\n");
}

View file

@ -0,0 +1,155 @@
/*
* platform indepent driver interface
* Copyright (C) 2016 Goodix
*
* 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/delay.h>
#include <linux/workqueue.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/timer.h>
#include <linux/err.h>
#include "gf_spi.h"
#if defined(USE_SPI_BUS)
#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>
#elif defined(USE_PLATFORM_BUS)
#include <linux/platform_device.h>
#endif
int gf_parse_dts(struct gf_dev *gf_dev)
{
int rc = 0;
struct device *dev = &gf_dev->spi->dev;
struct device_node *np = dev->of_node;
gf_dev->pwr_gpio = of_get_named_gpio(np, "fp-gpio-ven", 0);
if (gf_dev->pwr_gpio < 0) {
pr_warn("failed to get pwr gpio!\n");
}
if (gpio_is_valid(gf_dev->pwr_gpio)) {
rc = devm_gpio_request(dev, gf_dev->pwr_gpio, "goodix_pwr");
if (rc) {
pr_err("failed to request pwr gpio, rc = %d\n", rc);
goto err_pwr;
}
gpio_direction_output(gf_dev->pwr_gpio, 1);
msleep(10);
}
gf_dev->reset_gpio = of_get_named_gpio(np, "fp-gpio-reset", 0);
if (gf_dev->reset_gpio < 0) {
pr_err("failed to get reset gpio!\n");
return gf_dev->reset_gpio;
}
rc = devm_gpio_request(dev, gf_dev->reset_gpio, "goodix_reset");
if (rc) {
pr_err("failed to request reset gpio, rc = %d\n", rc);
goto err_reset;
}
gpio_direction_output(gf_dev->reset_gpio, 1);
gf_dev->irq_gpio = of_get_named_gpio(np, "fp-gpio-irq", 0);
if (gf_dev->irq_gpio < 0) {
pr_err("failed to get irq gpio!\n");
return gf_dev->irq_gpio;
}
rc = devm_gpio_request(dev, gf_dev->irq_gpio, "goodix_irq");
if (rc) {
pr_err("failed to request irq gpio, rc = %d\n", rc);
goto err_irq;
}
gpio_direction_input(gf_dev->irq_gpio);
err_irq:
devm_gpio_free(dev, gf_dev->reset_gpio);
err_reset:
if (gpio_is_valid(gf_dev->pwr_gpio))
devm_gpio_free(dev, gf_dev->pwr_gpio);
err_pwr:
return rc;
}
void gf_cleanup(struct gf_dev *gf_dev)
{
pr_info("[info] %s\n", __func__);
if (gpio_is_valid(gf_dev->irq_gpio)) {
gpio_free(gf_dev->irq_gpio);
pr_info("remove irq_gpio success\n");
}
if (gpio_is_valid(gf_dev->reset_gpio)) {
gpio_free(gf_dev->reset_gpio);
pr_info("remove reset_gpio success\n");
}
if (gpio_is_valid(gf_dev->pwr_gpio)) {
gpio_free(gf_dev->pwr_gpio);
pr_info("remove pwr_gpio success\n");
}
}
int gf_power_on(struct gf_dev *gf_dev)
{
int rc = 0;
if (!gf_dev->power_enabled) {
gf_dev->power_enabled = 1;
/* TODO: add your power control here */
}
return rc;
}
int gf_power_off(struct gf_dev *gf_dev)
{
int rc = 0;
if (gf_dev->power_enabled) {
gf_dev->power_enabled = 0;
/* TODO: add your power control here */
}
return rc;
}
int gf_hw_reset(struct gf_dev *gf_dev, unsigned int delay_ms)
{
if (!gf_dev) {
pr_err("Input buff is NULL.\n");
return -ENODEV;
}
gpio_direction_output(gf_dev->reset_gpio, 1);
gpio_set_value(gf_dev->reset_gpio, 0);
mdelay(3);
gpio_set_value(gf_dev->reset_gpio, 1);
mdelay(delay_ms);
return 0;
}
int gf_irq_num(struct gf_dev *gf_dev)
{
if (!gf_dev) {
pr_err("Input buff is NULL.\n");
return -ENODEV;
} else {
return gpio_to_irq(gf_dev->irq_gpio);
}
}