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:
starlight5234 2021-04-02 12:53:01 +05:30 committed by Gagan Malvi
parent 2784ac0278
commit c78d6785f5
No known key found for this signature in database
GPG key ID: B932A7CE71E9198F
33 changed files with 5044 additions and 119 deletions

View file

@ -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

View file

@ -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/

View file

@ -0,0 +1,5 @@
config CDFINGER_FINGERPRINT
tristate "generic cdfinger fingerprint driver"
default y
help
add support for cdfinger fingerprint driver.

View file

@ -0,0 +1,4 @@
#
# Makefile for the fingerprint drivers.
#
obj-y += cdfingerfp.o

View 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
View 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");

View file

@ -0,0 +1,5 @@
config FOCALTECH_FINGERPRINT
tristate "generic focaltech fingerprint driver"
default y
help
add support for focaltech fingerprint driver.

View 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

View 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");

View 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__ */

View 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__ */

View 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__ */

View 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";
}

View file

@ -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

View file

@ -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

View file

@ -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.");

View file

@ -0,0 +1,5 @@
config GOODIX_FINGERPRINT
tristate "generic goodix fingerprint driver"
default y
help
add support for goodix fingerprint driver.

View file

@ -0,0 +1 @@
obj-$(CONFIG_GOODIX_FINGERPRINT) += gf_spi.o platform.o netlink.o

View 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");

View 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*/

View 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");
}

View 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);
}
}

View 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

View 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

View 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");

View 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

View 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");

View 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

View 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
View 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");

View 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

View file

@ -0,0 +1 @@
obj-$(CONFIG_MI_FS)+=mi_fs.o

View 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");