motorola: dlkm: synaptics touch driver

Moving Synaptics touchscreen driver from kernel to DLKM

Change-Id: Ia72f58cd400fb2c51b6170a8e0b43ceb16c72c94
Signed-off-by: Konstantin Makariev <kmakariev@motorola.com>
Reviewed-on: https://gerrit.mot.com/1102121
SLTApproved: Slta Waiver
SME-Granted: SME Approvals Granted
Tested-by: Jira Key
Submit-Approved: Jira Key
This commit is contained in:
Konstantin Makariev 2017-12-08 14:18:27 -06:00
commit bd24670ac0
13 changed files with 19910 additions and 0 deletions

View file

@ -0,0 +1,27 @@
DLKM_DIR := motorola/kernel/modules
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := synaptics_mmi.ko
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT)
include $(DLKM_DIR)/AndroidKernelModule.mk
include $(CLEAR_VARS)
LOCAL_MODULE := synaptics_dsx_fw_update.ko
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT)
include $(DLKM_DIR)/AndroidKernelModule.mk
include $(CLEAR_VARS)
LOCAL_MODULE := synaptics_dsx_rmi_dev.ko
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT)
include $(DLKM_DIR)/AndroidKernelModule.mk
#This requires F54 support, thus disable it for now
#include $(CLEAR_VARS)
#LOCAL_MODULE := synaptics_dsx_test_reporting.ko
#LOCAL_MODULE_TAGS := optional
#LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT)
#include $(DLKM_DIR)/AndroidKernelModule.mk

View file

@ -0,0 +1,15 @@
# add -Wall to try to catch everything we can.
EXTRA_CFLAGS += -Wall
EXTRA_CFLAGS += -I$(TOP)/motorola/kernel/modules/include \
-I$(TOP)/motorola/kernel/modules/drivers/input/touchscreen/synaptics_mmi
synaptics_mmi-objs = synaptics_dsx_i2c.o
ifneq ($(filter m y,$(CONFIG_DROPBOX)),)
synaptics_mmi-objs += synaptics_dsx_dropbox.o
endif
obj-m += synaptics_mmi.o
obj-m += synaptics_dsx_fw_update.o
obj-m += synaptics_dsx_rmi_dev.o
#obj-m += synaptics_dsx_test_reporting.o

View file

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

View file

@ -0,0 +1,443 @@
/*
* Synaptics DSX touchscreen driver extension
*
* Copyright (C) 2015 Motorola Mobility, Inc.
*
* Copyright (C) 2015 Konstantin Makariev <kmakariev@motorola.com>
*
* 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) "synaptics-CAB: %s: " fmt, __func__
#include <linux/kernel.h>
#include <linux/module.h>
#include "synaptics_dsx_i2c.h"
#include "synaptics_dsx_control_access_block.h"
#define cab_debug pr_debug
static struct touch_control_access_block *control_access_block;
struct touch_control_access_block *control_access_block_get(void) {
return control_access_block;
}
EXPORT_SYMBOL_GPL(control_access_block_get);
#define GROUP_NAME(grp) (grp == SYN_DSX_STATUS ? "STATUS" : \
(grp == SYN_DSX_DATA ? "DATA" : \
(grp == SYN_DSX_MODE ? "MODE" : \
(grp == SYN_DSX_WAKE ? "WAKE" : \
(grp == SYN_DSX_SLEEP ? "SLEEP" : \
(grp == SYN_DSX_RESET ? "RESET" : "CONFIG"))))))
#define DIR_NAME(grp) (grp & MODE_WO ? "WO" : "RO")
#define SPRINTF_PAGE(fmt, args...) {\
blen += scnprintf(buf + blen, PAGE_SIZE - blen, fmt, ##args);\
}
static ssize_t control_access_block_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int i, j, k, m;
ssize_t blen = 0;
struct touch_control_access_block *cab = control_access_block_get();
if (cab->do_sync)
SPRINTF_PAGE("has been changed, ");
SPRINTF_PAGE("has %d groups\n", cab->grp_num);
for (i = 0; i < cab->grp_num; i++) {
struct grp_info *ginfo = cab->grp_ptr[i];
/* skip empty slots */
if (!ginfo)
continue;
SPRINTF_PAGE("group [%s] access %s has %d data blocks\n",
GROUP_NAME(ginfo->group),
DIR_NAME(ginfo->group),
ginfo->data_blk_cnt);
for (m = 0; m < ginfo->data_blk_cnt; m++) {
SPRINTF_PAGE("\tdata block %d:\n", m);
if (ginfo->group & MODE_WO) {
struct op_write *wo = ginfo->wo_data[m];
SPRINTF_PAGE("\t\tpage:%d, addr:0x%X, blk_sz:%d, blk_cnt:%d, mask:0x%x\n",
wo->paddr >> 8, wo->paddr & 0xff,
wo->blk_sz, wo->blk_cnt, wo->mask);
SPRINTF_PAGE("\t\tdata: ");
for (j = 0; j < wo->blk_cnt; j++) {
unsigned char *data = &wo->data[j];
SPRINTF_PAGE("[%d] ", j);
for (k = 0; k < wo->blk_sz; k++)
SPRINTF_PAGE("0x%X ", *(data + k));
SPRINTF_PAGE("\n");
}
} else {
struct op_read *ro = ginfo->ro_data[m];
SPRINTF_PAGE("\t\tpage:%d, addr:0x%X, blk_sz:%d, blk_cnt:%d\n",
ro->paddr >> 8, ro->paddr & 0xff,
ro->blk_sz, ro->blk_cnt);
}
}
}
cab->do_sync = false;
cab_debug("clear do_sync flag\n");
return blen;
}
static struct op_write *alloc_op_write(unsigned short paddr,
unsigned char blk_sz, unsigned char blk_cnt,
unsigned char mask, unsigned char *data)
{
int i;
struct op_write *wo;
wo = kzalloc(sizeof(struct op_write) + blk_sz, GFP_KERNEL);
if (!wo)
return NULL;
wo->paddr = paddr;
wo->blk_sz = blk_sz;
wo->blk_cnt = blk_cnt;
wo->mask = mask;
for (i = 0; i < blk_sz; i++)
wo->data[i] = *data++;
cab_debug("added WO block: addr={%X}, sz=%u, cnt=%u\n", paddr, blk_sz, blk_cnt);
return wo;
}
static struct op_read *alloc_op_read(unsigned short paddr,
unsigned char blk_sz, unsigned char blk_cnt)
{
struct op_read *ro;
ro = kzalloc(sizeof(struct op_read), GFP_KERNEL);
if (!ro)
return NULL;
ro->paddr = paddr;
ro->blk_sz = blk_sz;
ro->blk_cnt = blk_cnt;
cab_debug("added RO block: addr={%X}, sz=%u, cnt=%u\n", paddr, blk_sz, blk_cnt);
return ro;
}
/* create a new group and add one or more entities */
static struct grp_info *alloc_grp_info(unsigned char group,
unsigned char data_blk_cnt, ...)
{
int i;
va_list ap;
struct grp_info *gi;
gi = kzalloc(sizeof(struct grp_info), GFP_KERNEL);
if (!gi)
return NULL;
gi->group = group;
gi->data_blk_cnt = data_blk_cnt;
va_start(ap, data_blk_cnt);
if (group & MODE_WO) {
struct op_write *data;
gi->wo_data = (struct op_write **)kzalloc(
sizeof(struct op_write *)*data_blk_cnt,
GFP_KERNEL);
if (!gi->wo_data)
goto alloc_failure;
for (i = 0; i < data_blk_cnt; i++) {
data = va_arg(ap, struct op_write *);
gi->wo_data[i] = data;
}
} else {
struct op_read *data;
gi->ro_data = (struct op_read **)kzalloc(
sizeof(struct op_read *)*data_blk_cnt,
GFP_KERNEL);
if (!gi->ro_data)
goto alloc_failure;
for (i = 0; i < data_blk_cnt; i++) {
data = va_arg(ap, struct op_read *);
gi->ro_data[i] = data;
}
}
goto success;
alloc_failure:
gi->data_blk_cnt = 0;
success:
cab_debug("created group [%s] access %s has %d data block(s)\n",
GROUP_NAME(gi->group), DIR_NAME(gi->group), gi->data_blk_cnt);
va_end(ap);
return gi;
}
/* add one or more entities to existing group */
static int modify_grp_info(struct grp_info *gi,
unsigned char data_blk_cnt, ...)
{
int i, retval = 0;
int new_max = gi->data_blk_cnt + data_blk_cnt;
va_list ap;
va_start(ap, data_blk_cnt);
if (gi->group & MODE_WO) {
struct op_write **realloc, *append;
realloc = (struct op_write **)kzalloc(sizeof(struct op_write *)*
(gi->data_blk_cnt + data_blk_cnt),
GFP_KERNEL);
if (!realloc) {
retval = -ENOMEM;
goto alloc_failure;
}
for (i = 0; i < new_max; i++) {
if (i < gi->data_blk_cnt) {
realloc[i] = gi->wo_data[i];
}else {
append = va_arg(ap, struct op_write *);
realloc[i] = append;
}
}
kfree(gi->wo_data);
gi->wo_data = realloc;
} else {
struct op_read **realloc, *append;
realloc = (struct op_read **)kzalloc(sizeof(struct op_read *)*
(gi->data_blk_cnt + data_blk_cnt),
GFP_KERNEL);
if (!realloc) {
retval = -ENOMEM;
goto alloc_failure;
}
for (i = 0; i < new_max; i++) {
if (i < gi->data_blk_cnt) {
realloc[i] = gi->ro_data[i];
}else {
append = va_arg(ap, struct op_read *);
realloc[i] = append;
}
}
kfree(gi->ro_data);
gi->ro_data = realloc;
}
gi->data_blk_cnt += data_blk_cnt;
cab_debug("modified group:[%s] access:%s has %d data blocks\n",
GROUP_NAME(gi->group), DIR_NAME(gi->group), gi->data_blk_cnt);
alloc_failure:
va_end(ap);
return retval;
}
void control_access_block_zap(unsigned char gid)
{
struct touch_control_access_block *cab = control_access_block_get();
struct grp_info **grp_ptr = NULL;
int i, idx = 0;
for (i = 0; i < cab->grp_num; i++) {
if (cab->grp_ptr[i] && cab->grp_ptr[i]->group == gid) {
idx = i;
grp_ptr = &cab->grp_ptr[idx];
break;
}
}
if (!grp_ptr)
return;
/* clean up data and make slot available */
if ((*grp_ptr)->group & MODE_WO) {
struct op_write *data;
for (i = 0; i < (*grp_ptr)->data_blk_cnt; i++) {
data = (*grp_ptr)->wo_data[i];
kfree(data);
}
kfree((*grp_ptr)->wo_data);
} else {
struct op_read *data;
for (i = 0; i < (*grp_ptr)->data_blk_cnt; i++) {
data = (*grp_ptr)->ro_data[i];
kfree(data);
}
kfree((*grp_ptr)->ro_data);
}
kfree(*grp_ptr);
*grp_ptr = NULL;
cab_debug("group [%s] idx %d removed %d record(s)\n", GROUP_NAME(gid), idx, i);
}
static struct grp_info **find_group(unsigned char gid)
{
struct touch_control_access_block *cab = control_access_block_get();
struct grp_info **grp_ptr = NULL;
int i;
for (i = 0; i < cab->grp_num; i++) {
if (cab->grp_ptr[i] && cab->grp_ptr[i]->group &&
cab->grp_ptr[i]->group != gid)
continue;
/* match or empty slot */
grp_ptr = &cab->grp_ptr[i];
break;
}
cab_debug("idx %d %s\n", i, *grp_ptr ? "existing" : "new");
return grp_ptr;
}
static int add_tail(struct grp_info **grp, unsigned char gid, void *io_data)
{
int retval = 0;
if (!*grp) {
struct grp_info *new;
new = alloc_grp_info(gid, 1, (void *)io_data);
if (!new) {
retval = -ENOMEM;
goto alloc_failure;
}
*grp = new;
cab_debug("created new %s group\n", DIR_NAME((*grp)->group));
} else {
modify_grp_info(*grp, 1, (void *)io_data);
cab_debug("added %s block\n", DIR_NAME((*grp)->group));
}
alloc_failure:
return retval;
}
int control_access_block_update_ro(
unsigned char gid,
unsigned short paddr,
unsigned char sz,
unsigned char cnt)
{
struct op_read *ro = alloc_op_read(paddr, sz, cnt);
struct grp_info **grp = find_group(gid);
return !ro ? -1 : add_tail(grp, gid, (void *)ro);
}
int control_access_block_update_wo(
unsigned char gid,
unsigned short paddr,
unsigned char sz,
unsigned char mask,
unsigned char *data)
{
struct op_write *wo = alloc_op_write(paddr, sz, 1, mask, data);
struct grp_info **grp = find_group(gid);
return !wo ? -1 : add_tail(grp, gid, (void *)wo);
}
static struct device_attribute attrs[] = {
__ATTR(control_access_block, S_IRUSR | S_IRGRP,
control_access_block_show, NULL),
};
static int synaptics_control_access_block_init(
struct synaptics_rmi4_data *rmi4_data)
{
int retval, attr_count;
control_access_block = kzalloc(sizeof(*control_access_block), GFP_KERNEL);
if (!control_access_block) {
dev_err(&rmi4_data->i2c_client->dev,
"%s: Failed to alloc controll access block\n",
__func__);
goto exit;
}
control_access_block->grp_num = GROUP_MAX;
for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) {
retval = sysfs_create_file(&rmi4_data->i2c_client->dev.kobj,
&attrs[attr_count].attr);
if (retval < 0) {
dev_err(&rmi4_data->i2c_client->dev,
"%s: Failed to create sysfs attributes\n",
__func__);
retval = -ENODEV;
goto exit_remove_attrs;
}
}
return 0;
exit_remove_attrs:
for (attr_count--; attr_count >= 0; attr_count--) {
sysfs_remove_file(&rmi4_data->i2c_client->dev.kobj,
&attrs[attr_count].attr);
}
exit:
return 0;
}
static struct completion remove_complete;
static void synaptics_control_access_block_remove(
struct synaptics_rmi4_data *rmi4_data)
{
int i, m, attr_count;
for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) {
sysfs_remove_file(&rmi4_data->i2c_client->dev.kobj,
&attrs[attr_count].attr);
}
for (i = 0; i < control_access_block->grp_num; i++) {
struct grp_info *ginfo = control_access_block->grp_ptr[i];
void **ptr_to_free;
if (ginfo->group & MODE_WO)
ptr_to_free = (void **)ginfo->wo_data;
else
ptr_to_free = (void **)ginfo->ro_data;
for (m = 0; m < ginfo->data_blk_cnt; m++) {
if (ginfo->group & MODE_WO)
kfree(ginfo->wo_data[m]);
else
kfree(ginfo->ro_data[m]);
}
kfree(ptr_to_free);
kfree(ginfo);
}
kfree(control_access_block->grp_ptr);
kfree(control_access_block);
control_access_block = NULL;
complete(&remove_complete);
return;
}
static int __init synaptics_control_access_block_module_init(void)
{
synaptics_rmi4_new_function(RMI_CTRL_ACCESS_BLK, true,
synaptics_control_access_block_init,
synaptics_control_access_block_remove,
NULL, NULL, IC_MODE_ANY);
return 0;
}
static void __exit synaptics_control_access_block_module_exit(void)
{
init_completion(&remove_complete);
synaptics_rmi4_new_function(RMI_CTRL_ACCESS_BLK, false,
synaptics_control_access_block_init,
synaptics_control_access_block_remove,
NULL, NULL, IC_MODE_ANY);
wait_for_completion(&remove_complete);
return;
}
module_init(synaptics_control_access_block_module_init);
module_exit(synaptics_control_access_block_module_exit);
MODULE_AUTHOR("Motorola Mobility, Inc.");
MODULE_DESCRIPTION("Synaptics DSX Touch Access Block Module");
MODULE_LICENSE("GPL v2");
MODULE_VERSION(SYNAPTICS_DSX_DRIVER_VERSION);

View file

@ -0,0 +1,111 @@
/*
* Synaptics DSX touchscreen driver extension
*
* Copyright (C) 2015 Motorola Mobility, Inc.
*
* Copyright (C) 2015 Konstantin Makariev <kmakariev@motorola.com>
*
* 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 _SYNAPTICS_DSX_CONTROL_ACCESS_BLOCK_H_
#define _SYNAPTICS_DSX_CONTROL_ACCESS_BLOCK_H_
#define MODE_WO (1 << 7)
#define GROUP_MAX 7
enum access_groups {
SYN_DSX_STATUS = 1, /* RO:mandatory, read status to clear IRQ */
SYN_DSX_MODE, /* RO:mandatory, read touch IC mode */
SYN_DSX_DATA, /* RO:mandatory, read touch data */
SYN_DSX_WAKE = SYN_DSX_DATA + 1 + MODE_WO,
/* WO:mandatory, command(s) to wake up touch IC */
SYN_DSX_SLEEP, /* WO:mandatory, command(s) to put touch IC to sleep */
SYN_DSX_RESET, /* WO:optional, command to reset touch IC */
SYN_DSX_CONFIG, /* WO:optional, command(s) to apply custom config */
SYN_DSX_VAL_MAX = 0xff
};
struct op_read {
struct {
unsigned short paddr; /* (page << 8) + address */
unsigned char blk_sz; /* read block size */
unsigned char blk_cnt; /* read block count */
};
};
struct op_write {
struct {
unsigned short paddr; /* (page << 8) + address */
unsigned char blk_sz; /* write block size */
unsigned char blk_cnt; /* write block count */
};
unsigned char mask; /* mask to clear bit(s) before apply data */
/* only effective when blk_cnt and blk_sz */
/* both are 1s, otherwise mask is ignored */
unsigned char data[0]; /* blk_cnt of data chunks of blk_sz each */
};
struct grp_info {
unsigned char group; /* access group; defines type of data */
unsigned char data_blk_cnt; /* number of data blocks in the group */
union {
struct op_read **ro_data;
struct op_write **wo_data;
};
};
struct touch_control_access_block {
unsigned char do_sync; /* flag to indicate data block was updated */
unsigned char grp_num; /* number of groups in data block */
struct grp_info *grp_ptr[GROUP_MAX];
};
#if defined(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_CTRL_ACCESS_BLK)
struct touch_control_access_block *control_access_block_get(void);
int control_access_block_update_ro(unsigned char gid, unsigned short paddr,
unsigned char sz, unsigned char cnt);
int control_access_block_update_wo(unsigned char gid, unsigned short paddr,
unsigned char sz, unsigned char mask, unsigned char *data);
void control_access_block_zap(unsigned char gid);
#else
static inline struct touch_control_access_block *control_access_block_get(void) {
return NULL;
}
static int inline control_access_block_update_ro(
unsigned char gid, unsigned short paddr,
unsigned char sz, unsigned char cnt) {
return -1;
}
static int inline control_access_block_update_wo(
unsigned char gid, unsigned short paddr,
unsigned char sz, unsigned char mask, unsigned char *data) {
return -1;
}
static void inline control_access_block_zap(unsigned char gid) {
}
#endif
#define control_access_block_update_status(paddr, size) \
control_access_block_update_ro(SYN_DSX_STATUS, paddr, size, 1)
#define control_access_block_update_mode(paddr, size) \
control_access_block_update_ro(SYN_DSX_MODE, paddr, size, 1)
#define control_access_block_update_data(paddr, size) \
control_access_block_update_ro(SYN_DSX_DATA, paddr, size, 1)
#define control_access_block_update_data_mt(paddr, size, mt) \
control_access_block_update_ro(SYN_DSX_DATA, paddr, size, mt)
#define control_access_block_update_reset(paddr, size, data) \
control_access_block_update_wo(SYN_DSX_RESET, paddr, size, 0, data)
#define control_access_block_update_wake(paddr, size, data) \
control_access_block_update_wo(SYN_DSX_WAKE, paddr, size, 0x7, data)
#define control_access_block_update_sleep(paddr, size, data) \
control_access_block_update_wo(SYN_DSX_SLEEP, paddr, size, 0x7, data)
#endif

View file

@ -0,0 +1,40 @@
/* Copyright (c) 2014 - 2015, Motorola Mobility LLC. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only 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/dropbox.h>
#include <linux/string.h>
#include "synaptics_dsx_dropbox.h"
#define DROPBOX_TOUCH_ISSUE "touch_issue"
static DEFINE_RATELIMIT_STATE(synaptics_dropbox_global_rl, 3600 * HZ, 1);
void synaptics_dropbox_report_event(char *msg, int count)
{
char dropbox_entry[256];
snprintf(dropbox_entry, sizeof(dropbox_entry), "%s\ncount: %d",
msg, count);
pr_err("%s: touch issue detected[count: %d][%s], reporting dropbox\n",
__func__, count, msg);
dropbox_queue_event_text(DROPBOX_TOUCH_ISSUE,
dropbox_entry, strlen(dropbox_entry));
}
void synaptics_dropbox_report_event_ratelimit(char *msg, int count)
{
struct ratelimit_state *rs = &synaptics_dropbox_global_rl;
if (!__ratelimit(rs))
return;
synaptics_dropbox_report_event(msg, count);
}

View file

@ -0,0 +1,27 @@
/* Copyright (c) 2014 - 2015, Motorola Mobility LLC. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only 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 SYNAPTICS_DROPBOX_H
#define SYNAPTICS_DROPBOX_H
#include <linux/ratelimit.h>
#define SYNAPTICS_DROPBOX_MSG_I2C "Touch I2C communication error detected"
#define SYNAPTICS_DROPBOX_MSG_RESET_TIMEOUT "Touch reset time out detected"
#if defined(CONFIG_DROPBOX)
void synaptics_dropbox_report_event(char *msg, int count);
void synaptics_dropbox_report_event_ratelimit(char *msg, int count);
#else
static inline void synaptics_dropbox_report_event(char *msg, int count) {}
static inline void synaptics_dropbox_report_event_ratelimit(char *msg, int count) {}
#endif
#endif /* SYNAPTICS_DROPBOX_H */

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,692 @@
/*
* Synaptics DSX touchscreen driver
*
* Copyright (C) 2012 Synaptics Incorporated
*
* Copyright (C) 2012 Alexandra Chin <alexandra.chin@tw.synaptics.com>
* Copyright (C) 2012 Scott Lin <scott.lin@tw.synaptics.com>
*
* 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 _SYNAPTICS_DSX_RMI4_H_
#define _SYNAPTICS_DSX_RMI4_H_
#define SYNAPTICS_DSX_DRIVER_VERSION "DSX 1.1"
/* define to enable USB charger detection */
#undef USB_CHARGER_DETECTION
#include <linux/version.h>
#include <linux/ktime.h>
#include <linux/semaphore.h>
#include <linux/pm_qos.h>
#if defined(USB_CHARGER_DETECTION)
#include <linux/usb.h>
#include <linux/power_supply.h>
#endif
#if defined(CONFIG_MMI_PANEL_NOTIFICATIONS)
#include <linux/mmi_panel_notifier.h>
#elif defined(CONFIG_FB)
#include <linux/notifier.h>
#include <linux/fb.h>
#endif
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 38))
#define KERNEL_ABOVE_2_6_38
#endif
#ifdef KERNEL_ABOVE_2_6_38
#define sstrtoul(...) kstrtoul(__VA_ARGS__)
#else
#define sstrtoul(...) strict_strtoul(__VA_ARGS__)
#endif
#if (LINUX_VERSION_CODE > KERNEL_VERSION(3, 7, 0))
#define KERNEL_ABOVE_3_7
#endif
#define PDT_PROPS (0x00EF)
#define PDT_START (0x00E9)
#define PDT_END (0x000A)
#define PDT_ENTRY_SIZE (0x0006)
#define PAGES_TO_SERVICE (10)
#define PAGE_SELECT_LEN (2)
#define SYNAPTICS_RMI4_F01 (0x01)
#define SYNAPTICS_RMI4_F11 (0x11)
#define SYNAPTICS_RMI4_F12 (0x12)
#define SYNAPTICS_RMI4_F1A (0x1a)
#define SYNAPTICS_RMI4_F34 (0x34)
#define SYNAPTICS_RMI4_F35 (0x35)
#define SYNAPTICS_RMI4_F38 (0x38)
#define SYNAPTICS_RMI4_F51 (0x51)
#define SYNAPTICS_RMI4_F54 (0x54)
#define SYNAPTICS_RMI4_F55 (0x55)
#define SYNAPTICS_RMI4_FDB (0xdb)
#define SYNAPTICS_RMI4_PRODUCT_INFO_SIZE 2
#define SYNAPTICS_RMI4_PRODUCT_ID_SIZE 10
#define SYNAPTICS_RMI4_BUILD_ID_SIZE 3
#define SYNAPTICS_RMI4_SERIAL_SIZE 7
#define SYNAPTICS_RMI4_CONFIG_ID_SIZE 4
#define SYNAPTICS_RMI4_PACKAGE_ID_SIZE 4
#define SYNAPTICS_RMI4_FILENAME_SIZE 80
#define PACKAGE_ID_OFFSET 17
#define FW_VERSION_OFFSET 18
#define F34_PROPERTIES_OFFSET 1
#define F34_PROPERTIES_OFFSET_V2 0
#define MAX_NUMBER_OF_FINGERS 10
#define MAX_NUMBER_OF_BUTTONS 4
#define MAX_INTR_REGISTERS 4
#define MASK_16BIT 0xFFFF
#define MASK_8BIT 0xFF
#define MASK_7BIT 0x7F
#define MASK_6BIT 0x3F
#define MASK_5BIT 0x1F
#define MASK_4BIT 0x0F
#define MASK_3BIT 0x07
#define MASK_2BIT 0x03
#define MASK_1BIT 0x01
#define PRODUCT_INFO_SIZE 2
#define PRODUCT_ID_SIZE 10
/*
* struct synaptics_rmi4_fn_desc - function descriptor fields in PDT
* @query_base_addr: base address for query registers
* @cmd_base_addr: base address for command registers
* @ctrl_base_addr: base address for control registers
* @data_base_addr: base address for data registers
* @intr_src_count: number of interrupt sources
* @fn_number: function number
*/
struct synaptics_rmi4_fn_desc {
unsigned char query_base_addr;
unsigned char cmd_base_addr;
unsigned char ctrl_base_addr;
unsigned char data_base_addr;
unsigned char intr_src_count:3;
unsigned char reserved_1:2;
unsigned char fn_version:2;
unsigned char reserved_2:1;
unsigned char fn_number;
};
/*
* synaptics_rmi4_fn_full_addr - full 16-bit base addresses
* @query_base: 16-bit base address for query registers
* @cmd_base: 16-bit base address for data registers
* @ctrl_base: 16-bit base address for command registers
* @data_base: 16-bit base address for control registers
*/
struct synaptics_rmi4_fn_full_addr {
unsigned short query_base;
unsigned short cmd_base;
unsigned short ctrl_base;
unsigned short data_base;
};
/*
* struct synaptics_rmi4_fn - function handler data structure
* @fn_number: function number
* @num_of_data_sources: number of data sources
* @num_of_data_points: maximum number of fingers supported
* @size_of_data_register_block: data register block size
* @data1_offset: offset to data1 register from data base address
* @intr_reg_num: index to associated interrupt register
* @intr_mask: interrupt mask
* @full_addr: full 16-bit base addresses of function registers
* @link: linked list for function handlers
* @data_size: size of private data
* @data: pointer to private data
*/
struct synaptics_rmi4_fn {
unsigned char fn_number;
unsigned char num_of_data_sources;
unsigned char num_of_data_points;
unsigned char size_of_data_register_block;
unsigned char data1_offset;
unsigned char intr_reg_num;
unsigned char intr_mask;
struct synaptics_rmi4_fn_full_addr full_addr;
struct list_head link;
int data_size;
void *data;
};
/*
* struct synaptics_rmi4_device_info - device information
* @version_major: rmi protocol major version number
* @version_minor: rmi protocol minor version number
* @manufacturer_id: manufacturer id
* @product_props: product properties information
* @product_info: product info array
* @date_code: device manufacture date
* @tester_id: tester id array
* @serial_number: device serial number
* @product_id_string: device product id
* @support_fn_list: linked list for function handlers
*/
struct synaptics_rmi4_device_info {
unsigned int version_major;
unsigned int version_minor;
unsigned char manufacturer_id;
unsigned char product_props;
unsigned char product_info[SYNAPTICS_RMI4_PRODUCT_INFO_SIZE];
unsigned char serial[SYNAPTICS_RMI4_SERIAL_SIZE];
unsigned char package_id[SYNAPTICS_RMI4_PACKAGE_ID_SIZE];
unsigned char product_id_string[SYNAPTICS_RMI4_PRODUCT_ID_SIZE + 1];
unsigned char build_id[SYNAPTICS_RMI4_BUILD_ID_SIZE];
unsigned char config_id[SYNAPTICS_RMI4_CONFIG_ID_SIZE];
struct list_head support_fn_list;
};
struct touch_up_down {
int mismatch;
unsigned char up_down;
unsigned int counter;
};
struct touch_area_stats {
struct touch_up_down *ud;
ssize_t ud_len;
ssize_t ud_id;
ssize_t unknown_counter;
const char *name;
};
struct synaptics_dsx_func_patch {
unsigned short func;
unsigned char regstr;
unsigned char subpkt;
unsigned char size;
unsigned char bitmask;
unsigned char *data;
struct list_head link;
};
struct synaptics_clip_area {
unsigned xul_clip, yul_clip, xbr_clip, ybr_clip;
unsigned inversion; /* clip inside (when 1) or ouside otherwise */
};
enum {
SYNA_MOD_AOD,
SYNA_MOD_STATS,
SYNA_MOD_FOLIO,
SYNA_MOD_CHARGER,
SYNA_MOD_WAKEUP,
SYNA_MOD_FPS,
SYNA_MOD_QUERY, /* run time query; active only */
SYNA_MOD_RT, /* run time patch; active only; always effective */
SYNA_MOD_MAX
};
#define FLAG_FORCE_UPDATE 1
#define FLAG_WAKEABLE 2
#define FLAG_POWER_SLEEP 4
struct synaptics_dsx_patch {
const char *name;
int cfg_num;
unsigned int flags;
struct semaphore list_sema;
struct list_head cfg_head;
};
struct config_modifier {
const char *name;
int id;
bool effective;
struct synaptics_dsx_patch *active;
struct synaptics_dsx_patch *suspended;
struct synaptics_clip_area *clipa;
struct list_head link;
};
struct synaptics_dsx_modifiers {
int mods_num;
struct semaphore list_sema;
struct list_head mod_head;
};
struct f34_properties {
union {
struct {
unsigned char regmap:1;
unsigned char unlocked:1;
unsigned char has_config_id:1;
unsigned char has_perm_config:1;
unsigned char has_bl_config:1;
unsigned char has_display_config:1;
unsigned char has_blob_config:1;
unsigned char reserved:1;
} __packed;
unsigned char data[1];
};
};
struct f34_properties_v2 {
union {
struct {
unsigned char query0_subpkt1_size:3;
unsigned char has_config_id:1;
unsigned char reserved:1;
unsigned char has_thqa:1;
unsigned char reserved2:2;
} __packed;
unsigned char data[1];
};
};
struct f54_control_95n {
union {
struct {
/* byte 0 - flags*/
unsigned char c95_filter_bw:3;
unsigned char c95_byte0_b3_b6:4;
unsigned char c95_disable:1;
/* bytes 1 - 10 */
unsigned char c95_first_burst_length_lsb;
unsigned char c95_first_burst_length_msb;
unsigned char c95_addl_burst_length_lsb;
unsigned char c95_addl_burst_length_msb;
unsigned char c95_i_stretch;
unsigned char c95_r_stretch;
unsigned char c95_noise_control1;
unsigned char c95_noise_control2;
unsigned char c95_noise_control3;
unsigned char c95_noise_control4;
} __packed;
struct {
unsigned char data[11];
} __packed;
};
};
enum {
STATE_UNKNOWN,
STATE_ACTIVE,
STATE_SUSPEND,
STATE_STANDBY = 4,
STATE_BL,
STATE_INIT,
STATE_FLASH,
STATE_QUERY,
STATE_INVALID
};
#define STATE_UI (STATE_ACTIVE | STATE_SUSPEND)
enum ic_modes {
IC_MODE_ANY = 0,
IC_MODE_BL,
IC_MODE_UI
};
enum exp_fn {
RMI_DEV = 0,
RMI_F34,
RMI_F54,
RMI_FW_UPDATER,
RMI_CTRL_ACCESS_BLK,
RMI_LAST,
};
static inline void batohs(unsigned short *dest, unsigned char *src)
{
*dest = src[1] * 0x100 + src[0];
}
static inline void hstoba(unsigned char *dest, unsigned short src)
{
dest[0] = src % 0x100;
dest[1] = src / 0x100;
}
static inline void batohui(unsigned int *dest, unsigned char *src, size_t size)
{
int si = size;
*dest = 0;
for (; --si >= 0;)
*dest += src[si] << (si << 3);
}
struct synaptics_rmi4_subpkt {
bool present;
bool expected;
short offset;
unsigned int size;
void *data;
};
#define RMI4_SUBPKT(a)\
{.present = 0, .expected = 1, .size = sizeof(a), .data = &a}
#define RMI4_SUBPKT_STATIC(o, a)\
{.present = 1, .expected = 1,\
.offset = o, .size = sizeof(a), .data = &a}
struct synaptics_rmi4_packet_reg {
unsigned short r_number;
bool updated; /* indicate that value in *data has been updated */
bool modified; /* indicate that value in *data has been modified */
bool expected;
short offset;
unsigned int size;
unsigned char *data;
unsigned char nr_subpkts;
struct synaptics_rmi4_subpkt *subpkt;
};
#define RMI4_NO_REG(r) {\
.r_number = r, .offset = -1, .updated = 0, .modified = 0,\
.expected = 0, .size = 0, .data = NULL,\
.nr_subpkts = 0, .subpkt = NULL}
#define RMI4_REG(r, s) {\
.r_number = r, .offset = -1, .updated = 0, .modified = 0,\
.expected = 1, .size = 0, .data = NULL,\
.nr_subpkts = ARRAY_SIZE(s), .subpkt = s}
#define RMI4_REG_STATIC(r, s, sz) {\
.r_number = r, .offset = r, .updated = 0, .modified = 0,\
.expected = 1, .size = sz, .data = NULL,\
.nr_subpkts = ARRAY_SIZE(s), .subpkt = s}
struct synaptics_rmi4_func_packet_regs {
unsigned short f_number;
unsigned short base_addr;
unsigned short query_offset;
int nr_regs;
struct synaptics_rmi4_packet_reg *regs;
};
struct reporting_ctrl {
bool reporting_stopped;
unsigned int events_cnt;
int expected, max_seen;
struct semaphore ctrl_sema;
};
#define MAX_READ_WRITE_SIZE 8096
#define MIN_READ_WRITE_BUF_SIZE 256
struct temp_buffer {
unsigned char *buf;
unsigned short buf_size;
};
/*
* struct synaptics_rmi4_data - rmi4 device instance data
* @i2c_client: pointer to associated i2c client
* @input_dev: pointer to associated input device
* @board: constant pointer to platform data
* @rmi4_mod_info: device information
* @regulator: pointer to associated regulator
* @vdd_quirk: pointer to associated regulator for 'quirk' config
* @rmi4_io_ctrl_mutex: mutex for i2c i/o control
* @det_work: work thread instance for expansion function detection
* @det_workqueue: pointer to work queue for work thread instance
* @early_suspend: instance to support early suspend power management
* @current_page: current page in sensor to acess
* @button_0d_enabled: flag for 0d button support
* @full_pm_cycle: flag for full power management cycle in early suspend stage
* @num_of_intr_regs: number of interrupt registers
* @f01_query_base_addr: query base address for f01
* @f01_cmd_base_addr: command base address for f01
* @f01_ctrl_base_addr: control base address for f01
* @f01_data_base_addr: data base address for f01
* @irq: attention interrupt
* @sensor_max_x: sensor maximum x value
* @sensor_max_y: sensor maximum y value
* @irq_enabled: flag for indicating interrupt enable status
* @touch_stopped: touch is in suspend state
* @flash_enabled: allow flashing once transition to active state is complete
* @ic_on: touch ic power state
* @fingers_on_2d: flag to indicate presence of fingers in 2d area
* @wait: wait queue for touch data polling in interrupt thread
* @number_irq: total number of remembered interrupt times
* @last_irq: last interrup time's number (index of the location of interrupt)
* @irq_info: information about last few interrupt times
* @i2c_read: pointer to i2c read function
* @i2c_write: pointer to i2c write function
* @irq_enable: pointer to irq enable function
*/
struct synaptics_rmi4_data {
struct i2c_client *i2c_client;
struct input_dev *input_dev;
const struct synaptics_dsx_platform_data *board;
struct synaptics_rmi4_device_info rmi4_mod_info;
struct regulator *regulator;
struct regulator *vdd_quirk;
struct mutex rmi4_io_ctrl_mutex;
struct mutex state_mutex;
#if defined(CONFIG_MMI_PANEL_NOTIFICATIONS)
struct mmi_notifier panel_nb;
#elif defined(CONFIG_FB)
struct notifier_block panel_nb;
#endif
atomic_t panel_off_flag;
unsigned char current_page;
unsigned char button_0d_enabled;
unsigned char num_of_rx;
unsigned char num_of_tx;
unsigned char num_of_fingers;
unsigned char aod_mt;
unsigned char f01_ctrl_register_0;
unsigned char intr_mask[MAX_INTR_REGISTERS];
unsigned short num_of_intr_regs;
unsigned short f01_query_base_addr;
unsigned short f01_cmd_base_addr;
unsigned short f01_ctrl_base_addr;
unsigned short f01_data_base_addr;
unsigned int active_fn_intr_mask;
int state;
int irq;
int sensor_max_x;
int sensor_max_y;
bool irq_enabled;
atomic_t touch_stopped;
bool flash_enabled;
bool ic_on;
bool fingers_on_2d;
bool input_registered;
bool in_bootloader;
wait_queue_head_t wait;
int (*i2c_read)(struct synaptics_rmi4_data *pdata, unsigned short addr,
unsigned char *data, unsigned short length);
int (*i2c_write)(struct synaptics_rmi4_data *pdata, unsigned short addr,
unsigned char *data, unsigned short length);
void (*set_state)(struct synaptics_rmi4_data *rmi4_data, int state);
int (*ready_state)(struct synaptics_rmi4_data *rmi4_data, bool standby);
int (*irq_enable)(struct synaptics_rmi4_data *rmi4_data, bool enable);
int (*reset_device)(struct synaptics_rmi4_data *rmi4_data);
int (*get_status)(struct synaptics_rmi4_data *rmi4_data);
int number_irq;
int last_irq;
struct synaptics_rmi4_irq_info *irq_info;
/* features enablers */
bool charger_detection_enabled;
bool folio_detection_enabled;
bool fps_detection_enabled;
bool wakeup_detection_enabled;
bool patching_enabled;
bool purge_enabled;
bool suspend_is_wakeable;
bool clipping_on;
struct synaptics_clip_area *clipa;
struct synaptics_dsx_modifiers modifiers;
struct work_struct resume_work;
struct synaptics_rmi4_func_packet_regs *f12_data_registers_ptr;
struct notifier_block rmi_reboot;
#if defined(USB_CHARGER_DETECTION)
struct power_supply psy;
#endif
#ifdef CONFIG_MMI_HALL_NOTIFICATIONS
struct notifier_block folio_notif;
#endif
bool is_fps_registered; /* FPS notif registration might be delayed */
struct notifier_block fps_notif;
struct mutex rmi4_exp_init_mutex;
uint32_t pm_qos_latency;
struct pm_qos_request pm_qos_irq;
struct reporting_ctrl rctrl;
unsigned char tsb_buff_clean_flag;
bool touch_data_contiguous;
uint8_t *touch_data;
uint16_t touch_data_size;
struct temp_buffer write_buf;
#if defined(CONFIG_DYNAMIC_DEBUG) || defined(DEBUG)
/* TEST OPTIONS */
int test_irq_delay_ms;
int test_irq_data_contig;
#endif
};
static inline ssize_t synaptics_rmi4_show_error(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev);
dev_warn(&rmi4_data->i2c_client->dev,
"%s Attempted to read from write-only attribute %s\n",
__func__, attr->attr.name);
return -EPERM;
}
static inline ssize_t synaptics_rmi4_store_error(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev);
dev_warn(&rmi4_data->i2c_client->dev,
"%s Attempted to write to read-only attribute %s\n",
__func__, attr->attr.name);
return -EPERM;
}
struct time_keeping {
int id;
unsigned long long duration;
} __packed;
struct statistics {
int active; /* current index within keeper array */
int max; /* keeper array dimension */
bool clk_run;/* flag to indicate whether clock is ticking */
unsigned int flags; /* time keeping flags */
unsigned char abbr; /* abbreviation letter */
ktime_t start; /* time notch */
struct time_keeping keeper[0];
} __packed;
enum {
FREQ_DURATION, /* scanning frequencies duration */
FREQ_HOPPING, /* scanning frequencies hopping */
MODE_NMS, /* noise mitigation mode */
MODE_METAL_PLATE, /* metal plate detection */
STATISTICS_MAX
};
struct synaptics_dsx_stats {
bool enabled;
ktime_t uptime;
bool uptime_run;
unsigned long long uptime_dur;
struct statistics *stats[STATISTICS_MAX];
#define dur stats[FREQ_DURATION]
#define hop stats[FREQ_HOPPING]
#define nms stats[MODE_NMS]
#define mpd stats[MODE_METAL_PLATE]
} __packed;
struct synaptics_rmi4_exp_fn_ptr {
int (*read)(struct synaptics_rmi4_data *rmi4_data, unsigned short addr,
unsigned char *data, unsigned short length);
int (*write)(struct synaptics_rmi4_data *rmi4_data, unsigned short addr,
unsigned char *data, unsigned short length);
int (*enable)(struct synaptics_rmi4_data *rmi4_data, bool enable);
};
void synaptics_rmi4_new_function(enum exp_fn fn_type, bool insert,
int (*func_init)(struct synaptics_rmi4_data *rmi4_data),
void (*func_remove)(struct synaptics_rmi4_data *rmi4_data),
void (*func_attn)(struct synaptics_rmi4_data *rmi4_data,
unsigned char intr_mask),
int (*func_status)(struct synaptics_rmi4_data *rmi4_data),
enum ic_modes mode);
int synaptics_rmi4_scan_packet_reg_info(
struct synaptics_rmi4_data *rmi4_data,
unsigned short query_addr,
unsigned short regs_base_addr,
struct synaptics_rmi4_func_packet_regs *regs);
int synaptics_rmi4_read_packet_reg(
struct synaptics_rmi4_data *rmi4_data,
struct synaptics_rmi4_func_packet_regs *regs, int idx);
int synaptics_rmi4_read_packet_regs(
struct synaptics_rmi4_data *rmi4_data,
struct synaptics_rmi4_func_packet_regs *regs);
int alloc_buffer(struct temp_buffer *tb, size_t count);
static inline int secure_memcpy(unsigned char *dest, unsigned int dest_size,
const unsigned char *src, unsigned int src_size,
unsigned int count)
{
if (dest == NULL || src == NULL)
return -EINVAL;
if (count > dest_size || count > src_size)
return -EINVAL;
memcpy((void *)dest, (const void *)src, count);
return 0;
}
static inline int synaptics_rmi4_reg_read(
struct synaptics_rmi4_data *rmi4_data,
unsigned short addr,
unsigned char *data,
unsigned short len)
{
return rmi4_data->i2c_read(rmi4_data, addr, data, len);
}
static inline int synaptics_rmi4_reg_write(
struct synaptics_rmi4_data *rmi4_data,
unsigned short addr,
unsigned char *data,
unsigned short len)
{
return rmi4_data->i2c_write(rmi4_data, addr, data, len);
}
extern int FPS_register_notifier(struct notifier_block *nb,
unsigned long stype, bool report);
extern int FPS_unregister_notifier(struct notifier_block *nb,
unsigned long stype);
#endif

View file

@ -0,0 +1,791 @@
/*
* Synaptics DSX touchscreen driver
*
* Copyright (C) 2012 Synaptics Incorporated
*
* Copyright (C) 2012 Alexandra Chin <alexandra.chin@tw.synaptics.com>
* Copyright (C) 2012 Scott Lin <scott.lin@tw.synaptics.com>
*
* 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/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/input.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/input/synaptics_dsx_mmi.h>
#include "synaptics_dsx_i2c.h"
#define CHAR_DEVICE_NAME "rmi"
#define DEVICE_CLASS_NAME "rmidev"
#define DEV_NUMBER 1
#define REG_ADDR_LIMIT 0xFFFF
static ssize_t rmidev_sysfs_data_show(struct file *data_file,
struct kobject *kobj, struct bin_attribute *attributes,
char *buf, loff_t pos, size_t count);
static ssize_t rmidev_sysfs_data_store(struct file *data_file,
struct kobject *kobj, struct bin_attribute *attributes,
char *buf, loff_t pos, size_t count);
static ssize_t rmidev_sysfs_open_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count);
static ssize_t rmidev_sysfs_release_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count);
static ssize_t rmidev_sysfs_address_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count);
static ssize_t rmidev_sysfs_length_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count);
static ssize_t rmidev_sysfs_attn_state_show(struct device *dev,
struct device_attribute *attr, char *buf);
struct rmidev_handle {
dev_t dev_no;
unsigned short address;
unsigned int length;
struct device dev;
struct synaptics_rmi4_data *rmi4_data;
struct synaptics_rmi4_exp_fn_ptr *fn_ptr;
struct kobject *sysfs_dir;
void *data;
};
struct rmidev_data {
int ref_count;
struct cdev main_dev;
struct class *device_class;
struct mutex file_mutex;
struct rmidev_handle *rmi_dev;
struct temp_buffer data_buf;
};
static struct bin_attribute attr_data = {
.attr = {
.name = "data",
.mode = (S_IRUGO | S_IWUSR | S_IWGRP),
},
.size = 0,
.read = rmidev_sysfs_data_show,
.write = rmidev_sysfs_data_store,
};
static struct device_attribute attrs[] = {
__ATTR(open, S_IWUSR | S_IWGRP,
NULL,
rmidev_sysfs_open_store),
__ATTR(release, S_IWUSR | S_IWGRP,
NULL,
rmidev_sysfs_release_store),
__ATTR(address, S_IWUSR | S_IWGRP,
NULL,
rmidev_sysfs_address_store),
__ATTR(length, S_IWUSR | S_IWGRP,
NULL,
rmidev_sysfs_length_store),
__ATTR(attn_state, S_IRUSR | S_IRGRP,
rmidev_sysfs_attn_state_show,
NULL),
};
static int rmidev_major_num;
static struct class *rmidev_device_class;
static struct rmidev_handle *rmidev;
static struct completion remove_complete;
static ssize_t rmidev_sysfs_data_show(struct file *data_file,
struct kobject *kobj, struct bin_attribute *attributes,
char *buf, loff_t pos, size_t count)
{
int retval;
unsigned int data_length = rmidev->length;
if (data_length > (REG_ADDR_LIMIT - rmidev->address))
data_length = REG_ADDR_LIMIT - rmidev->address;
if (count < data_length) {
dev_err(&rmidev->rmi4_data->i2c_client->dev,
"%s: Not enough space (%zu bytes) in buffer\n",
__func__, count);
return -EINVAL;
}
if (data_length) {
retval = rmidev->fn_ptr->read(rmidev->rmi4_data,
rmidev->address,
(unsigned char *)buf,
data_length);
if (retval < 0) {
dev_err(&rmidev->rmi4_data->i2c_client->dev,
"%s: Failed to read data\n",
__func__);
return retval;
}
} else {
return -EINVAL;
}
return data_length;
}
static ssize_t rmidev_sysfs_data_store(struct file *data_file,
struct kobject *kobj, struct bin_attribute *attributes,
char *buf, loff_t pos, size_t count)
{
int retval;
unsigned int data_length = rmidev->length;
if (data_length > (REG_ADDR_LIMIT - rmidev->address))
data_length = REG_ADDR_LIMIT - rmidev->address;
if (data_length) {
retval = rmidev->fn_ptr->write(rmidev->rmi4_data,
rmidev->address,
(unsigned char *)buf,
data_length);
if (retval < 0) {
dev_err(&rmidev->rmi4_data->i2c_client->dev,
"%s: Failed to write data\n",
__func__);
return retval;
}
} else {
return -EINVAL;
}
return count;
}
static ssize_t rmidev_sysfs_open_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
unsigned int input;
if (sscanf(buf, "%u", &input) != 1)
return -EINVAL;
if (input != 1)
return -EINVAL;
rmidev->fn_ptr->enable(rmidev->rmi4_data, false);
dev_dbg(&rmidev->rmi4_data->i2c_client->dev,
"%s: Attention interrupt disabled\n",
__func__);
return count;
}
static ssize_t rmidev_sysfs_release_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
unsigned int input;
if (sscanf(buf, "%u", &input) != 1)
return -EINVAL;
if (input != 1)
return -EINVAL;
rmidev->fn_ptr->enable(rmidev->rmi4_data, true);
dev_dbg(&rmidev->rmi4_data->i2c_client->dev,
"%s: Attention interrupt enabled\n",
__func__);
return count;
}
static ssize_t rmidev_sysfs_address_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
unsigned int input;
if (sscanf(buf, "%u", &input) != 1)
return -EINVAL;
if (input > REG_ADDR_LIMIT)
return -EINVAL;
rmidev->address = (unsigned short)input;
return count;
}
static ssize_t rmidev_sysfs_length_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
unsigned int input;
if (sscanf(buf, "%u", &input) != 1)
return -EINVAL;
if (input > REG_ADDR_LIMIT)
return -EINVAL;
rmidev->length = input;
return count;
}
static ssize_t rmidev_sysfs_attn_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int attn_state;
const struct synaptics_dsx_platform_data *platform_data =
rmidev->rmi4_data->board;
attn_state = gpio_get_value(platform_data->irq_gpio);
return snprintf(buf, PAGE_SIZE, "%u\n", attn_state);
}
/*
* rmidev_llseek - used to set up register address
*
* @filp: file structure for seek
* @off: offset
* if whence == SEEK_SET,
* high 16 bits: page address
* low 16 bits: register address
* if whence == SEEK_CUR,
* offset from current position
* if whence == SEEK_END,
* offset from end position (0xFFFF)
* @whence: SEEK_SET, SEEK_CUR, or SEEK_END
*/
static loff_t rmidev_llseek(struct file *filp, loff_t off, int whence)
{
loff_t newpos;
struct rmidev_data *dev_data = filp->private_data;
if (IS_ERR(dev_data)) {
pr_err("%s: Pointer of char device data is invalid", __func__);
return -EBADF;
}
mutex_lock(&(dev_data->file_mutex));
switch (whence) {
case SEEK_SET:
newpos = off;
break;
case SEEK_CUR:
newpos = filp->f_pos + off;
break;
case SEEK_END:
newpos = REG_ADDR_LIMIT + off;
break;
default:
newpos = -EINVAL;
goto clean_up;
}
if (newpos < 0 || newpos > REG_ADDR_LIMIT) {
dev_err(&rmidev->rmi4_data->i2c_client->dev,
"%s: New position 0x%04x is invalid\n",
__func__, (unsigned int)newpos);
newpos = -EINVAL;
goto clean_up;
}
filp->f_pos = newpos;
clean_up:
mutex_unlock(&(dev_data->file_mutex));
return newpos;
}
/*
* rmidev_read: - use to read data from rmi device
*
* @filp: file structure for read
* @buf: user space buffer pointer
* @count: number of bytes to read
* @f_pos: offset (starting register address)
*/
static ssize_t rmidev_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
ssize_t retval;
struct rmidev_data *dev_data = filp->private_data;
struct temp_buffer *tb;
if (IS_ERR(dev_data)) {
pr_err("%s: Pointer of char device data is invalid", __func__);
return -EBADF;
}
mutex_lock(&(dev_data->file_mutex));
if (count > (REG_ADDR_LIMIT - *f_pos))
count = REG_ADDR_LIMIT - *f_pos;
if (count == 0) {
retval = 0;
goto clean_up;
}
if (*f_pos > REG_ADDR_LIMIT) {
retval = -EFAULT;
goto clean_up;
}
tb = &dev_data->data_buf;
if (tb->buf_size < count && alloc_buffer(tb, count) != 0) {
retval = -ENOMEM;
goto clean_up;
}
retval = rmidev->fn_ptr->read(rmidev->rmi4_data,
*f_pos,
tb->buf,
count);
if (retval < 0)
goto clean_up;
if (copy_to_user(buf, tb->buf, count))
retval = -EFAULT;
else
*f_pos += retval;
clean_up:
mutex_unlock(&(dev_data->file_mutex));
return retval;
}
/*
* rmidev_write: - used to write data to rmi device
*
* @filep: file structure for write
* @buf: user space buffer pointer
* @count: number of bytes to write
* @f_pos: offset (starting register address)
*/
static ssize_t rmidev_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
ssize_t retval;
struct rmidev_data *dev_data = filp->private_data;
struct temp_buffer *tb;
if (IS_ERR(dev_data)) {
pr_err("%s: Pointer of char device data is invalid", __func__);
return -EBADF;
}
mutex_lock(&(dev_data->file_mutex));
if (*f_pos > REG_ADDR_LIMIT) {
retval = -EFAULT;
goto clean_up;
}
if (count > (REG_ADDR_LIMIT - *f_pos))
count = REG_ADDR_LIMIT - *f_pos;
if (count == 0) {
retval = 0;
goto clean_up;
}
tb = &dev_data->data_buf;
if (tb->buf_size < count && alloc_buffer(tb, count) != 0) {
retval = -ENOMEM;
goto clean_up;
}
if (copy_from_user(tb->buf, buf, count)) {
retval = -EFAULT;
goto clean_up;
}
retval = rmidev->fn_ptr->write(rmidev->rmi4_data,
*f_pos,
tb->buf,
count);
if (retval >= 0)
*f_pos += retval;
clean_up:
mutex_unlock(&(dev_data->file_mutex));
return retval;
}
/*
* rmidev_open: enable access to rmi device
* @inp: inode struture
* @filp: file structure
*/
static int rmidev_open(struct inode *inp, struct file *filp)
{
int retval = 0;
struct rmidev_data *dev_data =
container_of(inp->i_cdev, struct rmidev_data, main_dev);
if (!dev_data)
return -EACCES;
filp->private_data = dev_data;
mutex_lock(&(dev_data->file_mutex));
rmidev->fn_ptr->enable(rmidev->rmi4_data, false);
dev_dbg(&rmidev->rmi4_data->i2c_client->dev,
"%s: Attention interrupt disabled\n",
__func__);
if (dev_data->ref_count < 1)
dev_data->ref_count++;
else
retval = -EACCES;
mutex_unlock(&(dev_data->file_mutex));
return retval;
}
/*
* rmidev_release: - release access to rmi device
* @inp: inode structure
* @filp: file structure
*/
static int rmidev_release(struct inode *inp, struct file *filp)
{
struct rmidev_data *dev_data =
container_of(inp->i_cdev, struct rmidev_data, main_dev);
if (!dev_data)
return -EACCES;
mutex_lock(&(dev_data->file_mutex));
dev_data->ref_count--;
if (dev_data->ref_count < 0)
dev_data->ref_count = 0;
rmidev->fn_ptr->enable(rmidev->rmi4_data, true);
dev_dbg(&rmidev->rmi4_data->i2c_client->dev,
"%s: Attention interrupt enabled\n",
__func__);
mutex_unlock(&(dev_data->file_mutex));
return 0;
}
static const struct file_operations rmidev_fops = {
.owner = THIS_MODULE,
.llseek = rmidev_llseek,
.read = rmidev_read,
.write = rmidev_write,
.open = rmidev_open,
.release = rmidev_release,
};
static void rmidev_device_cleanup(struct rmidev_data *dev_data)
{
dev_t devno;
if (dev_data) {
devno = dev_data->main_dev.dev;
if (dev_data->device_class)
device_destroy(dev_data->device_class, devno);
cdev_del(&dev_data->main_dev);
unregister_chrdev_region(devno, 1);
dev_dbg(&rmidev->rmi4_data->i2c_client->dev,
"%s: rmidev device removed\n",
__func__);
}
return;
}
static char *rmi_char_devnode(struct device *dev, umode_t *mode)
{
if (!mode)
return NULL;
*mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
return kasprintf(GFP_KERNEL, "rmi/%s", dev_name(dev));
}
static int rmidev_create_device_class(void)
{
rmidev_device_class = class_create(THIS_MODULE, DEVICE_CLASS_NAME);
if (IS_ERR(rmidev_device_class)) {
pr_err("%s: Failed to create /dev/%s\n",
__func__, CHAR_DEVICE_NAME);
return -ENODEV;
}
rmidev_device_class->devnode = rmi_char_devnode;
return 0;
}
static int rmidev_init_device(struct synaptics_rmi4_data *rmi4_data)
{
int retval;
dev_t dev_no;
unsigned char attr_count;
struct rmidev_data *dev_data;
struct device *device_ptr;
rmidev = kzalloc(sizeof(*rmidev), GFP_KERNEL);
if (!rmidev) {
dev_err(&rmi4_data->i2c_client->dev,
"%s: Failed to alloc mem for rmidev\n",
__func__);
retval = -ENOMEM;
goto err_rmidev;
}
rmidev->fn_ptr = kzalloc(sizeof(*(rmidev->fn_ptr)), GFP_KERNEL);
if (!rmidev) {
dev_err(&rmi4_data->i2c_client->dev,
"%s: Failed to alloc mem for fn_ptr\n",
__func__);
retval = -ENOMEM;
goto err_fn_ptr;
}
rmidev->fn_ptr->read = rmi4_data->i2c_read;
rmidev->fn_ptr->write = rmi4_data->i2c_write;
rmidev->fn_ptr->enable = rmi4_data->irq_enable;
rmidev->rmi4_data = rmi4_data;
retval = rmidev_create_device_class();
if (retval < 0) {
dev_err(&rmi4_data->i2c_client->dev,
"%s: Failed to create device class\n",
__func__);
goto err_device_class;
}
if (rmidev_major_num) {
dev_no = MKDEV(rmidev_major_num, DEV_NUMBER);
retval = register_chrdev_region(dev_no, 1, CHAR_DEVICE_NAME);
} else {
retval = alloc_chrdev_region(&dev_no, 0, 1, CHAR_DEVICE_NAME);
if (retval < 0) {
dev_err(&rmi4_data->i2c_client->dev,
"%s: Failed to allocate char device region\n",
__func__);
goto err_device_region;
}
rmidev_major_num = MAJOR(dev_no);
dev_dbg(&rmi4_data->i2c_client->dev,
"%s: Major number of rmidev = %d\n",
__func__, rmidev_major_num);
}
dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL);
if (!dev_data) {
dev_err(&rmi4_data->i2c_client->dev,
"%s: Failed to alloc mem for dev_data\n",
__func__);
retval = -ENOMEM;
goto err_dev_data;
}
mutex_init(&dev_data->file_mutex);
dev_data->rmi_dev = rmidev;
rmidev->data = dev_data;
cdev_init(&dev_data->main_dev, &rmidev_fops);
retval = cdev_add(&dev_data->main_dev, dev_no, 1);
if (retval < 0) {
dev_err(&rmi4_data->i2c_client->dev,
"%s: Failed to add rmi char device\n",
__func__);
goto err_char_device;
}
dev_set_name(&rmidev->dev, "rmidev%d", MINOR(dev_no));
dev_data->device_class = rmidev_device_class;
device_ptr = device_create(dev_data->device_class, NULL, dev_no,
NULL, CHAR_DEVICE_NAME"%d", MINOR(dev_no));
if (IS_ERR(device_ptr)) {
dev_err(&rmi4_data->i2c_client->dev,
"%s: Failed to create rmi char device\n",
__func__);
retval = -ENODEV;
goto err_char_device;
}
retval = gpio_export(rmi4_data->board->irq_gpio, false);
if (retval < 0) {
dev_err(&rmi4_data->i2c_client->dev,
"%s: Failed to export attention gpio\n",
__func__);
} else {
retval = gpio_export_link(&(rmi4_data->i2c_client->dev),
"attn", rmi4_data->board->irq_gpio);
if (retval < 0) {
dev_err(&rmi4_data->i2c_client->dev,
"%s Failed to create gpio symlink\n",
__func__);
} else {
dev_dbg(&rmi4_data->i2c_client->dev,
"%s: Exported attention gpio %d\n",
__func__, rmi4_data->board->irq_gpio);
}
}
rmidev->sysfs_dir = kobject_create_and_add("rmidev",
&rmi4_data->i2c_client->dev.kobj);
if (!rmidev->sysfs_dir) {
dev_err(&rmi4_data->i2c_client->dev,
"%s: Failed to create sysfs directory\n",
__func__);
goto err_sysfs_dir;
}
retval = sysfs_create_bin_file(rmidev->sysfs_dir,
&attr_data);
if (retval < 0) {
dev_err(&rmi4_data->i2c_client->dev,
"%s: Failed to create sysfs bin file\n",
__func__);
goto err_sysfs_bin;
}
for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) {
retval = sysfs_create_file(rmidev->sysfs_dir,
&attrs[attr_count].attr);
if (retval < 0) {
dev_err(&rmi4_data->i2c_client->dev,
"%s: Failed to create sysfs attributes\n",
__func__);
retval = -ENODEV;
goto err_sysfs_attrs;
}
}
return 0;
err_sysfs_attrs:
for (attr_count--; attr_count >= 0; attr_count--)
sysfs_remove_file(rmidev->sysfs_dir, &attrs[attr_count].attr);
sysfs_remove_bin_file(rmidev->sysfs_dir, &attr_data);
err_sysfs_bin:
kobject_put(rmidev->sysfs_dir);
err_sysfs_dir:
err_char_device:
rmidev_device_cleanup(dev_data);
kfree(dev_data);
err_dev_data:
unregister_chrdev_region(dev_no, 1);
err_device_region:
class_destroy(rmidev_device_class);
err_device_class:
kfree(rmidev->fn_ptr);
err_fn_ptr:
kfree(rmidev);
err_rmidev:
return retval;
}
static void rmidev_remove_device(struct synaptics_rmi4_data *rmi4_data)
{
unsigned char attr_count;
struct rmidev_data *dev_data;
if (!rmidev)
return;
for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++)
sysfs_remove_file(rmidev->sysfs_dir, &attrs[attr_count].attr);
sysfs_remove_bin_file(rmidev->sysfs_dir, &attr_data);
kobject_put(rmidev->sysfs_dir);
dev_data = rmidev->data;
if (dev_data) {
rmidev_device_cleanup(dev_data);
kfree(dev_data);
}
unregister_chrdev_region(rmidev->dev_no, 1);
class_destroy(rmidev_device_class);
kfree(rmidev->fn_ptr);
kfree(rmidev);
complete(&remove_complete);
return;
}
static int __init rmidev_module_init(void)
{
synaptics_rmi4_new_function(RMI_DEV, true,
rmidev_init_device,
rmidev_remove_device,
NULL, NULL, IC_MODE_ANY);
return 0;
}
static void __exit rmidev_module_exit(void)
{
init_completion(&remove_complete);
synaptics_rmi4_new_function(RMI_DEV, false,
rmidev_init_device,
rmidev_remove_device,
NULL, NULL, IC_MODE_ANY);
wait_for_completion(&remove_complete);
return;
}
module_init(rmidev_module_init);
module_exit(rmidev_module_exit);
MODULE_AUTHOR("Synaptics, Inc.");
MODULE_DESCRIPTION("Synaptics DSX RMI Dev Module");
MODULE_LICENSE("GPL v2");
MODULE_VERSION(SYNAPTICS_DSX_DRIVER_VERSION);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,78 @@
/*
* Synaptics DSX touchscreen driver
*
* Copyright (C) 2012 Synaptics Incorporated
*
* Copyright (C) 2012 Alexandra Chin <alexandra.chin@tw.synaptics.com>
* Copyright (C) 2012 Scott Lin <scott.lin@tw.synaptics.com>
*
* 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 _SYNAPTICS_DSX_H_
#define _SYNAPTICS_DSX_H_
/*
* struct synaptics_dsx_cap_button_map - 0d button map
* @nbuttons: number of 0d buttons
* @map: pointer to array of button types
*/
struct synaptics_dsx_cap_button_map {
unsigned char nbuttons;
unsigned char *map;
};
/*
* struct synaptics_dsx_platform_data - dsx platform data
* @x_flip: x flip flag
* @y_flip: y flip flag
* @i2c_pull_up: pull up i2c bus with regulator
* @power_down_enable: enable complete regulator shutdown in suspend
* @irq_gpio: attention interrupt gpio
* @irq_flags: flags used by the irq
* @reset_flags: flags used by reset line
* @reset_gpio: reset gpio
* @disp_maxx: display panel maximum values on the x
* @disp_maxy: display panel maximum values on the y
* @disp_minx: display panel minimum values on the x
* @disp_miny: display panel minimum values on the y
* @panel_maxx: touch panel maximum values on the x
* @panel_maxy: touch panel maximum values on the y
* @panel_minx: touch panel minimum values on the x
* @panel_miny: touch panel minimum values on the y
* @reset_delay: reset delay
* @gpio_config: pointer to gpio configuration function
* @cap_button_map: pointer to 0d button map
*/
struct synaptics_dsx_platform_data {
bool x_flip;
bool y_flip;
bool i2c_pull_up;
bool power_down_enable;
bool disable_gpios;
bool do_lockdown;
bool regulator_en;
unsigned irq_gpio;
unsigned long irq_flags;
unsigned reset_gpio;
unsigned panel_minx;
unsigned panel_miny;
unsigned panel_maxx;
unsigned panel_maxy;
unsigned disp_minx;
unsigned disp_miny;
unsigned reset_delay;
int (*gpio_config)(struct synaptics_dsx_platform_data *pdata,
bool configure);
struct synaptics_dsx_cap_button_map *cap_button_map;
};
#endif