drivers: misc: Import xiaomi changes
The Patch based on QualComm release TAG:LA.UM.8.15.r1-06600-KAMORTA.0 Change-Id: I2d074674b2b77c49f35b7a2825da63d160d644fb Signed-off-by: starlight5234 <starlight5234@protonmail.ch>
This commit is contained in:
parent
2784ac0278
commit
c78d6785f5
33 changed files with 5044 additions and 119 deletions
|
@ -577,6 +577,18 @@ config OKL4_RINGBUF
|
|||
Say Y here if you want to test communication between OKL4 guests
|
||||
using virtual interrupts and shared memory.
|
||||
|
||||
config SIMTRAY_STATUS
|
||||
tristate "Xiaomi SIM tray status"
|
||||
default n
|
||||
help
|
||||
Say 'y' here to support SIM tray GPIO detection
|
||||
|
||||
config DPDT_STATUS
|
||||
tristate "Xiaomi dpdt status"
|
||||
default n
|
||||
help
|
||||
Say 'y' here to support dpdt GPIO detection
|
||||
|
||||
config TEST_IRQ_REQUESTER
|
||||
tristate "Receive interrupt notifications in user-space"
|
||||
depends on DEBUG_FS
|
||||
|
@ -637,6 +649,11 @@ source "drivers/misc/cxl/Kconfig"
|
|||
source "drivers/misc/ocxl/Kconfig"
|
||||
source "drivers/misc/cardreader/Kconfig"
|
||||
source "drivers/misc/fpr_FingerprintCard/Kconfig"
|
||||
source "drivers/misc/goodix/Kconfig"
|
||||
source "drivers/misc/focaltech/Kconfig"
|
||||
source "drivers/misc/cdfingerfp/Kconfig"
|
||||
source "drivers/misc/xiaomi_fs/Kconfig"
|
||||
source "drivers/misc/hqsysfs/Kconfig"
|
||||
endmenu
|
||||
|
||||
config OKL4_USER_VIPC
|
||||
|
|
|
@ -68,6 +68,10 @@ obj-$(CONFIG_QPNP_MISC) += qpnp-misc.o
|
|||
obj-$(CONFIG_OKL4_USER_VIRQ) += okl4-virq.o
|
||||
obj-$(CONFIG_OKL4_RINGBUF) += okl4-ringbuf.o
|
||||
obj-$(CONFIG_TEST_IRQ_REQUESTER) += irq_requester.o
|
||||
obj-$(CONFIG_SIMTRAY_STATUS) += simtray.o
|
||||
obj-$(CONFIG_DPDT_STATUS) += dpdt.o
|
||||
obj-$(CONFIG_MI_FS) +=xiaomi_fs/
|
||||
obj-$(CONFIG_HQ_SYSFS_SUPPORT) += hqsysfs/
|
||||
|
||||
obj-$(CONFIG_OKL4_USER_VIPC) += okl4-vipc.o
|
||||
obj-$(CONFIG_OKL4_GUEST) += okl4-panic.o
|
||||
|
@ -76,3 +80,6 @@ obj-$(CONFIG_WIGIG_SENSING_SPI) += wigig_sensing.o
|
|||
obj-$(CONFIG_QTI_MAXIM_FAN_CONTROLLER) += max31760.o
|
||||
obj-$(CONFIG_QTI_XR_SMRTVWR_MISC) += qxr-stdalonevwr.o
|
||||
obj-$(CONFIG_FPR_FPC) += fpr_FingerprintCard/
|
||||
obj-y += goodix/
|
||||
obj-y += focaltech/
|
||||
obj-y += cdfingerfp/
|
||||
|
|
5
drivers/misc/cdfingerfp/Kconfig
Normal file
5
drivers/misc/cdfingerfp/Kconfig
Normal file
|
@ -0,0 +1,5 @@
|
|||
config CDFINGER_FINGERPRINT
|
||||
tristate "generic cdfinger fingerprint driver"
|
||||
default y
|
||||
help
|
||||
add support for cdfinger fingerprint driver.
|
4
drivers/misc/cdfingerfp/Makefile
Normal file
4
drivers/misc/cdfingerfp/Makefile
Normal file
|
@ -0,0 +1,4 @@
|
|||
#
|
||||
# Makefile for the fingerprint drivers.
|
||||
#
|
||||
obj-y += cdfingerfp.o
|
708
drivers/misc/cdfingerfp/cdfingerfp.c
Normal file
708
drivers/misc/cdfingerfp/cdfingerfp.c
Normal file
|
@ -0,0 +1,708 @@
|
|||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irqreturn.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/sched.h>
|
||||
#ifdef CONFIG_PM_WAKELOCKS
|
||||
#include <linux/pm_wakeup.h>
|
||||
#else
|
||||
#include <linux/wakelock.h>
|
||||
#endif
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/fs.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <linux/spi/spidev.h>
|
||||
#include <linux/semaphore.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/fcntl.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/signal.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/fb.h>
|
||||
#include <linux/notifier.h>
|
||||
|
||||
|
||||
typedef struct key_report {
|
||||
int key;
|
||||
int value;
|
||||
} key_report_t;
|
||||
|
||||
struct cdfinger_key_map {
|
||||
unsigned int type;
|
||||
unsigned int code;
|
||||
};
|
||||
|
||||
#define CDFINGER_IOCTL_MAGIC_NO 0xFB
|
||||
#define CDFINGER_INIT _IOW(CDFINGER_IOCTL_MAGIC_NO, 0, uint8_t)
|
||||
#define CDFINGER_GETIMAGE _IOW(CDFINGER_IOCTL_MAGIC_NO, 1, uint8_t)
|
||||
#define CDFINGER_INITERRUPT_MODE _IOW(CDFINGER_IOCTL_MAGIC_NO, 2, uint8_t)
|
||||
#define CDFINGER_INITERRUPT_KEYMODE _IOW(CDFINGER_IOCTL_MAGIC_NO, 3, uint8_t)
|
||||
#define CDFINGER_INITERRUPT_FINGERUPMODE _IOW(CDFINGER_IOCTL_MAGIC_NO, 4, uint8_t)
|
||||
#define CDFINGER_RELEASE_WAKELOCK _IO(CDFINGER_IOCTL_MAGIC_NO, 5)
|
||||
#define CDFINGER_CHECK_INTERRUPT _IO(CDFINGER_IOCTL_MAGIC_NO, 6)
|
||||
#define CDFINGER_SET_SPI_SPEED _IOW(CDFINGER_IOCTL_MAGIC_NO, 7, uint32_t)
|
||||
#define CDFINGER_REPORT_KEY_LEGACY _IOW(CDFINGER_IOCTL_MAGIC_NO, 10, uint8_t)
|
||||
#define CDFINGER_POWERDOWN _IO(CDFINGER_IOCTL_MAGIC_NO, 11)
|
||||
#define CDFINGER_GETID _IO(CDFINGER_IOCTL_MAGIC_NO, 12)
|
||||
|
||||
#define CDFINGER_INIT_GPIO _IO(CDFINGER_IOCTL_MAGIC_NO, 20)
|
||||
#define CDFINGER_INIT_IRQ _IO(CDFINGER_IOCTL_MAGIC_NO, 21)
|
||||
#define CDFINGER_POWER_ON _IO(CDFINGER_IOCTL_MAGIC_NO, 22)
|
||||
#define CDFINGER_RESET _IO(CDFINGER_IOCTL_MAGIC_NO, 23)
|
||||
#define CDFINGER_POWER_OFF _IO(CDFINGER_IOCTL_MAGIC_NO, 24)
|
||||
#define CDFINGER_RELEASE_DEVICE _IO(CDFINGER_IOCTL_MAGIC_NO, 25)
|
||||
|
||||
#define CDFINGER_DISABLE_IRQ _IO(CDFINGER_IOCTL_MAGIC_NO, 13)
|
||||
#define CDFINGER_HW_RESET _IOW(CDFINGER_IOCTL_MAGIC_NO, 14, uint8_t)
|
||||
#define CDFINGER_GET_STATUS _IO(CDFINGER_IOCTL_MAGIC_NO, 15)
|
||||
#define CDFINGER_REPORT_KEY _IOW(CDFINGER_IOCTL_MAGIC_NO, 19, key_report_t)
|
||||
#define CDFINGER_NEW_KEYMODE _IOW(CDFINGER_IOCTL_MAGIC_NO, 37, uint8_t)
|
||||
#define CDFINGER_WAKE_LOCK _IOW(CDFINGER_IOCTL_MAGIC_NO, 26, uint8_t)
|
||||
|
||||
/*if want change key value for event , do it*/
|
||||
#define CF_NAV_INPUT_UP 600
|
||||
#define CF_NAV_INPUT_DOWN 601
|
||||
#define CF_NAV_INPUT_LEFT 602
|
||||
#define CF_NAV_INPUT_RIGHT 603
|
||||
#define CF_NAV_INPUT_CLICK KEY_SELECT
|
||||
#define CF_NAV_INPUT_DOUBLE_CLICK KEY_VOLUMEUP
|
||||
#define CF_NAV_INPUT_LONG_PRESS 605
|
||||
|
||||
#define CF_KEY_INPUT_HOME KEY_HOME
|
||||
#define CF_KEY_INPUT_MENU KEY_MENU
|
||||
#define CF_KEY_INPUT_BACK KEY_BACK
|
||||
#define CF_KEY_INPUT_POWER KEY_POWER
|
||||
#define CF_KEY_INPUT_CAMERA KEY_CAMERA
|
||||
|
||||
#define DEVICE_NAME "fpsdev0"
|
||||
#define INPUT_DEVICE_NAME "cdfinger_input"
|
||||
|
||||
//#define SUPPORT_ID_NUM
|
||||
//#define POWER_GPIO
|
||||
#define POWER_REGULATOR
|
||||
|
||||
static int isInKeyMode; // key mode
|
||||
static int screen_status = 1; // screen on
|
||||
static u8 cdfinger_debug = 0x01;
|
||||
static int isInit;
|
||||
|
||||
#define CDFINGER_DBG(fmt, args...) \
|
||||
do { \
|
||||
if (cdfinger_debug & 0x01) \
|
||||
printk("[DBG][cdfinger]:%5d: <%s>" fmt, __LINE__, __func__, ##args); \
|
||||
} while (0)
|
||||
|
||||
#define CDFINGER_ERR(fmt, args...) \
|
||||
do { \
|
||||
printk("[DBG][cdfinger]:%5d: <%s>" fmt, __LINE__, __func__, ##args); \
|
||||
} while (0)
|
||||
|
||||
struct cdfingerfp_data {
|
||||
struct platform_device *cdfinger_dev;
|
||||
struct miscdevice *miscdev;
|
||||
#ifdef SUPPORT_ID_NUM
|
||||
u32 id_num;
|
||||
u8 chip_id;
|
||||
// struct pinctrl *fps_pinctrl;
|
||||
// struct pinctrl_state *fps_id_high;
|
||||
#endif
|
||||
u32 irq_num;
|
||||
u32 reset_num;
|
||||
#ifdef POWER_GPIO
|
||||
u32 pwr_num;
|
||||
#endif
|
||||
#ifdef POWER_REGULATOR
|
||||
struct regulator *vdd;
|
||||
#endif
|
||||
struct fasync_struct *async_queue;
|
||||
#ifdef CONFIG_PM_WAKELOCKS
|
||||
struct wakeup_source cdfinger_ws;
|
||||
#else
|
||||
struct wake_lock cdfinger_lock;
|
||||
#endif
|
||||
|
||||
struct notifier_block notifier;
|
||||
struct mutex buf_lock;
|
||||
struct input_dev *cdfinger_input;
|
||||
} *g_cdfingerfp_data;
|
||||
|
||||
static struct cdfinger_key_map maps[] = {
|
||||
{ EV_KEY, CF_KEY_INPUT_HOME },
|
||||
{ EV_KEY, CF_KEY_INPUT_MENU },
|
||||
{ EV_KEY, CF_KEY_INPUT_BACK },
|
||||
{ EV_KEY, CF_KEY_INPUT_POWER },
|
||||
|
||||
{ EV_KEY, CF_NAV_INPUT_UP },
|
||||
{ EV_KEY, CF_NAV_INPUT_DOWN },
|
||||
{ EV_KEY, CF_NAV_INPUT_RIGHT },
|
||||
{ EV_KEY, CF_NAV_INPUT_LEFT },
|
||||
{ EV_KEY, CF_KEY_INPUT_CAMERA },
|
||||
{ EV_KEY, CF_NAV_INPUT_CLICK },
|
||||
{ EV_KEY, CF_NAV_INPUT_DOUBLE_CLICK },
|
||||
{ EV_KEY, CF_NAV_INPUT_LONG_PRESS },
|
||||
};
|
||||
|
||||
static int cdfinger_init_gpio(struct cdfingerfp_data *cdfinger)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
CDFINGER_DBG("enter\n");
|
||||
#ifdef POWER_GPIO
|
||||
if (gpio_is_valid(cdfinger->pwr_num)) {
|
||||
err = gpio_request(cdfinger->pwr_num, "cdfinger-pwr");
|
||||
if (err) {
|
||||
gpio_free(cdfinger->pwr_num);
|
||||
err = gpio_request(cdfinger->pwr_num, "cdfinger-pwr");
|
||||
if (err) {
|
||||
CDFINGER_DBG("Could not request pwr gpio.\n");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
CDFINGER_DBG("not valid pwr gpio\n");
|
||||
return -EIO;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (gpio_is_valid(cdfinger->reset_num)) {
|
||||
err = gpio_request(cdfinger->reset_num, "cdfinger-reset");
|
||||
if (err) {
|
||||
gpio_free(cdfinger->reset_num);
|
||||
err = gpio_request(cdfinger->reset_num, "cdfinger-reset");
|
||||
if (err) {
|
||||
CDFINGER_ERR("Could not request reset gpio.\n");
|
||||
#ifdef POWER_GPIO
|
||||
gpio_free(cdfinger->pwr_num);
|
||||
#endif
|
||||
return err;
|
||||
}
|
||||
}
|
||||
gpio_direction_output(cdfinger->reset_num, 1);
|
||||
} else {
|
||||
CDFINGER_ERR("not valid reset gpio\n");
|
||||
#ifdef POWER_GPIO
|
||||
gpio_free(cdfinger->pwr_num);
|
||||
#endif
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (gpio_is_valid(cdfinger->irq_num)) {
|
||||
err = gpio_request(cdfinger->irq_num, "cdfinger-irq");
|
||||
if (err) {
|
||||
gpio_free(cdfinger->irq_num);
|
||||
err = gpio_request(cdfinger->irq_num, "cdfinger-irq");
|
||||
if (err) {
|
||||
CDFINGER_ERR("Could not request irq gpio.\n");
|
||||
gpio_free(cdfinger->reset_num);
|
||||
#ifdef POWER_GPIO
|
||||
gpio_free(cdfinger->pwr_num);
|
||||
#endif
|
||||
return err;
|
||||
}
|
||||
}
|
||||
gpio_direction_input(cdfinger->irq_num);
|
||||
} else {
|
||||
CDFINGER_ERR(KERN_ERR "not valid irq gpio\n");
|
||||
gpio_free(cdfinger->reset_num);
|
||||
#ifdef POWER_GPIO
|
||||
gpio_free(cdfinger->pwr_num);
|
||||
#endif
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cdfinger_free_gpio(struct cdfingerfp_data *cdfinger)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
CDFINGER_DBG("enter\n");
|
||||
|
||||
if (gpio_is_valid(cdfinger->irq_num)) {
|
||||
gpio_free(cdfinger->irq_num);
|
||||
if (isInit == 1) {
|
||||
free_irq(gpio_to_irq(cdfinger->irq_num), (void *)cdfinger);
|
||||
isInit = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (gpio_is_valid(cdfinger->reset_num)) {
|
||||
gpio_free(cdfinger->reset_num);
|
||||
}
|
||||
#ifdef POWER_GPIO
|
||||
if (gpio_is_valid(cdfinger->pwr_num)) {
|
||||
gpio_free(cdfinger->pwr_num);
|
||||
}
|
||||
#endif
|
||||
return err;
|
||||
}
|
||||
|
||||
static void cdfinger_reset(struct cdfingerfp_data *pdata, int ms)
|
||||
{
|
||||
gpio_set_value(pdata->reset_num, 1);
|
||||
mdelay(ms);
|
||||
gpio_set_value(pdata->reset_num, 0);
|
||||
mdelay(ms);
|
||||
gpio_set_value(pdata->reset_num, 1);
|
||||
mdelay(ms);
|
||||
}
|
||||
|
||||
static int cdfinger_parse_dts(struct device *dev, struct cdfingerfp_data *cdfinger)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
CDFINGER_DBG("enter\n");
|
||||
#ifdef POWER_GPIO
|
||||
cdfinger->pwr_num = of_get_named_gpio(dev->of_node, "cdfinger,pwr_gpio", 0);
|
||||
#endif
|
||||
cdfinger->reset_num = of_get_named_gpio(dev->of_node, "cdfinger,reset_gpio", 0);
|
||||
cdfinger->irq_num = of_get_named_gpio(dev->of_node, "cdfinger,irq_gpio", 0);
|
||||
#ifdef POWER_REGULATOR
|
||||
cdfinger->vdd = regulator_get(dev, "vdd");
|
||||
#endif
|
||||
|
||||
#ifdef SUPPORT_ID_NUM
|
||||
cdfinger->id_num = of_get_named_gpio(dev->of_node, "cdfinger,id_gpio", 0);
|
||||
//cdfinger->fps_pinctrl = devm_pinctrl_get(dev);
|
||||
//if (IS_ERR(cdfinger->fps_pinctrl))
|
||||
//{
|
||||
// CDFINGER_ERR("pinctrl get failed!\n");
|
||||
// err = -1;
|
||||
//}
|
||||
#endif
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cdfinger_power_on(struct cdfingerfp_data *pdata)
|
||||
{
|
||||
int ret = 0;
|
||||
#ifdef POWER_GPIO
|
||||
gpio_direction_output(pdata->pwr_num, 1);
|
||||
#endif
|
||||
#ifdef POWER_REGULATOR
|
||||
regulator_set_voltage(pdata->vdd, 0, 2800000);
|
||||
ret = regulator_enable(pdata->vdd);
|
||||
if (ret) {
|
||||
CDFINGER_ERR("enable regulato fail\n");
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
mdelay(1);
|
||||
gpio_set_value(pdata->reset_num, 1);
|
||||
msleep(10);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cdfinger_power_off(struct cdfingerfp_data *pdata)
|
||||
{
|
||||
#ifdef POWER_GPIO
|
||||
gpio_direction_output(pdata->pwr_num, 0);
|
||||
#endif
|
||||
#ifdef POWER_REGULATOR
|
||||
regulator_disable(pdata->vdd);
|
||||
#endif
|
||||
mdelay(1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cdfinger_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
CDFINGER_DBG("enter\n");
|
||||
|
||||
file->private_data = g_cdfingerfp_data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cdfinger_async_fasync(int fd, struct file *file, int mode)
|
||||
{
|
||||
struct cdfingerfp_data *cdfingerfp = g_cdfingerfp_data;
|
||||
return fasync_helper(fd, file, mode, &cdfingerfp->async_queue);
|
||||
}
|
||||
|
||||
static int cdfinger_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct cdfingerfp_data *cdfingerfp = file->private_data;
|
||||
|
||||
CDFINGER_DBG("enter\n");
|
||||
|
||||
if (NULL == cdfingerfp) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
file->private_data = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cdfinger_async_report(void)
|
||||
{
|
||||
struct cdfingerfp_data *cdfingerfp = g_cdfingerfp_data;
|
||||
#ifdef CONFIG_PM_WAKELOCKS
|
||||
__pm_wakeup_event(&cdfingerfp->cdfinger_ws, 1000);
|
||||
#else
|
||||
wake_lock_timeout(&cdfingerfp->cdfinger_lock, msecs_to_jiffies(1000));
|
||||
#endif
|
||||
kill_fasync(&cdfingerfp->async_queue, SIGIO, POLL_IN);
|
||||
}
|
||||
|
||||
static irqreturn_t cdfinger_eint_handler(int irq, void *dev_id)
|
||||
{
|
||||
cdfinger_async_report();
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int cdfinger_init_irq(struct cdfingerfp_data *pdata)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
CDFINGER_DBG("enter\n");
|
||||
|
||||
if (isInit == 1)
|
||||
return 0;
|
||||
|
||||
error = request_irq(gpio_to_irq(pdata->irq_num), cdfinger_eint_handler, IRQF_TRIGGER_RISING, "cdfinger_eint", NULL);
|
||||
if (error < 0) {
|
||||
CDFINGER_ERR("irq init err\n");
|
||||
return error;
|
||||
}
|
||||
enable_irq_wake(gpio_to_irq(pdata->irq_num));
|
||||
isInit = 1;
|
||||
return error;
|
||||
}
|
||||
|
||||
static void cdfinger_wake_lock(struct cdfingerfp_data *pdata, int arg)
|
||||
{
|
||||
if (arg) {
|
||||
#ifdef CONFIG_PM_WAKELOCKS
|
||||
__pm_stay_awake(&pdata->cdfinger_ws);
|
||||
#else
|
||||
wake_lock(&pdata->cdfinger_lock);
|
||||
#endif
|
||||
} else {
|
||||
#ifdef CONFIG_PM_WAKELOCKS
|
||||
__pm_relax(&pdata->cdfinger_ws);
|
||||
__pm_wakeup_event(&pdata->cdfinger_ws, 3000);
|
||||
#else
|
||||
wake_unlock(&pdata->cdfinger_lock);
|
||||
wake_lock_timeout(&pdata->cdfinger_lock, msecs_to_jiffies(3000));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static int cdfinger_report_key(struct cdfingerfp_data *cdfinger, unsigned long arg)
|
||||
{
|
||||
int error = -1;
|
||||
key_report_t report;
|
||||
if (copy_from_user(&report, (key_report_t *)arg, sizeof(key_report_t))) {
|
||||
CDFINGER_ERR("%s err\n", __func__);
|
||||
return error;
|
||||
}
|
||||
switch (report.key) {
|
||||
case KEY_UP:
|
||||
report.key = CF_NAV_INPUT_UP;
|
||||
break;
|
||||
case KEY_DOWN:
|
||||
report.key = CF_NAV_INPUT_DOWN;
|
||||
break;
|
||||
case KEY_RIGHT:
|
||||
report.key = CF_NAV_INPUT_RIGHT;
|
||||
break;
|
||||
case KEY_LEFT:
|
||||
report.key = CF_NAV_INPUT_LEFT;
|
||||
break;
|
||||
case KEY_F11:
|
||||
report.key = CF_NAV_INPUT_CLICK;
|
||||
break;
|
||||
case KEY_F12:
|
||||
report.key = CF_NAV_INPUT_LONG_PRESS;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
input_report_key(cdfinger->cdfinger_input, report.key, !!report.value);
|
||||
input_sync(cdfinger->cdfinger_input);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long cdfinger_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
struct cdfingerfp_data *cdfinger = filp->private_data;
|
||||
|
||||
mutex_lock(&cdfinger->buf_lock);
|
||||
switch (cmd) {
|
||||
case CDFINGER_INIT_GPIO:
|
||||
err = cdfinger_init_gpio(cdfinger);
|
||||
break;
|
||||
case CDFINGER_INIT_IRQ:
|
||||
err = cdfinger_init_irq(cdfinger);
|
||||
#ifdef SUPPORT_ID_NUM
|
||||
cdfinger->chip_id = 0x98;
|
||||
#endif
|
||||
break;
|
||||
case CDFINGER_RELEASE_DEVICE:
|
||||
cdfinger_free_gpio(cdfinger);
|
||||
#ifdef SUPPORT_ID_NUM
|
||||
cdfinger->chip_id = 0x00;
|
||||
#endif
|
||||
misc_deregister(cdfinger->miscdev);
|
||||
break;
|
||||
case CDFINGER_WAKE_LOCK:
|
||||
cdfinger_wake_lock(cdfinger, arg);
|
||||
break;
|
||||
case CDFINGER_POWER_ON:
|
||||
err = cdfinger_power_on(cdfinger);
|
||||
break;
|
||||
case CDFINGER_POWER_OFF:
|
||||
err = cdfinger_power_off(cdfinger);
|
||||
break;
|
||||
case CDFINGER_RESET:
|
||||
cdfinger_reset(cdfinger, 1);
|
||||
break;
|
||||
case CDFINGER_INITERRUPT_MODE:
|
||||
isInKeyMode = 1; // not key mode
|
||||
cdfinger_reset(cdfinger, 1);
|
||||
break;
|
||||
case CDFINGER_NEW_KEYMODE:
|
||||
isInKeyMode = 0;
|
||||
cdfinger_reset(cdfinger, 1);
|
||||
break;
|
||||
case CDFINGER_HW_RESET:
|
||||
cdfinger_reset(cdfinger, arg);
|
||||
break;
|
||||
case CDFINGER_GET_STATUS:
|
||||
err = screen_status;
|
||||
break;
|
||||
case CDFINGER_REPORT_KEY:
|
||||
err = cdfinger_report_key(cdfinger, arg);
|
||||
break;
|
||||
case CDFINGER_GETID:
|
||||
#ifdef SUPPORT_ID_NUM
|
||||
err = cdfinger->chip_id;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&cdfinger->buf_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct file_operations cdfinger_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = cdfinger_open,
|
||||
.unlocked_ioctl = cdfinger_ioctl,
|
||||
.release = cdfinger_release,
|
||||
.fasync = cdfinger_async_fasync,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = cdfinger_ioctl,
|
||||
#endif
|
||||
};
|
||||
|
||||
static struct miscdevice st_cdfinger_dev = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = DEVICE_NAME,
|
||||
.fops = &cdfinger_fops,
|
||||
};
|
||||
|
||||
static int cdfinger_fb_notifier_callback(struct notifier_block *self,
|
||||
unsigned long event, void *data)
|
||||
{
|
||||
struct fb_event *evdata = data;
|
||||
unsigned int blank;
|
||||
int retval = 0;
|
||||
|
||||
if (event != FB_EVENT_BLANK /* FB_EARLY_EVENT_BLANK */) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
blank = *(int *)evdata->data;
|
||||
|
||||
switch (blank) {
|
||||
case FB_BLANK_UNBLANK:
|
||||
mutex_lock(&g_cdfingerfp_data->buf_lock);
|
||||
screen_status = 1;
|
||||
if (isInKeyMode == 0)
|
||||
cdfinger_async_report();
|
||||
mutex_unlock(&g_cdfingerfp_data->buf_lock);
|
||||
break;
|
||||
case FB_BLANK_POWERDOWN:
|
||||
mutex_lock(&g_cdfingerfp_data->buf_lock);
|
||||
screen_status = 0;
|
||||
if (isInKeyMode == 0)
|
||||
cdfinger_async_report();
|
||||
mutex_unlock(&g_cdfingerfp_data->buf_lock);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
#ifdef SUPPORT_ID_NUM
|
||||
static int cdfinger_support_id(struct cdfingerfp_data *cdfinger)
|
||||
{
|
||||
int err = 0;
|
||||
//cdfinger->fps_id_high = pinctrl_lookup_state(cdfinger->fps_pinctrl, "cdfinger_id_pin");
|
||||
//if (IS_ERR(cdfinger->fps_id_high)){
|
||||
// CDFINGER_ERR("look up state err\n");
|
||||
// return -1;
|
||||
//}
|
||||
//pinctrl_select_state(cdfinger->fps_pinctrl, cdfinger->fps_id_high);
|
||||
if (gpio_is_valid(cdfinger->id_num)) {
|
||||
err = gpio_request(cdfinger->id_num, "cdfinger-id");
|
||||
if (err) {
|
||||
gpio_free(cdfinger->id_num);
|
||||
err = gpio_request(cdfinger->id_num, "cdfinger-id");
|
||||
if (err) {
|
||||
CDFINGER_ERR("Could not request id gpio.\n");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
gpio_direction_input(cdfinger->id_num);
|
||||
} else {
|
||||
CDFINGER_ERR(KERN_ERR "not valid irq gpio\n");
|
||||
return -EIO;
|
||||
}
|
||||
err = gpio_get_value(cdfinger->id_num);
|
||||
gpio_free(cdfinger->id_num);
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int cdfinger_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct cdfingerfp_data *cdfingerdev = NULL;
|
||||
int status = -ENODEV;
|
||||
int i;
|
||||
CDFINGER_DBG("enter\n");
|
||||
|
||||
cdfingerdev = kzalloc(sizeof(struct cdfingerfp_data), GFP_KERNEL);
|
||||
cdfingerdev->cdfinger_dev = pdev;
|
||||
|
||||
status = cdfinger_parse_dts(&cdfingerdev->cdfinger_dev->dev, cdfingerdev);
|
||||
if (status) {
|
||||
CDFINGER_ERR("cdfinger parse err %d\n", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
#ifdef SUPPORT_ID_NUM
|
||||
status = cdfinger_support_id(cdfingerdev);
|
||||
if (status != 1) { // id pin is high , cdfinger
|
||||
CDFINGER_ERR("cdfinger support id error %d\n", status);
|
||||
return status;
|
||||
}
|
||||
#endif
|
||||
|
||||
status = misc_register(&st_cdfinger_dev);
|
||||
if (status) {
|
||||
CDFINGER_ERR("cdfinger misc register err%d\n", status);
|
||||
return status;
|
||||
}
|
||||
cdfingerdev->miscdev = &st_cdfinger_dev;
|
||||
mutex_init(&cdfingerdev->buf_lock);
|
||||
#ifdef CONFIG_PM_WAKELOCKS
|
||||
wakeup_source_init(&cdfingerdev->cdfinger_ws, "cdfinger wakelock");
|
||||
#else
|
||||
wake_lock_init(&cdfingerdev->cdfinger_lock, WAKE_LOCK_SUSPEND, "cdfinger wakelock");
|
||||
#endif
|
||||
cdfingerdev->cdfinger_input = input_allocate_device();
|
||||
if (!cdfingerdev->cdfinger_input) {
|
||||
CDFINGER_ERR("crate cdfinger_input faile!\n");
|
||||
goto unregister_dev;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(maps); i++)
|
||||
input_set_capability(cdfingerdev->cdfinger_input, maps[i].type, maps[i].code);
|
||||
cdfingerdev->cdfinger_input->name = INPUT_DEVICE_NAME;
|
||||
if (input_register_device(cdfingerdev->cdfinger_input)) {
|
||||
input_free_device(cdfingerdev->cdfinger_input);
|
||||
cdfingerdev->cdfinger_input = NULL;
|
||||
goto unregister_dev;
|
||||
}
|
||||
|
||||
cdfingerdev->notifier.notifier_call = cdfinger_fb_notifier_callback;
|
||||
fb_register_client(&cdfingerdev->notifier);
|
||||
|
||||
g_cdfingerfp_data = cdfingerdev;
|
||||
|
||||
return 0;
|
||||
|
||||
unregister_dev:
|
||||
#ifdef CONFIG_PM_WAKELOCKS
|
||||
wakeup_source_trash(&cdfingerdev->cdfinger_ws);
|
||||
#else
|
||||
wake_lock_destroy(&cdfingerdev->cdfinger_lock);
|
||||
#endif
|
||||
misc_deregister(&st_cdfinger_dev);
|
||||
kfree(cdfingerdev);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
static const struct of_device_id cdfinger_of_match[] = {
|
||||
{ .compatible = "cdfinger,fps998e", },
|
||||
{ .compatible = "cdfinger,fingerprint", },
|
||||
{},
|
||||
};
|
||||
|
||||
static const struct platform_device_id cdfinger_id[] = {
|
||||
{"cdfinger_fp", 0},
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver cdfinger_driver = {
|
||||
.driver = {
|
||||
.name = "cdfinger_fp",
|
||||
.of_match_table = cdfinger_of_match,
|
||||
},
|
||||
.id_table = cdfinger_id,
|
||||
.probe = cdfinger_probe,
|
||||
};
|
||||
|
||||
static int __init cdfinger_fp_init(void)
|
||||
{
|
||||
return platform_driver_register(&cdfinger_driver);
|
||||
}
|
||||
|
||||
static void __exit cdfinger_fp_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&cdfinger_driver);
|
||||
}
|
||||
|
||||
module_init(cdfinger_fp_init);
|
||||
|
||||
module_exit(cdfinger_fp_exit);
|
||||
|
||||
MODULE_DESCRIPTION("cdfinger Driver");
|
||||
MODULE_AUTHOR("cdfinger@cdfinger.com");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("cdfinger");
|
87
drivers/misc/dpdt.c
Normal file
87
drivers/misc/dpdt.c
Normal file
|
@ -0,0 +1,87 @@
|
|||
#include <linux/device.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
struct dpdt_data {
|
||||
struct device *dev;
|
||||
int status_gpio;
|
||||
};
|
||||
|
||||
static ssize_t dpdt_status_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct dpdt_data *data = dev_get_drvdata(dev);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n", gpio_get_value(data->status_gpio));
|
||||
}
|
||||
static DEVICE_ATTR(status, 0444, dpdt_status_show, NULL);
|
||||
|
||||
static int dpdt_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct dpdt_data *data;
|
||||
|
||||
pr_info("%s enter\n", __func__);
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(struct dpdt_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->status_gpio = of_get_named_gpio(np, "status-gpio", 0);
|
||||
if (data->status_gpio < 0)
|
||||
return -EINVAL;
|
||||
|
||||
ret = sysfs_create_file(&dev->kobj, &dev_attr_status.attr);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to create sysfs node.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
data->dev = dev;
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dpdt_remove(struct platform_device *pdev)
|
||||
{
|
||||
sysfs_remove_file(&pdev->dev.kobj, &dev_attr_status.attr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id dpdt_of_match[] = {
|
||||
{ .compatible = "xiaomi,dpdt-status", },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver dpdt_status_driver = {
|
||||
.driver = {
|
||||
.name = "dpdt-status",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(dpdt_of_match),
|
||||
},
|
||||
.probe = dpdt_probe,
|
||||
.remove = dpdt_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(dpdt_status_driver);
|
||||
MODULE_AUTHOR("Tao Jun<taojun@xiaomi.com>");
|
||||
MODULE_DESCRIPTION("Xiaomi dpdt status");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
|
||||
|
5
drivers/misc/focaltech/Kconfig
Normal file
5
drivers/misc/focaltech/Kconfig
Normal file
|
@ -0,0 +1,5 @@
|
|||
config FOCALTECH_FINGERPRINT
|
||||
tristate "generic focaltech fingerprint driver"
|
||||
default y
|
||||
help
|
||||
add support for focaltech fingerprint driver.
|
21
drivers/misc/focaltech/Makefile
Normal file
21
drivers/misc/focaltech/Makefile
Normal file
|
@ -0,0 +1,21 @@
|
|||
|
||||
ccflags-y += -D__FF_BUILD_DATE=\"$(shell date +%Y%m%d)\"
|
||||
|
||||
# Early-logging level for ff_hal, ff_ta and driver as well. Note that this
|
||||
# logging level only takes effect before ff_config module is loaded.
|
||||
#
|
||||
# Options:
|
||||
# 0 - FF_LOG_LEVEL_ALL, All
|
||||
# 1 - FF_LOG_LEVEL_VBS, Verbose
|
||||
# 2 - FF_LOG_LEVEL_DBG, Debug
|
||||
# 3 - FF_LOG_LEVEL_INF, Info
|
||||
# 4 - FF_LOG_LEVEL_WRN, Warning
|
||||
# 5 - FF_LOG_LEVEL_ERR, Error
|
||||
ccflags-y += -D__FF_EARLY_LOG_LEVEL=2
|
||||
|
||||
# Source files.
|
||||
obj-y := focaltech_fp.o
|
||||
focaltech_fp-objs := ff_ctl.o
|
||||
focaltech_fp-objs += plat-msm8916.o
|
||||
|
||||
|
723
drivers/misc/focaltech/ff_ctl.c
Normal file
723
drivers/misc/focaltech/ff_ctl.c
Normal file
|
@ -0,0 +1,723 @@
|
|||
/**
|
||||
* The device control driver for FocalTech's fingerprint sensor.
|
||||
*
|
||||
* Copyright (C) 2016-2017 FocalTech Systems Co., Ltd. All Rights Reserved.
|
||||
* Copyright (C) 2020 XiaoMi, Inc.
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/signal.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/bug.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/param.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/fb.h>
|
||||
#include <linux/notifier.h>
|
||||
#ifdef CONFIG_PM_WAKELOCKS
|
||||
#include <linux/pm_wakeup.h>
|
||||
#else
|
||||
#include <linux/wakelock.h>
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
#include <linux/compat.h>
|
||||
#endif
|
||||
|
||||
#include "ff_log.h"
|
||||
#include "ff_err.h"
|
||||
#include "ff_ctl.h"
|
||||
|
||||
/*
|
||||
* Define the driver version string.
|
||||
*/
|
||||
#define FF_DRV_VERSION "v2.1.2"
|
||||
|
||||
/*
|
||||
* Define the driver name.
|
||||
*/
|
||||
#define FF_DRV_NAME "focaltech_fp"
|
||||
|
||||
/*
|
||||
* Driver context definition and its singleton instance.
|
||||
*/
|
||||
typedef struct {
|
||||
struct miscdevice miscdev;
|
||||
int irq_num;
|
||||
struct work_struct work_queue;
|
||||
struct fasync_struct *async_queue;
|
||||
struct input_dev *input;
|
||||
struct notifier_block fb_notifier;
|
||||
#ifdef CONFIG_PM_WAKELOCKS
|
||||
struct wakeup_source wake_lock;
|
||||
#else
|
||||
struct wake_lock wake_lock;
|
||||
#endif
|
||||
bool b_driver_inited;
|
||||
bool b_config_dirtied;
|
||||
} ff_ctl_context_t;
|
||||
static ff_ctl_context_t *g_context;
|
||||
|
||||
/*
|
||||
* Driver configuration.
|
||||
*/
|
||||
static ff_driver_config_t driver_config;
|
||||
ff_driver_config_t *g_config;
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Logging driver to logcat through uevent mechanism.
|
||||
|
||||
# undef LOG_TAG
|
||||
#define LOG_TAG "ff_ctl"
|
||||
|
||||
/*
|
||||
* Log level can be runtime configurable by 'FF_IOC_SYNC_CONFIG'.
|
||||
*/
|
||||
ff_log_level_t g_log_level = 3;//__FF_EARLY_LOG_LEVEL;
|
||||
|
||||
int ff_log_printf(ff_log_level_t level, const char *tag, const char *fmt, ...)
|
||||
{
|
||||
/* Prepare the storage. */
|
||||
va_list ap;
|
||||
static char uevent_env_buf[128];
|
||||
char *ptr = uevent_env_buf;
|
||||
int n, available = sizeof(uevent_env_buf);
|
||||
|
||||
/* Fill logging level. */
|
||||
available -= snprintf(uevent_env_buf, 128, "FF_LOG=%1d", level);
|
||||
ptr += strlen(uevent_env_buf);
|
||||
|
||||
/* Fill logging message. */
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(ptr, available, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
/* Send to ff_device. */
|
||||
if (likely(g_context) && likely(g_config) && unlikely(g_config->logcat_driver)) {
|
||||
char *uevent_env[2] = {uevent_env_buf, NULL};
|
||||
kobject_uevent_env(&g_context->miscdev.this_device->kobj, KOBJ_CHANGE, uevent_env);
|
||||
}
|
||||
|
||||
/* Native output. */
|
||||
switch (level) {
|
||||
case FF_LOG_LEVEL_ERR:
|
||||
n = printk(KERN_ERR FF_DRV_NAME": %s\n", ptr);
|
||||
break;
|
||||
case FF_LOG_LEVEL_WRN:
|
||||
n = printk(KERN_WARNING FF_DRV_NAME": %s\n", ptr);
|
||||
break;
|
||||
case FF_LOG_LEVEL_INF:
|
||||
n = printk(KERN_INFO FF_DRV_NAME": %s\n", ptr);
|
||||
break;
|
||||
case FF_LOG_LEVEL_DBG:
|
||||
case FF_LOG_LEVEL_VBS:
|
||||
default:
|
||||
n = printk(KERN_DEBUG FF_DRV_NAME": %s\n", ptr);
|
||||
break;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
const char *ff_err_strerror(int err)
|
||||
{
|
||||
static char errstr[32] = {'\0', };
|
||||
|
||||
switch (err) {
|
||||
case FF_SUCCESS: return "Success";
|
||||
case FF_ERR_INTERNAL: return "Internal error";
|
||||
|
||||
/* Base on unix errno. */
|
||||
case FF_ERR_NOENT: return "No such file or directory";
|
||||
case FF_ERR_INTR: return "Interrupted";
|
||||
case FF_ERR_IO: return "I/O error";
|
||||
case FF_ERR_AGAIN: return "Try again";
|
||||
case FF_ERR_NOMEM: return "Out of memory";
|
||||
case FF_ERR_BUSY: return "Resource busy / Timeout";
|
||||
|
||||
/* Common error. */
|
||||
case FF_ERR_BAD_PARAMS: return "Bad parameter(s)";
|
||||
case FF_ERR_NULL_PTR: return "Null pointer";
|
||||
case FF_ERR_BUF_OVERFLOW: return "Buffer overflow";
|
||||
case FF_ERR_BAD_PROTOCOL: return "Bad protocol";
|
||||
case FF_ERR_SENSOR_SIZE: return "Wrong sensor dimension";
|
||||
case FF_ERR_NULL_DEVICE: return "Device not found";
|
||||
case FF_ERR_DEAD_DEVICE: return "Device is dead";
|
||||
case FF_ERR_REACH_LIMIT: return "Up to the limit";
|
||||
case FF_ERR_REE_TEMPLATE: return "Template store in REE";
|
||||
case FF_ERR_NOT_TRUSTED: return "Untrusted enrollment";
|
||||
|
||||
default:
|
||||
snprintf(errstr, 32, "%d", err);
|
||||
break;
|
||||
}
|
||||
|
||||
return (const char *)errstr;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/* See plat-xxxx.c for platform dependent implementation. */
|
||||
extern int ff_ctl_init_pins(int *irq_num);
|
||||
extern int ff_ctl_free_pins(void);
|
||||
#ifdef FF_SPI_SET
|
||||
extern int ff_ctl_enable_spiclk(bool on);
|
||||
#endif
|
||||
extern int ff_ctl_enable_power(bool on);
|
||||
extern int ff_ctl_reset_device(void);
|
||||
extern const char *ff_ctl_arch_str(void);
|
||||
|
||||
|
||||
#ifdef CHIP_TYPE_FT9304
|
||||
/* See chip-xxxx.c for chip dependent implementation. */
|
||||
extern int ff_chip_init(void);
|
||||
#endif
|
||||
|
||||
static int ff_ctl_enable_irq(bool on)
|
||||
{
|
||||
int err = 0;
|
||||
FF_LOGV("'%s' enter.", __func__);
|
||||
FF_LOGD("irq: '%s'.", on ? "enable" : "disabled");
|
||||
|
||||
if (unlikely(!g_context)) {
|
||||
return (-ENOSYS);
|
||||
}
|
||||
|
||||
if (on) {
|
||||
enable_irq(g_context->irq_num);
|
||||
} else {
|
||||
disable_irq(g_context->irq_num);
|
||||
}
|
||||
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void ff_ctl_device_event(struct work_struct *ws)
|
||||
{
|
||||
ff_ctl_context_t *ctx = container_of(ws, ff_ctl_context_t, work_queue);
|
||||
char *uevent_env[2] = {"FF_INTERRUPT", NULL};
|
||||
FF_LOGV("'%s' enter.", __func__);
|
||||
FF_LOGD("%s(irq = %d, ..) toggled.", __func__, ctx->irq_num);
|
||||
#ifdef CONFIG_PM_WAKELOCKS
|
||||
__pm_wakeup_event(&g_context->wake_lock, jiffies_to_msecs(2*HZ));
|
||||
#else
|
||||
wake_lock_timeout(&g_context->wake_lock, 2 * HZ); // 2 seconds.
|
||||
#endif
|
||||
kobject_uevent_env(&ctx->miscdev.this_device->kobj, KOBJ_CHANGE, uevent_env);
|
||||
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
}
|
||||
|
||||
static irqreturn_t ff_ctl_device_irq(int irq, void *dev_id)
|
||||
{
|
||||
ff_ctl_context_t *ctx = (ff_ctl_context_t *)dev_id;
|
||||
disable_irq_nosync(irq);
|
||||
|
||||
if (likely(irq == ctx->irq_num)) {
|
||||
if (g_config && g_config->enable_fasync && g_context->async_queue) {
|
||||
kill_fasync(&g_context->async_queue, SIGIO, POLL_IN);
|
||||
} else {
|
||||
schedule_work(&ctx->work_queue);
|
||||
}
|
||||
}
|
||||
|
||||
enable_irq(irq);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int ff_ctl_report_key_event(struct input_dev *input, ff_key_event_t *kevent)
|
||||
{
|
||||
int err = 0;
|
||||
FF_LOGV("'%s' enter.", __func__);
|
||||
|
||||
input_report_key(input, kevent->code, kevent->value);
|
||||
input_sync(input);
|
||||
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
return err;
|
||||
}
|
||||
|
||||
static const char *ff_ctl_get_version(void)
|
||||
{
|
||||
static char version[FF_DRV_VERSION_LEN] = {'\0', };
|
||||
FF_LOGV("'%s' enter.", __func__);
|
||||
|
||||
/* Version info. */
|
||||
version[0] = '\0';
|
||||
strlcat(version, FF_DRV_VERSION, FF_DRV_VERSION_LEN);
|
||||
#ifdef __FF_SVN_REV
|
||||
if (strlen(__FF_SVN_REV) > 0) {
|
||||
snprintf(version, FF_DRV_VERSION_LEN, "%s-r%s", version, __FF_SVN_REV);
|
||||
}
|
||||
#endif
|
||||
#ifdef __FF_BUILD_DATE
|
||||
strlcat(version, "-"__FF_BUILD_DATE, FF_DRV_VERSION_LEN);
|
||||
#endif
|
||||
snprintf(version, FF_DRV_VERSION_LEN, "%s-%s", version, ff_ctl_arch_str());
|
||||
FF_LOGD("version: '%s'.", version);
|
||||
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
return (const char *)version;
|
||||
}
|
||||
static char screen_state[1];
|
||||
static int ff_ctl_fb_notifier_callback(struct notifier_block *nb, unsigned long action, void *data)
|
||||
{
|
||||
struct fb_event *event;
|
||||
int blank;
|
||||
char *uevent_env[2];
|
||||
|
||||
/* If we aren't interested in this event, skip it immediately ... */
|
||||
if (action != FB_EVENT_BLANK /* FB_EARLY_EVENT_BLANK */) {
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
FF_LOGV("'%s' enter.", __func__);
|
||||
|
||||
event = (struct fb_event *)data;
|
||||
blank = *(int *)event->data;
|
||||
|
||||
switch (blank) {
|
||||
case FB_BLANK_UNBLANK:
|
||||
uevent_env[0] = "FF_SCREEN_ON";
|
||||
screen_state[0] = 0;
|
||||
break;
|
||||
case FB_BLANK_POWERDOWN:
|
||||
uevent_env[0] = "FF_SCREEN_OFF";
|
||||
screen_state[0] = 1;
|
||||
break;
|
||||
default:
|
||||
uevent_env[0] = "FF_SCREEN_??";
|
||||
break;
|
||||
}
|
||||
uevent_env[1] = NULL;
|
||||
// kobject_uevent_env(&g_context->miscdev.this_device->kobj, KOBJ_CHANGE, uevent_env);
|
||||
kill_fasync(&g_context->async_queue, SIGIO, POLL_IN);
|
||||
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static int ff_ctl_register_input(void)
|
||||
{
|
||||
int err = 0;
|
||||
FF_LOGV("'%s' enter.", __func__);
|
||||
|
||||
/* Allocate the input device. */
|
||||
g_context->input = input_allocate_device();
|
||||
if (!g_context->input) {
|
||||
FF_LOGE("input_allocate_device() failed.");
|
||||
return (-ENOMEM);
|
||||
}
|
||||
|
||||
/* Register the key event capabilities. */
|
||||
if (g_config) {
|
||||
input_set_capability(g_context->input, EV_KEY, g_config->keycode_nav_left);
|
||||
input_set_capability(g_context->input, EV_KEY, g_config->keycode_nav_right);
|
||||
input_set_capability(g_context->input, EV_KEY, g_config->keycode_nav_up);
|
||||
input_set_capability(g_context->input, EV_KEY, g_config->keycode_nav_down);
|
||||
input_set_capability(g_context->input, EV_KEY, g_config->keycode_double_click);
|
||||
input_set_capability(g_context->input, EV_KEY, g_config->keycode_click);
|
||||
input_set_capability(g_context->input, EV_KEY, g_config->keycode_long_press);
|
||||
input_set_capability(g_context->input, EV_KEY, g_config->keycode_simulation);
|
||||
}
|
||||
|
||||
/* Register the allocated input device. */
|
||||
g_context->input->name = "ff_key";
|
||||
err = input_register_device(g_context->input);
|
||||
if (err) {
|
||||
FF_LOGE("input_register_device(..) = %d.", err);
|
||||
input_free_device(g_context->input);
|
||||
g_context->input = NULL;
|
||||
return (-ENODEV);
|
||||
}
|
||||
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ff_ctl_free_driver(void)
|
||||
{
|
||||
int err = 0;
|
||||
FF_LOGV("'%s' enter.", __func__);
|
||||
|
||||
/* Unregister framebuffer event notifier. */
|
||||
err = fb_unregister_client(&g_context->fb_notifier);
|
||||
|
||||
#ifdef FF_SPI_SET
|
||||
/* Disable SPI clock. */
|
||||
err = ff_ctl_enable_spiclk(0);
|
||||
#endif
|
||||
|
||||
/* Disable the fingerprint module's power. */
|
||||
err = ff_ctl_enable_power(0);
|
||||
|
||||
#ifdef FF_SPI_SET
|
||||
/* Unregister the spidev device. */
|
||||
err = ff_spi_free();
|
||||
#endif
|
||||
|
||||
/* De-initialize the input subsystem. */
|
||||
if (g_context->input) {
|
||||
/*
|
||||
* Once input device was registered use input_unregister_device() and
|
||||
* memory will be freed once last reference to the device is dropped.
|
||||
*/
|
||||
input_unregister_device(g_context->input);
|
||||
g_context->input = NULL;
|
||||
}
|
||||
|
||||
/* Release IRQ resource. */
|
||||
if (g_context->irq_num > 0) {
|
||||
err = disable_irq_wake(g_context->irq_num);
|
||||
if (err) {
|
||||
FF_LOGE("disable_irq_wake(%d) = %d.", g_context->irq_num, err);
|
||||
}
|
||||
free_irq(g_context->irq_num, (void *)g_context);
|
||||
g_context->irq_num = -1;
|
||||
}
|
||||
|
||||
/* Release pins resource. */
|
||||
err = ff_ctl_free_pins();
|
||||
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ff_ctl_init_driver(void)
|
||||
{
|
||||
int err = 0;
|
||||
FF_LOGV("'%s' enter.", __func__);
|
||||
|
||||
if (unlikely(!g_context)) {
|
||||
return (-ENOSYS);
|
||||
}
|
||||
|
||||
do {
|
||||
/* Initialize the PWR/SPI/RST/INT pins resource. */
|
||||
err = ff_ctl_init_pins(&g_context->irq_num);
|
||||
if (err > 0) {
|
||||
g_context->b_config_dirtied = true;
|
||||
} else \
|
||||
if (err) {
|
||||
FF_LOGE("ff_ctl_init_pins(..) = %d.", err);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Register IRQ. */
|
||||
err = request_irq(g_context->irq_num, ff_ctl_device_irq,
|
||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT, "ff_irq", (void *)g_context);
|
||||
if (err) {
|
||||
FF_LOGE("request_irq(..) = %d.", err);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Wake up the system while receiving the interrupt. */
|
||||
err = enable_irq_wake(g_context->irq_num);
|
||||
if (err) {
|
||||
FF_LOGE("enable_irq_wake(%d) = %d.", g_context->irq_num, err);
|
||||
}
|
||||
|
||||
/* Register spidev device. For REE-Emulation solution only. */
|
||||
if (g_config && g_config->enable_spidev) {
|
||||
#ifdef FF_SPI_SET
|
||||
err = ff_spi_init();
|
||||
if (err) {
|
||||
FF_LOGE("ff_spi_init(..) = %d.", err);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} while (0);
|
||||
|
||||
if (err) {
|
||||
ff_ctl_free_driver();
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Initialize the input subsystem. */
|
||||
err = ff_ctl_register_input();
|
||||
if (err) {
|
||||
FF_LOGE("ff_ctl_init_input() = %d.", err);
|
||||
//return err;
|
||||
}
|
||||
|
||||
/* Enable the fingerprint module's power at system startup. */
|
||||
err = ff_ctl_enable_power(1);
|
||||
|
||||
#ifdef FF_SPI_SET
|
||||
/* Enable SPI clock. */
|
||||
err = ff_ctl_enable_spiclk(1);
|
||||
#endif
|
||||
|
||||
/* Register screen on/off callback. */
|
||||
g_context->fb_notifier.notifier_call = ff_ctl_fb_notifier_callback;
|
||||
err = fb_register_client(&g_context->fb_notifier);
|
||||
|
||||
g_context->b_driver_inited = true;
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
return err;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// struct file_operations fields.
|
||||
|
||||
static int ff_ctl_fasync(int fd, struct file *filp, int mode)
|
||||
{
|
||||
int err = 0;
|
||||
FF_LOGV("'%s' enter.", __func__);
|
||||
|
||||
FF_LOGD("%s: mode = 0x%08x.", __func__, mode);
|
||||
err = fasync_helper(fd, filp, mode, &g_context->async_queue);
|
||||
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
return err;
|
||||
}
|
||||
|
||||
static long ff_ctl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int err = 0;
|
||||
struct miscdevice *dev = (struct miscdevice *)filp->private_data;
|
||||
ff_ctl_context_t *ctx = container_of(dev, ff_ctl_context_t, miscdev);
|
||||
FF_LOGV("'%s' enter.", __func__);
|
||||
|
||||
#if 1
|
||||
if (g_log_level <= FF_LOG_LEVEL_DBG) {
|
||||
static const char *cmd_names[] = {
|
||||
"FF_IOC_INIT_DRIVER", "FF_IOC_FREE_DRIVER",
|
||||
"FF_IOC_RESET_DEVICE",
|
||||
"FF_IOC_ENABLE_IRQ", "FF_IOC_DISABLE_IRQ",
|
||||
"FF_IOC_ENABLE_SPI_CLK", "FF_IOC_DISABLE_SPI_CLK",
|
||||
"FF_IOC_ENABLE_POWER", "FF_IOC_DISABLE_POWER",
|
||||
"FF_IOC_REPORT_KEY_EVENT", "FF_IOC_SYNC_CONFIG",
|
||||
"FF_IOC_GET_VERSION", "unknown",
|
||||
};
|
||||
unsigned int _cmd = _IOC_NR(cmd);
|
||||
if (_cmd > FF_IOC_GET_VERSION) {
|
||||
_cmd = FF_IOC_GET_VERSION + 1;
|
||||
}
|
||||
FF_LOGD("%s(.., %s, ..) invoke.", __func__, cmd_names[_cmd]);
|
||||
}
|
||||
#endif
|
||||
|
||||
switch (cmd) {
|
||||
case FF_IOC_INIT_DRIVER: {
|
||||
if (g_context->b_driver_inited) {
|
||||
err = ff_ctl_free_driver();
|
||||
}
|
||||
if (!err) {
|
||||
err = ff_ctl_init_driver();
|
||||
// TODO: Sync the dirty configuration back to HAL.
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FF_IOC_FREE_DRIVER:
|
||||
if (g_context->b_driver_inited) {
|
||||
err = ff_ctl_free_driver();
|
||||
g_context->b_driver_inited = false;
|
||||
}
|
||||
break;
|
||||
case FF_IOC_RESET_DEVICE:
|
||||
err = ff_ctl_reset_device();
|
||||
break;
|
||||
case FF_IOC_ENABLE_IRQ:
|
||||
err = ff_ctl_enable_irq(1);
|
||||
break;
|
||||
case FF_IOC_DISABLE_IRQ:
|
||||
err = ff_ctl_enable_irq(0);
|
||||
break;
|
||||
case FF_IOC_ENABLE_SPI_CLK:
|
||||
#ifdef FF_SPI_SET
|
||||
err = ff_ctl_enable_spiclk(1);
|
||||
#endif
|
||||
break;
|
||||
case FF_IOC_DISABLE_SPI_CLK:
|
||||
#ifdef FF_SPI_SET
|
||||
err = ff_ctl_enable_spiclk(0);
|
||||
#endif
|
||||
break;
|
||||
case FF_IOC_ENABLE_POWER:
|
||||
err = ff_ctl_enable_power(1);
|
||||
break;
|
||||
case FF_IOC_DISABLE_POWER:
|
||||
err = ff_ctl_enable_power(0);
|
||||
break;
|
||||
case FF_IOC_REPORT_KEY_EVENT: {
|
||||
ff_key_event_t kevent;
|
||||
if (copy_from_user(&kevent, (ff_key_event_t *)arg, sizeof(ff_key_event_t))) {
|
||||
FF_LOGE("copy_from_user(..) failed.");
|
||||
err = (-EFAULT);
|
||||
break;
|
||||
}
|
||||
err = ff_ctl_report_key_event(ctx->input, &kevent);
|
||||
break;
|
||||
}
|
||||
case FF_IOC_SYNC_CONFIG: {
|
||||
if (copy_from_user(&driver_config, (ff_driver_config_t *)arg, sizeof(ff_driver_config_t))) {
|
||||
FF_LOGE("copy_from_user(..) failed.");
|
||||
err = (-EFAULT);
|
||||
break;
|
||||
}
|
||||
g_config = &driver_config;
|
||||
|
||||
/* Take logging level effect. */
|
||||
g_log_level = 3;//g_config->log_level;
|
||||
break;
|
||||
}
|
||||
case FF_IOC_GET_VERSION: {
|
||||
if (copy_to_user((void *)arg, ff_ctl_get_version(), FF_DRV_VERSION_LEN)) {
|
||||
FF_LOGE("copy_to_user(..) failed.");
|
||||
err = (-EFAULT);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FF_IOC_GET_SCREEN_STATE: {
|
||||
if (copy_to_user((void *)arg, (const char *)screen_state, 1)) {
|
||||
FF_LOGE("copy_to_user(..) failed.");
|
||||
err = (-EFAULT);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
err = (-EINVAL);
|
||||
break;
|
||||
}
|
||||
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ff_ctl_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
FF_LOGD("'%s' nothing to do.", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ff_ctl_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
int err = 0;
|
||||
FF_LOGV("'%s' enter.", __func__);
|
||||
|
||||
/* Remove this filp from the asynchronously notified filp's. */
|
||||
err = ff_ctl_fasync(-1, filp, 0);
|
||||
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
static long ff_ctl_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int err = 0;
|
||||
FF_LOGV("focal '%s' enter.\n", __func__);
|
||||
|
||||
err = ff_ctl_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
|
||||
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static struct file_operations ff_ctl_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.fasync = ff_ctl_fasync,
|
||||
.unlocked_ioctl = ff_ctl_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = ff_ctl_compat_ioctl,
|
||||
#endif
|
||||
.open = ff_ctl_open,
|
||||
.release = ff_ctl_release,
|
||||
};
|
||||
|
||||
static ff_ctl_context_t ff_ctl_context = {
|
||||
.miscdev = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = FF_DRV_NAME,
|
||||
.fops = &ff_ctl_fops,
|
||||
}, 0,
|
||||
};
|
||||
|
||||
static int __init ff_ctl_driver_init(void)
|
||||
{
|
||||
int err = 0;
|
||||
FF_LOGV("'%s' enter.", __func__);
|
||||
/* Register as a miscellaneous device. */
|
||||
err = misc_register(&ff_ctl_context.miscdev);
|
||||
if (err) {
|
||||
FF_LOGE("misc_register(..) = %d.", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Init the interrupt workqueue. */
|
||||
INIT_WORK(&ff_ctl_context.work_queue, ff_ctl_device_event);
|
||||
|
||||
/* Init the wake lock. */
|
||||
#ifdef CONFIG_PM_WAKELOCKS
|
||||
wakeup_source_init(&ff_ctl_context.wake_lock, "ff_wake_lock");
|
||||
#else
|
||||
wake_lock_init(&ff_ctl_context.wake_lock, WAKE_LOCK_SUSPEND, "ff_wake_lock");
|
||||
#endif
|
||||
|
||||
/* Assign the context instance. */
|
||||
g_context = &ff_ctl_context;
|
||||
|
||||
g_context->b_driver_inited = false;
|
||||
FF_LOGI("FocalTech fingerprint device control driver registered.");
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit ff_ctl_driver_exit(void)
|
||||
{
|
||||
int err = 0;
|
||||
FF_LOGV("'%s' enter.", __func__);
|
||||
|
||||
/* Release the HW resources if needs. */
|
||||
if (g_context->b_driver_inited) {
|
||||
err = ff_ctl_free_driver();
|
||||
g_context->b_driver_inited = false;
|
||||
}
|
||||
|
||||
/* De-init the wake lock. */
|
||||
#ifdef CONFIG_PM_WAKELOCKS
|
||||
wakeup_source_trash(&g_context->wake_lock);
|
||||
#else
|
||||
wake_lock_destroy(&g_context->wake_lock);
|
||||
#endif
|
||||
/* Unregister the miscellaneous device. */
|
||||
misc_deregister(&g_context->miscdev);
|
||||
|
||||
/* 'g_context' could not use any more. */
|
||||
g_context = NULL;
|
||||
|
||||
FF_LOGI("FocalTech fingerprint device control driver released.");
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
}
|
||||
|
||||
module_init(ff_ctl_driver_init);
|
||||
module_exit(ff_ctl_driver_exit);
|
||||
|
||||
MODULE_DESCRIPTION("The device control driver for FocalTech's fingerprint sensor.");
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
MODULE_AUTHOR("FocalTech Fingerprint R&D department");
|
||||
|
119
drivers/misc/focaltech/ff_ctl.h
Normal file
119
drivers/misc/focaltech/ff_ctl.h
Normal file
|
@ -0,0 +1,119 @@
|
|||
/**
|
||||
* User space driver API for FocalTech's fingerprint device.
|
||||
* ATTENTION: Do NOT edit this file unless the corresponding driver changed.
|
||||
*
|
||||
* Copyright (C) 2016-2017 FocalTech Systems Co., Ltd. All Rights Reserved.
|
||||
* Copyright (C) 2020 XiaoMi, Inc.
|
||||
*
|
||||
* 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 __FF_CTRL_API_H__
|
||||
#define __FF_CTRL_API_H__
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
|
||||
/* Device node. */
|
||||
#define FF_CTL_DEV_NAME "/dev/focaltech_fp"
|
||||
|
||||
/* Max driver version buffer length. */
|
||||
#define FF_DRV_VERSION_LEN 32
|
||||
|
||||
/* set chip type */
|
||||
/*
|
||||
#define CHIP_TYPE_FT9304
|
||||
*/
|
||||
#define CHIP_TYPE_MSM8916
|
||||
|
||||
/* set spi */
|
||||
//#define FF_SPI_SET
|
||||
|
||||
/* set vdd gpio */
|
||||
//#define FF_VDD_GPIO
|
||||
|
||||
/* set iovcc gpio */
|
||||
//#define FF_IOVCC_GPIO
|
||||
|
||||
typedef struct {
|
||||
unsigned int code;
|
||||
int value;
|
||||
} ff_key_event_t;
|
||||
|
||||
typedef struct {
|
||||
/* Using asynchronous notification mechanism instead of NETLINK. */
|
||||
bool enable_fasync;
|
||||
|
||||
/* Gesture(Key emulation & Navigation) key codes. */
|
||||
int32_t keycode_nav_left;
|
||||
int32_t keycode_nav_right;
|
||||
int32_t keycode_nav_up;
|
||||
int32_t keycode_nav_down;
|
||||
int32_t keycode_double_click;
|
||||
int32_t keycode_click;
|
||||
int32_t keycode_long_press;
|
||||
int32_t keycode_simulation;
|
||||
|
||||
/* For '/dev/spidevB.C' of REE-Emulation. */
|
||||
bool enable_spidev;
|
||||
int32_t spidev_bus;
|
||||
int32_t spidev_c_s;
|
||||
|
||||
/* For obsolete driver that doesn't support device tree. */
|
||||
int32_t gpio_mosi_pin;
|
||||
int32_t gpio_miso_pin;
|
||||
int32_t gpio_ck_pin;
|
||||
int32_t gpio_cs_pin;
|
||||
int32_t gpio_rst_pin;
|
||||
int32_t gpio_int_pin;
|
||||
int32_t gpio_power_pin;
|
||||
int32_t gpio_iovcc_pin;
|
||||
|
||||
|
||||
/* Logging driver to logcat through uevent mechanism. */
|
||||
int32_t log_level;
|
||||
bool logcat_driver;
|
||||
} ff_driver_config_t;
|
||||
|
||||
/* Magic code for IOCTL-subsystem, 'f'(0x66) means '[F]ocalTech'. */
|
||||
#define FF_IOC_MAGIC 'f'
|
||||
|
||||
/* Allocate/Release driver resource (GPIO/SPI etc.). */
|
||||
#define FF_IOC_INIT_DRIVER _IO(FF_IOC_MAGIC, 0x00)
|
||||
#define FF_IOC_FREE_DRIVER _IO(FF_IOC_MAGIC, 0x01)
|
||||
|
||||
/* HW reset the fingerprint module. */
|
||||
#define FF_IOC_RESET_DEVICE _IO(FF_IOC_MAGIC, 0x02)
|
||||
|
||||
/* Low-level IRQ control. */
|
||||
#define FF_IOC_ENABLE_IRQ _IO(FF_IOC_MAGIC, 0x03)
|
||||
#define FF_IOC_DISABLE_IRQ _IO(FF_IOC_MAGIC, 0x04)
|
||||
|
||||
/* SPI bus clock control, for power-saving purpose. */
|
||||
#define FF_IOC_ENABLE_SPI_CLK _IO(FF_IOC_MAGIC, 0x05)
|
||||
#define FF_IOC_DISABLE_SPI_CLK _IO(FF_IOC_MAGIC, 0x06)
|
||||
|
||||
/* Fingerprint module power control. */
|
||||
#define FF_IOC_ENABLE_POWER _IO(FF_IOC_MAGIC, 0x07)
|
||||
#define FF_IOC_DISABLE_POWER _IO(FF_IOC_MAGIC, 0x08)
|
||||
|
||||
/* Androind system-wide key event, for navigation purpose. */
|
||||
#define FF_IOC_REPORT_KEY_EVENT _IOW(FF_IOC_MAGIC, 0x09, ff_key_event_t)
|
||||
|
||||
/* Sync 'ff_driver_config_t', the driver configuration. */
|
||||
#define FF_IOC_SYNC_CONFIG _IOWR(FF_IOC_MAGIC, 0x0a, ff_driver_config_t)
|
||||
|
||||
/* Query the driver version string. */
|
||||
#define FF_IOC_GET_VERSION _IOR(FF_IOC_MAGIC, 0x0b, const char)
|
||||
|
||||
/* get screen state. */
|
||||
#define FF_IOC_GET_SCREEN_STATE _IOR(FF_IOC_MAGIC, 0x0c, const char)
|
||||
|
||||
#endif /* __FF_CTRL_API_H__ */
|
103
drivers/misc/focaltech/ff_err.h
Normal file
103
drivers/misc/focaltech/ff_err.h
Normal file
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* ${ANDROID_BUILD_TOP}/vendor/focaltech/src/base/focaltech/ff_err.h
|
||||
*
|
||||
* Copyright (C) 2014-2017 FocalTech Systems Co., Ltd. All Rights Reserved.
|
||||
* Copyright (C) 2020 XiaoMi, Inc.
|
||||
*
|
||||
**/
|
||||
|
||||
#ifndef __FF_ERROR_H__
|
||||
#define __FF_ERROR_H__
|
||||
|
||||
#include "ff_log.h" /* FF_LOGE(..) */
|
||||
|
||||
//
|
||||
// All the following macros will return while error.
|
||||
//
|
||||
|
||||
#define FF_CHECK_ERR(expr) \
|
||||
do { \
|
||||
int __e = (expr); \
|
||||
if (__e) { \
|
||||
FF_LOGE("'%s'.", ff_err_strerror(__e)); \
|
||||
return __e; \
|
||||
} \
|
||||
} while (0)
|
||||
/* End of FF_CHECK_ERR */
|
||||
|
||||
#define FF_CHECK_PTR(ptr) \
|
||||
do { \
|
||||
if ((ptr) == NULL) { \
|
||||
FF_CHECK_ERR(FF_ERR_NULL_PTR); \
|
||||
} \
|
||||
} while (0)
|
||||
/* End of FF_CHECK_PTR */
|
||||
|
||||
#define FF_FAILURE_RETRY(expr, retry) \
|
||||
do { \
|
||||
int __e, i = 0, j = retry; \
|
||||
do { \
|
||||
__e = (expr); \
|
||||
if (!__e) \
|
||||
break; \
|
||||
if (++i <= j) { \
|
||||
FF_LOGW("'"#expr"' failed, try again (%d/%d).", i, j); \
|
||||
} else { \
|
||||
FF_LOGE("'%s'.", ff_err_strerror(__e)); \
|
||||
return __e; \
|
||||
} \
|
||||
} while (true); \
|
||||
} while (0)
|
||||
/* End of FF_FAILURE_RETRY */
|
||||
|
||||
typedef enum {
|
||||
FF_SUCCESS = 0, /* No error. */
|
||||
FF_ERR_INTERNAL = -1, /* Generic error. */
|
||||
|
||||
/* Base on unix errno. */
|
||||
FF_ERR_NOENT = -2,
|
||||
FF_ERR_INTR = -4,
|
||||
FF_ERR_IO = -5,
|
||||
FF_ERR_AGAIN = -11,
|
||||
FF_ERR_NOMEM = -12,
|
||||
FF_ERR_BUSY = -16,
|
||||
|
||||
/* Common error. */
|
||||
FF_ERR_BAD_PARAMS = -200,
|
||||
FF_ERR_NULL_PTR = -201,
|
||||
FF_ERR_BUF_OVERFLOW = -202,
|
||||
FF_ERR_BAD_PROTOCOL = -203,
|
||||
FF_ERR_SENSOR_SIZE = -204,
|
||||
FF_ERR_NULL_DEVICE = -205,
|
||||
FF_ERR_DEAD_DEVICE = -206,
|
||||
FF_ERR_REACH_LIMIT = -207,
|
||||
FF_ERR_REE_TEMPLATE = -208,
|
||||
FF_ERR_NOT_TRUSTED = -209,
|
||||
} ff_err_t;
|
||||
|
||||
/*
|
||||
* For some functions that don't return an int(ff_err_t) type error
|
||||
* code, 'ff_last_error' shall be set if there is an error happened.
|
||||
*/
|
||||
extern ff_err_t ff_last_error;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Return an error description string corresponding to $err.
|
||||
*
|
||||
* @params
|
||||
* err: Error number.
|
||||
*
|
||||
* @return
|
||||
* Static error description string.
|
||||
*/
|
||||
const char *ff_err_strerror(int err);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __FF_ERROR_H__ */
|
110
drivers/misc/focaltech/ff_log.h
Normal file
110
drivers/misc/focaltech/ff_log.h
Normal file
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* ${ANDROID_BUILD_TOP}/vendor/focaltech/src/base/focaltech/ff_log.h
|
||||
*
|
||||
* Copyright (C) 2014-2017 FocalTech Systems Co., Ltd. All Rights Reserved.
|
||||
* Copyright (C) 2020 XiaoMi, Inc.
|
||||
*
|
||||
**/
|
||||
|
||||
#ifndef __FF_LOGGING_H__
|
||||
#define __FF_LOGGING_H__
|
||||
|
||||
/*
|
||||
* The default LOG_TAG is 'focaltech', using these two lines to define newtag.
|
||||
* # undef LOG_TAG
|
||||
* #define LOG_TAG "newtag"
|
||||
*/
|
||||
#define LOG_TAG "focaltech"
|
||||
|
||||
/*
|
||||
* Log level can be used in 'logcat <tag>[:priority]', and also be
|
||||
* used in output control while '__FF_EARLY_LOG_LEVEL' is defined.
|
||||
*/
|
||||
typedef enum {
|
||||
FF_LOG_LEVEL_ALL = 0,
|
||||
FF_LOG_LEVEL_VBS = 1, /* Verbose */
|
||||
FF_LOG_LEVEL_DBG = 2, /* Debug */
|
||||
FF_LOG_LEVEL_INF = 3, /* Info */
|
||||
FF_LOG_LEVEL_WRN = 4, /* Warning */
|
||||
FF_LOG_LEVEL_ERR = 5, /* Error */
|
||||
} ff_log_level_t;
|
||||
|
||||
/*
|
||||
* __FF_EARLY_LOG_LEVEL can be defined as compilation option.
|
||||
* default level is FF_LOG_LEVEL_ALL(all the logs will be output).
|
||||
*/
|
||||
#ifndef __FF_EARLY_LOG_LEVEL
|
||||
#define __FF_EARLY_LOG_LEVEL FF_LOG_LEVEL_ALL
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Log level can be runtime configurable.
|
||||
*/
|
||||
extern ff_log_level_t g_log_level; /* = __FF_EARLY_LOG_LEVEL */
|
||||
|
||||
/*
|
||||
* Using the following five macros for conveniently logging.
|
||||
*/
|
||||
#define FF_LOGV(...) \
|
||||
do { \
|
||||
if (g_log_level <= FF_LOG_LEVEL_VBS) { \
|
||||
ff_log_printf(FF_LOG_LEVEL_VBS, LOG_TAG, __VA_ARGS__); \
|
||||
}; \
|
||||
} while (0)
|
||||
|
||||
#define FF_LOGD(...) \
|
||||
do { \
|
||||
if (g_log_level <= FF_LOG_LEVEL_DBG) { \
|
||||
ff_log_printf(FF_LOG_LEVEL_DBG, LOG_TAG, __VA_ARGS__); \
|
||||
}; \
|
||||
} while (0)
|
||||
|
||||
#define FF_LOGI(...) \
|
||||
do { \
|
||||
if (g_log_level <= FF_LOG_LEVEL_INF) { \
|
||||
ff_log_printf(FF_LOG_LEVEL_INF, LOG_TAG, __VA_ARGS__); \
|
||||
}; \
|
||||
} while (0)
|
||||
|
||||
#define FF_LOGW(...) \
|
||||
do { \
|
||||
if (g_log_level <= FF_LOG_LEVEL_WRN) { \
|
||||
ff_log_printf(FF_LOG_LEVEL_WRN, LOG_TAG, __VA_ARGS__); \
|
||||
}; \
|
||||
} while (0)
|
||||
|
||||
#define FF_LOGE(format, ...) \
|
||||
do { \
|
||||
if (g_log_level <= FF_LOG_LEVEL_ERR) { \
|
||||
const char *__fname__ = __FILE__, *s = __fname__; \
|
||||
do { if (*s == '/') __fname__ = s + 1; } while (*s++); \
|
||||
ff_log_printf(FF_LOG_LEVEL_ERR, LOG_TAG, \
|
||||
"error at %s[%s:%d]: " format, __FUNCTION__, \
|
||||
__fname__, __LINE__, ##__VA_ARGS__); \
|
||||
}; \
|
||||
} while (0)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Logging API. Do NOT use it directly but FF_LOF* instead.
|
||||
*
|
||||
* @params
|
||||
* level: Logging level for logcat.
|
||||
* tag : Logging tag for logcat.
|
||||
* fmt : See POSIX printf(..).
|
||||
* ... : See POSIX printf(..).
|
||||
*
|
||||
* @return
|
||||
* The number of characters printed, or a negative value if there
|
||||
* was an output error.
|
||||
*/
|
||||
int ff_log_printf(ff_log_level_t level, const char *tag, const char *fmt, ...);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __FF_LOGGING_H__ */
|
239
drivers/misc/focaltech/plat-msm8916.c
Normal file
239
drivers/misc/focaltech/plat-msm8916.c
Normal file
|
@ -0,0 +1,239 @@
|
|||
/**
|
||||
* plat-msm8916.c
|
||||
*
|
||||
**/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/of_gpio.h>
|
||||
|
||||
#include "ff_log.h"
|
||||
#include "ff_ctl.h"
|
||||
|
||||
# undef LOG_TAG
|
||||
#define LOG_TAG "msm8916"
|
||||
|
||||
#define FF_COMPATIBLE_NODE "focaltech,fingerprint"
|
||||
|
||||
/*
|
||||
* Driver configuration. See ff_ctl.c
|
||||
*/
|
||||
extern ff_driver_config_t *g_config;
|
||||
|
||||
int ff_ctl_init_pins(int *irq_num)
|
||||
{
|
||||
int err = 0, gpio;
|
||||
struct device_node *dev_node = NULL;
|
||||
enum of_gpio_flags flags;
|
||||
bool b_config_dirtied = false;
|
||||
FF_LOGV("'%s' enter.", __func__);
|
||||
|
||||
if (unlikely(!g_config)) {
|
||||
return (-ENOSYS);
|
||||
}
|
||||
|
||||
/* Find device tree node. */
|
||||
dev_node = of_find_compatible_node(NULL, NULL, FF_COMPATIBLE_NODE);
|
||||
if (!dev_node) {
|
||||
FF_LOGE("of_find_compatible_node(.., '%s') failed.", FF_COMPATIBLE_NODE);
|
||||
return (-ENODEV);
|
||||
}
|
||||
|
||||
/* Initialize RST pin. */
|
||||
gpio = of_get_named_gpio_flags(dev_node, "fp,reset_gpio", 0, &flags);
|
||||
if (gpio > 0) {
|
||||
g_config->gpio_rst_pin = gpio;
|
||||
b_config_dirtied = true;
|
||||
}
|
||||
if (!gpio_is_valid(g_config->gpio_rst_pin)) {
|
||||
FF_LOGE("g_config->gpio_rst_pin(%d) is invalid.", g_config->gpio_rst_pin);
|
||||
return (-ENODEV);
|
||||
}
|
||||
err = gpio_request(g_config->gpio_rst_pin, "ff_gpio_rst_pin");
|
||||
if (err) {
|
||||
FF_LOGE("gpio_request(%d) = %d.", g_config->gpio_rst_pin, err);
|
||||
return err;
|
||||
}
|
||||
err = gpio_direction_output(g_config->gpio_rst_pin, 1);
|
||||
if (err) {
|
||||
FF_LOGE("gpio_direction_output(%d, 1) = %d.", g_config->gpio_rst_pin, err);
|
||||
return err;
|
||||
}
|
||||
#ifdef FF_VDD_GPIO
|
||||
/* Initialize PWR/VDD pin. */
|
||||
gpio = of_get_named_gpio_flags(dev_node, "fp,vdd_gpio", 0, &flags);
|
||||
if (gpio > 0) {
|
||||
g_config->gpio_power_pin = gpio;
|
||||
b_config_dirtied = true;
|
||||
}
|
||||
if (!gpio_is_valid(g_config->gpio_power_pin)) {
|
||||
FF_LOGE("g_config->gpio_power_pin(%d) is invalid.", g_config->gpio_power_pin);
|
||||
return (-ENODEV);
|
||||
}
|
||||
err = gpio_request(g_config->gpio_power_pin, "ff_gpio_power_pin");
|
||||
if (err) {
|
||||
FF_LOGE("gpio_request(%d) = %d.", g_config->gpio_power_pin, err);
|
||||
return err;
|
||||
}
|
||||
err = gpio_direction_output(g_config->gpio_power_pin, 0); // power off.
|
||||
if (err) {
|
||||
FF_LOGE("gpio_direction_output(%d, 0) = %d.", g_config->gpio_power_pin, err);
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef FF_IOVCC_GPIO
|
||||
/* Initialize IOVCC pin. */
|
||||
gpio = of_get_named_gpio_flags(dev_node, "fp,iovcc_gpio", 0, &flags);
|
||||
if (gpio > 0) {
|
||||
g_config->gpio_iovcc_pin = gpio;
|
||||
b_config_dirtied = true;
|
||||
}
|
||||
if (!gpio_is_valid(g_config->gpio_iovcc_pin)) {
|
||||
FF_LOGE("g_config->gpio_iovcc_pin(%d) is invalid.", g_config->gpio_iovcc_pin);
|
||||
return (-ENODEV);
|
||||
}
|
||||
err = gpio_request(g_config->gpio_iovcc_pin, "ff_gpio_iovcc_pin");
|
||||
if (err) {
|
||||
FF_LOGE("gpio_request(%d) = %d.", g_config->gpio_iovcc_pin, err);
|
||||
return err;
|
||||
}
|
||||
err = gpio_direction_output(g_config->gpio_iovcc_pin, 0); // power off.
|
||||
if (err) {
|
||||
FF_LOGE("gpio_direction_output(%d, 0) = %d.", g_config->gpio_iovcc_pin, err);
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Initialize INT pin. */
|
||||
gpio = of_get_named_gpio_flags(dev_node, "fp,irq_gpio", 0, &flags);
|
||||
if (gpio > 0) {
|
||||
g_config->gpio_int_pin = gpio;
|
||||
b_config_dirtied = true;
|
||||
}
|
||||
if (!gpio_is_valid(g_config->gpio_int_pin)) {
|
||||
FF_LOGE("g_config->gpio_int_pin(%d) is invalid.", g_config->gpio_int_pin);
|
||||
return (-ENODEV);
|
||||
}
|
||||
err = gpio_request(g_config->gpio_int_pin, "ff_gpio_int_pin");
|
||||
if (err) {
|
||||
FF_LOGE("gpio_request(%d) = %d.", g_config->gpio_int_pin, err);
|
||||
return err;
|
||||
}
|
||||
err = gpio_direction_input(g_config->gpio_int_pin);
|
||||
if (err) {
|
||||
FF_LOGE("gpio_direction_input(%d) = %d.", g_config->gpio_int_pin, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Retrieve the IRQ number. */
|
||||
*irq_num = gpio_to_irq(g_config->gpio_int_pin);
|
||||
if (*irq_num < 0) {
|
||||
FF_LOGE("gpio_to_irq(%d) failed.", g_config->gpio_int_pin);
|
||||
return (-EIO);
|
||||
} else {
|
||||
FF_LOGD("gpio_to_irq(%d) = %d.", g_config->gpio_int_pin, *irq_num);
|
||||
}
|
||||
|
||||
/* Configuration is dirty, must sync back to HAL. */
|
||||
if (!err && b_config_dirtied) {
|
||||
err = 1;
|
||||
}
|
||||
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
return err;
|
||||
}
|
||||
|
||||
int ff_ctl_free_pins(void)
|
||||
{
|
||||
int err = 0;
|
||||
FF_LOGV("'%s' enter.", __func__);
|
||||
|
||||
/* Release GPIO resources. */
|
||||
gpio_free(g_config->gpio_rst_pin);
|
||||
gpio_free(g_config->gpio_int_pin);
|
||||
#ifdef FF_IOVCC_GPIO
|
||||
gpio_free(g_config->gpio_iovcc_pin);
|
||||
#endif
|
||||
#ifdef FF_VDD_GPIO
|
||||
gpio_free(g_config->gpio_power_pin);
|
||||
#endif
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
return err;
|
||||
}
|
||||
|
||||
int ff_ctl_enable_spiclk(bool on)
|
||||
{
|
||||
int err = 0;
|
||||
FF_LOGV("'%s' enter.", __func__);
|
||||
FF_LOGD("clock: '%s'.", on ? "enable" : "disabled");
|
||||
|
||||
if (on) {
|
||||
// TODO:
|
||||
} else {
|
||||
// TODO:
|
||||
}
|
||||
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
return err;
|
||||
}
|
||||
|
||||
int ff_ctl_enable_power(bool on)
|
||||
{
|
||||
int err = 0;
|
||||
FF_LOGV("'%s' enter.", __func__);
|
||||
FF_LOGD("power: '%s'.", on ? "on" : "off");
|
||||
|
||||
if (unlikely(!g_config)) {
|
||||
return (-ENOSYS);
|
||||
}
|
||||
|
||||
if (on) {
|
||||
#ifdef FF_VDD_GPIO
|
||||
err = gpio_direction_output(g_config->gpio_power_pin, 1);
|
||||
#endif
|
||||
msleep(5);
|
||||
#ifdef FF_IOVCC_GPIO
|
||||
err = gpio_direction_output(g_config->gpio_iovcc_pin, 1);
|
||||
#endif
|
||||
} else {
|
||||
#ifdef FF_IOVCC_GPIO
|
||||
err = gpio_direction_output(g_config->gpio_iovcc_pin, 0);
|
||||
#endif
|
||||
msleep(5);
|
||||
#ifdef FF_VDD_GPIO
|
||||
err = gpio_direction_output(g_config->gpio_power_pin, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
return err;
|
||||
}
|
||||
|
||||
int ff_ctl_reset_device(void)
|
||||
{
|
||||
int err = 0;
|
||||
FF_LOGV("'%s' enter.", __func__);
|
||||
|
||||
if (unlikely(!g_config)) {
|
||||
return (-ENOSYS);
|
||||
}
|
||||
|
||||
/* 3-1: Pull down RST pin. */
|
||||
err = gpio_direction_output(g_config->gpio_rst_pin, 0);
|
||||
|
||||
/* 3-2: Delay for 10ms. */
|
||||
mdelay(10);
|
||||
|
||||
/* Pull up RST pin. */
|
||||
err = gpio_direction_output(g_config->gpio_rst_pin, 1);
|
||||
|
||||
FF_LOGV("'%s' leave.", __func__);
|
||||
return err;
|
||||
}
|
||||
|
||||
const char *ff_ctl_arch_str(void)
|
||||
{
|
||||
return "msm8916";
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#
|
||||
# FingerprintCard fingerprint driver
|
||||
#
|
||||
menu "FingerprintCard fingerprint driver"
|
||||
config FPR_FPC
|
||||
tristate "FPC fingerprint sensor support"
|
||||
depends on SPI_MASTER
|
||||
help
|
||||
support fpc1020 and fpc1028.
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called fpc1020.
|
||||
default n
|
||||
tristate "FPC_BTP fingerprint sensor support"
|
||||
depends on SPI_MASTER
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
# Makefile for FingerprintCard fingerprint driver
|
||||
|
||||
fpc1020-objs := fpc1020_platform_tee.o
|
||||
obj-$(CONFIG_FPR_FPC) += fpc1020.o
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* FPC1020 Fingerprint sensor device driver
|
||||
*
|
||||
|
@ -19,6 +18,7 @@
|
|||
*
|
||||
*
|
||||
* Copyright (c) 2015 Fingerprint Cards AB <tech@fingerprints.com>
|
||||
* Copyright (C) 2020 XiaoMi, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License Version 2
|
||||
|
@ -38,21 +38,23 @@
|
|||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
|
||||
#define FPC_TTW_HOLD_TIME 1000
|
||||
#define CONFIG_FPC_COMPAT 1
|
||||
|
||||
#define FPC_TTW_HOLD_TIME 1000
|
||||
#define RESET_LOW_SLEEP_MIN_US 5000
|
||||
#define RESET_LOW_SLEEP_MAX_US (RESET_LOW_SLEEP_MIN_US + 100)
|
||||
#define RESET_HIGH_SLEEP1_MIN_US 100
|
||||
#define RESET_HIGH_SLEEP1_MAX_US (RESET_HIGH_SLEEP1_MIN_US + 100)
|
||||
#define RESET_HIGH_SLEEP2_MIN_US 5000
|
||||
#define RESET_HIGH_SLEEP2_MAX_US (RESET_HIGH_SLEEP2_MIN_US + 100)
|
||||
#define PWR_ON_SLEEP_MIN_US 100
|
||||
#define PWR_ON_SLEEP_MAX_US (PWR_ON_SLEEP_MIN_US + 900)
|
||||
#define NUM_PARAMS_REG_ENABLE_SET 2
|
||||
#define RESET_LOW_SLEEP_MIN_US 5000
|
||||
#define RESET_LOW_SLEEP_MAX_US (RESET_LOW_SLEEP_MIN_US + 100)
|
||||
#define RESET_HIGH_SLEEP1_MIN_US 100
|
||||
#define RESET_HIGH_SLEEP1_MAX_US (RESET_HIGH_SLEEP1_MIN_US + 100)
|
||||
#define RESET_HIGH_SLEEP2_MIN_US 5000
|
||||
#define RESET_HIGH_SLEEP2_MAX_US (RESET_HIGH_SLEEP2_MIN_US + 100)
|
||||
#define PWR_ON_SLEEP_MIN_US 100
|
||||
#define PWR_ON_SLEEP_MAX_US (PWR_ON_SLEEP_MIN_US + 900)
|
||||
|
||||
#define RELEASE_WAKELOCK_W_V "release_wakelock_with_verification"
|
||||
#define RELEASE_WAKELOCK "release_wakelock"
|
||||
#define START_IRQS_RECEIVED_CNT "start_irqs_received_counter"
|
||||
#define NUM_PARAMS_REG_ENABLE_SET 2
|
||||
|
||||
#define RELEASE_WAKELOCK_W_V "release_wakelock_with_verification"
|
||||
#define RELEASE_WAKELOCK "release_wakelock"
|
||||
#define START_IRQS_RECEIVED_CNT "start_irqs_received_counter"
|
||||
|
||||
static const char * const pctl_names[] = {
|
||||
"fpc1020_reset_reset",
|
||||
|
@ -67,7 +69,7 @@ struct vreg_config {
|
|||
int ua_load;
|
||||
};
|
||||
|
||||
static const struct vreg_config vreg_conf[] = {
|
||||
static const struct vreg_config const vreg_conf[] = {
|
||||
{ "vdd_ana", 1800000UL, 1800000UL, 6000, },
|
||||
{ "vcc_spi", 1800000UL, 1800000UL, 10, },
|
||||
{ "vdd_io", 1800000UL, 1800000UL, 6000, },
|
||||
|
@ -75,6 +77,7 @@ static const struct vreg_config vreg_conf[] = {
|
|||
|
||||
struct fpc1020_data {
|
||||
struct device *dev;
|
||||
|
||||
struct pinctrl *fingerprint_pinctrl;
|
||||
struct pinctrl_state *pinctrl_state[ARRAY_SIZE(pctl_names)];
|
||||
struct regulator *vreg[ARRAY_SIZE(vreg_conf)];
|
||||
|
@ -82,12 +85,22 @@ struct fpc1020_data {
|
|||
struct mutex lock; /* To set/get exported values in sysfs */
|
||||
int irq_gpio;
|
||||
int rst_gpio;
|
||||
|
||||
int nbr_irqs_received;
|
||||
int nbr_irqs_received_counter_start;
|
||||
|
||||
bool prepared;
|
||||
#ifdef CONFIG_FPC_COMPAT
|
||||
bool compatible_enabled;
|
||||
#endif
|
||||
atomic_t wakeup_enabled; /* Used both in ISR and non-ISR */
|
||||
};
|
||||
|
||||
static irqreturn_t fpc1020_irq_handler(int irq, void *handle);
|
||||
static int fpc1020_request_named_gpio(struct fpc1020_data *fpc1020,
|
||||
const char *label, int *gpio);
|
||||
static int hw_reset(struct fpc1020_data *fpc1020);
|
||||
|
||||
static int vreg_setup(struct fpc1020_data *fpc1020, const char *name,
|
||||
bool enable)
|
||||
{
|
||||
|
@ -96,10 +109,10 @@ static int vreg_setup(struct fpc1020_data *fpc1020, const char *name,
|
|||
struct regulator *vreg;
|
||||
struct device *dev = fpc1020->dev;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(vreg_conf); i++) {
|
||||
for (i = 0; i < ARRAY_SIZE(fpc1020->vreg); i++) {
|
||||
const char *n = vreg_conf[i].name;
|
||||
|
||||
if (!memcmp(n, name, strlen(n)))
|
||||
if (!strncmp(n, name, strlen(n)))
|
||||
goto found;
|
||||
}
|
||||
|
||||
|
@ -113,10 +126,8 @@ static int vreg_setup(struct fpc1020_data *fpc1020, const char *name,
|
|||
if (!vreg) {
|
||||
vreg = devm_regulator_get(dev, name);
|
||||
if (IS_ERR_OR_NULL(vreg)) {
|
||||
dev_info(dev,
|
||||
"No regulator %s, maybe fixed regulator\n",
|
||||
name);
|
||||
return 0;
|
||||
dev_err(dev, "Unable to get %s\n", name);
|
||||
return PTR_ERR(vreg);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,7 +165,7 @@ static int vreg_setup(struct fpc1020_data *fpc1020, const char *name,
|
|||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* sysfs node for controlling clocks.
|
||||
*
|
||||
* This is disabled in platform variant of this driver but kept for
|
||||
|
@ -172,7 +183,7 @@ static ssize_t clk_enable_store(struct device *dev,
|
|||
}
|
||||
static DEVICE_ATTR_WO(clk_enable);
|
||||
|
||||
/*
|
||||
/**
|
||||
* Will try to select the set of pins (GPIOS) defined in a pin control node of
|
||||
* the device tree named @p name.
|
||||
*
|
||||
|
@ -190,23 +201,25 @@ static int select_pin_ctl(struct fpc1020_data *fpc1020, const char *name)
|
|||
int rc;
|
||||
struct device *dev = fpc1020->dev;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(pctl_names); i++) {
|
||||
for (i = 0; i < ARRAY_SIZE(fpc1020->pinctrl_state); i++) {
|
||||
const char *n = pctl_names[i];
|
||||
|
||||
if (!memcmp(n, name, strlen(n))) {
|
||||
if (!strncmp(n, name, strlen(n))) {
|
||||
rc = pinctrl_select_state(fpc1020->fingerprint_pinctrl,
|
||||
fpc1020->pinctrl_state[i]);
|
||||
if (rc)
|
||||
dev_err(dev, "cannot select '%s'\n", name);
|
||||
else
|
||||
dev_dbg(dev, "Selected '%s'\n", name);
|
||||
|
||||
return rc;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
rc = -EINVAL;
|
||||
dev_err(dev, "%s:'%s' not found\n", __func__, name);
|
||||
return -EINVAL;
|
||||
|
||||
exit:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static ssize_t pinctl_set_store(struct device *dev,
|
||||
|
@ -232,7 +245,7 @@ static ssize_t regulator_enable_store(struct device *dev,
|
|||
int rc;
|
||||
bool enable;
|
||||
|
||||
if (sscanf(buf, "%15[^,],%c", name, &op) != NUM_PARAMS_REG_ENABLE_SET)
|
||||
if (NUM_PARAMS_REG_ENABLE_SET != sscanf(buf, "%15[^,],%c", name, &op))
|
||||
return -EINVAL;
|
||||
if (op == 'e')
|
||||
enable = true;
|
||||
|
@ -249,42 +262,55 @@ static ssize_t regulator_enable_store(struct device *dev,
|
|||
}
|
||||
static DEVICE_ATTR_WO(regulator_enable);
|
||||
|
||||
static void hw_reset(struct fpc1020_data *fpc1020)
|
||||
static int hw_reset(struct fpc1020_data *fpc1020)
|
||||
{
|
||||
(void)gpio_get_value(fpc1020->irq_gpio);
|
||||
|
||||
select_pin_ctl(fpc1020, "fpc1020_reset_active");
|
||||
int irq_gpio;
|
||||
int rc;
|
||||
struct device *dev = fpc1020->dev;
|
||||
irq_gpio = gpio_get_value(fpc1020->irq_gpio);
|
||||
dev_info(dev, "IRQ before reset %d\n", irq_gpio);
|
||||
rc = select_pin_ctl(fpc1020, "fpc1020_reset_active");
|
||||
|
||||
if (rc)
|
||||
goto exit;
|
||||
usleep_range(RESET_HIGH_SLEEP1_MIN_US, RESET_HIGH_SLEEP1_MAX_US);
|
||||
|
||||
select_pin_ctl(fpc1020, "fpc1020_reset_reset");
|
||||
|
||||
rc = select_pin_ctl(fpc1020, "fpc1020_reset_reset");
|
||||
if (rc)
|
||||
goto exit;
|
||||
usleep_range(RESET_LOW_SLEEP_MIN_US, RESET_LOW_SLEEP_MAX_US);
|
||||
|
||||
select_pin_ctl(fpc1020, "fpc1020_reset_active");
|
||||
|
||||
rc = select_pin_ctl(fpc1020, "fpc1020_reset_active");
|
||||
if (rc)
|
||||
goto exit;
|
||||
usleep_range(RESET_HIGH_SLEEP2_MIN_US, RESET_HIGH_SLEEP2_MAX_US);
|
||||
|
||||
(void)gpio_get_value(fpc1020->irq_gpio);
|
||||
irq_gpio = gpio_get_value(fpc1020->irq_gpio);
|
||||
dev_info(dev, "IRQ after reset %d\n", irq_gpio);
|
||||
|
||||
exit:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static ssize_t hw_reset_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
int rc;
|
||||
struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
|
||||
|
||||
if (!memcmp(buf, "reset", strlen("reset"))) {
|
||||
if (!strncmp(buf, "reset", strlen("reset"))) {
|
||||
mutex_lock(&fpc1020->lock);
|
||||
hw_reset(fpc1020);
|
||||
rc = hw_reset(fpc1020);
|
||||
mutex_unlock(&fpc1020->lock);
|
||||
return count;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
return rc ? rc : count;
|
||||
}
|
||||
static DEVICE_ATTR_WO(hw_reset);
|
||||
|
||||
/*
|
||||
/**
|
||||
* Will setup GPIOs, and regulators to correctly initialize the touch sensor to
|
||||
* be ready for work.
|
||||
*
|
||||
|
@ -296,35 +322,57 @@ static DEVICE_ATTR_WO(hw_reset);
|
|||
* @note This function will not send any commands to the sensor it will only
|
||||
* control it "electrically".
|
||||
*/
|
||||
static void device_prepare(struct fpc1020_data *fpc1020, bool enable)
|
||||
static int device_prepare(struct fpc1020_data *fpc1020, bool enable)
|
||||
{
|
||||
int rc;
|
||||
|
||||
mutex_lock(&fpc1020->lock);
|
||||
if (enable && !fpc1020->prepared) {
|
||||
fpc1020->prepared = true;
|
||||
select_pin_ctl(fpc1020, "fpc1020_reset_reset");
|
||||
|
||||
vreg_setup(fpc1020, "vcc_spi", true);
|
||||
vreg_setup(fpc1020, "vdd_io", true);
|
||||
vreg_setup(fpc1020, "vdd_ana", true);
|
||||
rc = vreg_setup(fpc1020, "vcc_spi", true);
|
||||
if (rc)
|
||||
goto exit;
|
||||
|
||||
rc = vreg_setup(fpc1020, "vdd_io", true);
|
||||
if (rc)
|
||||
goto exit_1;
|
||||
|
||||
rc = vreg_setup(fpc1020, "vdd_ana", true);
|
||||
if (rc)
|
||||
goto exit_2;
|
||||
|
||||
usleep_range(PWR_ON_SLEEP_MIN_US, PWR_ON_SLEEP_MAX_US);
|
||||
|
||||
select_pin_ctl(fpc1020, "fpc1020_reset_active");
|
||||
/* As we can't control chip select here the other part of the
|
||||
* sensor driver eg. the TEE driver needs to do a _SOFT_ reset
|
||||
* on the sensor after power up to be sure that the sensor is
|
||||
* in a good state after power up. Okeyed by ASIC. */
|
||||
|
||||
(void)select_pin_ctl(fpc1020, "fpc1020_reset_active");
|
||||
} else if (!enable && fpc1020->prepared) {
|
||||
select_pin_ctl(fpc1020, "fpc1020_reset_reset");
|
||||
rc = 0;
|
||||
(void)select_pin_ctl(fpc1020, "fpc1020_reset_reset");
|
||||
|
||||
usleep_range(PWR_ON_SLEEP_MIN_US, PWR_ON_SLEEP_MAX_US);
|
||||
|
||||
vreg_setup(fpc1020, "vdd_ana", false);
|
||||
vreg_setup(fpc1020, "vdd_io", false);
|
||||
vreg_setup(fpc1020, "vcc_spi", false);
|
||||
|
||||
(void)vreg_setup(fpc1020, "vdd_ana", false);
|
||||
exit_2:
|
||||
(void)vreg_setup(fpc1020, "vdd_io", false);
|
||||
exit_1:
|
||||
(void)vreg_setup(fpc1020, "vcc_spi", false);
|
||||
exit:
|
||||
fpc1020->prepared = false;
|
||||
} else {
|
||||
rc = 0;
|
||||
}
|
||||
mutex_unlock(&fpc1020->lock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* sysfs node to enable/disable (power up/power down) the touch sensor
|
||||
*
|
||||
* @see device_prepare
|
||||
|
@ -332,20 +380,19 @@ static void device_prepare(struct fpc1020_data *fpc1020, bool enable)
|
|||
static ssize_t device_prepare_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
int rc = 0;
|
||||
int rc;
|
||||
struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
|
||||
|
||||
if (!memcmp(buf, "enable", strlen("enable")))
|
||||
device_prepare(fpc1020, true);
|
||||
else if (!memcmp(buf, "disable", strlen("disable")))
|
||||
device_prepare(fpc1020, false);
|
||||
if (!strncmp(buf, "enable", strlen("enable")))
|
||||
rc = device_prepare(fpc1020, true);
|
||||
else if (!strncmp(buf, "disable", strlen("disable")))
|
||||
rc = device_prepare(fpc1020, false);
|
||||
else
|
||||
rc = -EINVAL;
|
||||
return -EINVAL;
|
||||
|
||||
return rc ? rc : count;
|
||||
}
|
||||
static DEVICE_ATTR_WO(device_prepare);
|
||||
|
||||
DEVICE_ATTR_WO(device_prepare);
|
||||
/**
|
||||
* sysfs node for controlling whether the driver is allowed
|
||||
* to wake up the platform on interrupt.
|
||||
|
@ -357,9 +404,9 @@ static ssize_t wakeup_enable_store(struct device *dev,
|
|||
ssize_t ret = count;
|
||||
|
||||
mutex_lock(&fpc1020->lock);
|
||||
if (!memcmp(buf, "enable", strlen("enable")))
|
||||
if (!strncmp(buf, "enable", strlen("enable")))
|
||||
atomic_set(&fpc1020->wakeup_enabled, 1);
|
||||
else if (!memcmp(buf, "disable", strlen("disable")))
|
||||
else if (!strncmp(buf, "disable", strlen("disable")))
|
||||
atomic_set(&fpc1020->wakeup_enabled, 0);
|
||||
else
|
||||
ret = -EINVAL;
|
||||
|
@ -369,31 +416,30 @@ static ssize_t wakeup_enable_store(struct device *dev,
|
|||
}
|
||||
static DEVICE_ATTR_WO(wakeup_enable);
|
||||
|
||||
|
||||
/*
|
||||
/**
|
||||
* sysfs node for controlling the wakelock.
|
||||
*/
|
||||
static ssize_t handle_wakelock_store(struct device *dev,
|
||||
static ssize_t handle_wakelock_cmd(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
|
||||
ssize_t ret = count;
|
||||
|
||||
mutex_lock(&fpc1020->lock);
|
||||
if (!memcmp(buf, RELEASE_WAKELOCK_W_V,
|
||||
if (!strncmp(buf, RELEASE_WAKELOCK_W_V,
|
||||
min(count, strlen(RELEASE_WAKELOCK_W_V)))) {
|
||||
if (fpc1020->nbr_irqs_received_counter_start ==
|
||||
fpc1020->nbr_irqs_received) {
|
||||
__pm_relax(fpc1020->ttw_wl);
|
||||
} else {
|
||||
dev_dbg(dev, "Ignore releasing of wakelock %d != %d",
|
||||
fpc1020->nbr_irqs_received_counter_start,
|
||||
fpc1020->nbr_irqs_received);
|
||||
fpc1020->nbr_irqs_received_counter_start,
|
||||
fpc1020->nbr_irqs_received);
|
||||
}
|
||||
} else if (!memcmp(buf, RELEASE_WAKELOCK, min(count,
|
||||
} else if (!strncmp(buf, RELEASE_WAKELOCK, min(count,
|
||||
strlen(RELEASE_WAKELOCK)))) {
|
||||
__pm_relax(fpc1020->ttw_wl);
|
||||
} else if (!memcmp(buf, START_IRQS_RECEIVED_CNT,
|
||||
} else if (!strncmp(buf, START_IRQS_RECEIVED_CNT,
|
||||
min(count, strlen(START_IRQS_RECEIVED_CNT)))) {
|
||||
fpc1020->nbr_irqs_received_counter_start =
|
||||
fpc1020->nbr_irqs_received;
|
||||
|
@ -403,21 +449,131 @@ static ssize_t handle_wakelock_store(struct device *dev,
|
|||
|
||||
return ret;
|
||||
}
|
||||
static DEVICE_ATTR_WO(handle_wakelock);
|
||||
static DEVICE_ATTR(handle_wakelock, S_IWUSR, NULL, handle_wakelock_cmd);
|
||||
|
||||
/*
|
||||
/**
|
||||
* 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_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
static ssize_t irq_get(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
|
||||
int irq = gpio_get_value(fpc1020->irq_gpio);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%i\n", irq);
|
||||
}
|
||||
static DEVICE_ATTR_RO(irq);
|
||||
|
||||
/**
|
||||
* 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 *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
|
||||
|
||||
dev_dbg(fpc1020->dev, "%s\n", __func__);
|
||||
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR(irq, S_IRUSR | S_IWUSR, irq_get, irq_ack);
|
||||
|
||||
#ifdef CONFIG_FPC_COMPAT
|
||||
static ssize_t compatible_all_set(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
int rc;
|
||||
int i;
|
||||
int irqf;
|
||||
struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
|
||||
dev_err(dev, "compatible all enter %d\n", fpc1020->compatible_enabled);
|
||||
if (!strncmp(buf, "enable", strlen("enable")) && fpc1020->compatible_enabled != 1) {
|
||||
rc = fpc1020_request_named_gpio(fpc1020, "fpc,gpio_irq",
|
||||
&fpc1020->irq_gpio);
|
||||
if (rc)
|
||||
goto exit;
|
||||
|
||||
rc = fpc1020_request_named_gpio(fpc1020, "fpc,gpio_rst",
|
||||
&fpc1020->rst_gpio);
|
||||
dev_err(dev, "fpc request reset result = %d\n", rc);
|
||||
if (rc)
|
||||
goto exit;
|
||||
fpc1020->fingerprint_pinctrl = devm_pinctrl_get(dev);
|
||||
if (IS_ERR(fpc1020->fingerprint_pinctrl)) {
|
||||
if (PTR_ERR(fpc1020->fingerprint_pinctrl) == -EPROBE_DEFER) {
|
||||
dev_info(dev, "pinctrl not ready\n");
|
||||
rc = -EPROBE_DEFER;
|
||||
goto exit;
|
||||
}
|
||||
dev_err(dev, "Target does not use pinctrl\n");
|
||||
fpc1020->fingerprint_pinctrl = NULL;
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(fpc1020->pinctrl_state); i++) {
|
||||
const char *n = pctl_names[i];
|
||||
struct pinctrl_state *state =
|
||||
pinctrl_lookup_state(fpc1020->fingerprint_pinctrl, n);
|
||||
if (IS_ERR(state)) {
|
||||
dev_err(dev, "cannot find '%s'\n", n);
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
dev_info(dev, "found pin control %s\n", n);
|
||||
fpc1020->pinctrl_state[i] = state;
|
||||
}
|
||||
rc = select_pin_ctl(fpc1020, "fpc1020_reset_reset");
|
||||
if (rc)
|
||||
goto exit;
|
||||
rc = select_pin_ctl(fpc1020, "fpc1020_irq_active");
|
||||
if (rc)
|
||||
goto exit;
|
||||
irqf = IRQF_TRIGGER_RISING | IRQF_ONESHOT;
|
||||
if (of_property_read_bool(dev->of_node, "fpc,enable-wakeup")) {
|
||||
irqf |= IRQF_NO_SUSPEND;
|
||||
device_init_wakeup(dev, 1);
|
||||
}
|
||||
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_dbg(dev, "requested irq %d\n", gpio_to_irq(fpc1020->irq_gpio));
|
||||
|
||||
/* Request that the interrupt should be wakeable */
|
||||
enable_irq_wake(gpio_to_irq(fpc1020->irq_gpio));
|
||||
fpc1020->compatible_enabled = 1;
|
||||
if (of_property_read_bool(dev->of_node, "fpc,enable-on-boot")) {
|
||||
dev_info(dev, "Enabling hardware\n");
|
||||
(void)device_prepare(fpc1020, true);
|
||||
|
||||
}
|
||||
hw_reset(fpc1020);
|
||||
} else if (!strncmp(buf, "disable", strlen("disable")) && fpc1020->compatible_enabled != 0) {
|
||||
if (gpio_is_valid(fpc1020->irq_gpio)) {
|
||||
devm_gpio_free(dev, fpc1020->irq_gpio);
|
||||
pr_info("remove irq_gpio success\n");
|
||||
}
|
||||
if (gpio_is_valid(fpc1020->rst_gpio)) {
|
||||
devm_gpio_free(dev, fpc1020->rst_gpio);
|
||||
pr_info("remove rst_gpio success\n");
|
||||
}
|
||||
devm_free_irq(dev, gpio_to_irq(fpc1020->irq_gpio), fpc1020);
|
||||
fpc1020->compatible_enabled = 0;
|
||||
}
|
||||
return count;
|
||||
exit:
|
||||
return -EINVAL;
|
||||
}
|
||||
static DEVICE_ATTR(compatible_all, S_IWUSR, NULL, compatible_all_set);
|
||||
#endif
|
||||
|
||||
static struct attribute *attributes[] = {
|
||||
&dev_attr_pinctl_set.attr,
|
||||
|
@ -428,6 +584,9 @@ static struct attribute *attributes[] = {
|
|||
&dev_attr_handle_wakelock.attr,
|
||||
&dev_attr_clk_enable.attr,
|
||||
&dev_attr_irq.attr,
|
||||
#ifdef CONFIG_FPC_COMPAT
|
||||
&dev_attr_compatible_all.attr,
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -439,7 +598,8 @@ static irqreturn_t fpc1020_irq_handler(int irq, void *handle)
|
|||
{
|
||||
struct fpc1020_data *fpc1020 = handle;
|
||||
|
||||
pr_info("fpc1020 irq handler: %s\n", __func__);
|
||||
dev_dbg(fpc1020->dev, "%s\n", __func__);
|
||||
|
||||
mutex_lock(&fpc1020->lock);
|
||||
if (atomic_read(&fpc1020->wakeup_enabled)) {
|
||||
fpc1020->nbr_irqs_received++;
|
||||
|
@ -458,9 +618,7 @@ static int fpc1020_request_named_gpio(struct fpc1020_data *fpc1020,
|
|||
{
|
||||
struct device *dev = fpc1020->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
int rc;
|
||||
|
||||
rc = of_get_named_gpio(np, label, 0);
|
||||
int rc = of_get_named_gpio(np, label, 0);
|
||||
|
||||
if (rc < 0) {
|
||||
dev_err(dev, "failed to get '%s'\n", label);
|
||||
|
@ -481,69 +639,94 @@ static int fpc1020_request_named_gpio(struct fpc1020_data *fpc1020,
|
|||
static int fpc1020_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct fpc1020_data *fpc1020;
|
||||
int rc;
|
||||
int rc = 0;
|
||||
#ifndef CONFIG_FPC_COMPAT
|
||||
size_t i;
|
||||
int irqf = 0;
|
||||
int irqf;
|
||||
#endif
|
||||
struct device_node *np = dev->of_node;
|
||||
struct fpc1020_data *fpc1020 = devm_kzalloc(dev, sizeof(*fpc1020),
|
||||
GFP_KERNEL);
|
||||
|
||||
fpc1020 = devm_kzalloc(dev, sizeof(*fpc1020), GFP_KERNEL);
|
||||
if (!fpc1020)
|
||||
return -ENOMEM;
|
||||
if (!fpc1020) {
|
||||
dev_err(dev,
|
||||
"failed to allocate memory for struct fpc1020_data\n");
|
||||
rc = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
fpc1020->dev = dev;
|
||||
platform_set_drvdata(pdev, fpc1020);
|
||||
|
||||
if (!np) {
|
||||
dev_err(dev, "no of node found\n");
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
#ifndef CONFIG_FPC_COMPAT
|
||||
rc = fpc1020_request_named_gpio(fpc1020, "fpc,gpio_irq",
|
||||
&fpc1020->irq_gpio);
|
||||
if (rc)
|
||||
return -EINVAL;
|
||||
goto exit;
|
||||
rc = fpc1020_request_named_gpio(fpc1020, "fpc,gpio_rst",
|
||||
&fpc1020->rst_gpio);
|
||||
if (rc)
|
||||
return -EINVAL;
|
||||
goto exit;
|
||||
|
||||
fpc1020->fingerprint_pinctrl = devm_pinctrl_get(dev);
|
||||
if (IS_ERR(fpc1020->fingerprint_pinctrl)) {
|
||||
rc = PTR_ERR(fpc1020->fingerprint_pinctrl);
|
||||
dev_err(dev, "Cannot get pinctrl\n", rc);
|
||||
return rc;
|
||||
if (PTR_ERR(fpc1020->fingerprint_pinctrl) == -EPROBE_DEFER) {
|
||||
dev_info(dev, "pinctrl not ready\n");
|
||||
rc = -EPROBE_DEFER;
|
||||
goto exit;
|
||||
}
|
||||
dev_err(dev, "Target does not use pinctrl\n");
|
||||
fpc1020->fingerprint_pinctrl = NULL;
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(pctl_names); i++) {
|
||||
for (i = 0; i < ARRAY_SIZE(fpc1020->pinctrl_state); i++) {
|
||||
const char *n = pctl_names[i];
|
||||
struct pinctrl_state *state =
|
||||
pinctrl_lookup_state(fpc1020->fingerprint_pinctrl, n);
|
||||
if (IS_ERR(state)) {
|
||||
dev_err(dev, "cannot find '%s'\n", n);
|
||||
return PTR_ERR(state);
|
||||
rc = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
dev_dbg(dev, "found pin control %s\n", n);
|
||||
dev_info(dev, "found pin control %s\n", n);
|
||||
fpc1020->pinctrl_state[i] = state;
|
||||
}
|
||||
|
||||
select_pin_ctl(fpc1020, "fpc1020_reset_reset");
|
||||
select_pin_ctl(fpc1020, "fpc1020_irq_active");
|
||||
rc = select_pin_ctl(fpc1020, "fpc1020_reset_reset");
|
||||
if (rc)
|
||||
goto exit;
|
||||
rc = select_pin_ctl(fpc1020, "fpc1020_irq_active");
|
||||
if (rc)
|
||||
goto exit;
|
||||
|
||||
atomic_set(&fpc1020->wakeup_enabled, 0);
|
||||
|
||||
irqf = IRQF_TRIGGER_RISING | IRQF_ONESHOT;
|
||||
if (of_property_read_bool(dev->of_node, "fpc,enable-wakeup")) {
|
||||
irqf = IRQF_NO_SUSPEND;
|
||||
irqf |= IRQF_NO_SUSPEND;
|
||||
device_init_wakeup(dev, 1);
|
||||
}
|
||||
|
||||
mutex_init(&fpc1020->lock);
|
||||
rc = devm_request_threaded_irq(dev, gpio_to_irq(fpc1020->irq_gpio),
|
||||
NULL, fpc1020_irq_handler,
|
||||
irqf | IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
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));
|
||||
return rc;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "requested irq %d\n", gpio_to_irq(fpc1020->irq_gpio));
|
||||
|
||||
/* Request that the interrupt should be wakeable */
|
||||
enable_irq_wake(gpio_to_irq(fpc1020->irq_gpio));
|
||||
|
||||
fpc1020->ttw_wl = wakeup_source_register(dev, "fpc_ttw_wl");
|
||||
|
@ -553,17 +736,36 @@ static int fpc1020_probe(struct platform_device *pdev)
|
|||
rc = sysfs_create_group(&dev->kobj, &attribute_group);
|
||||
if (rc) {
|
||||
dev_err(dev, "could not create sysfs\n");
|
||||
return rc;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (of_property_read_bool(dev->of_node, "fpc,enable-on-boot")) {
|
||||
dev_dbg(dev, "Enabling hardware\n");
|
||||
device_prepare(fpc1020, true);
|
||||
dev_info(dev, "Enabling hardware\n");
|
||||
(void)device_prepare(fpc1020, true);
|
||||
}
|
||||
|
||||
hw_reset(fpc1020);
|
||||
rc = hw_reset(fpc1020);
|
||||
if (rc) {
|
||||
dev_err(dev, "hardware reset failed\n");
|
||||
goto exit;
|
||||
}
|
||||
#else
|
||||
mutex_init(&fpc1020->lock);
|
||||
|
||||
return 0;
|
||||
fpc1020->ttw_wl = wakeup_source_register("fpc_ttw_wl");
|
||||
if (!fpc1020->ttw_wl)
|
||||
return -ENOMEM;
|
||||
|
||||
rc = sysfs_create_group(&dev->kobj, &attribute_group);
|
||||
if (rc) {
|
||||
dev_err(dev, "could not create sysfs\n");
|
||||
goto exit;
|
||||
}
|
||||
#endif
|
||||
dev_info(dev, "%s: ok\n", __func__);
|
||||
|
||||
exit:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int fpc1020_remove(struct platform_device *pdev)
|
||||
|
@ -580,7 +782,7 @@ static int fpc1020_remove(struct platform_device *pdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id fpc1020_of_match[] = {
|
||||
static struct of_device_id fpc1020_of_match[] = {
|
||||
{ .compatible = "fpc,fpc1020", },
|
||||
{}
|
||||
};
|
||||
|
@ -589,13 +791,35 @@ 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,
|
||||
.remove = fpc1020_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(fpc1020_driver);
|
||||
static int __init fpc1020_init(void)
|
||||
{
|
||||
int rc = platform_driver_register(&fpc1020_driver);
|
||||
|
||||
if (!rc)
|
||||
pr_info("%s OK\n", __func__);
|
||||
else
|
||||
pr_err("%s %d\n", __func__, rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void __exit fpc1020_exit(void)
|
||||
{
|
||||
pr_info("%s\n", __func__);
|
||||
platform_driver_unregister(&fpc1020_driver);
|
||||
}
|
||||
|
||||
module_init(fpc1020_init);
|
||||
module_exit(fpc1020_exit);
|
||||
|
||||
MODULE_DESCRIPTION("FPC1020 Fingerprint sensor device driver.");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_AUTHOR("Aleksej Makarov");
|
||||
MODULE_AUTHOR("Henrik Tillman <henrik.tillman@fingerprints.com>");
|
||||
MODULE_DESCRIPTION("FPC1020 Fingerprint sensor device driver.");
|
||||
|
|
5
drivers/misc/goodix/Kconfig
Normal file
5
drivers/misc/goodix/Kconfig
Normal file
|
@ -0,0 +1,5 @@
|
|||
config GOODIX_FINGERPRINT
|
||||
tristate "generic goodix fingerprint driver"
|
||||
default y
|
||||
help
|
||||
add support for goodix fingerprint driver.
|
1
drivers/misc/goodix/Makefile
Normal file
1
drivers/misc/goodix/Makefile
Normal file
|
@ -0,0 +1 @@
|
|||
obj-$(CONFIG_GOODIX_FINGERPRINT) += gf_spi.o platform.o netlink.o
|
905
drivers/misc/goodix/gf_spi.c
Normal file
905
drivers/misc/goodix/gf_spi.c
Normal file
|
@ -0,0 +1,905 @@
|
|||
/*
|
||||
* TEE driver for goodix fingerprint sensor
|
||||
* Copyright (C) 2016 Goodix
|
||||
* Copyright (C) 2020 XiaoMi, Inc.
|
||||
*
|
||||
* 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 <linux/pm_wakeup.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 11
|
||||
|
||||
#define WAKELOCK_HOLD_TIME 500 /* in ms */
|
||||
|
||||
#define GF_SPIDEV_NAME "goodix,fingerprint"
|
||||
/*device name after register in charater*/
|
||||
#define GF_DEV_NAME "goodix_fp"
|
||||
#define GF_INPUT_NAME "uinput-goodix" /*"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 wakeup_source fp_wakelock;
|
||||
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 irqreturn_t gf_irq(int irq, void *handle)
|
||||
{
|
||||
#if defined(GF_NETLINK_ENABLE)
|
||||
char msg = GF_NET_EVENT_IRQ;
|
||||
|
||||
__pm_wakeup_event(&fp_wakelock, 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 irq_setup(struct gf_dev *gf_dev)
|
||||
{
|
||||
int status;
|
||||
|
||||
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);
|
||||
return status;
|
||||
}
|
||||
enable_irq_wake(gf_dev->irq);
|
||||
gf_dev->irq_enabled = 1;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void irq_cleanup(struct gf_dev *gf_dev)
|
||||
{
|
||||
gf_dev->irq_enabled = 0;
|
||||
disable_irq(gf_dev->irq);
|
||||
disable_irq_wake(gf_dev->irq);
|
||||
free_irq(gf_dev->irq, gf_dev);
|
||||
}
|
||||
|
||||
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))) {
|
||||
pr_err("GF_IOC_INIT failed\n");
|
||||
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__);
|
||||
irq_cleanup(gf_dev);
|
||||
gf_cleanup(gf_dev);
|
||||
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 int gf_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct gf_dev *gf_dev = &gf;
|
||||
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) {
|
||||
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) {
|
||||
status = gf_parse_dts(gf_dev);
|
||||
if (status)
|
||||
goto err_parse_dt;
|
||||
|
||||
status = irq_setup(gf_dev);
|
||||
if (status)
|
||||
goto err_irq;
|
||||
}
|
||||
gf_hw_reset(gf_dev, 3);
|
||||
gf_dev->device_available = 1;
|
||||
}
|
||||
} else {
|
||||
pr_info("No device for minor %d\n", iminor(inode));
|
||||
}
|
||||
mutex_unlock(&device_list_lock);
|
||||
|
||||
return status;
|
||||
err_irq:
|
||||
gf_cleanup(gf_dev);
|
||||
err_parse_dt:
|
||||
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 = &gf;
|
||||
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("disble_irq. irq = %d\n", gf_dev->irq);
|
||||
//gf_disable_irq(gf_dev);
|
||||
//add Release resources fangshasha 20180622 start
|
||||
irq_cleanup(gf_dev);
|
||||
gf_cleanup(gf_dev);
|
||||
//add Release resources fangshasha 20180622 end
|
||||
/*power off the sensor*/
|
||||
gf_dev->device_available = 0;
|
||||
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;
|
||||
unsigned 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:
|
||||
if (gf_dev->device_available == 1) {
|
||||
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:
|
||||
if (gf_dev->device_available == 1) {
|
||||
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;
|
||||
int i;
|
||||
|
||||
/* Initialize the driver data */
|
||||
INIT_LIST_HEAD(&gf_dev->device_entry);
|
||||
#if defined(USE_SPI_BUS)
|
||||
gf_dev->spi = spi;
|
||||
#elif defined(USE_PLATFORM_BUS)
|
||||
gf_dev->spi = pdev;
|
||||
#endif
|
||||
gf_dev->irq_gpio = -EINVAL;
|
||||
gf_dev->reset_gpio = -EINVAL;
|
||||
gf_dev->pwr_gpio = -EINVAL;
|
||||
gf_dev->device_available = 0;
|
||||
gf_dev->fb_black = 0;
|
||||
|
||||
/* 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;
|
||||
goto error_hw;
|
||||
}
|
||||
mutex_unlock(&device_list_lock);
|
||||
|
||||
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);
|
||||
|
||||
wakeup_source_init(&fp_wakelock, "fp_wakelock");
|
||||
|
||||
pr_info("version V%d.%d.%02d\n", VER_MAJOR, VER_MINOR, PATCH_LEVEL);
|
||||
|
||||
return status;
|
||||
|
||||
#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_dev->device_available = 0;
|
||||
|
||||
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;
|
||||
|
||||
wakeup_source_trash(&fp_wakelock);
|
||||
fb_unregister_client(&gf_dev->notifier);
|
||||
if (gf_dev->input)
|
||||
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);
|
||||
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");
|
154
drivers/misc/goodix/gf_spi.h
Normal file
154
drivers/misc/goodix/gf_spi.h
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* driver definition for sensor driver
|
||||
*
|
||||
* Coypright (c) 2017 Goodix
|
||||
*/
|
||||
#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_SELECT
|
||||
#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 magic number
|
||||
#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 users;
|
||||
signed irq_gpio;
|
||||
signed reset_gpio;
|
||||
signed pwr_gpio;
|
||||
int irq;
|
||||
int irq_enabled;
|
||||
int clk_enabled;
|
||||
#ifdef GF_FASYNC
|
||||
struct fasync_struct *async;
|
||||
#endif
|
||||
struct notifier_block notifier;
|
||||
char device_available;
|
||||
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*/
|
104
drivers/misc/goodix/netlink.c
Normal file
104
drivers/misc/goodix/netlink.c
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* netlink interface
|
||||
*
|
||||
* Copyright (c) 2017 Goodix
|
||||
* Copyright (C) 2020 XiaoMi, Inc.
|
||||
*/
|
||||
#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");
|
||||
}
|
131
drivers/misc/goodix/platform.c
Normal file
131
drivers/misc/goodix/platform.c
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* platform indepent driver interface
|
||||
*
|
||||
* Coypritht (c) 2017 Goodix
|
||||
*/
|
||||
#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-power-en", 0);
|
||||
if (gf_dev->pwr_gpio < 0) {
|
||||
pr_err("falied to get pwr gpio!\n");
|
||||
return 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);
|
||||
|
||||
gf_dev->reset_gpio = of_get_named_gpio(np, "fp-gpio-reset", 0);
|
||||
if (gf_dev->reset_gpio < 0) {
|
||||
pr_err("falied 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("falied 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:
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
int gf_power_on(struct gf_dev *gf_dev)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
/* TODO: add your power control here */
|
||||
return rc;
|
||||
}
|
||||
|
||||
int gf_power_off(struct gf_dev *gf_dev)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
/* TODO: add your power control here */
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int gf_hw_reset(struct gf_dev *gf_dev, unsigned int delay_ms)
|
||||
{
|
||||
int ret = -1;
|
||||
|
||||
if (gf_dev == NULL) {
|
||||
pr_info("Input buff is NULL.\n");
|
||||
return ret;
|
||||
}
|
||||
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)
|
||||
{
|
||||
int ret = -1;
|
||||
if (gf_dev == NULL) {
|
||||
pr_info("Input buff is NULL.\n");
|
||||
return ret;
|
||||
} else {
|
||||
return gpio_to_irq(gf_dev->irq_gpio);
|
||||
}
|
||||
}
|
7
drivers/misc/hqsysfs/Kconfig
Normal file
7
drivers/misc/hqsysfs/Kconfig
Normal file
|
@ -0,0 +1,7 @@
|
|||
config HQ_SYSFS_SUPPORT
|
||||
bool "Enable Huaqin Sysfs Driver"
|
||||
default n
|
||||
help
|
||||
Huaqin Sysfs information driver includes specific information about hardware info and other info for HQ tools
|
||||
|
||||
|
15
drivers/misc/hqsysfs/Makefile
Normal file
15
drivers/misc/hqsysfs/Makefile
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Copyright (C) 2015 MediaTek Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2 as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
|
||||
obj-y := hqsysfs.o
|
||||
obj-y += hqsys_misc.o
|
||||
obj-y += hqsys_pcba.o
|
264
drivers/misc/hqsysfs/hqsys_misc.c
Normal file
264
drivers/misc/hqsysfs/hqsys_misc.c
Normal file
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* Copyright (C) 2015 MediaTek Inc.
|
||||
* Copyright (C) 2020 XiaoMi, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "hqsys_misc.h"
|
||||
|
||||
|
||||
MISC_INFO(MISC_EMMC_SIZE, emmc_size);
|
||||
MISC_INFO(MISC_RAM_SIZE, ram_size);
|
||||
MISC_INFO(MISC_BOOT_MODE, boot_mode);
|
||||
MISC_INFO(MISC_OTP_SN, otp_sn);
|
||||
|
||||
extern unsigned int msdc_get_capacity(int get_emmc_total);
|
||||
extern char *get_emmc_name(void);
|
||||
|
||||
unsigned int round_kbytes_to_readable_mbytes(unsigned int k) {
|
||||
unsigned int r_size_m = 0;
|
||||
unsigned int in_mega = k/1024;
|
||||
|
||||
if (in_mega > 64*1024) { //if memory is larger than 64G
|
||||
r_size_m = 128*1024; // It should be 128G
|
||||
}else if (in_mega > 32*1024) { //larger than 32G
|
||||
r_size_m = 64*1024; //should be 64G
|
||||
}else if (in_mega > 16*1024) {
|
||||
r_size_m = 32*1024;
|
||||
}else if (in_mega > 8*1024) {
|
||||
r_size_m = 16*1024;
|
||||
}else if (in_mega > 6*1024) {
|
||||
r_size_m = 8*1024;
|
||||
}else if (in_mega > 4*1024) {
|
||||
r_size_m = 6*1024; //RAM may be 6G
|
||||
}else if (in_mega > 3*1024) {
|
||||
r_size_m = 4*1024;
|
||||
}else if (in_mega > 2*1024) {
|
||||
r_size_m = 3*1024; //RAM may be 3G
|
||||
}else if (in_mega > 1024) {
|
||||
r_size_m = 2*1024;
|
||||
}else if (in_mega > 512) {
|
||||
r_size_m = 1024;
|
||||
}else if (in_mega > 256) {
|
||||
r_size_m = 512;
|
||||
}else if (in_mega > 128) {
|
||||
r_size_m = 256;
|
||||
}else {
|
||||
k = 0;
|
||||
}
|
||||
return r_size_m;
|
||||
}
|
||||
|
||||
ssize_t hq_emmcinfo(char *buf)
|
||||
{
|
||||
ssize_t count = -1;
|
||||
struct file *pfile = NULL;
|
||||
mm_segment_t old_fs;
|
||||
loff_t pos;
|
||||
ssize_t ret = 0;
|
||||
|
||||
unsigned long long Size_buf = 0;
|
||||
char buf_size[qcom_emmc_len];
|
||||
|
||||
memset(buf_size, 0, sizeof(buf_size));
|
||||
|
||||
pfile = filp_open(qcom_emmc, O_RDONLY,0);
|
||||
if (IS_ERR(pfile)) {
|
||||
goto ERR_0;
|
||||
}
|
||||
|
||||
old_fs = get_fs();
|
||||
set_fs(KERNEL_DS);
|
||||
pos = 0;
|
||||
|
||||
ret = vfs_read(pfile, buf_size, qcom_emmc_len, &pos);
|
||||
if (ret <= 0 ) {
|
||||
goto ERR_1;
|
||||
}
|
||||
|
||||
Size_buf = simple_strtoull(buf_size, NULL, 0);
|
||||
|
||||
Size_buf >>= 1; //Switch to KB
|
||||
|
||||
|
||||
count = sprintf(buf, "%dGB", round_kbytes_to_readable_mbytes((unsigned int)Size_buf)/1024);
|
||||
|
||||
ERR_1:
|
||||
|
||||
filp_close(pfile, NULL);
|
||||
|
||||
set_fs(old_fs);
|
||||
|
||||
return count;
|
||||
|
||||
ERR_0:
|
||||
return count;
|
||||
|
||||
}
|
||||
|
||||
static struct attribute *hq_misc_attrs[] = {
|
||||
&misc_info_emmc_size.attr,
|
||||
&misc_info_ram_size.attr,
|
||||
&misc_info_boot_mode.attr,
|
||||
&misc_info_otp_sn.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
#if 0
|
||||
extern int hq_read_sn_from_otp(char *sn);
|
||||
extern int hq_write_sn_to_otp(char *sn, unsigned int len);
|
||||
#endif
|
||||
#define SN_LEN (12) //for B6H Nikeh
|
||||
|
||||
static ssize_t hq_misc_show(struct kobject *kobj, struct attribute *a, char *buf) {
|
||||
ssize_t count = 0;
|
||||
|
||||
struct misc_info *mi = container_of(a, struct misc_info, attr);
|
||||
|
||||
switch (mi->m_id) {
|
||||
case MISC_RAM_SIZE:
|
||||
{
|
||||
#define K(x) ((x) << (PAGE_SHIFT - 10))
|
||||
struct sysinfo i;
|
||||
si_meminfo(&i);
|
||||
//count=sprintf(buf,"%u",(unsigned int)K(i.totalram));
|
||||
|
||||
if(round_kbytes_to_readable_mbytes(K(i.totalram)) >= 1024) {
|
||||
count = sprintf(buf, "%dGB", round_kbytes_to_readable_mbytes(K(i.totalram))/1024);
|
||||
}else {
|
||||
count = sprintf(buf, "%dMB", round_kbytes_to_readable_mbytes(K(i.totalram)));
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case MISC_EMMC_SIZE:
|
||||
//count = sprintf(buf,"%dGB",round_kbytes_to_readable_mbytes(msdc_get_capacity(1)/2)/1024);
|
||||
count = hq_emmcinfo(buf);
|
||||
break;
|
||||
case MISC_OTP_SN:
|
||||
#if 0 //#ifdef CONFIG_MTK_EMMC_SUPPORT_OTP
|
||||
{
|
||||
char temp[SN_LEN+1] = {0};
|
||||
int result = 0;
|
||||
|
||||
int i = 0;
|
||||
|
||||
result = hq_read_sn_from_otp(temp);
|
||||
|
||||
if (0 == result) {
|
||||
//#if 0
|
||||
//check if alpha and num
|
||||
for (i=0; i<SN_LEN; i++){
|
||||
if (!isalnum(temp[i])) {
|
||||
count = sprintf(buf,"Not Valid SN\n");
|
||||
goto r_error;
|
||||
}
|
||||
}
|
||||
//#endif
|
||||
count = sprintf(buf,"%s", temp);
|
||||
}else {
|
||||
count = sprintf(buf, "Read SN in OTP error %d\n", result);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
count = sprintf(buf, "SN in OTP not enabled\n");
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
count = sprintf(buf, "Not support");
|
||||
break;
|
||||
}
|
||||
|
||||
//r_error:
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t hq_misc_store(struct kobject *kobj, struct attribute *a, const char *buf, size_t count) {
|
||||
|
||||
struct misc_info *mi = container_of(a, struct misc_info, attr);
|
||||
|
||||
switch (mi->m_id) {
|
||||
#if 0 //#ifdef CONFIG_MTK_EMMC_SUPPORT_OTP
|
||||
case MISC_OTP_SN:
|
||||
{
|
||||
char temp[SN_LEN+1] = {0};
|
||||
int result = 0;
|
||||
int i = 0;
|
||||
|
||||
if(0 != strncmp(buf, "SN:=",4)) {
|
||||
printk("[%s] invalid write sn command\n", __func__);
|
||||
break;
|
||||
}
|
||||
for (i=0; i<SN_LEN; i++) {
|
||||
temp[i] = buf[i+4];
|
||||
if(('\n' == buf[i+4]) || ('\r' == buf[i+4])) {
|
||||
temp[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
result = hq_write_sn_to_otp(temp,strlen(temp));
|
||||
if(0 != result)
|
||||
printk("[%s] called write error %d\n", __func__,result);
|
||||
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* hq_misc object */
|
||||
static struct kobject hq_misc_kobj;
|
||||
static const struct sysfs_ops hq_misc_sysfs_ops = {
|
||||
.show = hq_misc_show,
|
||||
.store = hq_misc_store,
|
||||
};
|
||||
|
||||
/* hq_misc type */
|
||||
static struct kobj_type hq_misc_ktype = {
|
||||
.sysfs_ops = &hq_misc_sysfs_ops,
|
||||
.default_attrs = hq_misc_attrs
|
||||
};
|
||||
|
||||
|
||||
static int __init create_misc(void) {
|
||||
int ret;
|
||||
|
||||
/* add kobject */
|
||||
ret = register_kboj_under_hqsysfs(&hq_misc_kobj, &hq_misc_ktype, HUAQIN_MISC_NAME);
|
||||
if (ret < 0) {
|
||||
pr_err("%s fail to add hq_misc_kobj\n",__func__);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int __init hq_misc_sys_init(void)
|
||||
{
|
||||
/* create sysfs entry at /sys/class/hq_misc/interface/misc */
|
||||
create_misc();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
late_initcall(hq_misc_sys_init);
|
||||
MODULE_AUTHOR("KaKa Ni <nigang@hq_misc.com>");
|
||||
MODULE_DESCRIPTION("Huaqin Hardware Info Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
112
drivers/misc/hqsysfs/hqsys_misc.h
Normal file
112
drivers/misc/hqsysfs/hqsys_misc.h
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright (C) 2015 MediaTek Inc.
|
||||
* Copyright (C) 2020 XiaoMi, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
#ifndef __HQ_SYSFS_MISC_H__
|
||||
#define __HQ_SYSFS_MISC_H__
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/kfifo.h>
|
||||
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/syscalls.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/of.h>
|
||||
#ifdef CONFIG_OF
|
||||
#include <linux/of_fdt.h>
|
||||
#endif
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/ctype.h>
|
||||
|
||||
#include <linux/hqsysfs.h>
|
||||
|
||||
#define qcom_emmc "/sys/class/mmc_host/mmc0/mmc0:0001/block/mmcblk0/size"
|
||||
#define qcom_emmc_len 16
|
||||
|
||||
#define HUAQIN_MISC_NAME "misc"
|
||||
|
||||
|
||||
enum misc_id{
|
||||
MISC_EMMC_SIZE = 0,
|
||||
MISC_RAM_SIZE,
|
||||
MISC_BOOT_MODE,
|
||||
MISC_OTP_SN,
|
||||
MISC_NONE
|
||||
};
|
||||
|
||||
struct emmc_info{
|
||||
unsigned int cid[4] ; //emmc cid
|
||||
const char *emmc_name; //emmc name
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct misc_info{
|
||||
enum misc_id m_id;
|
||||
struct attribute attr;
|
||||
};
|
||||
|
||||
struct cam_info{
|
||||
char *cam_drv_name;
|
||||
char *cam_vendro_name;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#define __MISC(_id, _misc_name) { \
|
||||
.m_id = _id, \
|
||||
.attr = {.name = __stringify(_misc_name), \
|
||||
.mode = VERIFY_OCTAL_PERMISSIONS(S_IWUSR|S_IRUGO) }, \
|
||||
}
|
||||
|
||||
|
||||
#define MISC_INFO(_id, _misc_name) \
|
||||
struct misc_info misc_info_##_misc_name = __MISC(_id, _misc_name)
|
||||
|
||||
|
||||
|
||||
#define __EMMC(cid_0,cid_1,cid_2,cid_3,_emmc_name) { \
|
||||
.cid[0] = cid_0, \
|
||||
.cid[1] = cid_1, \
|
||||
.cid[2] = cid_2, \
|
||||
.cid[3] = cid_3, \
|
||||
.emmc_name = __stringify(_emmc_name), \
|
||||
}
|
||||
|
||||
|
||||
#define EMMC_INFO(cid_0,cid_1,cid_2,cid_3, _emmc_name) \
|
||||
struct emmc_info emmc_info_##_emmc_name = __EMMC(cid_0,cid_1,cid_2,cid_3,_emmc_name)
|
||||
|
||||
|
||||
|
||||
#define CAM_MAP_INFO(_drv,_vendor) \
|
||||
struct cam_info cam_info_##_drv = { \
|
||||
.cam_drv_name = __stringify(_drv), \
|
||||
.cam_vendro_name = __stringify(_vendor), \
|
||||
}
|
||||
|
||||
char *get_emmc_name(void);
|
||||
char *map_cam_drv_to_vendor(char *drv);
|
||||
|
||||
|
||||
#endif
|
||||
|
109
drivers/misc/hqsysfs/hqsys_pcba.c
Normal file
109
drivers/misc/hqsysfs/hqsys_pcba.c
Normal file
|
@ -0,0 +1,109 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/iio/consumer.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/soc/qcom/smem.h>
|
||||
#include "hqsys_pcba.h"
|
||||
|
||||
#define SMEM_ID_VENDOR1 135
|
||||
|
||||
/*struct board_id_information {
|
||||
int adc_channel;
|
||||
int voltage;
|
||||
};
|
||||
|
||||
static struct board_id_information board_id;*/
|
||||
static PCBA_CONFIG huaqin_pcba_config = PCBA_UNKNOW;
|
||||
|
||||
//extern char *saved_command_line;
|
||||
//extern int IMM_GetOneChannelValue(int dwChannel, int data[4], int *rawdata);
|
||||
//static bool read_pcba_config(void);
|
||||
|
||||
static void read_pcba_config_form_smem(void)
|
||||
{
|
||||
PCBA_CONFIG *pcba_config = NULL;
|
||||
size_t size;
|
||||
pcba_config = (PCBA_CONFIG *)qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_ID_VENDOR1, &size);
|
||||
if (pcba_config) {
|
||||
pr_err("pcba config =%d.\n", *(pcba_config));
|
||||
if (*(pcba_config) > PCBA_UNKNOW && *(pcba_config) < PCBA_END) {
|
||||
huaqin_pcba_config = *pcba_config;
|
||||
} else {
|
||||
huaqin_pcba_config = PCBA_UNKNOW;
|
||||
}
|
||||
} else {
|
||||
pr_err("pcba config fail\n");
|
||||
|
||||
huaqin_pcba_config = PCBA_UNKNOW;
|
||||
}
|
||||
}
|
||||
|
||||
PCBA_CONFIG get_huaqin_pcba_config(void)
|
||||
{
|
||||
return huaqin_pcba_config;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(get_huaqin_pcba_config);
|
||||
|
||||
|
||||
#if 0
|
||||
static int board_id_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
printk("board_id_probe enter\n");
|
||||
ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
|
||||
if (ret) {
|
||||
pr_err("[%s] Failed %d!!!\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
read_pcba_config_form_smem();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int board_id_remove(struct platform_device *pdev)
|
||||
{
|
||||
pr_err("enter [%s] \n", __func__);
|
||||
return 0;
|
||||
}
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id boardId_of_match[] = {
|
||||
{.compatible = "mediatek,board_id",},
|
||||
{},
|
||||
};
|
||||
#endif
|
||||
|
||||
static struct platform_driver boardId_driver = {
|
||||
.probe = board_id_probe,
|
||||
.remove = board_id_remove,
|
||||
.driver = {
|
||||
.name = "board_id",
|
||||
.owner = THIS_MODULE,
|
||||
#ifdef CONFIG_OF
|
||||
.of_match_table = boardId_of_match,
|
||||
#endif
|
||||
},
|
||||
};
|
||||
#endif
|
||||
|
||||
static int __init huaqin_pcba_early_init(void) {
|
||||
read_pcba_config_form_smem();
|
||||
return 0;
|
||||
}
|
||||
|
||||
subsys_initcall(huaqin_pcba_early_init); //before device_initcall
|
||||
|
||||
//late_initcall(huaqin_pcba_early_init); //late initcall
|
||||
|
||||
MODULE_AUTHOR("lizheng<LiZheng6@huaqin.com>");
|
||||
MODULE_DESCRIPTION("huaqin sys pcba");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
46
drivers/misc/hqsysfs/hqsys_pcba.h
Normal file
46
drivers/misc/hqsysfs/hqsys_pcba.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
#ifndef HQSYS_PCBA
|
||||
#define HQSYS_PCBA
|
||||
|
||||
typedef enum {
|
||||
PCBA_UNKNOW = 0,
|
||||
PCBA_J19S_P0_1_INDIA,
|
||||
PCBA_J19S_P0_1_CN,
|
||||
PCBA_J19C_P0_1_GLOBAL,
|
||||
PCBA_J19C_P0_1_INDIA,
|
||||
PCBA_J19S_P1_INDIA,
|
||||
PCBA_J19S_P1_CN,
|
||||
PCBA_J19C_P1_GLOBAL,
|
||||
PCBA_J19C_P1_INDIA,
|
||||
PCBA_J19S_P1_1_INDIA,
|
||||
PCBA_J19S_P1_1_CN,
|
||||
PCBA_J19C_P1_1_GLOBAL,
|
||||
PCBA_J19C_P1_1_THAILAND,
|
||||
PCBA_J19C_P1_1_INDIA,
|
||||
PCBA_J19S_P2_INDIA,
|
||||
PCBA_J19S_P2_CN,
|
||||
PCBA_J19C_P2_GLOBAL,
|
||||
PCBA_J19C_P2_INDIA,
|
||||
PCBA_J19S_P1_GLOBAL,
|
||||
PCBA_J19S_P1_JAPAN,
|
||||
PCBA_J19S_P1_THAILAND,
|
||||
PCBA_J19L_P0_1_LA,
|
||||
PCBA_J19N_P0_1_GLOBAL_CARRIERS,
|
||||
PCBA_J19S_MP_INDIA,
|
||||
PCBA_J19S_MP_CN,
|
||||
PCBA_J19C_MP_GLOBAL,
|
||||
PCBA_J19C_MP_THAILAND,
|
||||
PCBA_J19C_MP_INDIA,
|
||||
PCBA_J19S_P2_GLOBAL,
|
||||
PCBA_J19S_P2_JAPAN,
|
||||
PCBA_END,
|
||||
} PCBA_CONFIG;
|
||||
|
||||
struct pcba_info {
|
||||
PCBA_CONFIG pcba_config;
|
||||
char pcba_name[32];
|
||||
};
|
||||
|
||||
PCBA_CONFIG get_huaqin_pcba_config(void);
|
||||
|
||||
#endif
|
||||
|
467
drivers/misc/hqsysfs/hqsysfs.c
Normal file
467
drivers/misc/hqsysfs/hqsysfs.c
Normal file
|
@ -0,0 +1,467 @@
|
|||
/*
|
||||
* Copyright (C) 2015 MediaTek Inc.
|
||||
* Copyright (C) 2020 XiaoMi, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/hqsysfs.h>
|
||||
#include <linux/qpnp/qpnp-revid.h>
|
||||
#include "hqsys_misc.h"
|
||||
#include "hqsys_pcba.h"
|
||||
|
||||
#define HQ_SYS_FS_VER "2016-03-11 V0.2"
|
||||
|
||||
static HW_INFO(HWID_VER, ver);
|
||||
static HW_INFO(HWID_SUMMARY, hw_summary);
|
||||
static HW_INFO(HWID_DDR, ram);
|
||||
static HW_INFO(HWID_EMMC, emmc);
|
||||
static HW_INFO(HWID_LCM, lcm);
|
||||
//static HW_INFO(HWID_BIAS_IC,lcm_bias_ic);
|
||||
static HW_INFO(HWID_CTP, ctp);
|
||||
static HW_INFO(HWID_SUB_CAM, sub_cam); //sub
|
||||
static HW_INFO(HWID_SUB_CAM_2, main0_cam); //main0
|
||||
static HW_INFO(HWID_MAIN_CAM, main1_cam); //main1
|
||||
static HW_INFO(HWID_MAIN_CAM_2, main2_cam); //main2
|
||||
static HW_INFO(HWID_MAIN_CAM_3, main3_cam); //main3
|
||||
|
||||
static HW_INFO(HWID_MAIN_LENS, main_cam_len);
|
||||
static HW_INFO(HWID_FLASHLIGHT, flashlight);
|
||||
static HW_INFO(HWID_GSENSOR, gsensor);
|
||||
static HW_INFO(HWID_ALSPS, alsps);
|
||||
static HW_INFO(HWID_MSENSOR, msensor);
|
||||
static HW_INFO(HWID_GYRO, gyro);
|
||||
static HW_INFO(HWID_IRDA, irda);
|
||||
static HW_INFO(HWID_FUEL_GAUGE_IC, fuel_gauge_ic);
|
||||
static HW_INFO(HWID_NFC, nfc);
|
||||
static HW_INFO(HWID_FP, fingerprint);
|
||||
//static HW_INFO(HWID_TEE,tee);
|
||||
static HW_INFO(HWID_PCBA, pcba_config);
|
||||
static HW_INFO(HWID_PMIC, pmic_verison);
|
||||
|
||||
struct pcba_info pcba[] = {
|
||||
{PCBA_J19S_P0_1_INDIA, "PCBA_J19S_P0-1_INDIA"},
|
||||
{PCBA_J19S_P0_1_CN, "PCBA_J19S_P0-1_CN"},
|
||||
{PCBA_J19C_P0_1_GLOBAL, "PCBA_J19C_P0-1_GLOBAL"},
|
||||
{PCBA_J19C_P0_1_INDIA, "PCBA_J19C_P0-1_INDIA"},
|
||||
{PCBA_J19S_P1_INDIA, "PCBA_J19S_P1_INDIA"},
|
||||
{PCBA_J19S_P1_CN, "PCBA_J19S_P1_CN"},
|
||||
{PCBA_J19C_P1_GLOBAL, "PCBA_J19C_P1_GLOBAL"},
|
||||
{PCBA_J19C_P1_INDIA, "PCBA_J19C_P1_INDIA"},
|
||||
{PCBA_J19S_P1_1_INDIA, "PCBA_J19S_P1-1_INDIA"},
|
||||
{PCBA_J19S_P1_1_CN, "PCBA_J19S_P1-1_CN"},
|
||||
{PCBA_J19C_P1_1_GLOBAL, "PCBA_J19C_P1-1_GLOBAL"},
|
||||
{PCBA_J19C_P1_1_THAILAND, "PCBA_J19C_P1-1_THAILAND"},
|
||||
{PCBA_J19C_P1_1_INDIA, "PCBA_J19C_P1-1_INDIA"},
|
||||
{PCBA_J19S_P2_INDIA, "PCBA_J19S_P2_INDIA"},
|
||||
{PCBA_J19S_P2_CN, "PCBA_J19S_P2_CN"},
|
||||
{PCBA_J19C_P2_GLOBAL, "PCBA_J19C_P2_GLOBAL"},
|
||||
{PCBA_J19C_P2_INDIA, "PCBA_J19C_P2_INDIA"},
|
||||
{PCBA_J19S_P1_GLOBAL, "PCBA_J19S_P1_GLOBAL"},
|
||||
{PCBA_J19S_P1_JAPAN, "PCBA_J19S_P1_JAPAN"},
|
||||
{PCBA_J19S_P1_THAILAND, "PCBA_J19S_P1_THAILAND"},
|
||||
{PCBA_J19L_P0_1_LA, "PCBA_J19L_P0_1_LA"},
|
||||
{PCBA_J19N_P0_1_GLOBAL_CARRIERS, "PCBA_J19N_P0_1_GLOBAL_CARRIERS"},
|
||||
{PCBA_J19S_MP_INDIA, "PCBA_J19S_MP_INDIA"},
|
||||
{PCBA_J19S_MP_CN, "PCBA_J19S_MP_CN"},
|
||||
{PCBA_J19C_MP_GLOBAL, "PCBA_J19C_MP_GLOBAL"},
|
||||
{PCBA_J19C_MP_THAILAND, "PCBA_J19C_MP_THAILAND"},
|
||||
{PCBA_J19C_MP_INDIA, "PCBA_J19C_MP_INDIA"},
|
||||
{PCBA_J19S_P2_GLOBAL, "PCBA_J19S_P2_GLOBAL"},
|
||||
{PCBA_J19S_P2_JAPAN, "PCBA_J19S_P2_JAPAN"},
|
||||
};
|
||||
|
||||
static PCBA_CONFIG huaqin_pcba_config;// = PCBA_UNKNOW;
|
||||
|
||||
static struct attribute *huaqin_attrs[] = {
|
||||
&hw_info_ver.attr,
|
||||
&hw_info_hw_summary.attr,
|
||||
&hw_info_ram.attr,
|
||||
&hw_info_emmc.attr,
|
||||
&hw_info_lcm.attr,
|
||||
// &hw_info_lcm_bias_ic.attr,
|
||||
&hw_info_ctp.attr,
|
||||
&hw_info_sub_cam.attr,
|
||||
&hw_info_main0_cam.attr,
|
||||
&hw_info_main1_cam.attr,
|
||||
&hw_info_main2_cam.attr,
|
||||
&hw_info_main3_cam.attr,
|
||||
|
||||
&hw_info_main_cam_len.attr,
|
||||
&hw_info_flashlight.attr,
|
||||
&hw_info_gsensor.attr,
|
||||
&hw_info_alsps.attr,
|
||||
&hw_info_msensor.attr,
|
||||
&hw_info_gyro.attr,
|
||||
&hw_info_irda.attr,
|
||||
&hw_info_fuel_gauge_ic.attr,
|
||||
&hw_info_nfc.attr,
|
||||
&hw_info_fingerprint.attr,
|
||||
&hw_info_pcba_config.attr,
|
||||
&hw_info_pmic_verison.attr,
|
||||
// &hw_info_tee.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static ssize_t huaqin_show(struct kobject *kobj, struct attribute *a, char *buf)
|
||||
{
|
||||
ssize_t count = 0;
|
||||
int i = 0;
|
||||
struct hw_info *hw = container_of(a, struct hw_info, attr);
|
||||
|
||||
if (hw == NULL) {
|
||||
return snprintf(buf, 128, "Data error\n");
|
||||
}
|
||||
|
||||
if (HWID_VER == hw->hw_id) {
|
||||
count = snprintf(buf, 128, "%s\n", HQ_SYS_FS_VER);
|
||||
} else if (HWID_SUMMARY == hw->hw_id) {
|
||||
//iterate all device and output the detail
|
||||
int iterator = 0;
|
||||
struct hw_info *curent_hw = NULL;
|
||||
struct attribute *attr = huaqin_attrs[iterator];
|
||||
|
||||
while (attr) {
|
||||
curent_hw = container_of(attr, struct hw_info, attr);
|
||||
iterator += 1;
|
||||
attr = huaqin_attrs[iterator];
|
||||
|
||||
if (curent_hw->hw_exist && (curent_hw->hw_device_name != NULL)) {
|
||||
count += snprintf(buf+count, 128, "%s: %s\n", curent_hw->attr.name, curent_hw->hw_device_name);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (HWID_PCBA == hw->hw_id) {
|
||||
|
||||
if (get_huaqin_pcba_config() >= PCBA_UNKNOW && get_huaqin_pcba_config() < PCBA_END) {
|
||||
huaqin_pcba_config = get_huaqin_pcba_config();
|
||||
} else {
|
||||
huaqin_pcba_config = PCBA_UNKNOW;
|
||||
}
|
||||
|
||||
for (i = 0; i < sizeof(pcba)/sizeof(struct pcba_info); i++) {
|
||||
if (huaqin_pcba_config == pcba[i].pcba_config) {
|
||||
count = snprintf (buf, 128, "%s\n", pcba[i].pcba_name);
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
count = snprintf(buf, 128, "%s\n", "PCBA_UNKONW");
|
||||
} else if (HWID_PMIC == hw->hw_id) {
|
||||
count = snprintf (buf, 128, "%s\n", hq_pmic_string);
|
||||
} else {
|
||||
|
||||
if (0 == hw->hw_exist) {
|
||||
count = snprintf (buf, 128, "Not support\n");
|
||||
} else if (NULL == hw->hw_device_name) {
|
||||
count = snprintf (buf, 128, "Installed with no device Name\n");
|
||||
} else {
|
||||
count = snprintf (buf, 128, "%s\n", hw->hw_device_name);
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t huaqin_store(struct kobject *kobj, struct attribute *a, const char *buf, size_t count)
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
/* huaqin object */
|
||||
static struct kobject huaqin_kobj;
|
||||
static const struct sysfs_ops huaqin_sysfs_ops = {
|
||||
.show = huaqin_show,
|
||||
.store = huaqin_store,
|
||||
};
|
||||
|
||||
/* huaqin type */
|
||||
static struct kobj_type huaqin_ktype = {
|
||||
.sysfs_ops = &huaqin_sysfs_ops,
|
||||
.default_attrs = huaqin_attrs
|
||||
};
|
||||
|
||||
/* huaqin device class */
|
||||
static struct class *huaqin_class;
|
||||
static struct device *huaqin_hw_device;
|
||||
|
||||
|
||||
int register_kboj_under_hqsysfs(struct kobject *kobj, struct kobj_type *ktype, const char *fmt, ...)
|
||||
{
|
||||
return kobject_init_and_add(kobj, ktype, &(huaqin_hw_device->kobj), fmt);
|
||||
}
|
||||
|
||||
static int __init create_sysfs(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* create class (device model) */
|
||||
huaqin_class = class_create(THIS_MODULE, HUAQIN_CLASS_NAME);
|
||||
if (IS_ERR(huaqin_class)) {
|
||||
pr_err ("%s fail to create class\n", __func__);
|
||||
return (int)-1;
|
||||
}
|
||||
|
||||
huaqin_hw_device = device_create(huaqin_class, NULL, MKDEV(0, 0), NULL, HUAIN_INTERFACE_NAME);
|
||||
if (IS_ERR(huaqin_hw_device)) {
|
||||
pr_warn("fail to create device\n");
|
||||
return (int)-1;
|
||||
}
|
||||
|
||||
/* add kobject */
|
||||
ret = kobject_init_and_add (&huaqin_kobj, &huaqin_ktype, &(huaqin_hw_device->kobj), HUAQIN_HWID_NAME);
|
||||
if (ret < 0) {
|
||||
pr_err ("%s fail to add kobject\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hq_deregister_hw_info (enum hardware_id id, char *device_name)
|
||||
{
|
||||
int ret = 0;
|
||||
int find_hw_id = 0;
|
||||
int iterator = 0;
|
||||
|
||||
struct hw_info *hw = NULL;
|
||||
struct attribute *attr = huaqin_attrs[iterator];
|
||||
|
||||
if (NULL == device_name) {
|
||||
pr_err ("[%s]: device_name does not allow empty\n", __func__);
|
||||
ret = -2;
|
||||
goto err;
|
||||
}
|
||||
|
||||
while (attr) {
|
||||
hw = container_of (attr, struct hw_info, attr);
|
||||
|
||||
iterator += 1;
|
||||
attr = huaqin_attrs[iterator];
|
||||
|
||||
if (NULL == hw) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (id == hw->hw_id) {
|
||||
find_hw_id = 1;
|
||||
|
||||
if (0 == hw->hw_exist) {
|
||||
pr_err("[%s]: device has not registed hw->id:0x%x . Cant be deregistered\n"
|
||||
, __func__
|
||||
, hw->hw_id);
|
||||
|
||||
ret = -4;
|
||||
goto err;
|
||||
} else if (NULL == hw->hw_device_name) {
|
||||
|
||||
pr_err("[%s]:hw_id is 0x%x Device name cant be NULL\n"
|
||||
, __func__
|
||||
, hw->hw_id);
|
||||
ret = -5;
|
||||
goto err;
|
||||
} else {
|
||||
if (0 == strncmp(hw->hw_device_name, device_name, strlen(hw->hw_device_name))) {
|
||||
hw->hw_device_name = NULL;
|
||||
hw->hw_exist = 0;
|
||||
} else {
|
||||
pr_err("[%s]: hw_id is 0x%x Registered device name %s , want to deregister: %s\n"
|
||||
, __func__
|
||||
, hw->hw_id
|
||||
, hw->hw_device_name
|
||||
, device_name);
|
||||
ret = -6;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
goto err;
|
||||
|
||||
} else
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
if (0 == find_hw_id) {
|
||||
pr_err("[%s]: Cant find correct hardware_id: 0x%x\n", __func__, id);
|
||||
ret = -3;
|
||||
}
|
||||
|
||||
err:
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
|
||||
int hq_regiser_hw_info(enum hardware_id id, char *device_name)
|
||||
{
|
||||
|
||||
int ret = 0;
|
||||
int find_hw_id = 0;
|
||||
int iterator = 0;
|
||||
|
||||
struct hw_info *hw = NULL;
|
||||
struct attribute *attr = huaqin_attrs[iterator];
|
||||
|
||||
if (NULL == device_name) {
|
||||
pr_err("[%s]: device_name does not allow empty\n", __func__);
|
||||
ret = -2;
|
||||
goto err;
|
||||
}
|
||||
|
||||
while (attr) {
|
||||
hw = container_of(attr, struct hw_info, attr);
|
||||
|
||||
iterator += 1;
|
||||
attr = huaqin_attrs[iterator];
|
||||
|
||||
if (NULL == hw) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (id == hw->hw_id) {
|
||||
find_hw_id = 1;
|
||||
|
||||
if (hw->hw_exist) {
|
||||
pr_err("[%s]: device has already registed hw->id:0x%x hw_device_name:%s\n"
|
||||
, __func__
|
||||
, hw->hw_id
|
||||
, hw->hw_device_name);
|
||||
ret = -4;
|
||||
goto err;
|
||||
}
|
||||
|
||||
switch (hw->hw_id) {
|
||||
/*
|
||||
if(map_cam_drv_to_vendor(device_name))
|
||||
hw->hw_device_name = map_cam_drv_to_vendor(device_name);
|
||||
else
|
||||
hw->hw_device_name = "Can't find Camera Vendor";
|
||||
break;
|
||||
*/
|
||||
default:
|
||||
hw->hw_device_name = device_name;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
hw->hw_exist = 1;
|
||||
goto err;
|
||||
|
||||
} else
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
if (find_hw_id == 0) {
|
||||
pr_err(
|
||||
"[%s]: Cant find correct hardware_id: 0x%x\n", __func__, id);
|
||||
ret = -3;
|
||||
}
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(hq_regiser_hw_info);
|
||||
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/gpio.h>
|
||||
#define PROC_BOOT_REASON_FILE "boot_status"
|
||||
#define SDC_DETECT_STATUS "sdc_det_gpio_status"
|
||||
|
||||
#define SDC_DETECT_GPIO 343
|
||||
|
||||
static struct proc_dir_entry *boot_reason_proc;// = NULL;
|
||||
static struct proc_dir_entry *sdc_detect_status;// = NULL;
|
||||
static unsigned int boot_into_factory;// = 0;
|
||||
static int boot_reason_proc_show(struct seq_file *file, void *data)
|
||||
{
|
||||
char temp[40] = {0};
|
||||
|
||||
snprintf(temp, sizeof(temp), "%d\n", boot_into_factory);
|
||||
seq_printf(file, "%s\n", temp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int boot_reason_proc_open (struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, boot_reason_proc_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations boot_reason_proc_fops = {
|
||||
.open = boot_reason_proc_open,
|
||||
.read = seq_read,
|
||||
};
|
||||
|
||||
static int __init get_boot_rease(char *str)
|
||||
{
|
||||
if (strcmp("boot_with_factory", str) == 0) {
|
||||
boot_into_factory = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
__setup("androidboot.boot_reason=", get_boot_rease);
|
||||
|
||||
|
||||
static int sdc_detect_proc_show(struct seq_file *file, void *data)
|
||||
{
|
||||
int gpio_value = -1;
|
||||
|
||||
gpio_value = gpio_get_value(SDC_DETECT_GPIO);
|
||||
seq_printf(file, "%d\n", gpio_value ? 1 : 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdc_detect_proc_open (struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, sdc_detect_proc_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations sdc_detect_proc_fops = {
|
||||
.open = sdc_detect_proc_open,
|
||||
.read = seq_read,
|
||||
};
|
||||
|
||||
|
||||
static int sdc_detect_init(void)
|
||||
{
|
||||
sdc_detect_status = proc_create(SDC_DETECT_STATUS, 0644, NULL, &sdc_detect_proc_fops);
|
||||
if (sdc_detect_status == NULL) {
|
||||
pr_err("[%s]: create_proc_entry sdc_detect_status failed\n", __func__);
|
||||
return (int)-1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init hq_harware_init(void)
|
||||
{
|
||||
/* create sysfs entry at /sys/class/huaqin/interface/hw_info */
|
||||
create_sysfs();
|
||||
|
||||
boot_reason_proc = proc_create(PROC_BOOT_REASON_FILE, 0644, NULL, &boot_reason_proc_fops);
|
||||
|
||||
if (boot_reason_proc == NULL) {
|
||||
pr_err("[%s]: create_proc_entry boot_reason_proc failed\n", __func__);
|
||||
}
|
||||
if (sdc_detect_init() < 0)
|
||||
pr_err("[%s]: create_proc_entry sdc_detect_proc failed\n", __func__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
core_initcall(hq_harware_init);
|
||||
MODULE_AUTHOR("KaKa Ni <nigang@huaqin.com>");
|
||||
MODULE_DESCRIPTION("Huaqin Hardware Info Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
85
drivers/misc/simtray.c
Normal file
85
drivers/misc/simtray.c
Normal file
|
@ -0,0 +1,85 @@
|
|||
#include <linux/device.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
struct simtray_data {
|
||||
struct device *dev;
|
||||
int status_gpio;
|
||||
};
|
||||
|
||||
static ssize_t simtray_status_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct simtray_data *data = dev_get_drvdata(dev);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n", gpio_get_value(data->status_gpio));
|
||||
}
|
||||
static DEVICE_ATTR(status, 0444, simtray_status_show, NULL);
|
||||
|
||||
static int simtray_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct simtray_data *data;
|
||||
|
||||
pr_info("%s enter\n", __func__);
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(struct simtray_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->status_gpio = of_get_named_gpio(np, "status-gpio", 0);
|
||||
if (data->status_gpio < 0)
|
||||
return -EINVAL;
|
||||
|
||||
ret = sysfs_create_file(&dev->kobj, &dev_attr_status.attr);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to create sysfs node.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
data->dev = dev;
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int simtray_remove(struct platform_device *pdev)
|
||||
{
|
||||
sysfs_remove_file(&pdev->dev.kobj, &dev_attr_status.attr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id simtray_of_match[] = {
|
||||
{ .compatible = "xiaomi,simtray-status", },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver simtray_status_driver = {
|
||||
.driver = {
|
||||
.name = "simtray-status",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(simtray_of_match),
|
||||
},
|
||||
.probe = simtray_probe,
|
||||
.remove = simtray_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(simtray_status_driver);
|
||||
MODULE_AUTHOR("Tao Jun<taojun@xiaomi.com>");
|
||||
MODULE_DESCRIPTION("Xiaomi SIM tray status");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
6
drivers/misc/xiaomi_fs/Kconfig
Normal file
6
drivers/misc/xiaomi_fs/Kconfig
Normal file
|
@ -0,0 +1,6 @@
|
|||
config MI_FS
|
||||
bool "Enable Mi fs Driver"
|
||||
default n
|
||||
help
|
||||
MI fs information driver includes specific information about hardware info
|
||||
|
1
drivers/misc/xiaomi_fs/Makefile
Normal file
1
drivers/misc/xiaomi_fs/Makefile
Normal file
|
@ -0,0 +1 @@
|
|||
obj-$(CONFIG_MI_FS)+=mi_fs.o
|
141
drivers/misc/xiaomi_fs/mi_fs.c
Normal file
141
drivers/misc/xiaomi_fs/mi_fs.c
Normal file
|
@ -0,0 +1,141 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/seq_file.h>
|
||||
#define cpumaxfreq_proc_name "cpumaxfreq"
|
||||
static struct proc_dir_entry *cpumaxfreq_proc;
|
||||
#define CPU_PRESENT "/sys/devices/system/cpu/present"
|
||||
|
||||
int find_symbol_form_string(char *string, char symbol)
|
||||
{
|
||||
int i = 0;
|
||||
while (!(string[i] == symbol))
|
||||
++i;
|
||||
return i+1;
|
||||
}
|
||||
|
||||
int read_file(char *file_path, char *buf, int size)
|
||||
{
|
||||
|
||||
struct file *file_p = NULL;
|
||||
struct filename *filename;
|
||||
mm_segment_t old_fs;
|
||||
loff_t pos;
|
||||
int ret;
|
||||
|
||||
filename = getname_kernel(file_path);
|
||||
file_p = file_open_name(filename, O_RDONLY, 0);
|
||||
if (IS_ERR(file_p)) {
|
||||
pr_err ("%s fail to open file \n", __func__);
|
||||
return PTR_ERR(file_p); }
|
||||
else {
|
||||
old_fs = get_fs();
|
||||
set_fs(KERNEL_DS);
|
||||
pos = 0;
|
||||
ret = vfs_read(file_p, buf, size, &pos);
|
||||
filp_close(file_p, NULL);
|
||||
set_fs(old_fs);
|
||||
file_p = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
int get_core_count(void)
|
||||
{
|
||||
char buf[8] = {0};
|
||||
int symbol_position = 0;
|
||||
int core_count = 0;
|
||||
read_file(CPU_PRESENT, buf, sizeof(buf));
|
||||
symbol_position = find_symbol_form_string(buf, '-');
|
||||
|
||||
core_count = buf[symbol_position]-'0'+1;
|
||||
|
||||
return core_count;
|
||||
|
||||
|
||||
|
||||
}
|
||||
void read_cpumaxfreq(char *cpumaxfreq_buf)
|
||||
{
|
||||
uint16_t i = 0;
|
||||
char buf[16] = {0};
|
||||
char path[128] = {0};
|
||||
long cpumaxfreq = 0;
|
||||
int core_count = 0;
|
||||
core_count = get_core_count();
|
||||
//find max freq cpu
|
||||
while (i < core_count) {
|
||||
memset(buf, 0, sizeof(buf));
|
||||
snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", i);
|
||||
read_file(path, buf, sizeof(buf));
|
||||
if (simple_strtoul(buf, NULL, 0) > cpumaxfreq)
|
||||
cpumaxfreq = simple_strtoul(buf, NULL, 0);
|
||||
|
||||
++i;
|
||||
}
|
||||
//get max freq
|
||||
snprintf(cpumaxfreq_buf, 16, "%u.%u", (uint16_t)(cpumaxfreq/1000000),
|
||||
(uint16_t)((cpumaxfreq/100000)%10));
|
||||
}
|
||||
|
||||
static int cpumaxfreq_show(struct seq_file *file, void *data)
|
||||
{
|
||||
char cpumaxfreq_buf[16];
|
||||
#if 0
|
||||
char *cpumaxfreq_buf = NULL;
|
||||
cpumaxfreq_buf = kmalloc(sizeof(*cpumaxfreq_buf)*cpu_num*32, GFP_KERNEL);//32bytes for every cpu
|
||||
if (IS_ERR(cpumaxfreq_buf)) {
|
||||
pr_err("%s cpumaxfreq_buf kmalloc fail.\n", __func__);
|
||||
return PTR_ERR(cpumaxfreq_buf);
|
||||
}
|
||||
memset(cpumaxfreq_buf, 0, sizeof(*cpumaxfreq_buf)*cpu_num*32);
|
||||
#endif
|
||||
memset(cpumaxfreq_buf, 0, sizeof(cpumaxfreq_buf));
|
||||
read_cpumaxfreq(cpumaxfreq_buf);
|
||||
seq_printf(file, "%s\n", cpumaxfreq_buf);
|
||||
#if 0
|
||||
if (!cpumaxfreq_buf)
|
||||
kfree(cpumaxfreq_buf);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
static int cpumaxfreq_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, cpumaxfreq_show, inode->i_private);
|
||||
}
|
||||
static const struct file_operations cpumaxfreq_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = cpumaxfreq_open,
|
||||
.read = seq_read,
|
||||
};
|
||||
int create_fs(void)
|
||||
{
|
||||
/*proc/cpumaxfreq*/
|
||||
long rc = 1;
|
||||
cpumaxfreq_proc = proc_create(cpumaxfreq_proc_name, 0444, NULL, &cpumaxfreq_ops);
|
||||
if (IS_ERR(cpumaxfreq_proc)) {
|
||||
pr_err("%s cpumaxfreq proc create fail.\n", __func__);
|
||||
rc = PTR_ERR(cpumaxfreq_proc);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int __init mi_fs_init(void)
|
||||
{
|
||||
/* create fs*/
|
||||
create_fs();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
late_initcall(mi_fs_init); //after module_init
|
||||
MODULE_AUTHOR("ninjia <nijiayu@huaqin.com>");
|
||||
MODULE_DESCRIPTION("MI FS For Adaptation");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
Loading…
Add table
Reference in a new issue